pvsecret: Use hybrid keys

Allow the creation of Add-secret requests using hybrid (=quantum safe)
keys. This results in using the headers in version 2 (0x200).

Co-developed-by: Marc Hartmayer <marc@linux.ibm.com>
Signed-off-by: Marc Hartmayer <marc@linux.ibm.com>
Signed-off-by: Timo Keller <tkeller@linux.ibm.com>
Reviewed-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
This commit is contained in:
Timo Keller
2026-07-21 18:08:38 +02:00
committed by Steffen Eiden
parent 9dca2d3181
commit 50808edb7c
9 changed files with 420 additions and 73 deletions

View File

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

View File

@@ -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<F: Into<UvFlags>>(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<F: Into<UvFlags>>(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<AddSecretFlags> 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<u32> for AddSecretVersion {
type Error = Error;
fn try_from(value: u32) -> Result<Self> {
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<AddSecretVersion> for RequestVersion {
fn from(val: AddSecretVersion) -> Self {
val as Self
}
}
impl From<crate::request::SeHdrVersion> 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<Self> {
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<AddSecretVersion> for RequestVersion {
#[derive(Debug)]
pub struct AddSecretRequest {
version: AddSecretVersion,
aad: ReqAuthData,
aad: ReqAuthDataVersion,
keyslots: Vec<Keyslot>,
conf: ReqConfData,
user_data: UserData,
}
static_assert!(AddSecretRequest::USER_DATA_OFFS == 0x218);
static_assert!(
AddSecretRequest::USER_DATA_OFFS == size_of::<RequestHdr>() + size_of::<ReqAuthDataV2>()
);
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::<RequestHdr>() + size_of::<ReqAuthDataV1>();
/// 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<Self> {
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<Aad> = 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

View File

@@ -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<BigEndian>,
secret_len: U32<BigEndian>,
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<BigEndian>,
secret_len: U32<BigEndian>,
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::<u8>::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());
}
}

View File

@@ -133,6 +133,7 @@ impl RetrievedSecret {
RetrievedSecret::ProtectedKey(p) => p.into_bytes(),
}
}
/// Get the data in PEM format.
///
/// # Errors

View File

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

View File

@@ -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<Vec<u8>> {
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<Public>, 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<E>(
guest_secret: GuestSecret,
ext_secret: E,
flags: AddSecretFlags,
cuid: bool,
) -> Result<Vec<u8>>
where
E: Into<Option<ExtSecret>>,
{
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<Private>, user_data: Vec<u8>) -> Vec<u8> {
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<Private>, user_data: Vec<u8>) -> Vec<u8> {
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();

View File

@@ -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<SecretVersion>;
impl From<SecretVersion> 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)]

View File

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

View File

@@ -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<P, D>(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<SecretVersion> 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<AddSecretRequest> {
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)?;