httpclient 2.6.0.1 → 2.7.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,5 +1,5 @@
1
1
  # HTTPClient - HTTP client library.
2
- # Copyright (C) 2000-2009 NAKAMURA, Hiroshi <nahi@ruby-lang.org>.
2
+ # Copyright (C) 2000-2015 NAKAMURA, Hiroshi <nahi@ruby-lang.org>.
3
3
  #
4
4
  # This program is copyrighted free software by NAKAMURA, Hiroshi. You can
5
5
  # redistribute it and/or modify it under the same terms of Ruby's license;
@@ -20,20 +20,19 @@ class HTTPClient
20
20
  #
21
21
  # == Trust Anchor Control
22
22
  #
23
- # SSLConfig loads 'httpclient/cacert.p7s' as a trust anchor
23
+ # SSLConfig loads 'httpclient/cacert.pem' as a trust anchor
24
24
  # (trusted certificate(s)) with add_trust_ca in initialization time.
25
25
  # This means that HTTPClient instance trusts some CA certificates by default,
26
- # like Web browsers. 'httpclient/cacert.p7s' is created by the author and
27
- # included in released package.
28
- #
29
- # 'cacert.p7s' is automatically generated from JDK 1.6. Regardless its
30
- # filename extension (p7s), HTTPClient doesn't verify the signature in it.
26
+ # like Web browsers. 'httpclient/cacert.pem' is downloaded from curl web
27
+ # site by the author and included in released package.
31
28
  #
32
29
  # You may want to change trust anchor by yourself. Call clear_cert_store
33
30
  # then add_trust_ca for that purpose.
34
31
  class SSLConfig
35
32
  include OpenSSL if SSLEnabled
36
33
 
34
+ CIPHERS_DEFAULT = "ALL:!aNULL:!eNULL:!SSLv2" # OpenSSL >1.0.0 default
35
+
37
36
  # Which TLS protocol version (also called method) will be used. Defaults
38
37
  # to :auto which means that OpenSSL decides (In my tests this resulted
39
38
  # with always the highest available protocol being used).
@@ -42,18 +41,20 @@ class HTTPClient
42
41
  # See {OpenSSL::SSL::SSLContext::METHODS} for a list of available versions
43
42
  # in your specific Ruby environment.
44
43
  attr_reader :ssl_version
45
- # OpenSSL::X509::Certificate:: certificate for SSL client authenticateion.
46
- # nil by default. (no client authenticateion)
44
+ # OpenSSL::X509::Certificate:: certificate for SSL client authentication.
45
+ # nil by default. (no client authentication)
47
46
  attr_reader :client_cert
48
47
  # OpenSSL::PKey::PKey:: private key for SSL client authentication.
49
- # nil by default. (no client authenticateion)
48
+ # nil by default. (no client authentication)
50
49
  attr_reader :client_key
50
+ attr_reader :client_key_pass
51
51
 
52
52
  # A number which represents OpenSSL's verify mode. Default value is
53
53
  # OpenSSL::SSL::VERIFY_PEER | OpenSSL::SSL::VERIFY_FAIL_IF_NO_PEER_CERT.
54
54
  attr_reader :verify_mode
55
55
  # A number of verify depth. Certification path which length is longer than
56
56
  # this depth is not allowed.
57
+ # CAUTION: this is OpenSSL specific option and ignored on JRuby.
57
58
  attr_reader :verify_depth
58
59
  # A callback handler for custom certificate verification. nil by default.
59
60
  # If the handler is set, handler.call is invoked just after general
@@ -65,6 +66,8 @@ class HTTPClient
65
66
  attr_reader :timeout
66
67
  # A number of OpenSSL's SSL options. Default value is
67
68
  # OpenSSL::SSL::OP_ALL | OpenSSL::SSL::OP_NO_SSLv2
69
+ # CAUTION: this is OpenSSL specific option and ignored on JRuby.
70
+ # Use ssl_version to specify the TLS version you want to use.
68
71
  attr_reader :options
69
72
  # A String of OpenSSL's cipher configuration. Default value is
