tlspretense 0.6.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (81) hide show
  1. data/.document +6 -0
  2. data/.gitignore +7 -0
  3. data/.rspec +1 -0
  4. data/Gemfile +2 -0
  5. data/Gemfile.lock +41 -0
  6. data/LICENSE.txt +20 -0
  7. data/README.rdoc +231 -0
  8. data/Rakefile +44 -0
  9. data/bin/makeder.sh +6 -0
  10. data/bin/tlspretense +7 -0
  11. data/bin/view.sh +3 -0
  12. data/doc/general_setup.rdoc +288 -0
  13. data/doc/linux_setup.rdoc +64 -0
  14. data/lib/certmaker.rb +61 -0
  15. data/lib/certmaker/certificate_factory.rb +106 -0
  16. data/lib/certmaker/certificate_suite_generator.rb +120 -0
  17. data/lib/certmaker/ext_core/hash_indifferent_fetch.rb +12 -0
  18. data/lib/certmaker/runner.rb +27 -0
  19. data/lib/certmaker/tasks.rb +20 -0
  20. data/lib/packetthief.rb +167 -0
  21. data/lib/packetthief/handlers.rb +14 -0
  22. data/lib/packetthief/handlers/abstract_ssl_handler.rb +249 -0
  23. data/lib/packetthief/handlers/proxy_redirector.rb +26 -0
  24. data/lib/packetthief/handlers/ssl_client.rb +87 -0
  25. data/lib/packetthief/handlers/ssl_server.rb +174 -0
  26. data/lib/packetthief/handlers/ssl_smart_proxy.rb +143 -0
  27. data/lib/packetthief/handlers/ssl_transparent_proxy.rb +225 -0
  28. data/lib/packetthief/handlers/transparent_proxy.rb +183 -0
  29. data/lib/packetthief/impl.rb +11 -0
  30. data/lib/packetthief/impl/ipfw.rb +140 -0
  31. data/lib/packetthief/impl/manual.rb +54 -0
  32. data/lib/packetthief/impl/netfilter.rb +109 -0
  33. data/lib/packetthief/impl/pf_divert.rb +168 -0
  34. data/lib/packetthief/impl/pf_rdr.rb +192 -0
  35. data/lib/packetthief/logging.rb +49 -0
  36. data/lib/packetthief/redirect_rule.rb +29 -0
  37. data/lib/packetthief/util.rb +36 -0
  38. data/lib/ssl_test.rb +21 -0
  39. data/lib/ssl_test/app_context.rb +17 -0
  40. data/lib/ssl_test/certificate_manager.rb +33 -0
  41. data/lib/ssl_test/config.rb +79 -0
  42. data/lib/ssl_test/ext_core/io_raw_input.rb +31 -0
  43. data/lib/ssl_test/input_handler.rb +35 -0
  44. data/lib/ssl_test/runner.rb +110 -0
  45. data/lib/ssl_test/runner_options.rb +68 -0
  46. data/lib/ssl_test/ssl_test_case.rb +46 -0
  47. data/lib/ssl_test/ssl_test_report.rb +24 -0
  48. data/lib/ssl_test/ssl_test_result.rb +30 -0
  49. data/lib/ssl_test/test_listener.rb +140 -0
  50. data/lib/ssl_test/test_manager.rb +116 -0
  51. data/lib/tlspretense.rb +13 -0
  52. data/lib/tlspretense/app.rb +52 -0
  53. data/lib/tlspretense/init_runner.rb +115 -0
  54. data/lib/tlspretense/skel/ca/goodcacert.pem +19 -0
  55. data/lib/tlspretense/skel/ca/goodcakey.pem +27 -0
  56. data/lib/tlspretense/skel/config.yml +523 -0
  57. data/lib/tlspretense/version.rb +3 -0
  58. data/packetthief_examples/em_ssl_test.rb +73 -0
  59. data/packetthief_examples/redirector.rb +29 -0
  60. data/packetthief_examples/setup_iptables.sh +24 -0
  61. data/packetthief_examples/ssl_client_simple.rb +27 -0
  62. data/packetthief_examples/ssl_server_simple.rb +44 -0
  63. data/packetthief_examples/ssl_smart_proxy.rb +115 -0
  64. data/packetthief_examples/ssl_transparent_proxy.rb +97 -0
  65. data/packetthief_examples/transparent_proxy.rb +56 -0
  66. data/spec/packetthief/impl/ipfw_spec.rb +98 -0
  67. data/spec/packetthief/impl/manual_spec.rb +65 -0
  68. data/spec/packetthief/impl/netfilter_spec.rb +66 -0
  69. data/spec/packetthief/impl/pf_divert_spec.rb +82 -0
  70. data/spec/packetthief/impl/pf_rdr_spec.rb +133 -0
  71. data/spec/packetthief/logging_spec.rb +78 -0
  72. data/spec/packetthief_spec.rb +47 -0
  73. data/spec/spec_helper.rb +53 -0
  74. data/spec/ssl_test/certificate_manager_spec.rb +222 -0
  75. data/spec/ssl_test/config_spec.rb +76 -0
  76. data/spec/ssl_test/runner_spec.rb +360 -0
  77. data/spec/ssl_test/ssl_test_case_spec.rb +113 -0
  78. data/spec/ssl_test/test_listener_spec.rb +199 -0
  79. data/spec/ssl_test/test_manager_spec.rb +324 -0
  80. data/tlspretense.gemspec +35 -0
  81. metadata +262 -0
