mt-ruby-tls 2.4.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.
@@ -0,0 +1,864 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'ffi'
4
+ require 'ffi-compiler/loader'
5
+ require 'thread'
6
+ require 'concurrent'
7
+
8
+
9
+ module MTRubyTls
10
+ module SSL
11
+ extend FFI::Library
12
+ if FFI::Platform.windows?
13
+ begin
14
+ ffi_lib 'libeay32', 'ssleay32'
15
+ rescue LoadError
16
+ ffi_lib 'libcrypto-1_1-x64', 'libssl-1_1-x64'
17
+ end
18
+ else
19
+ ffi_lib 'ssl'
20
+ end
21
+
22
+ # Common structures
23
+ typedef :pointer, :user_data
24
+ typedef :pointer, :bio
25
+ typedef :pointer, :evp_key
26
+ typedef :pointer, :evp_key_pointer
27
+ typedef :pointer, :x509
28
+ typedef :pointer, :x509_pointer
29
+ typedef :pointer, :ssl
30
+ typedef :pointer, :ssl_ctx
31
+ typedef :int, :buffer_length
32
+ typedef :int, :pass_length
33
+ typedef :int, :read_write_flag
34
+
35
+ SSL_ST_OK = 0x03
36
+ begin
37
+ attach_function :SSL_library_init, [], :int
38
+ attach_function :SSL_load_error_strings, [], :void
39
+ attach_function :ERR_load_crypto_strings, [], :void
40
+
41
+ attach_function :SSL_state, [:ssl], :int
42
+ def self.SSL_is_init_finished(ssl)
43
+ SSL_state(ssl) == SSL_ST_OK
44
+ end
45
+
46
+ OPENSSL_V1_1 = false
47
+ rescue FFI::NotFoundError
48
+ OPENSSL_V1_1 = true
49
+ OPENSSL_INIT_LOAD_SSL_STRINGS = 0x200000
50
+ OPENSSL_INIT_NO_LOAD_SSL_STRINGS = 0x100000
51
+ attach_function :OPENSSL_init_ssl, [:uint64, :pointer], :int
52
+
53
+ attach_function :SSL_get_state, [:ssl], :int
54
+ def self.SSL_is_init_finished(ssl)
55
+ SSL_get_state(ssl) == SSL_ST_OK
56
+ end
57
+ end
58
+
59
+ # Multi-threaded support
60
+ #callback :locking_cb, [:int, :int, :string, :int], :void
61
+ #callback :thread_id_cb, [], :ulong
62
+ #attach_function :CRYPTO_num_locks, [], :int
63
+ #attach_function :CRYPTO_set_locking_callback, [:locking_cb], :void
64
+ #attach_function :CRYPTO_set_id_callback, [:thread_id_cb], :void
65
+
66
+ # InitializeDefaultCredentials
67
+ attach_function :BIO_new_mem_buf, [:string, :buffer_length], :bio
68
+ attach_function :EVP_PKEY_free, [:evp_key], :void
69
+
70
+ callback :pem_password_cb, [:pointer, :buffer_length, :read_write_flag, :user_data], :pass_length
71
+ attach_function :PEM_read_bio_PrivateKey, [:bio, :evp_key_pointer, :pem_password_cb, :user_data], :evp_key
72
+
73
+ attach_function :X509_free, [:x509], :void
74
+ attach_function :PEM_read_bio_X509, [:bio, :x509_pointer, :pem_password_cb, :user_data], :x509
75
+
76
+ attach_function :BIO_free, [:bio], :int
77
+
78
+ # GetPeerCert
79
+ attach_function :SSL_get_peer_certificate, [:ssl], :x509
80
+
81
+
82
+ # PutPlaintext
83
+ attach_function :SSL_write, [:ssl, :buffer_in, :buffer_length], :int
84
+ attach_function :SSL_get_error, [:ssl, :int], :int
85
+
86
+
87
+ # GetCiphertext
88
+ attach_function :BIO_read, [:bio, :buffer_out, :buffer_length], :int
89
+
90
+ # CanGetCiphertext
91
+ attach_function :BIO_ctrl, [:bio, :int, :long, :pointer], :long
92
+ BIO_CTRL_PENDING = 10 # opt - is their more data buffered?
93
+ def self.BIO_pending(bio)
94
+ BIO_ctrl(bio, BIO_CTRL_PENDING, 0, nil)
95
+ end
96
+
97
+
98
+ # GetPlaintext
99
+ attach_function :SSL_accept, [:ssl], :int
100
+ attach_function :SSL_read, [:ssl, :buffer_out, :buffer_length], :int
101
+ attach_function :SSL_pending, [:ssl], :int
102
+
103
+ # PutCiphertext
104
+ attach_function :BIO_write, [:bio, :buffer_in, :buffer_length], :int
105
+
106
+ # Deconstructor
107
+ attach_function :SSL_get_shutdown, [:ssl], :int
108
+ attach_function :SSL_shutdown, [:ssl], :int
109
+ attach_function :SSL_clear, [:ssl], :void
110
+ attach_function :SSL_free, [:ssl], :void
111
+
112
+
113
+ # Constructor
114
+ attach_function :BIO_s_mem, [], :pointer
115
+ attach_function :BIO_new, [:pointer], :bio
116
+ attach_function :SSL_new, [:ssl_ctx], :ssl
117
+ # r, w
118
+ attach_function :SSL_set_bio, [:ssl, :bio, :bio], :void
119
+
120
+ attach_function :SSL_set_ex_data, [:ssl, :int, :string], :int
121
+ callback :verify_callback, [:int, :x509], :int
122
+ attach_function :SSL_set_verify, [:ssl, :int, :verify_callback], :void
123
+ attach_function :SSL_connect, [:ssl], :int
124
+
125
+ # Verify callback
126
+ attach_function :X509_STORE_CTX_get_current_cert, [:pointer], :x509
127
+ attach_function :SSL_get_ex_data_X509_STORE_CTX_idx, [], :int
128
+ attach_function :X509_STORE_CTX_get_ex_data, [:pointer, :int], :ssl
129
+ attach_function :PEM_write_bio_X509, [:bio, :x509], :int
130
+
131
+ # SSL Context Class
132
+ # OpenSSL before 1.1.0 do not have these methods
133
+ # https://www.openssl.org/docs/man1.1.0/ssl/TLSv1_2_server_method.html
134
+ begin
135
+ attach_function :TLS_server_method, [], :pointer
136
+ attach_function :TLS_client_method, [], :pointer
137
+ rescue FFI::NotFoundError
138
+ attach_function :SSLv23_server_method, [], :pointer
139
+ attach_function :SSLv23_client_method, [], :pointer
140
+
141
+ def self.TLS_server_method; self.SSLv23_server_method; end
142
+ def self.TLS_client_method; self.SSLv23_client_method; end
143
+ end
144
+
145
+ # Version can be one of:
146
+ # :SSL3, :TLS1, :TLS1_1, :TLS1_2, :TLS1_3, :TLS_MAX
147
+ begin
148
+ attach_function :SSL_CTX_set_min_proto_version, [:ssl_ctx, :int], :int
149
+ attach_function :SSL_CTX_set_max_proto_version, [:ssl_ctx, :int], :int
150
+
151
+ VERSION_SUPPORTED = true
152
+
153
+ SSL3_VERSION = 0x0300
154
+ TLS1_VERSION = 0x0301
155
+ TLS1_1_VERSION = 0x0302
156
+ TLS1_2_VERSION = 0x0303
157
+ TLS1_3_VERSION = 0x0304
158
+ TLS_MAX_VERSION = TLS1_3_VERSION
159
+ ANY_VERSION = 0
160
+ rescue FFI::NotFoundError
161
+ VERSION_SUPPORTED = false
162
+ end
163
+
164
+
165
+ attach_function :SSL_CTX_new, [:pointer], :ssl_ctx
166
+
167
+ attach_function :SSL_CTX_ctrl, [:ssl_ctx, :int, :ulong, :pointer], :long
168
+ SSL_CTRL_OPTIONS = 32
169
+ def self.SSL_CTX_set_options(ssl_ctx, op)
170
+ SSL_CTX_ctrl(ssl_ctx, SSL_CTRL_OPTIONS, op, nil)
171
+ end
172
+ SSL_CTRL_MODE = 33
173
+ def self.SSL_CTX_set_mode(ssl_ctx, op)
174
+ SSL_CTX_ctrl(ssl_ctx, SSL_CTRL_MODE, op, nil)
175
+ end
176
+ SSL_CTRL_SET_SESS_CACHE_SIZE = 42
177
+ def self.SSL_CTX_sess_set_cache_size(ssl_ctx, op)
178
+ SSL_CTX_ctrl(ssl_ctx, SSL_CTRL_SET_SESS_CACHE_SIZE, op, nil)
179
+ end
180
+
181
+ attach_function :SSL_ctrl, [:ssl, :int, :long, :pointer], :long
182
+ SSL_CTRL_SET_TLSEXT_HOSTNAME = 55
183
+ def self.SSL_set_tlsext_host_name(ssl, host_name)
184
+ name = FFI::MemoryPointer.from_string(host_name)
185
+ SSL_ctrl(ssl, SSL_CTRL_SET_TLSEXT_HOSTNAME, TLSEXT_NAMETYPE_host_name, name)
186
+ end
187
+
188
+ # Server Name Indication (SNI) Support
189
+ # NOTE:: We've hard coded the callback here (SSL defines a NULL callback)
190
+ callback :ssl_servername_cb, [:ssl, :pointer, :pointer], :int
191
+ attach_function :SSL_CTX_callback_ctrl, [:ssl_ctx, :int, :ssl_servername_cb], :long
192
+ SSL_CTRL_SET_TLSEXT_SERVERNAME_CB = 53
193
+ def self.SSL_CTX_set_tlsext_servername_callback(ctx, callback)
194
+ SSL_CTX_callback_ctrl(ctx, SSL_CTRL_SET_TLSEXT_SERVERNAME_CB, callback)
195
+ end
196
+
197
+ attach_function :SSL_get_servername, [:ssl, :int], :string
198
+ TLSEXT_NAMETYPE_host_name = 0
199
+
200
+ attach_function :SSL_set_SSL_CTX, [:ssl, :ssl_ctx], :ssl_ctx
201
+
202
+ SSL_TLSEXT_ERR_OK = 0
203
+ SSL_TLSEXT_ERR_ALERT_WARNING = 1
204
+ SSL_TLSEXT_ERR_ALERT_FATAL = 2
205
+ SSL_TLSEXT_ERR_NOACK = 3
206
+
207
+ attach_function :SSL_CTX_use_PrivateKey_file, [:ssl_ctx, :string, :int], :int, :blocking => true
208
+ attach_function :SSL_CTX_use_PrivateKey, [:ssl_ctx, :pointer], :int
209
+ attach_function :ERR_print_errors_fp, [:pointer], :void # Pointer == File Handle
210
+ attach_function :SSL_CTX_use_certificate_chain_file, [:ssl_ctx, :string], :int, :blocking => true
211
+ attach_function :SSL_CTX_use_certificate, [:ssl_ctx, :x509], :int
212
+ attach_function :SSL_CTX_set_cipher_list, [:ssl_ctx, :string], :int
213
+ attach_function :SSL_CTX_set_session_id_context, [:ssl_ctx, :string, :buffer_length], :int
214
+ attach_function :SSL_load_client_CA_file, [:string], :pointer
215
+ attach_function :SSL_CTX_set_client_CA_list, [:ssl_ctx, :pointer], :void
216
+ attach_function :SSL_CTX_load_verify_locations, [:ssl_ctx, :pointer], :int, :blocking => true
217
+
218
+ # OpenSSL before 1.0.2 do not have these methods
219
+ begin
220
+ attach_function :SSL_CTX_set_alpn_protos, [:ssl_ctx, :string, :uint], :int
221
+
222
+ OPENSSL_NPN_UNSUPPORTED = 0
223
+ OPENSSL_NPN_NEGOTIATED = 1
224
+ OPENSSL_NPN_NO_OVERLAP = 2
225
+
226
+ attach_function :SSL_select_next_proto, [:pointer, :pointer, :string, :uint, :string, :uint], :int
227
+
228
+ # array of str, unit8 out,uint8 in, *arg
229
+ callback :alpn_select_cb, [:ssl, :pointer, :pointer, :string, :uint, :pointer], :int
230
+ attach_function :SSL_CTX_set_alpn_select_cb, [:ssl_ctx, :alpn_select_cb, :pointer], :void
231
+
232
+ attach_function :SSL_get0_alpn_selected, [:ssl, :pointer, :pointer], :void
233
+ ALPN_SUPPORTED = true
234
+ rescue FFI::NotFoundError
235
+ ALPN_SUPPORTED = false
236
+ end
237
+
238
+
239
+ # Deconstructor
240
+ attach_function :SSL_CTX_free, [:ssl_ctx], :void
241
+
242
+
243
+ PrivateMaterials = <<-keystr
244
+ -----BEGIN RSA PRIVATE KEY-----
245
+ MIICXAIBAAKBgQDCYYhcw6cGRbhBVShKmbWm7UVsEoBnUf0cCh8AX+MKhMxwVDWV
246
+ Igdskntn3cSJjRtmgVJHIK0lpb/FYHQB93Ohpd9/Z18pDmovfFF9nDbFF0t39hJ/
247
+ AqSzFB3GiVPoFFZJEE1vJqh+3jzsSF5K56bZ6azz38VlZgXeSozNW5bXkQIDAQAB
248
+ AoGALA89gIFcr6BIBo8N5fL3aNHpZXjAICtGav+kTUpuxSiaym9cAeTHuAVv8Xgk
249
+ H2Wbq11uz+6JMLpkQJH/WZ7EV59DPOicXrp0Imr73F3EXBfR7t2EQDYHPMthOA1D
250
+ I9EtCzvV608Ze90hiJ7E3guGrGppZfJ+eUWCPgy8CZH1vRECQQDv67rwV/oU1aDo
251
+ 6/+d5nqjeW6mWkGqTnUU96jXap8EIw6B+0cUKskwx6mHJv+tEMM2748ZY7b0yBlg
252
+ w4KDghbFAkEAz2h8PjSJG55LwqmXih1RONSgdN9hjB12LwXL1CaDh7/lkEhq0PlK
253
+ PCAUwQSdM17Sl0Xxm2CZiekTSlwmHrtqXQJAF3+8QJwtV2sRJp8u2zVe37IeH1cJ
254
+ xXeHyjTzqZ2803fnjN2iuZvzNr7noOA1/Kp+pFvUZUU5/0G2Ep8zolPUjQJAFA7k
255
+ xRdLkzIx3XeNQjwnmLlncyYPRv+qaE3FMpUu7zftuZBnVCJnvXzUxP3vPgKTlzGa
256
+ dg5XivDRfsV+okY5uQJBAMV4FesUuLQVEKb6lMs7rzZwpeGQhFDRfywJzfom2TLn
257
+ 2RdJQQ3dcgnhdVDgt5o1qkmsqQh8uJrJ9SdyLIaZQIc=
258
+ -----END RSA PRIVATE KEY-----
259
+ -----BEGIN CERTIFICATE-----
260
+ MIID6TCCA1KgAwIBAgIJANm4W/Tzs+s+MA0GCSqGSIb3DQEBBQUAMIGqMQswCQYD
261
+ VQQGEwJVUzERMA8GA1UECBMITmV3IFlvcmsxETAPBgNVBAcTCE5ldyBZb3JrMRYw
262
+ FAYDVQQKEw1TdGVhbWhlYXQubmV0MRQwEgYDVQQLEwtFbmdpbmVlcmluZzEdMBsG
263
+ A1UEAxMUb3BlbmNhLnN0ZWFtaGVhdC5uZXQxKDAmBgkqhkiG9w0BCQEWGWVuZ2lu
264
+ ZWVyaW5nQHN0ZWFtaGVhdC5uZXQwHhcNMDYwNTA1MTcwNjAzWhcNMjQwMjIwMTcw
265
+ NjAzWjCBqjELMAkGA1UEBhMCVVMxETAPBgNVBAgTCE5ldyBZb3JrMREwDwYDVQQH
266
+ EwhOZXcgWW9yazEWMBQGA1UEChMNU3RlYW1oZWF0Lm5ldDEUMBIGA1UECxMLRW5n
267
+ aW5lZXJpbmcxHTAbBgNVBAMTFG9wZW5jYS5zdGVhbWhlYXQubmV0MSgwJgYJKoZI
268
+ hvcNAQkBFhllbmdpbmVlcmluZ0BzdGVhbWhlYXQubmV0MIGfMA0GCSqGSIb3DQEB
269
+ AQUAA4GNADCBiQKBgQDCYYhcw6cGRbhBVShKmbWm7UVsEoBnUf0cCh8AX+MKhMxw
270
+ VDWVIgdskntn3cSJjRtmgVJHIK0lpb/FYHQB93Ohpd9/Z18pDmovfFF9nDbFF0t3
271
+ 9hJ/AqSzFB3GiVPoFFZJEE1vJqh+3jzsSF5K56bZ6azz38VlZgXeSozNW5bXkQID
272
+ AQABo4IBEzCCAQ8wHQYDVR0OBBYEFPJvPd1Fcmd8o/Tm88r+NjYPICCkMIHfBgNV
273
+ HSMEgdcwgdSAFPJvPd1Fcmd8o/Tm88r+NjYPICCkoYGwpIGtMIGqMQswCQYDVQQG
274
+ EwJVUzERMA8GA1UECBMITmV3IFlvcmsxETAPBgNVBAcTCE5ldyBZb3JrMRYwFAYD
275
+ VQQKEw1TdGVhbWhlYXQubmV0MRQwEgYDVQQLEwtFbmdpbmVlcmluZzEdMBsGA1UE
276
+ AxMUb3BlbmNhLnN0ZWFtaGVhdC5uZXQxKDAmBgkqhkiG9w0BCQEWGWVuZ2luZWVy
277
+ aW5nQHN0ZWFtaGVhdC5uZXSCCQDZuFv087PrPjAMBgNVHRMEBTADAQH/MA0GCSqG
278
+ SIb3DQEBBQUAA4GBAC1CXey/4UoLgJiwcEMDxOvW74plks23090iziFIlGgcIhk0
279
+ Df6hTAs7H3MWww62ddvR8l07AWfSzSP5L6mDsbvq7EmQsmPODwb6C+i2aF3EDL8j
280
+ uw73m4YIGI0Zw2XdBpiOGkx2H56Kya6mJJe/5XORZedh1wpI7zki01tHYbcy
281
+ -----END CERTIFICATE-----
282
+ keystr
283
+
284
+
285
+ BuiltinPasswdCB = FFI::Function.new(:int, [:pointer, :int, :int, :pointer]) do |buffer, len, flag, data|
286
+ buffer.write_string('kittycat')
287
+ 8
288
+ end
289
+
290
+ # Locking isn't provided as long as all writes are done on the same thread.
291
+ # This is my main use case. Happy to enable it if someone requires it and can
292
+ # get it to work on MRI Ruby (Currently only works on JRuby and Rubinius)
293
+ # as MRI callbacks occur on a thread pool?
294
+
295
+ #CRYPTO_LOCK = 0x1
296
+ #LockingCB = FFI::Function.new(:void, [:int, :int, :string, :int]) do |mode, type, file, line|
297
+ # if (mode & CRYPTO_LOCK) != 0
298
+ # SSL_LOCKS[type].lock
299
+ # else
300
+ # Unlock a lock
301
+ # SSL_LOCKS[type].unlock
302
+ # end
303
+ #end
304
+ #ThreadIdCB = FFI::Function.new(:ulong, []) do
305
+ # Thread.current.object_id
306
+ #end
307
+
308
+
309
+ # INIT CODE
310
+ @init_required ||= false
311
+ unless @init_required
312
+ if OPENSSL_V1_1
313
+ self.OPENSSL_init_ssl(OPENSSL_INIT_LOAD_SSL_STRINGS, ::FFI::Pointer::NULL)
314
+ else
315
+ self.SSL_load_error_strings
316
+ self.SSL_library_init
317
+ self.ERR_load_crypto_strings
318
+ end
319
+
320
+ # Setup multi-threaded support
321
+ #SSL_LOCKS = []
322
+ #num_locks = self.CRYPTO_num_locks
323
+ #num_locks.times { SSL_LOCKS << Mutex.new }
324
+
325
+ #self.CRYPTO_set_locking_callback(LockingCB)
326
+ #self.CRYPTO_set_id_callback(ThreadIdCB)
327
+
328
+
329
+ bio = self.BIO_new_mem_buf(PrivateMaterials, PrivateMaterials.bytesize)
330
+
331
+ # Get the private key structure
332
+ pointer = FFI::MemoryPointer.new(:pointer)
333
+ self.PEM_read_bio_PrivateKey(bio, pointer, BuiltinPasswdCB, nil)
334
+ DEFAULT_PRIVATE = pointer.get_pointer(0)
335
+
336
+ # Get the certificate structure
337
+ pointer = FFI::MemoryPointer.new(:pointer)
338
+ self.PEM_read_bio_X509(bio, pointer, nil, nil)
339
+ DEFAULT_CERT = pointer.get_pointer(0)
340
+
341
+ self.BIO_free(bio)
342
+
343
+ @init_required = true
344
+ end
345
+
346
+
347
+
348
+
349
+ # Save RAM by releasing read and write buffers when they're empty
350
+ SSL_MODE_RELEASE_BUFFERS = 0x00000010
351
+ SSL_OP_ALL = 0x80000BFF
352
+ SSL_FILETYPE_PEM = 1
353
+
354
+ class Context
355
+ # Based on information from https://raymii.org/s/tutorials/Strong_SSL_Security_On_nginx.html
356
+ 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'
357
+ SESSION = 'ruby-tls'
358
+
359
+
360
+ ALPN_LOOKUP = ::Concurrent::Map.new
361
+ ALPN_Select_CB = FFI::Function.new(:int, [
362
+ # array of str, unit8 out,uint8 in, *arg
363
+ :pointer, :pointer, :pointer, :string, :uint, :pointer
364
+ ]) do |ssl_p, out, outlen, inp, inlen, arg|
365
+ ssl = Box::InstanceLookup[ssl_p.address]
366
+ return SSL::SSL_TLSEXT_ERR_ALERT_FATAL unless ssl
367
+
368
+ protos = ssl.context.alpn_str
369
+ status = SSL.SSL_select_next_proto(out, outlen, protos, protos.length, inp, inlen)
370
+ ssl.negotiated
371
+
372
+ case status
373
+ when SSL::OPENSSL_NPN_UNSUPPORTED
374
+ SSL::SSL_TLSEXT_ERR_ALERT_FATAL
375
+ when SSL::OPENSSL_NPN_NEGOTIATED
376
+ SSL::SSL_TLSEXT_ERR_OK
377
+ when SSL::OPENSSL_NPN_NO_OVERLAP
378
+ SSL::SSL_TLSEXT_ERR_ALERT_WARNING
379
+ end
380
+ end
381
+
382
+ def initialize(server, options = {})
383
+ @is_server = server
384
+
385
+ if @is_server
386
+ @ssl_ctx = SSL.SSL_CTX_new(SSL.TLS_server_method)
387
+ set_private_key(options[:private_key] || SSL::DEFAULT_PRIVATE)
388
+ set_certificate(options[:cert_chain] || SSL::DEFAULT_CERT)
389
+ set_client_ca(options[:client_ca])
390
+ else
391
+ @ssl_ctx = SSL.SSL_CTX_new(SSL.TLS_client_method)
392
+ end
393
+
394
+ SSL.SSL_CTX_set_options(@ssl_ctx, SSL::SSL_OP_ALL)
395
+ SSL.SSL_CTX_set_mode(@ssl_ctx, SSL::SSL_MODE_RELEASE_BUFFERS)
396
+
397
+ SSL.SSL_CTX_set_cipher_list(@ssl_ctx, options[:ciphers] || CIPHERS)
398
+ @alpn_set = false
399
+
400
+ version = options[:version]
401
+ if version
402
+ vresult = set_min_proto_version(version)
403
+ raise "#{version} is unsupported" unless vresult
404
+ end
405
+
406
+ if @is_server
407
+ SSL.SSL_CTX_sess_set_cache_size(@ssl_ctx, 128)
408
+ SSL.SSL_CTX_set_session_id_context(@ssl_ctx, SESSION, 8)
409
+
410
+ if SSL::ALPN_SUPPORTED && options[:protocols]
411
+ @alpn_str = Context.build_alpn_string(options[:protocols])
412
+ SSL.SSL_CTX_set_alpn_select_cb(@ssl_ctx, ALPN_Select_CB, nil)
413
+ @alpn_set = true
414
+ end
415
+ else
416
+ set_private_key(options[:private_key])
417
+ set_certificate(options[:cert_chain])
418
+
419
+ # Check for ALPN support
420
+ if SSL::ALPN_SUPPORTED && options[:protocols]
421
+ protocols = Context.build_alpn_string(options[:protocols])
422
+ @alpn_set = SSL.SSL_CTX_set_alpn_protos(@ssl_ctx, protocols, protocols.length) == 0
423
+ end
424
+ end
425
+ end
426
+
427
+ # Version can be one of:
428
+ # :SSL3, :TLS1, :TLS1_1, :TLS1_2, :TLS1_3, :TLS_MAX
429
+ if SSL::VERSION_SUPPORTED
430
+ def set_min_proto_version(version)
431
+ num = SSL.const_get("#{version}_VERSION")
432
+ SSL.SSL_CTX_set_min_proto_version(@ssl_ctx, num) == 1
433
+ rescue NameError
434
+ false
435
+ end
436
+
437
+ def set_max_proto_version(version)
438
+ num = SSL.const_get("#{version}_VERSION")
439
+ SSL.SSL_CTX_set_max_proto_version(@ssl_ctx, num) == 1
440
+ rescue NameError
441
+ false
442
+ end
443
+ else
444
+ def set_min_proto_version(version); false; end
445
+ def set_max_proto_version(version); false; end
446
+ end
447
+
448
+ def cleanup
449
+ if @ssl_ctx
450
+ SSL.SSL_CTX_free(@ssl_ctx)
451
+ @ssl_ctx = nil
452
+ end
453
+ end
454
+
455
+ attr_reader :is_server
456
+ attr_reader :ssl_ctx
457
+ attr_reader :alpn_set
458
+ attr_reader :alpn_str
459
+
460
+ def add_server_name_indication
461
+ raise 'only valid for server mode context' unless @is_server
462
+ SSL.SSL_CTX_set_tlsext_servername_callback(@ssl_ctx, ServerNameCB)
463
+ end
464
+
465
+ ServerNameCB = FFI::Function.new(:int, [:pointer, :pointer, :pointer]) do |ssl, _, _|
466
+ ruby_ssl = Box::InstanceLookup[ssl.address]
467
+ return SSL::SSL_TLSEXT_ERR_NOACK unless ruby_ssl
468
+
469
+ ctx = ruby_ssl.hosts[SSL.SSL_get_servername(ssl, SSL::TLSEXT_NAMETYPE_host_name)]
470
+ if ctx
471
+ SSL.SSL_set_SSL_CTX(ssl, ctx.ssl_ctx)
472
+ SSL::SSL_TLSEXT_ERR_OK
473
+ else
474
+ SSL::SSL_TLSEXT_ERR_ALERT_FATAL
475
+ end
476
+ end
477
+
478
+
479
+ private
480
+
481
+
482
+ def self.build_alpn_string(protos)
483
+ protocols = String.new.force_encoding('ASCII-8BIT')
484
+ protos.each do |prot|
485
+ protocol = prot.to_s
486
+ protocols << protocol.length
487
+ protocols << protocol
488
+ end
489
+ protocols
490
+ end
491
+
492
+ def set_private_key(key)
493
+ err = if key.is_a? FFI::Pointer
494
+ SSL.SSL_CTX_use_PrivateKey(@ssl_ctx, key)
495
+ elsif key && File.file?(key)
496
+ SSL.SSL_CTX_use_PrivateKey_file(@ssl_ctx, key, SSL_FILETYPE_PEM)
497
+ else
498
+ 1
499
+ end
500
+
501
+ # Check for errors
502
+ if err <= 0
503
+ # TODO:: ERR_print_errors_fp or ERR_print_errors
504
+ # So we can properly log the issue
505
+ cleanup
506
+ raise 'invalid private key or file not found'
507
+ end
508
+ end
509
+
510
+ def set_certificate(cert)
511
+ err = if cert.is_a? FFI::Pointer
512
+ SSL.SSL_CTX_use_certificate(@ssl_ctx, cert)
513
+ elsif cert && File.file?(cert)
514
+ SSL.SSL_CTX_use_certificate_chain_file(@ssl_ctx, cert)
515
+ else
516
+ 1
517
+ end
518
+
519
+ if err <= 0
520
+ cleanup
521
+ raise 'invalid certificate or file not found'
522
+ end
523
+ end
524
+
525
+ def set_client_ca(ca)
526
+ return unless ca
527
+
528
+ if File.file?(ca) && (ca_ptr = SSL.SSL_load_client_CA_file(ca))
529
+ # there is no error checking provided by SSL_CTX_set_client_CA_list
530
+ SSL.SSL_CTX_set_client_CA_list(@ssl_ctx, ca_ptr)
531
+ else
532
+ cleanup
533
+ raise 'invalid ca certificate or file not found'
534
+ end
535
+ end
536
+ end
537
+
538
+
539
+
540
+
541
+ class Box
542
+ InstanceLookup = ::Concurrent::Map.new
543
+
544
+ READ_BUFFER = 2048
545
+
546
+ SSL_VERIFY_PEER = 0x01
547
+ SSL_VERIFY_CLIENT_ONCE = 0x04
548
+ def initialize(server, transport, options = {})
549
+ @ready = true
550
+
551
+ @handshake_completed = false
552
+ @handshake_signaled = false
553
+ @negotiated = false
554
+ @transport = transport
555
+
556
+ @read_buffer = FFI::MemoryPointer.new(:char, READ_BUFFER, false)
557
+
558
+ @is_server = server
559
+ @context = Context.new(server, options)
560
+ @bioRead = SSL.BIO_new(SSL.BIO_s_mem)
561
+ @bioWrite = SSL.BIO_new(SSL.BIO_s_mem)
562
+ @ssl = SSL.SSL_new(@context.ssl_ctx)
563
+ SSL.SSL_set_bio(@ssl, @bioRead, @bioWrite)
564
+
565
+ @write_queue = []
566
+
567
+ InstanceLookup[@ssl.address] = self
568
+
569
+ @alpn_fallback = options[:fallback]
570
+ if options[:verify_peer]
571
+ SSL.SSL_set_verify(@ssl, SSL_VERIFY_PEER | SSL_VERIFY_CLIENT_ONCE, VerifyCB)
572
+ end
573
+
574
+ # Add Server Name Indication (SNI) for client connections
575
+ if options[:host_name]
576
+ if server
577
+ @hosts = ::Concurrent::Map.new
578
+ @hosts[options[:host_name].to_s] = @context
579
+ @context.add_server_name_indication
580
+ else
581
+ SSL.SSL_set_tlsext_host_name(@ssl, options[:host_name])
582
+ end
583
+ end
584
+
585
+ SSL.SSL_connect(@ssl) unless server
586
+ end
587
+
588
+
589
+ def add_host(host_name:, **options)
590
+ raise 'Server Name Indication (SNI) not configured for default host' unless @hosts
591
+ raise 'only valid for server mode context' unless @is_server
592
+ context = Context.new(true, options)
593
+ @hosts[host_name.to_s] = context
594
+ context.add_server_name_indication
595
+ nil
596
+ end
597
+
598
+ # Careful with this.
599
+ # If you remove all the hosts you'll end up with a segfault
600
+ def remove_host(host_name)
601
+ raise 'Server Name Indication (SNI) not configured for default host' unless @hosts
602
+ raise 'only valid for server mode context' unless @is_server
603
+ context = @hosts[host_name.to_s]
604
+ if context
605
+ @hosts.delete(host_name.to_s)
606
+ context.cleanup
607
+ end
608
+ nil
609
+ end
610
+
611
+
612
+ attr_reader :is_server, :context
613
+ attr_reader :handshake_completed
614
+ attr_reader :hosts
615
+
616
+
617
+ def get_peer_cert
618
+ return '' unless @ready
619
+ SSL.SSL_get_peer_certificate(@ssl)
620
+ end
621
+
622
+ def negotiated_protocol
623
+ return nil unless @context.alpn_set
624
+
625
+ proto = FFI::MemoryPointer.new(:pointer, 1, true)
626
+ len = FFI::MemoryPointer.new(:uint, 1, true)
627
+ SSL.SSL_get0_alpn_selected(@ssl, proto, len)
628
+
629
+ resp = proto.get_pointer(0)
630
+ if resp.address == 0
631
+ :failed
632
+ else
633
+ length = len.get_uint(0)
634
+ resp.read_string(length).to_sym
635
+ end
636
+ end
637
+
638
+ def start
639
+ return unless @ready
640
+
641
+ dispatch_cipher_text
642
+ end
643
+
644
+ def encrypt(data)
645
+ return unless @ready
646
+
647
+ wrote = put_plain_text data
648
+ if wrote < 0
649
+ @transport.close_cb
650
+ else
651
+ dispatch_cipher_text
652
+ end
653
+ end
654
+
655
+ SSL_ERROR_WANT_READ = 2
656
+ SSL_ERROR_SSL = 1
657
+ def decrypt(data)
658
+ return unless @ready
659
+
660
+ put_cipher_text data
661
+
662
+ if not SSL.SSL_is_init_finished(@ssl)
663
+ resp = @is_server ? SSL.SSL_accept(@ssl) : SSL.SSL_connect(@ssl)
664
+
665
+ if resp < 0
666
+ err_code = SSL.SSL_get_error(@ssl, resp)
667
+ if err_code != SSL_ERROR_WANT_READ
668
+ @transport.close_cb if err_code == SSL_ERROR_SSL
669
+ return
670
+ end
671
+ end
672
+
673
+ @handshake_completed = true
674
+ signal_handshake unless @handshake_signaled
675
+ end
676
+
677
+ while true do
678
+ size = get_plain_text(@read_buffer, READ_BUFFER)
679
+ if size > 0
680
+ @transport.dispatch_cb @read_buffer.read_string(size)
681
+ else
682
+ break
683
+ end
684
+ end
685
+
686
+ dispatch_cipher_text
687
+ end
688
+
689
+ def signal_handshake
690
+ @handshake_signaled = true
691
+
692
+ # Check protocol support here
693
+ if @context.alpn_set
694
+ proto = negotiated_protocol
695
+
696
+ if proto == :failed
697
+ if @negotiated
698
+ # We should shutdown if this is the case
699
+ @transport.close_cb
700
+ return
701
+ elsif @alpn_fallback
702
+ # Client or Server with a client that doesn't support ALPN
703
+ proto = @alpn_fallback.to_sym
704
+ end
705
+ end
706
+ else
707
+ proto = nil
708
+ end
709
+
710
+ @transport.handshake_cb(proto)
711
+ end
712
+
713
+ def negotiated
714
+ @negotiated = true
715
+ end
716
+
717
+ SSL_RECEIVED_SHUTDOWN = 2
718
+ def cleanup
719
+ return unless @ready
720
+ @ready = false
721
+
722
+ InstanceLookup.delete @ssl.address
723
+
724
+ if (SSL.SSL_get_shutdown(@ssl) & SSL_RECEIVED_SHUTDOWN) != 0
725
+ SSL.SSL_shutdown @ssl
726
+ else
727
+ SSL.SSL_clear @ssl
728
+ end
729
+
730
+ SSL.SSL_free @ssl
731
+
732
+ if @hosts
733
+ @hosts.each_value do |context|
734
+ context.cleanup
735
+ end
736
+ @hosts = nil
737
+ else
738
+ @context.cleanup
739
+ end
740
+ end
741
+
742
+ # Called from class level callback function
743
+ def verify(cert)
744
+ @transport.verify_cb(cert) == true ? 1 : 0
745
+ end
746
+
747
+
748
+ private
749
+
750
+
751
+ def get_plain_text(buffer, ready)
752
+ # Read the buffered clear text
753
+ size = SSL.SSL_read(@ssl, buffer, ready)
754
+ if size >= 0
755
+ size
756
+ else
757
+ SSL.SSL_get_error(@ssl, size) == SSL_ERROR_WANT_READ ? 0 : -1
758
+ end
759
+ end
760
+
761
+ VerifyCB = FFI::Function.new(:int, [:int, :pointer]) do |preverify_ok, x509_store|
762
+ x509 = SSL.X509_STORE_CTX_get_current_cert(x509_store)
763
+ ssl = SSL.X509_STORE_CTX_get_ex_data(x509_store, SSL.SSL_get_ex_data_X509_STORE_CTX_idx)
764
+
765
+ bio_out = SSL.BIO_new(SSL.BIO_s_mem)
766
+ SSL.PEM_write_bio_X509(bio_out, x509)
767
+
768
+ len = SSL.BIO_pending(bio_out)
769
+ buffer = FFI::MemoryPointer.new(:char, len, false)
770
+ size = SSL.BIO_read(bio_out, buffer, len)
771
+
772
+ # THis is the callback into the ruby class
773
+ result = InstanceLookup[ssl.address].verify(buffer.read_string(size))
774
+
775
+ SSL.BIO_free(bio_out)
776
+ result
777
+ end
778
+
779
+
780
+ def pending_data(bio)
781
+ SSL.BIO_pending(bio)
782
+ end
783
+
784
+ def get_cipher_text(buffer, length)
785
+ SSL.BIO_read(@bioWrite, buffer, length)
786
+ end
787
+
788
+ def put_cipher_text(data)
789
+ len = data.bytesize
790
+ wrote = SSL.BIO_write(@bioRead, data, len)
791
+ wrote == len
792
+ end
793
+
794
+
795
+ SSL_ERROR_WANT_WRITE = 3
796
+ def put_plain_text(data)
797
+ @write_queue.push(data) if data
798
+ return 0 unless SSL.SSL_is_init_finished(@ssl)
799
+
800
+ fatal = false
801
+ did_work = false
802
+
803
+ while !@write_queue.empty? do
804
+ data = @write_queue.pop
805
+ len = data.bytesize
806
+
807
+ wrote = SSL.SSL_write(@ssl, data, len)
808
+
809
+ if wrote > 0
810
+ did_work = true;
811
+ else
812
+ err_code = SSL.SSL_get_error(@ssl, wrote)
813
+ if (err_code != SSL_ERROR_WANT_READ) && (err_code != SSL_ERROR_WANT_WRITE)
814
+ fatal = true
815
+ else
816
+ # Not fatal - add back to the queue
817
+ @write_queue.unshift data
818
+ end
819
+
820
+ break
821
+ end
822
+ end
823
+
824
+ if did_work
825
+ 1
826
+ elsif fatal
827
+ -1
828
+ else
829
+ 0
830
+ end
831
+ end
832
+
833
+
834
+ CIPHER_DISPATCH_FAILED = 'Cipher text dispatch failed'
835
+ def dispatch_cipher_text
836
+ begin
837
+ did_work = false
838
+
839
+ # Get all the encrypted data and transmit it
840
+ pending = pending_data(@bioWrite)
841
+ if pending > 0
842
+ buffer = FFI::MemoryPointer.new(:char, pending, false)
843
+
844
+ resp = get_cipher_text(buffer, pending)
845
+ raise CIPHER_DISPATCH_FAILED unless resp > 0
846
+
847
+ @transport.transmit_cb(buffer.read_string(resp))
848
+ did_work = true
849
+ end
850
+
851
+ # Send any queued out going data
852
+ unless @write_queue.empty?
853
+ resp = put_plain_text nil
854
+ if resp > 0
855
+ did_work = true
856
+ elsif resp < 0
857
+ @transport.close_cb
858
+ end
859
+ end
860
+ end while did_work
861
+ end
862
+ end
863
+ end
864
+ end