class PacketThief::Handlers::AbstractSSLHandler

Parent class for both SSLServer and SSLClient.

TODO: get_peer_cert, get_peername, etc.

Attributes

ctx[RW]

The OpenSSL::SSL::SSLContext. Modify this in post_init or in the initializing code block to add certificates, etc.

sni_hostname[RW]

(Used by SSLClient only) The hostname that the SNI TLS extension should request. Set it in post_init or in the initializing code block — it is applied to the SSLSocket during tls_begin.

sslsocket[RW]

The SSLSocket. It is not available until tls_begin creates it, after post_init and the initializing code block.

tcpsocket[RW]

The TCPSocket that the SSLSocket will be created from. It is added by initialize.

Public Class Methods

new(tcpsocket) click to toggle source
# File lib/packetthief/handlers/abstract_ssl_handler.rb, line 29
def initialize(tcpsocket)
  logdebug "initialize"
  # Set up initial values
  @tcpsocket = tcpsocket
  @ctx = OpenSSL::SSL::SSLContext.new

  @close_after_writing = false
  @state = :new
end

Public Instance Methods

close_connection() click to toggle source
# File lib/packetthief/handlers/abstract_ssl_handler.rb, line 192
      def close_connection
        detach
        @sslsocket.close if @sslsocket and not @sslsocket.closed?
        @tcpsocket.close if not @tcpsocket.closed?
#        unbind
      end
close_connection_after_writing() click to toggle source
# File lib/packetthief/handlers/abstract_ssl_handler.rb, line 199
def close_connection_after_writing
  @close_after_writing = true
  # if we aren't waiting to write, then we can flush and close.
  if not notify_writable?
    @sslsocket.flush
    close_connection
  end

end
notify_readable() click to toggle source

Calls accept_nonblock/connect_nonblock, read_nonblock, or write_nonblock based on the current state of the connection.

# File lib/packetthief/handlers/abstract_ssl_handler.rb, line 62
def notify_readable
  logdebug "notify_readable", :state => @state
  case @state
  when :initialized
    attempt_connection
  when :ready_to_read
    attempt_read
  when :write_needs_to_read
    attempt_write
  end
end
notify_writable() click to toggle source

We only care about notify_writable if we are waiting to write for some reason.

# File lib/packetthief/handlers/abstract_ssl_handler.rb, line 76
def notify_writable
  logdebug "notify_writable", :state => @state
  notify_writable = false # disable it now. if we still need it, we'll renabled it.
  case @state
  when :initialized
    attempt_connection
  when :read_needs_to_write
    attempt_read
  when :write_needs_to_write
    attempt_write
  end

  # if we waiting to close and are not longer waiting to write, we can flush and close the connection.
  if @close_after_writing and not notify_writable?
    @sslsock.flush
    close_connection
  end
end
post_init() click to toggle source

Note that post_init dos not have access to the sslsocket. The sslsocket is not added until tls_begin is called, after the code block.

post_init gives you a chance to manipulate the SSLContext.

# File lib/packetthief/handlers/abstract_ssl_handler.rb, line 215
def post_init
end
receive_data(data) click to toggle source

Override this to do something with the unecrypted data.

# File lib/packetthief/handlers/abstract_ssl_handler.rb, line 235
def receive_data(data)
  logdebug "receive_data:", :data => data
end
send_data(data) click to toggle source

Call this to send data to the other end of the connection.

# File lib/packetthief/handlers/abstract_ssl_handler.rb, line 187
def send_data(data)
  logdebug "send_data:", :data => data
  attempt_write(data)
end
tls_begin() click to toggle source

Creates sslsocket from tcpsocket and ctx, and initializes the handler's internal state. Called from the class method that creates the object, after post_init and the optional code block.

@note (SSLClient only) If @sni_hostname exists on the handler at this point, it will be added to the SSLSocket in order to enable sending a hostname in the SNI TLS extension.

# File lib/packetthief/handlers/abstract_ssl_handler.rb, line 46
def tls_begin
  logdebug "tls begin", :sni_hostname => @sni_hostname
  @sslsocket = OpenSSL::SSL::SSLSocket.new(@tcpsocket, @ctx)
  if @sni_hostname
    if @sslsocket.respond_to? :hostname
      @sslsocket.hostname = @sni_hostname
    else
      logwarn "#{@sslsocket.class} does not support setting an SNI hostname! This requires Ruby 1.9.x built against OpenSSL with SNI support.",
        :ruby_version => RUBY_VERSION
    end
  end
  @state = :initialized