@@ -0,0 +1,199 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__),'..','spec_helper'))
2
+ require 'certmaker'
3
+
4
+ # quick and dirty. just create the fields we need.
5
+ def quickcertmaker(hostname, altnames=nil)
6
+ cert = OpenSSL::X509::Certificate.new
7
+ cert.version = 2
8
+ cert.subject = OpenSSL::X509::Name.parse("C=US, CN=#{hostname}")
9
+ ef = OpenSSL::X509::ExtensionFactory.new
10
+ ef.subject_certificate = cert
11
+ cert.add_extension(ef.create_ext_from_string("subjectAltName = #{altnames}")) if altnames
12
+ cert
13
+ end
14
+
15
+
16
+ module SSLTest
17
+ describe TestListener do
18
+ let(:tcpsocket) { double('tcpsocket') }
19
+ let(:cacert) { double('cacert') }
20
+ let(:cakey) { double('cakey') }
21
+ let(:hosttotest) { double('hosttotest') }
22
+ let(:chaintotest) { [ double('hostcert'), double('intermediatecert'), cacert] }
23
+ let(:keytotest) { double('keytotest') }
24
+
25
+ let(:appcontext) { double('appcontext') }
26
+ let(:curr_test) { double('curr_test',
27
+ :id => 'curr_test',
28
+ :hosttotest => hosttotest,
29
+ :certchain => chaintotest,
30
+ :keychain => [keytotest],
31
+ :cacert => cacert,
32
+ :cakey => cakey,
33
+ ) }
34
+ let(:test_manager) { double('test_manager',
35
+ :paused? => false,
36
+ :current_test => curr_test,
37
+ :test_completed => nil,
38
+ :testing_method => 'senddata',
39
+ :goodcacert => double('goodcacert'),
40
+ :goodcakey => double('goodcakey')
41
+ ) }
42
+
43
+ # Yes this is bad, but there are too many side effects to calling into
44
+ # the parent class right now.
45
+ before(:each) do
46
+ # PacketThief::Handlers::SSLSmartProxy.any_instance.stub(:initialize)
47
+ # PacketThief::Handlers::SSLSmartProxy.any_instance.stub(:tls_successful_handshake)
48
+ # PacketThief::Handlers::SSLSmartProxy.any_instance.stub(:tls_failed_handshake)
49
+ # PacketThief::Handlers::SSLSmartProxy.any_instance.stub(:unbind)
50
+ class PacketThief::Handlers::SSLSmartProxy
51
+ def initialize(socket, certchain, key, logger=nil)
52
+ end
53
+
54
+ def tls_successful_handshake
55
+ end
56
+
57
+ def tls_failed_handshake(e)
58
+ end
59
+
60
+ def unbind
61
+ end
62
+ end
63
+ end
64
+
65
+ subject do
66
+ _tcpsocket, _appcontext, _testmanager = tcpsocket, appcontext, test_manager
67
+ TestListener.allocate.instance_eval do
68
+ initialize(_tcpsocket, _testmanager)
69
+ self
70
+ end
71
+ end
72
+
73
+ describe ".cert_matches_host" do
74
+ context "when the CN in the subject is 'my.hostname.com'" do
75
+ let(:cert) { quickcertmaker("my.hostname.com") }
76
+
77
+ it {TestListener.cert_matches_host(cert, 'my.hostname.com').should == true}
78
+ it {TestListener.cert_matches_host(cert, 'my.HosTname.Com').should == true}
79
+ it {TestListener.cert_matches_host(cert, 'my2.hostname.com').should == false}
80
+
81
+ context "when another.hostname.com is in the subjectAltName" do
82
+ let(:cert) { quickcertmaker("my.hostname.com", "DNS:another.hostname.com, DNS:my.hostname.com") }
83
+
84
+ it {TestListener.cert_matches_host(cert, 'my.hostname.com').should == true}
85
+ it {TestListener.cert_matches_host(cert, 'my.HosTname.Com').should == true}
86
+ it {TestListener.cert_matches_host(cert, 'my2.hostname.com').should == false}
87
+ it {TestListener.cert_matches_host(cert, 'another.hostname.com').should == true}
88
+ it {TestListener.cert_matches_host(cert, 'another2.hostname.com').should == false}
89
+ end
90
+ end
91
+ end
92
+
93
+ describe "#check_for_hosttotest" do
94
+ context "when the certificate of the destination matches" do
95
+ before(:each) do
96
+ TestListener.stub(:cert_matches_host).and_return(true)
97
+ end
98
+
99
+ it "Returns a context where the certificate data matches the hostotest" do
100
+ @context = OpenSSL::SSL::SSLContext.new
101
+ @context.cert = double('resigned remote cert', :subject => double('resigned remote cert subject'))
102
+ @context.key = cakey
103
+ @context.extra_chain_cert = [cacert]
104
+
105
+ @newcontext = subject.check_for_hosttotest(@context)
106
+
107
+ @newcontext.cert.should == chaintotest[0]
108
+ @newcontext.key.should == keytotest
109
+ @newcontext.extra_chain_cert.should == chaintotest[1..-1]
110
+ end
111
+ end
112
+ context "when the certificate of the destination does not match" do
113
+ before(:each) do
114
+ TestListener.stub(:cert_matches_host).and_return(false)
115
+ end
116
+
117
+ it "Returns an unchanged context" do
118
+ @context = OpenSSL::SSL::SSLContext.new
119
+ @remotecert = double('resigned remote cert', :subject => double('resigned remote cert subject'))
120
+ @context.cert = @remotecert
121
+ @context.key = cakey
122
+ @context.extra_chain_cert = [cacert]
123
+
124
+ @newcontext = subject.check_for_hosttotest(@context)
125
+
126
+ @newcontext.cert.should == @remotecert
127
+ @newcontext.key.should == cakey
128
+ @newcontext.extra_chain_cert.should == [cacert]
129
+ end
130
+ end
131
+
132
+ context "when the test manager reports that testing is paused" do
133
+ before(:each) do
134
+ test_manager.stub(:paused?).and_return(true)
135
+ end
136
+
137
+ it "returns an unchanged context" do
138
+ @context = OpenSSL::SSL::SSLContext.new
139
+ @remotecert = double('resigned remote cert', :subject => double('resigned remote cert subject'))
140
+ @context.cert = @remotecert
141
+ @context.key = cakey
142
+ @context.extra_chain_cert = [cacert]
143
+
144
+ @newcontext = subject.check_for_hosttotest(@context)
145
+
146
+ @newcontext.cert.should == @remotecert
147
+ @newcontext.key.should == cakey
148
+ @newcontext.extra_chain_cert.should == [cacert]
149
+ end
150
+ end
151
+
152
+ end
153
+
154
+ describe "reporting the result" do
155
+ context "when a test listener's connection is to the host to test" do
156
+ before(:each) do
157
+ TestListener.stub(:cert_matches_host).and_return(true)
158
+ @ctx = OpenSSL::SSL::SSLContext.new
159
+ @ctx.cert = double('dest cert', :subject => double('dest cert subject'))
160
+ subject.check_for_hosttotest(@ctx)
161
+ end
162
+ context "when the client connects" do
163
+ before(:each) do
164
+ subject.tls_successful_handshake
165
+ end
166
+
167
+ it "calls the test_manager's test_completed callback with :connected when the connection closes" do
168
+ test_manager.should_receive(:test_completed).with(test_manager.current_test, :connected)
169
+
170
+ subject.unbind
171
+ end
172
+ end
173
+ context "when the client rejects" do
174
+ it "calls the test_manager's test_completed callback with :rejected" do
175
+ test_manager.should_receive(:test_completed).with(test_manager.current_test, :rejected)
176
+
177
+ subject.tls_failed_handshake(double('error'))
178
+ end
179
+ end
180
+ context "when the client sends data" do
181
+ context "when the testing_method is 'senddata'" do
182
+ before(:each) do
183
+ test_manager.stub(:testing_method).and_return('senddata')
184
+ end
185
+ it "client_recv calls the test_manager's test_completed callback with :sentdata" do
186
+ subject.stub(:send_to_dest)
187
+
188
+ test_manager.should_receive(:test_completed).with(test_manager.current_test, :sentdata)
189
+
190
+ subject.client_recv(double('data'))
191
+ end
192
+ end
193
+ end
194
+
195
+ end
196
+ end
197
+
198
+ end
199
+ end
@@ -0,0 +1,324 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__),'..','spec_helper'))
2
+
3
+ module SSLTest
4
+ describe TestManager do
5
+
6
+ let(:config) do
7
+ double("config",
8
+ :dest_port => 443,
9
+ :listener_port => 54321,
10
+ :hosttotest => "my.hostname.com",
11
+ :packetthief => {
12
+ :protocol => 'tcp',
13
+ :dest_port => 443,
14
+ :in_interface => 'en1'
15
+ },
16
+ :testing_method => 'tlshandshake',
17
+ :pause? => false
18
+ )
19
+ end
20
+ let(:certchain) { double('certchain') }
21
+ let(:keychain) { [ double('firstkey'), double('secondkey'), double('cakey') ] }
22
+ let(:goodcacert) { double('goodcacert') }
23
+ let(:goodcakey) { double('goodcakey') }
24
+ let(:cert_manager) do
25
+ @cert_manager = double(
26
+ "cert_manager",
27
+ :get_chain => certchain,
28
+ :get_keychain => keychain,
29
+ :get_cert => double('cert'),
30
+ :get_key => double('key')
31
+ )
32
+ @cert_manager.stub(:get_cert).with('goodca').and_return(goodcacert)
33
+ @cert_manager.stub(:get_key).with('goodca').and_return(goodcakey)
34
+ @cert_manager
35
+ end
36
+ let(:logger) { Logger.new(nil) }
37
+ let(:app_context) { AppContext.new(config, cert_manager, logger) }
38
+ let(:report) { double('report', :add_result => nil) }
39
+ let(:listener) { double('listener') }
40
+
41
+ let(:foo_test_data) do
42
+ {
43
+ 'alias' => 'foo',
44
+ 'name' => 'test foo',
45
+ 'certchain' => [ 'a', 'b' ]
46
+ }
47
+ end
48
+ let(:bar_test_data) do
49
+ {
50
+ 'alias' => 'bar',
51
+ 'name' => 'test bar',
52
+ 'certchain' => [ 'c', 'd' ]
53
+ }
54
+ end
55
+ let(:foo_test) { double('foo test',
56
+ :expected_result => 'connected',
57
+ :id => 'foo',
58
+ :description => 'foo test',
59
+ ) }
60
+ let(:bar_test) { double('bar test',
61
+ :expected_result => 'connected',
62
+ :id => 'bar',
63
+ :description => 'bar test',
64
+ ) }
65
+ let(:conf_tests_data) { [ foo_test_data, bar_test_data ] }
66
+ let(:testlist) { [foo_test, bar_test] }
67
+
68
+ subject { TestManager.new(app_context, testlist, report) }
69
+
70
+
71
+ describe "#prepare_next_test" do
72
+
73
+ context "when there are 3 tests in the testlist" do
74
+ let(:testlist) { [ double('test1', :id => 'test1'), double('test2', :id => 'test2'), double('test3', :id => 'test3') ] }
75
+ it "sets current_test to the first element of remaining_tests" do
76
+ subject.current_test.should == testlist[0]
77
+ subject.remaining_tests.should == [testlist[1], testlist[2]]
78
+
79
+ subject.prepare_next_test
80
+
81
+ subject.current_test.should == testlist[1]
82
+ subject.remaining_tests.should == [testlist[2]]
83
+ end
84
+
85
+ context "when the application does not want to pause after each test" do
86
+ before(:each) { config.stub(:pause?).and_return(false) }
87
+ it "does not set paused to true" do
88
+ subject.prepare_next_test
89
+
90
+ subject.paused?.should == false
91
+ end
92
+ end
93
+ context "when the application wants to pause after each test" do
94
+ before(:each) { config.stub(:pause?).and_return(true) }
95
+
96
+ it "does not set paused to true after initialization" do
97
+ subject
98
+
99
+ subject.paused?.should_not == true
100
+ end
101
+
102
+ it "sets paused to true" do
103
+ subject.prepare_next_test
104
+
105
+ subject.paused?.should == true
106
+ end
107
+ end
108
+ end
109
+
110
+ context "when there are no tests remaining" do
111
+ let(:testlist) { [double('test1', :id => 'test1')] }
112
+ it "stops testing" do
113
+ subject.should_receive(:stop_testing)
114
+
115
+ subject.prepare_next_test
116
+ end
117
+ end
118
+
119
+ end
120
+
121
+ describe "#test_completed" do
122
+ let(:result) { double("result", :description= => nil,
123
+ :expected_result= => nil, :actual_result= => nil,
124
+ :start_time= => nil, :stop_time= => nil) }
125
+ before(:each) do
126
+ SSLTestResult.stub(:new).and_return(result)
127
+ end
128
+
129
+ context "when the current test is 'foo'" do
130
+ before(:each) do
131
+ subject.current_test = foo_test
132
+ end
133
+
134
+ context "when the expected result is a successful connection" do
135
+
136
+ context "when the listener reports success" do
137
+ it "creates a result that reports passing" do
138
+
139
+ SSLTestResult.should_receive(:new).with('foo', true).and_return(result)
140
+ result.should_receive(:description=).with('foo test')
141
+ result.should_receive(:expected_result=).with('connected')
142
+ result.should_receive(:actual_result=).with('connected')
143
+ result.should_receive(:start_time=)
144
+ result.should_receive(:stop_time=)
145
+
146
+ subject.test_completed(subject.current_test, :connected)
147
+ end
148
+ it "adds the result to a report" do
149
+
150
+ report.should_receive(:add_result).with(result)
151
+
152
+ subject.test_completed(subject.current_test, :connected)
153
+ end
154
+
155
+ it "prepares for the next test" do
156
+ subject.should_receive(:prepare_next_test)
157
+
158
+ subject.test_completed(subject.current_test, :connected)
159
+ end
160
+
161
+ end
162
+
163
+ context "when the listener reports rejected" do
164
+ it "creates a result that reports not passing" do
165
+ SSLTestResult.should_receive(:new).with('foo', false).and_return(result)
166
+ result.should_receive(:description=).with('foo test')
167
+ result.should_receive(:expected_result=).with('connected')
168
+ result.should_receive(:actual_result=).with('rejected')
169
+ result.should_receive(:start_time=)
170
+ result.should_receive(:stop_time=)
171
+
172
+ subject.test_completed(subject.current_test, :rejected)
173
+ end
174
+ it "adds the result to a report" do
175
+ report.should_receive(:add_result).with(result)
176
+
177
+ subject.test_completed(subject.current_test, :rejected)
178
+ end
179
+ end
180
+
181
+ context "when the listener reports a still running test that has stopped" do
182
+ it "just returns" do
183
+ SSLTestResult.should_not_receive(:new)
184
+
185
+ subject.test_completed(subject.current_test, :running)
186
+ end
187
+ end
188
+ context "when the listener reports connected" do
189
+ it "creates a result that reports passing" do
190
+ SSLTestResult.should_receive(:new).with('foo', true).and_return(result)
191
+
192
+ subject.test_completed(subject.current_test, :connected)
193
+ end
194
+ end
195
+ context "when the listener reports sentdata" do
196
+ it "creates a result that reports passing" do
197
+ SSLTestResult.should_receive(:new).with('foo', true).and_return(result)
198
+
199
+ subject.test_completed(subject.current_test, :sentdata)
200
+ end
201
+ end
202
+
203
+ end
204
+
205
+ context "when the configuration requires the client to send data for it to consider it to be connected" do
206
+ before(:each) do
207
+ config.stub(:testing_method).and_return('senddata')
208
+ end
209
+ context "when the expected result is a successful connection" do
210
+ before(:each) do
211
+ foo_test.stub(:expected_result).and_return('connected')
212
+ end
213
+
214
+ context "when the listener reports rejected" do
215
+ it "creates a result that reports not passing" do
216
+ SSLTestResult.should_receive(:new).with('foo', false).and_return(result)
217
+
218
+ subject.test_completed(subject.current_test, :rejected)
219
+ end
220
+ end
221
+
222
+ context "when the listener reports connected" do
223
+ it "creates a result that reports not passing" do
224
+ SSLTestResult.should_receive(:new).with('foo', false).and_return(result)
225
+
226
+ subject.test_completed(subject.current_test, :connected)
227
+ end
228
+ end
229
+
230
+ context "when the listener reports sentdata" do
231
+ it "creates a result that reports passing" do
232
+ SSLTestResult.should_receive(:new).with('foo', true).and_return(result)
233
+
234
+ subject.test_completed(subject.current_test, :sentdata)
235
+ end
236
+ end
237
+
238
+ end
239
+ context "when the expected result is a rejected connection" do
240
+ before(:each) do
241
+ foo_test.stub(:expected_result).and_return('rejected')
242
+ end
243
+
244
+ context "when the listener reports rejected" do
245
+ it "creates a result that reports not passing" do
246
+ SSLTestResult.should_receive(:new).with('foo', true).and_return(result)
247
+
248
+ subject.test_completed(subject.current_test, :rejected)
249
+ end
250
+ end
251
+
252
+ context "when the listener reports connected" do
253
+ it "creates a result that reports not passing" do
254
+ SSLTestResult.should_receive(:new).with('foo', true).and_return(result)
255
+
256
+ subject.test_completed(subject.current_test, :connected)
257
+ end
258
+ end
259
+
260
+ context "when the listener reports sentdata" do
261
+ it "creates a result that reports passing" do
262
+ SSLTestResult.should_receive(:new).with('foo', false).and_return(result)
263
+
264
+ subject.test_completed(subject.current_test, :sentdata)
265
+ end
266
+ end
267
+ end
268
+ end
269
+
270
+ context "when the test passed in is the current_test" do
271
+ it "progresses to the next test" do
272
+ subject.should_receive(:prepare_next_test)
273
+
274
+ subject.test_completed(subject.current_test, :sentdata)
275
+ end
276
+ end
277
+
278
+ context "when the test passed in to test_completed is 'bar'" do
279
+ it "does not progress the current test to the next test" do
280
+ subject.should_not_receive(:prepare_next_test)
281
+
282
+ subject.test_completed(bar_test, :sentdata)
283
+ end
284
+ end
285
+
286
+ end
287
+ end
288
+
289
+ describe "#unpause" do
290
+ context "when the manager is paused" do
291
+ before(:each) do
292
+ config.stub(:pause?).and_return(true)
293
+ subject.prepare_next_test
294
+ end
295
+
296
+ it "unpauses the manager" do
297
+ subject.unpause
298
+
299
+ subject.paused?.should == false
300
+ end
301
+ end
302
+ end
303
+
304
+ describe "#stop_testing" do
305
+ before(:each) do
306
+ EM.stub(:stop_event_loop)
307
+ end
308
+ context "when the manager is bound to a listener" do
309
+ before(:each) { subject.listener = listener }
310
+ it "stops the listener" do
311
+ listener.should_receive(:stop_server)
312
+
313
+ subject.stop_testing
314
+ end
315
+ end
316
+ it "stops event machine" do
317
+ EM.should_receive(:stop_event_loop)
318
+
319
+ subject.stop_testing
320
+ end
321
+ end
322
+
323
+ end
324
+ end