Implement PTY-native session terminal control

This commit is contained in:
ms2k 2026-08-02 17:36:40 +09:00
commit 8cef037fa3

View file

@ -28,6 +28,7 @@ use rustls::{ClientConfig, ClientConnection, RootCertStore};
use sha2::{Digest, Sha256};
pub const CONTROL_ALPN: &[u8] = b"lazaret-control/1";
pub const CONTROL_ALPN_V2: &[u8] = b"lazaret-control/2";
pub const CONTROL_SERVER_NAME: &str = "coordinator.lazaret.invalid";
pub const CONTROL_SOCKET_NAME: &str = "c.sock";
pub const PUBLICATION_IDENTITY_NAME: &str = "protocol-identity.postcard";
@ -237,6 +238,25 @@ impl ControlClient {
pub fn config(
publication: &ProtocolPublicationIdentity,
client: Option<(&TlsCredential, PrivateKeyDer<'static>)>,
) -> Result<ClientConfig, ControlClientError> {
Self::config_with_alpn(publication, client, CONTROL_ALPN)
}
/// Creates a TLS client configuration for the interactive v2 protocol.
///
/// # Errors
/// Returns malformed trust or credential failures.
pub fn config_v2(
publication: &ProtocolPublicationIdentity,
client: Option<(&TlsCredential, PrivateKeyDer<'static>)>,
) -> Result<ClientConfig, ControlClientError> {
Self::config_with_alpn(publication, client, CONTROL_ALPN_V2)
}
fn config_with_alpn(
publication: &ProtocolPublicationIdentity,
client: Option<(&TlsCredential, PrivateKeyDer<'static>)>,
alpn: &[u8],
) -> Result<ClientConfig, ControlClientError> {
let mut roots = RootCertStore::empty();
roots.add(CertificateDer::from(
@ -258,7 +278,7 @@ impl ControlClient {
} else {
builder.with_no_client_auth()
};
config.alpn_protocols = vec![CONTROL_ALPN.to_vec()];
config.alpn_protocols = vec![alpn.to_vec()];
config.enable_early_data = false;
config.resumption = rustls::client::Resumption::disabled();
Ok(config)
@ -275,6 +295,33 @@ impl ControlClient {
) -> Result<
rustls::StreamOwned<ClientConnection, std::os::unix::net::UnixStream>,
ControlClientError,
> {
Self::connect_with_alpn(socket_path, config, publication, CONTROL_ALPN)
}
/// Connects a pinned TLS stream using the interactive v2 ALPN.
///
/// # Errors
/// Returns socket, TLS, or certificate-profile failures.
pub fn connect_v2(
socket_path: &Path,
config: ClientConfig,
publication: &ProtocolPublicationIdentity,
) -> Result<
rustls::StreamOwned<ClientConnection, std::os::unix::net::UnixStream>,
ControlClientError,
> {
Self::connect_with_alpn(socket_path, config, publication, CONTROL_ALPN_V2)
}
fn connect_with_alpn(
socket_path: &Path,
config: ClientConfig,
publication: &ProtocolPublicationIdentity,
alpn: &[u8],
) -> Result<
rustls::StreamOwned<ClientConnection, std::os::unix::net::UnixStream>,
ControlClientError,
> {
let socket = std::os::unix::net::UnixStream::connect(socket_path)?;
let server_name = ServerName::try_from(CONTROL_SERVER_NAME)
@ -288,7 +335,7 @@ impl ControlClient {
return Err(ControlClientError::InvalidCertificateProfile);
};
if connection.protocol_version() != Some(rustls::ProtocolVersion::TLSv1_3)
|| connection.alpn_protocol() != Some(CONTROL_ALPN)
|| connection.alpn_protocol() != Some(alpn)
|| certificate_digest(certificate) != publication.coordinator_tls_certificate_sha256
{
return Err(ControlClientError::InvalidCertificateProfile);