Skip to content

Commit

Permalink
🔒 Add Net::IMAP#tls_verified?
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
nevans committed Aug 29, 2023
1 parent 3086d9b commit 9041eef
Show file tree
Hide file tree
Showing 2 changed files with 40 additions and 11 deletions.
7 changes: 7 additions & 0 deletions lib/net/imap.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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

Expand Down
44 changes: 33 additions & 11 deletions test/net/imap/test_imap.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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

Expand Down Expand Up @@ -1068,6 +1089,7 @@ def imaps_test(timeout: 10)
begin
imap = yield(port)
imap.logout
imap
ensure
imap.disconnect if imap
end
Expand Down

0 comments on commit 9041eef

Please sign in to comment.