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
2 changes: 0 additions & 2 deletions keylime-ima-emulator/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@ use keylime::algorithms::HashAlgorithm;
use keylime::ima;
use openssl::hash::{hash, MessageDigest};

use log::*;

use clap::Parser;
use signal_hook::consts::SIGINT;
use signal_hook::consts::SIGTERM;
Expand Down
87 changes: 78 additions & 9 deletions keylime-push-model-agent/src/registration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,11 @@ pub struct RegistrarTlsConfig {
}

pub async fn check_registration(
context_info: Option<context_info::ContextInfo>,
mut context_info: Option<context_info::ContextInfo>,
tls_config: Option<RegistrarTlsConfig>,
) -> Result<()> {
if context_info.is_some() {
crate::registration::register_agent(
&mut context_info.unwrap(),
tls_config,
)
.await?;
if let Some(ref mut ctx) = context_info {
crate::registration::register_agent(ctx, tls_config).await?;
}
Ok(())
}
Expand Down Expand Up @@ -76,10 +72,14 @@ pub async fn register_agent(
registrar_ip: config.registrar_ip().to_string(),
registrar_port: config.registrar_port(),
enable_iak_idevid: config.enable_iak_idevid(),
// TODO: make it to not panic on failure
ek_handle: config
.ek_handle()
.expect("failed to get ek_handle")
.map_err(|e| {
keylime::error::Error::ConfigurationGenericError(format!(
"ek_handle configuration could not be retrieved: {e}. \
Please verify ek_handle is properly configured in your config file."
))
})?
.to_string(),
registrar_ca_cert: ca_cert,
registrar_client_cert: client_cert,
Expand Down Expand Up @@ -700,4 +700,73 @@ mod tests {
assert!(result.is_err());
assert!(context_info.flush_context().is_ok());
}

#[tokio::test]
async fn test_register_agent_ek_handle_error_handling() {
// REGRESSION TEST: Verifies that register_agent() uses .map_err() instead of
// .expect() for config.ek_handle(), preventing panics on configuration errors.
//
// CONTEXT: The ek_handle() method uses a transform function
// (override_default_ek_handle in keylime/src/config/push_model.rs) that
// currently always succeeds by returning a default value. This means the
// error path cannot be triggered in the current implementation.
//
// TEST PURPOSE:
// 1. Ensures the panic-to-error conversion remains in place (regression test)
// 2. Documents the improved error handling pattern for future maintainers
// 3. Will provide full error path coverage when the transform function is
// updated to validate ek_handle input
//
// FUTURE IMPROVEMENT: When override_default_ek_handle is updated to perform
// actual validation, this test should be enhanced to trigger and verify the
// error message: "ek_handle configuration could not be retrieved..."
let _mutex = testing::lock_tests().await;

let tmpdir = tempfile::tempdir().expect("failed to create tmpdir");
let mut config = get_testing_config(tmpdir.path(), None);
let alg_config = AlgorithmConfigurationString {
tpm_encryption_alg: "rsa".to_string(),
tpm_hash_alg: "sha256".to_string(),
tpm_signing_alg: "rsassa".to_string(),
agent_data_path: "".to_string(),
};

config.exponential_backoff_initial_delay = None;
config.exponential_backoff_max_retries = None;
config.exponential_backoff_max_delay = None;
config.registrar_ip = "127.0.0.1".to_string();
config.registrar_port = 1; // Invalid port causes connection failure

let _guard = keylime::config::TestConfigGuard::new(config);

let mut context_info = ContextInfo::new_from_str(alg_config)
.expect("Failed to create context info from string");

// Call register_agent - should fail on connection, not on ek_handle config
let result = register_agent(&mut context_info, None).await;

// ASSERTION 1: Function returns an error (connection failure), not a panic
assert!(
result.is_err(),
"Expected error due to invalid registrar port"
);

// ASSERTION 2: Verify the old .expect() panic message is not present
// This confirms the panic-to-error conversion is in place
let err_msg = format!("{:?}", result.unwrap_err());
assert!(
!err_msg.contains("failed to get ek_handle"),
"Old panic message should not appear; confirms .map_err() is used instead of .expect()"
);

// ASSERTION 3: Ensure ek_handle() is actually called without panic
// If it still used .expect(), this test would panic before reaching here
// The fact we get to this assertion proves the error handling works
assert!(
err_msg.contains("Connection") || err_msg.contains("Network") || err_msg.contains("Http"),
"Error should be connection-related, confirming ek_handle() succeeded without panic. Got: {err_msg}"
);

assert!(context_info.flush_context().is_ok());
}
}
130 changes: 47 additions & 83 deletions keylime-push-model-agent/src/struct_filler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,6 @@ fn log_mutex_poisoning_recovery() {
#[async_trait]
pub trait StructureFiller {
fn get_attestation_request(&mut self) -> structures::AttestationRequest;
#[allow(dead_code)]
fn get_session_request(&mut self) -> structures::SessionRequest;
async fn get_evidence_handling_request(
&mut self,
response_info: &crate::attestation::ResponseInformation,
Expand All @@ -80,9 +78,6 @@ impl StructureFiller for FillerFromHardware<'_> {
fn get_attestation_request(&mut self) -> structures::AttestationRequest {
self.get_attestation_request_final()
}
fn get_session_request(&mut self) -> structures::SessionRequest {
self.get_session_request_final()
}
async fn get_evidence_handling_request(
&mut self,
response_info: &crate::attestation::ResponseInformation,
Expand Down Expand Up @@ -286,26 +281,6 @@ impl<'a> FillerFromHardware<'a> {
}
}

// TODO: Change this function to use the session request appropriately
// TODO: This is expected to be used once the PoP authentication is implemented
#[allow(dead_code)]
pub fn get_session_request_final(
&mut self,
) -> structures::SessionRequest {
structures::SessionRequest {
data: structures::SessionRequestData {
data_type: "session".to_string(),
attributes: structures::SessionRequestAttributes {
agent_id: "example-agent".to_string(),
auth_supported: vec![structures::SupportedAuthMethod {
auth_class: "pop".to_string(),
auth_type: "tpm_pop".to_string(),
}],
},
},
}
}

pub async fn get_evidence_handling_request_final(
&mut self,
response_info: &crate::attestation::ResponseInformation,
Expand Down Expand Up @@ -343,6 +318,47 @@ impl<'a> FillerFromHardware<'a> {
Ok(evidence) => evidence,
Err(e) => {
error!("Failed to perform attestation: {e}");

// Check if this is a fatal mutex poisoning error
if let keylime::context_info::ContextInfoError::Tpm(
keylime::tpm::TpmError::MutexPoisonedDuringOperation {
operation,
},
) = &e
{
error!("FATAL: TPM mutex poisoned during {operation} - terminating agent");
error!("The agent will now exit to allow systemd/supervisors to restart it with a clean state");

// Mutex poisoning indicates a thread panicked while holding the TPM lock,
// which represents an unrecoverable state for the following reasons:
//
// 1. TPM HARDWARE STATE: The TPM is a hardware security device with exclusive
// access requirements. If a panic occurred mid-operation, the TPM may be
// in an inconsistent state that cannot be safely recovered within the
// current process.
//
// 2. SECURITY CONSIDERATIONS: For a remote attestation agent, any indication
// of internal corruption (which mutex poisoning signals) should be treated
// as a critical security event. Continuing operation could compromise the
// integrity of attestation measurements.
//
// 3. CLEAN RESTART REQUIRED: The only safe recovery is a complete process
// restart that reinitializes all TPM contexts from a known-good state.
// systemd/supervisors should be configured with appropriate restart
// policies to handle this scenario.
//
// 4. IMMEDIATE EXIT JUSTIFICATION: While std::process::exit(1) bypasses
// normal Rust cleanup (Drop implementations, thread joins), this is
// acceptable here because:
// - The TPM state is already compromised and cleanup may fail
// - Attempting graceful shutdown could trigger additional panics
// - The supervisor will perform any necessary system-level cleanup
//
// DEPLOYMENT NOTE: Ensure systemd unit or supervisor is configured with
// Restart=on-failure and appropriate RestartSec to handle this exit.
std::process::exit(1);
}

return structures::EvidenceHandlingRequest {
data: structures::EvidenceHandlingRequestData {
data_type: "error".to_string(),
Expand Down Expand Up @@ -396,17 +412,6 @@ impl StructureFiller for TestingFiller {
},
}
}
fn get_session_request(&mut self) -> structures::SessionRequest {
structures::SessionRequest {
data: structures::SessionRequestData {
data_type: "session".to_string(),
attributes: structures::SessionRequestAttributes {
agent_id: "example-agent".to_string(),
auth_supported: vec![],
},
},
}
}
async fn get_evidence_handling_request(
&mut self,
_response_info: &crate::attestation::ResponseInformation,
Expand Down Expand Up @@ -547,38 +552,6 @@ mod tests {
assert!(context_info.flush_context().is_ok());
}

#[tokio::test]
async fn test_session_request() {
use keylime::context_info;
let _mutex = testing::lock_tests().await;
let context_info_result = context_info::ContextInfo::new_from_str(
context_info::AlgorithmConfigurationString {
tpm_encryption_alg: "rsa".to_string(),
tpm_hash_alg: "sha256".to_string(),
tpm_signing_alg: "rsassa".to_string(),
agent_data_path: "".to_string(),
},
);

// Skip test if TPM access is not available
let mut context_info = match context_info_result {
Ok(ctx) => ctx,
Err(_) => {
println!("Skipping test_session_request: TPM not available");
return;
}
};

let privileged_resources = create_test_privileged_resources();
let mut filler =
FillerFromHardware::new(&mut context_info, &privileged_resources);
let session_request = filler.get_session_request_final();
assert_eq!(session_request.data.data_type, "session");
let serialized = serde_json::to_string(&session_request).unwrap();
assert!(!serialized.is_empty());
assert!(context_info.flush_context().is_ok());
} // test_session_request

#[tokio::test]
async fn test_failing_evidence_handling_request() {
use std::collections::HashMap;
Expand Down Expand Up @@ -690,13 +663,10 @@ mod tests {
if let Ok(mut ctx) = context_info_result {
{
let privileged_resources = create_test_privileged_resources();
let mut filler =
let _filler =
get_filler_request(Some(&mut ctx), &privileged_resources);
// To check the type, we can't directly compare types of Box<dyn Trait>.
// A simple way is to check the output of a method.
let req = filler.get_session_request();
// FillerFromHardware returns a specific agent_id
assert_eq!(req.data.attributes.agent_id, "example-agent");
// Verify that we can create a FillerFromHardware successfully
// (actual method testing is done in other tests)
}
assert!(ctx.clone().flush_context().is_ok());
}
Expand All @@ -705,10 +675,9 @@ mod tests {
#[tokio::test]
async fn test_get_filler_request_without_tpm() {
let privileged_resources = create_test_privileged_resources();
let mut filler = get_filler_request(None, &privileged_resources);
// TestingFiller returns an empty auth_supported vector
let req = filler.get_session_request();
assert!(req.data.attributes.auth_supported.is_empty());
let _filler = get_filler_request(None, &privileged_resources);
// Verify that we can create a TestingFiller successfully
// (actual method testing is done in other tests)
}

#[tokio::test]
Expand All @@ -724,11 +693,6 @@ mod tests {
.evidence_supported
.is_empty());

// Test get_session_request
let session_req = filler.get_session_request();
assert_eq!(session_req.data.data_type, "session");
assert!(session_req.data.attributes.auth_supported.is_empty());

// Test get_evidence_handling_request
let dummy_response = crate::attestation::ResponseInformation {
status_code: reqwest::StatusCode::OK,
Expand Down
1 change: 0 additions & 1 deletion keylime/src/agent_identity.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
use log::*;
use openssl::x509::X509;
use thiserror::Error;

Expand Down
Loading
Loading