Skip to content
Open
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
59 changes: 57 additions & 2 deletions e2e-identity/tests/e2e.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::*;
Expand All @@ -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;
Expand Down Expand Up @@ -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<String> = 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::<HashSet<_>>(),
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]
Expand Down
5 changes: 3 additions & 2 deletions e2e-identity/tests/utils/hooks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
};
Expand All @@ -30,7 +31,7 @@ impl PkiEnvironmentHooks for TestPkiEnvironmentHooks {
mut headers: Vec<HttpHeader>,
body: Vec<u8>,
) -> Result<HttpResponse, PkiEnvironmentHooksError> {
let client = default_http_client()
let client = ctx_get_http_client_builder()
.add_root_certificate(self.acme.ca_cert.clone())
.build()
.unwrap();
Expand Down
60 changes: 41 additions & 19 deletions e2e-identity/tests/utils/stepca.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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::<serde_json::Value>(&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());
Copy link
Copy Markdown
Member

@istankovic istankovic Apr 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here we are calling from_str to convert authority to a serde_json::Value, but a few lines above we generate a serde_json::Value by calling generate_authority_config and immediately convert it to a string. Can't we just use

        .insert("authority".to_string(), generate_authority_config(ca_cfg));

like the previous version did?

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();
}

Expand Down Expand Up @@ -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();
}
Expand All @@ -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;
Expand Down
Loading