Compare commits

...

68 Commits

Author SHA1 Message Date
Jan Höppner
6f15ed3264 New release s390-tools-2.31.0
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2024-02-02 17:49:44 +01:00
Joern Siglen
0d2b5af007 dbginfo.sh: check for DPM mode
check if the LPAR is running in DPM mode and reflect this in the summary.

Suggested-by: Mike Storzer <MSTORZER@de.ibm.com>
Acked-by: Eberhard Pasch <epasch@de.ibm.com>
Reviewed-by: Mario Held <mario.held@de.ibm.com>
Signed-off-by: Joern Siglen <siglen@de.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2024-02-02 16:11:22 +01:00
Jan Höppner
9e7a8f48e8 common.mak: Set default C/C++ standard to gnu11/gnu++11
s390-tools doesn't define a common C/C++ standard at the moment.
Set the standard to gnu11/gnu++11, which is also used by the Kernel, and
establish a common baseline for all tools.

The -std flag is added to ALL_CFLAGS and ALL_CXXFLAGS to avoid losing it
in case CFLAGS are set by an outside entity.
It is also added to CLAGS_FOR_BUILD for this one special cross build
case.

The -std flag is removed from all tools that set it manually until now.

Reviewed-by: Steffen Eiden <seiden@linux.ibm.com>
Reviewed-by: Marc Hartmayer <mhartmay@linux.ibm.com>
Reviewed-by: Benjamin Block <bblock@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2024-02-02 16:11:22 +01:00
Steffen Eiden
98f7a0569c rust/pvsecret: User defined signatures and verifications
Introduces the ability to `pvsecret` to add a signature (ecdsa or rsa)
to the program-reserved space (user-data) of an add-secret request
during the request creation. Additionally, some arbitrary data may be
inserted.

The new command `verify` checks if add-secret requests are sane (e.g.
start with the correct magic value). If the request contains a
user-signature `verify` will also verify this signature.

Acked-by: Marc Hartmayer <mhartmay@linux.ibm.com>
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2024-02-02 16:11:22 +01:00
Steffen Eiden
551f66282e rust/pvsecret: Streamline man and README
Makes the source for the manfiles&README more readable. Fixes some nits
like double spaces, unnessecary indentations, line breaks, ...
Removes pvsecret-version.1 as this command maps to the --version
option.

Acked-by: Marc Hartmayer <mhartmay@linux.ibm.com>
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2024-02-02 16:11:22 +01:00
Steffen Eiden
3d2ba5aaed rust/pvsecret: Sync man and help
The output of --help and man pages got out of sync.
Apply changes to the help output to fix this.

Acked-by: Marc Hartmayer <mhartmay@linux.ibm.com>
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2024-02-02 16:11:22 +01:00
Steffen Eiden
94942a48ab rust/pv: Add function to read a private key
Add a function to read a private key in PEM or DER format.

While at it, fix some documentation issues in read_{certs, crls}.

Reviewed-by: Marc Hartmayer <mhartmay@linux.ibm.com>
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2024-02-02 16:11:22 +01:00
Steffen Eiden
ab8984a7a3 rust/pv: Remove unused functions
Reviewed-by: Marc Hartmayer <mhartmay@linux.ibm.com>
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2024-02-02 16:11:22 +01:00
Marc Hartmayer
4990f643c1 rust/pv: Add-Secret: Add user-data and user-defined signing keys
User-data can be added to add-secret requests. User-data can be unsigned
or containing a signature of the request using a custom private key.

The user-data is placed at offset 0x218 and 0x200 bytes long.

Reviewed-by: Marc Hartmayer <mhartmay@linux.ibm.com>
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2024-02-02 16:11:22 +01:00
Steffen Eiden
34bef977e8 rust/pv: User-data signing and verifying
Add the ability to generate signed user-data and to verify the
signature.

Reviewed-by: Marc Hartmayer <mhartmay@linux.ibm.com>
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2024-02-02 16:11:22 +01:00
Steffen Eiden
f36c34038b rust/pv: Add-Secret: Add user-data types
Add four new user-data types an add-secret request could have:
Unsigned, Signed(EcSECP521R1, Rsa(2048, 3072))

As the user-data enum was not marked as non-exaustive this might be a
breaking change for users. (Not for any crate in this repo though).

The addition of such user-data is provided by following patches.

Reviewed-by: Marc Hartmayer <mhartmay@linux.ibm.com>
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2024-02-02 16:11:22 +01:00
Steffen Eiden
bfd0e12d22 rust/pv: Signing messages
Adds the ability to sign and verify messages using ECDH or RSA keys.

Reviewed-by: Marc Hartmayer <mhartmay@linux.ibm.com>
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2024-02-02 16:11:21 +01:00
Steffen Eiden
1450f85ada rust/pv: Fix documentation issue
Add the missing plaintext flags to the request description.

Reviewed-by: Marc Hartmayer <mhartmay@linux.ibm.com>
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2024-02-02 16:11:21 +01:00
Marc Hartmayer
2a0f1e6977 pvsecret: improve warning if host key document contains multiple certificates
Improve the warning for the case where a host key document contains
multiple certificates (only possible for a PEM file). In case there are
multiple host key document only the first certificate is used.

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>
2024-02-02 16:11:21 +01:00
Marc Hartmayer
0f433b1142 pvsecret: fix panic if empty file is used as host key document
Fix a panic in `pvsecret` when a empty file is used as a host key
document.

$ touch hkd
$ pvsecret create --no-verify -k hkd --output req.bin --hdr sehdr
...
The host key document in 'bla' contains more than one certificate!
thread 'main' panicked at pvsecret/src/cmd/create.rs:192:31:

Fixes: dd82c26f87 ("rust: Add tool to manage UV-secrets")
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>
2024-02-02 16:11:21 +01:00
Marc Hartmayer
ab6bcad263 pv/error.rs: fix typo
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>
2024-02-02 16:11:21 +01:00
Marc Hartmayer
e40a3e0621 pvapconfig/tests: use a temporary directory for tests
Use the `TemporaryDirectory` type to create a temporary directory. This
type has the advantage that the directory is automatically removed once
it goes out of scope. The old implementation in test_sysfs_write_i32
leaks a directory if a previous subtest failed. Also, using a temporary
directory for the LockFile test fixes the following error:

  $ RUST_BACKTRACE=1 cargo test -- helper::tests::test_lockfile
  ...
  running 1 test
  test helper::tests::test_lockfile ... FAILED

  failures:

  ---- helper::tests::test_lockfile stdout ----
  thread 'helper::tests::test_lockfile' panicked at pvapconfig/src/helper.rs:265:9:
  assertion failed: r1.is_ok()
  stack backtrace:
     0: rust_begin_unwind
	       at /rustc/79e9716c980570bfd1f666e3b16ac583f0168962/library/std/src/panicking.rs:597:5
     1: core::panicking::panic_fmt
	       at /rustc/79e9716c980570bfd1f666e3b16ac583f0168962/library/core/src/panicking.rs:72:14
     2: core::panicking::panic
	       at /rustc/79e9716c980570bfd1f666e3b16ac583f0168962/library/core/src/panicking.rs:127:5
     3: pvapconfig::helper::tests::test_lockfile
	       at ./src/helper.rs:265:9
     4: pvapconfig::helper::tests::test_lockfile::{{closure}}
	       at ./src/helper.rs:263:24
     5: core::ops::function::FnOnce::call_once
	       at /rustc/79e9716c980570bfd1f666e3b16ac583f0168962/library/core/src/ops/function.rs:250:5
     6: core::ops::function::FnOnce::call_once
	       at /rustc/79e9716c980570bfd1f666e3b16ac583f0168962/library/core/src/ops/function.rs:250:5
  note: Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose backtrace.

Reviewed-by: Steffen Eiden <seiden@linux.ibm.com>
Reviewed-By: Harald Freudenberger <freude@de.ibm.com>
Signed-off-by: Marc Hartmayer <mhartmay@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2024-02-02 16:11:21 +01:00
Marc Hartmayer
e56acf4f14 pv_core: add TemporaryDirectory
Add the type `TemporaryDirectory` that creates a temporary directory
that is automatically removed when it goes out of scope.

Reviewed-by: Steffen Eiden <seiden@linux.ibm.com>
Reviewed-By: Harald Freudenberger <freude@de.ibm.com>
Signed-off-by: Marc Hartmayer <mhartmay@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2024-02-02 16:11:21 +01:00
Finn Callies
17977eda30 cpacfstats: Fix typo PCMKO to PCKMO
This commit fixes a typo in the cpacfstats.c client application and its
respective manpage cpacfstats.1.

Signed-off-by: Finn Callies <fcallies@linux.ibm.com>
Reviewed-by: Harald Freudenberger <freude@linux.ibm.com>
Reviewed-by: Ingo Franzki <ifranzki@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2024-02-02 16:11:21 +01:00
Steffen Eiden
459a257568 rust/pvapconfig: Fix typos and clippy findings
Reviewed-by: Marc Hartmayer <mhartmay@linux.ibm.com>
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2024-02-02 16:11:21 +01:00
Steffen Eiden
fb65b53b9b rust/pvapconfig: use pv_base instead of pv crate
Replace the pv crate with the pv_core crate.

Reviewed-by: Marc Hartmayer <mhartmay@linux.ibm.com>
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2024-02-02 16:11:21 +01:00
Steffen Eiden
c8d4062f73 rust/pv: bump up crate version to 1.0.0
Recent changes makes it reasonable to update the version string of pv
and pv_base to 1.0.0. Recent changes introduced some non-backwards
compatible changes, like move some Error definition from pv to the new
pv_core crate. Also, the pv crates seem to be in a good shape to be
considered released.

Therefore, move up the Semver to 1.0.0.
See also:
https://doc.rust-lang.org/cargo/reference/specifying-dependencies.html

NOTE: As these crates are not published to `crates.io` as of now, the
version number is meaningless, as cargo will not pull them from
anywhere and just use the files provided by this repository.

Reviewed-by: Marc Hartmayer <mhartmay@linux.ibm.com>
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2024-02-02 16:11:21 +01:00
Steffen Eiden
c8e0992814 rust/Cargo.lock: Update zerocopy crate
Update the zerocopy dependency from v0.6.* to the non forward compatible
v0.7.*. Incompatible changes are the requirment of the FromZeroes trait
for FromBytes trait and the non-default derive feature which this
patches handles as well.

Reviewed-by: Marc Hartmayer <mhartmay@linux.ibm.com>
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2024-02-02 16:11:21 +01:00
Steffen Eiden
c0a12b29d0 rust/pv: Reorganize some tests
Use a static Mockito server instead of creating multiple on-the-fly
during testing. Add the `mockito_server_available` test to quickly
verify that the mockito server could be constructed.  Relocate the
verifier API test to the test folder as it tests the public API. Also,
make the Debug impl for CertVerifier public. The test relocation
requires this. Before, there could be a test local implementation of the
Debug impl as it was in the same crate.

While at it, get rid of the lazy_static crate in favor of the once_cell
crate. As the new std lib implementation is very near to the one from
once cell. The project will switch to the std impl when v1.70 is
available in all supported distros. See:
https://doc.rust-lang.org/std/sync/struct.OnceLock.html

Reviewed-by: Marc Hartmayer <mhartmay@linux.ibm.com>
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2024-02-02 16:11:21 +01:00
Steffen Eiden
02dded11a5 rust/pv: Replace libc wherever possible
Replace all libc references to rust-std references if available.
This eliminates the need to include libc in the pv crate.
However, pv_base still refers to libc::ioctl and libc::ENOTTY.

Reviewed-by: Marc Hartmayer <mhartmay@linux.ibm.com>
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2024-02-02 16:11:21 +01:00
Steffen Eiden
b71279cda5 rust/pv: Remove memeq function
The rust std lib already provides functionality to compare two slies.
Replace all `memeq` invocations with == and remove the `memeq` function.
As a side effect this eliminates some unsafe code in this crate.

Reviewed-by: Marc Hartmayer <mhartmay@linux.ibm.com>
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2024-02-02 16:11:21 +01:00
Steffen Eiden
c70477f8c6 rust/pv: Fix 'elided_lifetimes_in_associated_constant' warning
Fixes "warning: `&` without an explicit lifetime name cannot be used
here". This warning will become a hard error in "the future". For more
information, see issue #115010 <https://github.com/rust-lang/rust/issues/115010>

Closes: https://github.com/ibm-s390-linux/s390-tools/issues/162
Reviewed-by: Marc Hartmayer <mhartmay@linux.ibm.com>
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2024-02-02 16:11:21 +01:00
Steffen Eiden
9b51b8b882 rust/pv: Refactor pv crate
Big refactoring patch of the pv crate. The main reason behind this
refactoring is to simplify testing and maintaining the pv crate while
keeping OpenSSL/libcurl dependencies optional. Using crate features
increases the number of targets that have to be tested. This refactoring
eliminates the use of features by splitting the functionality of pv into
a use OpenSSL and no-use-OpenSSL crate.

Split off some code from the pv crate into a pv_core crate. pv requires
pv_core and reexports all symbols. pv_base contains all code from former
pv that does not use OpenSSL or libcurl functionalities. The refactored
pv crate contains functionalities to generate requests and validate host
key documents. All features from pv are dropped as they are not needed
anymore and to streamline the codebase for easier use and testing. While
at it fix some documentation issues.

Users (pvsecret & pvapconfig) have next to no code change, besides the
different import of the crate.

Acked-by: Marc Hartmayer <mhartmay@linux.ibm.com>
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2024-02-02 16:11:21 +01:00
Steffen Eiden
48539596ef rust/pv: Use constants for invalid secret types
Add local constants for the reserved, and Null secret type and use
them.

Reviewed-by: Marc Hartmayer <mhartmay@linux.ibm.com>
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2024-02-02 16:11:21 +01:00
Steffen Eiden
cafa99774c rust/pv: Replace file-macros with functions
A function does the job as well. This improves code readability and
useability. While at it remove the implicit Buffer wrapper. Users are
currently not use the benefits of a buffered write. Also, streamline the
write_out helper function.

Reviewed-by: Marc Hartmayer <mhartmay@linux.ibm.com>
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2024-02-02 16:11:21 +01:00
Tobias Huschle
b5f7ac95d8 cpuplugd: adjust to CPU 0 being no longer hotpluggable
With kernel commit 496bb034f4ff ("s390/smp: disallow CPU hotplug
of CPU 0") it is no longer possible to hotplug CPU 0.

As a side effect, the sysfs handle /sys/devices/system/cpu/cpu0/online
does no longer exist. Since cpuplugd relies on checking all online
handles of all CPUs, this change causes cpuplugd to fail as it
interprets a non existing online file as an indicator that no other CPUs
with higher IDs exist as well. This leads to cpuplugd assuming that
there are no CPUs available.

Instead of checking for the online file, it is preferable to check for
the existence of the parent folder to verify the existence of a CPU.
As a consequence, all other checks for non-existing online files must
now imply that the CPU is online, but not hotpluggable.

Signed-off-by: Tobias Huschle <huschle@linux.ibm.com>
Tested-by: Mete Durlu <meted@linux.ibm.com>
Reviewed-by: Gerald Schaefer <gerald.schaefer@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2024-02-02 16:11:21 +01:00
Thomas Richter
e984b97db0 s390-tools/cpumf: set exit code on pai data collection error
When pai records data it may fail in select() system call.
This error is not reported and the pai program exits with success.
Change this and exit with proper exit code.

Fixes: d7b1cbad8b ("cpumf/pai: Add Processor Activity Instrumentation tool")
Signed-off-by: Thomas Richter <tmricht@linux.ibm.com>
Acked-by: Sumanth Korikkar <sumanthk@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2024-02-02 16:11:21 +01:00
Thomas Richter
64d4e02b4f cpumf/pai: Handle perf records type PERF_RECORD_SWITCH
Running command pai -r <file> might encounter entries of type
PERF_RECORD_SWITCH. Entries of that type are currently not handled
and cause an error message:

 # pai -r ~/paicrypto.000
 unknown header-type 14 unknown header-type 14 \
 unknown header-type 14 ...

The error message is not terminated by a newline.

Handle entries of this type PERF_RECORD_SWITCH. These records
do not carry any payload at all, just a bit is set in the
header::misc member. This bit set determines context switch out.

Output after:
 # ./pai -r ~/paicrypto.000
 0x4b814018f4f3 6 cs-out
 0x4b817bc3c936 6 cs-in
 0x4b817bc5246c 6 cs-out
 0x4b817bd90e9a 6 cs-in
 ....
 #

Signed-off-by: Thomas Richter <tmricht@linux.ibm.com>
Acked-by: Sumanth Korikkar <sumanthk@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2024-02-02 16:11:21 +01:00
Marc Hartmayer
90ddef5a41 pre-commit: Provide pre-commit configuration
Add pre-commit (https://pre-commit.com/) configuration to allow
developers to run certain checks on their commits automatically.

Currently enabled checks and hooks are:
- Generic checks (e.g. merge-conflicts, trailing whitespace, etc.)
- git clang-format
- Codespell
- Shellcheck (https://www.shellcheck.net/)

Note: For some hooks Rust is excluded as a lot of false positives are
produced at the moment.

Signed-off-by: Marc Hartmayer <mhartmay@linux.ibm.com>
Reviewed-by: Jan Höppner <hoeppner@linux.ibm.com>
Reviewed-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2024-02-02 16:11:21 +01:00
Joern Siglen
58ef99f76b dbginfo.sh: handle relative and absolute calling path
include the dbginfo.sh script used - enabled for relative paths

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>
2024-01-12 14:10:52 +01:00
Joern Siglen
43c34956fb dbginfo.sh: adding new step to man page
step "initrd config files" was added as 19th step to our data collection
updating for newer date and distro version in the sample output

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>
2024-01-12 14:10:52 +01:00
Joern Siglen
d7dee1b9d3 dbginfo.sh: change copyright for 2024
prepare for the next updates

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>
2024-01-12 14:10:52 +01:00
Harald Freudenberger
94a38ebc3a rust/pvapconfig: Introduce new tool pvapconfig
pvapconfig is a new tool for automatically configuring the APQNs
within an Secure Execution KVM guest with AP pass-through support.
Based on a given AP configuration it tries to find a matching
APQN and bind and associate it with the correct secret.

Signed-off-by: Harald Freudenberger <freude@linux.ibm.com>
Reviewed-by: Ingo Franzki <ifranzki@linux.ibm.com>
Reviewed-by: Holger Dengler <dengler@linux.ibm.com>
Reviewed-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2024-01-12 14:10:52 +01:00
Steffen Eiden
0764460eaf rust/pv: Provide access for SecretList members
Adds getter for SecretList and SecretEntry.
Adds enum to represent secret types.
Add Iterator functionality for SecretList.

While at it, make the datatype of the capacity of the list transparent
for users.

Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2024-01-12 14:10:52 +01:00
Steffen Eiden
6fd02279da rust/Makefile: Fix CC/AR variables for TEST_TARGETS
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2024-01-12 14:10:52 +01:00
Yaakov Selkowitz
6274294bc5 libkmipclient: Fix build with libxml2-2.12.0
https://gitlab.gnome.org/GNOME/libxml2/-/releases/v2.12.0

"Several cyclic dependencies in public header files were fixed. As a
result, certain headers won't include other headers as before."

Closes: https://github.com/ibm-s390-linux/s390-tools/pull/160
Signed-off-by: Yaakov Selkowitz <yselkowi@redhat.com>
Reviewed-by: Ingo Franzki <ifranzki@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2023-12-20 18:32:34 +01:00
Steffen Maier
27708026d4 zdev/dracut: state requirement for sed during kdump initrd build
v2.27.0 commit 73c46a3056 ("zdev/dracut: fix kdump by only activating
required devices") started using `sed` during kdump initrd build time.
It works, but explicitly add the tool as a requirement.

Fixes: 73c46a3056 ("zdev/dracut: fix kdump by only activating required devices")
Closes: https://github.com/ibm-s390-linux/s390-tools/pull/158
Reviewed-by: Marc Hartmayer <mhartmay@linux.ibm.com>
Acked-by: Vineeth Vijayan <vneethv@linux.ibm.com>
Acked-by: Peter Oberparleiter <oberpar@linux.ibm.com>
Signed-off-by: Steffen Maier <maier@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2023-12-14 15:25:22 +01:00
Steffen Maier
849aa5b105 zdev/dracut: put temporary files under the subdir ${DRACUT_TMPDIR}
This way, dracut cleans up everything just in case the "rm" here fails.

Github-ID: https://github.com/ibm-s390-linux/s390-tools/pull/158
Reviewed-by: Peter Oberparleiter <oberpar@linux.ibm.com>
Acked-by: Vineeth Vijayan <vneethv@linux.ibm.com>
Acked-by: Peter Oberparleiter <oberpar@linux.ibm.com>
Signed-off-by: Steffen Maier <maier@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2023-12-14 15:25:22 +01:00
Steffen Maier
0be83bfbba dbginfo.sh: collect migration backups & logs from old persistent config
Complements commits
("zdev/dracut: add rd.zfcp cmdline option handling")
("zdev/dracut: add rd.dasd cmdline option handling")
and s390utils commits
("zfcp: migrate to consolidated persistent device config with
zdev (#1937046,#1937048)")
("dasd: migrate to consolidated persistent device config with
zdev (#1937046,#1937048)")
.

Github-ID: https://github.com/ibm-s390-linux/s390-tools/pull/158
Reviewed-by: Fedor Loshakov <loshakov@linux.ibm.com>
Acked-by: Vineeth Vijayan <vneethv@linux.ibm.com>
Acked-by: Peter Oberparleiter <oberpar@linux.ibm.com>
Signed-off-by: Steffen Maier <maier@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2023-12-14 15:25:22 +01:00
Steffen Maier
f8592be43d dbginfo.sh: collect file list and config files from initrd-s
For each initrd, collect verbose content listing as well as
the content of files matching the file glob patterns in $CONFIGFILES.
For dracut-squash such as RHEL kdump, this includes squashfs content.

Code is intentionally compatible with dash, so no bashisms.

Ubuntu initramfs has zdev persistent device configuration udev rules under
/lib/udev/rules.d/ (as opposed to /etc/udev/rules.d/).
Ubuntu initramfs stores configuration under /conf/.
Ubuntu kdump-tools have initrds under /var/lib/kdump/.

SLES auxiliary boot stage with grub2-s390x-emu has initrds under /boot/zipl/.

Dracut initramfs stores configuration under /etc/cmdline.d/ and /etc/conf.d/.
Dracut kdump have initrds under /boot/.

Github-ID: https://github.com/ibm-s390-linux/s390-tools/pull/158
Reviewed-by: Fedor Loshakov <loshakov@linux.ibm.com>
Acked-by: Vineeth Vijayan <vneethv@linux.ibm.com>
Acked-by: Peter Oberparleiter <oberpar@linux.ibm.com>
Signed-off-by: Steffen Maier <maier@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2023-12-14 15:25:22 +01:00
Steffen Maier
647ad51423 dbginfo.sh: collect other config data from zdev in initrd
Complements commit ("zdev/dracut: retain early persistent config over
switch root").

Github-ID: https://github.com/ibm-s390-linux/s390-tools/pull/158
Reviewed-by: Fedor Loshakov <loshakov@linux.ibm.com>
Acked-by: Vineeth Vijayan <vneethv@linux.ibm.com>
Acked-by: Peter Oberparleiter <oberpar@linux.ibm.com>
Signed-off-by: Steffen Maier <maier@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2023-12-14 15:25:22 +01:00
Steffen Maier
73f51e45a8 zdev: add helper to convert from zdev config to rd.znet
Converts zdev configuration into the syntax of the dracut cmdline option
rd.znet. Only znet options with non-default values are emitted. The result
string occurs on stdout. It represents one device-specification for the
given network interface name.

Example:
/lib/s390-tools/zdev-to-rd.znet persistent encbdf0
qeth,0.0.bdf0,0.0.bdf1,0.0.bdf2,layer2=1

User:
https://github.com/rhinstaller/anaconda/pull/5250
to generate dracut cmdline entries such as
rd.znet=qeth,0.0.bdf0,0.0.bdf1,0.0.bdf2,layer2=1

Github-ID: https://github.com/ibm-s390-linux/s390-tools/pull/158
Acked-by: Vineeth Vijayan <vneethv@linux.ibm.com>
Acked-by: Peter Oberparleiter <oberpar@linux.ibm.com>
Signed-off-by: Steffen Maier <maier@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2023-12-14 15:25:22 +01:00
Steffen Maier
bc4f455151 zdev/dracut: retain early persistent config over switch root
Useful for debugging (what happened during early boot).

Also useful for distro installers, which can import the early config into
their own environment early after starting:
chzdev --import /run/zdev.initrd.config --persistent --yes \
       --no-root-update --force --verbose

After that, distro installers can modify/add the device config based
on interactive or unattended installation choices using
chzdev --enable --active --persistent ...

Finally, distro installers can likewise transfer the entire device config
to the installed system mounted under $SYSROOT:
chzdev --export /tmp/zdev.config --all --type --persistent --verbose

Github-ID: https://github.com/ibm-s390-linux/s390-tools/pull/158
Acked-by: Vineeth Vijayan <vneethv@linux.ibm.com>
Acked-by: Peter Oberparleiter <oberpar@linux.ibm.com>
Signed-off-by: Steffen Maier <maier@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2023-12-14 15:25:22 +01:00
Steffen Maier
b4b5e0b6aa zdev/dracut: produce helpful user information on parsing rd.zfcp
It builds on the previously added helper function in parse-dasd.sh, which
gets sourced first by dracut so the function is also available to the later
parsing hook parse-zfcp.sh.

Github-ID: https://github.com/ibm-s390-linux/s390-tools/pull/158
Acked-by: Vineeth Vijayan <vneethv@linux.ibm.com>
Acked-by: Peter Oberparleiter <oberpar@linux.ibm.com>
Signed-off-by: Steffen Maier <maier@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2023-12-14 15:25:22 +01:00
Steffen Maier
9927023680 zdev/dracut: add rd.dasd cmdline option handling
Add parsing of dracut cmdline option "rd.dasd=..." at initrd runtime.
It delegates configuration to chzdev.

Implement `dracut --print-cmdline` and `dracut --hostonly-cmdline` for
initrd build time. Emit an rd.dasd option for each DASD disk in dracut's
device dependency graph (to mount the root-fs, or to access the kdump
target).

This allows a distribution independent device configuration.
Configuration is consistent by using chzdev as backend.
It also prevents duplicate activations of the same device.

Copy the udev rule for unique DASD device nodes under /dev/disk/
[59-dasd.rules] into initrd for the same functionality like 95dasd or
95dasd_rules.

Along with the existing functionality of zdev/dracut, it makes the
following dracut modules superfluous:
https://github.com/dracutdevs/dracut/tree/master/modules.d/95dasd
https://github.com/dracutdevs/dracut/tree/master/modules.d/95dasd_mod
https://github.com/dracutdevs/dracut/tree/master/modules.d/95dasd_rules

Github-ID: https://github.com/ibm-s390-linux/s390-tools/pull/158
Acked-by: Vineeth Vijayan <vneethv@linux.ibm.com>
Acked-by: Peter Oberparleiter <oberpar@linux.ibm.com>
Signed-off-by: Steffen Maier <maier@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2023-12-14 15:25:22 +01:00
Steffen Maier
9b2fb1d4d2 zdev: add helper to convert from dasd_mod.dasd to zdev config
Shell library, which can be sourced by other POSIX compatible shell
scripts. Provide helper function parsing its stdin based on the syntax of
kernel device driver parameter dasd_mod.dasd= and invoking chzdev to
produce corresponding persistent device configurations. The helper function
takes one argument, which is either "globals" or "ranges". For a complete
configuration, call the function twice, first with "globals" and then with
"ranges".

The new script library file should be packaged in a core (sub)package of
s390-tools so the script is available for initrd environments.

Users with examples:

Subsequent commit ("zdev/dracut: add rd.dasd parsing") introduces
zdev/dracut/95zdev/parse-dasd.sh performing:
zdev_parse_rd_dasd | zdev_parse_dasd_list globals 2>&1 | zdev_vinfo
zdev_parse_rd_dasd | zdev_parse_dasd_list ranges 2>&1 | zdev_vinfo

https://github.com/dracutdevs/dracut/pull/2534 updates
modules.d/80cms/cmssetup.sh performing:
echo "$DASD" | zdev_parse_dasd_list globals 2>&1 | vinfo
echo "$DASD" | zdev_parse_dasd_list ranges 2>&1 | vinfo

The parsing code for rd.dasd using the same syntax as dasd_mod.dasd=
is inspired by the own implementation that used to be in linuxrc.s390
of https://github.com/rhinstaller/anaconda.
https://web.archive.org/web/20190721154444/https://www.redhat.com/archives/anaconda-devel-list/2009-February/msg00392.html
https://www.spinics.net/linux/fedora/anaconda-devel/msg08316.html
("Re: Improved linuxrc.s390 (third try)")
=> 9249e40f42ff ("IBM improvements to linuxrc.s390 (#475350)")
https://web.archive.org/web/20190721150254/https://www.redhat.com/archives/anaconda-devel-list/2009-July/msg00310.html
=> 5f0fcf6688d0 ("Update linuxrc.s390 and friends to reflect review comments.")
https://web.archive.org/web/20190721125255/https://www.redhat.com/archives/anaconda-devel-list/2009-August/msg00158.html
=> 523095c86876 ("Handle activation of DASDs in linuxrc.s390 since loader no longer works")

Github-ID: https://github.com/ibm-s390-linux/s390-tools/pull/158
Acked-by: Vineeth Vijayan <vneethv@linux.ibm.com>
Acked-by: Peter Oberparleiter <oberpar@linux.ibm.com>
Signed-off-by: Steffen Maier <maier@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2023-12-14 15:25:22 +01:00
Steffen Maier
689b894506 zdev: add helper to convert from zdev config to dasd_mod.dasd
Converts zdev configuration into the syntax of the kernel module parameter
dasd_mod.dasd. Only DASD options with non-default values are emitted. The
result string occurs on stdout. It represents one device-specification for
the given DASD device bus-ID.

Example:
/lib/s390-tools/zdev-to-dasd_mod.dasd persistent 0.0.da5d
0.0.da5d(erplog)

User:
https://github.com/storaged-project/blivet/pull/1162
to generate dracut cmdline entries such as rd.dasd=0.0.da5d(erplog)

Github-ID: https://github.com/ibm-s390-linux/s390-tools/pull/158
Acked-by: Vineeth Vijayan <vneethv@linux.ibm.com>
Acked-by: Peter Oberparleiter <oberpar@linux.ibm.com>
Signed-off-by: Steffen Maier <maier@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2023-12-14 15:25:22 +01:00
Steffen Maier
06a30ae529 zdev/dracut: add rd.zfcp cmdline option handling
Add parsing of dracut cmdline option "rd.zfcp=..." at initrd runtime.
It delegates configuration to chzdev.

Implement `dracut --print-cmdline` and `dracut --hostonly-cmdline` for
initrd build time. Emit an rd.zfcp option for each zfcp-attached SCSI disk
in dracut's device dependency graph (to mount the root-fs, or to access the
kdump target).

This allows a distribution independent device configuration.
Configuration is consistent by using chzdev as backend.
It also prevents duplicate activations of the same device.

Along with the existing functionality of zdev/dracut, it makes the
following dracut modules superfluous:
https://github.com/dracutdevs/dracut/tree/master/modules.d/95zfcp
[rd.zfcp.conf is no longer needed and thus ignored here; the preceding
 ("zdev/dracut: fix marking hostonly files so delete option works")
 makes rd.hostonly=0 work as a generic replacement]
https://github.com/dracutdevs/dracut/tree/master/modules.d/95zfcp_rules

Github-ID: https://github.com/ibm-s390-linux/s390-tools/pull/158
Acked-by: Vineeth Vijayan <vneethv@linux.ibm.com>
Acked-by: Peter Oberparleiter <oberpar@linux.ibm.com>
Signed-off-by: Steffen Maier <maier@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2023-12-14 15:25:22 +01:00
Steffen Maier
ed106d7f28 zdev/dracut: add site support for early devices to the kdump case
The existing code can already determine the required devices for the
actively running system, but this does not work for execution environments
in different (disaster recovery) sites.

Kdump likely does not notice when running in a different site and does not
re-generate the kdump initrd (because the kdump config itself does not
change).

The new code allows users to explicitly specify devices required for early
boot in initrd on different sites. Assuming this is a small number of
devices and could even overlap between the root-fs and the kdump case, do
not further distinguish those two sets of devices, but simply use the same
marker attribute "zdev:early=1" for both cases. With all this site
information available at the (initial) kdump initrd generation, the
resulting kdump initrd can work on any site without having to re-generate.

Complements commit 3c7adcc3c81d ("zdev: dracut: modifiy the
module-setup.sh").

Github-ID: https://github.com/ibm-s390-linux/s390-tools/pull/158
Reviewed-by: Vineeth Vijayan <vneethv@linux.ibm.com>
Acked-by: Vineeth Vijayan <vneethv@linux.ibm.com>
Acked-by: Peter Oberparleiter <oberpar@linux.ibm.com>
Signed-off-by: Steffen Maier <maier@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2023-12-14 15:25:22 +01:00
Steffen Maier
9d08fd8c7e zdev/dracut: import persistent config of early devices into kdump initrd
The existing code can already determine the required devices for the
actively running system. In case users would have a need to configure
additional devices for early boot in kdump initrd, import those devices
marked with "zdev:early=1" for completeness. Assuming this is a small
number of devices and could even overlap between the root-fs and the kdump
case, do not further distinguish those two sets of devices.

Github-ID: https://github.com/ibm-s390-linux/s390-tools/pull/158
Reviewed-by: Vineeth Vijayan <vneethv@linux.ibm.com>
Acked-by: Vineeth Vijayan <vneethv@linux.ibm.com>
Acked-by: Peter Oberparleiter <oberpar@linux.ibm.com>
Signed-off-by: Steffen Maier <maier@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2023-12-14 15:25:22 +01:00
Steffen Maier
7e8126704b zdev/dracut: fix to not depend on existing chzdev persistent config
Dracut knows exactly what dependencies it needs. Do not break it by
accidentally missing parts in the chzdev persistent configuration
(/etc/udev/rules.d/41-*.rules). Depicted as mathematical sets:

+-------------------------------+
| active                        |
|            +---------------------------------+
|            |                  |   persistent |
|   +------------------------------------+     |
|   |      root / kdump dependencies     |     |
|   | was    |                  |        |     |
|   | missing|   zdev so far    |   DR   |     |
|   +------------------------------------+     |
|            |                  |              |
+-------------------------------+              |
             |                                 |
             +---------------------------------+

Github-ID: https://github.com/ibm-s390-linux/s390-tools/pull/158
Reviewed-by: Vineeth Vijayan <vneethv@linux.ibm.com>
Acked-by: Vineeth Vijayan <vneethv@linux.ibm.com>
Acked-by: Peter Oberparleiter <oberpar@linux.ibm.com>
Signed-off-by: Steffen Maier <maier@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2023-12-14 15:25:22 +01:00
Steffen Maier
d96767ee45 zdev/dracut: fix marking hostonly files so delete option works
Chzdev import generates persistent device configuration which are hostonly
files. Dracut cmdline rd.hostonly=0 should be able to delete those.

Github-ID: https://github.com/ibm-s390-linux/s390-tools/pull/158
Reviewed-by: Alexander Egorenkov <egorenar@linux.ibm.com>
Acked-by: Vineeth Vijayan <vneethv@linux.ibm.com>
Acked-by: Peter Oberparleiter <oberpar@linux.ibm.com>
Signed-off-by: Steffen Maier <maier@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2023-12-14 15:25:22 +01:00
Steffen Maier
63f31bf73e zdev/dracut: add field debugging capabilities with logging
Additional debugging output can be generated with e.g. dracut option
"--stdlog 5" (or short -L5). It shows the chzdev export result, the output
of chzdev imports, and an overview of the resulting persistent config
within the initrd.
On systems, which default to using dracut option "--quiet", you might need
an additional "--verbose" to counter "--quiet" so -L5 has effect.
Typically combined with "--debug" to get a shell trace from building an
initrd (Note: --debug does not increase the log levels).

Github-ID: https://github.com/ibm-s390-linux/s390-tools/pull/158
Reviewed-by: Alexander Egorenkov <egorenar@linux.ibm.com>
Reviewed-by: Vineeth Vijayan <vneethv@linux.ibm.com>
Acked-by: Vineeth Vijayan <vneethv@linux.ibm.com>
Acked-by: Peter Oberparleiter <oberpar@linux.ibm.com>
Signed-off-by: Steffen Maier <maier@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2023-12-14 15:25:22 +01:00
Steffen Maier
7ecfe2353f zdev/dracut: respect hostonly mode choice
Otherwise it risks adding arbitrary persistent configuration of the build
host into generic dracut initrds such as those for distro installers.

Github-ID: https://github.com/ibm-s390-linux/s390-tools/pull/158
Reviewed-by: Marc Hartmayer <mhartmay@linux.ibm.com>
Acked-by: Vineeth Vijayan <vneethv@linux.ibm.com>
Acked-by: Peter Oberparleiter <oberpar@linux.ibm.com>
Signed-off-by: Steffen Maier <maier@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2023-12-14 15:25:22 +01:00
Steffen Maier
7bec672c7e zdev/dracut: fix kernel module install even for hostonly mode
Otherwise, dracut skips modules that are "not hostonly", i.e. not used /
loaded on the system when running dracut to build an initrd.
Without the fix, zdev device auto configuration only works for device
types for which a device driver happened to be loaded when building an
initrd. Likewise for specifying rd.* boot options.

Hostonly mode is often the default in Linux distributions.
Make zdev device auto configuration work nonetheless without users
having to know about and tweak dracut modes for initrd generation.

On the typical sloppy hostonly mode, the code disables hostonly for
installing kernel modules. This covers building regular initrds, where zdev
device auto configuration should even work for device types, for which
device drivers were not loaded when an initrd was generated. This can
happen when new devices of new types are configured for a DPM logical
partition. Also, users could want to start using dracut cmdline options
rd.{dasd,zfcp,znet} for a device type that was not used when the initrd was
built.

The special strict hostonly mode is used by some kdump implementations. In
that case, hostonly remains in effect intentionally because only support
for the really required devices as determined by dracut module 95zdev-kdump
should be included in a kdump initrd due to the memory-constrained kdump
environment. Cf. commit 73c46a3056 ("zdev/dracut: fix kdump by only
activating required devices"), which also provides more references on
strict hostonly mode. Even for non-kdump cases, let strict hostonly mode be
effective here in 95zdev in case some future use case appears for this
special mode beyond kdump.

Github-ID: https://github.com/ibm-s390-linux/s390-tools/pull/158
Reviewed-by: Alexander Egorenkov <egorenar@linux.ibm.com>
Acked-by: Vineeth Vijayan <vneethv@linux.ibm.com>
Acked-by: Peter Oberparleiter <oberpar@linux.ibm.com>
Signed-off-by: Steffen Maier <maier@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2023-12-14 15:25:22 +01:00
Steffen Maier
5aac5deb75 zdev/dracut/module-setup.sh: add comments for dracut called functions
Add a short explaining comment to those functions, which are just callbacks
invoked by the dracut core infrastructure. So nobody wonders how these
functions get called, especially if there are no local callers here in the
code.

Cf. dracut commit
https://github.com/dracutdevs/dracut/commit/8bcfd683bd0c
("*/module-setup.sh: add comments for dracut called functions")

For more information about the dracut module callback functions:
https://github.com/dracutdevs/dracut/blob/master/man/dracut.modules.7.asc#writing-a-module
https://github.com/dracutdevs/dracut/blob/master/docs/HACKING.md#writing-modules

Github-ID: https://github.com/ibm-s390-linux/s390-tools/pull/158
Reviewed-by: Alexander Egorenkov <egorenar@linux.ibm.com>
Reviewed-by: Marc Hartmayer <mhartmay@linux.ibm.com>
Reviewed-by: Vineeth Vijayan <vneethv@linux.ibm.com>
Acked-by: Vineeth Vijayan <vneethv@linux.ibm.com>
Acked-by: Peter Oberparleiter <oberpar@linux.ibm.com>
Signed-off-by: Steffen Maier <maier@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2023-12-14 15:25:22 +01:00
Steffen Maier
54e016ae71 zdev/dracut: use namespace prefix in functions of zdev-lib.sh
Use namespace prefix in functions of zdev-lib.sh to avoid collisions with
different modules being sourced in the shell.

Github-ID: https://github.com/ibm-s390-linux/s390-tools/pull/158
Reviewed-by: Alexander Egorenkov <egorenar@linux.ibm.com>
Reviewed-by: Marc Hartmayer <mhartmay@linux.ibm.com>
Acked-by: Vineeth Vijayan <vneethv@linux.ibm.com>
Acked-by: Peter Oberparleiter <oberpar@linux.ibm.com>
Signed-off-by: Steffen Maier <maier@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2023-12-14 15:25:22 +01:00
Steffen Maier
6b53378839 zdev/dracut/95zdev: non-functional changes and annotations for shellcheck
Clears all shellcheck reports in old existing code so newly added code can
use shellcheck to find newly introduced suboptimal shell code.

Github-ID: https://github.com/ibm-s390-linux/s390-tools/pull/158
Reviewed-by: Marc Hartmayer <mhartmay@linux.ibm.com>
Reviewed-by: Vineeth Vijayan <vneethv@linux.ibm.com>
Acked-by: Vineeth Vijayan <vneethv@linux.ibm.com>
Acked-by: Peter Oberparleiter <oberpar@linux.ibm.com>
Signed-off-by: Steffen Maier <maier@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2023-12-14 15:25:22 +01:00
Steffen Maier
1266f86444 zdev/dracut: add shellcheck annotations for recently added code
The annotations and the one non-functional code change in
zdev/dracut/95zdev-kdump/module-setup.sh check() clear all shellcheck
reports.

complements
73c46a3056 ("zdev/dracut: fix kdump by only activating required devices")

Github-ID: https://github.com/ibm-s390-linux/s390-tools/pull/158
Reviewed-by: Vineeth Vijayan <vneethv@linux.ibm.com>
Acked-by: Vineeth Vijayan <vneethv@linux.ibm.com>
Acked-by: Peter Oberparleiter <oberpar@linux.ibm.com>
Signed-off-by: Steffen Maier <maier@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2023-12-14 15:25:22 +01:00
Steffen Maier
231c02cdeb zdev: fix typo for example in chzdev man page
Github-ID: https://github.com/ibm-s390-linux/s390-tools/pull/158
Reviewed-by: Alexander Egorenkov <egorenar@linux.ibm.com>
Reviewed-by: Marc Hartmayer <mhartmay@linux.ibm.com>
Acked-by: Vineeth Vijayan <vneethv@linux.ibm.com>
Acked-by: Peter Oberparleiter <oberpar@linux.ibm.com>
Signed-off-by: Steffen Maier <maier@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2023-12-14 15:25:22 +01:00
Marc Hartmayer
454a8d9d7b systemd/cpi.service: add missing Requires=sysinit.target
`After=sysinit.target` configures only the order but not that the
systemd unit is required and must be activated. Therefore add the
missing `Requires=sysinit.target`. It probably does not fix anything
because sysinit.target should always be activated anyway but it's
cleaner to have it. See
https://www.freedesktop.org/software/systemd/man/systemd.unit.html for
details.

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-12-14 15:25:22 +01:00
Marc Hartmayer
bbe92b9cd3 systemd/cpi.service: fix race condition between stopping unit and shutdown
There is a race condition between stopping the cpi systemd service and
systemd getting a request to exit the main loop, so it may proceed with
shutdown before all pending stop jobs have been processed. Therefore,
add `Before=shutdown.target` as it would have been added by
the (deactivated) default dependencies. [1]

While at it, sort the directives.

[1] https://www.freedesktop.org/software/systemd/man/systemd.service.html#Default%20Dependencies

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-12-14 15:25:22 +01:00
Jan Höppner
7bb41732fb Prepare for next release
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2023-12-01 15:03:39 +01:00
104 changed files with 7116 additions and 1814 deletions

View File

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

35
.pre-commit-config.yaml Normal file
View File

@@ -0,0 +1,35 @@
---
exclude: \.(crt|crl)$
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.1.0
hooks:
- id: check-merge-conflict
- id: end-of-file-fixer
- id: mixed-line-ending
- id: trailing-whitespace
- id: check-executables-have-shebangs
- id: check-shebang-scripts-are-executable
exclude_types: ['rust']
- repo: local
hooks:
- id: git-clang-format
name: git-clang-format
description: Run git-clang-format
entry: git
args: [clang-format, --staged, --]
pass_filenames: true
language: system
require_serial: true
minimum_pre_commit_version: "2.9.0"
types_or: [c++, c]
- repo: https://github.com/codespell-project/codespell
rev: v2.2.1
hooks:
- id: codespell
exclude_types: ['rust']
- repo: https://github.com/jumanjihouse/pre-commit-hooks
rev: 3.0.0
hooks:
- id: shellcheck
args: ["--external-sources"]

View File

@@ -36,6 +36,7 @@ List of all individuals having contributed content to s390-tools
- Farhan Ali
- Fedor Loshakov
- Felix Beck
- Finn Callies
- Frank Blaschka
- Frank Heimes
- Frank Munzert
@@ -127,6 +128,7 @@ List of all individuals having contributed content to s390-tools
- Thomas Spatzier
- Thomas Weber
- Thorsten Winkler
- Tobias Huschle
- Tuan Hoang
- Ursula Braun
- Utz Bacher
@@ -137,3 +139,4 @@ List of all individuals having contributed content to s390-tools
- Volker Sameske
- Wenjia Zhang
- Wolfgang Taphorn
- Yaakov Selkowitz

View File

@@ -1,6 +1,31 @@
Release history for s390-tools (MIT version)
--------------------------------------------
* __v2.31.0 (2024-02-02)__
For Linux kernel version: 6.7
General:
- common.mak: Set default C/C++ standard to gnu11/gnu++11
Add new tools / libraries:
- pvapconfig: Tool to automatically configure APQNs in SE KVM guests
- s390-tools: Provide pre-commit configuration
Changes of existing tools:
- cpuplugd: Adjust to CPU 0 being no longer hotpluggable
- dbginfo.sh: Check for Dynamic Partition Mode
- dbginfo.sh: Update man page and copyright
- rust/pv: Add user-data signing and verifying
- rust/pvsecret: Add user defined signatures and verifications
- zdev/dracut: Consolidate device configuration
Bug Fixes:
- dbginfo.sh: Fix relative path on script copy
- libkmipclient: Fix build with libxml2-2.12.0
- pvsecret: Fix panic if empty file is used as host key document
- rust/pv: Fix 'elided_lifetimes_in_associated_constant' warning
* __v2.30.0 (2023-12-01)__
For Linux kernel version: 6.6
@@ -22,6 +47,7 @@ Release history for s390-tools (MIT version)
- zipl/boot: compile the bootloaders only if HOST_ARCH is s390x
- zipl: Add --no-compress option to zipl command
- zkey: Also check for deconfigured and check-stopped cards
- dbginfo.sh: fix relative path on script copy
Bug Fixes:
- ap_tools/ap-check: handle get-attributes between pre and post event

View File

@@ -32,7 +32,7 @@ endif
# The variable "DISTRELEASE" should be overwritten in rpm spec files with:
# "make DISTRELEASE=%{release}" and "make install DISTRELEASE=%{release}"
VERSION = 2
RELEASE = 30
RELEASE = 31
PATCHLEVEL = 0
DISTRELEASE = build-$(shell date +%Y%m%d)
S390_TOOLS_RELEASE = $(VERSION).$(RELEASE).$(PATCHLEVEL)-$(DISTRELEASE)
@@ -304,7 +304,7 @@ ZFCPDUMP_FLAVOR = zfcpdump
export ZFCPDUMP_DIR ZFCPDUMP_IMAGE ZFCPDUMP_INITRD ZFCPDUMP_FLAVOR
CFLAGS ?= $(DEFAULT_CFLAGS) $(OPT_FLAGS)
CFLAGS_FOR_BUILD ?= $(DEFAULT_CFLAGS) $(OPT_FLAGS)
CFLAGS_FOR_BUILD ?= -std=gnu11 $(DEFAULT_CFLAGS) $(OPT_FLAGS)
CPPFLAGS ?= $(DEFAULT_CPPFLAGS)
LDFLAGS ?= $(DEFAULT_LDFLAGS)
@@ -313,14 +313,14 @@ ALL_CFLAGS = -DS390_TOOLS_RELEASE=$(S390_TOOLS_RELEASE) \
-DS390_TOOLS_DATADIR=$(TOOLS_DATADIR) \
-DS390_TOOLS_SYSCONFDIR=$(SYSCONFDIR) \
-DS390_TOOLS_BINDIR=$(BINDIR) \
$(CFLAGS)
-std=gnu11 $(CFLAGS)
CXXFLAGS ?= $(DEFAULT_CFLAGS) $(OPT_FLAGS)
ALL_CXXFLAGS = -DS390_TOOLS_RELEASE=$(S390_TOOLS_RELEASE) \
-DS390_TOOLS_LIBDIR=$(TOOLS_LIBDIR) \
-DS390_TOOLS_DATADIR=$(TOOLS_DATADIR) \
-DS390_TOOLS_SYSCONFDIR=$(SYSCONFDIR) \
-DS390_TOOLS_BINDIR=$(BINDIR) \
$(CXXFLAGS)
-std=gnu++11 $(CXXFLAGS)
ALL_CPPFLAGS = -I $(rootdir)include $(CPPFLAGS)
ALL_LDFLAGS = $(LDFLAGS)

View File

@@ -355,13 +355,13 @@ PCKMO DES,
.IP \(bu
PCKMO 2key TDES,
.IP \(bu
PCMKO TDES,
PCKMO TDES,
.IP \(bu
PCKMO AES 128bit,
.IP \(bu
PCKMO AES 192bit,
.IP \(bu
PCMKO AES 256bit,
PCKMO AES 256bit,
.IP \(bu
PCKMO ECC P256,
.IP \(bu

View File

@@ -204,10 +204,10 @@ static const char *const pai_str[] = {
[142] = "KDSA EdDSA Sign Ed448 protected key",
[143] = "PCKMO DES",
[144] = "PCKMO 2key TDES",
[145] = "PCMKO TDES",
[145] = "PCKMO TDES",
[146] = "PCKMO AES 128bit",
[147] = "PCKMO AES 192bit",
[148] = "PCMKO AES 256bit",
[148] = "PCKMO AES 256bit",
[149] = "PCKMO ECC P256",
[150] = "PCKMO ECC P384",
[151] = "PCKMO ECC P521",

View File

@@ -320,7 +320,7 @@ static void readmap(int fd)
* ring buffer per event, sleep some short time and always read all
* ring buffer for new contents.
*/
static void collect(unsigned long cnt)
static int collect(unsigned long cnt)
{
fd_set r_fds, e_fds, a_fds;
struct pai_event *p;
@@ -328,6 +328,7 @@ static void collect(unsigned long cnt)
int rc, max_fd;
do {
rc = -1;
max_fd = -1;
tv.tv_sec = read_interval / 1000;
tv.tv_usec = (1000 * read_interval) % 1000000;
@@ -357,6 +358,7 @@ static void collect(unsigned long cnt)
}
}
} while (rc != -1 && --cnt > 0);
return rc;
}
static void lookup_event(__u64 evtnum, __u16 ctr, __u64 value)
@@ -449,6 +451,11 @@ static void evt_show(__u64 evtnum, const char *evtsel, struct pai_event_out *ev)
ev->u.s_comm.tid);
break;
case PERF_RECORD_SWITCH:
printf("cs-%s",
(ev->misc & PERF_RECORD_MISC_SWITCH_OUT) ? "out" : "in");
break;
case PERF_RECORD_SWITCH_CPU_WIDE:
if (ev->misc & PERF_RECORD_MISC_SWITCH_OUT) {
short p = PERF_RECORD_MISC_SWITCH_OUT_PREEMPT;
@@ -549,6 +556,9 @@ static int evt_scan(char *fn, unsigned char *buf, size_t len,
offset -= sizeof(__u64);
break;
case PERF_RECORD_SWITCH:
break;
case PERF_RECORD_SWITCH_CPU_WIDE:
memcpy(&ev.u, buf + offset, sizeof(ev.u.s_cs));
offset += sizeof(ev.u.s_cs);
@@ -583,7 +593,7 @@ static int evt_scan(char *fn, unsigned char *buf, size_t len,
break;
default:
printf("unknown header-type %d ", hdr->type);
printf("unknown header-type %d\n", hdr->type);
offset += hdr->size - sizeof(*hdr);
goto bypass;
}
@@ -1115,12 +1125,12 @@ int main(int argc, char **argv)
ev_install(group);
ev_enable();
collect(loop_count);
ch = collect(loop_count);
ev_disable();
ev_deinstall();
ev_dealloc();
return EXIT_SUCCESS;
return ch < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
}
/* Must be reporting */

View File

@@ -25,7 +25,7 @@ int get_numcpus()
for (i = 0; ; i++) {
/* check whether file exists and is readable */
sprintf(path, "/sys/devices/system/cpu/cpu%d/online", i);
sprintf(path, "/sys/devices/system/cpu/cpu%d", i);
if (access(path, R_OK) == 0)
number++;
else
@@ -45,11 +45,13 @@ int get_num_online_cpus()
int status = 0;
int value_of_onlinefile, rc;
for (i = 0; i <= get_numcpus(); i++) {
for (i = 0; i < get_numcpus(); i++) {
/* check wether file exists and is readable */
sprintf(path, "/sys/devices/system/cpu/cpu%d/online", i);
if (access(path, R_OK) != 0)
if (access(path, R_OK) != 0) {
status++;
continue;
}
filp = fopen(path, "r");
if (!filp)
cpuplugd_exit("Cannot open cpu online file: "
@@ -101,10 +103,8 @@ int hotplug(int cpuid)
cpuid);
return -1;
}
} else {
cpuplugd_error("hotplugging cpu with id %d failed\n", cpuid);
return -1;
}
cpuplugd_debug("cpu with id %d cannot be hotplugged\n", cpuid);
return -1;
}
@@ -135,9 +135,8 @@ int hotunplug(int cpuid)
fclose(filp);
if (state == 0)
return 1;
} else {
cpuplugd_error("unplugging cpu with id %d failed\n", cpuid);
}
cpuplugd_debug("cpu with id %d cannot be hotunplugged\n", cpuid);
return retval;
}
@@ -163,6 +162,8 @@ int is_online(int cpuid)
retval = 0;
}
fclose(filp);
} else {
retval = 1;
}
return retval;
}

View File

@@ -1,7 +1,7 @@
# Common definitions
include ../../common.mak
ALL_CPPFLAGS += -I../include -std=gnu99 -Wno-unused-parameter
ALL_CPPFLAGS += -I../include -Wno-unused-parameter
LDLIBS += -lpthread -lrt
ifneq ($(HAVE_ZLIB),0)
ALL_CPPFLAGS += -DHAVE_ZLIB

View File

@@ -27,7 +27,7 @@ $(bin_PROGRAM)_SRCS := $(bin_PROGRAM).c pv/pv_stage3.c pv/pv_image.c \
$(NULL)
$(bin_PROGRAM)_OBJS := $($(bin_PROGRAM)_SRCS:.c=.o)
ALL_CFLAGS += -std=gnu11 -DPKGDATADIR=$(PKGDATADIR) \
ALL_CFLAGS += -DPKGDATADIR=$(PKGDATADIR) \
$(GLIB2_CFLAGS) $(LIBCRYPTO_CFLAGS) $(LIBCURL_CFLAGS) \
-DOPENSSL_API_COMPAT=0x10100000L \
$(WARNINGS) \

View File

@@ -16,6 +16,7 @@
#include <openssl/ssl.h>
#include <json-c/json.h>
#include <libxml/parser.h>
#include <libxml/tree.h>
#include <curl/curl.h>

View File

@@ -21,8 +21,7 @@ WARNINGS := -Wall -Wextra -Wshadow \
-Wno-unused-function -Wno-unused-parameter -Wno-unused-variable \
$(NULL)
ALL_CFLAGS += -std=gnu11 \
-DOPENSSL_API_COMPAT=0x10101000L \
ALL_CFLAGS += -DOPENSSL_API_COMPAT=0x10101000L \
$(GLIB2_CFLAGS) \
$(LIBCRYPTO_CFLAGS) \
$(LIBCURL_CFLAGS) \

View File

@@ -35,8 +35,7 @@ LIBCRYPTO_LIBS := $(shell $(PKG_CONFIG) --silence-errors --libs libcrypto)
LIBCURL_CFLAGS := $(shell $(PKG_CONFIG) --silence-errors --cflags libcurl)
LIBCURL_LIBS := $(shell $(PKG_CONFIG) --silence-errors --libs libcurl)
ALL_CFLAGS += -std=gnu11 \
-DPKGDATADIR=$(PKGDATADIR) \
ALL_CFLAGS += -DPKGDATADIR=$(PKGDATADIR) \
-DOPENSSL_API_COMPAT=0x10101000L \
$(GLIB2_CFLAGS) \
$(LIBCRYPTO_CFLAGS) \

89
rust/Cargo.lock generated
View File

@@ -182,7 +182,7 @@ dependencies = [
"heck",
"proc-macro2",
"quote",
"syn 2.0.18",
"syn",
]
[[package]]
@@ -340,7 +340,7 @@ checksum = "53b153fd91e4b0147f4aced87be237c98248656bb01050b96bf3ee89220a8ddb"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.18",
"syn",
]
[[package]]
@@ -651,9 +651,9 @@ dependencies = [
[[package]]
name = "once_cell"
version = "1.18.0"
version = "1.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d"
checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
[[package]]
name = "openssl"
@@ -678,7 +678,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.18",
"syn",
]
[[package]]
@@ -765,33 +765,65 @@ checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
[[package]]
name = "proc-macro2"
version = "1.0.60"
version = "1.0.75"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dec2b086b7a862cf4de201096214fa870344cf922b2b30c167badb3af3195406"
checksum = "907a61bd0f64c2f29cd1cf1dc34d05176426a3f504a78010f08416ddb7b13708"
dependencies = [
"unicode-ident",
]
[[package]]
name = "pv"
version = "0.9.0"
version = "1.0.0"
dependencies = [
"byteorder",
"cfg-if",
"clap",
"curl",
"log",
"mockito",
"once_cell",
"openssl",
"openssl_extensions",
"pv_core",
"serde",
"serde_test",
"thiserror",
"utils",
"zerocopy",
]
[[package]]
name = "pv_core"
version = "1.0.0"
dependencies = [
"byteorder",
"lazy_static",
"libc",
"log",
"mockito",
"openssl",
"openssl_extensions",
"serde",
"serde_test",
"thiserror",
"utils",
"zerocopy",
]
[[package]]
name = "pvapconfig"
version = "0.9.0"
dependencies = [
"clap",
"lazy_static",
"openssl",
"openssl-sys",
"pv_core",
"rand",
"regex",
"serde",
"serde_yaml",
"utils",
]
[[package]]
name = "pvsecret"
version = "0.9.0"
@@ -806,9 +838,9 @@ dependencies = [
[[package]]
name = "quote"
version = "1.0.28"
version = "1.0.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b9ab9c7eadfd8df19006f1cf1a4aed13540ed5cbc047010ece5826e10825488"
checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef"
dependencies = [
"proc-macro2",
]
@@ -939,7 +971,7 @@ checksum = "d9735b638ccc51c28bf6914d90a2e9725b377144fc612c49a611fddd1b631d68"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.18",
"syn",
]
[[package]]
@@ -1045,20 +1077,9 @@ checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
[[package]]
name = "syn"
version = "1.0.109"
version = "2.0.47"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "syn"
version = "2.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32d41677bcbe24c20c52e7c70b0d8db04134c5d1066bf98662e2871ad200ea3e"
checksum = "1726efe18f42ae774cc644f330953a5e7b3c3003d3edcecf18850fe9d4dd9afb"
dependencies = [
"proc-macro2",
"quote",
@@ -1092,7 +1113,7 @@ checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.18",
"syn",
]
[[package]]
@@ -1122,7 +1143,7 @@ checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.18",
"syn",
]
[[package]]
@@ -1360,9 +1381,9 @@ checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a"
[[package]]
name = "zerocopy"
version = "0.6.1"
version = "0.7.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "332f188cc1bcf1fe1064b8c58d150f497e697f49774aa846f2dc949d9a25f236"
checksum = "74d4d3961e53fa4c9a25a8637fc2bfaf2595b3d3ae34875568a5cf64787716be"
dependencies = [
"byteorder",
"zerocopy-derive",
@@ -1370,11 +1391,11 @@ dependencies = [
[[package]]
name = "zerocopy-derive"
version = "0.3.2"
version = "0.7.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6505e6815af7de1746a08f69c69606bb45695a17149517680f3b2149713b19a3"
checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6"
dependencies = [
"proc-macro2",
"quote",
"syn 1.0.109",
"syn",
]

View File

@@ -1,6 +1,8 @@
[workspace]
members = [
"pv",
"pv_core",
"pvapconfig",
"pvsecret",
"utils",
]

View File

@@ -19,7 +19,7 @@ ifneq (${HAVE_CARGO},0)
ifneq (${HAVE_OPENSSL},0)
ifneq (${HAVE_LIBCURL},0)
PV_TARGETS := pvsecret
PV_TARGETS := pvsecret pvapconfig
PV_BUILD_TARGETS := $(PV_TARGETS)
CARGO_TEST_TARGETS += $(addsuffix .test,pv $(PV_TARGETS))
@@ -37,8 +37,8 @@ endif
# the cc crate uses these variables to compile c code. It does not open a shell
# 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)
$(BUILD_TARGETS) $(TEST_TARGETS) rust-test: CC = $(CC_SILENT)
$(BUILD_TARGETS) $(TEST_TARGETS) rust-test: AR = $(AR_SILENT)
$(PV_TARGETS): .check-dep-pvtools
$(PV_TARGETS) $(CARGO_TARGETS): .check-cargo .no-cross-compile

View File

@@ -25,16 +25,18 @@ Tip: You can use `make version` to get the version string.
## Internal Libraries
* __utils__ _Library for rust tools that bundles common stuff for the 390-tools_
* currently only provides a macro to get the `S390_TOOLS_RELEASE` string
* provides a macro to get the `S390_TOOLS_RELEASE` string
* provides macros for compile time assertions
* __pv_core__ _Library for pv tools, providing uvdevice access and utilities to send, receive and interpret various UV-calls._
* __pv__ _Library for pv tools, providing uvdevice access, encryption utilities, and utilities for generating UV-request_
* requires openssl and libcurl for the feature `request`; use `HAVE_<OPENSSL|CURL>=0` to
disable build that use pv with the request feature.
* requires openssl and libcurl
* reexports ann symbols from __pv_core__
* if no encryption utilities required, use __pv_core__
## 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

View File

@@ -1,32 +1,24 @@
[package]
name = "pv"
version = "0.9.0"
version = "1.0.0"
edition.workspace = true
license.workspace = true
[dependencies]
libc = "0.2.49"
byteorder = "1.3"
clap = { version ="4", features = ["derive", "wrap_help"] }
curl = "0.4.7"
log = { version = "0.4.6", features = ["std", "release_max_level_debug"] }
openssl = "0.10.49"
serde = { version = "1.0.139", features = ["derive"] }
thiserror = "1.0.33"
zerocopy = "0.6"
cfg-if = "1.0.0"
utils = {path = "../utils"}
zerocopy = { version="0.7", features = ["derive"] }
# dependencies for request feature
clap = { version ="4", features = ["derive", "wrap_help"], optional = true }
curl = { version ="0.4.7", optional = true }
openssl = {version = "0.10.49", optional = true }
openssl_extensions = { path = "openssl_extensions", optional = true }
serde = { version = "1.0.139", features = ["derive"], optional = true }
# misc optional dependencies
byteorder = {version = "1.3", optional = true }
openssl_extensions = { path = "openssl_extensions" }
pv_core = { path = "../pv_core" }
[dev-dependencies]
mockito = {version = "1", default-features = false }
once_cell = "1.19"
serde_test = "1"
lazy_static = "1.1"
[features]
default = []
request = ["dep:openssl", "dep:curl", "dep:openssl_extensions", "dep:serde", "dep:clap"]
uvsecret = ["dep:byteorder", "dep:serde"]

View File

@@ -5,15 +5,15 @@
use std::fmt;
use foreign_types::{foreign_type, ForeignType, ForeignTypeRef};
use libc::c_int;
use openssl::x509::{X509CrlRef, X509Ref};
use std::ffi::c_int;
mod ffi {
extern "C" {
pub fn X509_check_akid(
issuer: *const openssl_sys::X509,
akid: *const openssl_sys::AUTHORITY_KEYID,
) -> ::libc::c_int;
) -> super::c_int;
}
}

View File

@@ -30,7 +30,7 @@ mod ffi {
pub fn X509_STORE_add_crl(
xs: *mut openssl_sys::X509_STORE,
x: *mut openssl_sys::X509_CRL,
) -> libc::c_int;
) -> std::ffi::c_int;
}
}

View File

@@ -5,13 +5,13 @@
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;
use std::ffi::c_int;
pub struct StackableX509Crl(*mut openssl_sys::X509_CRL);

View File

@@ -8,11 +8,10 @@ use std::{
};
// (SE) boot request control block aka SE header
use crate::{
assert_size, request::MagicValue, requires_feat, static_assert, Error, Result, PAGESIZE,
};
use crate::{assert_size, static_assert, Error, Result, PAGESIZE};
use log::debug;
use zerocopy::{AsBytes, BigEndian, FromBytes, U32, U64};
use pv_core::request::MagicValue;
use zerocopy::{AsBytes, BigEndian, FromBytes, FromZeroes, U32, U64};
/// Struct containing all SE-header tags.
///
@@ -22,7 +21,6 @@ use zerocopy::{AsBytes, BigEndian, FromBytes, U32, U64};
/// Tweak List Digest (tld)
/// SE Header Tag (seht)
///
#[doc = requires_feat!(request)]
#[repr(C)]
#[derive(Debug, Clone, Copy, AsBytes, PartialEq, Eq)]
pub struct BootHdrTags {
@@ -144,7 +142,7 @@ impl BootHdrTags {
}
#[repr(C)]
#[derive(Debug, Clone, FromBytes)]
#[derive(Debug, Clone, FromBytes, FromZeroes)]
struct BootHdrHead {
magic: U64<BigEndian>,
version: U32<BigEndian>,

View File

@@ -2,14 +2,12 @@
//
// Copyright IBM Corp. 2023
use crate::misc::{create_file, open_file};
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"])),
@@ -32,15 +30,15 @@ pub struct CertificateOptions {
/// 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.
/// Do not use for a production request unless you verified the host-key document beforehand.
#[arg(long)]
pub no_verify: bool,
/// Use FILE as a certificate to verify the host-key(s).
/// Use FILE as a certificate to verify the host key or keys.
///
/// The certificates are used to establish a chain of trust for the verification
/// of the host-key documents. Specify this option twice to specify the IBM Z signing key and
/// the intermediate CA certificate (signed by the rootCA).
/// the intermediate CA certificate (signed by the root CA).
#[arg(
short= 'C',
long = "cert",
@@ -54,7 +52,7 @@ pub struct CertificateOptions {
/// Use FILE as a certificate revocation list.
///
/// That list is used to check whether a certificate of the chain of
/// The list is used to check whether a certificate of the chain of
/// trust is revoked. Specify this option multiple times to use multiple CRLs.
#[arg(
long = "crl",
@@ -72,7 +70,7 @@ pub struct CertificateOptions {
/// Use FILE as the root-CA certificate for the verification.
///
/// If omitted, the system wide root CAs installed on the system are used.
/// If omitted, the system wide-root CAs installed on the system are used.
/// Use this only if you trust the specified certificate.
#[arg(long, requires("certs"))]
pub root_ca: Option<String>,
@@ -104,37 +102,25 @@ impl CertificateOptions {
}
/// 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)))
Ok(Box::new(create_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)))
Ok(Box::new(open_file(path)?))
}
}

View File

@@ -2,29 +2,29 @@
//
// 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::{HasPublic, Id, PKey, PKeyRef, Private, Public},
pkey_ctx::{HkdfMode, PkeyCtx},
symm::{encrypt, encrypt_aead, Cipher},
rand::rand_bytes,
rsa::Padding,
sign::{Signer, Verifier},
symm::{encrypt_aead, Cipher},
};
use std::convert::TryInto;
use std::{convert::TryInto, ops::Range};
/// An AES256-key that will purge itself out of the memory when going out of scope
///
#[doc = requires_feat!(request)]
pub type Aes256Key = Secret<[u8; 32]>;
pub(crate) const AES_256_GCM_TAG_SIZE: usize = 16;
/// Types of symmetric keys, to specify during construction.
///
#[doc = requires_feat!(request)]
#[non_exhaustive]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SymKeyType {
@@ -34,7 +34,6 @@ pub enum SymKeyType {
/// Types of symmetric keys
///
#[doc = requires_feat!(request)]
#[non_exhaustive]
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum SymKey {
@@ -88,7 +87,6 @@ impl From<Aes256Key> for SymKey {
/// 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.
@@ -113,7 +111,6 @@ pub fn hkdf_rfc_5869<const COUNT: usize>(
/// 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.
@@ -132,7 +129,6 @@ pub fn derive_key(k1: &PKey<Private>, k2: &PKey<Public>) -> Result<Aes256Key> {
/// Generate a random array.
///
#[doc = requires_feat!(request)]
/// # Errors
///
/// This function will return an error if the entropy source fails or is not available.
@@ -144,7 +140,6 @@ pub fn random_array<const COUNT: usize>() -> Result<[u8; COUNT]> {
/// Generate a new random EC-SECP521R1 key.
///
#[doc = requires_feat!(request)]
/// # Errors
///
/// This function will return an error if the key could not be generated by OpenSSL.
@@ -154,24 +149,6 @@ pub fn gen_ec_key() -> Result<PKey<Private>> {
PKey::from_ec_key(key).map_err(Error::Crypto)
}
/// 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
@@ -179,7 +156,6 @@ pub fn encrypt_aes(key: &SymKey, iv: &[u8], conf: &[u8]) -> Result<Vec<u8>> {
/// * `aad` - additional authentic data
/// * `conf` - data to be encrypted
///
#[doc = requires_feat!(request)]
/// # Returns
/// [`Vec<u8>`] with the following content:
/// 1. `aad`
@@ -189,8 +165,13 @@ pub fn encrypt_aes(key: &SymKey, iv: &[u8], conf: &[u8]) -> Result<Vec<u8>> {
/// # 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];
pub fn encrypt_aes_gcm(
key: &SymKey,
iv: &[u8],
aad: &[u8],
conf: &[u8],
) -> Result<(Vec<u8>, Range<usize>, Range<usize>, Range<usize>)> {
let mut tag = vec![0xff; AES_256_GCM_TAG_SIZE];
let encr = match key {
SymKey::Aes256(key) => encrypt_aead(
Cipher::aes_256_gcm(),
@@ -202,17 +183,28 @@ pub fn encrypt_aes_gcm(key: &SymKey, iv: &[u8], aad: &[u8], conf: &[u8]) -> Resu
)?,
};
let mut res = vec![0; aad.len() + encr.len() + 16];
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);
let mut res = vec![0; aad.len() + encr.len() + tag.len()];
let aad_range = Range {
start: 0,
end: aad.len(),
};
let encr_range = Range {
start: aad.len(),
end: aad.len() + encr.len(),
};
let tag_range = Range {
start: aad.len() + encr.len(),
end: aad.len() + encr.len() + tag.len(),
};
Ok(res)
res[aad_range.clone()].copy_from_slice(aad);
res[encr_range.clone()].copy_from_slice(&encr);
res[tag_range.clone()].copy_from_slice(&tag);
Ok((res, aad_range, encr_range, tag_range))
}
/// Calculate the hash of a slice.
///
#[doc = requires_feat!(request)]
/// # Errors
///
/// This function will return an error if OpenSSL could not compute the hash.
@@ -220,10 +212,100 @@ pub fn hash(t: MessageDigest, data: &[u8]) -> Result<DigestBytes> {
openssl::hash::hash(t, data).map_err(Error::Crypto)
}
/// Calculate a digital signature scheme.
///
/// Calculates the digital signature of the provided message using the signing key. [`Id::EC`],
/// and [`Id::RSA`] keys are supported. For [`Id::RSA`] [`Padding::PKCS1_PSS`] is used.
///
/// # Errors
///
/// This function will return an error if OpenSSL could not compute the signature.
pub fn sign_msg(skey: &PKeyRef<Private>, dgst: MessageDigest, msg: &[u8]) -> Result<Vec<u8>> {
match skey.id() {
Id::EC => {
let mut sgn = Signer::new(dgst, skey)?;
sgn.sign_oneshot_to_vec(msg).map_err(Error::Crypto)
}
Id::RSA => {
let mut sgn = Signer::new(dgst, skey)?;
sgn.set_rsa_padding(Padding::PKCS1_PSS)?;
sgn.sign_oneshot_to_vec(msg).map_err(Error::Crypto)
}
_ => Err(Error::UnsupportedSigningKey),
}
}
/// Verify the digital signature of a message.
///
/// Verifies the digital signature of the provided message using the signing key.
/// [`Id::EC`] and [`Id::RSA`] keys are supported. For [`Id::RSA`] [`Padding::PKCS1_PSS`] is used.
///
/// # Returns
/// true if signature could be verified, false otherwise
///
/// # Errors
///
/// This function will return an error if OpenSSL could not compute the signature.
pub fn verify_signature<T: HasPublic>(
skey: &PKeyRef<T>,
dgst: MessageDigest,
msg: &[u8],
sign: &[u8],
) -> Result<bool> {
match skey.id() {
Id::EC => {
let mut ctx = Verifier::new(dgst, skey)?;
ctx.update(msg)?;
ctx.verify(sign).map_err(Error::Crypto)
}
Id::RSA => {
let mut ctx = Verifier::new(dgst, skey)?;
ctx.set_rsa_padding(Padding::PKCS1_PSS)?;
ctx.verify_oneshot(sign, msg).map_err(Error::Crypto)
}
_ => Err(Error::UnsupportedVerificationKey),
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::test_utils::*;
use crate::{get_test_asset, test_utils::*};
#[test]
fn sign_ec() {
let (ec_key, _) = get_test_keys();
let data = "sample".as_bytes();
let sign = sign_msg(&ec_key, MessageDigest::sha512(), data).unwrap();
assert!(sign.len() <= 139 && sign.len() >= 137);
assert!(verify_signature(&ec_key, MessageDigest::sha512(), data, &sign).unwrap());
}
#[test]
fn sign_rsa_2048() {
let keypair = get_test_asset!("keys/rsa2048key.pem");
let keypair = PKey::private_key_from_pem(keypair).unwrap();
let data = "sample".as_bytes();
let sign = sign_msg(&keypair, MessageDigest::sha512(), data).unwrap();
assert_eq!(256, sign.len());
assert!(verify_signature(&keypair, MessageDigest::sha512(), data, &sign).unwrap());
}
#[test]
fn sign_rsa_3072() {
let keypair = get_test_asset!("keys/rsa3072key.pem");
let keypair = PKey::private_key_from_pem(keypair).unwrap();
let data = "sample".as_bytes();
let sign = sign_msg(&keypair, MessageDigest::sha512(), data).unwrap();
assert_eq!(384, sign.len());
assert!(verify_signature(&keypair, MessageDigest::sha512(), data, &sign).unwrap());
}
#[test]
fn derive_key() {
@@ -285,7 +367,7 @@ mod tests {
0xee, 0x62, 0x98, 0xf7, 0x7e, 0x0c,
];
let res = encrypt_aes_gcm(
let (res, ..) = encrypt_aes_gcm(
&SymKey::Aes256(aes_gcm_key.into()),
&aes_gcm_iv,
&aes_gcm_aad,
@@ -294,32 +376,4 @@ mod tests {
.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);
}
}

View File

@@ -10,49 +10,19 @@ pub type Result<T, E = Error> = std::result::Result<T, E>;
#[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")]
#[error("Too many host-keys provided")]
ManyHostkeys,
#[error("Cannot load {ty} from {path}")]
#[cfg(feature = "request")]
X509Load {
path: String,
ty: &'static str,
@@ -60,77 +30,67 @@ pub enum Error {
},
#[error("Internal (unexpected) error: {0}, caused by {1}")]
#[cfg(feature = "request")]
InternalSsl(&'static str, #[source] openssl::error::ErrorStack),
#[error("Signing is only supported for EC and RSA keys")]
UnsupportedSigningKey,
#[error("Verifying signatures is only supported for EC and RSA keys")]
UnsupportedVerificationKey,
#[error("Provided binary request is too small")]
BinRequestSmall,
#[error("No Config UID found: {0}")]
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,
#[error("Invalid {0} user-data for signing provided. Max {} bytes allowed", .0.max())]
AsrcbInvSgnUserData(UserDataType),
#[cfg(feature = "uvsecret")]
#[error("Cannot decode secret list")]
InvSecretList(#[source] std::io::Error),
#[error("Unsupported user data signing key provided. Only EC(secp521r1) and RSA(2048 & 3072 bit) are supported")]
BinAsrcbUnsupportedUserDataSgnKey,
#[cfg(feature = "uvsecret")]
#[error("Input does not contain an add-secret request")]
NoAsrcb,
#[error("No user-key for verification provided and user-data is signed")]
BinAsrcbNoUserDataSgnKey,
#[error("Input does not contain an add-secret request version 1")]
BinAsrcbInvVersion,
#[error("Provided user-data key type ({key}) does not match with the user-data ({kind})")]
AsrcbUserDataKeyMismatch { key: String, kind: UserDataType },
#[error(
"The user-defined request signature could not be verified with the provided certificate"
)]
AsrcbUserDataSgnFail,
// errors from other crates
#[error(transparent)]
PvCore(#[from] pv_core::Error),
#[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,
pub const CRL: &'static str = "CRL";
pub const CERT: &'static str = "certificate";
}
/// 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,
@@ -160,37 +120,11 @@ pub enum HkdVerifyErrorType {
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;
use crate::request::uvsecret::UserDataType;

View File

@@ -2,7 +2,6 @@
//
// Copyright IBM Corp. 2023
#![allow(macro_expanded_macro_exports_accessed_by_absolute_paths)]
#![deny(missing_docs)]
//! pv - library for pv-tools
//!
@@ -11,155 +10,85 @@
//! `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.
//! If you do not need any OpenSSL features use `pv_core`.
//! This crate reexports all symbols from `pv_core`
mod brcb;
mod cli;
mod crypto;
mod error;
mod log;
mod req;
mod secret;
mod utils;
mod uvdevice;
mod uvsecret;
mod verify;
/// Internal macro to conveninetly document required features on items
// #[macro_export]
/// utility functions for writing TESTS!!!
//hide any test helpers on docs!
#[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)]
pub mod test_utils;
pub use ::utils::assert_size;
pub use ::utils::static_assert;
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::{
pub use pv_core::uv::{
uv_ioctl, ConfigUid, UvCmd, UvDevice, UvDeviceInfo, UvFlags, UvcSuccess,
};
#[cfg(feature = "uvsecret")]
pub use crate::uvsecret::{
secret_list::SecretList,
uvc::{AddCmd, ListCmd, LockCmd},
};
pub use pv_core::uv::{AddCmd, ListCmd, LockCmd};
pub use pv_core::uv::{ListableSecretType, SecretEntry, SecretList};
}
/// 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};
pub use crate::utils::{read_certs, read_crls, read_private_key};
pub use pv_core::misc::*;
pub use pv_core::PvLogger;
}
#[cfg(feature = "request")]
pub use crate::error::HkdVerifyErrorType;
pub use error::{Error, FileAccessErrorType, FileIoErrorType, Result};
pub use error::{Error, Result};
/// Functionalities to build UV requests
#[doc = requires_feat!(request)]
pub mod request {
pub use crate::brcb::{BootHdrMagic, BootHdrTags};
pub use crate::crypto::derive_key;
pub use crate::crypto::random_array;
pub use crate::crypto::{encrypt_aes_gcm, gen_ec_key};
pub use crate::crypto::{hash, hkdf_rfc_5869};
pub use crate::crypto::{sign_msg, verify_signature};
pub use crate::crypto::{Aes256Key, SymKey, SymKeyType};
pub use crate::req::{Aad, BinReqValues, Encrypt, Keyslot, ReqEncrCtx, Request};
pub use crate::secret::{Secret, Zeroize};
pub use crate::verify::{CertVerifier, HkdVerifier, NoVerifyHkd};
cfg_if::cfg_if! {
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;
}
}
/// Reexports some useful OpenSSL symbols
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)
}
/// Functionalities for creating add-secret requests
pub mod uvsecret {
pub use crate::uvsecret::{
asrcb::{AddSecretFlags, AddSecretRequest, AddSecretVersion},
ext_secret::ExtSecret,
guest_secret::GuestSecret,
user_data::verify_asrcb_and_get_user_data,
};
pub use pv_core::request::uvsecret::AddSecretMagic;
pub use pv_core::request::uvsecret::UserDataType;
}
pub use pv_core::request::RequestMagic;
}
/// Provides cargo version Info about this crate.
@@ -168,39 +97,3 @@ pub mod request {
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))?,
)
};
}

View File

@@ -2,19 +2,21 @@
//
// Copyright IBM Corp. 2023
use crate::crypto::AES_256_GCM_TAG_SIZE;
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::request::{derive_key, encrypt_aes_gcm, gen_ec_key, random_array, 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 pv_core::request::{RequestMagic, RequestVersion};
use std::convert::TryInto;
use zerocopy::{AsBytes, BigEndian, FromBytes, U32};
use std::mem::size_of;
use std::ops::Range;
use utils::assert_size;
use zerocopy::{AsBytes, BigEndian, FromBytes, FromZeroes, U32};
/// Encrypt a _secret_ using self and a given private key.
pub trait Encrypt {
@@ -94,7 +96,7 @@ impl Encrypt for Keyslot {
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 (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);
@@ -215,10 +217,11 @@ impl ReqEncrCtx {
}
}
let rql = to_u32(auth_data.len() + encr_size + 16)
.ok_or_else(|| Error::Specification("Configured request size to large".to_string()))?;
let rql = to_u32(auth_data.len() + encr_size + 16).ok_or_else(|| {
pv_core::Error::Specification("Configured request size to large".to_string())
})?;
let sea = to_u32(encr_size)
.ok_or_else(|| Error::Specification("Encrypted size to large".to_string()))?;
.ok_or_else(|| pv_core::Error::Specification("Encrypted size to large".to_string()))?;
let req_hdr = RequestHdr::new(version, rql, self.iv, nks, sea, magic);
// copy request header to the start of the request
@@ -235,17 +238,6 @@ impl ReqEncrCtx {
self.priv_key.as_ref().try_into().map_err(Error::Crypto)
}
/// 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
@@ -260,7 +252,11 @@ impl ReqEncrCtx {
/// # 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>> {
pub fn encrypt_aead(
&self,
aad: &[u8],
conf: &[u8],
) -> Result<(Vec<u8>, Range<usize>, Range<usize>, Range<usize>)> {
encrypt_aes_gcm(&self.prot_key, &self.iv, aad, conf)
}
}
@@ -309,7 +305,7 @@ ecdh_from!(Public);
/// Representation of the shared parts of the request header.
/// Used by [`ReqEncrCtx`]
#[repr(C)]
#[derive(Debug, Copy, Clone, AsBytes, FromBytes)]
#[derive(Debug, Copy, Clone, AsBytes, FromBytes, FromZeroes)]
struct RequestHdr {
magic: [u8; 8],
rqvn: U32<BigEndian>,
@@ -321,6 +317,7 @@ struct RequestHdr {
reserved28: u32,
sea: U32<BigEndian>,
}
assert_size!(RequestHdr, 48);
impl RequestHdr {
fn new(rqvn: u32, rql: u32, iv: [u8; 12], nks: u8, sea: u32, magic: Option<[u8; 8]>) -> Self {
@@ -379,6 +376,67 @@ pub trait Request {
fn add_hostkey(&mut self, hostkey: PKey<Public>);
}
/// A struct to represent some parts of a binary/encrypted request.
#[derive(Debug)]
#[allow(unused)]
#[allow(clippy::len_without_is_empty)]
pub struct BinReqValues<'a> {
iv: &'a [u8],
aad: &'a [u8],
req_dep_aad: &'a [u8],
encr: &'a [u8],
tag: &'a [u8],
version: u32,
len: usize,
}
impl<'a> BinReqValues<'a> {
pub(crate) const TAG_LEN: usize = AES_256_GCM_TAG_SIZE;
/// Get the locations from this request.
///
/// Does minimal sanity test, just tests to prevent panics.
/// `req` may be larger than the actual request.
pub fn get(req: &'a [u8]) -> Result<Self> {
let hdr = RequestHdr::read_from_prefix(req).ok_or(Error::BinRequestSmall)?;
let rql = hdr.rql.get() as usize;
let sea = hdr.sea.get() as usize;
if rql < req.len() || sea + Self::TAG_LEN > rql {
return Err(Error::BinRequestSmall);
}
let aad_size = rql - sea - Self::TAG_LEN;
if aad_size < size_of::<RequestHdr>() {
return Err(Error::BinRequestSmall);
}
let iv = &req[0x10..0x1c];
let aad = &req[..aad_size];
let req_dep_aad = &req[size_of::<RequestHdr>()..aad_size];
let encr = &req[aad_size..(aad_size + sea)];
let tag = &req[rql - Self::TAG_LEN..];
Ok(Self {
iv,
aad,
req_dep_aad,
encr,
tag,
version: hdr.rqvn.get(),
len: rql,
})
}
/// Returns the version of this [`BinReqValues`].
pub fn version(&self) -> u32 {
self.version
}
/// Returns the length of this [`BinReqValues`].
pub fn len(&self) -> usize {
self.len
}
}
#[cfg(test)]
mod tests {
use super::*;

View File

@@ -1,336 +1,18 @@
// SPDX-License-Identifier: MIT
//
// Copyright IBM Corp. 2023
use crate::{
error::{bail_spec, file_error, path_to_str},
Error, FileIoErrorType, Result,
use crate::{Error, Result};
use openssl::{
pkey::{PKey, Private},
x509::{X509Crl, X509},
};
#[cfg(feature = "request")]
use openssl::x509::X509Crl;
#[cfg(feature = "request")]
use openssl::x509::X509;
use std::io::{Read, Write};
use std::path::Path;
use zerocopy::{AsBytes, BigEndian, FromBytes, U64};
/// Asserts a constant expression evaluates to `true`.
///
/// If the expression is not evaluated to `true` the compilation will fail.
#[macro_export]
macro_rules! static_assert {
($condition:expr) => {
const _: () = core::assert!($condition);
};
}
/// Asserts that a type has a specific size.
///
/// Useful to validate structs that are passed to C code.
/// If the expression is not evaluated to `true` the compilation will fail.
///
/// # Example
/// ```rust
/// # use pv::assert_size;
/// # fn main() {}
/// #[repr(C)]
/// struct c_struct {
/// v: u64,
/// }
/// assert_size!(c_struct, 8);
/// // assert_size!(c_struct, 7);//won't compile
/// ```
#[macro_export]
macro_rules! assert_size {
($t:ty, $sz:expr ) => {
$crate::static_assert!(::std::mem::size_of::<$t>() == $sz);
};
}
/// Trait that describes bitflags, represented by `T`.
pub trait Flags<T>: From<T> + for<'a> From<&'a T> {
/// Set the specified bit to one.
/// # Panics
///Panics if bit is >= 64
fn set_bit(&mut self, bit: u8);
/// Set the specified bit to zero.
/// # Panics
///Panics if bit is >= 64
fn unset_bit(&mut self, bit: u8);
/// Test if the specified bit is set.
/// # Panics
///Panics if bit is >= 64
fn is_set(&self, bit: u8) -> bool;
}
/// Bitflags in MSB0 ordering
///
/// Wraps an u64 to set/get individual bits
#[repr(C)]
#[derive(Debug, Clone, Copy, Default, AsBytes, FromBytes)]
pub struct Msb0Flags64(U64<BigEndian>);
impl Flags<u64> for Msb0Flags64 {
#[track_caller]
fn set_bit(&mut self, bit: u8) {
assert!(bit < 64, "Flag bit set to greater than 63");
let mut v = self.0.get();
v |= 1 << (63 - bit);
self.0.set(v)
}
#[track_caller]
fn unset_bit(&mut self, bit: u8) {
assert!(bit < 64, "Flag bit set to greater than 63");
let mut v = self.0.get();
v &= !(1 << (63 - bit));
self.0.set(v)
}
#[track_caller]
fn is_set(&self, bit: u8) -> bool {
assert!(bit < 64, "Flag bit set to greater than 63");
self.0.get() & (1 << (63 - bit)) > 0
}
}
impl From<u64> for Msb0Flags64 {
fn from(value: u64) -> Self {
Self(value.into())
}
}
impl From<&u64> for Msb0Flags64 {
fn from(value: &u64) -> Self {
(*value).into()
}
}
/// Bitflags in LSB0 ordering
///
/// Wraps an u64 to set/get individual bits
#[repr(C)]
#[derive(Debug, Clone, Copy, Default, AsBytes, FromBytes)]
pub struct Lsb0Flags64(U64<BigEndian>);
impl Flags<u64> for Lsb0Flags64 {
#[track_caller]
fn set_bit(&mut self, bit: u8) {
assert!(bit < 64, "Flag bit set to greater than 63");
let mut v = self.0.get();
v |= 1 << bit;
self.0.set(v)
}
#[track_caller]
fn unset_bit(&mut self, bit: u8) {
assert!(bit < 64, "Flag bit set to greater than 63");
let mut v = self.0.get();
v &= !(1 << bit);
self.0.set(v)
}
#[track_caller]
fn is_set(&self, bit: u8) -> bool {
assert!(bit < 64, "Flag bit set to greater than 63");
self.0.get() & (1 << bit) > 0
}
}
impl From<u64> for Lsb0Flags64 {
fn from(value: u64) -> Self {
Self(value.into())
}
}
impl From<&u64> for Lsb0Flags64 {
fn from(value: &u64) -> Self {
(*value).into()
}
}
/// Tries to convert a BE hex string into a 128 unsigned integer
/// The hexstring must contain 32chars of hexdigits
///
/// * `hex_str` - string to convert can be prepended with "0x"
/// * `ctx` - Error context string in case of an error
/// ```rust
/// # use std::error::Error;
/// # use pv::misc::try_parse_u128;
/// # fn main() -> Result<(), Box<dyn Error>> {
/// let hex = "11223344556677889900aabbccddeeff";
/// try_parse_u128(&hex, "The test")?;
/// # Ok(())
/// # }
/// ```
///
/// # Errors
/// If `hex_string` is not a 32 byte hex string an Error appears
pub fn try_parse_u128(hex_str: &str, ctx: &str) -> Result<[u8; 16]> {
let hex_str = if hex_str.starts_with("0x") {
hex_str.split_at(2).1
} else {
hex_str
};
if hex_str.len() != 32 {
bail_spec!(format!(
"{ctx} hexstring must be 32chars long to cover all 16 bytes"
));
}
parse_hex(hex_str).try_into().map_err(|_| {
Error::Specification(format!(
"{ctx} hexstring must be 32chars long to cover all 16 bytes"
))
})
}
/// Tries to convert a BE hex string into a 64 unsigned integer
/// The hexstring must *NOT* contain 16 chars of hexdigits, but
/// 16 chars at most.
///
/// * `hex_str` - string to convert can be prepended with "0x"
/// * `ctx` - Error context string in case of an error
/// ```rust
/// # use std::error::Error;
/// # use pv::misc::try_parse_u64;
/// # fn main() -> Result<(), Box<dyn Error>> {
/// let hex = "1234567890abcdef";
/// try_parse_u64(&hex, "The test")?;
/// # Ok(())
/// # }
/// ```
///
/// # Errors
/// If `hex_string` is not a 32 byte hex string an Error appears
pub fn try_parse_u64(hex_str: &str, ctx: &str) -> Result<u64> {
let hex_str = if hex_str.starts_with("0x") {
hex_str.split_at(2).1
} else {
hex_str
};
if hex_str.len() > 16 {
bail_spec!(format!(
"{ctx} hexstring {hex_str} must be max 16 chars long"
));
}
Ok(u64::from_str_radix(hex_str, 16)?)
}
/// Read exactly COUNT bytes into the buffer.
///
/// * `path` - Path to file
/// * `ctx` - Error context string in case of an error
///
/// # Errors
/// If this function encounters an "end of file" before completely filling
/// the buffer, it returns an error. The contents of `buf` are unspecified in this case.
///
/// If any other read error is encountered then this function immediately
/// returns. The contents of `buf` are unspecified in this case.
///
/// If this function returns an error, it is unspecified how many bytes it
/// has read, but it will never read more than would be necessary to
/// completely fill the buffer.
pub fn read_exact_file<P: AsRef<Path>, const COUNT: usize>(
path: P,
ctx: &str,
) -> Result<[u8; COUNT]> {
let mut f = std::fs::File::open(&path).map_err(|e| Error::FileAccess {
ty: crate::FileAccessErrorType::Open,
path: path_to_str!(path).to_string(),
source: e,
})?;
if f.metadata()?.len() as usize != COUNT {
bail_spec!(format!("{ctx} must be exactly {COUNT} bytes long"));
}
let mut buf = [0; COUNT];
f.read_exact(&mut buf)
.map_err(|e| file_error!(Read, ctx, path_to_str!(path).to_string(), e))?;
Ok(buf)
}
/// Read content from a file and add context in case of an error
///
/// * `path` - Path to file
/// * `ctx` - Error context string in case of an error
///
///
/// # Errors
/// Passes through any kind of error `std::fs::read` produces
pub fn read_file<P: AsRef<Path>>(path: P, ctx: &str) -> Result<Vec<u8>> {
std::fs::read(&path).map_err(|e| {
file_error!(
Read,
ctx,
path.as_ref().to_str().unwrap_or("no UTF-8 path"),
e
)
})
}
/// Reads all content from a [`std::io::Read`] and add context in case of an error
///
/// * `path` - Path to file
/// * `ctx` - Error context string in case of an error
///
///
/// # Errors
/// Passes through any kind of error `std::fs::write` produces
pub fn read<R: Read>(rd: &mut R, path: &str, ctx: &str) -> Result<Vec<u8>> {
let mut buf = vec![];
rd.read_to_end(&mut buf).map_err(|e| Error::FileIo {
ty: FileIoErrorType::Write,
ctx: ctx.to_string(),
path: path.to_string(),
source: e,
})?;
Ok(buf)
}
/// write content to a file and add context in case of an error
///
/// * `path` - Path to file
/// * `ctx` - Error context string in case of an error
///
///
/// # Errors
/// Passes through any kind of error `std::fs::write` produces
pub fn write_file<D: AsRef<[u8]>>(path: &str, data: D, ctx: &str) -> Result<()> {
std::fs::write(path, data.as_ref()).map_err(|e| Error::FileIo {
ty: FileIoErrorType::Write,
ctx: ctx.to_string(),
path: path.to_string(),
source: e,
})
}
/// Write content to a [`std::io::Write`] and add context in case of an error
///
/// * `path` - Path to file
/// * `ctx` - Error context string in case of an error
///
///
/// # Errors
/// Passes through any kind of error `std::fs::write` produces
pub fn write<D: AsRef<[u8]>, W: Write>(wr: &mut W, data: D, path: &str, ctx: &str) -> Result<()> {
wr.write_all(data.as_ref()).map_err(|e| Error::FileIo {
ty: FileIoErrorType::Write,
ctx: ctx.to_string(),
path: path.to_string(),
source: e,
})
}
/// Read all CRLs from the buffer and parse them into a vector.
///
/// # Errors
///
/// This function will return an error if the underlying openssl implementation cannot parse `buf`
/// This function will return an error if the underlying OpenSSL implementation cannot parse `buf`
/// as `DER` or `PEM`.
///
/// Requires the `request` feature.
#[cfg(feature = "request")]
pub fn read_crls(buf: &[u8]) -> Result<Vec<X509Crl>> {
use openssl_extensions::crl::StackableX509Crl;
X509Crl::from_der(buf)
@@ -343,11 +25,8 @@ pub fn read_crls(buf: &[u8]) -> Result<Vec<X509Crl>> {
///
/// # 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")]
/// This function will return an error if the underlying OpenSSL implementation cannot parse `buf`
pub fn read_certs(buf: &[u8]) -> Result<Vec<X509>> {
X509::from_der(buf)
.map(|crt| vec![crt])
@@ -355,191 +34,23 @@ pub fn read_certs(buf: &[u8]) -> Result<Vec<X509>> {
.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.
/// Read+parse the first key from the buffer.
///
/// 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.
/// # Errors
///
/// 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
/// This function will return an error if the underlying OpenSSL implementation cannot parse `buf`
/// as `DER` or `PEM`.
pub fn read_private_key(buf: &[u8]) -> Result<PKey<Private>> {
PKey::private_key_from_der(buf)
.or_else(|_| PKey::private_key_from_pem(buf))
.map_err(Error::Crypto)
}
#[cfg(test)]
mod tests {
use std::usize;
use super::*;
#[cfg(feature = "request")]
use crate::test_utils::*;
use crate::{get_test_asset, 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");
@@ -550,7 +61,6 @@ mod tests {
}
#[test]
#[cfg(feature = "request")]
fn read_certs() {
let crt = get_cert_asset("ibm.crt");
let crt_der = get_cert_asset("der.crt");
@@ -561,98 +71,16 @@ mod tests {
}
#[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));
fn read_private_key() {
let key = get_test_asset!("keys/rsa3072key.pem");
let key = super::read_private_key(key).unwrap();
assert_eq!(key.rsa().unwrap().size(), 384);
}
#[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, &[]));
fn read_private_key_fail() {
let key = get_test_asset!("exp/secure_guest.hdr");
let key = super::read_private_key(key);
assert!(key.is_err());
}
}

View File

@@ -2,79 +2,12 @@
//
// 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)
}
pub mod user_data;

View File

@@ -2,23 +2,23 @@
//
// Copyright IBM Corp. 2023
use super::{AddSecretMagic, UserDataType};
use crate::requires_feat;
use super::user_data::UserData;
use crate::{
assert_size,
misc::Flags,
request::{
hkdf_rfc_5869,
openssl::{
pkey::{PKey, Public},
pkey::{PKey, Private, Public},
Md,
},
uvsecret::{ExtSecret, GuestSecret},
Aad, BootHdrTags, Keyslot, ReqEncrCtx, Request, RequestVersion, Secret,
Aad, BootHdrTags, Keyslot, ReqEncrCtx, Request, Secret,
},
uv::{ConfigUid, UvFlags},
Result,
};
use pv_core::request::RequestVersion;
use zerocopy::AsBytes;
/// Internal wrapper for Guest Secret, so that we can dump it in the form the UV wants it to be
@@ -52,6 +52,7 @@ impl From<GuestSecret> for BinGuestSecret {
}
}
/// Authenticated data w/o user data
#[repr(C)]
#[derive(Debug, Clone, Copy, AsBytes)]
struct ReqAuthData {
@@ -59,9 +60,8 @@ struct ReqAuthData {
boot_tags: BootHdrTags,
cuid: ConfigUid,
reserved90: [u8; 0x100],
prog_res190: [u8; 0x200],
}
assert_size!(ReqAuthData, 0x3e8);
assert_size!(ReqAuthData, 0x1e8);
impl ReqAuthData {
fn new<F: Into<UvFlags>>(boot_tags: BootHdrTags, flags: F) -> Self {
@@ -70,7 +70,6 @@ impl ReqAuthData {
boot_tags,
cuid: [0; 0x10],
reserved90: [0; 0x100],
prog_res190: [0; 0x200],
}
}
}
@@ -96,8 +95,6 @@ impl ReqConfData {
}
/// Flags for [`AddSecretRequest`]
///
#[doc = requires_feat!(reqsecret)]
#[derive(Default, Clone, Copy, Debug)]
pub struct AddSecretFlags(UvFlags);
impl AddSecretFlags {
@@ -123,8 +120,6 @@ impl From<AddSecretFlags> for UvFlags {
}
/// Versions for [`AddSecretRequest`]
///
#[doc = requires_feat!(reqsecret)]
#[repr(u32)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AddSecretVersion {
@@ -143,12 +138,6 @@ impl From<AddSecretVersion> for RequestVersion {
}
}
impl AddSecretMagic {
fn get(&self) -> crate::request::RequestMagic {
self.as_bytes().try_into().unwrap()
}
}
/// Add-secret request Control Block
///
/// An ASRCB wraps a secret to transport it securely to the Ultravisor.
@@ -158,6 +147,7 @@ impl AddSecretMagic {
/// _______________________________________________________________
/// | generic header (48)
/// | --------------------------------------------------- |
/// | Plaintext Add-Secret flags (8) |
/// | SE header tags: PLD(64) ALD(64) TLD(64) HeaderTag(16) |
/// | Configuration unique ID(16) (Attestation) |
/// | Optional, defaults to 0 |
@@ -173,18 +163,19 @@ impl AddSecretMagic {
/// | 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,
user_data: UserData,
}
impl AddSecretRequest {
/// Offset of the user-data in the add-secret request in bytes
pub(super) const V1_USER_DATA_OFFS: usize = 0x218;
/// Create a new add-secret request.
///
/// The request has no extension secret, no configuration UID, no host-keys,
@@ -204,7 +195,7 @@ impl AddSecretRequest {
aad: ReqAuthData::new(boot_tags, flags),
keyslots: vec![],
version,
magic: UserDataType::Null.into(),
user_data: UserData::Null,
}
}
@@ -238,18 +229,41 @@ impl AddSecretRequest {
&self.conf.secret.0
}
/// Add user-data to the Add-Secret request
///
/// (Signed) user-data is a non-architectual feature. It allows to add arbitrary
/// data (message) to the request, that is signed optionally with an user defined key.
/// Allowed keys are:
/// - no key (up to 512 bytes of message)
/// - EC SECP521R1 (up to 256 byte message)
/// - RSA 2048 bit (up to 256 byte message)
/// - RSA 3072 bit (up to 128 byte message)
///
/// The signature can be verified during the verification of the secret-request on the target machine.
pub fn set_user_data(&mut self, msg: Vec<u8>, skey: Option<PKey<Private>>) -> Result<()> {
self.user_data = UserData::new(skey, msg)?;
Ok(())
}
/// compiles the authenticated area of this request
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 user_data = self.user_data.data();
let mut aad: Vec<Aad> = Vec::with_capacity(3 + self.keyslots.len());
let mut aad: Vec<Aad> = Vec::with_capacity(5 + self.keyslots.len());
aad.push(Aad::Plain(self.aad.as_bytes()));
if let Some(data) = user_data.0 {
aad.push(Aad::Plain(data));
}
if let Some(data) = &user_data.1 {
aad.push(Aad::Plain(data));
}
aad.push(Aad::Plain(cust_pub_key.as_ref()));
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())
ctx.build_aad(self.version.into(), &aad, conf_len, self.user_data.magic())
}
#[doc(hidden)]
@@ -268,13 +282,41 @@ impl AddSecretRequest {
res.append(&mut vec![0x24; 32]);
Ok(res)
}
/// encrypt data, sign request with user-provided signing key, insert signature into aad,
/// calculate request tag
fn encrypt_with_signed_user_data(&self, ctx: &ReqEncrCtx) -> Result<Vec<u8>> {
//encrypt data w/o aead
let conf = self.conf.to_bytes();
let aad = self.aad(ctx, conf.value().len())?;
let (mut buf, aad_range, encr_range, _) = ctx.encrypt_aead(&aad, conf.value())?;
drop(aad);
// sign aad+encrypted data (w/o tag) with user signning key
// add signature to authenticated data starting with USER_DATA_OFFS
self.user_data.sign(
&mut buf[aad_range.start..encr_range.end],
Self::V1_USER_DATA_OFFS,
)?;
// encrypt again with signed data
buf[encr_range.clone()].copy_from_slice(conf.value());
ctx.encrypt_aead(&buf[aad_range], &buf[encr_range])
.map(|(buf, ..)| buf)
}
}
impl Request for AddSecretRequest {
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())
match self.user_data {
UserData::Null | UserData::Unsigned(_) => {
let conf = self.conf.to_bytes();
let aad = self.aad(ctx, conf.value().len())?;
ctx.encrypt_aead(&aad, conf.value()).map(|(buf, ..)| buf)
}
_ => self.encrypt_with_signed_user_data(ctx),
}
}
fn add_hostkey(&mut self, hostkey: PKey<Public>) {

View File

@@ -2,11 +2,9 @@
//
// Copyright IBM Corp. 2023
use crate::{request::Secret, requires_feat};
use crate::request::Secret;
/// 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

View File

@@ -4,18 +4,16 @@
#[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,
Result,
};
use pv_core::for_pv::{ser_gsid, SECRET_ID_SIZE};
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

View File

@@ -1,224 +0,0 @@
// SPDX-License-Identifier: MIT
//
// Copyright IBM Corp. 2023
use crate::{misc::to_u16, uv::ListCmd, uvdevice::UvCmd, Error, Result};
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
use serde::{Serialize, Serializer};
use std::usize;
use std::{
fmt::Display,
io::{Cursor, Read, Seek, Write},
};
use zerocopy::{AsBytes, FromBytes, U16, U32};
use super::ser_gsid;
/// List of secrets used to parse the [`crate::uv::ListCmd`] result
///
/// Requires the `uvsecret` feature.
#[derive(Debug, PartialEq, Eq, Serialize)]
pub struct SecretList {
total_num_secrets: u16,
secrets: Vec<SecretEntry>,
}
impl SecretList {
/// Encodes the list in the same binary format the UV would do
pub fn encode<T: Write>(&self, w: &mut T) -> Result<()> {
let num_s = to_u16(self.secrets.len()).ok_or(Error::ManySecrets)?;
w.write_u16::<BigEndian>(num_s)?;
w.write_u16::<BigEndian>(self.total_num_secrets)?;
w.write_all(&[0u8; 12])?;
for secret in &self.secrets {
w.write_all(secret.as_bytes())?;
}
w.flush().map_err(Error::Io)
}
/// Decodes the list from the binary format of the UV into this internal representation
pub fn decode<R: Read + Seek>(r: &mut R) -> std::io::Result<Self> {
let num_s = r.read_u16::<BigEndian>()?;
let total_num_secrets = r.read_u16::<BigEndian>()?;
let mut v: Vec<SecretEntry> = Vec::with_capacity(num_s as usize);
r.seek(std::io::SeekFrom::Current(12))?; //skip reserved bytes
let mut buf = [0u8; SECRET_ENTRY_SIZE];
for _ in 0..num_s {
r.read_exact(&mut buf)?;
//cannot fail. buffer has the same size as the secret entry
let secr = SecretEntry::read_from(buf.as_slice()).unwrap();
v.push(secr);
}
Ok(Self {
total_num_secrets,
secrets: v,
})
}
}
impl TryFrom<ListCmd> for SecretList {
type Error = Error;
fn try_from(mut list: ListCmd) -> Result<SecretList> {
SecretList::decode(&mut Cursor::new(list.data().unwrap())).map_err(Error::InvSecretList)
}
}
impl Display for SecretList {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
writeln!(f, "Total number of secrets: {}", self.total_num_secrets)?;
if !self.secrets.is_empty() {
writeln!(f)?;
}
for s in &self.secrets {
writeln!(f, "{s}")?;
}
Ok(())
}
}
fn ser_u32<S: Serializer>(v: &U32<BigEndian>, ser: S) -> Result<S::Ok, S::Error> {
ser.serialize_u32(v.get())
}
fn ser_u16<S: Serializer>(v: &U16<BigEndian>, ser: S) -> Result<S::Ok, S::Error> {
ser.serialize_u16(v.get())
}
/// A secret in a [`SecretList`]
///
/// Fields are in big endian
#[repr(C)]
#[derive(Debug, PartialEq, Eq, AsBytes, FromBytes, Serialize)]
pub struct SecretEntry {
#[serde(serialize_with = "ser_u16")]
index: U16<BigEndian>,
#[serde(serialize_with = "ser_u16")]
stype: U16<BigEndian>,
#[serde(serialize_with = "ser_u32")]
len: U32<BigEndian>,
#[serde(skip)]
res_8: u64,
#[serde(serialize_with = "ser_gsid")]
id: [u8; 32],
}
const SECRET_ENTRY_SIZE: usize = 0x30;
fn stype_str(stype: u16) -> String {
match stype {
// should never match (not incl in list), but here for completeness
1 => "Null".to_string(),
2 => "Association".to_string(),
n => format!("Unknown {n}"),
}
}
impl Display for SecretEntry {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
writeln!(f, "{} {}:", self.index, stype_str(self.stype.get()))?;
write!(f, " ")?;
for b in self.id {
write!(f, "{b:02x}")?;
}
Ok(())
}
}
#[cfg(test)]
mod test {
use super::*;
use std::io::{BufReader, BufWriter, Cursor};
#[test]
fn secret_entry_size() {
assert_eq!(::std::mem::size_of::<SecretEntry>(), SECRET_ENTRY_SIZE);
}
#[test]
fn dump_secret_entry() {
const EXP: &[u8] = &[
0x00, 0x01, 0x00, 0x02, //idx + type
0x00, 0x00, 0x00, 0x20, //len
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // reserved
// id
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
];
let s = SecretEntry {
index: 1.into(),
stype: 2.into(),
len: 32.into(),
res_8: 0,
id: [0; 32],
};
assert_eq!(s.as_bytes(), EXP);
}
#[test]
fn secret_list_dec() {
let buf = [
0x00u8, 0x01, // num secr stored
0x01, 0x12, // total num secrets
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //reserved
// secret
0x00, 0x01, 0x00, 0x02, //idx + type
0x00, 0x00, 0x00, 0x20, //len
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // reserved
// id
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
];
let exp = SecretList {
total_num_secrets: 0x112,
secrets: vec![SecretEntry {
index: 1.into(),
stype: 2.into(),
len: 32.into(),
res_8: 0,
id: [0; 32],
}],
};
let mut br = BufReader::new(Cursor::new(buf));
let sl = SecretList::decode(&mut br).unwrap();
assert_eq!(sl, exp);
}
#[test]
fn secret_list_enc() {
const EXP: &[u8] = &[
0x00, 0x01, // num secr stored
0x01, 0x12, // total num secrets
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //reserved
// secret
0x00, 0x01, 0x00, 0x02, //idx + type
0x00, 0x00, 0x00, 0x20, //len
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // reserved
// id
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
];
let sl = SecretList {
total_num_secrets: 0x112,
secrets: vec![SecretEntry {
index: 1.into(),
stype: 2.into(),
len: 32.into(),
res_8: 0,
id: [0; 32],
}],
};
let mut buf = [0u8; 0x40];
{
let mut bw = BufWriter::new(&mut buf[..]);
sl.encode(&mut bw).unwrap();
}
println!("list: {sl:?}");
assert_eq!(buf, EXP);
}
}

View File

@@ -0,0 +1,611 @@
use crate::{
crypto::{sign_msg, verify_signature},
req::BinReqValues,
request::{
openssl::{
pkey::{PKey, Private},
MessageDigest,
},
uvsecret::{AddSecretRequest, AddSecretVersion},
RequestMagic,
},
Error, Result,
};
use openssl::{
nid::Nid,
pkey::{HasParams, HasPublic, Id, PKeyRef, Public},
};
use pv_core::request::uvsecret::AddSecretMagic;
use pv_core::request::uvsecret::UserDataType;
use utils::assert_size;
use zerocopy::{AsBytes, BigEndian, FromBytes, FromZeroes, U16};
/// User data.
///
/// User defined data can be:
/// - 512 bytes arbitrary data
/// - 256 bytes arbitrary data + EC(secp521r1) signature
/// ```none
/// LAYOUT
/// |------------------------|
/// | user-data (256) |
/// | ec signature (139) |
/// | reserved (5) |
/// | signature size (2) (BE)|
/// | reserved (110) |
/// |------------------------|
/// ```
/// - 256 bytes arbitrary data + RSA2048 signature
/// ```none
/// LAYOUT
/// |---------------------|
/// | user-data (256) |
/// | rsa signature (256) |
/// |---------------------|
/// ```
/// - 128 bytes arbitrary data + RSA3072 signature
/// ```none
/// LAYOUT
/// |---------------------|
/// | user-data (128) |
/// | rsa signature (384) |
/// |---------------------|
/// ```
///
/// Ensures that the data+signature fits into 512 bytes
/// must be created via functions!
#[derive(Debug, Clone)]
pub(super) enum UserData {
Null,
Unsigned(Vec<u8>),
Signed(SignedUserData),
}
#[repr(C)]
#[derive(Debug, AsBytes, FromBytes, FromZeroes)]
struct EcUserData {
data: [u8; 256],
signature: [u8; EC_SIGN_MAX_SIZE],
res_18b: [u8; 5],
sgn_size: U16<BigEndian>,
res_192: [u8; 110],
}
assert_size!(EcUserData, USER_DATA_SIZE);
const USER_DATA_SIZE: usize = 0x200;
const EC_SIGN_MAX_SIZE: usize = 139;
impl EcUserData {
// Sets the signature to this data.
//
//# Panic
// Panics if `sgn` is longer than 139 bytes
fn set_signature(&mut self, sgn: &[u8]) {
debug_assert!(sgn.len() <= EC_SIGN_MAX_SIZE);
self.signature.fill(0);
self.signature[..sgn.len()].copy_from_slice(sgn);
self.res_18b.fill(0);
self.sgn_size = (sgn.len() as u16).into();
self.res_192.fill(0);
}
}
#[derive(Debug, Clone)]
pub(super) struct SignedUserData {
sign_key: PKey<Private>,
data: Vec<u8>,
}
impl UserData {
const USER_DATA_SIZE: usize = 0x200;
fn user_data_type<P: HasPublic>(sign_key: &PKeyRef<P>) -> Result<UserDataType> {
fn check_curve<P: HasParams>(pkey: &PKeyRef<P>) -> Result<bool> {
let nid = pkey.ec_key()?.group().curve_name();
match nid {
Some(nid) => Ok(nid == Nid::SECP521R1),
None => Ok(false),
}
}
match sign_key.id() {
Id::EC if check_curve(sign_key)? => Ok(UserDataType::SgnEcSECP521R1),
Id::RSA if sign_key.rsa()?.size() == 2048 / 8 => Ok(UserDataType::SgnRsa2048),
Id::RSA if sign_key.rsa()?.size() == 3072 / 8 => Ok(UserDataType::SgnRsa3072),
_ => Err(Error::BinAsrcbUnsupportedUserDataSgnKey),
}
}
pub(super) fn magic(&self) -> RequestMagic {
let magic: AddSecretMagic = self.data_type().into();
magic.get()
}
/// Creates new user data
///
/// Verifies that the provided data + signature fits into 512 bytes
///
/// #Error
/// An error is reported if the provided data and the signature would not fit into 512 bytes
/// An error is reported if the key is not of type RSA (2048|3072) or EC(specp521r1)
pub(super) fn new(sign_key: Option<PKey<Private>>, data: Vec<u8>) -> Result<Self> {
let sign_key = match sign_key {
None => {
return match data.len() > UserDataType::Unsigned.max() {
true => Err(Error::AsrcbInvSgnUserData(UserDataType::Unsigned)),
false => Ok(Self::Unsigned(data)),
};
}
Some(skey) => skey,
};
let kind = Self::user_data_type(&sign_key)?;
// does the data fit into the arbitrary buffer?
if data.len() > kind.max() {
return Err(Error::AsrcbInvSgnUserData(kind));
}
Ok(Self::Signed(SignedUserData { sign_key, data }))
}
/// Signs data in buf, writes signature to buf+user_data_offset+sign_offset if applicable.
///
/// Uses [`MessageDigest::sha512`] as digest. Does not modify the abritary user data buffer.
///
/// * buf: user data buffer, must be at least 512 bytes long
///
/// # Panic
/// panics if `buf` is smaller than 512 bytes
///
/// # Errors
/// Returns an error if signature could not be calculated.
/// It is considered no error if no signature is required by user data type
pub(super) fn sign(&self, buf: &mut [u8], user_data_offset: usize) -> Result<()> {
// get signing info or return if no signature is required
let signed_data = match self {
UserData::Null | UserData::Unsigned(_) => return Ok(()),
UserData::Signed(s) => s,
};
debug_assert!(buf.len() >= USER_DATA_SIZE);
// clear the signature area
let sgn_offset = user_data_offset + self.data_type().max();
buf[sgn_offset..user_data_offset + USER_DATA_SIZE].fill(0);
// calculate signature
let sgn = sign_msg(&signed_data.sign_key, MessageDigest::sha512(), buf)?;
// insert signature
if let UserDataType::SgnEcSECP521R1 = self.data_type() {
// Panic: will not panic buffer is 512+ bytes long
let buf_ec = EcUserData::mut_from_prefix(&mut buf[user_data_offset..]).unwrap();
buf_ec.set_signature(&sgn);
} else {
// Panic: will not panic buffer is 512+ bytes long
buf[sgn_offset..sgn_offset + sgn.len()].copy_from_slice(&sgn);
}
Ok(())
}
fn data_type(&self) -> UserDataType {
match self {
Self::Null => UserDataType::Null,
Self::Unsigned(_) => UserDataType::Unsigned,
Self::Signed(data) => Self::user_data_type(&data.sign_key).unwrap(),
}
}
/// returns a slice for the abitraty user data as first tuple part if User data is available
/// the second part contains a vector, created on the fly, which contains enough zeros to fill
/// the missing bytes to fill 512 bytes of space or None if the first slice already contains
/// 512 bytes
pub(super) fn data(&self) -> (Option<&[u8]>, Option<Vec<u8>>) {
let buf = match self {
UserData::Null => None,
UserData::Unsigned(d) => Some(d),
UserData::Signed(SignedUserData { data, .. }) => Some(data),
};
let remaining_size = Self::USER_DATA_SIZE - buf.map(|b| b.len()).unwrap_or(0);
let remaining = match remaining_size > 0 {
true => Some(vec![0; remaining_size]),
false => None,
};
(buf.map(|b| b.as_ref()), remaining)
}
}
fn format_vrfy_key(key: &PKeyRef<Public>) -> String {
let id = key.id();
match key.rsa() {
Ok(key) => format!("RSA {}", key.size() * 8),
Err(_) if id == Id::EC => "EC".to_string(),
Err(_) => "Unknown".to_string(),
}
}
fn check_key_format(kind: UserDataType, key: &PKeyRef<Public>) -> Result<()> {
let other_kind =
UserData::user_data_type(key).map_err(|_| Error::AsrcbUserDataKeyMismatch {
key: format_vrfy_key(key),
kind,
})?;
if other_kind == kind {
Ok(())
} else {
Err(Error::AsrcbUserDataKeyMismatch {
key: format_vrfy_key(key),
kind,
})
}
}
/// Verify the user data contained in the add-secret request.
///
/// First checks that the provided data contains a sound add-secret request.
/// Then performs the inverse action that happened during the add-secret generation with user-data
/// signature:
/// - extract and replace the signature with zeros
/// - verify the signature of the request until, but not including the request tag
///
/// # Returns
///
/// Extracrted user-data if available
///
/// #Errors
///
/// returns an error if
/// - No sound add-secret request presented
/// - Sinned user-data indicated, but no key provided
/// - Another keytype provided than indicated in the request
/// - Signature could not be verified by the provided key
/// - any OpenSSL error that might happen during the verification process
pub fn verify_asrcb_and_get_user_data(
mut asrcb: Vec<u8>,
key: Option<PKey<Public>>,
) -> Result<Option<Vec<u8>>> {
// check that the provided buffer contains an Add Secret request
let magic = AddSecretMagic::try_from_bytes(&asrcb)?;
let req = BinReqValues::get(&asrcb)?;
if req.version() != AddSecretVersion::One as u32 {
return Err(Error::BinAsrcbInvVersion);
}
//preventing the two lines after the truncate from panicking
let req_len = req.len();
if asrcb.len() < req_len
|| req_len < AddSecretRequest::V1_USER_DATA_OFFS + UserData::USER_DATA_SIZE
{
return Err(pv_core::Error::NoAsrcb.into());
}
// forget the tag (and all additional data that might be behind the tag)
asrcb.truncate(req_len - BinReqValues::TAG_LEN);
// get a mutable refrenence on the 512 bytes of user data
let (_, user_data) = asrcb.split_at_mut(AddSecretRequest::V1_USER_DATA_OFFS);
let user_data = &mut user_data[..UserData::USER_DATA_SIZE];
// depending on the user_data_type do:
// Null -> exit w/o user data
// Unsigned -> exit return all user data
// Signed ->
// - check that provided key matches user data keytype
// - extract user data& signature
let (key, user_data) = match (key, magic.kind()) {
(_, UserDataType::Null) => return Ok(None),
(None, UserDataType::Unsigned) => return Ok(Some(user_data.to_vec())),
(Some(key), UserDataType::Unsigned) => {
return Err(Error::AsrcbUserDataKeyMismatch {
key: format_vrfy_key(&key),
kind: UserDataType::Unsigned,
})
}
(Some(key), _) => {
check_key_format(magic.kind(), &key)?;
(key, VerifiedUserData::new(user_data, magic.kind()))
}
(None, _) => return Err(Error::BinAsrcbNoUserDataSgnKey),
};
match verify_signature(&key, MessageDigest::sha512(), &asrcb, user_data.signature())? {
false => Err(Error::AsrcbUserDataSgnFail),
true => Ok(Some(user_data.into())),
}
}
// Internal representation of the 512 bytes of user-data, signing-algorithm agnostic
struct VerifiedUserData {
data: Vec<u8>,
signature: Vec<u8>,
}
impl VerifiedUserData {
/// Reads user-data from buf depending on the indicated user data type.
/// Overwrites the signature in the buf with zeros.
///
/// #Panics
/// Panics it provided buffer is smaller that 512 bytes or kind is Null or Unsigned
fn new(buf: &mut [u8], kind: UserDataType) -> Self {
assert!(buf.len() >= 0x200);
let (ret, sgn) = match kind {
UserDataType::SgnEcSECP521R1 => {
let EcUserData {
data,
signature,
sgn_size,
..
} = EcUserData::mut_from_prefix(buf).unwrap();
let data_len: usize = data.len();
let data = data.to_vec();
let mut signature = signature.to_vec();
signature.truncate(sgn_size.get() as usize);
(Self { data, signature }, &mut buf[data_len..])
}
UserDataType::SgnRsa2048 => (
Self {
data: buf[..0x100].to_vec(),
signature: buf[0x100..].to_vec(),
},
&mut buf[0x100..],
),
UserDataType::SgnRsa3072 => (
Self {
data: buf[..0x80].to_vec(),
signature: buf[0x80..].to_vec(),
},
&mut buf[0x80..],
),
UserDataType::Null => unreachable!(),
UserDataType::Unsigned => unreachable!(),
};
//overwrite signature field with zeros
sgn.fill(0);
ret
}
fn signature(&self) -> &[u8] {
self.signature.as_ref()
}
}
impl From<VerifiedUserData> for Vec<u8> {
fn from(value: VerifiedUserData) -> Self {
value.data
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::{get_test_asset, test_utils::get_test_keys};
#[test]
fn sign_null() {
let mut buf = vec![17; 0x200];
let user_data = UserData::Null;
let (data, _) = user_data.data();
assert!(data.is_none());
user_data.sign(&mut buf, 0).unwrap();
// sign should not touch the buffer
assert_eq!(buf, vec![17; 0x200]);
}
#[test]
fn sign_unsigned() {
let user_data = UserData::Unsigned(vec![0x11; 0x200]);
let (data, _) = user_data.data();
assert_eq!(data.unwrap(), &[0x11; 0x200]);
let mut buf = vec![17; 0x200];
user_data.sign(&mut buf, 0).unwrap();
// sign should not touch the buffer
assert_eq!(buf, vec![17; 0x200]);
}
#[test]
fn sign_rsa2048() {
let rsa = get_test_asset!("keys/rsa2048key.pem");
let rsa = PKey::private_key_from_pem(rsa).unwrap();
let mut buf = vec![0x17; 0x200];
let user_data = UserData::new(Some(rsa.clone()), vec![0x11; 0x100]).unwrap();
let (data, _) = user_data.data();
let data = data.unwrap();
buf[..0x100].copy_from_slice(data);
user_data.sign(&mut buf, 0).unwrap();
let vrf_user_data = VerifiedUserData::new(&mut buf, UserDataType::SgnRsa2048);
let res = verify_signature(
&rsa,
MessageDigest::sha512(),
&buf,
vrf_user_data.signature(),
)
.unwrap();
assert!(res);
}
#[test]
fn sign_rsa3072() {
let rsa = get_test_asset!("keys/rsa3072key.pem");
let rsa = PKey::private_key_from_pem(rsa).unwrap();
let mut buf = vec![0x17; 0x200];
let user_data = UserData::new(Some(rsa.clone()), vec![0x11; 0x80]).unwrap();
let (data, _) = user_data.data();
let data = data.unwrap();
buf[..0x80].copy_from_slice(data);
user_data.sign(&mut buf, 0).unwrap();
let vrf_user_data = VerifiedUserData::new(&mut buf, UserDataType::SgnRsa3072);
let res = verify_signature(
&rsa,
MessageDigest::sha512(),
&buf,
vrf_user_data.signature(),
)
.unwrap();
assert!(res);
}
#[test]
fn sign_rsa4096_fail() {
let rsa = get_test_asset!("keys/rsa4096key.pem");
let rsa = PKey::private_key_from_pem(rsa).unwrap();
let user_data = UserData::new(Some(rsa.clone()), vec![]);
assert!(matches!(
user_data.unwrap_err(),
Error::BinAsrcbUnsupportedUserDataSgnKey
));
}
#[test]
fn sign_ec() {
let (ec, _) = get_test_keys();
let mut buf = vec![0x11; 0x200];
let user_data = UserData::new(Some(ec.clone()), vec![0x11; 0x80]).unwrap();
let (data, _) = user_data.data();
let data = data.unwrap();
buf[..0x80].copy_from_slice(data);
user_data.sign(&mut buf, 0).unwrap();
let buf_ec = EcUserData::mut_from(&mut buf).unwrap();
let EcUserData {
data,
signature,
res_18b,
sgn_size,
res_192,
} = buf_ec;
assert_eq!(data, &[0x11u8; 256]);
assert_ne!(signature, &[0x11u8; 139]);
assert_eq!(res_18b, &[0u8; 5]);
assert!(sgn_size.get() <= 139);
assert_eq!(res_192, &[0u8; 110]);
let vrf_user_data = VerifiedUserData::new(&mut buf, UserDataType::SgnEcSECP521R1);
let res = verify_signature(
&ec,
MessageDigest::sha512(),
&buf,
vrf_user_data.signature(),
)
.unwrap();
assert!(res);
}
#[test]
fn sign_ec_fail() {
let ec = get_test_asset!("keys/ecsecp256k1.pem");
let ec = PKey::private_key_from_pem(ec).unwrap();
let user_data = UserData::new(Some(ec.clone()), vec![]);
assert!(matches!(
user_data.unwrap_err(),
Error::BinAsrcbUnsupportedUserDataSgnKey
));
}
#[test]
fn check_format() {
let (_, ec) = get_test_keys();
check_key_format(UserDataType::SgnEcSECP521R1, &ec).unwrap();
let res = check_key_format(UserDataType::SgnRsa2048, &ec);
assert!(matches!(res, Err(Error::AsrcbUserDataKeyMismatch { .. })));
let rsa = get_test_asset!("keys/rsa2048key.pub.pem");
let rsa = PKey::public_key_from_pem(rsa).unwrap();
check_key_format(UserDataType::SgnRsa2048, &rsa).unwrap();
let rsa = get_test_asset!("keys/rsa3072key.pub.pem");
let rsa = PKey::public_key_from_pem(rsa).unwrap();
check_key_format(UserDataType::SgnRsa3072, &rsa).unwrap();
let res = check_key_format(UserDataType::SgnRsa2048, &rsa);
assert!(matches!(res, Err(Error::AsrcbUserDataKeyMismatch { .. })));
let rsa = get_test_asset!("keys/rsa4096key.pem");
let rsa = PKey::private_key_from_pem(rsa).unwrap();
let rsa = PKey::public_key_from_pem(&rsa.public_key_to_pem().unwrap()).unwrap();
let res = check_key_format(UserDataType::SgnRsa2048, &rsa);
assert!(matches!(res, Err(Error::AsrcbUserDataKeyMismatch { .. })));
}
#[test]
fn kind() {
let (ec, _) = get_test_keys();
let kind = UserData::user_data_type(&ec).unwrap();
assert_eq!(kind, UserDataType::SgnEcSECP521R1);
let rsa = get_test_asset!("keys/rsa2048key.pem");
let rsa = PKey::private_key_from_pem(rsa).unwrap();
let kind = UserData::user_data_type(&rsa).unwrap();
assert_eq!(kind, UserDataType::SgnRsa2048);
let rsa = get_test_asset!("keys/rsa3072key.pem");
let rsa = PKey::private_key_from_pem(rsa).unwrap();
let kind = UserData::user_data_type(&rsa).unwrap();
assert_eq!(kind, UserDataType::SgnRsa3072);
let rsa = get_test_asset!("keys/rsa4096key.pem");
let rsa = PKey::private_key_from_pem(rsa).unwrap();
let kind = UserData::user_data_type(&rsa).unwrap_err();
assert!(matches!(kind, Error::BinAsrcbUnsupportedUserDataSgnKey));
}
#[test]
fn new() {
let (ec, _) = get_test_keys();
let user_data = UserData::new(
Some(ec.clone()),
vec![1; UserDataType::SgnEcSECP521R1.max()],
)
.unwrap();
assert!(matches!(user_data, UserData::Signed(_)));
let user_data = UserData::new(Some(ec), vec![1; UserDataType::SgnEcSECP521R1.max() + 1]);
assert!(matches!(
user_data,
Err(Error::AsrcbInvSgnUserData(UserDataType::SgnEcSECP521R1))
));
let user_data = UserData::new(None, vec![1; UserDataType::Unsigned.max()]).unwrap();
assert!(matches!(user_data, UserData::Unsigned(_)));
let user_data = UserData::new(None, vec![1; UserDataType::Unsigned.max() + 1]);
assert!(matches!(
user_data,
Err(Error::AsrcbInvSgnUserData(UserDataType::Unsigned))
));
}
#[test]
fn data() {
let (ec, _) = get_test_keys();
let data_in = vec![1; UserDataType::SgnEcSECP521R1.max()];
let user_data = UserData::new(Some(ec.clone()), data_in.clone()).unwrap();
let exp_pad = Some(vec![0; UserData::USER_DATA_SIZE - data_in.len()]);
let (data_out, pad) = user_data.data();
assert_eq!(data_out, Some(data_in.as_ref()));
assert_eq!(pad, exp_pad);
let data_in = vec![1; UserDataType::SgnEcSECP521R1.max() - 1];
let user_data = UserData::new(Some(ec.clone()), data_in.clone()).unwrap();
let exp_pad = Some(vec![0; UserData::USER_DATA_SIZE - data_in.len()]);
let (data_out, pad) = user_data.data();
assert_eq!(data_out, Some(data_in.as_ref()));
assert_eq!(pad, exp_pad);
}
}

View File

@@ -2,13 +2,12 @@
//
// Copyright IBM Corp. 2023
use super::AddSecretMagic;
use crate::{
request::MagicValue,
requires_feat,
uv::{uv_ioctl, UvCmd, UvDevice},
Error, Result, PAGESIZE,
};
use pv_core::request::{uvsecret::AddSecretMagic, MagicValue};
use std::io::Read;
use std::usize;
@@ -64,7 +63,7 @@ impl AddCmd {
let mut data = Vec::with_capacity(PAGESIZE);
bin_add_secret_req.read_to_end(&mut data)?;
if !AddSecretMagic::starts_with_magic(&data[..6]) {
if !AddSecretMagic::starts_with_magic(&data) {
return Err(Error::NoAsrcb);
}
Ok(Self(data))

View File

@@ -45,6 +45,12 @@ pub struct CertVerifier {
offline: bool,
}
impl std::fmt::Debug for CertVerifier {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str("CertVerifier")
}
}
impl HkdVerifier for CertVerifier {
/// This function verifies a host-key
/// document. To do so multiple steps are required:

View File

@@ -3,11 +3,10 @@
// Copyright IBM Corp. 2023
use crate::error::bail_hkd_verify;
use crate::misc::{memeq, read_crls};
use crate::misc::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},
@@ -26,9 +25,7 @@ use openssl_extensions::{
akid::{AkidCheckResult, AkidExtension},
crl::X509StoreExtension,
};
use std::cmp::Ordering;
use std::time::Duration;
use std::usize;
use std::{cmp::Ordering, ffi::c_int, time::Duration, 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
@@ -219,7 +216,7 @@ 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),
Some(entry) => entry.data().as_slice() == rhs,
}
}
@@ -376,11 +373,12 @@ fn check_x509_name_equal(lhs: &X509NameRef, rhs: &X509NameRef) -> Result<()> {
}
for l in lhs.entries() {
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())) {
if !rhs
.entries()
.any(|r| l.data().as_slice() == r.data().as_slice())
{
bail_hkd_verify!(IssuerMismatch);
}
}

View File

@@ -7,45 +7,30 @@
use super::{helper, helper::*, *};
use crate::{Error, HkdVerifyErrorType::*};
use core::slice;
use once_cell::sync::OnceCell;
use openssl::stack::Stack;
use std::sync::Mutex;
use crate::test_utils::*;
pub fn mock_endpt(res: &str) -> mockito::Mock {
static MOCK_SERVER: OnceCell<Mutex<mockito::Server>> = OnceCell::new();
let res_path = get_cert_asset_path(res);
let mut server = mockito::Server::new();
server
MOCK_SERVER
.get_or_init(|| mockito::Server::new_with_port(1234).into())
.lock()
.expect("COULD NOT GET THE MOCK_SERVER LOCK")
.mock("GET", format!("/crl/{res}").as_str())
.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
.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 mockito_server_available() {
let _mock = mock_endpt("ibm.crt");
}
#[test]
@@ -174,97 +159,6 @@ fn verify_offline() {
));
}
#[test]
fn verifier_new() {
let root_chn_crt = get_cert_asset_path_string("root_ca.chained.crt");
let root_crt = get_cert_asset_path_string("root_ca.crt");
let inter_crt = get_cert_asset_path_string("inter_ca.crt");
let inter_fake_crt = get_cert_asset_path_string("fake_inter_ca.crt");
let inter_fake_crl = get_cert_asset_path_string("fake_inter_ca.crl");
let inter_crl = get_cert_asset_path_string("inter_ca.crl");
let ibm_crt = get_cert_asset_path_string("ibm.crt");
let ibm_early_crt = get_cert_asset_path_string("ibm_outdated_early.crl");
let ibm_late_crt = get_cert_asset_path_string("ibm_outdated_late.crl");
let ibm_rev_crt = get_cert_asset_path_string("ibm_rev.crt");
// To many signing keys
let verifier = CertVerifier::new(&[ibm_crt.clone(), ibm_rev_crt.clone()], &[], &None, true);
assert!(matches!(verifier, Err(Error::HkdVerify(ManyIbmSignKeys))));
// no CRL for each X509
let verifier = CertVerifier::new(
&[inter_crt.clone(), ibm_crt.clone()],
&[inter_crl.clone()],
&Some(root_crt),
false,
);
verify_sign_error(3, verifier.unwrap_err());
let verifier = CertVerifier::new(
&[inter_crt.clone(), ibm_crt.clone()],
&[],
&Some(root_chn_crt.clone()),
false,
);
verify_sign_error(3, verifier.unwrap_err());
// wrong intermediate (or ibm key)
let verifier = CertVerifier::new(
&[inter_fake_crt, ibm_crt.clone()],
&[inter_fake_crl],
&Some(root_chn_crt.clone()),
true,
);
//Depending on the OpenSSL version different error codes can appear
verify_sign_error_slice(&[20, 30], verifier.unwrap_err());
//wrong root ca
let verifier = CertVerifier::new(
&[inter_crt.clone(), ibm_crt.clone()],
&[inter_crl.clone()],
&None,
true,
);
verify_sign_error(20, verifier.unwrap_err());
//correct signing key + intermediate cert
let _verifier = CertVerifier::new(
&[inter_crt.clone(), ibm_crt.clone()],
&[inter_crl.clone()],
&Some(root_chn_crt.clone()),
false,
)
.unwrap();
// no intermediate key
let verifier = CertVerifier::new(&[ibm_crt], &[], &Some(root_chn_crt.clone()), false);
verify_sign_error(20, verifier.unwrap_err());
//Ibm Sign outdated
let verifier = CertVerifier::new(
&[inter_crt.clone(), ibm_early_crt],
&[inter_crl.clone()],
&Some(root_chn_crt.clone()),
false,
);
assert!(matches!(verifier, Err(Error::HkdVerify(NoIbmSignKey))));
let verifier = CertVerifier::new(
&[inter_crt.clone(), ibm_late_crt],
&[inter_crl.clone()],
&Some(root_chn_crt.clone()),
false,
);
assert!(matches!(verifier, Err(Error::HkdVerify(NoIbmSignKey))));
// revoked
let verifier = CertVerifier::new(
&[inter_crt, ibm_rev_crt],
&[inter_crl],
&Some(root_chn_crt),
false,
);
verify_sign_error(23, verifier.unwrap_err());
}
#[test]
fn dist_points() {
let crt = load_gen_cert("ibm.crt");

View File

@@ -1,13 +1,19 @@
// SPDX-License-Identifier: MIT
//
// Copyright IBM Corp. 2023
#![cfg(all(feature = "request", feature = "uvsecret"))]
use openssl::{
ec::{EcGroup, EcKey},
nid::Nid,
pkey::Private,
};
use pv::{
get_test_asset,
request::{
openssl::pkey::{PKey, Public},
uvsecret::{AddSecretFlags, AddSecretRequest, AddSecretVersion, ExtSecret, GuestSecret},
uvsecret::{
verify_asrcb_and_get_user_data, AddSecretFlags, AddSecretRequest, AddSecretVersion,
ExtSecret, GuestSecret,
},
BootHdrTags, ReqEncrCtx, Request, SymKey,
},
test_utils::get_test_keys,
@@ -85,6 +91,93 @@ fn no_flag() -> AddSecretFlags {
AddSecretFlags::default()
}
fn create_signed_asrcb(skey: PKey<Private>, user_data: Vec<u8>) -> Vec<u8> {
let (host_key, ctx) = get_crypto();
let mut asrcb =
AddSecretRequest::new(AddSecretVersion::One, GuestSecret::Null, TAGS, no_flag());
asrcb.add_hostkey(host_key);
asrcb.set_user_data(user_data, Some(skey)).unwrap();
asrcb.encrypt(&ctx).unwrap()
}
#[test]
fn null_none_default_ncuid_one_user_unsgn() {
let user_data_orig = vec![0x56; 0x183];
let (host_key, ctx) = get_crypto();
let mut asrcb =
AddSecretRequest::new(AddSecretVersion::One, GuestSecret::Null, TAGS, no_flag());
asrcb.add_hostkey(host_key);
asrcb.set_user_data(user_data_orig.clone(), None).unwrap();
let asrcb = asrcb.encrypt(&ctx).unwrap();
let user_data = verify_asrcb_and_get_user_data(asrcb, None).unwrap();
assert_eq!(
user_data_orig.as_slice(),
&user_data.as_ref().unwrap()[..user_data_orig.len()]
);
}
#[test]
fn null_none_default_ncuid_one_user_ec() {
let (usr_sgn_key, _) = get_test_keys();
let usr_vrfy_key = usr_sgn_key.ec_key().unwrap();
let usr_vrfy_key = usr_vrfy_key.public_key();
let usr_vrfy_key = PKey::from_ec_key(
EcKey::from_public_key(
&EcGroup::from_curve_name(Nid::SECP521R1).unwrap(),
usr_vrfy_key,
)
.unwrap(),
)
.unwrap();
let user_data_orig = vec![0x56; 0x100];
let asrcb = create_signed_asrcb(usr_sgn_key, user_data_orig.clone());
let user_data = verify_asrcb_and_get_user_data(asrcb, Some(usr_vrfy_key)).unwrap();
assert_eq!(
user_data_orig.as_slice(),
&user_data.as_ref().unwrap()[..user_data_orig.len()]
);
}
#[test]
fn null_none_default_ncuid_one_user_rsa2048() {
let usr_sgn_key = get_test_asset!("keys/rsa2048key.pem");
let usr_sgn_key = PKey::private_key_from_pem(usr_sgn_key).unwrap();
let user_data_orig = vec![0x56; 0x100];
let asrcb = create_signed_asrcb(usr_sgn_key, user_data_orig.clone());
let usr_vrfy_key = get_test_asset!("keys/rsa2048key.pub.pem");
let usr_vrfy_key = PKey::public_key_from_pem(usr_vrfy_key).unwrap();
let user_data = verify_asrcb_and_get_user_data(asrcb, Some(usr_vrfy_key)).unwrap();
assert_eq!(
user_data_orig.as_slice(),
&user_data.as_ref().unwrap()[..user_data_orig.len()]
);
}
#[test]
fn null_none_default_ncuid_one_user_rsa3072() {
let usr_sgn_key = get_test_asset!("keys/rsa3072key.pem");
let usr_sgn_key = PKey::private_key_from_pem(usr_sgn_key).unwrap();
let user_data_orig = vec![0x56; 0x80];
let asrcb = create_signed_asrcb(usr_sgn_key, user_data_orig.clone());
let usr_vrfy_key = get_test_asset!("keys/rsa3072key.pub.pem");
let usr_vrfy_key = PKey::public_key_from_pem(usr_vrfy_key).unwrap();
let user_data = verify_asrcb_and_get_user_data(asrcb, Some(usr_vrfy_key)).unwrap();
assert_eq!(
user_data_orig.as_slice(),
&user_data.as_ref().unwrap()[..user_data_orig.len()]
);
}
#[test]
fn null_none_default_cuid_one() {
let asrcb = gen_asrcb(GuestSecret::Null, None, no_flag(), true).unwrap();
@@ -155,3 +248,12 @@ fn null_none_default_cuid_seven() {
let exp = get_test_asset!("exp/asrcb/null_none_default_cuid_seven");
assert_eq!(asrcb, exp);
}
#[test]
fn verify_no_user_data() {
let req = get_test_asset!("exp/asrcb/null_none_default_ncuid_one");
assert!(matches!(
verify_asrcb_and_get_user_data(req.to_vec(), None),
Ok(None)
))
}

View File

@@ -0,0 +1,8 @@
-----BEGIN EC PARAMETERS-----
BgUrgQQACg==
-----END EC PARAMETERS-----
-----BEGIN EC PRIVATE KEY-----
MHQCAQEEIAiQERmhEIwksExNpW2CsaQeFPKFEN6PIdMWUiX3Mu7FoAcGBSuBBAAK
oUQDQgAEO9U+NDdHcIZ59RzTq/6lDH2RA7sEyRlvFVQsj7feiuXh4Q4qeZNCb2sn
s7Hd1lUjFexH+Cp97/ggwSa3q2y/0Q==
-----END EC PRIVATE KEY-----

View File

@@ -0,0 +1,21 @@
-----BEGIN CERTIFICATE-----
MIIDZzCCAk+gAwIBAgIUK4kgzElokcI7s6XNCRD1iA7NfMQwDQYJKoZIhvcNAQEL
BQAwQjELMAkGA1UEBhMCWFgxFTATBgNVBAcMDERlZmF1bHQgQ2l0eTEcMBoGA1UE
CgwTRGVmYXVsdCBDb21wYW55IEx0ZDAgFw0yNDAxMjkxNzAxMjRaGA8zMDIzMDYw
MTE3MDEyNFowQjELMAkGA1UEBhMCWFgxFTATBgNVBAcMDERlZmF1bHQgQ2l0eTEc
MBoGA1UECgwTRGVmYXVsdCBDb21wYW55IEx0ZDCCASIwDQYJKoZIhvcNAQEBBQAD
ggEPADCCAQoCggEBAOYCZGvIP+X8Y8gpHnjg57Oo45k+jsO1GeZGBtM+MfXP1HaV
bT48VlaxaLB+K0GbQ5ykBdP7Xa1nLdbFVWtmATkbrS78xzUsefpXG8WKtA3gtE9/
v3licc34dBHiDBNrZf/elC2P3EZOkwX7AYJSlhg5uehgy0Jp4MvAWZMHOgDNRf41
Tr/jCpuntUKUcdv9+CdhzD0Z634gzPckO4QXWKaBKwa8AU4GTb41/RTOF6U5Zh1Q
exx4rdf8h7s3sbfEa5ELLpMKSUeWXd7LVFUM6TSP+/zTgRxLHmiM3gGEawYFEvla
UluGqGtC2MVpu0bGQvxgJmuy0cw7rPE5RzudIOUCAwEAAaNTMFEwHQYDVR0OBBYE
FBheoZmtoNcYxXMwzZsujv51NgslMB8GA1UdIwQYMBaAFBheoZmtoNcYxXMwzZsu
jv51NgslMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBADeRY7AS
waBH/YnZ/q/cixkaeMqtM9zRpOAVnBGOEbQiLVFhLXaT7XMT8r9JjbbYQW7RgaQS
fOHIxlG4jjly+/kXluF+Di54dMwrMCeAbxg7C/QQvMGVEWCufoJ9UGXWCzdZTziO
xREupk7RvjWUZL88kO9peGgsAelnvjWtgwEXpYpfOY7zwLvvm9RR7qujfjRMsip+
WTGjh4hksGQ9Yh8yFTx96QEnhZ69sU6ohtagOdW0/G2Yeh4fO/k77Afq1KNRatrZ
W3IeLGIVZIfajxfCYQ/ARmJg3I1h1Hw1pnw50h6Q8AQf50s0MtxIe6UaVUz4KqJH
RgLQPVsWXaWw5KA=
-----END CERTIFICATE-----

View File

@@ -0,0 +1,28 @@
-----BEGIN PRIVATE KEY-----
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDmAmRryD/l/GPI
KR544OezqOOZPo7DtRnmRgbTPjH1z9R2lW0+PFZWsWiwfitBm0OcpAXT+12tZy3W
xVVrZgE5G60u/Mc1LHn6VxvFirQN4LRPf795YnHN+HQR4gwTa2X/3pQtj9xGTpMF
+wGCUpYYObnoYMtCaeDLwFmTBzoAzUX+NU6/4wqbp7VClHHb/fgnYcw9Get+IMz3
JDuEF1imgSsGvAFOBk2+Nf0UzhelOWYdUHsceK3X/Ie7N7G3xGuRCy6TCklHll3e
y1RVDOk0j/v804EcSx5ojN4BhGsGBRL5WlJbhqhrQtjFabtGxkL8YCZrstHMO6zx
OUc7nSDlAgMBAAECggEACx1seuTnaZbDv6V1CoRbzgvIS9yBC6YRxVX2+HhV87oC
W2SnLc2+olvaYkUGlBoLr2MhwPextDJ7PZi1owX/4yGaQd4HRmOK1XUmK2qjftQw
AnN67kO6mih6e27ABKQ4/p9j67u6+k7Lvgs+LG0PQcZwXHFpASGasl3uNjOwMt+h
IJxo1UJcNM4s0WyFLWWg7dWHE/m8QWA0Qc9zBL3cIewTgr6SiTTgQGeNFpSQThgJ
E1G9cpLvchKLCcsc3L8/03ZJX00R/GyBYcJsvsTfsva5FzFG6OSt4G+73jthEC8E
jUeI0D/tw27XjBGRYNbMlsdIHqdTWxraB1HiK3CpIQKBgQD2nYXrr5isnlOSaeww
de1QMUTY7A/IUtYGknZxCj1Q/xS483er/bmfrNRAo2YMGqO/vcJxqy7vA3yACMqR
mUP8XInkfkTILYaLEECfW4+g6/f6wgBnFdIjn4vqR8XGPFwYX6IoSvzjh5iqOTon
LVx1v2Co9cdvFiD+rGT2t9FZyQKBgQDuwxjWw2bx0iH4CXQRtqv97lDu/eB9SiIl
Nb0AYGYf6/QhVkVxOBjUHitgSDWdOy2vJ8tojuxM2qT2SmGYFmjI33WAReHvPPE0
BzjIHdCAzKRJgHk02Sh86vw9NMDiGE37Tc9GdGNz8hXF4BTyuPbnDu7ta54bn7JQ
U4xVQgLcPQKBgFnM0k8YCcgRfuz5lP+wcVGaHy++BaE0SKrr+iT/TYSI3crbHOCL
62OxUziWRzpxGN9hAcTSSKzP4C3fBaQu4wqpx6xkLjeWHD/XEzDz7kDyq8Grkun2
kCZYmmIc8G9W21IkwK9bXuqbHYQegoZxhhF6NoBPvuc8b5dMvxb3qEvBAoGBAJaL
556LCN8TWx3VgB0nLF1pphh7ddAqLUt/DdQt9FbOZVmb1x9jeqxM9Za5ES05wj19
/xDHhZGUD43IKq9F+f4mgCVIawR5TbE7mH795ZlA75n06vf6sT/Kvw8ssy4cmxcI
4dgPzSGvlDD3cN326J+aV+bWA5RxZnKlq4eanskVAoGBAK+QvIp17Lq2RdcBcFr3
1Do0M2tDzjBgdzIp+8bfJHSfe3v4EEupeNa2tD2lnRlETc2JjIMFx1SMHYTi9+ax
Iuhwl7txcijtXT+5kUO4Km8oiQGbskJLwH1iVAGIMMASJf8lQHODby2R8x3OUQnX
YdC+wJJq6gfNo436A4lkPQsM
-----END PRIVATE KEY-----

View File

@@ -0,0 +1,9 @@
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA5gJka8g/5fxjyCkeeODn
s6jjmT6Ow7UZ5kYG0z4x9c/UdpVtPjxWVrFosH4rQZtDnKQF0/tdrWct1sVVa2YB
ORutLvzHNSx5+lcbxYq0DeC0T3+/eWJxzfh0EeIME2tl/96ULY/cRk6TBfsBglKW
GDm56GDLQmngy8BZkwc6AM1F/jVOv+MKm6e1QpRx2/34J2HMPRnrfiDM9yQ7hBdY
poErBrwBTgZNvjX9FM4XpTlmHVB7HHit1/yHuzext8RrkQsukwpJR5Zd3stUVQzp
NI/7/NOBHEseaIzeAYRrBgUS+VpSW4aoa0LYxWm7RsZC/GAma7LRzDus8TlHO50g
5QIDAQAB
-----END PUBLIC KEY-----

View File

@@ -0,0 +1,26 @@
-----BEGIN CERTIFICATE-----
MIIEZzCCAs+gAwIBAgIUK67CEnpp4Hbp9awAbW67M6M8t9cwDQYJKoZIhvcNAQEL
BQAwQjELMAkGA1UEBhMCWFgxFTATBgNVBAcMDERlZmF1bHQgQ2l0eTEcMBoGA1UE
CgwTRGVmYXVsdCBDb21wYW55IEx0ZDAgFw0yNDAxMjkxNzAxMzZaGA8zMDIzMDYw
MTE3MDEzNlowQjELMAkGA1UEBhMCWFgxFTATBgNVBAcMDERlZmF1bHQgQ2l0eTEc
MBoGA1UECgwTRGVmYXVsdCBDb21wYW55IEx0ZDCCAaIwDQYJKoZIhvcNAQEBBQAD
ggGPADCCAYoCggGBAJy1pz6/Xzq9Gicai7se9HC49cPATTPXrcdVjCi11LiPDMoL
eFCeCJhXCBNLpWWDTcl2t1EFTuIv9SDguY4nWgaxVaOz0c0FLXi0DCLEUUwjPt4L
EZoRxwOAbut1N3ACE1NY0cQnDhqHUeFehp4wXUuAmCk+z3zPVRNhFjjBKXcLOphQ
ak/3C8/ajJJBznXC4r8o4MNZSl6BjJxfLArpC4xM8/5rQ2qu1CoW6nrDLeFRfFGu
CvhJ89NHnvqvUjDSb+G5gfBRFwsjw3rI80o8VYIPeM79PWILy5nMtWZDQ/J6L713
eRhrXyr42kre3c6tkkSjfciEUTwyXxcCOThX/wnN8mWg9PQY7UqyrR8mPFbSFTi0
CIL8nPi3U9tcpHOMKzrVyaYELeNhUNpDfkWE5s1XYdfiDl3Jd60GxOKpU9gCgof8
Sf21cSIcHCITqjG6Oga10ZxeCZrGEZrm/46p+KWXuWL+Tp12SvQqm7Fjc7W8MjSC
ZXEWltQcWzgq+DkOfQIDAQABo1MwUTAdBgNVHQ4EFgQU9QUBlQYenYdS6Jeo0//F
4S+xRZEwHwYDVR0jBBgwFoAU9QUBlQYenYdS6Jeo0//F4S+xRZEwDwYDVR0TAQH/
BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAYEAOjinmH5+2smoKj0fkKmgFWQqH2St
TbwtC4M7xJpge4uwEj/48CZPECZzMmkKs//x1bRepX3wBfCSrVZNjoynFxZtyjCA
2Ss86F3h/FGQaLJoIGUJN1EW8MIqxQRkv0KRBPKiGay2xocErjNjxlkDeyuDliyP
y5GUF4h+pgjlVO0b5uR03loYty65jmHucPijGkF7Kgr208SCOXArWZEyLZx1Pian
PiVxJP2mnsK9wsBBNvjH1DB+gMbOW3v6nl/2xD1Q/Lz0gif7fZZiTTnt47mlqD4d
QWOH22uPO1EMG7K66CgYQd2mo3ZKpMhPeAC3OajEnY2hXOeI/w3dsvaMwIAMUQgu
y585iOfifUgEwXXAgGS0Hq1nQwDK1TjvM5sP7nbBfIlPwI78NoK9QM7p/O7UvccD
UHAjsDgmU5q/hhqs6v6gvlX3GqOnFSdviUQGeX2l9Hw6lFWM9HIKC968ykzlp9m6
uOv1xi5CIF/ZqIkDF//ZdISgZuyB4zWOcRIT
-----END CERTIFICATE-----

View File

@@ -0,0 +1,40 @@
-----BEGIN PRIVATE KEY-----
MIIG/QIBADANBgkqhkiG9w0BAQEFAASCBucwggbjAgEAAoIBgQCctac+v186vRon
Gou7HvRwuPXDwE0z163HVYwotdS4jwzKC3hQngiYVwgTS6Vlg03JdrdRBU7iL/Ug
4LmOJ1oGsVWjs9HNBS14tAwixFFMIz7eCxGaEccDgG7rdTdwAhNTWNHEJw4ah1Hh
XoaeMF1LgJgpPs98z1UTYRY4wSl3CzqYUGpP9wvP2oySQc51wuK/KODDWUpegYyc
XywK6QuMTPP+a0NqrtQqFup6wy3hUXxRrgr4SfPTR576r1Iw0m/huYHwURcLI8N6
yPNKPFWCD3jO/T1iC8uZzLVmQ0Pyei+9d3kYa18q+NpK3t3OrZJEo33IhFE8Ml8X
Ajk4V/8JzfJloPT0GO1Ksq0fJjxW0hU4tAiC/Jz4t1PbXKRzjCs61cmmBC3jYVDa
Q35FhObNV2HX4g5dyXetBsTiqVPYAoKH/En9tXEiHBwiE6oxujoGtdGcXgmaxhGa
5v+Oqfill7li/k6ddkr0KpuxY3O1vDI0gmVxFpbUHFs4Kvg5Dn0CAwEAAQKCAYAP
wpkaT2bFYoVBz4KkA/3zZDabRDi/cP5yIKFi6r6E/SX9H+Auw2X75CzUu/PbXLQz
AbR3+n+vccFGWDNxH3YHXEhPmWMNKmsjS4A48uN2M8k7zYMsqoMIUB9XoMc9hYfq
BvG5t2/MrTVH5BjHyfdoA3vKEh4N4FAfDdGNTafc6A0xikzW0/KlKcafYuzfnQTu
0q6gFtfK316Dz3J/emaF6qtTN/hkv0ZpITrds8lmcqp9EHLb+5wA8KAFeCJc9JzR
dwLUf1e5eY51kQK+Iq7BsS0NujXCpEFmmUnjfrODyjkuF14ezFCCrYLz+EOdJEzV
ktCjwopSV53EkjstktkS91szUv2tWMZ9V7cZFXyHvrnmbMXOS+wqU/6gV4ZUT5XK
e7EH4mE4kre2ho2NVXHJjQWNB4ok+9+4ThmwUwhtT3+55we8u9WjjARrqZCewPq7
enmMgPJ24M17CTFd370Wufx+X1ydLdxIYXj2Q06bbyAtc+l6iVjffjoRSR0PImUC
gcEA2oRbvnTM/RqgXV39+aUGALsXOqQsWoFQIJmebseQbthf3N2GrgQx6EZZld93
EvWBLoN8/eTCAjoVRWYbX/DIr+lHXV6MmvhytUuQdpKXgmV2nXzEPSFbWKI8oHXu
rArNyHiawsiEFyzY2pKkFa8TuJ61H3zPKqZMCkH1Lb0ozqr/0hFjS7SDEbKelX0n
K66uJzktj62Qb3qLuqn/CcRokG3g5PyItVycX+TWyXiKFPk/pWfRrTcshazSLhOD
sJ1nAoHBALeXLAo8c1mICGM8QnJxma047jnWqKt8zwaXoTWu7pJwy8luUgkNDdau
FtjW3h1AcBiV6mWXXGNqXBETaBPoBbDXVtZ1QA6c0JKZKSJb4iaGHOkuopNksjcW
4RKn/rCho0eov1prbvSkAi9ghsLZFRxNgkPohazxCdZbPf+L7h7mwRLLpfTxawAS
zIZprbKDlaSijGV10yuOeHtrjnrVNleeApTtu1In/wxM3R0yYoK/d4cPbrt9Z2ZH
s6C/i4RiewKBwQCuvrQDXbJkIbvmjnjBACgJw5Py8EEL62dhepfJLUwiaGRY4sqG
NJKYllGj2b2Gas/wm3QEMcC4OSyzf9EaaxqCGoJYd3jnoRFSS+ht+hKxU2SE0ra3
0AoVmj/zbfv8lvMzgsb4R5PiWJneSPBw6r5RoJFladDd4XjjpxVji2arxvGnUwJe
Ey/Rok7RavE9LIbWlRILWRgtHgnJr5JlhCy+ezAizpSgvDvNL9pX6U47MYulQZ3a
8ElpaOf3suFYBrcCgcBmfrNLk6wiU9u66y19jwZoutvk1n6cHrXFzGln+x2eC/L5
pemOvAb5z39Z17txQURTtX6C8RnXNs3nipoUYviqXc5RNGxqvAmFjpR0Zi+/qigB
1tNUzmbgcpl9POTn35W0r/UEme38DoRZxt31chRjFLKhDvNyNa90SMQ4IvrxrYok
4CTqts0x4UzlzaH3g5bLryQKkNkv+/4viUCmOtp31/NBCegGWeX+C/t2DWcJ77Lf
aDI94ikT5PvXxpgxdHkCgcAw5MMLz6i30bQOCEQdGurjbev3oFmHyNFIxkNmUdrA
/4ixCLSXW/m2s+T09qLIFbyABNK9VvTKVvM0V4l4ZvvuZdhLCrSZTCnXBmNBhFq1
UCa1BvE8f98dDcDNTp7ICjTOaVk4ZB8yhL0f4kMIs1jJZ1XoWCbsRS6Y2YSsF6IH
Ntzs7smF3ExIaDlYrVxB/K0TPAXPcCzQ+np9KspLinY+gCThAzQ057YxzRaj00I4
ErvUlL2xPdMTZ14eNgDwQk8=
-----END PRIVATE KEY-----

View File

@@ -0,0 +1,11 @@
-----BEGIN PUBLIC KEY-----
MIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEAnLWnPr9fOr0aJxqLux70
cLj1w8BNM9etx1WMKLXUuI8Mygt4UJ4ImFcIE0ulZYNNyXa3UQVO4i/1IOC5jida
BrFVo7PRzQUteLQMIsRRTCM+3gsRmhHHA4Bu63U3cAITU1jRxCcOGodR4V6GnjBd
S4CYKT7PfM9VE2EWOMEpdws6mFBqT/cLz9qMkkHOdcLivyjgw1lKXoGMnF8sCukL
jEzz/mtDaq7UKhbqesMt4VF8Ua4K+Enz00ee+q9SMNJv4bmB8FEXCyPDesjzSjxV
gg94zv09YgvLmcy1ZkND8novvXd5GGtfKvjaSt7dzq2SRKN9yIRRPDJfFwI5OFf/
Cc3yZaD09BjtSrKtHyY8VtIVOLQIgvyc+LdT21ykc4wrOtXJpgQt42FQ2kN+RYTm
zVdh1+IOXcl3rQbE4qlT2AKCh/xJ/bVxIhwcIhOqMbo6BrXRnF4JmsYRmub/jqn4
pZe5Yv5OnXZK9CqbsWNztbwyNIJlcRaW1BxbOCr4OQ59AgMBAAE=
-----END PUBLIC KEY-----

View File

@@ -0,0 +1,52 @@
-----BEGIN PRIVATE KEY-----
MIIJQgIBADANBgkqhkiG9w0BAQEFAASCCSwwggkoAgEAAoICAQDpMm6nXtXh9qK+
1wbdeNaYbLLwDcBa9JtNp/Ewz+uFbCuvbdHTyigWtGn7gHtALf/SzVIIhiLPFJ3R
4BKPZN9SXz0KqmI27IQL+WPSNZCW1XLlmHj5YCsGXnrE+ogpd/addDes3ByuVJ84
Sm6FuRzjwb4x7k/D01+tFRhSg9uujwr93FMjimWd0T29za8RTOvh3/lrY4nIDjvx
pcUD9M/C7w8VcvtxQNZmgnN8KYLL5SMpVjJgTKLwMgZRP8XclX/BtMMdeRvd86KC
QYUVVJqrs8kBslZqGIJ0K1en32/KpzFKj7lJ6/lkPZwQcfmaMdZnHEwbUkloy63S
GJSFsq4NRnmPc1WaOnvyk+DCRh4eehFxJ62FRm8nT+SaHEzugZQLAm4ZvV7DB8MP
TgXarFrEYYdtKP6d1/+n0CCKT0jCvj4FRVZgh2nLqUpi3mDd3cid2cZyG6GktyV8
2j9WsJ4rbrLAuGl8QUvmpnAdPVGNkij39AB2S6nPfRZIpYUcfVKBgpDFvLscmPwp
lR0IwtOq3Z8jo7n9S/Y07gY75lWBur6FiYfLGgdlWIHdZSpO12mABc5jVsivMDwi
3/XVVGT7l0ELRbVlDEFXWZlholRdsfxqmr0O/GsRN0g9+aiqaKBkKI2eblooQCBW
AGbVEEggUSW2RQ5Z9c9bSAIRGKVNVQIDAQABAoICABN0gHee/9t3bVVClz4IhAH5
DGZFBdR3LjKPKlFXAPoDb9/8GrjypEZdcuIapZQeW+0R8bm1IL5QcZvXqw5V7Ubw
AljoPuozvUIXSLt6E8AIISsEF3bZ2rBDVza/BN3nNkBYnwFHs9TcSa3mkWXyonUv
h4o4eZfuA8A3tez2BApO8/nN7qaIBOyvtXKNigEfGI08CsQ2bIF8pOxtVtlHpijh
mbHBnM/Y1IpLPXTd+bcmox8iij1igQCzch3hL8UbBXeU3xpcYKjM8YF9RqvkgESG
6uQk8fStnf4wurVn4xzv6e4KI5YGBXgJc89Wn7fviuJuh++9/VKse3a/U9TIdwck
gFknia4j/ThU0t8gCppTcu2iXRmIKYr7jBPhd/7axxPEfA17gGJ7suslm/S/5MGn
+EEm04FjhoSAHO9Ny5jnZUQ01Lurg0d0se3rrBJzYWiWn+CS30bnhrqjiQARoBpn
HtqRDGqfvt1H0N5l10/4AoqGw3BfFhGmUBhTkfLeR8MqiK8VgaVH8GlPfGVQNJgi
B/0Q1Wvu2sAFWh5xFUNceJv2hsVt/s6V8SLLB4bN9OULBwL62teB3RgFezYwVC/i
AGSOo56JflZJFDeqYrMv+UACwIUaqm739aVfCd2kda+Ef1YCWfilWloUghQHsDGJ
WtKmQgiZCow3bpf3373ZAoIBAQD+YXKT2nY1L4GsfJJoCKh48P1yO52qbt/K8ce8
vNI+ZOwCNsOXyAdcnlYmd9GiABzOlHxkjWmfAgNE9suzvKGRHnfpYLh7RPt9f9CX
ltWlkcrNxcGAyG8XPb59ygr6o69US31U9/oiC8/WJeue5x+KyaatcjNad087qUVM
SYGb4fgZ0YLhVKDUvq3zbC8D6z2vN7t5gLGEB8VvC2DY0tLi8u2PEY6laVbyPWIv
5OBbp7XHWvDS+bMKYUJATHr2DF0K10IC/DZZMGf/V2rrzjR76gZDkyZPclTZv4Ec
EQr37Ssc8Wa7IWisSEmKVO6HKbpaUE/fBRMmzAa8ikmkVTB7AoIBAQDqrnZwI25x
zdcMD8es7ymlVfb9xwymxp3rSaLvvaSbdGjnw+B1gnNo91eJGPJUbgWwMDlO9wch
3Y9B2UIMLQfr0+OP3lWvyVqdCK8LHtkH2FJhZtEGu099EZNKvTtU4b47fna6Vcll
K0o11CzsE+TuLWZNhjriEA/xCUqR7gxm8cB+nDzuDqW4GYpk/JuAYLQwcI0dgMjH
5j9le94Hz+YHkratanU98BQh/xOTVF2wfeiWeO83LgiugewiLdEENPzgCrZ72xUG
2qH0zO6Oe++9RW2JPNTXxOmTiX/fS3pvqf2hRGnQQHSLmBk//kkKL2cSVDXltsZf
19cG+1/+sVhvAoIBAQCAnOZgSNEBPP60JdukC9SaKUYLLocdt6cgpbPWAiXOdBo7
WFL196V5N3jZRDVSUfo+nCZ0dGcVhOAAS0NcoZ+SjrQT49oCQTNiFXByZEmqx5ah
CT43jZ9VQduBH9kDNiUcWnqIrkPiWNSBMnT5x2NUOXYzNY0vTSlbQc8Org+Ar/RP
oXUp1i7mwW2TqHvw4Ew0vdnkqaOW3JL+/OK/XYBQTIC9mCPmD+ds3Io7Kt4gmR+4
JouiL6tNMTVc6w40PrYAGCuA9OHJoEbEe/9ML9qrV/xIyCpTervZyBBQD3HbPidR
H+BP9qlEidvcu+pJnlhg90AawycWs4soYkavdn7nAoIBAE6UTCQQORLyEWeAgSNu
kUtDFYiz9ZCCQK5f4abUoSFMQTQZajbujyY+a5kEOV/71vu/qfC+1iyIu0cZL/xT
t6tHL5MkpxxNF4ItPi9DDPiLfkoO2z6dyWffUqpb4lnxnHjfR3Hs95pgfGC5wrDp
mGYH3ZFnuJ+pMS1MJyPlyVFO8V1zgxNS5DpYkvZtfsPlnW/6v8V5McF9XwzkBNwo
yKUQgwpd8xPiJ1uaxTs3rIdEIXUA2VhomY+VATQQk0QNWf7Kl3+xQ8Efw8rsM5SR
+xws+xhQvhPhYzR7fc2OwFhTWxF8jtGkQ2Hac9nxkxNN+/0vJTzdjl7ZLq6vzNzD
FRECggEACyj3OwkbDMnyCtCH9Wp/fLpARkpHN+LZecUL0V6Zt2Iy9aVLSZuVLe92
dA2UWcOEenMmMzON5bL+d9Nt+beyZaU8tQZYFXLLZ4j+mZZhWX6O5rHdZhlIEN4S
NSO72c9ikcp7+PPYh07hAKEFlGnv0WwvTsdCWovv9CrPrj/vkBUevKWSWRkff+pW
9kMmFgTpA2/9z5Cys45jVpUM5M5clTzNCUPBrVVVmpcTOwKUrwemy5m4PirNJgjg
YRfenxGajQkp/ypi8eh1m7ceYnOxa4Mk9YctaIym3lkGBqn/9hgoOrI0nPEDM+qQ
io4wIYBrX/SPmp7tQvWI18Ike/IDtA==
-----END PRIVATE KEY-----

View File

@@ -0,0 +1,120 @@
// SPDX-License-Identifier: MIT
//
// Copyright IBM Corp. 2023
use pv::request::CertVerifier;
use pv::test_utils::*;
use pv::{Error, HkdVerifyErrorType::*};
use std::ffi::c_int;
#[track_caller]
fn verify_sign_error(exp_raw: c_int, obs: Error) {
verify_sign_error_slice(&[exp_raw], obs)
}
fn verify_sign_error_slice(exp_raw: &[c_int], obs: Error) {
if exp_raw
.iter()
.filter(|e| match &obs {
Error::HkdVerify(ty) => match ty {
IbmSignInvalid(err, _d) => &&err.as_raw() == e,
_ => false,
},
e => panic!("Unexpected error type: {e:?}"),
})
.count()
== 0
{
panic!("Error {obs:?} did not match one of the expected {exp_raw:?}");
}
}
#[test]
fn verifier_new() {
let root_chn_crt = get_cert_asset_path_string("root_ca.chained.crt");
let root_crt = get_cert_asset_path_string("root_ca.crt");
let inter_crt = get_cert_asset_path_string("inter_ca.crt");
let inter_fake_crt = get_cert_asset_path_string("fake_inter_ca.crt");
let inter_fake_crl = get_cert_asset_path_string("fake_inter_ca.crl");
let inter_crl = get_cert_asset_path_string("inter_ca.crl");
let ibm_crt = get_cert_asset_path_string("ibm.crt");
let ibm_early_crt = get_cert_asset_path_string("ibm_outdated_early.crl");
let ibm_late_crt = get_cert_asset_path_string("ibm_outdated_late.crl");
let ibm_rev_crt = get_cert_asset_path_string("ibm_rev.crt");
// Too many signing keys
let verifier = CertVerifier::new(&[ibm_crt.clone(), ibm_rev_crt.clone()], &[], &None, true);
assert!(matches!(verifier, Err(Error::HkdVerify(ManyIbmSignKeys))));
// No CRL for each X509
let verifier = CertVerifier::new(
&[inter_crt.clone(), ibm_crt.clone()],
&[inter_crl.clone()],
&Some(root_crt),
false,
);
verify_sign_error(3, verifier.unwrap_err());
let verifier = CertVerifier::new(
&[inter_crt.clone(), ibm_crt.clone()],
&[],
&Some(root_chn_crt.clone()),
false,
);
verify_sign_error(3, verifier.unwrap_err());
// Wrong intermediate (or ibm key)
let verifier = CertVerifier::new(
&[inter_fake_crt, ibm_crt.clone()],
&[inter_fake_crl],
&Some(root_chn_crt.clone()),
true,
);
// Depending on the OpenSSL version different error codes can appear
verify_sign_error_slice(&[20, 30], verifier.unwrap_err());
// Wrong root ca
let verifier = CertVerifier::new(
&[inter_crt.clone(), ibm_crt.clone()],
&[inter_crl.clone()],
&None,
true,
);
verify_sign_error(20, verifier.unwrap_err());
// Correct signing key + intermediate cert
let _verifier = CertVerifier::new(
&[inter_crt.clone(), ibm_crt.clone()],
&[inter_crl.clone()],
&Some(root_chn_crt.clone()),
false,
)
.unwrap();
// No intermediate key
let verifier = CertVerifier::new(&[ibm_crt], &[], &Some(root_chn_crt.clone()), false);
verify_sign_error(20, verifier.unwrap_err());
// IBM Sign outdated
let verifier = CertVerifier::new(
&[inter_crt.clone(), ibm_early_crt],
&[inter_crl.clone()],
&Some(root_chn_crt.clone()),
false,
);
assert!(matches!(verifier, Err(Error::HkdVerify(NoIbmSignKey))));
let verifier = CertVerifier::new(
&[inter_crt.clone(), ibm_late_crt],
&[inter_crl.clone()],
&Some(root_chn_crt.clone()),
false,
);
assert!(matches!(verifier, Err(Error::HkdVerify(NoIbmSignKey))));
// Revoked
let verifier = CertVerifier::new(
&[inter_crt, ibm_rev_crt],
&[inter_crl],
&Some(root_chn_crt),
false,
);
verify_sign_error(23, verifier.unwrap_err());
}

21
rust/pv_core/Cargo.toml Normal file
View File

@@ -0,0 +1,21 @@
[package]
name = "pv_core"
version = "1.0.0"
edition.workspace = true
license.workspace = true
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
libc = "0.2.49"
log = { version = "0.4.6", features = ["std", "release_max_level_debug"] }
thiserror = "1.0.33"
utils = {path = "../utils"}
zerocopy = {version = "0.7", features = ["derive"]}
serde = { version = "1.0.139", features = ["derive"]}
byteorder = "1.3"
[dev-dependencies]
serde_test = "1"
mockito = {version = "1", default-features = false }
lazy_static = "1.1"

77
rust/pv_core/src/error.rs Normal file
View File

@@ -0,0 +1,77 @@
// SPDX-License-Identifier: MIT
//
// Copyright IBM Corp. 2023
/// Result type for this crate
pub type Result<T, E = Error> = std::result::Result<T, E>;
/// Error cases for this crate
#[allow(missing_docs)]
#[derive(thiserror::Error, Debug)]
#[non_exhaustive]
pub enum Error {
#[cfg_attr(debug_assertions, error("Ultravisor: '{msg}' ({rc:#06x},{rrc:#06x})"))]
#[cfg_attr(not(debug_assertions), error("Ultravisor: '{msg}' ({rc:#06x})"))]
Uv {
rc: u16,
rrc: u16,
msg: &'static str,
},
#[error("{0}")]
Specification(String),
#[error("Cannot {ty} {ctx} at `{path}`")]
FileIo {
ty: FileIoErrorType,
ctx: String,
path: String,
source: std::io::Error,
},
#[error("Cannot {ty} `{path}`")]
FileAccess {
ty: FileAccessErrorType,
path: String,
source: std::io::Error,
},
#[error("Cannot encode secrets (Too many secrets)")]
ManySecrets,
#[error("Cannot decode secret list")]
InvSecretList(#[source] std::io::Error),
#[error("Input does not contain an add-secret request")]
NoAsrcb,
#[error("Input contains unsupported user-data type: {0:#06x}")]
UnsupportedUserData(u16),
// errors from other crates
#[error(transparent)]
Io(#[from] std::io::Error),
#[error(transparent)]
ParseInt(#[from] std::num::ParseIntError),
}
/// Error cases for I/O operations
#[derive(thiserror::Error, Debug)]
#[non_exhaustive]
#[allow(missing_docs)]
pub enum FileIoErrorType {
#[error("read")]
Read,
#[error("write")]
Write,
}
/// Error cases for accessing files
#[derive(thiserror::Error, Debug)]
#[non_exhaustive]
#[allow(missing_docs)]
pub enum FileAccessErrorType {
#[error("open")]
Open,
#[error("create")]
Create,
}

93
rust/pv_core/src/lib.rs Normal file
View File

@@ -0,0 +1,93 @@
// SPDX-License-Identifier: MIT
//
// Copyright IBM Corp. 2023
#![deny(missing_docs)]
#![allow(unused)]
//! pv_core - basic library for pv-tools
//!
//! This library is intened to be used by tools and libraries that
//! are used for creating and managing IBM Secure Execution guests.
//! `pv_core` provides abstraction layers for secure memory management,
//! logging, and accessing the uvdevice.
//!
//! It does not provide any cryptographic operations through OpenSSL.
//! For this use `pv` which reexports all symbos from this crate.
mod error;
mod log;
mod macros;
mod tmpfile;
mod utils;
mod uvdevice;
mod uvsecret;
pub use crate::log::PvLogger;
pub use error::{Error, FileAccessErrorType, FileIoErrorType, Result};
/// Miscellaneous functions and definitions
pub mod misc {
pub use crate::utils::pv_guest_bit_set;
pub use crate::utils::{create_file, open_file, read_exact_file, read_file, write_file};
pub use crate::utils::{parse_hex, to_u16, to_u32, try_parse_u128, try_parse_u64};
pub use crate::utils::{read, write};
pub use crate::utils::{Flags, Lsb0Flags64, Msb0Flags64};
pub use crate::tmpfile::TemporaryDirectory;
}
/// Definitions and functions for interacting with the Ultravisor
pub mod uv {
pub use crate::uvdevice::secret::{AddCmd, ListCmd, LockCmd};
pub use crate::uvdevice::secret::{ListableSecretType, SecretEntry, SecretList};
pub use crate::uvdevice::{
uv_ioctl, ConfigUid, UvCmd, UvDevice, UvDeviceInfo, UvFlags, UvcSuccess,
};
}
/// Functionalities to verify UV requests
pub mod request {
/// Functionalities for reading add-secret requests
pub mod uvsecret {
pub use crate::uvsecret::AddSecretMagic;
pub use crate::uvsecret::UserDataType;
}
/// Version number of the request in system-endian
pub type RequestVersion = u32;
/// Request magic value
///
/// The first 8 byte of a request providing an identifier of the request type
/// for programs
pub type RequestMagic = [u8; 8];
/// A `MagicValue` is a bytepattern, that indicates if a byte slice contains the specified
/// (binary) data.
pub trait MagicValue<const N: usize> {
/// Magic value as byte array
const MAGIC: [u8; N];
/// Test whether the given slice starts with the magic value.
fn starts_with_magic(v: &[u8]) -> bool {
if v.len() < Self::MAGIC.len() {
return false;
}
v[..Self::MAGIC.len()] == Self::MAGIC
}
}
}
/// Provides cargo version Info about this crate.
///
/// Produces `pv_core-crate <version>`
pub const fn crate_info() -> &'static str {
concat!(env!("CARGO_PKG_NAME"), "-crate ", env!("CARGO_PKG_VERSION"))
}
// Internal definitions/ imports
const PAGESIZE: usize = 0x1000;
use ::utils::assert_size;
use ::utils::static_assert;
#[doc(hidden)]
/// stuff pv_core and pv share. Not intended for other users
pub mod for_pv {
pub use crate::uvdevice::secret::ser_gsid;
pub use crate::uvdevice::secret::SECRET_ID_SIZE;
}

View File

@@ -0,0 +1,41 @@
// SPDX-License-Identifier: MIT
//
// Copyright IBM Corp. 2023
macro_rules! path_to_str {
($path: expr) => {
$path.as_ref().to_str().unwrap_or("no UTF-8 path")
};
}
pub(crate) use path_to_str;
macro_rules! file_error {
($ty: tt, $ctx: expr, $path:expr, $src: expr) => {
$crate::Error::FileIo {
ty: $crate::FileIoErrorType::$ty,
ctx: $ctx.to_string(),
path: $path.to_string(),
source: $src,
}
};
}
pub(crate) use file_error;
macro_rules! bail_spec {
($str: expr) => {
return Err($crate::Error::Specification($str.to_string()))
};
}
pub(crate) use bail_spec;
#[doc(hidden)]
#[macro_export]
macro_rules! file_acc_error {
($ty: tt, $path:expr, $src: expr) => {
$crate::Error::FileAccess {
ty: $crate::FileAccessErrorType::$ty,
path: $path.to_string(),
source: $src,
}
};
}

152
rust/pv_core/src/tmpfile.rs Normal file
View File

@@ -0,0 +1,152 @@
use std::{
ffi::{CString, OsStr},
os::unix::prelude::OsStrExt,
path::{Path, PathBuf},
};
/// Rust wrapper for `libc::mkdtemp`
fn mkdtemp<P: AsRef<Path>>(template: P) -> Result<PathBuf, std::io::Error> {
let template_cstr = CString::new(template.as_ref().as_os_str().as_bytes())?;
let template_raw = template_cstr.into_raw();
unsafe {
// SAFETY: template_raw is a valid CString because it was generated by
// the `CString::new`.
let ret = libc::mkdtemp(template_raw);
if ret.is_null() {
Err(std::io::Error::last_os_error())
} else {
// SAFETY: `template_raw` is still a valid CString because it was
// generated by `CString::new` and modified by `libc::mkdtemp`.
let path_cstr = std::ffi::CString::from_raw(template_raw);
let path = OsStr::from_bytes(path_cstr.as_bytes());
let path = std::path::PathBuf::from(path);
Ok(path)
}
}
}
/// This type creates a temporary directory that is automatically removed when
/// it goes out of scope. It utilizes the `mkdtemp` function and its semantics,
/// with the addition of automatically including the template characters
/// `XXXXXX`.
#[derive(PartialEq, Eq, Debug)]
pub struct TemporaryDirectory {
path: Box<Path>,
}
impl TemporaryDirectory {
/// Creates a temporary directory using `prefix` as directory prefix.
///
/// # Errors
///
/// An error is returned if the temporary directory could not be created.
pub fn new<P: AsRef<Path>>(prefix: P) -> Result<Self, std::io::Error> {
let mut template = prefix.as_ref().to_owned();
let mut template_os_string = template.as_mut_os_string();
template_os_string.push("XXXXXX");
let temp_dir = mkdtemp(template_os_string)?;
Ok(Self {
path: temp_dir.into_boxed_path(),
})
}
/// Returns the path of the created temporary directory.
pub fn path(&self) -> &Path {
self.path.as_ref()
}
fn forget(mut self) {
self.path = PathBuf::new().into_boxed_path();
std::mem::forget(self);
}
/// Removes the created temporary directory and it's contents.
pub fn close(mut self) -> std::io::Result<()> {
let ret = std::fs::remove_dir_all(&self.path);
self.forget();
ret
}
}
impl AsRef<Path> for TemporaryDirectory {
fn as_ref(&self) -> &Path {
self.path()
}
}
impl Drop for TemporaryDirectory {
fn drop(&mut self) {
let _ = std::fs::remove_dir_all(&self.path);
}
}
#[cfg(test)]
mod tests {
use std::path::PathBuf;
use super::{mkdtemp, TemporaryDirectory};
#[test]
fn mkdtemp_test() {
let template_inv_not_last_characters = "XXXXXXyay";
let template_inv_too_less_x = "yayXXXXX";
let template_inv_path_does_not_exist = "../NA-yay/XXXXXX";
let template = "yayXXXXXX";
let err = mkdtemp(template_inv_not_last_characters).expect_err("invalid template");
let err = mkdtemp(template_inv_too_less_x).expect_err("invalid template");
let err =
mkdtemp(template_inv_path_does_not_exist).expect_err("path does not exist template");
let path = mkdtemp(template).expect("mkdtemp should work");
assert!(path.exists());
assert!(path.as_os_str().to_str().expect("works").starts_with("yay"));
std::fs::remove_dir(path);
}
#[test]
fn temporary_directory_empty_name_test() {
let temp_dir = TemporaryDirectory::new("").expect("should work");
let path = temp_dir.path().to_owned();
assert!(path.exists());
// Test that close removes the directory
temp_dir.close();
assert!(!path.exists());
}
#[test]
fn temporary_directory_drop_test() {
let temp_dir = TemporaryDirectory::new("").expect("should work");
let path = temp_dir.path().to_owned();
assert!(path.exists());
// Test that the destructor removes the directory
drop(temp_dir);
assert!(!path.exists());
}
#[test]
fn temporary_directory_close_test() {
let temp_dir = TemporaryDirectory::new("yay").expect("should work");
let path = temp_dir.path().to_owned();
assert!(path.exists());
assert!(path.as_os_str().to_str().expect("works").starts_with("yay"));
// Test that close() removes the directory
temp_dir.close();
assert!(!path.exists());
}
#[test]
fn temporary_directory_as_ref_test() {
let temp_dir = TemporaryDirectory::new("").expect("should work");
assert_eq!(temp_dir.path(), temp_dir.as_ref());
}
}

562
rust/pv_core/src/utils.rs Normal file
View File

@@ -0,0 +1,562 @@
// SPDX-License-Identifier: MIT
//
// Copyright IBM Corp. 2023
use crate::{
macros::{bail_spec, file_error, path_to_str},
Error, FileAccessErrorType, FileIoErrorType, Result,
};
use std::{
fs::File,
io::{Read, Write},
path::Path,
};
use zerocopy::{AsBytes, BigEndian, FromBytes, FromZeroes, U64};
/// Trait that describes bitflags, represented by `T`.
pub trait Flags<T>: From<T> + for<'a> From<&'a T> {
/// Set the specified bit to one.
/// # Panics
///Panics if bit is >= 64
fn set_bit(&mut self, bit: u8);
/// Set the specified bit to zero.
/// # Panics
///Panics if bit is >= 64
fn unset_bit(&mut self, bit: u8);
/// Test if the specified bit is set.
/// # Panics
///Panics if bit is >= 64
fn is_set(&self, bit: u8) -> bool;
}
/// Bitflags in MSB0 ordering
///
/// Wraps an u64 to set/get individual bits
#[repr(C)]
#[derive(Debug, Clone, Copy, Default, AsBytes, FromZeroes, FromBytes)]
pub struct Msb0Flags64(U64<BigEndian>);
impl Flags<u64> for Msb0Flags64 {
#[track_caller]
fn set_bit(&mut self, bit: u8) {
assert!(bit < 64, "Flag bit set to greater than 63");
let mut v = self.0.get();
v |= 1 << (63 - bit);
self.0.set(v)
}
#[track_caller]
fn unset_bit(&mut self, bit: u8) {
assert!(bit < 64, "Flag bit set to greater than 63");
let mut v = self.0.get();
v &= !(1 << (63 - bit));
self.0.set(v)
}
#[track_caller]
fn is_set(&self, bit: u8) -> bool {
assert!(bit < 64, "Flag bit set to greater than 63");
self.0.get() & (1 << (63 - bit)) > 0
}
}
impl From<u64> for Msb0Flags64 {
fn from(value: u64) -> Self {
Self(value.into())
}
}
impl From<&u64> for Msb0Flags64 {
fn from(value: &u64) -> Self {
(*value).into()
}
}
/// Bitflags in LSB0 ordering
///
/// Wraps an u64 to set/get individual bits
#[repr(C)]
#[derive(Debug, Clone, Copy, Default, AsBytes, FromZeroes, FromBytes)]
pub struct Lsb0Flags64(U64<BigEndian>);
impl Flags<u64> for Lsb0Flags64 {
#[track_caller]
fn set_bit(&mut self, bit: u8) {
assert!(bit < 64, "Flag bit set to greater than 63");
let mut v = self.0.get();
v |= 1 << bit;
self.0.set(v)
}
#[track_caller]
fn unset_bit(&mut self, bit: u8) {
assert!(bit < 64, "Flag bit set to greater than 63");
let mut v = self.0.get();
v &= !(1 << bit);
self.0.set(v)
}
#[track_caller]
fn is_set(&self, bit: u8) -> bool {
assert!(bit < 64, "Flag bit set to greater than 63");
self.0.get() & (1 << bit) > 0
}
}
impl From<u64> for Lsb0Flags64 {
fn from(value: u64) -> Self {
Self(value.into())
}
}
impl From<&u64> for Lsb0Flags64 {
fn from(value: &u64) -> Self {
(*value).into()
}
}
/// Tries to convert a BE hex string into a 128 unsigned integer
/// The hexstring must contain 32chars of hexdigits
///
/// * `hex_str` - string to convert can be prepended with "0x"
/// * `ctx` - Error context string in case of an error
/// ```rust
/// # use std::error::Error;
/// # use pv_core::misc::try_parse_u128;
/// # fn main() -> Result<(), Box<dyn Error>> {
/// let hex = "11223344556677889900aabbccddeeff";
/// try_parse_u128(&hex, "The test")?;
/// # Ok(())
/// # }
/// ```
///
/// # Errors
/// If `hex_string` is not a 32 byte hex string an Error appears
pub fn try_parse_u128(hex_str: &str, ctx: &str) -> Result<[u8; 16]> {
let hex_str = if hex_str.starts_with("0x") {
hex_str.split_at(2).1
} else {
hex_str
};
if hex_str.len() != 32 {
bail_spec!(format!(
"{ctx} hexstring must be 32chars long to cover all 16 bytes"
));
}
parse_hex(hex_str).try_into().map_err(|_| {
Error::Specification(format!(
"{ctx} hexstring must be 32chars long to cover all 16 bytes"
))
})
}
/// Tries to convert a BE hex string into a 64 unsigned integer
/// The hexstring must *NOT* contain 16 chars of hexdigits, but
/// 16 chars at most.
///
/// * `hex_str` - string to convert can be prepended with "0x"
/// * `ctx` - Error context string in case of an error
/// ```rust
/// # use std::error::Error;
/// # use pv_core::misc::try_parse_u64;
/// # fn main() -> Result<(), Box<dyn Error>> {
/// let hex = "1234567890abcdef";
/// try_parse_u64(&hex, "The test")?;
/// # Ok(())
/// # }
/// ```
///
/// # Errors
/// If `hex_string` is not a 32 byte hex string an Error appears
pub fn try_parse_u64(hex_str: &str, ctx: &str) -> Result<u64> {
let hex_str = if hex_str.starts_with("0x") {
hex_str.split_at(2).1
} else {
hex_str
};
if hex_str.len() > 16 {
bail_spec!(format!(
"{ctx} hexstring {hex_str} must be max 16 chars long"
));
}
Ok(u64::from_str_radix(hex_str, 16)?)
}
/// Open a file.
///
/// Wraps [`File::open`]
///
/// * `path` - Path to file
pub fn open_file<P: AsRef<Path>>(path: P) -> Result<File> {
File::open(&path).map_err(|e| Error::FileAccess {
ty: FileAccessErrorType::Open,
path: path_to_str!(path).to_string(),
source: e,
})
}
/// Create a file.
///
/// Wraps [`File::create`]
///
/// * `path` - Path to file
pub fn create_file<P: AsRef<Path>>(path: P) -> Result<File> {
File::create(&path).map_err(|e| Error::FileAccess {
ty: FileAccessErrorType::Create,
path: path_to_str!(path).to_string(),
source: e,
})
}
/// Read exactly COUNT bytes into a buffer and return it.
///
/// * `path` - Path to file
/// * `ctx` - Error context string in case of an error
///
/// # Errors
/// If this function encounters an "end of file" before completely filling
/// the buffer, it returns an error. The contents of `buf` are unspecified in this case.
///
/// If aber so hast du mmmeny other read error is encountered then this function immediately
/// returns. The contents of `buf` are unspecified in this case.
///
/// If this function returns an error, it is unspecified how many bytes it
/// has read, but it will never read more than would be necessary to
/// completely fill the buffer.
pub fn read_exact_file<P: AsRef<Path>, const COUNT: usize>(
path: P,
ctx: &str,
) -> Result<[u8; COUNT]> {
let mut f = std::fs::File::open(&path).map_err(|e| Error::FileAccess {
ty: crate::FileAccessErrorType::Open,
path: path_to_str!(path).to_string(),
source: e,
})?;
if f.metadata()?.len() as usize != COUNT {
bail_spec!(format!("{ctx} must be exactly {COUNT} bytes long"));
}
let mut buf = [0; COUNT];
f.read_exact(&mut buf)
.map_err(|e| file_error!(Read, ctx, path_to_str!(path).to_string(), e))?;
Ok(buf)
}
/// Read content from a file and add context in case of an error
///
/// * `path` - Path to file
/// * `ctx` - Error context string in case of an error
///
///
/// # Errors
/// Passes through any kind of error `std::fs::read` produces
pub fn read_file<P: AsRef<Path>>(path: P, ctx: &str) -> Result<Vec<u8>> {
std::fs::read(&path).map_err(|e| {
file_error!(
Read,
ctx,
path.as_ref().to_str().unwrap_or("no UTF-8 path"),
e
)
})
}
/// Reads all content from a [`std::io::Read`] and add context in case of an error
///
/// * `path` - Path to file
/// * `ctx` - Error context string in case of an error
///
///
/// # Errors
/// Passes through any kind of error `std::fs::read` produces
pub fn read<R: Read>(rd: &mut R, path: &str, ctx: &str) -> Result<Vec<u8>> {
let mut buf = vec![];
rd.read_to_end(&mut buf).map_err(|e| Error::FileIo {
ty: FileIoErrorType::Write,
ctx: ctx.to_string(),
path: path.to_string(),
source: e,
})?;
Ok(buf)
}
/// write content to a file and add context in case of an error
///
/// * `path` - Path to file
/// * `ctx` - Error context string in case of an error
///
///
/// # Errors
/// Passes through any kind of error `std::fs::write` produces
pub fn write_file<D: AsRef<[u8]>>(path: &str, data: D, ctx: &str) -> Result<()> {
std::fs::write(path, data.as_ref()).map_err(|e| Error::FileIo {
ty: FileIoErrorType::Write,
ctx: ctx.to_string(),
path: path.to_string(),
source: e,
})
}
/// Write content to a [`std::io::Write`] and add context in case of an error
///
/// * `path` - Path to file
/// * `ctx` - Error context string in case of an error
///
///
/// # Errors
/// Passes through any kind of error `std::fs::write` produces
pub fn write<D: AsRef<[u8]>, W: Write>(wr: &mut W, data: D, path: &str, ctx: &str) -> Result<()> {
wr.write_all(data.as_ref()).map_err(|e| Error::FileIo {
ty: FileIoErrorType::Write,
ctx: ctx.to_string(),
path: path.to_string(),
source: e,
})
}
macro_rules! usize_to_ui {
($(#[$attr:meta])* => $t: ident, $name:ident) => {
///Converts an [`usize`] to an [`
$(#[$attr])*
///`] if possible
pub fn $name(u: usize) -> Option<$t> {
if u > $t::MAX as usize {
None
} else {
Some(u as $t)
}
}
}
}
usize_to_ui! {
#[doc = r"u32"]
=> u32, to_u32}
usize_to_ui! {
#[doc = r"u16"]
=> u16, to_u16}
/// Converts the hexstring into a byte vector.
///
/// Stops if the end or until a non hex chat is found
pub fn parse_hex(hex_str: &str) -> Vec<u8> {
let mut hex_bytes = hex_str.as_bytes().iter().map_while(|b| match b {
b'0'..=b'9' => Some(b - b'0'),
b'a'..=b'f' => Some(b - b'a' + 10),
b'A'..=b'F' => Some(b - b'A' + 10),
_ => None,
});
let mut bytes = Vec::new();
while let (Some(h), Some(l)) = (hex_bytes.next(), hex_bytes.next()) {
bytes.push(h << 4 | l)
}
bytes
}
/// Report if the `prot_virt_guest` sysfs entry is one.
///
/// If the entry does not exist returns false.
///
/// for non-s390-architectures:
/// Returns always false
/// A non-s390 system cannot be a secure execution guest.
#[allow(unreachable_code)]
pub fn pv_guest_bit_set() -> bool {
#[cfg(not(target_arch = "s390x"))]
return false;
//s390 branch
let v = std::fs::read("/sys/firmware/uv/prot_virt_guest").unwrap_or_else(|_| vec![0]);
let v: u8 = String::from_utf8_lossy(&v[..1]).parse().unwrap_or(0);
v == 1
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn msb_flags() {
let v = 17;
let v_flag: Msb0Flags64 = v.into();
assert_eq!(v, v_flag.0.get());
let mut v: Msb0Flags64 = 4.into();
v.unset_bit(61);
assert_eq!(v.0.get(), 0);
v.set_bit(61);
assert_eq!(4, v.0.get());
let mut v = Msb0Flags64::default();
v.set_bit(0);
assert_eq!(&[0x80, 0, 0, 0, 0, 0, 0, 0], v.as_bytes());
v.set_bit(0);
assert_eq!(&[0x80, 0, 0, 0, 0, 0, 0, 0], v.as_bytes());
v.set_bit(1);
assert_eq!(&[0xc0, 0, 0, 0, 0, 0, 0, 0], v.as_bytes());
v.set_bit(2);
assert_eq!(&[0xe0, 0, 0, 0, 0, 0, 0, 0], v.as_bytes());
v.set_bit(3);
assert_eq!(&[0xf0, 0, 0, 0, 0, 0, 0, 0], v.as_bytes());
v.unset_bit(3);
assert_eq!(&[0xe0, 0, 0, 0, 0, 0, 0, 0], v.as_bytes());
v.unset_bit(3);
assert_eq!(&[0xe0, 0, 0, 0, 0, 0, 0, 0], v.as_bytes());
v.set_bit(16);
assert_eq!(&[0xe0, 0, 0x80, 0, 0, 0, 0, 0], v.as_bytes());
}
#[test]
#[should_panic]
fn msb_flags_set_panic() {
Msb0Flags64::default().set_bit(64)
}
#[test]
#[should_panic]
fn msb_flags_unset_panic() {
Msb0Flags64::default().unset_bit(64)
}
#[test]
fn lsb_flags() {
let v = 17;
let v_flag: Lsb0Flags64 = v.into();
assert_eq!(v, v_flag.0.get());
let mut v: Lsb0Flags64 = 4.into();
v.unset_bit(2);
assert_eq!(v.0.get(), 0);
v.set_bit(2);
assert_eq!(4, v.0.get());
let mut v = Lsb0Flags64::default();
v.set_bit(0);
assert_eq!(&[0, 0, 0, 0, 0, 0, 0, 1], v.as_bytes());
v.set_bit(0);
assert_eq!(&[0, 0, 0, 0, 0, 0, 0, 1], v.as_bytes());
v.set_bit(1);
assert_eq!(&[0, 0, 0, 0, 0, 0, 0, 3], v.as_bytes());
v.set_bit(2);
assert_eq!(&[0, 0, 0, 0, 0, 0, 0, 7], v.as_bytes());
v.set_bit(3);
assert_eq!(&[0, 0, 0, 0, 0, 0, 0, 0xf], v.as_bytes());
v.unset_bit(3);
assert_eq!(&[0, 0, 0, 0, 0, 0, 0, 7], v.as_bytes());
v.unset_bit(3);
assert_eq!(&[0, 0, 0, 0, 0, 0, 0, 7], v.as_bytes());
v.set_bit(16);
assert_eq!(&[0, 0, 0, 0, 0, 1, 0, 7], v.as_bytes());
}
#[test]
#[should_panic]
fn lsb_flags_set_panic() {
Lsb0Flags64::default().set_bit(64)
}
#[test]
#[should_panic]
fn lsb_flags_unset_panic() {
Lsb0Flags64::default().unset_bit(64)
}
#[test]
fn parse_hex() {
let s = "123456acbef0";
let exp = vec![0x12, 0x34, 0x56, 0xac, 0xbe, 0xf0];
assert_eq!(super::parse_hex(s), exp);
let s = "00123456acbef0";
let exp = vec![0, 0x12, 0x34, 0x56, 0xac, 0xbe, 0xf0];
assert_eq!(super::parse_hex(s), exp);
let s = "00123456acbef0ii90";
let exp = vec![0, 0x12, 0x34, 0x56, 0xac, 0xbe, 0xf0];
assert_eq!(super::parse_hex(s), exp);
}
#[test]
fn to_u32() {
assert_eq!(Some(17), super::to_u32(17));
assert_eq!(Some(0), super::to_u32(0));
assert_eq!(Some(u32::MAX), super::to_u32(u32::MAX as usize));
assert_eq!(None, super::to_u32(u32::MAX as usize + 1));
assert_eq!(None, super::to_u32(usize::MAX));
}
#[test]
fn parse_u128() {
assert!(matches!(
try_parse_u128("123456", ""),
Err(Error::Specification(_))
));
assert!(matches!(
try_parse_u128("-1234", ""),
Err(Error::Specification(_))
));
assert!(matches!(
try_parse_u128("0011223344556677889900aabbccddeeff", ""),
Err(Error::Specification(_))
));
assert!(matches!(
try_parse_u128("dd11223344556677889900aabbccddeeff", ""),
Err(Error::Specification(_))
));
assert!(matches!(
try_parse_u128("-1223344556677889900aabbccddeeff", ""),
Err(Error::Specification(_))
));
assert!(matches!(
try_parse_u128("0x123456", ""),
Err(Error::Specification(_))
));
assert!(matches!(
try_parse_u128("-0x1234", ""),
Err(Error::Specification(_))
));
assert!(matches!(
try_parse_u128("0x0011223344556677889900aabbccddeeff", ""),
Err(Error::Specification(_))
));
assert!(matches!(
try_parse_u128("0xdd11223344556677889900aabbccddeeff", ""),
Err(Error::Specification(_))
));
assert!(matches!(
try_parse_u128("0x-1223344556677889900aabbccddeeff", ""),
Err(Error::Specification(_))
));
assert_eq!(
[
0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0x00, 0xaa, 0xbb, 0xcc, 0xdd,
0xee, 0xff
],
try_parse_u128("11223344556677889900aabbccddeeff", "").unwrap()
);
assert_eq!(
[
0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0x00, 0xaa, 0xbb, 0xcc, 0xdd,
0xee, 0xff
],
try_parse_u128("0x11223344556677889900aabbccddeeff", "").unwrap()
);
assert_eq!(
[
0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd,
0xee, 0xff
],
try_parse_u128("00112233445566778899aabbccddeeff", "").unwrap()
);
assert_eq!(
[
0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd,
0xee, 0xff
],
try_parse_u128("00112233445566778899aabbccddeeff", "").unwrap()
);
}
}

View File

@@ -3,13 +3,15 @@
// Copyright IBM Corp. 2023
#![allow(non_camel_case_types)]
use crate::file_acc_error;
use crate::FileAccessErrorType;
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};
use std::{
convert::TryInto,
ffi::c_ulong,
fs::File,
os::unix::prelude::{AsRawFd, RawFd},
};
#[cfg(not(test))]
use ::libc::ioctl;
@@ -22,6 +24,7 @@ mod ffi;
mod info;
mod test;
pub use ffi::uv_ioctl;
pub mod secret;
pub use info::UvDeviceInfo;
#[allow(dead_code)] //TODO rm when pv learns attestation
@@ -178,7 +181,11 @@ impl UvDevice {
.read(true)
.write(true)
.open(UvDevice::PATH)
.map_err(|e| file_acc_error!(Open, UvDevice::PATH, e))?,
.map_err(|e| Error::FileAccess {
ty: FileAccessErrorType::Open,
path: (UvDevice::PATH).to_string(),
source: e,
})?,
))
}

View File

@@ -3,7 +3,7 @@
// Copyright IBM Corp. 2023
use crate::{assert_size, static_assert};
use zerocopy::{AsBytes, FromBytes};
use zerocopy::{AsBytes, FromBytes, FromZeroes};
pub const UVIO_ATT_ARCB_MAX_LEN: usize = 0x100000;
pub const UVIO_ATT_MEASUREMENT_MAX_LEN: usize = 0x8000;
@@ -55,7 +55,7 @@ assert_size!(uvio_ioctl_cb, 0x40);
/// Note that bit 0 (UVIO_IOCTL_UVDEV_INFO_NR) is always zero for `supp_uv_cmds`
/// as there is no corresponding UV-call.
#[repr(C)]
#[derive(Debug, Copy, Clone, AsBytes, FromBytes)]
#[derive(Debug, Copy, Clone, AsBytes, FromZeroes, FromBytes)]
pub struct uvio_uvdev_info {
pub supp_uvio_cmds: u64,
pub supp_uv_cmds: u64,
@@ -84,7 +84,7 @@ pub const UVIO_ATT_UID_LEN: usize = 0x10;
/// UV will return invalid command rc.
/// Obviously all numbers are in BIG-endian!
#[repr(C)]
#[derive(Debug, AsBytes, FromBytes)]
#[derive(Debug, AsBytes, FromZeroes, FromBytes)]
pub struct uvio_attest {
pub arcb_addr: u64, //in
pub meas_addr: u64, //out

View File

@@ -9,7 +9,7 @@ use crate::{
Result,
};
use std::fmt::Display;
use zerocopy::{AsBytes, FromBytes};
use zerocopy::{AsBytes, FromZeroes};
/// Information of supported functions by the uvdevice
///

View File

@@ -0,0 +1,489 @@
// SPDX-License-Identifier: MIT
//
// Copyright IBM Corp. 2023
use crate::{
assert_size,
misc::to_u16,
request::{uvsecret::AddSecretMagic, MagicValue},
uv::{uv_ioctl, UvCmd, UvDevice},
Error, Result, PAGESIZE,
};
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
use serde::{Serialize, Serializer};
use std::{
fmt::Display,
io::{Cursor, Read, Seek, Write},
slice::Iter,
vec::IntoIter,
};
use zerocopy::{AsBytes, FromBytes, FromZeroes, U16, U32};
/// _List Secrets_ Ultravisor command.
///
/// The List Secrets Ultravisor call is used to list the
/// secrets that are in the secret store for the current SE-guest.
pub struct ListCmd(Vec<u8>);
impl ListCmd {
fn with_size(size: usize) -> Self {
Self(vec![0; size])
}
/// Create a new list secrets command with a one page capacity
pub fn new() -> Self {
Self::with_size(PAGESIZE)
}
}
impl Default for ListCmd {
fn default() -> Self {
Self::new()
}
}
impl UvCmd for ListCmd {
fn data(&mut self) -> Option<&mut [u8]> {
Some(self.0.as_mut_slice())
}
fn cmd(&self) -> u64 {
uv_ioctl(UvDevice::LIST_SECRET_NR)
}
fn rc_fmt(&self, _rc: u16, _rrc: u16) -> Option<&'static str> {
None
}
}
/// _Add Secret_ Ultravisor command.
///
/// The Add Secret Ultravisor-call is used to add a secret
/// to the secret store for the current SE-guest.
pub struct AddCmd(Vec<u8>);
impl AddCmd {
/// Create a new Add Secret command using the provided data.
///
/// # Errors
///
/// This function will return an error if the provided data does not start with the
/// ['crate::AddSecretRequest'] magic Value.
pub fn new<R: Read>(bin_add_secret_req: &mut R) -> Result<Self> {
let mut data = Vec::with_capacity(PAGESIZE);
bin_add_secret_req.read_to_end(&mut data)?;
if !AddSecretMagic::starts_with_magic(&data[..6]) {
return Err(Error::NoAsrcb);
}
Ok(Self(data))
}
}
impl UvCmd for AddCmd {
fn data(&mut self) -> Option<&mut [u8]> {
Some(&mut self.0)
}
fn cmd(&self) -> u64 {
uv_ioctl(UvDevice::ADD_SECRET_NR)
}
fn rc_fmt(&self, rc: u16, _rrc: u16) -> Option<&'static str> {
match rc {
0x0101 => Some("not allowed to modify the secret store"),
0x0102 => Some("secret store locked"),
0x0103 => Some("access exception when accessing request control block"),
0x0104 => Some("unsupported add secret version"),
0x0105 => Some("invalid request size"),
0x0106 => Some("invalid number of host-keys"),
0x0107 => Some("unsupported flags specified"),
0x0108 => Some("unable to decrypt the request"),
0x0109 => Some("unsupported secret provided"),
0x010a => Some("invalid length for the specified secret"),
0x010b => Some("secret store full"),
0x010c => Some("unable to add secret"),
0x010d => Some("dump in progress, try again later"),
_ => None,
}
}
}
/// _Lock Secret Store_ Ultravisor command.
///
/// The Lock Secret Store Ultravisor-call is used to block
/// all changes to the secret store. Upon successful
/// completion of a Lock Secret Store Ultravisor-call, any
/// request to modify the secret store will fail.
pub struct LockCmd;
impl UvCmd for LockCmd {
fn cmd(&self) -> u64 {
uv_ioctl(UvDevice::LOCK_SECRET_NR)
}
fn rc_fmt(&self, rc: u16, _rrc: u16) -> Option<&'static str> {
match rc {
0x0101 => Some("not allowed to modify the secret store"),
0x0102 => Some("secret store already locked"),
_ => None,
}
}
}
/// List of secrets used to parse the [`crate::uv::ListCmd`] result.
///
/// The list should not hold more than 0xffffffff elements
#[derive(Debug, PartialEq, Eq, Serialize)]
pub struct SecretList {
total_num_secrets: usize,
secrets: Vec<SecretEntry>,
}
impl<'a> IntoIterator for &'a SecretList {
type Item = &'a SecretEntry;
type IntoIter = Iter<'a, SecretEntry>;
fn into_iter(self) -> Self::IntoIter {
self.iter()
}
}
impl IntoIterator for SecretList {
type Item = SecretEntry;
type IntoIter = IntoIter<Self::Item>;
fn into_iter(self) -> Self::IntoIter {
self.secrets.into_iter()
}
}
impl FromIterator<SecretEntry> for SecretList {
fn from_iter<T: IntoIterator<Item = SecretEntry>>(iter: T) -> Self {
let secrets: Vec<_> = iter.into_iter().collect();
let total_num_secrets = secrets.len() as u16;
Self::new(total_num_secrets, secrets)
}
}
impl SecretList {
/// Creates a new SecretList.
///
/// The content of this list will very liekly not represent the status of the guest in the
/// Ultravisor. Use of [`SecretList::decode`] in any non-test environments is encuraged.
pub fn new(total_num_secrets: u16, secrets: Vec<SecretEntry>) -> Self {
Self {
total_num_secrets: total_num_secrets as usize,
secrets,
}
}
/// Returns an iterator over the slice.
///
/// The iterator yields all secret entries from start to end.
pub fn iter(&self) -> Iter<'_, SecretEntry> {
self.secrets.iter()
}
/// Returns the length of this [`SecretList`].
pub fn len(&self) -> usize {
self.secrets.len()
}
/// Returns `true` if the [`SecretList`] contains no [`SecretEntry`].
pub fn is_empty(&self) -> bool {
self.secrets.is_empty()
}
/// Reports the number of secrets stored in UV.
///
/// This number may be not equal to the provided number of [`SecretEntry`]
pub fn total_num_secrets(&self) -> usize {
self.total_num_secrets
}
/// Encodes the list in the same binary format the UV would do
pub fn encode<T: Write>(&self, w: &mut T) -> Result<()> {
let num_s = to_u16(self.secrets.len()).ok_or(Error::ManySecrets)?;
w.write_u16::<BigEndian>(num_s)?;
w.write_u16::<BigEndian>(
self.total_num_secrets
.try_into()
.map_err(|_| Error::ManySecrets)?,
)?;
w.write_all(&[0u8; 12])?;
for secret in &self.secrets {
w.write_all(secret.as_bytes())?;
}
w.flush().map_err(Error::Io)
}
/// Decodes the list from the binary format of the UV into this internal representation
pub fn decode<R: Read + Seek>(r: &mut R) -> std::io::Result<Self> {
let num_s = r.read_u16::<BigEndian>()?;
let total_num_secrets = r.read_u16::<BigEndian>()? as usize;
let mut v: Vec<SecretEntry> = Vec::with_capacity(num_s as usize);
r.seek(std::io::SeekFrom::Current(12))?; //skip reserved bytes
let mut buf = [0u8; SecretEntry::STRUCT_SIZE];
for _ in 0..num_s {
r.read_exact(&mut buf)?;
//cannot fail. buffer has the same size as the secret entry
let secr = SecretEntry::read_from(buf.as_slice()).unwrap();
v.push(secr);
}
Ok(Self {
total_num_secrets,
secrets: v,
})
}
}
impl TryFrom<ListCmd> for SecretList {
type Error = Error;
fn try_from(mut list: ListCmd) -> Result<SecretList> {
SecretList::decode(&mut Cursor::new(list.data().unwrap())).map_err(Error::InvSecretList)
}
}
impl Display for SecretList {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
writeln!(f, "Total number of secrets: {}", self.total_num_secrets)?;
if !self.secrets.is_empty() {
writeln!(f)?;
}
for s in &self.secrets {
writeln!(f, "{s}")?;
}
Ok(())
}
}
fn ser_u32<S: Serializer>(v: &U32<BigEndian>, ser: S) -> Result<S::Ok, S::Error> {
ser.serialize_u32(v.get())
}
fn ser_u16<S: Serializer>(v: &U16<BigEndian>, ser: S) -> Result<S::Ok, S::Error> {
ser.serialize_u16(v.get())
}
/// Secret types that can appear in a [`SecretList`]
#[non_exhaustive]
#[derive(PartialEq, Eq)]
pub enum ListableSecretType {
/// Association Secret
Association,
/// Invalid secret type, that should never appear in a list
///
/// 0 is reserved
/// 1 is Null secret, with no id and not listable
Invalid(u16),
/// Unknown secret type
Unknown(u16),
}
impl ListableSecretType {
const RESERVED_0: u16 = 0x0000;
const NULL: u16 = 0x0001;
const ASSOCIATION: u16 = 0x0002;
}
impl Display for ListableSecretType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Association => write!(f, "Association"),
Self::Invalid(n) => write!(f, "Invalid({n})"),
Self::Unknown(n) => write!(f, "Unknown({n})"),
}
}
}
impl From<U16<BigEndian>> for ListableSecretType {
fn from(value: U16<BigEndian>) -> Self {
match value.get() {
Self::RESERVED_0 => Self::Invalid(Self::RESERVED_0),
Self::NULL => Self::Invalid(Self::NULL),
Self::ASSOCIATION => ListableSecretType::Association,
n => Self::Unknown(n),
}
}
}
impl From<ListableSecretType> for U16<BigEndian> {
fn from(value: ListableSecretType) -> Self {
match value {
ListableSecretType::Association => ListableSecretType::ASSOCIATION,
ListableSecretType::Invalid(n) | ListableSecretType::Unknown(n) => n,
}
.into()
}
}
#[doc(hidden)]
pub const SECRET_ID_SIZE: usize = 32;
#[doc(hidden)]
pub fn ser_gsid<S>(id: &[u8; SECRET_ID_SIZE], ser: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
let mut s = String::with_capacity(32 * 2 + 2);
s.push_str("0x");
let s = id.iter().fold(s, |acc, e| acc + &format!("{e:02x}"));
ser.serialize_str(&s)
}
/// A secret in a [`SecretList`]
#[repr(C)]
#[derive(Debug, PartialEq, Eq, AsBytes, FromZeroes, FromBytes, Serialize)]
pub struct SecretEntry {
#[serde(serialize_with = "ser_u16")]
index: U16<BigEndian>,
#[serde(serialize_with = "ser_u16")]
stype: U16<BigEndian>,
#[serde(serialize_with = "ser_u32")]
len: U32<BigEndian>,
#[serde(skip)]
res_8: u64,
#[serde(serialize_with = "ser_gsid")]
id: [u8; SECRET_ID_SIZE],
}
assert_size!(SecretEntry, SecretEntry::STRUCT_SIZE);
impl SecretEntry {
const STRUCT_SIZE: usize = 0x30;
/// Create a new entry for a [`SecretList`].
///
/// The content of this entry will very liekly not represent the status of the guest in the
/// Ultravisor. Use of [`SecretList::decode`] in any non-test environments is encuraged.
pub fn new(index: u16, stype: ListableSecretType, id: [u8; 32], secret_len: u32) -> Self {
Self {
index: index.into(),
stype: stype.into(),
len: secret_len.into(),
res_8: 0,
id,
}
}
/// Returns the index of this [`SecretEntry`].
pub fn index(&self) -> u16 {
self.index.get()
}
/// Returns the secret type of this [`SecretEntry`].
pub fn stype(&self) -> ListableSecretType {
self.stype.into()
}
/// Returns a reference to the id of this [`SecretEntry`].
pub fn id(&self) -> &[u8] {
&self.id
}
}
impl Display for SecretEntry {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let stype: ListableSecretType = self.stype.into();
writeln!(f, "{} {}:", self.index, stype)?;
write!(f, " ")?;
for b in self.id {
write!(f, "{b:02x}")?;
}
Ok(())
}
}
#[cfg(test)]
mod test {
use super::*;
use std::io::{BufReader, BufWriter, Cursor};
#[test]
fn dump_secret_entry() {
const EXP: &[u8] = &[
0x00, 0x01, 0x00, 0x02, //idx + type
0x00, 0x00, 0x00, 0x20, //len
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // reserved
// id
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
];
let s = SecretEntry {
index: 1.into(),
stype: 2.into(),
len: 32.into(),
res_8: 0,
id: [0; 32],
};
assert_eq!(s.as_bytes(), EXP);
}
#[test]
fn secret_list_dec() {
let buf = [
0x00u8, 0x01, // num secr stored
0x01, 0x12, // total num secrets
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //reserved
// secret
0x00, 0x01, 0x00, 0x02, //idx + type
0x00, 0x00, 0x00, 0x20, //len
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // reserved
// id
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
];
let exp = SecretList {
total_num_secrets: 0x112,
secrets: vec![SecretEntry {
index: 1.into(),
stype: 2.into(),
len: 32.into(),
res_8: 0,
id: [0; 32],
}],
};
let mut br = BufReader::new(Cursor::new(buf));
let sl = SecretList::decode(&mut br).unwrap();
assert_eq!(sl, exp);
}
#[test]
fn secret_list_enc() {
const EXP: &[u8] = &[
0x00, 0x01, // num secr stored
0x01, 0x12, // total num secrets
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //reserved
// secret
0x00, 0x01, 0x00, 0x02, //idx + type
0x00, 0x00, 0x00, 0x20, //len
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // reserved
// id
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
];
let sl = SecretList {
total_num_secrets: 0x112,
secrets: vec![SecretEntry {
index: 1.into(),
stype: 2.into(),
len: 32.into(),
res_8: 0,
id: [0; 32],
}],
};
let mut buf = [0u8; 0x40];
{
let mut bw = BufWriter::new(&mut buf[..]);
sl.encode(&mut bw).unwrap();
}
println!("list: {sl:?}");
assert_eq!(buf, EXP);
}
}

View File

@@ -5,6 +5,7 @@
#![cfg(test)]
use std::{
ffi::{c_int, c_ulong},
os::unix::prelude::FromRawFd,
sync::{Mutex, MutexGuard},
};
@@ -28,18 +29,18 @@ fn get_lock<T>(m: &'static Mutex<T>) -> MutexGuard<'static, T> {
struct IoctlCtx {
modify: Box<dyn FnMut(&mut ffi::uvio_ioctl_cb) -> i32 + Send + Sync>,
exp_cmd: ::libc::c_ulong,
exp_cmd: c_ulong,
called: bool,
}
impl IoctlCtx {
pub fn exp_cmd(&mut self, cmd: ::libc::c_ulong) -> &mut Self {
pub fn exp_cmd(&mut self, cmd: c_ulong) -> &mut Self {
self.exp_cmd = cmd;
self
}
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,
F: FnMut(&mut ffi::uvio_ioctl_cb) -> c_int + 'static + Send + Sync,
{
self.modify = Box::new(mdfy);
self
@@ -62,11 +63,7 @@ impl IoctlCtx {
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 {
pub unsafe fn ioctl(fd: c_int, cmd: c_ulong, data: *mut ffi::uvio_ioctl_cb) -> c_int {
let mut ctx = get_lock(&IOCTL_MTX);
assert!(!ctx.called, "IOCTL called more than once");
ctx.called = true;

View File

@@ -0,0 +1,194 @@
// SPDX-License-Identifier: MIT
//
// Copyright IBM Corp. 2023
use crate::{
misc::to_u16,
request::{MagicValue, RequestMagic},
uv::{ListCmd, UvCmd},
Error, Result,
};
use byteorder::{BigEndian, ByteOrder};
use std::{
fmt::Display,
io::{Cursor, Read, Seek, Write},
mem::size_of,
};
use utils::{assert_size, static_assert};
use zerocopy::{AsBytes, FromBytes, U16, U32};
/// The magic value used to identify an add-secret request`]
///
/// The magic value is ASCII:
/// ```rust
/// # use pv_core::request::uvsecret::AddSecretMagic;
/// # use pv_core::request::MagicValue;
/// # fn main() {
/// # let magic =
/// b"asrcbM"
/// # ;
/// # assert!(AddSecretMagic::starts_with_magic(magic));
/// # }
///```
///
#[repr(C)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, AsBytes)]
pub struct AddSecretMagic {
magic: [u8; 6], // [0x61, 0x73, 0x72, 0x63, 0x62, 0x4D]
kind: U16<BigEndian>,
}
assert_size!(AddSecretMagic, 8);
impl MagicValue<6> for AddSecretMagic {
// "asrcbM"
const MAGIC: [u8; 6] = [0x61, 0x73, 0x72, 0x63, 0x62, 0x4D];
}
impl AddSecretMagic {
/// Get the magic value.
pub fn get(&self) -> RequestMagic {
let mut res = RequestMagic::default();
debug_assert!(res.len() == size_of::<AddSecretMagic>());
// Panic: does not panic, buf is 8 bytes long
self.write_to(&mut res).unwrap();
res
}
/// Try to convert from a byte slice.
///
/// Retuns [`None`] if the byte slice does not contain a valid magic value variant.
pub fn try_from_bytes(bytes: &[u8]) -> Result<Self> {
if !Self::starts_with_magic(bytes) || bytes.len() < size_of::<AddSecretMagic>() {
return Err(Error::NoAsrcb);
}
// Panic: Will not panic, bytes is at least 8 elements long
let kind = BigEndian::read_u16(&bytes[6..8]);
let kind = UserDataType::try_from(kind)?;
Ok(Self::from(kind))
}
/// Returns the [`UserDataType`] of this [`AddSecretMagic`].
pub fn kind(&self) -> UserDataType {
// Panic: Will never panic. The value is cheched during construcion of the object for
// beeing one of the enum values.
self.kind.get().try_into().unwrap()
}
}
/// Types of (non architectured) user data for an add-secret request
#[repr(u16)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum UserDataType {
/// Marker that the request does not contain any user data
Null = 0x0000,
/// Arbitrary user data (max 512 bytes)
Unsigned = 0x0001,
/// User data message signed with an Ec key, (max 256 byte)
SgnEcSECP521R1 = 0x0002,
/// User data message signature with a Rsa key of 2048 bit size, (max 256 byte)
SgnRsa2048 = 0x0003,
/// User data message signature with a Rsa key of 3072 bit size, (max 128 byte)
SgnRsa3072 = 0x0004,
}
impl UserDataType {
/// Returns the maximum user-data size in bytes.
pub fn max(&self) -> usize {
match self {
UserDataType::Null => 0,
UserDataType::Unsigned => 512,
UserDataType::SgnEcSECP521R1 => 256,
UserDataType::SgnRsa2048 => 256,
UserDataType::SgnRsa3072 => 128,
}
}
}
impl Display for UserDataType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}",
match self {
Self::Null => "None",
Self::Unsigned => "unsigned",
Self::SgnEcSECP521R1 => "ECDSA signed",
Self::SgnRsa2048 => "RSA 2048 signed",
Self::SgnRsa3072 => "RSA 3072 signed",
}
)
}
}
impl TryFrom<u16> for UserDataType {
type Error = Error;
fn try_from(value: u16) -> std::result::Result<Self, Self::Error> {
if value == UserDataType::Null as u16 {
Ok(UserDataType::Null)
} else if value == UserDataType::Unsigned as u16 {
Ok(UserDataType::Unsigned)
} else if value == UserDataType::SgnEcSECP521R1 as u16 {
Ok(UserDataType::SgnEcSECP521R1)
} else if value == UserDataType::SgnRsa2048 as u16 {
Ok(UserDataType::SgnRsa2048)
} else if value == UserDataType::SgnRsa3072 as u16 {
Ok(UserDataType::SgnRsa3072)
} else {
Err(Error::UnsupportedUserData(value))
}
}
}
impl From<UserDataType> for AddSecretMagic {
fn from(kind: UserDataType) -> Self {
Self {
magic: Self::MAGIC,
kind: (kind as u16).into(),
}
}
}
#[cfg(test)]
mod test {
use crate::{
request::{
uvsecret::{AddSecretMagic, UserDataType},
MagicValue,
},
Error,
};
#[test]
fn convert_user_data() {
assert!(matches!(
UserDataType::try_from(5),
Err(Error::UnsupportedUserData(5))
));
}
#[test]
fn magic_get() {
let user_data = AddSecretMagic::from(UserDataType::SgnEcSECP521R1);
assert_eq!(
user_data.get(),
[0x61, 0x73, 0x72, 0x63, 0x62, 0x4D, 0x00, 0x02]
);
}
#[test]
fn magic_try_from() {
let bin = [0x61, 0x73, 0x72, 0x63, 0x62, 0x4D, 0x00, 0x02];
let magic = AddSecretMagic::try_from_bytes(&bin).unwrap();
assert_eq!(
magic,
AddSecretMagic {
magic: AddSecretMagic::MAGIC,
kind: (UserDataType::SgnEcSECP521R1 as u16).into()
}
);
}
}

View File

@@ -0,0 +1,19 @@
[package]
name = "pvapconfig"
description = "A tool to configure the AP resources inside a SE guest based on UV secrets and an AP config file."
authors = ["Harald Freudenberger <freude@linux.ibm.com>"]
version = "0.9.0"
edition.workspace = true
license.workspace = true
[dependencies]
clap = { version ="4.1", features = ["derive", "wrap_help"]}
lazy_static = "1.1"
openssl = { version = "0.10" }
openssl-sys = { version = "0.9" }
pv_core = { path = "../pv_core"}
rand = "0.8"
regex = "1"
serde = { version = "1.0", features = ["derive"] }
serde_yaml = "0.9"
utils = { path = "../utils" }

View File

@@ -0,0 +1,9 @@
<!--
Copyright 2023 IBM Corp.
s390-tools is free software; you can redistribute it and/or modify
it under the terms of the MIT license. See LICENSE for details.
-->
# pvapconfig
## Description
**pvapconfig** is used to automatically set up the AP configuration
within an IBM Secure Execution guest.

View File

@@ -0,0 +1,174 @@
.\" pvapconfig.1
.\"
.\" Copyright 2023 IBM Corp.
.\" s390-tools is free software; you can redistribute it and/or modify
.\" it under the terms of the MIT license. See LICENSE for details.
.\"
.\" use
.\" groff -man -Tutf8 pvapconfig.1
.\" or
.\" nroff -man pvapconfig.1
.\" to process this source
.\"
.TH PVAPCONFIG 1 "DEC 2023" "s390-tools"
.SH NAME
pvapconfig \- automatic configure APQNs within an SE KVM guest.
.SH SYNOPSIS
.TP 9
.B pvapconfig [OPTIONS]
.SH DESCRIPTION
pvapconfig is a tool for automatically configuring the APQNs within an
Secure Execution KVM guest with AP pass-through support. Based on a
given AP configuration it tries to find matching APQNs and binds and
associates them with the given secret(s).
Here is a description of pvapconfig's process:
.TP 3
1. Check AP bus: Support for AP bus needs to be available and the AP
bus needs to support APSB. APSB is only available within an KVM SE
guest with AP pass-through support.
.TP 3
2. Check Ultravisor: UV support needs to be available and the UV needs
to support the
.I list secrets
feature.
.TP 3
3. Read in and validate the AP configuration file. By default if not
overwritten by the --config option the AP configuration is read from
.I /etc/pvapconfig.yaml
and syntactically verified. See section CONFIGFILE for details about
the syntax and semantic of the configuration file.
.TP 3
4. Fetch the list of association secrets from the UV. Actually the
index of the secret and the secret id for each entry is collected. The
secret value is NOT fetched as it is NOT accessible but only usable
within the UV firmware.
.TP 3
5. Gather all APQNs available within this KVM SE guest. Collect
information about each APQN like online states, crypto card serial
numbers and master key verification patterns (MKVP).
.TP 3
6. Go through all AP config entries. For each AP config entry try to
find an APQN which is already configured (bound/associated) to
satisfy this config entry. If such a match is found, the AP config
entry is assumed to be fulfilled and marked as done.
.TP 3
7. All remaining APQNs which do not already satisfy an AP config entry
are now examined for their bind and association state and maybe reset
to unbound state.
.TP 3
8. Go through all AP config entries which are still not
fulfilled. For each such AP config entry try to search for an APQN
which would match to this entry and then prepare this APQN (bind,
maybe associate). If successful, mark the AP config entry as done.
.TP 3
9. Evaluation of the applied AP config entries. Applied means the AP
config entry has been fulfilled either in step 6 or in step 8. With
the strict option given ALL AP config entries need to apply otherwise
an error message is printed and pvapconfig returns with exit failure.
If the strict option is not given, it is enough to satisfy at least one
AP config entry from the configuration and pvapconfig will return
successfully.
.SH OPTIONS
.TP 8
.B -c, --config <configfile>
Use <configfile> as the AP config file for pvapconfig. If pvapconfig
is run without this option the default configuration file
/etc/pvapconfig.yaml is used.
.TP 8
.B -h, --help
Print pvapconfig usage information and exit.
.TP 8
.B -n, --dry-run
Do not bind, unbind or associate APQNs but only process the
configuration and the available APQNs and secrets and simulate the
bind, unbind or associate action on the chosen APQN. Use it together
with the verbose option to see which actions pvapconfig would do if
unleashed.
.TP 8
.B -s, --strict
All AP config entries need to be satisfied to have pvapconfig
terminate with success. Without this option one applied AP config
entry is enough to meet the expectations.
.TP 8
.B -v, --verbose
Print out informational messages about what pvapconfig is actually
doing.
.TP 8
.B -V, --version
Print version information and exit.
.SH CONFIGFILE
The pvapconfig yaml configuration file consists of a list of AP config
entries. Each entry may hold this information:
.TP 2
- mode: AP queue mode information, required, either "EP11" or "Accel".
.TP 2
- mkvp: AP queue Master Key Verification Pattern (MKVP), required for
EP11, hex string optional prepented with 0x. The MKVP hex string value
may hold either 16 bytes (32 hex characters) or 32 bytes (64 hex
characters) but only the leftmost 16 bytes hold MKVP information and
thus the rest is ignored.
.TP 2
- serialnr: Crypto Card Serial Number, string, optional for EP11,
ignored for Accel. As this is a real ASCII string uppercase and
lowercase character(s) count different.
.TP 2
- mingen: Card Minimal Generation, string "CEX4", "CEX5", "CEX6",
"CEX7" or "CEX8" for Accelerator, string "CEC8" for EP11, optional. If
given specifies the minimal accepted Crypto card generation.
.TP 2
- secretid: Secret id, hex string with optional 0x prepented, required
for EP11, ignored for Accel. Details see the following text.
.TP 2
- name: ASCII string, optional, but see details below.
.TP 2
- description: Description of this AP config entry, string, ignored,
just for convenience for the reader or editor of the configuration.
.PP
The secret id uniquely identifies an association secret. However, it
is a clumsy hex string of 64 characters which represent the readable
sha256 value over the secret's name. So pvapconfig can use the name
instead and calculate the secret id from the name. So the rule is:
.TP 2
- If name and secretid is given, the secretid needs to match to the
sha256 hash over the given name for this AP config entry.
.TP 2
- If only name is given then the secretid is calculated with a sha256
hash over the given name.
.TP 2
- If only the secretid is given, there is nothing more to do but
verify that the value is a hex string with 64 characters.
.SH LOCKING
Pvapconfig needs to have a consistent view of the AP resources
during lifetime. There must not run multiple instances of pvapconfig
or any manipulations of the AP resources in parallel. To prevent the
execution of multiple pvapconfig instances the lock file
/run/lock/pvapconfig.lock is established. A second instance of
pvapconfig will detect this lock file and terminated with an error
message. If for any reason this file still exists as a leftover from a
previous pvapconfig crash for example, it needs to get removed by
hand. The lock file contains the process id of the pvapconfig process
which created this file.
.SH RETURN VALUE
.TP 8
.B 0 - Successful termination.
At least one AP config entry has been applied or at least one APQN has
been found in a state matching to one AP config entry. If strict
option is given, ALL AP config entries have been applied. An AP config
entry is applied either by configuring the APQN accordingly or an APQN
has been found which already fulfills the constrains.
.RE
.TP 8
.B 1 - Failure.
Either some kind of failure happened during processing the
configuration or the configuration could not get applied
successful. In all cases pvapconfig prints out a message to standard
error with details about the failure. Also pvapconfig does NOT reset
the APQNs to the state found at the startup when failing to apply the
configuration.
.SH NOTES
For more information and details see the IBM documentation about
Confidential Computing "Introducing IBM Secure Execution for Linux"
available at https://www.ibm.com/docs/.
.SH SEE ALSO
\fBpvsecret\fR(1), \fBlszcrypt\fR(8), \fBchzcrypt\fR(8)

817
rust/pvapconfig/src/ap.rs Normal file
View File

@@ -0,0 +1,817 @@
// SPDX-License-Identifier: MIT
//
// Copyright IBM Corp. 2023
//
//! AP support functions for pvapconfig
//
use crate::helper::*;
use regex::Regex;
use std::fmt;
use std::path::Path;
use std::slice::Iter;
use std::thread;
use std::time;
const PATH_SYS_BUS_AP: &str = "/sys/bus/ap";
const PATH_SYS_BUS_AP_FEATURES: &str = "/sys/bus/ap/features";
const PATH_SYS_BUS_AP_BINDINGS: &str = "/sys/bus/ap/bindings";
const PATH_SYS_DEVICES_AP: &str = "/sys/devices/ap";
const RE_CARD_DIR: &str = r"^card([[:xdigit:]]{2})$";
const RE_QUEUE_DIR: &str = r"^([[:xdigit:]]{2})\.([[:xdigit:]]{4})$";
const RE_CARD_TYPE: &str = r"^CEX([3-8])([ACP])$";
const RE_EP11_MKVP: &str = r"WK\s+CUR:\s+(\S+)\s+(\S+)";
const RE_CCA_AES_MKVP: &str = r"AES\s+CUR:\s+(\S+)\s+(\S+)";
const RE_CCA_APKA_MKVP: &str = r"APKA\s+CUR:\s+(\S+)\s+(\S+)";
const SYS_BUS_AP_BINDINGS_POLL_MS: u64 = 500;
const SYS_BUS_AP_BIND_POLL_MS: u64 = 500;
const SYS_BUS_AP_BIND_TIMEOUT_MS: u64 = 10000;
const SYS_BUS_AP_ASSOC_POLL_MS: u64 = 500;
const SYS_BUS_AP_ASSOC_TIMEOUT_MS: u64 = 10000;
/// Check if AP bus support is available.
/// Returns Result with Ok(()) or Err(failurestring).
pub fn check_ap_bus_support() -> Result<(), String> {
if !Path::new(PATH_SYS_BUS_AP).is_dir() {
return Err(format!(
"AP bus support missing (path {PATH_SYS_BUS_AP} is invalid)."
));
}
Ok(())
}
/// Check if AP bus supports APSB.
///
/// When APSB support is available returns Result
/// with Ok(()) or otherwise Err(failurestring).
pub fn ap_bus_has_apsb_support() -> Result<(), String> {
if !Path::new(PATH_SYS_BUS_AP_FEATURES).is_file() {
return Err(format!(
"AP bus features support missing (file {PATH_SYS_BUS_AP_FEATURES} does not exist)."
));
}
let features = sysfs_read_string(PATH_SYS_BUS_AP_FEATURES).map_err(|err| {
format!("Failure reading AP bus features from {PATH_SYS_BUS_AP_FEATURES} ({err:?}).")
})?;
match features.find("APSB") {
Some(_) => Ok(()),
None => Err("Missing AP bus feature APSB (SE AP pass-through not enabled ?).".to_string()),
}
}
/// Wait for AP bus set up all it's devices.
///
/// This function loops until the AP bus reports that
/// - all AP queue devices have been constructed
/// - and all AP device have been bound to a device driver.
/// This may take some time and even loop forever if there
/// is something wrong with the kernel modules setup.
/// Returns true when AP bus bindings are complete,
/// otherwise false and a message is printed.
/// When AP bus binding complete is not immediately reached
/// and this function needs to loop, about every 5 seconds
/// a message is printed "Waiting for ...".
pub fn wait_for_ap_bus_bindings_complete() -> bool {
let mut counter = 0;
loop {
match sysfs_read_string(PATH_SYS_BUS_AP_BINDINGS) {
Ok(s) => {
if s.contains("complete") {
return true;
}
}
Err(err) => {
eprintln!(
"Failure reading AP bus bindings from {} ({:?}).",
PATH_SYS_BUS_AP_BINDINGS, err
);
return false;
}
}
thread::sleep(time::Duration::from_millis(SYS_BUS_AP_BINDINGS_POLL_MS));
counter += 1;
if counter % 10 == 0 {
println!("Waiting for AP bus bindings complete.");
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ApqnMode {
Accel,
Ep11,
Cca,
}
#[derive(Debug, Clone)]
pub struct ApqnInfoAccel {
// empty
}
#[derive(Debug, Clone)]
pub struct ApqnInfoEp11 {
pub serialnr: String,
pub mkvp: String, // may be an empty string if no WK set
}
#[derive(Debug, Clone)]
pub struct ApqnInfoCca {
pub serialnr: String,
pub mkvp_aes: String, // may be an empty string if no MK set
pub mkvp_apka: String, // may be an empty string if no MK set
}
#[derive(Debug, Clone)]
pub enum ApqnInfo {
Accel(ApqnInfoAccel),
Ep11(ApqnInfoEp11),
Cca(ApqnInfoCca),
}
impl ApqnInfo {
fn accel_info(_carddir: &str, _queuedir: &str) -> Result<ApqnInfo, String> {
Ok(ApqnInfo::Accel(ApqnInfoAccel {}))
}
fn cca_info(carddir: &str, queuedir: &str) -> Result<ApqnInfo, String> {
let serialnr = match sysfs_read_string(&format!("{carddir}/serialnr")) {
Ok(r) => r,
Err(err) => {
return Err(format!(
"Failure reading serialnr from {carddir}/serialnr: {:?}.",
err
))
}
};
let mkvps = match sysfs_read_string(&format!("{carddir}/{queuedir}/mkvps")) {
Ok(r) => r,
Err(err) => {
return Err(format!(
"Failure reading mkvps from {carddir}/{queuedir}/mkvps: {:?}.",
err
))
}
};
let mut aes_mkvp = String::new();
let re_cca_aes_mkvp = Regex::new(RE_CCA_AES_MKVP).unwrap();
if !re_cca_aes_mkvp.is_match(&mkvps) {
return Err(format!(
"APQN {} failure parsing mkvps string '{}'.",
queuedir, mkvps
));
} else {
let caps = re_cca_aes_mkvp.captures(&mkvps).unwrap();
let valid = caps.get(1).unwrap().as_str().to_lowercase();
if valid != "valid" {
eprintln!(
"Warning: APQN {} has no valid AES master key set.",
queuedir
);
} else {
aes_mkvp = caps.get(2).unwrap().as_str().to_lowercase();
if aes_mkvp.starts_with("0x") {
aes_mkvp = String::from(&aes_mkvp[2..]);
}
}
}
let mut apka_mkvp = String::new();
let re_cca_apka_mkvp = Regex::new(RE_CCA_APKA_MKVP).unwrap();
if !re_cca_apka_mkvp.is_match(&mkvps) {
return Err(format!(
"APQN {} failure parsing mkvps string '{}'.",
queuedir, mkvps
));
} else {
let caps = re_cca_apka_mkvp.captures(&mkvps).unwrap();
let valid = caps.get(1).unwrap().as_str().to_lowercase();
if valid != "valid" {
eprintln!(
"Warning: APQN {} has no valid APKA master key set.",
queuedir
);
} else {
apka_mkvp = caps.get(2).unwrap().as_str().to_lowercase();
if apka_mkvp.starts_with("0x") {
apka_mkvp = String::from(&apka_mkvp[2..]);
}
}
}
Ok(ApqnInfo::Cca(ApqnInfoCca {
serialnr,
mkvp_aes: aes_mkvp,
mkvp_apka: apka_mkvp,
}))
}
fn ep11_info(carddir: &str, queuedir: &str) -> Result<ApqnInfo, String> {
let serialnr = match sysfs_read_string(&format!("{carddir}/serialnr")) {
Ok(r) => r,
Err(err) => {
return Err(format!(
"Failure reading serialnr from {carddir}/serialnr: {:?}.",
err
))
}
};
let mkvps = match sysfs_read_string(&format!("{carddir}/{queuedir}/mkvps")) {
Ok(r) => r,
Err(err) => {
return Err(format!(
"Failure reading mkvps from {carddir}/{queuedir}/mkvps: {:?}.",
err
))
}
};
let mut mkvp = String::new();
let re_ep11_mkvp = Regex::new(RE_EP11_MKVP).unwrap();
if !re_ep11_mkvp.is_match(&mkvps) {
return Err(format!(
"APQN {} failure parsing mkvps string '{}'.",
queuedir, mkvps
));
} else {
let caps = re_ep11_mkvp.captures(&mkvps).unwrap();
let valid = caps.get(1).unwrap().as_str().to_lowercase();
if valid != "valid" {
eprintln!("Warning: APQN {} has no valid wrapping key set.", queuedir);
} else {
mkvp = caps.get(2).unwrap().as_str().to_lowercase();
if mkvp.starts_with("0x") {
mkvp = String::from(&mkvp[2..]);
}
if mkvp.len() > 32 {
mkvp = String::from(&mkvp[..32])
}
}
}
Ok(ApqnInfo::Ep11(ApqnInfoEp11 { serialnr, mkvp }))
}
fn info(mode: &ApqnMode, carddir: &str, queuedir: &str) -> Result<ApqnInfo, String> {
match mode {
ApqnMode::Accel => ApqnInfo::accel_info(carddir, queuedir),
ApqnMode::Cca => ApqnInfo::cca_info(carddir, queuedir),
ApqnMode::Ep11 => ApqnInfo::ep11_info(carddir, queuedir),
}
}
}
#[derive(Debug, Clone)]
pub struct Apqn {
pub name: String,
pub card: u32,
pub domain: u32,
pub gen: u32,
pub mode: ApqnMode,
pub info: Option<ApqnInfo>,
}
impl fmt::Display for Apqn {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "({},{})", self.card, self.domain)
}
}
impl Apqn {
pub fn bind_state(&self) -> Result<BindState, String> {
get_apqn_bind_state(self.card, self.domain)
}
pub fn set_bind_state(&self, state: BindState) -> Result<(), String> {
set_apqn_bind_state(self.card, self.domain, state)
}
pub fn associate_state(&self) -> Result<AssocState, String> {
get_apqn_associate_state(self.card, self.domain)
}
pub fn set_associate_state(&self, state: AssocState) -> Result<(), String> {
set_apqn_associate_state(self.card, self.domain, state)
}
}
/// Wrapper object around Vector of Apqns
pub struct ApqnList(Vec<Apqn>);
impl ApqnList {
#[cfg(test)] // only used in test code
pub fn from_apqn_vec(apqns: Vec<Apqn>) -> ApqnList {
ApqnList(apqns)
}
#[cfg(test)] // only used in test code
pub fn to_apqn_vec(&self) -> Vec<Apqn> {
self.0.clone()
}
pub fn iter(&self) -> Iter<'_, Apqn> {
self.0.iter()
}
pub fn len(&self) -> usize {
self.0.len()
}
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
/// Scan AP bus devices in sysfs and construct the Apqnlist.
///
/// The list is a vector of struct Apqn for each APQN found in sysfs
/// which is online and the card type matches to the regular expression
/// RE_CARD_TYPE.
/// On success a vector of struct Apqn is returned. This list may be
/// empty if there are no APQNs available or do not match to the conditions.
/// On failure None is returned.
/// Fatal errors which should never happened like unable to compile a
/// static regular expression will result in calling panic.
/// # Panics
/// Panics if the compilation of a static regular expression fails.
pub fn gather_apqns() -> Option<ApqnList> {
let mut apqns: Vec<Apqn> = Vec::new();
let re_card_type = Regex::new(RE_CARD_TYPE).unwrap();
let re_queue_dir = Regex::new(RE_QUEUE_DIR).unwrap();
let card_dirs =
match sysfs_get_list_of_subdirs_matching_regex(PATH_SYS_DEVICES_AP, RE_CARD_DIR) {
Ok(r) => r,
Err(err) => {
eprintln!(
"Failure reading AP devices {} ({:?}).",
PATH_SYS_DEVICES_AP, err
);
return None;
}
};
for dir in card_dirs {
let path = format!("{PATH_SYS_DEVICES_AP}/{dir}");
let card_type = match sysfs_read_string(&format!("{path}/type")) {
Ok(r) => r,
Err(err) => {
eprintln!("Failure reading card type from {} ({:?}).", path, err);
return None;
}
};
if !re_card_type.is_match(&card_type) {
eprintln!("Failure parsing card type string '{}'.", card_type);
return None;
}
let caps = re_card_type.captures(&card_type).unwrap();
let gen = caps.get(1).unwrap().as_str().parse::<u32>().unwrap();
let mode = match caps.get(2).unwrap().as_str().parse::<char>().unwrap() {
'A' => ApqnMode::Accel,
'C' => ApqnMode::Cca,
'P' => ApqnMode::Ep11,
_ => panic!("Code inconsistence between regex RE_CARD_TYPE and evaluation code."),
};
if pv_core::misc::pv_guest_bit_set() {
// the UV blocks requests to CCA cards within SE guest with
// AP pass-through support. However, filter out CCA cards as these
// cards cause hangs during information gathering.
if mode == ApqnMode::Cca {
continue;
}
}
let queue_dirs = match sysfs_get_list_of_subdirs_matching_regex(&path, RE_QUEUE_DIR) {
Ok(r) => r,
Err(err) => {
eprintln!(
"Failure reading AP queue directories in {} ({:?}).",
path, err
);
return None;
}
};
for queue_dir in queue_dirs {
let _online = match sysfs_read_i32(&format!("{path}/{queue_dir}/online")) {
Ok(1) => true,
_ => continue,
};
let caps = re_queue_dir.captures(&queue_dir).unwrap();
let cardstr = caps.get(1).unwrap().as_str();
let card = u32::from_str_radix(cardstr, 16).unwrap();
let domstr = caps.get(2).unwrap().as_str();
let dom = u32::from_str_radix(domstr, 16).unwrap();
// For the mpvk and serialnr to fetch from the APQN within a SE
// guest the APQN needs to be bound to the guest. So if the APQN
// is not bound, temporarily bind it here until the info has
// been retrieved.
let mut tempbound = false;
if pv_core::misc::pv_guest_bit_set() {
let cbs = match get_apqn_bind_state(card, dom) {
Ok(bs) => bs,
Err(err) => {
eprintln!(
"Error: Failure reading APQN ({},{}) bind state: {}",
card, dom, err
);
BindState::NotSupported
}
};
if cbs == BindState::Unbound {
let r = set_apqn_bind_state(card, dom, BindState::Bound);
if r.is_err() {
eprintln!(
"Warning: Failure to temp. bind APQN ({},{}): {}",
card,
dom,
r.unwrap_err()
);
continue;
} else {
tempbound = true;
}
};
};
let info = match ApqnInfo::info(&mode, &path, &queue_dir) {
Err(err) => {
// print the error but continue with info set to None
eprintln!(
"Warning: Failure to gather info for APQN ({},{}): {}",
card, dom, err
);
None
}
Ok(i) => Some(i),
};
if tempbound {
let r = set_apqn_bind_state(card, dom, BindState::Unbound);
if r.is_err() {
eprintln!(
"Warning: Failure to unbind temp. bound APQN ({},{}): {}",
card,
dom,
r.unwrap_err()
);
}
};
apqns.push(Apqn {
name: queue_dir.clone(),
card,
domain: dom,
gen,
mode: mode.clone(),
info,
});
}
}
Some(ApqnList(apqns))
}
/// Sort this Apqnlist by card generation:
/// newest generation first, older generations last.
pub fn sort_by_gen(&mut self) {
self.0.sort_unstable_by(|a, b| b.gen.cmp(&a.gen));
}
/// Check MK restriction
///
/// Within one card there must not exist 2 APQNs with same
/// MK setup. This rule only applies to EP11 cards.
/// Returns true if this check passed,
/// otherwise false and a message is printed.
pub fn check_mk_restriction(&self) -> bool {
for a1 in self.0.iter() {
for a2 in self.0.iter() {
if a1.card == a2.card
&& a1.domain < a2.domain
&& a1.mode == ApqnMode::Ep11
&& a1.info.is_some()
&& a2.info.is_some()
{
let i1 = match a1.info.as_ref().unwrap() {
ApqnInfo::Ep11(i) => i,
_ => continue,
};
let i2 = match a2.info.as_ref().unwrap() {
ApqnInfo::Ep11(i) => i,
_ => continue,
};
if i1.mkvp.is_empty() || i2.mkvp.is_empty() {
continue;
}
if i1.mkvp == i2.mkvp {
eprintln!("APQN {} and APQN {} have same MPVK", a1, a2);
return false;
}
}
}
}
true
}
}
#[derive(PartialEq, Eq)]
pub enum BindState {
Bound,
Unbound,
NotSupported,
}
/// Query bind state for this APQN.
///
/// Returns a BindState enum as defined above or on failure
/// an error string. Does NOT print any error messages.
pub fn get_apqn_bind_state(card: u32, dom: u32) -> Result<BindState, String> {
let path = format!(
"{}/card{:02x}/{:02x}.{:04x}/se_bind",
PATH_SYS_DEVICES_AP, card, card, dom
);
match sysfs_read_string(&path) {
Err(err) => Err(format!(
"Failure reading se_bind attribute for APQN({},{}): {:?}.",
card, dom, err
)),
Ok(str) => match str.as_str() {
"bound" => Ok(BindState::Bound),
"unbound" => Ok(BindState::Unbound),
"-" => Ok(BindState::NotSupported),
_ => Err(format!("Unknown bind state '{str}'.")),
},
}
}
/// Bind or unbind an APQN.
///
/// The action is determined by the BindState given in.
/// But of course only Bound and Unbound is supported - otherwise
/// this function panics!
/// The function actively loops over the bind state until
/// the requested bind state is reached or a timeout has
/// occurred (SYS_BUS_AP_BIND_TIMEOUT_MS).
/// On success () is returned, on failure an error string
/// is returned. Does NOT print any error messages.
/// # Panics
/// Panics if a desired bind state other than Bound or Unbound is given.
pub fn set_apqn_bind_state(card: u32, dom: u32, state: BindState) -> Result<(), String> {
let path = format!(
"{}/card{:02x}/{:02x}.{:04x}/se_bind",
PATH_SYS_DEVICES_AP, card, card, dom
);
let r = match state {
BindState::Bound => sysfs_write_i32(&path, 1),
BindState::Unbound => sysfs_write_i32(&path, 0),
_ => panic!("set_apqn_bind_state called with invalid BindState."),
};
if r.is_err() {
return Err(format!(
"Failure writing se_bind attribute for APQN({},{}): {:?}.",
card,
dom,
r.unwrap_err()
));
}
let mut ms: u64 = 0;
loop {
thread::sleep(time::Duration::from_millis(SYS_BUS_AP_BIND_POLL_MS));
ms += SYS_BUS_AP_BIND_POLL_MS;
if ms >= SYS_BUS_AP_BIND_TIMEOUT_MS {
break Err(format!(
"Timeout setting APQN({},{}) bind state.",
card, dom
));
}
let newstate = match get_apqn_bind_state(card, dom) {
Err(err) => return Err(err),
Ok(s) => s,
};
if newstate == state {
return Ok(());
}
}
}
#[derive(PartialEq, Eq)]
pub enum AssocState {
Associated(u16),
AssociationPending,
Unassociated,
NotSupported,
}
/// Query association state for this APQN.
///
/// Returns an AssocState enum as defined above or on failure
/// an error string. Does NOT print any error messages.
pub fn get_apqn_associate_state(card: u32, dom: u32) -> Result<AssocState, String> {
let path = format!(
"{}/card{:02x}/{:02x}.{:04x}/se_associate",
PATH_SYS_DEVICES_AP, card, card, dom
);
match sysfs_read_string(&path) {
Err(err) => Err(format!(
"Failure reading se_associate attribute for APQN({},{}: {:?}",
card, dom, err
)),
Ok(str) => {
if let Some(prefix) = str.strip_prefix("associated ") {
let value = &prefix.parse::<u16>();
match value {
Ok(v) => Ok(AssocState::Associated(*v)),
Err(_) => Err(format!("Invalid association index in '{str}'.")),
}
} else {
match str.as_str() {
"association pending" => Ok(AssocState::AssociationPending),
"unassociated" => Ok(AssocState::Unassociated),
"-" => Ok(AssocState::NotSupported),
_ => Err(format!("Unknown association state '{str}'.")),
}
}
}
}
}
fn set_apqn_associate_state_associate(card: u32, dom: u32, idx: u16) -> Result<(), String> {
let path = format!(
"{}/card{:02x}/{:02x}.{:04x}/se_associate",
PATH_SYS_DEVICES_AP, card, card, dom
);
let r = sysfs_write_i32(&path, idx as i32);
if r.is_err() {
return Err(format!(
"Failure writing se_associate attribute for APQN({},{}): {:?}.",
card,
dom,
r.unwrap_err()
));
}
let mut ms: u64 = 0;
loop {
thread::sleep(time::Duration::from_millis(SYS_BUS_AP_ASSOC_POLL_MS));
ms += SYS_BUS_AP_ASSOC_POLL_MS;
if ms >= SYS_BUS_AP_ASSOC_TIMEOUT_MS {
break Err(format!(
"Timeout setting APQN({},{}) association idx {} state.",
card, dom, idx
));
}
let newstate = match get_apqn_associate_state(card, dom) {
Err(err) => return Err(err),
Ok(s) => s,
};
if let AssocState::Associated(i) = newstate {
if idx == i {
return Ok(());
} else {
return Err(format!(
"Failure: APQN({},{}) is associated with {} but it should be {}.",
card, dom, i, idx
));
}
}
}
}
fn set_apqn_associate_state_unbind(card: u32, dom: u32) -> Result<(), String> {
let bindpath = format!(
"{}/card{:02x}/{:02x}.{:04x}/se_bind",
PATH_SYS_DEVICES_AP, card, card, dom
);
let r = sysfs_write_i32(&bindpath, 0);
if r.is_err() {
return Err(format!(
"Failure writing se_bind attribute for APQN({},{}): {:?}.",
card,
dom,
r.unwrap_err()
));
}
let mut ms: u64 = 0;
loop {
thread::sleep(time::Duration::from_millis(SYS_BUS_AP_ASSOC_POLL_MS));
ms += SYS_BUS_AP_ASSOC_POLL_MS;
if ms >= SYS_BUS_AP_ASSOC_TIMEOUT_MS {
break Err(format!(
"Timeout setting APQN({},{}) association unbind state.",
card, dom
));
}
let newstate = match get_apqn_associate_state(card, dom) {
Err(err) => return Err(err),
Ok(s) => s,
};
if newstate == AssocState::Unassociated {
return Ok(());
}
}
}
/// Associate or Unassociate an APQN.
///
/// The action is determined by the AssocState given in.
/// But of course only Associated and Unassociated is supported
/// otherwise this function panics!
/// The function actively loops over the association state until
/// the requested state is reached or a timeout has
/// occurred (SYS_BUS_AP_ASSOC_TIMEOUT_MS).
/// The unassociate is in fact a unbind. So the code triggers
/// an unbind and then loops over the sysfs se_associate until
/// "unassociated" is reached.
/// On success () is returned, on failure an error string
/// is returned. Does NOT print any error messages.
/// # Panics
/// Panics if a desired bind state other than Associated or
/// Unassociated is given.
pub fn set_apqn_associate_state(card: u32, dom: u32, state: AssocState) -> Result<(), String> {
match state {
AssocState::Associated(idx) => set_apqn_associate_state_associate(card, dom, idx),
AssocState::Unassociated => set_apqn_associate_state_unbind(card, dom),
_ => panic!("set_apqn_associate_state called with invalid AssocState."),
}
}
#[cfg(test)]
mod tests {
use super::*;
// These tests assume, there is an AP bus available
// Also for each APQN which is online, it is assumed
// to have a valid master key set up (for Ep11 and CCA).
#[test]
fn test_check_ap_bus_support() {
if Path::new(PATH_SYS_BUS_AP).is_dir() {
assert!(check_ap_bus_support().is_ok());
} else {
assert!(check_ap_bus_support().is_err());
}
}
#[test]
fn test_check_ap_bus_apsb_support() {
if Path::new(PATH_SYS_BUS_AP).is_dir() {
// if we are inside a secure execution guest the
// apsb check should succeed. Outside an SE guest
// the check should fail.
if pv_core::misc::pv_guest_bit_set() {
assert!(ap_bus_has_apsb_support().is_ok());
} else {
assert!(ap_bus_has_apsb_support().is_err());
}
} else {
assert!(ap_bus_has_apsb_support().is_err());
}
}
#[test]
fn test_wait_for_ap_bus_bindings_complete() {
let r = wait_for_ap_bus_bindings_complete();
if Path::new(PATH_SYS_BUS_AP).is_dir() {
assert!(r);
} else {
assert!(!r);
}
}
#[test]
fn test_gather_apqns() {
let r = ApqnList::gather_apqns();
if Path::new(PATH_SYS_BUS_AP).is_dir() {
assert!(r.is_some());
// fail if no entries found
let l = r.unwrap();
let v = l.to_apqn_vec();
for a in v {
match a.mode {
ApqnMode::Accel => {
// fail if no ApqnInfo is attached
assert!(a.info.is_some());
}
ApqnMode::Ep11 => {
// fail if no ApqnInfo is attached
assert!(a.info.is_some());
let info = a.info.unwrap();
let i = match &info {
ApqnInfo::Ep11(i) => i,
_ => panic!("ApqnInfo attached onto Ep11 APQN is NOT ApqnInfoEp11 ?!?"),
};
// fail if no serialnr
assert!(!i.serialnr.is_empty());
// mkvp is either empty (no WK set) or has exact 32 characters
assert!(i.mkvp.is_empty() || i.mkvp.len() == 32);
}
ApqnMode::Cca => {
// fail if no ApqnInfo is attached
assert!(a.info.is_some());
let info = a.info.unwrap();
let i = match &info {
ApqnInfo::Cca(i) => i,
_ => panic!("ApqnInfo attached onto Cca APQN is NOT ApqnInfoCca ?!?"),
};
// fail if no serialnr
assert!(!i.serialnr.is_empty());
// aes mkvp is either empty (no MK set) or exact 16 characters
assert!(i.mkvp_aes.is_empty() || i.mkvp_aes.len() == 16);
// apka mkvp is either empty (no MK set) or exact 16 characters
assert!(i.mkvp_apka.is_empty() || i.mkvp_apka.len() == 16);
}
}
}
} else {
assert!(r.is_none());
}
}
}

View File

@@ -0,0 +1,63 @@
// SPDX-License-Identifier: MIT
//
// Copyright IBM Corp. 2023
//
//! Command line interface for pvapconfig
//
use clap::Parser;
use lazy_static::lazy_static;
/// The default pvapconfig config file
pub const PATH_DEFAULT_CONFIG_FILE: &str = "/etc/pvapconfig.yaml";
#[derive(Parser, Clone)]
pub struct Cli {
/// Provide a custom config file (overwrites default /etc/pvapconfig.yaml).
#[arg(short, long, value_name = "FILE")]
pub config: Option<String>,
/// Dry run: display the actions but don't actually perform them on the APQNs.
#[arg(short = 'n', long = "dry-run")]
pub dryrun: bool,
/// Enforce strict match: All config entries need to be fulfilled.
///
/// By default it is enough to successfully apply at least one config entry.
/// With the strict flag enabled, all config entries within a config file
/// need to be applied successful.
#[arg(long = "strict")]
pub strict: bool,
/// Provide more detailed output.
#[arg(short, long)]
pub verbose: bool,
/// Print version information and exit.
#[arg(short = 'V', long)]
pub version: bool,
}
lazy_static! {
pub static ref ARGS: Cli = Cli::parse();
}
impl Cli {
/// verbose returns true if the verbose command line option
/// was given, otherwise false is returned.
pub fn verbose(&self) -> bool {
self.verbose
}
/// dryrun returns true if the dry-run command line option
/// was given, otherwise false is returned.
pub fn dryrun(&self) -> bool {
self.dryrun
}
/// strict returns true if the strict flag was given, otherwise
/// false is returned.
pub fn strict(&self) -> bool {
self.strict
}
}

View File

@@ -0,0 +1,391 @@
// SPDX-License-Identifier: MIT
//
// Copyright IBM Corp. 2023
//
//! Functions around handling the pvapconfig configuration file
//
use openssl::sha::sha256;
use regex::Regex;
use serde::{Deserialize, Serialize};
use serde_yaml::{self};
use std::fs::File;
use std::slice::Iter;
pub const STR_MODE_EP11: &str = "ep11";
pub const STR_MODE_ACCEL: &str = "accel";
const RE_EP11_MKVP_32: &str = r"^(0x)?([[:xdigit:]]{32})$";
const RE_EP11_MKVP_64: &str = r"^(0x)?([[:xdigit:]]{64})$";
const RE_SERIALNR: &str = r"^(\S{16})$";
const RE_EP11_GEN: &str = r"^cex(8)$";
const RE_ACCEL_GEN: &str = r"^cex([4-8])$";
const RE_SECRETID: &str = r"^(0x)?([[:xdigit:]]{64})$";
#[derive(Debug, Serialize, Deserialize, Default, Clone)]
#[serde(default, deny_unknown_fields)]
pub struct ApConfigEntry {
pub name: String, // name and description are unmodified from the config file
pub description: String, // accel after validation ep11 after validation
pub mode: String, // "accel" "ep11"
pub mkvp: String, // empty 32 hex lowercase characters
pub serialnr: String, // empty empty or 16 non-whitespace characters
pub mingen: String, // empty or "cex4"..."cex8" empty or "cex8"
pub secretid: String, // empty 64 hex lowercase characters
}
impl ApConfigEntry {
fn validate_secretid(&mut self) -> Result<(), String> {
// either secret id or name may be given
if self.secretid.is_empty() && self.name.is_empty() {
return Err("Neither secretid nor name given.".to_string());
}
// if name is given, calculate sha256 digest for this name
// test for the hash calculated here can be done with openssl:
// echo -n "Hello" >in.bin; openssl dgst -sha256 -binary -out out.bin in.bin; hexdump -C out.bin
if self.name.is_empty() {
return Ok(());
}
let hash = sha256(self.name.as_bytes());
let hashstr = crate::helper::u8_to_hexstring(&hash);
// if there is a secretid given, this must match to the hash
if !self.secretid.is_empty() {
if self.secretid != hashstr {
return Err("Mismatch between sha256(name) and secretid.".to_string());
}
} else {
self.secretid = hashstr;
}
Ok(())
}
/// # Panics
/// Panics if the compilation of a static regular expression fails.
fn validate_ep11_entry(&mut self) -> Result<(), String> {
// mkvp is required
let mut mkvp = self.mkvp.trim().to_lowercase();
if mkvp.is_empty() {
return Err("Mkvp value missing.".to_string());
}
// either 64 hex or 32 hex
if Regex::new(RE_EP11_MKVP_64).unwrap().is_match(&mkvp) {
// need to cut away the last 32 hex characters
mkvp = String::from(&mkvp[..mkvp.len() - 32])
} else if Regex::new(RE_EP11_MKVP_32).unwrap().is_match(&mkvp) {
// nothing to do here
} else {
return Err(format!("Mkvp value '{}' is not valid.", &self.mkvp));
}
self.mkvp = match mkvp.strip_prefix("0x") {
Some(rest) => String::from(rest),
None => mkvp,
};
// serialnr is optional
let serialnr = self.serialnr.trim().to_string();
if !serialnr.is_empty() && !Regex::new(RE_SERIALNR).unwrap().is_match(&serialnr) {
return Err(format!("Serialnr value '{}' is not valid.", &self.serialnr));
}
self.serialnr = serialnr;
// mingen is optional, but if given only CEX8 is valid
let mingen = self.mingen.trim().to_lowercase();
if !mingen.is_empty() && !Regex::new(RE_EP11_GEN).unwrap().is_match(&mingen) {
return Err(format!("Mingen value '{}' is not valid.", &self.mingen));
}
self.mingen = mingen;
// secretid or name is required
let secretid = self.secretid.trim().to_lowercase();
if !secretid.is_empty() && !Regex::new(RE_SECRETID).unwrap().is_match(&secretid) {
return Err(format!("Secretid value '{}' is not valid.", &self.secretid));
}
self.secretid = match secretid.strip_prefix("0x") {
Some(rest) => String::from(rest),
None => secretid,
};
// name is optional, ignored here
// description is optional, ignored here
// but the secretid needs some more validation
self.validate_secretid()
}
/// # Panics
/// Panics if the compilation of a static regular expression fails.
fn validate_accel_entry(&mut self) -> Result<(), String> {
// mkvp is ignored
self.mkvp.clear();
// serialnr is ignored
self.serialnr.clear();
// mingen is optional, but if given must match to CEX4..CEX8
let mingen = self.mingen.trim().to_lowercase();
if !mingen.is_empty() && !Regex::new(RE_ACCEL_GEN).unwrap().is_match(&mingen) {
return Err(format!("Mingen value '{}' is not valid.", &self.mingen));
}
self.mingen = mingen;
// secretid is ignored
self.secretid.clear();
// name is optional, ignored here
// description is optional, ignored here
Ok(())
}
fn validate(&mut self) -> Result<(), String> {
// trim name
self.name = self.name.trim().to_string();
// mode is always required
let mode = self.mode.trim().to_lowercase();
match mode.as_str() {
STR_MODE_EP11 => {
self.mode = mode;
self.validate_ep11_entry()?;
}
STR_MODE_ACCEL => {
self.mode = mode;
self.validate_accel_entry()?;
}
_ => return Err(format!("Unknown or invalid mode '{}'.", mode)),
}
Ok(())
}
}
/// Wrapper object around Vector of ApConfigEntry
pub struct ApConfigList(Vec<ApConfigEntry>);
impl ApConfigList {
#[cfg(test)] // only used in test code
pub fn from_apconfigentry_vec(apconfigs: Vec<ApConfigEntry>) -> ApConfigList {
ApConfigList(apconfigs)
}
pub fn iter(&self) -> Iter<'_, ApConfigEntry> {
self.0.iter()
}
pub fn len(&self) -> usize {
self.0.len()
}
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
fn read_yaml_file(fname: &str) -> Result<Vec<ApConfigEntry>, String> {
let file = match File::open(fname) {
Ok(f) => f,
Err(err) => {
return Err(format!(
"Failure to open AP config file {}: {:?}",
fname, err
))
}
};
match serde_yaml::from_reader(file) {
Ok(cfg) => Ok(cfg),
Err(err) => Err(format!(
"Failure parsing AP config file {}: {:?}",
fname, err
)),
}
}
fn validate(config: &mut [ApConfigEntry]) -> Result<(), String> {
for (i, entry) in config.iter_mut().enumerate() {
let ename = if !entry.name.trim().is_empty() {
format!("AP config entry {} '{}'", i, entry.name.trim())
} else {
format!("AP config entry {}", i)
};
if let Err(err) = &entry.validate() {
return Err(format!("{}: {}", ename, err));
}
}
Ok(())
}
/// Read in and validate the yaml configuration from a file.
/// Returns a Result with Ok(ApConfigList) on success
/// or an Err(errorstring) on failure.
pub fn read_and_validate_yaml_file(fname: &str) -> Result<ApConfigList, String> {
let mut apconfig = ApConfigList::read_yaml_file(fname)?;
ApConfigList::validate(&mut apconfig)?;
Ok(ApConfigList(apconfig))
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::env;
use std::fs;
use std::io::Write;
const GOOD_CONFIGS: [&str; 8] = [
"# good test 1
- name: my Accelerator
mode: AcCel
mingen: Cex7\n",
"# good test 2
- name: my Accelerator 2
description: Accelerator entry with description
mode: Accel\n",
"# good test 3
- name: my EP11 APQN 1
mode: Ep11
mkvp: 0xDB3C3B3C3F097DD55EC7EB0E7FDBCB93
serialnr: 93AADFK719460083
secretid: 0xBC9d46c052BC3574454C5715757274629a283767ed237922cfb8651c0e77320A\n",
"# good test 4
- name: my EP11 APQN 2
mode: EP11
mkvp: 0xdb3c3b3c3f097dd55ec7eb0e7fdbcb93
serialnr: 93aaDHzu42082261
secretid: 0x2ca853f959fc5ce5f1888cb48dae39514a27bb66520ac85f6073a7f678d262c0\n",
"# good test 5
- name: my EP11 APQN 3
mode: EP11
mkvp: 0xdb3c3b3c3f097dd55ec7eb0e7fdbcb93db3c3b3c3f097dd55ec7eb0e7fdbcb93
serialnr: 93aaDHzu42082261
secretid: 0xd146c9ae77cdff25fa87a5b3487587dc29a4e391b315c98570e8fa2e2ec91454\n",
"# no name but secretid given
- mode: EP11
mkvp: 0xdb3c3b3c3f097dd55ec7eb0e7fdbcb93
secretid: 0x0767668dd22f23fa675c4641e04bb4e991f443be4df13ce3896b8eeca59fcc10\n",
"# no secretid but name given
- mode: EP11
name: My-EP11-AP-config
mkvp: 0xdb3c3b3c3f097dd55ec7eb0e7fdbcb93\n",
"# secretid and name given
- mode: EP11
name: My-EP11-AP-config
mkvp: 0xdb3c3b3c3f097dd55ec7eb0e7fdbcb93
secretid: 0x0767668dd22f23fa675c4641e04bb4e991f443be4df13ce3896b8eeca59fcc10\n",
];
const BAD_CONFIGS: [&str; 12] = [
"# mode missing
- name: bad test 1
mkvp: 0xdb3c3b3c3f097dd55ec7eb0e7fdbcb93
secretid: 0x0767668dd22f23fa675c4641e04bb4e991f443be4df13ce3896b8eeca59fcc10\n",
"# invalid mode
- name: bad test 2
mode: CCA
mkvp: 0xdb3c3b3c3f097dd55ec7eb0e7fdbcb93
secretid: 0x0767668dd22f23fa675c4641e04bb4e991f443be4df13ce3896b8eeca59fcc10\n",
"# Accelerator with wrong CEX3
- name: bad test 3
mode: Accel
mingen: Cex3\n",
"# Accelerator with wrong CEX9
- name: bad test 4
mode: Accel
mingen: CEX9\n",
"# EP11 with mkvp missing
- name: bad test 5
mode: EP11
serialnr: 93AADHZU42082261\n",
"# EP11 with non hex mkvp
- name: bad test 6
mode: EP11
mkvp: 0xabcdefghijklmnopqqponmlkjihgfedcba
serialnr: 93AADHZU42082261\n",
"# EP11 with mkvp too big
- name: bad test 7
mode: EP11
mkvp: 0xdb3c3b3c3f097dd55ec7eb0e7fdbcb93aa
serialnr: 93AADHZU42082261\n",
"# EP11 with mkvp too small
- name: bad test 8
mode: EP11
mkvp: 0xdb3c3b3c3f097dd55ec7eb0e7fdbcb
serialnr: 93AADHZU42082261\n",
"# EP11 with invalid CEXx
- name: bad test 9
mode: EP11
mingen: CEX7
mkvp: 0x00112233445566778899aabbccddeeff
serialnr: 93AADHZU42082261\n",
"# EP11 with invalid Serialnr
- name: bad test 10
mode: EP11
mkvp: 0x00112233445566778899aabbccddeeff
serialnr: 93AADHZU4208226\n",
"# EP11 with invalid Serialnr
- name: bad test 11
mode: EP11
mkvp: 0x00112233445566778899aabbccddeeff
serialnr: 93AAD ZU42082261\n",
"# EP11 with sha256(name) != secretid
- name: bad test 12
mode: EP11
mkvp: 0x00112233445566778899aabbccddeeff
serialnr: AABBCCDDEEFFGGHH
secretid: 0x2ca853f959fc5ce5f1888cb48dae39514a27bb66520ac85f6073a7f678d262c0\n",
];
const BAD_DESERIALIZE: [&str; 2] = [
"/*\ntotal nonsense\n */\n",
"# wrong/unknown field
- name: de-serialize failure 1
type: EP11\n",
];
fn write_yaml_config_to_temp_file(content: &str) -> Result<String, String> {
let dir = env::temp_dir();
let rnd = rand::random::<u32>();
let fname = format!("{}/config-test-{}.yaml", dir.to_str().unwrap(), rnd);
let mut f = match File::create(&fname) {
Ok(f) => f,
Err(_) => return Err(format!("Failure creating temp file '{fname}'.")),
};
match f.write_all(content.as_bytes()) {
Ok(_) => Ok(fname),
Err(_) => {
fs::remove_file(&fname).ok();
Err(format!("Failure writing to temp file '{fname}'."))
}
}
}
#[test]
fn test_good_yaml() {
for yaml in GOOD_CONFIGS {
let f = write_yaml_config_to_temp_file(yaml).unwrap();
let config = ApConfigList::read_and_validate_yaml_file(&f).unwrap();
assert!(!config.is_empty());
fs::remove_file(&f).ok();
}
}
#[test]
fn test_bad_yaml() {
for yaml in BAD_CONFIGS {
let f = write_yaml_config_to_temp_file(yaml).unwrap();
let r = ApConfigList::read_and_validate_yaml_file(&f);
assert!(r.is_err());
fs::remove_file(&f).ok();
}
}
#[test]
fn test_invalid_deserizalize() {
for yaml in BAD_DESERIALIZE {
let f = write_yaml_config_to_temp_file(yaml).unwrap();
let r = ApConfigList::read_and_validate_yaml_file(&f);
assert!(r.is_err());
fs::remove_file(&f).ok();
}
}
#[test]
fn test_sha256() {
assert!(
crate::helper::u8_to_hexstring(&sha256("Hello".as_bytes()))
== "185f8db32271fe25f561a6fc938b2e264306ec304eda518007d1764826381969"
);
assert!(
crate::helper::u8_to_hexstring(&sha256("SECRET1".as_bytes()))
== "03153249db7ce46b0330ffb1a760b59710531af08ec4d7f8424a6870fae49360"
);
assert!(
crate::helper::u8_to_hexstring(&sha256("SECRET2".as_bytes()))
== "258499e710e0bd3bb878d6bac7e478b30f3f3e72566989f638c4143d14f6c0b6"
);
}
}

View File

@@ -0,0 +1,279 @@
// SPDX-License-Identifier: MIT
//
// Copyright IBM Corp. 2023
//
//! Collection of helper functions for pvapconfig
//
use regex::Regex;
use std::error::Error;
use std::fs;
use std::fs::{File, OpenOptions};
use std::io::{Read, Write};
use std::path::PathBuf;
pub const PATH_PVAPCONFIG_LOCK: &str = "/run/lock/pvapconfig.lock";
/// Convert u8 slice to (lowercase) hex string
pub fn u8_to_hexstring(slice: &[u8]) -> String {
let s = String::with_capacity(2 * slice.len());
slice.iter().fold(s, |acc, e| acc + &format!("{e:02x}"))
}
/// Convert hexstring to u8 vector
/// The hexstring may contain whitespaces which are ignored.
/// If there are other characters in there or if the number
/// of hex characters is uneven panic() is called.
/// # Panics
/// Panics if the given string contains characters other than
/// hex digits and whitespace. Panics if the number of hex digits
/// is not even.
#[cfg(test)] // currently only used in test code
pub fn hexstring_to_u8(hex: &str) -> Vec<u8> {
let mut s = String::new();
for c in hex.chars() {
if c.is_ascii_hexdigit() {
s.push(c);
} else if c.is_whitespace() {
// ignore
} else {
panic!("Invalid character '{c}'");
}
}
if s.len() % 2 == 1 {
panic!("Uneven # of hex characters in '{s}'");
}
let mut hex_bytes = s.as_bytes().iter().map_while(|b| match b {
b'0'..=b'9' => Some(b - b'0'),
b'a'..=b'f' => Some(b - b'a' + 10),
b'A'..=b'F' => Some(b - b'A' + 10),
_ => None,
});
let mut bytes = Vec::with_capacity(s.len());
while let (Some(h), Some(l)) = (hex_bytes.next(), hex_bytes.next()) {
bytes.push(h << 4 | l)
}
bytes
}
/// Read sysfs file into string
pub fn sysfs_read_string(fname: &str) -> Result<String, Box<dyn Error>> {
let mut file = File::open(fname)?;
let mut content = String::new();
file.read_to_string(&mut content)?;
let trimmed_content = String::from(content.trim());
Ok(trimmed_content)
}
/// Write string into sysfs file
pub fn sysfs_write_string(fname: &str, value: &str) -> Result<(), Box<dyn Error>> {
let mut file = OpenOptions::new().write(true).open(fname)?;
file.write_all(value.as_bytes())?;
Ok(())
}
/// Read sysfs file content and parse as i32 value
pub fn sysfs_read_i32(fname: &str) -> Result<i32, Box<dyn Error>> {
let content = sysfs_read_string(fname)?;
Ok(content.parse::<i32>()?)
}
/// Write an i32 value into a sysfs file
pub fn sysfs_write_i32(fname: &str, value: i32) -> Result<(), Box<dyn Error>> {
sysfs_write_string(fname, &value.to_string())
}
/// For a given (sysfs) directory construct a list of all subdirs
/// and give it back as a vector of strings. If there is no subdir,
/// the vector is empty.
pub fn sysfs_get_list_of_subdirs(dname: &str) -> Result<Vec<String>, Box<dyn Error>> {
let mut v: Vec<String> = Vec::new();
let entries = fs::read_dir(dname)?;
for entry in entries.flatten() {
let file_type = match entry.file_type() {
Ok(ft) => ft,
_ => continue,
};
if !file_type.is_dir() {
continue;
}
let fname = match entry.file_name().into_string() {
Ok(s) => s,
_ => continue,
};
v.push(fname);
}
Ok(v)
}
/// For a given (sysfs) directory construct a list of all subdirs which
/// match to the given regular expression and give the list back as a
/// vector of strings. If there is no subdir, the vector is empty.
pub fn sysfs_get_list_of_subdirs_matching_regex(
dname: &str,
regex: &str,
) -> Result<Vec<String>, Box<dyn Error>> {
let mut v: Vec<String> = Vec::new();
let re = Regex::new(regex)?;
let entries = sysfs_get_list_of_subdirs(dname)?;
for entry in entries {
if re.is_match(&entry) {
v.push(entry);
}
}
Ok(v)
}
/// LockFile for inter-process locking
///
/// Simple class for process locking for pvapconfig.
/// The lock concept is simple: The existence of a file is used as the
/// locking indicator. If the file exists something is locked, if it does
/// not exist something is not locked. In the lock file the PID of the
/// process created the file ("owning this file") is written in.
/// With the ProcessLock object leaving scope the associated lock file
/// is automatically deleted. It is assumed that the creation of a file
/// is an atomic operation - that's true for most filesystems but may
/// cause problems with network based filesystems.
/// Example:
/// ```
/// let lock = LockFile::lock("/var/lock/process.lock");
/// assert!(lock.is_ok());
/// let lock2 = LockFile::lock("/var/lock/process.lock");
/// assert!(lock2.is_err());
/// drop(lock);
/// let lock3 = LockFile::lock("/var/lock/process.lock");
/// assert!(lock3.is_ok());
/// ```
#[derive(Debug)]
pub struct LockFile {
lockfile: PathBuf,
}
impl LockFile {
/// Try to establish the lock file.
/// Upon success the given file is fresh created and has the pid of this
/// process written in. The function returns a new LockFile object
/// which has implemented the Drop Trait. So with this object going out
/// of scope the lock file is deleted. If establishing the lock file
/// fails for any reason (for example the file already exists), the
/// function fails with returning an Error string. This function does
/// NOT panic if establishing the lock file fails for any reason. If
/// the lock file could be esablished but writing in the PID fails, a
/// warning is printed but the function continues with returning a
/// LockFile object.
pub fn try_lock(fname: &str) -> Result<Self, String> {
let lockfile = PathBuf::from(fname);
let mut file = match OpenOptions::new()
.write(true)
.create_new(true)
.open(&lockfile)
{
Err(err) => {
return Err(format!(
"Failure trying to create lock file {fname}: {err:?}."
))
}
Ok(f) => f,
};
let _ = file
.write(format!("{}", std::process::id()).as_bytes())
.map_err(|err| {
println!("Warning: could not write PID into lockfile {fname}: {err:?}.")
});
Ok(LockFile { lockfile })
}
}
impl Drop for LockFile {
fn drop(&mut self) {
let _ = fs::remove_file(&self.lockfile).map_err(|err| {
println!(
"Warning: could not remove lockfile {}: {err:?}.",
self.lockfile.display()
)
});
}
}
#[cfg(test)]
mod tests {
use super::*;
use pv_core::misc::TemporaryDirectory;
// Only very simple tests
const TEST_BYTES: [u8; 8] = [0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef];
const TEST_HEXSTR: &str = "0123456789abcdef";
#[test]
fn test_u8_to_hexstring() {
let str = u8_to_hexstring(&TEST_BYTES);
assert!(str == TEST_HEXSTR);
}
#[test]
fn test_hexstring_to_u8() {
let bytes = hexstring_to_u8(TEST_HEXSTR);
assert!(bytes.as_slice() == TEST_BYTES);
}
#[test]
fn test_sysfs_read_string() {
let r = sysfs_read_string("/proc/cpuinfo");
assert!(r.is_ok());
}
#[test]
fn test_sysfs_read_i32() {
let r = sysfs_read_i32("/proc/sys/kernel/random/entropy_avail");
assert!(r.is_ok());
}
#[test]
fn test_sysfs_get_list_of_subdirs() {
let r = sysfs_get_list_of_subdirs("/proc/self");
assert!(r.is_ok());
let v = r.unwrap();
assert!(!v.is_empty());
}
#[test]
fn test_sysfs_get_list_of_subdirs_matching_regex() {
let r = sysfs_get_list_of_subdirs_matching_regex("/proc/self", "fd.*");
assert!(r.is_ok());
let v = r.unwrap();
assert!(!v.is_empty());
for e in v {
assert!(e.strip_prefix("fd").is_some());
}
}
#[test]
fn test_sysfs_write_i32() {
let temp_dir =
TemporaryDirectory::new("pvtests").expect("creating a temporary directory should work");
let test_path = temp_dir.path().join("test");
let test_path = test_path.as_os_str().to_str().expect("should work");
let mut file = File::create(test_path).unwrap();
let _ = file.write_all(b"XYZ");
drop(file);
let r = sysfs_read_i32(test_path);
assert!(r.is_err());
let r = sysfs_write_i32(test_path, 999);
assert!(r.is_ok());
let r = sysfs_read_i32(test_path);
assert!(r.is_ok());
let v = r.unwrap();
assert!(v == 999);
}
#[test]
fn test_lockfile() {
let temp_dir =
TemporaryDirectory::new("pvtests").expect("creating a temporary directory should work");
let file_path = temp_dir.path().join("my.lock");
let file_path_str = file_path.to_str().expect("should work");
let r1 = LockFile::try_lock(file_path_str);
assert!(r1.is_ok());
let r2 = LockFile::try_lock(file_path_str);
assert!(r2.is_err());
drop(r1);
let r3 = LockFile::try_lock(file_path_str);
assert!(r3.is_ok());
}
}

668
rust/pvapconfig/src/main.rs Normal file
View File

@@ -0,0 +1,668 @@
// SPDX-License-Identifier: MIT
//
// Copyright IBM Corp. 2023
//
//! pvapconfig - Tool to automatically set up the AP configuration
//! within an IBM Secure Execution guest.
//
mod ap;
mod cli;
mod config;
mod helper;
mod uv;
use ap::{Apqn, ApqnList};
use cli::ARGS;
use config::{ApConfigEntry, ApConfigList};
use helper::{LockFile, PATH_PVAPCONFIG_LOCK};
use pv_core::uv::{ListableSecretType, SecretList};
use std::process::ExitCode;
use utils::release_string;
/// Simple macro for
/// if Cli::verbose() {
/// print!(...);
/// }
macro_rules! info {
($($arg:tt)*) => {{
if ARGS.verbose() {
print!($($arg)*);
}
}};
}
/// Simple macro for the main function only
/// Does a eprintln of the arguments and then
/// return with exit failure.
macro_rules! println_and_exit_failure {
($($arg:tt)*) => {{
eprintln!($($arg)*);
return ExitCode::FAILURE;
}};
}
/// Simple macro for the main function only
/// Check if given object has is_err() true and
/// then eprintln the unwrapped error and
/// returns with exit failure.
macro_rules! on_error_print_and_exit {
($r:expr) => {
if $r.is_err() {
eprintln!("{}", $r.unwrap_err());
return ExitCode::FAILURE;
}
};
}
fn main() -> ExitCode {
// handle version option
if cli::ARGS.version {
println!(
"{} version {}\nCopyright IBM Corp. 2023",
env!("CARGO_PKG_NAME"),
release_string!()
);
return ExitCode::SUCCESS;
}
// make sure only one pvapconfig instance is running
let r = LockFile::try_lock(PATH_PVAPCONFIG_LOCK);
on_error_print_and_exit!(r);
let _lockfile = r.unwrap();
// AP bus check
info!("Checking AP bus support and facilities...\n");
let r = ap::check_ap_bus_support();
on_error_print_and_exit!(r);
let r = ap::ap_bus_has_apsb_support();
on_error_print_and_exit!(r);
info!("AP bus support and facilities are ok.\n");
// UV check
info!("Checking UV support and environment...\n");
if !pv_core::misc::pv_guest_bit_set() {
println_and_exit_failure!("Failure: this is not a SE guest.");
}
let r = uv::has_list_secrets_facility();
on_error_print_and_exit!(r);
info!("UV support and environment is ok.\n");
// read configuration
let configfile: &str = match &cli::ARGS.config {
Some(f) => f,
_ => cli::PATH_DEFAULT_CONFIG_FILE,
};
info!(
"Reading AP configuration entries from file '{}'...\n",
configfile
);
let apconfig: ApConfigList = match ApConfigList::read_and_validate_yaml_file(configfile) {
Ok(apcfg) => apcfg,
Err(err) => println_and_exit_failure!("{}", err),
};
if apconfig.is_empty() {
println!(
"No AP configuration entries in config file '{}': Nothing to do.",
configfile
);
return ExitCode::SUCCESS;
}
info!("Found {} AP configuration entries.\n", apconfig.len());
// get list of secrets from UV
info!("Fetching list of secrets from UV...\n");
let secrets: SecretList = match uv::gather_secrets() {
Err(e) => println_and_exit_failure!("{}", e),
Ok(los) => los,
};
info!("Fetched {} Secret entries from UV.\n", secrets.len());
// Warning if no UV secrets given but AP config entries require it
let non_accel_apc = apconfig
.iter()
.filter(|apc| apc.mode != config::STR_MODE_ACCEL)
.count();
if non_accel_apc > 0 && secrets.is_empty() {
println!(
"Warning: No UV Secrets given but at least one AP config entry requires a Secret."
);
}
info!("Waiting for AP bus bindings complete...\n");
if !ap::wait_for_ap_bus_bindings_complete() {
return ExitCode::FAILURE;
}
info!("Fetching list of available APQNs...\n");
let mut apqns: ApqnList = match ApqnList::gather_apqns() {
Some(l) => l,
None => return ExitCode::FAILURE,
};
if apqns.is_empty() {
info!("List of available APQNs is empty: So there's nothing to do.\n");
return ExitCode::SUCCESS;
}
info!("Found {} APQNs.\n", apqns.len());
// check MK restriction
if !apqns.check_mk_restriction() {
return ExitCode::FAILURE;
}
// now the real work
info!("Applying AP configuration...\n");
let n = match do_ap_config(&mut apqns, &secrets, &apconfig, false) {
Err(e) => println_and_exit_failure!("{}", e),
Ok(n) => n,
};
if n == 0 {
println_and_exit_failure!(
"None out of {} AP config entries could be applied.",
apconfig.len()
);
} else if ARGS.strict() && n != apconfig.len() {
println_and_exit_failure!(
"Strict flag given and only {} out of {} AP config entries have been applied.",
n,
apconfig.len()
);
}
info!(
"Successfully applied {} out of {} AP config entries.\n",
n,
apconfig.len()
);
ExitCode::SUCCESS
}
/// The real worker function
///
/// This is the real algorithm which is trying to apply the
/// AP configuration read from the config file to the existing
/// APQNs with the info from the list of secrets from the UV.
/// Returns the nr of AP config entries which are fulfilled
/// after the function ended.
/// apqns needs to be mutable as the function does a resort
/// but content stays the same.
fn do_ap_config(
apqns: &mut ApqnList,
secrets: &SecretList,
apconfig: &ApConfigList,
fntest: bool,
) -> Result<usize, String> {
let mut resolved_entries = 0;
let mut apconfig_done = vec![false; apconfig.len()];
let mut apqn_done = vec![false; apqns.len()];
// Preparation: Sort APQNs by generation.
// All the following steps iterate through the list
// of APQNs. So by sorting the APQNs starting with
// highest card generation down to the older card
// generations we prefer newer card generations over
// older card generations.
apqns.sort_by_gen();
// Step 1:
// Go through all AP config entries and try to find an APQN
// which already matches to this entry. If such an APQN is
// found mark the AP config entry as done, and mark the APQN
// as used so that entry and APQN will get skipped over in
// the next steps.
for (ci, apc) in apconfig.iter().enumerate() {
let cistr = if !apc.name.is_empty() {
format!("#{} '{}'", ci + 1, apc.name)
} else {
format!("#{}", ci + 1)
};
for (ai, apqn) in apqns.iter().enumerate() {
if apqn_done[ai] {
continue;
}
if !config_and_apqn_match(apc, apqn) {
continue;
}
if fntest {
continue;
}
match apqn.mode {
ap::ApqnMode::Accel => {
// check bind state of this APQN
let bind_state_ok = match apqn.bind_state() {
Err(err) => {
eprintln!("Warning: Failure reading APQN {apqn} bind state: {err}");
false
}
Ok(ap::BindState::Bound) => true,
Ok(_) => false,
};
if !bind_state_ok {
continue;
}
// This APQN matches to the current AP config entry and is already bound.
// So this AP config entry is satisfied: mark this config entry as done
// and mark this APQN as used.
info!("Accelerator APQN {apqn} already satisfies AP config entry {cistr}.\n");
apconfig_done[ci] = true;
apqn_done[ai] = true;
resolved_entries += 1;
break;
}
ap::ApqnMode::Ep11 => {
// check association state of this APQN
let (assoc_state_ok, assoc_idx) = match apqn.associate_state() {
Err(err) => {
eprintln!(
"Warning: Failure reading APQN {apqn} associate state: {err}"
);
(false, 0)
}
Ok(ap::AssocState::Associated(idx)) => (true, idx),
Ok(_) => (false, 0),
};
if !assoc_state_ok {
continue;
}
// check association index
let r = secrets.iter().find(|&se| {
se.stype() == ListableSecretType::Association
&& se.id().len() == uv::AP_ASSOC_SECRET_ID_SIZE
&& se.index() == assoc_idx
&& helper::u8_to_hexstring(se.id()) == apc.secretid
});
if r.is_none() {
continue;
}
// This APQN matches to the current AP config entry and is already
// associated with the right secret id. So this AP config entry is
// satisfied: mark this config entry as done and mark this APQN as used.
info!("EP11 APQN {apqn} already satisfies AP config entry {cistr}.\n");
apconfig_done[ci] = true;
apqn_done[ai] = true;
resolved_entries += 1;
break;
}
_ => {
// (currently) unknown/unsupported APQN mode
}
}
}
}
// Step 2:
// All APQNs NOT marked as done are now examined for their bind
// and association state and maybe reset to "unbound".
for (ai, apqn) in apqns.iter().enumerate() {
if apqn_done[ai] || fntest {
continue;
}
match apqn.bind_state() {
Err(err) => eprintln!("Warning: Failure reading APQN {apqn} bind state: {err}"),
Ok(ap::BindState::Bound) => {
info!("Unbind APQN {apqn} as this bind/associate does not match to any AP config entry.\n");
if !ARGS.dryrun() {
if let Err(err) = apqn.set_bind_state(ap::BindState::Unbound) {
return Err(format!("Failure unbinding APQN {apqn}: {err}"));
}
}
}
Ok(_) => {}
};
}
// Step 3:
// Go through all remaining AP config entries and try to fulfill each
// by searching for an APQN which would match to this config entry and
// then prepare this APQN (bind, maybe associate).
for (ci, apc) in apconfig.iter().enumerate() {
let cistr = if !apc.name.is_empty() {
format!("#{} '{}'", ci + 1, apc.name)
} else {
format!("#{}", ci + 1)
};
if apconfig_done[ci] {
continue;
}
for (ai, apqn) in apqns.iter().enumerate() {
if apqn_done[ai] {
continue;
}
if !config_and_apqn_match(apc, apqn) {
continue;
}
match apqn.mode {
ap::ApqnMode::Accel => {
// try to bind this accelerator APQN
if ARGS.verbose() || fntest {
println!("Bind APQN {apqn} to match to AP config entry {cistr}.");
}
if !(ARGS.dryrun() || fntest) {
if let Err(err) = apqn.set_bind_state(ap::BindState::Bound) {
// bind failed, unbind/reset this apqn, return with failure
let _ = apqn.set_bind_state(ap::BindState::Unbound);
return Err(format!("Failure binding APQN {apqn}: {err}"));
}
}
apconfig_done[ci] = true;
apqn_done[ai] = true;
resolved_entries += 1;
break;
}
ap::ApqnMode::Ep11 => {
// EP11 needs bind and associate, but before doing this let's
// check out which secret index to use with the associate
let se = match secrets.iter().find(|&se| {
se.stype() == ListableSecretType::Association
&& se.id().len() == uv::AP_ASSOC_SECRET_ID_SIZE
&& helper::u8_to_hexstring(se.id()) == apc.secretid
}) {
None => {
eprintln!("Warning: Secret id '{}' from config entry {} not found in UV secrets list.",
apc.secretid, cistr);
break;
}
Some(se) => se,
};
// try to bind
if ARGS.verbose() || fntest {
println!(
"Bind APQN {apqn} to match to AP config entry {cistr} (step 1/2)."
);
}
if !(ARGS.dryrun() || fntest) {
if let Err(err) = apqn.set_bind_state(ap::BindState::Bound) {
// bind failed, unbind/reset this apqn, return with failure
let _ = apqn.set_bind_state(ap::BindState::Unbound);
return Err(format!("Failure binding APQN {}: {}", apqn, err));
}
}
// try to associate
if ARGS.verbose() || fntest {
println!(
"Associate APQN {} with uv secrets index {} to match AP config entry {} (step 2/2).",
apqn, se.index(), cistr
);
}
if !(ARGS.dryrun() || fntest) {
let apas = ap::AssocState::Associated(se.index());
apqn.set_associate_state(apas)
.map_err(|err| format!("Failure associating APQN {apqn}: {err}"))?;
}
apconfig_done[ci] = true;
apqn_done[ai] = true;
resolved_entries += 1;
break;
}
_ => {
// (currently) unknown/unsupported APQN mode
}
}
}
}
Ok(resolved_entries)
}
/// # Panics
/// Panics if mingen for an accelerator has not a number as the 4th character.
/// Panics if mingen for an ep11 has not a number as the 4th character.
/// Please note this can not happen, as mingen is already checked via RE
/// during storing the value into mingen.
fn config_and_apqn_match(apc: &ApConfigEntry, apqn: &Apqn) -> bool {
if apc.mode == config::STR_MODE_ACCEL && apqn.mode == ap::ApqnMode::Accel {
// config and apqn are accelerators
// maybe check mingen
if !apc.mingen.is_empty() {
let mingen = &apc.mingen[3..].parse::<u32>().unwrap();
if mingen < &apqn.gen {
return false;
}
}
return true;
} else if apc.mode == config::STR_MODE_EP11 && apqn.mode == ap::ApqnMode::Ep11 {
// config and apqn are ep11
let info = match &apqn.info {
Some(ap::ApqnInfo::Ep11(i)) => i,
_ => return false,
};
// maybe check mingen
if !apc.mingen.is_empty() {
let mingen = &apc.mingen[3..].parse::<u32>().unwrap();
if mingen < &apqn.gen {
return false;
}
}
// maybe check serialnr
if !apc.serialnr.is_empty() && apc.serialnr != info.serialnr {
return false;
}
// check mkvp, currently an ep11 config entry must state an mkvp value
// whereas an ep11 info from an APQN may have an empty mkvp value to
// indicate that there is no WK set on this APQN.
if apc.mkvp != info.mkvp {
return false;
}
return true;
}
false
}
#[cfg(test)]
mod tests {
use super::*;
use helper::hexstring_to_u8;
use pv_core::uv::SecretEntry;
// This is more or less only a test for the do_ap_config() function
// However, this is THE main functionality of the whole application.
fn make_test_apqns() -> Vec<Apqn> {
vec![
ap::Apqn {
name: String::from("10.0007"),
card: 16,
domain: 7,
gen: 8,
mode: ap::ApqnMode::Accel,
info: Option::Some(ap::ApqnInfo::Accel(ap::ApqnInfoAccel {})),
},
ap::Apqn {
name: String::from("11.0008"),
card: 17,
domain: 8,
gen: 8,
mode: ap::ApqnMode::Ep11,
info: Option::Some(ap::ApqnInfo::Ep11(ap::ApqnInfoEp11 {
serialnr: String::from("93AADFK719460083"),
mkvp: String::from("db3c3b3c3f097dd55ec7eb0e7fdbcb93"),
})),
},
ap::Apqn {
name: String::from("12.0009"),
card: 18,
domain: 9,
gen: 8,
mode: ap::ApqnMode::Ep11,
info: Option::Some(ap::ApqnInfo::Ep11(ap::ApqnInfoEp11 {
serialnr: String::from("93AADHZU42082261"),
mkvp: String::from("4a27bb66520ac85f6073a7f678d262c0"),
})),
},
ap::Apqn {
name: String::from("12.000a"),
card: 18,
domain: 10,
gen: 8,
mode: ap::ApqnMode::Ep11,
info: Option::Some(ap::ApqnInfo::Ep11(ap::ApqnInfoEp11 {
serialnr: String::from("93AADHZU42082261"),
mkvp: String::from("383d2a9ab781f35343554c5b3d9337cd"),
})),
},
ap::Apqn {
name: String::from("13.000d"),
card: 19,
domain: 13,
gen: 8,
mode: ap::ApqnMode::Ep11,
info: Option::Some(ap::ApqnInfo::Ep11(ap::ApqnInfoEp11 {
serialnr: String::from("87HU397G150TZGR"),
mkvp: String::new(),
})),
},
ap::Apqn {
name: String::from("13.000f"),
card: 19,
domain: 15,
gen: 8,
mode: ap::ApqnMode::Ep11,
info: Option::None,
},
]
}
fn make_assoc_secretentry(idx: u16, hexidstr: &str) -> SecretEntry {
let id = hexstring_to_u8(hexidstr);
let idlen: u32 = id.len().try_into().unwrap();
let idarray = <&[u8; 32]>::try_from(id.as_slice()).unwrap();
SecretEntry::new(idx, ListableSecretType::Association, *idarray, idlen)
}
fn make_test_secrets() -> Vec<SecretEntry> {
vec![
make_assoc_secretentry(
33,
"3333333333333333333333333333333333333333333333333333333333333333",
),
make_assoc_secretentry(
13,
"bc9d46c052bc3574454c5715757274629a283767ed237922cfb8651c0e77320a",
),
make_assoc_secretentry(
44,
"4444444444444444444444444444444444444444444444444444444444444444",
),
make_assoc_secretentry(
15,
"06cdbbac76a595b481110d108154bc05ebbf900a0f16e36a24045998934fb1e9",
),
make_assoc_secretentry(
17,
"6831af07f8c8e7309a3ace9f3b5554d34e3eaa4a27a08fdee469e367c3fa3e9e",
),
]
}
fn make_test_apconfigs() -> Vec<ApConfigEntry> {
vec![
config::ApConfigEntry {
name: String::from("test_1"),
description: String::from("test_1"),
mode: String::from("accel"),
mkvp: String::from(""),
serialnr: String::from(""),
mingen: String::from("cex8"),
secretid: String::from(""),
},
config::ApConfigEntry {
name: String::from("test_2"),
description: String::from("test_2"),
mode: String::from("ep11"),
mkvp: String::from("db3c3b3c3f097dd55ec7eb0e7fdbcb93"),
serialnr: String::from("93AADFK719460083"),
mingen: String::from("cex8"),
secretid: String::from(
"bc9d46c052bc3574454c5715757274629a283767ed237922cfb8651c0e77320a",
),
},
config::ApConfigEntry {
name: String::from("test_3"),
description: String::from("test_3"),
mode: String::from("ep11"),
mkvp: String::from("4a27bb66520ac85f6073a7f678d262c0"),
serialnr: String::from(""),
mingen: String::from("cex8"),
secretid: String::from(
"06cdbbac76a595b481110d108154bc05ebbf900a0f16e36a24045998934fb1e9",
),
},
config::ApConfigEntry {
name: String::from("test_4"),
description: String::from("test_4"),
mode: String::from("ep11"),
mkvp: String::from("8be1eaf5c44e2fa8b18804551b604b1b"),
serialnr: String::from(""),
mingen: String::from("cex8"),
secretid: String::from(
"6831af07f8c8e7309a3ace9f3b5554d34e3eaa4a27a08fdee469e367c3fa3e9e",
),
},
]
}
#[test]
fn test_do_ap_config_invocation_1() {
let test_apqns = make_test_apqns();
let mut apqns: Vec<Apqn> = Vec::new();
apqns.push(test_apqns[0].clone());
let secrets: Vec<SecretEntry> = Vec::new();
let secretlist = SecretList::new(secrets.len() as u16, secrets);
let test_apconfigs = make_test_apconfigs();
let mut apconfig: Vec<ApConfigEntry> = Vec::new();
apconfig.push(test_apconfigs[0].clone());
let apcfglist = ApConfigList::from_apconfigentry_vec(apconfig);
let mut apqnlist = ApqnList::from_apqn_vec(apqns);
let r = do_ap_config(&mut apqnlist, &secretlist, &apcfglist, true);
assert!(r.is_ok());
let n = r.unwrap();
assert!(n == 1);
}
#[test]
fn test_do_ap_config_invocation_2() {
let test_apqns = make_test_apqns();
let mut apqns: Vec<Apqn> = Vec::new();
apqns.push(test_apqns[1].clone());
let mut secrets = make_test_secrets();
while secrets.len() > 2 {
secrets.pop();
}
let secretlist = SecretList::new(secrets.len() as u16, secrets);
let test_apconfigs = make_test_apconfigs();
let mut apconfig: Vec<ApConfigEntry> = Vec::new();
apconfig.push(test_apconfigs[1].clone());
let apcfglist = ApConfigList::from_apconfigentry_vec(apconfig);
let mut apqnlist = ApqnList::from_apqn_vec(apqns);
let r = do_ap_config(&mut apqnlist, &secretlist, &apcfglist, true);
assert!(r.is_ok());
let n = r.unwrap();
assert!(n == 1);
}
#[test]
fn test_do_ap_config_invocation_3() {
let test_apqns = make_test_apqns();
let mut apqns: Vec<Apqn> = Vec::new();
for a in test_apqns.iter() {
apqns.push(a.clone());
}
apqns.reverse();
let secrets = make_test_secrets();
let secretlist = SecretList::new(secrets.len() as u16, secrets);
let test_apconfigs = make_test_apconfigs();
let mut apconfig: Vec<ApConfigEntry> = Vec::new();
for c in test_apconfigs.iter() {
apconfig.push(c.clone());
}
let apcfglist = ApConfigList::from_apconfigentry_vec(apconfig);
let mut apqnlist = ApqnList::from_apqn_vec(apqns);
let r = do_ap_config(&mut apqnlist, &secretlist, &apcfglist, true);
assert!(r.is_ok());
let n = r.unwrap();
assert!(n == 3, "n = {} != 3", n);
}
}

105
rust/pvapconfig/src/uv.rs Normal file
View File

@@ -0,0 +1,105 @@
// SPDX-License-Identifier: MIT
//
// Copyright IBM Corp. 2023
//
//! UV related functions for pvapconfig
//
use pv_core::uv::{ListCmd, SecretList, UvDevice, UvcSuccess};
use regex::Regex;
use std::path::Path;
/// The byte size of association secret of type 2 in struct SecretEntry
pub const AP_ASSOC_SECRET_ID_SIZE: usize = 32;
const PATH_SYS_FW_UV_FACILITIES: &str = "/sys/firmware/uv/query/facilities";
const RE_UV_FACILITIES: &str = r"^(0x)?([[:xdigit:]]+)";
const RE_UV_FAC_BIT_LIST_SECRETS: u32 = 30;
/// Check UV facilities to offer the 'list secrets' call.
/// Returns a Result with Ok(()) if the 'list secrets' feature
/// is available, otherwise an Err(reasonstring) is returned where
/// the string denotes a hint which can be displayed.
/// # Panics
/// Panics if the compilation of a static regular expression fails.
/// Panics if RE_UV_FACILITIES does not match.
pub fn has_list_secrets_facility() -> Result<(), String> {
if !Path::new(PATH_SYS_FW_UV_FACILITIES).is_file() {
return Err(format!(
"UV facilities sysfs attribute not found (file {} does not exist).",
PATH_SYS_FW_UV_FACILITIES
));
}
let facstr = match crate::helper::sysfs_read_string(PATH_SYS_FW_UV_FACILITIES) {
Ok(s) => s,
Err(err) => {
return Err(format!(
"Failure reading UV facilities from {PATH_SYS_FW_UV_FACILITIES} ({:?}).",
err
))
}
};
let re_uv_facilities = Regex::new(RE_UV_FACILITIES).unwrap();
if !re_uv_facilities.is_match(&facstr) {
Err(format!("Failure parsing UV facilities entry '{facstr}'."))
} else {
let caps = re_uv_facilities.captures(&facstr).unwrap();
let fachex = caps.get(2).unwrap().as_str();
let i: usize = RE_UV_FAC_BIT_LIST_SECRETS as usize / 4;
if i >= fachex.len() {
return Err(format!("Failure parsing UV facilities entry '{fachex}'."));
}
let nibble = u32::from_str_radix(&fachex[i..i + 1], 16).unwrap();
const THEBIT: u32 = 1 << (3 - (RE_UV_FAC_BIT_LIST_SECRETS % 4));
if nibble & THEBIT == 0 {
return Err("The 'list secret' feature is missing on this UV.".to_string());
}
Ok(())
}
}
/// Fetch the list of secrets from the UV.
/// Returns Err(errorstring) on error or
/// Ok(SecretList) on success.
/// The list may be empty if the UV doesn't have any secrets stored.
pub fn gather_secrets() -> Result<SecretList, String> {
let uv = match UvDevice::open() {
Err(e) => return Err(format!("Failed to open UV device: {:?}.", e)),
Ok(u) => u,
};
let mut cmd = ListCmd::default();
match uv.send_cmd(&mut cmd).map_err(|e| format!("{e:?}"))? {
UvcSuccess::RC_SUCCESS => (),
UvcSuccess::RC_MORE_DATA => println!("Warning: There is more data available than expected"),
};
cmd.try_into().map_err(|e| format!("{e:?}"))
}
#[cfg(test)]
mod tests {
use super::*;
// As the name says: check for list secrets feature bit in UV facilities.
#[test]
fn test_has_list_secrets_facility() {
let r = has_list_secrets_facility();
if pv_core::misc::pv_guest_bit_set() {
assert!(r.is_ok());
} else {
assert!(r.is_err());
}
}
// Simple invocation of the list_secrets function. Should not fail
#[test]
fn test_list_secrets() {
let r = gather_secrets();
if pv_core::misc::pv_guest_bit_set() {
assert!(r.is_ok());
} else {
assert!(r.is_err());
}
}
}

View File

@@ -10,5 +10,5 @@ clap = { version ="4", features = ["derive", "wrap_help"]}
log = { version = "0.4.6", features = ["std", "release_max_level_debug"] }
serde_yaml = "0.9"
pv = { path = "../pv", features = ["uvsecret", "request"] }
pv = { path = "../pv" }
utils = { path = "../utils" }

View File

@@ -4,11 +4,13 @@ s390-tools is free software; you can redistribute it and/or modify
it under the terms of the MIT license. See LICENSE for details.
-->
# pvsecret
## Synopsis
`pvsecret [OPTIONS] <COMMAND>`
## Description
Use **pvsecret** to manage secrets for IBM Secure Execution guests. **pvsecret**
can **create** add-secret requests on any architecture. On s390x systems, use
**pvsecret** to **add** the secrets to the ultravisor secret store, **list** all
secrets in the secret store, or lock the secret store to prevent any
secrets in the secret store, or **lock** the secret store to prevent any
modifications in the future.
The ultravisor secret store stores secrets for the IBM Secure Execution guest.
@@ -18,193 +20,247 @@ Create requests only on trusted systems that are not the IBM Secure Execution
guest where you want to inject the secrets. This approach prevents the secrets
from being in cleartext on the guest. For extra safety, do an attestation with
**pvattest** of your guest beforehand, and include the configuration UID in the
secret request using **--cuid**. Refer to **pvsecret-add**(1) for more
information. For all certificates, revocation lists, and host-key documents,
both the PEM and DER input formats are supported.
secret request using **--cuid**. Refer to **pvsecret-add** for more information.
For all certificates, revocation lists, and host-key documents, both the PEM and
DER input formats are supported.
## Synopsis
`pvsecret [OPTIONS] <COMMAND>`
## Commands Overview
- **create**
Create a new add-secret request
- **add**
Repeat an add-secret request (s390x only)
- **lock**
Lock the secret-store (s390x only)
- **list**
List all ultravisor secrets (s390x only)
- **create**
<ul>
Create a new add-secret request
</ul>
- **add**
<ul>
Perform an add-secret request (s390x only)
</ul>
- **lock**
<ul>
Lock the secret-store (s390x only)
</ul>
- **list**
<ul>
List all ultravisor secrets (s390x only)
</ul>
- **verify**
<ul>
Verify that an add-secret request is sane
</ul>
## Options
`-v`, `--verbose`
<ul>
Provide more detailed output
Provide more detailed output
</ul>
`--version`
<ul>
Print version information and exit
Print version information and exit
</ul>
## pvsecret create
### Synopsis
`pvsecret create [OPTIONS] --host-key-document <FILE> --hdr <FILE> --output <FILE> <--no-verify|--cert <FILE>> <COMMAND>`
### Description
Create add-secret requests for IBM Secure Execution guests. Only create these
requests in a trusted environment, such as your workstation. The **pvattest
create** command creates a randomly generated key to protect the request. The
generated requests can then be added on an IBM Secure Execution guest using
**pvsecret add**. The guest can then use the secrets with the use case
depending on the secret type.
**pvsecret add**. The guest can then use the secrets with the use case depending
on the secret type.
Such a request is bound to a specific IBM Secure Execution image specified with
**--hdr**. Optionally, the request can be bound to a specific instance when
**--hdr**. Optionally, the request can be bound to a specific instance when
bound to the Configuration Unique ID from **pvattest** using **--cuid**
### Synopsis
`pvsecret create [OPTIONS] --host-key-document <FILE> --hdr <FILE> --output <FILE> <--no-verify|--cert <FILE>> <COMMAND>`
### Commands Overview
- **meta**
Use a meta secret to carry flags to the ultravisor without having to provide
an actual secret value. Meta secrets do not appear in the list of secrets
- **association**
Use an association secret to connect a trusted I/O device to a guest. The
`pvapconfig` tool provides more information about association secrets
- **meta**
<ul>
Create a meta secret
</ul>
- **association**
<ul>
Create an association secret
</ul>
### Options
`-k`, `--host-key-document <FILE>`
<ul>
Use FILE as a host-key document. Can be specified multiple times and must be
used at least once.
Use FILE as a host-key document. Can be specified multiple times and must be
used at least once.
</ul>
`--no-verify`
<ul>
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 beforehand.
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 beforehand.
</ul>
`-C`, `--cert <FILE>`
<ul>
Use FILE as a certificate to verify the host key or keys. The certificates
are used to establish a chain of trust for the verification of the host-key
documents. Specify this option twice to specify the IBM Z signing key and
the intermediate CA certificate (signed by the root CA).
Use FILE as a certificate to verify the host key or keys. The certificates are
used to establish a chain of trust for the verification of the host-key
documents. Specify this option twice to specify the IBM Z signing key and the
intermediate CA certificate (signed by the root CA).
</ul>
`--crl <FILE>`
<ul>
Use FILE as a certificate revocation list. The list is used to check whether
a certificate of the chain of trust is revoked. Specify this option multiple
times to use multiple CRLs.
Use FILE as a certificate revocation list. The list is used to check whether a
certificate of the chain of trust is revoked. Specify this option multiple times
to use multiple CRLs.
</ul>
`--offline`
<ul>
Make no attempt to download CRLs
Make no attempt to download CRLs
</ul>
`--root-ca <ROOT_CA>`
<ul>
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.
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.
</ul>
`--hdr <FILE>`
<ul>
Specifies the header of the guest image. Can be an IBM Secure Execution
image created by genprotimg or an extracted IBM Secure Execution header. The
header must start at a page boundary.
Specifies the header of the guest image. Can be an IBM Secure Execution image
created by genprotimg or an extracted IBM Secure Execution header. The header
must start at a page boundary.
</ul>
`-f`, `--force`
<ul>
Force the generation of add-secret requests on IBM Secure Execution guests.
If the program detects that it is running on an IBM Secure Execution guest,
it denies the generation of add-secret requests. The force flag overwrites
this behavior.
Force the generation of add-secret requests on IBM Secure Execution guests. If
the program detects that it is running on an IBM Secure Execution guest, it
denies the generation of add-secret requests. The force flag overwrites this
behavior.
</ul>
`-o`, `--output <FILE>`
<ul>
Write the generated request to FILE
Write the generated request to FILE
</ul>
`--extension-secret <FILE>`
<ul>
Use the content of FILE as an extension secret. The file must be exactly 32
bytes long. If this request is the first, all subsequent requests must have
the same extension secret. Only makes sense if bit 1 of the secret control
flags of the IBM Secure Execution header is 0. Otherwise the ultravisor
rejects the request.
Use the content of FILE as an extension secret. The file must be exactly 32
bytes long. If this request is the first, all subsequent requests must have the
same extension secret. Only makes sense if bit 1 of the secret control flags of
the IBM Secure Execution header is 0. Otherwise the ultravisor rejects the
request.
</ul>
`--cck <FILE>`
<ul>
Use the content of FILE as the customer-communication key (CCK) to derive
the extension secret. The file must contain exactly 32 bytes of data. If the
target guest was started with bit 1 of the secret control flag set, the
ultravisor also derives the secret from the CCK. Otherwise, the ultravisor
interprets the extension secret as a normal one. This still works if you
use the same CCK for all requests.
Use the content of FILE as the customer-communication key (CCK) to derive the
extension secret. The file must contain exactly 32 bytes of data. If the target
guest was started with bit 1 of the secret control flag set, the ultravisor also
derives the secret from the CCK. Otherwise, the ultravisor interprets the
extension secret as a normal one. This still works if you use the same CCK for
all requests.
</ul>
`--cuid-hex <HEXSTRING>`
<ul>
Use HEXSTRING as the Configuration Unique ID. Must be a hex 128-bit unsigned
big endian number string. Leading zeros must be provided. If specified, the
value must match with the Config-UID from the attestation result of that
guest. If not specified, the CUID will be ignored by the ultravisor during
the verification of the request.
Use HEXSTRING as the Configuration Unique ID. Must be a hex 128-bit unsigned big
endian number string. Leading zeros must be provided. If specified, the value
must match with the Config-UID from the attestation result of that guest. If not
specified, the CUID will be ignored by the ultravisor during the verification of
the request.
</ul>
`--cuid <FILE>`
<ul>
Use the content of FILE as the Configuration Unique ID. The file must
contain exactly 128 bit of data, a hex string, or a yaml with a `cuid`
entry. If specified, the value must match the Config-UID from the
attestation result of that guest. If not specified, the CUID will be ignored
by the Ultravisor during the verification of the request.
Use the content of FILE as the Configuration Unique ID. The file must contain
exactly 128 bit of data or a yaml with a `cuid` entry. If specified, the value
must match the Config-UID from the attestation result of that guest. If not
specified, the CUID will be ignored by the Ultravisor during the verification
of the request.
</ul>
`--flags <FLAGS>`
<ul>
Flags for the add-secret request.
Flags for the add-secret request
Possible values:
- disable-dump: Disables host-initiated dumping for the target guest
instance
- **disable-dump**: Disables host-initiated dumping for the target guest instance
</ul>
`--user-data <FILE>`
<ul>
Use the content of FILE as user-data. Passes user data defined in <FILE> through
the add-secret request to the ultravisor. The user data can be up to 512 bytes
of arbitrary data, and the maximum size depends on the size of the user-signing
key:
- No key: user data can be 512 bytes.
- EC(secp521r1) or RSA 2048 keys: user data can be 256 bytes.
- RSA 3072 key: user data can be 128 bytes.
The firmware ignores this data, but the request tag protects the user-data.
Optional. No user-data by default.
</ul>
`--user-sign-key <FILE>`
<ul>
Use the content of FILE as user signing key. Adds a signature defined calculated
from the key in <FILE> to the add-secret request. The file must be in DER or PEM
format containing a private key. Supported are RSA 2048 & 3072-bit and
EC(secp521r1) keys. The firmware ignores the content, but the request tag
protects the signature. The user-signing key signs the request. The location of
the signature is filled with zeros during the signature calculation. The request
tag also secures the signature. See man pvsecret verify for more details.
Optional. No signature by default.
</ul>
### pvsecret create meta
#### Description
Use a meta secret to carry flags to the ultravisor without having to provide an
actual secret value. Meta secrets do not appear in the list of secrets.
#### Synopsis
`pvsecret create meta`
#### Description
Create a meta secret. Use a meta secret to carry flags to the ultravisor without
having to provide an actual secret value. Meta secrets do not appear in the list
of secrets.
### pvsecret create association
#### Description
Use an association secret to connect a trusted I/O device to a guest. The
`pvapconfig` tool provides more information about association secrets.
#### Synopsis
`pvsecret create association [OPTIONS] <NAME>`
#### Description
Create an association secret. Use an association secret to connect a trusted I/O
device to a guest. The `pvapconfig` tool provides more information about
association secrets.
#### Arguments
`<NAME>`
<ul>
String to identify the new secret. The actual secret is set with
--input-secret. The name is saved in `NAME.yaml` with white-spaces
mapped to `_`.
String to identify the new secret. The actual secret is set with --input-secret.
The name is saved in `NAME.yaml` with white-spaces mapped to `_`.
</ul>
@@ -212,73 +268,142 @@ Use an association secret to connect a trusted I/O device to a guest. The
`--stdout`
<ul>
Print the hashed name to stdout. The hashed name will not be written to
`NAME.yaml`
Print the hashed name to stdout. The hashed name is not written to `NAME.yaml`
</ul>
`--input-secret <FILE>`
<ul>
Path from which to read the plaintext secret. Uses a random secret if not
specified.
Path from which to read the plaintext secret. Uses a random secret if not
specified
</ul>
`--output-secret <FILE>`
<ul>
Save the generated secret as plaintext in FILE. The generated secret can be
used to generate add-secret requests for a different guest with the same
secret using --input-secret. Destroy the secret when it is not used
anymore.
Save the generated secret as plaintext in FILE. The generated secret can be used
to generate add-secret requests for a different guest with the same secret using
--input-secret. Destroy the secret when it is not used anymore.
</ul>
## pvsecret add
### Description
Perform an add-secret request using a previously generated add-secret request.
Only available on s390x.
### Synopsis
`pvsecret add <FILE>`
### Description
Perform an add-secret request (s390x only). Perform an add-secret request using
a previously generated add-secret request. Only available on s390x.
### Arguments
`<FILE>`
<ul>
Specify the request to be sent
Specify the request to be sent
</ul>
## pvsecret lock
### Description
Lock the secret store (s390x only). After this command executed successfully,
all add-secret requests will fail. Only available on s390x.
### Synopsis
`pvsecret lock`
### Description
Lock the secret-store (s390x only). Lock the secret store (s390x only). After
this command executed successfully, all add-secret requests will fail. Only
available on s390x.
## pvsecret list
### Description
Lists the IDs of all non-null secrets currently stored in the ultravisor for the
currently running IBM Secure Execution guest. Only available on s390x.
### Synopsis
`pvsecret list [OPTIONS] [FILE]`
### Description
List all ultravisor secrets (s390x only). Lists the IDs of all non-null secrets
currently stored in the ultravisor for the currently running IBM Secure
Execution guest. Only available on s390x.
### Arguments
`<FILE>`
<ul>
Store the result in FILE. Default value: '-'
Store the result in FILE
Default value: '-'
</ul>
### Options
`--format <FORMAT>`
Define the output format of the list. Default value: 'human'
<ul>
Define the output format of the list
Default value: 'human'
Possible values:
- human: Human-focused, non-parsable output format
- yaml: Use yaml format
- bin: Use the format the ultravisor uses to pass the list
- **human**: Human-focused, non-parsable output format
- **yaml**: Use yaml format
- **bin**: Use the format the ultravisor uses to pass the list
</ul>
## pvsecret verify
### Synopsis
`pvsecret verify [OPTIONS] <FILE>`
### Description
Verifies that the given request is an Add-Secret request by testing for some
values to be present. If the request contains signed user-data, the signature
is verified with the provided key. Outputs the arbitrary user-data. All data in
the request is in big endian. `verify` checks the following:
- The first 6 bytes of the request are equal to: `B6173 7263 624d | asrcbM`
- The sizes in the request header are sane and do not point out of the file
- The request version is supported by the binary
- If user-data contains a signature, verify the signature using a public key
The content of bytes 6&7 of the request define which kind of user-data the
request contains.
- **0x0000** `no user-data (512 bytes zero)`
- **0x0001** `512 bytes user-data`
- **0x0002** `265 bytes user-data| 139 bytes ecdsa signature | 5 bytes reserved
| 2 bytes signature size | ...`
- **0x0003** `256 bytes user-data | 256 bytes rsa2048 signature`
- **0x0004** `128 bytes user-data | 384 bytes rsa3072 signature`
The actual user-data may be less than the capacity. If less data was provided
during `create` zeros are appended.
For type 2-4 The signature is calculated as follows:
1) The request is generated with the user-data in place and zeros for the
signature data.
2) The signature is calculated for the request. The signature signs the
authenticated data and the encrypted data, but not the request tag. I.e. the
signature signs the whole request but the last 16 bytes a,d with the signature
bytes set to zero.
3) The signature is inserted to its location in the request.
4) The request GCM tag is calculated.
The verification process works as follows:
1) copy the signature to a buffer
2) overwrite the signature with zeros
3) verify the signature of the request but the last 16 bytes
### Arguments
`<FILE>`
<ul>
Specify the request to be checked
</ul>
### Options
`--user-cert <FILE>`
<ul>
Certificate containing a public key used to verify the user data signature.
Specifies a public key used to verify the user-data signature. The file must be
a X509 certificate in DSA or PEM format. The certificate must hold the public
EC, RSA 2048, or RSA 3072 key corresponding to the private user-key used during
`create`. No chain of trust is established. Ensuring that the certificate can be
trusted is the responsibility of the user. The EC key must use the NIST/SECG
curve over a 521 bit prime field (secp521r1).
</ul>
`-o`, `--output <FILE>`
<ul>
Store the result in FILE If the request contained abirtary user-data the output
contains this user-data with padded zeros if available.
Default value: '-'
</ul>

View File

@@ -3,11 +3,11 @@
.\" it under the terms of the MIT license. See LICENSE for details.
.\"
.TH pvsecret-add 1 "2023-07-28" "s390-tools" "UV-Secret Manual"
.TH pvsecret-add 1 "2024-01-30" "s390-tools" "UV-Secret Manual"
.nh
.ad l
.SH NAME
\fBpvsecret add\fP - Repeat an add-secret request (s390x only)
\fBpvsecret add\fP - Perform an add-secret request (s390x only)
\fB
.SH SYNOPSIS
.nf
@@ -18,7 +18,6 @@ pvsecret add <FILE>
.SH DESCRIPTION
Perform an add-secret request using a previously generated add-secret request.
Only available on s390x.
.SH OPTIONS
.PP
<FILE>

View File

@@ -3,11 +3,11 @@
.\" it under the terms of the MIT license. See LICENSE for details.
.\"
.TH pvsecret-create-association 1 "2023-07-28" "s390-tools" "UV-Secret Manual"
.TH pvsecret-create-association 1 "2024-01-30" "s390-tools" "UV-Secret Manual"
.nh
.ad l
.SH NAME
\fBpvsecret create association\fP - Use an association secret to connect an I/O device to a guest
\fBpvsecret create association\fP - Create an association secret
\fB
.SH SYNOPSIS
.nf
@@ -18,13 +18,12 @@ pvsecret create association [OPTIONS] <NAME>
.SH DESCRIPTION
Use an association secret to connect a trusted I/O device to a guest. The
`pvapconfig` tool provides more information about association secrets.
.SH OPTIONS
.PP
<NAME>
.RS 4
String to identify the new secret. The actual secret is set with
\fB--input-secret\fR. The name is saved in `NAME.yaml` with white-spaces mapped
\fB--input-secret\fR. The name is saved in `NAME.yaml` with white-spaces mapped
to `_`.
.RE
.RE

View File

@@ -3,11 +3,11 @@
.\" it under the terms of the MIT license. See LICENSE for details.
.\"
.TH pvsecret-create-meta 1 "2023-07-28" "s390-tools" "UV-Secret Manual"
.TH pvsecret-create-meta 1 "2024-01-30" "s390-tools" "UV-Secret Manual"
.nh
.ad l
.SH NAME
\fBpvsecret create meta\fP - Use a meta secret to carry flags to the ultravisor
\fBpvsecret create meta\fP - Create a meta secret
\fB
.SH SYNOPSIS
.nf
@@ -17,8 +17,7 @@ pvsecret create meta
.fi
.SH DESCRIPTION
Use a meta secret to carry flags to the ultravisor without having to provide an
actual secret value. Meta secrets do not appear in the list of secrets.
actual secret value. Meta secrets do not appear in the list of secrets.
.SH "SEE ALSO"
.sp
\fBpvsecret\fR(1) \fBpvsecret-create\fR(1)

View File

@@ -3,7 +3,7 @@
.\" it under the terms of the MIT license. See LICENSE for details.
.\"
.TH pvsecret-create 1 "2023-07-28" "s390-tools" "UV-Secret Manual"
.TH pvsecret-create 1 "2024-01-30" "s390-tools" "UV-Secret Manual"
.nh
.ad l
.SH NAME
@@ -22,10 +22,10 @@ create\fR command creates a randomly generated key to protect the request. The
generated requests can then be added on an IBM Secure Execution guest using
\fBpvsecret add\fR. The guest can then use the secrets with the use case
depending on the secret type.
Such a request is bound to a specific IBM Secure Execution image specified with
\fB--hdr\fR. Optionally, the request can be bound to a specific instance when
bound to the Configuration Unique ID from \fBpvattest\fR using \fB--cuid\fR
.SH OPTIONS
.PP
\-k, \-\-host-key-document <FILE>
@@ -62,15 +62,15 @@ to use multiple CRLs.
.PP
\-\-offline
.RS 4
Make no attempt to download CRLs
Make no attempt to download CRLs.
.RE
.RE
.PP
\-\-root-ca <ROOT_CA>
.RS 4
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.
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.
.RE
.RE
.PP
@@ -93,7 +93,7 @@ behavior.
.PP
\-o, \-\-output <FILE>
.RS 4
Write the generated request to FILE
Write the generated request to FILE.
.RE
.RE
.PP
@@ -120,21 +120,21 @@ all requests.
.PP
\-\-cuid-hex <HEXSTRING>
.RS 4
Use HEXSTRING as the Configuration Unique ID. Must be a hex 128-bit unsigned
big endian number string. Leading zeros must be provided. If specified, the
value must match with the Config-UID from the attestation result of that guest.
If not specified, the CUID will be ignored by the ultravisor during the
verification of the request.
Use HEXSTRING as the Configuration Unique ID. Must be a hex 128-bit unsigned big
endian number string. Leading zeros must be provided. If specified, the value
must match with the Config-UID from the attestation result of that guest. If not
specified, the CUID will be ignored by the ultravisor during the verification of
the request.
.RE
.RE
.PP
\-\-cuid <FILE>
.RS 4
Use the content of FILE as the Configuration Unique ID. The file must contain
exactly 128 bit of data, a hex string, or a yaml with a `cuid` entry. If
specified, the value must match the Config-UID from the attestation result of
that guest. If not specified, the CUID will be ignored by the Ultravisor during
the verification of the request.
exactly 128 bit of data or a yaml with a `cuid` entry. If specified, the value
must match the Config-UID from the attestation result of that guest. If not
specified, the CUID will be ignored by the Ultravisor during the verification
of the request.
.RE
.RE
.PP
@@ -146,6 +146,37 @@ Possible values:
.RS 4
- \fBdisable-dump\fP: Disables host-initiated dumping for the target guest instance.
.RE
.RE
.PP
\-\-user-data <FILE>
.RS 4
Use the content of FILE as user-data. Passes user data defined in <FILE> through
the add-secret request to the ultravisor. The user data can be up to 512 bytes
of arbitrary data, and the maximum size depends on the size of the user-signing
key:
- No key: user data can be 512 bytes.
- EC(secp521r1) or RSA 2048 keys: user data can be 256 bytes.
- RSA 3072 key: user data can be 128 bytes.
The firmware ignores this data, but the request tag protects the user-data.
Optional. No user-data by default.
.RE
.RE
.PP
\-\-user-sign-key <FILE>
.RS 4
Use the content of FILE as user signing key. Adds a signature defined calculated
from the key in <FILE> to the add-secret request. The file must be in DER or PEM
format containing a private key. Supported are RSA 2048 & 3072-bit and
EC(secp521r1) keys. The firmware ignores the content, but the request tag
protects the signature. The user-signing key signs the request. The location of
the signature is filled with zeros during the signature calculation. The request
tag also secures the signature. See man pvsecret verify for more details.
Optional. No signature by default.
.RE
.RE

View File

@@ -3,7 +3,7 @@
.\" it under the terms of the MIT license. See LICENSE for details.
.\"
.TH pvsecret-list 1 "2023-07-28" "s390-tools" "UV-Secret Manual"
.TH pvsecret-list 1 "2024-01-30" "s390-tools" "UV-Secret Manual"
.nh
.ad l
.SH NAME
@@ -22,22 +22,24 @@ currently running IBM Secure Execution guest. Only available on s390x.
.PP
<FILE>
.RS 4
Store the result in FILE. Default value: '-'
Store the result in FILE.
[default: '-']
.RE
.RE
.PP
\-\-format <FORMAT>
.RS 4
Define the output format of the list. Default value: 'human'
Define the output format of the list.
[default: 'human']
Possible values:
.RS 4
- \fBhuman\fP: Human-focused, non-parsable output format
- \fBhuman\fP: Human-focused, non-parsable output format.
- \fByaml\fP: Use yaml format
- \fByaml\fP: Use yaml format.
- \fBbin\fP: Use the format the ultravisor uses to pass the list
- \fBbin\fP: Use the format the ultravisor uses to pass the list.
.RE
.RE

View File

@@ -3,7 +3,7 @@
.\" it under the terms of the MIT license. See LICENSE for details.
.\"
.TH pvsecret-lock 1 "2023-07-28" "s390-tools" "UV-Secret Manual"
.TH pvsecret-lock 1 "2024-01-30" "s390-tools" "UV-Secret Manual"
.nh
.ad l
.SH NAME
@@ -18,9 +18,6 @@ pvsecret lock
.SH DESCRIPTION
Lock the secret store (s390x only). After this command executed successfully,
all add-secret requests will fail. Only available on s390x.
.SH OPTIONS
.SH "SEE ALSO"
.sp
\fBpvsecret\fR(1)

View File

@@ -0,0 +1,139 @@
.\" Copyright 2023 IBM Corp.
.\" s390-tools is free software; you can redistribute it and/or modify
.\" it under the terms of the MIT license. See LICENSE for details.
.\"
.TH pvsecret-verify 1 "2024-01-30" "s390-tools" "UV-Secret Manual"
.nh
.ad l
.SH NAME
\fBpvsecret verify\fP - Verify that an add-secret request is sane
\fB
.SH SYNOPSIS
.nf
.fam C
pvsecret verify [OPTIONS] <FILE>
.fam C
.fi
.SH DESCRIPTION
.PP
Verifies that the given request is an Add-Secret request by testing for some
values to be present. If the request contains signed user-data, the signature is
verified with the provided key. Outputs the arbitrary user-data. All data in the
request is in big endian.
.PP
\fIverify\fP checks the following:
.RS
.IP \[bu] 2
The first 6 bytes of the request are equal to: \fB6173 7263 624d | asrcbM\fP
.IP \[bu] 2
The sizes in the request header are sane and do not point out of the
file
.IP \[bu] 2
The request version is supported by the binary
.IP \[bu] 2
If user-data contains a signature, verify the signature using a public
key
.RE
.PP
The content of bytes 6&7 of the request define which kind
of user-data the request contains.
.IP \fB0x0000\fP 8
no user-data (512 bytes zero)
.IP \fB0x0001\fP 8
512 bytes user-data
.IP \fB0x0002\fP 8
265 bytes user-data| 139 bytes ecdsa signature | 5 bytes reserved | 2 bytes
signature size | ...
.IP \fB0x0003\fP 8
256 bytes user-data | 256 bytes rsa2048 signature
.IP \fB0x0004\fP 8
128 bytes user-data | 384 bytes rsa3072 signature
.PP
The actual user-data may be less than the capacity. If less data was provided
during \fIcreate\fP zeros are appended.
.
For type 2-4 The signature is calculated as follows:
.RS
.IP "1." 3
The request is generated with the user-data in place and zeros for the
signature data.
.IP "2." 3
The signature is calculated for the request. The signature signs the
authenticated data and the encrypted data, but not the request tag. I.e. the
signature signs the whole request but the last 16 bytes and with the signature
bytes set to zero.
.IP "3." 3
The signature is inserted to its location in the request.
.IP "4." 3
The request GCM tag is calculated.
.PP
.RE
The verification process works as follows:
.RS
.IP "1." 3
copy the signature to a buffer
.IP "2." 3
overwrite the signature with zeros
.IP "3." 3
verify the signature of the request but the last 16 bytes
.RE
.SH OPTIONS
.PP
<FILE>
.RS 4
Specify the request to be checked.
.RE
.RE
.PP
\-\-user-cert <FILE>
.RS 4
Certificate containing a public key used to verify the user data signature.
Specifies a public key used to verify the user-data signature. The file must be
a X509 certificate in DSA or PEM format. The certificate must hold the public
EC, RSA 2048, or RSA 3072 key corresponding to the private user-key used during
`create`. No chain of trust is established. Ensuring that the certificate can be
trusted is the responsibility of the user. The EC key must use the NIST/SECG
curve over a 521 bit prime field (secp521r1).
.RE
.RE
.PP
\-o, \-\-output <FILE>
.RS 4
Store the result in FILE If the request contained abirtary user-data the output
contains this user-data with padded zeros if available.
[default: '-']
.RE
.RE
.SH EXAMPLES
.PP
Create the add-secret request on a trusted system with signed user datai similar to the example for \fFpvsecret\fP. Let's assume there are three more files present .\fFuser_data\fP contains ascii "some example user-data", a private user-signing key e.g. rsa3072 \fFusr_sgn_key.priv.pem\fF, and a certificate containing the corresponding public key to the private rsa3072 key \fFuser_cert.pem\fP.
.PP
.RS
.IP trusted:~$ 12
pvsecret create -k hkd.crt --cert CA.crt --cert ibmsk.crt --hdr pvimage -o addsecreq.bin --user-data user_data --user-sign-key usr_sgn_key.priv.pem association EXAMPLE
.RE
.RS
Successfully generated the request
.br
Successfully wrote association info to 'EXAMPLE.yaml'
.RE
For example, on the SE-guest, perform \fIverify\fP on the request to verify the user-signature and the saneness of the request. On success, The user-data is printed to stdout (if \fI--output\fP was not specified) and \fFSuccesfully verified the request.\fP is printed to stderr.
.PP
.RS
.IP seguest:~$ 12
pvsecret verify --user-cert user_cert.pem -o addsecreq.bin
.RE
.RS
some example user-data
.br
Successfully verified the request
.RE
.SH "SEE ALSO"
.sp
\fBpvsecret\fR(1)

View File

@@ -1,25 +0,0 @@
.\" Copyright 2023 IBM Corp.
.\" s390-tools is free software; you can redistribute it and/or modify
.\" it under the terms of the MIT license. See LICENSE for details.
.\"
.TH pvsecret-version 1 "2023-07-28" "s390-tools" "UV-Secret Manual"
.nh
.ad l
.SH NAME
\fBpvsecret version\fP- Print version information and exit
\fB
.SH SYNOPSIS
.nf
.fam C
pvsecret version
.fam C
.fi
.SH DESCRIPTION
Print version information and exit
.SH OPTIONS
.SH "SEE ALSO"
.sp
\fBpvsecret\fR(1)

View File

@@ -3,7 +3,7 @@
.\" it under the terms of the MIT license. See LICENSE for details.
.\"
.TH pvsecret 1 "2023-07-28" "s390-tools" "UV-Secret Manual"
.TH pvsecret 1 "2024-01-30" "s390-tools" "UV-Secret Manual"
.nh
.ad l
.SH NAME
@@ -30,7 +30,7 @@ guest where you want to inject the secrets. This approach prevents the secrets
from being in cleartext on the guest. For extra safety, do an attestation with
\fBpvattest\fR of your guest beforehand, and include the configuration UID in
the secret request using \fB--cuid\fR. Refer to \fBpvsecret-add\fR(1) for more
information. For all certificates, revocation lists, and host-key documents,
information. For all certificates, revocation lists, and host-key documents,
both the PEM and DER input formats are supported.
.SH OPTIONS
@@ -49,20 +49,16 @@ Print version information and exit.
.SH EXAMPLES
.PP
Create the add-secret request on a trusted system. The program generates three
files. \fFaddsecreq.bin\fP contains the add-secret request. \fFTEST.yaml\fP
Create the add-secret request on a trusted system. The program generates two
files. \fFaddsecreq.bin\fP contains the add-secret request. \fEXAMPLE.yaml\fP
contains the non-confidential information about the generated secret. It
contains the name and ID of the secret. \fFTEST\fP contains the plaintext secret
that is encrypted in the request. It can be used to generate add-secret requests
for a different guest with the same secret. Destroy the secret when it is not
used anymore.
contains name and id of the secret.
.PP
.nf
.fam C
trusted:~$ pvsecret create -k hkd.crt --cert CA.crt --cert ibmsk.crt --hdr pvimage -o addsecreq.bin association EXAMPLE
Successfully generated the request
Successfully wrote association info to 'EXAMPLE.yaml'
Successfully wrote generated association secret to 'EXAMPLE'
.fam T
.fi
On the SE-guest, \fIadd\fP the secret from request to the secret store.
@@ -100,4 +96,4 @@ On the SE-guest, \fIlock\fP the secret store.
.fi
.SH "SEE ALSO"
.sp
\fBpvsecret-create\fR(1) \fBpvsecret-add\fR(1) \fBpvsecret-lock\fR(1) \fBpvsecret-list\fR(1) \fBpvsecret-version\fR(1)
\fBpvsecret-create\fR(1) \fBpvsecret-add\fR(1) \fBpvsecret-lock\fR(1) \fBpvsecret-list\fR(1) \fBpvsecret-verify\fR(1)

View File

@@ -4,7 +4,6 @@
use clap::{ArgGroup, Args, CommandFactory, Parser, Subcommand, ValueEnum, ValueHint};
use pv::misc::CertificateOptions;
#[cfg(target_arch = "s390x")]
use pv::misc::STDOUT;
/// Manage secrets for IBM Secure Execution guests.
@@ -84,7 +83,7 @@ pub struct CreateSecretOpt {
/// Use the content of FILE as the Configuration Unique ID.
///
/// The file must contain exactly 128 bit of data, a hex string, or a yaml with a `cuid` entry.
/// The file must contain exactly 128 bit of data or a yaml with a `cuid` entry.
/// If specified, the value must match the Config-UID from the attestation result of that
/// guest. If not specified, the CUID will be ignored by the Ultravisor during the verification
/// of the request.
@@ -113,14 +112,43 @@ pub struct CreateSecretOpt {
value_delimiter = ','
)]
pub flags: Vec<CreateSecretFlags>,
/// Use the content of FILE as user-data.
///
/// Passes user data defined in <FILE> through the add-secret request to the ultravisor. The
/// user data can be up to 512 bytes of arbitrary data, and the maximum size depends on the
/// size of the user-signing key:
/// - No key: user data can be 512 bytes.
/// - EC or RSA 2048 keys: user data can be 256 bytes.
/// - RSA 3072 key: user data can be 128 bytes.
///
/// The firmware ignores this data, but the request tag protects the user-data. Optional. No
/// user-data by default.
#[arg(long, value_name = "FILE", value_hint = ValueHint::FilePath,)]
pub user_data: Option<String>,
/// Use the content of FILE as user signing key.
///
/// Adds a signature calculated from the key in <FILE> to the add-secret request. The
/// file must be in DER or PEM format containing a private key. Supported are RSA 2048 &
/// 3072-bit and EC(secp521r1) keys. The firmware ignores the content, but the request tag protects the
/// signature. The user-signing key signs the request. The location of the signature is filled
/// with zeros during the signature calculation. The request tag also secures the signature.
/// See man pvsecret verify for more details. Optional. No signature by default.
#[arg(long, value_name = "FILE", value_hint = ValueHint::FilePath,)]
pub user_sign_key: Option<String>,
}
#[derive(Subcommand, Debug)]
pub enum AddSecretType {
/// Create a meta secret.
///
/// Use a meta secret to carry flags to the ultravisor without having to provide an actual
/// secret value. Meta secrets do not appear in the list of secrets.
Meta,
/// Create an association secret.
///
/// Use an association secret to connect a trusted I/O device to a guest. The `pvapconfig` tool
/// provides more information about association secrets.
Association {
@@ -132,7 +160,7 @@ pub enum AddSecretType {
///Print the hashed name to stdout.
///
///The hashed name will not be written to `NAME.yaml`
///The hashed name is not written to `NAME.yaml`
#[arg(long)]
stdout: bool,
@@ -185,6 +213,30 @@ pub struct ListSecretOpt {
pub format: ListSecretOutputType,
}
#[derive(Args, Debug)]
pub struct VerifyOpt {
/// Specify the request to be checked.
#[arg(value_name = "FILE", value_hint = ValueHint::FilePath,)]
pub input: String,
/// Certificate containing a public key used to verify the user data signature.
///
/// Specifies a public key used to verify the user-data signature. The file must be a X509
/// certificate in DSA or PEM format. The certificate must hold the public EC, RSA 2048, or RSA
/// 3072 key corresponding to the private user-key used during `create`. No chain of trust is
/// established. Ensuring that the certificate can be trusted is the responsibility of the
/// user. The EC key must use the NIST/SECG curve over a 521 bit prime field (secp521r1).
#[arg(long, value_name = "FILE", value_hint = ValueHint::FilePath,)]
pub user_cert: Option<String>,
/// Store the result in FILE
///
/// If the request contained abirtary user-data the output contains this user-data with padded
/// zeros if available.
#[arg(short, long, value_name = "FILE", default_value = STDOUT, value_hint = ValueHint::FilePath,)]
pub output: String,
}
#[derive(Subcommand, Debug)]
pub enum Command {
/// Create a new add-secret request.
@@ -196,7 +248,7 @@ pub enum Command {
/// the use case depending on the secret type.
Create(Box<CreateSecretOpt>),
/// Repeat an add-secret request (s390x only).
/// Perform an add-secret request (s390x only).
///
/// Perform an add-secret request using a previously generated add-secret request. Only
/// available on s390x.
@@ -214,6 +266,13 @@ pub enum Command {
/// running IBM Secure Execution guest. Only available on s390x.
List(ListSecretOpt),
/// Verify that an add-secret request is sane.
///
/// Verifies that the given request is an add-secret request by testing for some values to be
/// present. If the request contains signed user-data, the signature is verified with the
/// provided key. Outputs the arbitrary user-data.
Verify(VerifyOpt),
/// Print version information and exit.
#[command(aliases(["--version"]), hide(true))]
Version,

View File

@@ -5,6 +5,9 @@
mod create;
pub use create::create;
mod verify;
pub use verify::verify;
// Commands (directly) related to UVCs are only available on s389x
#[cfg(target_arch = "s390x")]
mod add;

View File

@@ -7,10 +7,9 @@ use anyhow::{anyhow, bail, Context, Result};
use log::{debug, info, trace, warn};
use pv::{
misc::{
get_writer_from_cli_file_arg, parse_hex, pv_guest_bit_set, read_certs, read_exact_file,
read_file, try_parse_u128, try_parse_u64,
get_writer_from_cli_file_arg, open_file, parse_hex, pv_guest_bit_set, read_certs,
read_exact_file, read_file, read_private_key, try_parse_u128, try_parse_u64, write,
},
open_buffered_file,
request::{
openssl::pkey::{PKey, Public},
uvsecret::{AddSecretFlags, AddSecretRequest, AddSecretVersion, ExtSecret, GuestSecret},
@@ -21,11 +20,8 @@ use pv::{
use serde_yaml::Value;
fn write_out<D: AsRef<[u8]>>(path: &str, data: D, ctx: &str) -> pv::Result<()> {
let mut wr = match get_writer_from_cli_file_arg(path) {
Ok(it) => it,
Err(err) => return Err(err),
};
pv::misc::write(&mut wr, data, path, ctx)?;
let mut wr = get_writer_from_cli_file_arg(path)?;
write(&mut wr, data, path, ctx)?;
Ok(())
}
@@ -89,7 +85,7 @@ fn build_asrcb(opt: &CreateSecretOpt) -> Result<AddSecretRequest> {
});
debug!("FLAGS: {flags:x?}");
let mut se_hdr = open_buffered_file!(&opt.hdr);
let mut se_hdr = open_file(&opt.hdr)?;
let mut asrcb = AddSecretRequest::new(
AddSecretVersion::One,
secret,
@@ -110,6 +106,27 @@ fn build_asrcb(opt: &CreateSecretOpt) -> Result<AddSecretRequest> {
asrcb.set_ext_secret(ExtSecret::Derived(read_exact_file(path, "CCK")?.into()))?;
}
// add user data
let user_data = opt
.user_data
.as_ref()
.map(|p| read_file(p, "user-data"))
.transpose()?;
if user_data.as_ref().is_some_and(|data| data.is_empty()) {
warn!("Added empty user-data file.");
}
let user_key = opt
.user_sign_key
.as_ref()
.map(|p| read_file(p, "User-signing key"))
.transpose()?
.map(|buf| read_private_key(&buf))
.transpose()?;
if user_data.is_some() || user_key.is_some() {
asrcb.set_user_data(user_data.unwrap_or_default(), user_key)?;
}
Ok(asrcb)
}
@@ -144,7 +161,7 @@ fn try_from_val(val: Value) -> anyhow::Result<ConfigUid> {
.ok_or(anyhow!("No 'cuid' entry found"))?;
let cuid = cuid
.strip_prefix("0x")
.ok_or(anyhow!("Value starts not with 0x".to_string()))?
.ok_or(anyhow!("CUID value starts not with 0x".to_string()))?
.to_owned();
if cuid.len() != ::std::mem::size_of::<ConfigUid>() * 2 {
return Err(anyhow!(format!("len invalid ({})", cuid.len())));
@@ -187,11 +204,18 @@ fn read_and_verify_hkds(
let certs = read_certs(&hk).with_context(|| {
format!("The provided Host Key Document in '{hkd}' is not in PEM or DER format")
})?;
if certs.len() != 1 {
warn!("The host key document in '{hkd}' contains more than one certificate!")
if certs.is_empty() {
let msg = format!(
"The provided host key document in {} contains no certificate!",
hkd
);
return Err(anyhow!(msg));
}
if certs.len() > 1 {
warn!("The host key document in '{hkd}' contains more than one certificate! Only the first certificate will be used.")
}
// len is 1 -> unwrap will succeed
// len is >= 1 -> unwrap will succeed
let c = certs.first().unwrap();
verifier.verify(c)?;
res.push(c.public_key()?);

View File

@@ -0,0 +1,46 @@
use anyhow::{anyhow, Context, Result};
use log::warn;
use pv::{
misc::{get_reader_from_cli_file_arg, get_writer_from_cli_file_arg, read_certs, read_file},
request::{
openssl::pkey::{PKey, Public},
uvsecret::verify_asrcb_and_get_user_data,
},
};
use crate::cli::VerifyOpt;
/// read the content of a DER or PEM x509 and return the public key
fn read_sgn_key(path: &str) -> Result<PKey<Public>> {
read_certs(&read_file(path, "user-signing key")?)?
.get(0)
.ok_or(anyhow!("File does not contain a X509 certificate"))?
.public_key()
.map_err(anyhow::Error::new)
}
pub fn verify(opt: &VerifyOpt) -> Result<()> {
let mut rd_in = get_reader_from_cli_file_arg(&opt.input)?;
let mut data_in = Vec::with_capacity(0x1000);
rd_in
.read_to_end(&mut data_in)
.with_context(|| format!("Cannot read input file {}", opt.input))?;
let verify_cert = opt
.user_cert
.as_ref()
.map(|p| read_sgn_key(p))
.transpose()
.context("Cannot read user-verification certificate.")?;
let user_data = verify_asrcb_and_get_user_data(data_in, verify_cert)
.context("Could not verify the the Add-secret request")?;
if let Some(user_data) = user_data {
get_writer_from_cli_file_arg(&opt.output)?
.write_all(&user_data)
.with_context(|| format!("Cannot write user data to {}", opt.output))?;
}
warn!("Successfully verified the request.");
Ok(())
}

View File

@@ -25,6 +25,7 @@ const FEATURES: &[&str] = &[
"+lock",
#[cfg(target_arch = "s390x")]
"+list",
"+verify",
];
fn print_error(e: anyhow::Error, verbosity: u8) -> ExitCode {
@@ -116,6 +117,7 @@ fn main() -> ExitCode {
Command::Lock => not_supported(),
Command::Create(opt) => cmd::create(opt),
Command::Version => print_version(cli.verbose),
Command::Verify(opt) => cmd::verify(opt),
};
match res {

View File

@@ -27,3 +27,36 @@ macro_rules! release_string {
env!("S390_TOOLS_RELEASE", "env 'S390_TOOLS_RELEASE' must be set for release builds. Trigger build using the s390-tools build system or export the variable yourself")
}};
}
/// Asserts a constant expression evaluates to `true`.
///
/// If the expression is not evaluated to `true` the compilation will fail.
#[macro_export]
macro_rules! static_assert {
($condition:expr) => {
const _: () = core::assert!($condition);
};
}
/// Asserts that a type has a specific size.
///
/// Useful to validate structs that are passed to C code.
/// If the size has not the expected value the compilation will fail.
///
/// # Example
/// ```rust
/// # use utils::assert_size;
/// # fn main() {}
/// #[repr(C)]
/// struct c_struct {
/// v: u64,
/// }
/// assert_size!(c_struct, 8);
/// // assert_size!(c_struct, 7);//won't compile
/// ```
#[macro_export]
macro_rules! assert_size {
($t:ty, $sz:expr ) => {
$crate::static_assert!(::std::mem::size_of::<$t>() == $sz);
};
}

View File

@@ -2,7 +2,7 @@
#
# dbginfo.sh - Tool to collect runtime, configuration, and trace information
#
# Copyright IBM Corp. 2002, 2023
# Copyright IBM Corp. 2002, 2024
#
# s390-tools is free software; you can redistribute it and/or modify
# it under the terms of the MIT license. See LICENSE for details.
@@ -15,6 +15,7 @@ export LC_ALL
########################################
# Global used variables
readonly SCRIPTNAME="${0##*/}" # general name of this script
readonly STARTDIR="$(pwd)" # save calling directory
#
readonly DATETIME="$(date +%Y-%m-%d-%H-%M-%S 2>/dev/null)"
readonly DOCKER=$(if type docker >/dev/null 2>&1; then echo "YES"; else echo "NO"; fi)
@@ -51,6 +52,14 @@ if test "x${PROCESSORVERSION}" = "xFF" || test "x${PROCESSORVERSION}" = "xff"; t
else
RUNTIME_ENVIRONMENT="LPAR"
fi
# check for Dynamic Partition Mode
readonly DPM_UUID=$(grep "LPAR UUID" /proc/sysinfo | \
sed 's/.*:[[:space:]]*\([[:graph:]]*\).*/\1/g')
if test "${#DPM_UUID}" -eq 0; then
DPM_MODE="NO"
else
DPM_MODE="YES"
fi
readonly SYSTEMHOSTNAME="$(hostname -s 2>/dev/null)" # hostname of system being analysed
readonly TERMINAL="$(tty 2>/dev/null)"
# timeout seconds TOS / kill timeout TOKS
@@ -72,7 +81,7 @@ paramWORKDIR_BASE="/tmp" # initial default path
print_version() {
cat <<EOF
${SCRIPTNAME}: Debug information script version %S390_TOOLS_VERSION%
Copyright IBM Corp. 2002, 2023
Copyright IBM Corp. 2002, 2024
EOF
}
@@ -155,7 +164,7 @@ print_check() {
cat <<EOF
Hardware platform = ${HW}
Runtime environment = ${RUNTIME_ENVIRONMENT}
Runtime environment = ${RUNTIME_ENVIRONMENT} - DPM: ${DPM_MODE}
$(cat /proc/sysinfo | grep 'Name')
Kernel version = ${KERNEL_INFO}
OS version / distro = ${OS_NAME}
@@ -274,6 +283,7 @@ ALL_STEPS="\
collect_sysfs\
collect_procfs\
collect_configfiles\
collect_initrd_configfiles\
collect_cmdsout\
collect_hyptop\
collect_vmcmdsout\
@@ -381,14 +391,19 @@ CONFIGFILES="\
/boot/loader/entries/*.conf\
/boot/zipl/active_devices.txt\
/boot/zipl/config\
/conf/\
/etc/*.conf\
/etc/*release\
/etc/anacrontab\
/etc/apparmor.d\
/etc/auto.*\
/etc/cmdline\
/etc/cmdline.d\
/etc/conf.d\
/etc/cron.*\
/etc/crontab\
/etc/crypttab\
/etc/dasd.*\
/etc/default\
/etc/depmod.d\
/etc/dnf/dnf.conf\
@@ -429,12 +444,14 @@ CONFIGFILES="\
/etc/systemd\
/etc/udev*\
/etc/xinet.d\
/etc/zfcp.*\
/lib/modprobe.d\
$(find /lib/modules -name modules.dep 2>/dev/null)\
/lib/systemd/system/docker.service\
/lib/udev/rules.d\
/usr/lib/modprobe.d\
/usr/lib/modules-load.d/*.conf\
/usr/lib/systemd/system\
/usr/lib/systemd/system/\
/usr/local/lib/modprobe.d\
/usr/local/lib/modules-load.d/*.conf\
/run/modprobe.d\
@@ -442,6 +459,7 @@ CONFIGFILES="\
/run/udev/chreiplzfcpmp-ipl-volume-id\
/run/udev/rules.d\
/run/zdev_id.env\
/run/zdev.*\
"
########################################
@@ -904,6 +922,89 @@ collect_configfiles() {
done
}
########################################
collect_initrd_configfiles() {
local file_name
local rd rdbase rdreldir rdextract mypopd noglob mycat patterns=""
pr_collect "initrd config files"
cd "${WORKPATH}" || return
mypopd="$OLDPWD" # (pushd alternative)
# prevent expansion of glob special characters such as '*'
noglob=$(set +o | grep -F noglob)
set -o noglob
# prepare relative configfiles paths
for file_name in ${CONFIGFILES}; do
# strip leading slash to make path relative as in initrd
file_name=${file_name#/}
# in contrast to call_collect_file(), cpio extract with patterns
# does not recurse, but cpio wildcards match slash, so add suffix;
# otherwise e.g. /lib/udev/rules.d or /etc/multipath only extracts
# just that directory without any of its content
file_name="${file_name}*"
patterns="$patterns ${file_name}"
done
patterns="$patterns squash-root.img"
$noglob
# iterate over initrds
for rd in /boot/initr* /boot/zipl/initr* /var/lib/kdump/initr*; do
[ -f "$rd" ] || continue # (nullglob alternative)
rdbase=${rd##*/} # basename
rdreldir=${rd%/*} # dirname
rdreldir=${rdreldir#/} # strip leading slash to make path relative
mkdir -p "${rdreldir}"
cd "${rdreldir}" || break # (pushd alternative)
if type lsinitrd >/dev/null 2>&1; then
lsinitrd "${rd}" > "${rdbase}.lsinitrd"
fi
if type lsinitramfs >/dev/null 2>&1; then
lsinitramfs -l "${rd}" > "${rdbase}.lsinitramfs"
fi
cd - || break # change to $OLDPWD (popd alternative)
rdextract=${rd#/}
mkdir -p "${rdextract}"
cd "${rdextract}" || break # (pushd alternative)
case $(file "${rd}") in
*cpio*) mycat="cat" ;;
*gzip*) mycat="zcat" ;;
*XZ*) mycat="xzcat" ;;
*Zstandard*) mycat="zstdcat" ;;
*bzip2*) mycat="bzcat" ;;
*LZMA*) mycat="lzcat" ;;
*)
# change to $OLDPWD (popd alternative)
cd - || { cd "$mypopd" || return; return; }
continue
;;
esac
# prevent expansion of glob special characters such as '*'
noglob=$(set +o | grep -F noglob)
set -o noglob
# we expect no early-initrd stuff on s390 so extract directly.
# globbing is turned off and we want word split on $patterns:
# shellcheck disable=SC2086
"$mycat" "${rd}" | cpio --extract --no-absolute-filenames \
--make-directories \
--preserve-modification-time $patterns
$noglob
if [ ! -f "squash-root.img" ]; then
cd - || break # change to $OLDPWD (popd alternative)
continue
fi
# prevent expansion of glob special characters such as '*'
noglob=$(set +o | grep -F noglob)
set -o noglob
# globbing is turned off and we want word split on $patterns:
# shellcheck disable=SC2086
unsquashfs -no-progress -dest "squashfs-root" "squash-root.img" \
$patterns
$noglob
rm -f "squash-root.img"
cd - || break # change to $OLDPWD (popd alternative)
done
cd "$mypopd" || return
}
########################################
collect_osaoat() {
local network_devices
@@ -1319,10 +1420,11 @@ environment_setup() {
create_package() {
local rc_tar
pr_syslog_stdout ${step_num} "Finalizing: Creating archive with collected data"
# get a copy of the script used - enabled for relative path calls
cd "${STARTDIR}"
cp -p "${BASH_SOURCE[0]}" "${WORKPATH}"
# create the archive
cd "${WORKDIR_BASE}"
# get a copy of the script used
cp -p $0 "${WORKDIR_CURRENT}"
touch "${WORKARCHIVE}"
chmod 0600 "${WORKARCHIVE}"
tar -czf "${WORKARCHIVE}" "${WORKDIR_CURRENT}"
@@ -1437,7 +1539,7 @@ trap emergency_exit SIGHUP SIGINT SIGTERM
pr_log_stdout ""
pr_log_stdout "Hardware platform = ${HW}"
pr_log_stdout "Runtime environment = ${RUNTIME_ENVIRONMENT}"
pr_log_stdout "Runtime environment = ${RUNTIME_ENVIRONMENT} - DPM: ${DPM_MODE}"
pr_log_stdout "Kernel version = ${KERNEL_INFO} (${KERNEL_BASE})"
pr_log_stdout "OS version / distro = ${OS_NAME}"
pr_log_stdout "Date and time of info = ${DATETIME}"

View File

@@ -1,4 +1,4 @@
.TH DBGINFO.SH 8 "Sep 2022" "s390-tools"
.TH DBGINFO.SH 8 "01 2024" "s390-tools"
.SH NAME
dbginfo.sh \- collect runtime, configuration and trace information
@@ -59,61 +59,63 @@ Sample invocation:
.br
dbginfo.sh: Debug information script version %S390_TOOLS_VERSION%
.br
Copyright IBM Corp. 2002, 2022
Copyright IBM Corp. 2002, 2024
.PP
Hardware platform = s390x
.br
Runtime environment = z/VM
Runtime environment = z/VM - DPM: NO
.br
Kernel version = 4.18.0 (4.18.0-305.el8.s390x)
Kernel version = 5.14.0 (5.14.0-162.6.1.el9_1.s390x)
.br
OS version / distro = Red Hat Enterprise Linux 8.4 (Ootpa)
OS version / distro = Red Hat Enterprise Linux 9.1 (Plow)
.br
Date and time of info = 2021-08-26-15-27-58
Date and time of info = 2023-12-22-11-43-08
.PP
1 of 18: Collecting sysfs
1 of 19: Collecting sysfs
.PP
2 of 18: Collecting procfs
2 of 19: Collecting procfs
.PP
3 of 18: Collecting config files
3 of 19: Collecting config files
.PP
4 of 18: Collecting command output
4 of 19: Collecting initrd config files
.PP
5 of 18: Collecting hyptop for z/VM - 5s output
5 of 19: Collecting command output
.PP
6 of 18: Collecting z/VM output
6 of 19: Collecting hyptop for z/VM - 5s output
.PP
7 of 18: Collecting network output
7 of 19: Collecting z/VM output
.PP
8 of 18: Collecting osa oat output
8 of 19: Collecting network output
.PP
9 of 18: Collecting ethtool output
9 of 19: Collecting osa oat output
.PP
10 of 18: Collecting Trafic Control output
10 of 19: Collecting ethtool output
.PP
11 of 18: Collecting bridge output
11 of 19: Collecting Trafic Control output
.PP
12 of 18: Skip OpenVSwitch: ovs-vsctl not available
12 of 19: Collecting bridge output
.PP
13 of 18: Skip KVM: no virsh command
13 of 19: Skip OpenVSwitch: ovs-vsctl not available
.PP
14 of 18: Collecting container host output
14 of 19: Skip KVM: no virsh command
.PP
15 of 19: Collecting container host output
.PP
Kubernetes ...
.br
15 of 18: Collecting NVME storage output
16 of 19: Collecting NVME storage output
.PP
16 of 18: Collecting log files
17 of 19: Collecting log files
.PP
0 logfiles over 50 MB
.PP
17 of 18: Postprocessing
18 of 19: Postprocessing
.PP
18 of 18: Finalizing: Creating archive with collected data
19 of 19: Finalizing: Creating archive with collected data
.PP
Collected data was saved to:
.br
>> /data\-collection/DBGINFO\-2021\-08\-26\-15\-27\-58\-host\-012345.tgz <<
>> /data\-collection/DBGINFO\-2023\-12\-22\-11\-43\-08\-host\-012345.tgz <<
.br
Please review all collected data before sending to your service organization.
.SH HINTS

View File

@@ -7,11 +7,13 @@
#
[Unit]
Description=Apply Control Program Identification (CPI)
DefaultDependencies=no
Conflicts=shutdown.target
After=sysinit.target
Before=shutdown.target
ConditionPathIsReadWrite=/sys/firmware/cpi
Conflicts=shutdown.target
DefaultDependencies=no
Description=Apply Control Program Identification (CPI)
Requires=sysinit.target
[Service]
Type=oneshot

View File

@@ -8,7 +8,8 @@
#
# 95zdev-kdump/module_setup.sh
# This module installs configuration files (udev rules and modprobe.conf
# files) required to enable the kdump target on s390.
# files) required to enable the kdump target on s390. In addition,
# hooks are installed to parse rd.zfcp= and rd.dasd= kernel parameters.
#
# called by dracut
@@ -16,15 +17,18 @@ check() {
local _arch=${DRACUT_ARCH:-$(uname -m)}
# Ensure that we're running on s390
[ "$_arch" = "s390" -o "$_arch" = "s390x" ] || return 1
[ "$_arch" = "s390" ] || [ "$_arch" = "s390x" ] || return 1
source "$moddir/../95zdev/zdev-lib.sh"
# shellcheck source=/dev/null
source "${moddir:?}/../95zdev/zdev-lib.sh"
# Ensure this module is only included when building kdump initrd
is_kdump || return 1
zdev_is_kdump || return 1
# Ensure that required tools are available
require_binaries chzdev || return 1
require_binaries chzdev lszdev || return 1
require_binaries sed || return 1
require_binaries grep sort uniq || return 1
return 0
}
@@ -34,6 +38,19 @@ depends() {
return 0
}
# called by dracut and (conditionally) locally by install()
# Generate rd.zfcp dracut cmdline options for each zfcp-attached
# SCSI disk in dracut's device dependency graph (to mount the root-fs,
# or to access the kdump target). With "dracut --print-cmdline", dracut
# prints the list. With "dracut --hostonly-cmdline", dracut stores the
# list inside the generated initrd.
cmdline() {
# shellcheck disable=SC2154
if [[ $hostonly ]]; then
for_each_host_dev_and_slaves_all zdev_check_dev | sort | uniq
fi
}
# called by dracut
installkernel() {
# Add modules for all device types supported by chzdev (required for
@@ -50,9 +67,29 @@ install() {
# installing avoids error messages from zdev site udev rule processing
inst_multiple -o /lib/s390-tools/zdev_id
# Ensure that required tools are available
inst_multiple chzdev grep
# Hook to parse zfcp dracut cmdline parameter
inst_hook cmdline 95 "$moddir/../95zdev/parse-zfcp.sh"
# Hook to parse dasd dracut cmdline parameter
# Must be the first one to use chzdev so dasd_mod is not yet loaded.
inst_hook cmdline 94 "$moddir/../95zdev/parse-dasd.sh"
inst_multiple /lib/s390-tools/zdev-from-dasd_mod.dasd
inst_rules "59-dasd.rules"
# Obtain kdump target device configuration
_tempfile=$(mktemp --tmpdir dracut-zdev.XXXXXX)
# shellcheck disable=SC2154
if [[ $hostonly_cmdline == "yes" ]]; then
local _rdsomedev
for _rdsomedev in $(cmdline); do
printf "%s\n" "$_rdsomedev" >> "${initdir:?}/etc/cmdline.d/94zdev.conf"
done
fi
_tempfile=$(mktemp --tmpdir="${DRACUT_TMPDIR}" dracut-zdev.XXXXXX)
# work with systems that are not based on chzdev persistent config
local _configuration="--active"
@@ -60,8 +97,8 @@ install() {
# many zFCP LUNs
inst_dir /etc/modprobe.d
chzdev zfcp --type "allow_lun_scan=0" --persistent \
--base "/etc=$initdir/etc" --yes --quiet --no-root-update \
--force >/dev/null
--base "/etc=${initdir:?}/etc" --yes --no-root-update \
--force 2>&1 | ddebug
# drop /etc/zfcp.conf from dracut module 95zfcp
echo "rd.zfcp.conf=0" > "$initdir/etc/cmdline.d/00-no-zfcp-conf.conf"
# => only activate individual zfcp paths found as required below
@@ -80,22 +117,48 @@ install() {
for_each_host_dev_and_slaves_all check_zdev
sed -i -e 's/^\[active /\[persistent /' "$_tempfile"
# in addition to active devices above, also add persistent config
# of devices the user marked for early initrd use
chzdev --export - --persistent --by-attrib "zdev:early=1" --quiet \
--type 2>/dev/null >> "$_tempfile"
# site support
# Obtain early device configuration for site-specific settings
for (( site=0; site<10; site++ ))
do
chzdev --export - --persistent --by-attrib "zdev:early=1" --site $site \
--quiet 2>/dev/null >> "$_tempfile"
done
ddebug < "$_tempfile"
# Apply via --import to prevent other devices from being configured
chzdev --import "$_tempfile" --persistent --base "/etc=$initdir/etc" \
--yes --quiet --no-root-update --force >/dev/null
--yes --no-root-update --force 2>&1 | ddebug
# Apply site-specific configurations via --import
for (( site=0; site<10; site++ ))
do
chzdev --import "$_tempfile" --persistent --base "/etc=$initdir/etc" \
--site $site --yes --no-root-update --force 2>&1 | ddebug
done
lszdev --configured --persistent --info \
--base "/etc=$initdir/etc" 2>&1 | ddebug
rm -f "$_tempfile"
# these are purely generated udev rules so we have to glob expand
# within $initdir and strip the $initdir prefix for mark_hostonly
local -a _array
# shellcheck disable=SC2155
local _nullglob=$(shopt -p nullglob)
shopt -u nullglob
readarray -t _array < \
<(ls -1 $initdir/etc/udev/rules.d/41-*.rules 2> /dev/null)
<(ls -1 "$initdir"/etc/udev/rules.d/41-*.rules 2> /dev/null)
[[ ${#_array[@]} -gt 0 ]] && mark_hostonly "${_array[@]#$initdir}"
readarray -t _array < \
<(ls -1 $initdir/etc/modprobe.d/s390x-*.conf 2> /dev/null)
<(ls -1 "$initdir"/etc/modprobe.d/s390x-*.conf 2> /dev/null)
[[ ${#_array[@]} -gt 0 ]] && mark_hostonly "${_array[@]#$initdir}"
$_nullglob

View File

@@ -1,6 +1,6 @@
#!/bin/bash
#
# Copyright IBM Corp. 2016, 2017
# Copyright IBM Corp. 2016, 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.
@@ -10,52 +10,100 @@
# This module installs configuration files (udev rules and modprobe.conf
# files) required to enable the root device on s390. It will only work when
# the root device was configured using the chzdev tool. In addition,
# a hook is installed to parse rd.zdev= kernel parameters.
# hooks are installed to parse rd.zdev= and rd.zfcp= and rd.dasd=
# kernel parameters.
#
# called by dracut
check() {
local _arch=$(uname -m)
local _arch
_arch=$(uname -m)
# Ensure that we're running on s390
[ "$_arch" = "s390" -o "$_arch" = "s390x" ] || return 1
[ "$_arch" = "s390" ] || [ "$_arch" = "s390x" ] || return 1
source "$moddir/zdev-lib.sh"
# shellcheck source=/dev/null
source "${moddir:?}/zdev-lib.sh"
# Leave kdump device configuration to module zdev-kdump to
# ensure a minimal device footprint
is_kdump && return 1
zdev_is_kdump && return 1
# Ensure that required tools are available
require_binaries chzdev lszdev /lib/s390-tools/zdev_id || return 1
require_binaries sed || return 1
require_binaries grep sort uniq || return 1
return 0
}
# called by dracut
depends() {
return 0
}
# called by dracut and (conditionally) locally by install()
# Generate rd.zfcp dracut cmdline options for each zfcp-attached
# SCSI disk in dracut's device dependency graph (to mount the root-fs,
# or to access the kdump target). With "dracut --print-cmdline", dracut
# prints the list. With "dracut --hostonly-cmdline" [the case where
# install() calls cmdline()], dracut stores the list inside the generated
# initrd.
cmdline() {
# shellcheck disable=SC2154
if [[ $hostonly ]]; then
for_each_host_dev_and_slaves_all zdev_check_dev | sort | uniq
fi
}
# called by dracut
installkernel() {
# Add modules for all device types supported by chzdev (required for
# auto-configuration)
hostonly="$(optional_hostonly)" \
instmods ctcm lcs qeth qeth_l2 qeth_l3 dasd_mod dasd_eckd_mod dasd_fba_mod \
dasd_diag_mod zfcp
}
# called by dracut
install() {
local _tempfile
# Ensure that required tools are available
inst_multiple chzdev lszdev vmcp /lib/s390-tools/zdev_id
inst_multiple grep
# Hook to parse zdev kernel parameter
inst_hook cmdline 95 "$moddir/parse-zdev.sh"
# Hook to parse zfcp dracut cmdline parameter
inst_hook cmdline 95 "$moddir/parse-zfcp.sh"
# Hook to parse dasd dracut cmdline parameter
# Must be the first one to use chzdev so dasd_mod is not yet loaded.
inst_hook cmdline 94 "$moddir/parse-dasd.sh"
inst_multiple /lib/s390-tools/zdev-from-dasd_mod.dasd
inst_hook pre-pivot 95 "$moddir/retain-zdev.sh"
# Rule to automatically enable devices when running in DPM
inst_rules "81-dpm.rules"
inst_rules "59-dasd.rules"
# Obtain early + root device configuration
_tempfile=$(mktemp --tmpdir dracut-zdev.XXXXXX)
# shellcheck disable=SC2154
if [[ $hostonly_cmdline == "yes" ]]; then
local _rdsomedev
for _rdsomedev in $(cmdline); do
printf "%s\n" "$_rdsomedev" >> "${initdir:?}/etc/cmdline.d/94zdev.conf"
done
fi
# If enabled, add the host-specific config of required devices into initrd
# shellcheck disable=SC2154
[[ $hostonly ]] || return 0
_tempfile=$(mktemp --tmpdir="${DRACUT_TMPDIR}" dracut-zdev.XXXXXX)
function check_zdev() {
local _dev=$1
local _devsysfs _bdevpath
@@ -63,10 +111,13 @@ install() {
cd -P /sys/dev/block/"$_dev" && echo "$PWD"
)
_bdevpath=/dev/${_devsysfs##*/}
chzdev --export - --persistent --by-node "$_bdevpath" --quiet \
--type 2>/dev/null >> "$_tempfile"
chzdev --export - --active --by-node "$_bdevpath" --quiet \
2>/dev/null >> "$_tempfile"
#--type # needs a change in chzdev to not have subsequent
# import bail out on unknown type properties
}
for_each_host_dev_and_slaves_all check_zdev
sed -i -e 's/^\[active /\[persistent /' "$_tempfile"
chzdev --export - --persistent --by-attrib "zdev:early=1" --quiet \
--type 2>/dev/null >> "$_tempfile"
@@ -78,17 +129,36 @@ install() {
--quiet 2>/dev/null >> "$_tempfile"
done
ddebug < "$_tempfile"
# Apply via --import to prevent other devices from being configured
chzdev --import "$_tempfile" --persistent --base "/etc=$initdir/etc" \
--yes --quiet --no-root-update --force >/dev/null
chzdev --import "$_tempfile" --persistent --base "/etc=${initdir:?}/etc" \
--yes --no-root-update --force 2>&1 | ddebug
# Apply site-specific configurations via --import
for (( site=0; site<10; site++ ))
do
chzdev --import "$_tempfile" --persistent --base "/etc=$initdir/etc" \
--site $site --yes --quiet --no-root-update --force >/dev/null
--site $site --yes --no-root-update --force 2>&1 | ddebug
done
lszdev --configured --persistent --info \
--base "/etc=$initdir/etc" 2>&1 | ddebug
rm -f "$_tempfile"
# these are purely generated udev rules so we have to glob expand
# within $initdir and strip the $initdir prefix for mark_hostonly
local -a _array
local _nullglob
_nullglob=$(shopt -p nullglob)
shopt -u nullglob
readarray -t _array < \
<(ls -1 "$initdir"/etc/udev/rules.d/41-*.rules 2> /dev/null)
[[ ${#_array[@]} -gt 0 ]] && mark_hostonly "${_array[@]#$initdir}"
readarray -t _array < \
<(ls -1 "$initdir"/etc/modprobe.d/s390x-*.conf 2> /dev/null)
[[ ${#_array[@]} -gt 0 ]] && mark_hostonly "${_array[@]#$initdir}"
$_nullglob
return 0
}

View File

@@ -0,0 +1,37 @@
#!/bin/sh
#
# Copyright IBM Corp. 2023
#
# s390-tools is free software; you can redistribute it and/or modify
# it under the terms of the MIT license. See LICENSE for details.
#
# 95zdev/parse-dasd.sh
# Parse the command line for rd.dasd parameters. These
# parameters are evaluated and used to configure dasd devices.
#
# shellcheck source=/dev/null
type zdev_parse_dasd_list > /dev/null 2>&1 || . /lib/s390-tools/zdev-from-dasd_mod.dasd
# at this point in time dracut's vinfo() only logs to journal which is hard for
# s390 users to find and access on a line mode console such as 3215 mode
# so use a vinfo alternative that still prints to the console via kmsg
zdev_vinfo() {
local _zdev_vinfo_line
while read -r _zdev_vinfo_line || [ -n "$_zdev_vinfo_line" ]; do
# Prefix "<30>" represents facility LOG_DAEMON 3 and loglevel INFO 6:
# (facility << 3) | level.
echo "<30>dracut: $_zdev_vinfo_line" > /dev/kmsg
done
}
zdev_parse_rd_dasd() {
local _zdev_dasd _zdev_dasd_list
for _zdev_dasd in $(getargs rd.dasd -d 'rd_DASD='); do
_zdev_dasd_list="${_zdev_dasd_list:+${_zdev_dasd_list},}$_zdev_dasd"
done
echo "$_zdev_dasd_list"
}
zdev_parse_rd_dasd | zdev_parse_dasd_list globals 2>&1 | zdev_vinfo
zdev_parse_rd_dasd | zdev_parse_dasd_list ranges 2>&1 | zdev_vinfo

View File

@@ -37,6 +37,7 @@ for zdev_arg in $(getargs rd.zdev); do
done
if [ $zdev_auto -eq 1 ] ; then
# shellcheck disable=SC2086
chzdev --import "$zdev_fw_file" $zdev_base_args
# Get information about DPM environment
@@ -50,7 +51,7 @@ if [ $zdev_auto -eq 1 ] ; then
# work for PCI devices defined before boot because there is no coldplug
# trigger for /sys/bus/pci/slots
for slot in /sys/bus/pci/slots/* ; do
read power < "$slot/power"
read -r power < "$slot/power"
if [ "$power" = "0" ] ; then
echo 1 > "$slot/power"
fi

View File

@@ -0,0 +1,36 @@
#!/bin/sh
#
# Copyright IBM Corp. 2023
#
# s390-tools is free software; you can redistribute it and/or modify
# it under the terms of the MIT license. See LICENSE for details.
#
# 95zdev/parse-zfcp.sh
# Parse the command line for rd.zfcp parameters. These
# parameters are evaluated and used to configure zfcp devices.
#
zdev_zfcp_base_args="--no-settle --yes --no-root-update --force"
for zdev_zfcp_arg in $(getargs rd.zfcp -d 'rd_ZFCP='); do
(
IFS_SAVED="$IFS"
IFS="," # did not work in front of built-in set command below
# shellcheck disable=SC2086
set -- $zdev_zfcp_arg
IFS=":" args="$*"
IFS="$IFS_SAVED"
echo "rd.zfcp ${zdev_zfcp_arg} :" | zdev_vinfo
if [ "$#" -eq 1 ]; then
# shellcheck disable=SC2086
chzdev --enable --persistent $zdev_zfcp_base_args \
zfcp-host "$args" 2>&1 | zdev_vinfo
else
# shellcheck disable=SC2086
chzdev --enable --persistent $zdev_zfcp_base_args \
zfcp-lun "$args" 2>&1 | zdev_vinfo
fi
)
done
unset zdev_zfcp_arg
unset zdev_zfcp_base_args

View File

@@ -0,0 +1,26 @@
#!/bin/sh
#
# Copyright IBM Corp. 2023
#
# s390-tools is free software; you can redistribute it and/or modify
# it under the terms of the MIT license. See LICENSE for details.
#
# 95zdev/retain-zdev.sh
# Copy zdev persistent config from initrd to root-fs.
#
chzdev --export /run/zdev.initrd.config --all --type --persistent --quiet
# Apart from debugging purposes, this is useful for distro installers,
# which can import the early config into their own environment early
# after starting:
# chzdev --import /run/zdev.initrd.config --persistent --yes --no-root-update --force --verbose
#
# After that, distro installers can modify/add the device config based
# on interactive or unattended installation choices using
# chzdev --enable --active --persistent ...
#
# Finally, distro installers can likewise transfer the entire device config
# to the installed system mounted under $SYSROOT:
# chzdev --export /tmp/zdev.config --all --type --persistent --verbose
# chzdev --import /tmp/zdev.config --persistent --yes --no-root-update --force --verbose --base "$SYSROOT"

View File

@@ -10,16 +10,101 @@
#
# shellcheck shell=bash
is_kdump() {
zdev_is_kdump() {
# https://src.fedoraproject.org/rpms/kexec-tools/c/4eedcae5e1540690a3761857fe2e692774c44960
# https://src.fedoraproject.org/rpms/kexec-tools/blob/rawhide/f/mkdumprd
# https://src.fedoraproject.org/rpms/kexec-tools/blob/rawhide/f/dracut-module-setup.sh
# shellcheck disable=SC2154
if [[ $hostonly && "$hostonly_mode" == "strict" && -n "$IN_KDUMP" ]]; then
return 0
fi
# https://github.com/openSUSE/kdump/blob/master/dracut/module-setup.sh
# shellcheck disable=SC2154
if [[ " $dracutmodules $add_dracutmodules $force_add_dracutmodules " == *\ kdump\ * ]]; then
return 0
fi
return 1
}
zdev_zfcp_auto_lun_scan_active() {
local _fcpdevsysfs=$1
local _porttype _lunscan
read -r _porttype < "$_fcpdevsysfs"/host*/fc_host/host*/port_type
read -r _lunscan < /sys/module/zfcp/parameters/allow_lun_scan
if [[ "$_porttype" == "NPIV VPORT" && "$_lunscan" == "Y" ]]; then
return 0
fi
return 1
}
# zdev_check_dev() can be extended for other device types such as PCIe
zdev_check_dev() {
local _dev=$1
local _devsysfs _devtype _subsystem _driver
local _intlun _fcplun _scsitarget _wwpn _hbaid
local _busid _arg
_devsysfs=$(
cd -P /sys/dev/block/"$_dev" 2> /dev/null && echo "$PWD"
)
# This is roughly what systemd's udev-builtin-path_id does:
while [[ -n "$_devsysfs" ]]; do
# ascend to parent: strip last path part
_devsysfs=${_devsysfs%/*}
_subsystem=$(
cd -P "$_devsysfs"/subsystem 2> /dev/null && echo "$PWD"
)
if [[ "${_subsystem##*/}" == "scsi" ]]; then
_devtype=$(grep "^DEVTYPE=" "$_devsysfs"/uevent)
# check for FCP LUN
if [[ "$_devtype" == "DEVTYPE=scsi_device" ]]; then
_intlun=${_devsysfs##*:}
# convert _intlun to _fcplun [int_to_scsilun()]
_fcplun=0
((_fcplun |= (_intlun & 0x000000000000ffff) << 48))
((_fcplun |= (_intlun & 0x00000000ffff0000) << 16))
((_fcplun |= (_intlun & 0x0000ffff00000000) >> 16))
((_fcplun |= (_intlun & 0xffff000000000000) >> 48))
printf -v _fcplun "0x%016x" "$_fcplun"
# bail out if not scsi_transport_fc
[[ "${_devsysfs/*rport-*/FOUND}" == "FOUND" ]] || return 0
continue
fi
# check for target WWPN
if [[ "$_devtype" == "DEVTYPE=scsi_target" ]]; then
_scsitarget=${_devsysfs##*/}
read -r _wwpn < "$_devsysfs/fc_transport/$_scsitarget/port_name"
continue
fi
fi
if [[ "${_subsystem##*/}" == "ccw" ]]; then
_driver=$(
cd -P "$_devsysfs"/driver 2> /dev/null && echo "$PWD"
)
# check for FCP device (vHBA) bus-ID
if [[ "${_driver##*/}" == "zfcp" ]]; then
_hbaid=${_devsysfs##*/}
# drop full path and use zfcp auto LUN scan, if:
# - not building for kdump which has zfcp auto LUN scan off
# - and zfcp auto LUN scan is available
if ! zdev_is_kdump && \
zdev_zfcp_auto_lun_scan_active "$_devsysfs"; then
unset _wwpn
unset _fcplun
fi
if [[ -n "$_wwpn" ]] && [[ -n "$_fcplun" ]]; then
printf " rd.zfcp=%s,%s,%s\n" "$_hbaid" "$_wwpn" "$_fcplun"
else
printf " rd.zfcp=%s\n" "$_hbaid"
fi
break
fi
# check for DASD device bus-ID
if [[ "${_driver##*/}" == dasd-* ]]; then
_busid=${_devsysfs##*/}
_arg=$(/lib/s390-tools/zdev-to-dasd_mod.dasd "active" "$_busid")
printf " rd.dasd=%s\n" "$_arg"
break
fi
fi
done
}

View File

@@ -21,6 +21,9 @@ install:
$(INSTALL) -m 755 $(ZDEVDIR)/module-setup.sh \
$(ZDEVDIR)/parse-zdev.sh \
$(ZDEVDIR)/zdev-lib.sh \
$(ZDEVDIR)/parse-zfcp.sh \
$(ZDEVDIR)/parse-dasd.sh \
$(ZDEVDIR)/retain-zdev.sh \
$(DESTDIR)$(DRACUTMODDIR)/$(ZDEVDIR)/
$(INSTALL) -m 755 -d $(DESTDIR)$(DRACUTMODDIR)/$(ZDEVKDUMPDIR)
$(INSTALL) -m 755 $(ZDEVKDUMPDIR)/module-setup.sh \

View File

@@ -571,7 +571,7 @@ portion is replaced with
.IR VALUE .
.B Example:
.CL chzdev -dasd-eckd 1000 -e -p --base /etc=/mnt/etc
.CL chzdev dasd-eckd 1000 -e -p --base /etc=/mnt/etc
.PP
.
.OD dry-run "" ""

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