Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions keylime-agent/src/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Comment thread
ansasaki marked this conversation as resolved.
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 {
Expand All @@ -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())),
}
}
Expand Down
22 changes: 1 addition & 21 deletions keylime-agent/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
};
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)),
};
Expand Down
2 changes: 0 additions & 2 deletions keylime-push-model-agent/src/registration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
};
Expand Down
88 changes: 58 additions & 30 deletions keylime/src/agent_registration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand Down Expand Up @@ -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;
Expand All @@ -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)
Expand All @@ -81,16 +103,44 @@ 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"
.to_string(),
));
};

// 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())?
Expand All @@ -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?;

Expand Down
2 changes: 1 addition & 1 deletion keylime/src/config/base.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down
10 changes: 10 additions & 0 deletions keylime/src/registrar_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<'_>,
Expand Down
23 changes: 17 additions & 6 deletions keylime/src/tpm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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());
Expand Down
Loading