httpx 0.19.2 → 0.19.5

Sign up to get free protection for your applications and to get access to all the features.
@@ -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