pvimg: Use hybrid keys

Allow the creation of SE images using headers with hybrid (=quantum safe) keys.
This results in using the headers in version 2 (0x200).

Co-developed-by: Marc Hartmayer <marc@linux.ibm.com>
Signed-off-by: Marc Hartmayer <marc@linux.ibm.com>
Signed-off-by: Timo Keller <tkeller@linux.ibm.com>
Reviewed-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
This commit is contained in:
Timo Keller
2026-07-21 14:29:32 +02:00
committed by Steffen Eiden
parent d6fc4921fb
commit 89577c2f8c
22 changed files with 1814 additions and 146 deletions

View File

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

View File

@@ -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};

View File

@@ -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};

View File

@@ -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<HdrVersion>;
impl From<HdrVersion> 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<bool>,
/// 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<bool>,
/// 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<bool>,
@@ -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");

View File

@@ -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;

View File

@@ -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<HdrVersion> 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<OwnExitCode> {
// 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<OwnExitCode> {
// 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<HostKey> = vec![];
let version = auto_detect_version(&keys);
assert_eq!(version, SeHdrVersion::V1);
}
#[test]
fn test_determine_version_explicit_v1() {
let keys: Vec<HostKey> = 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<HostKey> = 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<HostKey> = 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 {

View File

@@ -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()
);
}
}

View File

@@ -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,
};
}

View File

@@ -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};

View File

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

View File

@@ -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::{

View File

@@ -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<Private>;
type TargetKeyType = pv::request::HostKey;
fn contains<K>(&self, key: K) -> Result<bool>
where
K: AsRef<Self::TargetKeyType>,
{
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<H: AsRef<[u8]>>(&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<PKey<Public>> {
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<Private>;
type TargetKeyType = pv::request::HostKey;
fn contains_hash<H: AsRef<[u8]>>(&self, hash: H) -> bool {
self.data.contains_hash(hash)
}
fn contains<K: AsRef<PKeyRef<Public>>>(&self, key: K) -> Result<bool> {
self.data.contains(key)
}
fn cust_pub_key(&mut self) -> Result<PKey<Public>> {
self.data.cust_pub_key()
}
@@ -331,6 +392,13 @@ impl KeyExchangeTrait for SeHdr {
fn key_type(&self) -> SymKeyType {
self.aead_key_type()
}
fn contains<K>(&self, key: K) -> Result<bool>
where
K: AsRef<Self::TargetKeyType>,
{
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<Private>;
type TargetKeyType = pv::request::HostKey;
fn add_keyslot(
&mut self,
hostkey: &PKeyRef<Public>,
aead_key: &SymKey,
priv_key: &PKeyRef<Private>,
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<PKey<Private>> {
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<Private>) -> 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<K: AsRef<PKeyRef<Public>>>(&self, key: K) -> Result<bool> {
self.data.contains(key)
}
type PrivateKeyType = PKey<Private>;
type TargetKeyType = pv::request::HostKey;
fn cust_pub_key(&mut self) -> Result<PKey<Public>> {
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<H: AsRef<[u8]>>(&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<K>(&self, key: K) -> Result<bool>
where
K: AsRef<Self::TargetKeyType>,
{
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),
}
}
}

View File

@@ -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<SeHdrPlain>;
impl SeHdrBuilder<'_> {
pub fn new<M: Into<ComponentMetadata>>(
@@ -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");
}
}

View File

@@ -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<Msb0Flags64> 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<Msb0Flags64> 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));
}
}

View File

@@ -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"),
}
}
}

View File

