ruby-tls 1.0.3 → 2.0.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 4fc0dda5fe9d4c27285d3edd638c41042e03c38b
4
- data.tar.gz: dd6678ff4e8361f5f1cb348408493bc4df58b3ba
3
+ metadata.gz: 9bc9634a3d7fd408edb071d71b01d231173cc73a
4
+ data.tar.gz: 4594c48be9ef0577869259c2d6113fd23a996c8c
5
5
  SHA512:
6
- metadata.gz: 731a5c8d44bf7581111e49b1ad398eb976ee0b02cacd416d0ffc2b7a47f44d92f8470be37d3ffd2437b2ec1a9d47f839143de62eea3268064dba395b711bc018
7
- data.tar.gz: 46a86dc3bb5ff2bd117d856cd8cc33355dd735a335127d34e723bc6a49f7bb83458faad0c807f9b2de0f80979d0a793dcc9eef5a819f4a4ed7c2fa1730b2486a
6
+ metadata.gz: 13c78e049178f73860d056c76900feb1aa9ab5f814db1d7e52566ff4761b1b1f3440a43683cb12ece6ef740b4193be5b0803ca9ce9c61fedb14eef8f3bcec06f
7
+ data.tar.gz: f9bb0ab94cc497721fe5fbf4354ccbac997d9249dc13539b1bc09114b0aecfb02ca597093ea67b8ddcda726ab81bb4a7cc6bee7396f0a1c4d719435155335588
data/README.md CHANGED
@@ -1,71 +1,100 @@
1
- # ruby-tls
2
-
3
- Ruby-TLS decouples the management of encrypted communications, putting you in charge of the transport layer. It can be used as an alternative to Ruby's SSLSocket.
4
-
5
- [![Build Status](https://travis-ci.org/cotag/ruby-tls.png?branch=master)](https://travis-ci.org/cotag/ruby-tls)
6
-
7
-
8
- ## Install the gem
9
-
10
- Install it with [RubyGems](https://rubygems.org/)
11
-
12
- gem install ruby-tls
13
-
14
- or add this to your Gemfile if you use [Bundler](http://gembundler.com/):
15
-
16
- gem "ruby-tls"
17
-
18
-
19
- Windows users will require an installation of OpenSSL (32bit or 64bit matching the Ruby installation) and be setup with [Ruby Installers DevKit](http://rubyinstaller.org/downloads/)
20
-
21
-
22
- ## Usage
23
-
24
- ```ruby
25
- require 'rubygems'
26
- require 'ruby-tls'
27
-
28
- #
29
- # Create a new TLS connection and attach callbacks
30
- #
31
- connection = RubyTls::Connection.new do |state|
32
- state.handshake_cb do
33
- puts "TLS handshake complete"
34
- end
35
-
36
- state.transmit_cb do |data|
37
- puts "Data for transmission to remote"
38
- end
39
-
40
- state.dispatch_cb do |data|
41
- puts "Clear text data that has been decrypted"
42
- end
43
-
44
- state.close_cb do |inst, data|
45
- puts "An error occurred, the transport layer should be shutdown"
46
- end
47
- end
48
-
49
- #
50
- # Init the handshake
51
- #
52
- connection.start
53
-
54
- #
55
- # Start sending data to the remote, this will trigger the
56
- # transmit_cb with encrypted data to send.
57
- #
58
- connection.encrypt('client request')
59
-
60
- #
61
- # Similarly when data is received from the remote it should be
62
- # passed to connection.decrypt where the dispatch_cb will be
63
- # called with clear text
64
- #
65
- ```
66
-
67
-
68
- ## License and copyright
69
-
70
- The core SSL code was originally extracted and isolated from [EventMachine](https://github.com/eventmachine/eventmachine/). So is licensed under the same terms, either the GPL or Ruby's License.
71
-
1
+ # ruby-tls
2
+
3
+ Ruby-TLS decouples the management of encrypted communications, putting you in charge of the transport layer. It can be used as an alternative to Ruby's SSLSocket.
4
+
5
+ [![Build Status](https://travis-ci.org/cotag/ruby-tls.png?branch=master)](https://travis-ci.org/cotag/ruby-tls)
6
+
7
+
8
+ ## Install the gem
9
+
10
+ Install it with [RubyGems](https://rubygems.org/)
11
+
12
+ gem install ruby-tls
13
+
14
+ or add this to your Gemfile if you use [Bundler](http://gembundler.com/):
15
+
16
+ gem "ruby-tls"
17
+
18
+
19
+ Windows users will require an installation of OpenSSL (32bit or 64bit matching the Ruby installation)
20
+
21
+
22
+ ## Usage
23
+
24
+ ```ruby
25
+ require 'rubygems'
26
+ require 'ruby-tls'
27
+
28
+ class transport
29
+ def initialize
30
+ is_server = true
31
+ callback_obj = self
32
+ options = {
33
+ verify_peer: true,
34
+ private_key: '/file/path.pem',
35
+ cert_chain: '/file/path.crt',
36
+ ciphers: 'ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-RC4-SHA:ECDHE-RSA-AES128-SHA:AES128-GCM-SHA256:RC4:HIGH:!MD5:!aNULL:!EDH:!CAMELLIA:@STRENGTH' # (default)
37
+ }
38
+ @ssl_layer = RubyTls::SSL::Box.new(is_server, callback_obj, options)
39
+ end
40
+
41
+ def close_cb
42
+ puts "The transport layer should be shutdown"
43
+ end
44
+
45
+ def dispatch_cb(data)
46
+ puts "Clear text data that has been decrypted"
47
+ end
48
+
49
+ def transmit_cb(data)
50
+ puts "Encrypted data for transmission to remote"
51
+ # @tcp.send data
52
+ end
53
+
54
+ def handshake_cb
55
+ puts "initial handshake has completed"
56
+ end
57
+
58
+ def verify_cb(cert)
59
+ # Return true or false
60
+ is_cert_valid? cert
61
+ end
62
+
63
+ def start_tls
64
+ # Start SSL negotiation when you are ready
65
+ @ssl_layer.start
66
+ end
67
+
68
+ def send(data)
69
+ @ssl_layer.encrypt(data)
70
+ end
71
+ end
72
+
73
+ #
74
+ # Create a new TLS connection
75
+ #
76
+ connection = transport.new
77
+
78
+ #
79
+ # Init the handshake
80
+ #
81
+ connection.start_tls
82
+
83
+ #
84
+ # Start sending data to the remote, this will trigger the
85
+ # transmit_cb with encrypted data to send.
86
+ #
87
+ connection.send('client request')
88
+
89
+ #
90
+ # Similarly when data is received from the remote it should be
91
+ # passed to connection.decrypt where the dispatch_cb will be
92
+ # called with clear text
93
+ #
94
+ ```
95
+
96
+
97
+ ## License and copyright
98
+
99
+ MIT
100
+
@@ -1,7 +1,5 @@
1
- require "ffi" # Bindings to C libraries
2
-
3
- require "ruby-tls/ext" # Loads the ext using FFI
4
- require "ruby-tls/connection" # The ruby abstraction
5
-
6
- module RubyTls
7
- end
1
+
2
+ require "ruby-tls/ssl" # Loads OpenSSL using FFI
3
+
4
+ module RubyTls
5
+ end
@@ -0,0 +1,582 @@
1
+ require 'ffi'
2
+ require 'ffi-compiler/loader'
3
+ require 'thread'
4
+ require 'thread_safe'
5
+
6
+
7
+ module RubyTls
8
+ module SSL
9
+ extend FFI::Library
10
+ if FFI::Platform.windows?
11
+ ffi_lib 'libeay32', 'ssleay32'
12
+ else
13
+ ffi_lib 'ssl'
14
+ end
15
+
16
+ attach_function :SSL_library_init, [], :int
17
+ attach_function :SSL_load_error_strings, [], :void
18
+ attach_function :ERR_load_crypto_strings, [], :void
19
+
20
+
21
+ # Common structures
22
+ typedef :pointer, :user_data
23
+ typedef :pointer, :bio
24
+ typedef :pointer, :evp_key
25
+ typedef :pointer, :evp_key_pointer
26
+ typedef :pointer, :x509
27
+ typedef :pointer, :x509_pointer
28
+ typedef :pointer, :ssl
29
+ typedef :pointer, :ssl_ctx
30
+ typedef :int, :buffer_length
31
+ typedef :int, :pass_length
32
+ typedef :int, :read_write_flag
33
+
34
+
35
+ # Multi-threaded support
36
+ callback :locking_cb, [:int, :int, :string, :int], :void
37
+ callback :thread_id_cb, [], :ulong
38
+ attach_function :CRYPTO_num_locks, [], :int
39
+ attach_function :CRYPTO_set_locking_callback, [:locking_cb], :void
40
+ attach_function :CRYPTO_set_id_callback, [:thread_id_cb], :void
41
+
42
+
43
+ # InitializeDefaultCredentials
44
+ attach_function :BIO_new_mem_buf, [:string, :buffer_length], :bio
45
+ attach_function :EVP_PKEY_free, [:evp_key], :void
46
+
47
+ callback :pem_password_cb, [:pointer, :buffer_length, :read_write_flag, :user_data], :pass_length
48
+ attach_function :PEM_read_bio_PrivateKey, [:bio, :evp_key_pointer, :pem_password_cb, :user_data], :evp_key
49
+
50
+ attach_function :X509_free, [:x509], :void
51
+ attach_function :PEM_read_bio_X509, [:bio, :x509_pointer, :pem_password_cb, :user_data], :x509
52
+
53
+ attach_function :BIO_free, [:bio], :int
54
+
55
+ # CONSTANTS
56
+ SSL_ST_OK = 0x03
57
+ attach_function :SSL_state, [:ssl], :int
58
+ def self.SSL_is_init_finished(ssl)
59
+ SSL_state(ssl) == SSL_ST_OK
60
+ end
61
+
62
+ # GetPeerCert
63
+ attach_function :SSL_get_peer_certificate, [:ssl], :x509
64
+
65
+
66
+ # PutPlaintext
67
+ attach_function :SSL_write, [:ssl, :buffer_in, :buffer_length], :int
68
+ attach_function :SSL_get_error, [:ssl, :int], :int
69
+
70
+
71
+ # GetCiphertext
72
+ attach_function :BIO_read, [:bio, :buffer_out, :buffer_length], :int
73
+
74
+ # CanGetCiphertext
75
+ attach_function :BIO_ctrl, [:bio, :int, :long, :pointer], :long
76
+ BIO_CTRL_PENDING = 10 # opt - is their more data buffered?
77
+ def self.BIO_pending(bio)
78
+ BIO_ctrl(bio, BIO_CTRL_PENDING, 0, nil)
79
+ end
80
+
81
+
82
+ # GetPlaintext
83
+ attach_function :SSL_accept, [:ssl], :int
84
+ attach_function :SSL_read, [:ssl, :buffer_out, :buffer_length], :int
85
+ attach_function :SSL_pending, [:ssl], :int
86
+
87
+ # PutCiphertext
88
+ attach_function :BIO_write, [:bio, :buffer_in, :buffer_length], :int
89
+
90
+ # SelectALPNCallback
91
+ # TODO:: SSL_select_next_proto
92
+
93
+ # Deconstructor
94
+ attach_function :SSL_get_shutdown, [:ssl], :int
95
+ attach_function :SSL_shutdown, [:ssl], :int
96
+ attach_function :SSL_clear, [:ssl], :void
97
+ attach_function :SSL_free, [:ssl], :void
98
+
99
+
100
+ # Constructor
101
+ attach_function :BIO_s_mem, [], :pointer
102
+ attach_function :BIO_new, [:pointer], :bio
103
+ attach_function :SSL_new, [:ssl_ctx], :ssl
104
+ # r, w
105
+ attach_function :SSL_set_bio, [:ssl, :bio, :bio], :void
106
+
107
+ # TODO:: SSL_CTX_set_alpn_select_cb
108
+ # Will have to put a try catch around these and support when available
109
+
110
+ attach_function :SSL_set_ex_data, [:ssl, :int, :string], :int
111
+ callback :verify_callback, [:int, :x509], :int
112
+ attach_function :SSL_set_verify, [:ssl, :int, :verify_callback], :void
113
+ attach_function :SSL_connect, [:ssl], :int
114
+
115
+ # Verify callback
116
+ attach_function :X509_STORE_CTX_get_current_cert, [:pointer], :x509
117
+ attach_function :SSL_get_ex_data_X509_STORE_CTX_idx, [], :int
118
+ attach_function :X509_STORE_CTX_get_ex_data, [:pointer, :int], :ssl
119
+ attach_function :PEM_write_bio_X509, [:bio, :x509], :int
120
+
121
+
122
+ # SSL Context Class
123
+ # Constructor
124
+ attach_function :SSLv23_server_method, [], :pointer
125
+ attach_function :SSLv23_client_method, [], :pointer
126
+ attach_function :SSL_CTX_new, [:pointer], :ssl_ctx
127
+
128
+ attach_function :SSL_CTX_ctrl, [:ssl_ctx, :int, :ulong, :pointer], :long
129
+ SSL_CTRL_OPTIONS = 32
130
+ def self.SSL_CTX_set_options(ssl_ctx, op)
131
+ SSL_CTX_ctrl(ssl_ctx, SSL_CTRL_OPTIONS, op, nil)
132
+ end
133
+ SSL_CTRL_MODE = 33
134
+ def self.SSL_CTX_set_mode(ssl_ctx, op)
135
+ SSL_CTX_ctrl(ssl_ctx, SSL_CTRL_MODE, op, nil)
136
+ end
137
+ SSL_CTRL_SET_SESS_CACHE_SIZE = 42
138
+ def self.SSL_CTX_sess_set_cache_size(ssl_ctx, op)
139
+ SSL_CTX_ctrl(ssl_ctx, SSL_CTRL_SET_SESS_CACHE_SIZE, op, nil)
140
+ end
141
+
142
+ attach_function :SSL_CTX_use_PrivateKey_file, [:ssl_ctx, :string, :int], :int
143
+ attach_function :SSL_CTX_use_PrivateKey, [:ssl_ctx, :pointer], :int
144
+ attach_function :ERR_print_errors_fp, [:pointer], :void # Pointer == File Handle
145
+ attach_function :SSL_CTX_use_certificate_chain_file, [:ssl_ctx, :string], :int
146
+ attach_function :SSL_CTX_use_certificate, [:ssl_ctx, :x509], :int
147
+ attach_function :SSL_CTX_set_cipher_list, [:ssl_ctx, :string], :int
148
+ attach_function :SSL_CTX_set_session_id_context, [:ssl_ctx, :string, :buffer_length], :int
149
+
150
+ # TODO:: SSL_CTX_set_alpn_protos
151
+
152
+
153
+ # Deconstructor
154
+ attach_function :SSL_CTX_free, [:ssl_ctx], :void
155
+
156
+
157
+ PrivateMaterials = <<-keystr
158
+ -----BEGIN RSA PRIVATE KEY-----
159
+ MIICXAIBAAKBgQDCYYhcw6cGRbhBVShKmbWm7UVsEoBnUf0cCh8AX+MKhMxwVDWV
160
+ Igdskntn3cSJjRtmgVJHIK0lpb/FYHQB93Ohpd9/Z18pDmovfFF9nDbFF0t39hJ/
161
+ AqSzFB3GiVPoFFZJEE1vJqh+3jzsSF5K56bZ6azz38VlZgXeSozNW5bXkQIDAQAB
162
+ AoGALA89gIFcr6BIBo8N5fL3aNHpZXjAICtGav+kTUpuxSiaym9cAeTHuAVv8Xgk
163
+ H2Wbq11uz+6JMLpkQJH/WZ7EV59DPOicXrp0Imr73F3EXBfR7t2EQDYHPMthOA1D
164
+ I9EtCzvV608Ze90hiJ7E3guGrGppZfJ+eUWCPgy8CZH1vRECQQDv67rwV/oU1aDo
165
+ 6/+d5nqjeW6mWkGqTnUU96jXap8EIw6B+0cUKskwx6mHJv+tEMM2748ZY7b0yBlg
166
+ w4KDghbFAkEAz2h8PjSJG55LwqmXih1RONSgdN9hjB12LwXL1CaDh7/lkEhq0PlK
167
+ PCAUwQSdM17Sl0Xxm2CZiekTSlwmHrtqXQJAF3+8QJwtV2sRJp8u2zVe37IeH1cJ
168
+ xXeHyjTzqZ2803fnjN2iuZvzNr7noOA1/Kp+pFvUZUU5/0G2Ep8zolPUjQJAFA7k
169
+ xRdLkzIx3XeNQjwnmLlncyYPRv+qaE3FMpUu7zftuZBnVCJnvXzUxP3vPgKTlzGa
170
+ dg5XivDRfsV+okY5uQJBAMV4FesUuLQVEKb6lMs7rzZwpeGQhFDRfywJzfom2TLn
171
+ 2RdJQQ3dcgnhdVDgt5o1qkmsqQh8uJrJ9SdyLIaZQIc=
172
+ -----END RSA PRIVATE KEY-----
173
+ -----BEGIN CERTIFICATE-----
174
+ MIID6TCCA1KgAwIBAgIJANm4W/Tzs+s+MA0GCSqGSIb3DQEBBQUAMIGqMQswCQYD
175
+ VQQGEwJVUzERMA8GA1UECBMITmV3IFlvcmsxETAPBgNVBAcTCE5ldyBZb3JrMRYw
176
+ FAYDVQQKEw1TdGVhbWhlYXQubmV0MRQwEgYDVQQLEwtFbmdpbmVlcmluZzEdMBsG
177
+ A1UEAxMUb3BlbmNhLnN0ZWFtaGVhdC5uZXQxKDAmBgkqhkiG9w0BCQEWGWVuZ2lu
178
+ ZWVyaW5nQHN0ZWFtaGVhdC5uZXQwHhcNMDYwNTA1MTcwNjAzWhcNMjQwMjIwMTcw
179
+ NjAzWjCBqjELMAkGA1UEBhMCVVMxETAPBgNVBAgTCE5ldyBZb3JrMREwDwYDVQQH
180
+ EwhOZXcgWW9yazEWMBQGA1UEChMNU3RlYW1oZWF0Lm5ldDEUMBIGA1UECxMLRW5n
181
+ aW5lZXJpbmcxHTAbBgNVBAMTFG9wZW5jYS5zdGVhbWhlYXQubmV0MSgwJgYJKoZI
182
+ hvcNAQkBFhllbmdpbmVlcmluZ0BzdGVhbWhlYXQubmV0MIGfMA0GCSqGSIb3DQEB
183
+ AQUAA4GNADCBiQKBgQDCYYhcw6cGRbhBVShKmbWm7UVsEoBnUf0cCh8AX+MKhMxw
184
+ VDWVIgdskntn3cSJjRtmgVJHIK0lpb/FYHQB93Ohpd9/Z18pDmovfFF9nDbFF0t3
185
+ 9hJ/AqSzFB3GiVPoFFZJEE1vJqh+3jzsSF5K56bZ6azz38VlZgXeSozNW5bXkQID
186
+ AQABo4IBEzCCAQ8wHQYDVR0OBBYEFPJvPd1Fcmd8o/Tm88r+NjYPICCkMIHfBgNV
187
+ HSMEgdcwgdSAFPJvPd1Fcmd8o/Tm88r+NjYPICCkoYGwpIGtMIGqMQswCQYDVQQG
188
+ EwJVUzERMA8GA1UECBMITmV3IFlvcmsxETAPBgNVBAcTCE5ldyBZb3JrMRYwFAYD
189
+ VQQKEw1TdGVhbWhlYXQubmV0MRQwEgYDVQQLEwtFbmdpbmVlcmluZzEdMBsGA1UE
190
+ AxMUb3BlbmNhLnN0ZWFtaGVhdC5uZXQxKDAmBgkqhkiG9w0BCQEWGWVuZ2luZWVy
191
+ aW5nQHN0ZWFtaGVhdC5uZXSCCQDZuFv087PrPjAMBgNVHRMEBTADAQH/MA0GCSqG
192
+ SIb3DQEBBQUAA4GBAC1CXey/4UoLgJiwcEMDxOvW74plks23090iziFIlGgcIhk0
193
+ Df6hTAs7H3MWww62ddvR8l07AWfSzSP5L6mDsbvq7EmQsmPODwb6C+i2aF3EDL8j
194
+ uw73m4YIGI0Zw2XdBpiOGkx2H56Kya6mJJe/5XORZedh1wpI7zki01tHYbcy
195
+ -----END CERTIFICATE-----
196
+ keystr
197
+
198
+
199
+ BuiltinPasswdCB = FFI::Function.new(:int, [:pointer, :int, :int, :pointer]) do |buffer, len, flag, data|
200
+ buffer.write_string('kittycat')
201
+ 8
202
+ end
203
+
204
+ CRYPTO_LOCK = 0x1
205
+ LockingCB = FFI::Function.new(:void, [:int, :int, :string, :int]) do |mode, type, file, line|
206
+ if (mode & CRYPTO_LOCK) != 0
207
+ SSL_LOCKS[type].lock
208
+ else
209
+ # Unlock a lock
210
+ SSL_LOCKS[type].unlock
211
+ end
212
+ end
213
+
214
+ ThreadIdCB = FFI::Function.new(:ulong, []) do
215
+ Thread.current.object_id
216
+ end
217
+
218
+
219
+ # INIT CODE
220
+ @init_required ||= false
221
+ unless @init_required
222
+ self.SSL_load_error_strings
223
+ self.SSL_library_init
224
+ self.ERR_load_crypto_strings
225
+
226
+
227
+ # Setup multi-threaded support
228
+ SSL_LOCKS = []
229
+ num_locks = self.CRYPTO_num_locks
230
+ num_locks.times { SSL_LOCKS << Mutex.new }
231
+
232
+ self.CRYPTO_set_locking_callback(LockingCB)
233
+ self.CRYPTO_set_id_callback(ThreadIdCB)
234
+
235
+
236
+ bio = self.BIO_new_mem_buf(PrivateMaterials, PrivateMaterials.bytesize)
237
+
238
+ # Get the private key structure
239
+ pointer = FFI::MemoryPointer.new(:pointer)
240
+ self.PEM_read_bio_PrivateKey(bio, pointer, BuiltinPasswdCB, nil)
241
+ DEFAULT_PRIVATE = pointer.get_pointer(0)
242
+
243
+ # Get the certificate structure
244
+ pointer = FFI::MemoryPointer.new(:pointer)
245
+ self.PEM_read_bio_X509(bio, pointer, nil, nil)
246
+ DEFAULT_CERT = pointer.get_pointer(0)
247
+
248
+ self.BIO_free(bio)
249
+
250
+ @init_required = true
251
+ end
252
+
253
+
254
+
255
+
256
+ # Save RAM by releasing read and write buffers when they're empty
257
+ SSL_MODE_RELEASE_BUFFERS = 0x00000010
258
+ SSL_OP_ALL = 0x80000BFF
259
+ SSL_FILETYPE_PEM = 1
260
+
261
+ class Context
262
+ CIPHERS = 'ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-RC4-SHA:ECDHE-RSA-AES128-SHA:AES128-GCM-SHA256:RC4:HIGH:!MD5:!aNULL:!EDH:!CAMELLIA:@STRENGTH'.freeze
263
+ SESSION = 'ruby-tls'.freeze
264
+
265
+ def initialize(server, options = {})
266
+ @is_server = server
267
+ @ssl_ctx = SSL.SSL_CTX_new(server ? SSL.SSLv23_server_method : SSL.SSLv23_client_method)
268
+ SSL.SSL_CTX_set_options(@ssl_ctx, SSL::SSL_OP_ALL)
269
+ SSL.SSL_CTX_set_mode(@ssl_ctx, SSL::SSL_MODE_RELEASE_BUFFERS)
270
+
271
+ if @is_server
272
+ set_private_key(options[:private_key] || SSL::DEFAULT_PRIVATE)
273
+ set_certificate(options[:cert_chain] || SSL::DEFAULT_CERT)
274
+ end
275
+
276
+ SSL.SSL_CTX_set_cipher_list(@ssl_ctx, options[:ciphers] || CIPHERS)
277
+
278
+ if @is_server
279
+ SSL.SSL_CTX_sess_set_cache_size(@ssl_ctx, 128)
280
+ SSL.SSL_CTX_set_session_id_context(@ssl_ctx, SESSION, 8)
281
+ else
282
+ set_private_key(options[:private_key])
283
+ set_certificate(options[:cert_chain])
284
+ end
285
+
286
+ # TODO:: Check for ALPN support
287
+ end
288
+
289
+ def cleanup
290
+ if @ssl_ctx
291
+ SSL.SSL_CTX_free(@ssl_ctx)
292
+ @ssl_ctx = nil
293
+ end
294
+ end
295
+
296
+ attr_reader :is_server
297
+ attr_reader :ssl_ctx
298
+
299
+
300
+ private
301
+
302
+
303
+ def set_private_key(key)
304
+ err = if key.is_a? FFI::Pointer
305
+ SSL.SSL_CTX_use_PrivateKey(@ssl_ctx, key)
306
+ elsif key && File.file?(key)
307
+ SSL.SSL_CTX_use_PrivateKey_file(@ssl_ctx, key, SSL_FILETYPE_PEM)
308
+ else
309
+ 1
310
+ end
311
+
312
+ # Check for errors
313
+ if err <= 0
314
+ # TODO:: ERR_print_errors_fp or ERR_print_errors
315
+ # So we can properly log the issue
316
+ cleanup
317
+ raise 'invalid private key or file not found'
318
+ end
319
+ end
320
+
321
+ def set_certificate(cert)
322
+ err = if cert.is_a? FFI::Pointer
323
+ SSL.SSL_CTX_use_certificate(@ssl_ctx, cert)
324
+ elsif cert && File.file?(cert)
325
+ SSL.SSL_CTX_use_certificate_chain_file(@ssl_ctx, cert)
326
+ else
327
+ 1
328
+ end
329
+
330
+ if err <= 0
331
+ cleanup
332
+ raise 'invalid certificate or file not found'
333
+ end
334
+ end
335
+ end
336
+
337
+
338
+
339
+
340
+ class Box
341
+ READ_BUFFER = 2048
342
+
343
+ SSL_VERIFY_PEER = 0x01
344
+ SSL_VERIFY_CLIENT_ONCE = 0x04
345
+ def initialize(server, transport, options = {})
346
+ @ready = true
347
+
348
+ @handshake_completed = false
349
+ @handshake_signaled = false
350
+ @transport = transport
351
+
352
+ @read_buffer = FFI::MemoryPointer.new(:char, READ_BUFFER, false)
353
+
354
+ @is_server = server
355
+ @context = Context.new(server, options)
356
+ @bioRead = SSL.BIO_new(SSL.BIO_s_mem)
357
+ @bioWrite = SSL.BIO_new(SSL.BIO_s_mem)
358
+ @ssl = SSL.SSL_new(@context.ssl_ctx)
359
+ SSL.SSL_set_bio(@ssl, @bioRead, @bioWrite)
360
+
361
+ @write_queue = []
362
+
363
+ # TODO:: if server && options[:alpn_string]
364
+ # SSL_CTX_set_alpn_select_cb
365
+
366
+ InstanceLookup[@ssl.address] = self
367
+
368
+ if options[:verify_peer]
369
+ SSL.SSL_set_verify(@ssl, SSL_VERIFY_PEER | SSL_VERIFY_CLIENT_ONCE, VerifyCB)
370
+ end
371
+
372
+ SSL.SSL_connect(@ssl) unless server
373
+ end
374
+
375
+
376
+ attr_reader :is_server
377
+ attr_reader :handshake_completed
378
+
379
+
380
+ def get_peer_cert
381
+ return '' unless @ready
382
+ SSL.SSL_get_peer_certificate(@ssl)
383
+ end
384
+
385
+ def start
386
+ return unless @ready
387
+
388
+ dispatch_cipher_text
389
+ end
390
+
391
+ def encrypt(data)
392
+ return unless @ready
393
+
394
+ wrote = put_plain_text data
395
+ if wrote < 0
396
+ @transport.close_cb
397
+ else
398
+ dispatch_cipher_text
399
+ end
400
+ end
401
+
402
+ SSL_ERROR_WANT_READ = 2
403
+ SSL_ERROR_SSL = 1
404
+ def decrypt(data)
405
+ return unless @ready
406
+
407
+ put_cipher_text data
408
+
409
+ if not SSL.SSL_is_init_finished(@ssl)
410
+ resp = @is_server ? SSL.SSL_accept(@ssl) : SSL.SSL_connect(@ssl)
411
+
412
+ if resp < 0
413
+ err_code = SSL.SSL_get_error(@ssl, resp)
414
+ if err_code != SSL_ERROR_WANT_READ
415
+ @transport.close_cb if err_code == SSL_ERROR_SSL
416
+ return
417
+ end
418
+ end
419
+
420
+ @handshake_completed = true
421
+ signal_handshake unless @handshake_signaled
422
+ end
423
+
424
+ while true do
425
+ size = get_plain_text(@read_buffer, READ_BUFFER)
426
+ if size > 0
427
+ @transport.dispatch_cb @read_buffer.read_string(size)
428
+ else
429
+ break
430
+ end
431
+ end
432
+
433
+ dispatch_cipher_text
434
+ end
435
+
436
+ def signal_handshake
437
+ @handshake_signaled = true
438
+ @transport.handshake_cb
439
+ end
440
+
441
+ SSL_RECEIVED_SHUTDOWN = 2
442
+ def cleanup
443
+ @ready = false
444
+
445
+ InstanceLookup.delete @ssl.address
446
+
447
+ if (SSL.SSL_get_shutdown(@ssl) & SSL_RECEIVED_SHUTDOWN) != 0
448
+ SSL.SSL_shutdown @ssl
449
+ else
450
+ SSL.SSL_clear @ssl
451
+ end
452
+
453
+ SSL.SSL_free @ssl
454
+
455
+ @context.cleanup
456
+ end
457
+
458
+ # Called from class level callback function
459
+ def verify(cert)
460
+ @transport.verify_cb(cert) == true ? 1 : 0
461
+ end
462
+
463
+
464
+ private
465
+
466
+
467
+ def get_plain_text(buffer, ready)
468
+ # Read the buffered clear text
469
+ size = SSL.SSL_read(@ssl, buffer, ready)
470
+ if size >= 0
471
+ size
472
+ else
473
+ SSL.SSL_get_error(@ssl, size) == SSL_ERROR_WANT_READ ? 0 : -1
474
+ end
475
+ end
476
+
477
+
478
+ InstanceLookup = ThreadSafe::Cache.new
479
+ VerifyCB = FFI::Function.new(:int, [:int, :pointer]) do |preverify_ok, x509_store|
480
+ x509 = SSL.X509_STORE_CTX_get_current_cert(x509_store)
481
+ ssl = SSL.X509_STORE_CTX_get_ex_data(x509_store, SSL.SSL_get_ex_data_X509_STORE_CTX_idx)
482
+
483
+ bio_out = SSL.BIO_new(SSL.BIO_s_mem)
484
+ SSL.PEM_write_bio_X509(bio_out, x509)
485
+
486
+ len = SSL.BIO_pending(bio_out)
487
+ buffer = FFI::MemoryPointer.new(:char, len, false)
488
+ size = SSL.BIO_read(bio_out, buffer, len)
489
+
490
+ # THis is the callback into the ruby class
491
+ result = InstanceLookup[ssl.address].verify(buffer.read_string(size))
492
+
493
+ SSL.BIO_free(bio_out)
494
+ result
495
+ end
496
+
497
+
498
+ def pending_data(bio)
499
+ SSL.BIO_pending(bio)
500
+ end
501
+
502
+ def get_cipher_text(buffer, length)
503
+ SSL.BIO_read(@bioWrite, buffer, length)
504
+ end
505
+
506
+ def put_cipher_text(data)
507
+ len = data.bytesize
508
+ wrote = SSL.BIO_write(@bioRead, data, len)
509
+ wrote == len
510
+ end
511
+
512
+
513
+ SSL_ERROR_WANT_WRITE = 3
514
+ def put_plain_text(data)
515
+ @write_queue.push(data) if data
516
+ return 0 unless SSL.SSL_is_init_finished(@ssl)
517
+
518
+ fatal = false
519
+ did_work = false
520
+
521
+ while !@write_queue.empty? do
522
+ data = @write_queue.pop
523
+ len = data.bytesize
524
+
525
+ wrote = SSL.SSL_write(@ssl, data, len)
526
+
527
+ if wrote > 0
528
+ did_work = true;
529
+ else
530
+ err_code = SSL.SSL_get_error(@ssl, wrote)
531
+ if (err_code != SSL_ERROR_WANT_READ) && (err_code != SSL_ERROR_WANT_WRITE)
532
+ fatal = true
533
+ else
534
+ # Not fatal - add back to the queue
535
+ @write_queue.unshift data
536
+ end
537
+
538
+ break
539
+ end
540
+ end
541
+
542
+ if did_work
543
+ 1
544
+ elsif fatal
545
+ -1
546
+ else
547
+ 0
548
+ end
549
+ end
550
+
551
+
552
+ CIPHER_DISPATCH_FAILED = 'Cipher text dispatch failed'.freeze
553
+ def dispatch_cipher_text
554
+ begin
555
+ did_work = false
556
+
557
+ # Get all the encrypted data and transmit it
558
+ pending = pending_data(@bioWrite)
559
+ if pending > 0
560
+ buffer = FFI::MemoryPointer.new(:char, pending, false)
561
+
562
+ resp = get_cipher_text(buffer, pending)
563
+ raise CIPHER_DISPATCH_FAILED unless resp > 0
564
+
565
+ @transport.transmit_cb(buffer.read_string(resp))
566
+ did_work = true
567
+ end
568
+
569
+ # Send any queued out going data
570
+ unless @write_queue.empty?
571
+ resp = put_plain_text nil
572
+ if resp > 0
573
+ did_work = true
574
+ elsif resp < 0
575
+ @transport.close_cb
576
+ end
577
+ end
578
+ end while did_work
579
+ end
580
+ end
581
+ end
582
+ end