manticore 0.8.0-java → 0.9.0-java

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 063b0fd32e3dfbee4335d04293e098e8b1bc054ecd727ff51011e6eef3675c4b
4
- data.tar.gz: 8a5da3e5e6433d7d57e388069e95393fbc66220f1695ab7b1443f9dc49f84d11
3
+ metadata.gz: 281400646207bd8e4b34bed53610787e5b5d222c72fe5e82dd1d42d8f071f1be
4
+ data.tar.gz: 421a4a8f1c4f8e06a7d12377812da2613877877904c73d3d8375348fbb9e2103
5
5
  SHA512:
6
- metadata.gz: db22357ef64973e1452bb970dbb2d7a6c960c895db7d0afe1ae96339f414a06dba8c1039b7f5d3d0ce493443317a9d72c7dce04e7c7fbab98704e55e124d2766
7
- data.tar.gz: 6744d52a35f0e94d23db237b9f364353eeec4bb3667eb35a06620cf16b676e5b49bd76bb12ba7296f6924b2b7ae052cb4f1d443a6f1e87b00d32b9d1af60c507
6
+ metadata.gz: c72e625dc8aa526a1336c239ced7d46cebf9b16817f9c309b1caaa80028a14e959cbe342e745db69dfef8fd284b1413085a518322406ecab3e7a28725815a34f
7
+ data.tar.gz: 37774c46167abf4c8cb637580dc2cf91afc4311b8a284c17d48441ba00824ba0af41e47121780837caffef60632aa3c26bb075b323011a2fcb56fb055495e83c
data/.travis.yml CHANGED
@@ -4,18 +4,18 @@ cache:
4
4
  - bundler
5
5
  - directories:
6
6
  - $HOME/.m2
7
- rvm:
8
- - jruby-9.2.19.0 # Ruby 2.5
9
- jdk:
10
- - oraclejdk8
11
- - openjdk8
12
- - openjdk11
13
7
  before_install:
14
8
  - gem install bundler -v 1.17.3
15
9
  matrix:
16
10
  include:
17
11
  - rvm: jruby-head
18
12
  jdk: openjdk11
13
+ - rvm: jruby-9.3.4.0 # Ruby 2.6
14
+ jdk: openjdk11
15
+ - rvm: jruby-9.2.20.0 # Ruby 2.5
16
+ jdk: oraclejdk8
17
+ - rvm: jruby-9.2.20.0
18
+ jdk: openjdk11
19
19
  - rvm: jruby-9.1.17.0 # Ruby 2.3
20
20
  jdk: openjdk8
21
21
  allow_failures:
