diff options
-rw-r--r-- | src/client.rs | 4 | ||||
-rw-r--r-- | src/client/certificate.rs | 104 |
2 files changed, 65 insertions, 43 deletions
diff --git a/src/client.rs b/src/client.rs index a7a12f2..201a795 100644 --- a/src/client.rs +++ b/src/client.rs @@ -30,7 +30,7 @@ use crate::{QuinnetError, DEFAULT_KILL_MESSAGE_QUEUE_SIZE, DEFAULT_MESSAGE_QUEUE use self::certificate::{ load_known_hosts_store_from_config, CertVerificationStatus, CertVerifierAction, CertificateFingerprint, CertificateInteractionEvent, CertificateUpdateEvent, - CertificateVerificationMode, ServName, SkipServerVerification, TofuServerVerification, + CertificateVerificationMode, ServerName, SkipServerVerification, TofuServerVerification, }; pub mod certificate; @@ -103,7 +103,7 @@ pub(crate) enum InternalAsyncMessage { action_sender: oneshot::Sender<CertVerifierAction>, }, TrustedCertificateUpdate { - server_name: ServName, + server_name: ServerName, fingerprint: CertificateFingerprint, }, } diff --git a/src/client/certificate.rs b/src/client/certificate.rs index a359b57..44838ff 100644 --- a/src/client/certificate.rs +++ b/src/client/certificate.rs @@ -10,7 +10,7 @@ use std::{ use bevy::prelude::warn; use futures::executor::block_on; -use rustls::ServerName; +use rustls::ServerName as RustlsServerName; use tokio::sync::{mpsc, oneshot}; use crate::QuinnetError; @@ -22,6 +22,7 @@ pub const DEFAULT_CERT_VERIFIER_BEHAVIOUR: CertVerifierBehaviour = /// Event raised when a user/app interaction is needed for the server's certificate validation pub struct CertificateInteractionEvent { + /// The current status of the verification pub status: CertVerificationStatus, /// Mutex for interior mutability pub(crate) action_sender: Mutex<Option<oneshot::Sender<CertVerifierAction>>>, @@ -38,7 +39,9 @@ impl CertificateInteractionEvent { /// Event raised when a new certificate is trusted pub struct CertificateUpdateEvent { - pub server_name: ServName, + /// Identifies the server name + pub server_name: ServerName, + /// Fingerprint of the server's certificate pub fingerprint: CertificateFingerprint, } @@ -49,44 +52,32 @@ pub enum CertificateVerificationMode { SkipVerification, /// Client will only trust a server certificate signed by a conventional certificate authority SignedByCertificateAuthority, - /// The client will look up the server identifier in [`KnownHosts`]. - /// TODO Revamp doc - /// - If no identifier exists yet for this server, the client will accept the given server's certificate and return it. - /// - If some certificate already existed for this server, and the received one is different, an error will be raised. + /// The client will use a Trust on first authentication scheme (<https://en.wikipedia.org/wiki/Trust_on_first_use>) configured by a [`TrustOnFirstUseConfig`]. TrustOnFirstUse(TrustOnFirstUseConfig), } -#[derive(Debug, Clone, Eq, PartialEq, Hash)] -pub enum CertVerificationStatus { - /// First time connecting to this host. - UnknownCertificate, - /// The certificate fingerprint does not match the one in the known hosts fingerprints store. - UntrustedCertificate, - /// Known host and certificate matching the one in the known hosts fingerprints store. - TrustedCertificate, -} - -#[derive(Clone, Debug, Eq, Hash, PartialEq)] -pub struct ServName(ServerName); - -impl fmt::Display for ServName { - #[inline] - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match &self.0 { - ServerName::DnsName(dns) => fmt::Display::fmt(dns.as_ref(), f), - ServerName::IpAddress(ip) => fmt::Display::fmt(&ip, f), - _ => todo!(), - } - } -} - +/// Configuration of the Trust on first authentication scheme for server certificates +/// +/// # Example +/// +/// ``` +/// TrustOnFirstUseConfig { +/// known_hosts: bevy_quinnet::client::certificate::KnownHosts::HostsFile( +/// "my_own_hosts_file".to_string(), +/// ), +/// ..Default::default() +/// } +/// ``` #[derive(Debug, Clone)] pub struct TrustOnFirstUseConfig { - pub(crate) known_hosts: KnownHosts, - pub(crate) verifier_behaviour: HashMap<CertVerificationStatus, CertVerifierBehaviour>, + /// known_hosts stores all the already known and trusted endpoints + pub known_hosts: KnownHosts, + /// verifier_behaviour stores the [`CertVerifierBehaviour`] that the certificate verifier will adopt for each possible [`CertVerificationStatus`] + pub verifier_behaviour: HashMap<CertVerificationStatus, CertVerifierBehaviour>, } impl Default for TrustOnFirstUseConfig { + /// Returns the default [`TrustOnFirstUseConfig`] fn default() -> Self { TrustOnFirstUseConfig { known_hosts: KnownHosts::HostsFile(DEFAULT_KNOWN_HOSTS_FILE.to_string()), @@ -108,6 +99,32 @@ impl Default for TrustOnFirstUseConfig { } } +/// Status of the server's certificate verification. +#[derive(Debug, Clone, Eq, PartialEq, Hash)] +pub enum CertVerificationStatus { + /// First time connecting to this host. + UnknownCertificate, + /// The certificate fingerprint does not match the one in the known hosts fingerprints store. + UntrustedCertificate, + /// This is a known host and the certificate is matching the one in the known hosts fingerprints store. + TrustedCertificate, +} + +/// Encodes ways a client can know the expected name of the server. See [`rustls::ServerName`] +#[derive(Clone, Debug, Eq, Hash, PartialEq)] +pub struct ServerName(RustlsServerName); + +impl fmt::Display for ServerName { + #[inline] + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match &self.0 { + RustlsServerName::DnsName(dns) => fmt::Display::fmt(dns.as_ref(), f), + RustlsServerName::IpAddress(ip) => fmt::Display::fmt(&ip, f), + _ => todo!(), + } + } +} + #[derive(Debug, Clone, Eq, PartialEq)] pub enum CertVerifierBehaviour { /// Raises an event to the client app (containing the cert info) and waits for an API call @@ -120,9 +137,10 @@ pub enum CertVerifierBehaviour { pub enum CertVerifierAction { /// Abort the connection and raise an error (containing the cert info) AbortConnection, - /// Continue the connection discarding the cert info + /// Accept the server's certificate and continue the connection, but discard the certificate's info. They will not be stored nor available as an event. TrustOnce, - /// Continue the connection and add the cert info to the store + /// Accept the server's certificate and continue the connection. A [`CertificateUpdateEvent`] will be raised containing the certificate's info. + /// If the certificate store ([`KnownHosts`]) is a file, this action also adds the certificate's info to the store file. Else it is up to the user to update its own store with the content of [`CertificateUpdateEvent`]. TrustAndStore, } @@ -145,12 +163,16 @@ impl fmt::Display for CertificateFingerprint { } } -pub type CertStore = HashMap<ServName, CertificateFingerprint>; +/// Certificate fingerprint storage +pub type CertStore = HashMap<ServerName, CertificateFingerprint>; +/// Certificate fingerprint storage as a value or as a file #[derive(Debug, Clone)] pub enum KnownHosts { + /// Directly contains the server name to fingerprint mapping Store(CertStore), - HostsFile(String), + /// Path of a file caontaing the server name to fingerprint mapping. + HostsFile(String), // TODO More on the file format + the limitations } /// Implementation of `ServerCertVerifier` that verifies everything as trustworthy. @@ -204,7 +226,7 @@ impl TofuServerVerification { fn apply_verifier_behaviour_for_status( &self, status: CertVerificationStatus, - server_name: &ServName, + server_name: &ServerName, fingerprint: CertificateFingerprint, ) -> Result<rustls::client::ServerCertVerified, rustls::Error> { let behaviour = self @@ -239,7 +261,7 @@ impl TofuServerVerification { fn apply_verifier_immediate_action( &self, action: &CertVerifierAction, - server_name: &ServName, + server_name: &ServerName, fingerprint: CertificateFingerprint, ) -> Result<rustls::client::ServerCertVerified, rustls::Error> { match action { @@ -289,7 +311,7 @@ impl rustls::client::ServerCertVerifier for TofuServerVerification { // TODO Could add some optional validity checks on the cert content. let status; let fingerprint = CertificateFingerprint::from(_end_entity); - let server_name = ServName(_server_name.clone()); + let server_name = ServerName(_server_name.clone()); if let Some(known_fingerprint) = self.store.get(&server_name) { if *known_fingerprint == fingerprint { status = Some(CertVerificationStatus::TrustedCertificate); @@ -323,11 +345,11 @@ fn store_known_hosts_to_file(file: &String, store: &CertStore) -> Result<(), Box fn parse_known_host_line( line: String, -) -> Result<(ServName, CertificateFingerprint), Box<dyn Error>> { +) -> Result<(ServerName, CertificateFingerprint), Box<dyn Error>> { let mut parts = line.split_whitespace(); let adr_str = parts.next().ok_or(QuinnetError::InvalidHostFile)?; - let serv_name = ServName(ServerName::try_from(adr_str)?); + let serv_name = ServerName(RustlsServerName::try_from(adr_str)?); let fingerprint_b64 = parts.next().ok_or(QuinnetError::InvalidHostFile)?; let fingerprint_bytes = base64::decode(&fingerprint_b64)?; |