Compare commits

...

52 Commits

Author SHA1 Message Date
Jan Höppner
d9ce54dee3 New release s390-tools-2.29.0
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2023-08-04 14:51:41 +02:00
Marc Hartmayer
7b056735ed rust/pv: some cargo clippy fixes
Found and fixed by the command `cargo clippy --fix -- -Dwarnings`.

Reviewed-by: Jan Höppner <hoeppner@linux.ibm.com>
Signed-off-by: Marc Hartmayer <mhartmay@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2023-08-04 14:18:49 +02:00
Marc Hartmayer
33fde99138 rust: pv/pvsecret: some typo fixes
It reads 'add-secret requests' and not 'add secret requests'.

Reviewed-by: Jan Höppner <hoeppner@linux.ibm.com>
Signed-off-by: Marc Hartmayer <mhartmay@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2023-08-04 14:18:49 +02:00
Steffen Maier
4b486e87cc zdev/dracut: fix kdump build to integrate with site support
This complements v2.27.0 commit 73c46a3056 ("zdev/dracut: fix kdump by
only activating required devices"). On older distributions, the absence of
zdev_id can cause the following harmless error messages for each udev
event:

(spawn)[387]: failed to execute '/lib/s390-tools/zdev_id' \
'/lib/s390-tools/zdev_id': No such file or directory

Kdump is still functional nonetheless.

As of v2.24.0 commit 2e89722ef0 ("zdev: make site specific udev-rule for
ccw"), the invocations of chzdev within
zdev/dracut/95zdev-kdump/module-setup.sh generate
/etc/udev/rules.d/40-zdev-id.rules. And so even though zdev-kdump
intentionally does not install zdev_id and its previous singular user
zdev/udev/81-dpm.rules into the kdump initrd, because DPM device auto
configuration is not desired in the kdump environment, zdev_id meanwhile
has an additional functionality for site-support and the generated
40-zdev-id.rules calls /lib/s390-tools/zdev_id. By installing zdev_id into
the kdump initrd, 40-zdev-id.rules can work without error.

Fixes: 73c46a3056 ("zdev/dracut: fix kdump by only activating required devices")
Reviewed-by: Alexander Egorenkov <egorenar@linux.ibm.com>
Reviewed-by: Vineeth Vijayan <vneethv@linux.ibm.com>
Signed-off-by: Steffen Maier <maier@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2023-08-04 13:10:00 +02:00
Marc Hartmayer
a07d1bca74 pvattest: Add --output option to verify subcommand
Other tools may need to process the configuration-unique id. Provide a
machine readable format by writing to a YAML file containing a `cuid`
entry and optionally an `add` entry. New CLI options `--format` and
`--output` are introduced for this. Currently, only the output format
`yaml` is supported.

Reviewed-by: Jan Höppner <hoeppner@linux.ibm.com>
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Marc Hartmayer <mhartmay@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2023-08-04 13:10:00 +02:00
Marc Hartmayer
5e1ef90962 pvattest: refactor fprint_verify_result
Refactor the code responsible for printing the verification result into
a new function named `fprint_verify_result`. This function will be
reused in the future and a new output format will be added. While at it,
increase the dump data width for the addition data. In addition, add a
prefix `0x` to the values in order to indicate that these are
hexadecimal values.

Reviewed-by: Jan Höppner <hoeppner@linux.ibm.com>
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Marc Hartmayer <mhartmay@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2023-08-04 13:10:00 +02:00
Marc Hartmayer
8fe214d915 pvattest: pvattest_hexdump: add error checks
`fprintf` can fail, therefore check the return code of it.

Reviewed-by: Jan Höppner <hoeppner@linux.ibm.com>
Signed-off-by: Marc Hartmayer <mhartmay@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2023-08-04 13:10:00 +02:00
Marc Hartmayer
bec9d1dfcd pvattest: pvattest_hexdump: add beautify parameter
Add `beautify` parameter to `pvattest_hexdump`. If the parameter is set
to true, a offset and whitespaces will be added for better readability.

With beautify set to FALSE:

14141414141414141414141414141414

With beautify set to TRUE:

0x0000  1414 1414 1414 1414 1414 1414 1414 1414

Reviewed-by: Marc Hartmayer <mhartmay@linux.ibm.com>
Reviewed-by: Jan Höppner <hoeppner@linux.ibm.com>
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2023-08-04 13:10:00 +02:00
Marc Hartmayer
694d5d4638 pvattest: pvattest_log_bytes: use GBytes and handle @width == 0
The only user of `pvattest_hexdump` uses GBytes anyway, therefore let's
use GBytes as parameter type for `pvattest_hexdump`.

While at it, change the order of the parameters, constify `@width` and
handle the `@width == 0` case, which results in an hex-string without
any line breaks.

Reviewed-by: Jan Höppner <hoeppner@linux.ibm.com>
Signed-off-by: Marc Hartmayer <mhartmay@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2023-08-04 13:10:00 +02:00
Marc Hartmayer
a6ac7cd876 common.mak: set SHELL to /bin/bash
This fixes the following error (using GNU Make 4.3.0):

  make[2]: command: Command not found

The reason for this is that `command` is a bash builtin. `command` is
used in `common.mak` for the `combdb` Makefile target.

While at it, remove the now useless `SHELL := /bin/bash` definitions in
the sub-Makefiles.

Fixes: 3d098416c6 ("common.mak: add `compdb` Makefile target")
Reviewed-by: Jan Höppner <hoeppner@linux.ibm.com>
Signed-off-by: Marc Hartmayer <mhartmay@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2023-08-04 11:41:25 +02:00
Joern Siglen
28751a097a dbginfo.sh: global original Input Field Separator (IFS)
Replace local ifs_orig variables in different functions by a single
global IFS_ORI variable. This will reduce the risk of missing a local
saving and restore of the original IFS.

Reviewed-by: Michael Storzer <MSTORZER@de.ibm.com>
Signed-off-by: Joern Siglen <siglen@de.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2023-08-04 11:41:25 +02:00
Steffen Eiden
19c795be0d rust: Add README
Add a README.md to the rust subdirectory as a guideline for writing
s390-tools tools in Rust. This includes build integration, dependency
handling, and a few coding style hints. Rust related information
is also added to the main README.md.

Reviewed-by: Jan Höppner <hoeppner@linux.ibm.com>
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
[hoeppner@linux.ibm.com: Adapt details in README.md]
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2023-08-04 11:41:21 +02:00
Steffen Eiden
dd82c26f87 rust: Add tool to manage UV-secrets
Add `pvsecret` a tool to create, add, list, and delete Ultravisor
secrets. `pvsecret` uses the functionality from the pv-crate
to provide an command line tool to manage the secrets.

Add a new target group PV_TARGETS in rust/Makefile that additionally
requires openssl and libcurl as pv with the feature "request" uses
openssl and libcurl fearures.

Acked-by: Jan Höppner <hoeppner@linux.ibm.com>
Acked-by: Marc Hartmayer <mhartmay@linux.ibm.com>
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
[hoeppner@linux.ibm.com: Adapt man pages and help output]
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2023-08-04 11:41:09 +02:00
Steffen Eiden
c6f621d0dc rust: Add library for pv tools
Add a `pv` crate that bundles useful functions and structs for creating
requests like `Attestation`, `Add Secret`, or even `Boot` a.k.a.
Secure Execution Image.
Note pv includes a subcrate `openssl_extensions` that (temporarily)
bundles some needed `openssl-rust` functionalities that are not
upstream yet. The plan is to remove these, when they become
upstream.

The pv crate has multiple features:
 * request - code to generate requests
 * uvsecret - code to access the UV-secret api
		with request enabled also generating requests is
		possible

Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
Acked-by: Jan Höppner <hoeppner@linux.ibm.com>
Acked-by: Marc Hartmayer <mhartmay@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2023-08-04 11:40:54 +02:00
Steffen Eiden
e6add997eb Integrate rust into s390-tools build system
The rust integration into the s390-tools build system consists of the
following steps:

- Add a subdirectory for the rust code.
- Add a Makefile that forwards rust builds to `cargo`.
- Add a `utils` crate for rust code in s390-tools.
- Add rust stuff for dotfiles:
  - gitignore
  - editorconfig
  - codespellrc (while at it, add an ignore file)

With cargo the rust ecosystem has its own build system which also is
responsible to resolve rust dependencies via downloading the dependencies
from (default) crates.io and discover the source files. Therefore, the
Makefile just calls `cargo build` to forward the build to cargo.

If a rust crate does not require external dependencies, users might call
rustc directly.

A simple `make` will build all the rust targets as well (with --release
specified). Also `make install` will work as usual.

A few Makefile configuration variables are introduced for rust/Cargo:
  - HAVE_CARGO (default 1) to toggle the build of rust code using cargo
  - CARGOFLAGS             to add custom cargo flags, e.g. --offline
  - CARGO		   Cargo binary location defaults to
                           $(where cargo)

A new global make target is defined to get the current s390-tools
version:
$ make version
  2.28.0

rust/Makefile also has the `print-rust-targets`  target to print all rust
directories/crates that should be shipped/installed.

Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
Reviewed-by: Jan Höppner <hoeppner@linux.ibm.com>
Acked-by: Marc Hartmayer <mhartmay@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2023-08-04 11:40:46 +02:00
Marc Hartmayer
a2baeb2fe2 zdev/dracut/zdev-lib.sh: add ShellCheck shell directive
Add a ShellCheck [1] directive before the first command in the file to tell
ShellCheck and the reader which shell to use. See
https://www.shellcheck.net/wiki/SC2148 for details.

[1] https://www.shellcheck.net/

Reviewed-by: Steffen Maier <maier@linux.ibm.com>
Signed-off-by: Marc Hartmayer <mhartmay@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2023-08-02 14:48:12 +02:00
Thomas Richter
a500946e50 vmur: fix wrong command flag exclusion
Command vmur issues a warning and aborts receiving a file from
the reader when either option -t or option -b is specified.
Example:
 ./vmur re -t 0x25,0x40 22 -H /tmp/xxx.txt
 vmur: Conflicting options: -b and -t are mutually exclusive.
This is wrong as there is only one option specified.
The command should be aborted only when both flags are specified.

Fix this wrong behavior.

Fixes: d5f853c460 ("vmur: Remove option -c for dump file conversion")
Signed-off-by: Thomas Richter <tmricht@linux.ibm.com>
Reported-by: Benjamin Block <bblock@linux.ibm.com>
Reviewed-by: Benjamin Block <bblock@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2023-08-02 14:48:12 +02:00
Marc Hartmayer
b7c9c2679e genprotimg: add support for add-secret requests
IBM Secure Execution guests may want to share additional secrets with
the Ultravisor in a secure manner. For this the concept of secret
requests and three new Ultravisor-calls were introduced.

Add support to genprotimg to prepare an Secure Execution image with the
requirement that add-secret requests must provide an extension secret
that matches the customer communication key (CCK) derived extension
secret.

Reviewed-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Marc Hartmayer <mhartmay@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2023-08-02 14:48:12 +02:00
Marc Hartmayer
bc8a14895a genprotimg: improve the --comm-key description
In the future, this key will be used for additional things than the
guest dump encryption.

Reviewed-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Marc Hartmayer <mhartmay@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2023-08-02 14:48:12 +02:00
Marc Hartmayer
4ae68d0430 genprotimg: add NAME macro parameter
In preparation for the next patch, add the parameter `NAME` to the
`MUT_EXCL_BOOL_FLAG` macro. This is useful for the case when the command
line flag has a different naming than the struct field.

Reviewed-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Marc Hartmayer <mhartmay@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2023-08-02 14:48:12 +02:00
Marc Hartmayer
aabf97f885 genprotimg: refactor arguments related to the control flags into own struct
Refactor arguments related to the SE header control flags into own
struct with the name `PvControlFlagsArgs`. This change makes it easier
to extend the control flags arguments further, without touching the
signature of `pv_img_set_control_flags`. While at it, rename the struct
members `allow_...` to `enable_...`. This matches with the command line
option names.

Reviewed-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Marc Hartmayer <mhartmay@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2023-08-02 14:48:12 +02:00
Marc Hartmayer
a9b546cb0f genprotimg: pv_img_set_control_flags: refactor code
Introduce a function for setting the control flags. This makes it easier
to add more control flags in the future.

Reviewed-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Marc Hartmayer <mhartmay@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2023-08-02 14:48:12 +02:00
Marc Hartmayer
5566c31458 genprotimg: help|manpage: remove superfluous optional
A default is always optional, therefore let's remove the superfluous
"optional" statements.

Reviewed-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Marc Hartmayer <mhartmay@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2023-08-02 14:48:12 +02:00
Marc Hartmayer
ff96d6158e genprotimg/boot: avoid the deletion of .lds files by using .SECONDARY
Avoid the deletion of the intermediate
`(stage3a|stage3b|stage3b_reloc).lds` files by adding them to the
special built-in target `.SECONDARY` as prerequisites. This way they're
declared as intermediate files that should never be deleted
automatically. [1]

[1] https://www.gnu.org/software/make/manual/html_node/Special-Targets.html#index-preserving-with-_002eSECONDARY

Reviewed-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Marc Hartmayer <mhartmay@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2023-08-02 14:48:12 +02:00
Marc Hartmayer
2aa9071aed genprotimg: help: add missing period
Reviewed-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Marc Hartmayer <mhartmay@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2023-08-02 14:48:12 +02:00
Marc Hartmayer
adf2a030e6 genprotimg: fix error message
The option name is `--enable-dump` and not `--allow-dump`.

Reviewed-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Marc Hartmayer <mhartmay@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2023-08-02 14:48:12 +02:00
Marc Hartmayer
0783aa99d7 zipl/boot: simplify clean target
Simplify the clean target since there are several redundant
things (*.bin) and files that aren't built at all (.xxx and .yyy).

Reviewed-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Marc Hartmayer <mhartmay@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2023-08-02 14:48:12 +02:00
Marc Hartmayer
5a848c98bb zipl/src: remove no-pie and noexecstack compiler and linker flags
Only the bootloaders cannot be built as PIE and for that there are
already the -no-pie linker and compiler flags set by the boot/Makefile.
In addition, remove the `noexecstack` linker flag as it has no use
anymore (see commit 518bf7d7357 ("zipl/boot: use
`--no-warn-rwx-segments` linker flag")). It was originally introduced to
declare the ELF segment of the bootloader stack as non-executable. But
this ELF attribute had no effect for multiple reasons:

1. ALL_LDFLAGS is not used for the bootloaders
2. no ELF loader is used for the bootloaders that would take this ELF
   attribute into account

This fixes the problem of overriding  `-fPIE` set by the distributor.

Reviewed-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Marc Hartmayer <mhartmay@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2023-08-02 14:48:12 +02:00
Marc Hartmayer
e98f9b9c4a .editorconfig: provide more editor settings
Set the maximum line length for commit messages to 72 characters, and
the indentation style for Makefiles to TAB.

Reviewed-by: Jan Höppner <hoeppner@linux.ibm.com>
Signed-off-by: Marc Hartmayer <mhartmay@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2023-08-02 14:48:12 +02:00
Marc Hartmayer
faac2520c9 genprotimg|zipl: build debuginfo files
Currently, the debug information of the bootloader is discarded during
the raw binary creation. Change this by creating separate
<loader>.bin.debug files containing the debug information. The packager
will then be able to package these files as desired and the developer
can use them to debug the code.

Reviewed-by: Jan Höppner <hoeppner@linux.ibm.com>
Signed-off-by: Marc Hartmayer <mhartmay@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2023-08-02 14:48:12 +02:00
Marc Hartmayer
3d098416c6 common.mak: add compdb Makefile target
Add a new Makefile target 'compdb' to create the compilation database
'compile_commands.json' [1]. This file can then be used by the language
server 'clangd' [2], which is a possible backend for the so called
'Language Server Protocol' (LSP) [3] used by many IDEs and editors.

In addition, add this file to `.gitignore`.

[1] https://clang.llvm.org/docs/JSONCompilationDatabase.html
[2] https://clangd.llvm.org/
[3] https://microsoft.github.io/language-server-protocol/

Reviewed-by: Jan Höppner <hoeppner@linux.ibm.com>
Signed-off-by: Marc Hartmayer <mhartmay@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2023-08-02 14:48:12 +02:00
Peter Oberparleiter
7e53611e3a dump2tar: fix truncated paths
When creating a tar archive, dump2tar incorrectly truncates the last
character of file paths that are exactly 100 characters long.

Paths up to 100 characters can be represented in the 100-byte name field
of a tar header entry, while longer paths are handled via an additional
tar data block. For 100-character paths, dump2tar determines that a
single header is sufficient, but then uses util_strlcpy() to store the
path into the name field. Since util_strlcpy() ensures nul-terminated
strings, the final character of the path is overwritten.

Fix this by using strncpy() instead of util_strlcpy(). Also mark the
affected name fields as "nonstring" to prevent associated compiler
warnings.

Reported-by: Steffen Maier <maier@linux.ibm.com>
Fixes: d85cf20981 ("dump2tar: Change SET_STR_FIELD to copy strings correctly")
Reviewed-by: Jan Höppner <hoeppner@linux.ibm.com>
Signed-off-by: Peter Oberparleiter <oberpar@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2023-07-20 21:45:27 +02:00
Jan Höppner
d8496160cb Makefile: Fix pretty print indentation
Commit 62ec87680a61 ("common.mak: improve cross compilation support")
added one extra whitespace during the changes of the toolchain command
definitions. The rest of the commands did not receive that change.

Since then the pretty print output looks like this:
...
CC       zipl/boot/eckd1b.o
CXX      ziomon/ziorep_collapser.o
SED     zdev/src/lszdev_usage.c
CC       hyptop/sd_cpu_items.o
MV      zfcpdump/zfcpdump-initrd
LINK     dasdfmt/dasdfmt
...

Add the additional whitespace to all other tools definitions used during
the build process.

Note: This doesn't fix the misaligned indentation for commands like
$(CC_FOR_BUILD) as those inherit the whitespace from the original
command, here $(CC).

Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2023-07-20 21:45:27 +02:00
Ingo Franzki
c7f10bc76d zkey: man: Fix groff/troff warnings
Fix the following warnings:

troff: zkey-ekmfweb.1:455: warning: macro 'APP=LINUX'' not defined
troff: zkey-ekmfweb.1:457: warning: macro 'encvol'.' not defined

A single quote (') at the beginning of ta line is interpreted as macro.
Fix this by starting the line with a dummy character (\&).

Signed-off-by: Ingo Franzki <ifranzki@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2023-07-20 21:45:27 +02:00
Joern Siglen
f8910caa59 dbginfo.sh: include the dbginfo.sh used
include the version of dbginfo.sh just used for data collection into our
tar file - so we have the used version in case of data collection problems.

Reviewed-by: Michael Storzer <MSTORZER@de.ibm.com>
Signed-off-by: Joern Siglen <siglen@de.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2023-07-20 21:45:27 +02:00
Jan Höppner
b1d948daef gitignore: Sort list of generated executables
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2023-07-20 21:45:27 +02:00
Marc Hartmayer
4d4ddbd887 Recursive Makefiles: avoid race condition in the install target
The `install` Makefile target of the top Makefile has `all` and
`install-recursive` as prerequisites. This leads to the two recursive
Makefile calls `make -C <SUBDIR> all` and `make -C <SUBDIR> install`.

The problem is these two targets try to build the same object files and
this leads to a race condition between these two targets in case of a
parallel build.

Fix this problem by removing the `all` prerequisite from the `install`
target, as it is not needed since all the `install` targets in the
sub-Makefiles already have proper prerequisites.

Reviewed-by: Jan Höppner <hoeppner@linux.ibm.com>
Signed-off-by: Marc Hartmayer <mhartmay@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2023-07-20 21:45:27 +02:00
Mikhail Zaslonko
d205b47c08 zipl/boot: Conditionally clear memory upon reipl at ccw dump end
- Instead of always clearing the memory on reipl after the ccw dump has
  been taken, check for the special OS_INFO_FLAG_REIPL_CLEAR flag in
  os_info flags entry (indicates if sysfs 'clear' attribute has been set
  on the panicked system) and trigger diag308 with a proper subcode.
- Get rid of superfluous ipib_info structure in stage2dump.c.
- Collect ipl_pbt constants in boot/ipl.h header.

Signed-off-by: Mikhail Zaslonko <zaslonko@linux.ibm.com>
Acked-by: Marc Hartmayer <mhartmay@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2023-07-20 21:45:27 +02:00
Mikhail Zaslonko
2b7df1fcac include/boot: Rename DIAG308 constants
Add DIAG308_LOAD_NORMAL diag308 subcode (used by FCP/NVMe normal ipl).
Rename other diag308_subcode and diag308_rc constants to be in sync with
kernel naming.

Signed-off-by: Mikhail Zaslonko <zaslonko@linux.ibm.com>
Reviewed-by: Alexander Egorenkov <egorenar@linux.ibm.com>
Reviewed-by: Marc Hartmayer <mhartmay@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2023-07-20 21:45:27 +02:00
Mikhail Zaslonko
53eccc0a3d zipl/boot: Update struct os_info with new OS_INFO_FLAGS_ENTRY
Introduce new os_info flags entry pointing to the field with bit flags.
The flag OS_INFO_FLAG_REIPL_CLEAR indicates that 'clear' sysfs attribute
has been set on a panicked system.

Signed-off-by: Mikhail Zaslonko <zaslonko@linux.ibm.com>
Reviewed-by: Marc Hartmayer <mhartmay@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2023-07-20 21:45:27 +02:00
Mikhail Zaslonko
951cf9d7b0 zipl/boot: Reuse os_info_valid() in kdump_os_info_check()
Signed-off-by: Mikhail Zaslonko <zaslonko@linux.ibm.com>
Reviewed-by: Marc Hartmayer <mhartmay@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2023-07-20 21:45:27 +02:00
Mikhail Zaslonko
87136bb0d0 zipl/boot: Introduce os_info validation API
- Add os_info_check() function to verify os_info address, magic and
  checksum.
- Add os_info_entry_is_valid() function to check whether requested entry
  is present and valid.

Signed-off-by: Mikhail Zaslonko <zaslonko@linux.ibm.com>
Acked-by: Marc Hartmayer <mhartmay@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2023-07-20 21:45:27 +02:00
Mikhail Zaslonko
eb06ebe245 include/boot: Move zipl/boot/error.h to include/boot
- Move zipl/boot/error.h to include/boot
- Adjust include statements in zipb/boot and genprotimg/boot
- Remove error.h from tunedasd/src/tunedasd.c as not needed
- Fix tunedasd/src/Makefile

Signed-off-by: Mikhail Zaslonko <zaslonko@linux.ibm.com>
Reviewed-by: Marc Hartmayer <mhartmay@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2023-07-20 21:45:27 +02:00
Mikhail Zaslonko
b06ca88cd4 zipl/boot: Make kdump_os_info_check() argument a const.
Make 'struct os_info *os_info' a const to ensure/indicate that no changes
are made to the given structure.

Signed-off-by: Mikhail Zaslonko <zaslonko@linux.ibm.com>
Reviewed-by: Marc Hartmayer <mhartmay@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2023-07-20 21:45:27 +02:00
Mikhail Zaslonko
5af2e30d9a zipl/boot: Move struct os_info to the separate header
- Move struct os_info from kdump.h to the new header os_info.h.
- Place os_info.h to include/boot in order to use it in zgetdump
  code as well.
- Replace hardcoded value of OS_INFO_CSUM_SIZE with a properly
  calculated one.
- Rename os_info_check() of kdump.h to kdump_os_info_check().

Signed-off-by: Mikhail Zaslonko <zaslonko@linux.ibm.com>
Reviewed-by: Marc Hartmayer <mhartmay@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2023-07-20 21:45:27 +02:00
Mete Durlu
ea3529e624 hyptop: allow users to set speedup factor
While calculating real CPU SMT utilization, the SMT speedup factor needs
to be taken into account. Speedup factor depends on machine generations
and variations on workload the machine has. The users should be able to
determine the value according to their needs.

Signed-off-by: Mete Durlu <meted@linux.ibm.com>
Reviewed-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2023-07-20 21:45:22 +02:00
Mete Durlu
0209c11bc1 hyptop: Add real SMT utilization field
By using core utilization, thread utilization, and management
utilization, it is possible to determine how much capacity is left or
how much the real CPU SMT utilization is on lpars. Extending hyptop
with this new field provides useful information.

For more info about real CPU SMT utilization:
https://linux.mainframe.blog/smt_utilization/

Briefly:
ur = real SMT util
uc = core util
ut = thread util
um = *management util
s  = **speedup factor

	ur = ((uc * per_core_thr_count) - ut) / s + (ut - uc) + um

* management utilization:
logical core time spent on hypervisor instead of logical partition.
** speedup factor:
metric used to calculate the SMT utilization on that logical core. This
value varies depending on the workload and the machine generation due
to hardware optimization level.

Signed-off-by: Mete Durlu <meted@linux.ibm.com>
Reviewed-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2023-07-20 21:45:16 +02:00
Dan Horák
6a24660472 libekmfweb: fix permissions for /usr/include/ekmfweb
Closes: https://github.com/ibm-s390-linux/s390-tools/pull/153
Signed-off-by: Dan Horák <dan@danny.cz>
Reviewed-by: Ingo Franzki <ifranzki@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2023-07-13 11:04:13 +02:00
Dan Horák
a3bc87d87d libkmipclient: fix permissions for /usr/include/kmipclient
GitHub-ID: https://github.com/ibm-s390-linux/s390-tools/pull/153
Signed-off-by: Dan Horák <dan@danny.cz>
Reviewed-by: Ingo Franzki <ifranzki@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2023-07-13 11:04:13 +02:00
Dan Horák
85493a2581 zdev: silence "maybe uninitialized" warning in lszdev.c
The compiler doesn't fully understand the code block that precedes the
usage of `site` in the condition and thus it thinks it could be
uninitialized. Silence the warning with an explicit initialization.

In function 'get_site_from_pers',
    inlined from 'dev_table_get_value' at lszdev.c:1079:10:
lszdev.c:258:20: warning: 'site' may be used uninitialized [-Wmaybe-uninitialized]
  258 |                 if (site == SITE_FALLBACK)
      |                    ^
lszdev.c: In function 'dev_table_get_value':
lszdev.c:234:13: note: 'site' was declared here
  234 |         int site, i, num = 0;
      |             ^~~~

Closes: https://github.com/ibm-s390-linux/s390-tools/pull/152
Signed-off-by: Dan Horák <dan@danny.cz>
Reviewed-by: Marc Hartmayer <mhartmay@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2023-07-13 11:04:13 +02:00
Dan Horák
ad544565fe zipl: silence "maybe uninitialized" warning in scan.c
The compiler doesn't fully understand the code block that precedes the
usage of title_off in the condition and thus it thinks it could be
uninitialized. Silence the warning with an explicit initialization.

  CC       zipl/src/scan.o
In function ‘sort_bls_fields’,
    inlined from ‘scan_bls’ at scan.c:1502:8:
scan.c:874:12: warning: ‘title_off’ may be used uninitialized [-Wmaybe-uninitialized]
  874 |         if (title_off == 0)
      |            ^
scan.c: In function ‘scan_bls’:
scan.c:842:16: note: ‘title_off’ was declared here
  842 |         size_t title_off;
      |                ^~~~~~~~~

GitHub-ID: https://github.com/ibm-s390-linux/s390-tools/pull/152
Signed-off-by: Dan Horák <dan@danny.cz>
Reviewed-by: Marc Hartmayer <mhartmay@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2023-07-13 11:04:13 +02:00
Steffen Eiden
5e135a9daf Prepare for next release
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
2023-07-11 13:15:19 +02:00
169 changed files with 10538 additions and 291 deletions

5
.codespell.ignore Normal file
View File

@@ -0,0 +1,5 @@
parm
parms
crate
ser
deriver

View File

@@ -1,5 +1,4 @@
[codespell]
ignore-words-list = parm,parms
skip = ''
ignore-words = .codespell.ignore
count = ''
quiet-level = 3

View File

@@ -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]
max_line_length = 72

11
.gitignore vendored
View File

@@ -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
@@ -50,8 +56,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 +102,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 +122,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 +135,3 @@ zkey/kmip/zkey-kmip.so
zkey/zkey
zkey/zkey-cryptsetup
zpcictl/zpcictl
**/.detect-openssl.dep.c

12
.rustfmt.toml Normal file
View 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

View File

@@ -1,6 +1,32 @@
Release history for s390-tools (MIT version)
--------------------------------------------
* __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

View File

@@ -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)

View File

@@ -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.

View File

@@ -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

View File

@@ -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 = 29
PATCHLEVEL = 0
DISTRELEASE = build-$(shell date +%Y%m%d)
S390_TOOLS_RELEASE = $(VERSION).$(RELEASE).$(PATCHLEVEL)-$(DISTRELEASE)
@@ -89,17 +93,17 @@ 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 $(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))
endef
@@ -118,12 +122,16 @@ endif
$(call define_toolchain_variables,_FOR_BUILD,)
$(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:

View File

@@ -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,

View File

@@ -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)"

View File

@@ -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,14 @@ 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 $< $@
%.bin: %.elf
$(OBJCOPY) -O binary $< $@
@chmod a-x $@
@@ -89,7 +94,7 @@ else
# `-include $(dependencies_c)` statement).
.PHONY: $(dependencies_c)
$(FILES):
$(FILES) $(DEBUG_FILES):
echo " SKIP $@ due to HOST_ARCH != s390x"
install:
@@ -97,9 +102,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

View File

@@ -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;

View File

@@ -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;

View File

@@ -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

View File

@@ -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)

View File

@@ -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);

