From 692af4058c04dd925b6f791f7262108cd791dbdd Mon Sep 17 00:00:00 2001 From: Marc Hartmayer Date: Fri, 3 Jul 2026 14:36:16 +0200 Subject: [PATCH] pv: Rewrite CRL download tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace the existing mocks with a trait-based test infrastructure. This allows testing download_first_crl_from_x509() functionally while avoiding actual network access, resulting in more comprehensive and realistic test coverage. Assisted-by: IBM Bob:1.0.5 Signed-off-by: Marc Hartmayer Acked-by: Steffen Eiden Signed-off-by: Jan Höppner --- rust/pv/src/error.rs | 2 + rust/pv/src/verify.rs | 3 - rust/pv/src/verify/helper.rs | 184 +++++++++++++++++++++++++++++++++++ rust/pv/src/verify/test.rs | 24 ----- 4 files changed, 186 insertions(+), 27 deletions(-) diff --git a/rust/pv/src/error.rs b/rust/pv/src/error.rs index 5ae0ce69..1cf2be88 100644 --- a/rust/pv/src/error.rs +++ b/rust/pv/src/error.rs @@ -175,6 +175,8 @@ pub enum HkdVerifyErrorType { NoCrlDP, #[error("The IBM Z signing key could not be verified. Error occurred at level {1}")] IbmSignInvalid(#[source] openssl::x509::X509VerifyResult, u32), + #[error("CRL download failed")] + CrlDownloadFailed, } macro_rules! bail_hkd_verify { diff --git a/rust/pv/src/verify.rs b/rust/pv/src/verify.rs index a865e85c..aef66c25 100644 --- a/rust/pv/src/verify.rs +++ b/rust/pv/src/verify.rs @@ -5,15 +5,12 @@ use core::slice; use std::path::Path; -#[cfg(not(test))] use helper::download_first_crl_from_x509; use log::{debug, trace}; use openssl::error::ErrorStack; use openssl::stack::Stack; use openssl::x509::store::X509Store; use openssl::x509::{CrlStatus, X509NameRef, X509Ref, X509StoreContext, X509StoreContextRef, X509}; -#[cfg(test)] -use test::download_first_crl_from_x509; use crate::error::bail_hkd_verify; use crate::misc::{read_certs, read_file}; diff --git a/rust/pv/src/verify/helper.rs b/rust/pv/src/verify/helper.rs index 6828c0fd..90105107 100644 --- a/rust/pv/src/verify/helper.rs +++ b/rust/pv/src/verify/helper.rs @@ -22,6 +22,8 @@ use openssl::x509::{ }; #[cfg(not(test))] pub(crate) use prod_client::download_first_crl_from_x509; +#[cfg(test)] +pub(crate) use tests::download_first_crl_from_x509; use crate::error::bail_hkd_verify; use crate::openssl_extensions::{AkidCheckResult, AkidExtension}; @@ -545,4 +547,186 @@ mod tests { )); assert!(super::get_ibm_z_sign_key(&[ibm_crt, no_sign_crt]).is_ok(),); } + + use std::collections::HashMap; + + use openssl::bn::{BigNum, MsbOption}; + use openssl::hash::MessageDigest; + use openssl::pkey::PKey; + use openssl::rsa::Rsa; + use openssl::x509::{X509Builder, X509Crl, X509Extension, X509NameBuilder}; + + use crate::test_utils::get_cert_asset_path; + + /// Mock HTTP response for testing + pub struct MockResponse { + pub data: Vec, + pub should_fail: bool, + } + + /// Mock HTTP client for testing + pub struct MockHttpClient { + url: String, + responses: HashMap, + perform_count: usize, + } + + impl MockHttpClient { + pub fn new(responses: HashMap) -> Self { + Self { + url: String::new(), + responses, + perform_count: 0, + } + } + } + + impl HttpClient for MockHttpClient { + fn url(&mut self, url: &str) -> Result<()> { + self.url = url.to_string(); + Ok(()) + } + + fn get(&mut self, _enable: bool) -> Result<()> { + Ok(()) + } + + fn follow_location(&mut self, _enable: bool) -> Result<()> { + Ok(()) + } + + fn timeout(&mut self, _timeout: Duration) -> Result<()> { + Ok(()) + } + + fn useragent(&mut self, _agent: &str) -> Result<()> { + Ok(()) + } + + fn perform(&mut self) -> Result<()> { + self.perform_count += 1; + if let Some(response) = self.responses.get(&self.url) { + if response.should_fail { + bail_hkd_verify!(CrlDownloadFailed); + } + Ok(()) + } else { + bail_hkd_verify!(CrlDownloadFailed); + } + } + + fn get_ref(&self) -> &[u8] { + if let Some(response) = self.responses.get(&self.url) { + &response.data + } else { + &[] + } + } + } + + /// Helper to create mock response with data + fn mock_response(data: Vec) -> MockResponse { + MockResponse { + data, + should_fail: false, + } + } + + /// Helper to create mock failure response + fn mock_failure() -> MockResponse { + MockResponse { + data: vec![], + should_fail: true, + } + } + + /// Helper to create test certificate with CRL distribution points + fn create_cert_with_crl_dps(crl_uris: &[&str]) -> X509 { + // Generate a key pair + let rsa = Rsa::generate(2048).unwrap(); + let key_pair = PKey::from_rsa(rsa).unwrap(); + + let mut builder = X509Builder::new().unwrap(); + builder.set_version(2).unwrap(); + + // Set serial number + let serial = { + let mut num = BigNum::new().unwrap(); + num.rand(159, MsbOption::MAYBE_ZERO, false).unwrap(); + num.to_asn1_integer().unwrap() + }; + builder.set_serial_number(&serial).unwrap(); + + // Set subject name + let mut name_builder = X509NameBuilder::new().unwrap(); + name_builder.append_entry_by_text("C", "US").unwrap(); + name_builder + .append_entry_by_text("O", "Test Organization") + .unwrap(); + name_builder + .append_entry_by_text("CN", "Test Certificate") + .unwrap(); + let name = name_builder.build(); + + builder.set_subject_name(&name).unwrap(); + builder.set_issuer_name(&name).unwrap(); + builder.set_pubkey(&key_pair).unwrap(); + + // Set validity + builder + .set_not_before(&Asn1Time::days_from_now(0).unwrap()) + .unwrap(); + builder + .set_not_after(&Asn1Time::days_from_now(365).unwrap()) + .unwrap(); + + // Add CRL Distribution Points if provided + if !crl_uris.is_empty() { + // Create a single extension with all URIs + let crl_dp_value = crl_uris + .iter() + .map(|uri| format!("URI:{}", uri)) + .collect::>() + .join(","); + #[allow(deprecated)] + let crl_ext = + X509Extension::new_nid(None, None, Nid::CRL_DISTRIBUTION_POINTS, &crl_dp_value) + .unwrap(); + builder.append_extension(crl_ext).unwrap(); + } + + // Sign the certificate + builder.sign(&key_pair, MessageDigest::sha256()).unwrap(); + + builder.build() + } + + /// Helper to test with mock client + fn download_with_mock( + cert: &X509Ref, + responses: HashMap, + ) -> Result>> { + download_first_crl_from_x509_impl(cert, MockHttpClient::new(responses)) + } + + // Mock function + pub(crate) fn download_first_crl_from_x509(cert: &X509Ref) -> Result>> { + use std::collections::HashMap; + + let dist_points = x509_dist_points(cert); + + // Build mock responses for each distribution point + let mut responses = HashMap::new(); + for dist_point in dist_points { + // Treat distribution point as filename + let path = get_cert_asset_path(&dist_point); + + if let Ok(crl_data) = std::fs::read(&path) { + responses.insert(dist_point, mock_response(crl_data)); + } + // If file doesn't exist, skip this distribution point + } + + download_with_mock(cert, responses) + } } diff --git a/rust/pv/src/verify/test.rs b/rust/pv/src/verify/test.rs index 74c760f5..6d7a5938 100644 --- a/rust/pv/src/verify/test.rs +++ b/rust/pv/src/verify/test.rs @@ -4,38 +4,14 @@ #![cfg(test)] -use std::path::Path; - use openssl::stack::Stack; -use openssl::x509::X509Crl; use super::helper::*; use super::{helper, *}; use crate::test_utils::*; -use crate::utils::read_crls; use crate::Error; use crate::HkdVerifyErrorType::*; -// Mock function -pub fn download_first_crl_from_x509(cert: &X509Ref) -> Result>> { - fn mock_download>(path: P) -> Result> { - read_crls(std::fs::read(path)?) - } - - for dist_point in x509_dist_points(cert) { - { - let path = get_cert_asset_path(&dist_point); - let crls = if let Ok(buf) = mock_download(&path) { - buf - } else { - continue; - }; - return Ok(Some(crls)); - } - } - Ok(None) -} - #[test] fn store_setup() { let ibm_path = get_cert_asset_path("ibm.crt");