httpclient 2.8.2.4 → 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.rb +2 -0
- data/lib/httpclient/jruby_ssl_socket.rb +83 -38
- data/lib/httpclient/session.rb +11 -5
- data/lib/httpclient/ssl_config.rb +45 -102
- data/lib/httpclient/ssl_socket.rb +26 -24
- data/lib/httpclient/version.rb +1 -1
- data/test/jruby_ssl_socket/test_pemutils.rb +32 -0
- data/test/test_httpclient.rb +12 -0
- data/test/test_ssl.rb +47 -1
- 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
|
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.
|
@@ -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
|
@@ -440,27 +457,56 @@ unless defined?(SSLSocket)
|
|
440
457
|
end
|
441
458
|
|
442
459
|
def self.create_socket(session)
|
443
|
-
|
444
|
-
|
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
|
445
468
|
begin
|
446
469
|
if session.proxy
|
470
|
+
site = session.proxy || session.dest
|
471
|
+
socket = JavaSocketWrap.connect(Socket.new, site, opts)
|
447
472
|
session.connect_ssl_proxy(JavaSocketWrap.new(socket), Util.urify(session.dest.to_s))
|
448
473
|
end
|
474
|
+
new(socket, session.dest, session.ssl_config, opts)
|
449
475
|
rescue
|
450
|
-
socket.close
|
476
|
+
socket.close if socket
|
451
477
|
raise
|
452
478
|
end
|
453
|
-
new(socket, session.dest, session.ssl_config, session.debug_dev)
|
454
479
|
end
|
455
480
|
|
456
481
|
DEFAULT_SSL_PROTOCOL = (java.lang.System.getProperty('java.specification.version') == '1.7') ? 'TLSv1.2' : 'TLS'
|
457
|
-
def initialize(socket, dest, config,
|
482
|
+
def initialize(socket, dest, config, opts = {})
|
458
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)
|
459
502
|
if config.ssl_version == :auto
|
460
|
-
|
503
|
+
DEFAULT_SSL_PROTOCOL
|
461
504
|
else
|
462
|
-
|
505
|
+
config.ssl_version.to_s.tr('_', '.')
|
463
506
|
end
|
507
|
+
end
|
508
|
+
|
509
|
+
def create_ssl_context(config)
|
464
510
|
unless config.cert_store_crl_items.empty?
|
465
511
|
raise NotImplementedError.new('Manual CRL configuration is not yet supported')
|
466
512
|
end
|
@@ -489,36 +535,24 @@ unless defined?(SSLSocket)
|
|
489
535
|
tmf.init(trust_store)
|
490
536
|
tm = tmf.getTrustManagers
|
491
537
|
|
492
|
-
ctx = SSLContext.getInstance(
|
538
|
+
ctx = SSLContext.getInstance(java_ssl_version(config))
|
493
539
|
ctx.init(km, tm, nil)
|
494
540
|
if config.timeout
|
495
541
|
ctx.getClientSessionContext.setSessionTimeout(config.timeout)
|
496
542
|
end
|
543
|
+
ctx
|
544
|
+
end
|
497
545
|
|
546
|
+
def create_ssl_socket(socket, dest, config, opts)
|
547
|
+
ctx = create_ssl_context(config)
|
498
548
|
factory = ctx.getSocketFactory
|
499
|
-
|
549
|
+
if socket
|
500
550
|
ssl_socket = factory.createSocket(socket, dest.host, dest.port, true)
|
501
|
-
|
502
|
-
|
503
|
-
|
504
|
-
end
|
505
|
-
ssl_socket.startHandshake
|
506
|
-
ssl_session = ssl_socket.getSession
|
507
|
-
@peer_cert = JavaCertificate.new(ssl_session.getPeerCertificates.first)
|
508
|
-
if $DEBUG
|
509
|
-
warn("Protocol version: #{ssl_session.getProtocol}")
|
510
|
-
warn("Cipher: #{ssl_socket.getSession.getCipherSuite}")
|
511
|
-
end
|
512
|
-
post_connection_check(dest.host, @peer_cert)
|
513
|
-
rescue java.security.GeneralSecurityException => e
|
514
|
-
raise OpenSSL::SSL::SSLError.new(e.getMessage)
|
515
|
-
rescue javax.net.ssl.SSLException => e
|
516
|
-
raise OpenSSL::SSL::SSLError.new(e.getMessage)
|
517
|
-
rescue java.net.SocketException => e
|
518
|
-
raise OpenSSL::SSL::SSLError.new(e.getMessage)
|
551
|
+
else
|
552
|
+
ssl_socket = factory.createSocket
|
553
|
+
JavaSocketWrap.connect(ssl_socket, dest, opts)
|
519
554
|
end
|
520
|
-
|
521
|
-
super(ssl_socket, debug_dev)
|
555
|
+
ssl_socket
|
522
556
|
end
|
523
557
|
|
524
558
|
def peer_cert
|
@@ -527,11 +561,22 @@ unless defined?(SSLSocket)
|
|
527
561
|
|
528
562
|
private
|
529
563
|
|
530
|
-
def
|
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)
|
531
576
|
if !@config.verify?
|
532
577
|
return
|
533
578
|
else
|
534
|
-
BrowserCompatHostnameVerifier.new.verify(hostname,
|
579
|
+
BrowserCompatHostnameVerifier.new.verify(hostname, @peer_cert.cert)
|
535
580
|
end
|
536
581
|
end
|
537
582
|
end
|
data/lib/httpclient/session.rb
CHANGED
@@ -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
|
@@ -66,55 +66,76 @@ class HTTPClient
|
|
66
66
|
end
|
67
67
|
end
|
68
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
|
+
|
69
88
|
CIPHERS_DEFAULT = "ALL:!aNULL:!eNULL:!SSLv2" # OpenSSL >1.0.0 default
|
70
89
|
|
71
90
|
# Which TLS protocol version (also called method) will be used. Defaults
|
72
|
-
# 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
|
73
92
|
# with always the highest available protocol being used).
|
74
93
|
# String name of OpenSSL's SSL version method name: TLSv1_2, TLSv1_1, TLSv1,
|
75
94
|
# SSLv2, SSLv23, SSLv3 or :auto (and nil) to allow version negotiation (default).
|
76
95
|
# See {OpenSSL::SSL::SSLContext::METHODS} for a list of available versions
|
77
96
|
# in your specific Ruby environment.
|
78
|
-
|
97
|
+
attr_config :ssl_version
|
79
98
|
# OpenSSL::X509::Certificate:: certificate for SSL client authentication.
|
80
99
|
# nil by default. (no client authentication)
|
81
|
-
|
100
|
+
attr_config :client_cert
|
82
101
|
# OpenSSL::PKey::PKey:: private key for SSL client authentication.
|
83
102
|
# nil by default. (no client authentication)
|
84
|
-
|
85
|
-
|
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
|
86
107
|
|
87
108
|
# A number which represents OpenSSL's verify mode. Default value is
|
88
109
|
# OpenSSL::SSL::VERIFY_PEER | OpenSSL::SSL::VERIFY_FAIL_IF_NO_PEER_CERT.
|
89
|
-
|
110
|
+
attr_config :verify_mode
|
90
111
|
# A number of verify depth. Certification path which length is longer than
|
91
112
|
# this depth is not allowed.
|
92
113
|
# CAUTION: this is OpenSSL specific option and ignored on JRuby.
|
93
|
-
|
114
|
+
attr_config :verify_depth
|
94
115
|
# A callback handler for custom certificate verification. nil by default.
|
95
116
|
# If the handler is set, handler.call is invoked just after general
|
96
117
|
# OpenSSL's verification. handler.call is invoked with 2 arguments,
|
97
118
|
# ok and ctx; ok is a result of general OpenSSL's verification. ctx is a
|
98
119
|
# OpenSSL::X509::StoreContext.
|
99
|
-
|
120
|
+
attr_config :verify_callback
|
100
121
|
# SSL timeout in sec. nil by default.
|
101
|
-
|
122
|
+
attr_config :timeout
|
102
123
|
# A number of OpenSSL's SSL options. Default value is
|
103
124
|
# OpenSSL::SSL::OP_ALL | OpenSSL::SSL::OP_NO_SSLv2
|
104
125
|
# CAUTION: this is OpenSSL specific option and ignored on JRuby.
|
105
126
|
# Use ssl_version to specify the TLS version you want to use.
|
106
|
-
|
127
|
+
attr_config :options
|
107
128
|
# A String of OpenSSL's cipher configuration. Default value is
|
108
129
|
# ALL:!ADH:!LOW:!EXP:!MD5:+SSLv2:@STRENGTH
|
109
130
|
# See ciphers(1) man in OpenSSL for more detail.
|
110
|
-
|
131
|
+
attr_config :ciphers
|
111
132
|
|
112
133
|
# OpenSSL::X509::X509::Store used for verification. You can reset the
|
113
134
|
# store with clear_cert_store and set the new store with cert_store=.
|
114
135
|
attr_reader :cert_store # don't use if you don't know what it is.
|
115
136
|
|
116
137
|
# For server side configuration. Ignore this.
|
117
|
-
|
138
|
+
attr_config :client_ca # :nodoc:
|
118
139
|
|
119
140
|
# These array keeps original files/dirs that was added to @cert_store
|
120
141
|
def cert_store_items; @cert_store._httpclient_cert_store_items; end
|
@@ -126,7 +147,7 @@ class HTTPClient
|
|
126
147
|
@client = client
|
127
148
|
@cert_store = X509::Store.new
|
128
149
|
@cert_store_crl_items = []
|
129
|
-
@client_cert = @client_key = @client_ca = nil
|
150
|
+
@client_cert = @client_key = @client_key_pass = @client_ca = nil
|
130
151
|
@verify_mode = SSL::VERIFY_PEER | SSL::VERIFY_FAIL_IF_NO_PEER_CERT
|
131
152
|
@verify_depth = nil
|
132
153
|
@verify_callback = nil
|
@@ -144,42 +165,18 @@ class HTTPClient
|
|
144
165
|
@cacerts_loaded = false
|
145
166
|
end
|
146
167
|
|
147
|
-
# Sets SSL version method String. Possible values: "SSLv2" for SSL2,
|
148
|
-
# "SSLv3" for SSL3 and TLS1.x, "SSLv23" for SSL3 with fallback to SSL2.
|
149
|
-
def ssl_version=(ssl_version)
|
150
|
-
@ssl_version = ssl_version
|
151
|
-
change_notify
|
152
|
-
end
|
153
|
-
|
154
|
-
# Sets certificate (OpenSSL::X509::Certificate) for SSL client
|
155
|
-
# authentication.
|
156
|
-
# client_key and client_cert must be a pair.
|
157
|
-
#
|
158
|
-
# Calling this method resets all existing sessions.
|
159
|
-
def client_cert=(client_cert)
|
160
|
-
@client_cert = client_cert
|
161
|
-
change_notify
|
162
|
-
end
|
163
|
-
|
164
|
-
# Sets private key (OpenSSL::PKey::PKey) for SSL client authentication.
|
165
|
-
# client_key and client_cert must be a pair.
|
166
|
-
#
|
167
|
-
# Calling this method resets all existing sessions.
|
168
|
-
def client_key=(client_key)
|
169
|
-
@client_key = client_key
|
170
|
-
change_notify
|
171
|
-
end
|
172
|
-
|
173
168
|
# Sets certificate and private key for SSL client authentication.
|
174
169
|
# cert_file:: must be a filename of PEM/DER formatted file.
|
175
170
|
# key_file:: must be a filename of PEM/DER formatted file. Key must be an
|
176
171
|
# RSA key. If you want to use other PKey algorithm,
|
177
172
|
# use client_key=.
|
178
173
|
#
|
179
|
-
# Calling this method resets all existing sessions.
|
174
|
+
# Calling this method resets all existing sessions if value is changed.
|
180
175
|
def set_client_cert_file(cert_file, key_file, pass = nil)
|
181
|
-
@client_cert
|
182
|
-
|
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
|
183
180
|
end
|
184
181
|
|
185
182
|
# Sets OpenSSL's default trusted CA certificates. Generally, OpenSSL is
|
@@ -216,9 +213,12 @@ class HTTPClient
|
|
216
213
|
#
|
217
214
|
# Calling this method resets all existing sessions.
|
218
215
|
def cert_store=(cert_store)
|
219
|
-
|
220
|
-
@cert_store
|
221
|
-
|
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
|
222
222
|
end
|
223
223
|
|
224
224
|
# Sets trust anchor certificate(s) for verification.
|
@@ -276,62 +276,6 @@ class HTTPClient
|
|
276
276
|
end
|
277
277
|
alias set_crl add_crl
|
278
278
|
|
279
|
-
# Sets verify mode of OpenSSL. New value must be a combination of
|
280
|
-
# constants OpenSSL::SSL::VERIFY_*
|
281
|
-
#
|
282
|
-
# Calling this method resets all existing sessions.
|
283
|
-
def verify_mode=(verify_mode)
|
284
|
-
@verify_mode = verify_mode
|
285
|
-
change_notify
|
286
|
-
end
|
287
|
-
|
288
|
-
# Sets verify depth. New value must be a number.
|
289
|
-
#
|
290
|
-
# Calling this method resets all existing sessions.
|
291
|
-
def verify_depth=(verify_depth)
|
292
|
-
@verify_depth = verify_depth
|
293
|
-
change_notify
|
294
|
-
end
|
295
|
-
|
296
|
-
# Sets callback handler for custom certificate verification.
|
297
|
-
# See verify_callback.
|
298
|
-
#
|
299
|
-
# Calling this method resets all existing sessions.
|
300
|
-
def verify_callback=(verify_callback)
|
301
|
-
@verify_callback = verify_callback
|
302
|
-
change_notify
|
303
|
-
end
|
304
|
-
|
305
|
-
# Sets SSL timeout in sec.
|
306
|
-
#
|
307
|
-
# Calling this method resets all existing sessions.
|
308
|
-
def timeout=(timeout)
|
309
|
-
@timeout = timeout
|
310
|
-
change_notify
|
311
|
-
end
|
312
|
-
|
313
|
-
# Sets SSL options. New value must be a combination of # constants
|
314
|
-
# OpenSSL::SSL::OP_*
|
315
|
-
#
|
316
|
-
# Calling this method resets all existing sessions.
|
317
|
-
def options=(options)
|
318
|
-
@options = options
|
319
|
-
change_notify
|
320
|
-
end
|
321
|
-
|
322
|
-
# Sets cipher configuration. New value must be a String.
|
323
|
-
#
|
324
|
-
# Calling this method resets all existing sessions.
|
325
|
-
def ciphers=(ciphers)
|
326
|
-
@ciphers = ciphers
|
327
|
-
change_notify
|
328
|
-
end
|
329
|
-
|
330
|
-
def client_ca=(client_ca) # :nodoc:
|
331
|
-
@client_ca = client_ca
|
332
|
-
change_notify
|
333
|
-
end
|
334
|
-
|
335
279
|
def verify?
|
336
280
|
@verify_mode && (@verify_mode & OpenSSL::SSL::VERIFY_PEER != 0)
|
337
281
|
end
|
@@ -471,7 +415,6 @@ class HTTPClient
|
|
471
415
|
|
472
416
|
# Use 2048 bit certs trust anchor
|
473
417
|
def load_cacerts(cert_store)
|
474
|
-
ver = OpenSSL::OPENSSL_VERSION
|
475
418
|
file = File.join(File.dirname(__FILE__), 'cacert.pem')
|
476
419
|
add_trust_ca_to_store(cert_store, file)
|
477
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
@@ -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
@@ -388,6 +388,45 @@ e61RBaxk5OHOA0bLtvJblV6NL72ZEZhX60wAWbrOPhpT
|
|
388
388
|
assert_equal(store, store.add_cert(OpenSSL::X509::Certificate.new(VERIFY_TEST_CERT_LOCALHOST)))
|
389
389
|
end
|
390
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
|
+
|
391
430
|
private
|
392
431
|
|
393
432
|
def cert(filename)
|
@@ -420,7 +459,7 @@ private
|
|
420
459
|
:SSLCertName => nil
|
421
460
|
)
|
422
461
|
@serverport = @server.config[:Port]
|
423
|
-
[:hello].each do |sym|
|
462
|
+
[:hello, :sleep].each do |sym|
|
424
463
|
@server.mount(
|
425
464
|
"/#{sym}",
|
426
465
|
WEBrick::HTTPServlet::ProcHandler.new(method("do_#{sym}").to_proc)
|
@@ -490,6 +529,13 @@ private
|
|
490
529
|
res.body = "hello"
|
491
530
|
end
|
492
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
|
+
|
493
539
|
def start_server_thread(server)
|
494
540
|
t = Thread.new {
|
495
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-09
|
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
|