mirror of
https://github.com/ibm-s390-linux/s390-tools.git
synced 2026-08-05 02:14:52 +00:00
Compare commits
120 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c217f6be6a | ||
|
|
21662d38e6 | ||
|
|
19f3842292 | ||
|
|
ae0cbf00b1 | ||
|
|
9019c6864a | ||
|
|
d1b61c37fa | ||
|
|
32b68a5fad | ||
|
|
55fdb17b18 | ||
|
|
af730c79a6 | ||
|
|
041e6131d1 | ||
|
|
5a7d7e05b8 | ||
|
|
71b93d55ef | ||
|
|
d2b5e1e2d6 | ||
|
|
a3cb877c54 | ||
|
|
6895a71cc4 | ||
|
|
d9034b01f1 | ||
|
|
488ac8c3f2 | ||
|
|
ecf36d53c8 | ||
|
|
893ad920c5 | ||
|
|
0695c79f4e | ||
|
|
8837ea24cb | ||
|
|
65222d03b9 | ||
|
|
54937495e2 | ||
|
|
8a783b81a4 | ||
|
|
093da2a5a7 | ||
|
|
b68ea5fc7d | ||
|
|
90c587408f | ||
|
|
90475fbaa5 | ||
|
|
07ff9e1da0 | ||
|
|
5637799c92 | ||
|
|
73c82441e7 | ||
|
|
6d06921276 | ||
|
|
2996b34ddf | ||
|
|
e5821301f6 | ||
|
|
f4d1874ac5 | ||
|
|
f3428929a2 | ||
|
|
263d6950a1 | ||
|
|
8024f8e31a | ||
|
|
3849b29594 | ||
|
|
0e4d4da0e5 | ||
|
|
ca3cd51f91 | ||
|
|
fda1e0d33d | ||
|
|
1a850392bc | ||
|
|
14a79eb142 | ||
|
|
271b809495 | ||
|
|
2363269c1c | ||
|
|
f1db473d11 | ||
|
|
e35d05a5e3 | ||
|
|
c61783546b | ||
|
|
c08794bdfb | ||
|
|
4905975f81 | ||
|
|
d53bfb9201 | ||
|
|
0dac47cb62 | ||
|
|
7b68552359 | ||
|
|
775495c7e7 | ||
|
|
1057f13cdc | ||
|
|
ce59a299cb | ||
|
|
8235e025d4 | ||
|
|
b301381f90 | ||
|
|
c62f930634 | ||
|
|
85eb44ac95 | ||
|
|
d5f8063900 | ||
|
|
ee66929465 | ||
|
|
1b044b8a40 | ||
|
|
f46f6d34d3 | ||
|
|
3a96e8826f | ||
|
|
84738668ca | ||
|
|
dbea311aa8 | ||
|
|
d9ce54dee3 | ||
|
|
7b056735ed | ||
|
|
33fde99138 | ||
|
|
4b486e87cc | ||
|
|
a07d1bca74 | ||
|
|
5e1ef90962 | ||
|
|
8fe214d915 | ||
|
|
bec9d1dfcd | ||
|
|
694d5d4638 | ||
|
|
a6ac7cd876 | ||
|
|
28751a097a | ||
|
|
19c795be0d | ||
|
|
dd82c26f87 | ||
|
|
c6f621d0dc | ||
|
|
e6add997eb | ||
|
|
a2baeb2fe2 | ||
|
|
a500946e50 | ||
|
|
b7c9c2679e | ||
|
|
bc8a14895a | ||
|
|
4ae68d0430 | ||
|
|
aabf97f885 | ||
|
|
a9b546cb0f | ||
|
|
5566c31458 | ||
|
|
ff96d6158e | ||
|
|
2aa9071aed | ||
|
|
adf2a030e6 | ||
|
|
0783aa99d7 | ||
|
|
5a848c98bb | ||
|
|
e98f9b9c4a | ||
|
|
faac2520c9 | ||
|
|
3d098416c6 | ||
|
|
7e53611e3a | ||
|
|
d8496160cb | ||
|
|
c7f10bc76d | ||
|
|
f8910caa59 | ||
|
|
b1d948daef | ||
|
|
4d4ddbd887 | ||
|
|
d205b47c08 | ||
|
|
2b7df1fcac | ||
|
|
53eccc0a3d | ||
|
|
951cf9d7b0 | ||
|
|
87136bb0d0 | ||
|
|
eb06ebe245 | ||
|
|
b06ca88cd4 | ||
|
|
5af2e30d9a | ||
|
|
ea3529e624 | ||
|
|
0209c11bc1 | ||
|
|
6a24660472 | ||
|
|
a3bc87d87d | ||
|
|
85493a2581 | ||
|
|
ad544565fe | ||
|
|
5e135a9daf |
5
.codespell.ignore
Normal file
5
.codespell.ignore
Normal file
@@ -0,0 +1,5 @@
|
||||
parm
|
||||
parms
|
||||
crate
|
||||
ser
|
||||
deriver
|
||||
@@ -1,5 +1,4 @@
|
||||
[codespell]
|
||||
ignore-words-list = parm,parms
|
||||
skip = ''
|
||||
ignore-words = .codespell.ignore
|
||||
count = ''
|
||||
quiet-level = 3
|
||||
|
||||
@@ -8,6 +8,12 @@ insert_final_newline = true
|
||||
charset = utf-8
|
||||
indent_style = tab
|
||||
tab_width = 8
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[*.rs]
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
tab_width = 4
|
||||
|
||||
[*.sh]
|
||||
shell_variant = bash # used by `shfmt`
|
||||
@@ -19,3 +25,9 @@ indent_size = 2
|
||||
[*.py]
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
|
||||
[{Makefile,*.mak}]
|
||||
indent_style = tab
|
||||
|
||||
[{COMMIT_EDITMSG,EDIT_DESCRIPTION}]
|
||||
max_line_length = 72
|
||||
|
||||
12
.gitignore
vendored
12
.gitignore
vendored
@@ -9,6 +9,10 @@
|
||||
tags
|
||||
TAGS
|
||||
|
||||
# compile_commands.json
|
||||
# (https://clang.llvm.org/docs/JSONCompilationDatabase.html)
|
||||
compile_commands.json
|
||||
|
||||
# clangd cache (https://clangd.llvm.org/design/indexing#backgroundindex)
|
||||
.cache/
|
||||
|
||||
@@ -19,6 +23,8 @@ TAGS
|
||||
#
|
||||
# Ignore generated executables and other generated files
|
||||
#
|
||||
**/.detect-openssl.dep.c
|
||||
*.debug
|
||||
ap_tools/ap-check
|
||||
cmsfs-fuse/cmsfs-fuse
|
||||
cpacfstats/cpacfstats
|
||||
@@ -26,6 +32,7 @@ cpacfstats/cpacfstatsd
|
||||
cpumf/chcpumf
|
||||
cpumf/lscpumf
|
||||
cpumf/lshwc
|
||||
cpumf/lspai
|
||||
cpumf/pai
|
||||
cpuplugd/cpuplugd
|
||||
dasdfmt/dasdfmt
|
||||
@@ -50,8 +57,8 @@ iucvterm/src/iucvconn
|
||||
iucvterm/src/iucvtty
|
||||
iucvterm/src/ttyrun
|
||||
iucvterm/test/test_afiucv
|
||||
libap/check-dep-lock
|
||||
libap/check-dep-json
|
||||
libap/check-dep-lock
|
||||
libekmfweb/check-dep-libekmfweb
|
||||
libekmfweb/detect-openssl-version.dep
|
||||
libekmfweb/libekmfweb.so
|
||||
@@ -96,6 +103,7 @@ zdev/src/chzdev
|
||||
zdev/src/chzdev_usage.c
|
||||
zdev/src/lszdev
|
||||
zdev/src/lszdev_usage.c
|
||||
zdev/src/zdev_id
|
||||
zdsfs/zdsfs
|
||||
zdump/.check_dep_fuse
|
||||
zdump/.check_dep_zgetdump
|
||||
@@ -115,7 +123,6 @@ zipl/boot/*.exec
|
||||
zipl/boot/.loaders
|
||||
zipl/boot/data.h
|
||||
zipl/src/chreipl_helper.device-mapper
|
||||
zdev/src/zdev_id
|
||||
zipl/src/zipl
|
||||
zipl/src/zipl-editenv
|
||||
zipl/src/zipl_helper.device-mapper
|
||||
@@ -129,4 +136,3 @@ zkey/kmip/zkey-kmip.so
|
||||
zkey/zkey
|
||||
zkey/zkey-cryptsetup
|
||||
zpcictl/zpcictl
|
||||
**/.detect-openssl.dep.c
|
||||
|
||||
12
.rustfmt.toml
Normal file
12
.rustfmt.toml
Normal file
@@ -0,0 +1,12 @@
|
||||
edition = "2021"
|
||||
newline_style = "Unix"
|
||||
|
||||
# Unstable options that help catching some mistakes in formatting and that we may want to enable
|
||||
# when they become stable.
|
||||
#
|
||||
# They are kept here since they are useful to run from time to time.
|
||||
#format_code_in_doc_comments = true
|
||||
#reorder_impl_items = true
|
||||
#comment_width = 100
|
||||
#wrap_comments = true
|
||||
#normalize_comments = true
|
||||
5
.shellcheckrc
Normal file
5
.shellcheckrc
Normal file
@@ -0,0 +1,5 @@
|
||||
# Search in the current script's directory by default (since 0.7.0)
|
||||
source-path=SCRIPTDIR
|
||||
|
||||
# Allow external-sources (since 0.8.0)
|
||||
external-sources=true
|
||||
@@ -27,6 +27,7 @@ List of all individuals having contributed content to s390-tools
|
||||
- Eberhard Pasch
|
||||
- Eduard Shishkin
|
||||
- Einar Lueck
|
||||
- Eric Farman
|
||||
- Eric Sandeen
|
||||
- Erwin Vicari
|
||||
- Eugene Crosser
|
||||
@@ -56,6 +57,7 @@ List of all individuals having contributed content to s390-tools
|
||||
- Horst Hummel
|
||||
- Ingo Franzki
|
||||
- Ingo Tuchscherer
|
||||
- Jakub Čajka
|
||||
- Jan Glauber
|
||||
- Jan Höppner
|
||||
- Jan Willeke
|
||||
@@ -124,6 +126,7 @@ List of all individuals having contributed content to s390-tools
|
||||
- Thomas Richter
|
||||
- Thomas Spatzier
|
||||
- Thomas Weber
|
||||
- Thorsten Winkler
|
||||
- Tuan Hoang
|
||||
- Ursula Braun
|
||||
- Utz Bacher
|
||||
|
||||
53
CHANGELOG.md
53
CHANGELOG.md
@@ -1,6 +1,59 @@
|
||||
Release history for s390-tools (MIT version)
|
||||
--------------------------------------------
|
||||
|
||||
* __v2.30.0 (2023-12-01)__
|
||||
|
||||
For Linux kernel version: 6.6
|
||||
|
||||
Add new tools / libraries:
|
||||
- lspai: Tool to display PAI counter sets
|
||||
- s390-tools: Provide a ShellCheck configuration
|
||||
|
||||
Changes of existing tools / libraries:
|
||||
- cpumf/pai: Add command line option for realtime scheduling
|
||||
- dbginfo.sh: enhance ethtool collection for ROCE
|
||||
- libutil/util_lockfile: add routine to return owning pid of file lock
|
||||
- lszcrypt: Improve lszcrypt output on SE guests
|
||||
- rust: Use a single workspace for all rust tools
|
||||
- zdev: limit the derivation of ZDEV_SITE_ID
|
||||
- zdump/df_s390: Update 'zgetdump -i' output with zlib info
|
||||
- zdump/dfi_s390: Support reading compressed s390_ext dumps
|
||||
- zipl/boot: Integrate zlib compression to single volume DASD dumper
|
||||
- zipl/boot: compile the bootloaders only if HOST_ARCH is s390x
|
||||
- zipl: Add --no-compress option to zipl command
|
||||
- zkey: Also check for deconfigured and check-stopped cards
|
||||
|
||||
Bug Fixes:
|
||||
- ap_tools/ap-check: handle get-attributes between pre and post event
|
||||
- libutil: fix util_file_read_*() using wrong format specifiers
|
||||
- rust/pv: fix Invalid write of size 1
|
||||
|
||||
* __v2.29.0 (2023-08-04)__
|
||||
|
||||
For Linux kernel version: 6.5
|
||||
|
||||
General:
|
||||
- s390-tools now supports tools written in Rust.
|
||||
- Add `compdb` Makefile target to create 'compile_commands.json' to LSP
|
||||
backends in IDEs and editors
|
||||
|
||||
Add new tools / libraries:
|
||||
- rust/pv: Library for pv tools written in rust
|
||||
- rust/pvsecret: Tool to manage UV-secrets
|
||||
|
||||
Changes of existing tools:
|
||||
- dbginfo.sh: Global IFS variable
|
||||
- genprotimg: Add support for add-secret requests
|
||||
- genprotimg: Build debuginfo files for bootloader
|
||||
- hyptop: Add real SMT utilization field
|
||||
- hyptop: Allow users to set speedup factor
|
||||
- pvattest: Add yaml-output for verify command
|
||||
- zipl: Build debuginfo files for bootloader
|
||||
|
||||
Bug Fixes:
|
||||
- dump2tar: Fix truncated paths
|
||||
- zdev/dracut: fix kdump build to integrate with site support
|
||||
|
||||
* __v2.28.0 (2023-07-11)__
|
||||
|
||||
For Linux kernel version: 6.4
|
||||
|
||||
5
Makefile
5
Makefile
@@ -15,11 +15,12 @@ TOOL_DIRS = zipl zdump fdasd dasdfmt dasdview tunedasd \
|
||||
vmcp man mon_tools dasdinfo vmur cpuplugd ipl_tools \
|
||||
ziomon iucvterm hyptop cmsfs-fuse qethqoat zfcpdump zdsfs cpumf \
|
||||
systemd hmcdrvfs cpacfstats zdev dump2tar zkey netboot etc zpcictl \
|
||||
genprotimg lsstp hsci hsavmcore chreipl-fcp-mpath ap_tools pvattest
|
||||
genprotimg lsstp hsci hsavmcore chreipl-fcp-mpath ap_tools pvattest \
|
||||
rust
|
||||
else
|
||||
BASELIB_DIRS =
|
||||
LIB_DIRS = libpv
|
||||
TOOL_DIRS = genprotimg pvattest
|
||||
TOOL_DIRS = genprotimg pvattest rust
|
||||
endif
|
||||
|
||||
SUB_DIRS = $(BASELIB_DIRS) $(LIB_DIRS) $(TOOL_DIRS)
|
||||
|
||||
37
README.md
37
README.md
@@ -15,6 +15,11 @@ The package also contains the following files:
|
||||
Package contents
|
||||
----------------
|
||||
|
||||
* rust:
|
||||
all s390-tools that are written in rust and require external crates.
|
||||
Disable the compilation of all tools in `rust/` using HAVE_CARGO=0
|
||||
See the `rust/README.md` for Details
|
||||
|
||||
* dasdfmt:
|
||||
Low-level format ECKD DASDs with the classical Linux disk layout or the new
|
||||
z/OS compatible disk layout.
|
||||
@@ -305,13 +310,14 @@ build options:
|
||||
| net-snmp | `HAVE_SNMP` | osasnmpd |
|
||||
| glibc-static | `HAVE_LIBC_STATIC` | zfcpdump |
|
||||
| openssl | `HAVE_OPENSSL` | genprotimg, zkey, libekmfweb, |
|
||||
| | | libkmipclient, pvattest, zgetdump |
|
||||
| | | libkmipclient, pvattest, zgetdump, |
|
||||
| | | rust/pvsecret, |
|
||||
| cryptsetup | `HAVE_CRYPTSETUP2` | zkey-cryptsetup |
|
||||
| json-c | `HAVE_JSONC` | zkey-cryptsetup, libekmfweb, |
|
||||
| | | libkmipclient |
|
||||
| glib2 | `HAVE_GLIB2` | genprotimg, pvattest, zgetdump |
|
||||
| libcurl | `HAVE_LIBCURL` | genprotimg, libekmfweb, libkmipclient,|
|
||||
| | | pvattest |
|
||||
| | | pvattest, rust/pvsecret, |
|
||||
| libxml2 | `HAVE_LIBXML2` | libkmipclient |
|
||||
| systemd | `HAVE_SYSTEMD` | hsavmcore |
|
||||
| libudev | `HAVE_LIBUDEV` | cpacfstatsd |
|
||||
@@ -324,6 +330,7 @@ This table lists additional build or install options:
|
||||
| | | zipl |
|
||||
| initramfs-tools | `HAVE_INITRAMFS` | zdev, zipl |
|
||||
| | `ZDEV_ALWAYS_UPDATE_INITRD` | zdev |
|
||||
| rust | `HAVE_CARGO` | rust/* |
|
||||
|
||||
The s390-tools build process uses "pkg-config" and therefore it must be
|
||||
available.
|
||||
@@ -334,6 +341,14 @@ Build and runtime requirements for specific tools
|
||||
In the following more details on the build an runtime requirements of
|
||||
the different tools are provided:
|
||||
|
||||
* rust/pvsecret:
|
||||
For building pvsecret you need OpenSSL version 1.1.1 or newer
|
||||
installed (openssl-devel.rpm). Also required is cargo and libcurl.
|
||||
Tip: you may skip the pvsecret build by adding
|
||||
`HAVE_OPENSSL=0`, `HAVE_LIBCURL=0`, or `HAVE_CARGO=0`.
|
||||
|
||||
The runtime requirements are: openssl-libs (>= 1.1.1).
|
||||
|
||||
* dbginfo.sh:
|
||||
The tar package is required to archive collected data.
|
||||
|
||||
@@ -371,6 +386,24 @@ the different tools are provided:
|
||||
- Packages: blktrace, multipath-tools, sg3-utils
|
||||
- Tools: rsync, tar, lsscsi
|
||||
|
||||
* zipl
|
||||
For CCW-type DASD dump, zlib compression can be used to compress the dump
|
||||
data before writing it to the DASD partition. It can benefit from
|
||||
s390 on-chip compression accelerator (DFLTCC) and provide a faster dumping
|
||||
process, hence lower system downtime.
|
||||
The zlib version integrated with zipl (zipl/boot/zlib) is based on the Linux
|
||||
kernel zlib (kernel version 6.3) which represents zlib version 1.1.3 with a
|
||||
limited number of functions and a number of updates on top including s390
|
||||
hardware compression (DFLTCC) support. Also, all memory allocations are
|
||||
performed in advance, which aligns with zipl requirements.
|
||||
The CCW-type standalone dumper is built as a single binary and must be
|
||||
loaded to stage2 during boot. Hence, all required zlib functions must be
|
||||
integrated into it, and its size is restricted. To limit the size, only
|
||||
deflate-related parts are integrated (no decompression is required during
|
||||
dumping).
|
||||
Removing the inflate modules and function prototypes are the only major
|
||||
modifications made to the kernel version of zlib.
|
||||
|
||||
* zgetdump
|
||||
For building zgetdump you need OpenSSL version 1.1.0 or newer
|
||||
installed (openssl-devel.rpm). Also required is glib2
|
||||
|
||||
@@ -798,12 +798,34 @@ static int ap_check_handle_get_attributes(struct ap_check_anchor *anc)
|
||||
FILE *f;
|
||||
int rc;
|
||||
|
||||
rc = ap_get_lock_callout();
|
||||
if (rc) {
|
||||
fprintf(stderr, "Failed to acquire configuration lock %d\n", rc);
|
||||
return -1;
|
||||
/*
|
||||
* For the get-attributes callout, we are typically called without the
|
||||
* callout lock held. However, there is a particular scenario (define
|
||||
* of an active mdev) where we may or may not be called with the lock
|
||||
* already held on behalf of mdevctl, depending on the mdevctl version.
|
||||
* Let's test for lock ownership first and, if already owned by the
|
||||
* parent (mdevctl) proceed rather than waiting on the file lock.
|
||||
*/
|
||||
rc = ap_try_lock_callout();
|
||||
switch (rc) {
|
||||
case 0:
|
||||
/* Lock acquired */
|
||||
anc->cleanup_lock = true;
|
||||
break;
|
||||
case 1:
|
||||
/* Lock held by parent -- trust the lock will remain held */
|
||||
break;
|
||||
default:
|
||||
/* Lock not acquired or held by parent -- do a normal obtain */
|
||||
rc = ap_get_lock_callout();
|
||||
if (rc) {
|
||||
fprintf(stderr,
|
||||
"Failed to acquire configuration lock %d\n",
|
||||
rc);
|
||||
return -1;
|
||||
}
|
||||
anc->cleanup_lock = true;
|
||||
}
|
||||
anc->cleanup_lock = true;
|
||||
|
||||
/*
|
||||
* Read the 'matrix' and 'control_domains' attributes to get the
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
# GNU awk:
|
||||
# - gawk
|
||||
|
||||
override SHELL := /bin/bash
|
||||
override .SHELLFLAGS := -O globstar -O nullglob -O extglob -c
|
||||
|
||||
# Include common s390-tools definitions
|
||||
|
||||
83
common.mak
83
common.mak
@@ -8,10 +8,14 @@ ASAN ?= 0
|
||||
ENABLE_WERROR ?= 0
|
||||
OPT_FLAGS ?=
|
||||
MAKECMDGOALS ?=
|
||||
CARGO ?= cargo
|
||||
CARGOFLAGS ?=
|
||||
|
||||
ifeq ($(COMMON_INCLUDED),false)
|
||||
COMMON_INCLUDED := true
|
||||
|
||||
override SHELL := /bin/bash
|
||||
|
||||
# 'BUILD_ARCH' is the architecture of the machine where the build takes place
|
||||
BUILD_ARCH := $(shell uname -m | sed -e 's/i.86/i386/' -e 's/sun4u/sparc64/' -e 's/arm.*/arm/' -e 's/sa110/arm/')
|
||||
# 'HOST_ARCH' is the architecture of the machine that will run the compiled output
|
||||
@@ -28,7 +32,7 @@ endif
|
||||
# The variable "DISTRELEASE" should be overwritten in rpm spec files with:
|
||||
# "make DISTRELEASE=%{release}" and "make install DISTRELEASE=%{release}"
|
||||
VERSION = 2
|
||||
RELEASE = 28
|
||||
RELEASE = 30
|
||||
PATCHLEVEL = 0
|
||||
DISTRELEASE = build-$(shell date +%Y%m%d)
|
||||
S390_TOOLS_RELEASE = $(VERSION).$(RELEASE).$(PATCHLEVEL)-$(DISTRELEASE)
|
||||
@@ -89,19 +93,19 @@ define cmd_define_and_export
|
||||
endef
|
||||
|
||||
define define_toolchain_variables
|
||||
$(eval $(call cmd_define_and_export, AS$(1)," AS$(1) ",$(2)as))
|
||||
$(eval $(call cmd_define_and_export, CC$(1)," CC$(1) ",$(2)gcc))
|
||||
$(eval $(call cmd_define_and_export, LINK$(1)," LINK$(1) ",$$(CC$(1))))
|
||||
$(eval $(call cmd_define_and_export, CXX$(1)," CXX$(1) ",$(2)g++))
|
||||
$(eval $(call cmd_define_and_export, LINKXX$(1)," LINKXX$(1) ",$$(CXX$(1))))
|
||||
$(eval $(call cmd_define_and_export, CPP$(1)," CPP$(1) ",$(2)gcc -E))
|
||||
$(eval $(call cmd_define_and_export, AR$(1)," AR$(1) ",$(2)ar))
|
||||
$(eval $(call cmd_define_and_export, NM$(1)," NM$(1) ",$(2)nm))
|
||||
$(eval $(call cmd_define_and_export, STRIP$(1)," STRIP$(1) ",$(2)strip))
|
||||
$(eval $(call cmd_define_and_export,OBJCOPY$(1)," OBJCOPY$(1) ",$(2)objcopy))
|
||||
$(eval $(call cmd_define_and_export,OBJDUMP$(1)," OBJDUMP$(1) ",$(2)objdump))
|
||||
$(eval PKG_CONFIG$(1) = pkg-config)
|
||||
$(eval export PKG_CONFIG$(1))
|
||||
$(call cmd_define_and_export, AS$(1)," AS$(1) ",$(2)as)
|
||||
$(call cmd_define_and_export, CC$(1)," CC$(1) ",$(2)gcc)
|
||||
$(call cmd_define_and_export, LINK$(1)," LINK$(1) ",$$(CC$(1)))
|
||||
$(call cmd_define_and_export, CXX$(1)," CXX$(1) ",$(2)g++)
|
||||
$(call cmd_define_and_export, LINKXX$(1)," LINKXX$(1) ",$$(CXX$(1)))
|
||||
$(call cmd_define_and_export, CPP$(1)," CPP$(1) ",$(2)gcc -E)
|
||||
$(call cmd_define_and_export, AR$(1)," AR$(1) ",$(2)ar)
|
||||
$(call cmd_define_and_export, NM$(1)," NM$(1) ",$(2)nm)
|
||||
$(call cmd_define_and_export, STRIP$(1)," STRIP$(1) ",$(2)strip)
|
||||
$(call cmd_define_and_export,OBJCOPY$(1)," OBJCOPY$(1) ",$(2)objcopy)
|
||||
$(call cmd_define_and_export,OBJDUMP$(1)," OBJDUMP$(1) ",$(2)objdump)
|
||||
PKG_CONFIG$(1) = pkg-config
|
||||
export PKG_CONFIG$(1)
|
||||
endef
|
||||
|
||||
# If the host architecture is not the same as the build architecture
|
||||
@@ -115,15 +119,19 @@ ifneq ($(HOST_ARCH),$(BUILD_ARCH))
|
||||
endif
|
||||
endif
|
||||
|
||||
$(call define_toolchain_variables,_FOR_BUILD,)
|
||||
$(call define_toolchain_variables,,$(CROSS_COMPILE))
|
||||
$(eval $(call define_toolchain_variables,_FOR_BUILD,))
|
||||
$(eval $(call define_toolchain_variables,,$(CROSS_COMPILE)))
|
||||
|
||||
$(eval $(call cmd_define,RUNTEST," RUNTEST ",$(S390_TEST_LIB_PATH)/s390_runtest))
|
||||
$(eval $(call cmd_define, CAT," CAT ",cat))
|
||||
$(eval $(call cmd_define, SED," SED ",sed))
|
||||
$(eval $(call cmd_define, GZIP," GZIP ",gzip))
|
||||
$(eval $(call cmd_define, MV," MV ",mv))
|
||||
$(eval $(call cmd_define, PERLC," PERLC ",perl -c))
|
||||
|
||||
$(eval $(call cmd_define, RUNTEST," RUNTEST ",$(S390_TEST_LIB_PATH)/s390_runtest))
|
||||
$(eval $(call cmd_define, CAT," CAT ",cat))
|
||||
$(eval $(call cmd_define, SED," SED ",sed))
|
||||
$(eval $(call cmd_define, GZIP," GZIP ",gzip))
|
||||
$(eval $(call cmd_define, MV," MV ",mv))
|
||||
$(eval $(call cmd_define, PERLC," PERLC ",perl -c))
|
||||
$(eval $(call cmd_define,CARGO_BUILD," CARGO BUILD ",$(CARGO) build))
|
||||
$(eval $(call cmd_define,CARGO_TEST, " CARGO TEST ",$(CARGO) test))
|
||||
$(eval $(call cmd_define,CARGO_CLEAN," CARGO CLEAN ",$(CARGO) clean))
|
||||
|
||||
CHECK = sparse
|
||||
CHECK_SILENT := $(CHECK)
|
||||
@@ -133,8 +141,10 @@ SKIP = echo " SKIP $(call reldir) due to"
|
||||
|
||||
INSTALL = install
|
||||
CP = cp
|
||||
ALL_CARGOFLAGS := $(CARGOFLAGS)
|
||||
ifneq ("${V}","1")
|
||||
MAKEFLAGS += --quiet
|
||||
ALL_CARGOFLAGS += --quiet
|
||||
echocmd=echo $1$(call reldir)$2;
|
||||
RUNTEST += > /dev/null 2>&1
|
||||
else
|
||||
@@ -360,6 +370,7 @@ help:
|
||||
@echo ' all Build all tools (default target)'
|
||||
@echo ' install Install tools'
|
||||
@echo ' clean Delete all generated files'
|
||||
@echo ' compdb Generate compile_commands.json for clangd'
|
||||
@echo ''
|
||||
@echo 'OPTIONS'
|
||||
@echo ' D=1 Build with debugging option "-Og"'
|
||||
@@ -375,6 +386,32 @@ help:
|
||||
@echo ' # make C=1 CHECK=smatch'
|
||||
.PHONY: help
|
||||
|
||||
|
||||
# 'compile_commands.json' generation
|
||||
#
|
||||
# Create the compilation database 'compile_commands.json'. See
|
||||
# https://clang.llvm.org/docs/JSONCompilationDatabase.html for details.
|
||||
#
|
||||
.PHONY: compdb
|
||||
compdb:
|
||||
$(MAKE) clean
|
||||
ifneq ($(shell command -v compiledb),)
|
||||
compiledb $(MAKE)
|
||||
else ifneq ($(shell command -v bear),)
|
||||
ifeq ($(shell bear --help|grep -- '-- ...'),)
|
||||
bear $(MAKE)
|
||||
else
|
||||
bear -- $(MAKE)
|
||||
endif
|
||||
else
|
||||
$(error Please install either 'compiledb' or 'bear')
|
||||
endif
|
||||
|
||||
# Prints the s390-tools release string
|
||||
version:
|
||||
$(info $(S390_TOOLS_RELEASE))
|
||||
.PHONY: version
|
||||
|
||||
# Automatic dependency generation
|
||||
#
|
||||
# Create ".o.d" dependency files with the -MM compile option for all ".c" and
|
||||
@@ -485,7 +522,7 @@ install_echo:
|
||||
install: install_echo install_dirs
|
||||
|
||||
clean_echo:
|
||||
$(call echocmd," CLEAN ")
|
||||
$(call echocmd," CLEAN ")
|
||||
clean_gcov:
|
||||
rm -f -- *.gcda *.gcno *.gcov
|
||||
clean_dep:
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
include ../common.mak
|
||||
|
||||
BIN_FILES = lscpumf chcpumf lshwc pai
|
||||
MAN_FILES = lscpumf.8 chcpumf.8 lshwc.8 pai.8
|
||||
BIN_FILES = lscpumf chcpumf lshwc pai lspai
|
||||
MAN_FILES = lscpumf.8 chcpumf.8 lshwc.8 pai.8 lspai.8
|
||||
|
||||
all: $(BIN_FILES)
|
||||
|
||||
@@ -11,6 +11,7 @@ lscpumf: lscpumf.o $(libs)
|
||||
chcpumf: chcpumf.o $(libs)
|
||||
lshwc: lshwc.o $(libs)
|
||||
pai: pai.o $(libs)
|
||||
lspai: lspai.o $(libs)
|
||||
|
||||
install: all install-man
|
||||
$(INSTALL) -d -m 755 $(DESTDIR)$(BINDIR) $(DESTDIR)$(MANDIR)/man8
|
||||
|
||||
352
cpumf/lspai.c
Normal file
352
cpumf/lspai.c
Normal file
@@ -0,0 +1,352 @@
|
||||
/* 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.
|
||||
*/
|
||||
|
||||
/* List available Processor Assist Instrumentation (PAI) counters. */
|
||||
|
||||
#include <ctype.h>
|
||||
#include <dirent.h>
|
||||
#include <err.h>
|
||||
#include <errno.h>
|
||||
#include <limits.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "lib/util_opt.h"
|
||||
#include "lib/util_prg.h"
|
||||
#include "lib/util_base.h"
|
||||
#include "lib/util_path.h"
|
||||
#include "lib/util_scandir.h"
|
||||
#include "lib/util_libc.h"
|
||||
#include "lib/util_file.h"
|
||||
#include "lib/util_list.h"
|
||||
#include "lib/libcpumf.h"
|
||||
|
||||
static struct util_opt opt_vec[] = {
|
||||
UTIL_OPT_SECTION("OPTIONS"),
|
||||
{
|
||||
.option = { "numeric", no_argument, NULL, 'n' },
|
||||
.desc = "Sort PAI counters by counter number"
|
||||
},
|
||||
{
|
||||
.option = { "type", required_argument, NULL, 't' },
|
||||
.argument = "TYPE",
|
||||
.desc = "Type of PAI counters to show: crypto, nnpa"
|
||||
},
|
||||
UTIL_OPT_HELP,
|
||||
UTIL_OPT_VERSION,
|
||||
UTIL_OPT_END
|
||||
};
|
||||
|
||||
static const struct util_prg prg = {
|
||||
.desc = "List Processor Assist Information counter sets",
|
||||
.copyright_vec = {
|
||||
{
|
||||
.owner = "IBM Corp.",
|
||||
.pub_first = 2023,
|
||||
.pub_last = 2023,
|
||||
},
|
||||
UTIL_PRG_COPYRIGHT_END
|
||||
}
|
||||
};
|
||||
|
||||
static bool numsort; /* If true sort counter numerically */
|
||||
|
||||
#define PAI_PATH "/bus/event_source/devices/%s"
|
||||
|
||||
enum pai_types { /* Bit mask for supported PAI counters */
|
||||
pai_type_crypto = 0, /* PAI Crypto Counters */
|
||||
pai_type_nnpa = 1, /* PAI NNPA Counters */
|
||||
pai_type_max = 2, /* PAI maximum value, must be last */
|
||||
};
|
||||
|
||||
static int pai_types_show;
|
||||
|
||||
struct pai_ctrname { /* List of defined counters */
|
||||
char *name; /* Counter name */
|
||||
unsigned long nr; /* Counter number */
|
||||
};
|
||||
|
||||
struct pai_node { /* Head for PAI counter sets */
|
||||
struct util_list_node node; /* Successor in PAI counter set list */
|
||||
enum pai_types type; /* PAI type */
|
||||
int pmu; /* Assigned PMU type number */
|
||||
const char *name; /* Counter set name */
|
||||
char *name_uc; /* Counter set name upper case */
|
||||
const char *sysfs_name; /* Counter set name in /sysfs tree */
|
||||
const char *filter_name; /* Counter set name for scandir filter */
|
||||
struct pai_ctrname *ctrlist; /* List of counter names & numbers */
|
||||
size_t ctrsize; /* Total size in bytes of ctrlist */
|
||||
int ctridx; /* Index of last entry used in ctrlist */
|
||||
unsigned long base; /* Base number for counter set */
|
||||
};
|
||||
|
||||
static struct util_list pai_list;
|
||||
|
||||
/* Return base of counter set, this is the first counter of this set. */
|
||||
static unsigned long pai_type_base(enum pai_types t)
|
||||
{
|
||||
switch (t) {
|
||||
case pai_type_crypto:
|
||||
return 0x1000;
|
||||
case pai_type_nnpa:
|
||||
return 0x1800;
|
||||
case pai_type_max:
|
||||
break;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Test PAI counter name from command line option. */
|
||||
static const char *pai_type_name(enum pai_types t)
|
||||
{
|
||||
switch (t) {
|
||||
case pai_type_crypto:
|
||||
return "crypto";
|
||||
case pai_type_nnpa:
|
||||
return "nnpa";
|
||||
case pai_type_max:
|
||||
break;
|
||||
}
|
||||
return "unknown";
|
||||
}
|
||||
|
||||
/* Convert PAI counter type to sysfs directory name. Only validated
|
||||
* input at this time.
|
||||
*/
|
||||
static const char *pai_type_sysfs(enum pai_types t)
|
||||
{
|
||||
if (t == pai_type_crypto)
|
||||
return "pai_crypto";
|
||||
return "pai_ext";
|
||||
}
|
||||
|
||||
/* Convert PAI counter type to sysfs directory name filter for scandir(). */
|
||||
static const char *pai_type_filter(enum pai_types t)
|
||||
{
|
||||
if (t == pai_type_nnpa)
|
||||
return "^NNPA";
|
||||
return "[^.]"; /* Matches anything but . and .. in sysfs */
|
||||
}
|
||||
|
||||
/* Sort PAI counter names by assigned counter number. */
|
||||
static int pai_ctrcmp(const void *p1, const void *p2)
|
||||
{
|
||||
struct pai_ctrname *l = (struct pai_ctrname *)p1;
|
||||
struct pai_ctrname *r = (struct pai_ctrname *)p2;
|
||||
|
||||
return l->nr > r->nr ? 1 : -1;
|
||||
}
|
||||
|
||||
/* Convert string to upper case. */
|
||||
static char *str2uc(const char *s)
|
||||
{
|
||||
char *uc = util_strdup(s), *old_uc = uc;
|
||||
|
||||
for (; *uc; ++uc)
|
||||
*uc = toupper(*uc);
|
||||
return old_uc;
|
||||
}
|
||||
|
||||
/* Read counter names and assigned event number from sysfs file tree.
|
||||
* Exit when sysfs directory can not be scanned.
|
||||
*/
|
||||
static void read_counternames(struct pai_node *node)
|
||||
{
|
||||
int i, more = 0, ctr = 0, count = 0;
|
||||
struct dirent **namelist = NULL;
|
||||
char *path, *ctrpath;
|
||||
|
||||
/* Read counter names and assigned event number. */
|
||||
path = util_path_sysfs(PAI_PATH "/events", node->sysfs_name);
|
||||
count = util_scandir(&namelist, alphasort, path, node->filter_name);
|
||||
if (count <= 0)
|
||||
errx(EXIT_FAILURE, "Cannot open %s", path);
|
||||
|
||||
node->ctrsize = count * sizeof(*node->ctrlist);
|
||||
node->ctrlist = util_malloc(node->ctrsize);
|
||||
for (i = 0; i < count && ctr >= 0; i++) {
|
||||
util_asprintf(&ctrpath, "%s/%s", path, namelist[i]->d_name);
|
||||
if (util_file_read_va(ctrpath, "event=%x", &ctr) == 1) {
|
||||
node->ctrlist[node->ctridx].name = util_strdup(namelist[i]->d_name);
|
||||
node->ctrlist[node->ctridx++].nr = ctr;
|
||||
more++;
|
||||
} else {
|
||||
warnx("Cannot parse %s", ctrpath);
|
||||
}
|
||||
free(ctrpath);
|
||||
}
|
||||
util_scandir_free(namelist, count);
|
||||
free(path);
|
||||
|
||||
if (numsort && more > 1)
|
||||
qsort(node->ctrlist, more, sizeof(*node->ctrlist), pai_ctrcmp);
|
||||
}
|
||||
|
||||
static void show_painode(void)
|
||||
{
|
||||
struct pai_node *node;
|
||||
int indent = 0;
|
||||
int offset = 0;
|
||||
|
||||
util_list_iterate(&pai_list, node) {
|
||||
for (int i = 0; i < node->ctridx; ++i)
|
||||
indent = MAX((size_t)indent, strlen(node->ctrlist[i].name));
|
||||
}
|
||||
|
||||
printf("RAW %*s NAME %*s DESCRIPTION\n", 3, "", indent - 5, "");
|
||||
util_list_iterate(&pai_list, node) {
|
||||
for (int i = 0; i < node->ctridx; ++i) {
|
||||
printf("%d:%ld %s", node->pmu,
|
||||
node->ctrlist[i].nr, node->ctrlist[i].name);
|
||||
|
||||
offset = indent - strlen(node->ctrlist[i].name) + 1;
|
||||
printf("%*s", offset, "");
|
||||
|
||||
printf("Counter %ld / PAI %s counter set\n",
|
||||
node->ctrlist[i].nr - node->base, node->name_uc);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Release all memory allocated at make_painode(). */
|
||||
static void free_painode(void)
|
||||
{
|
||||
struct pai_node *next, *node;
|
||||
|
||||
util_list_iterate_safe(&pai_list, node, next) {
|
||||
free(node->name_uc);
|
||||
for (int i = 0; i < node->ctridx; ++i)
|
||||
free(node->ctrlist[i].name);
|
||||
free(node->ctrlist);
|
||||
free(node);
|
||||
}
|
||||
}
|
||||
|
||||
static void make_painode(enum pai_types t)
|
||||
{
|
||||
struct pai_node *node = util_zalloc(sizeof(*node));
|
||||
char *path;
|
||||
|
||||
node->type = t;
|
||||
node->sysfs_name = pai_type_sysfs(t);
|
||||
node->name = pai_type_name(t);
|
||||
node->name_uc = str2uc(node->name);
|
||||
node->filter_name = pai_type_filter(t);
|
||||
node->base = pai_type_base(t);
|
||||
|
||||
/* Read PMU type number. */
|
||||
path = util_path_sysfs(PAI_PATH, node->sysfs_name);
|
||||
node->pmu = libcpumf_pmutype(path);
|
||||
if (node->pmu < 0)
|
||||
errx(EXIT_FAILURE, "Cannot open %s", path);
|
||||
free(path);
|
||||
|
||||
read_counternames(node);
|
||||
|
||||
util_list_add_tail(&pai_list, node);
|
||||
}
|
||||
|
||||
static int painode_cmp(void *a, void *b, void *UNUSED(data))
|
||||
{
|
||||
struct pai_node *n1 = (struct pai_node *)a;
|
||||
struct pai_node *n2 = (struct pai_node *)b;
|
||||
|
||||
return n1->pmu < n2->pmu ? -1 : 1;
|
||||
}
|
||||
|
||||
static void sort_painode(void)
|
||||
{
|
||||
util_list_sort(&pai_list, painode_cmp, NULL);
|
||||
}
|
||||
|
||||
/* Check for hardware support and return false if not available. */
|
||||
static bool have_support(enum pai_types t)
|
||||
{
|
||||
const char *sysfn = pai_type_sysfs(t);
|
||||
char *path = util_path_sysfs(PAI_PATH, sysfn);
|
||||
bool rc = true;
|
||||
|
||||
if (!util_path_is_dir(path)) {
|
||||
warnx("No support for PAI %s facility", pai_type_name(t));
|
||||
rc = false;
|
||||
}
|
||||
free(path);
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
* Check the argument for option -t. It must be a valid PAI counter set.
|
||||
* Exit when an invalid PAI counter set name has been specified.
|
||||
*/
|
||||
static void check_type_name(const char *type)
|
||||
{
|
||||
bool no_match = true;
|
||||
enum pai_types i;
|
||||
const char *fn;
|
||||
|
||||
for (i = pai_type_crypto; i < pai_type_max; ++i) {
|
||||
fn = pai_type_name(i);
|
||||
if (!strcasecmp(fn, type)) {
|
||||
pai_types_show |= (1 << i);
|
||||
no_match = false;
|
||||
}
|
||||
}
|
||||
if (no_match)
|
||||
errx(EXIT_FAILURE, "Invalid argument for -t %s", type);
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
int ch;
|
||||
|
||||
util_list_init(&pai_list, struct pai_node, node);
|
||||
util_prg_init(&prg);
|
||||
util_opt_init(opt_vec, NULL);
|
||||
|
||||
while ((ch = util_opt_getopt_long(argc, argv)) != -1) {
|
||||
switch (ch) {
|
||||
default:
|
||||
util_opt_print_parse_error(ch, argv);
|
||||
return EXIT_FAILURE;
|
||||
case 'h':
|
||||
util_prg_print_help();
|
||||
util_opt_print_help();
|
||||
return EXIT_SUCCESS;
|
||||
case 'v':
|
||||
util_prg_print_version();
|
||||
return EXIT_SUCCESS;
|
||||
case 'n':
|
||||
numsort = true;
|
||||
break;
|
||||
case 't':
|
||||
check_type_name(optarg);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* Nothing specified, show all PAI counters */
|
||||
if (!pai_types_show)
|
||||
pai_types_show = (1 << pai_type_crypto) | (1 << pai_type_nnpa);
|
||||
|
||||
/* Check for hardware support */
|
||||
for (enum pai_types i = pai_type_crypto; i < pai_type_max; ++i) {
|
||||
if ((pai_types_show & (1 << i))) {
|
||||
if (!have_support(i))
|
||||
pai_types_show &= ~(1 << i);
|
||||
else
|
||||
make_painode(i);
|
||||
}
|
||||
}
|
||||
sort_painode();
|
||||
show_painode();
|
||||
free_painode();
|
||||
return ch;
|
||||
}
|
||||
80
cpumf/man/lspai.8
Normal file
80
cpumf/man/lspai.8
Normal file
@@ -0,0 +1,80 @@
|
||||
.\" lspai.8
|
||||
.\"
|
||||
.\"
|
||||
.\" Copyright IBM Corp. 2021
|
||||
.\" s390-tools is free software; you can redistribute it and/or modify
|
||||
.\" it under the terms of the MIT license. See LICENSE for details.
|
||||
.\" ----------------------------------------------------------------------
|
||||
.ds c \fBlspai\fP
|
||||
.
|
||||
.TH \*c "8" "August 2023" "s390-tools" "CPU-MF management programs"
|
||||
.
|
||||
.SH NAME
|
||||
\*c \- list Processor Activity Instrumentation (PAI) counters
|
||||
.
|
||||
.SH SYNOPSIS
|
||||
\*c
|
||||
.RB [ \-n ]
|
||||
.RB [ \-t
|
||||
.IR "\ TYPE" ]
|
||||
.br
|
||||
\*c
|
||||
.BR \-h | \-\-help
|
||||
.br
|
||||
\*c
|
||||
.BR \-v | \-\-version
|
||||
.
|
||||
.
|
||||
.SH DESCRIPTION
|
||||
\*c displays the Processor Activity Instrumentation (PAI) counters
|
||||
for Linux on IBM Z.
|
||||
The output is a human-readable list of available PAI counter
|
||||
names and numbers.
|
||||
.SH OPTIONS
|
||||
.TP
|
||||
.BR \-h ", " \-\-help
|
||||
Displays help information, then exits.
|
||||
.
|
||||
.TP
|
||||
.BR \-v ", " \-\-version
|
||||
Displays version information, then exits.
|
||||
.
|
||||
.TP
|
||||
.BR \-t ", " \-\-type "\ TYPE"
|
||||
Specifies the PAI counter set to list.
|
||||
Valid counter set values are
|
||||
.I crypto
|
||||
and
|
||||
.IR nnpa .
|
||||
By default, the command lists all available PAI counter sets.
|
||||
NNPA refers to the Neural Network Processing Assist facility counter set.
|
||||
Crypto refers to the Cryptografic Processing Assist facility counter set.
|
||||
.
|
||||
.TP
|
||||
.BR \-n ", " \-\-numeric
|
||||
Shows the PAI counter sets sorted by counter number.
|
||||
Default sort order is PAI counter name.
|
||||
.
|
||||
.SH "EXAMPLE"
|
||||
The \*c invocation lists all PAI Neural Network Processing Assist Facility
|
||||
(NNPA) counters in numeric order:
|
||||
.nf
|
||||
# lspai -t nnpa -n
|
||||
RAW NAME DESCRIPTION
|
||||
13:6144 NNPA_ALL Counter 0 / PAI NNPA counter set
|
||||
13:6145 NNPA_ADD Counter 1 / PAI NNPA counter set
|
||||
13:6146 NNPA_SUB Counter 2 / PAI NNPA counter set
|
||||
13:6147 NNPA_MUL Counter 3 / PAI NNPA counter set
|
||||
\&...
|
||||
.fi
|
||||
The first column shows the raw event number suitable for
|
||||
.IR perf "(8)"
|
||||
raw event specification.
|
||||
The second column shows the PAI NNPA counter name,
|
||||
suitable for
|
||||
.IR perf "(8)"
|
||||
event specification by name.
|
||||
The third gives a short explanation, if available.
|
||||
.SH "SEE ALSO"
|
||||
.BR pai (8)
|
||||
.BR lscpumf (8)
|
||||
@@ -18,6 +18,8 @@
|
||||
.IR size ]
|
||||
.RB [ \-i | \-\-interval
|
||||
.IR ms ]
|
||||
.RB [ \-R | \-\-realtime
|
||||
.IR prio ]
|
||||
.BR \-c | \-\-crypto [ \fIcpulist ][: \fIdata\fR "] [" \fIloops\fP ]
|
||||
.br
|
||||
\*c
|
||||
@@ -25,6 +27,8 @@
|
||||
.IR size ]
|
||||
.RB [ \-i | \-\-interval
|
||||
.IR ms ]
|
||||
.RB [ \-R | \-\-realtime
|
||||
.IR prio ]
|
||||
.BR \-n | \-\-nnpa [ \fIcpulist ][: \fIdata\fR "] [" \fIloops\fP ]
|
||||
.br
|
||||
\*c
|
||||
@@ -191,6 +195,14 @@ The ring buffer is created with the
|
||||
.IR mmap (2)
|
||||
system call.
|
||||
.
|
||||
.TP
|
||||
.BR \-R ", " \-\-realtime "\ prio"
|
||||
Collect data using the RT SCHED_FIFO priority specified by
|
||||
.BR prio .
|
||||
Valid values are integers in the range 1 (low) to 99 (high).
|
||||
Use this option when gathering data from multiple CPUs
|
||||
to prevent data loss.
|
||||
.
|
||||
.SH ARGUMENT
|
||||
The command line options determine how command line
|
||||
arguments are interpreted.
|
||||
|
||||
21
cpumf/pai.c
21
cpumf/pai.c
@@ -944,6 +944,11 @@ static struct util_opt opt_vec[] = {
|
||||
.option = { "report", no_argument, NULL, 'r' },
|
||||
.desc = "Report file contents"
|
||||
},
|
||||
{
|
||||
.option = { "realtime", required_argument, NULL, 'R' },
|
||||
.argument = "PRIO",
|
||||
.desc = "Collect data with this RT SCHED_FIFO priority"
|
||||
},
|
||||
{
|
||||
.option = { "interval", required_argument, NULL, 'i' },
|
||||
.argument = "NUMBER",
|
||||
@@ -1007,6 +1012,19 @@ static unsigned long check_mapsize(unsigned long n)
|
||||
return cnt == 1 ? n : 0;
|
||||
}
|
||||
|
||||
static void setprio(const char *prio)
|
||||
{
|
||||
struct sched_param param;
|
||||
char *endstr;
|
||||
|
||||
memset(¶m, 0, sizeof(param));
|
||||
param.sched_priority = strtoul(prio, &endstr, 0);
|
||||
if (*endstr)
|
||||
errno = EINVAL;
|
||||
if (*endstr || sched_setscheduler(0, SCHED_FIFO, ¶m))
|
||||
err(EXIT_FAILURE, "Could not set realtime priority");
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
bool crypto_record = false, report = false;
|
||||
@@ -1061,6 +1079,9 @@ int main(int argc, char **argv)
|
||||
record_cpus_nnpa(optarg);
|
||||
nnpa_record = true;
|
||||
break;
|
||||
case 'R':
|
||||
setprio(optarg);
|
||||
break;
|
||||
case 'r':
|
||||
report = true;
|
||||
break;
|
||||
|
||||
@@ -15,13 +15,13 @@ dasdfmt \- formatting of DASD (ECKD) disk drives.
|
||||
|
||||
.SH DESCRIPTION
|
||||
\fBdasdfmt\fR formats a DASD (ECKD) disk drive to prepare it
|
||||
for usage with Linux for S/390.
|
||||
for usage with Linux for S/390.
|
||||
The \fIdevice\fR is the node of the device (e.g. '/dev/dasda').
|
||||
Any device node created by udev for kernel 2.6 can be used
|
||||
Any device node created by udev for kernel 2.6 can be used
|
||||
(e.g. '/dev/dasd/0.0.b100/disc').
|
||||
.br
|
||||
|
||||
\fBWARNING\fR: Careless usage of \fBdasdfmt\fR can result in
|
||||
\fBWARNING\fR: Careless usage of \fBdasdfmt\fR can result in
|
||||
\fBLOSS OF DATA\fR.
|
||||
|
||||
.SH OPTIONS
|
||||
@@ -31,7 +31,7 @@ Print usage and exit.
|
||||
|
||||
.TP
|
||||
\fB-t\fR or \fB--test\fR
|
||||
Disables any modification of the disk drive.
|
||||
Disables any modification of the disk drive.
|
||||
.br
|
||||
\fBdasdfmt\fR just prints
|
||||
out, what it \fBwould\fR do.
|
||||
@@ -41,7 +41,7 @@ out, what it \fBwould\fR do.
|
||||
Increases verbosity.
|
||||
|
||||
.TP
|
||||
\fB-y\fR
|
||||
\fB-y\fR
|
||||
Start formatting without further user-confirmation.
|
||||
|
||||
.TP
|
||||
@@ -59,7 +59,7 @@ Omit the writing of a disk label after formatting.
|
||||
.br
|
||||
This makes only sense for the 'ldl' disk layout.
|
||||
.br
|
||||
The '-L' option has to be specified after the '-d ldl' option.
|
||||
The '-L' option has to be specified after the '-d ldl' option.
|
||||
.br
|
||||
|
||||
e.g. dasdfmt -d ldl -L /dev/...
|
||||
@@ -84,13 +84,13 @@ Formats the device with compatible disk layout or linux disk layout.
|
||||
\fIlayout\fR is either \fIcdl\fR for the compatible disk layout
|
||||
(default) or \fIldl\fR for the linux disk layout.
|
||||
.br
|
||||
Compatible disk layout means a special handling of the
|
||||
first two tracks of the volume. This enables other S/390 or zSeries
|
||||
Compatible disk layout means a special handling of the
|
||||
first two tracks of the volume. This enables other S/390 or zSeries
|
||||
operating systems to access this device (e.g. for backup purposes).
|
||||
|
||||
.TP
|
||||
\fB-p\fR or \fB--progressbar\fR
|
||||
Print a progress bar while formatting.
|
||||
Print a progress bar while formatting.
|
||||
Do not use this option if you are using a 3270 console,
|
||||
running in background or redirecting the output to a file.
|
||||
|
||||
@@ -164,30 +164,30 @@ and always be a power of two. The recommended blocksize is 4096 bytes.
|
||||
|
||||
.TP
|
||||
\fB-l\fR \fIvolser\fR or \fB--label\fR=\fIvolser\fR
|
||||
Specify the volume serial number or volume identifier to be written
|
||||
to disk after formatting. If no label is specified, a sensible default
|
||||
is used. \fIvolser\fR is interpreted as ASCII string and is automatically
|
||||
Specify the volume serial number or volume identifier to be written
|
||||
to disk after formatting. If no label is specified, a sensible default
|
||||
is used. \fIvolser\fR is interpreted as ASCII string and is automatically
|
||||
converted to uppercase and then to EBCDIC.
|
||||
.br
|
||||
|
||||
e.g. -l LNX001 or --label=DASD01
|
||||
.br
|
||||
|
||||
The \fIvolser\fR identifies by serial number the volume. A volume serial
|
||||
The \fIvolser\fR identifies by serial number the volume. A volume serial
|
||||
number is 1 through 6 alphanumeric or one of the following special
|
||||
characters: $, #, @, %. Enclose a serial number that contains special
|
||||
characters in apostrophes. If the number is shorter than six
|
||||
characters: $, #, @, %. Enclose a serial number that contains special
|
||||
characters in apostrophes. If the number is shorter than six
|
||||
characters, it is padded with trailing blanks.
|
||||
.br
|
||||
.br
|
||||
|
||||
Do not code a volume serial number as SCRTCH, PRIVAT, or Lnnnnn (L with
|
||||
five numbers); these are used in OS/390 messages to ask the operator to
|
||||
mount a volume. Do not code a volume serial number as MIGRAT, which is
|
||||
used by the OS/390 Hierarchical Storage Manager DFSMShsm for migrated
|
||||
Do not code a volume serial number as SCRTCH, PRIVAT, or Lnnnnn (L with
|
||||
five numbers); these are used in OS/390 messages to ask the operator to
|
||||
mount a volume. Do not code a volume serial number as MIGRAT, which is
|
||||
used by the OS/390 Hierarchical Storage Manager DFSMShsm for migrated
|
||||
data sets.
|
||||
.br
|
||||
|
||||
NOTE: Try to avoid using special characters in the volume serial. This may cause problems accessing a disk by volser.
|
||||
NOTE: Try to avoid using special characters in the volume serial. This may cause problems accessing a disk by volser.
|
||||
.br
|
||||
In case you really have to use special characters, make sure you are using quotes. In addition there is a special handling for the '$' sign. Please specify it using '\\$' if necessary.
|
||||
.br
|
||||
@@ -197,9 +197,8 @@ e.g. -l 'a@b\\$c#' to get A@B$C#
|
||||
|
||||
.TP
|
||||
\fB-k\fR or \fB--keep_volser\fR
|
||||
Keeps the Volume Serial Number, when writing the Volume Label. This is
|
||||
useful, if the Serial Number has been written with a VM Tool and should not
|
||||
be overwritten.
|
||||
Keeps the Volume Serial Number when writing the Volume Label. This is useful if
|
||||
the volume already has a Serial Number that should not be overwritten.
|
||||
.br
|
||||
|
||||
.SH SEE ALSO
|
||||
|
||||
@@ -23,9 +23,16 @@
|
||||
|
||||
#define BLOCKSIZE 512
|
||||
|
||||
#if __has_attribute(nonstring)
|
||||
# define __nonstring __attribute__ ((nonstring))
|
||||
#else
|
||||
# define __nonstring
|
||||
#endif
|
||||
|
||||
|
||||
/* Basic TAR header */
|
||||
struct tar_header {
|
||||
char name[100];
|
||||
char name[100] __nonstring;
|
||||
char mode[8];
|
||||
char uid[8];
|
||||
char gid[8];
|
||||
@@ -33,7 +40,7 @@ struct tar_header {
|
||||
char mtime[12];
|
||||
char chksum[8];
|
||||
char typeflag;
|
||||
char linkname[100];
|
||||
char linkname[100] __nonstring;
|
||||
char magic[6];
|
||||
char version[2];
|
||||
char uname[32];
|
||||
@@ -78,7 +85,7 @@ static void set_time(char *dest, size_t len, time_t value)
|
||||
#define SET_TIME_FIELD(obj, name, value) \
|
||||
set_time((obj)->name, sizeof((obj)->name), (time_t) (value))
|
||||
#define SET_STR_FIELD(obj, name, value) \
|
||||
util_strlcpy((obj)->name, (value), sizeof((obj)->name))
|
||||
strncpy((obj)->name, (value), sizeof((obj)->name))
|
||||
|
||||
/* Initialize the tar file @header with the provided data */
|
||||
static void init_header(struct tar_header *header, const char *filename,
|
||||
|
||||
@@ -19,10 +19,10 @@ help:
|
||||
.br
|
||||
\fBfdasd\fR {-h|-v}
|
||||
.SH DESCRIPTION
|
||||
\fBfdasd\fR writes a partition table to a cdl (compatible disk layout)
|
||||
\fBfdasd\fR writes a partition table to a cdl (compatible disk layout)
|
||||
formatted DASD, in the form of
|
||||
a VTOC (volume table of contents) for usage with Linux for S/390
|
||||
or zSeries. If fdasd detects a valid \fBVOL1\fR volume label, it
|
||||
or zSeries. If fdasd detects a valid \fBVOL1\fR volume label, it
|
||||
will use it, otherwise it asks to write a new one.
|
||||
.br
|
||||
|
||||
@@ -34,51 +34,51 @@ will use it, otherwise it asks to write a new one.
|
||||
Print usage information, then exit.
|
||||
|
||||
.TP
|
||||
\fB-v\fR or \fB--version\fR
|
||||
\fB-v\fR or \fB--version\fR
|
||||
Print version information, then exit.
|
||||
|
||||
.TP
|
||||
\fB-s\fR or \fB--silent\fR
|
||||
\fB-s\fR or \fB--silent\fR
|
||||
Suppress messages in non-interactive mode.
|
||||
|
||||
.TP
|
||||
\fB-r\fR or \fB--verbose\fR
|
||||
\fB-r\fR or \fB--verbose\fR
|
||||
Provide more verbose output.
|
||||
|
||||
.TP
|
||||
\fB-a\fR or \fB--auto\fR
|
||||
Automatically create a partition using the entire disk in non-interactive
|
||||
\fB-a\fR or \fB--auto\fR
|
||||
Automatically create a partition using the entire disk in non-interactive
|
||||
mode.
|
||||
|
||||
.TP
|
||||
\fB-k\fR or \fB--keep_volser\fR
|
||||
Keeps the volume serial when writing the volume label.
|
||||
Keeps the Volume Serial Number when writing the Volume Label.
|
||||
.br
|
||||
This is useful, if the volume serial has been written before and should not
|
||||
be overwritten. This option is only applicable in non-interactive mode.
|
||||
This is useful if the volume already has a Serial Number that should not be
|
||||
overwritten. This option is only applicable in non-interactive mode.
|
||||
|
||||
.TP
|
||||
\fB-l\fR \fIvolser\fR or \fB--label\fR \fIvolser\fR
|
||||
Specify the volume serial.
|
||||
.br
|
||||
\fIvolser\fR is interpreted as ASCII string and is automatically converted to
|
||||
\fIvolser\fR is interpreted as ASCII string and is automatically converted to
|
||||
uppercase, padded with blanks and finally converted to EBCDIC to be written
|
||||
to disk. This option is only applicable in non-interactive mode.
|
||||
.br
|
||||
|
||||
Do not use the following reserved volume serial: SCRTCH, PRIVAT, MIGRAT,
|
||||
or Lnnnnn (L with five digit number); These are used as keywords by
|
||||
Do not use the following reserved volume serial: SCRTCH, PRIVAT, MIGRAT,
|
||||
or Lnnnnn (L with five digit number); These are used as keywords by
|
||||
other operating systems (OS/390).
|
||||
.br
|
||||
|
||||
A volume serial is 1 through 6 alphanumeric characters or one of the
|
||||
following special characters: $, #, @, %. All other characters are simply
|
||||
ignored.
|
||||
A volume serial is 1 through 6 alphanumeric characters or one of the
|
||||
following special characters: $, #, @, %. All other characters are simply
|
||||
ignored.
|
||||
.br
|
||||
Try to avoid using special characters in the volume serial.
|
||||
This may cause problems accessing a disk by volser.
|
||||
In case you really have to use special characters, make sure you are using
|
||||
quotes. In addition there is a special handling for the '$' sign.
|
||||
Try to avoid using special characters in the volume serial.
|
||||
This may cause problems accessing a disk by volser.
|
||||
In case you really have to use special characters, make sure you are using
|
||||
quotes. In addition there is a special handling for the '$' sign.
|
||||
Please specify it using '\\$' if necessary.
|
||||
.br
|
||||
|
||||
@@ -124,14 +124,14 @@ partitions that use the entire disk:
|
||||
.br
|
||||
|
||||
.TP
|
||||
\fB-i\fR or \fB--volser\fR
|
||||
\fB-i\fR or \fB--volser\fR
|
||||
Print the volume serial, then exit.
|
||||
|
||||
.TP
|
||||
\fB-p\fR or \fB--table\fR
|
||||
Print partition table, then exit.
|
||||
\fB-p\fR or \fB--table\fR
|
||||
Print partition table, then exit.
|
||||
.br
|
||||
In combination with the -s option fdasd will display a short version of the
|
||||
In combination with the -s option fdasd will display a short version of the
|
||||
partition table.
|
||||
|
||||
.TP
|
||||
@@ -179,7 +179,7 @@ In case your are not using the device file system, please specify:
|
||||
.br
|
||||
|
||||
where \fIx\fR is one or more lowercase letter(s) or any other device
|
||||
node specification configured by udev for kernel 2.6 or higher.
|
||||
node specification configured by udev for kernel 2.6 or higher.
|
||||
|
||||
.SH SEE ALSO
|
||||
.BR dasdfmt (8)
|
||||
|
||||
@@ -10,7 +10,7 @@ RECURSIVE_TARGETS := all-recursive install-recursive clean-recursive
|
||||
|
||||
all: all-recursive
|
||||
|
||||
install: all install-recursive
|
||||
install: install-recursive
|
||||
$(INSTALL) -d -m 755 "$(PKGDATADIR)"
|
||||
$(INSTALL) -g $(GROUP) -o $(OWNER) -m 755 samples/check_hostkeydoc "$(PKGDATADIR)"
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
include ../../common.mak
|
||||
|
||||
FILES := stage3a.bin stage3b.bin stage3b_reloc.bin
|
||||
DEBUG_FILES := $(addsuffix .debug,$(FILES))
|
||||
|
||||
ifeq ($(HOST_ARCH),s390x)
|
||||
ZIPL_DIR := $(rootdir)/zipl
|
||||
@@ -71,10 +72,15 @@ stage3b_reloc.o: stage3b.bin
|
||||
stage3a.elf: head.o stage3a_init.o $(ZIPL_OBJS)
|
||||
stage3b.elf: head.o $(ZIPL_OBJS)
|
||||
|
||||
.SECONDARY: $(FILES:.bin=.lds)
|
||||
%.elf: %.lds %.o
|
||||
$(LINK) $(NO_PIE_LDFLAGS) $(NO_WARN_RWX_SEGMENTS_LDFLAGS) -Wl,-T,$< -Wl,--build-id=none -m64 -static -nostdlib $(filter %.o, $^) -o $@
|
||||
@chmod a-x $@
|
||||
|
||||
%.bin.debug: %.elf
|
||||
$(OBJCOPY) --only-keep-debug $< $@
|
||||
@chmod a-x $@
|
||||
|
||||
%.bin: %.elf
|
||||
$(OBJCOPY) -O binary $< $@
|
||||
@chmod a-x $@
|
||||
@@ -89,7 +95,7 @@ else
|
||||
# `-include $(dependencies_c)` statement).
|
||||
.PHONY: $(dependencies_c)
|
||||
|
||||
$(FILES):
|
||||
$(FILES) $(DEBUG_FILES):
|
||||
echo " SKIP $@ due to HOST_ARCH != s390x"
|
||||
|
||||
install:
|
||||
@@ -97,9 +103,9 @@ install:
|
||||
endif
|
||||
|
||||
.DEFAULT_GOAL := all
|
||||
all: $(FILES)
|
||||
all: $(FILES) $(DEBUG_FILES)
|
||||
|
||||
clean:
|
||||
rm -f *.o *.elf *.bin *.map .*.d *.lds
|
||||
rm -f -- *.o *.elf *.bin *.map .*.d *.lds *.debug
|
||||
|
||||
.PHONY: all clean
|
||||
|
||||
@@ -11,9 +11,10 @@
|
||||
#include "stage3a.h"
|
||||
|
||||
#include "lib/zt_common.h"
|
||||
#include "boot/error.h"
|
||||
#include "boot/s390.h"
|
||||
#include "boot/ipl.h"
|
||||
#include "sclp.h"
|
||||
#include "error.h"
|
||||
|
||||
|
||||
static volatile struct stage3a_args __section(".loader_parms") loader_parms;
|
||||
|
||||
@@ -12,10 +12,11 @@
|
||||
|
||||
#include "lib/zt_common.h"
|
||||
#include "boot/psw.h"
|
||||
#include "boot/error.h"
|
||||
#include "boot/s390.h"
|
||||
#include "boot/linux_layout.h"
|
||||
#include "boot/loaders_layout.h"
|
||||
#include "sclp.h"
|
||||
#include "error.h"
|
||||
|
||||
|
||||
static volatile struct stage3b_args __section(".loader_parms") loader_parms;
|
||||
@@ -60,13 +61,17 @@ void __noreturn start(void)
|
||||
if (cmdline->size > get_kernel_cmdline_size())
|
||||
panic(EINTERNAL, "Command line is too large\n");
|
||||
|
||||
/* move the kernel cmdline */
|
||||
memmove((void *)COMMAND_LINE,
|
||||
(void *)cmdline->src,
|
||||
cmdline->size);
|
||||
if (cmdline->size > 0) {
|
||||
/* make sure the cmdline is a null-terminated string */
|
||||
if (((char *)cmdline->src)[cmdline->size - 1] != '\0')
|
||||
panic(EINTERNAL, "Command line needs to be null-terminated\n");
|
||||
|
||||
/* move the kernel cmdline */
|
||||
memmove((void *)COMMAND_LINE, (void *)cmdline->src, cmdline->size);
|
||||
}
|
||||
/* the initrd does not need to be moved */
|
||||
|
||||
if (initrd->size != 0) {
|
||||
if (initrd->size > 0) {
|
||||
/* copy initrd start address and size into new kernel space */
|
||||
*(unsigned long long *)INITRD_START = initrd->src;
|
||||
*(unsigned long long *)INITRD_SIZE = initrd->size;
|
||||
|
||||
@@ -97,18 +97,29 @@ Do not use for a production image unless you verified
|
||||
the host-key document before. Optional.
|
||||
.TP
|
||||
\fB\-\-comm\-key\fR=\fI\,FILE\/\fR
|
||||
Specifies the encryption key you want to use for the PV guest dump. Use a
|
||||
secure, random, plaintext AES-256 GCM key. Optional.
|
||||
Specifies the customer communication key (CCK). This key is used for the
|
||||
PV guest dump encryption and to derive the CCK-derived extension secret
|
||||
used for add-secret requests. Use a secure, random, plaintext AES-256
|
||||
GCM key. Optional.
|
||||
.TP
|
||||
\fB\-\-enable\-dump\fR
|
||||
Enable PV guest dumps. Requires the \fB\-\-comm-key\fR option. Optional.
|
||||
Enable PV guest dumps. Requires the \fB\-\-comm\-key\fR option. Optional.
|
||||
.TP
|
||||
\fB\-\-disable\-dump\fR
|
||||
Disable PV guest dumps. This is the default. Optional.
|
||||
Disable PV guest dumps. This is the default.
|
||||
.TP
|
||||
\fB\-\-enable\-cck\-extension\-secret\fR
|
||||
Add-secret requests must provide an extension secret that matches the
|
||||
CCK-derived extension secret. Requires the \fB\-\-comm\-key\fR option.
|
||||
Optional.
|
||||
.TP
|
||||
\fB\-\-disable\-cck\-extension\-secret\fR
|
||||
Add-secret requests don't have to provide an extension secret. This is
|
||||
the default.
|
||||
.TP
|
||||
\fB\-\-enable\-pckmo\fR
|
||||
Enable the support for the DEA, TDEA, AES, and ECC PCKMO key encryption
|
||||
functions. This is the default. Optional.
|
||||
functions. This is the default.
|
||||
.TP
|
||||
\fB\-\-disable\-pckmo\fR
|
||||
Disable the support for the DEA, TDEA, AES, and ECC PCKMO key encryption
|
||||
|
||||
@@ -34,6 +34,10 @@
|
||||
#define PV_PCF_PCKMO_AES __PV_BIT(57) /* PCKMO encrypt-AES-key functions allowed */
|
||||
#define PV_PCF_PCKM_ECC __PV_BIT(58) /* PCKMO encrypt-ECC-key functions allowed */
|
||||
|
||||
/* Secret control flags */
|
||||
#define PV_SCF_CCK_EXTENSION_SECRET_ENFORCMENT \
|
||||
__PV_BIT(1) /* All add-secret requests must provide an extension secret */
|
||||
|
||||
/* maxima for the PV version 1 */
|
||||
#define PV_V1_IPIB_MAX_SIZE PAGE_SIZE
|
||||
#define PV_V1_PV_HDR_MAX_SIZE (2 * PAGE_SIZE)
|
||||
|
||||
@@ -62,11 +62,12 @@ static gint pv_args_set_defaults(PvArgs *args, GError **err G_GNUC_UNUSED)
|
||||
|
||||
static gint pv_args_validate_options(PvArgs *args, GError **err)
|
||||
{
|
||||
const PvControlFlagsArgs *cf_args = &args->cf_args;
|
||||
PvComponentType KERNEL = PV_COMP_TYPE_KERNEL;
|
||||
|
||||
/* Check for mutually exclusive arguments */
|
||||
if (args->pcf && !(args->allow_pckmo == PV_NOT_SET &&
|
||||
args->allow_dump == PV_NOT_SET)) {
|
||||
if (cf_args->pcf &&
|
||||
!(cf_args->enable_pckmo == PV_NOT_SET && cf_args->enable_dump == PV_NOT_SET)) {
|
||||
g_set_error(
|
||||
err, PV_PARSE_ERROR, PV_PARSE_ERROR_SYNTAX,
|
||||
_("The '--x-pcf' option cannot be used with the '--(enable|disable)-pckmo' or"
|
||||
@@ -74,6 +75,13 @@ static gint pv_args_validate_options(PvArgs *args, GError **err)
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (cf_args->scf && !(cf_args->enable_cck_extension_secret_enforcement == PV_NOT_SET)) {
|
||||
g_set_error(
|
||||
err, PV_PARSE_ERROR, PV_PARSE_ERROR_SYNTAX,
|
||||
_("The '--x-scf' option cannot be used with the '--(enable|disable)-extension-secret-required' flags.\nUse 'genprotimg --help' for more information"));
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Check for unused arguments */
|
||||
if (args->unused_values->len > 0) {
|
||||
g_autofree gchar *unused = NULL;
|
||||
@@ -93,12 +101,20 @@ static gint pv_args_validate_options(PvArgs *args, GError **err)
|
||||
}
|
||||
|
||||
/* Check for mandatory arguments */
|
||||
if (args->allow_dump == PV_TRUE && !args->cust_comm_key_path) {
|
||||
if (cf_args->enable_dump == PV_TRUE && !args->cust_comm_key_path) {
|
||||
g_set_error(err, PV_PARSE_ERROR, PR_PARSE_ERROR_MISSING_ARGUMENT,
|
||||
_("Option '--allow-dump' requires the '--comm-key' option.\nUse 'genprotimg "
|
||||
_("Option '--enable-dump' requires the '--comm-key' option.\nUse 'genprotimg "
|
||||
"--help' for more information"));
|
||||
return -1;
|
||||
}
|
||||
if (cf_args->enable_cck_extension_secret_enforcement == PV_TRUE &&
|
||||
!args->cust_comm_key_path) {
|
||||
g_set_error(
|
||||
err, PV_PARSE_ERROR, PR_PARSE_ERROR_MISSING_ARGUMENT,
|
||||
_("Option '--enable-cck-extension-secret' requires the '--comm-key' option.\nUse 'genprotimg "
|
||||
"--help' for more information"));
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!args->output_path) {
|
||||
g_set_error(err, PV_PARSE_ERROR, PR_PARSE_ERROR_MISSING_ARGUMENT,
|
||||
@@ -178,11 +194,11 @@ static gboolean cb_set_string_option(const gchar *option, const gchar *value,
|
||||
if (g_str_equal(option, "--x-header-key"))
|
||||
args_option = &args->cust_root_key_path;
|
||||
if (g_str_equal(option, "--x-pcf"))
|
||||
args_option = &args->pcf;
|
||||
args_option = &args->cf_args.pcf;
|
||||
if (g_str_equal(option, "--x-psw"))
|
||||
args_option = &args->psw_addr;
|
||||
if (g_str_equal(option, "--x-scf"))
|
||||
args_option = &args->scf;
|
||||
args_option = &args->cf_args.scf;
|
||||
|
||||
if (!args_option) {
|
||||
g_set_error(err, PV_PARSE_ERROR, PV_PARSE_ERROR_SYNTAX,
|
||||
@@ -217,49 +233,48 @@ static gboolean cb_remaining_values(const gchar *option G_GNUC_UNUSED,
|
||||
}
|
||||
|
||||
#define MUT_EXCL_BOOL_FLAG_CB_NAME(FLAG, VALUE) (cb_##FLAG##_##VALUE)
|
||||
#define DEFINE_MUT_EXCL_BOOL_FLAG_CB(FLAG, VALUE) \
|
||||
static gboolean MUT_EXCL_BOOL_FLAG_CB_NAME(FLAG, VALUE)( \
|
||||
const gchar *option G_GNUC_UNUSED, const gchar *value G_GNUC_UNUSED, \
|
||||
PvArgs *args, GError **err) \
|
||||
{ \
|
||||
if (!(args->allow_##FLAG == PV_NOT_SET || \
|
||||
args->allow_##FLAG == VALUE)) { \
|
||||
g_set_error(err, G_OPTION_ERROR, G_OPTION_ERROR_FAILED, \
|
||||
"'--enable-" #FLAG "' and '--disable-" #FLAG \
|
||||
"' are mutually exclusive"); \
|
||||
return FALSE; \
|
||||
} \
|
||||
args->allow_##FLAG = VALUE; \
|
||||
return TRUE; \
|
||||
#define DEFINE_MUT_EXCL_BOOL_FLAG_CB(FLAG, VALUE) \
|
||||
static gboolean MUT_EXCL_BOOL_FLAG_CB_NAME(FLAG, VALUE)(const gchar *option G_GNUC_UNUSED, \
|
||||
const gchar *value G_GNUC_UNUSED, \
|
||||
PvArgs *args, GError **err) \
|
||||
{ \
|
||||
if (!(args->cf_args.enable_##FLAG == PV_NOT_SET || \
|
||||
args->cf_args.enable_##FLAG == VALUE)) { \
|
||||
g_set_error(err, G_OPTION_ERROR, G_OPTION_ERROR_FAILED, \
|
||||
"'--enable-" #FLAG "' and '--disable-" #FLAG \
|
||||
"' are mutually exclusive"); \
|
||||
return FALSE; \
|
||||
} \
|
||||
args->cf_args.enable_##FLAG = VALUE; \
|
||||
return TRUE; \
|
||||
}
|
||||
|
||||
#define DEFINE_MUT_EXCL_BOOL_FLAG_CBS(FLAG) \
|
||||
DEFINE_MUT_EXCL_BOOL_FLAG_CB(FLAG, PV_TRUE) \
|
||||
DEFINE_MUT_EXCL_BOOL_FLAG_CB(FLAG, PV_FALSE)
|
||||
|
||||
#define MUT_EXCL_BOOL_FLAG(FLAG, ENABLE_DESC, DISABLE_DESC) \
|
||||
{ \
|
||||
.long_name = "enable-" #FLAG, \
|
||||
.short_name = 0, \
|
||||
.flags = G_OPTION_FLAG_NO_ARG, \
|
||||
.arg = G_OPTION_ARG_CALLBACK, \
|
||||
.arg_data = MUT_EXCL_BOOL_FLAG_CB_NAME(FLAG, PV_TRUE), \
|
||||
.description = ENABLE_DESC, \
|
||||
}, \
|
||||
{ \
|
||||
.long_name = "disable-" #FLAG, \
|
||||
.short_name = 0, \
|
||||
.flags = G_OPTION_FLAG_NO_ARG, \
|
||||
.arg = G_OPTION_ARG_CALLBACK, \
|
||||
.arg_data = MUT_EXCL_BOOL_FLAG_CB_NAME(FLAG, PV_FALSE), \
|
||||
.description = DISABLE_DESC, \
|
||||
#define MUT_EXCL_BOOL_FLAG(NAME, FLAG, ENABLE_DESC, DISABLE_DESC) \
|
||||
{ \
|
||||
.long_name = "enable-" #NAME, \
|
||||
.short_name = 0, \
|
||||
.flags = G_OPTION_FLAG_NO_ARG, \
|
||||
.arg = G_OPTION_ARG_CALLBACK, \
|
||||
.arg_data = MUT_EXCL_BOOL_FLAG_CB_NAME(FLAG, PV_TRUE), \
|
||||
.description = ENABLE_DESC, \
|
||||
}, \
|
||||
{ \
|
||||
.long_name = "disable-" #NAME, .short_name = 0, .flags = G_OPTION_FLAG_NO_ARG, \
|
||||
.arg = G_OPTION_ARG_CALLBACK, \
|
||||
.arg_data = MUT_EXCL_BOOL_FLAG_CB_NAME(FLAG, PV_FALSE), \
|
||||
.description = DISABLE_DESC, \
|
||||
}
|
||||
|
||||
#define INDENT " "
|
||||
#define INDENT " "
|
||||
|
||||
/* Define the callbacks for mutually exclusive command line flags */
|
||||
DEFINE_MUT_EXCL_BOOL_FLAG_CBS(dump)
|
||||
DEFINE_MUT_EXCL_BOOL_FLAG_CBS(pckmo)
|
||||
DEFINE_MUT_EXCL_BOOL_FLAG_CBS(dump);
|
||||
DEFINE_MUT_EXCL_BOOL_FLAG_CBS(pckmo);
|
||||
DEFINE_MUT_EXCL_BOOL_FLAG_CBS(cck_extension_secret_enforcement);
|
||||
|
||||
gint pv_args_parse_options(PvArgs *args, gint *argc, gchar **argv[],
|
||||
GError **err)
|
||||
@@ -280,7 +295,7 @@ gint pv_args_parse_options(PvArgs *args, gint *argc, gchar **argv[],
|
||||
.arg_data = &args->host_keys,
|
||||
.description =
|
||||
_("FILE specifies a host-key document. At least\n" INDENT
|
||||
"one is required Specify this option multiple times\n" INDENT
|
||||
"one is required. Specify this option multiple times\n" INDENT
|
||||
"to enable the image to run on more than one host."),
|
||||
.arg_description = _("FILE") },
|
||||
{ .long_name = "cert",
|
||||
@@ -325,27 +340,31 @@ gint pv_args_parse_options(PvArgs *args, gint *argc, gchar **argv[],
|
||||
.description = _("Use the kernel parameters stored in PARMFILE\n" INDENT
|
||||
"(optional)."),
|
||||
.arg_description = _("PARMFILE") },
|
||||
MUT_EXCL_BOOL_FLAG(dump, dump,
|
||||
_("Enable PV guest dumps (optional). This option\n" INDENT
|
||||
"requires the '--comm-key' option."),
|
||||
_("Disable PV guest dumps (default).")),
|
||||
MUT_EXCL_BOOL_FLAG(
|
||||
dump,
|
||||
_("Enable PV guest dumps (optional). This option\n" INDENT
|
||||
"requires the '--comm-key' option."),
|
||||
_("Disable PV guest dumps (default) (optional).")),
|
||||
MUT_EXCL_BOOL_FLAG(
|
||||
pckmo,
|
||||
_("Enable the support for the DEA, TDEA, AES, and\n" INDENT
|
||||
"ECC PCKMO key encryption functions (default)\n" INDENT
|
||||
"(optional)."),
|
||||
_("Disable the support for the DEA, TDEA, AES, and\n" INDENT
|
||||
"ECC PCKMO key encryption functions (optional).")),
|
||||
cck-extension-secret, cck_extension_secret_enforcement,
|
||||
_("Add-secret requests must provide an extension\n" INDENT
|
||||
"secret that matches the CCK-derived extension\n" INDENT
|
||||
"secret (optional). This option requires the\n" INDENT
|
||||
"'--comm-key' option."),
|
||||
_("Add-secret requests don't have to provide\n" INDENT
|
||||
"the CCK-derived extension secret (default).")),
|
||||
MUT_EXCL_BOOL_FLAG(pckmo, pckmo,
|
||||
_("Enable the support for the DEA, TDEA, AES, and\n" INDENT
|
||||
"ECC PCKMO key encryption functions (default)."),
|
||||
_("Disable the support for the DEA, TDEA, AES, and\n" INDENT
|
||||
"ECC PCKMO key encryption functions (optional).")),
|
||||
{ .long_name = "comm-key",
|
||||
.short_name = 0,
|
||||
.flags = G_OPTION_FLAG_FILENAME,
|
||||
.arg = G_OPTION_ARG_CALLBACK,
|
||||
.arg_data = cb_set_string_option,
|
||||
.description = _(
|
||||
"FILE contains the key with which you encrypt\n" INDENT
|
||||
"the PV guest dump (optional). Required by\n" INDENT
|
||||
"the '--enable-dump' option."),
|
||||
"FILE contains the customer communication key\n" INDENT
|
||||
"(CCK) (optional)."),
|
||||
.arg_description = _("FILE") },
|
||||
{ .long_name = "crl",
|
||||
.short_name = 0,
|
||||
@@ -450,6 +469,8 @@ gint pv_args_parse_options(PvArgs *args, gint *argc, gchar **argv[],
|
||||
.arg_data = cb_set_string_option,
|
||||
.description = _("Specify the secret control flags\n" INDENT
|
||||
"as a hexadecimal value.\n" INDENT
|
||||
"Optional; mutually exclusive with\n" INDENT
|
||||
"'--(enable|disable)-cck-extension-secret';\n" INDENT
|
||||
"Optional; default: '0x0'."),
|
||||
.arg_description = _("VALUE") },
|
||||
{ 0 },
|
||||
@@ -487,8 +508,10 @@ PvArgs *pv_args_new(void)
|
||||
g_autoptr(PvArgs) args = g_new0(PvArgs, 1);
|
||||
|
||||
args->unused_values = g_ptr_array_new_with_free_func(g_free);
|
||||
args->allow_dump = PV_NOT_SET;
|
||||
args->allow_pckmo = PV_NOT_SET;
|
||||
/* `args->cf_args` is implicitly initialized with zeros since
|
||||
* `g_new0` is used. So there is no reason to explicitly
|
||||
* initialize the values as PV_NOT_SET == 0.
|
||||
*/
|
||||
return g_steal_pointer(&args);
|
||||
}
|
||||
|
||||
@@ -497,8 +520,8 @@ void pv_args_free(PvArgs *args)
|
||||
if (!args)
|
||||
return;
|
||||
|
||||
g_free(args->pcf);
|
||||
g_free(args->scf);
|
||||
g_free(args->cf_args.pcf);
|
||||
g_free(args->cf_args.scf);
|
||||
g_free(args->psw_addr);
|
||||
g_free(args->cust_root_key_path);
|
||||
g_free(args->cust_comm_key_path);
|
||||
|
||||
@@ -23,19 +23,27 @@ PvArg *pv_arg_new(PvComponentType type, const gchar *path);
|
||||
void pv_arg_free(PvArg *arg);
|
||||
|
||||
typedef enum pv_tristate {
|
||||
PV_NOT_SET = 0,
|
||||
PV_TRUE,
|
||||
PV_FALSE,
|
||||
PV_NOT_SET = 0,
|
||||
PV_TRUE,
|
||||
PV_FALSE,
|
||||
} PvTristate;
|
||||
/* The value of PV_NOT_SET is not allowed to be changed */
|
||||
STATIC_ASSERT(PV_NOT_SET == 0)
|
||||
|
||||
typedef struct {
|
||||
gchar *pcf;
|
||||
gchar *scf;
|
||||
/* Add-secret requests do require CCK-extension secrets */
|
||||
PvTristate enable_cck_extension_secret_enforcement;
|
||||
PvTristate enable_dump;
|
||||
PvTristate enable_pckmo;
|
||||
} PvControlFlagsArgs;
|
||||
|
||||
typedef struct {
|
||||
gint log_level;
|
||||
gint no_verify;
|
||||
gboolean offline;
|
||||
gchar *pcf;
|
||||
gchar *scf;
|
||||
PvTristate allow_dump;
|
||||
PvTristate allow_pckmo;
|
||||
PvControlFlagsArgs cf_args;
|
||||
gchar *psw_addr; /* PSW address which will be used for the start of
|
||||
* the actual component (e.g. Linux kernel)
|
||||
*/
|
||||
|
||||
@@ -228,37 +228,40 @@ static gint pv_img_set_psw_addr(PvImage *img, const gchar *psw_addr_s,
|
||||
return 0;
|
||||
}
|
||||
|
||||
static gint pv_img_set_control_flags(PvImage *img, const gchar *pcf_s,
|
||||
const gchar *scf_s,
|
||||
PvTristate allow_dump,
|
||||
PvTristate allow_pckmo, GError **err)
|
||||
static void pv_img_set_control_flag(uint64_t *flags, const PvTristate option, const uint64_t flag)
|
||||
{
|
||||
if (option == PV_TRUE)
|
||||
*flags |= flag;
|
||||
else if (option == PV_FALSE)
|
||||
*flags &= ~flag;
|
||||
}
|
||||
|
||||
static gint pv_img_set_control_flags(PvImage *img, const PvControlFlagsArgs *cf_args, GError **err)
|
||||
{
|
||||
uint64_t flags;
|
||||
|
||||
if (pcf_s) {
|
||||
if (hex_str_toull(pcf_s, &flags, err) < 0)
|
||||
/* Set plain control flags */
|
||||
if (cf_args->pcf) {
|
||||
if (hex_str_toull(cf_args->pcf, &flags, err) < 0)
|
||||
return -1;
|
||||
|
||||
img->pcf = flags;
|
||||
}
|
||||
|
||||
if (scf_s) {
|
||||
if (hex_str_toull(scf_s, &flags, err) < 0)
|
||||
pv_img_set_control_flag(&img->pcf, cf_args->enable_dump, PV_PCF_ALLOW_DUMPING);
|
||||
pv_img_set_control_flag(&img->pcf, cf_args->enable_pckmo,
|
||||
PV_PCF_PCKM_ECC | PV_PCF_PCKMO_AES | PV_PCF_PCKMO_DEA_TDEA);
|
||||
|
||||
/* Set secret control flags */
|
||||
if (cf_args->scf) {
|
||||
if (hex_str_toull(cf_args->scf, &flags, err) < 0)
|
||||
return -1;
|
||||
|
||||
img->scf = flags;
|
||||
}
|
||||
|
||||
if (allow_dump == PV_TRUE)
|
||||
img->pcf |= PV_PCF_ALLOW_DUMPING;
|
||||
else if (allow_dump == PV_FALSE)
|
||||
img->pcf &= ~PV_PCF_ALLOW_DUMPING;
|
||||
|
||||
if (allow_pckmo == PV_TRUE)
|
||||
img->pcf |= PV_PCF_PCKM_ECC | PV_PCF_PCKMO_AES | PV_PCF_PCKMO_DEA_TDEA;
|
||||
else if (allow_pckmo == PV_FALSE)
|
||||
img->pcf &= ~(PV_PCF_PCKM_ECC | PV_PCF_PCKMO_AES | PV_PCF_PCKMO_DEA_TDEA);
|
||||
|
||||
pv_img_set_control_flag(&img->scf, cf_args->enable_cck_extension_secret_enforcement,
|
||||
PV_SCF_CCK_EXTENSION_SECRET_ENFORCMENT);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -610,9 +613,7 @@ PvImage *pv_img_new(PvArgs *args, const gchar *stage3a_path, GError **err)
|
||||
return NULL;
|
||||
|
||||
/* set the control flags: PCF and SCF */
|
||||
if (pv_img_set_control_flags(ret, args->pcf, args->scf,
|
||||
args->allow_dump, args->allow_pckmo,
|
||||
err) < 0)
|
||||
if (pv_img_set_control_flags(ret, &args->cf_args, err) < 0)
|
||||
return NULL;
|
||||
|
||||
/* read in the keys */
|
||||
@@ -683,7 +684,26 @@ gint pv_img_add_component(PvImage *img, const PvArg *arg, GError **err)
|
||||
{
|
||||
g_autoptr(PvComponent) comp = NULL;
|
||||
|
||||
comp = pv_component_new_file(arg->type, arg->path, err);
|
||||
switch (arg->type) {
|
||||
case PV_COMP_TYPE_INITRD:
|
||||
case PV_COMP_TYPE_KERNEL:
|
||||
case PV_COMP_TYPE_STAGE3B:
|
||||
comp = pv_component_new_file(arg->type, arg->path, err);
|
||||
break;
|
||||
case PV_COMP_TYPE_CMDLINE: {
|
||||
g_autoptr(PvBuffer) buf = NULL;
|
||||
g_autofree char *data = NULL;
|
||||
gsize length;
|
||||
|
||||
if (!g_file_get_contents(arg->path, &data, &length, err))
|
||||
return -1;
|
||||
|
||||
/* Add one for the null terminator */
|
||||
buf = pv_buffer_take(g_steal_pointer(&data), length + 1);
|
||||
comp = pv_component_new_buf(arg->type, buf, err);
|
||||
} break;
|
||||
}
|
||||
|
||||
if (!comp)
|
||||
return -1;
|
||||
|
||||
|
||||
@@ -59,7 +59,7 @@ static gint pv_ipib_init(IplParameterBlock *ipib, GSList *comps,
|
||||
ipib_size = MAX(ipl_pl_hdr_size + blk0_len, (uint32_t)PAGE_SIZE);
|
||||
g_assert(pv_ipib_get_size(comps_length) == ipib_size);
|
||||
|
||||
pv->pbt = IPL_TYPE_PV;
|
||||
pv->pbt = IPL_PBT_PV;
|
||||
pv->len = GUINT32_TO_BE(blk0_len);
|
||||
pv->num_comp = GUINT32_TO_BE(comps_length);
|
||||
/* both values will be overwritten during the IPL process by
|
||||
|
||||
@@ -26,6 +26,15 @@ PvBuffer *pv_buffer_alloc(gsize size)
|
||||
return ret;
|
||||
}
|
||||
|
||||
PvBuffer *pv_buffer_take(char *data, gsize size)
|
||||
{
|
||||
PvBuffer *ret = g_new0(PvBuffer, 1);
|
||||
|
||||
ret->data = data;
|
||||
ret->size = size;
|
||||
return ret;
|
||||
}
|
||||
|
||||
PvBuffer *pv_buffer_dup(const PvBuffer *buf, gboolean page_aligned)
|
||||
{
|
||||
PvBuffer *ret;
|
||||
|
||||
@@ -21,6 +21,10 @@ typedef struct PvBuffer {
|
||||
} PvBuffer;
|
||||
|
||||
PvBuffer *pv_buffer_alloc(gsize size);
|
||||
/* After this call @data belongs to the PvBuffer and must no longer be modified
|
||||
* by the caller.
|
||||
*/
|
||||
PvBuffer *pv_buffer_take(char *data, gsize size);
|
||||
void pv_buffer_free(PvBuffer *buf);
|
||||
void pv_buffer_clear(PvBuffer **buf);
|
||||
gint pv_buffer_write(const PvBuffer *buf, FILE *file, GError **err);
|
||||
|
||||
@@ -116,6 +116,7 @@ static void l_sd_cpu_fill(struct sd_cpu *cpu, struct l_x_cpu_info *cpu_info,
|
||||
int threads)
|
||||
{
|
||||
sd_cpu_cpu_time_us_set(cpu, cpu_info->lp_time);
|
||||
sd_cpu_threads_per_core_set(cpu, threads);
|
||||
if (threads > 1)
|
||||
sd_cpu_thread_time_us_set(cpu,
|
||||
cpu_info->lp_time * threads - cpu_info->mt_idle_time);
|
||||
@@ -297,6 +298,7 @@ static struct sd_sys_item *l_sys_item_vec[] = {
|
||||
&sd_sys_item_thread_cnt,
|
||||
&sd_sys_item_core_diff,
|
||||
&sd_sys_item_thread_diff,
|
||||
&sd_sys_item_smt_diff,
|
||||
&sd_sys_item_mgm_diff,
|
||||
&sd_sys_item_core,
|
||||
&sd_sys_item_thread,
|
||||
@@ -326,6 +328,7 @@ static struct sd_cpu_item *l_cpu_item_vec[] = {
|
||||
&sd_cpu_item_type,
|
||||
&sd_cpu_item_core_diff,
|
||||
&sd_cpu_item_thread_diff,
|
||||
&sd_cpu_item_smt_diff,
|
||||
&sd_cpu_item_mgm_diff,
|
||||
&sd_cpu_item_core,
|
||||
&sd_cpu_item_thread,
|
||||
|
||||
@@ -391,3 +391,24 @@ void hyptop_helper_init(void)
|
||||
if (l_iconv_ebcdic_ascii == (iconv_t) -1)
|
||||
ERR_EXIT("Could not initialize iconv\n");
|
||||
}
|
||||
|
||||
/*
|
||||
* Calculate real SMT utilization
|
||||
* @core_us: core utilization in us
|
||||
* @thr_us: thread utilization in us
|
||||
* @mgm_us: management utilization in us
|
||||
* @thread_per_core: SMT thread count per core
|
||||
*/
|
||||
s64 ht_calculate_smt_util(u64 core_us, u64 thr_us, u64 mgm_us, int thread_per_core)
|
||||
{
|
||||
s64 component1, component2, smt_us;
|
||||
double smt_factor = g.o.smt_factor;
|
||||
|
||||
component1 = thread_per_core * core_us - thr_us;
|
||||
if (thread_per_core > 1)
|
||||
component1 /= smt_factor;
|
||||
component2 = thr_us - core_us;
|
||||
smt_us = G0(component1 + component2 + mgm_us);
|
||||
|
||||
return smt_us;
|
||||
}
|
||||
|
||||
@@ -34,6 +34,7 @@ extern void ht_ebcdic_to_ascii(char *in, char *out, size_t len);
|
||||
extern char *ht_mount_point_get(const char *fs_type);
|
||||
extern u64 ht_ext_tod_2_us(void *tod_ext);
|
||||
extern void ht_print_time(void);
|
||||
extern s64 ht_calculate_smt_util(u64 core_us, u64 thr_us, u64 mgm_us, int thread_per_core);
|
||||
|
||||
/*
|
||||
* Memory alloc functions
|
||||
|
||||
@@ -74,6 +74,18 @@ In this mode no user input is accepted.
|
||||
.BR "\-d <SECONDS>" " or " "\-\-delay=<SECONDS>"
|
||||
Specifies the delay between screen updates.
|
||||
.TP
|
||||
.BR "\-m <FACTOR>" " or " "\-\-smt_factor=<FACTOR>"
|
||||
Specifies a workload dependent SMT speedup factor.
|
||||
For IBM z15 servers, the default value is 1.3. If the workload benefits
|
||||
from SMT, you can specify a higher value. If the workload does not benefit
|
||||
from SMT, specifying lower values results in more accurate reports of
|
||||
real CPU SMT utilization field for LPARs. There is no hard boundary except
|
||||
that it must be a positive value. Example ranges to select a sensible value
|
||||
from:
|
||||
|
||||
For IBM z13: [0.8, 1.3]
|
||||
For IBM z15: [1.1, 1.5]
|
||||
.TP
|
||||
.BR "\-n <ITERATIONS>" " or " "\-\-iterations=<ITERATIONS>"
|
||||
Specifies the maximum number of iterations before ending.
|
||||
|
||||
@@ -119,6 +131,7 @@ The following fields are available under LPAR:
|
||||
In "sys_list" and "sys" window:
|
||||
'c' - Core dispatch time per second
|
||||
'e' - Thread time per second
|
||||
'S' - Real CPU SMT utilization
|
||||
'm' - Management time per second
|
||||
'C' - Total core dispatch time
|
||||
'E' - Total thread time
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
#include "table.h"
|
||||
|
||||
#define HYPTOP_OPT_DEFAULT_DELAY 2
|
||||
#define HYPTOP_OPT_DEFAULT_SMT_SCALE 1.3
|
||||
#define HYPTOP_MAX_WIN_DEPTH 4
|
||||
#define HYPTOP_MAX_LINE 512
|
||||
#define PROG_NAME "hyptop"
|
||||
@@ -60,6 +61,8 @@ struct hyptop_opts {
|
||||
|
||||
int delay_s;
|
||||
int delay_us;
|
||||
|
||||
double smt_factor;
|
||||
};
|
||||
|
||||
/*
|
||||
|
||||
@@ -39,6 +39,7 @@ static char HELP_TEXT[] =
|
||||
"-t, --cpu_types TYPE[,..] CPU types used for time calculations\n"
|
||||
"-b, --batch_mode Use batch mode (no curses)\n"
|
||||
"-d, --delay SECONDS Delay time between screen updates\n"
|
||||
"-m, --smt_factor FACTOR Machine generation dependent SMT speedup factor.\n"
|
||||
"-n, --iterations NUMBER Number of iterations before ending\n";
|
||||
|
||||
/*
|
||||
@@ -48,6 +49,7 @@ static void l_init_defaults(void)
|
||||
{
|
||||
g.prog_name = PROG_NAME;
|
||||
g.o.delay_s = HYPTOP_OPT_DEFAULT_DELAY;
|
||||
g.o.smt_factor = HYPTOP_OPT_DEFAULT_SMT_SCALE;
|
||||
g.w.cur = &win_sys_list;
|
||||
g.o.cur_win = &win_sys_list;
|
||||
}
|
||||
@@ -108,6 +110,20 @@ static void l_delay_set(char *delay_string)
|
||||
g.o.delay_us = 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Set SMT factor option
|
||||
*/
|
||||
static void l_factor_set(char *value_string)
|
||||
{
|
||||
double factor;
|
||||
|
||||
if (sscanf(value_string, "%lf", &factor) != 1)
|
||||
ERR_EXIT("The SMT factor \"%s\" is invalid\n", value_string);
|
||||
if (factor <= 0)
|
||||
ERR_EXIT("The SMT factor \"%s\" is <= 0\n", value_string);
|
||||
g.o.smt_factor = factor;
|
||||
}
|
||||
|
||||
/*
|
||||
* Get number of occurrences of character 'c' in "str"
|
||||
*/
|
||||
@@ -299,6 +315,7 @@ void opts_parse(int argc, char *argv[])
|
||||
{ "help", no_argument, NULL, 'h'},
|
||||
{ "batch_mode", no_argument, NULL, 'b'},
|
||||
{ "delay", required_argument, NULL, 'd'},
|
||||
{ "smt_factor", required_argument, NULL, 'm'},
|
||||
{ "window", required_argument, NULL, 'w'},
|
||||
{ "sys", required_argument, NULL, 's'},
|
||||
{ "iterations", required_argument, NULL, 'n'},
|
||||
@@ -307,7 +324,7 @@ void opts_parse(int argc, char *argv[])
|
||||
{ "cpu_types", required_argument, NULL, 't'},
|
||||
{ NULL, 0, NULL, 0 }
|
||||
};
|
||||
static const char option_string[] = "vhbd:w:s:n:f:t:S:";
|
||||
static const char option_string[] = "vhbd:m:w:s:n:f:t:S:";
|
||||
|
||||
l_init_defaults();
|
||||
while (1) {
|
||||
@@ -328,6 +345,9 @@ void opts_parse(int argc, char *argv[])
|
||||
case 'd':
|
||||
l_delay_set(optarg);
|
||||
break;
|
||||
case 'm':
|
||||
l_factor_set(optarg);
|
||||
break;
|
||||
case 'w':
|
||||
l_window_set(optarg);
|
||||
break;
|
||||
|
||||
@@ -200,6 +200,7 @@ struct sd_cpu {
|
||||
struct sd_cpu_info *d_cur;
|
||||
struct sd_cpu_info *d_prev;
|
||||
u16 cnt;
|
||||
int threads_per_core;
|
||||
enum sd_cpu_state state;
|
||||
};
|
||||
|
||||
@@ -232,6 +233,11 @@ static inline void sd_cpu_cpu_time_us_set(struct sd_cpu *cpu, u64 value)
|
||||
cpu->d_cur->cpu_time_us = value;
|
||||
}
|
||||
|
||||
static inline void sd_cpu_threads_per_core_set(struct sd_cpu *cpu, int value)
|
||||
{
|
||||
cpu->threads_per_core = value;
|
||||
}
|
||||
|
||||
static inline void sd_cpu_thread_time_us_set(struct sd_cpu *cpu, u64 value)
|
||||
{
|
||||
cpu->d_cur->thread_time_us = value;
|
||||
@@ -335,6 +341,7 @@ extern struct sd_cpu_item sd_cpu_item_state;
|
||||
extern struct sd_cpu_item sd_cpu_item_cpu_diff;
|
||||
extern struct sd_cpu_item sd_cpu_item_core_diff;
|
||||
extern struct sd_cpu_item sd_cpu_item_thread_diff;
|
||||
extern struct sd_cpu_item sd_cpu_item_smt_diff;
|
||||
extern struct sd_cpu_item sd_cpu_item_mgm_diff;
|
||||
extern struct sd_cpu_item sd_cpu_item_wait_diff;
|
||||
extern struct sd_cpu_item sd_cpu_item_steal_diff;
|
||||
@@ -398,6 +405,7 @@ static inline char *sd_sys_item_str(struct sd_sys *sys,
|
||||
extern struct sd_sys_item sd_sys_item_cpu_cnt;
|
||||
extern struct sd_sys_item sd_sys_item_core_cnt;
|
||||
extern struct sd_sys_item sd_sys_item_thread_cnt;
|
||||
extern struct sd_sys_item sd_sys_item_smt_diff;
|
||||
extern struct sd_sys_item sd_sys_item_cpu_oper_cnt;
|
||||
extern struct sd_sys_item sd_sys_item_cpu_deconf_cnt;
|
||||
extern struct sd_sys_item sd_sys_item_cpu_stop_cnt;
|
||||
|
||||
@@ -98,6 +98,18 @@ static u64 l_cpu_item_64(struct sd_cpu_item *item, struct sd_cpu *cpu)
|
||||
return l_cpu_info_u64(cpu->d_cur, item->offset) / cpu->cnt;
|
||||
}
|
||||
|
||||
static u64 l_cpu_smt_util(struct sd_cpu_item *item, struct sd_cpu *cpu)
|
||||
{
|
||||
u64 core_us, thr_us, mgm_us;
|
||||
(void)item;
|
||||
|
||||
core_us = sd_cpu_item_u64(&sd_cpu_item_core_diff, cpu);
|
||||
thr_us = sd_cpu_item_u64(&sd_cpu_item_thread_diff, cpu);
|
||||
mgm_us = sd_cpu_item_u64(&sd_cpu_item_mgm_diff, cpu);
|
||||
|
||||
return ht_calculate_smt_util(core_us, thr_us, mgm_us, cpu->threads_per_core);
|
||||
}
|
||||
|
||||
/*
|
||||
* CPU item definitions
|
||||
*/
|
||||
@@ -139,6 +151,13 @@ struct sd_cpu_item sd_cpu_item_thread_diff = {
|
||||
.fn_u64 = l_cpu_diff_u64,
|
||||
};
|
||||
|
||||
struct sd_cpu_item sd_cpu_item_smt_diff = {
|
||||
.table_col = TABLE_COL_TIME_DIFF_SUM(table_col_unit_perc, 'S', "smt"),
|
||||
.type = SD_TYPE_U64,
|
||||
.desc = "Real CPU SMT utilization",
|
||||
.fn_u64 = l_cpu_smt_util,
|
||||
};
|
||||
|
||||
struct sd_cpu_item sd_cpu_item_mgm_diff = {
|
||||
.table_col = TABLE_COL_TIME_DIFF_SUM(table_col_unit_perc, 'm', "mgm"),
|
||||
.type = SD_TYPE_U64,
|
||||
|
||||
@@ -208,6 +208,18 @@ static s64 l_sys_cpu_info_diff_s64(struct sd_sys_item *item, struct sd_sys *sys)
|
||||
return rc;
|
||||
}
|
||||
|
||||
static u64 l_sys_smt_util(struct sd_sys_item *item, struct sd_sys *sys)
|
||||
{
|
||||
u64 core_us, thr_us, mgm_us;
|
||||
(void)item;
|
||||
|
||||
core_us = sd_sys_item_u64(sys, &sd_sys_item_core_diff);
|
||||
thr_us = sd_sys_item_u64(sys, &sd_sys_item_thread_diff);
|
||||
mgm_us = sd_sys_item_u64(sys, &sd_sys_item_mgm_diff);
|
||||
|
||||
return ht_calculate_smt_util(core_us, thr_us, mgm_us, sys->threads_per_core);
|
||||
}
|
||||
|
||||
/*
|
||||
* System item definitions
|
||||
*/
|
||||
@@ -277,6 +289,13 @@ struct sd_sys_item sd_sys_item_thread_diff = {
|
||||
.fn_u64 = l_sys_cpu_info_diff_u64,
|
||||
};
|
||||
|
||||
struct sd_sys_item sd_sys_item_smt_diff = {
|
||||
.table_col = TABLE_COL_TIME_DIFF_SUM(table_col_unit_perc, 'S', "smt"),
|
||||
.type = SD_TYPE_U64,
|
||||
.desc = "Real CPU SMT utilization",
|
||||
.fn_u64 = l_sys_smt_util,
|
||||
};
|
||||
|
||||
struct sd_sys_item sd_sys_item_mgm_diff = {
|
||||
.table_col = TABLE_COL_TIME_DIFF_SUM(table_col_unit_perc, 'm', "mgm"),
|
||||
.offset = SD_CPU_INFO_OFFSET(mgm_time_us),
|
||||
|
||||
@@ -34,10 +34,10 @@
|
||||
/* Secure IPL error */
|
||||
#define ESECUREBOOT 0x00004512
|
||||
|
||||
/* kdump: No operating system information was found */
|
||||
/* os_info error: No operating system information was found */
|
||||
#define EOS_INFO_MISSING 0x00004520
|
||||
|
||||
/* kdump: The checksum of the operating system information is incorrect */
|
||||
/* os_info error: The checksum of the operating system information is incorrect */
|
||||
#define EOS_INFO_CSUM_FAILED 0x00004521
|
||||
|
||||
/* kdump: The major version of the operating system information is too high */
|
||||
@@ -21,10 +21,6 @@
|
||||
#define IPL_MAX_SUPPORTED_VERSION 0
|
||||
#define IPL_PARM_BLOCK_VERSION 0x1
|
||||
|
||||
/* IPL Types */
|
||||
#define IPL_TYPE_PV 0x5
|
||||
|
||||
|
||||
#ifndef __ASSEMBLER__
|
||||
|
||||
#include <stdint.h>
|
||||
@@ -43,6 +39,16 @@ struct ipl_pb_hdr {
|
||||
uint8_t pbt;
|
||||
} __packed;
|
||||
|
||||
/* IPL Parameter Block types */
|
||||
enum ipl_pbt {
|
||||
IPL_PBT_FCP = 0,
|
||||
IPL_PBT_SCP_DATA = 1,
|
||||
IPL_PBT_CCW = 2,
|
||||
IPL_PBT_ECKD = 3,
|
||||
IPL_PBT_NVME = 4,
|
||||
IPL_PBT_PV = 5,
|
||||
};
|
||||
|
||||
/* IPL Parameter Block 0 with common fields */
|
||||
struct ipl_pb0_common {
|
||||
uint32_t len;
|
||||
|
||||
@@ -22,11 +22,19 @@
|
||||
#define STAGE2_DESC _AC(0x78, UL)
|
||||
#define STAGE2_ENTRY _AC(0x2018, UL)
|
||||
#define STAGE2_HEAP_ADDRESS _AC(0x6000, UL)
|
||||
#define ECKD2DUMP_SV_HEAP_ADDRESS _AC(0xb000, UL)
|
||||
#define STAGE2_HEAP_SIZE _AC(0x3000, UL)
|
||||
#define STAGE2_STACK_ADDRESS _AC(0xe400, UL)
|
||||
#define STAGE2_STACK_SIZE _AC(0x1c00, UL)
|
||||
#define ECKD2DUMP_SV_STACK_ADDRESS _AC(0xe000, UL)
|
||||
#define ECKD2DUMP_SV_STACK_SIZE _AC(0x2000, UL)
|
||||
#define STAGE2_MAX_SIZE _AC(0x3000, UL)
|
||||
|
||||
#define STAGE2_DUMPER_SIZE_V1 _AC(0x1000, UL)
|
||||
#define STAGE2_DUMPER_SIZE_V2 _AC(0x2000, UL)
|
||||
#define STAGE2_DUMPER_SIZE_V3 _AC(0x3000, UL)
|
||||
#define STAGE2_DUMPER_SIZE_ZLIB _AC(0x8000, UL)
|
||||
|
||||
#define STAGE3_ENTRY _AC(0xa000, UL)
|
||||
|
||||
#define STAGE2_LOAD_ADDRESS _AC(0x2000, UL)
|
||||
|
||||
78
include/boot/os_info.h
Normal file
78
include/boot/os_info.h
Normal file
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
* zipl - zSeries Initial Program Loader tool
|
||||
*
|
||||
* os-info definitions
|
||||
*
|
||||
* Copyright IBM Corp. 2013, 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.
|
||||
*/
|
||||
|
||||
#ifndef OS_INFO_H
|
||||
#define OS_INFO_H
|
||||
|
||||
#include "lib/zt_common.h"
|
||||
#include "boot/error.h"
|
||||
#include "boot/s390.h"
|
||||
#include <stdint.h>
|
||||
|
||||
#define OS_INFO_MAGIC 0x4f53494e464f535aULL /* OSINFOSZ */
|
||||
#define OS_INFO_CSUM_SIZE (sizeof(struct os_info) - offsetof(struct os_info, version_major))
|
||||
#define OS_INFO_FLAGS_ENTRY_SIZE (sizeof(unsigned long))
|
||||
|
||||
#define OS_INFO_VMCOREINFO 0
|
||||
#define OS_INFO_REIPL_BLOCK 1
|
||||
#define OS_INFO_FLAGS_ENTRY 2
|
||||
|
||||
#define OS_INFO_FLAG_REIPL_CLEAR (1UL << 0)
|
||||
|
||||
struct os_info_entry {
|
||||
uint64_t addr;
|
||||
uint64_t size;
|
||||
uint32_t csum;
|
||||
} __packed;
|
||||
|
||||
struct os_info {
|
||||
uint64_t magic;
|
||||
uint32_t csum;
|
||||
uint16_t version_major;
|
||||
uint16_t version_minor;
|
||||
uint64_t crashkernel_addr;
|
||||
uint64_t crashkernel_size;
|
||||
struct os_info_entry entry[3];
|
||||
uint8_t reserved[4004];
|
||||
} __packed;
|
||||
|
||||
/*
|
||||
* Return 0 in case of valid os_info
|
||||
* Return -EOS_INFO_MISSING if os_info address is not page aligned or page is
|
||||
* not accessible or os_info magic value is missing.
|
||||
* Return -EOS_INFO_CSUM_FAILED if os_info checksum is invalid.
|
||||
*/
|
||||
static inline int os_info_check(const struct os_info *os_info)
|
||||
{
|
||||
if (!os_info ||
|
||||
(unsigned long)os_info % PAGE_SIZE ||
|
||||
!page_is_valid((unsigned long)os_info) ||
|
||||
os_info->magic != OS_INFO_MAGIC)
|
||||
return -EOS_INFO_MISSING;
|
||||
if (os_info->csum != csum_partial(&os_info->version_major, OS_INFO_CSUM_SIZE, 0))
|
||||
return -EOS_INFO_CSUM_FAILED;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Return 1 in case of valid os_info_entry, otherwise 0
|
||||
* Make sure that the entire os_info structure is checked first with os_info_check().
|
||||
*/
|
||||
static inline int os_info_entry_is_valid(const struct os_info_entry *entry)
|
||||
{
|
||||
return (entry &&
|
||||
entry->addr &&
|
||||
entry->size &&
|
||||
page_is_valid(entry->addr) &&
|
||||
entry->csum == csum_partial((void *)entry->addr, entry->size, 0));
|
||||
}
|
||||
|
||||
#endif /* OS_INFO_H */
|
||||
@@ -25,6 +25,7 @@
|
||||
#define STACK_FRAME_OVERHEAD _AC(160, U)
|
||||
|
||||
/* Facilities */
|
||||
#define DFLTCC_FACILITY _AC(151, U)
|
||||
#define UNPACK_FACILITY _AC(161, U)
|
||||
|
||||
#ifndef __ASSEMBLER__
|
||||
@@ -285,18 +286,21 @@ static __always_inline void __ctl_set_bit(unsigned int cr, unsigned int bit)
|
||||
* DIAG 308 support
|
||||
*/
|
||||
enum diag308_subcode {
|
||||
DIAG308_REL_HSA = 2,
|
||||
DIAG308_IPL = 3,
|
||||
DIAG308_DUMP = 4,
|
||||
DIAG308_SET = 5,
|
||||
DIAG308_STORE = 6,
|
||||
DIAG308_CLEAR_RESET = 0,
|
||||
DIAG308_LOAD_NORMAL_RESET = 1,
|
||||
DIAG308_REL_HSA = 2,
|
||||
DIAG308_LOAD_CLEAR = 3,
|
||||
DIAG308_LOAD_NORMAL_DUMP = 4,
|
||||
DIAG308_SET = 5,
|
||||
DIAG308_STORE = 6,
|
||||
DIAG308_LOAD_NORMAL = 7,
|
||||
DIAG308_SET_PV = 8,
|
||||
DIAG308_UNPACK_PV = 10,
|
||||
};
|
||||
|
||||
enum diag308_rc {
|
||||
DIAG308_RC_OK = 0x0001,
|
||||
DIAG308_RC_NO_CONF = 0x0102,
|
||||
DIAG308_RC_NOCONFIG = 0x0102,
|
||||
};
|
||||
|
||||
static __always_inline unsigned long diag308(unsigned long subcode, void *addr)
|
||||
|
||||
134
include/dump/s390_dump.h
Normal file
134
include/dump/s390_dump.h
Normal file
@@ -0,0 +1,134 @@
|
||||
/*
|
||||
* s390 related definitions and functions.
|
||||
*
|
||||
* Copyright IBM Corp. 2013, 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.
|
||||
*/
|
||||
|
||||
#ifndef S390_DUMP_H
|
||||
#define S390_DUMP_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include "boot/page.h"
|
||||
#include "lib/zt_common.h"
|
||||
|
||||
/*
|
||||
* S390 dump format defines
|
||||
*/
|
||||
#define DF_S390_MAGIC 0xa8190173618f23fdULL
|
||||
#define DF_S390_MAGIC_EXT 0xa8190173618f23feULL
|
||||
#define DF_S390_HDR_SIZE 0x1000
|
||||
#define DF_S390_EM_SIZE 16
|
||||
#define DF_S390_EM_MAGIC 0x44554d505f454e44ULL
|
||||
#define DF_S390_EM_STR "DUMP_END"
|
||||
#define DF_S390_CPU_MAX 512
|
||||
#define DF_S390_MAGIC_BLK_ECKD 3
|
||||
|
||||
/*
|
||||
* Architecture of dumped system
|
||||
*/
|
||||
enum df_s390_arch {
|
||||
DF_S390_ARCH_32 = 1,
|
||||
DF_S390_ARCH_64 = 2,
|
||||
};
|
||||
|
||||
/*
|
||||
* zipl parameters passed at tail of dump tools
|
||||
*/
|
||||
struct stage2dump_parm_tail {
|
||||
char reserved[6];
|
||||
uint8_t no_compress;
|
||||
uint8_t mvdump_force;
|
||||
uint64_t mem_upper_limit;
|
||||
} __packed;
|
||||
|
||||
/*
|
||||
* s390 dump header format
|
||||
*/
|
||||
struct df_s390_hdr {
|
||||
uint64_t magic; /* 0x000 */
|
||||
uint32_t version; /* 0x008 */
|
||||
uint32_t hdr_size; /* 0x00c */
|
||||
uint32_t dump_level; /* 0x010 */
|
||||
uint32_t page_size; /* 0x014 */
|
||||
uint64_t mem_size; /* 0x018 */
|
||||
uint64_t mem_start; /* 0x020 */
|
||||
uint64_t mem_end; /* 0x028 */
|
||||
uint32_t num_pages; /* 0x030 */
|
||||
uint32_t pad; /* 0x034 */
|
||||
uint64_t tod; /* 0x038 */
|
||||
uint64_t cpu_id; /* 0x040 */
|
||||
uint32_t arch; /* 0x048 */
|
||||
uint32_t volnr; /* 0x04c */
|
||||
uint32_t build_arch; /* 0x050 */
|
||||
uint64_t mem_size_real; /* 0x054 */
|
||||
uint8_t mvdump; /* 0x05c */
|
||||
uint16_t cpu_cnt; /* 0x05d */
|
||||
uint16_t real_cpu_cnt; /* 0x05f */
|
||||
uint8_t zlib_version_s390; /* 0x061 */
|
||||
uint32_t zlib_entry_size; /* 0x062 */
|
||||
uint8_t end_pad1[0x200 - 0x066]; /* 0x066 */
|
||||
uint64_t mvdump_sign; /* 0x200 */
|
||||
uint64_t mvdump_zipl_time; /* 0x208 */
|
||||
uint8_t end_pad2[0x800 - 0x210]; /* 0x210 */
|
||||
uint32_t lc_vec[DF_S390_CPU_MAX]; /* 0x800 */
|
||||
} __packed __aligned(16);
|
||||
|
||||
/*
|
||||
* End marker: Should be at the end of every valid s390 crash dump
|
||||
*/
|
||||
struct df_s390_em {
|
||||
union {
|
||||
uint64_t magic;
|
||||
char str[8];
|
||||
};
|
||||
uint64_t tod;
|
||||
} __packed __aligned(16);
|
||||
|
||||
/*
|
||||
* Dump segment header
|
||||
*/
|
||||
struct df_s390_dump_segm_hdr {
|
||||
union {
|
||||
struct {
|
||||
uint64_t start; /* 0x000 */
|
||||
uint64_t len; /* 0x008 */
|
||||
uint64_t stop_marker; /* 0x010 */
|
||||
/* Size in blocks of compressed dump segment written to disk */
|
||||
uint32_t size_on_disk; /* 0x018 */
|
||||
uint8_t reserved_pad[0x30 - 0x1c]; /* 0x01c */
|
||||
/*
|
||||
* Number of compressed entries in this dump segment (up to
|
||||
* 1011 entries)
|
||||
*/
|
||||
uint32_t entry_count; /* 0x030 */
|
||||
/*
|
||||
* Offsets in blocks to compressed entries written to disk
|
||||
* from the start of the dump segment.
|
||||
* High-order bit is set if the entry has been written
|
||||
* uncompressed.
|
||||
*/
|
||||
uint32_t entry_offset[]; /* 0x034 */
|
||||
} __packed;
|
||||
uint8_t padding[PAGE_SIZE];
|
||||
};
|
||||
};
|
||||
|
||||
/* Data compression granularity (size of input data chunk for zlib deflate) */
|
||||
#define DUMP_SEGM_ZLIB_ENTSIZE (1 * MIB)
|
||||
/* Maximum number of compressed entries in one dump segment */
|
||||
#define DUMP_SEGM_ZLIB_MAXENTS ((sizeof(struct df_s390_dump_segm_hdr) \
|
||||
- offsetof(struct df_s390_dump_segm_hdr, entry_offset)) \
|
||||
/ sizeof(uint32_t))
|
||||
/*
|
||||
* Maximum length of compressed dump segment considering the size of
|
||||
* a single input chunk
|
||||
*/
|
||||
#define DUMP_SEGM_ZLIB_MAXLEN (DUMP_SEGM_ZLIB_MAXENTS * DUMP_SEGM_ZLIB_ENTSIZE)
|
||||
/* Bitmask to mark uncompressed chunks */
|
||||
#define DUMP_SEGM_ENTRY_UNCOMPRESSED 0x80000000
|
||||
|
||||
#endif /* S390_DUMP_H */
|
||||
@@ -89,6 +89,7 @@ void ap_list_remove_all(struct util_list *list);
|
||||
/* Lock Functions */
|
||||
int ap_get_lock(void);
|
||||
int ap_get_lock_callout(void);
|
||||
int ap_try_lock_callout(void);
|
||||
int ap_release_lock(void);
|
||||
int ap_release_lock_callout(void);
|
||||
|
||||
|
||||
@@ -23,4 +23,6 @@ int util_lockfile_parent_lock(char *lockfile, int retries);
|
||||
int util_lockfile_release(char *lockfile);
|
||||
int util_lockfile_parent_release(char *lockfile);
|
||||
|
||||
int util_lockfile_peek_owner(char *lockfile, int *pid);
|
||||
|
||||
#endif /** LIB_UTIL_LOCKFILE_H @} */
|
||||
|
||||
@@ -13,8 +13,7 @@ RECURSIVE_TARGETS = all-recursive install-recursive clean-recursive \
|
||||
|
||||
|
||||
all: all-recursive
|
||||
check: check-recursive
|
||||
install: all install-recursive
|
||||
install: install-recursive
|
||||
clean: clean-recursive
|
||||
|
||||
|
||||
|
||||
28
libap/ap.c
28
libap/ap.c
@@ -722,6 +722,34 @@ int ap_get_lock_callout(void)
|
||||
return util_lockfile_parent_lock(AP_LOCKFILE, AP_LOCK_RETRIES);
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to acquire the ap config lock using the Parent Process ID without
|
||||
* waiting/retries. Detect if the attempt was rejected because the lock is
|
||||
* already held by the Parent Process ID.
|
||||
*
|
||||
* @retval 0 Lock acquired on behalf of parent process
|
||||
* @retval 1 Lock not obtained, already held by parent
|
||||
* @retval != 0 Lock was not obtained, other error
|
||||
*/
|
||||
int ap_try_lock_callout(void)
|
||||
{
|
||||
int pid, ppid, rc;
|
||||
|
||||
if (util_lockfile_parent_lock(AP_LOCKFILE, 0)) {
|
||||
/* Lock is already held, let's peek at the owner */
|
||||
ppid = getppid();
|
||||
rc = util_lockfile_peek_owner(AP_LOCKFILE, &pid);
|
||||
if (rc || pid != ppid) {
|
||||
/* We didn't get the lock, unknown or other owner */
|
||||
return 2;
|
||||
}
|
||||
/* Signify that the lock is already held by the caller */
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Release the ap config lock
|
||||
*
|
||||
|
||||
@@ -55,7 +55,7 @@ check-dep-libekmfweb: detect-openssl-version.dep
|
||||
"detect-openssl-version.dep", \
|
||||
"openssl-devel version >= 1.1.1", \
|
||||
"HAVE_OPENSSL=0", \
|
||||
-I. -lcrypto -DOPENSSL_SUPPRESS_DEPRECATED)
|
||||
-I. `$(PKG_CONFIG) --cflags --libs libcrypto` -DOPENSSL_SUPPRESS_DEPRECATED)
|
||||
$(call check_dep, \
|
||||
"libekmfweb", \
|
||||
"json-c/json.h", \
|
||||
@@ -66,7 +66,7 @@ check-dep-libekmfweb: detect-openssl-version.dep
|
||||
"curl/curl.h", \
|
||||
"libcurl-devel", \
|
||||
"HAVE_LIBCURL=0" \
|
||||
`$(CURL_CONFIG) --cflags` `$(CURL_CONFIG) --libs`)
|
||||
`$(PKG_CONFIG) --cflags --libs libcurl`)
|
||||
$(CURL_CONFIG) --ssl-backends | grep OpenSSL >/dev/null 2>&1 || { echo "Error: libcurl is not built with the OpenSSL backend"; exit 1; }
|
||||
touch check-dep-libekmfweb
|
||||
|
||||
@@ -85,8 +85,8 @@ ekmfweb.o: check-dep-libekmfweb ekmfweb.c utilities.h cca.h $(rootdir)include/ek
|
||||
utilities.o: check-dep-libekmfweb utilities.c utilities.h $(rootdir)include/ekmfweb/ekmfweb.h
|
||||
cca.o: check-dep-libekmfweb cca.c cca.h utilities.h $(rootdir)include/ekmfweb/ekmfweb.h
|
||||
|
||||
libekmfweb.so.$(VERSION): ALL_CFLAGS += -fPIC `$(CURL_CONFIG) --cflags`
|
||||
libekmfweb.so.$(VERSION): LDLIBS = -ljson-c -lcrypto -lssl `$(CURL_CONFIG) --libs` -ldl
|
||||
libekmfweb.so.$(VERSION): ALL_CFLAGS += -fPIC `$(PKG_CONFIG) --cflags json-c libcurl libcrypto libssl`
|
||||
libekmfweb.so.$(VERSION): LDLIBS = `$(PKG_CONFIG) --libs json-c libcurl libcrypto libssl` -ldl
|
||||
libekmfweb.so.$(VERSION): ALL_LDFLAGS += -shared -Wl,--version-script=libekmfweb.map \
|
||||
-Wl,-z,defs,-Bsymbolic -Wl,-soname,libekmfweb.so.$(VERM)
|
||||
libekmfweb.so.$(VERSION): ekmfweb.o utilities.o cca.o $(libs)
|
||||
@@ -98,7 +98,7 @@ install-libekmfweb.so.$(VERSION): libekmfweb.so.$(VERSION)
|
||||
$(INSTALL) -g $(GROUP) -o $(OWNER) -m 755 -T libekmfweb.so.$(VERSION) $(DESTDIR)$(SOINSTALLDIR)/libekmfweb.so.$(VERSION)
|
||||
ln -srf $(DESTDIR)$(SOINSTALLDIR)/libekmfweb.so.$(VERSION) $(DESTDIR)$(SOINSTALLDIR)/libekmfweb.so.$(VERM)
|
||||
ln -srf $(DESTDIR)$(SOINSTALLDIR)/libekmfweb.so.$(VERSION) $(DESTDIR)$(SOINSTALLDIR)/libekmfweb.so
|
||||
$(INSTALL) -d -m 770 $(DESTDIR)$(USRINCLUDEDIR)/ekmfweb
|
||||
$(INSTALL) -d -m 755 $(DESTDIR)$(USRINCLUDEDIR)/ekmfweb
|
||||
$(INSTALL) -g $(GROUP) -o $(OWNER) -m 644 $(rootdir)include/ekmfweb/ekmfweb.h $(DESTDIR)$(USRINCLUDEDIR)/ekmfweb
|
||||
|
||||
install: all $(INSTALL_TARGETS)
|
||||
|
||||
@@ -51,7 +51,6 @@ detect-openssl-version.dep:
|
||||
mv $(TMPFILE) $@
|
||||
|
||||
CURL_CONFIG ?= curl-config
|
||||
XML2_CONFIG ?= xml2-config
|
||||
|
||||
check-dep-libkmipclient: detect-openssl-version.dep
|
||||
$(call check_dep, \
|
||||
@@ -59,7 +58,7 @@ check-dep-libkmipclient: detect-openssl-version.dep
|
||||
"detect-openssl-version.dep", \
|
||||
"openssl-devel version >= 1.1.1", \
|
||||
"HAVE_OPENSSL=0", \
|
||||
-I. -lcrypto -DOPENSSL_SUPPRESS_DEPRECATED)
|
||||
-I. `$(PKG_CONFIG) --cflags --libs libcrypto` -DOPENSSL_SUPPRESS_DEPRECATED)
|
||||
$(call check_dep, \
|
||||
"libkmipclient", \
|
||||
"json-c/json.h", \
|
||||
@@ -70,13 +69,13 @@ check-dep-libkmipclient: detect-openssl-version.dep
|
||||
"libxml/tree.h", \
|
||||
"libxml2-devel", \
|
||||
"HAVE_LIBXML2=0", \
|
||||
`$(XML2_CONFIG) --cflags` `$(XML2_CONFIG) --libs`)
|
||||
`$(PKG_CONFIG) --cflags --libs libxml-2.0`)
|
||||
$(call check_dep, \
|
||||
"libkmipclient", \
|
||||
"curl/curl.h", \
|
||||
"libcurl-devel", \
|
||||
"HAVE_LIBCURL=0" \
|
||||
`$(CURL_CONFIG) --cflags` `$(CURL_CONFIG) --libs`)
|
||||
`$(PKG_CONFIG) --cflags --libs libcurl`)
|
||||
$(CURL_CONFIG) --ssl-backends | grep OpenSSL >/dev/null 2>&1 || { echo "Error: libcurl is not built with the OpenSSL backend"; exit 1; }
|
||||
touch check-dep-libkmipclient
|
||||
|
||||
@@ -107,8 +106,8 @@ tls.o: check-dep-libkmipclient tls.c kmip.h utils.h $(rootdir)include/kmipclient
|
||||
names.o: check-dep-libkmipclient names.c names.h utils.h $(rootdir)include/kmipclient/kmipclient.h
|
||||
utils.o: check-dep-libkmipclient utils.c names.h utils.h $(rootdir)include/kmipclient/kmipclient.h
|
||||
|
||||
libkmipclient.so.$(VERSION): ALL_CFLAGS += -fPIC `$(XML2_CONFIG) --cflags` `$(CURL_CONFIG) --cflags`
|
||||
libkmipclient.so.$(VERSION): LDLIBS = -ljson-c -lcrypto -lssl `$(XML2_CONFIG) --libs` `$(CURL_CONFIG) --libs`
|
||||
libkmipclient.so.$(VERSION): ALL_CFLAGS += -fPIC `$(PKG_CONFIG) --cflags json-c libcrypto libssl libxml-2.0 libcurl`
|
||||
libkmipclient.so.$(VERSION): LDLIBS = `$(PKG_CONFIG) --libs json-c libcrypto libssl libxml-2.0 libcurl`
|
||||
libkmipclient.so.$(VERSION): ALL_LDFLAGS += -shared -Wl,--version-script=libkmipclient.map \
|
||||
-Wl,-z,defs,-Bsymbolic -Wl,-soname,libkmipclient.so.$(VERM)
|
||||
libkmipclient.so.$(VERSION): kmip.o request.o response.o attribute.o key.o ttlv.o json.o \
|
||||
@@ -121,7 +120,7 @@ install-libkmipclient.so.$(VERSION): libkmipclient.so.$(VERSION)
|
||||
$(INSTALL) -g $(GROUP) -o $(OWNER) -m 755 -T libkmipclient.so.$(VERSION) $(DESTDIR)$(SOINSTALLDIR)/libkmipclient.so.$(VERSION)
|
||||
ln -srf $(DESTDIR)$(SOINSTALLDIR)/libkmipclient.so.$(VERSION) $(DESTDIR)$(SOINSTALLDIR)/libkmipclient.so.$(VERM)
|
||||
ln -srf $(DESTDIR)$(SOINSTALLDIR)/libkmipclient.so.$(VERSION) $(DESTDIR)$(SOINSTALLDIR)/libkmipclient.so
|
||||
$(INSTALL) -d -m 770 $(DESTDIR)$(USRINCLUDEDIR)/kmipclient
|
||||
$(INSTALL) -d -m 755 $(DESTDIR)$(USRINCLUDEDIR)/kmipclient
|
||||
$(INSTALL) -g $(GROUP) -o $(OWNER) -m 644 $(rootdir)include/kmipclient/kmipclient.h $(DESTDIR)$(USRINCLUDEDIR)/kmipclient
|
||||
|
||||
install: all $(INSTALL_TARGETS)
|
||||
|
||||
@@ -311,13 +311,13 @@ int util_file_read_i(int *val, int base, const char *fmt, ...)
|
||||
return -1;
|
||||
switch (base) {
|
||||
case 8:
|
||||
count = sscanf(buf, "%do", val);
|
||||
count = sscanf(buf, "%o", val);
|
||||
break;
|
||||
case 10:
|
||||
count = sscanf(buf, "%dd", val);
|
||||
count = sscanf(buf, "%d", val);
|
||||
break;
|
||||
case 16:
|
||||
count = sscanf(buf, "%dx", val);
|
||||
count = sscanf(buf, "%x", val);
|
||||
break;
|
||||
default:
|
||||
util_panic("Invalid base: %d\n", base);
|
||||
@@ -425,13 +425,13 @@ int util_file_read_ui(unsigned int *val, int base, const char *fmt, ...)
|
||||
return -1;
|
||||
switch (base) {
|
||||
case 8:
|
||||
count = sscanf(buf, "%uo", val);
|
||||
count = sscanf(buf, "%o", val);
|
||||
break;
|
||||
case 10:
|
||||
count = sscanf(buf, "%uu", val);
|
||||
count = sscanf(buf, "%u", val);
|
||||
break;
|
||||
case 16:
|
||||
count = sscanf(buf, "%ux", val);
|
||||
count = sscanf(buf, "%x", val);
|
||||
break;
|
||||
default:
|
||||
util_panic("Invalid base: %d\n", base);
|
||||
|
||||
@@ -299,3 +299,35 @@ int util_lockfile_parent_release(char *lockfile)
|
||||
{
|
||||
return do_lockfile_release(lockfile, getppid());
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the pid that owns the specified lockfile.
|
||||
*
|
||||
* @param[in] lockfile Path to the lock file
|
||||
* @param[in,out] pid Buffer to place owning pid
|
||||
*
|
||||
* @retval 0 pid provided in buffer
|
||||
* @retval !=0 Error, no pid provided
|
||||
*/
|
||||
int util_lockfile_peek_owner(char *lockfile, int *pid)
|
||||
{
|
||||
char buf[BUFSIZE];
|
||||
int fd, len;
|
||||
|
||||
if (!lockfile || !pid)
|
||||
return UTIL_LOCKFILE_ERR;
|
||||
|
||||
/* Open lockfile, read the owning pid if it exists */
|
||||
fd = open(lockfile, O_RDONLY);
|
||||
if (fd < 0)
|
||||
return UTIL_LOCKFILE_ERR;
|
||||
|
||||
len = read(fd, buf, sizeof(buf));
|
||||
close(fd);
|
||||
if (len <= 0)
|
||||
return UTIL_LOCKFILE_ERR;
|
||||
buf[len] = 0;
|
||||
*pid = atoi(buf);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
/* we may use header_generic and header_simple_table from the util_funcs module */
|
||||
|
||||
config_require(util_funcs)
|
||||
config_require(util_funcs);
|
||||
|
||||
|
||||
/* function prototypes */
|
||||
|
||||
@@ -9,7 +9,7 @@ RECURSIVE_TARGETS := all-recursive clean-recursive install-recursive
|
||||
|
||||
all: all-recursive
|
||||
|
||||
install: all install-recursive
|
||||
install: install-recursive
|
||||
|
||||
clean: clean-recursive
|
||||
|
||||
|
||||
@@ -24,12 +24,27 @@ Show help options
|
||||
\fBFILE\fP specifies the attestation result as input.
|
||||
.TP
|
||||
.B
|
||||
\fB-o\fP, \fB--ouput\fP=\fBFILE\fP
|
||||
\fBFILE\fP specifies the output for the verification result.
|
||||
.TP
|
||||
.B
|
||||
\fB--hdr\fP=\fBFILE\fP
|
||||
Specify the header of the guest image. Exactly one is required.
|
||||
.TP
|
||||
.B
|
||||
\fB-a\fP, \fB--arpk\fP=\fBFILE\fP
|
||||
Use \fBFILE\fP to specify the GCM-AES256 key to decrypt the attestation request. Delete this key after verification.
|
||||
.TP
|
||||
.B
|
||||
\fB--format\fP=\fByaml\fP
|
||||
Define the output format.
|
||||
Default value: 'yaml'
|
||||
|
||||
Possible values:
|
||||
.RS 4
|
||||
- \fByaml\fP: Use YAML format
|
||||
.RE
|
||||
|
||||
.TP
|
||||
.B
|
||||
\fB-V\fP, \fB--verbose\fP
|
||||
|
||||
@@ -49,8 +49,10 @@ static pvattest_config_t pvattest_config = {
|
||||
},
|
||||
.verify = {
|
||||
.input_path = NULL,
|
||||
.output_path = NULL,
|
||||
.hdr_path = NULL,
|
||||
.arp_key_in_path = NULL,
|
||||
.output_fmt = VERIFY_FMT_YAML,
|
||||
},
|
||||
};
|
||||
typedef gboolean (*verify_options_fn_t)(GError **);
|
||||
@@ -329,6 +331,15 @@ static gboolean hex_str_toull(const char *nptr, uint64_t *dst, GError **error)
|
||||
.description = "Use FILE to specify the user data.\n", .arg_description = "FILE", \
|
||||
}
|
||||
|
||||
#define _entry__verify_format(__indent) \
|
||||
{ \
|
||||
.long_name = "format", .short_name = 0, .flags = G_OPTION_FLAG_NONE, \
|
||||
.arg = G_OPTION_ARG_CALLBACK, .arg_data = &set_verify_output_format, \
|
||||
.description = "Define the output format.\n" __indent \
|
||||
"Defaults to 'yaml'. (possible values: 'yaml')\n", \
|
||||
.arg_description = "FORMAT", \
|
||||
}
|
||||
|
||||
static gboolean increase_log_lvl(G_GNUC_UNUSED const char *option_name,
|
||||
G_GNUC_UNUSED const char *value, G_GNUC_UNUSED void *data,
|
||||
G_GNUC_UNUSED GError **error)
|
||||
@@ -337,6 +348,20 @@ static gboolean increase_log_lvl(G_GNUC_UNUSED const char *option_name,
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean set_verify_output_format(const char *option_name, const char *value,
|
||||
G_GNUC_UNUSED void *data, GError **error)
|
||||
{
|
||||
if (!g_strcmp0(value, "yaml")) {
|
||||
pvattest_config.verify.output_fmt = VERIFY_FMT_YAML;
|
||||
} else {
|
||||
g_set_error(error, G_OPTION_ERROR, G_OPTION_ERROR_FAILED,
|
||||
_("Found value '%s' for option '%s', but only 'yaml' is allowed."),
|
||||
value, option_name);
|
||||
return FALSE;
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean create_set_paf(G_GNUC_UNUSED const char *option_name, const char *value,
|
||||
G_GNUC_UNUSED void *data, GError **error)
|
||||
{
|
||||
@@ -445,13 +470,16 @@ static gboolean verify_perform(GError **error)
|
||||
}
|
||||
|
||||
/************************* VERIFY OPTIONS ************************************/
|
||||
#define verify_indent " "
|
||||
#define verify_indent " "
|
||||
|
||||
static GOptionEntry verify_options[] = {
|
||||
_entry_input(&pvattest_config.verify.input_path, "attestation result", verify_indent),
|
||||
_entry_output(&pvattest_config.verify.output_path,
|
||||
"verification result.\n" verify_indent "(optional)", verify_indent),
|
||||
_entry_guest_hdr(&pvattest_config.verify.hdr_path, verify_indent),
|
||||
_entry_att_prot_key_load(&pvattest_config.verify.arp_key_in_path, verify_indent),
|
||||
_entry_verbose(verify_indent),
|
||||
_entry__verify_format(verify_indent),
|
||||
{ NULL },
|
||||
};
|
||||
|
||||
@@ -631,6 +659,7 @@ static void pvattest_parse_clear_verify_config(pvattest_verify_config_t *config)
|
||||
if (!config)
|
||||
return;
|
||||
g_free(config->input_path);
|
||||
g_free(config->output_path);
|
||||
g_free(config->hdr_path);
|
||||
g_free(config->arp_key_in_path);
|
||||
}
|
||||
|
||||
@@ -58,8 +58,15 @@ typedef struct {
|
||||
char *user_data_path; /* default NULL */
|
||||
} pvattest_perform_config_t;
|
||||
|
||||
enum verify_output_format {
|
||||
VERIFY_FMT_HUMAN,
|
||||
VERIFY_FMT_YAML,
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
char *input_path;
|
||||
char *output_path;
|
||||
enum verify_output_format output_fmt;
|
||||
char *hdr_path;
|
||||
char *arp_key_in_path;
|
||||
} pvattest_verify_config_t;
|
||||
|
||||
@@ -455,8 +455,7 @@ static void print_entry(const char *name, GBytes *data, const gboolean print_dat
|
||||
fprintf(stream, _("%s (%#lx bytes)"), name, g_bytes_get_size(data));
|
||||
if (print_data) {
|
||||
fprintf(stream, ":\n");
|
||||
pvattest_hexdump(g_bytes_get_data(data, NULL), g_bytes_get_size(data), 16, " ",
|
||||
stream);
|
||||
pvattest_hexdump(stream, data, 16, " ", TRUE);
|
||||
}
|
||||
fprintf(stream, "\n");
|
||||
}
|
||||
|
||||
@@ -159,24 +159,47 @@ void pvattest_log_bytes(const void *data, size_t size, size_t width, const char
|
||||
g_log(PVATTEST_BYTES_LOG_DOMAIN, log_lvl, "\n");
|
||||
}
|
||||
|
||||
void pvattest_hexdump(const void *data, size_t size, size_t width, const char *prefix, FILE *stream)
|
||||
int pvattest_hexdump(FILE *stream, GBytes *bytes, const size_t width, const char *prefix,
|
||||
const gboolean beautify)
|
||||
{
|
||||
const uint8_t *data_b = data;
|
||||
const uint8_t *data;
|
||||
size_t size;
|
||||
|
||||
pv_wrapped_g_assert(data);
|
||||
pv_wrapped_g_assert(bytes);
|
||||
pv_wrapped_g_assert(stream);
|
||||
|
||||
fprintf(stream, "%s0x0000 ", prefix);
|
||||
data = g_bytes_get_data(bytes, &size);
|
||||
pv_wrapped_g_assert(data);
|
||||
|
||||
if (beautify) {
|
||||
if (fprintf(stream, "%s0x0000 ", prefix) < 0)
|
||||
return -1;
|
||||
} else {
|
||||
if (fprintf(stream, "%s", prefix) < 0)
|
||||
return -1;
|
||||
}
|
||||
for (size_t i = 0; i < size; i++) {
|
||||
fprintf(stream, "%02x", data_b[i]);
|
||||
if (i % 2 == 1)
|
||||
fprintf(stream, " ");
|
||||
if (fprintf(stream, "%02x", data[i]) < 0)
|
||||
return -1;
|
||||
if (i % 2 == 1 && beautify) {
|
||||
if (fprintf(stream, " ") < 0)
|
||||
return -1;
|
||||
}
|
||||
if (i == size - 1)
|
||||
break;
|
||||
if (i % width == width - 1)
|
||||
fprintf(stream, "\n%s0x%04lx ", prefix, i + 1);
|
||||
if (width == 0)
|
||||
continue;
|
||||
if (i % width == width - 1) {
|
||||
if (beautify) {
|
||||
if (fprintf(stream, "\n%s0x%04lx ", prefix, i + 1) < 0)
|
||||
return -1;
|
||||
} else {
|
||||
if (fprintf(stream, "\n%s", prefix) < 0)
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
fprintf(stream, "\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
void pvattest_log_GError(const char *info, GError *error)
|
||||
|
||||
@@ -60,8 +60,8 @@ void pvattest_log_plain_logger(const char *log_domain, GLogLevelFlags level, con
|
||||
}
|
||||
void pvattest_log_bytes(const void *data, size_t size, size_t width, const char *prefix,
|
||||
gboolean beautify, GLogLevelFlags log_lvl) PV_NONNULL(1);
|
||||
void pvattest_hexdump(const void *data, size_t size, size_t width, const char *prefix, FILE *stream)
|
||||
PV_NONNULL(1, 5);
|
||||
int pvattest_hexdump(FILE *stream, GBytes *bytes, const size_t width, const char *prefix,
|
||||
const gboolean beautify) PV_NONNULL(1, 2);
|
||||
void pvattest_log_GError(const char *info, GError *error) PV_NONNULL(1);
|
||||
|
||||
#endif /* PVATTEST_LOG_H */
|
||||
|
||||
@@ -257,14 +257,63 @@ err_exit:
|
||||
}
|
||||
#endif /* PVATTEST_COMPILE_PERFORM */
|
||||
|
||||
static int fprint_verify_result(FILE *stream, const enum verify_output_format fmt,
|
||||
GBytes *config_uid, GBytes *additional_data)
|
||||
{
|
||||
switch (fmt) {
|
||||
case VERIFY_FMT_HUMAN:
|
||||
if (fprintf(stream, _("Attestation measurement verified\n")) < 0)
|
||||
return -1;
|
||||
if (fprintf(stream, _("Config UID:\n")) < 0)
|
||||
return -1;
|
||||
if (pvattest_hexdump(stream, config_uid, 0x10L, "0x", FALSE) < 0)
|
||||
return -1;
|
||||
if (fprintf(stream, _("\n")) < 0)
|
||||
return -1;
|
||||
|
||||
if (additional_data) {
|
||||
if (fprintf(stream, _("Additional Data:\n")) < 0)
|
||||
return -1;
|
||||
if (pvattest_hexdump(stream, additional_data, 0x60L, "0x", FALSE) < 0)
|
||||
return -1;
|
||||
if (fprintf(stream, _("\n")) < 0)
|
||||
return -1;
|
||||
}
|
||||
break;
|
||||
case VERIFY_FMT_YAML:
|
||||
if (fprintf(stream, "cuid: ") < 0)
|
||||
return -1;
|
||||
if (pvattest_hexdump(stream, config_uid, 0L, "'0x", FALSE) < 0)
|
||||
return -1;
|
||||
if (fprintf(stream, _("'\n")) < 0)
|
||||
return -1;
|
||||
|
||||
if (additional_data) {
|
||||
if (fprintf(stream, "add: ") < 0)
|
||||
return -1;
|
||||
|
||||
if (pvattest_hexdump(stream, additional_data, 0x0L, "'0x", FALSE) < 0)
|
||||
return -1;
|
||||
if (fprintf(stream, _("'\n")) < 0)
|
||||
return -1;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
g_assert_not_reached();
|
||||
break;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
#define __PVATTEST_VERIFY_ERROR_MSG _("Attestation measurement verification failed")
|
||||
static int do_verify(pvattest_verify_config_t *verify_config)
|
||||
static int do_verify(const pvattest_verify_config_t *verify_config, const int appl_log_lvl)
|
||||
{
|
||||
g_autoptr(GBytes) user_data = NULL, uv_measurement = NULL, additional_data = NULL,
|
||||
image_hdr = NULL, calc_measurement = NULL, config_uid = NULL,
|
||||
meas_key = NULL, arp_key = NULL, nonce = NULL, serialized_arcb = NULL;
|
||||
g_autofree att_meas_ctx_t *measurement_hdr = NULL;
|
||||
g_autoptr(exchange_format_ctx_t) input_ctx = NULL;
|
||||
const char *err_prefix = __PVATTEST_VERIFY_ERROR_MSG;
|
||||
g_autoptr(GError) error = NULL;
|
||||
gboolean rc;
|
||||
|
||||
@@ -322,21 +371,37 @@ static int do_verify(pvattest_verify_config_t *verify_config)
|
||||
return PVATTEST_EXIT_MEASURE_NOT_VERIFIED;
|
||||
}
|
||||
|
||||
pvattest_log_info(_("Attestation measurement verified"));
|
||||
pvattest_log_info(_("Config UID:"));
|
||||
pvattest_log_bytes(g_bytes_get_data(config_uid, NULL), g_bytes_get_size(config_uid), 16L,
|
||||
"", FALSE, PVATTEST_LOG_LVL_INFO);
|
||||
/* Write human-readable output to stdout */
|
||||
if (appl_log_lvl >= PVATTEST_LOG_LVL_INFO) {
|
||||
if (fprint_verify_result(stdout, VERIFY_FMT_HUMAN, config_uid, additional_data) <
|
||||
0) {
|
||||
g_set_error(&error, PV_GLIB_HELPER_ERROR, PV_GLIB_HELPER_FILE_ERROR,
|
||||
"stdout: %s", g_strerror(errno));
|
||||
err_prefix = "Failed to write output";
|
||||
goto err_exit;
|
||||
}
|
||||
}
|
||||
|
||||
if (additional_data) {
|
||||
pvattest_log_info(_("\nAdditional Data:"));
|
||||
pvattest_log_bytes(g_bytes_get_data(additional_data, NULL),
|
||||
g_bytes_get_size(additional_data), 16L, "", FALSE,
|
||||
PVATTEST_LOG_LVL_INFO);
|
||||
/* Write to file */
|
||||
if (verify_config->output_path) {
|
||||
g_autoptr(FILE) output = pv_file_open(verify_config->output_path, "wx", &error);
|
||||
|
||||
if (!output) {
|
||||
err_prefix = "Failed to write output";
|
||||
goto err_exit;
|
||||
}
|
||||
if (fprint_verify_result(output, verify_config->output_fmt, config_uid,
|
||||
additional_data) < 0) {
|
||||
g_set_error(&error, PV_GLIB_HELPER_ERROR, PV_GLIB_HELPER_FILE_ERROR,
|
||||
"'%s': %s", verify_config->output_path, g_strerror(errno));
|
||||
err_prefix = "Failed to write output";
|
||||
goto err_exit;
|
||||
}
|
||||
}
|
||||
return EXIT_SUCCESS;
|
||||
|
||||
err_exit:
|
||||
pvattest_log_GError(__PVATTEST_VERIFY_ERROR_MSG, error);
|
||||
pvattest_log_GError(err_prefix, error);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
@@ -389,7 +454,7 @@ int main(int argc, char *argv[])
|
||||
break;
|
||||
#endif /* PVATTEST_COMPILE_PERFORM */
|
||||
case PVATTEST_SUBC_VERIFY:
|
||||
rc = do_verify(&config->verify);
|
||||
rc = do_verify(&config->verify, appl_log_lvl);
|
||||
break;
|
||||
default:
|
||||
g_return_val_if_reached(EXIT_FAILURE);
|
||||
|
||||
11
rust/.gitignore
vendored
Normal file
11
rust/.gitignore
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
# Generated by Cargo
|
||||
# will have compiled files and executables
|
||||
debug/
|
||||
target/
|
||||
|
||||
# These are backup files generated by rustfmt
|
||||
*.rs.bk
|
||||
|
||||
# Generated during make build can be removed at any point
|
||||
.check-dep-pvtools
|
||||
.check-cargo
|
||||
1380
rust/Cargo.lock
generated
Normal file
1380
rust/Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
14
rust/Cargo.toml
Normal file
14
rust/Cargo.toml
Normal file
@@ -0,0 +1,14 @@
|
||||
[workspace]
|
||||
members = [
|
||||
"pv",
|
||||
"pvsecret",
|
||||
"utils",
|
||||
]
|
||||
resolver = "2"
|
||||
|
||||
[workspace.package]
|
||||
edition = "2021"
|
||||
license = "MIT"
|
||||
|
||||
[profile.release]
|
||||
lto = true
|
||||
124
rust/Makefile
Normal file
124
rust/Makefile
Normal file
@@ -0,0 +1,124 @@
|
||||
include ../common.mak
|
||||
HAVE_CARGO ?= 1
|
||||
HAVE_OPENSSL ?= 1
|
||||
HAVE_LIBCURL ?= 1
|
||||
|
||||
INSTALL_TARGETS := skip-build
|
||||
BUILD_TARGETS := skip-build
|
||||
PV_BUILD_TARGETS := skip-pv-build
|
||||
CARGO_TARGETS :=
|
||||
PV_TARGETS :=
|
||||
CARGO_TEST_TARGETS :=
|
||||
|
||||
ifneq (${HAVE_CARGO},0)
|
||||
CARGO_TARGETS :=
|
||||
|
||||
BUILD_TARGETS = $(CARGO_TARGETS)
|
||||
INSTALL_TARGETS := install-rust-tools install-man
|
||||
CARGO_TEST_TARGETS = $(addsuffix .test, $(CARGO_TARGETS))
|
||||
|
||||
ifneq (${HAVE_OPENSSL},0)
|
||||
ifneq (${HAVE_LIBCURL},0)
|
||||
PV_TARGETS := pvsecret
|
||||
|
||||
PV_BUILD_TARGETS := $(PV_TARGETS)
|
||||
CARGO_TEST_TARGETS += $(addsuffix .test,pv $(PV_TARGETS))
|
||||
endif #LIBCURL
|
||||
endif #OPENSSL
|
||||
TEST_TARGETS := $(addsuffix _build,$(CARGO_TEST_TARGETS))
|
||||
endif #CARGO
|
||||
|
||||
BUILD_TARGETS += $(PV_BUILD_TARGETS)
|
||||
|
||||
# build release targets by default
|
||||
ifeq ("${D}","0")
|
||||
ALL_CARGOFLAGS += --release
|
||||
endif
|
||||
|
||||
# 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
|
||||
$(BUILD_TARGETS) rust-test: CC = $(CC_SILENT)
|
||||
$(BUILD_TARGETS) rust-test: AR = $(AR_SILENT)
|
||||
|
||||
$(PV_TARGETS): .check-dep-pvtools
|
||||
$(PV_TARGETS) $(CARGO_TARGETS): .check-cargo .no-cross-compile
|
||||
$(CARGO_BUILD) --bin $@ $(ALL_CARGOFLAGS)
|
||||
.PHONY: $(PV_TARGETS) $(CARGO_TARGETS)
|
||||
|
||||
$(TEST_TARGETS): ALL_CARGOFLAGS += --no-run
|
||||
$(CARGO_TEST_TARGETS) $(TEST_TARGETS): .check-cargo .no-cross-compile
|
||||
$(CARGO_TEST) --package $(basename $@) --all-features $(ALL_CARGOFLAGS)
|
||||
.PHONY: $(TEST_TARGETS) $(CARGO_TEST_TARGETS)
|
||||
|
||||
skip-build:
|
||||
echo " SKIP rust-tools due to unresolved dependencies"
|
||||
|
||||
skip-pv-build:
|
||||
echo " SKIP rust-pv-tools due to unresolved dependencies"
|
||||
|
||||
all: $(BUILD_TARGETS)
|
||||
install: $(INSTALL_TARGETS)
|
||||
|
||||
print-rust-targets:
|
||||
echo $(BUILD_TARGETS)
|
||||
|
||||
clean:
|
||||
$(CARGO_CLEAN) ${ALL_CARGOFLAGS}
|
||||
$(RM) -- .check-dep-pvtools .detect-openssl.dep.c .check-cargo
|
||||
|
||||
rust-test: $(CARGO_TEST_TARGETS)
|
||||
|
||||
install-rust-tools: $(BUILD_TARGETS)
|
||||
$(INSTALL) -d -m 755 $(DESTDIR)$(USRBINDIR)
|
||||
$(foreach target,$(CARGO_TARGETS),\
|
||||
$(INSTALL) target/release/$(target) $(DESTDIR)$(USRBINDIR);)
|
||||
$(foreach target,$(PV_TARGETS),\
|
||||
$(INSTALL) target/release/$(target) $(DESTDIR)$(USRBINDIR);)
|
||||
|
||||
install-man:
|
||||
$(foreach target,$(CARGO_TARGETS),\
|
||||
$(INSTALL) -m 644 $(target)/man/*.1 -t $(DESTDIR)$(MANDIR)/man1;)
|
||||
$(foreach target,$(PV_TARGETS),\
|
||||
$(INSTALL) -m 644 $(target)/man/*.1 -t $(DESTDIR)$(MANDIR)/man1;)
|
||||
|
||||
.PHONY: all install clean skip-build install-rust-tools print-rust-targets install-man rust-test
|
||||
|
||||
.check-cargo:
|
||||
ifeq ($(shell command -v $(CARGO)),)
|
||||
$(call check_dep, \
|
||||
"rust/cargo", \
|
||||
"invalid-incl", \
|
||||
"cargo", \
|
||||
"HAVE_CARGO=0")
|
||||
endif
|
||||
touch $@
|
||||
|
||||
.no-cross-compile:
|
||||
ifneq ($(HOST_ARCH), $(BUILD_ARCH))
|
||||
$(error Cross compiling is not supported for rust code. Specify HAVE_CARGO=0 to disable rust compilation)
|
||||
endif
|
||||
.PHONY: .no-cross-compile
|
||||
|
||||
.detect-openssl.dep.c:
|
||||
echo "#include <openssl/evp.h>" > $@
|
||||
echo "#if OPENSSL_VERSION_NUMBER < 0x10101000L" >> $@
|
||||
echo " #error openssl version 1.1.1 is required" >> $@
|
||||
echo "#endif" >> $@
|
||||
echo "static void __attribute__((unused)) test(void) {" >> $@
|
||||
echo " EVP_MD_CTX *ctx = EVP_MD_CTX_new();" >> $@
|
||||
echo " EVP_MD_CTX_free(ctx);" >> $@
|
||||
echo "}" >> $@
|
||||
|
||||
.check-dep-pvtools: .detect-openssl.dep.c
|
||||
$(call check_dep, \
|
||||
"Rust-pv", \
|
||||
$^, \
|
||||
"openssl-devel / libssl-dev version >= 1.1.1", \
|
||||
"HAVE_OPENSSL=0", \
|
||||
"-I.")
|
||||
$(call check_dep, \
|
||||
"Rust-pv", \
|
||||
"curl/curl.h", \
|
||||
"libcurl-devel", \
|
||||
"HAVE_LIBCURL=0")
|
||||
touch $@
|
||||
144
rust/README.md
Normal file
144
rust/README.md
Normal file
@@ -0,0 +1,144 @@
|
||||
# s390-tools tools written in rust
|
||||
|
||||
## Setting up rust development and build environment
|
||||
Please refer to the official documentation to set up a working rust environment:
|
||||
https://www.rust-lang.org/learn/get-started
|
||||
|
||||
## Building rust code
|
||||
### s390-tools build system
|
||||
If `cargo` is installed a simple `make` should do the job. Note that,
|
||||
compiling rust programs take significantly longer than C code. To closely
|
||||
monitor the progress use `make V=1` By default release builds are made.
|
||||
|
||||
With `make CARGOFLAGS=<flags>` one can pass additional flags to cargo.
|
||||
With `make HAVE_CARGO=0` one can turn of any compilation that requires cargo.
|
||||
With `make CARGO=<...>` one can set the cargo binary
|
||||
|
||||
### cargo
|
||||
If you need to run cargo directly, `cd` to each project you want to build and
|
||||
issue your cargo commands. Do **NOT** forget to specify `--release` if you are
|
||||
building tools for a release. The s390-tools expect the environment variable
|
||||
`S390_TOOLS_RELEASE` to be present at build time. This is the version string the
|
||||
rust tools provide.
|
||||
|
||||
Tip: You can use `make version` to get the version string.
|
||||
|
||||
## Internal Libraries
|
||||
* __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
|
||||
|
||||
* __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
|
||||
disable build that use pv with the request feature.
|
||||
|
||||
|
||||
## Tools
|
||||
* __pvsecret__ _Manage secrets for IBM Secure Execution guests_
|
||||
* requires pv with the `request` feature
|
||||
|
||||
## Writing new tools
|
||||
We encourage to use Rust for new tools. However, for some use cases it makes
|
||||
sense to use C and C is still allowed to be used for a new tool/library.
|
||||
Exiting tools may be rewritten in Rust.
|
||||
|
||||
### What (third-party) crates can be used for s390-tools?
|
||||
A huge list of libraries are made available through Rusts' ecosystem and is one
|
||||
of many upsides. However, just like with Coding Style Guidelines, it is
|
||||
important to limit the usage of those libraries so that within a project,
|
||||
everyone is on the same page and that code written in Rust uses similar
|
||||
approaches. It makes it easier for code review and maintainability in general.
|
||||
|
||||
The following list of crates should cover a wide variety of use cases. This list
|
||||
is a start, but can change over time.
|
||||
|
||||
* [anyhow](https://crates.io/crates/anyhow)
|
||||
* Flexible concrete Error type built on std::error::Error
|
||||
* [byteorder](https://crates.io/crates/byteorder)
|
||||
* Library for reading/writing numbers in big-endian and little-endian.
|
||||
* [cfg-if](https://crates.io/crates/cfg-if)
|
||||
* A macro to ergonomically define an item depending on a large number of
|
||||
#[cfg] parameters. Structured like an if-else chain, the first matching
|
||||
branch is the item that gets emitted.
|
||||
* [clap](https://crates.io/crates/clap)
|
||||
* A simple to use, efficient, and full-featured Command Line Argument Parser
|
||||
* [curl](https://crates.io/crates/curl)
|
||||
* Rust bindings to libcurl for making HTTP requests
|
||||
* [libc](https://crates.io/crates/libc)
|
||||
* Raw FFI bindings to platform libraries like libc.
|
||||
* [log](https://crates.io/crates/log)
|
||||
* A lightweight logging facade for Rust
|
||||
* [openssl](https://crates.io/crates/openssl)
|
||||
* OpenSSL bindings
|
||||
* [serde](https://crates.io/crates/serde)
|
||||
* A generic serialization/deserialization framework
|
||||
* [serde_yaml](https://crates.io/crates/serde_yaml)
|
||||
* YAML data format for Serde
|
||||
* [thiserror](https://crates.io/crates/thiserror)
|
||||
* derive(Error)
|
||||
* [zerocopy](https://crates.io/crates/zerocopy)
|
||||
* Utilities for zero-copy parsing and serialization
|
||||
|
||||
Dependencies used by the crates listed above can be used, too.
|
||||
|
||||
### Add new tool
|
||||
To add a new tool issue `cargo new $TOOLNAME` in the `rust` directory.
|
||||
|
||||
Add the tool to the _s390-tools_ build system:
|
||||
```Makefile
|
||||
CARGO_TARGETS := $TOOLNAME
|
||||
```
|
||||
Add the library to the _s390-tools_ test list:
|
||||
```Makefile
|
||||
CARGO_TEST_TARGETS := $LIBNAME
|
||||
```
|
||||
|
||||
Add the tool/library to the cargo workspace:
|
||||
```toml
|
||||
[workspace]
|
||||
members = [
|
||||
"pv",
|
||||
"pvsecret",
|
||||
"$TOOLNAME",
|
||||
"$LIBNAME"
|
||||
"utils",
|
||||
]
|
||||
```
|
||||
|
||||
### Versions
|
||||
Do not communicate the version defined in the `toml` file by default. Use
|
||||
`release_string` from the `rust/utils` crate instead:
|
||||
|
||||
```rust
|
||||
use utils::release_string;
|
||||
|
||||
fn print_version() {
|
||||
println!(
|
||||
"{} version {}\nCopyright IBM Corp. 2023",
|
||||
env!("CARGO_PKG_NAME"), // collapses into the crates name
|
||||
release_string!() // this (very likely) collapses into a compile time constant
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### Unsafe rust
|
||||
rust allows you to write unsafe rust. Try to avoid it, it can make rust
|
||||
_unsafe_. If you need to, e.g. interacting with other languages like C, keep
|
||||
the `unsafe` block as small as possible and add a reasoning using `// SAFETY:
|
||||
`why this code is safe. Example:
|
||||
|
||||
```rust
|
||||
// Get the raw pointer and do an ioctl.
|
||||
//
|
||||
// SAFETY: the passed pointer points to a valid memory region that
|
||||
// contains the expected C-struct. The struct outlives this function.
|
||||
unsafe {
|
||||
let ptr: *mut ffi::uvio_ioctl_cb = cb as *mut _;
|
||||
rc = ioctl(raw_fd, cmd, ptr);
|
||||
}
|
||||
```
|
||||
|
||||
### Coding style
|
||||
Make `cargo fmt` and `cargo clippy` happy!
|
||||
|
||||
### Testing
|
||||
Prefer writing tests using rustdoc. Use explicit rust tests for more edge case tests.
|
||||
32
rust/pv/Cargo.toml
Normal file
32
rust/pv/Cargo.toml
Normal file
@@ -0,0 +1,32 @@
|
||||
[package]
|
||||
name = "pv"
|
||||
version = "0.9.0"
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
[dependencies]
|
||||
libc = "0.2.49"
|
||||
log = { version = "0.4.6", features = ["std", "release_max_level_debug"] }
|
||||
thiserror = "1.0.33"
|
||||
zerocopy = "0.6"
|
||||
cfg-if = "1.0.0"
|
||||
|
||||
# dependencies for request feature
|
||||
clap = { version ="4", features = ["derive", "wrap_help"], optional = true }
|
||||
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]
|
||||
mockito = {version = "1", default-features = false }
|
||||
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"]
|
||||
12
rust/pv/openssl_extensions/Cargo.toml
Normal file
12
rust/pv/openssl_extensions/Cargo.toml
Normal file
@@ -0,0 +1,12 @@
|
||||
[package]
|
||||
name = "openssl_extensions"
|
||||
version = "0.1.0"
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
[dependencies]
|
||||
foreign-types = "0.3.1"
|
||||
libc = {version = "0.2.49", features = [ "extra_traits"] }
|
||||
log = { version = "0.4.6", features = ["std", "release_max_level_debug"] }
|
||||
openssl = "0.10.49"
|
||||
openssl-sys = "0.9.85"
|
||||
45
rust/pv/openssl_extensions/build.rs
Normal file
45
rust/pv/openssl_extensions/build.rs
Normal file
@@ -0,0 +1,45 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
//
|
||||
// Copyright IBM Corp. 2023
|
||||
|
||||
#![allow(
|
||||
clippy::inconsistent_digit_grouping,
|
||||
clippy::uninlined_format_args,
|
||||
clippy::unusual_byte_groupings
|
||||
)]
|
||||
|
||||
use std::env;
|
||||
|
||||
fn main() {
|
||||
if let Ok(vars) = env::var("DEP_OPENSSL_CONF") {
|
||||
for var in vars.split(',') {
|
||||
println!("cargo:rustc-cfg=osslconf=\"{}\"", var);
|
||||
}
|
||||
}
|
||||
|
||||
if let Ok(version) = env::var("DEP_OPENSSL_VERSION_NUMBER") {
|
||||
let version = u64::from_str_radix(&version, 16).unwrap();
|
||||
|
||||
if version >= 0x1_00_01_00_0 {
|
||||
println!("cargo:rustc-cfg=ossl101");
|
||||
}
|
||||
if version >= 0x1_00_02_00_0 {
|
||||
println!("cargo:rustc-cfg=ossl102");
|
||||
}
|
||||
if version >= 0x1_01_00_00_0 {
|
||||
println!("cargo:rustc-cfg=ossl110");
|
||||
}
|
||||
if version >= 0x1_01_00_07_0 {
|
||||
println!("cargo:rustc-cfg=ossl110g");
|
||||
}
|
||||
if version >= 0x1_01_00_08_0 {
|
||||
println!("cargo:rustc-cfg=ossl110h");
|
||||
}
|
||||
if version >= 0x1_01_01_00_0 {
|
||||
println!("cargo:rustc-cfg=ossl111");
|
||||
}
|
||||
if version >= 0x3_00_00_00_0 {
|
||||
println!("cargo:rustc-cfg=ossl300");
|
||||
}
|
||||
}
|
||||
}
|
||||
120
rust/pv/openssl_extensions/src/akid.rs
Normal file
120
rust/pv/openssl_extensions/src/akid.rs
Normal file
@@ -0,0 +1,120 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
//
|
||||
// Copyright IBM Corp. 2023
|
||||
|
||||
use std::fmt;
|
||||
|
||||
use foreign_types::{foreign_type, ForeignType, ForeignTypeRef};
|
||||
use libc::c_int;
|
||||
use openssl::x509::{X509CrlRef, X509Ref};
|
||||
|
||||
mod ffi {
|
||||
extern "C" {
|
||||
pub fn X509_check_akid(
|
||||
issuer: *const openssl_sys::X509,
|
||||
akid: *const openssl_sys::AUTHORITY_KEYID,
|
||||
) -> ::libc::c_int;
|
||||
}
|
||||
}
|
||||
|
||||
foreign_type! {
|
||||
type CType = openssl_sys::AUTHORITY_KEYID;
|
||||
fn drop = openssl_sys::AUTHORITY_KEYID_free;
|
||||
|
||||
/// An `Authority Key Identifier`.
|
||||
pub struct Akid;
|
||||
/// Reference to `Akid`
|
||||
pub struct AkidRef;
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Eq)]
|
||||
pub struct AkidCheckResult(c_int);
|
||||
|
||||
impl fmt::Debug for AkidCheckResult {
|
||||
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
fmt.debug_struct("AkidCheckResult")
|
||||
.field("code", &self.0)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl AkidCheckResult {
|
||||
/// Creates an `AkidCheckResult` from a raw error number.
|
||||
unsafe fn from_raw(err: c_int) -> AkidCheckResult {
|
||||
AkidCheckResult(err)
|
||||
}
|
||||
|
||||
pub const OK: AkidCheckResult = AkidCheckResult(openssl_sys::X509_V_OK);
|
||||
pub const ERR_AKID_ISSUER_SERIAL_MISMATCH: AkidCheckResult =
|
||||
AkidCheckResult(openssl_sys::X509_V_ERR_AKID_ISSUER_SERIAL_MISMATCH);
|
||||
pub const ERR_AKID_SKID_MISMATCH: AkidCheckResult =
|
||||
AkidCheckResult(openssl_sys::X509_V_ERR_AKID_SKID_MISMATCH);
|
||||
}
|
||||
|
||||
impl AkidRef {
|
||||
///Check if the `Akid` matches the issuer
|
||||
///
|
||||
pub fn check(&self, issuer: &X509Ref) -> AkidCheckResult {
|
||||
unsafe {
|
||||
let res = ffi::X509_check_akid(issuer.as_ptr(), self.as_ptr());
|
||||
AkidCheckResult::from_raw(res)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait AkidExtension {
|
||||
fn akid(&self) -> Option<Akid>;
|
||||
}
|
||||
|
||||
impl AkidExtension for X509Ref {
|
||||
fn akid(&self) -> Option<Akid> {
|
||||
unsafe {
|
||||
let ptr = openssl_sys::X509_get_ext_d2i(
|
||||
self.as_ptr(),
|
||||
openssl_sys::NID_authority_key_identifier,
|
||||
std::ptr::null_mut(),
|
||||
std::ptr::null_mut(),
|
||||
);
|
||||
if ptr.is_null() {
|
||||
None
|
||||
} else {
|
||||
Some(Akid::from_ptr(ptr as *mut _))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AkidExtension for X509CrlRef {
|
||||
fn akid(&self) -> Option<Akid> {
|
||||
unsafe {
|
||||
let ptr = openssl_sys::X509_CRL_get_ext_d2i(
|
||||
self.as_ptr(),
|
||||
openssl_sys::NID_authority_key_identifier,
|
||||
std::ptr::null_mut(),
|
||||
std::ptr::null_mut(),
|
||||
);
|
||||
if ptr.is_null() {
|
||||
None
|
||||
} else {
|
||||
Some(Akid::from_ptr(ptr as *mut _))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::test_utils::load_gen_cert;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn akid() {
|
||||
let cert = load_gen_cert("ibm.crt");
|
||||
let ca = load_gen_cert("root_ca.crt");
|
||||
|
||||
let akid = cert.akid().unwrap();
|
||||
let res = akid.check(&ca);
|
||||
assert_eq!(res, AkidCheckResult::OK);
|
||||
}
|
||||
}
|
||||
128
rust/pv/openssl_extensions/src/crl.rs
Normal file
128
rust/pv/openssl_extensions/src/crl.rs
Normal file
@@ -0,0 +1,128 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
//
|
||||
// Copyright IBM Corp. 2023
|
||||
|
||||
pub use crate::stackable_crl::*;
|
||||
use foreign_types::{ForeignType, ForeignTypeRef};
|
||||
use openssl::{
|
||||
error::ErrorStack,
|
||||
stack::{Stack, StackRef},
|
||||
x509::{
|
||||
store::{X509StoreBuilderRef, X509StoreRef},
|
||||
X509CrlRef, X509NameRef, X509Ref, X509StoreContextRef, X509,
|
||||
},
|
||||
};
|
||||
|
||||
pub fn opt_to_ptr<T: ForeignTypeRef>(o: Option<&T>) -> *mut T::CType {
|
||||
match o {
|
||||
None => std::ptr::null_mut(),
|
||||
Some(p) => p.as_ptr(),
|
||||
}
|
||||
}
|
||||
|
||||
mod ffi {
|
||||
extern "C" {
|
||||
#[cfg(ossl110)]
|
||||
pub fn X509_STORE_CTX_get1_crls(
|
||||
ctx: *mut openssl_sys::X509_STORE_CTX,
|
||||
nm: *mut openssl_sys::X509_NAME,
|
||||
) -> *mut openssl_sys::stack_st_X509_CRL;
|
||||
pub fn X509_STORE_add_crl(
|
||||
xs: *mut openssl_sys::X509_STORE,
|
||||
x: *mut openssl_sys::X509_CRL,
|
||||
) -> libc::c_int;
|
||||
}
|
||||
}
|
||||
|
||||
pub trait X509StoreExtension {
|
||||
fn add_crl(&mut self, crl: &X509CrlRef) -> Result<(), ErrorStack>;
|
||||
}
|
||||
|
||||
impl X509StoreExtension for X509StoreBuilderRef {
|
||||
fn add_crl(&mut self, crl: &X509CrlRef) -> Result<(), ErrorStack> {
|
||||
unsafe {
|
||||
{
|
||||
let r = ffi::X509_STORE_add_crl(self.as_ptr(), crl.as_ptr());
|
||||
if r <= 0 {
|
||||
Err(ErrorStack::get())
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait X509StoreContextExtension {
|
||||
fn init_opt<F, T>(
|
||||
&mut self,
|
||||
trust: &X509StoreRef,
|
||||
cert: Option<&X509Ref>,
|
||||
cert_chain: Option<&StackRef<X509>>,
|
||||
with_context: F,
|
||||
) -> Result<T, ErrorStack>
|
||||
where
|
||||
F: FnOnce(&mut X509StoreContextRef) -> std::result::Result<T, ErrorStack>;
|
||||
fn crls(
|
||||
&mut self,
|
||||
subj: &X509NameRef,
|
||||
) -> std::result::Result<Stack<StackableX509Crl>, ErrorStack>;
|
||||
}
|
||||
|
||||
impl X509StoreContextExtension for X509StoreContextRef {
|
||||
fn init_opt<F, T>(
|
||||
&mut self,
|
||||
trust: &X509StoreRef,
|
||||
cert: Option<&X509Ref>,
|
||||
cert_chain: Option<&StackRef<X509>>,
|
||||
with_context: F,
|
||||
) -> Result<T, ErrorStack>
|
||||
where
|
||||
F: FnOnce(&mut X509StoreContextRef) -> std::result::Result<T, ErrorStack>,
|
||||
{
|
||||
struct Cleanup<'a>(&'a mut X509StoreContextRef);
|
||||
|
||||
impl<'a> Drop for Cleanup<'a> {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
openssl_sys::X509_STORE_CTX_cleanup(self.0.as_ptr());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsafe {
|
||||
{
|
||||
let r = openssl_sys::X509_STORE_CTX_init(
|
||||
self.as_ptr(),
|
||||
trust.as_ptr(),
|
||||
opt_to_ptr(cert),
|
||||
opt_to_ptr(cert_chain),
|
||||
);
|
||||
if r <= 0 {
|
||||
Err(ErrorStack::get())
|
||||
} else {
|
||||
Ok(r)
|
||||
}
|
||||
}?;
|
||||
}
|
||||
let cleanup = Cleanup(self);
|
||||
with_context(cleanup.0)
|
||||
}
|
||||
/// Get all Certificate Revocation Lists with the subject currently stored
|
||||
#[cfg(ossl110)]
|
||||
fn crls(
|
||||
&mut self,
|
||||
subj: &X509NameRef,
|
||||
) -> std::result::Result<Stack<StackableX509Crl>, ErrorStack> {
|
||||
unsafe {
|
||||
{
|
||||
let r = ffi::X509_STORE_CTX_get1_crls(self.as_ptr(), subj.as_ptr());
|
||||
if r.is_null() {
|
||||
Err(ErrorStack::get())
|
||||
} else {
|
||||
Ok(Stack::from_ptr(r))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
26
rust/pv/openssl_extensions/src/lib.rs
Normal file
26
rust/pv/openssl_extensions/src/lib.rs
Normal file
@@ -0,0 +1,26 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
//
|
||||
// Copyright IBM Corp. 2023
|
||||
|
||||
#![doc(hidden)]
|
||||
|
||||
/// Extensions to the rust-openssl crate, that are not upstream yet
|
||||
/// Upstreaming mostly work in progress
|
||||
pub mod akid;
|
||||
pub mod crl;
|
||||
mod stackable_crl;
|
||||
|
||||
/// Test if two CRLs are equal.
|
||||
///
|
||||
/// relates to X509_CRL_match
|
||||
/// (Upstream is missing that functionality)
|
||||
pub fn x509_crl_eq(a: &openssl::x509::X509CrlRef, b: &openssl::x509::X509CrlRef) -> bool {
|
||||
use foreign_types::ForeignTypeRef;
|
||||
let cmp = unsafe { openssl_sys::X509_CRL_match(a.as_ptr(), b.as_ptr()) };
|
||||
cmp == 0
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
mod test_utils {
|
||||
include!("../../src/test_utils.rs");
|
||||
}
|
||||
142
rust/pv/openssl_extensions/src/stackable_crl.rs
Normal file
142
rust/pv/openssl_extensions/src/stackable_crl.rs
Normal file
@@ -0,0 +1,142 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
//
|
||||
// Copyright IBM Corp. 2023
|
||||
|
||||
use std::{marker::PhantomData, ptr};
|
||||
|
||||
use foreign_types::{ForeignType, ForeignTypeRef};
|
||||
use libc::c_int;
|
||||
use openssl::{
|
||||
error::ErrorStack,
|
||||
stack::Stackable,
|
||||
x509::{X509Crl, X509CrlRef},
|
||||
};
|
||||
use openssl_sys::BIO_new_mem_buf;
|
||||
|
||||
pub struct StackableX509Crl(*mut openssl_sys::X509_CRL);
|
||||
|
||||
impl ForeignType for StackableX509Crl {
|
||||
type CType = openssl_sys::X509_CRL;
|
||||
type Ref = X509CrlRef;
|
||||
unsafe fn from_ptr(ptr: *mut openssl_sys::X509_CRL) -> StackableX509Crl {
|
||||
StackableX509Crl(ptr)
|
||||
}
|
||||
fn as_ptr(&self) -> *mut openssl_sys::X509_CRL {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
impl Drop for StackableX509Crl {
|
||||
fn drop(&mut self) {
|
||||
unsafe { (openssl_sys::X509_CRL_free)(self.0) }
|
||||
}
|
||||
}
|
||||
impl ::std::ops::Deref for StackableX509Crl {
|
||||
type Target = X509CrlRef;
|
||||
fn deref(&self) -> &X509CrlRef {
|
||||
unsafe { ForeignTypeRef::from_ptr(self.0) }
|
||||
}
|
||||
}
|
||||
impl ::std::ops::DerefMut for StackableX509Crl {
|
||||
fn deref_mut(&mut self) -> &mut X509CrlRef {
|
||||
unsafe { ForeignTypeRef::from_ptr_mut(self.0) }
|
||||
}
|
||||
}
|
||||
#[allow(clippy::explicit_auto_deref)]
|
||||
impl ::std::borrow::Borrow<X509CrlRef> for StackableX509Crl {
|
||||
fn borrow(&self) -> &X509CrlRef {
|
||||
&**self
|
||||
}
|
||||
}
|
||||
#[allow(clippy::explicit_auto_deref)]
|
||||
impl ::std::convert::AsRef<X509CrlRef> for StackableX509Crl {
|
||||
fn as_ref(&self) -> &X509CrlRef {
|
||||
&**self
|
||||
}
|
||||
}
|
||||
|
||||
impl Stackable for StackableX509Crl {
|
||||
type StackType = openssl_sys::stack_st_X509_CRL;
|
||||
}
|
||||
|
||||
pub struct MemBioSlice<'a>(*mut openssl_sys::BIO, PhantomData<&'a [u8]>);
|
||||
impl<'a> Drop for MemBioSlice<'a> {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
openssl_sys::BIO_free_all(self.0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> MemBioSlice<'a> {
|
||||
pub fn new(buf: &'a [u8]) -> Result<MemBioSlice<'a>, ErrorStack> {
|
||||
openssl_sys::init();
|
||||
|
||||
assert!(buf.len() <= c_int::max_value() as usize);
|
||||
let bio = unsafe {
|
||||
{
|
||||
let r = BIO_new_mem_buf(buf.as_ptr() as *const _, buf.len() as c_int);
|
||||
if r.is_null() {
|
||||
Err(ErrorStack::get())
|
||||
} else {
|
||||
Ok(r)
|
||||
}
|
||||
}?
|
||||
};
|
||||
|
||||
Ok(MemBioSlice(bio, PhantomData))
|
||||
}
|
||||
|
||||
pub fn as_ptr(&self) -> *mut openssl_sys::BIO {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl StackableX509Crl {
|
||||
pub fn stack_from_pem(pem: &[u8]) -> Result<Vec<X509Crl>, ErrorStack> {
|
||||
unsafe {
|
||||
openssl_sys::init();
|
||||
let bio = MemBioSlice::new(pem)?;
|
||||
|
||||
let mut crls = vec![];
|
||||
loop {
|
||||
let r = openssl_sys::PEM_read_bio_X509_CRL(
|
||||
bio.as_ptr(),
|
||||
ptr::null_mut(),
|
||||
None,
|
||||
ptr::null_mut(),
|
||||
);
|
||||
if r.is_null() {
|
||||
let err = openssl_sys::ERR_peek_last_error();
|
||||
if openssl_sys::ERR_GET_LIB(err) as c_int == openssl_sys::ERR_LIB_PEM
|
||||
&& openssl_sys::ERR_GET_REASON(err) == openssl_sys::PEM_R_NO_START_LINE
|
||||
{
|
||||
openssl_sys::ERR_clear_error();
|
||||
break;
|
||||
}
|
||||
|
||||
return Err(ErrorStack::get());
|
||||
} else {
|
||||
crls.push(X509Crl::from_ptr(r));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(crls)
|
||||
}
|
||||
}
|
||||
}
|
||||
impl From<X509Crl> for StackableX509Crl {
|
||||
fn from(value: X509Crl) -> Self {
|
||||
unsafe {
|
||||
openssl_sys::X509_CRL_up_ref(value.as_ptr());
|
||||
StackableX509Crl::from_ptr(value.as_ptr())
|
||||
}
|
||||
}
|
||||
}
|
||||
impl From<StackableX509Crl> for X509Crl {
|
||||
fn from(value: StackableX509Crl) -> Self {
|
||||
unsafe {
|
||||
openssl_sys::X509_CRL_up_ref(value.as_ptr());
|
||||
X509Crl::from_ptr(value.as_ptr())
|
||||
}
|
||||
}
|
||||
}
|
||||
1
rust/pv/openssl_extensions/tests/assets
Symbolic link
1
rust/pv/openssl_extensions/tests/assets
Symbolic link
@@ -0,0 +1 @@
|
||||
../../tests/assets
|
||||
247
rust/pv/src/brcb.rs
Normal file
247
rust/pv/src/brcb.rs
Normal file
@@ -0,0 +1,247 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
//
|
||||
// Copyright IBM Corp. 2023
|
||||
|
||||
use std::{
|
||||
io::{Read, Seek, SeekFrom::Current},
|
||||
mem::size_of,
|
||||
};
|
||||
|
||||
// (SE) boot request control block aka SE header
|
||||
use crate::{
|
||||
assert_size, request::MagicValue, requires_feat, static_assert, Error, Result, PAGESIZE,
|
||||
};
|
||||
use log::debug;
|
||||
use zerocopy::{AsBytes, BigEndian, FromBytes, U32, U64};
|
||||
|
||||
/// Struct containing all SE-header tags.
|
||||
///
|
||||
/// Contains:
|
||||
/// Page List Digest (pld)
|
||||
/// Address List Digest (ald)
|
||||
/// Tweak List Digest (tld)
|
||||
/// SE Header Tag (seht)
|
||||
///
|
||||
#[doc = requires_feat!(request)]
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Clone, Copy, AsBytes, PartialEq, Eq)]
|
||||
pub struct BootHdrTags {
|
||||
pld: [u8; BootHdrHead::DIGEST_SIZE],
|
||||
ald: [u8; BootHdrHead::DIGEST_SIZE],
|
||||
tld: [u8; BootHdrHead::DIGEST_SIZE],
|
||||
seht: [u8; BootHdrHead::SEHT_SIZE],
|
||||
}
|
||||
|
||||
/// Magiv value for a SE-(boot)header
|
||||
pub struct BootHdrMagic;
|
||||
impl MagicValue<8> for BootHdrMagic {
|
||||
const MAGIC: [u8; 8] = [0x49, 0x42, 0x4d, 0x53, 0x65, 0x63, 0x45, 0x78];
|
||||
}
|
||||
|
||||
impl BootHdrTags {
|
||||
/// Returns a reference to the SE-hdr tag of this [`BootHdrTags`].
|
||||
pub fn seht(&self) -> &[u8; 16] {
|
||||
&self.seht
|
||||
}
|
||||
|
||||
/// Creates a new [`BootHdrTags`]. Useful for writing tests.
|
||||
#[doc(hidden)]
|
||||
pub const fn new(pld: [u8; 64], ald: [u8; 64], tld: [u8; 64], seht: [u8; 16]) -> Self {
|
||||
Self {
|
||||
ald,
|
||||
tld,
|
||||
pld,
|
||||
seht,
|
||||
}
|
||||
}
|
||||
|
||||
/// returns false if no hdr found, true otherwise
|
||||
/// in the very unlikel case an IO error can appear
|
||||
/// when seeking to the beginning of the header
|
||||
fn seek_se_hdr_start<R>(img: &mut R) -> Result<bool>
|
||||
where
|
||||
R: Read + Seek,
|
||||
{
|
||||
const MAX_ITER: usize = 0x15;
|
||||
const BUF_SIZE: i64 = 8;
|
||||
static_assert!(BootHdrMagic::MAGIC.len() == BUF_SIZE as usize);
|
||||
|
||||
let mut buf = [0; BUF_SIZE as usize];
|
||||
for _ in [0; MAX_ITER] {
|
||||
match img.read_exact(&mut buf) {
|
||||
Ok(it) => it,
|
||||
Err(_) => return Ok(false),
|
||||
};
|
||||
|
||||
if BootHdrMagic::starts_with_magic(&buf) {
|
||||
// go back to the beginning of the header
|
||||
img.seek(Current(-BUF_SIZE))?;
|
||||
|
||||
return Ok(true);
|
||||
}
|
||||
// goto next page start
|
||||
// or report invalid file format if file ends "early"
|
||||
match img.seek(Current(PAGESIZE as i64 - BUF_SIZE)) {
|
||||
Ok(it) => it,
|
||||
Err(_) => return Ok(false),
|
||||
};
|
||||
}
|
||||
Ok(false)
|
||||
}
|
||||
|
||||
/// Deserializes a (SE) boot header and extracts the tags.
|
||||
///
|
||||
/// Searches for the header; if found extracts the tags.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// This function will return an error if `hdr` is not at least as long as the header specifies
|
||||
/// in bytes 12-15 or the first 8 bytes do not contain the magic value.
|
||||
pub fn from_se_image<R>(img: &mut R) -> Result<Self>
|
||||
where
|
||||
R: Read + Seek,
|
||||
{
|
||||
if !Self::seek_se_hdr_start(img)? {
|
||||
debug!("No boot hdr found");
|
||||
return Err(Error::InvBootHdr);
|
||||
}
|
||||
// read in the header
|
||||
let mut hdr = vec![0u8; size_of::<BootHdrHead>()];
|
||||
img.read_exact(&mut hdr)?;
|
||||
|
||||
let hdr_head = match BootHdrHead::read_from_prefix(hdr.as_mut_slice()) {
|
||||
Some(hdr) => hdr,
|
||||
None => {
|
||||
debug!("Boot hdr is to small");
|
||||
return Err(Error::InvBootHdr);
|
||||
}
|
||||
};
|
||||
|
||||
//Some sanity checks
|
||||
if !BootHdrMagic::starts_with_magic(&hdr) || hdr_head.version.get() != 0x100 {
|
||||
debug!("Inv magic or size");
|
||||
return Err(Error::InvBootHdr);
|
||||
}
|
||||
|
||||
//go to the Bot header tag
|
||||
img.seek(Current(
|
||||
hdr_head.size.get() as i64
|
||||
- size_of::<BootHdrHead>() as i64
|
||||
- BootHdrHead::SEHT_SIZE as i64,
|
||||
))?;
|
||||
|
||||
// read in the tag
|
||||
let mut seht = [0u8; BootHdrHead::SEHT_SIZE];
|
||||
img.read_exact(seht.as_mut_slice())?;
|
||||
|
||||
Ok(BootHdrTags {
|
||||
pld: hdr_head.pld,
|
||||
ald: hdr_head.ald,
|
||||
tld: hdr_head.tld,
|
||||
seht,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Clone, FromBytes)]
|
||||
struct BootHdrHead {
|
||||
magic: U64<BigEndian>,
|
||||
version: U32<BigEndian>,
|
||||
size: U32<BigEndian>,
|
||||
iv: [u8; 12],
|
||||
res1: u32,
|
||||
nks: U64<BigEndian>,
|
||||
sea: U64<BigEndian>,
|
||||
nep: U64<BigEndian>,
|
||||
pcf: U64<BigEndian>,
|
||||
user_pubkey: [u8; 160],
|
||||
pld: [u8; Self::DIGEST_SIZE],
|
||||
ald: [u8; Self::DIGEST_SIZE],
|
||||
tld: [u8; Self::DIGEST_SIZE],
|
||||
}
|
||||
assert_size!(BootHdrHead, 0x1A0);
|
||||
impl BootHdrHead {
|
||||
const DIGEST_SIZE: usize = 0x40;
|
||||
const SEHT_SIZE: usize = 0x10;
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::io::Cursor;
|
||||
|
||||
use super::*;
|
||||
use crate::get_test_asset;
|
||||
use crate::Error;
|
||||
|
||||
const EXP_HDR: BootHdrTags = BootHdrTags {
|
||||
pld: [
|
||||
0xbe, 0x94, 0xb5, 0xea, 0xb3, 0xc1, 0xb1, 0x18, 0xc7, 0x57, 0xd7, 0xdb, 0x7e, 0xa0,
|
||||
0xf6, 0x5d, 0x9b, 0x64, 0x82, 0x3a, 0x8d, 0xc5, 0x5b, 0xf8, 0xa8, 0x72, 0x5b, 0x58,
|
||||
0x07, 0x2d, 0x9d, 0x42, 0x58, 0xc5, 0x3e, 0x8a, 0x5d, 0xa8, 0x2d, 0xfb, 0x21, 0x92,
|
||||
0xd9, 0x1d, 0x07, 0xbc, 0x1c, 0x39, 0xb9, 0x5d, 0x63, 0x21, 0xd3, 0xba, 0x16, 0xa7,
|
||||
0x51, 0xa6, 0xe3, 0xe3, 0x2f, 0x3e, 0x01, 0x61,
|
||||
],
|
||||
ald: [
|
||||
0x28, 0x58, 0xc3, 0x36, 0x8b, 0x2a, 0x0a, 0xf0, 0xc5, 0xea, 0x0f, 0xde, 0x79, 0x05,
|
||||
0xeb, 0x15, 0xaf, 0x9c, 0xd1, 0xdd, 0x73, 0x71, 0x65, 0x93, 0x3c, 0xda, 0xa2, 0xb8,
|
||||
0x50, 0xb6, 0xa8, 0xe2, 0xf0, 0xf4, 0x2c, 0x7b, 0x36, 0xdd, 0x53, 0x81, 0x09, 0x62,
|
||||
0x88, 0xdc, 0x09, 0x2d, 0xaa, 0x8a, 0x6f, 0xac, 0xec, 0x25, 0x34, 0x13, 0x7b, 0xc9,
|
||||
0x4c, 0xa8, 0x0b, 0xda, 0x4f, 0xcb, 0x93, 0x28,
|
||||
],
|
||||
tld: [
|
||||
0x48, 0x60, 0xeb, 0xcf, 0x7b, 0x9d, 0x24, 0xeb, 0x90, 0x9a, 0x79, 0x53, 0x56, 0xad,
|
||||
0x32, 0xc9, 0x36, 0xb6, 0x21, 0x65, 0x98, 0x8a, 0x9f, 0xfc, 0xd6, 0x61, 0x70, 0xdb,
|
||||
0xc5, 0x90, 0xc2, 0x30, 0x10, 0xd7, 0x95, 0x2f, 0xa8, 0x82, 0xd1, 0xbb, 0x79, 0x55,
|
||||
0x8f, 0x9b, 0xe0, 0xa5, 0x49, 0xd8, 0xd7, 0xa9, 0x4a, 0xe7, 0x20, 0xe5, 0xc0, 0x76,
|
||||
0x0a, 0x82, 0x5d, 0x47, 0x9f, 0xe6, 0x7a, 0xf5,
|
||||
],
|
||||
seht: [
|
||||
0x92, 0x30, 0x9d, 0x45, 0x89, 0xb9, 0xa8, 0x5b, 0x42, 0x7f, 0x87, 0x53, 0x17, 0x1d,
|
||||
0x15, 0x20,
|
||||
],
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn from_se_image_hdr() {
|
||||
let bin_hdr = get_test_asset!("exp/secure_guest.hdr");
|
||||
let hdr_tags = BootHdrTags::from_se_image(&mut Cursor::new(*bin_hdr)).unwrap();
|
||||
assert_eq!(hdr_tags, EXP_HDR);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn from_se_image_fail() {
|
||||
let bin_hdr = get_test_asset!("exp/secure_guest.hdr");
|
||||
let short_hdr = &bin_hdr[1..];
|
||||
|
||||
assert!(matches!(
|
||||
BootHdrTags::from_se_image(&mut Cursor::new(short_hdr)),
|
||||
Err(Error::InvBootHdr)
|
||||
));
|
||||
|
||||
// mess up magic
|
||||
let mut bin_hdr_copy = *bin_hdr;
|
||||
bin_hdr_copy.swap(0, 1);
|
||||
assert!(matches!(
|
||||
BootHdrTags::from_se_image(&mut Cursor::new(bin_hdr_copy)),
|
||||
Err(Error::InvBootHdr)
|
||||
));
|
||||
|
||||
//header is at a non expected position
|
||||
let mut img = vec![0u8; PAGESIZE];
|
||||
img[0x008..0x288].copy_from_slice(bin_hdr);
|
||||
assert!(matches!(
|
||||
BootHdrTags::from_se_image(&mut Cursor::new(img)),
|
||||
Err(Error::InvBootHdr)
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn from_se_image_img() {
|
||||
let mut img = vec![0u8; 0x13000];
|
||||
let bin_hdr = get_test_asset!("exp/secure_guest.hdr");
|
||||
img[0x12000..0x12280].copy_from_slice(bin_hdr);
|
||||
let hdr_tags = BootHdrTags::from_se_image(&mut Cursor::new(img)).unwrap();
|
||||
assert_eq!(hdr_tags, EXP_HDR);
|
||||
}
|
||||
}
|
||||
180
rust/pv/src/cli.rs
Normal file
180
rust/pv/src/cli.rs
Normal file
@@ -0,0 +1,180 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
//
|
||||
// Copyright IBM Corp. 2023
|
||||
|
||||
use crate::Result;
|
||||
use crate::{create_buffered_file, open_buffered_file};
|
||||
use clap::{ArgGroup, Args, ValueHint};
|
||||
use std::io::{Read, Write};
|
||||
|
||||
/// CLI Argument collection for handling certificates.
|
||||
///
|
||||
#[doc = requires_feat!(request)]
|
||||
#[derive(Args, Debug, PartialEq, Eq, Default)]
|
||||
#[command(
|
||||
group(ArgGroup::new("pv_verify").required(true).args(["no_verify", "certs"])),
|
||||
)]
|
||||
pub struct CertificateOptions {
|
||||
/// Use FILE as a host-key document.
|
||||
///
|
||||
/// Can be specified multiple times and must be used at least once.
|
||||
#[arg(
|
||||
short = 'k',
|
||||
long = "host-key-document",
|
||||
value_name = "FILE",
|
||||
required = true,
|
||||
value_hint = ValueHint::FilePath,
|
||||
use_value_delimiter = true,
|
||||
value_delimiter = ',',
|
||||
)]
|
||||
pub host_key_documents: Vec<String>,
|
||||
|
||||
/// Disable the host-key document verification.
|
||||
///
|
||||
/// Does not require the host-key documents to be valid.
|
||||
/// Do not use for a production request unless you verified the host-key document before.
|
||||
#[arg(long)]
|
||||
pub no_verify: bool,
|
||||
|
||||
/// Use FILE as a certificate to verify the host-key(s).
|
||||
///
|
||||
/// The certificates are used to establish a chain of trust for the verification
|
||||
/// of the host-key documents. Specify this option twice to specify the IBM Z signing key and
|
||||
/// the intermediate CA certificate (signed by the rootCA).
|
||||
#[arg(
|
||||
short= 'C',
|
||||
long = "cert",
|
||||
value_name = "FILE",
|
||||
alias("crt"),
|
||||
value_hint = ValueHint::FilePath,
|
||||
use_value_delimiter = true,
|
||||
value_delimiter = ',',
|
||||
)]
|
||||
pub certs: Vec<String>,
|
||||
|
||||
/// Use FILE as a certificate revocation list.
|
||||
///
|
||||
/// That list is used to check whether a certificate of the chain of
|
||||
/// trust is revoked. Specify this option multiple times to use multiple CRLs.
|
||||
#[arg(
|
||||
long = "crl",
|
||||
requires("certs"),
|
||||
value_name = "FILE",
|
||||
value_hint = ValueHint::FilePath,
|
||||
use_value_delimiter = true,
|
||||
value_delimiter = ',',
|
||||
)]
|
||||
pub crls: Vec<String>,
|
||||
|
||||
/// Make no attempt to download CRLs.
|
||||
#[arg(long, requires("certs"))]
|
||||
pub offline: bool,
|
||||
|
||||
/// Use FILE as the root-CA certificate for the verification.
|
||||
///
|
||||
/// If omitted, the system wide root CAs installed on the system are used.
|
||||
/// Use this only if you trust the specified certificate.
|
||||
#[arg(long, requires("certs"))]
|
||||
pub root_ca: Option<String>,
|
||||
}
|
||||
|
||||
impl CertificateOptions {
|
||||
/// Returns the verifier of this [`CertificateOptions`] based on the given CLI options.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// This function will return an error if [`crate::request::HkdVerifier`] cannot be created.
|
||||
pub fn verifier(&self) -> Result<Box<dyn crate::verify::HkdVerifier>> {
|
||||
use crate::verify::{CertVerifier, NoVerifyHkd};
|
||||
match self.no_verify {
|
||||
true => {
|
||||
log::warn!(
|
||||
"Host-key document verification is disabled. The secret may not be protected."
|
||||
);
|
||||
Ok(Box::new(NoVerifyHkd))
|
||||
}
|
||||
false => Ok(Box::new(CertVerifier::new(
|
||||
&self.certs,
|
||||
&self.crls,
|
||||
&self.root_ca,
|
||||
self.offline,
|
||||
)?)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// stdout
|
||||
#[cfg(feature = "request")]
|
||||
pub const STDOUT: &str = "-";
|
||||
/// stdin
|
||||
#[cfg(feature = "request")]
|
||||
pub const STDIN: &str = "-";
|
||||
|
||||
/// 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>> {
|
||||
if path == STDOUT {
|
||||
Ok(Box::new(std::io::stdout()))
|
||||
} else {
|
||||
Ok(Box::new(create_buffered_file!(path)))
|
||||
}
|
||||
}
|
||||
|
||||
/// 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>> {
|
||||
if path == STDIN {
|
||||
Ok(Box::new(std::io::stdin()))
|
||||
} else {
|
||||
Ok(Box::new(open_buffered_file!(path)))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use clap::Parser;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
#[rustfmt::skip]
|
||||
fn cli_args() {
|
||||
//Verify only that some arguments are optional, we do not want to test clap, only the
|
||||
//configuration
|
||||
let valid_args = [vec!["pgr", "-k", "hkd.crt", "--no-verify"], vec!["pgr", "-k", "hkd.crt", "--crt", "abc.crt"]];
|
||||
// Test for the minimal amount of flags to yield an invalid combination
|
||||
let invalid_args = [
|
||||
vec!["pgr", "-k", "hkd.crt"],
|
||||
vec!["pgr", "--no-verify", "--crt", "abc.crt"],
|
||||
vec!["pgr", "--no-verify", "--crt", "abc.crt", "--offline"],
|
||||
vec!["pgr", "--no-verify", "--crt", "abc.crt", "--crl", "abc.crl"],
|
||||
vec!["pgr", "--no-verify", "--crt", "abc.crt", "--root-ca", "root.crt"],
|
||||
vec!["pgr", "--offline"],
|
||||
vec!["pgr", "--crl", "abc.crl"],
|
||||
vec!["pgr", "--root-ca", "root.crt"],
|
||||
];
|
||||
#[derive(Parser, Debug)]
|
||||
struct TestParser {
|
||||
#[command(flatten)]
|
||||
pub verify_args: CertificateOptions,
|
||||
}
|
||||
|
||||
for arg in valid_args {
|
||||
let res = TestParser::try_parse_from(&arg);
|
||||
assert!(res.is_ok());
|
||||
}
|
||||
|
||||
for arg in invalid_args {
|
||||
let res = TestParser::try_parse_from(&arg);
|
||||
assert!(res.is_err());
|
||||
}
|
||||
}
|
||||
}
|
||||
325
rust/pv/src/crypto.rs
Normal file
325
rust/pv/src/crypto.rs
Normal file
@@ -0,0 +1,325 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
//
|
||||
// Copyright IBM Corp. 2023
|
||||
|
||||
use crate::requires_feat;
|
||||
use crate::{error::Result, secret::Secret, Error};
|
||||
use openssl::rand::rand_bytes;
|
||||
use openssl::{
|
||||
derive::Deriver,
|
||||
ec::{EcGroup, EcKey},
|
||||
hash::{DigestBytes, MessageDigest},
|
||||
md::MdRef,
|
||||
nid::Nid,
|
||||
pkey::{Id, PKey, Private, Public},
|
||||
pkey_ctx::{HkdfMode, PkeyCtx},
|
||||
symm::{encrypt, encrypt_aead, Cipher},
|
||||
};
|
||||
use std::convert::TryInto;
|
||||
|
||||
/// 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]>;
|
||||
|
||||
/// Types of symmetric keys, to specify during construction.
|
||||
///
|
||||
#[doc = requires_feat!(request)]
|
||||
#[non_exhaustive]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum SymKeyType {
|
||||
/// AES 256 key (32 bytes)
|
||||
Aes256,
|
||||
}
|
||||
|
||||
/// Types of symmetric keys
|
||||
///
|
||||
#[doc = requires_feat!(request)]
|
||||
#[non_exhaustive]
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum SymKey {
|
||||
/// AES 256 key (32 bytes)
|
||||
Aes256(Aes256Key),
|
||||
}
|
||||
|
||||
impl SymKey {
|
||||
/// Generates a random symmetric key.
|
||||
///
|
||||
/// * `key_tp` - type of the symmetric key
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// This function will return an error if the Key cannot be generated.
|
||||
pub fn random(key_tp: SymKeyType) -> Result<Self> {
|
||||
match key_tp {
|
||||
SymKeyType::Aes256 => Ok(Self::Aes256(random_array().map(|v| v.into())?)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a reference to the value of this [`SymKey`].
|
||||
pub fn value(&self) -> &[u8] {
|
||||
match self {
|
||||
Self::Aes256(key) => key.value(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Aes256Key {
|
||||
/// Generates an AES256 key from an digest (hash).
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if `digset` is not 32 bytes long.
|
||||
fn from_digest(digest: DigestBytes) -> Self {
|
||||
let key: [u8; 32] = digest
|
||||
.as_ref()
|
||||
.try_into()
|
||||
.expect("Unexpected OpenSSl Error. Sha256 hash not 32 bytes long");
|
||||
key.into()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Aes256Key> for SymKey {
|
||||
fn from(value: Aes256Key) -> Self {
|
||||
Self::Aes256(value)
|
||||
}
|
||||
}
|
||||
|
||||
/// Performs an hkdf according to RFC 5869.
|
||||
/// See [`OpenSSL HKDF`]()
|
||||
///
|
||||
#[doc = requires_feat!(request)]
|
||||
/// # Errors
|
||||
///
|
||||
/// This function will return an OpenSSL error if the key could not be generated.
|
||||
pub fn hkdf_rfc_5869<const COUNT: usize>(
|
||||
md: &MdRef,
|
||||
ikm: &[u8],
|
||||
salt: &[u8],
|
||||
info: &[u8],
|
||||
) -> Result<[u8; COUNT]> {
|
||||
let mut ctx = PkeyCtx::new_id(Id::HKDF)?;
|
||||
ctx.derive_init()?;
|
||||
ctx.set_hkdf_mode(HkdfMode::EXTRACT_THEN_EXPAND)?;
|
||||
ctx.set_hkdf_md(md)?;
|
||||
ctx.set_hkdf_salt(salt)?;
|
||||
ctx.set_hkdf_key(ikm)?;
|
||||
ctx.add_hkdf_info(info)?;
|
||||
|
||||
let mut res = [0; COUNT];
|
||||
ctx.derive(Some(&mut res))?;
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
/// Derive a symmetric key from a private and a public key.
|
||||
///
|
||||
#[doc = requires_feat!(request)]
|
||||
/// # Errors
|
||||
///
|
||||
/// This function will return an error if something went bad in OpenSSL.
|
||||
pub fn derive_key(k1: &PKey<Private>, k2: &PKey<Public>) -> Result<Aes256Key> {
|
||||
let mut der = Deriver::new(k1)?;
|
||||
der.set_peer(k2)?;
|
||||
let mut key = der.derive_to_vec()?;
|
||||
key.extend([0, 0, 0, 1]);
|
||||
let secr = Secret::new(key);
|
||||
|
||||
Ok(Aes256Key::from_digest(hash(
|
||||
MessageDigest::sha256(),
|
||||
secr.value(),
|
||||
)?))
|
||||
}
|
||||
|
||||
/// Generate a random array.
|
||||
///
|
||||
#[doc = requires_feat!(request)]
|
||||
/// # Errors
|
||||
///
|
||||
/// This function will return an error if the entropy source fails or is not available.
|
||||
pub fn random_array<const COUNT: usize>() -> Result<[u8; COUNT]> {
|
||||
let mut rand = [0; COUNT];
|
||||
rand_bytes(&mut rand)?;
|
||||
Ok(rand)
|
||||
}
|
||||
|
||||
/// Generate a new random EC-SECP521R1 key.
|
||||
///
|
||||
#[doc = requires_feat!(request)]
|
||||
/// # Errors
|
||||
///
|
||||
/// This function will return an error if the key could not be generated by OpenSSL.
|
||||
pub fn gen_ec_key() -> Result<PKey<Private>> {
|
||||
let group = EcGroup::from_curve_name(Nid::SECP521R1)?;
|
||||
let key: EcKey<Private> = EcKey::generate(&group)?;
|
||||
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.
|
||||
///
|
||||
/// * `key` - symmetric key used for encryption
|
||||
/// * `iv` - initialisation vector
|
||||
/// * `aad` - additional authentic data
|
||||
/// * `conf` - data to be encrypted
|
||||
///
|
||||
#[doc = requires_feat!(request)]
|
||||
/// # Returns
|
||||
/// [`Vec<u8>`] with the following content:
|
||||
/// 1. `aad`
|
||||
/// 2. `encr(conf)`
|
||||
/// 3. `aes gcm tag`
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// This function will return an error if the data could not be encrypted by OpenSSL.
|
||||
pub fn encrypt_aes_gcm(key: &SymKey, iv: &[u8], aad: &[u8], conf: &[u8]) -> Result<Vec<u8>> {
|
||||
let mut tag = vec![0xff; 16];
|
||||
let encr = match key {
|
||||
SymKey::Aes256(key) => encrypt_aead(
|
||||
Cipher::aes_256_gcm(),
|
||||
key.value(),
|
||||
Some(iv),
|
||||
aad,
|
||||
conf,
|
||||
&mut tag,
|
||||
)?,
|
||||
};
|
||||
|
||||
let mut res = vec![0; aad.len() + encr.len() + 16];
|
||||
res[0..aad.len()].copy_from_slice(aad);
|
||||
res[aad.len()..aad.len() + encr.len()].copy_from_slice(&encr);
|
||||
res[aad.len() + encr.len()..aad.len() + encr.len() + 16].copy_from_slice(&tag);
|
||||
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
/// Calculate the hash of a slice.
|
||||
///
|
||||
#[doc = requires_feat!(request)]
|
||||
/// # Errors
|
||||
///
|
||||
/// This function will return an error if OpenSSL could not compute the hash.
|
||||
pub fn hash(t: MessageDigest, data: &[u8]) -> Result<DigestBytes> {
|
||||
openssl::hash::hash(t, data).map_err(Error::Crypto)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::test_utils::*;
|
||||
|
||||
#[test]
|
||||
fn derive_key() {
|
||||
let (cust_key, host_key) = get_test_keys();
|
||||
|
||||
let exp_key: Aes256Key = [
|
||||
0x75, 0x32, 0x77, 0x55, 0x8f, 0x3b, 0x60, 0x3, 0x41, 0x9e, 0xf2, 0x49, 0xae, 0x3c,
|
||||
0x4b, 0x55, 0xaa, 0xd7, 0x7d, 0x9, 0xd9, 0x7f, 0xdd, 0x1f, 0xc8, 0x8f, 0xd8, 0xf0,
|
||||
0xcf, 0x22, 0xf1, 0x49,
|
||||
]
|
||||
.into();
|
||||
|
||||
let calc_key = super::derive_key(&cust_key, &host_key).unwrap();
|
||||
|
||||
assert_eq!(&calc_key, &exp_key);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hkdf_rfc_5869() {
|
||||
use openssl::md::Md;
|
||||
// RFC 6869 test vector 1
|
||||
let ikm = [0x0bu8; 22];
|
||||
let salt: [u8; 13] = [
|
||||
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c,
|
||||
];
|
||||
let info: [u8; 10] = [0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9];
|
||||
let exp: [u8; 42] = [
|
||||
0x3c, 0xb2, 0x5f, 0x25, 0xfa, 0xac, 0xd5, 0x7a, 0x90, 0x43, 0x4f, 0x64, 0xd0, 0x36,
|
||||
0x2f, 0x2a, 0x2d, 0x2d, 0x0a, 0x90, 0xcf, 0x1a, 0x5a, 0x4c, 0x5d, 0xb0, 0x2d, 0x56,
|
||||
0xec, 0xc4, 0xc5, 0xbf, 0x34, 0x00, 0x72, 0x08, 0xd5, 0xb8, 0x87, 0x18, 0x58, 0x65,
|
||||
];
|
||||
let res: [u8; 42] = super::hkdf_rfc_5869(Md::sha256(), &ikm, &salt, &info).unwrap();
|
||||
|
||||
assert_eq!(exp, res);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn encrypt_aes_256_gcm() {
|
||||
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_aad = [
|
||||
0x4d, 0x23, 0xc3, 0xce, 0xc3, 0x34, 0xb4, 0x9b, 0xdb, 0x37, 0x0c, 0x43, 0x7f, 0xec,
|
||||
0x78, 0xde,
|
||||
];
|
||||
let aes_gcm_res = vec![
|
||||
0x4d, 0x23, 0xc3, 0xce, 0xc3, 0x34, 0xb4, 0x9b, 0xdb, 0x37, 0x0c, 0x43, 0x7f, 0xec,
|
||||
0x78, 0xde, 0xf7, 0x26, 0x44, 0x13, 0xa8, 0x4c, 0x0e, 0x7c, 0xd5, 0x36, 0x86, 0x7e,
|
||||
0xb9, 0xf2, 0x17, 0x36, 0x67, 0xba, 0x05, 0x10, 0x26, 0x2a, 0xe4, 0x87, 0xd7, 0x37,
|
||||
0xee, 0x62, 0x98, 0xf7, 0x7e, 0x0c,
|
||||
];
|
||||
|
||||
let res = encrypt_aes_gcm(
|
||||
&SymKey::Aes256(aes_gcm_key.into()),
|
||||
&aes_gcm_iv,
|
||||
&aes_gcm_aad,
|
||||
&aes_gcm_plain,
|
||||
)
|
||||
.unwrap();
|
||||
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);
|
||||
}
|
||||
}
|
||||
196
rust/pv/src/error.rs
Normal file
196
rust/pv/src/error.rs
Normal file
@@ -0,0 +1,196 @@
|
||||
// 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("Invalid SE header provided")]
|
||||
#[cfg(feature = "request")]
|
||||
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}")]
|
||||
#[cfg(feature = "request")]
|
||||
HkdVerify(HkdVerifyErrorType),
|
||||
|
||||
#[error("No host-key provided")]
|
||||
#[cfg(feature = "request")]
|
||||
NoHostkey,
|
||||
|
||||
#[error("To many host-keys provided")]
|
||||
#[cfg(feature = "request")]
|
||||
ManyHostkeys,
|
||||
|
||||
#[error("Cannot load {ty} from {path}")]
|
||||
#[cfg(feature = "request")]
|
||||
X509Load {
|
||||
path: String,
|
||||
ty: &'static str,
|
||||
source: openssl::error::ErrorStack,
|
||||
},
|
||||
|
||||
#[error("Internal (unexpected) error: {0}, caused by {1}")]
|
||||
#[cfg(feature = "request")]
|
||||
InternalSsl(&'static str, #[source] openssl::error::ErrorStack),
|
||||
|
||||
#[error("No Config UID found: {0}")]
|
||||
NoCuid(String),
|
||||
// errors from request types
|
||||
#[cfg(feature = "uvsecret")]
|
||||
#[error("Customer Communication Key must be 32 bytes long")]
|
||||
CckSize,
|
||||
|
||||
#[cfg(feature = "uvsecret")]
|
||||
#[error("Cannot encode secrets (Too many secrets)")]
|
||||
ManySecrets,
|
||||
|
||||
#[cfg(feature = "uvsecret")]
|
||||
#[error("Cannot decode secret list")]
|
||||
InvSecretList(#[source] std::io::Error),
|
||||
|
||||
#[cfg(feature = "uvsecret")]
|
||||
#[error("Input does not contain an add-secret request")]
|
||||
NoAsrcb,
|
||||
|
||||
// errors from other crates
|
||||
#[error(transparent)]
|
||||
Io(#[from] std::io::Error),
|
||||
#[error(transparent)]
|
||||
#[cfg(feature = "request")]
|
||||
Crypto(#[from] openssl::error::ErrorStack),
|
||||
#[error(transparent)]
|
||||
ParseInt(#[from] std::num::ParseIntError),
|
||||
#[cfg(feature = "request")]
|
||||
#[error(transparent)]
|
||||
Curl(#[from] curl::Error),
|
||||
}
|
||||
|
||||
// used in macros
|
||||
#[doc(hidden)]
|
||||
impl Error {
|
||||
pub const CRL: &str = "CRL";
|
||||
pub const CERT: &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
|
||||
///
|
||||
#[doc = crate::requires_feat!(request)]
|
||||
#[allow(missing_docs)]
|
||||
#[derive(thiserror::Error, Debug, PartialEq, Eq)]
|
||||
#[non_exhaustive]
|
||||
#[cfg(feature = "request")]
|
||||
pub enum HkdVerifyErrorType {
|
||||
#[error("Signature verification failed")]
|
||||
Signature,
|
||||
#[error("No valid CRL found")]
|
||||
NoCrl,
|
||||
#[error("Host-key document is revoked.")]
|
||||
HdkRevoked,
|
||||
#[error("Not enough bits of security. ({0}, {1} expected)")]
|
||||
SecurityBits(u32, u32),
|
||||
#[error("Authority Key Id mismatch")]
|
||||
Akid,
|
||||
#[error("CRL has no validity period")]
|
||||
NoValidityPeriod,
|
||||
#[error("Specify one IBM Z signing key")]
|
||||
NoIbmSignKey,
|
||||
#[error("Specify only one IBM Z signing key")]
|
||||
ManyIbmSignKeys,
|
||||
#[error("Before validity period")]
|
||||
BeforeValidity,
|
||||
#[error("After validity period")]
|
||||
AfterValidity,
|
||||
#[error("Issuer mismatch")]
|
||||
IssuerMismatch,
|
||||
#[error("No CRL distribution points found")]
|
||||
NoCrlDP,
|
||||
#[error("The IBM Z signing key could not be verified. Error occurred at level {1}")]
|
||||
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 {
|
||||
($var: tt) => {
|
||||
return Err($crate::Error::HkdVerify($crate::HkdVerifyErrorType::$var))
|
||||
};
|
||||
}
|
||||
#[cfg(feature = "request")]
|
||||
pub(crate) use bail_hkd_verify;
|
||||
|
||||
macro_rules! bail_spec {
|
||||
($str: expr) => {
|
||||
return Err($crate::Error::Specification($str.to_string()))
|
||||
};
|
||||
}
|
||||
pub(crate) use bail_spec;
|
||||
206
rust/pv/src/lib.rs
Normal file
206
rust/pv/src/lib.rs
Normal file
@@ -0,0 +1,206 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
//
|
||||
// Copyright IBM Corp. 2023
|
||||
|
||||
#![allow(macro_expanded_macro_exports_accessed_by_absolute_paths)]
|
||||
#![deny(missing_docs)]
|
||||
//! pv - 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` provides abstraction layers for encryption, secure memory management,
|
||||
//! logging, and accessing the uvdevice.
|
||||
//!
|
||||
//! ## Feature Flags
|
||||
//! The following feature flags are available:
|
||||
//! - `request`
|
||||
//! - optional
|
||||
//! - Enables generation of UV requests
|
||||
//! - `uvsecret`
|
||||
//! - optional
|
||||
//! - Enables support for the UV Secret API.
|
||||
mod error;
|
||||
mod log;
|
||||
mod utils;
|
||||
mod uvdevice;
|
||||
|
||||
/// Internal macro to conveninetly document required features on items
|
||||
// #[macro_export]
|
||||
#[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)]
|
||||
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
|
||||
pub mod uv {
|
||||
pub use crate::uvdevice::{
|
||||
uv_ioctl, ConfigUid, UvCmd, UvDevice, UvDeviceInfo, UvFlags, UvcSuccess,
|
||||
};
|
||||
#[cfg(feature = "uvsecret")]
|
||||
pub use crate::uvsecret::{
|
||||
secret_list::SecretList,
|
||||
uvc::{AddCmd, ListCmd, LockCmd},
|
||||
};
|
||||
}
|
||||
|
||||
/// Miscellaneous functions and definitions
|
||||
pub mod misc {
|
||||
|
||||
#[cfg(feature = "request")]
|
||||
pub use crate::cli::{
|
||||
get_reader_from_cli_file_arg, get_writer_from_cli_file_arg, CertificateOptions, STDIN,
|
||||
STDOUT,
|
||||
};
|
||||
pub use crate::log::PvLogger;
|
||||
pub use crate::utils::{
|
||||
memeq, parse_hex, pv_guest_bit_set, read, read_exact_file, read_file, to_u16, to_u32,
|
||||
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 error::{Error, FileAccessErrorType, FileIoErrorType, Result};
|
||||
|
||||
/// Functionalities to build UV requests
|
||||
#[doc = requires_feat!(request)]
|
||||
pub mod request {
|
||||
|
||||
cfg_if::cfg_if! {
|
||||
if #[cfg(feature = "request")] {
|
||||
pub use crate::brcb::{BootHdrTags, BootHdrMagic};
|
||||
pub use crate::crypto::{
|
||||
derive_key, encrypt_aes, encrypt_aes_gcm, gen_ec_key, hash, hkdf_rfc_5869,
|
||||
random_array, Aes256Key, SymKey, SymKeyType,
|
||||
};
|
||||
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! {
|
||||
if #[cfg(feature = "uvsecret")] {
|
||||
/// Functionalities for creating add-secret requests
|
||||
pub mod uvsecret {
|
||||
#[cfg(feature = "request")]
|
||||
pub use crate::uvsecret::{
|
||||
asrcb::{AddSecretFlags, AddSecretRequest, AddSecretVersion,},
|
||||
ext_secret::ExtSecret,
|
||||
guest_secret::GuestSecret,
|
||||
};
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Provides cargo version Info about this crate.
|
||||
///
|
||||
/// Produces `pv-crate <version>`
|
||||
pub const fn crate_info() -> &'static str {
|
||||
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))?,
|
||||
)
|
||||
};
|
||||
}
|
||||
48
rust/pv/src/log.rs
Normal file
48
rust/pv/src/log.rs
Normal file
@@ -0,0 +1,48 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
//
|
||||
// Copyright IBM Corp. 2023
|
||||
|
||||
use log::{self, Level, LevelFilter, Log, Metadata, Record};
|
||||
|
||||
/// A simple Logger that prints to stderr if the verbosity level is high enough.
|
||||
/// Prints log-level for Debug+Trace
|
||||
#[derive(Clone, Default, Debug)]
|
||||
pub struct PvLogger;
|
||||
|
||||
fn to_level(verbosity: u8) -> LevelFilter {
|
||||
match verbosity {
|
||||
// Error and Warn on by default
|
||||
0 => LevelFilter::Warn,
|
||||
1 => LevelFilter::Info,
|
||||
2 => LevelFilter::Debug,
|
||||
_ => LevelFilter::Trace,
|
||||
}
|
||||
}
|
||||
|
||||
impl PvLogger {
|
||||
/// Set self as the logger for this application.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// An error is returned if a logger has already been set.
|
||||
pub fn start(&'static self, verbosity: u8) -> Result<(), log::SetLoggerError> {
|
||||
log::set_logger(self).map(|()| log::set_max_level(to_level(verbosity)))
|
||||
}
|
||||
}
|
||||
|
||||
impl Log for PvLogger {
|
||||
fn enabled(&self, _metadata: &Metadata) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn log(&self, record: &Record) {
|
||||
if self.enabled(record.metadata()) {
|
||||
if record.level() > Level::Info {
|
||||
eprintln!("{}: {}", record.level(), record.args());
|
||||
} else {
|
||||
eprintln!("{}", record.args());
|
||||
}
|
||||
}
|
||||
}
|
||||
fn flush(&self) {}
|
||||
}
|
||||
528
rust/pv/src/req.rs
Normal file
528
rust/pv/src/req.rs
Normal file
@@ -0,0 +1,528 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
//
|
||||
// Copyright IBM Corp. 2023
|
||||
|
||||
use crate::misc::to_u32;
|
||||
use crate::request::{
|
||||
derive_key, encrypt_aes, encrypt_aes_gcm, gen_ec_key, random_array, RequestMagic,
|
||||
RequestVersion, SymKey, SymKeyType,
|
||||
};
|
||||
use crate::{Error, Result};
|
||||
use openssl::bn::{BigNum, BigNumContext};
|
||||
use openssl::ec::{EcGroupRef, EcPointRef};
|
||||
use openssl::error::ErrorStack;
|
||||
use openssl::hash::{hash, MessageDigest};
|
||||
use openssl::pkey::{PKey, PKeyRef, Private, Public};
|
||||
use std::convert::TryInto;
|
||||
use zerocopy::{AsBytes, BigEndian, FromBytes, U32};
|
||||
|
||||
/// Encrypt a _secret_ using self and a given private key.
|
||||
pub trait Encrypt {
|
||||
/// Encrypts `secret` using `self` and `priv_key` the encryption.
|
||||
///
|
||||
/// # Returns
|
||||
/// the encrypted data.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// This function will return an error if OpenSSL could not encrypt the secret.
|
||||
fn encrypt(&self, secret: &[u8], priv_key: &PKey<Private>) -> Result<Vec<u8>> {
|
||||
let mut res = Vec::with_capacity(80);
|
||||
self.encrypt_to(secret, priv_key, &mut res)?;
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
/// Encrypts `secret` using `self` and `priv_key` the encryption.
|
||||
/// Appends the encrypted data to `to`
|
||||
///
|
||||
/// # Returns
|
||||
/// The encrypted data.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// This function will return an error if OpenSSL could not encrypt the secret.
|
||||
fn encrypt_to(&self, secret: &[u8], priv_key: &PKey<Private>, to: &mut Vec<u8>) -> Result<()>;
|
||||
}
|
||||
|
||||
/// Types of Authenticated Data
|
||||
pub enum Aad<'a> {
|
||||
/// Authenticated Keyslot
|
||||
Ks(&'a Keyslot),
|
||||
/// Unchanged authenticated data
|
||||
Plain(&'a [u8]),
|
||||
/// Authenticated data that has to be encrypted in beforehand
|
||||
Encr(&'a dyn Encrypt),
|
||||
}
|
||||
|
||||
/// IBM Z Host key-slot
|
||||
///
|
||||
/// Layout in binary format:
|
||||
/// ```none
|
||||
/// _______________________________________________________________
|
||||
/// | Public Host Key Hash (32) |
|
||||
/// | Wrapped(=Encrypted) Request Protection Key(32) |
|
||||
/// | Key Slot Tag (16) |
|
||||
/// |_____________________________________________________________|
|
||||
/// ```
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Keyslot(PKey<Public>);
|
||||
|
||||
impl Keyslot {
|
||||
/// Size of a host-key hash
|
||||
pub const PHKH_SIZE: u32 = 0x20;
|
||||
|
||||
/// Creates a new Keyslot from the provided public key
|
||||
pub fn new(hostkey: PKey<Public>) -> Self {
|
||||
Self(hostkey)
|
||||
}
|
||||
}
|
||||
|
||||
impl Encrypt for Keyslot {
|
||||
/// Encrypts the given request protection key `prot_key`.
|
||||
///
|
||||
/// The AES256 encryption key is derived from `self` as public key, and `priv_key` as private key.
|
||||
/// # Returns
|
||||
/// The encrypted Keyslot.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// This function will return an error if OpenSSL could not encrypt the secret.
|
||||
fn encrypt_to(
|
||||
&self,
|
||||
prot_key: &[u8],
|
||||
priv_key: &PKey<Private>,
|
||||
to: &mut Vec<u8>,
|
||||
) -> Result<()> {
|
||||
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 phk: EcdhPubkeyCoord = self.0.as_ref().try_into()?;
|
||||
|
||||
to.reserve(80);
|
||||
to.extend_from_slice(&hash(MessageDigest::sha256(), phk.as_ref())?);
|
||||
to.append(&mut wrpk_and_kst);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Context used to mange the encryption of requests.
|
||||
/// Intended to be used by [`Request`] implementations
|
||||
#[derive(Debug)]
|
||||
pub struct ReqEncrCtx {
|
||||
iv: [u8; 12],
|
||||
priv_key: PKey<Private>,
|
||||
prot_key: SymKey,
|
||||
}
|
||||
impl ReqEncrCtx {
|
||||
/// Create a new encryption context that uses AES256.
|
||||
///
|
||||
/// * `iv` - Initialization vector for the request encryption
|
||||
/// * `priv_key` - Private key to wrap [`Keyslot`]
|
||||
/// * `prot_key` - Symmetric key for request encryption. Part of [`Keyslot`]
|
||||
///
|
||||
/// If an argument is set to `None` a ranom is generated
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// This function will return an error if OpenSSL could not generate a random value.
|
||||
pub fn new_aes_256<I, P, S>(iv: I, priv_key: P, prot_key: S) -> Result<Self>
|
||||
where
|
||||
I: Into<Option<[u8; 12]>>,
|
||||
P: Into<Option<PKey<Private>>>,
|
||||
S: Into<Option<SymKey>>,
|
||||
{
|
||||
let iv = iv.into().unwrap_or(random_array()?);
|
||||
let priv_key = priv_key.into().unwrap_or(gen_ec_key()?);
|
||||
let prot_key = prot_key
|
||||
.into()
|
||||
.unwrap_or(SymKey::random(SymKeyType::Aes256)?);
|
||||
Ok(ReqEncrCtx {
|
||||
iv,
|
||||
priv_key,
|
||||
prot_key,
|
||||
})
|
||||
}
|
||||
///
|
||||
/// Create a new encryption context with random input values.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// This function will return an error if OpenSSL could not generate a random value.
|
||||
pub fn random(ket_tp: SymKeyType) -> Result<Self> {
|
||||
match ket_tp {
|
||||
SymKeyType::Aes256 => Self::new_aes_256(None, None, None),
|
||||
}
|
||||
}
|
||||
|
||||
///Panics if data does not fit into bin_aad+offs
|
||||
// #[track_caller]
|
||||
// pub fn copy_to_bin_aad(_bin_aad: &mut [u8], _aad_offs: usize, _data: &[u8]) {
|
||||
// todo!();
|
||||
// }
|
||||
|
||||
/// Build the authenticated data for a request.
|
||||
/// # Returns
|
||||
/// ```none
|
||||
/// _______________________________________________________________
|
||||
/// | MAGIC (8) Version Number (4) Size (4)|
|
||||
/// | IV (12) Reserved (4)|
|
||||
/// | Reserved (7) Num keyslots (1) Reserved(4) Encr Size (4)|
|
||||
/// | --------------------------------------------------- |
|
||||
/// | Request type dependent AAD data |
|
||||
/// |-------------------------------------------------------------|
|
||||
/// ```
|
||||
///
|
||||
pub fn build_aad<O>(
|
||||
&self,
|
||||
version: RequestVersion,
|
||||
aad: &Vec<Aad>,
|
||||
encr_size: usize,
|
||||
magic: O,
|
||||
) -> Result<Vec<u8>>
|
||||
where
|
||||
O: Into<Option<RequestMagic>>,
|
||||
{
|
||||
self.build_aad_impl(version, aad, encr_size, magic.into())
|
||||
}
|
||||
|
||||
/// Concrete implementation for [`ReqEncrCtx::build_aad`].
|
||||
fn build_aad_impl(
|
||||
&self,
|
||||
version: RequestVersion,
|
||||
aad: &Vec<Aad>,
|
||||
encr_size: usize,
|
||||
magic: Option<RequestMagic>,
|
||||
) -> Result<Vec<u8>> {
|
||||
let nks = aad.iter().filter(|a| matches!(a, Aad::Ks(_))).count();
|
||||
let nks: u8 = match nks {
|
||||
0 => Err(Error::NoHostkey),
|
||||
n if n > u8::MAX as usize => Err(Error::ManyHostkeys),
|
||||
n => Ok(n as u8),
|
||||
}?;
|
||||
let mut auth_data: Vec<u8> = Vec::with_capacity(2048);
|
||||
|
||||
//reserve space for the request header
|
||||
auth_data.resize(std::mem::size_of::<RequestHdr>(), 0);
|
||||
|
||||
for a in aad {
|
||||
match a {
|
||||
Aad::Plain(p) => auth_data.extend_from_slice(p),
|
||||
Aad::Ks(ks) => {
|
||||
ks.encrypt_to(self.prot_key.value(), &self.priv_key, &mut auth_data)?
|
||||
}
|
||||
Aad::Encr(e) => {
|
||||
e.encrypt_to(self.prot_key.value(), &self.priv_key, &mut auth_data)?
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let rql = to_u32(auth_data.len() + encr_size + 16)
|
||||
.ok_or_else(|| Error::Specification("Configured request size to large".to_string()))?;
|
||||
let sea = to_u32(encr_size)
|
||||
.ok_or_else(|| Error::Specification("Encrypted size to large".to_string()))?;
|
||||
|
||||
let req_hdr = RequestHdr::new(version, rql, self.iv, nks, sea, magic);
|
||||
// copy request header to the start of the request
|
||||
auth_data[..std::mem::size_of::<RequestHdr>()].copy_from_slice(req_hdr.as_bytes());
|
||||
Ok(auth_data)
|
||||
}
|
||||
|
||||
/// get the public coordinates from the private key (Customer private key)
|
||||
/// # Errors
|
||||
///
|
||||
/// This function will return an error if the public key could not be extracted by OpenSSL.
|
||||
/// Very unlikely.
|
||||
pub fn key_coords(&self) -> Result<EcdhPubkeyCoord> {
|
||||
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.
|
||||
///
|
||||
/// * `aad` - additional authentic data
|
||||
/// * `conf` - data to be encrypted
|
||||
///
|
||||
/// # Returns
|
||||
/// [`Vec<u8>`] with the following content:
|
||||
/// 1. `aad`
|
||||
/// 2. `encr(conf)`
|
||||
/// 3. `aes gcm tag`
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// This function will return an error if the data could not be encrypted by OpenSSL.
|
||||
pub fn encrypt_aead(&self, aad: &[u8], conf: &[u8]) -> Result<Vec<u8>> {
|
||||
encrypt_aes_gcm(&self.prot_key, &self.iv, aad, conf)
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct EcdhPubkeyCoord([u8; 160]);
|
||||
impl AsRef<[u8]> for EcdhPubkeyCoord {
|
||||
fn as_ref(&self) -> &[u8] {
|
||||
self.0.as_slice()
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the pub ecdh coordinates in the format the Ultravisor expects it:
|
||||
/// The two coordinates are pdadded to 80 bytes each.
|
||||
fn get_pub_ecdh_points(pkey: &EcPointRef, grp: &EcGroupRef) -> Result<[u8; 160], ErrorStack> {
|
||||
const ECDH_PUB_KEY_COORD_POINT_SIZE: i32 = 0x50;
|
||||
let mut x = BigNum::new()?;
|
||||
let mut y = BigNum::new()?;
|
||||
let mut bn_ctx = BigNumContext::new()?;
|
||||
pkey.affine_coordinates(grp, &mut x, &mut y, &mut bn_ctx)?;
|
||||
let mut coord: Vec<u8> = x.to_vec_padded(ECDH_PUB_KEY_COORD_POINT_SIZE)?;
|
||||
coord.append(&mut y.to_vec_padded(ECDH_PUB_KEY_COORD_POINT_SIZE)?);
|
||||
Ok(coord.try_into().unwrap())
|
||||
}
|
||||
|
||||
macro_rules! ecdh_from {
|
||||
($type: ty) => {
|
||||
impl TryFrom<&PKeyRef<$type>> for EcdhPubkeyCoord {
|
||||
type Error = ErrorStack;
|
||||
fn try_from(key: &PKeyRef<$type>) -> Result<Self, Self::Error> {
|
||||
let k = key.ec_key()?;
|
||||
k.check_key()?;
|
||||
let grp = k.group();
|
||||
let pub_key = k.public_key();
|
||||
let coord = get_pub_ecdh_points(pub_key, grp)?;
|
||||
Ok(EcdhPubkeyCoord(coord))
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
ecdh_from!(Private);
|
||||
ecdh_from!(Public);
|
||||
|
||||
/// Representation of the shared parts of the request header.
|
||||
/// Used by [`ReqEncrCtx`]
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Copy, Clone, AsBytes, FromBytes)]
|
||||
struct RequestHdr {
|
||||
magic: [u8; 8],
|
||||
rqvn: U32<BigEndian>,
|
||||
rql: U32<BigEndian>,
|
||||
iv: [u8; 12],
|
||||
reserved1c: [u8; 4],
|
||||
reserved20: [u8; 7],
|
||||
nks: u8,
|
||||
reserved28: u32,
|
||||
sea: U32<BigEndian>,
|
||||
}
|
||||
|
||||
impl RequestHdr {
|
||||
fn new(rqvn: u32, rql: u32, iv: [u8; 12], nks: u8, sea: u32, magic: Option<[u8; 8]>) -> Self {
|
||||
Self {
|
||||
magic: magic.unwrap_or_default(),
|
||||
rqvn: rqvn.into(),
|
||||
rql: rql.into(),
|
||||
iv,
|
||||
reserved1c: [0; 4],
|
||||
reserved20: [0; 7],
|
||||
nks,
|
||||
reserved28: 0,
|
||||
sea: sea.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A trait representing a request for the Ultravisor.
|
||||
///
|
||||
/// All requests share a few things:
|
||||
/// * All requests need to be encrypted on a trusted machine
|
||||
/// * All requests have at least one Hostkeyslot
|
||||
///
|
||||
/// The encryption setup is handled by [`ReqEncrCtx`]. Implementers need to pass the data to the
|
||||
/// `ReqEncrCtx` when implementing `encrypt`. A hostkey should be represented by [`Keyslot`] during
|
||||
/// encryption.
|
||||
///
|
||||
/// An UV request consists of an authenticated area (AAD), an encrypted area (Encr) and a 16 byte tag.
|
||||
/// The AAD contains a general header and Request type defined data (including Keyslots).
|
||||
/// It is encrypted with an Request protection key (symmetric). This key is encrypted with a
|
||||
/// (generated) private key and the public key of the host system (Host key)
|
||||
/// ```none
|
||||
/// _______________________________________________________________
|
||||
/// | MAGIC (8) Version Number (4) Size (4)|
|
||||
/// | IV (12) Reserved (4)|
|
||||
/// | Reserved (7) Num keyslots (1) Reserved(4) Encr Size (4)|
|
||||
/// | --------------------------------------------------- |
|
||||
/// | Request type dependent AAD data |
|
||||
/// | ---------------------------------------------------- |
|
||||
/// | Encrypted (request type dependent) data |
|
||||
/// | ---------------------------------------------------- |
|
||||
/// | AES GCM Tag (16) |
|
||||
/// |_____________________________________________________________|
|
||||
///```
|
||||
pub trait Request {
|
||||
/// Encrypt the request into its binary format
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// This function will return an error if the encryption fails, the request does not have at
|
||||
/// least a hostkey, or other implementation dependent contracts are not met.
|
||||
fn encrypt(&self, ctx: &ReqEncrCtx) -> Result<Vec<u8>>;
|
||||
/// Add a host-key to this request
|
||||
///
|
||||
/// Must be called at least once, otherwise {`Request::encrypt`} will fail
|
||||
fn add_hostkey(&mut self, hostkey: PKey<Public>);
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::get_test_asset;
|
||||
use crate::request::SymKey;
|
||||
use crate::test_utils::*;
|
||||
use openssl::ec::EcGroup;
|
||||
use openssl::nid::Nid;
|
||||
|
||||
static TEST_MAGIC: [u8; 8] = 0x12345689abcdef00u64.to_be_bytes();
|
||||
|
||||
#[test]
|
||||
fn encr_build_aad() {
|
||||
let (cust_key, host_key) = get_test_keys();
|
||||
let ks = Keyslot::new(host_key);
|
||||
let ctx = ReqEncrCtx::new_aes_256(
|
||||
Some([0x11; 12]),
|
||||
Some(cust_key),
|
||||
Some(SymKey::Aes256([0x17; 32].into())),
|
||||
)
|
||||
.unwrap();
|
||||
let v = [0x55; 8];
|
||||
let aad = Aad::Plain(&v);
|
||||
let aad = ctx
|
||||
.build_aad(0x200, &vec![aad, Aad::Ks(&ks)], 16, Some(TEST_MAGIC))
|
||||
.unwrap();
|
||||
|
||||
let mut aad_exp = vec![
|
||||
0x12, 0x34, 0x56, 0x89, 0xab, 0xcd, 0xef, 0, //progr
|
||||
0, 0, 2, 0, // vers
|
||||
0, 0, 0, 168, //size
|
||||
0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, // iv
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //res
|
||||
1, //nks
|
||||
0, 0, 0, 0, // res
|
||||
0, 0, 0, 16, // sea
|
||||
0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, //aad
|
||||
];
|
||||
aad_exp.extend_from_slice(get_test_asset!("exp/keyslot.bin"));
|
||||
assert_eq!(&aad, &aad_exp);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn encr_build_aad_nks_no() {
|
||||
let ctx = ReqEncrCtx::new_aes_256(Some([0x11; 12]), None, None).unwrap();
|
||||
|
||||
let aad = Vec::<Aad>::new();
|
||||
|
||||
let aad = ctx.build_aad(0x200, &aad, 16, Some(TEST_MAGIC));
|
||||
assert!(matches!(aad, Err(Error::NoHostkey)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn encr_build_aad_nks_many() {
|
||||
let (_, host_key) = get_test_keys();
|
||||
let ctx = ReqEncrCtx::new_aes_256(Some([0x11; 12]), None, None).unwrap();
|
||||
|
||||
let ks: Vec<Keyslot> = (0..257).map(|_| Keyslot::new(host_key.clone())).collect();
|
||||
let mut aad = Vec::<Aad>::new();
|
||||
ks.iter().for_each(|ks| aad.push(Aad::Ks(ks)));
|
||||
|
||||
let aad = ctx.build_aad(0x200, &aad, 16, Some(TEST_MAGIC));
|
||||
assert!(matches!(aad, Err(Error::ManyHostkeys)));
|
||||
}
|
||||
#[test]
|
||||
fn encr_build_aad_nks() {
|
||||
let (_, host_key) = get_test_keys();
|
||||
let ctx = ReqEncrCtx::new_aes_256(Some([0x11; 12]), None, None).unwrap();
|
||||
|
||||
let ks = vec![
|
||||
Keyslot::new(host_key.clone()),
|
||||
Keyslot::new(host_key.clone()),
|
||||
Keyslot::new(host_key),
|
||||
];
|
||||
let mut aad = Vec::<Aad>::new();
|
||||
ks.iter().for_each(|ks| aad.push(Aad::Ks(ks)));
|
||||
|
||||
let aad = ctx.build_aad(0x200, &aad, 16, Some(TEST_MAGIC)).unwrap();
|
||||
|
||||
assert_eq!(aad.get(39).unwrap(), &3u8);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn req_hdr() {
|
||||
let hdr = RequestHdr::new(0x200, 22, [0x11; 12], 15, 44, None);
|
||||
let hdr_bin = hdr.as_bytes();
|
||||
let hdr_bin_exp = [
|
||||
0u8, 0, 0, 0, 0, 0, 0, 0, //magic
|
||||
0, 0, 2, 0, // vers
|
||||
0, 0, 0, 22, //size
|
||||
0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, // iv
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //res
|
||||
15, //nks
|
||||
0, 0, 0, 0, // res
|
||||
0, 0, 0, 44, // sea
|
||||
];
|
||||
assert_eq!(hdr_bin, &hdr_bin_exp);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn req_hdr2() {
|
||||
let mut hdr = RequestHdr::new(0x200, 0x1234, [0x11; 12], 15, 44, Some(TEST_MAGIC));
|
||||
let hdr_bin = hdr.as_bytes_mut();
|
||||
let hdr_bin_exp = [
|
||||
0x12, 0x34, 0x56, 0x89, 0xab, 0xcd, 0xef, 0, //magic
|
||||
0, 0, 2, 0, // vers
|
||||
0, 0, 0x12, 0x34, //size
|
||||
0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, // iv
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //res
|
||||
15, //nks
|
||||
0, 0, 0, 0, // res
|
||||
0, 0, 0, 44, // sea
|
||||
];
|
||||
assert_eq!(hdr_bin, &hdr_bin_exp);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn keyslot() {
|
||||
let (cust_key, host_key) = get_test_keys();
|
||||
let exp_keyslot = get_test_asset!("exp/keyslot.bin").to_vec();
|
||||
|
||||
let keyslot = Keyslot::new(host_key);
|
||||
let encr_ks = keyslot.encrypt(&[0x17u8; 32], &cust_key).unwrap();
|
||||
|
||||
assert_eq!(exp_keyslot, encr_ks);
|
||||
|
||||
let encr_ks = keyslot.encrypt(&[0x16u8; 32], &cust_key).unwrap();
|
||||
assert_ne!(exp_keyslot, encr_ks);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn get_pub_ecdh_points() {
|
||||
let (cust_key, _) = get_test_keys();
|
||||
|
||||
let pub_key = get_test_asset!("keys/public_cust.bin");
|
||||
|
||||
assert_eq!(pub_key.len(), 160);
|
||||
|
||||
let points = cust_key.ec_key().unwrap();
|
||||
let points = points.public_key();
|
||||
let grp = EcGroup::from_curve_name(Nid::SECP521R1).unwrap();
|
||||
|
||||
let points = super::get_pub_ecdh_points(points, &grp).unwrap();
|
||||
|
||||
assert_eq!(&points, pub_key);
|
||||
}
|
||||
}
|
||||
117
rust/pv/src/secret.rs
Normal file
117
rust/pv/src/secret.rs
Normal file
@@ -0,0 +1,117 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
//
|
||||
// Copyright IBM Corp. 2023
|
||||
|
||||
use std::fmt::Debug;
|
||||
|
||||
/// Trait for securely zeroizing memory.
|
||||
///
|
||||
/// To be used with [`Secret`]
|
||||
pub trait Zeroize {
|
||||
/// Reliably overwrites the given buffer with zeros,
|
||||
fn zeroize(&mut self);
|
||||
}
|
||||
|
||||
/* Automatically impl Zeroize for u8 arrays */
|
||||
impl<const COUNT: usize> Zeroize for [u8; COUNT] {
|
||||
/// Reliably overwrites the given buffer with zeros,
|
||||
/// by performing a volatile write followed by a memory barrier
|
||||
fn zeroize(&mut self) {
|
||||
// SAFETY: given buffer(self) has the correct (compile time) size
|
||||
unsafe { std::ptr::write_volatile(self, [0u8; COUNT]) };
|
||||
std::sync::atomic::compiler_fence(std::sync::atomic::Ordering::SeqCst);
|
||||
}
|
||||
}
|
||||
|
||||
impl Zeroize for Vec<u8> {
|
||||
/// Reliably overwrites the given buffer with zeros,
|
||||
/// by overwriting the whole vector's capacity with zeros.
|
||||
fn zeroize(&mut self) {
|
||||
//TODO use `volatile_set_memory` when stabilized
|
||||
let mut dst = self.as_mut_ptr();
|
||||
for _ in 0..self.capacity() {
|
||||
// SAFETY:
|
||||
// * Vec allocated at least capacity elements continuously
|
||||
// * dst points always to a valid location
|
||||
unsafe {
|
||||
std::ptr::write_volatile(dst, 0);
|
||||
dst = dst.add(1);
|
||||
}
|
||||
}
|
||||
std::sync::atomic::compiler_fence(std::sync::atomic::Ordering::SeqCst);
|
||||
}
|
||||
}
|
||||
|
||||
/// Thin wrapper around an type implementing Zeroize.
|
||||
///
|
||||
/// A `Secret` represents a confidential value that must be securely overwritten during drop.
|
||||
/// Will never leak its wrapped value during [`Debug`]
|
||||
///
|
||||
/// ```rust
|
||||
/// use pv::request::Secret;
|
||||
/// fn foo(value: Secret<[u8; 2]>) {
|
||||
/// println!("value: {value:?}");
|
||||
/// }
|
||||
/// # fn main() {
|
||||
/// foo([1,2].into());
|
||||
/// // prints:
|
||||
/// // in debug builds:
|
||||
/// // value: Secret([1, 2])
|
||||
/// // in release builds:
|
||||
/// // value: Secret(***)
|
||||
/// # }
|
||||
/// ```
|
||||
#[derive(Clone, PartialEq, Eq, Default)]
|
||||
pub struct Secret<C: Zeroize>(C);
|
||||
impl<C: Zeroize> Secret<C> {
|
||||
/// Convert a type into a self overwriting one.
|
||||
///
|
||||
/// Prefer using [`Into`]
|
||||
pub fn new(v: C) -> Self {
|
||||
Secret(v)
|
||||
}
|
||||
|
||||
/// Get a reference to the contained value
|
||||
pub fn value(&self) -> &C {
|
||||
&self.0
|
||||
}
|
||||
/// Get a imutable reference to the contained value
|
||||
///
|
||||
/// NOTE that modifications to a mutable reference can trigger reallocation.
|
||||
/// e.g. a [`Vec`] might expand if more space needed. -> preallocate enough space
|
||||
/// or operate on slices. The old locations can and will **NOT** be zeroized.
|
||||
pub fn value_mut(&mut self) -> &mut C {
|
||||
&mut self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: Zeroize + Debug> Debug for Secret<C> {
|
||||
#[allow(unreachable_code)]
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
// do NOT leak secrets in production builds
|
||||
#[cfg(not(debug_assertions))]
|
||||
return write!(f, "Secret(***)");
|
||||
|
||||
let mut b = f.debug_tuple("Secret");
|
||||
b.field(&self.0);
|
||||
b.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: Zeroize> From<C> for Secret<C> {
|
||||
fn from(v: C) -> Secret<C> {
|
||||
Secret(v)
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: Zeroize> Zeroize for Secret<C> {
|
||||
fn zeroize(&mut self) {
|
||||
self.0.zeroize();
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: Zeroize> Drop for Secret<C> {
|
||||
fn drop(&mut self) {
|
||||
self.0.zeroize();
|
||||
}
|
||||
}
|
||||
120
rust/pv/src/test_utils.rs
Normal file
120
rust/pv/src/test_utils.rs
Normal file
@@ -0,0 +1,120 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
//
|
||||
// Copyright IBM Corp. 2023
|
||||
|
||||
// DO NOT USE ANY OF THESE ITEMS IN PRODUCTION CODE
|
||||
// USED FOR INTERNAL UNIT AND FVT TESTING ONLY!!!
|
||||
use openssl::{
|
||||
bn::BigNum,
|
||||
ec::{EcGroup, EcKey},
|
||||
error::ErrorStack,
|
||||
nid::Nid,
|
||||
pkey::{PKey, Private, Public},
|
||||
x509::{X509Crl, X509},
|
||||
};
|
||||
use std::{
|
||||
fs,
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
|
||||
/// TEST ONLY! Loads the specified asset into the binary at compile time.
|
||||
///
|
||||
/// For testing-assets only!
|
||||
/// The asset must be present at `{crate}/test/assets/{file}`
|
||||
#[doc(hidden)]
|
||||
#[macro_export]
|
||||
macro_rules! get_test_asset {
|
||||
($file:expr) => {
|
||||
include_bytes!(concat!(env!("CARGO_MANIFEST_DIR"), "/tests/assets/", $file))
|
||||
};
|
||||
}
|
||||
|
||||
pub fn get_cert_asset_path<P: AsRef<Path>>(path: P) -> PathBuf {
|
||||
let mut p = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"));
|
||||
p.push("tests");
|
||||
p.push("assets");
|
||||
p.push("cert");
|
||||
p.push(path);
|
||||
println!("CERT path: {}", p.to_str().unwrap());
|
||||
p
|
||||
}
|
||||
|
||||
pub fn get_cert_asset_path_string(path: &'static str) -> String {
|
||||
get_cert_asset_path(path)
|
||||
.into_os_string()
|
||||
.into_string()
|
||||
.unwrap()
|
||||
}
|
||||
/// TEST ONLY! Load an cert
|
||||
///
|
||||
/// panic on errors
|
||||
pub fn get_cert_asset(path: &'static str) -> Vec<u8> {
|
||||
let p = get_cert_asset_path(path);
|
||||
fs::read(p).unwrap()
|
||||
}
|
||||
|
||||
/// TEST ONLY! Load cert found in the asset path
|
||||
///
|
||||
/// panic on errors
|
||||
pub fn load_gen_cert(asset_path: &'static str) -> X509 {
|
||||
let buf = get_cert_asset(asset_path);
|
||||
let mut cert = X509::from_der(&buf)
|
||||
.map(|crt| vec![crt])
|
||||
.or_else(|_| X509::stack_from_pem(&buf))
|
||||
.unwrap();
|
||||
assert_eq!(cert.len(), 1);
|
||||
cert.pop().unwrap()
|
||||
}
|
||||
|
||||
/// TEST ONLY! Load the crl found in the asset path
|
||||
///
|
||||
/// panic on errors
|
||||
pub fn load_gen_crl(asset_path: &'static str) -> X509Crl {
|
||||
let buf = get_cert_asset(asset_path);
|
||||
|
||||
X509Crl::from_der(&buf)
|
||||
.or_else(|_| X509Crl::from_pem(&buf))
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
/// TEST ONLY! Get a fixed private/public pair and a fixed public key
|
||||
///
|
||||
/// Intened for TESTING only. All parts of the key including the private key are checked in git and
|
||||
/// visible for the public
|
||||
pub fn get_test_keys() -> (PKey<Private>, PKey<Public>) {
|
||||
let pub_key = get_test_asset!("keys/public_cust.bin");
|
||||
let priv_key = get_test_asset!("keys/private_cust.bin");
|
||||
let host_key = get_test_asset!("keys/host.pem.crt");
|
||||
|
||||
assert_eq!(pub_key.len(), 160);
|
||||
assert_eq!(priv_key.len(), 80);
|
||||
|
||||
let cust_key = get_keypair(pub_key, priv_key).unwrap();
|
||||
let host_key = X509::from_pem(host_key).unwrap().public_key().unwrap();
|
||||
|
||||
(cust_key, host_key)
|
||||
}
|
||||
|
||||
fn read_ecdh_pubkey(coords: &[u8]) -> Result<PKey<Public>, ErrorStack> {
|
||||
assert!(coords.len() == 160);
|
||||
let x = BigNum::from_slice(&coords[..80])?;
|
||||
let y = BigNum::from_slice(&coords[80..])?;
|
||||
let group = EcGroup::from_curve_name(Nid::SECP521R1)?;
|
||||
|
||||
let key = EcKey::from_public_key_affine_coordinates(&group, &x, &y)?;
|
||||
PKey::from_ec_key(key)
|
||||
}
|
||||
|
||||
fn get_keypair(pub_coords: &[u8], priv_num: &[u8]) -> Result<PKey<Private>, ErrorStack> {
|
||||
assert!(pub_coords.len() == 160);
|
||||
assert!(priv_num.len() == 80);
|
||||
let pub_key = read_ecdh_pubkey(pub_coords)?;
|
||||
let pub_key = pub_key.ec_key()?;
|
||||
let pub_key = pub_key.public_key();
|
||||
let priv_key = BigNum::from_slice(priv_num)?;
|
||||
let group = EcGroup::from_curve_name(Nid::SECP521R1)?;
|
||||
|
||||
let key = EcKey::from_private_components(&group, &priv_key, pub_key)?;
|
||||
key.check_key()?;
|
||||
PKey::from_ec_key(key)
|
||||
}
|
||||
658
rust/pv/src/utils.rs
Normal file
658
rust/pv/src/utils.rs
Normal file
@@ -0,0 +1,658 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
//
|
||||
// Copyright IBM Corp. 2023
|
||||
|
||||
use crate::{
|
||||
error::{bail_spec, file_error, path_to_str},
|
||||
Error, FileIoErrorType, Result,
|
||||
};
|
||||
|
||||
#[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.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// 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_crls(buf: &[u8]) -> Result<Vec<X509Crl>> {
|
||||
use openssl_extensions::crl::StackableX509Crl;
|
||||
X509Crl::from_der(buf)
|
||||
.map(|crl| vec![crl])
|
||||
.or_else(|_| StackableX509Crl::stack_from_pem(buf))
|
||||
.map_err(Error::Crypto)
|
||||
}
|
||||
|
||||
/// Read all certificates from the buffer and parse them into a vector.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// 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>> {
|
||||
X509::from_der(buf)
|
||||
.map(|crt| vec![crt])
|
||||
.or_else(|_| X509::stack_from_pem(buf))
|
||||
.map_err(Error::Crypto)
|
||||
}
|
||||
|
||||
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}
|
||||
|
||||
/// Test if both slices contain the exact same bytes.
|
||||
///
|
||||
/// Do not use this to compare cryptographic values (i.e. hashes)
|
||||
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
|
||||
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 std::usize;
|
||||
|
||||
use super::*;
|
||||
#[cfg(feature = "request")]
|
||||
use crate::test_utils::*;
|
||||
|
||||
#[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() {
|
||||
let crl = get_cert_asset("ibm.crl");
|
||||
let crl_der = get_cert_asset("der.crl");
|
||||
let fail = get_cert_asset("ibm.crt");
|
||||
assert_eq!(super::read_crls(&crl).unwrap().len(), 1);
|
||||
assert_eq!(super::read_crls(&crl_der).unwrap().len(), 1);
|
||||
assert_eq!(super::read_crls(&fail).unwrap().len(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "request")]
|
||||
fn read_certs() {
|
||||
let crt = get_cert_asset("ibm.crt");
|
||||
let crt_der = get_cert_asset("der.crt");
|
||||
let fail = get_cert_asset("ibm.crl");
|
||||
assert_eq!(super::read_certs(&crt).unwrap().len(), 1);
|
||||
assert_eq!(super::read_certs(&crt_der).unwrap().len(), 1);
|
||||
assert_eq!(super::read_certs(&fail).unwrap().len(), 0);
|
||||
}
|
||||
|
||||
#[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()
|
||||
);
|
||||
}
|
||||
|
||||
#[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, &[]));
|
||||
}
|
||||
}
|
||||
208
rust/pv/src/uvdevice.rs
Normal file
208
rust/pv/src/uvdevice.rs
Normal file
@@ -0,0 +1,208 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
//
|
||||
// Copyright IBM Corp. 2023
|
||||
|
||||
#![allow(non_camel_case_types)]
|
||||
use crate::file_acc_error;
|
||||
use crate::{Error, Result};
|
||||
use libc::c_ulong;
|
||||
use log::debug;
|
||||
use std::convert::TryInto;
|
||||
use std::fs::File;
|
||||
use std::os::unix::prelude::{AsRawFd, RawFd};
|
||||
|
||||
#[cfg(not(test))]
|
||||
use ::libc::ioctl;
|
||||
#[cfg(test)]
|
||||
use test::mock_libc::ioctl;
|
||||
|
||||
/// Contains the rust representation of asm/uvdevice.h
|
||||
/// from kernel version: 6.5 verify
|
||||
mod ffi;
|
||||
mod info;
|
||||
mod test;
|
||||
pub use ffi::uv_ioctl;
|
||||
|
||||
pub use info::UvDeviceInfo;
|
||||
#[allow(dead_code)] //TODO rm when pv learns attestation
|
||||
pub type AttestationUserData = [u8; ffi::UVIO_ATT_USER_DATA_LEN];
|
||||
|
||||
///Configuration Unique Id of the Secure Execution guest
|
||||
pub type ConfigUid = [u8; ffi::UVIO_ATT_UID_LEN];
|
||||
|
||||
/// Bitflags as used by the Ultravisor in MSB0 ordering
|
||||
///
|
||||
/// Wraps an u64 to set/get individual bits
|
||||
pub type UvFlags = crate::misc::Msb0Flags64;
|
||||
|
||||
/// Fire an ioctl.
|
||||
///
|
||||
/// # Safety:
|
||||
/// Raw fd must point to an open file
|
||||
fn ioctl_raw(raw_fd: RawFd, cmd: c_ulong, cb: &mut IoctlCb) -> Result<()> {
|
||||
debug!("calling unsafe fn wrapper uv::ioctl_raw with {raw_fd:#x?}, {cmd:#x?}, {cb:?}");
|
||||
|
||||
let rc;
|
||||
|
||||
// Get the raw pointer and do an ioctl.
|
||||
//
|
||||
// SAFETY: the passed pointer points to a valid memory region that
|
||||
// contains the expected C-struct. The struct outlives this function.
|
||||
unsafe {
|
||||
rc = ioctl(raw_fd, cmd, cb.as_ptr_mut());
|
||||
}
|
||||
|
||||
debug!("ioctl resulted with {cb:?}");
|
||||
match rc {
|
||||
0 => Ok(()),
|
||||
//NOTE io::Error handles all errnos ioctl uses
|
||||
_ => Err(std::io::Error::last_os_error().into()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Converts UV return codes into human readable error messages
|
||||
fn rc_fmt<C: UvCmd>(rc: u16, rrc: u16, cmd: &mut C) -> &'static str {
|
||||
let s = match (rc, rrc) {
|
||||
(0x0000, _) => Some("invalid rc"),
|
||||
(0x0002, _) => Some("invalid UV command"),
|
||||
(0x0005, _) => Some("request has an invalid size"),
|
||||
(0x0030, _) => Some("home address space control bit has R-bit set to one"),
|
||||
(0x0031, _) => Some("access exception"),
|
||||
(0x0032, _) => Some("request contains virtual address translating to an invalid address"),
|
||||
(UvDevice::RC_MORE_DATA, _) => unreachable!("This is no Error!!!!"),
|
||||
(UvDevice::RC_SUCCESS, _) => unreachable!("This is no Error!!!!"),
|
||||
|
||||
_ => cmd.rc_fmt(rc, rrc),
|
||||
};
|
||||
s.unwrap_or("unexpected error-code")
|
||||
}
|
||||
|
||||
/// Ultravisor Command.
|
||||
pub trait UvCmd {
|
||||
/// Returns the uvdevice IOCTL command that his command uses.
|
||||
///
|
||||
/// # Returns
|
||||
/// The IOCTL cmd for this UvCmd usually sth like `uv_ioctl!(CMD_NR)`
|
||||
fn cmd(&self) -> u64;
|
||||
/// Converts UV return codes into human readable error messages
|
||||
///
|
||||
/// no need to handle `0x0000, 0x0001, 0x0002, 0x0005, 0x0030, 0x0031, 0x0032, 0x0100`
|
||||
fn rc_fmt(&self, rc: u16, rrc: u16) -> Option<&'static str>;
|
||||
/// Returns data used by this command if available.
|
||||
fn data(&mut self) -> Option<&mut [u8]> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// [`UvDevice`] IOCTL control block.
|
||||
#[derive(Debug)]
|
||||
struct IoctlCb(ffi::uvio_ioctl_cb);
|
||||
impl IoctlCb {
|
||||
fn new(data: Option<&mut [u8]>) -> Result<Self> {
|
||||
let (data_raw, data_size) = match data {
|
||||
Some(data) => (
|
||||
data.as_mut_ptr(),
|
||||
data.len()
|
||||
.try_into()
|
||||
.map_err(|_| Error::Specification("passed data too large".to_string()))?,
|
||||
),
|
||||
None => (std::ptr::null_mut(), 0),
|
||||
};
|
||||
|
||||
Ok(Self(ffi::uvio_ioctl_cb {
|
||||
flags: 0,
|
||||
uv_rc: 0,
|
||||
uv_rrc: 0,
|
||||
argument_addr: data_raw as u64,
|
||||
argument_len: data_size,
|
||||
reserved14: [0; 44],
|
||||
}))
|
||||
}
|
||||
|
||||
fn rc(&self) -> u16 {
|
||||
self.0.uv_rc
|
||||
}
|
||||
|
||||
fn rrc(&self) -> u16 {
|
||||
self.0.uv_rrc
|
||||
}
|
||||
|
||||
fn as_ptr_mut(&mut self) -> *mut ffi::uvio_ioctl_cb {
|
||||
&mut self.0 as *mut _
|
||||
}
|
||||
}
|
||||
|
||||
/// The Ultravisor has two codes that represent a successful execution.
|
||||
/// These are represented by this enum.
|
||||
#[repr(u16)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum UvcSuccess {
|
||||
/// Command executed successfully
|
||||
RC_SUCCESS = UvDevice::RC_SUCCESS,
|
||||
/// Command executed successfully, but there is more data available and the buffer was to small
|
||||
/// to hold it all. The returned data is still valid.
|
||||
RC_MORE_DATA = UvDevice::RC_MORE_DATA,
|
||||
}
|
||||
|
||||
/// The UvDevice is a (virtual) device on s390 machines to send Ultravisor commands from userspace.
|
||||
pub struct UvDevice(File);
|
||||
|
||||
impl UvDevice {
|
||||
const RC_SUCCESS: u16 = 0x0001;
|
||||
const RC_MORE_DATA: u16 = 0x0100;
|
||||
const PATH: &'static str = "/dev/uv";
|
||||
|
||||
/// IOCTL number for the info UVC
|
||||
pub const INFO_NR: u8 = ffi::UVIO_IOCTL_UVDEV_INFO_NR;
|
||||
/// IOCTL number for the attestation UVC
|
||||
pub const ATTESTATION_NR: u8 = ffi::UVIO_IOCTL_ATT_NR;
|
||||
/// IOCTL number for the add secret UVC
|
||||
pub const ADD_SECRET_NR: u8 = ffi::UVIO_IOCTL_ADD_SECRET_NR;
|
||||
/// IOCTL number for the list secret UVC
|
||||
pub const LIST_SECRET_NR: u8 = ffi::UVIO_IOCTL_LIST_SECRETS_NR;
|
||||
/// IOCTL number for the lock ksecret UVC
|
||||
pub const LOCK_SECRET_NR: u8 = ffi::UVIO_IOCTL_LOCK_SECRETS_NR;
|
||||
/// Maximum length for add-secret requests
|
||||
pub const ADD_SECRET_MAX_LEN: usize = ffi::UVIO_ADD_SECRET_MAX_LEN;
|
||||
/// Size of the buffer for list secret requests
|
||||
pub const LIST_SECRETS_LEN: usize = ffi::UVIO_LIST_SECRETS_LEN;
|
||||
|
||||
/// Open the uvdevice located at `/dev/uv`
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// This function will return an error if the device file cannot be opened.
|
||||
pub fn open() -> Result<Self> {
|
||||
Ok(Self(
|
||||
std::fs::OpenOptions::new()
|
||||
.read(true)
|
||||
.write(true)
|
||||
.open(UvDevice::PATH)
|
||||
.map_err(|e| file_acc_error!(Open, UvDevice::PATH, e))?,
|
||||
))
|
||||
}
|
||||
|
||||
/// Send an Ultravisor Command via this uvdevice.
|
||||
///
|
||||
/// This works by sending an IOCTL to the uvdevice.
|
||||
/// # Errors
|
||||
///
|
||||
/// This function will return an error if the IOCTL fails or the Ultravisor does not report
|
||||
/// a success.
|
||||
/// # Returns
|
||||
/// [`UvcSuccess`] if the UVC ececuted successfully
|
||||
pub fn send_cmd<C: UvCmd>(&self, cmd: &mut C) -> Result<UvcSuccess> {
|
||||
let mut cb = IoctlCb::new(cmd.data())?;
|
||||
ioctl_raw(self.0.as_raw_fd(), cmd.cmd(), &mut cb)?;
|
||||
|
||||
match (cb.rc(), cb.rrc()) {
|
||||
(Self::RC_SUCCESS, _) => Ok(UvcSuccess::RC_SUCCESS),
|
||||
(Self::RC_MORE_DATA, _) => Ok(UvcSuccess::RC_MORE_DATA),
|
||||
(rc, rrc) => Err(Error::Uv {
|
||||
rc,
|
||||
rrc,
|
||||
msg: rc_fmt(rc, rrc, cmd),
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
128
rust/pv/src/uvdevice/ffi.rs
Normal file
128
rust/pv/src/uvdevice/ffi.rs
Normal file
@@ -0,0 +1,128 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
//
|
||||
// Copyright IBM Corp. 2023
|
||||
|
||||
use crate::{assert_size, static_assert};
|
||||
use zerocopy::{AsBytes, FromBytes};
|
||||
|
||||
pub const UVIO_ATT_ARCB_MAX_LEN: usize = 0x100000;
|
||||
pub const UVIO_ATT_MEASUREMENT_MAX_LEN: usize = 0x8000;
|
||||
pub const UVIO_ATT_ADDITIONAL_MAX_LEN: usize = 0x8000;
|
||||
pub const UVIO_ADD_SECRET_MAX_LEN: usize = 0x100000;
|
||||
pub const UVIO_LIST_SECRETS_LEN: usize = 0x1000;
|
||||
|
||||
// equal to ascii 'u'
|
||||
pub const UVIO_TYPE_UVC: u8 = 117u8;
|
||||
|
||||
pub const UVIO_IOCTL_UVDEV_INFO_NR: u8 = 0;
|
||||
pub const UVIO_IOCTL_ATT_NR: u8 = 1;
|
||||
pub const UVIO_IOCTL_ADD_SECRET_NR: u8 = 2;
|
||||
pub const UVIO_IOCTL_LIST_SECRETS_NR: u8 = 3;
|
||||
pub const UVIO_IOCTL_LOCK_SECRETS_NR: u8 = 4;
|
||||
|
||||
/// Uvdevice IOCTL control block
|
||||
/// Programs can use this struct to communicate with the uvdevice via IOCTLs
|
||||
/// `argument_{addr,len}` specifies in/out data depending on the request
|
||||
///
|
||||
/// 'uv_rc' and `uv_rrc` are the response and reason response codes from the
|
||||
/// Ultravisor.
|
||||
///
|
||||
/// `flags` is currently unused and to be set zero
|
||||
///
|
||||
#[repr(C)]
|
||||
#[derive(Debug)]
|
||||
pub struct uvio_ioctl_cb {
|
||||
pub flags: u32,
|
||||
pub uv_rc: u16,
|
||||
pub uv_rrc: u16,
|
||||
pub argument_addr: u64,
|
||||
pub argument_len: u32,
|
||||
pub reserved14: [u8; 44usize],
|
||||
}
|
||||
assert_size!(uvio_ioctl_cb, 0x40);
|
||||
|
||||
/// Information of supported functions by the uvdevice
|
||||
///
|
||||
/// * `supp_uvio_cmds` - supported IOCTLs by this device
|
||||
/// * `supp_uv_cmds` - supported UVCs corresponding to the IOCTL
|
||||
///
|
||||
/// UVIO request to get information about supported request types by this
|
||||
/// uvdevice and the Ultravisor.
|
||||
/// Everything is output. Bits are in LSB0 ordering.
|
||||
/// If the bit is set in both, `supp_uvio_cmds` and `supp_uv_cmds`,
|
||||
/// the uvdevice and the Ultravisor support that call.
|
||||
///
|
||||
/// Note that bit 0 (UVIO_IOCTL_UVDEV_INFO_NR) is always zero for `supp_uv_cmds`
|
||||
/// as there is no corresponding UV-call.
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Copy, Clone, AsBytes, FromBytes)]
|
||||
pub struct uvio_uvdev_info {
|
||||
pub supp_uvio_cmds: u64,
|
||||
pub supp_uv_cmds: u64,
|
||||
}
|
||||
assert_size!(uvio_uvdev_info, 0x10);
|
||||
|
||||
pub const UVIO_ATT_USER_DATA_LEN: usize = 0x100;
|
||||
pub const UVIO_ATT_UID_LEN: usize = 0x10;
|
||||
|
||||
/// Request Attestation Measurement control block
|
||||
///
|
||||
/// The Attestation Request has two input and two outputs.
|
||||
/// ARCB and User Data are inputs for the UV.
|
||||
/// Measurement and Additional Data are outputs generated by UV.
|
||||
///
|
||||
/// The Attestation Request Control Block (ARCB) is a cryptographically verified
|
||||
/// and secured request to UV and User Data is some plaintext data which is
|
||||
/// going to be included in the Attestation Measurement calculation.
|
||||
///
|
||||
/// Measurement is a cryptographic measurement of the callers properties,
|
||||
/// optional data configured by the ARCB and the user data. If specified by the
|
||||
/// ARCB, UV will add some Additional Data to the measurement calculation.
|
||||
/// This Additional Data is then returned as well.
|
||||
///
|
||||
/// If the Retrieve Attestation Measurement UV facility is not present,
|
||||
/// UV will return invalid command rc.
|
||||
/// Obviously all numbers are in BIG-endian!
|
||||
#[repr(C)]
|
||||
#[derive(Debug, AsBytes, FromBytes)]
|
||||
pub struct uvio_attest {
|
||||
pub arcb_addr: u64, //in
|
||||
pub meas_addr: u64, //out
|
||||
pub add_data_addr: u64, //out
|
||||
pub user_data: [u8; UVIO_ATT_USER_DATA_LEN], //in
|
||||
pub config_uid: [u8; UVIO_ATT_UID_LEN], //out
|
||||
pub arcb_len: u32,
|
||||
pub meas_len: u32,
|
||||
pub add_data_len: u32,
|
||||
pub user_data_len: u16,
|
||||
pub reserved136: u16,
|
||||
}
|
||||
assert_size!(uvio_attest, 0x138);
|
||||
|
||||
#[allow(dead_code)] //TODO rm when pv learns attestation
|
||||
impl uvio_attest {
|
||||
pub const ARCB_MAX_LEN: usize = UVIO_ATT_ARCB_MAX_LEN;
|
||||
pub const MEASUREMENT_MAX_LEN: usize = UVIO_ATT_MEASUREMENT_MAX_LEN;
|
||||
pub const ADDITIONAL_MAX_LEN: usize = UVIO_ATT_ADDITIONAL_MAX_LEN;
|
||||
}
|
||||
|
||||
/// corresponds to the UV_IOCTL macro
|
||||
pub const fn uv_ioctl(nr: u8) -> u64 {
|
||||
iowr(UVIO_TYPE_UVC, nr, std::mem::size_of::<uvio_ioctl_cb>())
|
||||
}
|
||||
static_assert!(uv_ioctl(UVIO_IOCTL_ATT_NR) == 0xc0407501);
|
||||
|
||||
/// corresponds to the __IOWR macro
|
||||
const fn iowr(ty: u8, nr: u8, size: usize) -> u64 {
|
||||
// constants and calculation from linux: asm-generic/ioctl.h
|
||||
const _IOC_WRITE: u32 = 1;
|
||||
const _IOC_READ: u32 = 2;
|
||||
const _IOC_NRSHIFT: u32 = 0;
|
||||
const _IOC_TYPESHIFT: u32 = 8;
|
||||
const _IOC_SIZESHIFT: u32 = 16;
|
||||
const _IOC_DIRSHIFT: u32 = 30;
|
||||
((_IOC_READ | _IOC_WRITE) as u64) << _IOC_DIRSHIFT
|
||||
| ((ty as u64) << _IOC_TYPESHIFT)
|
||||
| ((nr as u64) << _IOC_NRSHIFT)
|
||||
| ((size as u64) << _IOC_SIZESHIFT)
|
||||
}
|
||||
130
rust/pv/src/uvdevice/info.rs
Normal file
130
rust/pv/src/uvdevice/info.rs
Normal file
@@ -0,0 +1,130 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
//
|
||||
// Copyright IBM Corp. 2023
|
||||
|
||||
use super::ffi::uvio_uvdev_info;
|
||||
use crate::{
|
||||
misc::{Flags, Lsb0Flags64},
|
||||
uv::{uv_ioctl, UvCmd, UvDevice},
|
||||
Result,
|
||||
};
|
||||
use std::fmt::Display;
|
||||
use zerocopy::{AsBytes, FromBytes};
|
||||
|
||||
/// Information of supported functions by the uvdevice
|
||||
///
|
||||
/// * `supp_uvio_cmds` - supported IOCTLs by this device
|
||||
/// * `supp_uv_cmds` - supported UVCs corresponding to the IOCTL
|
||||
///
|
||||
/// UVIO request to get information about supported request types by this
|
||||
/// uvdevice and the Ultravisor.
|
||||
/// Everything is output.
|
||||
/// If the bit is set in both, `supp_uvio_cmds` and `supp_uv_cmds`,
|
||||
/// the uvdevice and the Ultravisor support that call.
|
||||
///
|
||||
/// Note that bit 0 ([`UvDevice::INFO_NR`]) is always zero for `supp_uv_cmds`
|
||||
/// as there is no corresponding UV-call.
|
||||
///
|
||||
#[derive(Debug)]
|
||||
pub struct UvDeviceInfo {
|
||||
supp_uvio_cmds: Lsb0Flags64,
|
||||
supp_uv_cmds: Option<Lsb0Flags64>,
|
||||
}
|
||||
|
||||
impl UvDeviceInfo {
|
||||
/// Get information from the uvdevice.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// This function will return an error if the ioctl fails and the error code is not
|
||||
/// [`libc::ENOTTY`].
|
||||
/// `ENOTTY` is most likely because the uvdevice does not support the info IOCTL.
|
||||
/// In that case one can safely assume that the device only supports the Attestation IOCTL.
|
||||
/// Therefore this is what this function returns IOCTL support for Attestation and _Data not
|
||||
/// available_ for the UV Attestation facility.
|
||||
/// To check if the Ultravisor supports the Attestation call check at
|
||||
/// `/sys/firmware/uv/query/facilities` and check for bit 28 (Msb0 ordering!)
|
||||
pub fn get(uv: &UvDevice) -> Result<Self> {
|
||||
let mut cmd = uvio_uvdev_info::new_zeroed();
|
||||
match uv.send_cmd(&mut cmd) {
|
||||
Ok(_) => Ok(cmd.into()),
|
||||
Err(crate::Error::Io(e)) if e.raw_os_error() == Some(libc::ENOTTY) => Ok(Self {
|
||||
supp_uvio_cmds: (UvDevice::ATTESTATION_NR as u64).into(),
|
||||
supp_uv_cmds: None,
|
||||
}),
|
||||
Err(e) => Err(e),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<uvio_uvdev_info> for UvDeviceInfo {
|
||||
fn from(value: uvio_uvdev_info) -> Self {
|
||||
Self {
|
||||
supp_uvio_cmds: value.supp_uvio_cmds.into(),
|
||||
supp_uv_cmds: Some(value.supp_uv_cmds.into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl UvCmd for uvio_uvdev_info {
|
||||
fn cmd(&self) -> u64 {
|
||||
uv_ioctl(UvDevice::INFO_NR)
|
||||
}
|
||||
|
||||
fn data(&mut self) -> Option<&mut [u8]> {
|
||||
Some(self.as_bytes_mut())
|
||||
}
|
||||
|
||||
fn rc_fmt(&self, _: u16, _: u16) -> Option<&'static str> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn nr_as_string(nr: u8) -> Option<&'static str> {
|
||||
match nr {
|
||||
UvDevice::INFO_NR => Some("Info"),
|
||||
UvDevice::ATTESTATION_NR => Some("Attestation"),
|
||||
UvDevice::ADD_SECRET_NR => Some("Add Secret"),
|
||||
UvDevice::LIST_SECRET_NR => Some("List Secrets"),
|
||||
UvDevice::LOCK_SECRET_NR => Some("Lock Secret Store"),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn print_uvdevice_cmd(nr: u8, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match nr_as_string(nr) {
|
||||
Some(s) => write!(f, "{s}"),
|
||||
None => write!(f, "Unknown ({nr})"),
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_flags(uv_cmds: &Lsb0Flags64, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let supp_cmds: Vec<_> = (0u8..64)
|
||||
.filter(|v| -> bool { uv_cmds.is_set(*v) })
|
||||
.enumerate()
|
||||
.collect();
|
||||
let num_supp_cmds = supp_cmds.len();
|
||||
if num_supp_cmds == 0 {
|
||||
println!("None");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
for (n, cmd) in supp_cmds {
|
||||
print_uvdevice_cmd(cmd, f)?;
|
||||
if n != num_supp_cmds - 1 {
|
||||
write!(f, ", ")?;
|
||||
}
|
||||
}
|
||||
writeln!(f)
|
||||
}
|
||||
impl Display for UvDeviceInfo {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "uvdevice supports:")?;
|
||||
parse_flags(&self.supp_uvio_cmds, f)?;
|
||||
writeln!(f, "Ultravisor-calls available:")?;
|
||||
match &self.supp_uv_cmds {
|
||||
Some(cmds) => parse_flags(cmds, f),
|
||||
None => writeln!(f, "Data not available"),
|
||||
}
|
||||
}
|
||||
}
|
||||
231
rust/pv/src/uvdevice/test.rs
Normal file
231
rust/pv/src/uvdevice/test.rs
Normal file
@@ -0,0 +1,231 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
//
|
||||
// Copyright IBM Corp. 2023
|
||||
|
||||
#![cfg(test)]
|
||||
|
||||
use std::{
|
||||
os::unix::prelude::FromRawFd,
|
||||
sync::{Mutex, MutexGuard},
|
||||
};
|
||||
|
||||
use super::*;
|
||||
use lazy_static::lazy_static;
|
||||
|
||||
lazy_static! {
|
||||
/// needed to serialize all tests as tests operate on static data required by the mock
|
||||
static ref TEST_LOCK: Mutex<()> = Mutex::new(());
|
||||
/// exists to have a lazy static mod variable
|
||||
static ref IOCTL_MTX: Mutex<IoctlCtx> = Mutex::new(IoctlCtx::new());
|
||||
}
|
||||
|
||||
fn get_lock<T>(m: &'static Mutex<T>) -> MutexGuard<'static, T> {
|
||||
match m.lock() {
|
||||
Ok(guard) => guard,
|
||||
Err(poisoned) => poisoned.into_inner(),
|
||||
}
|
||||
}
|
||||
|
||||
struct IoctlCtx {
|
||||
modify: Box<dyn FnMut(&mut ffi::uvio_ioctl_cb) -> i32 + Send + Sync>,
|
||||
exp_cmd: ::libc::c_ulong,
|
||||
called: bool,
|
||||
}
|
||||
|
||||
impl IoctlCtx {
|
||||
pub fn exp_cmd(&mut self, cmd: ::libc::c_ulong) -> &mut Self {
|
||||
self.exp_cmd = cmd;
|
||||
self
|
||||
}
|
||||
pub fn set_mdfy<F>(&mut self, mdfy: F) -> &mut Self
|
||||
where
|
||||
F: FnMut(&mut ffi::uvio_ioctl_cb) -> ::libc::c_int + 'static + Send + Sync,
|
||||
{
|
||||
self.modify = Box::new(mdfy);
|
||||
self
|
||||
}
|
||||
pub fn reset(&mut self) -> bool {
|
||||
let old = self.called;
|
||||
self.called = false;
|
||||
old
|
||||
}
|
||||
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
modify: Box::new(|_| -1),
|
||||
exp_cmd: 0,
|
||||
called: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub mod mock_libc {
|
||||
use super::*;
|
||||
|
||||
pub unsafe fn ioctl(
|
||||
fd: ::libc::c_int,
|
||||
cmd: ::libc::c_ulong,
|
||||
data: *mut ffi::uvio_ioctl_cb,
|
||||
) -> ::libc::c_int {
|
||||
let mut ctx = get_lock(&IOCTL_MTX);
|
||||
assert!(!ctx.called, "IOCTL called more than once");
|
||||
ctx.called = true;
|
||||
|
||||
assert_eq!(cmd, ctx.exp_cmd, "IOCTL cmd mismatch");
|
||||
assert_eq!(fd, 17, "IOCTL fd mismatch");
|
||||
|
||||
let data_ref: &mut ffi::uvio_ioctl_cb = &mut *data;
|
||||
|
||||
(ctx.modify)(data_ref)
|
||||
}
|
||||
}
|
||||
|
||||
impl ffi::uvio_ioctl_cb {
|
||||
fn addr_eq(&self, exp: u64) -> &Self {
|
||||
assert_eq!(
|
||||
self.argument_addr, exp,
|
||||
"ioctl arg addr not eq: {} == {}",
|
||||
self.argument_addr, exp
|
||||
);
|
||||
self
|
||||
}
|
||||
fn size_eq(&self, exp: u32) -> &Self {
|
||||
assert_eq!(
|
||||
self.argument_len, exp,
|
||||
"ioctl arg len not eq: {} == {}",
|
||||
self.argument_len, exp
|
||||
);
|
||||
self
|
||||
}
|
||||
fn set_rc(&mut self, rc: u16) -> &mut Self {
|
||||
self.uv_rc = rc;
|
||||
self
|
||||
}
|
||||
fn set_rrc(&mut self, rrc: u16) -> &mut Self {
|
||||
self.uv_rrc = rrc;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
const TEST_CMD: u64 = 17;
|
||||
struct TestCmd(Option<Vec<u8>>);
|
||||
impl UvCmd for TestCmd {
|
||||
fn cmd(&self) -> u64 {
|
||||
TEST_CMD
|
||||
}
|
||||
fn rc_fmt(&self, _rc: u16, _rrc: u16) -> Option<&'static str> {
|
||||
None
|
||||
}
|
||||
fn data(&mut self) -> Option<&mut [u8]> {
|
||||
match &mut self.0 {
|
||||
None => None,
|
||||
Some(d) => Some(d.as_mut_slice()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl UvDevice {
|
||||
///use some random fd for `uvdevice` its OK, as the ioctl is mocked and never touches the passed file
|
||||
fn test_dev() -> Self {
|
||||
UvDevice(unsafe { std::fs::File::from_raw_fd(17) })
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ioctl_fail() {
|
||||
let _m = get_lock(&TEST_LOCK);
|
||||
|
||||
let mut mock_cmd = TestCmd(None);
|
||||
|
||||
get_lock(&IOCTL_MTX).exp_cmd(TEST_CMD).set_mdfy(|_| -1);
|
||||
|
||||
let uv = UvDevice::test_dev();
|
||||
|
||||
let res = uv.send_cmd(&mut mock_cmd);
|
||||
assert!(get_lock(&IOCTL_MTX).reset(), "IOCTL was never called");
|
||||
assert!(matches!(res, Err(Error::Io(_))));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ioctl_simpleo() {
|
||||
let _m = get_lock(&TEST_LOCK);
|
||||
|
||||
let mut mock_cmd = TestCmd(None);
|
||||
|
||||
get_lock(&IOCTL_MTX).exp_cmd(TEST_CMD).set_mdfy(|cb| {
|
||||
cb.set_rc(1).addr_eq(0).size_eq(0);
|
||||
0
|
||||
});
|
||||
|
||||
let uv = UvDevice::test_dev();
|
||||
let res = uv.send_cmd(&mut mock_cmd);
|
||||
assert!(get_lock(&IOCTL_MTX).reset(), "IOCTL was never called");
|
||||
assert!(res.is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ioctl_simple_err() {
|
||||
let _m = get_lock(&TEST_LOCK);
|
||||
|
||||
let mut mock_cmd = TestCmd(None);
|
||||
|
||||
get_lock(&IOCTL_MTX).exp_cmd(TEST_CMD).set_mdfy(|cb| {
|
||||
cb.set_rc(17).set_rrc(3).addr_eq(0).size_eq(0);
|
||||
0
|
||||
});
|
||||
|
||||
let uv = UvDevice::test_dev();
|
||||
let res = uv.send_cmd(&mut mock_cmd);
|
||||
assert!(get_lock(&IOCTL_MTX).reset(), "IOCTL was never called");
|
||||
assert!(matches!(res, Err(Error::Uv{rc, rrc, ..}) if rc == 17 && rrc == 3 ));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ioctl_write_data() {
|
||||
let _m = get_lock(&TEST_LOCK);
|
||||
|
||||
let cmd_data = vec![0u8; 32];
|
||||
let cmd_data_len = cmd_data.len();
|
||||
let data_addr = cmd_data.as_ptr() as u64;
|
||||
|
||||
let mut mock_cmd = TestCmd(Some(cmd_data));
|
||||
|
||||
get_lock(&IOCTL_MTX).exp_cmd(TEST_CMD).set_mdfy(move |cb| {
|
||||
cb.set_rc(1).addr_eq(data_addr).size_eq(32);
|
||||
unsafe {
|
||||
::libc::memset(cb.argument_addr as *mut ::libc::c_void, 0x42, cmd_data_len);
|
||||
}
|
||||
0
|
||||
});
|
||||
|
||||
let uv = UvDevice::test_dev();
|
||||
let res = uv.send_cmd(&mut mock_cmd);
|
||||
assert!(get_lock(&IOCTL_MTX).reset(), "IOCTL was never called");
|
||||
assert_eq!(res.unwrap(), UvcSuccess::RC_SUCCESS);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ioctl_read_data() {
|
||||
let _m = get_lock(&TEST_LOCK);
|
||||
|
||||
let cmd_data = vec![42u8; 32];
|
||||
let cmd_data_len = cmd_data.len();
|
||||
let data_addr = cmd_data.as_ptr() as u64;
|
||||
let data_exp = cmd_data.clone();
|
||||
|
||||
let mut mock_cmd = TestCmd(Some(cmd_data));
|
||||
|
||||
get_lock(&IOCTL_MTX).exp_cmd(TEST_CMD).set_mdfy(move |cb| {
|
||||
cb.set_rc(1).addr_eq(data_addr).size_eq(32);
|
||||
unsafe {
|
||||
let data = std::slice::from_raw_parts(cb.argument_addr as *const u8, cmd_data_len);
|
||||
assert_eq!(data, data_exp);
|
||||
}
|
||||
0
|
||||
});
|
||||
|
||||
let uv = UvDevice::test_dev();
|
||||
let res = uv.send_cmd(&mut mock_cmd);
|
||||
assert!(get_lock(&IOCTL_MTX).reset(), "IOCTL was never called");
|
||||
assert_eq!(res.unwrap(), UvcSuccess::RC_SUCCESS);
|
||||
}
|
||||
80
rust/pv/src/uvsecret.rs
Normal file
80
rust/pv/src/uvsecret.rs
Normal file
@@ -0,0 +1,80 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
//
|
||||
// Copyright IBM Corp. 2023
|
||||
|
||||
#![cfg(feature = "uvsecret")]
|
||||
//! Provides functionality to manage the UV secret store.
|
||||
//!
|
||||
//! Provides functionality to build `add-secret` requests.
|
||||
//! Also provides interfaces, to dispatch `Add Secret`, `Lock Secret Store`,
|
||||
//! and `List Secrets` requests,
|
||||
#[cfg(feature = "request")]
|
||||
pub mod asrcb;
|
||||
#[cfg(feature = "request")]
|
||||
pub mod ext_secret;
|
||||
#[cfg(feature = "request")]
|
||||
pub mod guest_secret;
|
||||
pub mod secret_list;
|
||||
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)
|
||||
}
|
||||
314
rust/pv/src/uvsecret/asrcb.rs
Normal file
314
rust/pv/src/uvsecret/asrcb.rs
Normal file
@@ -0,0 +1,314 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
//
|
||||
// Copyright IBM Corp. 2023
|
||||
|
||||
use super::{AddSecretMagic, UserDataType};
|
||||
use crate::requires_feat;
|
||||
use crate::{
|
||||
assert_size,
|
||||
misc::Flags,
|
||||
request::{
|
||||
hkdf_rfc_5869,
|
||||
openssl::{
|
||||
pkey::{PKey, Public},
|
||||
Md,
|
||||
},
|
||||
uvsecret::{ExtSecret, GuestSecret},
|
||||
Aad, BootHdrTags, Keyslot, ReqEncrCtx, Request, RequestVersion, Secret,
|
||||
},
|
||||
uv::{ConfigUid, UvFlags},
|
||||
Result,
|
||||
};
|
||||
use zerocopy::AsBytes;
|
||||
|
||||
/// Internal wrapper for Guest Secret, so that we can dump it in the form the UV wants it to be
|
||||
#[derive(Debug, Clone)]
|
||||
struct BinGuestSecret(GuestSecret);
|
||||
impl BinGuestSecret {
|
||||
/// Reference to the confidential data
|
||||
fn confidential(&self) -> &[u8] {
|
||||
match &self.0 {
|
||||
GuestSecret::Null => &[],
|
||||
GuestSecret::Association { secret, .. } => secret.value().as_slice(),
|
||||
}
|
||||
}
|
||||
fn dump_auth(&self) -> Vec<u8> {
|
||||
match &self.0 {
|
||||
GuestSecret::Null => vec![0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
GuestSecret::Association { id, .. } => {
|
||||
let mut buf = vec![0; 48];
|
||||
buf[3] = 2;
|
||||
buf[7] = 0x20;
|
||||
buf[16..48].copy_from_slice(id.as_slice());
|
||||
buf
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<GuestSecret> for BinGuestSecret {
|
||||
fn from(secret: GuestSecret) -> Self {
|
||||
BinGuestSecret(secret)
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Clone, Copy, AsBytes)]
|
||||
struct ReqAuthData {
|
||||
flags: UvFlags,
|
||||
boot_tags: BootHdrTags,
|
||||
cuid: ConfigUid,
|
||||
reserved90: [u8; 0x100],
|
||||
prog_res190: [u8; 0x200],
|
||||
}
|
||||
assert_size!(ReqAuthData, 0x3e8);
|
||||
|
||||
impl ReqAuthData {
|
||||
fn new<F: Into<UvFlags>>(boot_tags: BootHdrTags, flags: F) -> Self {
|
||||
ReqAuthData {
|
||||
flags: flags.into(),
|
||||
boot_tags,
|
||||
cuid: [0; 0x10],
|
||||
reserved90: [0; 0x100],
|
||||
prog_res190: [0; 0x200],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct ReqConfData {
|
||||
secret: BinGuestSecret,
|
||||
extension_secret: Secret<[u8; 32]>,
|
||||
}
|
||||
|
||||
impl ReqConfData {
|
||||
fn to_bytes(&self) -> Secret<Vec<u8>> {
|
||||
let secret = self.secret.confidential();
|
||||
|
||||
let mut v = vec![0; secret.len() + 32];
|
||||
if !secret.is_empty() {
|
||||
v[..secret.len()].copy_from_slice(secret);
|
||||
}
|
||||
v[secret.len()..32 + secret.len()]
|
||||
.copy_from_slice(self.extension_secret.value().as_slice());
|
||||
v.into()
|
||||
}
|
||||
}
|
||||
|
||||
/// Flags for [`AddSecretRequest`]
|
||||
///
|
||||
#[doc = requires_feat!(reqsecret)]
|
||||
#[derive(Default, Clone, Copy, Debug)]
|
||||
pub struct AddSecretFlags(UvFlags);
|
||||
impl AddSecretFlags {
|
||||
/// Enables the disable-dump flag
|
||||
///
|
||||
/// After the request was dispatched successfully,
|
||||
/// the UV will not provide any dump decryption information for the SE-guest anymore.
|
||||
pub fn set_disable_dump(&mut self) {
|
||||
self.0.set_bit(0)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&u64> for AddSecretFlags {
|
||||
fn from(v: &u64) -> Self {
|
||||
Self(v.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<AddSecretFlags> for UvFlags {
|
||||
fn from(f: AddSecretFlags) -> Self {
|
||||
f.0
|
||||
}
|
||||
}
|
||||
|
||||
/// Versions for [`AddSecretRequest`]
|
||||
///
|
||||
#[doc = requires_feat!(reqsecret)]
|
||||
#[repr(u32)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum AddSecretVersion {
|
||||
/// Version 1 (= 0x0100)
|
||||
One = 0x0100,
|
||||
|
||||
#[cfg(not(doc))]
|
||||
#[cfg(any(debug_assertions, test))]
|
||||
/// Only for testing
|
||||
Inv = 0,
|
||||
}
|
||||
|
||||
impl From<AddSecretVersion> for RequestVersion {
|
||||
fn from(val: AddSecretVersion) -> Self {
|
||||
val as RequestVersion
|
||||
}
|
||||
}
|
||||
|
||||
impl AddSecretMagic {
|
||||
fn get(&self) -> crate::request::RequestMagic {
|
||||
self.as_bytes().try_into().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
/// Add-secret request Control Block
|
||||
///
|
||||
/// An ASRCB wraps a secret to transport it securely to the Ultravisor.
|
||||
///
|
||||
/// Layout:
|
||||
///```none
|
||||
/// _______________________________________________________________
|
||||
/// | generic header (48)
|
||||
/// | --------------------------------------------------- |
|
||||
/// | SE header tags: PLD(64) ALD(64) TLD(64) HeaderTag(16) |
|
||||
/// | Configuration unique ID(16) (Attestation) |
|
||||
/// | Optional, defaults to 0 |
|
||||
/// | Reserved(256) |
|
||||
/// | User Data(512) (reserved) |
|
||||
/// | Customer Public Key (160) generated for each request |
|
||||
/// | N Keyslots(80 each) |
|
||||
/// | Secret header (Secret dependent) |
|
||||
/// | --------------------------------------------------- |
|
||||
/// | Secret to add (Secret type dependent)(may be 0 bytes) | Encrypted
|
||||
/// | Extension secret(32) Optional, defaults to 0 | Encrypted
|
||||
/// | --------------------------------------------------- |
|
||||
/// | AES GCM Tag (16) |
|
||||
/// |_____________________________________________________________|
|
||||
///```
|
||||
///
|
||||
#[doc = requires_feat!(reqsecret)]
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct AddSecretRequest {
|
||||
magic: AddSecretMagic,
|
||||
version: AddSecretVersion,
|
||||
aad: ReqAuthData,
|
||||
keyslots: Vec<Keyslot>,
|
||||
conf: ReqConfData,
|
||||
}
|
||||
|
||||
impl AddSecretRequest {
|
||||
/// Create a new add-secret request.
|
||||
///
|
||||
/// The request has no extension secret, no configuration UID, no host-keys,
|
||||
/// and no user data
|
||||
///
|
||||
pub fn new(
|
||||
version: AddSecretVersion,
|
||||
secret: GuestSecret,
|
||||
boot_tags: BootHdrTags,
|
||||
flags: AddSecretFlags,
|
||||
) -> Self {
|
||||
AddSecretRequest {
|
||||
conf: ReqConfData {
|
||||
extension_secret: Secret::new([0; 32]),
|
||||
secret: secret.into(),
|
||||
},
|
||||
aad: ReqAuthData::new(boot_tags, flags),
|
||||
keyslots: vec![],
|
||||
version,
|
||||
magic: UserDataType::Null.into(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets the Configuration Unique Id of this [`AddSecretRequest`].
|
||||
pub fn set_cuid(&mut self, cuid: ConfigUid) {
|
||||
self.aad.cuid = cuid;
|
||||
}
|
||||
|
||||
/// Sets the extension secret of this [`AddSecretRequest`].
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// This function will return an error if the key derivation fails for a [`ExtSecret::Derived`].
|
||||
pub fn set_ext_secret(&mut self, ext_secret: ExtSecret) -> Result<()> {
|
||||
const DER_EXT_SECRET_INFO: &[u8] = "IBM Z Ultravisor Add-Secret".as_bytes();
|
||||
self.conf.extension_secret = match ext_secret {
|
||||
ExtSecret::Simple(s) => s,
|
||||
ExtSecret::Derived(cck) => hkdf_rfc_5869(
|
||||
Md::sha512(),
|
||||
cck.value(),
|
||||
self.aad.boot_tags.seht(),
|
||||
DER_EXT_SECRET_INFO,
|
||||
)?
|
||||
.into(),
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Returns a reference to the guest secret of this [`AddSecretRequest`].
|
||||
pub fn guest_secret(&self) -> &GuestSecret {
|
||||
&self.conf.secret.0
|
||||
}
|
||||
|
||||
/// compiles the authenticated area of this request
|
||||
fn aad(&self, ctx: &ReqEncrCtx, conf_len: usize) -> Result<Vec<u8>> {
|
||||
let cust_pub_key = ctx.key_coords()?;
|
||||
let secr_auth = self.conf.secret.dump_auth();
|
||||
|
||||
let mut aad: Vec<Aad> = Vec::with_capacity(3 + self.keyslots.len());
|
||||
aad.push(Aad::Plain(self.aad.as_bytes()));
|
||||
aad.push(Aad::Plain(cust_pub_key.as_ref()));
|
||||
self.keyslots.iter().for_each(|k| aad.push(Aad::Ks(k)));
|
||||
aad.push(Aad::Plain(&secr_auth));
|
||||
|
||||
ctx.build_aad(self.version.into(), &aad, conf_len, self.magic.get())
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
#[cfg(any(debug_assertions, test))]
|
||||
pub fn aad_and_conf(&self, ctx: &ReqEncrCtx) -> Result<(Vec<u8>, Vec<u8>)> {
|
||||
let conf = self.conf.to_bytes();
|
||||
let aad = self.aad(ctx, conf.value().len())?;
|
||||
Ok((aad, conf.value().to_owned()))
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
#[cfg(any(debug_assertions, test))]
|
||||
pub fn no_encrypt(&self, ctx: &ReqEncrCtx) -> Result<Vec<u8>> {
|
||||
let (mut res, mut conf) = self.aad_and_conf(ctx)?;
|
||||
res.append(&mut conf);
|
||||
res.append(&mut vec![0x24; 32]);
|
||||
Ok(res)
|
||||
}
|
||||
}
|
||||
|
||||
impl Request for AddSecretRequest {
|
||||
fn encrypt(&self, ctx: &ReqEncrCtx) -> Result<Vec<u8>> {
|
||||
let conf = self.conf.to_bytes();
|
||||
let aad = self.aad(ctx, conf.value().len())?;
|
||||
ctx.encrypt_aead(&aad, conf.value())
|
||||
}
|
||||
|
||||
fn add_hostkey(&mut self, hostkey: PKey<Public>) {
|
||||
self.keyslots.push(Keyslot::new(hostkey))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn guest_secret_bin_null() {
|
||||
let gs: BinGuestSecret = GuestSecret::Null.into();
|
||||
let gs_bytes = gs.dump_auth();
|
||||
let exp = vec![0u8, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
|
||||
|
||||
assert_eq!(exp, gs_bytes);
|
||||
assert_eq!(&Vec::<u8>::new(), gs.confidential())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn guest_secret_bin_ap() {
|
||||
let gs: BinGuestSecret = GuestSecret::Association {
|
||||
name: "test".to_string(),
|
||||
id: [1; 32],
|
||||
secret: [2; 32].into(),
|
||||
}
|
||||
.into();
|
||||
let gs_bytes_auth = gs.dump_auth();
|
||||
let mut exp = vec![0u8, 0, 0, 2, 0, 0, 0, 0x20, 0, 0, 0, 0, 0, 0, 0, 0];
|
||||
exp.extend([1; 32]);
|
||||
|
||||
assert_eq!(exp, gs_bytes_auth);
|
||||
assert_eq!(&[2; 32], gs.confidential());
|
||||
}
|
||||
}
|
||||
16
rust/pv/src/uvsecret/ext_secret.rs
Normal file
16
rust/pv/src/uvsecret/ext_secret.rs
Normal file
@@ -0,0 +1,16 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
//
|
||||
// Copyright IBM Corp. 2023
|
||||
|
||||
use crate::{request::Secret, requires_feat};
|
||||
|
||||
/// Extension Secret for [`crate::request::uvsecret::AddSecretRequest`]
|
||||
///
|
||||
#[doc = requires_feat!(reqsecret)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum ExtSecret {
|
||||
/// A bytepattern that must be equal for each request targeting the same SE-guest instance
|
||||
Simple(Secret<[u8; 32]>), // contains the secret
|
||||
/// A secret that is derived from the Customer communication key from the SE-header
|
||||
Derived(Secret<[u8; 32]>), // contains the cck
|
||||
}
|
||||
146
rust/pv/src/uvsecret/guest_secret.rs
Normal file
146
rust/pv/src/uvsecret/guest_secret.rs
Normal file
@@ -0,0 +1,146 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
//
|
||||
// Copyright IBM Corp. 2023
|
||||
|
||||
#[allow(unused_imports)] //used for more convenient docstring
|
||||
use super::asrcb::AddSecretRequest;
|
||||
use super::{ser_gsid, SECRET_ID_SIZE};
|
||||
use crate::{
|
||||
request::{hash, openssl::MessageDigest, random_array, Secret},
|
||||
requires_feat, Result,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::convert::TryInto;
|
||||
|
||||
const SECRET_SIZE: usize = 32;
|
||||
/// A Secret to be added in [`AddSecretRequest`]
|
||||
///
|
||||
#[doc = requires_feat!(reqsecret)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||
pub enum GuestSecret {
|
||||
/// No guest secret
|
||||
Null,
|
||||
/// Association secret used to associate an extension card to a SE guest
|
||||
///
|
||||
/// Create Associations using [`GuestSecret::association`]
|
||||
Association {
|
||||
/// Name of the secret
|
||||
name: String,
|
||||
#[serde(serialize_with = "ser_gsid", deserialize_with = "de_gsid")]
|
||||
/// SHA256 hash of [`GuestSecret::Association::name`]
|
||||
id: [u8; SECRET_ID_SIZE],
|
||||
/// Confidential actual assocuiation secret (32 bytes)
|
||||
#[serde(skip)]
|
||||
secret: Secret<[u8; SECRET_SIZE]>,
|
||||
},
|
||||
}
|
||||
|
||||
impl GuestSecret {
|
||||
/// Create a new [`GuestSecret::Association`].
|
||||
///
|
||||
/// * `name` - Name of the secret. Will be hashed into a 32 byte id
|
||||
/// * `secret` - Value of the secret. Ranom if [`Option::None`]
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// This function will return an error if OpenSSL cannot create a hash.
|
||||
pub fn association<O>(name: &str, secret: O) -> Result<GuestSecret>
|
||||
where
|
||||
O: Into<Option<[u8; SECRET_SIZE]>>,
|
||||
{
|
||||
let id = hash(MessageDigest::sha256(), name.as_bytes())?.to_vec();
|
||||
let secret = match secret.into() {
|
||||
Some(s) => s,
|
||||
None => random_array()?,
|
||||
};
|
||||
|
||||
Ok(GuestSecret::Association {
|
||||
name: name.to_string(),
|
||||
id: id.try_into().unwrap(),
|
||||
secret: secret.into(),
|
||||
})
|
||||
}
|
||||
}
|
||||
fn de_gsid<'de, D>(de: D) -> Result<[u8; 32], D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
struct FieldVisitor;
|
||||
|
||||
impl<'de> serde::de::Visitor<'de> for FieldVisitor {
|
||||
type Value = [u8; SECRET_ID_SIZE];
|
||||
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
formatter.write_str("a `32 bytes long hexstring` prepended with 0x")
|
||||
}
|
||||
fn visit_str<E>(self, s: &str) -> Result<Self::Value, E>
|
||||
where
|
||||
E: serde::de::Error,
|
||||
{
|
||||
if s.len() != SECRET_ID_SIZE * 2 + 2 {
|
||||
return Err(serde::de::Error::invalid_length(s.len(), &self));
|
||||
}
|
||||
let nb = s.strip_prefix("0x").ok_or_else(|| {
|
||||
serde::de::Error::invalid_value(serde::de::Unexpected::Str(s), &self)
|
||||
})?;
|
||||
crate::misc::parse_hex(nb)
|
||||
.try_into()
|
||||
.map_err(|_| serde::de::Error::invalid_value(serde::de::Unexpected::Str(s), &self))
|
||||
}
|
||||
}
|
||||
de.deserialize_identifier(FieldVisitor)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use serde_test::{assert_tokens, Token};
|
||||
|
||||
//todo test GuestSecret::association
|
||||
#[test]
|
||||
fn association() {
|
||||
let secret_value = [0x11; 32];
|
||||
let exp_id = [
|
||||
0x75, 0xad, 0x01, 0xb4, 0x03, 0xa9, 0xe4, 0x59, 0x5d, 0xf0, 0x7a, 0xce, 0x38, 0x12,
|
||||
0x97, 0x99, 0xdd, 0xad, 0x90, 0x8a, 0x8f, 0x82, 0xf9, 0xc3, 0x2c, 0xdd, 0x7d, 0x53,
|
||||
0xef, 0xc7, 0x3c, 0x62,
|
||||
];
|
||||
let name = "association secret".to_string();
|
||||
let secret = GuestSecret::association("association secret", secret_value).unwrap();
|
||||
let exp = GuestSecret::Association {
|
||||
name,
|
||||
id: exp_id,
|
||||
secret: secret_value.into(),
|
||||
};
|
||||
assert_eq!(secret, exp);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ap_asc_parse() {
|
||||
let id = [
|
||||
0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0x01, 0x23, 0x45, 0x67, 0x89, 0xab,
|
||||
0xcd, 0xef, 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0x01, 0x23, 0x45, 0x67,
|
||||
0x89, 0xab, 0xcd, 0xef,
|
||||
];
|
||||
let asc = GuestSecret::Association {
|
||||
name: "test123".to_string(),
|
||||
id,
|
||||
secret: [0; 32].into(),
|
||||
};
|
||||
|
||||
assert_tokens(
|
||||
&asc,
|
||||
&[
|
||||
Token::StructVariant {
|
||||
name: "GuestSecret",
|
||||
variant: "Association",
|
||||
len: 2,
|
||||
},
|
||||
Token::String("name"),
|
||||
Token::String("test123"),
|
||||
Token::String("id"),
|
||||
Token::String("0x0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"),
|
||||
Token::StructVariantEnd,
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
224
rust/pv/src/uvsecret/secret_list.rs
Normal file
224
rust/pv/src/uvsecret/secret_list.rs
Normal file
@@ -0,0 +1,224 @@
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
124
rust/pv/src/uvsecret/uvc.rs
Normal file
124
rust/pv/src/uvsecret/uvc.rs
Normal file
@@ -0,0 +1,124 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
//
|
||||
// Copyright IBM Corp. 2023
|
||||
|
||||
use super::AddSecretMagic;
|
||||
use crate::{
|
||||
request::MagicValue,
|
||||
requires_feat,
|
||||
uv::{uv_ioctl, UvCmd, UvDevice},
|
||||
Error, Result, PAGESIZE,
|
||||
};
|
||||
use std::io::Read;
|
||||
use std::usize;
|
||||
|
||||
/// _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.
|
||||
///
|
||||
#[doc = requires_feat!(uvsecret)]
|
||||
pub struct ListCmd(Vec<u8>);
|
||||
impl ListCmd {
|
||||
fn with_size(size: usize) -> Self {
|
||||
Self(vec![0; size])
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for ListCmd {
|
||||
fn default() -> Self {
|
||||
Self::with_size(PAGESIZE)
|
||||
}
|
||||
}
|
||||
|
||||
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.
|
||||
///
|
||||
#[doc = requires_feat!(uvsecret)]
|
||||
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.
|
||||
///
|
||||
#[doc = requires_feat!(uvsecret)]
|
||||
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,
|
||||
}
|
||||
}
|
||||
}
|
||||
158
rust/pv/src/verify.rs
Normal file
158
rust/pv/src/verify.rs
Normal file
@@ -0,0 +1,158 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
//
|
||||
// Copyright IBM Corp. 2023
|
||||
|
||||
use core::slice;
|
||||
use log::debug;
|
||||
use openssl::stack::Stack;
|
||||
use openssl::x509::store::X509Store;
|
||||
use openssl::x509::{CrlStatus, X509Ref, X509StoreContext, X509};
|
||||
use openssl_extensions::crl::StackableX509Crl;
|
||||
use openssl_extensions::crl::X509StoreContextExtension;
|
||||
|
||||
use crate::error::bail_hkd_verify;
|
||||
use crate::misc::{read_certs, read_file};
|
||||
use crate::Result;
|
||||
|
||||
mod helper;
|
||||
mod test;
|
||||
|
||||
/// A HkdVerifier verifies that a host-key document(HKD) can be trusted.
|
||||
///
|
||||
/// If the verification fails the HKD should not be used to create requests.
|
||||
pub trait HkdVerifier {
|
||||
/// Checks if the given host-key document can be trusted.
|
||||
///
|
||||
/// #Errors
|
||||
///
|
||||
/// This function will return an error if the Hostkey cannot be trusted.
|
||||
/// Refer to the concrete Error type for the specific reason.
|
||||
fn verify(&self, hkd: &X509Ref) -> Result<()>;
|
||||
}
|
||||
|
||||
/// A "verifier" that does not verify and accepts all given host-keys as valid.
|
||||
pub struct NoVerifyHkd;
|
||||
impl HkdVerifier for NoVerifyHkd {
|
||||
fn verify(&self, _hkd: &X509Ref) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// A Verifier that checks the host-key document against a chain of trust.
|
||||
pub struct CertVerifier {
|
||||
store: X509Store,
|
||||
ibm_z_sign_key: X509,
|
||||
offline: bool,
|
||||
}
|
||||
|
||||
impl HkdVerifier for CertVerifier {
|
||||
/// This function verifies a host-key
|
||||
/// document. To do so multiple steps are required:
|
||||
///
|
||||
/// 1. issuer(host_key) == subject(ibm_z_sign_key)
|
||||
/// 2. Signature verification
|
||||
/// 3. @hkd must not be expired
|
||||
/// 4. @hkd must not be revoked
|
||||
fn verify(&self, hkd: &X509Ref) -> Result<()> {
|
||||
helper::verify_hkd_options(hkd, &self.ibm_z_sign_key)?;
|
||||
|
||||
// verify that the hkd was signed with the key of the IBM signing key
|
||||
if !hkd.verify(self.ibm_z_sign_key.public_key()?.as_ref())? {
|
||||
bail_hkd_verify!(Signature);
|
||||
}
|
||||
|
||||
// Find matching crl for sign key in the store or download them
|
||||
let crls = self.hkd_crls(hkd)?;
|
||||
|
||||
// Verify that the CLRs are still valid
|
||||
let mut verified_crls = Vec::with_capacity(crls.len());
|
||||
for crl in &crls {
|
||||
if helper::verify_crl(crl, &self.ibm_z_sign_key).is_some() {
|
||||
verified_crls.push(crl.to_owned());
|
||||
}
|
||||
}
|
||||
|
||||
// Test if hkd was revoked (min1 required)
|
||||
if verified_crls.is_empty() {
|
||||
bail_hkd_verify!(NoCrl);
|
||||
}
|
||||
for crl in &verified_crls {
|
||||
match crl.get_by_cert(&hkd.to_owned()) {
|
||||
CrlStatus::NotRevoked => (),
|
||||
_ => bail_hkd_verify!(HdkRevoked),
|
||||
}
|
||||
}
|
||||
debug!("HKD: verified");
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl CertVerifier {
|
||||
///Download the CLRs that a HKD refers to.
|
||||
pub fn hkd_crls(&self, hkd: &X509Ref) -> Result<Stack<StackableX509Crl>> {
|
||||
let mut ctx = X509StoreContext::new()?;
|
||||
// Unfortunately we cannot use a dedicated function here and have to use a closure (E0434)
|
||||
// Otherwise, we cannot refer to self
|
||||
let mut crls = ctx.init_opt(&self.store, None, None, |ctx| {
|
||||
let subject = self.ibm_z_sign_key.subject_name();
|
||||
match ctx.crls(subject) {
|
||||
Ok(crls) => Ok(crls),
|
||||
_ => {
|
||||
// reorder the name and try again
|
||||
let broken_subj = helper::reorder_x509_names(subject)?;
|
||||
ctx.crls(&broken_subj).or_else(helper::stack_err_hlp)
|
||||
}
|
||||
}
|
||||
})?;
|
||||
|
||||
if !self.offline {
|
||||
// Try to download a CRL if defined in the HKD
|
||||
if let Some(crl) = helper::download_first_crl_from_x509(hkd)? {
|
||||
crl.into_iter().try_for_each(|c| crls.push(c.into()))?;
|
||||
}
|
||||
}
|
||||
Ok(crls)
|
||||
}
|
||||
}
|
||||
|
||||
impl CertVerifier {
|
||||
/// Create a `CertVerifier`.
|
||||
///
|
||||
/// * `cert_paths` - Paths to Cerificates for the chain of trust
|
||||
/// * `crl_paths` - Paths to certificate revocation lists for the chain of trust
|
||||
/// * `root_ca_path` - Path to the root of trust
|
||||
/// * `offline` - if set to true the verification process will not try to download CRLs from the
|
||||
/// internet.
|
||||
/// # Errors
|
||||
///
|
||||
/// This function will return an error if the chain of trust could not be established.
|
||||
pub fn new(
|
||||
cert_paths: &[String],
|
||||
crl_paths: &[String],
|
||||
root_ca_path: &Option<String>,
|
||||
offline: bool,
|
||||
) -> Result<Self> {
|
||||
let mut store = helper::store_setup(root_ca_path, crl_paths, cert_paths)?;
|
||||
let mut untr_certs = Vec::with_capacity(cert_paths.len());
|
||||
for path in cert_paths {
|
||||
let mut crt = read_certs(&read_file(path, "certificate")?)?;
|
||||
if !offline {
|
||||
helper::download_crls_into_store(&mut store, &crt)?;
|
||||
}
|
||||
untr_certs.append(&mut crt);
|
||||
}
|
||||
|
||||
// remove the IBM signing certificate from chain.
|
||||
// We have to verify them separately as they are not marked as intermediate certs
|
||||
let (ibm_z_sign_key, chain) = helper::extract_ibm_sign_key(untr_certs)?;
|
||||
|
||||
let store = store.build();
|
||||
helper::verify_chain(&store, &chain, slice::from_ref(&ibm_z_sign_key))?;
|
||||
|
||||
Ok(Self {
|
||||
store,
|
||||
ibm_z_sign_key,
|
||||
offline,
|
||||
})
|
||||
}
|
||||
}
|
||||
518
rust/pv/src/verify/helper.rs
Normal file
518
rust/pv/src/verify/helper.rs
Normal file
@@ -0,0 +1,518 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
//
|
||||
// Copyright IBM Corp. 2023
|
||||
|
||||
use crate::error::bail_hkd_verify;
|
||||
use crate::misc::{memeq, read_crls};
|
||||
use crate::HkdVerifyErrorType::*;
|
||||
use crate::{Error, Result};
|
||||
use curl::easy::{Easy2, Handler, WriteError};
|
||||
use libc::c_int;
|
||||
use log::debug;
|
||||
use openssl::{
|
||||
asn1::{Asn1Time, Asn1TimeRef},
|
||||
error::ErrorStack,
|
||||
nid::Nid,
|
||||
ssl::SslFiletype,
|
||||
stack::{Stack, Stackable},
|
||||
x509::{
|
||||
store::{File, X509Lookup, X509StoreBuilder, X509StoreBuilderRef, X509StoreRef},
|
||||
verify::{X509VerifyFlags, X509VerifyParam},
|
||||
X509Crl, X509CrlRef, X509Name, X509NameRef, X509PurposeId, X509Ref, X509StoreContext,
|
||||
X509StoreContextRef, X509VerifyResult, X509,
|
||||
},
|
||||
};
|
||||
use openssl_extensions::{
|
||||
akid::{AkidCheckResult, AkidExtension},
|
||||
crl::X509StoreExtension,
|
||||
};
|
||||
use std::cmp::Ordering;
|
||||
use std::time::Duration;
|
||||
use std::usize;
|
||||
|
||||
/// 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
|
||||
/// for details).
|
||||
///
|
||||
const SECURITY_LEVEL: usize = 2;
|
||||
const SECURITY_BITS_ARRAY: [u32; 6] = [0, 80, 112, 128, 192, 256];
|
||||
const SECURITY_BITS: u32 = SECURITY_BITS_ARRAY[SECURITY_LEVEL];
|
||||
const SECURITY_CHAIN_MAX_LEN: c_int = 2;
|
||||
|
||||
/// verifies that the HKD
|
||||
/// * has enough security bits
|
||||
/// * is inside its validity period
|
||||
/// * issuer name is the subject name of the [`sign_key`]
|
||||
/// * the Authority Key ID matches the Signing Key ID of the [`sign_key`]
|
||||
pub fn verify_hkd_options(hkd: &X509Ref, sign_key: &X509Ref) -> Result<()> {
|
||||
let hk_pkey = hkd.public_key()?;
|
||||
let security_bits = hk_pkey.security_bits();
|
||||
|
||||
if SECURITY_BITS > 0 && SECURITY_BITS > security_bits {
|
||||
return Err(Error::HkdVerify(SecurityBits(security_bits, SECURITY_BITS)));
|
||||
}
|
||||
// TODO rust-openssl fix X509::not.after/before() impl to return Option& not panic on nullptr from C?
|
||||
//try_... rust-openssl
|
||||
// verify that the hkd is still valid
|
||||
check_validity_period(hkd.not_before(), hkd.not_after())?;
|
||||
|
||||
// check if hkd.issuer_name == issuer.subject
|
||||
check_x509_name_equal(sign_key.subject_name(), hkd.issuer_name())?;
|
||||
|
||||
// verify that the AKID of the hkd matches the SKID of the issuer
|
||||
if let Some(akid) = hkd.akid() {
|
||||
if akid.check(sign_key) != AkidCheckResult::OK {
|
||||
bail_hkd_verify!(Akid);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn verify_crl(crl: &X509CrlRef, issuer: &X509Ref) -> Option<()> {
|
||||
let last = crl.last_update();
|
||||
let next = crl.next_update()?;
|
||||
|
||||
check_validity_period(last, next).ok()?;
|
||||
if let Some(akid) = crl.akid() {
|
||||
if akid.check(issuer) != AkidCheckResult::OK {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
|
||||
check_x509_name_equal(crl.issuer_name(), issuer.subject_name()).ok()?;
|
||||
|
||||
match crl.verify(issuer.public_key().ok()?.as_ref()).ok()? {
|
||||
true => Some(()),
|
||||
false => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Setup the x509Store such that it can be used it for verifying certificates
|
||||
pub fn store_setup(
|
||||
root_ca_path: &Option<String>,
|
||||
crl_paths: &[String],
|
||||
cert_w_crl_paths: &[String],
|
||||
) -> Result<X509StoreBuilder> {
|
||||
let mut x509store = X509StoreBuilder::new()?;
|
||||
|
||||
match root_ca_path {
|
||||
None => x509store.set_default_paths()?,
|
||||
Some(p) => load_root_ca(p, &mut x509store)?,
|
||||
}
|
||||
|
||||
for crl in crl_paths {
|
||||
load_crl_to_store(&mut x509store, crl, true).map_err(|source| Error::X509Load {
|
||||
path: crl.to_owned(),
|
||||
ty: Error::CRL,
|
||||
source,
|
||||
})?;
|
||||
}
|
||||
|
||||
for crl in cert_w_crl_paths {
|
||||
load_crl_to_store(&mut x509store, crl, false).map_err(|source| Error::X509Load {
|
||||
path: crl.to_owned(),
|
||||
ty: Error::CRL,
|
||||
source,
|
||||
})?;
|
||||
}
|
||||
let mut param = X509VerifyParam::new()?;
|
||||
let flags = X509VerifyFlags::X509_STRICT
|
||||
| X509VerifyFlags::CRL_CHECK
|
||||
| X509VerifyFlags::CRL_CHECK_ALL
|
||||
| X509VerifyFlags::TRUSTED_FIRST
|
||||
| X509VerifyFlags::CHECK_SS_SIGNATURE
|
||||
| X509VerifyFlags::POLICY_CHECK;
|
||||
|
||||
param.set_depth(SECURITY_CHAIN_MAX_LEN);
|
||||
param.set_auth_level(SECURITY_LEVEL as i32);
|
||||
param.set_purpose(X509PurposeId::ANY)?;
|
||||
param.set_flags(flags)?;
|
||||
x509store.set_param(¶m)?;
|
||||
|
||||
Ok(x509store)
|
||||
}
|
||||
|
||||
/// Verify that the given IBM signing keys can be trusted
|
||||
/// -> check the chain: IBMsignKey<-InterCA(s)<-RootCA
|
||||
pub fn verify_chain(
|
||||
store: &X509StoreRef,
|
||||
untrusted_certs: &Stack<X509>,
|
||||
sign_keys: &[X509],
|
||||
) -> Result<()> {
|
||||
fn verify_fun(ctx: &mut X509StoreContextRef) -> std::result::Result<bool, ErrorStack> {
|
||||
// verify certificate
|
||||
let res = ctx.verify_cert()?;
|
||||
if !res {
|
||||
debug!("Failed to verify the singing key with the chain of trust");
|
||||
return Ok(res);
|
||||
}
|
||||
// verify that the chain is as expected
|
||||
let chain = match ctx.chain() {
|
||||
Some(c) => c,
|
||||
None => {
|
||||
debug!("No verification chain in verify-context. (openssl BUG)");
|
||||
ctx.set_error(X509VerifyResult::APPLICATION_VERIFICATION);
|
||||
return Ok(false);
|
||||
}
|
||||
};
|
||||
if chain.len() < SECURITY_CHAIN_MAX_LEN as usize {
|
||||
debug!("Verification expects one root and at least one intermediate certificate",);
|
||||
ctx.set_error(X509VerifyResult::APPLICATION_VERIFICATION);
|
||||
Ok(false)
|
||||
} else {
|
||||
Ok(true)
|
||||
}
|
||||
}
|
||||
|
||||
let mut store_ctx = X509StoreContext::new()?;
|
||||
|
||||
for sign_key in sign_keys {
|
||||
// (rust)OpenSSL should not error out on `X509_verify_cert`\
|
||||
// (Internal (probably unrecoverable) error like OOM)
|
||||
if !store_ctx
|
||||
.init(store, sign_key, untrusted_certs, verify_fun)
|
||||
.map_err(|e| Error::InternalSsl("The IBM Z signing key could not be verified.", e))?
|
||||
{
|
||||
return Err(Error::HkdVerify(IbmSignInvalid(
|
||||
store_ctx.error(),
|
||||
store_ctx.error_depth(),
|
||||
)));
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Consumes and splits the given vector into a single IBM Z signing key and other certificates
|
||||
///
|
||||
/// Error if not exactly one IBM Z signing key available
|
||||
pub fn extract_ibm_sign_key(certs: Vec<X509>) -> Result<(X509, Stack<X509>)> {
|
||||
let ibm_z_sign_key = get_ibm_z_sign_key(&certs)?;
|
||||
|
||||
let mut chain = Stack::<X509>::new()?;
|
||||
for x in certs.into_iter().filter(|x| !is_ibm_signing_cert(x)) {
|
||||
chain.push(x)?;
|
||||
}
|
||||
Ok((ibm_z_sign_key, chain))
|
||||
}
|
||||
|
||||
/// for all certs load the first CRL specified into our store
|
||||
pub fn download_crls_into_store(store: &mut X509StoreBuilderRef, crts: &[X509]) -> Result<()> {
|
||||
for crt in crts {
|
||||
debug!("Download crls for {crt:?}");
|
||||
if let Some(crl) = download_first_crl_from_x509(crt)? {
|
||||
crl.iter().try_for_each(|c| store.add_crl(c))?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Name Entry values of an IBM Z key signing cert
|
||||
//Asn1StringRef::as_slice aka ASN1_STRING_get0_data gives a string without \0 delimiter
|
||||
const IBM_Z_COMMON_NAME: &[u8; 43usize] = b"International Business Machines Corporation";
|
||||
const IBM_Z_COUNTRY_NAME: &[u8; 2usize] = b"US";
|
||||
const IBM_Z_LOCALITY_NAME: &[u8; 12usize] = b"Poughkeepsie";
|
||||
const IBM_Z_ORGANIZATIONAL_UNIT_NAME_SUFFIX: &str = "Key Signing Service";
|
||||
const IBM_Z_ORGANIZATION_NAME: &[u8; 43usize] = b"International Business Machines Corporation";
|
||||
const IBM_Z_STATE: &[u8; 8usize] = b"New York";
|
||||
const IMB_Z_ENTRY_COUNT: usize = 6;
|
||||
fn name_data_eq(entries: &X509NameRef, nid: Nid, rhs: &[u8]) -> bool {
|
||||
let mut it = entries.entries_by_nid(nid);
|
||||
match it.next() {
|
||||
None => false,
|
||||
Some(entry) => memeq(entry.data().as_slice(), rhs),
|
||||
}
|
||||
}
|
||||
|
||||
fn is_ibm_signing_cert(cert: &X509) -> bool {
|
||||
let subj = cert.subject_name();
|
||||
|
||||
if subj.entries().count() != IMB_Z_ENTRY_COUNT
|
||||
|| !name_data_eq(subj, Nid::COUNTRYNAME, IBM_Z_COUNTRY_NAME)
|
||||
|| !name_data_eq(subj, Nid::STATEORPROVINCENAME, IBM_Z_STATE)
|
||||
|| !name_data_eq(subj, Nid::LOCALITYNAME, IBM_Z_LOCALITY_NAME)
|
||||
|| !name_data_eq(subj, Nid::ORGANIZATIONNAME, IBM_Z_ORGANIZATION_NAME)
|
||||
|| !name_data_eq(subj, Nid::COMMONNAME, IBM_Z_COMMON_NAME)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return match subj.entries_by_nid(Nid::ORGANIZATIONALUNITNAME).next() {
|
||||
None => false,
|
||||
Some(entry) => match entry.data().as_utf8() {
|
||||
Err(_) => false,
|
||||
Ok(s) => s
|
||||
.as_bytes()
|
||||
.ends_with(IBM_Z_ORGANIZATIONAL_UNIT_NAME_SUFFIX.as_bytes()),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
fn get_ibm_z_sign_key(certs: &[X509]) -> Result<X509> {
|
||||
let mut ibm_sign_keys = certs.iter().filter(|x| is_ibm_signing_cert(x)).cloned();
|
||||
match ibm_sign_keys.next() {
|
||||
None => bail_hkd_verify!(NoIbmSignKey),
|
||||
Some(k) => match ibm_sign_keys.next() {
|
||||
None => Ok(k),
|
||||
Some(_) => bail_hkd_verify!(ManyIbmSignKeys),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn load_root_ca(path: &str, x509_store: &mut X509StoreBuilder) -> Result<()> {
|
||||
let lu = x509_store.add_lookup(X509Lookup::<File>::file())?;
|
||||
|
||||
// Try to load cert as PEM file
|
||||
match lu.load_cert_file(path, SslFiletype::PEM) {
|
||||
Ok(_) => lu
|
||||
.load_crl_file(path, SslFiletype::PEM)
|
||||
.map(|_| ())
|
||||
.or(Ok(())),
|
||||
// Not a PEM file? try ASN1
|
||||
Err(_) => lu
|
||||
.load_cert_file(path, SslFiletype::ASN1)
|
||||
.map(|_| ())
|
||||
.map_err(|source| Error::X509Load {
|
||||
path: path.to_string(),
|
||||
ty: Error::CERT,
|
||||
source,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
fn load_crl_to_store(
|
||||
x509_store: &mut X509StoreBuilder,
|
||||
path: &str,
|
||||
err_out_empty_crl: bool,
|
||||
) -> std::result::Result<(), openssl::error::ErrorStack> {
|
||||
let lu = x509_store.add_lookup(X509Lookup::<File>::file())?;
|
||||
// Try to load cert as PEM file
|
||||
if lu.load_crl_file(path, SslFiletype::PEM).is_err() {
|
||||
// Not a PEM file? try read as ASN1
|
||||
let res = lu.load_crl_file(path, SslFiletype::ASN1);
|
||||
if err_out_empty_crl {
|
||||
res?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
///Run through the forest of the distribution points and find them
|
||||
pub fn x509_dist_points(cert: &X509Ref) -> Vec<String> {
|
||||
let mut res = Vec::<String>::with_capacity(1);
|
||||
let dps = match cert.crl_distribution_points() {
|
||||
Some(d) => d,
|
||||
None => return res,
|
||||
};
|
||||
for dp in dps {
|
||||
let dp_nm = match dp.distpoint() {
|
||||
Some(nm) => nm,
|
||||
None => continue,
|
||||
};
|
||||
let dp_gns = match dp_nm.fullname() {
|
||||
Some(gns) => gns,
|
||||
None => continue,
|
||||
};
|
||||
for dp_gn in dp_gns {
|
||||
match dp_gn.uri() {
|
||||
Some(uri) => res.push(uri.to_string()),
|
||||
None => continue,
|
||||
};
|
||||
}
|
||||
}
|
||||
res
|
||||
}
|
||||
|
||||
const CRL_TIMEOUT_MAX: Duration = Duration::from_secs(3);
|
||||
|
||||
/// Searches for CRL Distribution points and downloads the CRL. Stops after the first successful
|
||||
/// download.
|
||||
///
|
||||
/// Error if sth bad(=unexpected) happens (not bad: crl not available at link, unexpected format)
|
||||
/// Other issues are mapped to Ok(None)
|
||||
pub fn download_first_crl_from_x509(cert: &X509Ref) -> Result<Option<Vec<X509Crl>>> {
|
||||
struct Buf(Vec<u8>);
|
||||
|
||||
impl Handler for Buf {
|
||||
fn write(&mut self, data: &[u8]) -> std::result::Result<usize, WriteError> {
|
||||
self.0.extend_from_slice(data);
|
||||
Ok(data.len())
|
||||
}
|
||||
}
|
||||
|
||||
for dist_point in x509_dist_points(cert) {
|
||||
// A typical certificate is about 1200 bytes long
|
||||
let mut handle = Easy2::new(Buf(Vec::with_capacity(1500)));
|
||||
handle.url(&dist_point)?;
|
||||
handle.get(true)?;
|
||||
handle.follow_location(true)?;
|
||||
handle.timeout(CRL_TIMEOUT_MAX)?;
|
||||
handle.useragent("s390-tools-pv-crl")?;
|
||||
|
||||
if handle.perform().is_err() {
|
||||
continue;
|
||||
}
|
||||
match read_crls(&handle.get_ref().0) {
|
||||
Err(_) => continue,
|
||||
Ok(crl) => return Ok(Some(crl)),
|
||||
}
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
fn check_validity_period(not_before: &Asn1TimeRef, not_after: &Asn1TimeRef) -> Result<()> {
|
||||
let now = Asn1Time::days_from_now(0)?;
|
||||
if let Ordering::Less = now.compare(not_before)? {
|
||||
bail_hkd_verify!(BeforeValidity);
|
||||
}
|
||||
match now.compare(not_after)? {
|
||||
Ordering::Less => Ok(()),
|
||||
_ => bail_hkd_verify!(AfterValidity),
|
||||
}
|
||||
}
|
||||
|
||||
fn check_x509_name_equal(lhs: &X509NameRef, rhs: &X509NameRef) -> Result<()> {
|
||||
if lhs.entries().count() != rhs.entries().count() {
|
||||
bail_hkd_verify!(IssuerMismatch);
|
||||
}
|
||||
|
||||
for l in lhs.entries() {
|
||||
let ldata = l.data().as_slice();
|
||||
|
||||
// search for the matching value in the rhs names
|
||||
// found none? -> names are not equal
|
||||
if !rhs.entries().any(|r| memeq(ldata, r.data().as_slice())) {
|
||||
bail_hkd_verify!(IssuerMismatch);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
const NIDS_CORRECT_ORDER: [Nid; 6] = [
|
||||
Nid::COUNTRYNAME,
|
||||
Nid::ORGANIZATIONNAME,
|
||||
Nid::ORGANIZATIONALUNITNAME,
|
||||
Nid::LOCALITYNAME,
|
||||
Nid::STATEORPROVINCENAME,
|
||||
Nid::COMMONNAME,
|
||||
];
|
||||
/**
|
||||
* Workaround to fix the mismatch between issuer name of the
|
||||
* IBM Z signing CRLs and the IBM Z signing key subject name.
|
||||
*/
|
||||
pub fn reorder_x509_names(subject: &X509NameRef) -> std::result::Result<X509Name, ErrorStack> {
|
||||
let mut correct_subj = X509Name::builder()?;
|
||||
for nid in NIDS_CORRECT_ORDER {
|
||||
if let Some(name) = subject.entries_by_nid(nid).next() {
|
||||
correct_subj.append_entry(name)?;
|
||||
}
|
||||
}
|
||||
Ok(correct_subj.build())
|
||||
}
|
||||
|
||||
pub fn stack_err_hlp<T: Stackable>(
|
||||
e: ErrorStack,
|
||||
) -> std::result::Result<Stack<T>, openssl::error::ErrorStack> {
|
||||
match e.errors().len() {
|
||||
0 => Stack::<T>::new(),
|
||||
_ => Err(e),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
/// tests for some private functions
|
||||
mod test {
|
||||
|
||||
use openssl_extensions::x509_crl_eq;
|
||||
|
||||
use super::*;
|
||||
use crate::test_utils::*;
|
||||
use std::time::{Duration, SystemTime};
|
||||
|
||||
fn sys_to_asn1_time(syst: SystemTime) -> Asn1Time {
|
||||
let secs = syst
|
||||
.duration_since(SystemTime::UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.as_secs();
|
||||
Asn1Time::from_unix(secs as i64).unwrap()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn check_validity_period() {
|
||||
let day = Duration::from_secs(60 * 60 * 24);
|
||||
let yesterday = sys_to_asn1_time(SystemTime::now() - day);
|
||||
let tomorrow = sys_to_asn1_time(SystemTime::now() + day);
|
||||
|
||||
assert!(super::check_validity_period(&yesterday, &tomorrow).is_ok());
|
||||
assert!(matches!(
|
||||
super::check_validity_period(&tomorrow, &tomorrow),
|
||||
Err(Error::HkdVerify(BeforeValidity))
|
||||
));
|
||||
assert!(matches!(
|
||||
super::check_validity_period(&yesterday, &yesterday),
|
||||
Err(Error::HkdVerify(AfterValidity))
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn x509_name_equal() {
|
||||
let sign_crt = load_gen_cert("ibm.crt");
|
||||
let hkd = load_gen_cert("host.crt");
|
||||
let other = load_gen_cert("inter_ca.crt");
|
||||
|
||||
assert!(super::check_x509_name_equal(sign_crt.subject_name(), hkd.issuer_name()).is_ok(),);
|
||||
|
||||
assert!(matches!(
|
||||
super::check_x509_name_equal(other.subject_name(), hkd.subject_name()),
|
||||
Err(Error::HkdVerify(IssuerMismatch))
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn is_ibm_z_sign_key() {
|
||||
let ibm_crt = load_gen_cert("ibm.crt");
|
||||
let no_ibm_crt = load_gen_cert("inter_ca.crt");
|
||||
let ibm_wrong_subj = load_gen_cert("ibm_wrong_subject.crt");
|
||||
|
||||
assert!(is_ibm_signing_cert(&ibm_crt));
|
||||
assert!(!is_ibm_signing_cert(&no_ibm_crt));
|
||||
assert!(!is_ibm_signing_cert(&ibm_wrong_subj));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn get_ibm_z_sign_key() {
|
||||
let ibm_crt = load_gen_cert("ibm.crt");
|
||||
let ibm_wrong_subj = load_gen_cert("ibm_wrong_subject.crt");
|
||||
let no_sign_crt = load_gen_cert("inter_ca.crt");
|
||||
|
||||
assert!(super::get_ibm_z_sign_key(&[ibm_crt.clone()]).is_ok());
|
||||
assert!(matches!(
|
||||
super::get_ibm_z_sign_key(&[ibm_crt.clone(), ibm_crt.clone()]),
|
||||
Err(Error::HkdVerify(ManyIbmSignKeys))
|
||||
));
|
||||
assert!(matches!(
|
||||
super::get_ibm_z_sign_key(&[ibm_wrong_subj]),
|
||||
Err(Error::HkdVerify(NoIbmSignKey))
|
||||
));
|
||||
assert!(matches!(
|
||||
super::get_ibm_z_sign_key(&[no_sign_crt.clone()]),
|
||||
Err(Error::HkdVerify(NoIbmSignKey))
|
||||
));
|
||||
assert!(super::get_ibm_z_sign_key(&[ibm_crt, no_sign_crt]).is_ok(),);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn download_first_crl_from_x509() {
|
||||
let ibm_crt = load_gen_cert("ibm.crt");
|
||||
let inter_crl = load_gen_crl("inter_ca.crl");
|
||||
let _m_inter = super::super::test::mock_endpt("inter_ca.crl");
|
||||
|
||||
let crl_d = super::download_first_crl_from_x509(&ibm_crt)
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
assert_eq!(crl_d.len(), 1);
|
||||
assert!(x509_crl_eq(
|
||||
crl_d.first().unwrap().as_ref(),
|
||||
inter_crl.as_ref()
|
||||
));
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user