70
73
  # ALL:!ADH:!LOW:!EXP:!MD5:+SSLv2:@STRENGTH
@@ -78,11 +81,17 @@ class HTTPClient
78
81
  # For server side configuration. Ignore this.
79
82
  attr_reader :client_ca # :nodoc:
80
83
 
84
+ # These array keeps original files/dirs that was added to @cert_store
85
+ attr_reader :cert_store_items
86
+ attr_reader :cert_store_crl_items
87
+
81
88
  # Creates a SSLConfig.
82
89
  def initialize(client)
83
90
  return unless SSLEnabled
84
91
  @client = client
85
92
  @cert_store = X509::Store.new
93
+ @cert_store_items = [:default]
94
+ @cert_store_crl_items = []
86
95
  @client_cert = @client_key = @client_ca = nil
87
96
  @verify_mode = SSL::VERIFY_PEER | SSL::VERIFY_FAIL_IF_NO_PEER_CERT
88
97
  @verify_depth = nil
@@ -97,7 +106,7 @@ class HTTPClient
97
106
  @options |= OpenSSL::SSL::OP_NO_SSLv2 if defined?(OpenSSL::SSL::OP_NO_SSLv2)
98
107
  @options |= OpenSSL::SSL::OP_NO_SSLv3 if defined?(OpenSSL::SSL::OP_NO_SSLv3)
99
108
  # OpenSSL 0.9.8 default: "ALL:!ADH:!LOW:!EXP:!MD5:+SSLv2:@STRENGTH"
100
- @ciphers = "ALL:!aNULL:!eNULL:!SSLv2" # OpenSSL >1.0.0 default
109
+ @ciphers = CIPHERS_DEFAULT
101
110
  @cacerts_loaded = false
102
111
  end
103
112
 
@@ -135,8 +144,7 @@ class HTTPClient
135
144
  #
136
145
  # Calling this method resets all existing sessions.
137
146
  def set_client_cert_file(cert_file, key_file, pass = nil)
138
- @client_cert = X509::Certificate.new(File.open(cert_file) { |f| f.read })
139
- @client_key = PKey::RSA.new(File.open(key_file) { |f| f.read }, pass)
147
+ @client_cert, @client_key, @client_key_pass = cert_file, key_file, pass
140
148
  change_notify
141
149
  end
142
150
 
@@ -155,6 +163,7 @@ class HTTPClient
155
163
  @cacerts_loaded = true # avoid lazy override
156
164
  @cert_store = X509::Store.new
157
165
  @cert_store.set_default_paths
166
+ @cert_store_items = [ENV['SSL_CERT_FILE'] || :default]
158
167
  change_notify
159
168
  end
160
169
 
@@ -165,6 +174,7 @@ class HTTPClient
165
174
  def clear_cert_store
166
175
  @cacerts_loaded = true # avoid lazy override
167
176
  @cert_store = X509::Store.new
177
+ @cert_store_items.clear
168
178
  change_notify
169
179
  end
170
180
 
@@ -175,6 +185,7 @@ class HTTPClient
175
185
  def cert_store=(cert_store)
176
186
  @cacerts_loaded = true # avoid lazy override
177
187
  @cert_store = cert_store
188
+ @cert_store_items.clear
178
189
  change_notify
179
190
  end
180
191
 
@@ -188,6 +199,7 @@ class HTTPClient
188
199
  def add_trust_ca(trust_ca_file_or_hashed_dir)
189
200
  @cacerts_loaded = true # avoid lazy override
190
201
  add_trust_ca_to_store(@cert_store, trust_ca_file_or_hashed_dir)
202
+ @cert_store_items << trust_ca_file_or_hashed_dir
191
203
  change_notify
192
204
  end
193
205
  alias set_trust_ca add_trust_ca
@@ -211,12 +223,20 @@ class HTTPClient
211
223
  # crl:: a OpenSSL::X509::CRL or a filename of a PEM/DER formatted
212
224
  # OpenSSL::X509::CRL.
213
225
  #
