diff --git a/rust/pv/src/uvsecret/guest_secret.rs b/rust/pv/src/uvsecret/guest_secret.rs index 8cf78483..52244d6a 100644 --- a/rust/pv/src/uvsecret/guest_secret.rs +++ b/rust/pv/src/uvsecret/guest_secret.rs @@ -29,6 +29,7 @@ use zerocopy::{BigEndian, KnownLayout}; use zerocopy::{FromBytes, Immutable, IntoBytes, U16, U32}; const ASSOC_SECRET_SIZE: usize = 32; +const CCK_SIZE: usize = 32; /// Maximum size of a plain-text secret payload (8190) pub(crate) const MAX_SIZE_PLAIN_PAYLOAD: usize = RetrieveCmd::MAX_SIZE - 2; static_assert!(MAX_SIZE_PLAIN_PAYLOAD == 8190); @@ -65,6 +66,14 @@ pub enum GuestSecret { #[serde(skip)] secret: Confidential>, }, + /// CCK update + /// + /// Create CCK updates using [`GuestSecret::update_cck`] + UpdateCck { + /// Confidential actual CCK (32 bytes) + #[serde(skip)] + secret: Confidential<[u8; CCK_SIZE]>, + }, } macro_rules! retr_constructor { @@ -136,10 +145,19 @@ impl GuestSecret { retr_constructor!(#[doc = r"This function will return an error if OpenSSL cannot create a hash or the curve is invalid"] | #[doc = r"EC PRIVATE Key"] => PKey, ec); + /// Create a new [`GuestSecret::UpdateCck`]. + /// + /// * `secret` - New CCK. + pub fn update_cck(secret: [u8; CCK_SIZE]) -> Self { + Self::UpdateCck { + secret: secret.into(), + } + } + /// Use the name as ID, do not hash it pub fn no_hash_name(&mut self) { match self { - Self::Null => (), + Self::Null | Self::UpdateCck { .. } => (), Self::Association { name, ref mut id, .. } @@ -155,6 +173,7 @@ impl GuestSecret { Self::Null => &[], Self::Association { secret, .. } => secret.value().as_slice(), Self::Retrievable { secret, .. } => secret.value(), + Self::UpdateCck { secret, .. } => secret.value(), } } @@ -162,7 +181,8 @@ impl GuestSecret { pub(crate) fn auth(&self) -> SecretAuth { match &self { Self::Null => SecretAuth::Null, - // Panic: every non null secret type is list-able -> no panic + Self::UpdateCck { .. } => SecretAuth::UpdateCck, + // Panic: other secret types are list-able -> no panic listable => { SecretAuth::Listable(ListableSecretHdr::from_guest_secret(listable).unwrap()) } @@ -176,6 +196,7 @@ impl GuestSecret { Self::Null => ListableSecretType::NULL, Self::Association { .. } => ListableSecretType::ASSOCIATION, Self::Retrievable { kind, .. } => kind.into(), + Self::UpdateCck { .. } => ListableSecretType::UPDATE_CCK, } } @@ -185,13 +206,14 @@ impl GuestSecret { Self::Null => 0, Self::Association { secret, .. } => secret.value().len() as u32, Self::Retrievable { secret, .. } => secret.value().len() as u32, + Self::UpdateCck { secret } => secret.value().len() as u32, } } /// Returns the ID of the secret type (if any) fn id(&self) -> Option { match self { - Self::Null => None, + Self::Null | Self::UpdateCck { .. } => None, Self::Association { id, .. } | Self::Retrievable { id, .. } => Some(id.to_owned()), } } @@ -368,15 +390,18 @@ impl Display for GuestSecret { pub(crate) enum SecretAuth { Null, Listable(ListableSecretHdr), + UpdateCck, } impl SecretAuth { const NULL_HDR: NullSecretHdr = NullSecretHdr::new(); + const UPDATE_CCK_HDR: UpdateCckHdr = UpdateCckHdr::new(); pub fn get(&self) -> &[u8] { match self { Self::Null => Self::NULL_HDR.as_bytes(), Self::Listable(h) => h.as_bytes(), + Self::UpdateCck => Self::UPDATE_CCK_HDR.as_bytes(), } } } @@ -425,6 +450,29 @@ impl ListableSecretHdr { } } +#[repr(C)] +#[derive(Debug, IntoBytes, Default, Immutable)] +struct UpdateCckHdr { + res0: u16, + kind: U16, + secret_len: U32, + res8: u64, + res10: [u8; 0x20], +} +assert_size!(UpdateCckHdr, 0x30); + +impl UpdateCckHdr { + const fn new() -> Self { + Self { + res0: 0, + kind: U16::new(ListableSecretType::UPDATE_CCK), + secret_len: U32::new(CCK_SIZE as u32), + res8: 0, + res10: [0; 0x20], + } + } +} + #[cfg(test)] mod test { @@ -484,6 +532,16 @@ mod test { retr_test!(retr_aes_hmac_256, hmac_sha, 64, HmacSha(HmacSizes::Sha256)); retr_test!(retr_aes_hmac_512, hmac_sha, 128, HmacSha(HmacSizes::Sha512)); + #[test] + fn update_cck() { + let new_cck = [11; 32]; + let req = GuestSecret::update_cck(new_cck); + let exp = GuestSecret::UpdateCck { + secret: new_cck.into(), + }; + assert_eq!(req, exp); + } + #[test] fn plaintext_no_pad() { let key = vec![0, 14, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7]; @@ -625,6 +683,24 @@ mod test { ); } + #[test] + fn update_cck_parse() { + let cck = GuestSecret::UpdateCck { + secret: [0; 32].into(), + }; + assert_tokens( + &cck, + &[ + Token::StructVariant { + name: "GuestSecret", + variant: "UpdateCck", + len: 0, + }, + Token::StructVariantEnd, + ], + ) + } + #[test] fn guest_secret_bin_null() { let gs = GuestSecret::Null; @@ -666,4 +742,17 @@ mod test { assert_eq!(exp, gs_bytes_auth); assert_eq!(&[2; 32], gs.confidential()); } + + #[test] + fn guest_secret_bin_cck() { + let gs = GuestSecret::UpdateCck { + secret: [2; 32].into(), + }; + let gs_bytes_auth = gs.auth(); + 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!(&[2; 32], gs.confidential()); + } } diff --git a/rust/pv_core/src/uvdevice/secret_list.rs b/rust/pv_core/src/uvdevice/secret_list.rs index 9fb3d585..708016e6 100644 --- a/rust/pv_core/src/uvdevice/secret_list.rs +++ b/rust/pv_core/src/uvdevice/secret_list.rs @@ -398,6 +398,7 @@ pub enum ListableSecretType { /// /// 0 is reserved /// 1 is Null secret, with no id and not list-able + /// 21 is Update CCK secret, with no id and not list-able Invalid(u16), /// Unknown secret type Unknown(u16), @@ -436,6 +437,8 @@ impl ListableSecretType { pub const ECDSA_ED25519_KEY: u16 = 0x0014; /// UV secret-type id for an ed448-private-key secret pub const ECDSA_ED448_KEY: u16 = 0x0015; + /// UV secret-type id for a new customer communication key + pub const UPDATE_CCK: u16 = 0x0016; } impl Display for ListableSecretType { @@ -482,6 +485,7 @@ impl From for ListableSecretType { Self::ECDSA_P521_KEY => Self::Retrievable(RetrievableSecret::Ec(EcCurves::Secp521R1)), Self::ECDSA_ED25519_KEY => Self::Retrievable(RetrievableSecret::Ec(EcCurves::Ed25519)), Self::ECDSA_ED448_KEY => Self::Retrievable(RetrievableSecret::Ec(EcCurves::Ed448)), + Self::UPDATE_CCK => Self::Invalid(Self::UPDATE_CCK), n => Self::Unknown(n), } } diff --git a/rust/pvsecret/man/pvsecret-create-update-cck.1 b/rust/pvsecret/man/pvsecret-create-update-cck.1 new file mode 100644 index 00000000..90d9f4d6 --- /dev/null +++ b/rust/pvsecret/man/pvsecret-create-update-cck.1 @@ -0,0 +1,35 @@ +.\" Copyright 2025 IBM Corp. +.\" s390-tools is free software; you can redistribute it and/or modify +.\" it under the terms of the MIT license. See LICENSE for details. +.\" + +.TH "PVSECRET-CREATE-UPDATE-CCK" "1" "2025-02-19" "s390-tools" "UV-Secret Manual" +.nh +.ad l +.SH NAME +pvsecret-create-update-cck \- Update customer communication key. +.SH SYNOPSIS +.nf +.fam C +pvsecret create update-cck [OPTIONS] \-\-secret +.fam C +.fi +.SH DESCRIPTION +Insert a customer communication key into a guest. +.SH OPTIONS +.PP +\-\-secret +.RS 4 +Use CCK\-FILE as new CCK +.RE +.RE +.PP +\-h, \-\-help +.RS 4 +Print help (see a summary with \fB\-h\fR). +.RE +.RE + +.SH "SEE ALSO" +.sp +\fBpvsecret\fR(1) \fBpvsecret-create\fR(1) diff --git a/rust/pvsecret/man/pvsecret-create.1 b/rust/pvsecret/man/pvsecret-create.1 index 87c8d8bd..4b296905 100644 --- a/rust/pvsecret/man/pvsecret-create.1 +++ b/rust/pvsecret/man/pvsecret-create.1 @@ -3,7 +3,7 @@ .\" it under the terms of the MIT license. See LICENSE for details. .\" -.TH "PVSECRET-CREATE" "1" "2024-12-19" "s390-tools" "UV-Secret Manual" +.TH "PVSECRET-CREATE" "1" "2025-04-25" "s390-tools" "UV-Secret Manual" .nh .ad l .SH NAME @@ -47,6 +47,11 @@ Create an association secret Create a retrievable secret .RE +\fBpvsecret create-update-cck(1)\fR +.RS 4 +Update customer communication key +.RE + .SH OPTIONS .PP \-k, \-\-host\-key\-document diff --git a/rust/pvsecret/src/cli.rs b/rust/pvsecret/src/cli.rs index a0910201..bceb6f72 100644 --- a/rust/pvsecret/src/cli.rs +++ b/rust/pvsecret/src/cli.rs @@ -217,6 +217,16 @@ pub enum AddSecretType { #[arg(long = "type", value_name = "TYPE")] kind: RetrieveableSecretInpKind, }, + + /// Update customer communication key. + /// + /// Insert a customer communication key into a guest. + #[command(visible_alias = "cck")] + UpdateCck { + /// Use CCK-FILE as new CCK. + #[arg(long, value_name = "CCK-FILE", value_hint = ValueHint::FilePath)] + secret: String, + }, } #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum, Debug)] @@ -474,6 +484,7 @@ mod test { vec!["pvsecret", "add"], vec!["pvsecret", "create", "-k", "abc", "--hdr", "abc", "-o", "abc", "--no-verify", "meta"], vec!["pvsecret", "create", "-k", "abc", "--hdr", "abc", "-o", "abc", "--no-verify", "association", "name" ], + vec!["pvsecret", "create", "-k", "abc", "--hdr", "abc", "-o", "abc", "--no-verify", "update-cck", "--secret", "abc"], // verify that arguments stay backwards compatible vec!["pvsecret", "create", "-k", "abc,cdef", "--hdr", "abc", "-o", "abc", "-C", "uuu,ggg", "--crl", "yyy,hhh", "--root-ca", "tttt", "--extension-secret", "fff", "--cuid", "cuid", "--flags", "disable-dump", "meta"], @@ -501,6 +512,7 @@ mod test { vec!["pvsecret", "create", "-k", "abc", "--hdr", "abc", "-o", "abc", "--no-verify", "--cuid", "abc", "--cuid_hex", "9", "null"], vec!["pvsecret", "create", "-k", "abc", "--hdr", "abc", "-o", "abc", "--no-verify", "association"], vec!["pvsecret", "create", "-k", "abc", "--hdr", "abc", "-o", "abc", "--no-verify", "association", "name", "--output-secret", "secret", "--input-secret", "secret"], + vec!["pvsecret", "create", "-k", "abc", "--hdr", "abc", "-o", "abc", "--no-verify", "update-cck"], ]; for arg in valid_args { let res = CliOptions::try_parse_from(&arg); diff --git a/rust/pvsecret/src/cmd/create.rs b/rust/pvsecret/src/cmd/create.rs index fab37e67..a6f76213 100644 --- a/rust/pvsecret/src/cmd/create.rs +++ b/rust/pvsecret/src/cmd/create.rs @@ -109,6 +109,9 @@ fn build_asrcb(opt: &CreateSecretOpt) -> Result { AddSecretType::Retrievable { name, secret, kind, .. } => retrievable(name, secret, kind)?, + AddSecretType::UpdateCck { secret } => { + GuestSecret::update_cck(read_exact_file(secret, "CCK file")?) + } }; trace!("AddSecret: {secret:x?}");