httpx 0.11.3 → 0.14.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (99) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +2 -2
  3. data/doc/release_notes/0_10_1.md +1 -1
  4. data/doc/release_notes/0_11_1.md +5 -1
  5. data/doc/release_notes/0_12_0.md +55 -0
  6. data/doc/release_notes/0_13_0.md +58 -0
  7. data/doc/release_notes/0_13_1.md +5 -0
  8. data/doc/release_notes/0_13_2.md +9 -0
  9. data/doc/release_notes/0_14_0.md +79 -0
  10. data/lib/httpx.rb +3 -3
  11. data/lib/httpx/adapters/faraday.rb +4 -6
  12. data/lib/httpx/altsvc.rb +1 -0
  13. data/lib/httpx/callbacks.rb +12 -3
  14. data/lib/httpx/chainable.rb +2 -2
  15. data/lib/httpx/connection.rb +92 -37
  16. data/lib/httpx/connection/http1.rb +37 -19
  17. data/lib/httpx/connection/http2.rb +82 -31
  18. data/lib/httpx/headers.rb +1 -1
  19. data/lib/httpx/io.rb +16 -3
  20. data/lib/httpx/io/ssl.rb +35 -24
  21. data/lib/httpx/io/tcp.rb +50 -28
  22. data/lib/httpx/io/tls.rb +218 -0
  23. data/lib/httpx/io/tls/box.rb +365 -0
  24. data/lib/httpx/io/tls/context.rb +199 -0
  25. data/lib/httpx/io/tls/ffi.rb +390 -0
  26. data/lib/httpx/io/udp.rb +31 -7
  27. data/lib/httpx/io/unix.rb +27 -12
  28. data/lib/httpx/options.rb +97 -74
  29. data/lib/httpx/parser/http1.rb +4 -4
  30. data/lib/httpx/plugins/aws_sdk_authentication.rb +84 -0
  31. data/lib/httpx/plugins/aws_sigv4.rb +219 -0
  32. data/lib/httpx/plugins/basic_authentication.rb +8 -3
  33. data/lib/httpx/plugins/compression.rb +24 -12
  34. data/lib/httpx/plugins/compression/brotli.rb +10 -7
  35. data/lib/httpx/plugins/compression/deflate.rb +8 -10
  36. data/lib/httpx/plugins/compression/gzip.rb +4 -3
  37. data/lib/httpx/plugins/cookies.rb +3 -7
  38. data/lib/httpx/plugins/digest_authentication.rb +5 -5
  39. data/lib/httpx/plugins/expect.rb +6 -6
  40. data/lib/httpx/plugins/follow_redirects.rb +4 -4
  41. data/lib/httpx/plugins/grpc.rb +247 -0
  42. data/lib/httpx/plugins/grpc/call.rb +62 -0
  43. data/lib/httpx/plugins/grpc/message.rb +85 -0
  44. data/lib/httpx/plugins/h2c.rb +43 -58
  45. data/lib/httpx/plugins/internal_telemetry.rb +93 -0
  46. data/lib/httpx/plugins/multipart.rb +2 -0
  47. data/lib/httpx/plugins/multipart/encoder.rb +4 -9
  48. data/lib/httpx/plugins/multipart/part.rb +1 -1
  49. data/lib/httpx/plugins/proxy.rb +4 -8
  50. data/lib/httpx/plugins/proxy/http.rb +1 -1
  51. data/lib/httpx/plugins/proxy/socks4.rb +8 -0
  52. data/lib/httpx/plugins/proxy/socks5.rb +8 -0
  53. data/lib/httpx/plugins/proxy/ssh.rb +3 -3
  54. data/lib/httpx/plugins/push_promise.rb +3 -2
  55. data/lib/httpx/plugins/rate_limiter.rb +1 -1
  56. data/lib/httpx/plugins/retries.rb +15 -16
  57. data/lib/httpx/plugins/stream.rb +99 -77
  58. data/lib/httpx/plugins/upgrade.rb +84 -0
  59. data/lib/httpx/plugins/upgrade/h2.rb +54 -0
  60. data/lib/httpx/pool.rb +14 -6
  61. data/lib/httpx/registry.rb +1 -7
  62. data/lib/httpx/request.rb +36 -3
  63. data/lib/httpx/resolver/https.rb +3 -11
  64. data/lib/httpx/resolver/native.rb +7 -3
  65. data/lib/httpx/response.rb +18 -7
  66. data/lib/httpx/selector.rb +5 -0
  67. data/lib/httpx/session.rb +41 -8
  68. data/lib/httpx/transcoder/body.rb +3 -5
  69. data/lib/httpx/transcoder/chunker.rb +1 -1
  70. data/lib/httpx/version.rb +1 -1
  71. data/sig/callbacks.rbs +2 -0
  72. data/sig/chainable.rbs +2 -1
  73. data/sig/connection/http1.rbs +7 -2
  74. data/sig/connection/http2.rbs +10 -4
  75. data/sig/options.rbs +16 -22
  76. data/sig/plugins/aws_sdk_authentication.rbs +19 -0
  77. data/sig/plugins/aws_sigv4.rbs +64 -0
  78. data/sig/plugins/basic_authentication.rbs +2 -0
  79. data/sig/plugins/compression.rbs +7 -5
  80. data/sig/plugins/compression/brotli.rbs +1 -1
  81. data/sig/plugins/compression/deflate.rbs +1 -1
  82. data/sig/plugins/compression/gzip.rbs +1 -1
  83. data/sig/plugins/cookies.rbs +0 -1
  84. data/sig/plugins/digest_authentication.rbs +0 -1
  85. data/sig/plugins/expect.rbs +0 -2
  86. data/sig/plugins/follow_redirects.rbs +0 -2
  87. data/sig/plugins/h2c.rbs +5 -10
  88. data/sig/plugins/persistent.rbs +0 -1
  89. data/sig/plugins/proxy.rbs +0 -1
  90. data/sig/plugins/push_promise.rbs +1 -1
  91. data/sig/plugins/retries.rbs +0 -4
  92. data/sig/plugins/stream.rbs +17 -16
  93. data/sig/plugins/upgrade.rbs +23 -0
  94. data/sig/request.rbs +7 -2
  95. data/sig/response.rbs +4 -1
  96. data/sig/session.rbs +4 -0
  97. metadata +56 -33
  98. data/lib/httpx/timeout.rb +0 -67
  99. data/sig/timeout.rbs +0 -29
@@ -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
@@ -0,0 +1,390 @@
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