diff --git a/e2e-identity/tests/e2e.rs b/e2e-identity/tests/e2e.rs index d6a322ea9f..0267c74456 100644 --- a/e2e-identity/tests/e2e.rs +++ b/e2e-identity/tests/e2e.rs @@ -23,7 +23,11 @@ #![cfg(not(target_os = "unknown"))] -use std::{collections::HashMap, net::SocketAddr, sync::Arc}; +use std::{ + collections::{HashMap, HashSet}, + net::SocketAddr, + sync::Arc, +}; use core_crypto_keystore::{ConnectionType, Database, DatabaseKey}; use jwt_simple::prelude::*; @@ -37,7 +41,11 @@ use utils::{ rand_client_id, rand_str, stepca::CaCfg, }; -use wire_e2e_identity::{X509CredentialAcquisition, acquisition::X509CredentialConfiguration, pki_env::PkiEnvironment}; +use wire_e2e_identity::{ + X509CredentialAcquisition, acquisition::X509CredentialConfiguration, pki_env::PkiEnvironment, + x509_check::extract_crl_uris, +}; +use x509_cert::{crl::CertificateList, der::Decode as _}; #[path = "utils/mod.rs"] mod utils; @@ -246,6 +254,53 @@ async fn x509_cert_acquisition_works(test_env: TestEnvironment, #[case] sign_alg .unwrap(); } +#[tokio::test] +#[rstest] +#[case(JwsAlgorithm::P256)] +#[case(JwsAlgorithm::P384)] +#[case(JwsAlgorithm::P521)] +#[case(JwsAlgorithm::Ed25519)] +async fn fetching_crls_works(test_env: TestEnvironment, #[case] sign_alg: JwsAlgorithm) { + let (pki_env, config) = prepare_pki_env_and_config(&test_env, sign_alg).await; + let acq = X509CredentialAcquisition::try_new(Arc::new(pki_env.clone()), config).unwrap(); + let (_sign_kp, certs) = acq + .complete_dpop_challenge() + .await + .unwrap() + .complete_oidc_challenge() + .await + .unwrap(); + + let crl_uris: HashSet = certs + .iter() + .map(|cert| x509_cert::Certificate::from_der(cert).expect("certificate in chain parses")) + .filter_map(|cert| extract_crl_uris(&cert).expect("CRL distribution points can be extracted")) + .flatten() + .collect(); + + assert!( + !crl_uris.is_empty(), + "issued certificate chain should advertise at least one CRL" + ); + + let result = pki_env + .fetch_crls(crl_uris.iter().map(String::as_str)) + .await + .expect("fetched CRL URLs"); + + assert_eq!(result.len(), crl_uris.len(), "each advertised CRL should be fetched"); + assert_eq!( + result.keys().cloned().collect::>(), + crl_uris, + "fetched CRLs should match the advertised distribution points", + ); + + for crl_der in result.values() { + assert!(!crl_der.is_empty(), "fetched CRL should not be empty"); + let _ = CertificateList::from_der(crl_der).expect("fetched body is a valid DER CRL"); + } +} + // @SF.PROVISIONING @TSFI.ACME // TODO: ignore this test for now, until the relevant PKI environment checks are in place #[ignore] diff --git a/e2e-identity/tests/utils/hooks.rs b/e2e-identity/tests/utils/hooks.rs index 97b44aa027..be0ba0a2d0 100644 --- a/e2e-identity/tests/utils/hooks.rs +++ b/e2e-identity/tests/utils/hooks.rs @@ -6,7 +6,8 @@ use wire_e2e_identity::pki_env::hooks::{ }; use crate::utils::{ - OauthCfg, WireServer, default_http_client, + OauthCfg, WireServer, + ctx::ctx_get_http_client_builder, idp::{IdpServer, OidcProvider, fetch_id_token}, stepca::AcmeServer, }; @@ -30,7 +31,7 @@ impl PkiEnvironmentHooks for TestPkiEnvironmentHooks { mut headers: Vec, body: Vec, ) -> Result { - let client = default_http_client() + let client = ctx_get_http_client_builder() .add_root_certificate(self.acme.ca_cert.clone()) .build() .unwrap(); diff --git a/e2e-identity/tests/utils/stepca.rs b/e2e-identity/tests/utils/stepca.rs index 9f9ae4fc66..5d68fa1bc2 100644 --- a/e2e-identity/tests/utils/stepca.rs +++ b/e2e-identity/tests/utils/stepca.rs @@ -104,24 +104,30 @@ fn generate_authority_config(cfg: &CaCfg) -> serde_json::Value { }) } -const INTERMEDIATE_CERT_TEMPLATE: &str = r#" - { - "subject": "Wire Intermediate CA", - "keyUsage": ["certSign", "crlSign"], - "basicConstraints": { - "isCA": true, - "maxPathLen": 0 - }, - "nameConstraints": { - "critical": true, - "permittedDNSDomains": ["localhost", "stepca"], - "permittedURIDomains": ["wire.localhost"] - } - } -"#; - -pub(crate) const ACME_PROVISIONER: &str = "wire"; +const ACME_PROVISIONER: &str = "wire"; const PORT: ContainerPort = ContainerPort::Tcp(9000); +const CRL_DISTRIBUTION_POINT_PLACEHOLDER: &str = "__CRL_DISTRIBUTION_POINT__"; + +fn intermediate_cert_template() -> String { + format!( + r#" + {{ + "subject": "Wire Intermediate CA", + "keyUsage": ["certSign", "crlSign"], + "basicConstraints": {{ + "isCA": true, + "maxPathLen": 0 + }}, + "nameConstraints": {{ + "critical": true, + "permittedDNSDomains": ["localhost", "stepca"], + "permittedURIDomains": ["wire.localhost"] + }}, + "crlDistributionPoints": ["{CRL_DISTRIBUTION_POINT_PLACEHOLDER}"] + }} + "# + ) +} /// This returns the Smallstep certificate template for leaf certificates, i.e. the ones /// issued by the intermediate CA. @@ -144,9 +150,13 @@ fn alter_configuration(host_volume: &Path, ca_cfg: &CaCfg) { let cfg_file = host_volume.join("config").join("ca.json"); let cfg_content = std::fs::read_to_string(&cfg_file).unwrap(); let mut cfg = serde_json::from_str::(&cfg_content).unwrap(); + let authority = serde_json::to_string(&generate_authority_config(ca_cfg)).unwrap(); cfg.as_object_mut() .unwrap() - .insert("authority".to_string(), generate_authority_config(ca_cfg)); + .insert("authority".to_string(), serde_json::from_str(&authority).unwrap()); + cfg.as_object_mut() + .unwrap() + .insert("crl".to_string(), json!({ "enabled": true })); std::fs::write(&cfg_file, serde_json::to_string_pretty(&cfg).unwrap()).unwrap(); } @@ -180,7 +190,7 @@ pub(crate) async fn start_acme_server(ca_cfg: &CaCfg) -> AcmeServer { std::fs::set_permissions(&host_volume, permissions).unwrap(); std::fs::write( host_volume.join("intermediate.template"), - INTERMEDIATE_CERT_TEMPLATE.to_string().into_bytes(), + intermediate_cert_template().into_bytes(), ) .unwrap(); } @@ -201,6 +211,18 @@ pub(crate) async fn start_acme_server(ca_cfg: &CaCfg) -> AcmeServer { .with_cmd(["bash", "-c", "sleep 1h"]); let node = image.start().await.expect("Error running Step CA image"); + let crl_distribution_point = format!( + "https://{}:{}/1.0/crl", + ca_cfg.host, + node.get_host_port_ipv4(PORT).await.unwrap() + ); + std::fs::write( + host_volume.join("intermediate.template"), + intermediate_cert_template() + .replace(CRL_DISTRIBUTION_POINT_PLACEHOLDER, &crl_distribution_point) + .into_bytes(), + ) + .unwrap(); // Generate the root certificate. run_command(&node, "bash -c 'dd if=/dev/random bs=1 count=20 | base64 > password'").await;