226
+ # On JRuby, instead of setting CRL by yourself you can set following
227
+ # options to let HTTPClient to perform revocation check with CRL and OCSP:
228
+ # -J-Dcom.sun.security.enableCRLDP=true -J-Dcom.sun.net.ssl.checkRevocation=true
229
+ # ex. jruby -J-Dcom.sun.security.enableCRLDP=true -J-Dcom.sun.net.ssl.checkRevocation=true app.rb
230
+ #
231
+ # Revoked cert example: https://test-sspev.verisign.com:2443/test-SSPEV-revoked-verisign.html
232
+ #
214
233
  # Calling this method resets all existing sessions.
215
234
  def add_crl(crl)
216
235
  unless crl.is_a?(X509::CRL)
217
236
  crl = X509::CRL.new(File.open(crl) { |f| f.read })
218
237
  end
219
238
  @cert_store.add_crl(crl)
239
+ @cert_store_crl_items << crl
220
240
  @cert_store.flags = X509::V_FLAG_CRL_CHECK | X509::V_FLAG_CRL_CHECK_ALL
221
241
  change_notify
222
242
  end
@@ -278,7 +298,7 @@ class HTTPClient
278
298
  change_notify
279
299
  end
280
300
 
281
- # interfaces for SSLSocketWrap.
301
+ # interfaces for SSLSocket.
282
302
  def set_context(ctx) # :nodoc:
283
303
  load_trust_ca unless @cacerts_loaded
284
304
  @cacerts_loaded = true
@@ -288,8 +308,14 @@ class HTTPClient
288
308
  ctx.verify_depth = @verify_depth if @verify_depth
289
309
  ctx.verify_callback = @verify_callback || method(:default_verify_callback)
290
310
  # SSL config
291
- ctx.cert = @client_cert
292
- ctx.key = @client_key
311
+ if @client_cert
312
+ ctx.cert = @client_cert.is_a?(X509::Certificate) ? @client_cert :
313
+ X509::Certificate.new(File.open(@client_cert) { |f| f.read })
314
+ end
315
+ if @client_key
316
+ ctx.key = @client_key.is_a?(PKey::PKey) ? @client_key :
317
+ PKey::RSA.new(File.open(@client_key) { |f| f.read }, @client_key_pass)
318
+ end
293
319
  ctx.client_ca = @client_ca
294
320
  ctx.timeout = @timeout
295
321
  ctx.options = @options
@@ -405,9 +431,20 @@ class HTTPClient
405
431
  nil
406
432
  end
407
433
 
434
+ # Use 2014 bit certs trust anchor if possible.
435
+ # CVE-2015-1793 requires: OpenSSL >= 1.0.2d or OpenSSL >= 1.0.1p
436
+ # OpenSSL before 1.0.1 does not have CVE-2015-1793 problem
408
437
  def load_cacerts(cert_store)
409
- file = File.join(File.dirname(__FILE__), 'cacert.p7s')
438
+ ver = OpenSSL::OPENSSL_VERSION
439
+ if (ver.start_with?('OpenSSL 1.0.1') && ver >= 'OpenSSL 1.0.1p') ||
440
+ (ver.start_with?('OpenSSL ') && ver >= 'OpenSSL 1.0.2d')
441
+ filename = 'cacert.pem'
442
+ else
443
+ filename = 'cacert1024.pem'
444
+ end
445
+ file = File.join(File.dirname(__FILE__), filename)
410
446
  add_trust_ca_to_store(cert_store, file)
447
+ @cert_store_items << file
411
448
  end
412
449
  end
413
450
 