data/CHANGELOG.md CHANGED
@@ -1,3 +1,11 @@
1
+ ### v0.9.0
2
+
3
+ * [feat] revamped client.close to release resources (#108)
4
+ * [fix] null password handling when loading keystore
5
+ * [fix] ambiguous Java method selection
6
+ * [refactor] revisit hostname verification (#103)
7
+ * [feat] ssl[:trust_strategy]: wrapper for `org.apache.http.conn.ssl.TrustStrategy` (#106)
8
+
1
9
  ### v0.8.0
2
10
 
3
11
  * [feat] restore compat with (legacy) verify: false (#102)
data/Gemfile CHANGED
@@ -4,13 +4,15 @@ source 'https://rubygems.org'
4
4
  gemspec
5
5
 
6
6
  group :development, :test do
7
- gem "net-http-server", "~> 0.2"
7
+ gem "rake-compiler", require: false
8
+ gem "simplecov"
9
+
8
10
  gem "rspec", "~> 3.0"
9
11
  gem "rspec-its"
10
- gem "httpclient", "~> 2.3"
11
- gem "rack", ">= 2.1.4"
12
- gem "rake-compiler"
13
- gem "gserver"
14
- gem "simplecov"
15
- gem "json"
12
+
13
+ gem "rack", ">= 2.1.4", require: false
14
+ gem "json", require: false
15
+ gem "webrick", require: false
16
+ gem "net-http-server", require: false
17
+ gem "gserver", require: false
16
18
  end
@@ -0,0 +1,119 @@
1
+ module Manticore
2
+ class Client
3
+ ##
4
+ # TrustStrategies is a utility module that provides helpers for
5
+ # working with org.apache.http.conn.ssl.TrustStrategy
6
+ module TrustStrategies
7
+ # Coerces to org.apache.http.conn.ssl.TrustStrategy, allowing nil pass-through
8
+ #
9
+ # @overload coerce(coercible)
10
+ # @param coercible [nil|TrustStrategy]
11
+ # @return [nil,TrustStrategy]
12
+ # @overload coerce(coercible)
13
+ # @param coercible [Proc<(Array<OpenSSL::X509::Certificate>,String)>:Boolean]
14
+ # A proc that accepts two arguments and returns a boolean value, and is effectively a
15
+ # Ruby-native implementation of `org.apache.http.conn.ssl.TrustStrategy#isTrusted`.
16
+ # @param cert_chain [Enumerable<OpenSSL::X509::Certificate>]: the peer's certificate chain
17
+ # @param auth_type [String]: the authentication type based on the client certificate
18
+ # @raise [OpenSSL::X509::CertificateError]: thrown if the certificate is not trusted or invalid
19
+ # @return [Boolean]: true if the certificate can be trusted without verification by the trust manager,
20
+ # false otherwise.
21
+ # @example: CA Trusted Fingerprint
22
+ # ca_trusted_fingerprint = lambda do |cert_chain, type|
23
+ # cert_chain.lazy
24
+ # .map(&:to_der)
25
+ # .map(&::Digest::SHA256.method(:hexdigest))
26
+ # .include?("324a87eebb19265ffb675dc345eb0f3b5d9de3f015159227a00fe552291d4cc4")
27
+ # end
28
+ # TrustStrategies.coerce(ca_trusted_fingerprint)
29
+ def self.coerce(coercible)
30
+ case coercible
31
+ when org.apache.http.conn.ssl.TrustStrategy, nil then coercible
32
+ when ::Proc then CustomTrustStrategy.new(coercible)
33
+ else fail(ArgumentError, "No implicit conversion of #{coercible} to #{self}")
34
+ end
35
+ end
36
+
37
+ # Combines two possibly-nil TrustStrategies-coercible objects into a
38
+ # single org.apache.http.conn.ssl.TrustStrategy, or to nil if both are nil.
39
+ #
40
+ # @param lhs [nil|TrustStrategie#coerce]
41
+ # @param rhs [nil|TrustStrategies#coerce]
42
+ # @return [nil,org.apache.http.conn.ssl.TrustStrategy]
43
+ def self.combine(lhs, rhs)
44
+ return coerce(rhs) if lhs.nil?
45
+ return coerce(lhs) if rhs.nil?
46
+
47
+ CombinedTrustStrategy.new(coerce(lhs), coerce(rhs))
48
+ end
49
+ end
50
+
51
+ ##
52
+ # @api private
53
+ # A CombinedTrustStrategy can be used to bypass the Trust Manager if
54
+ # *EITHER* TrustStrategy trusts the provided certificate chain.
55
+ # @see TrustStrategies::combine
56
+ class CombinedTrustStrategy
57
+ include org.apache.http.conn.ssl.TrustStrategy
58
+
59
+ ##
60
+ # @api private
61
+ # @see TrustStrategies::combine
62
+ def initialize(lhs, rhs)
63
+ @lhs = lhs
64
+ @rhs = rhs
65
+ super()
66
+ end
67
+
68
+ ##
69
+ # @override (see org.apache.http.conn.ssl.TrustStrategy#isTrusted)
70
+ def trusted?(chain, type)
71
+ @lhs.trusted?(chain, type) || @rhs.trusted?(chain, type)
72
+ end
73
+ end
74
+
75
+
76
+ ##
77
+ # @api private
78
+ # A CustomTrustStrategy is an org.apache.http.conn.ssl.TrustStrategy
79
+ # defined with a proc that uses Ruby OpenSSL::X509::Certificates
80
+ # @see TrustStrategies::coerce(Proc)
81
+ class CustomTrustStrategy
82
+ include org.apache.http.conn.ssl.TrustStrategy
83
+
84
+ ##
85
+ # @see TrustStrategies.coerce(Proc)
86
+ def initialize(proc)
87
+ fail(ArgumentError, "2-arity proc required") unless proc.arity == 2
88
+ @trust_strategy = proc
89
+ end
90
+
91
+ CONVERT_JAVA_CERTIFICATE_TO_RUBY = -> (java_cert) { ::OpenSSL::X509::Certificate.new(java_cert.encoded) }
92
+ private_constant :CONVERT_JAVA_CERTIFICATE_TO_RUBY
93
+
94
+ ##
95
+ # @override (see org.apache.http.conn.ssl.TrustStrategy#isTrusted)
96
+ def trusted?(java_chain, type)
97
+ @trust_strategy.call(java_chain.lazy.map(&CONVERT_JAVA_CERTIFICATE_TO_RUBY), String.new(type))
98
+ rescue OpenSSL::X509::CertificateError => e
99
+ raise java_certificate_exception(e)
100
+ end
101
+
102
+ private
103
+
104
+ begin
105
+ # Ruby exceptions can be converted to Throwable since JRuby 9.2
106
+ Exception.new("sentinel").to_java(java.lang.Throwable)
107
+ def java_certificate_exception(ruby_certificate_error)
108
+ throwable = ruby_certificate_error.to_java(java.lang.Throwable)
109
+ java.security.cert.CertificateException.new(throwable)
110
+ end
111
+ rescue TypeError
112
+ def java_certificate_exception(ruby_certificate_error)
113
+ message = ruby_certificate_error.message
114
+ java.security.cert.CertificateException.new(message)
115
+ end
116
+ end
117
+ end
118
+ end
119
+ end
@@ -86,25 +86,38 @@ module Manticore
86
86
  java_import "org.manticore.HttpGetWithEntity"
87
87
  java_import "org.manticore.HttpDeleteWithEntity"
88
88
  java_import "org.apache.http.auth.UsernamePasswordCredentials"
89
+ java_import "org.apache.http.conn.ssl.DefaultHostnameVerifier"
90
+ java_import "org.apache.http.conn.ssl.NoopHostnameVerifier"
89
91
  java_import "org.apache.http.conn.ssl.SSLConnectionSocketFactory"
90
- java_import "org.apache.http.conn.ssl.SSLContextBuilder"
91
92
  java_import "org.apache.http.conn.ssl.TrustAllStrategy"
92
93
  java_import "org.apache.http.conn.ssl.TrustSelfSignedStrategy"
93
94
  java_import "org.apache.http.client.utils.URIBuilder"
94
95
  java_import "org.apache.http.impl.DefaultConnectionReuseStrategy"
95
96
  java_import "org.apache.http.impl.auth.BasicScheme"
97
+ java_import "org.apache.http.ssl.SSLContextBuilder"
98
+ java_import "org.apache.http.ssl.TrustStrategy"
96
99
 
97
100
  # This is a class rather than a proc because the proc holds a closure around
98
101
  # the instance of the Client that creates it.
99
102
  class ExecutorThreadFactory
100
- include ::Java::JavaUtilConcurrent::ThreadFactory
103
+ include java.util.concurrent.ThreadFactory
104
+
105
+ java_import 'java.lang.Thread'
106
+
107
+ @@factory_no = java.util.concurrent.atomic.AtomicInteger.new
108
+
109
+ def initialize(client)
110
+ @thread_no = java.util.concurrent.atomic.AtomicInteger.new
111
+ @name_prefix = "manticore##{client.object_id}-#{@@factory_no.increment_and_get}-"
112
+ end
101
113
 
102
114
  def newThread(runnable)
103
- thread = Executors.defaultThreadFactory.newThread(runnable)
104
- thread.daemon = true
105
- return thread
115
+ thread = Thread.new(runnable, @name_prefix + @thread_no.increment_and_get.to_s)
116
+ thread.setDaemon(true)
117
+ thread
106
118
  end
107
119
  end
120
+ private_constant :ExecutorThreadFactory
108
121
 
109
122
  include ProxiesInterface
110
123
 
@@ -162,10 +175,12 @@ module Manticore
162
175
  # @option options [Hash] ssl Hash of options for configuring SSL
163
176
  # @option options [Array<String>] ssl[:protocols] (nil) A list of protocols that Manticore should accept
164
177
  # @option options [Array<String>] ssl[:cipher_suites] (nil) A list of cipher suites that Manticore should accept
165
- # @option options [Symbol] ssl[:verify] (:strict) Hostname verification setting. Set to `:disable` to turn off hostname verification. Setting to `:browser` will
178
+ # @option options [Symbol] ssl[:verify] (:default) Hostname verification setting. Set to `:none` to turn off hostname verification. Setting to `:browser` will
166
179
  # cause Manticore to accept a certificate for *.foo.com for all subdomains and sub-subdomains (eg a.b.foo.com).
167
- # The default `:strict` is like `:browser` except it'll only accept a single level of subdomains for wildcards,
180
+ # The default `:default` is like `:browser` but more strict - only accepts a single level of subdomains for wildcards,
168
181
  # eg `b.foo.com` will be accepted for a `*.foo.com` certificate, but `a.b.foo.com` will not be.
182
+ # @option options [Client::TrustStrategiesInterface] ssl[:trust_strategy] (nil) A trust strategy to use in addition to any built by `ssl[:verify]`.
183
+ # @see Client::TrustStrategiesInterface#coerce
169
184
  # @option options [String] ssl[:truststore] (nil) Path to a custom trust store to use the verifying SSL connections
170
185
  # @option options [String] ssl[:truststore_password] (nil) Password used for decrypting the server trust store
171
186
  # @option options [String] ssl[:truststore_type] (nil) Format of the trust store, ie "JKS" or "PKCS12". If left nil, the type will be inferred from the truststore filename.
@@ -178,8 +193,8 @@ module Manticore
178
193
  # @option options [boolean] ssl[:track_state] (false) Turn on or off connection state tracking. This helps prevent SSL information from leaking across threads, but means that connections
179
194
  # can't be shared across those threads. This should generally be left off unless you know what you're doing.
180
195
  def initialize(options = {})
181
- @finalizers = []
182
- self.class.shutdown_on_finalize self, @finalizers
196
+ @finalizer = Finalizer.new
197
+ self.class.shutdown_on_finalize self, @finalizer
183
198
 
184
199
  builder = client_builder
185
200
  builder.set_user_agent options.fetch(:user_agent, "Manticore #{VERSION}")
@@ -345,21 +360,58 @@ module Manticore
345
360
  end
346
361
  end
347
362
 
348
- # Free resources associated with the CloseableHttpClient
349
- def close
350
- @client.close if @client
363
+ # Release resources held by this client, namely:
364
+ # - close the internal http client
365
+ # - shutdown the connection pool
366
+ # - stops accepting async requests in the executor
367
+ #
368
+ # After this call the client is no longer usable.
369
+ # @note In versions before 0.9 this method only closed the underlying `CloseableHttpClient`
370
+ def close(await: nil)
371
+ ObjectSpace.undefine_finalizer(self)
372
+ @finalizer.call # which does ~ :
373
+ # @executor&.shutdown rescue nil
374
+ # @client&.close
375
+ # @pool&.shutdown rescue nil
376
+ @async_requests.close
377
+ case await
378
+ when false, nil
379
+ @executor&.shutdown_now rescue nil
380
+ when Numeric
381
+ # NOTE: the concept of awaiting gracefully might/should also be used with the pool/client closing
382
+ millis = java.util.concurrent.TimeUnit::MILLISECONDS
383
+ @executor&.await_termination(await * 1000, millis) rescue nil
384
+ else
385
+ nil
386
+ end
351
387
  end
352
388
 
389
+ # @private
390
+ class Finalizer
391
+
392
+ def initialize
393
+ @_objs = []
394
+ end
395
+
396
+ def push(obj, args)
397
+ @_objs.unshift [java.lang.ref.WeakReference.new(obj), Array(args)]
398
+ end
399
+
400
+ def call(id = nil) # when called on finalization an object id arg is passed
401
+ @_objs.each { |obj, args| obj.get&.send(*args) rescue nil }
402
+ end
403
+
404
+ end
405
+ private_constant :Finalizer
406
+
353
407
  # Get at the underlying ExecutorService used to invoke asynchronous calls.
354
408
  def executor
355
- create_executor_if_needed
356
- @executor
409
+ @executor ||= create_executor
357
410
  end
358
411
 
359
- def self.shutdown_on_finalize(client, objs)
360
- ObjectSpace.define_finalizer client, -> {
361
- objs.each { |obj, args| obj.send(*args) rescue nil }
362
- }
412
+ # @private
413
+ def self.shutdown_on_finalize(client, finalizer)
414
+ ObjectSpace.define_finalizer(client, finalizer)
363
415
  end
364
416
 
365
417
  protected
@@ -367,8 +419,9 @@ module Manticore
367
419
  # Takes an object and a message to pass to the object to destroy it. This is done rather than
368
420
  # a proc to avoid creating a closure that would maintain a reference to this client, which
369
421
  # would prevent the client from being cleaned up.
422
+ # @private
370
423
  def finalize(object, args)
371
- @finalizers << [WeakRef.new(object), Array(args)]
424
+ @finalizer.push(object, args)
372
425
  end
373
426
 
374
427
  def url_as_regex(url)
@@ -388,7 +441,7 @@ module Manticore
388
441
 
389
442
  # :nocov:
390
443
  if options[:ignore_ssl_validation]
391
- $stderr.puts "The options[:ignore_ssl_validation] setting is deprecated in favor of options[:ssl][:verify]"
444
+ warn "The options[:ignore_ssl_validation] setting is deprecated in favor of options[:ssl][:verify]"
392
445
  options[:ssl] ||= {}
393
446
  options[:ssl] = {:verify => !options.delete(:ignore_ssl_validation)}.merge(options[:ssl])
394
447
  end
@@ -420,17 +473,16 @@ module Manticore
420
473
  socket_config_builder.build
421
474
  end
422
475
 
423
- def create_executor_if_needed
424
- return @executor if @executor
425
- @executor = Executors.new_cached_thread_pool(ExecutorThreadFactory.new)
426
- finalize @executor, :shutdown
476
+ def create_executor
477
+ executor = Executors.new_cached_thread_pool(ExecutorThreadFactory.new(self))
478
+ finalize executor, :shutdown
479
+ executor
427
480
  end
428
481
 
429
482
  def request(klass, url, options, &block)
430
483
  req, context = request_from_options(klass, url, options)
431
484
  async = options.delete(:async)
432
485
  background = options.delete(:async_background)
433
- create_executor_if_needed if (background || async)
434
486
  response = response_object_for(req, context, &block)
435
487
 
436
488
  if async
@@ -525,7 +577,6 @@ module Manticore
525
577
  end
526
578
 
527
579
  def get_proxy_host(opt)
528
- host = nil
529
580
  if opt.is_a? String
530
581
  uri = URI.parse(opt)
531
582
  if uri.host
@@ -610,23 +661,27 @@ module Manticore
610
661
 
611
662
  # Configure the SSL Context
612
663
  def ssl_socket_factory_from_options(ssl_options)
613
- trust_store = trust_strategy = nil
664
+ trust_strategy = nil
614
665
 
615
- case ssl_options.fetch(:verify, :strict)
616
- when false
617
- trust_store = nil
618
- trust_strategy = TrustSelfSignedStrategy::INSTANCE
619
- verifier = SSLConnectionSocketFactory::ALLOW_ALL_HOSTNAME_VERIFIER
620
- when :disable, :none
621
- trust_store = nil
666
+ case ssl_options.fetch(:verify, :default)
667
+ when :none, :disable
622
668
  trust_strategy = TrustAllStrategy::INSTANCE
669
+ verifier = NoopHostnameVerifier::INSTANCE
670
+ when false # compatibility
671
+ trust_strategy = TrustSelfSignedStrategy::INSTANCE
623
672
  verifier = SSLConnectionSocketFactory::ALLOW_ALL_HOSTNAME_VERIFIER
624
673
  when :browser
625
674
  verifier = SSLConnectionSocketFactory::BROWSER_COMPATIBLE_HOSTNAME_VERIFIER
626
- when true, :strict, :default
675
+ when :default, true
676
+ verifier = DefaultHostnameVerifier.new
677
+ when :strict # compatibility
627
678
  verifier = SSLConnectionSocketFactory::STRICT_HOSTNAME_VERIFIER
628
679
  else
629
- raise "Invalid value for :verify. Valid values are (:all, :browser, :default)"
680
+ raise "Invalid value for :verify. Valid values are (:default, :browser, :none)"
681
+ end
682
+
683
+ if ssl_options.include?(:trust_strategy)
684
+ trust_strategy = TrustStrategies.combine(trust_strategy, ssl_options.fetch(:trust_strategy))
630
685
  end
631
686
 
632
687
  context = SSLContextBuilder.new
@@ -650,7 +705,7 @@ module Manticore
650
705
  end
651
706
  end
652
707
 
653
- context.load_trust_material(trust_store, trust_strategy)
708
+ context.java_send :loadTrustMaterial, [KeyStore, TrustStrategy], trust_store, trust_strategy
654
709
  end
655
710
 
656
711
  KEY_EXTRACTION_REGEXP = /(?:^-----BEGIN(.* )PRIVATE KEY-----\n)(.*?)(?:-----END\1PRIVATE KEY.*$)/m
@@ -662,7 +717,6 @@ module Manticore
662
717
  # Support OpenSSL-style bare X.509 certs with an RSA key
663
718
  if ssl_options[:client_cert] && ssl_options[:client_key]
664
719
  key_store ||= blank_keystore
665
- certs, key = nil, nil
666
720
 
667
721
  cert_str = if ssl_options[:client_cert].is_a?(OpenSSL::X509::Certificate)
668
722
  ssl_options[:client_cert].to_s
@@ -701,7 +755,7 @@ module Manticore
701
755
  def get_store(prefix, options)
702
756
  KeyStore.get_instance(options[:"#{prefix}_type"] || guess_store_type(options[prefix])).tap do |store|
703
757
  instream = open(options[prefix], "rb").to_inputstream
704
- store.load(instream, options.fetch(:"#{prefix}_password", nil).to_java.toCharArray)
758
+ store.load(instream, options.fetch(:"#{prefix}_password", nil).to_java&.toCharArray)
705
759
  end
706
760
  end
707
761
 
@@ -1,3 +1,3 @@
1
1
  module Manticore
2
- VERSION = "0.8.0"
2
+ VERSION = "0.9.0"
3
3
  end
data/lib/manticore.rb CHANGED
@@ -67,6 +67,7 @@ module Manticore
67
67
 
68
68
  require_relative "./manticore/java_extensions"
69
69
  require_relative "./manticore/client/proxies"
70
+ require_relative "./manticore/client/trust_strategies"
70
71
  require_relative "./manticore/client"
71
72
  require_relative "./manticore/response"
72
73
  require_relative "./manticore/stubbed_response"
Binary file
data/manticore.gemspec CHANGED
@@ -29,8 +29,6 @@ Gem::Specification.new do |spec|
29
29
 
30
30
  spec.add_dependency "openssl_pkcs8_pure"
31
31
 
32
- spec.add_development_dependency "bundler"
33
- spec.add_development_dependency "rake"
34
32
  spec.add_development_dependency "jar-dependencies", "~> 0.4.1"
35
33
 
36
34
  spec.requirements << "jar org.apache.httpcomponents:httpclient, '~> 4.5.13'"
@@ -1,12 +1,11 @@
1
1
  # encoding: utf-8
2
2
  require "spec_helper"
3
3
 
4
- java_import "org.apache.http.entity.mime.MultipartEntityBuilder"
5
- java_import "org.apache.http.entity.ContentType"
6
-
7
4
  describe Manticore::Client do
8
5
  let(:client) { Manticore::Client.new }
9
6
 
7
+ after { client.close }
8
+
10
9
  it "fetches a URL and return a response" do
11
10
  expect(client.get(local_server)).to be_a Manticore::Response
12
11
  end
@@ -96,7 +95,7 @@ describe Manticore::Client do
96
95
 
97
96
  describe "ignore_ssl_validation (deprecated option)" do
98
97
  context "when on" do
99
- let(:client) { Manticore::Client.new ssl: {verify: false} }
98
+ let(:client) { Manticore::Client.new ssl: { verify: false } }
100
99
 
101
100
  it "does not break on SSL validation errors" do
102
101
  expect { client.get("https://localhost:55444/").body }.to_not raise_exception
@@ -104,7 +103,7 @@ describe Manticore::Client do
104
103
  end
105
104
 
106
105
  context "when off" do
107
- let(:client) { Manticore::Client.new ssl: {verify: true} }
106
+ let(:client) { Manticore::Client.new ssl: { verify: true } }
108
107
 
109
108
  it "breaks on SSL validation errors" do
110
109
  expect { client.get("https://localhost:55444/").call }.to raise_exception(Manticore::ClientProtocolException)
@@ -148,6 +147,29 @@ describe Manticore::Client do
148
147
  end
149
148
  end
150
149
 
150
+ context "when on and custom trust strategy is given" do
151
+ # let(:custom_trust_strategy) { Proc.new {|chain,type| true } }
152
+ let(:client) { Manticore::Client.new :ssl => {:verify => :strict, :trust_strategy => custom_trust_strategy} }
153
+ context 'and trust strategy approves the cert chain' do
154
+ let(:custom_trust_strategy) { Proc.new { |chain,type| true } }
155
+ it "verifies the request and succeed" do
156
+ expect { client.get("https://localhost:55444/").body }.to_not raise_exception
157
+ end
158
+ end
159
+ context 'and trust strategy does not approve the cert chain' do
160
+ let(:custom_trust_strategy) { Proc.new { |chain,type| false } }
161
+ it "breaks on SSL validation errors" do
162
+ begin
163
+ client.get("https://localhost:55445/").body
164
+ rescue Manticore::ClientProtocolException => e
165
+ expect( e.cause ).to be_a javax.net.ssl.SSLHandshakeException
166
+ else
167
+ fail "exception not raised"
168
+ end
169
+ end
170
+ end
171
+ end
172
+
151
173
  context "when the client specifies a protocol list" do
152
174
  let(:client) { Manticore::Client.new :ssl => {verify: :strict, truststore: File.expand_path("../../ssl/truststore.jks", __FILE__), truststore_password: "test123", protocols: ["TLSv1", "TLSv1.1", "TLSv1.2"]} }
153
175
 
@@ -157,10 +179,10 @@ describe Manticore::Client do
157
179
  end
158
180
 
159
181
  context "when on and custom trust store is given with the wrong password" do
160
- let(:client) { Manticore::Client.new :ssl => {verify: :strict, truststore: File.expand_path("../../ssl/truststore.jks", __FILE__), truststore_password: "wrongpass"} }
182
+ let(:ssl_opts) { { verify: :strict, truststore: File.expand_path("../../ssl/truststore.jks", __FILE__), truststore_password: "wrongpass" } }
161
183
 
162
184
  it "fails to load the keystore" do
163
- expect { client.get("https://localhost:55444/").body }.to raise_exception(Java::JavaIo::IOException)
185
+ expect { Manticore::Client.new(:ssl => ssl_opts) }.to raise_exception(Java::JavaIo::IOException)
164
186
  end
165
187
  end
166
188
 
@@ -193,7 +215,7 @@ describe Manticore::Client do
193
215
  let(:client) {
194
216
  Manticore::Client.new(
195
217
  :ssl => {
196
- verify: :strict,
218
+ verify: :default,
197
219
  ca_file: File.expand_path("../../ssl/root-ca.crt", __FILE__),
198
220
  client_cert: OpenSSL::X509::Certificate.new(File.read(File.expand_path("../../ssl/client.crt", __FILE__))),
199
221
  client_key: OpenSSL::PKey::RSA.new(File.read(File.expand_path("../../ssl/client.key", __FILE__))),
@@ -210,7 +232,7 @@ describe Manticore::Client do
210
232
  let(:client) {
211
233
  Manticore::Client.new(
212
234
  :ssl => {
213
- verify: :strict,
235
+ verify: :default,
214
236
  ca_file: File.expand_path("../../ssl/root-ca.crt", __FILE__),
215
237
  client_cert: File.read(File.expand_path("../../ssl/client.crt", __FILE__)),
216
238
  client_key: File.read(File.expand_path("../../ssl/client.key", __FILE__)),
@@ -237,6 +259,23 @@ describe Manticore::Client do
237
259
  it "does not break on untrusted certificates" do
238
260
  expect { client.get("https://localhost:55447/").body }.to_not raise_exception
239
261
  end
262
+
263
+ context "when custom trust strategy is given" do
264
+ # let(:custom_trust_strategy) { Proc.new {|chain,type| true } }
265
+ let(:client) { Manticore::Client.new :ssl => {:verify => :disable, :trust_strategy => custom_trust_strategy} }
266
+ context 'and trust strategy approves the cert chain' do
267
+ let(:custom_trust_strategy) { Proc.new { |chain,type| true } }
268
+ it "verifies the request and succeed" do
269
+ expect { client.get("https://localhost:55444/").body }.to_not raise_exception
270
+ end
271
+ end
272
+ context 'and trust strategy does not approve the cert chain' do
273
+ let(:custom_trust_strategy) { Proc.new { |chain,type| false } }
274
+ it "verifies the request and succeed" do
275
+ expect { client.get("https://localhost:55444/").body }.to_not raise_exception
276
+ end
277
+ end
278
+ end
240
279
  end
241
280
 
242
281
  context "against a server that verifies clients" do
@@ -276,11 +315,11 @@ describe Manticore::Client do
276
315
  end
277
316
 
278
317
  describe ":cipher_suites" do
279
- skip
318
+ skip 'TODO: someone should write the spec'
280
319
  end
281
320
 
282
321
  describe ":protocols" do
283
- skip
322
+ skip 'TODO: someone should write the spec'
284
323
  end
285
324
  end
286
325
 
@@ -535,7 +574,9 @@ describe Manticore::Client do
535
574
 
536
575
  it "sends an arbitrary entity" do
537
576
  f = open(File.expand_path(File.join(__FILE__, "..", "..", "spec_helper.rb")), "r").to_inputstream
538
- multipart_entity = MultipartEntityBuilder.create.add_text_body("foo", "bar").add_binary_body("whatever", f, ContentType::TEXT_PLAIN, __FILE__)
577
+ multipart_entity = org.apache.http.entity.mime.MultipartEntityBuilder.create.
578
+ add_text_body("foo", "bar").
579
+ add_binary_body("whatever", f, org.apache.http.entity.ContentType::TEXT_PLAIN, __FILE__)
539
580
  response = client.post(local_server, entity: multipart_entity.build)
540
581
  expect(response.body).to match "RSpec.configure"
541
582
  end
@@ -746,14 +787,13 @@ describe Manticore::Client do
746
787
  context "with a misbehaving endpoint" do
747
788
  let(:port) do
748
789
  p = 4000
749
- server = nil
750
790
  begin
751
791
  server = TCPServer.new p
752
792
  rescue Errno::EADDRINUSE
753
793
  p += 1
754
794
  retry
755
795
  ensure
756
- server.close
796
+ server&.close
757
797
  end
758
798
  p
759
799
  end
@@ -774,6 +814,7 @@ describe Manticore::Client do
774
814
  ].join("\n"))
775
815
  client.close
776
816
  rescue IOError => e
817
+ warn "caught an error: #{e.inspect}"
777
818
  break
778
819
  end
779
820
  end
@@ -0,0 +1,168 @@
1
+ # encoding: utf-8
2
+ require "spec_helper"
3
+ describe Manticore::Client::TrustStrategies do
4
+ describe '#coerce' do
5
+ subject(:coerced) { described_class.coerce(input) }
6
+ context 'with a nil value' do
7
+ let(:input) { nil }
8
+ it 'returns the value unchanged' do
9
+ expect(coerced).to be_nil
10
+ end
11
+ end
12
+ context 'with an implementation of org.apache.http.conn.ssl.TrustStrategy' do
13
+ let(:input) { org.apache.http.conn.ssl.TrustAllStrategy::INSTANCE }
14
+ it 'returns the value unchanged' do
15
+ expect(coerced).to be input
16
+ end
17
+ end
18
+ context 'with a Proc' do
19
+ let(:input) { ->(chain, type) { true } }
20
+ it 'wraps the proc in a `CustomTrustStrategy`' do
21
+ expect(Manticore::Client::CustomTrustStrategy).to receive(:new).with(input).and_call_original
22
+ expect(described_class.coerce(input)).to be_a_kind_of Manticore::Client::CustomTrustStrategy
23
+ end
24
+ end
25
+ end
26
+
27
+ describe '#combine' do
28
+ context 'when left-hand value is nil' do
29
+ let(:left_hand_strategy) { nil }
30
+ let(:right_hand_strategy) { described_class.coerce(->(chain,type){ true }) }
31
+ it 'returns the right-hand value coerced' do
32
+ expect(described_class).to receive(:coerce).with(right_hand_strategy).and_call_original
33
+ expect(described_class.combine(left_hand_strategy, right_hand_strategy)).to be right_hand_strategy
34
+ end
35
+ end
36
+ context 'when the right-hand value is nil' do
37
+ let(:left_hand_strategy) { described_class.coerce(->(chain,type){ true }) }
38
+ let(:right_hand_strategy) { nil }
39
+ it 'returns the left-hand value coerced' do
40
+ expect(described_class).to receive(:coerce).with(left_hand_strategy).and_call_original
41
+ expect(described_class.combine(left_hand_strategy, right_hand_strategy)).to be left_hand_strategy
42
+ end
43
+ end
44
+ context 'when neither value is nil' do
45
+ let(:left_hand_strategy) { described_class.coerce(->(chain,type){ true }) }
46
+ let(:right_hand_strategy) { described_class.coerce(->(chain,type){ true }) }
47
+
48
+ it 'returns a CombinedTrustStrategy' do
49
+ expect(Manticore::Client::CombinedTrustStrategy)
50
+ .to receive(:new).with(left_hand_strategy, right_hand_strategy).and_call_original
51
+
52
+ # ensures that the values are coerced.
53
+ expect(described_class).to receive(:coerce).with(left_hand_strategy).and_call_original
54
+ expect(described_class).to receive(:coerce).with(right_hand_strategy).and_call_original
55
+
56
+ combined = described_class.combine(left_hand_strategy, right_hand_strategy)
57
+ expect(combined).to be_a_kind_of Manticore::Client::CombinedTrustStrategy
58
+ end
59
+ end
60
+ context 'when both values are nil' do
61
+ let(:left_hand_strategy) { nil }
62
+ let(:right_hand_strategy) { nil }
63
+
64
+ it 'returns nil' do
65
+ expect(described_class.combine(left_hand_strategy, right_hand_strategy)).to be nil
66
+ end
67
+ end
68
+ end
69
+ end
70
+
71
+ describe Manticore::Client::CustomTrustStrategy do
72
+
73
+ subject(:custom_trust_strategy) { described_class.new(trust_strategy_proc) }
74
+
75
+ context 'when called via Java interface' do
76
+ def load_java_cert(file_path)
77
+ pem_contents = File.read(file_path)
78
+ cf = java.security.cert.CertificateFactory::getInstance("X.509")
79
+ is = java.io.ByteArrayInputStream.new(pem_contents.to_java_bytes)
80
+ cf.generateCertificate(is)
81
+ end
82
+
83
+ let(:java_host_cert) { load_java_cert(File.expand_path("../../ssl/host.crt", __FILE__)) }
84
+ let(:java_root_cert) { load_java_cert(File.expand_path("../../ssl/root-ca.crt", __FILE__)) }
85
+ let(:java_chain) { [java_host_cert, java_root_cert].to_java(java.security.cert.X509Certificate) }
86
+ let(:java_type) { java.lang.String.new("my_type".to_java_bytes) }
87
+
88
+ subject(:java_trust_strategy) { custom_trust_strategy.to_java(org.apache.http.conn.ssl.TrustStrategy) }
89
+
90
+ context 'when called with Java Certs and a Java String' do
91
+ let(:trust_strategy_proc) { ->(chain,type) { true } }
92
+ it 'yields an enum of equivalent Ruby certs and an equivalent Ruby String' do
93
+ expect(trust_strategy_proc).to receive(:call) do |chain, type|
94
+ expect(chain.to_a.length).to eq java_chain.length
95
+ chain.each_with_index do |cert, idx|
96
+ expect(cert).to be_a_kind_of OpenSSL::X509::Certificate
97
+ expect(cert.to_der).to eq String.from_java_bytes(java_chain[idx].encoded)
98
+ end
99
+ expect(type).to be_a_kind_of String
100
+ expect(type).to eq String.from_java_bytes(java_type.bytes)
101
+ end
102
+
103
+ expect(java_trust_strategy.isTrusted(java_chain, java_type)).to be true
104
+ end
105
+ end
106
+
107
+ context 'when the ruby block returns false' do
108
+ let(:trust_strategy_proc) { ->(chain,type) { false } }
109
+ it 'returns false' do
110
+ expect(java_trust_strategy.isTrusted(java_chain, java_type)).to be false
111
+ end
112
+ end
113
+
114
+ context 'when the ruby block returns true' do
115
+ let(:trust_strategy_proc) { ->(chain,type) { true } }
116
+ it 'returns true' do
117
+ expect(java_trust_strategy.isTrusted(java_chain, java_type)).to be true
118
+ end
119
+ end
120
+
121
+ context 'when the ruby block raises an exception' do
122
+ let(:trust_strategy_proc) { ->(chain, type) { fail(OpenSSL::X509::CertificateError, 'intentional') } }
123
+ it 'throws a CertificateException' do
124
+ expect {
125
+ java_trust_strategy.isTrusted(java_chain, java_type)
126
+ }.to raise_exception(java.security.cert.CertificateException)
127
+ end
128
+ end
129
+ end
130
+ end
131
+
132
+ describe Manticore::Client::CombinedTrustStrategy do
133
+ let(:always_trust_strategy) { ->(chain,type) { true } }
134
+ let(:never_trust_strategy) { ->(chain,type) { false } }
135
+
136
+ subject(:combined_trust_strategy) { Manticore::Client::TrustStrategies.combine(left_hand_strategy, right_hand_strategy) }
137
+
138
+ context 'when left-hand strategy trusts' do
139
+ let(:left_hand_strategy) { always_trust_strategy }
140
+ context 'when right-hand strategy trusts' do
141
+ let(:right_hand_strategy) { always_trust_strategy }
142
+ it 'trusts' do
143
+ expect(combined_trust_strategy.trusted?([],'ignored')).to be true
144
+ end
145
+ end
146
+ context 'when right-hand strategy does not trust' do
147
+ let(:right_hand_strategy) { never_trust_strategy }
148
+ it 'trusts' do
149
+ expect(combined_trust_strategy.trusted?([],'ignored')).to be true
150
+ end
151
+ end
152
+ end
153
+ context 'when left-hand strategy does not trust' do
154
+ let(:left_hand_strategy) { never_trust_strategy }
155
+ context 'when right-hand strategy trusts' do
156
+ let(:right_hand_strategy) { always_trust_strategy }
157
+ it 'trusts' do
158
+ expect(combined_trust_strategy.trusted?([],'ignored')).to be true
159
+ end
160
+ end
161
+ context 'when right-hand strategy does not trust' do
162
+ let(:right_hand_strategy) { never_trust_strategy }
163
+ it 'does not trust' do
164
+ expect(combined_trust_strategy.trusted?([],'ignored')).to be false
165
+ end
166
+ end
167
+ end
168
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: manticore
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.8.0
4
+ version: 0.9.0
5
5
  platform: java
6
6
  authors:
7
7
  - Chris Heald
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-01-18 00:00:00.000000000 Z
11
+ date: 2022-06-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  requirement: !ruby/object:Gem::Requirement
@@ -24,34 +24,6 @@ dependencies:
24
24
  - - ">="
25
25
  - !ruby/object:Gem::Version
26
26
  version: '0'
27
- - !ruby/object:Gem::Dependency
28
- requirement: !ruby/object:Gem::Requirement
29
- requirements:
30
- - - ">="
31
- - !ruby/object:Gem::Version
32
- version: '0'
33
- name: bundler
34
- prerelease: false
35
- type: :development
36
- version_requirements: !ruby/object:Gem::Requirement
37
- requirements:
38
- - - ">="
39
- - !ruby/object:Gem::Version
40
- version: '0'
41
- - !ruby/object:Gem::Dependency
42
- requirement: !ruby/object:Gem::Requirement
43
- requirements:
44
- - - ">="
45
- - !ruby/object:Gem::Version
46
- version: '0'
47
- name: rake
48
- prerelease: false
49
- type: :development
50
- version_requirements: !ruby/object:Gem::Requirement
51
- requirements:
52
- - - ">="
53
- - !ruby/object:Gem::Version
54
- version: '0'
55
27
  - !ruby/object:Gem::Dependency
56
28
  requirement: !ruby/object:Gem::Requirement
57
29
  requirements:
@@ -92,6 +64,7 @@ files:
92
64
  - lib/manticore.rb
93
65
  - lib/manticore/client.rb
94
66
  - lib/manticore/client/proxies.rb
67
+ - lib/manticore/client/trust_strategies.rb
95
68
  - lib/manticore/cookie.rb
96
69
  - lib/manticore/facade.rb
97
70
  - lib/manticore/java_extensions.rb
@@ -106,6 +79,7 @@ files:
106
79
  - manticore.gemspec
107
80
  - spec/manticore/client_proxy_spec.rb
108
81
  - spec/manticore/client_spec.rb
82
+ - spec/manticore/client_trust_strategies_spec.rb
109
83
  - spec/manticore/cookie_spec.rb
110
84
  - spec/manticore/facade_spec.rb
111
85
  - spec/manticore/response_spec.rb
@@ -136,13 +110,14 @@ requirements:
136
110
  - jar commons-logging:commons-logging, '~> 1.2'
137
111
  - jar commons-codec:commons-codec, '~> 1.9'
138
112
  - jar org.apache.httpcomponents:httpcore, '~> 4.4.14'
139
- rubygems_version: 3.1.6
113
+ rubygems_version: 3.2.29
140
114
  signing_key:
141
115
  specification_version: 4
142
116
  summary: Manticore is an HTTP client built on the Apache HttpCore components
143
117
  test_files:
144
118
  - spec/manticore/client_proxy_spec.rb
145
119
  - spec/manticore/client_spec.rb
120
+ - spec/manticore/client_trust_strategies_spec.rb
146
121
  - spec/manticore/cookie_spec.rb
147
122
  - spec/manticore/facade_spec.rb
148
123
  - spec/manticore/response_spec.rb