Class: Socketry::SSL::Socket

Inherits:
TCP::Socket show all
Defined in:
lib/socketry/ssl/socket.rb

Overview

SSL Sockets

Constant Summary

Constants included from Timeout

Timeout::DEFAULT_TIMEOUTS, Timeout::DEFAULT_TIMER

Instance Attribute Summary

Attributes inherited from TCP::Socket

#local_addr, #local_port, #read_timeout, #remote_addr, #remote_port, #resolver, #socket_class, #write_timeout

Instance Method Summary collapse

Methods inherited from TCP::Socket

#closed?, connect, #nodelay, #nodelay=, #read, #readpartial, #reconnect, #to_io, #write, #writepartial

Methods included from Timeout

#clear_timeout, #lifetime, #set_timeout, #start_timer, #time_remaining

Constructor Details

#initialize(ssl_socket_class: OpenSSL::SSL::SSLSocket, ssl_context: OpenSSL::SSL::SSLContext.new, ssl_params: nil, **args) ⇒ Socketry::SSL::Socket

Create an unconnected Socketry::SSL::Socket

Raises:

  • (TypeError)


19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
# File 'lib/socketry/ssl/socket.rb', line 19

def initialize(
  ssl_socket_class: OpenSSL::SSL::SSLSocket,
  ssl_context: OpenSSL::SSL::SSLContext.new,
  ssl_params: nil,
  **args
)
  raise TypeError, "invalid SSL context (#{ssl_context.class})" unless ssl_context.is_a?(OpenSSL::SSL::SSLContext)
  raise TypeError, "expected Hash, got #{ssl_params.class}" if ssl_params && !ssl_params.is_a?(Hash)

  @ssl_socket_class = ssl_socket_class
  @ssl_context = ssl_context
  @ssl_context.set_params(ssl_params) if ssl_params && !ssl_params.empty?
  @ssl_context.freeze
  @ssl_socket = nil

  super(**args)
end

Instance Method Details

#closetrue, false

Close the socket



154
155
156
157
# File 'lib/socketry/ssl/socket.rb', line 154

def close
  @ssl_socket.close
  super
end

#connect(remote_addr, remote_port, local_addr: nil, local_port: nil, timeout: Socketry::Timeout::DEFAULT_TIMEOUTS[:connect], verify_hostname: true) ⇒ self

Make an SSL connection to a remote host

Raises:



49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
# File 'lib/socketry/ssl/socket.rb', line 49

def connect(
  remote_addr,
  remote_port,
  local_addr: nil,
  local_port: nil,
  timeout: Socketry::Timeout::DEFAULT_TIMEOUTS[:connect],
  verify_hostname: true
)
  super(remote_addr, remote_port, local_addr: local_addr, local_port: local_port, timeout: timeout)

  @ssl_socket = OpenSSL::SSL::SSLSocket.new(@socket, @ssl_context)
  @ssl_socket.hostname = remote_addr

  begin
    @ssl_socket.connect_nonblock
  rescue IO::WaitReadable
    retry if @socket.wait_readable(timeout)
    raise Socketry::TimeoutError, "connection to #{remote_addr}:#{remote_port} timed out"
  rescue IO::WaitWritable
    retry if @socket.wait_writable(timeout)
    raise Socketry::TimeoutError, "connection to #{remote_addr}:#{remote_port} timed out"
  rescue OpenSSL::SSL::SSLError => ex
    raise Socketry::SSL::Error, ex.message, ex.backtrace
  end

  begin
    @ssl_socket.post_connection_check(remote_addr) if verify_hostname
  rescue OpenSSL::SSL::SSLError => ex
    raise Socketry::SSL::HostnameError, ex.message, ex.backtrace
  end

  self
rescue => ex
  @socket.close rescue nil
  @socket = nil
  @ssl_socket.close rescue nil
  @ssl_socket = nil
  raise ex
end

#from_socket(socket, ssl_socket) ⇒ self

Wrap a Ruby OpenSSL::SSL::SSLSocket (or other low-level SSL socket)

Raises:

  • (TypeError)


94
95
96
97
98
99
100
101
102
103
104
# File 'lib/socketry/ssl/socket.rb', line 94

def from_socket(socket, ssl_socket)
  raise TypeError, "expected #{@socket_class}, got #{socket.class}" unless socket.is_a?(@socket_class)
  raise TypeError, "expected #{@ssl_socket_class}, got #{ssl_socket.class}" unless ssl_socket.is_a?(@ssl_socket_class)
  raise StateError, "already connected" if @socket && @socket != socket

  @socket = socket
  @ssl_socket = ssl_socket
  @ssl_socket.sync_close = true

  self
end

#read_nonblock(size, outbuf: nil) ⇒ String, :wait_readable

Perform a non-blocking read operation

Raises:



112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
# File 'lib/socketry/ssl/socket.rb', line 112

def read_nonblock(size, outbuf: nil)
  ensure_connected
  case outbuf
  when String
    @ssl_socket.read_nonblock(size, outbuf, exception: false)
  when NilClass
    @ssl_socket.read_nonblock(size, exception: false)
  else raise TypeError, "unexpected outbuf class: #{outbuf.class}"
  end
# Some buggy Rubies continue to raise exceptions in these cases
rescue IO::WaitReadable
  :wait_readable
# Due to SSL, we may need to write to complete a read (e.g. renegotiation)
rescue IO::WaitWritable
  :wait_writable
rescue => ex
  # TODO: more specific exceptions
  raise Socketry::Error, ex.message, ex.backtrace
end

#write_nonblock(data) ⇒ Fixnum, :wait_writable

Perform a non-blocking write operation

Raises:



137
138
139
140
141
142
143
144
145
146
147
148
149
# File 'lib/socketry/ssl/socket.rb', line 137

def write_nonblock(data)
  ensure_connected
  @ssl_socket.write_nonblock(data, exception: false)
# Some buggy Rubies continue to raise this exception
rescue IO::WaitWriteable
  :wait_writable
# Due to SSL, we may need to write to complete a read (e.g. renegotiation)
rescue IO::WaitReadable
  :wait_readable
rescue => ex
  # TODO: more specific exceptions
  raise Socketry::Error, ex.message, ex.backtrace
end