mtls.rs 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488
  1. //! `test(mtls)` — mTLS client authentication (plan todo 21).
  2. //!
  3. //! Covers the two RFC 8705 verifiers (chain-skipping `self_signed` vs
  4. //! `WebPkiClientVerifier` + client-CA bundle), thumbprint/DN matching,
  5. //! rejection of unknown/missing/ambiguous identities, the trusted-proxy
  6. //! header mode, and the request-extension injection over a real TLS
  7. //! handshake through [`d_oauth::tls::serve_tls`].
  8. //!
  9. //! Test names contain `mtls` so
  10. //! `cargo nextest run -E 'test(mtls)'` selects exactly them. Bodies use
  11. //! `Result` + `?` (`unwrap`/`expect`/`panic` are denied workspace-wide).
  12. #[path = "mtls/certs.rs"]
  13. mod certs;
  14. use std::{
  15. net::{IpAddr, SocketAddr},
  16. sync::Arc,
  17. };
  18. use certs::FixtureError;
  19. use d_oauth::{
  20. db::client_repo::{self, Client},
  21. error::{AppError, OAuthErrorCode},
  22. protocol::mtls::{
  23. self, MtlsCnf, MtlsError, PROXY_CERT_HEADER, PROXY_VERIFY_HEADER, PROXY_VERIFY_SUCCESS,
  24. },
  25. };
  26. mod common;
  27. /// The `oauth_clients` migration, embedded byte-for-byte from the repo.
  28. const OAUTH_CLIENTS_SQL: &str = include_str!("../migrations/1791072000_oauth_clients.sql");
  29. /// The forward-only client column additions the repository queries need.
  30. const CLIENT_DPOP_BOUND_SQL: &str = include_str!("../migrations/1791244800_client_dpop_bound.sql");
  31. /// Start a fresh database on the shared test Postgres and connect a pool
  32. /// to it (see `tests/common/mod.rs`). Each test migrates its own database,
  33. /// so parallel tests never share state.
  34. async fn mtls_postgres() -> Result<sqlx::PgPool, TestError> {
  35. common::postgres()
  36. .await
  37. .map_err(|error| TestError::Setup(error.to_string()))
  38. }
  39. /// Apply the embedded migration script exactly as the migrator would.
  40. async fn mtls_migrate(pool: &sqlx::PgPool) -> Result<(), TestError> {
  41. sqlx::raw_sql(OAUTH_CLIENTS_SQL).execute(pool).await?;
  42. sqlx::raw_sql(CLIENT_DPOP_BOUND_SQL).execute(pool).await?;
  43. Ok(())
  44. }
  45. /// `x5t#S256` of the self-signed fixture (minted 2026-10-04; see
  46. /// `tests/mtls/certs.rs` if fixtures are re-minted).
  47. const SELF_THUMBPRINT: &str = "9qdr8V8zS6_ay9l9KkxIDqDtLK_JpWmWVeDLEm89VX0";
  48. /// `x5t#S256` of the CA-signed fixture.
  49. const PKI_THUMBPRINT: &str = "PC8ir71uIdRIdw1XFkMmoFvGC8Ud4CANxkk4x3BWqAc";
  50. /// Subject of the self-signed fixture in [`mtls::subject_dn`] rendering.
  51. const SELF_DN: &str = "CN=mtls-test-client, O=Example";
  52. /// Subject of the CA-signed fixture in [`mtls::subject_dn`] rendering.
  53. const PKI_DN: &str = "CN=mtls-pki-client, O=Example";
  54. /// Test-body error.
  55. #[derive(Debug, thiserror::Error)]
  56. enum TestError {
  57. /// The shared test service is unreachable (usually: compose not started).
  58. #[error("test setup: {0}")]
  59. Setup(String),
  60. /// A sqlx operation failed.
  61. #[error("database error: {0}")]
  62. Database(#[from] sqlx::Error),
  63. /// An mTLS operation failed.
  64. #[error("mtls error: {0}")]
  65. Mtls(#[from] MtlsError),
  66. /// A fixture did not decode.
  67. #[error("fixture error: {0}")]
  68. Fixture(#[from] FixtureError),
  69. /// A rustls operation failed.
  70. #[error("tls error: {0}")]
  71. Tls(#[from] rustls::Error),
  72. /// An I/O operation failed.
  73. #[error("io error: {0}")]
  74. Io(#[from] std::io::Error),
  75. /// An HTTP value was invalid.
  76. #[error("http error: {0}")]
  77. Http(#[from] axum::http::Error),
  78. /// A spawned task failed.
  79. #[error("task error: {0}")]
  80. Task(#[from] tokio::task::JoinError),
  81. /// An address did not parse.
  82. #[error("address error: {0}")]
  83. Addr(#[from] std::net::AddrParseError),
  84. /// A DNS name was invalid.
  85. #[error("dns error: {0}")]
  86. Dns(#[from] rustls::pki_types::InvalidDnsNameError),
  87. /// A response was not UTF-8.
  88. #[error("encoding error: {0}")]
  89. Encoding(#[from] std::string::FromUtf8Error),
  90. }
  91. type TestResult = Result<(), TestError>;
  92. /// Insert one mTLS client row with a runtime query (no sqlx macros, so the
  93. /// offline query cache is untouched).
  94. async fn insert_mtls_client(
  95. pool: &sqlx::PgPool,
  96. client_id: &str,
  97. method: &str,
  98. dn: Option<&str>,
  99. thumbprint: Option<&str>,
  100. ) -> TestResult {
  101. sqlx::query(
  102. "INSERT INTO oauth_clients (id, client_id, client_id_issued_at, client_name,
  103. grant_types, response_types, scope, token_endpoint_auth_method,
  104. tls_client_auth_subject_dn, tls_client_auth_cert_thumbprint,
  105. created_at, updated_at)
  106. VALUES ($1, $2, now(), 'mTLS Test',
  107. ARRAY['client_credentials'], ARRAY['token'], 'api:read', $3, $4, $5,
  108. now(), now())",
  109. )
  110. .bind(uuid::Uuid::new_v4())
  111. .bind(client_id)
  112. .bind(method)
  113. .bind(dn)
  114. .bind(thumbprint)
  115. .execute(pool)
  116. .await?;
  117. Ok(())
  118. }
  119. /// Minimal in-memory client for the pure (DB-free) selector tests.
  120. fn fixture_client(method: &str, dn: Option<&str>, thumbprint: Option<&str>) -> Client {
  121. Client {
  122. id: uuid::Uuid::nil(),
  123. client_id: "mtls-fixture".to_owned(),
  124. client_secret_expires_at: None,
  125. client_id_issued_at: time::OffsetDateTime::UNIX_EPOCH,
  126. client_name: "mTLS Fixture".to_owned(),
  127. grant_types: Vec::new(),
  128. response_types: Vec::new(),
  129. scope: String::new(),
  130. token_endpoint_auth_method: method.to_owned(),
  131. jwks: None,
  132. jwks_uri: None,
  133. tls_client_auth_subject_dn: dn.map(str::to_owned),
  134. tls_client_auth_cert_thumbprint: thumbprint.map(str::to_owned),
  135. request_uris: None,
  136. contacts: None,
  137. software_id: None,
  138. dpop_bound_access_tokens: false,
  139. active: true,
  140. created_at: time::OffsetDateTime::UNIX_EPOCH,
  141. updated_at: time::OffsetDateTime::UNIX_EPOCH,
  142. }
  143. }
  144. fn is_invalid_client(result: Result<MtlsCnf, MtlsError>) -> bool {
  145. matches!(
  146. result.map(|_| ()).map_err(MtlsError::into_app_error),
  147. Err(AppError::OAuth {
  148. code: OAuthErrorCode::InvalidClient,
  149. ..
  150. })
  151. )
  152. }
  153. /// The thumbprint matches the `openssl`-computed vector (RFC 8705 §2.1).
  154. #[test]
  155. fn mtls_thumbprint_matches_openssl_vector() -> TestResult {
  156. let der = certs::cert_der(certs::CLIENT_SELF_CERT_PEM)?;
  157. assert_eq!(mtls::thumbprint_b64url(der.as_ref()), SELF_THUMBPRINT);
  158. let pki = certs::cert_der(certs::CLIENT_PKI_CERT_PEM)?;
  159. assert_eq!(mtls::thumbprint_b64url(pki.as_ref()), PKI_THUMBPRINT);
  160. Ok(())
  161. }
  162. /// The subject DN renders in RFC 4514 certificate-attribute order.
  163. #[test]
  164. fn mtls_subject_dn_renders_rfc4514() -> TestResult {
  165. let der = certs::cert_der(certs::CLIENT_SELF_CERT_PEM)?;
  166. assert_eq!(mtls::subject_dn(der.as_ref())?, SELF_DN);
  167. let pki = certs::cert_der(certs::CLIENT_PKI_CERT_PEM)?;
  168. assert_eq!(mtls::subject_dn(pki.as_ref())?, PKI_DN);
  169. Ok(())
  170. }
  171. /// A registered self-signed certificate binds `cnf.x5t#S256`.
  172. #[tokio::test]
  173. async fn mtls_registered_thumbprint_binds_cnf() -> TestResult {
  174. let pool = mtls_postgres().await?;
  175. mtls_migrate(&pool).await?;
  176. insert_mtls_client(
  177. &pool,
  178. "mtls-self",
  179. "self_signed_tls_client_auth",
  180. None,
  181. Some(SELF_THUMBPRINT),
  182. )
  183. .await?;
  184. let client = client_repo::find_active_by_client_id(&pool, "mtls-self")
  185. .await?
  186. .ok_or(MtlsError::UnknownClient)?;
  187. let leaf = certs::cert_der(certs::CLIENT_SELF_CERT_PEM)?;
  188. let cnf = mtls::authenticate(&pool, &client, Some(&leaf)).await?;
  189. assert_eq!(cnf.x5t_s256, SELF_THUMBPRINT);
  190. assert_eq!(cnf.claim()["x5t#S256"], SELF_THUMBPRINT);
  191. Ok(())
  192. }
  193. /// A CA-issued certificate binds via subject DN under `tls_client_auth`.
  194. #[tokio::test]
  195. async fn mtls_pki_subject_dn_binds_cnf() -> TestResult {
  196. let pool = mtls_postgres().await?;
  197. mtls_migrate(&pool).await?;
  198. insert_mtls_client(&pool, "mtls-pki", "tls_client_auth", Some(PKI_DN), None).await?;
  199. let client = client_repo::find_active_by_client_id(&pool, "mtls-pki")
  200. .await?
  201. .ok_or(MtlsError::UnknownClient)?;
  202. let leaf = certs::cert_der(certs::CLIENT_PKI_CERT_PEM)?;
  203. let cnf = mtls::authenticate(&pool, &client, Some(&leaf)).await?;
  204. assert_eq!(cnf.x5t_s256, PKI_THUMBPRINT);
  205. Ok(())
  206. }
  207. /// An unregistered certificate is rejected as `invalid_client`.
  208. #[tokio::test]
  209. async fn mtls_unregistered_thumbprint_rejected_as_invalid_client() -> TestResult {
  210. let pool = mtls_postgres().await?;
  211. mtls_migrate(&pool).await?;
  212. insert_mtls_client(
  213. &pool,
  214. "mtls-self",
  215. "self_signed_tls_client_auth",
  216. None,
  217. Some(SELF_THUMBPRINT),
  218. )
  219. .await?;
  220. let client = client_repo::find_active_by_client_id(&pool, "mtls-self")
  221. .await?
  222. .ok_or(MtlsError::UnknownClient)?;
  223. let rogue = certs::cert_der(certs::CLIENT_ROGUE_CERT_PEM)?;
  224. let result = mtls::authenticate(&pool, &client, Some(&rogue)).await;
  225. assert!(matches!(result, Err(MtlsError::UnknownCertificate)));
  226. assert!(is_invalid_client(result));
  227. Ok(())
  228. }
  229. /// A missing certificate rejects every mTLS-registered client.
  230. #[tokio::test]
  231. async fn mtls_missing_cert_rejects_mtls_clients() -> TestResult {
  232. let pool = mtls_postgres().await?;
  233. mtls_migrate(&pool).await?;
  234. let pinned = fixture_client("self_signed_tls_client_auth", None, Some(SELF_THUMBPRINT));
  235. let result = mtls::authenticate(&pool, &pinned, None).await;
  236. assert!(matches!(result, Err(MtlsError::MissingCertificate)));
  237. assert!(is_invalid_client(result));
  238. let pki = fixture_client("tls_client_auth", Some(PKI_DN), None);
  239. let result = mtls::authenticate(&pool, &pki, None).await;
  240. assert!(matches!(result, Err(MtlsError::MissingCertificate)));
  241. assert!(is_invalid_client(result));
  242. Ok(())
  243. }
  244. /// A subject DN shared by two clients is rejected as ambiguous.
  245. #[test]
  246. fn mtls_subject_dn_ambiguity_rejected() -> TestResult {
  247. let first = fixture_client("tls_client_auth", Some(PKI_DN), None);
  248. let second = fixture_client("tls_client_auth", Some(PKI_DN), None);
  249. let clients = [first, second];
  250. let result = mtls::select_by_subject_dn(&clients, PKI_DN);
  251. assert!(matches!(result, Err(MtlsError::AmbiguousSubjectDn)));
  252. assert!(is_invalid_client(result.map(|_| MtlsCnf {
  253. x5t_s256: String::new()
  254. })));
  255. let lone = fixture_client("tls_client_auth", Some(PKI_DN), None);
  256. let clients = [lone];
  257. assert_eq!(
  258. mtls::select_by_subject_dn(&clients, PKI_DN)?.client_id,
  259. "mtls-fixture"
  260. );
  261. assert!(matches!(
  262. mtls::select_by_subject_dn(&clients, "CN=nobody, O=Example"),
  263. Err(MtlsError::UnknownCertificate)
  264. ));
  265. Ok(())
  266. }
  267. /// Build request parts carrying the given proxy headers.
  268. fn mtls_proxy_parts(pairs: &[(&str, &str)]) -> Result<axum::http::request::Parts, TestError> {
  269. let mut builder = axum::http::Request::builder().uri("/token");
  270. for (name, value) in pairs {
  271. builder = builder.header(*name, *value);
  272. }
  273. Ok(builder.body(())?.into_parts().0)
  274. }
  275. /// A forged proxy header is ignored without a valid verification signal,
  276. /// and always ignored when no proxy is trusted.
  277. #[test]
  278. fn mtls_forged_proxy_header_ignored_without_success_signal() -> TestResult {
  279. let der_b64 = certs::der_b64(certs::CLIENT_SELF_CERT_PEM)?;
  280. let proxy: Option<IpAddr> = Some("127.0.0.1".parse()?);
  281. // No trusted proxy: even a SUCCESS-signed header is ignored.
  282. let parts = mtls_proxy_parts(&[
  283. (PROXY_VERIFY_HEADER, PROXY_VERIFY_SUCCESS),
  284. (PROXY_CERT_HEADER, &der_b64),
  285. ])?;
  286. assert!(mtls::presented_leaf(&parts, None)?.is_none());
  287. // Trusted proxy but no signal: ignored.
  288. let parts = mtls_proxy_parts(&[(PROXY_CERT_HEADER, &der_b64)])?;
  289. assert!(mtls::presented_leaf(&parts, proxy)?.is_none());
  290. // Trusted proxy with a failed signal: ignored.
  291. let parts = mtls_proxy_parts(&[
  292. (PROXY_VERIFY_HEADER, "FAILED"),
  293. (PROXY_CERT_HEADER, &der_b64),
  294. ])?;
  295. assert!(mtls::presented_leaf(&parts, proxy)?.is_none());
  296. // SUCCESS signal but no certificate: nothing to trust.
  297. let parts = mtls_proxy_parts(&[(PROXY_VERIFY_HEADER, PROXY_VERIFY_SUCCESS)])?;
  298. assert!(mtls::presented_leaf(&parts, proxy)?.is_none());
  299. Ok(())
  300. }
  301. /// A SUCCESS-signed header is honored only with `trusted_proxy`, and the
  302. /// direct listener strips inbound headers before routing.
  303. #[test]
  304. fn mtls_proxy_success_signal_accepted_and_stripped() -> TestResult {
  305. let der_b64 = certs::der_b64(certs::CLIENT_SELF_CERT_PEM)?;
  306. let proxy: Option<IpAddr> = Some("127.0.0.1".parse()?);
  307. let parts = mtls_proxy_parts(&[
  308. (PROXY_VERIFY_HEADER, PROXY_VERIFY_SUCCESS),
  309. (PROXY_CERT_HEADER, &der_b64),
  310. ])?;
  311. let leaf = mtls::presented_leaf(&parts, proxy)?.ok_or(MtlsError::MissingCertificate)?;
  312. assert_eq!(mtls::thumbprint_b64url(leaf.as_ref()), SELF_THUMBPRINT);
  313. // A SUCCESS-signed garbage header fails closed instead of binding.
  314. let parts = mtls_proxy_parts(&[
  315. (PROXY_VERIFY_HEADER, PROXY_VERIFY_SUCCESS),
  316. (PROXY_CERT_HEADER, "!!!"),
  317. ])?;
  318. assert!(matches!(
  319. mtls::presented_leaf(&parts, proxy),
  320. Err(MtlsError::MalformedCertificate)
  321. ));
  322. // Stripping removes both headers, so downstream sees no certificate.
  323. let mut parts = mtls_proxy_parts(&[
  324. (PROXY_VERIFY_HEADER, PROXY_VERIFY_SUCCESS),
  325. (PROXY_CERT_HEADER, &der_b64),
  326. ])?;
  327. mtls::strip_proxy_headers(&mut parts.headers);
  328. assert!(!parts.headers.contains_key(PROXY_CERT_HEADER));
  329. assert!(!parts.headers.contains_key(PROXY_VERIFY_HEADER));
  330. assert!(mtls::presented_leaf(&parts, proxy)?.is_none());
  331. Ok(())
  332. }
  333. /// Handler echo for the handshake test: the bound thumbprint, or `no-cert`.
  334. async fn mtls_thumbprint_handler(req: axum::http::Request<axum::body::Body>) -> String {
  335. match req.extensions().get::<d_oauth::tls::PeerCertificates>() {
  336. Some(peer) => peer.chain().first().map_or_else(
  337. || "empty".to_owned(),
  338. |der| mtls::thumbprint_b64url(der.as_ref()),
  339. ),
  340. None => "no-cert".to_owned(),
  341. }
  342. }
  343. /// Raw HTTPS GET over a rustls client config, returning the response body.
  344. async fn mtls_get(
  345. addr: SocketAddr,
  346. client_cert: Option<(
  347. Vec<rustls::pki_types::CertificateDer<'static>>,
  348. rustls::pki_types::PrivateKeyDer<'static>,
  349. )>,
  350. ) -> Result<String, TestError> {
  351. use tokio::io::{AsyncReadExt as _, AsyncWriteExt as _};
  352. let mut roots = rustls::RootCertStore::empty();
  353. roots.add(certs::cert_der(certs::SERVER_CERT_PEM)?)?;
  354. let builder = rustls::ClientConfig::builder().with_root_certificates(roots);
  355. let config = match client_cert {
  356. Some((chain, key)) => builder.with_client_auth_cert(chain, key)?,
  357. None => builder.with_no_client_auth(),
  358. };
  359. let connector = tokio_rustls::TlsConnector::from(Arc::new(config));
  360. let stream = tokio::net::TcpStream::connect(addr).await?;
  361. let server_name = rustls::pki_types::ServerName::try_from("localhost")?.to_owned();
  362. let mut tls = connector.connect(server_name, stream).await?;
  363. tls
  364. .write_all(b"GET / HTTP/1.1\r\nhost: localhost\r\nconnection: close\r\n\r\n")
  365. .await?;
  366. let mut raw = Vec::new();
  367. tls.read_to_end(&mut raw).await?;
  368. let text = String::from_utf8(raw)?;
  369. Ok(text.split("\r\n\r\n").last().unwrap_or("").to_owned())
  370. }
  371. /// The extension-injection mechanism works over a real TLS handshake: a
  372. /// self-signed client (no CA involved) binds, and a cert-less connection on
  373. /// the same listener is served with no extension.
  374. #[tokio::test]
  375. async fn mtls_real_tls_handshake_injects_peer_extension() -> TestResult {
  376. let app = axum::Router::new().route("/", axum::routing::get(mtls_thumbprint_handler));
  377. let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await?;
  378. let addr = listener.local_addr()?;
  379. let config = mtls::server_config(
  380. certs::SERVER_CERT_PEM.as_bytes(),
  381. certs::SERVER_KEY_PEM.as_bytes(),
  382. mtls::self_signed_verifier(),
  383. )?;
  384. let acceptor = tokio_rustls::TlsAcceptor::from(Arc::new(config));
  385. let server = tokio::spawn(async move { d_oauth::tls::serve_tls(listener, acceptor, app).await });
  386. let chain = vec![certs::cert_der(certs::CLIENT_SELF_CERT_PEM)?];
  387. let key = certs::private_key(certs::CLIENT_SELF_KEY_PEM)?;
  388. assert_eq!(mtls_get(addr, Some((chain, key))).await?, SELF_THUMBPRINT);
  389. assert_eq!(mtls_get(addr, None).await?, "no-cert");
  390. server.abort();
  391. Ok(())
  392. }
  393. /// One TLS byte-roundtrip with the given client identity.
  394. async fn mtls_roundtrip(addr: SocketAddr, cert_pem: &str, key_pem: &str) -> Result<u8, TestError> {
  395. use tokio::io::{AsyncReadExt as _, AsyncWriteExt as _};
  396. let mut roots = rustls::RootCertStore::empty();
  397. roots.add(certs::cert_der(certs::SERVER_CERT_PEM)?)?;
  398. let config = rustls::ClientConfig::builder()
  399. .with_root_certificates(roots)
  400. .with_client_auth_cert(
  401. vec![certs::cert_der(cert_pem)?],
  402. certs::private_key(key_pem)?,
  403. )?;
  404. let connector = tokio_rustls::TlsConnector::from(Arc::new(config));
  405. let stream = tokio::net::TcpStream::connect(addr).await?;
  406. let server_name = rustls::pki_types::ServerName::try_from("localhost")?.to_owned();
  407. let mut tls = connector.connect(server_name, stream).await?;
  408. tls.write_all(b"Z").await?;
  409. let mut byte = [0_u8; 1];
  410. tls.read_exact(&mut byte).await?;
  411. Ok(byte[0])
  412. }
  413. /// The PKI verifier chains to the client CA: a CA-signed client completes
  414. /// the handshake while a self-signed impostor is rejected mid-handshake.
  415. #[tokio::test]
  416. async fn mtls_pki_verifier_accepts_ca_signed_rejects_self_signed() -> TestResult {
  417. use tokio::io::{AsyncReadExt as _, AsyncWriteExt as _};
  418. let verifier = mtls::pki_verifier(certs::CA_CERT_PEM.as_bytes())?;
  419. let config = mtls::server_config(
  420. certs::SERVER_CERT_PEM.as_bytes(),
  421. certs::SERVER_KEY_PEM.as_bytes(),
  422. verifier,
  423. )?;
  424. let acceptor = tokio_rustls::TlsAcceptor::from(Arc::new(config));
  425. let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await?;
  426. let addr = listener.local_addr()?;
  427. let server = tokio::spawn(async move {
  428. let mut completed = 0;
  429. for _ in 0..2 {
  430. let (stream, _) = listener.accept().await?;
  431. let mut tls = acceptor.accept(stream).await?;
  432. let mut byte = [0_u8; 1];
  433. tls.read_exact(&mut byte).await?;
  434. tls.write_all(&byte).await?;
  435. completed += 1;
  436. }
  437. Ok::<u32, TestError>(completed)
  438. });
  439. assert_eq!(
  440. mtls_roundtrip(addr, certs::CLIENT_PKI_CERT_PEM, certs::CLIENT_PKI_KEY_PEM).await?,
  441. b'Z'
  442. );
  443. assert!(
  444. mtls_roundtrip(
  445. addr,
  446. certs::CLIENT_SELF_CERT_PEM,
  447. certs::CLIENT_SELF_KEY_PEM
  448. )
  449. .await
  450. .is_err()
  451. );
  452. assert!(server.await?.is_err());
  453. Ok(())
  454. }