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());