Compare commits

..

228 Commits

Author SHA1 Message Date
Steffen Eiden
b592b0cc06 Prepare for next release
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
2026-07-31 15:51:59 +02:00
Steffen Eiden
03f7566436 New release s390-tools-2.44.0
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
2026-07-31 15:51:50 +02:00
Marc Hartmayer
177d8ae0dd pvimg: Get rid of most 'ignore' doctest directives
Signed-off-by: Marc Hartmayer <marc@linux.ibm.com>
Reviewed-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
2026-07-31 09:51:14 +02:00
Marc Hartmayer
08d21b24b4 pvimg: Use indexed array for UV key hashes
Replace individual UV key hash fields with an indexed array and
introduce dedicated types for hash indices and key hash kinds. This
simplifies hash handling.

Co-developed-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Marc Hartmayer <marc@linux.ibm.com>
Reviewed-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
2026-07-31 09:51:14 +02:00
Marc Hartmayer
ed74e84862 pvimg: Report host-key document type mismatches
Report host-key document type mismatches before hash verification.

For example, when testing a v2 Secure Execution header with a classical
host-key document, report that a hybrid host-key document is required
instead of failing with a generic header validation error.

Before:

$ pvimg -- test --host-key-document v1.hkd se.v2.img
error: Invalid Secure Execution header

After:

$ pvimg -- test --host-key-document v1.hkd se.v2.img
Host-key document 'v1.hkd' is not a hybrid host-key document.
error: The provided host key document in v1.hkd contains the wrong number of keys!

Signed-off-by: Marc Hartmayer <marc@linux.ibm.com>
Reviewed-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
2026-07-31 09:51:14 +02:00
Marc Hartmayer
0263637d9f pv: Refactor 'get_verified_hkds'
Get rid of 'read_hkd' by refactoring the 'get_verified_hkds' function.
For this a new HkdLoader::load_and_verify is introduced that is a
reworked version of the original code.

In addition, add test cases for testing all the edge cases.

Assisted-by: IBM Bob:1.0.6
Signed-off-by: Marc Hartmayer <marc@linux.ibm.com>
Reviewed-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
2026-07-31 09:51:14 +02:00
Mete Durlu
1aa1558f91 zmemtopo: Add case-insensitive partition name filtering
Introduce the -p/--partition-filter option to display only partitions
whose names contain a specified substring. The filter performs
case-insensitive matching and applies consistently across all output
formats (table, tree, and reverse tree views).

Example:

$ zmemtopo -p "part74"
LPAR/LEVEL      SIZE
PARTITION74      12G
└LEVEL4_1        12G
  ├LEVEL3_0       3G
  ├LEVEL3_1       3G
  ├LEVEL3_2       3G
  └LEVEL3_3       3G

Suggested-by: Niklas Schnelle <schnelle@linux.ibm.com>
Reviewed-by: Jan Höppner <hoeppner@linux.ibm.com>
Signed-off-by: Mete Durlu <meted@linux.ibm.com>
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
2026-07-31 09:51:14 +02:00
Mete Durlu
29a1ef0023 zmemtopo: Fix table view spacing
Left justify partition number and partition name fields on table view
to better accommodate untrimmed and longer partition names.

Reviewed-by: Jan Höppner <hoeppner@linux.ibm.com>
Signed-off-by: Mete Durlu <meted@linux.ibm.com>
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
2026-07-31 09:51:14 +02:00
Mete Durlu
39eb46763b zmemtopo: Remove false leading/trailing space trimming
Remove all util_strstrip() calls. The current users are command line
option stripping and partition name stripping.
Keep user passed arguments as is, typical shells already remove leading
and trailing whitespace unless explicitly quoted.
Spaces in partition names are valid. Keep the leading/trailing spaces
to preserve user given partition names.

Reviewed-by: Jan Höppner <hoeppner@linux.ibm.com>
Signed-off-by: Mete Durlu <meted@linux.ibm.com>
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
2026-07-31 09:51:14 +02:00
Mete Durlu
ce1464ad5f zmemtopo: Clean up hardcoded value
Replace array length of lpar name with LPAR_NAME_LEN macro.

Reviewed-by: Jan Höppner <hoeppner@linux.ibm.com>
Signed-off-by: Mete Durlu <meted@linux.ibm.com>
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
2026-07-31 09:51:14 +02:00
Steffen Eiden
c6d74eebfb pvimg: Fix hash comparison in SeHdrAadV2::contains_hash
Ensure both sides of the hash comparison use the same slice length
by explicitly slicing both the keyslot hash and the input hash to
UV_KEY_HASH_SIZE. Previously, only the keyslot hash was sliced while
comparing against the full input hash reference, which could lead to
incorrect comparisons if the input hash length differs.

This makes the comparison more explicit and ensures we're always
comparing equal-length hashes. This is useful if one hash is a subset of
another, e.g. a sha512 hash truncated to 32 bytes.

Fixes: 89577c2f8c ("pvimg: Use hybrid keys")
Reviewed-by: Marc Hartmayer <marc@linux.ibm.com>
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
2026-07-31 09:51:14 +02:00
Marc Hartmayer
ad129057b5 pvattest: tests: Implement TODO
Instead of blindly casting the values to u32 use try_into().unwrap().

Signed-off-by: Marc Hartmayer <marc@linux.ibm.com>
Reviewed-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
2026-07-28 11:00:01 +02:00
Marc Hartmayer
5b8fdc2bec pv: Improve API by marking some functions as must_use
Make the API less prone to error by specifying some of them as must_use.
This avoids issues that the result is not used.

Signed-off-by: Marc Hartmayer <marc@linux.ibm.com>
Reviewed-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
2026-07-28 11:00:01 +02:00
Marc Hartmayer
784cff18c3 pv: Reuse RequestVersion and RequestMagic
Use the typedefs to make the intention and compatibility of the fields
more clear.

Signed-off-by: Marc Hartmayer <marc@linux.ibm.com>
Reviewed-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
2026-07-28 11:00:01 +02:00
Timo Keller
fb33935c9b pvsecret: test using hybrid keys
Add unite tests and (binary) test asses for the hybrid key patch.

Co-developed-by: Marc Hartmayer <marc@linux.ibm.com>
Signed-off-by: Marc Hartmayer <marc@linux.ibm.com>
Signed-off-by: Timo Keller <tkeller@linux.ibm.com>
Reviewed-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
2026-07-28 11:00:01 +02:00
Marc Hartmayer
175b336d32 rust/utils: Replace get_verified_hkds with get_verified_hkds_new
Now as no users of get_verified_hkds exists, replace it with
get_verified_hkds_new.

Signed-off-by: Marc Hartmayer <marc@linux.ibm.com>
Reviewed-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
2026-07-28 11:00:01 +02:00
Steffen Eiden
9a5c9cd7f9 pv: Require matching versions of request and HKD
Enforce that a v{1,2} request also has a v{1,2} hostkey.
This requires to change the signature of Request::add_hostkey to return
a Result.

Co-Developed-by: Timo Keller <tkeller@linux.ibm.com>
Signed-off-by: Timo Keller <tkeller@linux.ibm.com>
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
Reviewed-by: Marc Hartmayer <marc@linux.ibm.com>
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
2026-07-28 11:00:01 +02:00
Steffen Eiden
73c227fa9a pvattest: Verify hybrid keys
Allow pvattest verify to verify hybrid keys. The hybrid key is
represented by a sha512 hash truncated to 32 bytes.

Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
Reviewed-by: Marc Hartmayer <marc@linux.ibm.com>
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
2026-07-28 11:00:01 +02:00
Steffen Eiden
1078e6d45f pvverify: Use hybrid keys
Allow pvverify to also verify hybrid keys. By default the key format is
autodetected.

Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
Reviewed-by: Marc Hartmayer <marc@linux.ibm.com>
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
2026-07-28 11:00:01 +02:00
Timo Keller
50808edb7c pvsecret: Use hybrid keys
Allow the creation of Add-secret requests using hybrid (=quantum safe)
keys. This results in using the headers in version 2 (0x200).

Co-developed-by: Marc Hartmayer <marc@linux.ibm.com>
Signed-off-by: Marc Hartmayer <marc@linux.ibm.com>
Signed-off-by: Timo Keller <tkeller@linux.ibm.com>
Reviewed-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
2026-07-28 11:00:01 +02:00
Timo Keller
9dca2d3181 pvattest: Use hybrid keys
Allow the creation of Attestation requests using hybrid (=quantum safe)
keys. This results in using the headers in version 2 (0x200).

Co-developed-by: Marc Hartmayer <marc@linux.ibm.com>
Signed-off-by: Marc Hartmayer <marc@linux.ibm.com>
Signed-off-by: Timo Keller <tkeller@linux.ibm.com>
Reviewed-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
2026-07-28 11:00:00 +02:00
Timo Keller
fc853f3259 pvimg/create-sehdr: Use hybrid keys
Allow the creation of SE headers with hybrid (=quantum safe) keys. This
results in using the headers in version 2 (0x200).

Co-developed-by: Marc Hartmayer <marc@linux.ibm.com>
Signed-off-by: Marc Hartmayer <marc@linux.ibm.com>
Signed-off-by: Timo Keller <tkeller@linux.ibm.com>
Reviewed-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
2026-07-28 11:00:00 +02:00
Timo Keller
89577c2f8c pvimg: Use hybrid keys
Allow the creation of SE images using headers with hybrid (=quantum safe) keys.
This results in using the headers in version 2 (0x200).

Co-developed-by: Marc Hartmayer <marc@linux.ibm.com>
Signed-off-by: Marc Hartmayer <marc@linux.ibm.com>
Signed-off-by: Timo Keller <tkeller@linux.ibm.com>
Reviewed-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
2026-07-28 11:00:00 +02:00
Marc Hartmayer
d6fc4921fb utils: Read hybrid HKDs
* Add `HkdVersion` (classic or hybrid) and `HkdVersionSelection`.
* Add `get_verified_hkds_new` that returns a list of verified HKDs
  read according to the given `HkdVersionSelection`.

Co-Developed-by: Timo Keller <tkeller@linux.ibm.com>
Signed-off-by: Timo Keller <tkeller@linux.ibm.com>
Signed-off-by: Marc Hartmayer <marc@linux.ibm.com>
Reviewed-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
2026-07-28 11:00:00 +02:00
Marc Hartmayer
cb9c2f993c utils/cli: Introduce AutoOrExplicit
* Add `AutoOrExplicit` that dispatches between `Auto` and `Explicit`.
* Add `AutoOrExplicitParser` for it.

In a later commit, this will be used for auto detection of HKD version
or explicitly specifying the version.

Signed-off-by: Marc Hartmayer <marc@linux.ibm.com>
Reviewed-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
2026-07-28 11:00:00 +02:00
Marc Hartmayer
637c8196a6 pvimg: tests: Refactor test code and increase coverage
Refactor test code and increase coverage. The new test code tests for
the correct error kind instead of simply for an error.

Assisted-by: IBM Bob:1.0.6
Signed-off-by: Marc Hartmayer <marc@linux.ibm.com>
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
2026-07-28 11:00:00 +02:00
Marc Hartmayer
c87d11abfe pvimg and create-sehdr: Refactor code to use generic flags
Replace version-specific flag enums (PcfV1, ScfV1) with a unified
SeHdrFlag enum and generic ControlFlagsModel. This refactoring provides:

 - Single SeHdrFlag enum for all control flags across SE header versions
 - Generic SeHdrControlFlagsModel with version-specific configurations
 - EffectiveControlFlags type replacing PlaintextControlFlagsV1/SecretControlFlagsV1
 - FlagsOverride mechanism for customizing flag configurations
 - Enhanced FromStr implementation for flag parsing
 - Comprehensive test coverage for flag operations and conversions

The new design improves maintainability by eliminating code duplication
and provides a more flexible API for working with SE header control
flags.

Assisted-by: IBM Bob:1.0.4
Signed-off-by: Marc Hartmayer <marc@linux.ibm.com>
Reviewed-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
2026-07-28 11:00:00 +02:00
Marc Hartmayer
20fffb31d7 pvimg: Introduce generic flags
Add a generic flags framework for managing (Secure Execution) header
control flags. This implementation provides:

  - Clap compatibility for auto-completion and proper help messages
  - ControlFlagTrait for defining flag types with bit positions and version support
  - ControlFlagsModel for managing default flags and validation rules
  - FlagsOverride for customizing individual flag states
  - EffectiveControlFlags representing the final flag configuration
  - UnknownFlags handling for forward compatibility
  - Comprehensive error handling via FlagValidationError
  - Display implementations for both human-readable and binary formats
  - Type-safe conversions between flag representations

The framework supports version-aware flag validation, allowing flags to
be marked as supported only in specific Secure Execution header
versions. It enables flexible flag configuration through overrides while
maintaining type safety and validation.

Assisted-by: IBM Bob:1.0.4
Signed-off-by: Marc Hartmayer <marc@linux.ibm.com>
Reviewed-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
2026-07-28 11:00:00 +02:00
Marc Hartmayer
2d330cd45f utils_macros: Implement derive_control_flag
Add a new derive macro 'derive_control_flag' that is used in the next
commit to reimplement how the code deals with Secure Execution control
flags.

It implements Display, IntoEnumIterator and the ControlFlagTrait for
enums using unit variants only.

  /// Trait for control flags that provide bit position information.
  pub trait ControlFlagTrait {
      /// Returns the bit position for this flag.
      fn bit_position(self) -> u8;
  }

Assisted-by: IBM Bob:1.0.4
Signed-off-by: Marc Hartmayer <marc@linux.ibm.com>
Reviewed-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
2026-07-28 11:00:00 +02:00
Marc Hartmayer
618e22e38b utils: Add utils_macros: Implement ValueEnumDisplay and ValueEnumFromStr macros
Add new derive macros ValueEnumDisplay and ValueEnumFromStr which can be
used to derive 'Display' and 'FromStr' for enums implementing
'clap::ValueEnum'. It helps in reducing boilerplate code and keep things
in sync.

Assisted-by: IBM Bob:1.0.4
Signed-off-by: Marc Hartmayer <marc@linux.ibm.com>
Reviewed-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
2026-07-28 11:00:00 +02:00
Marc Hartmayer
173a055372 pvimg: Use log level info for printing used flags
Use log level info for printed the used flags, instead of log level
debug.

Signed-off-by: Marc Hartmayer <marc@linux.ibm.com>
Reviewed-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
2026-07-28 11:00:00 +02:00
Timo Keller
4670b108a6 pv: Implement HostKey::V2 and Keyslot::V2
Introduce HybridPublicKey combining ECDH and ML-KEM public keys. Use it
for HostKey::V2 and add Keyslot::V2 for hybrid keyslots.

Add tests for the new functionality.

Assisted-by: IBM Bob:1.0.5
Signed-off-by: Timo Keller <tkeller@linux.ibm.com>
Reviewed-by: Marc Hartmayer <marc@linux.ibm.com>
Reviewed-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
2026-07-28 11:00:00 +02:00
Timo Keller
ebe56592c8 pv: Add hybrid key derivation support
Add derive_aes256_gcm_key_hybrid() function that derives an Aes256GcmKey
and ML-KEM1024 ciphertext from a private ECDH customer key, a public
ECDH target key, and a public ML-KEM target key. This enables hybrid
post-quantum cryptography support.

Assisted-by: IBM Bob:1.0.5
Co-developed-by: Marc Hartmayer <marc@linux.ibm.com>
Signed-off-by: Marc Hartmayer <marc@linux.ibm.com>
Signed-off-by: Timo Keller <tkeller@linux.ibm.com>
Acked-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
2026-07-28 11:00:00 +02:00
Marc Hartmayer
1746d2bb6a pv: Change ec_key() to return Option<&PKeyRef<Public>>
Prepare the API for future host keys that might not have an EC key by
returning Option instead of a direct reference. Additionally, use
&PKeyRef<Public> as the borrowed view on PKey for better API
consistency.

Signed-off-by: Marc Hartmayer <marc@linux.ibm.com>
Reviewed-by: Timo Keller <tkeller@linux.ibm.com>
Reviewed-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
2026-07-28 11:00:00 +02:00
Niklas Schnelle
91123e5d45 lib/zt_common.h: zpcimon: Add __force and use it to annotate le32toh() conversions
When using sparse on zpcimon several warnings like the one below are
generated:

  warning: incorrect type in argument 1 (different base types)
     expected unsigned int [usertype] __bsx
     got restricted __le32 [usertype] warning_temp_time

This is because several members in struct nvme_smart_log are marked as
__le32. These members are correctly converted to host endianness before
use via le32toh() respectively le16toh(). Since these functions take
their parameters as plain uint32_t or uint16_t however the implicit
conversion triggers the above warning. Fix this by adding the __force
attribute and using it to mark type conversions in the leXXtoh() calls.

Reviewed-by: Jan Höppner <hoeppner@linux.ibm.com>
Signed-off-by: Niklas Schnelle <schnelle@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2026-07-24 18:21:51 +02:00
Niklas Schnelle
0f0e8cf33b zpcimon: Make optics_los_implemente() static
Sparse noted that optics_los_implemente() can be static. Make it so.

Reviewed-by: Jan Höppner <hoeppner@linux.ibm.com>
Signed-off-by: Niklas Schnelle <schnelle@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2026-07-24 18:21:51 +02:00
Niklas Schnelle
7586a1ad59 zpcimon: Add missing include for __packed attribute
The __packed attribute is used without including the necessary
zt_common.h header, add it. Note that luckily the struct as it currently
exists doesn't actually required __packed as the natural alignment
leaves no holes. Still if in the future the reserved field is split up
it would need it and this keeps it consistent with the kernel
declaration.

Reviewed-by: Jan Höppner <hoeppner@linux.ibm.com>
Signed-off-by: Niklas Schnelle <schnelle@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2026-07-24 18:21:51 +02:00
Niklas Schnelle
1da328921b dbginfo.sh: Let zpcimon log both optical module and SMART data
Add zpcimon data collection with both SMART log and optical module data.
Do so in the Z device subsystem commands section to account for the fact
that zpcimon is no longer network specific. At the same time keep the
existing opticsmon invocation for compatibility when executed on systems
without zpcimon.

Reviewed-by: Jan Höppner <hoeppner@linux.ibm.com>
Reviewed-by: Jörn Siglen <siglen@de.ibm.com>
Signed-off-by: Niklas Schnelle <schnelle@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2026-07-24 18:21:51 +02:00
Niklas Schnelle
f96f09a5c9 nvmemon: libzpci: Skip SCLP on NVMes with non-IBM subsystem vendor ID
The SCLP Write Event Data Action Qualifier 4 is only available for
NVMes with subsystem vendor ID matching IBM. Add both IDs to struct
zpci_dev in libzpci. Also add them to the libzpci_example output
and adjust its output to multiline to handle more data.

Reviewed-by: Jan Höppner <hoeppner@linux.ibm.com>
Signed-off-by: Niklas Schnelle <schnelle@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2026-07-24 18:21:51 +02:00
Niklas Schnelle
4fd607ae5c zpcimon: Monitor for hotplug of NVMes to trigger SMART collection
Use libudev to monitor for "change" events in the "nvme" subsystem that
use the "pcie" transport to trigger NVMe SMART data collection. Only
collect data from the NVMe referenced by the udev event.

Assisted-by: IBM Bob:1.0.1
Reviewed-by: Jan Höppner <hoeppner@linux.ibm.com>
Signed-off-by: Niklas Schnelle <schnelle@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2026-07-24 18:21:51 +02:00
Niklas Schnelle
f1a11a01df zpcimon: Add JSON output for SMART data details
This uses the same format as:

   nvme smart-log --output-format=json /dev/nvmeX

One complication being that several values in the SMART data use
unsigned 128 bit integers. Both GCC and Clang support __uint128_t as
a C extension but don't offer printf() support for it. Just like
nvme-cli add a custom uint128_t to string function..

Reviewed-by: Jan Höppner <hoeppner@linux.ibm.com>
Signed-off-by: Niklas Schnelle <schnelle@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2026-07-24 18:21:50 +02:00
Niklas Schnelle
2eceedeb11 zpcimon: Add NVMe SMART data monitor
Add a new monitor which uses libnvme to collect SMART data from NVMes.
This monitor only implements the .collect_adapter_data operation since
it carries no state across data collections. Nevertheless for future
symmetry and possible future expansion also add an empty struct
nvmemon_ctx.

Reviewed-by: Jan Höppner <hoeppner@linux.ibm.com>
Signed-off-by: Niklas Schnelle <schnelle@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2026-07-24 18:21:50 +02:00
Niklas Schnelle
a9dbb8a1a2 zpcimon: Factor out JSON printing of common PCI adapter attributes
Some of the attributes printed as JSON by opticsmon are common to all
PCI adapters factor these out into reusable helper functions
zpci_adapter_json_print_start() and zpci_adapter_json_print_end().

Reviewed-by: Jan Höppner <hoeppner@linux.ibm.com>
Signed-off-by: Niklas Schnelle <schnelle@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2026-07-24 18:21:50 +02:00
Niklas Schnelle
78dd82d129 libzpci: Extract get_device_node() from zpcictl to libzpci
The get_device_node() function for getting the name of an NVMe's
controller device e.g. "nvme0" as implemented in zpcictl is generally
useful and will be needed for NVMe SMART data collection in zpcimon as
well. Move it to libzpci and rename it to zpci_get_nvme_device_node() to
account for the change in namespace.

It would be tempting to pass a struct zpci_dev* instead of a const char*
but as zpcictl uses an incompatible struct zpci_device* this will
require a larger rework. Prioritize minimizing code duplication for now.

Reviewed-by: Jan Höppner <hoeppner@linux.ibm.com>
Signed-off-by: Niklas Schnelle <schnelle@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2026-07-24 18:21:50 +02:00
Niklas Schnelle
113e9ebfef zpcimon: Split optics monitoring out into separate monitor
Introduce the concept of a generic zPCI device monitor by creating the
zpcimon_ops operations struct turning all optics specific calls into
abstract monitor calls. Handle monitors as a static array of zpcimon_ops
based monitors of which the now split out optics monitor is currently
the only one. All operations are in principle optional though a monitor
which provides an init operation must also provide the corresponding
destroy operation.

Keep the base64 based JSON pair as non optics specific for later re-use
and make it just skip the output in the very unlikely case that encoding
fails. In follow on commits a monitor for NVMe devices collecting SMART
data will be added.

Reviewed-by: Jan Höppner <hoeppner@linux.ibm.com>
Signed-off-by: Niklas Schnelle <schnelle@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2026-07-24 18:21:50 +02:00
Niklas Schnelle
f9ef8a0106 util_time: zpcimon: Use a constant for seconds per day
Replace the magic value 86400 with a new util_time.h constant
SEC_PER_DAY.

Reviewed-by: Jan Höppner <hoeppner@linux.ibm.com>
Signed-off-by: Niklas Schnelle <schnelle@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2026-07-24 18:21:50 +02:00
Niklas Schnelle
2c63c69af4 zpcimon: Allow setting output format and use JSON Lines in systemd unit
Introduce separate '--format' option to set output format. Use the new
JSON Lines format explicitly in the systemd unit. Since JSON Lines
format only uses printable characters this removes the need for passing
'-a' to systemctl status. At the same time JSON-SEQ is more easily
parsable using 'jq --seq' so keep it the default for easier scripting.

Reviewed-by: Jan Höppner <hoeppner@linux.ibm.com>
Signed-off-by: Niklas Schnelle <schnelle@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2026-07-24 18:21:50 +02:00
Niklas Schnelle
8a4a4e0557 libutil/util_fmt: Add util_fmt_type_to_name()
Add a function to get the textual name of an enum util_fmt_t value. To
make this robust to changes in the order of elements in the format array
initialize this using named indices.

Reviewed-by: Jan Höppner <hoeppner@linux.ibm.com>
Signed-off-by: Niklas Schnelle <schnelle@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2026-07-24 18:21:50 +02:00
Niklas Schnelle
e8550a4f8d opticsmon: zpcimon: Rename opticsmon to zpcimon
The opticsmon tool started out as a tool for monitoring the health of
optical modules in directly attached PCI NICs. In the future however it
will also monitor the health of other PCI devices. In particular in
a first step it will monitor the health of directly attached NVMe
devices.

To reflect this broadening of its scope rename opticsmon to zpcimon. Add
zpcimon.service and install it both under the new name and symlinked as
opticsmon.service for backwards compatibility. Since users are expected
to mostly just enable the service this keeps old instructions just
working.

Reviewed-by: Jan Höppner <hoeppner@linux.ibm.com>
Signed-off-by: Niklas Schnelle <schnelle@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2026-07-24 18:21:50 +02:00
Niklas Schnelle
e7bc4a5d31 opticsmon: Enforce 1 second as minimal interval
So far it was possible for the user to specify an interval of 0 which
would result in not re-arming the timer at all thus disabling interval
based data collection. While this would still do optics data collection
on startup and link changes the intention is to collect data at least
every 24 hours. If interval is given as 0 treat this as minimum update
interval of 1 second.

Fixes: c34adb9cab ("opticsmon: Introduce opticsmon tool")
Reviewed-by: Jan Höppner <hoeppner@linux.ibm.com>
Signed-off-by: Niklas Schnelle <schnelle@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2026-07-24 18:21:50 +02:00
Niklas Schnelle
8b38bf37b0 opticsmon: Fix wrong size check for OPTICS_QSFP28_LOS_IMPLEMENTED_OFFSET
The code accesses OPTICS_QSFP28_LOS_IMPLEMENTED_OFFSET so the size needs
to be one larger for the LOS to be accessible.

Fixes: c34adb9cab ("opticsmon: Introduce opticsmon tool")
Reviewed-by: Jan Höppner <hoeppner@linux.ibm.com>
Signed-off-by: Niklas Schnelle <schnelle@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2026-07-24 18:21:50 +02:00
Niklas Schnelle
3696d6bd08 opticsmon: Handle error return of ethtool_nl_connect()
Don't just ignore the return of ethtool_nl_connect(). This would also
otherwise lead to invalid socket accesses later. Also make
ethtool_nl_connect() return negative error values instead of
EXIT_FAILURE.

Reviewed-by: Jan Höppner <hoeppner@linux.ibm.com>
Signed-off-by: Niklas Schnelle <schnelle@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2026-07-24 18:21:50 +02:00
Niklas Schnelle
6847b6a8cf opticsmon: Fix error path free of struct optics in ethtool_nl_get_optics()
When ethtool_nl_get_optics() fails after ethtool_nl_cb() has allocated
oi->raw this memory would leak because only the struct optics* itself
was freed. Fix this by using optics_free() and relying on free(oi->raw)
being a no-op for NULL pointers in combination with using util_zalloc()
to handle errors both before and after oi->raw was allocated.

Fixes: c34adb9cab ("opticsmon: Introduce opticsmon tool")
Reviewed-by: Jan Höppner <hoeppner@linux.ibm.com>
Signed-off-by: Niklas Schnelle <schnelle@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2026-07-24 18:21:50 +02:00
Niklas Schnelle
1f339a9e7e opticsmon: Return errors from monitor_wait_loop() in monitor_mode()
Don't ignore the return value of monitor_wait_loop() but instead forward
it to the caller.

Fixes: c34adb9cab ("opticsmon: Introduce opticsmon tool")
Reviewed-by: Jan Höppner <hoeppner@linux.ibm.com>
Signed-off-by: Niklas Schnelle <schnelle@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2026-07-24 18:21:50 +02:00
Niklas Schnelle
905c3921af opticsmon: Close epoll fd in monitor_wait_loop()
The epoll fd (epfd) in monitor_wait_loop() is never closed. Close it in
all return paths. Also there is no explicit check for an error return of
epoll_create1() add that too.

Fixes: c34adb9cab ("opticsmon: Introduce opticsmon tool")
Reviewed-by: Jan Höppner <hoeppner@linux.ibm.com>
Signed-off-by: Niklas Schnelle <schnelle@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2026-07-24 18:21:50 +02:00
Niklas Schnelle
e55e0e470a opticsmon: Fix wrong JSON print for tx_fault
The "tx_fault" field in the JSON output accidentally contained the
rx_los value instead of the tx_fault one. Change it to the correct
value. Note that this does not impact the data relayed to firmware as
that uses the raw module data.

Fixes: c34adb9cab ("opticsmon: Introduce opticsmon tool")
Reviewed-by: Jan Höppner <hoeppner@linux.ibm.com>
Signed-off-by: Niklas Schnelle <schnelle@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2026-07-24 18:21:50 +02:00
Jan Höppner
9d9fca6e72 Prepare for next release
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2026-07-22 18:49:20 +02:00
Jan Höppner
2a6657dfd1 New release s390-tools-2.43.1
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2026-07-22 18:46:13 +02:00
Hendrik Brueckner
b3dd05e16d iucvterm/ts-shell: Add Perl taint tests for auditdir and getpwuid()
The audit directory ts-shell configuration and also the output of
getpwuid() are considered tainted when running in Perl taint (-T) mode.

Provide untaint checks with validating the content.

Reviewed-by: Jan Höppner <hoeppner@linux.ibm.com>
Signed-off-by: Hendrik Brueckner <brueckner@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2026-07-22 17:45:06 +02:00
Hendrik Brueckner
f216862052 iucvterm/ts-shell: Split reading and checking configuration
The updateConfiguration() function updates and checks the ts-shell
configuration for each line in the ts-shell configuration file.

Run the checks just once after the configuration is completely read.

Reviewed-by: Jan Höppner <hoeppner@linux.ibm.com>
Signed-off-by: Hendrik Brueckner <brueckner@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2026-07-22 17:45:06 +02:00
Hendrik Brueckner
e4aeeef8eb iucvterm/ts-shell: Introduce config parameter to specify iucvconn binary
Introduce a configuration setting to allow administrators to specify
path to the iucvconn binary as part of the ts-shell configuration.

Reviewed-by: Jan Höppner <hoeppner@linux.ibm.com>
Signed-off-by: Hendrik Brueckner <brueckner@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2026-07-22 17:45:06 +02:00
Hendrik Brueckner
c911419121 iucvterm/ts-shell: Validate guest name before constructing the audit file
The guest name is provided on the CLI and needs to be validated before
the file path to the session audit file is constructed.

Reviewed-by: Jan Höppner <hoeppner@linux.ibm.com>
Reviewed-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Hendrik Brueckner <brueckner@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2026-07-22 17:45:06 +02:00
Hendrik Brueckner
e6de495daa iucvterm/ts-shell: Improve regex for matching terminal identifier
Anchor the regex to no longer match a part within the terminal
identifier.

Reviewed-by: Steffen Eiden <seiden@linux.ibm.com>
Reviewed-by: Jan Höppner <hoeppner@linux.ibm.com>
Signed-off-by: Hendrik Brueckner <brueckner@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2026-07-22 17:45:06 +02:00
Hendrik Brueckner
419d93bef7 iucvterm/ts-shell: Corrections to run in Perl Taint (-T) mode
Perl provides a Taint (-T) mode which tracks data that is obtained
by external means (e.g. arguments, reading file contents, environment
variables, ...)

Introduce a first set of corrections to clear and validate data
obtained from external sources.

Reviewed-by: Jan Höppner <hoeppner@linux.ibm.com>
Signed-off-by: Hendrik Brueckner <brueckner@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2026-07-22 17:45:06 +02:00
Hendrik Brueckner
2714f1d90e iucvterm/ts-shell: Introduce pager config to replace env variable
Do not obtain the pager program through the PAGER environment
variable.  Instead, introduce a configuration setting to specify
the pager program as part of the ts-shell configuration.

Reviewed-by: Jan Höppner <hoeppner@linux.ibm.com>
Signed-off-by: Hendrik Brueckner <brueckner@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2026-07-22 17:45:06 +02:00
Hendrik Brueckner
10ec3f18fc iucvterm/ts-shell: Correct user/group names being interpreted as regex
When ts-shell creates the authorization for current user, the
user name and group names are interpreted as regex.  This might
create additional authorizations which are not permitted for
current user.

Correct this behavior by quoting the user name in the regex.

Reviewed-by: Steffen Eiden <seiden@linux.ibm.com>
Reviewed-by: Jan Höppner <hoeppner@linux.ibm.com>
Signed-off-by: Hendrik Brueckner <brueckner@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2026-07-22 17:45:06 +02:00
Hendrik Brueckner
0c771423b4 iucvterm/iucvtty: Ensure PTY and server fd's are closed at exec
Check the return code of fcntl() calls to ensure the close-on-exec
setting succeeded.  Otherwise, the PTY and server file descriptors
would become available to the forked client process.

Reviewed-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Hendrik Brueckner <brueckner@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2026-07-22 17:45:06 +02:00
Hendrik Brueckner
f67fa03581 iucvterm/iucvtty: Prevent connection stalls when receiving TERM env
iucvtty expects to receive the TERM environment information right
after a connection has been established. Reading this information
can lead to a connection stall (DoS).  Introduce a temporary timeout
and fall back to the default TERM environment.

Reviewed-by: Jan Höppner <hoeppner@linux.ibm.com>
Signed-off-by: Hendrik Brueckner <brueckner@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2026-07-22 17:45:06 +02:00
Hendrik Brueckner
29db9032b3 iucvterm: Improve received message type and length checking
The iucvtty_read_msg() now receives the entire message header.
Perform message header checks for specific message types where the
payload length is clearly defined.  Also this needs to be done prior
starting the message chunk processing because the message datalen
field will be adjusted based on the read chunks.

Reviewed-by: Jan Höppner <hoeppner@linux.ibm.com>
Signed-off-by: Hendrik Brueckner <brueckner@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2026-07-22 17:45:06 +02:00
Hendrik Brueckner
56a55901c1 iucvterm/tests: Update test cases to use iucvtty_skip_msg_chunk()
Reviewed-by: Jan Höppner <hoeppner@linux.ibm.com>
Signed-off-by: Hendrik Brueckner <brueckner@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2026-07-22 17:45:04 +02:00
Jan Höppner
43edafd088 iucvterm/tests: Remove unused termio.h
The inclusion of termio.h leads to the following compilation error:

  CC          iucvterm/tests/test_functions.o
test_functions.c:16:10: fatal error: termio.h: No such file or directory
   16 | #include <termio.h>
      |          ^~~~~~~~~~

The termio ioctl interface has been obsolete for a very long time and
was replaced by termios. termio.h was still present for compatibility
but was removed with glibc 2.42.
iucvterm uses termios.h correctly in the tool. The test however not only
includes termio.h erroneously, it doesn't even require it. Remove the
inclusion of termio.h.

Reviewed-by: Marc Hartmayer <mhartmay@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2026-07-22 17:44:10 +02:00
Hendrik Brueckner
b1e3ee255f iucvterm: Improve receiving of IUCV terminal messages
If iucvtty_read_data() receives message data and reads less than
MSG_DATA_OFFSET, out-of-bound reads will happen.

Correct and improve the receiving path by always reading the entire
message header. Also improve the "residual" message data handling by
replacing and processing every message now as chunk.

Acked-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Hendrik Brueckner <brueckner@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2026-07-22 17:44:10 +02:00
Hendrik Brueckner
a69a46eafd iucvterm/iucvtty: Close PTY slave fd after fork
The PTY slave file descriptor is the controlling terminal
allocated for the child process.  When all file descriptors
are being closed, a SIGHUP is triggered for the child process.

Keeping the file descriptor in the parent open would prevent
the SIGHUP to be delivered to the child process.

For proper SIGHUP processing, close the file descriptor in
the parent process.

Acked-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Hendrik Brueckner <brueckner@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2026-07-22 17:44:10 +02:00
Hendrik Brueckner
c85b4e54dd iucvterm/iucvtty: Validate TERM environment name
Improve handling of TERM environment processing by validating
the received terminal name.  If the terminal name is not valid,
a message will be displayed and the default terminal will be used.

Reviewed-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Hendrik Brueckner <brueckner@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2026-07-22 17:44:10 +02:00
Finn Callies
da4b881eac 95sel-ebc: Add umount to boot service
Explicitly unmount the root partition on unit stopping to prevent still
mounted boot partition on switch root which results in boot being unable
to be mounted after switch root.

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-07-22 17:44:10 +02:00
Finn Callies
9f03ddf519 95sel-ebc: Add udev-settle dependency
Since this unit cannot have a dependency on the actual needed
dev-disk-by2xdlabel-boot.device unit because it might not exist if the
label is never set but this unit has to run after it when it does exist
wait for udev-settle which creates the symlink if it does exist.

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-07-22 17:44:10 +02:00
Aswin Karuvally
a05935cca9 osasnmpd: Fix SNMP non-compliance
Update osasnmpd to support Object ID Sub-IDs with length up-to and
including 10 digits, in line with SNMP specification. While at it, also
replace sprintf() calls with snprintf(). Together, these changes
contribute to better memory safety.

Suggested-by: Alexandra Winter <wintera@linux.ibm.com>
Reviewed-by: Hidayath Khan <hidayath@linux.ibm.com>
Signed-off-by: Aswin Karuvally <aswin@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2026-07-22 17:44:10 +02:00
Marc Hartmayer
6f9f846bc0 pv: verify_chain: Use error statements instead of debug
These messages indicate verification failures and are therefore reported
as errors rather than debug output. Unfortunately,
X509StoreContext::init(...) expects the callback to return an OpenSSL
ErrorStack, so it's not possible to propagate these failures through our
own error hierarchy.

Signed-off-by: Marc Hartmayer <marc@linux.ibm.com>
Reviewed-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2026-07-22 17:44:10 +02:00
Marc Hartmayer
603a2b1762 pv: Increase flexibility of verify_chain()
Increase the flexibility of verify_chain and reduce the need of using
clone.

Signed-off-by: Marc Hartmayer <marc@linux.ibm.com>
Reviewed-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2026-07-22 17:44:10 +02:00
Marc Hartmayer
dcece3f980 pv: Fix typo
Signed-off-by: Marc Hartmayer <marc@linux.ibm.com>
Reviewed-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2026-07-22 17:44:10 +02:00
Marc Hartmayer
ff19180f03 pv: Add root CA organization pinning to certificate verification
Enforce root CA organization name requirements during certificate chain
validation to prevent accepting certificates from unexpected root CAs.

Add RootCaVerification enum with RootCaOrganizationPinning and
SkipPinning variants. Extend verify_chain() to validate root CA
organization. Default to pinning "DigiCert" for standard trust chains,
skip pinning when custom root CA is provided.

Assisted-by: IBM Bob:1.0.6
Signed-off-by: Marc Hartmayer <marc@linux.ibm.com>
Reviewed-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2026-07-22 17:44:10 +02:00
Gerald Schaefer
42eba67268 mon_procd: Fix possible static buffer overflow
The command line length value is not properly checked and limited to the
documented 1024 bytes, even though a MAX_CMD_LEN is already defined but
not used.

With this, an overflow of the static char mon_record[] buffer is possible
in read_cmdline(), corrupting adjacent .bss data. The data is sanitized to
printable ASCII bytes, but in theory a crash of the mon_procd daemon could
be possible.

Fix it by adding a check and truncation similar to the other restricted
fields like e.g. ruser_len or cmd_len.

Reviewed-by: Ilya Leoshkevich <iii@linux.ibm.com>
Signed-off-by: Gerald Schaefer <gerald.schaefer@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2026-07-20 15:25:59 +02:00
Peter Oberparleiter
3cf95a1d92 zdev: Harden against invalid udev data
Reduce chances of unintended side-effects when importing udev
data which might have been corrupted.

Reviewed-by: Vineeth Vijayan <vneethv@linux.ibm.com>
Reviewed-by: Jan Höppner <hoeppner@linux.ibm.com>
Signed-off-by: Peter Oberparleiter <oberpar@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2026-07-20 15:25:59 +02:00
Peter Oberparleiter
5e93b53f78 zdev: Harden against invalid import data
Reduce chances of unintended side-effects when importing device
configuration data which might have been corrupted.

Reviewed-by: Vineeth Vijayan <vneethv@linux.ibm.com>
Reviewed-by: Jan Höppner <hoeppner@linux.ibm.com>
Signed-off-by: Peter Oberparleiter <oberpar@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2026-07-20 15:25:59 +02:00
Peter Oberparleiter
360e8d4156 zdev: Harden against invalid firmware data
Reduce chances of unintended side-effects when evaluating firmware
data which might have been corrupted.

Reviewed-by: Vineeth Vijayan <vneethv@linux.ibm.com>
Reviewed-by: Jan Höppner <hoeppner@linux.ibm.com>
Signed-off-by: Peter Oberparleiter <oberpar@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2026-07-20 15:25:59 +02:00
Peter Oberparleiter
a048670bec zdev: Harden against invalid hypervisor data
Reduce chances of unintended side-effects when evaluating hypervisor
data which might have been corrupted.

Reviewed-by: Vineeth Vijayan <vneethv@linux.ibm.com>
Reviewed-by: Jan Höppner <hoeppner@linux.ibm.com>
Signed-off-by: Peter Oberparleiter <oberpar@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2026-07-20 15:25:59 +02:00
Volkan Unal
4ca93aa808 fdasd: Fix memory leak in yes_no() function
The yes_no() function was leaking memory when returning
early from the loop, as the 'answer' buffer allocated by
getline() was not freed before the return statements.

Restructure the function to use a single exit point, ensuring
free(answer) is always called before returning.

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-07-20 15:25:59 +02:00
Eduard Shishkin
9a5886c0b3 zipl/src: Check keyword duplications in BLS entries
sort_bls_entries() already checks for the keyword "title"
duplication. Add also checks for keywords "linux", "initrd", and
"options" duplication (Rule #5).

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-07-20 15:25:59 +02:00
Eduard Shishkin
78676df2f0 zipl-editenv: Check in-bootmap environment block validity
Check decimal prefixes, representing site values in on-disk
environment block. Reject any values different from {0, ..., 9}
as invalid ones.

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-07-20 15:25:59 +02:00
Eduard Shishkin
224e35b600 zipl/src: Check file trailer
Before extracting a file trailer, check that the file is big enough;
Check that signature length stored in the extracted trailer doesn't
exceed the size of the file without the trailer. Treat the case of
the failed checks as unsigned file.

Without the checks, memcmp() may read the area before the allocated
@buffer, dumping heap into bootmap or crashing.

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-07-20 15:25:59 +02:00
Eduard Shishkin
bd52e14740 zipl/boot: Check in-bootmap environment block syntax
When parsing environment block, check each its line for the
compliance with the 'foo=bar\n' pattern. In case of missing '=',
or '\n' abort the parsing procedure with a warning message.

Without this check, stage3 may write a zero byte to the absolute
address 0 (robustness defect)

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-07-20 15:25:59 +02:00
Eduard Shishkin
dd380942ed zipl/boot: Limit the hash table size when parsing environment block
When parsing environment block, limit the number of the hash table
entries, so that total size of all records doesn't exceed PAGE_SIZE
bytes allocated for that hash table.

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-07-20 15:25:59 +02:00
Eduard Shishkin
9ce9ed72a9 zipl/boot: Fix the bounds check in the command line processing
By design, before replacement of '${FOO}' with its value, the
procedure checks that the resulted command line doesn't exceed the
maximum one.
The old check used the never updated length of the original command
line, which is incorrect. Instead, use its current length resulting
from the replacement happened at the previous iteration.

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-07-20 15:25:59 +02:00
Mikhail Zaslonko
a8a87779c0 zdump/dfi_lkcd: Validate LKCD page address against mem_end
mem_init_flex() allocates the page header index array sized for
ADDR_TO_IDX(mem_end) + 1 entries, but never checked that each
page record's address falls within [0, mem_end).
A crafted LKCD page record with addr >= mem_end writes 8 bytes
beyond the allocation.

Fix by rejecting any page record whose address is > mem_end - PAGE_SIZE
with ERR_EXIT(), immediately after the end-of-dump marker check.

Signed-off-by: Mikhail Zaslonko <zaslonko@linux.ibm.com>
Reviewed-by: Alexander Egorenkov <egorenar@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2026-07-20 15:25:59 +02:00
Mikhail Zaslonko
cdabf280ac zdump/dfi_lkcd: Validate LKCD page size before buffer read
read_page_buf() uses the file-controlled pg_hdr->size directly as the
byte count for zg_read() into fixed stack buffers of PAGE_SIZE.
A crafted LKCD dump with pg_hdr->size > PAGE_SIZE overflows the buffer
and smashes the stack frame. A size of 0 for a raw page silently
produces uninitialised data.

Enforce page header size constraints following crash-utility's logic:
- Compressed or Raw pages with size > PAGE_SIZE are invalid;
  exit with an error.
- Raw pages with size 0 contain no data in the file; fill the
  output buffer with zeros without reading.
- Compressed pages with size 0 are caught by the uncompress() return
  code check.
- Raw pages with size != PAGE_SIZE are invalid; exit with an error.

Check the return code of uncompress() and exit with an error if
decompression fails, rather than silently proceeding with an incomplete
output buffer.

Signed-off-by: Mikhail Zaslonko <zaslonko@linux.ibm.com>
Reviewed-by: Alexander Egorenkov <egorenar@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2026-07-20 15:25:59 +02:00
Marc Hartmayer
33c75584cd zipl: Fix -Wstringop-overread warning in IMPORT_DATA macro
Declare binary data symbols as arrays to correctly represent
linker-provided symbols of unknown size. This resolves compiler warnings
about reading 22-24 bytes from a 1-byte region when including stage*
loaders.

DEBUG: In function ‘memcpy’,
DEBUG:     inlined from ‘boot_init_eckd_ldl_stage0’ at boot.c:181:2,
DEBUG:     inlined from ‘install_svdump_eckd_ldl’ at install.c:891:2,
DEBUG:     inlined from ‘install_dump’ at install.c:1221:9,
DEBUG:     inlined from ‘main’ at zipl.c:185:9:
DEBUG: /usr/include/bits/string_fortified.h:29:10: warning: ‘__builtin_memcpy’ reading 22 bytes from a region of size 1 [-Wstringop-overread]
DEBUG:    29 |   return __builtin___memcpy_chk (__dest, __src, __len,
DEBUG:       |          ^

Signed-off-by: Marc Hartmayer <marc@linux.ibm.com>
Reviewed-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2026-07-20 15:25:59 +02:00
Alexander Egorenkov
1f68c1aaf3 zdump/ngdump: Perform sanity checks on path to dump image
Ensure that the path to a dump image specified in the NGDump meta file
of a dump device points to a valid location within the dump device.
Especially, disallow escaping from a dump device with a dump image path
using references to '..' or symbolic links pointing outside of the dump
device.

Signed-off-by: Alexander Egorenkov <egorenar@linux.ibm.com>
Reviewed-by: Ilya Leoshkevich <iii@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2026-07-20 15:25:59 +02:00
Alexander Egorenkov
6c6938d1e2 zdump/ngdump: Use OpenSSL's SHA256 to compute digest of dump image
Replace the popen() call to the external tool sha256sum with OpenSSL's
SHA256 digest algorithm to compute the checksum of a dump image
to improve security of NGDump.

The call to popen() is inherently insecure because a malicious user
controlling a dump device could manipulate the latter and let the user
applying zgetdump to the dump device execute arbitrary shell code.

Furthermore, getting rid of process and shell spawning performed by
popen() shall improve performance of calculation of dump image
checksums.

Signed-off-by: Alexander Egorenkov <egorenar@linux.ibm.com>
Reviewed-by: Mikhail Zaslonko <zaslonko@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2026-07-20 15:25:59 +02:00
Alexander Egorenkov
90ad42d814 zdump/zg: Use fstat() to obtain file information in zg_open()
Use the file descriptor opened in zg_open() before to obtain information
about the file. Theoretically, there is a tiny window for a race
condition between opening a file and obtaining its information with
stat(). A malicious user could use it to entice incorrect behavior
from zgetdump. Using the same file descriptor for both operations
prevents such situations.

Suggested-by: Peter Jin <pjin@linux.ibm.com>
Signed-off-by: Alexander Egorenkov <egorenar@linux.ibm.com>
Reviewed-by: Marc Hartmayer <marc@linux.ibm.com>
Reviewed-by: Mikhail Zaslonko <zaslonko@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2026-07-20 15:25:59 +02:00
Alexander Egorenkov
cf7c74b2dc zdump/ngdump: Include header zg.h in ngdump.h
Add the missing header include statement because some function
declarations use struct zg_fh from zg.h.

Signed-off-by: Alexander Egorenkov <egorenar@linux.ibm.com>
Reviewed-by: Mikhail Zaslonko <zaslonko@linux.ibm.com>
Reviewed-by: Marc Hartmayer <marc@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2026-07-20 15:25:59 +02:00
Alexander Egorenkov
4388a6d299 zdump/ngdump: Remove prefix 'ngdump_' from the name of private functions
The name of a private NGDump function does not require to have a unique
prefix to avoid naming conflicts with public functions of other C modules.
Furthermore, be consistent with private NGDump functions not having
the prefix 'ngdump_'.

Signed-off-by: Alexander Egorenkov <egorenar@linux.ibm.com>
Reviewed-by: Mikhail Zaslonko <zaslonko@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2026-07-20 15:25:59 +02:00
Mikhail Zaslonko
44821d0586 zdump/dfo_elf: Include vmcoreinfo note size in ELF header allocation
dfo_elf_init() allocates the output ELF header buffer based on
HDR_BASE_SIZE, per-CPU note sizes, and per-memory-chunk overhead,
but does not account for the vmcoreinfo note. notes_init() then
writes the full vmcoreinfo string into the buffer via nt_vmcoreinfo(),
overflowing the heap allocation for any dump with a vmcoreinfo blob
larger than what fits in HDR_BASE_SIZE. The following ABORT check
fires after the heap was already corrupted.

Fix by extracting the allocation size calculation into a new
hdr_alloc_size() function that explicitly accounts for all items
written into the header buffer.
Remove the opaque HDR_BASE_SIZE and HDR_PER_MEMC_SIZE macros.

Signed-off-by: Mikhail Zaslonko <zaslonko@linux.ibm.com>
Reviewed-by: Alexander Egorenkov <egorenar@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2026-07-20 15:25:59 +02:00
Mikhail Zaslonko
405b2da3f3 zdump/dfi_vmcoreinfo: Validate vmcoreinfo value length before memcpy
vmcoreinfo_item() copies a key's value string from the vmcoreinfo heap
blob into the caller's buffer using memcpy() without checking the source
length. The length is derived from the distance between the '=' separator
and the next '\n' (or '\0') in the blob, which is bounded only by the
total vmcoreinfo size. A crafted dump with a vmcoreinfo value >= 1024
bytes would overflow the buffer.
Additionally, the len parameter of vmcoreinfo_item() was declared UNUSED
and never checked.
Fix by computing val_len before the copy and returning -1 if val_len >= len.
Change len type from int to size_t, which is natural for a buffer size,
drops the need for a negativity guard, and makes the call site passing
sizeof(str) type-consistent.
Write directly into the caller's buf, instead of going through the
intermediate str[].
Replace two strchr() calls with a single strchrnul().

Signed-off-by: Mikhail Zaslonko <zaslonko@linux.ibm.com>
Reviewed-by: Alexander Egorenkov <egorenar@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2026-07-20 15:25:59 +02:00
Mikhail Zaslonko
d3ac9f3365 zdump/dfi_vmdump: Validate ADSR sec5_len before buffer read
The 16-bit sec5_len field from the ADSR record was used directly as
the byte count for zg_read() and ebc_2_asc() into a pair of 1024-byte
stack buffers.  A crafted VMDUMP file with sec5_len > 1024 would
overflow both buffers and write past the stack frame.

The overflow is only reachable when zgetdump is invoked with the -V
(verbose) flag, but that is a common diagnostic usage.

Section 5 contains a human-readable dump symptom string used only for
display purposes.  Truncate sec5_len to sizeof(buf) - 1 so that
oversized values are silently clamped rather than causing an abort,
and the symptom string is still printed up to the buffer limit.

Signed-off-by: Mikhail Zaslonko <zaslonko@linux.ibm.com>
Reviewed-by: Alexander Egorenkov <egorenar@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2026-07-20 15:25:59 +02:00
Ingo Franzki
7535682b1b zkey: Silence false positive -Wstringop-overflow= warning
Building with ASAN=1 produces the following warning:

    utils.c: In function 'sysfs_get_mkvps':
    utils.c:416:9: warning: writing 8 bytes into a region of size 0
                                                [-Wstringop-overflow=]
      416 |         memcpy(mk_reg->mkvp, &mkvp, sizeof(mkvp));
          |         ^
    In file included from utils.c:32:
    utils.h:51:17: note: at offset 4 into destination object 'mk_state'
                                                 of size 4
       51 |         int     mk_state;

This is a false positive, field mk_reg->mkvp is defined as an array of
u8 with a length of 16 elements, thus it is 16 bytes in size, and thus
more than long enough to copy 8 bytes into it.

Signed-off-by: Ingo Franzki <ifranzki@linux.ibm.com>
Reviewed-by: Finn Callies <fcallies@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2026-07-20 15:25:59 +02:00
Ingo Franzki
72f1a2e0a9 zkey: Fix typo
The word 'device' is spelled wrongly at 2 places.

Reported-by: Finn Callies <fcallies@linux.ibm.com>
Signed-off-by: Ingo Franzki <ifranzki@linux.ibm.com>
Reviewed-by: Finn Callies <fcallies@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2026-07-20 15:25:59 +02:00
Ingo Franzki
220f51fb98 libseckey: Fix integer underflow for malformed SPKIs
When the BER encoded BITRSTRING field has a zero length, then skipping
the unused-bits byte that a BITRSTING normally contains will cause a
underflow and thus may cause an out of bounds read by the caller of
sk_ep11_parse_spki().

Assisted-by: IBM Bob:2.0.0
Signed-off-by: Ingo Franzki <ifranzki@linux.ibm.com>
Reviewed-by: Finn Callies <fcallies@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2026-07-20 15:25:59 +02:00
Ingo Franzki
5ab3473442 libseckey: Fix length check in sk_cca_get_public_from_rsa_key()
Check for the size of struct cca_rsa_pub_key_section, instead of
struct cca_ec_pub_key_section.

Assisted-by: IBM Bob:2.0.0
Signed-off-by: Ingo Franzki <ifranzki@linux.ibm.com>
Reviewed-by: Finn Callies <fcallies@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2026-07-20 15:25:59 +02:00
Ingo Franzki
cf51aa1421 libseckey: Fix incorrect flag checking related to RSA X9.31
Use a bit-wise AND, not a logical AND to mask the flag out of the flags
field.

Assisted-by: IBM Bob:2.0.0
Signed-off-by: Ingo Franzki <ifranzki@linux.ibm.com>
Reviewed-by: Finn Callies <fcallies@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2026-07-20 15:25:59 +02:00
Ingo Franzki
b1d44876bd libseckey: FIx memory leak in error path (OpenSSL 1.1.1 only)
When EVP_PKEY_get0_EC_KEY(), EC_KEY_set_ex_data(), EVP_PKEY_get0_RSA()
or RSA_set_ex_data() fails, the function returns -EIO without freeing
the data struct or the data->key_blob that were allocated before.

Assisted-by: IBM Bob:2.0.0
Signed-off-by: Ingo Franzki <ifranzki@linux.ibm.com>
Reviewed-by: Finn Callies <fcallies@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2026-07-20 15:25:59 +02:00
Ingo Franzki
c006800bd5 libseckey: Fix length check of SPKI
Field spki_size is defined as size_t, so it can never be negative.
Before the check, sk_ep11_valid_ep11_blob() ensures that hdr->len is
never greater than key_token_length, so the subtraction can never
overflow the result, but it still can be zero.

Assisted-by: IBM Bob:2.0.0
Signed-off-by: Ingo Franzki <ifranzki@linux.ibm.com>
Reviewed-by: Finn Callies <fcallies@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2026-07-20 15:25:59 +02:00
Ingo Franzki
634095aa24 libseckey: Fix possible out of bounds read in sk_ep11_parse_der_tag()
For long-length field encoding, check if the number of length bytes
exceed the data size.

Assisted-by: IBM Bob:2.0.0
Signed-off-by: Ingo Franzki <ifranzki@linux.ibm.com>
Reviewed-by: Finn Callies <fcallies@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2026-07-20 15:25:58 +02:00
Ingo Franzki
b4f575abc4 libseckey: Fix possible endless loop for malformed CCA key tokens
A malformed CCA key token with a zero size section can cause an endless
loop in sk_cca_get_pka_section().

Assisted-by: IBM Bob:2.0.0
Signed-off-by: Ingo Franzki <ifranzki@linux.ibm.com>
Reviewed-by: Finn Callies <fcallies@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2026-07-20 15:25:58 +02:00
Ingo Franzki
6c729fba9e libseckey: Fix error checking in SK_OPENSSL_init() (OpenSSL 1.1.1 only)
Fix the NULL-checks to test the variable that was just assigned.

Assisted-by: IBM Bob:2.0.0
Signed-off-by: Ingo Franzki <ifranzki@linux.ibm.com>
Reviewed-by: Finn Callies <fcallies@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2026-07-20 15:25:58 +02:00
Ingo Franzki
71503c0e7d libseckey: Fix wrong RSA public exponent value 257 assignment
Array element key_value_structure.public_exponent[0] is written twice,
but key_value_structure.public_exponent[1] is not set, leading to a
wrong public exponent value (i.e. 0x01) in the key value structure.

CCA key generation will fail is this case.

Assisted-by: IBM Bob:2.0.0
Signed-off-by: Ingo Franzki <ifranzki@linux.ibm.com>
Reviewed-by: Finn Callies <fcallies@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2026-07-20 15:25:58 +02:00
Ingo Franzki
5c8bd68f6f libseckey: Fix and document size query call with NULL key_token
Functions SK_CCA_generate_ec_key_pair(), SK_CCA_generate_ec_key_pair(),
SK_EP11_generate_ec_key_pair() and SK_EP11_generate_rsa_key_pair() are
supposed to allow parameter key_token to be NULL. In this case the
required buffer size is returned in key_token_length only.

This size query does not work because the parameter checking rejects
the call with a NULL key_token returning -EINVAL.

Currently the size query is not used by any known caller, but lets
correct it anyway.

Assisted-by: IBM Bob:2.0.0
Signed-off-by: Ingo Franzki <ifranzki@linux.ibm.com>
Reviewed-by: Finn Callies <fcallies@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2026-07-20 15:25:58 +02:00
Ingo Franzki
927f901969 libekmfweb: Fix size check of the response party info
The party info contained in the response might be of an arbitrary size.
Check for an overflow before allocating the party info buffer for the
combined request and response party info data. With a very large
response party info size, the combined size might overflow (i.e. wrap
around), causing a too small buffer being allocated and the following
memcpy's would cause an out of bounds write.

Assisted-by: IBM Bob:2.0.0
Signed-off-by: Ingo Franzki <ifranzki@linux.ibm.com>
Reviewed-by: Finn Callies <fcallies@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2026-07-20 15:25:58 +02:00
Ingo Franzki
35cc35894b libekmfweb: Fix integer overflow errors in base64 encode/decode functions
On large data sizes the calculation may cause an overflow, if done with
int type. Do the calculation with size_t instead to avoid an overflow.

Assisted-by: IBM Bob:2.0.0
Signed-off-by: Ingo Franzki <ifranzki@linux.ibm.com>
Reviewed-by: Finn Callies <fcallies@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2026-07-20 15:25:58 +02:00
Ingo Franzki
dfaa1791b7 libekmfweb: Fix error checking typos
Fix the error checking to check the correct variables using the right
conditions.

Assisted-by: IBM Bob:2.0.0
Signed-off-by: Ingo Franzki <ifranzki@linux.ibm.com>
Reviewed-by: Finn Callies <fcallies@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2026-07-20 15:25:58 +02:00
Ingo Franzki
52158e73fa libekmfweb: Fix invalid free in error path of parse_json_web_token()
Don't attempt to free the local function parameter 'signature', but the
memory where *signature points to.

Assisted-by: IBM Bob:2.0.0
Signed-off-by: Ingo Franzki <ifranzki@linux.ibm.com>
Reviewed-by: Finn Callies <fcallies@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2026-07-20 15:25:58 +02:00
Ingo Franzki
cc6f1a6d7b libkmipclient: Fix kmip_parse_decimal_uint() to reject negative numbers
Function strtoull() silently accepts a leading '-' and returns a large
positive number (two's complement wrap). Reject this by checking for
the minus character.

Assisted-by: IBM Bob:2.0.0
Signed-off-by: Ingo Franzki <ifranzki@linux.ibm.com>
Reviewed-by: Finn Callies <fcallies@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2026-07-20 15:25:58 +02:00
Ingo Franzki
866324bcd2 libkmipclient: Fix possible NULL pointer dereference
Function gmtime() might return NULL which would then be passed to
strftime(). Return an error in case gmtime() return NULL.

Assisted-by: IBM Bob:2.0.0
Signed-off-by: Ingo Franzki <ifranzki@linux.ibm.com>
Reviewed-by: Finn Callies <fcallies@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2026-07-20 15:25:58 +02:00
Ingo Franzki
7fd2421a2c libkmipclient: Detect possible length overflow situations
A deeply nested or pathologically large KMIP node tree crafted by a
malicious server can wrap the length calculation around to a small
value. Detect this and return an error in this case.

Assisted-by: IBM Bob:2.0.0
Signed-off-by: Ingo Franzki <ifranzki@linux.ibm.com>
Reviewed-by: Finn Callies <fcallies@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2026-07-20 15:25:58 +02:00
Ingo Franzki
66581d57f3 libkmipclient: Fix error checking in kmip_node_clone()
Check the right pointer after strdup.

Assisted-by: IBM Bob:2.0.0
Signed-off-by: Ingo Franzki <ifranzki@linux.ibm.com>
Reviewed-by: Finn Callies <fcallies@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2026-07-20 15:25:58 +02:00
Ingo Franzki
de688f350f libkmipclient: Fix invalid return of -1 for an unsigned int
Functions kmip_node_get_structure_element_count() and
kmip_node_get_structure_element_by_tag_count() return an unsigned int,
but the error case returns -1. This leads to a wrap around actually
returning 0xffffffff which the caller might interpret as a very large
number of elements.

Return 0 in case of an error instead.

Also fix some callers to not unconditionally subtract -1 from the
returned value,  but return an error if the returned value is zero.
These callers check the preconditions upfront, so the error case
won't be hit anyway.

Assisted-by: IBM Bob:2.0.0
Signed-off-by: Ingo Franzki <ifranzki@linux.ibm.com>
Reviewed-by: Finn Callies <fcallies@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2026-07-20 15:25:58 +02:00
Ingo Franzki
958ccb46c5 libkmipclient: Fix parsing of optional 'name' element
Duplicate the 'name_obj' string, not the 'tag_obj' that was previously
used.

Assisted-by: IBM Bob:2.0.0
Signed-off-by: Ingo Franzki <ifranzki@linux.ibm.com>
Reviewed-by: Finn Callies <fcallies@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2026-07-20 15:25:58 +02:00
Ingo Franzki
3ea90a2258 libkmipclient: Fix integer overflow with large value length
Limit the value_len to not be larger than INT_MAX, because later on
BIO_read() is called with value_len and it uses the int type for
length parameter and return value.

This check also prevents the 'value_len + 1' from overflow, because
value_len is a size_t and this accepts larger values than int (even
on 32 bit architectures).

Assisted-by: IBM Bob:2.0.0
Signed-off-by: Ingo Franzki <ifranzki@linux.ibm.com>
Reviewed-by: Finn Callies <fcallies@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2026-07-20 15:25:58 +02:00
Ingo Franzki
7d56dde24a libkmipclient: Fix size underflow when padding bytes exceed remaining size
When decoding a value that is exactly at the boundary of the available size
(i.e., *size == value_len), and the value length is not a multiple of the
TTLV block length, then *size wraps to a huge value when the pad_len is
subtracted.

Assisted-by: IBM Bob:2.0.0
Signed-off-by: Ingo Franzki <ifranzki@linux.ibm.com>
Reviewed-by: Finn Callies <fcallies@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2026-07-20 15:25:58 +02:00
Ingo Franzki
c7d0d3c1b9 libkmipclient: Fix integer overflow in kmip_format_hex()
If length is >= 0x80000000 (2 GB), length * 2 wraps around to a small
value, calloc allocates a too small buffer, then the loop writes
length * 2 bytes into it causing a heap buffer overflow.

Fix this by using a size_t for size calculation, and also checking
the length before multiplication (needed on 32 bit platforms).

Assisted-by: IBM Bob:2.0.0
Signed-off-by: Ingo Franzki <ifranzki@linux.ibm.com>
Reviewed-by: Finn Callies <fcallies@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2026-07-20 15:25:58 +02:00
Ingo Franzki
d7648875cc libkmipclient: Fix signed-integer overflow in TTLV tag/length byte-shift
In C, operands of '<<' are subject to integer promotion. So the unsigned
char array elements are promoted to signed int and then shifted producing
signed overflow — undefined behaviour in C.

Fix this by first casting to uint32_t and then shift.

Assisted-by: IBM Bob:2.0.0
Signed-off-by: Ingo Franzki <ifranzki@linux.ibm.com>
Reviewed-by: Finn Callies <fcallies@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2026-07-20 15:25:58 +02:00
Ingo Franzki
5fdeaab3d0 libkmipclient: Limit the nesting level of KMIP STRUCTURE recursion
KMIP STRUCTURE elements can be nested, which causes a recursion of
functions kmip_decode_ttlv(), kmip_decode_xml(), and kmip_decode_json().
A malformed KMIP response may thus cause stack exhaustion.

Limit the KMIP STRUCTURE nesting level to 32 levels. This is more than
enough for currently defined KMIP responses. The practically used
nesting level is 8 or 9, dependent on the type of KMIP response.

Assisted-by: IBM Bob:2.0.0
Signed-off-by: Ingo Franzki <ifranzki@linux.ibm.com>
Reviewed-by: Finn Callies <fcallies@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2026-07-20 15:25:58 +02:00
Ingo Franzki
f0bf1985c3 libkmipclient: Protect from symlink-following attacks
When creating or writing files, make sure that the file is not a
sysmlink. Such files created by libkmipclient are typically stored inside
the zkey repository and the owner and mode of them are changed to.
allow read/write for the owner user and the 'zkeyadm' group. It
would allow a symlink-following attack if the file being created are
symlinks. Make sure to open such files with the 'O_NOFOLLOW' flag.

Assisted-by: IBM Bob:2.0.0
Signed-off-by: Ingo Franzki <ifranzki@linux.ibm.com>
Reviewed-by: Finn Callies <fcallies@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2026-07-20 15:25:58 +02:00
Ingo Franzki
a3a2d70603 libekmfweb: Protect from symlink-following attacks
When creating or writing files, make sure that the file is not a
sysmlink. Such files created by libekmfweb are typically stored inside
the zkey repository and the owner and mode of them are changed to.
allow read/write for the owner user and the 'zkeyadm' group. It
would allow a symlink-following attack if the file being created are
symlinks. Make sure to open such files with the 'O_NOFOLLOW' flag.

Assisted-by: IBM Bob:2.0.0
Signed-off-by: Ingo Franzki <ifranzki@linux.ibm.com>
Reviewed-by: Finn Callies <fcallies@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2026-07-20 15:25:58 +02:00
Ingo Franzki
226728731c libseckey: Protect from symlink-following attacks
When creating or writing files, make sure that the file is not a
sysmlink. Such files created by libseckey are typically stored inside
the zkey repository and the owner and mode of them are changed to.
allow read/write for the owner user and the 'zkeyadm' group. It
would allow a symlink-following attack if the file being created are
symlinks. Make sure to open such files with the 'O_NOFOLLOW' flag.

Assisted-by: IBM Bob:2.0.0
Signed-off-by: Ingo Franzki <ifranzki@linux.ibm.com>
Reviewed-by: Finn Callies <fcallies@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2026-07-20 15:25:58 +02:00
Ingo Franzki
94292dac54 zkey: Protect from symlink-following attacks
Files in the zkey repository can be created by any member of the
'zkeyadm' group as well as 'root'. Such files are owned by the creator
and the 'zkeyadm' group, and allow read and write for the owner user
and owner group.

When creating or writing files inside the zkey repository, make sure
that the file is not a sysmlink. That way, only files within the zkey
repository are set to be read/write for the owner user and members of
the 'zkeyadm' group. Make sure to open such files with the 'O_NOFOLLOW'
flag, and use 'lstat()' to check for files and directories.

Assisted-by: IBM Bob:2.0.0
Signed-off-by: Ingo Franzki <ifranzki@linux.ibm.com>
Reviewed-by: Finn Callies <fcallies@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2026-07-20 15:25:58 +02:00
Ingo Franzki
278f4f6fd5 zkey: Check values before constructing a command to execute
Check the values that are incorporated into commands that might get
executed to not contain any characters that could be used for
shell-injection attacks.

Assisted-by: IBM Bob:2.0.0
Signed-off-by: Ingo Franzki <ifranzki@linux.ibm.com>
Reviewed-by: Finn Callies <fcallies@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2026-07-20 15:25:58 +02:00
Finn Callies
17007ab121 95sel-ebc: Harden boot mount service
Pin the supported filesystem type to ext4 to prevent the risk of auto fs
parsing bugs. Additionally mount the boot partition with more
restrictive options.

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-07-20 15:25:58 +02:00
Finn Callies
4f9e5d7842 95sel-ebc: Reorder PAES enforcement service
Fix the sel-ebc-paes-enforce.service to run AFTER the sysroot.mount
which prevents a TOCTOU situation.

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-07-20 15:25:58 +02:00
Finn Callies
8a52693acc 95sel-ebc: Fix SICS existence check
Fix the sel-ebc-pvebc.service unit to execute the failure action when
the sics directory does not exist instead of getting skipped.

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-07-20 15:25:58 +02:00
Marc Hartmayer
a93146d976 pv: BinReqValues: Fix length validation in get()
Return an error when the provided request data is smaller than the
expected request length. The previous condition used the wrong
comparison, which could lead to an out-of-bounds slice and panic.

Add a regression test.

Fixes: 34bef977e8 ("rust/pv: User-data signing and verifying")
Assisted-by: IBM Bob:1.0.5
Signed-off-by: Marc Hartmayer <marc@linux.ibm.com>
Reviewed-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2026-07-20 15:25:58 +02:00
Marc Hartmayer
187437c6c8 pv: Defer CRL downloads until certificate validation succeeds
Perform certificate verification in two stages. First, verify the
certificate chain without CRL checks. Once the chain has been validated,
download the referenced CRLs and repeat the verification with CRL
checking enabled.

Fixes: c6f621d0dc ("rust: Add library for pv tools")
Signed-off-by: Marc Hartmayer <marc@linux.ibm.com>
Reviewed-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2026-07-20 15:25:58 +02:00
Marc Hartmayer
cdc787d92d pv: download_first_crl_from_x509: Increase the timeout to 10s
3-second timeout might be too short for slow networks therefore increase
it to 10s. In addition move this constant to a more prominent position
and add documentation.

Assisted-by: IBM Bob:1.0.5
Signed-off-by: Marc Hartmayer <marc@linux.ibm.com>
Reviewed-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2026-07-20 15:25:58 +02:00
Marc Hartmayer
5feee12827 pv: Limit CRL download size
Add a dedicated HKD verification error for CRL downloads that exceed the
maximum file size of 10 MiB and cover the max_filesize behavior in the
helper tests and add tests for it.

This commit adds a new development/test dependency as it verifies that
the correct messages are being logged.

Fixes: c6f621d0dc ("rust: Add library for pv tools")
Assisted-by: IBM Bob:1.0.5
Signed-off-by: Marc Hartmayer <marc@linux.ibm.com>
Reviewed-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2026-07-20 15:25:58 +02:00
Marc Hartmayer
cf70a27d76 pv: Restrict CRL downloads to HTTP(S) and limit redirects
Only allow CRL downloads over HTTP and HTTPS to avoid accessing
unexpected protocol handlers.

The Rust curl bindings do not expose support for configuring allowed
protocols or the maximum number of redirects [1][2][3]. Therefore,
redirect handling is implemented manually, validating each redirect
target and enforcing a maximum of five redirects.

The redirect limit also prevents infinite redirect loops.

[1] https://curl.se/libcurl/c/CURLOPT_PROTOCOLS_STR.html
[2] https://curl.se/libcurl/c/CURLOPT_REDIR_PROTOCOLS_STR.html
[3] https://curl.se/libcurl/c/CURLOPT_MAXREDIRS.html

Fixes: c6f621d0dc ("rust: Add library for pv tools")
Assisted-by: IBM Bob:1.0.5
Signed-off-by: Marc Hartmayer <marc@linux.ibm.com>
Reviewed-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2026-07-20 15:25:58 +02:00
Marc Hartmayer
5552b476bd pv: tests: Regenerate the fake certificates using http:// URL
Adapt the script to generate the test certificates to use the prefix
'http://' for the CRL distribution points as otherwise the test would
fail with an upcoming change. Regenerate the certificates and adapt the
test case dist_points' accordingly as the distribution point has
changed.

Note: The files der.crt and der.crl are skipped as that would result in
      binary changes which caues trouble with quilt. They are only used
      by some basic tests read_crls and read_crts anyway

Assisted-by: IBM Bob:1.0.5
Signed-off-by: Marc Hartmayer <marc@linux.ibm.com>
Acked-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2026-07-20 15:25:58 +02:00
Marc Hartmayer
7510b96238 pv: Fix script to generate the test certificates
Fix datetime.datetime.utcnow() deprecation and fix a TypeError.

  File "create_certs.py", line 16, in createEcKeyPair
    return ec.generate_private_key(curve=curve, backend=default_backend())
           ~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  TypeError: curve must be an EllipticCurve instance

Signed-off-by: Marc Hartmayer <marc@linux.ibm.com>
Reviewed-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2026-07-20 15:25:58 +02:00
Marc Hartmayer
778d9e436a pv: Add more tests for testing the CRL download functionality
Add more tests for testing the CRL download functionality.

Signed-off-by: Marc Hartmayer <marc@linux.ibm.com>
Acked-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2026-07-20 15:25:57 +02:00
Marc Hartmayer
692af4058c pv: Rewrite CRL download tests
Replace the existing mocks with a trait-based test infrastructure. This
allows testing download_first_crl_from_x509() functionally while
avoiding actual network access, resulting in more comprehensive and
realistic test coverage.

Assisted-by: IBM Bob:1.0.5
Signed-off-by: Marc Hartmayer <marc@linux.ibm.com>
Acked-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2026-07-20 15:25:57 +02:00
Marc Hartmayer
2565a4ca99 pv: verify: helper: Rename module test into tests
Rename the module to the more common test module name tests and remove a
superfluous comment.

Signed-off-by: Marc Hartmayer <marc@linux.ibm.com>
Reviewed-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2026-07-20 15:25:57 +02:00
Marc Hartmayer
6d5f4a99de pv: Refactor CRL download code for easier testing
Use a trait-based abstraction for CRL downloads so the download
implementation can be replaced more easily, for example in tests that
should avoid real network calls.

Assisted-by: IBM Bob:1.0.5
Signed-off-by: Marc Hartmayer <marc@linux.ibm.com>
Reviewed-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2026-07-20 15:25:57 +02:00
Marc Hartmayer
a9ed19285a pv: download_first_crl_from_x509: Fix comment
The comment mentions certificate but it's a certificate revocation
list (CRL). In addition, use 1200 bytes as vector capacity as the
comment says 1200.

Signed-off-by: Marc Hartmayer <marc@linux.ibm.com>
Reviewed-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2026-07-20 15:25:57 +02:00
Marc Hartmayer
4eb18bd6b7 pv: download_first_crl_from_x509: Log errors
Although most errors are ignored and the next CRL distribution point is
tried, log the errors as debug messages.

Signed-off-by: Marc Hartmayer <marc@linux.ibm.com>
Reviewed-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2026-07-20 15:25:57 +02:00
Finn Callies
cf45a6e108 pvics: Fail on non-existend BLE
Issue an error if the specified boot loader entry cannot be found.

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-07-20 15:25:57 +02:00
Marc Hartmayer
a879036e3f pv/error: Fix typo in BinArcbSeaSmall error message
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-07-03 12:05:06 +02:00
Marc Hartmayer
5340d11165 pv: test_utils: Add TEST-RAND generator for testing
Implement a test random number generator (RNG) using the TEST-RAND [1]
generator provided by OpenSSL. This allows writing tests for OpenSSL
functionality where OpenSSL internally uses RNGs, e.g. used for ML-KEM.

The implementation uses RAND_set0_public [2] to set the
thread-local RNG, which requires OpenSSL >= 3.1.

[1] https://docs.openssl.org/3.0/man7/EVP_RAND-TEST-RAND/
[2] https://docs.openssl.org/3.1/man3/RAND_set0_public

Assisted-by: IBM Bob:1.0.5
Acked-by: Steffen Eiden <seiden@linux.ibm.com>
Acked-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-07-03 12:05:06 +02:00
Sven Schnelle
162a041077 cmsfs-fuse: Fix strip_right() function
strip_right() strips spaces beginning at the end of the string moving
to the beginning. However, it doesn't check whether it's at beginning
of the array, and would continue reading if the string only contains
spaces.

Fix this by adding the necessary check.

Signed-off-by: Sven Schnelle <svens@linux.ibm.com>
Reviewed-by: Mete Durlu <meted@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2026-07-03 12:05:06 +02:00
Sven Schnelle
b38d5833fa hmcdrvfs: Fix hmcdrv_path_copy()
While iterating over the string, hmcdrv_path_copy() subtracts one byte
of the length to account for the NUL byte, but fails to account for
the '/' character it might add. Change the test to leave two bytes of
room.

Signed-off-by: Sven Schnelle <svens@linux.ibm.com>
Reviewed-by: Marc Hartmayer <marc@linux.ibm.com>
Reviewed-by: Mete Durlu <meted@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2026-07-03 12:05:06 +02:00
Eduard Shishkin
ee8897f9db zipl/src: Add ->set_location() private method of program component
Add/use ->set_location() private method of struct component_footer
instead of checking component types every time when operating with
program components.

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-07-03 12:05:06 +02:00
Eduard Shishkin
4f622325ef zipl/src: Fix add_component_file_range()
Fix add_component_file_range() to process components of all types
(not only of COMPONENT_TYPE_LOAD), similar to how it is going in
add_component_buffer().

Earlier components of COMPONENT_TYPE_SIGNATURE were processed only
by add_component_buffer(). Starting from 60bc1e3, signatures for
mirrors (i.e. base disks of #1 and larger ID) are processed also by
the function add_component_file_range(), which works incorrectly for
such type of components.

Fixes: 60bc1e38d0 ("zipl/src: Reuse data of buffer components in bootmap")
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-07-03 12:05:06 +02:00
Eduard Shishkin
f2902a1990 zipl/src: Fix alignment of componets location in bootmap file
Calculate the alignment value as maximum of physical block sizes of
disks participating in the mirrored setup. Earlier the alignment was
calculated as a physical block size of the first disk of a mirrored
setup.

Without this change, the assertion in disk_get_blocklist_from_file()
'assert(reg->offset % info->phy_block_size == 0)' is triggered in case
when blocksize of base disk #J > blocksize of base disk #I && (J>I).
To reproduce the problem, build a mixed mirrored setup with first SCSI
disk, second DASD disk and prepare it for IPL.

Fixes: 731f00202c ("zipl/src: Enable heterogeneous mirrors support")
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-07-03 12:05:06 +02:00
Eduard Shishkin
9882aabe44 zipl/src: Update format of zipl helpers output
Change the format of zipl helpers output to support hybrid mirrors
(i.e. composed of disks of different types and geometry).
The new format imposes an additional requirement that any key-value
pair "targetbase=X:Y" titles a new section with a set of target
parmeters for the base disk identified by that pair.

Without this change, zipl reports "Inconsistent script output".
To reproduce the problem, build a mixed mirrored setup with first
SCSI disk and second DASD disk, and prepare it for IPL.

Fixes: 731f00202c ("zipl/src: Enable heterogeneous mirrors support")
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-07-03 12:05:06 +02:00
Marc Hartmayer
d06d197522 pv: Fix unfulfilled_lint_expectations warning
The test cases uses the ml_kem functions, therefore disable the
directive for tests.

$ cargo test
...
 --> pv/src/openssl_extensions/ml_kem.rs:5:11
  |
5 | #![expect(unused)]
  |           ^^^^^^
  |
  = note: `#[warn(unfulfilled_lint_expectations)]` on by default

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-07-03 12:05:06 +02:00
Mikhail Zaslonko
3fd07bb7f2 zdump/df_vmdump: Add a documentation link to the header file
Add a link to 'CP Data Areas and Control Blocks' manual.

Signed-off-by: Mikhail Zaslonko <zaslonko@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2026-07-03 12:05:06 +02:00
Ingo Franzki
58ede126cc zkey: Retry PKEY_KBLOB2PROTK3 ioctl in case of EBUSY
After a master key change, it can happen that the PKEY_KBLOB2PROTK3 ioctl
returns EBUSY. This is a temporary situation and the operation will
succeed, once the firmware has completed some internal processing related
with the master key change. Delay 1 second and retry up to 10 times.

A similar retry loop was previously used for the AF_ALG-based handling,
but the retry logic was not included for the new ioctl-based handling.

Fixes: 7fffdcfe8c ("zkey: Remove the use of AF_ALG for calculating key verification patterns")
Signed-off-by: Ingo Franzki <ifranzki@linux.ibm.com>
Reviewed-by: Finn Callies <fcallies@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2026-07-03 12:05:03 +02:00
Volkan Unal
e4a03c6f71 dasdfmt: Check disk type before prompting for blocksize
Move check_disk() call before user input to fail fast
on unsupported or unavailable devices (FBA, read-only,
in-use, raw-track mode).

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-07-03 12:01:58 +02:00
Jan Höppner
c66495888d Prepare for next release
Signed-off by: Jan Höppner <hoeppner@linux.ibm.com>
2026-06-25 14:49:06 +02:00
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
Jan Höppner
77083b1cbb New release s390-tools-2.42.1
Signed-off by: Jan Höppner <hoeppner@linux.ibm.com>
2026-05-22 16:34:32 +02:00
Thomas Richter
21c2a04347 cpumf/pai: Remove unnecessary const parameter definition
With glibc 2.43 const-ness is preserved for standard library functions
and a compiler warning will be issued if this is violated.

pai parse_cpulist() receives user input via the parm function
parameter. The parameter is defined as 'const char *' and used as input
value for strchr(). The target pointer (cp) is defined as mutable
'char *' leading to violation of const correctness and this compiler
warning:

pai.c: In function ‘parse_cpulist’:
pai.c:907:20: warning: assignment discards ‘const’ qualifier from
			pointer target type [-Wdiscarded-qualifiers]
  907 |                 cp = strchr(parm, ':');

Since the user input is coming from optarg 'non-const char *', it is
unnecessary to have the function parameters carrying this data defined
as const.

Remove the unnecessary const definition in the function call chain to
fix the described issue.

Signed-off-by: Thomas Richter <tmricht@linux.ibm.com>
Suggested-by: Jan Hoeppner <hoeppner@linux.ibm.com>
Suggested-by: Juergen Christ <jchrist@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2026-05-22 15:59:36 +02:00
Thomas Richter
8f2d77c9d3 cpumf/pai: Improve -m XXX argument verification
Speed up the check of the option -m argument and improve
the error message. The argument number must be a power of 2 number
and this check is improved.
Furthermore split the error message and provide one message for
invalid characters and one error message for the argument
not being a power of 2 number.

Signed-off-by: Thomas Richter <tmricht@linux.ibm.com>
Suggested-by: Eduard Stefes <eddy@linux.ibm.com>
Suggested-by: Juergen Christ <jchrist@linux.ibm.com>
Tested-by: Jan Polensky <japo@linux.ibm.com>
Reviewed-by: Jan Polensky <japo@linux.ibm.com>
Reviewed-by: Juergen Christ <jchrist@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2026-05-22 15:59:36 +02:00
Steffen Eiden
b6a4d7a6c1 pvsecret: Update man files
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2026-05-22 15:50:15 +02:00
Steffen Eiden
f223069f0b pvsecret: Fix argument parsing
The latest commit 'promote' a few optional arguments to required.
Fix this breaking behaviour by making them optional again.

Fixes: 080a6678fb ("pvsecret: Add -i -o option variants")
Reported-by: Ingo Franzki <ifranzki@linux.ibm.com>
Reviewed-by: Jan Höppner <hoeppner@linux.ibm.com>
Tested-by: Ingo Franzki <ifranzki@linux.ibm.com>
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2026-05-22 15:50:15 +02:00
Steffen Eiden
19a5af8da9 rust/utils: Add functions to combine I/O arguments
Add functions to properly parse required and optional input/output
parameters that have to be combined into one.

Reviewed-by: Jan Höppner <hoeppner@linux.ibm.com>
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2026-05-22 15:49:45 +02:00
Steffen Eiden
03b73ab3f9 pvsecret: Update man and Readme
man and README got out of sync with --help over time.
Resync them.

Reviewed-by: Jan Höppner <hoeppner@linux.ibm.com>
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2026-05-21 13:20:06 +02:00
Steffen Eiden
080a6678fb pvsecret: Add -i -o option variants
All pvattest subcommands use the command line option -i <input> and -o
<output> to specify file input and output respectively. pvsecret however
uses mostly positional arguments for <input> and <output> exclusively,
e.g. pvattest check input.bin output.yaml

$ pvsecret add secret.bin

This provides an inconsistent user interface within the Secure
Execution tools and may confuse users.

Add the -i and -o option to the subcommands if applicable.
Input/output can then be specified like so:

$ pvsecret add -i secret.bin
$ pvsecret list -o list.yaml
$ pvsecret verify -i FILE -o out.yaml
$ pvsecret retrieve -i ID -o id.yaml

Reviewed-by: Jan Höppner <hoeppner@linux.ibm.com>
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2026-05-21 13:20:06 +02:00
Steffen Eiden
1d6f7d0bec pvattest: Update man and Readme
man and README got out of sync with --help over time.
Resync them.

Reviewed-by: Jan Höppner <hoeppner@linux.ibm.com>
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2026-05-21 13:20:06 +02:00
Steffen Eiden
a50d0485c9 pvattest: Add -i -o option variant for check
Most pvattest subcommands use the command line option -i <input> and -o
<output> to specify file input and output respectively. pvattest check
however only uses positional arguments for <input> and <output>, e.g.

$ pvattest check input.bin output.yaml

This provides an inconsistent user interface within the tool and may
confuse users.

Add the command -i and -o option to the check subcommand to bring it in
line with the rest of the tool.

$ pvattest check -i input.bin -o output.yaml

Reviewed-by: Jan Höppner <hoeppner@linux.ibm.com>
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2026-05-21 13:20:06 +02:00
Steffen Eiden
d2a6a771a5 pvattest: Show perform -i & -o option in help
Show -i & -o option and display them in the help output. Showing the
additional options to the user may provide a better experience. Users
may expect -i /-o options as other subcommands have them as well.

Reviewed-by: Jan Höppner <hoeppner@linux.ibm.com>
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2026-05-21 13:20:06 +02:00
Steffen Eiden
7568a0790f pv: Fix error description
The error texts printed a hardcoded .0 instead of the actual value.
Fix this by using the proper string format arguments.

Reviewed-by: Jan Höppner <hoeppner@linux.ibm.com>
Fixes: 61c5d7d431 ("rust/pv: Attestation generation and verification support")
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2026-05-21 13:20:06 +02:00
Finn Callies
ca0ee966b8 pvics: Add image prereqs in manpage
Specify the requirements for the base image in the pvics manpage.

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-05-21 13:20:06 +02:00
Finn Callies
36a7b2e6eb pvebc: Minor bash improvements
Add minor improvements to the scripts the dracut module calls.

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-05-21 13:20:06 +02:00
Finn Callies
851f63eb03 pvebc: Fix dependency for non EBC guests
For guests that do not make use of the EBC feature the boot should not
be impacted by this module. This requires removing the boot.mount unit
because it will unconditionally create a dependency on a unit that
conflicts with that idea. The downside is that mounting of the boot
partition has to be done manually.

Fixes: https://github.com/ibm-s390-linux/s390-tools/issues/202
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-05-21 13:20:06 +02:00
Finn Callies
3d679f61fc pvebc: Disable unit logging to /boot
The boot partition is not designed for log files. Disable logging of the
EBC units to /boot/sics/log.

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-05-21 13:20:06 +02:00
Finn Callies
9237c5b675 pvebc: Fix kernel module dependencies
Include the required kernel modules unconditionally when this module is
installed into an initramfs.
The new .conf file contains a list of kernel modules that are loaded by
the systemd-modules-load.service systemd unit.

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-05-21 13:20:06 +02:00
Eduard Shishkin
d1ab6be082 zipl/src: Don't modify job->data.dump and job->data.mvdump sequentially
Set job->data.dump.no_compress (job->data.mvdump.force) only after
making sure that the job is 'dump' ('mvdump') respectively. Othrewise,
the second assignment can potentially corrupt the value that was set by
the first assignment.

Reviewed-by: Mikhail Zaslonko <zaslonko@linux.ibm.com>
Signed-off-by: Eduard Shishkin <edward6@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2026-05-21 13:20:06 +02:00
Jan Höppner
d6c2bac99f Prepare for next release
Signed-off by: Jan Höppner <hoeppner@linux.ibm.com>
2026-04-30 17:07:01 +02:00
386 changed files with 19637 additions and 5718 deletions

8
.gitignore vendored
View File

@@ -102,9 +102,11 @@ lsstp/_lsstp
lsstp/lsstp.bash
mon_tools/mon_fsstatd
mon_tools/mon_procd
opticsmon/opticsmon
opticsmon/_opticsmon
opticsmon/opticsmon.bash
zpcimon/zpcimon
zpcimon/zpcimon.bash
zpcimon/_zpcimon
zpcimon/opticsmon.bash
zpcimon/_opticsmon
osasnmpd/osasnmpd
qetharp/qetharp
qethqoat/qethqoat

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,110 @@
Release history for s390-tools (MIT version)
--------------------------------------------
* __v2.xx.x (2023-xx-xx)__
For Linux kernel version: 6.x
Add new tools / libraries:
Changes of existing tools:
Bug Fixes:
* __v2.44.0 (2026-07-31)__
For Linux kernel version: 7.2
Changes of existing tools / libraries:
- create-sehdr: Enable quantum safe keys usage
- dbginfo.sh: Let zpcimon log both optical module and SMART data
- libutil/util_fmt: Add util_fmt_type_to_name()
- nvmemon: Skip SCLP on NVMes with non-IBM subsystem vendor ID
- opticsmon: zpcimon: Rename opticsmon to zpcimon
- pvattest: Enable quantum safe keys usage
- pvimg: Add '--flags <...>' and '--disable-flags <...>' option
- pvimg: Enable quantum safe keys usage
- pvsecret: Enable quantum safe keys usage
- pvverify: Enable quantum safe keys usage
- zmemtopo: Add CLI option to filter partitions by name
- zpcimon: Allow setting output format using --format CLI option
- zpcimon: Monitor for hotplug of NVMes to trigger SMART collection
Bug Fixes:
- opticsmon: Fix wrong size check for OPTICS_QSFP28_LOS_IMPLEMENTED_OFFSET
- opticsmon: Handle error return of ethtool_nl_connect()
- opticsmon: Fix error path free of struct optics in ethtool_nl_get_optics()
- opticsmon: Close epoll fd in monitor_wait_loop()
- opticsmon: Fix wrong JSON print for tx_fault
* __v2.43.1 (2026-07-22)__
For Linux kernel version: 7.1
Changes of existing tools:
- dasdfmt: Check disk type before prompting for blocksize
- iucvterm/ts-shell: Introduce config parameter to specify iucvconn binary
- iucvterm/ts-shell: Introduce pager config to replace env variable
- pv: Add root CA organization pinning to certificate verification
- zdump/ngdump: Use OpenSSL's SHA256 to compute digest of dump image
Bug Fixes:
- 95sel-ebc: Add udev-settle dependency
- 95sel-ebc: Add umount to boot service
- 95sel-ebc: Harden boot mount service
- fdasd: Fix memory leak in yes_no() function
- iucvterm/iucvtty: Ensure PTY and server fd's are closed at exec
- iucvterm/iucvtty: Prevent connection stalls when receiving TERM env
- iucvterm/iucvtty: Validate TERM environment name
- libkmipclient: Protect from symlink-following attacks
- libkmipclient: Various bugfixes
- libseckey: Protect from symlink-following attacks
- libseckey: Various bugfixes
- mon_procd: Fix possible static buffer overflow
- osasnmpd: Fix SNMP non-compliance
- zdev: Harden against invalid udev, import, firmware, or hypervisor data
- zipl-editenv: Check in-bootmap environment block validity
- zipl/boot: Check in-bootmap environment block syntax
- zipl: Check keyword duplications in BLS entries
- zipl: Fix bugs in mirror support
- zkey: Protect from symlink-following attacks
* __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
Changes of existing tools:
- cpumf/pai: Improve -m XXX argument verification
- pvattest: Add -i -o option variant for check
- pvattest: Show perform -i & -o option in help
- pvebc: Disable unit logging to /boot
- pvsecret: Add -i -o option variants
Bug Fixes:
- cpumf/pai: Remove unnecessary const parameter definition
- pv: Fix error description
- pvebc: Fix dependency for non EBC guests
- pvebc: Fix kernel module dependencies
- zipl: Don't modify job->data.dump and job->data.mvdump sequentially
* __v2.42.0 (2026-04-30)__
For Linux kernel version: 7.0

View File

@@ -15,7 +15,7 @@ TOOL_DIRS = zipl zdump fdasd dasdfmt dasdview tunedasd \
vmcp man mon_tools dasdinfo vmur cpuplugd ipl_tools \
ziomon iucvterm hyptop cmsfs-fuse qethqoat zfcpdump zdsfs cpumf \
systemd hmcdrvfs cpacfstats zdev dump2tar zkey netboot etc zpcictl \
lsstp hsci hsavmcore chreipl-fcp-mpath ap_tools rust opticsmon \
lsstp hsci hsavmcore chreipl-fcp-mpath ap_tools rust zpcimon \
zpwr zmemtopo
else

View File

@@ -316,7 +316,7 @@ build options:
| glibc-static | `HAVE_LIBC_STATIC` | zfcpdump |
| openssl | `HAVE_OPENSSL` | zkey, libekmfweb, libkmipclient, |
| | | zgetdump, rust/pvattest, rust/pvimg, |
| | | zgetdump/pvsecret, opticsmon |
| | | zgetdump/pvsecret, zpcimon |
| cryptsetup | `HAVE_CRYPTSETUP2` | zkey-cryptsetup |
| json-c | `HAVE_JSONC` | zkey-cryptsetup, libekmfweb, |
| | | libkmipclient |
@@ -325,8 +325,9 @@ build options:
| | | rust/pvattest, rust/pvsecret, |
| libxml2 | `HAVE_LIBXML2` | libkmipclient |
| systemd | `HAVE_SYSTEMD` | hsavmcore |
| libudev | `HAVE_LIBUDEV` | cpacfstatsd |
| libnl3 | `HAVE_LIBNL3` | opticsmon |
| libudev | `HAVE_LIBUDEV` | cpacfstatsd, zpcimon |
| libnl3 | `HAVE_LIBNL3` | zpcimon |
| libnvme | `HAVE_LIBNVME` | zpcimon |
This table lists additional build or install options:
@@ -374,11 +375,11 @@ the different tools are provided:
The runtime requirements are: openssl-libs (>= 1.1.1) and libcurl.
* opticsmon:
For building opticsmon OpenSSL and the Netlink Library Suite (libnl3) are
required.
Tip: you may skip the opticsmon build by adding
`HAVE_OPENSSL=0` or `HAVE_LIBNL3=0`
* zpcimon:
For building zpcimon OpenSSL, the Netlink Library Suite (libnl3),
and libnvme are required.
Tip: you may skip the zpcimon build by adding
`HAVE_OPENSSL=0` or `HAVE_LIBNL3=0` or `HAVE_LIBNVME=0`.
* osasnmpd:
You need at least the NET-SNMP 5.1.x package (net-snmp-devel.rpm)

View File

@@ -713,7 +713,7 @@ static int readdir_entry(struct fst_entry *fst, off_t addr)
*/
static inline int strip_right(const char *str, int size)
{
while (str[size - 1] == 0x20)
while (size > 0 && str[size - 1] == 0x20)
size--;
return size;
}

View File

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

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

@@ -894,7 +894,7 @@ static int parse_event_attr(char *cp)
}
/* Parse CPU list and event specifications */
static void parse_cpulist(int enr, const char *parm)
static void parse_cpulist(int enr, char *parm)
{
unsigned int evt_attr = 0;
cpu_set_t cmdlist, result;
@@ -948,33 +948,27 @@ static const struct util_prg prg = {
}
};
static void record_cpus_crypto(const char *cp)
static void record_cpus_crypto(char *cp)
{
if (!libcpumf_have_pai_crypto())
errx(EXIT_FAILURE, "No support for PAI crypto counters");
parse_cpulist(S390_EVT_PAI_CRYPTO, cp);
}
static void record_cpus_nnpa(const char *cp)
static void record_cpus_nnpa(char *cp)
{
if (!libcpumf_have_pai_nnpa())
errx(EXIT_FAILURE, "No support for PAI nnpa counters");
parse_cpulist(S390_EVT_PAI_NNPA, cp);
}
/* Mapsize must be power of 2 and larger than 4. Count bits in n and
* return 0 if input is invalid and has a bit count larger than one.
/* Mapsize must be power of 2 and larger than 4. Return true in this case.
*/
static unsigned long check_mapsize(unsigned long n)
static bool check_mapsize(unsigned long n)
{
int bit, cnt = 0;
if (n < 4)
return 0;
for (bit = 0; bit < __BITS_PER_LONG; ++bit)
if (n & (1 << bit))
++cnt;
return cnt == 1 ? n : 0;
return (n & (n - 1)) == 0;
}
static void setprio(const char *prio)
@@ -1040,11 +1034,11 @@ int main(int argc, char **argv)
errx(EXIT_FAILURE, "Invalid argument for -%c", ch);
break;
case 'm':
errno = 0;
mapsize = strtoul(optarg, &slash, 0);
mapsize = check_mapsize(mapsize);
if (errno || !mapsize || *slash)
if (!mapsize || *slash)
errx(EXIT_FAILURE, "Invalid argument for -%c", ch);
if (!check_mapsize(mapsize))
errx(EXIT_FAILURE, "No power of 2 number for -%c", ch);
break;
case 'n':
record_cpus_nnpa(optarg);

View File

@@ -1566,6 +1566,8 @@ int main(int argc, char *argv[])
g.ese = dasd_sys_ese(g.dev_node);
eval_format_mode();
check_disk();
/* Either let the user specify the blksize or get it from the kernel */
if (!g.blksize_specified) {
if (!(mode == FULL ||
@@ -1587,8 +1589,6 @@ int main(int argc, char *argv[])
error("VOLSER not found on device %s", g.dev_path);
}
check_disk();
if (check_param(str, ERR_LENGTH, &format_params) < 0)
error("%s", str);

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

@@ -387,20 +387,28 @@ static int yes_no(char *question_str)
ssize_t bytes_read;
char *answer;
size_t size;
int rc;
size = 0;
answer = NULL;
while (1) {
printf("%s (y/n): ", question_str);
bytes_read = getline(&answer, &size, stdin);
if (bytes_read < 0)
return -1;
if (answer[0] == 'y')
return 0;
if (answer[0] == 'n')
return 1;
if (bytes_read < 0) {
rc = -1;
break;
}
if (answer[0] == 'y') {
rc = 0;
break;
}
if (answer[0] == 'n') {
rc = 1;
break;
}
}
free(answer);
return rc;
}
static char *fdasd_partition_type(char *dsname)

View File

@@ -514,7 +514,7 @@ static int hmcdrv_path_copy(struct hmcdrv_fuse_file *fp, char *dest)
char *src = HMCDRV_FUSE_PATH(fp);
int len = 0;
while ((len < (HMCDRV_FUSE_MAXPATH - 1)) &&
while ((len < (HMCDRV_FUSE_MAXPATH - 2)) &&
(*src != '\0')) {
*dest = *src;

View File

@@ -17,6 +17,9 @@
#include "util_list.h"
#define PCI_BDF_LEN 13 /* DDDD:BB:dd.f\0 */
#define PCI_VENDOR_ID_IBM 0x1014
enum zpci_pft {
ZPCI_PFT_UNCLASSIFIED = 0x00,
@@ -65,6 +68,9 @@ struct zpci_dev {
bool uid_is_unique;
/* Configuration state 0 - Standby, 1 Configured */
bool conf;
/* Common PCI attributes */
uint16_t subsystem_vendor;
uint16_t subsystem_device;
/* Associated netdevs if any */
int num_netdevs;
@@ -96,4 +102,6 @@ operstate_t zpci_operstate_from_str(const char *oper_str);
struct zpci_dev *zpci_find_by_netdev(struct util_list *zpci_list, char *netdev_name,
struct zpci_netdev **netdev);
char *zpci_get_nvme_device_node(const char *pci_addr);
#endif /* LIB_ZPCI_PCI_LIST_H */

View File

@@ -21,9 +21,11 @@
#define SCLP_ERRNOTIFY_AQ_DECONF 1
#define SCLP_ERRNOTIFY_AQ_REPORT_ERR 2
#define SCLP_ERRNOTIFY_AQ_OPTICS_DATA 3
#define SCLP_ERRNOTIFY_AQ_NVME_SMART_DATA 4
#define SCLP_ERRNOTIFY_ID_ZPCICTL 0x4713
#define SCLP_ERRNOTIFY_ID_OPTICSMON 0x4714
#define SCLP_ERRNOTIFY_ID_NVMEMON 0x4715
#define SCLP_ERRNOTIFY_DATA_SIZE 4054

View File

@@ -157,6 +157,16 @@ void util_fmt_exit(void);
*/
bool util_fmt_name_to_type(const char *name, enum util_fmt_t *type);
/**
* util_fmt_type_to_name() - Get name of given format type identifier.
* @type: Format type identifier
*
* Get the name corresponding to the given format type identifier.
*
* Return: name of the format type
*/
const char *util_fmt_type_to_name(enum util_fmt_t type);
/**
* util_fmt_set_indent() - Set indentation parameters.
* @base : Base indentation level to apply to all output lines (default 0)

View File

@@ -19,6 +19,8 @@
#define MSEC_PER_SEC 1000L
#define SEC_PER_DAY (24 * 60 * 60)
/**
* Convert nanoseconds to microseconds
*

View File

@@ -72,6 +72,11 @@
#define __section(x) __attribute__((__section__(#x)))
#define __noinline __attribute__((__noinline__))
#define __big_endian
#ifdef __CHECKER__
# define __force __attribute__((force))
#else
# define __force
#endif
/* The Linux kernel (in stddef.h) and glibc (sys/cdefs.h) define
* __always_inline. Therefore undefine it first to allow the headers
* to be included first.
@@ -102,6 +107,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

@@ -14,14 +14,19 @@ use strict;
use warnings;
use File::Basename;
use Getopt::Long qw(:config no_ignore_case);
use Scalar::Util qw(tainted);
use Term::ReadLine;
use POSIX;
$ENV{'PERL5LIB'} = "";
$ENV{'PERL5OPT'} = "";
$ENV{'PERLLIB'} = "";
$ENV{'PERL_USE_UNSAFE_INC'} = "";
delete @ENV{qw(IFS CDPATH ENV BASH_ENV)};
$ENV{'PERL_RL'} = " o=0"; # use best avail. readline
$ENV{'PATH'} = "/bin:/sbin:/usr/bin:/usr/sbin";
$ENV{'LESSSECURE'} = 1; # let less run in "secure" mode
$ENV{'PAGER'} = $ENV{'PAGER'} || "/usr/bin/less";
my $ts_shell = fileparse($0, qr/\.[^.]+/);
$SIG{__WARN__} = sub { print STDERR "$ts_shell: $_[0]"; };
$SIG{__DIE__} = sub { print STDERR "$ts_shell: $_[0]"; exit 255; };
@@ -36,6 +41,7 @@ my %config = (
'auditdir' => "@var_path@/log/ts-shell",
'iucvconn' => "@iucvconn_path@/iucvconn",
'prompt' => getpwuid($>) . '@'."$ts_shell> ",
'pager' => "/usr/bin/less",
# runtime options
'rl' => undef, # terminal readline (rl)
'user' => getpwuid($>), # user name
@@ -65,6 +71,7 @@ sub cmd_list(\%);
sub rl_cmd_completion($$$);
sub list_regex_match($);
sub updateConfiguration(\%$);
sub checkConfiguration(\%);
sub loadAuthorization(\%);
sub readFile($$);
sub log_debug($);
@@ -103,6 +110,7 @@ sub main()
"$config{conffile} failed: $!";
exit 3;
}
checkConfiguration(%config);
# load list of systems the ts-shell is allowed to use; and
# hash the systems names in uppercase
@@ -185,11 +193,50 @@ sub updateConfiguration(\%$)
{
my ($cfg, $line) = @_;
my ($option, $value) = split /\s*=\s*/, $line;
$cfg->{sysfile} = $value if $option =~ /^ts-systems$/;
$cfg->{authfile} = $value if $option =~ /^ts-authorization$/;
$cfg->{auditdir} = $value if $option =~ /^transcript-directory$/;
$cfg->{auditfile} = $value if $option =~ /^transcript-systems$/;
$cfg->{pager} = $value if $option =~ /^pager$/;
$cfg->{iucvconn} = $value if $option =~ /^iucvconn$/;
}
# checkConfiguration() - Validate and untaint configuration values
#
# Called once after readFile() has finished populating %cfg from
# ts-shell.conf. Validates each security-sensitive path and untaints
# it via a regex capture so Perl taint mode permits later filesystem use.
#
# %cfg: Hash reference to terminal server configuration
#
sub checkConfiguration(\%)
{
my $cfg = shift;
# Validate pager to untaint for later use
unless ($cfg->{pager} =~ m#^(/[\w./-]+)$#) {
log_error "Invalid pager configuration";
exit 6;
}
$cfg->{pager} = $1;
# Validate iucvconn path to untaint for later use
unless ($cfg->{iucvconn} =~ m#^(/[\w./-]+)$#) {
log_error "Invalid iucvconn configuration";
exit 7;
}
$cfg->{iucvconn} = $1;
# Validate transcript-directory to untaint for later use
unless ($cfg->{auditdir} =~ m#^(/[\w./-]+)$#) {
log_error "Invalid transcript-directory configuration";
exit 8;
}
$cfg->{auditdir} = $1;
# Untaint user name (tainted by getpwuid) for use in filesystem paths
($cfg->{user}) = ($cfg->{user} =~ /^([\w.-]+)$/);
}
# loadAuthorization() - Load system authorizations from file
@@ -205,7 +252,7 @@ sub loadAuthorization(\%)
{
my $cfg = shift();
return 0 unless open(AUTH, "<$cfg->{authfile}");
return 0 unless open(AUTH, "<", $cfg->{authfile});
AUTH_ENT: while (<AUTH>) {
chomp;
@@ -217,13 +264,13 @@ sub loadAuthorization(\%)
my ($key, $val) = split /\s*=\s*/;
# read authorization configuration for user and its groups
if ($key =~ /^$cfg->{user}$/) {
if ($key =~ /^\Q$cfg->{user}\E$/) {
$authorized = 1;
log_debug "Found user: $key";
} elsif ($key =~ /^@(\S+)$/) {
my $group = $1;
$authorized = 1 if grep {/^${group}$/} @{$cfg->{groups}};
$authorized = 1 if grep {/^\Q${group}\E$/} @{$cfg->{groups}};
log_debug "Found group: $key" if $authorized;
}
@@ -303,7 +350,7 @@ sub readFile($$)
{
my ($file, $sub) = @_;
return 0 unless open(CONF, "<$file");
return 0 unless open(CONF, "<", "$file");
while (<CONF>) {
chomp;
next if /^#/; # ignore comments
@@ -392,7 +439,15 @@ sub get_auditlog_file($$$)
}
}
$$filepath = "$cfg->{auditdir}/$cfg->{user}/" . uc $guest . "_";
# validate and sanitize guest directory
$guest = uc $guest;
unless ($guest =~ /^([A-Z0-9@#\$_-]{1,8})$/) {
log_error "Creating session file path failed for guest: $guest";
return 0; # return error
}
# construct the file path
$$filepath = "$cfg->{auditdir}/$cfg->{user}/" . $guest . "_";
$$filepath .= strftime '%F-%H%M%S', localtime;
log_debug "Session transcript file: $$filepath";
@@ -416,7 +471,7 @@ sub cmd_connect($\%)
}
my ($guest, $srv) = split /\s+/, $params;
$service = $srv if $srv && $srv =~ /\w{1,8}/;
$service = $srv if $srv && $srv =~ /^\w{1,8}$/;
# check authorization:
# The auth_func contract is to return a code, that must be one of:
@@ -482,7 +537,7 @@ sub cmd_service($\%)
return;
}
if ($new =~ /\w{1,8}/) {
if ($new =~ /^\w{1,8}$/) {
$cfg->{service} = $new;
# push new service for cmd completion
unless (grep /$new/, @{$cfg->{services}}) {
@@ -545,7 +600,7 @@ sub pager($)
{
my $eval = shift();
unless (open(PAGER, "|$ENV{'PAGER'}")) {
unless (open(PAGER, '|-', "$config{'pager'}")) {
eval &$eval;
return;
}

View File

@@ -1,9 +1,8 @@
.\" SPDX-License-Identifier: MIT
.\"
.\" ts-shell.1
.\"
.\"
.\" Copyright IBM Corp. 2008, 2017
.\" s390-tools is free software; you can redistribute it and/or modify
.\" it under the terms of the MIT license. See LICENSE for details.
.\" Copyright IBM Corp.
.\" ----------------------------------------------------------------------
.TH "ts-shell" "1" "March 2009" "s390-tools" "Terminal Server over IUCV"
.
@@ -269,6 +268,10 @@ session transcripts are saved.
See section "Configure terminal session transcripts" for details.
.
.TP
.BR pager " = " \fI/usr/bin/less\fP
Specifies the path to a pager program to paginate output of ts-shell commands.
.
.RE
.
.
@@ -542,14 +545,6 @@ Directory for saving terminal session transcripts.
.
.SH "ENVIRONMENT"
.TP
.B PAGER
The \fBPAGER\fP environment variable designates a program used as pager for the
\fBlist\fP command of the terminal server shell.
If \fBPAGER\fP is not set or empty,
.BR less (1)
is used.
.
.TP
.B LESSSECURE
\fBts-shell\fP sets this variable to run
.BR less (1)

View File

@@ -2,6 +2,12 @@
#
# See ts-shell(1) manual page for file format syntax.
# Generic settings
# ----------------
# pager
# binary to paginate output (default: less)
#pager = /usr/bin/less
# System and authorization settings
# ---------------------------------

View File

@@ -21,6 +21,9 @@
/* Message buffer: message header + 4096 bytes of data */
#define MSG_BUFFER_SIZE (MSG_DATA_OFFSET + (4096))
/* Message data sizes */
#define MAX_TERM_SIZE 256
/* Error macros */
#define print_error(s) program_error(PRG_COMPONENT, (s))
#define iucvtty_error(m) \
@@ -55,13 +58,14 @@ extern int iucvtty_read_data(int, struct iucvtty_msg *, size_t);
extern int iucvtty_read_msg(int, struct iucvtty_msg *, size_t, size_t *);
extern int iucvtty_write_msg(int, struct iucvtty_msg *);
extern void iucvtty_skip_msg_residual(int, size_t *);
extern void iucvtty_skip_msg_chunk(int, size_t *);
extern ssize_t __write(int, const void*, size_t);
extern int strmatch(const char *, const char *);
extern int is_regex_valid(const char *);
extern int is_client_allowed(const char *, const struct iucvterm_cfg *);
extern int is_term_valid(const char *term, size_t len);
extern void userid_cpy(char [9], const char [8]);
extern void iucv_msg_error(const char *, uint32_t);

View File

@@ -86,7 +86,7 @@ static inline struct iucvtty_msg *msg_alloc(uint8_t type, uint16_t size)
{
struct iucvtty_msg *m;
m = malloc(size + MSG_DATA_OFFSET);
m = calloc(1, size + MSG_DATA_OFFSET);
if (m != NULL) {
m->version = MSG_VERSION;
m->type = type;

View File

@@ -8,6 +8,8 @@
* s390-tools is free software; you can redistribute it and/or modify
* it under the terms of the MIT license. See LICENSE for details.
*/
#include <assert.h>
#include <ctype.h>
#include <errno.h>
#include <regex.h>
#include <stdio.h>
@@ -36,7 +38,7 @@
* @len: Buffer length
*
* Write @len number of bytes from the buffer @buf to the file
* descriptor @fd. The routines handles EINTR and partially writes.
* descriptor @fd. The routine handles EINTR and partial writes.
* Returns the error code from the underlying write(2) syscall.
*/
ssize_t __write(int fd, const void *buf, size_t len)
@@ -55,6 +57,33 @@ ssize_t __write(int fd, const void *buf, size_t len)
return written;
}
/**
* __read() - Read data
* @fd: File descriptor
* @buf: Pointer to data buffer
* @len: Buffer size
*
* Read up to @len number of bytes from file descriptor @fd and stores them
* in the buffer to which @buf points.
* The routine handles EINTR and partial reads and returns the error code
* from the underlying read(2) syscall.
*/
ssize_t __read(int fd, void *buf, size_t len)
{
ssize_t rc;
size_t count = 0;
while (count < len) {
rc = read(fd, buf + count, len - count);
if (rc == -1 && errno == EINTR)
continue;
if (rc <= 0)
return rc;
count += rc;
}
return count;
}
#ifdef __DEBUG__
static void __dump_msg(int fd, const struct iucvtty_msg *m, char dir)
{
@@ -116,14 +145,16 @@ int iucvtty_tx_termenv(int dest, char *dflt)
len = 0;
if (term != NULL)
len = 1 + strlen(term);
len = MIN(1 + strlen(term), (size_t)MAX_TERM_SIZE);
/* Note: The server console tool waits for terminal environment
* information: the message is sent even if it is empty */
msg = msg_alloc(MSG_TYPE_TERMENV, len);
if (msg == NULL)
return -1;
msg_cpy_from(msg, term, len);
msg->datalen = len;
if (msg->datalen)
snprintf((char *)msg->data, msg->datalen, "%s", term);
rc = iucvtty_write_msg(dest, msg);
msg_free(msg);
@@ -146,12 +177,14 @@ int iucvtty_rx_termenv(int fd, void *buf, size_t len)
return -1;
skip = 0;
rc = iucvtty_read_msg(fd, msg, msg_size(msg), &skip);
iucvtty_skip_msg_residual(fd, &skip);
iucvtty_skip_msg_chunk(fd, &skip);
if (!rc) {
if (msg->datalen == 0)
if (msg->datalen == 0) {
memset(buf, 0, MIN(1u, len));
else
} else {
msg_cpy_to(msg, buf, len);
memset(buf + MIN(msg->datalen, len - 1), 0, 1);
}
}
msg_free(msg);
return rc;
@@ -262,93 +295,175 @@ int iucvtty_copy_data(int dest, struct iucvtty_msg *msg)
}
/**
* iucvtty_skip_msg_residual() - Skip (receive & forget) count number of bytes
* iucvtty_skip_msg_chunk() - Skip (receive & forget) count number of bytes
* @fd: File descriptor
* @residual: Residual of an iucv tty message received by iucvtty_read_msg()
* @chunk: Remaining chunk from a previous iucvtty_read_msg() call
*
* See iucvtty_read_msg() for an explanation when to use this routine.
* Note: The @residual parameter shall not be NULL.
* Note: The @chunk parameter must not be NULL.
*/
void iucvtty_skip_msg_residual(int fd, size_t *residual)
void iucvtty_skip_msg_chunk(int fd, size_t *chunk)
{
char b;
size_t i;
int r;
char buf[256];
if (*residual <= 0)
if (*chunk <= 0)
return;
for (i = 0; i < *residual; i++)
if (read(fd, &b, 1) <= 0)
while (*chunk) {
r = __read(fd, buf, MIN(sizeof(buf), *chunk));
if (r <= 0)
break;
*residual = 0;
*chunk -= r;
}
*chunk = 0;
}
/**
* iucvtty_read_msg_chunk() - Read IUCV message chunk
* @fd: File descriptor to read from
* @msg: Pointer to IUCV message buffer
* @msglen: IUCV message buffer size (including message header)
* @chunk: Size of chunk data to read
*
* Stores new message data and calculates next chunk size if not all data
* could be read. Returns zero on success, non-zero otherwise.
*/
static int iucvtty_read_msg_chunk(int fd, struct iucvtty_msg *msg,
size_t msglen, size_t *chunk)
{
ssize_t r;
size_t datalen;
/* Calculate message data length to read */
datalen = MIN(msglen - MSG_DATA_OFFSET, *chunk);
for (;;) {
r = read(fd, msg->data, datalen);
if (r == -1 && errno == EINTR)
continue;
if (r <= 0)
return -1;
break;
}
/* Update message and re-calculate next chunk */
msg->datalen = r;
*chunk -= r;
return 0;
}
/**
* validate_msg() - Perform sanity checks on a received message
* @msg: IUCV message buffer
*
* Returns zero if the message is valid; otherwise non-zero
*/
static int validate_msg(struct iucvtty_msg *msg)
{
switch (msg->type) {
case MSG_TYPE_DATA:
/* The datalen ranges from 0 to its maximum of 0xffff
* which is the maximum of the type definition of uint16_t.
*
* Consider the datalen value as valid.
*/
break;
case MSG_TYPE_ERROR:
if (msg->datalen != sizeof(uint32_t))
return 1;
break;
case MSG_TYPE_TERMENV:
if (msg->datalen > MAX_TERM_SIZE)
return 1;
break;
case MSG_TYPE_TERMIOS: /* ignored */
break;
case MSG_TYPE_WINSIZE:
if (msg->datalen != sizeof(struct winsize))
return 1;
break;
default:
/* Invalid message type */
return 1;
}
return 0;
}
/**
* iucvtty_read_msg() - Read/Receive an IUCV message
* @fd: File descriptor to read from
* @msg: Pointer to IUCV message buffer
* @len: IUCV message data len
* @residual: Status to be used by next call
* @msglen: IUCV message buffer size (MUST BE > MSG_DATA_OFFSET)
* @chunk: Size of remaining data; must be passed on next call
*
* The function reads up to @len bytes from file descriptor @fd.
* If the received message is larger than @len bytes, the @residual value
* is set to the number of bytes remaining.
* The function shall then be re-called to create a new message and receive
* the next chunk of size @residual; or the remaining characters must be
* skipped using the iucvtty_skip_msg() routine.
* Note: The @len parameter shall be greater than MSG_DATA_OFFSET.
* The @residual parameter shall not be NULL.
* The function reads up to @len bytes from file descriptor @fd. If the
* received message is larger than @len bytes, the @chunk value is set to
* the number of remaining bytes. The function shall then be re-called to
* create a new message and receive the outstanding data. Alternatively,
* call iucvtty_skip_msg_chunk() to discard remaining data.
*
* NOTE: The @len parameter must be greater than MSG_DATA_OFFSET!
* The @chunk parameter must not be NULL.
*/
int iucvtty_read_msg(int fd, struct iucvtty_msg *msg,
size_t len, size_t *residual)
size_t msglen, size_t *chunk)
{
int rc;
ssize_t r; /* number of bytes read from fd */
if (*residual)
len = MIN(len - MSG_DATA_OFFSET, *residual);
/* Ensure message buffer size can hold data and can be processed */
assert(msglen > MSG_DATA_OFFSET);
assert(chunk != NULL);
while (1) {
if (*residual) {
r = read(fd, msg->data, len);
if (r > 0)
msg->datalen = r;
} else
r = read(fd, msg, len);
/* Read pending message data */
if (*chunk)
return iucvtty_read_msg_chunk(fd, msg, msglen, chunk);
if (r == -1 && errno == EINTR)
continue;
if (r <= 0) {
rc = -1;
goto out_read_error;
}
break; /* exit loop for a successful read */
/* Read message header of new message */
r = __read(fd, msg, MSG_DATA_OFFSET);
if (r <= 0) {
rc = -1;
goto out_read_error;
}
#ifdef __DEBUG__
if (!*residual)
__dump_msg(fd, msg, 'R');
__dump_msg(fd, msg, 'R');
#endif
/* (re)calculate next chunk */
if (*residual)
*residual -= msg->datalen;
else
if (msg->datalen > (r - MSG_DATA_OFFSET)) {
/* calculate pending msg data and update datalen */
*residual = msg->datalen - (r - MSG_DATA_OFFSET);
msg->datalen = r - MSG_DATA_OFFSET;
}
/* check for a sane message */
/* Check message header */
if (msg->version != MSG_VERSION) {
fprintf(stderr, _("%s: %s\n"),
PRG_COMPONENT, _("The version of the received data "
"message is not supported\n"));
"message is not supported"));
fprintf(stderr, "MSG: msg->version=%u msg->type=%u msg->datalen=%u\n",
msg->version, msg->type, msg->datalen);
rc = -2;
goto out_read_error;
}
rc = 0;
/* Check for an empty message */
if (!msg->datalen)
return 0;
/* Check message type and data length */
if (validate_msg(msg)) {
fprintf(stderr, _("%s: %s\n"),
PRG_COMPONENT, _("The received message is invalid"));
fprintf(stderr, "MSG: msg->version=%u msg->type=%u msg->datalen=%u\n",
msg->version, msg->type, msg->datalen);
rc = -3;
goto out_read_error;
}
/* Process the new message as a one chunk */
*chunk = msg->datalen;
msg->datalen = 0;
rc = iucvtty_read_msg_chunk(fd, msg, msglen, chunk);
out_read_error:
return rc;
}
@@ -501,6 +616,31 @@ int is_client_allowed(const char *client, const struct iucvterm_cfg *cfg)
return strmatch(client, cfg->client_re);
}
/**
* is_term_valid() - Validate TERM environment value
* @term: Terminal environment name to validate
* @len: Maximum number of characters to validate
*/
int is_term_valid(const char *term, size_t len)
{
const char *c;
if (term == NULL || *term == '\0')
return 0;
for (c = term; *c != '\0'; c++) {
if ((size_t)(c - term) >= len)
return 0;
if (!isalnum((unsigned char)*c) &&
*c != '.' &&
*c != '_' &&
*c != '-')
return 0;
}
return 1;
}
/**
* userid_cpy() - Copy z/VM user ID and skip trailing spaces.
* @dest: Destination buffer

View File

@@ -17,6 +17,7 @@
#include <string.h>
#include <sys/select.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <syslog.h>
@@ -32,7 +33,6 @@
#define SYSLOG_IDENT "iucvtty"
#define PRG_COMPONENT SYSLOG_IDENT
#define TERM_BUFSIZE 256
#define TERM_DEFAULT "linux"
@@ -63,12 +63,46 @@ static int exec_login_prog(char *cmd[])
return rc;
}
/**
* receive_term_env() - receive terminal environment with timeout
* @term: Terminal environment variable
* @len: Maximum characters for the terminal environment variable
*/
static void receive_term_env(int client, char *term, size_t len)
{
struct timeval tv = { .tv_sec = 0, .tv_usec = 0};
/* Set a temporary timeout of 10s to time out a stalling
* client connection. Use SO_RCVTIMEO to set the timeout.
* Using those hard coded values directly is safe as
* SO_RCVTIMEO is not being used in other parts of iucvtty.
*/
tv.tv_sec = 10;
setsockopt(client, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
memset(term, 0, len);
if (iucvtty_rx_termenv(client, term, len))
snprintf(term, len, "%s", TERM_DEFAULT);
tv.tv_sec = 0;
setsockopt(client, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
if (!is_term_valid(term, len)) {
print_error("Ignoring received TERM env due to invalid character(s)");
snprintf(term, len, "%s", TERM_DEFAULT);
}
}
/**
* iucvtty_worker() - Handle an incoming client connection
* @client: Client file descriptor
* @master: PTY master file descriptor
* @slave: PTY slave file descriptor
* @cfg: IUCV TTY configuration structure.
*
* Unlike the HVC IUCV terminal device driver, iucvtty expects to
* receive the TERM environment first. This is necessary to correctly
* set up the login program.
*/
static int iucvtty_worker(int client, int master, int slave,
const struct iucvterm_cfg *cfg)
@@ -78,15 +112,14 @@ static int iucvtty_worker(int client, int master, int slave,
pid_t child;
fd_set set;
size_t chunk;
char term_env[TERM_BUFSIZE];
char term_env[MAX_TERM_SIZE];
/* flush pending terminal data */
tcflush(master, TCIOFLUSH);
/* read terminal parameters from client */
if (iucvtty_rx_termenv(client, term_env, TERM_BUFSIZE))
sprintf(term_env, TERM_DEFAULT);
/* read and validate terminal parameters from client */
receive_term_env(client, term_env, sizeof(term_env));
/* start login program */
child = fork();
@@ -113,6 +146,9 @@ static int iucvtty_worker(int client, int master, int slave,
exit(3); /* we only reach here if exec has failed */
}
/* Close slave fd to properly handle SIGHUP for the child process */
close(slave);
/* setup buffers */
msg = malloc(MSG_BUFFER_SIZE);
if (msg == NULL) {
@@ -226,8 +262,13 @@ int main(int argc, char *argv[])
}
/* set close-on-exec for file descriptors */
fcntl(master, F_SETFD, FD_CLOEXEC);
fcntl(server, F_SETFD, FD_CLOEXEC);
if (fcntl(master, F_SETFD, FD_CLOEXEC) ||
fcntl(server, F_SETFD, FD_CLOEXEC)) {
print_error("Setting file controls failed");
close(server);
rc = 1;
goto exit_on_error;
}
/* syslog */
openlog(SYSLOG_IDENT, LOG_PID, LOG_AUTHPRIV);
@@ -240,6 +281,7 @@ int main(int argc, char *argv[])
client = accept(server, (struct sockaddr *) &caddr, &len);
if (client == -1) {
print_error("An incoming connection could not be accepted");
close(server);
rc = 2;
goto exit_on_error;
}
@@ -256,10 +298,14 @@ int main(int argc, char *argv[])
} else { /* client is allowed to connect */
syslog(LOG_INFO, "Accepted client connection from %s",
client_host);
/* set close-on-exec for client socket */
fcntl(client, F_SETFD, FD_CLOEXEC);
/* close server socket */
close(server);
/* set close-on-exec for client socket */
if (fcntl(client, F_SETFD, FD_CLOEXEC)) {
print_error("Setting file controls failed");
rc = 4;
goto exit_on_error;
}
/* setup signal handler to notify shutdown signal */
sigemptyset(&sigact.sa_mask);

View File

@@ -13,7 +13,6 @@
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <termio.h>
#include <unistd.h>
#include "iucvterm/functions.h"
@@ -114,7 +113,7 @@ static int __testReadWriteMessage_nochunks(int sv[2])
if (iucvtty_read_msg(sv[1], msg[1], 6 + MSG_DATA_OFFSET, &residual))
return 2;
assert(0 == memcmp(msg[1]->data, data, msg[1]->datalen));
iucvtty_skip_msg_residual(sv[1], &residual);
iucvtty_skip_msg_chunk(sv[1], &residual);
assert(0 == residual);
/*printf("datalen=%u data='%s'\n",
msg[1]->datalen, msg[1]->data);*/
@@ -144,7 +143,7 @@ static int __testReadWriteMessage(int sv[2])
/* read msg */
if (iucvtty_read_msg(sv[1], msg[1], BUF_SIZE, &chunk))
return 2;
iucvtty_skip_msg_residual(sv[1], &chunk);
iucvtty_skip_msg_chunk(sv[1], &chunk);
/* compare msg */
if (__msgcmp(msg[0], msg[1]))

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

@@ -233,7 +233,7 @@ static int _ekmf_extract_pubkey(const char *cert, const char *pub_key_pem,
goto out;
}
fp = fopen(pub_key_pem, "w");
fp = fopen_nofollow(pub_key_pem, "w");
if (fp == NULL) {
rc = -errno;
pr_verbose(verbose, "File '%s': %s", pub_key_pem,
@@ -458,7 +458,7 @@ retry:
out);
if (server_cert_pem != NULL) {
fp = fopen(server_cert_pem, "w");
fp = fopen_nofollow(server_cert_pem, "w");
if (fp == NULL) {
rc = -errno;
pr_verbose(verbose, "File '%s': %s", server_cert_pem,
@@ -495,7 +495,7 @@ retry:
*/
if (ci->num_of_certs > 1 && ca_bundle_pem != NULL &&
do_verify == 0) {
fp = fopen(ca_bundle_pem, "w");
fp = fopen_nofollow(ca_bundle_pem, "w");
if (fp == NULL) {
rc = -errno;
pr_verbose(verbose, "File '%s': %s",
@@ -1415,7 +1415,7 @@ int ekmf_login(const struct ekmf_config *config, CURL **curl_handle,
while (*tok == ' ')
tok++;
fp = fopen(config->login_token, "w");
fp = fopen_nofollow(config->login_token, "w");
if (fp == NULL) {
rc = -errno;
pr_verbose(verbose, "Failed to open file %s: '%s'",
@@ -1739,7 +1739,7 @@ int ekmf_get_settings(const struct ekmf_config *config, CURL **curl_handle,
if (xts_key2_template != NULL) {
*xts_key2_template = _ekmf_find_setting(response_obj,
SETTING_ID_XTS_KEY2_TEMPLATE, verbose);
if (*identity_template == NULL) {
if (*xts_key2_template == NULL) {
if (error_msg != NULL) {
if (asprintf(error_msg, "The EKMF Web setting "
"'XTS Key Template Name (Key 2)' "
@@ -1882,8 +1882,9 @@ int ekmf_check_feature(const struct ekmf_config *config, CURL **curl_handle,
pr_verbose(verbose, "Feature '%s' is not installed",
FEATURE_ID_PERVASIVE_ENCRYPTION);
rc = -ENOTSUP;
if (asprintf(error_msg, "EKMF Web feature "
"'Pervasive Encryption' is not installed.")) {
if (error_msg != NULL &&
asprintf(error_msg, "EKMF Web feature "
"'Pervasive Encryption' is not installed.") < 0) {
pr_verbose(verbose, "asprintf failed");
rc = -ENOMEM;
}
@@ -1925,7 +1926,7 @@ static int _ekmf_build_party_info(const char *key_uuid, const char *timestamp,
if (*party_info_length < (size_t)EVP_MD_size(md)) {
pr_verbose(verbose, "Party info buffer is too small");
return -ERANGE;
rc = -ERANGE;
goto out;
}
@@ -2219,6 +2220,11 @@ static int _ekmf_import_key(unsigned char *req_sess_key,
unsigned char *party_info = NULL;
int rc;
if (resp_party_info_length > SIZE_MAX - req_party_info_length) {
rc = -EINVAL;
goto out;
}
party_info_length = req_party_info_length + resp_party_info_length;
party_info = malloc(party_info_length);
if (party_info == NULL) {
@@ -4547,8 +4553,9 @@ out:
*/
static char *_ekmf_base64_encode(const unsigned char *data, size_t data_size)
{
int outlen, len;
size_t outlen;
char *out;
int len;
outlen = (data_size / 3) * 4;
if (data_size % 3 > 0)
@@ -4559,7 +4566,7 @@ static char *_ekmf_base64_encode(const unsigned char *data, size_t data_size)
return NULL;
len = EVP_EncodeBlock((unsigned char *)out, data, data_size);
if (len != outlen) {
if (len < 0 || (size_t)len != outlen) {
free(out);
return NULL;
}
@@ -4596,7 +4603,7 @@ static int _ekmf_build_key_material(const unsigned char *certificate,
"JSON object", verbose, out);
payload = _ekmf_base64_encode(certificate, certificate_size);
JSON_CHECK_ERROR(*keymat_obj == NULL, rc, -EIO,
JSON_CHECK_ERROR(payload == NULL, rc, -EIO,
"Failed to base64 encode the certificate",
verbose, out);

View File

@@ -9,9 +9,11 @@
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <stdbool.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <unistd.h>
#include <openssl/evp.h>
#include <openssl/pem.h>
@@ -104,7 +106,7 @@ int decode_base64url(unsigned char *output, size_t *outlen,
len = EVP_DecodeBlock((unsigned char *)padded_output,
(unsigned char *)padded_input, padded_inlen);
if (len != (int)padded_inlen * 3 / 4) {
if ((size_t)len != padded_inlen * 3 / 4) {
rc = -EIO;
goto out;
}
@@ -229,8 +231,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 +257,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) {
@@ -363,9 +366,9 @@ out:
*payload_obj = pld;
else
json_object_put(pld);
if (signature != NULL && rc != 0) {
free(signature);
signature = NULL;
if (signature != NULL && *signature != NULL && rc != 0) {
free(*signature);
*signature = NULL;
*signature_len = 0;
}
if (json != NULL)
@@ -653,7 +656,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;
@@ -1034,7 +1037,7 @@ int clone_tag_def_list(const struct ekmf_tag_def_list *src,
if (src->tag_defs[i].description != NULL) {
dest->tag_defs[i].description =
strdup(src->tag_defs[i].description);
if (dest->tag_defs[i].description != NULL) {
if (dest->tag_defs[i].description == NULL) {
rc = -ENOMEM;
goto out;
}
@@ -1930,7 +1933,7 @@ int write_key_blob(const char *filename, unsigned char *key_blob,
if (filename == NULL || key_blob == NULL || key_blob_len == 0)
return -EINVAL;
fp = fopen(filename, "w");
fp = fopen_nofollow(filename, "w");
if (fp == NULL)
return -errno;
@@ -2051,7 +2054,7 @@ int write_x509_certificate(const char *pem_filename, X509 *cert)
if (pem_filename == NULL || cert == NULL)
return -EINVAL;
fp = fopen(pem_filename, "w");
fp = fopen_nofollow(pem_filename, "w");
if (fp == NULL)
return -errno;
@@ -2085,7 +2088,7 @@ int write_x509_request(const char *pem_filename, X509_REQ *req, bool new_hdr)
if (pem_filename == NULL || req == NULL)
return -EINVAL;
fp = fopen(pem_filename, "w");
fp = fopen_nofollow(pem_filename, "w");
if (fp == NULL)
return -errno;
@@ -2153,7 +2156,7 @@ int write_public_key(const char *pem_filename, EVP_PKEY *pkey)
if (pem_filename == NULL || pkey == NULL)
return -EINVAL;
fp = fopen(pem_filename, "w");
fp = fopen_nofollow(pem_filename, "w");
if (fp == NULL)
return -errno;
@@ -2605,3 +2608,33 @@ int json_object_object_add_ex(struct json_object *obj, const char *const key,
return 0;
}
#endif
FILE *fopen_nofollow(const char *path, const char *mode)
{
int flags = O_NOFOLLOW;
int fd;
FILE *fp;
/* Determine flags based on mode */
if (mode[0] == 'r')
flags |= (mode[1] == '+') ? O_RDWR : O_RDONLY;
else if (mode[0] == 'w')
flags |= O_CREAT | O_TRUNC |
((mode[1] == '+') ? O_RDWR : O_WRONLY);
else if (mode[0] == 'a')
flags |= O_CREAT | O_APPEND |
((mode[1] == '+') ? O_RDWR : O_WRONLY);
else
return NULL;
fd = open(path, flags, 0600);
if (fd < 0)
return NULL;
fp = fdopen(fd, mode);
if (fp == NULL) {
close(fd);
return NULL;
}
return fp;
}

View File

@@ -124,4 +124,6 @@ int json_object_object_add_ex(struct json_object *obj, const char *const key,
const unsigned int opts);
#endif
FILE *fopen_nofollow(const char *path, const char *mode);
#endif

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

@@ -735,7 +735,8 @@ int kmip_connection_https_perform(struct kmip_connection *conn,
switch (conn->config.encoding) {
case KMIP_ENCODING_TTLV:
rc = kmip_decode_ttlv(write_cb.ttlv.resp_mem_bio, NULL,
response, debug);
response, KMIP_DECODE_MAX_NESTING_LEVEL,
debug);
if (rc != 0) {
kmip_debug(debug, "kmip_decode_ttlv failed");
goto out;
@@ -750,7 +751,7 @@ int kmip_connection_https_perform(struct kmip_connection *conn,
}
rc = kmip_decode_json(write_cb.json.resp_obj, NULL, response,
debug);
KMIP_DECODE_MAX_NESTING_LEVEL, debug);
if (rc != 0) {
kmip_debug(debug, "kmip_decode_json failed");
goto out;
@@ -768,7 +769,8 @@ int kmip_connection_https_perform(struct kmip_connection *conn,
rc = kmip_decode_xml(xmlDocGetRootElement(
write_cb.xml.ctx->myDoc),
NULL, response, debug);
NULL, response,
KMIP_DECODE_MAX_NESTING_LEVEL, debug);
if (rc != 0) {
kmip_debug(debug, "kmip_decode_xml failed");
goto out;

View File

@@ -27,12 +27,16 @@
* @param parent the parent node or NULL if no parent exists.
* @param node On return: the decoded node. The newly allocated
* node has a reference count of 1.
* @param max_nesting_level the maximum nesting levels of structures within the
* KMIP node. If the nesting level is reached, E2BIG
* is returned.
* @param debug if true, debug messages are printed
*
* @returns 0 in case of success, or a negative errno value
*/
int kmip_decode_json(const json_object *obj, struct kmip_node *parent,
struct kmip_node **node, bool debug)
struct kmip_node **node, size_t max_nesting_level,
bool debug)
{
json_object *tag_obj, *type_obj, *value_obj, *name_obj;
enum kmip_tag tag, v1_attr_tag = 0;
@@ -42,6 +46,9 @@ int kmip_decode_json(const json_object *obj, struct kmip_node *parent,
int rc, num, i;
int64_t int64;
if (max_nesting_level == 0)
return -E2BIG;
if (obj == NULL || node == NULL)
return -EINVAL;
@@ -82,7 +89,7 @@ int kmip_decode_json(const json_object *obj, struct kmip_node *parent,
rc = -EBADMSG;
goto out;
}
n->name = strdup(json_object_get_string(tag_obj));
n->name = strdup(json_object_get_string(name_obj));
}
type_obj = json_object_object_get(obj, KMIP_JSON_TYPE);
@@ -138,7 +145,7 @@ int kmip_decode_json(const json_object *obj, struct kmip_node *parent,
for (i = 0; i < num; i++) {
rc = kmip_decode_json(
json_object_array_get_idx(value_obj, i),
n, &e, debug);
n, &e, max_nesting_level - 1, debug);
if (rc != 0) {
kmip_debug(debug, "Failed to parse "
"array element %d", i);
@@ -614,6 +621,10 @@ int kmip_encode_json(const struct kmip_node *node, json_object **obj,
case KMIP_TYPE_DATE_TIME:
tm = gmtime((time_t *)&node->date_time_value);
if (tm == NULL) {
rc = -EINVAL;
goto out;
}
strftime(outstr, sizeof(outstr), KMIP_ISO8601_TIMESTAMP_UTC,
tm);
memb_obj = json_object_new_string(outstr);

View File

@@ -426,8 +426,14 @@ int kmip_get_key_value(const struct kmip_node *node,
/* Must be a KMIP v1.x attribute then */
kmip_node_free(attr);
if (num_attrs != NULL)
*num_attrs = kmip_node_get_structure_element_count(node) - 1;
if (num_attrs != NULL) {
*num_attrs = kmip_node_get_structure_element_count(node);
if (*num_attrs == 0) {
rc = -EBADMSG;
goto error;
}
(*num_attrs)--;
}
if (v2_attr == NULL)
return 0;

View File

@@ -258,7 +258,7 @@ int kmip_node_add_structure_elements(struct kmip_node *node,
*
* @param node the KMIP node
*
* @returns the number of elements, or -1 if the node is not of type structure
* @returns the number of elements, or 0 if the node is not of type structure
*/
unsigned int kmip_node_get_structure_element_count(const struct kmip_node *node)
{
@@ -266,10 +266,10 @@ unsigned int kmip_node_get_structure_element_count(const struct kmip_node *node)
unsigned int i;
if (node == NULL)
return -1;
return 0;
if (node->type != KMIP_TYPE_STRUCTURE)
return -1;
return 0;
element = node->structure_value;
for (i = 0; element != NULL; i++)
@@ -319,7 +319,7 @@ struct kmip_node *kmip_node_get_structure_element_by_index(
* @param node the KMIP node
* @param tag the tag to find
*
* @returns the number of elements, or -1 if the node is not of type structure
* @returns the number of elements, or 0 if the node is not of type structure
*/
unsigned int kmip_node_get_structure_element_by_tag_count(
const struct kmip_node *node,
@@ -329,10 +329,10 @@ unsigned int kmip_node_get_structure_element_by_tag_count(
unsigned int i;
if (node == NULL)
return -1;
return 0;
if (node->type != KMIP_TYPE_STRUCTURE)
return -1;
return 0;
element = node->structure_value;
for (i = 0; element != NULL; element = element->next) {
@@ -897,7 +897,7 @@ struct kmip_node *kmip_node_clone(const struct kmip_node *node)
case KMIP_TYPE_TEXT_STRING:
if (node->text_value != NULL) {
clone->text_value = strdup(node->text_value);
if (node->text_value == NULL)
if (clone->text_value == NULL)
goto error;
clone->length = strlen(clone->text_value);
}
@@ -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;
@@ -1488,7 +1489,7 @@ retry:
}
if (i == 0 && server_cert_pem != NULL) {
fp = fopen(server_cert_pem, "w");
fp = fopen_nofollow(server_cert_pem, "w");
if (fp == NULL) {
rc = -errno;
kmip_debug(debug, "Failed to open %s for write",
@@ -1506,7 +1507,7 @@ retry:
fp = NULL;
if (server_pubkey_pem != NULL) {
fp = fopen(server_pubkey_pem, "w");
fp = fopen_nofollow(server_pubkey_pem, "w");
if (fp == NULL) {
rc = -errno;
kmip_debug(debug, "Failed to open %s "
@@ -1532,7 +1533,7 @@ retry:
if (i > 0 && cert_chain_pem != NULL) {
if (fp == NULL)
fp = fopen(cert_chain_pem, "w");
fp = fopen_nofollow(cert_chain_pem, "w");
if (fp == NULL) {
rc = -errno;
kmip_debug(debug, "Failed to open %s for write",

View File

@@ -94,19 +94,23 @@ int kmip_connection_https_perform(struct kmip_connection *connection,
bool debug);
void kmip_connection_https_term(struct kmip_connection *connection);
/* KIMP decoding and encoding internal functions */
/* KIMP decoding and encoding internal functions and definitions */
#define KMIP_DECODE_MAX_NESTING_LEVEL 32
int kmip_decode_ttlv(BIO *bio, size_t *size, struct kmip_node **node,
bool debug);
size_t max_nesting_level, bool debug);
int kmip_encode_ttlv(struct kmip_node *node, BIO *bio, size_t *size,
bool debug);
int kmip_decode_json(const json_object *obj, struct kmip_node *parent,
struct kmip_node **node, bool debug);
struct kmip_node **node, size_t max_nesting_level,
bool debug);
int kmip_encode_json(const struct kmip_node *node, json_object **obj,
bool debug);
int kmip_decode_xml(const xmlNode *xml, struct kmip_node *parent,
struct kmip_node **node, bool debug);
struct kmip_node **node, size_t max_nesting_level,
bool debug);
int kmip_encode_xml(const struct kmip_node *node, xmlNode **xml, bool debug);
#endif

View File

@@ -686,14 +686,20 @@ int kmip_get_get_attribute_list_response_payload(const struct kmip_node *node,
if (kmip_node_get_tag(node) != KMIP_TAG_RESPONSE_PAYLOAD)
return -EBADMSG;
if (kmip_node_get_type(node) != KMIP_TYPE_STRUCTURE)
return -EBADMSG;
if (unique_id != NULL)
*unique_id = kmip_node_get_structure_element_by_tag(node,
KMIP_TAG_UNIQUE_IDENTIFIER, 0);
if (num_attr_refs != NULL)
if (num_attr_refs != NULL) {
*num_attr_refs =
kmip_node_get_structure_element_count(node) - 1;
kmip_node_get_structure_element_count(node);
if (*num_attr_refs == 0)
return -EBADMSG;
(*num_attr_refs)--;
}
if (attr_ref == NULL)
return 0;
@@ -766,6 +772,8 @@ int kmip_get_get_attributes_response_payload(const struct kmip_node *node,
if (kmip_node_get_tag(node) != KMIP_TAG_RESPONSE_PAYLOAD)
return -EBADMSG;
if (kmip_node_get_type(node) != KMIP_TYPE_STRUCTURE)
return -EBADMSG;
if (unique_id != NULL)
*unique_id = kmip_node_get_structure_element_by_tag(node,
@@ -798,8 +806,12 @@ int kmip_get_get_attributes_response_payload(const struct kmip_node *node,
/* Must be a KMIP v1.x attribute then */
kmip_node_free(attr);
if (num_attrs != NULL)
*num_attrs = kmip_node_get_structure_element_count(node) - 1;
if (num_attrs != NULL) {
*num_attrs = kmip_node_get_structure_element_count(node);
if (*num_attrs == 0)
return -EBADMSG;
(*num_attrs)--;
}
if (v2_attr == NULL)
return 0;

View File

@@ -493,7 +493,8 @@ int kmip_connection_tls_perform(struct kmip_connection *conn,
kmip_debug(debug, "%lu bytes sent", size);
/* receive the response */
rc = kmip_decode_ttlv(conn->plain_tls.bio, NULL, response, debug);
rc = kmip_decode_ttlv(conn->plain_tls.bio, NULL, response,
KMIP_DECODE_MAX_NESTING_LEVEL, debug);
if (rc != 0 || *response == NULL) {
kmip_debug(debug, "kmip_decode_ttlv failed");
goto out;

View File

@@ -28,12 +28,15 @@
* as many bytes as needed.
* @param node On return: the decoded node. The newly allocated
* node has a reference count of 1.
* @param max_nesting_level the maximum nesting levels of structures within the
* KMIP node. If the nesting level is reached, E2BIG
* is returned.
* @param debug if true, debug messages are printed
*
* @returns 0 in case of success, or a negative errno value
*/
int kmip_decode_ttlv(BIO *bio, size_t *size, struct kmip_node **node,
bool debug)
size_t max_nesting_level, bool debug)
{
unsigned char padding[KMIP_TTLV_BLOCK_LENGTH];
unsigned char ttlv[KMIP_TTLV_HEADER_LENGTH];
@@ -44,6 +47,9 @@ int kmip_decode_ttlv(BIO *bio, size_t *size, struct kmip_node **node,
uint64_t int64;
int rc;
if (max_nesting_level == 0)
return -E2BIG;
if (bio == NULL || node == NULL)
return -EINVAL;
@@ -73,18 +79,18 @@ int kmip_decode_ttlv(BIO *bio, size_t *size, struct kmip_node **node,
n->ref_count = 1;
/* Tag: 3-byte binary unsigned integer, transmitted big endian */
n->tag |= (uint32_t)(ttlv[0] << 16);
n->tag |= (uint32_t)(ttlv[1] << 8);
n->tag |= (uint32_t)(ttlv[2]);
n->tag |= (uint32_t)ttlv[0] << 16;
n->tag |= (uint32_t)ttlv[1] << 8;
n->tag |= (uint32_t)ttlv[2];
/* Type: 1 byte containing a coded value that indicates the data type */
n->type = ttlv[3];
/* Length: 32-bit binary integer, transmitted big-endian */
n->length |= (uint32_t)(ttlv[4] << 24);
n->length |= (uint32_t)(ttlv[5] << 16);
n->length |= (uint32_t)(ttlv[6] << 8);
n->length |= (uint32_t)(ttlv[7]);
n->length |= (uint32_t)ttlv[4] << 24;
n->length |= (uint32_t)ttlv[5] << 16;
n->length |= (uint32_t)ttlv[6] << 8;
n->length |= (uint32_t)ttlv[7];
kmip_debug(debug, "tag: 0x%x type: 0x%x, length: %u", n->tag, n->type,
n->length);
@@ -98,6 +104,11 @@ int kmip_decode_ttlv(BIO *bio, size_t *size, struct kmip_node **node,
case KMIP_TYPE_TEXT_STRING:
case KMIP_TYPE_BYTE_STRING:
value_len = n->length;
if (value_len > INT_MAX) {
rc = -EMSGSIZE;
goto out;
}
value = calloc(1, value_len + 1);
if (value == NULL) {
kmip_debug(debug, "calloc failed");
@@ -160,14 +171,22 @@ int kmip_decode_ttlv(BIO *bio, size_t *size, struct kmip_node **node,
rc = -EIO;
goto out;
}
if (size != NULL)
if (size != NULL) {
if (*size < pad_len) {
rc = -EMSGSIZE;
goto out;
}
*size -= pad_len;
}
}
switch (n->type) {
case KMIP_TYPE_STRUCTURE:
while (value_len > 0) {
rc = kmip_decode_ttlv(bio, &value_len, &e, debug);
rc = kmip_decode_ttlv(bio, &value_len, &e,
max_nesting_level - 1, debug);
if (rc != 0) {
kmip_debug(debug, "kmip_decode_ttlv failed: "
"rc: %d", rc);
@@ -263,7 +282,7 @@ out:
static int kmip_node_get_length(struct kmip_node *node, size_t *length)
{
struct kmip_node *element;
size_t len;
size_t len, prev_len;
int rc;
if (node == NULL || length == NULL)
@@ -278,10 +297,13 @@ static int kmip_node_get_length(struct kmip_node *node, size_t *length)
if (rc != 0)
return rc;
prev_len = *length;
*length += KMIP_TTLV_HEADER_LENGTH + len;
if ((len % KMIP_TTLV_BLOCK_LENGTH) != 0)
*length += KMIP_TTLV_BLOCK_LENGTH -
(len % KMIP_TTLV_BLOCK_LENGTH);
if (*length < prev_len)
return -EOVERFLOW;
element = element->next;
}

View File

@@ -12,9 +12,11 @@
#include <errno.h>
#include <err.h>
#include <fcntl.h>
#include <stdarg.h>
#include <string.h>
#include <strings.h>
#include <unistd.h>
#include "utils.h"
#include "names.h"
@@ -72,6 +74,8 @@ int kmip_parse_decimal_uint(const char *str, uint64_t *val)
if (str == NULL)
return -EINVAL;
if (str[0] == '-')
return -EBADMSG;
errno = 0;
v = strtoull(str, &endptr, 10);
@@ -164,13 +168,16 @@ int kmip_parse_hex(const char *str, bool has_prefix, unsigned char **val,
* Format a hex string from the byte array specified in val. The caller must
* free the returned str.
*/
int kmip_format_hex(const unsigned char *val, uint32_t length, bool prefix,
int kmip_format_hex(const unsigned char *val, size_t length, bool prefix,
char **str)
{
uint32_t str_len, i;
size_t str_len, i;
char tmp[4];
char *ret;
if (length > (SIZE_MAX - ((prefix ? 2 : 0) + 1)) / 2)
return -EINVAL;
str_len = length * 2 + (prefix ? 2 : 0) + 1;
ret = calloc(1, str_len);
if (ret == NULL)
@@ -220,14 +227,18 @@ int kmip_parse_bignum(const char *str, bool has_prefix, BIGNUM **bn)
int kmip_format_bignum(const BIGNUM *bn, bool prefix, char **str)
{
unsigned char *buf;
uint32_t len;
uint32_t len, prev_len;
int rc;
len = kmip_encode_bignum_length(bn);
/* BIG INTEGERS must be a multiple of 8 bytes long */
if ((len % KMIP_BIG_INTEGER_BLOCK_LENGTH) != 0)
if ((len % KMIP_BIG_INTEGER_BLOCK_LENGTH) != 0) {
prev_len = len;
len += KMIP_BIG_INTEGER_BLOCK_LENGTH -
(len % KMIP_BIG_INTEGER_BLOCK_LENGTH);
if (len < prev_len)
return -EOVERFLOW;
}
buf = malloc(len);
if (buf == NULL)
@@ -402,10 +413,10 @@ int kmip_parse_mask(enum kmip_tag tag, const char *str, char separator,
return rc;
}
static int kmip_append_string(char **str, int *str_len, char separator,
static int kmip_append_string(char **str, size_t *str_len, char separator,
const char *append)
{
int new_len;
size_t new_len;
char *tmp;
if (str == NULL || str_len == NULL)
@@ -422,6 +433,9 @@ static int kmip_append_string(char **str, int *str_len, char separator,
if (append != NULL)
new_len += strlen(append);
if (new_len < *str_len)
return -EOVERFLOW;
tmp = realloc(*str, new_len);
if (tmp == NULL)
return -ENOMEM;
@@ -446,8 +460,9 @@ int kmip_format_mask(enum kmip_tag tag, int32_t value, char separator,
char **str)
{
const struct kmip_enum *info;
int rc = 0, i, s_len = 0;
char *s = NULL, *tmp;
size_t s_len = 0;
int rc = 0, i;
info = kmip_enum_info_by_tag(tag);
if (info == NULL || value == 0)
@@ -726,3 +741,32 @@ enum kmip_tag kmip_find_v1_attribute_name_tag(struct kmip_node *parent)
return 0;
}
FILE *fopen_nofollow(const char *path, const char *mode)
{
int flags = O_NOFOLLOW;
int fd;
FILE *fp;
/* Determine flags based on mode */
if (mode[0] == 'r')
flags |= (mode[1] == '+') ? O_RDWR : O_RDONLY;
else if (mode[0] == 'w')
flags |= O_CREAT | O_TRUNC |
((mode[1] == '+') ? O_RDWR : O_WRONLY);
else if (mode[0] == 'a')
flags |= O_CREAT | O_APPEND |
((mode[1] == '+') ? O_RDWR : O_WRONLY);
else
return NULL;
fd = open(path, flags, 0600);
if (fd < 0)
return NULL;
fp = fdopen(fd, mode);
if (fp == NULL) {
close(fd);
return NULL;
}
return fp;
}

View File

@@ -38,7 +38,7 @@ int kmip_parse_decimal_uint(const char *str, uint64_t *val);
int kmip_parse_hex_int(const char *str, int64_t *val);
int kmip_parse_hex(const char *str, bool has_prefix, unsigned char **val,
uint32_t *length);
int kmip_format_hex(const unsigned char *val, uint32_t length, bool prefix,
int kmip_format_hex(const unsigned char *val, size_t length, bool prefix,
char **str);
int kmip_parse_bignum(const char *str, bool has_prefix, BIGNUM **bn);
@@ -58,4 +58,6 @@ void kmip_node_dump(struct kmip_node *node, bool debug);
enum kmip_tag kmip_find_v1_attribute_name_tag(struct kmip_node *parent);
FILE *fopen_nofollow(const char *path, const char *mode);
#endif

View File

@@ -28,12 +28,16 @@
* @param parent the parent node or NULL if no parent exists.
* @param node On return: the decoded node.The newly allocated
* node has a reference count of 1.
* @param max_nesting_level the maximum nesting levels of structures within the
* KMIP node. If the nesting level is reached, E2BIG
* is returned.
* @param debug if true, debug messages are printed
*
* @returns 0 in case of success, or a negative errno value
*/
int kmip_decode_xml(const xmlNode *xml, struct kmip_node *parent,
struct kmip_node **node, bool debug)
struct kmip_node **node, size_t max_nesting_level,
bool debug)
{
char *tag_attr = NULL, *name_attr = NULL, *type_attr = NULL;
enum kmip_tag tag, v1_attr_tag = 0;
@@ -44,6 +48,9 @@ int kmip_decode_xml(const xmlNode *xml, struct kmip_node *parent,
int64_t int64;
int rc = 0, i;
if (max_nesting_level == 0)
return -E2BIG;
if (xml == NULL || node == NULL)
return -EINVAL;
@@ -122,7 +129,8 @@ int kmip_decode_xml(const xmlNode *xml, struct kmip_node *parent,
if (child->type != XML_ELEMENT_NODE)
continue;
rc = kmip_decode_xml(child, n, &e, debug);
rc = kmip_decode_xml(child, n, &e,
max_nesting_level - 1, debug);
if (rc != 0) {
kmip_debug(debug, "Failed to parse child "
"element %d", i);
@@ -452,6 +460,10 @@ int kmip_encode_xml(const struct kmip_node *node, xmlNode **xml, bool debug)
case KMIP_TYPE_DATE_TIME:
tm = gmtime((time_t *)&node->date_time_value);
if (tm == NULL) {
rc = -EINVAL;
goto out;
}
strftime(tmp_str, sizeof(tmp_str), KMIP_ISO8601_TIMESTAMP_UTC,
tm);
attr = xmlSetProp(ret_xml, (xmlChar *)KMIP_XML_VALUE,

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

View File

@@ -258,7 +258,9 @@ static int sk_cca_get_library_functions(const struct sk_ext_cca_lib *cca_lib,
*
* @param cca_lib the CCA library structure
* @param curve_nid the nid specifying the curve.
* @param key_token a buffer to store the generated key token
* @param key_token a buffer to store the generated key token. If NULL,
* the required buffer size is returned in
* key_token_length (size query).
* @param key_token_length On entry: the size of the buffer
* On return: the size of the key token
* @param debug if true, debug messages are printed
@@ -286,7 +288,7 @@ int SK_CCA_generate_ec_key_pair(const struct sk_ext_cca_lib *cca_lib,
long param1 = 0;
int rc;
if (cca_lib == NULL || key_token == NULL || key_token_length == NULL)
if (cca_lib == NULL || key_token_length == NULL)
return -EINVAL;
if (key_token == NULL) {
@@ -384,7 +386,9 @@ int SK_CCA_generate_ec_key_pair(const struct sk_ext_cca_lib *cca_lib,
* 3, 5, 17, 257, or 65537. Specify zero to choose the
* exponent by random (only possible for modulus_bits
* up to 2048).
* @param key_token a buffer to store the generated key token
* @param key_token a buffer to store the generated key token. If NULL,
* the required buffer size is returned in
* key_token_length (size query).
* @param key_token_length On entry: the size of the buffer
* On return: the size of the key token
* @param debug if true, debug messages are printed
@@ -412,7 +416,7 @@ int SK_CCA_generate_rsa_key_pair(const struct sk_ext_cca_lib *cca_lib,
long param1 = 0;
int rc;
if (cca_lib == NULL || key_token == NULL || key_token_length == NULL)
if (cca_lib == NULL || key_token_length == NULL)
return -EINVAL;
if (key_token == NULL) {
@@ -459,7 +463,7 @@ int SK_CCA_generate_rsa_key_pair(const struct sk_ext_cca_lib *cca_lib,
case 257:
key_value_structure.public_exp_length = 2;
key_value_structure.public_exponent[0] = 0x01;
key_value_structure.public_exponent[0] = 0x01;
key_value_structure.public_exponent[1] = 0x01;
break;
case 65537:
key_value_structure.public_exp_length = 3;
@@ -560,6 +564,11 @@ static const void *sk_cca_get_pka_section(const unsigned char *key_token,
section_hdr = (struct cca_section_header *)&key_token[ofs];
while (section_hdr->section_identifier != section_id) {
if (section_hdr->section_length <
sizeof(struct cca_section_header)) {
sk_debug(debug, "ERROR: invalid section length");
return NULL;
}
ofs += section_hdr->section_length;
if (ofs >= token_hdr->token_length) {
sk_debug(debug, "ERROR: section %u not found",
@@ -1385,7 +1394,7 @@ static int sk_cca_get_public_from_rsa_key(const unsigned char *key_token,
return -EINVAL;
}
if (rsa_pub_section->section_header.section_length <
sizeof(struct cca_ec_pub_key_section)) {
sizeof(struct cca_rsa_pub_key_section)) {
sk_debug(debug, "ERROR: invalid RSA public key section length");
return -EINVAL;
}

View File

@@ -195,7 +195,7 @@ static int sk_ep11_generate_key_pair(const struct sk_ext_ep11_lib *ep11_lib,
CK_RV rv;
int rc;
if (ep11_lib == NULL || key_token == NULL || key_token_length == NULL)
if (ep11_lib == NULL || key_token_length == NULL)
return -EINVAL;
if (key_token == NULL) {
@@ -262,7 +262,9 @@ static int sk_ep11_generate_key_pair(const struct sk_ext_ep11_lib *ep11_lib,
*
* @param ep11_lib the Ep11 library structure
* @param curve_nid the nid specifying the curve.
* @param key_token a buffer to store the generated key token
* @param key_token a buffer to store the generated key token. If NULL,
* the required buffer size is returned in
* key_token_length (size query).
* @param key_token_length On entry: the size of the buffer
* On return: the size of the key token
* @param debug if true, debug messages are printed
@@ -292,7 +294,7 @@ int SK_EP11_generate_ec_key_pair(const struct sk_ext_ep11_lib *ep11_lib,
const struct sk_ec_curve_info *curve;
int rc;
if (ep11_lib == NULL || key_token == NULL || key_token_length == NULL)
if (ep11_lib == NULL || key_token_length == NULL)
return -EINVAL;
sk_debug(debug, "curve_nid: %d", curve_nid);
@@ -331,7 +333,9 @@ int SK_EP11_generate_ec_key_pair(const struct sk_ext_ep11_lib *ep11_lib,
* 3, 5, 17, 257, or 65537. Specify zero to choose the
* exponent by random.
* @param x9_31 if true, generate a X9.31 RSA key
* @param key_token a buffer to store the generated key token
* @param key_token a buffer to store the generated key token. If NULL,
* the required buffer size is returned in
* key_token_length (size query).
* @param key_token_length On entry: the size of the buffer
* On return: the size of the key token
* @param debug if true, debug messages are printed
@@ -365,7 +369,7 @@ int SK_EP11_generate_rsa_key_pair(const struct sk_ext_ep11_lib *ep11_lib,
CK_ULONG priv_tmpl_num = sizeof(priv_tmpl) / sizeof(CK_ATTRIBUTE);
int rc;
if (ep11_lib == NULL || key_token == NULL || key_token_length == NULL)
if (ep11_lib == NULL || key_token_length == NULL)
return -EINVAL;
sk_debug(debug, "modulus_bits: %lu pub_exp: %u x9_31: %d",
@@ -413,6 +417,8 @@ static unsigned char sk_ep11_parse_der_tag(const unsigned char *data,
if (num > sizeof(size_t))
return 0;
*value_len = data[2];
if (data_len < 2 + num)
return 0;
for (i = 1; i < num; i++) {
*value_len <<= 8;
*value_len |= data[2 + i];
@@ -498,6 +504,8 @@ static int sk_ep11_parse_spki(const unsigned char *spki, size_t spki_len,
pub_key, pub_key_len);
if (tag != 0x03) /* BITSTRING */
return -EINVAL;
if (*pub_key_len == 0)
return -EINVAL;
/* skip unsused-bits byte */
(*pub_key)++;
@@ -555,7 +563,7 @@ int SK_EP11_get_key_type(const unsigned char *key_token,
return -EINVAL;
spki_size = key_token_length - hdr->len;
if (spki_size <= 0)
if (spki_size == 0)
return -EINVAL;
rc = sk_ep11_parse_spki(key_token + hdr->len, spki_size,
@@ -702,7 +710,7 @@ static int sk_ep11_rsa_sign(const unsigned char *key_token,
case RSA_X931_PADDING:
mech.mechanism = CKM_RSA_X9_31;
if ((hdr->flags && PKEY_TYPE_EP11_FLAG_X9_31) == 0) {
if ((hdr->flags & PKEY_TYPE_EP11_FLAG_X9_31) == 0) {
sk_debug(debug, "ERROR: no RSA X9.31 key");
return -EINVAL;
}
@@ -1375,7 +1383,7 @@ int SK_EP11_get_public_from_secure_key(const unsigned char *key_token,
return -EINVAL;
spki_size = key_token_length - hdr->len;
if (spki_size <= 0)
if (spki_size == 0)
return -EINVAL;
rc = sk_ep11_parse_spki(key_token + hdr->len, spki_size,

View File

@@ -698,6 +698,7 @@ static int sk_pkey_meth_setup_pkey(EVP_PKEY *pkey,
struct sk_pkey_data *data;
EC_KEY *ec;
RSA *rsa;
int rc;
if (pkey == NULL || secure_key == NULL || secure_key_size == 0 ||
funcs == NULL)
@@ -733,12 +734,14 @@ static int sk_pkey_meth_setup_pkey(EVP_PKEY *pkey,
ec = EVP_PKEY_get0_EC_KEY(pkey);
if (ec == NULL) {
sk_debug(debug, "EVP_PKEY_get0_EC_KEY failed");
return -EIO;
rc = -EIO;
goto error;
}
if (!EC_KEY_set_ex_data(ec, sk_pkey_data_ec_index, data)) {
sk_debug(debug, "EC_KEY_set_ex_data failed");
return -EIO;
rc = -EIO;
goto error;
}
break;
case EVP_PKEY_RSA:
@@ -746,17 +749,29 @@ static int sk_pkey_meth_setup_pkey(EVP_PKEY *pkey,
rsa = EVP_PKEY_get0_RSA(pkey);
if (rsa == NULL) {
sk_debug(debug, "EVP_PKEY_get0_RSA failed");
return -EIO;
rc = -EIO;
goto error;
}
if (!RSA_set_ex_data(rsa, sk_pkey_data_rsa_index, data)) {
sk_debug(debug, "RSA_set_ex_data failed");
return -EIO;
rc = -EIO;
goto error;
}
break;
default:
rc = -EIO;
goto error;
}
return 0;
error:
if (data != NULL) {
OPENSSL_free(data->key_blob);
OPENSSL_free(data);
}
return rc;
}
/**
@@ -796,13 +811,13 @@ int SK_OPENSSL_init(bool debug)
return -EIO;
}
sk_pkey_meth_default_method_rsa = EVP_PKEY_meth_find(EVP_PKEY_RSA);
if (sk_pkey_meth_default_method_ec == NULL) {
if (sk_pkey_meth_default_method_rsa == NULL) {
sk_debug(debug, "ERROR: EVP_PKEY_meth_find(RSA) failed");
return -EIO;
}
sk_pkey_meth_default_method_rsa_pss =
EVP_PKEY_meth_find(EVP_PKEY_RSA_PSS);
if (sk_pkey_meth_default_method_ec == NULL) {
if (sk_pkey_meth_default_method_rsa_pss == NULL) {
sk_debug(debug, "ERROR: EVP_PKEY_meth_find(RSA-PSS) failed");
return -EIO;
}

View File

@@ -10,10 +10,12 @@
#include <string.h>
#include <err.h>
#include <errno.h>
#include <fcntl.h>
#include <stdarg.h>
#include <stdbool.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <unistd.h>
#include <openssl/evp.h>
#include <openssl/sha.h>
@@ -811,6 +813,35 @@ out:
return rc;
}
static inline FILE *fopen_nofollow(const char *path, const char *mode)
{
int flags = O_NOFOLLOW;
int fd;
FILE *fp;
/* Determine flags based on mode */
if (mode[0] == 'r')
flags |= (mode[1] == '+') ? O_RDWR : O_RDONLY;
else if (mode[0] == 'w')
flags |= O_CREAT | O_TRUNC |
((mode[1] == '+') ? O_RDWR : O_WRONLY);
else if (mode[0] == 'a')
flags |= O_CREAT | O_APPEND |
((mode[1] == '+') ? O_RDWR : O_WRONLY);
else
return NULL;
fd = open(path, flags, 0600);
if (fd < 0)
return NULL;
fp = fdopen(fd, mode);
if (fp == NULL) {
close(fd);
return NULL;
}
return fp;
}
/**
* Reads a X.509 certificate from the specified PEM file.
@@ -863,7 +894,7 @@ int SK_UTIL_write_x509_certificate(const char *pem_filename, X509 *cert)
if (pem_filename == NULL || cert == NULL)
return -EINVAL;
fp = fopen(pem_filename, "w");
fp = fopen_nofollow(pem_filename, "w");
if (fp == NULL)
return -errno;
@@ -898,7 +929,7 @@ int SK_UTIL_write_x509_request(const char *pem_filename, X509_REQ *req,
if (pem_filename == NULL || req == NULL)
return -EINVAL;
fp = fopen(pem_filename, "w");
fp = fopen_nofollow(pem_filename, "w");
if (fp == NULL)
return -errno;
@@ -982,7 +1013,7 @@ int SK_UTIL_write_key_blob(const char *filename, unsigned char *key_blob,
if (filename == NULL || key_blob == NULL || key_blob_len == 0)
return -EINVAL;
fp = fopen(filename, "w");
fp = fopen_nofollow(filename, "w");
if (fp == NULL)
return -errno;
@@ -1047,7 +1078,7 @@ int SK_UTIL_write_public_key(const char *pem_filename, EVP_PKEY *pkey)
if (pem_filename == NULL || pkey == NULL)
return -EINVAL;
fp = fopen(pem_filename, "w");
fp = fopen_nofollow(pem_filename, "w");
if (fp == NULL)
return -errno;

View File

@@ -89,11 +89,26 @@ static const struct {
const char *name;
enum util_fmt_t fmt;
} formats[] = {
{ "json", FMT_JSON },
{ "json-seq", FMT_JSONSEQ },
{ "jsonl", FMT_JSONL },
{ "pairs", FMT_PAIRS },
{ "csv", FMT_CSV },
[FMT_JSON] = {
.name = "json",
.fmt = FMT_JSON
},
[FMT_JSONSEQ] = {
.name = "json-seq",
.fmt = FMT_JSONSEQ
},
[FMT_JSONL] = {
.name = "jsonl",
.fmt = FMT_JSONL
},
[FMT_PAIRS] = {
.name = "pairs",
.fmt = FMT_PAIRS
},
[FMT_CSV] = {
.name = "csv",
.fmt = FMT_CSV
},
};
/* Signal mask for blocking INT and TERM signals. */
@@ -112,6 +127,11 @@ bool util_fmt_name_to_type(const char *name, enum util_fmt_t *type)
return false;
}
const char *util_fmt_type_to_name(enum util_fmt_t type)
{
return formats[type].name;
}
bool util_fmt_is_json(enum util_fmt_t type)
{
switch (type) {

View File

@@ -12,13 +12,17 @@ static void zpci_print(struct zpci_dev *zdev)
int i;
if (!zdev->conf) {
printf("fid: %8x address: %s\n", zdev->fid, pci_addr);
printf("fid:\t%4x address: %s\n", zdev->fid, pci_addr);
} else {
printf("fid: %8x address: %s uid: %4x%s pchid: %4x vfn: %4d port: %1d pft: %s ",
printf("fid:\t%4x address: %s uid: %4x%s pchid: %4x vfn: %4d\n",
zdev->fid, pci_addr, zdev->uid, (zdev->uid_is_unique) ? " (unique)" : "",
zdev->pchid, zdev->vfn, zdev->port, zpci_pft_str(zdev));
zdev->pchid, zdev->vfn);
printf("\n");
printf("\tport: %1d pft: %s subsystem_device: %4x subsystem_vendor: %4x\n",
zdev->port, zpci_pft_str(zdev), zdev->subsystem_device,
zdev->subsystem_vendor);
if (zdev->num_netdevs) {
printf("netdevs: ");
printf("\tnetdevs: ");
for (i = 0; i < zdev->num_netdevs; i++) {
printf("%s (%s)", zdev->netdevs[i].name,
zpci_operstate_str(zdev->netdevs[i].operstate));

View File

@@ -20,6 +20,7 @@
#include "lib/util_list.h"
#include "lib/util_path.h"
#include "lib/util_scandir.h"
#include "lib/util_sys.h"
/**
* Get the function type name for the given device
@@ -137,7 +138,7 @@ const char *zpci_operstate_str(operstate_t state)
static int zpci_populate_from_slot_dir(struct zpci_dev *zdev, const char *slot_dir,
const char *slot_name)
{
char buf_addr[11]; /* "dddd:bb:dd\0" */
char buf_addr[PCI_BDF_LEN];
uint8_t bus, df;
uint32_t domain;
int val, rc;
@@ -261,6 +262,16 @@ static int zpci_populate_from_dev_dir(struct zpci_dev *zdev)
goto out_path;
zdev->pft = val;
rc = util_file_read_i(&val, 16, "%s/subsystem_device", path);
if (rc)
goto out_path;
zdev->subsystem_device = val;
rc = util_file_read_i(&val, 16, "%s/subsystem_vendor", path);
if (rc)
goto out_path;
zdev->subsystem_vendor = val;
if (util_path_is_readable("%s/net", path)) {
rc = zpci_populate_netdevices(zdev, path);
if (rc)
@@ -387,3 +398,46 @@ struct zpci_dev *zpci_find_by_netdev(struct util_list *zpci_list, char *netdev_n
}
return NULL;
}
/**
* Get the NVMe device file name given a PCI address
*
* This function retrieves the NVMe device file "/dev/nvmeX"
* for a given PCI address. The device name can be used to construct
* the path /dev/nvmeX which is the NVMe's controller's character
* device used for example to retrieve S.M.A.R.T. data.
*
* @param[in] pci_addr The "DDDD:bb:dd.f" format PCI address
*
* @return The NVMe device file name if one is found NULL otherwise
*/
char *zpci_get_nvme_device_node(const char *pci_addr)
{
char *path, *dev = NULL;
char dev_addr[PCI_BDF_LEN];
struct dirent **de_vec;
int count, i;
path = util_path_sysfs("bus/pci/devices/%s/nvme", pci_addr);
count = util_scandir(&de_vec, alphasort, path, "nvme*");
if (count == -1) {
warnx("Could not read directory %s: %s", path, strerror(errno));
goto exit_path;
}
for (i = 0; i < count; i++) {
util_asprintf(&dev, "/dev/%s", de_vec[i]->d_name);
if (util_sys_get_dev_addr(dev, dev_addr) != 0)
goto free_continue;
if (strcmp(dev_addr, pci_addr) == 0)
break;
free_continue:
free(dev);
dev = NULL;
}
util_scandir_free(de_vec, count);
exit_path:
free(path);
return dev;
}

View File

@@ -742,6 +742,8 @@ static int read_cmdline(struct task_t *task)
buf[i] = ' ';
}
name_lens.cmdline_len = num;
if (name_lens.cmdline_len > MAX_CMD_LEN)
name_lens.cmdline_len = MAX_CMD_LEN;
cmdlnlenp = mon_record + sizeof(struct monwrite_hdr);
cmdlnlenp += sizeof(struct procd_hdr);
cmdlnlenp += sizeof(struct task_t);

View File

@@ -1,407 +0,0 @@
/*
* opticsmon - Report optics monitoring data to firmware
*
* Copyright IBM Corp. 2024
*
* s390-tools is free software; you can redistribute it and/or modify
* it under the terms of the MIT license. See LICENSE for details.
*/
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <errno.h>
#include <sys/epoll.h>
#include <sys/signalfd.h>
#include <signal.h>
#include <sys/timerfd.h>
#include <time.h>
#include <linux/if.h>
#include "lib/util_list.h"
#include "lib/pci_list.h"
#include "lib/util_prg.h"
#include "lib/util_opt.h"
#include "lib/util_fmt.h"
#include "lib/util_libc.h"
#include <openssl/evp.h>
#include "ethtool.h"
#include "link_mon.h"
#include "optics_info.h"
#include "optics_sclp.h"
#include "opticsmon_cli.h"
#define API_LEVEL 1
struct options {
bool monitor;
bool report;
bool module_info;
bool quiet;
uint32_t interval_seconds;
};
struct opticsmon_ctx {
struct options opts;
struct ethtool_nl_ctx ethtool_ctx;
struct link_mon_nl_ctx lctx;
struct util_list *zpci_list;
};
static const struct util_prg prg = {
.desc = "Use opticsmon to monitor the health of the optical modules\n"
"of directly attached PCI based NICs",
.copyright_vec = { {
.owner = "IBM Corp.",
.pub_first = 2024,
.pub_last = 2024,
},
UTIL_PRG_COPYRIGHT_END }
};
static void parse_cmdline(int argc, char *argv[], struct options *opts)
{
uint32_t seconds;
int cmd, ret;
util_prg_init(&prg);
util_opt_init(opt_vec, NULL);
do {
cmd = util_opt_getopt_long(argc, argv);
switch (cmd) {
case 'm':
opts->monitor = true;
break;
case 'r':
opts->report = true;
break;
case 'q':
opts->quiet = true;
break;
case OPT_DUMP:
opts->module_info = true;
break;
case 'i':
ret = sscanf(optarg, "%u", &seconds);
if (ret != 1) {
fprintf(stderr,
"Failed to parse interval argument \"%s\" as seconds\n",
optarg);
exit(EXIT_FAILURE);
}
if (seconds < 86400)
opts->interval_seconds = seconds;
break;
case 'h':
util_prg_print_help();
util_opt_print_help();
exit(EXIT_SUCCESS);
case 'v':
util_prg_print_version();
exit(EXIT_SUCCESS);
case -1:
/* End of options string */
break;
}
} while (cmd != -1);
}
static int module_info_pair(struct optics *oi)
{
size_t b64_calclen, b64len;
int rc = EXIT_SUCCESS;
char *b64;
b64_calclen = (oi->size / 3) * 4;
if (oi->size % 3 > 0)
b64_calclen += 4;
b64 = util_zalloc(b64_calclen + 1); /* adds NUL byte */
b64len = EVP_EncodeBlock((unsigned char *)b64, oi->raw, oi->size);
if (b64len != b64_calclen) {
fprintf(stderr, "encoding base64 via openssl failed\n");
rc = EXIT_FAILURE;
goto out;
}
util_fmt_pair(FMT_QUOTE, "module_info", b64);
out:
free(b64);
return rc;
}
static void optics_json_print(struct opticsmon_ctx *ctx, struct zpci_netdev *nd, struct optics *oi)
{
util_fmt_obj_start(FMT_DEFAULT, "netdev");
util_fmt_pair(FMT_QUOTE, "name", nd->name);
util_fmt_pair(FMT_QUOTE, "operstate", zpci_operstate_str(nd->operstate));
util_fmt_obj_start(FMT_DEFAULT, "optics");
util_fmt_pair(FMT_QUOTE, "type", optics_type_str(optics_type(oi)));
util_fmt_pair(FMT_QUOTE, "rx_los", optics_los_str(optics_rx_los(oi)));
util_fmt_pair(FMT_QUOTE, "tx_los", optics_los_str(optics_tx_los(oi)));
util_fmt_pair(FMT_QUOTE, "tx_fault", optics_los_str(optics_rx_los(oi)));
if (ctx->opts.module_info)
module_info_pair(oi);
util_fmt_obj_end();
util_fmt_obj_end();
}
static int dump_adapter_data(struct opticsmon_ctx *ctx, struct zpci_dev *zdev)
{
struct optics **ois;
int num_ois = 0;
char *pci_addr;
int i, rc;
ois = util_zalloc(sizeof(ois[0]) * zdev->num_netdevs);
for (i = 0; i < zdev->num_netdevs; i++) {
rc = ethtool_nl_get_optics(&ctx->ethtool_ctx, zdev->netdevs[i].name, &ois[i]);
if (rc)
goto free_ois;
num_ois++;
}
if (!ctx->opts.quiet) {
util_fmt_obj_start(FMT_DEFAULT, "adapter");
util_fmt_pair(FMT_QUOTE, "pft", zpci_pft_str(zdev));
util_fmt_obj_start(FMT_DEFAULT, "ids");
util_fmt_pair(FMT_QUOTE, "fid", "0x%0x", zdev->fid);
if (zdev->uid_is_unique)
util_fmt_pair(FMT_QUOTE, "uid", "0x%0x", zdev->uid);
pci_addr = zpci_pci_addr(zdev);
util_fmt_pair(FMT_QUOTE, "pci_address", pci_addr);
free(pci_addr);
util_fmt_obj_end();
util_fmt_obj_start(FMT_LIST, "netdevs");
for (i = 0; i < zdev->num_netdevs; i++)
optics_json_print(ctx, &zdev->netdevs[i], ois[i]);
util_fmt_obj_end(); /* netdevs list */
util_fmt_obj_end(); /* adapter */
fflush(stdout);
}
if (ctx->opts.report) {
for (i = 0; i < zdev->num_netdevs; i++) {
rc = sclp_issue_optics_report(zdev, ois[i]);
if (rc == -ENOTSUP) {
fprintf(stderr, "Skipping %s which does not support reporting\n",
zdev->netdevs[i].name);
} else if (rc < 0) {
fprintf(stderr, "Error issuing SCLP for optics data failed: %s\n",
strerror(-rc));
}
}
}
free_ois:
for (i = 0; i < num_ois; i++)
optics_free(ois[i]);
free(ois);
return rc;
}
static void zpci_list_reload(struct util_list **zpci_list)
{
if (*zpci_list)
zpci_free_dev_list(*zpci_list);
*zpci_list = zpci_dev_list();
}
static void dump_all_adapter_data(struct opticsmon_ctx *ctx)
{
struct zpci_dev *zdev;
zpci_list_reload(&ctx->zpci_list);
util_list_iterate(ctx->zpci_list, zdev) {
/* Filter non-NIC devices and VFs */
if (zpci_is_vf(zdev) || !zdev->num_netdevs)
continue;
dump_adapter_data(ctx, zdev);
}
}
static int oneshot_mode(struct opticsmon_ctx *ctx)
{
util_fmt_init(stdout, FMT_JSON, FMT_DEFAULT, API_LEVEL);
if (!ctx->opts.quiet)
util_fmt_obj_start(FMT_LIST, "adapters");
dump_all_adapter_data(ctx);
if (!ctx->opts.quiet)
util_fmt_obj_end();
util_fmt_exit();
return EXIT_SUCCESS;
}
void on_link_change(struct zpci_netdev *netdev, void *arg)
{
struct opticsmon_ctx *ctx = arg;
struct zpci_netdev *found_netdev;
struct zpci_dev *zdev = NULL;
int reloads = 1;
do {
if (ctx->zpci_list) {
zdev = zpci_find_by_netdev(ctx->zpci_list, netdev->name, &found_netdev);
if (zdev) {
/* Skip data collection if operational state is
* unchanged
*/
if (found_netdev->operstate == netdev->operstate)
return;
/* Update operation state for VFs even though
* they are skipped just for a consistent view
*/
found_netdev->operstate = netdev->operstate;
/* Only collect optics data for PFs */
if (!zpci_is_vf(zdev))
dump_adapter_data(ctx, zdev);
return;
}
}
/* Could be uninitalized list or a new device, retry after reload */
zpci_list_reload(&ctx->zpci_list);
reloads--;
} while (reloads > 0);
}
#define MAX_EVENTS 8
static int monitor_wait_loop(struct opticsmon_ctx *ctx, int sigfd, int timerfd)
{
struct epoll_event events[MAX_EVENTS];
struct signalfd_siginfo fdsi;
int i, nlfd, epfd, nfds;
struct epoll_event ev;
uint64_t expirations;
ssize_t sread;
epfd = epoll_create1(EPOLL_CLOEXEC);
ev.events = EPOLLIN;
ev.data.fd = sigfd;
if (epoll_ctl(epfd, EPOLL_CTL_ADD, sigfd, &ev) == -1)
return -EIO;
ev.events = EPOLLIN;
ev.data.fd = timerfd;
if (epoll_ctl(epfd, EPOLL_CTL_ADD, timerfd, &ev) == -1)
return -EIO;
nlfd = link_mon_nl_waitfd_getfd(&ctx->lctx);
ev.events = EPOLLIN;
ev.data.fd = nlfd;
if (epoll_ctl(epfd, EPOLL_CTL_ADD, nlfd, &ev) == -1)
return -EIO;
while (1) {
nfds = epoll_wait(epfd, events, MAX_EVENTS, -1);
if (nfds < 0)
return nfds;
for (i = 0; i < nfds; i++) {
/* signal fd */
if (events[i].data.fd == sigfd) {
sread = read(sigfd, &fdsi, sizeof(fdsi));
if (sread != sizeof(fdsi))
return -EIO;
switch (fdsi.ssi_signo) {
case SIGINT:
case SIGTERM:
case SIGQUIT:
return 0;
/* Unexpected signal */
default:
return -EIO;
}
/* timer fd */
} else if (events[i].data.fd == timerfd) {
sread = read(timerfd, &expirations, sizeof(uint64_t));
if (sread != sizeof(uint64_t))
return -EIO;
if (!expirations)
continue;
dump_all_adapter_data(ctx);
/* netlink fd */
} else if (events[i].data.fd == nlfd) {
link_mon_nl_waitfd_read(&ctx->lctx);
}
}
}
return 0;
}
static int monitor_mode(struct opticsmon_ctx *ctx)
{
struct itimerspec timerspec;
int sigfd, timerfd, ret;
sigset_t mask;
sigemptyset(&mask);
sigaddset(&mask, SIGINT);
sigaddset(&mask, SIGQUIT);
sigaddset(&mask, SIGTERM);
if (sigprocmask(SIG_BLOCK, &mask, NULL) == -1)
return -EIO;
sigfd = signalfd(-1, &mask, 0);
if (sigfd == -1) {
fprintf(stderr, "Failed to create signalfd\n");
return -EIO;
}
timerfd = timerfd_create(CLOCK_MONOTONIC, 0);
if (timerfd == -1) {
fprintf(stderr, "Failed to create timerfd\n");
ret = -EIO;
goto close_signalfd;
}
/* Set initial expiration to 1 ns so we gather optics data at startup */
timerspec.it_value.tv_sec = 0;
timerspec.it_value.tv_nsec = 1;
timerspec.it_interval.tv_sec = ctx->opts.interval_seconds;
timerspec.it_interval.tv_nsec = 0;
ret = timerfd_settime(timerfd, 0, &timerspec, NULL);
if (ret == -1) {
fprintf(stderr, "Failed to arm timer\n");
goto close_timerfd;
}
util_fmt_init(stdout, FMT_JSONSEQ, FMT_DEFAULT, API_LEVEL);
ret = link_mon_nl_waitfd_create(&ctx->lctx, on_link_change, ctx);
if (ret) {
fprintf(stderr, "Failed to create link monitoring socket\n");
goto close_timerfd;
}
monitor_wait_loop(ctx, sigfd, timerfd);
link_mon_nl_waitfd_destroy(&ctx->lctx);
util_fmt_exit();
close_signalfd:
close(sigfd);
close_timerfd:
close(timerfd);
return ret;
}
int main(int argc, char **argv)
{
struct opticsmon_ctx ctx = { .opts = { .interval_seconds = 86400 } };
int ret;
parse_cmdline(argc, argv, &ctx.opts);
ethtool_nl_connect(&ctx.ethtool_ctx);
if (ctx.opts.monitor)
ret = monitor_mode(&ctx);
else
ret = oneshot_mode(&ctx);
ethtool_nl_close(&ctx.ethtool_ctx);
if (ctx.zpci_list)
zpci_free_dev_list(ctx.zpci_list);
return ret;
}

View File

@@ -44,7 +44,20 @@
#define MAX_GET_DATA 4094 /* maximum GET response data length */
#define GET_AREA_LEN MAX_GET_DATA + 512 /* size for GET command area length */
#define TIME_BUF_SIZE 128 /* buffer size for date and time string */
#define MAX_OID_STR_LEN MAX_OID_LEN * 5 /* max OID string size */
/*
* Define maximum SNMP OID string size.
* An OID can have up to 128 sub-ids and each sub-id can have up to 10
* digits. When converted to string, this means 1 character for the
* period separator and 10 characters for the digits themselves. At
* the end of OID string, we need one more character for the null
* terminator. The unsigned long data type, which can have up to 20
* digits is used to represent a sub-id. This means each sub-id
* string now needs 20+1 characters instead of 10+1 in-order to avoid
* compiler warnings.
*/
#define MAX_OID_STR_LEN (MAX_OID_LEN * 21) + 1
/* definitions for 2.6 qeth */
#define QETH_SYSFILE "/sys/bus/ccwgroup/drivers/qeth/notifier_register"
#define SIOC_QETH_ADP_SET_SNMP_CONTROL (SIOCDEVPRIVATE + 5)

View File

@@ -110,7 +110,7 @@ int str_to_oid_conv ( char* uc_oid, oid* ul_oid )
*********************************************************************/
int oid_to_str_conv (oid* ul_oid, size_t length, char* uc_oid )
{
#define MAX_CHARS 50 /* size of buffer */
#define MAX_CHARS 22 /* size of buffer */
int i;
short valid = TRUE;
char buffer[MAX_CHARS]; /* buffer used for conversion */
@@ -125,9 +125,9 @@ int oid_to_str_conv (oid* ul_oid, size_t length, char* uc_oid )
{
/* convert and append OID digit to return string */
if (i == 0)
sprintf( buffer, "%lu", ul_oid[i] );
snprintf( buffer, sizeof(buffer), "%lu", ul_oid[i] );
else
sprintf( buffer, ".%lu", ul_oid[i] );
snprintf( buffer, sizeof(buffer), ".%lu", ul_oid[i] );
strcat( uc_oid, buffer );
} /* end for */

599
rust/Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -11,18 +11,18 @@ members = [
"pvsecret",
"pvverify",
"utils",
"utils_macros",
]
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

@@ -114,8 +114,10 @@ ifneq ($(HAVE_DRACUT),0)
$(INSTALL) -m 755 pvebc/$(SEL_EBC_MODDIR)/module-setup.sh \
pvebc/$(SEL_EBC_MODDIR)/override-crypttab.sh \
pvebc/$(SEL_EBC_MODDIR)/pvebc-wrapper.sh \
pvebc/$(SEL_EBC_MODDIR)/boot-mount.sh \
pvebc/$(SEL_EBC_MODDIR)/sel-ebc-modules.conf \
$(DESTDIR)$(DRACUTMODDIR)/$(SEL_EBC_MODDIR)
$(INSTALL) -m 644 pvebc/$(SEL_EBC_MODDIR)/boot.mount \
$(INSTALL) -m 644 pvebc/$(SEL_EBC_MODDIR)/sel-ebc-boot-mount.service \
pvebc/$(SEL_EBC_MODDIR)/sel-ebc-override-crypttab.service \
pvebc/$(SEL_EBC_MODDIR)/sel-ebc-paes-enforce.service \
pvebc/$(SEL_EBC_MODDIR)/sel-ebc-pvebc.service \

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,17 +15,18 @@ 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" }
[dev-dependencies]
serde_test = "1.0.177"
testing_logger = "0.1.1"

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,15 @@ 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,
/// Secure Execution header v2
Two = 0x200,
}
/// Struct containing all SE-header tags.
///
/// Contains:
@@ -206,7 +214,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 +241,14 @@ 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,
0x200 => SeHdrVersion::Two,
_ => {
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 +261,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 +335,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 +371,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 +396,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,73 @@
//
// 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::bn::BigNumContext;
use openssl::derive::Deriver;
use openssl::ec::{EcGroup, EcKey, EcPoint};
use openssl::error::ErrorStack;
use openssl::hash::{DigestBytes, MessageDigest};
use openssl::md::MdRef;
use openssl::nid::Nid;
use openssl::pkey::{HasPublic, Id, KeyType, 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::openssl_extensions::PkeyEncapsulateContext;
use crate::req::get_pub_ecdh_points;
use crate::request::EcPubKeyCoord;
use crate::Error;
/// Compute ECDH shared secret from public and private keys
///
/// It is expected that the public and private key are with respect to EC-P521.
///
/// Note that the output is the concatenation of the 80-bytes-left-padded x and
/// 80-bytes-left-padded y coordinate.
fn ecdh_shared_secret(
pub_key: &PKeyRef<Public>,
priv_key: &PKeyRef<Private>,
) -> Result<[u8; 160], ErrorStack> {
let pub_key = pub_key.ec_key()?;
let priv_key = priv_key.ec_key()?;
// Verify both keys use the EC-P521 curve (SECP521R1)
assert_eq!(
pub_key.group().curve_name(),
Some(Nid::SECP521R1),
"Public key must use EC-P521 curve"
);
assert_eq!(
priv_key.group().curve_name(),
Some(Nid::SECP521R1),
"Private key must use EC-P521 curve"
);
pub_key.check_key()?;
priv_key.check_key()?;
let group = pub_key.group();
let mut bn_ctx = BigNumContext::new()?;
let mut point = EcPoint::new(group)?;
point.mul2(
group,
pub_key.public_key(),
priv_key.private_key(),
&mut bn_ctx,
)?;
let coord = get_pub_ecdh_points(&point, group)?;
Ok(coord)
}
/// 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]>;
@@ -71,6 +119,7 @@ impl SymKeyType {
}
/// Returns true if the [`SymKeyType`] is an AEAD key
#[must_use]
pub const fn is_aead(&self) -> bool {
self.tag_len().is_some()
}
@@ -159,6 +208,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`]()
///
@@ -205,6 +260,220 @@ pub fn derive_aes256_gcm_key(k1: &PKeyRef<Private>, k2: &PKeyRef<Public>) -> Res
))
}
/// Determines the KeyType of a given PKey by testing against all known key types.
///
/// This function iterates through all known OpenSSL key types and uses the `is_a()`
/// method to identify which type the provided key matches. This is more reliable than
/// using `Id` to `KeyType` conversion, especially for newer key types like ML-KEM
/// that may not have a direct `Id` mapping.
///
/// # Parameters
///
/// * `key` - A reference to the PKey to identify
///
/// # Returns
///
/// * `Some(KeyType)` - If the key matches one of the known key types
/// * `None` - If the key type is not recognized or doesn't match any known types
fn pkey_to_keytype<T>(key: &PKeyRef<T>) -> Option<KeyType> {
const KNOWN_KEY_TYPES: &[KeyType] = &[
KeyType::RSA,
KeyType::RSA_PSS,
KeyType::DSA,
KeyType::DH,
KeyType::EC,
KeyType::HMAC,
KeyType::CMAC,
KeyType::X25519,
KeyType::ED25519,
KeyType::X448,
KeyType::ED448,
KeyType::ML_KEM_512,
KeyType::ML_KEM_768,
KeyType::ML_KEM_1024,
];
KNOWN_KEY_TYPES
.iter()
.find(|&&key_type| key.is_a(key_type))
.copied()
}
fn key_type_str(t: KeyType) -> &'static str {
if t == KeyType::RSA {
"RSA"
} else if t == KeyType::RSA_PSS {
"RSA-PSS"
} else if t == KeyType::DSA {
"DSA"
} else if t == KeyType::DH {
"DH"
} else if t == KeyType::EC {
"EC"
} else if t == KeyType::HMAC {
"HMAC"
} else if t == KeyType::CMAC {
"CMAC"
} else if t == KeyType::X25519 {
"X25519"
} else if t == KeyType::ED25519 {
"ED25519"
} else if t == KeyType::X448 {
"X448"
} else if t == KeyType::ED448 {
"ED448"
} else if t == KeyType::ML_KEM_512 {
"ML-KEM-512"
} else if t == KeyType::ML_KEM_768 {
"ML-KEM-768"
} else if t == KeyType::ML_KEM_1024 {
"ML-KEM-1024"
} else {
"unknown"
}
}
/// Validates that a key matches the expected key type.
///
/// # Errors
///
/// Returns an error if the key doesn't match the expected type.
pub(crate) fn validate_key_type<T: HasPublic>(
key: &PKeyRef<T>,
key_name: &str,
expected_type: KeyType,
) -> Result<()> {
if !key.is_a(expected_type) {
return Err(Error::RetrInvKey {
what: "key type",
kind: key_name.to_string(),
value: pkey_to_keytype(key)
.map(key_type_str)
.unwrap_or("unknown")
.to_string(),
exp: key_type_str(expected_type).to_string(),
});
}
Ok(())
}
/// Validates that a key is an EC key with the specified curve.
///
/// # Errors
///
/// Returns an error if the key is not an EC key or doesn't use the expected curve.
pub(crate) fn validate_ec_key<T: HasPublic>(
key: &PKeyRef<T>,
key_name: &str,
expected_curve: Nid,
) -> Result<()> {
if key.id() != Id::EC {
return Err(Error::RetrInvKey {
what: "key type",
kind: key_name.to_string(),
value: pkey_to_keytype(key)
.map(key_type_str)
.unwrap_or("unknown")
.to_string(),
exp: format!("EC ({})", expected_curve.long_name().unwrap_or("unknown")),
});
}
let ec_key = key.ec_key()?;
if ec_key.group().curve_name() != Some(expected_curve) {
return Err(Error::RetrInvKey {
what: "curve",
kind: key_name.to_string(),
value: ec_key
.group()
.curve_name()
.and_then(|nid| nid.long_name().ok())
.unwrap_or("unknown")
.to_string(),
exp: expected_curve.long_name().unwrap_or("unknown").to_string(),
});
}
Ok(())
}
/// Derive a symmetric AES 256 GCM key from a private target key, a public
/// customer key, and a public ML-KEM target key.
///
/// # Returns
///
/// The derived key and the ML-KEM ciphertext (KC).
///
/// # Errors
///
/// This function will return an error if something went bad in OpenSSL or the
/// wrong key types were used.
pub fn derive_aes256_gcm_key_hybrid(
priv_ecdh_cust_key: &PKeyRef<Private>,
pub_ecdh_target_key: &PKeyRef<Public>,
pub_mlkem_target_key: &PKeyRef<Public>,
) -> Result<(Aes256GcmKey, Vec<u8>)> {
let mut buffer: Vec<u8> = vec![0, 0, 0, 1];
validate_ec_key(priv_ecdh_cust_key, "ECDH customer key", Nid::SECP521R1)?;
validate_ec_key(pub_ecdh_target_key, "ECDH target key", Nid::SECP521R1)?;
validate_key_type(
pub_mlkem_target_key,
"ML-KEM target key",
KeyType::ML_KEM_1024,
)?;
// Derive the ECDH shared secret
let ecdh_derived_secret = ecdh_shared_secret(pub_ecdh_target_key, priv_ecdh_cust_key)?;
assert_eq!(ecdh_derived_secret.as_ref().len(), 160);
buffer.extend_from_slice(ecdh_derived_secret.as_ref());
// Derive the ML-KEM shared secret
let mut ctx = PkeyCtx::new(pub_mlkem_target_key)?;
ctx.encapsulate_init()?;
let (mut ciphertext, mut shared_secret) = (vec![], vec![]);
ctx.encapsulate_to_vec(&mut ciphertext, &mut shared_secret)?;
assert_eq!(ciphertext.len(), 1568);
assert_eq!(shared_secret.len(), 32);
buffer.extend_from_slice(&shared_secret);
// Append the private ECDH customer key
let pub_ecdh_cust_key = EcPubKeyCoord::try_from(priv_ecdh_cust_key)?;
assert_eq!(pub_ecdh_cust_key.as_ref().len(), 160);
buffer.extend_from_slice(pub_ecdh_cust_key.as_ref());
// Append the ciphertext
buffer.extend_from_slice(&ciphertext);
// Append the public ECDH target key
let pub_ecdh_target_key: EcPubKeyCoord = pub_ecdh_target_key.try_into()?;
assert_eq!(pub_ecdh_target_key.as_ref().len(), 160);
buffer.extend_from_slice(pub_ecdh_target_key.as_ref());
// Append the public ML-KEM target key
assert_eq!(pub_mlkem_target_key.raw_public_key()?.len(), 1568);
buffer.extend_from_slice(&pub_mlkem_target_key.raw_public_key()?);
// Append the magic string
const STRING: &str = "PQC Secure Execution with Format-2 Key Slots KS2";
assert_eq!(STRING.len(), 48);
buffer.extend_from_slice(STRING.as_bytes());
// Sanity check
assert_eq!(buffer.len(), 4 + 160 + 32 + 160 + 1568 + 160 + 1568 + 48);
let secr = Confidential::new(buffer);
// Panic: does not panic as SHA256 digest is 32 bytes long
Ok((
Aes256GcmKey::new(
hash(MessageDigest::sha256(), secr.value())?
.as_ref()
.try_into()
.unwrap(),
),
ciphertext,
))
}
/// Generate a random array.
///
/// # Errors
@@ -539,8 +808,166 @@ pub(crate) fn verify_signature<T: HasPublic>(
#[cfg(test)]
mod tests {
use std::sync::Arc;
use std::thread;
use super::*;
use crate::{get_test_asset, test_utils::*, PvCoreError};
use crate::test_utils::*;
use crate::{get_test_asset, PvCoreError};
/// Test that deterministic RNG contexts are thread-local and don't interfere.
///
/// Per OpenSSL documentation (RAND_get0_primary(3)):
/// "The public and private DRBG are thread-local instances, which are used by
/// RAND_bytes() and RAND_priv_bytes(), respectively."
///
/// Reference: <https://docs.openssl.org/3.1/man3/RAND_get0_primary/>
///
/// Note: RAND_set0_public() and RAND_set0_private() require OpenSSL >= 3.1.
#[test]
fn test_deterministic_rng_thread_isolation() {
use std::sync::Barrier;
use openssl::rand::rand_bytes;
// Barriers to synchronize: thread1 installs → thread2 installs → both generate → both
// complete
let barrier_after_t1_install = Arc::new(Barrier::new(2));
let barrier_after_t2_install = Arc::new(Barrier::new(2));
let barrier_after_rand_bytes = Arc::new(Barrier::new(2));
let barrier1_clone = Arc::clone(&barrier_after_t1_install);
let barrier2_clone = Arc::clone(&barrier_after_t2_install);
let barrier3_clone = Arc::clone(&barrier_after_rand_bytes);
// Thread 1: Install deterministic RNG with specific entropy
let thread1 = thread::spawn(move || {
let entropy = [0x42u8; 4096];
let nonce = [0x24u8; 48];
// Install thread-local deterministic RNG
let _rng = DeterministicTestRandGuard::install(&entropy, &nonce).unwrap();
// Signal thread2 that we've installed our RNG
barrier1_clone.wait();
// Wait for thread2 to install its RNG
barrier2_clone.wait();
// Now generate bytes while thread2 also has its RNG installed
let mut buf = [0u8; 32];
rand_bytes(&mut buf).unwrap();
// Wait for thread2 to also complete rand_bytes
barrier3_clone.wait();
buf
});
// Thread 2: Install different deterministic RNG after thread1
let thread2 = thread::spawn(move || {
// Wait for thread1 to install its RNG first
barrier_after_t1_install.wait();
// Now install our own thread-local deterministic RNG with different entropy
let entropy = [0xAAu8; 4096];
let nonce = [0x55u8; 48];
let _rng = DeterministicTestRandGuard::install(&entropy, &nonce).unwrap();
// Signal thread1 that we've installed our RNG
barrier_after_t2_install.wait();
// Generate bytes with our different entropy (concurrently with thread1)
let mut buf = [0u8; 32];
rand_bytes(&mut buf).unwrap();
// Wait for thread1 to also complete rand_bytes
barrier_after_rand_bytes.wait();
buf
});
let t1_buf = thread1.join().unwrap();
let t2_buf = thread2.join().unwrap();
// Expected deterministic values for thread 1 (entropy=0x42, nonce=0x24)
let expected_t1: [u8; 32] = [
66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66,
66, 66, 66, 66, 66, 66, 66, 66, 66, 66,
];
// Expected deterministic values for thread 2 (entropy=0xAA, nonce=0x55)
let expected_t2: [u8; 32] = [
170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170,
170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170,
];
// Verify each thread produced its expected deterministic output
assert_eq!(
t1_buf, expected_t1,
"Thread 1 should produce deterministic output"
);
assert_eq!(
t2_buf, expected_t2,
"Thread 2 should produce deterministic output"
);
// Also verify they are different (proves thread-local isolation)
assert_ne!(
t1_buf, t2_buf,
"Different thread-local entropy should produce different output"
);
}
/// Test that the original RNG is properly restored after DeterministicTestRandGuard is dropped
#[test]
fn test_deterministic_rng_restoration() {
use openssl::rand::rand_bytes;
// Generate random bytes with system RNG before installing deterministic RNG
let mut before_buf = [0u8; 32];
rand_bytes(&mut before_buf).unwrap();
let deterministic_buf = {
let entropy = [0x42u8; 4096];
let nonce = [0x24u8; 48];
// Install deterministic RNG
let _rng = DeterministicTestRandGuard::install(&entropy, &nonce).unwrap();
// Generate deterministic bytes
let mut buf = [0u8; 32];
rand_bytes(&mut buf).unwrap();
// Expected deterministic output
let expected: [u8; 32] = [
66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66,
66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66,
];
assert_eq!(buf, expected, "Should produce deterministic output");
buf
// _rng is dropped here, should restore original RNG
};
// Generate random bytes again with restored system RNG
let mut after_buf = [0u8; 32];
rand_bytes(&mut after_buf).unwrap();
// The system RNG should produce different random values each time
// (extremely unlikely to match the deterministic output)
assert_ne!(
after_buf, deterministic_buf,
"After restoration, system RNG should produce different random values"
);
// Also verify that before and after are different (system RNG produces random values)
// Note: This could theoretically fail with probability 1/2^256, but that's negligible
assert_ne!(
before_buf, after_buf,
"System RNG should produce different random values on each call"
);
}
#[test]
fn sign_ec() {
@@ -593,6 +1020,112 @@ mod tests {
assert_eq!(&calc_key, &exp_key);
}
#[test]
fn derive_aes256_gcm_key_hybrid() {
let (cust_key, host_key_1, host_key_2) = get_test_keys_hybrid();
let entropy = [0x5au8; 4096];
let nonce = [0xa5u8; 48];
let _rng = DeterministicTestRandGuard::install(&entropy, &nonce).unwrap();
let exp_key: Aes256GcmKey = [
197, 167, 157, 112, 186, 112, 72, 125, 192, 219, 168, 132, 178, 167, 249, 123, 149, 3,
151, 166, 162, 66, 120, 39, 41, 230, 143, 54, 172, 10, 200, 143,
]
.into();
let exp_kc = [
54, 117, 96, 77, 148, 147, 170, 100, 34, 177, 95, 7, 35, 243, 145, 115, 7, 87, 178, 9,
169, 99, 193, 99, 244, 195, 23, 78, 11, 153, 221, 196, 5, 192, 253, 192, 86, 49, 194,
236, 43, 69, 183, 125, 166, 87, 158, 188, 13, 152, 19, 6, 253, 29, 194, 0, 101, 236,
28, 171, 3, 236, 53, 186, 191, 109, 7, 83, 220, 93, 126, 29, 19, 203, 201, 39, 59, 7,
131, 51, 81, 73, 254, 69, 105, 185, 214, 179, 155, 194, 189, 122, 106, 130, 249, 48, 4,
33, 245, 170, 163, 4, 223, 208, 138, 224, 203, 119, 105, 59, 187, 153, 235, 90, 79,
127, 29, 136, 230, 142, 78, 83, 27, 131, 58, 126, 76, 53, 129, 20, 85, 108, 86, 64,
244, 90, 84, 177, 239, 105, 90, 41, 118, 189, 88, 174, 224, 216, 29, 10, 123, 81, 212,
203, 197, 120, 20, 190, 3, 45, 37, 194, 208, 249, 232, 221, 67, 10, 62, 121, 143, 169,
227, 165, 17, 30, 85, 223, 44, 141, 114, 142, 105, 119, 187, 41, 46, 8, 6, 17, 29, 165,
117, 254, 92, 174, 231, 25, 117, 69, 112, 216, 80, 73, 185, 54, 50, 119, 145, 220, 174,
26, 105, 81, 114, 210, 144, 148, 109, 218, 64, 78, 231, 196, 229, 88, 46, 128, 106,
125, 204, 58, 184, 127, 193, 207, 86, 163, 98, 164, 57, 242, 29, 59, 251, 227, 185, 60,
18, 68, 74, 47, 203, 61, 164, 78, 245, 100, 87, 148, 210, 97, 158, 252, 79, 78, 50,
143, 35, 231, 215, 211, 75, 133, 214, 227, 140, 27, 21, 46, 221, 84, 89, 165, 161, 227,
46, 117, 193, 254, 190, 237, 130, 28, 57, 52, 14, 235, 154, 115, 172, 185, 67, 116, 34,
242, 158, 209, 0, 126, 196, 93, 224, 29, 246, 145, 65, 73, 185, 196, 4, 107, 124, 241,
157, 230, 168, 244, 238, 84, 188, 173, 17, 238, 26, 161, 24, 176, 229, 226, 33, 244,
167, 41, 107, 156, 29, 226, 248, 64, 146, 191, 210, 234, 76, 144, 219, 92, 136, 173,
241, 98, 0, 71, 135, 214, 196, 116, 63, 243, 73, 71, 130, 171, 86, 204, 149, 69, 164,
20, 177, 122, 95, 226, 95, 126, 106, 160, 59, 97, 137, 8, 73, 113, 189, 172, 24, 114,
60, 62, 249, 193, 3, 99, 34, 153, 42, 238, 77, 181, 80, 185, 223, 39, 8, 44, 215, 119,
214, 30, 136, 19, 215, 35, 184, 69, 94, 10, 170, 179, 51, 183, 105, 237, 237, 48, 199,
122, 159, 87, 183, 71, 230, 87, 102, 77, 81, 116, 28, 126, 195, 72, 50, 157, 223, 243,
83, 36, 16, 168, 111, 209, 132, 12, 96, 56, 140, 57, 144, 75, 253, 119, 123, 168, 2,
79, 214, 121, 80, 154, 93, 235, 222, 130, 181, 166, 97, 51, 106, 21, 138, 224, 8, 144,
223, 162, 152, 183, 6, 80, 64, 144, 21, 155, 56, 255, 108, 248, 125, 196, 46, 99, 119,
94, 104, 63, 46, 15, 165, 30, 98, 75, 212, 193, 116, 151, 189, 65, 42, 83, 253, 183,
41, 195, 45, 206, 178, 66, 36, 215, 197, 105, 236, 79, 91, 135, 164, 71, 187, 199, 200,
150, 226, 182, 254, 6, 234, 109, 3, 17, 116, 249, 44, 211, 184, 61, 189, 44, 181, 249,
8, 58, 230, 236, 8, 188, 14, 178, 100, 120, 250, 29, 1, 204, 158, 46, 161, 39, 66, 76,
42, 114, 149, 160, 31, 87, 254, 181, 224, 17, 162, 163, 99, 11, 34, 149, 50, 203, 205,
224, 38, 18, 233, 161, 49, 7, 151, 63, 81, 68, 71, 174, 49, 22, 143, 93, 50, 0, 154,
152, 178, 134, 147, 152, 118, 196, 241, 233, 67, 102, 149, 179, 213, 176, 118, 64, 172,
143, 134, 196, 232, 154, 110, 129, 155, 159, 103, 117, 202, 11, 35, 75, 104, 5, 11,
160, 147, 174, 49, 248, 45, 247, 16, 7, 64, 209, 255, 170, 243, 242, 40, 158, 94, 239,
194, 225, 113, 24, 90, 243, 73, 137, 217, 175, 130, 50, 133, 139, 250, 145, 190, 76,
151, 183, 30, 86, 146, 59, 171, 214, 211, 135, 203, 192, 42, 189, 90, 47, 152, 132,
168, 252, 175, 71, 234, 118, 207, 161, 176, 254, 189, 54, 174, 160, 178, 158, 133, 122,
63, 75, 95, 201, 55, 139, 2, 208, 232, 110, 74, 201, 196, 135, 244, 156, 87, 208, 101,
203, 121, 187, 16, 106, 80, 120, 165, 44, 147, 182, 114, 173, 186, 185, 255, 99, 85,
88, 26, 27, 43, 203, 176, 207, 88, 20, 253, 169, 210, 168, 109, 75, 234, 239, 8, 243,
244, 65, 164, 193, 255, 240, 215, 54, 158, 188, 93, 93, 54, 46, 77, 152, 78, 174, 154,
67, 248, 24, 235, 172, 240, 83, 224, 17, 100, 217, 15, 172, 176, 46, 85, 107, 105, 127,
147, 158, 202, 255, 145, 237, 84, 223, 100, 214, 38, 133, 169, 112, 227, 138, 220, 125,
72, 197, 5, 227, 94, 245, 42, 70, 33, 209, 243, 70, 229, 37, 118, 214, 147, 43, 87, 39,
241, 107, 26, 169, 28, 72, 223, 133, 145, 44, 248, 213, 52, 127, 250, 99, 193, 115,
113, 147, 89, 112, 237, 199, 208, 36, 155, 106, 144, 73, 249, 8, 116, 198, 107, 120,
233, 145, 11, 155, 178, 7, 66, 157, 255, 206, 128, 155, 233, 111, 148, 194, 214, 238,
252, 230, 96, 119, 30, 37, 73, 133, 129, 87, 185, 149, 251, 156, 17, 8, 83, 106, 207,
98, 203, 100, 39, 199, 127, 253, 59, 37, 121, 161, 216, 146, 6, 178, 183, 243, 191, 91,
106, 243, 132, 111, 216, 163, 87, 210, 197, 173, 146, 65, 131, 194, 96, 70, 6, 7, 192,
45, 173, 71, 44, 134, 122, 60, 173, 208, 238, 22, 187, 208, 212, 51, 191, 185, 174, 3,
125, 28, 134, 216, 209, 4, 224, 199, 16, 15, 56, 70, 188, 216, 92, 24, 96, 57, 125,
138, 151, 73, 254, 245, 106, 53, 4, 150, 74, 43, 42, 4, 157, 238, 125, 168, 41, 224,
22, 249, 45, 117, 32, 180, 161, 41, 39, 180, 96, 24, 2, 102, 57, 116, 34, 75, 90, 72,
134, 176, 2, 196, 59, 143, 182, 201, 117, 178, 153, 81, 108, 167, 122, 139, 71, 197,
55, 114, 60, 161, 130, 14, 29, 79, 152, 55, 136, 62, 190, 228, 202, 53, 126, 4, 173,
99, 28, 190, 224, 255, 134, 123, 166, 162, 244, 55, 26, 81, 120, 207, 193, 10, 103,
153, 215, 220, 12, 71, 67, 217, 154, 212, 44, 200, 232, 0, 178, 39, 44, 22, 7, 14, 215,
183, 192, 104, 51, 46, 93, 102, 195, 65, 9, 191, 241, 237, 151, 5, 64, 103, 228, 162,
41, 123, 29, 5, 80, 203, 198, 234, 230, 107, 53, 60, 58, 253, 47, 152, 22, 77, 81, 86,
215, 132, 152, 135, 6, 218, 46, 92, 192, 218, 198, 234, 76, 178, 25, 203, 48, 61, 76,
215, 96, 6, 49, 195, 37, 225, 10, 175, 222, 186, 133, 63, 50, 236, 215, 247, 17, 199,
8, 134, 64, 246, 194, 167, 105, 15, 57, 62, 50, 51, 243, 192, 242, 122, 11, 46, 202,
47, 10, 71, 153, 212, 226, 38, 12, 90, 150, 154, 152, 233, 7, 172, 111, 185, 160, 246,
0, 166, 113, 90, 37, 203, 166, 43, 53, 255, 211, 127, 139, 73, 7, 10, 164, 1, 168, 223,
87, 127, 43, 47, 87, 68, 84, 247, 223, 108, 108, 113, 36, 17, 50, 98, 236, 48, 10, 219,
182, 107, 240, 198, 207, 20, 178, 9, 142, 14, 93, 163, 166, 147, 38, 176, 172, 156, 73,
174, 238, 175, 231, 130, 159, 51, 128, 76, 34, 37, 138, 19, 3, 59, 71, 78, 144, 238,
226, 214, 188, 27, 42, 142, 245, 238, 131, 190, 211, 240, 41, 122, 69, 124, 171, 75,
115, 45, 144, 133, 176, 19, 81, 125, 230, 149, 235, 159, 6, 155, 195, 119, 62, 140, 50,
52, 209, 124, 3, 93, 232, 20, 130, 138, 110, 60, 183, 177, 161, 52, 114, 91, 19, 211,
156, 185, 202, 200, 36, 103, 253, 113, 45, 245, 177, 238, 43, 144, 38, 221, 0, 102, 50,
255, 20, 154, 56, 156, 155, 92, 157, 57, 209, 77, 84, 88, 24, 116, 116, 54, 213, 222,
76, 212, 193, 168, 216, 247, 125, 135, 114, 226, 128, 140, 250, 103, 82, 215, 238, 32,
74, 252, 45, 224, 23, 95, 126, 124, 135, 124, 128, 53, 203, 40, 65, 222, 8, 83, 178,
211, 64, 141, 64, 98, 188, 134, 100, 65, 166, 52, 249, 1, 206, 58, 55, 195, 23, 218,
239, 41, 73, 88, 113, 148, 132, 209, 93, 37, 205, 58, 92, 14, 1, 133, 168, 162, 192,
147, 70, 167, 101, 170, 152, 159, 0, 212, 26, 97, 49, 43, 217, 173, 38, 215, 136, 26,
208, 244, 19, 83, 207, 38, 224, 254, 92, 169, 219, 236, 172, 49, 55, 98, 55, 15, 187,
173, 114, 99, 130, 211, 78, 168, 221, 209, 250, 88, 189, 17, 186, 172, 129, 56, 90,
238, 120, 23, 176, 87, 133, 81, 244, 29, 2, 215, 34, 88, 247, 231, 167, 56,
];
let (exc_key, kc) =
super::derive_aes256_gcm_key_hybrid(&cust_key, &host_key_1, &host_key_2).unwrap();
assert_eq!(exc_key, exp_key);
assert_eq!(kc, exp_kc);
}
#[test]
fn hkdf_rfc_5869() {
use openssl::md::Md;
@@ -766,4 +1299,94 @@ mod tests {
}))
));
}
#[test]
fn validate_ec_key_valid() {
let (cust_key, host_key) = get_test_keys();
// Both test keys are SECP521R1 EC keys
assert!(validate_ec_key(&cust_key, "customer key", Nid::SECP521R1).is_ok());
assert!(validate_ec_key(&host_key, "host key", Nid::SECP521R1).is_ok());
}
#[test]
fn validate_ec_key_wrong_curve() {
let (cust_key, _) = get_test_keys();
// Test key is SECP521R1, but we expect SECP384R1
let result = validate_ec_key(&cust_key, "customer key", Nid::SECP384R1);
assert!(result.is_err());
if let Err(Error::RetrInvKey {
what,
kind,
value,
exp,
}) = result
{
assert_eq!(what, "curve");
assert_eq!(kind, "customer key");
assert_eq!(value, "secp521r1");
assert_eq!(exp, "secp384r1");
} else {
panic!("Expected RetrInvKey error");
}
}
#[test]
fn validate_ec_key_not_ec() {
let keypair = crate::get_test_asset!("keys/rsa2048key.pem");
let keypair = PKey::private_key_from_pem(keypair).unwrap();
// RSA key is not an EC key
let result = validate_ec_key(&keypair, "EC key", Nid::SECP521R1);
assert!(result.is_err());
if let Err(Error::RetrInvKey {
what,
kind,
value,
exp,
}) = result
{
assert_eq!(what, "key type");
assert_eq!(kind, "EC key");
assert_eq!(value, "RSA");
assert_eq!(exp, "EC (secp521r1)");
} else {
panic!("Expected RetrInvKey error");
}
}
#[test]
fn validate_mlkem_key_valid() {
let (_, _, mlkem_key) = get_test_keys_hybrid();
// The third key from get_test_keys_hybrid is ML-KEM-1024
assert!(validate_key_type(&mlkem_key, "ML-KEM key", KeyType::ML_KEM_1024).is_ok());
}
#[test]
fn validate_mlkem_key_wrong_type() {
let (ec_key, _) = get_test_keys();
// EC key is not ML-KEM
let result = validate_key_type(&ec_key, "EC key", KeyType::ML_KEM_1024);
assert!(result.is_err());
if let Err(Error::RetrInvKey {
what,
kind,
value,
exp,
}) = result
{
assert_eq!(what, "key type");
assert_eq!(kind, "EC key");
assert_eq!(value, "EC");
assert_eq!(exp, "ML-KEM-1024");
} else {
panic!("Expected RetrInvKey error");
}
}
}

View File

@@ -64,8 +64,11 @@ pub enum Error {
#[error("No user-key for verification provided and user-data is signed")]
BinAsrcbNoUserDataSgnKey,
#[error("Input does not contain an add-secret request version 1")]
BinAsrcbInvVersion,
#[error("Input contains an unknown add-secret request version {0}")]
BinAsrcbInvVersion(u32),
#[error("Unsupported add-secret request version: {0}")]
UnsupportedAddSecretVersion(u32),
#[error("Provided user-data key type ({key}) does not match with the user-data ({kind})")]
AsrcbUserDataKeyMismatch { key: String, kind: UserDataType },
@@ -84,17 +87,20 @@ pub enum Error {
#[error("The provided host key document in {0} contains no certificate!")]
NoHkdInFile(String),
#[error("The provided host key document in {0} contains the wrong number of keys!")]
WrongNumberOfKeys(String),
#[error("Invalid input size ({0}) for boot hdr")]
InvBootHdrSize(usize),
#[error("Input does not contain an attestation request")]
NoArcb,
#[error("The attestation request has an unknown version (.0)")]
#[error("The attestation request has an unknown version {0}")]
BinArcbInvVersion(u32),
#[error(
"The attestation request encrypted sice is to0 small (.0). Request probably tampered with."
"The attestation request encrypted sice is too small {0}. Request probably tampered with."
)]
BinArcbSeaSmall(u32),
@@ -135,6 +141,12 @@ pub enum Error {
#[error("Unsupported cipher: {:?}", .0.as_raw())]
UnsupportedCipher(Nid),
#[error("{}", .0)]
InvalidHkd(String),
#[error("All host keys must use the same version (all hybrid or all non-hybrid)")]
MixedHostkeyVersions,
}
// used in macros
@@ -173,8 +185,16 @@ pub enum HkdVerifyErrorType {
IssuerMismatch,
#[error("No CRL distribution points found")]
NoCrlDP,
#[error("CRL distribution point uses unsupported protocol (only HTTP/HTTPS allowed)")]
InvalidCrlProtocol,
#[error("The IBM Z signing key could not be verified. Error occurred at level {1}")]
IbmSignInvalid(#[source] openssl::x509::X509VerifyResult, u32),
#[error("Too many redirections during CRL download")]
TooManyRedirectionsCrlDownload,
#[error("CRL download exceeds maximum file size of {} MiB", .0 / (1024 * 1024))]
CrlDownloadTooLarge(u64),
#[error("CRL download failed")]
CrlDownloadFailed,
}
macro_rules! bail_hkd_verify {

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,25 @@ 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, SeHdrVersion, 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, HybridPKey, Keyslot, KeyslotV1, KeyslotV2, 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 +116,6 @@ pub mod request {
}
pub use pv_core::request::*;
pub use pv_core::PolicyReference;
}
@@ -128,11 +123,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.
#![cfg_attr(not(test), 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,10 @@
mod akid;
mod bio;
mod crl;
mod ml_kem;
mod stackable_crl;
pub use akid::*;
pub use bio::*;
pub use crl::*;
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);
}
}

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

@@ -0,0 +1,157 @@
// 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.
pub(crate) 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),
}

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

@@ -0,0 +1,93 @@
// SPDX-License-Identifier: MIT
//
// Copyright IBM Corp.
//! Request header structure for UV requests.
use pv_core::request::{RequestMagic, RequestVersion};
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(super) fn new(
rqvn: RequestVersion,
rql: u32,
iv: [u8; 12],
nks: u8,
sea: u32,
magic: Option<RequestMagic>,
) -> 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);
}
}

342
rust/pv/src/req/hostkey.rs Normal file
View File

@@ -0,0 +1,342 @@
// SPDX-License-Identifier: MIT
//
// Copyright IBM Corp.
//! Host key types for UV requests
use openssl::nid::Nid;
use openssl::pkey::{KeyType, PKey, PKeyRef, Public};
use crate::crypto::{validate_ec_key, validate_key_type};
pub use crate::error::Result;
/// Hybrid public key (ECDH and ML-KEM)
#[derive(Clone, Debug)]
pub struct HybridPKey {
/// ECDH public key
pub(super) ec_key: PKey<Public>,
/// ML-KEM public key
pub(super) mlkem_key: PKey<Public>,
}
impl HybridPKey {
/// Creates a new hybrid public key with validation.
///
/// # Parameters
/// - `ec_key`: ECDH public key (must be SECP521R1)
/// - `mlkem_key`: ML-KEM public key (must be ML-KEM-1024)
///
/// # Errors
/// Returns an error if:
/// - EC key is not SECP521R1 curve
/// - ML-KEM key is not ML-KEM-1024
pub fn new(ec_key: PKey<Public>, mlkem_key: PKey<Public>) -> Result<Self> {
validate_ec_key(&ec_key, "ECDH key", Nid::SECP521R1)?;
validate_key_type(&mlkem_key, "ML-KEM key", KeyType::ML_KEM_1024)?;
Ok(Self { ec_key, mlkem_key })
}
/// Returns a reference to the EC key
pub fn ec_key(&self) -> &PKeyRef<Public> {
&self.ec_key
}
/// Returns a reference to the ML-KEM key
pub fn mlkem_key(&self) -> &PKeyRef<Public> {
&self.mlkem_key
}
}
impl AsRef<HybridPKey> for HybridPKey {
fn as_ref(&self) -> &HybridPKey {
self
}
}
/// Versioned host keys container
#[non_exhaustive]
#[derive(Clone, Debug)]
pub enum HostKey {
/// ECDH public key
V1(PKey<Public>),
/// Hybrid public key (ECDH and ML-KEM)
V2(HybridPKey),
}
impl HostKey {
/// Return the ECDH public key
pub fn ec_key(&self) -> Option<&PKeyRef<Public>> {
Some(match self {
HostKey::V1(ec_key) => ec_key,
HostKey::V2(hybrid) => hybrid.ec_key(),
})
}
/// Return the ML-KEM public key
pub fn mlkem_key(&self) -> Option<&PKeyRef<Public>> {
match self {
HostKey::V1(_) => None,
HostKey::V2(hybrid) => Some(hybrid.mlkem_key()),
}
}
/// Test if the hostkey is hybrid
#[must_use]
pub fn is_hybrid(&self) -> bool {
matches!(self, HostKey::V2(_))
}
}
impl AsRef<HostKey> for HostKey {
fn as_ref(&self) -> &HostKey {
self
}
}
#[cfg(test)]
mod tests {
use openssl::ec::{EcGroup, EcKey};
use openssl::pkey::Private;
use super::*;
use crate::openssl_extensions::generate_ml_kem;
use crate::test_utils::get_test_key_and_cert_hybrid;
use crate::Error;
fn to_public_key(key: &PKey<Private>) -> Result<PKey<Public>> {
let der = key.public_key_to_der()?;
Ok(PKey::public_key_from_der(&der)?)
}
#[test]
fn test_hostkey_v1_variant() {
let (_, ec_key) = crate::test_utils::get_test_key_and_cert();
let hostkey = HostKey::V1(ec_key.public_key().unwrap());
assert!(!hostkey.is_hybrid(), "V1 HostKey should not be hybrid");
assert!(matches!(hostkey, HostKey::V1(_)));
}
#[test]
fn test_hostkey_v2_variant() {
let (_, ec_key, mlkem_key) = get_test_key_and_cert_hybrid();
let hybrid = HybridPKey::new(
ec_key.public_key().unwrap(),
mlkem_key.public_key().unwrap(),
)
.unwrap();
let hostkey = HostKey::V2(hybrid);
assert!(hostkey.is_hybrid(), "V2 HostKey should be hybrid");
assert!(matches!(hostkey, HostKey::V2(_)));
}
#[test]
fn test_hostkey_ec_key_access() {
let (_, ec_key, mlkem_key) = get_test_key_and_cert_hybrid();
let hybrid = HybridPKey::new(
ec_key.public_key().unwrap(),
mlkem_key.public_key().unwrap(),
)
.unwrap();
let v1_key = HostKey::V1(ec_key.public_key().unwrap());
let v2_key = HostKey::V2(hybrid);
assert!(v1_key.ec_key().unwrap().public_key_to_der().is_ok());
assert!(v2_key.ec_key().unwrap().public_key_to_der().is_ok());
}
#[test]
fn test_hostkey_v2_mlkem_key_access() {
let (_, ec_key, mlkem_key) = get_test_key_and_cert_hybrid();
let hybrid = HybridPKey::new(
ec_key.public_key().unwrap(),
mlkem_key.public_key().unwrap(),
)
.unwrap();
let hostkey = HostKey::V2(hybrid);
assert!(hostkey.mlkem_key().unwrap().public_key_to_der().is_ok());
}
#[test]
fn test_hostkey_v1_has_no_mlkem_key() {
let (_, ec_key) = crate::test_utils::get_test_key_and_cert();
let hostkey = HostKey::V1(ec_key.public_key().unwrap());
assert!(hostkey.mlkem_key().is_none());
}
#[test]
fn test_hybrid_public_key_structure() {
let (_, ec_key, mlkem_key) = get_test_key_and_cert_hybrid();
let hybrid = HybridPKey::new(
ec_key.public_key().unwrap(),
mlkem_key.public_key().unwrap(),
)
.unwrap();
// Verify both components are present and valid
assert!(!hybrid.ec_key().public_key_to_der().unwrap().is_empty());
assert!(!hybrid.mlkem_key().public_key_to_der().unwrap().is_empty());
}
#[test]
fn test_hybrid_pkey_invalid_ec_curve() {
// Generate a P-256 key instead of P-521
let group = EcGroup::from_curve_name(Nid::X9_62_PRIME256V1).unwrap();
let wrong_ec_key = PKey::from_ec_key(EcKey::generate(&group).unwrap()).unwrap();
let (_, _, mlkem_key) = get_test_key_and_cert_hybrid();
let result = HybridPKey::new(
to_public_key(&wrong_ec_key).unwrap(),
mlkem_key.public_key().unwrap(),
);
assert!(result.is_err(), "Should reject EC key with wrong curve");
if let Err(Error::RetrInvKey {
what,
kind,
value,
exp,
}) = result
{
assert_eq!(what, "curve");
assert_eq!(kind, "ECDH key");
assert_eq!(value, "prime256v1");
assert_eq!(exp, "secp521r1");
} else {
panic!("Expected RetrInvKey error for wrong curve");
}
}
#[test]
fn test_hybrid_pkey_invalid_mlkem_type() {
let (_, ec_key, _) = get_test_key_and_cert_hybrid();
// Generate ML-KEM-512 instead of ML-KEM-1024
let wrong_mlkem_key = generate_ml_kem(KeyType::ML_KEM_512).unwrap();
let result = HybridPKey::new(
ec_key.public_key().unwrap(),
to_public_key(&wrong_mlkem_key).unwrap(),
);
assert!(result.is_err(), "Should reject ML-KEM key with wrong type");
if let Err(Error::RetrInvKey {
what,
kind,
value,
exp,
}) = result
{
assert_eq!(what, "key type");
assert_eq!(kind, "ML-KEM key");
assert_eq!(value, "ML-KEM-512");
assert_eq!(exp, "ML-KEM-1024");
} else {
panic!("Expected RetrInvKey error for wrong ML-KEM type");
}
}
#[test]
fn test_hybrid_pkey_clone() {
let (_, ec_key, mlkem_key) = get_test_key_and_cert_hybrid();
let hybrid = HybridPKey::new(
ec_key.public_key().unwrap(),
mlkem_key.public_key().unwrap(),
)
.unwrap();
let cloned = hybrid.clone();
// Verify both original and clone have valid keys
assert!(!hybrid.ec_key().public_key_to_der().unwrap().is_empty());
assert!(!hybrid.mlkem_key().public_key_to_der().unwrap().is_empty());
assert!(!cloned.ec_key().public_key_to_der().unwrap().is_empty());
assert!(!cloned.mlkem_key().public_key_to_der().unwrap().is_empty());
// Verify the keys are equivalent
assert_eq!(
hybrid.ec_key().public_key_to_der().unwrap(),
cloned.ec_key().public_key_to_der().unwrap()
);
assert_eq!(
hybrid.mlkem_key().public_key_to_der().unwrap(),
cloned.mlkem_key().public_key_to_der().unwrap()
);
}
#[test]
fn test_hostkey_clone() {
let (_, ec_key, mlkem_key) = get_test_key_and_cert_hybrid();
let hybrid = HybridPKey::new(
ec_key.public_key().unwrap(),
mlkem_key.public_key().unwrap(),
)
.unwrap();
// Test cloning V1
let v1_key = HostKey::V1(ec_key.public_key().unwrap());
let v1_cloned = v1_key.clone();
assert!(!v1_cloned.is_hybrid());
assert_eq!(
v1_key.ec_key().unwrap().public_key_to_der().unwrap(),
v1_cloned.ec_key().unwrap().public_key_to_der().unwrap()
);
// Test cloning V2
let v2_key = HostKey::V2(hybrid);
let v2_cloned = v2_key.clone();
assert!(v2_cloned.is_hybrid());
assert_eq!(
v2_key.ec_key().unwrap().public_key_to_der().unwrap(),
v2_cloned.ec_key().unwrap().public_key_to_der().unwrap()
);
assert_eq!(
v2_key.mlkem_key().unwrap().public_key_to_der().unwrap(),
v2_cloned.mlkem_key().unwrap().public_key_to_der().unwrap()
);
}
#[test]
fn test_hybrid_pkey_as_ref() {
let (_, ec_key, mlkem_key) = get_test_key_and_cert_hybrid();
let hybrid = HybridPKey::new(
ec_key.public_key().unwrap(),
mlkem_key.public_key().unwrap(),
)
.unwrap();
let hybrid_ref: &HybridPKey = hybrid.as_ref();
assert!(!hybrid_ref.ec_key().public_key_to_der().unwrap().is_empty());
assert!(!hybrid_ref
.mlkem_key()
.public_key_to_der()
.unwrap()
.is_empty());
}
#[test]
fn test_hostkey_as_ref() {
let (_, ec_key, mlkem_key) = get_test_key_and_cert_hybrid();
let hybrid = HybridPKey::new(
ec_key.public_key().unwrap(),
mlkem_key.public_key().unwrap(),
)
.unwrap();
let v1_key = HostKey::V1(ec_key.public_key().unwrap());
let v1_ref: &HostKey = v1_key.as_ref();
assert!(!v1_ref.is_hybrid());
let v2_key = HostKey::V2(hybrid);
let v2_ref: &HostKey = v2_key.as_ref();
assert!(v2_ref.is_hybrid());
}
}

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

@@ -0,0 +1,467 @@
// SPDX-License-Identifier: MIT
//
// Copyright IBM Corp.
//! IBM Z Host key-slot implementations.
use openssl::hash::{DigestBytes, MessageDigest};
use openssl::pkey::{PKey, PKeyRef, Private, Public};
use crate::crypto::{derive_aes256_gcm_key, derive_aes256_gcm_key_hybrid, encrypt_aead, hash};
use crate::req::{EcPubKeyCoord, Encrypt, HostKey, HybridPKey};
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(())
}
}
/// IBM Z hybrid (V2) Host key-slot
///
/// Layout in binary format:
/// ```none
/// _______________________________________________________________
/// | Public Host Key Hash (64) |
/// | Wrapped(=Encrypted) Request Protection Key(32) |
/// | Key Slot Tag (16) |
/// | ML-KEM Ciphertext (1568) |
/// |_____________________________________________________________|
/// ```
#[derive(Debug, Clone)]
pub struct KeyslotV2 {
ec_hostkey: PKey<Public>,
mlkem_hostkey: PKey<Public>,
}
impl KeyslotV2 {
/// Size of a hybrid host-key hash
pub const PHKH_SIZE: u32 = 0x40;
/// Size of a complete V2 keyslot in bytes
pub const SIZE: usize = 1680;
/// Creates a new HybridKeyslot from the provided hybrid public key
pub fn new(hostkey: HybridPKey) -> Self {
let HybridPKey { ec_key, mlkem_key } = hostkey;
Self {
ec_hostkey: ec_key,
mlkem_hostkey: mlkem_key,
}
}
/// calculates the sha512 of this hybrid key
pub fn sha512(&self) -> Result<DigestBytes> {
let mut phk_buf = Vec::<u8>::with_capacity(160 + 1568);
let ec_phk: EcPubKeyCoord = self.ec_hostkey.as_ref().try_into()?;
phk_buf.extend_from_slice(ec_phk.as_ref());
phk_buf.extend_from_slice(&self.mlkem_hostkey.raw_public_key()?);
assert_eq!(phk_buf.len(), 160 + 1568);
let hash = hash(MessageDigest::sha512(), &phk_buf)?;
assert_eq!(hash.len(), 64);
Ok(hash)
}
/// 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.
pub fn encrypt(&self, secret: &[u8], priv_key: &PKeyRef<Private>) -> Result<Vec<u8>> {
let mut res = Vec::with_capacity(1680);
self.encrypt_to(secret, priv_key, &mut res)?;
Ok(res)
}
/// 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 HybridKeyslot.
///
/// # 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, ciphertext) =
derive_aes256_gcm_key_hybrid(priv_key, &self.ec_hostkey, &self.mlkem_hostkey)?;
let mut wrpk_and_kst =
encrypt_aead(&derived_key.into(), &[0; 12], &[], prot_key)?.into_buf();
assert_eq!(wrpk_and_kst.len(), 48);
to.reserve(1680);
let hash = self.sha512()?;
assert_eq!(hash.len(), 64);
to.extend_from_slice(&hash);
to.append(&mut wrpk_and_kst);
assert_eq!(ciphertext.len(), 1568);
to.extend_from_slice(&ciphertext);
Ok(())
}
}
/// Versioned keyslot container
#[non_exhaustive]
#[derive(Debug, Clone)]
pub enum Keyslot {
/// V1 key-slots with ECDH keys
V1(KeyslotV1),
/// V2 key-slots with hybrid ECDH/ML-KEM keys
V2(KeyslotV2),
}
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)),
HostKey::V2(key) => Keyslot::V2(KeyslotV2::new(key)),
}
}
/// Return the public host key hash size for the given version of the key-slot in bytes
#[must_use]
pub fn phkh_size(&self) -> u32 {
match self {
Keyslot::V1(_) => KeyslotV1::PHKH_SIZE,
Keyslot::V2(_) => KeyslotV2::PHKH_SIZE,
}
}
/// Return the size of the key-slot in bytes
#[must_use]
pub fn size(&self) -> usize {
match self {
Keyslot::V1(_) => KeyslotV1::SIZE,
Keyslot::V2(_) => KeyslotV2::SIZE,
}
}
/// Return whether the key-slot uses hybrid keys
#[must_use]
pub fn is_hybrid(&self) -> bool {
match self {
Keyslot::V1(_) => false,
Keyslot::V2(_) => true,
}
}
}
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),
Keyslot::V2(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))
}
}
impl From<HybridPKey> for Keyslot {
fn from(key: HybridPKey) -> Self {
Keyslot::V2(KeyslotV2::new(key))
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::get_test_asset;
use crate::test_utils::{DeterministicTestRandGuard, *};
#[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);
}
#[test]
fn keyslot_v2() {
// Install deterministic RNG for reproducible encryption
let _guard = DeterministicTestRandGuard::install(&[0x42; 64], &[0x17; 16]).unwrap();
let (cust_key, host_key1, host_key2) = get_test_keys_hybrid();
let host_key = HostKey::V2(HybridPKey::new(host_key1, host_key2).unwrap());
let exp_keyslot = vec![
255, 94, 191, 53, 220, 196, 47, 37, 93, 227, 234, 101, 1, 174, 171, 68, 42, 136, 92,
238, 72, 6, 17, 77, 231, 225, 174, 22, 222, 188, 212, 15, 248, 145, 72, 126, 139, 17,
233, 225, 156, 46, 233, 151, 54, 2, 175, 88, 215, 254, 243, 222, 37, 81, 50, 110, 18,
76, 252, 12, 210, 146, 66, 23, 123, 246, 141, 14, 70, 157, 73, 124, 205, 112, 192, 82,
160, 243, 2, 154, 134, 145, 176, 147, 30, 217, 221, 93, 170, 239, 165, 37, 30, 192, 47,
94, 129, 165, 12, 137, 239, 12, 180, 199, 240, 160, 180, 205, 117, 84, 16, 148, 51, 48,
243, 216, 50, 132, 187, 115, 137, 3, 109, 145, 172, 159, 224, 25, 63, 96, 241, 105, 89,
35, 42, 180, 248, 35, 166, 81, 116, 175, 179, 252, 81, 188, 189, 42, 17, 60, 231, 132,
32, 111, 90, 138, 201, 16, 3, 97, 246, 130, 173, 85, 218, 187, 232, 92, 52, 120, 56,
142, 137, 101, 31, 21, 194, 186, 198, 38, 240, 58, 150, 93, 86, 18, 186, 5, 12, 41, 37,
94, 162, 119, 16, 237, 82, 252, 178, 159, 57, 58, 162, 20, 173, 140, 10, 172, 203, 241,
94, 110, 117, 32, 36, 217, 7, 168, 50, 105, 43, 33, 174, 140, 55, 88, 26, 233, 81, 253,
98, 121, 190, 70, 146, 84, 186, 6, 51, 181, 2, 59, 216, 45, 78, 4, 82, 90, 21, 180, 99,
239, 195, 254, 33, 171, 124, 97, 36, 110, 119, 142, 98, 125, 70, 236, 51, 192, 32, 8,
214, 224, 101, 169, 45, 173, 137, 194, 78, 114, 224, 76, 174, 188, 133, 227, 167, 169,
47, 241, 190, 15, 145, 217, 87, 254, 15, 150, 52, 138, 192, 230, 52, 129, 36, 8, 40,
37, 126, 103, 85, 86, 25, 78, 53, 243, 107, 193, 18, 102, 3, 83, 212, 5, 252, 224, 241,
7, 205, 220, 168, 183, 15, 37, 52, 190, 12, 9, 152, 124, 210, 65, 152, 210, 199, 81,
95, 240, 141, 196, 236, 8, 249, 221, 116, 130, 42, 52, 208, 218, 180, 27, 249, 59, 245,
21, 165, 142, 234, 140, 75, 89, 136, 250, 155, 157, 12, 154, 152, 188, 178, 227, 237,
124, 111, 176, 131, 227, 238, 164, 22, 88, 80, 217, 42, 217, 80, 148, 118, 12, 136, 95,
53, 69, 209, 131, 74, 91, 193, 253, 229, 146, 11, 58, 125, 19, 8, 229, 60, 177, 244,
74, 41, 92, 91, 62, 64, 221, 20, 213, 227, 4, 40, 22, 177, 155, 236, 147, 165, 27, 4,
99, 4, 151, 155, 82, 72, 151, 22, 102, 183, 192, 242, 18, 104, 146, 205, 107, 21, 254,
88, 23, 69, 246, 57, 217, 249, 124, 246, 54, 163, 244, 38, 74, 215, 144, 50, 45, 142,
36, 216, 88, 39, 70, 67, 88, 130, 1, 8, 205, 240, 159, 210, 205, 233, 237, 60, 81, 176,
112, 172, 187, 121, 239, 198, 43, 17, 49, 55, 170, 228, 243, 255, 76, 72, 121, 125,
157, 250, 93, 251, 55, 25, 4, 129, 67, 195, 30, 37, 6, 76, 10, 240, 178, 255, 151, 138,
36, 255, 32, 237, 133, 162, 130, 91, 9, 238, 67, 134, 4, 86, 225, 179, 166, 219, 4,
144, 7, 125, 248, 75, 132, 54, 56, 51, 237, 206, 252, 96, 9, 208, 140, 127, 143, 180,
32, 179, 254, 18, 233, 187, 122, 159, 172, 77, 204, 9, 179, 242, 153, 18, 138, 82, 13,
210, 140, 207, 9, 217, 216, 241, 106, 205, 109, 195, 223, 82, 172, 181, 48, 91, 124,
235, 85, 17, 18, 166, 216, 86, 120, 185, 54, 147, 156, 84, 106, 30, 235, 234, 114, 27,
23, 121, 210, 251, 136, 117, 53, 177, 28, 153, 144, 17, 70, 190, 206, 27, 135, 42, 227,
39, 243, 86, 170, 11, 63, 191, 102, 89, 92, 137, 103, 59, 142, 236, 203, 156, 231, 255,
30, 5, 86, 53, 59, 76, 181, 218, 184, 244, 21, 50, 100, 72, 81, 98, 193, 34, 58, 67,
150, 15, 17, 162, 216, 119, 213, 203, 50, 27, 158, 61, 105, 134, 151, 200, 68, 103, 74,
207, 12, 5, 163, 30, 198, 28, 41, 41, 97, 93, 251, 213, 133, 41, 225, 178, 67, 160, 85,
132, 146, 1, 201, 99, 49, 185, 27, 150, 213, 165, 134, 45, 248, 204, 67, 145, 49, 81,
35, 246, 25, 215, 209, 159, 106, 212, 14, 149, 193, 163, 90, 24, 83, 230, 178, 216,
194, 130, 118, 169, 81, 49, 49, 145, 96, 206, 216, 15, 134, 43, 130, 25, 110, 84, 39,
175, 223, 183, 209, 123, 5, 166, 244, 19, 89, 2, 47, 226, 3, 93, 156, 163, 67, 156,
237, 17, 59, 69, 99, 94, 32, 180, 64, 64, 115, 75, 44, 241, 150, 43, 169, 17, 36, 125,
216, 132, 92, 3, 244, 16, 83, 179, 192, 65, 133, 208, 19, 156, 17, 60, 54, 29, 253,
237, 18, 158, 145, 142, 232, 147, 2, 91, 21, 145, 125, 204, 243, 161, 245, 110, 140,
219, 206, 70, 235, 211, 167, 138, 104, 132, 248, 157, 65, 153, 216, 47, 125, 205, 237,
137, 220, 25, 228, 146, 194, 10, 169, 201, 224, 88, 119, 38, 140, 120, 125, 140, 46,
184, 221, 30, 10, 47, 34, 140, 173, 64, 38, 48, 77, 236, 206, 163, 111, 80, 46, 40,
232, 63, 247, 222, 25, 20, 246, 143, 9, 107, 172, 180, 84, 188, 234, 102, 87, 181, 173,
83, 14, 163, 170, 91, 29, 209, 93, 52, 158, 213, 6, 91, 71, 54, 244, 189, 198, 60, 21,
131, 210, 35, 18, 36, 164, 188, 87, 54, 73, 208, 115, 11, 248, 57, 107, 93, 23, 49,
129, 221, 61, 12, 172, 31, 199, 129, 196, 5, 184, 78, 226, 210, 83, 232, 153, 64, 17,
119, 243, 45, 73, 50, 129, 35, 94, 243, 146, 86, 136, 202, 86, 87, 97, 193, 59, 160,
181, 95, 150, 148, 117, 96, 31, 151, 97, 159, 53, 72, 171, 239, 210, 67, 207, 201, 109,
160, 230, 235, 176, 35, 235, 98, 128, 166, 195, 144, 200, 156, 7, 72, 242, 15, 95, 99,
69, 41, 219, 254, 244, 80, 158, 177, 64, 83, 11, 235, 98, 201, 130, 176, 119, 22, 214,
135, 215, 37, 65, 108, 91, 34, 94, 0, 153, 161, 87, 144, 177, 12, 122, 205, 11, 72,
233, 213, 63, 37, 12, 255, 235, 188, 194, 189, 65, 169, 185, 207, 131, 182, 243, 233,
30, 245, 39, 42, 188, 157, 28, 121, 231, 194, 121, 250, 121, 11, 28, 252, 98, 151, 238,
124, 56, 138, 106, 116, 187, 203, 21, 75, 239, 132, 19, 62, 43, 42, 12, 214, 87, 225,
217, 169, 134, 183, 193, 241, 175, 140, 36, 147, 248, 86, 6, 58, 212, 72, 112, 0, 56,
168, 128, 68, 253, 173, 225, 152, 167, 138, 254, 97, 123, 227, 163, 135, 198, 49, 49,
138, 249, 234, 245, 78, 150, 140, 170, 199, 41, 206, 246, 19, 117, 241, 27, 112, 102,
4, 94, 237, 30, 0, 68, 252, 163, 205, 10, 63, 146, 147, 37, 153, 197, 52, 162, 50, 250,
219, 130, 197, 3, 60, 246, 133, 83, 140, 103, 227, 50, 212, 121, 165, 114, 139, 225,
195, 145, 107, 249, 127, 194, 23, 112, 141, 242, 14, 218, 42, 131, 19, 245, 143, 73,
79, 194, 135, 224, 171, 249, 169, 129, 160, 153, 75, 66, 26, 12, 254, 180, 212, 229,
172, 134, 159, 122, 212, 219, 21, 29, 33, 11, 176, 149, 73, 169, 26, 150, 96, 133, 90,
217, 18, 37, 244, 48, 249, 4, 180, 129, 9, 45, 219, 106, 215, 28, 81, 118, 48, 98, 109,
167, 72, 107, 187, 78, 127, 251, 184, 170, 74, 57, 188, 91, 196, 229, 251, 70, 163, 68,
227, 238, 71, 115, 155, 246, 146, 97, 208, 21, 245, 62, 127, 47, 79, 131, 217, 41, 153,
52, 237, 159, 60, 98, 18, 169, 149, 6, 84, 135, 2, 45, 170, 165, 213, 201, 127, 23,
209, 2, 158, 235, 240, 195, 255, 76, 54, 189, 113, 50, 105, 230, 191, 217, 29, 46, 181,
80, 197, 60, 177, 243, 61, 52, 24, 140, 134, 147, 176, 198, 22, 92, 156, 217, 189, 134,
17, 122, 53, 49, 14, 87, 128, 99, 207, 123, 113, 169, 195, 206, 127, 211, 21, 216, 166,
18, 137, 110, 148, 70, 26, 54, 52, 113, 189, 45, 89, 254, 218, 247, 193, 71, 19, 153,
35, 179, 49, 237, 199, 176, 251, 143, 95, 115, 195, 3, 122, 161, 243, 220, 39, 102,
147, 134, 25, 172, 164, 167, 110, 123, 221, 177, 28, 236, 100, 165, 186, 179, 45, 189,
183, 76, 171, 127, 209, 108, 220, 83, 207, 136, 98, 54, 76, 37, 188, 57, 157, 204, 109,
150, 181, 110, 57, 5, 26, 168, 34, 11, 117, 3, 184, 147, 155, 122, 244, 251, 215, 1,
211, 226, 185, 214, 120, 206, 212, 75, 203, 174, 140, 20, 93, 93, 207, 28, 15, 122, 9,
83, 98, 107, 51, 202, 151, 220, 42, 95, 17, 141, 141, 201, 149, 253, 55, 169, 170, 237,
166, 92, 92, 20, 89, 124, 167, 102, 161, 87, 97, 88, 20, 245, 175, 32, 111, 0, 2, 192,
25, 63, 182, 10, 226, 165, 162, 223, 35, 243, 198, 189, 167, 137, 134, 207, 84, 240, 8,
95, 137, 31, 159, 58, 211, 161, 94, 150, 54, 188, 145, 253, 206, 156, 130, 121, 192,
221, 73, 249, 92, 91, 184, 211, 131, 206, 205, 183, 159, 195, 170, 47, 13, 131, 6, 132,
103, 121, 40, 252, 250, 251, 85, 253, 68, 66, 211, 24, 104, 48, 150, 176, 62, 201, 161,
93, 204, 120, 196, 159, 208, 199, 96, 228, 239, 29, 239, 128, 156, 14, 131, 25, 27,
157, 249, 253, 250, 193, 148, 133, 59, 138, 202, 239,
];
let keyslot = Keyslot::new(host_key);
let encr_keyslot = keyslot.encrypt(&[0x17u8; 32], &cust_key).unwrap();
assert_eq!(encr_keyslot, exp_keyslot);
}
#[test]
fn test_keyslot_v2_constants() {
// Test V2 keyslot constants
assert_eq!(
KeyslotV2::SIZE,
1680,
"V2 keyslot size should be 1680 bytes"
);
assert_eq!(
KeyslotV2::PHKH_SIZE,
0x40,
"V2 PHKH size should be 64 bytes (SHA-512)"
);
}
#[test]
fn test_keyslot_v2_creation() {
let (_, ec_key, mlkem_key) = get_test_key_and_cert_hybrid();
let hybrid = HybridPKey::new(
ec_key.public_key().unwrap(),
mlkem_key.public_key().unwrap(),
)
.unwrap();
let keyslot = KeyslotV2::new(hybrid);
// Verify keyslot was created successfully
assert!(keyslot.ec_hostkey.public_key_to_der().is_ok());
assert!(keyslot.mlkem_hostkey.public_key_to_der().is_ok());
}
#[test]
fn test_keyslot_enum_v1_variant() {
let (_, host_key) = get_test_keys();
let keyslot = Keyslot::V1(KeyslotV1(host_key));
assert_eq!(keyslot.phkh_size(), KeyslotV1::PHKH_SIZE);
assert_eq!(keyslot.size(), KeyslotV1::SIZE);
assert!(matches!(keyslot, Keyslot::V1(_)));
}
#[test]
fn test_keyslot_enum_v2_variant() {
let (_, ec_key, mlkem_key) = get_test_key_and_cert_hybrid();
let hybrid = HybridPKey::new(
ec_key.public_key().unwrap(),
mlkem_key.public_key().unwrap(),
)
.unwrap();
let keyslot = Keyslot::V2(KeyslotV2::new(hybrid));
assert_eq!(keyslot.phkh_size(), KeyslotV2::PHKH_SIZE);
assert_eq!(keyslot.size(), KeyslotV2::SIZE);
assert!(matches!(keyslot, Keyslot::V2(_)));
}
#[test]
fn test_keyslot_from_hostkey_v1() {
let (_, ec_key) = get_test_keys();
let hostkey = HostKey::V1(ec_key);
let keyslot = Keyslot::new(hostkey);
assert!(matches!(keyslot, Keyslot::V1(_)));
assert_eq!(keyslot.size(), KeyslotV1::SIZE);
}
#[test]
fn test_keyslot_from_hostkey_v2() {
let (_, ec_key, mlkem_key) = get_test_key_and_cert_hybrid();
let hybrid = HybridPKey::new(
ec_key.public_key().unwrap(),
mlkem_key.public_key().unwrap(),
)
.unwrap();
let hostkey = HostKey::V2(hybrid);
let keyslot = Keyslot::new(hostkey);
assert!(matches!(keyslot, Keyslot::V2(_)));
assert_eq!(keyslot.size(), KeyslotV2::SIZE);
}
#[test]
fn test_keyslot_from_hybrid_public_key() {
let (_, ec_key, mlkem_key) = get_test_key_and_cert_hybrid();
let hybrid = HybridPKey::new(
ec_key.public_key().unwrap(),
mlkem_key.public_key().unwrap(),
)
.unwrap();
let keyslot: Keyslot = hybrid.into();
assert!(matches!(keyslot, Keyslot::V2(_)));
}
#[test]
fn test_keyslot_sizes() {
// Document the sizes for V1 and V2
assert_eq!(KeyslotV1::SIZE, 80, "V1 keyslot size");
assert_eq!(
KeyslotV2::SIZE,
1680,
"V2 keyslot size (includes ML-KEM ciphertext)"
);
}
}

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

@@ -0,0 +1,26 @@
// 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(crate) use ec_coord::get_pub_ecdh_points;
pub use ec_coord::EcPubKeyCoord;
pub use encrypt::{Aad, Encrypt};
pub use header::RequestHdr;
pub use hostkey::{HostKey, HybridPKey};
pub use keyslot::{Keyslot, KeyslotV1, KeyslotV2};
pub use request::{BinReqValues, Request};

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

@@ -0,0 +1,343 @@
// 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) -> Result<()>;
}
/// 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 req.len() < rql || 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::crypto::SymKey;
use crate::get_test_asset;
use crate::req::{Aad, HybridPKey, Keyslot};
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_v2() {
let (cust_key, host_key1, host_key2) = get_test_keys_hybrid();
let ks = Keyslot::new(HostKey::V2(HybridPKey::new(host_key1, host_key2).unwrap()));
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 aad_exp = vec![
0x12, 0x34, 0x56, 0x89, 0xab, 0xcd, 0xef, 0, // progr
0, 0, 2, 0, // vers
0, 0, 6, 232, // 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
];
// only compare non-randomized part
assert_eq!(aad[..aad_exp.len()], 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_many_v2() {
let (_, host_key1, host_key2) = get_test_keys_hybrid();
let host_key = HostKey::V2(HybridPKey::new(host_key1, host_key2).unwrap());
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 encr_build_aad_nks_v2() {
let (_, host_key1, host_key2) = get_test_keys_hybrid();
let host_key = HostKey::V2(HybridPKey::new(host_key1, host_key2).unwrap());
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 bin_req_values_buffer_too_small() {
// Create a valid header with request length set to 200 bytes
let hdr = RequestHdr::new(0x200, 200, [0x11; 12], 1, 32, Some(TEST_MAGIC));
let hdr_bin = hdr.as_bytes();
// Create a buffer that's smaller than the declared request length (48
// bytes)
let small_buffer = Vec::from(hdr_bin);
let result = BinReqValues::get(&small_buffer);
assert!(matches!(result, Err(Error::BinRequestSmall)));
}
}

View File

@@ -1,22 +1,21 @@
// SPDX-License-Identifier: MIT
//
// Copyright IBM Corp. 2023
// Copyright IBM Corp.
// 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::ffi::c_void;
use std::fs;
use std::mem::{size_of, ManuallyDrop};
use std::path::{Path, PathBuf};
use std::ptr::NonNull;
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.
///
@@ -90,6 +89,27 @@ pub fn get_test_key_and_cert() -> (PKey<Private>, X509) {
(cust_key, host_key)
}
pub fn get_test_key_and_cert_hybrid() -> (PKey<Private>, X509, X509) {
let pub_key = get_test_asset!("keys/public_cust.bin");
let priv_key = get_test_asset!("keys/private_cust.bin");
let host_key = get_test_asset!("keys/host.ec.crt");
let host_keys = get_test_asset!("keys/host.hybrid.crt");
assert_eq!(pub_key.len(), 160);
assert_eq!(priv_key.len(), 80);
let cust_key = get_keypair(pub_key, priv_key).unwrap();
let host_key1 = X509::from_pem(host_key).unwrap();
let host_keys = X509::stack_from_pem(host_keys).unwrap();
assert_eq!(host_keys.len(), 2);
println!("host_key1 = {host_key1:?}");
println!("host_keys[0] = {:?}", host_keys[0]);
println!("host_keys[1] = {:?}", host_keys[1]);
(cust_key, host_key1, host_keys[1].clone())
}
/// TEST ONLY! Get a fixed private/public pair and a fixed public key
///
/// Intended for TESTING only. All parts of the key including the private key are checked in git and
@@ -99,6 +119,15 @@ pub fn get_test_keys() -> (PKey<Private>, PKey<Public>) {
(cust_key, host.public_key().unwrap())
}
pub fn get_test_keys_hybrid() -> (PKey<Private>, PKey<Public>, PKey<Public>) {
let (cust_key, host_key_1, host_key_2) = get_test_key_and_cert_hybrid();
(
cust_key,
host_key_1.public_key().unwrap(),
host_key_2.public_key().unwrap(),
)
}
fn read_ecdh_pubkey(coords: &[u8]) -> Result<PKey<Public>, ErrorStack> {
assert!(coords.len() == 160);
let x = BigNum::from_slice(&coords[..80])?;
@@ -122,3 +151,379 @@ fn get_keypair(pub_coords: &[u8], priv_num: &[u8]) -> Result<PKey<Private>, Erro
key.check_key()?;
PKey::from_ec_key(key)
}
// To regenerate these bindings, run:
// bindgen bindgen_wrapper.h -o bindgen_output.rs \
// --allowlist-function "RAND_get0_public" \
// --allowlist-function "RAND_get0_private" --allowlist-function "EVP_RAND_fetch" \
// --allowlist-function "EVP_RAND_free" --allowlist-function "EVP_RAND_CTX_new" \
// --allowlist-function "EVP_RAND_CTX_free" --allowlist-function "EVP_RAND_CTX_up_ref" \
// --allowlist-function "EVP_RAND_instantiate" --allowlist-function "RAND_set0_public" \
// --allowlist-function "RAND_set0_private" --allowlist-type "OSSL_PARAM"
// where bindgen_wrapper.h contains:
// #include <openssl/provider.h>
// #include <openssl/rand.h>
// #include <openssl/evp.h>
// #include <openssl/params.h>
mod ffi {
use std::ffi::{c_char, c_int, c_uchar, c_uint, c_void};
#[repr(C)]
pub struct OsslParam {
pub key: *const c_char,
pub data_type: c_uint,
pub data: *mut c_void,
pub data_size: usize,
pub return_size: usize,
}
pub enum OsslLibCtx {}
pub enum OsslProvider {}
pub enum EvpRand {}
pub enum EvpRandCtx {}
unsafe extern "C" {
pub fn RAND_get0_public(ctx: *mut OsslLibCtx) -> *mut EvpRandCtx;
pub fn RAND_get0_private(ctx: *mut OsslLibCtx) -> *mut EvpRandCtx;
pub fn EVP_RAND_fetch(
libctx: *mut OsslLibCtx,
algorithm: *const c_char,
properties: *const c_char,
) -> *mut EvpRand;
pub fn EVP_RAND_free(rand: *mut EvpRand);
pub fn EVP_RAND_CTX_new(rand: *mut EvpRand, parent: *mut EvpRandCtx) -> *mut EvpRandCtx;
pub fn EVP_RAND_CTX_free(ctx: *mut EvpRandCtx);
pub fn EVP_RAND_CTX_up_ref(ctx: *mut EvpRandCtx) -> c_int;
pub fn EVP_RAND_instantiate(
ctx: *mut EvpRandCtx,
strength: c_uint,
prediction_resistance: c_int,
pstr: *const c_uchar,
pstr_len: usize,
params: *const OsslParam,
) -> c_int;
pub fn RAND_set0_public(ctx: *mut OsslLibCtx, rand: *mut EvpRandCtx) -> c_int;
pub fn RAND_set0_private(ctx: *mut OsslLibCtx, rand: *mut EvpRandCtx) -> c_int;
}
}
// Constants for OSSL_PARAM construction
const OSSL_PARAM_OCTET_STRING: u32 = 5;
const OSSL_PARAM_UNSIGNED_INTEGER: u32 = 2;
const OSSL_PARAM_END: u32 = 0;
fn ossl_param_end() -> ffi::OsslParam {
ffi::OsslParam {
key: std::ptr::null(),
data_type: OSSL_PARAM_END,
data: std::ptr::null_mut(),
data_size: 0,
return_size: 0,
}
}
fn ossl_param_octet_string(name: &'static [u8], data: &mut [u8]) -> ffi::OsslParam {
// SAFETY: Constructing OSSL_PARAM for octet string.
// - name is a static null-terminated C string, valid for 'static
// - data is a valid mutable slice, pointer remains valid during param usage
// - Pointer casts are safe as they preserve alignment and validity
ffi::OsslParam {
key: name.as_ptr().cast(),
data_type: OSSL_PARAM_OCTET_STRING,
data: data.as_mut_ptr().cast(),
data_size: data.len(),
return_size: data.len(),
}
}
fn ossl_param_uint(name: &'static [u8], value: &mut u32) -> ffi::OsslParam {
// SAFETY: Constructing OSSL_PARAM for unsigned integer.
// - name is a static null-terminated C string, valid for 'static
// - value is a valid mutable reference, pointer remains valid during param usage
// - Pointer cast to c_void is safe as it preserves alignment and validity
ffi::OsslParam {
key: name.as_ptr().cast(),
data_type: OSSL_PARAM_UNSIGNED_INTEGER,
data: (value as *mut u32).cast::<c_void>(),
data_size: size_of::<u32>(),
return_size: size_of::<u32>(),
}
}
#[derive(Debug)]
struct FetchedRand(NonNull<ffi::EvpRand>);
impl FetchedRand {
const TEST_RAND_NAME: &'static [u8] = b"TEST-RAND\0";
fn fetch_test_rand() -> Result<Self, ErrorStack> {
// SAFETY: Calling OpenSSL C API with valid parameters.
// - null_mut() is valid for optional OSSL_LIB_CTX parameter
// - Self::TEST_RAND_NAME is a valid null-terminated C string
// - null() is valid for optional properties parameter
// - Returns null on error, which we handle via NonNull::new
let rand = unsafe {
ffi::EVP_RAND_fetch(
std::ptr::null_mut(),
Self::TEST_RAND_NAME.as_ptr().cast(),
std::ptr::null(),
)
};
NonNull::new(rand).map(Self).ok_or_else(ErrorStack::get)
}
fn as_ptr(&self) -> *mut ffi::EvpRand {
self.0.as_ptr()
}
}
impl Drop for FetchedRand {
fn drop(&mut self) {
// SAFETY: self.0 is a valid non-null EVP_RAND pointer that we own.
// This is the only place we call free, preventing double-free.
unsafe {
ffi::EVP_RAND_free(self.0.as_ptr());
}
}
}
#[derive(Debug)]
struct RandCtx(NonNull<ffi::EvpRandCtx>);
impl RandCtx {
fn new(rand: &FetchedRand) -> Result<Self, ErrorStack> {
// SAFETY: Calling OpenSSL C API with valid parameters.
// - rand.as_ptr() is a valid non-null EVP_RAND pointer
// - null_mut() is valid for optional parent parameter
// - Returns null on error, which we handle via NonNull::new
let ctx = unsafe { ffi::EVP_RAND_CTX_new(rand.as_ptr(), std::ptr::null_mut()) };
NonNull::new(ctx).map(Self).ok_or_else(ErrorStack::get)
}
fn up_ref(ptr: *mut ffi::EvpRandCtx) -> Result<Self, ErrorStack> {
let ptr = NonNull::new(ptr).ok_or_else(ErrorStack::get)?;
// SAFETY: ptr is a valid non-null EVP_RAND_CTX pointer.
// EVP_RAND_CTX_up_ref increments the reference count.
// Returns 1 on success, 0 on failure.
let rc = unsafe { ffi::EVP_RAND_CTX_up_ref(ptr.as_ptr()) };
if rc == 1 {
Ok(Self(ptr))
} else {
Err(ErrorStack::get())
}
}
fn current_public() -> Result<Option<Self>, ErrorStack> {
// SAFETY: RAND_get0_public returns a borrowed pointer (no ownership transfer).
// Returns null if no public RNG is set, which we handle.
let ptr = unsafe { ffi::RAND_get0_public(std::ptr::null_mut()) };
if ptr.is_null() {
Ok(None)
} else {
Self::up_ref(ptr).map(Some)
}
}
fn current_private() -> Result<Option<Self>, ErrorStack> {
// SAFETY: RAND_get0_private returns a borrowed pointer (no ownership transfer).
// Returns null if no private RNG is set, which we handle.
let ptr = unsafe { ffi::RAND_get0_private(std::ptr::null_mut()) };
if ptr.is_null() {
Ok(None)
} else {
Self::up_ref(ptr).map(Some)
}
}
fn as_ptr(&self) -> *mut ffi::EvpRandCtx {
self.0.as_ptr()
}
fn instantiate_test_rand(&self, entropy: &[u8], nonce: &[u8]) -> Result<(), ErrorStack> {
// See https://docs.openssl.org/3.1/man7/EVP_RAND-TEST-RAND/#description
// for the available parameters.
const TEST_ENTROPY_PARAM: &[u8] = b"test_entropy\0";
const TEST_NONCE_PARAM: &[u8] = b"test_nonce\0";
const STRENGTH_PARAM: &[u8] = b"strength\0";
let mut entropy = entropy.to_vec();
let mut nonce = nonce.to_vec();
let mut strength = 256u32;
let params = [
ossl_param_uint(STRENGTH_PARAM, &mut strength),
ossl_param_octet_string(TEST_ENTROPY_PARAM, &mut entropy),
ossl_param_octet_string(TEST_NONCE_PARAM, &mut nonce),
ossl_param_end(),
];
// SAFETY: Calling OpenSSL C API with valid parameters.
// - self.as_ptr() is a valid non-null EVP_RAND_CTX pointer
// - strength is a valid u32 value
// - prediction_resistance=0 is valid
// - pstr=null and pstr_len=0 indicate no personalization string
// - params points to a valid array of OSSL_PARAM with proper terminator
// - All mutable references in params remain valid for the call duration
let rc = unsafe {
ffi::EVP_RAND_instantiate(
self.as_ptr(),
strength,
0,
std::ptr::null(),
0,
params.as_ptr(),
)
};
if rc == 1 {
Ok(())
} else {
Err(ErrorStack::get())
}
}
fn install_as_public(self) -> Result<InstalledRandCtx, ErrorStack> {
// SAFETY: Calling OpenSSL C API to transfer ownership.
// - self.as_ptr() is a valid non-null EVP_RAND_CTX pointer
// - RAND_set0_public takes ownership of the context on success (rc==1)
// - We wrap in ManuallyDrop to prevent double-free since OpenSSL now owns it
let rc = unsafe { ffi::RAND_set0_public(std::ptr::null_mut(), self.as_ptr()) };
if rc == 1 {
Ok(InstalledRandCtx(ManuallyDrop::new(self)))
} else {
Err(ErrorStack::get())
}
}
fn install_as_private(self) -> Result<InstalledRandCtx, ErrorStack> {
// SAFETY: Calling OpenSSL C API to transfer ownership.
// - self.as_ptr() is a valid non-null EVP_RAND_CTX pointer
// - RAND_set0_private takes ownership of the context on success (rc==1)
// - We wrap in ManuallyDrop to prevent double-free since OpenSSL now owns it
let rc = unsafe { ffi::RAND_set0_private(std::ptr::null_mut(), self.as_ptr()) };
if rc == 1 {
Ok(InstalledRandCtx(ManuallyDrop::new(self)))
} else {
Err(ErrorStack::get())
}
}
}
impl Drop for RandCtx {
fn drop(&mut self) {
// SAFETY: self.0 is a valid non-null EVP_RAND_CTX pointer that we own.
// This is only called when ownership was NOT transferred to OpenSSL.
// InstalledRandCtx uses ManuallyDrop to prevent this from running after transfer.
unsafe {
ffi::EVP_RAND_CTX_free(self.0.as_ptr());
}
}
}
#[derive(Debug)]
struct InstalledRandCtx(ManuallyDrop<RandCtx>);
impl Drop for InstalledRandCtx {
fn drop(&mut self) {
// SAFETY: Ownership of the EVP_RAND_CTX was transferred to OpenSSL
// via RAND_set0_public/private, so we must not call EVP_RAND_CTX_free.
// ManuallyDrop prevents RandCtx::drop from running automatically.
}
}
#[derive(Debug)]
struct PreviousRandCtx(Option<RandCtx>);
impl PreviousRandCtx {
fn capture_public() -> Result<Self, ErrorStack> {
RandCtx::current_public().map(Self)
}
fn capture_private() -> Result<Self, ErrorStack> {
RandCtx::current_private().map(Self)
}
fn restore_public(&mut self) {
let Some(ctx) = self.0.take() else {
return;
};
// SAFETY: Restoring previously captured RNG context.
// - ctx.as_ptr() is a valid non-null EVP_RAND_CTX pointer
// - RAND_set0_public takes ownership of the context
// - We forget ctx to prevent double-free since OpenSSL now owns it
// - Ignoring return value as restoration is best-effort during cleanup
let _ = unsafe { ffi::RAND_set0_public(std::ptr::null_mut(), ctx.as_ptr()) };
std::mem::forget(ctx);
}
fn restore_private(&mut self) {
let Some(ctx) = self.0.take() else {
return;
};
// SAFETY: Restoring previously captured RNG context.
// - ctx.as_ptr() is a valid non-null EVP_RAND_CTX pointer
// - RAND_set0_private takes ownership of the context
// - We forget ctx to prevent double-free since OpenSSL now owns it
// - Ignoring return value as restoration is best-effort during cleanup
let _ = unsafe { ffi::RAND_set0_private(std::ptr::null_mut(), ctx.as_ptr()) };
std::mem::forget(ctx);
}
}
#[derive(Debug)]
pub struct DeterministicTestRandGuard {
previous_public: PreviousRandCtx,
previous_private: PreviousRandCtx,
_public: InstalledRandCtx,
_private: InstalledRandCtx,
}
impl DeterministicTestRandGuard {
/// Install OpenSSL >= 3 TEST-RAND as the thread-local public/private RNG for deterministic
/// tests.
///
/// The supplied entropy is consumed across generate calls. The nonce is replayed for each
/// nonce request. Per OpenSSL documentation, the public and private DRBG instances are
/// thread-local, so each thread can safely install its own deterministic RNG without
/// affecting other threads.
///
/// # Thread Safety
///
/// From OpenSSL documentation (RAND_get0_primary(3)):
/// > "The public and private DRBG are thread-local instances, which are used by
/// > RAND_bytes() and RAND_priv_bytes(), respectively."
///
/// Reference: <https://docs.openssl.org/3.1/man3/RAND_get0_primary/>
///
/// **Note:** RAND_set0_public() and RAND_set0_private() require OpenSSL >= 3.1.
///
/// # Errors
///
/// Returns an OpenSSL error if the TEST-RAND provider cannot be configured.
pub fn install(entropy: &[u8], nonce: &[u8]) -> Result<Self, ErrorStack> {
let previous_public = PreviousRandCtx::capture_public()?;
let previous_private = PreviousRandCtx::capture_private()?;
let rand = FetchedRand::fetch_test_rand()?;
let public = RandCtx::new(&rand)?;
public.instantiate_test_rand(entropy, nonce)?;
let public = public.install_as_public()?;
let private = RandCtx::new(&rand)?;
private.instantiate_test_rand(entropy, nonce)?;
let private = private.install_as_private()?;
Ok(Self {
previous_public,
previous_private,
_public: public,
_private: private,
})
}
}
impl Drop for DeterministicTestRandGuard {
fn drop(&mut self) {
self.previous_private.restore_private();
self.previous_public.restore_public();
}
}

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,20 @@
//
// Copyright IBM Corp. 2024
use openssl::pkey::{PKey, Public};
use std::mem::size_of;
use pv_core::static_assert;
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, Error, Result};
#[cfg(doc)]
use crate::{
request::SymKeyType,
@@ -59,7 +52,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 +61,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);
/// // arcb.add_hostkey(HostKey::V1(another_hkd))?;
/// // encrypt it
/// let ctx = ReqEncrCtx::random(SymKeyType::Aes256)?;
/// let ctx = ReqEncrCtx::random(SymKeyType::Aes256Gcm)?;
/// let arcb = arcb.encrypt(&ctx)?;
/// # Ok(())
/// # }
@@ -114,7 +107,13 @@ impl AttestationRequest {
/// Returns a reference to the flags of this [`AttestationRequest`].
pub fn flags(&self) -> &AttestationFlags {
&self.aad.flags
self.aad.flags()
}
/// Returns the request version, derived from the type of added host-keys.
/// Returns [`AttestationVersion::One`] if no host-keys have been added yet.
pub fn version(&self) -> AttestationVersion {
self.version
}
/// Returns a copy of the confidential data of this [`AttestationRequest`].
@@ -150,6 +149,7 @@ impl AttestationRequest {
let values = BinReqValues::get(arcb)?;
match values.version().try_into()? {
AttestationVersion::One => (),
AttestationVersion::Two => (),
};
Ok(values)
@@ -243,8 +243,19 @@ impl Request for AttestationRequest {
ctx.encrypt_aead(&aad, conf).map(|res| res.into_buf())
}
fn add_hostkey(&mut self, hostkey: PKey<Public>) {
self.keyslots.push(Keyslot::new(hostkey))
fn add_hostkey(&mut self, hostkey: HostKey) -> Result<()> {
match self.version {
AttestationVersion::One if !hostkey.is_hybrid() => Ok(()),
AttestationVersion::Two if hostkey.is_hybrid() => Ok(()),
AttestationVersion::One => Err(Error::InvalidHkd(
"Add classical hostkey to a v1 attestation request".to_string(),
)),
AttestationVersion::Two => Err(Error::InvalidHkd(
"Add hybrid key to a v2 attetstation request".to_string(),
)),
}?;
self.keyslots.push(Keyslot::new(hostkey));
Ok(())
}
}
@@ -254,6 +265,8 @@ impl Request for AttestationRequest {
pub enum AttestationVersion {
/// Version 1 (= 0x0100)
One = 0x0100,
/// Version 2 (= 0x0200)
Two = 0x0200,
}
impl TryFrom<u32> for AttestationVersion {
@@ -262,6 +275,8 @@ impl TryFrom<u32> for AttestationVersion {
fn try_from(value: u32) -> Result<Self> {
if value == Self::One as u32 {
Ok(Self::One)
} else if value == Self::Two as u32 {
Ok(Self::Two)
} else {
Err(Error::BinArcbInvVersion(value))
}
@@ -419,8 +434,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::{HybridPKey, SymKey};
use crate::test_utils::{get_test_keys, get_test_keys_hybrid};
const ARPK: [u8; 32] = [0x17; 32];
const NONCE: [u8; 16] = [0xab; 16];
@@ -428,6 +444,8 @@ mod test {
fn mk_arcb() -> Vec<u8> {
let (cust_key, host_key) = get_test_keys();
let host_key = HostKey::V1(host_key);
let ctx = ReqEncrCtx::new_aes_256(
Some([0x55; 12]),
Some(cust_key),
@@ -450,7 +468,37 @@ mod test {
arcb.conf.value_mut().nonce = NONCE;
arcb.conf.value_mut().meas_key = MEAS;
arcb.add_hostkey(host_key);
arcb.add_hostkey(host_key).unwrap();
arcb.encrypt(&ctx).unwrap()
}
fn mk_arcb_v2() -> Vec<u8> {
let (cust_key, host_key1, host_key2) = get_test_keys_hybrid();
let host_key = HostKey::V2(HybridPKey::new(host_key1, host_key2).unwrap());
let ctx = ReqEncrCtx::new_aes_256(
Some([0x55; 12]),
Some(cust_key),
Some(SymKey::Aes256(ARPK.into())),
)
.unwrap();
let mut flags = AttestationFlags::default();
flags.set_image_phkh();
flags.set_attest_phkh();
let mut arcb = AttestationRequest::new(
AttestationVersion::Two,
AttestationMeasAlg::HmacSha512,
flags,
)
.unwrap();
// manually set confidential data (API does not allow this)
arcb.conf.value_mut().nonce = NONCE;
arcb.conf.value_mut().meas_key = MEAS;
arcb.add_hostkey(host_key).unwrap();
arcb.encrypt(&ctx).unwrap()
}
@@ -462,6 +510,32 @@ mod test {
assert_eq!(request, exp);
}
#[test]
fn arcb_v2() {
let request = mk_arcb_v2();
// Expected bytes for a V2 ARCB: rqvn = 0x0200 (bytes 8-11), rest deterministic.
// The first 288 bytes cover header + customer-public-key + one V2 keyslot header.
let exp: [u8; 288] = [
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 7, 208, 85, 85, 85, 85, 85, 85, 85, 85, 85,
85, 85, 85, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 80, 112, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 199,
93, 52, 249, 22, 82, 219, 69, 123, 11, 32, 156, 70, 164, 145, 164, 78, 226, 177, 110,
35, 194, 216, 218, 241, 22, 103, 138, 98, 242, 76, 227, 50, 197, 153, 95, 8, 69, 107,
102, 177, 109, 213, 90, 146, 197, 7, 241, 227, 26, 247, 140, 100, 168, 46, 122, 84, 27,
21, 19, 80, 21, 242, 2, 134, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 64, 128, 88,
167, 241, 165, 195, 80, 151, 83, 58, 2, 169, 56, 121, 231, 222, 103, 186, 40, 11, 206,
131, 101, 236, 148, 178, 185, 8, 245, 137, 195, 169, 152, 216, 190, 30, 99, 7, 215, 74,
224, 26, 220, 70, 130, 95, 246, 187, 111, 160, 92, 17, 71, 207, 226, 204, 244, 162, 79,
61, 131, 61, 218, 112, 255, 94, 191, 53, 220, 196, 47, 37, 93, 227, 234, 101, 1, 174,
171, 68, 42, 136, 92, 238, 72, 6, 17, 77, 231, 225, 174, 22, 222, 188, 212, 15, 248,
145, 72, 126, 139, 17, 233, 225, 156, 46, 233, 151, 54, 2, 175, 88, 215, 254, 243, 222,
37, 81, 50, 110, 18, 76, 252, 12, 210, 146, 66, 23,
];
// only compare non-randomized part
assert_eq!(request[..288], exp[..288]);
}
#[test]
fn auth_bin() {
let request = mk_arcb();
@@ -471,6 +545,15 @@ mod test {
assert_eq!(exp, auth_bin.as_bytes());
}
#[test]
fn auth_bin_v2() {
let request = mk_arcb_v2();
let auth_bin = AttestationRequest::auth_bin(&request).unwrap();
let exp = &request[0x30..0x40];
assert_eq!(exp, auth_bin.as_bytes());
}
#[test]
fn decrypt_bin() {
let request = mk_arcb();
@@ -480,6 +563,100 @@ mod test {
assert_eq!(conf.nonce().as_ref().unwrap().value(), &NONCE);
}
#[test]
fn decrypt_bin_v2() {
let request = mk_arcb_v2();
let arpk = SymKey::Aes256(ARPK.into());
let (_, conf) = AttestationRequest::decrypt_bin(&request, &arpk).unwrap();
assert_eq!(conf.measurement_key(), &MEAS);
assert_eq!(conf.nonce().as_ref().unwrap().value(), &NONCE);
}
#[test]
fn arcb_v1_version() {
// Without any host-keys, version defaults to One
let arcb = AttestationRequest::new(
AttestationVersion::One,
AttestationMeasAlg::HmacSha512,
AttestationFlags::default(),
)
.unwrap();
assert_eq!(arcb.version(), AttestationVersion::One);
}
#[test]
fn arcb_v2_version() {
// After adding a V2 host-key, version is Two
let mut arcb = AttestationRequest::new(
AttestationVersion::Two,
AttestationMeasAlg::HmacSha512,
AttestationFlags::default(),
)
.unwrap();
let (_, host_key1, host_key2) = get_test_keys_hybrid();
arcb.add_hostkey(HostKey::V2(HybridPKey::new(host_key1, host_key2).unwrap()))
.unwrap();
assert_eq!(arcb.version(), AttestationVersion::Two);
}
#[test]
fn attestation_version_try_from() {
// Test version conversion from u32
assert_eq!(
AttestationVersion::try_from(0x0100).unwrap(),
AttestationVersion::One
);
assert_eq!(
AttestationVersion::try_from(0x0200).unwrap(),
AttestationVersion::Two
);
// Invalid version should error
assert!(AttestationVersion::try_from(0x0300).is_err());
}
#[test]
fn attestation_flags_expected_size() {
// Test expected additional data size calculation for V1
let mut flags = AttestationFlags::default();
// Image PHKH flag - should be 32 bytes
flags.set_image_phkh();
assert_eq!(flags.expected_additional_size(), 32);
// Add attest PHKH flag - should be 64 bytes (32 + 32)
flags.set_attest_phkh();
assert_eq!(flags.expected_additional_size(), 64);
// Add secret store hash - should be 128 bytes (64 + 64)
flags.set_secret_store_hash();
assert_eq!(flags.expected_additional_size(), 128);
// Add firmware state - should be 448 bytes (128 + 320)
flags.set_firmware_state();
assert_eq!(flags.expected_additional_size(), 448);
}
#[test]
fn confidential_data_v2() {
// Test confidential data extraction (version-independent)
let arcb = AttestationRequest::new(
AttestationVersion::Two,
AttestationMeasAlg::HmacSha512,
AttestationFlags::default(),
)
.unwrap();
let conf = arcb.confidential_data();
// Should have measurement key and nonce
assert_eq!(conf.measurement_key().len(), 64);
assert!(conf.nonce().is_some());
assert_eq!(conf.nonce().as_ref().unwrap().value().len(), 16);
}
#[test]
fn decrypt_bin_fail_magic() {
let arpk = SymKey::Aes256(ARPK.into());
@@ -491,6 +668,17 @@ mod test {
assert!(matches!(ret, Err(Error::NoArcb)));
}
#[test]
fn decrypt_bin_fail_magic_v2() {
let arpk = SymKey::Aes256(ARPK.into());
let mut tamp_arcb = mk_arcb_v2();
// tamper magic
tamp_arcb[0] = 17;
let ret = AttestationRequest::decrypt_bin(&tamp_arcb, &arpk);
assert!(matches!(ret, Err(Error::NoArcb)));
}
#[test]
fn decrypt_bin_fail_mai() {
let arpk = SymKey::Aes256(ARPK.into());
@@ -506,6 +694,21 @@ mod test {
));
}
#[test]
fn decrypt_bin_fail_mai_v2() {
let arpk = SymKey::Aes256(ARPK.into());
let mut tamp_arcb = mk_arcb_v2();
// tamper MAI
tamp_arcb[0x3b] = 17;
let ret = AttestationRequest::decrypt_bin(&tamp_arcb, &arpk);
println!("{ret:?}");
assert!(matches!(
ret,
Err(Error::PvCore(pv_core::Error::BinArcbInvAlgorithm(17)))
));
}
#[test]
fn decrypt_bin_fail_aad() {
let arpk = SymKey::Aes256(ARPK.into());
@@ -516,4 +719,15 @@ mod test {
let ret = AttestationRequest::decrypt_bin(&tamp_arcb, &arpk);
assert!(matches!(ret, Err(Error::GcmTagMismatch)));
}
#[test]
fn decrypt_bin_fail_aad_v2() {
let arpk = SymKey::Aes256(ARPK.into());
let mut tamp_arcb = mk_arcb_v2();
// tamper AAD
tamp_arcb[0x3c] = 17;
let ret = AttestationRequest::decrypt_bin(&tamp_arcb, &arpk);
assert!(matches!(ret, Err(Error::GcmTagMismatch)));
}
}

View File

@@ -2,20 +2,22 @@
//
// 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 pv_core::misc::write_file;
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.
///
@@ -100,6 +102,7 @@ impl AttestationItems {
}
items.extend_from_slice(additional);
assert!(items.len() == size);
write_file("additional_data_parsed", &items, "blah").unwrap();
Self(items.into())
}
}
@@ -151,9 +154,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,36 +2,36 @@
//
// 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::static_assert;
use pv_core::uv::SecretId;
use zerocopy::{Immutable, IntoBytes};
use super::guest_secret::{ListableSecretHdr, SecretAuth};
use super::user_data::UserData;
use crate::crypto::{hkdf_rfc_5869, AeadEncryptionResult};
use crate::misc::Flags;
use crate::req::{Aad, BinReqValues, HostKey, Keyslot, ReqEncrCtx, RequestHdr};
use crate::request::{BootHdrTags, Confidential, Request};
use crate::secret::{ExtSecret, GuestSecret};
use crate::uv::{ConfigUid, UvFlags};
use crate::{assert_size, Error, Result};
/// Authenticated data w/o user data
#[repr(C)]
#[derive(Debug, Clone, Copy, IntoBytes, Immutable)]
struct ReqAuthData {
struct ReqAuthDataV1 {
flags: UvFlags,
boot_tags: BootHdrTags,
cuid: ConfigUid,
reserved90: [u8; 0x100],
}
assert_size!(ReqAuthData, 0x1e8);
assert_size!(ReqAuthDataV1, 0x1e8);
impl ReqAuthData {
impl ReqAuthDataV1 {
fn new<F: Into<UvFlags>>(boot_tags: BootHdrTags, flags: F) -> Self {
Self {
flags: flags.into(),
@@ -42,6 +42,30 @@ impl ReqAuthData {
}
}
/// Authenticated data w/o user data for v2 header: move up secret header 2
#[repr(C)]
#[derive(Debug, Clone, IntoBytes, Immutable)]
struct ReqAuthDataV2 {
flags: UvFlags,
boot_tags: BootHdrTags,
cuid: ConfigUid,
secr_auth: [u8; 0x30],
reservedd0: [u8; 0x100 - 0x30],
}
assert_size!(ReqAuthDataV2, 0x1e8);
impl ReqAuthDataV2 {
fn new<F: Into<UvFlags>>(boot_tags: BootHdrTags, flags: F, secr_auth: [u8; 0x30]) -> Self {
Self {
flags: flags.into(),
boot_tags,
cuid: [0; 0x10],
secr_auth,
reservedd0: [0; 0xd0],
}
}
}
#[derive(Debug)]
struct ReqConfData {
secret: GuestSecret,
@@ -93,6 +117,8 @@ impl From<AddSecretFlags> for UvFlags {
pub enum AddSecretVersion {
/// Version 1 (= 0x0100)
One = 0x0100,
/// Version 2 (= 0x0200)
Two = 0x0200,
#[cfg(not(doc))]
#[cfg(any(debug_assertions, test))]
@@ -100,17 +126,182 @@ pub enum AddSecretVersion {
Inv = 0,
}
impl TryFrom<u32> for AddSecretVersion {
type Error = Error;
fn try_from(value: u32) -> Result<Self> {
if value == Self::One as u32 {
Ok(Self::One)
} else if value == Self::Two as u32 {
Ok(Self::Two)
} else {
Err(Error::BinAsrcbInvVersion(value))
}
}
}
impl From<AddSecretVersion> for RequestVersion {
fn from(val: AddSecretVersion) -> Self {
val as Self
}
}
impl From<crate::request::SeHdrVersion> for AddSecretVersion {
fn from(val: crate::request::SeHdrVersion) -> Self {
match val {
crate::request::SeHdrVersion::One => AddSecretVersion::One,
crate::request::SeHdrVersion::Two => AddSecretVersion::Two,
}
}
}
/// Trait for authenticated data in add-secret requests.
///
/// This trait provides a common interface for different versions of authenticated data,
/// allowing flexible addition of new versions in the future.
trait ReqAuthData: IntoBytes + Immutable {
/// Get the configuration UID
fn cuid(&self) -> &ConfigUid;
/// Set the configuration UID
fn set_cuid(&mut self, cuid: ConfigUid);
/// Get the boot tags
fn boot_tags(&self) -> &BootHdrTags;
/// Get the flags
fn flags(&self) -> &UvFlags;
}
impl ReqAuthData for ReqAuthDataV1 {
fn cuid(&self) -> &ConfigUid {
&self.cuid
}
fn set_cuid(&mut self, cuid: ConfigUid) {
self.cuid = cuid;
}
fn boot_tags(&self) -> &BootHdrTags {
&self.boot_tags
}
fn flags(&self) -> &UvFlags {
&self.flags
}
}
impl ReqAuthData for ReqAuthDataV2 {
fn cuid(&self) -> &ConfigUid {
&self.cuid
}
fn set_cuid(&mut self, cuid: ConfigUid) {
self.cuid = cuid;
}
fn boot_tags(&self) -> &BootHdrTags {
&self.boot_tags
}
fn flags(&self) -> &UvFlags {
&self.flags
}
}
/// Enum holding version-specific authenticated data
#[derive(Debug)]
enum ReqAuthDataVersion {
V1(ReqAuthDataV1),
V2(ReqAuthDataV2),
}
impl ReqAuthDataVersion {
fn new(
version: AddSecretVersion,
boot_tags: BootHdrTags,
flags: AddSecretFlags,
conf_data: &SecretAuth,
) -> Result<Self> {
Ok(match version {
AddSecretVersion::One => Self::V1(ReqAuthDataV1::new(boot_tags, flags)),
AddSecretVersion::Two => Self::V2(ReqAuthDataV2::new(
boot_tags,
flags,
conf_data.get(version).try_into().expect(
"SecretAuth::get() must return exactly 0x30 bytes for AddSecretVersion::Two",
),
)),
#[cfg(any(debug_assertions, test))]
AddSecretVersion::Inv => panic!("Invalid version for production use"),
})
}
#[allow(dead_code)]
fn cuid(&self) -> &ConfigUid {
match self {
Self::V1(v) => v.cuid(),
Self::V2(v) => v.cuid(),
}
}
fn set_cuid(&mut self, cuid: ConfigUid) {
match self {
Self::V1(v) => v.set_cuid(cuid),
Self::V2(v) => v.set_cuid(cuid),
}
}
fn boot_tags(&self) -> &BootHdrTags {
match self {
Self::V1(v) => v.boot_tags(),
Self::V2(v) => v.boot_tags(),
}
}
#[allow(dead_code)]
fn flags(&self) -> &UvFlags {
match self {
Self::V1(v) => v.flags(),
Self::V2(v) => v.flags(),
}
}
fn as_bytes(&self) -> &[u8] {
match self {
Self::V1(v) => v.as_bytes(),
Self::V2(v) => v.as_bytes(),
}
}
}
/// Add-secret request Control Block
///
/// An ASRCB wraps a secret to securely transport it to the Ultravisor.
///
/// Layout:
/// Layout V2:
/// ```none
/// _______________________________________________________________
/// | generic header (48)
/// | --------------------------------------------------- |
/// | Plaintext Add-Secret flags (8) |
/// | SE header tags: PLD(64) ALD(64) TLD(64) HeaderTag(16) |
/// | Configuration unique ID(16) (Attestation) |
/// | Optional, defaults to 0 |
/// | Secret header (48) |
/// | Reserved(208) |
/// | User Data(512) (reserved) |
/// | Customer Public Key (160) generated for each request |
/// | N Keyslots(1680 each) |
/// | --------------------------------------------------- |
/// | Secret to add (Secret type dependent)(may be 0 bytes) | Encrypted
/// | Extension secret(32) Optional, defaults to 0 | Encrypted
/// | --------------------------------------------------- |
/// | AES GCM Tag (16) |
/// |_____________________________________________________________|
/// ```
///
/// Layout V1:
/// ```none
/// _______________________________________________________________
/// | generic header (48)
@@ -134,15 +325,19 @@ impl From<AddSecretVersion> for RequestVersion {
#[derive(Debug)]
pub struct AddSecretRequest {
version: AddSecretVersion,
aad: ReqAuthData,
aad: ReqAuthDataVersion,
keyslots: Vec<Keyslot>,
conf: ReqConfData,
user_data: UserData,
}
static_assert!(AddSecretRequest::USER_DATA_OFFS == 0x218);
static_assert!(
AddSecretRequest::USER_DATA_OFFS == size_of::<RequestHdr>() + size_of::<ReqAuthDataV2>()
);
impl AddSecretRequest {
/// Offset of the user-data in the add-secret request in bytes
pub(super) const V1_USER_DATA_OFFS: usize = 0x218;
pub(super) const USER_DATA_OFFS: usize = size_of::<RequestHdr>() + size_of::<ReqAuthDataV1>();
/// Create a new add-secret request.
///
@@ -153,22 +348,23 @@ impl AddSecretRequest {
secret: GuestSecret,
boot_tags: BootHdrTags,
flags: AddSecretFlags,
) -> Self {
Self {
conf: ReqConfData {
extension_secret: Confidential::new([0; 32]),
secret,
},
aad: ReqAuthData::new(boot_tags, flags),
) -> Result<Self> {
let conf = ReqConfData {
extension_secret: Confidential::new([0; 32]),
secret,
};
Ok(Self {
aad: ReqAuthDataVersion::new(version, boot_tags, flags, &conf.secret.auth())?,
conf,
keyslots: vec![],
version,
user_data: UserData::Null,
}
})
}
/// Sets the Configuration Unique Id of this [`AddSecretRequest`].
pub fn set_cuid(&mut self, cuid: ConfigUid) {
self.aad.cuid = cuid;
self.aad.set_cuid(cuid);
}
/// Sets the extension secret of this [`AddSecretRequest`].
@@ -183,7 +379,7 @@ impl AddSecretRequest {
ExtSecret::Derived(cck) => hkdf_rfc_5869(
Md::sha512(),
cck.value(),
self.aad.boot_tags.tag(),
self.aad.boot_tags().tag(),
DER_EXT_SECRET_INFO,
)?
.into(),
@@ -222,7 +418,6 @@ impl AddSecretRequest {
let cust_pub_key = ctx.key_coords()?;
let secr_auth = self.conf.secret.auth();
let user_data = self.user_data.data();
let mut aad: Vec<Aad> = Vec::with_capacity(5 + self.keyslots.len());
aad.push(Aad::Plain(self.aad.as_bytes()));
if let Some(data) = user_data.0 {
@@ -233,7 +428,13 @@ impl AddSecretRequest {
}
aad.push(Aad::Plain(cust_pub_key.as_ref()));
self.keyslots.iter().for_each(|k| aad.push(Aad::Ks(k)));
aad.push(Aad::Plain(secr_auth.get()));
// write secret header (1) only for v1
match self.version {
AddSecretVersion::One => aad.push(Aad::Plain(secr_auth.get(AddSecretVersion::One))),
AddSecretVersion::Two => {}
#[cfg(any(debug_assertions, test))]
_ => return Err(Error::UnsupportedAddSecretVersion(self.version as u32)),
}
ctx.build_aad(self.version.into(), &aad, conf_len, self.user_data.magic())
}
@@ -274,7 +475,7 @@ impl AddSecretRequest {
// add signature to authenticated data starting with USER_DATA_OFFS
self.user_data.sign(
&mut buf[aad_range.start..encr_range.end],
Self::V1_USER_DATA_OFFS,
Self::USER_DATA_OFFS,
)?;
// encrypt again with signed data
@@ -310,7 +511,101 @@ impl Request for AddSecretRequest {
}
}
fn add_hostkey(&mut self, hostkey: PKey<Public>) {
self.keyslots.push(Keyslot::new(hostkey))
fn add_hostkey(&mut self, hostkey: HostKey) -> Result<()> {
match self.version {
AddSecretVersion::One if !hostkey.is_hybrid() => Ok(()),
AddSecretVersion::Two if hostkey.is_hybrid() => Ok(()),
AddSecretVersion::One => Err(Error::InvalidHkd(
"Add classical hostkey to a v1 attestation request".to_string(),
)),
AddSecretVersion::Two => Err(Error::InvalidHkd(
"Add hybrid key to a v2 attetstation request".to_string(),
)),
#[cfg(any(debug_assertions, test))]
AddSecretVersion::Inv => panic!("Invalid version for production use"),
}?;
self.keyslots.push(Keyslot::new(hostkey));
Ok(())
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::request::SeHdrVersion;
#[test]
fn add_secret_version_v1() {
// Test V1 version constant
assert_eq!(AddSecretVersion::One as u32, 0x0100);
}
#[test]
fn add_secret_version_v2() {
// Test V2 version constant
assert_eq!(AddSecretVersion::Two as u32, 0x0200);
}
#[test]
fn add_secret_version_conversion_v1() {
// Test conversion from SeHdrVersion to AddSecretVersion for V1
let v1: AddSecretVersion = SeHdrVersion::One.into();
assert_eq!(v1, AddSecretVersion::One);
}
#[test]
fn add_secret_version_conversion_v2() {
// Test conversion from SeHdrVersion to AddSecretVersion for V2
let v2: AddSecretVersion = SeHdrVersion::Two.into();
assert_eq!(v2, AddSecretVersion::Two);
}
#[test]
fn add_secret_version_into_request_version() {
// Test conversion from AddSecretVersion to RequestVersion
let v1: RequestVersion = AddSecretVersion::One.into();
assert_eq!(v1, 0x0100);
let v2: RequestVersion = AddSecretVersion::Two.into();
assert_eq!(v2, 0x0200);
}
#[test]
fn add_secret_flags_default() {
// Test default flags have no bits set
let flags = AddSecretFlags::default();
let uv_flags: UvFlags = flags.into();
// Default should have all bits cleared
assert_eq!(uv_flags.as_bytes(), &[0u8; 8]);
}
#[test]
fn add_secret_flags_disable_dump() {
// Test disable dump flag sets bit 0
let mut flags = AddSecretFlags::default();
flags.set_disable_dump();
let uv_flags: UvFlags = flags.into();
// Bit 0 should be set, so bytes should not be all zeros
assert_ne!(uv_flags.as_bytes(), &[0u8; 8]);
}
#[test]
fn req_auth_data_size() {
// Test ReqAuthData size constant
use std::mem::size_of;
assert_eq!(size_of::<ReqAuthDataV1>(), 0x1e8);
}
#[test]
#[cfg(any(debug_assertions, test))]
fn add_secret_version_inv_for_testing() {
// Test that invalid version exists for testing
assert_eq!(AddSecretVersion::Inv as u32, 0);
assert_ne!(AddSecretVersion::Inv, AddSecretVersion::One);
assert_ne!(AddSecretVersion::Inv, AddSecretVersion::Two);
}
}

View File

@@ -2,31 +2,26 @@
//
// 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::secret::AddSecretVersion;
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;
@@ -394,12 +389,18 @@ pub(crate) enum SecretAuth {
}
impl SecretAuth {
const NULL_HDR: NullSecretHdr = NullSecretHdr::new();
const NULL_HDR_V1: NullSecretHdrV1 = NullSecretHdrV1::new();
const NULL_HDR_V2: NullSecretHdrV2 = NullSecretHdrV2::new();
const UPDATE_CCK_HDR: UpdateCckHdr = UpdateCckHdr::new();
pub fn get(&self) -> &[u8] {
pub fn get(&self, version: AddSecretVersion) -> &[u8] {
match self {
Self::Null => Self::NULL_HDR.as_bytes(),
Self::Null => match version {
AddSecretVersion::One => Self::NULL_HDR_V1.as_bytes(),
AddSecretVersion::Two => Self::NULL_HDR_V2.as_bytes(),
#[cfg(any(debug_assertions, test))]
_ => panic!("Invalid AddSecretVersion"),
},
Self::Listable(h) => h.as_bytes(),
Self::UpdateCck => Self::UPDATE_CCK_HDR.as_bytes(),
}
@@ -408,15 +409,15 @@ impl SecretAuth {
#[repr(C)]
#[derive(Debug, IntoBytes, FromBytes, Immutable, KnownLayout)]
struct NullSecretHdr {
struct NullSecretHdrV1 {
res0: u16,
kind: U16<BigEndian>,
secret_len: U32<BigEndian>,
res8: u64,
}
assert_size!(NullSecretHdr, 0x10);
assert_size!(NullSecretHdrV1, 0x10);
impl NullSecretHdr {
impl NullSecretHdrV1 {
const fn new() -> Self {
Self {
res0: 0,
@@ -427,6 +428,29 @@ impl NullSecretHdr {
}
}
#[repr(C)]
#[derive(Debug, IntoBytes, FromBytes, Immutable, KnownLayout)]
struct NullSecretHdrV2 {
res0: u16,
kind: U16<BigEndian>,
secret_len: U32<BigEndian>,
res8: u64,
reserved1: [u8; 32],
}
assert_size!(NullSecretHdrV2, 0x30);
impl NullSecretHdrV2 {
const fn new() -> Self {
Self {
res0: 0,
kind: U16::new(ListableSecretType::NULL),
secret_len: U32::ZERO,
res8: 0,
reserved1: [0; 32],
}
}
}
#[repr(C)]
#[derive(Debug, IntoBytes, FromBytes, Immutable, KnownLayout)]
pub(crate) struct ListableSecretHdr {
@@ -476,13 +500,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];
@@ -707,7 +731,7 @@ mod test {
let gs_bytes = gs.auth();
let exp = vec![0u8, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
assert_eq!(exp, gs_bytes.get());
assert_eq!(exp, gs_bytes.get(AddSecretVersion::One));
assert_eq!(&Vec::<u8>::new(), gs.confidential())
}
@@ -722,7 +746,7 @@ mod test {
let mut exp = vec![0u8, 0, 0, 2, 0, 0, 0, 0x20, 0, 0, 0, 0, 0, 0, 0, 0];
exp.extend([1; 32]);
assert_eq!(exp, gs_bytes_auth.get());
assert_eq!(exp, gs_bytes_auth.get(AddSecretVersion::One));
assert_eq!(&[2; 32], gs.confidential());
}
@@ -735,7 +759,7 @@ mod test {
secret: vec![2; 32].into(),
};
let auth = gs.auth();
let gs_bytes_auth = auth.get();
let gs_bytes_auth = auth.get(AddSecretVersion::One);
let mut exp = vec![0u8, 0, 0, 3, 0, 0, 0, 0x20, 0, 0, 0, 0, 0, 0, 0, 0];
exp.extend([1; 32]);
@@ -752,7 +776,7 @@ mod test {
let mut exp = vec![0u8, 0, 0, 0x16, 0, 0, 0, 0x20];
exp.extend([0; 40]);
assert_eq!(exp, gs_bytes_auth.get());
assert_eq!(exp, gs_bytes_auth.get(AddSecretVersion::One));
assert_eq!(&[2; 32], gs.confidential());
}
}

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
@@ -133,6 +133,7 @@ impl RetrievedSecret {
RetrievedSecret::ProtectedKey(p) => p.into_bytes(),
}
}
/// Get the data in PEM format.
///
/// # Errors
@@ -148,9 +149,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, UserDataType};
use crate::{assert_size, Error, Result};
/// User data.
///
/// User defined data can be:
@@ -265,21 +261,19 @@ pub fn verify_asrcb_and_get_user_data(
// check that the provided buffer contains an Add Secret request
let magic = AddSecretMagic::try_from_bytes(&asrcb)?;
let req = BinReqValues::get(&asrcb)?;
if req.version() != AddSecretVersion::One as u32 {
return Err(Error::BinAsrcbInvVersion);
}
// preventing the two lines after the truncate from panicking
let req_len = req.len();
if asrcb.len() < req_len
|| req_len < AddSecretRequest::V1_USER_DATA_OFFS + UserData::USER_DATA_SIZE
|| req_len < AddSecretRequest::USER_DATA_OFFS + UserData::USER_DATA_SIZE
{
return Err(pv_core::Error::NoAsrcb.into());
}
// forget the tag (and all additional data that might be behind the tag)
asrcb.truncate(req_len - BinReqValues::TAG_LEN);
// get a mutable refrenence on the 512 bytes of user data
let (_, user_data) = asrcb.split_at_mut(AddSecretRequest::V1_USER_DATA_OFFS);
let (_, user_data) = asrcb.split_at_mut(AddSecretRequest::USER_DATA_OFFS);
let user_data = &mut user_data[..UserData::USER_DATA_SIZE];
// depending on the user_data_type do:
@@ -380,7 +374,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,18 @@
//
// Copyright IBM Corp. 2023
use crate::openssl_extensions::{StackableX509Crl, X509StoreContextExtension, X509StoreExtension};
use core::slice;
use std::path::Path;
use helper::{download_first_crl_from_x509, StoreSetupMode};
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 +163,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
///
@@ -183,26 +180,51 @@ impl CertVerifier {
Q: AsRef<Path>,
R: AsRef<Path>,
{
let mut store = helper::store_setup(root_ca_path, crl_paths, cert_paths)?;
let mut untr_certs = Vec::with_capacity(cert_paths.len());
for path in cert_paths {
let mut crt = read_certs(&read_file(path, "certificate")?)?;
if !offline {
for c in &crt {
if let Some(crl) = download_first_crl_from_x509(c)? {
crl.iter().try_for_each(|c| store.add_crl(c))?;
}
}
}
untr_certs.append(&mut crt);
}
let (ibm_z_sign_key, chain) = helper::extract_ibm_sign_key(untr_certs.clone())?;
let root_ca_verification = match root_ca_path {
Some(_) => helper::RootCaVerification::SkipPinning,
None => helper::RootCaVerification::RootCaOrganizationPinning("DigiCert"),
};
// remove the IBM signing certificate from chain.
// We have to verify them separately as they are not marked as intermediate certs
let (ibm_z_sign_key, chain) = helper::extract_ibm_sign_key(untr_certs)?;
// Two-round verification:
//
// Round 1: Verify chain without CRL checks before downloading files
// from URLs from (yet) untrusted certificates.
let store_builder = helper::store_setup(
root_ca_path.as_ref(),
crl_paths,
cert_paths,
StoreSetupMode::WithoutCrlCheck,
)?;
helper::verify_chain(
&store_builder.build(),
&chain,
&[&ibm_z_sign_key],
&root_ca_verification,
)?;
let store = store.build();
helper::verify_chain(&store, &chain, slice::from_ref(&ibm_z_sign_key))?;
// Round 2: Download CRLs and verify again, but this time with CRL checks
let mut store_builder = helper::store_setup(
root_ca_path,
crl_paths,
cert_paths,
StoreSetupMode::WithCrlCheck,
)?;
if !offline {
for cert in &untr_certs {
if let Some(crls) = download_first_crl_from_x509(cert)? {
crls.iter().try_for_each(|c| store_builder.add_crl(c))?;
}
}
}
let store = store_builder.build();
helper::verify_chain(&store, &chain, &[&ibm_z_sign_key], &root_ca_verification)?;
Ok(Self {
store,

File diff suppressed because it is too large Load Diff

View File

@@ -4,32 +4,14 @@
#![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 super::helper::*;
use super::{helper, *};
use crate::test_utils::*;
// Mock function
pub fn download_first_crl_from_x509(cert: &X509Ref) -> Result<Option<Vec<X509Crl>>> {
fn mock_download<P: AsRef<Path>>(path: P) -> Result<Vec<X509Crl>> {
read_crls(std::fs::read(path)?)
}
for dist_point in x509_dist_points(cert) {
{
let path = get_cert_asset_path(&dist_point);
let crls = if let Ok(buf) = mock_download(&path) {
buf
} else {
continue;
};
return Ok(Some(crls));
}
}
Ok(None)
}
use crate::verify::helper::StoreSetupMode;
use crate::Error;
use crate::HkdVerifyErrorType::*;
#[test]
fn store_setup() {
@@ -37,7 +19,12 @@ fn store_setup() {
let inter_path = get_cert_asset_path("inter.crt");
let crls: [String; 0] = [];
let store = helper::store_setup(None::<String>, &crls, &[&ibm_path, &inter_path]);
let store = helper::store_setup(
None::<String>,
&crls,
&[&ibm_path, &inter_path],
StoreSetupMode::WithCrlCheck,
);
assert!(store.is_ok());
}
@@ -60,20 +47,51 @@ fn verify_chain_offline() {
let root_crt = get_cert_asset_path("root_ca.chained.crt");
let certs: [String; 0] = [];
let store = helper::store_setup(Some(&root_crt), &[&inter_crl], &certs)
.unwrap()
.build();
let store = helper::store_setup(
Some(&root_crt),
&[&inter_crl],
&certs,
StoreSetupMode::WithCrlCheck,
)
.unwrap()
.build();
let mut sk = Stack::<X509>::new().unwrap();
sk.push(inter_crt).unwrap();
assert!(verify_chain(&store, &sk, &[ibm_crt]).is_ok());
assert!(verify_chain(
&store,
&sk,
&[&ibm_crt],
&RootCaVerification::RootCaOrganizationPinning(
"International Business Machines Corporationn"
)
)
.is_err());
assert!(verify_chain(
&store,
&sk,
&[&ibm_crt],
&RootCaVerification::RootCaOrganizationPinning("International")
)
.is_err());
assert!(verify_chain(
&store,
&sk,
&[&ibm_crt],
&RootCaVerification::RootCaOrganizationPinning(
"International Business Machines Corporation"
)
)
.is_ok());
assert!(verify_chain(&store, &sk, &[&ibm_crt], &RootCaVerification::SkipPinning).is_ok());
}
#[test]
fn dist_points() {
let crt = load_gen_cert("ibm.crt");
let res = x509_dist_points(&crt);
let exp = vec!["inter_ca.crl"];
let exp = vec!["http://inter_ca.crl"];
assert_eq!(res, exp);
}

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;
use s390_pv::request::{BootHdrTags, HostKey, HybridPKey, 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, get_test_keys_hybrid, DeterministicTestRandGuard};
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,11 +26,16 @@ 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);
let asrcb = match hkd {
HostKey::V1(_) => AddSecretRequest::new(AddSecretVersion::One, guest_secret, TAGS, flags),
HostKey::V2(_) => AddSecretRequest::new(AddSecretVersion::Two, guest_secret, TAGS, flags),
_ => unreachable!("Unknown HostKey version"),
};
let mut asrcb = asrcb?;
if let Some(s) = ext_secret {
asrcb.set_ext_secret(s)?
};
@@ -45,12 +43,25 @@ fn create_asrcb(
asrcb.set_cuid(c);
};
asrcb.add_hostkey(hkd);
asrcb.add_hostkey(hkd)?;
asrcb.encrypt(ctx)
}
fn get_crypto() -> (PKey<Public>, ReqEncrCtx) {
fn get_crypto() -> (HostKey, ReqEncrCtx) {
let (cust_key, host_key) = get_test_keys();
let host_key = HostKey::V1(host_key);
let ctx = ReqEncrCtx::new_aes_256(
Some([0x55; 12]),
Some(cust_key),
Some(SymKey::Aes256([0x17; 32].into())),
)
.unwrap();
(host_key, ctx)
}
fn get_crypto_v2() -> (HostKey, ReqEncrCtx) {
let (cust_key, host_key1, host_key2) = get_test_keys_hybrid();
let host_key = HostKey::V2(HybridPKey::new(host_key1, host_key2).unwrap());
let ctx = ReqEncrCtx::new_aes_256(
Some([0x55; 12]),
Some(cust_key),
@@ -77,6 +88,24 @@ where
create_asrcb(guest_secret, ext_secret.into(), flags, cuid, host_key, &ctx)
}
fn gen_asrcb_v2<E>(
guest_secret: GuestSecret,
ext_secret: E,
flags: AddSecretFlags,
cuid: bool,
) -> Result<Vec<u8>>
where
E: Into<Option<ExtSecret>>,
{
let _guard = DeterministicTestRandGuard::install(&[0x42; 4096], &[0x11; 32]).unwrap();
let (host_key, ctx) = get_crypto_v2();
let cuid = match cuid {
true => Some(CUID),
false => None,
};
create_asrcb(guest_secret, ext_secret.into(), flags, cuid, host_key, &ctx)
}
fn association() -> GuestSecret {
GuestSecret::association(ASSOC_ID, ASSOC_SECRET).unwrap()
}
@@ -96,9 +125,21 @@ fn no_flag() -> AddSecretFlags {
fn create_signed_asrcb(skey: PKey<Private>, user_data: Vec<u8>) -> Vec<u8> {
let (host_key, ctx) = get_crypto();
let mut asrcb =
AddSecretRequest::new(AddSecretVersion::One, GuestSecret::Null, TAGS, no_flag());
AddSecretRequest::new(AddSecretVersion::One, GuestSecret::Null, TAGS, no_flag())
.expect("AddSecretRequest::new failed");
asrcb.add_hostkey(host_key);
asrcb.add_hostkey(host_key).unwrap();
asrcb.set_user_data(user_data, Some(skey)).unwrap();
asrcb.encrypt(&ctx).unwrap()
}
fn create_signed_asrcb_v2(skey: PKey<Private>, user_data: Vec<u8>) -> Vec<u8> {
let (host_key, ctx) = get_crypto_v2();
let mut asrcb =
AddSecretRequest::new(AddSecretVersion::Two, GuestSecret::Null, TAGS, no_flag())
.expect("AddSecretRequest::new failed");
asrcb.add_hostkey(host_key).unwrap();
asrcb.set_user_data(user_data, Some(skey)).unwrap();
asrcb.encrypt(&ctx).unwrap()
}
@@ -108,9 +149,10 @@ fn null_none_default_ncuid_one_user_unsgn() {
let user_data_orig = vec![0x56; 0x183];
let (host_key, ctx) = get_crypto();
let mut asrcb =
AddSecretRequest::new(AddSecretVersion::One, GuestSecret::Null, TAGS, no_flag());
AddSecretRequest::new(AddSecretVersion::One, GuestSecret::Null, TAGS, no_flag())
.expect("AddSecretRequest::new failed");
asrcb.add_hostkey(host_key);
asrcb.add_hostkey(host_key).unwrap();
asrcb.set_user_data(user_data_orig.clone(), None).unwrap();
let asrcb = asrcb.encrypt(&ctx).unwrap();
@@ -121,6 +163,7 @@ fn null_none_default_ncuid_one_user_unsgn() {
&user_data.as_ref().unwrap()[..user_data_orig.len()]
);
}
#[test]
fn null_none_default_ncuid_one_user_ec() {
let (usr_sgn_key, _) = get_test_keys();
@@ -242,8 +285,11 @@ fn null_none_default_ncuid_one() {
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()));
AddSecretRequest::new(AddSecretVersion::One, GuestSecret::Null, TAGS, no_flag())
.expect("AddSecretRequest::new failed");
for _ in 0..7 {
asrcb.add_hostkey(hkd.clone()).unwrap()
}
asrcb.set_cuid(CUID);
let asrcb = asrcb.encrypt(&ctx).unwrap();
@@ -259,3 +305,164 @@ fn verify_no_user_data() {
Ok(None)
))
}
// V2 tests (using hybrid keys)
#[test]
fn null_none_default_ncuid_two_user_unsgn() {
let _guard = DeterministicTestRandGuard::install(&[0x42; 4096], &[0x11; 32]).unwrap();
let user_data_orig = vec![0x56; 0x183];
let (host_key, ctx) = get_crypto_v2();
let mut asrcb =
AddSecretRequest::new(AddSecretVersion::Two, GuestSecret::Null, TAGS, no_flag())
.expect("AddSecretRequest::new failed");
asrcb.add_hostkey(host_key).unwrap();
asrcb.set_user_data(user_data_orig.clone(), None).unwrap();
let asrcb = asrcb.encrypt(&ctx).unwrap();
let user_data = verify_asrcb_and_get_user_data(asrcb, None).unwrap();
assert_eq!(
user_data_orig.as_slice(),
&user_data.as_ref().unwrap()[..user_data_orig.len()]
);
}
#[test]
fn null_none_default_ncuid_two_user_ec() {
let _guard = DeterministicTestRandGuard::install(&[0x42; 4096], &[0x11; 32]).unwrap();
let (usr_sgn_key, _) = get_test_keys();
let usr_vrfy_key = usr_sgn_key.ec_key().unwrap();
let usr_vrfy_key = usr_vrfy_key.public_key();
let usr_vrfy_key = PKey::from_ec_key(
EcKey::from_public_key(
&EcGroup::from_curve_name(Nid::SECP521R1).unwrap(),
usr_vrfy_key,
)
.unwrap(),
)
.unwrap();
let user_data_orig = vec![0x56; 0x100];
let asrcb = create_signed_asrcb_v2(usr_sgn_key, user_data_orig.clone());
let user_data = verify_asrcb_and_get_user_data(asrcb, Some(usr_vrfy_key)).unwrap();
assert_eq!(
user_data_orig.as_slice(),
&user_data.as_ref().unwrap()[..user_data_orig.len()]
);
}
#[test]
fn null_none_default_ncuid_two_user_rsa2048() {
let _guard = DeterministicTestRandGuard::install(&[0x42; 4096], &[0x11; 32]).unwrap();
let usr_sgn_key = get_test_asset!("keys/rsa2048key.pem");
let usr_sgn_key = PKey::private_key_from_pem(usr_sgn_key).unwrap();
let user_data_orig = vec![0x56; 0x100];
let asrcb = create_signed_asrcb_v2(usr_sgn_key, user_data_orig.clone());
let usr_vrfy_key = get_test_asset!("keys/rsa2048key.pub.pem");
let usr_vrfy_key = PKey::public_key_from_pem(usr_vrfy_key).unwrap();
let user_data = verify_asrcb_and_get_user_data(asrcb, Some(usr_vrfy_key)).unwrap();
assert_eq!(
user_data_orig.as_slice(),
&user_data.as_ref().unwrap()[..user_data_orig.len()]
);
}
#[test]
fn null_none_default_ncuid_two_user_rsa3072() {
let _guard = DeterministicTestRandGuard::install(&[0x42; 4096], &[0x11; 32]).unwrap();
let usr_sgn_key = get_test_asset!("keys/rsa3072key.pem");
let usr_sgn_key = PKey::private_key_from_pem(usr_sgn_key).unwrap();
let user_data_orig = vec![0x56; 0x80];
let asrcb = create_signed_asrcb_v2(usr_sgn_key, user_data_orig.clone());
let usr_vrfy_key = get_test_asset!("keys/rsa3072key.pub.pem");
let usr_vrfy_key = PKey::public_key_from_pem(usr_vrfy_key).unwrap();
let user_data = verify_asrcb_and_get_user_data(asrcb, Some(usr_vrfy_key)).unwrap();
assert_eq!(
user_data_orig.as_slice(),
&user_data.as_ref().unwrap()[..user_data_orig.len()]
);
}
#[test]
fn null_none_default_cuid_two() {
let _guard = DeterministicTestRandGuard::install(&[0x42; 4096], &[0x11; 32]).unwrap();
let asrcb = gen_asrcb_v2(GuestSecret::Null, None, no_flag(), true).unwrap();
let exp = get_test_asset!("exp/asrcb/null_none_default_cuid_two");
assert_eq!(asrcb, exp);
}
#[test]
fn assoc_none_default_cuid_two() {
let asrcb = gen_asrcb_v2(association(), None, no_flag(), true).unwrap();
let exp = get_test_asset!("exp/asrcb/assoc_none_default_cuid_two");
assert_eq!(asrcb, exp);
}
#[test]
fn null_simple_default_cuid_two() {
let asrcb = gen_asrcb_v2(GuestSecret::Null, ext_simple(), no_flag(), true).unwrap();
let exp = get_test_asset!("exp/asrcb/null_simple_default_cuid_two");
assert_eq!(asrcb, exp);
}
#[test]
fn assoc_simple_default_cuid_two() {
let asrcb = gen_asrcb_v2(association(), ext_simple(), no_flag(), true).unwrap();
let exp = get_test_asset!("exp/asrcb/assoc_simple_default_cuid_two");
assert_eq!(asrcb, exp);
}
#[test]
fn null_derived_default_cuid_two() {
let asrcb = gen_asrcb_v2(GuestSecret::Null, ext_derived(), no_flag(), true).unwrap();
let exp = get_test_asset!("exp/asrcb/null_derived_default_cuid_two");
assert_eq!(asrcb, exp);
}
#[test]
fn assoc_derived_default_cuid_two() {
let asrcb = gen_asrcb_v2(association(), ext_derived(), no_flag(), true).unwrap();
let exp = get_test_asset!("exp/asrcb/assoc_derived_default_cuid_two");
assert_eq!(asrcb, exp);
}
#[test]
fn null_none_dump_cuid_two() {
let mut flags = no_flag();
flags.set_disable_dump();
let asrcb = gen_asrcb_v2(GuestSecret::Null, None, flags, true).unwrap();
let exp = get_test_asset!("exp/asrcb/null_none_dump_cuid_two");
assert_eq!(asrcb, exp);
}
#[test]
fn null_none_default_ncuid_two() {
let asrcb = gen_asrcb_v2(GuestSecret::Null, None, no_flag(), false).unwrap();
let exp = get_test_asset!("exp/asrcb/null_none_default_ncuid_two");
assert_eq!(asrcb, exp);
}
#[test]
fn null_none_default_cuid_seven_two() {
let _guard = DeterministicTestRandGuard::install(&[0x42; 4096], &[0x11; 32]).unwrap();
let (hkd, ctx) = get_crypto_v2();
let mut asrcb =
AddSecretRequest::new(AddSecretVersion::Two, GuestSecret::Null, TAGS, no_flag())
.expect("AddSecretRequest::new failed");
for _ in 0..7 {
asrcb.add_hostkey(hkd.clone()).unwrap()
}
asrcb.set_cuid(CUID);
let asrcb = asrcb.encrypt(&ctx).unwrap();
let exp = get_test_asset!("exp/asrcb/null_none_default_cuid_seven_two");
assert_eq!(asrcb, exp);
}

View File

@@ -1,29 +1,29 @@
-----BEGIN CERTIFICATE-----
MIIE/TCCAuWgAwIBAgIUWT/F3gP9fOTTq3yOMVskcXM8vOAwDQYJKoZIhvcNAQEN
MIIFBDCCAuygAwIBAgIUD2bTT6yk21U+ojAQnf3iw5w2EEowDQYJKoZIhvcNAQEN
BQAwgcwxCzAJBgNVBAYTAlVTMTQwMgYDVQQKDCtJbnRlcm5hdGlvbmFsIEJ1c2lu
ZXNzIE1hY2hpbmVzIENvcnBvcmF0aW9uMTQwMgYDVQQDDCtJbnRlcm5hdGlvbmFs
IEJ1c2luZXNzIE1hY2hpbmVzIENvcnBvcmF0aW9uMREwDwYDVQQIDAhOZXcgWW9y
azEVMBMGA1UEBwwMUG91Z2hrZWVwc2llMScwJQYDVQQLDB5JQk0gWiBIb3N0IEtl
eSBTaWduaW5nIFNlcnZpY2UwIBcNMjQwMzIxMTQ1MjM5WhgPMjM4ODEyMjMxNDUy
MzlaMIG2MQswCQYDVQQGEwJVUzE0MDIGA1UECgwrSW50ZXJuYXRpb25hbCBCdXNp
eSBTaWduaW5nIFNlcnZpY2UwIBcNMjYwNzAyMDg0NzQ4WhgPMjM5MTA0MDUwODQ3
NDhaMIG2MQswCQYDVQQGEwJVUzE0MDIGA1UECgwrSW50ZXJuYXRpb25hbCBCdXNp
bmVzcyBNYWNoaW5lcyBDb3Jwb3JhdGlvbjE0MDIGA1UEAwwrSW50ZXJuYXRpb25h
bCBCdXNpbmVzcyBNYWNoaW5lcyBDb3Jwb3JhdGlvbjERMA8GA1UECAwITmV3IFlv
cmsxDzANBgNVBAcMBkFybW9uazEXMBUGA1UECwwOSUJNIFogSG9zdCBLZXkwgZsw
EAYHKoZIzj0CAQYFK4EEACMDgYYABAAT1zpBNemPCT0tEx2S9qnlPxyBgxKl6BSX
ghqf3MfOqQKIUdKEbxP0nr2QtIR/MwvBp4YhjxZ9AZtzVtXbQrULTAGlKK4qcf1I
W1rYZ5S0b4dmvh2HbIv9wZLWKaZ/ovnvAJk949WDCbBzC3Dy1E3zCaYPefLHHNve
dWX8RtFUhnTqm6NxMG8wHQYDVR0fBBYwFDASoBCgDoYMZmFrZV9pYm0uY3J0MA4G
A1UdDwEB/wQEAwIDCDAfBgNVHSMEGDAWgBSQCOxEmGwiupA2ER7srMBydGO1aTAd
BgNVHQ4EFgQUUl5SPIEseFNeneyuOidsw06L7gQwDQYJKoZIhvcNAQENBQADggIB
AFAg/hFlaBT+WNlUmVJlAd6FYr6vvRjJiKxcNBZ5wElzxA5OjuGX6pLYiNkzXSy2
N/4nQ3a8fr04IB9Uhx5ncMzSKVkG+4mbN3xmR7f6zZuFWV/T9Aom3LIbQ5KYR2wW
EvX8b2xbvd74rKAgavq/iuFRn8skQJGgQk9J2YEApOW9wkoFQRgziuu55Cw5GT+f
w9rKgAFTN33ZwfWs86ELJlDOY0aX5373WGccuEKm8y+l2UVLlly125eezz84RSh7
5j0VUTjK4ZqpSD8yiPxN+vocV+nY52cgWxrVf7g7wlPunxXWX4rxR0z/mEVDDBAC
y7cAxsa4HtmJexuZbjEh8TEgDZXDA24BuFXqtkwqnUpv8KghyKb0KgTQecP9rRhL
b2iHdQrALnddMzJzp7Tn+jsR2A2G0lLLsIuIRFR/1eAxecgTMoxXV8N0SJHn6emH
Uwif1Qr5JVw4UOFSLW3MT3f70hY5hMzrxowOapcYhyR0vbhGFrF1YI8YmAuk2m4P
Dfd5Za5cnfVJiAdNBnNPtV/5wiViX1VJGCrIxgDN6B2VE0VQLp2gKwpsSmhqN4CA
qHzAi3Yj3F8vQ2mWQeuABWLAJrIoxXpVYQvTf4uij0ARZRbajQXx+LZnAUsCi5eZ
LaYrTVn8NzHlGQocUWljG06GaLY8GNqKPQrZD/+6hIYK
EAYHKoZIzj0CAQYFK4EEACMDgYYABAGXMdZTHQ6O1boDC7ncHiILfFqHBRkOaEPs
NTntrxSdyWj7CCAa/VkUKVxq3CnyXqbUv8V/fNncU/ibw0o1m/T3SABExiXKfuKl
1rUm99FfFeZO2uuIcg0pzbGf+opPttIyR+zp1LLbF3eDo0PEovBLT1J1s6iUIbx8
hyfMwUG5JmxdiKN4MHYwJAYDVR0fBB0wGzAZoBegFYYTaHR0cDovL2Zha2VfaWJt
LmNydDAOBgNVHQ8BAf8EBAMCAwgwHwYDVR0jBBgwFoAUVpyIVzghY6i5zPVTHvRO
xxIW8SswHQYDVR0OBBYEFGWsrMjwzRKXSrSBTPdYPTSU6aHnMA0GCSqGSIb3DQEB
DQUAA4ICAQAZ9mWLrrIFk65OJIk6UuX3wwRo824+rsU0TSATzTqRNVb4Ie0KUhMK
V3JbLG16PKk/1k/U6t9h+hZp9O8e3netj5rpxUxQVdMXbUdF4FSxNr1MrY0tWynf
U121bdsDLAgaZlBtl+DRgltH/ApDNnyVetAT7LpOwdmssnPhFytD5//kE4+s54Fy
GsB+aKgvZDHjFgIUkcTYvuB3eTK3J3jrEv8QJYB/Pp3233yMnsSbrE3XLRafJtqQ
77Vi7U8/i9aC9XpDPjTC76sH4nPpby0M85+RnDe+wuV98c7oAd+HcBvxlb80dIbu
jhzXXC6i9teR9PqjJo32T+Pn+gmZlQf1ddXzrYxFa/4mVnVw6IjQfs8NsZRVios7
7vxxm4sLWIJ5s+2gp+jEsV0I+KY/54KLBpJ4XkPRgVhkZRgKieeaFhdknyKOPIPP
dlCbt4e4ivGnbtwO5BnLxq/ek+DuHAsSp+dE4z7MtOKsL5HYgKEqTOV0H1UdjXH9
ezAUwrrAaMc5cSk5ksnA/LOmGd4fZNhD9r6m/Dwtk3PY6DrESEIh/IXYEcurdNtu
vj/Lhscv3AT5DZ7IRbBpHKtNPGezFvcLM18rK+pqHJyoFg77PhKJEUDqRbpbq+VM
vFWV/aTqd6rNPpkVwlI1p3I3BculIlmKPaUc9qhsAlQXHWnBAzvSSg==
-----END CERTIFICATE-----

View File

@@ -1,8 +1,8 @@
-----BEGIN PRIVATE KEY-----
MIHuAgEAMBAGByqGSM49AgEGBSuBBAAjBIHWMIHTAgEBBEIAXR6FCIgd+fjhO/WJ
KwLTP01mBDtYkWbVE71jshjLLpZHunRpsYKbQKCwaDmMYLRrQnTxPgZH1PVKzguI
7M7n9zyhgYkDgYYABAAT1zpBNemPCT0tEx2S9qnlPxyBgxKl6BSXghqf3MfOqQKI
UdKEbxP0nr2QtIR/MwvBp4YhjxZ9AZtzVtXbQrULTAGlKK4qcf1IW1rYZ5S0b4dm
vh2HbIv9wZLWKaZ/ovnvAJk949WDCbBzC3Dy1E3zCaYPefLHHNvedWX8RtFUhnTq
mw==
MIHuAgEAMBAGByqGSM49AgEGBSuBBAAjBIHWMIHTAgEBBEIBoV15DZCYEBy2dx5Q
7PjcNBni/J3FXBka2MZdy402KKrrW6mZI554n4IlpNeyL9Tb4bMp4glYdDsoHLp/
jbZ4K4ChgYkDgYYABAGXMdZTHQ6O1boDC7ncHiILfFqHBRkOaEPsNTntrxSdyWj7
CCAa/VkUKVxq3CnyXqbUv8V/fNncU/ibw0o1m/T3SABExiXKfuKl1rUm99FfFeZO
2uuIcg0pzbGf+opPttIyR+zp1LLbF3eDo0PEovBLT1J1s6iUIbx8hyfMwUG5Jmxd
iA==
-----END PRIVATE KEY-----

View File

@@ -3,18 +3,18 @@ MIIDVTCCAT0CAQEwDQYJKoZIhvcNAQENBQAwgcwxCzAJBgNVBAYTAlVTMTQwMgYD
VQQKDCtJbnRlcm5hdGlvbmFsIEJ1c2luZXNzIE1hY2hpbmVzIENvcnBvcmF0aW9u
MTQwMgYDVQQDDCtJbnRlcm5hdGlvbmFsIEJ1c2luZXNzIE1hY2hpbmVzIENvcnBv
cmF0aW9uMREwDwYDVQQIDAhOZXcgWW9yazEVMBMGA1UEBwwMUG91Z2hrZWVwc2ll
MScwJQYDVQQLDB5JQk0gWiBIb3N0IEtleSBTaWduaW5nIFNlcnZpY2UXDTI0MDMx
MTE1NTIzOVoYDzIzODgxMjIzMTU1MjM5WjAVMBMCAgIrFw0yNDAzMjAxNTUyMzla
oCMwITAfBgNVHSMEGDAWgBSQCOxEmGwiupA2ER7srMBydGO1aTANBgkqhkiG9w0B
AQ0FAAOCAgEAsRE3oW/VAx2JPESuWDZVbKIX9n26BZUZ2mdmUocRmn7KQ6CAi0Ac
L5YRUvYt7kmGo6BSA6rUa0TMoMBtFzfIJ6HQZzEoA/LOkIKMfNHsFFzpIJxghPYJ
PfPkXoLpAOBcLDrYWr1bJ3mkvrd6Tuyx02wJEhVmTcF8W/18AFRPuEVg/u3vJmeQ
yMdwQZG42kEslvhCTO688vozYeX+dXO1/AXamzYQZyEWk2cBQ28DIc5eg39Tq77N
89xqWNK/FWKkF4USn9psiBJQpKEjq+P1jTgdpuN3IGv618hlxS455eGyJrbiTxjD
osWGShI+ZVznnqw98aX558hnNWHic+5JVvdJAwNaMMuNNkApFdz328z9dPtcUAZT
7mNcKAuY1NcqdxKTPwkGWfmZm+WndGwKLwpshBhF/ImiAb/UiDrLh0jGbQ/FDE3j
DtWs4k8eXIibtzGhgjfewdXO66jw5Z55FajcOPgja3uw2g2KEhJ+/VP1YtCvCpkm
NX0liKgKLzUGDCbzyrH7QD16T633ebLbak3CtNMMlxZRQO8DQDbDsEKybCG62f/w
OmM+QmL7lRFcXDbtVNxBSQKaDBDlNaEGEjM8phz166g+UMD7M6xJjmwJx/G/QvUd
98YQfMEIt8IlrIbIfHXnAlj4SJwWNfOw+SN0dCqD5CDEYzYVtZgrNXY=
MScwJQYDVQQLDB5JQk0gWiBIb3N0IEtleSBTaWduaW5nIFNlcnZpY2UXDTI2MDYy
MjEwNDc0OFoYDzIzOTEwNDA1MTA0NzQ4WjAVMBMCAgIrFw0yNjA3MDExMDQ3NDha
oCMwITAfBgNVHSMEGDAWgBRWnIhXOCFjqLnM9VMe9E7HEhbxKzANBgkqhkiG9w0B
AQ0FAAOCAgEAQIp0vuCpPifq82vs30SfcTn0ppIeM2s7TSpGL8AVC0+56x8RXl+O
1QUw4gOWtlO4m7legzGbCu1Wh3gXFb+6nUhWhT0egtAneoNDXRSHGXdo0nWAoH3J
XUN/PaipGvDIEZF/UnehYoRrLG307CgJBQkCErFVvI6XJxqGtc6wgsQM2a5A72zV
jlPEAbCsj5k+y7XzJ+vFVA531IHjdtYrEVMh2T0dN821n/yCwwecuMP4semlqYx0
xJNm2y4whvyQnDpg4YIfLw5LUc9otvztLYW8h3Si34TO0ApJ6TpEdBdFx23jm/jJ
VbfM+g2wFoqsW621BiHn9S1KYD0ENSsbaG6BVRm/CTbPNYw6AdgZS1ggZ+a30NCn
ktg6wxVmXK0I3fzgTcOnaspr/rxkBjDi16sfob9yUuaBuTBOgMO0suq7BEFIl97Q
tpkVVz9mb53Ixt/ZuF6QpB4DqOyksS8YehBJc9nOGMknOxcS1zYrWJzVJyyrKrlB
3VHQ4aYE0WsmP+dpX/QE+gj9/Mp6cvTywVSjW/d9zGOyrSvS/ViU1qsMWrr2iy23
tIOaWRVJBi0TX8S8om4ACgi/qwYGTAcH32Hde0nKy07Bm7JJjGUtJbfLzkM/2rjW
qvmPlmmPWJxMyVYoY9+mamuDd2jRtJyoULrE5hCIupFKVV7Di8RQJJc=
-----END X509 CRL-----

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