liminal-sdk 0.20.21

Application-facing SDK traits for liminal messaging clients
Documentation
//! AGT-006 R2: the client speaks `wss://` to the TLS-terminating proxy
//! itself, and a certificate it does not trust is refused by name.

#![cfg(feature = "wss")]

use std::net::TcpListener;
use std::sync::Arc;
use std::thread;

use liminal_sdk::{RemoteConfig, SdkError};
use rustls::pki_types::{PrivateKeyDer, PrivatePkcs8KeyDer};

type TestResult = Result<(), Box<dyn std::error::Error>>;

/// A listener's port and the thread that answers its one connection.
type Listener = (u16, thread::JoinHandle<Result<(), String>>);

/// A one-connection TLS listener on loopback whose certificate is
/// self-signed for `localhost`: no webpki root vouches for it. Answers its
/// port and the server's handshake outcome.
fn untrusted_tls_listener() -> Result<Listener, Box<dyn std::error::Error>> {
    let certified = rcgen::generate_simple_self_signed(vec!["localhost".to_owned()])?;
    let key = PrivateKeyDer::Pkcs8(PrivatePkcs8KeyDer::from(certified.key_pair.serialize_der()));
    let config = rustls::ServerConfig::builder()
        .with_no_client_auth()
        .with_single_cert(vec![certified.cert.der().clone()], key)?;
    let listener = TcpListener::bind("127.0.0.1:0")?;
    let port = listener.local_addr()?.port();
    let handle = thread::spawn(move || {
        let (mut tcp, _) = listener.accept().map_err(|error| error.to_string())?;
        let mut server =
            rustls::ServerConnection::new(Arc::new(config)).map_err(|error| error.to_string())?;
        // The client's refusal ends the handshake; the server sees its alert.
        while server.is_handshaking() {
            if server.complete_io(&mut tcp).is_err() {
                break;
            }
        }
        Ok(())
    });
    Ok((port, handle))
}

#[test]
fn a_wss_server_whose_certificate_no_root_vouches_for_is_refused_by_name() -> TestResult {
    let (port, server) = untrusted_tls_listener()?;
    let config = RemoteConfig::new(
        format!("wss://localhost:{port}/ws"),
        "c".to_owned(),
        "s".to_owned(),
        liminal_sdk::ConnectionPoolConfig::new(1, 10, 16),
    )?;
    let refused = config.connect_websocket();
    let Err(SdkError::Connection { description }) = refused else {
        return Err("an untrusted certificate was not refused".into());
    };
    assert!(
        description.contains("invalid peer certificate") && description.contains("UnknownIssuer"),
        "{description}"
    );
    server.join().map_err(|_| "the TLS listener panicked")??;
    Ok(())
}