Skip to content

Commit 49db173

Browse files
committed
Use a single TPM context and avoid race conditions during tests
This implements a singleton to use a single instance of the tss_espi::Context. This also avoids race conditions by using a thread-safe mutex to access the context. The tests were modified to cleanup generated key handles between test cases, preventing leftover handles to fill the TPM memory. Introduce a mutex to allow only a single test that create keys in the TPM to run at once. It is required that tests that create keys in the TPM to run the tpm::testing::lock_tests() to obtain the mutex and be able to continue executing without the risk of other tests filling the TPM memory. The QuoteData::fixture implementation (used only during tests) was modified to flush the created key contexts when dropped. It also was modified to lock the global mutex by calling the tpm::testing::lock_tests() and provide the respective guard, preventing other tests that use the fixture (which necessarily create TPM keys) to run in parallel. Note that it is necessary to drop the QuoteData explicitly to guarantee that the keys are flushed before releasing the global mutex. Finally, the following changes were made to the tests/run.sh: * Do not use tpm2-abrmd anymore, and use only the swtpm socket instead * Stop the started processes at exit With all these changes, the tests/run.sh can now be executed locally without any special setting. Signed-off-by: Anderson Toshiyuki Sasaki <[email protected]>
1 parent 351ef6f commit 49db173

10 files changed

+606
-343
lines changed

Cargo.lock

+1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

keylime-agent/src/agent_handler.rs

