httpclient 2.1.5 → 2.8.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/README.md +85 -0
- data/bin/httpclient +77 -0
- data/bin/jsonclient +85 -0
- data/lib/hexdump.rb +50 -0
- data/lib/http-access2.rb +6 -4
- data/lib/httpclient/auth.rb +575 -173
- data/lib/httpclient/cacert.pem +3952 -0
- data/lib/httpclient/cacert1024.pem +3866 -0
- data/lib/httpclient/connection.rb +6 -2
- data/lib/httpclient/cookie.rb +162 -504
- data/lib/httpclient/http.rb +334 -119
- data/lib/httpclient/include_client.rb +85 -0
- data/lib/httpclient/jruby_ssl_socket.rb +588 -0
- data/lib/httpclient/session.rb +385 -288
- data/lib/httpclient/ssl_config.rb +195 -155
- data/lib/httpclient/ssl_socket.rb +150 -0
- data/lib/httpclient/timeout.rb +14 -10
- data/lib/httpclient/util.rb +142 -6
- data/lib/httpclient/version.rb +3 -0
- data/lib/httpclient/webagent-cookie.rb +459 -0
- data/lib/httpclient.rb +509 -202
- data/lib/jsonclient.rb +63 -0
- data/lib/oauthclient.rb +111 -0
- data/sample/async.rb +8 -0
- data/sample/auth.rb +11 -0
- data/sample/cookie.rb +18 -0
- data/sample/dav.rb +103 -0
- data/sample/howto.rb +49 -0
- data/sample/jsonclient.rb +67 -0
- data/sample/oauth_buzz.rb +57 -0
- data/sample/oauth_friendfeed.rb +59 -0
- data/sample/oauth_twitter.rb +61 -0
- data/sample/ssl/0cert.pem +22 -0
- data/sample/ssl/0key.pem +30 -0
- data/sample/ssl/1000cert.pem +19 -0
- data/sample/ssl/1000key.pem +18 -0
- data/sample/ssl/htdocs/index.html +10 -0
- data/sample/ssl/ssl_client.rb +22 -0
- data/sample/ssl/webrick_httpsd.rb +29 -0
- data/sample/stream.rb +21 -0
- data/sample/thread.rb +27 -0
- data/sample/wcat.rb +21 -0
- data/test/ca-chain.pem +44 -0
- data/test/ca.cert +23 -0
- data/test/client-pass.key +18 -0
- data/test/client.cert +19 -0
- data/test/client.key +15 -0
- data/test/helper.rb +131 -0
- data/test/htdigest +1 -0
- data/test/htpasswd +2 -0
- data/test/jruby_ssl_socket/test_pemutils.rb +32 -0
- data/test/runner.rb +2 -0
- data/test/server.cert +19 -0
- data/test/server.key +15 -0
- data/test/sslsvr.rb +65 -0
- data/test/subca.cert +21 -0
- data/test/test_auth.rb +492 -0
- data/test/test_cookie.rb +309 -0
- data/test/test_hexdump.rb +14 -0
- data/test/test_http-access2.rb +508 -0
- data/test/test_httpclient.rb +2145 -0
- data/test/test_include_client.rb +52 -0
- data/test/test_jsonclient.rb +80 -0
- data/test/test_ssl.rb +559 -0
- data/test/test_webagent-cookie.rb +465 -0
- metadata +85 -44
- data/lib/httpclient/auth.rb.orig +0 -513
- data/lib/httpclient/cacert.p7s +0 -1579
- data/lib/httpclient.rb.orig +0 -1020
- data/lib/tags +0 -908
@@ -0,0 +1,85 @@
|
|
1
|
+
# It is useful to re-use a HTTPClient instance for multiple requests, to
|
2
|
+
# re-use HTTP 1.1 persistent connections.
|
3
|
+
#
|
4
|
+
# To do that, you sometimes want to store an HTTPClient instance in a global/
|
5
|
+
# class variable location, so it can be accessed and re-used.
|
6
|
+
#
|
7
|
+
# This mix-in makes it easy to create class-level access to one or more
|
8
|
+
# HTTPClient instances. The HTTPClient instances are lazily initialized
|
9
|
+
# on first use (to, for instance, avoid interfering with WebMock/VCR),
|
10
|
+
# and are initialized in a thread-safe manner. Note that a
|
11
|
+
# HTTPClient, once initialized, is safe for use in multiple threads.
|
12
|
+
#
|
13
|
+
# Note that you `extend` HTTPClient::IncludeClient, not `include.
|
14
|
+
#
|
15
|
+
# require 'httpclient/include_client'
|
16
|
+
# class Widget
|
17
|
+
# extend HTTPClient::IncludeClient
|
18
|
+
#
|
19
|
+
# include_http_client
|
20
|
+
# # and/or, specify more stuff
|
21
|
+
# include_http_client('http://myproxy:8080', :method_name => :my_client) do |client|
|
22
|
+
# # any init you want
|
23
|
+
# client.set_cookie_store nil
|
24
|
+
# client.
|
25
|
+
# end
|
26
|
+
# end
|
27
|
+
#
|
28
|
+
# That creates two HTTPClient instances available at the class level.
|
29
|
+
# The first will be available from Widget.http_client (default method
|
30
|
+
# name for `include_http_client`), with default initialization.
|
31
|
+
#
|
32
|
+
# The second will be available at Widget.my_client, with the init arguments
|
33
|
+
# provided, further initialized by the block provided.
|
34
|
+
#
|
35
|
+
# In addition to a class-level method, for convenience instance-level methods
|
36
|
+
# are also provided. Widget.http_client is identical to Widget.new.http_client
|
37
|
+
#
|
38
|
+
#
|
39
|
+
require 'httpclient'
|
40
|
+
|
41
|
+
class HTTPClient
|
42
|
+
module IncludeClient
|
43
|
+
|
44
|
+
|
45
|
+
def include_http_client(*args, &block)
|
46
|
+
# We're going to dynamically define a class
|
47
|
+
# to hold our state, namespaced, as well as possibly dynamic
|
48
|
+
# name of cover method.
|
49
|
+
method_name = (args.last.delete(:method_name) if args.last.kind_of? Hash) || :http_client
|
50
|
+
args.pop if args.last == {} # if last arg was named methods now empty, remove it.
|
51
|
+
|
52
|
+
# By the amazingness of closures, we can create these things
|
53
|
+
# in local vars here and use em in our method, we don't even
|
54
|
+
# need iVars for state.
|
55
|
+
client_instance = nil
|
56
|
+
client_mutex = Mutex.new
|
57
|
+
client_args = args
|
58
|
+
client_block = block
|
59
|
+
|
60
|
+
# to define a _class method_ on the specific class that's currently
|
61
|
+
# `self`, we have to use this bit of metaprogramming, sorry.
|
62
|
+
(class << self; self ; end).instance_eval do
|
63
|
+
define_method(method_name) do
|
64
|
+
# implementation copied from ruby stdlib singleton
|
65
|
+
# to create this global obj thread-safely.
|
66
|
+
return client_instance if client_instance
|
67
|
+
client_mutex.synchronize do
|
68
|
+
return client_instance if client_instance
|
69
|
+
# init HTTPClient with specified args/block
|
70
|
+
client_instance = HTTPClient.new(*client_args)
|
71
|
+
client_block.call(client_instance) if client_block
|
72
|
+
end
|
73
|
+
return client_instance
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
# And for convenience, an _instance method_ on the class that just
|
78
|
+
# delegates to the class method.
|
79
|
+
define_method(method_name) do
|
80
|
+
self.class.send(method_name)
|
81
|
+
end
|
82
|
+
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
@@ -0,0 +1,588 @@
|
|
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 'java'
|
10
|
+
require 'httpclient/ssl_config'
|
11
|
+
|
12
|
+
|
13
|
+
class HTTPClient
|
14
|
+
|
15
|
+
unless defined?(SSLSocket)
|
16
|
+
|
17
|
+
class JavaSocketWrap
|
18
|
+
java_import 'java.net.InetSocketAddress'
|
19
|
+
java_import 'java.io.BufferedInputStream'
|
20
|
+
|
21
|
+
BUF_SIZE = 1024 * 16
|
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
|
+
|
35
|
+
def initialize(socket, debug_dev = nil)
|
36
|
+
@socket = socket
|
37
|
+
@debug_dev = debug_dev
|
38
|
+
@outstr = @socket.getOutputStream
|
39
|
+
@instr = BufferedInputStream.new(@socket.getInputStream)
|
40
|
+
@buf = (' ' * BUF_SIZE).to_java_bytes
|
41
|
+
@bufstr = ''
|
42
|
+
end
|
43
|
+
|
44
|
+
def close
|
45
|
+
@socket.close
|
46
|
+
end
|
47
|
+
|
48
|
+
def closed?
|
49
|
+
@socket.isClosed
|
50
|
+
end
|
51
|
+
|
52
|
+
def eof?
|
53
|
+
@socket.isClosed
|
54
|
+
end
|
55
|
+
|
56
|
+
def gets(rs)
|
57
|
+
while (size = @bufstr.index(rs)).nil?
|
58
|
+
if fill() == -1
|
59
|
+
size = @bufstr.size
|
60
|
+
break
|
61
|
+
end
|
62
|
+
end
|
63
|
+
str = @bufstr.slice!(0, size + rs.size)
|
64
|
+
debug(str)
|
65
|
+
str
|
66
|
+
end
|
67
|
+
|
68
|
+
def read(size, buf = nil)
|
69
|
+
while @bufstr.size < size
|
70
|
+
if fill() == -1
|
71
|
+
break
|
72
|
+
end
|
73
|
+
end
|
74
|
+
str = @bufstr.slice!(0, size)
|
75
|
+
debug(str)
|
76
|
+
if buf
|
77
|
+
buf.replace(str)
|
78
|
+
else
|
79
|
+
str
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def readpartial(size, buf = nil)
|
84
|
+
while @bufstr.size == 0
|
85
|
+
if fill() == -1
|
86
|
+
raise EOFError.new('end of file reached')
|
87
|
+
end
|
88
|
+
end
|
89
|
+
str = @bufstr.slice!(0, size)
|
90
|
+
debug(str)
|
91
|
+
if buf
|
92
|
+
buf.replace(str)
|
93
|
+
else
|
94
|
+
str
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def <<(str)
|
99
|
+
rv = @outstr.write(str.to_java_bytes)
|
100
|
+
debug(str)
|
101
|
+
rv
|
102
|
+
end
|
103
|
+
|
104
|
+
def flush
|
105
|
+
@socket.flush
|
106
|
+
end
|
107
|
+
|
108
|
+
def sync
|
109
|
+
true
|
110
|
+
end
|
111
|
+
|
112
|
+
def sync=(sync)
|
113
|
+
unless sync
|
114
|
+
raise "sync = false is not supported. This option was introduced for backward compatibility just in case."
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
private
|
119
|
+
|
120
|
+
def fill
|
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}")
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
def debug(str)
|
133
|
+
@debug_dev << str if @debug_dev && str
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
class JRubySSLSocket < JavaSocketWrap
|
138
|
+
java_import 'java.io.ByteArrayInputStream'
|
139
|
+
java_import 'java.io.InputStreamReader'
|
140
|
+
java_import 'java.net.Socket'
|
141
|
+
java_import 'java.security.KeyStore'
|
142
|
+
java_import 'java.security.cert.Certificate'
|
143
|
+
java_import 'java.security.cert.CertificateFactory'
|
144
|
+
java_import 'javax.net.ssl.KeyManagerFactory'
|
145
|
+
java_import 'javax.net.ssl.SSLContext'
|
146
|
+
java_import 'javax.net.ssl.SSLSocketFactory'
|
147
|
+
java_import 'javax.net.ssl.TrustManager'
|
148
|
+
java_import 'javax.net.ssl.TrustManagerFactory'
|
149
|
+
java_import 'javax.net.ssl.X509TrustManager'
|
150
|
+
java_import 'org.jruby.ext.openssl.x509store.PEMInputOutput'
|
151
|
+
|
152
|
+
class JavaCertificate
|
153
|
+
attr_reader :cert
|
154
|
+
|
155
|
+
def initialize(cert)
|
156
|
+
@cert = cert
|
157
|
+
end
|
158
|
+
|
159
|
+
def subject
|
160
|
+
@cert.getSubjectDN
|
161
|
+
end
|
162
|
+
|
163
|
+
def to_text
|
164
|
+
@cert.toString
|
165
|
+
end
|
166
|
+
|
167
|
+
def to_pem
|
168
|
+
'(not in PEM format)'
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
class SSLStoreContext
|
173
|
+
attr_reader :current_cert, :chain, :error_depth, :error, :error_string
|
174
|
+
|
175
|
+
def initialize(current_cert, chain, error_depth, error, error_string)
|
176
|
+
@current_cert, @chain, @error_depth, @error, @error_string =
|
177
|
+
current_cert, chain, error_depth, error, error_string
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
class JSSEVerifyCallback
|
182
|
+
def initialize(verify_callback)
|
183
|
+
@verify_callback = verify_callback
|
184
|
+
end
|
185
|
+
|
186
|
+
def call(is_ok, chain, error_depth = -1, error = -1, error_string = '(unknown)')
|
187
|
+
if @verify_callback
|
188
|
+
ruby_chain = chain.map { |cert|
|
189
|
+
JavaCertificate.new(cert)
|
190
|
+
}.reverse
|
191
|
+
# NOTE: The order depends on provider implementation
|
192
|
+
ruby_chain.each do |cert|
|
193
|
+
is_ok = @verify_callback.call(
|
194
|
+
is_ok,
|
195
|
+
SSLStoreContext.new(cert, ruby_chain, error_depth, error, error_string)
|
196
|
+
)
|
197
|
+
end
|
198
|
+
end
|
199
|
+
is_ok
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
class VerifyNoneTrustManagerFactory
|
204
|
+
class VerifyNoneTrustManager
|
205
|
+
include X509TrustManager
|
206
|
+
|
207
|
+
def initialize(verify_callback)
|
208
|
+
@verify_callback = JSSEVerifyCallback.new(verify_callback)
|
209
|
+
end
|
210
|
+
|
211
|
+
def checkServerTrusted(chain, authType)
|
212
|
+
@verify_callback.call(true, chain)
|
213
|
+
end
|
214
|
+
|
215
|
+
def checkClientTrusted(chain, authType); end
|
216
|
+
def getAcceptedIssuers; end
|
217
|
+
end
|
218
|
+
|
219
|
+
def initialize(verify_callback = nil)
|
220
|
+
@verify_callback = verify_callback
|
221
|
+
end
|
222
|
+
|
223
|
+
def init(trustStore)
|
224
|
+
@managers = [VerifyNoneTrustManager.new(@verify_callback)].to_java(X509TrustManager)
|
225
|
+
end
|
226
|
+
|
227
|
+
def getTrustManagers
|
228
|
+
@managers
|
229
|
+
end
|
230
|
+
end
|
231
|
+
|
232
|
+
class SystemTrustManagerFactory
|
233
|
+
class SystemTrustManager
|
234
|
+
include X509TrustManager
|
235
|
+
|
236
|
+
def initialize(original, verify_callback)
|
237
|
+
@original = original
|
238
|
+
@verify_callback = JSSEVerifyCallback.new(verify_callback)
|
239
|
+
end
|
240
|
+
|
241
|
+
def checkServerTrusted(chain, authType)
|
242
|
+
is_ok = false
|
243
|
+
excn = nil
|
244
|
+
# TODO can we detect the depth from excn?
|
245
|
+
error_depth = -1
|
246
|
+
error = nil
|
247
|
+
error_message = nil
|
248
|
+
begin
|
249
|
+
@original.checkServerTrusted(chain, authType)
|
250
|
+
is_ok = true
|
251
|
+
rescue java.security.cert.CertificateException => excn
|
252
|
+
is_ok = false
|
253
|
+
error = excn.class.name
|
254
|
+
error_message = excn.getMessage
|
255
|
+
end
|
256
|
+
is_ok = @verify_callback.call(is_ok, chain, error_depth, error, error_message)
|
257
|
+
unless is_ok
|
258
|
+
excn ||= OpenSSL::SSL::SSLError.new('verifycallback failed')
|
259
|
+
raise excn
|
260
|
+
end
|
261
|
+
end
|
262
|
+
|
263
|
+
def checkClientTrusted(chain, authType); end
|
264
|
+
def getAcceptedIssuers; end
|
265
|
+
end
|
266
|
+
|
267
|
+
def initialize(verify_callback = nil)
|
268
|
+
@verify_callback = verify_callback
|
269
|
+
end
|
270
|
+
|
271
|
+
def init(trust_store)
|
272
|
+
tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm)
|
273
|
+
tmf.java_method(:init, [KeyStore]).call(trust_store)
|
274
|
+
@original = tmf.getTrustManagers.find { |tm|
|
275
|
+
tm.is_a?(X509TrustManager)
|
276
|
+
}
|
277
|
+
@managers = [SystemTrustManager.new(@original, @verify_callback)].to_java(X509TrustManager)
|
278
|
+
end
|
279
|
+
|
280
|
+
def getTrustManagers
|
281
|
+
@managers
|
282
|
+
end
|
283
|
+
end
|
284
|
+
|
285
|
+
module PEMUtils
|
286
|
+
def self.read_certificate(pem)
|
287
|
+
cert = pem.sub(/.*?-----BEGIN CERTIFICATE-----/m, '').sub(/-----END CERTIFICATE-----.*?/m, '')
|
288
|
+
der = cert.unpack('m*').first
|
289
|
+
cf = CertificateFactory.getInstance('X.509')
|
290
|
+
cf.generateCertificate(ByteArrayInputStream.new(der.to_java_bytes))
|
291
|
+
end
|
292
|
+
|
293
|
+
def self.read_private_key(pem, password)
|
294
|
+
if password
|
295
|
+
password = password.unpack('C*').to_java(:char)
|
296
|
+
end
|
297
|
+
PEMInputOutput.read_private_key(InputStreamReader.new(ByteArrayInputStream.new(pem.to_java_bytes)), password)
|
298
|
+
end
|
299
|
+
end
|
300
|
+
|
301
|
+
class KeyStoreLoader
|
302
|
+
PASSWORD = 16.times.map { rand(256) }.to_java(:char)
|
303
|
+
|
304
|
+
def initialize
|
305
|
+
@keystore = KeyStore.getInstance('JKS')
|
306
|
+
@keystore.load(nil)
|
307
|
+
end
|
308
|
+
|
309
|
+
def add(cert_source, key_source, password)
|
310
|
+
cert_str = cert_source.respond_to?(:to_pem) ? cert_source.to_pem : File.read(cert_source.to_s)
|
311
|
+
cert = PEMUtils.read_certificate(cert_str)
|
312
|
+
@keystore.setCertificateEntry('client_cert', cert)
|
313
|
+
key_str = key_source.respond_to?(:to_pem) ? key_source.to_pem : File.read(key_source.to_s)
|
314
|
+
key_pair = PEMUtils.read_private_key(key_str, password)
|
315
|
+
@keystore.setKeyEntry('client_key', key_pair.getPrivate, PASSWORD, [cert].to_java(Certificate))
|
316
|
+
end
|
317
|
+
|
318
|
+
def keystore
|
319
|
+
@keystore
|
320
|
+
end
|
321
|
+
end
|
322
|
+
|
323
|
+
class TrustStoreLoader
|
324
|
+
attr_reader :size
|
325
|
+
|
326
|
+
def initialize
|
327
|
+
@trust_store = KeyStore.getInstance('JKS')
|
328
|
+
@trust_store.load(nil)
|
329
|
+
@size = 0
|
330
|
+
end
|
331
|
+
|
332
|
+
def add(cert_source)
|
333
|
+
return if cert_source == :default
|
334
|
+
if cert_source.respond_to?(:to_pem)
|
335
|
+
pem = cert_source.to_pem
|
336
|
+
load_pem(pem)
|
337
|
+
elsif File.directory?(cert_source)
|
338
|
+
warn("#{cert_source}: directory not yet supported")
|
339
|
+
return
|
340
|
+
else
|
341
|
+
pem = nil
|
342
|
+
File.read(cert_source).each_line do |line|
|
343
|
+
case line
|
344
|
+
when /-----BEGIN CERTIFICATE-----/
|
345
|
+
pem = ''
|
346
|
+
when /-----END CERTIFICATE-----/
|
347
|
+
load_pem(pem)
|
348
|
+
# keep parsing in case where multiple certificates in a file
|
349
|
+
else
|
350
|
+
if pem
|
351
|
+
pem << line
|
352
|
+
end
|
353
|
+
end
|
354
|
+
end
|
355
|
+
end
|
356
|
+
end
|
357
|
+
|
358
|
+
def trust_store
|
359
|
+
if @size == 0
|
360
|
+
nil
|
361
|
+
else
|
362
|
+
@trust_store
|
363
|
+
end
|
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
|
373
|
+
end
|
374
|
+
|
375
|
+
# Ported from commons-httpclient 'BrowserCompatHostnameVerifier'
|
376
|
+
class BrowserCompatHostnameVerifier
|
377
|
+
BAD_COUNTRY_2LDS = %w(ac co com ed edu go gouv gov info lg ne net or org).sort
|
378
|
+
require 'ipaddr'
|
379
|
+
|
380
|
+
def extract_sans(cert, subject_type)
|
381
|
+
sans = cert.getSubjectAlternativeNames rescue nil
|
382
|
+
if sans.nil?
|
383
|
+
return nil
|
384
|
+
end
|
385
|
+
sans.find_all { |san|
|
386
|
+
san.first.to_i == subject_type
|
387
|
+
}.map { |san|
|
388
|
+
san[1]
|
389
|
+
}
|
390
|
+
end
|
391
|
+
|
392
|
+
def extract_cn(cert)
|
393
|
+
subject = cert.getSubjectX500Principal()
|
394
|
+
if subject
|
395
|
+
subject_dn = javax.naming.ldap.LdapName.new(subject.toString)
|
396
|
+
subject_dn.getRdns.to_a.reverse.each do |rdn|
|
397
|
+
attributes = rdn.toAttributes
|
398
|
+
cn = attributes.get('cn')
|
399
|
+
if cn
|
400
|
+
if value = cn.get
|
401
|
+
return value.to_s
|
402
|
+
end
|
403
|
+
end
|
404
|
+
end
|
405
|
+
end
|
406
|
+
end
|
407
|
+
|
408
|
+
def ipaddr?(addr)
|
409
|
+
!(IPAddr.new(addr) rescue nil).nil?
|
410
|
+
end
|
411
|
+
|
412
|
+
def verify(hostname, cert)
|
413
|
+
is_ipaddr = ipaddr?(hostname)
|
414
|
+
sans = extract_sans(cert, is_ipaddr ? 7 : 2)
|
415
|
+
cn = extract_cn(cert)
|
416
|
+
if sans
|
417
|
+
sans.each do |san|
|
418
|
+
return true if match_identify(hostname, san)
|
419
|
+
end
|
420
|
+
raise OpenSSL::SSL::SSLError.new("Certificate for <#{hostname}> doesn't match any of the subject alternative names: #{sans}")
|
421
|
+
elsif cn
|
422
|
+
return true if match_identify(hostname, cn)
|
423
|
+
raise OpenSSL::SSL::SSLError.new("Certificate for <#{hostname}> doesn't match common name of the certificate subject: #{cn}")
|
424
|
+
end
|
425
|
+
raise OpenSSL::SSL::SSLError.new("Certificate subject for for <#{hostname}> doesn't contain a common name and does not have alternative names")
|
426
|
+
end
|
427
|
+
|
428
|
+
def match_identify(hostname, identity)
|
429
|
+
if hostname.nil?
|
430
|
+
return false
|
431
|
+
end
|
432
|
+
hostname = hostname.downcase
|
433
|
+
identity = identity.downcase
|
434
|
+
parts = identity.split('.')
|
435
|
+
if parts.length >= 3 && parts.first.end_with?('*') && valid_country_wildcard(parts)
|
436
|
+
create_wildcard_regexp(identity) =~ hostname
|
437
|
+
else
|
438
|
+
hostname == identity
|
439
|
+
end
|
440
|
+
end
|
441
|
+
|
442
|
+
def create_wildcard_regexp(value)
|
443
|
+
# Escape first then search '\*' for meta-char interpolation
|
444
|
+
labels = value.split('.').map { |e| Regexp.escape(e) }
|
445
|
+
# Handle '*'s only at the left-most label, exclude A-label and U-label
|
446
|
+
labels[0].gsub!(/\\\*/, '[^.]+') if !labels[0].start_with?('xn\-\-') and labels[0].ascii_only?
|
447
|
+
/\A#{labels.join('\.')}\z/i
|
448
|
+
end
|
449
|
+
|
450
|
+
def valid_country_wildcard(parts)
|
451
|
+
if parts.length != 3 || parts[2].length != 2
|
452
|
+
true
|
453
|
+
else
|
454
|
+
!BAD_COUNTRY_2LDS.include?(parts[1])
|
455
|
+
end
|
456
|
+
end
|
457
|
+
end
|
458
|
+
|
459
|
+
def self.create_socket(session)
|
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
|
468
|
+
begin
|
469
|
+
if session.proxy
|
470
|
+
site = session.proxy || session.dest
|
471
|
+
socket = JavaSocketWrap.connect(Socket.new, site, opts)
|
472
|
+
session.connect_ssl_proxy(JavaSocketWrap.new(socket), Util.urify(session.dest.to_s))
|
473
|
+
end
|
474
|
+
new(socket, session.dest, session.ssl_config, opts)
|
475
|
+
rescue
|
476
|
+
socket.close if socket
|
477
|
+
raise
|
478
|
+
end
|
479
|
+
end
|
480
|
+
|
481
|
+
DEFAULT_SSL_PROTOCOL = (java.lang.System.getProperty('java.specification.version') == '1.7') ? 'TLSv1.2' : 'TLS'
|
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)
|
502
|
+
if config.ssl_version == :auto
|
503
|
+
DEFAULT_SSL_PROTOCOL
|
504
|
+
else
|
505
|
+
config.ssl_version.to_s.tr('_', '.')
|
506
|
+
end
|
507
|
+
end
|
508
|
+
|
509
|
+
def create_ssl_context(config)
|
510
|
+
unless config.cert_store_crl_items.empty?
|
511
|
+
raise NotImplementedError.new('Manual CRL configuration is not yet supported')
|
512
|
+
end
|
513
|
+
|
514
|
+
km = nil
|
515
|
+
if config.client_cert && config.client_key
|
516
|
+
loader = KeyStoreLoader.new
|
517
|
+
loader.add(config.client_cert, config.client_key, config.client_key_pass)
|
518
|
+
kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm)
|
519
|
+
kmf.init(loader.keystore, KeyStoreLoader::PASSWORD)
|
520
|
+
km = kmf.getKeyManagers
|
521
|
+
end
|
522
|
+
|
523
|
+
trust_store = nil
|
524
|
+
verify_callback = config.verify_callback || config.method(:default_verify_callback)
|
525
|
+
if !config.verify?
|
526
|
+
tmf = VerifyNoneTrustManagerFactory.new(verify_callback)
|
527
|
+
else
|
528
|
+
tmf = SystemTrustManagerFactory.new(verify_callback)
|
529
|
+
loader = TrustStoreLoader.new
|
530
|
+
config.cert_store_items.each do |item|
|
531
|
+
loader.add(item)
|
532
|
+
end
|
533
|
+
trust_store = loader.trust_store
|
534
|
+
end
|
535
|
+
tmf.init(trust_store)
|
536
|
+
tm = tmf.getTrustManagers
|
537
|
+
|
538
|
+
ctx = SSLContext.getInstance(java_ssl_version(config))
|
539
|
+
ctx.init(km, tm, nil)
|
540
|
+
if config.timeout
|
541
|
+
ctx.getClientSessionContext.setSessionTimeout(config.timeout)
|
542
|
+
end
|
543
|
+
ctx
|
544
|
+
end
|
545
|
+
|
546
|
+
def create_ssl_socket(socket, dest, config, opts)
|
547
|
+
ctx = create_ssl_context(config)
|
548
|
+
factory = ctx.getSocketFactory
|
549
|
+
if socket
|
550
|
+
ssl_socket = factory.createSocket(socket, dest.host, dest.port, true)
|
551
|
+
else
|
552
|
+
ssl_socket = factory.createSocket
|
553
|
+
JavaSocketWrap.connect(ssl_socket, dest, opts)
|
554
|
+
end
|
555
|
+
ssl_socket
|
556
|
+
end
|
557
|
+
|
558
|
+
def peer_cert
|
559
|
+
@peer_cert
|
560
|
+
end
|
561
|
+
|
562
|
+
private
|
563
|
+
|
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
|
581
|
+
end
|
582
|
+
end
|
583
|
+
|
584
|
+
SSLSocket = JRubySSLSocket
|
585
|
+
|
586
|
+
end
|
587
|
+
|
588
|
+
end
|