From 4b274d7f8cb2d1be4d99ddf22ac00eab357d03cf Mon Sep 17 00:00:00 2001 From: Anderson Toshiyuki Sasaki Date: Wed, 27 May 2026 22:20:46 +0200 Subject: [PATCH] Hash agent ID before TPM2_Certify qualifying data This allows using long agent UUID on systems with SHA-256-only TPM. Resolves: RHEL-169745 Signed-off-by: Anderson Toshiyuki Sasaki --- 0004-rust-keylime-hash-before-certify.patch | 340 ++++++++++++++++++++ keylime-agent-rust.spec | 3 + 2 files changed, 343 insertions(+) create mode 100644 0004-rust-keylime-hash-before-certify.patch diff --git a/0004-rust-keylime-hash-before-certify.patch b/0004-rust-keylime-hash-before-certify.patch new file mode 100644 index 0000000..f6abdf5 --- /dev/null +++ b/0004-rust-keylime-hash-before-certify.patch @@ -0,0 +1,340 @@ +From 6a6b1046a2fb5bd0a84982c7777268f4f9c14f25 Mon Sep 17 00:00:00 2001 +From: Anderson Toshiyuki Sasaki +Date: Mon, 11 May 2026 19:01:25 +0200 +Subject: [PATCH] agent: Hash agent ID before TPM2_Certify qualifying data + +SHA-256 hash the agent ID before passing it as qualifyingData to +TPM2_Certify when certifying the AK with the IAK. This produces a fixed +32-byte value that fits within TPM2B_DATA on all TPMs, including those +that only support SHA-256 (where the limit is 34 bytes). + +Without this fix, UUID strings (36 bytes) and other agent IDs exceed the +TPM2B_DATA capacity on SHA-256-only TPMs, causing TPM_RC_SIZE errors +during registration. + +Bumps the pull-mode API version to 2.6. + +Fixes: https://github.com/keylime/rust-keylime/issues/1226 + +Co-Authored-By: Claude Opus 4.6 +Signed-off-by: Anderson Toshiyuki Sasaki +--- + keylime-agent/src/api.rs | 21 +++++ + keylime-agent/src/main.rs | 22 +---- + keylime-push-model-agent/src/registration.rs | 2 - + keylime/src/agent_registration.rs | 88 +++++++++++++------- + keylime/src/config/base.rs | 2 +- + keylime/src/registrar_client.rs | 10 +++ + keylime/src/tpm.rs | 23 +++-- + 7 files changed, 108 insertions(+), 60 deletions(-) + +diff --git a/keylime-agent/src/api.rs b/keylime-agent/src/api.rs +index e9f4562dd..4d135a9b3 100644 +--- a/keylime-agent/src/api.rs ++++ b/keylime-agent/src/api.rs +@@ -188,6 +188,25 @@ fn configure_api_v2_5(cfg: &mut web::ServiceConfig) { + ) + } + ++/// Configure the endpoints supported by API version 2.6 ++/// ++/// Version 2.6 has the same agent-side endpoints as 2.5. The version bump ++/// reflects a protocol change: the agent now SHA-256 hashes the agent ID ++/// before using it as qualifying data in TPM2_Certify (IAK-based AK ++/// certification). This ensures the qualifying data fits within the ++/// TPM2B_DATA size limit on SHA-256-only TPMs (34 bytes). The certify ++/// operation is deferred until after API version negotiation so the agent ++/// can fall back to raw UUID bytes when the registrar only supports 2.5. ++fn configure_api_v2_6(cfg: &mut web::ServiceConfig) { ++ let version = Version::from_str("2.6").expect("Invalid API version"); ++ _ = cfg.app_data(web::Data::new(version)); ++ configure_base_endpoints(cfg); ++ _ = cfg.service( ++ web::scope("/agent") ++ .configure(agent_handler::configure_agent_endpoints), ++ ) ++} ++ + /// Get a scope configured for the given API version + pub(crate) fn get_api_scope(version: &str) -> Result { + match version { +@@ -201,6 +220,8 @@ pub(crate) fn get_api_scope(version: &str) -> Result { + .configure(configure_api_v2_4)), + "2.5" => Ok(web::scope(format!("v{version}").as_ref()) + .configure(configure_api_v2_5)), ++ "2.6" => Ok(web::scope(format!("v{version}").as_ref()) ++ .configure(configure_api_v2_6)), + _ => Err(APIError::UnsupportedVersion(version.into())), + } + } +diff --git a/keylime-agent/src/main.rs b/keylime-agent/src/main.rs +index b05eed214..280a1ac88 100644 +--- a/keylime-agent/src/main.rs ++++ b/keylime-agent/src/main.rs +@@ -87,7 +87,7 @@ use tss_esapi::{ + handles::KeyHandle, + interface_types::algorithm::{AsymmetricAlgorithm, HashingAlgorithm}, + interface_types::resource_handles::Hierarchy, +- structures::{Auth, Data, Digest, MaxBuffer, PublicBuffer}, ++ structures::{Auth, Digest, MaxBuffer, PublicBuffer}, + traits::Marshall, + Context, + }; +@@ -494,24 +494,6 @@ async fn main() -> Result<()> { + None + }; + +- let (attest, signature) = if let Some(dev_id) = &mut device_id { +- let qualifying_data = Data::try_from(agent_uuid.as_bytes())?; +- let (attest, signature) = +- dev_id.certify(qualifying_data, ak_handle, &mut ctx)?; +- +- info!("AK certified with IAK."); +- +- // // For debugging certify(), the following checks the generated signature +- // let max_b = MaxBuffer::try_from(attest.clone().marshall()?)?; +- // let (hashed_attest, _) = ctx.inner.hash(max_b, HashingAlgorithm::Sha256, Hierarchy::Endorsement,)?; +- // println!("{:?}", hashed_attest); +- // println!("{:?}", signature); +- // println!("{:?}", ctx.inner.verify_signature(iak.as_ref().unwrap().handle, hashed_attest, signature.clone())?); //#[allow_ci] +- (Some(attest), Some(signature)) +- } else { +- (None, None) +- }; +- + // Load or generate RSA key pair for secure transmission of u, v keys. + // The u, v keys are two halves of the key used to decrypt the workload after + // the Identity and Integrity Quotes sent by the agent are validated +@@ -645,8 +627,6 @@ async fn main() -> Result<()> { + agent_uuid: agent_uuid.clone(), + mtls_cert, + device_id, +- attest, +- signature, + ak_handle, + retry_config: Some(get_retry_config(&config)), + }; +diff --git a/keylime-push-model-agent/src/registration.rs b/keylime-push-model-agent/src/registration.rs +index 0189cb15b..0cca01c17 100644 +--- a/keylime-push-model-agent/src/registration.rs ++++ b/keylime-push-model-agent/src/registration.rs +@@ -109,8 +109,6 @@ pub async fn register_agent( + agent_uuid, + mtls_cert: None, + device_id: None, // TODO: Check how to proceed with device ID +- attest: None, // TODO: Check how to proceed with attestation, normally, no device ID means no attest +- signature: None, // TODO: Normally, no device ID means no signature + ak_handle: context_info.ak_handle, + retry_config, + }; +diff --git a/keylime/src/agent_registration.rs b/keylime/src/agent_registration.rs +index 8a4fe0c3a..9e60f8cf2 100644 +--- a/keylime/src/agent_registration.rs ++++ b/keylime/src/agent_registration.rs +@@ -9,10 +9,12 @@ use crate::{ + tpm::{self}, + }; + use base64::{engine::general_purpose, Engine as _}; +-use log::{error, info}; ++use log::{error, info, warn}; + use openssl::x509::X509; + use tss_esapi::{ +- handles::KeyHandle, structures::PublicBuffer, traits::Marshall, ++ handles::KeyHandle, ++ structures::{Data, PublicBuffer}, ++ traits::Marshall, + }; + + #[derive(Debug)] +@@ -44,14 +46,12 @@ pub struct AgentRegistration { + pub agent_uuid: String, + pub mtls_cert: Option, + pub device_id: Option, +- pub attest: Option, +- pub signature: Option, + pub ak_handle: KeyHandle, + pub retry_config: Option, + } + + pub async fn register_agent( +- aa: AgentRegistration, ++ mut aa: AgentRegistration, + ctx: &mut tpm::Context<'_>, + ) -> Result<()> { + let iak_pub; +@@ -60,6 +60,28 @@ pub async fn register_agent( + let ek_pub = + &PublicBuffer::try_from(aa.ek_result.public.clone())?.marshall()?; + ++ let ac = &aa.agent_registration_config; ++ ++ // Build the registrar client first so we can query its supported API ++ // versions before performing TPM operations that depend on that version. ++ // Uses HTTPS if CA certificate is provided, otherwise plain HTTP. ++ let mut builder = RegistrarClientBuilder::new() ++ .registrar_address(ac.registrar_ip.clone()) ++ .registrar_port(ac.registrar_port) ++ .retry_config(aa.retry_config.clone()); ++ ++ if let Some(ca_cert) = &ac.registrar_ca_cert { ++ builder = builder.ca_certificate(ca_cert.clone()); ++ } ++ if let Some(disable_tls) = ac.registrar_disable_tls { ++ builder = builder.disable_tls(disable_tls); ++ } ++ if let Some(timeout) = ac.registrar_timeout { ++ builder = builder.timeout(timeout); ++ } ++ ++ let mut registrar_client = builder.build().await?; ++ + let mut ai_builder = AgentIdentityBuilder::new() + .ak_pub(ak_pub) + .ek_pub(ek_pub) +@@ -81,9 +103,7 @@ pub async fn register_agent( + + // Set the IAK/IDevID related fields, if enabled + if aa.agent_registration_config.enable_iak_idevid { +- let (Some(dev_id), Some(attest), Some(signature)) = +- (&aa.device_id, aa.attest, aa.signature) +- else { ++ let Some(dev_id) = aa.device_id.as_mut() else { + error!("IDevID and IAK are enabled but could not be generated"); + return Err(Error::ConfigurationGenericError( + "IDevID and IAK are enabled but could not be generated" +@@ -91,6 +111,36 @@ pub async fn register_agent( + )); + }; + ++ // Hash the agent UUID (SHA-256) when the registrar supports API 2.6+. ++ // This keeps qualifying data within the TPM2B_DATA size limit on ++ // SHA-256-only TPMs (34 bytes). For registrars that only support 2.5 ++ // or earlier, fall back to the raw UUID bytes for backward compat. ++ let qualifying_data = if registrar_client.supports_api_version("2.6") ++ { ++ let hash = crypto::hash( ++ aa.agent_uuid.as_bytes(), ++ openssl::hash::MessageDigest::sha256(), ++ )?; ++ Data::try_from(hash.as_slice())? ++ } else { ++ let uuid_bytes = aa.agent_uuid.as_bytes(); ++ if uuid_bytes.len() > 34 { ++ // TPM2B_DATA limit on SHA-256-only TPMs is 34 bytes. ++ // Raw UUID won't fit; the registrar must support 2.6+. ++ warn!( ++ "Agent UUID is {} bytes (max 34 for pre-2.6 registrar); \ ++ registration will likely fail. Update the registrar to 2.6+.", ++ uuid_bytes.len() ++ ); ++ } ++ Data::try_from(uuid_bytes)? ++ }; ++ ++ let (attest, signature) = ++ dev_id.certify(qualifying_data, aa.ak_handle, ctx)?; ++ ++ info!("AK certified with IAK."); ++ + iak_pub = + PublicBuffer::try_from(dev_id.iak_pubkey.clone())?.marshall()?; + idevid_pub = PublicBuffer::try_from(dev_id.idevid_pubkey.clone())? +@@ -115,28 +165,6 @@ pub async fn register_agent( + // Build the Agent Identity + let ai = ai_builder.build().await?; + +- let ac = &aa.agent_registration_config; +- +- // Build the registrar client +- // Uses HTTPS if CA certificate is provided, otherwise plain HTTP +- let mut builder = RegistrarClientBuilder::new() +- .registrar_address(ac.registrar_ip.clone()) +- .registrar_port(ac.registrar_port) +- .retry_config(aa.retry_config.clone()); +- +- // Add TLS configuration if CA certificate is provided +- if let Some(ca_cert) = &ac.registrar_ca_cert { +- builder = builder.ca_certificate(ca_cert.clone()); +- } +- if let Some(disable_tls) = ac.registrar_disable_tls { +- builder = builder.disable_tls(disable_tls); +- } +- if let Some(timeout) = ac.registrar_timeout { +- builder = builder.timeout(timeout); +- } +- +- let mut registrar_client = builder.build().await?; +- + // Request keyblob material + let keyblob = registrar_client.register_agent(&ai).await?; + +diff --git a/keylime/src/config/base.rs b/keylime/src/config/base.rs +index 31fdc44ef..eb6cee993 100644 +--- a/keylime/src/config/base.rs ++++ b/keylime/src/config/base.rs +@@ -20,7 +20,7 @@ use uuid::Uuid; + + pub static CONFIG_VERSION: &str = "2.0"; + pub static SUPPORTED_API_VERSIONS: &[&str] = +- &["2.1", "2.2", "2.3", "2.4", "2.5"]; ++ &["2.1", "2.2", "2.3", "2.4", "2.5", "2.6"]; + pub static DEFAULT_REGISTRAR_API_VERSIONS: &str = "default"; + + pub static DEFAULT_CONFIG: &str = "/etc/keylime/agent.conf"; +diff --git a/keylime/src/registrar_client.rs b/keylime/src/registrar_client.rs +index 7750a381e..04523d1a0 100644 +--- a/keylime/src/registrar_client.rs ++++ b/keylime/src/registrar_client.rs +@@ -400,6 +400,16 @@ struct Register<'a> { + } + + impl RegistrarClient { ++ /// Check if the registrar supports the given API version. ++ /// ++ /// Returns `false` if the registrar did not advertise supported versions ++ /// (e.g., it does not expose a `/version` endpoint). ++ pub fn supports_api_version(&self, version: &str) -> bool { ++ self.supported_api_versions ++ .as_ref() ++ .is_some_and(|versions| versions.iter().any(|v| v == version)) ++ } ++ + async fn try_register_agent( + &self, + ai: &AgentIdentity<'_>, +diff --git a/keylime/src/tpm.rs b/keylime/src/tpm.rs +index fb5d843ec..50c94cf5f 100644 +--- a/keylime/src/tpm.rs ++++ b/keylime/src/tpm.rs +@@ -3291,14 +3291,25 @@ pub mod tests { + .expect("failed to create IAK") + .handle; + +- let qualifying_data = "some_uuid".as_bytes(); ++ // Use a realistic UUID string (36 bytes) — the exact input size that ++ // triggers TPM_RC_SIZE on SHA-256-only TPMs without the hash fix. ++ let agent_uuid = "d432fbb3-d2f1-4a97-9ef7-75bd81c00000"; ++ let qualifying_data = crate::crypto::hash( ++ agent_uuid.as_bytes(), ++ openssl::hash::MessageDigest::sha256(), ++ ) ++ .unwrap(); //#[allow_ci] ++ ++ let data = Data::try_from(qualifying_data.as_slice()).unwrap(); //#[allow_ci] ++ let (attest, _signature) = ctx ++ .certify_credential_with_iak(data, ak_handle, iak_handle) ++ .expect("certify_credential_with_iak failed"); + +- let r = ctx.certify_credential_with_iak( +- Data::try_from(qualifying_data).unwrap(), //#[allow_ci] +- ak_handle, +- iak_handle, ++ assert_eq!( ++ attest.extra_data().value(), ++ qualifying_data.as_slice(), ++ "extra_data in Attest must equal the SHA-256 hash of the agent UUID" + ); +- assert!(r.is_ok(), "Result: {r:?}"); + + // Flush context to free TPM memory + let r = ctx.flush_context(ek_handle.into()); diff --git a/keylime-agent-rust.spec b/keylime-agent-rust.spec index 2e59048..941dae1 100644 --- a/keylime-agent-rust.spec +++ b/keylime-agent-rust.spec @@ -62,6 +62,9 @@ Patch2: 0002-rust-keylime-do-not-require-usr-libexec.patch # Use the correct registrar port when TLS is enabled # Backported from https://github.com/keylime/rust-keylime/pull/1204 Patch3: 0003-rust-keylime-registrar-tls-port.patch +# Hash UUID before using as qualifying data for IAK-based AK certification +# Backported from https://github.com/keylime/rust-keylime/pull/1239 +Patch4: 0004-rust-keylime-hash-before-certify.patch ## (100-199) Patches for building from system Rust libraries (Fedora) ## (200+) Patches for building from vendored Rust libraries (RHEL)