+5-2
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ pub(crate) struct AgentInfo {
2121
// It should return a AgentInfo object as JSON
2222
async fn info(
2323
req: HttpRequest,
24-
data: web::Data<QuoteData>,
24+
data: web::Data<QuoteData<'_>>,
2525
) -> impl Responder {
2626
debug!("Returning agent information");
2727

@@ -87,7 +87,7 @@ mod tests {
8787

8888
#[actix_rt::test]
8989
async fn test_agent_info() {
90-
let mut quotedata = QuoteData::fixture().unwrap(); //#[allow_ci]
90+
let (mut quotedata, mutex) = QuoteData::fixture().await.unwrap(); //#[allow_ci]
9191
quotedata.hash_alg = keylime::algorithms::HashAlgorithm::Sha256;
9292
quotedata.enc_alg = keylime::algorithms::EncryptionAlgorithm::Rsa;
9393
quotedata.sign_alg = keylime::algorithms::SignAlgorithm::RsaSsa;
@@ -112,6 +112,9 @@ mod tests {
112112
assert_eq!(result.results.tpm_hash_alg.as_str(), "sha256");
113113
assert_eq!(result.results.tpm_enc_alg.as_str(), "rsa");
114114
assert_eq!(result.results.tpm_sign_alg.as_str(), "rsassa");
115+
116+
// Explicitly drop QuoteData to cleanup keys
117+
drop(data);
115118
}
116119

117120
#[actix_rt::test]

keylime-agent/src/common.rs

+17-4
Original file line numberDiff line numberDiff line change
@@ -274,9 +274,10 @@ mod tests {
274274
Context,
275275
};
276276

277+
#[tokio::test]
277278
#[cfg(feature = "testing")]
278-
#[test]
279-
fn test_agent_data() -> Result<()> {
279+
async fn test_agent_data() -> Result<()> {
280+
let _mutex = tpm::testing::lock_tests().await;
280281
let mut config = KeylimeConfig::default();
281282

282283
let mut ctx = tpm::Context::new()?;
@@ -319,13 +320,21 @@ mod tests {
319320
tpm_signing_alg,
320321
ek_hash.as_bytes(),
321322
);
323+
322324
assert!(valid);
325+
326+
// Cleanup created keys
327+
let ak_handle = ctx.load_ak(ek_result.key_handle, &ak)?;
328+
ctx.flush_context(ak_handle.into());
329+
ctx.flush_context(ek_result.key_handle.into());
330+
323331
Ok(())
324332
}
325333

334+
#[tokio::test]
326335
#[cfg(feature = "testing")]
327-
#[test]
328-
fn test_hash() -> Result<()> {
336+
async fn test_hash() -> Result<()> {
337+
let _mutex = tpm::testing::lock_tests().await;
329338
let mut config = KeylimeConfig::default();
330339

331340
let mut ctx = tpm::Context::new()?;
@@ -342,6 +351,10 @@ mod tests {
342351
let result = hash_ek_pubkey(ek_result.public);
343352

344353
assert!(result.is_ok());
354+
355+
// Cleanup created keys
356+
ctx.flush_context(ek_result.key_handle.into());
357+
345358
Ok(())
346359
}
347360
}

keylime-agent/src/keys_handler.rs

+13-6
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ fn try_combine_keys(
133133
async fn u_key(
134134
body: web::Json<KeylimeUKey>,
135135
req: HttpRequest,
136-
quote_data: web::Data<QuoteData>,
136+
quote_data: web::Data<QuoteData<'_>>,
137137
) -> impl Responder {
138138
debug!("Received ukey");
139139

@@ -247,7 +247,7 @@ async fn u_key(
247247
async fn v_key(
248248
body: web::Json<KeylimeVKey>,
249249
req: HttpRequest,
250-
quote_data: web::Data<QuoteData>,
250+
quote_data: web::Data<QuoteData<'_>>,
251251
) -> impl Responder {
252252
debug!("Received vkey");
253253

@@ -317,7 +317,7 @@ async fn v_key(
317317

318318
async fn pubkey(
319319
req: HttpRequest,
320-
data: web::Data<QuoteData>,
320+
data: web::Data<QuoteData<'_>>,
321321
) -> impl Responder {
322322
match crypto::pkey_pub_to_pem(&data.pub_key) {
323323
Ok(pubkey) => {
@@ -367,7 +367,7 @@ async fn get_symm_key(
367367
async fn verify(
368368
param: web::Query<KeylimeChallenge>,
369369
req: HttpRequest,
370-
data: web::Data<QuoteData>,
370+
data: web::Data<QuoteData<'_>>,
371371
) -> impl Responder {
372372
if param.challenge.is_empty() {
373373
warn!(
@@ -875,7 +875,7 @@ mod tests {
875875
#[cfg(feature = "testing")]
876876
async fn test_u_or_v_key(key_len: usize, payload: Option<&[u8]>) {
877877
let test_config = KeylimeConfig::default();
878-
let mut fixture = QuoteData::fixture().unwrap(); //#[allow_ci]
878+
let (mut fixture, mutex) = QuoteData::fixture().await.unwrap(); //#[allow_ci]
879879

880880
// Create temporary working directory and secure mount
881881
let temp_workdir = tempfile::tempdir().unwrap(); //#[allow_ci]
@@ -1073,6 +1073,9 @@ mod tests {
10731073
keys_tx.send((KeyMessage::Shutdown, None)).await.unwrap(); //#[allow_ci]
10741074
payload_tx.send(PayloadMessage::Shutdown).await.unwrap(); //#[allow_ci]
10751075
arbiter.join();
1076+
1077+
// Explicitly drop QuoteData to cleanup keys
1078+
drop(quotedata);
10761079
}
10771080

10781081
#[cfg(feature = "testing")]
@@ -1090,7 +1093,8 @@ mod tests {
10901093
#[cfg(feature = "testing")]
10911094
#[actix_rt::test]
10921095
async fn test_pubkey() {
1093-
let quotedata = web::Data::new(QuoteData::fixture().unwrap()); //#[allow_ci]
1096+
let (fixture, mutex) = QuoteData::fixture().await.unwrap(); //#[allow_ci]
1097+
let quotedata = web::Data::new(fixture);
10941098
let mut app =
10951099
test::init_service(App::new().app_data(quotedata.clone()).route(
10961100
&format!("/{API_VERSION}/keys/pubkey"),
@@ -1110,5 +1114,8 @@ mod tests {
11101114
assert!(pkey_pub_from_pem(&result.results.pubkey)
11111115
.unwrap() //#[allow_ci]
11121116
.public_eq(&quotedata.pub_key));
1117+
1118+
// Explicitly drop QuoteData to cleanup keys
1119+
drop(quotedata);
11131120
}
11141121
}

keylime-agent/src/main.rs

+64-48
Original file line numberDiff line numberDiff line change
@@ -98,8 +98,8 @@ static NOTFOUND: &[u8] = b"Not Found";
9898
// This data is passed in to the actix httpserver threads that
9999
// handle quotes.
100100
#[derive(Debug)]
101-
pub struct QuoteData {
102-
tpmcontext: Mutex<tpm::Context>,
101+
pub struct QuoteData<'a> {
102+
tpmcontext: Mutex<tpm::Context<'a>>,
103103
priv_key: PKey<Private>,
104104
pub_key: PKey<Public>,
105105
ak_handle: KeyHandle,
@@ -273,14 +273,6 @@ async fn main() -> Result<()> {
273273

274274
let mut ctx = tpm::Context::new()?;
275275

276-
// Retrieve the TPM Vendor, this allows us to warn if someone is using a
277-
// Software TPM ("SW")
278-
if tss_esapi::utils::get_tpm_vendor(ctx.as_mut())?.contains("SW") {
279-
warn!("INSECURE: Keylime is currently using a software TPM emulator rather than a real hardware TPM.");
280-
warn!("INSECURE: The security of Keylime is NOT linked to a hardware root of trust.");
281-
warn!("INSECURE: Only use Keylime in this mode for testing or debugging purposes.");
282-
}
283-
284276
cfg_if::cfg_if! {
285277
if #[cfg(feature = "legacy-python-actions")] {
286278
warn!("The support for legacy python revocation actions is deprecated and will be removed on next major release");
@@ -313,7 +305,7 @@ async fn main() -> Result<()> {
313305
} else {
314306
Auth::try_from(tpm_ownerpassword.as_bytes())?
315307
};
316-
ctx.as_mut().tr_set_auth(Hierarchy::Endorsement.into(), auth)
308+
ctx.tr_set_auth(Hierarchy::Endorsement.into(), auth)
317309
.map_err(|e| {
318310
Error::Configuration(config::KeylimeConfigError::Generic(format!(
319311
"Failed to set TPM context password for Endorsement Hierarchy: {e}"
@@ -406,7 +398,7 @@ async fn main() -> Result<()> {
406398
/// If handle is not set in config, recreate IDevID according to template
407399
info!("Recreating IDevID.");
408400
let regen_idev = ctx.create_idevid(asym_alg, name_alg)?;
409-
ctx.as_mut().flush_context(regen_idev.handle.into())?;
401+
ctx.flush_context(regen_idev.handle.into())?;
410402
// Flush after creating to make room for AK and EK and IAK
411403
regen_idev
412404
} else {
@@ -780,7 +772,7 @@ async fn main() -> Result<()> {
780772
)?;
781773
// Flush EK if we created it
782774
if config.agent.ek_handle.is_empty() {
783-
ctx.as_mut().flush_context(ek_result.key_handle.into())?;
775+
ctx.flush_context(ek_result.key_handle.into())?;
784776
}
785777
let mackey = general_purpose::STANDARD.encode(key.value());
786778
let auth_tag =
@@ -1062,6 +1054,11 @@ mod testing {
10621054
use crate::{config::KeylimeConfig, crypto::CryptoError};
10631055
use thiserror::Error;
10641056

1057+
use std::sync::{Arc, Mutex, OnceLock};
1058+
use tokio::sync::{Mutex as AsyncMutex, MutexGuard as AsyncMutexGuard};
1059+
1060+
use keylime::tpm::testing::lock_tests;
1061+
10651062
#[derive(Error, Debug)]
10661063
pub(crate) enum MainTestError {
10671064
/// Algorithm error
@@ -1093,8 +1090,22 @@ mod testing {
10931090
TSSError(#[from] tss_esapi::Error),
10941091
}
10951092

1096-
impl QuoteData {
1097-
pub(crate) fn fixture() -> std::result::Result<Self, MainTestError> {
1093+
impl<'a> Drop for QuoteData<'a> {
1094+
/// Flush the created AK when dropping
1095+
fn drop(&mut self) {
1096+
self.tpmcontext
1097+
.lock()
1098+
.unwrap() //#[allow_ci]
1099+
.flush_context(self.ak_handle.into());
1100+
}
1101+
}
1102+
1103+
impl<'a> QuoteData<'a> {
1104+
pub(crate) async fn fixture() -> std::result::Result<
1105+
(Self, AsyncMutexGuard<'static, ()>),
1106+
MainTestError,
1107+
> {
1108+
let mutex = lock_tests().await;
10981109
let test_config = KeylimeConfig::default();
10991110
let mut ctx = tpm::Context::new()?;
11001111

@@ -1103,9 +1114,6 @@ mod testing {
11031114
test_config.agent.tpm_encryption_alg.as_str(),
11041115
)?;
11051116

1106-
// Gather EK and AK key values and certs
1107-
let ek_result = ctx.create_ek(tpm_encryption_alg, None)?;
1108-
11091117
let tpm_hash_alg = keylime::algorithms::HashAlgorithm::try_from(
11101118
test_config.agent.tpm_hash_alg.as_str(),
11111119
)?;
@@ -1115,14 +1123,19 @@ mod testing {
11151123
test_config.agent.tpm_signing_alg.as_str(),
11161124
)?;
11171125

1118-
let ak_result = ctx.create_ak(
1119-
ek_result.key_handle,
1120-
tpm_hash_alg,
1121-
tpm_signing_alg,
1122-
)?;
1123-
let ak_handle = ctx.load_ak(ek_result.key_handle, &ak_result)?;
1124-
let ak_tpm2b_pub =
1125-
PublicBuffer::try_from(ak_result.public)?.marshall()?;
1126+
// Gather EK and AK key values and certs
1127+
let ek_result = ctx.create_ek(tpm_encryption_alg, None).unwrap(); //#[allow_ci]
1128+
let ak_result = ctx
1129+
.create_ak(
1130+
ek_result.key_handle,
1131+
tpm_hash_alg,
1132+
tpm_signing_alg,
1133+
)
1134+
.unwrap(); //#[allow_ci]
1135+
let ak_handle =
1136+
ctx.load_ak(ek_result.key_handle, &ak_result).unwrap(); //#[allow_ci]
1137+
1138+
ctx.flush_context(ek_result.key_handle.into()).unwrap(); //#[allow_ci]
11261139

11271140
let rsa_key_path = Path::new(env!("CARGO_MANIFEST_DIR"))
11281141
.join("test-data")
@@ -1177,28 +1190,31 @@ mod testing {
11771190
Err(err) => None,
11781191
};
11791192

1180-
Ok(QuoteData {
1181-
tpmcontext: Mutex::new(ctx),
1182-
priv_key: nk_priv,
1183-
pub_key: nk_pub,
1184-
ak_handle,
1185-
keys_tx,
1186-
payload_tx,
1187-
revocation_tx,
1188-
hash_alg: keylime::algorithms::HashAlgorithm::Sha256,
1189-
enc_alg: keylime::algorithms::EncryptionAlgorithm::Rsa,
1190-
sign_alg: keylime::algorithms::SignAlgorithm::RsaSsa,
1191-
agent_uuid: test_config.agent.uuid,
1192-
allow_payload_revocation_actions: test_config
1193-
.agent
1194-
.allow_payload_revocation_actions,
1195-
secure_size: test_config.agent.secure_size,
1196-
work_dir,
1197-
ima_ml_file,
1198-
measuredboot_ml_file,
1199-
ima_ml: Mutex::new(MeasurementList::new()),
1200-
secure_mount,
1201-
})
1193+
Ok((
1194+
QuoteData {
1195+
tpmcontext: Mutex::new(ctx),
1196+
priv_key: nk_priv,
1197+
pub_key: nk_pub,
1198+
ak_handle,
1199+
keys_tx,
1200+
payload_tx,
1201+
revocation_tx,
1202+
hash_alg: keylime::algorithms::HashAlgorithm::Sha256,
1203+
enc_alg: keylime::algorithms::EncryptionAlgorithm::Rsa,
1204+
sign_alg: keylime::algorithms::SignAlgorithm::RsaSsa,
1205+
agent_uuid: test_config.agent.uuid,
1206+
allow_payload_revocation_actions: test_config
1207+
.agent
1208+
.allow_payload_revocation_actions,
1209+
secure_size: test_config.agent.secure_size,
1210+
work_dir,
1211+
ima_ml_file,
1212+
measuredboot_ml_file,
1213+
ima_ml: Mutex::new(MeasurementList::new()),
1214+
secure_mount,
1215+
},
1216+
mutex,
1217+
))
12021218
}
12031219
}
12041220
}

keylime-agent/src/notifications_handler.rs

+5-2
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ use std::path::{Path, PathBuf};
1515
async fn revocation(
1616
body: web::Json<Revocation>,
1717
req: HttpRequest,
18-
data: web::Data<QuoteData>,
18+
data: web::Data<QuoteData<'_>>,
1919
) -> impl Responder {
2020
info!("Received revocation");
2121

@@ -138,7 +138,7 @@ mod tests {
138138
PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/actions"),
139139
);
140140

141-
let mut fixture = QuoteData::fixture().unwrap(); //#[allow_ci]
141+
let (mut fixture, mutex) = QuoteData::fixture().await.unwrap(); //#[allow_ci]
142142

143143
// Replace the channels on the fixture with some local ones
144144
let (mut revocation_tx, mut revocation_rx) =
@@ -188,5 +188,8 @@ mod tests {
188188

189189
let resp = test::call_service(&app, req).await;
190190
assert!(resp.status().is_success());
191+
192+
// Explicitly drop QuoteData to cleanup keys
193+
drop(quotedata);
191194
}
192195
}

0 commit comments

Comments
 (0)