diff --git a/rust/pv/src/brcb.rs b/rust/pv/src/brcb.rs index 3aaec8c0..2d5939f9 100644 --- a/rust/pv/src/brcb.rs +++ b/rust/pv/src/brcb.rs @@ -17,6 +17,8 @@ use crate::{assert_size, request::MagicValue, static_assert, Error, Result, PAGE pub enum SeHdrVersion { /// Secure Execution header v1 One = 0x100, + /// Secure Execution header v2 + Two = 0x200, } /// Struct containing all SE-header tags. @@ -241,6 +243,7 @@ impl BootHdrTags { // Some sanity checks 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); diff --git a/rust/pv/src/lib.rs b/rust/pv/src/lib.rs index 98ab383d..b52ec491 100644 --- a/rust/pv/src/lib.rs +++ b/rust/pv/src/lib.rs @@ -89,14 +89,15 @@ pub use crate::error::HkdVerifyErrorType; /// Functionalities to build UV requests pub mod request { - pub use crate::brcb::{seek_se_hdr_start, BootHdrTags, SeImgMetaData}; + 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, ReqEncrCtx, Request, + EcPubKeyCoord, Encrypt, HostKey, HybridPKey, Keyslot, KeyslotV1, KeyslotV2, ReqEncrCtx, + Request, }; pub use crate::verify::{CertVerifier, HkdVerifier, NoVerifyHkd}; diff --git a/rust/pv/src/req/mod.rs b/rust/pv/src/req/mod.rs index 3869dd15..301c0d7a 100644 --- a/rust/pv/src/req/mod.rs +++ b/rust/pv/src/req/mod.rs @@ -22,6 +22,5 @@ pub use ec_coord::EcPubKeyCoord; pub use encrypt::{Aad, Encrypt}; pub use header::RequestHdr; pub use hostkey::{HostKey, HybridPKey}; -#[expect(unused)] pub use keyslot::{Keyslot, KeyslotV1, KeyslotV2}; pub use request::{BinReqValues, Request}; diff --git a/rust/pvimg/src/cli.rs b/rust/pvimg/src/cli.rs index 726fa258..792917c2 100644 --- a/rust/pvimg/src/cli.rs +++ b/rust/pvimg/src/cli.rs @@ -13,7 +13,10 @@ use std::string::ToString; use clap::builder::{PossibleValue, TypedValueParser}; use clap::{Arg, ArgGroup, Args, Command, CommandFactory, Parser, ValueEnum, ValueHint}; use log::warn; -use utils::{CertificateOptions, DeprecatedVerbosityOptions, ValueEnumDisplay}; +use utils::{ + AutoOrExplicit, CertificateOptions, DeprecatedVerbosityOptions, HkdVersion, ValueEnumDisplay, + ValueEnumFromStr, +}; /// SE header control flags for CLI #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, ValueEnum, ValueEnumDisplay)] @@ -39,6 +42,28 @@ pub enum SeHdrFlagName { NoComponentEncryption, } +/// Secure Execution header version for CLI +#[derive(Debug, Clone, Copy, PartialEq, Eq, ValueEnum, ValueEnumDisplay, ValueEnumFromStr)] +pub enum HdrVersion { + #[value(name = "1")] + /// Version 1 - uses traditional cryptographic keys + V1, + #[value(name = "2")] + /// Version 2 - uses hybrid (post-quantum) cryptographic keys + V2, +} + +pub type HdrVersionSelection = AutoOrExplicit; + +impl From for HkdVersion { + fn from(val: HdrVersion) -> Self { + match val { + HdrVersion::V1 => Self::Classical, + HdrVersion::V2 => Self::Hybrid, + } + } +} + /// Create and inspect IBM Secure Execution images. /// /// Use pvimg to create an IBM Secure Execution image, which can be loaded using @@ -318,11 +343,13 @@ pub struct CreateBootImageLegacyFlags { #[arg(long, action = clap::ArgAction::SetTrue, conflicts_with="enable_pckmo", group="header-flags")] pub disable_pckmo: Option, - /// Enable the support for the HMAC PCKMO key encryption function. + /// Enable the support for the HMAC PCKMO key encryption function (default for header version + /// 2). #[arg(long, action = clap::ArgAction::SetTrue, group="header-flags")] pub enable_pckmo_hmac: Option, - /// Disable the support for the HMAC PCKMO key encryption function (default). + /// Disable the support for the HMAC PCKMO key encryption function (default for header version + /// 1). #[arg(long, action = clap::ArgAction::SetTrue, conflicts_with="enable_pckmo_hmac", group="header-flags")] pub disable_pckmo_hmac: Option, @@ -653,7 +680,6 @@ impl GenprotimgCliOptions { } #[derive(Parser, Debug)] -#[cfg_attr(test, derive(Default))] pub struct CreateBootImageArgs { #[clap(flatten)] pub component_paths: ComponentPaths, @@ -676,6 +702,10 @@ pub struct CreateBootImageArgs { #[arg(long)] pub overwrite: bool, + /// Specify the Secure Execution header version to use. + #[arg(long = "hdr-version", value_name = "VERSION", default_value_t = HdrVersion::V1)] + pub hdr_version: HdrVersion, + #[clap(flatten)] pub keys: UserKeys, @@ -709,6 +739,25 @@ pub struct CreateBootImageArgs { pub experimental_args: CreateBootImageExperimentalArgs, } +#[cfg(test)] +impl Default for CreateBootImageArgs { + fn default() -> Self { + Self { + component_paths: ComponentPaths::default(), + output: PathBuf::new(), + certificate_args: CertificateOptions::default(), + no_component_check: false, + overwrite: false, + hdr_version: HdrVersion::V1, + keys: UserKeys::default(), + legacy_flags: CreateBootImageLegacyFlags::default(), + flags: Vec::new(), + disable_flags: Vec::new(), + experimental_args: CreateBootImageExperimentalArgs::default(), + } + } +} + /// Experimental options #[derive(Args, Debug)] #[cfg_attr(test, derive(Default))] @@ -985,6 +1034,9 @@ mod test { // Test with NoComponentEncryption flag flat_map_collect(insert(mvca.clone(), vec![CliOption::new("flags", ["--flags", &SeHdrFlagName::NoComponentEncryption.to_string()])])), + + // Test with HdrVersion V2 + flat_map_collect(insert(mvca.clone(), vec![CliOption::new("hdr-version", ["--hdr-version", "2"])])), ]; // Invalid test cases grouped by expected error kind let invalid_missing_required = [ @@ -1480,6 +1532,12 @@ mod test { ); } + #[test] + fn test_hdr_version_conversions() { + assert_eq!(HkdVersion::from(HdrVersion::V1), HkdVersion::Classical); + assert_eq!(HkdVersion::from(HdrVersion::V2), HkdVersion::Hybrid); + } + #[test] fn test_output_format_kind_display() { assert_eq!(OutputFormatKind::Text.to_string(), "human-readable"); diff --git a/rust/pvimg/src/cmd.rs b/rust/pvimg/src/cmd.rs index 8fda1029..b1f8a8d0 100644 --- a/rust/pvimg/src/cmd.rs +++ b/rust/pvimg/src/cmd.rs @@ -8,7 +8,7 @@ mod info; mod test; mod version; -pub const CMD_FN: &[&str] = &["+create", "+test", "+info"]; +pub const CMD_FN: &[&str] = &["+create", "+test", "+info", "+quantumsafe"]; pub use create::create; pub use info::info; diff --git a/rust/pvimg/src/cmd/create.rs b/rust/pvimg/src/cmd/create.rs index 57f4bd4a..243bdfe5 100644 --- a/rust/pvimg/src/cmd/create.rs +++ b/rust/pvimg/src/cmd/create.rs @@ -8,15 +8,18 @@ use std::io::BufReader; use anyhow::{anyhow, Context, Result}; use log::{debug, info, warn}; use pv::misc::{open_file, try_parse_u64}; +use pv::request::HostKey; use pvimg::error::OwnExitCode; use pvimg::secured_comp::ComponentTrait; use pvimg::uvdata::{ EffectiveControlFlags, FlagState, FlagsOverride, SeHdrControlFlags, SeHdrControlFlagsModel, - SeHdrDataV1, SeHdrFlag, SeHdrVersion, SeTarget, + SeHdrDataV1, SeHdrDataV2, SeHdrFlag, SeHdrVersion, SeTarget, }; use utils::{AtomicFile, AtomicFileOperation}; -use crate::cli::{ComponentPaths, CreateBootImageArgs, SeHdrFlagName}; +use crate::cli::{ + ComponentPaths, CreateBootImageArgs, HdrVersion, HdrVersionSelection, SeHdrFlagName, +}; use crate::cmd::common::read_user_provided_keys; use crate::se_img::{SeHdrArgs, SeImgBuilder}; use crate::se_img_comps::cmdline::Cmdline; @@ -274,15 +277,54 @@ fn parse_flags( Ok((pcf, scf)) } +/// Auto-detect the SE header version based on the host keys. +/// +/// Returns V2 if any host key is a hybrid key, otherwise returns V1. +fn auto_detect_version(host_keys: &[HostKey]) -> SeHdrVersion { + let use_hybrid_keys = host_keys.iter().any(|k: &HostKey| k.is_hybrid()); + if use_hybrid_keys { + SeHdrVersion::V2 + } else { + SeHdrVersion::V1 + } +} + +impl From for SeHdrVersion { + fn from(value: HdrVersion) -> Self { + match value { + HdrVersion::V1 => Self::V1, + HdrVersion::V2 => Self::V2, + } + } +} + +/// Determine the SE header 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: HdrVersionSelection, host_keys: &[HostKey]) -> SeHdrVersion { + match cli_version { + HdrVersionSelection::Auto => auto_detect_version(host_keys), + HdrVersionSelection::Explicit(hdr_version) => hdr_version.into(), + } +} + /// Create a Secure Execution boot image pub fn create(opt: &CreateBootImageArgs) -> Result { // Verify host key documents first, because if they are not valid there is // no reason to continue. - let verified_host_keys = opt - .certificate_args - .get_verified_hkds("Secure Execution image")?; + let verified_host_keys = opt.certificate_args.get_verified_hkds_new( + "Secure Execution image", + HdrVersionSelection::Explicit(opt.hdr_version).map(|v| v.into()), + )?; + + let version = determine_version( + HdrVersionSelection::Explicit(opt.hdr_version), + &verified_host_keys, + ); + let user_provided_keys = read_user_provided_keys(&opt.keys)?; - let (plaintext_flags, secret_flags) = parse_flags(opt, SeHdrVersion::V1)?; + let (plaintext_flags, secret_flags) = parse_flags(opt, version)?; if plaintext_flags.has(SeHdrFlag::NoComponentEncryption) { warn!("The components encryption is disabled, make sure that the components do not contain any confidential content."); @@ -297,9 +339,13 @@ pub fn create(opt: &CreateBootImageArgs) -> Result { // FIXME get rid of the legacy mode. But that's only possible as soon as all // available tools are updated. - let expected_se_hdr_size = SeHdrDataV1::expected_size(verified_host_keys.len())?; + let expected_se_hdr_size = match version { + SeHdrVersion::V1 => SeHdrDataV1::expected_size(verified_host_keys.len())?, + SeHdrVersion::V2 => SeHdrDataV2::expected_size(verified_host_keys.len())?, + _ => return Err(anyhow!("Unsupported SE header version: {:?}", version)), + }; let mut writer = AtomicFile::with_extension(&opt.output, "part", &mut OpenOptions::new())?; - let mut seimg_ctx = SeImgBuilder::new_v1( + let mut seimg_ctx = SeImgBuilder::new( &mut writer, !plaintext_flags.has(SeHdrFlag::NoComponentEncryption), Some(expected_se_hdr_size), @@ -402,6 +448,37 @@ mod test { assert_eq!(parsed_flags.1, expected_scf); } + #[test] + fn test_auto_detect_version_v1() { + // Mock non-hybrid keys - should return V1 + // Note: This is a simplified test. In real usage, you'd need actual HostKey instances + let keys: Vec = vec![]; + let version = auto_detect_version(&keys); + assert_eq!(version, SeHdrVersion::V1); + } + + #[test] + fn test_determine_version_explicit_v1() { + let keys: Vec = vec![]; + let version = determine_version(HdrVersionSelection::Explicit(HdrVersion::V1), &keys); + assert_eq!(version, SeHdrVersion::V1); + } + + #[test] + fn test_determine_version_explicit_v2() { + let keys: Vec = vec![]; + let version = determine_version(HdrVersionSelection::Explicit(HdrVersion::V2), &keys); + assert_eq!(version, SeHdrVersion::V2); + } + + #[test] + fn test_determine_version_auto_detect() { + let keys: Vec = vec![]; + let version = determine_version(HdrVersionSelection::Auto, &keys); + // With empty keys, should default to V1 + assert_eq!(version, SeHdrVersion::V1); + } + #[test] fn parse_flags_with_disable_flags_no_conflict() { let args = CreateBootImageArgs { diff --git a/rust/pvimg/src/cmd/test.rs b/rust/pvimg/src/cmd/test.rs index 4a1b114d..14643601 100644 --- a/rust/pvimg/src/cmd/test.rs +++ b/rust/pvimg/src/cmd/test.rs @@ -6,9 +6,9 @@ use std::path::Path; use anyhow::Result; use log::{info, warn}; -use pv::misc::{open_file, read_certs, read_file}; +use pv::misc::{open_file, read_hkd}; use pv::{FileAccessErrorType, PvCoreError}; -use pvimg::error::{Error, OwnExitCode, PvError}; +use pvimg::error::{Error, OwnExitCode}; use pvimg::uvdata::{KeyExchangeTrait, SeHdr, UvKeyHashesV1}; use utils::HexSlice; @@ -72,27 +72,17 @@ where let mut result = false; for path in host_key_documents { - let hkd_path = path.as_ref(); - let hkd_data = read_file(hkd_path, "host key document")?; - let certs = read_certs(&hkd_data)?; - if certs.is_empty() { - return Err(PvError::NoHkdInFile(hkd_path.display().to_string()).into()); - } - - if certs.len() != 1 { - warn!("The host key document in '{}' contains more than one certificate! Only the first certificate will be used.", - hkd_path.display()); - } - - // Panic: len is == 1 -> unwrap will succeed/not panic - let cert = certs.first().unwrap(); - if hdr.contains(cert.public_key()?)? { + let hkd = read_hkd(path)?; + if hdr.contains(hkd)? { result = true; - log_println!(" ✓ Host key document '{}' is included", hkd_path.display()); + log_println!( + " ✓ Host key document '{}' is included", + path.as_ref().display() + ); } else { log_println!( " ✘ Host key document '{}' is not included", - hkd_path.display() + path.as_ref().display() ); } } diff --git a/rust/pvimg/src/lib.rs b/rust/pvimg/src/lib.rs index 002adfad..5d229eb4 100644 --- a/rust/pvimg/src/lib.rs +++ b/rust/pvimg/src/lib.rs @@ -39,8 +39,9 @@ pub mod uvdata { AeadPlainDataTrait, BuilderTrait, ComponentMetadataV1, ControlFlagTrait, EffectiveControlFlags, EnvelopeSeHdrV1, FlagData, FlagState, FlagsOverride, IntoEnumIterator, KeyExchangeTrait, SeH, SeHdr, SeHdrAadV1, SeHdrBinV1, SeHdrBuilder, - SeHdrControlFlags, SeHdrControlFlagsModel, SeHdrData, SeHdrDataV1, SeHdrFlag, SeHdrPlain, - SeHdrVersion, SeHdrVersioned, SeTarget, UvDataPlainTrait, UvDataTrait, UvKeyHashesV1, + SeHdrControlFlags, SeHdrControlFlagsModel, SeHdrData, SeHdrDataV1, SeHdrDataV2, SeHdrFlag, + SeHdrPlain, SeHdrVersion, SeHdrVersioned, SeTarget, UvDataPlainTrait, UvDataTrait, + UvKeyHashesV1, }; } diff --git a/rust/pvimg/src/pv_utils.rs b/rust/pvimg/src/pv_utils.rs index 0c6955a8..8b67ddfc 100644 --- a/rust/pvimg/src/pv_utils.rs +++ b/rust/pvimg/src/pv_utils.rs @@ -20,8 +20,8 @@ pub use psw::{ShortPsw, PSW, PSW_MASK_BA, PSW_MASK_EA}; pub use se_hdr::{ ComponentMetadataV1, ControlFlagTrait, EffectiveControlFlags, EnvelopeSeHdrV1, FlagData, FlagState, FlagsOverride, IntoEnumIterator, SeH, SeHdr, SeHdrAadV1, SeHdrBinV1, SeHdrBuilder, - SeHdrControlFlags, SeHdrControlFlagsModel, SeHdrData, SeHdrDataV1, SeHdrFlag, SeHdrPlain, - SeHdrVersion, SeHdrVersioned, SeTarget, + SeHdrControlFlags, SeHdrControlFlagsModel, SeHdrData, SeHdrDataV1, SeHdrDataV2, SeHdrFlag, + SeHdrPlain, SeHdrVersion, SeHdrVersioned, SeTarget, }; pub use secured_comp::{ComponentTrait, SecuredComponent, SecuredComponentBuilder}; pub use serializing::{bytesize, serialize_to_bytes}; diff --git a/rust/pvimg/src/pv_utils/error.rs b/rust/pvimg/src/pv_utils/error.rs index f3e4b719..1a2bdfa8 100644 --- a/rust/pvimg/src/pv_utils/error.rs +++ b/rust/pvimg/src/pv_utils/error.rs @@ -126,6 +126,12 @@ pub enum Error { max_output_size: usize, }, + #[error("Operation {operation} not supported")] + UnsupportedOperation { operation: String }, + + #[error("Unsupported SE header version: {0:?}")] + UnsupportedSeHdrVersion(pv::request::SeHdrVersion), + // Errors from other crates #[error(transparent)] Deku(#[from] deku::DekuError), diff --git a/rust/pvimg/src/pv_utils/se_hdr.rs b/rust/pvimg/src/pv_utils/se_hdr.rs index 2de26b4f..0c6a8405 100644 --- a/rust/pvimg/src/pv_utils/se_hdr.rs +++ b/rust/pvimg/src/pv_utils/se_hdr.rs @@ -7,11 +7,12 @@ mod builder; mod flags; mod generic_flags; mod hdr_v1; +mod hdr_v2; mod keys; pub use brb::{ - ComponentMetadata, ComponentMetadataV1, EnvelopeSeHdrV1, SeH, SeHdr, SeHdrBinV1, SeHdrData, - SeHdrDataV1, SeHdrPlain, SeHdrVersion, SeHdrVersioned, + ComponentMetadata, ComponentMetadataV1, EnvelopeSeHdrV1, SeH, SeHdr, SeHdrBinV1, SeHdrBinV2, + SeHdrData, SeHdrDataV1, SeHdrDataV2, SeHdrPlain, SeHdrVersion, SeHdrVersioned, }; pub use builder::SeHdrBuilder; pub use flags::{ diff --git a/rust/pvimg/src/pv_utils/se_hdr/brb.rs b/rust/pvimg/src/pv_utils/se_hdr/brb.rs index 976a58bd..41c791c5 100644 --- a/rust/pvimg/src/pv_utils/se_hdr/brb.rs +++ b/rust/pvimg/src/pv_utils/se_hdr/brb.rs @@ -8,13 +8,14 @@ use std::mem::size_of; use deku::ctx::Endian; use deku::prelude::*; use enum_dispatch::enum_dispatch; -use pv::request::openssl::pkey::{PKey, PKeyRef, Private, Public}; +use pv::request::openssl::pkey::{PKey, Private, Public}; use pv::request::{seek_se_hdr_start, Aes256XtsKey, Confidential, SymKey, SymKeyType}; use pv::static_assert; use serde::{Deserialize, Serialize}; use utils::S390ToolsMetaData; pub use super::hdr_v1::{SeHdrBinV1, SeHdrDataV1}; +pub use super::hdr_v2::{SeHdrBinV2, SeHdrDataV2}; use super::{EffectiveControlFlags, SeHdrFlag}; use crate::misc::PAGESIZE; use crate::pv_utils::error::{Error, Result}; @@ -60,6 +61,8 @@ impl EnvelopeSeHdrV1 { pub enum SeHdrVersion { /// Secure Execution header v1 V1 = 0x100, + /// Secure Execution header v2 + V2 = 0x200, } impl Display for SeHdrVersion { @@ -69,6 +72,7 @@ impl Display for SeHdrVersion { "{}", match self { SeHdrVersion::V1 => "1", + SeHdrVersion::V2 => "2", } ) } @@ -204,13 +208,53 @@ impl Display for SeHdrPlain { } } -#[enum_dispatch(AeadCipherTrait, AeadDataTrait, KeyExchangeTrait)] +#[non_exhaustive] +#[enum_dispatch(AeadCipherTrait, AeadDataTrait)] #[derive(Clone, PartialEq, Eq, Debug, DekuRead, DekuWrite, Serialize, Deserialize)] #[serde(untagged)] #[deku(ctx = "_endian: Endian, version: SeHdrVersion", id = "version")] pub enum SeHdrVersioned { #[deku(id = "SeHdrVersion::V1")] SeHdrBinV1(SeHdrBinV1), + #[deku(id = "SeHdrVersion::V2")] + SeHdrBinV2(SeHdrBinV2), +} + +impl KeyExchangeTrait for SeHdrVersioned { + type PrivateKeyType = PKey; + type TargetKeyType = pv::request::HostKey; + + fn contains(&self, key: K) -> Result + where + K: AsRef, + { + match (self, key.as_ref()) { + (SeHdrVersioned::SeHdrBinV1(data), pv::request::HostKey::V1(key)) => data.contains(key), + (SeHdrVersioned::SeHdrBinV2(data), pv::request::HostKey::V2(key)) => data.contains(key), + (_, _) => Err(Error::InvalidSeHdr), + } + } + + fn contains_hash>(&self, hash: H) -> bool { + match self { + SeHdrVersioned::SeHdrBinV1(data) => data.contains_hash(hash), + SeHdrVersioned::SeHdrBinV2(data) => data.contains_hash(hash), + } + } + + fn cust_pub_key(&mut self) -> Result> { + match self { + SeHdrVersioned::SeHdrBinV1(data) => data.cust_pub_key(), + SeHdrVersioned::SeHdrBinV2(data) => data.cust_pub_key(), + } + } + + fn key_type(&self) -> SymKeyType { + match self { + SeHdrVersioned::SeHdrBinV1(data) => data.key_type(), + SeHdrVersioned::SeHdrBinV2(data) => data.key_type(), + } + } } impl Display for SeHdrVersioned { @@ -223,10 +267,18 @@ impl Display for SeHdrVersioned { write!(f, "{se_hdr_bin_v1}") } } + SeHdrVersioned::SeHdrBinV2(se_hdr_bin_v2) => { + if f.alternate() { + write!(f, "{se_hdr_bin_v2:#}") + } else { + write!(f, "{se_hdr_bin_v2}") + } + } } } } +#[non_exhaustive] #[enum_dispatch( AeadCipherTrait, AeadPlainDataTrait, @@ -239,6 +291,8 @@ impl Display for SeHdrVersioned { pub enum SeHdrData { #[deku(id = "SeHdrVersion::V1")] SeHdrDataV1(SeHdrDataV1), + #[deku(id = "SeHdrVersion::V2")] + SeHdrDataV2(SeHdrDataV2), } impl Display for SeHdrData { @@ -251,6 +305,13 @@ impl Display for SeHdrData { write!(f, "{data_v1}") } } + SeHdrData::SeHdrDataV2(data_v2) => { + if f.alternate() { + write!(f, "{data_v2:#}") + } else { + write!(f, "{data_v2}") + } + } } } } @@ -259,6 +320,7 @@ impl AeadCipherBuilderTrait for SeHdrData { fn set_iv(&mut self, iv: &[u8]) -> Result<()> { match self { Self::SeHdrDataV1(data) => data.set_iv(iv), + Self::SeHdrDataV2(data) => data.set_iv(iv), } } } @@ -316,14 +378,13 @@ impl AeadDataTrait for SeHdr { } impl KeyExchangeTrait for SeHdr { + type PrivateKeyType = PKey; + type TargetKeyType = pv::request::HostKey; + fn contains_hash>(&self, hash: H) -> bool { self.data.contains_hash(hash) } - fn contains>>(&self, key: K) -> Result { - self.data.contains(key) - } - fn cust_pub_key(&mut self) -> Result> { self.data.cust_pub_key() } @@ -331,6 +392,13 @@ impl KeyExchangeTrait for SeHdr { fn key_type(&self) -> SymKeyType { self.aead_key_type() } + + fn contains(&self, key: K) -> Result + where + K: AsRef, + { + self.data.contains(key) + } } impl UvDataTrait for SeHdr { @@ -407,25 +475,46 @@ impl AeadCipherBuilderTrait for SeHdrPlain { } impl KeyExchangeBuilderTrait for SeHdrPlain { + type AeadKeyType = SymKey; + type PrivateKeyType = PKey; + type TargetKeyType = pv::request::HostKey; + fn add_keyslot( &mut self, - hostkey: &PKeyRef, - aead_key: &SymKey, - priv_key: &PKeyRef, + hostkey: &Self::TargetKeyType, + aead_key: &Self::AeadKeyType, + priv_key: &Self::PrivateKeyType, ) -> Result<()> { - self.data.add_keyslot(hostkey, aead_key, priv_key) + match (&mut self.data, hostkey) { + (SeHdrData::SeHdrDataV1(data), pv::request::HostKey::V1(key)) => { + data.add_keyslot(key, aead_key, priv_key) + } + (SeHdrData::SeHdrDataV2(data), pv::request::HostKey::V2(key)) => { + data.add_keyslot(key, aead_key, priv_key) + } + (_, _) => Err(Error::InvalidSeHdr), + } } fn clear_keyslots(&mut self) -> Result<()> { - self.data.clear_keyslots() + match &mut self.data { + SeHdrData::SeHdrDataV1(data) => data.clear_keyslots(), + SeHdrData::SeHdrDataV2(data) => data.clear_keyslots(), + } } fn generate_private_key(&self) -> Result> { - self.data.generate_private_key() + match &self.data { + SeHdrData::SeHdrDataV1(data) => data.generate_private_key(), + SeHdrData::SeHdrDataV2(data) => data.generate_private_key(), + } } - fn set_cust_public_key(&mut self, key: &PKeyRef) -> Result<()> { - self.data.set_cust_public_key(key) + fn set_cust_public_key(&mut self, key: &Self::PrivateKeyType) -> Result<()> { + match &mut self.data { + SeHdrData::SeHdrDataV1(data) => data.set_cust_public_key(key), + SeHdrData::SeHdrDataV2(data) => data.set_cust_public_key(key), + } } } @@ -451,20 +540,39 @@ pub enum ComponentMetadata { } impl KeyExchangeTrait for SeHdrPlain { - fn contains>>(&self, key: K) -> Result { - self.data.contains(key) - } + type PrivateKeyType = PKey; + type TargetKeyType = pv::request::HostKey; fn cust_pub_key(&mut self) -> Result> { - self.data.cust_pub_key() + match &mut self.data { + SeHdrData::SeHdrDataV1(data) => data.cust_pub_key(), + SeHdrData::SeHdrDataV2(data) => data.cust_pub_key(), + } } fn key_type(&self) -> SymKeyType { - self.data.key_type() + match &self.data { + SeHdrData::SeHdrDataV1(data) => data.key_type(), + SeHdrData::SeHdrDataV2(data) => data.key_type(), + } } fn contains_hash>(&self, hash: H) -> bool { - self.data.contains_hash(hash) + match &self.data { + SeHdrData::SeHdrDataV1(data) => data.contains_hash(hash), + SeHdrData::SeHdrDataV2(data) => data.contains_hash(hash), + } + } + + fn contains(&self, key: K) -> Result + where + K: AsRef, + { + match (&self.data, key.as_ref()) { + (SeHdrData::SeHdrDataV1(data), pv::request::HostKey::V1(key)) => data.aad.contains(key), + (SeHdrData::SeHdrDataV2(data), pv::request::HostKey::V2(key)) => data.aad.contains(key), + (_, _) => Err(Error::InvalidSeHdr), + } } } diff --git a/rust/pvimg/src/pv_utils/se_hdr/builder.rs b/rust/pvimg/src/pv_utils/se_hdr/builder.rs index 1223848c..822a5199 100644 --- a/rust/pvimg/src/pv_utils/se_hdr/builder.rs +++ b/rust/pvimg/src/pv_utils/se_hdr/builder.rs @@ -5,6 +5,7 @@ use pv::request::Confidential; use super::hdr_v1::SeHdrDataV1; +use super::hdr_v2::SeHdrDataV2; use super::{EffectiveControlFlags, SeHdr, SeHdrFlag}; use crate::pv_utils::error::{Error, Result}; use crate::pv_utils::se_hdr::brb::{ @@ -18,7 +19,7 @@ use crate::pv_utils::uvdata_builder::{ use crate::pv_utils::PSW; /// `SeHdrBuilder` -pub type SeHdrBuilder<'a> = UvDataBuilder<'a, SeHdrPlain>; +pub type SeHdrBuilder<'a> = UvDataBuilder; impl SeHdrBuilder<'_> { pub fn new>( @@ -40,6 +41,19 @@ impl SeHdrBuilder<'_> { data.set_cust_public_key(&priv_key)?; (SeHdrData::SeHdrDataV1(data), aead_key, priv_key) } + SeHdrVersion::V2 => { + let mut data = SeHdrDataV2::new( + psw, + components_meta + .into() + .try_into() + .map_err(|_| Error::InvalidComponentMetadata)?, + )?; + let aead_key = data.generate_aead_key()?; + let priv_key = data.generate_private_key()?; + data.set_cust_public_key(&priv_key)?; + (SeHdrData::SeHdrDataV2(data), aead_key, priv_key) + } }; let common = SeHdrCommon::new(version); let hdr = SeHdrPlain { common, data }; @@ -95,8 +109,8 @@ impl BuilderTrait for SeHdrBuilder<'_> { mod tests { use std::io::Cursor; - use pv::request::{Confidential, SymKeyType, SHA_512_HASH_LEN}; - use pv::test_utils::get_test_key_and_cert; + use pv::request::{Confidential, HostKey, HybridPKey, SymKeyType, SHA_512_HASH_LEN}; + use pv::test_utils::{get_test_key_and_cert, get_test_key_and_cert_hybrid}; use super::*; use crate::pv_utils::se_hdr::ComponentMetadataV1; @@ -108,7 +122,7 @@ mod tests { use pv::test_utils::get_test_key_and_cert; let (cust_key, host_key) = get_test_key_and_cert(); - let host_keys = [host_key.public_key().unwrap()]; + let host_keys = [HostKey::V1(host_key.public_key().unwrap())]; let xts_key = Confidential::new([0x3; SymKeyType::AES_256_XTS_KEY_LEN]); let xts_key2 = Confidential::new([0x3; SymKeyType::AES_256_XTS_KEY_LEN]); let mut builder = SeHdrBuilder::new( @@ -236,10 +250,140 @@ mod tests { let _decrypted_hdrv1: SeHdrDataV1 = decrypted.data.try_into().expect("BUG"); } + #[test] + fn builder_test_v2() { + use pv::test_utils::get_test_key_and_cert_hybrid; + + let (cust_key, host_key1, host_key2) = get_test_key_and_cert_hybrid(); + let host_keys = [HostKey::V2( + HybridPKey::new( + host_key1.public_key().unwrap(), + host_key2.public_key().unwrap(), + ) + .unwrap(), + )]; + let xts_key = Confidential::new([0x3; SymKeyType::AES_256_XTS_KEY_LEN]); + let xts_key2 = Confidential::new([0x3; SymKeyType::AES_256_XTS_KEY_LEN]); + let mut builder = SeHdrBuilder::new( + SeHdrVersion::V2, + PSW { + addr: 1234, + mask: 5678, + }, + ComponentMetadata::ComponentMetadataV1(ComponentMetadataV1 { + ald: [0x1; SHA_512_HASH_LEN], + pld: [0x2; SHA_512_HASH_LEN], + tld: [0x3; SHA_512_HASH_LEN], + nep: 1, + key: xts_key, + }), + ) + .expect("should not fail"); + + // builder.add_comp_data(addr, tweak, )?; + builder + .with_components(ComponentMetadataV1 { + ald: [0x1; SHA_512_HASH_LEN], + pld: [0x2; SHA_512_HASH_LEN], + tld: [0x3; SHA_512_HASH_LEN], + nep: 1, + key: xts_key2, + }) + .expect("should not fail"); + builder + .with_priv_key(&cust_key) + .expect_err("Error expected as expert mode is not enabled"); + + builder.expert_mode = true; + builder.with_priv_key(&cust_key).expect("should not fail"); + + // Set CCK + // Too large key + builder + .with_cck([49; SymKeyType::AES_256_GCM_KEY_LEN - 1].to_vec().into()) + .expect_err("should fail"); + // Too small key + builder + .with_cck([49; SymKeyType::AES_256_GCM_KEY_LEN + 1].to_vec().into()) + .expect_err("should fail"); + + builder + .with_cck([49; SymKeyType::AES_256_GCM_KEY_LEN].to_vec().into()) + .expect("should not fail"); + + // Set protection key + // Too large key + builder + .with_aead_key(Confidential::new([50; 33].into())) + .expect_err("should fail"); + // Too small key + builder + .with_aead_key(Confidential::new([50; 31].into())) + .expect_err("should fail"); + + builder + .with_aead_key(Confidential::new([50; 32].into())) + .expect("should not fail"); + + // Set IV + // Too large IV + builder.with_iv(&[51; 13]).expect_err("should fail"); + // Too small IV + builder.with_iv(&[51; 11]).expect_err("should fail"); + + builder.with_iv(&[51; 12]).expect("should not fail"); + + builder.add_hostkeys(&host_keys).expect("should not fail"); + + let prot_key = builder.prot_key().clone(); + let bin = builder.build().expect("wuhu"); + assert_eq!(bin.common.version, SeHdrVersion::V2); + assert_eq!(bin.as_bytes().expect("should not fail").len(), 2240); + assert_eq!( + bin.as_bytes().expect("should not fail")[..480], + [ + 73, 66, 77, 83, 101, 99, 69, 120, 0, 0, 2, 0, 0, 0, 8, 192, 51, 51, 51, 51, 51, 51, + 51, 51, 51, 51, 51, 51, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 128, 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, 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, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 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 + ][..480] + ); + + let decrypted = bin.decrypt(&prot_key).expect("BUG"); + assert_eq!(bin.common, decrypted.common); + assert_eq!( + bin.aad().expect("should not fail"), + decrypted.aad().expect("should not fail") + ); + assert_ne!( + &bin.data(), + decrypted.data().expect("should not fail").value() + ); + let _decrypted_hdrv2: SeHdrDataV2 = decrypted.data.try_into().expect("BUG"); + } + #[test] fn chain_test() { let (_, host_key) = get_test_key_and_cert(); - let host_keys = [host_key.public_key().unwrap()]; + let host_keys = [HostKey::V1(host_key.public_key().unwrap())]; let xts_key = Confidential::new([0x3; SymKeyType::AES_256_XTS_KEY_LEN]); let meta = ComponentMetadataV1 { ald: [0x1; SHA_512_HASH_LEN], @@ -274,4 +418,49 @@ mod tests { assert_eq!(hdr_plain.common.version, hdr.common.version); let _hdr_data_v1: SeHdrDataV1 = hdr_plain.data.try_into().expect("should not fail"); } + + #[test] + fn chain_test_v2() { + let (_, host_key1, host_key2) = get_test_key_and_cert_hybrid(); + let host_keys = [HostKey::V2( + HybridPKey::new( + host_key1.public_key().unwrap(), + host_key2.public_key().unwrap(), + ) + .unwrap(), + )]; + let xts_key = Confidential::new([0x3; SymKeyType::AES_256_XTS_KEY_LEN]); + let meta = ComponentMetadataV1 { + ald: [0x1; SHA_512_HASH_LEN], + pld: [0x2; SHA_512_HASH_LEN], + tld: [0x3; SHA_512_HASH_LEN], + nep: 3, + key: xts_key, + }; + let cck = Confidential::new([0x42; 32].to_vec()); + let mut builder = SeHdrBuilder::new( + SeHdrVersion::V2, + PSW { + addr: 1234, + mask: 5678, + }, + meta, + ) + .expect("should not fail"); + + let prot_key = builder.prot_key().to_owned(); + builder + .add_hostkeys(&host_keys) + .expect("should not fail") + .with_cck(cck) + .expect("should not fail"); + let bin = builder.build().expect("should not fail"); + + let reader = Cursor::new(bin.as_bytes().expect("should not fail")); + let hdr = SeHdr::try_from_io(reader).unwrap(); + let hdr_plain = hdr.decrypt(&prot_key).unwrap(); + assert_eq!(hdr_plain.common.version, SeHdrVersion::V2); + assert_eq!(hdr_plain.common.version, hdr.common.version); + let _hdr_data_v2: SeHdrDataV2 = hdr_plain.data.try_into().expect("should not fail"); + } } diff --git a/rust/pvimg/src/pv_utils/se_hdr/flags.rs b/rust/pvimg/src/pv_utils/se_hdr/flags.rs index 2d9e364e..a7a0bc5a 100644 --- a/rust/pvimg/src/pv_utils/se_hdr/flags.rs +++ b/rust/pvimg/src/pv_utils/se_hdr/flags.rs @@ -39,6 +39,9 @@ //! //! // Get plaintext control flags configuration for V1-max //! let pcf_v1 = SeHdrControlFlagsModel::pcf_for_target(SeTarget::V1Max); +//! +//! // Get secret control flags configuration for V2-max +//! let scf_v2 = SeHdrControlFlagsModel::scf_for_target(SeTarget::V2Max); //! ``` //! //! ## 2. Checking Flag Support and Defaults @@ -73,6 +76,35 @@ //! assert!(result.is_ok()); //! ``` //! +//! ## 4. Working with Flag Collections +//! +//! ```rust +//! use pvimg::uvdata::{SeHdrControlFlagsModel, SeTarget}; +//! +//! let pcf_v2 = SeHdrControlFlagsModel::pcf_for_target(SeTarget::V2Max); +//! +//! // Get all supported flags +//! let supported = pcf_v2.supported_flags(); +//! println!("V2-max supports {} plaintext flags", supported.len()); +//! +//! // Get default flags +//! let defaults = pcf_v2.default_flags(); +//! println!("V2-max has {} default plaintext flags", defaults.len()); +//! ``` +//! +//! # Version Differences +//! +//! ## Plaintext Control Flags +//! +//! **V1 Defaults:** `PckmoDeaTdea`, `PckmoAes`, `PckmoEcc` +//! +//! **V2 Defaults:** `PckmoDeaTdea`, `PckmoAes`, `PckmoEcc`, `PckmoHmac` +//! +//! ## Secret Control Flags +//! +//! **V1 & V2:** Both versions support `CckExtensionSecretEnforcement` and `CckUpdate` +//! (no defaults, must be explicitly enabled via overrides) +//! //! # Error Handling //! //! The API uses [`FlagValidationError`] to report issues: @@ -170,6 +202,10 @@ impl SeHdrControlFlagsModel { common_defaults.into_iter().collect(), common_supported.into_iter().collect(), ), + SeTarget::V2Max => ( + common_defaults.into_iter().chain([PckmoHmac]).collect(), + common_supported.into_iter().chain([]).collect(), + ), }; Self::new(target, default, supported) @@ -326,6 +362,25 @@ mod test { assert!(!pcfs_v1.is_default(SeHdrFlag::ConfidentialDump)); } + #[test] + fn test_pcfs_v2() { + let pcfs_v2 = SeHdrControlFlagsModel::pcf_for_target(SeTarget::V2Max); + + // V1 flags (should all be supported in V2) + assert!(pcfs_v2.supports(SeHdrFlag::ConfidentialDump)); + assert!(pcfs_v2.supports(SeHdrFlag::NoComponentEncryption)); + assert!(pcfs_v2.supports(SeHdrFlag::PckmoDeaTdea)); + assert!(pcfs_v2.supports(SeHdrFlag::PckmoAes)); + assert!(pcfs_v2.supports(SeHdrFlag::PckmoEcc)); + assert!(pcfs_v2.supports(SeHdrFlag::PckmoHmac)); + assert!(pcfs_v2.supports(SeHdrFlag::BackupTargetKeys)); + + // Check defaults (same as V1) + assert!(pcfs_v2.is_default(SeHdrFlag::PckmoDeaTdea)); + assert!(pcfs_v2.is_default(SeHdrFlag::PckmoAes)); + assert!(pcfs_v2.is_default(SeHdrFlag::PckmoEcc)); + } + #[test] fn test_scfs_v1() { let scfs_v1 = SeHdrControlFlagsModel::scf_for_target(SeTarget::V1Max); @@ -338,6 +393,14 @@ mod test { assert!(!scfs_v1.is_default(SeHdrFlag::CckUpdate)); } + #[test] + fn test_scfs_v2() { + let scfs_v2 = SeHdrControlFlagsModel::scf_for_target(SeTarget::V2Max); + + assert!(scfs_v2.supports(SeHdrFlag::CckExtensionSecretEnforcement)); + assert!(scfs_v2.supports(SeHdrFlag::CckUpdate)); + } + #[test] fn test_scfs_v1_with_overrides() { // Create base model for V1 secret control flags @@ -479,6 +542,45 @@ mod test { assert!(!configured_pcf.has(SeHdrFlag::PckmoEcc)); } + #[test] + fn test_pcfs_v2_with_overrides_mixed() { + // Create base model for V2 plaintext control flags + let pcf_v2 = SeHdrControlFlagsModel::pcf_for_target(SeTarget::V2Max); + + // Verify V2 defaults: PckmoDeaTdea, PckmoAes, PckmoEcc, PckmoHmac + assert_eq!(pcf_v2.default_flags().len(), 4); + assert!(pcf_v2.is_default(SeHdrFlag::PckmoDeaTdea)); + assert!(pcf_v2.is_default(SeHdrFlag::PckmoAes)); + assert!(pcf_v2.is_default(SeHdrFlag::PckmoEcc)); + assert!(pcf_v2.is_default(SeHdrFlag::PckmoHmac)); + + // Create mixed overrides: disable one default, enable one non-default + let mut overrides = FlagsOverride::new(); + overrides.disable(SeHdrFlag::PckmoHmac); // Disable a default + overrides.enable(SeHdrFlag::NoComponentEncryption); // Enable a non-default + + // Verify overrides + assert_eq!(overrides.len(), 2); + assert_eq!( + overrides.get(SeHdrFlag::PckmoHmac), + Some(FlagState::Disabled) + ); + assert_eq!( + overrides.get(SeHdrFlag::NoComponentEncryption), + Some(FlagState::Enabled) + ); + + // Apply overrides + let configured_pcf = pcf_v2 + .with_overrides(&overrides) + .expect("Valid overrides should succeed"); + + // Verify the configured model maintains version and support + assert_eq!(configured_pcf.version(), SeHdrVersion::V2); + assert!(!configured_pcf.has(SeHdrFlag::PckmoHmac)); + assert!(configured_pcf.has(SeHdrFlag::NoComponentEncryption)); + } + #[test] fn test_asref_flexibility() { // Test that both owned values and references work with AsRef-based API @@ -583,6 +685,34 @@ mod test { assert!(supported_flags.contains(&SeHdrFlag::BackupTargetKeys)); } + #[test] + fn test_plaintext_control_flags_construction_v2() { + // Test construction of PlaintextControlFlags for V2 + let pcf_v2 = SeHdrControlFlagsModel::pcf_for_target(SeTarget::V2Max); + + // Verify version + assert_eq!(pcf_v2.version(), SeHdrVersion::V2); + + // Verify default flags for V2 + let default_flags = pcf_v2.default_flags(); + assert_eq!(default_flags.len(), 4); + assert!(default_flags.contains(&SeHdrFlag::PckmoDeaTdea)); + assert!(default_flags.contains(&SeHdrFlag::PckmoAes)); + assert!(default_flags.contains(&SeHdrFlag::PckmoEcc)); + assert!(default_flags.contains(&SeHdrFlag::PckmoHmac)); + + // Verify supported flags for V2 + let supported_flags = pcf_v2.supported_flags(); + assert_eq!(supported_flags.len(), 7); + assert!(supported_flags.contains(&SeHdrFlag::ConfidentialDump)); + assert!(supported_flags.contains(&SeHdrFlag::NoComponentEncryption)); + assert!(supported_flags.contains(&SeHdrFlag::PckmoDeaTdea)); + assert!(supported_flags.contains(&SeHdrFlag::PckmoAes)); + assert!(supported_flags.contains(&SeHdrFlag::PckmoEcc)); + assert!(supported_flags.contains(&SeHdrFlag::PckmoHmac)); + assert!(supported_flags.contains(&SeHdrFlag::BackupTargetKeys)); + } + #[test] fn test_secret_control_flags_construction_v1() { // Test construction of SecretControlFlags for V1 @@ -603,6 +733,61 @@ mod test { assert!(supported_flags.contains(&SeHdrFlag::CckUpdate)); } + #[test] + fn test_secret_control_flags_construction_v2() { + // Test construction of SecretControlFlags for V2 + let scf_v2 = SeHdrControlFlagsModel::scf_for_target(SeTarget::V2Max); + + // Verify version + assert_eq!(scf_v2.version(), SeHdrVersion::V2); + + // Verify default flags for V2 (should be empty) + let default_flags = scf_v2.default_flags(); + assert_eq!(default_flags.len(), 0); + assert!(default_flags.is_empty()); + + // Verify supported flags for V2 (same as V1) + let supported_flags = scf_v2.supported_flags(); + assert_eq!(supported_flags.len(), 2); + assert!(supported_flags.contains(&SeHdrFlag::CckExtensionSecretEnforcement)); + assert!(supported_flags.contains(&SeHdrFlag::CckUpdate)); + } + + #[test] + fn test_plaintext_flags_v1_vs_v2_differences() { + let pcf_v1 = SeHdrControlFlagsModel::pcf_for_target(SeTarget::V1Max); + let pcf_v2 = SeHdrControlFlagsModel::pcf_for_target(SeTarget::V2Max); + + // V1 should have 3 default flags, V2 should have 4 + assert_eq!(pcf_v1.default_flags().len(), 3); + assert_eq!(pcf_v2.default_flags().len(), 4); + + // V2 adds PckmoHmac to defaults + assert!(!pcf_v1.is_default(SeHdrFlag::PckmoHmac)); + assert!(pcf_v2.is_default(SeHdrFlag::PckmoHmac)); + + // V1 should have 7 supported flags, V2 should have 7 [TODO: Marc claims 8?] + assert_eq!(pcf_v1.supported_flags().len(), 7); + assert_eq!(pcf_v2.supported_flags().len(), 7); + } + + #[test] + fn test_secret_flags_v1_vs_v2_consistency() { + let scf_v1 = SeHdrControlFlagsModel::scf_for_target(SeTarget::V1Max); + let scf_v2 = SeHdrControlFlagsModel::scf_for_target(SeTarget::V2Max); + + // Both versions should have the same defaults (empty) + assert_eq!(scf_v1.default_flags().len(), scf_v2.default_flags().len()); + assert!(scf_v1.default_flags().is_empty()); + assert!(scf_v2.default_flags().is_empty()); + + // Both versions should have the same supported flags + assert_eq!( + scf_v1.supported_flags().len(), + scf_v2.supported_flags().len() + ); + assert_eq!(scf_v1.supported_flags(), scf_v2.supported_flags()); + } // Tests for Into trait implementations #[test] @@ -623,6 +808,25 @@ mod test { assert!(!flags.is_set(SeHdrFlag::ConfidentialDump.bit_position())); } + #[test] + fn test_into_msb0_flags_borrowed() { + // Test Into for borrowed ControlFlagsModel + let pcf_v2 = SeHdrControlFlagsModel::pcf_for_target(SeTarget::V2Max); + + // Convert using reference (model remains usable) + let flags: Msb0Flags64 = (&pcf_v2).into(); + + // V2 defaults: PckmoDeaTdea, PckmoAes, PckmoEcc, PckmoHmac + assert!(flags.is_set(SeHdrFlag::PckmoDeaTdea.bit_position())); + assert!(flags.is_set(SeHdrFlag::PckmoAes.bit_position())); + assert!(flags.is_set(SeHdrFlag::PckmoEcc.bit_position())); + assert!(flags.is_set(SeHdrFlag::PckmoHmac.bit_position())); + + // Model should still be usable + assert_eq!(pcf_v2.version(), SeHdrVersion::V2); + assert_eq!(pcf_v2.default_flags().len(), 4); + } + #[test] fn test_to_msb0_flags_with_overrides() { let pcf_v1 = SeHdrControlFlagsModel::pcf_for_target(SeTarget::V1Max); @@ -741,4 +945,16 @@ mod test { assert_eq!(flags.known_flags().len(), 0); assert_eq!(flags.unknown_flags().bits(), 0); } + + #[test] + fn test_effective_control_flags_from_u64_v2_specific() { + // Test V2-specific flags + let mut value = 0u64; + value |= 1u64 << (63 - SeHdrFlag::PckmoHmac.bit_position()); + + let flags = SeHdrControlFlags::from_u64(value, SeTarget::V2Max, true); + + assert_eq!(flags.version(), SeHdrVersion::V2); + assert!(flags.has(SeHdrFlag::PckmoHmac)); + } } diff --git a/rust/pvimg/src/pv_utils/se_hdr/generic_flags.rs b/rust/pvimg/src/pv_utils/se_hdr/generic_flags.rs index f8733b8c..e9d71bb1 100644 --- a/rust/pvimg/src/pv_utils/se_hdr/generic_flags.rs +++ b/rust/pvimg/src/pv_utils/se_hdr/generic_flags.rs @@ -104,6 +104,12 @@ pub enum SeTarget { /// Targets the most recent V1 SE header format with all available V1 control flags. /// Use this for maximum compatibility with older machine generations that support V1. V1Max, + + /// Latest V2 configuration. + /// + /// Targets the most recent V2 SE header format with all available V2 control flags. + /// Use this for newest features and machine generations that support V2. + V2Max, } impl SeTarget { @@ -111,6 +117,7 @@ impl SeTarget { pub fn to_se_hdr_version(self) -> SeHdrVersion { match self { SeTarget::V1Max => SeHdrVersion::V1, + SeTarget::V2Max => SeHdrVersion::V2, } } @@ -118,6 +125,7 @@ impl SeTarget { pub fn from_se_hdr_version(version: SeHdrVersion) -> Self { match version { SeHdrVersion::V1 => SeTarget::V1Max, + SeHdrVersion::V2 => SeTarget::V2Max, } } } @@ -126,6 +134,7 @@ impl Display for SeTarget { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { SeTarget::V1Max => write!(f, "V1-max"), + SeTarget::V2Max => write!(f, "V2-max"), } } } diff --git a/rust/pvimg/src/pv_utils/se_hdr/hdr_v1.rs b/rust/pvimg/src/pv_utils/se_hdr/hdr_v1.rs index 6c937aa9..16e005f0 100644 --- a/rust/pvimg/src/pv_utils/se_hdr/hdr_v1.rs +++ b/rust/pvimg/src/pv_utils/se_hdr/hdr_v1.rs @@ -106,10 +106,8 @@ impl Display for SeHdrAadV1 { } impl KeyExchangeTrait for SeHdrAadV1 { - fn contains>>(&self, key: K) -> Result { - let phkh = phkh_v1(key)?; - Ok(self.contains_hash(phkh)) - } + type PrivateKeyType = PKey; + type TargetKeyType = PKeyRef; fn cust_pub_key(&mut self) -> Result> { self.cust_pub_key.clone().try_into() @@ -128,6 +126,14 @@ impl KeyExchangeTrait for SeHdrAadV1 { } false } + + fn contains(&self, key: K) -> Result + where + K: AsRef, + { + let phkh = phkh_v1(key)?; + Ok(self.contains_hash(phkh)) + } } #[derive(PartialEq, Eq, Debug, Clone, DekuRead, DekuWrite, Serialize, Deserialize)] @@ -412,11 +418,15 @@ impl UvDataPlainTrait for SeHdrDataV1 { impl SeHdrPlainTrait for SeHdrDataV1 {} impl KeyExchangeBuilderTrait for SeHdrDataV1 { + type AeadKeyType = SymKey; + type PrivateKeyType = PKeyRef; + type TargetKeyType = PKeyRef; + fn add_keyslot( &mut self, - hostkey: &PKeyRef, - aead_key: &SymKey, - priv_key: &PKeyRef, + hostkey: &Self::TargetKeyType, + aead_key: &Self::AeadKeyType, + priv_key: &Self::PrivateKeyType, ) -> Result<()> { let keyslot = Keyslot::new(HostKey::V1(hostkey.to_owned())); let keyslot_bin = keyslot.encrypt(aead_key.value(), priv_key)?.try_into()?; @@ -460,9 +470,8 @@ impl KeyExchangeBuilderTrait for SeHdrDataV1 { } impl KeyExchangeTrait for SeHdrDataV1 { - fn contains>>(&self, key: K) -> Result { - self.aad.contains(key) - } + type PrivateKeyType = PKey; + type TargetKeyType = PKeyRef; fn cust_pub_key(&mut self) -> Result> { self.aad.cust_pub_key() @@ -475,6 +484,13 @@ impl KeyExchangeTrait for SeHdrDataV1 { fn contains_hash>(&self, hash: H) -> bool { self.aad.contains_hash(hash) } + + fn contains(&self, key: K) -> Result + where + K: AsRef, + { + self.aad.contains(key) + } } impl SeHdrConfBuilderTrait for SeHdrDataV1 { @@ -590,9 +606,8 @@ impl AeadCipherBuilderTrait for SeHdrDataV1 { } impl KeyExchangeTrait for SeHdrBinV1 { - fn contains>>(&self, key: K) -> Result { - self.aad.contains(key) - } + type PrivateKeyType = PKey; + type TargetKeyType = PKeyRef; fn cust_pub_key(&mut self) -> Result> { self.aad.cust_pub_key() @@ -605,6 +620,13 @@ impl KeyExchangeTrait for SeHdrBinV1 { fn contains_hash>(&self, hash: H) -> bool { self.aad.contains_hash(hash) } + + fn contains(&self, key: K) -> Result + where + K: AsRef, + { + self.aad.contains(key) + } } impl AeadDataTrait for SeHdrBinV1 { @@ -654,6 +676,7 @@ mod tests { use std::io::Cursor; + use pv::request::HostKey; use pv::test_utils::get_test_key_and_cert; use super::*; @@ -662,7 +685,7 @@ mod tests { #[test] fn iv_keys_auto_generation_test() { let (_, host_key) = get_test_key_and_cert(); - let host_keys = [host_key.public_key().unwrap()]; + let host_keys = [HostKey::V1(host_key.public_key().unwrap())]; let mut builder = SeHdrBuilder::new( SeHdrVersion::V1, PSW { @@ -684,7 +707,7 @@ mod tests { #[test] fn chain_test() { let (_, host_key) = get_test_key_and_cert(); - let host_keys = [host_key.public_key().unwrap()]; + let host_keys = [HostKey::V1(host_key.public_key().unwrap())]; let xts_key = Confidential::new([0x3; SymKeyType::AES_256_XTS_KEY_LEN]); let meta = ComponentMetadataV1 { ald: [0x1; SHA_512_HASH_LEN], @@ -730,7 +753,7 @@ mod tests { const MAX_HOST_KEYS: usize = 95; let (_, host_key) = get_test_key_and_cert(); - let pub_key = host_key.public_key().unwrap(); + let pub_key = HostKey::V1(host_key.public_key().unwrap()); let host_keys_max: Vec<_> = (0..MAX_HOST_KEYS).map(|_| pub_key.clone()).collect(); let too_many_host_keys: Vec<_> = (0..MAX_HOST_KEYS + 1).map(|_| pub_key.clone()).collect(); let xts_key = Confidential::new([0x3; SymKeyType::AES_256_XTS_KEY_LEN]); diff --git a/rust/pvimg/src/pv_utils/se_hdr/hdr_v2.rs b/rust/pvimg/src/pv_utils/se_hdr/hdr_v2.rs new file mode 100644 index 00000000..4d481de1 --- /dev/null +++ b/rust/pvimg/src/pv_utils/se_hdr/hdr_v2.rs @@ -0,0 +1,901 @@ +// SPDX-License-Identifier: MIT +// +// Copyright IBM Corp. 2026 +use std::fmt::Display; +use std::mem::{size_of, size_of_val}; + +use base64::prelude::BASE64_STANDARD; +use base64::Engine; +use deku::ctx::Endian; +use deku::prelude::*; +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, HybridPKey, KeyslotV2, + SymKey, SymKeyType, Zeroize, SHA_512_HASH_LEN, +}; +use serde::{Deserialize, Serialize}; +use utils::HexSlice; + +use super::keys::phkh_v2; +use super::{EffectiveControlFlags, SeHdrControlFlags}; +use crate::error::Error; +use crate::pv_utils::error::Result; +use crate::pv_utils::misc::display_indented; +use crate::pv_utils::se_hdr::brb::{ + ComponentMetadata, ComponentMetadataV1, SeHdrCommon, SeHdrConfBuilderTrait, SeHdrPlainTrait, + SeHdrPubBuilderTrait, SeHdrTrait, +}; +use crate::pv_utils::se_hdr::keys::{BinaryKeySlotV2, EcPubKeyCoordV1}; +use crate::pv_utils::serializing::{ + bytesize, bytesize_confidential, confidential_read_slice, confidential_write_slice, + serde_base64, serde_hex_array, serde_hex_confidential_array, serde_hex_left_padded_u64, + serialize_to_bytes, +}; +use crate::pv_utils::uv_keys::UvKeyHashV1; +use crate::pv_utils::uvdata::{ + AeadCipherTrait, AeadDataTrait, AeadPlainDataTrait, KeyExchangeTrait, UvDataPlainTrait, + UvDataTrait, +}; +use crate::pv_utils::uvdata_builder::{AeadCipherBuilderTrait, KeyExchangeBuilderTrait}; +use crate::pv_utils::{try_copy_slice_to_array, SeHdrFlag, SeTarget, PSW}; + +#[derive(Debug)] +struct HdrSizesV2 { + pub phs: u64, + pub sea: u64, +} + +#[derive(Debug, Clone, PartialEq, Eq, DekuRead, DekuWrite, Serialize, Deserialize)] +#[deku(endian = "endian", ctx = "endian: Endian", ctx_default = "Endian::Big")] +pub struct SeHdrAadV2 { + #[deku(assert = "*sehs <= SeHdrDataV2::MAX_SIZE.try_into().unwrap()")] + pub sehs: u32, + #[serde(with = "serde_hex_array", rename = "iv_hex")] + pub iv: [u8; SymKeyType::AES_256_GCM_IV_LEN], + #[serde(skip)] + res1: u32, + #[deku(assert = "*nks <= (*sehs).into()", update = "self.keyslots.len()")] + pub nks: u64, + #[deku(assert = "*sea <= (*sehs).into()")] + pub sea: u64, + pub nep: u64, + #[serde(with = "serde_hex_left_padded_u64", rename = "pcf_hex")] + pub pcf: u64, + pub cust_pub_key: EcPubKeyCoordV1, + #[serde(with = "serde_hex_array", rename = "pld_hex")] + pub pld: [u8; SHA_512_HASH_LEN], + #[serde(with = "serde_hex_array", rename = "ald_hex")] + pub ald: [u8; SHA_512_HASH_LEN], + #[serde(with = "serde_hex_array", rename = "tld_hex")] + pub tld: [u8; SHA_512_HASH_LEN], + #[deku(count = "nks")] + pub keyslots: Vec, +} + +impl SeHdrAadV2 { + const KEY_TYPE: SymKeyType = SymKeyType::Aes256Gcm; +} + +impl Display for SeHdrAadV2 { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + // Support verbose mode if the `alternate` (`{:#}`) flag is used. + if f.alternate() { + writeln!(f, "size: {} bytes", self.sehs)?; + writeln!(f, "number of key slots: {}", self.nks)?; + } + writeln!(f, "key slots:")?; + for s in &self.keyslots { + writeln!(f, " - {s}")?; + } + if f.alternate() { + let value = display_indented(f, &self.cust_pub_key, 2); + writeln!(f, "customer public key:\n{value}",)?; + writeln!(f, "number of component pages: {}", self.nep)?; + writeln!(f, "components content hash: {:}", HexSlice::from(&self.pld))?; + writeln!(f, "components address hash: {:}", HexSlice::from(&self.ald))?; + writeln!(f, "components tweak hash: {:}", HexSlice::from(&self.tld))?; + } + writeln!( + f, + "plaintext control flags:\n{}", + SeHdrControlFlags::from_u64(self.pcf, SeTarget::V2Max, true) + )?; + Ok(()) + } +} + +impl KeyExchangeTrait for SeHdrAadV2 { + type PrivateKeyType = PKey; + type TargetKeyType = HybridPKey; + + fn cust_pub_key(&mut self) -> Result> { + self.cust_pub_key.clone().try_into() + } + + fn key_type(&self) -> SymKeyType { + Self::KEY_TYPE + } + + fn contains_hash>(&self, hash: H) -> bool { + let hash = hash.as_ref(); + self.keyslots + .iter() + .any(|ks| &ks.phkh[..UvKeyHashV1::UV_KEY_HASH_SIZE] == hash) + } + + fn contains(&self, key: K) -> Result + where + K: AsRef, + { + let key = key.as_ref(); + let phkh = phkh_v2(key)?; + Ok(self.contains_hash(phkh)) + } +} + +#[derive(PartialEq, Eq, Debug, Clone, DekuRead, DekuWrite, Serialize, Deserialize)] +#[deku(endian = "endian", ctx = "endian: Endian", ctx_default = "Endian::Big")] +pub struct SeHdrConfV2 { + #[serde(with = "serde_hex_confidential_array", rename = "cck_hex")] + #[deku( + reader = "confidential_read_slice(deku::reader, endian)", + writer = "confidential_write_slice(cck, deku::writer, endian)" + )] + cck: Confidential<[u8; 32]>, + #[serde(with = "serde_hex_confidential_array", rename = "xts_hex")] + #[deku( + reader = "confidential_read_slice(deku::reader, endian)", + writer = "confidential_write_slice(xts, deku::writer, endian)" + )] + xts: Aes256XtsKey, + psw: PSW, + #[serde(with = "serde_hex_left_padded_u64", rename = "scf_hex")] + pub scf: u64, + #[serde(skip)] + #[deku(assert_eq = "0")] + noi: u32, + #[serde(skip)] + res2: u32, + #[serde(skip)] + #[deku(count = "noi")] + opt_items: Vec, +} + +impl Zeroize for SeHdrConfV2 { + fn zeroize(&mut self) { + self.cck.zeroize(); + self.xts.zeroize(); + self.psw.zeroize(); + self.scf.zeroize(); + self.noi.zeroize(); + self.res2.zeroize(); + self.opt_items.zeroize(); + } +} + +impl Display for SeHdrConfV2 { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + writeln!( + f, + "secret control flags:\n{}", + SeHdrControlFlags::from_u64(self.scf, SeTarget::V2Max, false) + )?; + + // Support verbose mode if the `alternate` (`{:#}`) flag is used. + if f.alternate() { + writeln!(f, "CCK: {:}", HexSlice::from(self.cck.value()))?; + writeln!(f, "XTS key: {:}", HexSlice::from(self.xts.value()))?; + let psw = display_indented(f, &self.psw, 2); + writeln!(f, "PSW:\n{psw}")?; + } + Ok(()) + } +} + +#[derive(Default, PartialEq, Eq, Debug, Clone, DekuRead, DekuWrite, Serialize, Deserialize)] +#[deku(endian = "endian", ctx = "endian: Endian", ctx_default = "Endian::Big")] +pub struct SeHdrTagV2 { + #[serde(with = "serde_hex_array", rename = "tag_hex")] + tag: [u8; SymKeyType::AES_256_GCM_TAG_LEN], +} + +impl Display for SeHdrTagV2 { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{:}", HexSlice::from(&self.tag)) + } +} + +mod ser_confidential_confv2 { + use pv::request::Confidential; + use serde::{Deserialize, Deserializer, Serialize, Serializer}; + + use super::SeHdrConfV2; + + pub fn serialize( + encrypted: &Confidential, + ser: S, + ) -> Result { + encrypted.value().serialize(ser) + } + + pub fn deserialize<'de, D: Deserializer<'de>>( + deserializer: D, + ) -> Result, D::Error> { + let conf = SeHdrConfV2::deserialize(deserializer)?; + Ok(Confidential::new(conf)) + } +} + +/// Secure Execution Header definition +#[derive(Debug, Clone, PartialEq, Eq, DekuRead, DekuWrite, Serialize, Deserialize)] +#[deku(endian = "big")] +pub struct SeHdrDataV2 { + #[serde(flatten)] + pub aad: SeHdrAadV2, + #[serde(flatten, with = "ser_confidential_confv2")] + #[deku( + reader = "confidential_read_sehdrconf_v2(deku::reader)", + writer = "confidential_write_sehdrconf_v2(data, deku::writer)" + )] + pub data: Confidential, + #[serde(flatten)] + tag: SeHdrTagV2, +} + +impl Display for SeHdrDataV2 { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + // Support verbose mode if the `alternate` (`{:#}`) flag is used. + if f.alternate() { + write!(f, "{:#}", self.aad)?; + write!(f, "{:#}", self.data.value())?; + writeln!(f, "GCM tag: {}", self.tag)?; + } else { + write!(f, "{}", self.aad)?; + write!(f, "{}", self.data.value())?; + } + Ok(()) + } +} + +/// Reads from a `reader` and creates a confidential `SeHdrConfV2`. +/// +/// # Errors +/// +/// This function will return an error if there was an I/O error or the +/// `SeHdrConfV2` could not be constructed. +fn confidential_read_sehdrconf_v2( + reader: &mut Reader, +) -> Result, DekuError> +where + R: std::io::Read + std::io::Seek, +{ + Ok(Confidential::new(SeHdrConfV2::from_reader_with_ctx( + reader, + (), + )?)) +} + +/// Writes a `Confidential` into this `writer`. +/// +/// # Errors +/// +/// This function will return an error if there was an I/O error. +fn confidential_write_sehdrconf_v2( + value: &Confidential, + writer: &mut Writer, +) -> Result<(), DekuError> +where + W: std::io::Write + std::io::Seek, +{ + value.value().to_writer(writer, ()) +} + +impl SeHdrDataV2 { + // For Linux kernel >= 7.0, this is 1 MiB + const MAX_SIZE: usize = 1024 * 1024; + const PCF_DEFAULT: u64 = 0x0; + const SCF_DEFAULT: u64 = 0x0; + + /// Creates a new `SeHdrDataV2`. It initializes the CCK and IV with random + /// data. + /// + /// # Errors + /// + /// This function will return an error if there was not enough entropy to + /// create the random data or another error has occurred. + pub fn new(psw: PSW, components: ComponentMetadataV1) -> Result { + // Safety: The CCK is also 32 bytes large. + let cck = SymKey::random(SymKeyType::Aes256Gcm)?.try_into().unwrap(); + let mut ret = Self { + aad: SeHdrAadV2 { + sehs: 0, + pcf: Self::PCF_DEFAULT, + ald: components.ald, + pld: components.pld, + tld: components.tld, + nep: components.nep, + sea: 0, + iv: random_array()?, + res1: 0, + nks: 0, + cust_pub_key: EcPubKeyCoordV1 { coord: [0_u8; 160] }, + keyslots: vec![], + }, + data: SeHdrConfV2 { + cck, + scf: Self::SCF_DEFAULT, + psw, + xts: components.key, + noi: 0, + res2: 0, + opt_items: vec![], + } + .into(), + tag: SeHdrTagV2::default(), + }; + let hdr_size = ret.size()?; + let phs = hdr_size.phs.try_into()?; + if phs > Self::MAX_SIZE { + return Err(Error::InvalidSeHdrTooLarge { + given: phs, + maximum: Self::MAX_SIZE, + }); + } + ret.aad.sehs = phs.try_into()?; + ret.aad.sea = hdr_size.sea; + Ok(ret) + } + + fn size(&self) -> Result { + let sea = bytesize_confidential(&self.data)?; + let mut phs = bytesize(&self.aad)? + .checked_add(size_of::()) + .ok_or(Error::UnexpectedOverflow)?; + phs = phs + .checked_add(bytesize(&self.tag)?) + .ok_or(Error::UnexpectedOverflow)?; + phs = phs.checked_add(sea).ok_or(Error::UnexpectedOverflow)?; + + Ok(HdrSizesV2 { + sea: sea.try_into()?, + phs: phs.try_into()?, + }) + } + + /// Return the expected size of an constructed `SeHdrDataV2` with `n` key + /// slots. + /// + /// # Errors + /// + /// This function will return an error if there was an arithmetic overflow + /// or. + pub fn expected_size(nks: usize) -> Result { + let cck = [0x0; 32].into(); + let hdr = Self { + aad: SeHdrAadV2 { + sehs: 0, + pcf: Self::PCF_DEFAULT, + ald: [0x0; SHA_512_HASH_LEN], + pld: [0x0; SHA_512_HASH_LEN], + tld: [0x0; SHA_512_HASH_LEN], + nep: 0, + sea: 0, + iv: [0x0_u8; SymKeyType::AES_256_GCM_IV_LEN], + res1: 0, + nks: 0, + cust_pub_key: EcPubKeyCoordV1 { coord: [0_u8; 160] }, + keyslots: vec![], + }, + data: SeHdrConfV2 { + cck, + scf: Self::SCF_DEFAULT, + psw: PSW { mask: 0, addr: 0 }, + xts: [0x0; SymKeyType::AES_256_XTS_KEY_LEN].into(), + noi: 0, + res2: 0, + opt_items: vec![], + } + .into(), + tag: SeHdrTagV2::default(), + }; + let hdr_size: usize = hdr.size()?.phs.try_into().unwrap(); + + hdr_size + .checked_add( + size_of::() + .checked_mul(nks) + .ok_or(Error::UnexpectedOverflow)?, + ) + .ok_or(Error::UnexpectedOverflow) + } +} + +impl UvDataPlainTrait for SeHdrDataV2 { + type C = SeHdrBinV2; +} +impl SeHdrPlainTrait for SeHdrDataV2 {} + +impl KeyExchangeBuilderTrait for SeHdrDataV2 { + type AeadKeyType = SymKey; + type PrivateKeyType = PKeyRef; + type TargetKeyType = HybridPKey; + + fn generate_private_key(&self) -> Result> { + Ok(gen_ec_key(Nid::SECP521R1)?) + } + + fn set_cust_public_key(&mut self, key: &PKeyRef) -> Result<()> { + self.aad.cust_pub_key = TryInto::::try_into(key)?.into(); + Ok(()) + } + + fn clear_keyslots(&mut self) -> Result<()> { + let old_nks: usize = self.aad.nks.try_into().unwrap(); + let keyslot_bin_size = size_of::(); + self.aad.keyslots.clear(); + self.aad.nks = 0; + self.aad.sehs -= u32::try_from( + old_nks + .checked_mul(keyslot_bin_size) + .ok_or(Error::UnexpectedOverflow)?, + ) + .unwrap(); + Ok(()) + } + + fn add_keyslot( + &mut self, + hostkey: &Self::TargetKeyType, + aead_key: &Self::AeadKeyType, + priv_key: &Self::PrivateKeyType, + ) -> Result<()> { + let keyslot = KeyslotV2::new(hostkey.clone()); + 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); + self.aad.nks = self + .aad + .nks + .checked_add(1) + .ok_or(Error::UnexpectedOverflow)?; + self.aad.sehs = self + .aad + .sehs + .checked_add(keyslot_bin_size) + .ok_or(Error::UnexpectedOverflow)?; + Ok(()) + } +} + +impl KeyExchangeTrait for SeHdrDataV2 { + type PrivateKeyType = PKey; + type TargetKeyType = HybridPKey; + + fn cust_pub_key(&mut self) -> Result> { + self.aad.cust_pub_key() + } + + fn key_type(&self) -> SymKeyType { + self.aad.key_type() + } + + fn contains_hash>(&self, hash: H) -> bool { + self.aad.contains_hash(hash) + } + + fn contains(&self, key: K) -> Result + where + K: AsRef, + { + self.aad.contains(key) + } +} + +impl SeHdrConfBuilderTrait for SeHdrDataV2 { + fn set_psw(&mut self, psw: &PSW) { + self.data.value_mut().psw = psw.clone(); + } + + fn set_scf(&mut self, scf: &EffectiveControlFlags) -> Result<()> { + self.data.value_mut().scf = scf.to_u64(); + Ok(()) + } + + fn set_cck(&mut self, cck: Confidential>) -> Result<()> { + self.data.value_mut().cck = cck.try_into()?; + Ok(()) + } + + fn generate_cck(&self) -> Result { + Ok(SymKey::random(SymKeyType::Aes256Gcm)?) + } +} + +impl SeHdrPubBuilderTrait for SeHdrDataV2 { + fn set_pcf(&mut self, pcf: &EffectiveControlFlags) -> Result<()> { + self.aad.pcf = pcf.to_u64(); + Ok(()) + } + + fn set_components(&mut self, meta: ComponentMetadata) -> Result<()> { + let ComponentMetadataV1 { + ald, + pld, + tld, + nep, + key, + }: ComponentMetadataV1 = meta + .try_into() + .map_err(|_| Error::InvalidComponentMetadata)?; + self.data.value_mut().xts = key; + self.aad.ald = ald; + self.aad.pld = pld; + self.aad.tld = tld; + self.aad.nep = nep; + Ok(()) + } +} + +#[derive(Debug, Clone, PartialEq, Eq, DekuRead, DekuWrite, Serialize, Deserialize)] +#[deku(endian = "big")] +pub struct SeHdrBinV2 { + #[serde(flatten)] + pub aad: SeHdrAadV2, + #[serde(with = "serde_base64", rename = "cipher_data_b64")] + #[deku(bytes_read = "aad.sea")] + pub data: Vec, + #[serde(flatten)] + pub tag: SeHdrTagV2, +} + +impl Display for SeHdrBinV2 { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + // Support verbose mode if the `alternate` (`{:#}`) flag is used. + if f.alternate() { + write!(f, "{:#}", self.aad)?; + writeln!( + f, + "encrypted data: {:#}", + BASE64_STANDARD.encode(&self.data) + )?; + writeln!(f, "GCM tag: {:#}", self.tag)?; + } else { + write!(f, "{}", self.aad)?; + } + Ok(()) + } +} + +impl SeHdrBinV2 { + pub fn new(d: &[u8]) -> Result { + Self::try_from_data(d) + } + + pub(crate) fn try_from_data(data: &[u8]) -> Result { + let (_rest, val) = Self::from_bytes((data, 0))?; + Ok(val) + } +} + +impl UvDataTrait for SeHdrBinV2 { + type P = SeHdrDataV2; +} +impl SeHdrTrait for SeHdrBinV2 {} + +impl AeadCipherTrait for SeHdrBinV2 { + fn aead_key_type(&self) -> SymKeyType { + self.key_type() + } + + fn iv(&self) -> &[u8] { + &self.aad.iv + } + + fn aead_tag_size(&self) -> usize { + SymKeyType::AES_256_GCM_TAG_LEN + } +} + +impl AeadCipherBuilderTrait for SeHdrDataV2 { + fn set_iv(&mut self, iv: &[u8]) -> Result<()> { + self.aad.iv = try_copy_slice_to_array(iv)?; + Ok(()) + } +} + +impl KeyExchangeTrait for SeHdrBinV2 { + type PrivateKeyType = PKey; + type TargetKeyType = HybridPKey; + + fn cust_pub_key(&mut self) -> Result> { + self.aad.cust_pub_key() + } + + fn key_type(&self) -> SymKeyType { + self.aad.key_type() + } + + fn contains_hash>(&self, hash: H) -> bool { + self.aad.contains_hash(hash) + } + + fn contains(&self, key: K) -> Result + where + K: AsRef, + { + self.aad.contains(key) + } +} + +impl AeadDataTrait for SeHdrBinV2 { + fn aad(&self) -> Result> { + serialize_to_bytes(&self.aad) + } + + fn data(&self) -> Vec { + self.data.to_owned() + } + + fn tag(&self) -> Vec { + serialize_to_bytes(&self.tag).unwrap() + } +} + +impl AeadPlainDataTrait for SeHdrDataV2 { + fn aad(&self) -> Result> { + serialize_to_bytes(&self.aad) + } + + fn data(&self) -> Result>> { + Ok(serialize_to_bytes(self.data.value())?.into()) + } + + fn tag(&self) -> Vec { + serialize_to_bytes(&self.tag).unwrap() + } +} + +impl AeadCipherTrait for SeHdrDataV2 { + fn aead_key_type(&self) -> SymKeyType { + self.aad.key_type() + } + + fn iv(&self) -> &[u8] { + &self.aad.iv + } + + fn aead_tag_size(&self) -> usize { + SymKeyType::AES_256_GCM_TAG_LEN + } +} + +#[cfg(test)] +mod tests { + + use std::io::Cursor; + + use pv::request::HostKey; + use pv::test_utils::get_test_key_and_cert_hybrid; + + use super::*; + use crate::pv_utils::{BuilderTrait, SeHdr, SeHdrBuilder, SeHdrVersion}; + + #[test] + fn iv_keys_auto_generation_test() { + let (_, host_key1, host_key2) = get_test_key_and_cert_hybrid(); + let host_keys = [HostKey::V2( + HybridPKey::new( + host_key1.public_key().unwrap(), + host_key2.public_key().unwrap(), + ) + .unwrap(), + )]; + let mut builder = SeHdrBuilder::new( + SeHdrVersion::V2, + PSW { + addr: 1234, + mask: 5678, + }, + ComponentMetadataV1 { + ald: [0x1; SHA_512_HASH_LEN], + pld: [0x2; SHA_512_HASH_LEN], + tld: [0x3; SHA_512_HASH_LEN], + nep: 1, + key: Confidential::new([0x0_u8; SymKeyType::AES_256_XTS_KEY_LEN]), + }, + ) + .expect("should not fail"); + builder.add_hostkeys(&host_keys).expect("should not fail"); + } + + #[test] + fn chain_test() { + let (_, host_key1, host_key2) = get_test_key_and_cert_hybrid(); + let host_keys = [HostKey::V2( + HybridPKey::new( + host_key1.public_key().unwrap(), + host_key2.public_key().unwrap(), + ) + .unwrap(), + )]; + let xts_key = Confidential::new([0x3; SymKeyType::AES_256_XTS_KEY_LEN]); + let meta = ComponentMetadataV1 { + ald: [0x1; SHA_512_HASH_LEN], + pld: [0x2; SHA_512_HASH_LEN], + tld: [0x3; SHA_512_HASH_LEN], + nep: 3, + key: xts_key, + }; + let cck: Confidential> = [0x42; 32].to_vec().into(); + let psw = PSW { + addr: 1234, + mask: 5678, + }; + + let mut builder = SeHdrBuilder::new(SeHdrVersion::V2, psw.clone(), meta.clone()) + .expect("should not fail"); + + builder + .add_hostkeys(&host_keys) + .expect("should not fail") + .with_components(meta.clone()) + .expect("should not fail") + .with_cck(cck.clone()) + .expect("should not fail"); + let prot_key = builder.prot_key().to_owned(); + let bin = builder.build().expect("should not fail"); + + let reader = Cursor::new(bin.as_bytes().expect("should not fail")); + let hdr = SeHdr::try_from_io(reader).unwrap(); + + let hdr_plain = hdr.decrypt(&prot_key).unwrap(); + assert_eq!(hdr_plain.common.version, SeHdrVersion::V2); + let hdr_data_v2: SeHdrDataV2 = hdr_plain.data.try_into().expect("should not fail"); + assert_eq!(meta.ald, hdr_data_v2.aad.ald); + assert_eq!(meta.pld, hdr_data_v2.aad.pld); + assert_eq!(meta.tld, hdr_data_v2.aad.tld); + assert_eq!(psw, hdr_data_v2.data.value().psw); + assert_eq!(cck.value(), hdr_data_v2.data.value().cck.value()); + } + + #[test] + fn max_size_sehdr_test() { + const MAX_HOST_KEYS: usize = 623; // since Linux kernel 7.0 + + let (_, host_key1, host_key2) = get_test_key_and_cert_hybrid(); + let pub_key = HostKey::V2( + HybridPKey::new( + host_key1.public_key().unwrap(), + host_key2.public_key().unwrap(), + ) + .unwrap(), + ); + let host_keys_max: Vec<_> = (0..MAX_HOST_KEYS).map(|_| pub_key.clone()).collect(); + let too_many_host_keys: Vec<_> = (0..MAX_HOST_KEYS + 1).map(|_| pub_key.clone()).collect(); + let xts_key = Confidential::new([0x3; SymKeyType::AES_256_XTS_KEY_LEN]); + let meta = ComponentMetadataV1 { + ald: [0x1; SHA_512_HASH_LEN], + pld: [0x2; SHA_512_HASH_LEN], + tld: [0x3; SHA_512_HASH_LEN], + nep: 3, + key: xts_key, + }; + let psw = PSW { + addr: 1234, + mask: 5678, + }; + + let mut builder = SeHdrBuilder::new(SeHdrVersion::V2, psw.clone(), meta.clone()) + .expect("should not fail"); + builder + .add_hostkeys(&host_keys_max) + .expect("should not fail") + .with_components(meta.clone()) + .expect("should not fail"); + let bin = builder.build().expect("should not fail"); + assert_eq!(bin.common.version, SeHdrVersion::V2); + let hdr_v2: SeHdrBinV2 = bin.data.try_into().expect("should not fail"); + assert_eq!(hdr_v2.aad.sehs, 1047200); // since kernel 7.0 + + let mut builder = SeHdrBuilder::new(SeHdrVersion::V2, psw.clone(), meta.clone()) + .expect("should not fail"); + + builder + .add_hostkeys(&too_many_host_keys) + .expect("should not fail") + .with_components(meta) + .expect("should not fail"); + assert!(matches!(builder.build(), Err(Error::InvalidSeHdr))); + } + + #[test] + fn roundtrip_se_hdr_tag_v2_json() { + let tag = SeHdrTagV2 { + tag: [0x42; SymKeyType::AES_256_GCM_TAG_LEN], + }; + + let json = serde_json::to_string(&tag).expect("should serialize"); + assert_eq!(json, "{\"tag_hex\":\"42424242424242424242424242424242\"}"); + let deserialized: SeHdrTagV2 = serde_json::from_str(&json).expect("should deserialize"); + + assert_eq!(tag, deserialized); + } + + #[test] + fn roundtrip_se_hdr_conf_v2_json() { + let conf = SeHdrConfV2 { + cck: Confidential::new([0x11; 32]), + xts: Confidential::new([0x22; SymKeyType::AES_256_XTS_KEY_LEN]), + psw: PSW { + addr: 0x1000, + mask: 0x2000, + }, + scf: 0x42, + noi: 0, + res2: 0, + opt_items: vec![], + }; + + let json = serde_json::to_string(&conf).expect("should serialize"); + assert_eq!(json, "{\"cck_hex\":\"1111111111111111111111111111111111111111111111111111111111111111\",\"xts_hex\":\"22222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222\",\"psw\":{\"mask_hex\":\"0000000000002000\",\"addr_hex\":\"0000000000001000\"},\"scf_hex\":\"0000000000000042\"}"); + let deserialized: SeHdrConfV2 = serde_json::from_str(&json).expect("should deserialize"); + + assert_eq!(conf, deserialized); + } + + #[test] + fn roundtrip_se_hdr_aad_v2_json() { + let aad = SeHdrAadV2 { + sehs: 1024, + iv: [0x33; SymKeyType::AES_256_GCM_IV_LEN], + res1: 0, + nks: 2, + sea: 512, + nep: 10, + pcf: 0x100, + cust_pub_key: EcPubKeyCoordV1 { coord: [0x44; 160] }, + pld: [0x55; SHA_512_HASH_LEN], + ald: [0x66; SHA_512_HASH_LEN], + tld: [0x77; SHA_512_HASH_LEN], + keyslots: vec![], + }; + + let json = serde_json::to_string(&aad).expect("should serialize"); + assert_eq!(json, "{\"sehs\":1024,\"iv_hex\":\"333333333333333333333333\",\"nks\":2,\"sea\":512,\"nep\":10,\"pcf_hex\":\"0000000000000100\",\"cust_pub_key\":{\"coord_hex\":\"44444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444\"},\"pld_hex\":\"55555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555\",\"ald_hex\":\"66666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666\",\"tld_hex\":\"77777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777\",\"keyslots\":[]}"); + let deserialized: SeHdrAadV2 = serde_json::from_str(&json).expect("should deserialize"); + + assert_eq!(aad, deserialized); + } + + #[test] + fn roundtrip_se_hdr_bin_v2_json() { + let bin = SeHdrBinV2 { + aad: SeHdrAadV2 { + sehs: 1024, + iv: [0x33; SymKeyType::AES_256_GCM_IV_LEN], + res1: 0, + nks: 0, + sea: 64, + nep: 10, + pcf: 0x100, + cust_pub_key: EcPubKeyCoordV1 { coord: [0x44; 160] }, + pld: [0x55; SHA_512_HASH_LEN], + ald: [0x66; SHA_512_HASH_LEN], + tld: [0x77; SHA_512_HASH_LEN], + keyslots: vec![], + }, + data: vec![0x88; 64], + tag: SeHdrTagV2 { + tag: [0x99; SymKeyType::AES_256_GCM_TAG_LEN], + }, + }; + + let json = serde_json::to_string(&bin).expect("should serialize"); + assert_eq!(json, "{\"sehs\":1024,\"iv_hex\":\"333333333333333333333333\",\"nks\":0,\"sea\":64,\"nep\":10,\"pcf_hex\":\"0000000000000100\",\"cust_pub_key\":{\"coord_hex\":\"44444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444\"},\"pld_hex\":\"55555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555\",\"ald_hex\":\"66666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666\",\"tld_hex\":\"77777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777\",\"keyslots\":[],\"cipher_data_b64\":\"iIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiA==\",\"tag_hex\":\"99999999999999999999999999999999\"}"); + let deserialized: SeHdrBinV2 = serde_json::from_str(&json).expect("should deserialize"); + + assert_eq!(bin, deserialized); + } +} diff --git a/rust/pvimg/src/pv_utils/se_hdr/keys.rs b/rust/pvimg/src/pv_utils/se_hdr/keys.rs index 85639bde..bfa834b6 100644 --- a/rust/pvimg/src/pv_utils/se_hdr/keys.rs +++ b/rust/pvimg/src/pv_utils/se_hdr/keys.rs @@ -10,7 +10,7 @@ use deku::ctx::Endian; use deku::{DekuRead, DekuWrite}; use openssl::hash::{hash, MessageDigest}; use openssl::pkey::{PKey, PKeyRef, Public}; -use pv::request::EcPubKeyCoord; +use pv::request::{EcPubKeyCoord, HybridPKey}; use pv::static_assert; use serde::{Deserialize, Serialize}; use utils::HexSlice; @@ -30,6 +30,20 @@ pub fn phkh_v1>>(key: T) -> Result<[u8; 32]> { try_copy_slice_to_array(&binding) } +/// Try to hash the public hybrid EC + ML-KEM key. +/// +/// # Errors +/// +/// This function will return an error if OpenSSL could not hash the key. +pub fn phkh_v2(key: &HybridPKey) -> Result<[u8; 64]> { + let mut buf: Vec = vec![]; + let phk: EcPubKeyCoord = key.ec_key().try_into()?; + buf.extend_from_slice(phk.as_ref()); + buf.extend_from_slice(&key.mlkem_key().raw_public_key()?); + let binding = hash(MessageDigest::sha512(), &buf)?; + try_copy_slice_to_array(&binding) +} + #[derive(Debug, Clone, PartialEq, Eq, DekuRead, DekuWrite, Serialize, Deserialize)] #[deku(endian = "endian", ctx = "endian: Endian", ctx_default = "Endian::Big")] pub struct EcPubKeyCoordV1 { @@ -112,6 +126,58 @@ impl TryFrom> for BinaryKeySlotV1 { } } +#[repr(C)] +#[derive(Debug, PartialEq, Eq, Clone, DekuRead, DekuWrite, Serialize, Deserialize)] +#[deku(endian = "endian", ctx = "endian: Endian", ctx_default = "Endian::Big")] +/// Binary key slot v1 +pub struct BinaryKeySlotV2 { + #[serde(with = "serde_hex_array", rename = "phkh_hex")] + /// Public host key hash + pub phkh: [u8; 64], + #[serde(with = "serde_hex_array", rename = "wrpk_hex")] + /// Wrapper key + pub wrpk: [u8; 32], + /// Tag + #[serde(with = "serde_hex_array", rename = "kst_hex")] + pub kst: [u8; 16], + #[serde(with = "serde_hex_array", rename = "kc_hex")] + /// Ciohertext + pub kc: [u8; 1568], +} +static_assert!(size_of::() == 1680); + +impl Display for BinaryKeySlotV2 { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "target key hash: {:}", HexSlice::from(&self.phkh)) + } +} + +impl Default for BinaryKeySlotV2 { + fn default() -> Self { + BinaryKeySlotV2 { + phkh: [0_u8; 64], + wrpk: [0_u8; 32], + kst: [0_u8; 16], + kc: [0_u8; 1568], + } + } +} + +impl TryFrom> for BinaryKeySlotV2 { + type Error = Error; + + fn try_from(value: Vec) -> Result { + let data: [u8; 1680] = try_copy_slice_to_array(&value)?; + let bin = Self { + phkh: data[..64].try_into().unwrap(), + wrpk: data[64..96].try_into().unwrap(), + kst: data[96..112].try_into().unwrap(), + kc: data[112..].try_into().unwrap(), + }; + Ok(bin) + } +} + #[cfg(test)] mod serde_tests { use super::*; diff --git a/rust/pvimg/src/pv_utils/uv_keys.rs b/rust/pvimg/src/pv_utils/uv_keys.rs index ce3da76a..7170cc6b 100644 --- a/rust/pvimg/src/pv_utils/uv_keys.rs +++ b/rust/pvimg/src/pv_utils/uv_keys.rs @@ -49,7 +49,8 @@ pub struct UvKeyHashesV1 { } impl UvKeyHashV1 { - pub const UV_KEY_HASH_NULL: Self = Self([0x0_u8; 32]); + pub const UV_KEY_HASH_SIZE: usize = 32; + pub const UV_KEY_HASH_NULL: Self = Self([0x0_u8; Self::UV_KEY_HASH_SIZE]); } impl AsRef<[u8]> for UvKeyHashV1 { diff --git a/rust/pvimg/src/pv_utils/uvdata.rs b/rust/pvimg/src/pv_utils/uvdata.rs index 5c2e8f80..921b1262 100644 --- a/rust/pvimg/src/pv_utils/uvdata.rs +++ b/rust/pvimg/src/pv_utils/uvdata.rs @@ -3,12 +3,10 @@ // Copyright IBM Corp. 2024 use enum_dispatch::enum_dispatch; -use pv::request::openssl::pkey::{PKey, PKeyRef, Private, Public}; -use pv::request::{ - decrypt_aead, derive_aes256_gcm_key, encrypt_aead, Confidential, SymKey, SymKeyType, -}; +use pv::request::openssl::pkey::{PKey, Public}; +use pv::request::{decrypt_aead, encrypt_aead, Confidential, SymKey, SymKeyType}; -use super::se_hdr::{SeHdrBinV1, SeHdrData, SeHdrVersioned}; +use super::se_hdr::{SeHdrBinV1, SeHdrBinV2, SeHdrData, SeHdrVersioned}; use crate::pv_utils::error::{Error, Result}; use crate::pv_utils::serializing::deserialize_from_bytes; @@ -54,15 +52,19 @@ pub trait AeadPlainDataTrait { } /// Key exchange related methods -#[enum_dispatch] pub trait KeyExchangeTrait { - /// Checks if a public key was used. + type TargetKeyType; + type PrivateKeyType: ToOwned; + + /// Checks if a public target key was used. /// /// # Errors /// /// This function will return an error if the public key cannot be converted /// into a hash. - fn contains>>(&self, key: K) -> Result; + fn contains(&self, key: K) -> Result + where + K: AsRef; /// Checks if the hash of a public key was used. fn contains_hash>(&self, hash: H) -> bool; @@ -79,22 +81,24 @@ pub trait KeyExchangeTrait { /// Returns the key type of the exchanged key. fn key_type(&self) -> SymKeyType; - /// Derive the key. - /// - /// # Errors - /// - /// This function will return an error if there is no customer public key is - /// available or the key derivations fails. - fn derive_key>>(&mut self, other_priv_key: K) -> Result { - match self.key_type() { - SymKeyType::Aes256Gcm => Ok(derive_aes256_gcm_key( - other_priv_key.as_ref(), - self.cust_pub_key()?.as_ref(), - )? - .into()), - _ => unreachable!("BUG"), - } - } + // TODO Implement it + // /// Derive the key. + // /// + // /// # Errors + // /// + // /// This function will return an error if there is no customer public key is + // /// available or the key derivations fails. + // fn derive_key(&mut self, other_priv_key: K) -> Result where + // K: AsRef{ + // match self.key_type() { + // SymKeyType::Aes256Gcm => + // match Self::TargetKeyType { + // PKeyRef:: => {todo!()}, + // HybridPKey => {todo!()}, + // } + // _ => unreachable!("BUG"), + // } + // } } /// Trait to be used for plain UV data. diff --git a/rust/pvimg/src/pv_utils/uvdata_builder.rs b/rust/pvimg/src/pv_utils/uvdata_builder.rs index c50f1927..6e609ee2 100644 --- a/rust/pvimg/src/pv_utils/uvdata_builder.rs +++ b/rust/pvimg/src/pv_utils/uvdata_builder.rs @@ -2,13 +2,14 @@ // // Copyright IBM Corp. 2024 +use std::fmt::Display; + use enum_dispatch::enum_dispatch; -use openssl::pkey::{PKey, PKeyRef, Private, Public}; +use openssl::pkey::{PKey, Private}; use pv::request::{Confidential, SymKey}; use super::Error; use crate::pv_utils::error::Result; -use crate::pv_utils::se_hdr::SeHdrData; use crate::pv_utils::uvdata::{AeadCipherTrait, UvDataPlainTrait}; #[enum_dispatch] @@ -20,33 +21,33 @@ pub trait AeadCipherBuilderTrait: AeadCipherTrait { } /// Key exchange related methods -#[enum_dispatch] pub trait KeyExchangeBuilderTrait { + type TargetKeyType: ToOwned; + type AeadKeyType: Display + std::fmt::Debug; + type PrivateKeyType: ToOwned; + fn add_keyslot( &mut self, - hostkey: &PKeyRef, - aead_key: &SymKey, - priv_key: &PKeyRef, + hostkey: &Self::TargetKeyType, + aead_key: &Self::AeadKeyType, + priv_key: &Self::PrivateKeyType, ) -> Result<()>; + fn clear_keyslots(&mut self) -> Result<()>; + // TODO How to handle PKey vs &PKeyRef? fn generate_private_key(&self) -> Result>; - fn set_cust_public_key(&mut self, key: &PKeyRef) -> Result<()>; + fn set_cust_public_key(&mut self, key: &Self::PrivateKeyType) -> Result<()>; } -pub struct UvDataBuilder< - 'a, - T: KeyExchangeBuilderTrait + AeadCipherBuilderTrait, - K = PKeyRef, - P = PKey, -> { +pub struct UvDataBuilder { pub(crate) expert_mode: bool, - pub(crate) prot_key: SymKey, - pub(crate) priv_key: P, - pub(crate) target_keys: Vec<&'a K>, + pub(crate) prot_key: T::AeadKeyType, + pub(crate) priv_key: T::PrivateKeyType, + pub(crate) target_keys: Vec, pub(crate) plain_data: T, } -impl UvDataBuilder<'_, T, K, P> { +impl UvDataBuilder { /// Enable expert mode - this is required for specifying PSW, etc. pub fn i_know_what_i_am_doing(&mut self) { self.expert_mode = true; @@ -54,7 +55,7 @@ impl UvDataBuilder<'_ } impl - std::fmt::Debug for UvDataBuilder<'_, T> + std::fmt::Debug for UvDataBuilder { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("UvDataBuilder") @@ -65,15 +66,18 @@ impl UvDataBuilder<'a, T> { - pub fn add_hostkeys>>( - &mut self, - hostkeys: &'a [P], - ) -> Result<&mut Self> { +impl UvDataBuilder +where + T: KeyExchangeBuilderTrait + AeadCipherBuilderTrait, +{ + pub fn add_hostkeys(&mut self, hostkeys: &[T::TargetKeyType]) -> Result<&mut Self> + where + T::TargetKeyType: Clone, + { for hk in hostkeys { self.plain_data - .add_keyslot(hk.as_ref(), &self.prot_key, &self.priv_key)?; - self.target_keys.push(hk.as_ref()); + .add_keyslot(hk, &self.prot_key, &self.priv_key)?; + self.target_keys.push(hk.clone()); } Ok(self) @@ -89,9 +93,9 @@ impl<'a, T: KeyExchangeBuilderTrait + AeadCipherBuilderTrait> UvDataBuilder<'a, fn update_target_key_slots(&mut self) -> Result<()> { self.plain_data.clear_keyslots()?; - for hk in &self.target_keys { + for hostkey in &self.target_keys { self.plain_data - .add_keyslot(hk, &self.prot_key, &self.priv_key)?; + .add_keyslot(hostkey, &self.prot_key, &self.priv_key)?; } Ok(()) } @@ -100,6 +104,7 @@ impl<'a, T: KeyExchangeBuilderTrait + AeadCipherBuilderTrait> UvDataBuilder<'a, if !self.expert_mode { return Err(Error::NonExpertMode); } + // TODO Implement TryFrom<...> ?! let key = SymKey::try_from_data(self.plain_data.aead_key_type(), data)?; self.prot_key = key; self.update_target_key_slots()?; @@ -107,23 +112,26 @@ impl<'a, T: KeyExchangeBuilderTrait + AeadCipherBuilderTrait> UvDataBuilder<'a, Ok(self) } - pub fn with_priv_key(&mut self, priv_key: &PKeyRef) -> Result<&mut Self> { + pub fn with_priv_key(&mut self, priv_key: &T::PrivateKeyType) -> Result<&mut Self> + where + T::PrivateKeyType: Clone, + { if !self.expert_mode { return Err(Error::NonExpertMode); } self.plain_data.set_cust_public_key(priv_key)?; - self.priv_key = priv_key.to_owned(); + self.priv_key = priv_key.clone(); self.update_target_key_slots()?; Ok(self) } - pub const fn prot_key(&self) -> &SymKey { + pub const fn prot_key(&self) -> &::AeadKeyType { &self.prot_key } - pub fn priv_key(&self) -> &PKeyRef { - self.priv_key.as_ref() + pub fn priv_key(&self) -> &::PrivateKeyType { + &self.priv_key } } diff --git a/rust/pvimg/src/se_img.rs b/rust/pvimg/src/se_img.rs index de422bd8..9b4e612f 100644 --- a/rust/pvimg/src/se_img.rs +++ b/rust/pvimg/src/se_img.rs @@ -10,9 +10,8 @@ use std::rc::Rc; use anyhow::{anyhow, Context, Result}; use deku::DekuContainerRead; use log::debug; -use openssl::pkey::{PKey, Public}; use pv::misc::read_file; -use pv::request::Confidential; +use pv::request::{Confidential, HostKey}; use pvimg::error::Error; use pvimg::misc::{round_up, serialize_to_bytes, ShortPsw, PSW, PSW_MASK_BA, PSW_MASK_EA}; use pvimg::secured_comp::{ @@ -31,7 +30,7 @@ use crate::se_img_comps::{ }; pub struct SeHdrArgs<'a> { - pub keys: &'a [PKey], + pub keys: &'a [HostKey], pub pcf: &'a EffectiveControlFlags, pub scf: &'a EffectiveControlFlags, pub cck: &'a Option<(PathBuf, Confidential>)>, @@ -93,7 +92,7 @@ impl SeImgBuilder { /// Create a Secure Execution boot image builder #[allow(clippy::similar_names)] - pub(crate) fn new_v1( + pub(crate) fn new( mut writer: W, encryption: bool, legacy_expected_se_hdr_size: Option, @@ -320,8 +319,16 @@ impl SeImgBuilder { fn add_sehdr(&mut self, stage3b_entry: u64, sehdr_args: SeHdrArgs) -> Result> { let meta = self.builder.finish()?; + // Determine version from first key (all keys should be same version) + let version = match sehdr_args.keys.first() { + Some(HostKey::V1(_)) => SeHdrVersion::V1, + Some(HostKey::V2(_)) => SeHdrVersion::V2, + Some(_) => unreachable!("Unknown HostKey version"), + None => return Err(Error::NoHostkey.into()), + }; + let mut se_hdr_builder = SeHdrBuilder::new( - SeHdrVersion::V1, + version, PSW { addr: sehdr_args.psw_addr.unwrap_or(stage3b_entry), mask: Self::DEFAULT_INITIAL_PSW_MASK, @@ -495,7 +502,7 @@ mod tests { let encryption = true; let mut writer = Cursor::new(Vec::new()); - let ctx_res = SeImgBuilder::new_v1(&mut writer, encryption, None, None); + let ctx_res = SeImgBuilder::new(&mut writer, encryption, None, None); assert!(ctx_res.is_ok()); let ctx = ctx_res.unwrap();