mt-ruby-tls 2.4.0

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