From 3cc9fcd86b5c1ab7721a101fe7cd015ab74b2498 Mon Sep 17 00:00:00 2001 From: yggverse Date: Sun, 1 Dec 2024 02:57:21 +0200 Subject: [PATCH] replace deprecated re-handshake feature with session-resumption-enabled property set --- src/client.rs | 96 +++++++++++------------------- src/client/connection.rs | 113 +++++++++++------------------------- src/client/error.rs | 4 -- src/client/session.rs | 98 ------------------------------- src/client/session/error.rs | 16 ----- 5 files changed, 68 insertions(+), 259 deletions(-) delete mode 100644 src/client/session.rs delete mode 100644 src/client/session/error.rs diff --git a/src/client.rs b/src/client.rs index b01b744..379b66a 100644 --- a/src/client.rs +++ b/src/client.rs @@ -4,12 +4,10 @@ pub mod connection; pub mod error; pub mod response; -pub mod session; pub use connection::Connection; pub use error::Error; pub use response::Response; -pub use session::Session; use gio::{ prelude::{IOStreamExt, OutputStreamExt, SocketClientExt, TlsConnectionExt}, @@ -26,7 +24,6 @@ pub const DEFAULT_TIMEOUT: u32 = 10; /// Provides high-level API for session-safe interaction with /// [Gemini](https://geminiprotocol.net) socket server pub struct Client { - session: Rc, pub socket: SocketClient, } @@ -63,23 +60,13 @@ impl Client { }); // Done - Self { - session: Rc::new(Session::new()), - socket, - } + Self { socket } } // Actions /// High-level method make new async request to given [Uri](https://docs.gtk.org/glib/struct.Uri.html), /// callback with new `Response`on success or `Error` on failure - /// - /// * implement `certificate` comparison with previously defined for this `uri`, force rehandshake if does not match - /// * method does not close new `Connection` by default, hold it in `Session`, - /// expect from user manual `Response` handle with close act on complete - /// * ignores default session resumption provided by Glib TLS backend, - /// instead, applies new `certificate` to available sessions match - /// `uri` [scope](https://geminiprotocol.net/docs/protocol-specification.gmi#status-60) pub fn request_async( &self, uri: Uri, @@ -92,56 +79,43 @@ impl Client { // * guest sessions will not work without! self.socket.set_tls(certificate.is_none()); - // Update previous session if available for this `uri`, does force rehandshake on `certificate` change - match self.session.update(&uri, certificate.as_ref()) { - // Begin new connection - // * [NetworkAddress](https://docs.gtk.org/gio/class.NetworkAddress.html) required for valid - // [SNI](https://geminiprotocol.net/docs/protocol-specification.gmi#server-name-indication) - Ok(()) => match crate::gio::network_address::from_uri(&uri, crate::DEFAULT_PORT) { - Ok(network_address) => self.socket.connect_async( - &network_address.clone(), - match cancellable { - Some(ref cancellable) => Some(cancellable.clone()), - None => None::, - } - .as_ref(), - { - let session = self.session.clone(); - move |result| match result { + // Begin new connection + // * [NetworkAddress](https://docs.gtk.org/gio/class.NetworkAddress.html) required for valid + // [SNI](https://geminiprotocol.net/docs/protocol-specification.gmi#server-name-indication) + match crate::gio::network_address::from_uri(&uri, crate::DEFAULT_PORT) { + Ok(network_address) => self.socket.connect_async( + &network_address.clone(), + match cancellable { + Some(ref cancellable) => Some(cancellable.clone()), + None => None::, + } + .as_ref(), + move |result| match result { + Ok(connection) => { + // Wrap required connection dependencies into the struct holder + match Connection::new( + connection, + certificate, + Some(network_address), + cancellable.clone(), + ) { Ok(connection) => { - // Wrap required connection dependencies into the struct holder - match Connection::new_wrap( - connection, - certificate, - Some(network_address), - cancellable.clone(), - ) { - Ok(connection) => { - // Wrap to shared reference support clone semantics - let connection = Rc::new(connection); - - // Renew session - session.set(uri.to_string(), connection.clone()); - - // Begin new request - request_async( - connection, - uri.to_string(), - priority, - cancellable, - callback, // result - ) - } - Err(e) => callback(Err(Error::Connection(e))), - } + // Begin new request + request_async( + Rc::new(connection), + uri.to_string(), + priority, + cancellable, + callback, // result + ) } - Err(e) => callback(Err(Error::Connect(e))), + Err(e) => callback(Err(Error::Connection(e))), } - }, - ), - Err(e) => callback(Err(Error::NetworkAddress(e))), - }, - Err(e) => callback(Err(Error::Session(e))), + } + Err(e) => callback(Err(Error::Connect(e))), + }, + ), + Err(e) => callback(Err(Error::NetworkAddress(e))), } } } diff --git a/src/client/connection.rs b/src/client/connection.rs index 8863f31..a2a4013 100644 --- a/src/client/connection.rs +++ b/src/client/connection.rs @@ -5,20 +5,20 @@ use gio::{ prelude::{CancellableExt, IOStreamExt, TlsConnectionExt}, Cancellable, IOStream, NetworkAddress, SocketConnection, TlsCertificate, TlsClientConnection, }; -use glib::object::{Cast, IsA}; +use glib::object::{Cast, IsA, ObjectExt}; pub struct Connection { pub cancellable: Option, - pub server_identity: Option, + pub certificate: Option, pub socket_connection: SocketConnection, - pub tls_client_connection: Option, + pub tls_client_connection: TlsClientConnection, } impl Connection { // Constructors /// Create new `Self` - pub fn new_wrap( + pub fn new( socket_connection: SocketConnection, certificate: Option, server_identity: Option, @@ -30,20 +30,32 @@ impl Connection { Ok(Self { cancellable, - server_identity: server_identity.clone(), + certificate: certificate.clone(), socket_connection: socket_connection.clone(), - tls_client_connection: match certificate { - Some(certificate) => { - match new_tls_client_connection( - &socket_connection, - &certificate, - server_identity.as_ref(), - ) { - Ok(tls_client_connection) => Some(tls_client_connection), - Err(e) => return Err(e), + tls_client_connection: match TlsClientConnection::new( + &socket_connection.clone(), + server_identity.as_ref(), + ) { + Ok(tls_client_connection) => { + // Prevent session resumption (on certificate change in runtime) + tls_client_connection.set_property("session-resumption-enabled", &false); + + // Is user session + // https://geminiprotocol.net/docs/protocol-specification.gmi#client-certificates + if let Some(ref certificate) = certificate { + tls_client_connection.set_certificate(certificate); } + + // @TODO handle + // https://geminiprotocol.net/docs/protocol-specification.gmi#closing-connections + tls_client_connection.set_require_close_notify(true); + + // @TODO validate + // https://geminiprotocol.net/docs/protocol-specification.gmi#tls-server-certificate-validation + tls_client_connection.connect_accept_certificate(move |_, _, _| true); + tls_client_connection } - None => None, + Err(e) => return Err(Error::TlsClientConnection(e)), }, }) } @@ -71,77 +83,18 @@ impl Connection { } } - /// Force non-cancellable handshake request for `Self` - /// * useful for certificate change in runtime - /// * support guest and user sessions - pub fn rehandshake(&self) -> Result<(), Error> { - match self.tls_client_connection()?.handshake(Cancellable::NONE) { - Ok(()) => Ok(()), - Err(e) => Err(Error::Rehandshake(e)), - } - } - // Getters - /// Upcast [IOStream](https://docs.gtk.org/gio/class.IOStream.html) + /// Get [IOStream](https://docs.gtk.org/gio/class.IOStream.html) /// for [SocketConnection](https://docs.gtk.org/gio/class.SocketConnection.html) /// or [TlsClientConnection](https://docs.gtk.org/gio/iface.TlsClientConnection.html) (if available) - /// * wanted to keep `Connection` active in async I/O context + /// * useful also to keep `Connection` active in async I/O context pub fn stream(&self) -> impl IsA { - match self.tls_client_connection.clone() { - Some(tls_client_connection) => tls_client_connection.upcast::(), + // * do not replace with `tls_client_connection.base_io_stream()` + // as it will not work for user certificate sessions! + match self.certificate { + Some(_) => self.tls_client_connection.clone().upcast::(), None => self.socket_connection.clone().upcast::(), } } - - /// Get [TlsClientConnection](https://docs.gtk.org/gio/iface.TlsClientConnection.html) for `Self` - /// * compatible with both user and guest connection types - pub fn tls_client_connection(&self) -> Result { - match self.tls_client_connection.clone() { - // User session - Some(tls_client_connection) => Ok(tls_client_connection), - // Guest session - None => { - // Create new wrapper for `IOStream` to interact `TlsClientConnection` API - match TlsClientConnection::new( - self.stream().as_ref(), - self.server_identity.as_ref(), - ) { - Ok(tls_client_connection) => Ok(tls_client_connection), - Err(e) => Err(Error::TlsClientConnection(e)), - } - } - } - } -} - -// Tools - -pub fn new_tls_client_connection( - socket_connection: &SocketConnection, - certificate: &TlsCertificate, - server_identity: Option<&NetworkAddress>, -) -> Result { - if socket_connection.is_closed() { - return Err(Error::Closed); - } - - // https://geminiprotocol.net/docs/protocol-specification.gmi#the-use-of-tls - match TlsClientConnection::new(socket_connection, server_identity) { - Ok(tls_client_connection) => { - // https://geminiprotocol.net/docs/protocol-specification.gmi#client-certificates - tls_client_connection.set_certificate(certificate); - - // @TODO handle exceptions - // https://geminiprotocol.net/docs/protocol-specification.gmi#closing-connections - tls_client_connection.set_require_close_notify(true); - - // @TODO host validation - // https://geminiprotocol.net/docs/protocol-specification.gmi#tls-server-certificate-validation - tls_client_connection.connect_accept_certificate(move |_, _, _| true); - - Ok(tls_client_connection) - } - Err(e) => Err(Error::TlsClientConnection(e)), - } } diff --git a/src/client/error.rs b/src/client/error.rs index bbb159a..944ba90 100644 --- a/src/client/error.rs +++ b/src/client/error.rs @@ -9,7 +9,6 @@ pub enum Error { OutputStream(glib::Error), Request(glib::Error), Response(crate::client::response::Error), - Session(crate::client::session::Error), } impl Display for Error { @@ -36,9 +35,6 @@ impl Display for Error { Self::Response(e) => { write!(f, "Response error: {e}") } - Self::Session(e) => { - write!(f, "Session error: {e}") - } } } } diff --git a/src/client/session.rs b/src/client/session.rs deleted file mode 100644 index 2e918d2..0000000 --- a/src/client/session.rs +++ /dev/null @@ -1,98 +0,0 @@ -mod error; -pub use error::Error; - -use super::Connection; -use gio::{ - prelude::{TlsCertificateExt, TlsConnectionExt}, - TlsCertificate, -}; -use glib::{Uri, UriHideFlags}; -use std::{cell::RefCell, collections::HashMap, rc::Rc}; - -/// Request sessions holder -/// * useful to keep connections open in async context and / or validate TLS certificate updates in runtime -pub struct Session { - index: RefCell>>, -} - -impl Default for Session { - fn default() -> Self { - Self::new() - } -} - -impl Session { - pub fn new() -> Self { - Self { - index: RefCell::new(HashMap::new()), - } - } - - pub fn set(&self, request: String, connection: Rc) -> Option> { - self.index.borrow_mut().insert(request, connection) - } - - /// Update existing session match [scope](https://geminiprotocol.net/docs/protocol-specification.gmi#status-60) - /// for given [Uri](https://docs.gtk.org/glib/struct.Uri.html) - /// and [TlsCertificate](https://docs.gtk.org/gio/class.TlsCertificate.html) - /// - /// * force rehandshake on user certificate change in runtime (ignore default session resumption by Glib TLS backend) - pub fn update(&self, uri: &Uri, certificate: Option<&TlsCertificate>) -> Result<(), Error> { - // Get cached `Client` connections match `uri` scope - // https://geminiprotocol.net/docs/protocol-specification.gmi#status-60 - for (request, connection) in self.index.borrow().iter() { - if request.starts_with( - uri.to_string_partial(UriHideFlags::QUERY | UriHideFlags::FRAGMENT) - .as_str(), - ) { - // Begin re-handshake on `certificate` change - match connection.tls_client_connection { - // User certificate session - Some(ref tls_client_connection) => { - match certificate { - Some(new) => { - // Get previous certificate - if let Some(ref old) = tls_client_connection.certificate() { - // User -> User - if !new.is_same(old) { - rehandshake(connection.as_ref()); - } - } - } - // User -> Guest - None => rehandshake(connection.as_ref()), - } - } - // Guest - None => { - // Guest -> User - if certificate.is_some() { - rehandshake(connection.as_ref()) - } - } - } - } - - // Cancel previous session operations - if let Err(e) = connection.cancel() { - return Err(Error::Connection(e)); - } - - // Close previous session connections - if let Err(e) = connection.close() { - return Err(Error::Connection(e)); - } - } - Ok(()) - } -} - -// Tools - -/// Applies re-handshake to `Connection` -/// to prevent default session resumption on user certificate change in runtime -pub fn rehandshake(connection: &Connection) { - if let Err(e) = connection.rehandshake() { - println!("warning: {e}"); // @TODO keep in mind until solution for TLS 1.3 - } -} diff --git a/src/client/session/error.rs b/src/client/session/error.rs deleted file mode 100644 index a974d1a..0000000 --- a/src/client/session/error.rs +++ /dev/null @@ -1,16 +0,0 @@ -use std::fmt::{Display, Formatter, Result}; - -#[derive(Debug)] -pub enum Error { - Connection(crate::client::connection::Error), -} - -impl Display for Error { - fn fmt(&self, f: &mut Formatter) -> Result { - match self { - Self::Connection(e) => { - write!(f, "Connection error: {e}") - } - } - } -}