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 +4 -4
- data/.travis.yml +6 -6
- data/CHANGELOG.md +8 -0
- data/Gemfile +9 -7
- data/lib/manticore/client/trust_strategies.rb +119 -0
- data/lib/manticore/client.rb +93 -39
- data/lib/manticore/version.rb +1 -1
- data/lib/manticore.rb +1 -0
- data/lib/org/manticore/manticore-ext.jar +0 -0
- data/manticore.gemspec +0 -2
- data/spec/manticore/client_spec.rb +55 -14
- data/spec/manticore/client_trust_strategies_spec.rb +168 -0
- metadata +6 -31
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 281400646207bd8e4b34bed53610787e5b5d222c72fe5e82dd1d42d8f071f1be
|
4
|
+
data.tar.gz: 421a4a8f1c4f8e06a7d12377812da2613877877904c73d3d8375348fbb9e2103
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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 "
|
7
|
+
gem "rake-compiler", require: false
|
8
|
+
gem "simplecov"
|
9
|
+
|
8
10
|
gem "rspec", "~> 3.0"
|
9
11
|
gem "rspec-its"
|
10
|
-
|
11
|
-
gem "rack", ">= 2.1.4"
|
12
|
-
gem "
|
13
|
-
gem "
|
14
|
-
gem "
|
15
|
-
gem "
|
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
|
data/lib/manticore/client.rb
CHANGED
@@ -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
|
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 =
|
104
|
-
thread.
|
105
|
-
|
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] (:
|
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 `:
|
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
|
-
@
|
182
|
-
self.class.shutdown_on_finalize self, @
|
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
|
-
#
|
349
|
-
|
350
|
-
|
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
|
-
|
356
|
-
@executor
|
409
|
+
@executor ||= create_executor
|
357
410
|
end
|
358
411
|
|
359
|
-
|
360
|
-
|
361
|
-
|
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
|
-
@
|
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
|
-
|
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
|
424
|
-
|
425
|
-
|
426
|
-
|
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
|
-
|
664
|
+
trust_strategy = nil
|
614
665
|
|
615
|
-
case ssl_options.fetch(:verify, :
|
616
|
-
when
|
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
|
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 (:
|
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.
|
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
|
758
|
+
store.load(instream, options.fetch(:"#{prefix}_password", nil).to_java&.toCharArray)
|
705
759
|
end
|
706
760
|
end
|
707
761
|
|
data/lib/manticore/version.rb
CHANGED
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(:
|
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 {
|
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: :
|
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: :
|
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.
|
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
|
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.
|
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-
|
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.
|
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
|