httpclient-fixcerts 2.8.5

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.
Files changed (69) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +98 -0
  3. data/bin/httpclient +77 -0
  4. data/bin/jsonclient +85 -0
  5. data/lib/hexdump.rb +50 -0
  6. data/lib/http-access2/cookie.rb +1 -0
  7. data/lib/http-access2/http.rb +1 -0
  8. data/lib/http-access2.rb +55 -0
  9. data/lib/httpclient/auth.rb +924 -0
  10. data/lib/httpclient/cacert.pem +3952 -0
  11. data/lib/httpclient/cacert1024.pem +3866 -0
  12. data/lib/httpclient/connection.rb +88 -0
  13. data/lib/httpclient/cookie.rb +220 -0
  14. data/lib/httpclient/http.rb +1082 -0
  15. data/lib/httpclient/include_client.rb +85 -0
  16. data/lib/httpclient/jruby_ssl_socket.rb +594 -0
  17. data/lib/httpclient/session.rb +960 -0
  18. data/lib/httpclient/ssl_config.rb +433 -0
  19. data/lib/httpclient/ssl_socket.rb +150 -0
  20. data/lib/httpclient/timeout.rb +140 -0
  21. data/lib/httpclient/util.rb +222 -0
  22. data/lib/httpclient/version.rb +3 -0
  23. data/lib/httpclient/webagent-cookie.rb +459 -0
  24. data/lib/httpclient.rb +1332 -0
  25. data/lib/jsonclient.rb +66 -0
  26. data/lib/oauthclient.rb +111 -0
  27. data/sample/async.rb +8 -0
  28. data/sample/auth.rb +11 -0
  29. data/sample/cookie.rb +18 -0
  30. data/sample/dav.rb +103 -0
  31. data/sample/howto.rb +49 -0
  32. data/sample/jsonclient.rb +67 -0
  33. data/sample/oauth_buzz.rb +57 -0
  34. data/sample/oauth_friendfeed.rb +59 -0
  35. data/sample/oauth_twitter.rb +61 -0
  36. data/sample/ssl/0cert.pem +22 -0
  37. data/sample/ssl/0key.pem +30 -0
  38. data/sample/ssl/1000cert.pem +19 -0
  39. data/sample/ssl/1000key.pem +18 -0
  40. data/sample/ssl/htdocs/index.html +10 -0
  41. data/sample/ssl/ssl_client.rb +22 -0
  42. data/sample/ssl/webrick_httpsd.rb +29 -0
  43. data/sample/stream.rb +21 -0
  44. data/sample/thread.rb +27 -0
  45. data/sample/wcat.rb +21 -0
  46. data/test/ca-chain.pem +44 -0
  47. data/test/ca.cert +23 -0
  48. data/test/client-pass.key +18 -0
  49. data/test/client.cert +19 -0
  50. data/test/client.key +15 -0
  51. data/test/helper.rb +131 -0
  52. data/test/htdigest +1 -0
  53. data/test/htpasswd +2 -0
  54. data/test/jruby_ssl_socket/test_pemutils.rb +32 -0
  55. data/test/runner.rb +2 -0
  56. data/test/server.cert +19 -0
  57. data/test/server.key +15 -0
  58. data/test/sslsvr.rb +65 -0
  59. data/test/subca.cert +21 -0
  60. data/test/test_auth.rb +492 -0
  61. data/test/test_cookie.rb +309 -0
  62. data/test/test_hexdump.rb +14 -0
  63. data/test/test_http-access2.rb +508 -0
  64. data/test/test_httpclient.rb +2145 -0
  65. data/test/test_include_client.rb +52 -0
  66. data/test/test_jsonclient.rb +98 -0
  67. data/test/test_ssl.rb +562 -0
  68. data/test/test_webagent-cookie.rb +465 -0
  69. metadata +124 -0
