From 4520aa86961c8793d68668e2403441cba23f2332 Mon Sep 17 00:00:00 2001 From: nick evans Date: Thu, 15 Dec 2022 09:06:44 -0500 Subject: [PATCH] =?UTF-8?q?=F0=9F=94=92=20Add=20Net::IMAP#tls=5Fverified?= =?UTF-8?q?=3F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Returns true after the TLS negotiation has completed _and_ the remote hostname has been verified. This can be used, for example, by automated safeguards against selecting particular SASL mechanisms—or against authenticating at all—when TLS hasn't been established _and_ verified the peer. --- lib/net/imap.rb | 7 ++++++ test/net/imap/test_imap.rb | 44 ++++++++++++++++++++++++++++---------- 2 files changed, 40 insertions(+), 11 deletions(-) diff --git a/lib/net/imap.rb b/lib/net/imap.rb index 9a93c542..754d3282 100644 --- a/lib/net/imap.rb +++ b/lib/net/imap.rb @@ -696,6 +696,11 @@ class IMAP < Protocol # The port this client connected to attr_reader :port + # Returns true after the TLS negotiation has completed and the remote + # hostname has been verified. Returns false when TLS has been established + # but peer verification was disabled. + def tls_verified?; @tls_verified end + # Returns the debug mode. def self.debug return @@debug @@ -2261,6 +2266,7 @@ def initialize(host, port_or_options = {}, @utf8_strings = false @open_timeout = options[:open_timeout] || 30 @idle_response_timeout = options[:idle_response_timeout] || 5 + @tls_verified = false @parser = ResponseParser.new @sock = tcp_socket(@host, @port) begin @@ -2632,6 +2638,7 @@ def start_tls_session(params = {}) ssl_socket_connect(@sock, @open_timeout) if context.verify_mode != VERIFY_NONE @sock.post_connection_check(@host) + @tls_verified = true end end diff --git a/test/net/imap/test_imap.rb b/test/net/imap/test_imap.rb index 291a97b5..0f89691e 100644 --- a/test/net/imap/test_imap.rb +++ b/test/net/imap/test_imap.rb @@ -41,27 +41,41 @@ def test_imaps_unknown_ca end def test_imaps_with_ca_file + # Assert verified *after* the imaps_test and assert_nothing_raised blocks. + # Otherwise, failures can't logout and need to wait for the timeout. + verified, imap = :unknown, nil assert_nothing_raised do imaps_test do |port| - begin - Net::IMAP.new("localhost", - :port => port, - :ssl => { :ca_file => CA_FILE }) - rescue SystemCallError - skip $! - end + imap = Net::IMAP.new("localhost", + port: port, + ssl: { :ca_file => CA_FILE }) + verified = imap.tls_verified? + imap + rescue SystemCallError + skip $! end end + assert_equal true, verified + assert_equal true, imap.tls_verified? end def test_imaps_verify_none + # Assert verified *after* the imaps_test and assert_nothing_raised blocks. + # Otherwise, failures can't logout and need to wait for the timeout. + verified, imap = :unknown, nil assert_nothing_raised do imaps_test do |port| - Net::IMAP.new(server_addr, - :port => port, - :ssl => { :verify_mode => OpenSSL::SSL::VERIFY_NONE }) + imap = Net::IMAP.new( + server_addr, + port: port, + ssl: { :verify_mode => OpenSSL::SSL::VERIFY_NONE } + ) + verified = imap.tls_verified? + imap end end + assert_equal false, verified + assert_equal false, imap.tls_verified? end def test_imaps_post_connection_check @@ -79,12 +93,15 @@ def test_imaps_post_connection_check if defined?(OpenSSL::SSL) def test_starttls - imap = nil + verified, imap = :unknown, nil starttls_test do |port| imap = Net::IMAP.new("localhost", :port => port) imap.starttls(:ca_file => CA_FILE) + verified = imap.tls_verified? imap end + assert_equal true, verified + assert_equal true, imap.tls_verified? rescue SystemCallError skip $! ensure @@ -94,13 +111,17 @@ def test_starttls end def test_starttls_stripping + verified, imap = :unknown, nil starttls_stripping_test do |port| imap = Net::IMAP.new("localhost", :port => port) assert_raise(Net::IMAP::UnknownResponseError) do imap.starttls(:ca_file => CA_FILE) end + verified = imap.tls_verified? imap end + assert_equal false, verified + assert_equal false, imap.tls_verified? end end @@ -1068,6 +1089,7 @@ def imaps_test(timeout: 10) begin imap = yield(port) imap.logout + imap ensure imap.disconnect if imap end