@@ -2,12 +2,14 @@ use crate::MqttMessage;
22use crate :: TopicFilter ;
33use certificate:: parse_root_certificate;
44use certificate:: CertificateError ;
5- use log:: debug;
65use rumqttc:: tokio_rustls:: rustls;
76use rumqttc:: tokio_rustls:: rustls:: pki_types:: CertificateDer ;
87use rumqttc:: LastWill ;
98use std:: fmt:: Debug ;
109use std:: fmt:: Formatter ;
10+ use std:: fs:: File ;
11+ use std:: io:: BufRead ;
12+ use std:: io:: BufReader ;
1113use std:: ops:: Deref ;
1214use std:: path:: Path ;
1315use std:: sync:: Arc ;
@@ -74,7 +76,7 @@ pub struct BrokerConfig {
7476 /// Default: 1883
7577 pub port : u16 ,
7678
77- /// Certificate authentication configuration
79+ /// Authentication configuration
7880 pub authentication : Option < AuthenticationConfig > ,
7981}
8082
@@ -88,35 +90,114 @@ pub struct BrokerConfig {
8890/// 3. server and client authentication - clients will verify MQTT broker
8991/// certificate and broker will verify client certificates
9092///
93+ /// In addition, supporting username/password authentication with any combinations.
94+ ///
9195/// [mosquitto]: https://mosquitto.org/man/mosquitto-conf-5.html#authentication
9296#[ derive( Debug , Clone ) ]
9397pub struct AuthenticationConfig {
9498 /// Trusted root certificate store used to verify broker certificate
9599 cert_store : rustls:: RootCertStore ,
96100
97- /// Client authentication configuration
98- client_auth : Option < ClientAuthConfig > ,
101+ /// Client certificate and key
102+ cert_config : Option < ClientAuthCertConfig > ,
103+
104+ /// Client username
105+ username : Option < String > ,
106+
107+ /// Client password: it can be set only when username is set due to the MQTT specification.
108+ /// Therefore, the value can be read only via API.
109+ password : Option < Zeroizing < String > > ,
99110}
100111
101112impl Default for AuthenticationConfig {
102113 fn default ( ) -> Self {
103114 AuthenticationConfig {
104115 cert_store : rustls:: RootCertStore :: empty ( ) ,
105- client_auth : None ,
116+ cert_config : None ,
117+ username : None ,
118+ password : None ,
119+ }
120+ }
121+ }
122+
123+ impl AuthenticationConfig {
124+ pub fn get_cert_store_mut ( & mut self ) -> & mut rustls:: RootCertStore {
125+ & mut self . cert_store
126+ }
127+
128+ pub fn set_cert_config (
129+ & mut self ,
130+ cert_path : impl AsRef < Path > ,
131+ key_path : impl AsRef < Path > ,
132+ ) -> Result < ( ) , CertificateError > {
133+ let cert_config = ClientAuthCertConfig :: new ( cert_path. as_ref ( ) , key_path. as_ref ( ) ) ?;
134+ self . cert_config = Some ( cert_config) ;
135+ Ok ( ( ) )
136+ }
137+
138+ pub fn set_username ( & mut self , username : String ) {
139+ self . username = Some ( username) ;
140+ }
141+
142+ pub fn set_password ( & mut self , password : Zeroizing < String > ) {
143+ self . password = Some ( password) ;
144+ }
145+
146+ pub fn to_rustls_client_config ( & self ) -> Result < Option < rustls:: ClientConfig > , rustls:: Error > {
147+ if self . cert_store . is_empty ( ) {
148+ return Ok ( None ) ;
149+ }
150+
151+ let tls_config =
152+ rustls:: ClientConfig :: builder ( ) . with_root_certificates ( self . cert_store . clone ( ) ) ;
153+
154+ let tls_config = match & self . cert_config {
155+ Some ( cert_config) => tls_config. with_client_auth_cert (
156+ cert_config. cert_chain . clone ( ) ,
157+ cert_config. key . deref ( ) . 0 . clone_key ( ) ,
158+ ) ?,
159+ None => tls_config. with_no_client_auth ( ) ,
160+ } ;
161+ Ok ( Some ( tls_config) )
162+ }
163+
164+ /// When the password is empty, this returns an empty string.
165+ /// This is because `rumqttc::MqttOptions::set_credentials()` always requires a value for password.
166+ pub fn get_credentials ( & self ) -> Option < ( String , Zeroizing < String > ) > {
167+ match & self . username {
168+ Some ( username) => {
169+ let password = self . password . clone ( ) . unwrap_or_default ( ) ;
170+ Some ( ( username. to_string ( ) , password) )
171+ }
172+ None => None ,
106173 }
107174 }
108175}
109176
110177#[ derive( Clone ) ]
111- struct ClientAuthConfig {
178+ struct ClientAuthCertConfig {
112179 cert_chain : Vec < CertificateDer < ' static > > ,
113180 key : Arc < Zeroizing < PrivateKey > > ,
114181}
115182
116- impl Debug for ClientAuthConfig {
183+ impl ClientAuthCertConfig {
184+ pub fn new (
185+ cert_path : impl AsRef < Path > ,
186+ key_path : impl AsRef < Path > ,
187+ ) -> Result < Self , CertificateError > {
188+ let cert_chain = parse_root_certificate:: read_cert_chain ( cert_path) ?;
189+ let key = parse_root_certificate:: read_pvt_key ( key_path) ?;
190+ Ok ( ClientAuthCertConfig {
191+ cert_chain,
192+ key : Arc :: new ( Zeroizing :: new ( PrivateKey ( key) ) ) ,
193+ } )
194+ }
195+ }
196+
197+ impl Debug for ClientAuthCertConfig {
117198 fn fmt ( & self , f : & mut Formatter < ' _ > ) -> std:: fmt:: Result {
118- f. debug_struct ( "ClientAuthConfig " )
119- . field ( "cert_chain" , & self . cert_chain )
199+ f. debug_struct ( "ClientAuthCertConfig " )
200+ . field ( "cert_chain" , & self )
120201 . finish ( )
121202 }
122203}
@@ -270,58 +351,11 @@ impl Config {
270351 }
271352 }
272353
273- /// Adds all certificates present in `ca_file` file to the trust store.
274- /// Enables server authentication.
275- pub fn with_cafile (
354+ pub fn with_client_auth (
276355 & mut self ,
277- ca_file : impl AsRef < Path > ,
356+ config : AuthenticationConfig ,
278357 ) -> Result < & mut Self , certificate:: CertificateError > {
279- debug ! ( target: "MQTT" , "Using CA certificate: {}" , ca_file. as_ref( ) . display( ) ) ;
280- let authentication_config = self . broker . authentication . get_or_insert ( Default :: default ( ) ) ;
281- let cert_store = & mut authentication_config. cert_store ;
282-
283- parse_root_certificate:: add_certs_from_file ( cert_store, ca_file) ?;
284-
285- Ok ( self )
286- }
287-
288- /// Adds all certificate from all files in the directory `ca_dir` to the
289- /// trust store. Enables server authentication.
290- pub fn with_cadir (
291- & mut self ,
292- ca_dir : impl AsRef < Path > ,
293- ) -> Result < & mut Self , certificate:: CertificateError > {
294- debug ! ( target: "MQTT" , "Using CA directory: {}" , ca_dir. as_ref( ) . display( ) ) ;
295- let authentication_config = self . broker . authentication . get_or_insert ( Default :: default ( ) ) ;
296- let cert_store = & mut authentication_config. cert_store ;
297-
298- parse_root_certificate:: add_certs_from_directory ( cert_store, ca_dir) ?;
299-
300- Ok ( self )
301- }
302-
303- /// Provide client certificate and private key for authentication. If server
304- /// authentication was not enabled by previously calling
305- /// [`Config::with_cafile`] or [`Config::with_cadir`], this method also
306- /// enables it but initializes an empty root cert store.
307- pub fn with_client_auth < P : AsRef < Path > > (
308- & mut self ,
309- cert_file : P ,
310- key_file : P ,
311- ) -> Result < & mut Self , CertificateError > {
312- debug ! ( target: "MQTT" , "Using client certificate: {}" , cert_file. as_ref( ) . display( ) ) ;
313- debug ! ( target: "MQTT" , "Using client private key: {}" , key_file. as_ref( ) . display( ) ) ;
314- let cert_chain = parse_root_certificate:: read_cert_chain ( cert_file) ?;
315- let key = parse_root_certificate:: read_pvt_key ( key_file) ?;
316-
317- let client_auth_config = ClientAuthConfig {
318- cert_chain,
319- key : Arc :: new ( Zeroizing :: new ( PrivateKey ( key) ) ) ,
320- } ;
321-
322- let authentication_config = self . broker . authentication . get_or_insert ( Default :: default ( ) ) ;
323- authentication_config. client_auth = Some ( client_auth_config) ;
324-
358+ self . broker . authentication . get_or_insert ( config) ;
325359 Ok ( self )
326360 }
327361
@@ -347,18 +381,12 @@ impl Config {
347381 }
348382
349383 if let Some ( authentication_config) = & broker_config. authentication {
350- let tls_config = rustls:: ClientConfig :: builder ( )
351- . with_root_certificates ( authentication_config. cert_store . clone ( ) ) ;
352-
353- let tls_config = match authentication_config. client_auth . clone ( ) {
354- Some ( client_auth_config) => tls_config. with_client_auth_cert (
355- client_auth_config. cert_chain ,
356- client_auth_config. key . deref ( ) . 0 . clone_key ( ) ,
357- ) ?,
358- None => tls_config. with_no_client_auth ( ) ,
359- } ;
360-
361- mqtt_options. set_transport ( rumqttc:: Transport :: tls_with_config ( tls_config. into ( ) ) ) ;
384+ if let Some ( ( username, password) ) = authentication_config. get_credentials ( ) {
385+ mqtt_options. set_credentials ( username, password. clone ( ) . to_string ( ) ) ;
386+ }
387+ if let Some ( tls_config) = authentication_config. to_rustls_client_config ( ) ? {
388+ mqtt_options. set_transport ( rumqttc:: Transport :: tls_with_config ( tls_config. into ( ) ) ) ;
389+ }
362390 }
363391
364392 mqtt_options. set_max_packet_size ( MAX_PACKET_SIZE , MAX_PACKET_SIZE ) ;
@@ -376,3 +404,21 @@ impl Config {
376404 Ok ( mqtt_options)
377405 }
378406}
407+
408+ /// Read the first line of the given file and return it.
409+ pub fn read_password ( path : impl AsRef < Path > ) -> Result < Zeroizing < String > , CertificateError > {
410+ let f = File :: open ( & path) . map_err ( |error| CertificateError :: IoError {
411+ error,
412+ path : path. as_ref ( ) . to_owned ( ) ,
413+ } ) ?;
414+ let reader = BufReader :: new ( f) ;
415+
416+ match reader. lines ( ) . next ( ) {
417+ Some ( Ok ( password) ) => Ok ( Zeroizing :: new ( password) ) ,
418+ Some ( Err ( error) ) => Err ( CertificateError :: IoError {
419+ error,
420+ path : path. as_ref ( ) . to_owned ( ) ,
421+ } ) ,
422+ None => Ok ( Zeroizing :: new ( "" . to_string ( ) ) ) ,
423+ }
424+ }
0 commit comments