dnp3/tcp/tls/
outstation.rs

1use sfio_rustls_config::ClientNameVerification;
2use std::path::Path;
3use std::sync::Arc;
4
5use tokio_rustls::rustls;
6
7use crate::app::{ConnectStrategy, Listener};
8use crate::link::LinkErrorMode;
9use crate::outstation::task::OutstationTask;
10use crate::outstation::{
11    ControlHandler, OutstationApplication, OutstationConfig, OutstationHandle,
12    OutstationInformation,
13};
14use crate::tcp::client::ClientTask;
15use crate::tcp::tls::{CertificateMode, MinTlsVersion, TlsClientConfig, TlsError};
16use crate::tcp::{ClientState, ConnectOptions, EndpointList, PostConnectionHandler};
17use crate::util::phys::{PhysAddr, PhysLayer};
18use crate::util::session::{Enabled, Session};
19use tokio::net::TcpStream;
20use tokio_rustls::rustls::pki_types::ServerName;
21
22use crate::link::reader::LinkModes;
23use tracing::Instrument;
24
25/// Spawn a TLS client task onto the `Tokio` runtime. The task runs until the returned handle is dropped.
26///
27/// **Note**: This function may only be called from within the runtime itself, and panics otherwise.
28/// Use Runtime::enter() if required.
29#[allow(clippy::too_many_arguments)]
30pub fn spawn_outstation_tls_client(
31    link_error_mode: LinkErrorMode,
32    endpoints: EndpointList,
33    connect_strategy: ConnectStrategy,
34    connect_options: ConnectOptions,
35    config: OutstationConfig,
36    application: Box<dyn OutstationApplication>,
37    information: Box<dyn OutstationInformation>,
38    control_handler: Box<dyn ControlHandler>,
39    listener: Box<dyn Listener<ClientState>>,
40    tls_config: TlsClientConfig,
41) -> OutstationHandle {
42    let main_addr = endpoints.main_addr().to_string();
43    let (task, handle) = OutstationTask::create(
44        Enabled::No,
45        LinkModes::stream(link_error_mode),
46        config,
47        PhysAddr::None,
48        application,
49        information,
50        control_handler,
51    );
52    let session = Session::outstation(task);
53    let mut client = ClientTask::new(
54        session,
55        endpoints,
56        connect_strategy,
57        connect_options,
58        PostConnectionHandler::Tls(tls_config),
59        listener,
60    );
61
62    let future = async move {
63        client
64            .run()
65            .instrument(tracing::info_span!("dnp3-outstation-tls-client", "endpoint" = ?main_addr))
66            .await;
67    };
68    tokio::spawn(future);
69    handle
70}
71
72/// TLS configuration for a server
73pub struct TlsServerConfig {
74    config: Arc<rustls::ServerConfig>,
75}
76
77impl TlsServerConfig {
78    /// Legacy method of creating a TLS server configuration
79    #[deprecated(
80        since = "1.4.1",
81        note = "Please use `full_pki` or `self_signed` instead"
82    )]
83    pub fn new(
84        client_subject_name: &str,
85        peer_cert_path: &Path,
86        local_cert_path: &Path,
87        private_key_path: &Path,
88        password: Option<&str>,
89        min_tls_version: MinTlsVersion,
90        certificate_mode: CertificateMode,
91    ) -> Result<Self, TlsError> {
92        match certificate_mode {
93            CertificateMode::AuthorityBased => Self::full_pki(
94                Some(client_subject_name.to_string()),
95                peer_cert_path,
96                local_cert_path,
97                private_key_path,
98                password,
99                min_tls_version,
100            ),
101            CertificateMode::SelfSigned => Self::self_signed(
102                peer_cert_path,
103                local_cert_path,
104                private_key_path,
105                password,
106                min_tls_version,
107            ),
108        }
109    }
110
111    /// Create a TLS server configuration that expects a full PKI with an authority, and possibly
112    /// intermediate CA certificates.
113    ///
114    /// If `client_subject_name` is specified, than the server will verify name is present in the
115    /// SAN extension or in the Common Name of the client certificate.
116    ///
117    /// If `client_subject_name` is set to None, then no client name validate is performed, and
118    /// any authenticated client is allowed.
119    pub fn full_pki(
120        client_subject_name: Option<String>,
121        peer_cert_path: &Path,
122        local_cert_path: &Path,
123        private_key_path: &Path,
124        password: Option<&str>,
125        min_tls_version: MinTlsVersion,
126    ) -> Result<Self, TlsError> {
127        let name_verification = match client_subject_name {
128            None => ClientNameVerification::None,
129            Some(name) => {
130                let name: ServerName<'static> = name.try_into()?;
131                ClientNameVerification::SanOrCommonName(name)
132            }
133        };
134
135        let config = sfio_rustls_config::server::authority(
136            min_tls_version.into(),
137            name_verification,
138            peer_cert_path,
139            local_cert_path,
140            private_key_path,
141            password,
142        )?;
143
144        Ok(Self {
145            config: Arc::new(config),
146        })
147    }
148
149    /// Create a TLS server configuration that expects the client to present a single certificate.
150    ///
151    /// In lieu of performing client subject name validation, the server validates:
152    ///
153    /// 1) That the client presents a single certificate
154    /// 2) That the certificate is a byte-for-byte match with the one loaded in `peer_cert_path`.
155    /// 3) That the certificate's Validity (not before / not after) is currently valid.
156    ///
157    pub fn self_signed(
158        peer_cert_path: &Path,
159        local_cert_path: &Path,
160        private_key_path: &Path,
161        password: Option<&str>,
162        min_tls_version: MinTlsVersion,
163    ) -> Result<Self, TlsError> {
164        let config = sfio_rustls_config::server::self_signed(
165            min_tls_version.into(),
166            peer_cert_path,
167            local_cert_path,
168            private_key_path,
169            password,
170        )?;
171
172        Ok(Self {
173            config: Arc::new(config),
174        })
175    }
176
177    pub(crate) async fn handle_connection(
178        &mut self,
179        socket: TcpStream,
180    ) -> Result<PhysLayer, String> {
181        let connector = tokio_rustls::TlsAcceptor::from(self.config.clone());
182        match connector.accept(socket).await {
183            Err(err) => Err(format!("failed to establish TLS session: {err}")),
184            Ok(stream) => Ok(PhysLayer::Tls(Box::new(tokio_rustls::TlsStream::from(
185                stream,
186            )))),
187        }
188    }
189}