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? {|m|
  OpenSSL::SSL::SSLSocket.public_method_defined?(m)
}

Constants included from Utils

Utils::ESCAPED, Utils::UNESCAPED

Instance Attribute Summary

Attributes inherited from Socket

#data, #remote_ip

Instance Method Summary collapse

Methods inherited from Socket

#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, #valid_connection_keys, #valid_request_keys

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
# 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_path = ENV['SSL_CERT_DIR'] || @data[:ssl_ca_path]
      ssl_context.ca_path = ca_path
    elsif ca_file = ENV['SSL_CERT_FILE'] || @data[:ssl_ca_file]
      ssl_context.ca_file = ca_file
    else # attempt default, fallback to bundled
      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.gsub!(/^jar:/, "") if ca_file =~ /^jar:file:\//

      begin
        ssl_context.cert_store.add_file(ca_file)
      rescue => e
        Excon.display_warning("Excon unable to add file to cert store, ignoring: #{ca_file}\n[#{e.class}] #{e.message}")
      end
    end
  else
    # turn verification off
    ssl_context.verify_mode = OpenSSL::SSL::VERIFY_NONE
  end

  # maintain existing API
  certificate_path = @data[:client_cert] || @data[:certificate_path]
  private_key_path = @data[:client_key] || @data[:private_key_path]

  if certificate_path && private_key_path
    ssl_context.cert = OpenSSL::X509::Certificate.new(File.read(certificate_path))
    ssl_context.key = OpenSSL::PKey::RSA.new(File.read(private_key_path))
  elsif @data.has_key?(:certificate) && @data.has_key?(:private_key)
    ssl_context.cert = OpenSSL::X509::Certificate.new(@data[:certificate])
    ssl_context.key = OpenSSL::PKey::RSA.new(@data[:private_key])
  end

  if @data[:proxy]
    request = 'CONNECT ' << @data[:host] << port_string(@data) << Excon::HTTP_1_1
    request << 'Host: ' << @data[:host] << port_string(@data) << Excon::CR_NL

    if @data[:proxy][:password] || @data[:proxy][:user]
      auth = ['' << @data[:proxy][:user].to_s << ':' << @data[:proxy][:password].to_s].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(@socket, { :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
    Timeout.timeout(@data[:connect_timeout]) do
      if @nonblock
        while true
          begin
            @socket.connect_nonblock
            break # connect succeeded
          rescue OpenSSL::SSL::SSLError => error
            # would block, rescue and retry as select is non-helpful
            unless error.message == 'read would block'
              raise error
            end
          end
        end
      else
        @socket.connect
      end
    end
  rescue Timeout::Error
    raise Excon::Errors::Timeout.new('connect timeout reached')
  end

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

  @socket
end