@@ -106,10 +106,8 @@ impl Display for SeHdrAadV1 {
}
impl KeyExchangeTrait for SeHdrAadV1 {
fn contains<K: AsRef<PKeyRef<Public>>>(&self, key: K) -> Result<bool> {
let phkh = phkh_v1(key)?;
Ok(self.contains_hash(phkh))
}
type PrivateKeyType = PKey<Private>;
type TargetKeyType = PKeyRef<Public>;
fn cust_pub_key(&mut self) -> Result<PKey<Public>> {
self.cust_pub_key.clone().try_into()
@@ -128,6 +126,14 @@ impl KeyExchangeTrait for SeHdrAadV1 {
}
false
}
fn contains<K>(&self, key: K) -> Result<bool>
where
K: AsRef<Self::TargetKeyType>,
{
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<Private>;
type TargetKeyType = PKeyRef<Public>;
fn add_keyslot(
&mut self,
hostkey: &PKeyRef<Public>,
aead_key: &SymKey,
priv_key: &PKeyRef<Private>,
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<K: AsRef<PKeyRef<Public>>>(&self, key: K) -> Result<bool> {
self.aad.contains(key)
}
type PrivateKeyType = PKey<Private>;
type TargetKeyType = PKeyRef<Public>;
fn cust_pub_key(&mut self) -> Result<PKey<Public>> {
self.aad.cust_pub_key()
@@ -475,6 +484,13 @@ impl KeyExchangeTrait for SeHdrDataV1 {
fn contains_hash<H: AsRef<[u8]>>(&self, hash: H) -> bool {
self.aad.contains_hash(hash)
}
fn contains<K>(&self, key: K) -> Result<bool>
where
K: AsRef<Self::TargetKeyType>,
{
self.aad.contains(key)
}
}
impl SeHdrConfBuilderTrait for SeHdrDataV1 {
@@ -590,9 +606,8 @@ impl AeadCipherBuilderTrait for SeHdrDataV1 {
}
impl KeyExchangeTrait for SeHdrBinV1 {
fn contains<K: AsRef<PKeyRef<Public>>>(&self, key: K) -> Result<bool> {
self.aad.contains(key)
}
type PrivateKeyType = PKey<Private>;
type TargetKeyType = PKeyRef<Public>;
fn cust_pub_key(&mut self) -> Result<PKey<Public>> {
self.aad.cust_pub_key()
@@ -605,6 +620,13 @@ impl KeyExchangeTrait for SeHdrBinV1 {
fn contains_hash<H: AsRef<[u8]>>(&self, hash: H) -> bool {
self.aad.contains_hash(hash)
}
fn contains<K>(&self, key: K) -> Result<bool>
where
K: AsRef<Self::TargetKeyType>,
{
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]);

View File

@@ -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<BinaryKeySlotV2>,
}
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<Private>;
type TargetKeyType = HybridPKey;
fn cust_pub_key(&mut self) -> Result<PKey<Public>> {
self.cust_pub_key.clone().try_into()
}
fn key_type(&self) -> SymKeyType {
Self::KEY_TYPE
}
fn contains_hash<H: AsRef<[u8]>>(&self, hash: H) -> bool {
let hash = hash.as_ref();
self.keyslots
.iter()
.any(|ks| &ks.phkh[..UvKeyHashV1::UV_KEY_HASH_SIZE] == hash)
}
fn contains<K>(&self, key: K) -> Result<bool>
where
K: AsRef<Self::TargetKeyType>,
{
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<u8>,
}
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<S: Serializer>(
encrypted: &Confidential<SeHdrConfV2>,
ser: S,
) -> Result<S::Ok, S::Error> {
encrypted.value().serialize(ser)
}
pub fn deserialize<'de, D: Deserializer<'de>>(
deserializer: D,
) -> Result<Confidential<SeHdrConfV2>, 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<SeHdrConfV2>,
#[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<R>(
reader: &mut Reader<R>,
) -> Result<Confidential<SeHdrConfV2>, DekuError>
where
R: std::io::Read + std::io::Seek,
{
Ok(Confidential::new(SeHdrConfV2::from_reader_with_ctx(
reader,
(),
)?))
}
/// Writes a `Confidential<SeHdrConf1>` into this `writer`.
///
/// # Errors
///
/// This function will return an error if there was an I/O error.
fn confidential_write_sehdrconf_v2<W>(
value: &Confidential<SeHdrConfV2>,
writer: &mut Writer<W>,
) -> 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<Self> {
// 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<HdrSizesV2> {
let sea = bytesize_confidential(&self.data)?;
let mut phs = bytesize(&self.aad)?
.checked_add(size_of::<SeHdrCommon>())
.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<usize> {
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::<BinaryKeySlotV2>()
.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<Private>;
type TargetKeyType = HybridPKey;
fn generate_private_key(&self) -> Result<PKey<Private>> {
Ok(gen_ec_key(Nid::SECP521R1)?)
}
fn set_cust_public_key(&mut self, key: &PKeyRef<Private>) -> Result<()> {
self.aad.cust_pub_key = TryInto::<EcPubKeyCoord>::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::<BinaryKeySlotV2>();
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<Private>;
type TargetKeyType = HybridPKey;
fn cust_pub_key(&mut self) -> Result<PKey<Public>> {
self.aad.cust_pub_key()
}
fn key_type(&self) -> SymKeyType {
self.aad.key_type()
}
fn contains_hash<H: AsRef<[u8]>>(&self, hash: H) -> bool {
self.aad.contains_hash(hash)
}
fn contains<K>(&self, key: K) -> Result<bool>
where
K: AsRef<Self::TargetKeyType>,
{
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<SeHdrFlag>) -> Result<()> {
self.data.value_mut().scf = scf.to_u64();
Ok(())
}
fn set_cck(&mut self, cck: Confidential<Vec<u8>>) -> Result<()> {
self.data.value_mut().cck = cck.try_into()?;
Ok(())
}
fn generate_cck(&self) -> Result<SymKey> {
Ok(SymKey::random(SymKeyType::Aes256Gcm)?)
}
}
impl SeHdrPubBuilderTrait for SeHdrDataV2 {
fn set_pcf(&mut self, pcf: &EffectiveControlFlags<SeHdrFlag>) -> 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<u8>,
#[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> {
Self::try_from_data(d)
}
pub(crate) fn try_from_data(data: &[u8]) -> Result<Self> {
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<Private>;
type TargetKeyType = HybridPKey;
fn cust_pub_key(&mut self) -> Result<PKey<Public>> {
self.aad.cust_pub_key()
}
fn key_type(&self) -> SymKeyType {
self.aad.key_type()
}
fn contains_hash<H: AsRef<[u8]>>(&self, hash: H) -> bool {
self.aad.contains_hash(hash)
}
fn contains<K>(&self, key: K) -> Result<bool>
where
K: AsRef<Self::TargetKeyType>,
{
self.aad.contains(key)
}
}
impl AeadDataTrait for SeHdrBinV2 {
fn aad(&self) -> Result<Vec<u8>> {
serialize_to_bytes(&self.aad)
}
fn data(&self) -> Vec<u8> {
self.data.to_owned()
}
fn tag(&self) -> Vec<u8> {
serialize_to_bytes(&self.tag).unwrap()
}
}
impl AeadPlainDataTrait for SeHdrDataV2 {
fn aad(&self) -> Result<Vec<u8>> {
serialize_to_bytes(&self.aad)
}
fn data(&self) -> Result<Confidential<Vec<u8>>> {
Ok(serialize_to_bytes(self.data.value())?.into())
}
fn tag(&self) -> Vec<u8> {
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<Vec<u8>> = [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);
}
}

View File

@@ -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<T: AsRef<PKeyRef<Public>>>(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<u8> = 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<Vec<u8>> 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::<BinaryKeySlotV2>() == 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<Vec<u8>> for BinaryKeySlotV2 {
type Error = Error;
fn try_from(value: Vec<u8>) -> Result<Self, Self::Error> {
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::*;

View File

@@ -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 {

View File

@@ -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<K: AsRef<PKeyRef<Public>>>(&self, key: K) -> Result<bool>;
fn contains<K>(&self, key: K) -> Result<bool>
where
K: AsRef<Self::TargetKeyType>;
/// Checks if the hash of a public key was used.
fn contains_hash<H: AsRef<[u8]>>(&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<K: AsRef<PKeyRef<Private>>>(&mut self, other_priv_key: K) -> Result<SymKey> {
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<K>(&mut self, other_priv_key: K) -> Result<SymKey> where
// K: AsRef<Self::PrivateKeyType>{
// match self.key_type() {
// SymKeyType::Aes256Gcm =>
// match Self::TargetKeyType {
// PKeyRef::<Public> => {todo!()},
// HybridPKey => {todo!()},
// }
// _ => unreachable!("BUG"),
// }
// }
}
/// Trait to be used for plain UV data.

View File

@@ -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<Public>,
aead_key: &SymKey,
priv_key: &PKeyRef<Private>,
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<PKey<Private>>;
fn set_cust_public_key(&mut self, key: &PKeyRef<Private>) -> Result<()>;
fn set_cust_public_key(&mut self, key: &Self::PrivateKeyType) -> Result<()>;
}
pub struct UvDataBuilder<
'a,
T: KeyExchangeBuilderTrait + AeadCipherBuilderTrait,
K = PKeyRef<Public>,
P = PKey<Private>,
> {
pub struct UvDataBuilder<T: KeyExchangeBuilderTrait + AeadCipherBuilderTrait> {
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<T::TargetKeyType>,
pub(crate) plain_data: T,
}
impl<T: KeyExchangeBuilderTrait + AeadCipherBuilderTrait, K, P> UvDataBuilder<'_, T, K, P> {
impl<T: KeyExchangeBuilderTrait + AeadCipherBuilderTrait> UvDataBuilder<T> {
/// 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<T: KeyExchangeBuilderTrait + AeadCipherBuilderTrait, K, P> UvDataBuilder<'_
}
impl<T: std::fmt::Debug + KeyExchangeBuilderTrait + AeadCipherBuilderTrait + UvDataPlainTrait>
std::fmt::Debug for UvDataBuilder<'_, T>
std::fmt::Debug for UvDataBuilder<T>
{
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("UvDataBuilder")
@@ -65,15 +66,18 @@ impl<T: std::fmt::Debug + KeyExchangeBuilderTrait + AeadCipherBuilderTrait + UvD
}
}
impl<'a, T: KeyExchangeBuilderTrait + AeadCipherBuilderTrait> UvDataBuilder<'a, T> {
pub fn add_hostkeys<P: AsRef<PKeyRef<Public>>>(
&mut self,
hostkeys: &'a [P],
) -> Result<&mut Self> {
impl<T> UvDataBuilder<T>
where
T: KeyExchangeBuilderTrait<AeadKeyType = SymKey> + 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<Private>) -> 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) -> &<T as KeyExchangeBuilderTrait>::AeadKeyType {
&self.prot_key
}
pub fn priv_key(&self) -> &PKeyRef<Private> {
self.priv_key.as_ref()
pub fn priv_key(&self) -> &<T as KeyExchangeBuilderTrait>::PrivateKeyType {
&self.priv_key
}
}

View File

@@ -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<Public>],
pub keys: &'a [HostKey],
pub pcf: &'a EffectiveControlFlags<SeHdrFlag>,
pub scf: &'a EffectiveControlFlags<SeHdrFlag>,
pub cck: &'a Option<(PathBuf, Confidential<Vec<u8>>)>,
@@ -93,7 +92,7 @@ impl<W: Write + Seek> SeImgBuilder<W> {
/// 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<usize>,
@@ -320,8 +319,16 @@ impl<W: Write + Seek> SeImgBuilder<W> {
fn add_sehdr(&mut self, stage3b_entry: u64, sehdr_args: SeHdrArgs) -> Result<Rc<ImgComponent>> {
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();