@@ -285,10 +285,40 @@ impl RawClient<ElectrumSslStream> {
285285 . connect ( & domain, stream)
286286 . map_err ( Error :: SslHandshakeError ) ?;
287287
288+ if !validate_domain {
289+ let store = TofuData :: setup ( )
290+ . map_err ( |e| Error :: TofuPersistError ( e. to_string ( ) ) ) ?;
291+
292+ if let Some ( peer_cert) = stream. ssl ( ) . peer_certificate ( ) {
293+ let der = peer_cert. to_der ( )
294+ . map_err ( |e| Error :: TofuPersistError ( e. to_string ( ) ) ) ?;
295+
296+ match store. getData ( & domain) . map_err ( |e| Error :: TofuPersistError ( e. to_string ( ) ) ) ? {
297+ Some ( saved_der) => {
298+ if saved_der != der {
299+ return Err ( Error :: TlsCertificateChanged ( domain) ) ;
300+ }
301+ }
302+ None => {
303+ // first time: persist certificate
304+ store
305+ . setData ( & domain, der)
306+ . map_err ( |e| Error :: TofuPersistError ( e. to_string ( ) ) ) ?;
307+ }
308+ }
309+ } else {
310+ return Err ( Error :: TofuPersistError (
311+ "Peer Certificate not available" . to_string ( ) ,
312+ ) ) ;
313+ }
314+ }
315+
288316 Ok ( stream. into ( ) )
289317 }
290318}
291319
320+
321+
292322#[ cfg( all(
293323 any(
294324 feature = "default" ,
@@ -299,10 +329,12 @@ impl RawClient<ElectrumSslStream> {
299329) ) ]
300330mod danger {
301331 use crate :: raw_client:: ServerName ;
332+ use crate :: tofu:: TofuData ;
302333 use rustls:: client:: danger:: { HandshakeSignatureValid , ServerCertVerified } ;
303334 use rustls:: crypto:: CryptoProvider ;
304335 use rustls:: pki_types:: { CertificateDer , UnixTime } ;
305336 use rustls:: DigitallySignedStruct ;
337+ use std:: sync:: { Arc , Mutex } ;
306338
307339 #[ derive( Debug ) ]
308340 pub struct NoCertificateVerification ( CryptoProvider ) ;
@@ -347,6 +379,94 @@ mod danger {
347379 self . 0 . signature_verification_algorithms . supported_schemes ( )
348380 }
349381 }
382+
383+ #[ derive( Debug ) ]
384+ pub struct TofuVerifier {
385+ provider : CryptoProvider ,
386+ host : String ,
387+ tofu_store : Arc < Mutex < Option < Arc < TofuData > > > > ,
388+ }
389+
390+ impl TofuVerifier {
391+ pub fn new ( provider : CryptoProvider , host : String ) -> Self {
392+ Self {
393+ provider,
394+ host,
395+ tofu_store : Arc :: new ( Mutex :: new ( None ) ) ,
396+ }
397+ }
398+
399+ fn get_tofu_store ( & self ) -> Result < Arc < TofuData > , crate :: Error > {
400+ let mut store = self . tofu_store . lock ( ) . unwrap ( ) ;
401+ if store. is_none ( ) {
402+ * store = Some ( Arc :: new (
403+ TofuData :: setup ( )
404+ . map_err ( |e| crate :: Error :: TofuPersistError ( e. to_string ( ) ) ) ?,
405+ ) ) ;
406+ }
407+ Ok ( store. as_ref ( ) . unwrap ( ) . clone ( ) )
408+ }
409+
410+ fn verify_tofu ( & self , cert_der : & [ u8 ] ) -> Result < ( ) , crate :: Error > {
411+ let store = self . get_tofu_store ( ) ?;
412+ match store
413+ . getData ( & self . host )
414+ . map_err ( |e| crate :: Error :: TofuPersistError ( e. to_string ( ) ) ) ?
415+ {
416+ Some ( saved_der) => {
417+ if saved_der != cert_der {
418+ return Err ( crate :: Error :: TlsCertificateChanged ( self . host . clone ( ) ) ) ;
419+ }
420+ }
421+ None => {
422+ // First time: persist certificate.
423+ store
424+ . setData ( & self . host , cert_der. to_vec ( ) )
425+ . map_err ( |e| crate :: Error :: TofuPersistError ( e. to_string ( ) ) ) ?;
426+ }
427+ }
428+
429+ Ok ( ( ) )
430+ }
431+ }
432+
433+ impl rustls:: client:: danger:: ServerCertVerifier for TofuVerifier {
434+ fn verify_server_cert (
435+ & self ,
436+ end_entity : & CertificateDer < ' _ > ,
437+ _intermediates : & [ CertificateDer < ' _ > ] ,
438+ _server_name : & ServerName < ' _ > ,
439+ _ocsp : & [ u8 ] ,
440+ _now : UnixTime ,
441+ ) -> Result < ServerCertVerified , rustls:: Error > {
442+ // Verify using TOFU
443+ self . verify_tofu ( end_entity. as_ref ( ) )
444+ . map_err ( |e| rustls:: Error :: General ( format ! ( "{:?}" , e) ) ) ?;
445+ Ok ( ServerCertVerified :: assertion ( ) )
446+ }
447+
448+ fn verify_tls12_signature (
449+ & self ,
450+ _message : & [ u8 ] ,
451+ _cert : & CertificateDer < ' _ > ,
452+ _dss : & DigitallySignedStruct ,
453+ ) -> Result < HandshakeSignatureValid , rustls:: Error > {
454+ Ok ( HandshakeSignatureValid :: assertion ( ) )
455+ }
456+
457+ fn verify_tls13_signature (
458+ & self ,
459+ _message : & [ u8 ] ,
460+ _cert : & CertificateDer < ' _ > ,
461+ _dss : & DigitallySignedStruct ,
462+ ) -> Result < HandshakeSignatureValid , rustls:: Error > {
463+ Ok ( HandshakeSignatureValid :: assertion ( ) )
464+ }
465+
466+ fn supported_verify_schemes ( & self ) -> Vec < rustls:: SignatureScheme > {
467+ self . provider . signature_verification_algorithms . supported_schemes ( )
468+ }
469+ }
350470}
351471
352472#[ cfg( all(
@@ -381,6 +501,7 @@ impl RawClient<ElectrumSslStream> {
381501 validate_domain,
382502 timeout
383503 ) ;
504+
384505 if validate_domain {
385506 socket_addrs. domain ( ) . ok_or ( Error :: MissingDomain ) ?;
386507 }
@@ -431,6 +552,7 @@ impl RawClient<ElectrumSslStream> {
431552
432553 let builder = ClientConfig :: builder ( ) ;
433554
555+ let domain = socket_addr. domain ( ) . unwrap_or ( "NONE" ) . to_string ( ) ;
434556 let config = if validate_domain {
435557 socket_addr. domain ( ) . ok_or ( Error :: MissingDomain ) ?;
436558
@@ -450,20 +572,28 @@ impl RawClient<ElectrumSslStream> {
450572 . dangerous ( )
451573 . with_custom_certificate_verifier ( std:: sync:: Arc :: new (
452574 #[ cfg( all( feature = "use-rustls" , not( feature = "use-rustls-ring" ) ) ) ]
453- danger:: NoCertificateVerification :: new ( rustls:: crypto:: aws_lc_rs:: default_provider ( ) ) ,
575+ danger:: TofuVerifier :: new ( rustls:: crypto:: aws_lc_rs:: default_provider ( ) , domain . clone ( ) ) ,
454576 #[ cfg( feature = "use-rustls-ring" ) ]
455- danger:: NoCertificateVerification :: new ( rustls:: crypto:: ring:: default_provider ( ) ) ,
577+ danger:: TofuVerifier :: new ( rustls:: crypto:: ring:: default_provider ( ) , domain . clone ( ) ) ,
456578 ) )
457579 . with_no_client_auth ( )
458580 } ;
459581
460- let domain = socket_addr. domain ( ) . unwrap_or ( "NONE" ) . to_string ( ) ;
461582 let session = ClientConnection :: new (
462583 std:: sync:: Arc :: new ( config) ,
463584 ServerName :: try_from ( domain. clone ( ) )
464585 . map_err ( |_| Error :: InvalidDNSNameError ( domain. clone ( ) ) ) ?,
465586 )
466- . map_err ( Error :: CouldNotCreateConnection ) ?;
587+ . map_err ( |e| {
588+ let error_msg = format ! ( "{}" , e) ;
589+ if error_msg. contains ( "TLS certificate changed" ) {
590+ Error :: TlsCertificateChanged ( domain. clone ( ) )
591+ } else if error_msg. contains ( "TOFU" ) {
592+ Error :: TofuPersistError ( error_msg)
593+ } else {
594+ Error :: CouldNotCreateConnection ( e)
595+ }
596+ } ) ?;
467597 let stream = StreamOwned :: new ( session, tcp_stream) ;
468598
469599 Ok ( stream. into ( ) )
0 commit comments