httpx 0.19.3 → 0.19.4

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 431fcabfda42f5d6010d903a15c6a83357123c9eef3528a769755c8639f1ec61
4
- data.tar.gz: 52512337b7b2081a4ab123f7261dee14adb97da019fc9244b9ddf8e7de53c904
3
+ metadata.gz: c1c8ae2cc46f5a4033ea5c776bef1a7703cfc27834818cded1487101b8a6bbbe
4
+ data.tar.gz: ea445112ce42d2d81c4465f646687d605da855e3248838d26075388463250d9b
5
5
  SHA512:
6
- metadata.gz: 7777af904a2e5a8f6f34984a69cb022c853483b6634dd631a3fbe92c9005fadb3637a21a214ff1e14ef4729ffa70d68362b703b9ccd25e2904b820fc674dbe6b
7
- data.tar.gz: 94d92ad004319ecb3098b5e901921bfaf5d818cbb0e2e87eed8987248d1cc52d0fc70a5dc1dfe1a92faa8eae9e7074c926c0c01563002cb8067af449ae79fd51
6
+ metadata.gz: effaefc6365aa24fe86a62bca9bdbfed701dc9c19354c6bf7f046ea2bf84b94f908e1924381f2e27cfe748c7ad4e0b8d97dfa0a6d695775cec8d1b8a92760fe7
7
+ data.tar.gz: 55f846f8412acb99008cd18834ce50fe73ae072a0446d5d4fdf76fe2c48be4b2a863721b18191ca406a60b6883a8e35cba2f856c07ce9fa8ba808476b5b66887
@@ -0,0 +1,13 @@
1
+ # 0.19.3
2
+
3
+ ## Improvements
4
+
5
+ ### Jruby: HTTP/2 with jruby-openssl (>= 0.12.2)
6
+
7
+ The (optional) FFI-based TLS module for jruby was deleted. Besides it being cumbersome and hard to maintain, `jruby`'s own `openssl` released support for ALPN negotiation (in v0.12.2), which solves the problem the deleted module was supposed to address.
8
+
9
+ ## Bugfixes
10
+
11
+ * `webmock` integration was fixed to take the mocked URI query string into account.
12
+ * fix internal codepath where mergeable-but-not-coalescable connections were still triggering the coalesce branch.
13
+ * fixed after-use mutation of connection addresses array which was making it empty after initial usage.
@@ -19,6 +19,7 @@ module WebMock
19
19
  class << self
20
20
  def build_webmock_request_signature(request)
21
21
  uri = WebMock::Util::URI.heuristic_parse(request.uri)
22
+ uri.query = request.query
22
23
  uri.path = uri.normalized_path.gsub("[^:]//", "/")
23
24
 
