mirror of
https://github.com/ibm-s390-linux/s390-tools.git
synced 2026-08-05 02:14:52 +00:00
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:
committed by
Steffen Eiden
parent
d6fc4921fb
commit
89577c2f8c
@@ -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);
|
||||
|
||||
@@ -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};
|
||||
|
||||
|
||||
@@ -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};
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -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};
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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::{
|
||||
|
||||
@@ -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),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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]);
|
||||
|
||||
901
rust/pvimg/src/pv_utils/se_hdr/hdr_v2.rs
Normal file
901
rust/pvimg/src/pv_utils/se_hdr/hdr_v2.rs
Normal 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);
|
||||
}
|
||||
}
|
||||
@@ -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::*;
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user