mirror of
https://github.com/ibm-s390-linux/s390-tools.git
synced 2026-08-05 02:14:52 +00:00
Compare commits
68 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6f15ed3264 | ||
|
|
0d2b5af007 | ||
|
|
9e7a8f48e8 | ||
|
|
98f7a0569c | ||
|
|
551f66282e | ||
|
|
3d2ba5aaed | ||
|
|
94942a48ab | ||
|
|
ab8984a7a3 | ||
|
|
4990f643c1 | ||
|
|
34bef977e8 | ||
|
|
f36c34038b | ||
|
|
bfd0e12d22 | ||
|
|
1450f85ada | ||
|
|
2a0f1e6977 | ||
|
|
0f433b1142 | ||
|
|
ab6bcad263 | ||
|
|
e40a3e0621 | ||
|
|
e56acf4f14 | ||
|
|
17977eda30 | ||
|
|
459a257568 | ||
|
|
fb65b53b9b | ||
|
|
c8d4062f73 | ||
|
|
c8e0992814 | ||
|
|
c0a12b29d0 | ||
|
|
02dded11a5 | ||
|
|
b71279cda5 | ||
|
|
c70477f8c6 | ||
|
|
9b51b8b882 | ||
|
|
48539596ef | ||
|
|
cafa99774c | ||
|
|
b5f7ac95d8 | ||
|
|
e984b97db0 | ||
|
|
64d4e02b4f | ||
|
|
90ddef5a41 | ||
|
|
58ef99f76b | ||
|
|
43c34956fb | ||
|
|
d7dee1b9d3 | ||
|
|
94a38ebc3a | ||
|
|
0764460eaf | ||
|
|
6fd02279da | ||
|
|
6274294bc5 | ||
|
|
27708026d4 | ||
|
|
849aa5b105 | ||
|
|
0be83bfbba | ||
|
|
f8592be43d | ||
|
|
647ad51423 | ||
|
|
73f51e45a8 | ||
|
|
bc4f455151 | ||
|
|
b4b5e0b6aa | ||
|
|
9927023680 | ||
|
|
9b2fb1d4d2 | ||
|
|
689b894506 | ||
|
|
06a30ae529 | ||
|
|
ed106d7f28 | ||
|
|
9d08fd8c7e | ||
|
|
7e8126704b | ||
|
|
d96767ee45 | ||
|
|
63f31bf73e | ||
|
|
7ecfe2353f | ||
|
|
7bec672c7e | ||
|
|
5aac5deb75 | ||
|
|
54e016ae71 | ||
|
|
6b53378839 | ||
|
|
1266f86444 | ||
|
|
231c02cdeb | ||
|
|
454a8d9d7b | ||
|
|
bbe92b9cd3 | ||
|
|
7bb41732fb |
@@ -1,5 +1,3 @@
|
|||||||
parm
|
parm
|
||||||
parms
|
parms
|
||||||
crate
|
crate
|
||||||
ser
|
|
||||||
deriver
|
|
||||||
|
|||||||
35
.pre-commit-config.yaml
Normal file
35
.pre-commit-config.yaml
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
---
|
||||||
|
exclude: \.(crt|crl)$
|
||||||
|
repos:
|
||||||
|
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||||
|
rev: v4.1.0
|
||||||
|
hooks:
|
||||||
|
- id: check-merge-conflict
|
||||||
|
- id: end-of-file-fixer
|
||||||
|
- id: mixed-line-ending
|
||||||
|
- id: trailing-whitespace
|
||||||
|
- id: check-executables-have-shebangs
|
||||||
|
- id: check-shebang-scripts-are-executable
|
||||||
|
exclude_types: ['rust']
|
||||||
|
- repo: local
|
||||||
|
hooks:
|
||||||
|
- id: git-clang-format
|
||||||
|
name: git-clang-format
|
||||||
|
description: Run git-clang-format
|
||||||
|
entry: git
|
||||||
|
args: [clang-format, --staged, --]
|
||||||
|
pass_filenames: true
|
||||||
|
language: system
|
||||||
|
require_serial: true
|
||||||
|
minimum_pre_commit_version: "2.9.0"
|
||||||
|
types_or: [c++, c]
|
||||||
|
- repo: https://github.com/codespell-project/codespell
|
||||||
|
rev: v2.2.1
|
||||||
|
hooks:
|
||||||
|
- id: codespell
|
||||||
|
exclude_types: ['rust']
|
||||||
|
- repo: https://github.com/jumanjihouse/pre-commit-hooks
|
||||||
|
rev: 3.0.0
|
||||||
|
hooks:
|
||||||
|
- id: shellcheck
|
||||||
|
args: ["--external-sources"]
|
||||||
@@ -36,6 +36,7 @@ List of all individuals having contributed content to s390-tools
|
|||||||
- Farhan Ali
|
- Farhan Ali
|
||||||
- Fedor Loshakov
|
- Fedor Loshakov
|
||||||
- Felix Beck
|
- Felix Beck
|
||||||
|
- Finn Callies
|
||||||
- Frank Blaschka
|
- Frank Blaschka
|
||||||
- Frank Heimes
|
- Frank Heimes
|
||||||
- Frank Munzert
|
- Frank Munzert
|
||||||
@@ -127,6 +128,7 @@ List of all individuals having contributed content to s390-tools
|
|||||||
- Thomas Spatzier
|
- Thomas Spatzier
|
||||||
- Thomas Weber
|
- Thomas Weber
|
||||||
- Thorsten Winkler
|
- Thorsten Winkler
|
||||||
|
- Tobias Huschle
|
||||||
- Tuan Hoang
|
- Tuan Hoang
|
||||||
- Ursula Braun
|
- Ursula Braun
|
||||||
- Utz Bacher
|
- Utz Bacher
|
||||||
@@ -137,3 +139,4 @@ List of all individuals having contributed content to s390-tools
|
|||||||
- Volker Sameske
|
- Volker Sameske
|
||||||
- Wenjia Zhang
|
- Wenjia Zhang
|
||||||
- Wolfgang Taphorn
|
- Wolfgang Taphorn
|
||||||
|
- Yaakov Selkowitz
|
||||||
|
|||||||
26
CHANGELOG.md
26
CHANGELOG.md
@@ -1,6 +1,31 @@
|
|||||||
Release history for s390-tools (MIT version)
|
Release history for s390-tools (MIT version)
|
||||||
--------------------------------------------
|
--------------------------------------------
|
||||||
|
|
||||||
|
* __v2.31.0 (2024-02-02)__
|
||||||
|
|
||||||
|
For Linux kernel version: 6.7
|
||||||
|
|
||||||
|
General:
|
||||||
|
- common.mak: Set default C/C++ standard to gnu11/gnu++11
|
||||||
|
|
||||||
|
Add new tools / libraries:
|
||||||
|
- pvapconfig: Tool to automatically configure APQNs in SE KVM guests
|
||||||
|
- s390-tools: Provide pre-commit configuration
|
||||||
|
|
||||||
|
Changes of existing tools:
|
||||||
|
- cpuplugd: Adjust to CPU 0 being no longer hotpluggable
|
||||||
|
- dbginfo.sh: Check for Dynamic Partition Mode
|
||||||
|
- dbginfo.sh: Update man page and copyright
|
||||||
|
- rust/pv: Add user-data signing and verifying
|
||||||
|
- rust/pvsecret: Add user defined signatures and verifications
|
||||||
|
- zdev/dracut: Consolidate device configuration
|
||||||
|
|
||||||
|
Bug Fixes:
|
||||||
|
- dbginfo.sh: Fix relative path on script copy
|
||||||
|
- libkmipclient: Fix build with libxml2-2.12.0
|
||||||
|
- pvsecret: Fix panic if empty file is used as host key document
|
||||||
|
- rust/pv: Fix 'elided_lifetimes_in_associated_constant' warning
|
||||||
|
|
||||||
* __v2.30.0 (2023-12-01)__
|
* __v2.30.0 (2023-12-01)__
|
||||||
|
|
||||||
For Linux kernel version: 6.6
|
For Linux kernel version: 6.6
|
||||||
@@ -22,6 +47,7 @@ Release history for s390-tools (MIT version)
|
|||||||
- zipl/boot: compile the bootloaders only if HOST_ARCH is s390x
|
- zipl/boot: compile the bootloaders only if HOST_ARCH is s390x
|
||||||
- zipl: Add --no-compress option to zipl command
|
- zipl: Add --no-compress option to zipl command
|
||||||
- zkey: Also check for deconfigured and check-stopped cards
|
- zkey: Also check for deconfigured and check-stopped cards
|
||||||
|
- dbginfo.sh: fix relative path on script copy
|
||||||
|
|
||||||
Bug Fixes:
|
Bug Fixes:
|
||||||
- ap_tools/ap-check: handle get-attributes between pre and post event
|
- ap_tools/ap-check: handle get-attributes between pre and post event
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ endif
|
|||||||
# The variable "DISTRELEASE" should be overwritten in rpm spec files with:
|
# The variable "DISTRELEASE" should be overwritten in rpm spec files with:
|
||||||
# "make DISTRELEASE=%{release}" and "make install DISTRELEASE=%{release}"
|
# "make DISTRELEASE=%{release}" and "make install DISTRELEASE=%{release}"
|
||||||
VERSION = 2
|
VERSION = 2
|
||||||
RELEASE = 30
|
RELEASE = 31
|
||||||
PATCHLEVEL = 0
|
PATCHLEVEL = 0
|
||||||
DISTRELEASE = build-$(shell date +%Y%m%d)
|
DISTRELEASE = build-$(shell date +%Y%m%d)
|
||||||
S390_TOOLS_RELEASE = $(VERSION).$(RELEASE).$(PATCHLEVEL)-$(DISTRELEASE)
|
S390_TOOLS_RELEASE = $(VERSION).$(RELEASE).$(PATCHLEVEL)-$(DISTRELEASE)
|
||||||
@@ -304,7 +304,7 @@ ZFCPDUMP_FLAVOR = zfcpdump
|
|||||||
export ZFCPDUMP_DIR ZFCPDUMP_IMAGE ZFCPDUMP_INITRD ZFCPDUMP_FLAVOR
|
export ZFCPDUMP_DIR ZFCPDUMP_IMAGE ZFCPDUMP_INITRD ZFCPDUMP_FLAVOR
|
||||||
|
|
||||||
CFLAGS ?= $(DEFAULT_CFLAGS) $(OPT_FLAGS)
|
CFLAGS ?= $(DEFAULT_CFLAGS) $(OPT_FLAGS)
|
||||||
CFLAGS_FOR_BUILD ?= $(DEFAULT_CFLAGS) $(OPT_FLAGS)
|
CFLAGS_FOR_BUILD ?= -std=gnu11 $(DEFAULT_CFLAGS) $(OPT_FLAGS)
|
||||||
CPPFLAGS ?= $(DEFAULT_CPPFLAGS)
|
CPPFLAGS ?= $(DEFAULT_CPPFLAGS)
|
||||||
LDFLAGS ?= $(DEFAULT_LDFLAGS)
|
LDFLAGS ?= $(DEFAULT_LDFLAGS)
|
||||||
|
|
||||||
@@ -313,14 +313,14 @@ ALL_CFLAGS = -DS390_TOOLS_RELEASE=$(S390_TOOLS_RELEASE) \
|
|||||||
-DS390_TOOLS_DATADIR=$(TOOLS_DATADIR) \
|
-DS390_TOOLS_DATADIR=$(TOOLS_DATADIR) \
|
||||||
-DS390_TOOLS_SYSCONFDIR=$(SYSCONFDIR) \
|
-DS390_TOOLS_SYSCONFDIR=$(SYSCONFDIR) \
|
||||||
-DS390_TOOLS_BINDIR=$(BINDIR) \
|
-DS390_TOOLS_BINDIR=$(BINDIR) \
|
||||||
$(CFLAGS)
|
-std=gnu11 $(CFLAGS)
|
||||||
CXXFLAGS ?= $(DEFAULT_CFLAGS) $(OPT_FLAGS)
|
CXXFLAGS ?= $(DEFAULT_CFLAGS) $(OPT_FLAGS)
|
||||||
ALL_CXXFLAGS = -DS390_TOOLS_RELEASE=$(S390_TOOLS_RELEASE) \
|
ALL_CXXFLAGS = -DS390_TOOLS_RELEASE=$(S390_TOOLS_RELEASE) \
|
||||||
-DS390_TOOLS_LIBDIR=$(TOOLS_LIBDIR) \
|
-DS390_TOOLS_LIBDIR=$(TOOLS_LIBDIR) \
|
||||||
-DS390_TOOLS_DATADIR=$(TOOLS_DATADIR) \
|
-DS390_TOOLS_DATADIR=$(TOOLS_DATADIR) \
|
||||||
-DS390_TOOLS_SYSCONFDIR=$(SYSCONFDIR) \
|
-DS390_TOOLS_SYSCONFDIR=$(SYSCONFDIR) \
|
||||||
-DS390_TOOLS_BINDIR=$(BINDIR) \
|
-DS390_TOOLS_BINDIR=$(BINDIR) \
|
||||||
$(CXXFLAGS)
|
-std=gnu++11 $(CXXFLAGS)
|
||||||
ALL_CPPFLAGS = -I $(rootdir)include $(CPPFLAGS)
|
ALL_CPPFLAGS = -I $(rootdir)include $(CPPFLAGS)
|
||||||
ALL_LDFLAGS = $(LDFLAGS)
|
ALL_LDFLAGS = $(LDFLAGS)
|
||||||
|
|
||||||
|
|||||||
@@ -355,13 +355,13 @@ PCKMO DES,
|
|||||||
.IP \(bu
|
.IP \(bu
|
||||||
PCKMO 2key TDES,
|
PCKMO 2key TDES,
|
||||||
.IP \(bu
|
.IP \(bu
|
||||||
PCMKO TDES,
|
PCKMO TDES,
|
||||||
.IP \(bu
|
.IP \(bu
|
||||||
PCKMO AES 128bit,
|
PCKMO AES 128bit,
|
||||||
.IP \(bu
|
.IP \(bu
|
||||||
PCKMO AES 192bit,
|
PCKMO AES 192bit,
|
||||||
.IP \(bu
|
.IP \(bu
|
||||||
PCMKO AES 256bit,
|
PCKMO AES 256bit,
|
||||||
.IP \(bu
|
.IP \(bu
|
||||||
PCKMO ECC P256,
|
PCKMO ECC P256,
|
||||||
.IP \(bu
|
.IP \(bu
|
||||||
|
|||||||
@@ -204,10 +204,10 @@ static const char *const pai_str[] = {
|
|||||||
[142] = "KDSA EdDSA Sign Ed448 protected key",
|
[142] = "KDSA EdDSA Sign Ed448 protected key",
|
||||||
[143] = "PCKMO DES",
|
[143] = "PCKMO DES",
|
||||||
[144] = "PCKMO 2key TDES",
|
[144] = "PCKMO 2key TDES",
|
||||||
[145] = "PCMKO TDES",
|
[145] = "PCKMO TDES",
|
||||||
[146] = "PCKMO AES 128bit",
|
[146] = "PCKMO AES 128bit",
|
||||||
[147] = "PCKMO AES 192bit",
|
[147] = "PCKMO AES 192bit",
|
||||||
[148] = "PCMKO AES 256bit",
|
[148] = "PCKMO AES 256bit",
|
||||||
[149] = "PCKMO ECC P256",
|
[149] = "PCKMO ECC P256",
|
||||||
[150] = "PCKMO ECC P384",
|
[150] = "PCKMO ECC P384",
|
||||||
[151] = "PCKMO ECC P521",
|
[151] = "PCKMO ECC P521",
|
||||||
|
|||||||
18
cpumf/pai.c
18
cpumf/pai.c
@@ -320,7 +320,7 @@ static void readmap(int fd)
|
|||||||
* ring buffer per event, sleep some short time and always read all
|
* ring buffer per event, sleep some short time and always read all
|
||||||
* ring buffer for new contents.
|
* ring buffer for new contents.
|
||||||
*/
|
*/
|
||||||
static void collect(unsigned long cnt)
|
static int collect(unsigned long cnt)
|
||||||
{
|
{
|
||||||
fd_set r_fds, e_fds, a_fds;
|
fd_set r_fds, e_fds, a_fds;
|
||||||
struct pai_event *p;
|
struct pai_event *p;
|
||||||
@@ -328,6 +328,7 @@ static void collect(unsigned long cnt)
|
|||||||
int rc, max_fd;
|
int rc, max_fd;
|
||||||
|
|
||||||
do {
|
do {
|
||||||
|
rc = -1;
|
||||||
max_fd = -1;
|
max_fd = -1;
|
||||||
tv.tv_sec = read_interval / 1000;
|
tv.tv_sec = read_interval / 1000;
|
||||||
tv.tv_usec = (1000 * read_interval) % 1000000;
|
tv.tv_usec = (1000 * read_interval) % 1000000;
|
||||||
@@ -357,6 +358,7 @@ static void collect(unsigned long cnt)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} while (rc != -1 && --cnt > 0);
|
} while (rc != -1 && --cnt > 0);
|
||||||
|
return rc;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void lookup_event(__u64 evtnum, __u16 ctr, __u64 value)
|
static void lookup_event(__u64 evtnum, __u16 ctr, __u64 value)
|
||||||
@@ -449,6 +451,11 @@ static void evt_show(__u64 evtnum, const char *evtsel, struct pai_event_out *ev)
|
|||||||
ev->u.s_comm.tid);
|
ev->u.s_comm.tid);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case PERF_RECORD_SWITCH:
|
||||||
|
printf("cs-%s",
|
||||||
|
(ev->misc & PERF_RECORD_MISC_SWITCH_OUT) ? "out" : "in");
|
||||||
|
break;
|
||||||
|
|
||||||
case PERF_RECORD_SWITCH_CPU_WIDE:
|
case PERF_RECORD_SWITCH_CPU_WIDE:
|
||||||
if (ev->misc & PERF_RECORD_MISC_SWITCH_OUT) {
|
if (ev->misc & PERF_RECORD_MISC_SWITCH_OUT) {
|
||||||
short p = PERF_RECORD_MISC_SWITCH_OUT_PREEMPT;
|
short p = PERF_RECORD_MISC_SWITCH_OUT_PREEMPT;
|
||||||
@@ -549,6 +556,9 @@ static int evt_scan(char *fn, unsigned char *buf, size_t len,
|
|||||||
offset -= sizeof(__u64);
|
offset -= sizeof(__u64);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case PERF_RECORD_SWITCH:
|
||||||
|
break;
|
||||||
|
|
||||||
case PERF_RECORD_SWITCH_CPU_WIDE:
|
case PERF_RECORD_SWITCH_CPU_WIDE:
|
||||||
memcpy(&ev.u, buf + offset, sizeof(ev.u.s_cs));
|
memcpy(&ev.u, buf + offset, sizeof(ev.u.s_cs));
|
||||||
offset += sizeof(ev.u.s_cs);
|
offset += sizeof(ev.u.s_cs);
|
||||||
@@ -583,7 +593,7 @@ static int evt_scan(char *fn, unsigned char *buf, size_t len,
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
printf("unknown header-type %d ", hdr->type);
|
printf("unknown header-type %d\n", hdr->type);
|
||||||
offset += hdr->size - sizeof(*hdr);
|
offset += hdr->size - sizeof(*hdr);
|
||||||
goto bypass;
|
goto bypass;
|
||||||
}
|
}
|
||||||
@@ -1115,12 +1125,12 @@ int main(int argc, char **argv)
|
|||||||
ev_install(group);
|
ev_install(group);
|
||||||
ev_enable();
|
ev_enable();
|
||||||
|
|
||||||
collect(loop_count);
|
ch = collect(loop_count);
|
||||||
|
|
||||||
ev_disable();
|
ev_disable();
|
||||||
ev_deinstall();
|
ev_deinstall();
|
||||||
ev_dealloc();
|
ev_dealloc();
|
||||||
return EXIT_SUCCESS;
|
return ch < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Must be reporting */
|
/* Must be reporting */
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ int get_numcpus()
|
|||||||
|
|
||||||
for (i = 0; ; i++) {
|
for (i = 0; ; i++) {
|
||||||
/* check whether file exists and is readable */
|
/* check whether file exists and is readable */
|
||||||
sprintf(path, "/sys/devices/system/cpu/cpu%d/online", i);
|
sprintf(path, "/sys/devices/system/cpu/cpu%d", i);
|
||||||
if (access(path, R_OK) == 0)
|
if (access(path, R_OK) == 0)
|
||||||
number++;
|
number++;
|
||||||
else
|
else
|
||||||
@@ -45,11 +45,13 @@ int get_num_online_cpus()
|
|||||||
int status = 0;
|
int status = 0;
|
||||||
int value_of_onlinefile, rc;
|
int value_of_onlinefile, rc;
|
||||||
|
|
||||||
for (i = 0; i <= get_numcpus(); i++) {
|
for (i = 0; i < get_numcpus(); i++) {
|
||||||
/* check wether file exists and is readable */
|
/* check wether file exists and is readable */
|
||||||
sprintf(path, "/sys/devices/system/cpu/cpu%d/online", i);
|
sprintf(path, "/sys/devices/system/cpu/cpu%d/online", i);
|
||||||
if (access(path, R_OK) != 0)
|
if (access(path, R_OK) != 0) {
|
||||||
|
status++;
|
||||||
continue;
|
continue;
|
||||||
|
}
|
||||||
filp = fopen(path, "r");
|
filp = fopen(path, "r");
|
||||||
if (!filp)
|
if (!filp)
|
||||||
cpuplugd_exit("Cannot open cpu online file: "
|
cpuplugd_exit("Cannot open cpu online file: "
|
||||||
@@ -101,10 +103,8 @@ int hotplug(int cpuid)
|
|||||||
cpuid);
|
cpuid);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
cpuplugd_error("hotplugging cpu with id %d failed\n", cpuid);
|
|
||||||
return -1;
|
|
||||||
}
|
}
|
||||||
|
cpuplugd_debug("cpu with id %d cannot be hotplugged\n", cpuid);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -135,9 +135,8 @@ int hotunplug(int cpuid)
|
|||||||
fclose(filp);
|
fclose(filp);
|
||||||
if (state == 0)
|
if (state == 0)
|
||||||
return 1;
|
return 1;
|
||||||
} else {
|
|
||||||
cpuplugd_error("unplugging cpu with id %d failed\n", cpuid);
|
|
||||||
}
|
}
|
||||||
|
cpuplugd_debug("cpu with id %d cannot be hotunplugged\n", cpuid);
|
||||||
return retval;
|
return retval;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -163,6 +162,8 @@ int is_online(int cpuid)
|
|||||||
retval = 0;
|
retval = 0;
|
||||||
}
|
}
|
||||||
fclose(filp);
|
fclose(filp);
|
||||||
|
} else {
|
||||||
|
retval = 1;
|
||||||
}
|
}
|
||||||
return retval;
|
return retval;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
# Common definitions
|
# Common definitions
|
||||||
include ../../common.mak
|
include ../../common.mak
|
||||||
|
|
||||||
ALL_CPPFLAGS += -I../include -std=gnu99 -Wno-unused-parameter
|
ALL_CPPFLAGS += -I../include -Wno-unused-parameter
|
||||||
LDLIBS += -lpthread -lrt
|
LDLIBS += -lpthread -lrt
|
||||||
ifneq ($(HAVE_ZLIB),0)
|
ifneq ($(HAVE_ZLIB),0)
|
||||||
ALL_CPPFLAGS += -DHAVE_ZLIB
|
ALL_CPPFLAGS += -DHAVE_ZLIB
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ $(bin_PROGRAM)_SRCS := $(bin_PROGRAM).c pv/pv_stage3.c pv/pv_image.c \
|
|||||||
$(NULL)
|
$(NULL)
|
||||||
$(bin_PROGRAM)_OBJS := $($(bin_PROGRAM)_SRCS:.c=.o)
|
$(bin_PROGRAM)_OBJS := $($(bin_PROGRAM)_SRCS:.c=.o)
|
||||||
|
|
||||||
ALL_CFLAGS += -std=gnu11 -DPKGDATADIR=$(PKGDATADIR) \
|
ALL_CFLAGS += -DPKGDATADIR=$(PKGDATADIR) \
|
||||||
$(GLIB2_CFLAGS) $(LIBCRYPTO_CFLAGS) $(LIBCURL_CFLAGS) \
|
$(GLIB2_CFLAGS) $(LIBCRYPTO_CFLAGS) $(LIBCURL_CFLAGS) \
|
||||||
-DOPENSSL_API_COMPAT=0x10100000L \
|
-DOPENSSL_API_COMPAT=0x10100000L \
|
||||||
$(WARNINGS) \
|
$(WARNINGS) \
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
#include <openssl/ssl.h>
|
#include <openssl/ssl.h>
|
||||||
|
|
||||||
#include <json-c/json.h>
|
#include <json-c/json.h>
|
||||||
|
#include <libxml/parser.h>
|
||||||
#include <libxml/tree.h>
|
#include <libxml/tree.h>
|
||||||
#include <curl/curl.h>
|
#include <curl/curl.h>
|
||||||
|
|
||||||
|
|||||||
@@ -21,8 +21,7 @@ WARNINGS := -Wall -Wextra -Wshadow \
|
|||||||
-Wno-unused-function -Wno-unused-parameter -Wno-unused-variable \
|
-Wno-unused-function -Wno-unused-parameter -Wno-unused-variable \
|
||||||
$(NULL)
|
$(NULL)
|
||||||
|
|
||||||
ALL_CFLAGS += -std=gnu11 \
|
ALL_CFLAGS += -DOPENSSL_API_COMPAT=0x10101000L \
|
||||||
-DOPENSSL_API_COMPAT=0x10101000L \
|
|
||||||
$(GLIB2_CFLAGS) \
|
$(GLIB2_CFLAGS) \
|
||||||
$(LIBCRYPTO_CFLAGS) \
|
$(LIBCRYPTO_CFLAGS) \
|
||||||
$(LIBCURL_CFLAGS) \
|
$(LIBCURL_CFLAGS) \
|
||||||
|
|||||||
@@ -35,8 +35,7 @@ LIBCRYPTO_LIBS := $(shell $(PKG_CONFIG) --silence-errors --libs libcrypto)
|
|||||||
LIBCURL_CFLAGS := $(shell $(PKG_CONFIG) --silence-errors --cflags libcurl)
|
LIBCURL_CFLAGS := $(shell $(PKG_CONFIG) --silence-errors --cflags libcurl)
|
||||||
LIBCURL_LIBS := $(shell $(PKG_CONFIG) --silence-errors --libs libcurl)
|
LIBCURL_LIBS := $(shell $(PKG_CONFIG) --silence-errors --libs libcurl)
|
||||||
|
|
||||||
ALL_CFLAGS += -std=gnu11 \
|
ALL_CFLAGS += -DPKGDATADIR=$(PKGDATADIR) \
|
||||||
-DPKGDATADIR=$(PKGDATADIR) \
|
|
||||||
-DOPENSSL_API_COMPAT=0x10101000L \
|
-DOPENSSL_API_COMPAT=0x10101000L \
|
||||||
$(GLIB2_CFLAGS) \
|
$(GLIB2_CFLAGS) \
|
||||||
$(LIBCRYPTO_CFLAGS) \
|
$(LIBCRYPTO_CFLAGS) \
|
||||||
|
|||||||
89
rust/Cargo.lock
generated
89
rust/Cargo.lock
generated
@@ -182,7 +182,7 @@ dependencies = [
|
|||||||
"heck",
|
"heck",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.18",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -340,7 +340,7 @@ checksum = "53b153fd91e4b0147f4aced87be237c98248656bb01050b96bf3ee89220a8ddb"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.18",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -651,9 +651,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "once_cell"
|
name = "once_cell"
|
||||||
version = "1.18.0"
|
version = "1.19.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d"
|
checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "openssl"
|
name = "openssl"
|
||||||
@@ -678,7 +678,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.18",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -765,33 +765,65 @@ checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "proc-macro2"
|
name = "proc-macro2"
|
||||||
version = "1.0.60"
|
version = "1.0.75"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "dec2b086b7a862cf4de201096214fa870344cf922b2b30c167badb3af3195406"
|
checksum = "907a61bd0f64c2f29cd1cf1dc34d05176426a3f504a78010f08416ddb7b13708"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pv"
|
name = "pv"
|
||||||
version = "0.9.0"
|
version = "1.0.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"byteorder",
|
"byteorder",
|
||||||
"cfg-if",
|
|
||||||
"clap",
|
"clap",
|
||||||
"curl",
|
"curl",
|
||||||
|
"log",
|
||||||
|
"mockito",
|
||||||
|
"once_cell",
|
||||||
|
"openssl",
|
||||||
|
"openssl_extensions",
|
||||||
|
"pv_core",
|
||||||
|
"serde",
|
||||||
|
"serde_test",
|
||||||
|
"thiserror",
|
||||||
|
"utils",
|
||||||
|
"zerocopy",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pv_core"
|
||||||
|
version = "1.0.0"
|
||||||
|
dependencies = [
|
||||||
|
"byteorder",
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
"libc",
|
"libc",
|
||||||
"log",
|
"log",
|
||||||
"mockito",
|
"mockito",
|
||||||
"openssl",
|
|
||||||
"openssl_extensions",
|
|
||||||
"serde",
|
"serde",
|
||||||
"serde_test",
|
"serde_test",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
|
"utils",
|
||||||
"zerocopy",
|
"zerocopy",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pvapconfig"
|
||||||
|
version = "0.9.0"
|
||||||
|
dependencies = [
|
||||||
|
"clap",
|
||||||
|
"lazy_static",
|
||||||
|
"openssl",
|
||||||
|
"openssl-sys",
|
||||||
|
"pv_core",
|
||||||
|
"rand",
|
||||||
|
"regex",
|
||||||
|
"serde",
|
||||||
|
"serde_yaml",
|
||||||
|
"utils",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pvsecret"
|
name = "pvsecret"
|
||||||
version = "0.9.0"
|
version = "0.9.0"
|
||||||
@@ -806,9 +838,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "quote"
|
name = "quote"
|
||||||
version = "1.0.28"
|
version = "1.0.35"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1b9ab9c7eadfd8df19006f1cf1a4aed13540ed5cbc047010ece5826e10825488"
|
checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
]
|
]
|
||||||
@@ -939,7 +971,7 @@ checksum = "d9735b638ccc51c28bf6914d90a2e9725b377144fc612c49a611fddd1b631d68"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.18",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1045,20 +1077,9 @@ checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "syn"
|
name = "syn"
|
||||||
version = "1.0.109"
|
version = "2.0.47"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
|
checksum = "1726efe18f42ae774cc644f330953a5e7b3c3003d3edcecf18850fe9d4dd9afb"
|
||||||
dependencies = [
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"unicode-ident",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "syn"
|
|
||||||
version = "2.0.18"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "32d41677bcbe24c20c52e7c70b0d8db04134c5d1066bf98662e2871ad200ea3e"
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
@@ -1092,7 +1113,7 @@ checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.18",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1122,7 +1143,7 @@ checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.18",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1360,9 +1381,9 @@ checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zerocopy"
|
name = "zerocopy"
|
||||||
version = "0.6.1"
|
version = "0.7.32"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "332f188cc1bcf1fe1064b8c58d150f497e697f49774aa846f2dc949d9a25f236"
|
checksum = "74d4d3961e53fa4c9a25a8637fc2bfaf2595b3d3ae34875568a5cf64787716be"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"byteorder",
|
"byteorder",
|
||||||
"zerocopy-derive",
|
"zerocopy-derive",
|
||||||
@@ -1370,11 +1391,11 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zerocopy-derive"
|
name = "zerocopy-derive"
|
||||||
version = "0.3.2"
|
version = "0.7.32"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6505e6815af7de1746a08f69c69606bb45695a17149517680f3b2149713b19a3"
|
checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 1.0.109",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
[workspace]
|
[workspace]
|
||||||
members = [
|
members = [
|
||||||
"pv",
|
"pv",
|
||||||
|
"pv_core",
|
||||||
|
"pvapconfig",
|
||||||
"pvsecret",
|
"pvsecret",
|
||||||
"utils",
|
"utils",
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ ifneq (${HAVE_CARGO},0)
|
|||||||
|
|
||||||
ifneq (${HAVE_OPENSSL},0)
|
ifneq (${HAVE_OPENSSL},0)
|
||||||
ifneq (${HAVE_LIBCURL},0)
|
ifneq (${HAVE_LIBCURL},0)
|
||||||
PV_TARGETS := pvsecret
|
PV_TARGETS := pvsecret pvapconfig
|
||||||
|
|
||||||
PV_BUILD_TARGETS := $(PV_TARGETS)
|
PV_BUILD_TARGETS := $(PV_TARGETS)
|
||||||
CARGO_TEST_TARGETS += $(addsuffix .test,pv $(PV_TARGETS))
|
CARGO_TEST_TARGETS += $(addsuffix .test,pv $(PV_TARGETS))
|
||||||
@@ -37,8 +37,8 @@ endif
|
|||||||
|
|
||||||
# the cc crate uses these variables to compile c code. It does not open a shell
|
# the cc crate uses these variables to compile c code. It does not open a shell
|
||||||
# to call the compiler, so no echo etc. allowed here, just a path to a program
|
# to call the compiler, so no echo etc. allowed here, just a path to a program
|
||||||
$(BUILD_TARGETS) rust-test: CC = $(CC_SILENT)
|
$(BUILD_TARGETS) $(TEST_TARGETS) rust-test: CC = $(CC_SILENT)
|
||||||
$(BUILD_TARGETS) rust-test: AR = $(AR_SILENT)
|
$(BUILD_TARGETS) $(TEST_TARGETS) rust-test: AR = $(AR_SILENT)
|
||||||
|
|
||||||
$(PV_TARGETS): .check-dep-pvtools
|
$(PV_TARGETS): .check-dep-pvtools
|
||||||
$(PV_TARGETS) $(CARGO_TARGETS): .check-cargo .no-cross-compile
|
$(PV_TARGETS) $(CARGO_TARGETS): .check-cargo .no-cross-compile
|
||||||
|
|||||||
@@ -25,16 +25,18 @@ Tip: You can use `make version` to get the version string.
|
|||||||
|
|
||||||
## Internal Libraries
|
## Internal Libraries
|
||||||
* __utils__ _Library for rust tools that bundles common stuff for the 390-tools_
|
* __utils__ _Library for rust tools that bundles common stuff for the 390-tools_
|
||||||
* currently only provides a macro to get the `S390_TOOLS_RELEASE` string
|
* provides a macro to get the `S390_TOOLS_RELEASE` string
|
||||||
|
* provides macros for compile time assertions
|
||||||
|
|
||||||
|
* __pv_core__ _Library for pv tools, providing uvdevice access and utilities to send, receive and interpret various UV-calls._
|
||||||
|
|
||||||
* __pv__ _Library for pv tools, providing uvdevice access, encryption utilities, and utilities for generating UV-request_
|
* __pv__ _Library for pv tools, providing uvdevice access, encryption utilities, and utilities for generating UV-request_
|
||||||
* requires openssl and libcurl for the feature `request`; use `HAVE_<OPENSSL|CURL>=0` to
|
* requires openssl and libcurl
|
||||||
disable build that use pv with the request feature.
|
* reexports ann symbols from __pv_core__
|
||||||
|
* if no encryption utilities required, use __pv_core__
|
||||||
|
|
||||||
## Tools
|
## Tools
|
||||||
* __pvsecret__ _Manage secrets for IBM Secure Execution guests_
|
* __pvsecret__ _Manage secrets for IBM Secure Execution guests_
|
||||||
* requires pv with the `request` feature
|
|
||||||
|
|
||||||
## Writing new tools
|
## Writing new tools
|
||||||
We encourage to use Rust for new tools. However, for some use cases it makes
|
We encourage to use Rust for new tools. However, for some use cases it makes
|
||||||
|
|||||||
@@ -1,32 +1,24 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "pv"
|
name = "pv"
|
||||||
version = "0.9.0"
|
version = "1.0.0"
|
||||||
edition.workspace = true
|
edition.workspace = true
|
||||||
license.workspace = true
|
license.workspace = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
libc = "0.2.49"
|
byteorder = "1.3"
|
||||||
|
clap = { version ="4", features = ["derive", "wrap_help"] }
|
||||||
|
curl = "0.4.7"
|
||||||
log = { version = "0.4.6", features = ["std", "release_max_level_debug"] }
|
log = { version = "0.4.6", features = ["std", "release_max_level_debug"] }
|
||||||
|
openssl = "0.10.49"
|
||||||
|
serde = { version = "1.0.139", features = ["derive"] }
|
||||||
thiserror = "1.0.33"
|
thiserror = "1.0.33"
|
||||||
zerocopy = "0.6"
|
utils = {path = "../utils"}
|
||||||
cfg-if = "1.0.0"
|
zerocopy = { version="0.7", features = ["derive"] }
|
||||||
|
|
||||||
# dependencies for request feature
|
openssl_extensions = { path = "openssl_extensions" }
|
||||||
clap = { version ="4", features = ["derive", "wrap_help"], optional = true }
|
pv_core = { path = "../pv_core" }
|
||||||
curl = { version ="0.4.7", optional = true }
|
|
||||||
openssl = {version = "0.10.49", optional = true }
|
|
||||||
openssl_extensions = { path = "openssl_extensions", optional = true }
|
|
||||||
serde = { version = "1.0.139", features = ["derive"], optional = true }
|
|
||||||
|
|
||||||
# misc optional dependencies
|
|
||||||
byteorder = {version = "1.3", optional = true }
|
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
mockito = {version = "1", default-features = false }
|
mockito = {version = "1", default-features = false }
|
||||||
|
once_cell = "1.19"
|
||||||
serde_test = "1"
|
serde_test = "1"
|
||||||
lazy_static = "1.1"
|
|
||||||
|
|
||||||
[features]
|
|
||||||
default = []
|
|
||||||
request = ["dep:openssl", "dep:curl", "dep:openssl_extensions", "dep:serde", "dep:clap"]
|
|
||||||
uvsecret = ["dep:byteorder", "dep:serde"]
|
|
||||||
|
|||||||
@@ -5,15 +5,15 @@
|
|||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
|
||||||
use foreign_types::{foreign_type, ForeignType, ForeignTypeRef};
|
use foreign_types::{foreign_type, ForeignType, ForeignTypeRef};
|
||||||
use libc::c_int;
|
|
||||||
use openssl::x509::{X509CrlRef, X509Ref};
|
use openssl::x509::{X509CrlRef, X509Ref};
|
||||||
|
use std::ffi::c_int;
|
||||||
|
|
||||||
mod ffi {
|
mod ffi {
|
||||||
extern "C" {
|
extern "C" {
|
||||||
pub fn X509_check_akid(
|
pub fn X509_check_akid(
|
||||||
issuer: *const openssl_sys::X509,
|
issuer: *const openssl_sys::X509,
|
||||||
akid: *const openssl_sys::AUTHORITY_KEYID,
|
akid: *const openssl_sys::AUTHORITY_KEYID,
|
||||||
) -> ::libc::c_int;
|
) -> super::c_int;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ mod ffi {
|
|||||||
pub fn X509_STORE_add_crl(
|
pub fn X509_STORE_add_crl(
|
||||||
xs: *mut openssl_sys::X509_STORE,
|
xs: *mut openssl_sys::X509_STORE,
|
||||||
x: *mut openssl_sys::X509_CRL,
|
x: *mut openssl_sys::X509_CRL,
|
||||||
) -> libc::c_int;
|
) -> std::ffi::c_int;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,13 +5,13 @@
|
|||||||
use std::{marker::PhantomData, ptr};
|
use std::{marker::PhantomData, ptr};
|
||||||
|
|
||||||
use foreign_types::{ForeignType, ForeignTypeRef};
|
use foreign_types::{ForeignType, ForeignTypeRef};
|
||||||
use libc::c_int;
|
|
||||||
use openssl::{
|
use openssl::{
|
||||||
error::ErrorStack,
|
error::ErrorStack,
|
||||||
stack::Stackable,
|
stack::Stackable,
|
||||||
x509::{X509Crl, X509CrlRef},
|
x509::{X509Crl, X509CrlRef},
|
||||||
};
|
};
|
||||||
use openssl_sys::BIO_new_mem_buf;
|
use openssl_sys::BIO_new_mem_buf;
|
||||||
|
use std::ffi::c_int;
|
||||||
|
|
||||||
pub struct StackableX509Crl(*mut openssl_sys::X509_CRL);
|
pub struct StackableX509Crl(*mut openssl_sys::X509_CRL);
|
||||||
|
|
||||||
|
|||||||
@@ -8,11 +8,10 @@ use std::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
// (SE) boot request control block aka SE header
|
// (SE) boot request control block aka SE header
|
||||||
use crate::{
|
use crate::{assert_size, static_assert, Error, Result, PAGESIZE};
|
||||||
assert_size, request::MagicValue, requires_feat, static_assert, Error, Result, PAGESIZE,
|
|
||||||
};
|
|
||||||
use log::debug;
|
use log::debug;
|
||||||
use zerocopy::{AsBytes, BigEndian, FromBytes, U32, U64};
|
use pv_core::request::MagicValue;
|
||||||
|
use zerocopy::{AsBytes, BigEndian, FromBytes, FromZeroes, U32, U64};
|
||||||
|
|
||||||
/// Struct containing all SE-header tags.
|
/// Struct containing all SE-header tags.
|
||||||
///
|
///
|
||||||
@@ -22,7 +21,6 @@ use zerocopy::{AsBytes, BigEndian, FromBytes, U32, U64};
|
|||||||
/// Tweak List Digest (tld)
|
/// Tweak List Digest (tld)
|
||||||
/// SE Header Tag (seht)
|
/// SE Header Tag (seht)
|
||||||
///
|
///
|
||||||
#[doc = requires_feat!(request)]
|
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
#[derive(Debug, Clone, Copy, AsBytes, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, AsBytes, PartialEq, Eq)]
|
||||||
pub struct BootHdrTags {
|
pub struct BootHdrTags {
|
||||||
@@ -144,7 +142,7 @@ impl BootHdrTags {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
#[derive(Debug, Clone, FromBytes)]
|
#[derive(Debug, Clone, FromBytes, FromZeroes)]
|
||||||
struct BootHdrHead {
|
struct BootHdrHead {
|
||||||
magic: U64<BigEndian>,
|
magic: U64<BigEndian>,
|
||||||
version: U32<BigEndian>,
|
version: U32<BigEndian>,
|
||||||
|
|||||||
@@ -2,14 +2,12 @@
|
|||||||
//
|
//
|
||||||
// Copyright IBM Corp. 2023
|
// Copyright IBM Corp. 2023
|
||||||
|
|
||||||
|
use crate::misc::{create_file, open_file};
|
||||||
use crate::Result;
|
use crate::Result;
|
||||||
use crate::{create_buffered_file, open_buffered_file};
|
|
||||||
use clap::{ArgGroup, Args, ValueHint};
|
use clap::{ArgGroup, Args, ValueHint};
|
||||||
use std::io::{Read, Write};
|
use std::io::{Read, Write};
|
||||||
|
|
||||||
/// CLI Argument collection for handling certificates.
|
/// CLI Argument collection for handling certificates.
|
||||||
///
|
|
||||||
#[doc = requires_feat!(request)]
|
|
||||||
#[derive(Args, Debug, PartialEq, Eq, Default)]
|
#[derive(Args, Debug, PartialEq, Eq, Default)]
|
||||||
#[command(
|
#[command(
|
||||||
group(ArgGroup::new("pv_verify").required(true).args(["no_verify", "certs"])),
|
group(ArgGroup::new("pv_verify").required(true).args(["no_verify", "certs"])),
|
||||||
@@ -32,15 +30,15 @@ pub struct CertificateOptions {
|
|||||||
/// Disable the host-key document verification.
|
/// Disable the host-key document verification.
|
||||||
///
|
///
|
||||||
/// Does not require the host-key documents to be valid.
|
/// Does not require the host-key documents to be valid.
|
||||||
/// Do not use for a production request unless you verified the host-key document before.
|
/// Do not use for a production request unless you verified the host-key document beforehand.
|
||||||
#[arg(long)]
|
#[arg(long)]
|
||||||
pub no_verify: bool,
|
pub no_verify: bool,
|
||||||
|
|
||||||
/// Use FILE as a certificate to verify the host-key(s).
|
/// Use FILE as a certificate to verify the host key or keys.
|
||||||
///
|
///
|
||||||
/// The certificates are used to establish a chain of trust for the verification
|
/// The certificates are used to establish a chain of trust for the verification
|
||||||
/// of the host-key documents. Specify this option twice to specify the IBM Z signing key and
|
/// of the host-key documents. Specify this option twice to specify the IBM Z signing key and
|
||||||
/// the intermediate CA certificate (signed by the rootCA).
|
/// the intermediate CA certificate (signed by the root CA).
|
||||||
#[arg(
|
#[arg(
|
||||||
short= 'C',
|
short= 'C',
|
||||||
long = "cert",
|
long = "cert",
|
||||||
@@ -54,7 +52,7 @@ pub struct CertificateOptions {
|
|||||||
|
|
||||||
/// Use FILE as a certificate revocation list.
|
/// Use FILE as a certificate revocation list.
|
||||||
///
|
///
|
||||||
/// That list is used to check whether a certificate of the chain of
|
/// The list is used to check whether a certificate of the chain of
|
||||||
/// trust is revoked. Specify this option multiple times to use multiple CRLs.
|
/// trust is revoked. Specify this option multiple times to use multiple CRLs.
|
||||||
#[arg(
|
#[arg(
|
||||||
long = "crl",
|
long = "crl",
|
||||||
@@ -72,7 +70,7 @@ pub struct CertificateOptions {
|
|||||||
|
|
||||||
/// Use FILE as the root-CA certificate for the verification.
|
/// Use FILE as the root-CA certificate for the verification.
|
||||||
///
|
///
|
||||||
/// If omitted, the system wide root CAs installed on the system are used.
|
/// If omitted, the system wide-root CAs installed on the system are used.
|
||||||
/// Use this only if you trust the specified certificate.
|
/// Use this only if you trust the specified certificate.
|
||||||
#[arg(long, requires("certs"))]
|
#[arg(long, requires("certs"))]
|
||||||
pub root_ca: Option<String>,
|
pub root_ca: Option<String>,
|
||||||
@@ -104,37 +102,25 @@ impl CertificateOptions {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// stdout
|
/// stdout
|
||||||
#[cfg(feature = "request")]
|
|
||||||
pub const STDOUT: &str = "-";
|
pub const STDOUT: &str = "-";
|
||||||
/// stdin
|
/// stdin
|
||||||
#[cfg(feature = "request")]
|
|
||||||
pub const STDIN: &str = "-";
|
pub const STDIN: &str = "-";
|
||||||
|
|
||||||
/// Converts an argument value into a Writer.
|
/// Converts an argument value into a Writer.
|
||||||
///
|
|
||||||
/// # Errors
|
|
||||||
/// No Error will occur but function must match a signature
|
|
||||||
///
|
|
||||||
#[cfg(feature = "request")]
|
|
||||||
pub fn get_writer_from_cli_file_arg(path: &str) -> Result<Box<dyn Write>> {
|
pub fn get_writer_from_cli_file_arg(path: &str) -> Result<Box<dyn Write>> {
|
||||||
if path == STDOUT {
|
if path == STDOUT {
|
||||||
Ok(Box::new(std::io::stdout()))
|
Ok(Box::new(std::io::stdout()))
|
||||||
} else {
|
} else {
|
||||||
Ok(Box::new(create_buffered_file!(path)))
|
Ok(Box::new(create_file(path)?))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Converts an argument value into a Reader.
|
/// Converts an argument value into a Reader.
|
||||||
///
|
|
||||||
/// # Errors
|
|
||||||
/// No Error will occur but function must match a signature
|
|
||||||
///
|
|
||||||
#[cfg(feature = "request")]
|
|
||||||
pub fn get_reader_from_cli_file_arg(path: &str) -> Result<Box<dyn Read>> {
|
pub fn get_reader_from_cli_file_arg(path: &str) -> Result<Box<dyn Read>> {
|
||||||
if path == STDIN {
|
if path == STDIN {
|
||||||
Ok(Box::new(std::io::stdin()))
|
Ok(Box::new(std::io::stdin()))
|
||||||
} else {
|
} else {
|
||||||
Ok(Box::new(open_buffered_file!(path)))
|
Ok(Box::new(open_file(path)?))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,29 +2,29 @@
|
|||||||
//
|
//
|
||||||
// Copyright IBM Corp. 2023
|
// Copyright IBM Corp. 2023
|
||||||
|
|
||||||
use crate::requires_feat;
|
|
||||||
use crate::{error::Result, secret::Secret, Error};
|
use crate::{error::Result, secret::Secret, Error};
|
||||||
use openssl::rand::rand_bytes;
|
|
||||||
use openssl::{
|
use openssl::{
|
||||||
derive::Deriver,
|
derive::Deriver,
|
||||||
ec::{EcGroup, EcKey},
|
ec::{EcGroup, EcKey},
|
||||||
hash::{DigestBytes, MessageDigest},
|
hash::{DigestBytes, MessageDigest},
|
||||||
md::MdRef,
|
md::MdRef,
|
||||||
nid::Nid,
|
nid::Nid,
|
||||||
pkey::{Id, PKey, Private, Public},
|
pkey::{HasPublic, Id, PKey, PKeyRef, Private, Public},
|
||||||
pkey_ctx::{HkdfMode, PkeyCtx},
|
pkey_ctx::{HkdfMode, PkeyCtx},
|
||||||
symm::{encrypt, encrypt_aead, Cipher},
|
rand::rand_bytes,
|
||||||
|
rsa::Padding,
|
||||||
|
sign::{Signer, Verifier},
|
||||||
|
symm::{encrypt_aead, Cipher},
|
||||||
};
|
};
|
||||||
use std::convert::TryInto;
|
use std::{convert::TryInto, ops::Range};
|
||||||
|
|
||||||
/// An AES256-key that will purge itself out of the memory when going out of scope
|
/// An AES256-key that will purge itself out of the memory when going out of scope
|
||||||
///
|
///
|
||||||
#[doc = requires_feat!(request)]
|
|
||||||
pub type Aes256Key = Secret<[u8; 32]>;
|
pub type Aes256Key = Secret<[u8; 32]>;
|
||||||
|
pub(crate) const AES_256_GCM_TAG_SIZE: usize = 16;
|
||||||
|
|
||||||
/// Types of symmetric keys, to specify during construction.
|
/// Types of symmetric keys, to specify during construction.
|
||||||
///
|
///
|
||||||
#[doc = requires_feat!(request)]
|
|
||||||
#[non_exhaustive]
|
#[non_exhaustive]
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
pub enum SymKeyType {
|
pub enum SymKeyType {
|
||||||
@@ -34,7 +34,6 @@ pub enum SymKeyType {
|
|||||||
|
|
||||||
/// Types of symmetric keys
|
/// Types of symmetric keys
|
||||||
///
|
///
|
||||||
#[doc = requires_feat!(request)]
|
|
||||||
#[non_exhaustive]
|
#[non_exhaustive]
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
pub enum SymKey {
|
pub enum SymKey {
|
||||||
@@ -88,7 +87,6 @@ impl From<Aes256Key> for SymKey {
|
|||||||
/// Performs an hkdf according to RFC 5869.
|
/// Performs an hkdf according to RFC 5869.
|
||||||
/// See [`OpenSSL HKDF`]()
|
/// See [`OpenSSL HKDF`]()
|
||||||
///
|
///
|
||||||
#[doc = requires_feat!(request)]
|
|
||||||
/// # Errors
|
/// # Errors
|
||||||
///
|
///
|
||||||
/// This function will return an OpenSSL error if the key could not be generated.
|
/// This function will return an OpenSSL error if the key could not be generated.
|
||||||
@@ -113,7 +111,6 @@ pub fn hkdf_rfc_5869<const COUNT: usize>(
|
|||||||
|
|
||||||
/// Derive a symmetric key from a private and a public key.
|
/// Derive a symmetric key from a private and a public key.
|
||||||
///
|
///
|
||||||
#[doc = requires_feat!(request)]
|
|
||||||
/// # Errors
|
/// # Errors
|
||||||
///
|
///
|
||||||
/// This function will return an error if something went bad in OpenSSL.
|
/// This function will return an error if something went bad in OpenSSL.
|
||||||
@@ -132,7 +129,6 @@ pub fn derive_key(k1: &PKey<Private>, k2: &PKey<Public>) -> Result<Aes256Key> {
|
|||||||
|
|
||||||
/// Generate a random array.
|
/// Generate a random array.
|
||||||
///
|
///
|
||||||
#[doc = requires_feat!(request)]
|
|
||||||
/// # Errors
|
/// # Errors
|
||||||
///
|
///
|
||||||
/// This function will return an error if the entropy source fails or is not available.
|
/// This function will return an error if the entropy source fails or is not available.
|
||||||
@@ -144,7 +140,6 @@ pub fn random_array<const COUNT: usize>() -> Result<[u8; COUNT]> {
|
|||||||
|
|
||||||
/// Generate a new random EC-SECP521R1 key.
|
/// Generate a new random EC-SECP521R1 key.
|
||||||
///
|
///
|
||||||
#[doc = requires_feat!(request)]
|
|
||||||
/// # Errors
|
/// # Errors
|
||||||
///
|
///
|
||||||
/// This function will return an error if the key could not be generated by OpenSSL.
|
/// This function will return an error if the key could not be generated by OpenSSL.
|
||||||
@@ -154,24 +149,6 @@ pub fn gen_ec_key() -> Result<PKey<Private>> {
|
|||||||
PKey::from_ec_key(key).map_err(Error::Crypto)
|
PKey::from_ec_key(key).map_err(Error::Crypto)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Encrypt confidential Data with a symmetric key.
|
|
||||||
///
|
|
||||||
/// * `key` - symmetric key used for encryption
|
|
||||||
/// * `iv` - initialisation vector
|
|
||||||
/// * `conf` - data to be encrypted
|
|
||||||
///
|
|
||||||
#[doc = requires_feat!(request)]
|
|
||||||
/// # Errors
|
|
||||||
///
|
|
||||||
/// This function will return an error if the data could not be encrypted by OpenSSL.
|
|
||||||
pub fn encrypt_aes(key: &SymKey, iv: &[u8], conf: &[u8]) -> Result<Vec<u8>> {
|
|
||||||
match key {
|
|
||||||
SymKey::Aes256(key) => {
|
|
||||||
encrypt(Cipher::aes_256_gcm(), key.value(), Some(iv), conf).map_err(Error::Crypto)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Encrypt confidential Data with a symmetric key and provida a gcm tag.
|
/// Encrypt confidential Data with a symmetric key and provida a gcm tag.
|
||||||
///
|
///
|
||||||
/// * `key` - symmetric key used for encryption
|
/// * `key` - symmetric key used for encryption
|
||||||
@@ -179,7 +156,6 @@ pub fn encrypt_aes(key: &SymKey, iv: &[u8], conf: &[u8]) -> Result<Vec<u8>> {
|
|||||||
/// * `aad` - additional authentic data
|
/// * `aad` - additional authentic data
|
||||||
/// * `conf` - data to be encrypted
|
/// * `conf` - data to be encrypted
|
||||||
///
|
///
|
||||||
#[doc = requires_feat!(request)]
|
|
||||||
/// # Returns
|
/// # Returns
|
||||||
/// [`Vec<u8>`] with the following content:
|
/// [`Vec<u8>`] with the following content:
|
||||||
/// 1. `aad`
|
/// 1. `aad`
|
||||||
@@ -189,8 +165,13 @@ pub fn encrypt_aes(key: &SymKey, iv: &[u8], conf: &[u8]) -> Result<Vec<u8>> {
|
|||||||
/// # Errors
|
/// # Errors
|
||||||
///
|
///
|
||||||
/// This function will return an error if the data could not be encrypted by OpenSSL.
|
/// This function will return an error if the data could not be encrypted by OpenSSL.
|
||||||
pub fn encrypt_aes_gcm(key: &SymKey, iv: &[u8], aad: &[u8], conf: &[u8]) -> Result<Vec<u8>> {
|
pub fn encrypt_aes_gcm(
|
||||||
let mut tag = vec![0xff; 16];
|
key: &SymKey,
|
||||||
|
iv: &[u8],
|
||||||
|
aad: &[u8],
|
||||||
|
conf: &[u8],
|
||||||
|
) -> Result<(Vec<u8>, Range<usize>, Range<usize>, Range<usize>)> {
|
||||||
|
let mut tag = vec![0xff; AES_256_GCM_TAG_SIZE];
|
||||||
let encr = match key {
|
let encr = match key {
|
||||||
SymKey::Aes256(key) => encrypt_aead(
|
SymKey::Aes256(key) => encrypt_aead(
|
||||||
Cipher::aes_256_gcm(),
|
Cipher::aes_256_gcm(),
|
||||||
@@ -202,17 +183,28 @@ pub fn encrypt_aes_gcm(key: &SymKey, iv: &[u8], aad: &[u8], conf: &[u8]) -> Resu
|
|||||||
)?,
|
)?,
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut res = vec![0; aad.len() + encr.len() + 16];
|
let mut res = vec![0; aad.len() + encr.len() + tag.len()];
|
||||||
res[0..aad.len()].copy_from_slice(aad);
|
let aad_range = Range {
|
||||||
res[aad.len()..aad.len() + encr.len()].copy_from_slice(&encr);
|
start: 0,
|
||||||
res[aad.len() + encr.len()..aad.len() + encr.len() + 16].copy_from_slice(&tag);
|
end: aad.len(),
|
||||||
|
};
|
||||||
|
let encr_range = Range {
|
||||||
|
start: aad.len(),
|
||||||
|
end: aad.len() + encr.len(),
|
||||||
|
};
|
||||||
|
let tag_range = Range {
|
||||||
|
start: aad.len() + encr.len(),
|
||||||
|
end: aad.len() + encr.len() + tag.len(),
|
||||||
|
};
|
||||||
|
|
||||||
Ok(res)
|
res[aad_range.clone()].copy_from_slice(aad);
|
||||||
|
res[encr_range.clone()].copy_from_slice(&encr);
|
||||||
|
res[tag_range.clone()].copy_from_slice(&tag);
|
||||||
|
Ok((res, aad_range, encr_range, tag_range))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Calculate the hash of a slice.
|
/// Calculate the hash of a slice.
|
||||||
///
|
///
|
||||||
#[doc = requires_feat!(request)]
|
|
||||||
/// # Errors
|
/// # Errors
|
||||||
///
|
///
|
||||||
/// This function will return an error if OpenSSL could not compute the hash.
|
/// This function will return an error if OpenSSL could not compute the hash.
|
||||||
@@ -220,10 +212,100 @@ pub fn hash(t: MessageDigest, data: &[u8]) -> Result<DigestBytes> {
|
|||||||
openssl::hash::hash(t, data).map_err(Error::Crypto)
|
openssl::hash::hash(t, data).map_err(Error::Crypto)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Calculate a digital signature scheme.
|
||||||
|
///
|
||||||
|
/// Calculates the digital signature of the provided message using the signing key. [`Id::EC`],
|
||||||
|
/// and [`Id::RSA`] keys are supported. For [`Id::RSA`] [`Padding::PKCS1_PSS`] is used.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// This function will return an error if OpenSSL could not compute the signature.
|
||||||
|
pub fn sign_msg(skey: &PKeyRef<Private>, dgst: MessageDigest, msg: &[u8]) -> Result<Vec<u8>> {
|
||||||
|
match skey.id() {
|
||||||
|
Id::EC => {
|
||||||
|
let mut sgn = Signer::new(dgst, skey)?;
|
||||||
|
sgn.sign_oneshot_to_vec(msg).map_err(Error::Crypto)
|
||||||
|
}
|
||||||
|
Id::RSA => {
|
||||||
|
let mut sgn = Signer::new(dgst, skey)?;
|
||||||
|
sgn.set_rsa_padding(Padding::PKCS1_PSS)?;
|
||||||
|
sgn.sign_oneshot_to_vec(msg).map_err(Error::Crypto)
|
||||||
|
}
|
||||||
|
_ => Err(Error::UnsupportedSigningKey),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Verify the digital signature of a message.
|
||||||
|
///
|
||||||
|
/// Verifies the digital signature of the provided message using the signing key.
|
||||||
|
/// [`Id::EC`] and [`Id::RSA`] keys are supported. For [`Id::RSA`] [`Padding::PKCS1_PSS`] is used.
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
/// true if signature could be verified, false otherwise
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// This function will return an error if OpenSSL could not compute the signature.
|
||||||
|
pub fn verify_signature<T: HasPublic>(
|
||||||
|
skey: &PKeyRef<T>,
|
||||||
|
dgst: MessageDigest,
|
||||||
|
msg: &[u8],
|
||||||
|
sign: &[u8],
|
||||||
|
) -> Result<bool> {
|
||||||
|
match skey.id() {
|
||||||
|
Id::EC => {
|
||||||
|
let mut ctx = Verifier::new(dgst, skey)?;
|
||||||
|
ctx.update(msg)?;
|
||||||
|
ctx.verify(sign).map_err(Error::Crypto)
|
||||||
|
}
|
||||||
|
Id::RSA => {
|
||||||
|
let mut ctx = Verifier::new(dgst, skey)?;
|
||||||
|
ctx.set_rsa_padding(Padding::PKCS1_PSS)?;
|
||||||
|
ctx.verify_oneshot(sign, msg).map_err(Error::Crypto)
|
||||||
|
}
|
||||||
|
_ => Err(Error::UnsupportedVerificationKey),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::test_utils::*;
|
use crate::{get_test_asset, test_utils::*};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn sign_ec() {
|
||||||
|
let (ec_key, _) = get_test_keys();
|
||||||
|
|
||||||
|
let data = "sample".as_bytes();
|
||||||
|
let sign = sign_msg(&ec_key, MessageDigest::sha512(), data).unwrap();
|
||||||
|
assert!(sign.len() <= 139 && sign.len() >= 137);
|
||||||
|
|
||||||
|
assert!(verify_signature(&ec_key, MessageDigest::sha512(), data, &sign).unwrap());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn sign_rsa_2048() {
|
||||||
|
let keypair = get_test_asset!("keys/rsa2048key.pem");
|
||||||
|
let keypair = PKey::private_key_from_pem(keypair).unwrap();
|
||||||
|
|
||||||
|
let data = "sample".as_bytes();
|
||||||
|
let sign = sign_msg(&keypair, MessageDigest::sha512(), data).unwrap();
|
||||||
|
assert_eq!(256, sign.len());
|
||||||
|
|
||||||
|
assert!(verify_signature(&keypair, MessageDigest::sha512(), data, &sign).unwrap());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn sign_rsa_3072() {
|
||||||
|
let keypair = get_test_asset!("keys/rsa3072key.pem");
|
||||||
|
let keypair = PKey::private_key_from_pem(keypair).unwrap();
|
||||||
|
|
||||||
|
let data = "sample".as_bytes();
|
||||||
|
let sign = sign_msg(&keypair, MessageDigest::sha512(), data).unwrap();
|
||||||
|
assert_eq!(384, sign.len());
|
||||||
|
|
||||||
|
assert!(verify_signature(&keypair, MessageDigest::sha512(), data, &sign).unwrap());
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn derive_key() {
|
fn derive_key() {
|
||||||
@@ -285,7 +367,7 @@ mod tests {
|
|||||||
0xee, 0x62, 0x98, 0xf7, 0x7e, 0x0c,
|
0xee, 0x62, 0x98, 0xf7, 0x7e, 0x0c,
|
||||||
];
|
];
|
||||||
|
|
||||||
let res = encrypt_aes_gcm(
|
let (res, ..) = encrypt_aes_gcm(
|
||||||
&SymKey::Aes256(aes_gcm_key.into()),
|
&SymKey::Aes256(aes_gcm_key.into()),
|
||||||
&aes_gcm_iv,
|
&aes_gcm_iv,
|
||||||
&aes_gcm_aad,
|
&aes_gcm_aad,
|
||||||
@@ -294,32 +376,4 @@ mod tests {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!(res, aes_gcm_res);
|
assert_eq!(res, aes_gcm_res);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn encrypt_aes_256() {
|
|
||||||
let aes_gcm_key = [
|
|
||||||
0xee, 0xbc, 0x1f, 0x57, 0x48, 0x7f, 0x51, 0x92, 0x1c, 0x04, 0x65, 0x66, 0x5f, 0x8a,
|
|
||||||
0xe6, 0xd1, 0x65, 0x8b, 0xb2, 0x6d, 0xe6, 0xf8, 0xa0, 0x69, 0xa3, 0x52, 0x02, 0x93,
|
|
||||||
0xa5, 0x72, 0x07, 0x8f,
|
|
||||||
];
|
|
||||||
let aes_gcm_iv = [
|
|
||||||
0x99, 0xaa, 0x3e, 0x68, 0xed, 0x81, 0x73, 0xa0, 0xee, 0xd0, 0x66, 0x84,
|
|
||||||
];
|
|
||||||
let aes_gcm_plain = [
|
|
||||||
0xf5, 0x6e, 0x87, 0x05, 0x5b, 0xc3, 0x2d, 0x0e, 0xeb, 0x31, 0xb2, 0xea, 0xcc, 0x2b,
|
|
||||||
0xf2, 0xa5,
|
|
||||||
];
|
|
||||||
let aes_gcm_res = vec![
|
|
||||||
0xf7, 0x26, 0x44, 0x13, 0xa8, 0x4c, 0x0e, 0x7c, 0xd5, 0x36, 0x86, 0x7e, 0xb9, 0xf2,
|
|
||||||
0x17, 0x36,
|
|
||||||
];
|
|
||||||
|
|
||||||
let res = encrypt_aes(
|
|
||||||
&SymKey::Aes256(aes_gcm_key.into()),
|
|
||||||
&aes_gcm_iv,
|
|
||||||
&aes_gcm_plain,
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
assert_eq!(res, aes_gcm_res);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,49 +10,19 @@ pub type Result<T, E = Error> = std::result::Result<T, E>;
|
|||||||
#[derive(thiserror::Error, Debug)]
|
#[derive(thiserror::Error, Debug)]
|
||||||
#[non_exhaustive]
|
#[non_exhaustive]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
#[cfg_attr(debug_assertions, error("Ultravisor: '{msg}' ({rc:#06x},{rrc:#06x})"))]
|
|
||||||
#[cfg_attr(not(debug_assertions), error("Ultravisor: '{msg}' ({rc:#06x})"))]
|
|
||||||
Uv {
|
|
||||||
rc: u16,
|
|
||||||
rrc: u16,
|
|
||||||
msg: &'static str,
|
|
||||||
},
|
|
||||||
|
|
||||||
#[error("Invalid SE header provided")]
|
#[error("Invalid SE header provided")]
|
||||||
#[cfg(feature = "request")]
|
|
||||||
InvBootHdr,
|
InvBootHdr,
|
||||||
|
|
||||||
#[error("{0}")]
|
|
||||||
Specification(String),
|
|
||||||
|
|
||||||
#[error("Cannot {ty} {ctx} at `{path}`")]
|
|
||||||
FileIo {
|
|
||||||
ty: FileIoErrorType,
|
|
||||||
ctx: String,
|
|
||||||
path: String,
|
|
||||||
source: std::io::Error,
|
|
||||||
},
|
|
||||||
#[error("Cannot {ty} `{path}`")]
|
|
||||||
FileAccess {
|
|
||||||
ty: FileAccessErrorType,
|
|
||||||
path: String,
|
|
||||||
source: std::io::Error,
|
|
||||||
},
|
|
||||||
|
|
||||||
#[error("Host-key verification failed: {0}")]
|
#[error("Host-key verification failed: {0}")]
|
||||||
#[cfg(feature = "request")]
|
|
||||||
HkdVerify(HkdVerifyErrorType),
|
HkdVerify(HkdVerifyErrorType),
|
||||||
|
|
||||||
#[error("No host-key provided")]
|
#[error("No host-key provided")]
|
||||||
#[cfg(feature = "request")]
|
|
||||||
NoHostkey,
|
NoHostkey,
|
||||||
|
|
||||||
#[error("To many host-keys provided")]
|
#[error("Too many host-keys provided")]
|
||||||
#[cfg(feature = "request")]
|
|
||||||
ManyHostkeys,
|
ManyHostkeys,
|
||||||
|
|
||||||
#[error("Cannot load {ty} from {path}")]
|
#[error("Cannot load {ty} from {path}")]
|
||||||
#[cfg(feature = "request")]
|
|
||||||
X509Load {
|
X509Load {
|
||||||
path: String,
|
path: String,
|
||||||
ty: &'static str,
|
ty: &'static str,
|
||||||
@@ -60,77 +30,67 @@ pub enum Error {
|
|||||||
},
|
},
|
||||||
|
|
||||||
#[error("Internal (unexpected) error: {0}, caused by {1}")]
|
#[error("Internal (unexpected) error: {0}, caused by {1}")]
|
||||||
#[cfg(feature = "request")]
|
|
||||||
InternalSsl(&'static str, #[source] openssl::error::ErrorStack),
|
InternalSsl(&'static str, #[source] openssl::error::ErrorStack),
|
||||||
|
|
||||||
|
#[error("Signing is only supported for EC and RSA keys")]
|
||||||
|
UnsupportedSigningKey,
|
||||||
|
|
||||||
|
#[error("Verifying signatures is only supported for EC and RSA keys")]
|
||||||
|
UnsupportedVerificationKey,
|
||||||
|
|
||||||
|
#[error("Provided binary request is too small")]
|
||||||
|
BinRequestSmall,
|
||||||
|
|
||||||
#[error("No Config UID found: {0}")]
|
#[error("No Config UID found: {0}")]
|
||||||
NoCuid(String),
|
NoCuid(String),
|
||||||
|
|
||||||
// errors from request types
|
// errors from request types
|
||||||
#[cfg(feature = "uvsecret")]
|
|
||||||
#[error("Customer Communication Key must be 32 bytes long")]
|
#[error("Customer Communication Key must be 32 bytes long")]
|
||||||
CckSize,
|
CckSize,
|
||||||
|
|
||||||
#[cfg(feature = "uvsecret")]
|
#[error("Invalid {0} user-data for signing provided. Max {} bytes allowed", .0.max())]
|
||||||
#[error("Cannot encode secrets (Too many secrets)")]
|
AsrcbInvSgnUserData(UserDataType),
|
||||||
ManySecrets,
|
|
||||||
|
|
||||||
#[cfg(feature = "uvsecret")]
|
#[error("Unsupported user data signing key provided. Only EC(secp521r1) and RSA(2048 & 3072 bit) are supported")]
|
||||||
#[error("Cannot decode secret list")]
|
BinAsrcbUnsupportedUserDataSgnKey,
|
||||||
InvSecretList(#[source] std::io::Error),
|
|
||||||
|
|
||||||
#[cfg(feature = "uvsecret")]
|
#[error("No user-key for verification provided and user-data is signed")]
|
||||||
#[error("Input does not contain an add-secret request")]
|
BinAsrcbNoUserDataSgnKey,
|
||||||
NoAsrcb,
|
|
||||||
|
#[error("Input does not contain an add-secret request version 1")]
|
||||||
|
BinAsrcbInvVersion,
|
||||||
|
|
||||||
|
#[error("Provided user-data key type ({key}) does not match with the user-data ({kind})")]
|
||||||
|
AsrcbUserDataKeyMismatch { key: String, kind: UserDataType },
|
||||||
|
|
||||||
|
#[error(
|
||||||
|
"The user-defined request signature could not be verified with the provided certificate"
|
||||||
|
)]
|
||||||
|
AsrcbUserDataSgnFail,
|
||||||
|
|
||||||
// errors from other crates
|
// errors from other crates
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
|
PvCore(#[from] pv_core::Error),
|
||||||
|
#[error(transparent)]
|
||||||
Io(#[from] std::io::Error),
|
Io(#[from] std::io::Error),
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
#[cfg(feature = "request")]
|
|
||||||
Crypto(#[from] openssl::error::ErrorStack),
|
Crypto(#[from] openssl::error::ErrorStack),
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
ParseInt(#[from] std::num::ParseIntError),
|
|
||||||
#[cfg(feature = "request")]
|
|
||||||
#[error(transparent)]
|
|
||||||
Curl(#[from] curl::Error),
|
Curl(#[from] curl::Error),
|
||||||
}
|
}
|
||||||
|
|
||||||
// used in macros
|
// used in macros
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
impl Error {
|
impl Error {
|
||||||
pub const CRL: &str = "CRL";
|
pub const CRL: &'static str = "CRL";
|
||||||
pub const CERT: &str = "certificate";
|
pub const CERT: &'static str = "certificate";
|
||||||
}
|
|
||||||
|
|
||||||
/// Error cases for I/O operations
|
|
||||||
#[allow(missing_docs)]
|
|
||||||
#[derive(thiserror::Error, Debug)]
|
|
||||||
#[non_exhaustive]
|
|
||||||
pub enum FileIoErrorType {
|
|
||||||
#[error("read")]
|
|
||||||
Read,
|
|
||||||
#[error("write")]
|
|
||||||
Write,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Error cases for accessing files
|
|
||||||
#[allow(missing_docs)]
|
|
||||||
#[derive(thiserror::Error, Debug)]
|
|
||||||
#[non_exhaustive]
|
|
||||||
pub enum FileAccessErrorType {
|
|
||||||
#[error("open")]
|
|
||||||
Open,
|
|
||||||
#[error("create")]
|
|
||||||
Create,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Error cases for verifying host-key documents
|
/// Error cases for verifying host-key documents
|
||||||
///
|
///
|
||||||
#[doc = crate::requires_feat!(request)]
|
|
||||||
#[allow(missing_docs)]
|
#[allow(missing_docs)]
|
||||||
#[derive(thiserror::Error, Debug, PartialEq, Eq)]
|
#[derive(thiserror::Error, Debug, PartialEq, Eq)]
|
||||||
#[non_exhaustive]
|
#[non_exhaustive]
|
||||||
#[cfg(feature = "request")]
|
|
||||||
pub enum HkdVerifyErrorType {
|
pub enum HkdVerifyErrorType {
|
||||||
#[error("Signature verification failed")]
|
#[error("Signature verification failed")]
|
||||||
Signature,
|
Signature,
|
||||||
@@ -160,37 +120,11 @@ pub enum HkdVerifyErrorType {
|
|||||||
IbmSignInvalid(#[source] openssl::x509::X509VerifyResult, u32),
|
IbmSignInvalid(#[source] openssl::x509::X509VerifyResult, u32),
|
||||||
}
|
}
|
||||||
|
|
||||||
macro_rules! path_to_str {
|
|
||||||
($path: expr) => {
|
|
||||||
$path.as_ref().to_str().unwrap_or("no UTF-8 path")
|
|
||||||
};
|
|
||||||
}
|
|
||||||
pub(crate) use path_to_str;
|
|
||||||
|
|
||||||
macro_rules! file_error {
|
|
||||||
($ty: tt, $ctx: expr, $path:expr, $src: expr) => {
|
|
||||||
$crate::Error::FileIo {
|
|
||||||
ty: $crate::FileIoErrorType::$ty,
|
|
||||||
ctx: $ctx.to_string(),
|
|
||||||
path: $path.to_string(),
|
|
||||||
source: $src,
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
pub(crate) use file_error;
|
|
||||||
|
|
||||||
#[cfg(feature = "request")]
|
|
||||||
macro_rules! bail_hkd_verify {
|
macro_rules! bail_hkd_verify {
|
||||||
($var: tt) => {
|
($var: tt) => {
|
||||||
return Err($crate::Error::HkdVerify($crate::HkdVerifyErrorType::$var))
|
return Err($crate::Error::HkdVerify($crate::HkdVerifyErrorType::$var))
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
#[cfg(feature = "request")]
|
|
||||||
pub(crate) use bail_hkd_verify;
|
pub(crate) use bail_hkd_verify;
|
||||||
|
|
||||||
macro_rules! bail_spec {
|
use crate::request::uvsecret::UserDataType;
|
||||||
($str: expr) => {
|
|
||||||
return Err($crate::Error::Specification($str.to_string()))
|
|
||||||
};
|
|
||||||
}
|
|
||||||
pub(crate) use bail_spec;
|
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
//
|
//
|
||||||
// Copyright IBM Corp. 2023
|
// Copyright IBM Corp. 2023
|
||||||
|
|
||||||
#![allow(macro_expanded_macro_exports_accessed_by_absolute_paths)]
|
|
||||||
#![deny(missing_docs)]
|
#![deny(missing_docs)]
|
||||||
//! pv - library for pv-tools
|
//! pv - library for pv-tools
|
||||||
//!
|
//!
|
||||||
@@ -11,155 +10,85 @@
|
|||||||
//! `pv` provides abstraction layers for encryption, secure memory management,
|
//! `pv` provides abstraction layers for encryption, secure memory management,
|
||||||
//! logging, and accessing the uvdevice.
|
//! logging, and accessing the uvdevice.
|
||||||
//!
|
//!
|
||||||
//! ## Feature Flags
|
//! If you do not need any OpenSSL features use `pv_core`.
|
||||||
//! The following feature flags are available:
|
//! This crate reexports all symbols from `pv_core`
|
||||||
//! - `request`
|
mod brcb;
|
||||||
//! - optional
|
mod cli;
|
||||||
//! - Enables generation of UV requests
|
mod crypto;
|
||||||
//! - `uvsecret`
|
|
||||||
//! - optional
|
|
||||||
//! - Enables support for the UV Secret API.
|
|
||||||
mod error;
|
mod error;
|
||||||
mod log;
|
mod req;
|
||||||
|
mod secret;
|
||||||
mod utils;
|
mod utils;
|
||||||
mod uvdevice;
|
mod uvsecret;
|
||||||
|
mod verify;
|
||||||
|
|
||||||
/// Internal macro to conveninetly document required features on items
|
/// utility functions for writing TESTS!!!
|
||||||
// #[macro_export]
|
//hide any test helpers on docs!
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
macro_rules! requires_feat {
|
|
||||||
(request) => {
|
|
||||||
" Requires the feature `request`"
|
|
||||||
};
|
|
||||||
(uvsecret) => {
|
|
||||||
" Requires the feature `uvsecret`"
|
|
||||||
};
|
|
||||||
(reqsecret) => {
|
|
||||||
"Requires the features `request` & `uvsecret`"
|
|
||||||
};
|
|
||||||
}
|
|
||||||
#[allow(unused_imports)]
|
|
||||||
use requires_feat;
|
|
||||||
|
|
||||||
//only some features need this
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
|
pub mod test_utils;
|
||||||
|
|
||||||
|
pub use ::utils::assert_size;
|
||||||
|
pub use ::utils::static_assert;
|
||||||
|
|
||||||
const PAGESIZE: usize = 0x1000;
|
const PAGESIZE: usize = 0x1000;
|
||||||
|
|
||||||
cfg_if::cfg_if! {
|
|
||||||
if #[cfg(feature = "request")] {
|
|
||||||
mod brcb;
|
|
||||||
mod cli;
|
|
||||||
mod crypto;
|
|
||||||
mod req;
|
|
||||||
mod secret;
|
|
||||||
mod uvsecret;
|
|
||||||
mod verify;
|
|
||||||
|
|
||||||
/// utility functions for writing TESTS!!!
|
|
||||||
#[allow(dead_code)]
|
|
||||||
//hide any test helpers on docs!
|
|
||||||
#[doc(hidden)]
|
|
||||||
pub mod test_utils;
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/// Definitions and functions for interacting with the Ultravisor
|
/// Definitions and functions for interacting with the Ultravisor
|
||||||
pub mod uv {
|
pub mod uv {
|
||||||
pub use crate::uvdevice::{
|
pub use pv_core::uv::{
|
||||||
uv_ioctl, ConfigUid, UvCmd, UvDevice, UvDeviceInfo, UvFlags, UvcSuccess,
|
uv_ioctl, ConfigUid, UvCmd, UvDevice, UvDeviceInfo, UvFlags, UvcSuccess,
|
||||||
};
|
};
|
||||||
#[cfg(feature = "uvsecret")]
|
pub use pv_core::uv::{AddCmd, ListCmd, LockCmd};
|
||||||
pub use crate::uvsecret::{
|
pub use pv_core::uv::{ListableSecretType, SecretEntry, SecretList};
|
||||||
secret_list::SecretList,
|
|
||||||
uvc::{AddCmd, ListCmd, LockCmd},
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Miscellaneous functions and definitions
|
/// Miscellaneous functions and definitions
|
||||||
pub mod misc {
|
pub mod misc {
|
||||||
|
|
||||||
#[cfg(feature = "request")]
|
|
||||||
pub use crate::cli::{
|
pub use crate::cli::{
|
||||||
get_reader_from_cli_file_arg, get_writer_from_cli_file_arg, CertificateOptions, STDIN,
|
get_reader_from_cli_file_arg, get_writer_from_cli_file_arg, CertificateOptions, STDIN,
|
||||||
STDOUT,
|
STDOUT,
|
||||||
};
|
};
|
||||||
pub use crate::log::PvLogger;
|
pub use crate::utils::{read_certs, read_crls, read_private_key};
|
||||||
pub use crate::utils::{
|
pub use pv_core::misc::*;
|
||||||
memeq, parse_hex, pv_guest_bit_set, read, read_exact_file, read_file, to_u16, to_u32,
|
pub use pv_core::PvLogger;
|
||||||
try_parse_u128, try_parse_u64, write, write_file, Flags, Lsb0Flags64, Msb0Flags64,
|
|
||||||
};
|
|
||||||
#[cfg(feature = "request")]
|
|
||||||
pub use crate::utils::{read_certs, read_crls};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "request")]
|
|
||||||
pub use crate::error::HkdVerifyErrorType;
|
pub use crate::error::HkdVerifyErrorType;
|
||||||
pub use error::{Error, FileAccessErrorType, FileIoErrorType, Result};
|
pub use error::{Error, Result};
|
||||||
|
|
||||||
/// Functionalities to build UV requests
|
/// Functionalities to build UV requests
|
||||||
#[doc = requires_feat!(request)]
|
|
||||||
pub mod request {
|
pub mod request {
|
||||||
|
pub use crate::brcb::{BootHdrMagic, BootHdrTags};
|
||||||
|
pub use crate::crypto::derive_key;
|
||||||
|
pub use crate::crypto::random_array;
|
||||||
|
pub use crate::crypto::{encrypt_aes_gcm, gen_ec_key};
|
||||||
|
pub use crate::crypto::{hash, hkdf_rfc_5869};
|
||||||
|
pub use crate::crypto::{sign_msg, verify_signature};
|
||||||
|
pub use crate::crypto::{Aes256Key, SymKey, SymKeyType};
|
||||||
|
pub use crate::req::{Aad, BinReqValues, Encrypt, Keyslot, ReqEncrCtx, Request};
|
||||||
|
pub use crate::secret::{Secret, Zeroize};
|
||||||
|
pub use crate::verify::{CertVerifier, HkdVerifier, NoVerifyHkd};
|
||||||
|
|
||||||
cfg_if::cfg_if! {
|
/// Reexports some useful OpenSSL symbols
|
||||||
if #[cfg(feature = "request")] {
|
pub mod openssl {
|
||||||
pub use crate::brcb::{BootHdrTags, BootHdrMagic};
|
pub use openssl::error::ErrorStack;
|
||||||
pub use crate::crypto::{
|
pub use openssl::hash::MessageDigest;
|
||||||
derive_key, encrypt_aes, encrypt_aes_gcm, gen_ec_key, hash, hkdf_rfc_5869,
|
pub use openssl::md::Md;
|
||||||
random_array, Aes256Key, SymKey, SymKeyType,
|
pub use openssl::pkey;
|
||||||
};
|
|
||||||
pub use crate::req::{Aad, Encrypt, Keyslot, ReqEncrCtx, Request};
|
|
||||||
pub use crate::secret::{Secret, Zeroize};
|
|
||||||
pub use crate::verify::HkdVerifier;
|
|
||||||
|
|
||||||
/// Reexports some useful OpenSSL symbols
|
|
||||||
///
|
|
||||||
#[doc = requires_feat!(request)]
|
|
||||||
pub mod openssl {
|
|
||||||
pub use openssl::error::ErrorStack;
|
|
||||||
pub use openssl::hash::MessageDigest;
|
|
||||||
pub use openssl::md::Md;
|
|
||||||
pub use openssl::pkey;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
cfg_if::cfg_if! {
|
/// Functionalities for creating add-secret requests
|
||||||
if #[cfg(feature = "uvsecret")] {
|
pub mod uvsecret {
|
||||||
/// Functionalities for creating add-secret requests
|
pub use crate::uvsecret::{
|
||||||
pub mod uvsecret {
|
asrcb::{AddSecretFlags, AddSecretRequest, AddSecretVersion},
|
||||||
#[cfg(feature = "request")]
|
ext_secret::ExtSecret,
|
||||||
pub use crate::uvsecret::{
|
guest_secret::GuestSecret,
|
||||||
asrcb::{AddSecretFlags, AddSecretRequest, AddSecretVersion,},
|
user_data::verify_asrcb_and_get_user_data,
|
||||||
ext_secret::ExtSecret,
|
};
|
||||||
guest_secret::GuestSecret,
|
pub use pv_core::request::uvsecret::AddSecretMagic;
|
||||||
};
|
pub use pv_core::request::uvsecret::UserDataType;
|
||||||
pub use crate::uvsecret::AddSecretMagic;
|
|
||||||
pub use crate::uvsecret::UserDataType;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Version number of the request in system-endian
|
|
||||||
pub type RequestVersion = u32;
|
|
||||||
/// Request magic value
|
|
||||||
///
|
|
||||||
/// The first 8 byte of a request providing an identifier of the request type
|
|
||||||
/// for programs
|
|
||||||
pub type RequestMagic = [u8; 8];
|
|
||||||
/// A `MagicValue` is a bytepattern, that indicates if a byte slice contains the specified
|
|
||||||
/// (binary) data.
|
|
||||||
pub trait MagicValue<const N: usize> {
|
|
||||||
/// Magic value as byte array
|
|
||||||
const MAGIC: [u8; N];
|
|
||||||
/// Test whether the given slice starts with the magic value.
|
|
||||||
fn starts_with_magic(v: &[u8]) -> bool {
|
|
||||||
if v.len() < Self::MAGIC.len() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
crate::misc::memeq(&v[..Self::MAGIC.len()], &Self::MAGIC)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
pub use pv_core::request::RequestMagic;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Provides cargo version Info about this crate.
|
/// Provides cargo version Info about this crate.
|
||||||
@@ -168,39 +97,3 @@ pub mod request {
|
|||||||
pub const fn crate_info() -> &'static str {
|
pub const fn crate_info() -> &'static str {
|
||||||
concat!(env!("CARGO_PKG_NAME"), "-crate ", env!("CARGO_PKG_VERSION"))
|
concat!(env!("CARGO_PKG_NAME"), "-crate ", env!("CARGO_PKG_VERSION"))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[doc(hidden)]
|
|
||||||
#[macro_export]
|
|
||||||
macro_rules! file_acc_error {
|
|
||||||
($ty: tt, $path:expr, $src: expr) => {
|
|
||||||
$crate::Error::FileAccess {
|
|
||||||
ty: $crate::FileAccessErrorType::$ty,
|
|
||||||
path: $path.to_string(),
|
|
||||||
source: $src,
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
#[macro_export]
|
|
||||||
/// Create a file wrapped in a [BufWriter]
|
|
||||||
///
|
|
||||||
/// [BufWriter]: std::io#BufWriter
|
|
||||||
macro_rules! create_buffered_file {
|
|
||||||
($path: expr) => {
|
|
||||||
std::io::BufWriter::new(
|
|
||||||
std::fs::File::create($path).map_err(|e| $crate::file_acc_error!(Create, $path, e))?,
|
|
||||||
)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
#[macro_export]
|
|
||||||
/// Open a file wrapped in a [BufReader]
|
|
||||||
///
|
|
||||||
/// [BufReader]: std::io#BufReader
|
|
||||||
macro_rules! open_buffered_file {
|
|
||||||
($path: expr) => {
|
|
||||||
std::io::BufReader::new(
|
|
||||||
std::fs::File::open($path).map_err(|e| $crate::file_acc_error!(Open, $path, e))?,
|
|
||||||
)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -2,19 +2,21 @@
|
|||||||
//
|
//
|
||||||
// Copyright IBM Corp. 2023
|
// Copyright IBM Corp. 2023
|
||||||
|
|
||||||
|
use crate::crypto::AES_256_GCM_TAG_SIZE;
|
||||||
use crate::misc::to_u32;
|
use crate::misc::to_u32;
|
||||||
use crate::request::{
|
use crate::request::{derive_key, encrypt_aes_gcm, gen_ec_key, random_array, SymKey, SymKeyType};
|
||||||
derive_key, encrypt_aes, encrypt_aes_gcm, gen_ec_key, random_array, RequestMagic,
|
|
||||||
RequestVersion, SymKey, SymKeyType,
|
|
||||||
};
|
|
||||||
use crate::{Error, Result};
|
use crate::{Error, Result};
|
||||||
use openssl::bn::{BigNum, BigNumContext};
|
use openssl::bn::{BigNum, BigNumContext};
|
||||||
use openssl::ec::{EcGroupRef, EcPointRef};
|
use openssl::ec::{EcGroupRef, EcPointRef};
|
||||||
use openssl::error::ErrorStack;
|
use openssl::error::ErrorStack;
|
||||||
use openssl::hash::{hash, MessageDigest};
|
use openssl::hash::{hash, MessageDigest};
|
||||||
use openssl::pkey::{PKey, PKeyRef, Private, Public};
|
use openssl::pkey::{PKey, PKeyRef, Private, Public};
|
||||||
|
use pv_core::request::{RequestMagic, RequestVersion};
|
||||||
use std::convert::TryInto;
|
use std::convert::TryInto;
|
||||||
use zerocopy::{AsBytes, BigEndian, FromBytes, U32};
|
use std::mem::size_of;
|
||||||
|
use std::ops::Range;
|
||||||
|
use utils::assert_size;
|
||||||
|
use zerocopy::{AsBytes, BigEndian, FromBytes, FromZeroes, U32};
|
||||||
|
|
||||||
/// Encrypt a _secret_ using self and a given private key.
|
/// Encrypt a _secret_ using self and a given private key.
|
||||||
pub trait Encrypt {
|
pub trait Encrypt {
|
||||||
@@ -94,7 +96,7 @@ impl Encrypt for Keyslot {
|
|||||||
to: &mut Vec<u8>,
|
to: &mut Vec<u8>,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let derived_key = derive_key(priv_key, &self.0)?;
|
let derived_key = derive_key(priv_key, &self.0)?;
|
||||||
let mut wrpk_and_kst = encrypt_aes_gcm(&derived_key.into(), &[0; 12], &[], prot_key)?;
|
let (mut wrpk_and_kst, ..) = encrypt_aes_gcm(&derived_key.into(), &[0; 12], &[], prot_key)?;
|
||||||
let phk: EcdhPubkeyCoord = self.0.as_ref().try_into()?;
|
let phk: EcdhPubkeyCoord = self.0.as_ref().try_into()?;
|
||||||
|
|
||||||
to.reserve(80);
|
to.reserve(80);
|
||||||
@@ -215,10 +217,11 @@ impl ReqEncrCtx {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let rql = to_u32(auth_data.len() + encr_size + 16)
|
let rql = to_u32(auth_data.len() + encr_size + 16).ok_or_else(|| {
|
||||||
.ok_or_else(|| Error::Specification("Configured request size to large".to_string()))?;
|
pv_core::Error::Specification("Configured request size to large".to_string())
|
||||||
|
})?;
|
||||||
let sea = to_u32(encr_size)
|
let sea = to_u32(encr_size)
|
||||||
.ok_or_else(|| Error::Specification("Encrypted size to large".to_string()))?;
|
.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);
|
let req_hdr = RequestHdr::new(version, rql, self.iv, nks, sea, magic);
|
||||||
// copy request header to the start of the request
|
// copy request header to the start of the request
|
||||||
@@ -235,17 +238,6 @@ impl ReqEncrCtx {
|
|||||||
self.priv_key.as_ref().try_into().map_err(Error::Crypto)
|
self.priv_key.as_ref().try_into().map_err(Error::Crypto)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Encrypt confidential Data with this encryption context.
|
|
||||||
///
|
|
||||||
/// * `conf` - data to be encrypted
|
|
||||||
///
|
|
||||||
/// # Errors
|
|
||||||
///
|
|
||||||
/// This function will return an error if the data could not be encrypted by OpenSSL.
|
|
||||||
pub fn encrypt(&self, conf: &[u8]) -> Result<Vec<u8>> {
|
|
||||||
encrypt_aes(&self.prot_key, &self.iv, conf)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Encrypt confidential Data with this encryption context and provide a gcm tag.
|
/// Encrypt confidential Data with this encryption context and provide a gcm tag.
|
||||||
///
|
///
|
||||||
/// * `aad` - additional authentic data
|
/// * `aad` - additional authentic data
|
||||||
@@ -260,7 +252,11 @@ impl ReqEncrCtx {
|
|||||||
/// # Errors
|
/// # Errors
|
||||||
///
|
///
|
||||||
/// This function will return an error if the data could not be encrypted by OpenSSL.
|
/// This function will return an error if the data could not be encrypted by OpenSSL.
|
||||||
pub fn encrypt_aead(&self, aad: &[u8], conf: &[u8]) -> Result<Vec<u8>> {
|
pub fn encrypt_aead(
|
||||||
|
&self,
|
||||||
|
aad: &[u8],
|
||||||
|
conf: &[u8],
|
||||||
|
) -> Result<(Vec<u8>, Range<usize>, Range<usize>, Range<usize>)> {
|
||||||
encrypt_aes_gcm(&self.prot_key, &self.iv, aad, conf)
|
encrypt_aes_gcm(&self.prot_key, &self.iv, aad, conf)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -309,7 +305,7 @@ ecdh_from!(Public);
|
|||||||
/// Representation of the shared parts of the request header.
|
/// Representation of the shared parts of the request header.
|
||||||
/// Used by [`ReqEncrCtx`]
|
/// Used by [`ReqEncrCtx`]
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
#[derive(Debug, Copy, Clone, AsBytes, FromBytes)]
|
#[derive(Debug, Copy, Clone, AsBytes, FromBytes, FromZeroes)]
|
||||||
struct RequestHdr {
|
struct RequestHdr {
|
||||||
magic: [u8; 8],
|
magic: [u8; 8],
|
||||||
rqvn: U32<BigEndian>,
|
rqvn: U32<BigEndian>,
|
||||||
@@ -321,6 +317,7 @@ struct RequestHdr {
|
|||||||
reserved28: u32,
|
reserved28: u32,
|
||||||
sea: U32<BigEndian>,
|
sea: U32<BigEndian>,
|
||||||
}
|
}
|
||||||
|
assert_size!(RequestHdr, 48);
|
||||||
|
|
||||||
impl RequestHdr {
|
impl RequestHdr {
|
||||||
fn new(rqvn: u32, rql: u32, iv: [u8; 12], nks: u8, sea: u32, magic: Option<[u8; 8]>) -> Self {
|
fn new(rqvn: u32, rql: u32, iv: [u8; 12], nks: u8, sea: u32, magic: Option<[u8; 8]>) -> Self {
|
||||||
@@ -379,6 +376,67 @@ pub trait Request {
|
|||||||
fn add_hostkey(&mut self, hostkey: PKey<Public>);
|
fn add_hostkey(&mut self, hostkey: PKey<Public>);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A struct to represent some parts of a binary/encrypted request.
|
||||||
|
#[derive(Debug)]
|
||||||
|
#[allow(unused)]
|
||||||
|
#[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 = AES_256_GCM_TAG_SIZE;
|
||||||
|
|
||||||
|
/// Get the locations from this request.
|
||||||
|
///
|
||||||
|
/// Does minimal sanity test, just tests to prevent panics.
|
||||||
|
/// `req` may be larger than the actual request.
|
||||||
|
pub fn get(req: &'a [u8]) -> Result<Self> {
|
||||||
|
let hdr = RequestHdr::read_from_prefix(req).ok_or(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 fn version(&self) -> u32 {
|
||||||
|
self.version
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the length of this [`BinReqValues`].
|
||||||
|
pub fn len(&self) -> usize {
|
||||||
|
self.len
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|||||||
@@ -1,336 +1,18 @@
|
|||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
//
|
//
|
||||||
// Copyright IBM Corp. 2023
|
// Copyright IBM Corp. 2023
|
||||||
|
use crate::{Error, Result};
|
||||||
use crate::{
|
use openssl::{
|
||||||
error::{bail_spec, file_error, path_to_str},
|
pkey::{PKey, Private},
|
||||||
Error, FileIoErrorType, Result,
|
x509::{X509Crl, X509},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[cfg(feature = "request")]
|
|
||||||
use openssl::x509::X509Crl;
|
|
||||||
#[cfg(feature = "request")]
|
|
||||||
use openssl::x509::X509;
|
|
||||||
use std::io::{Read, Write};
|
|
||||||
use std::path::Path;
|
|
||||||
use zerocopy::{AsBytes, BigEndian, FromBytes, U64};
|
|
||||||
|
|
||||||
/// Asserts a constant expression evaluates to `true`.
|
|
||||||
///
|
|
||||||
/// If the expression is not evaluated to `true` the compilation will fail.
|
|
||||||
#[macro_export]
|
|
||||||
macro_rules! static_assert {
|
|
||||||
($condition:expr) => {
|
|
||||||
const _: () = core::assert!($condition);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Asserts that a type has a specific size.
|
|
||||||
///
|
|
||||||
/// Useful to validate structs that are passed to C code.
|
|
||||||
/// If the expression is not evaluated to `true` the compilation will fail.
|
|
||||||
///
|
|
||||||
/// # Example
|
|
||||||
/// ```rust
|
|
||||||
/// # use pv::assert_size;
|
|
||||||
/// # fn main() {}
|
|
||||||
/// #[repr(C)]
|
|
||||||
/// struct c_struct {
|
|
||||||
/// v: u64,
|
|
||||||
/// }
|
|
||||||
/// assert_size!(c_struct, 8);
|
|
||||||
/// // assert_size!(c_struct, 7);//won't compile
|
|
||||||
/// ```
|
|
||||||
#[macro_export]
|
|
||||||
macro_rules! assert_size {
|
|
||||||
($t:ty, $sz:expr ) => {
|
|
||||||
$crate::static_assert!(::std::mem::size_of::<$t>() == $sz);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Trait that describes bitflags, represented by `T`.
|
|
||||||
pub trait Flags<T>: From<T> + for<'a> From<&'a T> {
|
|
||||||
/// Set the specified bit to one.
|
|
||||||
/// # Panics
|
|
||||||
///Panics if bit is >= 64
|
|
||||||
fn set_bit(&mut self, bit: u8);
|
|
||||||
/// Set the specified bit to zero.
|
|
||||||
/// # Panics
|
|
||||||
///Panics if bit is >= 64
|
|
||||||
fn unset_bit(&mut self, bit: u8);
|
|
||||||
/// Test if the specified bit is set.
|
|
||||||
/// # Panics
|
|
||||||
///Panics if bit is >= 64
|
|
||||||
fn is_set(&self, bit: u8) -> bool;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Bitflags in MSB0 ordering
|
|
||||||
///
|
|
||||||
/// Wraps an u64 to set/get individual bits
|
|
||||||
#[repr(C)]
|
|
||||||
#[derive(Debug, Clone, Copy, Default, AsBytes, FromBytes)]
|
|
||||||
pub struct Msb0Flags64(U64<BigEndian>);
|
|
||||||
impl Flags<u64> for Msb0Flags64 {
|
|
||||||
#[track_caller]
|
|
||||||
fn set_bit(&mut self, bit: u8) {
|
|
||||||
assert!(bit < 64, "Flag bit set to greater than 63");
|
|
||||||
let mut v = self.0.get();
|
|
||||||
v |= 1 << (63 - bit);
|
|
||||||
self.0.set(v)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[track_caller]
|
|
||||||
fn unset_bit(&mut self, bit: u8) {
|
|
||||||
assert!(bit < 64, "Flag bit set to greater than 63");
|
|
||||||
let mut v = self.0.get();
|
|
||||||
v &= !(1 << (63 - bit));
|
|
||||||
self.0.set(v)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[track_caller]
|
|
||||||
fn is_set(&self, bit: u8) -> bool {
|
|
||||||
assert!(bit < 64, "Flag bit set to greater than 63");
|
|
||||||
self.0.get() & (1 << (63 - bit)) > 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<u64> for Msb0Flags64 {
|
|
||||||
fn from(value: u64) -> Self {
|
|
||||||
Self(value.into())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<&u64> for Msb0Flags64 {
|
|
||||||
fn from(value: &u64) -> Self {
|
|
||||||
(*value).into()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Bitflags in LSB0 ordering
|
|
||||||
///
|
|
||||||
/// Wraps an u64 to set/get individual bits
|
|
||||||
#[repr(C)]
|
|
||||||
#[derive(Debug, Clone, Copy, Default, AsBytes, FromBytes)]
|
|
||||||
pub struct Lsb0Flags64(U64<BigEndian>);
|
|
||||||
impl Flags<u64> for Lsb0Flags64 {
|
|
||||||
#[track_caller]
|
|
||||||
fn set_bit(&mut self, bit: u8) {
|
|
||||||
assert!(bit < 64, "Flag bit set to greater than 63");
|
|
||||||
let mut v = self.0.get();
|
|
||||||
v |= 1 << bit;
|
|
||||||
self.0.set(v)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[track_caller]
|
|
||||||
fn unset_bit(&mut self, bit: u8) {
|
|
||||||
assert!(bit < 64, "Flag bit set to greater than 63");
|
|
||||||
let mut v = self.0.get();
|
|
||||||
v &= !(1 << bit);
|
|
||||||
self.0.set(v)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[track_caller]
|
|
||||||
fn is_set(&self, bit: u8) -> bool {
|
|
||||||
assert!(bit < 64, "Flag bit set to greater than 63");
|
|
||||||
self.0.get() & (1 << bit) > 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<u64> for Lsb0Flags64 {
|
|
||||||
fn from(value: u64) -> Self {
|
|
||||||
Self(value.into())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<&u64> for Lsb0Flags64 {
|
|
||||||
fn from(value: &u64) -> Self {
|
|
||||||
(*value).into()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Tries to convert a BE hex string into a 128 unsigned integer
|
|
||||||
/// The hexstring must contain 32chars of hexdigits
|
|
||||||
///
|
|
||||||
/// * `hex_str` - string to convert can be prepended with "0x"
|
|
||||||
/// * `ctx` - Error context string in case of an error
|
|
||||||
/// ```rust
|
|
||||||
/// # use std::error::Error;
|
|
||||||
/// # use pv::misc::try_parse_u128;
|
|
||||||
/// # fn main() -> Result<(), Box<dyn Error>> {
|
|
||||||
/// let hex = "11223344556677889900aabbccddeeff";
|
|
||||||
/// try_parse_u128(&hex, "The test")?;
|
|
||||||
/// # Ok(())
|
|
||||||
/// # }
|
|
||||||
/// ```
|
|
||||||
///
|
|
||||||
/// # Errors
|
|
||||||
/// If `hex_string` is not a 32 byte hex string an Error appears
|
|
||||||
pub fn try_parse_u128(hex_str: &str, ctx: &str) -> Result<[u8; 16]> {
|
|
||||||
let hex_str = if hex_str.starts_with("0x") {
|
|
||||||
hex_str.split_at(2).1
|
|
||||||
} else {
|
|
||||||
hex_str
|
|
||||||
};
|
|
||||||
if hex_str.len() != 32 {
|
|
||||||
bail_spec!(format!(
|
|
||||||
"{ctx} hexstring must be 32chars long to cover all 16 bytes"
|
|
||||||
));
|
|
||||||
}
|
|
||||||
parse_hex(hex_str).try_into().map_err(|_| {
|
|
||||||
Error::Specification(format!(
|
|
||||||
"{ctx} hexstring must be 32chars long to cover all 16 bytes"
|
|
||||||
))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Tries to convert a BE hex string into a 64 unsigned integer
|
|
||||||
/// The hexstring must *NOT* contain 16 chars of hexdigits, but
|
|
||||||
/// 16 chars at most.
|
|
||||||
///
|
|
||||||
/// * `hex_str` - string to convert can be prepended with "0x"
|
|
||||||
/// * `ctx` - Error context string in case of an error
|
|
||||||
/// ```rust
|
|
||||||
/// # use std::error::Error;
|
|
||||||
/// # use pv::misc::try_parse_u64;
|
|
||||||
/// # fn main() -> Result<(), Box<dyn Error>> {
|
|
||||||
/// let hex = "1234567890abcdef";
|
|
||||||
/// try_parse_u64(&hex, "The test")?;
|
|
||||||
/// # Ok(())
|
|
||||||
/// # }
|
|
||||||
/// ```
|
|
||||||
///
|
|
||||||
/// # Errors
|
|
||||||
/// If `hex_string` is not a 32 byte hex string an Error appears
|
|
||||||
pub fn try_parse_u64(hex_str: &str, ctx: &str) -> Result<u64> {
|
|
||||||
let hex_str = if hex_str.starts_with("0x") {
|
|
||||||
hex_str.split_at(2).1
|
|
||||||
} else {
|
|
||||||
hex_str
|
|
||||||
};
|
|
||||||
if hex_str.len() > 16 {
|
|
||||||
bail_spec!(format!(
|
|
||||||
"{ctx} hexstring {hex_str} must be max 16 chars long"
|
|
||||||
));
|
|
||||||
}
|
|
||||||
Ok(u64::from_str_radix(hex_str, 16)?)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Read exactly COUNT bytes into the buffer.
|
|
||||||
///
|
|
||||||
/// * `path` - Path to file
|
|
||||||
/// * `ctx` - Error context string in case of an error
|
|
||||||
///
|
|
||||||
/// # Errors
|
|
||||||
/// If this function encounters an "end of file" before completely filling
|
|
||||||
/// the buffer, it returns an error. The contents of `buf` are unspecified in this case.
|
|
||||||
///
|
|
||||||
/// If any other read error is encountered then this function immediately
|
|
||||||
/// returns. The contents of `buf` are unspecified in this case.
|
|
||||||
///
|
|
||||||
/// If this function returns an error, it is unspecified how many bytes it
|
|
||||||
/// has read, but it will never read more than would be necessary to
|
|
||||||
/// completely fill the buffer.
|
|
||||||
pub fn read_exact_file<P: AsRef<Path>, const COUNT: usize>(
|
|
||||||
path: P,
|
|
||||||
ctx: &str,
|
|
||||||
) -> Result<[u8; COUNT]> {
|
|
||||||
let mut f = std::fs::File::open(&path).map_err(|e| Error::FileAccess {
|
|
||||||
ty: crate::FileAccessErrorType::Open,
|
|
||||||
path: path_to_str!(path).to_string(),
|
|
||||||
source: e,
|
|
||||||
})?;
|
|
||||||
|
|
||||||
if f.metadata()?.len() as usize != COUNT {
|
|
||||||
bail_spec!(format!("{ctx} must be exactly {COUNT} bytes long"));
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut buf = [0; COUNT];
|
|
||||||
f.read_exact(&mut buf)
|
|
||||||
.map_err(|e| file_error!(Read, ctx, path_to_str!(path).to_string(), e))?;
|
|
||||||
Ok(buf)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Read content from a file and add context in case of an error
|
|
||||||
///
|
|
||||||
/// * `path` - Path to file
|
|
||||||
/// * `ctx` - Error context string in case of an error
|
|
||||||
///
|
|
||||||
///
|
|
||||||
/// # Errors
|
|
||||||
/// Passes through any kind of error `std::fs::read` produces
|
|
||||||
pub fn read_file<P: AsRef<Path>>(path: P, ctx: &str) -> Result<Vec<u8>> {
|
|
||||||
std::fs::read(&path).map_err(|e| {
|
|
||||||
file_error!(
|
|
||||||
Read,
|
|
||||||
ctx,
|
|
||||||
path.as_ref().to_str().unwrap_or("no UTF-8 path"),
|
|
||||||
e
|
|
||||||
)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Reads all content from a [`std::io::Read`] and add context in case of an error
|
|
||||||
///
|
|
||||||
/// * `path` - Path to file
|
|
||||||
/// * `ctx` - Error context string in case of an error
|
|
||||||
///
|
|
||||||
///
|
|
||||||
/// # Errors
|
|
||||||
/// Passes through any kind of error `std::fs::write` produces
|
|
||||||
pub fn read<R: Read>(rd: &mut R, path: &str, ctx: &str) -> Result<Vec<u8>> {
|
|
||||||
let mut buf = vec![];
|
|
||||||
rd.read_to_end(&mut buf).map_err(|e| Error::FileIo {
|
|
||||||
ty: FileIoErrorType::Write,
|
|
||||||
ctx: ctx.to_string(),
|
|
||||||
path: path.to_string(),
|
|
||||||
source: e,
|
|
||||||
})?;
|
|
||||||
Ok(buf)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// write content to a file and add context in case of an error
|
|
||||||
///
|
|
||||||
/// * `path` - Path to file
|
|
||||||
/// * `ctx` - Error context string in case of an error
|
|
||||||
///
|
|
||||||
///
|
|
||||||
/// # Errors
|
|
||||||
/// Passes through any kind of error `std::fs::write` produces
|
|
||||||
pub fn write_file<D: AsRef<[u8]>>(path: &str, data: D, ctx: &str) -> Result<()> {
|
|
||||||
std::fs::write(path, data.as_ref()).map_err(|e| Error::FileIo {
|
|
||||||
ty: FileIoErrorType::Write,
|
|
||||||
ctx: ctx.to_string(),
|
|
||||||
path: path.to_string(),
|
|
||||||
source: e,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Write content to a [`std::io::Write`] and add context in case of an error
|
|
||||||
///
|
|
||||||
/// * `path` - Path to file
|
|
||||||
/// * `ctx` - Error context string in case of an error
|
|
||||||
///
|
|
||||||
///
|
|
||||||
/// # Errors
|
|
||||||
/// Passes through any kind of error `std::fs::write` produces
|
|
||||||
pub fn write<D: AsRef<[u8]>, W: Write>(wr: &mut W, data: D, path: &str, ctx: &str) -> Result<()> {
|
|
||||||
wr.write_all(data.as_ref()).map_err(|e| Error::FileIo {
|
|
||||||
ty: FileIoErrorType::Write,
|
|
||||||
ctx: ctx.to_string(),
|
|
||||||
path: path.to_string(),
|
|
||||||
source: e,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Read all CRLs from the buffer and parse them into a vector.
|
/// Read all CRLs from the buffer and parse them into a vector.
|
||||||
///
|
///
|
||||||
/// # Errors
|
/// # Errors
|
||||||
///
|
///
|
||||||
/// This function will return an error if the underlying openssl implementation cannot parse `buf`
|
/// This function will return an error if the underlying OpenSSL implementation cannot parse `buf`
|
||||||
/// as `DER` or `PEM`.
|
/// as `DER` or `PEM`.
|
||||||
///
|
|
||||||
/// Requires the `request` feature.
|
|
||||||
#[cfg(feature = "request")]
|
|
||||||
pub fn read_crls(buf: &[u8]) -> Result<Vec<X509Crl>> {
|
pub fn read_crls(buf: &[u8]) -> Result<Vec<X509Crl>> {
|
||||||
use openssl_extensions::crl::StackableX509Crl;
|
use openssl_extensions::crl::StackableX509Crl;
|
||||||
X509Crl::from_der(buf)
|
X509Crl::from_der(buf)
|
||||||
@@ -343,11 +25,8 @@ pub fn read_crls(buf: &[u8]) -> Result<Vec<X509Crl>> {
|
|||||||
///
|
///
|
||||||
/// # Errors
|
/// # Errors
|
||||||
///
|
///
|
||||||
/// This function will return an error if the underlying openssl implementation cannot parse `buf`
|
/// This function will return an error if the underlying OpenSSL implementation cannot parse `buf`
|
||||||
/// as `DER` or `PEM`.
|
|
||||||
///
|
|
||||||
/// Requires the `request` feature.
|
|
||||||
#[cfg(feature = "request")]
|
|
||||||
pub fn read_certs(buf: &[u8]) -> Result<Vec<X509>> {
|
pub fn read_certs(buf: &[u8]) -> Result<Vec<X509>> {
|
||||||
X509::from_der(buf)
|
X509::from_der(buf)
|
||||||
.map(|crt| vec![crt])
|
.map(|crt| vec![crt])
|
||||||
@@ -355,191 +34,23 @@ pub fn read_certs(buf: &[u8]) -> Result<Vec<X509>> {
|
|||||||
.map_err(Error::Crypto)
|
.map_err(Error::Crypto)
|
||||||
}
|
}
|
||||||
|
|
||||||
macro_rules! usize_to_ui {
|
/// Read+parse the first key from the buffer.
|
||||||
($(#[$attr:meta])* => $t: ident, $name:ident) => {
|
|
||||||
///Converts an [`usize`] to an [`
|
|
||||||
$(#[$attr])*
|
|
||||||
///`] if possible
|
|
||||||
pub fn $name(u: usize) -> Option<$t> {
|
|
||||||
if u > $t::MAX as usize {
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
Some(u as $t)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
usize_to_ui! {
|
|
||||||
#[doc = r"u32"]
|
|
||||||
=> u32, to_u32}
|
|
||||||
usize_to_ui! {
|
|
||||||
#[doc = r"u16"]
|
|
||||||
=> u16, to_u16}
|
|
||||||
|
|
||||||
/// Test if both slices contain the exact same bytes.
|
|
||||||
///
|
///
|
||||||
/// Do not use this to compare cryptographic values (i.e. hashes)
|
/// # Errors
|
||||||
pub fn memeq(lhs: &[u8], rhs: &[u8]) -> bool {
|
|
||||||
let size = lhs.len();
|
|
||||||
|
|
||||||
size == rhs.len()
|
|
||||||
&& unsafe {
|
|
||||||
let l = lhs as *const _ as _;
|
|
||||||
let r = rhs as *const _ as _;
|
|
||||||
(l as usize) == (r as usize) || libc::memcmp(l, r, size) == 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Converts the hexstring into a byte vector.
|
|
||||||
///
|
///
|
||||||
/// Stops if the end or until a non hex chat is found
|
/// This function will return an error if the underlying OpenSSL implementation cannot parse `buf`
|
||||||
pub fn parse_hex(hex_str: &str) -> Vec<u8> {
|
/// as `DER` or `PEM`.
|
||||||
let mut hex_bytes = hex_str.as_bytes().iter().map_while(|b| match b {
|
pub fn read_private_key(buf: &[u8]) -> Result<PKey<Private>> {
|
||||||
b'0'..=b'9' => Some(b - b'0'),
|
PKey::private_key_from_der(buf)
|
||||||
b'a'..=b'f' => Some(b - b'a' + 10),
|
.or_else(|_| PKey::private_key_from_pem(buf))
|
||||||
b'A'..=b'F' => Some(b - b'A' + 10),
|
.map_err(Error::Crypto)
|
||||||
_ => None,
|
|
||||||
});
|
|
||||||
|
|
||||||
let mut bytes = Vec::new();
|
|
||||||
while let (Some(h), Some(l)) = (hex_bytes.next(), hex_bytes.next()) {
|
|
||||||
bytes.push(h << 4 | l)
|
|
||||||
}
|
|
||||||
bytes
|
|
||||||
}
|
|
||||||
/// Report if the `prot_virt_guest` sysfs entry is one.
|
|
||||||
///
|
|
||||||
/// If the entry does not exist returns false.
|
|
||||||
///
|
|
||||||
/// for non-s390-architectures:
|
|
||||||
/// Returns always false
|
|
||||||
/// A non-s390 system cannot be a secure execution guest.
|
|
||||||
#[allow(unreachable_code)]
|
|
||||||
pub fn pv_guest_bit_set() -> bool {
|
|
||||||
#[cfg(not(target_arch = "s390x"))]
|
|
||||||
return false;
|
|
||||||
//s390 branch
|
|
||||||
let v = std::fs::read("/sys/firmware/uv/prot_virt_guest").unwrap_or_else(|_| vec![0]);
|
|
||||||
let v: u8 = String::from_utf8_lossy(&v[..1]).parse().unwrap_or(0);
|
|
||||||
v == 1
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use std::usize;
|
use crate::{get_test_asset, test_utils::*};
|
||||||
|
|
||||||
use super::*;
|
|
||||||
#[cfg(feature = "request")]
|
|
||||||
use crate::test_utils::*;
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn msb_flags() {
|
|
||||||
let v = 17;
|
|
||||||
let v_flag: Msb0Flags64 = v.into();
|
|
||||||
assert_eq!(v, v_flag.0.get());
|
|
||||||
|
|
||||||
let mut v: Msb0Flags64 = 4.into();
|
|
||||||
v.unset_bit(61);
|
|
||||||
assert_eq!(v.0.get(), 0);
|
|
||||||
v.set_bit(61);
|
|
||||||
assert_eq!(4, v.0.get());
|
|
||||||
|
|
||||||
let mut v = Msb0Flags64::default();
|
|
||||||
v.set_bit(0);
|
|
||||||
assert_eq!(&[0x80, 0, 0, 0, 0, 0, 0, 0], v.as_bytes());
|
|
||||||
v.set_bit(0);
|
|
||||||
assert_eq!(&[0x80, 0, 0, 0, 0, 0, 0, 0], v.as_bytes());
|
|
||||||
v.set_bit(1);
|
|
||||||
assert_eq!(&[0xc0, 0, 0, 0, 0, 0, 0, 0], v.as_bytes());
|
|
||||||
v.set_bit(2);
|
|
||||||
assert_eq!(&[0xe0, 0, 0, 0, 0, 0, 0, 0], v.as_bytes());
|
|
||||||
v.set_bit(3);
|
|
||||||
assert_eq!(&[0xf0, 0, 0, 0, 0, 0, 0, 0], v.as_bytes());
|
|
||||||
|
|
||||||
v.unset_bit(3);
|
|
||||||
assert_eq!(&[0xe0, 0, 0, 0, 0, 0, 0, 0], v.as_bytes());
|
|
||||||
v.unset_bit(3);
|
|
||||||
assert_eq!(&[0xe0, 0, 0, 0, 0, 0, 0, 0], v.as_bytes());
|
|
||||||
|
|
||||||
v.set_bit(16);
|
|
||||||
assert_eq!(&[0xe0, 0, 0x80, 0, 0, 0, 0, 0], v.as_bytes());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
#[should_panic]
|
|
||||||
fn msb_flags_set_panic() {
|
|
||||||
Msb0Flags64::default().set_bit(64)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
#[should_panic]
|
|
||||||
fn msb_flags_unset_panic() {
|
|
||||||
Msb0Flags64::default().unset_bit(64)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn lsb_flags() {
|
|
||||||
let v = 17;
|
|
||||||
let v_flag: Lsb0Flags64 = v.into();
|
|
||||||
assert_eq!(v, v_flag.0.get());
|
|
||||||
|
|
||||||
let mut v: Lsb0Flags64 = 4.into();
|
|
||||||
v.unset_bit(2);
|
|
||||||
assert_eq!(v.0.get(), 0);
|
|
||||||
v.set_bit(2);
|
|
||||||
assert_eq!(4, v.0.get());
|
|
||||||
|
|
||||||
let mut v = Lsb0Flags64::default();
|
|
||||||
v.set_bit(0);
|
|
||||||
assert_eq!(&[0, 0, 0, 0, 0, 0, 0, 1], v.as_bytes());
|
|
||||||
v.set_bit(0);
|
|
||||||
assert_eq!(&[0, 0, 0, 0, 0, 0, 0, 1], v.as_bytes());
|
|
||||||
v.set_bit(1);
|
|
||||||
assert_eq!(&[0, 0, 0, 0, 0, 0, 0, 3], v.as_bytes());
|
|
||||||
v.set_bit(2);
|
|
||||||
assert_eq!(&[0, 0, 0, 0, 0, 0, 0, 7], v.as_bytes());
|
|
||||||
v.set_bit(3);
|
|
||||||
assert_eq!(&[0, 0, 0, 0, 0, 0, 0, 0xf], v.as_bytes());
|
|
||||||
|
|
||||||
v.unset_bit(3);
|
|
||||||
assert_eq!(&[0, 0, 0, 0, 0, 0, 0, 7], v.as_bytes());
|
|
||||||
v.unset_bit(3);
|
|
||||||
assert_eq!(&[0, 0, 0, 0, 0, 0, 0, 7], v.as_bytes());
|
|
||||||
|
|
||||||
v.set_bit(16);
|
|
||||||
assert_eq!(&[0, 0, 0, 0, 0, 1, 0, 7], v.as_bytes());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
#[should_panic]
|
|
||||||
fn lsb_flags_set_panic() {
|
|
||||||
Lsb0Flags64::default().set_bit(64)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
#[should_panic]
|
|
||||||
fn lsb_flags_unset_panic() {
|
|
||||||
Lsb0Flags64::default().unset_bit(64)
|
|
||||||
}
|
|
||||||
#[test]
|
|
||||||
fn parse_hex() {
|
|
||||||
let s = "123456acbef0";
|
|
||||||
let exp = vec![0x12, 0x34, 0x56, 0xac, 0xbe, 0xf0];
|
|
||||||
assert_eq!(super::parse_hex(s), exp);
|
|
||||||
|
|
||||||
let s = "00123456acbef0";
|
|
||||||
let exp = vec![0, 0x12, 0x34, 0x56, 0xac, 0xbe, 0xf0];
|
|
||||||
assert_eq!(super::parse_hex(s), exp);
|
|
||||||
|
|
||||||
let s = "00123456acbef0ii90";
|
|
||||||
let exp = vec![0, 0x12, 0x34, 0x56, 0xac, 0xbe, 0xf0];
|
|
||||||
assert_eq!(super::parse_hex(s), exp);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
#[cfg(feature = "request")]
|
|
||||||
fn read_crls() {
|
fn read_crls() {
|
||||||
let crl = get_cert_asset("ibm.crl");
|
let crl = get_cert_asset("ibm.crl");
|
||||||
let crl_der = get_cert_asset("der.crl");
|
let crl_der = get_cert_asset("der.crl");
|
||||||
@@ -550,7 +61,6 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[cfg(feature = "request")]
|
|
||||||
fn read_certs() {
|
fn read_certs() {
|
||||||
let crt = get_cert_asset("ibm.crt");
|
let crt = get_cert_asset("ibm.crt");
|
||||||
let crt_der = get_cert_asset("der.crt");
|
let crt_der = get_cert_asset("der.crt");
|
||||||
@@ -561,98 +71,16 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn to_u32() {
|
fn read_private_key() {
|
||||||
assert_eq!(Some(17), super::to_u32(17));
|
let key = get_test_asset!("keys/rsa3072key.pem");
|
||||||
assert_eq!(Some(0), super::to_u32(0));
|
let key = super::read_private_key(key).unwrap();
|
||||||
assert_eq!(Some(u32::MAX), super::to_u32(u32::MAX as usize));
|
assert_eq!(key.rsa().unwrap().size(), 384);
|
||||||
assert_eq!(None, super::to_u32(u32::MAX as usize + 1));
|
|
||||||
assert_eq!(None, super::to_u32(usize::MAX));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn parse_u128() {
|
fn read_private_key_fail() {
|
||||||
assert!(matches!(
|
let key = get_test_asset!("exp/secure_guest.hdr");
|
||||||
try_parse_u128("123456", ""),
|
let key = super::read_private_key(key);
|
||||||
Err(Error::Specification(_))
|
assert!(key.is_err());
|
||||||
));
|
|
||||||
assert!(matches!(
|
|
||||||
try_parse_u128("-1234", ""),
|
|
||||||
Err(Error::Specification(_))
|
|
||||||
));
|
|
||||||
assert!(matches!(
|
|
||||||
try_parse_u128("0011223344556677889900aabbccddeeff", ""),
|
|
||||||
Err(Error::Specification(_))
|
|
||||||
));
|
|
||||||
assert!(matches!(
|
|
||||||
try_parse_u128("dd11223344556677889900aabbccddeeff", ""),
|
|
||||||
Err(Error::Specification(_))
|
|
||||||
));
|
|
||||||
assert!(matches!(
|
|
||||||
try_parse_u128("-1223344556677889900aabbccddeeff", ""),
|
|
||||||
Err(Error::Specification(_))
|
|
||||||
));
|
|
||||||
|
|
||||||
assert!(matches!(
|
|
||||||
try_parse_u128("0x123456", ""),
|
|
||||||
Err(Error::Specification(_))
|
|
||||||
));
|
|
||||||
assert!(matches!(
|
|
||||||
try_parse_u128("-0x1234", ""),
|
|
||||||
Err(Error::Specification(_))
|
|
||||||
));
|
|
||||||
assert!(matches!(
|
|
||||||
try_parse_u128("0x0011223344556677889900aabbccddeeff", ""),
|
|
||||||
Err(Error::Specification(_))
|
|
||||||
));
|
|
||||||
assert!(matches!(
|
|
||||||
try_parse_u128("0xdd11223344556677889900aabbccddeeff", ""),
|
|
||||||
Err(Error::Specification(_))
|
|
||||||
));
|
|
||||||
assert!(matches!(
|
|
||||||
try_parse_u128("0x-1223344556677889900aabbccddeeff", ""),
|
|
||||||
Err(Error::Specification(_))
|
|
||||||
));
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
[
|
|
||||||
0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0x00, 0xaa, 0xbb, 0xcc, 0xdd,
|
|
||||||
0xee, 0xff
|
|
||||||
],
|
|
||||||
try_parse_u128("11223344556677889900aabbccddeeff", "").unwrap()
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
[
|
|
||||||
0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0x00, 0xaa, 0xbb, 0xcc, 0xdd,
|
|
||||||
0xee, 0xff
|
|
||||||
],
|
|
||||||
try_parse_u128("0x11223344556677889900aabbccddeeff", "").unwrap()
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
[
|
|
||||||
0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd,
|
|
||||||
0xee, 0xff
|
|
||||||
],
|
|
||||||
try_parse_u128("00112233445566778899aabbccddeeff", "").unwrap()
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
[
|
|
||||||
0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd,
|
|
||||||
0xee, 0xff
|
|
||||||
],
|
|
||||||
try_parse_u128("00112233445566778899aabbccddeeff", "").unwrap()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn memeq() {
|
|
||||||
let a = [1, 2, 3, 4, 5, 6, 7, 8, 9, 0];
|
|
||||||
let b = [1, 2, 3, 4, 5, 6, 7, 8, 9, 1];
|
|
||||||
let c = [0, 0, 1, 2, 3, 4];
|
|
||||||
|
|
||||||
assert!(super::memeq(&a, &a));
|
|
||||||
assert!(super::memeq(&a, &a.clone()));
|
|
||||||
assert!(!super::memeq(&b, &a));
|
|
||||||
assert!(!super::memeq(&b, &c));
|
|
||||||
assert!(!super::memeq(&b, &[]));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,79 +2,12 @@
|
|||||||
//
|
//
|
||||||
// Copyright IBM Corp. 2023
|
// Copyright IBM Corp. 2023
|
||||||
|
|
||||||
#![cfg(feature = "uvsecret")]
|
|
||||||
//! Provides functionality to manage the UV secret store.
|
//! Provides functionality to manage the UV secret store.
|
||||||
//!
|
//!
|
||||||
//! Provides functionality to build `add-secret` requests.
|
//! Provides functionality to build `add-secret` requests.
|
||||||
//! Also provides interfaces, to dispatch `Add Secret`, `Lock Secret Store`,
|
//! Also provides interfaces, to dispatch `Add Secret`, `Lock Secret Store`,
|
||||||
//! and `List Secrets` requests,
|
//! and `List Secrets` requests,
|
||||||
#[cfg(feature = "request")]
|
|
||||||
pub mod asrcb;
|
pub mod asrcb;
|
||||||
#[cfg(feature = "request")]
|
|
||||||
pub mod ext_secret;
|
pub mod ext_secret;
|
||||||
#[cfg(feature = "request")]
|
|
||||||
pub mod guest_secret;
|
pub mod guest_secret;
|
||||||
pub mod secret_list;
|
pub mod user_data;
|
||||||
pub mod uvc;
|
|
||||||
|
|
||||||
use crate::request::MagicValue;
|
|
||||||
use crate::requires_feat;
|
|
||||||
|
|
||||||
#[allow(unused_imports)] //used for more convenient docstring
|
|
||||||
use asrcb::AddSecretRequest;
|
|
||||||
/// Types of (non architectured) user data for [`AddSecretRequest`]
|
|
||||||
///
|
|
||||||
#[doc = requires_feat!(uvsecret)]
|
|
||||||
#[repr(u16)]
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, zerocopy::AsBytes)]
|
|
||||||
pub enum UserDataType {
|
|
||||||
/// Marker that the request does not contain any user data
|
|
||||||
Null = 0x0000,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The magic value used to identify an [`AddSecretRequest`]
|
|
||||||
///
|
|
||||||
/// The magic value is ASCII:
|
|
||||||
/// ```rust
|
|
||||||
/// # use pv::request::uvsecret::AddSecretMagic;
|
|
||||||
/// # use pv::request::MagicValue;
|
|
||||||
/// # fn main() {
|
|
||||||
/// # let magic =
|
|
||||||
/// # b"asrcbM"
|
|
||||||
/// # ;
|
|
||||||
/// # assert!(AddSecretMagic::starts_with_magic(magic));
|
|
||||||
/// # }
|
|
||||||
///```
|
|
||||||
///
|
|
||||||
#[doc = requires_feat!(uvsecret)]
|
|
||||||
#[repr(C)]
|
|
||||||
#[derive(Debug, Clone, Copy, zerocopy::AsBytes)]
|
|
||||||
pub struct AddSecretMagic {
|
|
||||||
magic: [u8; 6], // [0x61, 0x73, 0x72, 0x63, 0x62, 0x4D]
|
|
||||||
tp: UserDataType,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl MagicValue<6> for AddSecretMagic {
|
|
||||||
// "asrcbM"
|
|
||||||
const MAGIC: [u8; 6] = [0x61, 0x73, 0x72, 0x63, 0x62, 0x4D];
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<UserDataType> for AddSecretMagic {
|
|
||||||
fn from(tp: UserDataType) -> Self {
|
|
||||||
Self {
|
|
||||||
magic: Self::MAGIC,
|
|
||||||
tp,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const SECRET_ID_SIZE: usize = 32;
|
|
||||||
fn ser_gsid<S>(id: &[u8; SECRET_ID_SIZE], ser: S) -> Result<S::Ok, S::Error>
|
|
||||||
where
|
|
||||||
S: serde::Serializer,
|
|
||||||
{
|
|
||||||
let mut s = String::with_capacity(32 * 2 + 2);
|
|
||||||
s.push_str("0x");
|
|
||||||
let s = id.iter().fold(s, |acc, e| acc + &format!("{e:02x}"));
|
|
||||||
ser.serialize_str(&s)
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -2,23 +2,23 @@
|
|||||||
//
|
//
|
||||||
// Copyright IBM Corp. 2023
|
// Copyright IBM Corp. 2023
|
||||||
|
|
||||||
use super::{AddSecretMagic, UserDataType};
|
use super::user_data::UserData;
|
||||||
use crate::requires_feat;
|
|
||||||
use crate::{
|
use crate::{
|
||||||
assert_size,
|
assert_size,
|
||||||
misc::Flags,
|
misc::Flags,
|
||||||
request::{
|
request::{
|
||||||
hkdf_rfc_5869,
|
hkdf_rfc_5869,
|
||||||
openssl::{
|
openssl::{
|
||||||
pkey::{PKey, Public},
|
pkey::{PKey, Private, Public},
|
||||||
Md,
|
Md,
|
||||||
},
|
},
|
||||||
uvsecret::{ExtSecret, GuestSecret},
|
uvsecret::{ExtSecret, GuestSecret},
|
||||||
Aad, BootHdrTags, Keyslot, ReqEncrCtx, Request, RequestVersion, Secret,
|
Aad, BootHdrTags, Keyslot, ReqEncrCtx, Request, Secret,
|
||||||
},
|
},
|
||||||
uv::{ConfigUid, UvFlags},
|
uv::{ConfigUid, UvFlags},
|
||||||
Result,
|
Result,
|
||||||
};
|
};
|
||||||
|
use pv_core::request::RequestVersion;
|
||||||
use zerocopy::AsBytes;
|
use zerocopy::AsBytes;
|
||||||
|
|
||||||
/// Internal wrapper for Guest Secret, so that we can dump it in the form the UV wants it to be
|
/// Internal wrapper for Guest Secret, so that we can dump it in the form the UV wants it to be
|
||||||
@@ -52,6 +52,7 @@ impl From<GuestSecret> for BinGuestSecret {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Authenticated data w/o user data
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
#[derive(Debug, Clone, Copy, AsBytes)]
|
#[derive(Debug, Clone, Copy, AsBytes)]
|
||||||
struct ReqAuthData {
|
struct ReqAuthData {
|
||||||
@@ -59,9 +60,8 @@ struct ReqAuthData {
|
|||||||
boot_tags: BootHdrTags,
|
boot_tags: BootHdrTags,
|
||||||
cuid: ConfigUid,
|
cuid: ConfigUid,
|
||||||
reserved90: [u8; 0x100],
|
reserved90: [u8; 0x100],
|
||||||
prog_res190: [u8; 0x200],
|
|
||||||
}
|
}
|
||||||
assert_size!(ReqAuthData, 0x3e8);
|
assert_size!(ReqAuthData, 0x1e8);
|
||||||
|
|
||||||
impl ReqAuthData {
|
impl ReqAuthData {
|
||||||
fn new<F: Into<UvFlags>>(boot_tags: BootHdrTags, flags: F) -> Self {
|
fn new<F: Into<UvFlags>>(boot_tags: BootHdrTags, flags: F) -> Self {
|
||||||
@@ -70,7 +70,6 @@ impl ReqAuthData {
|
|||||||
boot_tags,
|
boot_tags,
|
||||||
cuid: [0; 0x10],
|
cuid: [0; 0x10],
|
||||||
reserved90: [0; 0x100],
|
reserved90: [0; 0x100],
|
||||||
prog_res190: [0; 0x200],
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -96,8 +95,6 @@ impl ReqConfData {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Flags for [`AddSecretRequest`]
|
/// Flags for [`AddSecretRequest`]
|
||||||
///
|
|
||||||
#[doc = requires_feat!(reqsecret)]
|
|
||||||
#[derive(Default, Clone, Copy, Debug)]
|
#[derive(Default, Clone, Copy, Debug)]
|
||||||
pub struct AddSecretFlags(UvFlags);
|
pub struct AddSecretFlags(UvFlags);
|
||||||
impl AddSecretFlags {
|
impl AddSecretFlags {
|
||||||
@@ -123,8 +120,6 @@ impl From<AddSecretFlags> for UvFlags {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Versions for [`AddSecretRequest`]
|
/// Versions for [`AddSecretRequest`]
|
||||||
///
|
|
||||||
#[doc = requires_feat!(reqsecret)]
|
|
||||||
#[repr(u32)]
|
#[repr(u32)]
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
pub enum AddSecretVersion {
|
pub enum AddSecretVersion {
|
||||||
@@ -143,12 +138,6 @@ impl From<AddSecretVersion> for RequestVersion {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AddSecretMagic {
|
|
||||||
fn get(&self) -> crate::request::RequestMagic {
|
|
||||||
self.as_bytes().try_into().unwrap()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Add-secret request Control Block
|
/// Add-secret request Control Block
|
||||||
///
|
///
|
||||||
/// An ASRCB wraps a secret to transport it securely to the Ultravisor.
|
/// An ASRCB wraps a secret to transport it securely to the Ultravisor.
|
||||||
@@ -158,6 +147,7 @@ impl AddSecretMagic {
|
|||||||
/// _______________________________________________________________
|
/// _______________________________________________________________
|
||||||
/// | generic header (48)
|
/// | generic header (48)
|
||||||
/// | --------------------------------------------------- |
|
/// | --------------------------------------------------- |
|
||||||
|
/// | Plaintext Add-Secret flags (8) |
|
||||||
/// | SE header tags: PLD(64) ALD(64) TLD(64) HeaderTag(16) |
|
/// | SE header tags: PLD(64) ALD(64) TLD(64) HeaderTag(16) |
|
||||||
/// | Configuration unique ID(16) (Attestation) |
|
/// | Configuration unique ID(16) (Attestation) |
|
||||||
/// | Optional, defaults to 0 |
|
/// | Optional, defaults to 0 |
|
||||||
@@ -173,18 +163,19 @@ impl AddSecretMagic {
|
|||||||
/// | AES GCM Tag (16) |
|
/// | AES GCM Tag (16) |
|
||||||
/// |_____________________________________________________________|
|
/// |_____________________________________________________________|
|
||||||
///```
|
///```
|
||||||
///
|
|
||||||
#[doc = requires_feat!(reqsecret)]
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct AddSecretRequest {
|
pub struct AddSecretRequest {
|
||||||
magic: AddSecretMagic,
|
|
||||||
version: AddSecretVersion,
|
version: AddSecretVersion,
|
||||||
aad: ReqAuthData,
|
aad: ReqAuthData,
|
||||||
keyslots: Vec<Keyslot>,
|
keyslots: Vec<Keyslot>,
|
||||||
conf: ReqConfData,
|
conf: ReqConfData,
|
||||||
|
user_data: UserData,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AddSecretRequest {
|
impl AddSecretRequest {
|
||||||
|
/// Offset of the user-data in the add-secret request in bytes
|
||||||
|
pub(super) const V1_USER_DATA_OFFS: usize = 0x218;
|
||||||
|
|
||||||
/// Create a new add-secret request.
|
/// Create a new add-secret request.
|
||||||
///
|
///
|
||||||
/// The request has no extension secret, no configuration UID, no host-keys,
|
/// The request has no extension secret, no configuration UID, no host-keys,
|
||||||
@@ -204,7 +195,7 @@ impl AddSecretRequest {
|
|||||||
aad: ReqAuthData::new(boot_tags, flags),
|
aad: ReqAuthData::new(boot_tags, flags),
|
||||||
keyslots: vec![],
|
keyslots: vec![],
|
||||||
version,
|
version,
|
||||||
magic: UserDataType::Null.into(),
|
user_data: UserData::Null,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -238,18 +229,41 @@ impl AddSecretRequest {
|
|||||||
&self.conf.secret.0
|
&self.conf.secret.0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Add user-data to the Add-Secret request
|
||||||
|
///
|
||||||
|
/// (Signed) user-data is a non-architectual feature. It allows to add arbitrary
|
||||||
|
/// data (message) to the request, that is signed optionally with an user defined key.
|
||||||
|
/// Allowed keys are:
|
||||||
|
/// - no key (up to 512 bytes of message)
|
||||||
|
/// - EC SECP521R1 (up to 256 byte message)
|
||||||
|
/// - RSA 2048 bit (up to 256 byte message)
|
||||||
|
/// - RSA 3072 bit (up to 128 byte message)
|
||||||
|
///
|
||||||
|
/// The signature can be verified during the verification of the secret-request on the target machine.
|
||||||
|
pub fn set_user_data(&mut self, msg: Vec<u8>, skey: Option<PKey<Private>>) -> Result<()> {
|
||||||
|
self.user_data = UserData::new(skey, msg)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
/// compiles the authenticated area of this request
|
/// compiles the authenticated area of this request
|
||||||
fn aad(&self, ctx: &ReqEncrCtx, conf_len: usize) -> Result<Vec<u8>> {
|
fn aad(&self, ctx: &ReqEncrCtx, conf_len: usize) -> Result<Vec<u8>> {
|
||||||
let cust_pub_key = ctx.key_coords()?;
|
let cust_pub_key = ctx.key_coords()?;
|
||||||
let secr_auth = self.conf.secret.dump_auth();
|
let secr_auth = self.conf.secret.dump_auth();
|
||||||
|
let user_data = self.user_data.data();
|
||||||
|
|
||||||
let mut aad: Vec<Aad> = Vec::with_capacity(3 + self.keyslots.len());
|
let mut aad: Vec<Aad> = Vec::with_capacity(5 + self.keyslots.len());
|
||||||
aad.push(Aad::Plain(self.aad.as_bytes()));
|
aad.push(Aad::Plain(self.aad.as_bytes()));
|
||||||
|
if let Some(data) = user_data.0 {
|
||||||
|
aad.push(Aad::Plain(data));
|
||||||
|
}
|
||||||
|
if let Some(data) = &user_data.1 {
|
||||||
|
aad.push(Aad::Plain(data));
|
||||||
|
}
|
||||||
aad.push(Aad::Plain(cust_pub_key.as_ref()));
|
aad.push(Aad::Plain(cust_pub_key.as_ref()));
|
||||||
self.keyslots.iter().for_each(|k| aad.push(Aad::Ks(k)));
|
self.keyslots.iter().for_each(|k| aad.push(Aad::Ks(k)));
|
||||||
aad.push(Aad::Plain(&secr_auth));
|
aad.push(Aad::Plain(&secr_auth));
|
||||||
|
|
||||||
ctx.build_aad(self.version.into(), &aad, conf_len, self.magic.get())
|
ctx.build_aad(self.version.into(), &aad, conf_len, self.user_data.magic())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
@@ -268,13 +282,41 @@ impl AddSecretRequest {
|
|||||||
res.append(&mut vec![0x24; 32]);
|
res.append(&mut vec![0x24; 32]);
|
||||||
Ok(res)
|
Ok(res)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// encrypt data, sign request with user-provided signing key, insert signature into aad,
|
||||||
|
/// calculate request tag
|
||||||
|
fn encrypt_with_signed_user_data(&self, ctx: &ReqEncrCtx) -> Result<Vec<u8>> {
|
||||||
|
//encrypt data w/o aead
|
||||||
|
let conf = self.conf.to_bytes();
|
||||||
|
let aad = self.aad(ctx, conf.value().len())?;
|
||||||
|
let (mut buf, aad_range, encr_range, _) = ctx.encrypt_aead(&aad, conf.value())?;
|
||||||
|
|
||||||
|
drop(aad);
|
||||||
|
|
||||||
|
// sign aad+encrypted data (w/o tag) with user signning key
|
||||||
|
// 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,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// encrypt again with signed data
|
||||||
|
buf[encr_range.clone()].copy_from_slice(conf.value());
|
||||||
|
ctx.encrypt_aead(&buf[aad_range], &buf[encr_range])
|
||||||
|
.map(|(buf, ..)| buf)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Request for AddSecretRequest {
|
impl Request for AddSecretRequest {
|
||||||
fn encrypt(&self, ctx: &ReqEncrCtx) -> Result<Vec<u8>> {
|
fn encrypt(&self, ctx: &ReqEncrCtx) -> Result<Vec<u8>> {
|
||||||
let conf = self.conf.to_bytes();
|
match self.user_data {
|
||||||
let aad = self.aad(ctx, conf.value().len())?;
|
UserData::Null | UserData::Unsigned(_) => {
|
||||||
ctx.encrypt_aead(&aad, conf.value())
|
let conf = self.conf.to_bytes();
|
||||||
|
let aad = self.aad(ctx, conf.value().len())?;
|
||||||
|
ctx.encrypt_aead(&aad, conf.value()).map(|(buf, ..)| buf)
|
||||||
|
}
|
||||||
|
_ => self.encrypt_with_signed_user_data(ctx),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn add_hostkey(&mut self, hostkey: PKey<Public>) {
|
fn add_hostkey(&mut self, hostkey: PKey<Public>) {
|
||||||
|
|||||||
@@ -2,11 +2,9 @@
|
|||||||
//
|
//
|
||||||
// Copyright IBM Corp. 2023
|
// Copyright IBM Corp. 2023
|
||||||
|
|
||||||
use crate::{request::Secret, requires_feat};
|
use crate::request::Secret;
|
||||||
|
|
||||||
/// Extension Secret for [`crate::request::uvsecret::AddSecretRequest`]
|
/// Extension Secret for [`crate::request::uvsecret::AddSecretRequest`]
|
||||||
///
|
|
||||||
#[doc = requires_feat!(reqsecret)]
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum ExtSecret {
|
pub enum ExtSecret {
|
||||||
/// A bytepattern that must be equal for each request targeting the same SE-guest instance
|
/// A bytepattern that must be equal for each request targeting the same SE-guest instance
|
||||||
|
|||||||
@@ -4,18 +4,16 @@
|
|||||||
|
|
||||||
#[allow(unused_imports)] //used for more convenient docstring
|
#[allow(unused_imports)] //used for more convenient docstring
|
||||||
use super::asrcb::AddSecretRequest;
|
use super::asrcb::AddSecretRequest;
|
||||||
use super::{ser_gsid, SECRET_ID_SIZE};
|
|
||||||
use crate::{
|
use crate::{
|
||||||
request::{hash, openssl::MessageDigest, random_array, Secret},
|
request::{hash, openssl::MessageDigest, random_array, Secret},
|
||||||
requires_feat, Result,
|
Result,
|
||||||
};
|
};
|
||||||
|
use pv_core::for_pv::{ser_gsid, SECRET_ID_SIZE};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::convert::TryInto;
|
use std::convert::TryInto;
|
||||||
|
|
||||||
const SECRET_SIZE: usize = 32;
|
const SECRET_SIZE: usize = 32;
|
||||||
/// A Secret to be added in [`AddSecretRequest`]
|
/// A Secret to be added in [`AddSecretRequest`]
|
||||||
///
|
|
||||||
#[doc = requires_feat!(reqsecret)]
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||||
pub enum GuestSecret {
|
pub enum GuestSecret {
|
||||||
/// No guest secret
|
/// No guest secret
|
||||||
|
|||||||
@@ -1,224 +0,0 @@
|
|||||||
// SPDX-License-Identifier: MIT
|
|
||||||
//
|
|
||||||
// Copyright IBM Corp. 2023
|
|
||||||
|
|
||||||
use crate::{misc::to_u16, uv::ListCmd, uvdevice::UvCmd, Error, Result};
|
|
||||||
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
|
|
||||||
use serde::{Serialize, Serializer};
|
|
||||||
use std::usize;
|
|
||||||
use std::{
|
|
||||||
fmt::Display,
|
|
||||||
io::{Cursor, Read, Seek, Write},
|
|
||||||
};
|
|
||||||
use zerocopy::{AsBytes, FromBytes, U16, U32};
|
|
||||||
|
|
||||||
use super::ser_gsid;
|
|
||||||
|
|
||||||
/// List of secrets used to parse the [`crate::uv::ListCmd`] result
|
|
||||||
///
|
|
||||||
/// Requires the `uvsecret` feature.
|
|
||||||
#[derive(Debug, PartialEq, Eq, Serialize)]
|
|
||||||
pub struct SecretList {
|
|
||||||
total_num_secrets: u16,
|
|
||||||
secrets: Vec<SecretEntry>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SecretList {
|
|
||||||
/// Encodes the list in the same binary format the UV would do
|
|
||||||
pub fn encode<T: Write>(&self, w: &mut T) -> Result<()> {
|
|
||||||
let num_s = to_u16(self.secrets.len()).ok_or(Error::ManySecrets)?;
|
|
||||||
w.write_u16::<BigEndian>(num_s)?;
|
|
||||||
w.write_u16::<BigEndian>(self.total_num_secrets)?;
|
|
||||||
w.write_all(&[0u8; 12])?;
|
|
||||||
for secret in &self.secrets {
|
|
||||||
w.write_all(secret.as_bytes())?;
|
|
||||||
}
|
|
||||||
w.flush().map_err(Error::Io)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Decodes the list from the binary format of the UV into this internal representation
|
|
||||||
pub fn decode<R: Read + Seek>(r: &mut R) -> std::io::Result<Self> {
|
|
||||||
let num_s = r.read_u16::<BigEndian>()?;
|
|
||||||
let total_num_secrets = r.read_u16::<BigEndian>()?;
|
|
||||||
let mut v: Vec<SecretEntry> = Vec::with_capacity(num_s as usize);
|
|
||||||
r.seek(std::io::SeekFrom::Current(12))?; //skip reserved bytes
|
|
||||||
let mut buf = [0u8; SECRET_ENTRY_SIZE];
|
|
||||||
for _ in 0..num_s {
|
|
||||||
r.read_exact(&mut buf)?;
|
|
||||||
//cannot fail. buffer has the same size as the secret entry
|
|
||||||
let secr = SecretEntry::read_from(buf.as_slice()).unwrap();
|
|
||||||
v.push(secr);
|
|
||||||
}
|
|
||||||
Ok(Self {
|
|
||||||
total_num_secrets,
|
|
||||||
secrets: v,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TryFrom<ListCmd> for SecretList {
|
|
||||||
type Error = Error;
|
|
||||||
fn try_from(mut list: ListCmd) -> Result<SecretList> {
|
|
||||||
SecretList::decode(&mut Cursor::new(list.data().unwrap())).map_err(Error::InvSecretList)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Display for SecretList {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
writeln!(f, "Total number of secrets: {}", self.total_num_secrets)?;
|
|
||||||
if !self.secrets.is_empty() {
|
|
||||||
writeln!(f)?;
|
|
||||||
}
|
|
||||||
for s in &self.secrets {
|
|
||||||
writeln!(f, "{s}")?;
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn ser_u32<S: Serializer>(v: &U32<BigEndian>, ser: S) -> Result<S::Ok, S::Error> {
|
|
||||||
ser.serialize_u32(v.get())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn ser_u16<S: Serializer>(v: &U16<BigEndian>, ser: S) -> Result<S::Ok, S::Error> {
|
|
||||||
ser.serialize_u16(v.get())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A secret in a [`SecretList`]
|
|
||||||
///
|
|
||||||
/// Fields are in big endian
|
|
||||||
#[repr(C)]
|
|
||||||
#[derive(Debug, PartialEq, Eq, AsBytes, FromBytes, Serialize)]
|
|
||||||
pub struct SecretEntry {
|
|
||||||
#[serde(serialize_with = "ser_u16")]
|
|
||||||
index: U16<BigEndian>,
|
|
||||||
#[serde(serialize_with = "ser_u16")]
|
|
||||||
stype: U16<BigEndian>,
|
|
||||||
#[serde(serialize_with = "ser_u32")]
|
|
||||||
len: U32<BigEndian>,
|
|
||||||
#[serde(skip)]
|
|
||||||
res_8: u64,
|
|
||||||
#[serde(serialize_with = "ser_gsid")]
|
|
||||||
id: [u8; 32],
|
|
||||||
}
|
|
||||||
const SECRET_ENTRY_SIZE: usize = 0x30;
|
|
||||||
|
|
||||||
fn stype_str(stype: u16) -> String {
|
|
||||||
match stype {
|
|
||||||
// should never match (not incl in list), but here for completeness
|
|
||||||
1 => "Null".to_string(),
|
|
||||||
2 => "Association".to_string(),
|
|
||||||
n => format!("Unknown {n}"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Display for SecretEntry {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
writeln!(f, "{} {}:", self.index, stype_str(self.stype.get()))?;
|
|
||||||
write!(f, " ")?;
|
|
||||||
for b in self.id {
|
|
||||||
write!(f, "{b:02x}")?;
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod test {
|
|
||||||
use super::*;
|
|
||||||
use std::io::{BufReader, BufWriter, Cursor};
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn secret_entry_size() {
|
|
||||||
assert_eq!(::std::mem::size_of::<SecretEntry>(), SECRET_ENTRY_SIZE);
|
|
||||||
}
|
|
||||||
#[test]
|
|
||||||
fn dump_secret_entry() {
|
|
||||||
const EXP: &[u8] = &[
|
|
||||||
0x00, 0x01, 0x00, 0x02, //idx + type
|
|
||||||
0x00, 0x00, 0x00, 0x20, //len
|
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // reserved
|
|
||||||
// id
|
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
||||||
0x00, 0x00, 0x00, 0x00,
|
|
||||||
];
|
|
||||||
let s = SecretEntry {
|
|
||||||
index: 1.into(),
|
|
||||||
stype: 2.into(),
|
|
||||||
len: 32.into(),
|
|
||||||
res_8: 0,
|
|
||||||
id: [0; 32],
|
|
||||||
};
|
|
||||||
|
|
||||||
assert_eq!(s.as_bytes(), EXP);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn secret_list_dec() {
|
|
||||||
let buf = [
|
|
||||||
0x00u8, 0x01, // num secr stored
|
|
||||||
0x01, 0x12, // total num secrets
|
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //reserved
|
|
||||||
// secret
|
|
||||||
0x00, 0x01, 0x00, 0x02, //idx + type
|
|
||||||
0x00, 0x00, 0x00, 0x20, //len
|
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // reserved
|
|
||||||
// id
|
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
||||||
0x00, 0x00, 0x00, 0x00,
|
|
||||||
];
|
|
||||||
|
|
||||||
let exp = SecretList {
|
|
||||||
total_num_secrets: 0x112,
|
|
||||||
secrets: vec![SecretEntry {
|
|
||||||
index: 1.into(),
|
|
||||||
stype: 2.into(),
|
|
||||||
len: 32.into(),
|
|
||||||
res_8: 0,
|
|
||||||
id: [0; 32],
|
|
||||||
}],
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut br = BufReader::new(Cursor::new(buf));
|
|
||||||
let sl = SecretList::decode(&mut br).unwrap();
|
|
||||||
assert_eq!(sl, exp);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn secret_list_enc() {
|
|
||||||
const EXP: &[u8] = &[
|
|
||||||
0x00, 0x01, // num secr stored
|
|
||||||
0x01, 0x12, // total num secrets
|
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //reserved
|
|
||||||
// secret
|
|
||||||
0x00, 0x01, 0x00, 0x02, //idx + type
|
|
||||||
0x00, 0x00, 0x00, 0x20, //len
|
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // reserved
|
|
||||||
// id
|
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
||||||
0x00, 0x00, 0x00, 0x00,
|
|
||||||
];
|
|
||||||
|
|
||||||
let sl = SecretList {
|
|
||||||
total_num_secrets: 0x112,
|
|
||||||
secrets: vec![SecretEntry {
|
|
||||||
index: 1.into(),
|
|
||||||
stype: 2.into(),
|
|
||||||
len: 32.into(),
|
|
||||||
res_8: 0,
|
|
||||||
id: [0; 32],
|
|
||||||
}],
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut buf = [0u8; 0x40];
|
|
||||||
{
|
|
||||||
let mut bw = BufWriter::new(&mut buf[..]);
|
|
||||||
sl.encode(&mut bw).unwrap();
|
|
||||||
}
|
|
||||||
println!("list: {sl:?}");
|
|
||||||
assert_eq!(buf, EXP);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
611
rust/pv/src/uvsecret/user_data.rs
Normal file
611
rust/pv/src/uvsecret/user_data.rs
Normal file
@@ -0,0 +1,611 @@
|
|||||||
|
use crate::{
|
||||||
|
crypto::{sign_msg, verify_signature},
|
||||||
|
req::BinReqValues,
|
||||||
|
request::{
|
||||||
|
openssl::{
|
||||||
|
pkey::{PKey, Private},
|
||||||
|
MessageDigest,
|
||||||
|
},
|
||||||
|
uvsecret::{AddSecretRequest, AddSecretVersion},
|
||||||
|
RequestMagic,
|
||||||
|
},
|
||||||
|
Error, Result,
|
||||||
|
};
|
||||||
|
use openssl::{
|
||||||
|
nid::Nid,
|
||||||
|
pkey::{HasParams, HasPublic, Id, PKeyRef, Public},
|
||||||
|
};
|
||||||
|
use pv_core::request::uvsecret::AddSecretMagic;
|
||||||
|
use pv_core::request::uvsecret::UserDataType;
|
||||||
|
use utils::assert_size;
|
||||||
|
use zerocopy::{AsBytes, BigEndian, FromBytes, FromZeroes, U16};
|
||||||
|
|
||||||
|
/// User data.
|
||||||
|
///
|
||||||
|
/// User defined data can be:
|
||||||
|
/// - 512 bytes arbitrary data
|
||||||
|
/// - 256 bytes arbitrary data + EC(secp521r1) signature
|
||||||
|
/// ```none
|
||||||
|
/// LAYOUT
|
||||||
|
/// |------------------------|
|
||||||
|
/// | user-data (256) |
|
||||||
|
/// | ec signature (139) |
|
||||||
|
/// | reserved (5) |
|
||||||
|
/// | signature size (2) (BE)|
|
||||||
|
/// | reserved (110) |
|
||||||
|
/// |------------------------|
|
||||||
|
/// ```
|
||||||
|
/// - 256 bytes arbitrary data + RSA2048 signature
|
||||||
|
/// ```none
|
||||||
|
/// LAYOUT
|
||||||
|
/// |---------------------|
|
||||||
|
/// | user-data (256) |
|
||||||
|
/// | rsa signature (256) |
|
||||||
|
/// |---------------------|
|
||||||
|
/// ```
|
||||||
|
/// - 128 bytes arbitrary data + RSA3072 signature
|
||||||
|
/// ```none
|
||||||
|
/// LAYOUT
|
||||||
|
/// |---------------------|
|
||||||
|
/// | user-data (128) |
|
||||||
|
/// | rsa signature (384) |
|
||||||
|
/// |---------------------|
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// Ensures that the data+signature fits into 512 bytes
|
||||||
|
/// must be created via functions!
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub(super) enum UserData {
|
||||||
|
Null,
|
||||||
|
Unsigned(Vec<u8>),
|
||||||
|
Signed(SignedUserData),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Debug, AsBytes, FromBytes, FromZeroes)]
|
||||||
|
struct EcUserData {
|
||||||
|
data: [u8; 256],
|
||||||
|
signature: [u8; EC_SIGN_MAX_SIZE],
|
||||||
|
res_18b: [u8; 5],
|
||||||
|
sgn_size: U16<BigEndian>,
|
||||||
|
res_192: [u8; 110],
|
||||||
|
}
|
||||||
|
assert_size!(EcUserData, USER_DATA_SIZE);
|
||||||
|
const USER_DATA_SIZE: usize = 0x200;
|
||||||
|
const EC_SIGN_MAX_SIZE: usize = 139;
|
||||||
|
|
||||||
|
impl EcUserData {
|
||||||
|
// Sets the signature to this data.
|
||||||
|
//
|
||||||
|
//# Panic
|
||||||
|
// Panics if `sgn` is longer than 139 bytes
|
||||||
|
fn set_signature(&mut self, sgn: &[u8]) {
|
||||||
|
debug_assert!(sgn.len() <= EC_SIGN_MAX_SIZE);
|
||||||
|
self.signature.fill(0);
|
||||||
|
self.signature[..sgn.len()].copy_from_slice(sgn);
|
||||||
|
|
||||||
|
self.res_18b.fill(0);
|
||||||
|
self.sgn_size = (sgn.len() as u16).into();
|
||||||
|
self.res_192.fill(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub(super) struct SignedUserData {
|
||||||
|
sign_key: PKey<Private>,
|
||||||
|
data: Vec<u8>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl UserData {
|
||||||
|
const USER_DATA_SIZE: usize = 0x200;
|
||||||
|
|
||||||
|
fn user_data_type<P: HasPublic>(sign_key: &PKeyRef<P>) -> Result<UserDataType> {
|
||||||
|
fn check_curve<P: HasParams>(pkey: &PKeyRef<P>) -> Result<bool> {
|
||||||
|
let nid = pkey.ec_key()?.group().curve_name();
|
||||||
|
match nid {
|
||||||
|
Some(nid) => Ok(nid == Nid::SECP521R1),
|
||||||
|
None => Ok(false),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
match sign_key.id() {
|
||||||
|
Id::EC if check_curve(sign_key)? => Ok(UserDataType::SgnEcSECP521R1),
|
||||||
|
Id::RSA if sign_key.rsa()?.size() == 2048 / 8 => Ok(UserDataType::SgnRsa2048),
|
||||||
|
Id::RSA if sign_key.rsa()?.size() == 3072 / 8 => Ok(UserDataType::SgnRsa3072),
|
||||||
|
_ => Err(Error::BinAsrcbUnsupportedUserDataSgnKey),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn magic(&self) -> RequestMagic {
|
||||||
|
let magic: AddSecretMagic = self.data_type().into();
|
||||||
|
magic.get()
|
||||||
|
}
|
||||||
|
/// Creates new user data
|
||||||
|
///
|
||||||
|
/// Verifies that the provided data + signature fits into 512 bytes
|
||||||
|
///
|
||||||
|
/// #Error
|
||||||
|
/// An error is reported if the provided data and the signature would not fit into 512 bytes
|
||||||
|
/// An error is reported if the key is not of type RSA (2048|3072) or EC(specp521r1)
|
||||||
|
pub(super) fn new(sign_key: Option<PKey<Private>>, data: Vec<u8>) -> Result<Self> {
|
||||||
|
let sign_key = match sign_key {
|
||||||
|
None => {
|
||||||
|
return match data.len() > UserDataType::Unsigned.max() {
|
||||||
|
true => Err(Error::AsrcbInvSgnUserData(UserDataType::Unsigned)),
|
||||||
|
false => Ok(Self::Unsigned(data)),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
Some(skey) => skey,
|
||||||
|
};
|
||||||
|
|
||||||
|
let kind = Self::user_data_type(&sign_key)?;
|
||||||
|
|
||||||
|
// does the data fit into the arbitrary buffer?
|
||||||
|
if data.len() > kind.max() {
|
||||||
|
return Err(Error::AsrcbInvSgnUserData(kind));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Self::Signed(SignedUserData { sign_key, data }))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Signs data in buf, writes signature to buf+user_data_offset+sign_offset if applicable.
|
||||||
|
///
|
||||||
|
/// Uses [`MessageDigest::sha512`] as digest. Does not modify the abritary user data buffer.
|
||||||
|
///
|
||||||
|
/// * buf: user data buffer, must be at least 512 bytes long
|
||||||
|
///
|
||||||
|
/// # Panic
|
||||||
|
/// panics if `buf` is smaller than 512 bytes
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
/// Returns an error if signature could not be calculated.
|
||||||
|
/// It is considered no error if no signature is required by user data type
|
||||||
|
pub(super) fn sign(&self, buf: &mut [u8], user_data_offset: usize) -> Result<()> {
|
||||||
|
// get signing info or return if no signature is required
|
||||||
|
let signed_data = match self {
|
||||||
|
UserData::Null | UserData::Unsigned(_) => return Ok(()),
|
||||||
|
UserData::Signed(s) => s,
|
||||||
|
};
|
||||||
|
debug_assert!(buf.len() >= USER_DATA_SIZE);
|
||||||
|
|
||||||
|
// clear the signature area
|
||||||
|
let sgn_offset = user_data_offset + self.data_type().max();
|
||||||
|
buf[sgn_offset..user_data_offset + USER_DATA_SIZE].fill(0);
|
||||||
|
|
||||||
|
// calculate signature
|
||||||
|
let sgn = sign_msg(&signed_data.sign_key, MessageDigest::sha512(), buf)?;
|
||||||
|
|
||||||
|
// insert signature
|
||||||
|
if let UserDataType::SgnEcSECP521R1 = self.data_type() {
|
||||||
|
// Panic: will not panic buffer is 512+ bytes long
|
||||||
|
let buf_ec = EcUserData::mut_from_prefix(&mut buf[user_data_offset..]).unwrap();
|
||||||
|
buf_ec.set_signature(&sgn);
|
||||||
|
} else {
|
||||||
|
// Panic: will not panic buffer is 512+ bytes long
|
||||||
|
buf[sgn_offset..sgn_offset + sgn.len()].copy_from_slice(&sgn);
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn data_type(&self) -> UserDataType {
|
||||||
|
match self {
|
||||||
|
Self::Null => UserDataType::Null,
|
||||||
|
Self::Unsigned(_) => UserDataType::Unsigned,
|
||||||
|
Self::Signed(data) => Self::user_data_type(&data.sign_key).unwrap(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// returns a slice for the abitraty user data as first tuple part if User data is available
|
||||||
|
/// the second part contains a vector, created on the fly, which contains enough zeros to fill
|
||||||
|
/// the missing bytes to fill 512 bytes of space or None if the first slice already contains
|
||||||
|
/// 512 bytes
|
||||||
|
pub(super) fn data(&self) -> (Option<&[u8]>, Option<Vec<u8>>) {
|
||||||
|
let buf = match self {
|
||||||
|
UserData::Null => None,
|
||||||
|
UserData::Unsigned(d) => Some(d),
|
||||||
|
UserData::Signed(SignedUserData { data, .. }) => Some(data),
|
||||||
|
};
|
||||||
|
|
||||||
|
let remaining_size = Self::USER_DATA_SIZE - buf.map(|b| b.len()).unwrap_or(0);
|
||||||
|
let remaining = match remaining_size > 0 {
|
||||||
|
true => Some(vec![0; remaining_size]),
|
||||||
|
false => None,
|
||||||
|
};
|
||||||
|
|
||||||
|
(buf.map(|b| b.as_ref()), remaining)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn format_vrfy_key(key: &PKeyRef<Public>) -> String {
|
||||||
|
let id = key.id();
|
||||||
|
match key.rsa() {
|
||||||
|
Ok(key) => format!("RSA {}", key.size() * 8),
|
||||||
|
Err(_) if id == Id::EC => "EC".to_string(),
|
||||||
|
Err(_) => "Unknown".to_string(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn check_key_format(kind: UserDataType, key: &PKeyRef<Public>) -> Result<()> {
|
||||||
|
let other_kind =
|
||||||
|
UserData::user_data_type(key).map_err(|_| Error::AsrcbUserDataKeyMismatch {
|
||||||
|
key: format_vrfy_key(key),
|
||||||
|
kind,
|
||||||
|
})?;
|
||||||
|
if other_kind == kind {
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(Error::AsrcbUserDataKeyMismatch {
|
||||||
|
key: format_vrfy_key(key),
|
||||||
|
kind,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Verify the user data contained in the add-secret request.
|
||||||
|
///
|
||||||
|
/// First checks that the provided data contains a sound add-secret request.
|
||||||
|
/// Then performs the inverse action that happened during the add-secret generation with user-data
|
||||||
|
/// signature:
|
||||||
|
/// - extract and replace the signature with zeros
|
||||||
|
/// - verify the signature of the request until, but not including the request tag
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
///
|
||||||
|
/// Extracrted user-data if available
|
||||||
|
///
|
||||||
|
/// #Errors
|
||||||
|
///
|
||||||
|
/// returns an error if
|
||||||
|
/// - No sound add-secret request presented
|
||||||
|
/// - Sinned user-data indicated, but no key provided
|
||||||
|
/// - Another keytype provided than indicated in the request
|
||||||
|
/// - Signature could not be verified by the provided key
|
||||||
|
/// - any OpenSSL error that might happen during the verification process
|
||||||
|
pub fn verify_asrcb_and_get_user_data(
|
||||||
|
mut asrcb: Vec<u8>,
|
||||||
|
key: Option<PKey<Public>>,
|
||||||
|
) -> Result<Option<Vec<u8>>> {
|
||||||
|
// 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
|
||||||
|
{
|
||||||
|
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 = &mut user_data[..UserData::USER_DATA_SIZE];
|
||||||
|
|
||||||
|
// depending on the user_data_type do:
|
||||||
|
// Null -> exit w/o user data
|
||||||
|
// Unsigned -> exit return all user data
|
||||||
|
// Signed ->
|
||||||
|
// - check that provided key matches user data keytype
|
||||||
|
// - extract user data& signature
|
||||||
|
let (key, user_data) = match (key, magic.kind()) {
|
||||||
|
(_, UserDataType::Null) => return Ok(None),
|
||||||
|
(None, UserDataType::Unsigned) => return Ok(Some(user_data.to_vec())),
|
||||||
|
(Some(key), UserDataType::Unsigned) => {
|
||||||
|
return Err(Error::AsrcbUserDataKeyMismatch {
|
||||||
|
key: format_vrfy_key(&key),
|
||||||
|
kind: UserDataType::Unsigned,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
(Some(key), _) => {
|
||||||
|
check_key_format(magic.kind(), &key)?;
|
||||||
|
(key, VerifiedUserData::new(user_data, magic.kind()))
|
||||||
|
}
|
||||||
|
(None, _) => return Err(Error::BinAsrcbNoUserDataSgnKey),
|
||||||
|
};
|
||||||
|
|
||||||
|
match verify_signature(&key, MessageDigest::sha512(), &asrcb, user_data.signature())? {
|
||||||
|
false => Err(Error::AsrcbUserDataSgnFail),
|
||||||
|
true => Ok(Some(user_data.into())),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Internal representation of the 512 bytes of user-data, signing-algorithm agnostic
|
||||||
|
struct VerifiedUserData {
|
||||||
|
data: Vec<u8>,
|
||||||
|
signature: Vec<u8>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl VerifiedUserData {
|
||||||
|
/// Reads user-data from buf depending on the indicated user data type.
|
||||||
|
/// Overwrites the signature in the buf with zeros.
|
||||||
|
///
|
||||||
|
/// #Panics
|
||||||
|
/// Panics it provided buffer is smaller that 512 bytes or kind is Null or Unsigned
|
||||||
|
fn new(buf: &mut [u8], kind: UserDataType) -> Self {
|
||||||
|
assert!(buf.len() >= 0x200);
|
||||||
|
|
||||||
|
let (ret, sgn) = match kind {
|
||||||
|
UserDataType::SgnEcSECP521R1 => {
|
||||||
|
let EcUserData {
|
||||||
|
data,
|
||||||
|
signature,
|
||||||
|
sgn_size,
|
||||||
|
..
|
||||||
|
} = EcUserData::mut_from_prefix(buf).unwrap();
|
||||||
|
let data_len: usize = data.len();
|
||||||
|
let data = data.to_vec();
|
||||||
|
let mut signature = signature.to_vec();
|
||||||
|
signature.truncate(sgn_size.get() as usize);
|
||||||
|
(Self { data, signature }, &mut buf[data_len..])
|
||||||
|
}
|
||||||
|
UserDataType::SgnRsa2048 => (
|
||||||
|
Self {
|
||||||
|
data: buf[..0x100].to_vec(),
|
||||||
|
signature: buf[0x100..].to_vec(),
|
||||||
|
},
|
||||||
|
&mut buf[0x100..],
|
||||||
|
),
|
||||||
|
UserDataType::SgnRsa3072 => (
|
||||||
|
Self {
|
||||||
|
data: buf[..0x80].to_vec(),
|
||||||
|
signature: buf[0x80..].to_vec(),
|
||||||
|
},
|
||||||
|
&mut buf[0x80..],
|
||||||
|
),
|
||||||
|
UserDataType::Null => unreachable!(),
|
||||||
|
UserDataType::Unsigned => unreachable!(),
|
||||||
|
};
|
||||||
|
|
||||||
|
//overwrite signature field with zeros
|
||||||
|
sgn.fill(0);
|
||||||
|
ret
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature(&self) -> &[u8] {
|
||||||
|
self.signature.as_ref()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<VerifiedUserData> for Vec<u8> {
|
||||||
|
fn from(value: VerifiedUserData) -> Self {
|
||||||
|
value.data
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use super::*;
|
||||||
|
use crate::{get_test_asset, test_utils::get_test_keys};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn sign_null() {
|
||||||
|
let mut buf = vec![17; 0x200];
|
||||||
|
|
||||||
|
let user_data = UserData::Null;
|
||||||
|
let (data, _) = user_data.data();
|
||||||
|
assert!(data.is_none());
|
||||||
|
|
||||||
|
user_data.sign(&mut buf, 0).unwrap();
|
||||||
|
|
||||||
|
// sign should not touch the buffer
|
||||||
|
assert_eq!(buf, vec![17; 0x200]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn sign_unsigned() {
|
||||||
|
let user_data = UserData::Unsigned(vec![0x11; 0x200]);
|
||||||
|
let (data, _) = user_data.data();
|
||||||
|
assert_eq!(data.unwrap(), &[0x11; 0x200]);
|
||||||
|
|
||||||
|
let mut buf = vec![17; 0x200];
|
||||||
|
user_data.sign(&mut buf, 0).unwrap();
|
||||||
|
|
||||||
|
// sign should not touch the buffer
|
||||||
|
assert_eq!(buf, vec![17; 0x200]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn sign_rsa2048() {
|
||||||
|
let rsa = get_test_asset!("keys/rsa2048key.pem");
|
||||||
|
let rsa = PKey::private_key_from_pem(rsa).unwrap();
|
||||||
|
|
||||||
|
let mut buf = vec![0x17; 0x200];
|
||||||
|
|
||||||
|
let user_data = UserData::new(Some(rsa.clone()), vec![0x11; 0x100]).unwrap();
|
||||||
|
let (data, _) = user_data.data();
|
||||||
|
let data = data.unwrap();
|
||||||
|
buf[..0x100].copy_from_slice(data);
|
||||||
|
|
||||||
|
user_data.sign(&mut buf, 0).unwrap();
|
||||||
|
|
||||||
|
let vrf_user_data = VerifiedUserData::new(&mut buf, UserDataType::SgnRsa2048);
|
||||||
|
let res = verify_signature(
|
||||||
|
&rsa,
|
||||||
|
MessageDigest::sha512(),
|
||||||
|
&buf,
|
||||||
|
vrf_user_data.signature(),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
assert!(res);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn sign_rsa3072() {
|
||||||
|
let rsa = get_test_asset!("keys/rsa3072key.pem");
|
||||||
|
let rsa = PKey::private_key_from_pem(rsa).unwrap();
|
||||||
|
|
||||||
|
let mut buf = vec![0x17; 0x200];
|
||||||
|
|
||||||
|
let user_data = UserData::new(Some(rsa.clone()), vec![0x11; 0x80]).unwrap();
|
||||||
|
let (data, _) = user_data.data();
|
||||||
|
let data = data.unwrap();
|
||||||
|
buf[..0x80].copy_from_slice(data);
|
||||||
|
|
||||||
|
user_data.sign(&mut buf, 0).unwrap();
|
||||||
|
|
||||||
|
let vrf_user_data = VerifiedUserData::new(&mut buf, UserDataType::SgnRsa3072);
|
||||||
|
let res = verify_signature(
|
||||||
|
&rsa,
|
||||||
|
MessageDigest::sha512(),
|
||||||
|
&buf,
|
||||||
|
vrf_user_data.signature(),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
assert!(res);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn sign_rsa4096_fail() {
|
||||||
|
let rsa = get_test_asset!("keys/rsa4096key.pem");
|
||||||
|
let rsa = PKey::private_key_from_pem(rsa).unwrap();
|
||||||
|
|
||||||
|
let user_data = UserData::new(Some(rsa.clone()), vec![]);
|
||||||
|
assert!(matches!(
|
||||||
|
user_data.unwrap_err(),
|
||||||
|
Error::BinAsrcbUnsupportedUserDataSgnKey
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn sign_ec() {
|
||||||
|
let (ec, _) = get_test_keys();
|
||||||
|
|
||||||
|
let mut buf = vec![0x11; 0x200];
|
||||||
|
|
||||||
|
let user_data = UserData::new(Some(ec.clone()), vec![0x11; 0x80]).unwrap();
|
||||||
|
let (data, _) = user_data.data();
|
||||||
|
let data = data.unwrap();
|
||||||
|
buf[..0x80].copy_from_slice(data);
|
||||||
|
|
||||||
|
user_data.sign(&mut buf, 0).unwrap();
|
||||||
|
let buf_ec = EcUserData::mut_from(&mut buf).unwrap();
|
||||||
|
let EcUserData {
|
||||||
|
data,
|
||||||
|
signature,
|
||||||
|
res_18b,
|
||||||
|
sgn_size,
|
||||||
|
res_192,
|
||||||
|
} = buf_ec;
|
||||||
|
assert_eq!(data, &[0x11u8; 256]);
|
||||||
|
assert_ne!(signature, &[0x11u8; 139]);
|
||||||
|
assert_eq!(res_18b, &[0u8; 5]);
|
||||||
|
assert!(sgn_size.get() <= 139);
|
||||||
|
assert_eq!(res_192, &[0u8; 110]);
|
||||||
|
|
||||||
|
let vrf_user_data = VerifiedUserData::new(&mut buf, UserDataType::SgnEcSECP521R1);
|
||||||
|
let res = verify_signature(
|
||||||
|
&ec,
|
||||||
|
MessageDigest::sha512(),
|
||||||
|
&buf,
|
||||||
|
vrf_user_data.signature(),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
assert!(res);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn sign_ec_fail() {
|
||||||
|
let ec = get_test_asset!("keys/ecsecp256k1.pem");
|
||||||
|
let ec = PKey::private_key_from_pem(ec).unwrap();
|
||||||
|
|
||||||
|
let user_data = UserData::new(Some(ec.clone()), vec![]);
|
||||||
|
assert!(matches!(
|
||||||
|
user_data.unwrap_err(),
|
||||||
|
Error::BinAsrcbUnsupportedUserDataSgnKey
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn check_format() {
|
||||||
|
let (_, ec) = get_test_keys();
|
||||||
|
check_key_format(UserDataType::SgnEcSECP521R1, &ec).unwrap();
|
||||||
|
let res = check_key_format(UserDataType::SgnRsa2048, &ec);
|
||||||
|
assert!(matches!(res, Err(Error::AsrcbUserDataKeyMismatch { .. })));
|
||||||
|
|
||||||
|
let rsa = get_test_asset!("keys/rsa2048key.pub.pem");
|
||||||
|
let rsa = PKey::public_key_from_pem(rsa).unwrap();
|
||||||
|
check_key_format(UserDataType::SgnRsa2048, &rsa).unwrap();
|
||||||
|
|
||||||
|
let rsa = get_test_asset!("keys/rsa3072key.pub.pem");
|
||||||
|
let rsa = PKey::public_key_from_pem(rsa).unwrap();
|
||||||
|
check_key_format(UserDataType::SgnRsa3072, &rsa).unwrap();
|
||||||
|
let res = check_key_format(UserDataType::SgnRsa2048, &rsa);
|
||||||
|
assert!(matches!(res, Err(Error::AsrcbUserDataKeyMismatch { .. })));
|
||||||
|
|
||||||
|
let rsa = get_test_asset!("keys/rsa4096key.pem");
|
||||||
|
let rsa = PKey::private_key_from_pem(rsa).unwrap();
|
||||||
|
let rsa = PKey::public_key_from_pem(&rsa.public_key_to_pem().unwrap()).unwrap();
|
||||||
|
let res = check_key_format(UserDataType::SgnRsa2048, &rsa);
|
||||||
|
assert!(matches!(res, Err(Error::AsrcbUserDataKeyMismatch { .. })));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn kind() {
|
||||||
|
let (ec, _) = get_test_keys();
|
||||||
|
let kind = UserData::user_data_type(&ec).unwrap();
|
||||||
|
assert_eq!(kind, UserDataType::SgnEcSECP521R1);
|
||||||
|
|
||||||
|
let rsa = get_test_asset!("keys/rsa2048key.pem");
|
||||||
|
let rsa = PKey::private_key_from_pem(rsa).unwrap();
|
||||||
|
let kind = UserData::user_data_type(&rsa).unwrap();
|
||||||
|
assert_eq!(kind, UserDataType::SgnRsa2048);
|
||||||
|
|
||||||
|
let rsa = get_test_asset!("keys/rsa3072key.pem");
|
||||||
|
let rsa = PKey::private_key_from_pem(rsa).unwrap();
|
||||||
|
let kind = UserData::user_data_type(&rsa).unwrap();
|
||||||
|
assert_eq!(kind, UserDataType::SgnRsa3072);
|
||||||
|
|
||||||
|
let rsa = get_test_asset!("keys/rsa4096key.pem");
|
||||||
|
let rsa = PKey::private_key_from_pem(rsa).unwrap();
|
||||||
|
let kind = UserData::user_data_type(&rsa).unwrap_err();
|
||||||
|
assert!(matches!(kind, Error::BinAsrcbUnsupportedUserDataSgnKey));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn new() {
|
||||||
|
let (ec, _) = get_test_keys();
|
||||||
|
let user_data = UserData::new(
|
||||||
|
Some(ec.clone()),
|
||||||
|
vec![1; UserDataType::SgnEcSECP521R1.max()],
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
assert!(matches!(user_data, UserData::Signed(_)));
|
||||||
|
|
||||||
|
let user_data = UserData::new(Some(ec), vec![1; UserDataType::SgnEcSECP521R1.max() + 1]);
|
||||||
|
assert!(matches!(
|
||||||
|
user_data,
|
||||||
|
Err(Error::AsrcbInvSgnUserData(UserDataType::SgnEcSECP521R1))
|
||||||
|
));
|
||||||
|
|
||||||
|
let user_data = UserData::new(None, vec![1; UserDataType::Unsigned.max()]).unwrap();
|
||||||
|
assert!(matches!(user_data, UserData::Unsigned(_)));
|
||||||
|
|
||||||
|
let user_data = UserData::new(None, vec![1; UserDataType::Unsigned.max() + 1]);
|
||||||
|
assert!(matches!(
|
||||||
|
user_data,
|
||||||
|
Err(Error::AsrcbInvSgnUserData(UserDataType::Unsigned))
|
||||||
|
));
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn data() {
|
||||||
|
let (ec, _) = get_test_keys();
|
||||||
|
let data_in = vec![1; UserDataType::SgnEcSECP521R1.max()];
|
||||||
|
let user_data = UserData::new(Some(ec.clone()), data_in.clone()).unwrap();
|
||||||
|
let exp_pad = Some(vec![0; UserData::USER_DATA_SIZE - data_in.len()]);
|
||||||
|
|
||||||
|
let (data_out, pad) = user_data.data();
|
||||||
|
assert_eq!(data_out, Some(data_in.as_ref()));
|
||||||
|
assert_eq!(pad, exp_pad);
|
||||||
|
|
||||||
|
let data_in = vec![1; UserDataType::SgnEcSECP521R1.max() - 1];
|
||||||
|
let user_data = UserData::new(Some(ec.clone()), data_in.clone()).unwrap();
|
||||||
|
let exp_pad = Some(vec![0; UserData::USER_DATA_SIZE - data_in.len()]);
|
||||||
|
|
||||||
|
let (data_out, pad) = user_data.data();
|
||||||
|
assert_eq!(data_out, Some(data_in.as_ref()));
|
||||||
|
assert_eq!(pad, exp_pad);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,13 +2,12 @@
|
|||||||
//
|
//
|
||||||
// Copyright IBM Corp. 2023
|
// Copyright IBM Corp. 2023
|
||||||
|
|
||||||
use super::AddSecretMagic;
|
|
||||||
use crate::{
|
use crate::{
|
||||||
request::MagicValue,
|
|
||||||
requires_feat,
|
requires_feat,
|
||||||
uv::{uv_ioctl, UvCmd, UvDevice},
|
uv::{uv_ioctl, UvCmd, UvDevice},
|
||||||
Error, Result, PAGESIZE,
|
Error, Result, PAGESIZE,
|
||||||
};
|
};
|
||||||
|
use pv_core::request::{uvsecret::AddSecretMagic, MagicValue};
|
||||||
use std::io::Read;
|
use std::io::Read;
|
||||||
use std::usize;
|
use std::usize;
|
||||||
|
|
||||||
@@ -64,7 +63,7 @@ impl AddCmd {
|
|||||||
let mut data = Vec::with_capacity(PAGESIZE);
|
let mut data = Vec::with_capacity(PAGESIZE);
|
||||||
bin_add_secret_req.read_to_end(&mut data)?;
|
bin_add_secret_req.read_to_end(&mut data)?;
|
||||||
|
|
||||||
if !AddSecretMagic::starts_with_magic(&data[..6]) {
|
if !AddSecretMagic::starts_with_magic(&data) {
|
||||||
return Err(Error::NoAsrcb);
|
return Err(Error::NoAsrcb);
|
||||||
}
|
}
|
||||||
Ok(Self(data))
|
Ok(Self(data))
|
||||||
|
|||||||
@@ -45,6 +45,12 @@ pub struct CertVerifier {
|
|||||||
offline: bool,
|
offline: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Debug for CertVerifier {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
f.write_str("CertVerifier")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl HkdVerifier for CertVerifier {
|
impl HkdVerifier for CertVerifier {
|
||||||
/// This function verifies a host-key
|
/// This function verifies a host-key
|
||||||
/// document. To do so multiple steps are required:
|
/// document. To do so multiple steps are required:
|
||||||
|
|||||||
@@ -3,11 +3,10 @@
|
|||||||
// Copyright IBM Corp. 2023
|
// Copyright IBM Corp. 2023
|
||||||
|
|
||||||
use crate::error::bail_hkd_verify;
|
use crate::error::bail_hkd_verify;
|
||||||
use crate::misc::{memeq, read_crls};
|
use crate::misc::read_crls;
|
||||||
use crate::HkdVerifyErrorType::*;
|
use crate::HkdVerifyErrorType::*;
|
||||||
use crate::{Error, Result};
|
use crate::{Error, Result};
|
||||||
use curl::easy::{Easy2, Handler, WriteError};
|
use curl::easy::{Easy2, Handler, WriteError};
|
||||||
use libc::c_int;
|
|
||||||
use log::debug;
|
use log::debug;
|
||||||
use openssl::{
|
use openssl::{
|
||||||
asn1::{Asn1Time, Asn1TimeRef},
|
asn1::{Asn1Time, Asn1TimeRef},
|
||||||
@@ -26,9 +25,7 @@ use openssl_extensions::{
|
|||||||
akid::{AkidCheckResult, AkidExtension},
|
akid::{AkidCheckResult, AkidExtension},
|
||||||
crl::X509StoreExtension,
|
crl::X509StoreExtension,
|
||||||
};
|
};
|
||||||
use std::cmp::Ordering;
|
use std::{cmp::Ordering, ffi::c_int, time::Duration, usize};
|
||||||
use std::time::Duration;
|
|
||||||
use std::usize;
|
|
||||||
|
|
||||||
/// Minimum security level for the keys/certificates used to establish a chain of
|
/// Minimum security level for the keys/certificates used to establish a chain of
|
||||||
/// trust (see https://www.openssl.org/docs/man1.1.1/man3/X509_VERIFY_PARAM_set_auth_level.html
|
/// trust (see https://www.openssl.org/docs/man1.1.1/man3/X509_VERIFY_PARAM_set_auth_level.html
|
||||||
@@ -219,7 +216,7 @@ fn name_data_eq(entries: &X509NameRef, nid: Nid, rhs: &[u8]) -> bool {
|
|||||||
let mut it = entries.entries_by_nid(nid);
|
let mut it = entries.entries_by_nid(nid);
|
||||||
match it.next() {
|
match it.next() {
|
||||||
None => false,
|
None => false,
|
||||||
Some(entry) => memeq(entry.data().as_slice(), rhs),
|
Some(entry) => entry.data().as_slice() == rhs,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -376,11 +373,12 @@ fn check_x509_name_equal(lhs: &X509NameRef, rhs: &X509NameRef) -> Result<()> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for l in lhs.entries() {
|
for l in lhs.entries() {
|
||||||
let ldata = l.data().as_slice();
|
|
||||||
|
|
||||||
// search for the matching value in the rhs names
|
// search for the matching value in the rhs names
|
||||||
// found none? -> names are not equal
|
// found none? -> names are not equal
|
||||||
if !rhs.entries().any(|r| memeq(ldata, r.data().as_slice())) {
|
if !rhs
|
||||||
|
.entries()
|
||||||
|
.any(|r| l.data().as_slice() == r.data().as_slice())
|
||||||
|
{
|
||||||
bail_hkd_verify!(IssuerMismatch);
|
bail_hkd_verify!(IssuerMismatch);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,45 +7,30 @@
|
|||||||
use super::{helper, helper::*, *};
|
use super::{helper, helper::*, *};
|
||||||
use crate::{Error, HkdVerifyErrorType::*};
|
use crate::{Error, HkdVerifyErrorType::*};
|
||||||
use core::slice;
|
use core::slice;
|
||||||
|
use once_cell::sync::OnceCell;
|
||||||
use openssl::stack::Stack;
|
use openssl::stack::Stack;
|
||||||
|
use std::sync::Mutex;
|
||||||
|
|
||||||
use crate::test_utils::*;
|
use crate::test_utils::*;
|
||||||
|
|
||||||
pub fn mock_endpt(res: &str) -> mockito::Mock {
|
pub fn mock_endpt(res: &str) -> mockito::Mock {
|
||||||
|
static MOCK_SERVER: OnceCell<Mutex<mockito::Server>> = OnceCell::new();
|
||||||
|
|
||||||
let res_path = get_cert_asset_path(res);
|
let res_path = get_cert_asset_path(res);
|
||||||
|
|
||||||
let mut server = mockito::Server::new();
|
MOCK_SERVER
|
||||||
server
|
.get_or_init(|| mockito::Server::new_with_port(1234).into())
|
||||||
|
.lock()
|
||||||
|
.expect("COULD NOT GET THE MOCK_SERVER LOCK")
|
||||||
.mock("GET", format!("/crl/{res}").as_str())
|
.mock("GET", format!("/crl/{res}").as_str())
|
||||||
.with_header("content-type", "application/pkix-crl")
|
.with_header("content-type", "application/pkix-crl")
|
||||||
.with_body_from_file(res_path)
|
.with_body_from_file(res_path)
|
||||||
.create()
|
.create()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[track_caller]
|
#[test]
|
||||||
fn verify_sign_error(exp_raw: libc::c_int, obs: Error) {
|
fn mockito_server_available() {
|
||||||
verify_sign_error_slice(&[exp_raw], obs)
|
let _mock = mock_endpt("ibm.crt");
|
||||||
}
|
|
||||||
fn verify_sign_error_slice(exp_raw: &[libc::c_int], obs: Error) {
|
|
||||||
if exp_raw
|
|
||||||
.iter()
|
|
||||||
.filter(|e| match &obs {
|
|
||||||
Error::HkdVerify(ty) => match ty {
|
|
||||||
IbmSignInvalid(err, _d) => &&err.as_raw() == e,
|
|
||||||
_ => false,
|
|
||||||
},
|
|
||||||
e => panic!("Unexpected error type: {e:?}"),
|
|
||||||
})
|
|
||||||
.count()
|
|
||||||
== 0
|
|
||||||
{
|
|
||||||
panic!("Error {obs:?} did not match one of the expected {exp_raw:?}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl std::fmt::Debug for CertVerifier {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
f.write_str("CertVerifier")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -174,97 +159,6 @@ fn verify_offline() {
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn verifier_new() {
|
|
||||||
let root_chn_crt = get_cert_asset_path_string("root_ca.chained.crt");
|
|
||||||
let root_crt = get_cert_asset_path_string("root_ca.crt");
|
|
||||||
let inter_crt = get_cert_asset_path_string("inter_ca.crt");
|
|
||||||
let inter_fake_crt = get_cert_asset_path_string("fake_inter_ca.crt");
|
|
||||||
let inter_fake_crl = get_cert_asset_path_string("fake_inter_ca.crl");
|
|
||||||
let inter_crl = get_cert_asset_path_string("inter_ca.crl");
|
|
||||||
let ibm_crt = get_cert_asset_path_string("ibm.crt");
|
|
||||||
let ibm_early_crt = get_cert_asset_path_string("ibm_outdated_early.crl");
|
|
||||||
let ibm_late_crt = get_cert_asset_path_string("ibm_outdated_late.crl");
|
|
||||||
let ibm_rev_crt = get_cert_asset_path_string("ibm_rev.crt");
|
|
||||||
|
|
||||||
// To many signing keys
|
|
||||||
let verifier = CertVerifier::new(&[ibm_crt.clone(), ibm_rev_crt.clone()], &[], &None, true);
|
|
||||||
assert!(matches!(verifier, Err(Error::HkdVerify(ManyIbmSignKeys))));
|
|
||||||
|
|
||||||
// no CRL for each X509
|
|
||||||
let verifier = CertVerifier::new(
|
|
||||||
&[inter_crt.clone(), ibm_crt.clone()],
|
|
||||||
&[inter_crl.clone()],
|
|
||||||
&Some(root_crt),
|
|
||||||
false,
|
|
||||||
);
|
|
||||||
verify_sign_error(3, verifier.unwrap_err());
|
|
||||||
let verifier = CertVerifier::new(
|
|
||||||
&[inter_crt.clone(), ibm_crt.clone()],
|
|
||||||
&[],
|
|
||||||
&Some(root_chn_crt.clone()),
|
|
||||||
false,
|
|
||||||
);
|
|
||||||
verify_sign_error(3, verifier.unwrap_err());
|
|
||||||
|
|
||||||
// wrong intermediate (or ibm key)
|
|
||||||
let verifier = CertVerifier::new(
|
|
||||||
&[inter_fake_crt, ibm_crt.clone()],
|
|
||||||
&[inter_fake_crl],
|
|
||||||
&Some(root_chn_crt.clone()),
|
|
||||||
true,
|
|
||||||
);
|
|
||||||
//Depending on the OpenSSL version different error codes can appear
|
|
||||||
verify_sign_error_slice(&[20, 30], verifier.unwrap_err());
|
|
||||||
|
|
||||||
//wrong root ca
|
|
||||||
let verifier = CertVerifier::new(
|
|
||||||
&[inter_crt.clone(), ibm_crt.clone()],
|
|
||||||
&[inter_crl.clone()],
|
|
||||||
&None,
|
|
||||||
true,
|
|
||||||
);
|
|
||||||
verify_sign_error(20, verifier.unwrap_err());
|
|
||||||
|
|
||||||
//correct signing key + intermediate cert
|
|
||||||
let _verifier = CertVerifier::new(
|
|
||||||
&[inter_crt.clone(), ibm_crt.clone()],
|
|
||||||
&[inter_crl.clone()],
|
|
||||||
&Some(root_chn_crt.clone()),
|
|
||||||
false,
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
// no intermediate key
|
|
||||||
let verifier = CertVerifier::new(&[ibm_crt], &[], &Some(root_chn_crt.clone()), false);
|
|
||||||
verify_sign_error(20, verifier.unwrap_err());
|
|
||||||
|
|
||||||
//Ibm Sign outdated
|
|
||||||
let verifier = CertVerifier::new(
|
|
||||||
&[inter_crt.clone(), ibm_early_crt],
|
|
||||||
&[inter_crl.clone()],
|
|
||||||
&Some(root_chn_crt.clone()),
|
|
||||||
false,
|
|
||||||
);
|
|
||||||
assert!(matches!(verifier, Err(Error::HkdVerify(NoIbmSignKey))));
|
|
||||||
let verifier = CertVerifier::new(
|
|
||||||
&[inter_crt.clone(), ibm_late_crt],
|
|
||||||
&[inter_crl.clone()],
|
|
||||||
&Some(root_chn_crt.clone()),
|
|
||||||
false,
|
|
||||||
);
|
|
||||||
assert!(matches!(verifier, Err(Error::HkdVerify(NoIbmSignKey))));
|
|
||||||
|
|
||||||
// revoked
|
|
||||||
let verifier = CertVerifier::new(
|
|
||||||
&[inter_crt, ibm_rev_crt],
|
|
||||||
&[inter_crl],
|
|
||||||
&Some(root_chn_crt),
|
|
||||||
false,
|
|
||||||
);
|
|
||||||
verify_sign_error(23, verifier.unwrap_err());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn dist_points() {
|
fn dist_points() {
|
||||||
let crt = load_gen_cert("ibm.crt");
|
let crt = load_gen_cert("ibm.crt");
|
||||||
|
|||||||
@@ -1,13 +1,19 @@
|
|||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
//
|
//
|
||||||
// Copyright IBM Corp. 2023
|
// Copyright IBM Corp. 2023
|
||||||
|
use openssl::{
|
||||||
#![cfg(all(feature = "request", feature = "uvsecret"))]
|
ec::{EcGroup, EcKey},
|
||||||
|
nid::Nid,
|
||||||
|
pkey::Private,
|
||||||
|
};
|
||||||
use pv::{
|
use pv::{
|
||||||
get_test_asset,
|
get_test_asset,
|
||||||
request::{
|
request::{
|
||||||
openssl::pkey::{PKey, Public},
|
openssl::pkey::{PKey, Public},
|
||||||
uvsecret::{AddSecretFlags, AddSecretRequest, AddSecretVersion, ExtSecret, GuestSecret},
|
uvsecret::{
|
||||||
|
verify_asrcb_and_get_user_data, AddSecretFlags, AddSecretRequest, AddSecretVersion,
|
||||||
|
ExtSecret, GuestSecret,
|
||||||
|
},
|
||||||
BootHdrTags, ReqEncrCtx, Request, SymKey,
|
BootHdrTags, ReqEncrCtx, Request, SymKey,
|
||||||
},
|
},
|
||||||
test_utils::get_test_keys,
|
test_utils::get_test_keys,
|
||||||
@@ -85,6 +91,93 @@ fn no_flag() -> AddSecretFlags {
|
|||||||
AddSecretFlags::default()
|
AddSecretFlags::default()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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());
|
||||||
|
|
||||||
|
asrcb.add_hostkey(host_key);
|
||||||
|
asrcb.set_user_data(user_data, Some(skey)).unwrap();
|
||||||
|
asrcb.encrypt(&ctx).unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
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());
|
||||||
|
|
||||||
|
asrcb.add_hostkey(host_key);
|
||||||
|
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_one_user_ec() {
|
||||||
|
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(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_one_user_rsa2048() {
|
||||||
|
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(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_one_user_rsa3072() {
|
||||||
|
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(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]
|
#[test]
|
||||||
fn null_none_default_cuid_one() {
|
fn null_none_default_cuid_one() {
|
||||||
let asrcb = gen_asrcb(GuestSecret::Null, None, no_flag(), true).unwrap();
|
let asrcb = gen_asrcb(GuestSecret::Null, None, no_flag(), true).unwrap();
|
||||||
@@ -155,3 +248,12 @@ fn null_none_default_cuid_seven() {
|
|||||||
let exp = get_test_asset!("exp/asrcb/null_none_default_cuid_seven");
|
let exp = get_test_asset!("exp/asrcb/null_none_default_cuid_seven");
|
||||||
assert_eq!(asrcb, exp);
|
assert_eq!(asrcb, exp);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn verify_no_user_data() {
|
||||||
|
let req = get_test_asset!("exp/asrcb/null_none_default_ncuid_one");
|
||||||
|
assert!(matches!(
|
||||||
|
verify_asrcb_and_get_user_data(req.to_vec(), None),
|
||||||
|
Ok(None)
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|||||||
8
rust/pv/tests/assets/keys/ecsecp256k1.pem
Normal file
8
rust/pv/tests/assets/keys/ecsecp256k1.pem
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
-----BEGIN EC PARAMETERS-----
|
||||||
|
BgUrgQQACg==
|
||||||
|
-----END EC PARAMETERS-----
|
||||||
|
-----BEGIN EC PRIVATE KEY-----
|
||||||
|
MHQCAQEEIAiQERmhEIwksExNpW2CsaQeFPKFEN6PIdMWUiX3Mu7FoAcGBSuBBAAK
|
||||||
|
oUQDQgAEO9U+NDdHcIZ59RzTq/6lDH2RA7sEyRlvFVQsj7feiuXh4Q4qeZNCb2sn
|
||||||
|
s7Hd1lUjFexH+Cp97/ggwSa3q2y/0Q==
|
||||||
|
-----END EC PRIVATE KEY-----
|
||||||
21
rust/pv/tests/assets/keys/rsa2048.crt
Normal file
21
rust/pv/tests/assets/keys/rsa2048.crt
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIDZzCCAk+gAwIBAgIUK4kgzElokcI7s6XNCRD1iA7NfMQwDQYJKoZIhvcNAQEL
|
||||||
|
BQAwQjELMAkGA1UEBhMCWFgxFTATBgNVBAcMDERlZmF1bHQgQ2l0eTEcMBoGA1UE
|
||||||
|
CgwTRGVmYXVsdCBDb21wYW55IEx0ZDAgFw0yNDAxMjkxNzAxMjRaGA8zMDIzMDYw
|
||||||
|
MTE3MDEyNFowQjELMAkGA1UEBhMCWFgxFTATBgNVBAcMDERlZmF1bHQgQ2l0eTEc
|
||||||
|
MBoGA1UECgwTRGVmYXVsdCBDb21wYW55IEx0ZDCCASIwDQYJKoZIhvcNAQEBBQAD
|
||||||
|
ggEPADCCAQoCggEBAOYCZGvIP+X8Y8gpHnjg57Oo45k+jsO1GeZGBtM+MfXP1HaV
|
||||||
|
bT48VlaxaLB+K0GbQ5ykBdP7Xa1nLdbFVWtmATkbrS78xzUsefpXG8WKtA3gtE9/
|
||||||
|
v3licc34dBHiDBNrZf/elC2P3EZOkwX7AYJSlhg5uehgy0Jp4MvAWZMHOgDNRf41
|
||||||
|
Tr/jCpuntUKUcdv9+CdhzD0Z634gzPckO4QXWKaBKwa8AU4GTb41/RTOF6U5Zh1Q
|
||||||
|
exx4rdf8h7s3sbfEa5ELLpMKSUeWXd7LVFUM6TSP+/zTgRxLHmiM3gGEawYFEvla
|
||||||
|
UluGqGtC2MVpu0bGQvxgJmuy0cw7rPE5RzudIOUCAwEAAaNTMFEwHQYDVR0OBBYE
|
||||||
|
FBheoZmtoNcYxXMwzZsujv51NgslMB8GA1UdIwQYMBaAFBheoZmtoNcYxXMwzZsu
|
||||||
|
jv51NgslMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBADeRY7AS
|
||||||
|
waBH/YnZ/q/cixkaeMqtM9zRpOAVnBGOEbQiLVFhLXaT7XMT8r9JjbbYQW7RgaQS
|
||||||
|
fOHIxlG4jjly+/kXluF+Di54dMwrMCeAbxg7C/QQvMGVEWCufoJ9UGXWCzdZTziO
|
||||||
|
xREupk7RvjWUZL88kO9peGgsAelnvjWtgwEXpYpfOY7zwLvvm9RR7qujfjRMsip+
|
||||||
|
WTGjh4hksGQ9Yh8yFTx96QEnhZ69sU6ohtagOdW0/G2Yeh4fO/k77Afq1KNRatrZ
|
||||||
|
W3IeLGIVZIfajxfCYQ/ARmJg3I1h1Hw1pnw50h6Q8AQf50s0MtxIe6UaVUz4KqJH
|
||||||
|
RgLQPVsWXaWw5KA=
|
||||||
|
-----END CERTIFICATE-----
|
||||||
28
rust/pv/tests/assets/keys/rsa2048key.pem
Normal file
28
rust/pv/tests/assets/keys/rsa2048key.pem
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
-----BEGIN PRIVATE KEY-----
|
||||||
|
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDmAmRryD/l/GPI
|
||||||
|
KR544OezqOOZPo7DtRnmRgbTPjH1z9R2lW0+PFZWsWiwfitBm0OcpAXT+12tZy3W
|
||||||
|
xVVrZgE5G60u/Mc1LHn6VxvFirQN4LRPf795YnHN+HQR4gwTa2X/3pQtj9xGTpMF
|
||||||
|
+wGCUpYYObnoYMtCaeDLwFmTBzoAzUX+NU6/4wqbp7VClHHb/fgnYcw9Get+IMz3
|
||||||
|
JDuEF1imgSsGvAFOBk2+Nf0UzhelOWYdUHsceK3X/Ie7N7G3xGuRCy6TCklHll3e
|
||||||
|
y1RVDOk0j/v804EcSx5ojN4BhGsGBRL5WlJbhqhrQtjFabtGxkL8YCZrstHMO6zx
|
||||||
|
OUc7nSDlAgMBAAECggEACx1seuTnaZbDv6V1CoRbzgvIS9yBC6YRxVX2+HhV87oC
|
||||||
|
W2SnLc2+olvaYkUGlBoLr2MhwPextDJ7PZi1owX/4yGaQd4HRmOK1XUmK2qjftQw
|
||||||
|
AnN67kO6mih6e27ABKQ4/p9j67u6+k7Lvgs+LG0PQcZwXHFpASGasl3uNjOwMt+h
|
||||||
|
IJxo1UJcNM4s0WyFLWWg7dWHE/m8QWA0Qc9zBL3cIewTgr6SiTTgQGeNFpSQThgJ
|
||||||
|
E1G9cpLvchKLCcsc3L8/03ZJX00R/GyBYcJsvsTfsva5FzFG6OSt4G+73jthEC8E
|
||||||
|
jUeI0D/tw27XjBGRYNbMlsdIHqdTWxraB1HiK3CpIQKBgQD2nYXrr5isnlOSaeww
|
||||||
|
de1QMUTY7A/IUtYGknZxCj1Q/xS483er/bmfrNRAo2YMGqO/vcJxqy7vA3yACMqR
|
||||||
|
mUP8XInkfkTILYaLEECfW4+g6/f6wgBnFdIjn4vqR8XGPFwYX6IoSvzjh5iqOTon
|
||||||
|
LVx1v2Co9cdvFiD+rGT2t9FZyQKBgQDuwxjWw2bx0iH4CXQRtqv97lDu/eB9SiIl
|
||||||
|
Nb0AYGYf6/QhVkVxOBjUHitgSDWdOy2vJ8tojuxM2qT2SmGYFmjI33WAReHvPPE0
|
||||||
|
BzjIHdCAzKRJgHk02Sh86vw9NMDiGE37Tc9GdGNz8hXF4BTyuPbnDu7ta54bn7JQ
|
||||||
|
U4xVQgLcPQKBgFnM0k8YCcgRfuz5lP+wcVGaHy++BaE0SKrr+iT/TYSI3crbHOCL
|
||||||
|
62OxUziWRzpxGN9hAcTSSKzP4C3fBaQu4wqpx6xkLjeWHD/XEzDz7kDyq8Grkun2
|
||||||
|
kCZYmmIc8G9W21IkwK9bXuqbHYQegoZxhhF6NoBPvuc8b5dMvxb3qEvBAoGBAJaL
|
||||||
|
556LCN8TWx3VgB0nLF1pphh7ddAqLUt/DdQt9FbOZVmb1x9jeqxM9Za5ES05wj19
|
||||||
|
/xDHhZGUD43IKq9F+f4mgCVIawR5TbE7mH795ZlA75n06vf6sT/Kvw8ssy4cmxcI
|
||||||
|
4dgPzSGvlDD3cN326J+aV+bWA5RxZnKlq4eanskVAoGBAK+QvIp17Lq2RdcBcFr3
|
||||||
|
1Do0M2tDzjBgdzIp+8bfJHSfe3v4EEupeNa2tD2lnRlETc2JjIMFx1SMHYTi9+ax
|
||||||
|
Iuhwl7txcijtXT+5kUO4Km8oiQGbskJLwH1iVAGIMMASJf8lQHODby2R8x3OUQnX
|
||||||
|
YdC+wJJq6gfNo436A4lkPQsM
|
||||||
|
-----END PRIVATE KEY-----
|
||||||
9
rust/pv/tests/assets/keys/rsa2048key.pub.pem
Normal file
9
rust/pv/tests/assets/keys/rsa2048key.pub.pem
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
-----BEGIN PUBLIC KEY-----
|
||||||
|
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA5gJka8g/5fxjyCkeeODn
|
||||||
|
s6jjmT6Ow7UZ5kYG0z4x9c/UdpVtPjxWVrFosH4rQZtDnKQF0/tdrWct1sVVa2YB
|
||||||
|
ORutLvzHNSx5+lcbxYq0DeC0T3+/eWJxzfh0EeIME2tl/96ULY/cRk6TBfsBglKW
|
||||||
|
GDm56GDLQmngy8BZkwc6AM1F/jVOv+MKm6e1QpRx2/34J2HMPRnrfiDM9yQ7hBdY
|
||||||
|
poErBrwBTgZNvjX9FM4XpTlmHVB7HHit1/yHuzext8RrkQsukwpJR5Zd3stUVQzp
|
||||||
|
NI/7/NOBHEseaIzeAYRrBgUS+VpSW4aoa0LYxWm7RsZC/GAma7LRzDus8TlHO50g
|
||||||
|
5QIDAQAB
|
||||||
|
-----END PUBLIC KEY-----
|
||||||
26
rust/pv/tests/assets/keys/rsa3072.crt
Normal file
26
rust/pv/tests/assets/keys/rsa3072.crt
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIEZzCCAs+gAwIBAgIUK67CEnpp4Hbp9awAbW67M6M8t9cwDQYJKoZIhvcNAQEL
|
||||||
|
BQAwQjELMAkGA1UEBhMCWFgxFTATBgNVBAcMDERlZmF1bHQgQ2l0eTEcMBoGA1UE
|
||||||
|
CgwTRGVmYXVsdCBDb21wYW55IEx0ZDAgFw0yNDAxMjkxNzAxMzZaGA8zMDIzMDYw
|
||||||
|
MTE3MDEzNlowQjELMAkGA1UEBhMCWFgxFTATBgNVBAcMDERlZmF1bHQgQ2l0eTEc
|
||||||
|
MBoGA1UECgwTRGVmYXVsdCBDb21wYW55IEx0ZDCCAaIwDQYJKoZIhvcNAQEBBQAD
|
||||||
|
ggGPADCCAYoCggGBAJy1pz6/Xzq9Gicai7se9HC49cPATTPXrcdVjCi11LiPDMoL
|
||||||
|
eFCeCJhXCBNLpWWDTcl2t1EFTuIv9SDguY4nWgaxVaOz0c0FLXi0DCLEUUwjPt4L
|
||||||
|
EZoRxwOAbut1N3ACE1NY0cQnDhqHUeFehp4wXUuAmCk+z3zPVRNhFjjBKXcLOphQ
|
||||||
|
ak/3C8/ajJJBznXC4r8o4MNZSl6BjJxfLArpC4xM8/5rQ2qu1CoW6nrDLeFRfFGu
|
||||||
|
CvhJ89NHnvqvUjDSb+G5gfBRFwsjw3rI80o8VYIPeM79PWILy5nMtWZDQ/J6L713
|
||||||
|
eRhrXyr42kre3c6tkkSjfciEUTwyXxcCOThX/wnN8mWg9PQY7UqyrR8mPFbSFTi0
|
||||||
|
CIL8nPi3U9tcpHOMKzrVyaYELeNhUNpDfkWE5s1XYdfiDl3Jd60GxOKpU9gCgof8
|
||||||
|
Sf21cSIcHCITqjG6Oga10ZxeCZrGEZrm/46p+KWXuWL+Tp12SvQqm7Fjc7W8MjSC
|
||||||
|
ZXEWltQcWzgq+DkOfQIDAQABo1MwUTAdBgNVHQ4EFgQU9QUBlQYenYdS6Jeo0//F
|
||||||
|
4S+xRZEwHwYDVR0jBBgwFoAU9QUBlQYenYdS6Jeo0//F4S+xRZEwDwYDVR0TAQH/
|
||||||
|
BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAYEAOjinmH5+2smoKj0fkKmgFWQqH2St
|
||||||
|
TbwtC4M7xJpge4uwEj/48CZPECZzMmkKs//x1bRepX3wBfCSrVZNjoynFxZtyjCA
|
||||||
|
2Ss86F3h/FGQaLJoIGUJN1EW8MIqxQRkv0KRBPKiGay2xocErjNjxlkDeyuDliyP
|
||||||
|
y5GUF4h+pgjlVO0b5uR03loYty65jmHucPijGkF7Kgr208SCOXArWZEyLZx1Pian
|
||||||
|
PiVxJP2mnsK9wsBBNvjH1DB+gMbOW3v6nl/2xD1Q/Lz0gif7fZZiTTnt47mlqD4d
|
||||||
|
QWOH22uPO1EMG7K66CgYQd2mo3ZKpMhPeAC3OajEnY2hXOeI/w3dsvaMwIAMUQgu
|
||||||
|
y585iOfifUgEwXXAgGS0Hq1nQwDK1TjvM5sP7nbBfIlPwI78NoK9QM7p/O7UvccD
|
||||||
|
UHAjsDgmU5q/hhqs6v6gvlX3GqOnFSdviUQGeX2l9Hw6lFWM9HIKC968ykzlp9m6
|
||||||
|
uOv1xi5CIF/ZqIkDF//ZdISgZuyB4zWOcRIT
|
||||||
|
-----END CERTIFICATE-----
|
||||||
40
rust/pv/tests/assets/keys/rsa3072key.pem
Normal file
40
rust/pv/tests/assets/keys/rsa3072key.pem
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
-----BEGIN PRIVATE KEY-----
|
||||||
|
MIIG/QIBADANBgkqhkiG9w0BAQEFAASCBucwggbjAgEAAoIBgQCctac+v186vRon
|
||||||
|
Gou7HvRwuPXDwE0z163HVYwotdS4jwzKC3hQngiYVwgTS6Vlg03JdrdRBU7iL/Ug
|
||||||
|
4LmOJ1oGsVWjs9HNBS14tAwixFFMIz7eCxGaEccDgG7rdTdwAhNTWNHEJw4ah1Hh
|
||||||
|
XoaeMF1LgJgpPs98z1UTYRY4wSl3CzqYUGpP9wvP2oySQc51wuK/KODDWUpegYyc
|
||||||
|
XywK6QuMTPP+a0NqrtQqFup6wy3hUXxRrgr4SfPTR576r1Iw0m/huYHwURcLI8N6
|
||||||
|
yPNKPFWCD3jO/T1iC8uZzLVmQ0Pyei+9d3kYa18q+NpK3t3OrZJEo33IhFE8Ml8X
|
||||||
|
Ajk4V/8JzfJloPT0GO1Ksq0fJjxW0hU4tAiC/Jz4t1PbXKRzjCs61cmmBC3jYVDa
|
||||||
|
Q35FhObNV2HX4g5dyXetBsTiqVPYAoKH/En9tXEiHBwiE6oxujoGtdGcXgmaxhGa
|
||||||
|
5v+Oqfill7li/k6ddkr0KpuxY3O1vDI0gmVxFpbUHFs4Kvg5Dn0CAwEAAQKCAYAP
|
||||||
|
wpkaT2bFYoVBz4KkA/3zZDabRDi/cP5yIKFi6r6E/SX9H+Auw2X75CzUu/PbXLQz
|
||||||
|
AbR3+n+vccFGWDNxH3YHXEhPmWMNKmsjS4A48uN2M8k7zYMsqoMIUB9XoMc9hYfq
|
||||||
|
BvG5t2/MrTVH5BjHyfdoA3vKEh4N4FAfDdGNTafc6A0xikzW0/KlKcafYuzfnQTu
|
||||||
|
0q6gFtfK316Dz3J/emaF6qtTN/hkv0ZpITrds8lmcqp9EHLb+5wA8KAFeCJc9JzR
|
||||||
|
dwLUf1e5eY51kQK+Iq7BsS0NujXCpEFmmUnjfrODyjkuF14ezFCCrYLz+EOdJEzV
|
||||||
|
ktCjwopSV53EkjstktkS91szUv2tWMZ9V7cZFXyHvrnmbMXOS+wqU/6gV4ZUT5XK
|
||||||
|
e7EH4mE4kre2ho2NVXHJjQWNB4ok+9+4ThmwUwhtT3+55we8u9WjjARrqZCewPq7
|
||||||
|
enmMgPJ24M17CTFd370Wufx+X1ydLdxIYXj2Q06bbyAtc+l6iVjffjoRSR0PImUC
|
||||||
|
gcEA2oRbvnTM/RqgXV39+aUGALsXOqQsWoFQIJmebseQbthf3N2GrgQx6EZZld93
|
||||||
|
EvWBLoN8/eTCAjoVRWYbX/DIr+lHXV6MmvhytUuQdpKXgmV2nXzEPSFbWKI8oHXu
|
||||||
|
rArNyHiawsiEFyzY2pKkFa8TuJ61H3zPKqZMCkH1Lb0ozqr/0hFjS7SDEbKelX0n
|
||||||
|
K66uJzktj62Qb3qLuqn/CcRokG3g5PyItVycX+TWyXiKFPk/pWfRrTcshazSLhOD
|
||||||
|
sJ1nAoHBALeXLAo8c1mICGM8QnJxma047jnWqKt8zwaXoTWu7pJwy8luUgkNDdau
|
||||||
|
FtjW3h1AcBiV6mWXXGNqXBETaBPoBbDXVtZ1QA6c0JKZKSJb4iaGHOkuopNksjcW
|
||||||
|
4RKn/rCho0eov1prbvSkAi9ghsLZFRxNgkPohazxCdZbPf+L7h7mwRLLpfTxawAS
|
||||||
|
zIZprbKDlaSijGV10yuOeHtrjnrVNleeApTtu1In/wxM3R0yYoK/d4cPbrt9Z2ZH
|
||||||
|
s6C/i4RiewKBwQCuvrQDXbJkIbvmjnjBACgJw5Py8EEL62dhepfJLUwiaGRY4sqG
|
||||||
|
NJKYllGj2b2Gas/wm3QEMcC4OSyzf9EaaxqCGoJYd3jnoRFSS+ht+hKxU2SE0ra3
|
||||||
|
0AoVmj/zbfv8lvMzgsb4R5PiWJneSPBw6r5RoJFladDd4XjjpxVji2arxvGnUwJe
|
||||||
|
Ey/Rok7RavE9LIbWlRILWRgtHgnJr5JlhCy+ezAizpSgvDvNL9pX6U47MYulQZ3a
|
||||||
|
8ElpaOf3suFYBrcCgcBmfrNLk6wiU9u66y19jwZoutvk1n6cHrXFzGln+x2eC/L5
|
||||||
|
pemOvAb5z39Z17txQURTtX6C8RnXNs3nipoUYviqXc5RNGxqvAmFjpR0Zi+/qigB
|
||||||
|
1tNUzmbgcpl9POTn35W0r/UEme38DoRZxt31chRjFLKhDvNyNa90SMQ4IvrxrYok
|
||||||
|
4CTqts0x4UzlzaH3g5bLryQKkNkv+/4viUCmOtp31/NBCegGWeX+C/t2DWcJ77Lf
|
||||||
|
aDI94ikT5PvXxpgxdHkCgcAw5MMLz6i30bQOCEQdGurjbev3oFmHyNFIxkNmUdrA
|
||||||
|
/4ixCLSXW/m2s+T09qLIFbyABNK9VvTKVvM0V4l4ZvvuZdhLCrSZTCnXBmNBhFq1
|
||||||
|
UCa1BvE8f98dDcDNTp7ICjTOaVk4ZB8yhL0f4kMIs1jJZ1XoWCbsRS6Y2YSsF6IH
|
||||||
|
Ntzs7smF3ExIaDlYrVxB/K0TPAXPcCzQ+np9KspLinY+gCThAzQ057YxzRaj00I4
|
||||||
|
ErvUlL2xPdMTZ14eNgDwQk8=
|
||||||
|
-----END PRIVATE KEY-----
|
||||||
11
rust/pv/tests/assets/keys/rsa3072key.pub.pem
Normal file
11
rust/pv/tests/assets/keys/rsa3072key.pub.pem
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
-----BEGIN PUBLIC KEY-----
|
||||||
|
MIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEAnLWnPr9fOr0aJxqLux70
|
||||||
|
cLj1w8BNM9etx1WMKLXUuI8Mygt4UJ4ImFcIE0ulZYNNyXa3UQVO4i/1IOC5jida
|
||||||
|
BrFVo7PRzQUteLQMIsRRTCM+3gsRmhHHA4Bu63U3cAITU1jRxCcOGodR4V6GnjBd
|
||||||
|
S4CYKT7PfM9VE2EWOMEpdws6mFBqT/cLz9qMkkHOdcLivyjgw1lKXoGMnF8sCukL
|
||||||
|
jEzz/mtDaq7UKhbqesMt4VF8Ua4K+Enz00ee+q9SMNJv4bmB8FEXCyPDesjzSjxV
|
||||||
|
gg94zv09YgvLmcy1ZkND8novvXd5GGtfKvjaSt7dzq2SRKN9yIRRPDJfFwI5OFf/
|
||||||
|
Cc3yZaD09BjtSrKtHyY8VtIVOLQIgvyc+LdT21ykc4wrOtXJpgQt42FQ2kN+RYTm
|
||||||
|
zVdh1+IOXcl3rQbE4qlT2AKCh/xJ/bVxIhwcIhOqMbo6BrXRnF4JmsYRmub/jqn4
|
||||||
|
pZe5Yv5OnXZK9CqbsWNztbwyNIJlcRaW1BxbOCr4OQ59AgMBAAE=
|
||||||
|
-----END PUBLIC KEY-----
|
||||||
52
rust/pv/tests/assets/keys/rsa4096key.pem
Normal file
52
rust/pv/tests/assets/keys/rsa4096key.pem
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
-----BEGIN PRIVATE KEY-----
|
||||||
|
MIIJQgIBADANBgkqhkiG9w0BAQEFAASCCSwwggkoAgEAAoICAQDpMm6nXtXh9qK+
|
||||||
|
1wbdeNaYbLLwDcBa9JtNp/Ewz+uFbCuvbdHTyigWtGn7gHtALf/SzVIIhiLPFJ3R
|
||||||
|
4BKPZN9SXz0KqmI27IQL+WPSNZCW1XLlmHj5YCsGXnrE+ogpd/addDes3ByuVJ84
|
||||||
|
Sm6FuRzjwb4x7k/D01+tFRhSg9uujwr93FMjimWd0T29za8RTOvh3/lrY4nIDjvx
|
||||||
|
pcUD9M/C7w8VcvtxQNZmgnN8KYLL5SMpVjJgTKLwMgZRP8XclX/BtMMdeRvd86KC
|
||||||
|
QYUVVJqrs8kBslZqGIJ0K1en32/KpzFKj7lJ6/lkPZwQcfmaMdZnHEwbUkloy63S
|
||||||
|
GJSFsq4NRnmPc1WaOnvyk+DCRh4eehFxJ62FRm8nT+SaHEzugZQLAm4ZvV7DB8MP
|
||||||
|
TgXarFrEYYdtKP6d1/+n0CCKT0jCvj4FRVZgh2nLqUpi3mDd3cid2cZyG6GktyV8
|
||||||
|
2j9WsJ4rbrLAuGl8QUvmpnAdPVGNkij39AB2S6nPfRZIpYUcfVKBgpDFvLscmPwp
|
||||||
|
lR0IwtOq3Z8jo7n9S/Y07gY75lWBur6FiYfLGgdlWIHdZSpO12mABc5jVsivMDwi
|
||||||
|
3/XVVGT7l0ELRbVlDEFXWZlholRdsfxqmr0O/GsRN0g9+aiqaKBkKI2eblooQCBW
|
||||||
|
AGbVEEggUSW2RQ5Z9c9bSAIRGKVNVQIDAQABAoICABN0gHee/9t3bVVClz4IhAH5
|
||||||
|
DGZFBdR3LjKPKlFXAPoDb9/8GrjypEZdcuIapZQeW+0R8bm1IL5QcZvXqw5V7Ubw
|
||||||
|
AljoPuozvUIXSLt6E8AIISsEF3bZ2rBDVza/BN3nNkBYnwFHs9TcSa3mkWXyonUv
|
||||||
|
h4o4eZfuA8A3tez2BApO8/nN7qaIBOyvtXKNigEfGI08CsQ2bIF8pOxtVtlHpijh
|
||||||
|
mbHBnM/Y1IpLPXTd+bcmox8iij1igQCzch3hL8UbBXeU3xpcYKjM8YF9RqvkgESG
|
||||||
|
6uQk8fStnf4wurVn4xzv6e4KI5YGBXgJc89Wn7fviuJuh++9/VKse3a/U9TIdwck
|
||||||
|
gFknia4j/ThU0t8gCppTcu2iXRmIKYr7jBPhd/7axxPEfA17gGJ7suslm/S/5MGn
|
||||||
|
+EEm04FjhoSAHO9Ny5jnZUQ01Lurg0d0se3rrBJzYWiWn+CS30bnhrqjiQARoBpn
|
||||||
|
HtqRDGqfvt1H0N5l10/4AoqGw3BfFhGmUBhTkfLeR8MqiK8VgaVH8GlPfGVQNJgi
|
||||||
|
B/0Q1Wvu2sAFWh5xFUNceJv2hsVt/s6V8SLLB4bN9OULBwL62teB3RgFezYwVC/i
|
||||||
|
AGSOo56JflZJFDeqYrMv+UACwIUaqm739aVfCd2kda+Ef1YCWfilWloUghQHsDGJ
|
||||||
|
WtKmQgiZCow3bpf3373ZAoIBAQD+YXKT2nY1L4GsfJJoCKh48P1yO52qbt/K8ce8
|
||||||
|
vNI+ZOwCNsOXyAdcnlYmd9GiABzOlHxkjWmfAgNE9suzvKGRHnfpYLh7RPt9f9CX
|
||||||
|
ltWlkcrNxcGAyG8XPb59ygr6o69US31U9/oiC8/WJeue5x+KyaatcjNad087qUVM
|
||||||
|
SYGb4fgZ0YLhVKDUvq3zbC8D6z2vN7t5gLGEB8VvC2DY0tLi8u2PEY6laVbyPWIv
|
||||||
|
5OBbp7XHWvDS+bMKYUJATHr2DF0K10IC/DZZMGf/V2rrzjR76gZDkyZPclTZv4Ec
|
||||||
|
EQr37Ssc8Wa7IWisSEmKVO6HKbpaUE/fBRMmzAa8ikmkVTB7AoIBAQDqrnZwI25x
|
||||||
|
zdcMD8es7ymlVfb9xwymxp3rSaLvvaSbdGjnw+B1gnNo91eJGPJUbgWwMDlO9wch
|
||||||
|
3Y9B2UIMLQfr0+OP3lWvyVqdCK8LHtkH2FJhZtEGu099EZNKvTtU4b47fna6Vcll
|
||||||
|
K0o11CzsE+TuLWZNhjriEA/xCUqR7gxm8cB+nDzuDqW4GYpk/JuAYLQwcI0dgMjH
|
||||||
|
5j9le94Hz+YHkratanU98BQh/xOTVF2wfeiWeO83LgiugewiLdEENPzgCrZ72xUG
|
||||||
|
2qH0zO6Oe++9RW2JPNTXxOmTiX/fS3pvqf2hRGnQQHSLmBk//kkKL2cSVDXltsZf
|
||||||
|
19cG+1/+sVhvAoIBAQCAnOZgSNEBPP60JdukC9SaKUYLLocdt6cgpbPWAiXOdBo7
|
||||||
|
WFL196V5N3jZRDVSUfo+nCZ0dGcVhOAAS0NcoZ+SjrQT49oCQTNiFXByZEmqx5ah
|
||||||
|
CT43jZ9VQduBH9kDNiUcWnqIrkPiWNSBMnT5x2NUOXYzNY0vTSlbQc8Org+Ar/RP
|
||||||
|
oXUp1i7mwW2TqHvw4Ew0vdnkqaOW3JL+/OK/XYBQTIC9mCPmD+ds3Io7Kt4gmR+4
|
||||||
|
JouiL6tNMTVc6w40PrYAGCuA9OHJoEbEe/9ML9qrV/xIyCpTervZyBBQD3HbPidR
|
||||||
|
H+BP9qlEidvcu+pJnlhg90AawycWs4soYkavdn7nAoIBAE6UTCQQORLyEWeAgSNu
|
||||||
|
kUtDFYiz9ZCCQK5f4abUoSFMQTQZajbujyY+a5kEOV/71vu/qfC+1iyIu0cZL/xT
|
||||||
|
t6tHL5MkpxxNF4ItPi9DDPiLfkoO2z6dyWffUqpb4lnxnHjfR3Hs95pgfGC5wrDp
|
||||||
|
mGYH3ZFnuJ+pMS1MJyPlyVFO8V1zgxNS5DpYkvZtfsPlnW/6v8V5McF9XwzkBNwo
|
||||||
|
yKUQgwpd8xPiJ1uaxTs3rIdEIXUA2VhomY+VATQQk0QNWf7Kl3+xQ8Efw8rsM5SR
|
||||||
|
+xws+xhQvhPhYzR7fc2OwFhTWxF8jtGkQ2Hac9nxkxNN+/0vJTzdjl7ZLq6vzNzD
|
||||||
|
FRECggEACyj3OwkbDMnyCtCH9Wp/fLpARkpHN+LZecUL0V6Zt2Iy9aVLSZuVLe92
|
||||||
|
dA2UWcOEenMmMzON5bL+d9Nt+beyZaU8tQZYFXLLZ4j+mZZhWX6O5rHdZhlIEN4S
|
||||||
|
NSO72c9ikcp7+PPYh07hAKEFlGnv0WwvTsdCWovv9CrPrj/vkBUevKWSWRkff+pW
|
||||||
|
9kMmFgTpA2/9z5Cys45jVpUM5M5clTzNCUPBrVVVmpcTOwKUrwemy5m4PirNJgjg
|
||||||
|
YRfenxGajQkp/ypi8eh1m7ceYnOxa4Mk9YctaIym3lkGBqn/9hgoOrI0nPEDM+qQ
|
||||||
|
io4wIYBrX/SPmp7tQvWI18Ike/IDtA==
|
||||||
|
-----END PRIVATE KEY-----
|
||||||
120
rust/pv/tests/cert_verifier.rs
Normal file
120
rust/pv/tests/cert_verifier.rs
Normal file
@@ -0,0 +1,120 @@
|
|||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
//
|
||||||
|
// Copyright IBM Corp. 2023
|
||||||
|
|
||||||
|
use pv::request::CertVerifier;
|
||||||
|
use pv::test_utils::*;
|
||||||
|
use pv::{Error, HkdVerifyErrorType::*};
|
||||||
|
use std::ffi::c_int;
|
||||||
|
|
||||||
|
#[track_caller]
|
||||||
|
fn verify_sign_error(exp_raw: c_int, obs: Error) {
|
||||||
|
verify_sign_error_slice(&[exp_raw], obs)
|
||||||
|
}
|
||||||
|
fn verify_sign_error_slice(exp_raw: &[c_int], obs: Error) {
|
||||||
|
if exp_raw
|
||||||
|
.iter()
|
||||||
|
.filter(|e| match &obs {
|
||||||
|
Error::HkdVerify(ty) => match ty {
|
||||||
|
IbmSignInvalid(err, _d) => &&err.as_raw() == e,
|
||||||
|
_ => false,
|
||||||
|
},
|
||||||
|
e => panic!("Unexpected error type: {e:?}"),
|
||||||
|
})
|
||||||
|
.count()
|
||||||
|
== 0
|
||||||
|
{
|
||||||
|
panic!("Error {obs:?} did not match one of the expected {exp_raw:?}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn verifier_new() {
|
||||||
|
let root_chn_crt = get_cert_asset_path_string("root_ca.chained.crt");
|
||||||
|
let root_crt = get_cert_asset_path_string("root_ca.crt");
|
||||||
|
let inter_crt = get_cert_asset_path_string("inter_ca.crt");
|
||||||
|
let inter_fake_crt = get_cert_asset_path_string("fake_inter_ca.crt");
|
||||||
|
let inter_fake_crl = get_cert_asset_path_string("fake_inter_ca.crl");
|
||||||
|
let inter_crl = get_cert_asset_path_string("inter_ca.crl");
|
||||||
|
let ibm_crt = get_cert_asset_path_string("ibm.crt");
|
||||||
|
let ibm_early_crt = get_cert_asset_path_string("ibm_outdated_early.crl");
|
||||||
|
let ibm_late_crt = get_cert_asset_path_string("ibm_outdated_late.crl");
|
||||||
|
let ibm_rev_crt = get_cert_asset_path_string("ibm_rev.crt");
|
||||||
|
|
||||||
|
// Too many signing keys
|
||||||
|
let verifier = CertVerifier::new(&[ibm_crt.clone(), ibm_rev_crt.clone()], &[], &None, true);
|
||||||
|
assert!(matches!(verifier, Err(Error::HkdVerify(ManyIbmSignKeys))));
|
||||||
|
|
||||||
|
// No CRL for each X509
|
||||||
|
let verifier = CertVerifier::new(
|
||||||
|
&[inter_crt.clone(), ibm_crt.clone()],
|
||||||
|
&[inter_crl.clone()],
|
||||||
|
&Some(root_crt),
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
verify_sign_error(3, verifier.unwrap_err());
|
||||||
|
let verifier = CertVerifier::new(
|
||||||
|
&[inter_crt.clone(), ibm_crt.clone()],
|
||||||
|
&[],
|
||||||
|
&Some(root_chn_crt.clone()),
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
verify_sign_error(3, verifier.unwrap_err());
|
||||||
|
|
||||||
|
// Wrong intermediate (or ibm key)
|
||||||
|
let verifier = CertVerifier::new(
|
||||||
|
&[inter_fake_crt, ibm_crt.clone()],
|
||||||
|
&[inter_fake_crl],
|
||||||
|
&Some(root_chn_crt.clone()),
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
// Depending on the OpenSSL version different error codes can appear
|
||||||
|
verify_sign_error_slice(&[20, 30], verifier.unwrap_err());
|
||||||
|
|
||||||
|
// Wrong root ca
|
||||||
|
let verifier = CertVerifier::new(
|
||||||
|
&[inter_crt.clone(), ibm_crt.clone()],
|
||||||
|
&[inter_crl.clone()],
|
||||||
|
&None,
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
verify_sign_error(20, verifier.unwrap_err());
|
||||||
|
|
||||||
|
// Correct signing key + intermediate cert
|
||||||
|
let _verifier = CertVerifier::new(
|
||||||
|
&[inter_crt.clone(), ibm_crt.clone()],
|
||||||
|
&[inter_crl.clone()],
|
||||||
|
&Some(root_chn_crt.clone()),
|
||||||
|
false,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// No intermediate key
|
||||||
|
let verifier = CertVerifier::new(&[ibm_crt], &[], &Some(root_chn_crt.clone()), false);
|
||||||
|
verify_sign_error(20, verifier.unwrap_err());
|
||||||
|
|
||||||
|
// IBM Sign outdated
|
||||||
|
let verifier = CertVerifier::new(
|
||||||
|
&[inter_crt.clone(), ibm_early_crt],
|
||||||
|
&[inter_crl.clone()],
|
||||||
|
&Some(root_chn_crt.clone()),
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
assert!(matches!(verifier, Err(Error::HkdVerify(NoIbmSignKey))));
|
||||||
|
let verifier = CertVerifier::new(
|
||||||
|
&[inter_crt.clone(), ibm_late_crt],
|
||||||
|
&[inter_crl.clone()],
|
||||||
|
&Some(root_chn_crt.clone()),
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
assert!(matches!(verifier, Err(Error::HkdVerify(NoIbmSignKey))));
|
||||||
|
|
||||||
|
// Revoked
|
||||||
|
let verifier = CertVerifier::new(
|
||||||
|
&[inter_crt, ibm_rev_crt],
|
||||||
|
&[inter_crl],
|
||||||
|
&Some(root_chn_crt),
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
verify_sign_error(23, verifier.unwrap_err());
|
||||||
|
}
|
||||||
21
rust/pv_core/Cargo.toml
Normal file
21
rust/pv_core/Cargo.toml
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
[package]
|
||||||
|
name = "pv_core"
|
||||||
|
version = "1.0.0"
|
||||||
|
edition.workspace = true
|
||||||
|
license.workspace = true
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
libc = "0.2.49"
|
||||||
|
log = { version = "0.4.6", features = ["std", "release_max_level_debug"] }
|
||||||
|
thiserror = "1.0.33"
|
||||||
|
utils = {path = "../utils"}
|
||||||
|
zerocopy = {version = "0.7", features = ["derive"]}
|
||||||
|
serde = { version = "1.0.139", features = ["derive"]}
|
||||||
|
byteorder = "1.3"
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
serde_test = "1"
|
||||||
|
mockito = {version = "1", default-features = false }
|
||||||
|
lazy_static = "1.1"
|
||||||
77
rust/pv_core/src/error.rs
Normal file
77
rust/pv_core/src/error.rs
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
//
|
||||||
|
// Copyright IBM Corp. 2023
|
||||||
|
|
||||||
|
/// Result type for this crate
|
||||||
|
pub type Result<T, E = Error> = std::result::Result<T, E>;
|
||||||
|
|
||||||
|
/// Error cases for this crate
|
||||||
|
#[allow(missing_docs)]
|
||||||
|
#[derive(thiserror::Error, Debug)]
|
||||||
|
#[non_exhaustive]
|
||||||
|
pub enum Error {
|
||||||
|
#[cfg_attr(debug_assertions, error("Ultravisor: '{msg}' ({rc:#06x},{rrc:#06x})"))]
|
||||||
|
#[cfg_attr(not(debug_assertions), error("Ultravisor: '{msg}' ({rc:#06x})"))]
|
||||||
|
Uv {
|
||||||
|
rc: u16,
|
||||||
|
rrc: u16,
|
||||||
|
msg: &'static str,
|
||||||
|
},
|
||||||
|
|
||||||
|
#[error("{0}")]
|
||||||
|
Specification(String),
|
||||||
|
|
||||||
|
#[error("Cannot {ty} {ctx} at `{path}`")]
|
||||||
|
FileIo {
|
||||||
|
ty: FileIoErrorType,
|
||||||
|
ctx: String,
|
||||||
|
path: String,
|
||||||
|
source: std::io::Error,
|
||||||
|
},
|
||||||
|
#[error("Cannot {ty} `{path}`")]
|
||||||
|
FileAccess {
|
||||||
|
ty: FileAccessErrorType,
|
||||||
|
path: String,
|
||||||
|
source: std::io::Error,
|
||||||
|
},
|
||||||
|
|
||||||
|
#[error("Cannot encode secrets (Too many secrets)")]
|
||||||
|
ManySecrets,
|
||||||
|
|
||||||
|
#[error("Cannot decode secret list")]
|
||||||
|
InvSecretList(#[source] std::io::Error),
|
||||||
|
|
||||||
|
#[error("Input does not contain an add-secret request")]
|
||||||
|
NoAsrcb,
|
||||||
|
|
||||||
|
#[error("Input contains unsupported user-data type: {0:#06x}")]
|
||||||
|
UnsupportedUserData(u16),
|
||||||
|
|
||||||
|
// errors from other crates
|
||||||
|
#[error(transparent)]
|
||||||
|
Io(#[from] std::io::Error),
|
||||||
|
#[error(transparent)]
|
||||||
|
ParseInt(#[from] std::num::ParseIntError),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Error cases for I/O operations
|
||||||
|
#[derive(thiserror::Error, Debug)]
|
||||||
|
#[non_exhaustive]
|
||||||
|
#[allow(missing_docs)]
|
||||||
|
pub enum FileIoErrorType {
|
||||||
|
#[error("read")]
|
||||||
|
Read,
|
||||||
|
#[error("write")]
|
||||||
|
Write,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Error cases for accessing files
|
||||||
|
#[derive(thiserror::Error, Debug)]
|
||||||
|
#[non_exhaustive]
|
||||||
|
#[allow(missing_docs)]
|
||||||
|
pub enum FileAccessErrorType {
|
||||||
|
#[error("open")]
|
||||||
|
Open,
|
||||||
|
#[error("create")]
|
||||||
|
Create,
|
||||||
|
}
|
||||||
93
rust/pv_core/src/lib.rs
Normal file
93
rust/pv_core/src/lib.rs
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
//
|
||||||
|
// Copyright IBM Corp. 2023
|
||||||
|
#![deny(missing_docs)]
|
||||||
|
#![allow(unused)]
|
||||||
|
//! pv_core - basic library for pv-tools
|
||||||
|
//!
|
||||||
|
//! This library is intened to be used by tools and libraries that
|
||||||
|
//! are used for creating and managing IBM Secure Execution guests.
|
||||||
|
//! `pv_core` provides abstraction layers for secure memory management,
|
||||||
|
//! logging, and accessing the uvdevice.
|
||||||
|
//!
|
||||||
|
//! It does not provide any cryptographic operations through OpenSSL.
|
||||||
|
//! For this use `pv` which reexports all symbos from this crate.
|
||||||
|
mod error;
|
||||||
|
mod log;
|
||||||
|
mod macros;
|
||||||
|
mod tmpfile;
|
||||||
|
mod utils;
|
||||||
|
mod uvdevice;
|
||||||
|
mod uvsecret;
|
||||||
|
|
||||||
|
pub use crate::log::PvLogger;
|
||||||
|
pub use error::{Error, FileAccessErrorType, FileIoErrorType, Result};
|
||||||
|
|
||||||
|
/// Miscellaneous functions and definitions
|
||||||
|
pub mod misc {
|
||||||
|
pub use crate::utils::pv_guest_bit_set;
|
||||||
|
pub use crate::utils::{create_file, open_file, read_exact_file, read_file, write_file};
|
||||||
|
pub use crate::utils::{parse_hex, to_u16, to_u32, try_parse_u128, try_parse_u64};
|
||||||
|
pub use crate::utils::{read, write};
|
||||||
|
pub use crate::utils::{Flags, Lsb0Flags64, Msb0Flags64};
|
||||||
|
|
||||||
|
pub use crate::tmpfile::TemporaryDirectory;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Definitions and functions for interacting with the Ultravisor
|
||||||
|
pub mod uv {
|
||||||
|
pub use crate::uvdevice::secret::{AddCmd, ListCmd, LockCmd};
|
||||||
|
pub use crate::uvdevice::secret::{ListableSecretType, SecretEntry, SecretList};
|
||||||
|
pub use crate::uvdevice::{
|
||||||
|
uv_ioctl, ConfigUid, UvCmd, UvDevice, UvDeviceInfo, UvFlags, UvcSuccess,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Functionalities to verify UV requests
|
||||||
|
pub mod request {
|
||||||
|
/// Functionalities for reading add-secret requests
|
||||||
|
pub mod uvsecret {
|
||||||
|
pub use crate::uvsecret::AddSecretMagic;
|
||||||
|
pub use crate::uvsecret::UserDataType;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Version number of the request in system-endian
|
||||||
|
pub type RequestVersion = u32;
|
||||||
|
/// Request magic value
|
||||||
|
///
|
||||||
|
/// The first 8 byte of a request providing an identifier of the request type
|
||||||
|
/// for programs
|
||||||
|
pub type RequestMagic = [u8; 8];
|
||||||
|
/// A `MagicValue` is a bytepattern, that indicates if a byte slice contains the specified
|
||||||
|
/// (binary) data.
|
||||||
|
pub trait MagicValue<const N: usize> {
|
||||||
|
/// Magic value as byte array
|
||||||
|
const MAGIC: [u8; N];
|
||||||
|
/// Test whether the given slice starts with the magic value.
|
||||||
|
fn starts_with_magic(v: &[u8]) -> bool {
|
||||||
|
if v.len() < Self::MAGIC.len() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
v[..Self::MAGIC.len()] == Self::MAGIC
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Provides cargo version Info about this crate.
|
||||||
|
///
|
||||||
|
/// Produces `pv_core-crate <version>`
|
||||||
|
pub const fn crate_info() -> &'static str {
|
||||||
|
concat!(env!("CARGO_PKG_NAME"), "-crate ", env!("CARGO_PKG_VERSION"))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Internal definitions/ imports
|
||||||
|
const PAGESIZE: usize = 0x1000;
|
||||||
|
use ::utils::assert_size;
|
||||||
|
use ::utils::static_assert;
|
||||||
|
|
||||||
|
#[doc(hidden)]
|
||||||
|
/// stuff pv_core and pv share. Not intended for other users
|
||||||
|
pub mod for_pv {
|
||||||
|
pub use crate::uvdevice::secret::ser_gsid;
|
||||||
|
pub use crate::uvdevice::secret::SECRET_ID_SIZE;
|
||||||
|
}
|
||||||
41
rust/pv_core/src/macros.rs
Normal file
41
rust/pv_core/src/macros.rs
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
//
|
||||||
|
// Copyright IBM Corp. 2023
|
||||||
|
|
||||||
|
macro_rules! path_to_str {
|
||||||
|
($path: expr) => {
|
||||||
|
$path.as_ref().to_str().unwrap_or("no UTF-8 path")
|
||||||
|
};
|
||||||
|
}
|
||||||
|
pub(crate) use path_to_str;
|
||||||
|
|
||||||
|
macro_rules! file_error {
|
||||||
|
($ty: tt, $ctx: expr, $path:expr, $src: expr) => {
|
||||||
|
$crate::Error::FileIo {
|
||||||
|
ty: $crate::FileIoErrorType::$ty,
|
||||||
|
ctx: $ctx.to_string(),
|
||||||
|
path: $path.to_string(),
|
||||||
|
source: $src,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
pub(crate) use file_error;
|
||||||
|
|
||||||
|
macro_rules! bail_spec {
|
||||||
|
($str: expr) => {
|
||||||
|
return Err($crate::Error::Specification($str.to_string()))
|
||||||
|
};
|
||||||
|
}
|
||||||
|
pub(crate) use bail_spec;
|
||||||
|
|
||||||
|
#[doc(hidden)]
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! file_acc_error {
|
||||||
|
($ty: tt, $path:expr, $src: expr) => {
|
||||||
|
$crate::Error::FileAccess {
|
||||||
|
ty: $crate::FileAccessErrorType::$ty,
|
||||||
|
path: $path.to_string(),
|
||||||
|
source: $src,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
152
rust/pv_core/src/tmpfile.rs
Normal file
152
rust/pv_core/src/tmpfile.rs
Normal file
@@ -0,0 +1,152 @@
|
|||||||
|
use std::{
|
||||||
|
ffi::{CString, OsStr},
|
||||||
|
os::unix::prelude::OsStrExt,
|
||||||
|
path::{Path, PathBuf},
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Rust wrapper for `libc::mkdtemp`
|
||||||
|
fn mkdtemp<P: AsRef<Path>>(template: P) -> Result<PathBuf, std::io::Error> {
|
||||||
|
let template_cstr = CString::new(template.as_ref().as_os_str().as_bytes())?;
|
||||||
|
let template_raw = template_cstr.into_raw();
|
||||||
|
unsafe {
|
||||||
|
// SAFETY: template_raw is a valid CString because it was generated by
|
||||||
|
// the `CString::new`.
|
||||||
|
let ret = libc::mkdtemp(template_raw);
|
||||||
|
|
||||||
|
if ret.is_null() {
|
||||||
|
Err(std::io::Error::last_os_error())
|
||||||
|
} else {
|
||||||
|
// SAFETY: `template_raw` is still a valid CString because it was
|
||||||
|
// generated by `CString::new` and modified by `libc::mkdtemp`.
|
||||||
|
let path_cstr = std::ffi::CString::from_raw(template_raw);
|
||||||
|
let path = OsStr::from_bytes(path_cstr.as_bytes());
|
||||||
|
let path = std::path::PathBuf::from(path);
|
||||||
|
|
||||||
|
Ok(path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This type creates a temporary directory that is automatically removed when
|
||||||
|
/// it goes out of scope. It utilizes the `mkdtemp` function and its semantics,
|
||||||
|
/// with the addition of automatically including the template characters
|
||||||
|
/// `XXXXXX`.
|
||||||
|
#[derive(PartialEq, Eq, Debug)]
|
||||||
|
pub struct TemporaryDirectory {
|
||||||
|
path: Box<Path>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TemporaryDirectory {
|
||||||
|
/// Creates a temporary directory using `prefix` as directory prefix.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// An error is returned if the temporary directory could not be created.
|
||||||
|
pub fn new<P: AsRef<Path>>(prefix: P) -> Result<Self, std::io::Error> {
|
||||||
|
let mut template = prefix.as_ref().to_owned();
|
||||||
|
let mut template_os_string = template.as_mut_os_string();
|
||||||
|
template_os_string.push("XXXXXX");
|
||||||
|
|
||||||
|
let temp_dir = mkdtemp(template_os_string)?;
|
||||||
|
Ok(Self {
|
||||||
|
path: temp_dir.into_boxed_path(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the path of the created temporary directory.
|
||||||
|
pub fn path(&self) -> &Path {
|
||||||
|
self.path.as_ref()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn forget(mut self) {
|
||||||
|
self.path = PathBuf::new().into_boxed_path();
|
||||||
|
std::mem::forget(self);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Removes the created temporary directory and it's contents.
|
||||||
|
pub fn close(mut self) -> std::io::Result<()> {
|
||||||
|
let ret = std::fs::remove_dir_all(&self.path);
|
||||||
|
self.forget();
|
||||||
|
ret
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AsRef<Path> for TemporaryDirectory {
|
||||||
|
fn as_ref(&self) -> &Path {
|
||||||
|
self.path()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for TemporaryDirectory {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
let _ = std::fs::remove_dir_all(&self.path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
use super::{mkdtemp, TemporaryDirectory};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn mkdtemp_test() {
|
||||||
|
let template_inv_not_last_characters = "XXXXXXyay";
|
||||||
|
let template_inv_too_less_x = "yayXXXXX";
|
||||||
|
let template_inv_path_does_not_exist = "../NA-yay/XXXXXX";
|
||||||
|
|
||||||
|
let template = "yayXXXXXX";
|
||||||
|
|
||||||
|
let err = mkdtemp(template_inv_not_last_characters).expect_err("invalid template");
|
||||||
|
let err = mkdtemp(template_inv_too_less_x).expect_err("invalid template");
|
||||||
|
let err =
|
||||||
|
mkdtemp(template_inv_path_does_not_exist).expect_err("path does not exist template");
|
||||||
|
|
||||||
|
let path = mkdtemp(template).expect("mkdtemp should work");
|
||||||
|
assert!(path.exists());
|
||||||
|
assert!(path.as_os_str().to_str().expect("works").starts_with("yay"));
|
||||||
|
std::fs::remove_dir(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn temporary_directory_empty_name_test() {
|
||||||
|
let temp_dir = TemporaryDirectory::new("").expect("should work");
|
||||||
|
let path = temp_dir.path().to_owned();
|
||||||
|
assert!(path.exists());
|
||||||
|
|
||||||
|
// Test that close removes the directory
|
||||||
|
temp_dir.close();
|
||||||
|
assert!(!path.exists());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn temporary_directory_drop_test() {
|
||||||
|
let temp_dir = TemporaryDirectory::new("").expect("should work");
|
||||||
|
let path = temp_dir.path().to_owned();
|
||||||
|
assert!(path.exists());
|
||||||
|
|
||||||
|
// Test that the destructor removes the directory
|
||||||
|
drop(temp_dir);
|
||||||
|
assert!(!path.exists());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn temporary_directory_close_test() {
|
||||||
|
let temp_dir = TemporaryDirectory::new("yay").expect("should work");
|
||||||
|
|
||||||
|
let path = temp_dir.path().to_owned();
|
||||||
|
assert!(path.exists());
|
||||||
|
assert!(path.as_os_str().to_str().expect("works").starts_with("yay"));
|
||||||
|
|
||||||
|
// Test that close() removes the directory
|
||||||
|
temp_dir.close();
|
||||||
|
assert!(!path.exists());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn temporary_directory_as_ref_test() {
|
||||||
|
let temp_dir = TemporaryDirectory::new("").expect("should work");
|
||||||
|
|
||||||
|
assert_eq!(temp_dir.path(), temp_dir.as_ref());
|
||||||
|
}
|
||||||
|
}
|
||||||
562
rust/pv_core/src/utils.rs
Normal file
562
rust/pv_core/src/utils.rs
Normal file
@@ -0,0 +1,562 @@
|
|||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
//
|
||||||
|
// Copyright IBM Corp. 2023
|
||||||
|
use crate::{
|
||||||
|
macros::{bail_spec, file_error, path_to_str},
|
||||||
|
Error, FileAccessErrorType, FileIoErrorType, Result,
|
||||||
|
};
|
||||||
|
use std::{
|
||||||
|
fs::File,
|
||||||
|
io::{Read, Write},
|
||||||
|
path::Path,
|
||||||
|
};
|
||||||
|
use zerocopy::{AsBytes, BigEndian, FromBytes, FromZeroes, U64};
|
||||||
|
|
||||||
|
/// Trait that describes bitflags, represented by `T`.
|
||||||
|
pub trait Flags<T>: From<T> + for<'a> From<&'a T> {
|
||||||
|
/// Set the specified bit to one.
|
||||||
|
/// # Panics
|
||||||
|
///Panics if bit is >= 64
|
||||||
|
fn set_bit(&mut self, bit: u8);
|
||||||
|
/// Set the specified bit to zero.
|
||||||
|
/// # Panics
|
||||||
|
///Panics if bit is >= 64
|
||||||
|
fn unset_bit(&mut self, bit: u8);
|
||||||
|
/// Test if the specified bit is set.
|
||||||
|
/// # Panics
|
||||||
|
///Panics if bit is >= 64
|
||||||
|
fn is_set(&self, bit: u8) -> bool;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Bitflags in MSB0 ordering
|
||||||
|
///
|
||||||
|
/// Wraps an u64 to set/get individual bits
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Debug, Clone, Copy, Default, AsBytes, FromZeroes, FromBytes)]
|
||||||
|
pub struct Msb0Flags64(U64<BigEndian>);
|
||||||
|
impl Flags<u64> for Msb0Flags64 {
|
||||||
|
#[track_caller]
|
||||||
|
fn set_bit(&mut self, bit: u8) {
|
||||||
|
assert!(bit < 64, "Flag bit set to greater than 63");
|
||||||
|
let mut v = self.0.get();
|
||||||
|
v |= 1 << (63 - bit);
|
||||||
|
self.0.set(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[track_caller]
|
||||||
|
fn unset_bit(&mut self, bit: u8) {
|
||||||
|
assert!(bit < 64, "Flag bit set to greater than 63");
|
||||||
|
let mut v = self.0.get();
|
||||||
|
v &= !(1 << (63 - bit));
|
||||||
|
self.0.set(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[track_caller]
|
||||||
|
fn is_set(&self, bit: u8) -> bool {
|
||||||
|
assert!(bit < 64, "Flag bit set to greater than 63");
|
||||||
|
self.0.get() & (1 << (63 - bit)) > 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<u64> for Msb0Flags64 {
|
||||||
|
fn from(value: u64) -> Self {
|
||||||
|
Self(value.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&u64> for Msb0Flags64 {
|
||||||
|
fn from(value: &u64) -> Self {
|
||||||
|
(*value).into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Bitflags in LSB0 ordering
|
||||||
|
///
|
||||||
|
/// Wraps an u64 to set/get individual bits
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Debug, Clone, Copy, Default, AsBytes, FromZeroes, FromBytes)]
|
||||||
|
pub struct Lsb0Flags64(U64<BigEndian>);
|
||||||
|
impl Flags<u64> for Lsb0Flags64 {
|
||||||
|
#[track_caller]
|
||||||
|
fn set_bit(&mut self, bit: u8) {
|
||||||
|
assert!(bit < 64, "Flag bit set to greater than 63");
|
||||||
|
let mut v = self.0.get();
|
||||||
|
v |= 1 << bit;
|
||||||
|
self.0.set(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[track_caller]
|
||||||
|
fn unset_bit(&mut self, bit: u8) {
|
||||||
|
assert!(bit < 64, "Flag bit set to greater than 63");
|
||||||
|
let mut v = self.0.get();
|
||||||
|
v &= !(1 << bit);
|
||||||
|
self.0.set(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[track_caller]
|
||||||
|
fn is_set(&self, bit: u8) -> bool {
|
||||||
|
assert!(bit < 64, "Flag bit set to greater than 63");
|
||||||
|
self.0.get() & (1 << bit) > 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<u64> for Lsb0Flags64 {
|
||||||
|
fn from(value: u64) -> Self {
|
||||||
|
Self(value.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&u64> for Lsb0Flags64 {
|
||||||
|
fn from(value: &u64) -> Self {
|
||||||
|
(*value).into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Tries to convert a BE hex string into a 128 unsigned integer
|
||||||
|
/// The hexstring must contain 32chars of hexdigits
|
||||||
|
///
|
||||||
|
/// * `hex_str` - string to convert can be prepended with "0x"
|
||||||
|
/// * `ctx` - Error context string in case of an error
|
||||||
|
/// ```rust
|
||||||
|
/// # use std::error::Error;
|
||||||
|
/// # use pv_core::misc::try_parse_u128;
|
||||||
|
/// # fn main() -> Result<(), Box<dyn Error>> {
|
||||||
|
/// let hex = "11223344556677889900aabbccddeeff";
|
||||||
|
/// try_parse_u128(&hex, "The test")?;
|
||||||
|
/// # Ok(())
|
||||||
|
/// # }
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
/// If `hex_string` is not a 32 byte hex string an Error appears
|
||||||
|
pub fn try_parse_u128(hex_str: &str, ctx: &str) -> Result<[u8; 16]> {
|
||||||
|
let hex_str = if hex_str.starts_with("0x") {
|
||||||
|
hex_str.split_at(2).1
|
||||||
|
} else {
|
||||||
|
hex_str
|
||||||
|
};
|
||||||
|
if hex_str.len() != 32 {
|
||||||
|
bail_spec!(format!(
|
||||||
|
"{ctx} hexstring must be 32chars long to cover all 16 bytes"
|
||||||
|
));
|
||||||
|
}
|
||||||
|
parse_hex(hex_str).try_into().map_err(|_| {
|
||||||
|
Error::Specification(format!(
|
||||||
|
"{ctx} hexstring must be 32chars long to cover all 16 bytes"
|
||||||
|
))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Tries to convert a BE hex string into a 64 unsigned integer
|
||||||
|
/// The hexstring must *NOT* contain 16 chars of hexdigits, but
|
||||||
|
/// 16 chars at most.
|
||||||
|
///
|
||||||
|
/// * `hex_str` - string to convert can be prepended with "0x"
|
||||||
|
/// * `ctx` - Error context string in case of an error
|
||||||
|
/// ```rust
|
||||||
|
/// # use std::error::Error;
|
||||||
|
/// # use pv_core::misc::try_parse_u64;
|
||||||
|
/// # fn main() -> Result<(), Box<dyn Error>> {
|
||||||
|
/// let hex = "1234567890abcdef";
|
||||||
|
/// try_parse_u64(&hex, "The test")?;
|
||||||
|
/// # Ok(())
|
||||||
|
/// # }
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
/// If `hex_string` is not a 32 byte hex string an Error appears
|
||||||
|
pub fn try_parse_u64(hex_str: &str, ctx: &str) -> Result<u64> {
|
||||||
|
let hex_str = if hex_str.starts_with("0x") {
|
||||||
|
hex_str.split_at(2).1
|
||||||
|
} else {
|
||||||
|
hex_str
|
||||||
|
};
|
||||||
|
if hex_str.len() > 16 {
|
||||||
|
bail_spec!(format!(
|
||||||
|
"{ctx} hexstring {hex_str} must be max 16 chars long"
|
||||||
|
));
|
||||||
|
}
|
||||||
|
Ok(u64::from_str_radix(hex_str, 16)?)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Open a file.
|
||||||
|
///
|
||||||
|
/// Wraps [`File::open`]
|
||||||
|
///
|
||||||
|
/// * `path` - Path to file
|
||||||
|
pub fn open_file<P: AsRef<Path>>(path: P) -> Result<File> {
|
||||||
|
File::open(&path).map_err(|e| Error::FileAccess {
|
||||||
|
ty: FileAccessErrorType::Open,
|
||||||
|
path: path_to_str!(path).to_string(),
|
||||||
|
source: e,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a file.
|
||||||
|
///
|
||||||
|
/// Wraps [`File::create`]
|
||||||
|
///
|
||||||
|
/// * `path` - Path to file
|
||||||
|
pub fn create_file<P: AsRef<Path>>(path: P) -> Result<File> {
|
||||||
|
File::create(&path).map_err(|e| Error::FileAccess {
|
||||||
|
ty: FileAccessErrorType::Create,
|
||||||
|
path: path_to_str!(path).to_string(),
|
||||||
|
source: e,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Read exactly COUNT bytes into a buffer and return it.
|
||||||
|
///
|
||||||
|
/// * `path` - Path to file
|
||||||
|
/// * `ctx` - Error context string in case of an error
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
/// If this function encounters an "end of file" before completely filling
|
||||||
|
/// the buffer, it returns an error. The contents of `buf` are unspecified in this case.
|
||||||
|
///
|
||||||
|
/// If aber so hast du mmmeny other read error is encountered then this function immediately
|
||||||
|
/// returns. The contents of `buf` are unspecified in this case.
|
||||||
|
///
|
||||||
|
/// If this function returns an error, it is unspecified how many bytes it
|
||||||
|
/// has read, but it will never read more than would be necessary to
|
||||||
|
/// completely fill the buffer.
|
||||||
|
pub fn read_exact_file<P: AsRef<Path>, const COUNT: usize>(
|
||||||
|
path: P,
|
||||||
|
ctx: &str,
|
||||||
|
) -> Result<[u8; COUNT]> {
|
||||||
|
let mut f = std::fs::File::open(&path).map_err(|e| Error::FileAccess {
|
||||||
|
ty: crate::FileAccessErrorType::Open,
|
||||||
|
path: path_to_str!(path).to_string(),
|
||||||
|
source: e,
|
||||||
|
})?;
|
||||||
|
|
||||||
|
if f.metadata()?.len() as usize != COUNT {
|
||||||
|
bail_spec!(format!("{ctx} must be exactly {COUNT} bytes long"));
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut buf = [0; COUNT];
|
||||||
|
f.read_exact(&mut buf)
|
||||||
|
.map_err(|e| file_error!(Read, ctx, path_to_str!(path).to_string(), e))?;
|
||||||
|
Ok(buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Read content from a file and add context in case of an error
|
||||||
|
///
|
||||||
|
/// * `path` - Path to file
|
||||||
|
/// * `ctx` - Error context string in case of an error
|
||||||
|
///
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
/// Passes through any kind of error `std::fs::read` produces
|
||||||
|
pub fn read_file<P: AsRef<Path>>(path: P, ctx: &str) -> Result<Vec<u8>> {
|
||||||
|
std::fs::read(&path).map_err(|e| {
|
||||||
|
file_error!(
|
||||||
|
Read,
|
||||||
|
ctx,
|
||||||
|
path.as_ref().to_str().unwrap_or("no UTF-8 path"),
|
||||||
|
e
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Reads all content from a [`std::io::Read`] and add context in case of an error
|
||||||
|
///
|
||||||
|
/// * `path` - Path to file
|
||||||
|
/// * `ctx` - Error context string in case of an error
|
||||||
|
///
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
/// Passes through any kind of error `std::fs::read` produces
|
||||||
|
pub fn read<R: Read>(rd: &mut R, path: &str, ctx: &str) -> Result<Vec<u8>> {
|
||||||
|
let mut buf = vec![];
|
||||||
|
rd.read_to_end(&mut buf).map_err(|e| Error::FileIo {
|
||||||
|
ty: FileIoErrorType::Write,
|
||||||
|
ctx: ctx.to_string(),
|
||||||
|
path: path.to_string(),
|
||||||
|
source: e,
|
||||||
|
})?;
|
||||||
|
Ok(buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// write content to a file and add context in case of an error
|
||||||
|
///
|
||||||
|
/// * `path` - Path to file
|
||||||
|
/// * `ctx` - Error context string in case of an error
|
||||||
|
///
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
/// Passes through any kind of error `std::fs::write` produces
|
||||||
|
pub fn write_file<D: AsRef<[u8]>>(path: &str, data: D, ctx: &str) -> Result<()> {
|
||||||
|
std::fs::write(path, data.as_ref()).map_err(|e| Error::FileIo {
|
||||||
|
ty: FileIoErrorType::Write,
|
||||||
|
ctx: ctx.to_string(),
|
||||||
|
path: path.to_string(),
|
||||||
|
source: e,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Write content to a [`std::io::Write`] and add context in case of an error
|
||||||
|
///
|
||||||
|
/// * `path` - Path to file
|
||||||
|
/// * `ctx` - Error context string in case of an error
|
||||||
|
///
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
/// Passes through any kind of error `std::fs::write` produces
|
||||||
|
pub fn write<D: AsRef<[u8]>, W: Write>(wr: &mut W, data: D, path: &str, ctx: &str) -> Result<()> {
|
||||||
|
wr.write_all(data.as_ref()).map_err(|e| Error::FileIo {
|
||||||
|
ty: FileIoErrorType::Write,
|
||||||
|
ctx: ctx.to_string(),
|
||||||
|
path: path.to_string(),
|
||||||
|
source: e,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! usize_to_ui {
|
||||||
|
($(#[$attr:meta])* => $t: ident, $name:ident) => {
|
||||||
|
///Converts an [`usize`] to an [`
|
||||||
|
$(#[$attr])*
|
||||||
|
///`] if possible
|
||||||
|
pub fn $name(u: usize) -> Option<$t> {
|
||||||
|
if u > $t::MAX as usize {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(u as $t)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
usize_to_ui! {
|
||||||
|
#[doc = r"u32"]
|
||||||
|
=> u32, to_u32}
|
||||||
|
usize_to_ui! {
|
||||||
|
#[doc = r"u16"]
|
||||||
|
=> u16, to_u16}
|
||||||
|
|
||||||
|
/// Converts the hexstring into a byte vector.
|
||||||
|
///
|
||||||
|
/// Stops if the end or until a non hex chat is found
|
||||||
|
pub fn parse_hex(hex_str: &str) -> Vec<u8> {
|
||||||
|
let mut hex_bytes = hex_str.as_bytes().iter().map_while(|b| match b {
|
||||||
|
b'0'..=b'9' => Some(b - b'0'),
|
||||||
|
b'a'..=b'f' => Some(b - b'a' + 10),
|
||||||
|
b'A'..=b'F' => Some(b - b'A' + 10),
|
||||||
|
_ => None,
|
||||||
|
});
|
||||||
|
|
||||||
|
let mut bytes = Vec::new();
|
||||||
|
while let (Some(h), Some(l)) = (hex_bytes.next(), hex_bytes.next()) {
|
||||||
|
bytes.push(h << 4 | l)
|
||||||
|
}
|
||||||
|
bytes
|
||||||
|
}
|
||||||
|
/// Report if the `prot_virt_guest` sysfs entry is one.
|
||||||
|
///
|
||||||
|
/// If the entry does not exist returns false.
|
||||||
|
///
|
||||||
|
/// for non-s390-architectures:
|
||||||
|
/// Returns always false
|
||||||
|
/// A non-s390 system cannot be a secure execution guest.
|
||||||
|
#[allow(unreachable_code)]
|
||||||
|
pub fn pv_guest_bit_set() -> bool {
|
||||||
|
#[cfg(not(target_arch = "s390x"))]
|
||||||
|
return false;
|
||||||
|
//s390 branch
|
||||||
|
let v = std::fs::read("/sys/firmware/uv/prot_virt_guest").unwrap_or_else(|_| vec![0]);
|
||||||
|
let v: u8 = String::from_utf8_lossy(&v[..1]).parse().unwrap_or(0);
|
||||||
|
v == 1
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn msb_flags() {
|
||||||
|
let v = 17;
|
||||||
|
let v_flag: Msb0Flags64 = v.into();
|
||||||
|
assert_eq!(v, v_flag.0.get());
|
||||||
|
|
||||||
|
let mut v: Msb0Flags64 = 4.into();
|
||||||
|
v.unset_bit(61);
|
||||||
|
assert_eq!(v.0.get(), 0);
|
||||||
|
v.set_bit(61);
|
||||||
|
assert_eq!(4, v.0.get());
|
||||||
|
|
||||||
|
let mut v = Msb0Flags64::default();
|
||||||
|
v.set_bit(0);
|
||||||
|
assert_eq!(&[0x80, 0, 0, 0, 0, 0, 0, 0], v.as_bytes());
|
||||||
|
v.set_bit(0);
|
||||||
|
assert_eq!(&[0x80, 0, 0, 0, 0, 0, 0, 0], v.as_bytes());
|
||||||
|
v.set_bit(1);
|
||||||
|
assert_eq!(&[0xc0, 0, 0, 0, 0, 0, 0, 0], v.as_bytes());
|
||||||
|
v.set_bit(2);
|
||||||
|
assert_eq!(&[0xe0, 0, 0, 0, 0, 0, 0, 0], v.as_bytes());
|
||||||
|
v.set_bit(3);
|
||||||
|
assert_eq!(&[0xf0, 0, 0, 0, 0, 0, 0, 0], v.as_bytes());
|
||||||
|
|
||||||
|
v.unset_bit(3);
|
||||||
|
assert_eq!(&[0xe0, 0, 0, 0, 0, 0, 0, 0], v.as_bytes());
|
||||||
|
v.unset_bit(3);
|
||||||
|
assert_eq!(&[0xe0, 0, 0, 0, 0, 0, 0, 0], v.as_bytes());
|
||||||
|
|
||||||
|
v.set_bit(16);
|
||||||
|
assert_eq!(&[0xe0, 0, 0x80, 0, 0, 0, 0, 0], v.as_bytes());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[should_panic]
|
||||||
|
fn msb_flags_set_panic() {
|
||||||
|
Msb0Flags64::default().set_bit(64)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[should_panic]
|
||||||
|
fn msb_flags_unset_panic() {
|
||||||
|
Msb0Flags64::default().unset_bit(64)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn lsb_flags() {
|
||||||
|
let v = 17;
|
||||||
|
let v_flag: Lsb0Flags64 = v.into();
|
||||||
|
assert_eq!(v, v_flag.0.get());
|
||||||
|
|
||||||
|
let mut v: Lsb0Flags64 = 4.into();
|
||||||
|
v.unset_bit(2);
|
||||||
|
assert_eq!(v.0.get(), 0);
|
||||||
|
v.set_bit(2);
|
||||||
|
assert_eq!(4, v.0.get());
|
||||||
|
|
||||||
|
let mut v = Lsb0Flags64::default();
|
||||||
|
v.set_bit(0);
|
||||||
|
assert_eq!(&[0, 0, 0, 0, 0, 0, 0, 1], v.as_bytes());
|
||||||
|
v.set_bit(0);
|
||||||
|
assert_eq!(&[0, 0, 0, 0, 0, 0, 0, 1], v.as_bytes());
|
||||||
|
v.set_bit(1);
|
||||||
|
assert_eq!(&[0, 0, 0, 0, 0, 0, 0, 3], v.as_bytes());
|
||||||
|
v.set_bit(2);
|
||||||
|
assert_eq!(&[0, 0, 0, 0, 0, 0, 0, 7], v.as_bytes());
|
||||||
|
v.set_bit(3);
|
||||||
|
assert_eq!(&[0, 0, 0, 0, 0, 0, 0, 0xf], v.as_bytes());
|
||||||
|
|
||||||
|
v.unset_bit(3);
|
||||||
|
assert_eq!(&[0, 0, 0, 0, 0, 0, 0, 7], v.as_bytes());
|
||||||
|
v.unset_bit(3);
|
||||||
|
assert_eq!(&[0, 0, 0, 0, 0, 0, 0, 7], v.as_bytes());
|
||||||
|
|
||||||
|
v.set_bit(16);
|
||||||
|
assert_eq!(&[0, 0, 0, 0, 0, 1, 0, 7], v.as_bytes());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[should_panic]
|
||||||
|
fn lsb_flags_set_panic() {
|
||||||
|
Lsb0Flags64::default().set_bit(64)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[should_panic]
|
||||||
|
fn lsb_flags_unset_panic() {
|
||||||
|
Lsb0Flags64::default().unset_bit(64)
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn parse_hex() {
|
||||||
|
let s = "123456acbef0";
|
||||||
|
let exp = vec![0x12, 0x34, 0x56, 0xac, 0xbe, 0xf0];
|
||||||
|
assert_eq!(super::parse_hex(s), exp);
|
||||||
|
|
||||||
|
let s = "00123456acbef0";
|
||||||
|
let exp = vec![0, 0x12, 0x34, 0x56, 0xac, 0xbe, 0xf0];
|
||||||
|
assert_eq!(super::parse_hex(s), exp);
|
||||||
|
|
||||||
|
let s = "00123456acbef0ii90";
|
||||||
|
let exp = vec![0, 0x12, 0x34, 0x56, 0xac, 0xbe, 0xf0];
|
||||||
|
assert_eq!(super::parse_hex(s), exp);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn to_u32() {
|
||||||
|
assert_eq!(Some(17), super::to_u32(17));
|
||||||
|
assert_eq!(Some(0), super::to_u32(0));
|
||||||
|
assert_eq!(Some(u32::MAX), super::to_u32(u32::MAX as usize));
|
||||||
|
assert_eq!(None, super::to_u32(u32::MAX as usize + 1));
|
||||||
|
assert_eq!(None, super::to_u32(usize::MAX));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_u128() {
|
||||||
|
assert!(matches!(
|
||||||
|
try_parse_u128("123456", ""),
|
||||||
|
Err(Error::Specification(_))
|
||||||
|
));
|
||||||
|
assert!(matches!(
|
||||||
|
try_parse_u128("-1234", ""),
|
||||||
|
Err(Error::Specification(_))
|
||||||
|
));
|
||||||
|
assert!(matches!(
|
||||||
|
try_parse_u128("0011223344556677889900aabbccddeeff", ""),
|
||||||
|
Err(Error::Specification(_))
|
||||||
|
));
|
||||||
|
assert!(matches!(
|
||||||
|
try_parse_u128("dd11223344556677889900aabbccddeeff", ""),
|
||||||
|
Err(Error::Specification(_))
|
||||||
|
));
|
||||||
|
assert!(matches!(
|
||||||
|
try_parse_u128("-1223344556677889900aabbccddeeff", ""),
|
||||||
|
Err(Error::Specification(_))
|
||||||
|
));
|
||||||
|
|
||||||
|
assert!(matches!(
|
||||||
|
try_parse_u128("0x123456", ""),
|
||||||
|
Err(Error::Specification(_))
|
||||||
|
));
|
||||||
|
assert!(matches!(
|
||||||
|
try_parse_u128("-0x1234", ""),
|
||||||
|
Err(Error::Specification(_))
|
||||||
|
));
|
||||||
|
assert!(matches!(
|
||||||
|
try_parse_u128("0x0011223344556677889900aabbccddeeff", ""),
|
||||||
|
Err(Error::Specification(_))
|
||||||
|
));
|
||||||
|
assert!(matches!(
|
||||||
|
try_parse_u128("0xdd11223344556677889900aabbccddeeff", ""),
|
||||||
|
Err(Error::Specification(_))
|
||||||
|
));
|
||||||
|
assert!(matches!(
|
||||||
|
try_parse_u128("0x-1223344556677889900aabbccddeeff", ""),
|
||||||
|
Err(Error::Specification(_))
|
||||||
|
));
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
[
|
||||||
|
0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0x00, 0xaa, 0xbb, 0xcc, 0xdd,
|
||||||
|
0xee, 0xff
|
||||||
|
],
|
||||||
|
try_parse_u128("11223344556677889900aabbccddeeff", "").unwrap()
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
[
|
||||||
|
0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0x00, 0xaa, 0xbb, 0xcc, 0xdd,
|
||||||
|
0xee, 0xff
|
||||||
|
],
|
||||||
|
try_parse_u128("0x11223344556677889900aabbccddeeff", "").unwrap()
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
[
|
||||||
|
0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd,
|
||||||
|
0xee, 0xff
|
||||||
|
],
|
||||||
|
try_parse_u128("00112233445566778899aabbccddeeff", "").unwrap()
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
[
|
||||||
|
0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd,
|
||||||
|
0xee, 0xff
|
||||||
|
],
|
||||||
|
try_parse_u128("00112233445566778899aabbccddeeff", "").unwrap()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,13 +3,15 @@
|
|||||||
// Copyright IBM Corp. 2023
|
// Copyright IBM Corp. 2023
|
||||||
|
|
||||||
#![allow(non_camel_case_types)]
|
#![allow(non_camel_case_types)]
|
||||||
use crate::file_acc_error;
|
use crate::FileAccessErrorType;
|
||||||
use crate::{Error, Result};
|
use crate::{Error, Result};
|
||||||
use libc::c_ulong;
|
|
||||||
use log::debug;
|
use log::debug;
|
||||||
use std::convert::TryInto;
|
use std::{
|
||||||
use std::fs::File;
|
convert::TryInto,
|
||||||
use std::os::unix::prelude::{AsRawFd, RawFd};
|
ffi::c_ulong,
|
||||||
|
fs::File,
|
||||||
|
os::unix::prelude::{AsRawFd, RawFd},
|
||||||
|
};
|
||||||
|
|
||||||
#[cfg(not(test))]
|
#[cfg(not(test))]
|
||||||
use ::libc::ioctl;
|
use ::libc::ioctl;
|
||||||
@@ -22,6 +24,7 @@ mod ffi;
|
|||||||
mod info;
|
mod info;
|
||||||
mod test;
|
mod test;
|
||||||
pub use ffi::uv_ioctl;
|
pub use ffi::uv_ioctl;
|
||||||
|
pub mod secret;
|
||||||
|
|
||||||
pub use info::UvDeviceInfo;
|
pub use info::UvDeviceInfo;
|
||||||
#[allow(dead_code)] //TODO rm when pv learns attestation
|
#[allow(dead_code)] //TODO rm when pv learns attestation
|
||||||
@@ -178,7 +181,11 @@ impl UvDevice {
|
|||||||
.read(true)
|
.read(true)
|
||||||
.write(true)
|
.write(true)
|
||||||
.open(UvDevice::PATH)
|
.open(UvDevice::PATH)
|
||||||
.map_err(|e| file_acc_error!(Open, UvDevice::PATH, e))?,
|
.map_err(|e| Error::FileAccess {
|
||||||
|
ty: FileAccessErrorType::Open,
|
||||||
|
path: (UvDevice::PATH).to_string(),
|
||||||
|
source: e,
|
||||||
|
})?,
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
// Copyright IBM Corp. 2023
|
// Copyright IBM Corp. 2023
|
||||||
|
|
||||||
use crate::{assert_size, static_assert};
|
use crate::{assert_size, static_assert};
|
||||||
use zerocopy::{AsBytes, FromBytes};
|
use zerocopy::{AsBytes, FromBytes, FromZeroes};
|
||||||
|
|
||||||
pub const UVIO_ATT_ARCB_MAX_LEN: usize = 0x100000;
|
pub const UVIO_ATT_ARCB_MAX_LEN: usize = 0x100000;
|
||||||
pub const UVIO_ATT_MEASUREMENT_MAX_LEN: usize = 0x8000;
|
pub const UVIO_ATT_MEASUREMENT_MAX_LEN: usize = 0x8000;
|
||||||
@@ -55,7 +55,7 @@ assert_size!(uvio_ioctl_cb, 0x40);
|
|||||||
/// Note that bit 0 (UVIO_IOCTL_UVDEV_INFO_NR) is always zero for `supp_uv_cmds`
|
/// Note that bit 0 (UVIO_IOCTL_UVDEV_INFO_NR) is always zero for `supp_uv_cmds`
|
||||||
/// as there is no corresponding UV-call.
|
/// as there is no corresponding UV-call.
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
#[derive(Debug, Copy, Clone, AsBytes, FromBytes)]
|
#[derive(Debug, Copy, Clone, AsBytes, FromZeroes, FromBytes)]
|
||||||
pub struct uvio_uvdev_info {
|
pub struct uvio_uvdev_info {
|
||||||
pub supp_uvio_cmds: u64,
|
pub supp_uvio_cmds: u64,
|
||||||
pub supp_uv_cmds: u64,
|
pub supp_uv_cmds: u64,
|
||||||
@@ -84,7 +84,7 @@ pub const UVIO_ATT_UID_LEN: usize = 0x10;
|
|||||||
/// UV will return invalid command rc.
|
/// UV will return invalid command rc.
|
||||||
/// Obviously all numbers are in BIG-endian!
|
/// Obviously all numbers are in BIG-endian!
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
#[derive(Debug, AsBytes, FromBytes)]
|
#[derive(Debug, AsBytes, FromZeroes, FromBytes)]
|
||||||
pub struct uvio_attest {
|
pub struct uvio_attest {
|
||||||
pub arcb_addr: u64, //in
|
pub arcb_addr: u64, //in
|
||||||
pub meas_addr: u64, //out
|
pub meas_addr: u64, //out
|
||||||
@@ -9,7 +9,7 @@ use crate::{
|
|||||||
Result,
|
Result,
|
||||||
};
|
};
|
||||||
use std::fmt::Display;
|
use std::fmt::Display;
|
||||||
use zerocopy::{AsBytes, FromBytes};
|
use zerocopy::{AsBytes, FromZeroes};
|
||||||
|
|
||||||
/// Information of supported functions by the uvdevice
|
/// Information of supported functions by the uvdevice
|
||||||
///
|
///
|
||||||
489
rust/pv_core/src/uvdevice/secret.rs
Normal file
489
rust/pv_core/src/uvdevice/secret.rs
Normal file
@@ -0,0 +1,489 @@
|
|||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
//
|
||||||
|
// Copyright IBM Corp. 2023
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
assert_size,
|
||||||
|
misc::to_u16,
|
||||||
|
request::{uvsecret::AddSecretMagic, MagicValue},
|
||||||
|
uv::{uv_ioctl, UvCmd, UvDevice},
|
||||||
|
Error, Result, PAGESIZE,
|
||||||
|
};
|
||||||
|
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
|
||||||
|
use serde::{Serialize, Serializer};
|
||||||
|
use std::{
|
||||||
|
fmt::Display,
|
||||||
|
io::{Cursor, Read, Seek, Write},
|
||||||
|
slice::Iter,
|
||||||
|
vec::IntoIter,
|
||||||
|
};
|
||||||
|
use zerocopy::{AsBytes, FromBytes, FromZeroes, U16, U32};
|
||||||
|
|
||||||
|
/// _List Secrets_ Ultravisor command.
|
||||||
|
///
|
||||||
|
/// The List Secrets Ultravisor call is used to list the
|
||||||
|
/// secrets that are in the secret store for the current SE-guest.
|
||||||
|
pub struct ListCmd(Vec<u8>);
|
||||||
|
impl ListCmd {
|
||||||
|
fn with_size(size: usize) -> Self {
|
||||||
|
Self(vec![0; size])
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a new list secrets command with a one page capacity
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self::with_size(PAGESIZE)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for ListCmd {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl UvCmd for ListCmd {
|
||||||
|
fn data(&mut self) -> Option<&mut [u8]> {
|
||||||
|
Some(self.0.as_mut_slice())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cmd(&self) -> u64 {
|
||||||
|
uv_ioctl(UvDevice::LIST_SECRET_NR)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn rc_fmt(&self, _rc: u16, _rrc: u16) -> Option<&'static str> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// _Add Secret_ Ultravisor command.
|
||||||
|
///
|
||||||
|
/// The Add Secret Ultravisor-call is used to add a secret
|
||||||
|
/// to the secret store for the current SE-guest.
|
||||||
|
pub struct AddCmd(Vec<u8>);
|
||||||
|
|
||||||
|
impl AddCmd {
|
||||||
|
/// Create a new Add Secret command using the provided data.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// This function will return an error if the provided data does not start with the
|
||||||
|
/// ['crate::AddSecretRequest'] magic Value.
|
||||||
|
pub fn new<R: Read>(bin_add_secret_req: &mut R) -> Result<Self> {
|
||||||
|
let mut data = Vec::with_capacity(PAGESIZE);
|
||||||
|
bin_add_secret_req.read_to_end(&mut data)?;
|
||||||
|
|
||||||
|
if !AddSecretMagic::starts_with_magic(&data[..6]) {
|
||||||
|
return Err(Error::NoAsrcb);
|
||||||
|
}
|
||||||
|
Ok(Self(data))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl UvCmd for AddCmd {
|
||||||
|
fn data(&mut self) -> Option<&mut [u8]> {
|
||||||
|
Some(&mut self.0)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cmd(&self) -> u64 {
|
||||||
|
uv_ioctl(UvDevice::ADD_SECRET_NR)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn rc_fmt(&self, rc: u16, _rrc: u16) -> Option<&'static str> {
|
||||||
|
match rc {
|
||||||
|
0x0101 => Some("not allowed to modify the secret store"),
|
||||||
|
0x0102 => Some("secret store locked"),
|
||||||
|
0x0103 => Some("access exception when accessing request control block"),
|
||||||
|
0x0104 => Some("unsupported add secret version"),
|
||||||
|
0x0105 => Some("invalid request size"),
|
||||||
|
0x0106 => Some("invalid number of host-keys"),
|
||||||
|
0x0107 => Some("unsupported flags specified"),
|
||||||
|
0x0108 => Some("unable to decrypt the request"),
|
||||||
|
0x0109 => Some("unsupported secret provided"),
|
||||||
|
0x010a => Some("invalid length for the specified secret"),
|
||||||
|
0x010b => Some("secret store full"),
|
||||||
|
0x010c => Some("unable to add secret"),
|
||||||
|
0x010d => Some("dump in progress, try again later"),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// _Lock Secret Store_ Ultravisor command.
|
||||||
|
///
|
||||||
|
/// The Lock Secret Store Ultravisor-call is used to block
|
||||||
|
/// all changes to the secret store. Upon successful
|
||||||
|
/// completion of a Lock Secret Store Ultravisor-call, any
|
||||||
|
/// request to modify the secret store will fail.
|
||||||
|
pub struct LockCmd;
|
||||||
|
impl UvCmd for LockCmd {
|
||||||
|
fn cmd(&self) -> u64 {
|
||||||
|
uv_ioctl(UvDevice::LOCK_SECRET_NR)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn rc_fmt(&self, rc: u16, _rrc: u16) -> Option<&'static str> {
|
||||||
|
match rc {
|
||||||
|
0x0101 => Some("not allowed to modify the secret store"),
|
||||||
|
0x0102 => Some("secret store already locked"),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// List of secrets used to parse the [`crate::uv::ListCmd`] result.
|
||||||
|
///
|
||||||
|
/// The list should not hold more than 0xffffffff elements
|
||||||
|
#[derive(Debug, PartialEq, Eq, Serialize)]
|
||||||
|
pub struct SecretList {
|
||||||
|
total_num_secrets: usize,
|
||||||
|
secrets: Vec<SecretEntry>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> IntoIterator for &'a SecretList {
|
||||||
|
type Item = &'a SecretEntry;
|
||||||
|
type IntoIter = Iter<'a, SecretEntry>;
|
||||||
|
|
||||||
|
fn into_iter(self) -> Self::IntoIter {
|
||||||
|
self.iter()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IntoIterator for SecretList {
|
||||||
|
type Item = SecretEntry;
|
||||||
|
type IntoIter = IntoIter<Self::Item>;
|
||||||
|
|
||||||
|
fn into_iter(self) -> Self::IntoIter {
|
||||||
|
self.secrets.into_iter()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromIterator<SecretEntry> for SecretList {
|
||||||
|
fn from_iter<T: IntoIterator<Item = SecretEntry>>(iter: T) -> Self {
|
||||||
|
let secrets: Vec<_> = iter.into_iter().collect();
|
||||||
|
let total_num_secrets = secrets.len() as u16;
|
||||||
|
Self::new(total_num_secrets, secrets)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SecretList {
|
||||||
|
/// Creates a new SecretList.
|
||||||
|
///
|
||||||
|
/// The content of this list will very liekly not represent the status of the guest in the
|
||||||
|
/// Ultravisor. Use of [`SecretList::decode`] in any non-test environments is encuraged.
|
||||||
|
pub fn new(total_num_secrets: u16, secrets: Vec<SecretEntry>) -> Self {
|
||||||
|
Self {
|
||||||
|
total_num_secrets: total_num_secrets as usize,
|
||||||
|
secrets,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns an iterator over the slice.
|
||||||
|
///
|
||||||
|
/// The iterator yields all secret entries from start to end.
|
||||||
|
pub fn iter(&self) -> Iter<'_, SecretEntry> {
|
||||||
|
self.secrets.iter()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the length of this [`SecretList`].
|
||||||
|
pub fn len(&self) -> usize {
|
||||||
|
self.secrets.len()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns `true` if the [`SecretList`] contains no [`SecretEntry`].
|
||||||
|
pub fn is_empty(&self) -> bool {
|
||||||
|
self.secrets.is_empty()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Reports the number of secrets stored in UV.
|
||||||
|
///
|
||||||
|
/// This number may be not equal to the provided number of [`SecretEntry`]
|
||||||
|
pub fn total_num_secrets(&self) -> usize {
|
||||||
|
self.total_num_secrets
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Encodes the list in the same binary format the UV would do
|
||||||
|
pub fn encode<T: Write>(&self, w: &mut T) -> Result<()> {
|
||||||
|
let num_s = to_u16(self.secrets.len()).ok_or(Error::ManySecrets)?;
|
||||||
|
w.write_u16::<BigEndian>(num_s)?;
|
||||||
|
w.write_u16::<BigEndian>(
|
||||||
|
self.total_num_secrets
|
||||||
|
.try_into()
|
||||||
|
.map_err(|_| Error::ManySecrets)?,
|
||||||
|
)?;
|
||||||
|
w.write_all(&[0u8; 12])?;
|
||||||
|
for secret in &self.secrets {
|
||||||
|
w.write_all(secret.as_bytes())?;
|
||||||
|
}
|
||||||
|
w.flush().map_err(Error::Io)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Decodes the list from the binary format of the UV into this internal representation
|
||||||
|
pub fn decode<R: Read + Seek>(r: &mut R) -> std::io::Result<Self> {
|
||||||
|
let num_s = r.read_u16::<BigEndian>()?;
|
||||||
|
let total_num_secrets = r.read_u16::<BigEndian>()? as usize;
|
||||||
|
let mut v: Vec<SecretEntry> = Vec::with_capacity(num_s as usize);
|
||||||
|
r.seek(std::io::SeekFrom::Current(12))?; //skip reserved bytes
|
||||||
|
let mut buf = [0u8; SecretEntry::STRUCT_SIZE];
|
||||||
|
for _ in 0..num_s {
|
||||||
|
r.read_exact(&mut buf)?;
|
||||||
|
//cannot fail. buffer has the same size as the secret entry
|
||||||
|
let secr = SecretEntry::read_from(buf.as_slice()).unwrap();
|
||||||
|
v.push(secr);
|
||||||
|
}
|
||||||
|
Ok(Self {
|
||||||
|
total_num_secrets,
|
||||||
|
secrets: v,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<ListCmd> for SecretList {
|
||||||
|
type Error = Error;
|
||||||
|
fn try_from(mut list: ListCmd) -> Result<SecretList> {
|
||||||
|
SecretList::decode(&mut Cursor::new(list.data().unwrap())).map_err(Error::InvSecretList)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for SecretList {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
writeln!(f, "Total number of secrets: {}", self.total_num_secrets)?;
|
||||||
|
if !self.secrets.is_empty() {
|
||||||
|
writeln!(f)?;
|
||||||
|
}
|
||||||
|
for s in &self.secrets {
|
||||||
|
writeln!(f, "{s}")?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ser_u32<S: Serializer>(v: &U32<BigEndian>, ser: S) -> Result<S::Ok, S::Error> {
|
||||||
|
ser.serialize_u32(v.get())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ser_u16<S: Serializer>(v: &U16<BigEndian>, ser: S) -> Result<S::Ok, S::Error> {
|
||||||
|
ser.serialize_u16(v.get())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Secret types that can appear in a [`SecretList`]
|
||||||
|
#[non_exhaustive]
|
||||||
|
#[derive(PartialEq, Eq)]
|
||||||
|
pub enum ListableSecretType {
|
||||||
|
/// Association Secret
|
||||||
|
Association,
|
||||||
|
/// Invalid secret type, that should never appear in a list
|
||||||
|
///
|
||||||
|
/// 0 is reserved
|
||||||
|
/// 1 is Null secret, with no id and not listable
|
||||||
|
Invalid(u16),
|
||||||
|
/// Unknown secret type
|
||||||
|
Unknown(u16),
|
||||||
|
}
|
||||||
|
impl ListableSecretType {
|
||||||
|
const RESERVED_0: u16 = 0x0000;
|
||||||
|
const NULL: u16 = 0x0001;
|
||||||
|
const ASSOCIATION: u16 = 0x0002;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for ListableSecretType {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
Self::Association => write!(f, "Association"),
|
||||||
|
Self::Invalid(n) => write!(f, "Invalid({n})"),
|
||||||
|
Self::Unknown(n) => write!(f, "Unknown({n})"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<U16<BigEndian>> for ListableSecretType {
|
||||||
|
fn from(value: U16<BigEndian>) -> Self {
|
||||||
|
match value.get() {
|
||||||
|
Self::RESERVED_0 => Self::Invalid(Self::RESERVED_0),
|
||||||
|
Self::NULL => Self::Invalid(Self::NULL),
|
||||||
|
Self::ASSOCIATION => ListableSecretType::Association,
|
||||||
|
n => Self::Unknown(n),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<ListableSecretType> for U16<BigEndian> {
|
||||||
|
fn from(value: ListableSecretType) -> Self {
|
||||||
|
match value {
|
||||||
|
ListableSecretType::Association => ListableSecretType::ASSOCIATION,
|
||||||
|
ListableSecretType::Invalid(n) | ListableSecretType::Unknown(n) => n,
|
||||||
|
}
|
||||||
|
.into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[doc(hidden)]
|
||||||
|
pub const SECRET_ID_SIZE: usize = 32;
|
||||||
|
|
||||||
|
#[doc(hidden)]
|
||||||
|
pub fn ser_gsid<S>(id: &[u8; SECRET_ID_SIZE], ser: S) -> Result<S::Ok, S::Error>
|
||||||
|
where
|
||||||
|
S: serde::Serializer,
|
||||||
|
{
|
||||||
|
let mut s = String::with_capacity(32 * 2 + 2);
|
||||||
|
s.push_str("0x");
|
||||||
|
let s = id.iter().fold(s, |acc, e| acc + &format!("{e:02x}"));
|
||||||
|
ser.serialize_str(&s)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A secret in a [`SecretList`]
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Debug, PartialEq, Eq, AsBytes, FromZeroes, FromBytes, Serialize)]
|
||||||
|
pub struct SecretEntry {
|
||||||
|
#[serde(serialize_with = "ser_u16")]
|
||||||
|
index: U16<BigEndian>,
|
||||||
|
#[serde(serialize_with = "ser_u16")]
|
||||||
|
stype: U16<BigEndian>,
|
||||||
|
#[serde(serialize_with = "ser_u32")]
|
||||||
|
len: U32<BigEndian>,
|
||||||
|
#[serde(skip)]
|
||||||
|
res_8: u64,
|
||||||
|
#[serde(serialize_with = "ser_gsid")]
|
||||||
|
id: [u8; SECRET_ID_SIZE],
|
||||||
|
}
|
||||||
|
assert_size!(SecretEntry, SecretEntry::STRUCT_SIZE);
|
||||||
|
|
||||||
|
impl SecretEntry {
|
||||||
|
const STRUCT_SIZE: usize = 0x30;
|
||||||
|
|
||||||
|
/// Create a new entry for a [`SecretList`].
|
||||||
|
///
|
||||||
|
/// The content of this entry will very liekly not represent the status of the guest in the
|
||||||
|
/// Ultravisor. Use of [`SecretList::decode`] in any non-test environments is encuraged.
|
||||||
|
pub fn new(index: u16, stype: ListableSecretType, id: [u8; 32], secret_len: u32) -> Self {
|
||||||
|
Self {
|
||||||
|
index: index.into(),
|
||||||
|
stype: stype.into(),
|
||||||
|
len: secret_len.into(),
|
||||||
|
res_8: 0,
|
||||||
|
id,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the index of this [`SecretEntry`].
|
||||||
|
pub fn index(&self) -> u16 {
|
||||||
|
self.index.get()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the secret type of this [`SecretEntry`].
|
||||||
|
pub fn stype(&self) -> ListableSecretType {
|
||||||
|
self.stype.into()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a reference to the id of this [`SecretEntry`].
|
||||||
|
pub fn id(&self) -> &[u8] {
|
||||||
|
&self.id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for SecretEntry {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
let stype: ListableSecretType = self.stype.into();
|
||||||
|
writeln!(f, "{} {}:", self.index, stype)?;
|
||||||
|
write!(f, " ")?;
|
||||||
|
for b in self.id {
|
||||||
|
write!(f, "{b:02x}")?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
use std::io::{BufReader, BufWriter, Cursor};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn dump_secret_entry() {
|
||||||
|
const EXP: &[u8] = &[
|
||||||
|
0x00, 0x01, 0x00, 0x02, //idx + type
|
||||||
|
0x00, 0x00, 0x00, 0x20, //len
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // reserved
|
||||||
|
// id
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00,
|
||||||
|
];
|
||||||
|
let s = SecretEntry {
|
||||||
|
index: 1.into(),
|
||||||
|
stype: 2.into(),
|
||||||
|
len: 32.into(),
|
||||||
|
res_8: 0,
|
||||||
|
id: [0; 32],
|
||||||
|
};
|
||||||
|
|
||||||
|
assert_eq!(s.as_bytes(), EXP);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn secret_list_dec() {
|
||||||
|
let buf = [
|
||||||
|
0x00u8, 0x01, // num secr stored
|
||||||
|
0x01, 0x12, // total num secrets
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //reserved
|
||||||
|
// secret
|
||||||
|
0x00, 0x01, 0x00, 0x02, //idx + type
|
||||||
|
0x00, 0x00, 0x00, 0x20, //len
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // reserved
|
||||||
|
// id
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00,
|
||||||
|
];
|
||||||
|
|
||||||
|
let exp = SecretList {
|
||||||
|
total_num_secrets: 0x112,
|
||||||
|
secrets: vec![SecretEntry {
|
||||||
|
index: 1.into(),
|
||||||
|
stype: 2.into(),
|
||||||
|
len: 32.into(),
|
||||||
|
res_8: 0,
|
||||||
|
id: [0; 32],
|
||||||
|
}],
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut br = BufReader::new(Cursor::new(buf));
|
||||||
|
let sl = SecretList::decode(&mut br).unwrap();
|
||||||
|
assert_eq!(sl, exp);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn secret_list_enc() {
|
||||||
|
const EXP: &[u8] = &[
|
||||||
|
0x00, 0x01, // num secr stored
|
||||||
|
0x01, 0x12, // total num secrets
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //reserved
|
||||||
|
// secret
|
||||||
|
0x00, 0x01, 0x00, 0x02, //idx + type
|
||||||
|
0x00, 0x00, 0x00, 0x20, //len
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // reserved
|
||||||
|
// id
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00,
|
||||||
|
];
|
||||||
|
|
||||||
|
let sl = SecretList {
|
||||||
|
total_num_secrets: 0x112,
|
||||||
|
secrets: vec![SecretEntry {
|
||||||
|
index: 1.into(),
|
||||||
|
stype: 2.into(),
|
||||||
|
len: 32.into(),
|
||||||
|
res_8: 0,
|
||||||
|
id: [0; 32],
|
||||||
|
}],
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut buf = [0u8; 0x40];
|
||||||
|
{
|
||||||
|
let mut bw = BufWriter::new(&mut buf[..]);
|
||||||
|
sl.encode(&mut bw).unwrap();
|
||||||
|
}
|
||||||
|
println!("list: {sl:?}");
|
||||||
|
assert_eq!(buf, EXP);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,6 +5,7 @@
|
|||||||
#![cfg(test)]
|
#![cfg(test)]
|
||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
|
ffi::{c_int, c_ulong},
|
||||||
os::unix::prelude::FromRawFd,
|
os::unix::prelude::FromRawFd,
|
||||||
sync::{Mutex, MutexGuard},
|
sync::{Mutex, MutexGuard},
|
||||||
};
|
};
|
||||||
@@ -28,18 +29,18 @@ fn get_lock<T>(m: &'static Mutex<T>) -> MutexGuard<'static, T> {
|
|||||||
|
|
||||||
struct IoctlCtx {
|
struct IoctlCtx {
|
||||||
modify: Box<dyn FnMut(&mut ffi::uvio_ioctl_cb) -> i32 + Send + Sync>,
|
modify: Box<dyn FnMut(&mut ffi::uvio_ioctl_cb) -> i32 + Send + Sync>,
|
||||||
exp_cmd: ::libc::c_ulong,
|
exp_cmd: c_ulong,
|
||||||
called: bool,
|
called: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IoctlCtx {
|
impl IoctlCtx {
|
||||||
pub fn exp_cmd(&mut self, cmd: ::libc::c_ulong) -> &mut Self {
|
pub fn exp_cmd(&mut self, cmd: c_ulong) -> &mut Self {
|
||||||
self.exp_cmd = cmd;
|
self.exp_cmd = cmd;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
pub fn set_mdfy<F>(&mut self, mdfy: F) -> &mut Self
|
pub fn set_mdfy<F>(&mut self, mdfy: F) -> &mut Self
|
||||||
where
|
where
|
||||||
F: FnMut(&mut ffi::uvio_ioctl_cb) -> ::libc::c_int + 'static + Send + Sync,
|
F: FnMut(&mut ffi::uvio_ioctl_cb) -> c_int + 'static + Send + Sync,
|
||||||
{
|
{
|
||||||
self.modify = Box::new(mdfy);
|
self.modify = Box::new(mdfy);
|
||||||
self
|
self
|
||||||
@@ -62,11 +63,7 @@ impl IoctlCtx {
|
|||||||
pub mod mock_libc {
|
pub mod mock_libc {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
pub unsafe fn ioctl(
|
pub unsafe fn ioctl(fd: c_int, cmd: c_ulong, data: *mut ffi::uvio_ioctl_cb) -> c_int {
|
||||||
fd: ::libc::c_int,
|
|
||||||
cmd: ::libc::c_ulong,
|
|
||||||
data: *mut ffi::uvio_ioctl_cb,
|
|
||||||
) -> ::libc::c_int {
|
|
||||||
let mut ctx = get_lock(&IOCTL_MTX);
|
let mut ctx = get_lock(&IOCTL_MTX);
|
||||||
assert!(!ctx.called, "IOCTL called more than once");
|
assert!(!ctx.called, "IOCTL called more than once");
|
||||||
ctx.called = true;
|
ctx.called = true;
|
||||||
194
rust/pv_core/src/uvsecret.rs
Normal file
194
rust/pv_core/src/uvsecret.rs
Normal file
@@ -0,0 +1,194 @@
|
|||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
//
|
||||||
|
// Copyright IBM Corp. 2023
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
misc::to_u16,
|
||||||
|
request::{MagicValue, RequestMagic},
|
||||||
|
uv::{ListCmd, UvCmd},
|
||||||
|
Error, Result,
|
||||||
|
};
|
||||||
|
use byteorder::{BigEndian, ByteOrder};
|
||||||
|
use std::{
|
||||||
|
fmt::Display,
|
||||||
|
io::{Cursor, Read, Seek, Write},
|
||||||
|
mem::size_of,
|
||||||
|
};
|
||||||
|
use utils::{assert_size, static_assert};
|
||||||
|
use zerocopy::{AsBytes, FromBytes, U16, U32};
|
||||||
|
|
||||||
|
/// The magic value used to identify an add-secret request`]
|
||||||
|
///
|
||||||
|
/// The magic value is ASCII:
|
||||||
|
/// ```rust
|
||||||
|
/// # use pv_core::request::uvsecret::AddSecretMagic;
|
||||||
|
/// # use pv_core::request::MagicValue;
|
||||||
|
/// # fn main() {
|
||||||
|
/// # let magic =
|
||||||
|
/// b"asrcbM"
|
||||||
|
/// # ;
|
||||||
|
/// # assert!(AddSecretMagic::starts_with_magic(magic));
|
||||||
|
/// # }
|
||||||
|
///```
|
||||||
|
///
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, AsBytes)]
|
||||||
|
pub struct AddSecretMagic {
|
||||||
|
magic: [u8; 6], // [0x61, 0x73, 0x72, 0x63, 0x62, 0x4D]
|
||||||
|
kind: U16<BigEndian>,
|
||||||
|
}
|
||||||
|
assert_size!(AddSecretMagic, 8);
|
||||||
|
|
||||||
|
impl MagicValue<6> for AddSecretMagic {
|
||||||
|
// "asrcbM"
|
||||||
|
const MAGIC: [u8; 6] = [0x61, 0x73, 0x72, 0x63, 0x62, 0x4D];
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AddSecretMagic {
|
||||||
|
/// Get the magic value.
|
||||||
|
pub fn get(&self) -> RequestMagic {
|
||||||
|
let mut res = RequestMagic::default();
|
||||||
|
debug_assert!(res.len() == size_of::<AddSecretMagic>());
|
||||||
|
// Panic: does not panic, buf is 8 bytes long
|
||||||
|
self.write_to(&mut res).unwrap();
|
||||||
|
res
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Try to convert from a byte slice.
|
||||||
|
///
|
||||||
|
/// Retuns [`None`] if the byte slice does not contain a valid magic value variant.
|
||||||
|
pub fn try_from_bytes(bytes: &[u8]) -> Result<Self> {
|
||||||
|
if !Self::starts_with_magic(bytes) || bytes.len() < size_of::<AddSecretMagic>() {
|
||||||
|
return Err(Error::NoAsrcb);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Panic: Will not panic, bytes is at least 8 elements long
|
||||||
|
let kind = BigEndian::read_u16(&bytes[6..8]);
|
||||||
|
let kind = UserDataType::try_from(kind)?;
|
||||||
|
Ok(Self::from(kind))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the [`UserDataType`] of this [`AddSecretMagic`].
|
||||||
|
pub fn kind(&self) -> UserDataType {
|
||||||
|
// Panic: Will never panic. The value is cheched during construcion of the object for
|
||||||
|
// beeing one of the enum values.
|
||||||
|
self.kind.get().try_into().unwrap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Types of (non architectured) user data for an add-secret request
|
||||||
|
#[repr(u16)]
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub enum UserDataType {
|
||||||
|
/// Marker that the request does not contain any user data
|
||||||
|
Null = 0x0000,
|
||||||
|
/// Arbitrary user data (max 512 bytes)
|
||||||
|
Unsigned = 0x0001,
|
||||||
|
/// User data message signed with an Ec key, (max 256 byte)
|
||||||
|
SgnEcSECP521R1 = 0x0002,
|
||||||
|
/// User data message signature with a Rsa key of 2048 bit size, (max 256 byte)
|
||||||
|
SgnRsa2048 = 0x0003,
|
||||||
|
/// User data message signature with a Rsa key of 3072 bit size, (max 128 byte)
|
||||||
|
SgnRsa3072 = 0x0004,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl UserDataType {
|
||||||
|
/// Returns the maximum user-data size in bytes.
|
||||||
|
pub fn max(&self) -> usize {
|
||||||
|
match self {
|
||||||
|
UserDataType::Null => 0,
|
||||||
|
UserDataType::Unsigned => 512,
|
||||||
|
UserDataType::SgnEcSECP521R1 => 256,
|
||||||
|
UserDataType::SgnRsa2048 => 256,
|
||||||
|
UserDataType::SgnRsa3072 => 128,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for UserDataType {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"{}",
|
||||||
|
match self {
|
||||||
|
Self::Null => "None",
|
||||||
|
Self::Unsigned => "unsigned",
|
||||||
|
Self::SgnEcSECP521R1 => "ECDSA signed",
|
||||||
|
Self::SgnRsa2048 => "RSA 2048 signed",
|
||||||
|
Self::SgnRsa3072 => "RSA 3072 signed",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<u16> for UserDataType {
|
||||||
|
type Error = Error;
|
||||||
|
|
||||||
|
fn try_from(value: u16) -> std::result::Result<Self, Self::Error> {
|
||||||
|
if value == UserDataType::Null as u16 {
|
||||||
|
Ok(UserDataType::Null)
|
||||||
|
} else if value == UserDataType::Unsigned as u16 {
|
||||||
|
Ok(UserDataType::Unsigned)
|
||||||
|
} else if value == UserDataType::SgnEcSECP521R1 as u16 {
|
||||||
|
Ok(UserDataType::SgnEcSECP521R1)
|
||||||
|
} else if value == UserDataType::SgnRsa2048 as u16 {
|
||||||
|
Ok(UserDataType::SgnRsa2048)
|
||||||
|
} else if value == UserDataType::SgnRsa3072 as u16 {
|
||||||
|
Ok(UserDataType::SgnRsa3072)
|
||||||
|
} else {
|
||||||
|
Err(Error::UnsupportedUserData(value))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<UserDataType> for AddSecretMagic {
|
||||||
|
fn from(kind: UserDataType) -> Self {
|
||||||
|
Self {
|
||||||
|
magic: Self::MAGIC,
|
||||||
|
kind: (kind as u16).into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use crate::{
|
||||||
|
request::{
|
||||||
|
uvsecret::{AddSecretMagic, UserDataType},
|
||||||
|
MagicValue,
|
||||||
|
},
|
||||||
|
Error,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn convert_user_data() {
|
||||||
|
assert!(matches!(
|
||||||
|
UserDataType::try_from(5),
|
||||||
|
Err(Error::UnsupportedUserData(5))
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn magic_get() {
|
||||||
|
let user_data = AddSecretMagic::from(UserDataType::SgnEcSECP521R1);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
user_data.get(),
|
||||||
|
[0x61, 0x73, 0x72, 0x63, 0x62, 0x4D, 0x00, 0x02]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn magic_try_from() {
|
||||||
|
let bin = [0x61, 0x73, 0x72, 0x63, 0x62, 0x4D, 0x00, 0x02];
|
||||||
|
|
||||||
|
let magic = AddSecretMagic::try_from_bytes(&bin).unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
magic,
|
||||||
|
AddSecretMagic {
|
||||||
|
magic: AddSecretMagic::MAGIC,
|
||||||
|
kind: (UserDataType::SgnEcSECP521R1 as u16).into()
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
19
rust/pvapconfig/Cargo.toml
Normal file
19
rust/pvapconfig/Cargo.toml
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
[package]
|
||||||
|
name = "pvapconfig"
|
||||||
|
description = "A tool to configure the AP resources inside a SE guest based on UV secrets and an AP config file."
|
||||||
|
authors = ["Harald Freudenberger <freude@linux.ibm.com>"]
|
||||||
|
version = "0.9.0"
|
||||||
|
edition.workspace = true
|
||||||
|
license.workspace = true
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
clap = { version ="4.1", features = ["derive", "wrap_help"]}
|
||||||
|
lazy_static = "1.1"
|
||||||
|
openssl = { version = "0.10" }
|
||||||
|
openssl-sys = { version = "0.9" }
|
||||||
|
pv_core = { path = "../pv_core"}
|
||||||
|
rand = "0.8"
|
||||||
|
regex = "1"
|
||||||
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
|
serde_yaml = "0.9"
|
||||||
|
utils = { path = "../utils" }
|
||||||
9
rust/pvapconfig/README.md
Normal file
9
rust/pvapconfig/README.md
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
<!--
|
||||||
|
Copyright 2023 IBM Corp.
|
||||||
|
s390-tools is free software; you can redistribute it and/or modify
|
||||||
|
it under the terms of the MIT license. See LICENSE for details.
|
||||||
|
-->
|
||||||
|
# pvapconfig
|
||||||
|
## Description
|
||||||
|
**pvapconfig** is used to automatically set up the AP configuration
|
||||||
|
within an IBM Secure Execution guest.
|
||||||
174
rust/pvapconfig/man/pvapconfig.1
Normal file
174
rust/pvapconfig/man/pvapconfig.1
Normal file
@@ -0,0 +1,174 @@
|
|||||||
|
.\" pvapconfig.1
|
||||||
|
.\"
|
||||||
|
.\" Copyright 2023 IBM Corp.
|
||||||
|
.\" s390-tools is free software; you can redistribute it and/or modify
|
||||||
|
.\" it under the terms of the MIT license. See LICENSE for details.
|
||||||
|
.\"
|
||||||
|
.\" use
|
||||||
|
.\" groff -man -Tutf8 pvapconfig.1
|
||||||
|
.\" or
|
||||||
|
.\" nroff -man pvapconfig.1
|
||||||
|
.\" to process this source
|
||||||
|
.\"
|
||||||
|
.TH PVAPCONFIG 1 "DEC 2023" "s390-tools"
|
||||||
|
.SH NAME
|
||||||
|
pvapconfig \- automatic configure APQNs within an SE KVM guest.
|
||||||
|
.SH SYNOPSIS
|
||||||
|
.TP 9
|
||||||
|
.B pvapconfig [OPTIONS]
|
||||||
|
.SH DESCRIPTION
|
||||||
|
pvapconfig is a tool for automatically configuring the APQNs within an
|
||||||
|
Secure Execution KVM guest with AP pass-through support. Based on a
|
||||||
|
given AP configuration it tries to find matching APQNs and binds and
|
||||||
|
associates them with the given secret(s).
|
||||||
|
|
||||||
|
Here is a description of pvapconfig's process:
|
||||||
|
.TP 3
|
||||||
|
1. Check AP bus: Support for AP bus needs to be available and the AP
|
||||||
|
bus needs to support APSB. APSB is only available within an KVM SE
|
||||||
|
guest with AP pass-through support.
|
||||||
|
.TP 3
|
||||||
|
2. Check Ultravisor: UV support needs to be available and the UV needs
|
||||||
|
to support the
|
||||||
|
.I list secrets
|
||||||
|
feature.
|
||||||
|
.TP 3
|
||||||
|
3. Read in and validate the AP configuration file. By default if not
|
||||||
|
overwritten by the --config option the AP configuration is read from
|
||||||
|
.I /etc/pvapconfig.yaml
|
||||||
|
and syntactically verified. See section CONFIGFILE for details about
|
||||||
|
the syntax and semantic of the configuration file.
|
||||||
|
.TP 3
|
||||||
|
4. Fetch the list of association secrets from the UV. Actually the
|
||||||
|
index of the secret and the secret id for each entry is collected. The
|
||||||
|
secret value is NOT fetched as it is NOT accessible but only usable
|
||||||
|
within the UV firmware.
|
||||||
|
.TP 3
|
||||||
|
5. Gather all APQNs available within this KVM SE guest. Collect
|
||||||
|
information about each APQN like online states, crypto card serial
|
||||||
|
numbers and master key verification patterns (MKVP).
|
||||||
|
.TP 3
|
||||||
|
6. Go through all AP config entries. For each AP config entry try to
|
||||||
|
find an APQN which is already configured (bound/associated) to
|
||||||
|
satisfy this config entry. If such a match is found, the AP config
|
||||||
|
entry is assumed to be fulfilled and marked as done.
|
||||||
|
.TP 3
|
||||||
|
7. All remaining APQNs which do not already satisfy an AP config entry
|
||||||
|
are now examined for their bind and association state and maybe reset
|
||||||
|
to unbound state.
|
||||||
|
.TP 3
|
||||||
|
8. Go through all AP config entries which are still not
|
||||||
|
fulfilled. For each such AP config entry try to search for an APQN
|
||||||
|
which would match to this entry and then prepare this APQN (bind,
|
||||||
|
maybe associate). If successful, mark the AP config entry as done.
|
||||||
|
.TP 3
|
||||||
|
9. Evaluation of the applied AP config entries. Applied means the AP
|
||||||
|
config entry has been fulfilled either in step 6 or in step 8. With
|
||||||
|
the strict option given ALL AP config entries need to apply otherwise
|
||||||
|
an error message is printed and pvapconfig returns with exit failure.
|
||||||
|
If the strict option is not given, it is enough to satisfy at least one
|
||||||
|
AP config entry from the configuration and pvapconfig will return
|
||||||
|
successfully.
|
||||||
|
.SH OPTIONS
|
||||||
|
.TP 8
|
||||||
|
.B -c, --config <configfile>
|
||||||
|
Use <configfile> as the AP config file for pvapconfig. If pvapconfig
|
||||||
|
is run without this option the default configuration file
|
||||||
|
/etc/pvapconfig.yaml is used.
|
||||||
|
.TP 8
|
||||||
|
.B -h, --help
|
||||||
|
Print pvapconfig usage information and exit.
|
||||||
|
.TP 8
|
||||||
|
.B -n, --dry-run
|
||||||
|
Do not bind, unbind or associate APQNs but only process the
|
||||||
|
configuration and the available APQNs and secrets and simulate the
|
||||||
|
bind, unbind or associate action on the chosen APQN. Use it together
|
||||||
|
with the verbose option to see which actions pvapconfig would do if
|
||||||
|
unleashed.
|
||||||
|
.TP 8
|
||||||
|
.B -s, --strict
|
||||||
|
All AP config entries need to be satisfied to have pvapconfig
|
||||||
|
terminate with success. Without this option one applied AP config
|
||||||
|
entry is enough to meet the expectations.
|
||||||
|
.TP 8
|
||||||
|
.B -v, --verbose
|
||||||
|
Print out informational messages about what pvapconfig is actually
|
||||||
|
doing.
|
||||||
|
.TP 8
|
||||||
|
.B -V, --version
|
||||||
|
Print version information and exit.
|
||||||
|
.SH CONFIGFILE
|
||||||
|
The pvapconfig yaml configuration file consists of a list of AP config
|
||||||
|
entries. Each entry may hold this information:
|
||||||
|
.TP 2
|
||||||
|
- mode: AP queue mode information, required, either "EP11" or "Accel".
|
||||||
|
.TP 2
|
||||||
|
- mkvp: AP queue Master Key Verification Pattern (MKVP), required for
|
||||||
|
EP11, hex string optional prepented with 0x. The MKVP hex string value
|
||||||
|
may hold either 16 bytes (32 hex characters) or 32 bytes (64 hex
|
||||||
|
characters) but only the leftmost 16 bytes hold MKVP information and
|
||||||
|
thus the rest is ignored.
|
||||||
|
.TP 2
|
||||||
|
- serialnr: Crypto Card Serial Number, string, optional for EP11,
|
||||||
|
ignored for Accel. As this is a real ASCII string uppercase and
|
||||||
|
lowercase character(s) count different.
|
||||||
|
.TP 2
|
||||||
|
- mingen: Card Minimal Generation, string "CEX4", "CEX5", "CEX6",
|
||||||
|
"CEX7" or "CEX8" for Accelerator, string "CEC8" for EP11, optional. If
|
||||||
|
given specifies the minimal accepted Crypto card generation.
|
||||||
|
.TP 2
|
||||||
|
- secretid: Secret id, hex string with optional 0x prepented, required
|
||||||
|
for EP11, ignored for Accel. Details see the following text.
|
||||||
|
.TP 2
|
||||||
|
- name: ASCII string, optional, but see details below.
|
||||||
|
.TP 2
|
||||||
|
- description: Description of this AP config entry, string, ignored,
|
||||||
|
just for convenience for the reader or editor of the configuration.
|
||||||
|
.PP
|
||||||
|
The secret id uniquely identifies an association secret. However, it
|
||||||
|
is a clumsy hex string of 64 characters which represent the readable
|
||||||
|
sha256 value over the secret's name. So pvapconfig can use the name
|
||||||
|
instead and calculate the secret id from the name. So the rule is:
|
||||||
|
.TP 2
|
||||||
|
- If name and secretid is given, the secretid needs to match to the
|
||||||
|
sha256 hash over the given name for this AP config entry.
|
||||||
|
.TP 2
|
||||||
|
- If only name is given then the secretid is calculated with a sha256
|
||||||
|
hash over the given name.
|
||||||
|
.TP 2
|
||||||
|
- If only the secretid is given, there is nothing more to do but
|
||||||
|
verify that the value is a hex string with 64 characters.
|
||||||
|
.SH LOCKING
|
||||||
|
Pvapconfig needs to have a consistent view of the AP resources
|
||||||
|
during lifetime. There must not run multiple instances of pvapconfig
|
||||||
|
or any manipulations of the AP resources in parallel. To prevent the
|
||||||
|
execution of multiple pvapconfig instances the lock file
|
||||||
|
/run/lock/pvapconfig.lock is established. A second instance of
|
||||||
|
pvapconfig will detect this lock file and terminated with an error
|
||||||
|
message. If for any reason this file still exists as a leftover from a
|
||||||
|
previous pvapconfig crash for example, it needs to get removed by
|
||||||
|
hand. The lock file contains the process id of the pvapconfig process
|
||||||
|
which created this file.
|
||||||
|
.SH RETURN VALUE
|
||||||
|
.TP 8
|
||||||
|
.B 0 - Successful termination.
|
||||||
|
At least one AP config entry has been applied or at least one APQN has
|
||||||
|
been found in a state matching to one AP config entry. If strict
|
||||||
|
option is given, ALL AP config entries have been applied. An AP config
|
||||||
|
entry is applied either by configuring the APQN accordingly or an APQN
|
||||||
|
has been found which already fulfills the constrains.
|
||||||
|
.RE
|
||||||
|
.TP 8
|
||||||
|
.B 1 - Failure.
|
||||||
|
Either some kind of failure happened during processing the
|
||||||
|
configuration or the configuration could not get applied
|
||||||
|
successful. In all cases pvapconfig prints out a message to standard
|
||||||
|
error with details about the failure. Also pvapconfig does NOT reset
|
||||||
|
the APQNs to the state found at the startup when failing to apply the
|
||||||
|
configuration.
|
||||||
|
.SH NOTES
|
||||||
|
For more information and details see the IBM documentation about
|
||||||
|
Confidential Computing "Introducing IBM Secure Execution for Linux"
|
||||||
|
available at https://www.ibm.com/docs/.
|
||||||
|
.SH SEE ALSO
|
||||||
|
\fBpvsecret\fR(1), \fBlszcrypt\fR(8), \fBchzcrypt\fR(8)
|
||||||
817
rust/pvapconfig/src/ap.rs
Normal file
817
rust/pvapconfig/src/ap.rs
Normal file
@@ -0,0 +1,817 @@
|
|||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
//
|
||||||
|
// Copyright IBM Corp. 2023
|
||||||
|
//
|
||||||
|
//! AP support functions for pvapconfig
|
||||||
|
//
|
||||||
|
|
||||||
|
use crate::helper::*;
|
||||||
|
use regex::Regex;
|
||||||
|
use std::fmt;
|
||||||
|
use std::path::Path;
|
||||||
|
use std::slice::Iter;
|
||||||
|
use std::thread;
|
||||||
|
use std::time;
|
||||||
|
|
||||||
|
const PATH_SYS_BUS_AP: &str = "/sys/bus/ap";
|
||||||
|
const PATH_SYS_BUS_AP_FEATURES: &str = "/sys/bus/ap/features";
|
||||||
|
const PATH_SYS_BUS_AP_BINDINGS: &str = "/sys/bus/ap/bindings";
|
||||||
|
const PATH_SYS_DEVICES_AP: &str = "/sys/devices/ap";
|
||||||
|
|
||||||
|
const RE_CARD_DIR: &str = r"^card([[:xdigit:]]{2})$";
|
||||||
|
const RE_QUEUE_DIR: &str = r"^([[:xdigit:]]{2})\.([[:xdigit:]]{4})$";
|
||||||
|
const RE_CARD_TYPE: &str = r"^CEX([3-8])([ACP])$";
|
||||||
|
const RE_EP11_MKVP: &str = r"WK\s+CUR:\s+(\S+)\s+(\S+)";
|
||||||
|
const RE_CCA_AES_MKVP: &str = r"AES\s+CUR:\s+(\S+)\s+(\S+)";
|
||||||
|
const RE_CCA_APKA_MKVP: &str = r"APKA\s+CUR:\s+(\S+)\s+(\S+)";
|
||||||
|
|
||||||
|
const SYS_BUS_AP_BINDINGS_POLL_MS: u64 = 500;
|
||||||
|
|
||||||
|
const SYS_BUS_AP_BIND_POLL_MS: u64 = 500;
|
||||||
|
const SYS_BUS_AP_BIND_TIMEOUT_MS: u64 = 10000;
|
||||||
|
|
||||||
|
const SYS_BUS_AP_ASSOC_POLL_MS: u64 = 500;
|
||||||
|
const SYS_BUS_AP_ASSOC_TIMEOUT_MS: u64 = 10000;
|
||||||
|
|
||||||
|
/// Check if AP bus support is available.
|
||||||
|
/// Returns Result with Ok(()) or Err(failurestring).
|
||||||
|
pub fn check_ap_bus_support() -> Result<(), String> {
|
||||||
|
if !Path::new(PATH_SYS_BUS_AP).is_dir() {
|
||||||
|
return Err(format!(
|
||||||
|
"AP bus support missing (path {PATH_SYS_BUS_AP} is invalid)."
|
||||||
|
));
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check if AP bus supports APSB.
|
||||||
|
///
|
||||||
|
/// When APSB support is available returns Result
|
||||||
|
/// with Ok(()) or otherwise Err(failurestring).
|
||||||
|
pub fn ap_bus_has_apsb_support() -> Result<(), String> {
|
||||||
|
if !Path::new(PATH_SYS_BUS_AP_FEATURES).is_file() {
|
||||||
|
return Err(format!(
|
||||||
|
"AP bus features support missing (file {PATH_SYS_BUS_AP_FEATURES} does not exist)."
|
||||||
|
));
|
||||||
|
}
|
||||||
|
let features = sysfs_read_string(PATH_SYS_BUS_AP_FEATURES).map_err(|err| {
|
||||||
|
format!("Failure reading AP bus features from {PATH_SYS_BUS_AP_FEATURES} ({err:?}).")
|
||||||
|
})?;
|
||||||
|
match features.find("APSB") {
|
||||||
|
Some(_) => Ok(()),
|
||||||
|
None => Err("Missing AP bus feature APSB (SE AP pass-through not enabled ?).".to_string()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Wait for AP bus set up all it's devices.
|
||||||
|
///
|
||||||
|
/// This function loops until the AP bus reports that
|
||||||
|
/// - all AP queue devices have been constructed
|
||||||
|
/// - and all AP device have been bound to a device driver.
|
||||||
|
/// This may take some time and even loop forever if there
|
||||||
|
/// is something wrong with the kernel modules setup.
|
||||||
|
/// Returns true when AP bus bindings are complete,
|
||||||
|
/// otherwise false and a message is printed.
|
||||||
|
/// When AP bus binding complete is not immediately reached
|
||||||
|
/// and this function needs to loop, about every 5 seconds
|
||||||
|
/// a message is printed "Waiting for ...".
|
||||||
|
pub fn wait_for_ap_bus_bindings_complete() -> bool {
|
||||||
|
let mut counter = 0;
|
||||||
|
loop {
|
||||||
|
match sysfs_read_string(PATH_SYS_BUS_AP_BINDINGS) {
|
||||||
|
Ok(s) => {
|
||||||
|
if s.contains("complete") {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
eprintln!(
|
||||||
|
"Failure reading AP bus bindings from {} ({:?}).",
|
||||||
|
PATH_SYS_BUS_AP_BINDINGS, err
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
thread::sleep(time::Duration::from_millis(SYS_BUS_AP_BINDINGS_POLL_MS));
|
||||||
|
counter += 1;
|
||||||
|
if counter % 10 == 0 {
|
||||||
|
println!("Waiting for AP bus bindings complete.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
pub enum ApqnMode {
|
||||||
|
Accel,
|
||||||
|
Ep11,
|
||||||
|
Cca,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct ApqnInfoAccel {
|
||||||
|
// empty
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct ApqnInfoEp11 {
|
||||||
|
pub serialnr: String,
|
||||||
|
pub mkvp: String, // may be an empty string if no WK set
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct ApqnInfoCca {
|
||||||
|
pub serialnr: String,
|
||||||
|
pub mkvp_aes: String, // may be an empty string if no MK set
|
||||||
|
pub mkvp_apka: String, // may be an empty string if no MK set
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum ApqnInfo {
|
||||||
|
Accel(ApqnInfoAccel),
|
||||||
|
Ep11(ApqnInfoEp11),
|
||||||
|
Cca(ApqnInfoCca),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ApqnInfo {
|
||||||
|
fn accel_info(_carddir: &str, _queuedir: &str) -> Result<ApqnInfo, String> {
|
||||||
|
Ok(ApqnInfo::Accel(ApqnInfoAccel {}))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cca_info(carddir: &str, queuedir: &str) -> Result<ApqnInfo, String> {
|
||||||
|
let serialnr = match sysfs_read_string(&format!("{carddir}/serialnr")) {
|
||||||
|
Ok(r) => r,
|
||||||
|
Err(err) => {
|
||||||
|
return Err(format!(
|
||||||
|
"Failure reading serialnr from {carddir}/serialnr: {:?}.",
|
||||||
|
err
|
||||||
|
))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let mkvps = match sysfs_read_string(&format!("{carddir}/{queuedir}/mkvps")) {
|
||||||
|
Ok(r) => r,
|
||||||
|
Err(err) => {
|
||||||
|
return Err(format!(
|
||||||
|
"Failure reading mkvps from {carddir}/{queuedir}/mkvps: {:?}.",
|
||||||
|
err
|
||||||
|
))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let mut aes_mkvp = String::new();
|
||||||
|
let re_cca_aes_mkvp = Regex::new(RE_CCA_AES_MKVP).unwrap();
|
||||||
|
if !re_cca_aes_mkvp.is_match(&mkvps) {
|
||||||
|
return Err(format!(
|
||||||
|
"APQN {} failure parsing mkvps string '{}'.",
|
||||||
|
queuedir, mkvps
|
||||||
|
));
|
||||||
|
} else {
|
||||||
|
let caps = re_cca_aes_mkvp.captures(&mkvps).unwrap();
|
||||||
|
let valid = caps.get(1).unwrap().as_str().to_lowercase();
|
||||||
|
if valid != "valid" {
|
||||||
|
eprintln!(
|
||||||
|
"Warning: APQN {} has no valid AES master key set.",
|
||||||
|
queuedir
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
aes_mkvp = caps.get(2).unwrap().as_str().to_lowercase();
|
||||||
|
if aes_mkvp.starts_with("0x") {
|
||||||
|
aes_mkvp = String::from(&aes_mkvp[2..]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let mut apka_mkvp = String::new();
|
||||||
|
let re_cca_apka_mkvp = Regex::new(RE_CCA_APKA_MKVP).unwrap();
|
||||||
|
if !re_cca_apka_mkvp.is_match(&mkvps) {
|
||||||
|
return Err(format!(
|
||||||
|
"APQN {} failure parsing mkvps string '{}'.",
|
||||||
|
queuedir, mkvps
|
||||||
|
));
|
||||||
|
} else {
|
||||||
|
let caps = re_cca_apka_mkvp.captures(&mkvps).unwrap();
|
||||||
|
let valid = caps.get(1).unwrap().as_str().to_lowercase();
|
||||||
|
if valid != "valid" {
|
||||||
|
eprintln!(
|
||||||
|
"Warning: APQN {} has no valid APKA master key set.",
|
||||||
|
queuedir
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
apka_mkvp = caps.get(2).unwrap().as_str().to_lowercase();
|
||||||
|
if apka_mkvp.starts_with("0x") {
|
||||||
|
apka_mkvp = String::from(&apka_mkvp[2..]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(ApqnInfo::Cca(ApqnInfoCca {
|
||||||
|
serialnr,
|
||||||
|
mkvp_aes: aes_mkvp,
|
||||||
|
mkvp_apka: apka_mkvp,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ep11_info(carddir: &str, queuedir: &str) -> Result<ApqnInfo, String> {
|
||||||
|
let serialnr = match sysfs_read_string(&format!("{carddir}/serialnr")) {
|
||||||
|
Ok(r) => r,
|
||||||
|
Err(err) => {
|
||||||
|
return Err(format!(
|
||||||
|
"Failure reading serialnr from {carddir}/serialnr: {:?}.",
|
||||||
|
err
|
||||||
|
))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let mkvps = match sysfs_read_string(&format!("{carddir}/{queuedir}/mkvps")) {
|
||||||
|
Ok(r) => r,
|
||||||
|
Err(err) => {
|
||||||
|
return Err(format!(
|
||||||
|
"Failure reading mkvps from {carddir}/{queuedir}/mkvps: {:?}.",
|
||||||
|
err
|
||||||
|
))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let mut mkvp = String::new();
|
||||||
|
let re_ep11_mkvp = Regex::new(RE_EP11_MKVP).unwrap();
|
||||||
|
if !re_ep11_mkvp.is_match(&mkvps) {
|
||||||
|
return Err(format!(
|
||||||
|
"APQN {} failure parsing mkvps string '{}'.",
|
||||||
|
queuedir, mkvps
|
||||||
|
));
|
||||||
|
} else {
|
||||||
|
let caps = re_ep11_mkvp.captures(&mkvps).unwrap();
|
||||||
|
let valid = caps.get(1).unwrap().as_str().to_lowercase();
|
||||||
|
if valid != "valid" {
|
||||||
|
eprintln!("Warning: APQN {} has no valid wrapping key set.", queuedir);
|
||||||
|
} else {
|
||||||
|
mkvp = caps.get(2).unwrap().as_str().to_lowercase();
|
||||||
|
if mkvp.starts_with("0x") {
|
||||||
|
mkvp = String::from(&mkvp[2..]);
|
||||||
|
}
|
||||||
|
if mkvp.len() > 32 {
|
||||||
|
mkvp = String::from(&mkvp[..32])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(ApqnInfo::Ep11(ApqnInfoEp11 { serialnr, mkvp }))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn info(mode: &ApqnMode, carddir: &str, queuedir: &str) -> Result<ApqnInfo, String> {
|
||||||
|
match mode {
|
||||||
|
ApqnMode::Accel => ApqnInfo::accel_info(carddir, queuedir),
|
||||||
|
ApqnMode::Cca => ApqnInfo::cca_info(carddir, queuedir),
|
||||||
|
ApqnMode::Ep11 => ApqnInfo::ep11_info(carddir, queuedir),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct Apqn {
|
||||||
|
pub name: String,
|
||||||
|
pub card: u32,
|
||||||
|
pub domain: u32,
|
||||||
|
pub gen: u32,
|
||||||
|
pub mode: ApqnMode,
|
||||||
|
pub info: Option<ApqnInfo>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for Apqn {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
write!(f, "({},{})", self.card, self.domain)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Apqn {
|
||||||
|
pub fn bind_state(&self) -> Result<BindState, String> {
|
||||||
|
get_apqn_bind_state(self.card, self.domain)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_bind_state(&self, state: BindState) -> Result<(), String> {
|
||||||
|
set_apqn_bind_state(self.card, self.domain, state)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn associate_state(&self) -> Result<AssocState, String> {
|
||||||
|
get_apqn_associate_state(self.card, self.domain)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_associate_state(&self, state: AssocState) -> Result<(), String> {
|
||||||
|
set_apqn_associate_state(self.card, self.domain, state)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Wrapper object around Vector of Apqns
|
||||||
|
pub struct ApqnList(Vec<Apqn>);
|
||||||
|
|
||||||
|
impl ApqnList {
|
||||||
|
#[cfg(test)] // only used in test code
|
||||||
|
pub fn from_apqn_vec(apqns: Vec<Apqn>) -> ApqnList {
|
||||||
|
ApqnList(apqns)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)] // only used in test code
|
||||||
|
pub fn to_apqn_vec(&self) -> Vec<Apqn> {
|
||||||
|
self.0.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn iter(&self) -> Iter<'_, Apqn> {
|
||||||
|
self.0.iter()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn len(&self) -> usize {
|
||||||
|
self.0.len()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_empty(&self) -> bool {
|
||||||
|
self.0.is_empty()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Scan AP bus devices in sysfs and construct the Apqnlist.
|
||||||
|
///
|
||||||
|
/// The list is a vector of struct Apqn for each APQN found in sysfs
|
||||||
|
/// which is online and the card type matches to the regular expression
|
||||||
|
/// RE_CARD_TYPE.
|
||||||
|
/// On success a vector of struct Apqn is returned. This list may be
|
||||||
|
/// empty if there are no APQNs available or do not match to the conditions.
|
||||||
|
/// On failure None is returned.
|
||||||
|
/// Fatal errors which should never happened like unable to compile a
|
||||||
|
/// static regular expression will result in calling panic.
|
||||||
|
/// # Panics
|
||||||
|
/// Panics if the compilation of a static regular expression fails.
|
||||||
|
pub fn gather_apqns() -> Option<ApqnList> {
|
||||||
|
let mut apqns: Vec<Apqn> = Vec::new();
|
||||||
|
let re_card_type = Regex::new(RE_CARD_TYPE).unwrap();
|
||||||
|
let re_queue_dir = Regex::new(RE_QUEUE_DIR).unwrap();
|
||||||
|
let card_dirs =
|
||||||
|
match sysfs_get_list_of_subdirs_matching_regex(PATH_SYS_DEVICES_AP, RE_CARD_DIR) {
|
||||||
|
Ok(r) => r,
|
||||||
|
Err(err) => {
|
||||||
|
eprintln!(
|
||||||
|
"Failure reading AP devices {} ({:?}).",
|
||||||
|
PATH_SYS_DEVICES_AP, err
|
||||||
|
);
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
for dir in card_dirs {
|
||||||
|
let path = format!("{PATH_SYS_DEVICES_AP}/{dir}");
|
||||||
|
let card_type = match sysfs_read_string(&format!("{path}/type")) {
|
||||||
|
Ok(r) => r,
|
||||||
|
Err(err) => {
|
||||||
|
eprintln!("Failure reading card type from {} ({:?}).", path, err);
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if !re_card_type.is_match(&card_type) {
|
||||||
|
eprintln!("Failure parsing card type string '{}'.", card_type);
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let caps = re_card_type.captures(&card_type).unwrap();
|
||||||
|
let gen = caps.get(1).unwrap().as_str().parse::<u32>().unwrap();
|
||||||
|
let mode = match caps.get(2).unwrap().as_str().parse::<char>().unwrap() {
|
||||||
|
'A' => ApqnMode::Accel,
|
||||||
|
'C' => ApqnMode::Cca,
|
||||||
|
'P' => ApqnMode::Ep11,
|
||||||
|
_ => panic!("Code inconsistence between regex RE_CARD_TYPE and evaluation code."),
|
||||||
|
};
|
||||||
|
if pv_core::misc::pv_guest_bit_set() {
|
||||||
|
// the UV blocks requests to CCA cards within SE guest with
|
||||||
|
// AP pass-through support. However, filter out CCA cards as these
|
||||||
|
// cards cause hangs during information gathering.
|
||||||
|
if mode == ApqnMode::Cca {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let queue_dirs = match sysfs_get_list_of_subdirs_matching_regex(&path, RE_QUEUE_DIR) {
|
||||||
|
Ok(r) => r,
|
||||||
|
Err(err) => {
|
||||||
|
eprintln!(
|
||||||
|
"Failure reading AP queue directories in {} ({:?}).",
|
||||||
|
path, err
|
||||||
|
);
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
for queue_dir in queue_dirs {
|
||||||
|
let _online = match sysfs_read_i32(&format!("{path}/{queue_dir}/online")) {
|
||||||
|
Ok(1) => true,
|
||||||
|
_ => continue,
|
||||||
|
};
|
||||||
|
let caps = re_queue_dir.captures(&queue_dir).unwrap();
|
||||||
|
let cardstr = caps.get(1).unwrap().as_str();
|
||||||
|
let card = u32::from_str_radix(cardstr, 16).unwrap();
|
||||||
|
let domstr = caps.get(2).unwrap().as_str();
|
||||||
|
let dom = u32::from_str_radix(domstr, 16).unwrap();
|
||||||
|
// For the mpvk and serialnr to fetch from the APQN within a SE
|
||||||
|
// guest the APQN needs to be bound to the guest. So if the APQN
|
||||||
|
// is not bound, temporarily bind it here until the info has
|
||||||
|
// been retrieved.
|
||||||
|
let mut tempbound = false;
|
||||||
|
if pv_core::misc::pv_guest_bit_set() {
|
||||||
|
let cbs = match get_apqn_bind_state(card, dom) {
|
||||||
|
Ok(bs) => bs,
|
||||||
|
Err(err) => {
|
||||||
|
eprintln!(
|
||||||
|
"Error: Failure reading APQN ({},{}) bind state: {}",
|
||||||
|
card, dom, err
|
||||||
|
);
|
||||||
|
BindState::NotSupported
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if cbs == BindState::Unbound {
|
||||||
|
let r = set_apqn_bind_state(card, dom, BindState::Bound);
|
||||||
|
if r.is_err() {
|
||||||
|
eprintln!(
|
||||||
|
"Warning: Failure to temp. bind APQN ({},{}): {}",
|
||||||
|
card,
|
||||||
|
dom,
|
||||||
|
r.unwrap_err()
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
tempbound = true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
let info = match ApqnInfo::info(&mode, &path, &queue_dir) {
|
||||||
|
Err(err) => {
|
||||||
|
// print the error but continue with info set to None
|
||||||
|
eprintln!(
|
||||||
|
"Warning: Failure to gather info for APQN ({},{}): {}",
|
||||||
|
card, dom, err
|
||||||
|
);
|
||||||
|
None
|
||||||
|
}
|
||||||
|
Ok(i) => Some(i),
|
||||||
|
};
|
||||||
|
if tempbound {
|
||||||
|
let r = set_apqn_bind_state(card, dom, BindState::Unbound);
|
||||||
|
if r.is_err() {
|
||||||
|
eprintln!(
|
||||||
|
"Warning: Failure to unbind temp. bound APQN ({},{}): {}",
|
||||||
|
card,
|
||||||
|
dom,
|
||||||
|
r.unwrap_err()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
apqns.push(Apqn {
|
||||||
|
name: queue_dir.clone(),
|
||||||
|
card,
|
||||||
|
domain: dom,
|
||||||
|
gen,
|
||||||
|
mode: mode.clone(),
|
||||||
|
info,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some(ApqnList(apqns))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sort this Apqnlist by card generation:
|
||||||
|
/// newest generation first, older generations last.
|
||||||
|
pub fn sort_by_gen(&mut self) {
|
||||||
|
self.0.sort_unstable_by(|a, b| b.gen.cmp(&a.gen));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check MK restriction
|
||||||
|
///
|
||||||
|
/// Within one card there must not exist 2 APQNs with same
|
||||||
|
/// MK setup. This rule only applies to EP11 cards.
|
||||||
|
/// Returns true if this check passed,
|
||||||
|
/// otherwise false and a message is printed.
|
||||||
|
pub fn check_mk_restriction(&self) -> bool {
|
||||||
|
for a1 in self.0.iter() {
|
||||||
|
for a2 in self.0.iter() {
|
||||||
|
if a1.card == a2.card
|
||||||
|
&& a1.domain < a2.domain
|
||||||
|
&& a1.mode == ApqnMode::Ep11
|
||||||
|
&& a1.info.is_some()
|
||||||
|
&& a2.info.is_some()
|
||||||
|
{
|
||||||
|
let i1 = match a1.info.as_ref().unwrap() {
|
||||||
|
ApqnInfo::Ep11(i) => i,
|
||||||
|
_ => continue,
|
||||||
|
};
|
||||||
|
let i2 = match a2.info.as_ref().unwrap() {
|
||||||
|
ApqnInfo::Ep11(i) => i,
|
||||||
|
_ => continue,
|
||||||
|
};
|
||||||
|
if i1.mkvp.is_empty() || i2.mkvp.is_empty() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if i1.mkvp == i2.mkvp {
|
||||||
|
eprintln!("APQN {} and APQN {} have same MPVK", a1, a2);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(PartialEq, Eq)]
|
||||||
|
pub enum BindState {
|
||||||
|
Bound,
|
||||||
|
Unbound,
|
||||||
|
NotSupported,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Query bind state for this APQN.
|
||||||
|
///
|
||||||
|
/// Returns a BindState enum as defined above or on failure
|
||||||
|
/// an error string. Does NOT print any error messages.
|
||||||
|
pub fn get_apqn_bind_state(card: u32, dom: u32) -> Result<BindState, String> {
|
||||||
|
let path = format!(
|
||||||
|
"{}/card{:02x}/{:02x}.{:04x}/se_bind",
|
||||||
|
PATH_SYS_DEVICES_AP, card, card, dom
|
||||||
|
);
|
||||||
|
match sysfs_read_string(&path) {
|
||||||
|
Err(err) => Err(format!(
|
||||||
|
"Failure reading se_bind attribute for APQN({},{}): {:?}.",
|
||||||
|
card, dom, err
|
||||||
|
)),
|
||||||
|
Ok(str) => match str.as_str() {
|
||||||
|
"bound" => Ok(BindState::Bound),
|
||||||
|
"unbound" => Ok(BindState::Unbound),
|
||||||
|
"-" => Ok(BindState::NotSupported),
|
||||||
|
_ => Err(format!("Unknown bind state '{str}'.")),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Bind or unbind an APQN.
|
||||||
|
///
|
||||||
|
/// The action is determined by the BindState given in.
|
||||||
|
/// But of course only Bound and Unbound is supported - otherwise
|
||||||
|
/// this function panics!
|
||||||
|
/// The function actively loops over the bind state until
|
||||||
|
/// the requested bind state is reached or a timeout has
|
||||||
|
/// occurred (SYS_BUS_AP_BIND_TIMEOUT_MS).
|
||||||
|
/// On success () is returned, on failure an error string
|
||||||
|
/// is returned. Does NOT print any error messages.
|
||||||
|
/// # Panics
|
||||||
|
/// Panics if a desired bind state other than Bound or Unbound is given.
|
||||||
|
pub fn set_apqn_bind_state(card: u32, dom: u32, state: BindState) -> Result<(), String> {
|
||||||
|
let path = format!(
|
||||||
|
"{}/card{:02x}/{:02x}.{:04x}/se_bind",
|
||||||
|
PATH_SYS_DEVICES_AP, card, card, dom
|
||||||
|
);
|
||||||
|
let r = match state {
|
||||||
|
BindState::Bound => sysfs_write_i32(&path, 1),
|
||||||
|
BindState::Unbound => sysfs_write_i32(&path, 0),
|
||||||
|
_ => panic!("set_apqn_bind_state called with invalid BindState."),
|
||||||
|
};
|
||||||
|
if r.is_err() {
|
||||||
|
return Err(format!(
|
||||||
|
"Failure writing se_bind attribute for APQN({},{}): {:?}.",
|
||||||
|
card,
|
||||||
|
dom,
|
||||||
|
r.unwrap_err()
|
||||||
|
));
|
||||||
|
}
|
||||||
|
let mut ms: u64 = 0;
|
||||||
|
loop {
|
||||||
|
thread::sleep(time::Duration::from_millis(SYS_BUS_AP_BIND_POLL_MS));
|
||||||
|
ms += SYS_BUS_AP_BIND_POLL_MS;
|
||||||
|
if ms >= SYS_BUS_AP_BIND_TIMEOUT_MS {
|
||||||
|
break Err(format!(
|
||||||
|
"Timeout setting APQN({},{}) bind state.",
|
||||||
|
card, dom
|
||||||
|
));
|
||||||
|
}
|
||||||
|
let newstate = match get_apqn_bind_state(card, dom) {
|
||||||
|
Err(err) => return Err(err),
|
||||||
|
Ok(s) => s,
|
||||||
|
};
|
||||||
|
if newstate == state {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(PartialEq, Eq)]
|
||||||
|
pub enum AssocState {
|
||||||
|
Associated(u16),
|
||||||
|
AssociationPending,
|
||||||
|
Unassociated,
|
||||||
|
NotSupported,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Query association state for this APQN.
|
||||||
|
///
|
||||||
|
/// Returns an AssocState enum as defined above or on failure
|
||||||
|
/// an error string. Does NOT print any error messages.
|
||||||
|
pub fn get_apqn_associate_state(card: u32, dom: u32) -> Result<AssocState, String> {
|
||||||
|
let path = format!(
|
||||||
|
"{}/card{:02x}/{:02x}.{:04x}/se_associate",
|
||||||
|
PATH_SYS_DEVICES_AP, card, card, dom
|
||||||
|
);
|
||||||
|
match sysfs_read_string(&path) {
|
||||||
|
Err(err) => Err(format!(
|
||||||
|
"Failure reading se_associate attribute for APQN({},{}: {:?}",
|
||||||
|
card, dom, err
|
||||||
|
)),
|
||||||
|
Ok(str) => {
|
||||||
|
if let Some(prefix) = str.strip_prefix("associated ") {
|
||||||
|
let value = &prefix.parse::<u16>();
|
||||||
|
match value {
|
||||||
|
Ok(v) => Ok(AssocState::Associated(*v)),
|
||||||
|
Err(_) => Err(format!("Invalid association index in '{str}'.")),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
match str.as_str() {
|
||||||
|
"association pending" => Ok(AssocState::AssociationPending),
|
||||||
|
"unassociated" => Ok(AssocState::Unassociated),
|
||||||
|
"-" => Ok(AssocState::NotSupported),
|
||||||
|
_ => Err(format!("Unknown association state '{str}'.")),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_apqn_associate_state_associate(card: u32, dom: u32, idx: u16) -> Result<(), String> {
|
||||||
|
let path = format!(
|
||||||
|
"{}/card{:02x}/{:02x}.{:04x}/se_associate",
|
||||||
|
PATH_SYS_DEVICES_AP, card, card, dom
|
||||||
|
);
|
||||||
|
let r = sysfs_write_i32(&path, idx as i32);
|
||||||
|
if r.is_err() {
|
||||||
|
return Err(format!(
|
||||||
|
"Failure writing se_associate attribute for APQN({},{}): {:?}.",
|
||||||
|
card,
|
||||||
|
dom,
|
||||||
|
r.unwrap_err()
|
||||||
|
));
|
||||||
|
}
|
||||||
|
let mut ms: u64 = 0;
|
||||||
|
loop {
|
||||||
|
thread::sleep(time::Duration::from_millis(SYS_BUS_AP_ASSOC_POLL_MS));
|
||||||
|
ms += SYS_BUS_AP_ASSOC_POLL_MS;
|
||||||
|
if ms >= SYS_BUS_AP_ASSOC_TIMEOUT_MS {
|
||||||
|
break Err(format!(
|
||||||
|
"Timeout setting APQN({},{}) association idx {} state.",
|
||||||
|
card, dom, idx
|
||||||
|
));
|
||||||
|
}
|
||||||
|
let newstate = match get_apqn_associate_state(card, dom) {
|
||||||
|
Err(err) => return Err(err),
|
||||||
|
Ok(s) => s,
|
||||||
|
};
|
||||||
|
if let AssocState::Associated(i) = newstate {
|
||||||
|
if idx == i {
|
||||||
|
return Ok(());
|
||||||
|
} else {
|
||||||
|
return Err(format!(
|
||||||
|
"Failure: APQN({},{}) is associated with {} but it should be {}.",
|
||||||
|
card, dom, i, idx
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_apqn_associate_state_unbind(card: u32, dom: u32) -> Result<(), String> {
|
||||||
|
let bindpath = format!(
|
||||||
|
"{}/card{:02x}/{:02x}.{:04x}/se_bind",
|
||||||
|
PATH_SYS_DEVICES_AP, card, card, dom
|
||||||
|
);
|
||||||
|
let r = sysfs_write_i32(&bindpath, 0);
|
||||||
|
if r.is_err() {
|
||||||
|
return Err(format!(
|
||||||
|
"Failure writing se_bind attribute for APQN({},{}): {:?}.",
|
||||||
|
card,
|
||||||
|
dom,
|
||||||
|
r.unwrap_err()
|
||||||
|
));
|
||||||
|
}
|
||||||
|
let mut ms: u64 = 0;
|
||||||
|
loop {
|
||||||
|
thread::sleep(time::Duration::from_millis(SYS_BUS_AP_ASSOC_POLL_MS));
|
||||||
|
ms += SYS_BUS_AP_ASSOC_POLL_MS;
|
||||||
|
if ms >= SYS_BUS_AP_ASSOC_TIMEOUT_MS {
|
||||||
|
break Err(format!(
|
||||||
|
"Timeout setting APQN({},{}) association unbind state.",
|
||||||
|
card, dom
|
||||||
|
));
|
||||||
|
}
|
||||||
|
let newstate = match get_apqn_associate_state(card, dom) {
|
||||||
|
Err(err) => return Err(err),
|
||||||
|
Ok(s) => s,
|
||||||
|
};
|
||||||
|
if newstate == AssocState::Unassociated {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Associate or Unassociate an APQN.
|
||||||
|
///
|
||||||
|
/// The action is determined by the AssocState given in.
|
||||||
|
/// But of course only Associated and Unassociated is supported
|
||||||
|
/// otherwise this function panics!
|
||||||
|
/// The function actively loops over the association state until
|
||||||
|
/// the requested state is reached or a timeout has
|
||||||
|
/// occurred (SYS_BUS_AP_ASSOC_TIMEOUT_MS).
|
||||||
|
/// The unassociate is in fact a unbind. So the code triggers
|
||||||
|
/// an unbind and then loops over the sysfs se_associate until
|
||||||
|
/// "unassociated" is reached.
|
||||||
|
/// On success () is returned, on failure an error string
|
||||||
|
/// is returned. Does NOT print any error messages.
|
||||||
|
/// # Panics
|
||||||
|
/// Panics if a desired bind state other than Associated or
|
||||||
|
/// Unassociated is given.
|
||||||
|
pub fn set_apqn_associate_state(card: u32, dom: u32, state: AssocState) -> Result<(), String> {
|
||||||
|
match state {
|
||||||
|
AssocState::Associated(idx) => set_apqn_associate_state_associate(card, dom, idx),
|
||||||
|
AssocState::Unassociated => set_apqn_associate_state_unbind(card, dom),
|
||||||
|
_ => panic!("set_apqn_associate_state called with invalid AssocState."),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
// These tests assume, there is an AP bus available
|
||||||
|
// Also for each APQN which is online, it is assumed
|
||||||
|
// to have a valid master key set up (for Ep11 and CCA).
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_check_ap_bus_support() {
|
||||||
|
if Path::new(PATH_SYS_BUS_AP).is_dir() {
|
||||||
|
assert!(check_ap_bus_support().is_ok());
|
||||||
|
} else {
|
||||||
|
assert!(check_ap_bus_support().is_err());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn test_check_ap_bus_apsb_support() {
|
||||||
|
if Path::new(PATH_SYS_BUS_AP).is_dir() {
|
||||||
|
// if we are inside a secure execution guest the
|
||||||
|
// apsb check should succeed. Outside an SE guest
|
||||||
|
// the check should fail.
|
||||||
|
if pv_core::misc::pv_guest_bit_set() {
|
||||||
|
assert!(ap_bus_has_apsb_support().is_ok());
|
||||||
|
} else {
|
||||||
|
assert!(ap_bus_has_apsb_support().is_err());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
assert!(ap_bus_has_apsb_support().is_err());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn test_wait_for_ap_bus_bindings_complete() {
|
||||||
|
let r = wait_for_ap_bus_bindings_complete();
|
||||||
|
if Path::new(PATH_SYS_BUS_AP).is_dir() {
|
||||||
|
assert!(r);
|
||||||
|
} else {
|
||||||
|
assert!(!r);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn test_gather_apqns() {
|
||||||
|
let r = ApqnList::gather_apqns();
|
||||||
|
if Path::new(PATH_SYS_BUS_AP).is_dir() {
|
||||||
|
assert!(r.is_some());
|
||||||
|
// fail if no entries found
|
||||||
|
let l = r.unwrap();
|
||||||
|
let v = l.to_apqn_vec();
|
||||||
|
for a in v {
|
||||||
|
match a.mode {
|
||||||
|
ApqnMode::Accel => {
|
||||||
|
// fail if no ApqnInfo is attached
|
||||||
|
assert!(a.info.is_some());
|
||||||
|
}
|
||||||
|
ApqnMode::Ep11 => {
|
||||||
|
// fail if no ApqnInfo is attached
|
||||||
|
assert!(a.info.is_some());
|
||||||
|
let info = a.info.unwrap();
|
||||||
|
let i = match &info {
|
||||||
|
ApqnInfo::Ep11(i) => i,
|
||||||
|
_ => panic!("ApqnInfo attached onto Ep11 APQN is NOT ApqnInfoEp11 ?!?"),
|
||||||
|
};
|
||||||
|
// fail if no serialnr
|
||||||
|
assert!(!i.serialnr.is_empty());
|
||||||
|
// mkvp is either empty (no WK set) or has exact 32 characters
|
||||||
|
assert!(i.mkvp.is_empty() || i.mkvp.len() == 32);
|
||||||
|
}
|
||||||
|
ApqnMode::Cca => {
|
||||||
|
// fail if no ApqnInfo is attached
|
||||||
|
assert!(a.info.is_some());
|
||||||
|
let info = a.info.unwrap();
|
||||||
|
let i = match &info {
|
||||||
|
ApqnInfo::Cca(i) => i,
|
||||||
|
_ => panic!("ApqnInfo attached onto Cca APQN is NOT ApqnInfoCca ?!?"),
|
||||||
|
};
|
||||||
|
// fail if no serialnr
|
||||||
|
assert!(!i.serialnr.is_empty());
|
||||||
|
// aes mkvp is either empty (no MK set) or exact 16 characters
|
||||||
|
assert!(i.mkvp_aes.is_empty() || i.mkvp_aes.len() == 16);
|
||||||
|
// apka mkvp is either empty (no MK set) or exact 16 characters
|
||||||
|
assert!(i.mkvp_apka.is_empty() || i.mkvp_apka.len() == 16);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
assert!(r.is_none());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
63
rust/pvapconfig/src/cli.rs
Normal file
63
rust/pvapconfig/src/cli.rs
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
//
|
||||||
|
// Copyright IBM Corp. 2023
|
||||||
|
//
|
||||||
|
//! Command line interface for pvapconfig
|
||||||
|
//
|
||||||
|
|
||||||
|
use clap::Parser;
|
||||||
|
use lazy_static::lazy_static;
|
||||||
|
|
||||||
|
/// The default pvapconfig config file
|
||||||
|
pub const PATH_DEFAULT_CONFIG_FILE: &str = "/etc/pvapconfig.yaml";
|
||||||
|
|
||||||
|
#[derive(Parser, Clone)]
|
||||||
|
pub struct Cli {
|
||||||
|
/// Provide a custom config file (overwrites default /etc/pvapconfig.yaml).
|
||||||
|
#[arg(short, long, value_name = "FILE")]
|
||||||
|
pub config: Option<String>,
|
||||||
|
|
||||||
|
/// Dry run: display the actions but don't actually perform them on the APQNs.
|
||||||
|
#[arg(short = 'n', long = "dry-run")]
|
||||||
|
pub dryrun: bool,
|
||||||
|
|
||||||
|
/// Enforce strict match: All config entries need to be fulfilled.
|
||||||
|
///
|
||||||
|
/// By default it is enough to successfully apply at least one config entry.
|
||||||
|
/// With the strict flag enabled, all config entries within a config file
|
||||||
|
/// need to be applied successful.
|
||||||
|
#[arg(long = "strict")]
|
||||||
|
pub strict: bool,
|
||||||
|
|
||||||
|
/// Provide more detailed output.
|
||||||
|
#[arg(short, long)]
|
||||||
|
pub verbose: bool,
|
||||||
|
|
||||||
|
/// Print version information and exit.
|
||||||
|
#[arg(short = 'V', long)]
|
||||||
|
pub version: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
lazy_static! {
|
||||||
|
pub static ref ARGS: Cli = Cli::parse();
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Cli {
|
||||||
|
/// verbose returns true if the verbose command line option
|
||||||
|
/// was given, otherwise false is returned.
|
||||||
|
pub fn verbose(&self) -> bool {
|
||||||
|
self.verbose
|
||||||
|
}
|
||||||
|
|
||||||
|
/// dryrun returns true if the dry-run command line option
|
||||||
|
/// was given, otherwise false is returned.
|
||||||
|
pub fn dryrun(&self) -> bool {
|
||||||
|
self.dryrun
|
||||||
|
}
|
||||||
|
|
||||||
|
/// strict returns true if the strict flag was given, otherwise
|
||||||
|
/// false is returned.
|
||||||
|
pub fn strict(&self) -> bool {
|
||||||
|
self.strict
|
||||||
|
}
|
||||||
|
}
|
||||||
391
rust/pvapconfig/src/config.rs
Normal file
391
rust/pvapconfig/src/config.rs
Normal file
@@ -0,0 +1,391 @@
|
|||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
//
|
||||||
|
// Copyright IBM Corp. 2023
|
||||||
|
//
|
||||||
|
//! Functions around handling the pvapconfig configuration file
|
||||||
|
//
|
||||||
|
|
||||||
|
use openssl::sha::sha256;
|
||||||
|
use regex::Regex;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use serde_yaml::{self};
|
||||||
|
use std::fs::File;
|
||||||
|
use std::slice::Iter;
|
||||||
|
|
||||||
|
pub const STR_MODE_EP11: &str = "ep11";
|
||||||
|
pub const STR_MODE_ACCEL: &str = "accel";
|
||||||
|
|
||||||
|
const RE_EP11_MKVP_32: &str = r"^(0x)?([[:xdigit:]]{32})$";
|
||||||
|
const RE_EP11_MKVP_64: &str = r"^(0x)?([[:xdigit:]]{64})$";
|
||||||
|
const RE_SERIALNR: &str = r"^(\S{16})$";
|
||||||
|
const RE_EP11_GEN: &str = r"^cex(8)$";
|
||||||
|
const RE_ACCEL_GEN: &str = r"^cex([4-8])$";
|
||||||
|
const RE_SECRETID: &str = r"^(0x)?([[:xdigit:]]{64})$";
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize, Default, Clone)]
|
||||||
|
#[serde(default, deny_unknown_fields)]
|
||||||
|
pub struct ApConfigEntry {
|
||||||
|
pub name: String, // name and description are unmodified from the config file
|
||||||
|
pub description: String, // accel after validation ep11 after validation
|
||||||
|
pub mode: String, // "accel" "ep11"
|
||||||
|
pub mkvp: String, // empty 32 hex lowercase characters
|
||||||
|
pub serialnr: String, // empty empty or 16 non-whitespace characters
|
||||||
|
pub mingen: String, // empty or "cex4"..."cex8" empty or "cex8"
|
||||||
|
pub secretid: String, // empty 64 hex lowercase characters
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ApConfigEntry {
|
||||||
|
fn validate_secretid(&mut self) -> Result<(), String> {
|
||||||
|
// either secret id or name may be given
|
||||||
|
if self.secretid.is_empty() && self.name.is_empty() {
|
||||||
|
return Err("Neither secretid nor name given.".to_string());
|
||||||
|
}
|
||||||
|
// if name is given, calculate sha256 digest for this name
|
||||||
|
// test for the hash calculated here can be done with openssl:
|
||||||
|
// echo -n "Hello" >in.bin; openssl dgst -sha256 -binary -out out.bin in.bin; hexdump -C out.bin
|
||||||
|
if self.name.is_empty() {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
let hash = sha256(self.name.as_bytes());
|
||||||
|
let hashstr = crate::helper::u8_to_hexstring(&hash);
|
||||||
|
// if there is a secretid given, this must match to the hash
|
||||||
|
if !self.secretid.is_empty() {
|
||||||
|
if self.secretid != hashstr {
|
||||||
|
return Err("Mismatch between sha256(name) and secretid.".to_string());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
self.secretid = hashstr;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// # Panics
|
||||||
|
/// Panics if the compilation of a static regular expression fails.
|
||||||
|
fn validate_ep11_entry(&mut self) -> Result<(), String> {
|
||||||
|
// mkvp is required
|
||||||
|
let mut mkvp = self.mkvp.trim().to_lowercase();
|
||||||
|
if mkvp.is_empty() {
|
||||||
|
return Err("Mkvp value missing.".to_string());
|
||||||
|
}
|
||||||
|
// either 64 hex or 32 hex
|
||||||
|
if Regex::new(RE_EP11_MKVP_64).unwrap().is_match(&mkvp) {
|
||||||
|
// need to cut away the last 32 hex characters
|
||||||
|
mkvp = String::from(&mkvp[..mkvp.len() - 32])
|
||||||
|
} else if Regex::new(RE_EP11_MKVP_32).unwrap().is_match(&mkvp) {
|
||||||
|
// nothing to do here
|
||||||
|
} else {
|
||||||
|
return Err(format!("Mkvp value '{}' is not valid.", &self.mkvp));
|
||||||
|
}
|
||||||
|
self.mkvp = match mkvp.strip_prefix("0x") {
|
||||||
|
Some(rest) => String::from(rest),
|
||||||
|
None => mkvp,
|
||||||
|
};
|
||||||
|
// serialnr is optional
|
||||||
|
let serialnr = self.serialnr.trim().to_string();
|
||||||
|
if !serialnr.is_empty() && !Regex::new(RE_SERIALNR).unwrap().is_match(&serialnr) {
|
||||||
|
return Err(format!("Serialnr value '{}' is not valid.", &self.serialnr));
|
||||||
|
}
|
||||||
|
self.serialnr = serialnr;
|
||||||
|
// mingen is optional, but if given only CEX8 is valid
|
||||||
|
let mingen = self.mingen.trim().to_lowercase();
|
||||||
|
if !mingen.is_empty() && !Regex::new(RE_EP11_GEN).unwrap().is_match(&mingen) {
|
||||||
|
return Err(format!("Mingen value '{}' is not valid.", &self.mingen));
|
||||||
|
}
|
||||||
|
self.mingen = mingen;
|
||||||
|
// secretid or name is required
|
||||||
|
let secretid = self.secretid.trim().to_lowercase();
|
||||||
|
if !secretid.is_empty() && !Regex::new(RE_SECRETID).unwrap().is_match(&secretid) {
|
||||||
|
return Err(format!("Secretid value '{}' is not valid.", &self.secretid));
|
||||||
|
}
|
||||||
|
self.secretid = match secretid.strip_prefix("0x") {
|
||||||
|
Some(rest) => String::from(rest),
|
||||||
|
None => secretid,
|
||||||
|
};
|
||||||
|
// name is optional, ignored here
|
||||||
|
// description is optional, ignored here
|
||||||
|
// but the secretid needs some more validation
|
||||||
|
self.validate_secretid()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// # Panics
|
||||||
|
/// Panics if the compilation of a static regular expression fails.
|
||||||
|
fn validate_accel_entry(&mut self) -> Result<(), String> {
|
||||||
|
// mkvp is ignored
|
||||||
|
self.mkvp.clear();
|
||||||
|
// serialnr is ignored
|
||||||
|
self.serialnr.clear();
|
||||||
|
// mingen is optional, but if given must match to CEX4..CEX8
|
||||||
|
let mingen = self.mingen.trim().to_lowercase();
|
||||||
|
if !mingen.is_empty() && !Regex::new(RE_ACCEL_GEN).unwrap().is_match(&mingen) {
|
||||||
|
return Err(format!("Mingen value '{}' is not valid.", &self.mingen));
|
||||||
|
}
|
||||||
|
self.mingen = mingen;
|
||||||
|
// secretid is ignored
|
||||||
|
self.secretid.clear();
|
||||||
|
// name is optional, ignored here
|
||||||
|
// description is optional, ignored here
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn validate(&mut self) -> Result<(), String> {
|
||||||
|
// trim name
|
||||||
|
self.name = self.name.trim().to_string();
|
||||||
|
// mode is always required
|
||||||
|
let mode = self.mode.trim().to_lowercase();
|
||||||
|
match mode.as_str() {
|
||||||
|
STR_MODE_EP11 => {
|
||||||
|
self.mode = mode;
|
||||||
|
self.validate_ep11_entry()?;
|
||||||
|
}
|
||||||
|
STR_MODE_ACCEL => {
|
||||||
|
self.mode = mode;
|
||||||
|
self.validate_accel_entry()?;
|
||||||
|
}
|
||||||
|
_ => return Err(format!("Unknown or invalid mode '{}'.", mode)),
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Wrapper object around Vector of ApConfigEntry
|
||||||
|
pub struct ApConfigList(Vec<ApConfigEntry>);
|
||||||
|
|
||||||
|
impl ApConfigList {
|
||||||
|
#[cfg(test)] // only used in test code
|
||||||
|
pub fn from_apconfigentry_vec(apconfigs: Vec<ApConfigEntry>) -> ApConfigList {
|
||||||
|
ApConfigList(apconfigs)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn iter(&self) -> Iter<'_, ApConfigEntry> {
|
||||||
|
self.0.iter()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn len(&self) -> usize {
|
||||||
|
self.0.len()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_empty(&self) -> bool {
|
||||||
|
self.0.is_empty()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_yaml_file(fname: &str) -> Result<Vec<ApConfigEntry>, String> {
|
||||||
|
let file = match File::open(fname) {
|
||||||
|
Ok(f) => f,
|
||||||
|
Err(err) => {
|
||||||
|
return Err(format!(
|
||||||
|
"Failure to open AP config file {}: {:?}",
|
||||||
|
fname, err
|
||||||
|
))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
match serde_yaml::from_reader(file) {
|
||||||
|
Ok(cfg) => Ok(cfg),
|
||||||
|
Err(err) => Err(format!(
|
||||||
|
"Failure parsing AP config file {}: {:?}",
|
||||||
|
fname, err
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn validate(config: &mut [ApConfigEntry]) -> Result<(), String> {
|
||||||
|
for (i, entry) in config.iter_mut().enumerate() {
|
||||||
|
let ename = if !entry.name.trim().is_empty() {
|
||||||
|
format!("AP config entry {} '{}'", i, entry.name.trim())
|
||||||
|
} else {
|
||||||
|
format!("AP config entry {}", i)
|
||||||
|
};
|
||||||
|
if let Err(err) = &entry.validate() {
|
||||||
|
return Err(format!("{}: {}", ename, err));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Read in and validate the yaml configuration from a file.
|
||||||
|
/// Returns a Result with Ok(ApConfigList) on success
|
||||||
|
/// or an Err(errorstring) on failure.
|
||||||
|
pub fn read_and_validate_yaml_file(fname: &str) -> Result<ApConfigList, String> {
|
||||||
|
let mut apconfig = ApConfigList::read_yaml_file(fname)?;
|
||||||
|
ApConfigList::validate(&mut apconfig)?;
|
||||||
|
Ok(ApConfigList(apconfig))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
use std::env;
|
||||||
|
use std::fs;
|
||||||
|
use std::io::Write;
|
||||||
|
|
||||||
|
const GOOD_CONFIGS: [&str; 8] = [
|
||||||
|
"# good test 1
|
||||||
|
- name: my Accelerator
|
||||||
|
mode: AcCel
|
||||||
|
mingen: Cex7\n",
|
||||||
|
"# good test 2
|
||||||
|
- name: my Accelerator 2
|
||||||
|
description: Accelerator entry with description
|
||||||
|
mode: Accel\n",
|
||||||
|
"# good test 3
|
||||||
|
- name: my EP11 APQN 1
|
||||||
|
mode: Ep11
|
||||||
|
mkvp: 0xDB3C3B3C3F097DD55EC7EB0E7FDBCB93
|
||||||
|
serialnr: 93AADFK719460083
|
||||||
|
secretid: 0xBC9d46c052BC3574454C5715757274629a283767ed237922cfb8651c0e77320A\n",
|
||||||
|
"# good test 4
|
||||||
|
- name: my EP11 APQN 2
|
||||||
|
mode: EP11
|
||||||
|
mkvp: 0xdb3c3b3c3f097dd55ec7eb0e7fdbcb93
|
||||||
|
serialnr: 93aaDHzu42082261
|
||||||
|
secretid: 0x2ca853f959fc5ce5f1888cb48dae39514a27bb66520ac85f6073a7f678d262c0\n",
|
||||||
|
"# good test 5
|
||||||
|
- name: my EP11 APQN 3
|
||||||
|
mode: EP11
|
||||||
|
mkvp: 0xdb3c3b3c3f097dd55ec7eb0e7fdbcb93db3c3b3c3f097dd55ec7eb0e7fdbcb93
|
||||||
|
serialnr: 93aaDHzu42082261
|
||||||
|
secretid: 0xd146c9ae77cdff25fa87a5b3487587dc29a4e391b315c98570e8fa2e2ec91454\n",
|
||||||
|
"# no name but secretid given
|
||||||
|
- mode: EP11
|
||||||
|
mkvp: 0xdb3c3b3c3f097dd55ec7eb0e7fdbcb93
|
||||||
|
secretid: 0x0767668dd22f23fa675c4641e04bb4e991f443be4df13ce3896b8eeca59fcc10\n",
|
||||||
|
"# no secretid but name given
|
||||||
|
- mode: EP11
|
||||||
|
name: My-EP11-AP-config
|
||||||
|
mkvp: 0xdb3c3b3c3f097dd55ec7eb0e7fdbcb93\n",
|
||||||
|
"# secretid and name given
|
||||||
|
- mode: EP11
|
||||||
|
name: My-EP11-AP-config
|
||||||
|
mkvp: 0xdb3c3b3c3f097dd55ec7eb0e7fdbcb93
|
||||||
|
secretid: 0x0767668dd22f23fa675c4641e04bb4e991f443be4df13ce3896b8eeca59fcc10\n",
|
||||||
|
];
|
||||||
|
|
||||||
|
const BAD_CONFIGS: [&str; 12] = [
|
||||||
|
"# mode missing
|
||||||
|
- name: bad test 1
|
||||||
|
mkvp: 0xdb3c3b3c3f097dd55ec7eb0e7fdbcb93
|
||||||
|
secretid: 0x0767668dd22f23fa675c4641e04bb4e991f443be4df13ce3896b8eeca59fcc10\n",
|
||||||
|
"# invalid mode
|
||||||
|
- name: bad test 2
|
||||||
|
mode: CCA
|
||||||
|
mkvp: 0xdb3c3b3c3f097dd55ec7eb0e7fdbcb93
|
||||||
|
secretid: 0x0767668dd22f23fa675c4641e04bb4e991f443be4df13ce3896b8eeca59fcc10\n",
|
||||||
|
"# Accelerator with wrong CEX3
|
||||||
|
- name: bad test 3
|
||||||
|
mode: Accel
|
||||||
|
mingen: Cex3\n",
|
||||||
|
"# Accelerator with wrong CEX9
|
||||||
|
- name: bad test 4
|
||||||
|
mode: Accel
|
||||||
|
mingen: CEX9\n",
|
||||||
|
"# EP11 with mkvp missing
|
||||||
|
- name: bad test 5
|
||||||
|
mode: EP11
|
||||||
|
serialnr: 93AADHZU42082261\n",
|
||||||
|
"# EP11 with non hex mkvp
|
||||||
|
- name: bad test 6
|
||||||
|
mode: EP11
|
||||||
|
mkvp: 0xabcdefghijklmnopqqponmlkjihgfedcba
|
||||||
|
serialnr: 93AADHZU42082261\n",
|
||||||
|
"# EP11 with mkvp too big
|
||||||
|
- name: bad test 7
|
||||||
|
mode: EP11
|
||||||
|
mkvp: 0xdb3c3b3c3f097dd55ec7eb0e7fdbcb93aa
|
||||||
|
serialnr: 93AADHZU42082261\n",
|
||||||
|
"# EP11 with mkvp too small
|
||||||
|
- name: bad test 8
|
||||||
|
mode: EP11
|
||||||
|
mkvp: 0xdb3c3b3c3f097dd55ec7eb0e7fdbcb
|
||||||
|
serialnr: 93AADHZU42082261\n",
|
||||||
|
"# EP11 with invalid CEXx
|
||||||
|
- name: bad test 9
|
||||||
|
mode: EP11
|
||||||
|
mingen: CEX7
|
||||||
|
mkvp: 0x00112233445566778899aabbccddeeff
|
||||||
|
serialnr: 93AADHZU42082261\n",
|
||||||
|
"# EP11 with invalid Serialnr
|
||||||
|
- name: bad test 10
|
||||||
|
mode: EP11
|
||||||
|
mkvp: 0x00112233445566778899aabbccddeeff
|
||||||
|
serialnr: 93AADHZU4208226\n",
|
||||||
|
"# EP11 with invalid Serialnr
|
||||||
|
- name: bad test 11
|
||||||
|
mode: EP11
|
||||||
|
mkvp: 0x00112233445566778899aabbccddeeff
|
||||||
|
serialnr: 93AAD ZU42082261\n",
|
||||||
|
"# EP11 with sha256(name) != secretid
|
||||||
|
- name: bad test 12
|
||||||
|
mode: EP11
|
||||||
|
mkvp: 0x00112233445566778899aabbccddeeff
|
||||||
|
serialnr: AABBCCDDEEFFGGHH
|
||||||
|
secretid: 0x2ca853f959fc5ce5f1888cb48dae39514a27bb66520ac85f6073a7f678d262c0\n",
|
||||||
|
];
|
||||||
|
|
||||||
|
const BAD_DESERIALIZE: [&str; 2] = [
|
||||||
|
"/*\ntotal nonsense\n */\n",
|
||||||
|
"# wrong/unknown field
|
||||||
|
- name: de-serialize failure 1
|
||||||
|
type: EP11\n",
|
||||||
|
];
|
||||||
|
|
||||||
|
fn write_yaml_config_to_temp_file(content: &str) -> Result<String, String> {
|
||||||
|
let dir = env::temp_dir();
|
||||||
|
let rnd = rand::random::<u32>();
|
||||||
|
let fname = format!("{}/config-test-{}.yaml", dir.to_str().unwrap(), rnd);
|
||||||
|
let mut f = match File::create(&fname) {
|
||||||
|
Ok(f) => f,
|
||||||
|
Err(_) => return Err(format!("Failure creating temp file '{fname}'.")),
|
||||||
|
};
|
||||||
|
match f.write_all(content.as_bytes()) {
|
||||||
|
Ok(_) => Ok(fname),
|
||||||
|
Err(_) => {
|
||||||
|
fs::remove_file(&fname).ok();
|
||||||
|
Err(format!("Failure writing to temp file '{fname}'."))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_good_yaml() {
|
||||||
|
for yaml in GOOD_CONFIGS {
|
||||||
|
let f = write_yaml_config_to_temp_file(yaml).unwrap();
|
||||||
|
let config = ApConfigList::read_and_validate_yaml_file(&f).unwrap();
|
||||||
|
assert!(!config.is_empty());
|
||||||
|
fs::remove_file(&f).ok();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn test_bad_yaml() {
|
||||||
|
for yaml in BAD_CONFIGS {
|
||||||
|
let f = write_yaml_config_to_temp_file(yaml).unwrap();
|
||||||
|
let r = ApConfigList::read_and_validate_yaml_file(&f);
|
||||||
|
assert!(r.is_err());
|
||||||
|
fs::remove_file(&f).ok();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn test_invalid_deserizalize() {
|
||||||
|
for yaml in BAD_DESERIALIZE {
|
||||||
|
let f = write_yaml_config_to_temp_file(yaml).unwrap();
|
||||||
|
let r = ApConfigList::read_and_validate_yaml_file(&f);
|
||||||
|
assert!(r.is_err());
|
||||||
|
fs::remove_file(&f).ok();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn test_sha256() {
|
||||||
|
assert!(
|
||||||
|
crate::helper::u8_to_hexstring(&sha256("Hello".as_bytes()))
|
||||||
|
== "185f8db32271fe25f561a6fc938b2e264306ec304eda518007d1764826381969"
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
crate::helper::u8_to_hexstring(&sha256("SECRET1".as_bytes()))
|
||||||
|
== "03153249db7ce46b0330ffb1a760b59710531af08ec4d7f8424a6870fae49360"
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
crate::helper::u8_to_hexstring(&sha256("SECRET2".as_bytes()))
|
||||||
|
== "258499e710e0bd3bb878d6bac7e478b30f3f3e72566989f638c4143d14f6c0b6"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
279
rust/pvapconfig/src/helper.rs
Normal file
279
rust/pvapconfig/src/helper.rs
Normal file
@@ -0,0 +1,279 @@
|
|||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
//
|
||||||
|
// Copyright IBM Corp. 2023
|
||||||
|
//
|
||||||
|
//! Collection of helper functions for pvapconfig
|
||||||
|
//
|
||||||
|
|
||||||
|
use regex::Regex;
|
||||||
|
use std::error::Error;
|
||||||
|
use std::fs;
|
||||||
|
use std::fs::{File, OpenOptions};
|
||||||
|
use std::io::{Read, Write};
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
pub const PATH_PVAPCONFIG_LOCK: &str = "/run/lock/pvapconfig.lock";
|
||||||
|
|
||||||
|
/// Convert u8 slice to (lowercase) hex string
|
||||||
|
pub fn u8_to_hexstring(slice: &[u8]) -> String {
|
||||||
|
let s = String::with_capacity(2 * slice.len());
|
||||||
|
slice.iter().fold(s, |acc, e| acc + &format!("{e:02x}"))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Convert hexstring to u8 vector
|
||||||
|
/// The hexstring may contain whitespaces which are ignored.
|
||||||
|
/// If there are other characters in there or if the number
|
||||||
|
/// of hex characters is uneven panic() is called.
|
||||||
|
/// # Panics
|
||||||
|
/// Panics if the given string contains characters other than
|
||||||
|
/// hex digits and whitespace. Panics if the number of hex digits
|
||||||
|
/// is not even.
|
||||||
|
#[cfg(test)] // currently only used in test code
|
||||||
|
pub fn hexstring_to_u8(hex: &str) -> Vec<u8> {
|
||||||
|
let mut s = String::new();
|
||||||
|
for c in hex.chars() {
|
||||||
|
if c.is_ascii_hexdigit() {
|
||||||
|
s.push(c);
|
||||||
|
} else if c.is_whitespace() {
|
||||||
|
// ignore
|
||||||
|
} else {
|
||||||
|
panic!("Invalid character '{c}'");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if s.len() % 2 == 1 {
|
||||||
|
panic!("Uneven # of hex characters in '{s}'");
|
||||||
|
}
|
||||||
|
let mut hex_bytes = s.as_bytes().iter().map_while(|b| match b {
|
||||||
|
b'0'..=b'9' => Some(b - b'0'),
|
||||||
|
b'a'..=b'f' => Some(b - b'a' + 10),
|
||||||
|
b'A'..=b'F' => Some(b - b'A' + 10),
|
||||||
|
_ => None,
|
||||||
|
});
|
||||||
|
let mut bytes = Vec::with_capacity(s.len());
|
||||||
|
while let (Some(h), Some(l)) = (hex_bytes.next(), hex_bytes.next()) {
|
||||||
|
bytes.push(h << 4 | l)
|
||||||
|
}
|
||||||
|
bytes
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Read sysfs file into string
|
||||||
|
pub fn sysfs_read_string(fname: &str) -> Result<String, Box<dyn Error>> {
|
||||||
|
let mut file = File::open(fname)?;
|
||||||
|
let mut content = String::new();
|
||||||
|
file.read_to_string(&mut content)?;
|
||||||
|
let trimmed_content = String::from(content.trim());
|
||||||
|
Ok(trimmed_content)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Write string into sysfs file
|
||||||
|
pub fn sysfs_write_string(fname: &str, value: &str) -> Result<(), Box<dyn Error>> {
|
||||||
|
let mut file = OpenOptions::new().write(true).open(fname)?;
|
||||||
|
file.write_all(value.as_bytes())?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Read sysfs file content and parse as i32 value
|
||||||
|
pub fn sysfs_read_i32(fname: &str) -> Result<i32, Box<dyn Error>> {
|
||||||
|
let content = sysfs_read_string(fname)?;
|
||||||
|
Ok(content.parse::<i32>()?)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Write an i32 value into a sysfs file
|
||||||
|
pub fn sysfs_write_i32(fname: &str, value: i32) -> Result<(), Box<dyn Error>> {
|
||||||
|
sysfs_write_string(fname, &value.to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// For a given (sysfs) directory construct a list of all subdirs
|
||||||
|
/// and give it back as a vector of strings. If there is no subdir,
|
||||||
|
/// the vector is empty.
|
||||||
|
pub fn sysfs_get_list_of_subdirs(dname: &str) -> Result<Vec<String>, Box<dyn Error>> {
|
||||||
|
let mut v: Vec<String> = Vec::new();
|
||||||
|
let entries = fs::read_dir(dname)?;
|
||||||
|
for entry in entries.flatten() {
|
||||||
|
let file_type = match entry.file_type() {
|
||||||
|
Ok(ft) => ft,
|
||||||
|
_ => continue,
|
||||||
|
};
|
||||||
|
if !file_type.is_dir() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let fname = match entry.file_name().into_string() {
|
||||||
|
Ok(s) => s,
|
||||||
|
_ => continue,
|
||||||
|
};
|
||||||
|
v.push(fname);
|
||||||
|
}
|
||||||
|
Ok(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// For a given (sysfs) directory construct a list of all subdirs which
|
||||||
|
/// match to the given regular expression and give the list back as a
|
||||||
|
/// vector of strings. If there is no subdir, the vector is empty.
|
||||||
|
pub fn sysfs_get_list_of_subdirs_matching_regex(
|
||||||
|
dname: &str,
|
||||||
|
regex: &str,
|
||||||
|
) -> Result<Vec<String>, Box<dyn Error>> {
|
||||||
|
let mut v: Vec<String> = Vec::new();
|
||||||
|
let re = Regex::new(regex)?;
|
||||||
|
let entries = sysfs_get_list_of_subdirs(dname)?;
|
||||||
|
for entry in entries {
|
||||||
|
if re.is_match(&entry) {
|
||||||
|
v.push(entry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// LockFile for inter-process locking
|
||||||
|
///
|
||||||
|
/// Simple class for process locking for pvapconfig.
|
||||||
|
/// The lock concept is simple: The existence of a file is used as the
|
||||||
|
/// locking indicator. If the file exists something is locked, if it does
|
||||||
|
/// not exist something is not locked. In the lock file the PID of the
|
||||||
|
/// process created the file ("owning this file") is written in.
|
||||||
|
/// With the ProcessLock object leaving scope the associated lock file
|
||||||
|
/// is automatically deleted. It is assumed that the creation of a file
|
||||||
|
/// is an atomic operation - that's true for most filesystems but may
|
||||||
|
/// cause problems with network based filesystems.
|
||||||
|
/// Example:
|
||||||
|
/// ```
|
||||||
|
/// let lock = LockFile::lock("/var/lock/process.lock");
|
||||||
|
/// assert!(lock.is_ok());
|
||||||
|
/// let lock2 = LockFile::lock("/var/lock/process.lock");
|
||||||
|
/// assert!(lock2.is_err());
|
||||||
|
/// drop(lock);
|
||||||
|
/// let lock3 = LockFile::lock("/var/lock/process.lock");
|
||||||
|
/// assert!(lock3.is_ok());
|
||||||
|
/// ```
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct LockFile {
|
||||||
|
lockfile: PathBuf,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LockFile {
|
||||||
|
/// Try to establish the lock file.
|
||||||
|
/// Upon success the given file is fresh created and has the pid of this
|
||||||
|
/// process written in. The function returns a new LockFile object
|
||||||
|
/// which has implemented the Drop Trait. So with this object going out
|
||||||
|
/// of scope the lock file is deleted. If establishing the lock file
|
||||||
|
/// fails for any reason (for example the file already exists), the
|
||||||
|
/// function fails with returning an Error string. This function does
|
||||||
|
/// NOT panic if establishing the lock file fails for any reason. If
|
||||||
|
/// the lock file could be esablished but writing in the PID fails, a
|
||||||
|
/// warning is printed but the function continues with returning a
|
||||||
|
/// LockFile object.
|
||||||
|
pub fn try_lock(fname: &str) -> Result<Self, String> {
|
||||||
|
let lockfile = PathBuf::from(fname);
|
||||||
|
let mut file = match OpenOptions::new()
|
||||||
|
.write(true)
|
||||||
|
.create_new(true)
|
||||||
|
.open(&lockfile)
|
||||||
|
{
|
||||||
|
Err(err) => {
|
||||||
|
return Err(format!(
|
||||||
|
"Failure trying to create lock file {fname}: {err:?}."
|
||||||
|
))
|
||||||
|
}
|
||||||
|
Ok(f) => f,
|
||||||
|
};
|
||||||
|
let _ = file
|
||||||
|
.write(format!("{}", std::process::id()).as_bytes())
|
||||||
|
.map_err(|err| {
|
||||||
|
println!("Warning: could not write PID into lockfile {fname}: {err:?}.")
|
||||||
|
});
|
||||||
|
Ok(LockFile { lockfile })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for LockFile {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
let _ = fs::remove_file(&self.lockfile).map_err(|err| {
|
||||||
|
println!(
|
||||||
|
"Warning: could not remove lockfile {}: {err:?}.",
|
||||||
|
self.lockfile.display()
|
||||||
|
)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use pv_core::misc::TemporaryDirectory;
|
||||||
|
|
||||||
|
// Only very simple tests
|
||||||
|
|
||||||
|
const TEST_BYTES: [u8; 8] = [0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef];
|
||||||
|
const TEST_HEXSTR: &str = "0123456789abcdef";
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_u8_to_hexstring() {
|
||||||
|
let str = u8_to_hexstring(&TEST_BYTES);
|
||||||
|
assert!(str == TEST_HEXSTR);
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn test_hexstring_to_u8() {
|
||||||
|
let bytes = hexstring_to_u8(TEST_HEXSTR);
|
||||||
|
assert!(bytes.as_slice() == TEST_BYTES);
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn test_sysfs_read_string() {
|
||||||
|
let r = sysfs_read_string("/proc/cpuinfo");
|
||||||
|
assert!(r.is_ok());
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn test_sysfs_read_i32() {
|
||||||
|
let r = sysfs_read_i32("/proc/sys/kernel/random/entropy_avail");
|
||||||
|
assert!(r.is_ok());
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn test_sysfs_get_list_of_subdirs() {
|
||||||
|
let r = sysfs_get_list_of_subdirs("/proc/self");
|
||||||
|
assert!(r.is_ok());
|
||||||
|
let v = r.unwrap();
|
||||||
|
assert!(!v.is_empty());
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn test_sysfs_get_list_of_subdirs_matching_regex() {
|
||||||
|
let r = sysfs_get_list_of_subdirs_matching_regex("/proc/self", "fd.*");
|
||||||
|
assert!(r.is_ok());
|
||||||
|
let v = r.unwrap();
|
||||||
|
assert!(!v.is_empty());
|
||||||
|
for e in v {
|
||||||
|
assert!(e.strip_prefix("fd").is_some());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn test_sysfs_write_i32() {
|
||||||
|
let temp_dir =
|
||||||
|
TemporaryDirectory::new("pvtests").expect("creating a temporary directory should work");
|
||||||
|
let test_path = temp_dir.path().join("test");
|
||||||
|
let test_path = test_path.as_os_str().to_str().expect("should work");
|
||||||
|
let mut file = File::create(test_path).unwrap();
|
||||||
|
let _ = file.write_all(b"XYZ");
|
||||||
|
drop(file);
|
||||||
|
let r = sysfs_read_i32(test_path);
|
||||||
|
assert!(r.is_err());
|
||||||
|
let r = sysfs_write_i32(test_path, 999);
|
||||||
|
assert!(r.is_ok());
|
||||||
|
let r = sysfs_read_i32(test_path);
|
||||||
|
assert!(r.is_ok());
|
||||||
|
let v = r.unwrap();
|
||||||
|
assert!(v == 999);
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn test_lockfile() {
|
||||||
|
let temp_dir =
|
||||||
|
TemporaryDirectory::new("pvtests").expect("creating a temporary directory should work");
|
||||||
|
let file_path = temp_dir.path().join("my.lock");
|
||||||
|
let file_path_str = file_path.to_str().expect("should work");
|
||||||
|
|
||||||
|
let r1 = LockFile::try_lock(file_path_str);
|
||||||
|
assert!(r1.is_ok());
|
||||||
|
let r2 = LockFile::try_lock(file_path_str);
|
||||||
|
assert!(r2.is_err());
|
||||||
|
drop(r1);
|
||||||
|
let r3 = LockFile::try_lock(file_path_str);
|
||||||
|
assert!(r3.is_ok());
|
||||||
|
}
|
||||||
|
}
|
||||||
668
rust/pvapconfig/src/main.rs
Normal file
668
rust/pvapconfig/src/main.rs
Normal file
@@ -0,0 +1,668 @@
|
|||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
//
|
||||||
|
// Copyright IBM Corp. 2023
|
||||||
|
//
|
||||||
|
//! pvapconfig - Tool to automatically set up the AP configuration
|
||||||
|
//! within an IBM Secure Execution guest.
|
||||||
|
//
|
||||||
|
|
||||||
|
mod ap;
|
||||||
|
mod cli;
|
||||||
|
mod config;
|
||||||
|
mod helper;
|
||||||
|
mod uv;
|
||||||
|
|
||||||
|
use ap::{Apqn, ApqnList};
|
||||||
|
use cli::ARGS;
|
||||||
|
use config::{ApConfigEntry, ApConfigList};
|
||||||
|
use helper::{LockFile, PATH_PVAPCONFIG_LOCK};
|
||||||
|
use pv_core::uv::{ListableSecretType, SecretList};
|
||||||
|
use std::process::ExitCode;
|
||||||
|
use utils::release_string;
|
||||||
|
|
||||||
|
/// Simple macro for
|
||||||
|
/// if Cli::verbose() {
|
||||||
|
/// print!(...);
|
||||||
|
/// }
|
||||||
|
macro_rules! info {
|
||||||
|
($($arg:tt)*) => {{
|
||||||
|
if ARGS.verbose() {
|
||||||
|
print!($($arg)*);
|
||||||
|
}
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Simple macro for the main function only
|
||||||
|
/// Does a eprintln of the arguments and then
|
||||||
|
/// return with exit failure.
|
||||||
|
macro_rules! println_and_exit_failure {
|
||||||
|
($($arg:tt)*) => {{
|
||||||
|
eprintln!($($arg)*);
|
||||||
|
return ExitCode::FAILURE;
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Simple macro for the main function only
|
||||||
|
/// Check if given object has is_err() true and
|
||||||
|
/// then eprintln the unwrapped error and
|
||||||
|
/// returns with exit failure.
|
||||||
|
macro_rules! on_error_print_and_exit {
|
||||||
|
($r:expr) => {
|
||||||
|
if $r.is_err() {
|
||||||
|
eprintln!("{}", $r.unwrap_err());
|
||||||
|
return ExitCode::FAILURE;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() -> ExitCode {
|
||||||
|
// handle version option
|
||||||
|
if cli::ARGS.version {
|
||||||
|
println!(
|
||||||
|
"{} version {}\nCopyright IBM Corp. 2023",
|
||||||
|
env!("CARGO_PKG_NAME"),
|
||||||
|
release_string!()
|
||||||
|
);
|
||||||
|
return ExitCode::SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
// make sure only one pvapconfig instance is running
|
||||||
|
let r = LockFile::try_lock(PATH_PVAPCONFIG_LOCK);
|
||||||
|
on_error_print_and_exit!(r);
|
||||||
|
let _lockfile = r.unwrap();
|
||||||
|
|
||||||
|
// AP bus check
|
||||||
|
info!("Checking AP bus support and facilities...\n");
|
||||||
|
let r = ap::check_ap_bus_support();
|
||||||
|
on_error_print_and_exit!(r);
|
||||||
|
let r = ap::ap_bus_has_apsb_support();
|
||||||
|
on_error_print_and_exit!(r);
|
||||||
|
info!("AP bus support and facilities are ok.\n");
|
||||||
|
|
||||||
|
// UV check
|
||||||
|
info!("Checking UV support and environment...\n");
|
||||||
|
if !pv_core::misc::pv_guest_bit_set() {
|
||||||
|
println_and_exit_failure!("Failure: this is not a SE guest.");
|
||||||
|
}
|
||||||
|
let r = uv::has_list_secrets_facility();
|
||||||
|
on_error_print_and_exit!(r);
|
||||||
|
info!("UV support and environment is ok.\n");
|
||||||
|
|
||||||
|
// read configuration
|
||||||
|
let configfile: &str = match &cli::ARGS.config {
|
||||||
|
Some(f) => f,
|
||||||
|
_ => cli::PATH_DEFAULT_CONFIG_FILE,
|
||||||
|
};
|
||||||
|
info!(
|
||||||
|
"Reading AP configuration entries from file '{}'...\n",
|
||||||
|
configfile
|
||||||
|
);
|
||||||
|
let apconfig: ApConfigList = match ApConfigList::read_and_validate_yaml_file(configfile) {
|
||||||
|
Ok(apcfg) => apcfg,
|
||||||
|
Err(err) => println_and_exit_failure!("{}", err),
|
||||||
|
};
|
||||||
|
if apconfig.is_empty() {
|
||||||
|
println!(
|
||||||
|
"No AP configuration entries in config file '{}': Nothing to do.",
|
||||||
|
configfile
|
||||||
|
);
|
||||||
|
return ExitCode::SUCCESS;
|
||||||
|
}
|
||||||
|
info!("Found {} AP configuration entries.\n", apconfig.len());
|
||||||
|
|
||||||
|
// get list of secrets from UV
|
||||||
|
info!("Fetching list of secrets from UV...\n");
|
||||||
|
let secrets: SecretList = match uv::gather_secrets() {
|
||||||
|
Err(e) => println_and_exit_failure!("{}", e),
|
||||||
|
Ok(los) => los,
|
||||||
|
};
|
||||||
|
info!("Fetched {} Secret entries from UV.\n", secrets.len());
|
||||||
|
|
||||||
|
// Warning if no UV secrets given but AP config entries require it
|
||||||
|
let non_accel_apc = apconfig
|
||||||
|
.iter()
|
||||||
|
.filter(|apc| apc.mode != config::STR_MODE_ACCEL)
|
||||||
|
.count();
|
||||||
|
if non_accel_apc > 0 && secrets.is_empty() {
|
||||||
|
println!(
|
||||||
|
"Warning: No UV Secrets given but at least one AP config entry requires a Secret."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
info!("Waiting for AP bus bindings complete...\n");
|
||||||
|
if !ap::wait_for_ap_bus_bindings_complete() {
|
||||||
|
return ExitCode::FAILURE;
|
||||||
|
}
|
||||||
|
info!("Fetching list of available APQNs...\n");
|
||||||
|
let mut apqns: ApqnList = match ApqnList::gather_apqns() {
|
||||||
|
Some(l) => l,
|
||||||
|
None => return ExitCode::FAILURE,
|
||||||
|
};
|
||||||
|
if apqns.is_empty() {
|
||||||
|
info!("List of available APQNs is empty: So there's nothing to do.\n");
|
||||||
|
return ExitCode::SUCCESS;
|
||||||
|
}
|
||||||
|
info!("Found {} APQNs.\n", apqns.len());
|
||||||
|
// check MK restriction
|
||||||
|
if !apqns.check_mk_restriction() {
|
||||||
|
return ExitCode::FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// now the real work
|
||||||
|
info!("Applying AP configuration...\n");
|
||||||
|
let n = match do_ap_config(&mut apqns, &secrets, &apconfig, false) {
|
||||||
|
Err(e) => println_and_exit_failure!("{}", e),
|
||||||
|
Ok(n) => n,
|
||||||
|
};
|
||||||
|
|
||||||
|
if n == 0 {
|
||||||
|
println_and_exit_failure!(
|
||||||
|
"None out of {} AP config entries could be applied.",
|
||||||
|
apconfig.len()
|
||||||
|
);
|
||||||
|
} else if ARGS.strict() && n != apconfig.len() {
|
||||||
|
println_and_exit_failure!(
|
||||||
|
"Strict flag given and only {} out of {} AP config entries have been applied.",
|
||||||
|
n,
|
||||||
|
apconfig.len()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
info!(
|
||||||
|
"Successfully applied {} out of {} AP config entries.\n",
|
||||||
|
n,
|
||||||
|
apconfig.len()
|
||||||
|
);
|
||||||
|
|
||||||
|
ExitCode::SUCCESS
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The real worker function
|
||||||
|
///
|
||||||
|
/// This is the real algorithm which is trying to apply the
|
||||||
|
/// AP configuration read from the config file to the existing
|
||||||
|
/// APQNs with the info from the list of secrets from the UV.
|
||||||
|
/// Returns the nr of AP config entries which are fulfilled
|
||||||
|
/// after the function ended.
|
||||||
|
/// apqns needs to be mutable as the function does a resort
|
||||||
|
/// but content stays the same.
|
||||||
|
fn do_ap_config(
|
||||||
|
apqns: &mut ApqnList,
|
||||||
|
secrets: &SecretList,
|
||||||
|
apconfig: &ApConfigList,
|
||||||
|
fntest: bool,
|
||||||
|
) -> Result<usize, String> {
|
||||||
|
let mut resolved_entries = 0;
|
||||||
|
let mut apconfig_done = vec![false; apconfig.len()];
|
||||||
|
let mut apqn_done = vec![false; apqns.len()];
|
||||||
|
|
||||||
|
// Preparation: Sort APQNs by generation.
|
||||||
|
// All the following steps iterate through the list
|
||||||
|
// of APQNs. So by sorting the APQNs starting with
|
||||||
|
// highest card generation down to the older card
|
||||||
|
// generations we prefer newer card generations over
|
||||||
|
// older card generations.
|
||||||
|
apqns.sort_by_gen();
|
||||||
|
|
||||||
|
// Step 1:
|
||||||
|
// Go through all AP config entries and try to find an APQN
|
||||||
|
// which already matches to this entry. If such an APQN is
|
||||||
|
// found mark the AP config entry as done, and mark the APQN
|
||||||
|
// as used so that entry and APQN will get skipped over in
|
||||||
|
// the next steps.
|
||||||
|
|
||||||
|
for (ci, apc) in apconfig.iter().enumerate() {
|
||||||
|
let cistr = if !apc.name.is_empty() {
|
||||||
|
format!("#{} '{}'", ci + 1, apc.name)
|
||||||
|
} else {
|
||||||
|
format!("#{}", ci + 1)
|
||||||
|
};
|
||||||
|
for (ai, apqn) in apqns.iter().enumerate() {
|
||||||
|
if apqn_done[ai] {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if !config_and_apqn_match(apc, apqn) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if fntest {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
match apqn.mode {
|
||||||
|
ap::ApqnMode::Accel => {
|
||||||
|
// check bind state of this APQN
|
||||||
|
let bind_state_ok = match apqn.bind_state() {
|
||||||
|
Err(err) => {
|
||||||
|
eprintln!("Warning: Failure reading APQN {apqn} bind state: {err}");
|
||||||
|
false
|
||||||
|
}
|
||||||
|
Ok(ap::BindState::Bound) => true,
|
||||||
|
Ok(_) => false,
|
||||||
|
};
|
||||||
|
if !bind_state_ok {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// This APQN matches to the current AP config entry and is already bound.
|
||||||
|
// So this AP config entry is satisfied: mark this config entry as done
|
||||||
|
// and mark this APQN as used.
|
||||||
|
info!("Accelerator APQN {apqn} already satisfies AP config entry {cistr}.\n");
|
||||||
|
apconfig_done[ci] = true;
|
||||||
|
apqn_done[ai] = true;
|
||||||
|
resolved_entries += 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
ap::ApqnMode::Ep11 => {
|
||||||
|
// check association state of this APQN
|
||||||
|
let (assoc_state_ok, assoc_idx) = match apqn.associate_state() {
|
||||||
|
Err(err) => {
|
||||||
|
eprintln!(
|
||||||
|
"Warning: Failure reading APQN {apqn} associate state: {err}"
|
||||||
|
);
|
||||||
|
(false, 0)
|
||||||
|
}
|
||||||
|
Ok(ap::AssocState::Associated(idx)) => (true, idx),
|
||||||
|
Ok(_) => (false, 0),
|
||||||
|
};
|
||||||
|
if !assoc_state_ok {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// check association index
|
||||||
|
let r = secrets.iter().find(|&se| {
|
||||||
|
se.stype() == ListableSecretType::Association
|
||||||
|
&& se.id().len() == uv::AP_ASSOC_SECRET_ID_SIZE
|
||||||
|
&& se.index() == assoc_idx
|
||||||
|
&& helper::u8_to_hexstring(se.id()) == apc.secretid
|
||||||
|
});
|
||||||
|
if r.is_none() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// This APQN matches to the current AP config entry and is already
|
||||||
|
// associated with the right secret id. So this AP config entry is
|
||||||
|
// satisfied: mark this config entry as done and mark this APQN as used.
|
||||||
|
info!("EP11 APQN {apqn} already satisfies AP config entry {cistr}.\n");
|
||||||
|
apconfig_done[ci] = true;
|
||||||
|
apqn_done[ai] = true;
|
||||||
|
resolved_entries += 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
// (currently) unknown/unsupported APQN mode
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 2:
|
||||||
|
// All APQNs NOT marked as done are now examined for their bind
|
||||||
|
// and association state and maybe reset to "unbound".
|
||||||
|
|
||||||
|
for (ai, apqn) in apqns.iter().enumerate() {
|
||||||
|
if apqn_done[ai] || fntest {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
match apqn.bind_state() {
|
||||||
|
Err(err) => eprintln!("Warning: Failure reading APQN {apqn} bind state: {err}"),
|
||||||
|
Ok(ap::BindState::Bound) => {
|
||||||
|
info!("Unbind APQN {apqn} as this bind/associate does not match to any AP config entry.\n");
|
||||||
|
if !ARGS.dryrun() {
|
||||||
|
if let Err(err) = apqn.set_bind_state(ap::BindState::Unbound) {
|
||||||
|
return Err(format!("Failure unbinding APQN {apqn}: {err}"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(_) => {}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 3:
|
||||||
|
// Go through all remaining AP config entries and try to fulfill each
|
||||||
|
// by searching for an APQN which would match to this config entry and
|
||||||
|
// then prepare this APQN (bind, maybe associate).
|
||||||
|
for (ci, apc) in apconfig.iter().enumerate() {
|
||||||
|
let cistr = if !apc.name.is_empty() {
|
||||||
|
format!("#{} '{}'", ci + 1, apc.name)
|
||||||
|
} else {
|
||||||
|
format!("#{}", ci + 1)
|
||||||
|
};
|
||||||
|
if apconfig_done[ci] {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
for (ai, apqn) in apqns.iter().enumerate() {
|
||||||
|
if apqn_done[ai] {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if !config_and_apqn_match(apc, apqn) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
match apqn.mode {
|
||||||
|
ap::ApqnMode::Accel => {
|
||||||
|
// try to bind this accelerator APQN
|
||||||
|
if ARGS.verbose() || fntest {
|
||||||
|
println!("Bind APQN {apqn} to match to AP config entry {cistr}.");
|
||||||
|
}
|
||||||
|
if !(ARGS.dryrun() || fntest) {
|
||||||
|
if let Err(err) = apqn.set_bind_state(ap::BindState::Bound) {
|
||||||
|
// bind failed, unbind/reset this apqn, return with failure
|
||||||
|
let _ = apqn.set_bind_state(ap::BindState::Unbound);
|
||||||
|
return Err(format!("Failure binding APQN {apqn}: {err}"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
apconfig_done[ci] = true;
|
||||||
|
apqn_done[ai] = true;
|
||||||
|
resolved_entries += 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
ap::ApqnMode::Ep11 => {
|
||||||
|
// EP11 needs bind and associate, but before doing this let's
|
||||||
|
// check out which secret index to use with the associate
|
||||||
|
let se = match secrets.iter().find(|&se| {
|
||||||
|
se.stype() == ListableSecretType::Association
|
||||||
|
&& se.id().len() == uv::AP_ASSOC_SECRET_ID_SIZE
|
||||||
|
&& helper::u8_to_hexstring(se.id()) == apc.secretid
|
||||||
|
}) {
|
||||||
|
None => {
|
||||||
|
eprintln!("Warning: Secret id '{}' from config entry {} not found in UV secrets list.",
|
||||||
|
apc.secretid, cistr);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
Some(se) => se,
|
||||||
|
};
|
||||||
|
// try to bind
|
||||||
|
if ARGS.verbose() || fntest {
|
||||||
|
println!(
|
||||||
|
"Bind APQN {apqn} to match to AP config entry {cistr} (step 1/2)."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if !(ARGS.dryrun() || fntest) {
|
||||||
|
if let Err(err) = apqn.set_bind_state(ap::BindState::Bound) {
|
||||||
|
// bind failed, unbind/reset this apqn, return with failure
|
||||||
|
let _ = apqn.set_bind_state(ap::BindState::Unbound);
|
||||||
|
return Err(format!("Failure binding APQN {}: {}", apqn, err));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// try to associate
|
||||||
|
if ARGS.verbose() || fntest {
|
||||||
|
println!(
|
||||||
|
"Associate APQN {} with uv secrets index {} to match AP config entry {} (step 2/2).",
|
||||||
|
apqn, se.index(), cistr
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if !(ARGS.dryrun() || fntest) {
|
||||||
|
let apas = ap::AssocState::Associated(se.index());
|
||||||
|
apqn.set_associate_state(apas)
|
||||||
|
.map_err(|err| format!("Failure associating APQN {apqn}: {err}"))?;
|
||||||
|
}
|
||||||
|
apconfig_done[ci] = true;
|
||||||
|
apqn_done[ai] = true;
|
||||||
|
resolved_entries += 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
// (currently) unknown/unsupported APQN mode
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(resolved_entries)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// # Panics
|
||||||
|
/// Panics if mingen for an accelerator has not a number as the 4th character.
|
||||||
|
/// Panics if mingen for an ep11 has not a number as the 4th character.
|
||||||
|
/// Please note this can not happen, as mingen is already checked via RE
|
||||||
|
/// during storing the value into mingen.
|
||||||
|
fn config_and_apqn_match(apc: &ApConfigEntry, apqn: &Apqn) -> bool {
|
||||||
|
if apc.mode == config::STR_MODE_ACCEL && apqn.mode == ap::ApqnMode::Accel {
|
||||||
|
// config and apqn are accelerators
|
||||||
|
// maybe check mingen
|
||||||
|
if !apc.mingen.is_empty() {
|
||||||
|
let mingen = &apc.mingen[3..].parse::<u32>().unwrap();
|
||||||
|
if mingen < &apqn.gen {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
} else if apc.mode == config::STR_MODE_EP11 && apqn.mode == ap::ApqnMode::Ep11 {
|
||||||
|
// config and apqn are ep11
|
||||||
|
let info = match &apqn.info {
|
||||||
|
Some(ap::ApqnInfo::Ep11(i)) => i,
|
||||||
|
_ => return false,
|
||||||
|
};
|
||||||
|
// maybe check mingen
|
||||||
|
if !apc.mingen.is_empty() {
|
||||||
|
let mingen = &apc.mingen[3..].parse::<u32>().unwrap();
|
||||||
|
if mingen < &apqn.gen {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// maybe check serialnr
|
||||||
|
if !apc.serialnr.is_empty() && apc.serialnr != info.serialnr {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// check mkvp, currently an ep11 config entry must state an mkvp value
|
||||||
|
// whereas an ep11 info from an APQN may have an empty mkvp value to
|
||||||
|
// indicate that there is no WK set on this APQN.
|
||||||
|
if apc.mkvp != info.mkvp {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
use helper::hexstring_to_u8;
|
||||||
|
use pv_core::uv::SecretEntry;
|
||||||
|
|
||||||
|
// This is more or less only a test for the do_ap_config() function
|
||||||
|
// However, this is THE main functionality of the whole application.
|
||||||
|
|
||||||
|
fn make_test_apqns() -> Vec<Apqn> {
|
||||||
|
vec![
|
||||||
|
ap::Apqn {
|
||||||
|
name: String::from("10.0007"),
|
||||||
|
card: 16,
|
||||||
|
domain: 7,
|
||||||
|
gen: 8,
|
||||||
|
mode: ap::ApqnMode::Accel,
|
||||||
|
info: Option::Some(ap::ApqnInfo::Accel(ap::ApqnInfoAccel {})),
|
||||||
|
},
|
||||||
|
ap::Apqn {
|
||||||
|
name: String::from("11.0008"),
|
||||||
|
card: 17,
|
||||||
|
domain: 8,
|
||||||
|
gen: 8,
|
||||||
|
mode: ap::ApqnMode::Ep11,
|
||||||
|
info: Option::Some(ap::ApqnInfo::Ep11(ap::ApqnInfoEp11 {
|
||||||
|
serialnr: String::from("93AADFK719460083"),
|
||||||
|
mkvp: String::from("db3c3b3c3f097dd55ec7eb0e7fdbcb93"),
|
||||||
|
})),
|
||||||
|
},
|
||||||
|
ap::Apqn {
|
||||||
|
name: String::from("12.0009"),
|
||||||
|
card: 18,
|
||||||
|
domain: 9,
|
||||||
|
gen: 8,
|
||||||
|
mode: ap::ApqnMode::Ep11,
|
||||||
|
info: Option::Some(ap::ApqnInfo::Ep11(ap::ApqnInfoEp11 {
|
||||||
|
serialnr: String::from("93AADHZU42082261"),
|
||||||
|
mkvp: String::from("4a27bb66520ac85f6073a7f678d262c0"),
|
||||||
|
})),
|
||||||
|
},
|
||||||
|
ap::Apqn {
|
||||||
|
name: String::from("12.000a"),
|
||||||
|
card: 18,
|
||||||
|
domain: 10,
|
||||||
|
gen: 8,
|
||||||
|
mode: ap::ApqnMode::Ep11,
|
||||||
|
info: Option::Some(ap::ApqnInfo::Ep11(ap::ApqnInfoEp11 {
|
||||||
|
serialnr: String::from("93AADHZU42082261"),
|
||||||
|
mkvp: String::from("383d2a9ab781f35343554c5b3d9337cd"),
|
||||||
|
})),
|
||||||
|
},
|
||||||
|
ap::Apqn {
|
||||||
|
name: String::from("13.000d"),
|
||||||
|
card: 19,
|
||||||
|
domain: 13,
|
||||||
|
gen: 8,
|
||||||
|
mode: ap::ApqnMode::Ep11,
|
||||||
|
info: Option::Some(ap::ApqnInfo::Ep11(ap::ApqnInfoEp11 {
|
||||||
|
serialnr: String::from("87HU397G150TZGR"),
|
||||||
|
mkvp: String::new(),
|
||||||
|
})),
|
||||||
|
},
|
||||||
|
ap::Apqn {
|
||||||
|
name: String::from("13.000f"),
|
||||||
|
card: 19,
|
||||||
|
domain: 15,
|
||||||
|
gen: 8,
|
||||||
|
mode: ap::ApqnMode::Ep11,
|
||||||
|
info: Option::None,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn make_assoc_secretentry(idx: u16, hexidstr: &str) -> SecretEntry {
|
||||||
|
let id = hexstring_to_u8(hexidstr);
|
||||||
|
let idlen: u32 = id.len().try_into().unwrap();
|
||||||
|
let idarray = <&[u8; 32]>::try_from(id.as_slice()).unwrap();
|
||||||
|
SecretEntry::new(idx, ListableSecretType::Association, *idarray, idlen)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn make_test_secrets() -> Vec<SecretEntry> {
|
||||||
|
vec![
|
||||||
|
make_assoc_secretentry(
|
||||||
|
33,
|
||||||
|
"3333333333333333333333333333333333333333333333333333333333333333",
|
||||||
|
),
|
||||||
|
make_assoc_secretentry(
|
||||||
|
13,
|
||||||
|
"bc9d46c052bc3574454c5715757274629a283767ed237922cfb8651c0e77320a",
|
||||||
|
),
|
||||||
|
make_assoc_secretentry(
|
||||||
|
44,
|
||||||
|
"4444444444444444444444444444444444444444444444444444444444444444",
|
||||||
|
),
|
||||||
|
make_assoc_secretentry(
|
||||||
|
15,
|
||||||
|
"06cdbbac76a595b481110d108154bc05ebbf900a0f16e36a24045998934fb1e9",
|
||||||
|
),
|
||||||
|
make_assoc_secretentry(
|
||||||
|
17,
|
||||||
|
"6831af07f8c8e7309a3ace9f3b5554d34e3eaa4a27a08fdee469e367c3fa3e9e",
|
||||||
|
),
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn make_test_apconfigs() -> Vec<ApConfigEntry> {
|
||||||
|
vec![
|
||||||
|
config::ApConfigEntry {
|
||||||
|
name: String::from("test_1"),
|
||||||
|
description: String::from("test_1"),
|
||||||
|
mode: String::from("accel"),
|
||||||
|
mkvp: String::from(""),
|
||||||
|
serialnr: String::from(""),
|
||||||
|
mingen: String::from("cex8"),
|
||||||
|
secretid: String::from(""),
|
||||||
|
},
|
||||||
|
config::ApConfigEntry {
|
||||||
|
name: String::from("test_2"),
|
||||||
|
description: String::from("test_2"),
|
||||||
|
mode: String::from("ep11"),
|
||||||
|
mkvp: String::from("db3c3b3c3f097dd55ec7eb0e7fdbcb93"),
|
||||||
|
serialnr: String::from("93AADFK719460083"),
|
||||||
|
mingen: String::from("cex8"),
|
||||||
|
secretid: String::from(
|
||||||
|
"bc9d46c052bc3574454c5715757274629a283767ed237922cfb8651c0e77320a",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
config::ApConfigEntry {
|
||||||
|
name: String::from("test_3"),
|
||||||
|
description: String::from("test_3"),
|
||||||
|
mode: String::from("ep11"),
|
||||||
|
mkvp: String::from("4a27bb66520ac85f6073a7f678d262c0"),
|
||||||
|
serialnr: String::from(""),
|
||||||
|
mingen: String::from("cex8"),
|
||||||
|
secretid: String::from(
|
||||||
|
"06cdbbac76a595b481110d108154bc05ebbf900a0f16e36a24045998934fb1e9",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
config::ApConfigEntry {
|
||||||
|
name: String::from("test_4"),
|
||||||
|
description: String::from("test_4"),
|
||||||
|
mode: String::from("ep11"),
|
||||||
|
mkvp: String::from("8be1eaf5c44e2fa8b18804551b604b1b"),
|
||||||
|
serialnr: String::from(""),
|
||||||
|
mingen: String::from("cex8"),
|
||||||
|
secretid: String::from(
|
||||||
|
"6831af07f8c8e7309a3ace9f3b5554d34e3eaa4a27a08fdee469e367c3fa3e9e",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_do_ap_config_invocation_1() {
|
||||||
|
let test_apqns = make_test_apqns();
|
||||||
|
let mut apqns: Vec<Apqn> = Vec::new();
|
||||||
|
apqns.push(test_apqns[0].clone());
|
||||||
|
let secrets: Vec<SecretEntry> = Vec::new();
|
||||||
|
let secretlist = SecretList::new(secrets.len() as u16, secrets);
|
||||||
|
let test_apconfigs = make_test_apconfigs();
|
||||||
|
let mut apconfig: Vec<ApConfigEntry> = Vec::new();
|
||||||
|
apconfig.push(test_apconfigs[0].clone());
|
||||||
|
let apcfglist = ApConfigList::from_apconfigentry_vec(apconfig);
|
||||||
|
let mut apqnlist = ApqnList::from_apqn_vec(apqns);
|
||||||
|
let r = do_ap_config(&mut apqnlist, &secretlist, &apcfglist, true);
|
||||||
|
assert!(r.is_ok());
|
||||||
|
let n = r.unwrap();
|
||||||
|
assert!(n == 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_do_ap_config_invocation_2() {
|
||||||
|
let test_apqns = make_test_apqns();
|
||||||
|
let mut apqns: Vec<Apqn> = Vec::new();
|
||||||
|
apqns.push(test_apqns[1].clone());
|
||||||
|
let mut secrets = make_test_secrets();
|
||||||
|
while secrets.len() > 2 {
|
||||||
|
secrets.pop();
|
||||||
|
}
|
||||||
|
let secretlist = SecretList::new(secrets.len() as u16, secrets);
|
||||||
|
let test_apconfigs = make_test_apconfigs();
|
||||||
|
let mut apconfig: Vec<ApConfigEntry> = Vec::new();
|
||||||
|
apconfig.push(test_apconfigs[1].clone());
|
||||||
|
let apcfglist = ApConfigList::from_apconfigentry_vec(apconfig);
|
||||||
|
let mut apqnlist = ApqnList::from_apqn_vec(apqns);
|
||||||
|
let r = do_ap_config(&mut apqnlist, &secretlist, &apcfglist, true);
|
||||||
|
assert!(r.is_ok());
|
||||||
|
let n = r.unwrap();
|
||||||
|
assert!(n == 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_do_ap_config_invocation_3() {
|
||||||
|
let test_apqns = make_test_apqns();
|
||||||
|
let mut apqns: Vec<Apqn> = Vec::new();
|
||||||
|
for a in test_apqns.iter() {
|
||||||
|
apqns.push(a.clone());
|
||||||
|
}
|
||||||
|
apqns.reverse();
|
||||||
|
let secrets = make_test_secrets();
|
||||||
|
let secretlist = SecretList::new(secrets.len() as u16, secrets);
|
||||||
|
let test_apconfigs = make_test_apconfigs();
|
||||||
|
let mut apconfig: Vec<ApConfigEntry> = Vec::new();
|
||||||
|
for c in test_apconfigs.iter() {
|
||||||
|
apconfig.push(c.clone());
|
||||||
|
}
|
||||||
|
let apcfglist = ApConfigList::from_apconfigentry_vec(apconfig);
|
||||||
|
let mut apqnlist = ApqnList::from_apqn_vec(apqns);
|
||||||
|
let r = do_ap_config(&mut apqnlist, &secretlist, &apcfglist, true);
|
||||||
|
assert!(r.is_ok());
|
||||||
|
let n = r.unwrap();
|
||||||
|
assert!(n == 3, "n = {} != 3", n);
|
||||||
|
}
|
||||||
|
}
|
||||||
105
rust/pvapconfig/src/uv.rs
Normal file
105
rust/pvapconfig/src/uv.rs
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
//
|
||||||
|
// Copyright IBM Corp. 2023
|
||||||
|
//
|
||||||
|
//! UV related functions for pvapconfig
|
||||||
|
//
|
||||||
|
|
||||||
|
use pv_core::uv::{ListCmd, SecretList, UvDevice, UvcSuccess};
|
||||||
|
use regex::Regex;
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
|
/// The byte size of association secret of type 2 in struct SecretEntry
|
||||||
|
pub const AP_ASSOC_SECRET_ID_SIZE: usize = 32;
|
||||||
|
|
||||||
|
const PATH_SYS_FW_UV_FACILITIES: &str = "/sys/firmware/uv/query/facilities";
|
||||||
|
|
||||||
|
const RE_UV_FACILITIES: &str = r"^(0x)?([[:xdigit:]]+)";
|
||||||
|
const RE_UV_FAC_BIT_LIST_SECRETS: u32 = 30;
|
||||||
|
|
||||||
|
/// Check UV facilities to offer the 'list secrets' call.
|
||||||
|
/// Returns a Result with Ok(()) if the 'list secrets' feature
|
||||||
|
/// is available, otherwise an Err(reasonstring) is returned where
|
||||||
|
/// the string denotes a hint which can be displayed.
|
||||||
|
/// # Panics
|
||||||
|
/// Panics if the compilation of a static regular expression fails.
|
||||||
|
/// Panics if RE_UV_FACILITIES does not match.
|
||||||
|
pub fn has_list_secrets_facility() -> Result<(), String> {
|
||||||
|
if !Path::new(PATH_SYS_FW_UV_FACILITIES).is_file() {
|
||||||
|
return Err(format!(
|
||||||
|
"UV facilities sysfs attribute not found (file {} does not exist).",
|
||||||
|
PATH_SYS_FW_UV_FACILITIES
|
||||||
|
));
|
||||||
|
}
|
||||||
|
let facstr = match crate::helper::sysfs_read_string(PATH_SYS_FW_UV_FACILITIES) {
|
||||||
|
Ok(s) => s,
|
||||||
|
Err(err) => {
|
||||||
|
return Err(format!(
|
||||||
|
"Failure reading UV facilities from {PATH_SYS_FW_UV_FACILITIES} ({:?}).",
|
||||||
|
err
|
||||||
|
))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let re_uv_facilities = Regex::new(RE_UV_FACILITIES).unwrap();
|
||||||
|
if !re_uv_facilities.is_match(&facstr) {
|
||||||
|
Err(format!("Failure parsing UV facilities entry '{facstr}'."))
|
||||||
|
} else {
|
||||||
|
let caps = re_uv_facilities.captures(&facstr).unwrap();
|
||||||
|
let fachex = caps.get(2).unwrap().as_str();
|
||||||
|
let i: usize = RE_UV_FAC_BIT_LIST_SECRETS as usize / 4;
|
||||||
|
if i >= fachex.len() {
|
||||||
|
return Err(format!("Failure parsing UV facilities entry '{fachex}'."));
|
||||||
|
}
|
||||||
|
let nibble = u32::from_str_radix(&fachex[i..i + 1], 16).unwrap();
|
||||||
|
const THEBIT: u32 = 1 << (3 - (RE_UV_FAC_BIT_LIST_SECRETS % 4));
|
||||||
|
if nibble & THEBIT == 0 {
|
||||||
|
return Err("The 'list secret' feature is missing on this UV.".to_string());
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Fetch the list of secrets from the UV.
|
||||||
|
/// Returns Err(errorstring) on error or
|
||||||
|
/// Ok(SecretList) on success.
|
||||||
|
/// The list may be empty if the UV doesn't have any secrets stored.
|
||||||
|
pub fn gather_secrets() -> Result<SecretList, String> {
|
||||||
|
let uv = match UvDevice::open() {
|
||||||
|
Err(e) => return Err(format!("Failed to open UV device: {:?}.", e)),
|
||||||
|
Ok(u) => u,
|
||||||
|
};
|
||||||
|
let mut cmd = ListCmd::default();
|
||||||
|
match uv.send_cmd(&mut cmd).map_err(|e| format!("{e:?}"))? {
|
||||||
|
UvcSuccess::RC_SUCCESS => (),
|
||||||
|
UvcSuccess::RC_MORE_DATA => println!("Warning: There is more data available than expected"),
|
||||||
|
};
|
||||||
|
cmd.try_into().map_err(|e| format!("{e:?}"))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
// As the name says: check for list secrets feature bit in UV facilities.
|
||||||
|
#[test]
|
||||||
|
fn test_has_list_secrets_facility() {
|
||||||
|
let r = has_list_secrets_facility();
|
||||||
|
if pv_core::misc::pv_guest_bit_set() {
|
||||||
|
assert!(r.is_ok());
|
||||||
|
} else {
|
||||||
|
assert!(r.is_err());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Simple invocation of the list_secrets function. Should not fail
|
||||||
|
#[test]
|
||||||
|
fn test_list_secrets() {
|
||||||
|
let r = gather_secrets();
|
||||||
|
if pv_core::misc::pv_guest_bit_set() {
|
||||||
|
assert!(r.is_ok());
|
||||||
|
} else {
|
||||||
|
assert!(r.is_err());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,5 +10,5 @@ clap = { version ="4", features = ["derive", "wrap_help"]}
|
|||||||
log = { version = "0.4.6", features = ["std", "release_max_level_debug"] }
|
log = { version = "0.4.6", features = ["std", "release_max_level_debug"] }
|
||||||
serde_yaml = "0.9"
|
serde_yaml = "0.9"
|
||||||
|
|
||||||
pv = { path = "../pv", features = ["uvsecret", "request"] }
|
pv = { path = "../pv" }
|
||||||
utils = { path = "../utils" }
|
utils = { path = "../utils" }
|
||||||
|
|||||||
@@ -4,11 +4,13 @@ s390-tools is free software; you can redistribute it and/or modify
|
|||||||
it under the terms of the MIT license. See LICENSE for details.
|
it under the terms of the MIT license. See LICENSE for details.
|
||||||
-->
|
-->
|
||||||
# pvsecret
|
# pvsecret
|
||||||
|
## Synopsis
|
||||||
|
`pvsecret [OPTIONS] <COMMAND>`
|
||||||
## Description
|
## Description
|
||||||
Use **pvsecret** to manage secrets for IBM Secure Execution guests. **pvsecret**
|
Use **pvsecret** to manage secrets for IBM Secure Execution guests. **pvsecret**
|
||||||
can **create** add-secret requests on any architecture. On s390x systems, use
|
can **create** add-secret requests on any architecture. On s390x systems, use
|
||||||
**pvsecret** to **add** the secrets to the ultravisor secret store, **list** all
|
**pvsecret** to **add** the secrets to the ultravisor secret store, **list** all
|
||||||
secrets in the secret store, or lock the secret store to prevent any
|
secrets in the secret store, or **lock** the secret store to prevent any
|
||||||
modifications in the future.
|
modifications in the future.
|
||||||
|
|
||||||
The ultravisor secret store stores secrets for the IBM Secure Execution guest.
|
The ultravisor secret store stores secrets for the IBM Secure Execution guest.
|
||||||
@@ -18,193 +20,247 @@ Create requests only on trusted systems that are not the IBM Secure Execution
|
|||||||
guest where you want to inject the secrets. This approach prevents the secrets
|
guest where you want to inject the secrets. This approach prevents the secrets
|
||||||
from being in cleartext on the guest. For extra safety, do an attestation with
|
from being in cleartext on the guest. For extra safety, do an attestation with
|
||||||
**pvattest** of your guest beforehand, and include the configuration UID in the
|
**pvattest** of your guest beforehand, and include the configuration UID in the
|
||||||
secret request using **--cuid**. Refer to **pvsecret-add**(1) for more
|
secret request using **--cuid**. Refer to **pvsecret-add** for more information.
|
||||||
information. For all certificates, revocation lists, and host-key documents,
|
For all certificates, revocation lists, and host-key documents, both the PEM and
|
||||||
both the PEM and DER input formats are supported.
|
DER input formats are supported.
|
||||||
|
|
||||||
|
|
||||||
## Synopsis
|
|
||||||
`pvsecret [OPTIONS] <COMMAND>`
|
|
||||||
## Commands Overview
|
## Commands Overview
|
||||||
- **create**
|
- **create**
|
||||||
Create a new add-secret request
|
<ul>
|
||||||
- **add**
|
Create a new add-secret request
|
||||||
Repeat an add-secret request (s390x only)
|
</ul>
|
||||||
- **lock**
|
|
||||||
Lock the secret-store (s390x only)
|
- **add**
|
||||||
- **list**
|
<ul>
|
||||||
List all ultravisor secrets (s390x only)
|
Perform an add-secret request (s390x only)
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
- **lock**
|
||||||
|
<ul>
|
||||||
|
Lock the secret-store (s390x only)
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
- **list**
|
||||||
|
<ul>
|
||||||
|
List all ultravisor secrets (s390x only)
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
- **verify**
|
||||||
|
<ul>
|
||||||
|
Verify that an add-secret request is sane
|
||||||
|
</ul>
|
||||||
|
|
||||||
## Options
|
## Options
|
||||||
|
|
||||||
`-v`, `--verbose`
|
`-v`, `--verbose`
|
||||||
<ul>
|
<ul>
|
||||||
Provide more detailed output
|
Provide more detailed output
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
|
|
||||||
`--version`
|
`--version`
|
||||||
<ul>
|
<ul>
|
||||||
Print version information and exit
|
Print version information and exit
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
|
|
||||||
## pvsecret create
|
## pvsecret create
|
||||||
|
### Synopsis
|
||||||
|
`pvsecret create [OPTIONS] --host-key-document <FILE> --hdr <FILE> --output <FILE> <--no-verify|--cert <FILE>> <COMMAND>`
|
||||||
### Description
|
### Description
|
||||||
Create add-secret requests for IBM Secure Execution guests. Only create these
|
Create add-secret requests for IBM Secure Execution guests. Only create these
|
||||||
requests in a trusted environment, such as your workstation. The **pvattest
|
requests in a trusted environment, such as your workstation. The **pvattest
|
||||||
create** command creates a randomly generated key to protect the request. The
|
create** command creates a randomly generated key to protect the request. The
|
||||||
generated requests can then be added on an IBM Secure Execution guest using
|
generated requests can then be added on an IBM Secure Execution guest using
|
||||||
**pvsecret add**. The guest can then use the secrets with the use case
|
**pvsecret add**. The guest can then use the secrets with the use case depending
|
||||||
depending on the secret type.
|
on the secret type.
|
||||||
|
|
||||||
Such a request is bound to a specific IBM Secure Execution image specified with
|
Such a request is bound to a specific IBM Secure Execution image specified with
|
||||||
**--hdr**. Optionally, the request can be bound to a specific instance when
|
**--hdr**. Optionally, the request can be bound to a specific instance when
|
||||||
bound to the Configuration Unique ID from **pvattest** using **--cuid**
|
bound to the Configuration Unique ID from **pvattest** using **--cuid**
|
||||||
|
|
||||||
|
|
||||||
### Synopsis
|
|
||||||
`pvsecret create [OPTIONS] --host-key-document <FILE> --hdr <FILE> --output <FILE> <--no-verify|--cert <FILE>> <COMMAND>`
|
|
||||||
### Commands Overview
|
### Commands Overview
|
||||||
- **meta**
|
- **meta**
|
||||||
Use a meta secret to carry flags to the ultravisor without having to provide
|
<ul>
|
||||||
an actual secret value. Meta secrets do not appear in the list of secrets
|
Create a meta secret
|
||||||
- **association**
|
</ul>
|
||||||
Use an association secret to connect a trusted I/O device to a guest. The
|
|
||||||
`pvapconfig` tool provides more information about association secrets
|
- **association**
|
||||||
|
<ul>
|
||||||
|
Create an association secret
|
||||||
|
</ul>
|
||||||
|
|
||||||
### Options
|
### Options
|
||||||
|
|
||||||
`-k`, `--host-key-document <FILE>`
|
`-k`, `--host-key-document <FILE>`
|
||||||
<ul>
|
<ul>
|
||||||
Use FILE as a host-key document. Can be specified multiple times and must be
|
Use FILE as a host-key document. Can be specified multiple times and must be
|
||||||
used at least once.
|
used at least once.
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
|
|
||||||
`--no-verify`
|
`--no-verify`
|
||||||
<ul>
|
<ul>
|
||||||
Disable the host-key document verification. Does not require the host-key
|
Disable the host-key document verification. Does not require the host-key
|
||||||
documents to be valid. Do not use for a production request unless you
|
documents to be valid. Do not use for a production request unless you verified
|
||||||
verified the host-key document beforehand.
|
the host-key document beforehand.
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
|
|
||||||
`-C`, `--cert <FILE>`
|
`-C`, `--cert <FILE>`
|
||||||
<ul>
|
<ul>
|
||||||
Use FILE as a certificate to verify the host key or keys. The certificates
|
Use FILE as a certificate to verify the host key or keys. The certificates are
|
||||||
are used to establish a chain of trust for the verification of the host-key
|
used to establish a chain of trust for the verification of the host-key
|
||||||
documents. Specify this option twice to specify the IBM Z signing key and
|
documents. Specify this option twice to specify the IBM Z signing key and the
|
||||||
the intermediate CA certificate (signed by the root CA).
|
intermediate CA certificate (signed by the root CA).
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
|
|
||||||
`--crl <FILE>`
|
`--crl <FILE>`
|
||||||
<ul>
|
<ul>
|
||||||
Use FILE as a certificate revocation list. The list is used to check whether
|
Use FILE as a certificate revocation list. The list is used to check whether a
|
||||||
a certificate of the chain of trust is revoked. Specify this option multiple
|
certificate of the chain of trust is revoked. Specify this option multiple times
|
||||||
times to use multiple CRLs.
|
to use multiple CRLs.
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
|
|
||||||
`--offline`
|
`--offline`
|
||||||
<ul>
|
<ul>
|
||||||
Make no attempt to download CRLs
|
Make no attempt to download CRLs
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
|
|
||||||
`--root-ca <ROOT_CA>`
|
`--root-ca <ROOT_CA>`
|
||||||
<ul>
|
<ul>
|
||||||
Use FILE as the root-CA certificate for the verification. If omitted, the
|
Use FILE as the root-CA certificate for the verification. If omitted, the system
|
||||||
system-wide root CAs installed on the system are used. Use this only if you
|
wide-root CAs installed on the system are used. Use this only if you trust the
|
||||||
trust the specified certificate.
|
specified certificate.
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
|
|
||||||
`--hdr <FILE>`
|
`--hdr <FILE>`
|
||||||
<ul>
|
<ul>
|
||||||
Specifies the header of the guest image. Can be an IBM Secure Execution
|
Specifies the header of the guest image. Can be an IBM Secure Execution image
|
||||||
image created by genprotimg or an extracted IBM Secure Execution header. The
|
created by genprotimg or an extracted IBM Secure Execution header. The header
|
||||||
header must start at a page boundary.
|
must start at a page boundary.
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
|
|
||||||
`-f`, `--force`
|
`-f`, `--force`
|
||||||
<ul>
|
<ul>
|
||||||
Force the generation of add-secret requests on IBM Secure Execution guests.
|
Force the generation of add-secret requests on IBM Secure Execution guests. If
|
||||||
If the program detects that it is running on an IBM Secure Execution guest,
|
the program detects that it is running on an IBM Secure Execution guest, it
|
||||||
it denies the generation of add-secret requests. The force flag overwrites
|
denies the generation of add-secret requests. The force flag overwrites this
|
||||||
this behavior.
|
behavior.
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
|
|
||||||
`-o`, `--output <FILE>`
|
`-o`, `--output <FILE>`
|
||||||
<ul>
|
<ul>
|
||||||
Write the generated request to FILE
|
Write the generated request to FILE
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
|
|
||||||
`--extension-secret <FILE>`
|
`--extension-secret <FILE>`
|
||||||
<ul>
|
<ul>
|
||||||
Use the content of FILE as an extension secret. The file must be exactly 32
|
Use the content of FILE as an extension secret. The file must be exactly 32
|
||||||
bytes long. If this request is the first, all subsequent requests must have
|
bytes long. If this request is the first, all subsequent requests must have the
|
||||||
the same extension secret. Only makes sense if bit 1 of the secret control
|
same extension secret. Only makes sense if bit 1 of the secret control flags of
|
||||||
flags of the IBM Secure Execution header is 0. Otherwise the ultravisor
|
the IBM Secure Execution header is 0. Otherwise the ultravisor rejects the
|
||||||
rejects the request.
|
request.
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
|
|
||||||
`--cck <FILE>`
|
`--cck <FILE>`
|
||||||
<ul>
|
<ul>
|
||||||
Use the content of FILE as the customer-communication key (CCK) to derive
|
Use the content of FILE as the customer-communication key (CCK) to derive the
|
||||||
the extension secret. The file must contain exactly 32 bytes of data. If the
|
extension secret. The file must contain exactly 32 bytes of data. If the target
|
||||||
target guest was started with bit 1 of the secret control flag set, the
|
guest was started with bit 1 of the secret control flag set, the ultravisor also
|
||||||
ultravisor also derives the secret from the CCK. Otherwise, the ultravisor
|
derives the secret from the CCK. Otherwise, the ultravisor interprets the
|
||||||
interprets the extension secret as a normal one. This still works if you
|
extension secret as a normal one. This still works if you use the same CCK for
|
||||||
use the same CCK for all requests.
|
all requests.
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
|
|
||||||
`--cuid-hex <HEXSTRING>`
|
`--cuid-hex <HEXSTRING>`
|
||||||
<ul>
|
<ul>
|
||||||
Use HEXSTRING as the Configuration Unique ID. Must be a hex 128-bit unsigned
|
Use HEXSTRING as the Configuration Unique ID. Must be a hex 128-bit unsigned big
|
||||||
big endian number string. Leading zeros must be provided. If specified, the
|
endian number string. Leading zeros must be provided. If specified, the value
|
||||||
value must match with the Config-UID from the attestation result of that
|
must match with the Config-UID from the attestation result of that guest. If not
|
||||||
guest. If not specified, the CUID will be ignored by the ultravisor during
|
specified, the CUID will be ignored by the ultravisor during the verification of
|
||||||
the verification of the request.
|
the request.
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
|
|
||||||
`--cuid <FILE>`
|
`--cuid <FILE>`
|
||||||
<ul>
|
<ul>
|
||||||
Use the content of FILE as the Configuration Unique ID. The file must
|
Use the content of FILE as the Configuration Unique ID. The file must contain
|
||||||
contain exactly 128 bit of data, a hex string, or a yaml with a `cuid`
|
exactly 128 bit of data or a yaml with a `cuid` entry. If specified, the value
|
||||||
entry. If specified, the value must match the Config-UID from the
|
must match the Config-UID from the attestation result of that guest. If not
|
||||||
attestation result of that guest. If not specified, the CUID will be ignored
|
specified, the CUID will be ignored by the Ultravisor during the verification
|
||||||
by the Ultravisor during the verification of the request.
|
of the request.
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
|
|
||||||
`--flags <FLAGS>`
|
`--flags <FLAGS>`
|
||||||
<ul>
|
<ul>
|
||||||
Flags for the add-secret request.
|
Flags for the add-secret request
|
||||||
|
|
||||||
Possible values:
|
Possible values:
|
||||||
- disable-dump: Disables host-initiated dumping for the target guest
|
- **disable-dump**: Disables host-initiated dumping for the target guest instance
|
||||||
instance
|
</ul>
|
||||||
|
|
||||||
|
|
||||||
|
`--user-data <FILE>`
|
||||||
|
<ul>
|
||||||
|
Use the content of FILE as user-data. Passes user data defined in <FILE> through
|
||||||
|
the add-secret request to the ultravisor. The user data can be up to 512 bytes
|
||||||
|
of arbitrary data, and the maximum size depends on the size of the user-signing
|
||||||
|
key:
|
||||||
|
|
||||||
|
- No key: user data can be 512 bytes.
|
||||||
|
|
||||||
|
- EC(secp521r1) or RSA 2048 keys: user data can be 256 bytes.
|
||||||
|
|
||||||
|
- RSA 3072 key: user data can be 128 bytes.
|
||||||
|
|
||||||
|
The firmware ignores this data, but the request tag protects the user-data.
|
||||||
|
Optional. No user-data by default.
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
|
||||||
|
`--user-sign-key <FILE>`
|
||||||
|
<ul>
|
||||||
|
Use the content of FILE as user signing key. Adds a signature defined calculated
|
||||||
|
from the key in <FILE> to the add-secret request. The file must be in DER or PEM
|
||||||
|
format containing a private key. Supported are RSA 2048 & 3072-bit and
|
||||||
|
EC(secp521r1) keys. The firmware ignores the content, but the request tag
|
||||||
|
protects the signature. The user-signing key signs the request. The location of
|
||||||
|
the signature is filled with zeros during the signature calculation. The request
|
||||||
|
tag also secures the signature. See man pvsecret verify for more details.
|
||||||
|
Optional. No signature by default.
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
|
|
||||||
### pvsecret create meta
|
### pvsecret create meta
|
||||||
#### Description
|
|
||||||
Use a meta secret to carry flags to the ultravisor without having to provide an
|
|
||||||
actual secret value. Meta secrets do not appear in the list of secrets.
|
|
||||||
|
|
||||||
|
|
||||||
#### Synopsis
|
#### Synopsis
|
||||||
`pvsecret create meta`
|
`pvsecret create meta`
|
||||||
|
#### Description
|
||||||
|
Create a meta secret. Use a meta secret to carry flags to the ultravisor without
|
||||||
|
having to provide an actual secret value. Meta secrets do not appear in the list
|
||||||
|
of secrets.
|
||||||
|
|
||||||
### pvsecret create association
|
### pvsecret create association
|
||||||
#### Description
|
|
||||||
Use an association secret to connect a trusted I/O device to a guest. The
|
|
||||||
`pvapconfig` tool provides more information about association secrets.
|
|
||||||
|
|
||||||
#### Synopsis
|
#### Synopsis
|
||||||
`pvsecret create association [OPTIONS] <NAME>`
|
`pvsecret create association [OPTIONS] <NAME>`
|
||||||
|
#### Description
|
||||||
|
Create an association secret. Use an association secret to connect a trusted I/O
|
||||||
|
device to a guest. The `pvapconfig` tool provides more information about
|
||||||
|
association secrets.
|
||||||
#### Arguments
|
#### Arguments
|
||||||
|
|
||||||
`<NAME>`
|
`<NAME>`
|
||||||
<ul>
|
<ul>
|
||||||
String to identify the new secret. The actual secret is set with
|
String to identify the new secret. The actual secret is set with --input-secret.
|
||||||
--input-secret. The name is saved in `NAME.yaml` with white-spaces
|
The name is saved in `NAME.yaml` with white-spaces mapped to `_`.
|
||||||
mapped to `_`.
|
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
|
|
||||||
@@ -212,73 +268,142 @@ Use an association secret to connect a trusted I/O device to a guest. The
|
|||||||
|
|
||||||
`--stdout`
|
`--stdout`
|
||||||
<ul>
|
<ul>
|
||||||
Print the hashed name to stdout. The hashed name will not be written to
|
Print the hashed name to stdout. The hashed name is not written to `NAME.yaml`
|
||||||
`NAME.yaml`
|
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
|
|
||||||
`--input-secret <FILE>`
|
`--input-secret <FILE>`
|
||||||
<ul>
|
<ul>
|
||||||
Path from which to read the plaintext secret. Uses a random secret if not
|
Path from which to read the plaintext secret. Uses a random secret if not
|
||||||
specified.
|
specified
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
|
|
||||||
`--output-secret <FILE>`
|
`--output-secret <FILE>`
|
||||||
<ul>
|
<ul>
|
||||||
Save the generated secret as plaintext in FILE. The generated secret can be
|
Save the generated secret as plaintext in FILE. The generated secret can be used
|
||||||
used to generate add-secret requests for a different guest with the same
|
to generate add-secret requests for a different guest with the same secret using
|
||||||
secret using --input-secret. Destroy the secret when it is not used
|
--input-secret. Destroy the secret when it is not used anymore.
|
||||||
anymore.
|
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
|
|
||||||
## pvsecret add
|
## pvsecret add
|
||||||
### Description
|
|
||||||
Perform an add-secret request using a previously generated add-secret request.
|
|
||||||
Only available on s390x.
|
|
||||||
|
|
||||||
|
|
||||||
### Synopsis
|
### Synopsis
|
||||||
`pvsecret add <FILE>`
|
`pvsecret add <FILE>`
|
||||||
|
### Description
|
||||||
|
Perform an add-secret request (s390x only). Perform an add-secret request using
|
||||||
|
a previously generated add-secret request. Only available on s390x.
|
||||||
### Arguments
|
### Arguments
|
||||||
|
|
||||||
`<FILE>`
|
`<FILE>`
|
||||||
<ul>
|
<ul>
|
||||||
Specify the request to be sent
|
Specify the request to be sent
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## pvsecret lock
|
## pvsecret lock
|
||||||
### Description
|
|
||||||
Lock the secret store (s390x only). After this command executed successfully,
|
|
||||||
all add-secret requests will fail. Only available on s390x.
|
|
||||||
|
|
||||||
|
|
||||||
### Synopsis
|
### Synopsis
|
||||||
`pvsecret lock`
|
`pvsecret lock`
|
||||||
|
### Description
|
||||||
|
Lock the secret-store (s390x only). Lock the secret store (s390x only). After
|
||||||
|
this command executed successfully, all add-secret requests will fail. Only
|
||||||
|
available on s390x.
|
||||||
|
|
||||||
## pvsecret list
|
## pvsecret list
|
||||||
### Description
|
|
||||||
Lists the IDs of all non-null secrets currently stored in the ultravisor for the
|
|
||||||
currently running IBM Secure Execution guest. Only available on s390x.
|
|
||||||
|
|
||||||
### Synopsis
|
### Synopsis
|
||||||
`pvsecret list [OPTIONS] [FILE]`
|
`pvsecret list [OPTIONS] [FILE]`
|
||||||
|
### Description
|
||||||
|
List all ultravisor secrets (s390x only). Lists the IDs of all non-null secrets
|
||||||
|
currently stored in the ultravisor for the currently running IBM Secure
|
||||||
|
Execution guest. Only available on s390x.
|
||||||
### Arguments
|
### Arguments
|
||||||
|
|
||||||
`<FILE>`
|
`<FILE>`
|
||||||
<ul>
|
<ul>
|
||||||
Store the result in FILE. Default value: '-'
|
Store the result in FILE
|
||||||
|
Default value: '-'
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
|
|
||||||
### Options
|
### Options
|
||||||
|
|
||||||
`--format <FORMAT>`
|
`--format <FORMAT>`
|
||||||
Define the output format of the list. Default value: 'human'
|
<ul>
|
||||||
|
Define the output format of the list
|
||||||
|
Default value: 'human'
|
||||||
Possible values:
|
Possible values:
|
||||||
- human: Human-focused, non-parsable output format
|
- **human**: Human-focused, non-parsable output format
|
||||||
- yaml: Use yaml format
|
- **yaml**: Use yaml format
|
||||||
- bin: Use the format the ultravisor uses to pass the list
|
- **bin**: Use the format the ultravisor uses to pass the list
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
|
||||||
|
## pvsecret verify
|
||||||
|
### Synopsis
|
||||||
|
`pvsecret verify [OPTIONS] <FILE>`
|
||||||
|
### Description
|
||||||
|
Verifies that the given request is an Add-Secret request by testing for some
|
||||||
|
values to be present. If the request contains signed user-data, the signature
|
||||||
|
is verified with the provided key. Outputs the arbitrary user-data. All data in
|
||||||
|
the request is in big endian. `verify` checks the following:
|
||||||
|
|
||||||
|
- The first 6 bytes of the request are equal to: `B6173 7263 624d | asrcbM`
|
||||||
|
- The sizes in the request header are sane and do not point out of the file
|
||||||
|
- The request version is supported by the binary
|
||||||
|
- If user-data contains a signature, verify the signature using a public key
|
||||||
|
|
||||||
|
The content of bytes 6&7 of the request define which kind of user-data the
|
||||||
|
request contains.
|
||||||
|
- **0x0000** `no user-data (512 bytes zero)`
|
||||||
|
- **0x0001** `512 bytes user-data`
|
||||||
|
- **0x0002** `265 bytes user-data| 139 bytes ecdsa signature | 5 bytes reserved
|
||||||
|
| 2 bytes signature size | ...`
|
||||||
|
- **0x0003** `256 bytes user-data | 256 bytes rsa2048 signature`
|
||||||
|
- **0x0004** `128 bytes user-data | 384 bytes rsa3072 signature`
|
||||||
|
|
||||||
|
The actual user-data may be less than the capacity. If less data was provided
|
||||||
|
during `create` zeros are appended.
|
||||||
|
For type 2-4 The signature is calculated as follows:
|
||||||
|
1) The request is generated with the user-data in place and zeros for the
|
||||||
|
signature data.
|
||||||
|
2) The signature is calculated for the request. The signature signs the
|
||||||
|
authenticated data and the encrypted data, but not the request tag. I.e. the
|
||||||
|
signature signs the whole request but the last 16 bytes a,d with the signature
|
||||||
|
bytes set to zero.
|
||||||
|
3) The signature is inserted to its location in the request.
|
||||||
|
4) The request GCM tag is calculated.
|
||||||
|
|
||||||
|
The verification process works as follows:
|
||||||
|
1) copy the signature to a buffer
|
||||||
|
2) overwrite the signature with zeros
|
||||||
|
3) verify the signature of the request but the last 16 bytes
|
||||||
|
|
||||||
|
### Arguments
|
||||||
|
|
||||||
|
`<FILE>`
|
||||||
|
<ul>
|
||||||
|
Specify the request to be checked
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
|
||||||
|
### Options
|
||||||
|
|
||||||
|
`--user-cert <FILE>`
|
||||||
|
<ul>
|
||||||
|
Certificate containing a public key used to verify the user data signature.
|
||||||
|
Specifies a public key used to verify the user-data signature. The file must be
|
||||||
|
a X509 certificate in DSA or PEM format. The certificate must hold the public
|
||||||
|
EC, RSA 2048, or RSA 3072 key corresponding to the private user-key used during
|
||||||
|
`create`. No chain of trust is established. Ensuring that the certificate can be
|
||||||
|
trusted is the responsibility of the user. The EC key must use the NIST/SECG
|
||||||
|
curve over a 521 bit prime field (secp521r1).
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
|
||||||
|
`-o`, `--output <FILE>`
|
||||||
|
<ul>
|
||||||
|
Store the result in FILE If the request contained abirtary user-data the output
|
||||||
|
contains this user-data with padded zeros if available.
|
||||||
|
Default value: '-'
|
||||||
|
</ul>
|
||||||
|
|||||||
@@ -3,11 +3,11 @@
|
|||||||
.\" it under the terms of the MIT license. See LICENSE for details.
|
.\" it under the terms of the MIT license. See LICENSE for details.
|
||||||
.\"
|
.\"
|
||||||
|
|
||||||
.TH pvsecret-add 1 "2023-07-28" "s390-tools" "UV-Secret Manual"
|
.TH pvsecret-add 1 "2024-01-30" "s390-tools" "UV-Secret Manual"
|
||||||
.nh
|
.nh
|
||||||
.ad l
|
.ad l
|
||||||
.SH NAME
|
.SH NAME
|
||||||
\fBpvsecret add\fP - Repeat an add-secret request (s390x only)
|
\fBpvsecret add\fP - Perform an add-secret request (s390x only)
|
||||||
\fB
|
\fB
|
||||||
.SH SYNOPSIS
|
.SH SYNOPSIS
|
||||||
.nf
|
.nf
|
||||||
@@ -18,7 +18,6 @@ pvsecret add <FILE>
|
|||||||
.SH DESCRIPTION
|
.SH DESCRIPTION
|
||||||
Perform an add-secret request using a previously generated add-secret request.
|
Perform an add-secret request using a previously generated add-secret request.
|
||||||
Only available on s390x.
|
Only available on s390x.
|
||||||
|
|
||||||
.SH OPTIONS
|
.SH OPTIONS
|
||||||
.PP
|
.PP
|
||||||
<FILE>
|
<FILE>
|
||||||
|
|||||||
@@ -3,11 +3,11 @@
|
|||||||
.\" it under the terms of the MIT license. See LICENSE for details.
|
.\" it under the terms of the MIT license. See LICENSE for details.
|
||||||
.\"
|
.\"
|
||||||
|
|
||||||
.TH pvsecret-create-association 1 "2023-07-28" "s390-tools" "UV-Secret Manual"
|
.TH pvsecret-create-association 1 "2024-01-30" "s390-tools" "UV-Secret Manual"
|
||||||
.nh
|
.nh
|
||||||
.ad l
|
.ad l
|
||||||
.SH NAME
|
.SH NAME
|
||||||
\fBpvsecret create association\fP - Use an association secret to connect an I/O device to a guest
|
\fBpvsecret create association\fP - Create an association secret
|
||||||
\fB
|
\fB
|
||||||
.SH SYNOPSIS
|
.SH SYNOPSIS
|
||||||
.nf
|
.nf
|
||||||
@@ -18,13 +18,12 @@ pvsecret create association [OPTIONS] <NAME>
|
|||||||
.SH DESCRIPTION
|
.SH DESCRIPTION
|
||||||
Use an association secret to connect a trusted I/O device to a guest. The
|
Use an association secret to connect a trusted I/O device to a guest. The
|
||||||
`pvapconfig` tool provides more information about association secrets.
|
`pvapconfig` tool provides more information about association secrets.
|
||||||
|
|
||||||
.SH OPTIONS
|
.SH OPTIONS
|
||||||
.PP
|
.PP
|
||||||
<NAME>
|
<NAME>
|
||||||
.RS 4
|
.RS 4
|
||||||
String to identify the new secret. The actual secret is set with
|
String to identify the new secret. The actual secret is set with
|
||||||
\fB--input-secret\fR. The name is saved in `NAME.yaml` with white-spaces mapped
|
\fB--input-secret\fR. The name is saved in `NAME.yaml` with white-spaces mapped
|
||||||
to `_`.
|
to `_`.
|
||||||
.RE
|
.RE
|
||||||
.RE
|
.RE
|
||||||
|
|||||||
@@ -3,11 +3,11 @@
|
|||||||
.\" it under the terms of the MIT license. See LICENSE for details.
|
.\" it under the terms of the MIT license. See LICENSE for details.
|
||||||
.\"
|
.\"
|
||||||
|
|
||||||
.TH pvsecret-create-meta 1 "2023-07-28" "s390-tools" "UV-Secret Manual"
|
.TH pvsecret-create-meta 1 "2024-01-30" "s390-tools" "UV-Secret Manual"
|
||||||
.nh
|
.nh
|
||||||
.ad l
|
.ad l
|
||||||
.SH NAME
|
.SH NAME
|
||||||
\fBpvsecret create meta\fP - Use a meta secret to carry flags to the ultravisor
|
\fBpvsecret create meta\fP - Create a meta secret
|
||||||
\fB
|
\fB
|
||||||
.SH SYNOPSIS
|
.SH SYNOPSIS
|
||||||
.nf
|
.nf
|
||||||
@@ -17,8 +17,7 @@ pvsecret create meta
|
|||||||
.fi
|
.fi
|
||||||
.SH DESCRIPTION
|
.SH DESCRIPTION
|
||||||
Use a meta secret to carry flags to the ultravisor without having to provide an
|
Use a meta secret to carry flags to the ultravisor without having to provide an
|
||||||
actual secret value. Meta secrets do not appear in the list of secrets.
|
actual secret value. Meta secrets do not appear in the list of secrets.
|
||||||
|
|
||||||
.SH "SEE ALSO"
|
.SH "SEE ALSO"
|
||||||
.sp
|
.sp
|
||||||
\fBpvsecret\fR(1) \fBpvsecret-create\fR(1)
|
\fBpvsecret\fR(1) \fBpvsecret-create\fR(1)
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
.\" it under the terms of the MIT license. See LICENSE for details.
|
.\" it under the terms of the MIT license. See LICENSE for details.
|
||||||
.\"
|
.\"
|
||||||
|
|
||||||
.TH pvsecret-create 1 "2023-07-28" "s390-tools" "UV-Secret Manual"
|
.TH pvsecret-create 1 "2024-01-30" "s390-tools" "UV-Secret Manual"
|
||||||
.nh
|
.nh
|
||||||
.ad l
|
.ad l
|
||||||
.SH NAME
|
.SH NAME
|
||||||
@@ -22,10 +22,10 @@ create\fR command creates a randomly generated key to protect the request. The
|
|||||||
generated requests can then be added on an IBM Secure Execution guest using
|
generated requests can then be added on an IBM Secure Execution guest using
|
||||||
\fBpvsecret add\fR. The guest can then use the secrets with the use case
|
\fBpvsecret add\fR. The guest can then use the secrets with the use case
|
||||||
depending on the secret type.
|
depending on the secret type.
|
||||||
|
|
||||||
Such a request is bound to a specific IBM Secure Execution image specified with
|
Such a request is bound to a specific IBM Secure Execution image specified with
|
||||||
\fB--hdr\fR. Optionally, the request can be bound to a specific instance when
|
\fB--hdr\fR. Optionally, the request can be bound to a specific instance when
|
||||||
bound to the Configuration Unique ID from \fBpvattest\fR using \fB--cuid\fR
|
bound to the Configuration Unique ID from \fBpvattest\fR using \fB--cuid\fR
|
||||||
|
|
||||||
.SH OPTIONS
|
.SH OPTIONS
|
||||||
.PP
|
.PP
|
||||||
\-k, \-\-host-key-document <FILE>
|
\-k, \-\-host-key-document <FILE>
|
||||||
@@ -62,15 +62,15 @@ to use multiple CRLs.
|
|||||||
.PP
|
.PP
|
||||||
\-\-offline
|
\-\-offline
|
||||||
.RS 4
|
.RS 4
|
||||||
Make no attempt to download CRLs
|
Make no attempt to download CRLs.
|
||||||
.RE
|
.RE
|
||||||
.RE
|
.RE
|
||||||
.PP
|
.PP
|
||||||
\-\-root-ca <ROOT_CA>
|
\-\-root-ca <ROOT_CA>
|
||||||
.RS 4
|
.RS 4
|
||||||
Use FILE as the root-CA certificate for the verification. If omitted, the
|
Use FILE as the root-CA certificate for the verification. If omitted, the system
|
||||||
system-wide root CAs installed on the system are used. Use this only if you
|
wide-root CAs installed on the system are used. Use this only if you trust the
|
||||||
trust the specified certificate.
|
specified certificate.
|
||||||
.RE
|
.RE
|
||||||
.RE
|
.RE
|
||||||
.PP
|
.PP
|
||||||
@@ -93,7 +93,7 @@ behavior.
|
|||||||
.PP
|
.PP
|
||||||
\-o, \-\-output <FILE>
|
\-o, \-\-output <FILE>
|
||||||
.RS 4
|
.RS 4
|
||||||
Write the generated request to FILE
|
Write the generated request to FILE.
|
||||||
.RE
|
.RE
|
||||||
.RE
|
.RE
|
||||||
.PP
|
.PP
|
||||||
@@ -120,21 +120,21 @@ all requests.
|
|||||||
.PP
|
.PP
|
||||||
\-\-cuid-hex <HEXSTRING>
|
\-\-cuid-hex <HEXSTRING>
|
||||||
.RS 4
|
.RS 4
|
||||||
Use HEXSTRING as the Configuration Unique ID. Must be a hex 128-bit unsigned
|
Use HEXSTRING as the Configuration Unique ID. Must be a hex 128-bit unsigned big
|
||||||
big endian number string. Leading zeros must be provided. If specified, the
|
endian number string. Leading zeros must be provided. If specified, the value
|
||||||
value must match with the Config-UID from the attestation result of that guest.
|
must match with the Config-UID from the attestation result of that guest. If not
|
||||||
If not specified, the CUID will be ignored by the ultravisor during the
|
specified, the CUID will be ignored by the ultravisor during the verification of
|
||||||
verification of the request.
|
the request.
|
||||||
.RE
|
.RE
|
||||||
.RE
|
.RE
|
||||||
.PP
|
.PP
|
||||||
\-\-cuid <FILE>
|
\-\-cuid <FILE>
|
||||||
.RS 4
|
.RS 4
|
||||||
Use the content of FILE as the Configuration Unique ID. The file must contain
|
Use the content of FILE as the Configuration Unique ID. The file must contain
|
||||||
exactly 128 bit of data, a hex string, or a yaml with a `cuid` entry. If
|
exactly 128 bit of data or a yaml with a `cuid` entry. If specified, the value
|
||||||
specified, the value must match the Config-UID from the attestation result of
|
must match the Config-UID from the attestation result of that guest. If not
|
||||||
that guest. If not specified, the CUID will be ignored by the Ultravisor during
|
specified, the CUID will be ignored by the Ultravisor during the verification
|
||||||
the verification of the request.
|
of the request.
|
||||||
.RE
|
.RE
|
||||||
.RE
|
.RE
|
||||||
.PP
|
.PP
|
||||||
@@ -146,6 +146,37 @@ Possible values:
|
|||||||
.RS 4
|
.RS 4
|
||||||
- \fBdisable-dump\fP: Disables host-initiated dumping for the target guest instance.
|
- \fBdisable-dump\fP: Disables host-initiated dumping for the target guest instance.
|
||||||
|
|
||||||
|
.RE
|
||||||
|
.RE
|
||||||
|
.PP
|
||||||
|
\-\-user-data <FILE>
|
||||||
|
.RS 4
|
||||||
|
Use the content of FILE as user-data. Passes user data defined in <FILE> through
|
||||||
|
the add-secret request to the ultravisor. The user data can be up to 512 bytes
|
||||||
|
of arbitrary data, and the maximum size depends on the size of the user-signing
|
||||||
|
key:
|
||||||
|
|
||||||
|
- No key: user data can be 512 bytes.
|
||||||
|
|
||||||
|
- EC(secp521r1) or RSA 2048 keys: user data can be 256 bytes.
|
||||||
|
|
||||||
|
- RSA 3072 key: user data can be 128 bytes.
|
||||||
|
|
||||||
|
The firmware ignores this data, but the request tag protects the user-data.
|
||||||
|
Optional. No user-data by default.
|
||||||
|
.RE
|
||||||
|
.RE
|
||||||
|
.PP
|
||||||
|
\-\-user-sign-key <FILE>
|
||||||
|
.RS 4
|
||||||
|
Use the content of FILE as user signing key. Adds a signature defined calculated
|
||||||
|
from the key in <FILE> to the add-secret request. The file must be in DER or PEM
|
||||||
|
format containing a private key. Supported are RSA 2048 & 3072-bit and
|
||||||
|
EC(secp521r1) keys. The firmware ignores the content, but the request tag
|
||||||
|
protects the signature. The user-signing key signs the request. The location of
|
||||||
|
the signature is filled with zeros during the signature calculation. The request
|
||||||
|
tag also secures the signature. See man pvsecret verify for more details.
|
||||||
|
Optional. No signature by default.
|
||||||
.RE
|
.RE
|
||||||
.RE
|
.RE
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
.\" it under the terms of the MIT license. See LICENSE for details.
|
.\" it under the terms of the MIT license. See LICENSE for details.
|
||||||
.\"
|
.\"
|
||||||
|
|
||||||
.TH pvsecret-list 1 "2023-07-28" "s390-tools" "UV-Secret Manual"
|
.TH pvsecret-list 1 "2024-01-30" "s390-tools" "UV-Secret Manual"
|
||||||
.nh
|
.nh
|
||||||
.ad l
|
.ad l
|
||||||
.SH NAME
|
.SH NAME
|
||||||
@@ -22,22 +22,24 @@ currently running IBM Secure Execution guest. Only available on s390x.
|
|||||||
.PP
|
.PP
|
||||||
<FILE>
|
<FILE>
|
||||||
.RS 4
|
.RS 4
|
||||||
Store the result in FILE. Default value: '-'
|
Store the result in FILE.
|
||||||
|
[default: '-']
|
||||||
.RE
|
.RE
|
||||||
.RE
|
.RE
|
||||||
|
|
||||||
.PP
|
.PP
|
||||||
\-\-format <FORMAT>
|
\-\-format <FORMAT>
|
||||||
.RS 4
|
.RS 4
|
||||||
Define the output format of the list. Default value: 'human'
|
Define the output format of the list.
|
||||||
|
[default: 'human']
|
||||||
|
|
||||||
Possible values:
|
Possible values:
|
||||||
.RS 4
|
.RS 4
|
||||||
- \fBhuman\fP: Human-focused, non-parsable output format
|
- \fBhuman\fP: Human-focused, non-parsable output format.
|
||||||
|
|
||||||
- \fByaml\fP: Use yaml format
|
- \fByaml\fP: Use yaml format.
|
||||||
|
|
||||||
- \fBbin\fP: Use the format the ultravisor uses to pass the list
|
- \fBbin\fP: Use the format the ultravisor uses to pass the list.
|
||||||
|
|
||||||
.RE
|
.RE
|
||||||
.RE
|
.RE
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
.\" it under the terms of the MIT license. See LICENSE for details.
|
.\" it under the terms of the MIT license. See LICENSE for details.
|
||||||
.\"
|
.\"
|
||||||
|
|
||||||
.TH pvsecret-lock 1 "2023-07-28" "s390-tools" "UV-Secret Manual"
|
.TH pvsecret-lock 1 "2024-01-30" "s390-tools" "UV-Secret Manual"
|
||||||
.nh
|
.nh
|
||||||
.ad l
|
.ad l
|
||||||
.SH NAME
|
.SH NAME
|
||||||
@@ -18,9 +18,6 @@ pvsecret lock
|
|||||||
.SH DESCRIPTION
|
.SH DESCRIPTION
|
||||||
Lock the secret store (s390x only). After this command executed successfully,
|
Lock the secret store (s390x only). After this command executed successfully,
|
||||||
all add-secret requests will fail. Only available on s390x.
|
all add-secret requests will fail. Only available on s390x.
|
||||||
|
|
||||||
.SH OPTIONS
|
|
||||||
|
|
||||||
.SH "SEE ALSO"
|
.SH "SEE ALSO"
|
||||||
.sp
|
.sp
|
||||||
\fBpvsecret\fR(1)
|
\fBpvsecret\fR(1)
|
||||||
|
|||||||
139
rust/pvsecret/man/pvsecret-verify.1
Normal file
139
rust/pvsecret/man/pvsecret-verify.1
Normal file
@@ -0,0 +1,139 @@
|
|||||||
|
.\" Copyright 2023 IBM Corp.
|
||||||
|
.\" s390-tools is free software; you can redistribute it and/or modify
|
||||||
|
.\" it under the terms of the MIT license. See LICENSE for details.
|
||||||
|
.\"
|
||||||
|
|
||||||
|
.TH pvsecret-verify 1 "2024-01-30" "s390-tools" "UV-Secret Manual"
|
||||||
|
.nh
|
||||||
|
.ad l
|
||||||
|
.SH NAME
|
||||||
|
\fBpvsecret verify\fP - Verify that an add-secret request is sane
|
||||||
|
\fB
|
||||||
|
.SH SYNOPSIS
|
||||||
|
.nf
|
||||||
|
.fam C
|
||||||
|
pvsecret verify [OPTIONS] <FILE>
|
||||||
|
.fam C
|
||||||
|
.fi
|
||||||
|
.SH DESCRIPTION
|
||||||
|
.PP
|
||||||
|
Verifies that the given request is an Add-Secret request by testing for some
|
||||||
|
values to be present. If the request contains signed user-data, the signature is
|
||||||
|
verified with the provided key. Outputs the arbitrary user-data. All data in the
|
||||||
|
request is in big endian.
|
||||||
|
.PP
|
||||||
|
\fIverify\fP checks the following:
|
||||||
|
.RS
|
||||||
|
.IP \[bu] 2
|
||||||
|
The first 6 bytes of the request are equal to: \fB6173 7263 624d | asrcbM\fP
|
||||||
|
.IP \[bu] 2
|
||||||
|
The sizes in the request header are sane and do not point out of the
|
||||||
|
file
|
||||||
|
.IP \[bu] 2
|
||||||
|
The request version is supported by the binary
|
||||||
|
.IP \[bu] 2
|
||||||
|
If user-data contains a signature, verify the signature using a public
|
||||||
|
key
|
||||||
|
.RE
|
||||||
|
.PP
|
||||||
|
The content of bytes 6&7 of the request define which kind
|
||||||
|
of user-data the request contains.
|
||||||
|
.IP \fB0x0000\fP 8
|
||||||
|
no user-data (512 bytes zero)
|
||||||
|
.IP \fB0x0001\fP 8
|
||||||
|
512 bytes user-data
|
||||||
|
.IP \fB0x0002\fP 8
|
||||||
|
265 bytes user-data| 139 bytes ecdsa signature | 5 bytes reserved | 2 bytes
|
||||||
|
signature size | ...
|
||||||
|
.IP \fB0x0003\fP 8
|
||||||
|
256 bytes user-data | 256 bytes rsa2048 signature
|
||||||
|
.IP \fB0x0004\fP 8
|
||||||
|
128 bytes user-data | 384 bytes rsa3072 signature
|
||||||
|
.PP
|
||||||
|
The actual user-data may be less than the capacity. If less data was provided
|
||||||
|
during \fIcreate\fP zeros are appended.
|
||||||
|
.
|
||||||
|
For type 2-4 The signature is calculated as follows:
|
||||||
|
.RS
|
||||||
|
.IP "1." 3
|
||||||
|
The request is generated with the user-data in place and zeros for the
|
||||||
|
signature data.
|
||||||
|
.IP "2." 3
|
||||||
|
The signature is calculated for the request. The signature signs the
|
||||||
|
authenticated data and the encrypted data, but not the request tag. I.e. the
|
||||||
|
signature signs the whole request but the last 16 bytes and with the signature
|
||||||
|
bytes set to zero.
|
||||||
|
.IP "3." 3
|
||||||
|
The signature is inserted to its location in the request.
|
||||||
|
.IP "4." 3
|
||||||
|
The request GCM tag is calculated.
|
||||||
|
.PP
|
||||||
|
.RE
|
||||||
|
|
||||||
|
The verification process works as follows:
|
||||||
|
.RS
|
||||||
|
.IP "1." 3
|
||||||
|
copy the signature to a buffer
|
||||||
|
.IP "2." 3
|
||||||
|
overwrite the signature with zeros
|
||||||
|
.IP "3." 3
|
||||||
|
verify the signature of the request but the last 16 bytes
|
||||||
|
.RE
|
||||||
|
|
||||||
|
.SH OPTIONS
|
||||||
|
.PP
|
||||||
|
<FILE>
|
||||||
|
.RS 4
|
||||||
|
Specify the request to be checked.
|
||||||
|
.RE
|
||||||
|
.RE
|
||||||
|
|
||||||
|
.PP
|
||||||
|
\-\-user-cert <FILE>
|
||||||
|
.RS 4
|
||||||
|
Certificate containing a public key used to verify the user data signature.
|
||||||
|
Specifies a public key used to verify the user-data signature. The file must be
|
||||||
|
a X509 certificate in DSA or PEM format. The certificate must hold the public
|
||||||
|
EC, RSA 2048, or RSA 3072 key corresponding to the private user-key used during
|
||||||
|
`create`. No chain of trust is established. Ensuring that the certificate can be
|
||||||
|
trusted is the responsibility of the user. The EC key must use the NIST/SECG
|
||||||
|
curve over a 521 bit prime field (secp521r1).
|
||||||
|
.RE
|
||||||
|
.RE
|
||||||
|
.PP
|
||||||
|
\-o, \-\-output <FILE>
|
||||||
|
.RS 4
|
||||||
|
Store the result in FILE If the request contained abirtary user-data the output
|
||||||
|
contains this user-data with padded zeros if available.
|
||||||
|
[default: '-']
|
||||||
|
.RE
|
||||||
|
.RE
|
||||||
|
|
||||||
|
.SH EXAMPLES
|
||||||
|
.PP
|
||||||
|
Create the add-secret request on a trusted system with signed user datai similar to the example for \fFpvsecret\fP. Let's assume there are three more files present .\fFuser_data\fP contains ascii "some example user-data", a private user-signing key e.g. rsa3072 \fFusr_sgn_key.priv.pem\fF, and a certificate containing the corresponding public key to the private rsa3072 key \fFuser_cert.pem\fP.
|
||||||
|
.PP
|
||||||
|
.RS
|
||||||
|
.IP trusted:~$ 12
|
||||||
|
pvsecret create -k hkd.crt --cert CA.crt --cert ibmsk.crt --hdr pvimage -o addsecreq.bin --user-data user_data --user-sign-key usr_sgn_key.priv.pem association EXAMPLE
|
||||||
|
.RE
|
||||||
|
.RS
|
||||||
|
Successfully generated the request
|
||||||
|
.br
|
||||||
|
Successfully wrote association info to 'EXAMPLE.yaml'
|
||||||
|
.RE
|
||||||
|
|
||||||
|
For example, on the SE-guest, perform \fIverify\fP on the request to verify the user-signature and the saneness of the request. On success, The user-data is printed to stdout (if \fI--output\fP was not specified) and \fFSuccesfully verified the request.\fP is printed to stderr.
|
||||||
|
.PP
|
||||||
|
.RS
|
||||||
|
.IP seguest:~$ 12
|
||||||
|
pvsecret verify --user-cert user_cert.pem -o addsecreq.bin
|
||||||
|
.RE
|
||||||
|
.RS
|
||||||
|
some example user-data
|
||||||
|
.br
|
||||||
|
Successfully verified the request
|
||||||
|
.RE
|
||||||
|
.SH "SEE ALSO"
|
||||||
|
.sp
|
||||||
|
\fBpvsecret\fR(1)
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
.\" Copyright 2023 IBM Corp.
|
|
||||||
.\" s390-tools is free software; you can redistribute it and/or modify
|
|
||||||
.\" it under the terms of the MIT license. See LICENSE for details.
|
|
||||||
.\"
|
|
||||||
|
|
||||||
.TH pvsecret-version 1 "2023-07-28" "s390-tools" "UV-Secret Manual"
|
|
||||||
.nh
|
|
||||||
.ad l
|
|
||||||
.SH NAME
|
|
||||||
\fBpvsecret version\fP- Print version information and exit
|
|
||||||
\fB
|
|
||||||
.SH SYNOPSIS
|
|
||||||
.nf
|
|
||||||
.fam C
|
|
||||||
pvsecret version
|
|
||||||
.fam C
|
|
||||||
.fi
|
|
||||||
.SH DESCRIPTION
|
|
||||||
Print version information and exit
|
|
||||||
|
|
||||||
.SH OPTIONS
|
|
||||||
|
|
||||||
.SH "SEE ALSO"
|
|
||||||
.sp
|
|
||||||
\fBpvsecret\fR(1)
|
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
.\" it under the terms of the MIT license. See LICENSE for details.
|
.\" it under the terms of the MIT license. See LICENSE for details.
|
||||||
.\"
|
.\"
|
||||||
|
|
||||||
.TH pvsecret 1 "2023-07-28" "s390-tools" "UV-Secret Manual"
|
.TH pvsecret 1 "2024-01-30" "s390-tools" "UV-Secret Manual"
|
||||||
.nh
|
.nh
|
||||||
.ad l
|
.ad l
|
||||||
.SH NAME
|
.SH NAME
|
||||||
@@ -30,7 +30,7 @@ guest where you want to inject the secrets. This approach prevents the secrets
|
|||||||
from being in cleartext on the guest. For extra safety, do an attestation with
|
from being in cleartext on the guest. For extra safety, do an attestation with
|
||||||
\fBpvattest\fR of your guest beforehand, and include the configuration UID in
|
\fBpvattest\fR of your guest beforehand, and include the configuration UID in
|
||||||
the secret request using \fB--cuid\fR. Refer to \fBpvsecret-add\fR(1) for more
|
the secret request using \fB--cuid\fR. Refer to \fBpvsecret-add\fR(1) for more
|
||||||
information. For all certificates, revocation lists, and host-key documents,
|
information. For all certificates, revocation lists, and host-key documents,
|
||||||
both the PEM and DER input formats are supported.
|
both the PEM and DER input formats are supported.
|
||||||
|
|
||||||
.SH OPTIONS
|
.SH OPTIONS
|
||||||
@@ -49,20 +49,16 @@ Print version information and exit.
|
|||||||
|
|
||||||
.SH EXAMPLES
|
.SH EXAMPLES
|
||||||
.PP
|
.PP
|
||||||
Create the add-secret request on a trusted system. The program generates three
|
Create the add-secret request on a trusted system. The program generates two
|
||||||
files. \fFaddsecreq.bin\fP contains the add-secret request. \fFTEST.yaml\fP
|
files. \fFaddsecreq.bin\fP contains the add-secret request. \fEXAMPLE.yaml\fP
|
||||||
contains the non-confidential information about the generated secret. It
|
contains the non-confidential information about the generated secret. It
|
||||||
contains the name and ID of the secret. \fFTEST\fP contains the plaintext secret
|
contains name and id of the secret.
|
||||||
that is encrypted in the request. It can be used to generate add-secret requests
|
|
||||||
for a different guest with the same secret. Destroy the secret when it is not
|
|
||||||
used anymore.
|
|
||||||
.PP
|
.PP
|
||||||
.nf
|
.nf
|
||||||
.fam C
|
.fam C
|
||||||
trusted:~$ pvsecret create -k hkd.crt --cert CA.crt --cert ibmsk.crt --hdr pvimage -o addsecreq.bin association EXAMPLE
|
trusted:~$ pvsecret create -k hkd.crt --cert CA.crt --cert ibmsk.crt --hdr pvimage -o addsecreq.bin association EXAMPLE
|
||||||
Successfully generated the request
|
Successfully generated the request
|
||||||
Successfully wrote association info to 'EXAMPLE.yaml'
|
Successfully wrote association info to 'EXAMPLE.yaml'
|
||||||
Successfully wrote generated association secret to 'EXAMPLE'
|
|
||||||
.fam T
|
.fam T
|
||||||
.fi
|
.fi
|
||||||
On the SE-guest, \fIadd\fP the secret from request to the secret store.
|
On the SE-guest, \fIadd\fP the secret from request to the secret store.
|
||||||
@@ -100,4 +96,4 @@ On the SE-guest, \fIlock\fP the secret store.
|
|||||||
.fi
|
.fi
|
||||||
.SH "SEE ALSO"
|
.SH "SEE ALSO"
|
||||||
.sp
|
.sp
|
||||||
\fBpvsecret-create\fR(1) \fBpvsecret-add\fR(1) \fBpvsecret-lock\fR(1) \fBpvsecret-list\fR(1) \fBpvsecret-version\fR(1)
|
\fBpvsecret-create\fR(1) \fBpvsecret-add\fR(1) \fBpvsecret-lock\fR(1) \fBpvsecret-list\fR(1) \fBpvsecret-verify\fR(1)
|
||||||
|
|||||||
@@ -4,7 +4,6 @@
|
|||||||
|
|
||||||
use clap::{ArgGroup, Args, CommandFactory, Parser, Subcommand, ValueEnum, ValueHint};
|
use clap::{ArgGroup, Args, CommandFactory, Parser, Subcommand, ValueEnum, ValueHint};
|
||||||
use pv::misc::CertificateOptions;
|
use pv::misc::CertificateOptions;
|
||||||
#[cfg(target_arch = "s390x")]
|
|
||||||
use pv::misc::STDOUT;
|
use pv::misc::STDOUT;
|
||||||
|
|
||||||
/// Manage secrets for IBM Secure Execution guests.
|
/// Manage secrets for IBM Secure Execution guests.
|
||||||
@@ -84,7 +83,7 @@ pub struct CreateSecretOpt {
|
|||||||
|
|
||||||
/// Use the content of FILE as the Configuration Unique ID.
|
/// Use the content of FILE as the Configuration Unique ID.
|
||||||
///
|
///
|
||||||
/// The file must contain exactly 128 bit of data, a hex string, or a yaml with a `cuid` entry.
|
/// The file must contain exactly 128 bit of data or a yaml with a `cuid` entry.
|
||||||
/// If specified, the value must match the Config-UID from the attestation result of that
|
/// If specified, the value must match the Config-UID from the attestation result of that
|
||||||
/// guest. If not specified, the CUID will be ignored by the Ultravisor during the verification
|
/// guest. If not specified, the CUID will be ignored by the Ultravisor during the verification
|
||||||
/// of the request.
|
/// of the request.
|
||||||
@@ -113,14 +112,43 @@ pub struct CreateSecretOpt {
|
|||||||
value_delimiter = ','
|
value_delimiter = ','
|
||||||
)]
|
)]
|
||||||
pub flags: Vec<CreateSecretFlags>,
|
pub flags: Vec<CreateSecretFlags>,
|
||||||
|
|
||||||
|
/// Use the content of FILE as user-data.
|
||||||
|
///
|
||||||
|
/// Passes user data defined in <FILE> through the add-secret request to the ultravisor. The
|
||||||
|
/// user data can be up to 512 bytes of arbitrary data, and the maximum size depends on the
|
||||||
|
/// size of the user-signing key:
|
||||||
|
/// - No key: user data can be 512 bytes.
|
||||||
|
/// - EC or RSA 2048 keys: user data can be 256 bytes.
|
||||||
|
/// - RSA 3072 key: user data can be 128 bytes.
|
||||||
|
///
|
||||||
|
/// The firmware ignores this data, but the request tag protects the user-data. Optional. No
|
||||||
|
/// user-data by default.
|
||||||
|
#[arg(long, value_name = "FILE", value_hint = ValueHint::FilePath,)]
|
||||||
|
pub user_data: Option<String>,
|
||||||
|
|
||||||
|
/// Use the content of FILE as user signing key.
|
||||||
|
///
|
||||||
|
/// Adds a signature calculated from the key in <FILE> to the add-secret request. The
|
||||||
|
/// file must be in DER or PEM format containing a private key. Supported are RSA 2048 &
|
||||||
|
/// 3072-bit and EC(secp521r1) keys. The firmware ignores the content, but the request tag protects the
|
||||||
|
/// signature. The user-signing key signs the request. The location of the signature is filled
|
||||||
|
/// with zeros during the signature calculation. The request tag also secures the signature.
|
||||||
|
/// See man pvsecret verify for more details. Optional. No signature by default.
|
||||||
|
#[arg(long, value_name = "FILE", value_hint = ValueHint::FilePath,)]
|
||||||
|
pub user_sign_key: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Subcommand, Debug)]
|
#[derive(Subcommand, Debug)]
|
||||||
pub enum AddSecretType {
|
pub enum AddSecretType {
|
||||||
|
/// Create a meta secret.
|
||||||
|
///
|
||||||
/// Use a meta secret to carry flags to the ultravisor without having to provide an actual
|
/// Use a meta secret to carry flags to the ultravisor without having to provide an actual
|
||||||
/// secret value. Meta secrets do not appear in the list of secrets.
|
/// secret value. Meta secrets do not appear in the list of secrets.
|
||||||
Meta,
|
Meta,
|
||||||
|
|
||||||
|
/// Create an association secret.
|
||||||
|
///
|
||||||
/// Use an association secret to connect a trusted I/O device to a guest. The `pvapconfig` tool
|
/// Use an association secret to connect a trusted I/O device to a guest. The `pvapconfig` tool
|
||||||
/// provides more information about association secrets.
|
/// provides more information about association secrets.
|
||||||
Association {
|
Association {
|
||||||
@@ -132,7 +160,7 @@ pub enum AddSecretType {
|
|||||||
|
|
||||||
///Print the hashed name to stdout.
|
///Print the hashed name to stdout.
|
||||||
///
|
///
|
||||||
///The hashed name will not be written to `NAME.yaml`
|
///The hashed name is not written to `NAME.yaml`
|
||||||
#[arg(long)]
|
#[arg(long)]
|
||||||
stdout: bool,
|
stdout: bool,
|
||||||
|
|
||||||
@@ -185,6 +213,30 @@ pub struct ListSecretOpt {
|
|||||||
pub format: ListSecretOutputType,
|
pub format: ListSecretOutputType,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Args, Debug)]
|
||||||
|
pub struct VerifyOpt {
|
||||||
|
/// Specify the request to be checked.
|
||||||
|
#[arg(value_name = "FILE", value_hint = ValueHint::FilePath,)]
|
||||||
|
pub input: String,
|
||||||
|
|
||||||
|
/// Certificate containing a public key used to verify the user data signature.
|
||||||
|
///
|
||||||
|
/// Specifies a public key used to verify the user-data signature. The file must be a X509
|
||||||
|
/// certificate in DSA or PEM format. The certificate must hold the public EC, RSA 2048, or RSA
|
||||||
|
/// 3072 key corresponding to the private user-key used during `create`. No chain of trust is
|
||||||
|
/// established. Ensuring that the certificate can be trusted is the responsibility of the
|
||||||
|
/// user. The EC key must use the NIST/SECG curve over a 521 bit prime field (secp521r1).
|
||||||
|
#[arg(long, value_name = "FILE", value_hint = ValueHint::FilePath,)]
|
||||||
|
pub user_cert: Option<String>,
|
||||||
|
|
||||||
|
/// Store the result in FILE
|
||||||
|
///
|
||||||
|
/// If the request contained abirtary user-data the output contains this user-data with padded
|
||||||
|
/// zeros if available.
|
||||||
|
#[arg(short, long, value_name = "FILE", default_value = STDOUT, value_hint = ValueHint::FilePath,)]
|
||||||
|
pub output: String,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Subcommand, Debug)]
|
#[derive(Subcommand, Debug)]
|
||||||
pub enum Command {
|
pub enum Command {
|
||||||
/// Create a new add-secret request.
|
/// Create a new add-secret request.
|
||||||
@@ -196,7 +248,7 @@ pub enum Command {
|
|||||||
/// the use case depending on the secret type.
|
/// the use case depending on the secret type.
|
||||||
Create(Box<CreateSecretOpt>),
|
Create(Box<CreateSecretOpt>),
|
||||||
|
|
||||||
/// Repeat an add-secret request (s390x only).
|
/// Perform an add-secret request (s390x only).
|
||||||
///
|
///
|
||||||
/// Perform an add-secret request using a previously generated add-secret request. Only
|
/// Perform an add-secret request using a previously generated add-secret request. Only
|
||||||
/// available on s390x.
|
/// available on s390x.
|
||||||
@@ -214,6 +266,13 @@ pub enum Command {
|
|||||||
/// running IBM Secure Execution guest. Only available on s390x.
|
/// running IBM Secure Execution guest. Only available on s390x.
|
||||||
List(ListSecretOpt),
|
List(ListSecretOpt),
|
||||||
|
|
||||||
|
/// Verify that an add-secret request is sane.
|
||||||
|
///
|
||||||
|
/// Verifies that the given request is an add-secret request by testing for some values to be
|
||||||
|
/// present. If the request contains signed user-data, the signature is verified with the
|
||||||
|
/// provided key. Outputs the arbitrary user-data.
|
||||||
|
Verify(VerifyOpt),
|
||||||
|
|
||||||
/// Print version information and exit.
|
/// Print version information and exit.
|
||||||
#[command(aliases(["--version"]), hide(true))]
|
#[command(aliases(["--version"]), hide(true))]
|
||||||
Version,
|
Version,
|
||||||
|
|||||||
@@ -5,6 +5,9 @@
|
|||||||
mod create;
|
mod create;
|
||||||
pub use create::create;
|
pub use create::create;
|
||||||
|
|
||||||
|
mod verify;
|
||||||
|
pub use verify::verify;
|
||||||
|
|
||||||
// Commands (directly) related to UVCs are only available on s389x
|
// Commands (directly) related to UVCs are only available on s389x
|
||||||
#[cfg(target_arch = "s390x")]
|
#[cfg(target_arch = "s390x")]
|
||||||
mod add;
|
mod add;
|
||||||
|
|||||||
@@ -7,10 +7,9 @@ use anyhow::{anyhow, bail, Context, Result};
|
|||||||
use log::{debug, info, trace, warn};
|
use log::{debug, info, trace, warn};
|
||||||
use pv::{
|
use pv::{
|
||||||
misc::{
|
misc::{
|
||||||
get_writer_from_cli_file_arg, parse_hex, pv_guest_bit_set, read_certs, read_exact_file,
|
get_writer_from_cli_file_arg, open_file, parse_hex, pv_guest_bit_set, read_certs,
|
||||||
read_file, try_parse_u128, try_parse_u64,
|
read_exact_file, read_file, read_private_key, try_parse_u128, try_parse_u64, write,
|
||||||
},
|
},
|
||||||
open_buffered_file,
|
|
||||||
request::{
|
request::{
|
||||||
openssl::pkey::{PKey, Public},
|
openssl::pkey::{PKey, Public},
|
||||||
uvsecret::{AddSecretFlags, AddSecretRequest, AddSecretVersion, ExtSecret, GuestSecret},
|
uvsecret::{AddSecretFlags, AddSecretRequest, AddSecretVersion, ExtSecret, GuestSecret},
|
||||||
@@ -21,11 +20,8 @@ use pv::{
|
|||||||
use serde_yaml::Value;
|
use serde_yaml::Value;
|
||||||
|
|
||||||
fn write_out<D: AsRef<[u8]>>(path: &str, data: D, ctx: &str) -> pv::Result<()> {
|
fn write_out<D: AsRef<[u8]>>(path: &str, data: D, ctx: &str) -> pv::Result<()> {
|
||||||
let mut wr = match get_writer_from_cli_file_arg(path) {
|
let mut wr = get_writer_from_cli_file_arg(path)?;
|
||||||
Ok(it) => it,
|
write(&mut wr, data, path, ctx)?;
|
||||||
Err(err) => return Err(err),
|
|
||||||
};
|
|
||||||
pv::misc::write(&mut wr, data, path, ctx)?;
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -89,7 +85,7 @@ fn build_asrcb(opt: &CreateSecretOpt) -> Result<AddSecretRequest> {
|
|||||||
});
|
});
|
||||||
debug!("FLAGS: {flags:x?}");
|
debug!("FLAGS: {flags:x?}");
|
||||||
|
|
||||||
let mut se_hdr = open_buffered_file!(&opt.hdr);
|
let mut se_hdr = open_file(&opt.hdr)?;
|
||||||
let mut asrcb = AddSecretRequest::new(
|
let mut asrcb = AddSecretRequest::new(
|
||||||
AddSecretVersion::One,
|
AddSecretVersion::One,
|
||||||
secret,
|
secret,
|
||||||
@@ -110,6 +106,27 @@ fn build_asrcb(opt: &CreateSecretOpt) -> Result<AddSecretRequest> {
|
|||||||
asrcb.set_ext_secret(ExtSecret::Derived(read_exact_file(path, "CCK")?.into()))?;
|
asrcb.set_ext_secret(ExtSecret::Derived(read_exact_file(path, "CCK")?.into()))?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// add user data
|
||||||
|
let user_data = opt
|
||||||
|
.user_data
|
||||||
|
.as_ref()
|
||||||
|
.map(|p| read_file(p, "user-data"))
|
||||||
|
.transpose()?;
|
||||||
|
if user_data.as_ref().is_some_and(|data| data.is_empty()) {
|
||||||
|
warn!("Added empty user-data file.");
|
||||||
|
}
|
||||||
|
|
||||||
|
let user_key = opt
|
||||||
|
.user_sign_key
|
||||||
|
.as_ref()
|
||||||
|
.map(|p| read_file(p, "User-signing key"))
|
||||||
|
.transpose()?
|
||||||
|
.map(|buf| read_private_key(&buf))
|
||||||
|
.transpose()?;
|
||||||
|
|
||||||
|
if user_data.is_some() || user_key.is_some() {
|
||||||
|
asrcb.set_user_data(user_data.unwrap_or_default(), user_key)?;
|
||||||
|
}
|
||||||
Ok(asrcb)
|
Ok(asrcb)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -144,7 +161,7 @@ fn try_from_val(val: Value) -> anyhow::Result<ConfigUid> {
|
|||||||
.ok_or(anyhow!("No 'cuid' entry found"))?;
|
.ok_or(anyhow!("No 'cuid' entry found"))?;
|
||||||
let cuid = cuid
|
let cuid = cuid
|
||||||
.strip_prefix("0x")
|
.strip_prefix("0x")
|
||||||
.ok_or(anyhow!("Value starts not with 0x".to_string()))?
|
.ok_or(anyhow!("CUID value starts not with 0x".to_string()))?
|
||||||
.to_owned();
|
.to_owned();
|
||||||
if cuid.len() != ::std::mem::size_of::<ConfigUid>() * 2 {
|
if cuid.len() != ::std::mem::size_of::<ConfigUid>() * 2 {
|
||||||
return Err(anyhow!(format!("len invalid ({})", cuid.len())));
|
return Err(anyhow!(format!("len invalid ({})", cuid.len())));
|
||||||
@@ -187,11 +204,18 @@ fn read_and_verify_hkds(
|
|||||||
let certs = read_certs(&hk).with_context(|| {
|
let certs = read_certs(&hk).with_context(|| {
|
||||||
format!("The provided Host Key Document in '{hkd}' is not in PEM or DER format")
|
format!("The provided Host Key Document in '{hkd}' is not in PEM or DER format")
|
||||||
})?;
|
})?;
|
||||||
if certs.len() != 1 {
|
if certs.is_empty() {
|
||||||
warn!("The host key document in '{hkd}' contains more than one certificate!")
|
let msg = format!(
|
||||||
|
"The provided host key document in {} contains no certificate!",
|
||||||
|
hkd
|
||||||
|
);
|
||||||
|
return Err(anyhow!(msg));
|
||||||
|
}
|
||||||
|
if certs.len() > 1 {
|
||||||
|
warn!("The host key document in '{hkd}' contains more than one certificate! Only the first certificate will be used.")
|
||||||
}
|
}
|
||||||
|
|
||||||
// len is 1 -> unwrap will succeed
|
// len is >= 1 -> unwrap will succeed
|
||||||
let c = certs.first().unwrap();
|
let c = certs.first().unwrap();
|
||||||
verifier.verify(c)?;
|
verifier.verify(c)?;
|
||||||
res.push(c.public_key()?);
|
res.push(c.public_key()?);
|
||||||
|
|||||||
46
rust/pvsecret/src/cmd/verify.rs
Normal file
46
rust/pvsecret/src/cmd/verify.rs
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
use anyhow::{anyhow, Context, Result};
|
||||||
|
use log::warn;
|
||||||
|
use pv::{
|
||||||
|
misc::{get_reader_from_cli_file_arg, get_writer_from_cli_file_arg, read_certs, read_file},
|
||||||
|
request::{
|
||||||
|
openssl::pkey::{PKey, Public},
|
||||||
|
uvsecret::verify_asrcb_and_get_user_data,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::cli::VerifyOpt;
|
||||||
|
|
||||||
|
/// read the content of a DER or PEM x509 and return the public key
|
||||||
|
fn read_sgn_key(path: &str) -> Result<PKey<Public>> {
|
||||||
|
read_certs(&read_file(path, "user-signing key")?)?
|
||||||
|
.get(0)
|
||||||
|
.ok_or(anyhow!("File does not contain a X509 certificate"))?
|
||||||
|
.public_key()
|
||||||
|
.map_err(anyhow::Error::new)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn verify(opt: &VerifyOpt) -> Result<()> {
|
||||||
|
let mut rd_in = get_reader_from_cli_file_arg(&opt.input)?;
|
||||||
|
let mut data_in = Vec::with_capacity(0x1000);
|
||||||
|
rd_in
|
||||||
|
.read_to_end(&mut data_in)
|
||||||
|
.with_context(|| format!("Cannot read input file {}", opt.input))?;
|
||||||
|
|
||||||
|
let verify_cert = opt
|
||||||
|
.user_cert
|
||||||
|
.as_ref()
|
||||||
|
.map(|p| read_sgn_key(p))
|
||||||
|
.transpose()
|
||||||
|
.context("Cannot read user-verification certificate.")?;
|
||||||
|
|
||||||
|
let user_data = verify_asrcb_and_get_user_data(data_in, verify_cert)
|
||||||
|
.context("Could not verify the the Add-secret request")?;
|
||||||
|
|
||||||
|
if let Some(user_data) = user_data {
|
||||||
|
get_writer_from_cli_file_arg(&opt.output)?
|
||||||
|
.write_all(&user_data)
|
||||||
|
.with_context(|| format!("Cannot write user data to {}", opt.output))?;
|
||||||
|
}
|
||||||
|
warn!("Successfully verified the request.");
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
@@ -25,6 +25,7 @@ const FEATURES: &[&str] = &[
|
|||||||
"+lock",
|
"+lock",
|
||||||
#[cfg(target_arch = "s390x")]
|
#[cfg(target_arch = "s390x")]
|
||||||
"+list",
|
"+list",
|
||||||
|
"+verify",
|
||||||
];
|
];
|
||||||
|
|
||||||
fn print_error(e: anyhow::Error, verbosity: u8) -> ExitCode {
|
fn print_error(e: anyhow::Error, verbosity: u8) -> ExitCode {
|
||||||
@@ -116,6 +117,7 @@ fn main() -> ExitCode {
|
|||||||
Command::Lock => not_supported(),
|
Command::Lock => not_supported(),
|
||||||
Command::Create(opt) => cmd::create(opt),
|
Command::Create(opt) => cmd::create(opt),
|
||||||
Command::Version => print_version(cli.verbose),
|
Command::Version => print_version(cli.verbose),
|
||||||
|
Command::Verify(opt) => cmd::verify(opt),
|
||||||
};
|
};
|
||||||
|
|
||||||
match res {
|
match res {
|
||||||
|
|||||||
@@ -27,3 +27,36 @@ macro_rules! release_string {
|
|||||||
env!("S390_TOOLS_RELEASE", "env 'S390_TOOLS_RELEASE' must be set for release builds. Trigger build using the s390-tools build system or export the variable yourself")
|
env!("S390_TOOLS_RELEASE", "env 'S390_TOOLS_RELEASE' must be set for release builds. Trigger build using the s390-tools build system or export the variable yourself")
|
||||||
}};
|
}};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Asserts a constant expression evaluates to `true`.
|
||||||
|
///
|
||||||
|
/// If the expression is not evaluated to `true` the compilation will fail.
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! static_assert {
|
||||||
|
($condition:expr) => {
|
||||||
|
const _: () = core::assert!($condition);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Asserts that a type has a specific size.
|
||||||
|
///
|
||||||
|
/// Useful to validate structs that are passed to C code.
|
||||||
|
/// If the size has not the expected value the compilation will fail.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
/// ```rust
|
||||||
|
/// # use utils::assert_size;
|
||||||
|
/// # fn main() {}
|
||||||
|
/// #[repr(C)]
|
||||||
|
/// struct c_struct {
|
||||||
|
/// v: u64,
|
||||||
|
/// }
|
||||||
|
/// assert_size!(c_struct, 8);
|
||||||
|
/// // assert_size!(c_struct, 7);//won't compile
|
||||||
|
/// ```
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! assert_size {
|
||||||
|
($t:ty, $sz:expr ) => {
|
||||||
|
$crate::static_assert!(::std::mem::size_of::<$t>() == $sz);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
#
|
#
|
||||||
# dbginfo.sh - Tool to collect runtime, configuration, and trace information
|
# dbginfo.sh - Tool to collect runtime, configuration, and trace information
|
||||||
#
|
#
|
||||||
# Copyright IBM Corp. 2002, 2023
|
# Copyright IBM Corp. 2002, 2024
|
||||||
#
|
#
|
||||||
# s390-tools is free software; you can redistribute it and/or modify
|
# s390-tools is free software; you can redistribute it and/or modify
|
||||||
# it under the terms of the MIT license. See LICENSE for details.
|
# it under the terms of the MIT license. See LICENSE for details.
|
||||||
@@ -15,6 +15,7 @@ export LC_ALL
|
|||||||
########################################
|
########################################
|
||||||
# Global used variables
|
# Global used variables
|
||||||
readonly SCRIPTNAME="${0##*/}" # general name of this script
|
readonly SCRIPTNAME="${0##*/}" # general name of this script
|
||||||
|
readonly STARTDIR="$(pwd)" # save calling directory
|
||||||
#
|
#
|
||||||
readonly DATETIME="$(date +%Y-%m-%d-%H-%M-%S 2>/dev/null)"
|
readonly DATETIME="$(date +%Y-%m-%d-%H-%M-%S 2>/dev/null)"
|
||||||
readonly DOCKER=$(if type docker >/dev/null 2>&1; then echo "YES"; else echo "NO"; fi)
|
readonly DOCKER=$(if type docker >/dev/null 2>&1; then echo "YES"; else echo "NO"; fi)
|
||||||
@@ -51,6 +52,14 @@ if test "x${PROCESSORVERSION}" = "xFF" || test "x${PROCESSORVERSION}" = "xff"; t
|
|||||||
else
|
else
|
||||||
RUNTIME_ENVIRONMENT="LPAR"
|
RUNTIME_ENVIRONMENT="LPAR"
|
||||||
fi
|
fi
|
||||||
|
# check for Dynamic Partition Mode
|
||||||
|
readonly DPM_UUID=$(grep "LPAR UUID" /proc/sysinfo | \
|
||||||
|
sed 's/.*:[[:space:]]*\([[:graph:]]*\).*/\1/g')
|
||||||
|
if test "${#DPM_UUID}" -eq 0; then
|
||||||
|
DPM_MODE="NO"
|
||||||
|
else
|
||||||
|
DPM_MODE="YES"
|
||||||
|
fi
|
||||||
readonly SYSTEMHOSTNAME="$(hostname -s 2>/dev/null)" # hostname of system being analysed
|
readonly SYSTEMHOSTNAME="$(hostname -s 2>/dev/null)" # hostname of system being analysed
|
||||||
readonly TERMINAL="$(tty 2>/dev/null)"
|
readonly TERMINAL="$(tty 2>/dev/null)"
|
||||||
# timeout seconds TOS / kill timeout TOKS
|
# timeout seconds TOS / kill timeout TOKS
|
||||||
@@ -72,7 +81,7 @@ paramWORKDIR_BASE="/tmp" # initial default path
|
|||||||
print_version() {
|
print_version() {
|
||||||
cat <<EOF
|
cat <<EOF
|
||||||
${SCRIPTNAME}: Debug information script version %S390_TOOLS_VERSION%
|
${SCRIPTNAME}: Debug information script version %S390_TOOLS_VERSION%
|
||||||
Copyright IBM Corp. 2002, 2023
|
Copyright IBM Corp. 2002, 2024
|
||||||
EOF
|
EOF
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -155,7 +164,7 @@ print_check() {
|
|||||||
cat <<EOF
|
cat <<EOF
|
||||||
|
|
||||||
Hardware platform = ${HW}
|
Hardware platform = ${HW}
|
||||||
Runtime environment = ${RUNTIME_ENVIRONMENT}
|
Runtime environment = ${RUNTIME_ENVIRONMENT} - DPM: ${DPM_MODE}
|
||||||
$(cat /proc/sysinfo | grep 'Name')
|
$(cat /proc/sysinfo | grep 'Name')
|
||||||
Kernel version = ${KERNEL_INFO}
|
Kernel version = ${KERNEL_INFO}
|
||||||
OS version / distro = ${OS_NAME}
|
OS version / distro = ${OS_NAME}
|
||||||
@@ -274,6 +283,7 @@ ALL_STEPS="\
|
|||||||
collect_sysfs\
|
collect_sysfs\
|
||||||
collect_procfs\
|
collect_procfs\
|
||||||
collect_configfiles\
|
collect_configfiles\
|
||||||
|
collect_initrd_configfiles\
|
||||||
collect_cmdsout\
|
collect_cmdsout\
|
||||||
collect_hyptop\
|
collect_hyptop\
|
||||||
collect_vmcmdsout\
|
collect_vmcmdsout\
|
||||||
@@ -381,14 +391,19 @@ CONFIGFILES="\
|
|||||||
/boot/loader/entries/*.conf\
|
/boot/loader/entries/*.conf\
|
||||||
/boot/zipl/active_devices.txt\
|
/boot/zipl/active_devices.txt\
|
||||||
/boot/zipl/config\
|
/boot/zipl/config\
|
||||||
|
/conf/\
|
||||||
/etc/*.conf\
|
/etc/*.conf\
|
||||||
/etc/*release\
|
/etc/*release\
|
||||||
/etc/anacrontab\
|
/etc/anacrontab\
|
||||||
/etc/apparmor.d\
|
/etc/apparmor.d\
|
||||||
/etc/auto.*\
|
/etc/auto.*\
|
||||||
|
/etc/cmdline\
|
||||||
|
/etc/cmdline.d\
|
||||||
|
/etc/conf.d\
|
||||||
/etc/cron.*\
|
/etc/cron.*\
|
||||||
/etc/crontab\
|
/etc/crontab\
|
||||||
/etc/crypttab\
|
/etc/crypttab\
|
||||||
|
/etc/dasd.*\
|
||||||
/etc/default\
|
/etc/default\
|
||||||
/etc/depmod.d\
|
/etc/depmod.d\
|
||||||
/etc/dnf/dnf.conf\
|
/etc/dnf/dnf.conf\
|
||||||
@@ -429,12 +444,14 @@ CONFIGFILES="\
|
|||||||
/etc/systemd\
|
/etc/systemd\
|
||||||
/etc/udev*\
|
/etc/udev*\
|
||||||
/etc/xinet.d\
|
/etc/xinet.d\
|
||||||
|
/etc/zfcp.*\
|
||||||
/lib/modprobe.d\
|
/lib/modprobe.d\
|
||||||
$(find /lib/modules -name modules.dep 2>/dev/null)\
|
$(find /lib/modules -name modules.dep 2>/dev/null)\
|
||||||
/lib/systemd/system/docker.service\
|
/lib/systemd/system/docker.service\
|
||||||
|
/lib/udev/rules.d\
|
||||||
/usr/lib/modprobe.d\
|
/usr/lib/modprobe.d\
|
||||||
/usr/lib/modules-load.d/*.conf\
|
/usr/lib/modules-load.d/*.conf\
|
||||||
/usr/lib/systemd/system\
|
/usr/lib/systemd/system/\
|
||||||
/usr/local/lib/modprobe.d\
|
/usr/local/lib/modprobe.d\
|
||||||
/usr/local/lib/modules-load.d/*.conf\
|
/usr/local/lib/modules-load.d/*.conf\
|
||||||
/run/modprobe.d\
|
/run/modprobe.d\
|
||||||
@@ -442,6 +459,7 @@ CONFIGFILES="\
|
|||||||
/run/udev/chreiplzfcpmp-ipl-volume-id\
|
/run/udev/chreiplzfcpmp-ipl-volume-id\
|
||||||
/run/udev/rules.d\
|
/run/udev/rules.d\
|
||||||
/run/zdev_id.env\
|
/run/zdev_id.env\
|
||||||
|
/run/zdev.*\
|
||||||
"
|
"
|
||||||
|
|
||||||
########################################
|
########################################
|
||||||
@@ -904,6 +922,89 @@ collect_configfiles() {
|
|||||||
done
|
done
|
||||||
}
|
}
|
||||||
|
|
||||||
|
########################################
|
||||||
|
collect_initrd_configfiles() {
|
||||||
|
local file_name
|
||||||
|
local rd rdbase rdreldir rdextract mypopd noglob mycat patterns=""
|
||||||
|
|
||||||
|
pr_collect "initrd config files"
|
||||||
|
cd "${WORKPATH}" || return
|
||||||
|
mypopd="$OLDPWD" # (pushd alternative)
|
||||||
|
# prevent expansion of glob special characters such as '*'
|
||||||
|
noglob=$(set +o | grep -F noglob)
|
||||||
|
set -o noglob
|
||||||
|
# prepare relative configfiles paths
|
||||||
|
for file_name in ${CONFIGFILES}; do
|
||||||
|
# strip leading slash to make path relative as in initrd
|
||||||
|
file_name=${file_name#/}
|
||||||
|
# in contrast to call_collect_file(), cpio extract with patterns
|
||||||
|
# does not recurse, but cpio wildcards match slash, so add suffix;
|
||||||
|
# otherwise e.g. /lib/udev/rules.d or /etc/multipath only extracts
|
||||||
|
# just that directory without any of its content
|
||||||
|
file_name="${file_name}*"
|
||||||
|
patterns="$patterns ${file_name}"
|
||||||
|
done
|
||||||
|
patterns="$patterns squash-root.img"
|
||||||
|
$noglob
|
||||||
|
# iterate over initrds
|
||||||
|
for rd in /boot/initr* /boot/zipl/initr* /var/lib/kdump/initr*; do
|
||||||
|
[ -f "$rd" ] || continue # (nullglob alternative)
|
||||||
|
rdbase=${rd##*/} # basename
|
||||||
|
rdreldir=${rd%/*} # dirname
|
||||||
|
rdreldir=${rdreldir#/} # strip leading slash to make path relative
|
||||||
|
mkdir -p "${rdreldir}"
|
||||||
|
cd "${rdreldir}" || break # (pushd alternative)
|
||||||
|
if type lsinitrd >/dev/null 2>&1; then
|
||||||
|
lsinitrd "${rd}" > "${rdbase}.lsinitrd"
|
||||||
|
fi
|
||||||
|
if type lsinitramfs >/dev/null 2>&1; then
|
||||||
|
lsinitramfs -l "${rd}" > "${rdbase}.lsinitramfs"
|
||||||
|
fi
|
||||||
|
cd - || break # change to $OLDPWD (popd alternative)
|
||||||
|
rdextract=${rd#/}
|
||||||
|
mkdir -p "${rdextract}"
|
||||||
|
cd "${rdextract}" || break # (pushd alternative)
|
||||||
|
case $(file "${rd}") in
|
||||||
|
*cpio*) mycat="cat" ;;
|
||||||
|
*gzip*) mycat="zcat" ;;
|
||||||
|
*XZ*) mycat="xzcat" ;;
|
||||||
|
*Zstandard*) mycat="zstdcat" ;;
|
||||||
|
*bzip2*) mycat="bzcat" ;;
|
||||||
|
*LZMA*) mycat="lzcat" ;;
|
||||||
|
*)
|
||||||
|
# change to $OLDPWD (popd alternative)
|
||||||
|
cd - || { cd "$mypopd" || return; return; }
|
||||||
|
continue
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
# prevent expansion of glob special characters such as '*'
|
||||||
|
noglob=$(set +o | grep -F noglob)
|
||||||
|
set -o noglob
|
||||||
|
# we expect no early-initrd stuff on s390 so extract directly.
|
||||||
|
# globbing is turned off and we want word split on $patterns:
|
||||||
|
# shellcheck disable=SC2086
|
||||||
|
"$mycat" "${rd}" | cpio --extract --no-absolute-filenames \
|
||||||
|
--make-directories \
|
||||||
|
--preserve-modification-time $patterns
|
||||||
|
$noglob
|
||||||
|
if [ ! -f "squash-root.img" ]; then
|
||||||
|
cd - || break # change to $OLDPWD (popd alternative)
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
# prevent expansion of glob special characters such as '*'
|
||||||
|
noglob=$(set +o | grep -F noglob)
|
||||||
|
set -o noglob
|
||||||
|
# globbing is turned off and we want word split on $patterns:
|
||||||
|
# shellcheck disable=SC2086
|
||||||
|
unsquashfs -no-progress -dest "squashfs-root" "squash-root.img" \
|
||||||
|
$patterns
|
||||||
|
$noglob
|
||||||
|
rm -f "squash-root.img"
|
||||||
|
cd - || break # change to $OLDPWD (popd alternative)
|
||||||
|
done
|
||||||
|
cd "$mypopd" || return
|
||||||
|
}
|
||||||
|
|
||||||
########################################
|
########################################
|
||||||
collect_osaoat() {
|
collect_osaoat() {
|
||||||
local network_devices
|
local network_devices
|
||||||
@@ -1319,10 +1420,11 @@ environment_setup() {
|
|||||||
create_package() {
|
create_package() {
|
||||||
local rc_tar
|
local rc_tar
|
||||||
pr_syslog_stdout ${step_num} "Finalizing: Creating archive with collected data"
|
pr_syslog_stdout ${step_num} "Finalizing: Creating archive with collected data"
|
||||||
|
# get a copy of the script used - enabled for relative path calls
|
||||||
|
cd "${STARTDIR}"
|
||||||
|
cp -p "${BASH_SOURCE[0]}" "${WORKPATH}"
|
||||||
|
# create the archive
|
||||||
cd "${WORKDIR_BASE}"
|
cd "${WORKDIR_BASE}"
|
||||||
# get a copy of the script used
|
|
||||||
cp -p $0 "${WORKDIR_CURRENT}"
|
|
||||||
|
|
||||||
touch "${WORKARCHIVE}"
|
touch "${WORKARCHIVE}"
|
||||||
chmod 0600 "${WORKARCHIVE}"
|
chmod 0600 "${WORKARCHIVE}"
|
||||||
tar -czf "${WORKARCHIVE}" "${WORKDIR_CURRENT}"
|
tar -czf "${WORKARCHIVE}" "${WORKDIR_CURRENT}"
|
||||||
@@ -1437,7 +1539,7 @@ trap emergency_exit SIGHUP SIGINT SIGTERM
|
|||||||
|
|
||||||
pr_log_stdout ""
|
pr_log_stdout ""
|
||||||
pr_log_stdout "Hardware platform = ${HW}"
|
pr_log_stdout "Hardware platform = ${HW}"
|
||||||
pr_log_stdout "Runtime environment = ${RUNTIME_ENVIRONMENT}"
|
pr_log_stdout "Runtime environment = ${RUNTIME_ENVIRONMENT} - DPM: ${DPM_MODE}"
|
||||||
pr_log_stdout "Kernel version = ${KERNEL_INFO} (${KERNEL_BASE})"
|
pr_log_stdout "Kernel version = ${KERNEL_INFO} (${KERNEL_BASE})"
|
||||||
pr_log_stdout "OS version / distro = ${OS_NAME}"
|
pr_log_stdout "OS version / distro = ${OS_NAME}"
|
||||||
pr_log_stdout "Date and time of info = ${DATETIME}"
|
pr_log_stdout "Date and time of info = ${DATETIME}"
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
.TH DBGINFO.SH 8 "Sep 2022" "s390-tools"
|
.TH DBGINFO.SH 8 "01 2024" "s390-tools"
|
||||||
|
|
||||||
.SH NAME
|
.SH NAME
|
||||||
dbginfo.sh \- collect runtime, configuration and trace information
|
dbginfo.sh \- collect runtime, configuration and trace information
|
||||||
@@ -59,61 +59,63 @@ Sample invocation:
|
|||||||
.br
|
.br
|
||||||
dbginfo.sh: Debug information script version %S390_TOOLS_VERSION%
|
dbginfo.sh: Debug information script version %S390_TOOLS_VERSION%
|
||||||
.br
|
.br
|
||||||
Copyright IBM Corp. 2002, 2022
|
Copyright IBM Corp. 2002, 2024
|
||||||
.PP
|
.PP
|
||||||
Hardware platform = s390x
|
Hardware platform = s390x
|
||||||
.br
|
.br
|
||||||
Runtime environment = z/VM
|
Runtime environment = z/VM - DPM: NO
|
||||||
.br
|
.br
|
||||||
Kernel version = 4.18.0 (4.18.0-305.el8.s390x)
|
Kernel version = 5.14.0 (5.14.0-162.6.1.el9_1.s390x)
|
||||||
.br
|
.br
|
||||||
OS version / distro = Red Hat Enterprise Linux 8.4 (Ootpa)
|
OS version / distro = Red Hat Enterprise Linux 9.1 (Plow)
|
||||||
.br
|
.br
|
||||||
Date and time of info = 2021-08-26-15-27-58
|
Date and time of info = 2023-12-22-11-43-08
|
||||||
.PP
|
.PP
|
||||||
1 of 18: Collecting sysfs
|
1 of 19: Collecting sysfs
|
||||||
.PP
|
.PP
|
||||||
2 of 18: Collecting procfs
|
2 of 19: Collecting procfs
|
||||||
.PP
|
.PP
|
||||||
3 of 18: Collecting config files
|
3 of 19: Collecting config files
|
||||||
.PP
|
.PP
|
||||||
4 of 18: Collecting command output
|
4 of 19: Collecting initrd config files
|
||||||
.PP
|
.PP
|
||||||
5 of 18: Collecting hyptop for z/VM - 5s output
|
5 of 19: Collecting command output
|
||||||
.PP
|
.PP
|
||||||
6 of 18: Collecting z/VM output
|
6 of 19: Collecting hyptop for z/VM - 5s output
|
||||||
.PP
|
.PP
|
||||||
7 of 18: Collecting network output
|
7 of 19: Collecting z/VM output
|
||||||
.PP
|
.PP
|
||||||
8 of 18: Collecting osa oat output
|
8 of 19: Collecting network output
|
||||||
.PP
|
.PP
|
||||||
9 of 18: Collecting ethtool output
|
9 of 19: Collecting osa oat output
|
||||||
.PP
|
.PP
|
||||||
10 of 18: Collecting Trafic Control output
|
10 of 19: Collecting ethtool output
|
||||||
.PP
|
.PP
|
||||||
11 of 18: Collecting bridge output
|
11 of 19: Collecting Trafic Control output
|
||||||
.PP
|
.PP
|
||||||
12 of 18: Skip OpenVSwitch: ovs-vsctl not available
|
12 of 19: Collecting bridge output
|
||||||
.PP
|
.PP
|
||||||
13 of 18: Skip KVM: no virsh command
|
13 of 19: Skip OpenVSwitch: ovs-vsctl not available
|
||||||
.PP
|
.PP
|
||||||
14 of 18: Collecting container host output
|
14 of 19: Skip KVM: no virsh command
|
||||||
|
.PP
|
||||||
|
15 of 19: Collecting container host output
|
||||||
.PP
|
.PP
|
||||||
Kubernetes ...
|
Kubernetes ...
|
||||||
.br
|
.br
|
||||||
15 of 18: Collecting NVME storage output
|
16 of 19: Collecting NVME storage output
|
||||||
.PP
|
.PP
|
||||||
16 of 18: Collecting log files
|
17 of 19: Collecting log files
|
||||||
.PP
|
.PP
|
||||||
0 logfiles over 50 MB
|
0 logfiles over 50 MB
|
||||||
.PP
|
.PP
|
||||||
17 of 18: Postprocessing
|
18 of 19: Postprocessing
|
||||||
.PP
|
.PP
|
||||||
18 of 18: Finalizing: Creating archive with collected data
|
19 of 19: Finalizing: Creating archive with collected data
|
||||||
.PP
|
.PP
|
||||||
Collected data was saved to:
|
Collected data was saved to:
|
||||||
.br
|
.br
|
||||||
>> /data\-collection/DBGINFO\-2021\-08\-26\-15\-27\-58\-host\-012345.tgz <<
|
>> /data\-collection/DBGINFO\-2023\-12\-22\-11\-43\-08\-host\-012345.tgz <<
|
||||||
.br
|
.br
|
||||||
Please review all collected data before sending to your service organization.
|
Please review all collected data before sending to your service organization.
|
||||||
.SH HINTS
|
.SH HINTS
|
||||||
|
|||||||
@@ -7,11 +7,13 @@
|
|||||||
#
|
#
|
||||||
|
|
||||||
[Unit]
|
[Unit]
|
||||||
Description=Apply Control Program Identification (CPI)
|
|
||||||
DefaultDependencies=no
|
|
||||||
Conflicts=shutdown.target
|
|
||||||
After=sysinit.target
|
After=sysinit.target
|
||||||
|
Before=shutdown.target
|
||||||
ConditionPathIsReadWrite=/sys/firmware/cpi
|
ConditionPathIsReadWrite=/sys/firmware/cpi
|
||||||
|
Conflicts=shutdown.target
|
||||||
|
DefaultDependencies=no
|
||||||
|
Description=Apply Control Program Identification (CPI)
|
||||||
|
Requires=sysinit.target
|
||||||
|
|
||||||
[Service]
|
[Service]
|
||||||
Type=oneshot
|
Type=oneshot
|
||||||
|
|||||||
@@ -8,7 +8,8 @@
|
|||||||
#
|
#
|
||||||
# 95zdev-kdump/module_setup.sh
|
# 95zdev-kdump/module_setup.sh
|
||||||
# This module installs configuration files (udev rules and modprobe.conf
|
# This module installs configuration files (udev rules and modprobe.conf
|
||||||
# files) required to enable the kdump target on s390.
|
# files) required to enable the kdump target on s390. In addition,
|
||||||
|
# hooks are installed to parse rd.zfcp= and rd.dasd= kernel parameters.
|
||||||
#
|
#
|
||||||
|
|
||||||
# called by dracut
|
# called by dracut
|
||||||
@@ -16,15 +17,18 @@ check() {
|
|||||||
local _arch=${DRACUT_ARCH:-$(uname -m)}
|
local _arch=${DRACUT_ARCH:-$(uname -m)}
|
||||||
|
|
||||||
# Ensure that we're running on s390
|
# Ensure that we're running on s390
|
||||||
[ "$_arch" = "s390" -o "$_arch" = "s390x" ] || return 1
|
[ "$_arch" = "s390" ] || [ "$_arch" = "s390x" ] || return 1
|
||||||
|
|
||||||
source "$moddir/../95zdev/zdev-lib.sh"
|
# shellcheck source=/dev/null
|
||||||
|
source "${moddir:?}/../95zdev/zdev-lib.sh"
|
||||||
|
|
||||||
# Ensure this module is only included when building kdump initrd
|
# Ensure this module is only included when building kdump initrd
|
||||||
is_kdump || return 1
|
zdev_is_kdump || return 1
|
||||||
|
|
||||||
# Ensure that required tools are available
|
# Ensure that required tools are available
|
||||||
require_binaries chzdev || return 1
|
require_binaries chzdev lszdev || return 1
|
||||||
|
require_binaries sed || return 1
|
||||||
|
require_binaries grep sort uniq || return 1
|
||||||
|
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
@@ -34,6 +38,19 @@ depends() {
|
|||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# called by dracut and (conditionally) locally by install()
|
||||||
|
# Generate rd.zfcp dracut cmdline options for each zfcp-attached
|
||||||
|
# SCSI disk in dracut's device dependency graph (to mount the root-fs,
|
||||||
|
# or to access the kdump target). With "dracut --print-cmdline", dracut
|
||||||
|
# prints the list. With "dracut --hostonly-cmdline", dracut stores the
|
||||||
|
# list inside the generated initrd.
|
||||||
|
cmdline() {
|
||||||
|
# shellcheck disable=SC2154
|
||||||
|
if [[ $hostonly ]]; then
|
||||||
|
for_each_host_dev_and_slaves_all zdev_check_dev | sort | uniq
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
# called by dracut
|
# called by dracut
|
||||||
installkernel() {
|
installkernel() {
|
||||||
# Add modules for all device types supported by chzdev (required for
|
# Add modules for all device types supported by chzdev (required for
|
||||||
@@ -50,9 +67,29 @@ install() {
|
|||||||
# installing avoids error messages from zdev site udev rule processing
|
# installing avoids error messages from zdev site udev rule processing
|
||||||
inst_multiple -o /lib/s390-tools/zdev_id
|
inst_multiple -o /lib/s390-tools/zdev_id
|
||||||
|
|
||||||
|
# Ensure that required tools are available
|
||||||
|
inst_multiple chzdev grep
|
||||||
|
|
||||||
|
# Hook to parse zfcp dracut cmdline parameter
|
||||||
|
inst_hook cmdline 95 "$moddir/../95zdev/parse-zfcp.sh"
|
||||||
|
# Hook to parse dasd dracut cmdline parameter
|
||||||
|
# Must be the first one to use chzdev so dasd_mod is not yet loaded.
|
||||||
|
inst_hook cmdline 94 "$moddir/../95zdev/parse-dasd.sh"
|
||||||
|
inst_multiple /lib/s390-tools/zdev-from-dasd_mod.dasd
|
||||||
|
|
||||||
|
inst_rules "59-dasd.rules"
|
||||||
|
|
||||||
# Obtain kdump target device configuration
|
# Obtain kdump target device configuration
|
||||||
|
|
||||||
_tempfile=$(mktemp --tmpdir dracut-zdev.XXXXXX)
|
# shellcheck disable=SC2154
|
||||||
|
if [[ $hostonly_cmdline == "yes" ]]; then
|
||||||
|
local _rdsomedev
|
||||||
|
for _rdsomedev in $(cmdline); do
|
||||||
|
printf "%s\n" "$_rdsomedev" >> "${initdir:?}/etc/cmdline.d/94zdev.conf"
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
_tempfile=$(mktemp --tmpdir="${DRACUT_TMPDIR}" dracut-zdev.XXXXXX)
|
||||||
|
|
||||||
# work with systems that are not based on chzdev persistent config
|
# work with systems that are not based on chzdev persistent config
|
||||||
local _configuration="--active"
|
local _configuration="--active"
|
||||||
@@ -60,8 +97,8 @@ install() {
|
|||||||
# many zFCP LUNs
|
# many zFCP LUNs
|
||||||
inst_dir /etc/modprobe.d
|
inst_dir /etc/modprobe.d
|
||||||
chzdev zfcp --type "allow_lun_scan=0" --persistent \
|
chzdev zfcp --type "allow_lun_scan=0" --persistent \
|
||||||
--base "/etc=$initdir/etc" --yes --quiet --no-root-update \
|
--base "/etc=${initdir:?}/etc" --yes --no-root-update \
|
||||||
--force >/dev/null
|
--force 2>&1 | ddebug
|
||||||
# drop /etc/zfcp.conf from dracut module 95zfcp
|
# drop /etc/zfcp.conf from dracut module 95zfcp
|
||||||
echo "rd.zfcp.conf=0" > "$initdir/etc/cmdline.d/00-no-zfcp-conf.conf"
|
echo "rd.zfcp.conf=0" > "$initdir/etc/cmdline.d/00-no-zfcp-conf.conf"
|
||||||
# => only activate individual zfcp paths found as required below
|
# => only activate individual zfcp paths found as required below
|
||||||
@@ -80,22 +117,48 @@ install() {
|
|||||||
for_each_host_dev_and_slaves_all check_zdev
|
for_each_host_dev_and_slaves_all check_zdev
|
||||||
sed -i -e 's/^\[active /\[persistent /' "$_tempfile"
|
sed -i -e 's/^\[active /\[persistent /' "$_tempfile"
|
||||||
|
|
||||||
|
# in addition to active devices above, also add persistent config
|
||||||
|
# of devices the user marked for early initrd use
|
||||||
|
chzdev --export - --persistent --by-attrib "zdev:early=1" --quiet \
|
||||||
|
--type 2>/dev/null >> "$_tempfile"
|
||||||
|
|
||||||
|
# site support
|
||||||
|
# Obtain early device configuration for site-specific settings
|
||||||
|
for (( site=0; site<10; site++ ))
|
||||||
|
do
|
||||||
|
chzdev --export - --persistent --by-attrib "zdev:early=1" --site $site \
|
||||||
|
--quiet 2>/dev/null >> "$_tempfile"
|
||||||
|
done
|
||||||
|
|
||||||
|
ddebug < "$_tempfile"
|
||||||
|
|
||||||
# Apply via --import to prevent other devices from being configured
|
# Apply via --import to prevent other devices from being configured
|
||||||
chzdev --import "$_tempfile" --persistent --base "/etc=$initdir/etc" \
|
chzdev --import "$_tempfile" --persistent --base "/etc=$initdir/etc" \
|
||||||
--yes --quiet --no-root-update --force >/dev/null
|
--yes --no-root-update --force 2>&1 | ddebug
|
||||||
|
|
||||||
|
# Apply site-specific configurations via --import
|
||||||
|
for (( site=0; site<10; site++ ))
|
||||||
|
do
|
||||||
|
chzdev --import "$_tempfile" --persistent --base "/etc=$initdir/etc" \
|
||||||
|
--site $site --yes --no-root-update --force 2>&1 | ddebug
|
||||||
|
done
|
||||||
|
|
||||||
|
lszdev --configured --persistent --info \
|
||||||
|
--base "/etc=$initdir/etc" 2>&1 | ddebug
|
||||||
|
|
||||||
rm -f "$_tempfile"
|
rm -f "$_tempfile"
|
||||||
|
|
||||||
# these are purely generated udev rules so we have to glob expand
|
# these are purely generated udev rules so we have to glob expand
|
||||||
# within $initdir and strip the $initdir prefix for mark_hostonly
|
# within $initdir and strip the $initdir prefix for mark_hostonly
|
||||||
local -a _array
|
local -a _array
|
||||||
|
# shellcheck disable=SC2155
|
||||||
local _nullglob=$(shopt -p nullglob)
|
local _nullglob=$(shopt -p nullglob)
|
||||||
shopt -u nullglob
|
shopt -u nullglob
|
||||||
readarray -t _array < \
|
readarray -t _array < \
|
||||||
<(ls -1 $initdir/etc/udev/rules.d/41-*.rules 2> /dev/null)
|
<(ls -1 "$initdir"/etc/udev/rules.d/41-*.rules 2> /dev/null)
|
||||||
[[ ${#_array[@]} -gt 0 ]] && mark_hostonly "${_array[@]#$initdir}"
|
[[ ${#_array[@]} -gt 0 ]] && mark_hostonly "${_array[@]#$initdir}"
|
||||||
readarray -t _array < \
|
readarray -t _array < \
|
||||||
<(ls -1 $initdir/etc/modprobe.d/s390x-*.conf 2> /dev/null)
|
<(ls -1 "$initdir"/etc/modprobe.d/s390x-*.conf 2> /dev/null)
|
||||||
[[ ${#_array[@]} -gt 0 ]] && mark_hostonly "${_array[@]#$initdir}"
|
[[ ${#_array[@]} -gt 0 ]] && mark_hostonly "${_array[@]#$initdir}"
|
||||||
$_nullglob
|
$_nullglob
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
#
|
#
|
||||||
# Copyright IBM Corp. 2016, 2017
|
# Copyright IBM Corp. 2016, 2023
|
||||||
#
|
#
|
||||||
# s390-tools is free software; you can redistribute it and/or modify
|
# s390-tools is free software; you can redistribute it and/or modify
|
||||||
# it under the terms of the MIT license. See LICENSE for details.
|
# it under the terms of the MIT license. See LICENSE for details.
|
||||||
@@ -10,52 +10,100 @@
|
|||||||
# This module installs configuration files (udev rules and modprobe.conf
|
# This module installs configuration files (udev rules and modprobe.conf
|
||||||
# files) required to enable the root device on s390. It will only work when
|
# files) required to enable the root device on s390. It will only work when
|
||||||
# the root device was configured using the chzdev tool. In addition,
|
# the root device was configured using the chzdev tool. In addition,
|
||||||
# a hook is installed to parse rd.zdev= kernel parameters.
|
# hooks are installed to parse rd.zdev= and rd.zfcp= and rd.dasd=
|
||||||
|
# kernel parameters.
|
||||||
#
|
#
|
||||||
|
|
||||||
|
# called by dracut
|
||||||
check() {
|
check() {
|
||||||
local _arch=$(uname -m)
|
local _arch
|
||||||
|
_arch=$(uname -m)
|
||||||
|
|
||||||
# Ensure that we're running on s390
|
# Ensure that we're running on s390
|
||||||
[ "$_arch" = "s390" -o "$_arch" = "s390x" ] || return 1
|
[ "$_arch" = "s390" ] || [ "$_arch" = "s390x" ] || return 1
|
||||||
|
|
||||||
source "$moddir/zdev-lib.sh"
|
# shellcheck source=/dev/null
|
||||||
|
source "${moddir:?}/zdev-lib.sh"
|
||||||
|
|
||||||
# Leave kdump device configuration to module zdev-kdump to
|
# Leave kdump device configuration to module zdev-kdump to
|
||||||
# ensure a minimal device footprint
|
# ensure a minimal device footprint
|
||||||
is_kdump && return 1
|
zdev_is_kdump && return 1
|
||||||
|
|
||||||
# Ensure that required tools are available
|
# Ensure that required tools are available
|
||||||
require_binaries chzdev lszdev /lib/s390-tools/zdev_id || return 1
|
require_binaries chzdev lszdev /lib/s390-tools/zdev_id || return 1
|
||||||
|
require_binaries sed || return 1
|
||||||
|
require_binaries grep sort uniq || return 1
|
||||||
|
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# called by dracut
|
||||||
depends() {
|
depends() {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# called by dracut and (conditionally) locally by install()
|
||||||
|
# Generate rd.zfcp dracut cmdline options for each zfcp-attached
|
||||||
|
# SCSI disk in dracut's device dependency graph (to mount the root-fs,
|
||||||
|
# or to access the kdump target). With "dracut --print-cmdline", dracut
|
||||||
|
# prints the list. With "dracut --hostonly-cmdline" [the case where
|
||||||
|
# install() calls cmdline()], dracut stores the list inside the generated
|
||||||
|
# initrd.
|
||||||
|
cmdline() {
|
||||||
|
# shellcheck disable=SC2154
|
||||||
|
if [[ $hostonly ]]; then
|
||||||
|
for_each_host_dev_and_slaves_all zdev_check_dev | sort | uniq
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# called by dracut
|
||||||
installkernel() {
|
installkernel() {
|
||||||
# Add modules for all device types supported by chzdev (required for
|
# Add modules for all device types supported by chzdev (required for
|
||||||
# auto-configuration)
|
# auto-configuration)
|
||||||
|
hostonly="$(optional_hostonly)" \
|
||||||
instmods ctcm lcs qeth qeth_l2 qeth_l3 dasd_mod dasd_eckd_mod dasd_fba_mod \
|
instmods ctcm lcs qeth qeth_l2 qeth_l3 dasd_mod dasd_eckd_mod dasd_fba_mod \
|
||||||
dasd_diag_mod zfcp
|
dasd_diag_mod zfcp
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# called by dracut
|
||||||
install() {
|
install() {
|
||||||
local _tempfile
|
local _tempfile
|
||||||
|
|
||||||
# Ensure that required tools are available
|
# Ensure that required tools are available
|
||||||
inst_multiple chzdev lszdev vmcp /lib/s390-tools/zdev_id
|
inst_multiple chzdev lszdev vmcp /lib/s390-tools/zdev_id
|
||||||
|
inst_multiple grep
|
||||||
|
|
||||||
# Hook to parse zdev kernel parameter
|
# Hook to parse zdev kernel parameter
|
||||||
inst_hook cmdline 95 "$moddir/parse-zdev.sh"
|
inst_hook cmdline 95 "$moddir/parse-zdev.sh"
|
||||||
|
# Hook to parse zfcp dracut cmdline parameter
|
||||||
|
inst_hook cmdline 95 "$moddir/parse-zfcp.sh"
|
||||||
|
# Hook to parse dasd dracut cmdline parameter
|
||||||
|
# Must be the first one to use chzdev so dasd_mod is not yet loaded.
|
||||||
|
inst_hook cmdline 94 "$moddir/parse-dasd.sh"
|
||||||
|
inst_multiple /lib/s390-tools/zdev-from-dasd_mod.dasd
|
||||||
|
|
||||||
|
inst_hook pre-pivot 95 "$moddir/retain-zdev.sh"
|
||||||
|
|
||||||
# Rule to automatically enable devices when running in DPM
|
# Rule to automatically enable devices when running in DPM
|
||||||
inst_rules "81-dpm.rules"
|
inst_rules "81-dpm.rules"
|
||||||
|
|
||||||
|
inst_rules "59-dasd.rules"
|
||||||
|
|
||||||
# Obtain early + root device configuration
|
# Obtain early + root device configuration
|
||||||
_tempfile=$(mktemp --tmpdir dracut-zdev.XXXXXX)
|
|
||||||
|
# shellcheck disable=SC2154
|
||||||
|
if [[ $hostonly_cmdline == "yes" ]]; then
|
||||||
|
local _rdsomedev
|
||||||
|
for _rdsomedev in $(cmdline); do
|
||||||
|
printf "%s\n" "$_rdsomedev" >> "${initdir:?}/etc/cmdline.d/94zdev.conf"
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
# If enabled, add the host-specific config of required devices into initrd
|
||||||
|
# shellcheck disable=SC2154
|
||||||
|
[[ $hostonly ]] || return 0
|
||||||
|
|
||||||
|
_tempfile=$(mktemp --tmpdir="${DRACUT_TMPDIR}" dracut-zdev.XXXXXX)
|
||||||
function check_zdev() {
|
function check_zdev() {
|
||||||
local _dev=$1
|
local _dev=$1
|
||||||
local _devsysfs _bdevpath
|
local _devsysfs _bdevpath
|
||||||
@@ -63,10 +111,13 @@ install() {
|
|||||||
cd -P /sys/dev/block/"$_dev" && echo "$PWD"
|
cd -P /sys/dev/block/"$_dev" && echo "$PWD"
|
||||||
)
|
)
|
||||||
_bdevpath=/dev/${_devsysfs##*/}
|
_bdevpath=/dev/${_devsysfs##*/}
|
||||||
chzdev --export - --persistent --by-node "$_bdevpath" --quiet \
|
chzdev --export - --active --by-node "$_bdevpath" --quiet \
|
||||||
--type 2>/dev/null >> "$_tempfile"
|
2>/dev/null >> "$_tempfile"
|
||||||
|
#--type # needs a change in chzdev to not have subsequent
|
||||||
|
# import bail out on unknown type properties
|
||||||
}
|
}
|
||||||
for_each_host_dev_and_slaves_all check_zdev
|
for_each_host_dev_and_slaves_all check_zdev
|
||||||
|
sed -i -e 's/^\[active /\[persistent /' "$_tempfile"
|
||||||
|
|
||||||
chzdev --export - --persistent --by-attrib "zdev:early=1" --quiet \
|
chzdev --export - --persistent --by-attrib "zdev:early=1" --quiet \
|
||||||
--type 2>/dev/null >> "$_tempfile"
|
--type 2>/dev/null >> "$_tempfile"
|
||||||
@@ -78,17 +129,36 @@ install() {
|
|||||||
--quiet 2>/dev/null >> "$_tempfile"
|
--quiet 2>/dev/null >> "$_tempfile"
|
||||||
done
|
done
|
||||||
|
|
||||||
|
ddebug < "$_tempfile"
|
||||||
|
|
||||||
# Apply via --import to prevent other devices from being configured
|
# Apply via --import to prevent other devices from being configured
|
||||||
chzdev --import "$_tempfile" --persistent --base "/etc=$initdir/etc" \
|
chzdev --import "$_tempfile" --persistent --base "/etc=${initdir:?}/etc" \
|
||||||
--yes --quiet --no-root-update --force >/dev/null
|
--yes --no-root-update --force 2>&1 | ddebug
|
||||||
# Apply site-specific configurations via --import
|
# Apply site-specific configurations via --import
|
||||||
for (( site=0; site<10; site++ ))
|
for (( site=0; site<10; site++ ))
|
||||||
do
|
do
|
||||||
chzdev --import "$_tempfile" --persistent --base "/etc=$initdir/etc" \
|
chzdev --import "$_tempfile" --persistent --base "/etc=$initdir/etc" \
|
||||||
--site $site --yes --quiet --no-root-update --force >/dev/null
|
--site $site --yes --no-root-update --force 2>&1 | ddebug
|
||||||
done
|
done
|
||||||
|
|
||||||
|
lszdev --configured --persistent --info \
|
||||||
|
--base "/etc=$initdir/etc" 2>&1 | ddebug
|
||||||
|
|
||||||
rm -f "$_tempfile"
|
rm -f "$_tempfile"
|
||||||
|
|
||||||
|
# these are purely generated udev rules so we have to glob expand
|
||||||
|
# within $initdir and strip the $initdir prefix for mark_hostonly
|
||||||
|
local -a _array
|
||||||
|
local _nullglob
|
||||||
|
_nullglob=$(shopt -p nullglob)
|
||||||
|
shopt -u nullglob
|
||||||
|
readarray -t _array < \
|
||||||
|
<(ls -1 "$initdir"/etc/udev/rules.d/41-*.rules 2> /dev/null)
|
||||||
|
[[ ${#_array[@]} -gt 0 ]] && mark_hostonly "${_array[@]#$initdir}"
|
||||||
|
readarray -t _array < \
|
||||||
|
<(ls -1 "$initdir"/etc/modprobe.d/s390x-*.conf 2> /dev/null)
|
||||||
|
[[ ${#_array[@]} -gt 0 ]] && mark_hostonly "${_array[@]#$initdir}"
|
||||||
|
$_nullglob
|
||||||
|
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|||||||
37
zdev/dracut/95zdev/parse-dasd.sh
Normal file
37
zdev/dracut/95zdev/parse-dasd.sh
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
#
|
||||||
|
# Copyright IBM Corp. 2023
|
||||||
|
#
|
||||||
|
# s390-tools is free software; you can redistribute it and/or modify
|
||||||
|
# it under the terms of the MIT license. See LICENSE for details.
|
||||||
|
#
|
||||||
|
# 95zdev/parse-dasd.sh
|
||||||
|
# Parse the command line for rd.dasd parameters. These
|
||||||
|
# parameters are evaluated and used to configure dasd devices.
|
||||||
|
#
|
||||||
|
|
||||||
|
# shellcheck source=/dev/null
|
||||||
|
type zdev_parse_dasd_list > /dev/null 2>&1 || . /lib/s390-tools/zdev-from-dasd_mod.dasd
|
||||||
|
|
||||||
|
# at this point in time dracut's vinfo() only logs to journal which is hard for
|
||||||
|
# s390 users to find and access on a line mode console such as 3215 mode
|
||||||
|
# so use a vinfo alternative that still prints to the console via kmsg
|
||||||
|
zdev_vinfo() {
|
||||||
|
local _zdev_vinfo_line
|
||||||
|
while read -r _zdev_vinfo_line || [ -n "$_zdev_vinfo_line" ]; do
|
||||||
|
# Prefix "<30>" represents facility LOG_DAEMON 3 and loglevel INFO 6:
|
||||||
|
# (facility << 3) | level.
|
||||||
|
echo "<30>dracut: $_zdev_vinfo_line" > /dev/kmsg
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
zdev_parse_rd_dasd() {
|
||||||
|
local _zdev_dasd _zdev_dasd_list
|
||||||
|
for _zdev_dasd in $(getargs rd.dasd -d 'rd_DASD='); do
|
||||||
|
_zdev_dasd_list="${_zdev_dasd_list:+${_zdev_dasd_list},}$_zdev_dasd"
|
||||||
|
done
|
||||||
|
echo "$_zdev_dasd_list"
|
||||||
|
}
|
||||||
|
|
||||||
|
zdev_parse_rd_dasd | zdev_parse_dasd_list globals 2>&1 | zdev_vinfo
|
||||||
|
zdev_parse_rd_dasd | zdev_parse_dasd_list ranges 2>&1 | zdev_vinfo
|
||||||
@@ -37,6 +37,7 @@ for zdev_arg in $(getargs rd.zdev); do
|
|||||||
done
|
done
|
||||||
|
|
||||||
if [ $zdev_auto -eq 1 ] ; then
|
if [ $zdev_auto -eq 1 ] ; then
|
||||||
|
# shellcheck disable=SC2086
|
||||||
chzdev --import "$zdev_fw_file" $zdev_base_args
|
chzdev --import "$zdev_fw_file" $zdev_base_args
|
||||||
|
|
||||||
# Get information about DPM environment
|
# Get information about DPM environment
|
||||||
@@ -50,7 +51,7 @@ if [ $zdev_auto -eq 1 ] ; then
|
|||||||
# work for PCI devices defined before boot because there is no coldplug
|
# work for PCI devices defined before boot because there is no coldplug
|
||||||
# trigger for /sys/bus/pci/slots
|
# trigger for /sys/bus/pci/slots
|
||||||
for slot in /sys/bus/pci/slots/* ; do
|
for slot in /sys/bus/pci/slots/* ; do
|
||||||
read power < "$slot/power"
|
read -r power < "$slot/power"
|
||||||
if [ "$power" = "0" ] ; then
|
if [ "$power" = "0" ] ; then
|
||||||
echo 1 > "$slot/power"
|
echo 1 > "$slot/power"
|
||||||
fi
|
fi
|
||||||
|
|||||||
36
zdev/dracut/95zdev/parse-zfcp.sh
Normal file
36
zdev/dracut/95zdev/parse-zfcp.sh
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
#
|
||||||
|
# Copyright IBM Corp. 2023
|
||||||
|
#
|
||||||
|
# s390-tools is free software; you can redistribute it and/or modify
|
||||||
|
# it under the terms of the MIT license. See LICENSE for details.
|
||||||
|
#
|
||||||
|
# 95zdev/parse-zfcp.sh
|
||||||
|
# Parse the command line for rd.zfcp parameters. These
|
||||||
|
# parameters are evaluated and used to configure zfcp devices.
|
||||||
|
#
|
||||||
|
|
||||||
|
zdev_zfcp_base_args="--no-settle --yes --no-root-update --force"
|
||||||
|
|
||||||
|
for zdev_zfcp_arg in $(getargs rd.zfcp -d 'rd_ZFCP='); do
|
||||||
|
(
|
||||||
|
IFS_SAVED="$IFS"
|
||||||
|
IFS="," # did not work in front of built-in set command below
|
||||||
|
# shellcheck disable=SC2086
|
||||||
|
set -- $zdev_zfcp_arg
|
||||||
|
IFS=":" args="$*"
|
||||||
|
IFS="$IFS_SAVED"
|
||||||
|
echo "rd.zfcp ${zdev_zfcp_arg} :" | zdev_vinfo
|
||||||
|
if [ "$#" -eq 1 ]; then
|
||||||
|
# shellcheck disable=SC2086
|
||||||
|
chzdev --enable --persistent $zdev_zfcp_base_args \
|
||||||
|
zfcp-host "$args" 2>&1 | zdev_vinfo
|
||||||
|
else
|
||||||
|
# shellcheck disable=SC2086
|
||||||
|
chzdev --enable --persistent $zdev_zfcp_base_args \
|
||||||
|
zfcp-lun "$args" 2>&1 | zdev_vinfo
|
||||||
|
fi
|
||||||
|
)
|
||||||
|
done
|
||||||
|
unset zdev_zfcp_arg
|
||||||
|
unset zdev_zfcp_base_args
|
||||||
26
zdev/dracut/95zdev/retain-zdev.sh
Normal file
26
zdev/dracut/95zdev/retain-zdev.sh
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
#
|
||||||
|
# Copyright IBM Corp. 2023
|
||||||
|
#
|
||||||
|
# s390-tools is free software; you can redistribute it and/or modify
|
||||||
|
# it under the terms of the MIT license. See LICENSE for details.
|
||||||
|
#
|
||||||
|
# 95zdev/retain-zdev.sh
|
||||||
|
# Copy zdev persistent config from initrd to root-fs.
|
||||||
|
#
|
||||||
|
|
||||||
|
chzdev --export /run/zdev.initrd.config --all --type --persistent --quiet
|
||||||
|
|
||||||
|
# Apart from debugging purposes, this is useful for distro installers,
|
||||||
|
# which can import the early config into their own environment early
|
||||||
|
# after starting:
|
||||||
|
# chzdev --import /run/zdev.initrd.config --persistent --yes --no-root-update --force --verbose
|
||||||
|
#
|
||||||
|
# After that, distro installers can modify/add the device config based
|
||||||
|
# on interactive or unattended installation choices using
|
||||||
|
# chzdev --enable --active --persistent ...
|
||||||
|
#
|
||||||
|
# Finally, distro installers can likewise transfer the entire device config
|
||||||
|
# to the installed system mounted under $SYSROOT:
|
||||||
|
# chzdev --export /tmp/zdev.config --all --type --persistent --verbose
|
||||||
|
# chzdev --import /tmp/zdev.config --persistent --yes --no-root-update --force --verbose --base "$SYSROOT"
|
||||||
@@ -10,16 +10,101 @@
|
|||||||
#
|
#
|
||||||
# shellcheck shell=bash
|
# shellcheck shell=bash
|
||||||
|
|
||||||
is_kdump() {
|
zdev_is_kdump() {
|
||||||
# https://src.fedoraproject.org/rpms/kexec-tools/c/4eedcae5e1540690a3761857fe2e692774c44960
|
# https://src.fedoraproject.org/rpms/kexec-tools/c/4eedcae5e1540690a3761857fe2e692774c44960
|
||||||
# https://src.fedoraproject.org/rpms/kexec-tools/blob/rawhide/f/mkdumprd
|
# https://src.fedoraproject.org/rpms/kexec-tools/blob/rawhide/f/mkdumprd
|
||||||
# https://src.fedoraproject.org/rpms/kexec-tools/blob/rawhide/f/dracut-module-setup.sh
|
# https://src.fedoraproject.org/rpms/kexec-tools/blob/rawhide/f/dracut-module-setup.sh
|
||||||
|
# shellcheck disable=SC2154
|
||||||
if [[ $hostonly && "$hostonly_mode" == "strict" && -n "$IN_KDUMP" ]]; then
|
if [[ $hostonly && "$hostonly_mode" == "strict" && -n "$IN_KDUMP" ]]; then
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
# https://github.com/openSUSE/kdump/blob/master/dracut/module-setup.sh
|
# https://github.com/openSUSE/kdump/blob/master/dracut/module-setup.sh
|
||||||
|
# shellcheck disable=SC2154
|
||||||
if [[ " $dracutmodules $add_dracutmodules $force_add_dracutmodules " == *\ kdump\ * ]]; then
|
if [[ " $dracutmodules $add_dracutmodules $force_add_dracutmodules " == *\ kdump\ * ]]; then
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
zdev_zfcp_auto_lun_scan_active() {
|
||||||
|
local _fcpdevsysfs=$1
|
||||||
|
local _porttype _lunscan
|
||||||
|
read -r _porttype < "$_fcpdevsysfs"/host*/fc_host/host*/port_type
|
||||||
|
read -r _lunscan < /sys/module/zfcp/parameters/allow_lun_scan
|
||||||
|
if [[ "$_porttype" == "NPIV VPORT" && "$_lunscan" == "Y" ]]; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# zdev_check_dev() can be extended for other device types such as PCIe
|
||||||
|
zdev_check_dev() {
|
||||||
|
local _dev=$1
|
||||||
|
local _devsysfs _devtype _subsystem _driver
|
||||||
|
local _intlun _fcplun _scsitarget _wwpn _hbaid
|
||||||
|
local _busid _arg
|
||||||
|
_devsysfs=$(
|
||||||
|
cd -P /sys/dev/block/"$_dev" 2> /dev/null && echo "$PWD"
|
||||||
|
)
|
||||||
|
# This is roughly what systemd's udev-builtin-path_id does:
|
||||||
|
while [[ -n "$_devsysfs" ]]; do
|
||||||
|
# ascend to parent: strip last path part
|
||||||
|
_devsysfs=${_devsysfs%/*}
|
||||||
|
_subsystem=$(
|
||||||
|
cd -P "$_devsysfs"/subsystem 2> /dev/null && echo "$PWD"
|
||||||
|
)
|
||||||
|
if [[ "${_subsystem##*/}" == "scsi" ]]; then
|
||||||
|
_devtype=$(grep "^DEVTYPE=" "$_devsysfs"/uevent)
|
||||||
|
# check for FCP LUN
|
||||||
|
if [[ "$_devtype" == "DEVTYPE=scsi_device" ]]; then
|
||||||
|
_intlun=${_devsysfs##*:}
|
||||||
|
# convert _intlun to _fcplun [int_to_scsilun()]
|
||||||
|
_fcplun=0
|
||||||
|
((_fcplun |= (_intlun & 0x000000000000ffff) << 48))
|
||||||
|
((_fcplun |= (_intlun & 0x00000000ffff0000) << 16))
|
||||||
|
((_fcplun |= (_intlun & 0x0000ffff00000000) >> 16))
|
||||||
|
((_fcplun |= (_intlun & 0xffff000000000000) >> 48))
|
||||||
|
printf -v _fcplun "0x%016x" "$_fcplun"
|
||||||
|
# bail out if not scsi_transport_fc
|
||||||
|
[[ "${_devsysfs/*rport-*/FOUND}" == "FOUND" ]] || return 0
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
# check for target WWPN
|
||||||
|
if [[ "$_devtype" == "DEVTYPE=scsi_target" ]]; then
|
||||||
|
_scsitarget=${_devsysfs##*/}
|
||||||
|
read -r _wwpn < "$_devsysfs/fc_transport/$_scsitarget/port_name"
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
if [[ "${_subsystem##*/}" == "ccw" ]]; then
|
||||||
|
_driver=$(
|
||||||
|
cd -P "$_devsysfs"/driver 2> /dev/null && echo "$PWD"
|
||||||
|
)
|
||||||
|
# check for FCP device (vHBA) bus-ID
|
||||||
|
if [[ "${_driver##*/}" == "zfcp" ]]; then
|
||||||
|
_hbaid=${_devsysfs##*/}
|
||||||
|
# drop full path and use zfcp auto LUN scan, if:
|
||||||
|
# - not building for kdump which has zfcp auto LUN scan off
|
||||||
|
# - and zfcp auto LUN scan is available
|
||||||
|
if ! zdev_is_kdump && \
|
||||||
|
zdev_zfcp_auto_lun_scan_active "$_devsysfs"; then
|
||||||
|
unset _wwpn
|
||||||
|
unset _fcplun
|
||||||
|
fi
|
||||||
|
if [[ -n "$_wwpn" ]] && [[ -n "$_fcplun" ]]; then
|
||||||
|
printf " rd.zfcp=%s,%s,%s\n" "$_hbaid" "$_wwpn" "$_fcplun"
|
||||||
|
else
|
||||||
|
printf " rd.zfcp=%s\n" "$_hbaid"
|
||||||
|
fi
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
# check for DASD device bus-ID
|
||||||
|
if [[ "${_driver##*/}" == dasd-* ]]; then
|
||||||
|
_busid=${_devsysfs##*/}
|
||||||
|
_arg=$(/lib/s390-tools/zdev-to-dasd_mod.dasd "active" "$_busid")
|
||||||
|
printf " rd.dasd=%s\n" "$_arg"
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|||||||
@@ -21,6 +21,9 @@ install:
|
|||||||
$(INSTALL) -m 755 $(ZDEVDIR)/module-setup.sh \
|
$(INSTALL) -m 755 $(ZDEVDIR)/module-setup.sh \
|
||||||
$(ZDEVDIR)/parse-zdev.sh \
|
$(ZDEVDIR)/parse-zdev.sh \
|
||||||
$(ZDEVDIR)/zdev-lib.sh \
|
$(ZDEVDIR)/zdev-lib.sh \
|
||||||
|
$(ZDEVDIR)/parse-zfcp.sh \
|
||||||
|
$(ZDEVDIR)/parse-dasd.sh \
|
||||||
|
$(ZDEVDIR)/retain-zdev.sh \
|
||||||
$(DESTDIR)$(DRACUTMODDIR)/$(ZDEVDIR)/
|
$(DESTDIR)$(DRACUTMODDIR)/$(ZDEVDIR)/
|
||||||
$(INSTALL) -m 755 -d $(DESTDIR)$(DRACUTMODDIR)/$(ZDEVKDUMPDIR)
|
$(INSTALL) -m 755 -d $(DESTDIR)$(DRACUTMODDIR)/$(ZDEVKDUMPDIR)
|
||||||
$(INSTALL) -m 755 $(ZDEVKDUMPDIR)/module-setup.sh \
|
$(INSTALL) -m 755 $(ZDEVKDUMPDIR)/module-setup.sh \
|
||||||
|
|||||||
@@ -571,7 +571,7 @@ portion is replaced with
|
|||||||
.IR VALUE .
|
.IR VALUE .
|
||||||
|
|
||||||
.B Example:
|
.B Example:
|
||||||
.CL chzdev -dasd-eckd 1000 -e -p --base /etc=/mnt/etc
|
.CL chzdev dasd-eckd 1000 -e -p --base /etc=/mnt/etc
|
||||||
.PP
|
.PP
|
||||||
.
|
.
|
||||||
.OD dry-run "" ""
|
.OD dry-run "" ""
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user