tlspretense 0.6.1
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.
- 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
|