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>
This commit is contained in:
Steffen Eiden
2026-07-22 17:24:38 +02:00
parent 1078e6d45f
commit 73c227fa9a
4 changed files with 51 additions and 25 deletions

View File

@@ -4,7 +4,7 @@
//! IBM Z Host key-slot implementations.
use openssl::hash::MessageDigest;
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};
@@ -101,6 +101,19 @@ impl KeyslotV2 {
}
}
/// 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
@@ -132,12 +145,6 @@ impl KeyslotV2 {
priv_key: &PKeyRef<Private>,
to: &mut Vec<u8>,
) -> Result<()> {
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 (derived_key, ciphertext) =
derive_aes256_gcm_key_hybrid(priv_key, &self.ec_hostkey, &self.mlkem_hostkey)?;
let mut wrpk_and_kst =
@@ -145,7 +152,7 @@ impl KeyslotV2 {
assert_eq!(wrpk_and_kst.len(), 48);
to.reserve(1680);
let hash = hash(MessageDigest::sha512(), &phk_buf)?;
let hash = self.sha512()?;
assert_eq!(hash.len(), 64);
to.extend_from_slice(&hash);
to.append(&mut wrpk_and_kst);

View File

@@ -6,8 +6,8 @@ use std::path::PathBuf;
use clap::{Args, Parser, Subcommand, ValueEnum, ValueHint};
use utils::{
AutoOrExplicit, AutoOrExplicitParser, CertificateOptions, DeprecatedVerbosityOptions,
HkdVersion, ValueEnumDisplay, ValueEnumFromStr,
AutoOrExplicit, CertificateOptions, DeprecatedVerbosityOptions, HkdVersion, ValueEnumDisplay,
ValueEnumFromStr,
};
/// create, perform, and verify attestation measurements
@@ -82,7 +82,6 @@ pub enum AttVersion {
}
pub type AttVersionSelection = AutoOrExplicit<AttVersion>;
pub type AttVersionSelectionParser = AutoOrExplicitParser<AttVersion>;
impl From<AttVersion> for HkdVersion {
fn from(val: AttVersion) -> Self {
@@ -122,8 +121,8 @@ pub struct CreateAttOpt {
pub add_data: Vec<AttAddFlags>,
/// Specify the Attestation Request version to use.
#[arg(long = "att-version", value_name = "VERSION", default_value_t = AttVersionSelection::Explicit(AttVersion::V1), value_parser = AttVersionSelectionParser::default())]
pub att_version: AttVersionSelection,
#[arg(long = "att-version", value_name = "VERSION", default_value_t = AttVersion::V1)]
pub att_version: AttVersion,
}
#[derive(Debug, ValueEnum, Clone, Copy)]

View File

@@ -6,10 +6,9 @@ use std::fmt::Display;
use std::path::Path;
use anyhow::Result;
use log::{debug, info};
use log::{debug, info, warn};
use pv::misc::{read_certs, read_file};
use pv::request::openssl::DigestBytes;
use pv::request::EcPubKeyCoord;
use pv::request::{EcPubKeyCoord, HybridPKey, KeyslotV2};
use serde::Serialize;
use utils::HexSlice;
@@ -36,7 +35,7 @@ impl Display for HkCheck {
}
}
fn load_host_keys<A: AsRef<Path>>(hkds: &[A]) -> Result<Vec<(&Path, DigestBytes)>> {
fn load_host_keys<A: AsRef<Path>>(hkds: &[A]) -> Result<Vec<(&Path, Vec<u8>)>> {
let mut hkd_hash = Vec::with_capacity(hkds.len());
for hkd in hkds {
let hkd = hkd.as_ref();
@@ -45,21 +44,41 @@ fn load_host_keys<A: AsRef<Path>>(hkds: &[A]) -> Result<Vec<(&Path, DigestBytes)
hkd: hkd.display().to_string(),
source,
})?;
let ec_coord: EcPubKeyCoord = certs.first().unwrap().public_key()?.as_ref().try_into()?;
hkd_hash.push((hkd, ec_coord.sha256()?));
let ec_key = certs.first().unwrap().public_key()?;
match certs.len() {
1 => {
let ec_coord: EcPubKeyCoord = ec_key.as_ref().try_into()?;
hkd_hash.push((hkd, ec_coord.sha256()?.as_ref().to_vec()));
}
2 => {
let mlkem_key = certs[1].public_key()?;
let ks = KeyslotV2::new(HybridPKey::new(ec_key, mlkem_key)?);
let hash = ks.sha512()?;
hkd_hash.push((hkd, hash[..32].to_vec()));
}
_ => {
warn!(
"The host-key document in '{}' contains more than two certificates!",
hkd.display()
);
Err(pv::Error::WrongNumberOfKeys(hkd.display().to_string()))?;
}
}
}
Ok(hkd_hash)
}
fn contains_phkh<'a>(
hkd_hashes: &[(&'a Path, DigestBytes)],
hkd_hashes: &[(&'a Path, Vec<u8>)],
phkh: &HexSlice<'_>,
mode: HkCheck,
check_enforced: bool,
) -> CheckState<HostKeyCheck<'a>> {
let hk: Vec<_> = hkd_hashes
.iter()
.filter_map(|(path, hash)| match hash.as_ref() == phkh.as_ref() {
.filter_map(|(path, hash)| match hash == phkh.as_ref() {
true => Some(*path),
false => None,
})

View File

@@ -62,11 +62,12 @@ fn determine_version(
}
pub fn create(opt: &CreateAttOpt) -> Result<ExitCode> {
let hkds = opt
.certificate_args
.get_verified_hkds_new("attestation request", opt.att_version.map(|v| v.into()))?;
let hkds = opt.certificate_args.get_verified_hkds_new(
"attestation request",
AttVersionSelection::Explicit(opt.att_version).map(|v| v.into()),
)?;
let att_version = determine_version(opt.att_version, &hkds);
let att_version = determine_version(AttVersionSelection::Explicit(opt.att_version), &hkds);
let meas_alg = AttestationMeasAlg::HmacSha512;
let mut arcb = AttestationRequest::new(att_version, meas_alg, flags(&opt.add_data))?;