httpclient 2.8.1 → 2.8.3
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.
- checksums.yaml +4 -4
- data/lib/httpclient/jruby_ssl_socket.rb +99 -42
- data/lib/httpclient/session.rb +12 -6
- data/lib/httpclient/ssl_config.rb +54 -106
- data/lib/httpclient/ssl_socket.rb +26 -24
- data/lib/httpclient/version.rb +1 -1
- data/lib/httpclient.rb +2 -0
- data/test/jruby_ssl_socket/test_pemutils.rb +32 -0
- data/test/test_httpclient.rb +12 -0
- data/test/test_ssl.rb +212 -13
- metadata +4 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 45217dcc777d36d71246dd468e40b79caad351d6
|
4
|
+
data.tar.gz: afcf1a175414e0a1dde95eb5823b0fa339655d9c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f5ae105eb3b269d67521a35446e3518b359e7a359c0c8a14500ef9e9ff8c4681c20b103bb4d5a2f96cdd9caa337fc149f39df3754bb9f3185b6914f284adfdc0
|
7
|
+
data.tar.gz: 005d1769b6906e0c107ba63e7a178c4d73aae59198ed3efe866e8722fdadcf4c4142c3724c167b6db5f8a45cb03f517083164c18fdd1aa65074dc4ab362bc9de
|
@@ -15,9 +15,23 @@ class HTTPClient
|
|
15
15
|
unless defined?(SSLSocket)
|
16
16
|
|
17
17
|
class JavaSocketWrap
|
18
|
+
java_import 'java.net.InetSocketAddress'
|
18
19
|
java_import 'java.io.BufferedInputStream'
|
20
|
+
|
19
21
|
BUF_SIZE = 1024 * 16
|
20
22
|
|
23
|
+
def self.connect(socket, site, opts = {})
|
24
|
+
socket_addr = InetSocketAddress.new(site.host, site.port)
|
25
|
+
if opts[:connect_timeout]
|
26
|
+
socket.connect(socket_addr, opts[:connect_timeout])
|
27
|
+
else
|
28
|
+
socket.connect(socket_addr)
|
29
|
+
end
|
30
|
+
socket.setSoTimeout(opts[:so_timeout]) if opts[:so_timeout]
|
31
|
+
socket.setKeepAlive(true) if opts[:tcp_keepalive]
|
32
|
+
socket
|
33
|
+
end
|
34
|
+
|
21
35
|
def initialize(socket, debug_dev = nil)
|
22
36
|
@socket = socket
|
23
37
|
@debug_dev = debug_dev
|
@@ -39,7 +53,6 @@ unless defined?(SSLSocket)
|
|
39
53
|
@socket.isClosed
|
40
54
|
end
|
41
55
|
|
42
|
-
|
43
56
|
def gets(rs)
|
44
57
|
while (size = @bufstr.index(rs)).nil?
|
45
58
|
if fill() == -1
|
@@ -105,11 +118,15 @@ unless defined?(SSLSocket)
|
|
105
118
|
private
|
106
119
|
|
107
120
|
def fill
|
108
|
-
|
109
|
-
|
110
|
-
|
121
|
+
begin
|
122
|
+
size = @instr.read(@buf)
|
123
|
+
if size > 0
|
124
|
+
@bufstr << String.from_java_bytes(@buf, Encoding::BINARY)[0, size]
|
125
|
+
end
|
126
|
+
size
|
127
|
+
rescue java.io.IOException => e
|
128
|
+
raise OpenSSL::SSL::SSLError.new("#{e.class}: #{e.getMessage}")
|
111
129
|
end
|
112
|
-
size
|
113
130
|
end
|
114
131
|
|
115
132
|
def debug(str)
|
@@ -267,8 +284,8 @@ unless defined?(SSLSocket)
|
|
267
284
|
|
268
285
|
module PEMUtils
|
269
286
|
def self.read_certificate(pem)
|
270
|
-
|
271
|
-
der =
|
287
|
+
cert = pem.sub(/.*?-----BEGIN CERTIFICATE-----/m, '').sub(/-----END CERTIFICATE-----.*?/m, '')
|
288
|
+
der = cert.unpack('m*').first
|
272
289
|
cf = CertificateFactory.getInstance('X.509')
|
273
290
|
cf.generateCertificate(ByteArrayInputStream.new(der.to_java_bytes))
|
274
291
|
end
|
@@ -316,6 +333,7 @@ unless defined?(SSLSocket)
|
|
316
333
|
return if cert_source == :default
|
317
334
|
if cert_source.respond_to?(:to_pem)
|
318
335
|
pem = cert_source.to_pem
|
336
|
+
load_pem(pem)
|
319
337
|
elsif File.directory?(cert_source)
|
320
338
|
warn("#{cert_source}: directory not yet supported")
|
321
339
|
return
|
@@ -326,7 +344,8 @@ unless defined?(SSLSocket)
|
|
326
344
|
when /-----BEGIN CERTIFICATE-----/
|
327
345
|
pem = ''
|
328
346
|
when /-----END CERTIFICATE-----/
|
329
|
-
|
347
|
+
load_pem(pem)
|
348
|
+
# keep parsing in case where multiple certificates in a file
|
330
349
|
else
|
331
350
|
if pem
|
332
351
|
pem << line
|
@@ -334,9 +353,6 @@ unless defined?(SSLSocket)
|
|
334
353
|
end
|
335
354
|
end
|
336
355
|
end
|
337
|
-
cert = PEMUtils.read_certificate(pem)
|
338
|
-
@size += 1
|
339
|
-
@trust_store.setCertificateEntry("cert_#{@size}", cert)
|
340
356
|
end
|
341
357
|
|
342
358
|
def trust_store
|
@@ -346,6 +362,14 @@ unless defined?(SSLSocket)
|
|
346
362
|
@trust_store
|
347
363
|
end
|
348
364
|
end
|
365
|
+
|
366
|
+
private
|
367
|
+
|
368
|
+
def load_pem(pem)
|
369
|
+
cert = PEMUtils.read_certificate(pem)
|
370
|
+
@size += 1
|
371
|
+
@trust_store.setCertificateEntry("cert_#{@size}", cert)
|
372
|
+
end
|
349
373
|
end
|
350
374
|
|
351
375
|
# Ported from commons-httpclient 'BrowserCompatHostnameVerifier'
|
@@ -433,26 +457,56 @@ unless defined?(SSLSocket)
|
|
433
457
|
end
|
434
458
|
|
435
459
|
def self.create_socket(session)
|
436
|
-
|
437
|
-
|
460
|
+
opts = {
|
461
|
+
:connect_timeout => session.connect_timeout * 1000,
|
462
|
+
# send_timeout is ignored in JRuby
|
463
|
+
:so_timeout => session.receive_timeout * 1000,
|
464
|
+
:tcp_keepalive => session.tcp_keepalive,
|
465
|
+
:debug_dev => session.debug_dev
|
466
|
+
}
|
467
|
+
socket = nil
|
438
468
|
begin
|
439
469
|
if session.proxy
|
470
|
+
site = session.proxy || session.dest
|
471
|
+
socket = JavaSocketWrap.connect(Socket.new, site, opts)
|
440
472
|
session.connect_ssl_proxy(JavaSocketWrap.new(socket), Util.urify(session.dest.to_s))
|
441
473
|
end
|
474
|
+
new(socket, session.dest, session.ssl_config, opts)
|
442
475
|
rescue
|
443
|
-
socket.close
|
476
|
+
socket.close if socket
|
444
477
|
raise
|
445
478
|
end
|
446
|
-
new(socket, session.dest, session.ssl_config, session.debug_dev)
|
447
479
|
end
|
448
480
|
|
449
481
|
DEFAULT_SSL_PROTOCOL = (java.lang.System.getProperty('java.specification.version') == '1.7') ? 'TLSv1.2' : 'TLS'
|
450
|
-
def initialize(socket, dest, config,
|
482
|
+
def initialize(socket, dest, config, opts = {})
|
483
|
+
@config = config
|
484
|
+
begin
|
485
|
+
@ssl_socket = create_ssl_socket(socket, dest, config, opts)
|
486
|
+
ssl_version = java_ssl_version(config)
|
487
|
+
@ssl_socket.setEnabledProtocols([ssl_version].to_java(java.lang.String)) if ssl_version != DEFAULT_SSL_PROTOCOL
|
488
|
+
if config.ciphers != SSLConfig::CIPHERS_DEFAULT
|
489
|
+
@ssl_socket.setEnabledCipherSuites(config.ciphers.to_java(java.lang.String))
|
490
|
+
end
|
491
|
+
ssl_connect(dest.host)
|
492
|
+
rescue java.security.GeneralSecurityException => e
|
493
|
+
raise OpenSSL::SSL::SSLError.new(e.getMessage)
|
494
|
+
rescue java.io.IOException => e
|
495
|
+
raise OpenSSL::SSL::SSLError.new("#{e.class}: #{e.getMessage}")
|
496
|
+
end
|
497
|
+
|
498
|
+
super(@ssl_socket, opts[:debug_dev])
|
499
|
+
end
|
500
|
+
|
501
|
+
def java_ssl_version(config)
|
451
502
|
if config.ssl_version == :auto
|
452
|
-
|
503
|
+
DEFAULT_SSL_PROTOCOL
|
453
504
|
else
|
454
|
-
|
505
|
+
config.ssl_version.to_s.tr('_', '.')
|
455
506
|
end
|
507
|
+
end
|
508
|
+
|
509
|
+
def create_ssl_context(config)
|
456
510
|
unless config.cert_store_crl_items.empty?
|
457
511
|
raise NotImplementedError.new('Manual CRL configuration is not yet supported')
|
458
512
|
end
|
@@ -481,36 +535,24 @@ unless defined?(SSLSocket)
|
|
481
535
|
tmf.init(trust_store)
|
482
536
|
tm = tmf.getTrustManagers
|
483
537
|
|
484
|
-
ctx = SSLContext.getInstance(
|
538
|
+
ctx = SSLContext.getInstance(java_ssl_version(config))
|
485
539
|
ctx.init(km, tm, nil)
|
486
540
|
if config.timeout
|
487
541
|
ctx.getClientSessionContext.setSessionTimeout(config.timeout)
|
488
542
|
end
|
543
|
+
ctx
|
544
|
+
end
|
489
545
|
|
546
|
+
def create_ssl_socket(socket, dest, config, opts)
|
547
|
+
ctx = create_ssl_context(config)
|
490
548
|
factory = ctx.getSocketFactory
|
491
|
-
|
549
|
+
if socket
|
492
550
|
ssl_socket = factory.createSocket(socket, dest.host, dest.port, true)
|
493
|
-
|
494
|
-
|
495
|
-
|
496
|
-
end
|
497
|
-
ssl_socket.startHandshake
|
498
|
-
ssl_session = ssl_socket.getSession
|
499
|
-
@peer_cert = JavaCertificate.new(ssl_session.getPeerCertificates.first)
|
500
|
-
if $DEBUG
|
501
|
-
warn("Protocol version: #{ssl_session.getProtocol}")
|
502
|
-
warn("Cipher: #{ssl_socket.getSession.getCipherSuite}")
|
503
|
-
end
|
504
|
-
post_connection_check(dest.host, @peer_cert)
|
505
|
-
rescue java.security.GeneralSecurityException => e
|
506
|
-
raise OpenSSL::SSL::SSLError.new(e.getMessage)
|
507
|
-
rescue javax.net.ssl.SSLException => e
|
508
|
-
raise OpenSSL::SSL::SSLError.new(e.getMessage)
|
509
|
-
rescue java.net.SocketException => e
|
510
|
-
raise OpenSSL::SSL::SSLError.new(e.getMessage)
|
551
|
+
else
|
552
|
+
ssl_socket = factory.createSocket
|
553
|
+
JavaSocketWrap.connect(ssl_socket, dest, opts)
|
511
554
|
end
|
512
|
-
|
513
|
-
super(ssl_socket, debug_dev)
|
555
|
+
ssl_socket
|
514
556
|
end
|
515
557
|
|
516
558
|
def peer_cert
|
@@ -519,8 +561,23 @@ unless defined?(SSLSocket)
|
|
519
561
|
|
520
562
|
private
|
521
563
|
|
522
|
-
def
|
523
|
-
|
564
|
+
def ssl_connect(hostname)
|
565
|
+
@ssl_socket.startHandshake
|
566
|
+
ssl_session = @ssl_socket.getSession
|
567
|
+
@peer_cert = JavaCertificate.new(ssl_session.getPeerCertificates.first)
|
568
|
+
if $DEBUG
|
569
|
+
warn("Protocol version: #{ssl_session.getProtocol}")
|
570
|
+
warn("Cipher: #{@ssl_socket.getSession.getCipherSuite}")
|
571
|
+
end
|
572
|
+
post_connection_check(hostname)
|
573
|
+
end
|
574
|
+
|
575
|
+
def post_connection_check(hostname)
|
576
|
+
if !@config.verify?
|
577
|
+
return
|
578
|
+
else
|
579
|
+
BrowserCompatHostnameVerifier.new.verify(hostname, @peer_cert.cert)
|
580
|
+
end
|
524
581
|
end
|
525
582
|
end
|
526
583
|
|
data/lib/httpclient/session.rb
CHANGED
@@ -21,7 +21,7 @@ require 'zlib'
|
|
21
21
|
require 'httpclient/timeout' # TODO: remove this once we drop 1.8 support
|
22
22
|
require 'httpclient/ssl_config'
|
23
23
|
require 'httpclient/http'
|
24
|
-
if
|
24
|
+
if RUBY_ENGINE == 'jruby'
|
25
25
|
require 'httpclient/jruby_ssl_socket'
|
26
26
|
else
|
27
27
|
require 'httpclient/ssl_socket'
|
@@ -76,7 +76,7 @@ class HTTPClient
|
|
76
76
|
def to_s # :nodoc:
|
77
77
|
addr
|
78
78
|
end
|
79
|
-
|
79
|
+
|
80
80
|
# Returns true if scheme, host and port of the given URI matches with this.
|
81
81
|
def match(uri)
|
82
82
|
(@scheme == uri.scheme) and (@host == uri.host) and (@port == uri.port.to_i)
|
@@ -105,6 +105,8 @@ class HTTPClient
|
|
105
105
|
attr_accessor :debug_dev
|
106
106
|
# Boolean value for Socket#sync
|
107
107
|
attr_accessor :socket_sync
|
108
|
+
# Boolean value to send TCP keepalive packets; no timing settings exist at present
|
109
|
+
attr_accessor :tcp_keepalive
|
108
110
|
|
109
111
|
attr_accessor :connect_timeout
|
110
112
|
# Maximum retry count. 0 for infinite.
|
@@ -137,6 +139,7 @@ class HTTPClient
|
|
137
139
|
@protocol_version = nil
|
138
140
|
@debug_dev = client.debug_dev
|
139
141
|
@socket_sync = true
|
142
|
+
@tcp_keepalive = false
|
140
143
|
@chunk_size = ::HTTP::Message::Body::DEFAULT_CHUNK_SIZE
|
141
144
|
|
142
145
|
@connect_timeout = 60
|
@@ -216,6 +219,7 @@ class HTTPClient
|
|
216
219
|
sess = Session.new(@client, site, @agent_name, @from)
|
217
220
|
sess.proxy = via_proxy ? @proxy : nil
|
218
221
|
sess.socket_sync = @socket_sync
|
222
|
+
sess.tcp_keepalive = @tcp_keepalive
|
219
223
|
sess.requested_version = @protocol_version if @protocol_version
|
220
224
|
sess.connect_timeout = @connect_timeout
|
221
225
|
sess.connect_retry = @connect_retry
|
@@ -437,6 +441,8 @@ class HTTPClient
|
|
437
441
|
attr_accessor :proxy
|
438
442
|
# Boolean value for Socket#sync
|
439
443
|
attr_accessor :socket_sync
|
444
|
+
# Boolean value to send TCP keepalive packets; no timing settings exist at present
|
445
|
+
attr_accessor :tcp_keepalive
|
440
446
|
# Requested protocol version
|
441
447
|
attr_accessor :requested_version
|
442
448
|
# Device for dumping log for debugging
|
@@ -464,6 +470,7 @@ class HTTPClient
|
|
464
470
|
@dest = dest
|
465
471
|
@proxy = nil
|
466
472
|
@socket_sync = true
|
473
|
+
@tcp_keepalive = false
|
467
474
|
@requested_version = nil
|
468
475
|
|
469
476
|
@debug_dev = nil
|
@@ -606,17 +613,16 @@ class HTTPClient
|
|
606
613
|
clean_local = @socket_local.host.delete("[]")
|
607
614
|
socket = TCPSocket.new(clean_host, port, clean_local, @socket_local.port)
|
608
615
|
end
|
616
|
+
socket.setsockopt(Socket::SOL_SOCKET, Socket::SO_KEEPALIVE, true) if @tcp_keepalive
|
609
617
|
if @debug_dev
|
610
618
|
@debug_dev << "! CONNECTION ESTABLISHED\n"
|
611
619
|
socket.extend(DebugSocket)
|
612
620
|
socket.debug_dev = @debug_dev
|
613
621
|
end
|
614
622
|
rescue SystemCallError => e
|
615
|
-
e.message
|
616
|
-
raise
|
623
|
+
raise e.class, e.message + " (#{host}:#{port})"
|
617
624
|
rescue SocketError => e
|
618
|
-
e.message
|
619
|
-
raise
|
625
|
+
raise e.class, e.message + " (#{host}:#{port})"
|
620
626
|
end
|
621
627
|
socket
|
622
628
|
end
|
@@ -44,16 +44,21 @@ class HTTPClient
|
|
44
44
|
class Store
|
45
45
|
attr_reader :_httpclient_cert_store_items
|
46
46
|
|
47
|
-
|
48
|
-
|
47
|
+
# TODO: use prepend instead when we drop JRuby + 1.9.x support
|
48
|
+
wrapped = {}
|
49
|
+
|
50
|
+
wrapped[:initialize] = instance_method(:initialize)
|
51
|
+
define_method(:initialize) do |*args|
|
52
|
+
wrapped[:initialize].bind(self).call(*args)
|
49
53
|
@_httpclient_cert_store_items = [ENV['SSL_CERT_FILE'] || :default]
|
50
54
|
end
|
51
55
|
|
52
56
|
[:add_cert, :add_file, :add_path].each do |m|
|
53
|
-
wrapped = instance_method(m)
|
57
|
+
wrapped[m] = instance_method(m)
|
54
58
|
define_method(m) do |cert|
|
55
|
-
wrapped.bind(self).call(cert)
|
59
|
+
res = wrapped[m].bind(self).call(cert)
|
56
60
|
@_httpclient_cert_store_items << cert
|
61
|
+
res
|
57
62
|
end
|
58
63
|
end
|
59
64
|
end
|
@@ -61,55 +66,76 @@ class HTTPClient
|
|
61
66
|
end
|
62
67
|
end
|
63
68
|
|
69
|
+
class << self
|
70
|
+
private
|
71
|
+
def attr_config(symbol)
|
72
|
+
name = symbol.to_s
|
73
|
+
ivar_name = "@#{name}"
|
74
|
+
define_method(name) {
|
75
|
+
instance_variable_get(ivar_name)
|
76
|
+
}
|
77
|
+
define_method("#{name}=") { |rhs|
|
78
|
+
if instance_variable_get(ivar_name) != rhs
|
79
|
+
instance_variable_set(ivar_name, rhs)
|
80
|
+
change_notify
|
81
|
+
end
|
82
|
+
}
|
83
|
+
symbol
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
|
64
88
|
CIPHERS_DEFAULT = "ALL:!aNULL:!eNULL:!SSLv2" # OpenSSL >1.0.0 default
|
65
89
|
|
66
90
|
# Which TLS protocol version (also called method) will be used. Defaults
|
67
|
-
# to :auto which means that OpenSSL decides (In my tests this resulted
|
91
|
+
# to :auto which means that OpenSSL decides (In my tests this resulted
|
68
92
|
# with always the highest available protocol being used).
|
69
93
|
# String name of OpenSSL's SSL version method name: TLSv1_2, TLSv1_1, TLSv1,
|
70
94
|
# SSLv2, SSLv23, SSLv3 or :auto (and nil) to allow version negotiation (default).
|
71
95
|
# See {OpenSSL::SSL::SSLContext::METHODS} for a list of available versions
|
72
96
|
# in your specific Ruby environment.
|
73
|
-
|
97
|
+
attr_config :ssl_version
|
74
98
|
# OpenSSL::X509::Certificate:: certificate for SSL client authentication.
|
75
99
|
# nil by default. (no client authentication)
|
76
|
-
|
100
|
+
attr_config :client_cert
|
77
101
|
# OpenSSL::PKey::PKey:: private key for SSL client authentication.
|
78
102
|
# nil by default. (no client authentication)
|
79
|
-
|
80
|
-
|
103
|
+
attr_config :client_key
|
104
|
+
# OpenSSL::PKey::PKey:: private key pass phrase for client_key.
|
105
|
+
# nil by default. (no pass phrase)
|
106
|
+
attr_config :client_key_pass
|
81
107
|
|
82
108
|
# A number which represents OpenSSL's verify mode. Default value is
|
83
109
|
# OpenSSL::SSL::VERIFY_PEER | OpenSSL::SSL::VERIFY_FAIL_IF_NO_PEER_CERT.
|
84
|
-
|
110
|
+
attr_config :verify_mode
|
85
111
|
# A number of verify depth. Certification path which length is longer than
|
86
112
|
# this depth is not allowed.
|
87
113
|
# CAUTION: this is OpenSSL specific option and ignored on JRuby.
|
88
|
-
|
114
|
+
attr_config :verify_depth
|
89
115
|
# A callback handler for custom certificate verification. nil by default.
|
90
116
|
# If the handler is set, handler.call is invoked just after general
|
91
117
|
# OpenSSL's verification. handler.call is invoked with 2 arguments,
|
92
118
|
# ok and ctx; ok is a result of general OpenSSL's verification. ctx is a
|
93
119
|
# OpenSSL::X509::StoreContext.
|
94
|
-
|
120
|
+
attr_config :verify_callback
|
95
121
|
# SSL timeout in sec. nil by default.
|
96
|
-
|
122
|
+
attr_config :timeout
|
97
123
|
# A number of OpenSSL's SSL options. Default value is
|
98
124
|
# OpenSSL::SSL::OP_ALL | OpenSSL::SSL::OP_NO_SSLv2
|
99
125
|
# CAUTION: this is OpenSSL specific option and ignored on JRuby.
|
100
126
|
# Use ssl_version to specify the TLS version you want to use.
|
101
|
-
|
127
|
+
attr_config :options
|
102
128
|
# A String of OpenSSL's cipher configuration. Default value is
|
103
129
|
# ALL:!ADH:!LOW:!EXP:!MD5:+SSLv2:@STRENGTH
|
104
130
|
# See ciphers(1) man in OpenSSL for more detail.
|
105
|
-
|
131
|
+
attr_config :ciphers
|
106
132
|
|
107
133
|
# OpenSSL::X509::X509::Store used for verification. You can reset the
|
108
134
|
# store with clear_cert_store and set the new store with cert_store=.
|
109
135
|
attr_reader :cert_store # don't use if you don't know what it is.
|
110
136
|
|
111
137
|
# For server side configuration. Ignore this.
|
112
|
-
|
138
|
+
attr_config :client_ca # :nodoc:
|
113
139
|
|
114
140
|
# These array keeps original files/dirs that was added to @cert_store
|
115
141
|
def cert_store_items; @cert_store._httpclient_cert_store_items; end
|
@@ -121,7 +147,7 @@ class HTTPClient
|
|
121
147
|
@client = client
|
122
148
|
@cert_store = X509::Store.new
|
123
149
|
@cert_store_crl_items = []
|
124
|
-
@client_cert = @client_key = @client_ca = nil
|
150
|
+
@client_cert = @client_key = @client_key_pass = @client_ca = nil
|
125
151
|
@verify_mode = SSL::VERIFY_PEER | SSL::VERIFY_FAIL_IF_NO_PEER_CERT
|
126
152
|
@verify_depth = nil
|
127
153
|
@verify_callback = nil
|
@@ -139,42 +165,18 @@ class HTTPClient
|
|
139
165
|
@cacerts_loaded = false
|
140
166
|
end
|
141
167
|
|
142
|
-
# Sets SSL version method String. Possible values: "SSLv2" for SSL2,
|
143
|
-
# "SSLv3" for SSL3 and TLS1.x, "SSLv23" for SSL3 with fallback to SSL2.
|
144
|
-
def ssl_version=(ssl_version)
|
145
|
-
@ssl_version = ssl_version
|
146
|
-
change_notify
|
147
|
-
end
|
148
|
-
|
149
|
-
# Sets certificate (OpenSSL::X509::Certificate) for SSL client
|
150
|
-
# authentication.
|
151
|
-
# client_key and client_cert must be a pair.
|
152
|
-
#
|
153
|
-
# Calling this method resets all existing sessions.
|
154
|
-
def client_cert=(client_cert)
|
155
|
-
@client_cert = client_cert
|
156
|
-
change_notify
|
157
|
-
end
|
158
|
-
|
159
|
-
# Sets private key (OpenSSL::PKey::PKey) for SSL client authentication.
|
160
|
-
# client_key and client_cert must be a pair.
|
161
|
-
#
|
162
|
-
# Calling this method resets all existing sessions.
|
163
|
-
def client_key=(client_key)
|
164
|
-
@client_key = client_key
|
165
|
-
change_notify
|
166
|
-
end
|
167
|
-
|
168
168
|
# Sets certificate and private key for SSL client authentication.
|
169
169
|
# cert_file:: must be a filename of PEM/DER formatted file.
|
170
170
|
# key_file:: must be a filename of PEM/DER formatted file. Key must be an
|
171
171
|
# RSA key. If you want to use other PKey algorithm,
|
172
172
|
# use client_key=.
|
173
173
|
#
|
174
|
-
# Calling this method resets all existing sessions.
|
174
|
+
# Calling this method resets all existing sessions if value is changed.
|
175
175
|
def set_client_cert_file(cert_file, key_file, pass = nil)
|
176
|
-
@client_cert
|
177
|
-
|
176
|
+
if (@client_cert != cert_file) || (@client_key != key_file) || (@client_key_pass != pass)
|
177
|
+
@client_cert, @client_key, @client_key_pass = cert_file, key_file, pass
|
178
|
+
change_notify
|
179
|
+
end
|
178
180
|
end
|
179
181
|
|
180
182
|
# Sets OpenSSL's default trusted CA certificates. Generally, OpenSSL is
|
@@ -211,9 +213,12 @@ class HTTPClient
|
|
211
213
|
#
|
212
214
|
# Calling this method resets all existing sessions.
|
213
215
|
def cert_store=(cert_store)
|
214
|
-
|
215
|
-
@cert_store
|
216
|
-
|
216
|
+
# This is object equality check, since OpenSSL::X509::Store doesn't overload ==
|
217
|
+
if !@cacerts_loaded || (@cert_store != cert_store)
|
218
|
+
@cacerts_loaded = true # avoid lazy override
|
219
|
+
@cert_store = cert_store
|
220
|
+
change_notify
|
221
|
+
end
|
217
222
|
end
|
218
223
|
|
219
224
|
# Sets trust anchor certificate(s) for verification.
|
@@ -271,62 +276,6 @@ class HTTPClient
|
|
271
276
|
end
|
272
277
|
alias set_crl add_crl
|
273
278
|
|
274
|
-
# Sets verify mode of OpenSSL. New value must be a combination of
|
275
|
-
# constants OpenSSL::SSL::VERIFY_*
|
276
|
-
#
|
277
|
-
# Calling this method resets all existing sessions.
|
278
|
-
def verify_mode=(verify_mode)
|
279
|
-
@verify_mode = verify_mode
|
280
|
-
change_notify
|
281
|
-
end
|
282
|
-
|
283
|
-
# Sets verify depth. New value must be a number.
|
284
|
-
#
|
285
|
-
# Calling this method resets all existing sessions.
|
286
|
-
def verify_depth=(verify_depth)
|
287
|
-
@verify_depth = verify_depth
|
288
|
-
change_notify
|
289
|
-
end
|
290
|
-
|
291
|
-
# Sets callback handler for custom certificate verification.
|
292
|
-
# See verify_callback.
|
293
|
-
#
|
294
|
-
# Calling this method resets all existing sessions.
|
295
|
-
def verify_callback=(verify_callback)
|
296
|
-
@verify_callback = verify_callback
|
297
|
-
change_notify
|
298
|
-
end
|
299
|
-
|
300
|
-
# Sets SSL timeout in sec.
|
301
|
-
#
|
302
|
-
# Calling this method resets all existing sessions.
|
303
|
-
def timeout=(timeout)
|
304
|
-
@timeout = timeout
|
305
|
-
change_notify
|
306
|
-
end
|
307
|
-
|
308
|
-
# Sets SSL options. New value must be a combination of # constants
|
309
|
-
# OpenSSL::SSL::OP_*
|
310
|
-
#
|
311
|
-
# Calling this method resets all existing sessions.
|
312
|
-
def options=(options)
|
313
|
-
@options = options
|
314
|
-
change_notify
|
315
|
-
end
|
316
|
-
|
317
|
-
# Sets cipher configuration. New value must be a String.
|
318
|
-
#
|
319
|
-
# Calling this method resets all existing sessions.
|
320
|
-
def ciphers=(ciphers)
|
321
|
-
@ciphers = ciphers
|
322
|
-
change_notify
|
323
|
-
end
|
324
|
-
|
325
|
-
def client_ca=(client_ca) # :nodoc:
|
326
|
-
@client_ca = client_ca
|
327
|
-
change_notify
|
328
|
-
end
|
329
|
-
|
330
279
|
def verify?
|
331
280
|
@verify_mode && (@verify_mode & OpenSSL::SSL::VERIFY_PEER != 0)
|
332
281
|
end
|
@@ -466,7 +415,6 @@ class HTTPClient
|
|
466
415
|
|
467
416
|
# Use 2048 bit certs trust anchor
|
468
417
|
def load_cacerts(cert_store)
|
469
|
-
ver = OpenSSL::OPENSSL_VERSION
|
470
418
|
file = File.join(File.dirname(__FILE__), 'cacert.pem')
|
471
419
|
add_trust_ca_to_store(cert_store, file)
|
472
420
|
end
|
@@ -14,43 +14,31 @@ class HTTPClient
|
|
14
14
|
# Wraps up OpenSSL::SSL::SSLSocket and offers debugging features.
|
15
15
|
class SSLSocket
|
16
16
|
def self.create_socket(session)
|
17
|
+
opts = {
|
18
|
+
:debug_dev => session.debug_dev
|
19
|
+
}
|
17
20
|
site = session.proxy || session.dest
|
18
21
|
socket = session.create_socket(site.host, site.port)
|
19
22
|
begin
|
20
23
|
if session.proxy
|
21
24
|
session.connect_ssl_proxy(socket, Util.urify(session.dest.to_s))
|
22
25
|
end
|
23
|
-
|
24
|
-
ssl_socket.ssl_connect(session.dest.host)
|
25
|
-
ssl_socket
|
26
|
+
new(socket, session.dest, session.ssl_config, opts)
|
26
27
|
rescue
|
27
28
|
socket.close
|
28
29
|
raise
|
29
30
|
end
|
30
31
|
end
|
31
32
|
|
32
|
-
def initialize(socket,
|
33
|
+
def initialize(socket, dest, config, opts = {})
|
33
34
|
unless SSLEnabled
|
34
35
|
raise ConfigurationError.new('Ruby/OpenSSL module is required')
|
35
36
|
end
|
36
37
|
@socket = socket
|
37
|
-
@
|
38
|
+
@config = config
|
38
39
|
@ssl_socket = create_openssl_socket(@socket)
|
39
|
-
@debug_dev = debug_dev
|
40
|
-
|
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
|
-
end
|
53
|
-
post_connection_check(hostname)
|
40
|
+
@debug_dev = opts[:debug_dev]
|
41
|
+
ssl_connect(dest.host)
|
54
42
|
end
|
55
43
|
|
56
44
|
def peer_cert
|
@@ -108,8 +96,22 @@ class HTTPClient
|
|
108
96
|
|
109
97
|
private
|
110
98
|
|
99
|
+
def ssl_connect(hostname = nil)
|
100
|
+
if hostname && @ssl_socket.respond_to?(:hostname=)
|
101
|
+
@ssl_socket.hostname = hostname
|
102
|
+
end
|
103
|
+
@ssl_socket.connect
|
104
|
+
if $DEBUG
|
105
|
+
if @ssl_socket.respond_to?(:ssl_version)
|
106
|
+
warn("Protocol version: #{@ssl_socket.ssl_version}")
|
107
|
+
end
|
108
|
+
warn("Cipher: #{@ssl_socket.cipher.inspect}")
|
109
|
+
end
|
110
|
+
post_connection_check(hostname)
|
111
|
+
end
|
112
|
+
|
111
113
|
def post_connection_check(hostname)
|
112
|
-
verify_mode = @
|
114
|
+
verify_mode = @config.verify_mode || OpenSSL::SSL::VERIFY_NONE
|
113
115
|
if verify_mode == OpenSSL::SSL::VERIFY_NONE
|
114
116
|
return
|
115
117
|
elsif @ssl_socket.peer_cert.nil? and
|
@@ -119,7 +121,7 @@ class HTTPClient
|
|
119
121
|
if @ssl_socket.respond_to?(:post_connection_check) and RUBY_VERSION > "1.8.4"
|
120
122
|
@ssl_socket.post_connection_check(hostname)
|
121
123
|
else
|
122
|
-
@
|
124
|
+
@config.post_connection_check(@ssl_socket.peer_cert, hostname)
|
123
125
|
end
|
124
126
|
end
|
125
127
|
|
@@ -131,11 +133,11 @@ class HTTPClient
|
|
131
133
|
ssl_socket = nil
|
132
134
|
if OpenSSL::SSL.const_defined?("SSLContext")
|
133
135
|
ctx = OpenSSL::SSL::SSLContext.new
|
134
|
-
@
|
136
|
+
@config.set_context(ctx)
|
135
137
|
ssl_socket = OpenSSL::SSL::SSLSocket.new(socket, ctx)
|
136
138
|
else
|
137
139
|
ssl_socket = OpenSSL::SSL::SSLSocket.new(socket)
|
138
|
-
@
|
140
|
+
@config.set_context(ssl_socket)
|
139
141
|
end
|
140
142
|
ssl_socket
|
141
143
|
end
|
data/lib/httpclient/version.rb
CHANGED
data/lib/httpclient.rb
CHANGED
@@ -355,6 +355,8 @@ class HTTPClient
|
|
355
355
|
# if your ruby is older than 2005-09-06, do not set socket_sync = false to
|
356
356
|
# avoid an SSL socket blocking bug in openssl/buffering.rb.
|
357
357
|
attr_proxy(:socket_sync, true)
|
358
|
+
# Enables TCP keepalive; no timing settings exist at present
|
359
|
+
attr_proxy(:tcp_keepalive, true)
|
358
360
|
# User-Agent header in HTTP request.
|
359
361
|
attr_proxy(:agent_name, true)
|
360
362
|
# From header in HTTP request.
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require File.expand_path('helper', File.join(File.dirname(__FILE__), ".."))
|
2
|
+
|
3
|
+
|
4
|
+
class PEMUtilsTest < Test::Unit::TestCase
|
5
|
+
include Helper
|
6
|
+
|
7
|
+
def setup
|
8
|
+
@raw_cert = "-----BEGIN CERTIFICATE-----\nMIIDOTCCAiGgAwIBAgIBAjANBgkqhkiG9w0BAQsFADBCMRMwEQYKCZImiZPyLGQB\nGRYDb3JnMRkwFwYKCZImiZPyLGQBGRYJcnVieS1sYW5nMRAwDgYDVQQDDAdSdWJ5\nIENBMB4XDTE2MDgxMDE3MjEzNFoXDTE3MDgxMDE3MjEzNFowSzETMBEGCgmSJomT\n8ixkARkWA29yZzEZMBcGCgmSJomT8ixkARkWCXJ1YnktbGFuZzEZMBcGA1UEAwwQ\nUnVieSBjZXJ0aWZpY2F0ZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB\nAJCfsSXpSMpmZCVa+ZCM+QDgomnhDlvnrGDq6pasTaIspGTXgws+7r8Dt/cNe6EH\nHJpRH2cGRiO4yPcfcT9eS4X7k8OC4f33wHfACOmLu6LeoNE8ujmSk6L6WzLUI+sE\nnLZbFrXxoAo4XHsm8vEG9C+jEoXZ1p+47wrAGaDwDQTnzlMy4dT9pRQEJP2G/Rry\nUkuZn8SUWmh3/YS78iaSzsNF1cgE1ealHOrPPFDjiCGDaH/LHyUPYlbFSLZ/B7Qx\nLxi5sePLcywWq/EJrmWpgeVTDjtNijsdKv/A3qkY+fm/oD0pzt7XsfJaP9YKNyJO\nQFdxWZeiPcDF+Hwf+IwSr+kCAwEAAaMxMC8wDgYDVR0PAQH/BAQDAgeAMB0GA1Ud\nDgQWBBQNvzYzJyXemGhxbA8NMXLolDnPyjANBgkqhkiG9w0BAQsFAAOCAQEARIJV\noKejGlOTn71QutnNnu07UtTu0IHs6YqjYzzND+m4JXLN+wvYm72AFUG0b1L7dRg0\niK8XjQrlNQNVqP1Mc6tffchy20neOPOHeiO6qTdRU8P2S8D3Uwe+1qhgxjfE+cWc\nwZmWxYK4HA8c58PxWMqrkr2QqXDplG9KWLvOgrtPGiLLZcQSKhvvB63QzItHBDU6\nRayiJY3oPkK/HrIvFlySqFqzWmuyknkciOFywEHQMz/tcSFJ2QFpPj/tBz9VXohH\nZ8KscmfhZrTPBjo+ky1lz/WraWoz4LMiLnkC2ABczWLRSawu+v3Irx1NFJngt05e\npqwtqIUeg7j+JLiTaA==\n-----END CERTIFICATE-----"
|
9
|
+
end
|
10
|
+
|
11
|
+
def test_read_certificate
|
12
|
+
assert_nothing_raised do
|
13
|
+
binary = HTTPClient::JRubySSLSocket::PEMUtils.read_certificate(@raw_cert)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def test_read_certificate_works_with_random_ascii_text_outside_begin_end
|
18
|
+
raw_cert_with_ascii = "some text before begin\n" + @raw_cert + "\nsome text after end"
|
19
|
+
assert_nothing_raised do
|
20
|
+
binary = HTTPClient::JRubySSLSocket::PEMUtils.read_certificate(raw_cert_with_ascii)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def test_read_certificate_uses_all_content_if_missing_begin_end
|
25
|
+
cert = @raw_cert.sub(/-----BEGIN CERTIFICATE-----/, '').sub(/-----END CERTIFICATE-----/, '')
|
26
|
+
assert_nothing_raised do
|
27
|
+
binary = HTTPClient::JRubySSLSocket::PEMUtils.read_certificate(cert)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
|
32
|
+
end
|
data/test/test_httpclient.rb
CHANGED
@@ -1908,6 +1908,18 @@ EOS
|
|
1908
1908
|
end
|
1909
1909
|
end
|
1910
1910
|
|
1911
|
+
def test_tcp_keepalive
|
1912
|
+
@client.tcp_keepalive = true
|
1913
|
+
@client.get(serverurl)
|
1914
|
+
|
1915
|
+
# expecting HTTP keepalive caches the socket
|
1916
|
+
session = @client.instance_variable_get(:@session_manager).send(:get_cached_session, HTTPClient::Site.new(URI.parse(serverurl)))
|
1917
|
+
socket = session.instance_variable_get(:@socket)
|
1918
|
+
|
1919
|
+
assert_true(session.tcp_keepalive)
|
1920
|
+
assert_equal(Socket::SO_KEEPALIVE, socket.getsockopt(Socket::SOL_SOCKET, Socket::SO_KEEPALIVE).optname)
|
1921
|
+
end
|
1922
|
+
|
1911
1923
|
private
|
1912
1924
|
|
1913
1925
|
def check_query_get(query)
|
data/test/test_ssl.rb
CHANGED
@@ -80,6 +80,16 @@ end
|
|
80
80
|
assert(str.scan(/^hello$/)[0])
|
81
81
|
end
|
82
82
|
|
83
|
+
def test_verification_without_httpclient
|
84
|
+
raw_cert = "-----BEGIN CERTIFICATE-----\nMIIDOTCCAiGgAwIBAgIBAjANBgkqhkiG9w0BAQsFADBCMRMwEQYKCZImiZPyLGQB\nGRYDb3JnMRkwFwYKCZImiZPyLGQBGRYJcnVieS1sYW5nMRAwDgYDVQQDDAdSdWJ5\nIENBMB4XDTE2MDgxMDE3MjEzNFoXDTE3MDgxMDE3MjEzNFowSzETMBEGCgmSJomT\n8ixkARkWA29yZzEZMBcGCgmSJomT8ixkARkWCXJ1YnktbGFuZzEZMBcGA1UEAwwQ\nUnVieSBjZXJ0aWZpY2F0ZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB\nAJCfsSXpSMpmZCVa+ZCM+QDgomnhDlvnrGDq6pasTaIspGTXgws+7r8Dt/cNe6EH\nHJpRH2cGRiO4yPcfcT9eS4X7k8OC4f33wHfACOmLu6LeoNE8ujmSk6L6WzLUI+sE\nnLZbFrXxoAo4XHsm8vEG9C+jEoXZ1p+47wrAGaDwDQTnzlMy4dT9pRQEJP2G/Rry\nUkuZn8SUWmh3/YS78iaSzsNF1cgE1ealHOrPPFDjiCGDaH/LHyUPYlbFSLZ/B7Qx\nLxi5sePLcywWq/EJrmWpgeVTDjtNijsdKv/A3qkY+fm/oD0pzt7XsfJaP9YKNyJO\nQFdxWZeiPcDF+Hwf+IwSr+kCAwEAAaMxMC8wDgYDVR0PAQH/BAQDAgeAMB0GA1Ud\nDgQWBBQNvzYzJyXemGhxbA8NMXLolDnPyjANBgkqhkiG9w0BAQsFAAOCAQEARIJV\noKejGlOTn71QutnNnu07UtTu0IHs6YqjYzzND+m4JXLN+wvYm72AFUG0b1L7dRg0\niK8XjQrlNQNVqP1Mc6tffchy20neOPOHeiO6qTdRU8P2S8D3Uwe+1qhgxjfE+cWc\nwZmWxYK4HA8c58PxWMqrkr2QqXDplG9KWLvOgrtPGiLLZcQSKhvvB63QzItHBDU6\nRayiJY3oPkK/HrIvFlySqFqzWmuyknkciOFywEHQMz/tcSFJ2QFpPj/tBz9VXohH\nZ8KscmfhZrTPBjo+ky1lz/WraWoz4LMiLnkC2ABczWLRSawu+v3Irx1NFJngt05e\npqwtqIUeg7j+JLiTaA==\n-----END CERTIFICATE-----"
|
85
|
+
raw_ca_cert = "-----BEGIN CERTIFICATE-----\nMIIDYjCCAkqgAwIBAgIBATANBgkqhkiG9w0BAQsFADBCMRMwEQYKCZImiZPyLGQB\nGRYDb3JnMRkwFwYKCZImiZPyLGQBGRYJcnVieS1sYW5nMRAwDgYDVQQDDAdSdWJ5\nIENBMB4XDTE2MDgxMDE3MjA1NFoXDTE4MDgxMDE3MjA1NFowQjETMBEGCgmSJomT\n8ixkARkWA29yZzEZMBcGCgmSJomT8ixkARkWCXJ1YnktbGFuZzEQMA4GA1UEAwwH\nUnVieSBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALKGwyM3Ejtl\npo7CqaDlS71gDZn3gm6IwWpmRMLJofSI9LCwAbjijSC2HvO0xUWoYW40FbzjnnEi\ngszsWyPwuQIx9t0bhuAyllNIfImmkaQkrikXKBKzia4jPnbc4iXPnfjuThjESFWl\ntfbN6y1B5TjKhD1KelfakUO+iMu8WlIA9NKQZYfJ/F3QSpP5Iqb3KN/jVifFbDV8\nbAl3Ln4rT2kTCKrZZcl1jmWsJv8jBw6+P7hk0/Mu0JeHAITsjbNbpHd8UXpCfbVs\nsNGZrBU4uJdZ2YTG+Y27/t25jFNQwb+TWbvig7rfdX2sjssuxa00BBxarC08tIVj\nZprM37KcNn8CAwEAAaNjMGEwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC\nAQYwHQYDVR0OBBYEFA2/NjMnJd6YaHFsDw0xcuiUOc/KMB8GA1UdIwQYMBYEFA2/\nNjMnJd6YaHFsDw0xcuiUOc/KMA0GCSqGSIb3DQEBCwUAA4IBAQAJSOw49XqvUll0\n3vU9EAO6yUdeZSsQENIfYbRMQgapbnN1vTyrUjPZkGC5hIE1pVdoHtEoUEICxIwy\nr6BKxiSLBDLp+rvIuDdzMkXIWdUVvTZguVRyKtM2gfnpsPLpVnv+stBmAW2SMyxm\nkymhOpkjdv3He+45uorB3tdfBS9VVomDEUJdg38UE1b5eXRQ3D6gG0iCPFzKszXg\nLoAYhGxtjCJaKlbzduMK0YO6aelgW1+XnVIKcA7DJ9egk5d/dFZBPFfwumwr9hTH\nh7/fp3Fr87weI+CkfmFyJZrsEBlXJBVuvPesMVHTh3Whm5kmCdWcBJU0QmSq42ZL\n72U0PXLR\n-----END CERTIFICATE-----"
|
86
|
+
ca_cert = ::OpenSSL::X509::Certificate.new(raw_ca_cert)
|
87
|
+
cert = ::OpenSSL::X509::Certificate.new(raw_cert)
|
88
|
+
store = ::OpenSSL::X509::Store.new
|
89
|
+
store.add_cert(ca_cert)
|
90
|
+
assert(store.verify(cert))
|
91
|
+
end
|
92
|
+
|
83
93
|
def test_verification
|
84
94
|
cfg = @client.ssl_config
|
85
95
|
cfg.verify_callback = method(:verify_callback).to_proc
|
@@ -165,6 +175,13 @@ end
|
|
165
175
|
#
|
166
176
|
cfg.cert_store.add_cert(cert('subca.cert'))
|
167
177
|
assert_equal("hello", @client.get_content(@url))
|
178
|
+
cfg.clear_cert_store
|
179
|
+
begin
|
180
|
+
@client.get(@url)
|
181
|
+
assert(false)
|
182
|
+
rescue OpenSSL::SSL::SSLError => ssle
|
183
|
+
assert_match(/(certificate verify failed|unable to find valid certification path to requested target)/, ssle.message)
|
184
|
+
end
|
168
185
|
end
|
169
186
|
|
170
187
|
if defined?(HTTPClient::JRubySSLSocket)
|
@@ -217,18 +234,14 @@ else
|
|
217
234
|
end
|
218
235
|
end
|
219
236
|
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
ENV['SSL_CERT_FILE'] = File.join(DIR, 'ca-chain.pem')
|
229
|
-
@client.ssl_config.set_default_paths
|
230
|
-
@client.get(@url)
|
231
|
-
end
|
237
|
+
def test_set_default_paths
|
238
|
+
assert_raise(OpenSSL::SSL::SSLError) do
|
239
|
+
@client.get(@url)
|
240
|
+
end
|
241
|
+
escape_env do
|
242
|
+
ENV['SSL_CERT_FILE'] = File.join(DIR, 'ca-chain.pem')
|
243
|
+
@client.ssl_config.set_default_paths
|
244
|
+
@client.get(@url)
|
232
245
|
end
|
233
246
|
end
|
234
247
|
|
@@ -262,6 +275,158 @@ end
|
|
262
275
|
end
|
263
276
|
end
|
264
277
|
|
278
|
+
VERIFY_TEST_CERT_LOCALHOST = OpenSSL::X509::Certificate.new(<<-EOS)
|
279
|
+
-----BEGIN CERTIFICATE-----
|
280
|
+
MIIB9jCCAV+gAwIBAgIJAIH8Gsm4PcNKMA0GCSqGSIb3DQEBCwUAMBQxEjAQBgNV
|
281
|
+
BAMMCWxvY2FsaG9zdDAeFw0xNjA4MTgxMDI2MDVaFw00NDAxMDMxMDI2MDVaMBQx
|
282
|
+
EjAQBgNVBAMMCWxvY2FsaG9zdDCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA
|
283
|
+
p7D8q0lcx5EZEV5+zPnQsxrbft5xyhH/MCStbH46DRATGPNSOaLRCG5r8gTKQzpD
|
284
|
+
4swGrQFYe2ienQ+7o4aEHErsXp4O/EmDKeiXWWrMqPr23r3HOBDebuynC/sCwy7N
|
285
|
+
epnX9u1VLB03eo+suj4d86OoOF+o11t9ZP+GA29Rsf8CAwEAAaNQME4wHQYDVR0O
|
286
|
+
BBYEFIxsJuPVvd5KKFcAvHGSeKSsWiUJMB8GA1UdIwQYMBaAFIxsJuPVvd5KKFcA
|
287
|
+
vHGSeKSsWiUJMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQELBQADgYEAMJaVCrrM
|
288
|
+
SM2I06Vr4BL+jtDFhZh3HmJFEDpwEFQ5Y9hduwdUGRBGCpkuea3fE2FKwWW9gLM1
|
289
|
+
w7rFMzYFtCEqm78dJWIU79MRy0wjO4LgtYfoikgBh6JKWuV5ed/+L3sLyLG0ZTtv
|
290
|
+
lrD7lzDtXgwvj007PxDoYRp3JwYzKRmTbH8=
|
291
|
+
-----END CERTIFICATE-----
|
292
|
+
EOS
|
293
|
+
|
294
|
+
VERIFY_TEST_CERT_FOO_DOMAIN = OpenSSL::X509::Certificate.new(<<-EOS)
|
295
|
+
-----BEGIN CERTIFICATE-----
|
296
|
+
MIIB8jCCAVugAwIBAgIJAL/od7Whx7VTMA0GCSqGSIb3DQEBCwUAMBIxEDAOBgNV
|
297
|
+
BAMMB2Zvby5jb20wHhcNMTYwODE4MTAyMzUyWhcNNDQwMTAzMTAyMzUyWjASMRAw
|
298
|
+
DgYDVQQDDAdmb28uY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCnsPyr
|
299
|
+
SVzHkRkRXn7M+dCzGtt+3nHKEf8wJK1sfjoNEBMY81I5otEIbmvyBMpDOkPizAat
|
300
|
+
AVh7aJ6dD7ujhoQcSuxeng78SYMp6JdZasyo+vbevcc4EN5u7KcL+wLDLs16mdf2
|
301
|
+
7VUsHTd6j6y6Ph3zo6g4X6jXW31k/4YDb1Gx/wIDAQABo1AwTjAdBgNVHQ4EFgQU
|
302
|
+
jGwm49W93kooVwC8cZJ4pKxaJQkwHwYDVR0jBBgwFoAUjGwm49W93kooVwC8cZJ4
|
303
|
+
pKxaJQkwDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOBgQCVKTvfxx+yezuR
|
304
|
+
5WpVKw1E9qabKOYFB5TqdHMHreRubMJTaoZC+YzhcCwtyLlAA9+axKINAiMM8T+z
|
305
|
+
jjfOHQSa2GS2TaaVDJWmXIgsAlEbjd2BEiQF0LZYGJRG9pyq0WbTV+CyFdrghjcO
|
306
|
+
xX/t7OG7NfOG9dhv3J+5SX10S5V5Dg==
|
307
|
+
-----END CERTIFICATE-----
|
308
|
+
EOS
|
309
|
+
|
310
|
+
VERIFY_TEST_CERT_ALT_NAME = OpenSSL::X509::Certificate.new(<<-EOS)
|
311
|
+
-----BEGIN CERTIFICATE-----
|
312
|
+
MIICDDCCAXWgAwIBAgIJAOxXY4nOwxhGMA0GCSqGSIb3DQEBCwUAMBQxEjAQBgNV
|
313
|
+
BAMMCWxvY2FsaG9zdDAeFw0xNjA4MTgxMDM0NTJaFw00NDAxMDMxMDM0NTJaMBQx
|
314
|
+
EjAQBgNVBAMMCWxvY2FsaG9zdDCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA
|
315
|
+
p7D8q0lcx5EZEV5+zPnQsxrbft5xyhH/MCStbH46DRATGPNSOaLRCG5r8gTKQzpD
|
316
|
+
4swGrQFYe2ienQ+7o4aEHErsXp4O/EmDKeiXWWrMqPr23r3HOBDebuynC/sCwy7N
|
317
|
+
epnX9u1VLB03eo+suj4d86OoOF+o11t9ZP+GA29Rsf8CAwEAAaNmMGQwFAYDVR0R
|
318
|
+
BA0wC4IJKi5mb28uY29tMB0GA1UdDgQWBBSMbCbj1b3eSihXALxxknikrFolCTAf
|
319
|
+
BgNVHSMEGDAWgBSMbCbj1b3eSihXALxxknikrFolCTAMBgNVHRMEBTADAQH/MA0G
|
320
|
+
CSqGSIb3DQEBCwUAA4GBADJlKNFuOnsDIhHGW72HuQw4naN6lM3eZE9JJ+UF/XIF
|
321
|
+
ghGtgqw+00Yy5wMFc1K2Wm4p5NymmDfC/P1FOe34bpxt9/IWm6mEoIWoodC3N4Cm
|
322
|
+
PtnSS1/CRWzVIPGMglTGGDcUc70tfeAWgyTxgcNQd4vTFtnN0f0RDdaXa8kfKMTw
|
323
|
+
-----END CERTIFICATE-----
|
324
|
+
EOS
|
325
|
+
|
326
|
+
VERIFY_TEST_PKEY = OpenSSL::PKey::RSA.new(<<-EOS)
|
327
|
+
-----BEGIN RSA PRIVATE KEY-----
|
328
|
+
MIICXQIBAAKBgQCnsPyrSVzHkRkRXn7M+dCzGtt+3nHKEf8wJK1sfjoNEBMY81I5
|
329
|
+
otEIbmvyBMpDOkPizAatAVh7aJ6dD7ujhoQcSuxeng78SYMp6JdZasyo+vbevcc4
|
330
|
+
EN5u7KcL+wLDLs16mdf27VUsHTd6j6y6Ph3zo6g4X6jXW31k/4YDb1Gx/wIDAQAB
|
331
|
+
AoGAe0RHx+WKtQx8/96VmTl951qzxMPho2etTYd4kAsNwzJwx2N9qu57eBYrdWF+
|
332
|
+
CQMYievucFhP4Y+bINtC1Eb6btz9TCUwjCfeIxfGRoFf3cxVmxlsRJJmN1kSZlu1
|
333
|
+
yYlcMVuP4noeFIMQBRrt5pyLCx2Z9A01NCQT4Y6VoREBIeECQQDWeNhsL6xkrmdB
|
334
|
+
M9+zl+SqHdNKhgKwMdp74+UNnAV9I8GB7bGlOWhc83aqMLgS+JBDFXcmNF/KawTR
|
335
|
+
zcnkod5xAkEAyClFgr3lZQSnwUwoA/AOcyW0+H63taaaXS/g8n3H8ENK6kL4ldUx
|
336
|
+
IgCk2ekbQ5Y3S2WScIGXNxMOza9MlsOvbwJAPUtoPvMZB+U4KVBT/JXKijvf6QqH
|
337
|
+
tidpU8L78XnHr84KPcHa5WeUxgvmvBkUYoebYzC9TrPlNIqFZBi2PJtuYQJBAMda
|
338
|
+
E5j7eJT75fhm2RPS6xFT5MH5sw6AOA3HucrJ63AoFVzsBpl0E9NBwO4ndLgDzF6T
|
339
|
+
cx4Kc4iuunewuB8QFpECQQCfvsHCjIJ/X4kiqeBzxDq2GR/oDgQkOzY+4H9U7Lwl
|
340
|
+
e61RBaxk5OHOA0bLtvJblV6NL72ZEZhX60wAWbrOPhpT
|
341
|
+
-----END RSA PRIVATE KEY-----
|
342
|
+
EOS
|
343
|
+
|
344
|
+
def test_post_connection_check
|
345
|
+
teardown_server
|
346
|
+
setup_server_with_server_cert(nil, VERIFY_TEST_CERT_LOCALHOST, VERIFY_TEST_PKEY)
|
347
|
+
file = Tempfile.new('cert')
|
348
|
+
File.write(file.path, VERIFY_TEST_CERT_LOCALHOST.to_pem)
|
349
|
+
@client.ssl_config.add_trust_ca(file.path)
|
350
|
+
assert_nothing_raised do
|
351
|
+
@client.get("https://localhost:#{serverport}/hello")
|
352
|
+
end
|
353
|
+
@client.ssl_config.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
354
|
+
assert_nothing_raised do
|
355
|
+
@client.get("https://localhost:#{serverport}/hello")
|
356
|
+
end
|
357
|
+
@client.ssl_config.verify_mode = OpenSSL::SSL::VERIFY_PEER
|
358
|
+
|
359
|
+
teardown_server
|
360
|
+
setup_server_with_server_cert(nil, VERIFY_TEST_CERT_FOO_DOMAIN, VERIFY_TEST_PKEY)
|
361
|
+
File.write(file.path, VERIFY_TEST_CERT_FOO_DOMAIN.to_pem)
|
362
|
+
@client.ssl_config.add_trust_ca(file.path)
|
363
|
+
assert_raises(OpenSSL::SSL::SSLError) do
|
364
|
+
@client.get("https://localhost:#{serverport}/hello")
|
365
|
+
end
|
366
|
+
@client.ssl_config.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
367
|
+
assert_nothing_raised do
|
368
|
+
@client.get("https://localhost:#{serverport}/hello")
|
369
|
+
end
|
370
|
+
@client.ssl_config.verify_mode = OpenSSL::SSL::VERIFY_PEER
|
371
|
+
|
372
|
+
teardown_server
|
373
|
+
setup_server_with_server_cert(nil, VERIFY_TEST_CERT_ALT_NAME, VERIFY_TEST_PKEY)
|
374
|
+
File.write(file.path, VERIFY_TEST_CERT_ALT_NAME.to_pem)
|
375
|
+
@client.ssl_config.add_trust_ca(file.path)
|
376
|
+
assert_raises(OpenSSL::SSL::SSLError) do
|
377
|
+
@client.get("https://localhost:#{serverport}/hello")
|
378
|
+
end
|
379
|
+
@client.ssl_config.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
380
|
+
assert_nothing_raised do
|
381
|
+
@client.get("https://localhost:#{serverport}/hello")
|
382
|
+
end
|
383
|
+
@client.ssl_config.verify_mode = OpenSSL::SSL::VERIFY_PEER
|
384
|
+
end
|
385
|
+
|
386
|
+
def test_x509_store_add_cert_prepend
|
387
|
+
store = OpenSSL::X509::Store.new
|
388
|
+
assert_equal(store, store.add_cert(OpenSSL::X509::Certificate.new(VERIFY_TEST_CERT_LOCALHOST)))
|
389
|
+
end
|
390
|
+
|
391
|
+
def test_tcp_keepalive
|
392
|
+
@client.tcp_keepalive = true
|
393
|
+
@client.ssl_config.add_trust_ca(path('ca-chain.pem'))
|
394
|
+
@client.get_content(@url)
|
395
|
+
|
396
|
+
# expecting HTTP keepalive caches the socket
|
397
|
+
session = @client.instance_variable_get(:@session_manager).send(:get_cached_session, HTTPClient::Site.new(URI.parse(@url)))
|
398
|
+
socket = session.instance_variable_get(:@socket).instance_variable_get(:@socket)
|
399
|
+
|
400
|
+
assert_true(session.tcp_keepalive)
|
401
|
+
if RUBY_ENGINE == 'jruby'
|
402
|
+
assert_true(socket.getKeepAlive())
|
403
|
+
else
|
404
|
+
assert_equal(Socket::SO_KEEPALIVE, socket.getsockopt(Socket::SOL_SOCKET, Socket::SO_KEEPALIVE).optname)
|
405
|
+
end
|
406
|
+
end
|
407
|
+
|
408
|
+
def test_timeout
|
409
|
+
url = "https://localhost:#{serverport}/"
|
410
|
+
@client.ssl_config.add_trust_ca(path('ca-chain.pem'))
|
411
|
+
assert_equal('sleep', @client.get_content(url + 'sleep?sec=2'))
|
412
|
+
@client.receive_timeout = 1
|
413
|
+
@client.reset_all
|
414
|
+
assert_equal('sleep', @client.get_content(url + 'sleep?sec=0'))
|
415
|
+
|
416
|
+
start = Time.now
|
417
|
+
assert_raise(HTTPClient::ReceiveTimeoutError) do
|
418
|
+
@client.get_content(url + 'sleep?sec=5')
|
419
|
+
end
|
420
|
+
if Time.now - start > 3
|
421
|
+
# before #342 it detected timeout when IO was freed
|
422
|
+
fail 'timeout does not work'
|
423
|
+
end
|
424
|
+
|
425
|
+
@client.receive_timeout = 3
|
426
|
+
@client.reset_all
|
427
|
+
assert_equal('sleep', @client.get_content(url + 'sleep?sec=2'))
|
428
|
+
end
|
429
|
+
|
265
430
|
private
|
266
431
|
|
267
432
|
def cert(filename)
|
@@ -294,7 +459,7 @@ private
|
|
294
459
|
:SSLCertName => nil
|
295
460
|
)
|
296
461
|
@serverport = @server.config[:Port]
|
297
|
-
[:hello].each do |sym|
|
462
|
+
[:hello, :sleep].each do |sym|
|
298
463
|
@server.mount(
|
299
464
|
"/#{sym}",
|
300
465
|
WEBrick::HTTPServlet::ProcHandler.new(method("do_#{sym}").to_proc)
|
@@ -332,11 +497,45 @@ private
|
|
332
497
|
@server_thread = start_server_thread(@server)
|
333
498
|
end
|
334
499
|
|
500
|
+
def setup_server_with_server_cert(ca_cert, server_cert, server_key)
|
501
|
+
logger = Logger.new(STDERR)
|
502
|
+
logger.level = Logger::Severity::FATAL # avoid logging SSLError (ERROR level)
|
503
|
+
@server = WEBrick::HTTPServer.new(
|
504
|
+
:BindAddress => "localhost",
|
505
|
+
:Logger => logger,
|
506
|
+
:Port => 0,
|
507
|
+
:AccessLog => [],
|
508
|
+
:DocumentRoot => DIR,
|
509
|
+
:SSLEnable => true,
|
510
|
+
:SSLCACertificateFile => ca_cert,
|
511
|
+
:SSLCertificate => server_cert,
|
512
|
+
:SSLPrivateKey => server_key,
|
513
|
+
:SSLVerifyClient => nil,
|
514
|
+
:SSLClientCA => nil,
|
515
|
+
:SSLCertName => nil
|
516
|
+
)
|
517
|
+
@serverport = @server.config[:Port]
|
518
|
+
[:hello].each do |sym|
|
519
|
+
@server.mount(
|
520
|
+
"/#{sym}",
|
521
|
+
WEBrick::HTTPServlet::ProcHandler.new(method("do_#{sym}").to_proc)
|
522
|
+
)
|
523
|
+
end
|
524
|
+
@server_thread = start_server_thread(@server)
|
525
|
+
end
|
526
|
+
|
335
527
|
def do_hello(req, res)
|
336
528
|
res['content-type'] = 'text/html'
|
337
529
|
res.body = "hello"
|
338
530
|
end
|
339
531
|
|
532
|
+
def do_sleep(req, res)
|
533
|
+
sec = req.query['sec'].to_i
|
534
|
+
sleep sec
|
535
|
+
res['content-type'] = 'text/html'
|
536
|
+
res.body = "sleep"
|
537
|
+
end
|
538
|
+
|
340
539
|
def start_server_thread(server)
|
341
540
|
t = Thread.new {
|
342
541
|
Thread.current.abort_on_exception = true
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: httpclient
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.8.
|
4
|
+
version: 2.8.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Hiroshi Nakamura
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-
|
11
|
+
date: 2016-12-09 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description:
|
14
14
|
email: nahi@ruby-lang.org
|
@@ -69,6 +69,7 @@ files:
|
|
69
69
|
- test/helper.rb
|
70
70
|
- test/htdigest
|
71
71
|
- test/htpasswd
|
72
|
+
- test/jruby_ssl_socket/test_pemutils.rb
|
72
73
|
- test/runner.rb
|
73
74
|
- test/server.cert
|
74
75
|
- test/server.key
|
@@ -103,7 +104,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
103
104
|
version: '0'
|
104
105
|
requirements: []
|
105
106
|
rubyforge_project:
|
106
|
-
rubygems_version: 2.
|
107
|
+
rubygems_version: 2.6.8
|
107
108
|
signing_key:
|
108
109
|
specification_version: 4
|
109
110
|
summary: gives something like the functionality of libwww-perl (LWP) in Ruby
|