summaryrefslogtreecommitdiff
path: root/lib/vendor/excon/lib/excon/ssl_socket.rb
blob: dc65d86c7cba27f7613722650bcede4684c12964 (plain)
1
2
3
4
5
6
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
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
# frozen_string_literal: true
module Excon
  class SSLSocket < Socket
    HAVE_NONBLOCK = [:connect_nonblock, :read_nonblock, :write_nonblock].all? do |m|
      OpenSSL::SSL::SSLSocket.public_method_defined?(m)
    end

    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][:password] || @data[:proxy][:user]
          auth = ["#{@data[:proxy][:user]}:#{@data[:proxy][:password]}"].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

    private

    def client_cert_data
      @client_cert_data ||= if ccd = @data[:client_cert_data]
                              ccd
                            elsif path = @data[:client_cert]
                              File.read path
                            elsif path = @data[:certificate_path]
                              warn ":certificate_path is no longer supported and will be deprecated. Please use :client_cert or :client_cert_data"
                              File.read path
                            end
    end

    def connect
      # backwards compatability for things lacking nonblock
      @nonblock = HAVE_NONBLOCK && @nonblock
      super
    end

    def client_key_data
      @client_key_data ||= if ckd = @data[:client_key_data]
                             ckd
                           elsif path = @data[:client_key]
                             File.read path
                           elsif path = @data[:private_key_path]
                             warn ":private_key_path is no longer supported and will be deprecated. Please use :client_key or :client_key_data"
                             File.read path
                           end
    end

    def client_key_pass
      @data[:client_key_pass] || @data[:private_key_pass]
    end

  end
end