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


7
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
137
138
139
140
141
142
143
# File 'lib/excon/ssl_socket.rb', line 7

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 = ENV['SSL_CERT_FILE'] || @data[:ssl_ca_file]
      ssl_context.ca_file = ca_file
    end
    if ca_path = ENV['SSL_CERT_DIR'] || @data[:ssl_ca_path]
      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.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[#{e.class}] #{e.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

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

  if certificate_path && private_key_path
    ssl_context.cert = OpenSSL::X509::Certificate.new(File.read(certificate_path))
    if OpenSSL::PKey.respond_to? :read
      ssl_context.key = OpenSSL::PKey.read(File.read(private_key_path), private_key_pass)
    else
      ssl_context.key = OpenSSL::PKey::RSA.new(File.read(private_key_path), private_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], private_key_pass)
    else
      ssl_context.key = OpenSSL::PKey::RSA.new(@data[:private_key], private_key_pass)
    end
  end

  if @data[:proxy]
    request = 'CONNECT ' << @data[:host] << port_string(@data.merge(:omit_default_port => false)) << 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(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
      loop do
        begin
          @socket.connect_nonblock
          break # connect succeeded
        rescue OpenSSL::SSL::SSLError => error
          # would block, rescue and retry as select is non-helpful
          raise error unless error.message == 'read would block'
        end
      end
    else
      @socket.connect
    end
  rescue OpenSSL::SSL::SSLError => e
    raise e
  rescue
    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