tlspretense 0.6.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +6 -0
- data/.gitignore +7 -0
- data/.rspec +1 -0
- data/Gemfile +2 -0
- data/Gemfile.lock +41 -0
- data/LICENSE.txt +20 -0
- data/README.rdoc +231 -0
- data/Rakefile +44 -0
- data/bin/makeder.sh +6 -0
- data/bin/tlspretense +7 -0
- data/bin/view.sh +3 -0
- data/doc/general_setup.rdoc +288 -0
- data/doc/linux_setup.rdoc +64 -0
- data/lib/certmaker.rb +61 -0
- data/lib/certmaker/certificate_factory.rb +106 -0
- data/lib/certmaker/certificate_suite_generator.rb +120 -0
- data/lib/certmaker/ext_core/hash_indifferent_fetch.rb +12 -0
- data/lib/certmaker/runner.rb +27 -0
- data/lib/certmaker/tasks.rb +20 -0
- data/lib/packetthief.rb +167 -0
- data/lib/packetthief/handlers.rb +14 -0
- data/lib/packetthief/handlers/abstract_ssl_handler.rb +249 -0
- data/lib/packetthief/handlers/proxy_redirector.rb +26 -0
- data/lib/packetthief/handlers/ssl_client.rb +87 -0
- data/lib/packetthief/handlers/ssl_server.rb +174 -0
- data/lib/packetthief/handlers/ssl_smart_proxy.rb +143 -0
- data/lib/packetthief/handlers/ssl_transparent_proxy.rb +225 -0
- data/lib/packetthief/handlers/transparent_proxy.rb +183 -0
- data/lib/packetthief/impl.rb +11 -0
- data/lib/packetthief/impl/ipfw.rb +140 -0
- data/lib/packetthief/impl/manual.rb +54 -0
- data/lib/packetthief/impl/netfilter.rb +109 -0
- data/lib/packetthief/impl/pf_divert.rb +168 -0
- data/lib/packetthief/impl/pf_rdr.rb +192 -0
- data/lib/packetthief/logging.rb +49 -0
- data/lib/packetthief/redirect_rule.rb +29 -0
- data/lib/packetthief/util.rb +36 -0
- data/lib/ssl_test.rb +21 -0
- data/lib/ssl_test/app_context.rb +17 -0
- data/lib/ssl_test/certificate_manager.rb +33 -0
- data/lib/ssl_test/config.rb +79 -0
- data/lib/ssl_test/ext_core/io_raw_input.rb +31 -0
- data/lib/ssl_test/input_handler.rb +35 -0
- data/lib/ssl_test/runner.rb +110 -0
- data/lib/ssl_test/runner_options.rb +68 -0
- data/lib/ssl_test/ssl_test_case.rb +46 -0
- data/lib/ssl_test/ssl_test_report.rb +24 -0
- data/lib/ssl_test/ssl_test_result.rb +30 -0
- data/lib/ssl_test/test_listener.rb +140 -0
- data/lib/ssl_test/test_manager.rb +116 -0
- data/lib/tlspretense.rb +13 -0
- data/lib/tlspretense/app.rb +52 -0
- data/lib/tlspretense/init_runner.rb +115 -0
- data/lib/tlspretense/skel/ca/goodcacert.pem +19 -0
- data/lib/tlspretense/skel/ca/goodcakey.pem +27 -0
- data/lib/tlspretense/skel/config.yml +523 -0
- data/lib/tlspretense/version.rb +3 -0
- data/packetthief_examples/em_ssl_test.rb +73 -0
- data/packetthief_examples/redirector.rb +29 -0
- data/packetthief_examples/setup_iptables.sh +24 -0
- data/packetthief_examples/ssl_client_simple.rb +27 -0
- data/packetthief_examples/ssl_server_simple.rb +44 -0
- data/packetthief_examples/ssl_smart_proxy.rb +115 -0
- data/packetthief_examples/ssl_transparent_proxy.rb +97 -0
- data/packetthief_examples/transparent_proxy.rb +56 -0
- data/spec/packetthief/impl/ipfw_spec.rb +98 -0
- data/spec/packetthief/impl/manual_spec.rb +65 -0
- data/spec/packetthief/impl/netfilter_spec.rb +66 -0
- data/spec/packetthief/impl/pf_divert_spec.rb +82 -0
- data/spec/packetthief/impl/pf_rdr_spec.rb +133 -0
- data/spec/packetthief/logging_spec.rb +78 -0
- data/spec/packetthief_spec.rb +47 -0
- data/spec/spec_helper.rb +53 -0
- data/spec/ssl_test/certificate_manager_spec.rb +222 -0
- data/spec/ssl_test/config_spec.rb +76 -0
- data/spec/ssl_test/runner_spec.rb +360 -0
- data/spec/ssl_test/ssl_test_case_spec.rb +113 -0
- data/spec/ssl_test/test_listener_spec.rb +199 -0
- data/spec/ssl_test/test_manager_spec.rb +324 -0
- data/tlspretense.gemspec +35 -0
- metadata +262 -0
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'eventmachine'
|
2
|
+
|
3
|
+
module PacketThief
|
4
|
+
module Handlers
|
5
|
+
autoload :AbstractSSLHandler, 'packetthief/handlers/abstract_ssl_handler'
|
6
|
+
autoload :SSLClient, 'packetthief/handlers/ssl_client'
|
7
|
+
autoload :SSLServer, 'packetthief/handlers/ssl_server'
|
8
|
+
autoload :SSLSmartProxy, 'packetthief/handlers/ssl_smart_proxy'
|
9
|
+
autoload :SSLTransparentProxy, 'packetthief/handlers/ssl_transparent_proxy'
|
10
|
+
autoload :TransparentProxy, 'packetthief/handlers/transparent_proxy'
|
11
|
+
autoload :ProxyRedirector, 'packetthief/handlers/proxy_redirector'
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
@@ -0,0 +1,249 @@
|
|
1
|
+
require 'openssl'
|
2
|
+
|
3
|
+
module PacketThief
|
4
|
+
module Handlers
|
5
|
+
|
6
|
+
# Parent class for both SSLServer and SSLClient.
|
7
|
+
#
|
8
|
+
# TODO: get_peer_cert, get_peername, etc.
|
9
|
+
class AbstractSSLHandler < ::EM::Connection
|
10
|
+
include Logging
|
11
|
+
|
12
|
+
# The OpenSSL::SSL::SSLContext. Modify this in post_init or in the
|
13
|
+
# initializing code block to add certificates, etc.
|
14
|
+
attr_accessor :ctx
|
15
|
+
|
16
|
+
# The TCPSocket that the SSLSocket will be created from. It is added by
|
17
|
+
# #initialize.
|
18
|
+
attr_accessor :tcpsocket
|
19
|
+
|
20
|
+
# The SSLSocket. It is not available until #tls_begin creates it, after
|
21
|
+
# post_init and the initializing code block.
|
22
|
+
attr_accessor :sslsocket
|
23
|
+
|
24
|
+
# (Used by SSLClient only) The hostname that the SNI TLS extension should
|
25
|
+
# request. Set it in post_init or in the initializing code block --- it
|
26
|
+
# is applied to the SSLSocket during #tls_begin.
|
27
|
+
attr_accessor :sni_hostname
|
28
|
+
|
29
|
+
def initialize(tcpsocket, logger=nil)
|
30
|
+
@logger = logger
|
31
|
+
logdebug "initialize"
|
32
|
+
# Set up initial values
|
33
|
+
@tcpsocket = tcpsocket
|
34
|
+
@ctx = OpenSSL::SSL::SSLContext.new
|
35
|
+
|
36
|
+
@close_after_writing = false
|
37
|
+
@state = :new
|
38
|
+
end
|
39
|
+
|
40
|
+
# Creates _sslsocket_ from _tcpsocket_ and _ctx_, and initializes the
|
41
|
+
# handler's internal state. Called from the class method that creates the
|
42
|
+
# object, after post_init and the optional code block.
|
43
|
+
#
|
44
|
+
# @note (SSLClient only) If @sni_hostname exists on the handler at this
|
45
|
+
# point, it will be added to the SSLSocket in order to enable sending a
|
46
|
+
# hostname in the SNI TLS extension.
|
47
|
+
def tls_begin
|
48
|
+
logdebug "tls begin", :sni_hostname => @sni_hostname
|
49
|
+
@sslsocket = OpenSSL::SSL::SSLSocket.new(@tcpsocket, @ctx)
|
50
|
+
if @sni_hostname
|
51
|
+
if @sslsocket.respond_to? :hostname
|
52
|
+
@sslsocket.hostname = @sni_hostname
|
53
|
+
else
|
54
|
+
logwarn "#{@sslsocket.class} does not support setting an SNI hostname! This requires Ruby 1.9.x built against OpenSSL with SNI support.",
|
55
|
+
:ruby_version => RUBY_VERSION
|
56
|
+
end
|
57
|
+
end
|
58
|
+
@state = :initialized
|
59
|
+
end
|
60
|
+
|
61
|
+
# Calls accept_nonblock/connect_nonblock, read_nonblock, or
|
62
|
+
# write_nonblock based on the current state of the connection.
|
63
|
+
def notify_readable
|
64
|
+
logdebug "notify_readable", :state => @state
|
65
|
+
case @state
|
66
|
+
when :initialized
|
67
|
+
attempt_connection
|
68
|
+
when :ready_to_read
|
69
|
+
attempt_read
|
70
|
+
when :write_needs_to_read
|
71
|
+
attempt_write
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
# We only care about notify_writable if we are waiting to write for some
|
76
|
+
# reason.
|
77
|
+
def notify_writable
|
78
|
+
logdebug "notify_writable", :state => @state
|
79
|
+
notify_writable = false # disable it now. if we still need it, we'll renabled it.
|
80
|
+
case @state
|
81
|
+
when :initialized
|
82
|
+
attempt_connection
|
83
|
+
when :read_needs_to_write
|
84
|
+
attempt_read
|
85
|
+
when :write_needs_to_write
|
86
|
+
attempt_write
|
87
|
+
end
|
88
|
+
|
89
|
+
# if we waiting to close and are not longer waiting to write, we can flush and close the connection.
|
90
|
+
if @close_after_writing and not notify_writable?
|
91
|
+
@sslsock.flush
|
92
|
+
close_connection
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
private
|
97
|
+
def attempt_connection
|
98
|
+
begin
|
99
|
+
# Client usess connect_nonblock, while server uses accept_nonblock.
|
100
|
+
connection_action
|
101
|
+
@state = :ready_to_read
|
102
|
+
tls_successful_handshake
|
103
|
+
attempt_write if write_buffer.length > 0
|
104
|
+
rescue IO::WaitReadable
|
105
|
+
# accept_nonblock needs to wait until it can read again.
|
106
|
+
notify_readable = true
|
107
|
+
rescue IO::WaitWritable
|
108
|
+
# accept_nonblock needs to wait until it can write again.
|
109
|
+
notify_writable = true
|
110
|
+
rescue OpenSSL::SSL::SSLError, Errno::ECONNREFUSED => e
|
111
|
+
# ssl handshake failed. Likely due to client rejecting our certificate!
|
112
|
+
tls_failed_handshake(e)
|
113
|
+
close_connection
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
private
|
118
|
+
def attempt_read
|
119
|
+
begin
|
120
|
+
data = @sslsocket.read_nonblock 4096 # much more than a network packet...
|
121
|
+
receive_data(data)
|
122
|
+
notify_writable = false
|
123
|
+
rescue EOFError, Errno::ECONNRESET
|
124
|
+
# remote closed. time to wrap up
|
125
|
+
close_connection
|
126
|
+
rescue IO::WaitReadable
|
127
|
+
# we had no data to read.
|
128
|
+
notify_readable = true
|
129
|
+
rescue IO::WaitWritable
|
130
|
+
# we ran out of buffer to send (yes, SSLSocket#read_nonblock can
|
131
|
+
# trigger this)
|
132
|
+
@state = :read_needs_to_write
|
133
|
+
notify_writable = true
|
134
|
+
rescue OpenSSL::SSL::SSLError => e
|
135
|
+
logerror "attempt_read: #{e} (#{e.class})"
|
136
|
+
close_connection
|
137
|
+
else
|
138
|
+
@state = :ready_to_read
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
public
|
143
|
+
def write_buffer
|
144
|
+
@write_buffer ||= ""
|
145
|
+
end
|
146
|
+
|
147
|
+
public
|
148
|
+
def write_buffer=(rhs)
|
149
|
+
@write_buffer = rhs
|
150
|
+
end
|
151
|
+
|
152
|
+
private
|
153
|
+
def attempt_write(data=nil)
|
154
|
+
logdebug "attempt_write"
|
155
|
+
write_buffer << data if data
|
156
|
+
# do not attempt to write until we are ready!
|
157
|
+
return if @state == :initialized or @state == :new
|
158
|
+
begin
|
159
|
+
count_written = @sslsocket.write_nonblock write_buffer
|
160
|
+
rescue IO::WaitWritable
|
161
|
+
notify_writable = true
|
162
|
+
rescue IO::WaitReadable
|
163
|
+
@state = :write_needs_to_read
|
164
|
+
rescue OpenSSL::SSL::SSLError, IOError => e
|
165
|
+
logerror "attempt_write: #{e} (#{e.class})"
|
166
|
+
close_connection
|
167
|
+
else
|
168
|
+
# shrink the buf
|
169
|
+
#
|
170
|
+
# byteslice was added in ruby 1.9.x. in ruby 1.8.7, bytesize is
|
171
|
+
# aliased to length, implying that a character coresponds to a
|
172
|
+
# byte.
|
173
|
+
@write_buffer = if write_buffer.respond_to?(:byteslice)
|
174
|
+
write_buffer.byteslice(count_written..-1)
|
175
|
+
else
|
176
|
+
write_buffer.slice(count_written..-1)
|
177
|
+
end
|
178
|
+
# if we didn't write everything, wait for writable.
|
179
|
+
notify_writable = true if write_buffer.bytesize > 0
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
####
|
184
|
+
|
185
|
+
public
|
186
|
+
|
187
|
+
# Call this to send data to the other end of the connection.
|
188
|
+
def send_data(data)
|
189
|
+
logdebug "send_data:", :data => data
|
190
|
+
attempt_write(data)
|
191
|
+
end
|
192
|
+
|
193
|
+
def close_connection
|
194
|
+
detach
|
195
|
+
@sslsocket.close if @sslsocket and not @sslsocket.closed?
|
196
|
+
@tcpsocket.close if not @tcpsocket.closed?
|
197
|
+
# unbind
|
198
|
+
end
|
199
|
+
|
200
|
+
def close_connection_after_writing
|
201
|
+
@close_after_writing = true
|
202
|
+
# if we aren't waiting to write, then we can flush and close.
|
203
|
+
if not notify_writable?
|
204
|
+
@sslsocket.flush
|
205
|
+
close_connection
|
206
|
+
end
|
207
|
+
|
208
|
+
end
|
209
|
+
|
210
|
+
|
211
|
+
# Note that post_init dos not have access to the _sslsocket_. The
|
212
|
+
# _sslsocket_ is not added until tls_begin is called, after the code
|
213
|
+
# block.
|
214
|
+
#
|
215
|
+
# #post_init gives you a chance to manipulate the SSLContext.
|
216
|
+
def post_init
|
217
|
+
end
|
218
|
+
|
219
|
+
|
220
|
+
# Called right after the SSL handshake succeeds. This is your "new"
|
221
|
+
# #post_init.
|
222
|
+
def tls_successful_handshake
|
223
|
+
logdebug "Succesful handshake!"
|
224
|
+
end
|
225
|
+
|
226
|
+
# Called right after accept_nonblock fails for some unknown reason. The
|
227
|
+
# only parameter contains the OpenSSL::SSL::SSLError object that was
|
228
|
+
# thrown.
|
229
|
+
#
|
230
|
+
# The connection will be closed after this.
|
231
|
+
def tls_failed_handshake(e)
|
232
|
+
logerror "tls_failed_handshake: Failed to accept: #{e} (#{e.class})"
|
233
|
+
end
|
234
|
+
|
235
|
+
# Override this to do something with the unecrypted data.
|
236
|
+
def receive_data(data)
|
237
|
+
logdebug "receive_data:", :data => data
|
238
|
+
end
|
239
|
+
|
240
|
+
# Override this to do something when the socket is finished.
|
241
|
+
def unbind
|
242
|
+
logdebug "unbind"
|
243
|
+
end
|
244
|
+
|
245
|
+
end
|
246
|
+
end
|
247
|
+
end
|
248
|
+
|
249
|
+
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module PacketThief
|
2
|
+
module Handlers
|
3
|
+
|
4
|
+
# Instead of forwarding the connection to the original host, forwards it to
|
5
|
+
# a configured host instead.
|
6
|
+
class ProxyRedirector < TransparentProxy
|
7
|
+
|
8
|
+
def initialize(proxy_host, proxy_port, log=nil)
|
9
|
+
super(log)
|
10
|
+
|
11
|
+
@proxy_host = proxy_host
|
12
|
+
@proxy_port = proxy_port
|
13
|
+
end
|
14
|
+
|
15
|
+
# Instead of using the original destination, use the configured destination.
|
16
|
+
def client_connected
|
17
|
+
@dest_host = @proxy_host
|
18
|
+
@dest_port = @proxy_port
|
19
|
+
connect_to_dest
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
@@ -0,0 +1,87 @@
|
|
1
|
+
module PacketThief
|
2
|
+
module Handlers
|
3
|
+
|
4
|
+
# Basic SSL/TLS Client built on Ruby's OpenSSL objects instead of on
|
5
|
+
# EventMachine's start_tls. This allows you to manipulate the SSLContext
|
6
|
+
# and other details of the connection that EM normally doesn't let you
|
7
|
+
# touch.
|
8
|
+
#
|
9
|
+
# Subclass it and override any of the methods in the following example to
|
10
|
+
# use the the functionality.
|
11
|
+
#
|
12
|
+
# You can #send_data to send encrypted data to the other side, and
|
13
|
+
# #receive_data will be called when there is data for the handler.
|
14
|
+
#
|
15
|
+
# EM.run {
|
16
|
+
# SSLClient.connect "www.isecpartners.com", 443 do |p|
|
17
|
+
#
|
18
|
+
# # Note: this code block is actually too late to set up a new
|
19
|
+
# # #post_init since it runs just after post_init. You can use
|
20
|
+
# # #post_init on a subclass though.
|
21
|
+
# def p.post_init
|
22
|
+
# # modify p.ctx to configure your certificates, key, etc.
|
23
|
+
# end
|
24
|
+
#
|
25
|
+
# # The following makes more sense for the initialization block.
|
26
|
+
# h.ctx.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
27
|
+
#
|
28
|
+
# def p.tls_successful_handshake
|
29
|
+
# # the handshake succeeded
|
30
|
+
# end
|
31
|
+
#
|
32
|
+
# def p.tls_failed_handshake(e)
|
33
|
+
# # the ssl handshake failed, probably due to the client rejecting
|
34
|
+
# # your certificate. =)
|
35
|
+
# end
|
36
|
+
#
|
37
|
+
# def p.unbind
|
38
|
+
# # unbind handler, called regardless of handshake success
|
39
|
+
# end
|
40
|
+
#
|
41
|
+
# def p.receive_data(data)
|
42
|
+
# # do something with the unencrypted stream
|
43
|
+
# p.send_data("some message") # data to be encrypted then sent to the client
|
44
|
+
# end
|
45
|
+
#
|
46
|
+
# end
|
47
|
+
# }
|
48
|
+
#
|
49
|
+
# Note: During #initialize and #post_init, this class
|
50
|
+
# does not have access to its socket yet. Instead, use #tls_pre_start or
|
51
|
+
# the code block you pass to .start to initialize the SSLContext, and use
|
52
|
+
# #tls_successful_handshake to do anything once the SSL handshake has
|
53
|
+
# completed.
|
54
|
+
class SSLClient < AbstractSSLHandler
|
55
|
+
|
56
|
+
def self.connect(host, port, *args, &block)
|
57
|
+
ssl_class = self
|
58
|
+
|
59
|
+
sock = TCPSocket.new host, port
|
60
|
+
|
61
|
+
::EM.watch sock, ssl_class, sock, *args do |h|
|
62
|
+
h.notify_readable = true
|
63
|
+
# h.notify_writable = true
|
64
|
+
block.call(h) if block
|
65
|
+
h.tls_begin
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
####
|
70
|
+
|
71
|
+
private
|
72
|
+
# SSLClient uses connect_nonblock instead of accept_nonblock.
|
73
|
+
def connection_action
|
74
|
+
@sslsocket.connect_nonblock
|
75
|
+
end
|
76
|
+
|
77
|
+
####
|
78
|
+
|
79
|
+
public
|
80
|
+
def tls_begin
|
81
|
+
super
|
82
|
+
attempt_connection
|
83
|
+
end
|
84
|
+
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
@@ -0,0 +1,174 @@
|
|
1
|
+
module PacketThief
|
2
|
+
module Handlers
|
3
|
+
|
4
|
+
# Basic SSL/TLS Server built on Ruby's OpenSSL objects instead of on
|
5
|
+
# EventMachine's start_tls. This allows you to manipulate the SSLContext
|
6
|
+
# and other details of the connection that EM normally doesn't let you
|
7
|
+
# touch.
|
8
|
+
#
|
9
|
+
# Subclass it and override any of the methods in the following example to
|
10
|
+
# use the the functionality.
|
11
|
+
#
|
12
|
+
# You can #send_data to send encrypted data to the other side, and
|
13
|
+
# #receive_data will be called when there is data for the handler.
|
14
|
+
#
|
15
|
+
# EM.run {
|
16
|
+
# # Leave the hostname blank for Linux's netfilter.
|
17
|
+
# SSLServer.start '', 54321 do |p|
|
18
|
+
#
|
19
|
+
# # Note: this code block is actually too late to set up a new
|
20
|
+
# # #post_init since it runs just after post_init. Instead, you would
|
21
|
+
# # use post_init in a subclass.
|
22
|
+
# def p.post_init
|
23
|
+
# # modify p.ctx to configure your certificates, key, etc.
|
24
|
+
# end
|
25
|
+
#
|
26
|
+
# # In this example, the following would work in this initialization
|
27
|
+
# # block:
|
28
|
+
# h.ctx.cert = cert
|
29
|
+
# h.ctx.extra_chain_cert = chain
|
30
|
+
# h.ctx.key = key
|
31
|
+
#
|
32
|
+
# def servername_cb(sock, hostname)
|
33
|
+
# # implement your own SNI handling callback. The default will
|
34
|
+
# # return the originally configured context.
|
35
|
+
# end
|
36
|
+
#
|
37
|
+
# def p.tls_successful_handshake
|
38
|
+
# # the handshake succeeded
|
39
|
+
# end
|
40
|
+
#
|
41
|
+
# def p.tls_failed_handshake(e)
|
42
|
+
# # the ssl handshake failed, probably due to the client rejecting
|
43
|
+
# # your certificate. =)
|
44
|
+
# end
|
45
|
+
#
|
46
|
+
# def p.unbind
|
47
|
+
# # unbind handler, called regardless of handshake success
|
48
|
+
# end
|
49
|
+
#
|
50
|
+
# def p.receive_data(data)
|
51
|
+
# # do something with the unencrypted stream
|
52
|
+
# p.send_data("some message") # data to be encrypted then sent to the client
|
53
|
+
# end
|
54
|
+
#
|
55
|
+
# end
|
56
|
+
# }
|
57
|
+
#
|
58
|
+
# Note: During #initialize and #post_init, this class
|
59
|
+
# does not have access to its socket yet. Instead, use #tls_pre_start or
|
60
|
+
# the code block you pass to .start to initialize the SSLContext, and use
|
61
|
+
# #tls_post_accept to do anything once the SSL handshake has completed. You
|
62
|
+
# can also override #servername_cb to perform the SNI callback.
|
63
|
+
class SSLServer < AbstractSSLHandler
|
64
|
+
|
65
|
+
# reference to the InitialServer that created the current handler. exists
|
66
|
+
# so you can call #stop_server or do something else to it directly.
|
67
|
+
attr_accessor :server_handler
|
68
|
+
|
69
|
+
def self.start(host, port, *args, &block)
|
70
|
+
ssl_class = self
|
71
|
+
|
72
|
+
serv = TCPServer.new host, port
|
73
|
+
|
74
|
+
# We use InitialServer to listen for incoming connections. It will then
|
75
|
+
# create the actual SSLServer.
|
76
|
+
initialserver = ::EM.watch serv, InitialServer, serv, ssl_class, args, block do |h|
|
77
|
+
h.notify_readable = true
|
78
|
+
end
|
79
|
+
|
80
|
+
initialserver
|
81
|
+
end
|
82
|
+
|
83
|
+
####
|
84
|
+
|
85
|
+
# Handles the initial listening socket. We can't seem to use
|
86
|
+
# EM.start_server -> EM.detach -> em.watch without triggering
|
87
|
+
# (in EventMachine 1.0.0):
|
88
|
+
#
|
89
|
+
# Assertion failed: (sd != INVALID_SOCKET), function _RunSelectOnce, file em.cpp, line 893.
|
90
|
+
#
|
91
|
+
# So we handle the server muckery ourselves.
|
92
|
+
module InitialServer
|
93
|
+
include Logging
|
94
|
+
|
95
|
+
def initialize(servsocket, ssl_class, args, block)
|
96
|
+
@servsocket = servsocket
|
97
|
+
@ssl_class = ssl_class
|
98
|
+
@args = args
|
99
|
+
@block = block
|
100
|
+
end
|
101
|
+
|
102
|
+
def notify_readable
|
103
|
+
logdebug "(#{@ssl_class}): Received a new connection, spawning a #{@ssl_class}"
|
104
|
+
sock = @servsocket.accept_nonblock
|
105
|
+
|
106
|
+
::EM.watch sock, @ssl_class, sock, *@args, @logger do |h|
|
107
|
+
logdebug "after initialize"
|
108
|
+
h.server_handler = self
|
109
|
+
h.notify_readable = true
|
110
|
+
h.logger = @logger
|
111
|
+
# Now call the caller's block.
|
112
|
+
@block.call(h) if @block
|
113
|
+
# And finally finish initialization by applying the context to an
|
114
|
+
# SSLSocket, and setting the internal state.
|
115
|
+
h.tls_begin unless h.tcpsocket.closed?
|
116
|
+
end
|
117
|
+
|
118
|
+
end
|
119
|
+
|
120
|
+
def notify_writable
|
121
|
+
logdebug "(#{@ssl_class}): Server socket notify writable"
|
122
|
+
end
|
123
|
+
|
124
|
+
# This must be called explicitly. EM doesn't seem to have a callback for when the EM::run call ends.
|
125
|
+
def stop_server
|
126
|
+
unless @servsocket.closed?
|
127
|
+
detach
|
128
|
+
@servsocket.close
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
def unbind
|
133
|
+
logdebug "(#{@ssl_class}): Stopping server socket"
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
####
|
138
|
+
|
139
|
+
private
|
140
|
+
# SSLServer uses accept_nonblock instead of connect_nonblock.
|
141
|
+
def connection_action
|
142
|
+
@sslsocket.accept_nonblock
|
143
|
+
end
|
144
|
+
|
145
|
+
####
|
146
|
+
|
147
|
+
public
|
148
|
+
def initialize(tcpsocket,logger=nil)
|
149
|
+
super(tcpsocket, logger)
|
150
|
+
@ctx.servername_cb = proc {|sslsocket, hostname| self.servername_cb(sslsocket, hostname) }
|
151
|
+
end
|
152
|
+
|
153
|
+
|
154
|
+
# Called when the client sends a hostname using the SNI TLS extension.
|
155
|
+
#
|
156
|
+
# This method should return an OpenSSL::SSL::SSLContext. It gives you an
|
157
|
+
# opportunity to pick or generate a different server certificate or
|
158
|
+
# certificate chain based on the hostname requested by the client.
|
159
|
+
#
|
160
|
+
# The default implementation does nothing by just returning the original
|
161
|
+
# SSLContext.
|
162
|
+
def servername_cb(sslsock, hostname)
|
163
|
+
sslsock.context
|
164
|
+
end
|
165
|
+
|
166
|
+
# Stops the InitialListener sever handler that spawned this handler. Due
|
167
|
+
# to our use of EM.watch, we can't rely on EM to close the socket.
|
168
|
+
def stop_server
|
169
|
+
@server_handler.stop_server
|
170
|
+
end
|
171
|
+
|
172
|
+
end
|
173
|
+
end
|
174
|
+
end
|