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 <ansasaki@redhat.com>
This commit is contained in:
parent
2d2f9fb027
commit
4b274d7f8c
340
0004-rust-keylime-hash-before-certify.patch
Normal file
340
0004-rust-keylime-hash-before-certify.patch
Normal file
@ -0,0 +1,340 @@
|
||||
From 6a6b1046a2fb5bd0a84982c7777268f4f9c14f25 Mon Sep 17 00:00:00 2001
|
||||
From: Anderson Toshiyuki Sasaki <ansasaki@redhat.com>
|
||||
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 <noreply@anthropic.com>
|
||||
Signed-off-by: Anderson Toshiyuki Sasaki <ansasaki@redhat.com>
|
||||
---
|
||||
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<Scope, APIError> {
|
||||
match version {
|
||||
@@ -201,6 +220,8 @@ pub(crate) fn get_api_scope(version: &str) -> Result<Scope, APIError> {
|
||||
.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<X509>,
|
||||
pub device_id: Option<device_id::DeviceID>,
|
||||
- pub attest: Option<tss_esapi::structures::Attest>,
|
||||
- pub signature: Option<tss_esapi::structures::Signature>,
|
||||
pub ak_handle: KeyHandle,
|
||||
pub retry_config: Option<RetryConfig>,
|
||||
}
|
||||
|
||||
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());
|
||||
@ -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)
|
||||
|
||||
Loading…
Reference in New Issue
Block a user