@@ -0,0 +1,149 @@
1
+ # HTTPClient - HTTP client library.
2
+ # Copyright (C) 2000-2015 NAKAMURA, Hiroshi <nahi@ruby-lang.org>.
3
+ #
4
+ # This program is copyrighted free software by NAKAMURA, Hiroshi. You can
5
+ # redistribute it and/or modify it under the same terms of Ruby's license;
6
+ # either the dual license version in 2003, or any later version.
7
+
8
+
9
+ require 'httpclient/ssl_config'
10
+
11
+
12
+ class HTTPClient
13
+
14
+ # Wraps up OpenSSL::SSL::SSLSocket and offers debugging features.
15
+ class SSLSocket
16
+ def self.create_socket(session)
17
+ site = session.proxy || session.dest
18
+ socket = session.create_socket(site.host, site.port)
19
+ begin
20
+ if session.proxy
21
+ session.connect_ssl_proxy(socket, Util.urify(session.dest.to_s))
22
+ end
23
+ ssl_socket = new(socket, session.ssl_config, session.debug_dev)
24
+ ssl_socket.ssl_connect(session.dest.host)
25
+ ssl_socket
26
+ rescue
27
+ socket.close
28
+ raise
29
+ end
30
+ end
31
+
32
+ def initialize(socket, context, debug_dev = nil)
33
+ unless SSLEnabled
34
+ raise ConfigurationError.new('Ruby/OpenSSL module is required')
35
+ end
36
+ @socket = socket
37
+ @context = context
38
+ @ssl_socket = create_openssl_socket(@socket)
39
+ @debug_dev = debug_dev
40
+ end
41
+
42
+ def ssl_connect(hostname = nil)
43
+ if hostname && @ssl_socket.respond_to?(:hostname=)
44
+ @ssl_socket.hostname = hostname
45
+ end
46
+ @ssl_socket.connect
47
+ if $DEBUG
48
+ if @ssl_socket.respond_to?(:ssl_version)
49
+ warn("Protocol version: #{@ssl_socket.ssl_version}")
50
+ end
51
+ warn("Cipher: #{@ssl_socket.cipher.inspect}")
52
+ warn("State: #{@ssl_socket.state}")
53
+ end
54
+ post_connection_check(hostname)
55
+ end
56
+
57
+ def peer_cert
58
+ @ssl_socket.peer_cert
59
+ end
60
+
61
+ def close
62
+ @ssl_socket.close
63
+ @socket.close
64
+ end
65
+
66
+ def closed?
67
+ @socket.closed?
68
+ end
69
+
70
+ def eof?
71
+ @ssl_socket.eof?
72
+ end
73
+
74
+ def gets(rs)
75
+ str = @ssl_socket.gets(rs)
76
+ debug(str)
77
+ str
78
+ end
79
+
80
+ def read(size, buf = nil)
81
+ str = @ssl_socket.read(size, buf)
82
+ debug(str)
83
+ str
84
+ end
85
+
86
+ def readpartial(size, buf = nil)
87
+ str = @ssl_socket.readpartial(size, buf)
88
+ debug(str)
89
+ str
90
+ end
91
+
92
+ def <<(str)
93
+ rv = @ssl_socket.write(str)
94
+ debug(str)
95
+ rv
96
+ end
97
+
98
+ def flush
99
+ @ssl_socket.flush
100
+ end
101
+
102
+ def sync
103
+ @ssl_socket.sync
104
+ end
105
+
106
+ def sync=(sync)
107
+ @ssl_socket.sync = sync
108
+ end
109
+
110
+ private
111
+
112
+ def post_connection_check(hostname)
113
+ verify_mode = @context.verify_mode || OpenSSL::SSL::VERIFY_NONE
114
+ if verify_mode == OpenSSL::SSL::VERIFY_NONE
115
+ return
116
+ elsif @ssl_socket.peer_cert.nil? and
117
+ check_mask(verify_mode, OpenSSL::SSL::VERIFY_FAIL_IF_NO_PEER_CERT)
118
+ raise OpenSSL::SSL::SSLError.new('no peer cert')
119
+ end
120
+ if @ssl_socket.respond_to?(:post_connection_check) and RUBY_VERSION > "1.8.4"
121
+ @ssl_socket.post_connection_check(hostname)
122
+ else
123
+ @context.post_connection_check(@ssl_socket.peer_cert, hostname)
124
+ end
125
+ end
126
+
127
+ def check_mask(value, mask)
128
+ value & mask == mask
129
+ end
130
+
131
+ def create_openssl_socket(socket)
132
+ ssl_socket = nil
133
+ if OpenSSL::SSL.const_defined?("SSLContext")
134
+ ctx = OpenSSL::SSL::SSLContext.new
135
+ @context.set_context(ctx)
136
+ ssl_socket = OpenSSL::SSL::SSLSocket.new(socket, ctx)
137
+ else
138
+ ssl_socket = OpenSSL::SSL::SSLSocket.new(socket)
139
+ @context.set_context(ssl_socket)
140
+ end
141
+ ssl_socket
142
+ end
143
+
144
+ def debug(str)
145
+ @debug_dev << str if @debug_dev && str
146
+ end
147
+ end
148
+
149
+ end
@@ -1,5 +1,5 @@
1
1
  # HTTPClient - HTTP client library.