@@ -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,594 @@
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.normalize_timeout(timeout)
24
+ [Java::JavaLang::Integer::MAX_VALUE, timeout].min
25
+ end
26
+
27
+ def self.connect(socket, site, opts = {})
28
+ socket_addr = InetSocketAddress.new(site.host, site.port)
29
+ if opts[:connect_timeout]
30
+ socket.connect(socket_addr, normalize_timeout(opts[:connect_timeout]))
31
+ else
32
+ socket.connect(socket_addr)
33
+ end
34
+ socket.setSoTimeout(normalize_timeout(opts[:so_timeout])) if opts[:so_timeout]
35
+ socket.setKeepAlive(true) if opts[:tcp_keepalive]
36
+ socket
37
+ end
38
+
39
+ def initialize(socket, debug_dev = nil)
40
+ @socket = socket
41
+ @debug_dev = debug_dev
42
+ @outstr = @socket.getOutputStream
43
+ @instr = BufferedInputStream.new(@socket.getInputStream)
44
+ @buf = (' ' * BUF_SIZE).to_java_bytes
45
+ @bufstr = ''
46
+ end
47
+
48
+ def close
49
+ @socket.close
50
+ end
51
+
52
+ def closed?
53
+ @socket.isClosed
54
+ end
55
+
56
+ def eof?
57
+ @socket.isClosed
58
+ end
59
+
60
+ def gets(rs)
61
+ while (size = @bufstr.index(rs)).nil?
62
+ if fill() == -1
63
+ size = @bufstr.size
64
+ break
65
+ end
66
+ end
67
+ str = @bufstr.slice!(0, size + rs.size)
68
+ debug(str)
69
+ str
70
+ end
71
+
72
+ def read(size, buf = nil)
73
+ while @bufstr.size < size
74
+ if fill() == -1
75
+ break
76
+ end
77
+ end
78
+ str = @bufstr.slice!(0, size)
79
+ debug(str)
80
+ if buf
81
+ buf.replace(str)
82
+ else
83
+ str
84
+ end
85
+ end
86
+
87
+ def readpartial(size, buf = nil)
88
+ while @bufstr.size == 0
89
+ if fill() == -1
90
+ raise EOFError.new('end of file reached')
91
+ end
92
+ end
93
+ str = @bufstr.slice!(0, size)
94
+ debug(str)
95
+ if buf
96
+ buf.replace(str)
97
+ else
98
+ str
99
+ end
100
+ end
101
+
102
+ def <<(str)
103
+ rv = @outstr.write(str.to_java_bytes)
104
+ debug(str)
105
+ rv
106
+ end
107
+
108
+ def flush
109
+ @socket.flush
110
+ end
111
+
112
+ def sync
113
+ true
114
+ end
115
+
116
+ def sync=(sync)
117
+ unless sync
118
+ raise "sync = false is not supported. This option was introduced for backward compatibility just in case."
119
+ end
120
+ end
121
+
122
+ private
123
+
124
+ def fill
125
+ begin
126
+ size = @instr.read(@buf)
127
+ if size > 0
128
+ @bufstr << String.from_java_bytes(@buf, Encoding::BINARY)[0, size]
129
+ end
130
+ size
131
+ rescue java.io.IOException => e
132
+ raise OpenSSL::SSL::SSLError.new("#{e.class}: #{e.getMessage}")
133
+ end
134
+ end
135
+
136
+ def debug(str)
137
+ @debug_dev << str if @debug_dev && str
138
+ end
139
+ end
140
+
141
+ class JRubySSLSocket < JavaSocketWrap
142
+ java_import 'java.io.ByteArrayInputStream'
143
+ java_import 'java.io.InputStreamReader'
144
+ java_import 'java.net.Socket'
145
+ java_import 'java.security.KeyStore'
146
+ java_import 'java.security.cert.Certificate'
147
+ java_import 'java.security.cert.CertificateFactory'
148
+ java_import 'javax.net.ssl.KeyManagerFactory'
149
+ java_import 'javax.net.ssl.SSLContext'
150
+ java_import 'javax.net.ssl.SSLSocketFactory'
151
+ java_import 'javax.net.ssl.TrustManager'
152
+ java_import 'javax.net.ssl.TrustManagerFactory'
153
+ java_import 'javax.net.ssl.X509TrustManager'
154
+ java_import 'org.jruby.ext.openssl.x509store.PEMInputOutput'
155
+
156
+ class JavaCertificate
157
+ attr_reader :cert
158
+
159
+ def initialize(cert)
160
+ @cert = cert
161
+ end
162
+
163
+ def subject
164
+ @cert.getSubjectDN
165
+ end
166
+
167
+ def to_text
168
+ @cert.toString
169
+ end
170
+
171
+ def to_pem
172
+ '(not in PEM format)'
173
+ end
174
+ end
175
+
176
+ class SSLStoreContext
177
+ attr_reader :current_cert, :chain, :error_depth, :error, :error_string
178
+
179
+ def initialize(current_cert, chain, error_depth, error, error_string)
180
+ @current_cert, @chain, @error_depth, @error, @error_string =
181
+ current_cert, chain, error_depth, error, error_string
182
+ end
183
+ end
184
+
185
+ class JSSEVerifyCallback
186
+ def initialize(verify_callback)
187
+ @verify_callback = verify_callback
188
+ end
189
+
190
+ def call(is_ok, chain, error_depth = -1, error = -1, error_string = '(unknown)')
191
+ if @verify_callback
192
+ ruby_chain = chain.map { |cert|
193
+ JavaCertificate.new(cert)
194
+ }.reverse
195
+ # NOTE: The order depends on provider implementation
196
+ ruby_chain.each do |cert|
197
+ is_ok = @verify_callback.call(
198
+ is_ok,
199
+ SSLStoreContext.new(cert, ruby_chain, error_depth, error, error_string)
200
+ )
201
+ end
202
+ end
203
+ is_ok
204
+ end
205
+ end
206
+
207
+ class VerifyNoneTrustManagerFactory
208
+ class VerifyNoneTrustManager
209
+ include X509TrustManager
210
+
211
+ def initialize(verify_callback)
212
+ @verify_callback = JSSEVerifyCallback.new(verify_callback)
213
+ end
214
+
215
+ def checkServerTrusted(chain, authType)
216
+ @verify_callback.call(true, chain)
217
+ end
218
+
219
+ def checkClientTrusted(chain, authType); end
220
+ def getAcceptedIssuers; end
221
+ end
222
+
223
+ def initialize(verify_callback = nil)
224
+ @verify_callback = verify_callback
225
+ end
226
+
227
+ def init(trustStore)
228
+ @managers = [VerifyNoneTrustManager.new(@verify_callback)].to_java(X509TrustManager)
229
+ end
230
+
231
+ def getTrustManagers
232
+ @managers
233
+ end
234
+ end
235
+
236
+ class SystemTrustManagerFactory
237
+ class SystemTrustManager
238
+ include X509TrustManager
239
+
240
+ def initialize(original, verify_callback)
241
+ @original = original
242
+ @verify_callback = JSSEVerifyCallback.new(verify_callback)
243
+ end
244
+
245
+ def checkServerTrusted(chain, authType)
246
+ is_ok = false
247
+ excn = nil
248
+ # TODO can we detect the depth from excn?
249
+ error_depth = -1
250
+ error = nil
251
+ error_message = nil
252
+ begin
253
+ @original.checkServerTrusted(chain, authType)
254
+ is_ok = true
255
+ rescue java.security.cert.CertificateException => excn
256
+ is_ok = false
257
+ error = excn.class.name
258
+ error_message = excn.getMessage
259
+ end
260
+ is_ok = @verify_callback.call(is_ok, chain, error_depth, error, error_message)
261
+ unless is_ok
262
+ excn ||= OpenSSL::SSL::SSLError.new('verifycallback failed')
263
+ raise excn
264
+ end
265
+ end
266
+
267
+ def checkClientTrusted(chain, authType); end
268
+ def getAcceptedIssuers; end
269
+ end
270
+
271
+ def initialize(verify_callback = nil)
272
+ @verify_callback = verify_callback
273
+ end
274
+
275
+ def init(trust_store)
276
+ tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm)
277
+ tmf.java_method(:init, [KeyStore]).call(trust_store)
278
+ @original = tmf.getTrustManagers.find { |tm|
279
+ tm.is_a?(X509TrustManager)
280
+ }
281
+ @managers = [SystemTrustManager.new(@original, @verify_callback)].to_java(X509TrustManager)
282
+ end
283
+
284
+ def getTrustManagers
285
+ @managers
286
+ end
287
+ end
288
+
289
+ module PEMUtils
290
+ def self.read_certificate(pem)
291
+ cert = pem.sub(/.*?-----BEGIN CERTIFICATE-----/m, '').sub(/-----END CERTIFICATE-----.*?/m, '')
292
+ der = cert.unpack('m*').first
293
+ cf = CertificateFactory.getInstance('X.509')
294
+ cf.generateCertificate(ByteArrayInputStream.new(der.to_java_bytes))
295
+ end
296
+
297
+ def self.read_private_key(pem, password)
298
+ if password
299
+ password = password.unpack('C*').to_java(:char)
300
+ end
301
+ PEMInputOutput.read_private_key(InputStreamReader.new(ByteArrayInputStream.new(pem.to_java_bytes)), password)
302
+ end
303
+ end
304
+
305
+ class KeyStoreLoader
306
+ PASSWORD = 16.times.map { rand(256) }.to_java(:char)
307
+
308
+ def initialize
309
+ @keystore = KeyStore.getInstance('JKS')
310
+ @keystore.load(nil)
311
+ end
312
+
313
+ def add(cert_source, key_source, password)
314
+ cert_str = cert_source.respond_to?(:to_pem) ? cert_source.to_pem : File.read(cert_source.to_s)
315
+ cert = PEMUtils.read_certificate(cert_str)
316
+ @keystore.setCertificateEntry('client_cert', cert)
317
+ key_str = key_source.respond_to?(:to_pem) ? key_source.to_pem : File.read(key_source.to_s)
318
+ key_pair = PEMUtils.read_private_key(key_str, password)
319
+ @keystore.setKeyEntry('client_key', key_pair.getPrivate, PASSWORD, [cert].to_java(Certificate))
320
+ end
321
+
322
+ def keystore
323
+ @keystore
324
+ end
325
+ end
326
+
327
+ class TrustStoreLoader
328
+ attr_reader :size
329
+
330
+ def initialize
331
+ @trust_store = KeyStore.getInstance('JKS')
332
+ @trust_store.load(nil)
333
+ @size = 0
334
+ end
335
+
336
+ def add(cert_source)
337
+ return if cert_source == :default
338
+ if cert_source.respond_to?(:to_pem)
339
+ pem = cert_source.to_pem
340
+ load_pem(pem)
341
+ elsif File.directory?(cert_source)
342
+ warn("#{cert_source}: directory not yet supported")
343
+ return
344
+ else
345
+ pem = nil
346
+ File.read(cert_source).each_line do |line|
347
+ case line
348
+ when /-----BEGIN CERTIFICATE-----/
349
+ pem = ''
350
+ when /-----END CERTIFICATE-----/
351
+ load_pem(pem)
352
+ # keep parsing in case where multiple certificates in a file
353
+ else
354
+ if pem
355
+ pem << line
356
+ end
357
+ end
358
+ end
359
+ end
360
+ end
361
+
362
+ def trust_store
363
+ if @size == 0
364
+ nil
365
+ else
366
+ @trust_store
367
+ end
368
+ end
369
+
370
+ private
371
+
372
+ def load_pem(pem)
373
+ cert = PEMUtils.read_certificate(pem)
374
+ @size += 1
375
+ @trust_store.setCertificateEntry("cert_#{@size}", cert)
376
+ end
377
+ end
378
+
379
+ # Ported from commons-httpclient 'BrowserCompatHostnameVerifier'
380
+ class BrowserCompatHostnameVerifier
381
+ BAD_COUNTRY_2LDS = %w(ac co com ed edu go gouv gov info lg ne net or org).sort
382
+ require 'ipaddr'
383
+
384
+ def extract_sans(cert, subject_type)
385
+ sans = cert.getSubjectAlternativeNames rescue nil
386
+ if sans.nil?
387
+ return nil
388
+ end
389
+ sans.find_all { |san|
390
+ san.first.to_i == subject_type
391
+ }.map { |san|
392
+ san[1]
393
+ }
394
+ end
395
+
396
+ def extract_cn(cert)
397
+ subject = cert.getSubjectX500Principal()
398
+ if subject
399
+ subject_dn = javax.naming.ldap.LdapName.new(subject.toString)
400
+ subject_dn.getRdns.to_a.reverse.each do |rdn|
401
+ attributes = rdn.toAttributes
402
+ cn = attributes.get('cn')
403
+ if cn
404
+ if value = cn.get
405
+ return value.to_s
406
+ end
407
+ end
408
+ end
409
+ end
410
+ end
411
+
412
+ def ipaddr?(addr)
413
+ !(IPAddr.new(addr) rescue nil).nil?
414
+ end
415
+
416
+ def verify(hostname, cert)
417
+ is_ipaddr = ipaddr?(hostname)
418
+ sans = extract_sans(cert, is_ipaddr ? 7 : 2)
419
+ cn = extract_cn(cert)
420
+ if sans
421
+ sans.each do |san|
422
+ return true if match_identify(hostname, san)
423
+ end
424
+ raise OpenSSL::SSL::SSLError.new("Certificate for <#{hostname}> doesn't match any of the subject alternative names: #{sans}")
425
+ elsif cn
426
+ return true if match_identify(hostname, cn)
427
+ raise OpenSSL::SSL::SSLError.new("Certificate for <#{hostname}> doesn't match common name of the certificate subject: #{cn}")
428
+ end
429
+ raise OpenSSL::SSL::SSLError.new("Certificate subject for for <#{hostname}> doesn't contain a common name and does not have alternative names")
430
+ end
431
+
432
+ def match_identify(hostname, identity)
433
+ if hostname.nil?
434
+ return false
435
+ end
436
+ hostname = hostname.downcase
437
+ identity = identity.downcase
438
+ parts = identity.split('.')
439
+ if parts.length >= 3 && parts.first.end_with?('*') && valid_country_wildcard(parts)
440
+ create_wildcard_regexp(identity) =~ hostname
441
+ else
442
+ hostname == identity
443
+ end
444
+ end
445
+
446
+ def create_wildcard_regexp(value)
447
+ # Escape first then search '\*' for meta-char interpolation
448
+ labels = value.split('.').map { |e| Regexp.escape(e) }
449
+ # Handle '*'s only at the left-most label, exclude A-label and U-label
450
+ labels[0].gsub!(/\\\*/, '[^.]+') if !labels[0].start_with?('xn\-\-') and labels[0].ascii_only?
451
+ /\A#{labels.join('\.')}\z/i
452
+ end
453
+
454
+ def valid_country_wildcard(parts)
455
+ if parts.length != 3 || parts[2].length != 2
456
+ true
457
+ else
458
+ !BAD_COUNTRY_2LDS.include?(parts[1])
459
+ end
460
+ end
461
+ end
462
+
463
+ def self.create_socket(session)
464
+ opts = {
465
+ :connect_timeout => session.connect_timeout * 1000,
466
+ # send_timeout is ignored in JRuby
467
+ :so_timeout => session.receive_timeout * 1000,
468
+ :tcp_keepalive => session.tcp_keepalive,
469
+ :debug_dev => session.debug_dev
470
+ }
471
+ socket = nil
472
+ begin
473
+ if session.proxy
474
+ site = session.proxy || session.dest
475
+ socket = JavaSocketWrap.connect(Socket.new, site, opts)
476
+ session.connect_ssl_proxy(JavaSocketWrap.new(socket), Util.urify(session.dest.to_s))
477
+ end
478
+ new(socket, session.dest, session.ssl_config, opts)
479
+ rescue
480
+ socket.close if socket
481
+ raise
482
+ end
483
+ end
484
+
485
+ DEFAULT_SSL_PROTOCOL = (java.lang.System.getProperty('java.specification.version') == '1.7') ? 'TLSv1.2' : 'TLS'
486
+ def initialize(socket, dest, config, opts = {})
487
+ @config = config
488
+ begin
489
+ @ssl_socket = create_ssl_socket(socket, dest, config, opts)
490
+ ssl_version = java_ssl_version(config)
491
+ @ssl_socket.setEnabledProtocols([ssl_version].to_java(java.lang.String)) if ssl_version != DEFAULT_SSL_PROTOCOL
492
+ if config.ciphers != SSLConfig::CIPHERS_DEFAULT
493
+ @ssl_socket.setEnabledCipherSuites(config.ciphers.to_java(java.lang.String))
494
+ end
495
+ ssl_connect(dest.host)
496
+ rescue java.security.GeneralSecurityException => e
497
+ raise OpenSSL::SSL::SSLError.new(e.getMessage)
498
+ rescue java.net.SocketTimeoutException => e
499
+ raise HTTPClient::ConnectTimeoutError.new(e.getMessage)
500
+ rescue java.io.IOException => e
501
+ raise OpenSSL::SSL::SSLError.new("#{e.class}: #{e.getMessage}")
502
+ end
503
+
504
+ super(@ssl_socket, opts[:debug_dev])
505
+ end
506
+
507
+ def java_ssl_version(config)
508
+ if config.ssl_version == :auto
509
+ DEFAULT_SSL_PROTOCOL
510
+ else
511
+ config.ssl_version.to_s.tr('_', '.')
512
+ end
513
+ end
514
+
515
+ def create_ssl_context(config)
516
+ unless config.cert_store_crl_items.empty?
517
+ raise NotImplementedError.new('Manual CRL configuration is not yet supported')
518
+ end
519
+
520
+ km = nil
521
+ if config.client_cert && config.client_key
522
+ loader = KeyStoreLoader.new
523
+ loader.add(config.client_cert, config.client_key, config.client_key_pass)
524
+ kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm)
525
+ kmf.init(loader.keystore, KeyStoreLoader::PASSWORD)
526
+ km = kmf.getKeyManagers
527
+ end
528
+
529
+ trust_store = nil
530
+ verify_callback = config.verify_callback || config.method(:default_verify_callback)
531
+ if !config.verify?
532
+ tmf = VerifyNoneTrustManagerFactory.new(verify_callback)
533
+ else
534
+ tmf = SystemTrustManagerFactory.new(verify_callback)
535
+ loader = TrustStoreLoader.new
536
+ config.cert_store_items.each do |item|
537
+ loader.add(item)
538
+ end
539
+ trust_store = loader.trust_store
540
+ end
541
+ tmf.init(trust_store)
542
+ tm = tmf.getTrustManagers
543
+
544
+ ctx = SSLContext.getInstance(java_ssl_version(config))
545
+ ctx.init(km, tm, nil)
546
+ if config.timeout
547
+ ctx.getClientSessionContext.setSessionTimeout(config.timeout)
548
+ end
549
+ ctx
550
+ end
551
+
552
+ def create_ssl_socket(socket, dest, config, opts)
553
+ ctx = create_ssl_context(config)
554
+ factory = ctx.getSocketFactory
555
+ unless socket
556
+ # Create a plain socket first to set connection timeouts on,
557
+ # then wrap it in a SSL socket so that SNI gets setup on it.
558
+ socket = javax.net.SocketFactory.getDefault.createSocket
559
+ JavaSocketWrap.connect(socket, dest, opts)
560
+ end
561
+ factory.createSocket(socket, dest.host, dest.port, true)
562
+ end
563
+
564
+ def peer_cert
565
+ @peer_cert
566
+ end
567
+
568
+ private
569
+
570
+ def ssl_connect(hostname)
571
+ @ssl_socket.startHandshake
572
+ ssl_session = @ssl_socket.getSession
573
+ @peer_cert = JavaCertificate.new(ssl_session.getPeerCertificates.first)
574
+ if $DEBUG
575
+ warn("Protocol version: #{ssl_session.getProtocol}")
576
+ warn("Cipher: #{@ssl_socket.getSession.getCipherSuite}")
577
+ end
578
+ post_connection_check(hostname)
579
+ end
580
+
581
+ def post_connection_check(hostname)
582
+ if !@config.verify?
583
+ return
584
+ else
585
+ BrowserCompatHostnameVerifier.new.verify(hostname, @peer_cert.cert)
586
+ end
587
+ end
588
+ end
589
+
590
+ SSLSocket = JRubySSLSocket
591
+
592
+ end
593
+
594
+ end