24
25
  WebMock::RequestSignature.new(
data/lib/httpx/io.rb CHANGED
@@ -1,9 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "socket"
4
+ require "httpx/io/udp"
4
5
  require "httpx/io/tcp"
5
6
  require "httpx/io/unix"
6
- require "httpx/io/udp"
7
+ require "httpx/io/ssl"
7
8
 
8
9
  module HTTPX
9
10
  module IO
@@ -11,20 +12,6 @@ module HTTPX
11
12
  register "udp", UDP
12
13
  register "unix", HTTPX::UNIX
13
14
  register "tcp", TCP
14
-
15
- if RUBY_ENGINE == "jruby"
16
- begin
17
- require "httpx/io/tls"
18
- register "ssl", TLS
19
- rescue LoadError
20
- # :nocov:
21
- require "httpx/io/ssl"
22
- register "ssl", SSL
23
- # :nocov:
24
- end
25
- else
26
- require "httpx/io/ssl"
27
- register "ssl", SSL
28
- end
15
+ register "ssl", SSL
29
16
  end
30
17
  end
data/lib/httpx/pool.rb CHANGED
@@ -133,7 +133,7 @@ module HTTPX
133
133
 
134
134
  if found_connection.open?
135
135
  coalesce_connections(found_connection, connection)
136
- throw(:coalesced, found_connection)
136
+ throw(:coalesced, found_connection) unless @connections.include?(connection)
137
137
  else
138
138
  found_connection.once(:open) do
139
139
  coalesce_connections(found_connection, connection)
@@ -119,7 +119,11 @@ module HTTPX
119
119
  private
120
120
 
121
121
  def calculate_interests
122
- !@write_buffer.empty? || @queries.empty? ? :w : :r
122
+ return :w unless @write_buffer.empty?
123
+
124
+ return :r unless @queries.empty?
125
+
126
+ nil
123
127
  end
124
128
 
125
129
  def consume
@@ -72,7 +72,7 @@ module HTTPX
72
72
 
73
73
  return unless addresses
74
74
 
75
- addresses.select! { |addr| addr.family == @family }
75
+ addresses = addresses.select { |addr| addr.family == @family }
76
76
 
77
77
  return if addresses.empty?
78
78
 
data/lib/httpx/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module HTTPX
4
- VERSION = "0.19.3"
4
+ VERSION = "0.19.4"
5
5
  end
@@ -21,7 +21,7 @@ module HTTPX
21
21
 
22
22
  def call: () -> void
23
23
 
24
- def interests: () -> io_interests
24
+ def interests: () -> (:r | :w | nil)
25
25
 
26
26
  def <<: (Connection) -> void
27
27
 
@@ -31,7 +31,7 @@ module HTTPX
31
31
 
32
32
  def initialize: (ip_family family, options options) -> void
33
33
 
34
- def calculate_interests: () -> (:r | :w)
34
+ def calculate_interests: () -> (:r | :w | nil)
35
35
 
36
36
  def consume: () -> void
37
37
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: httpx
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.19.3
4
+ version: 0.19.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tiago Cardoso
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-02-21 00:00:00.000000000 Z
11
+ date: 2022-03-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: http-2-next
@@ -74,6 +74,7 @@ extra_rdoc_files:
74
74
  - doc/release_notes/0_19_1.md
75
75
  - doc/release_notes/0_19_2.md
76
76
  - doc/release_notes/0_19_3.md
77
+ - doc/release_notes/0_19_4.md
77
78
  - doc/release_notes/0_1_0.md
78
79
  - doc/release_notes/0_2_0.md
79
80
  - doc/release_notes/0_2_1.md
@@ -141,6 +142,7 @@ files:
141
142
  - doc/release_notes/0_19_1.md
142
143
  - doc/release_notes/0_19_2.md
143
144
  - doc/release_notes/0_19_3.md
145
+ - doc/release_notes/0_19_4.md
144
146
  - doc/release_notes/0_1_0.md
145
147
  - doc/release_notes/0_2_0.md
146
148
  - doc/release_notes/0_2_1.md
@@ -181,10 +183,6 @@ files:
181
183
  - lib/httpx/io.rb
182
184
  - lib/httpx/io/ssl.rb
183
185
  - lib/httpx/io/tcp.rb
184
- - lib/httpx/io/tls.rb
185
- - lib/httpx/io/tls/box.rb
186
- - lib/httpx/io/tls/context.rb
187
- - lib/httpx/io/tls/ffi.rb
188
186
  - lib/httpx/io/udp.rb
189
187
  - lib/httpx/io/unix.rb
190
188
  - lib/httpx/loggable.rb
@@ -1,365 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- # Copyright (c) 2004-2013 Cotag Media
4
- #
5
- # Permission is hereby granted, free of charge, to any person obtaining a copy
6
- # of this software and associated documentation files (the "Software"), to deal
7
- # in the Software without restriction, including without limitation the rights
8
- # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- # copies of the Software, and to permit persons to whom the Software is furnished
10
- # to do so, subject to the following conditions:
11
- #
12
- # The above copyright notice and this permission notice shall be included in all
13
- # copies or substantial portions of the Software.
14
- #
15
- # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
- # THE SOFTWARE.
22
- #
23
-
24
- class HTTPX::TLS
25
- class Box
26
- InstanceLookup = ::Concurrent::Map.new
27
-
28
- READ_BUFFER = 2048
29
- SSL_VERIFY_PEER = 0x01
30
- SSL_VERIFY_CLIENT_ONCE = 0x04
31
-
32
- VerifyCB = FFI::Function.new(:int, %i[int pointer]) do |preverify_ok, x509_store|
33
- x509 = SSL.X509_STORE_CTX_get_current_cert(x509_store)
34
- ssl = SSL.X509_STORE_CTX_get_ex_data(x509_store, SSL.SSL_get_ex_data_X509_STORE_CTX_idx)
35
-
36
- bio_out = SSL.BIO_new(SSL.BIO_s_mem)
37
- ret = SSL.PEM_write_bio_X509(bio_out, x509)
38
- if ret
39
- len = SSL.BIO_pending(bio_out)
40
- buffer = FFI::MemoryPointer.new(:char, len, false)
41
- size = SSL.BIO_read(bio_out, buffer, len)
42
-
43
- # THis is the callback into the ruby class
44
- cert = buffer.read_string(size)
45
- SSL.BIO_free(bio_out)
46
- # InstanceLookup[ssl.address].verify(cert) || preverify_ok.zero? ? 1 : 0
47
- depth = SSL.X509_STORE_CTX_get_error_depth(ssl)
48
- box = InstanceLookup[ssl.address]
49
- if preverify_ok == 1
50
-
51
- hostname_verify = box.verify(cert)
52
- if hostname_verify
53
- 1
54
- else
55
- # SSL.X509_STORE_CTX_set_error(x509_store, SSL::X509_V_ERR_HOSTNAME_MISMATCH)
56
- 0
57
- end
58
- else
59
- 1
60
- end
61
- else
62
- SSL.BIO_free(bio_out)
63
- SSL.X509_STORE_CTX_set_error(x509_store, SSL::X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT)
64
- 0
65
- end
66
- end
67
-
68
- attr_reader :is_server, :context, :handshake_completed, :hosts, :ssl_version, :cipher, :verify_peer
69
-
70
- def initialize(is_server, transport, options = {})
71
- @ready = true
72
-
73
- @handshake_completed = false
74
- @handshake_signaled = false
75
- @alpn_negotiated = false
76
- @transport = transport
77
-
78
- @read_buffer = FFI::MemoryPointer.new(:char, READ_BUFFER, false)
79
-
80
- @is_server = is_server
81
- @context = Context.new(is_server, options)
82
-
83
- @bioRead = SSL.BIO_new(SSL.BIO_s_mem)
84
- @bioWrite = SSL.BIO_new(SSL.BIO_s_mem)
85
- @ssl = SSL.SSL_new(@context.ssl_ctx)
86
- SSL.SSL_set_bio(@ssl, @bioRead, @bioWrite)
87
-
88
- @write_queue = []
89
-
90
- InstanceLookup[@ssl.address] = self
91
-
92
- @verify_peer = options[:verify_peer]
93
-
94
- if @verify_peer
95
- SSL.SSL_set_verify(@ssl, SSL_VERIFY_PEER | SSL_VERIFY_CLIENT_ONCE, VerifyCB)
96
- end
97
-
98
- # Add Server Name Indication (SNI) for client connections
99
- if (hostname = options[:hostname])
100
- if is_server
101
- @hosts = ::Concurrent::Map.new
102
- @hosts[hostname.to_s] = @context
103
- @context.add_server_name_indication
104
- else
105
- SSL.SSL_set_tlsext_host_name(@ssl, hostname)
106
- end
107
- end
108
-
109
- SSL.SSL_connect(@ssl) unless is_server
110
- end
111
-
112
- def add_host(hostname:, **options)
113
- raise Error, "Server Name Indication (SNI) not configured for default host" unless @hosts
114
- raise Error, "only valid for server mode context" unless @is_server
115
-
116
- context = Context.new(true, options)
117
- @hosts[hostname.to_s] = context
118
- context.add_server_name_indication
119
- nil
120
- end
121
-
122
- # Careful with this.
123
- # If you remove all the hosts you'll end up with a segfault
124
- def remove_host(hostname)
125
- raise Error, "Server Name Indication (SNI) not configured for default host" unless @hosts
126
- raise Error, "only valid for server mode context" unless @is_server
127
-
128
- context = @hosts[hostname.to_s]
129
- if context
130
- @hosts.delete(hostname.to_s)
131
- context.cleanup
132
- end
133
- nil
134
- end
135
-
136
- def get_peer_cert
137
- return "" unless @ready
138
-
139
- SSL.SSL_get_peer_certificate(@ssl)
140
- end
141
-
142
- def start
143
- return unless @ready
144
-
145
- dispatch_cipher_text
146
- end
147
-
148
- def encrypt(data)
149
- return unless @ready
150
-
151
- wrote = put_plain_text data
152
- if wrote < 0
153
- @transport.close_cb
154
- else
155
- dispatch_cipher_text
156
- end
157
- end
158
-
159
- SSL_ERROR_WANT_READ = 2
160
- SSL_ERROR_SSL = 1
161
- def decrypt(data)
162
- return unless @ready
163
-
164
- put_cipher_text data
165
-
166
- unless SSL.is_init_finished(@ssl)
167
- resp = @is_server ? SSL.SSL_accept(@ssl) : SSL.SSL_connect(@ssl)
168
-
169
- if resp < 0
170
- err_code = SSL.SSL_get_error(@ssl, resp)
171
- if err_code != SSL_ERROR_WANT_READ
172
- if err_code == SSL_ERROR_SSL
173
- verify_msg = SSL.X509_verify_cert_error_string(SSL.SSL_get_verify_result(@ssl))
174
- @transport.close_cb(verify_msg)
175
- end
176
- return
177
- end
178
- end
179
-
180
- @handshake_completed = true
181
- @ssl_version = SSL.get_version(@ssl)
182
- @cipher = SSL.get_current_cipher(@ssl)
183
- signal_handshake unless @handshake_signaled
184
- end
185
-
186
- loop do
187
- size = get_plain_text(@read_buffer, READ_BUFFER)
188
- if size > 0
189
- @transport.dispatch_cb @read_buffer.read_string(size)
190
- else
191
- break
192
- end
193
- end
194
-
195
- dispatch_cipher_text
196
- end
197
-
198
- def signal_handshake
199
- @handshake_signaled = true
200
-
201
- # Check protocol support here
202
- if @context.alpn_set
203
- proto = alpn_negotiated_protocol
204
-
205
- if proto == :failed
206
- if @alpn_negotiated
207
- # We should shutdown if this is the case
208
- # TODO: send back proper error message
209
- @transport.close_cb
210
- return
211
- end
212
- end
213
- @transport.alpn_protocol_cb(proto)
214
- end
215
-
216
- @transport.handshake_cb
217
- end
218
-
219
- def alpn_negotiated!
220
- @alpn_negotiated = true
221
- end
222
-
223
- SSL_RECEIVED_SHUTDOWN = 2
224
- def cleanup
225
- return unless @ready
226
-
227
- @ready = false
228
-
229
- InstanceLookup.delete @ssl.address
230
-
231
- if (SSL.SSL_get_shutdown(@ssl) & SSL_RECEIVED_SHUTDOWN) != 0
232
- SSL.SSL_shutdown @ssl
233
- else
234
- SSL.SSL_clear @ssl
235
- end
236
-
237
- SSL.SSL_free @ssl
238
-
239
- if @hosts
240
- @hosts.each_value(&:cleanup)
241
- @hosts = nil
242
- else
243
- @context.cleanup
244
- end
245
- end
246
-
247
- # Called from class level callback function
248
- def verify(cert)
249
- @transport.verify_cb(cert)
250
- end
251
-
252
- def close(msg)
253
- @transport.close_cb(msg)
254
- end
255
-
256
- private
257
-
258
- def alpn_negotiated_protocol
259
- return nil unless @context.alpn_set
260
-
261
- proto = FFI::MemoryPointer.new(:pointer, 1, true)
262
- len = FFI::MemoryPointer.new(:uint, 1, true)
263
- SSL.SSL_get0_alpn_selected(@ssl, proto, len)
264
-
265
- resp = proto.get_pointer(0)
266
-
267
- return :failed if resp.address == 0
268
-
269
- length = len.get_uint(0)
270
- resp.read_string(length)
271
- end
272
-
273
- def get_plain_text(buffer, ready)
274
- # Read the buffered clear text
275
- size = SSL.SSL_read(@ssl, buffer, ready)
276
- if size >= 0
277
- size
278
- else
279
- SSL.SSL_get_error(@ssl, size) == SSL_ERROR_WANT_READ ? 0 : -1
280
- end
281
- end
282
-
283
- def pending_data(bio)
284
- SSL.BIO_pending(bio)
285
- end
286
-
287
- def get_cipher_text(buffer, length)
288
- SSL.BIO_read(@bioWrite, buffer, length)
289
- end
290
-
291
- def put_cipher_text(data)
292
- len = data.bytesize
293
- wrote = SSL.BIO_write(@bioRead, data, len)
294
- wrote == len
295
- end
296
-
297
- SSL_ERROR_WANT_WRITE = 3
298
- def put_plain_text(data)
299
- @write_queue.push(data) if data
300
- return 0 unless SSL.is_init_finished(@ssl)
301
-
302
- fatal = false
303
- did_work = false
304
-
305
- until @write_queue.empty?
306
- data = @write_queue.pop
307
- len = data.bytesize
308
-
309
- wrote = SSL.SSL_write(@ssl, data, len)
310
-
311
- if wrote > 0
312
- did_work = true
313
- else
314
- err_code = SSL.SSL_get_error(@ssl, wrote)
315
- if (err_code != SSL_ERROR_WANT_READ) && (err_code != SSL_ERROR_WANT_WRITE)
316
- fatal = true
317
- else
318
- # Not fatal - add back to the queue
319
- @write_queue.unshift data
320
- end
321
-
322
- break
323
- end
324
- end
325
-
326
- if did_work
327
- 1
328
- elsif fatal
329
- -1
330
- else
331
- 0
332
- end
333
- end
334
-
335
- CIPHER_DISPATCH_FAILED = "Cipher text dispatch failed"
336
- def dispatch_cipher_text
337
- loop do
338
- did_work = false
339
-
340
- # Get all the encrypted data and transmit it
341
- pending = pending_data(@bioWrite)
342
- if pending > 0
343
- buffer = FFI::MemoryPointer.new(:char, pending, false)
344
-
345
- resp = get_cipher_text(buffer, pending)
346
- raise Error, CIPHER_DISPATCH_FAILED unless resp > 0
347
-
348
- @transport.transmit_cb(buffer.read_string(resp))
349
- did_work = true
350
- end
351
-
352
- # Send any queued out going data
353
- unless @write_queue.empty?
354
- resp = put_plain_text nil
355
- if resp > 0
356
- did_work = true
357
- elsif resp < 0
358
- @transport.close_cb
359
- end
360
- end
361
- break unless did_work
362
- end
363
- end
364
- end
365
- end
@@ -1,199 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- # Copyright (c) 2004-2013 Cotag Media
4
- #
5
- # Permission is hereby granted, free of charge, to any person obtaining a copy
6
- # of this software and associated documentation files (the "Software"), to deal
7
- # in the Software without restriction, including without limitation the rights
8
- # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- # copies of the Software, and to permit persons to whom the Software is furnished
10
- # to do so, subject to the following conditions:
11
- #
12
- # The above copyright notice and this permission notice shall be included in all
13
- # copies or substantial portions of the Software.
14
- #
15
- # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
- # THE SOFTWARE.
22
- #
23
-
24
- class HTTPX::TLS
25
- class Context
26
-
27
- # Based on information from https://raymii.org/s/tutorials/Strong_SSL_Security_On_nginx.html
28
- CIPHERS = "EECDH+AESGCM:EDH+AESGCM:ECDHE-RSA-AES128-GCM-SHA256:AES256+EECDH:DHE-RSA-AES128-GCM-SHA256:AES256+EDH:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA:ECDHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES128-SHA256:DHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES256-GCM-SHA384:AES128-GCM-SHA256:AES256-SHA256:AES128-SHA256:AES256-SHA:AES128-SHA:DES-CBC3-SHA:HIGH:!aNULL:!eNULL:!EXPORT:!DES:!MD5:!PSK:!RC4"
29
- SESSION = "ruby-tls"
30
-
31
- ALPN_LOOKUP = ::Concurrent::Map.new
32
- ALPN_Select_CB = FFI::Function.new(:int, [
33
- # array of str, unit8 out,uint8 in, *arg
34
- :pointer, :pointer, :pointer, :string, :uint, :pointer
35
- ]) do |ssl_p, out, outlen, inp, inlen, _arg|
36
- ssl = Box::InstanceLookup[ssl_p.address]
37
- return SSL::SSL_TLSEXT_ERR_ALERT_FATAL unless ssl
38
-
39
- protos = ssl.context.alpn_str
40
- status = SSL.SSL_select_next_proto(out, outlen, protos, protos.length, inp, inlen)
41
- ssl.alpn_negotiated
42
-
43
- case status
44
- when SSL::OPENSSL_NPN_UNSUPPORTED
45
- SSL::SSL_TLSEXT_ERR_ALERT_FATAL
46
- when SSL::OPENSSL_NPN_NEGOTIATED
47
- SSL::SSL_TLSEXT_ERR_OK
48
- when SSL::OPENSSL_NPN_NO_OVERLAP
49
- SSL::SSL_TLSEXT_ERR_ALERT_WARNING
50
- end
51
- end
52
-
53
- attr_reader :is_server, :ssl_ctx, :alpn_set, :alpn_str
54
-
55
- def initialize(server, options = {})
56
- @is_server = server
57
-
58
- if @is_server
59
- @ssl_ctx = SSL.SSL_CTX_new(SSL.TLS_server_method)
60
- set_private_key(options[:private_key] || SSL::DEFAULT_PRIVATE)
61
- set_certificate(options[:cert_chain] || SSL::DEFAULT_CERT)
62
- set_client_ca(options[:client_ca])
63
- else
64
- @ssl_ctx = SSL.SSL_CTX_new(SSL.TLS_client_method)
65
- end
66
-
67
- SSL.SSL_CTX_set_options(@ssl_ctx, SSL::SSL_OP_ALL)
68
- SSL.SSL_CTX_set_mode(@ssl_ctx, SSL::SSL_MODE_RELEASE_BUFFERS)
69
-
70
- SSL.SSL_CTX_set_cipher_list(@ssl_ctx, options[:ciphers] || CIPHERS)
71
-
72
- set_min_version(options[:version])
73
-
74
- if @is_server
75
- SSL.SSL_CTX_sess_set_cache_size(@ssl_ctx, 128)
76
- SSL.SSL_CTX_set_session_id_context(@ssl_ctx, SESSION, 8)
77
- else
78
- set_private_key(options[:private_key])
79
- set_certificate(options[:cert_chain])
80
- end
81
- set_alpn_negotiation(options[:protocols])
82
- end
83
-
84
- def cleanup
85
- return unless @ssl_ctx
86
-
87
- SSL.SSL_CTX_free(@ssl_ctx)
88
- @ssl_ctx = nil
89
- end
90
-
91
- def add_server_name_indication
92
- raise Error, "only valid for server mode context" unless @is_server
93
-
94
- SSL.SSL_CTX_set_tlsext_servername_callback(@ssl_ctx, ServerNameCB)
95
- end
96
-
97
- ServerNameCB = FFI::Function.new(:int, %i[pointer pointer pointer]) do |ssl, _, _|
98
- ruby_ssl = Box::InstanceLookup[ssl.address]
99
- return SSL::SSL_TLSEXT_ERR_NOACK unless ruby_ssl
100
-
101
- ctx = ruby_ssl.hosts[SSL.SSL_get_servername(ssl, SSL::TLSEXT_NAMETYPE_host_name)]
102
- if ctx
103
- SSL.SSL_set_SSL_CTX(ssl, ctx.ssl_ctx)
104
- SSL::SSL_TLSEXT_ERR_OK
105
- else
106
- SSL::SSL_TLSEXT_ERR_ALERT_FATAL
107
- end
108
- end
109
-
110
- private
111
-
112
- def self.build_alpn_string(protos)
113
- protos.reduce("".b) do |buffer, proto|
114
- buffer << proto.bytesize
115
- buffer << proto
116
- end
117
- end
118
-
119
- # Version can be one of:
120
- # :SSL3, :TLS1, :TLS1_1, :TLS1_2, :TLS1_3, :TLS_MAX
121
- if SSL::VERSION_SUPPORTED
122
-
123
- def set_min_version(version)
124
- return unless version
125
-
126
- num = SSL.const_get("#{version}_VERSION")
127
- SSL.SSL_CTX_set_min_proto_version(@ssl_ctx, num) == 1
128
- rescue NameError
129
- raise Error, "#{version} is unsupported"
130
- end
131
-
132
- else
133
- def set_min_version(_version); end
134
- end
135
-
136
- if SSL::ALPN_SUPPORTED
137
- def set_alpn_negotiation(protocols)
138
- @alpn_set = false
139
- return unless protocols
140
-
141
- if @is_server
142
- @alpn_str = Context.build_alpn_string(protocols)
143
- SSL.SSL_CTX_set_alpn_select_cb(@ssl_ctx, ALPN_Select_CB, nil)
144
- @alpn_set = true
145
- else
146
- protocols = Context.build_alpn_string(protocols)
147
- @alpn_set = SSL.SSL_CTX_set_alpn_protos(@ssl_ctx, protocols, protocols.length) == 0
148
- end
149
- end
150
- else
151
- def set_alpn_negotiation(_protocols); end
152
- end
153
-
154
- def set_private_key(key)
155
- err = if key.is_a? FFI::Pointer
156
- SSL.SSL_CTX_use_PrivateKey(@ssl_ctx, key)
157
- elsif key && File.file?(key)
158
- SSL.SSL_CTX_use_PrivateKey_file(@ssl_ctx, key, SSL_FILETYPE_PEM)
159
- else
160
- 1
161
- end
162
-
163
- # Check for errors
164
- if err <= 0
165
- # TODO: : ERR_print_errors_fp or ERR_print_errors
166
- # So we can properly log the issue
167
- cleanup
168
- raise Error, "invalid private key or file not found"
169
- end
170
- end
171
-
172
- def set_certificate(cert)
173
- err = if cert.is_a? FFI::Pointer
174
- SSL.SSL_CTX_use_certificate(@ssl_ctx, cert)
175
- elsif cert && File.file?(cert)
176
- SSL.SSL_CTX_use_certificate_chain_file(@ssl_ctx, cert)
177
- else
178
- 1
179
- end
180
-
181
- if err <= 0
182
- cleanup
183
- raise Error, "invalid certificate or file not found"
184
- end
185
- end
186
-
187
- def set_client_ca(ca)
188
- return unless ca
189
-
190
- if File.file?(ca) && (ca_ptr = SSL.SSL_load_client_CA_file(ca))
191
- # there is no error checking provided by SSL_CTX_set_client_CA_list
192
- SSL.SSL_CTX_set_client_CA_list(@ssl_ctx, ca_ptr)
193
- else
194
- cleanup
195
- raise Error, "invalid ca certificate or file not found"
196
- end
197
- end
198
- end
199
- end
@@ -1,390 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "ffi"
4
- require "ffi-compiler/loader"
5
- require "concurrent"
6
-
7
- # Copyright (c) 2004-2013 Cotag Media
8
- #
9
- # Permission is hereby granted, free of charge, to any person obtaining a copy
10
- # of this software and associated documentation files (the "Software"), to deal
11
- # in the Software without restriction, including without limitation the rights
12
- # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13
- # copies of the Software, and to permit persons to whom the Software is furnished
14
- # to do so, subject to the following conditions:
15
- #
16
- # The above copyright notice and this permission notice shall be included in all
17
- # copies or substantial portions of the Software.
18
- #
19
- # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20
- # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21
- # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22
- # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23
- # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24
- # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
25
- # THE SOFTWARE.
26
- #
27
-
28
- module HTTPX::TLS::SSL
29
- Error = HTTPX::TLS::Error
30
-
31
- extend FFI::Library
32
-
33
- if FFI::Platform.windows?
34
- begin
35
- ffi_lib "libeay32", "ssleay32"
36
- rescue LoadError
37
- ffi_lib "libcrypto-1_1-x64", "libssl-1_1-x64"
38
- end
39
- else
40
- ffi_lib "ssl"
41
- end
42
-
43
- # Common structures
44
- typedef :pointer, :user_data
45
- typedef :pointer, :bio
46
- typedef :pointer, :evp_key
47
- typedef :pointer, :evp_key_pointer
48
- typedef :pointer, :x509
49
- typedef :pointer, :x509_pointer
50
- typedef :pointer, :ssl
51
- typedef :pointer, :cipher
52
- typedef :pointer, :ssl_ctx
53
- typedef :int, :buffer_length
54
- typedef :int, :pass_length
55
- typedef :int, :read_write_flag
56
-
57
- SSL_ST_OK = 0x03
58
- begin
59
- attach_function :SSL_library_init, [], :int
60
- attach_function :SSL_load_error_strings, [], :void
61
- attach_function :ERR_load_crypto_strings, [], :void
62
-
63
- attach_function :SSL_state, [:ssl], :int
64
- def self.is_init_finished(ssl)
65
- SSL_state(ssl) == SSL_ST_OK
66
- end
67
-
68
- OPENSSL_V1_1 = false
69
- rescue FFI::NotFoundError
70
- OPENSSL_V1_1 = true
71
- OPENSSL_INIT_LOAD_SSL_STRINGS = 0x200000
72
- OPENSSL_INIT_NO_LOAD_SSL_STRINGS = 0x100000
73
- attach_function :OPENSSL_init_ssl, %i[uint64 pointer], :int
74
-
75
- attach_function :SSL_get_state, [:ssl], :int
76
- attach_function :SSL_is_init_finished, [:ssl], :bool
77
-
78
- def self.is_init_finished(ssl)
79
- SSL_is_init_finished(ssl)
80
- end
81
- end
82
-
83
- # Multi-threaded support
84
- # callback :locking_cb, [:int, :int, :string, :int], :void
85
- # callback :thread_id_cb, [], :ulong
86
- # attach_function :CRYPTO_num_locks, [], :int
87
- # attach_function :CRYPTO_set_locking_callback, [:locking_cb], :void
88
- # attach_function :CRYPTO_set_id_callback, [:thread_id_cb], :void
89
-
90
- # InitializeDefaultCredentials
91
- attach_function :BIO_new_mem_buf, %i[string buffer_length], :bio
92
- attach_function :EVP_PKEY_free, [:evp_key], :void
93
-
94
- callback :pem_password_cb, %i[pointer buffer_length read_write_flag user_data], :pass_length
95
- attach_function :PEM_read_bio_PrivateKey, %i[bio evp_key_pointer pem_password_cb user_data], :evp_key
96
-
97
- attach_function :X509_free, [:x509], :void
98
- attach_function :PEM_read_bio_X509, %i[bio x509_pointer pem_password_cb user_data], :x509
99
-
100
- attach_function :BIO_free, [:bio], :int
101
-
102
- # GetPeerCert
103
- attach_function :SSL_get_peer_certificate, [:ssl], :x509
104
-
105
- # PutPlaintext
106
- attach_function :SSL_write, %i[ssl buffer_in buffer_length], :int
107
- attach_function :SSL_get_error, %i[ssl int], :int
108
-
109
- # GetCiphertext
110
- attach_function :BIO_read, %i[bio buffer_out buffer_length], :int
111
-
112
- # CanGetCiphertext
113
- attach_function :BIO_ctrl, %i[bio int long pointer], :long
114
- BIO_CTRL_PENDING = 10 # opt - is their more data buffered?
115
- def self.BIO_pending(bio)
116
- BIO_ctrl(bio, BIO_CTRL_PENDING, 0, nil)
117
- end
118
-
119
- # GetPlaintext
120
- attach_function :SSL_accept, [:ssl], :int
121
- attach_function :SSL_read, %i[ssl buffer_out buffer_length], :int
122
- attach_function :SSL_pending, [:ssl], :int
123
-
124
- # PutCiphertext
125
- attach_function :BIO_write, %i[bio buffer_in buffer_length], :int
126
-
127
- # Deconstructor
128
- attach_function :SSL_get_shutdown, [:ssl], :int
129
- attach_function :SSL_shutdown, [:ssl], :int
130
- attach_function :SSL_clear, [:ssl], :void
131
- attach_function :SSL_free, [:ssl], :void
132
-
133
- # Constructor
134
- attach_function :BIO_s_mem, [], :pointer
135
- attach_function :BIO_new, [:pointer], :bio
136
- attach_function :SSL_new, [:ssl_ctx], :ssl
137
- # r, w
138
- attach_function :SSL_set_bio, %i[ssl bio bio], :void
139
-
140
- attach_function :SSL_set_ex_data, %i[ssl int string], :int
141
- callback :verify_callback, %i[int x509], :int
142
- attach_function :SSL_set_verify, %i[ssl int verify_callback], :void
143
- attach_function :SSL_CTX_set_verify, %i[ssl int verify_callback], :void
144
- attach_function :SSL_get_verify_result, %i[ssl], :long
145
- attach_function :SSL_connect, [:ssl], :int
146
-
147
- # Verify callback
148
- X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT = 2
149
- X509_V_ERR_HOSTNAME_MISMATCH = 62
150
- X509_V_ERR_CERT_REJECTED = 28
151
- attach_function :X509_STORE_CTX_get_current_cert, [:pointer], :x509
152
- attach_function :SSL_get_ex_data_X509_STORE_CTX_idx, [], :int
153
- attach_function :X509_STORE_CTX_get_ex_data, %i[pointer int], :ssl
154
- attach_function :X509_STORE_CTX_get_error_depth, %i[x509], :int
155
- attach_function :PEM_write_bio_X509, %i[bio x509], :bool
156
- attach_function :X509_verify_cert_error_string, %i[long], :string
157
- attach_function :X509_STORE_CTX_set_error, %i[ssl_ctx long], :void
158
-
159
- # SSL Context Class
160
- # OpenSSL before 1.1.0 do not have these methods
161
- # https://www.openssl.org/docs/man1.1.0/ssl/TLSv1_2_server_method.html
162
- begin
163
- attach_function :TLS_server_method, [], :pointer
164
- attach_function :TLS_client_method, [], :pointer
165
- rescue FFI::NotFoundError
166
- attach_function :SSLv23_server_method, [], :pointer
167
- attach_function :SSLv23_client_method, [], :pointer
168
-
169
- def self.TLS_server_method
170
- self.SSLv23_server_method
171
- end
172
-
173
- def self.TLS_client_method
174
- self.SSLv23_client_method
175
- end
176
- end
177
-
178
- # Version can be one of:
179
- # :SSL3, :TLS1, :TLS1_1, :TLS1_2, :TLS1_3, :TLS_MAX
180
- begin
181
- attach_function :SSL_get_version, %i[ssl], :string
182
- attach_function :SSL_get_current_cipher, %i[ssl], :cipher
183
- attach_function :SSL_CIPHER_get_name, %i[cipher], :string
184
- attach_function :SSL_CTX_set_min_proto_version, %i[ssl_ctx int], :int
185
- attach_function :SSL_CTX_set_max_proto_version, %i[ssl_ctx int], :int
186
-
187
- VERSION_SUPPORTED = true
188
-
189
- SSL3_VERSION = 0x0300
190
- TLS1_VERSION = 0x0301
191
- TLS1_1_VERSION = 0x0302
192
- TLS1_2_VERSION = 0x0303
193
- TLS1_3_VERSION = 0x0304
194
- TLS_MAX_VERSION = TLS1_3_VERSION
195
- ANY_VERSION = 0
196
- rescue FFI::NotFoundError
197
- VERSION_SUPPORTED = false
198
- end
199
-
200
- def self.get_version(ssl)
201
- SSL_get_version(ssl)
202
- end
203
-
204
- def self.get_current_cipher(ssl)
205
- cipher = SSL_get_current_cipher(ssl)
206
- SSL_CIPHER_get_name(cipher)
207
- end
208
-
209
- attach_function :SSL_CTX_new, [:pointer], :ssl_ctx
210
-
211
- attach_function :SSL_CTX_ctrl, %i[ssl_ctx int ulong pointer], :long
212
- SSL_CTRL_OPTIONS = 32
213
- def self.SSL_CTX_set_options(ssl_ctx, op)
214
- SSL_CTX_ctrl(ssl_ctx, SSL_CTRL_OPTIONS, op, nil)
215
- end
216
- SSL_CTRL_MODE = 33
217
- def self.SSL_CTX_set_mode(ssl_ctx, op)
218
- SSL_CTX_ctrl(ssl_ctx, SSL_CTRL_MODE, op, nil)
219
- end
220
- SSL_CTRL_SET_SESS_CACHE_SIZE = 42
221
- def self.SSL_CTX_sess_set_cache_size(ssl_ctx, op)
222
- SSL_CTX_ctrl(ssl_ctx, SSL_CTRL_SET_SESS_CACHE_SIZE, op, nil)
223
- end
224
-
225
- attach_function :SSL_ctrl, %i[ssl int long pointer], :long
226
- SSL_CTRL_SET_TLSEXT_HOSTNAME = 55
227
-
228
- def self.SSL_set_tlsext_host_name(ssl, host_name)
229
- name_ptr = FFI::MemoryPointer.from_string(host_name)
230
- raise Error, "error setting SNI hostname" if SSL_ctrl(ssl, SSL_CTRL_SET_TLSEXT_HOSTNAME, TLSEXT_NAMETYPE_host_name, name_ptr).zero?
231
- end
232
-
233
- # Server Name Indication (SNI) Support
234
- # NOTE:: We've hard coded the callback here (SSL defines a NULL callback)
235
- callback :ssl_servername_cb, %i[ssl pointer pointer], :int
236
- attach_function :SSL_CTX_callback_ctrl, %i[ssl_ctx int ssl_servername_cb], :long
237
- SSL_CTRL_SET_TLSEXT_SERVERNAME_CB = 53
238
- def self.SSL_CTX_set_tlsext_servername_callback(ctx, callback)
239
- SSL_CTX_callback_ctrl(ctx, SSL_CTRL_SET_TLSEXT_SERVERNAME_CB, callback)
240
- end
241
-
242
- attach_function :SSL_get_servername, %i[ssl int], :string
243
- TLSEXT_NAMETYPE_host_name = 0
244
-
245
- attach_function :SSL_set_SSL_CTX, %i[ssl ssl_ctx], :ssl_ctx
246
-
247
- SSL_TLSEXT_ERR_OK = 0
248
- SSL_TLSEXT_ERR_ALERT_WARNING = 1
249
- SSL_TLSEXT_ERR_ALERT_FATAL = 2
250
- SSL_TLSEXT_ERR_NOACK = 3
251
-
252
- attach_function :SSL_CTX_use_PrivateKey_file, %i[ssl_ctx string int], :int, :blocking => true
253
- attach_function :SSL_CTX_use_PrivateKey, %i[ssl_ctx pointer], :int
254
- attach_function :ERR_print_errors_fp, [:pointer], :void # Pointer == File Handle
255
- attach_function :SSL_CTX_use_certificate_chain_file, %i[ssl_ctx string], :int, :blocking => true
256
- attach_function :SSL_CTX_use_certificate, %i[ssl_ctx x509], :int
257
- attach_function :SSL_CTX_set_cipher_list, %i[ssl_ctx string], :int
258
- attach_function :SSL_CTX_set_session_id_context, %i[ssl_ctx string buffer_length], :int
259
- attach_function :SSL_load_client_CA_file, [:string], :pointer
260
- attach_function :SSL_CTX_set_client_CA_list, %i[ssl_ctx pointer], :void
261
- attach_function :SSL_CTX_load_verify_locations, %i[ssl_ctx pointer], :int, :blocking => true
262
-
263
- # OpenSSL before 1.0.2 do not have these methods
264
- begin
265
- attach_function :SSL_CTX_set_alpn_protos, %i[ssl_ctx string uint], :int
266
-
267
- OPENSSL_NPN_UNSUPPORTED = 0
268
- OPENSSL_NPN_NEGOTIATED = 1
269
- OPENSSL_NPN_NO_OVERLAP = 2
270
-
271
- attach_function :SSL_select_next_proto, %i[pointer pointer string uint string uint], :int
272
-
273
- # array of str, unit8 out,uint8 in, *arg
274
- callback :alpn_select_cb, %i[ssl pointer pointer string uint pointer], :int
275
- attach_function :SSL_CTX_set_alpn_select_cb, %i[ssl_ctx alpn_select_cb pointer], :void
276
-
277
- attach_function :SSL_get0_alpn_selected, %i[ssl pointer pointer], :void
278
- ALPN_SUPPORTED = true
279
- rescue FFI::NotFoundError
280
- ALPN_SUPPORTED = false
281
- end
282
-
283
- # Deconstructor
284
- attach_function :SSL_CTX_free, [:ssl_ctx], :void
285
-
286
- PrivateMaterials = <<~KEYSTR
287
- -----BEGIN RSA PRIVATE KEY-----
288
- MIICXAIBAAKBgQDCYYhcw6cGRbhBVShKmbWm7UVsEoBnUf0cCh8AX+MKhMxwVDWV
289
- Igdskntn3cSJjRtmgVJHIK0lpb/FYHQB93Ohpd9/Z18pDmovfFF9nDbFF0t39hJ/
290
- AqSzFB3GiVPoFFZJEE1vJqh+3jzsSF5K56bZ6azz38VlZgXeSozNW5bXkQIDAQAB
291
- AoGALA89gIFcr6BIBo8N5fL3aNHpZXjAICtGav+kTUpuxSiaym9cAeTHuAVv8Xgk
292
- H2Wbq11uz+6JMLpkQJH/WZ7EV59DPOicXrp0Imr73F3EXBfR7t2EQDYHPMthOA1D
293
- I9EtCzvV608Ze90hiJ7E3guGrGppZfJ+eUWCPgy8CZH1vRECQQDv67rwV/oU1aDo
294
- 6/+d5nqjeW6mWkGqTnUU96jXap8EIw6B+0cUKskwx6mHJv+tEMM2748ZY7b0yBlg
295
- w4KDghbFAkEAz2h8PjSJG55LwqmXih1RONSgdN9hjB12LwXL1CaDh7/lkEhq0PlK
296
- PCAUwQSdM17Sl0Xxm2CZiekTSlwmHrtqXQJAF3+8QJwtV2sRJp8u2zVe37IeH1cJ
297
- xXeHyjTzqZ2803fnjN2iuZvzNr7noOA1/Kp+pFvUZUU5/0G2Ep8zolPUjQJAFA7k
298
- xRdLkzIx3XeNQjwnmLlncyYPRv+qaE3FMpUu7zftuZBnVCJnvXzUxP3vPgKTlzGa
299
- dg5XivDRfsV+okY5uQJBAMV4FesUuLQVEKb6lMs7rzZwpeGQhFDRfywJzfom2TLn
300
- 2RdJQQ3dcgnhdVDgt5o1qkmsqQh8uJrJ9SdyLIaZQIc=
301
- -----END RSA PRIVATE KEY-----
302
- -----BEGIN CERTIFICATE-----
303
- MIID6TCCA1KgAwIBAgIJANm4W/Tzs+s+MA0GCSqGSIb3DQEBBQUAMIGqMQswCQYD
304
- VQQGEwJVUzERMA8GA1UECBMITmV3IFlvcmsxETAPBgNVBAcTCE5ldyBZb3JrMRYw
305
- FAYDVQQKEw1TdGVhbWhlYXQubmV0MRQwEgYDVQQLEwtFbmdpbmVlcmluZzEdMBsG
306
- A1UEAxMUb3BlbmNhLnN0ZWFtaGVhdC5uZXQxKDAmBgkqhkiG9w0BCQEWGWVuZ2lu
307
- ZWVyaW5nQHN0ZWFtaGVhdC5uZXQwHhcNMDYwNTA1MTcwNjAzWhcNMjQwMjIwMTcw
308
- NjAzWjCBqjELMAkGA1UEBhMCVVMxETAPBgNVBAgTCE5ldyBZb3JrMREwDwYDVQQH
309
- EwhOZXcgWW9yazEWMBQGA1UEChMNU3RlYW1oZWF0Lm5ldDEUMBIGA1UECxMLRW5n
310
- aW5lZXJpbmcxHTAbBgNVBAMTFG9wZW5jYS5zdGVhbWhlYXQubmV0MSgwJgYJKoZI
311
- hvcNAQkBFhllbmdpbmVlcmluZ0BzdGVhbWhlYXQubmV0MIGfMA0GCSqGSIb3DQEB
312
- AQUAA4GNADCBiQKBgQDCYYhcw6cGRbhBVShKmbWm7UVsEoBnUf0cCh8AX+MKhMxw
313
- VDWVIgdskntn3cSJjRtmgVJHIK0lpb/FYHQB93Ohpd9/Z18pDmovfFF9nDbFF0t3
314
- 9hJ/AqSzFB3GiVPoFFZJEE1vJqh+3jzsSF5K56bZ6azz38VlZgXeSozNW5bXkQID
315
- AQABo4IBEzCCAQ8wHQYDVR0OBBYEFPJvPd1Fcmd8o/Tm88r+NjYPICCkMIHfBgNV
316
- HSMEgdcwgdSAFPJvPd1Fcmd8o/Tm88r+NjYPICCkoYGwpIGtMIGqMQswCQYDVQQG
317
- EwJVUzERMA8GA1UECBMITmV3IFlvcmsxETAPBgNVBAcTCE5ldyBZb3JrMRYwFAYD
318
- VQQKEw1TdGVhbWhlYXQubmV0MRQwEgYDVQQLEwtFbmdpbmVlcmluZzEdMBsGA1UE
319
- AxMUb3BlbmNhLnN0ZWFtaGVhdC5uZXQxKDAmBgkqhkiG9w0BCQEWGWVuZ2luZWVy
320
- aW5nQHN0ZWFtaGVhdC5uZXSCCQDZuFv087PrPjAMBgNVHRMEBTADAQH/MA0GCSqG
321
- SIb3DQEBBQUAA4GBAC1CXey/4UoLgJiwcEMDxOvW74plks23090iziFIlGgcIhk0
322
- Df6hTAs7H3MWww62ddvR8l07AWfSzSP5L6mDsbvq7EmQsmPODwb6C+i2aF3EDL8j
323
- uw73m4YIGI0Zw2XdBpiOGkx2H56Kya6mJJe/5XORZedh1wpI7zki01tHYbcy
324
- -----END CERTIFICATE-----
325
- KEYSTR
326
-
327
- BuiltinPasswdCB = FFI::Function.new(:int, %i[pointer int int pointer]) do |buffer, _len, _flag, _data|
328
- buffer.write_string("kittycat")
329
- 8
330
- end
331
-
332
- # Save RAM by releasing read and write buffers when they're empty
333
- SSL_MODE_RELEASE_BUFFERS = 0x00000010
334
- SSL_OP_ALL = 0x80000BFF
335
- SSL_FILETYPE_PEM = 1
336
-
337
- # Locking isn't provided as long as all writes are done on the same thread.
338
- # This is my main use case. Happy to enable it if someone requires it and can
339
- # get it to work on MRI Ruby (Currently only works on JRuby and Rubinius)
340
- # as MRI callbacks occur on a thread pool?
341
-
342
- # CRYPTO_LOCK = 0x1
343
- # LockingCB = FFI::Function.new(:void, [:int, :int, :string, :int]) do |mode, type, file, line|
344
- # if (mode & CRYPTO_LOCK) != 0
345
- # SSL_LOCKS[type].lock
346
- # else
347
- # Unlock a lock
348
- # SSL_LOCKS[type].unlock
349
- # end
350
- # end
351
- # ThreadIdCB = FFI::Function.new(:ulong, []) do
352
- # Thread.current.object_id
353
- # end
354
-
355
- # INIT CODE
356
- @init_required ||= false
357
- unless @init_required
358
- if OPENSSL_V1_1
359
- self.OPENSSL_init_ssl(OPENSSL_INIT_LOAD_SSL_STRINGS, ::FFI::Pointer::NULL)
360
- else
361
- self.SSL_load_error_strings
362
- self.SSL_library_init
363
- self.ERR_load_crypto_strings
364
- end
365
-
366
- # Setup multi-threaded support
367
- # SSL_LOCKS = []
368
- # num_locks = self.CRYPTO_num_locks
369
- # num_locks.times { SSL_LOCKS << Mutex.new }
370
-
371
- # self.CRYPTO_set_locking_callback(LockingCB)
372
- # self.CRYPTO_set_id_callback(ThreadIdCB)
373
-
374
- bio = self.BIO_new_mem_buf(PrivateMaterials, PrivateMaterials.bytesize)
375
-
376
- # Get the private key structure
377
- pointer = FFI::MemoryPointer.new(:pointer)
378
- self.PEM_read_bio_PrivateKey(bio, pointer, BuiltinPasswdCB, nil)
379
- DEFAULT_PRIVATE = pointer.get_pointer(0)
380
-
381
- # Get the certificate structure
382
- pointer = FFI::MemoryPointer.new(:pointer)
383
- self.PEM_read_bio_X509(bio, pointer, nil, nil)
384
- DEFAULT_CERT = pointer.get_pointer(0)
385
-
386
- self.BIO_free(bio)
387
-
388
- @init_required = true
389
- end
390
- end
data/lib/httpx/io/tls.rb DELETED
@@ -1,218 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "openssl"
4
-
5
- module HTTPX
6
- class TLS < TCP
7
- class Error < StandardError; end
8
-
9
- def initialize(_, _, options)
10
- super
11
- @encrypted = Buffer.new(Connection::BUFFER_SIZE)
12
- @decrypted = "".b
13
- tls_options = convert_tls_options(options.ssl)
14
- @sni_hostname = tls_options[:hostname]
15
- @ctx = TLS::Box.new(false, self, tls_options)
16
- @state = :negotiated if @keep_open
17
- end
18
-
19
- def interests
20
- @interests || super
21
- end
22
-
23
- def protocol
24
- @protocol || super
25
- end
26
-
27
- def connected?
28
- @state == :negotiated
29
- end
30
-
31
- def connect
32
- super
33
- if @keep_open
34
- @state = :negotiated
35
- return
36
- end
37
- return if @state == :negotiated ||
38
- @state != :connected
39
-
40
- super
41
- @ctx.start
42
- @interests = :r
43
- read(@options.window_size, @decrypted)
44
- end
45
-
46
- # :nocov:
47
- def inspect
48
- id = @io.closed? ? "closed" : @io
49
- "#<TLS(fd: #{id}): #{@ip}:#{@port} state: #{@state}>"
50
- end
51
- # :nocov:
52
-
53
- alias_method :transport_close, :close
54
- def close
55
- transport_close
56
- @ctx.cleanup
57
- end
58
-
59
- def read(*, buffer)
60
- ret = super
61
- return ret if !ret || ret.zero?
62
-
63
- @ctx.decrypt(buffer.to_s.dup)
64
- buffer.replace(@decrypted)
65
- @decrypted.clear
66
- buffer.bytesize
67
- end
68
-
69
- alias_method :unencrypted_write, :write
70
- def write(buffer)
71
- @ctx.encrypt(buffer.to_s.dup)
72
- buffer.clear
73
- do_write
74
- end
75
-
76
- # TLS callback.
77
- #
78
- # buffers the encrypted +data+
79
- def transmit_cb(data)
80
- log { "TLS encrypted: #{data.bytesize} bytes" }
81
- log(level: 2) { data.inspect }
82
- @encrypted << data
83
- do_write
84
- end
85
-
86
- # TLS callback.
87
- #
88
- # buffers the decrypted +data+
89
- def dispatch_cb(data)
90
- log { "TLS decrypted: #{data.bytesize} bytes" }
91
- log(level: 2) { data.inspect }
92
-
93
- @decrypted << data
94
- end
95
-
96
- # TLS callback.
97
- #
98
- # signals TLS invalid status / shutdown.
99
- def close_cb(msg = nil)
100
- log { "TLS Error: #{msg}, closing" }
101
- raise Error, "certificate verify failed (#{msg})"
102
- end
103
-
104
- # TLS callback.
105
- #
106
- # alpn protocol negotiation (+protocol+).
107
- #
108
- def alpn_protocol_cb(protocol)
109
- @protocol = protocol
110
- log { "TLS ALPN protocol negotiated: #{@protocol}" }
111
- end
112
-
113
- # TLS callback.
114
- #
115
- # handshake finished.
116
- #
117
- def handshake_cb
118
- log { "TLS handshake completed" }
119
- transition(:negotiated)
120
- end
121
-
122
- # TLS callback.
123
- #
124
- # passed the peer +cert+ to be verified.
125
- #
126
- def verify_cb(cert)
127
- raise Error, "Peer verification enabled, but no certificate received." if cert.nil?
128
-
129
- log { "TLS verifying #{cert}" }
130
- @peer_cert = OpenSSL::X509::Certificate.new(cert)
131
-
132
- # by default one doesn't verify client certificates in the server
133
- verify_hostname(@sni_hostname)
134
- end
135
-
136
- # copied from:
137
- # https://github.com/ruby/ruby/blob/8cbf2dae5aadfa5d6241b0df2bf44d55db46704f/ext/openssl/lib/openssl/ssl.rb#L395-L409
138
- #
139
- def verify_hostname(host)
140
- return false unless @ctx.verify_peer && @peer_cert
141
-
142
- OpenSSL::SSL.verify_certificate_identity(@peer_cert, host)
143
- end
144
-
145
- private
146
-
147
- def do_write
148
- nwritten = 0
149
- until @encrypted.empty?
150
- siz = unencrypted_write(@encrypted)
151
- break unless !siz || siz.zero?
152
-
153
- nwritten += siz
154
- end
155
- nwritten
156
- end
157
-
158
- def convert_tls_options(ssl_options)
159
- options = {}
160
- options[:verify_peer] = !ssl_options.key?(:verify_mode) || ssl_options[:verify_mode] != OpenSSL::SSL::VERIFY_NONE
161
- options[:version] = ssl_options[:ssl_version] if ssl_options.key?(:ssl_version)
162
-
163
- if ssl_options.key?(:key)
164
- private_key = ssl_options[:key]
165
- private_key = private_key.to_pem if private_key.respond_to?(:to_pem)
166
- options[:private_key] = private_key
167
- end
168
-
169
- if ssl_options.key?(:ca_path) || ssl_options.key?(:ca_file)
170
- ca_path = ssl_options[:ca_path] || ssl_options[:ca_file].path
171
- options[:cert_chain] = ca_path
172
- end
173
-
174
- options[:ciphers] = ssl_options[:ciphers] if ssl_options.key?(:ciphers)
175
- options[:protocols] = ssl_options.fetch(:alpn_protocols, %w[h2 http/1.1])
176
- options[:hostname] = ssl_options.fetch(:hostname, @hostname)
177
- options
178
- end
179
-
180
- def transition(nextstate)
181
- case nextstate
182
- when :negotiated
183
- return unless @state == :connected
184
- when :closed
185
- return unless @state == :negotiated ||
186
- @state == :connected
187
- end
188
- do_transition(nextstate)
189
- end
190
-
191
- def log_transition_state(nextstate)
192
- return super unless nextstate == :negotiated
193
-
194
- server_cert = @peer_cert
195
-
196
- "#{super}\n\n" \
197
- "SSL connection using #{@ctx.ssl_version} / #{Array(@ctx.cipher).first}\n" \
198
- "ALPN, server accepted to use #{protocol}\n" +
199
- (if server_cert
200
- "Server certificate:\n" \
201
- " subject: #{server_cert.subject}\n" \
202
- " start date: #{server_cert.not_before}\n" \
203
- " expire date: #{server_cert.not_after}\n" \
204
- " issuer: #{server_cert.issuer}\n" \
205
- " SSL certificate verify ok."
206
- else
207
- "SSL certificate verify failed."
208
- end
209
- )
210
- end
211
- end
212
-
213
- TLSError = TLS::Error
214
- end
215
-
216
- require "httpx/io/tls/ffi"
217
- require "httpx/io/tls/context"
218
- require "httpx/io/tls/box"