2
- # Copyright (C) 2000-2009 NAKAMURA, Hiroshi <nahi@ruby-lang.org>.
2
+ # Copyright (C) 2000-2015 NAKAMURA, Hiroshi <nahi@ruby-lang.org>.
3
3
  #
4
4
  # This program is copyrighted free software by NAKAMURA, Hiroshi. You can
5
5
  # redistribute it and/or modify it under the same terms of Ruby's license;
@@ -1,5 +1,5 @@
1
1
  # HTTPClient - HTTP client library.
2
- # Copyright (C) 2000-2012 NAKAMURA, Hiroshi <nahi@ruby-lang.org>.
2
+ # Copyright (C) 2000-2015 NAKAMURA, Hiroshi <nahi@ruby-lang.org>.
3
3
  #
4
4
  # This program is copyrighted free software by NAKAMURA, Hiroshi. You can
5
5
  # redistribute it and/or modify it under the same terms of Ruby's license;
@@ -24,6 +24,28 @@ if RUBY_VERSION < "1.9.3"
24
24
  end
25
25
  end
26
26
 
27
+ # With recent JRuby 1.7 + jruby-openssl, X509CRL#extentions_to_text causes
28
+ # StringIndexOOBException when we try to dump SSL Server Certificate.
29
+ # when one of extensions has "" as value.
30
+ if defined?(JRUBY_VERSION)
31
+ require 'openssl'
32
+ require 'java'
33
+ module OpenSSL
34
+ module X509
35
+ class Certificate
36
+ java_import 'java.security.cert.Certificate'
37
+ java_import 'java.security.cert.CertificateFactory'
38
+ java_import 'java.io.ByteArrayInputStream'
39
+ def to_text
40
+ cf = CertificateFactory.getInstance('X.509')
41
+ cf.generateCertificate(ByteArrayInputStream.new(self.to_der.to_java_bytes)).toString
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
47
+
48
+
27
49
  class HTTPClient
28
50
 
29
51
 
@@ -1,3 +1,3 @@
1
1
  class HTTPClient
2
- VERSION = '2.6.0.1'
2
+ VERSION = '2.7.0'
3
3
  end
@@ -1,5 +1,5 @@
1
1
  # HTTPClient - HTTP client library.
2
- # Copyright (C) 2000-2009 NAKAMURA, Hiroshi <nahi@ruby-lang.org>.
2
+ # Copyright (C) 2000-2015 NAKAMURA, Hiroshi <nahi@ruby-lang.org>.
3
3
  #
4
4
  # This program is copyrighted free software by NAKAMURA, Hiroshi. You can
5
5
  # redistribute it and/or modify it under the same terms of Ruby's license;
File without changes
@@ -59,11 +59,13 @@ module Helper
59
59
  @client = HTTPClient.new
60
60
  end
61
61
 
62
- #def setup_server
63
- # override it
64
- # @server = WEBrick::HTTPServer.new(...)
65
- # @server_thread = start_server_thread(@server)
66
- #end
62
+ def escape_noproxy
63
+ backup = HTTPClient::NO_PROXY_HOSTS.dup
64
+ HTTPClient::NO_PROXY_HOSTS.clear
65
+ yield
66
+ ensure
67
+ HTTPClient::NO_PROXY_HOSTS.replace(backup)
68
+ end
67
69
 
68
70
  def setup_proxyserver
