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>
This commit is contained in:
Marc Hartmayer
2026-06-24 16:45:28 +02:00
committed by Jan Höppner
parent d82beef937
commit e4e455630b
13 changed files with 150 additions and 41 deletions

View File

@@ -95,7 +95,7 @@ pub mod request {
AeadDecryptionResult, AeadEncryptionResult, Aes256GcmKey, Aes256XtsKey, SymKey, SymKeyType,
SHA_512_HASH_LEN,
};
pub use crate::req::{EcPubKeyCoord, Encrypt, Keyslot, ReqEncrCtx, Request};
pub use crate::req::{EcPubKeyCoord, Encrypt, HostKey, Keyslot, ReqEncrCtx, Request};
pub use crate::verify::{CertVerifier, HkdVerifier, NoVerifyHkd};
/// Reexports some useful OpenSSL symbols

View File

@@ -11,9 +11,7 @@ use openssl::pkey::{PKey, Private};
use pv_core::request::{RequestMagic, RequestVersion};
use zerocopy::IntoBytes;
use super::ec_coord::EcPubKeyCoord;
use super::encrypt::{Aad, Encrypt};
use super::header::RequestHdr;
use super::{Aad, EcPubKeyCoord, Encrypt, RequestHdr};
use crate::crypto::{
encrypt_aead, gen_ec_key, random_array, AeadEncryptionResult, SymKey, SymKeyType,
};
@@ -175,6 +173,7 @@ impl ReqEncrCtx {
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::*;
@@ -184,7 +183,7 @@ mod tests {
#[test]
fn encr_build_aad() {
let (cust_key, host_key) = get_test_keys();
let ks = Keyslot::new(host_key);
let ks = Keyslot::new(HostKey::V1(host_key));
let ctx = ReqEncrCtx::new_aes_256(
Some([0x11; 12]),
Some(cust_key),
@@ -227,7 +226,9 @@ mod tests {
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 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)));
@@ -238,6 +239,7 @@ mod tests {
#[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 = [

View File

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

View File

@@ -10,6 +10,7 @@ use openssl::pkey::{PKey, PKeyRef, Private, Public};
use super::ec_coord::EcPubKeyCoord;
use super::encrypt::Encrypt;
use crate::crypto::{derive_aes256_gcm_key, encrypt_aead, hash};
use crate::request::HostKey;
use crate::Result;
/// IBM Z Host key-slot
@@ -23,11 +24,13 @@ use crate::Result;
/// |_____________________________________________________________|
/// ```
#[derive(Debug, Clone)]
pub struct Keyslot(PKey<Public>);
pub struct KeyslotV1(PKey<Public>);
impl Keyslot {
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 {
@@ -35,7 +38,7 @@ impl Keyslot {
}
}
impl Encrypt for Keyslot {
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
@@ -56,15 +59,75 @@ impl Encrypt for Keyslot {
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);
to.extend_from_slice(&hash(MessageDigest::sha256(), phk.as_ref())?);
let hash = hash(MessageDigest::sha256(), phk.as_ref())?;
assert_eq!(hash.len(), 32);
to.extend_from_slice(&hash);
to.append(&mut wrpk_and_kst);
Ok(())
}
}
/// Versioned keyslot container
#[non_exhaustive]
#[derive(Debug, Clone)]
pub enum Keyslot {
/// V1 key-slots with ECDH keys
V1(KeyslotV1),
}
impl Keyslot {
/// Return a keyslot with the same key-type as the given host-key
pub fn new(hostkey: HostKey) -> Self {
match hostkey {
HostKey::V1(key) => Keyslot::V1(KeyslotV1::new(key)),
}
}
/// Return the public host key hash size for the given version of the key-slot in bytes
pub fn phkh_size(&self) -> u32 {
match self {
Keyslot::V1(_) => KeyslotV1::PHKH_SIZE,
}
}
/// Return the size of the key-slot in bytes
pub fn size(&self) -> usize {
match self {
Keyslot::V1(_) => KeyslotV1::SIZE,
}
}
/// Return whether the key-slot uses hybrid keys
pub fn is_hybrid(&self) -> bool {
match self {
Keyslot::V1(_) => false,
}
}
}
impl Encrypt for Keyslot {
fn encrypt_to(
&self,
secret: &[u8],
priv_key: &PKeyRef<Private>,
to: &mut Vec<u8>,
) -> Result<()> {
match self {
Keyslot::V1(ks) => ks.encrypt_to(secret, priv_key, to),
}
}
}
impl From<PKey<Public>> for Keyslot {
fn from(key: PKey<Public>) -> Self {
Keyslot::V1(KeyslotV1::new(key))
}
}
#[cfg(test)]
mod tests {
use super::*;
@@ -76,7 +139,7 @@ mod tests {
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 keyslot = KeyslotV1(host_key);
let encr_ks = keyslot.encrypt(&[0x17u8; 32], &cust_key).unwrap();
assert_eq!(exp_keyslot, encr_ks);

View File

@@ -11,6 +11,7 @@ mod context;
mod ec_coord;
mod encrypt;
mod header;
mod hostkey;
mod keyslot;
mod request;
@@ -18,5 +19,7 @@ mod request;
pub use context::ReqEncrCtx;
pub use ec_coord::EcPubKeyCoord;
pub use encrypt::{Aad, Encrypt};
pub use keyslot::Keyslot;
pub use header::RequestHdr;
pub use hostkey::HostKey;
pub use keyslot::{Keyslot, KeyslotV1};
pub use request::{BinReqValues, Request};

View File

@@ -3,9 +3,9 @@
// Copyright IBM Corp.
use std::mem::size_of;
use openssl::pkey::{PKey, Public};
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;
@@ -47,10 +47,11 @@ pub trait Request {
/// 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>);
fn add_hostkey(&mut self, hostkey: HostKey);
}
/// A struct to represent some parts of a binary/encrypted request.
@@ -149,7 +150,7 @@ mod tests {
use super::*;
use crate::get_test_asset;
use crate::req::header::RequestHdr;
use crate::req::{Aad, Keyslot, ReqEncrCtx};
use crate::req::{Aad, HostKey, Keyslot, ReqEncrCtx};
use crate::request::SymKey;
use crate::test_utils::*;
@@ -158,7 +159,7 @@ mod tests {
#[test]
fn encr_build_aad() {
let (cust_key, host_key) = get_test_keys();
let ks = Keyslot::new(host_key);
let ks = Keyslot::new(HostKey::V1(host_key));
let ctx = ReqEncrCtx::new_aes_256(
Some([0x11; 12]),
Some(cust_key),
@@ -199,6 +200,7 @@ mod tests {
#[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();
@@ -211,6 +213,7 @@ mod tests {
#[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 = [

View File

@@ -7,12 +7,12 @@ use std::fmt::Display;
use serde::Serialize;
use super::arcb::AttestationFlags;
use crate::req::Keyslot;
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

@@ -4,7 +4,6 @@
use std::mem::size_of;
use openssl::pkey::{PKey, Public};
use zerocopy::{BigEndian, FromBytes, Immutable, IntoBytes, KnownLayout, U32};
use super::additional::{FW_STATE_SIZE, PHKH_SIZE, SECRET_STORE_HASH_SIZE};
@@ -12,7 +11,7 @@ use super::AttNonce;
use crate::attest::{AttestationMagic, AttestationMeasAlg};
use crate::crypto::random_array;
use crate::misc::Flags;
use crate::req::{Aad, BinReqValues, Keyslot, ReqEncrCtx};
use crate::req::{Aad, BinReqValues, HostKey, Keyslot, ReqEncrCtx};
use crate::request::{Confidential, MagicValue, Request, RequestVersion, SymKey, Zeroize};
use crate::uv::UvFlags;
use crate::{assert_size, static_assert, Error, Result};
@@ -52,7 +51,7 @@ use crate::{
///
/// ```rust,no_run
/// # use s390_pv::attest::{AttestationFlags, AttestationMeasAlg, AttestationRequest, AttestationVersion};
/// # use s390_pv::request::{SymKeyType, Request, ReqEncrCtx};
/// # use s390_pv::request::{SymKeyType, Request, ReqEncrCtx, HostKey};
/// # fn main() -> s390_pv::Result<()> {
/// let att_version = AttestationVersion::One;
/// let meas_alg = AttestationMeasAlg::HmacSha512;
@@ -61,11 +60,11 @@ use crate::{
/// let hkd = s390_pv::misc::read_certs(&std::fs::read("host-key-document.crt")?)?;
/// // IBM issued HKD certificates typically have one X509
/// let hkd = hkd.first().unwrap().public_key()?;
/// arcb.add_hostkey(hkd);
/// arcb.add_hostkey(HostKey::V1(hkd));
/// // you can add multiple hostkeys
/// // arcb.add_hostkey(another_hkd);
/// // encrypt it
/// let ctx = ReqEncrCtx::random(SymKeyType::Aes256)?;
/// let ctx = ReqEncrCtx::random(SymKeyType::Aes256Gcm)?;
/// let arcb = arcb.encrypt(&ctx)?;
/// # Ok(())
/// # }
@@ -236,7 +235,7 @@ impl Request for AttestationRequest {
ctx.encrypt_aead(&aad, conf).map(|res| res.into_buf())
}
fn add_hostkey(&mut self, hostkey: PKey<Public>) {
fn add_hostkey(&mut self, hostkey: HostKey) {
self.keyslots.push(Keyslot::new(hostkey))
}
}
@@ -444,7 +443,7 @@ mod test {
arcb.conf.value_mut().nonce = NONCE;
arcb.conf.value_mut().meas_key = MEAS;
arcb.add_hostkey(host_key);
arcb.add_hostkey(HostKey::V1(host_key));
arcb.encrypt(&ctx).unwrap()
}

View File

@@ -3,7 +3,7 @@
// Copyright IBM Corp. 2023
use openssl::md::Md;
use openssl::pkey::{PKey, Private, Public};
use openssl::pkey::{PKey, Private};
use pv_core::request::RequestVersion;
use pv_core::secret::AddSecretMagic;
use pv_core::uv::SecretId;
@@ -13,7 +13,7 @@ use super::guest_secret::ListableSecretHdr;
use super::user_data::UserData;
use crate::crypto::{hkdf_rfc_5869, AeadEncryptionResult};
use crate::misc::Flags;
use crate::req::{Aad, BinReqValues, Keyslot, ReqEncrCtx};
use crate::req::{Aad, BinReqValues, HostKey, Keyslot, ReqEncrCtx};
use crate::request::{BootHdrTags, Confidential, Request};
use crate::secret::{ExtSecret, GuestSecret};
use crate::uv::{ConfigUid, UvFlags};
@@ -309,7 +309,7 @@ impl Request for AddSecretRequest {
}
}
fn add_hostkey(&mut self, hostkey: PKey<Public>) {
fn add_hostkey(&mut self, hostkey: HostKey) {
self.keyslots.push(Keyslot::new(hostkey))
}
}

View File

@@ -7,7 +7,7 @@ use openssl::ec::{EcGroup, EcKey};
use openssl::nid::Nid;
use openssl::pkey::Private;
use s390_pv::request::openssl::pkey::{PKey, Public};
use s390_pv::request::{BootHdrTags, ReqEncrCtx, Request, SymKey};
use s390_pv::request::{BootHdrTags, HostKey, ReqEncrCtx, Request, SymKey};
use s390_pv::secret::{
verify_asrcb_and_get_user_data, AddSecretFlags, AddSecretRequest, AddSecretVersion, ExtSecret,
GuestSecret,
@@ -26,7 +26,7 @@ fn create_asrcb(
ext_secret: Option<ExtSecret>,
flags: AddSecretFlags,
cuid: Option<ConfigUid>,
hkd: PKey<Public>,
hkd: HostKey,
ctx: &ReqEncrCtx,
) -> Result<Vec<u8>> {
let mut asrcb = AddSecretRequest::new(AddSecretVersion::One, guest_secret, TAGS, flags);
@@ -67,7 +67,14 @@ where
true => Some(CUID),
false => None,
};
create_asrcb(guest_secret, ext_secret.into(), flags, cuid, host_key, &ctx)
create_asrcb(
guest_secret,
ext_secret.into(),
flags,
cuid,
HostKey::V1(host_key),
&ctx,
)
}
fn association() -> GuestSecret {
@@ -91,7 +98,7 @@ fn create_signed_asrcb(skey: PKey<Private>, user_data: Vec<u8>) -> Vec<u8> {
let mut asrcb =
AddSecretRequest::new(AddSecretVersion::One, GuestSecret::Null, TAGS, no_flag());
asrcb.add_hostkey(host_key);
asrcb.add_hostkey(HostKey::V1(host_key));
asrcb.set_user_data(user_data, Some(skey)).unwrap();
asrcb.encrypt(&ctx).unwrap()
}
@@ -103,7 +110,7 @@ fn null_none_default_ncuid_one_user_unsgn() {
let mut asrcb =
AddSecretRequest::new(AddSecretVersion::One, GuestSecret::Null, TAGS, no_flag());
asrcb.add_hostkey(host_key);
asrcb.add_hostkey(HostKey::V1(host_key));
asrcb.set_user_data(user_data_orig.clone(), None).unwrap();
let asrcb = asrcb.encrypt(&ctx).unwrap();
@@ -236,7 +243,7 @@ fn null_none_default_cuid_seven() {
let (hkd, ctx) = get_crypto();
let mut asrcb =
AddSecretRequest::new(AddSecretVersion::One, GuestSecret::Null, TAGS, no_flag());
(0..7).for_each(|_| asrcb.add_hostkey(hkd.clone()));
(0..7).for_each(|_| asrcb.add_hostkey(HostKey::V1(hkd.clone())));
asrcb.set_cuid(CUID);
let asrcb = asrcb.encrypt(&ctx).unwrap();

View File

@@ -8,7 +8,7 @@ use anyhow::{bail, Context, Result};
use log::{debug, warn};
use pv::attest::{AttestationFlags, AttestationMeasAlg, AttestationRequest, AttestationVersion};
use pv::misc::{create_file, write_file};
use pv::request::{ReqEncrCtx, Request, SymKey, SymKeyType};
use pv::request::{HostKey, ReqEncrCtx, Request, SymKey, SymKeyType};
use crate::cli::{AttAddFlags, CreateAttOpt};
use crate::exchange::{ExchangeFormatRequest, ExchangeFormatVersion};
@@ -37,7 +37,7 @@ pub fn create(opt: &CreateAttOpt) -> Result<ExitCode> {
opt.certificate_args
.get_verified_hkds("attestation request")?
.into_iter()
.for_each(|k| arcb.add_hostkey(k));
.for_each(|k| arcb.add_hostkey(HostKey::V1(k)));
debug!("Added all host-keys");
let encr_ctx =

View File

@@ -11,8 +11,8 @@ use openssl::nid::Nid;
use openssl::pkey::{PKeyRef, Public};
use pv::request::openssl::pkey::{PKey, Private};
use pv::request::{
gen_ec_key, random_array, Aes256XtsKey, Confidential, EcPubKeyCoord, Encrypt, Keyslot, SymKey,
SymKeyType, Zeroize, SHA_512_HASH_LEN,
gen_ec_key, random_array, Aes256XtsKey, Confidential, EcPubKeyCoord, Encrypt, HostKey, Keyslot,
SymKey, SymKeyType, Zeroize, SHA_512_HASH_LEN,
};
use serde::{Deserialize, Serialize};
use utils::HexSlice;
@@ -419,7 +419,7 @@ impl KeyExchangeBuilderTrait for SeHdrDataV1 {
aead_key: &SymKey,
priv_key: &PKeyRef<Private>,
) -> Result<()> {
let keyslot = Keyslot::new(hostkey.to_owned());
let keyslot = Keyslot::new(HostKey::V1(hostkey.to_owned()));
let keyslot_bin = keyslot.encrypt(aead_key.value(), priv_key)?.try_into()?;
let keyslot_bin_size = u32::try_from(size_of_val(&keyslot_bin)).unwrap();
self.aad.keyslots.push(keyslot_bin);

View File

@@ -13,7 +13,9 @@ use pv::misc::{
try_parse_u128, try_parse_u64, write,
};
use pv::request::openssl::pkey::{PKey, Private};
use pv::request::{openssl, BootHdrTags, PolicyReference, ReqEncrCtx, Request, SymKeyType};
use pv::request::{
openssl, BootHdrTags, HostKey, PolicyReference, ReqEncrCtx, Request, SymKeyType,
};
use pv::secret::{AddSecretFlags, AddSecretRequest, AddSecretVersion, ExtSecret, GuestSecret};
use pv::uv::ConfigUid;
use serde_yaml::Value;
@@ -99,7 +101,7 @@ pub fn create(opt: &CreateSecretOpt) -> Result<()> {
opt.certificate_args
.get_verified_hkds("secret")?
.into_iter()
.for_each(|k| asrcb.add_hostkey(k));
.for_each(|k| asrcb.add_hostkey(HostKey::V1(k)));
debug!("Added all host-keys");