feat(ironrdp-tls)!: return x509_cert::Certificate from upgrade() (#1054)
Some checks failed
CI / Check formatting (push) Has been cancelled
CI / Check typos (push) Has been cancelled
Coverage / Coverage Report (push) Has been cancelled
Release crates / Open release PR (push) Has been cancelled
Release crates / Release crates (push) Has been cancelled
CI / Fuzzing (push) Has been cancelled
CI / Web Client (push) Has been cancelled
CI / FFI (push) Has been cancelled
CI / Success (push) Has been cancelled
CI / Checks [linux] (push) Has been cancelled
CI / Checks [macos] (push) Has been cancelled
CI / Checks [windows] (push) Has been cancelled

This allows client applications to verify details of the certificate,
possibly with the user, when connecting to a server using TLS.
This commit is contained in:
Will Warner 2025-12-17 23:14:51 -05:00 committed by GitHub
parent b50b648344
commit bd2aed7686
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 25 additions and 27 deletions

View file

@ -229,7 +229,7 @@ async fn connect(
// Ensure there is no leftover
let (initial_stream, leftover_bytes) = framed.into_inner();
let (upgraded_stream, server_public_key) = ironrdp_tls::upgrade(initial_stream, config.destination.name())
let (upgraded_stream, tls_cert) = ironrdp_tls::upgrade(initial_stream, config.destination.name())
.await
.map_err(|e| connector::custom_err!("TLS upgrade", e))?;
@ -238,13 +238,15 @@ async fn connect(
let erased_stream: Box<dyn AsyncReadWrite + Unpin + Send + Sync> = Box::new(upgraded_stream);
let mut upgraded_framed = ironrdp_tokio::TokioFramed::new_with_leftover(erased_stream, leftover_bytes);
let server_public_key = ironrdp_tls::extract_tls_server_public_key(&tls_cert)
.ok_or_else(|| connector::general_err!("unable to extract tls server public key"))?;
let connection_result = ironrdp_tokio::connect_finalize(
upgraded,
connector,
&mut upgraded_framed,
&mut ReqwestNetworkClient::new(),
(&config.destination).into(),
server_public_key,
server_public_key.to_owned(),
None,
)
.await?;

View file

@ -205,18 +205,20 @@ where
.await
.expect("begin connection");
let initial_stream = framed.into_inner_no_leftover();
let (upgraded_stream, server_public_key) = ironrdp_tls::upgrade(initial_stream, "localhost")
let (upgraded_stream, tls_cert) = ironrdp_tls::upgrade(initial_stream, "localhost")
.await
.expect("TLS upgrade");
let upgraded = ironrdp_tokio::mark_as_upgraded(should_upgrade, &mut connector);
let mut upgraded_framed = ironrdp_tokio::TokioFramed::new(upgraded_stream);
let server_public_key =
ironrdp_tls::extract_tls_server_public_key(&tls_cert).expect("extract server public key");
let connection_result = ironrdp_async::connect_finalize(
upgraded,
connector,
&mut upgraded_framed,
&mut ironrdp_tokio::reqwest::ReqwestNetworkClient::new(),
"localhost".into(),
server_public_key,
server_public_key.to_owned(),
None,
)
.await

View file

@ -23,7 +23,7 @@ stub = []
[dependencies]
tokio = { version = "1.47" }
x509-cert = { version = "0.2", default-features = false, features = ["std"], optional = true }
x509-cert = { version = "0.2", default-features = false, features = ["std"], optional = true } # public
tokio-native-tls = { version = "0.3", optional = true } # public
tokio-rustls = { version = "0.26", optional = true } # public

View file

@ -25,21 +25,9 @@ compile_error!("a TLS backend must be selected by enabling a single feature out
#[cfg(any(feature = "stub", feature = "native-tls", feature = "rustls"))]
pub use impl_::{upgrade, TlsStream};
#[cfg(any(feature = "native-tls", feature = "rustls"))]
pub(crate) fn extract_tls_server_public_key(cert: &[u8]) -> std::io::Result<Vec<u8>> {
use std::io;
use x509_cert::der::Decode as _;
let cert = x509_cert::Certificate::from_der(cert).map_err(io::Error::other)?;
let server_public_key = cert
.tbs_certificate
pub fn extract_tls_server_public_key(cert: &x509_cert::Certificate) -> Option<&[u8]> {
cert.tbs_certificate
.subject_public_key_info
.subject_public_key
.as_bytes()
.ok_or_else(|| io::Error::other("subject public key BIT STRING is not aligned"))?
.to_owned();
Ok(server_public_key)
}

View file

@ -4,7 +4,7 @@ use tokio::io::{AsyncRead, AsyncWrite, AsyncWriteExt as _};
pub type TlsStream<S> = tokio_native_tls::TlsStream<S>;
pub async fn upgrade<S>(stream: S, server_name: &str) -> io::Result<(TlsStream<S>, Vec<u8>)>
pub async fn upgrade<S>(stream: S, server_name: &str) -> io::Result<(TlsStream<S>, x509_cert::Certificate)>
where
S: Unpin + AsyncRead + AsyncWrite,
{
@ -24,15 +24,18 @@ where
tls_stream.flush().await?;
let server_public_key = {
let tls_cert = {
use x509_cert::der::Decode as _;
let cert = tls_stream
.get_ref()
.peer_certificate()
.map_err(|e| io::Error::new(io::ErrorKind::Other, e))?
.ok_or_else(|| io::Error::new(io::ErrorKind::Other, "peer certificate is missing"))?;
let cert = cert.to_der().map_err(|e| io::Error::new(io::ErrorKind::Other, e))?;
crate::extract_tls_server_public_key(&cert)?
x509_cert::Certificate::from_der(&cert).map_err(io::Error::other)?
};
Ok((tls_stream, server_public_key))
Ok((tls_stream, tls_cert))
}

View file

@ -6,7 +6,7 @@ use tokio_rustls::rustls::pki_types::ServerName;
pub type TlsStream<S> = tokio_rustls::client::TlsStream<S>;
pub async fn upgrade<S>(stream: S, server_name: &str) -> io::Result<(TlsStream<S>, Vec<u8>)>
pub async fn upgrade<S>(stream: S, server_name: &str) -> io::Result<(TlsStream<S>, x509_cert::Certificate)>
where
S: Unpin + AsyncRead + AsyncWrite,
{
@ -35,17 +35,20 @@ where
tls_stream.flush().await?;
let server_public_key = {
let tls_cert = {
use x509_cert::der::Decode as _;
let cert = tls_stream
.get_ref()
.1
.peer_certificates()
.and_then(|certificates| certificates.first())
.ok_or_else(|| io::Error::other("peer certificate is missing"))?;
crate::extract_tls_server_public_key(cert)?
x509_cert::Certificate::from_der(cert).map_err(io::Error::other)?
};
Ok((tls_stream, server_public_key))
Ok((tls_stream, tls_cert))
}
mod danger {