Compare commits

..

43 Commits

Author SHA1 Message Date
Jan Höppner
f85c8ebba0 New release s390-tools-2.43.0
Signed-off by: Jan Höppner <hoeppner@linux.ibm.com>
2026-06-25 14:49:06 +02:00
Marc Hartmayer
e4e455630b pv + tools: Introduce versioned HostKey and Keyslot enums
Add a HostKey enum (currently V1(PKey<Public>)) and introduce a
versioned Keyslot enum (V1(KeyslotV1)). Rename the existing Keyslot type
to KeyslotV1 to prepare for future format extensions.

Update pv, pvattest, pvimg, and pvsecret to use the new enums.

Reviewed-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Timo Keller <tkeller@linux.ibm.com>
Signed-off-by: Marc Hartmayer <marc@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2026-06-25 14:14:45 +02:00
Timo Keller
d82beef937 pv: Implement Display for SymKey based on key_type()
Add a Display implementation for SymKey that formats values as
`SymKey(<type>)`, delegating to key_type(). This provides a clearer
and stable human-readable representation for logs and CLI output.

Reviewed-by: Marc Hartmayer <marc@linux.ibm.com>
Reviewed-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Timo Keller <tkeller@linux.ibm.com
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2026-06-25 14:14:44 +02:00
Marc Hartmayer
c3e61e5e0f pv: from_se_image: Return SE header version in addition to tags
Return the Secure Execution header version in addition to tags. This
comes handy as soon as multiple Secure Execution header versions are
supported.

Reviewed-by: Jan Höppner <hoeppner@linux.ibm.com>
Signed-off-by: Timo Keller <tkeller@linux.ibm.com>
Signed-off-by: Marc Hartmayer <marc@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2026-06-25 14:14:44 +02:00
Marc Hartmayer
f04e46baf0 pv: req: Improve documentation and fix typos
Improve documentation and fix typos.

Reviewed-by: Jan Höppner <hoeppner@linux.ibm.com>
Reviewed-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Marc Hartmayer <marc@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2026-06-25 14:14:44 +02:00
Marc Hartmayer
e93bd7e61c pv: req: Split into multiple files
Split req.rs into multiple files so that additions can be done more
easily.

Assisted-by: IBM Bob:1.0.4
Reviewed-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Marc Hartmayer <marc@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2026-06-25 14:14:44 +02:00
Timo Keller
edf40976f0 pv: Add OpenSSL ML-KEM bindings
Add interfaces for creating an ML-KEM keypair and encapsulation and
decapsulation.

The public part of it can be used to encapsulate a shared secret in a
ciphertext. The latter can be decapsulated with the private part of the
keypair to recover the shared secret.

In terms of code, a keypair of type `PKey<Private>` can be generated
with `generate_ml_kem` specifying `KeyType::ML_KEM_512`,
`KeyType::ML_KEM_768`, or `KeyType::ML_KEM_1024`.

To encapsulate a shared secret, generate a `PkeyCtx<Public>` with
`PkeyCtx::new`, initialize it with `encapsulate_init`, and encapsulate
with `encapsulate_to_vec`.

To decapsulate the ciphertext to the shared secret, generate a
`PkeyCtx<Private>` with `PkeyCtx::new`, initialize it with
`decapsulate_init`, and decapsulate with `decapsulate_to_vec`.

Note that when https://github.com/rust-openssl/rust-openssl/pull/2532
is getting merged into `rust-openssl`, these changes will become
unnecessary.

Assisted-by: IBM Bob:1.0.4
Reviewed-by: Marc Hartmayer <marc@linux.ibm.com>
Acked-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Timo Keller <tkeller@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2026-06-25 14:14:44 +02:00
Marc Hartmayer
e557f41c6e pvimg: Unignore a Rust doctest
While at it, improve it.

Reviewed-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Marc Hartmayer <marc@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2026-06-25 14:14:44 +02:00
Marc Hartmayer
68fe816da9 pvimg: Add more CCK tests
Add more CCK tests to make sure that new changes do not introduce a
regression.

Assisted-by: IBM Bob:1.0.4
Reviewed-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Marc Hartmayer <marc@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2026-06-25 14:14:44 +02:00
Marc Hartmayer
367ca6e16b pvimg: Add comment why cck and not cck-available is required
Add a comment to the code to explain why cck and not cck-available is
required.

Reviewed-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Marc Hartmayer <marc@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2026-06-25 14:14:44 +02:00
Volkan Unal
6c67f05ca7 dasdinfo: Fix memory leak in dinfo_get_uid_from_devnode()
The function dinfo_get_uid_from_devnode() allocates memory for
readbuf but fails to free it in two code paths:
1. When the device name is truncated (error path)
2. At the successful function exit

Add the missing free(readbuf) calls to prevent memory leaks in
both paths.

Reviewed-by: Jan Höppner <hoeppner@linux.ibm.com>
Signed-off-by: Volkan Unal <vunal@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2026-06-25 14:14:44 +02:00
Jörn Siglen
4887e80995 dbginfo.sh: Add IBM appliance specific files
Some IBM appliance has additional config data /etc/fpc files or directory

Reviewed-by: Mario Held <mario.held@de.ibm.com>
Signed-off-by: Jörn Siglen <siglen@de.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2026-06-25 14:14:44 +02:00
Steffen Eiden
74a338edfc pvattest: Expand HTTPS timeout to 2 min
Match the clients timeout to the servers timeout.

Reviewed-by: Marc Hartmayer <marc@linux.ibm.com>
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2026-06-25 14:14:44 +02:00
Steffen Eiden
8878ba4fd2 pvattest: Add firmware check version 2
Add Response version 2 which includes more details about the
verification process.

Reviewed-by: Marc Hartmayer <marc@linux.ibm.com>
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2026-06-25 14:14:44 +02:00
Steffen Eiden
e53f5ccfea pvattest: Refactor firmware checking
Refactor the firmware verification client such that adding a new
request/response versions is simpler.

Reviewed-by: Marc Hartmayer <marc@linux.ibm.com>
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2026-06-25 14:14:44 +02:00
Jan Polensky
6e7eb62ea1 cpumf/lshwc: Show explicitly selected unnamed counters with --hide
When an unnamed counter is explicitly selected by numeric ID via
--counters (for example, --counters=218), show it even when --hide is
enabled.

Keep --hide effective for unnamed counters that were not explicitly
selected. To do that, evaluate the counter filter once per counter and
reuse the result to distinguish between a generic match and an explicit
numeric-ID match.

Suggested-by: Christian Borntraeger <borntraeger@linux.ibm.com>
Reviewed-by: Jan Höppner <hoeppner@linux.ibm.com>
Signed-off-by: Jan Polensky <japo@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2026-06-25 14:14:44 +02:00
Eduard Shishkin
2bb61f96c0 zipl/src: Make use of newly introduced verbosity classes/levels
Assign VERBOSITY_DEBUG class to the message printing information
about zipl helpers called by zipl tool.

Reviewed-by: Peter Oberparleiter <oberpar@linux.ibm.com>
Reviewed-by: Stefan Haberland <sth@linux.ibm.com>
Signed-off-by: Eduard Shishkin <edward6@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2026-06-25 14:14:43 +02:00
Eduard Shishkin
a902fd9afe zipl/src: Introduce verbosity levels of zipl session
Introduce verbosity levels of zipl session and verbosity classes of
messages. This is used by the next patches in the series to suppress
information not corresponding the default output of zipl tool that
could be confusing for user.

Add a new option "--debug" of zipl tool to set up verbosity level 2
(and higher) of zipl session.

Reviewed-by: Peter Oberparleiter <oberpar@linux.ibm.com>
Reviewed-by: Stefan Haberland <sth@linux.ibm.com>
Signed-off-by: Eduard Shishkin <edward6@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2026-06-25 14:14:43 +02:00
Finn Callies
dc42460d82 scripts/pvics: Fix virt-resize permission error
Although the script is to be run with root privileges virt-resize starts
as user qemu under the hood which depending on image location may cause
permission errors. To fix this use `LIBGUESTFS_BACKEND=direct`.

Reviewed-by: Holger Dengler <dengler@linux.ibm.com>
Signed-off-by: Finn Callies <fcallies@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2026-06-25 14:14:43 +02:00
Marc Hartmayer
6689e25865 rust: Fix all cargo clippy findings
- Remove useless type conversion in uvdevice.rs
- Replace useless comparison in hostname.rs
- Replace unnecessary unwrap patterns in pvapconfig
- Use sort_by_key instead of sort_by in pvimg example

Command line used to get the findings:

  $ clippy --all-features -- --cap-lints=warn
  warning: useless conversion to the same type: `u64`
    --> pv_core/src/uvdevice.rs:56:28
     |
  56 |         rc = ioctl(raw_fd, cmd.try_into().unwrap(), cb.as_ptr_mut());
     |                            ^^^^^^^^^^^^^^
     |
     = help: consider removing `.try_into()`
     = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#useless_conversion
     = note: `#[warn(clippy::useless_conversion)]` on by default

  warning: this comparison involving the minimum or maximum element for this type contains a case that is always true or always false
    --> utils/src/hostname.rs:60:13
     |
  60 |     assert!(isize::try_from(buf_len).unwrap() <= isize::MAX);
     |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
     |
     = help: because `isize::MAX` is the maximum value for this type, this comparison is always true
     = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#absurd_extreme_comparisons
     = note: `#[warn(clippy::absurd_extreme_comparisons)]` on by default

  warning: `utils` (lib) generated 1 warning
      Checking pvebc v0.12.0 (/home/mhartmay/git/s390-tools/rust/pvebc)
  warning: consider using `sort_unstable_by_key`
     --> pvapconfig/src/ap.rs:177:9
      |
  177 |         self.0.sort_unstable_by(|a, b| b.gen.cmp(&a.gen));
      |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
      |
      = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_sort_by
      = note: `#[warn(clippy::unnecessary_sort_by)]` on by default
  help: try
      |
  177 -         self.0.sort_unstable_by(|a, b| b.gen.cmp(&a.gen));
  177 +         self.0.sort_unstable_by_key(|b| std::cmp::Reverse(b.gen));

  warning: called `unwrap_err` on `r` after checking its variant with `is_err`
    --> pvapconfig/src/main.rs:55:29
     |
  54 |         if $r.is_err() {
     |         -------------- help: try: `if let Err(<item>) = r`
  55 |             eprintln!("{}", $r.unwrap_err());
     |                             ^^^^^^^^^^^^^^^
  ...
  87 |     on_error_print_and_exit!(r);
     |     --------------------------- in this macro invocation
     |
     = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_unwrap
     = note: this warning originates in the macro `on_error_print_and_exit` (in Nightly builds, run with -Z macro-backtrace for more info)

  warning: this `repeat().take()` can be written more concisely
    --> pvimg/src/se_img_comps/bootloader/ipl.rs:95:21
     |
  95 |           let comps = iter::repeat(ipl_pb0_pv_comp::default())
     |  _____________________^
  96 | |             .take(num_comp)
     | |___________________________^ help: consider using `repeat_n()` instead: `std::iter::repeat_n(ipl_pb0_pv_comp::default(), num_comp)`
     |
     = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#manual_repeat_n
     = note: `#[warn(clippy::manual_repeat_n)]` on by default

  warning: this `repeat().take()` can be written more concisely
     --> pvimg/src/se_img_comps/bootloader/ipl.rs:113:21
      |
  113 |         let comps = iter::repeat(comp).take(num_comp).collect();
      |                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `repeat_n()` instead: `std::iter::repeat_n(comp, num_comp)`
      |
      = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#manual_repeat_n

Reviewed-by: Timo Keller <tkeller@linux.ibm.com>
Reviewed-by: Steffen Eiden <seiden@linux.ibm.com>
Reviewed-by: Harald Freudenberger <freude@linux.ibm.com>
Signed-off-by: Marc Hartmayer <marc@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2026-06-25 14:14:43 +02:00
Marc Hartmayer
20f6791fb4 pvapconfig: Suppress unnecessary unwrap warning
The current code is easier to read as the suggested fix by Clippy.
Therefore adding directives to allow the unwrap.

  warning: called `unwrap` on `a1.info` after checking its variant with `is_some`
     --> pvapconfig/src/ap.rs:195:36
      |
  192 |                     && a1.info.is_some()
      |                        ----------------- the check is happening here
  ...
  195 |                     let i1 = match a1.info.as_ref().unwrap() {
      |                                    ^^^^^^^^^^^^^^^^^^^^^^^^^
      |
      = help: try using `match`
      = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_unwrap
      = note: `#[warn(clippy::unnecessary_unwrap)]` on by default

  warning: called `unwrap` on `a2.info` after checking its variant with `is_some`
     --> pvapconfig/src/ap.rs:199:36
      |
  193 |                     && a2.info.is_some()
      |                        ----------------- the check is happening here
  ...
  199 |                     let i2 = match a2.info.as_ref().unwrap() {
      |                                    ^^^^^^^^^^^^^^^^^^^^^^^^^
      |
      = help: try using `match`
      = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_unwrap

Suggested-by: Harald Freudenberger <freude@linux.ibm.com>
Reviewed-by: Steffen Eiden <seiden@linux.ibm.com>
Reviewed-by: Harald Freudenberger <freude@linux.ibm.com>
Signed-off-by: Marc Hartmayer <marc@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2026-06-25 14:14:43 +02:00
Marc Hartmayer
f0a148fd0b rust: Fix cargo clippy false positives
Add allow attributes for intentional patterns in pvverify and pvsecret.

Command line used to get the findings:

  $ clippy --all-features -- --cap-lints=warn
  ...
  help: remove this field
    --> pvverify/src/cli.rs:22:5
     |
  22 |     version: (),
     |     ^^^^^^^^^^^
     = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#manual_non_exhaustive
     = note: `#[warn(clippy::manual_non_exhaustive)]` on by default
  help: use the `#[non_exhaustive]` attribute instead
     |
  16 + #[non_exhaustive]
  17 | pub struct CliOptions {
     |

  warning: struct `AddSecretOptComb` is never constructed
     --> pvsecret/src/cli.rs:313:12
      |
  313 | pub struct AddSecretOptComb<'a> {
      |            ^^^^^^^^^^^^^^^^
      |
      = note: `-W dead-code` implied by `-W unused`
      = help: to override `-W unused` add `#[expect(dead_code)]` or `#[allow(dead_code)]`

  warning: struct `ListSecretOptComb` is never constructed
     --> pvsecret/src/cli.rs:355:12
      |
  355 | pub struct ListSecretOptComb<'a> {
      |            ^^^^^^^^^^^^^^^^^

  warning: struct `RetrSecretOptionsComb` is never constructed
     --> pvsecret/src/cli.rs:490:12
      |
  490 | pub struct RetrSecretOptionsComb<'a> {
      |            ^^^^^^^^^^^^^^^^^^^^^

Assisted-by: IBM Bob:1.0.4
Reviewed-by: Timo Keller <tkeller@linux.ibm.com>
Reviewed-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Marc Hartmayer <marc@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2026-06-25 14:14:43 +02:00
Marc Hartmayer
390e48869e pvebc: Fix cargo doc warning
$ cargo doc
...
warning: unclosed HTML tag `u8`
  --> pvebc/src/ebc_utils.rs:25:43
   |
25 | /// Read all data from a reader into a Vec<u8>
   |                                           ^^^^
   |
   = note: `#[warn(rustdoc::invalid_html_tags)]` on by default
help: try marking as source code
   |
25 | /// Read all data from a reader into a `Vec<u8>`
   |                                        +       +

Reviewed-by: Jan Höppner <hoeppner@linux.ibm.com>
Signed-off-by: Marc Hartmayer <marc@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2026-06-25 14:14:43 +02:00
Finn Callies
3635a19462 pvebc: Log services to journal+console
Change StandardOutput=console to StandardOutput=journal+console and
StandardError=console to StandardError=journal+console respectively for
the pvebc dracut module systemd services.

Fixes: https://github.com/ibm-s390-linux/s390-tools/issues/205
Fixes: 3d679f61f ("pvebc: Disable unit logging to /boot")
Reviewed-by: Holger Dengler <dengler@linux.ibm.com>
Signed-off-by: Finn Callies <fcallies@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2026-06-22 16:43:03 +02:00
Ingo Franzki
584e785f5c zkey: Adjust the error message when calculating the KVP fails
The 'paes' and 'phmac' kernel ciphers and the respective kernel modules
are no longer used for calculating the key verification pattern. Instead,
the 'pkey' kernel module and its sub modules is used now.

In case the 'pkey' kernel module is not available, an appropriate error
message is already printed by open_pkey_device() when opening the device.

Reviewed-by: Finn Callies <fcallies@linux.ibm.com>
Signed-off-by: Ingo Franzki <ifranzki@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2026-06-22 16:43:03 +02:00
Ingo Franzki
7fffdcfe8c zkey: Remove the use of AF_ALG for calculating key verification patterns
Instead of using AF_ALG to calculate key verification patterns, transform
the key blob into a protected key and calculate the key verification
patterns with CAPCF calls.

The 'zkey-cryptsetup convert' command also calculates key verification
patterns from clear keys. Support this, too.

Reviewed-by: Finn Callies <fcallies@linux.ibm.com>
Signed-off-by: Ingo Franzki <ifranzki@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2026-06-22 16:43:03 +02:00
Ingo Franzki
44d6020774 zkey: Pass /dev/pkey file descriptor through to all functions
To be able to calculate the key verification pattern of a key blob without
the use of AF_ALG, the file descriptor of the /dev/pkey device needs to
be passed through to all functions that might need to calculate the key
verification pattern.

Also make sure that all commands that might need to calculate the key
verification pattern have 'need_pkey_device = 1' so that the device is
opened.

Reviewed-by: Finn Callies <fcallies@linux.ibm.com>
Signed-off-by: Ingo Franzki <ifranzki@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2026-06-22 16:43:03 +02:00
Ingo Franzki
30ce11b8da zkey: Add helper function to transform a key blob into a protected key
To be able to calculate the key verification pattern of a key blob without
the use of AF_ALG, the key blob needs to be transformed into a protected
key via the PKEY_KBLOB2PROTK3 ioctl.

Reviewed-by: Finn Callies <fcallies@linux.ibm.com>
Signed-off-by: Ingo Franzki <ifranzki@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2026-06-22 16:43:03 +02:00
Ingo Franzki
c7ab70df2c zkey: Fix possible out of bounds access with malformed property files
When reading a properties file with a line containing a binary zero as first
character, an array access at index -1 is attempted, because strlen considers
this as an empty string (len = 0). The following array access 'line[len - 1]'
accesses the array at index -1.

Fix this by checking the line length and skip empty lines.

Reviewed-by: Finn Callies <fcallies@linux.ibm.com>
Signed-off-by: Ingo Franzki <ifranzki@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2026-06-22 16:43:03 +02:00
Ingo Franzki
c953486dec zkey-cryptsetup: Fix handling of 'hmac(sha512)' integrity algorithm
Fix a copy & paste error on handling the 'hmac(sha512)' integrity algorithm
with the 'convert' command.

Fixes: 97136585da ("zkey-cryptsetup: Support PHMAC integrity with convert command")
Reviewed-by: Finn Callies <fcallies@linux.ibm.com>
Signed-off-by: Ingo Franzki <ifranzki@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2026-06-22 16:43:03 +02:00
Ingo Franzki
610cc581da zkey: Fix file size checking in read_secure_key()
Key files read by read_secure_key() may not be just a key blob, but it
might also contain an additional integrity key blob.

Correct the upper bounds checking and allow up to 3 key blobs to be read.
This allows for an AES-XTS key blob consisting of 2 key blobs plus an
integrity key blob concatenated to the encryption key blob.

Fixes: 771c794580 ("zkey-cryptsetup: Support PHMAC integrity with setkey command")
Reviewed-by: Finn Callies <fcallies@linux.ibm.com>
Signed-off-by: Ingo Franzki <ifranzki@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2026-06-22 16:43:02 +02:00
Ingo Franzki
604c34e66b libkmipclient: Fix 'assignment discards 'const' qualifier' warnings
Fix compile warnings like

   warning: assignment discards 'const' qualifier from pointer target type
                                                   [-Wdiscarded-qualifiers]

by declaring the variables as const.

Reviewed-by: Finn Callies <fcallies@linux.ibm.com>
Signed-off-by: Ingo Franzki <ifranzki@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2026-06-22 16:43:02 +02:00
Ingo Franzki
e2cd2e78c5 libekmfweb: Fix 'assignment discards 'const' qualifier' warnings
Fix compile warnings like

   warning: assignment discards 'const' qualifier from pointer target type
                                                   [-Wdiscarded-qualifiers]

by declaring the variables as const.

Reviewed-by: Finn Callies <fcallies@linux.ibm.com>
Signed-off-by: Ingo Franzki <ifranzki@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2026-06-22 16:43:02 +02:00
Ingo Franzki
d04d331673 zkey/kmip: Fix 'assignment discards 'const' qualifier' warnings
Fix compile warnings like

   warning: assignment discards 'const' qualifier from pointer target type
                                                   [-Wdiscarded-qualifiers]

by declaring the variables as const or cast appropriately.

Reviewed-by: Finn Callies <fcallies@linux.ibm.com>
Signed-off-by: Ingo Franzki <ifranzki@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2026-06-22 16:43:02 +02:00
Ingo Franzki
27f20495a7 zkey: Fix 'assignment discards 'const' qualifier' warnings
Fix compile warnings like

   warning: assignment discards 'const' qualifier from pointer target type
                                                   [-Wdiscarded-qualifiers]

by declaring the variables as const or cast appropriately.

Reviewed-by: Finn Callies <fcallies@linux.ibm.com>
Signed-off-by: Ingo Franzki <ifranzki@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2026-06-22 16:43:02 +02:00
Stephen Gallagher
679ab77b96 Use EVP_PKEY_id() for OpenSSL 1.1 detection
The previous function, EVP_PKEY_meth_remove(), was deprecated in OpenSSL
3.0 and fully removed in OpenSSL 4.0. As a result, it fails detection of
OpenSSL 1.1+ if OpenSSL 4 is present.

Closes: https://github.com/ibm-s390-linux/s390-tools/pull/204
[seiden@linux.ibm.com: Add link to GH PR]
Signed-off-by: Stephen Gallagher <sgallagh@redhat.com>
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2026-06-22 16:43:02 +02:00
Dan Horák
8bb20be528 ebc: implement --version option for pvics
For consistency reasons implement the --version option handling for the new
pvics tool.

Closes: https://github.com/ibm-s390-linux/s390-tools/pull/203
[seiden@linux.ibm.com: Add link to GH PR]
Signed-off-by: Dan Horák <dan@danny.cz>
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>
2026-06-22 16:43:02 +02:00
Jan Höppner
d04ea8603d rust: Upgrade MSRV and dependencies
Bump MSRV to 1.85.0 and upgrade all dependencies using 'cargo upgrade'
from cargo-edit. The bump to 1.85.0 is requiered to get at least openssl
version 0.10.79 including fixes for a couple security issues.

Link: https://crates.io/crates/cargo-edit
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2026-06-22 16:43:02 +02:00
Steffen Eiden
51d2c92067 rust: Remove non_ascii_idents lint
This lint also covers dependencies that generate code i.e. derives.
Those dependencies might introduce non-ascii chars deliberately as e.g.
zerocopy 0.8.32+ does.

Remove it to avoid any confusing warnings regarding non-ascii chars.

Link: https://github.com/rust-lang/rust/issues/151025
[hoeppner@linux.ibm.com: Add link to reported issue]
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2026-06-22 16:43:02 +02:00
Marc Hartmayer
7e3b8b8c16 pvinfo: Remove comments after formatting the code
Remove them as they are pointless.

Suggested-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Marc Hartmayer <marc@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2026-06-22 16:43:02 +02:00
Marc Hartmayer
f8fb9ce32a rust: Run rustfmt with some experimental options
+ Sort and group the imports
+ Normalize and format comments (100 characters width)

Command used:

$ cargo +nightly fmt --

Acked-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Marc Hartmayer <marc@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2026-06-22 16:43:02 +02:00
Marc Hartmayer
3d5f75d6c3 rustfmt.toml: Enable additional formatting options
Enable more settings, including some that are currently unstable. Prefer
options that are also used by the Rust project itself [1].

[1] 64a965e901/rustfmt.toml (L5-L6)

Acked-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Marc Hartmayer <marc@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2026-06-22 16:43:02 +02:00
Jan Höppner
ddc8248b21 Prepare for next release
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2026-05-22 16:34:46 +02:00
186 changed files with 4837 additions and 2313 deletions

View File

@@ -1,13 +1,12 @@
edition = "2021"
newline_style = "Unix"
# Unstable options that help catching some mistakes in formatting and that we may want to enable
# when they become stable.
#
# They are kept here since they are useful to run from time to time.
#comment_width = 100
#format_code_in_doc_comments = true
#group_imports = "StdExternalCrate"
#normalize_comments = true
# Unstable options that help catching some mistakes in formatting
group_imports = "StdExternalCrate"
imports_granularity = "Module"
use_field_init_shorthand = true
comment_width = 100
format_code_in_doc_comments = true
normalize_comments = true
wrap_comments = true
#reorder_impl_items = true
#wrap_comments = true

View File

@@ -77,7 +77,6 @@ List of all individuals having contributed content to s390-tools
- Jean-Baptiste Joret
- Jens Remus
- Jochen Roehrig
- Joern Siglen
- Juergen Christ
- Julian Wiedmann
- Jörn Siglen
@@ -135,6 +134,7 @@ List of all individuals having contributed content to s390-tools
- Steffen Eiden
- Steffen Maier
- Steffen Thoss
- Stephen Gallagher
- Sumanth Korikkar
- Susanne Wintenberger
- Sven Schnelle
@@ -148,6 +148,7 @@ List of all individuals having contributed content to s390-tools
- Thomas Spatzier
- Thomas Weber
- Thorsten Winkler
- Timo Keller
- Tobias Huschle
- Tuan Hoang
- Ursula Braun

View File

@@ -1,6 +1,24 @@
Release history for s390-tools (MIT version)
--------------------------------------------
* __v2.43.0 (2026-06-25)__
For Linux kernel version: 7.1
s390-tools: Set Rust MSRV to 1.85.0
Changes of existing tools:
- dbginfo.sh: Add IBM appliance specific files
- lshwc: Show explicitly selected unnamed counters with --hide
- pvattest: Add firmware check version 2
- zipl: Introduce verbosity levels of zipl session (--debug)
- zkey: Remove the use of AF_ALG for calculating key verification patterns
Bug Fixes:
- ebc: implement --version option for pvics
- pvebc: Log services to journal+console
- pvics: Fix virt-resize permission error
* __v2.42.1 (2026-05-22)__
For Linux kernel version: 7.0

View File

@@ -32,8 +32,8 @@ endif
# The variable "DISTRELEASE" should be overwritten in rpm spec files with:
# "make DISTRELEASE=%{release}" and "make install DISTRELEASE=%{release}"
VERSION := 2
RELEASE := 42
PATCHLEVEL := 1
RELEASE := 43
PATCHLEVEL := 0
DISTRELEASE := build-$(shell date +%Y%m%d)
S390_TOOLS_RELEASE := $(VERSION).$(RELEASE).$(PATCHLEVEL)-$(DISTRELEASE)
export S390_TOOLS_RELEASE

View File

@@ -406,6 +406,38 @@ static void output_times(struct time_formats date)
}
}
struct ctr_filter_match {
bool selected;
bool selected_by_id;
};
static struct ctr_filter_match ctr_match(const char *name, const char *list,
size_t id)
{
struct ctr_filter_match match = { .selected = !list };
char *copy, *token;
char id_str[32];
if (!list)
return match;
snprintf(id_str, sizeof(id_str), "%zu", id);
copy = util_strdup(list);
for (token = strtok(copy, ","); token; token = strtok(NULL, ",")) {
if (name && strcmp(token, name) == 0) {
match.selected = true;
break;
}
if (strcmp(token, id_str) == 0) {
match.selected = true;
match.selected_by_id = true;
break;
}
}
free(copy);
return match;
}
static void prepare_counter(size_t id, unsigned long value)
{
if (output_format == FMT_CSV) {
@@ -438,9 +470,14 @@ static void output_per_cpu(struct time_formats date)
util_fmt_obj_start(FMT_LIST, "counters");
}
for (size_t i = 0; i < ARRAY_SIZE(ctrname); ++i) {
struct ctr_filter_match match;
if (!ctrname[i].hitcnt)
continue;
if (hideundef && !ctrname[i].name)
match = ctr_match(ctrname[i].name, ctrlist, i);
if (!match.selected)
continue;
if (hideundef && !ctrname[i].name && !match.selected_by_id)
continue;
prepare_counter(i, ctrname[i].ccv[h]);
}
@@ -462,9 +499,14 @@ static void output_total(struct time_formats date)
util_fmt_obj_start(FMT_LIST, "counters");
}
for (size_t i = 0; i < ARRAY_SIZE(ctrname); ++i) {
struct ctr_filter_match match;
if (!ctrname[i].hitcnt)
continue;
if (hideundef && !ctrname[i].name)
match = ctr_match(ctrname[i].name, ctrlist, i);
if (!match.selected)
continue;
if (hideundef && !ctrname[i].name && !match.selected_by_id)
continue;
prepare_counter(i, ctrname[i].total);
ctrname[i].total = 0;

View File

@@ -495,6 +495,7 @@ static int dinfo_get_uid_from_devnode(char **uidfile, char *devnode)
fprintf(stderr,
"Error: Device name was truncated\n");
free(path);
free(readbuf);
return -1;
}
@@ -504,6 +505,7 @@ static int dinfo_get_uid_from_devnode(char **uidfile, char *devnode)
closedir(directory);
free(path);
free(readbuf);
return 0;
}

View File

@@ -102,6 +102,9 @@
_x > _y ? _x : _y; \
})
#ifdef __SIZEOF_INT128__
typedef unsigned __int128 u128 __aligned(16);
#endif
typedef unsigned long long u64;
typedef signed long long s64;
typedef unsigned int u32;

