ruby-tls 1.0.3 → 2.0.0

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