Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package rust-keylime for openSUSE:Factory checked in at 2026-10-28 17:44:24 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/rust-keylime (Old) and /work/SRC/openSUSE:Factory/.rust-keylime.new.2252680 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "rust-keylime" Wed Oct 28 17:44:24 2026 rev:36 rq:1393019 version:0.2.10+12 Changes: -------- --- /work/SRC/openSUSE:Factory/rust-keylime/rust-keylime.changes 2026-07-08 17:37:35.799691077 +0200 +++ /work/SRC/openSUSE:Factory/.rust-keylime.new.2252680/rust-keylime.changes 2026-10-28 17:44:44.443536800 +0100 @@ -1,0 +2,23 @@ +Tue Oct 27 08:38:43 UTC 2026 - [email protected] + +- Add Cargo_toml.patch +- Update zip to 9.1.2 + bsc#1287921, CVE-2026-115905, CVE-2026-115906, CVE-2026-115907, + CVE-2026-115908, CVE-2026-115909 +- Update to version 0.2.10+12: + * log Agent UUID at push agent startup + * Limit actix-web workers + * Fix clippy needless_late_init warnings + * Unwrap OCTET STRING wrapped EK certificates + * build(deps): bump rand from 0.9.4 to 0.10.1 + * build(deps): bump glob from 0.3.3 to 0.3.4 + * build(deps): bump reqwest from 0.13.3 to 0.13.4 + * Check shutdown marker inside ml_extend loop + * Fix clippy useless_borrows_in_formatting warnings + * build(deps): bump tokio from 1.52.2 to 1.53.1 + * Fix payload key upgrade regression by falling back to mTLS key + * Accept any RSA key size for payload encryption + * Bump version to 0.2.10 + * keylime: use runtime OpenSSL algorithm detection instead of compile-time symbols + +------------------------------------------------------------------- Old: ---- rust-keylime-0.2.9+49.tar.zst New: ---- Cargo_toml.patch rust-keylime-0.2.10+12.tar.zst ----------(New B)---------- New: - Add Cargo_toml.patch - Update zip to 9.1.2 ----------(New E)---------- ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ rust-keylime.spec ++++++ --- /var/tmp/diff_new_pack.i4q2m4/_old 2026-10-28 17:44:51.885847191 +0100 +++ /var/tmp/diff_new_pack.i4q2m4/_new 2026-10-28 17:44:51.888847316 +0100 @@ -25,7 +25,7 @@ %define _config_norepl %config(noreplace) %endif Name: rust-keylime -Version: 0.2.9+49 +Version: 0.2.10+12 Release: 0 Summary: Rust implementation of the keylime agent License: (Apache-2.0 OR MIT) AND BSD-3-Clause AND (Apache-2.0 OR MIT) AND Unicode-DFS-2016 AND (Apache-2.0 OR BSL-1.0) AND (Apache-2.0 OR ISC OR MIT) AND (Apache-2.0 OR MIT) AND (Apache-2.0 OR Apache-2.0 WITH LLVM-exception OR MIT) AND (Apache-2.0 OR MIT OR Zlib) AND (MIT OR Unlicense) AND (Apache-2.0 OR Zlib OR MIT) AND Apache-2.0 AND Apache-2.0 WITH LLVM-exception AND BSD-3-Clause AND ISC AND MIT @@ -42,6 +42,7 @@ # PATCH-FIX-OPENSUSE keylime-agent.conf.diff Patch1: keylime-agent.conf.diff Patch2: Cargo_lock.patch +Patch3: Cargo_toml.patch BuildRequires: cargo-packaging BuildRequires: clang BuildRequires: firewall-macros ++++++ Cargo_lock.patch ++++++ ++++ 2752 lines (skipped) ++++ between /work/SRC/openSUSE:Factory/rust-keylime/Cargo_lock.patch ++++ and /work/SRC/openSUSE:Factory/.rust-keylime.new.2252680/Cargo_lock.patch ++++++ Cargo_toml.patch ++++++ diff --git i/Cargo.toml w/Cargo.toml index d006826..ee7bbbe 100644 --- i/Cargo.toml +++ w/Cargo.toml @@ -60,4 +60,4 @@ tokio = {version = "1", features = ["rt", "sync", "macros"]} tss-esapi = {version = "7.7.0", features = ["generate-bindings"]} url = "2.5.4" uuid = {version = "1.3", features = ["v4"]} -zip = {version = "0.6", default-features = false, features= ["deflate"]} +zip = {version = "*", default-features = false, features= ["deflate"]} ++++++ _service ++++++ --- /var/tmp/diff_new_pack.i4q2m4/_old 2026-10-28 17:44:52.083855414 +0100 +++ /var/tmp/diff_new_pack.i4q2m4/_new 2026-10-28 17:44:52.092855788 +0100 @@ -1,29 +1,29 @@ <services> - <service mode="disabled" name="obs_scm"> + <service mode="manual" name="tar_scm"> <param name="url">https://github.com/keylime/rust-keylime.git</param> - <!-- <param name="versionformat">@PARENT_TAG@</param> --> - <param name="versionformat">@PARENT_TAG@+@TAG_OFFSET@</param> <param name="scm">git</param> - <param name="revision">v0.2.9</param> + <!-- Use a release tag, or master for a snapshot --> + <!-- <param name="revision">v0.2.10</param> --> <param name="revision">master</param> - <param name="match-tag">*</param> - <param name="versionrewrite-pattern">v(\d+\.\d+\.\d+)</param> + <!-- <param name="versionformat">@PARENT_TAG@</param> --> + <param name="versionformat">@PARENT_TAG@+@TAG_OFFSET@</param> + <param name="match-tag">v*</param> + <param name="versionrewrite-pattern">v(.*)</param> <param name="versionrewrite-replacement">\1</param> <param name="changesgenerate">enable</param> <param name="changesauthor">[email protected]</param> </service> - <service mode="disabled" name="tar" /> - <service mode="disabled" name="recompress"> + <service mode="manual" name="recompress"> <param name="file">*.tar</param> <param name="compression">zst</param> </service> - <service mode="disabled" name="set_version"/> - <!-- <service name="cargo_vendor" mode="disabled"> --> - <!-- <param name="src">rust-keylime</param> --> - <!-- <param name="compression">zst</param> --> - <!-- <param name="update">true</param> --> + <service mode="manual" name="set_version"/> + <!-- <service mode="manual" name="cargo_vendor"> --> + <!-- <param name="srcdir">rust-keylime</param> --> + <!-- <param name="compression">zst</param> --> + <!-- <param name="update">true</param> --> <!-- </service> --> - <service name="cargo_audit" mode="disabled"> + <service mode="manual" name="cargo_audit"> <param name="srcdir">rust-keylime</param> </service> </services> ++++++ _servicedata ++++++ --- /var/tmp/diff_new_pack.i4q2m4/_old 2026-10-28 17:44:52.143857906 +0100 +++ /var/tmp/diff_new_pack.i4q2m4/_new 2026-10-28 17:44:52.156858446 +0100 @@ -1,6 +1,6 @@ <servicedata> <service name="tar_scm"> <param name="url">https://github.com/keylime/rust-keylime.git</param> - <param name="changesrevision">28a2e1dec001b29ea417ea152bfbd9a8b06f6e14</param></service></servicedata> + <param name="changesrevision">54c62facf656a499a21809486c300d52572444d7</param></service></servicedata> (No newline at EOF) ++++++ rust-keylime-0.2.9+49.tar.zst -> rust-keylime-0.2.10+12.tar.zst ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rust-keylime-0.2.9+49/Cargo.lock new/rust-keylime-0.2.10+12/Cargo.lock --- old/rust-keylime-0.2.9+49/Cargo.lock 2026-07-03 13:52:37.000000000 +0200 +++ new/rust-keylime-0.2.10+12/Cargo.lock 2026-09-25 13:29:29.000000000 +0200 @@ -931,9 +931,9 @@ [[package]] name = "glob" -version = "0.3.3" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" +checksum = "e4eba85ea1d0a966a983acd07deee566e67395d2d96b6fb39e62b5a833f1eb0b" [[package]] name = "h2" @@ -1338,7 +1338,7 @@ [[package]] name = "keylime" -version = "0.2.9" +version = "0.2.10" dependencies = [ "actix-rt", "actix-web", @@ -1360,11 +1360,12 @@ "pest_derive", "picky-asn1-der", "picky-asn1-x509", - "rand 0.9.4", + "rand 0.9.5", "reqwest", "reqwest-middleware", "reqwest-retry", "serde", + "serde_bytes", "serde_json", "static_assertions", "tempfile", @@ -1379,7 +1380,7 @@ [[package]] name = "keylime-macros" -version = "0.2.9" +version = "0.2.10" dependencies = [ "quote", "syn", @@ -1389,7 +1390,7 @@ [[package]] name = "keylime_agent" -version = "0.2.9" +version = "0.2.10" dependencies = [ "actix-rt", "actix-web", @@ -1417,7 +1418,7 @@ [[package]] name = "keylime_ima_emulator" -version = "0.2.9" +version = "0.2.10" dependencies = [ "clap", "hex", @@ -1431,7 +1432,7 @@ [[package]] name = "keylime_push_model_agent" -version = "0.2.9" +version = "0.2.10" dependencies = [ "actix-rt", "actix-web", @@ -1923,9 +1924,9 @@ [[package]] name = "rand" -version = "0.9.4" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea" +checksum = "b9ef1d0d795eb7d84685bca4f72f3649f064e6641543d3a8c415898726a57b41" dependencies = [ "rand_chacha", "rand_core 0.9.5", @@ -2013,9 +2014,9 @@ [[package]] name = "reqwest" -version = "0.13.3" +version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62e0021ea2c22aed41653bc7e1419abb2c97e038ff2c33d0e1309e49a97deec0" +checksum = "219c5811de6525e5416c7d5d53bb656d3afdbc6c5af816e0802bcfa42dbdc1c3" dependencies = [ "base64", "bytes", @@ -2409,7 +2410,7 @@ checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" dependencies = [ "fastrand", - "getrandom 0.3.4", + "getrandom 0.4.2", "once_cell", "rustix", "windows-sys 0.61.2", @@ -2496,9 +2497,9 @@ [[package]] name = "tokio" -version = "1.52.2" +version = "1.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "110a78583f19d5cdb2c5ccf321d1290344e71313c6c37d43520d386027d18386" +checksum = "202caea871b69668250d242070849eb495be178ed697a3e98aebce5bc81a0bed" dependencies = [ "bytes", "libc", diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rust-keylime-0.2.9+49/Cargo.toml new/rust-keylime-0.2.10+12/Cargo.toml --- old/rust-keylime-0.2.9+49/Cargo.toml 2026-07-03 13:52:37.000000000 +0200 +++ new/rust-keylime-0.2.10+12/Cargo.toml 2026-09-25 13:29:29.000000000 +0200 @@ -12,7 +12,7 @@ edition = "2021" license = "Apache-2.0" repository = "https://github.com/keylime/rust-keylime" -version = "0.2.9" +version = "0.2.10" [workspace.dependencies] actix-rt = "2" @@ -31,8 +31,8 @@ hex = "0.4" http = "1.1.0" httpdate = "1.0.3" -keylime = { version = "=0.2.9", path = "keylime" } -keylime-macros = { version = "=0.2.9", path = "keylime-macros" } +keylime = { version = "=0.2.10", path = "keylime" } +keylime-macros = { version = "=0.2.10", path = "keylime-macros" } libc = "0.2.43" log = "0.4" once_cell = "1.19.0" @@ -49,6 +49,7 @@ reqwest-retry = "0.9.1" retry-policies = "0.5.0" serde = "1.0.80" +serde_bytes = "0.11" serde_derive = "1.0.80" serde_json = { version = "1.0", features = ["raw_value"] } signal-hook = "0.3" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rust-keylime-0.2.9+49/keylime/Cargo.toml new/rust-keylime-0.2.10+12/keylime/Cargo.toml --- old/rust-keylime-0.2.9+49/keylime/Cargo.toml 2026-07-03 13:52:37.000000000 +0200 +++ new/rust-keylime-0.2.10+12/keylime/Cargo.toml 2026-09-25 13:29:29.000000000 +0200 @@ -30,6 +30,7 @@ reqwest-middleware.workspace = true reqwest-retry.workspace = true serde.workspace = true +serde_bytes.workspace = true serde_json.workspace = true static_assertions.workspace = true tempfile.workspace = true diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rust-keylime-0.2.9+49/keylime/src/agent_registration.rs new/rust-keylime-0.2.10+12/keylime/src/agent_registration.rs --- old/rust-keylime-0.2.9+49/keylime/src/agent_registration.rs 2026-07-03 13:52:37.000000000 +0200 +++ new/rust-keylime-0.2.10+12/keylime/src/agent_registration.rs 2026-09-25 13:29:29.000000000 +0200 @@ -168,7 +168,7 @@ // Request keyblob material let keyblob = registrar_client.register_agent(&ai).await?; - info!("SUCCESS: Agent {} registered", &aa.agent_uuid); + info!("SUCCESS: Agent {} registered", aa.agent_uuid); let key = ctx.activate_credential( keyblob, @@ -188,6 +188,6 @@ registrar_client.activate_agent(&ai, &auth_tag).await?; - info!("SUCCESS: Agent {} activated", &aa.agent_uuid); + info!("SUCCESS: Agent {} activated", aa.agent_uuid); Ok(()) } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rust-keylime-0.2.9+49/keylime/src/algorithms.rs new/rust-keylime-0.2.10+12/keylime/src/algorithms.rs --- old/rust-keylime-0.2.9+49/keylime/src/algorithms.rs 2026-07-03 13:52:37.000000000 +0200 +++ new/rust-keylime-0.2.10+12/keylime/src/algorithms.rs 2026-09-25 13:29:29.000000000 +0200 @@ -113,16 +113,33 @@ } } -impl From<HashAlgorithm> for MessageDigest { - fn from(hash_algorithm: HashAlgorithm) -> Self { - match hash_algorithm { - HashAlgorithm::Sha1 => MessageDigest::sha1(), - HashAlgorithm::Sha256 => MessageDigest::sha256(), - HashAlgorithm::Sha384 => MessageDigest::sha384(), - HashAlgorithm::Sha512 => MessageDigest::sha512(), - HashAlgorithm::Sm3_256 => MessageDigest::sm3(), +impl HashAlgorithm { + fn openssl_name(&self) -> &'static str { + match self { + HashAlgorithm::Sha1 => "sha1", + HashAlgorithm::Sha256 => "sha256", + HashAlgorithm::Sha384 => "sha384", + HashAlgorithm::Sha512 => "sha512", + HashAlgorithm::Sm3_256 => "sm3", } } + + pub fn is_supported(&self) -> bool { + MessageDigest::from_name(self.openssl_name()).is_some() + } +} + +impl TryFrom<HashAlgorithm> for MessageDigest { + type Error = AlgorithmError; + + fn try_from(hash_algorithm: HashAlgorithm) -> Result<Self, Self::Error> { + let name = hash_algorithm.openssl_name(); + MessageDigest::from_name(name).ok_or_else(|| { + AlgorithmError::UnsupportedHashingAlgorithm(format!( + "{name} (not supported by this OpenSSL build)" + )) + }) + } } #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] @@ -474,6 +491,17 @@ } #[test] + fn test_hash_to_message_digest() { + assert!(MessageDigest::try_from(HashAlgorithm::Sha1).is_ok()); + assert!(MessageDigest::try_from(HashAlgorithm::Sha256).is_ok()); + assert!(MessageDigest::try_from(HashAlgorithm::Sha384).is_ok()); + assert!(MessageDigest::try_from(HashAlgorithm::Sha512).is_ok()); + // SM3 may or may not be available depending on OpenSSL build; + // just verify it does not panic + let _ = MessageDigest::try_from(HashAlgorithm::Sm3_256); + } + + #[test] fn test_hash_to_hashing_algorithm() { let cases = [ (HashAlgorithm::Sha1, HashingAlgorithm::Sha1), diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rust-keylime-0.2.9+49/keylime/src/config/base.rs new/rust-keylime-0.2.10+12/keylime/src/config/base.rs --- old/rust-keylime-0.2.9+49/keylime/src/config/base.rs 2026-07-03 13:52:37.000000000 +0200 +++ new/rust-keylime-0.2.10+12/keylime/src/config/base.rs 2026-09-25 13:29:29.000000000 +0200 @@ -726,15 +726,15 @@ } "generate" => { let agent_uuid = Uuid::new_v4(); - info!("Generated a new UUID: {}", &agent_uuid); + info!("Generated a new UUID: {}", agent_uuid); agent_uuid.to_string() } uuid_config => match Uuid::parse_str(uuid_config) { Ok(uuid_config) => uuid_config.to_string(), Err(_) => { - warn!("Misformatted UUID: {}", &uuid_config); + warn!("Misformatted UUID: {}", uuid_config); let agent_uuid = Uuid::new_v4(); - info!("Using generated UUID: {}", &agent_uuid); + info!("Using generated UUID: {}", agent_uuid); agent_uuid.to_string() } }, diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rust-keylime-0.2.9+49/keylime/src/context_info.rs new/rust-keylime-0.2.10+12/keylime/src/context_info.rs --- old/rust-keylime-0.2.9+49/keylime/src/context_info.rs 2026-07-03 13:52:37.000000000 +0200 +++ new/rust-keylime-0.2.10+12/keylime/src/context_info.rs 2026-09-25 13:29:29.000000000 +0200 @@ -160,6 +160,16 @@ let tpm_hash_alg = config.tpm_hash_alg; let tpm_signing_alg = config.tpm_signing_alg; + if !tpm_hash_alg.is_supported() { + return Err(ContextInfoError::InvalidAlgorithm( + algorithms::AlgorithmError::UnsupportedHashingAlgorithm( + format!( + "{tpm_hash_alg} (not supported by this OpenSSL build)" + ), + ), + )); + } + let ek_result = tpm_context.create_ek(tpm_encryption_alg, None)?; let ek_handle = ek_result.key_handle; let ek_hash = hash_ek::hash_ek_pubkey(ek_result.public.clone()) @@ -372,7 +382,8 @@ let keylime_hash_alg: KeylimeInternalHashAlgorithm = name_h_alg_tss.try_into()?; let name_alg_id_value: u16 = name_h_alg_tss.into(); - let openssl_message_digest: MessageDigest = keylime_hash_alg.into(); + let openssl_message_digest: MessageDigest = + MessageDigest::try_from(keylime_hash_alg)?; let mut hasher = Hasher::new(openssl_message_digest)?; hasher.update(&marshalled_tpmt_public)?; let digest_bytes_vec = hasher.finish()?; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rust-keylime-0.2.9+49/keylime/src/ima/entry.rs new/rust-keylime-0.2.10+12/keylime/src/ima/entry.rs --- old/rust-keylime-0.2.9+49/keylime/src/ima/entry.rs 2026-07-03 13:52:37.000000000 +0200 +++ new/rust-keylime-0.2.10+12/keylime/src/ima/entry.rs 2026-09-25 13:29:29.000000000 +0200 @@ -32,7 +32,8 @@ impl Digest { /// Creates a new `Digest` with `algorithm` and `value`. pub fn new(algorithm: HashAlgorithm, value: &[u8]) -> Result<Self> { - let digest: MessageDigest = algorithm.into(); + let digest: MessageDigest = + MessageDigest::try_from(algorithm).map_err(Error::other)?; if value.len() != digest.size() { return Err(Error::new( ErrorKind::InvalidInput, @@ -54,22 +55,24 @@ /// Returns a pre-defined digest value used to indicate the start /// of the IMA measurement list. - pub fn start(algorithm: HashAlgorithm) -> Self { - let digest: MessageDigest = algorithm.into(); - Self { + pub fn start(algorithm: HashAlgorithm) -> Result<Self> { + let digest: MessageDigest = + MessageDigest::try_from(algorithm).map_err(Error::other)?; + Ok(Self { algorithm, value: vec![0x00u8; digest.size()], - } + }) } /// Returns a pre-defined digest value used to indicate the ToMToU /// error in the IMA measurement list. - pub fn ff(algorithm: HashAlgorithm) -> Self { - let digest: MessageDigest = algorithm.into(); - Self { + pub fn ff(algorithm: HashAlgorithm) -> Result<Self> { + let digest: MessageDigest = + MessageDigest::try_from(algorithm).map_err(Error::other)?; + Ok(Self { algorithm, value: vec![0xffu8; digest.size()], - } + }) } } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rust-keylime-0.2.9+49/keylime/src/registrar_client.rs new/rust-keylime-0.2.10+12/keylime/src/registrar_client.rs --- old/rust-keylime-0.2.9+49/keylime/src/registrar_client.rs 2026-07-03 13:52:37.000000000 +0200 +++ new/rust-keylime-0.2.10+12/keylime/src/registrar_client.rs 2026-09-25 13:29:29.000000000 +0200 @@ -441,7 +441,7 @@ info!( "Requesting agent registration from {} for {}", - &addr, &ai.uuid + addr, ai.uuid ); let resp = self @@ -596,10 +596,7 @@ "{scheme}://{registrar_ip}:{registrar_port}/v{api_version}/agents/{uuid}", ); - info!( - "Requesting agent activation from {} for {}", - &addr, &ai.uuid - ); + info!("Requesting agent activation from {} for {}", addr, ai.uuid); let resp = self .resilient_client diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rust-keylime-0.2.9+49/keylime/src/resilient_client.rs new/rust-keylime-0.2.10+12/keylime/src/resilient_client.rs --- old/rust-keylime-0.2.9+49/keylime/src/resilient_client.rs 2026-07-03 13:52:37.000000000 +0200 +++ new/rust-keylime-0.2.10+12/keylime/src/resilient_client.rs 2026-09-25 13:29:29.000000000 +0200 @@ -635,7 +635,7 @@ // Make a request. The test will pass only if the default header is sent correctly. let response = resilient_client .client - .get(format!("{}/test", &mock_server.uri())) + .get(format!("{}/test", mock_server.uri())) .send() .await .unwrap(); //#[allow_ci] @@ -663,7 +663,7 @@ let response = client .get_json_request_from_struct( Method::POST, - &format!("{}/submit", &mock_server.uri()), + &format!("{}/submit", mock_server.uri()), &json!({}), Some("application/vnd.api+json".to_string()), ) @@ -704,7 +704,7 @@ let response = client .get_json_request_from_struct( Method::POST, - &format!("{}/submit", &mock_server.uri()), + &format!("{}/submit", mock_server.uri()), &json!({}), None, ) @@ -740,7 +740,7 @@ let response = client .get_json_request_from_struct( Method::POST, - &format!("{}/submit", &mock_server.uri()), + &format!("{}/submit", mock_server.uri()), &json!({}), None, ) @@ -777,7 +777,7 @@ let response = client .get_json_request_from_struct( Method::POST, - &format!("{}/submit", &mock_server.uri()), + &format!("{}/submit", mock_server.uri()), &json!({}), None, ) @@ -860,7 +860,7 @@ let response = client .get_request( Method::GET, - &format!("{}/health", &mock_server.uri()), + &format!("{}/health", mock_server.uri()), ) .send() .await; @@ -906,7 +906,7 @@ let start_time = std::time::Instant::now(); let response = client - .get_request(Method::GET, &format!("{}/test", &mock_server.uri())) + .get_request(Method::GET, &format!("{}/test", mock_server.uri())) .send() .await .unwrap(); //#[allow_ci] @@ -957,7 +957,7 @@ let start_time = std::time::Instant::now(); let response = client - .get_request(Method::GET, &format!("{}/test", &mock_server.uri())) + .get_request(Method::GET, &format!("{}/test", mock_server.uri())) .send() .await .unwrap(); //#[allow_ci] @@ -995,7 +995,7 @@ let start_time = std::time::Instant::now(); let response = client - .get_request(Method::GET, &format!("{}/test", &mock_server.uri())) + .get_request(Method::GET, &format!("{}/test", mock_server.uri())) .send() .await .unwrap(); //#[allow_ci] @@ -1087,7 +1087,7 @@ let start_time = std::time::Instant::now(); let response = client - .get_request(Method::GET, &format!("{}/test", &mock_server.uri())) + .get_request(Method::GET, &format!("{}/test", mock_server.uri())) .send() .await .unwrap(); //#[allow_ci] diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rust-keylime-0.2.9+49/keylime/src/secure_mount.rs new/rust-keylime-0.2.10+12/keylime/src/secure_mount.rs --- old/rust-keylime-0.2.9+49/keylime/src/secure_mount.rs 2026-07-03 13:52:37.000000000 +0200 +++ new/rust-keylime-0.2.10+12/keylime/src/secure_mount.rs 2026-09-25 13:29:29.000000000 +0200 @@ -107,7 +107,7 @@ info!( "Mounting secure storage location {:?} on tmpfs.", - &secure_dir_path + secure_dir_path ); // mount tmpfs with secure directory diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rust-keylime-0.2.9+49/keylime/src/tpm.rs new/rust-keylime-0.2.10+12/keylime/src/tpm.rs --- old/rust-keylime-0.2.9+49/keylime/src/tpm.rs 2026-07-03 13:52:37.000000000 +0200 +++ new/rust-keylime-0.2.10+12/keylime/src/tpm.rs 2026-09-25 13:29:29.000000000 +0200 @@ -611,6 +611,34 @@ static TPM_CTX: OnceLock<Arc<Mutex<tss_esapi::Context>>> = OnceLock::new(); +// Some TPMs store the EK cert in NVRAM wrapped in an ASN.1 OCTET STRING +// (tag 0x04) instead of raw DER (tag 0x30). Unwrap it here so it can be +// parsed as a normal certificate. +fn unwrap_octet_string_ek_cert(cert: &[u8]) -> Vec<u8> { + if cert.first() != Some(&0x04) { + return cert.to_vec(); + } + + match picky_asn1_der::from_bytes::<serde_bytes::ByteBuf>(cert) { + Ok(inner) if inner.first() == Some(&0x30) => inner.into_vec(), + Ok(_) => { + warn!( + "EK certificate is OCTET-STRING-wrapped, but the inner \ + content does not start with a SEQUENCE tag; using it \ + as-is" + ); + cert.to_vec() + } + Err(e) => { + warn!( + "EK certificate looks OCTET-STRING-wrapped, but could \ + not be unwrapped: {e}" + ); + cert.to_vec() + } + } +} + impl Context<'_> { /// Creates a connection context. pub fn new() -> Result<Self> { @@ -722,13 +750,16 @@ }; let cert = match ek::retrieve_ek_pubcert(&mut ctx, alg.into()) { - Ok(cert) => match self.check_ek_cert(&cert) { - Ok(cert_checked) => Some(cert_checked), - Err(_) => { - warn!("EK certificate in TPM NVRAM is not ASN.1 DER encoded"); - Some(cert) + Ok(cert) => { + let cert = unwrap_octet_string_ek_cert(&cert); + match self.check_ek_cert(&cert) { + Ok(cert_checked) => Some(cert_checked), + Err(_) => { + warn!("EK certificate in TPM NVRAM is not ASN.1 DER encoded"); + Some(cert) // now uses the unwrapped cert + } } - }, + } Err(_) => { warn!("No EK certificate found in TPM NVRAM"); None @@ -1818,6 +1849,16 @@ } } } + usable_algs.retain(|alg| { + let supported = alg.is_supported(); + if !supported { + log::warn!( + "Hash algorithm {alg} supported by TPM but not by \ + this OpenSSL build; excluding" + ); + } + supported + }); Ok(usable_algs) } @@ -2329,14 +2370,14 @@ fn hash_alg_to_message_digest( hash_alg: HashingAlgorithm, ) -> Result<MessageDigest> { - match hash_alg { - HashingAlgorithm::Sha256 => Ok(MessageDigest::sha256()), - HashingAlgorithm::Sha1 => Ok(MessageDigest::sha1()), - HashingAlgorithm::Sha384 => Ok(MessageDigest::sha384()), - HashingAlgorithm::Sha512 => Ok(MessageDigest::sha512()), - HashingAlgorithm::Sm3_256 => Ok(MessageDigest::sm3()), - other => Err(TpmError::UnsupportedHashingAlgorithm { alg: other }), - } + let keylime_alg = KeylimeInternalHashAlgorithm::try_from(hash_alg) + .map_err(|_| TpmError::UnsupportedHashingAlgorithm { + alg: hash_alg, + })?; + MessageDigest::try_from(keylime_alg).map_err(|e| { + log::debug!("OpenSSL digest lookup failed for {hash_alg:?}: {e}"); + TpmError::UnsupportedHashingAlgorithm { alg: hash_alg } + }) } /// Check if the data attested in the quote matches the data read from the TPM PCRs @@ -3034,6 +3075,52 @@ } } + fn test_cert_der() -> Vec<u8> { + use std::path::Path; + + let cert_path = Path::new(env!("CARGO_MANIFEST_DIR")) + .join("test-data") + .join("test-cert.pem"); + let pem = + std::fs::read(cert_path).expect("unable to read test-cert.pem"); + X509::from_pem(&pem) + .expect("unable to parse test-cert.pem") + .to_der() + .expect("unable to re-encode test cert as DER") + } + + #[test] + fn test_unwrap_octet_string_ek_cert_wrapped() { + let cert_der = test_cert_der(); + assert_eq!(cert_der[0], 0x30); + + let wrapped = picky_asn1_der::to_vec(&serde_bytes::ByteBuf::from( + cert_der.clone(), + )) + .expect("failed to wrap cert in OCTET STRING"); + assert_eq!(wrapped[0], 0x04); + + assert_eq!(unwrap_octet_string_ek_cert(&wrapped), cert_der); + } + + #[test] + fn test_unwrap_octet_string_ek_cert_not_wrapped() { + let cert_der = test_cert_der(); + assert_eq!(unwrap_octet_string_ek_cert(&cert_der), cert_der); + } + + #[test] + fn test_unwrap_octet_string_ek_cert_invalid_inner() { + // wrapped, but inner content isn't a cert + let not_a_cert = b"not a certificate".to_vec(); + let wrapped = + picky_asn1_der::to_vec(&serde_bytes::ByteBuf::from(not_a_cert)) + .expect("failed to wrap non-cert data in OCTET STRING"); + assert_eq!(wrapped[0], 0x04); + + assert_eq!(unwrap_octet_string_ek_cert(&wrapped), wrapped); + } + #[test] #[cfg(feature = "testing")] fn test_quote_encode_decode() { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rust-keylime-0.2.9+49/keylime-agent/src/agent_handler.rs new/rust-keylime-0.2.10+12/keylime-agent/src/agent_handler.rs --- old/rust-keylime-0.2.9+49/keylime-agent/src/agent_handler.rs 2026-07-03 13:52:37.000000000 +0200 +++ new/rust-keylime-0.2.10+12/keylime-agent/src/agent_handler.rs 2026-09-25 13:29:29.000000000 +0200 @@ -46,23 +46,21 @@ /// Configure the endpoints for the /agent scope async fn agent_default(req: HttpRequest) -> impl Responder { - let error; - let response; - let message; - - match req.head().method { + let (error, message, response) = match req.head().method { http::Method::GET => { - error = 400; - message = "URI not supported, only /info is supported for GET in /agent interface"; - response = HttpResponse::BadRequest() + let error = 400; + let message = "URI not supported, only /info is supported for GET in /agent interface"; + let response = HttpResponse::BadRequest() .json(JsonWrapper::error(error, message)); + (error, message, response) } _ => { - error = 405; - message = "Method is not supported in /agent interface"; - response = HttpResponse::MethodNotAllowed() + let error = 405; + let message = "Method is not supported in /agent interface"; + let response = HttpResponse::MethodNotAllowed() .insert_header(http::header::Allow(vec![http::Method::GET])) .json(JsonWrapper::error(error, message)); + (error, message, response) } }; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rust-keylime-0.2.9+49/keylime-agent/src/api.rs new/rust-keylime-0.2.10+12/keylime-agent/src/api.rs --- old/rust-keylime-0.2.9+49/keylime-agent/src/api.rs 2026-07-03 13:52:37.000000000 +0200 +++ new/rust-keylime-0.2.10+12/keylime-agent/src/api.rs 2026-09-25 13:29:29.000000000 +0200 @@ -64,34 +64,33 @@ /// Handles the default case for the API version scope async fn api_default(req: HttpRequest) -> impl Responder { - let error; - let response; - let message; - - match req.head().method { + let (error, message, response) = match req.head().method { http::Method::GET => { - error = 400; - message = + let error = 400; + let message = "Not Implemented: Use /agent, /keys, or /quotes interfaces"; - response = HttpResponse::BadRequest() + let response = HttpResponse::BadRequest() .json(JsonWrapper::error(error, message)); + (error, message, response) } http::Method::POST => { - error = 400; - message = + let error = 400; + let message = "Not Implemented: Use /keys or /notifications interfaces"; - response = HttpResponse::BadRequest() + let response = HttpResponse::BadRequest() .json(JsonWrapper::error(error, message)); + (error, message, response) } _ => { - error = 405; - message = "Method is not supported"; - response = HttpResponse::MethodNotAllowed() + let error = 405; + let message = "Method is not supported"; + let response = HttpResponse::MethodNotAllowed() .insert_header(http::header::Allow(vec![ http::Method::GET, http::Method::POST, ])) .json(JsonWrapper::error(error, message)); + (error, message, response) } }; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rust-keylime-0.2.9+49/keylime-agent/src/errors_handler.rs new/rust-keylime-0.2.10+12/keylime-agent/src/errors_handler.rs --- old/rust-keylime-0.2.9+49/keylime-agent/src/errors_handler.rs 2026-07-03 13:52:37.000000000 +0200 +++ new/rust-keylime-0.2.10+12/keylime-agent/src/errors_handler.rs 2026-09-25 13:29:29.000000000 +0200 @@ -16,10 +16,6 @@ req: HttpRequest, quote_data: web::Data<QuoteData<'_>>, ) -> impl Responder { - let error; - let response; - let message; - let api_versions = quote_data .api_versions .iter() @@ -27,32 +23,35 @@ .collect::<Vec<_>>() .join(", "); - match req.head().method { + let (error, message, response) = match req.head().method { http::Method::GET => { - error = 400; - message = format!( + let error = 400; + let message = format!( "Not Implemented: Use {api_versions} or /version interfaces" ); - response = HttpResponse::BadRequest() + let response = HttpResponse::BadRequest() .json(JsonWrapper::error(error, &message)); + (error, message, response) } http::Method::POST => { - error = 400; - message = format!( + let error = 400; + let message = format!( "Not Implemented: Use {api_versions} or /version interfaces" ); - response = HttpResponse::BadRequest() + let response = HttpResponse::BadRequest() .json(JsonWrapper::error(error, &message)); + (error, message, response) } _ => { - error = 405; - message = "Method is not supported".to_string(); - response = HttpResponse::MethodNotAllowed() + let error = 405; + let message = "Method is not supported".to_string(); + let response = HttpResponse::MethodNotAllowed() .insert_header(http::header::Allow(vec![ http::Method::GET, http::Method::POST, ])) .json(JsonWrapper::error(error, &message)); + (error, message, response) } }; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rust-keylime-0.2.9+49/keylime-agent/src/keys_handler.rs new/rust-keylime-0.2.10+12/keylime-agent/src/keys_handler.rs --- old/rust-keylime-0.2.9+49/keylime-agent/src/keys_handler.rs 2026-07-03 13:52:37.000000000 +0200 +++ new/rust-keylime-0.2.10+12/keylime-agent/src/keys_handler.rs 2026-09-25 13:29:29.000000000 +0200 @@ -550,32 +550,31 @@ /// Handles the default case for the /keys scope async fn keys_default(req: HttpRequest) -> impl Responder { - let error; - let response; - let message; - - match req.head().method { + let (error, message, response) = match req.head().method { http::Method::GET => { - error = 400; - message = "URI not supported, only /pubkey and /verify are supported for GET in /keys interface"; - response = HttpResponse::BadRequest() + let error = 400; + let message = "URI not supported, only /pubkey and /verify are supported for GET in /keys interface"; + let response = HttpResponse::BadRequest() .json(JsonWrapper::error(error, message)); + (error, message, response) } http::Method::POST => { - error = 400; - message = "URI not supported, only /ukey and /vkey are supported for POST in /keys interface"; - response = HttpResponse::BadRequest() + let error = 400; + let message = "URI not supported, only /ukey and /vkey are supported for POST in /keys interface"; + let response = HttpResponse::BadRequest() .json(JsonWrapper::error(error, message)); + (error, message, response) } _ => { - error = 405; - message = "Method is not supported in /keys interface"; - response = HttpResponse::MethodNotAllowed() + let error = 405; + let message = "Method is not supported in /keys interface"; + let response = HttpResponse::MethodNotAllowed() .insert_header(http::header::Allow(vec![ http::Method::GET, http::Method::POST, ])) .json(JsonWrapper::error(error, message)); + (error, message, response) } }; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rust-keylime-0.2.9+49/keylime-agent/src/main.rs new/rust-keylime-0.2.10+12/keylime-agent/src/main.rs --- old/rust-keylime-0.2.9+49/keylime-agent/src/main.rs 2026-07-03 13:52:37.000000000 +0200 +++ new/rust-keylime-0.2.10+12/keylime-agent/src/main.rs 2026-09-25 13:29:29.000000000 +0200 @@ -66,7 +66,7 @@ }; use log::*; use openssl::{ - pkey::{PKey, Private, Public}, + pkey::{Id, PKey, Private, Public}, x509::X509, }; use std::{ @@ -118,6 +118,118 @@ } } +/// On upgrade from a pre-key-separation agent, the payload key file does not +/// exist yet. If the mTLS server key is RSA, copy it as the payload key so +/// that PCR 16 stays consistent with what the verifier has cached. +/// +/// Returns `Ok(())` when the copy succeeds or when a copy is not needed +/// (key already exists, server key missing, or server key is not RSA). +/// Returns `Err` if the server key is a valid RSA key but writing the +/// payload key file fails — continuing in that state could leave a +/// corrupt or insecure file behind. +fn ensure_payload_key( + payload_key_path: &Path, + payload_key_password: &str, + server_key_path: &Path, + server_key_password: &str, +) -> std::result::Result<(), Error> { + if payload_key_path.exists() { + return Ok(()); + } + if !server_key_path.exists() { + return Ok(()); + } + + match crypto::load_key_pair(server_key_path, Some(server_key_password)) { + Ok((_, priv_key)) + if priv_key.id() == Id::RSA && priv_key.bits() >= 2048 => + { + crypto::write_key_pair( + &priv_key, + payload_key_path, + Some(payload_key_password), + ) + .map_err(|e| { + if matches!(e, crypto::CryptoError::IOSetPermissionError(_)) { + // The key was written successfully but chmod failed. + // Remove the file so the next startup retries the copy + // rather than loading a key with insecure permissions. + // The file did not exist before this call (checked above), + // so removal cannot delete pre-existing user data. + match fs::remove_file(payload_key_path) { + Ok(()) => { + error!( + "Wrote mTLS key to {} but failed to set \ + file permissions: {e}. The file has been \ + removed; the copy will be retried on next \ + startup.", + payload_key_path.display() + ); + } + Err(rm_err) => { + error!( + "Wrote mTLS key to {} but failed to set \ + file permissions: {e}. Cleanup also failed: \ + {rm_err}. Remove the file manually before \ + restarting the agent to prevent loading a key \ + with insecure permissions.", + payload_key_path.display() + ); + } + } + } else { + error!( + "Failed to write mTLS key to {}: {e}. \ + If a partial file was left behind, remove \ + it manually before restarting the agent.", + payload_key_path.display() + ); + } + Error::Crypto(e) + })?; + + warn!( + "Payload key not found; wrote mTLS key from {} to {} \ + for backward compatibility. The same RSA key will \ + be used for both mTLS and payload encryption.", + server_key_path.display(), + payload_key_path.display() + ); + if !server_key_password.is_empty() + && payload_key_password.is_empty() + { + warn!( + "The mTLS key was encrypted at rest but the \ + payload key copy at {} is unencrypted. To \ + encrypt it, re-encrypt the file manually and \ + set 'payload_key_password' in the agent \ + configuration.", + payload_key_path.display() + ); + } + } + Ok(_) => { + warn!( + "mTLS key {} is not a supported RSA key (at least 2048 \ + bits); a new RSA payload key will be generated, which \ + will change PCR 16 and may cause attestation failures \ + until the agent is re-enrolled.", + server_key_path.display() + ); + } + Err(e) => { + warn!( + "Failed to load mTLS key {}: {e}. \ + A new payload key will be generated, which will change \ + PCR 16 and may cause attestation failures until the \ + agent is re-enrolled.", + server_key_path.display() + ); + } + } + Ok(()) +} + // This data is passed in to the actix httpserver threads that // handle quotes. #[derive(Debug)] @@ -254,7 +366,7 @@ { let message = "The agent mTLS is disabled and 'payload_script' is not empty. To allow the agent to run, 'enable_insecure_payload' has to be set to 'True'".to_string(); - error!("Configuration error: {}", &message); + error!("Configuration error: {}", message); return Err(Error::Configuration( config::KeylimeConfigError::Generic(message), )); @@ -284,7 +396,7 @@ if let Err(e) = permissions::run_as(user_group) { let message = "The user running the Keylime agent should be set in keylime-agent.conf, using the parameter `run_as`, with the format `user:group`".to_string(); - error!("Configuration error: {}", &message); + error!("Configuration error: {}", message); return Err(Error::Configuration( config::KeylimeConfigError::Generic(message), )); @@ -298,10 +410,7 @@ .map(|s| s.to_string()) .collect::<Vec<_>>(); - info!( - "Starting server with API versions: {}", - &config.api_versions - ); + info!("Starting server with API versions: {}", config.api_versions); let mut ctx = tpm::Context::new()?; @@ -500,11 +609,18 @@ // by the Tenant and Cloud Verifier, respectively. // The payload key is always persistent, stored at the configured path. let key_path = Path::new(&config.payload_key); + ensure_payload_key( + key_path, + config.payload_key_password.as_ref(), + Path::new(&config.server_key), + config.server_key_password.as_ref(), + )?; + let (payload_pub_key, payload_priv_key) = crypto::load_or_generate_key( key_path, Some(config.payload_key_password.as_ref()), keylime::algorithms::EncryptionAlgorithm::Rsa2048, - true, // Validate that loaded keys are RSA 2048 + false, // Don't validate key size (accept any RSA for backward compatibility) ) .map_err(|e| { error!( @@ -517,6 +633,25 @@ ))) })?; + if payload_priv_key.id() != Id::RSA || payload_priv_key.bits() < 2048 { + error!( + "Payload key {} must be an RSA key of at least 2048 bits, \ + found {:?} with {} bits", + key_path.display(), + payload_priv_key.id(), + payload_priv_key.bits() + ); + return Err(Error::Configuration( + config::KeylimeConfigError::Generic(format!( + "Payload key {} must be an RSA key of at least 2048 \ + bits, found {:?} with {} bits", + key_path.display(), + payload_priv_key.id(), + payload_priv_key.bits() + )), + )); + } + // Load or generate mTLS key pair (separate from payload keys) // The mTLS key is always persistent, stored at the configured path. // Uses ECC P-256 by default for better security and performance @@ -769,6 +904,10 @@ ) .default_service(web::to(errors_handler::app_default)) }) + // Limit the number of spawned workers, see: + // https://github.com/keylime/rust-keylime/issues/1276 + // for details + .workers(4) // Disable default signal handlers. See: // https://github.com/actix/actix-web/issues/2739 // for details. @@ -1186,4 +1325,201 @@ Some(config::DEFAULT_EXP_BACKOFF_MAX_DELAY as u64) ); } + + mod ensure_payload_key_tests { + use super::*; + use openssl::ec::{EcGroup, EcKey}; + use openssl::nid::Nid; + use openssl::pkey::PKey; + use openssl::rsa::Rsa; + use tempfile::TempDir; + + type Result<T> = std::result::Result<T, Box<dyn std::error::Error>>; + + fn write_rsa_key( + path: &Path, + bits: u32, + password: &str, + ) -> Result<()> { + let rsa = Rsa::generate(bits)?; + let key = PKey::from_rsa(rsa)?; + crypto::write_key_pair(&key, path, Some(password))?; + Ok(()) + } + + fn write_ec_key(path: &Path, password: &str) -> Result<()> { + let group = EcGroup::from_curve_name(Nid::X9_62_PRIME256V1)?; + let ec = EcKey::generate(&group)?; + let key = PKey::from_ec_key(ec)?; + crypto::write_key_pair(&key, path, Some(password))?; + Ok(()) + } + + #[test] + fn copies_rsa2048_server_key_as_payload_key() -> Result<()> { + let dir = TempDir::new()?; + let server = dir.path().join("server-private.pem"); + let payload = dir.path().join("payload-private.pem"); + + write_rsa_key(&server, 2048, "")?; + + ensure_payload_key(&payload, "", &server, "")?; + + assert!(payload.exists()); + let (_, loaded) = crypto::load_key_pair(&payload, Some(""))?; + assert_eq!(loaded.id(), Id::RSA); + assert_eq!(loaded.bits(), 2048); + Ok(()) + } + + #[test] + fn copies_rsa4096_server_key_as_payload_key() -> Result<()> { + let dir = TempDir::new()?; + let server = dir.path().join("server-private.pem"); + let payload = dir.path().join("payload-private.pem"); + + write_rsa_key(&server, 4096, "")?; + + ensure_payload_key(&payload, "", &server, "")?; + + assert!(payload.exists()); + let (_, loaded) = crypto::load_key_pair(&payload, Some(""))?; + assert_eq!(loaded.id(), Id::RSA); + assert_eq!(loaded.bits(), 4096); + Ok(()) + } + + #[test] + fn skips_when_payload_key_already_exists() -> Result<()> { + let dir = TempDir::new()?; + let server = dir.path().join("server-private.pem"); + let payload = dir.path().join("payload-private.pem"); + + write_rsa_key(&server, 2048, "")?; + write_rsa_key(&payload, 2048, "")?; + + let before = fs::read(&payload)?; + ensure_payload_key(&payload, "", &server, "")?; + let after = fs::read(&payload)?; + + assert_eq!(before, after); + Ok(()) + } + + #[test] + fn skips_when_no_server_key() -> Result<()> { + let dir = TempDir::new()?; + let server = dir.path().join("server-private.pem"); + let payload = dir.path().join("payload-private.pem"); + + ensure_payload_key(&payload, "", &server, "")?; + + assert!(!payload.exists()); + Ok(()) + } + + #[test] + fn skips_non_rsa_server_key() -> Result<()> { + let dir = TempDir::new()?; + let server = dir.path().join("server-private.pem"); + let payload = dir.path().join("payload-private.pem"); + + write_ec_key(&server, "")?; + + ensure_payload_key(&payload, "", &server, "")?; + + assert!(!payload.exists()); + Ok(()) + } + + #[test] + fn skips_rsa_key_below_minimum_size() -> Result<()> { + let dir = TempDir::new()?; + let server = dir.path().join("server-private.pem"); + let payload = dir.path().join("payload-private.pem"); + + write_rsa_key(&server, 1024, "")?; + + ensure_payload_key(&payload, "", &server, "")?; + + assert!(!payload.exists()); + Ok(()) + } + + #[test] + fn preserves_key_material_on_copy() -> Result<()> { + let dir = TempDir::new()?; + let server = dir.path().join("server-private.pem"); + let payload = dir.path().join("payload-private.pem"); + + write_rsa_key(&server, 2048, "")?; + let (_, original) = crypto::load_key_pair(&server, Some(""))?; + + ensure_payload_key(&payload, "", &server, "")?; + + let (_, copied) = crypto::load_key_pair(&payload, Some(""))?; + assert_eq!( + original.private_key_to_pem_pkcs8()?, + copied.private_key_to_pem_pkcs8()?, + ); + Ok(()) + } + + #[test] + fn handles_password_mismatch_gracefully() -> Result<()> { + let dir = TempDir::new()?; + let server = dir.path().join("server-private.pem"); + let payload = dir.path().join("payload-private.pem"); + + write_rsa_key(&server, 2048, "server-pass")?; + + ensure_payload_key(&payload, "payload-pass", &server, "wrong")?; + + assert!(!payload.exists()); + Ok(()) + } + + #[test] + fn copies_with_different_passwords() -> Result<()> { + let dir = TempDir::new()?; + let server = dir.path().join("server-private.pem"); + let payload = dir.path().join("payload-private.pem"); + + write_rsa_key(&server, 2048, "server-pass")?; + let (_, original) = + crypto::load_key_pair(&server, Some("server-pass"))?; + + ensure_payload_key( + &payload, + "payload-pass", + &server, + "server-pass", + )?; + + let (_, copied) = + crypto::load_key_pair(&payload, Some("payload-pass"))?; + assert_eq!( + original.private_key_to_pem_pkcs8()?, + copied.private_key_to_pem_pkcs8()?, + ); + Ok(()) + } + + #[test] + fn returns_err_when_write_fails() -> Result<()> { + let dir = TempDir::new()?; + let server = dir.path().join("server-private.pem"); + // Nonexistent parent directory forces FSCreateError in write_key_pair. + let payload = dir + .path() + .join("nonexistent-subdir") + .join("payload-private.pem"); + + write_rsa_key(&server, 2048, "")?; + + assert!(ensure_payload_key(&payload, "", &server, "").is_err()); + assert!(!payload.exists()); + Ok(()) + } + } } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rust-keylime-0.2.9+49/keylime-agent/src/notifications_handler.rs new/rust-keylime-0.2.10+12/keylime-agent/src/notifications_handler.rs --- old/rust-keylime-0.2.9+49/keylime-agent/src/notifications_handler.rs 2026-07-03 13:52:37.000000000 +0200 +++ new/rust-keylime-0.2.10+12/keylime-agent/src/notifications_handler.rs 2026-09-25 13:29:29.000000000 +0200 @@ -36,23 +36,22 @@ } async fn notifications_default(req: HttpRequest) -> impl Responder { - let error; - let response; - let message; - - match req.head().method { + let (error, message, response) = match req.head().method { http::Method::POST => { - error = 400; - message = "URI not supported, only /revocation is supported for POST in /notifications/ interface"; - response = HttpResponse::BadRequest() + let error = 400; + let message = "URI not supported, only /revocation is supported for POST in /notifications/ interface"; + let response = HttpResponse::BadRequest() .json(JsonWrapper::error(error, message)); + (error, message, response) } _ => { - error = 405; - message = "Method is not supported in /notifications/ interface"; - response = HttpResponse::MethodNotAllowed() + let error = 405; + let message = + "Method is not supported in /notifications/ interface"; + let response = HttpResponse::MethodNotAllowed() .insert_header(http::header::Allow(vec![http::Method::POST])) .json(JsonWrapper::error(error, message)); + (error, message, response) } }; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rust-keylime-0.2.9+49/keylime-agent/src/payloads.rs new/rust-keylime-0.2.10+12/keylime-agent/src/payloads.rs --- old/rust-keylime-0.2.9+49/keylime-agent/src/payloads.rs 2026-07-03 13:52:37.000000000 +0200 +++ new/rust-keylime-0.2.10+12/keylime-agent/src/payloads.rs 2026-09-25 13:29:29.000000000 +0200 @@ -143,7 +143,7 @@ { return Err(Error::Other(format!( "unable to set {:?} as executable", - &script_path + script_path ))); } @@ -159,12 +159,12 @@ .status() { Ok(_) => { - info!("{:?} ran successfully", &script_path); + info!("{:?} ran successfully", script_path); Ok(()) } Err(e) => Err(Error::Other(format!( "{:?} failed during run: {}", - &script_path, e + script_path, e ))), } } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rust-keylime-0.2.9+49/keylime-agent/src/quotes_handler.rs new/rust-keylime-0.2.10+12/keylime-agent/src/quotes_handler.rs --- old/rust-keylime-0.2.9+49/keylime-agent/src/quotes_handler.rs 2026-07-03 13:52:37.000000000 +0200 +++ new/rust-keylime-0.2.10+12/keylime-agent/src/quotes_handler.rs 2026-09-25 13:29:29.000000000 +0200 @@ -330,23 +330,21 @@ /// Handles the default case for the /quotes scope async fn quotes_default(req: HttpRequest) -> impl Responder { - let error; - let response; - let message; - - match req.head().method { + let (error, message, response) = match req.head().method { http::Method::GET => { - error = 400; - message = "URI not supported, only /identity and /integrity are supported for GET in /quotes/ interface"; - response = HttpResponse::BadRequest() + let error = 400; + let message = "URI not supported, only /identity and /integrity are supported for GET in /quotes/ interface"; + let response = HttpResponse::BadRequest() .json(JsonWrapper::error(error, message)); + (error, message, response) } _ => { - error = 405; - message = "Method is not supported in /quotes/ interface"; - response = HttpResponse::MethodNotAllowed() + let error = 405; + let message = "Method is not supported in /quotes/ interface"; + let response = HttpResponse::MethodNotAllowed() .insert_header(http::header::Allow(vec![http::Method::GET])) .json(JsonWrapper::error(error, message)); + (error, message, response) } }; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rust-keylime-0.2.9+49/keylime-agent/src/revocation.rs new/rust-keylime-0.2.10+12/keylime-agent/src/revocation.rs --- old/rust-keylime-0.2.9+49/keylime-agent/src/revocation.rs 2026-07-03 13:52:37.000000000 +0200 +++ new/rust-keylime-0.2.10+12/keylime-agent/src/revocation.rs 2026-09-25 13:29:29.000000000 +0200 @@ -64,7 +64,7 @@ if !py_action.set_extension("py") { return Err(Error::Other(format!( "unable to set action {} extension", - &action + action ))); } @@ -655,7 +655,7 @@ if #[cfg(feature = "legacy-python-actions")] { // Test local python action let expected = - format!("{}", &actions_dir.join("shim.py").display(),); + format!("{}", actions_dir.join("shim.py").display(),); assert_eq!( lookup_action( @@ -673,7 +673,7 @@ // Test local non-python action let expected = format!( "{}", - &actions_dir.join("local_action_hello_shell.sh").display() + actions_dir.join("local_action_hello_shell.sh").display() ); assert_eq!( @@ -691,7 +691,7 @@ if #[cfg(feature = "legacy-python-actions")] { // Test payload python action let expected = - format!("{}", &actions_dir.join("shim.py").display(),); + format!("{}", actions_dir.join("shim.py").display(),); assert_eq!( lookup_action( @@ -709,7 +709,7 @@ // Test payload non-python action let expected = format!( "{}", - &payload_dir.join("local_action_payload_shell.sh").display() + payload_dir.join("local_action_payload_shell.sh").display() ); assert_eq!( diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rust-keylime-0.2.9+49/keylime-ima-emulator/src/main.rs new/rust-keylime-0.2.10+12/keylime-ima-emulator/src/main.rs --- old/rust-keylime-0.2.9+49/keylime-ima-emulator/src/main.rs 2026-07-03 13:52:37.000000000 +0200 +++ new/rust-keylime-0.2.10+12/keylime-ima-emulator/src/main.rs 2026-09-25 13:29:29.000000000 +0200 @@ -59,15 +59,20 @@ ima_hash_alg: HashAlgorithm, pcr_hash_alg: HashAlgorithm, search_hash: Option<&Digest>, + shutdown: &AtomicBool, ) -> Result<usize> { let f = File::open(ml)?; let mut reader = BufReader::new(f); - let ima_digest: MessageDigest = ima_hash_alg.into(); - let ima_start_hash = ima::Digest::start(ima_hash_alg); - let pcr_digest: MessageDigest = pcr_hash_alg.into(); - let mut running_hash = ima::Digest::start(pcr_hash_alg); - let ff_hash = ima::Digest::ff(pcr_hash_alg); + let ima_digest: MessageDigest = MessageDigest::try_from(ima_hash_alg)?; + let ima_start_hash = ima::Digest::start(ima_hash_alg)?; + let pcr_digest: MessageDigest = MessageDigest::try_from(pcr_hash_alg)?; + let mut running_hash = ima::Digest::start(pcr_hash_alg)?; + let ff_hash = ima::Digest::ff(pcr_hash_alg)?; for line in reader.by_ref().lines().skip(position) { + if shutdown.load(Ordering::SeqCst) { + println!("Shutdown requested, interrupting IMA measurement list replay"); + break; + } let line = line?; if line.is_empty() { continue; @@ -129,7 +134,7 @@ } } - if search_hash.is_some() { + if search_hash.is_some() && !shutdown.load(Ordering::SeqCst) { return Err(ImaEmulatorError::Other( "Unable to find current measurement list position, Resetting the TPM emulator may be neccesary".to_string())); } @@ -176,6 +181,10 @@ positions.insert(pcr_hash_alg, 0usize); } + let shutdown_marker = Arc::new(AtomicBool::new(false)); + signal_hook::flag::register(SIGINT, Arc::clone(&shutdown_marker))?; + signal_hook::flag::register(SIGTERM, Arc::clone(&shutdown_marker))?; + for (pcr_hash_alg, position) in positions.iter_mut() { // check if pcr is clean let pcr_list = PcrSelectionListBuilder::new() @@ -198,7 +207,8 @@ ) })?; - let pcr_digest: MessageDigest = (*pcr_hash_alg).into(); + let pcr_digest: MessageDigest = + MessageDigest::try_from(*pcr_hash_alg)?; let pcr_start_hash = vec![0x00u8; pcr_digest.size()]; if digest.value() != pcr_start_hash { log::warn!("IMA PCR is not empty, trying to find the last updated file in the measurement list..."); @@ -209,13 +219,11 @@ ima_hash_alg, *pcr_hash_alg, Some(digest), + &shutdown_marker, )?; } } - let shutdown_marker = Arc::new(AtomicBool::new(false)); - signal_hook::flag::register(SIGINT, Arc::clone(&shutdown_marker))?; - signal_hook::flag::register(SIGTERM, Arc::clone(&shutdown_marker))?; println!("Monitoring {}", args.ima_log.display()); while !shutdown_marker.load(Ordering::SeqCst) { for (pcr_hash_alg, position) in positions.iter_mut() { @@ -226,6 +234,7 @@ ima_hash_alg, *pcr_hash_alg, None, + &shutdown_marker, ).expect("Error extending position {position} on PCR bank {pcr_hash_alg}"); } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rust-keylime-0.2.9+49/keylime-push-model-agent/src/main.rs new/rust-keylime-0.2.10+12/keylime-push-model-agent/src/main.rs --- old/rust-keylime-0.2.9+49/keylime-push-model-agent/src/main.rs 2026-07-03 13:52:37.000000000 +0200 +++ new/rust-keylime-0.2.10+12/keylime-push-model-agent/src/main.rs 2026-09-25 13:29:29.000000000 +0200 @@ -192,6 +192,7 @@ } } }; + info!("Agent UUID: {agent_identifier}"); let verifier_url = match args.verifier_url { Some(ref url) => url.clone(), _ => config.verifier_url().to_string(), diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rust-keylime-0.2.9+49/rpm/centos/keylime-agent-rust.spec new/rust-keylime-0.2.10+12/rpm/centos/keylime-agent-rust.spec --- old/rust-keylime-0.2.9+49/rpm/centos/keylime-agent-rust.spec 2026-07-03 13:52:37.000000000 +0200 +++ new/rust-keylime-0.2.10+12/rpm/centos/keylime-agent-rust.spec 2026-09-25 13:29:29.000000000 +0200 @@ -9,7 +9,7 @@ %global __brp_mangle_shebangs_exclude_from ^/usr/src/debug/.*$ Name: keylime-agent-rust -Version: 0.2.9 +Version: 0.2.10 Release: %{?autorelease}%{!?autorelease:1%{?dist}} Summary: Rust agent for Keylime diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rust-keylime-0.2.9+49/rpm/fedora/keylime-agent-rust.spec new/rust-keylime-0.2.10+12/rpm/fedora/keylime-agent-rust.spec --- old/rust-keylime-0.2.9+49/rpm/fedora/keylime-agent-rust.spec 2026-07-03 13:52:37.000000000 +0200 +++ new/rust-keylime-0.2.10+12/rpm/fedora/keylime-agent-rust.spec 2026-09-25 13:29:29.000000000 +0200 @@ -10,7 +10,7 @@ %global __brp_mangle_shebangs_exclude_from ^/usr/src/debug/.*$ Name: keylime-agent-rust -Version: 0.2.9 +Version: 0.2.10 Release: %{?autorelease}%{!?autorelease:1%{?dist}} Summary: Rust agent for Keylime ++++++ rust-keylime.obsinfo ++++++ --- /var/tmp/diff_new_pack.i4q2m4/_old 2026-10-28 17:44:52.630878131 +0100 +++ /var/tmp/diff_new_pack.i4q2m4/_new 2026-10-28 17:44:52.638878463 +0100 @@ -1,5 +1,5 @@ name: rust-keylime -version: 0.2.9+49 -mtime: 1783079557 -commit: 28a2e1dec001b29ea417ea152bfbd9a8b06f6e14 +version: 0.2.10+12 +mtime: 1790335769 +commit: 54c62facf656a499a21809486c300d52572444d7 ++++++ vendor.tar.zst ++++++ /work/SRC/openSUSE:Factory/rust-keylime/vendor.tar.zst /work/SRC/openSUSE:Factory/.rust-keylime.new.2252680/vendor.tar.zst differ: char 1, line 1