69
71
  @proxyserver = WEBrick::HTTPProxyServer.new(
@@ -124,6 +124,7 @@ class TestAuth < Test::Unit::TestCase
124
124
  end
125
125
  # Make it work if @value == nil
126
126
  class SecurityBuffer < FieldSet
127
+ remove_method(:data_size) if method_defined?(:data_size)
127
128
  def data_size
128
129
  @active && @value ? @value.size : 0
129
130
  end
@@ -230,7 +231,7 @@ class TestAuth < Test::Unit::TestCase
230
231
  c.www_auth.basic_auth.instance_eval { @scheme = "BASIC" }
231
232
  c.set_auth("http://localhost:#{serverport}/", 'admin', 'admin')
232
233
 
233
- threads = 100.times.map { |idx|
234
+ 100.times.map { |idx|
234
235
  Thread.new(idx) { |idx2|
235
236
  Thread.abort_on_exception = true
236
237
  Thread.pass
@@ -251,7 +252,7 @@ class TestAuth < Test::Unit::TestCase
251
252
  c.test_loopback_http_response << "HTTP/1.0 200 OK\nContent-Length: 2\n\nOK"
252
253
  c.debug_dev = str = ''
253
254
  c.get_content("http://localhost:#{serverport}/basic_auth/sub/dir/")
254
- assert_match /Authorization: Basic YWRtaW46YWRtaW4=/, str
255
+ assert_match(/Authorization: Basic YWRtaW46YWRtaW4=/, str)
255
256
  end
256
257
 
257
258
  def test_digest_auth
@@ -267,7 +268,7 @@ class TestAuth < Test::Unit::TestCase
267
268
  c.test_loopback_http_response << "HTTP/1.0 200 OK\nContent-Length: 2\n\nOK"
268
269
  c.debug_dev = str = ''
269
270
  c.get_content("http://localhost:#{serverport}/digest_auth/sub/dir/")
270
- assert_match /Authorization: Digest/, str
271
+ assert_match(/Authorization: Digest/, str)
271
272
  end
272
273
 
273
274
  def test_digest_auth_with_block
@@ -332,6 +333,16 @@ class TestAuth < Test::Unit::TestCase
332
333
  assert_match(/Proxy-Authorization: Basic YWRtaW46YWRtaW4=/, str)
333
334
  end
334
335
 
336
+ def test_proxy_auth_force
337
+ c = HTTPClient.new
338
+ c.set_proxy_auth('admin', 'admin')
339
+ c.force_basic_auth = true
340
+ c.test_loopback_http_response << "HTTP/1.0 200 OK\nContent-Length: 2\n\nOK"
341
+ c.debug_dev = str = ''
342
+ c.get_content('http://example.com/')
343
+ assert_match(/Proxy-Authorization: Basic YWRtaW46YWRtaW4=/, str)
344
+ end
345
+
335
346
  def test_proxy_auth_reuses_credentials
336
347
  c = HTTPClient.new
337
348
  c.set_proxy_auth('admin', 'admin')
@@ -442,11 +453,19 @@ class TestAuth < Test::Unit::TestCase
442
453
  end
443
454
 
444
455
  def test_basic_auth_post_with_multipart
445
- c = HTTPClient.new
446
- c.set_auth("http://localhost:#{serverport}/", 'admin', 'admin')
447
- File.open(__FILE__) do |f|
448
- # read 'f' twice for authorization negotiation
449
- assert_equal('basic_auth OK', c.post("http://localhost:#{serverport}/basic_auth", :file => f).content)
456
+ retry_times = 0
457
+ begin
458
+ c = HTTPClient.new
459
+ c.set_auth("http://localhost:#{serverport}/", 'admin', 'admin')
460
+ File.open(__FILE__) do |f|
461
+ # read 'f' twice for authorization negotiation
462
+ assert_equal('basic_auth OK', c.post("http://localhost:#{serverport}/basic_auth", :file => f).content)
463
+ end
464
+ rescue Errno::ECONNRESET, HTTPClient::KeepAliveDisconnected
465
+ # TODO: WEBrick server returns ECONNRESET/EPIPE before sending Unauthorized response to client?
466
+ raise if retry_times > 5
467
+ retry_times += 1
468
+ retry
450
469
  end
451
470
  end
452
471