diff --git a/rust/pv/src/error.rs b/rust/pv/src/error.rs index c6f7191e..06906833 100644 --- a/rust/pv/src/error.rs +++ b/rust/pv/src/error.rs @@ -64,8 +64,11 @@ pub enum Error { #[error("No user-key for verification provided and user-data is signed")] BinAsrcbNoUserDataSgnKey, - #[error("Input does not contain an add-secret request version 1")] - BinAsrcbInvVersion, + #[error("Input contains an unknown add-secret request version {0}")] + BinAsrcbInvVersion(u32), + + #[error("Unsupported add-secret request version: {0}")] + UnsupportedAddSecretVersion(u32), #[error("Provided user-data key type ({key}) does not match with the user-data ({kind})")] AsrcbUserDataKeyMismatch { key: String, kind: UserDataType }, diff --git a/rust/pv/src/uvsecret/asrcb.rs b/rust/pv/src/uvsecret/asrcb.rs index 774ca3b8..5d785062 100644 --- a/rust/pv/src/uvsecret/asrcb.rs +++ b/rust/pv/src/uvsecret/asrcb.rs @@ -6,31 +6,32 @@ use openssl::md::Md; use openssl::pkey::{PKey, Private}; use pv_core::request::RequestVersion; use pv_core::secret::AddSecretMagic; +use pv_core::static_assert; use pv_core::uv::SecretId; use zerocopy::{Immutable, IntoBytes}; -use super::guest_secret::ListableSecretHdr; +use super::guest_secret::{ListableSecretHdr, SecretAuth}; use super::user_data::UserData; use crate::crypto::{hkdf_rfc_5869, AeadEncryptionResult}; use crate::misc::Flags; -use crate::req::{Aad, BinReqValues, HostKey, Keyslot, ReqEncrCtx}; +use crate::req::{Aad, BinReqValues, HostKey, Keyslot, ReqEncrCtx, RequestHdr}; use crate::request::{BootHdrTags, Confidential, Request}; use crate::secret::{ExtSecret, GuestSecret}; use crate::uv::{ConfigUid, UvFlags}; -use crate::{assert_size, Result}; +use crate::{assert_size, Error, Result}; /// Authenticated data w/o user data #[repr(C)] #[derive(Debug, Clone, Copy, IntoBytes, Immutable)] -struct ReqAuthData { +struct ReqAuthDataV1 { flags: UvFlags, boot_tags: BootHdrTags, cuid: ConfigUid, reserved90: [u8; 0x100], } -assert_size!(ReqAuthData, 0x1e8); +assert_size!(ReqAuthDataV1, 0x1e8); -impl ReqAuthData { +impl ReqAuthDataV1 { fn new>(boot_tags: BootHdrTags, flags: F) -> Self { Self { flags: flags.into(), @@ -41,6 +42,30 @@ impl ReqAuthData { } } +/// Authenticated data w/o user data for v2 header: move up secret header 2 +#[repr(C)] +#[derive(Debug, Clone, IntoBytes, Immutable)] +struct ReqAuthDataV2 { + flags: UvFlags, + boot_tags: BootHdrTags, + cuid: ConfigUid, + secr_auth: [u8; 0x30], + reservedd0: [u8; 0x100 - 0x30], +} +assert_size!(ReqAuthDataV2, 0x1e8); + +impl ReqAuthDataV2 { + fn new>(boot_tags: BootHdrTags, flags: F, secr_auth: [u8; 0x30]) -> Self { + Self { + flags: flags.into(), + boot_tags, + cuid: [0; 0x10], + secr_auth, + reservedd0: [0; 0xd0], + } + } +} + #[derive(Debug)] struct ReqConfData { secret: GuestSecret, @@ -92,6 +117,8 @@ impl From for UvFlags { pub enum AddSecretVersion { /// Version 1 (= 0x0100) One = 0x0100, + /// Version 2 (= 0x0200) + Two = 0x0200, #[cfg(not(doc))] #[cfg(any(debug_assertions, test))] @@ -99,17 +126,182 @@ pub enum AddSecretVersion { Inv = 0, } +impl TryFrom for AddSecretVersion { + type Error = Error; + + fn try_from(value: u32) -> Result { + if value == Self::One as u32 { + Ok(Self::One) + } else if value == Self::Two as u32 { + Ok(Self::Two) + } else { + Err(Error::BinAsrcbInvVersion(value)) + } + } +} + impl From for RequestVersion { fn from(val: AddSecretVersion) -> Self { val as Self } } +impl From for AddSecretVersion { + fn from(val: crate::request::SeHdrVersion) -> Self { + match val { + crate::request::SeHdrVersion::One => AddSecretVersion::One, + crate::request::SeHdrVersion::Two => AddSecretVersion::Two, + } + } +} + +/// Trait for authenticated data in add-secret requests. +/// +/// This trait provides a common interface for different versions of authenticated data, +/// allowing flexible addition of new versions in the future. +trait ReqAuthData: IntoBytes + Immutable { + /// Get the configuration UID + fn cuid(&self) -> &ConfigUid; + + /// Set the configuration UID + fn set_cuid(&mut self, cuid: ConfigUid); + + /// Get the boot tags + fn boot_tags(&self) -> &BootHdrTags; + + /// Get the flags + fn flags(&self) -> &UvFlags; +} + +impl ReqAuthData for ReqAuthDataV1 { + fn cuid(&self) -> &ConfigUid { + &self.cuid + } + + fn set_cuid(&mut self, cuid: ConfigUid) { + self.cuid = cuid; + } + + fn boot_tags(&self) -> &BootHdrTags { + &self.boot_tags + } + + fn flags(&self) -> &UvFlags { + &self.flags + } +} + +impl ReqAuthData for ReqAuthDataV2 { + fn cuid(&self) -> &ConfigUid { + &self.cuid + } + + fn set_cuid(&mut self, cuid: ConfigUid) { + self.cuid = cuid; + } + + fn boot_tags(&self) -> &BootHdrTags { + &self.boot_tags + } + + fn flags(&self) -> &UvFlags { + &self.flags + } +} + +/// Enum holding version-specific authenticated data +#[derive(Debug)] +enum ReqAuthDataVersion { + V1(ReqAuthDataV1), + V2(ReqAuthDataV2), +} + +impl ReqAuthDataVersion { + fn new( + version: AddSecretVersion, + boot_tags: BootHdrTags, + flags: AddSecretFlags, + conf_data: &SecretAuth, + ) -> Result { + Ok(match version { + AddSecretVersion::One => Self::V1(ReqAuthDataV1::new(boot_tags, flags)), + AddSecretVersion::Two => Self::V2(ReqAuthDataV2::new( + boot_tags, + flags, + conf_data.get(version).try_into().expect( + "SecretAuth::get() must return exactly 0x30 bytes for AddSecretVersion::Two", + ), + )), + #[cfg(any(debug_assertions, test))] + AddSecretVersion::Inv => panic!("Invalid version for production use"), + }) + } + + #[allow(dead_code)] + fn cuid(&self) -> &ConfigUid { + match self { + Self::V1(v) => v.cuid(), + Self::V2(v) => v.cuid(), + } + } + + fn set_cuid(&mut self, cuid: ConfigUid) { + match self { + Self::V1(v) => v.set_cuid(cuid), + Self::V2(v) => v.set_cuid(cuid), + } + } + + fn boot_tags(&self) -> &BootHdrTags { + match self { + Self::V1(v) => v.boot_tags(), + Self::V2(v) => v.boot_tags(), + } + } + + #[allow(dead_code)] + fn flags(&self) -> &UvFlags { + match self { + Self::V1(v) => v.flags(), + Self::V2(v) => v.flags(), + } + } + + fn as_bytes(&self) -> &[u8] { + match self { + Self::V1(v) => v.as_bytes(), + Self::V2(v) => v.as_bytes(), + } + } +} + /// Add-secret request Control Block /// /// An ASRCB wraps a secret to securely transport it to the Ultravisor. /// -/// Layout: +/// Layout V2: +/// ```none +/// _______________________________________________________________ +/// | generic header (48) +/// | --------------------------------------------------- | +/// | Plaintext Add-Secret flags (8) | +/// | SE header tags: PLD(64) ALD(64) TLD(64) HeaderTag(16) | +/// | Configuration unique ID(16) (Attestation) | +/// | Optional, defaults to 0 | +/// | Secret header (48) | +/// | Reserved(208) | +/// | User Data(512) (reserved) | +/// | Customer Public Key (160) generated for each request | +/// | N Keyslots(1680 each) | +/// | --------------------------------------------------- | +/// | Secret to add (Secret type dependent)(may be 0 bytes) | Encrypted +/// | Extension secret(32) Optional, defaults to 0 | Encrypted +/// | --------------------------------------------------- | +/// | AES GCM Tag (16) | +/// |_____________________________________________________________| +/// ``` +/// +/// Layout V1: /// ```none /// _______________________________________________________________ /// | generic header (48) @@ -133,15 +325,19 @@ impl From for RequestVersion { #[derive(Debug)] pub struct AddSecretRequest { version: AddSecretVersion, - aad: ReqAuthData, + aad: ReqAuthDataVersion, keyslots: Vec, conf: ReqConfData, user_data: UserData, } +static_assert!(AddSecretRequest::USER_DATA_OFFS == 0x218); +static_assert!( + AddSecretRequest::USER_DATA_OFFS == size_of::() + size_of::() +); impl AddSecretRequest { /// Offset of the user-data in the add-secret request in bytes - pub(super) const V1_USER_DATA_OFFS: usize = 0x218; + pub(super) const USER_DATA_OFFS: usize = size_of::() + size_of::(); /// Create a new add-secret request. /// @@ -152,22 +348,23 @@ impl AddSecretRequest { secret: GuestSecret, boot_tags: BootHdrTags, flags: AddSecretFlags, - ) -> Self { - Self { - conf: ReqConfData { - extension_secret: Confidential::new([0; 32]), - secret, - }, - aad: ReqAuthData::new(boot_tags, flags), + ) -> Result { + let conf = ReqConfData { + extension_secret: Confidential::new([0; 32]), + secret, + }; + Ok(Self { + aad: ReqAuthDataVersion::new(version, boot_tags, flags, &conf.secret.auth())?, + conf, keyslots: vec![], version, user_data: UserData::Null, - } + }) } /// Sets the Configuration Unique Id of this [`AddSecretRequest`]. pub fn set_cuid(&mut self, cuid: ConfigUid) { - self.aad.cuid = cuid; + self.aad.set_cuid(cuid); } /// Sets the extension secret of this [`AddSecretRequest`]. @@ -182,7 +379,7 @@ impl AddSecretRequest { ExtSecret::Derived(cck) => hkdf_rfc_5869( Md::sha512(), cck.value(), - self.aad.boot_tags.tag(), + self.aad.boot_tags().tag(), DER_EXT_SECRET_INFO, )? .into(), @@ -221,7 +418,6 @@ impl AddSecretRequest { let cust_pub_key = ctx.key_coords()?; let secr_auth = self.conf.secret.auth(); let user_data = self.user_data.data(); - let mut aad: Vec = Vec::with_capacity(5 + self.keyslots.len()); aad.push(Aad::Plain(self.aad.as_bytes())); if let Some(data) = user_data.0 { @@ -232,7 +428,13 @@ impl AddSecretRequest { } aad.push(Aad::Plain(cust_pub_key.as_ref())); self.keyslots.iter().for_each(|k| aad.push(Aad::Ks(k))); - aad.push(Aad::Plain(secr_auth.get())); + // write secret header (1) only for v1 + match self.version { + AddSecretVersion::One => aad.push(Aad::Plain(secr_auth.get(AddSecretVersion::One))), + AddSecretVersion::Two => {} + #[cfg(any(debug_assertions, test))] + _ => return Err(Error::UnsupportedAddSecretVersion(self.version as u32)), + } ctx.build_aad(self.version.into(), &aad, conf_len, self.user_data.magic()) } @@ -273,7 +475,7 @@ impl AddSecretRequest { // add signature to authenticated data starting with USER_DATA_OFFS self.user_data.sign( &mut buf[aad_range.start..encr_range.end], - Self::V1_USER_DATA_OFFS, + Self::USER_DATA_OFFS, )?; // encrypt again with signed data diff --git a/rust/pv/src/uvsecret/guest_secret.rs b/rust/pv/src/uvsecret/guest_secret.rs index 5b720e01..6244241a 100644 --- a/rust/pv/src/uvsecret/guest_secret.rs +++ b/rust/pv/src/uvsecret/guest_secret.rs @@ -16,6 +16,7 @@ use super::asrcb::AddSecretRequest; use crate::crypto::{hash, random_array, SymKeyType}; use crate::request::openssl::{NID_ED25519, NID_ED448}; use crate::request::Confidential; +use crate::secret::AddSecretVersion; use crate::uv::{ AesSizes, AesXtsSizes, EcCurves, HmacShaSizes, ListableSecretType, RetrievableSecret, RetrieveCmd, SecretId, @@ -388,12 +389,18 @@ pub(crate) enum SecretAuth { } impl SecretAuth { - const NULL_HDR: NullSecretHdr = NullSecretHdr::new(); + const NULL_HDR_V1: NullSecretHdrV1 = NullSecretHdrV1::new(); + const NULL_HDR_V2: NullSecretHdrV2 = NullSecretHdrV2::new(); const UPDATE_CCK_HDR: UpdateCckHdr = UpdateCckHdr::new(); - pub fn get(&self) -> &[u8] { + pub fn get(&self, version: AddSecretVersion) -> &[u8] { match self { - Self::Null => Self::NULL_HDR.as_bytes(), + Self::Null => match version { + AddSecretVersion::One => Self::NULL_HDR_V1.as_bytes(), + AddSecretVersion::Two => Self::NULL_HDR_V2.as_bytes(), + #[cfg(any(debug_assertions, test))] + _ => panic!("Invalid AddSecretVersion"), + }, Self::Listable(h) => h.as_bytes(), Self::UpdateCck => Self::UPDATE_CCK_HDR.as_bytes(), } @@ -402,15 +409,15 @@ impl SecretAuth { #[repr(C)] #[derive(Debug, IntoBytes, FromBytes, Immutable, KnownLayout)] -struct NullSecretHdr { +struct NullSecretHdrV1 { res0: u16, kind: U16, secret_len: U32, res8: u64, } -assert_size!(NullSecretHdr, 0x10); +assert_size!(NullSecretHdrV1, 0x10); -impl NullSecretHdr { +impl NullSecretHdrV1 { const fn new() -> Self { Self { res0: 0, @@ -421,6 +428,29 @@ impl NullSecretHdr { } } +#[repr(C)] +#[derive(Debug, IntoBytes, FromBytes, Immutable, KnownLayout)] +struct NullSecretHdrV2 { + res0: u16, + kind: U16, + secret_len: U32, + res8: u64, + reserved1: [u8; 32], +} +assert_size!(NullSecretHdrV2, 0x30); + +impl NullSecretHdrV2 { + const fn new() -> Self { + Self { + res0: 0, + kind: U16::new(ListableSecretType::NULL), + secret_len: U32::ZERO, + res8: 0, + reserved1: [0; 32], + } + } +} + #[repr(C)] #[derive(Debug, IntoBytes, FromBytes, Immutable, KnownLayout)] pub(crate) struct ListableSecretHdr { @@ -701,7 +731,7 @@ mod test { let gs_bytes = gs.auth(); let exp = vec![0u8, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; - assert_eq!(exp, gs_bytes.get()); + assert_eq!(exp, gs_bytes.get(AddSecretVersion::One)); assert_eq!(&Vec::::new(), gs.confidential()) } @@ -716,7 +746,7 @@ mod test { let mut exp = vec![0u8, 0, 0, 2, 0, 0, 0, 0x20, 0, 0, 0, 0, 0, 0, 0, 0]; exp.extend([1; 32]); - assert_eq!(exp, gs_bytes_auth.get()); + assert_eq!(exp, gs_bytes_auth.get(AddSecretVersion::One)); assert_eq!(&[2; 32], gs.confidential()); } @@ -729,7 +759,7 @@ mod test { secret: vec![2; 32].into(), }; let auth = gs.auth(); - let gs_bytes_auth = auth.get(); + let gs_bytes_auth = auth.get(AddSecretVersion::One); let mut exp = vec![0u8, 0, 0, 3, 0, 0, 0, 0x20, 0, 0, 0, 0, 0, 0, 0, 0]; exp.extend([1; 32]); @@ -746,7 +776,7 @@ mod test { let mut exp = vec![0u8, 0, 0, 0x16, 0, 0, 0, 0x20]; exp.extend([0; 40]); - assert_eq!(exp, gs_bytes_auth.get()); + assert_eq!(exp, gs_bytes_auth.get(AddSecretVersion::One)); assert_eq!(&[2; 32], gs.confidential()); } } diff --git a/rust/pv/src/uvsecret/retr_secret.rs b/rust/pv/src/uvsecret/retr_secret.rs index 7c78592a..865fed23 100644 --- a/rust/pv/src/uvsecret/retr_secret.rs +++ b/rust/pv/src/uvsecret/retr_secret.rs @@ -133,6 +133,7 @@ impl RetrievedSecret { RetrievedSecret::ProtectedKey(p) => p.into_bytes(), } } + /// Get the data in PEM format. /// /// # Errors diff --git a/rust/pv/src/uvsecret/user_data.rs b/rust/pv/src/uvsecret/user_data.rs index 686afb37..d659d325 100644 --- a/rust/pv/src/uvsecret/user_data.rs +++ b/rust/pv/src/uvsecret/user_data.rs @@ -10,7 +10,7 @@ use crate::crypto::{sign_msg, verify_signature}; use crate::req::BinReqValues; use crate::request::openssl::pkey::{HasParams, HasPublic, Id, PKey, PKeyRef, Private, Public}; use crate::request::RequestMagic; -use crate::secret::{AddSecretMagic, AddSecretRequest, AddSecretVersion, UserDataType}; +use crate::secret::{AddSecretMagic, AddSecretRequest, UserDataType}; use crate::{assert_size, Error, Result}; /// User data. @@ -261,21 +261,19 @@ pub fn verify_asrcb_and_get_user_data( // check that the provided buffer contains an Add Secret request let magic = AddSecretMagic::try_from_bytes(&asrcb)?; let req = BinReqValues::get(&asrcb)?; - if req.version() != AddSecretVersion::One as u32 { - return Err(Error::BinAsrcbInvVersion); - } // preventing the two lines after the truncate from panicking let req_len = req.len(); if asrcb.len() < req_len - || req_len < AddSecretRequest::V1_USER_DATA_OFFS + UserData::USER_DATA_SIZE + || req_len < AddSecretRequest::USER_DATA_OFFS + UserData::USER_DATA_SIZE { return Err(pv_core::Error::NoAsrcb.into()); } + // forget the tag (and all additional data that might be behind the tag) asrcb.truncate(req_len - BinReqValues::TAG_LEN); // get a mutable refrenence on the 512 bytes of user data - let (_, user_data) = asrcb.split_at_mut(AddSecretRequest::V1_USER_DATA_OFFS); + let (_, user_data) = asrcb.split_at_mut(AddSecretRequest::USER_DATA_OFFS); let user_data = &mut user_data[..UserData::USER_DATA_SIZE]; // depending on the user_data_type do: diff --git a/rust/pv/tests/add_secret_request.rs b/rust/pv/tests/add_secret_request.rs index 31aa1726..65bdb8c9 100644 --- a/rust/pv/tests/add_secret_request.rs +++ b/rust/pv/tests/add_secret_request.rs @@ -6,13 +6,13 @@ use openssl::ec::{EcGroup, EcKey}; use openssl::nid::Nid; use openssl::pkey::Private; -use s390_pv::request::openssl::pkey::{PKey, Public}; -use s390_pv::request::{BootHdrTags, HostKey, ReqEncrCtx, Request, SymKey}; +use s390_pv::request::openssl::pkey::PKey; +use s390_pv::request::{BootHdrTags, HostKey, HybridPKey, ReqEncrCtx, Request, SymKey}; use s390_pv::secret::{ verify_asrcb_and_get_user_data, AddSecretFlags, AddSecretRequest, AddSecretVersion, ExtSecret, GuestSecret, }; -use s390_pv::test_utils::get_test_keys; +use s390_pv::test_utils::{get_test_keys, get_test_keys_hybrid, DeterministicTestRandGuard}; use s390_pv::uv::ConfigUid; use s390_pv::{get_test_asset, Result}; @@ -29,8 +29,13 @@ fn create_asrcb( hkd: HostKey, ctx: &ReqEncrCtx, ) -> Result> { - let mut asrcb = AddSecretRequest::new(AddSecretVersion::One, guest_secret, TAGS, flags); + let asrcb = match hkd { + HostKey::V1(_) => AddSecretRequest::new(AddSecretVersion::One, guest_secret, TAGS, flags), + HostKey::V2(_) => AddSecretRequest::new(AddSecretVersion::Two, guest_secret, TAGS, flags), + _ => unreachable!("Unknown HostKey version"), + }; + let mut asrcb = asrcb?; if let Some(s) = ext_secret { asrcb.set_ext_secret(s)? }; @@ -42,8 +47,21 @@ fn create_asrcb( asrcb.encrypt(ctx) } -fn get_crypto() -> (PKey, ReqEncrCtx) { +fn get_crypto() -> (HostKey, ReqEncrCtx) { let (cust_key, host_key) = get_test_keys(); + let host_key = HostKey::V1(host_key); + let ctx = ReqEncrCtx::new_aes_256( + Some([0x55; 12]), + Some(cust_key), + Some(SymKey::Aes256([0x17; 32].into())), + ) + .unwrap(); + (host_key, ctx) +} + +fn get_crypto_v2() -> (HostKey, ReqEncrCtx) { + let (cust_key, host_key1, host_key2) = get_test_keys_hybrid(); + let host_key = HostKey::V2(HybridPKey::new(host_key1, host_key2).unwrap()); let ctx = ReqEncrCtx::new_aes_256( Some([0x55; 12]), Some(cust_key), @@ -67,14 +85,25 @@ where true => Some(CUID), false => None, }; - create_asrcb( - guest_secret, - ext_secret.into(), - flags, - cuid, - HostKey::V1(host_key), - &ctx, - ) + create_asrcb(guest_secret, ext_secret.into(), flags, cuid, host_key, &ctx) +} + +fn gen_asrcb_v2( + guest_secret: GuestSecret, + ext_secret: E, + flags: AddSecretFlags, + cuid: bool, +) -> Result> +where + E: Into>, +{ + let _guard = DeterministicTestRandGuard::install(&[0x42; 4096], &[0x11; 32]).unwrap(); + let (host_key, ctx) = get_crypto_v2(); + let cuid = match cuid { + true => Some(CUID), + false => None, + }; + create_asrcb(guest_secret, ext_secret.into(), flags, cuid, host_key, &ctx) } fn association() -> GuestSecret { @@ -96,9 +125,21 @@ fn no_flag() -> AddSecretFlags { fn create_signed_asrcb(skey: PKey, user_data: Vec) -> Vec { let (host_key, ctx) = get_crypto(); let mut asrcb = - AddSecretRequest::new(AddSecretVersion::One, GuestSecret::Null, TAGS, no_flag()); + AddSecretRequest::new(AddSecretVersion::One, GuestSecret::Null, TAGS, no_flag()) + .expect("AddSecretRequest::new failed"); - asrcb.add_hostkey(HostKey::V1(host_key)); + asrcb.add_hostkey(host_key); + asrcb.set_user_data(user_data, Some(skey)).unwrap(); + asrcb.encrypt(&ctx).unwrap() +} + +fn create_signed_asrcb_v2(skey: PKey, user_data: Vec) -> Vec { + let (host_key, ctx) = get_crypto_v2(); + let mut asrcb = + AddSecretRequest::new(AddSecretVersion::Two, GuestSecret::Null, TAGS, no_flag()) + .expect("AddSecretRequest::new failed"); + + asrcb.add_hostkey(host_key); asrcb.set_user_data(user_data, Some(skey)).unwrap(); asrcb.encrypt(&ctx).unwrap() } @@ -108,9 +149,10 @@ fn null_none_default_ncuid_one_user_unsgn() { let user_data_orig = vec![0x56; 0x183]; let (host_key, ctx) = get_crypto(); let mut asrcb = - AddSecretRequest::new(AddSecretVersion::One, GuestSecret::Null, TAGS, no_flag()); + AddSecretRequest::new(AddSecretVersion::One, GuestSecret::Null, TAGS, no_flag()) + .expect("AddSecretRequest::new failed"); - asrcb.add_hostkey(HostKey::V1(host_key)); + asrcb.add_hostkey(host_key); asrcb.set_user_data(user_data_orig.clone(), None).unwrap(); let asrcb = asrcb.encrypt(&ctx).unwrap(); @@ -121,6 +163,7 @@ fn null_none_default_ncuid_one_user_unsgn() { &user_data.as_ref().unwrap()[..user_data_orig.len()] ); } + #[test] fn null_none_default_ncuid_one_user_ec() { let (usr_sgn_key, _) = get_test_keys(); @@ -242,8 +285,9 @@ fn null_none_default_ncuid_one() { fn null_none_default_cuid_seven() { let (hkd, ctx) = get_crypto(); let mut asrcb = - AddSecretRequest::new(AddSecretVersion::One, GuestSecret::Null, TAGS, no_flag()); - (0..7).for_each(|_| asrcb.add_hostkey(HostKey::V1(hkd.clone()))); + AddSecretRequest::new(AddSecretVersion::One, GuestSecret::Null, TAGS, no_flag()) + .expect("AddSecretRequest::new failed"); + (0..7).for_each(|_| asrcb.add_hostkey(hkd.clone())); asrcb.set_cuid(CUID); let asrcb = asrcb.encrypt(&ctx).unwrap(); diff --git a/rust/pvsecret/src/cli.rs b/rust/pvsecret/src/cli.rs index 8b4df131..146d488c 100644 --- a/rust/pvsecret/src/cli.rs +++ b/rust/pvsecret/src/cli.rs @@ -7,7 +7,8 @@ use std::fmt::Display; use clap::error::ErrorKind::ValueValidation; use clap::{ArgGroup, Args, CommandFactory, Parser, Subcommand, ValueEnum, ValueHint}; use utils::{ - combined_path_opt, combined_path_req, CertificateOptions, DeprecatedVerbosityOptions, STDOUT, + combined_path_opt, combined_path_req, AutoOrExplicit, CertificateOptions, + DeprecatedVerbosityOptions, HkdVersion, ValueEnumDisplay, ValueEnumFromStr, STDOUT, }; /// Manage secrets for IBM Secure Execution guests. @@ -34,6 +35,28 @@ pub enum CreateSecretFlags { DisableDump, } +/// Secure Execution add secret version for CLI +#[derive(Debug, Clone, Copy, PartialEq, Eq, ValueEnum, ValueEnumDisplay, ValueEnumFromStr)] +pub enum SecretVersion { + #[value(name = "1")] + /// Version 1 - uses traditional cryptographic keys + V1, + #[value(name = "2")] + /// Version 2 - uses hybrid (post-quantum) cryptographic keys + V2, +} + +pub type SecretVersionSelection = AutoOrExplicit; + +impl From for HkdVersion { + fn from(val: SecretVersion) -> Self { + match val { + SecretVersion::V1 => Self::Classical, + SecretVersion::V2 => Self::Hybrid, + } + } +} + #[derive(Args, Debug)] #[command(group(ArgGroup::new("as-ext").args(["cck", "extension_secret"])),)] pub struct CreateSecretOpt { @@ -173,6 +196,10 @@ pub struct CreateSecretOpt { /// Ignored for meta-secrets. #[arg(long)] pub use_name: bool, + + /// Specify the Add-secret version to use. + #[arg(long = "secret-version", value_name = "VERSION", default_value_t = SecretVersion::V1)] + pub secret_version: SecretVersion, } #[derive(Subcommand, Debug)] diff --git a/rust/pvsecret/src/cmd.rs b/rust/pvsecret/src/cmd.rs index dd306ef0..a1d749f0 100644 --- a/rust/pvsecret/src/cmd.rs +++ b/rust/pvsecret/src/cmd.rs @@ -8,7 +8,7 @@ pub use create::create; mod verify; pub use verify::verify; -pub const CMD_FN: &[&str] = &["+create", "+verify"]; +pub const CMD_FN: &[&str] = &["+create", "+verify", "+quantumsafe"]; #[cfg(target_arch = "s390x")] mod add; diff --git a/rust/pvsecret/src/cmd/create.rs b/rust/pvsecret/src/cmd/create.rs index 8cc890f4..ade09789 100644 --- a/rust/pvsecret/src/cmd/create.rs +++ b/rust/pvsecret/src/cmd/create.rs @@ -22,7 +22,10 @@ use serde_yaml::Value; use utils::get_writer_from_cli_file_arg; use zerocopy::IntoBytes; -use crate::cli::{AddSecretType, CreateSecretFlags, CreateSecretOpt, RetrieveableSecretInpKind}; +use crate::cli::{ + AddSecretType, CreateSecretFlags, CreateSecretOpt, RetrieveableSecretInpKind, SecretVersion, + SecretVersionSelection, +}; fn write_out(path: &P, data: D, ctx: &str) -> pv::Result<()> where @@ -83,6 +86,41 @@ fn retrievable(name: &str, secret: &str, kind: &RetrieveableSecretInpKind) -> Re .map_err(Error::from) } +/// Auto-detect the Add-secret version based on the host keys. +/// +/// Returns Two if any host key is a hybrid key, otherwise returns V1. +fn auto_detect_version(host_keys: &[HostKey]) -> AddSecretVersion { + let use_hybrid_keys = host_keys.iter().any(|k: &HostKey| k.is_hybrid()); + if use_hybrid_keys { + AddSecretVersion::Two + } else { + AddSecretVersion::One + } +} + +impl From for AddSecretVersion { + fn from(value: SecretVersion) -> Self { + match value { + SecretVersion::V1 => Self::One, + SecretVersion::V2 => Self::Two, + } + } +} + +/// Determine the attestation version to use. +/// +/// If an explicit version is provided via CLI, use that. +/// Otherwise, auto-detect based on the host key types. +fn determine_version( + cli_version: SecretVersionSelection, + host_keys: &[HostKey], +) -> AddSecretVersion { + match cli_version { + SecretVersionSelection::Auto => auto_detect_version(host_keys), + SecretVersionSelection::Explicit(att_version) => att_version.into(), + } +} + /// Prepare an add-secret request pub fn create(opt: &CreateSecretOpt) -> Result<()> { if pv_guest_bit_set() { @@ -94,17 +132,9 @@ pub fn create(opt: &CreateSecretOpt) -> Result<()> { } } - let mut asrcb = build_asrcb(opt)?; + let asrcb = build_asrcb(opt)?; debug!("Generated Add-secret request"); - // Add host-key documents - opt.certificate_args - .get_verified_hkds("secret")? - .into_iter() - .for_each(|k| asrcb.add_hostkey(HostKey::V1(k))); - - debug!("Added all host-keys"); - // build + encrypt the request let rq = ReqEncrCtx::random(SymKeyType::Aes256Gcm).context("Failed to generate random input")?; @@ -168,9 +198,21 @@ fn build_asrcb(opt: &CreateSecretOpt) -> Result { debug!("FLAGS: {flags:x?}"); let mut se_hdr = open_file(&opt.hdr)?; - let (tags, _) = BootHdrTags::from_se_image(&mut se_hdr) + let (boot_tags, _) = BootHdrTags::from_se_image(&mut se_hdr) .with_context(|| format!("Provided SE-header in '{}' is malformed", &opt.hdr))?; - let mut asrcb = AddSecretRequest::new(AddSecretVersion::One, secret, tags, flags); + + let hkds = opt.certificate_args.get_verified_hkds_new( + "secret", + SecretVersionSelection::Explicit(opt.secret_version).map(|v| v.into()), + )?; + let secret_version = + determine_version(SecretVersionSelection::Explicit(opt.secret_version), &hkds); + + let mut asrcb = AddSecretRequest::new(secret_version, secret, boot_tags, flags)?; + + hkds.into_iter().for_each(|k| asrcb.add_hostkey(k)); + + debug!("Added all host-keys"); // Set CUID read_cuid(&mut asrcb, opt)?;