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>
This commit is contained in:
Timo Keller
2026-07-21 17:05:00 +02:00
committed by Steffen Eiden
parent fc853f3259
commit 9dca2d3181
6 changed files with 348 additions and 18 deletions
+31 -1
View File
@@ -5,7 +5,10 @@
use std::path::PathBuf;
use clap::{Args, Parser, Subcommand, ValueEnum, ValueHint};
use utils::{CertificateOptions, DeprecatedVerbosityOptions};
use utils::{
AutoOrExplicit, AutoOrExplicitParser, CertificateOptions, DeprecatedVerbosityOptions,
HkdVersion, ValueEnumDisplay, ValueEnumFromStr,
};
/// create, perform, and verify attestation measurements
///
@@ -67,6 +70,29 @@ pub enum Command {
Version,
}
/// Secure Execution attestation version for CLI
#[derive(Debug, Clone, Copy, PartialEq, Eq, ValueEnum, ValueEnumDisplay, ValueEnumFromStr)]
pub enum AttVersion {
#[value(name = "1")]
/// Version 1 - uses traditional cryptographic keys
V1,
#[value(name = "2")]
/// Version 2 - uses hybrid (post-quantum) cryptographic keys
V2,
}
pub type AttVersionSelection = AutoOrExplicit<AttVersion>;
pub type AttVersionSelectionParser = AutoOrExplicitParser<AttVersion>;
impl From<AttVersion> for HkdVersion {
fn from(val: AttVersion) -> Self {
match val {
AttVersion::V1 => Self::Classical,
AttVersion::V2 => Self::Hybrid,
}
}
}
#[derive(Args, Debug)]
pub struct CreateAttOpt {
#[command(flatten)]
@@ -94,6 +120,10 @@ pub struct CreateAttOpt {
value_delimiter = ','
)]
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,
}
#[derive(Debug, ValueEnum, Clone, Copy)]
+1 -1
View File
@@ -12,7 +12,7 @@ pub use check::check;
pub use create::create;
pub use verify::verify;
pub const CMD_FN: &[&str] = &["+create", "+verify"];
pub const CMD_FN: &[&str] = &["+create", "+verify", "+quantumsafe"];
// s390 branch
#[cfg(target_arch = "s390x")]
mod uv_cmd {
+42 -6
View File
@@ -10,7 +10,7 @@ use pv::attest::{AttestationFlags, AttestationMeasAlg, AttestationRequest, Attes
use pv::misc::{create_file, write_file};
use pv::request::{HostKey, ReqEncrCtx, Request, SymKey, SymKeyType};
use crate::cli::{AttAddFlags, CreateAttOpt};
use crate::cli::{AttAddFlags, AttVersion, AttVersionSelection, CreateAttOpt};
use crate::exchange::{ExchangeFormatRequest, ExchangeFormatVersion};
fn flags(cli_flags: &[AttAddFlags]) -> AttestationFlags {
@@ -26,18 +26,54 @@ fn flags(cli_flags: &[AttAddFlags]) -> AttestationFlags {
att_flags
}
/// Auto-detect the attestation version based on the host keys.
///
/// Returns Two if any host key is a hybrid key, otherwise returns V1.
fn auto_detect_version(host_keys: &[HostKey]) -> AttestationVersion {
let use_hybrid_keys = host_keys.iter().any(|k: &HostKey| k.is_hybrid());
if use_hybrid_keys {
AttestationVersion::Two
} else {
AttestationVersion::One
}
}
impl From<AttVersion> for AttestationVersion {
fn from(value: AttVersion) -> Self {
match value {
AttVersion::V1 => Self::One,
AttVersion::V2 => Self::Two,
}
}
}
/// Determine the attestation version to use.
///
/// If an explicit version is provided via CLI, use that.
/// Otherwise, auto-detect based on the host key types.
fn determine_version(
cli_version: AttVersionSelection,
host_keys: &[HostKey],
) -> AttestationVersion {
match cli_version {
AttVersionSelection::Auto => auto_detect_version(host_keys),
AttVersionSelection::Explicit(att_version) => att_version.into(),
}
}
pub fn create(opt: &CreateAttOpt) -> Result<ExitCode> {
let att_version = AttestationVersion::One;
let hkds = opt
.certificate_args
.get_verified_hkds_new("attestation request", opt.att_version.map(|v| v.into()))?;
let att_version = determine_version(opt.att_version, &hkds);
let meas_alg = AttestationMeasAlg::HmacSha512;
let mut arcb = AttestationRequest::new(att_version, meas_alg, flags(&opt.add_data))?;
debug!("Generated Attestation request");
// Add host-key documents
opt.certificate_args
.get_verified_hkds("attestation request")?
.into_iter()
.for_each(|k| arcb.add_hostkey(HostKey::V1(k)));
hkds.into_iter().for_each(|k| arcb.add_hostkey(k));
debug!("Added all host-keys");
let encr_ctx =
+57 -4
View File
@@ -11,6 +11,8 @@ use pv::request::MagicValue;
use pv::uv::{AttestationCmd, ConfigUid};
use zerocopy::{BigEndian, ByteOrder, FromBytes, Immutable, IntoBytes, KnownLayout, U32, U64};
use crate::additional;
const INV_EXCHANGE_FMT_ERROR_TEXT: &str = "The input has not the correct format:";
#[repr(C)]
@@ -99,10 +101,18 @@ impl ExchangeFormatV1Hdr {
let measurement_entry = Entry::from_exp(Some(measurement));
let exp_add = match additional {
0 => None,
size => Some(size),
size => {
if size > AttestationCmd::ADDITIONAL_MAX_SIZE {
bail!(
"Additional data size ({}) exceeds maximum allowed size ({})",
size,
AttestationCmd::ADDITIONAL_MAX_SIZE
);
}
Some(size)
}
};
// TODO min and max size check?
let additional_entry = Entry::from_exp(exp_add); //, AttestationCmd::ADDITIONAL_MAX_SIZE, &mut offset);
let additional_entry = Entry::from_exp(exp_add);
let user_entry = Entry::from_none();
let cuid_entry = Entry::from_none();
@@ -530,7 +540,6 @@ impl ExchangeFormatResponse {
"{INV_EXCHANGE_FMT_ERROR_TEXT} Contains no attestation request.",
))?;
// TODO remove unwrap
let measurement = hdr.measurement.read(reader)?.data().ok_or(anyhow!(
"{INV_EXCHANGE_FMT_ERROR_TEXT} Contains no attestation response (Measurement missing).",
))?;
@@ -693,6 +702,50 @@ mod test {
ExchangeFormatRequest::new(ARCB.to_vec(), 0, ADDITIONAL.len() as u32).unwrap_err();
}
#[test]
fn test_additional_data_size_validation() {
// Test for TODO 1 fix: Additional data size validation
let arcb = ARCB.to_vec();
// Test with valid size at maximum
let result = ExchangeFormatV1Hdr::new_request(
&arcb,
MEASUREMENT.len() as u32,
AttestationCmd::ADDITIONAL_MAX_SIZE,
);
assert!(
result.is_ok(),
"Maximum additional data size should be accepted"
);
// Test with size exceeding maximum
let result = ExchangeFormatV1Hdr::new_request(
&arcb,
MEASUREMENT.len() as u32,
AttestationCmd::ADDITIONAL_MAX_SIZE + 1,
);
assert!(
result.is_err(),
"Additional data size exceeding maximum should fail"
);
if let Err(e) = result {
let error_msg = e.to_string();
assert!(
error_msg.contains("exceeds maximum"),
"Error should mention exceeding maximum: {}",
error_msg
);
}
// Test with zero size (no additional data)
let result = ExchangeFormatV1Hdr::new_request(&arcb, MEASUREMENT.len() as u32, 0);
assert!(
result.is_ok(),
"Zero additional data size should be accepted"
);
}
#[test]
fn min_req() {
test_read_write_request(