httpclient 2.6.0.1 → 2.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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