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