end
tls_failed_handshake(e) click to toggle source

Called right after accept_nonblock fails for some unknown reason. The only parameter contains the OpenSSL::SSL::SSLError object that was thrown.

The connection will be closed after this.

# File lib/packetthief/handlers/abstract_ssl_handler.rb, line 230
def tls_failed_handshake(e)
  logerror "tls_failed_handshake: Failed to accept: #{e} (#{e.class})"
end
tls_successful_handshake() click to toggle source

Called right after the SSL handshake succeeds. This is your “new” post_init.

# File lib/packetthief/handlers/abstract_ssl_handler.rb, line 221
def tls_successful_handshake
  logdebug "Succesful handshake!"
end
unbind() click to toggle source

Override this to do something when the socket is finished.

# File lib/packetthief/handlers/abstract_ssl_handler.rb, line 240
def unbind
  logdebug "unbind"
end
write_buffer() click to toggle source
# File lib/packetthief/handlers/abstract_ssl_handler.rb, line 142
def write_buffer
  @write_buffer ||= ""
end
write_buffer=(rhs) click to toggle source
# File lib/packetthief/handlers/abstract_ssl_handler.rb, line 147
def write_buffer=(rhs)
  @write_buffer = rhs
end

Private Instance Methods

attempt_connection() click to toggle source
# File lib/packetthief/handlers/abstract_ssl_handler.rb, line 96
def attempt_connection
  begin
    # Client usess connect_nonblock, while server uses accept_nonblock.
    connection_action
    @state = :ready_to_read
    tls_successful_handshake
    attempt_write if write_buffer.length > 0
  rescue IO::WaitReadable
    # accept_nonblock needs to wait until it can read again.
    notify_readable = true
  rescue IO::WaitWritable
    # accept_nonblock needs to wait until it can write again.
    notify_writable = true
  rescue OpenSSL::SSL::SSLError, Errno::ECONNREFUSED => e
    # ssl handshake failed. Likely due to client rejecting our certificate!
    tls_failed_handshake(e)
    close_connection
  end
end
attempt_read() click to toggle source
# File lib/packetthief/handlers/abstract_ssl_handler.rb, line 117
def attempt_read
  begin
    data = @sslsocket.read_nonblock 4096 # much more than a network packet...
    receive_data(data)
    notify_writable = false
  rescue EOFError, Errno::ECONNRESET
    # remote closed. time to wrap up
    close_connection
  rescue IO::WaitReadable
    # we had no data to read.
    notify_readable = true
  rescue IO::WaitWritable
    # we ran out of buffer to send (yes, SSLSocket#read_nonblock can
    # trigger this)
    @state = :read_needs_to_write
    notify_writable = true
  rescue OpenSSL::SSL::SSLError => e
    logerror "attempt_read: #{e} (#{e.class})"
    close_connection
  else
    @state = :ready_to_read
  end
end
attempt_write(data=nil) click to toggle source
# File lib/packetthief/handlers/abstract_ssl_handler.rb, line 152
def attempt_write(data=nil)
  logdebug "attempt_write"
  write_buffer << data if data
  # do not attempt to write until we are ready!
  return if @state == :initialized or @state == :new
  begin
    count_written = @sslsocket.write_nonblock write_buffer
  rescue IO::WaitWritable
    notify_writable = true
  rescue IO::WaitReadable
    @state = :write_needs_to_read
  rescue OpenSSL::SSL::SSLError, IOError => e
    logerror "attempt_write: #{e} (#{e.class})"
    close_connection
  else
    # shrink the buf
    #
    # byteslice was added in ruby 1.9.x. in ruby 1.8.7, bytesize is
    # aliased to length, implying that a character coresponds to a
    # byte.
    @write_buffer = if write_buffer.respond_to?(:byteslice)
                      write_buffer.byteslice(count_written..-1)
                    else
                      write_buffer.slice(count_written..-1)
                    end
    # if we didn't write everything, wait for writable.
    notify_writable = true if write_buffer.bytesize > 0
  end
end