httpx 0.11.0 → 0.13.0

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.
Files changed (78) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +2 -2
  3. data/doc/release_notes/0_11_1.md +5 -0
  4. data/doc/release_notes/0_11_2.md +5 -0
  5. data/doc/release_notes/0_11_3.md +5 -0
  6. data/doc/release_notes/0_12_0.md +55 -0
  7. data/doc/release_notes/0_13_0.md +58 -0
  8. data/lib/httpx.rb +2 -1
  9. data/lib/httpx/adapters/faraday.rb +4 -6
  10. data/lib/httpx/altsvc.rb +1 -0
  11. data/lib/httpx/chainable.rb +2 -2
  12. data/lib/httpx/connection.rb +80 -28
  13. data/lib/httpx/connection/http1.rb +19 -6
  14. data/lib/httpx/connection/http2.rb +32 -25
  15. data/lib/httpx/io.rb +16 -3
  16. data/lib/httpx/io/ssl.rb +35 -24
  17. data/lib/httpx/io/tcp.rb +48 -28
  18. data/lib/httpx/io/tls.rb +218 -0
  19. data/lib/httpx/io/tls/box.rb +365 -0
  20. data/lib/httpx/io/tls/context.rb +199 -0
  21. data/lib/httpx/io/tls/ffi.rb +390 -0
  22. data/lib/httpx/io/udp.rb +3 -2
  23. data/lib/httpx/io/unix.rb +27 -12
  24. data/lib/httpx/options.rb +11 -23
  25. data/lib/httpx/parser/http1.rb +4 -4
  26. data/lib/httpx/plugins/aws_sdk_authentication.rb +81 -0
  27. data/lib/httpx/plugins/aws_sigv4.rb +218 -0
  28. data/lib/httpx/plugins/compression.rb +21 -9
  29. data/lib/httpx/plugins/compression/brotli.rb +8 -6
  30. data/lib/httpx/plugins/compression/deflate.rb +4 -7
  31. data/lib/httpx/plugins/compression/gzip.rb +2 -2
  32. data/lib/httpx/plugins/cookies/set_cookie_parser.rb +1 -1
  33. data/lib/httpx/plugins/digest_authentication.rb +1 -1
  34. data/lib/httpx/plugins/follow_redirects.rb +1 -1
  35. data/lib/httpx/plugins/h2c.rb +43 -58
  36. data/lib/httpx/plugins/internal_telemetry.rb +93 -0
  37. data/lib/httpx/plugins/multipart.rb +2 -0
  38. data/lib/httpx/plugins/multipart/encoder.rb +4 -9
  39. data/lib/httpx/plugins/proxy.rb +1 -1
  40. data/lib/httpx/plugins/proxy/http.rb +1 -1
  41. data/lib/httpx/plugins/proxy/socks4.rb +8 -0
  42. data/lib/httpx/plugins/proxy/socks5.rb +8 -0
  43. data/lib/httpx/plugins/push_promise.rb +3 -2
  44. data/lib/httpx/plugins/retries.rb +2 -2
  45. data/lib/httpx/plugins/stream.rb +6 -6
  46. data/lib/httpx/plugins/upgrade.rb +83 -0
  47. data/lib/httpx/plugins/upgrade/h2.rb +54 -0
  48. data/lib/httpx/pool.rb +14 -6
  49. data/lib/httpx/registry.rb +1 -7
  50. data/lib/httpx/request.rb +11 -1
  51. data/lib/httpx/resolver/https.rb +3 -11
  52. data/lib/httpx/response.rb +14 -7
  53. data/lib/httpx/selector.rb +5 -0
  54. data/lib/httpx/session.rb +25 -2
  55. data/lib/httpx/transcoder/body.rb +3 -5
  56. data/lib/httpx/version.rb +1 -1
  57. data/sig/chainable.rbs +2 -1
  58. data/sig/connection/http1.rbs +3 -2
  59. data/sig/connection/http2.rbs +5 -3
  60. data/sig/options.rbs +7 -20
  61. data/sig/plugins/aws_sdk_authentication.rbs +17 -0
  62. data/sig/plugins/aws_sigv4.rbs +64 -0
  63. data/sig/plugins/compression.rbs +5 -3
  64. data/sig/plugins/compression/brotli.rbs +1 -1
  65. data/sig/plugins/compression/deflate.rbs +1 -1
  66. data/sig/plugins/compression/gzip.rbs +1 -1
  67. data/sig/plugins/cookies.rbs +0 -1
  68. data/sig/plugins/digest_authentication.rbs +0 -1
  69. data/sig/plugins/expect.rbs +0 -2
  70. data/sig/plugins/follow_redirects.rbs +0 -2
  71. data/sig/plugins/h2c.rbs +5 -10
  72. data/sig/plugins/persistent.rbs +0 -1
  73. data/sig/plugins/proxy.rbs +0 -1
  74. data/sig/plugins/push_promise.rbs +1 -1
  75. data/sig/plugins/retries.rbs +0 -4
  76. data/sig/plugins/upgrade.rbs +23 -0
  77. data/sig/response.rbs +3 -1
  78. metadata +48 -26
@@ -0,0 +1,365 @@
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
@@ -0,0 +1,199 @@
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