manticore 0.7.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 +10 -8
- data/CHANGELOG.md +23 -1
- data/Gemfile +9 -7
- data/README.md +18 -5
- data/Rakefile +2 -0
- data/lib/commons-codec/commons-codec/1.15/commons-codec-1.15.jar +0 -0
- data/lib/manticore/client/trust_strategies.rb +119 -0
- data/lib/manticore/client.rb +115 -50
- data/lib/manticore/response.rb +12 -12
- data/lib/manticore/version.rb +1 -1
- data/lib/manticore.rb +27 -2
- data/lib/manticore_jars.rb +16 -6
- data/lib/org/apache/httpcomponents/httpclient/4.5.13/httpclient-4.5.13.jar +0 -0
- data/lib/org/apache/httpcomponents/httpcore/4.4.14/httpcore-4.4.14.jar +0 -0
- data/lib/org/apache/httpcomponents/httpmime/4.5.13/httpmime-4.5.13.jar +0 -0
- data/lib/org/manticore/manticore-ext.jar +0 -0
- data/manticore.gemspec +5 -5
- data/spec/manticore/client_spec.rb +73 -16
- data/spec/manticore/client_trust_strategies_spec.rb +168 -0
- data/spec/manticore/response_spec.rb +1 -1
- data/spec/spec_helper.rb +1 -0
- metadata +16 -41
- data/lib/commons-codec/commons-codec/1.10/commons-codec-1.10.jar +0 -0
- data/lib/org/apache/httpcomponents/httpclient/4.5.2/httpclient-4.5.2.jar +0 -0
- data/lib/org/apache/httpcomponents/httpcore/4.4.4/httpcore-4.4.4.jar +0 -0
- data/lib/org/apache/httpcomponents/httpmime/4.5.2/httpmime-4.5.2.jar +0 -0
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
|
@@ -1,20 +1,22 @@
|
|
|
1
|
-
dist:
|
|
1
|
+
dist: trusty # due Oracle JDK
|
|
2
2
|
language: ruby
|
|
3
3
|
cache:
|
|
4
4
|
- bundler
|
|
5
5
|
- directories:
|
|
6
6
|
- $HOME/.m2
|
|
7
|
-
rvm:
|
|
8
|
-
- jruby-9.1.17.0 # Ruby 2.3
|
|
9
|
-
- jruby-9.2.13.0 # Ruby 2.5
|
|
10
|
-
jdk:
|
|
11
|
-
- openjdk9
|
|
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
|
-
jdk:
|
|
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
|
+
- rvm: jruby-9.1.17.0 # Ruby 2.3
|
|
20
|
+
jdk: openjdk8
|
|
19
21
|
allow_failures:
|
|
20
22
|
- rvm: jruby-head
|
data/CHANGELOG.md
CHANGED
|
@@ -1,10 +1,32 @@
|
|
|
1
|
-
|
|
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
|
+
|
|
9
|
+
### v0.8.0
|
|
10
|
+
|
|
11
|
+
* [feat] restore compat with (legacy) verify: false (#102)
|
|
12
|
+
* Accept untrusted certs when SSL verify is disabled (#100)
|
|
13
|
+
* [deps] update http-client to 4.5.13 (#99)
|
|
14
|
+
|
|
15
|
+
## v0.7
|
|
16
|
+
|
|
17
|
+
### v0.7.1
|
|
18
|
+
|
|
19
|
+
* Don't override certificates with same Subject (#93)
|
|
20
|
+
* Set Java cause for ManticoreException types (#96)
|
|
21
|
+
* Fix SSL handshake hang indefinitely (#98)
|
|
2
22
|
|
|
3
23
|
### v0.7.0
|
|
4
24
|
|
|
5
25
|
* Drop support for JRuby 1.7. It probably still works, but we don't test against it anymore
|
|
6
26
|
* Fix a thread safety issue with regards to adding requests to the parallel execution queue while the client is already processing a queue (#80)
|
|
7
27
|
|
|
28
|
+
## v0.6
|
|
29
|
+
|
|
8
30
|
### v0.6.4
|
|
9
31
|
|
|
10
32
|
* client_cert and client_key now take the literal keys as strings, OpenSSL::X509::Certificate/OpenSSL::PKey::Pkey instances, or key file paths. (#77)
|
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
|
data/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
**Note**: While I'll continue to maintain the library here, I've moved the canonical copy to Gitlab at https://gitlab.com/cheald/manticore - it is preferred that you submit issues and PRs there.
|
|
4
4
|
|
|
5
|
-
[](https://app.travis-ci.com/cheald/manticore)
|
|
6
6
|
|
|
7
7
|
Manticore is a fast, robust HTTP client built on the Apache HTTPClient libraries. It is only compatible with JRuby.
|
|
8
8
|
|
|
@@ -90,7 +90,11 @@ For detailed documentation, see the [full Manticore::Client documentation](http:
|
|
|
90
90
|
Rather than using the Facade, you can create your own standalone Client instances. When you create a `Client`, you will pass various parameters that it will use to set up the pool.
|
|
91
91
|
|
|
92
92
|
```ruby
|
|
93
|
-
client = Manticore::Client.new(request_timeout: 5,
|
|
93
|
+
client = Manticore::Client.new(request_timeout: 5,
|
|
94
|
+
connect_timeout: 5,
|
|
95
|
+
socket_timeout: 5,
|
|
96
|
+
pool_max: 10,
|
|
97
|
+
pool_max_per_route: 2)
|
|
94
98
|
```
|
|
95
99
|
|
|
96
100
|
Then, you can make requests from the client. Pooling and route maximum constraints are automatically managed:
|
|
@@ -122,11 +126,20 @@ per-route concurrency limits, and other neat things. In general, you should crea
|
|
|
122
126
|
To set this up, you might create 2 pools, each configured for the task:
|
|
123
127
|
|
|
124
128
|
```ruby
|
|
125
|
-
general_http_client = Manticore::Client.new
|
|
129
|
+
general_http_client = Manticore::Client.new(connect_timeout: 10,
|
|
130
|
+
socket_timeout: 10,
|
|
131
|
+
request_timeout: 10,
|
|
132
|
+
follow_redirects: true,
|
|
133
|
+
max_per_route: 2)
|
|
126
134
|
# With an OpenSSL CA store
|
|
127
|
-
proxied_backend_client = Manticore::Client.new
|
|
135
|
+
proxied_backend_client = Manticore::Client.new(proxy: "https://backend.internal:4242",
|
|
136
|
+
ssl: { ca_file: "my_certs.pem" })
|
|
128
137
|
# Or with a .jks truststore
|
|
129
|
-
|
|
138
|
+
proxied_backend_client = Manticore::Client.new(proxy: "https://backend.internal:4242",
|
|
139
|
+
ssl: {
|
|
140
|
+
truststore: "./truststore.jks",
|
|
141
|
+
truststore_password: "s3cr3t"
|
|
142
|
+
})
|
|
130
143
|
```
|
|
131
144
|
|
|
132
145
|
This would create 2 separate request pools; the first would be configured with generous timeouts and redirect following, and would use the system
|
data/Rakefile
CHANGED
|
@@ -36,6 +36,7 @@ task :generate_certs do
|
|
|
36
36
|
# Create the CA
|
|
37
37
|
"#{openssl} genrsa 4096 | #{openssl} pkcs8 -topk8 -nocrypt -out #{root}/root-ca.key",
|
|
38
38
|
"#{openssl} req -sha256 -x509 -newkey rsa:4096 -nodes -key #{root}/root-ca.key -sha256 -days 365 -out #{root}/root-ca.crt -subj \"/C=US/ST=The Internet/L=The Internet/O=Manticore CA/OU=Manticore/CN=localhost\"",
|
|
39
|
+
"#{openssl} req -sha256 -x509 -newkey rsa:4096 -nodes -key #{root}/root-ca.key -sha256 -days 365 -out #{root}/root-untrusted-ca.crt -subj \"/C=US/ST=The Darknet/L=The Darknet/O=Manticore CA/OU=Manticore/CN=localhost\"",
|
|
39
40
|
|
|
40
41
|
# Create the client CSR, key, and signed cert
|
|
41
42
|
"#{openssl} genrsa 4096 | #{openssl} pkcs8 -topk8 -nocrypt -out #{root}/client.key",
|
|
@@ -48,6 +49,7 @@ task :generate_certs do
|
|
|
48
49
|
"#{openssl} req -sha256 -key #{root}/host.key -newkey rsa:4096 -out #{root}/host.csr -subj \"/C=US/ST=The Internet/L=The Internet/O=Manticore Host/OU=Manticore/CN=localhost\"",
|
|
49
50
|
"#{openssl} x509 -req -in #{root}/host.csr -CA #{root}/root-ca.crt -CAkey #{root}/root-ca.key -CAcreateserial -out #{root}/host.crt -sha256 -days 1",
|
|
50
51
|
"#{openssl} x509 -req -in #{root}/host.csr -CA #{root}/root-ca.crt -CAkey #{root}/root-ca.key -CAcreateserial -out #{root}/host-expired.crt -sha256 -days -7",
|
|
52
|
+
"#{openssl} x509 -req -in #{root}/host.csr -CA #{root}/root-untrusted-ca.crt -CAkey #{root}/root-ca.key -CAcreateserial -out #{root}/host-untrusted.crt -sha256 -days 1",
|
|
51
53
|
|
|
52
54
|
"#{keytool} -import -file #{root}/root-ca.crt -alias rootCA -keystore #{root}/truststore.jks -noprompt -storepass test123",
|
|
53
55
|
"#{openssl} pkcs12 -export -clcerts -out #{root}/client.p12 -inkey #{root}/client.key -in #{root}/client.crt -certfile #{root}/root-ca.crt -password pass:test123",
|
|
Binary file
|
|
@@ -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
|
@@ -69,10 +69,8 @@ module Manticore
|
|
|
69
69
|
include_package "org.apache.http.client.config"
|
|
70
70
|
include_package "org.apache.http.config"
|
|
71
71
|
include_package "org.apache.http.conn.socket"
|
|
72
|
-
include_package "org.apache.http.impl"
|
|
73
72
|
include_package "org.apache.http.impl.client"
|
|
74
73
|
include_package "org.apache.http.impl.conn"
|
|
75
|
-
include_package "org.apache.http.impl.auth"
|
|
76
74
|
include_package "org.apache.http.entity"
|
|
77
75
|
include_package "org.apache.http.message"
|
|
78
76
|
include_package "org.apache.http.params"
|
|
@@ -80,28 +78,46 @@ module Manticore
|
|
|
80
78
|
include_package "org.apache.http.auth"
|
|
81
79
|
include_package "java.util.concurrent"
|
|
82
80
|
include_package "org.apache.http.client.protocol"
|
|
83
|
-
include_package "org.apache.http.conn.ssl"
|
|
84
81
|
include_package "java.security.cert"
|
|
85
82
|
include_package "java.security.spec"
|
|
86
83
|
include_package "java.security"
|
|
87
|
-
include_package "org.apache.http.client.utils"
|
|
88
84
|
java_import "org.apache.http.HttpHost"
|
|
89
85
|
java_import "javax.net.ssl.SSLContext"
|
|
90
86
|
java_import "org.manticore.HttpGetWithEntity"
|
|
91
87
|
java_import "org.manticore.HttpDeleteWithEntity"
|
|
92
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"
|
|
91
|
+
java_import "org.apache.http.conn.ssl.SSLConnectionSocketFactory"
|
|
92
|
+
java_import "org.apache.http.conn.ssl.TrustAllStrategy"
|
|
93
|
+
java_import "org.apache.http.conn.ssl.TrustSelfSignedStrategy"
|
|
94
|
+
java_import "org.apache.http.client.utils.URIBuilder"
|
|
95
|
+
java_import "org.apache.http.impl.DefaultConnectionReuseStrategy"
|
|
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"
|
|
93
99
|
|
|
94
100
|
# This is a class rather than a proc because the proc holds a closure around
|
|
95
101
|
# the instance of the Client that creates it.
|
|
96
102
|
class ExecutorThreadFactory
|
|
97
|
-
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
|
|
98
113
|
|
|
99
114
|
def newThread(runnable)
|
|
100
|
-
thread =
|
|
101
|
-
thread.
|
|
102
|
-
|
|
115
|
+
thread = Thread.new(runnable, @name_prefix + @thread_no.increment_and_get.to_s)
|
|
116
|
+
thread.setDaemon(true)
|
|
117
|
+
thread
|
|
103
118
|
end
|
|
104
119
|
end
|
|
120
|
+
private_constant :ExecutorThreadFactory
|
|
105
121
|
|
|
106
122
|
include ProxiesInterface
|
|
107
123
|
|
|
@@ -159,10 +175,12 @@ module Manticore
|
|
|
159
175
|
# @option options [Hash] ssl Hash of options for configuring SSL
|
|
160
176
|
# @option options [Array<String>] ssl[:protocols] (nil) A list of protocols that Manticore should accept
|
|
161
177
|
# @option options [Array<String>] ssl[:cipher_suites] (nil) A list of cipher suites that Manticore should accept
|
|
162
|
-
# @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
|
|
163
179
|
# cause Manticore to accept a certificate for *.foo.com for all subdomains and sub-subdomains (eg a.b.foo.com).
|
|
164
|
-
# The default `:
|
|
180
|
+
# The default `:default` is like `:browser` but more strict - only accepts a single level of subdomains for wildcards,
|
|
165
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
|
|
166
184
|
# @option options [String] ssl[:truststore] (nil) Path to a custom trust store to use the verifying SSL connections
|
|
167
185
|
# @option options [String] ssl[:truststore_password] (nil) Password used for decrypting the server trust store
|
|
168
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.
|
|
@@ -175,8 +193,8 @@ module Manticore
|
|
|
175
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
|
|
176
194
|
# can't be shared across those threads. This should generally be left off unless you know what you're doing.
|
|
177
195
|
def initialize(options = {})
|
|
178
|
-
@
|
|
179
|
-
self.class.shutdown_on_finalize self, @
|
|
196
|
+
@finalizer = Finalizer.new
|
|
197
|
+
self.class.shutdown_on_finalize self, @finalizer
|
|
180
198
|
|
|
181
199
|
builder = client_builder
|
|
182
200
|
builder.set_user_agent options.fetch(:user_agent, "Manticore #{VERSION}")
|
|
@@ -201,11 +219,7 @@ module Manticore
|
|
|
201
219
|
builder.set_connection_reuse_strategy DefaultConnectionReuseStrategy.new
|
|
202
220
|
end
|
|
203
221
|
|
|
204
|
-
|
|
205
|
-
socket_config_builder.set_so_timeout(options.fetch(:socket_timeout, DEFAULT_SOCKET_TIMEOUT) * 1000)
|
|
206
|
-
socket_config_builder.set_tcp_no_delay(options.fetch(:tcp_no_delay, true))
|
|
207
|
-
builder.set_default_socket_config socket_config_builder.build
|
|
208
|
-
|
|
222
|
+
builder.set_default_socket_config socket_config_from_options(options)
|
|
209
223
|
builder.set_connection_manager pool(options)
|
|
210
224
|
|
|
211
225
|
request_config = RequestConfig.custom
|
|
@@ -346,21 +360,58 @@ module Manticore
|
|
|
346
360
|
end
|
|
347
361
|
end
|
|
348
362
|
|
|
349
|
-
#
|
|
350
|
-
|
|
351
|
-
|
|
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
|
|
387
|
+
end
|
|
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
|
+
|
|
352
404
|
end
|
|
405
|
+
private_constant :Finalizer
|
|
353
406
|
|
|
354
407
|
# Get at the underlying ExecutorService used to invoke asynchronous calls.
|
|
355
408
|
def executor
|
|
356
|
-
|
|
357
|
-
@executor
|
|
409
|
+
@executor ||= create_executor
|
|
358
410
|
end
|
|
359
411
|
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
}
|
|
412
|
+
# @private
|
|
413
|
+
def self.shutdown_on_finalize(client, finalizer)
|
|
414
|
+
ObjectSpace.define_finalizer(client, finalizer)
|
|
364
415
|
end
|
|
365
416
|
|
|
366
417
|
protected
|
|
@@ -368,8 +419,9 @@ module Manticore
|
|
|
368
419
|
# Takes an object and a message to pass to the object to destroy it. This is done rather than
|
|
369
420
|
# a proc to avoid creating a closure that would maintain a reference to this client, which
|
|
370
421
|
# would prevent the client from being cleaned up.
|
|
422
|
+
# @private
|
|
371
423
|
def finalize(object, args)
|
|
372
|
-
@
|
|
424
|
+
@finalizer.push(object, args)
|
|
373
425
|
end
|
|
374
426
|
|
|
375
427
|
def url_as_regex(url)
|
|
@@ -389,7 +441,7 @@ module Manticore
|
|
|
389
441
|
|
|
390
442
|
# :nocov:
|
|
391
443
|
if options[:ignore_ssl_validation]
|
|
392
|
-
|
|
444
|
+
warn "The options[:ignore_ssl_validation] setting is deprecated in favor of options[:ssl][:verify]"
|
|
393
445
|
options[:ssl] ||= {}
|
|
394
446
|
options[:ssl] = {:verify => !options.delete(:ignore_ssl_validation)}.merge(options[:ssl])
|
|
395
447
|
end
|
|
@@ -407,22 +459,30 @@ module Manticore
|
|
|
407
459
|
cm.set_validate_after_inactivity options.fetch(:check_connection_timeout, 2_000)
|
|
408
460
|
cm.set_default_max_per_route options.fetch(:pool_max_per_route, @max_pool_size)
|
|
409
461
|
cm.set_max_total @max_pool_size
|
|
462
|
+
cm.set_default_socket_config socket_config_from_options(options)
|
|
463
|
+
|
|
410
464
|
finalize cm, :shutdown
|
|
411
465
|
end
|
|
412
466
|
end
|
|
413
467
|
end
|
|
468
|
+
|
|
469
|
+
def socket_config_from_options(options)
|
|
470
|
+
socket_config_builder = SocketConfig.custom
|
|
471
|
+
socket_config_builder.set_so_timeout(options.fetch(:socket_timeout, DEFAULT_SOCKET_TIMEOUT) * 1000)
|
|
472
|
+
socket_config_builder.set_tcp_no_delay(options.fetch(:tcp_no_delay, true))
|
|
473
|
+
socket_config_builder.build
|
|
474
|
+
end
|
|
414
475
|
|
|
415
|
-
def
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
476
|
+
def create_executor
|
|
477
|
+
executor = Executors.new_cached_thread_pool(ExecutorThreadFactory.new(self))
|
|
478
|
+
finalize executor, :shutdown
|
|
479
|
+
executor
|
|
419
480
|
end
|
|
420
481
|
|
|
421
482
|
def request(klass, url, options, &block)
|
|
422
483
|
req, context = request_from_options(klass, url, options)
|
|
423
484
|
async = options.delete(:async)
|
|
424
485
|
background = options.delete(:async_background)
|
|
425
|
-
create_executor_if_needed if (background || async)
|
|
426
486
|
response = response_object_for(req, context, &block)
|
|
427
487
|
|
|
428
488
|
if async
|
|
@@ -510,14 +570,13 @@ module Manticore
|
|
|
510
570
|
|
|
511
571
|
if @use_cookies == :per_request
|
|
512
572
|
store = BasicCookieStore.new
|
|
513
|
-
context.setAttribute(ClientContext
|
|
573
|
+
context.setAttribute(ClientContext::COOKIE_STORE, store)
|
|
514
574
|
end
|
|
515
575
|
|
|
516
576
|
return req, context
|
|
517
577
|
end
|
|
518
578
|
|
|
519
579
|
def get_proxy_host(opt)
|
|
520
|
-
host = nil
|
|
521
580
|
if opt.is_a? String
|
|
522
581
|
uri = URI.parse(opt)
|
|
523
582
|
if uri.host
|
|
@@ -602,23 +661,30 @@ module Manticore
|
|
|
602
661
|
|
|
603
662
|
# Configure the SSL Context
|
|
604
663
|
def ssl_socket_factory_from_options(ssl_options)
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
664
|
+
trust_strategy = nil
|
|
665
|
+
|
|
666
|
+
case ssl_options.fetch(:verify, :default)
|
|
667
|
+
when :none, :disable
|
|
668
|
+
trust_strategy = TrustAllStrategy::INSTANCE
|
|
669
|
+
verifier = NoopHostnameVerifier::INSTANCE
|
|
670
|
+
when false # compatibility
|
|
671
|
+
trust_strategy = TrustSelfSignedStrategy::INSTANCE
|
|
612
672
|
verifier = SSLConnectionSocketFactory::ALLOW_ALL_HOSTNAME_VERIFIER
|
|
613
673
|
when :browser
|
|
614
674
|
verifier = SSLConnectionSocketFactory::BROWSER_COMPATIBLE_HOSTNAME_VERIFIER
|
|
615
|
-
when
|
|
675
|
+
when :default, true
|
|
676
|
+
verifier = DefaultHostnameVerifier.new
|
|
677
|
+
when :strict # compatibility
|
|
616
678
|
verifier = SSLConnectionSocketFactory::STRICT_HOSTNAME_VERIFIER
|
|
617
679
|
else
|
|
618
|
-
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))
|
|
619
685
|
end
|
|
620
686
|
|
|
621
|
-
context =
|
|
687
|
+
context = SSLContextBuilder.new
|
|
622
688
|
setup_trust_store ssl_options, context, trust_strategy
|
|
623
689
|
setup_key_store ssl_options, context
|
|
624
690
|
|
|
@@ -633,13 +699,13 @@ module Manticore
|
|
|
633
699
|
trust_store ||= blank_keystore
|
|
634
700
|
open(ssl_options[:ca_file]) do |fp|
|
|
635
701
|
cert_collection = CertificateFactory.get_instance("X509").generate_certificates(fp.to_inputstream).to_a
|
|
636
|
-
cert_collection.
|
|
637
|
-
trust_store.set_certificate_entry(cert.getSubjectX500Principal.name, cert)
|
|
702
|
+
cert_collection.each_with_index do |cert, i|
|
|
703
|
+
trust_store.set_certificate_entry("#{i}#" + cert.getSubjectX500Principal.name, cert)
|
|
638
704
|
end
|
|
639
705
|
end
|
|
640
706
|
end
|
|
641
707
|
|
|
642
|
-
context.
|
|
708
|
+
context.java_send :loadTrustMaterial, [KeyStore, TrustStrategy], trust_store, trust_strategy
|
|
643
709
|
end
|
|
644
710
|
|
|
645
711
|
KEY_EXTRACTION_REGEXP = /(?:^-----BEGIN(.* )PRIVATE KEY-----\n)(.*?)(?:-----END\1PRIVATE KEY.*$)/m
|
|
@@ -651,7 +717,6 @@ module Manticore
|
|
|
651
717
|
# Support OpenSSL-style bare X.509 certs with an RSA key
|
|
652
718
|
if ssl_options[:client_cert] && ssl_options[:client_key]
|
|
653
719
|
key_store ||= blank_keystore
|
|
654
|
-
certs, key = nil, nil
|
|
655
720
|
|
|
656
721
|
cert_str = if ssl_options[:client_cert].is_a?(OpenSSL::X509::Certificate)
|
|
657
722
|
ssl_options[:client_cert].to_s
|
|
@@ -690,7 +755,7 @@ module Manticore
|
|
|
690
755
|
def get_store(prefix, options)
|
|
691
756
|
KeyStore.get_instance(options[:"#{prefix}_type"] || guess_store_type(options[prefix])).tap do |store|
|
|
692
757
|
instream = open(options[prefix], "rb").to_inputstream
|
|
693
|
-
store.load(instream, options.fetch(:"#{prefix}_password", nil).to_java
|
|
758
|
+
store.load(instream, options.fetch(:"#{prefix}_password", nil).to_java&.toCharArray)
|
|
694
759
|
end
|
|
695
760
|
end
|
|
696
761
|
|
data/lib/manticore/response.rb
CHANGED
|
@@ -11,14 +11,13 @@ module Manticore
|
|
|
11
11
|
# @!attribute [r] callback_result
|
|
12
12
|
# @return Value returned from any given on_success/response block
|
|
13
13
|
class Response
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
include_package "org.apache.http.protocol"
|
|
14
|
+
|
|
15
|
+
java_import "org.apache.http.client.ResponseHandler"
|
|
17
16
|
java_import "org.apache.http.client.protocol.HttpClientContext"
|
|
18
|
-
java_import "
|
|
17
|
+
java_import "org.apache.http.protocol.ExecutionContext"
|
|
19
18
|
|
|
20
|
-
include ResponseHandler
|
|
21
|
-
include Callable
|
|
19
|
+
include org.apache.http.client.ResponseHandler
|
|
20
|
+
include java.util.concurrent.Callable
|
|
22
21
|
|
|
23
22
|
attr_accessor :background
|
|
24
23
|
attr_reader :context, :request, :callback_result, :called, :future
|
|
@@ -54,15 +53,16 @@ module Manticore
|
|
|
54
53
|
ex = Manticore::ConnectTimeout
|
|
55
54
|
rescue Java::JavaNet::SocketException => e
|
|
56
55
|
ex = Manticore::SocketException
|
|
57
|
-
rescue Java::OrgApacheHttpClient::ClientProtocolException, Java::JavaxNetSsl::SSLHandshakeException,
|
|
58
|
-
Java::
|
|
56
|
+
rescue Java::OrgApacheHttpClient::ClientProtocolException, Java::JavaxNetSsl::SSLHandshakeException,
|
|
57
|
+
Java::OrgApacheHttpConn::HttpHostConnectException, Java::OrgApacheHttp::NoHttpResponseException,
|
|
58
|
+
Java::OrgApacheHttp::ConnectionClosedException => e
|
|
59
59
|
ex = Manticore::ClientProtocolException
|
|
60
60
|
rescue Java::JavaNet::UnknownHostException => e
|
|
61
61
|
ex = Manticore::ResolutionFailure
|
|
62
62
|
rescue Java::JavaLang::IllegalArgumentException => e
|
|
63
63
|
ex = Manticore::InvalidArgumentException
|
|
64
64
|
rescue Java::JavaLang::IllegalStateException => e
|
|
65
|
-
if e.message.
|
|
65
|
+
if (e.message || '').index('Connection pool shut down')
|
|
66
66
|
ex = Manticore::ClientStoppedException
|
|
67
67
|
else
|
|
68
68
|
@exception = e
|
|
@@ -75,7 +75,7 @@ module Manticore
|
|
|
75
75
|
|
|
76
76
|
# TODO: If calling async, execute_complete may fail and then silently swallow exceptions. How do we fix that?
|
|
77
77
|
if ex || @exception
|
|
78
|
-
@exception ||= ex.new(e
|
|
78
|
+
@exception ||= ex.new(e)
|
|
79
79
|
@handlers[:failure].call @exception
|
|
80
80
|
execute_complete
|
|
81
81
|
nil
|
|
@@ -90,8 +90,8 @@ module Manticore
|
|
|
90
90
|
# @return [String]
|
|
91
91
|
def final_url
|
|
92
92
|
call_once
|
|
93
|
-
last_request = context.get_attribute ExecutionContext
|
|
94
|
-
last_host = context.get_attribute ExecutionContext
|
|
93
|
+
last_request = context.get_attribute ExecutionContext::HTTP_REQUEST
|
|
94
|
+
last_host = context.get_attribute ExecutionContext::HTTP_TARGET_HOST
|
|
95
95
|
host = last_host.to_uri
|
|
96
96
|
url = last_request.get_uri
|
|
97
97
|
URI.join(host, url.to_s)
|