mtls.rs 18 KB

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