View File

@@ -43,7 +43,7 @@ detect-openssl-version.dep:
echo " #error openssl version 1.1 is required" >> $(TMPFILE)
echo "#endif" >> $(TMPFILE)
echo "static void __attribute__((unused)) test(void) {" >> $(TMPFILE)
echo " EVP_PKEY_meth_remove(NULL);" >> $(TMPFILE)
echo " EVP_PKEY_id(NULL);" >> $(TMPFILE)
echo "}" >> $(TMPFILE)
mv $(TMPFILE) $@

View File

@@ -229,8 +229,9 @@ int parse_json_web_token(const char *token, json_object **header_obj,
size_t *signature_len)
{
json_object *hdr = NULL, *pld = NULL, *b64_obj = NULL;
char *ch, *header, *payload, *json = NULL;
size_t header_len, payload_len, json_len;
const char *ch, *header, *payload;
char *json = NULL;
bool b64 = true;
int rc = 0;
@@ -254,7 +255,7 @@ int parse_json_web_token(const char *token, json_object **header_obj,
* and the following parts (if any) are used for JWS Signature, or JWE
* Encryption (not considered here).
*/
header = (char *)token;
header = token;
ch = strchr(token, '.');
if (ch == NULL) {
@@ -653,7 +654,7 @@ int verify_json_web_signature(const char *jws, const unsigned char *payload,
BIGNUM *bn_s = NULL;
const char *alg;
size_t sig_len;
char *ch;
const char *ch;
if (jws == NULL || pkey == NULL)
return -EINVAL;

View File

@@ -46,7 +46,7 @@ detect-openssl-version.dep:
echo " #error openssl version 1.1 is required" >> $(TMPFILE)
echo "#endif" >> $(TMPFILE)
echo "static void __attribute__((unused)) test(void) {" >> $(TMPFILE)
echo " EVP_PKEY_meth_remove(NULL);" >> $(TMPFILE)
echo " EVP_PKEY_id(NULL);" >> $(TMPFILE)
echo "}" >> $(TMPFILE)
mv $(TMPFILE) $@

View File

@@ -1370,7 +1370,8 @@ int kmip_connection_get_server_cert(const char *server,
struct kmip_conn_config config = { 0 };
struct kmip_connection *conn = NULL;
int rc, numcerts, i, port_found = 0;
char *hostname = NULL, *tok, *tok2;
const char *tok, *tok2;
char *hostname = NULL;
STACK_OF(X509) *chain;
bool do_verify = true;
FILE *fp = NULL;

View File

@@ -28,7 +28,7 @@ detect-openssl-version.dep:
echo " #error openssl version 1.1 is required" >> $(TMPFILE)
echo "#endif" >> $(TMPFILE)
echo "static void __attribute__((unused)) test(void) {" >> $(TMPFILE)
echo " EVP_PKEY_meth_remove(NULL);" >> $(TMPFILE)
echo " EVP_PKEY_id(NULL);" >> $(TMPFILE)
echo "}" >> $(TMPFILE)
mv $(TMPFILE) $@

577
rust/Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -17,12 +17,11 @@ resolver = "2"
[workspace.package]
edition = "2021"
license = "MIT"
rust-version = "1.75.0"
rust-version = "1.85.0"
[workspace.lints.rust]
missing_docs = { level = "deny", priority = 1 }
missing_debug_implementations = "warn"
non_ascii_idents = "warn"
nonstandard-style = "warn"
trivial_numeric_casts = "warn"
unstable_features = "warn"

View File

@@ -6,15 +6,15 @@ license.workspace = true
rust-version.workspace = true
[dependencies]
anyhow = "1.0.95"
clap = { version = "4.5", features = ["derive"] }
anyhow = "1.0.102"
clap = { version = "4.6", features = ["derive"] }
libc = "0.2"
serde = { version = "1.0.217", features = ["derive"] }
serde = { version = "1.0.228", features = ["derive"] }
serde_json = "1.0"
utils = { path = "../utils" }
zerocopy = { version="0.8", features = ["derive"] }
[build-dependencies]
cc = "1.2"
clap = { version ="4.5", features = ["derive"]}
clap = { version ="4.6", features = ["derive"]}
lazy_static = "1.5"

View File

@@ -7,11 +7,11 @@ mod msa;
mod query;
mod stfle;
use anyhow::bail;
use anyhow::Result as anyhowRes;
use clap::Parser;
use std::io::ErrorKind;
use std::result::Result::Ok;
use anyhow::{bail, Result as anyhowRes};
use clap::Parser;
use utils::print_version;
use crate::cli::{Cli, Format};

View File

@@ -2,11 +2,13 @@
//
// Copyright IBM Corp. 2024
use crate::cli::Cli;
use core::fmt::{Display, Formatter, Result};
use serde::{Serialize, Serializer};
use utils::HexSlice;
use crate::cli::Cli;
/// Number of total function codes (0 to 127)
pub const NUMBER_FUNC_CODES: usize = 128;

View File

@@ -3,15 +3,13 @@
// Copyright IBM Corp. 2024
use std::fs::File;
use std::io::Error;
use std::io::Read;
use std::io::{Error, Read};
use std::ops::Index;
use std::result::Result;
use zerocopy::FromBytes;
use crate::msa::InstructionKind;
use crate::msa::QueryAuthInfo;
use crate::msa::{InstructionKind, QueryAuthInfo};
/// Path to sysfs in which the query and qai informations are fetched from
const SYSFS_PATH: &str = "/sys/devices/system/cpu/cpacf/";

View File

@@ -15,14 +15,14 @@ workspace = true
[dependencies]
byteorder = "1.5"
curl = "0.4.47"
curl = "0.4.49"
enum_dispatch = "0.3.13"
foreign-types = "0.3.2"
log = { version = "0.4.25", features = ["std", "release_max_level_debug"] }
openssl = "0.10.70"
openssl-sys = "0.9.105"
serde = { version = "1.0.217", features = ["derive"] }
thiserror = "2.0.11"
log = { version = "0.4.29", features = ["std", "release_max_level_debug"] }
openssl = "0.10.80"
openssl-sys = "0.9.116"
serde = { version = "1.0.228", features = ["derive"] }
thiserror = "2.0.18"
zerocopy = { version="0.8", features = ["derive"] }
pv_core = { path = "../pv_core", package = "s390_pv_core", version = "0.12.0" }

View File

@@ -1,13 +1,12 @@
#![allow(missing_docs)]
use pv::request::EcPubKeyCoord;
use pv_core::misc::encode_hex;
use s390_pv as pv;
use std::env::args;
use pv::misc::{read_certs, read_file};
use pv::request::EcPubKeyCoord;
use pv::{Error, Result};
use pv_core::misc::encode_hex;
use s390_pv as pv;
fn main() -> Result<()> {
let hkd = args().nth(1).expect("Expect one Host-key document");

View File

@@ -2,10 +2,9 @@
//
// Copyright IBM Corp. 2023, 2024
use std::{
io::{Read, Seek, SeekFrom::Current},
mem::size_of,
};
use std::io::SeekFrom::Current;
use std::io::{Read, Seek};
use std::mem::size_of;
use log::{debug, warn};
use zerocopy::{BigEndian, FromBytes, Immutable, IntoBytes, KnownLayout, U32, U64};
@@ -13,6 +12,13 @@ use zerocopy::{BigEndian, FromBytes, Immutable, IntoBytes, KnownLayout, U32, U64
// (SE) boot request control block aka SE header
use crate::{assert_size, request::MagicValue, static_assert, Error, Result, PAGESIZE};
/// Version of the Secure Execution header
#[derive(Debug, PartialEq)]
pub enum SeHdrVersion {
/// Secure Execution header v1
One = 0x100,
}
/// Struct containing all SE-header tags.
///
/// Contains:
@@ -206,7 +212,7 @@ impl BootHdrTags {
///
/// This function will return an error if the header could not be found in
/// `img` or is invalid.
pub fn from_se_image<R>(img: &mut R) -> Result<Self>
pub fn from_se_image<R>(img: &mut R) -> Result<(Self, SeHdrVersion)>
where
R: Read + Seek,
{
@@ -233,10 +239,13 @@ impl BootHdrTags {
};
// Some sanity checks
if hdr_head.version.get() != 0x100 {
debug!("Unsupported hdr-version: {:0>4x}", hdr_head.version.get());
return Err(Error::InvBootHdr);
}
let hdr_version = match hdr_head.version.get() {
0x100 => SeHdrVersion::One,
_ => {
debug!("Unsupported hdr-version: {:0>4x}", hdr_head.version.get());
return Err(Error::InvBootHdr);
}
};
// go to the Boot header tag
img.seek(Current(
@@ -249,12 +258,15 @@ impl BootHdrTags {
let mut tag = [0u8; BootHdrHead::TAG_SIZE];
img.read_exact(tag.as_mut_slice())?;
Ok(Self {
pld: hdr_head.pld,
ald: hdr_head.ald,
tld: hdr_head.tld,
tag,
})
Ok((
Self {
pld: hdr_head.pld,
ald: hdr_head.ald,
tld: hdr_head.tld,
tag,
},
hdr_version,
))
}
}
@@ -320,7 +332,7 @@ mod tests {
fn from_se_image_hdr() {
let bin_hdr = get_test_asset!("exp/secure_guest.hdr");
let hdr_tags = BootHdrTags::from_se_image(&mut Cursor::new(*bin_hdr)).unwrap();
assert_eq!(hdr_tags, EXP_HDR);
assert_eq!(hdr_tags, (EXP_HDR, SeHdrVersion::One));
}
#[test]
@@ -356,18 +368,19 @@ mod tests {
let bin_hdr = get_test_asset!("exp/secure_guest.hdr");
img[0x12000..0x12280].copy_from_slice(bin_hdr);
let hdr_tags = BootHdrTags::from_se_image(&mut Cursor::new(img)).unwrap();
assert_eq!(hdr_tags, EXP_HDR);
assert_eq!(hdr_tags, (EXP_HDR, SeHdrVersion::One));
}
#[test]
fn tags_convert_u8() {
let bin_hdr = get_test_asset!("exp/secure_guest.hdr");
let hdr_tags = BootHdrTags::from_se_image(&mut Cursor::new(*bin_hdr)).unwrap();
let ser: &[u8] = hdr_tags.as_ref();
let ser: &[u8] = hdr_tags.0.as_ref();
let mut ser = ser.to_vec();
let der: BootHdrTags = ser.clone().try_into().unwrap();
assert_eq!(hdr_tags, der);
assert_eq!(hdr_tags.0, der);
assert_eq!(hdr_tags.1, SeHdrVersion::One);
ser.pop();
let der: Result<BootHdrTags> = ser.clone().try_into();
@@ -380,7 +393,7 @@ mod tests {
}
#[test]
fn se_img_metadata() {
fn se_img_metadata_v1() {
let metadata = SeImgMetaData::new_v1(0x14000, 0x16000);
let data = [
83, 101, 73, 109, 103, 76, 110, 120, 0, 0, 0, 0, 0, 1, 64, 0, 0, 0, 0, 1, 0, 0, 0, 0,

View File

@@ -2,25 +2,28 @@
//
// Copyright IBM Corp. 2023, 2024
use std::{convert::TryInto, fmt::Display, ops::Range};
use std::convert::TryInto;
use std::fmt::Display;
use std::ops::Range;
use enum_dispatch::enum_dispatch;
use openssl::{
derive::Deriver,
ec::{EcGroup, EcKey},
hash::{DigestBytes, MessageDigest},
md::MdRef,
nid::Nid,
pkey::{HasPublic, Id, PKey, PKeyRef, Private, Public},
pkey_ctx::{HkdfMode, PkeyCtx},
rand::rand_bytes,
rsa::Padding,
sign::{Signer, Verifier},
symm::{decrypt_aead as openssl_decrypt_aead, encrypt_aead as openssl_encrypt_aead, Cipher},
use openssl::derive::Deriver;
use openssl::ec::{EcGroup, EcKey};
use openssl::hash::{DigestBytes, MessageDigest};
use openssl::md::MdRef;
use openssl::nid::Nid;
use openssl::pkey::{HasPublic, Id, PKey, PKeyRef, Private, Public};
use openssl::pkey_ctx::{HkdfMode, PkeyCtx};
use openssl::rand::rand_bytes;
use openssl::rsa::Padding;
use openssl::sign::{Signer, Verifier};
use openssl::symm::{
decrypt_aead as openssl_decrypt_aead, encrypt_aead as openssl_encrypt_aead, Cipher,
};
use pv_core::request::Confidential;
use crate::{error::Result, Error};
use crate::error::Result;
use crate::Error;
/// An AES256-GCM key that will purge itself out of the memory when going out of scope
pub type Aes256GcmKey = Confidential<[u8; SymKeyType::AES_256_GCM_KEY_LEN]>;
@@ -159,6 +162,12 @@ impl SymKey {
}
}
impl Display for SymKey {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "SymKey({:?})", self.key_type())
}
}
/// Performs an hkdf according to RFC 5869.
/// See [`OpenSSL HKDF`]()
///
@@ -540,7 +549,8 @@ pub(crate) fn verify_signature<T: HasPublic>(
#[cfg(test)]
mod tests {
use super::*;
use crate::{get_test_asset, test_utils::*, PvCoreError};
use crate::test_utils::*;
use crate::{get_test_asset, PvCoreError};
#[test]
fn sign_ec() {

View File

@@ -63,13 +63,11 @@ pub mod uv {
pub mod attest {
pub use pv_core::attest::*;
pub use crate::uvattest::{
additional::AdditionalData,
arcb::{
AttestationAuthenticated, AttestationFlags, AttestationRequest, AttestationVersion,
},
attest::{AttestationItems, AttestationMeasurement},
pub use crate::uvattest::additional::AdditionalData;
pub use crate::uvattest::arcb::{
AttestationAuthenticated, AttestationFlags, AttestationRequest, AttestationVersion,
};
pub use crate::uvattest::attest::{AttestationItems, AttestationMeasurement};
}
/// Definitions and functions to write objects in PEM format
@@ -91,27 +89,22 @@ pub use crate::error::HkdVerifyErrorType;
/// Functionalities to build UV requests
pub mod request {
pub use crate::{
brcb::{seek_se_hdr_start, BootHdrTags, SeImgMetaData},
crypto::{
decrypt_aead, derive_aes256_gcm_key, encrypt_aead, gen_ec_key, random_array,
AeadDecryptionResult, AeadEncryptionResult, Aes256GcmKey, Aes256XtsKey, SymKey,
SymKeyType, SHA_512_HASH_LEN,
},
req::{EcPubKeyCoord, Encrypt, Keyslot, ReqEncrCtx, Request},
verify::{CertVerifier, HkdVerifier, NoVerifyHkd},
pub use crate::brcb::{seek_se_hdr_start, BootHdrTags, SeImgMetaData};
pub use crate::crypto::{
decrypt_aead, derive_aes256_gcm_key, encrypt_aead, gen_ec_key, random_array,
AeadDecryptionResult, AeadEncryptionResult, Aes256GcmKey, Aes256XtsKey, SymKey, SymKeyType,
SHA_512_HASH_LEN,
};
pub use crate::req::{EcPubKeyCoord, Encrypt, HostKey, Keyslot, ReqEncrCtx, Request};
pub use crate::verify::{CertVerifier, HkdVerifier, NoVerifyHkd};
/// Reexports some useful OpenSSL symbols
pub mod openssl {
pub use openssl::{
error::ErrorStack,
hash::DigestBytes,
nid::Nid,
pkey,
sha::{Sha256, Sha512},
x509,
};
pub use openssl::error::ErrorStack;
pub use openssl::hash::DigestBytes;
pub use openssl::nid::Nid;
pub use openssl::sha::{Sha256, Sha512};
pub use openssl::{pkey, x509};
// rust-OpenSSL does not define these NIDs
#[allow(missing_docs)]
pub const NID_ED25519: Nid = Nid::from_raw(openssl_sys::NID_ED25519);
@@ -120,7 +113,6 @@ pub mod request {
}
pub use pv_core::request::*;
pub use pv_core::PolicyReference;
}
@@ -128,11 +120,9 @@ pub mod request {
pub mod secret {
pub use pv_core::secret::*;
pub use crate::uvsecret::{
asrcb::{AddSecretFlags, AddSecretRequest, AddSecretVersion},
ext_secret::ExtSecret,
guest_secret::GuestSecret,
retr_secret::{IbmProtectedKey, RetrievedSecret},
user_data::verify_asrcb_and_get_user_data,
};
pub use crate::uvsecret::asrcb::{AddSecretFlags, AddSecretRequest, AddSecretVersion};
pub use crate::uvsecret::ext_secret::ExtSecret;
pub use crate::uvsecret::guest_secret::GuestSecret;
pub use crate::uvsecret::retr_secret::{IbmProtectedKey, RetrievedSecret};
pub use crate::uvsecret::user_data::verify_asrcb_and_get_user_data;
}

View File

@@ -2,11 +2,11 @@
//
// Copyright IBM Corp. 2023
use std::ffi::c_int;
use std::fmt;
use foreign_types::{foreign_type, ForeignType, ForeignTypeRef};
use openssl::x509::{X509CrlRef, X509Ref};
use std::ffi::c_int;
mod ffi {
extern "C" {
@@ -99,9 +99,8 @@ impl AkidExtension for X509CrlRef {
#[cfg(test)]
mod test {
use crate::test_utils::load_gen_cert;
use super::*;
use crate::test_utils::load_gen_cert;
#[test]
fn akid() {

View File

@@ -3,10 +3,12 @@
// Copyright IBM Corp. 2024
use core::slice;
use std::ffi::c_int;
use std::marker::PhantomData;
use std::ptr;
use openssl::error::ErrorStack;
use openssl_sys::BIO_new_mem_buf;
use std::ffi::c_int;
use std::{marker::PhantomData, ptr};
pub struct BioMem(*mut openssl_sys::BIO);

View File

@@ -1,16 +1,13 @@
// SPDX-License-Identifier: MIT
//
// Copyright IBM Corp. 2023
pub use crate::openssl_extensions::stackable_crl::*;
use foreign_types::{ForeignType, ForeignTypeRef};
use openssl::{
error::ErrorStack,
stack::{Stack, StackRef},
x509::{
store::{X509StoreBuilderRef, X509StoreRef},
X509CrlRef, X509NameRef, X509Ref, X509StoreContextRef, X509,
},
};
use openssl::error::ErrorStack;
use openssl::stack::{Stack, StackRef};
use openssl::x509::store::{X509StoreBuilderRef, X509StoreRef};
use openssl::x509::{X509CrlRef, X509NameRef, X509Ref, X509StoreContextRef, X509};
pub use crate::openssl_extensions::stackable_crl::*;
pub fn opt_to_ptr<T: ForeignTypeRef>(o: Option<&T>) -> *mut T::CType {
match o {

View File

@@ -0,0 +1,595 @@
// SPDX-License-Identifier: MIT
//
// Copyright IBM Corp.
#![expect(unused)]
use std::ffi::CStr;
use std::ptr::NonNull;
use foreign_types::ForeignType;
use openssl::error::ErrorStack;
use openssl::pkey::{KeyType, PKey, Private};
use openssl::pkey_ctx::PkeyCtx;
// automatically generated by rust-bindgen 0.69.5
mod ffi {
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct evp_pkey_ctx_st {
_unused: [u8; 0],
}
pub type EvpPkeyCtx = evp_pkey_ctx_st;
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct ossl_param_st {
pub key: *const ::std::os::raw::c_char,
pub data_type: ::std::os::raw::c_uint,
pub data: *mut ::std::os::raw::c_void,
pub data_size: usize,
pub return_size: usize,
}
pub type OsslParam = ossl_param_st;
extern "C" {
pub fn EVP_PKEY_CTX_new_from_name(
libctx: *mut ::std::os::raw::c_void,
name: *const ::std::os::raw::c_char,
propquery: *const ::std::os::raw::c_char,
) -> *mut EvpPkeyCtx;
}
extern "C" {
pub fn EVP_PKEY_keygen_init(ctx: *mut EvpPkeyCtx) -> ::std::os::raw::c_int;
}
extern "C" {
pub fn EVP_PKEY_generate(
ctx: *mut EvpPkeyCtx,
ppkey: *mut *mut ::std::os::raw::c_void,
) -> ::std::os::raw::c_int;
}
extern "C" {
pub fn EVP_PKEY_CTX_free(ctx: *mut EvpPkeyCtx);
}
extern "C" {
pub fn EVP_PKEY_encapsulate_init(
ctx: *mut EvpPkeyCtx,
params: *const OsslParam,
) -> ::std::os::raw::c_int;
}
extern "C" {
pub fn EVP_PKEY_encapsulate(
ctx: *mut EvpPkeyCtx,
wrappedkey: *mut ::std::os::raw::c_uchar,
wrappedkeylen: *mut usize,
genkey: *mut ::std::os::raw::c_uchar,
genkeylen: *mut usize,
) -> ::std::os::raw::c_int;
}
extern "C" {
pub fn EVP_PKEY_decapsulate_init(
ctx: *mut EvpPkeyCtx,
params: *const OsslParam,
) -> ::std::os::raw::c_int;
}
extern "C" {
pub fn EVP_PKEY_decapsulate(
ctx: *mut EvpPkeyCtx,
unwrapped: *mut ::std::os::raw::c_uchar,
unwrappedlen: *mut usize,
wrapped: *const ::std::os::raw::c_uchar,
wrappedlen: usize,
) -> ::std::os::raw::c_int;
}
}
const ML_KEM_512_NAME: &CStr = c"ML-KEM-512";
const ML_KEM_768_NAME: &CStr = c"ML-KEM-768";
const ML_KEM_1024_NAME: &CStr = c"ML-KEM-1024";
fn ml_kem_name(key_type: KeyType) -> Result<&'static CStr, ErrorStack> {
if key_type == KeyType::ML_KEM_512 {
Ok(ML_KEM_512_NAME)
} else if key_type == KeyType::ML_KEM_768 {
Ok(ML_KEM_768_NAME)
} else if key_type == KeyType::ML_KEM_1024 {
Ok(ML_KEM_1024_NAME)
} else {
Err(ErrorStack::get())
}
}
/// Encapsulate a key
pub trait PkeyEncapsulateContext {
/// Initialize the encapsulation operation.
///
/// # Errors
/// Returns an error if the OpenSSL operation fails.
fn encapsulate_init(&mut self) -> Result<(), ErrorStack>;
/// Perform the encapsulation operation.
///
/// # Parameters
/// - `wrappedkey`: Optional buffer to receive the wrapped key.
/// - `genkey`: Optional buffer to receive the generated key.
///
/// # Returns
/// A tuple of `(wrappedkey_len, genkey_len)` on success.
///
/// # Errors
/// Returns an error if the OpenSSL operation fails.
fn encapsulate(
&mut self,
wrappedkey: Option<&mut [u8]>,
genkey: Option<&mut [u8]>,
) -> Result<(usize, usize), ErrorStack>;
/// Convenience method to encapsulate into vectors.
///
/// # Parameters
/// - `wrappedkey`: Buffer to receive the wrapped key (ciphertext).
/// - `genkey`: Buffer to receive the generated key (shared secret).
///
/// # Returns
/// A tuple of `(wrappedkey_len, genkey_len)` on success.
///
/// # Errors
/// Returns an error if the OpenSSL operation fails.
fn encapsulate_to_vec(
&mut self,
wrappedkey: &mut Vec<u8>,
genkey: &mut Vec<u8>,
) -> Result<(usize, usize), ErrorStack> {
let wrappedkey_base = wrappedkey.len();
let genkey_base = genkey.len();
// Query the required output buffer sizes.
let (wrappedkey_len, genkey_len) = self.encapsulate(None, None)?;
wrappedkey.resize(wrappedkey_base + wrappedkey_len, 0);
genkey.resize(genkey_base + genkey_len, 0);
let (wrappedkey_len, genkey_len) = self.encapsulate(
Some(&mut wrappedkey[wrappedkey_base..]),
Some(&mut genkey[genkey_base..]),
)?;
wrappedkey.truncate(wrappedkey_base + wrappedkey_len);
genkey.truncate(genkey_base + genkey_len);
Ok((wrappedkey_len, genkey_len))
}
}
impl<T> PkeyEncapsulateContext for PkeyCtx<T> {
#[inline]
fn encapsulate_init(&mut self) -> Result<(), ErrorStack> {
// SAFETY: self.as_ptr() returns a valid EVP_PKEY_CTX pointer. Parameter-based
// configuration is intentionally unsupported here, so a null params pointer is passed.
let ret = unsafe {
ffi::EVP_PKEY_encapsulate_init(self.as_ptr() as *mut ffi::EvpPkeyCtx, std::ptr::null())
};
if ret == 1 {
Ok(())
} else {
Err(ErrorStack::get())
}
}
fn encapsulate(
&mut self,
wrappedkey: Option<&mut [u8]>,
genkey: Option<&mut [u8]>,
) -> Result<(usize, usize), ErrorStack> {
let mut wrappedkey_len = wrappedkey.as_ref().map_or(0, |buf| buf.len());
let mut genkey_len = genkey.as_ref().map_or(0, |buf| buf.len());
let wrappedkey_ptr = wrappedkey
.map(|buf| buf.as_mut_ptr())
.unwrap_or(std::ptr::null_mut());
let genkey_ptr = genkey
.map(|buf| buf.as_mut_ptr())
.unwrap_or(std::ptr::null_mut());
// SAFETY: All pointers are either valid mutable buffers or null.
let ret = unsafe {
ffi::EVP_PKEY_encapsulate(
self.as_ptr() as *mut ffi::EvpPkeyCtx,
wrappedkey_ptr,
&mut wrappedkey_len,
genkey_ptr,
&mut genkey_len,
)
};
if ret == 1 {
Ok((wrappedkey_len, genkey_len))
} else {
Err(ErrorStack::get())
}
}
}
/// Decapsulate a key
pub trait PkeyDecapsulateContext {
/// Initialize the decapsulation operation.
///
/// # Errors
/// Returns an error if the OpenSSL operation fails.
fn decapsulate_init(&mut self) -> Result<(), ErrorStack>;
/// Perform the decapsulation operation.
///
/// # Parameters
/// - `wrapped`: The wrapped key (ciphertext) to decapsulate.
/// - `unwrapped`: Optional buffer to receive the unwrapped shared secret.
///
/// # Returns
/// The length of the unwrapped shared secret on success.
///
/// # Errors
/// Returns an error if the OpenSSL operation fails.
fn decapsulate(
&mut self,
wrapped: &[u8],
unwrapped: Option<&mut [u8]>,
) -> Result<usize, ErrorStack>;
/// Convenience method to decapsulate into a vector.
///
/// # Parameters
/// - `wrapped`: The wrapped key (ciphertext) to decapsulate.
/// - `unwrapped`: Buffer to receive the unwrapped shared secret.
///
/// # Returns
/// The length of the unwrapped shared secret on success.
///
/// # Errors
/// Returns an error if the OpenSSL operation fails.
fn decapsulate_to_vec(
&mut self,
wrapped: &[u8],
unwrapped: &mut Vec<u8>,
) -> Result<usize, ErrorStack> {
let unwrapped_base = unwrapped.len();
// Query the required output buffer size.
let unwrapped_len = self.decapsulate(wrapped, None)?;
unwrapped.resize(unwrapped_base + unwrapped_len, 0);
let unwrapped_len = self.decapsulate(wrapped, Some(&mut unwrapped[unwrapped_base..]))?;
unwrapped.truncate(unwrapped_base + unwrapped_len);
Ok(unwrapped_len)
}
}
impl<T> PkeyDecapsulateContext for PkeyCtx<T> {
#[inline]
fn decapsulate_init(&mut self) -> Result<(), ErrorStack> {
// SAFETY: self.as_ptr() returns a valid EVP_PKEY_CTX pointer. Parameter-based
// configuration is intentionally unsupported here, so a null params pointer is passed.
let ret = unsafe {
ffi::EVP_PKEY_decapsulate_init(self.as_ptr() as *mut ffi::EvpPkeyCtx, std::ptr::null())
};
if ret == 1 {
Ok(())
} else {
Err(ErrorStack::get())
}
}
fn decapsulate(
&mut self,
wrapped: &[u8],
unwrapped: Option<&mut [u8]>,
) -> Result<usize, ErrorStack> {
let mut unwrapped_len = unwrapped.as_ref().map_or(0, |buf| buf.len());
let unwrapped_ptr = unwrapped
.map(|buf| buf.as_mut_ptr())
.unwrap_or(std::ptr::null_mut());
// SAFETY: All pointers are either valid mutable buffers or null, and wrapped is a valid
// slice.
let ret = unsafe {
ffi::EVP_PKEY_decapsulate(
self.as_ptr() as *mut ffi::EvpPkeyCtx,
unwrapped_ptr,
&mut unwrapped_len,
wrapped.as_ptr(),
wrapped.len(),
)
};
if ret == 1 {
Ok(unwrapped_len)
} else {
Err(ErrorStack::get())
}
}
}
/// ML-KEM key generation context.
///
/// The inner pointer is guaranteed to be non-null for the lifetime of the context.
pub struct MlKemKeyCtx {
ctx: NonNull<ffi::EvpPkeyCtx>,
}
impl MlKemKeyCtx {
/// Create a new ML-KEM key generation context.
///
/// # Parameters
/// - `key_type`: The ML-KEM key type (`KeyType::ML_KEM_512`, `KeyType::ML_KEM_768`, or
/// `KeyType::ML_KEM_1024`).
///
/// # Errors
/// Returns an error if the context cannot be created or the key type is unsupported.
pub fn new(key_type: KeyType) -> Result<Self, ErrorStack> {
let name = ml_kem_name(key_type)?;
// SAFETY: name is a valid null-terminated C string.
let ctx = unsafe {
ffi::EVP_PKEY_CTX_new_from_name(std::ptr::null_mut(), name.as_ptr(), std::ptr::null())
};
let Some(ctx) = NonNull::new(ctx) else {
return Err(ErrorStack::get());
};
// SAFETY: ctx is a valid non-null pointer.
let ret = unsafe { ffi::EVP_PKEY_keygen_init(ctx.as_ptr()) };
if ret != 1 {
// SAFETY: ctx is a valid non-null pointer.
unsafe { ffi::EVP_PKEY_CTX_free(ctx.as_ptr()) };
return Err(ErrorStack::get());
}
Ok(Self { ctx })
}
/// Generate an ML-KEM keypair.
///
/// # Returns
/// A `PKey` containing both the private and public key.
///
/// # Errors
/// Returns an error if key generation fails.
pub fn generate(&mut self) -> Result<PKey<Private>, ErrorStack> {
let mut pkey: *mut std::os::raw::c_void = std::ptr::null_mut();
// SAFETY: self.ctx is valid, and pkey is a valid mutable pointer.
let ret = unsafe { ffi::EVP_PKEY_generate(self.ctx.as_ptr(), &mut pkey) };
if ret != 1 || pkey.is_null() {
return Err(ErrorStack::get());
}
// SAFETY: pkey is a valid EVP_PKEY pointer created by OpenSSL.
unsafe { Ok(PKey::from_ptr(pkey as *mut openssl_sys::EVP_PKEY)) }
}
}
impl Drop for MlKemKeyCtx {
fn drop(&mut self) {
// SAFETY: self.ctx is guaranteed to be non-null and owned by this context.
unsafe {
ffi::EVP_PKEY_CTX_free(self.ctx.as_ptr());
}
}
}
/// Generate an ML-KEM keypair.
///
/// # Parameters
/// - `key_type`: The ML-KEM key type (`KeyType::ML_KEM_512`, `KeyType::ML_KEM_768`, or
/// `KeyType::ML_KEM_1024`).
///
/// # Returns
/// A `PKey` containing both the private and public key.
///
/// # Errors
/// Returns an error if key generation fails.
pub fn generate_ml_kem(key_type: KeyType) -> Result<PKey<Private>, ErrorStack> {
let mut ctx = MlKemKeyCtx::new(key_type)?;
ctx.generate()
}
#[cfg(test)]
mod tests {
use super::*;
fn roundtrip_ml_kem(key_type: KeyType, expected_ciphertext_len: usize) {
let keypair = generate_ml_kem(key_type).expect("Failed to generate ML-KEM keypair");
// Extract public key for encapsulation
let public_key_der = keypair
.public_key_to_der()
.expect("Failed to export public key");
let public_key =
PKey::public_key_from_der(&public_key_der).expect("Failed to import public key");
// Encapsulate: generate ciphertext and shared secret using public key
let mut ctx_enc =
PkeyCtx::new(&public_key).expect("Failed to create encapsulation context");
ctx_enc
.encapsulate_init()
.expect("Failed to initialize encapsulation");
let mut ciphertext = Vec::new();
let mut shared_secret_enc = Vec::new();
ctx_enc
.encapsulate_to_vec(&mut ciphertext, &mut shared_secret_enc)
.expect("Failed to encapsulate");
assert!(!ciphertext.is_empty(), "Ciphertext should not be empty");
assert!(
!shared_secret_enc.is_empty(),
"Shared secret should not be empty"
);
// Decapsulate: recover shared secret from ciphertext using private key
let mut ctx_dec = PkeyCtx::new(&keypair).expect("Failed to create decapsulation context");
ctx_dec
.decapsulate_init()
.expect("Failed to initialize decapsulation");
let mut shared_secret_dec = Vec::new();
ctx_dec
.decapsulate_to_vec(&ciphertext, &mut shared_secret_dec)
.expect("Failed to decapsulate");
// Verify that the shared secrets match
assert_eq!(
shared_secret_enc, shared_secret_dec,
"Shared secrets from encapsulation and decapsulation should match"
);
assert_eq!(ciphertext.len(), expected_ciphertext_len);
assert_eq!(shared_secret_enc.len(), 32);
}
#[test]
fn test_encapsulate_decapsulate_roundtrip_ml_kem_512() {
roundtrip_ml_kem(KeyType::ML_KEM_512, 768);
}
#[test]
fn test_encapsulate_decapsulate_roundtrip_ml_kem_768() {
roundtrip_ml_kem(KeyType::ML_KEM_768, 1088);
}
#[test]
fn test_encapsulate_decapsulate_roundtrip_ml_kem_1024() {
roundtrip_ml_kem(KeyType::ML_KEM_1024, 1568);
}
#[test]
fn test_ml_kem_key_ctx_new_accepts_supported_key_types() {
MlKemKeyCtx::new(KeyType::ML_KEM_512).expect("ML-KEM-512 context creation must succeed");
MlKemKeyCtx::new(KeyType::ML_KEM_768).expect("ML-KEM-768 context creation must succeed");
MlKemKeyCtx::new(KeyType::ML_KEM_1024).expect("ML-KEM-1024 context creation must succeed");
}
#[test]
fn test_ml_kem_key_ctx_new_rejects_unsupported_key_type() {
assert!(
MlKemKeyCtx::new(KeyType::RSA).is_err(),
"Unsupported key type must fail"
);
}
#[test]
fn test_ml_kem_key_ctx_generate_produces_ml_kem_keypair() {
let mut ctx = MlKemKeyCtx::new(KeyType::ML_KEM_768)
.expect("ML-KEM-768 context creation must succeed");
let keypair = ctx
.generate()
.expect("ML-KEM-768 key generation must succeed");
let public_key_der = keypair
.public_key_to_der()
.expect("Generated keypair must export a public key");
let public_key =
PKey::public_key_from_der(&public_key_der).expect("Generated public key must reimport");
let mut ctx_enc =
PkeyCtx::new(&public_key).expect("Failed to create encapsulation context");
ctx_enc
.encapsulate_init()
.expect("Failed to initialize encapsulation");
let mut ciphertext = Vec::new();
let mut shared_secret = Vec::new();
ctx_enc
.encapsulate_to_vec(&mut ciphertext, &mut shared_secret)
.expect("Generated keypair must support encapsulation");
assert_eq!(ciphertext.len(), 1088);
assert_eq!(shared_secret.len(), 32);
}
#[test]
fn test_generate_ml_kem_rejects_unsupported_key_type() {
generate_ml_kem(KeyType::RSA).expect_err("Unsupported key type must fail");
}
#[test]
fn test_encapsulate_requires_init() {
let keypair =
generate_ml_kem(KeyType::ML_KEM_512).expect("Failed to generate ML-KEM-512 keypair");
let public_key_der = keypair
.public_key_to_der()
.expect("Failed to export public key");
let public_key =
PKey::public_key_from_der(&public_key_der).expect("Failed to import public key");
let mut ctx_enc =
PkeyCtx::new(&public_key).expect("Failed to create encapsulation context");
let err = ctx_enc
.encapsulate_to_vec(&mut Vec::new(), &mut Vec::new())
.expect_err("Encapsulation without init must fail");
assert!(
!err.errors().is_empty(),
"OpenSSL should report an error when encapsulate_init was not called"
);
}
#[test]
fn test_decapsulate_requires_init() {
let keypair =
generate_ml_kem(KeyType::ML_KEM_512).expect("Failed to generate ML-KEM-512 keypair");
let mut ctx_dec = PkeyCtx::new(&keypair).expect("Failed to create decapsulation context");
let err = ctx_dec
.decapsulate_to_vec(&[0_u8; 768], &mut Vec::new())
.expect_err("Decapsulation without init must fail");
assert!(
!err.errors().is_empty(),
"OpenSSL should report an error when decapsulate_init was not called"
);
}
#[test]
fn test_decapsulate_rejects_undersized_output_buffer() {
let keypair =
generate_ml_kem(KeyType::ML_KEM_768).expect("Failed to generate ML-KEM-768 keypair");
let public_key_der = keypair
.public_key_to_der()
.expect("Failed to export public key");
let public_key =
PKey::public_key_from_der(&public_key_der).expect("Failed to import public key");
let mut ctx_enc =
PkeyCtx::new(&public_key).expect("Failed to create encapsulation context");
ctx_enc
.encapsulate_init()
.expect("Failed to initialize encapsulation");
let mut ciphertext = Vec::new();
let mut shared_secret = Vec::new();
ctx_enc
.encapsulate_to_vec(&mut ciphertext, &mut shared_secret)
.expect("Failed to encapsulate");
let mut ctx_dec = PkeyCtx::new(&keypair).expect("Failed to create decapsulation context");
ctx_dec
.decapsulate_init()
.expect("Failed to initialize decapsulation");
let mut undersized = [0_u8; 31];
let err = ctx_dec
.decapsulate(&ciphertext, Some(&mut undersized))
.expect_err("Undersized output buffer must fail");
assert!(
!err.errors().is_empty(),
"OpenSSL should report an error for an undersized output buffer"
);
}
}

View File

@@ -8,8 +8,11 @@
mod akid;
mod bio;
mod crl;
mod ml_kem;
mod stackable_crl;
pub use akid::*;
pub use bio::*;
pub use crl::*;
#[expect(unused_imports)]
pub use ml_kem::*;

View File

@@ -2,15 +2,15 @@
//
// Copyright IBM Corp. 2023
use crate::openssl_extensions::bio::BioMemSlice;
use foreign_types::{ForeignType, ForeignTypeRef};
use openssl::{
error::ErrorStack,
stack::Stackable,
x509::{X509Crl, X509CrlRef},
};
use std::ptr;
use foreign_types::{ForeignType, ForeignTypeRef};
use openssl::error::ErrorStack;
use openssl::stack::Stackable;
use openssl::x509::{X509Crl, X509CrlRef};
use crate::openssl_extensions::bio::BioMemSlice;
#[derive(Debug)]
pub struct StackableX509Crl(*mut openssl_sys::X509_CRL);

View File

@@ -2,18 +2,19 @@
//
// Copyright IBM Corp. 2024
use crate::Result;
use crate::{openssl_extensions::BioMem, Error};
use std::ffi::{c_char, CString};
use std::fmt::Display;
use openssl::error::ErrorStack;
use pv_core::request::Confidential;
use std::{
ffi::{c_char, CString},
fmt::Display,
};
use crate::openssl_extensions::BioMem;
use crate::{Error, Result};
mod ffi {
use openssl_sys::BIO;
use std::ffi::{c_char, c_int, c_long, c_uchar};
use openssl_sys::BIO;
extern "C" {
pub fn PEM_write_bio(
bio: *mut BIO,
@@ -141,8 +142,8 @@ impl Pem {
let inner_pem = InnerPem::new(name, header, data.as_ref())?;
// Create the PEM format eagerly so that to_string/display cannot fail because of ASCII or OpenSSL Errors
// Both error should be very unlikely
// Create the PEM format eagerly so that to_string/display cannot fail because of ASCII or
// OpenSSL Errors Both error should be very unlikely
// OpenSSL should be able to create PEM if there is enough memory and produce a non-null
// terminated ASCII-string
// Unwrap succeeds it's all ASCII

View File

@@ -1,692 +0,0 @@
// SPDX-License-Identifier: MIT
//
// Copyright IBM Corp. 2023
use std::mem::size_of;
use openssl::{
bn::{BigNum, BigNumContext},
ec::{EcGroup, EcGroupRef, EcKey, EcPointRef},
error::ErrorStack,
hash::{DigestBytes, MessageDigest},
nid::Nid,
pkey::{PKey, PKeyRef, Private, Public},
};
use pv_core::request::{RequestMagic, RequestVersion};
use zerocopy::{BigEndian, FromBytes, Immutable, IntoBytes, KnownLayout, U32};
use crate::{
assert_size,
crypto::{
decrypt_aead, derive_aes256_gcm_key, encrypt_aead, gen_ec_key, hash, random_array,
AeadEncryptionResult, SymKey, SymKeyType,
},
misc::to_u32,
request::Confidential,
Error, Result,
};
/// Encrypt a _secret_ using self and a given private key.
pub trait Encrypt {
/// Encrypts `secret` using `self` and `priv_key` the encryption.
///
/// # Returns
/// the encrypted data.
///
/// # Errors
///
/// This function will return an error if OpenSSL could not encrypt the secret.
fn encrypt(&self, secret: &[u8], priv_key: &PKeyRef<Private>) -> Result<Vec<u8>> {
let mut res = Vec::with_capacity(80);
self.encrypt_to(secret, priv_key, &mut res)?;
Ok(res)
}
/// Encrypts `secret` using `self` and `priv_key` the encryption.
/// Appends the encrypted data to `to`
///
/// # Returns
/// The encrypted data.
///
/// # Errors
///
/// This function will return an error if OpenSSL could not encrypt the secret.
fn encrypt_to(
&self,
secret: &[u8],
priv_key: &PKeyRef<Private>,
to: &mut Vec<u8>,
) -> Result<()>;
}
/// Types of Authenticated Data
#[allow(missing_debug_implementations)]
pub enum Aad<'a> {
/// Authenticated Keyslot
Ks(&'a Keyslot),
/// Unchanged authenticated data
Plain(&'a [u8]),
/// Authenticated data that has to be encrypted in beforehand
Encr(&'a dyn Encrypt),
}
/// IBM Z Host key-slot
///
/// Layout in binary format:
/// ```none
/// _______________________________________________________________
/// | Public Host Key Hash (32) |
/// | Wrapped(=Encrypted) Request Protection Key(32) |
/// | Key Slot Tag (16) |
/// |_____________________________________________________________|
/// ```
#[derive(Debug, Clone)]
pub struct Keyslot(PKey<Public>);
impl Keyslot {
/// Size of a host-key hash
pub const PHKH_SIZE: u32 = 0x20;
/// Creates a new Keyslot from the provided public key
pub fn new(hostkey: PKey<Public>) -> Self {
Self(hostkey)
}
}
impl Encrypt for Keyslot {
/// Encrypts the given request protection key `prot_key`.
///
/// The AES256 encryption key is derived from `self` as public key, and `priv_key` as private
/// key.
///
/// # Returns
/// The encrypted Keyslot.
///
/// # Errors
///
/// This function will return an error if OpenSSL could not encrypt the secret.
fn encrypt_to(
&self,
prot_key: &[u8],
priv_key: &PKeyRef<Private>,
to: &mut Vec<u8>,
) -> Result<()> {
let derived_key = derive_aes256_gcm_key(priv_key, &self.0)?;
let mut wrpk_and_kst =
encrypt_aead(&derived_key.into(), &[0; 12], &[], prot_key)?.into_buf();
let phk: EcPubKeyCoord = self.0.as_ref().try_into()?;
to.reserve(80);
to.extend_from_slice(&hash(MessageDigest::sha256(), phk.as_ref())?);
to.append(&mut wrpk_and_kst);
Ok(())
}
}
/// Context used to manage the encryption of requests.
/// Intended to be used by [`Request`] implementations
#[derive(Debug)]
pub struct ReqEncrCtx {
iv: [u8; 12],
priv_key: PKey<Private>,
prot_key: SymKey,
}
impl ReqEncrCtx {
/// Create a new encryption context that uses AES256.
///
/// * `iv` - Initialization vector for the request encryption
/// * `priv_key` - Private key to wrap [`Keyslot`]
/// * `prot_key` - Symmetric key for request encryption. Part of [`Keyslot`]
///
/// If an argument is set to `None` a ranom is generated
///
/// # Errors
///
/// This function will return an error if OpenSSL could not generate a random value.
pub fn new_aes_256<I, P, S>(iv: I, priv_key: P, prot_key: S) -> Result<Self>
where
I: Into<Option<[u8; 12]>>,
P: Into<Option<PKey<Private>>>,
S: Into<Option<SymKey>>,
{
let iv = iv.into().unwrap_or(random_array()?);
let priv_key = priv_key.into().unwrap_or(gen_ec_key(Nid::SECP521R1)?);
let prot_key = prot_key
.into()
.unwrap_or(SymKey::random(SymKeyType::Aes256Gcm)?);
Ok(Self {
iv,
priv_key,
prot_key,
})
}
/// Create a new encryption context with random input values.
///
/// # Errors
///
/// This function will return an error if OpenSSL could not generate a random value.
pub fn random(ket_tp: SymKeyType) -> Result<Self> {
match ket_tp {
SymKeyType::Aes256Gcm => Self::new_aes_256(None, None, None),
SymKeyType::Aes256Xts => Err(Error::NoAeadKey),
}
}
/// Build the authenticated data for a request.
/// # Returns
/// ```none
/// _______________________________________________________________
/// | MAGIC (8) Version Number (4) Size (4)|
/// | IV (12) Reserved (4)|
/// | Reserved (7) Num keyslots (1) Reserved(4) Encr Size (4)|
/// | --------------------------------------------------- |
/// | Request type dependent AAD data |
/// |-------------------------------------------------------------|
/// ```
pub fn build_aad<O>(
&self,
version: RequestVersion,
aad: &Vec<Aad>,
encr_size: usize,
magic: O,
) -> Result<Vec<u8>>
where
O: Into<Option<RequestMagic>>,
{
self.build_aad_impl(version, aad, encr_size, magic.into())
}
/// Concrete implementation for [`ReqEncrCtx::build_aad`].
fn build_aad_impl(
&self,
version: RequestVersion,
aad: &Vec<Aad>,
encr_size: usize,
magic: Option<RequestMagic>,
) -> Result<Vec<u8>> {
let nks = aad.iter().filter(|a| matches!(a, Aad::Ks(_))).count();
let nks: u8 = match nks {
0 => Err(Error::NoHostkey),
n if n > u8::MAX as usize => Err(Error::ManyHostkeys),
n => Ok(n as u8),
}?;
let mut auth_data: Vec<u8> = Vec::with_capacity(2048);
// reserve space for the request header
auth_data.resize(size_of::<RequestHdr>(), 0);
for a in aad {
match a {
Aad::Plain(p) => auth_data.extend_from_slice(p),
Aad::Ks(ks) => {
ks.encrypt_to(self.prot_key.value(), &self.priv_key, &mut auth_data)?
}
Aad::Encr(e) => {
e.encrypt_to(self.prot_key.value(), &self.priv_key, &mut auth_data)?
}
}
}
let rql = to_u32(auth_data.len() + encr_size + 16).ok_or_else(|| {
pv_core::Error::Specification("Configured request size to large".to_string())
})?;
let sea = to_u32(encr_size)
.ok_or_else(|| pv_core::Error::Specification("Encrypted size to large".to_string()))?;
let req_hdr = RequestHdr::new(version, rql, self.iv, nks, sea, magic);
// copy request header to the start of the request
auth_data[..size_of::<RequestHdr>()].copy_from_slice(req_hdr.as_bytes());
Ok(auth_data)
}
/// Get the public coordinates from the private key (Customer private key)
/// # Errors
///
/// This function will return an error if the public key could not be extracted by OpenSSL.
/// Very unlikely.
pub fn key_coords(&self) -> Result<EcPubKeyCoord> {
self.priv_key.as_ref().try_into().map_err(Error::Crypto)
}
/// Encrypt confidential Data with this encryption context and provide a GCM tag.
///
/// * `aad` - additional authentic data
/// * `conf` - data to be encrypted
///
/// # Returns
/// [`Vec<u8>`] with the following content:
/// 1. `aad`
/// 2. `encr(conf)`
/// 3. `aes gcm tag`
///
/// # Errors
///
/// This function will return an error if the data could not be encrypted by OpenSSL.
pub(crate) fn encrypt_aead(&self, aad: &[u8], conf: &[u8]) -> Result<AeadEncryptionResult> {
encrypt_aead(&self.prot_key, &self.iv, aad, conf)
}
/// Returns a reference to the request protection key of this [`ReqEncrCtx`].
pub fn prot_key(&self) -> &SymKey {
&self.prot_key
}
}
/// Public key components of an [`openssl::ec::EcKey`] key.
#[repr(C)]
#[derive(Debug, Clone)]
pub struct EcPubKeyCoord([u8; 160]);
impl AsRef<[u8]> for EcPubKeyCoord {
fn as_ref(&self) -> &[u8] {
self.0.as_slice()
}
}
const ECDH_PUB_KEY_COORD_POINT_SIZE: usize = 0x50;
impl EcPubKeyCoord {
/// Returns the SHA256 hash of the [`EcPubKeyCoord`].
///
/// If [`EcPubKeyCoord`] was built from a host-key, this value is the public host-key hash.
pub fn sha256(&self) -> Result<DigestBytes> {
hash(MessageDigest::sha256(), self.as_ref())
}
/// Construct a [`EcPubKeyCoord`]
///
/// # Safety
/// This function is marked unsafe, because data not representing two EC points violates the
/// invariant of this struct.
pub unsafe fn from_data(data: [u8; 160]) -> Self {
EcPubKeyCoord(data)
}
}
/// Get the pub ECDH coordinates in the format the Ultravisor expects it:
/// The two coordinates are padded to 80 bytes each.
fn get_pub_ecdh_points(pkey: &EcPointRef, grp: &EcGroupRef) -> Result<[u8; 160], ErrorStack> {
let mut x = BigNum::new()?;
let mut y = BigNum::new()?;
let mut bn_ctx = BigNumContext::new()?;
pkey.affine_coordinates(grp, &mut x, &mut y, &mut bn_ctx)?;
let mut coord: Vec<u8> = x.to_vec_padded(ECDH_PUB_KEY_COORD_POINT_SIZE as i32)?;
coord.append(&mut y.to_vec_padded(ECDH_PUB_KEY_COORD_POINT_SIZE as i32)?);
Ok(coord.try_into().unwrap())
}
impl TryFrom<EcPubKeyCoord> for PKey<Public> {
type Error = ErrorStack;
fn try_from(value: EcPubKeyCoord) -> Result<Self, Self::Error> {
let ecdh = value.as_ref();
let grp = EcGroup::from_curve_name(Nid::SECP521R1)?;
let x = BigNum::from_slice(&ecdh[..ECDH_PUB_KEY_COORD_POINT_SIZE])?;
let y = BigNum::from_slice(&ecdh[ECDH_PUB_KEY_COORD_POINT_SIZE..])?;
let ec_key = EcKey::from_public_key_affine_coordinates(&grp, &x, &y)?;
Self::from_ec_key(ec_key)
}
}
macro_rules! ecdh_from {
($type: ty) => {
impl TryFrom<&PKeyRef<$type>> for EcPubKeyCoord {
type Error = ErrorStack;
fn try_from(key: &PKeyRef<$type>) -> Result<Self, Self::Error> {
let k = key.ec_key()?;
k.check_key()?;
let grp = k.group();
let pub_key = k.public_key();
let coord = get_pub_ecdh_points(pub_key, grp)?;
Ok(Self(coord))
}
}
impl TryFrom<PKey<$type>> for EcPubKeyCoord {
type Error = ErrorStack;
fn try_from(key: PKey<$type>) -> Result<Self, Self::Error> {
let key_ref = key.as_ref();
key_ref.try_into()
}
}
};
}
ecdh_from!(Private);
ecdh_from!(Public);
/// Representation of the shared parts of the request header.
/// Used by [`ReqEncrCtx`]
#[repr(C)]
#[derive(Debug, Copy, Clone, IntoBytes, FromBytes, Immutable)]
struct RequestHdr {
magic: [u8; 8],
rqvn: U32<BigEndian>,
rql: U32<BigEndian>,
iv: [u8; 12],
reserved1c: [u8; 4],
reserved20: [u8; 7],
nks: u8,
reserved28: u32,
sea: U32<BigEndian>,
}
assert_size!(RequestHdr, 48);
impl RequestHdr {
fn new(rqvn: u32, rql: u32, iv: [u8; 12], nks: u8, sea: u32, magic: Option<[u8; 8]>) -> Self {
Self {
magic: magic.unwrap_or_default(),
rqvn: rqvn.into(),
rql: rql.into(),
iv,
reserved1c: [0; 4],
reserved20: [0; 7],
nks,
reserved28: 0,
sea: sea.into(),
}
}
}
/// A trait representing a request for the Ultravisor.
///
/// All requests share a few things:
/// * All requests need to be encrypted on a trusted machine
/// * All requests have at least one Hostkeyslot
///
/// The encryption setup is handled by [`ReqEncrCtx`]. Implementers need to pass the data to the
/// `ReqEncrCtx` when implementing `encrypt`. A hostkey should be represented by [`Keyslot`] during
/// encryption.
///
/// An UV request consists of an authenticated area (AAD), an encrypted area (Encr) and a 16 byte
/// tag. The AAD contains a general header and Request type defined data (including Keyslots). It
/// is encrypted with an Request protection key (symmetric). This key is encrypted with a
/// (generated) private key and the public key of the host system (Host key)
/// ```none
/// _______________________________________________________________
/// | MAGIC (8) Version Number (4) Size (4)|
/// | IV (12) Reserved (4)|
/// | Reserved (7) Num keyslots (1) Reserved(4) Encr Size (4)|
/// | --------------------------------------------------- |
/// | Request type dependent AAD data |
/// | ---------------------------------------------------- |
/// | Encrypted (request type dependent) data |
/// | ---------------------------------------------------- |
/// | AES GCM Tag (16) |
/// |_____________________________________________________________|
/// ```
pub trait Request {
/// Encrypt the request into its binary format
///
/// # Errors
///
/// This function will return an error if the encryption fails, the request does not have at
/// least a hostkey, or other implementation dependent contracts are not met.
fn encrypt(&self, ctx: &ReqEncrCtx) -> Result<Vec<u8>>;
/// Add a host-key to this request
///
/// Must be called at least once, otherwise {`Request::encrypt`} will fail
fn add_hostkey(&mut self, hostkey: PKey<Public>);
}
/// A struct to represent some parts of a binary/encrypted request.
#[derive(Debug)]
#[allow(clippy::len_without_is_empty)]
pub(crate) struct BinReqValues<'a> {
iv: &'a [u8],
aad: &'a [u8],
req_dep_aad: &'a [u8],
encr: &'a [u8],
tag: &'a [u8],
version: u32,
len: usize,
}
impl<'a> BinReqValues<'a> {
pub(crate) const TAG_LEN: usize = SymKeyType::AES_256_GCM_TAG_LEN;
/// Get the locations from this request.
///
/// Does minimal sanity test, just tests to prevent panics.
/// `req` may be larger than the actual request.
pub(crate) fn get(req: &'a [u8]) -> Result<Self> {
let (hdr, _) = RequestHdr::read_from_prefix(req).map_err(|_| Error::BinRequestSmall)?;
let rql = hdr.rql.get() as usize;
let sea = hdr.sea.get() as usize;
if rql < req.len() || sea + Self::TAG_LEN > rql {
return Err(Error::BinRequestSmall);
}
let aad_size = rql - sea - Self::TAG_LEN;
if aad_size < size_of::<RequestHdr>() {
return Err(Error::BinRequestSmall);
}
let iv = &req[0x10..0x1c];
let aad = &req[..aad_size];
let req_dep_aad = &req[size_of::<RequestHdr>()..aad_size];
let encr = &req[aad_size..(aad_size + sea)];
let tag = &req[rql - Self::TAG_LEN..];
Ok(Self {
iv,
aad,
req_dep_aad,
encr,
tag,
version: hdr.rqvn.get(),
len: rql,
})
}
/// Returns the version of this [`BinReqValues`].
pub(crate) fn version(&self) -> u32 {
self.version
}
/// Returns the length of this [`BinReqValues`].
pub(crate) fn len(&self) -> usize {
self.len
}
/// Returns the size of the encrypted area
pub(crate) fn sea(&self) -> u32 {
self.encr.len() as u32
}
/// Decrypts the encrypted area with the provided key
pub(crate) fn decrypt(&self, key: &SymKey) -> Result<Confidential<Vec<u8>>> {
let result = decrypt_aead(key, self.iv, self.aad, self.encr, self.tag)?;
Ok(result.into_plain())
}
/// Returns a reference to the request dependent authenticated area of this [`BinReqValues`]
/// already interpreted.
///
/// If target struct is larger than the request depended-AAD None is returned. See
/// [`FromBytes::ref_from_prefix`]
pub(crate) fn req_dep_aad<T>(&self) -> Option<&T>
where
T: FromBytes + Sized + Immutable + KnownLayout,
{
T::ref_from_prefix(self.req_dep_aad).map(|s| s.0).ok()
}
/// Returns a reference to the tag of this [`BinReqValues`].
pub(crate) fn tag(&self) -> &[u8] {
self.tag
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{get_test_asset, request::SymKey, test_utils::*};
static TEST_MAGIC: [u8; 8] = 0x12345689abcdef00u64.to_be_bytes();
#[test]
fn encr_build_aad() {
let (cust_key, host_key) = get_test_keys();
let ks = Keyslot::new(host_key);
let ctx = ReqEncrCtx::new_aes_256(
Some([0x11; 12]),
Some(cust_key),
Some(SymKey::Aes256([0x17; 32].into())),
)
.unwrap();
let v = [0x55; 8];
let aad = Aad::Plain(&v);
let aad = ctx
.build_aad(0x200, &vec![aad, Aad::Ks(&ks)], 16, Some(TEST_MAGIC))
.unwrap();
let mut aad_exp = vec![
0x12, 0x34, 0x56, 0x89, 0xab, 0xcd, 0xef, 0, // progr
0, 0, 2, 0, // vers
0, 0, 0, 168, // size
0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, // iv
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // res
1, // nks
0, 0, 0, 0, // res
0, 0, 0, 16, // sea
0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, // aad
];
aad_exp.extend_from_slice(get_test_asset!("exp/keyslot.bin"));
assert_eq!(&aad, &aad_exp);
}
#[test]
fn encr_build_aad_nks_no() {
let ctx = ReqEncrCtx::new_aes_256(Some([0x11; 12]), None, None).unwrap();
let aad = Vec::<Aad>::new();
let aad = ctx.build_aad(0x200, &aad, 16, Some(TEST_MAGIC));
assert!(matches!(aad, Err(Error::NoHostkey)));
}
#[test]
fn encr_build_aad_nks_many() {
let (_, host_key) = get_test_keys();
let ctx = ReqEncrCtx::new_aes_256(Some([0x11; 12]), None, None).unwrap();
let ks: Vec<Keyslot> = (0..257).map(|_| Keyslot::new(host_key.clone())).collect();
let mut aad = Vec::<Aad>::new();
ks.iter().for_each(|ks| aad.push(Aad::Ks(ks)));
let aad = ctx.build_aad(0x200, &aad, 16, Some(TEST_MAGIC));
assert!(matches!(aad, Err(Error::ManyHostkeys)));
}
#[test]
fn encr_build_aad_nks() {
let (_, host_key) = get_test_keys();
let ctx = ReqEncrCtx::new_aes_256(Some([0x11; 12]), None, None).unwrap();
let ks = [
Keyslot::new(host_key.clone()),
Keyslot::new(host_key.clone()),
Keyslot::new(host_key),
];
let mut aad = Vec::<Aad>::new();
ks.iter().for_each(|ks| aad.push(Aad::Ks(ks)));
let aad = ctx.build_aad(0x200, &aad, 16, Some(TEST_MAGIC)).unwrap();
assert_eq!(aad.get(39).unwrap(), &3u8);
}
#[test]
fn req_hdr() {
let hdr = RequestHdr::new(0x200, 22, [0x11; 12], 15, 44, None);
let hdr_bin = hdr.as_bytes();
let hdr_bin_exp = [
0u8, 0, 0, 0, 0, 0, 0, 0, // magic
0, 0, 2, 0, // vers
0, 0, 0, 22, // size
0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, // iv
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // res
15, // nks
0, 0, 0, 0, // res
0, 0, 0, 44, // sea
];
assert_eq!(hdr_bin, &hdr_bin_exp);
}
#[test]
fn req_hdr2() {
let mut hdr = RequestHdr::new(0x200, 0x1234, [0x11; 12], 15, 44, Some(TEST_MAGIC));
let hdr_bin = hdr.as_mut_bytes();
let hdr_bin_exp = [
0x12, 0x34, 0x56, 0x89, 0xab, 0xcd, 0xef, 0, // magic
0, 0, 2, 0, // vers
0, 0, 0x12, 0x34, // size
0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, // iv
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // res
15, // nks
0, 0, 0, 0, // res
0, 0, 0, 44, // sea
];
assert_eq!(hdr_bin, &hdr_bin_exp);
}
#[test]
fn keyslot() {
let (cust_key, host_key) = get_test_keys();
let exp_keyslot = get_test_asset!("exp/keyslot.bin").to_vec();
let keyslot = Keyslot::new(host_key);
let encr_ks = keyslot.encrypt(&[0x17u8; 32], &cust_key).unwrap();
assert_eq!(exp_keyslot, encr_ks);
let encr_ks = keyslot.encrypt(&[0x16u8; 32], &cust_key).unwrap();
assert_ne!(exp_keyslot, encr_ks);
}
#[test]
fn ec_pub_ec_coord_from() {
let (cust_key, _) = get_test_keys();
let pub_key = get_test_asset!("keys/public_cust.bin");
assert_eq!(pub_key.len(), 160);
let ec_coord: EcPubKeyCoord = cust_key.as_ref().try_into().unwrap();
assert_eq!(ec_coord.as_ref(), pub_key);
}
#[test]
fn ec_pub_ec_coord_hash() {
let exp = [
0x5e, 0xe9, 0x05, 0xa9, 0xbe, 0x70, 0x36, 0x68, 0x15, 0xa4, 0x56, 0x41, 0xaf, 0xae,
0x00, 0x97, 0x3b, 0x1f, 0x45, 0x29, 0x2f, 0x43, 0xbc, 0xd7, 0x63, 0x8e, 0xe2, 0xa7,
0x3f, 0xd7, 0xc4, 0x5e,
];
let (cust_key, _) = get_test_keys();
let ec_coord: EcPubKeyCoord = cust_key.as_ref().try_into().unwrap();
let hash = ec_coord.sha256().unwrap();
assert_eq!(hash.as_ref(), &exp);
}
#[test]
fn conversion_ecdh_and_vice_versa() {
let (_, cust_pub) = get_test_keys();
let phk: EcPubKeyCoord = cust_pub.clone().try_into().unwrap();
assert_eq!(
phk.as_ref(),
&[
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 118, 136, 28, 216, 75, 139, 109, 231,
18, 60, 126, 144, 14, 223, 120, 231, 247, 182, 132, 153, 145, 70, 177, 38, 59, 168,
184, 108, 132, 71, 240, 138, 182, 212, 105, 194, 177, 40, 237, 158, 28, 53, 1, 88,
5, 172, 211, 211, 2, 51, 211, 145, 34, 247, 226, 248, 170, 28, 43, 20, 123, 120,
131, 180, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 195, 69, 86, 194, 92,
249, 47, 41, 206, 102, 189, 68, 17, 77, 107, 123, 60, 120, 225, 58, 63, 144, 189,
185, 0, 64, 246, 135, 110, 82, 98, 247, 120, 166, 26, 147, 125, 27, 52, 128, 46,
178, 87, 227, 78, 6, 114, 221, 95, 42, 52, 122, 221, 170, 40, 32, 53, 9, 42, 112,
195, 92, 46, 121, 115
]
);
let cust_pub_back: PKey<Public> = phk.try_into().unwrap();
assert!(cust_pub.public_eq(&cust_pub_back));
}
}

257
rust/pv/src/req/context.rs Normal file
View File

@@ -0,0 +1,257 @@
// SPDX-License-Identifier: MIT
//
// Copyright IBM Corp.
//! Request encryption context for managing UV request encryption.
use std::mem::size_of;
use openssl::nid::Nid;
use openssl::pkey::{PKey, Private};
use pv_core::request::{RequestMagic, RequestVersion};
use zerocopy::IntoBytes;
use super::{Aad, EcPubKeyCoord, Encrypt, RequestHdr};
use crate::crypto::{
encrypt_aead, gen_ec_key, random_array, AeadEncryptionResult, SymKey, SymKeyType,
};
use crate::misc::to_u32;
use crate::{Error, Result};
/// Context used to manage the encryption of requests.
/// Intended to be used by [`Request`](super::Request) implementations
#[derive(Debug)]
pub struct ReqEncrCtx {
iv: [u8; 12],
priv_key: PKey<Private>,
prot_key: SymKey,
}
impl ReqEncrCtx {
/// Create a new encryption context that uses AES256.
///
/// * `iv` - Initialization vector for the request encryption
/// * `priv_key` - Private key to wrap [`Keyslot`](super::Keyslot)
/// * `prot_key` - Symmetric key for request encryption. Part of [`Keyslot`](super::Keyslot)
///
/// If an argument is set to `None` a random is generated
///
/// # Errors
///
/// This function will return an error if OpenSSL could not generate a random value.
pub fn new_aes_256<I, P, S>(iv: I, priv_key: P, prot_key: S) -> Result<Self>
where
I: Into<Option<[u8; 12]>>,
P: Into<Option<PKey<Private>>>,
S: Into<Option<SymKey>>,
{
let iv = iv.into().unwrap_or(random_array()?);
let priv_key = priv_key.into().unwrap_or(gen_ec_key(Nid::SECP521R1)?);
let prot_key = prot_key
.into()
.unwrap_or(SymKey::random(SymKeyType::Aes256Gcm)?);
Ok(Self {
iv,
priv_key,
prot_key,
})
}
/// Create a new encryption context with random input values.
///
/// # Errors
///
/// This function will return an error if OpenSSL could not generate a random value.
pub fn random(key_tp: SymKeyType) -> Result<Self> {
match key_tp {
SymKeyType::Aes256Gcm => Self::new_aes_256(None, None, None),
SymKeyType::Aes256Xts => Err(Error::NoAeadKey),
}
}
/// Build the authenticated data for a request.
/// # Returns
/// ```none
/// _______________________________________________________________
/// | MAGIC (8) Version Number (4) Size (4)|
/// | IV (12) Reserved (4)|
/// | Reserved (7) Num keyslots (1) Reserved(4) Encr Size (4)|
/// | --------------------------------------------------- |
/// | Request type dependent AAD data |
/// |-------------------------------------------------------------|
/// ```
pub fn build_aad<O>(
&self,
version: RequestVersion,
aad: &Vec<Aad>,
encr_size: usize,
magic: O,
) -> Result<Vec<u8>>
where
O: Into<Option<RequestMagic>>,
{
self.build_aad_impl(version, aad, encr_size, magic.into())
}
/// Concrete implementation for [`ReqEncrCtx::build_aad`].
fn build_aad_impl(
&self,
version: RequestVersion,
aad: &Vec<Aad>,
encr_size: usize,
magic: Option<RequestMagic>,
) -> Result<Vec<u8>> {
let nks = aad.iter().filter(|a| matches!(a, Aad::Ks(_))).count();
let nks: u8 = match nks {
0 => Err(Error::NoHostkey),
n if n > u8::MAX as usize => Err(Error::ManyHostkeys),
n => Ok(n as u8),
}?;
let mut auth_data: Vec<u8> = Vec::with_capacity(2048);
// reserve space for the request header
auth_data.resize(size_of::<RequestHdr>(), 0);
for a in aad {
match a {
Aad::Plain(p) => auth_data.extend_from_slice(p),
Aad::Ks(ks) => {
ks.encrypt_to(self.prot_key.value(), &self.priv_key, &mut auth_data)?
}
Aad::Encr(e) => {
e.encrypt_to(self.prot_key.value(), &self.priv_key, &mut auth_data)?
}
}
}
let rql = to_u32(auth_data.len() + encr_size + 16).ok_or_else(|| {
pv_core::Error::Specification("Configured request size to large".to_string())
})?;
let sea = to_u32(encr_size)
.ok_or_else(|| pv_core::Error::Specification("Encrypted size to large".to_string()))?;
let req_hdr = RequestHdr::new(version, rql, self.iv, nks, sea, magic);
// copy request header to the start of the request
auth_data[..size_of::<RequestHdr>()].copy_from_slice(req_hdr.as_bytes());
Ok(auth_data)
}
/// Get the public coordinates from the private key (Customer private key)
/// # Errors
///
/// This function will return an error if the public key could not be extracted by OpenSSL.
/// Very unlikely.
pub fn key_coords(&self) -> Result<EcPubKeyCoord> {
self.priv_key.as_ref().try_into().map_err(Error::Crypto)
}
/// Encrypt confidential Data with this encryption context and provide a GCM tag.
///
/// * `aad` - additional authentic data
/// * `conf` - data to be encrypted
///
/// # Returns
/// [`Vec<u8>`] with the following content:
/// 1. `aad`
/// 2. `encr(conf)`
/// 3. `aes gcm tag`
///
/// # Errors
///
/// This function will return an error if the data could not be encrypted by OpenSSL.
pub(crate) fn encrypt_aead(&self, aad: &[u8], conf: &[u8]) -> Result<AeadEncryptionResult> {
encrypt_aead(&self.prot_key, &self.iv, aad, conf)
}
/// Returns a reference to the request protection key of this [`ReqEncrCtx`].
pub fn prot_key(&self) -> &SymKey {
&self.prot_key
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::get_test_asset;
use crate::req::hostkey::HostKey;
use crate::req::keyslot::Keyslot;
use crate::request::SymKey;
use crate::test_utils::*;
static TEST_MAGIC: [u8; 8] = 0x12345689abcdef00u64.to_be_bytes();
#[test]
fn encr_build_aad() {
let (cust_key, host_key) = get_test_keys();
let ks = Keyslot::new(HostKey::V1(host_key));
let ctx = ReqEncrCtx::new_aes_256(
Some([0x11; 12]),
Some(cust_key),
Some(SymKey::Aes256([0x17; 32].into())),
)
.unwrap();
let v = [0x55; 8];
let aad = Aad::Plain(&v);
let aad = ctx
.build_aad(0x200, &vec![aad, Aad::Ks(&ks)], 16, Some(TEST_MAGIC))
.unwrap();
let mut aad_exp = vec![
0x12, 0x34, 0x56, 0x89, 0xab, 0xcd, 0xef, 0, // progr
0, 0, 2, 0, // vers
0, 0, 0, 168, // size
0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, // iv
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // res
1, // nks
0, 0, 0, 0, // res
0, 0, 0, 16, // sea
0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, // aad
];
aad_exp.extend_from_slice(get_test_asset!("exp/keyslot.bin"));
assert_eq!(&aad, &aad_exp);
}
#[test]
fn encr_build_aad_nks_no() {
let ctx = ReqEncrCtx::new_aes_256(Some([0x11; 12]), None, None).unwrap();
let aad = Vec::<Aad>::new();
let aad = ctx.build_aad(0x200, &aad, 16, Some(TEST_MAGIC));
assert!(matches!(aad, Err(Error::NoHostkey)));
}
#[test]
fn encr_build_aad_nks_many() {
let (_, host_key) = get_test_keys();
let ctx = ReqEncrCtx::new_aes_256(Some([0x11; 12]), None, None).unwrap();
let ks: Vec<Keyslot> = (0..257)
.map(|_| Keyslot::new(HostKey::V1(host_key.clone())))
.collect();
let mut aad = Vec::<Aad>::new();
ks.iter().for_each(|ks| aad.push(Aad::Ks(ks)));
let aad = ctx.build_aad(0x200, &aad, 16, Some(TEST_MAGIC));
assert!(matches!(aad, Err(Error::ManyHostkeys)));
}
#[test]
fn encr_build_aad_nks() {
let (_, host_key) = get_test_keys();
let host_key = HostKey::V1(host_key);
let ctx = ReqEncrCtx::new_aes_256(Some([0x11; 12]), None, None).unwrap();
let ks = [
Keyslot::new(host_key.clone()),
Keyslot::new(host_key.clone()),
Keyslot::new(host_key),
];
let mut aad = Vec::<Aad>::new();
ks.iter().for_each(|ks| aad.push(Aad::Ks(ks)));
let aad = ctx.build_aad(0x200, &aad, 16, Some(TEST_MAGIC)).unwrap();
assert_eq!(aad.get(39).unwrap(), &3u8);
}
}

154
rust/pv/src/req/ec_coord.rs Normal file
View File

@@ -0,0 +1,154 @@
// SPDX-License-Identifier: MIT
//
// Copyright IBM Corp.
//! EC public key coordinate utilities and conversions.
use openssl::bn::{BigNum, BigNumContext};
use openssl::ec::{EcGroup, EcGroupRef, EcKey, EcPointRef};
use openssl::error::ErrorStack;
use openssl::hash::{DigestBytes, MessageDigest};
use openssl::nid::Nid;
use openssl::pkey::{PKey, PKeyRef, Private, Public};
use crate::crypto::hash;
use crate::Result;
/// Public key components of an [`openssl::ec::EcKey`] key.
#[repr(C)]
#[derive(Debug, Clone)]
pub struct EcPubKeyCoord([u8; 160]);
impl AsRef<[u8]> for EcPubKeyCoord {
fn as_ref(&self) -> &[u8] {
self.0.as_slice()
}
}
const ECDH_PUB_KEY_COORD_POINT_SIZE: usize = 0x50;
impl EcPubKeyCoord {
/// Returns the SHA256 hash of the [`EcPubKeyCoord`].
///
/// If [`EcPubKeyCoord`] was built from a host-key, this value is the public host-key hash.
pub fn sha256(&self) -> Result<DigestBytes> {
hash(MessageDigest::sha256(), self.as_ref())
}
/// Construct a [`EcPubKeyCoord`]
///
/// # Safety
/// This function is marked unsafe, because data not representing two EC points violates the
/// invariant of this struct.
pub unsafe fn from_data(data: [u8; 160]) -> Self {
EcPubKeyCoord(data)
}
}
/// Get the pub ECDH coordinates in the format the Ultravisor expects it:
/// The two coordinates are padded to 80 bytes each.
fn get_pub_ecdh_points(pkey: &EcPointRef, grp: &EcGroupRef) -> Result<[u8; 160], ErrorStack> {
let mut x = BigNum::new()?;
let mut y = BigNum::new()?;
let mut bn_ctx = BigNumContext::new()?;
pkey.affine_coordinates(grp, &mut x, &mut y, &mut bn_ctx)?;
let mut coord: Vec<u8> = x.to_vec_padded(ECDH_PUB_KEY_COORD_POINT_SIZE as i32)?;
coord.append(&mut y.to_vec_padded(ECDH_PUB_KEY_COORD_POINT_SIZE as i32)?);
Ok(coord.try_into().unwrap())
}
impl TryFrom<EcPubKeyCoord> for PKey<Public> {
type Error = ErrorStack;
fn try_from(value: EcPubKeyCoord) -> Result<Self, Self::Error> {
let ecdh = value.as_ref();
let grp = EcGroup::from_curve_name(Nid::SECP521R1)?;
let x = BigNum::from_slice(&ecdh[..ECDH_PUB_KEY_COORD_POINT_SIZE])?;
let y = BigNum::from_slice(&ecdh[ECDH_PUB_KEY_COORD_POINT_SIZE..])?;
let ec_key = EcKey::from_public_key_affine_coordinates(&grp, &x, &y)?;
Self::from_ec_key(ec_key)
}
}
macro_rules! ecdh_from {
($type: ty) => {
impl TryFrom<&PKeyRef<$type>> for EcPubKeyCoord {
type Error = ErrorStack;
fn try_from(key: &PKeyRef<$type>) -> Result<Self, Self::Error> {
let k = key.ec_key()?;
k.check_key()?;
let grp = k.group();
let pub_key = k.public_key();
let coord = get_pub_ecdh_points(pub_key, grp)?;
Ok(Self(coord))
}
}
impl TryFrom<PKey<$type>> for EcPubKeyCoord {
type Error = ErrorStack;
fn try_from(key: PKey<$type>) -> Result<Self, Self::Error> {
let key_ref = key.as_ref();
key_ref.try_into()
}
}
};
}
ecdh_from!(Private);
ecdh_from!(Public);
#[cfg(test)]
mod tests {
use super::*;
use crate::get_test_asset;
use crate::test_utils::*;
#[test]
fn ec_pub_ec_coord_from() {
let (cust_key, _) = get_test_keys();
let pub_key = get_test_asset!("keys/public_cust.bin");
assert_eq!(pub_key.len(), 160);
let ec_coord: EcPubKeyCoord = cust_key.as_ref().try_into().unwrap();
assert_eq!(ec_coord.as_ref(), pub_key);
}
#[test]
fn ec_pub_ec_coord_hash() {
let exp = [
0x5e, 0xe9, 0x05, 0xa9, 0xbe, 0x70, 0x36, 0x68, 0x15, 0xa4, 0x56, 0x41, 0xaf, 0xae,
0x00, 0x97, 0x3b, 0x1f, 0x45, 0x29, 0x2f, 0x43, 0xbc, 0xd7, 0x63, 0x8e, 0xe2, 0xa7,
0x3f, 0xd7, 0xc4, 0x5e,
];
let (cust_key, _) = get_test_keys();
let ec_coord: EcPubKeyCoord = cust_key.as_ref().try_into().unwrap();
let hash = ec_coord.sha256().unwrap();
assert_eq!(hash.as_ref(), &exp);
}
#[test]
fn conversion_ecdh_and_vice_versa() {
let (_, cust_pub) = get_test_keys();
let phk: EcPubKeyCoord = cust_pub.clone().try_into().unwrap();
assert_eq!(
phk.as_ref(),
&[
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 118, 136, 28, 216, 75, 139, 109, 231,
18, 60, 126, 144, 14, 223, 120, 231, 247, 182, 132, 153, 145, 70, 177, 38, 59, 168,
184, 108, 132, 71, 240, 138, 182, 212, 105, 194, 177, 40, 237, 158, 28, 53, 1, 88,
5, 172, 211, 211, 2, 51, 211, 145, 34, 247, 226, 248, 170, 28, 43, 20, 123, 120,
131, 180, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 195, 69, 86, 194, 92,
249, 47, 41, 206, 102, 189, 68, 17, 77, 107, 123, 60, 120, 225, 58, 63, 144, 189,
185, 0, 64, 246, 135, 110, 82, 98, 247, 120, 166, 26, 147, 125, 27, 52, 128, 46,
178, 87, 227, 78, 6, 114, 221, 95, 42, 52, 122, 221, 170, 40, 32, 53, 9, 42, 112,
195, 92, 46, 121, 115
]
);
let cust_pub_back: PKey<Public> = phk.try_into().unwrap();
assert!(cust_pub.public_eq(&cust_pub_back));
}
}

View File

@@ -0,0 +1,54 @@
// SPDX-License-Identifier: MIT
//
// Copyright IBM Corp.
//! Encryption trait and authenticated data types for UV requests.
use openssl::pkey::{PKeyRef, Private};
use crate::req::Keyslot;
use crate::Result;
/// Encrypt a _secret_ using self and a given private key.
pub trait Encrypt {
/// Encrypts `secret` using `self` and `priv_key` the encryption.
///
/// # Returns
/// the encrypted data.
///
/// # Errors
///
/// This function will return an error if OpenSSL could not encrypt the secret.
fn encrypt(&self, secret: &[u8], priv_key: &PKeyRef<Private>) -> Result<Vec<u8>> {
let mut res = Vec::with_capacity(80);
self.encrypt_to(secret, priv_key, &mut res)?;
Ok(res)
}
/// Encrypts `secret` using `self` and `priv_key` the encryption.
/// Appends the encrypted data to `to`
///
/// # Returns
/// The encrypted data.
///
/// # Errors
///
/// This function will return an error if OpenSSL could not encrypt the secret.
fn encrypt_to(
&self,
secret: &[u8],
priv_key: &PKeyRef<Private>,
to: &mut Vec<u8>,
) -> Result<()>;
}
/// Types of Authenticated Data
#[allow(missing_debug_implementations)]
pub enum Aad<'a> {
/// Authenticated Keyslot
Ks(&'a Keyslot),
/// Unchanged authenticated data
Plain(&'a [u8]),
/// Authenticated data that has to be encrypted in beforehand
Encr(&'a dyn Encrypt),
}

91
rust/pv/src/req/header.rs Normal file
View File

@@ -0,0 +1,91 @@
// SPDX-License-Identifier: MIT
//
// Copyright IBM Corp.
//! Request header structure for UV requests.
use zerocopy::{BigEndian, FromBytes, Immutable, IntoBytes, U32};
use crate::assert_size;
/// Representation of the shared parts of the request header.
/// Used by [`ReqEncrCtx`](super::ReqEncrCtx)
#[repr(C)]
#[derive(Debug, Copy, Clone, IntoBytes, FromBytes, Immutable)]
pub struct RequestHdr {
magic: [u8; 8],
pub(crate) rqvn: U32<BigEndian>,
pub(crate) rql: U32<BigEndian>,
iv: [u8; 12],
reserved1c: [u8; 4],
reserved20: [u8; 7],
nks: u8,
reserved28: u32,
pub(crate) sea: U32<BigEndian>,
}
assert_size!(RequestHdr, 48);
impl RequestHdr {
pub(crate) fn new(
rqvn: u32,
rql: u32,
iv: [u8; 12],
nks: u8,
sea: u32,
magic: Option<[u8; 8]>,
) -> Self {
Self {
magic: magic.unwrap_or_default(),
rqvn: rqvn.into(),
rql: rql.into(),
iv,
reserved1c: [0; 4],
reserved20: [0; 7],
nks,
reserved28: 0,
sea: sea.into(),
}
}
}
#[cfg(test)]
mod tests {
use zerocopy::IntoBytes;
use super::*;
static TEST_MAGIC: [u8; 8] = 0x12345689abcdef00u64.to_be_bytes();
#[test]
fn req_hdr() {
let hdr = RequestHdr::new(0x200, 22, [0x11; 12], 15, 44, None);
let hdr_bin = hdr.as_bytes();
let hdr_bin_exp = [
0u8, 0, 0, 0, 0, 0, 0, 0, // magic
0, 0, 2, 0, // vers
0, 0, 0, 22, // size
0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, // iv
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // res
15, // nks
0, 0, 0, 0, // res
0, 0, 0, 44, // sea
];
assert_eq!(hdr_bin, &hdr_bin_exp);
}
#[test]
fn req_hdr2() {
let mut hdr = RequestHdr::new(0x200, 0x1234, [0x11; 12], 15, 44, Some(TEST_MAGIC));
let hdr_bin = hdr.as_mut_bytes();
let hdr_bin_exp = [
0x12, 0x34, 0x56, 0x89, 0xab, 0xcd, 0xef, 0, // magic
0, 0, 2, 0, // vers
0, 0, 0x12, 0x34, // size
0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, // iv
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // res
15, // nks
0, 0, 0, 0, // res
0, 0, 0, 44, // sea
];
assert_eq!(hdr_bin, &hdr_bin_exp);
}
}

View File

@@ -0,0 +1,30 @@
// SPDX-License-Identifier: MIT
//
// Copyright IBM Corp.
//! Host key types for UV requests
use openssl::pkey::{PKey, Public};
/// Versioned host keys container
#[non_exhaustive]
#[derive(Clone, Debug)]
pub enum HostKey {
/// ECDH public key
V1(PKey<Public>),
}
impl HostKey {
/// Return the ECDH public key
pub fn ec_key(&self) -> &PKey<Public> {
match self {
HostKey::V1(ec_key) => ec_key,
}
}
}
impl AsRef<HostKey> for HostKey {
fn as_ref(&self) -> &HostKey {
self
}
}

150
rust/pv/src/req/keyslot.rs Normal file
View File

@@ -0,0 +1,150 @@
// SPDX-License-Identifier: MIT
//
// Copyright IBM Corp.
//! IBM Z Host key-slot implementations.
use openssl::hash::MessageDigest;
use openssl::pkey::{PKey, PKeyRef, Private, Public};
use super::ec_coord::EcPubKeyCoord;
use super::encrypt::Encrypt;
use crate::crypto::{derive_aes256_gcm_key, encrypt_aead, hash};
use crate::request::HostKey;
use crate::Result;
/// IBM Z Host key-slot
///
/// Layout in binary format:
/// ```none
/// _______________________________________________________________
/// | Public Host Key Hash (32) |
/// | Wrapped(=Encrypted) Request Protection Key(32) |
/// | Key Slot Tag (16) |
/// |_____________________________________________________________|
/// ```
#[derive(Debug, Clone)]
pub struct KeyslotV1(PKey<Public>);
impl KeyslotV1 {
/// Size of a host-key hash
pub const PHKH_SIZE: u32 = 0x20;
/// Size of complete V1 keyslot in bytes
pub const SIZE: usize = 80;
/// Creates a new Keyslot from the provided public key
pub fn new(hostkey: PKey<Public>) -> Self {
Self(hostkey)
}
}
impl Encrypt for KeyslotV1 {
/// Encrypts the given request protection key `prot_key`.
///
/// The AES256 encryption key is derived from `self` as public key, and `priv_key` as private
/// key.
///
/// # Returns
/// The encrypted Keyslot.
///
/// # Errors
///
/// This function will return an error if OpenSSL could not encrypt the secret.
fn encrypt_to(
&self,
prot_key: &[u8],
priv_key: &PKeyRef<Private>,
to: &mut Vec<u8>,
) -> Result<()> {
let derived_key = derive_aes256_gcm_key(priv_key, &self.0)?;
let mut wrpk_and_kst =
encrypt_aead(&derived_key.into(), &[0; 12], &[], prot_key)?.into_buf();
assert_eq!(wrpk_and_kst.len(), 48);
let phk: EcPubKeyCoord = self.0.as_ref().try_into()?;
to.reserve(80);
let hash = hash(MessageDigest::sha256(), phk.as_ref())?;
assert_eq!(hash.len(), 32);
to.extend_from_slice(&hash);
to.append(&mut wrpk_and_kst);
Ok(())
}
}
/// Versioned keyslot container
#[non_exhaustive]
#[derive(Debug, Clone)]
pub enum Keyslot {
/// V1 key-slots with ECDH keys
V1(KeyslotV1),
}
impl Keyslot {
/// Return a keyslot with the same key-type as the given host-key
pub fn new(hostkey: HostKey) -> Self {
match hostkey {
HostKey::V1(key) => Keyslot::V1(KeyslotV1::new(key)),
}
}
/// Return the public host key hash size for the given version of the key-slot in bytes
pub fn phkh_size(&self) -> u32 {
match self {
Keyslot::V1(_) => KeyslotV1::PHKH_SIZE,
}
}
/// Return the size of the key-slot in bytes
pub fn size(&self) -> usize {
match self {
Keyslot::V1(_) => KeyslotV1::SIZE,
}
}
/// Return whether the key-slot uses hybrid keys
pub fn is_hybrid(&self) -> bool {
match self {
Keyslot::V1(_) => false,
}
}
}
impl Encrypt for Keyslot {
fn encrypt_to(
&self,
secret: &[u8],
priv_key: &PKeyRef<Private>,
to: &mut Vec<u8>,
) -> Result<()> {
match self {
Keyslot::V1(ks) => ks.encrypt_to(secret, priv_key, to),
}
}
}
impl From<PKey<Public>> for Keyslot {
fn from(key: PKey<Public>) -> Self {
Keyslot::V1(KeyslotV1::new(key))
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::get_test_asset;
use crate::test_utils::*;
#[test]
fn keyslot() {
let (cust_key, host_key) = get_test_keys();
let exp_keyslot = get_test_asset!("exp/keyslot.bin").to_vec();
let keyslot = KeyslotV1(host_key);
let encr_ks = keyslot.encrypt(&[0x17u8; 32], &cust_key).unwrap();
assert_eq!(exp_keyslot, encr_ks);
let encr_ks = keyslot.encrypt(&[0x16u8; 32], &cust_key).unwrap();
assert_ne!(exp_keyslot, encr_ks);
}
}

25
rust/pv/src/req/mod.rs Normal file
View File

@@ -0,0 +1,25 @@
// SPDX-License-Identifier: MIT
//
// Copyright IBM Corp.
//! Request encryption and key management for IBM Z Ultravisor.
//!
//! This module provides functionality for creating and encrypting requests to the
//! IBM Z Ultravisor, including host key management, keyslot encryption, and request
//! context handling.
mod context;
mod ec_coord;
mod encrypt;
mod header;
mod hostkey;
mod keyslot;
mod request;
// Re-export public types
pub use context::ReqEncrCtx;
pub use ec_coord::EcPubKeyCoord;
pub use encrypt::{Aad, Encrypt};
pub use header::RequestHdr;
pub use hostkey::HostKey;
pub use keyslot::{Keyslot, KeyslotV1};
pub use request::{BinReqValues, Request};

265
rust/pv/src/req/request.rs Normal file
View File

@@ -0,0 +1,265 @@
// SPDX-License-Identifier: MIT
//
// Copyright IBM Corp.
use std::mem::size_of;
use zerocopy::{FromBytes, Immutable, KnownLayout};
use super::HostKey;
use crate::crypto::{decrypt_aead, SymKey, SymKeyType};
use crate::req::context::ReqEncrCtx;
use crate::req::header::RequestHdr;
use crate::request::Confidential;
use crate::{Error, Result};
/// A trait representing a request for the Ultravisor.
///
/// All requests share a few things:
/// * All requests need to be encrypted on a trusted machine
/// * All requests have at least one Hostkeyslot
///
/// The encryption setup is handled by [`ReqEncrCtx`]. Implementers need to pass the data to the
/// `ReqEncrCtx` when implementing `encrypt`. A hostkey should be represented by
/// [`Keyslot`](super::Keyslot) during encryption.
///
/// An UV request consists of an authenticated area (AAD), an encrypted area (Encr) and a 16 byte
/// tag. The AAD contains a general header and Request type defined data (including Keyslots). It
/// is encrypted with an Request protection key (symmetric). This key is encrypted with a
/// (generated) private key and the public key of the host system (Host key)
/// ```none
/// _______________________________________________________________
/// | MAGIC (8) Version Number (4) Size (4)|
/// | IV (12) Reserved (4)|
/// | Reserved (7) Num keyslots (1) Reserved(4) Encr Size (4)|
/// | --------------------------------------------------- |
/// | Request type dependent AAD data |
/// | ---------------------------------------------------- |
/// | Encrypted (request type dependent) data |
/// | ---------------------------------------------------- |
/// | AES GCM Tag (16) |
/// |_____________________________________________________________|
/// ```
pub trait Request {
/// Encrypt the request into its binary format
///
/// # Errors
///
/// This function will return an error if the encryption fails, the request does not have at
/// least a hostkey, or other implementation dependent contracts are not met.
fn encrypt(&self, ctx: &ReqEncrCtx) -> Result<Vec<u8>>;
/// Add a host-key to this request
///
/// Must be called at least once, otherwise {`Request::encrypt`} will fail
fn add_hostkey(&mut self, hostkey: HostKey);
}
/// A struct to represent some parts of a binary/encrypted request.
#[derive(Debug)]
#[allow(clippy::len_without_is_empty)]
pub struct BinReqValues<'a> {
iv: &'a [u8],
aad: &'a [u8],
req_dep_aad: &'a [u8],
encr: &'a [u8],
tag: &'a [u8],
version: u32,
len: usize,
}
impl<'a> BinReqValues<'a> {
pub(crate) const TAG_LEN: usize = SymKeyType::AES_256_GCM_TAG_LEN;
/// Get the locations from this request.
///
/// Does minimal sanity test, just tests to prevent panics.
/// `req` may be larger than the actual request.
pub(crate) fn get(req: &'a [u8]) -> Result<Self> {
let (hdr, _) = RequestHdr::read_from_prefix(req).map_err(|_| Error::BinRequestSmall)?;
let rql = hdr.rql.get() as usize;
let sea = hdr.sea.get() as usize;
if rql < req.len() || sea + Self::TAG_LEN > rql {
return Err(Error::BinRequestSmall);
}
let aad_size = rql - sea - Self::TAG_LEN;
if aad_size < size_of::<RequestHdr>() {
return Err(Error::BinRequestSmall);
}
let iv = &req[0x10..0x1c];
let aad = &req[..aad_size];
let req_dep_aad = &req[size_of::<RequestHdr>()..aad_size];
let encr = &req[aad_size..(aad_size + sea)];
let tag = &req[rql - Self::TAG_LEN..];
Ok(Self {
iv,
aad,
req_dep_aad,
encr,
tag,
version: hdr.rqvn.get(),
len: rql,
})
}
/// Returns the version of this [`BinReqValues`].
pub(crate) fn version(&self) -> u32 {
self.version
}
/// Returns the length of this [`BinReqValues`].
pub(crate) fn len(&self) -> usize {
self.len
}
/// Returns the size of the encrypted area
pub(crate) fn sea(&self) -> u32 {
self.encr.len() as u32
}
/// Decrypts the encrypted area with the provided key
pub(crate) fn decrypt(&self, key: &SymKey) -> Result<Confidential<Vec<u8>>> {
let result = decrypt_aead(key, self.iv, self.aad, self.encr, self.tag)?;
Ok(result.into_plain())
}
/// Returns a reference to the request dependent authenticated area of this [`BinReqValues`]
/// already interpreted.
///
/// If target struct is larger than the request depended-AAD None is returned. See
/// [`FromBytes::ref_from_prefix`]
pub(crate) fn req_dep_aad<T>(&self) -> Option<&T>
where
T: FromBytes + Sized + Immutable + KnownLayout,
{
T::ref_from_prefix(self.req_dep_aad).map(|s| s.0).ok()
}
/// Returns a reference to the tag of this [`BinReqValues`].
pub(crate) fn tag(&self) -> &[u8] {
self.tag
}
}
#[cfg(test)]
mod tests {
use zerocopy::IntoBytes;
use super::*;
use crate::get_test_asset;
use crate::req::header::RequestHdr;
use crate::req::{Aad, HostKey, Keyslot, ReqEncrCtx};
use crate::request::SymKey;
use crate::test_utils::*;
static TEST_MAGIC: [u8; 8] = 0x12345689abcdef00u64.to_be_bytes();
#[test]
fn encr_build_aad() {
let (cust_key, host_key) = get_test_keys();
let ks = Keyslot::new(HostKey::V1(host_key));
let ctx = ReqEncrCtx::new_aes_256(
Some([0x11; 12]),
Some(cust_key),
Some(SymKey::Aes256([0x17; 32].into())),
)
.unwrap();
let v = [0x55; 8];
let aad = Aad::Plain(&v);
let aad = ctx
.build_aad(0x200, &vec![aad, Aad::Ks(&ks)], 16, Some(TEST_MAGIC))
.unwrap();
let mut aad_exp = vec![
0x12, 0x34, 0x56, 0x89, 0xab, 0xcd, 0xef, 0, // progr
0, 0, 2, 0, // vers
0, 0, 0, 168, // size
0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, // iv
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // res
1, // nks
0, 0, 0, 0, // res
0, 0, 0, 16, // sea
0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, // aad
];
aad_exp.extend_from_slice(get_test_asset!("exp/keyslot.bin"));
assert_eq!(&aad, &aad_exp);
}
#[test]
fn encr_build_aad_nks_no() {
let ctx = ReqEncrCtx::new_aes_256(Some([0x11; 12]), None, None).unwrap();
let aad = Vec::<Aad>::new();
let aad = ctx.build_aad(0x200, &aad, 16, Some(TEST_MAGIC));
assert!(matches!(aad, Err(Error::NoHostkey)));
}
#[test]
fn encr_build_aad_nks_many() {
let (_, host_key) = get_test_keys();
let host_key = HostKey::V1(host_key);
let ctx = ReqEncrCtx::new_aes_256(Some([0x11; 12]), None, None).unwrap();
let ks: Vec<Keyslot> = (0..257).map(|_| Keyslot::new(host_key.clone())).collect();
let mut aad = Vec::<Aad>::new();
ks.iter().for_each(|ks| aad.push(Aad::Ks(ks)));
let aad = ctx.build_aad(0x200, &aad, 16, Some(TEST_MAGIC));
assert!(matches!(aad, Err(Error::ManyHostkeys)));
}
#[test]
fn encr_build_aad_nks() {
let (_, host_key) = get_test_keys();
let host_key = HostKey::V1(host_key);
let ctx = ReqEncrCtx::new_aes_256(Some([0x11; 12]), None, None).unwrap();
let ks = [
Keyslot::new(host_key.clone()),
Keyslot::new(host_key.clone()),
Keyslot::new(host_key),
];
let mut aad = Vec::<Aad>::new();
ks.iter().for_each(|ks| aad.push(Aad::Ks(ks)));
let aad = ctx.build_aad(0x200, &aad, 16, Some(TEST_MAGIC)).unwrap();
assert_eq!(aad.get(39).unwrap(), &3u8);
}
#[test]
fn req_hdr() {
let hdr = RequestHdr::new(0x200, 22, [0x11; 12], 15, 44, None);
let hdr_bin = hdr.as_bytes();
let hdr_bin_exp = [
0u8, 0, 0, 0, 0, 0, 0, 0, // magic
0, 0, 2, 0, // vers
0, 0, 0, 22, // size
0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, // iv
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // res
15, // nks
0, 0, 0, 0, // res
0, 0, 0, 44, // sea
];
assert_eq!(hdr_bin, &hdr_bin_exp);
}
#[test]
fn req_hdr2() {
let mut hdr = RequestHdr::new(0x200, 0x1234, [0x11; 12], 15, 44, Some(TEST_MAGIC));
let hdr_bin = hdr.as_mut_bytes();
let hdr_bin_exp = [
0x12, 0x34, 0x56, 0x89, 0xab, 0xcd, 0xef, 0, // magic
0, 0, 2, 0, // vers
0, 0, 0x12, 0x34, // size
0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, // iv
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // res
15, // nks
0, 0, 0, 0, // res
0, 0, 0, 44, // sea
];
assert_eq!(hdr_bin, &hdr_bin_exp);
}
}

View File

@@ -4,19 +4,15 @@
// DO NOT USE ANY OF THESE ITEMS IN PRODUCTION CODE
// USED FOR INTERNAL UNIT AND FVT TESTING ONLY!!!
use std::{
fs,
path::{Path, PathBuf},
};
use std::fs;
use std::path::{Path, PathBuf};
use openssl::{
bn::BigNum,
ec::{EcGroup, EcKey},
error::ErrorStack,
nid::Nid,
pkey::{PKey, Private, Public},
x509::{X509Crl, X509},
};
use openssl::bn::BigNum;
use openssl::ec::{EcGroup, EcKey};
use openssl::error::ErrorStack;
use openssl::nid::Nid;
use openssl::pkey::{PKey, Private, Public};
use openssl::x509::{X509Crl, X509};
/// TEST ONLY! Loads the specified asset into the binary at compile time.
///

View File

@@ -1,11 +1,10 @@
// SPDX-License-Identifier: MIT
//
// Copyright IBM Corp. 2023
use openssl::error::ErrorStack;
use openssl::x509::{X509Crl, X509};
use crate::{Error, Result};
use openssl::{
error::ErrorStack,
x509::{X509Crl, X509},
};
/// Read all CRLs from the buffer and parse them into a vector.
///

View File

@@ -2,18 +2,17 @@
//
// Copyright IBM Corp. 2024
use serde::Serialize;
use std::fmt::Display;
use crate::req::Keyslot;
use crate::static_assert;
use crate::{Error, Result};
use serde::Serialize;
use super::arcb::AttestationFlags;
use crate::req::KeyslotV1;
use crate::{static_assert, Error, Result};
/// Hash for additional-data stuff used for parsing [`AdditionalData`]
pub(super) const PHKH_SIZE: u32 = 0x20;
static_assert!(Keyslot::PHKH_SIZE == PHKH_SIZE);
static_assert!(KeyslotV1::PHKH_SIZE == PHKH_SIZE);
pub(super) const SECRET_STORE_HASH_SIZE: u32 = 0x40;
pub(super) const FW_STATE_SIZE: u32 = 0x140;

View File

@@ -2,27 +2,19 @@
//
// Copyright IBM Corp. 2024
use openssl::pkey::{PKey, Public};
use std::mem::size_of;
use zerocopy::{BigEndian, FromBytes, Immutable, IntoBytes, KnownLayout, U32};
use crate::{
assert_size,
attest::{AttestationMagic, AttestationMeasAlg},
crypto::random_array,
misc::Flags,
req::{Aad, BinReqValues, Keyslot, ReqEncrCtx},
request::{Confidential, MagicValue, Request, RequestVersion, SymKey, Zeroize},
static_assert,
uv::UvFlags,
Error, Result,
};
use super::{
additional::{FW_STATE_SIZE, PHKH_SIZE, SECRET_STORE_HASH_SIZE},
AttNonce,
};
use super::additional::{FW_STATE_SIZE, PHKH_SIZE, SECRET_STORE_HASH_SIZE};
use super::AttNonce;
use crate::attest::{AttestationMagic, AttestationMeasAlg};
use crate::crypto::random_array;
use crate::misc::Flags;
use crate::req::{Aad, BinReqValues, HostKey, Keyslot, ReqEncrCtx};
use crate::request::{Confidential, MagicValue, Request, RequestVersion, SymKey, Zeroize};
use crate::uv::UvFlags;
use crate::{assert_size, static_assert, Error, Result};
#[cfg(doc)]
use crate::{
request::SymKeyType,
@@ -59,7 +51,7 @@ use crate::{
///
/// ```rust,no_run
/// # use s390_pv::attest::{AttestationFlags, AttestationMeasAlg, AttestationRequest, AttestationVersion};
/// # use s390_pv::request::{SymKeyType, Request, ReqEncrCtx};
/// # use s390_pv::request::{SymKeyType, Request, ReqEncrCtx, HostKey};
/// # fn main() -> s390_pv::Result<()> {
/// let att_version = AttestationVersion::One;
/// let meas_alg = AttestationMeasAlg::HmacSha512;
@@ -68,11 +60,11 @@ use crate::{
/// let hkd = s390_pv::misc::read_certs(&std::fs::read("host-key-document.crt")?)?;
/// // IBM issued HKD certificates typically have one X509
/// let hkd = hkd.first().unwrap().public_key()?;
/// arcb.add_hostkey(hkd);
/// arcb.add_hostkey(HostKey::V1(hkd));
/// // you can add multiple hostkeys
/// // arcb.add_hostkey(another_hkd);
/// // encrypt it
/// let ctx = ReqEncrCtx::random(SymKeyType::Aes256)?;
/// let ctx = ReqEncrCtx::random(SymKeyType::Aes256Gcm)?;
/// let arcb = arcb.encrypt(&ctx)?;
/// # Ok(())
/// # }
@@ -243,7 +235,7 @@ impl Request for AttestationRequest {
ctx.encrypt_aead(&aad, conf).map(|res| res.into_buf())
}
fn add_hostkey(&mut self, hostkey: PKey<Public>) {
fn add_hostkey(&mut self, hostkey: HostKey) {
self.keyslots.push(Keyslot::new(hostkey))
}
}
@@ -419,8 +411,9 @@ impl Zeroize for ReqConfData {
#[cfg(test)]
mod test {
use super::*;
use crate::{get_test_asset, request::SymKey, test_utils::get_test_keys};
use crate::get_test_asset;
use crate::request::SymKey;
use crate::test_utils::get_test_keys;
const ARPK: [u8; 32] = [0x17; 32];
const NONCE: [u8; 16] = [0xab; 16];
@@ -450,7 +443,7 @@ mod test {
arcb.conf.value_mut().nonce = NONCE;
arcb.conf.value_mut().meas_key = MEAS;
arcb.add_hostkey(host_key);
arcb.add_hostkey(HostKey::V1(host_key));
arcb.encrypt(&ctx).unwrap()
}

View File

@@ -2,20 +2,21 @@
//
// Copyright IBM Corp. 2024
use super::AttNonce;
use crate::{
attest::AttestationMeasAlg, brcb::BootHdrTags, crypto::calculate_hmac, request::Confidential,
uv::ConfigUid, Result,
};
use openssl::{
hash::MessageDigest,
pkey::{PKeyRef, Private},
};
use std::mem::size_of;
use openssl::hash::MessageDigest;
use openssl::pkey::{PKeyRef, Private};
use zerocopy::{BigEndian, IntoBytes, U16, U32};
use super::AttNonce;
use crate::attest::AttestationMeasAlg;
#[cfg(doc)]
use crate::attest::AttestationRequest;
use crate::brcb::BootHdrTags;
use crate::crypto::calculate_hmac;
use crate::request::Confidential;
use crate::uv::ConfigUid;
use crate::Result;
/// Holds the data to be measured.
///
@@ -151,9 +152,10 @@ impl From<Vec<u8>> for AttestationMeasurement {
#[cfg(test)]
mod test {
use super::*;
use openssl::pkey::PKey;
use super::*;
const M_KEY: [u8; 64] = [0x41; 64];
const BOOT_HDR_TAGS: BootHdrTags = BootHdrTags::new([1; 64], [2; 64], [3; 64], [4; 16]);
const CUID: [u8; 16] = [5; 16];

View File

@@ -2,24 +2,23 @@
//
// Copyright IBM Corp. 2023
use super::{guest_secret::ListableSecretHdr, user_data::UserData};
use crate::{
assert_size,
crypto::{hkdf_rfc_5869, AeadEncryptionResult},
misc::Flags,
req::{Aad, BinReqValues, Keyslot, ReqEncrCtx},
request::{BootHdrTags, Confidential, Request},
secret::{ExtSecret, GuestSecret},
uv::{ConfigUid, UvFlags},
Result,
};
use openssl::{
md::Md,
pkey::{PKey, Private, Public},
};
use pv_core::{request::RequestVersion, secret::AddSecretMagic, uv::SecretId};
use openssl::md::Md;
use openssl::pkey::{PKey, Private};
use pv_core::request::RequestVersion;
use pv_core::secret::AddSecretMagic;
use pv_core::uv::SecretId;
use zerocopy::{Immutable, IntoBytes};
use super::guest_secret::ListableSecretHdr;
use super::user_data::UserData;
use crate::crypto::{hkdf_rfc_5869, AeadEncryptionResult};
use crate::misc::Flags;
use crate::req::{Aad, BinReqValues, HostKey, Keyslot, ReqEncrCtx};
use crate::request::{BootHdrTags, Confidential, Request};
use crate::secret::{ExtSecret, GuestSecret};
use crate::uv::{ConfigUid, UvFlags};
use crate::{assert_size, Result};
/// Authenticated data w/o user data
#[repr(C)]
#[derive(Debug, Clone, Copy, IntoBytes, Immutable)]
@@ -310,7 +309,7 @@ impl Request for AddSecretRequest {
}
}
fn add_hostkey(&mut self, hostkey: PKey<Public>) {
fn add_hostkey(&mut self, hostkey: HostKey) {
self.keyslots.push(Keyslot::new(hostkey))
}
}

View File

@@ -2,31 +2,25 @@
//
// Copyright IBM Corp. 2023
#[allow(unused_imports)] // used for more convenient docstring
use super::asrcb::AddSecretRequest;
use crate::{
assert_size,
crypto::{hash, random_array, SymKeyType},
request::{
openssl::{NID_ED25519, NID_ED448},
Confidential,
},
uv::{
AesSizes, AesXtsSizes, EcCurves, HmacShaSizes, ListableSecretType, RetrievableSecret,
RetrieveCmd, SecretId,
},
Error, Result,
};
use openssl::{
hash::MessageDigest,
nid::Nid,
pkey::{Id, PKey, PKeyRef, Private},
};
use std::fmt::Display;
use openssl::hash::MessageDigest;
use openssl::nid::Nid;
use openssl::pkey::{Id, PKey, PKeyRef, Private};
use pv_core::static_assert;
use serde::{Deserialize, Serialize};
use std::fmt::Display;
use zerocopy::{BigEndian, KnownLayout};
use zerocopy::{FromBytes, Immutable, IntoBytes, U16, U32};
use zerocopy::{BigEndian, FromBytes, Immutable, IntoBytes, KnownLayout, U16, U32};
#[allow(unused_imports)] // used for more convenient docstring
use super::asrcb::AddSecretRequest;
use crate::crypto::{hash, random_array, SymKeyType};
use crate::request::openssl::{NID_ED25519, NID_ED448};
use crate::request::Confidential;
use crate::uv::{
AesSizes, AesXtsSizes, EcCurves, HmacShaSizes, ListableSecretType, RetrievableSecret,
RetrieveCmd, SecretId,
};
use crate::{assert_size, Error, Result};
const ASSOC_SECRET_SIZE: usize = 32;
const CCK_SIZE: usize = 32;
@@ -476,13 +470,13 @@ impl UpdateCckHdr {
#[cfg(test)]
mod test {
use super::HmacShaSizes as HmacSizes;
use super::RetrievableSecret::*;
use super::*;
use openssl::ec::{EcGroup, EcKey};
use pv_core::uv::AesSizes;
use serde_test::{assert_tokens, Token};
use super::RetrievableSecret::*;
use super::{HmacShaSizes as HmacSizes, *};
#[test]
fn association() {
let secret_value = [0x11; 32];

View File

@@ -2,15 +2,15 @@
//
// Copyright IBM Corp. 2024
use crate::{crypto::SymKeyType, pem::Pem, uvsecret::guest_secret::MAX_SIZE_PLAIN_PAYLOAD, Result};
use log::warn;
use pv_core::{
request::Confidential,
uv::{ListableSecretType, RetrievableSecret, RetrieveCmd},
};
use zerocopy::BigEndian;
use zerocopy::{FromBytes, U16};
use pv_core::request::Confidential;
use pv_core::uv::{ListableSecretType, RetrievableSecret, RetrieveCmd};
use zerocopy::{BigEndian, FromBytes, U16};
use crate::crypto::SymKeyType;
use crate::pem::Pem;
use crate::uvsecret::guest_secret::MAX_SIZE_PLAIN_PAYLOAD;
use crate::Result;
/// An IBM Protected Key
///
@@ -72,8 +72,8 @@ impl From<RetrieveCmd> for RetrievedSecret {
match kind {
ListableSecretType::Retrievable(RetrievableSecret::PlainText) => {
// Will not run into default, retrieve has a granularity of 16 bytes and 16 bytes is the
// minimum size
// Will not run into default, retrieve has a granularity of 16 bytes and 16 bytes is
// the minimum size
let len = U16::<BigEndian>::read_from_prefix(key.value())
.unwrap_or_default()
.0
@@ -81,8 +81,8 @@ impl From<RetrieveCmd> for RetrievedSecret {
// Test if the plain text secret has a size:
// 1. len <= 8190
// 2. first two bytes are max 15 less than buffer-size+2 i.e. smaller than the
// block length
// 2. first two bytes are max 15 less than buffer-size+2 i.e. smaller than the block
// length
// 3. bytes after len + 2 are zero
match len <= MAX_SIZE_PLAIN_PAYLOAD
&& key.value().len() - (len + 2) < SymKeyType::AES_256_GCM_BLOCK_LEN
@@ -148,9 +148,10 @@ impl RetrievedSecret {
#[cfg(test)]
mod test {
use super::*;
use pv_core::uv::*;
use super::*;
fn mk_retr(secret: &[u8]) -> RetrievedSecret {
let entry = SecretEntry::new(
0,

View File

@@ -2,21 +2,17 @@
//
// Copyright IBM Corp. 2024
use crate::assert_size;
use crate::{
crypto::{sign_msg, verify_signature},
req::BinReqValues,
request::{
openssl::pkey::{HasParams, HasPublic, Id, PKey, PKeyRef, Private, Public},
RequestMagic,
},
secret::{AddSecretMagic, AddSecretRequest, AddSecretVersion, UserDataType},
Error, Result,
};
use openssl::hash::MessageDigest;
use openssl::nid::Nid;
use zerocopy::{BigEndian, FromBytes, IntoBytes, KnownLayout, U16};
use crate::crypto::{sign_msg, verify_signature};
use crate::req::BinReqValues;
use crate::request::openssl::pkey::{HasParams, HasPublic, Id, PKey, PKeyRef, Private, Public};
use crate::request::RequestMagic;
use crate::secret::{AddSecretMagic, AddSecretRequest, AddSecretVersion, UserDataType};
use crate::{assert_size, Error, Result};
/// User data.
///
/// User defined data can be:
@@ -380,7 +376,8 @@ impl From<VerifiedUserData> for Vec<u8> {
#[cfg(test)]
mod test {
use super::*;
use crate::{get_test_asset, test_utils::get_test_keys};
use crate::get_test_asset;
use crate::test_utils::get_test_keys;
#[test]
fn sign_null() {

View File

@@ -2,22 +2,22 @@
//
// Copyright IBM Corp. 2023
use crate::openssl_extensions::{StackableX509Crl, X509StoreContextExtension, X509StoreExtension};
use core::slice;
use std::path::Path;
#[cfg(not(test))]
use helper::download_first_crl_from_x509;
use log::{debug, trace};
use openssl::error::ErrorStack;
use openssl::stack::Stack;
use openssl::x509::store::X509Store;
use openssl::x509::{CrlStatus, X509NameRef, X509Ref, X509StoreContext, X509StoreContextRef, X509};
use std::path::Path;
#[cfg(not(test))]
use helper::download_first_crl_from_x509;
#[cfg(test)]
use test::download_first_crl_from_x509;
use crate::error::bail_hkd_verify;
use crate::misc::{read_certs, read_file};
use crate::openssl_extensions::{StackableX509Crl, X509StoreContextExtension, X509StoreExtension};
use crate::Result;
mod helper;
@@ -167,7 +167,8 @@ impl CertVerifier {
/// * `cert_paths` - Paths to certificates for the chain of trust
/// * `crl_paths` - Paths to certificate revocation lists for the chain of trust
/// * `root_ca_path` - Path to the root of trust
/// * `offline` - if set to true the verification process will not try to download CRLs from the internet.
/// * `offline` - if set to true the verification process will not try to download CRLs from the
/// internet.
///
/// # Errors
///

View File

@@ -2,27 +2,28 @@
//
// Copyright IBM Corp. 2023
use std::cmp::Ordering;
use std::ffi::c_int;
use std::path::Path;
use std::str::from_utf8;
use log::debug;
use openssl::asn1::{Asn1Time, Asn1TimeRef};
use openssl::error::ErrorStack;
use openssl::nid::Nid;
use openssl::ssl::SslFiletype;
use openssl::stack::Stack;
use openssl::x509::store::{File, X509Lookup, X509StoreBuilder, X509StoreRef};
use openssl::x509::verify::{X509VerifyFlags, X509VerifyParam};
use openssl::x509::{
X509CrlRef, X509Name, X509NameRef, X509PurposeId, X509Ref, X509StoreContext,
X509StoreContextRef, X509VerifyResult, X509,
};
use crate::error::bail_hkd_verify;
use crate::openssl_extensions::{AkidCheckResult, AkidExtension};
use crate::HkdVerifyErrorType::*;
use crate::{Error, Result};
use log::debug;
use openssl::{
asn1::{Asn1Time, Asn1TimeRef},
error::ErrorStack,
nid::Nid,
ssl::SslFiletype,
stack::Stack,
x509::{
store::{File, X509Lookup, X509StoreBuilder, X509StoreRef},
verify::{X509VerifyFlags, X509VerifyParam},
X509CrlRef, X509Name, X509NameRef, X509PurposeId, X509Ref, X509StoreContext,
X509StoreContextRef, X509VerifyResult, X509,
},
};
use std::path::Path;
use std::str::from_utf8;
use std::{cmp::Ordering, ffi::c_int};
/// 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>
@@ -306,9 +307,11 @@ pub fn x509_dist_points(cert: &X509Ref) -> Vec<String> {
/// CRL not available at all URIs and unexpected format at all URIs are mapped to Ok(None)
#[cfg(not(test))]
pub fn download_first_crl_from_x509(cert: &X509Ref) -> Result<Option<Vec<openssl::x509::X509Crl>>> {
use crate::utils::read_crls;
use curl::easy::{Easy2, Handler, WriteError};
use std::time::Duration;
use curl::easy::{Easy2, Handler, WriteError};
use crate::utils::read_crls;
const CRL_TIMEOUT_MAX: Duration = Duration::from_secs(3);
struct Buf(Vec<u8>);
@@ -397,9 +400,10 @@ pub fn armonk_locality_fixup(subject: &X509NameRef) -> Option<X509Name> {
/// tests for some private functions
mod test {
use std::time::{Duration, SystemTime};
use super::*;
use crate::test_utils::*;
use std::time::{Duration, SystemTime};
fn sys_to_asn1_time(syst: SystemTime) -> Asn1Time {
let secs = syst

View File

@@ -4,12 +4,17 @@
#![cfg(test)]
use super::{helper, helper::*, *};
use crate::{utils::read_crls, Error, HkdVerifyErrorType::*};
use openssl::{stack::Stack, x509::X509Crl};
use std::path::Path;
use openssl::stack::Stack;
use openssl::x509::X509Crl;
use super::helper::*;
use super::{helper, *};
use crate::test_utils::*;
use crate::utils::read_crls;
use crate::Error;
use crate::HkdVerifyErrorType::*;
// Mock function
pub fn download_first_crl_from_x509(cert: &X509Ref) -> Result<Option<Vec<X509Crl>>> {

View File

@@ -3,25 +3,18 @@
// Copyright IBM Corp. 2023
#![allow(missing_docs)]
use openssl::{
ec::{EcGroup, EcKey},
nid::Nid,
pkey::Private,
};
use s390_pv::{
get_test_asset,
request::{
openssl::pkey::{PKey, Public},
BootHdrTags, ReqEncrCtx, Request, SymKey,
},
secret::{
verify_asrcb_and_get_user_data, AddSecretFlags, AddSecretRequest, AddSecretVersion,
ExtSecret, GuestSecret,
},
test_utils::get_test_keys,
uv::ConfigUid,
Result,
use openssl::ec::{EcGroup, EcKey};
use openssl::nid::Nid;
use openssl::pkey::Private;
use s390_pv::request::openssl::pkey::{PKey, Public};
use s390_pv::request::{BootHdrTags, HostKey, ReqEncrCtx, Request, SymKey};
use s390_pv::secret::{
verify_asrcb_and_get_user_data, AddSecretFlags, AddSecretRequest, AddSecretVersion, ExtSecret,
GuestSecret,
};
use s390_pv::test_utils::get_test_keys;
use s390_pv::uv::ConfigUid;
use s390_pv::{get_test_asset, Result};
const TAGS: BootHdrTags = BootHdrTags::new([1; 64], [2; 64], [3; 64], [4; 16]);
const CUID: ConfigUid = [0x42u8; 16];
@@ -33,7 +26,7 @@ fn create_asrcb(
ext_secret: Option<ExtSecret>,
flags: AddSecretFlags,
cuid: Option<ConfigUid>,
hkd: PKey<Public>,
hkd: HostKey,
ctx: &ReqEncrCtx,
) -> Result<Vec<u8>> {
let mut asrcb = AddSecretRequest::new(AddSecretVersion::One, guest_secret, TAGS, flags);
@@ -74,7 +67,14 @@ where
true => Some(CUID),
false => None,
};
create_asrcb(guest_secret, ext_secret.into(), flags, cuid, host_key, &ctx)
create_asrcb(
guest_secret,
ext_secret.into(),
flags,
cuid,
HostKey::V1(host_key),
&ctx,
)
}
fn association() -> GuestSecret {
@@ -98,7 +98,7 @@ fn create_signed_asrcb(skey: PKey<Private>, user_data: Vec<u8>) -> Vec<u8> {
let mut asrcb =
AddSecretRequest::new(AddSecretVersion::One, GuestSecret::Null, TAGS, no_flag());
asrcb.add_hostkey(host_key);
asrcb.add_hostkey(HostKey::V1(host_key));
asrcb.set_user_data(user_data, Some(skey)).unwrap();
asrcb.encrypt(&ctx).unwrap()
}
@@ -110,7 +110,7 @@ fn null_none_default_ncuid_one_user_unsgn() {
let mut asrcb =
AddSecretRequest::new(AddSecretVersion::One, GuestSecret::Null, TAGS, no_flag());
asrcb.add_hostkey(host_key);
asrcb.add_hostkey(HostKey::V1(host_key));
asrcb.set_user_data(user_data_orig.clone(), None).unwrap();
let asrcb = asrcb.encrypt(&ctx).unwrap();
@@ -243,7 +243,7 @@ fn null_none_default_cuid_seven() {
let (hkd, ctx) = get_crypto();
let mut asrcb =
AddSecretRequest::new(AddSecretVersion::One, GuestSecret::Null, TAGS, no_flag());
(0..7).for_each(|_| asrcb.add_hostkey(hkd.clone()));
(0..7).for_each(|_| asrcb.add_hostkey(HostKey::V1(hkd.clone())));
asrcb.set_cuid(CUID);
let asrcb = asrcb.encrypt(&ctx).unwrap();

View File

@@ -3,10 +3,12 @@
// Copyright IBM Corp. 2023
#![allow(missing_docs)]
use std::ffi::c_int;
use s390_pv::request::CertVerifier;
use s390_pv::test_utils::*;
use s390_pv::{Error, HkdVerifyErrorType::*};
use std::ffi::c_int;
use s390_pv::Error;
use s390_pv::HkdVerifyErrorType::*;
#[track_caller]
fn verify_sign_error(exp_raw: c_int, obs: Error) {

View File

@@ -16,13 +16,13 @@ readme = "README.md"
workspace = true
[dependencies]
libc = "0.2.169"
log = { version = "0.4.25", features = ["std", "release_max_level_debug"] }
thiserror = "2.0.11"
libc = "0.2.186"
log = { version = "0.4.29", features = ["std", "release_max_level_debug"] }
thiserror = "2.0.18"
zerocopy = {version = "0.8", features = ["derive"]}
serde = { version = "1.0.217", features = ["derive"]}
serde = { version = "1.0.228", features = ["derive"]}
byteorder = "1.5"
regex = "1.10"
regex = "1.12"
sha2 = "0.10.9"
[dev-dependencies]

View File

@@ -5,14 +5,12 @@
//! AP support functions
//
use crate::{
utils::{pv_guest_bit_set, read_file_string, write_file},
Error, Result,
};
use std::{fmt, thread, time};
use regex::Regex;
use std::fmt;
use std::thread;
use std::time;
use crate::utils::{pv_guest_bit_set, read_file_string, write_file};
use crate::{Error, Result};
const PATH_SYS_DEVICES_AP: &str = "/sys/devices/ap";

View File

@@ -82,7 +82,8 @@ impl<T: Default> Zeroize for Vec<T> {
impl Zeroize for String {
fn zeroize(&mut self) {
// SAFETY: The Vec<u8> zerorize function overwrites memory with the zero byte -> still valid UTF-8
// SAFETY: The Vec<u8> zerorize function overwrites memory with the zero byte -> still valid
// UTF-8
unsafe { self.as_mut_vec().zeroize() };
}
}

View File

@@ -13,7 +13,6 @@ mod uvdevice;
mod uvsecret;
pub use error::{Error, FileAccessErrorType, FileIoErrorType, Result};
/// Early Boot Customization (EBC) utilities.
///
/// This module provides types and functions for working with Early Boot
@@ -28,13 +27,11 @@ pub mod attest {
/// Miscellaneous functions and definitions
pub mod misc {
pub use crate::utils::pv_guest_bit_set;
pub use crate::utils::{create_file, open_file};
pub use crate::utils::{decode_hex, encode_hex, parse_hex};
pub use crate::utils::{read, write};
pub use crate::utils::{read_exact_file, read_file, read_file_string, write_file};
pub use crate::utils::{to_u16, to_u32, try_parse_u128, try_parse_u64};
pub use crate::utils::{Flags, Lsb0Flags64, Msb0Flags64};
pub use crate::utils::{
create_file, decode_hex, encode_hex, open_file, parse_hex, pv_guest_bit_set, read,
read_exact_file, read_file, read_file_string, to_u16, to_u32, try_parse_u128,
try_parse_u64, write, write_file, Flags, Lsb0Flags64, Msb0Flags64,
};
}
/// Definitions and functions for interacting with the Ultravisor
@@ -43,8 +40,9 @@ pub mod misc {
/// [`crate::uv::UvCmd`]
pub mod uv {
pub use crate::uvdevice::attest::AttestationCmd;
pub use crate::uvdevice::retr_secret::RetrievableSecret;
pub use crate::uvdevice::retr_secret::{AesSizes, AesXtsSizes, EcCurves, HmacShaSizes};
pub use crate::uvdevice::retr_secret::{
AesSizes, AesXtsSizes, EcCurves, HmacShaSizes, RetrievableSecret,
};
pub use crate::uvdevice::secret::{AddCmd, ListCmd, LockCmd, RetrieveCmd};
pub use crate::uvdevice::secret_list::{ListableSecretType, SecretEntry, SecretId, SecretList};
pub use crate::uvdevice::{ConfigUid, UvCmd, UvDevice, UvDeviceInfo, UvFlags, UvcSuccess};
@@ -77,15 +75,12 @@ pub mod request {
/// Functionalities for reading add-secret requests
pub mod secret {
pub use crate::uvsecret::AddSecretMagic;
pub use crate::uvsecret::UserDataType;
pub use crate::uvsecret::{AddSecretMagic, UserDataType};
}
/// Functionalities for the AP bus
pub mod ap {
pub use crate::apdevice::Apqn;
pub use crate::apdevice::RE_QUEUE_DIR;
pub use crate::apdevice::{get_apqn_bind_state, set_apqn_bind_state};
pub use crate::apdevice::{get_apqn_bind_state, set_apqn_bind_state, Apqn, RE_QUEUE_DIR};
/// AP modes
pub mod apqn_mode {
pub use crate::apdevice::ApqnMode::{self, *};

View File

@@ -2,17 +2,17 @@
//
// Copyright IBM Corp.
use std::fmt::{Display, Formatter, Result as Resfmt};
use std::fs::File;
use std::os::unix::ffi::OsStrExt;
use std::path::{Path, PathBuf};
use std::str::from_utf8;
use zerocopy::{FromBytes, Immutable, IntoBytes};
use crate::misc::encode_hex;
use crate::utils::open_file;
use crate::{Error, Result};
use std::{
fmt::{Display, Formatter, Result as Resfmt},
fs::File,
os::unix::ffi::OsStrExt,
path::{Path, PathBuf},
str::from_utf8,
};
use zerocopy::{FromBytes, Immutable, IntoBytes};
const HASH_LEN: usize = 32;
// UserDataType::Unsigned.max() returns 512

View File

@@ -1,18 +1,14 @@
// SPDX-License-Identifier: MIT
//
// Copyright IBM Corp. 2023
use std::{
fs::File,
io::{Read, Write},
path::Path,
};
use std::fs::File;
use std::io::{Read, Write};
use std::path::Path;
use zerocopy::{BigEndian, FromBytes, Immutable, IntoBytes, U64};
use crate::{
macros::{bail_spec, file_error},
Error, FileAccessErrorType, Result,
};
use crate::macros::{bail_spec, file_error};
use crate::{Error, FileAccessErrorType, Result};
/// Trait that describes bitflags, represented by `T`.
pub trait Flags<T>: From<T> + for<'a> From<&'a T> {

View File

@@ -2,9 +2,10 @@
//
// Copyright IBM Corp. 2024
use crate::{request::MagicValue, Error};
use zerocopy::U32;
use zerocopy::{BigEndian, ByteOrder};
use zerocopy::{BigEndian, ByteOrder, U32};
use crate::request::MagicValue;
use crate::Error;
/// The magic value used to identify an attestation request
///

View File

@@ -3,20 +3,18 @@
// Copyright IBM Corp. 2023
#![allow(non_camel_case_types)]
use crate::FileAccessErrorType;
use crate::{Error, Result};
use log::debug;
use std::{
convert::TryInto,
fs::File,
os::unix::prelude::{AsRawFd, RawFd},
};
use std::convert::TryInto;
use std::fs::File;
use std::os::unix::prelude::{AsRawFd, RawFd};
#[cfg(not(test))]
use ::libc::ioctl;
use log::debug;
#[cfg(test)]
use test::mock_libc::ioctl;
use crate::{Error, FileAccessErrorType, Result};
/// Contains the rust representation of asm/uvdevice.h
/// from kernel version: 6.5 verify
mod ffi;
@@ -55,7 +53,7 @@ fn ioctl_raw(raw_fd: RawFd, cmd: u64, cb: &mut IoctlCb) -> Result<()> {
// SAFETY: the passed pointer points to a valid memory region that
// contains the expected C-struct. The struct outlives this function.
unsafe {
rc = ioctl(raw_fd, cmd.try_into().unwrap(), cb.as_ptr_mut());
rc = ioctl(raw_fd, cmd, cb.as_ptr_mut());
}
// NOTE io::Error handles all errnos ioctl uses

View File

@@ -1,10 +1,12 @@
// SPDX-License-Identifier: MIT
//
// Copyright IBM Corp. 2024
use std::ptr;
use zerocopy::{FromZeros, IntoBytes};
use super::{ffi, AttestationUserData, ConfigUid, UvCmd};
use crate::{Error, Result};
use std::ptr;
use zerocopy::{FromZeros, IntoBytes};
/// _Retrieve Attestation Measurement_ UVC
///

View File

@@ -11,9 +11,10 @@
use std::mem::size_of;
use crate::{assert_size, static_assert};
use zerocopy::{FromBytes, IntoBytes};
use crate::{assert_size, static_assert};
pub const UVIO_ATT_ARCB_MAX_LEN: usize = 0x100000;
pub const UVIO_ATT_MEASUREMENT_MAX_LEN: usize = 0x8000;
pub const UVIO_ATT_ADDITIONAL_MAX_LEN: usize = 0x8000;

View File

@@ -2,15 +2,15 @@
//
// Copyright IBM Corp. 2023
use super::ffi::{self, uvio_uvdev_info};
use crate::{
misc::{Flags, Lsb0Flags64},
uv::{UvCmd, UvDevice},
Result,
};
use std::fmt::Display;
use zerocopy::{FromZeros, IntoBytes};
use super::ffi::{self, uvio_uvdev_info};
use crate::misc::{Flags, Lsb0Flags64};
use crate::uv::{UvCmd, UvDevice};
use crate::Result;
/// Information of supported functions by the uvdevice
///
/// * `supp_uvio_cmds` - supported IOCTLs by this device

View File

@@ -2,10 +2,12 @@
//
// Copyright IBM Corp. 2024
use crate::uv::{ListableSecretType, RetrieveCmd};
use serde::{Deserialize, Serialize, Serializer};
use std::fmt::Display;
use serde::{Deserialize, Serialize, Serializer};
use crate::uv::{ListableSecretType, RetrieveCmd};
/// Allowed sizes for AES keys
#[non_exhaustive]
#[derive(PartialEq, Eq, Debug)]
@@ -335,8 +337,8 @@ mod test {
assert_tokens(&retr, &[Token::Str("19 (EC-SECP521R1-PRIVATE-KEY)")]);
}
// Ensure that the string representation of the retrievable types stay constant, or PEM will have
// different, incompatible types
// Ensure that the string representation of the retrievable types stay constant, or PEM will
// have different, incompatible types
#[test]
fn stable_type_names() {
assert_eq!("PLAINTEXT", RetrievableSecret::PlainText.to_string());

View File

@@ -2,17 +2,18 @@
//
// Copyright IBM Corp. 2023
use super::ffi;
use crate::{
request::{Confidential, MagicValue},
uv::{SecretEntry, UvCmd},
uvsecret::AddSecretMagic,
Error, Result, PAGESIZE,
};
use std::io::Read;
use std::mem::size_of_val;
use log::debug;
use std::{io::Read, mem::size_of_val};
use zerocopy::IntoBytes;
use super::ffi;
use crate::request::{Confidential, MagicValue};
use crate::uv::{SecretEntry, UvCmd};
use crate::uvsecret::AddSecretMagic;
use crate::{Error, Result, PAGESIZE};
/// _List Secrets_ Ultravisor command.
///
/// The List Secrets Ultravisor call is used to list the

View File

@@ -2,24 +2,20 @@
//
// Copyright IBM Corp. 2024
use crate::{
assert_size,
uv::{AesSizes, AesXtsSizes, EcCurves, HmacShaSizes, ListCmd, RetrievableSecret},
uvdevice::UvCmd,
Error, Result,
};
use std::cmp::min;
use std::ffi::CStr;
use std::fmt::{Debug, Display, LowerHex, UpperHex};
use std::io::{Cursor, Read, Seek, Write};
use std::mem::size_of;
use std::slice::Iter;
use std::vec::IntoIter;
use serde::{Deserialize, Serialize, Serializer};
use std::{
cmp::min,
ffi::CStr,
fmt::{Debug, Display, LowerHex, UpperHex},
io::{Cursor, Read, Seek, Write},
mem::size_of,
slice::Iter,
vec::IntoIter,
};
use zerocopy::{BigEndian, ByteOrder};
use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout, U16, U32};
use zerocopy::{BigEndian, ByteOrder, FromBytes, Immutable, IntoBytes, KnownLayout, U16, U32};
use crate::uv::{AesSizes, AesXtsSizes, EcCurves, HmacShaSizes, ListCmd, RetrievableSecret};
use crate::uvdevice::UvCmd;
use crate::{assert_size, Error, Result};
/// The 32 byte long ID of an UV secret
///

View File

@@ -4,13 +4,12 @@
#![cfg(test)]
use std::{
ffi::{c_int, c_ulong},
sync::{Mutex, MutexGuard},
};
use std::ffi::{c_int, c_ulong};
use std::sync::{Mutex, MutexGuard};
use lazy_static::lazy_static;
use super::*;
use lazy_static::lazy_static;
lazy_static! {
/// needed to serialize all tests as tests operate on static data required by the mock
@@ -129,8 +128,8 @@ impl UvCmd for TestCmd {
}
impl UvDevice {
/// Use this file as backing file for `uvdevice`. This is OK, as the ioctl is mocked and never touches the
/// passed file
/// Use this file as backing file for `uvdevice`. This is OK, as the ioctl is mocked and never
/// touches the passed file
fn test_dev() -> Self {
Self(File::open(".").unwrap())
}

View File

@@ -2,15 +2,15 @@
//
// Copyright IBM Corp. 2023
use crate::assert_size;
use crate::{
request::{MagicValue, RequestMagic},
Error, Result,
};
use std::fmt::Display;
use std::mem::size_of;
use byteorder::ByteOrder;
use std::{fmt::Display, mem::size_of};
use zerocopy::{BigEndian, Immutable, IntoBytes, U16};
use crate::request::{MagicValue, RequestMagic};
use crate::{assert_size, Error, Result};
/// The magic value used to identify an `AddSecretRequest`.
///
/// The magic value is ASCII:
@@ -145,11 +145,9 @@ impl From<UserDataType> for AddSecretMagic {
#[cfg(test)]
mod test {
use crate::{
request::MagicValue,
secret::{AddSecretMagic, UserDataType},
Error,
};
use crate::request::MagicValue;
use crate::secret::{AddSecretMagic, UserDataType};
use crate::Error;
#[test]
fn convert_user_data() {

View File

@@ -8,17 +8,17 @@ license.workspace = true
rust-version.workspace = true
[dependencies]
clap = { version ="4.5", features = ["derive", "wrap_help"]}
clap = { version ="4.6", features = ["derive", "wrap_help"]}
lazy_static = "1.5"
openssl = { version = "0.10.70" }
openssl = { version = "0.10.80" }
pv_core = { path = "../pv_core", package = "s390_pv_core"}
rand = "0.9"
regex = "1.11"
serde = { version = "1.0.217", features = ["derive"] }
regex = "1.12"
serde = { version = "1.0.228", features = ["derive"] }
serde_yaml = "0.9"
utils = { path = "../utils" }
[build-dependencies]
clap = { version ="4.5", features = ["derive", "wrap_help"]}
clap_complete = "4.5"
clap = { version ="4.6", features = ["derive", "wrap_help"]}
clap_complete = "4.6"
lazy_static = "1.5"

View File

@@ -3,11 +3,12 @@
// Copyright IBM Corp. 2024
// it under the terms of the MIT license. See LICENSE for details.
use clap::{CommandFactory, ValueEnum};
use clap_complete::{generate_to, Shell};
use std::env;
use std::io::Error;
use clap::{CommandFactory, ValueEnum};
use clap_complete::{generate_to, Shell};
include!("src/cli.rs");
fn main() -> Result<(), Error> {

View File

@@ -5,13 +5,14 @@
//! AP support functions for pvapconfig
//
use crate::helper::*;
use pv_core::ap::*;
use pv_core::misc::read_file_string;
use std::path::Path;
use std::slice::Iter;
use std::thread;
use std::time;
use std::{thread, time};
use pv_core::ap::*;
use pv_core::misc::read_file_string;
use crate::helper::*;
const RE_CARD_DIR: &str = r"^card([[:xdigit:]]{2})$";
@@ -173,7 +174,7 @@ impl ApqnList {
/// 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));
self.0.sort_unstable_by_key(|b| std::cmp::Reverse(b.gen));
}
/// Check MK restriction
@@ -191,10 +192,12 @@ impl ApqnList {
&& a1.info.is_some()
&& a2.info.is_some()
{
#[allow(clippy::unnecessary_unwrap)]
let i1 = match a1.info.as_ref().unwrap() {
apqn_info::Ep11(i) => i,
_ => continue,
};
#[allow(clippy::unnecessary_unwrap)]
let i2 = match a2.info.as_ref().unwrap() {
apqn_info::Ep11(i) => i,
_ => continue,

View File

@@ -5,13 +5,14 @@
//! Functions around handling the pvapconfig configuration file
//
use std::fs::File;
use std::slice::Iter;
use openssl::sha::sha256;
use pv_core::misc::encode_hex;
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";
@@ -209,10 +210,10 @@ impl ApConfigList {
#[cfg(test)]
mod tests {
use super::*;
use std::env;
use std::fs;
use std::io::Write;
use std::{env, fs};
use super::*;
const GOOD_CONFIGS: [&str; 8] = [
"# good test 1

View File

@@ -5,13 +5,14 @@
//! Collection of helper functions for pvapconfig
//
use regex::Regex;
use std::error::Error;
use std::fs;
use std::fs::OpenOptions;
use std::io::Write;
use std::path::PathBuf;
use regex::Regex;
pub const PATH_PVAPCONFIG_LOCK: &str = "/run/lock/pvapconfig.lock";
/// For a given (sysfs) directory construct a list of all subdirs
@@ -129,9 +130,10 @@ impl Drop for LockFile {
#[cfg(test)]
mod tests {
use super::*;
use utils::TemporaryDirectory;
use super::*;
// Only very simple tests
#[test]

View File

@@ -12,6 +12,8 @@ mod config;
mod helper;
mod uv;
use std::process::ExitCode;
use ap::ApqnList;
use cli::ARGS;
use config::{ApConfigEntry, ApConfigList};
@@ -19,7 +21,6 @@ use helper::{LockFile, PATH_PVAPCONFIG_LOCK};
use pv_core::ap::{self as pvap, Apqn};
use pv_core::misc::encode_hex;
use pv_core::uv::{ListableSecretType, SecretList};
use std::process::ExitCode;
use utils::print_version;
/// Simple macro for
@@ -50,8 +51,8 @@ macro_rules! println_and_exit_failure {
/// returns with exit failure.
macro_rules! on_error_print_and_exit {
($r:expr) => {
if $r.is_err() {
eprintln!("{}", $r.unwrap_err());
if let Err(e) = $r {
eprintln!("{}", e);
return ExitCode::FAILURE;
}
};
@@ -456,8 +457,10 @@ fn config_and_apqn_match(apc: &ApConfigEntry, apqn: &Apqn) -> bool {
#[cfg(test)]
mod tests {
use pv_core::misc::decode_hex;
use pv_core::uv::SecretEntry;
use super::*;
use pv_core::{misc::decode_hex, 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.

View File

@@ -9,14 +9,14 @@ rust-version.workspace = true
workspace = true
[dependencies]
anyhow = { version = "1.0.95", features = ["std"] }
anyhow = { version = "1.0.102", features = ["std"] }
base64 = "0.22.1"
byteorder = "1.5"
clap = { version ="4.5", features = ["derive", "wrap_help"]}
curl = "0.4.47"
log = { version = "0.4.25", features = ["std", "release_max_level_debug"] }
openssl = "0.10.70"
serde = { version = "1.0.217", features = ["derive"]}
clap = { version ="4.6", features = ["derive", "wrap_help"]}
curl = "0.4.49"
log = { version = "0.4.29", features = ["std", "release_max_level_debug"] }
openssl = "0.10.80"
serde = { version = "1.0.228", features = ["derive"]}
serde_json = "1.0"
serde_yaml = "0.9"
zerocopy = { version="0.8", features = ["derive"] }
@@ -25,8 +25,8 @@ pv = { path = "../pv", package = "s390_pv" }
utils = { path = "../utils" }
[build-dependencies]
clap = { version ="4.5", features = ["derive", "wrap_help"]}
clap_complete = "4.5"
clap = { version ="4.6", features = ["derive", "wrap_help"]}
clap_complete = "4.6"
log = { version = "0.4", features = ["std", "release_max_level_debug"] }
utils = { path = "../utils" }

View File

@@ -365,6 +365,16 @@ Check whether the firmware is supported by IBM. Requires internet access.
</ul>
`--firmware-check-version <FIRMWARE_CHECK_VERSION>`
<ul>
Specify the firmware verification request version.
Default value: '1'
Possible values:
- **1**: Use firmware verification API request version 1.0.
- **2**: Use firmware verification API request version 2.0.
</ul>
`--firmware-verify-url <URL>`
<ul>
Specify the endpoint to use for firmware version verification. Use an endpoint

View File

@@ -4,11 +4,12 @@
// it under the terms of the MIT license. See LICENSE for details.
#![allow(missing_docs)]
use clap::CommandFactory;
use clap_complete::{generate_to, Shell};
use std::env;
use std::io::Error;
use clap::CommandFactory;
use clap_complete::{generate_to, Shell};
include!("src/cli.rs");
fn main() -> Result<(), Error> {

View File

@@ -3,7 +3,7 @@
.\" it under the terms of the MIT license. See LICENSE for details.
.\"
.TH "PVATTEST-CHECK" "1" "2026-05-19" "s390-tools" "Attestation Manual"
.TH "PVATTEST-CHECK" "1" "2026-06-23" "s390-tools" "Attestation Manual"
.nh
.ad l
.SH NAME
@@ -112,6 +112,20 @@ Required if add\-secret\-requests are specified.
\-\-firmware
.RS 4
Check whether the firmware is supported by IBM. Requires internet access.
.RE
.RE
.PP
\-\-firmware\-check\-version, \-\-fw\-ver <FIRMWARE_CHECK_VERSION>
.RS 4
Specify the firmware verification request version.
[default: '1']
Possible values:
.RS 4
\- \fB1\fP: Use firmware verification API request version 1.0.
\- \fB2\fP: Use firmware verification API request version 2.0.
.RE
.RE
.PP

View File

@@ -2,13 +2,15 @@
//
// Copyright IBM Corp. 2024
use crate::exchange::ExchangeFormatResponse;
use std::fmt::Display;
use anyhow::Result;
use pv::attest::{AdditionalData, AttestationFlags};
use serde::Serialize;
use std::fmt::Display;
use utils::HexSlice;
use crate::exchange::ExchangeFormatResponse;
#[derive(Serialize)]
pub struct AttestationResult<'a> {
pub cuid: HexSlice<'a>,

View File

@@ -58,7 +58,8 @@ pub enum Command {
/// Check if the attestation result matches defined policies.
///
/// After the attestation verification, check whether the attestation result complies with user-defined policies.
/// After the attestation verification, check whether the attestation result complies with
/// user-defined policies.
Check(CheckOpt),
/// Print version information and exit.
@@ -103,7 +104,8 @@ pub enum AttAddFlags {
/// additional-data.
PhkhAtt,
/// Request a hash over all successful Add-secret requests and the lock state as additional-data.
/// Request a hash over all successful Add-secret requests and the lock state as
/// additional-data.
SecretStoreHash,
/// Request the state of the firmware as additional-data.
@@ -214,6 +216,17 @@ pub enum OutputType {
Yaml,
}
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum, Debug, Default)]
pub enum FirmwareCheckVersion {
/// Use firmware check API version 1.0.
#[default]
#[value(name = "1")]
V1,
/// Use firmware check API version 2.0.
#[value(name = "2")]
V2,
}
#[derive(Args, Debug)]
pub struct CheckOpt {
/// Specify the attestation response to check whether the policies are validated.
@@ -301,6 +314,16 @@ pub struct CheckOpt {
#[arg(long)]
pub firmware: bool,
/// Specify the firmware verification request version.
#[arg(
long,
visible_alias("fw-ver"),
requires("firmware"),
value_enum,
default_value_t
)]
pub firmware_check_version: FirmwareCheckVersion,
/// Specify the endpoint to use for firmware version verification.
///
/// Use an endpoint you trust. Requires the --firmware option.

View File

@@ -6,28 +6,22 @@ mod firmware;
mod host_key;
mod secret_store;
use self::{
firmware::firmware_check,
host_key::{host_key_check, HostKeyCheck},
secret_store::secret_store_check,
secret_store::SecretStoreCheck,
};
use crate::{
additional::AttestationResult,
cli::{CheckOpt, CheckOptIO},
exchange::ExchangeFormatResponse,
};
use std::process::ExitCode;
use anyhow::Result;
use log::{debug, info, warn};
use pv::{
attest::AttestationRequest,
misc::{create_file, open_file, read_file},
};
use pv::attest::AttestationRequest;
use pv::misc::{create_file, open_file, read_file};
use serde::Serialize;
use std::process::ExitCode;
use utils::HexSlice;
use self::firmware::{firmware_check_v1, firmware_check_v2};
use self::host_key::{host_key_check, HostKeyCheck};
use self::secret_store::{secret_store_check, SecretStoreCheck};
use crate::additional::AttestationResult;
use crate::cli::{CheckOpt, CheckOptIO, FirmwareCheckVersion};
use crate::exchange::ExchangeFormatResponse;
#[derive(Default, Debug)]
enum CheckState<T> {
#[default]
@@ -126,7 +120,10 @@ pub fn check(opt: &CheckOpt) -> Result<ExitCode> {
let user_data = user_data_check(opt, &att_res)?.check(&mut issues);
let secret_store = secret_store_check(opt, &att_res)?.check(&mut issues);
let firmware_check = firmware_check(opt, &att_res)?;
let firmware_check = match opt.firmware_check_version {
FirmwareCheckVersion::V1 => firmware_check_v1(opt, &att_res)?,
FirmwareCheckVersion::V2 => firmware_check_v2(opt, &att_res)?,
};
let valid_firmware = match firmware_check {
CheckState::None => None,
CheckState::Data(_) => Some(true),

View File

@@ -2,7 +2,8 @@
//
// Copyright IBM Corp. 2024
use std::{fmt::Display, time::Duration};
use std::fmt::{Debug, Display};
use std::time::Duration;
use anyhow::{bail, Result};
use base64::prelude::*;
@@ -11,44 +12,114 @@ use log::{debug, info, log_enabled};
use serde::{Deserialize, Serialize};
use super::{bail_check, CheckState};
use crate::{additional::AttestationResult, cli::CheckOpt};
use crate::additional::AttestationResult;
use crate::cli::CheckOpt;
const CHECK_DEFAULT_ENDP: &str = "https://esupport.ibm.com/eccedge/ent/z";
const VERIFY_API: &str = "hmrs/firmware/attestation/v1/verify";
const TIMEOUT_MAX: Duration = Duration::from_secs(3);
const TIMEOUT_MAX: Duration = Duration::from_secs(120);
const USER_AGENT: &str = "s390-tools-pvattest";
const CONTENT_TYPE: &str = "Content-Type: application/json";
const CLIENT_ID: &str = "x-client-id: X";
#[derive(Debug, Serialize)]
struct Request {
version: String,
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
enum Version {
#[serde(rename = "1.0")]
V1,
#[serde(rename = "2.0")]
V2,
}
trait Request: Serialize + Debug {
type Response: Response;
fn new(firmware_hash: &[u8]) -> Self;
}
#[derive(Debug, Serialize, Deserialize)]
struct RequestV1_1 {
version: Version,
payload: String,
}
impl Request {
const VERSION_ONE: &'static str = "1.0";
impl Request for RequestV1_1 {
type Response = ResponseV1;
fn new_v1(firmware_hash: &[u8]) -> Self {
fn new(firmware_hash: &[u8]) -> Self {
Self {
version: Self::VERSION_ONE.to_string(),
version: Version::V1,
payload: BASE64_STANDARD.encode(firmware_hash),
}
}
}
#[derive(Debug, Serialize, Deserialize)]
struct RequestV1_2 {
version: Version,
payload: String,
}
impl Request for RequestV1_2 {
type Response = ResponseV2;
fn new(firmware_hash: &[u8]) -> Self {
Self {
version: Version::V1,
payload: BASE64_STANDARD.encode(firmware_hash),
}
}
}
/// Trait for firmware verification response types.
///
/// This trait defines the interface for handling responses from the IBM firmware
/// verification API.
trait Response: serde::de::DeserializeOwned + Debug + Display {
/// The API version constant for this response type.
const VERSION: Version;
/// Returns whether the firmware verification was successful.
///
/// # Returns
///
/// `true` if the firmware is in a valid state, `false` otherwise.
fn valid(&self) -> bool;
/// Constructs the verification API endpoint URL for this response version.
///
/// * `endp` - The base endpoint URL (e.g., "<https://esupport.ibm.com/eccedge/ent/z>")
///
/// # Returns
///
/// The complete API endpoint URL for firmware verification, including the version path.
fn verify_api(endp: &str) -> String {
let ver = match Self::VERSION {
Version::V1 => "v1",
Version::V2 => "v2",
};
format!("{endp}/hmrs/firmware/attestation/{ver}/verify",)
}
}
// allow unused because all fields are provided by the REST API but may be unused by this toolk
#[allow(unused)]
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
struct Response {
version: String,
struct ResponseV1 {
version: Version,
valid: bool,
reference_id: String,
#[serde(default)]
reason: Option<String>,
}
impl Display for Response {
impl Response for ResponseV1 {
const VERSION: Version = Version::V1;
fn valid(&self) -> bool {
self.valid
}
}
impl Display for ResponseV1 {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
@@ -62,6 +133,44 @@ impl Display for Response {
}
}
// allow unused because all fields are provided by the REST API but may be unused by this toolk
#[allow(unused)]
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
struct VerifiedHashV2 {
hash: String,
signature: String,
}
#[allow(unused)]
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
struct ResponseV2 {
version: Version,
valid: bool,
reason: String,
verified_hashes: Vec<VerifiedHashV2>,
}
impl Response for ResponseV2 {
const VERSION: Version = Version::V2;
fn valid(&self) -> bool {
self.valid
}
}
impl Display for ResponseV2 {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
writeln!(
f,
"The firmware is {}in a valid state",
if self.valid { "" } else { "not " }
)?;
let json = serde_json::to_string_pretty(self).map_err(|_| std::fmt::Error)?;
f.write_str(&json)
}
}
#[derive(Debug)]
struct Buf(Vec<u8>);
impl Handler for Buf {
@@ -71,10 +180,14 @@ impl Handler for Buf {
}
}
fn check<U: AsRef<[u8]>>(fw_hash: &U, endp: &str) -> Result<CheckState<()>> {
let req = serde_json::to_vec(&Request::new_v1(fw_hash.as_ref()))?;
fn check<Q, U>(fw_hash: &U, endp: &str) -> Result<CheckState<()>>
where
Q: Request,
U: AsRef<[u8]>,
{
let req = serde_json::to_vec(&Q::new(fw_hash.as_ref()))?;
let url = format!("{endp}/{VERIFY_API}");
let url = Q::Response::verify_api(endp);
debug!("POST {url}");
let mut http_header = List::new();
@@ -103,7 +216,7 @@ fn check<U: AsRef<[u8]>>(fw_hash: &U, endp: &str) -> Result<CheckState<()>> {
);
}
let resp: Response = match serde_json::from_slice(&handle.get_ref().0) {
let resp: Q::Response = match serde_json::from_slice(&handle.get_ref().0) {
Ok(res) => res,
Err(e) => bail!(
"Unexpected response from server: {} \n (\"{}\")",
@@ -115,7 +228,7 @@ fn check<U: AsRef<[u8]>>(fw_hash: &U, endp: &str) -> Result<CheckState<()>> {
debug!("Firmware check {resp:?}");
match resp.valid {
match resp.valid() {
true => info!("✓ {resp}"),
false => bail_check!(&format!("{resp}")),
}
@@ -123,7 +236,10 @@ fn check<U: AsRef<[u8]>>(fw_hash: &U, endp: &str) -> Result<CheckState<()>> {
Ok(CheckState::Data(()))
}
pub fn firmware_check(opt: &CheckOpt, att_res: &AttestationResult) -> Result<CheckState<()>> {
fn firmware_check<Q>(opt: &CheckOpt, att_res: &AttestationResult) -> Result<CheckState<()>>
where
Q: Request,
{
if !opt.firmware {
return Ok(None.into());
}
@@ -138,7 +254,7 @@ pub fn firmware_check(opt: &CheckOpt, att_res: &AttestationResult) -> Result<Che
.as_ref()
.and_then(|add| add.firmware_state())
{
Some(hash) => check(hash, endp),
Some(hash) => check::<Q, _>(hash, endp),
None => {
bail_check!(
"The Attestation response contains no firmware hash, but checking was enabled"
@@ -146,3 +262,144 @@ pub fn firmware_check(opt: &CheckOpt, att_res: &AttestationResult) -> Result<Che
}
}
}
pub fn firmware_check_v1(opt: &CheckOpt, att_res: &AttestationResult) -> Result<CheckState<()>> {
firmware_check::<RequestV1_1>(opt, att_res)
}
pub fn firmware_check_v2(opt: &CheckOpt, att_res: &AttestationResult) -> Result<CheckState<()>> {
firmware_check::<RequestV1_2>(opt, att_res)
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn serialize_request_v1_1() {
let payload = BASE64_STANDARD.encode([42u8; 320]);
let req = RequestV1_1::new(&[42u8; 320]);
assert!(matches!(req.version, Version::V1));
assert_eq!(req.payload, payload);
let json = serde_json::to_string(&req).unwrap();
let expected = format!(r#"{{"version":"1.0","payload":"{payload}"}}"#);
assert_eq!(json, expected);
}
#[test]
fn serialize_request_v1_2() {
let payload = BASE64_STANDARD.encode([42u8; 320]);
let req = RequestV1_2::new(&[42u8; 320]);
assert!(matches!(req.version, Version::V1));
assert_eq!(req.payload, payload);
let json = serde_json::to_string(&req).unwrap();
let expected = format!(r#"{{"version":"1.0","payload":"{payload}"}}"#);
assert_eq!(json, expected);
}
#[test]
fn parse_response_v1() {
let json = r#"{
"version": "1.0",
"valid": true,
"referenceId": "ref-1",
"reason": "string"
}"#;
let resp: ResponseV1 = serde_json::from_str(json).unwrap();
assert!(resp.valid());
assert!(matches!(resp.version, Version::V1));
assert_eq!(resp.reference_id, "ref-1");
assert_eq!(resp.reason.as_deref(), Some("string"));
let display = resp.to_string();
let expected = "The firmware is in a valid state\n Reason: string\n ReferenceId: ref-1";
assert_eq!(display, expected);
}
#[test]
fn parse_response_v1_without_reason() {
let json = r#"{
"version": "1.0",
"valid": true,
"referenceId": "ref-1"
}"#;
let resp: ResponseV1 = serde_json::from_str(json).unwrap();
assert!(resp.valid());
assert!(matches!(resp.version, Version::V1));
assert_eq!(resp.reference_id, "ref-1");
assert_eq!(resp.reason, None);
let display = resp.to_string();
let expected = "The firmware is in a valid state";
assert_eq!(display, expected);
}
#[test]
fn parse_response_v2() {
let verified_hashes: Vec<_> = (0u8..4)
.map(|i| {
(
BASE64_STANDARD.encode([42u8 + i; 256]),
BASE64_STANDARD.encode([17u8 + i; 256]),
)
})
.collect();
let json = format!(
r#"{{
"version": "2.0",
"valid": true,
"reason": "string",
"verifiedHashes": [
{{
"hash": "{}",
"signature": "{}"
}},
{{
"hash": "{}",
"signature": "{}"
}},
{{
"hash": "{}",
"signature": "{}"
}},
{{
"hash": "{}",
"signature": "{}"
}}
]
}}"#,
verified_hashes[0].0,
verified_hashes[0].1,
verified_hashes[1].0,
verified_hashes[1].1,
verified_hashes[2].0,
verified_hashes[2].1,
verified_hashes[3].0,
verified_hashes[3].1,
);
let resp: ResponseV2 = serde_json::from_str(&json).unwrap();
assert!(resp.valid());
assert!(matches!(resp.version, Version::V2));
assert_eq!(resp.reason, "string");
assert_eq!(resp.verified_hashes.len(), 4);
for (verified_hash, (hash, signature)) in
resp.verified_hashes.iter().zip(verified_hashes.iter())
{
assert_eq!(&verified_hash.hash, hash);
assert_eq!(&verified_hash.signature, signature);
}
let display = resp.to_string();
let expected = format!("The firmware is in a valid state\n{json}");
assert_eq!(display, expected);
}
}

View File

@@ -2,21 +2,20 @@
//
// Copyright IBM Corp. 2024
use std::fmt::Display;
use std::path::Path;
use anyhow::Result;
use log::{debug, info};
use pv::{
misc::{read_certs, read_file},
request::{openssl::DigestBytes, EcPubKeyCoord},
};
use pv::misc::{read_certs, read_file};
use pv::request::openssl::DigestBytes;
use pv::request::EcPubKeyCoord;
use serde::Serialize;
use std::{fmt::Display, path::Path};
use utils::HexSlice;
use super::CheckState;
use crate::{
additional::AttestationResult,
cli::{CheckOpt, HostKeyCheckPolicy},
};
use crate::additional::AttestationResult;
use crate::cli::{CheckOpt, HostKeyCheckPolicy};
#[derive(Debug, Clone, Copy)]
pub enum HkCheck {

View File

@@ -6,13 +6,14 @@ use std::path::{Path, PathBuf};
use anyhow::Result;
use log::info;
use openssl::hash::DigestBytes;
use openssl::hash::{hash, MessageDigest};
use pv::{misc::read_file, secret::AddSecretRequest};
use openssl::hash::{hash, DigestBytes, MessageDigest};
use pv::misc::read_file;
use pv::secret::AddSecretRequest;
use serde::Serialize;
use super::{bail_check, CheckState};
use crate::{additional::AttestationResult, cli::CheckOpt};
use crate::additional::AttestationResult;
use crate::cli::CheckOpt;
#[derive(Debug, Serialize)]
pub struct SecretStoreCheck<'a> {

View File

@@ -2,18 +2,16 @@
//
// Copyright IBM Corp. 2024
use crate::{
cli::{AttAddFlags, CreateAttOpt},
exchange::{ExchangeFormatRequest, ExchangeFormatVersion},
};
use std::process::ExitCode;
use anyhow::{bail, Context, Result};
use log::{debug, warn};
use pv::{
attest::{AttestationFlags, AttestationMeasAlg, AttestationRequest, AttestationVersion},
misc::{create_file, write_file},
request::{ReqEncrCtx, Request, SymKey, SymKeyType},
};
use std::process::ExitCode;
use pv::attest::{AttestationFlags, AttestationMeasAlg, AttestationRequest, AttestationVersion};
use pv::misc::{create_file, write_file};
use pv::request::{HostKey, ReqEncrCtx, Request, SymKey, SymKeyType};
use crate::cli::{AttAddFlags, CreateAttOpt};
use crate::exchange::{ExchangeFormatRequest, ExchangeFormatVersion};
fn flags(cli_flags: &[AttAddFlags]) -> AttestationFlags {
let mut att_flags = AttestationFlags::default();
@@ -39,7 +37,7 @@ pub fn create(opt: &CreateAttOpt) -> Result<ExitCode> {
opt.certificate_args
.get_verified_hkds("attestation request")?
.into_iter()
.for_each(|k| arcb.add_hostkey(k));
.for_each(|k| arcb.add_hostkey(HostKey::V1(k)));
debug!("Added all host-keys");
let encr_ctx =

View File

@@ -2,17 +2,15 @@
//
// Copyright IBM Corp. 2024
use crate::{
cli::PerformAttOptComb,
exchange::{ExchangeFormatRequest, ExchangeFormatResponse, ExchangeFormatVersion},
};
use anyhow::Result;
use pv::{
misc::{create_file, open_file, read_file},
uv::{AttestationCmd, UvDevice},
};
use std::process::ExitCode;
use anyhow::Result;
use pv::misc::{create_file, open_file, read_file};
use pv::uv::{AttestationCmd, UvDevice};
use crate::cli::PerformAttOptComb;
use crate::exchange::{ExchangeFormatRequest, ExchangeFormatResponse, ExchangeFormatVersion};
pub fn perform<'a, P>(opt: P) -> Result<ExitCode>
where
P: Into<PerformAttOptComb<'a>>,

View File

@@ -2,22 +2,20 @@
//
// Copyright IBM Corp. 2024
use std::process::ExitCode;
use anyhow::Result;
use log::{debug, warn};
use pv::{
attest::{AttestationItems, AttestationMeasurement, AttestationRequest},
misc::{create_file, open_file, read_exact_file, write_file},
request::{openssl::pkey::PKey, BootHdrTags, Confidential, SymKey},
};
use std::process::ExitCode;
use pv::attest::{AttestationItems, AttestationMeasurement, AttestationRequest};
use pv::misc::{create_file, open_file, read_exact_file, write_file};
use pv::request::openssl::pkey::PKey;
use pv::request::{BootHdrTags, Confidential, SymKey};
use utils::HexSlice;
use crate::{
additional::AttestationResult,
cli::{OutputType, VerifyOpt},
exchange::ExchangeFormatResponse,
EXIT_CODE_ATTESTATION_FAIL,
};
use crate::additional::AttestationResult;
use crate::cli::{OutputType, VerifyOpt};
use crate::exchange::ExchangeFormatResponse;
use crate::EXIT_CODE_ATTESTATION_FAIL;
pub fn verify(opt: &VerifyOpt) -> Result<ExitCode> {
let mut input = open_file(&opt.input)?;
@@ -26,7 +24,7 @@ pub fn verify(opt: &VerifyOpt) -> Result<ExitCode> {
let arpk = SymKey::Aes256(
read_exact_file(&opt.arpk, "Attestation request protection key").map(Confidential::new)?,
);
let tags = BootHdrTags::from_se_image(&mut img)?;
let (tags, _) = BootHdrTags::from_se_image(&mut img)?;
let exchange = ExchangeFormatResponse::read(&mut input)?;
let (auth, conf) = AttestationRequest::decrypt_bin(exchange.arcb(), &arpk)?;

View File

@@ -2,14 +2,14 @@
//
// Copyright IBM Corp. 2024
#![allow(unused)]
use std::io::{ErrorKind, Read, Seek, SeekFrom, Write};
use std::mem::size_of;
use anyhow::{anyhow, bail, Error, Result};
use pv::{assert_size, request::MagicValue, uv::AttestationCmd, uv::ConfigUid};
use std::{
io::{ErrorKind, Read, Seek, SeekFrom, Write},
mem::size_of,
};
use zerocopy::ByteOrder;
use zerocopy::{BigEndian, FromBytes, Immutable, IntoBytes, KnownLayout, U32, U64};
use pv::assert_size;
use pv::request::MagicValue;
use pv::uv::{AttestationCmd, ConfigUid};
use zerocopy::{BigEndian, ByteOrder, FromBytes, Immutable, IntoBytes, KnownLayout, U32, U64};
const INV_EXCHANGE_FMT_ERROR_TEXT: &str = "The input has not the correct format:";
@@ -583,9 +583,10 @@ mod test {
use std::io::Cursor;
use super::*;
use pv::misc::read_file;
use super::*;
#[test]
fn exchange_from_slice() {
let val = &[0; 17];

View File

@@ -8,10 +8,11 @@ mod cli;
mod cmd;
mod exchange;
use std::process::ExitCode;
use clap::{CommandFactory, Parser};
use cli::{CliOptions, Command};
use log::trace;
use std::process::ExitCode;
use utils::{print_cli_error, print_error, print_version, PvLogger};
use crate::cmd::{check, create, perform, verify, CMD_FN, UV_CMD_FN};

View File

@@ -28,8 +28,8 @@ RemainAfterExit=yes
FailureAction=poweroff-immediate
# boot partition is unencrypted and contains SICS so we can get logs out this way
# logs do not leek any sensitive information
StandardOutput=console
StandardError=console
StandardOutput=journal+console
StandardError=journal+console
[Install]
RequiredBy=sel-ebc.target

View File

@@ -20,8 +20,8 @@ FailureAction=poweroff-immediate
RemainAfterExit=yes
# logs do not leek any sensitive information
StandardOutput=console
StandardError=console
StandardOutput=journal+console
StandardError=journal+console
[Install]
RequiredBy=sel-ebc.target

View File

@@ -29,8 +29,8 @@ RemainAfterExit=yes
FailureAction=poweroff-immediate
# boot partition is unencrypted and contains SICS so we can get logs out this way
# logs do not leek any sensitive information
StandardOutput=console
StandardError=console
StandardOutput=journal+console
StandardError=journal+console
[Install]
RequiredBy=sel-ebc.target

View File

@@ -8,15 +8,15 @@ rust-version.workspace = true
[dependencies]
sha2 = "0.10"
hex = "0.4"
clap = { version = "4.5", features = ["derive"] }
clap = { version = "4.6", features = ["derive"] }
pv_core = { path = "../pv_core" , package = "s390_pv_core" }
utils = { path = "../utils" }
anyhow = { version = "1.0.95", features = ["std"] }
zerocopy = { version = "0.8.27", features = ["derive"] }
anyhow = { version = "1.0.102", features = ["std"] }
zerocopy = { version = "0.8.48", features = ["derive"] }
[build-dependencies]
clap = { version ="4.5", features = ["derive"]}
clap_complete = "4.5"
clap = { version ="4.6", features = ["derive"]}
clap_complete = "4.6"
[lints]
workspace = true

View File

@@ -2,7 +2,8 @@
//
// Copyright IBM Corp.
use std::{path::PathBuf, sync::OnceLock};
use std::path::PathBuf;
use std::sync::OnceLock;
use clap::{ArgAction, Parser, ValueHint};
@@ -21,8 +22,8 @@ pub struct Cli {
/// Specifies the toc.asr which is the root of the EBC resources
///
/// Specify the add-secret request file toc.asr that serves as the root of the EBC resources.
/// Its user data must contain a reference to toc.pol as generated by pvsecret with the --policy
/// option.
/// Its user data must contain a reference to toc.pol as generated by pvsecret with the
/// --policy option.
#[arg(short, long, value_name = "FILE", value_hint = ValueHint::FilePath)]
pub toc: PathBuf,

View File

@@ -2,9 +2,11 @@
//
// Copyright IBM Corp.
use std::io::Read;
use std::path::Path;
use anyhow::{bail, Context, Result};
use pv_core::misc::open_file;
use std::{io::Read, path::Path};
/// Length of the MAC tag in bytes (last 16 bytes of AddSecretRequest files)
pub const MAC_TAG_LEN: usize = 16;
@@ -20,7 +22,7 @@ pub fn get_reader(filepath: &Path) -> Result<Box<dyn Read>> {
.with_context(|| format!("unable to get reader from {:?}", filepath))
}
/// Read all data from a reader into a Vec<u8>
/// Read all data from a reader into a `Vec<u8>`
pub fn get_data(rd_in: &mut Box<dyn Read>) -> Result<Vec<u8>> {
let mut data_in = Vec::new();
rd_in

View File

@@ -20,27 +20,23 @@
mod cli;
mod ebc_utils;
#[cfg(target_arch = "s390x")]
use pv_core::uv::{AddCmd, UvDevice};
#[cfg(target_arch = "s390x")]
use utils::get_reader_from_cli_file_arg;
use std::fs::{self, File};
use std::io::{BufRead, Read};
use std::path::{Path, PathBuf};
use std::process::ExitCode;
use std::str::from_utf8;
use anyhow::{bail, Context, Error, Result};
use clap::Parser;
use pv_core::{
misc::{decode_hex, encode_hex},
PolicyReference,
};
use std::{
fs::{self, File},
io::{BufRead, Read},
path::{Path, PathBuf},
process::ExitCode,
str::from_utf8,
};
use pv_core::misc::{decode_hex, encode_hex};
#[cfg(target_arch = "s390x")]
use pv_core::uv::{AddCmd, UvDevice};
use pv_core::PolicyReference;
// Don't use openssl here because this tool is intended to run in the initramfs
// phase of the boot and there we don't want to dynamically link against a C lib
use sha2::{self, Digest};
#[cfg(target_arch = "s390x")]
use utils::get_reader_from_cli_file_arg;
use zerocopy::TryFromBytes;
use crate::cli::Cli;

View File

@@ -9,29 +9,29 @@ rust-version.workspace = true
workspace = true
[dependencies]
anyhow = { version = "1.0.95", features = ["std"] }
anyhow = { version = "1.0.102", features = ["std"] }
base64 = "0.22.1"
clap = { version ="4.5", features = ["derive", "wrap_help"]}
clap = { version ="4.6", features = ["derive", "wrap_help"]}
deku = "0.18"
deku_derive = "0.18"
enum_dispatch = "0.3.13"
log = { version = "0.4.25", features = ["std", "release_max_level_debug"] }
openssl = "0.10.70"
serde = { version = "1.0.217", features = ["derive"]}
log = { version = "0.4.29", features = ["std", "release_max_level_debug"] }
openssl = "0.10.80"
serde = { version = "1.0.228", features = ["derive"]}
serde_json = "1.0"
thiserror = "2.0.11"
thiserror = "2.0.18"
pv = { path = "../pv", package = "s390_pv" }
utils = { path = "../utils" }
[build-dependencies]
anyhow = { version = "1.0.95", features = ["std"] }
clap = { version ="4.5", features = ["derive", "wrap_help"]}
clap_complete = "4.5"
log = { version = "0.4.25", features = ["std", "release_max_level_debug"] }
anyhow = { version = "1.0.102", features = ["std"] }
clap = { version ="4.6", features = ["derive", "wrap_help"]}
clap_complete = "4.6"
log = { version = "0.4.29", features = ["std", "release_max_level_debug"] }
pv = { path = "../pv", package = "s390_pv" }
utils = { path = "../utils" }
[dev-dependencies]
proptest = "1.6"
proptest = "1.11"

View File

@@ -3,27 +3,21 @@
// Copyright IBM Corp. 2024
#![allow(missing_docs)]
use std::{
fmt::Display,
fs::{File, OpenOptions},
io::{BufReader, Read, Write},
path::PathBuf,
str::FromStr,
};
use std::fmt::Display;
use std::fs::{File, OpenOptions};
use std::io::{BufReader, Read, Write};
use std::path::PathBuf;
use std::str::FromStr;
use anyhow::{anyhow, Context, Error};
use clap::{Parser, ValueHint};
use log::{info, warn};
use pv::{
misc::{decode_hex, open_file, read_certs, read_file, try_parse_u64},
request::SymKeyType,
Error as PvError, Result,
};
use pvimg::{
misc::PSW,
secured_comp::{ComponentTrait, Layout, SecuredComponentBuilder},
uvdata::{BuilderTrait, SeHdrBuilder, SeHdrVersion},
};
use pv::misc::{decode_hex, open_file, read_certs, read_file, try_parse_u64};
use pv::request::SymKeyType;
use pv::{Error as PvError, Result};
use pvimg::misc::PSW;
use pvimg::secured_comp::{ComponentTrait, Layout, SecuredComponentBuilder};
use pvimg::uvdata::{BuilderTrait, SeHdrBuilder, SeHdrVersion};
use utils::{AtomicFile, AtomicFileOperation, HexSlice, PvLogger, VerbosityOptions};
/// Converts the hexstring into a byte vector.
@@ -182,7 +176,7 @@ fn main() -> anyhow::Result<()> {
let mut secure_comp_builer = SecuredComponentBuilder::new_v1(false)?;
// Sort components by address in ascending order
args.components.sort_by(|a, b| a.addr.cmp(&b.addr));
args.components.sort_by_key(|a| a.addr);
for component_arg in args.components {
info!("## Preparing {}", component_arg);
let mut comp = Comp {

View File

@@ -1,20 +1,18 @@
#![allow(missing_docs)]
use std::{fs::File, io::Write, path::PathBuf};
use std::fs::File;
use std::io::Write;
use std::path::PathBuf;
use anyhow::Context;
use clap::{Parser, ValueEnum, ValueHint};
use log::info;
use pv::{
misc::{open_file, parse_hex, read_file},
request::SymKey,
};
use pvimg::{
error::Result,
uvdata::{
KeyExchangeTrait, SeHdr, SeHdrBinV1, SeHdrData, SeHdrDataV1, SeHdrVersioned,
UvDataPlainTrait, UvDataTrait,
},
use pv::misc::{open_file, parse_hex, read_file};
use pv::request::SymKey;
use pvimg::error::Result;
use pvimg::uvdata::{
KeyExchangeTrait, SeHdr, SeHdrBinV1, SeHdrData, SeHdrDataV1, SeHdrVersioned, UvDataPlainTrait,
UvDataTrait,
};
use utils::{PvLogger, VerbosityOptions};

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