View File

@@ -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)
*/

View File

@@ -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 */

View File

@@ -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

View File

@@ -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,

View File

@@ -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 = G0(thread_per_core * core_us - thr_us);
if (thread_per_core > 1)
component1 /= smt_factor;
component2 = G0(thr_us - core_us);
smt_us = component1 + component2 + mgm_us;
return smt_us;
}

View File

@@ -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

View File

@@ -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

View File

@@ -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;
};
/*

View File

@@ -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;

View File

@@ -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;

View File

@@ -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,

View File

@@ -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),

View File

@@ -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 */

View File

@@ -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;

78
include/boot/os_info.h Normal file
View 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 */

View File

@@ -285,18 +285,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)

View File

@@ -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

View File

@@ -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)

View File

@@ -121,7 +121,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)

View File

@@ -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

View File

@@ -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

View File

@@ -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);
}

View File

@@ -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;

View File

@@ -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");
}

View File

@@ -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)

View File

@@ -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 */

View File

@@ -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);

14
rust/.gitignore vendored Normal file
View File

@@ -0,0 +1,14 @@
# 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
# Ignore lock files by default
Cargo.lock

131
rust/Makefile Normal file
View File

@@ -0,0 +1,131 @@
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_TARGETS)) pv
endif #LIBCURL
endif #OPENSSL
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)
$(CARGO_TARGETS): .check-cargo .no-cross-compile
$(CARGO_BUILD) --manifest-path=$@/Cargo.toml $(ALL_CARGOFLAGS)
.PHONY: $(CARGO_TARGETS)
$(CARGO_TEST_TARGETS): .check-cargo .no-cross-compile
$(CARGO_TEST) --manifest-path=$@/Cargo.toml --all-features $(CARGOFLAGS)
.PHONY: $(CARGO_TEST_TARGETS)
$(PV_TARGETS): .check-cargo .no-cross-compile .check-dep-pvtools
$(CARGO_BUILD) --manifest-path=$@/Cargo.toml $(ALL_CARGOFLAGS)
.PHONY: $(PV_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:
$(foreach target,$(CARGO_TARGETS),\
$(CARGO_CLEAN) --manifest-path=$(target)/Cargo.toml ${ALL_CARGOFLAGS} ;)
$(foreach target,$(PV_TARGETS),\
$(CARGO_CLEAN) --manifest-path=$(target)/Cargo.toml ${CARGOFLAGS} ;)
$(RM) -- .check-dep-pvtools .detect-openssl.dep.c .check-cargo
rust-test: .check-cargo .no-cross-compile
$(foreach target,$(CARGO_TEST_TARGETS),\
$(CARGO_TEST) --manifest-path=$(target)/Cargo.toml --all-features ${ALL_CARGOFLAGS} ;)
install-rust-tools: $(BUILD_TARGETS)
$(INSTALL) -d -m 755 $(DESTDIR)$(USRBINDIR)
$(foreach target,$(CARGO_TARGETS),\
$(INSTALL) $(target)/target/release/$(target) $(DESTDIR)$(USRBINDIR);)
$(foreach target,$(PV_TARGETS),\
$(INSTALL) $(target)/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, \
"$(BIN_PROGRAM)", \
$^, \
"openssl-devel / libssl-dev version >= 1.1.1", \
"HAVE_OPENSSL=0", \
"-I.")
$(call check_dep, \
"$(BIN_PROGRAM)", \
"curl/curl.h", \
"libcurl-devel", \
"HAVE_LIBCURL=0")
touch $@

128
rust/README.md Normal file
View File

@@ -0,0 +1,128 @@
# 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 significaltly longer than C code. To closely
monitor the prgress 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.
Tpp: 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 Guidlines, 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
```
### 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"), // collapes into the crates name
release_string!() // this (very likely) collapes 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.

36
rust/pv/Cargo.toml Normal file
View File

@@ -0,0 +1,36 @@
[package]
name = "pv"
version = "0.9.0"
edition = "2021"
license = "MIT"
[dependencies]
libc = "0.2"
log = { version = "0.4", features = ["std", "release_max_level_debug"] }
thiserror = "1"
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", optional = true }
openssl = {version = "0.10", optional = true }
openssl_extensions = { path = "openssl_extensions", optional = true }
serde = { version = "1", features = ["derive"], optional = true }
# misc optional dependencies
byteorder = {version = "1", optional = true }
[dev-dependencies]
mockito = {version = "0.31", default-features = false }
serde_test = "1"
lazy_static = "1"
[features]
default = []
request = ["dep:openssl", "dep:curl", "dep:openssl_extensions", "dep:serde", "dep:clap"]
uvsecret = ["dep:byteorder", "dep:serde"]
[profile.release]
lto = true
panic = "abort" # release builds now do not clean up stack after panics. .1 Mb

View File

@@ -0,0 +1,12 @@
[package]
name = "openssl_extensions"
version = "0.1.0"
edition = "2021"
license = "MIT"
[dependencies]
foreign-types = "0.3"
libc = {version = "0.2", features = [ "extra_traits"] }
log = { version = "0.4", features = ["std", "release_max_level_debug"] }
openssl = "0.10"
openssl-sys = "0.9"

View 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");
}
}
}

View 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);
}
}

View 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))
}
}
}
}
}

View 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");
}

View 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())
}
}
}

View File

@@ -0,0 +1 @@
../../tests/assets

247
rust/pv/src/brcb.rs Normal file
View 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.clone())).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.clone();
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
View 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
View 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
View 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
View 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
View 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
View 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.clone()),
];
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
View 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 {
dst = dst.add(1);
std::ptr::write_volatile(dst, 0)
}
}
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
View 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
View 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
View 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
View 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)
}

View 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"),
}
}
}

View 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
View 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)
}

View 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());
}
}

View 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
}

View 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.clone()).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,
],
);
}
}

View 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, "{:02x}", b)?;
}
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
View 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
View 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,
})
}
}

View 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(&param)?;
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(&vec!(ibm_crt.clone())).is_ok());
assert!(matches!(
super::get_ibm_z_sign_key(&vec!(ibm_crt.clone(), ibm_crt.clone())),
Err(Error::HkdVerify(ManyIbmSignKeys))
));
assert!(matches!(
super::get_ibm_z_sign_key(&vec!(ibm_wrong_subj)),
Err(Error::HkdVerify(NoIbmSignKey))
));
assert!(matches!(
super::get_ibm_z_sign_key(&vec!(no_sign_crt.clone())),
Err(Error::HkdVerify(NoIbmSignKey))
));
assert!(super::get_ibm_z_sign_key(&vec!(ibm_crt.clone(), no_sign_crt.clone())).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()
));
}
}

282
rust/pv/src/verify/test.rs Normal file
View File

@@ -0,0 +1,282 @@
// SPDX-License-Identifier: MIT
//
// Copyright IBM Corp. 2023
#![cfg(test)]
use super::{helper, helper::*, *};
use crate::{Error, HkdVerifyErrorType::*};
use core::slice;
use openssl::stack::Stack;
use crate::test_utils::*;
pub fn mock_endpt(res: &str) -> mockito::Mock {
let res_path = get_cert_asset_path(res);
mockito::mock("GET", format!("/crl/{res}").as_str())
.with_header("content-type", "application/pkix-crl")
.with_body_from_file(res_path)
.create()
}
#[track_caller]
fn verify_sign_error(exp_raw: libc::c_int, obs: Error) {
verify_sign_error_slice(&[exp_raw], obs)
}
fn verify_sign_error_slice(exp_raw: &[libc::c_int], obs: Error) {
if exp_raw
.into_iter()
.filter(|e| match &obs {
Error::HkdVerify(ty) => match ty {
IbmSignInvalid(err, _d) => &&err.as_raw() == e,
_ => false,
},
e => panic!("Unexpected error type: {e:?}"),
})
.count()
== 0
{
panic!("Error {obs:?} did not match one of the expected {exp_raw:?}");
}
}
impl std::fmt::Debug for CertVerifier {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str("CertVerifier")
}
}
#[test]
fn store_setup() {
let ibm_str = get_cert_asset_path_string("ibm.crt");
let inter_str = get_cert_asset_path_string("inter.crt");
let store = helper::store_setup(&None, &vec![], &vec![ibm_str, inter_str]);
assert!(store.is_ok());
}
#[test]
fn verify_chain_online() {
let ibm_crt = load_gen_cert("ibm.crt");
let inter_crt = load_gen_cert("inter_ca.crt");
let root_crt = get_cert_asset_path_string("root_ca.chained.crt");
let mock_inter = mock_endpt("inter_ca.crl");
let mut store = helper::store_setup(&Some(root_crt), &vec![], &vec![]).unwrap();
download_crls_into_store(&mut store, slice::from_ref(&ibm_crt)).unwrap();
let store = store.build();
mock_inter.assert();
let mut sk = Stack::<X509>::new().unwrap();
sk.push(inter_crt).unwrap();
verify_chain(&store, &sk, &vec![ibm_crt.clone()]).unwrap();
assert!(verify_chain(&store, &sk, &vec!(ibm_crt)).is_ok());
}
#[test]
fn verify_chain_offline() {
let ibm_crt = load_gen_cert("ibm.crt");
let inter_crl = get_cert_asset_path_string("inter_ca.crl");
let inter_crt = load_gen_cert("inter_ca.crt");
let root_crt = get_cert_asset_path_string("root_ca.chained.crt");
let store = helper::store_setup(&Some(root_crt), &vec![inter_crl], &vec![])
.unwrap()
.build();
let mut sk = Stack::<X509>::new().unwrap();
sk.push(inter_crt).unwrap();
assert!(verify_chain(&store, &sk, &vec![ibm_crt]).is_ok());
}
#[test]
fn verify_online() {
let root_crt = get_cert_asset_path_string("root_ca.chained.crt");
let inter_crt = get_cert_asset_path_string("inter_ca.crt");
let ibm_crt = get_cert_asset_path_string("ibm.crt");
let hkd_revoked = load_gen_cert("host_rev.crt");
let hkd_inv = load_gen_cert("host_invalid_signing_key.crt");
let hkd_exp = load_gen_cert("host_crt_expired.crt");
let hkd = load_gen_cert("host.crt");
let mock_inter = mock_endpt("inter_ca.crl");
let mock_ibm = mock_endpt("ibm.crl");
let inter_crl = get_cert_asset_path_string("inter_ca.crl");
let ibm_crl = get_cert_asset_path_string("ibm.crl");
let verifier = CertVerifier::new(
&vec![ibm_crt, inter_crt],
&vec![ibm_crl, inter_crl],
&Some(root_crt),
false,
)
.unwrap();
mock_inter.assert();
verifier.verify(&hkd).unwrap();
mock_ibm.assert();
assert!(matches!(
verifier.verify(&hkd_revoked),
Err(Error::HkdVerify(HdkRevoked))
));
assert!(matches!(
verifier.verify(&hkd_inv),
Err(Error::HkdVerify(IssuerMismatch))
));
assert!(matches!(
verifier.verify(&hkd_exp),
Err(Error::HkdVerify(AfterValidity))
));
}
#[test]
fn verify_offline() {
let root_crt = get_cert_asset_path_string("root_ca.chained.crt");
let inter_crt = get_cert_asset_path_string("inter_ca.crt");
let inter_crl = get_cert_asset_path_string("inter_ca.crl");
let ibm_crt = get_cert_asset_path_string("ibm.crt");
let ibm_crl = get_cert_asset_path_string("ibm.crl");
let hkd_revoked = load_gen_cert("host_rev.crt");
let hkd_inv = load_gen_cert("host_invalid_signing_key.crt");
let hkd_exp = load_gen_cert("host_crt_expired.crt");
let hkd = load_gen_cert("host.crt");
let verifier = CertVerifier::new(
&vec![ibm_crt, inter_crt],
&vec![ibm_crl, inter_crl],
&Some(root_crt),
true,
)
.unwrap();
verifier.verify(&hkd).unwrap();
assert!(matches!(
verifier.verify(&hkd_revoked),
Err(Error::HkdVerify(HdkRevoked))
));
assert!(matches!(
verifier.verify(&hkd_inv),
Err(Error::HkdVerify(IssuerMismatch))
));
assert!(matches!(
verifier.verify(&hkd_exp),
Err(Error::HkdVerify(AfterValidity))
));
}
#[test]
fn verifier_new() {
let root_chn_crt = get_cert_asset_path_string("root_ca.chained.crt");
let root_crt = get_cert_asset_path_string("root_ca.crt");
let inter_crt = get_cert_asset_path_string("inter_ca.crt");
let inter_fake_crt = get_cert_asset_path_string("fake_inter_ca.crt");
let inter_fake_crl = get_cert_asset_path_string("fake_inter_ca.crl");
let inter_crl = get_cert_asset_path_string("inter_ca.crl");
let ibm_crt = get_cert_asset_path_string("ibm.crt");
let ibm_early_crt = get_cert_asset_path_string("ibm_outdated_early.crl");
let ibm_late_crt = get_cert_asset_path_string("ibm_outdated_late.crl");
let ibm_rev_crt = get_cert_asset_path_string("ibm_rev.crt");
// To many signing keys
let verifier = CertVerifier::new(
&vec![ibm_crt.clone(), ibm_rev_crt.clone()],
&vec![],
&None,
true,
);
assert!(matches!(verifier, Err(Error::HkdVerify(ManyIbmSignKeys))));
// no CRL for each X509
let verifier = CertVerifier::new(
&vec![inter_crt.clone(), ibm_crt.clone()],
&vec![inter_crl.clone()],
&Some(root_crt.clone()),
false,
);
verify_sign_error(3, verifier.unwrap_err());
let verifier = CertVerifier::new(
&vec![inter_crt.clone(), ibm_crt.clone()],
&vec![],
&Some(root_chn_crt.clone()),
false,
);
verify_sign_error(3, verifier.unwrap_err());
// wrong intermediate (or ibm key)
let verifier = CertVerifier::new(
&vec![inter_fake_crt, ibm_crt.clone()],
&vec![inter_fake_crl],
&Some(root_chn_crt.clone()),
true,
);
//Depending on the OpenSSL version different error codes can appear
verify_sign_error_slice(&[20, 30], verifier.unwrap_err());
//wrong root ca
let verifier = CertVerifier::new(
&vec![inter_crt.clone(), ibm_crt.clone()],
&vec![inter_crl.clone()],
&None,
true,
);
verify_sign_error(20, verifier.unwrap_err());
//correct signing key + intermediate cert
let _verifier = CertVerifier::new(
&vec![inter_crt.clone(), ibm_crt.clone()],
&vec![inter_crl.clone()],
&Some(root_chn_crt.clone()),
false,
)
.unwrap();
// no intermediate key
let verifier = CertVerifier::new(
&vec![ibm_crt.clone()],
&vec![],
&Some(root_chn_crt.clone()),
false,
);
verify_sign_error(20, verifier.unwrap_err());
//Ibm Sign outdated
let verifier = CertVerifier::new(
&vec![inter_crt.clone(), ibm_early_crt.clone()],
&vec![inter_crl.clone()],
&Some(root_chn_crt.clone()),
false,
);
assert!(matches!(verifier, Err(Error::HkdVerify(NoIbmSignKey))));
let verifier = CertVerifier::new(
&vec![inter_crt.clone(), ibm_late_crt.clone()],
&vec![inter_crl.clone()],
&Some(root_chn_crt.clone()),
false,
);
assert!(matches!(verifier, Err(Error::HkdVerify(NoIbmSignKey))));
// revoked
let verifier = CertVerifier::new(
&vec![inter_crt.clone(), ibm_rev_crt.clone()],
&vec![inter_crl.clone()],
&Some(root_chn_crt.clone()),
false,
);
verify_sign_error(23, verifier.unwrap_err());
}
#[test]
fn dist_points() {
let crt = load_gen_cert("ibm.crt");
let res = x509_dist_points(&crt);
let exp = vec!["http://127.0.0.1:1234/crl/inter_ca.crl"];
assert_eq!(res, exp);
}

View File

@@ -0,0 +1,164 @@
// SPDX-License-Identifier: MIT
//
// Copyright IBM Corp. 2023
#![cfg(all(feature = "request", feature = "uvsecret"))]
use pv::{
get_test_asset,
request::{
openssl::pkey::{PKey, Public},
uvsecret::{AddSecretFlags, AddSecretRequest, AddSecretVersion, ExtSecret, GuestSecret},
BootHdrTags, ReqEncrCtx, Request, SymKey,
},
test_utils::get_test_keys,
uv::ConfigUid,
Result,
};
const TAGS: BootHdrTags = BootHdrTags::new([1; 64], [2; 64], [3; 64], [4; 16]);
const CUID: ConfigUid = [0x42u8; 16];
const ASSOC_SECRET: [u8; 32] = [0x11; 32];
const ASSOC_ID: &'static str = "add_secret_request";
fn create_asrcb(
guest_secret: GuestSecret,
ext_secret: Option<ExtSecret>,
flags: AddSecretFlags,
cuid: Option<ConfigUid>,
hkd: PKey<Public>,
ctx: &ReqEncrCtx,
) -> Result<Vec<u8>> {
let mut asrcb = AddSecretRequest::new(AddSecretVersion::One, guest_secret, TAGS, flags);
if let Some(s) = ext_secret {
asrcb.set_ext_secret(s)?
};
if let Some(c) = cuid {
asrcb.set_cuid(c);
};
asrcb.add_hostkey(hkd);
Ok(asrcb.encrypt(ctx)?)
}
fn get_crypto() -> (PKey<Public>, ReqEncrCtx) {
let (cust_key, host_key) = get_test_keys();
let ctx = ReqEncrCtx::new_aes_256(
Some([0x55; 12]),
Some(cust_key),
Some(SymKey::Aes256([0x17; 32].into())),
)
.unwrap();
(host_key, ctx)
}
fn gen_asrcb<E>(
guest_secret: GuestSecret,
ext_secret: E,
flags: AddSecretFlags,
cuid: bool,
) -> Result<Vec<u8>>
where
E: Into<Option<ExtSecret>>,
{
let (host_key, ctx) = get_crypto();
let cuid = match cuid {
true => Some(CUID.into()),
false => None,
};
create_asrcb(
guest_secret,
ext_secret.into(),
flags,
cuid.into(),
host_key,
&ctx,
)
}
fn association() -> GuestSecret {
GuestSecret::association(ASSOC_ID, ASSOC_SECRET).unwrap()
}
fn ext_simple() -> ExtSecret {
ExtSecret::Simple([0x17; 32].into())
}
fn ext_derived() -> ExtSecret {
ExtSecret::Derived([0; 32].into())
}
fn no_flag() -> AddSecretFlags {
AddSecretFlags::default()
}
#[test]
fn null_none_default_cuid_one() {
let asrcb = gen_asrcb(GuestSecret::Null, None, no_flag(), true).unwrap();
let exp = get_test_asset!("exp/asrcb/null_none_default_cuid_one");
assert_eq!(asrcb, exp);
}
#[test]
fn assoc_none_default_cuid_one() {
let asrcb = gen_asrcb(association(), None, no_flag(), true).unwrap();
let exp = get_test_asset!("exp/asrcb/assoc_none_default_cuid_one");
assert_eq!(asrcb, exp);
}
#[test]
fn null_simple_default_cuid_one() {
let asrcb = gen_asrcb(GuestSecret::Null, ext_simple(), no_flag(), true).unwrap();
let exp = get_test_asset!("exp/asrcb/null_simple_default_cuid_one");
assert_eq!(asrcb, exp);
}
#[test]
fn assoc_simple_default_cuid_one() {
let asrcb = gen_asrcb(association(), ext_simple(), no_flag(), true).unwrap();
let exp = get_test_asset!("exp/asrcb/assoc_simple_default_cuid_one");
assert_eq!(asrcb, exp);
}
#[test]
fn null_derived_default_cuid_one() {
let asrcb = gen_asrcb(GuestSecret::Null, ext_derived(), no_flag(), true).unwrap();
let exp = get_test_asset!("exp/asrcb/null_derived_default_cuid_one");
assert_eq!(asrcb, exp);
}
#[test]
fn assoc_derived_default_cuid_one() {
let asrcb = gen_asrcb(association(), ext_derived(), no_flag(), true).unwrap();
let exp = get_test_asset!("exp/asrcb/assoc_derived_default_cuid_one");
assert_eq!(asrcb, exp);
}
#[test]
fn null_none_dump_cuid_one() {
let mut flags = no_flag();
flags.set_disable_dump();
let asrcb = gen_asrcb(GuestSecret::Null, None, flags, true).unwrap();
let exp = get_test_asset!("exp/asrcb/null_none_dump_cuid_one");
assert_eq!(asrcb, exp);
}
#[test]
fn null_none_default_ncuid_one() {
let asrcb = gen_asrcb(GuestSecret::Null, None, no_flag(), false).unwrap();
let exp = get_test_asset!("exp/asrcb/null_none_default_ncuid_one");
assert_eq!(asrcb, exp);
}
#[test]
fn null_none_default_cuid_seven() {
let (hkd, ctx) = get_crypto();
let mut asrcb =
AddSecretRequest::new(AddSecretVersion::One, GuestSecret::Null, TAGS, no_flag());
(0..7).for_each(|_| asrcb.add_hostkey(hkd.clone()));
asrcb.set_cuid(CUID.into());
let asrcb = asrcb.encrypt(&ctx).unwrap();
let exp = get_test_asset!("exp/asrcb/null_none_default_cuid_seven");
assert_eq!(asrcb, exp);
}

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,19 @@
-----BEGIN X509 CRL-----
MIIDITCCAQkCAQEwDQYJKoZIhvcNAQELBQAwgb0xCzAJBgNVBAYTAlVTMTQwMgYD
VQQKDCtJbnRlcm5hdGlvbmFsIEJ1c2luZXNzIE1hY2hpbmVzIENvcnBvcmF0aW9u
MTQwMgYDVQQDDCtJbnRlcm5hdGlvbmFsIEJ1c2luZXNzIE1hY2hpbmVzIENvcnBv
cmF0aW9uMREwDwYDVQQIDAhOZXcgWW9yazEPMA0GA1UEBwwGQXJtb25rMR4wHAYD
VQQLDBVJQk0gWiBJbnRlcm1lZGlhdGUgQ0EXDTIzMDMxOTExMDQ0N1oYDzIzODcx
MjMxMTEwNDQ3WjAVMBMCAgG8Fw0yMzAzMjgxMTA0NDdaMA0GCSqGSIb3DQEBCwUA
A4ICAQA+KQrjx/6nKzPggDpKEAzH6XxhUU4CZmyUKFirUdenQBjYLag2Nono75o8
9DVK3vuK7aeg4tIkUYOBcOEgYx0wEPU6PEE+yOO6KErn7qilN9gRnDHOTrfT0iNY
Rabyeat256gyfrsS84ZB7MnbhecWwC6sP2NiH18VCprH865X6sm5SxAfez1zITV3
YVtudX4UczqbfpDgP4BU5ERMI71tqj4gKjaHFkC0TGizSphiINDKPmUMbd1w7FHg
Ilj+7pNZS377GCX9JzoTaKLuMBiblkwSTUJic7Z2BJZlTgm18hhfT3AZQDVvkq0A
AEPxQzm3be3ZUb+zJvueTeizVHkd3Eufnk69p7w4wNRQMwfj/icm27RZa3bBpF2o
esr2Ptik9ZBN81oMAakONZ4Wxuf0n/KBd6VBjy6WkbalKGVoZn70Nke3+9HGWSIh
bgbuHt7XAlvsgVChtZWsGsyxYw4p2ku4T2ajUfpxqQY1DDCAThweuHl2FND87Kr1
5sblLLhA3QdjQ0EavsCV1646xorvoyw7YdHkqCPjRb1FsPWm/IePbtu+w9/VjDRF
KcHgBZBWmmQHj/9ykSI9pA5J7R7Nij6sX6Iu1g2yKiPnXeRQFiwhgsxslNk8eJfq
cK4c4HhnNtXa/c8jHcbymwqkF8Qltz0cbEW1usxZ2u6153pyPQ==
-----END X509 CRL-----

View File

@@ -0,0 +1,38 @@
-----BEGIN CERTIFICATE-----
MIIGpjCCBI6gAwIBAgIUMp+RLATMshrQnbOfPkwKoDNyYhcwDQYJKoZIhvcNAQEL
BQAwgbUxCzAJBgNVBAYTAlVTMTQwMgYDVQQKDCtJbnRlcm5hdGlvbmFsIEJ1c2lu
ZXNzIE1hY2hpbmVzIENvcnBvcmF0aW9uMTQwMgYDVQQDDCtJbnRlcm5hdGlvbmFs
IEJ1c2luZXNzIE1hY2hpbmVzIENvcnBvcmF0aW9uMREwDwYDVQQIDAhOZXcgWW9y
azEPMA0GA1UEBwwGQXJtb25rMRYwFAYDVQQLDA1JQk0gWiBSb290IENBMCAXDTIz
MDMyOTA5MDQ0N1oYDzIzODcxMjMxMDkwNDQ3WjCBvTELMAkGA1UEBhMCVVMxNDAy
BgNVBAoMK0ludGVybmF0aW9uYWwgQnVzaW5lc3MgTWFjaGluZXMgQ29ycG9yYXRp
b24xNDAyBgNVBAMMK0ludGVybmF0aW9uYWwgQnVzaW5lc3MgTWFjaGluZXMgQ29y
cG9yYXRpb24xETAPBgNVBAgMCE5ldyBZb3JrMQ8wDQYDVQQHDAZBcm1vbmsxHjAc
BgNVBAsMFUlCTSBaIEludGVybWVkaWF0ZSBDQTCCAiIwDQYJKoZIhvcNAQEBBQAD
ggIPADCCAgoCggIBAK75zkJO8mpqPrD9vlSsfJgW7hbioQpuphSo1+3q9cAAWKFg
TYUcGBUNR/lUVdYqgzo3AglUwldWfeO9mBCIGNSN/heLFt1KzNutBsnE3YEeGKpM
7nHhMzh41otFdpZEZfrsGXGok07dy2mEV0mx72e9ALWbXFhxYsdWdSSVTlBH8xcd
38rAzfAiTbjgAUnTIdCPjAJKbXSDBXGXZ3+iuhFxNtSWyJr1AsxPzESErCPzUQjr
m8TM24lKq69zimTEkN4uwP5U8s2JPzbKosg2k24RbpDgkjO8iNK7RL9SMRUE8daP
+eru5EwN4BlZfsNpZDFbILxbt/2sxqmdsx/Nupa5ZAfcHRs88p4l1D3QIiZzaSEc
nCotM/kmnHWbgeJbkGbC9fD23dNJ29uqZU0fbRnG4HpSutrYD6lPg7PXnMt5tT+f
0+wQds38woXT9qW/kN/2WtkVYDhyVjxCgD8iHOZpz2LUmvJfi7Gz9B/DeW1dzgbo
cGxz9ee+R+T5KcKg+XvHD6slk82GrSM21b7zJeK92bJtjkqxBtQf+YgcKtOO7QX7
37C1XvSHFnKvyyRJrldJFGEKfK2C66hdASHRdbUhWHFo1AA7VqzKB1fU9M2+ltUZ
zRpYD7X36OtRY1KsHHn+SVvsn404hWwgPblZ04nsMPanj+jsN//6M9r5lMezAgMB
AAGjgaEwgZ4wOwYDVR0fBDQwMjAwoC6gLIYqaHR0cDovLzEyNy4wLjAuMToxMjM0
L2NybC9mYWtlX3Jvb3RfY2EuY3JsMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/
BAQDAgEGMB8GA1UdIwQYMBaAFNnX872GsEyiVMz7up1p/Nq/K+0VMB0GA1UdDgQW
BBT2zEtzeetYaOXAVw0Z9Pda1V9n1DANBgkqhkiG9w0BAQsFAAOCAgEAb7xPI93b
gqpXi4E/hAl7Vh354kKV+1smd1Omf7mQZbTrKBo6o1s0ZpXCbC9+WzK47R21VPNe
EgZ3uzTkURu4AX+5OnAMiWDFAGRSDt8ZXk6ZukWP7OmHsnLCu2strdhrvC4EhMF5
G9VPIQsTx16CpprrVjzVzJg4i/X+U9dypvnAQeneyz4Ul/kPr6di8bOB3FiBeEDu
dOkVTbnlDa+wMQlCvqlFroFjEHZBKK/+PVIx9cJYj1grmzgqzm2FGUs5Wvcixpgb
uSHCQY9JP8Hy0xl3wx58VaymUK4EMfs612CfzOMClaiooDYuZgzVfhalU6g268nc
PQ9RCRJtJuda4mJ2H3Rag79sIiCVV31tE6tLXjOGebuO0vEB8wOjJc4YW1gtrZFy
GltT+HMdFgjO2c6HynpkmtqS8axQAw2hVaOpdJbDW+R0jHihO98FAgXR27TqTFp9
sjBacfITeYRXGjQTDU1qxuEfoLnTZRIct1TjRTI1HBT8fl1exxgKUyEawH3MUCo7
LsTPSNASKkJH3Rp29be9xTejUx2HUUwOE/DIF0HKaN+aAc8TR31/4HvC3bf5VPyD
wIwazpjVDZlQ2w+Wry1zNezNCPKiWtkfkj+TkT32h4ZfQEX8t0MUpKnL1LOSV/8h
PV357EenQb+f9DeC4BIbmipovLaUWmml10c=
-----END CERTIFICATE-----

View File

@@ -0,0 +1,4 @@
*.key
*.crt
*.crl
gen_venv/*

View File

@@ -0,0 +1,7 @@
cert: clean
bash create_certs.sh
clean:
bash clean.sh
.PHONY: clean cert

View File

@@ -0,0 +1,2 @@
#!/bin/bash
rm -f -- *.key *.crt *.crl

View File

@@ -0,0 +1,733 @@
#!/bin/env python3
import datetime
import os.path
from enum import Enum
from cryptography import x509
from cryptography.x509.oid import NameOID
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric import ec
from cryptography.hazmat.primitives.asymmetric import rsa
ONE_DAY = datetime.timedelta(1, 0, 0)
def createEcKeyPair(curve=ec.SECP521R1):
return ec.generate_private_key(curve=curve, backend=default_backend())
def createRSAKeyPair(size=4096):
return rsa.generate_private_key(
public_exponent=65537, key_size=size, backend=default_backend()
)
def createCRL(pkey, issuer, serial_numbers=None, last_update=None, next_update=None):
serial_numbers = [333] if serial_numbers is None else serial_numbers
builder = x509.CertificateRevocationListBuilder()
builder = builder.issuer_name(issuer)
last_update = last_update or datetime.datetime.today() - 10 * ONE_DAY
next_update = next_update or datetime.datetime.today() + 365 * 365 * ONE_DAY
builder = builder.last_update(last_update)
builder = builder.next_update(next_update)
for sn in serial_numbers:
revoked_cert = (
x509.RevokedCertificateBuilder()
.serial_number(sn)
.revocation_date(
datetime.datetime.today() - ONE_DAY,
)
.build(default_backend())
)
builder = builder.add_revoked_certificate(revoked_cert)
crl = builder.sign(
private_key=pkey, algorithm=hashes.SHA256(), backend=default_backend()
)
return crl
def createRootCA(pkey, subject):
issuer = subject
ca = (
x509.CertificateBuilder()
.subject_name(subject)
.issuer_name(issuer)
.public_key(pkey.public_key())
.serial_number(x509.random_serial_number())
.not_valid_before(datetime.datetime.utcnow())
.not_valid_after(
datetime.datetime.utcnow() + datetime.timedelta(days=365 * 365)
)
.add_extension(
x509.BasicConstraints(ca=True, path_length=None),
critical=True,
# Sign our certificate with our private key
)
.add_extension(
x509.KeyUsage(
digital_signature=False,
key_encipherment=False,
content_commitment=False,
data_encipherment=False,
key_agreement=False,
encipher_only=False,
decipher_only=False,
key_cert_sign=True,
crl_sign=True,
),
critical=True,
)
.add_extension(
x509.SubjectKeyIdentifier.from_public_key(pkey.public_key()),
critical=False,
)
.sign(pkey, hashes.SHA512(), default_backend())
)
return ca
class CertType(Enum):
ROOT_CA = 1
INTER_CA = 2
SIGNING_CERT = 3
HOST_CERT = 4
def createCert(
pkey,
subject,
crl_uri,
issuer_crt=None,
issuer_pkey=None,
t=CertType.ROOT_CA,
not_before=None,
not_after=None,
pub_key=None,
):
sha = hashes.SHA256
not_before = not_before or datetime.datetime.utcnow()
not_after = not_after or datetime.datetime.utcnow() + datetime.timedelta(
days=365 * 365
)
crl_dp = None
if crl_uri is not None:
crl_dp = x509.DistributionPoint(
[x509.UniformResourceIdentifier(crl_uri)],
relative_name=None,
reasons=None,
crl_issuer=None,
)
cert_builder = x509.CertificateBuilder().subject_name(subject)
if t == CertType.ROOT_CA:
cert_builder = cert_builder.issuer_name(subject)
issuer_pub_key = pkey.public_key()
else:
cert_builder = cert_builder.issuer_name(issuer_crt.subject)
issuer_pub_key = issuer_crt.public_key()
if pub_key is None:
pub_key = pkey.public_key()
cert_builder = (
cert_builder.public_key(pub_key)
.serial_number(x509.random_serial_number())
.not_valid_before(not_before)
.not_valid_after(not_after)
)
if crl_dp is not None:
cert_builder = cert_builder.add_extension(
x509.CRLDistributionPoints([crl_dp]),
critical=False,
)
if t == CertType.ROOT_CA:
cert_builder = cert_builder.add_extension(
x509.BasicConstraints(ca=True, path_length=None),
critical=True,
).add_extension(
x509.KeyUsage(
digital_signature=False,
key_encipherment=False,
content_commitment=False,
data_encipherment=False,
key_agreement=False,
encipher_only=False,
decipher_only=False,
key_cert_sign=True,
crl_sign=True,
),
critical=True,
)
elif t == CertType.INTER_CA:
cert_builder = cert_builder.add_extension(
x509.BasicConstraints(ca=True, path_length=None),
critical=True,
).add_extension(
x509.KeyUsage(
digital_signature=False,
key_encipherment=False,
content_commitment=False,
data_encipherment=False,
key_agreement=False,
encipher_only=False,
decipher_only=False,
key_cert_sign=True,
crl_sign=True,
),
critical=True,
)
elif t == CertType.SIGNING_CERT:
cert_builder = (
cert_builder.add_extension(
x509.BasicConstraints(ca=False, path_length=None),
critical=True,
)
.add_extension(
x509.KeyUsage(
digital_signature=True,
key_encipherment=False,
content_commitment=False,
data_encipherment=False,
key_agreement=False,
encipher_only=False,
decipher_only=False,
key_cert_sign=False,
crl_sign=False,
),
critical=True,
)
.add_extension(
x509.ExtendedKeyUsage(
[x509.oid.ExtendedKeyUsageOID.CODE_SIGNING]
),
critical=False,
)
)
else:
sha = hashes.SHA512
cert_builder = cert_builder.add_extension(
x509.KeyUsage(
digital_signature=False,
key_encipherment=False,
content_commitment=False,
data_encipherment=False,
key_agreement=True,
encipher_only=False,
decipher_only=False,
key_cert_sign=False,
crl_sign=False,
),
critical=True,
)
cert_builder = cert_builder.add_extension(
x509.AuthorityKeyIdentifier.from_issuer_public_key(issuer_pub_key),
critical=False,
).add_extension(
x509.SubjectKeyIdentifier.from_public_key(pkey.public_key()),
critical=False,
)
return cert_builder.sign(issuer_pkey, sha(), default_backend())
def getPrivKey(path, create_priv_key):
pkey = None
if os.path.isfile(path):
with open(path, "rb") as f:
pkey = serialization.load_pem_private_key(
f.read(), password=None, backend=default_backend()
)
if not pkey:
pkey = create_priv_key()
with open(path, "wb") as f:
f.write(
pkey.private_bytes(
serialization.Encoding.PEM,
format=serialization.PrivateFormat.PKCS8,
encryption_algorithm=serialization.NoEncryption(),
)
)
return pkey
if __name__ == "__main__":
MOCKUP_CRL_DIST = "http://127.0.0.1:1234/crl/"
# create root CA
root_ca_subject = x509.Name(
[
x509.NameAttribute(NameOID.COUNTRY_NAME, u"US"),
x509.NameAttribute(
NameOID.ORGANIZATION_NAME,
u"International Business Machines Corporation",
),
x509.NameAttribute(
NameOID.COMMON_NAME, u"International Business Machines Corporation"
),
x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, u"New York"),
x509.NameAttribute(NameOID.LOCALITY_NAME, u"Armonk"),
x509.NameAttribute(NameOID.ORGANIZATIONAL_UNIT_NAME, u"IBM Z Root CA"),
]
)
root_ca_pkey = getPrivKey("root_ca.key", createRSAKeyPair)
root_ca_crl = createCRL(root_ca_pkey, root_ca_subject, [333])
root_ca_crt = createCert(
pkey=root_ca_pkey,
subject=root_ca_subject,
issuer_pkey=root_ca_pkey,
crl_uri=None,
t=CertType.ROOT_CA,
)
fake_root_ca_subject = x509.Name(
[
x509.NameAttribute(NameOID.COUNTRY_NAME, u"US"),
x509.NameAttribute(
NameOID.ORGANIZATION_NAME,
u"International Business Machines Corporation",
),
x509.NameAttribute(
NameOID.COMMON_NAME, u"International Business Machines Corporation"
),
x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, u"New York"),
x509.NameAttribute(NameOID.LOCALITY_NAME, u"Armonk"),
x509.NameAttribute(NameOID.ORGANIZATIONAL_UNIT_NAME, u"IBM Z Root CA"),
]
)
fake_root_ca_pkey = getPrivKey("fake_root_ca.key", createRSAKeyPair)
fake_root_ca_crl = createCRL(fake_root_ca_pkey, fake_root_ca_subject, [333])
fake_root_ca_valid_crl = createCRL(root_ca_pkey, fake_root_ca_subject, [333])
fake_root_ca_crt = createCert(
pkey=fake_root_ca_pkey,
subject=fake_root_ca_subject,
issuer_pkey=fake_root_ca_pkey,
crl_uri=None,
t=CertType.ROOT_CA,
)
fake_root_ca_crt = createRootCA(fake_root_ca_pkey, fake_root_ca_subject)
# create intermediate CA
inter_ca_pkey = getPrivKey("inter_ca.key", createRSAKeyPair)
inter_ca_subject = x509.Name(
[
x509.NameAttribute(NameOID.COUNTRY_NAME, u"US"),
x509.NameAttribute(
NameOID.ORGANIZATION_NAME,
u"International Business Machines Corporation",
),
x509.NameAttribute(
NameOID.COMMON_NAME, u"International Business Machines Corporation"
),
x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, u"New York"),
x509.NameAttribute(NameOID.LOCALITY_NAME, u"Armonk"),
x509.NameAttribute(
NameOID.ORGANIZATIONAL_UNIT_NAME, u"IBM Z Intermediate CA"
),
]
)
inter_ca_crt = createCert(
pkey=inter_ca_pkey,
subject=inter_ca_subject,
issuer_crt=root_ca_crt,
issuer_pkey=root_ca_pkey,
crl_uri=MOCKUP_CRL_DIST + "root_ca.crl",
t=CertType.INTER_CA,
)
fake_inter_ca_pkey = getPrivKey("fake_inter_ca.key", createRSAKeyPair)
fake_inter_ca_crt = createCert(
pkey=fake_inter_ca_pkey,
subject=inter_ca_subject,
issuer_crt=fake_root_ca_crt,
issuer_pkey=fake_root_ca_pkey,
crl_uri=MOCKUP_CRL_DIST + "fake_root_ca.crl",
t=CertType.INTER_CA,
)
fake_inter_ca_crl = createCRL(fake_inter_ca_pkey, inter_ca_subject, [444])
# create ibm certificate
ibm_pkey = getPrivKey("ibm.key", createRSAKeyPair)
ibm_subject = x509.Name(
[
x509.NameAttribute(NameOID.COUNTRY_NAME, u"US"),
x509.NameAttribute(
NameOID.ORGANIZATION_NAME,
u"International Business Machines Corporation",
),
x509.NameAttribute(
NameOID.COMMON_NAME, u"International Business Machines Corporation"
),
x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, u"New York"),
x509.NameAttribute(NameOID.LOCALITY_NAME, u"Poughkeepsie"),
x509.NameAttribute(
NameOID.ORGANIZATIONAL_UNIT_NAME, u"IBM Z Host Key Signing Service"
),
]
)
ibm_crt = createCert(
pkey=ibm_pkey,
subject=ibm_subject,
issuer_crt=inter_ca_crt,
issuer_pkey=inter_ca_pkey,
crl_uri=MOCKUP_CRL_DIST + "inter_ca.crl",
t=CertType.SIGNING_CERT,
)
ibm_expired_crt = createCert(
pkey=ibm_pkey,
subject=ibm_subject,
issuer_crt=inter_ca_crt,
issuer_pkey=inter_ca_pkey,
crl_uri=MOCKUP_CRL_DIST + "inter_ca.crl",
t=CertType.SIGNING_CERT,
not_before=datetime.datetime.today() - 2 * 365 * ONE_DAY,
not_after=datetime.datetime.today() - 1 * 365 * ONE_DAY,
)
#create revoked ibm certificate
ibm_rev_pkey = getPrivKey("ibm.key", createRSAKeyPair)
ibm_rev_crt = createCert(
pkey=ibm_rev_pkey,
subject=ibm_subject,
issuer_crt=inter_ca_crt,
issuer_pkey=inter_ca_pkey,
crl_uri=MOCKUP_CRL_DIST + "inter_ca.crl",
t=CertType.SIGNING_CERT,
)
# create inter CLRs
inter_ca_crl = createCRL(inter_ca_pkey, inter_ca_subject, [444, ibm_rev_crt.serial_number])
inter_ca_invalid_signer_crl = createCRL(root_ca_pkey, inter_ca_subject, [444])
inter_ca_invalid_date_crl = createCRL(
inter_ca_pkey,
inter_ca_subject,
[444],
last_update=datetime.datetime.today() - 2 * ONE_DAY,
next_update=datetime.datetime.today() - 1 * ONE_DAY,
)
# create signing key using wrong OU in subject
ibm_wrong_subject_pkey = getPrivKey("ibm_wrong_subject.key", createRSAKeyPair)
ibm_wrong_subject_subject = x509.Name(
[
x509.NameAttribute(NameOID.COUNTRY_NAME, u"US"),
x509.NameAttribute(
NameOID.ORGANIZATION_NAME,
u"International Business Machines Corporation",
),
x509.NameAttribute(
NameOID.COMMON_NAME, u"International Business Machines Corporation"
),
x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, u"New York"),
x509.NameAttribute(NameOID.LOCALITY_NAME, u"Poughkeepsie"),
x509.NameAttribute(
NameOID.ORGANIZATIONAL_UNIT_NAME, u"Key Signing Service Invalid"
),
]
)
ibm_wrong_subject_crl = createCRL(ibm_wrong_subject_pkey, ibm_wrong_subject_subject, [555])
ibm_wrong_subject_crt = createCert(
pkey=ibm_wrong_subject_pkey,
subject=ibm_wrong_subject_subject,
issuer_crt=inter_ca_crt,
issuer_pkey=inter_ca_pkey,
crl_uri=MOCKUP_CRL_DIST + "inter_ca.crl",
t=CertType.SIGNING_CERT,
)
fake_ibm_pkey = getPrivKey("fake_ibm.key", createRSAKeyPair)
fake_ibm_subject = x509.Name(
[
x509.NameAttribute(NameOID.COUNTRY_NAME, u"US"),
x509.NameAttribute(
NameOID.ORGANIZATION_NAME,
u"International Business Machines Corporation",
),
x509.NameAttribute(
NameOID.COMMON_NAME, u"International Business Machines Corporation"
),
x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, u"New York"),
x509.NameAttribute(NameOID.LOCALITY_NAME, u"Poughkeepsie"),
x509.NameAttribute(
NameOID.ORGANIZATIONAL_UNIT_NAME, u"IBM Z Host Key Signing Service"
),
]
)
fake_ibm_crl = createCRL(fake_ibm_pkey, fake_ibm_subject, [555])
fake_ibm_crt = createCert(
pkey=fake_ibm_pkey,
subject=fake_ibm_subject,
issuer_crt=fake_root_ca_crt,
issuer_pkey=fake_root_ca_pkey,
crl_uri=MOCKUP_CRL_DIST + "fake_root_ca.crl",
t=CertType.SIGNING_CERT,
)
def host_subj():
return x509.Name(
[
x509.NameAttribute(NameOID.COUNTRY_NAME, u"US"),
x509.NameAttribute(
NameOID.ORGANIZATION_NAME,
u"International Business Machines Corporation",
),
x509.NameAttribute(
NameOID.COMMON_NAME, u"International Business Machines Corporation"
),
x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, u"New York"),
x509.NameAttribute(NameOID.LOCALITY_NAME, u"Armonk"),
x509.NameAttribute(NameOID.ORGANIZATIONAL_UNIT_NAME, u"IBM Z Host Key"),
]
)
# create host certificate
host_pkey = getPrivKey("host.key", createEcKeyPair)
host_subject = host_subj()
host_crt = createCert(
pkey=host_pkey,
subject=host_subject,
issuer_crt=ibm_crt,
issuer_pkey=ibm_pkey,
crl_uri=MOCKUP_CRL_DIST + "ibm.crl",
t=CertType.HOST_CERT,
)
host_crt_expired = createCert(
pkey=host_pkey,
subject=host_subject,
issuer_crt=ibm_crt,
issuer_pkey=ibm_pkey,
crl_uri=MOCKUP_CRL_DIST + "ibm.crl",
t=CertType.HOST_CERT,
not_before=datetime.datetime.today() - 2 * 365 * ONE_DAY,
not_after=datetime.datetime.today() - 1 * 365 * ONE_DAY,
)
host_uri_na_crt = createCert(
pkey=host_pkey,
subject=host_subject,
issuer_crt=ibm_crt,
issuer_pkey=ibm_pkey,
crl_uri=MOCKUP_CRL_DIST + "notavailable",
t=CertType.HOST_CERT,
)
host_pkey = getPrivKey("host.key", createEcKeyPair)
host_subject = host_subj()
host_crt = createCert(
pkey=host_pkey,
subject=host_subject,
issuer_crt=ibm_crt,
issuer_pkey=ibm_pkey,
crl_uri=MOCKUP_CRL_DIST + "ibm.crl",
t=CertType.HOST_CERT,
)
host_rev_pkey = getPrivKey("host_rev.key", createEcKeyPair)
host_rev_subject = host_subj()
host_rev_crt = createCert(
pkey=host_rev_pkey,
subject=host_rev_subject,
issuer_crt=ibm_crt,
issuer_pkey=ibm_pkey,
crl_uri=MOCKUP_CRL_DIST + "ibm.crl",
t=CertType.HOST_CERT,
)
# some IBM revocation lists
ibm_crl = createCRL(ibm_pkey, ibm_subject, [555, host_rev_crt.serial_number])
ibm_outdated_early_crl = createCRL(
ibm_pkey,
ibm_subject,
[],
last_update=datetime.datetime.today() + 1000 * 365 * ONE_DAY,
next_update=datetime.datetime.today() + 1001 * 365 * ONE_DAY,
)
ibm_outdated_late_crl = createCRL(
ibm_pkey,
ibm_subject,
[],
last_update=datetime.datetime.today() - 2 * 365 * ONE_DAY,
next_update=datetime.datetime.today() - 1 * 365 * ONE_DAY,
)
ibm_wrong_issuer_crl = createCRL(ibm_pkey, inter_ca_subject, [])
ibm_invalid_hash_crl = createCRL(
inter_ca_pkey, ibm_subject, [555, host_crt.serial_number]
)
# create host certificate issued by a non-valid signing key
host_invalid_signing_key_pkey = getPrivKey("host_invalid_signing_key.key", createEcKeyPair)
host_invalid_signing_key_subject = x509.Name(
[
x509.NameAttribute(NameOID.COUNTRY_NAME, u"US"),
x509.NameAttribute(
NameOID.ORGANIZATION_NAME,
u"International Business Machines Corporation",
),
x509.NameAttribute(
NameOID.COMMON_NAME, u"International Business Machines Corporation"
),
x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, u"New York"),
x509.NameAttribute(NameOID.LOCALITY_NAME, u"Armonk"),
x509.NameAttribute(NameOID.ORGANIZATIONAL_UNIT_NAME, u"IBM Z Host Key"),
]
)
host_invalid_signing_key_crt = createCert(
pkey=host_invalid_signing_key_pkey,
subject=host_invalid_signing_key_subject,
issuer_crt=ibm_wrong_subject_crt,
issuer_pkey=ibm_wrong_subject_pkey,
crl_uri=MOCKUP_CRL_DIST + "ibm_wrong_subject.crl",
t=CertType.HOST_CERT,
)
host2_pkey = getPrivKey("host2.key", createEcKeyPair)
host2_subject = x509.Name(
[
x509.NameAttribute(NameOID.COUNTRY_NAME, u"US"),
x509.NameAttribute(
NameOID.ORGANIZATION_NAME,
u"International Business Machines Corporation",
),
x509.NameAttribute(
NameOID.COMMON_NAME, u"International Business Machines Corporation"
),
x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, u"New York"),
x509.NameAttribute(NameOID.LOCALITY_NAME, u"Armonk"),
x509.NameAttribute(NameOID.ORGANIZATIONAL_UNIT_NAME, u"IBM Z Host Key"),
]
)
host2_crt = createCert(
pkey=host2_pkey,
subject=host2_subject,
issuer_crt=ibm_crt,
issuer_pkey=ibm_pkey,
crl_uri=MOCKUP_CRL_DIST + "ibm.crl",
t=CertType.HOST_CERT,
)
fake_host_pkey = getPrivKey("fake_host.key", createEcKeyPair)
fake_host_subject = x509.Name(
[
x509.NameAttribute(NameOID.COUNTRY_NAME, u"US"),
x509.NameAttribute(
NameOID.ORGANIZATION_NAME,
u"International Business Machines Corporation",
),
x509.NameAttribute(
NameOID.COMMON_NAME, u"International Business Machines Corporation"
),
x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, u"New York"),
x509.NameAttribute(NameOID.LOCALITY_NAME, u"Armonk"),
x509.NameAttribute(NameOID.ORGANIZATIONAL_UNIT_NAME, u"IBM Z Host Key"),
]
)
fake_host_crt = createCert(
pkey=fake_host_pkey,
subject=fake_host_subject,
issuer_crt=fake_ibm_crt,
issuer_pkey=fake_ibm_pkey,
crl_uri=MOCKUP_CRL_DIST + "fake_ibm.crt",
t=CertType.HOST_CERT,
)
#TODO DER chain
# store CA
with open("root_ca.crt", "wb") as f:
f.write(root_ca_crt.public_bytes(serialization.Encoding.PEM))
with open("root_ca.crl", "wb") as f:
f.write(root_ca_crl.public_bytes(serialization.Encoding.PEM))
with open("root_ca.chained.crt", "wb") as f:
f.write(root_ca_crt.public_bytes(serialization.Encoding.PEM))
f.write(root_ca_crl.public_bytes(serialization.Encoding.PEM))
with open("fake_root_ca.crt", "wb") as f:
f.write(fake_root_ca_crt.public_bytes(serialization.Encoding.PEM))
with open("fake_root_ca.crl", "wb") as f:
f.write(fake_root_ca_crl.public_bytes(serialization.Encoding.PEM))
with open("fake_root_ca_valid.crl", "wb") as f:
f.write(fake_root_ca_valid_crl.public_bytes(serialization.Encoding.PEM))
with open("inter_ca.crt", "wb") as f:
f.write(inter_ca_crt.public_bytes(serialization.Encoding.PEM))
with open("inter_ca.crl", "wb") as f:
f.write(inter_ca_crl.public_bytes(serialization.Encoding.PEM))
with open("inter_ca.invalid_date.crl", "wb") as f:
f.write(inter_ca_invalid_date_crl.public_bytes(serialization.Encoding.PEM))
with open("inter_ca.invalid_signer.crl", "wb") as f:
f.write(inter_ca_invalid_signer_crl.public_bytes(serialization.Encoding.PEM))
with open("inter_ca.chained.crt", "wb") as f:
f.write(inter_ca_crt.public_bytes(serialization.Encoding.PEM))
f.write(inter_ca_crl.public_bytes(serialization.Encoding.PEM))
with open("fake_inter_ca.crt", "wb") as f:
f.write(fake_inter_ca_crt.public_bytes(serialization.Encoding.PEM))
with open("fake_inter_ca.crl", "wb") as f:
f.write(fake_inter_ca_crl.public_bytes(serialization.Encoding.PEM))
# store IBM
with open("ibm.crt", "wb") as f:
f.write(ibm_crt.public_bytes(serialization.Encoding.PEM))
with open("ibm_rev.crt", "wb") as f:
f.write(ibm_rev_crt.public_bytes(serialization.Encoding.PEM))
with open("ibm_expired.crt", "wb") as f:
f.write(ibm_expired_crt.public_bytes(serialization.Encoding.PEM))
with open("ibm.crl", "wb") as f:
f.write(ibm_crl.public_bytes(serialization.Encoding.PEM))
with open("ibm.chained.crt", "wb") as f:
f.write(ibm_crl.public_bytes(serialization.Encoding.PEM))
f.write(ibm_crt.public_bytes(serialization.Encoding.PEM))
with open("ibm_outdated_early.crl", "wb") as f:
f.write(ibm_outdated_early_crl.public_bytes(serialization.Encoding.PEM))
with open("ibm_outdated_late.crl", "wb") as f:
f.write(ibm_outdated_late_crl.public_bytes(serialization.Encoding.PEM))
with open("ibm_wrong_issuer.crl", "wb") as f:
f.write(ibm_wrong_issuer_crl.public_bytes(serialization.Encoding.PEM))
with open("ibm_invalid_hash.crl", "wb") as f:
f.write(ibm_invalid_hash_crl.public_bytes(serialization.Encoding.PEM))
with open("ibm_wrong_subject.crt", "wb") as f:
f.write(ibm_wrong_subject_crt.public_bytes(serialization.Encoding.PEM))
with open("ibm_wrong_subject.crl", "wb") as f:
f.write(ibm_wrong_subject_crl.public_bytes(serialization.Encoding.PEM))
with open("fake_ibm.crt", "wb") as f:
f.write(fake_ibm_crt.public_bytes(serialization.Encoding.PEM))
with open("fake_ibm.crl", "wb") as f:
f.write(fake_ibm_crl.public_bytes(serialization.Encoding.PEM))
# store host
with open("host.crt", "wb") as f:
f.write(host_crt.public_bytes(serialization.Encoding.PEM))
with open("host_uri_na.crt", "wb") as f:
f.write(host_uri_na_crt.public_bytes(serialization.Encoding.PEM))
# store host key issued by a signing key using the wrong subject OU
with open("host_invalid_signing_key.crt", "wb") as f:
f.write(host_invalid_signing_key_crt.public_bytes(serialization.Encoding.PEM))
# store revoked host
with open("host_rev.crt", "wb") as f:
f.write(host_rev_crt.public_bytes(serialization.Encoding.PEM))
# store host2
with open("host2.crt", "wb") as f:
f.write(host2_crt.public_bytes(serialization.Encoding.PEM))
# store fake host
with open("fake_host.crt", "wb") as f:
f.write(fake_host_crt.public_bytes(serialization.Encoding.PEM))
with open("host_crt_expired.crt", "wb") as f:
f.write(host_crt_expired.public_bytes(serialization.Encoding.PEM))
# store a DER cert and crl
with open("der.crt", "wb") as f:
f.write(ibm_crt.public_bytes(serialization.Encoding.DER))
with open("der.crl", "wb") as f:
f.write(ibm_crl.public_bytes(serialization.Encoding.DER))

View File

@@ -0,0 +1,17 @@
#!/bin/bash
if [ $# -eq 0 ]; then
path="."
else
path="$1"
fi
if test -f "${path}"/host.crt; then
exit 0
fi
python -m venv "${path}"/gen_venv
source "${path}"/gen_venv/bin/activate
pip3 install -r "${path}"/requirements.txt
cd "${path}" || exit 2
python3 ./create_certs.py
deactivate

View File

@@ -0,0 +1 @@
cryptography>=39.0.0

View File

@@ -0,0 +1,30 @@
-----BEGIN CERTIFICATE-----
MIIFFDCCAvygAwIBAgIUO+SNGBgRvkmR/lKiZhiN4l4zS70wDQYJKoZIhvcNAQEN
BQAwgcwxCzAJBgNVBAYTAlVTMTQwMgYDVQQKDCtJbnRlcm5hdGlvbmFsIEJ1c2lu
ZXNzIE1hY2hpbmVzIENvcnBvcmF0aW9uMTQwMgYDVQQDDCtJbnRlcm5hdGlvbmFs
IEJ1c2luZXNzIE1hY2hpbmVzIENvcnBvcmF0aW9uMREwDwYDVQQIDAhOZXcgWW9y
azEVMBMGA1UEBwwMUG91Z2hrZWVwc2llMScwJQYDVQQLDB5JQk0gWiBIb3N0IEtl
eSBTaWduaW5nIFNlcnZpY2UwIBcNMjMwMzI5MDkwNDQ4WhgPMjM4NzEyMzEwOTA0
NDhaMIG2MQswCQYDVQQGEwJVUzE0MDIGA1UECgwrSW50ZXJuYXRpb25hbCBCdXNp
bmVzcyBNYWNoaW5lcyBDb3Jwb3JhdGlvbjE0MDIGA1UEAwwrSW50ZXJuYXRpb25h
bCBCdXNpbmVzcyBNYWNoaW5lcyBDb3Jwb3JhdGlvbjERMA8GA1UECAwITmV3IFlv
cmsxDzANBgNVBAcMBkFybW9uazEXMBUGA1UECwwOSUJNIFogSG9zdCBLZXkwgZsw
EAYHKoZIzj0CAQYFK4EEACMDgYYABAF2iBzYS4tt5xI8fpAO33jn97aEmZFGsSY7
qLhshEfwirbUacKxKO2eHDUBWAWs09MCM9ORIvfi+KocKxR7eIO0BgDDRVbCXPkv
Kc5mvUQRTWt7PHjhOj+QvbkAQPaHblJi93imGpN9GzSALrJX404Gct1fKjR63aoo
IDUJKnDDXC55c6OBhzCBhDAyBgNVHR8EKzApMCegJaAjhiFodHRwOi8vMTI3LjAu
MC4xOjEyMzQvY3JsL2libS5jcmwwDgYDVR0PAQH/BAQDAgMIMB8GA1UdIwQYMBaA
FN6M1/Do0NgBB3F9b9loWSA+sGd8MB0GA1UdDgQWBBR6ESQ+J+iXQe0a/KZ2+N+Z
8a9SZjANBgkqhkiG9w0BAQ0FAAOCAgEAVWJHoCxxkqh7b+y93PY4KPy4jJC4gN1S
dAPqttD/yteJ/4mbVel1/KNSoQBk5EpJmRqeBwHCgGJaT/TxYXNw/b8mRPxe/xbb
wieZMqlSmH028UjYDku1eM0IgHISgoCesIR95D5iAOWbMMVUwIHIHTfmhK7DmZVe
SPf7RIkctrpYxZh0Gw8KLZO6Mfy/9tq3dps0A7KS6jjdrF+M9LavPGwFvtfvRMTi
rdteByO2saGAKDvrjtievwlWCNBJlKV1arW9krN7eqJY5YO6eRbX6UjuhbPRgjte
eZ4jL121TBJaKZU7Q/lvYHIWfzstwQdiem2Ua1GyiiEvPZrQlmqQ3gDBtwJQGB4Q
2myP7MY7THiKObjaB8qRsVxKM78ktwAtAYSZv7gZlmSJ/uTMzDV5D2TQxqs7zwCj
sV+psUn4nvh58xP+DW+MYbF/Cpmzvul9FjMKBs270vE1q+gMot27rbQHRRJ4lVN5
khiG6Oi6blVkPKExIVIiaZ9diXK6NhtWp15PWljNiDxZO+zpkeuw7cKLn/idzmvP
Gcj6m7DqcdsSIHNKbR5iM2VuDhg/j8uBD3uF2Wlymp31TBQgdYWSihJpwZKHNqJD
uq9SmegwI5gYg64KwABZM9hGbl/krXt/0CeCR5HRc+fthanKx/tO2tbCuVT3FR5J
XpKNUy1D78o=
-----END CERTIFICATE-----

View File

@@ -0,0 +1,30 @@
-----BEGIN CERTIFICATE-----
MIIFEjCCAvqgAwIBAgIUMXh4o6xcPRTKpYDr+YgZnmWeatMwDQYJKoZIhvcNAQEN
BQAwgcwxCzAJBgNVBAYTAlVTMTQwMgYDVQQKDCtJbnRlcm5hdGlvbmFsIEJ1c2lu
ZXNzIE1hY2hpbmVzIENvcnBvcmF0aW9uMTQwMgYDVQQDDCtJbnRlcm5hdGlvbmFs
IEJ1c2luZXNzIE1hY2hpbmVzIENvcnBvcmF0aW9uMREwDwYDVQQIDAhOZXcgWW9y
azEVMBMGA1UEBwwMUG91Z2hrZWVwc2llMScwJQYDVQQLDB5JQk0gWiBIb3N0IEtl
eSBTaWduaW5nIFNlcnZpY2UwHhcNMjEwMzI5MTEwNDQ4WhcNMjIwMzI5MTEwNDQ4
WjCBtjELMAkGA1UEBhMCVVMxNDAyBgNVBAoMK0ludGVybmF0aW9uYWwgQnVzaW5l
c3MgTWFjaGluZXMgQ29ycG9yYXRpb24xNDAyBgNVBAMMK0ludGVybmF0aW9uYWwg
QnVzaW5lc3MgTWFjaGluZXMgQ29ycG9yYXRpb24xETAPBgNVBAgMCE5ldyBZb3Jr
MQ8wDQYDVQQHDAZBcm1vbmsxFzAVBgNVBAsMDklCTSBaIEhvc3QgS2V5MIGbMBAG
ByqGSM49AgEGBSuBBAAjA4GGAAQBdogc2EuLbecSPH6QDt945/e2hJmRRrEmO6i4
bIRH8Iq21GnCsSjtnhw1AVgFrNPTAjPTkSL34viqHCsUe3iDtAYAw0VWwlz5LynO
Zr1EEU1rezx44To/kL25AED2h25SYvd4phqTfRs0gC6yV+NOBnLdXyo0et2qKCA1
CSpww1wueXOjgYcwgYQwMgYDVR0fBCswKTAnoCWgI4YhaHR0cDovLzEyNy4wLjAu
MToxMjM0L2NybC9pYm0uY3JsMA4GA1UdDwEB/wQEAwIDCDAfBgNVHSMEGDAWgBTe
jNfw6NDYAQdxfW/ZaFkgPrBnfDAdBgNVHQ4EFgQUehEkPifol0HtGvymdvjfmfGv
UmYwDQYJKoZIhvcNAQENBQADggIBAEsSd5vd7Vk1y1YsE4eWkrBrMElYa3/O6G2Q
oMZFo2mzzDH50NBEwYG4K+SjEmqJbAErtNHsAcJLWlvORiNoBmPcB6FEMifgCvuZ
zbSEiL/tt8XLI1M04DdKjVZ6AIrdhMKPz/AaRycnlHjbq0R0fEJP/SnWxtGnHewB
QGM8TDGCzXrwXsOr50soxQ+cbXFJ6eQyGrtNP0eyJ7kkIrz6+SJ0dQPXxoZpdtfY
XEv1OagX0tAuDUG26do6MjwC1qiDKoLdkxSFRkCvyRHqFapKlLhzBMrLhQ+Hl6E/
kD5ORD2nMvTHcHWbjb7Mr6tcxKG+7CcJO0hYJbdfNCcKYc3EmE49wazSTBKvWfJp
XObVEGeM/11cdcg6Li1jw/JrrexEeQpjgoNuAgGKRmxzJBOCPNkU8jGs5QEqFCyw
fpl7BA+ydW2/zAvcr7mZZgyK4KiRTdK5VTGfXuwTv+Q3hsE0CZ6L+byNCZajyGzs
xq9ydh2G4kIlWFzs+2gSxQWYRiOGt6W7FVdiPYOnAVgzmRJdfR1qVrWZTGbPxJbX
1O3qYBPQE2tU8xsyl/HuikGProda2xwfTjmRhr7DPYyF75nGPvtGm6vwBBcWm+xl
jI0a/dHqE5MR6acOrXYNSFflPcfd04vJ87Ajx/wFr0Glo/8LWtzMp0nFpvif9LDX
3sGEtRA1
-----END CERTIFICATE-----

View File

@@ -0,0 +1,30 @@
-----BEGIN CERTIFICATE-----
MIIFHzCCAwegAwIBAgIUBphLhhHJL2K96bK/cfgBk0d+7gkwDQYJKoZIhvcNAQEN
BQAwgckxCzAJBgNVBAYTAlVTMTQwMgYDVQQKDCtJbnRlcm5hdGlvbmFsIEJ1c2lu
ZXNzIE1hY2hpbmVzIENvcnBvcmF0aW9uMTQwMgYDVQQDDCtJbnRlcm5hdGlvbmFs
IEJ1c2luZXNzIE1hY2hpbmVzIENvcnBvcmF0aW9uMREwDwYDVQQIDAhOZXcgWW9y
azEVMBMGA1UEBwwMUG91Z2hrZWVwc2llMSQwIgYDVQQLDBtLZXkgU2lnbmluZyBT
ZXJ2aWNlIEludmFsaWQwIBcNMjMwMzI5MDkwNDQ4WhgPMjM4NzEyMzEwOTA0NDha
MIG2MQswCQYDVQQGEwJVUzE0MDIGA1UECgwrSW50ZXJuYXRpb25hbCBCdXNpbmVz
cyBNYWNoaW5lcyBDb3Jwb3JhdGlvbjE0MDIGA1UEAwwrSW50ZXJuYXRpb25hbCBC
dXNpbmVzcyBNYWNoaW5lcyBDb3Jwb3JhdGlvbjERMA8GA1UECAwITmV3IFlvcmsx
DzANBgNVBAcMBkFybW9uazEXMBUGA1UECwwOSUJNIFogSG9zdCBLZXkwgZswEAYH
KoZIzj0CAQYFK4EEACMDgYYABAF709fZwb3pGZWfHLcCG2fLNcvNh6IqPHwfjRYp
brM5BEE8XoXxcCAXwxo4EAGOcuZBRKP6Ofahek7ppizV8bkPMQDCIgNq2N8mwPFO
99CbyrZs4ZMeAWWWPJiHXUHbWQq4ko8w4UUT7nUIgdHQwWM6TdZml8ke+LE9Jxtx
h9KkfCPi/aOBlTCBkjBABgNVHR8EOTA3MDWgM6Axhi9odHRwOi8vMTI3LjAuMC4x
OjEyMzQvY3JsL2libV93cm9uZ19zdWJqZWN0LmNybDAOBgNVHQ8BAf8EBAMCAwgw
HwYDVR0jBBgwFoAU+jIyiVonTYe6GuSPiAkxA65qou0wHQYDVR0OBBYEFJdEECIf
7UdEf08saThHZDzSfxjhMA0GCSqGSIb3DQEBDQUAA4ICAQBgvREPqqKvAZM3q9pG
5S6wtUspz1Y1sBD4duPEnMZ7Vf9a1HRPrR4vc5ncFIcyS/U/UusvWgFMYoa6WIZR
l4OqRplKF1pwCaQ2F/8OdGMV37iUqZuN6V/GggbFXgMFK1dH29T6h4VtoKC9yScQ
ToHQLuz4ymkd2BwxYix19M6QwdrqomjJb2/zrc7pvMZ0k8KKYi/wt6tlz7FDvsxF
VSDf29gm98kfDJfzPfAC5D93YruAohsP8SakVdA2/YbTkDfImT8ggSnsE83upSD6
ssjKPPNRunLeCKLb55/Ikcok1iyGhfdmkJvdIHSEvyNp0p7mrohz6l748xdKkKNt
9hOzsfNjThq3zp97ND7M+knqNuzsZIkcV/OUdxNBootIrJXvfeqpaw++5SfWvf+6
1dHJQpDU3cXKAQ0/RvvqLC+aPvklk2efuvBKIKP9X4WqcP+l2P19GMaM1SZtr5S5
OWMxqT6sW5lSX5Smm4rMB3UmLDS0SXxHIIvFEQSABiWb6Y0ibDj8a+YrWVyQNH1K
fCSNvsW1r03D06q6gp9fxL1hFBLUw9ooi7ewPfNmm3doe2R0TQpE0pWkiRhhjDjJ
RcIPp+gfYDtt0LcoYVpNdKtLoRPVtO2K3zeU0ezW4PL9Q0tjxh2K/+1+8UEv7nJD
sFg4nQMeygdSK4w0ZlT8xqXVJw==
-----END CERTIFICATE-----

View File

@@ -0,0 +1,30 @@
-----BEGIN CERTIFICATE-----
MIIFFDCCAvygAwIBAgIUGuqbEx5X1pFQfF4T6kIfdcrUwbMwDQYJKoZIhvcNAQEN
BQAwgcwxCzAJBgNVBAYTAlVTMTQwMgYDVQQKDCtJbnRlcm5hdGlvbmFsIEJ1c2lu
ZXNzIE1hY2hpbmVzIENvcnBvcmF0aW9uMTQwMgYDVQQDDCtJbnRlcm5hdGlvbmFs
IEJ1c2luZXNzIE1hY2hpbmVzIENvcnBvcmF0aW9uMREwDwYDVQQIDAhOZXcgWW9y
azEVMBMGA1UEBwwMUG91Z2hrZWVwc2llMScwJQYDVQQLDB5JQk0gWiBIb3N0IEtl
eSBTaWduaW5nIFNlcnZpY2UwIBcNMjMwMzI5MDkwNDQ4WhgPMjM4NzEyMzEwOTA0
NDhaMIG2MQswCQYDVQQGEwJVUzE0MDIGA1UECgwrSW50ZXJuYXRpb25hbCBCdXNp
bmVzcyBNYWNoaW5lcyBDb3Jwb3JhdGlvbjE0MDIGA1UEAwwrSW50ZXJuYXRpb25h
bCBCdXNpbmVzcyBNYWNoaW5lcyBDb3Jwb3JhdGlvbjERMA8GA1UECAwITmV3IFlv
cmsxDzANBgNVBAcMBkFybW9uazEXMBUGA1UECwwOSUJNIFogSG9zdCBLZXkwgZsw
EAYHKoZIzj0CAQYFK4EEACMDgYYABABphgYAOqZ4uJPUtVIqaU7UsJgz9+xMmGDq
V7nFimGmkbmqPLT96jIyN4CWdLzfbxP0xvIklkEoOm1xV07YR2LHHQATNEPISTDH
7fySZ47QVc5tyECpZVW7JvSvWKA/KiApAbB1ixErfDrqW1nG5IUtEbDYJgtGwPI/
+I7e9cU2wkA5mKOBhzCBhDAyBgNVHR8EKzApMCegJaAjhiFodHRwOi8vMTI3LjAu
MC4xOjEyMzQvY3JsL2libS5jcmwwDgYDVR0PAQH/BAQDAgMIMB8GA1UdIwQYMBaA
FN6M1/Do0NgBB3F9b9loWSA+sGd8MB0GA1UdDgQWBBTECeFXnyfTnFazbR0K9cYk
lS3O0jANBgkqhkiG9w0BAQ0FAAOCAgEAOjEE4/KdvcJZbloSGLue27FSrhExvUJ3
tYS3rs2xg3Ua2daCioI00VrwIN2Fjisqvi10Nv6+NWz5w1220AJyjlmPxvWFcPco
sXLAOWDhi217JaoJ+RzavpOwzhTffEpvPwR6RU4A36vvonc4jm3mWs6F1i5T6YPi
ZaYuk3CRme6WX012rBhIc+heTGh5ZDwwPmGDMLXdpsu+2+3sCPxUW6eQcOWoXkhJ
jn+n6mU18JdN5+wU6Lig5uxXnoP1VN8Xog/mmKV4ThVAS8k9iS5wFK4jl27n6XZy
wfd65WWlm4MMEvKNruj025aSPJp/bcnchBNfnXXPuI5GnYS2cC3TXHd4XT3r3pYn
qgLoNi+AkxnTnxEv5lg+oE+yTMNxDh4iiYkX96ljamGbUPBvbD7bi3Oc/7EOVra1
BmCGmcjToEnm0e0it9yyuYwKQ6nTz906W3XzaFB+awnXZcGEMgwwdvdal8+eki/r
ofT0nO6cuXbbPYc0rSs/2f3WxjDVmQtiVoJ1dPYIrTX/4lbrsYWVS016Y0UcZ/D/
/qWWWZIyYpzPasTEgGqtwb5WhBvz3RAFTePefFlTzBHhSPk0Tsu4+W7zHpapfZ0M
0LXT1lGR5WBmur002vbTm4yt7tzdypMbL2i70WGEp4mpRohmBG1m9hYGcelip3iL
Im6vFBNWpVE=
-----END CERTIFICATE-----

View File

@@ -0,0 +1,20 @@
-----BEGIN X509 CRL-----
MIIDVzCCAT8CAQEwDQYJKoZIhvcNAQELBQAwgcwxCzAJBgNVBAYTAlVTMTQwMgYD
VQQKDCtJbnRlcm5hdGlvbmFsIEJ1c2luZXNzIE1hY2hpbmVzIENvcnBvcmF0aW9u
MTQwMgYDVQQDDCtJbnRlcm5hdGlvbmFsIEJ1c2luZXNzIE1hY2hpbmVzIENvcnBv
cmF0aW9uMREwDwYDVQQIDAhOZXcgWW9yazEVMBMGA1UEBwwMUG91Z2hrZWVwc2ll
MScwJQYDVQQLDB5JQk0gWiBIb3N0IEtleSBTaWduaW5nIFNlcnZpY2UXDTIzMDMx
OTExMDQ0OFoYDzIzODcxMjMxMTEwNDQ4WjA8MBMCAgIrFw0yMzAzMjgxMTA0NDha
MCUCFBrqmxMeV9aRUHxeE+pCH3XK1MGzFw0yMzAzMjgxMTA0NDhaMA0GCSqGSIb3
DQEBCwUAA4ICAQCGdm0ls5MXM6MUI0wR7qOitKh3TIfRnCvhSibVPskjlBZaBT01
F6xaQGyWVR19IzQNn9GxOGMqvRy/oSihznBeA0+e9497IOPXKop/JsypZR101539
ntVt691ncmctxKnb2nT4dw7AuiLTxMVzdJ/ouXovnPcgSv/r8lwBo1fXxOgQlQLE
Pi126WFkkgBK7EANnAXiXVWvdM6p67jl/AQGOVHp8MeXowejDdVqKzoU6yyMRDeE
uEU4QibvH/J8VPLC/A2oh4XTZbJ5rB6u3rz2fFGI03XqSrJJHbNenGVQ2ar5qJeI
6kHNDIuuwXN+7JPFf8JXdk8L0G88rQsnjrcm0GzQPW/nZ5bN3FA1V139rdOhSBLR
QgaKzju8Le/Zem317ykOJbC6nDBORmpBVzXYdXA9RMg4PIs3kRVqp/RMiiClz42z
w8c1khmcH6FO2Q5Z40vq8tmSLhbu6PgGIPIya/OQacgDjDiDGcWGvqzVCWv/6AoL
em7b5Piu4yznVkEUA2h3LvoigYTJCgHFrQnoIcuM8vx8QkDjXxSHeuy3wTd2l67S
pZp+jSJPdqWe2PWALJrYuq736E2rZ013eLybHKYOkoJP6ZLewh4gsomO0bpxTL1U
TjPsJncaAP/gLqHi0QD4+irMlo6Q9YpEIkbp9ScEoVMHRL9A/vBQfUJfZw==
-----END X509 CRL-----

View File

@@ -0,0 +1,39 @@
-----BEGIN CERTIFICATE-----
MIIGyzCCBLOgAwIBAgIUeGuWhNwpt9CPzFJ5UJAKfkIlLDcwDQYJKoZIhvcNAQEL
BQAwgb0xCzAJBgNVBAYTAlVTMTQwMgYDVQQKDCtJbnRlcm5hdGlvbmFsIEJ1c2lu
ZXNzIE1hY2hpbmVzIENvcnBvcmF0aW9uMTQwMgYDVQQDDCtJbnRlcm5hdGlvbmFs
IEJ1c2luZXNzIE1hY2hpbmVzIENvcnBvcmF0aW9uMREwDwYDVQQIDAhOZXcgWW9y
azEPMA0GA1UEBwwGQXJtb25rMR4wHAYDVQQLDBVJQk0gWiBJbnRlcm1lZGlhdGUg
Q0EwIBcNMjMwMzI5MDkwNDQ3WhgPMjM4NzEyMzEwOTA0NDdaMIHMMQswCQYDVQQG
EwJVUzE0MDIGA1UECgwrSW50ZXJuYXRpb25hbCBCdXNpbmVzcyBNYWNoaW5lcyBD
b3Jwb3JhdGlvbjE0MDIGA1UEAwwrSW50ZXJuYXRpb25hbCBCdXNpbmVzcyBNYWNo
aW5lcyBDb3Jwb3JhdGlvbjERMA8GA1UECAwITmV3IFlvcmsxFTATBgNVBAcMDFBv
dWdoa2VlcHNpZTEnMCUGA1UECwweSUJNIFogSG9zdCBLZXkgU2lnbmluZyBTZXJ2
aWNlMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAsXm4EgSxVmuNsKgo
sfJNxNQFkdbpCxxyMOuyBFyGKxSwiGl20VWB6itsG+b9NOmAI6D6FPvLPuB/gSq0
HR5FuSIkmp9AUx6Xi6lVA2L7qdINo1OA4nJKe5hLSHfjWn36DZFrDyoODlDzLJrS
O4h4QSP1nDSFhk1ilsR1czlvKreXkJw5FsUHRhZGG6idLoK2Ibrne6WsVhPG6WNA
abuvQFv40cQWcyRoePktG8ImnNF5GDewXwtpzARSQj9jTL/gE77DH78a77J8+H+h
d5guiBbPZK8gNkPe0CjD0B7tUx/+sByoaSeQffFG4Cnu5JJDqFq9LXPjtlG2nXSB
Qh6Sc2gzP3jKV+ZcSwQw9AxEQ0ZB/VIPsR8KZKReDDztEpcKxwxlh7bEix/0Y1sv
zT7/I+m5kufdDG6j3hHHzniKXL4b/WyedSfdnVqIJw82FgPFgyIY6F/0ccLdkhAm
fLtQJBHc3UyK13l0qVJxhAFdz1Q0zfScBS6qM/Gnbcdc6MY9/bZdIK7E+4op5iAM
kvQHap7qnArhI8VQ1bcXYlQ6asPj4e10lmzroiBHM4N/Yuxv38tmtUCudSB+EcXi
EgJIOLmLq2ZACRzug9KPyMXOD/Yxz2wPgs6I3gvrB22w/MfC77wMfEMuQQk4cZel
TFKosHgLjvcLG+zx5yh5ZVFcNV0CAwEAAaOBrzCBrDA3BgNVHR8EMDAuMCygKqAo
hiZodHRwOi8vMTI3LjAuMC4xOjEyMzQvY3JsL2ludGVyX2NhLmNybDAMBgNVHRMB
Af8EAjAAMA4GA1UdDwEB/wQEAwIHgDATBgNVHSUEDDAKBggrBgEFBQcDAzAfBgNV
HSMEGDAWgBQRqYdWstn6ntusRZ8LjyPFEicL5TAdBgNVHQ4EFgQU3ozX8OjQ2AEH
cX1v2WhZID6wZ3wwDQYJKoZIhvcNAQELBQADggIBAEqkZawT89MngrmMTREjYGKZ
+qrm7uQf9wFiM7H7Xs11OEJ5PkNh4jNnnIsXZxc8rr76x+zLr4F6aI08AQn5QOy9
JXGIbrMHLebtn198aIOYbxZisbXnBlVO3Xz+k8JLdzsu5zxjjaDY3/a63X2ccStJ
U53pSqvgJi6/AvMPA1CPazSjxu6na8rYz6d7c/god7OF0qwQ/ePqd4uJOaImm7HH
CCkwMPYO7UyOWU5CSPMcJ86SGYhvYkoM7wZeJoukK6HlKDI1SRubiTFAx+Hbyk1R
dyVY9vmIOeUlsGEMgsW836g++dg8efRIbIYbSBLQhUL64lLA6wZJ6/oCtC29aX+o
UfxcGUROrpZ5Xi4b4sn0vW4rYq65BzlU17x45XsZMh11hX9aPNE4B62Jl2XLjX3P
Sedu7b/QB6jWpwTAdH96LeLxVepAWiVcFBApBqpu7wxRhCs6M1t3Gh9nvlPE5NRz
zsmx+HVZIgWoP3CgHmiHqajphL0xp6R9qJOyzAVChsmbQYvr+rfaXMv24KBvJYgc
xq5iCP7IccgC6WlhpWyAoTSuhiStTZJtlCKPZqc+HRcuf2fLWXip8YKNHgEtTxNz
7citBXZNoRDFULWwiYDnwhGcZ53p5zPLABYKZdfNHdI+tV92AbzYyQaV0ZwcxL9K
ObAAlDzZKE8vJwT94E3O
-----END CERTIFICATE-----

View File

@@ -0,0 +1,19 @@
-----BEGIN X509 CRL-----
MIIDGzCCAQMCAQEwDQYJKoZIhvcNAQELBQAwgcwxCzAJBgNVBAYTAlVTMTQwMgYD
VQQKDCtJbnRlcm5hdGlvbmFsIEJ1c2luZXNzIE1hY2hpbmVzIENvcnBvcmF0aW9u
MTQwMgYDVQQDDCtJbnRlcm5hdGlvbmFsIEJ1c2luZXNzIE1hY2hpbmVzIENvcnBv
cmF0aW9uMREwDwYDVQQIDAhOZXcgWW9yazEVMBMGA1UEBwwMUG91Z2hrZWVwc2ll
MScwJQYDVQQLDB5JQk0gWiBIb3N0IEtleSBTaWduaW5nIFNlcnZpY2UYDzMwMjIw
NzMwMTEwNDQ4WhgPMzAyMzA3MzAxMTA0NDhaMA0GCSqGSIb3DQEBCwUAA4ICAQAa
bg7llgCL+OdmZQEeBKey5Dm/NxJGJljoT/sxFbQ+86lwACh1mbdxVkaPyUC/oE9T
4ppC/eHoaRcdmvN4FlIYrUhqnrTGD4s8VSoYvJ7+f5ZFGjUyflnMwyaal21hDaG4
2SZjPVOQ0ksEA3mrHE1MTRVFqFl4ZFxGhh7NYMoOEkffM1UooChWHTTBMz67nmbh
Ih0MDHhS5J7677K2N05402Z3v3S+Y8QEjIQjDsTC1S9V607eEfG9YEND2KicQKPH
r+CK9/fVaiTz9wgUEyybps4MFoWBuUqqRebQoargFZW8w329LuS6VokbM6BSduOT
qaYFtzp3DGZbvKwUGjiGVgB/PzzB1rv+2+i/EI3D4RJt+k8xvlBIIONxwK/hcjI3
/i6hJueQpeCuasfX8ck/uKzSf0PhCmyLwWxQux66FJq4sXqWoqwf5P/U+tbB8zna
0cX5/f8+rS7ansbxjeiCHUkbdUEoY7k7KMSNUrtqbgQ4VyjTziysTbSEG7jkb4ri
Jaa9mDfWCkdwfB3TqDofWRkOdNpPTkj9TVZJ5FdV1h39D9O7B+VvedIiVod/KhB1
DyOa44YpkEcS51PuNAC/exUd6nOv9Mz+WOUP+RrxHqndRYE0RGaFP9vENyks0Kga
4CLB/IbT2rmpLivK2i6i3NOqzcykHOab3LtwOLDDmg==
-----END X509 CRL-----

View File

@@ -0,0 +1,19 @@
-----BEGIN X509 CRL-----
MIIDFjCB/wIBATANBgkqhkiG9w0BAQsFADCBzDELMAkGA1UEBhMCVVMxNDAyBgNV
BAoMK0ludGVybmF0aW9uYWwgQnVzaW5lc3MgTWFjaGluZXMgQ29ycG9yYXRpb24x
NDAyBgNVBAMMK0ludGVybmF0aW9uYWwgQnVzaW5lc3MgTWFjaGluZXMgQ29ycG9y
YXRpb24xETAPBgNVBAgMCE5ldyBZb3JrMRUwEwYDVQQHDAxQb3VnaGtlZXBzaWUx
JzAlBgNVBAsMHklCTSBaIEhvc3QgS2V5IFNpZ25pbmcgU2VydmljZRcNMjEwMzI5
MTEwNDQ4WhcNMjIwMzI5MTEwNDQ4WjANBgkqhkiG9w0BAQsFAAOCAgEAh+81L4ws
rvOC1j7nwxthaIE4m8alnuaq9h9uxYfDZooimvGwhCjfcoWmfSSA8lt3vbMJ2vRe
ZwnXXCyGWmetZ6ObnT0jwL0QuOYEuIdA8QHleBxLYBeFr4k0O9i8i3VUG0YcgZuN
H+lcpGdoIx2WV0cZ4rbzZ1cNx3bieaivqNoLQAy7g9jTizmTfY5ZvlvuG3iqSG+P
08APxmtt1qFEm1LVu4SyUSyoGB1NaxeoziMITQdfFqHoPRsu7Wdyuqi9f5irIPwM
VQNKs/Y+3Q3S8YkTW3yqxhj4HdSKJE4qVBLMYm7muirDFWo25u2sDX1LJHBsQLvV
fi7cGY0YnOJL2Y7A3XKDuqtZ34zpXg3Hhqpa9RF55K2u5dYUaPq8MEQUHK67II1r
YZAwfarhijQQ6t03E0vrzPVYpK8VjNUunYKQdOBS3OkKgXCwEMuqQDrps98BgDQ4
qfbVfxwm9XEHJZaX/qFR0sp8OQd/SD5dnS3DBl0Pp5w+w2xIaSA7QmBpDqWY66Hb
cJq4CLOKTasHTddHKz7O7zIu8QhwJGLabtnx18iaHTNNHaTF6k/51pwvA3HkJgds
HVcUNljsDNSPE258JwR2XoQAUu6VuFwRzgD7lGwdI70CpIBeAP1TRSius5RsZB+u
cw+872CILlIdNJ72lzMPWQNH+IB1RU8U/eA=
-----END X509 CRL-----

View File

@@ -0,0 +1,39 @@
-----BEGIN CERTIFICATE-----
MIIGyzCCBLOgAwIBAgIUTPiBWJn8k37onZ/aYgLxudbD3kgwDQYJKoZIhvcNAQEL
BQAwgb0xCzAJBgNVBAYTAlVTMTQwMgYDVQQKDCtJbnRlcm5hdGlvbmFsIEJ1c2lu
ZXNzIE1hY2hpbmVzIENvcnBvcmF0aW9uMTQwMgYDVQQDDCtJbnRlcm5hdGlvbmFs
IEJ1c2luZXNzIE1hY2hpbmVzIENvcnBvcmF0aW9uMREwDwYDVQQIDAhOZXcgWW9y
azEPMA0GA1UEBwwGQXJtb25rMR4wHAYDVQQLDBVJQk0gWiBJbnRlcm1lZGlhdGUg
Q0EwIBcNMjMwMzI5MDkwNDQ3WhgPMjM4NzEyMzEwOTA0NDdaMIHMMQswCQYDVQQG
EwJVUzE0MDIGA1UECgwrSW50ZXJuYXRpb25hbCBCdXNpbmVzcyBNYWNoaW5lcyBD
b3Jwb3JhdGlvbjE0MDIGA1UEAwwrSW50ZXJuYXRpb25hbCBCdXNpbmVzcyBNYWNo
aW5lcyBDb3Jwb3JhdGlvbjERMA8GA1UECAwITmV3IFlvcmsxFTATBgNVBAcMDFBv
dWdoa2VlcHNpZTEnMCUGA1UECwweSUJNIFogSG9zdCBLZXkgU2lnbmluZyBTZXJ2
aWNlMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAsXm4EgSxVmuNsKgo
sfJNxNQFkdbpCxxyMOuyBFyGKxSwiGl20VWB6itsG+b9NOmAI6D6FPvLPuB/gSq0
HR5FuSIkmp9AUx6Xi6lVA2L7qdINo1OA4nJKe5hLSHfjWn36DZFrDyoODlDzLJrS
O4h4QSP1nDSFhk1ilsR1czlvKreXkJw5FsUHRhZGG6idLoK2Ibrne6WsVhPG6WNA
abuvQFv40cQWcyRoePktG8ImnNF5GDewXwtpzARSQj9jTL/gE77DH78a77J8+H+h
d5guiBbPZK8gNkPe0CjD0B7tUx/+sByoaSeQffFG4Cnu5JJDqFq9LXPjtlG2nXSB
Qh6Sc2gzP3jKV+ZcSwQw9AxEQ0ZB/VIPsR8KZKReDDztEpcKxwxlh7bEix/0Y1sv
zT7/I+m5kufdDG6j3hHHzniKXL4b/WyedSfdnVqIJw82FgPFgyIY6F/0ccLdkhAm
fLtQJBHc3UyK13l0qVJxhAFdz1Q0zfScBS6qM/Gnbcdc6MY9/bZdIK7E+4op5iAM
kvQHap7qnArhI8VQ1bcXYlQ6asPj4e10lmzroiBHM4N/Yuxv38tmtUCudSB+EcXi
EgJIOLmLq2ZACRzug9KPyMXOD/Yxz2wPgs6I3gvrB22w/MfC77wMfEMuQQk4cZel
TFKosHgLjvcLG+zx5yh5ZVFcNV0CAwEAAaOBrzCBrDA3BgNVHR8EMDAuMCygKqAo
hiZodHRwOi8vMTI3LjAuMC4xOjEyMzQvY3JsL2ludGVyX2NhLmNybDAMBgNVHRMB
Af8EAjAAMA4GA1UdDwEB/wQEAwIHgDATBgNVHSUEDDAKBggrBgEFBQcDAzAfBgNV
HSMEGDAWgBQRqYdWstn6ntusRZ8LjyPFEicL5TAdBgNVHQ4EFgQU3ozX8OjQ2AEH
cX1v2WhZID6wZ3wwDQYJKoZIhvcNAQELBQADggIBAA1ipq2MIRdRIiUZ6M6AjY91
L5iUpgKGrmhna/gg2b6AlugDOtVDsfeFm389aSplwY+zcJZ2AbUXhXe8RVHOEUwf
O7iEDWsMZ/wxit5ZotJ5kzr49n9RTtomgbQSqxWadLq6Q9hYOWg/cr1FvXwL5tyO
rHyuRXhkUMmuk4aQ1sfybfPp0PJKbzWu001Q4rxbJTlaib9b+CcsyLWHs06JXVKi
755Lg9/ND1bQjW2CMJbZ2rm8V7oh4J0tJuF3DntOjyOk+yosckTF3bFffGPR67WC
RkZebIKx2Rh6OXMrQTLz9ldqWo0cW0O353gSmrMxExWKhgoDrZKc4UeOanweDTqO
4lU0RP/4naDuQl6/FE0rUzUkfAJmsKIuI4G1lQNZhaqUH/BdN1du094RON0T5agK
etoBcPpNpxOn4N86TJaYoDjRSDpKwxXVKZs9lk5GRLxRhqtY3iQYVZrz2gY36Ri4
lnuKZCeFmfjHvvktmb08EmrvGiQAhXzI8yfeVhlwP8lhtumIO877VW++tedK0z0D
6aBz1LsVI3IbinDZPRsWl0EEi+JFmpIktmMTdSn+0vTs7XJjbBZ+VKaPPWOG/Afg
Qav6+1AnnMtieGfrj3tyyfKo0vPSZKbdGzEr79Ukl+cxLdG5O5qZTy4ASm3EOw36
p70HafhN8Vioa+uhQObP
-----END CERTIFICATE-----

View File

@@ -0,0 +1,39 @@
-----BEGIN CERTIFICATE-----
MIIGyDCCBLCgAwIBAgIUOgU+VHYEK4Q4dZokfM01Ok2XBtUwDQYJKoZIhvcNAQEL
BQAwgb0xCzAJBgNVBAYTAlVTMTQwMgYDVQQKDCtJbnRlcm5hdGlvbmFsIEJ1c2lu
ZXNzIE1hY2hpbmVzIENvcnBvcmF0aW9uMTQwMgYDVQQDDCtJbnRlcm5hdGlvbmFs
IEJ1c2luZXNzIE1hY2hpbmVzIENvcnBvcmF0aW9uMREwDwYDVQQIDAhOZXcgWW9y
azEPMA0GA1UEBwwGQXJtb25rMR4wHAYDVQQLDBVJQk0gWiBJbnRlcm1lZGlhdGUg
Q0EwIBcNMjMwMzI5MDkwNDQ4WhgPMjM4NzEyMzEwOTA0NDhaMIHJMQswCQYDVQQG
EwJVUzE0MDIGA1UECgwrSW50ZXJuYXRpb25hbCBCdXNpbmVzcyBNYWNoaW5lcyBD
b3Jwb3JhdGlvbjE0MDIGA1UEAwwrSW50ZXJuYXRpb25hbCBCdXNpbmVzcyBNYWNo
aW5lcyBDb3Jwb3JhdGlvbjERMA8GA1UECAwITmV3IFlvcmsxFTATBgNVBAcMDFBv
dWdoa2VlcHNpZTEkMCIGA1UECwwbS2V5IFNpZ25pbmcgU2VydmljZSBJbnZhbGlk
MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA61P3Lfow3E6wGpMJmUWp
wsfFZwcuKSf64JXDn1pVJLUcjTwhApshnGaSBb+knlpwvsO1evrR7re9ZRh51730
IintOP5IA3CSGd7fqmTpchx3kdFOndrXS7BwAWuB/eZ1qzKeOYpyAS3VSE4FphYi
LSxGfwSUl89pwYyWyqGl21hv/sBL6cc+Lm55vXbeRwWKW9K/w7BkhtK1zx1xm4i9
4x1aXJ6DGWQpIk1sVDNPtzQVZYvmR1Y10/r75sNgA/WMiZx3/2VyCREnV+UXfvsX
fyMLcbwMWWt6psdhtoGFZ2sLJka5ZNvttQKfbde4TA3I6fpsrMi+oTT9YO3it5zG
ORCUC+j5B+zrzbSv+RgL+SnnAPkHqufb1a/4mFs/uTbjUYHN2/rhObnkLK4Xtfly
FBlivxx5haT9o49YkCv7l57+We4nafBPMw96ac5AGzA0gVwdMTeRZ3joT2Pc/zSf
H5E9wg3MZfg3TN2THB4S//r1/XOaA5F4BGjorbpPhp1/YaeF0rRMlAbZVKXHZJBR
n5qN8hD/V2tXviEkrZRL+iW6ltkslsjIkzrYSS+6goymUjWrkGjmcsTo0SStHE0p
7pOChLwpUtpaElemp1NDzVJqvrglWPkM1ZIIjxpk23zxKj7V2FazqP6PVuyeWdkj
VYN86ULDRG5j1hfn/n0HEC0CAwEAAaOBrzCBrDA3BgNVHR8EMDAuMCygKqAohiZo
dHRwOi8vMTI3LjAuMC4xOjEyMzQvY3JsL2ludGVyX2NhLmNybDAMBgNVHRMBAf8E
AjAAMA4GA1UdDwEB/wQEAwIHgDATBgNVHSUEDDAKBggrBgEFBQcDAzAfBgNVHSME
GDAWgBQRqYdWstn6ntusRZ8LjyPFEicL5TAdBgNVHQ4EFgQU+jIyiVonTYe6GuSP
iAkxA65qou0wDQYJKoZIhvcNAQELBQADggIBABcvU42Z/T7hT8ke5viG2x7FJmwA
gkrphOYiooB77sxY+cjxaYsct4yvFXvwuNAcstnlBK0jJRaWzLwswR1t2bXbRwQF
kjDO3br4ALRMHkDPa8iNioogyap8X6r40p7rvfnudKX0+MruLHXN3ZM2ltucYAYU
oR/Wa04KxDuZQHeKrDosAsJCv5MwgF69H3oPbhspFQsP2V5fFsxupnWFzVlwPfcQ
0lgHVC3nZ2Rj7ZariT/px3nfZ6Eg3pRyK32r2SQWVN/oVBEd5cCTONvD7Hr2SrtB
9D58f+vDyVNWM5OED7NqlNDaQw2x9BMjdEVYTGGRW4IXPbXWH08NUcEkT1Tx/vUE
EPlTgwt88Fca03yvAn/8Daw7ezsJNAFwDpPDcQhPi3vg2l32nuRkuQ5641hJiTGw
TEtpJc3dg3FJymG999rOCLLIheNLMehEDMPZHqG7XeEg/42F0580MdkOenMpjhwg
ZhrommB85sZcGBOwc63VMb5PPInYDQi5PXz9Tpann/VliVd4Dpnyn0XVy73VccXu
WWgDt8gJKWUpRiJ6MZzEKkBrXYjPLmrKB64usEJNQ1e2NIKV3bwvH5K3PmibyVBu
9fT5t0VXQpNxxlwngCjvjtt0D/frMCJQXpXpnz25aQDog9bnD1yl02SzxdZaVK05
LZP4wR2beOGlz828
-----END CERTIFICATE-----

Some files were not shown because too many files have changed in this diff Show More