386 lines
9.0 KiB
Ruby
386 lines
9.0 KiB
Ruby
# -*- coding: binary -*-
|
|
require 'rex/socket'
|
|
###
|
|
#
|
|
# This class provides methods for interacting with an SSL TCP client
|
|
# connection.
|
|
#
|
|
###
|
|
module Rex::Socket::SslTcp
|
|
|
|
begin
|
|
@@loaded_openssl = false
|
|
|
|
begin
|
|
require 'openssl'
|
|
@@loaded_openssl = true
|
|
require 'openssl/nonblock'
|
|
rescue ::Exception
|
|
end
|
|
|
|
|
|
include Rex::Socket::Tcp
|
|
|
|
##
|
|
#
|
|
# Factory
|
|
#
|
|
##
|
|
|
|
#
|
|
# Creates an SSL TCP instance.
|
|
#
|
|
def self.create(hash = {})
|
|
raise RuntimeError, "No OpenSSL support" if not @@loaded_openssl
|
|
hash['SSL'] = true
|
|
self.create_param(Rex::Socket::Parameters.from_hash(hash))
|
|
end
|
|
|
|
#
|
|
# Set the SSL flag to true and call the base class's create_param routine.
|
|
#
|
|
def self.create_param(param)
|
|
param.ssl = true
|
|
Rex::Socket::Tcp.create_param(param)
|
|
end
|
|
|
|
##
|
|
#
|
|
# Class initialization
|
|
#
|
|
##
|
|
|
|
#
|
|
# Initializes the SSL socket.
|
|
#
|
|
def initsock(params = nil)
|
|
super
|
|
|
|
# The autonegotiation preference for SSL/TLS versions
|
|
versions = [:TLSv1, :SSLv3, :SSLv23, :SSLv2]
|
|
|
|
# Limit this to a specific SSL/TLS version if specified
|
|
if params
|
|
case params.ssl_version
|
|
when 'SSL2', :SSLv2
|
|
versions = [:SSLv2]
|
|
when 'SSL23', :SSLv23
|
|
versions = [:SSLv23]
|
|
when 'SSL3', :SSLv3
|
|
versions = [:SSLv3]
|
|
when 'TLS1', :TLSv1
|
|
versions = [:TLSv1]
|
|
else
|
|
# Leave the version list as-is (Auto)
|
|
end
|
|
end
|
|
|
|
# Limit our versions to those supported by the linked OpenSSL library
|
|
versions = versions.select {|v| OpenSSL::SSL::SSLContext::METHODS.include? v }
|
|
|
|
# Raise an error if no selected versions are supported
|
|
if versions.length == 0
|
|
raise ArgumentError, 'The system OpenSSL does not support the requested SSL/TLS version'
|
|
end
|
|
|
|
last_error = nil
|
|
|
|
# Iterate through SSL/TLS versions until we successfully negotiate
|
|
versions.each do |version|
|
|
begin
|
|
# Try intializing the socket with this SSL/TLS version
|
|
# This will throw an exception if it fails
|
|
initsock_with_ssl_version(params, version)
|
|
|
|
# Success! Record what method was used and return
|
|
self.ssl_negotiated_version = version
|
|
return
|
|
rescue OpenSSL::SSL::SSLError => e
|
|
last_error = e
|
|
end
|
|
end
|
|
|
|
# No SSL/TLS versions succeeded, raise the last error
|
|
raise last_error
|
|
end
|
|
|
|
def initsock_with_ssl_version(params, version)
|
|
# Build the SSL connection
|
|
self.sslctx = OpenSSL::SSL::SSLContext.new(version)
|
|
|
|
# Configure the SSL context
|
|
# TODO: Allow the user to specify the verify mode callback
|
|
# Valid modes:
|
|
# VERIFY_CLIENT_ONCE
|
|
# VERIFY_FAIL_IF_NO_PEER_CERT
|
|
# VERIFY_NONE
|
|
# VERIFY_PEER
|
|
if params.ssl_verify_mode
|
|
self.sslctx.verify_mode = OpenSSL::SSL.const_get("VERIFY_#{params.ssl_verify_mode}".intern)
|
|
else
|
|
# Could also do this as graceful faildown in case a passed verify_mode is not supported
|
|
self.sslctx.verify_mode = OpenSSL::SSL::VERIFY_PEER
|
|
end
|
|
|
|
self.sslctx.options = OpenSSL::SSL::OP_ALL
|
|
|
|
if params.ssl_cipher
|
|
self.sslctx.ciphers = params.ssl_cipher
|
|
end
|
|
|
|
# Set the verification callback
|
|
self.sslctx.verify_callback = Proc.new do |valid, store|
|
|
self.peer_verified = valid
|
|
true
|
|
end
|
|
|
|
# Tie the context to a socket
|
|
self.sslsock = OpenSSL::SSL::SSLSocket.new(self, self.sslctx)
|
|
|
|
# XXX - enabling this causes infinite recursion, so disable for now
|
|
# self.sslsock.sync_close = true
|
|
|
|
# Force a negotiation timeout
|
|
begin
|
|
Timeout.timeout(params.timeout) do
|
|
if not allow_nonblock?
|
|
self.sslsock.connect
|
|
else
|
|
begin
|
|
self.sslsock.connect_nonblock
|
|
# Ruby 1.8.7 and 1.9.0/1.9.1 uses a standard Errno
|
|
rescue ::Errno::EAGAIN, ::Errno::EWOULDBLOCK
|
|
IO::select(nil, nil, nil, 0.10)
|
|
retry
|
|
|
|
# Ruby 1.9.2+ uses IO::WaitReadable/IO::WaitWritable
|
|
rescue ::Exception => e
|
|
if ::IO.const_defined?('WaitReadable') and e.kind_of?(::IO::WaitReadable)
|
|
IO::select( [ self.sslsock ], nil, nil, 0.10 )
|
|
retry
|
|
end
|
|
|
|
if ::IO.const_defined?('WaitWritable') and e.kind_of?(::IO::WaitWritable)
|
|
IO::select( nil, [ self.sslsock ], nil, 0.10 )
|
|
retry
|
|
end
|
|
|
|
raise e
|
|
end
|
|
end
|
|
end
|
|
|
|
rescue ::Timeout::Error
|
|
raise Rex::ConnectionTimeout.new(params.peerhost, params.peerport)
|
|
end
|
|
end
|
|
|
|
##
|
|
#
|
|
# Stream mixin implementations
|
|
#
|
|
##
|
|
|
|
#
|
|
# Writes data over the SSL socket.
|
|
#
|
|
def write(buf, opts = {})
|
|
return sslsock.write(buf) if not allow_nonblock?
|
|
|
|
total_sent = 0
|
|
total_length = buf.length
|
|
block_size = 16384
|
|
retry_time = 0.5
|
|
|
|
begin
|
|
while( total_sent < total_length )
|
|
s = Rex::ThreadSafe.select( nil, [ self.sslsock ], nil, 0.25 )
|
|
if( s == nil || s[0] == nil )
|
|
next
|
|
end
|
|
data = buf[total_sent, block_size]
|
|
sent = sslsock.write_nonblock( data )
|
|
if sent > 0
|
|
total_sent += sent
|
|
end
|
|
end
|
|
|
|
rescue ::IOError, ::Errno::EPIPE
|
|
return nil
|
|
|
|
# Ruby 1.8.7 and 1.9.0/1.9.1 uses a standard Errno
|
|
rescue ::Errno::EAGAIN, ::Errno::EWOULDBLOCK
|
|
# Sleep for a half a second, or until we can write again
|
|
Rex::ThreadSafe.select( nil, [ self.sslsock ], nil, retry_time )
|
|
# Decrement the block size to handle full sendQs better
|
|
block_size = 1024
|
|
# Try to write the data again
|
|
retry
|
|
|
|
# Ruby 1.9.2+ uses IO::WaitReadable/IO::WaitWritable
|
|
rescue ::Exception => e
|
|
if ::IO.const_defined?('WaitReadable') and e.kind_of?(::IO::WaitReadable)
|
|
IO::select( [ self.sslsock ], nil, nil, retry_time )
|
|
retry
|
|
end
|
|
|
|
if ::IO.const_defined?('WaitWritable') and e.kind_of?(::IO::WaitWritable)
|
|
IO::select( nil, [ self.sslsock ], nil, retry_time )
|
|
retry
|
|
end
|
|
|
|
# Another form of SSL error, this is always fatal
|
|
if e.kind_of?(::OpenSSL::SSL::SSLError)
|
|
return nil
|
|
end
|
|
|
|
# Bubble the event up to the caller otherwise
|
|
raise e
|
|
end
|
|
|
|
total_sent
|
|
end
|
|
|
|
#
|
|
# Reads data from the SSL socket.
|
|
#
|
|
def read(length = nil, opts = {})
|
|
if not allow_nonblock?
|
|
length = 16384 unless length
|
|
begin
|
|
return sslsock.sysread(length)
|
|
rescue ::IOError, ::Errno::EPIPE, ::OpenSSL::SSL::SSLError
|
|
return nil
|
|
end
|
|
return
|
|
end
|
|
|
|
|
|
begin
|
|
while true
|
|
s = Rex::ThreadSafe.select( [ self.sslsock ], nil, nil, 0.10 )
|
|
if( s == nil || s[0] == nil )
|
|
next
|
|
end
|
|
return sslsock.read_nonblock( length )
|
|
end
|
|
|
|
rescue ::IOError, ::Errno::EPIPE
|
|
return nil
|
|
|
|
# Ruby 1.8.7 and 1.9.0/1.9.1 uses a standard Errno
|
|
rescue ::Errno::EAGAIN, ::Errno::EWOULDBLOCK
|
|
# Sleep for a tenth a second, or until we can read again
|
|
Rex::ThreadSafe.select( [ self.sslsock ], nil, nil, 0.10 )
|
|
# Decrement the block size to handle full sendQs better
|
|
block_size = 1024
|
|
# Try to write the data again
|
|
retry
|
|
|
|
# Ruby 1.9.2+ uses IO::WaitReadable/IO::WaitWritable
|
|
rescue ::Exception => e
|
|
if ::IO.const_defined?('WaitReadable') and e.kind_of?(::IO::WaitReadable)
|
|
IO::select( [ self.sslsock ], nil, nil, 0.5 )
|
|
retry
|
|
end
|
|
|
|
if ::IO.const_defined?('WaitWritable') and e.kind_of?(::IO::WaitWritable)
|
|
IO::select( nil, [ self.sslsock ], nil, 0.5 )
|
|
retry
|
|
end
|
|
|
|
# Another form of SSL error, this is always fatal
|
|
if e.kind_of?(::OpenSSL::SSL::SSLError)
|
|
return nil
|
|
end
|
|
|
|
raise e
|
|
end
|
|
|
|
end
|
|
|
|
|
|
#
|
|
# Closes the SSL socket.
|
|
#
|
|
def close
|
|
sslsock.close rescue nil
|
|
super
|
|
end
|
|
|
|
#
|
|
# Ignore shutdown requests
|
|
#
|
|
def shutdown(how=0)
|
|
# Calling shutdown() on an SSL socket can lead to bad things
|
|
# Cause of http://metasploit.com/dev/trac/ticket/102
|
|
end
|
|
|
|
#
|
|
# Access to peer cert
|
|
#
|
|
def peer_cert
|
|
sslsock.peer_cert if sslsock
|
|
end
|
|
|
|
#
|
|
# Access to peer cert chain
|
|
#
|
|
def peer_cert_chain
|
|
sslsock.peer_cert_chain if sslsock
|
|
end
|
|
|
|
#
|
|
# Access to the current cipher
|
|
#
|
|
def cipher
|
|
sslsock.cipher if sslsock
|
|
end
|
|
|
|
#
|
|
# Prevent a sysread from the bare socket
|
|
#
|
|
def sysread(*args)
|
|
raise RuntimeError, "Invalid sysread() call on SSL socket"
|
|
end
|
|
|
|
#
|
|
# Prevent a sysread from the bare socket
|
|
#
|
|
def syswrite(*args)
|
|
raise RuntimeError, "Invalid syswrite() call on SSL socket"
|
|
end
|
|
|
|
#
|
|
# This flag determines whether to use the non-blocking openssl
|
|
# API calls when they are available. This is still buggy on
|
|
# Linux/Mac OS X, but is required on Windows
|
|
#
|
|
def allow_nonblock?
|
|
avail = self.sslsock.respond_to?(:accept_nonblock)
|
|
if avail and Rex::Compat.is_windows
|
|
return true
|
|
end
|
|
false
|
|
end
|
|
|
|
attr_reader :peer_verified # :nodoc:
|
|
attr_reader :ssl_negotiated_version # :nodoc:
|
|
attr_accessor :sslsock, :sslctx # :nodoc:
|
|
|
|
protected
|
|
|
|
attr_writer :peer_verified # :nodoc:
|
|
attr_writer :ssl_negotiated_version # :nodoc:
|
|
|
|
|
|
rescue LoadError
|
|
end
|
|
|
|
def type?
|
|
return 'tcp-ssl'
|
|
end
|
|
|
|
end
|
|
|