Class: Excon::SSLSocket

Inherits:
Socket
  • Object
show all
Defined in:
lib/excon/ssl_socket.rb

Constant Summary collapse

HAVE_NONBLOCK =
[:connect_nonblock, :read_nonblock, :write_nonblock].all? do |m|
  OpenSSL::SSL::SSLSocket.public_method_defined?(m)
end

Constants included from Utils

Utils::CONTROL, Utils::DELIMS, Utils::ESCAPED, Utils::NONASCII, Utils::UNESCAPED, Utils::UNWISE

Instance Attribute Summary

Attributes inherited from Socket

#data, #remote_ip

Instance Method Summary collapse

Methods inherited from Socket

#legacy_readline, #local_address, #local_port, #params, #params=, #read, #readline, #write

Methods included from Utils

#connection_uri, #escape_uri, #port_string, #query_string, #request_uri, #split_header_value, #unescape_form, #unescape_uri

Constructor Details

#initialize(data = {}) ⇒ SSLSocket

Returns a new instance of SSLSocket.



8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
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
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
# File 'lib/excon/ssl_socket.rb', line 8

def initialize(data = {})
  super

  # create ssl context
  ssl_context = OpenSSL::SSL::SSLContext.new

  # disable less secure options, when supported
  ssl_context_options = OpenSSL::SSL::SSLContext::DEFAULT_PARAMS[:options]
  if defined?(OpenSSL::SSL::OP_DONT_INSERT_EMPTY_FRAGMENTS)
    ssl_context_options &= ~OpenSSL::SSL::OP_DONT_INSERT_EMPTY_FRAGMENTS
  end

  if defined?(OpenSSL::SSL::OP_NO_COMPRESSION)
    ssl_context_options |= OpenSSL::SSL::OP_NO_COMPRESSION
  end
  ssl_context.options = ssl_context_options

  ssl_context.ciphers = @data[:ciphers]
  if @data[:ssl_version]
    ssl_context.ssl_version = @data[:ssl_version]
  end

  if @data[:ssl_verify_peer]
    # turn verification on
    ssl_context.verify_mode = OpenSSL::SSL::VERIFY_PEER

    if ca_file = @data[:ssl_ca_file] || ENV['SSL_CERT_FILE']
      ssl_context.ca_file = ca_file
    end
    if ca_path = @data[:ssl_ca_path] || ENV['SSL_CERT_DIR']
      ssl_context.ca_path = ca_path
    end
    if cert_store = @data[:ssl_cert_store]
      ssl_context.cert_store = cert_store
    end

    # no defaults, fallback to bundled
    unless ca_file || ca_path || cert_store
      ssl_context.cert_store = OpenSSL::X509::Store.new
      ssl_context.cert_store.set_default_paths

      # workaround issue #257 (JRUBY-6970)
      ca_file = DEFAULT_CA_FILE
      ca_file = ca_file.gsub(/^jar:/, '') if ca_file =~ /^jar:file:\//

      begin
        ssl_context.cert_store.add_file(ca_file)
      rescue
        Excon.display_warning("Excon unable to add file to cert store, ignoring: #{ca_file}\n[#{$!.class}] #{$!.message}")
      end
    end

    if verify_callback = @data[:ssl_verify_callback]
      ssl_context.verify_callback = verify_callback
    end
  else
    # turn verification off
    ssl_context.verify_mode = OpenSSL::SSL::VERIFY_NONE
  end

  if client_cert_data && client_key_data
    ssl_context.cert = OpenSSL::X509::Certificate.new client_cert_data
    if OpenSSL::PKey.respond_to? :read
      ssl_context.key = OpenSSL::PKey.read(client_key_data, client_key_pass)
    else
      ssl_context.key = OpenSSL::PKey::RSA.new(client_key_data, client_key_pass)
    end
  elsif @data.key?(:certificate) && @data.key?(:private_key)
    ssl_context.cert = OpenSSL::X509::Certificate.new(@data[:certificate])
    if OpenSSL::PKey.respond_to? :read
      ssl_context.key = OpenSSL::PKey.read(@data[:private_key], client_key_pass)
    else
      ssl_context.key = OpenSSL::PKey::RSA.new(@data[:private_key], client_key_pass)
    end
  end

  if @data[:proxy]
    request = "CONNECT #{@data[:host]}#{port_string(@data.merge(:omit_default_port => false))}#{Excon::HTTP_1_1}" +
              "Host: #{@data[:host]}#{port_string(@data)}#{Excon::CR_NL}"

    if @data[:proxy].has_key?(:user) || @data[:proxy].has_key?(:password)
      user, pass = Utils.unescape_form(@data[:proxy][:user].to_s), Utils.unescape_form(@data[:proxy][:password].to_s)
      auth = ["#{user}:#{pass}"].pack('m').delete(Excon::CR_NL)
      request += "Proxy-Authorization: Basic #{auth}#{Excon::CR_NL}"
    end

    request += "Proxy-Connection: Keep-Alive#{Excon::CR_NL}"

    request += Excon::CR_NL

    # write out the proxy setup request
    @socket.write(request)

    # eat the proxy's connection response
    Excon::Response.parse(self,  :expects => 200, :method => 'CONNECT')
  end

  # convert Socket to OpenSSL::SSL::SSLSocket
  @socket = OpenSSL::SSL::SSLSocket.new(@socket, ssl_context)
  @socket.sync_close = true

  # Server Name Indication (SNI) RFC 3546
  if @socket.respond_to?(:hostname=)
    @socket.hostname = @data[:host]
  end

  begin
    if @nonblock
      begin
        @socket.connect_nonblock
      rescue Errno::EAGAIN, Errno::EWOULDBLOCK, IO::WaitReadable
        select_with_timeout(@socket, :connect_read) && retry
      rescue IO::WaitWritable
        select_with_timeout(@socket, :connect_write) && retry
      end
    else
      @socket.connect
    end
  rescue Errno::ETIMEDOUT, Timeout::Error
    raise Excon::Errors::Timeout.new('connect timeout reached')
  end

  # verify connection
  if @data[:ssl_verify_peer]
    @socket.post_connection_check(@data[:ssl_verify_peer_host] || @data[:host])
  end

  @socket
end