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,31 @@
|
|
1
|
+
require 'termios'
|
2
|
+
|
3
|
+
# Extends IO to enable "raw" input on TTYs.
|
4
|
+
class IO
|
5
|
+
# Enables raw character input for a TTY. It uses the ruby-termios gem to
|
6
|
+
# disable ICANON and ECHO functionality. This means that characters will
|
7
|
+
# become immediately available to IO#gets, IO#getchar, etc. after the user
|
8
|
+
# presses a button, and that the characters will not be implicitly echoed to
|
9
|
+
# the screen.
|
10
|
+
#
|
11
|
+
# If you call this on $stdin, you probably should ensure that you call
|
12
|
+
# #disable_raw_chars in order to restore the previous termios state after
|
13
|
+
# your program exits. Otherwise, you may screw up the user's terminal, and
|
14
|
+
# they will have to call `reset`.
|
15
|
+
def enable_raw_chars
|
16
|
+
raise IOError, "#{self} is not a TTY." unless self.tty?
|
17
|
+
@_oldtermios = Termios.tcgetattr(self)
|
18
|
+
newt = @_oldtermios.dup
|
19
|
+
newt.c_lflag &= ~Termios::ICANON
|
20
|
+
newt.c_lflag &= ~Termios::ECHO
|
21
|
+
Termios.tcsetattr(self, Termios::TCSANOW, newt)
|
22
|
+
end
|
23
|
+
|
24
|
+
# Reverts the termios state to what it was before calling #enable_raw_chars.
|
25
|
+
def disable_raw_chars
|
26
|
+
raise IOError, "#{self} is not a TTY." unless self.tty?
|
27
|
+
Termios.tcsetattr(self, Termios::TCSANOW, @_oldtermios)
|
28
|
+
end
|
29
|
+
|
30
|
+
|
31
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
|
2
|
+
|
3
|
+
module SSLTest
|
4
|
+
# EM handler to handle keyboard input while a test is running.
|
5
|
+
module InputHandler
|
6
|
+
|
7
|
+
def initialize(stdin=$stdin)
|
8
|
+
@stdin = stdin
|
9
|
+
@actions = {}
|
10
|
+
|
11
|
+
# Set the term to accept keystrokes immediately.
|
12
|
+
@stdin.enable_raw_chars
|
13
|
+
end
|
14
|
+
|
15
|
+
def unbind
|
16
|
+
# Clean up by resotring the old termios
|
17
|
+
@stdin.disable_raw_chars
|
18
|
+
end
|
19
|
+
|
20
|
+
# Receives one character at a time.
|
21
|
+
def receive_data(data)
|
22
|
+
raise "data was longer than 1 char: #{data.inspect}" if data.length != 1
|
23
|
+
if @actions.has_key? data
|
24
|
+
@actions[data].call
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def on(char, blk=nil, &block)
|
29
|
+
puts "Warning: setting a keyboard handler for a keystroke that is longer than one char: #{char.inspect}" if char.length != 1
|
30
|
+
raise ArgumentError, "No block passed in" if blk == nil and block == nil
|
31
|
+
@actions[char] = ( blk ? blk : block)
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,110 @@
|
|
1
|
+
module SSLTest
|
2
|
+
# Handles a list of arguments, and uses the arguments to run a sequence of tests.
|
3
|
+
class Runner
|
4
|
+
include PacketThief::Logging
|
5
|
+
|
6
|
+
attr_reader :results
|
7
|
+
|
8
|
+
def initialize(args, stdin, stdout)
|
9
|
+
options = RunnerOptions.parse(args)
|
10
|
+
@test_list = options.args
|
11
|
+
@stdin = stdin
|
12
|
+
@stdout = stdout
|
13
|
+
|
14
|
+
@config = Config.new options.options
|
15
|
+
cert_manager = CertificateManager.new(@config.certs)
|
16
|
+
@logger = Logger.new(@config.logfile)
|
17
|
+
@logger.level = @config.loglevel
|
18
|
+
@logger.datetime_format = "%Y-%m-%d %H:%M:%S %Z"
|
19
|
+
@logger.formatter = proc do |severity, datetime, progname, msg|
|
20
|
+
"#{datetime}:#{severity}: #{msg}\n"
|
21
|
+
end
|
22
|
+
@app_context = AppContext.new(@config, cert_manager, @logger)
|
23
|
+
|
24
|
+
@report = SSLTestReport.new
|
25
|
+
init_packetthief
|
26
|
+
end
|
27
|
+
|
28
|
+
def run
|
29
|
+
case @config.action
|
30
|
+
when :list
|
31
|
+
@stdout.puts "These are the test I will perform and their descriptions:"
|
32
|
+
@stdout.puts ''
|
33
|
+
SSLTestCase.factory(@app_context, @config.tests, @test_list).each do |test|
|
34
|
+
display_test test
|
35
|
+
end
|
36
|
+
when :runtests
|
37
|
+
@stdout.puts "Press spacebar to skip a test, or 'q' to stop testing."
|
38
|
+
loginfo "Hostname being tested (assuming certs are up to date): #{@config.hosttotest}"
|
39
|
+
|
40
|
+
@tests = SSLTestCase.factory(@app_context, @config.tests, @test_list)
|
41
|
+
loginfo "Running #{@tests.length} tests"
|
42
|
+
|
43
|
+
start_packetthief
|
44
|
+
run_tests(@tests)
|
45
|
+
stop_packetthief
|
46
|
+
|
47
|
+
@report.print_results(@stdout)
|
48
|
+
else
|
49
|
+
raise "Unknown action: #{opts[:action]}"
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def run_tests(testlist)
|
54
|
+
test_manager = TestManager.new(@app_context, testlist, @report, @logger)
|
55
|
+
EM.run do
|
56
|
+
# @listener handles the initial server socket, not the accepted connections.
|
57
|
+
# h in the code block is for each accepted connection.
|
58
|
+
@listener = TestListener.start('', @config.listener_port, test_manager)
|
59
|
+
@listener.logger = @logger
|
60
|
+
@keyboard = EM.open_keyboard InputHandler do |h|
|
61
|
+
h.on(' ') { test_manager.test_completed(test_manager.current_test, :skipped) }
|
62
|
+
h.on('q') { test_manager.stop_testing }
|
63
|
+
h.on("\n") { test_manager.unpause }
|
64
|
+
end
|
65
|
+
EM.add_periodic_timer(5) { logdebug "EM connection count: #{EM.connection_count}" }
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
# Initialize custom PacketThief modes of operation. Eg, we use manual when PT
|
70
|
+
# does not manage the firewall rules and when it should just return a
|
71
|
+
# preconfigured destination, and external for when PT does not manage the
|
72
|
+
# firewall rules but still needs to know how to discover the destination.
|
73
|
+
def init_packetthief
|
74
|
+
PacketThief.logger = @logger
|
75
|
+
if @config.packetthief.has_key? 'implementation'
|
76
|
+
impl = @config.packetthief['implementation']
|
77
|
+
case impl
|
78
|
+
when /manual\(/i
|
79
|
+
PacketThief.implementation = :manual
|
80
|
+
host = /manual\((.*)\)/i.match(impl)[1]
|
81
|
+
PacketThief.set_dest(host, @config.packetthief.fetch('dest_port',443))
|
82
|
+
when /external\(/i
|
83
|
+
real_impl = /external\((.*)\)/i.match(impl)[1]
|
84
|
+
PacketThief.implementation = real_impl.strip.downcase.to_sym
|
85
|
+
else
|
86
|
+
PacketThief.implementation = impl
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def start_packetthief
|
92
|
+
ptconf = @config.packetthief
|
93
|
+
unless ptconf.has_key? 'implementation' and ptconf['implementation'].match(/external/i)
|
94
|
+
PacketThief.redirect(:to_ports => @config.listener_port).where(ptconf).run
|
95
|
+
end
|
96
|
+
at_exit { PacketThief.revert }
|
97
|
+
end
|
98
|
+
|
99
|
+
def stop_packetthief
|
100
|
+
PacketThief.revert
|
101
|
+
end
|
102
|
+
|
103
|
+
def display_test(test)
|
104
|
+
@stdout.printf "%s: %s\n", test.id, test.description
|
105
|
+
@stdout.printf " %s\n", test.certchainalias.inspect
|
106
|
+
@stdout.puts ''
|
107
|
+
end
|
108
|
+
|
109
|
+
end
|
110
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
module SSLTest
|
2
|
+
class RunnerOptions
|
3
|
+
|
4
|
+
DEFAULT_OPTS = {
|
5
|
+
:pause => false,
|
6
|
+
:config => Config::DEFAULT,
|
7
|
+
:action => :runtests,
|
8
|
+
:loglevel => 'INFO',
|
9
|
+
:logfile => '-'
|
10
|
+
}
|
11
|
+
|
12
|
+
# Parsed command line options.
|
13
|
+
attr_reader :options
|
14
|
+
|
15
|
+
# Any command line arguments that are not options.
|
16
|
+
attr_reader :args
|
17
|
+
|
18
|
+
def self.parse(args)
|
19
|
+
opts = new(args)
|
20
|
+
opts.parse
|
21
|
+
opts
|
22
|
+
end
|
23
|
+
|
24
|
+
def initialize(args)
|
25
|
+
@orig_args = args
|
26
|
+
end
|
27
|
+
|
28
|
+
def parse
|
29
|
+
@options = DEFAULT_OPTS.dup
|
30
|
+
|
31
|
+
opts = OptionParser.new do |opts|
|
32
|
+
opts.banner = "Usage: #{$0} [options] [tests to run]"
|
33
|
+
|
34
|
+
opts.on("-p","--[no-]pause", "Pause between tests") do
|
35
|
+
@options[:pause] = true
|
36
|
+
end
|
37
|
+
|
38
|
+
opts.on("-c", "--config path/to/config.yml",
|
39
|
+
"Specify a custom config.yml file",
|
40
|
+
" (Default: #{DEFAULT_OPTS[:config]})") do |confname|
|
41
|
+
@options[:config] = confname
|
42
|
+
end
|
43
|
+
|
44
|
+
opts.on("-l","--list", "List all tests (or those specified on the command line") do
|
45
|
+
@options[:action] = :list
|
46
|
+
end
|
47
|
+
|
48
|
+
opts.on("--log-level=loglevel", "Set the log level. It can be one of:",
|
49
|
+
" DEBUG, INFO, WARN, ERROR, FATAL", " (Default: INFO, or whatever config.yml sets)") do |level|
|
50
|
+
@options[:loglevel] = level
|
51
|
+
end
|
52
|
+
|
53
|
+
opts.on("--log-file=somefile.log", "Specify the file to write logs to."," (Default: - (STDOUT))") do |file|
|
54
|
+
@options[:logfile] = file
|
55
|
+
end
|
56
|
+
opts.on_tail('-h', '--help', "Print this help message.") do
|
57
|
+
puts opts
|
58
|
+
exit
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
62
|
+
|
63
|
+
@args = @orig_args.dup
|
64
|
+
opts.parse!(@args)
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
module SSLTest
|
2
|
+
# Represents a single test case.
|
3
|
+
class SSLTestCase
|
4
|
+
include PacketThief::Logging
|
5
|
+
|
6
|
+
attr_reader :id
|
7
|
+
attr_reader :description
|
8
|
+
attr_reader :certchainalias
|
9
|
+
attr_reader :expected_result
|
10
|
+
|
11
|
+
attr_reader :certchain
|
12
|
+
attr_reader :keychain
|
13
|
+
attr_reader :hosttotest
|
14
|
+
|
15
|
+
def self.factory(appctx, test_data, tests_to_create)
|
16
|
+
if tests_to_create == [] or tests_to_create == nil
|
17
|
+
final_test_data = test_data
|
18
|
+
else
|
19
|
+
final_test_data = tests_to_create.map { |name| test_data.select { |test| test['alias'] == name }[0] }
|
20
|
+
end
|
21
|
+
final_test_data.map { |data| SSLTestCase.new(appctx, data) }
|
22
|
+
end
|
23
|
+
|
24
|
+
def initialize(appctx, testdesc)
|
25
|
+
@appctx = appctx
|
26
|
+
@raw = testdesc.dup
|
27
|
+
@id = @raw['alias']
|
28
|
+
@description = @raw['name']
|
29
|
+
@certchainalias = @raw['certchain']
|
30
|
+
@expected_result = @raw['expected_result']
|
31
|
+
end
|
32
|
+
|
33
|
+
def certchain
|
34
|
+
@certchain ||= @appctx.cert_manager.get_chain(@certchainalias)
|
35
|
+
end
|
36
|
+
|
37
|
+
def keychain
|
38
|
+
@keychain ||= @appctx.cert_manager.get_keychain(@certchainalias)
|
39
|
+
end
|
40
|
+
|
41
|
+
def hosttotest
|
42
|
+
@hosttotest ||= @appctx.config.hosttotest
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module SSLTest
|
2
|
+
# Represents an entire report. SSLTestCases add results to it, which it can
|
3
|
+
# later format.
|
4
|
+
class SSLTestReport
|
5
|
+
|
6
|
+
def initialize
|
7
|
+
@results = []
|
8
|
+
end
|
9
|
+
|
10
|
+
def add_result(result)
|
11
|
+
@results << result
|
12
|
+
end
|
13
|
+
|
14
|
+
def print_results(out)
|
15
|
+
out.puts "Alias Description P/F Expected Actual Start Time Stop Time "
|
16
|
+
out.puts "---------------- ---------------- ---- -------- -------- ---------- ----------"
|
17
|
+
@results.each do |r|
|
18
|
+
out.printf "%-16.16<id>s %-16.16<description>s %-4.4<passed>s %-8.8<expected_result>s %-8.8<actual_result>s %<start_time>s %<stop_time>s\n", r.to_h
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module SSLTest
|
2
|
+
# SSLTestResults are created by running SSLTestCases. They are then added to
|
3
|
+
# an SSLTestReport so that they can be included in that report.
|
4
|
+
class SSLTestResult
|
5
|
+
|
6
|
+
attr_reader :id
|
7
|
+
attr_accessor :description
|
8
|
+
attr_accessor :expected_result, :actual_result
|
9
|
+
attr_accessor :start_time, :stop_time
|
10
|
+
|
11
|
+
def passed? ; @passed ; end
|
12
|
+
|
13
|
+
def initialize(testcase_id, passed=false)
|
14
|
+
@id = testcase_id
|
15
|
+
@passed = passed
|
16
|
+
end
|
17
|
+
|
18
|
+
def to_h
|
19
|
+
{
|
20
|
+
:id => id,
|
21
|
+
:passed => passed? ? "Pass" : "Fail",
|
22
|
+
:description => description,
|
23
|
+
:expected_result => expected_result,
|
24
|
+
:actual_result => actual_result,
|
25
|
+
:start_time => start_time,
|
26
|
+
:stop_time => stop_time,
|
27
|
+
}
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,140 @@
|
|
1
|
+
module SSLTest
|
2
|
+
|
3
|
+
# TestListener is the real workhorse used by SSLTestCases. It builds on the
|
4
|
+
# SSLSmartProxy from PacketThief in order to intercept and forward SSL
|
5
|
+
# connections. It uses SSLSmartProxy because SSLSmartProxy provides a default
|
6
|
+
# behavior where it grabs the remote certificate from the destination and
|
7
|
+
# re-signs it before presenting it to the client.
|
8
|
+
#
|
9
|
+
# TestListener expands on this by presenting the configured test chain
|
10
|
+
# instead of the re-signed remote certificate when the destination
|
11
|
+
# corresponds to the hostname the test suite is testing off of.
|
12
|
+
class TestListener < PacketThief::Handlers::SSLSmartProxy
|
13
|
+
|
14
|
+
# For all hosts that do not match _hosttotest_, we currently use the
|
15
|
+
# _cacert_ and re-sign the original cert provided by the actual host. This
|
16
|
+
# will cause issues with certificate revocation.
|
17
|
+
#
|
18
|
+
# * _cacert_ [OpenSSL::X509::Certificate] A CA that the client should
|
19
|
+
# trust.
|
20
|
+
# * _cakey_ [OpenSSL::PKey::PKey] The CA's key, needed for resigning. It
|
21
|
+
# will also be the key used by the resigned certificates.
|
22
|
+
# * _hosttotest_ [String] The hostname we want to apply the test chain to.
|
23
|
+
# * _chaintotest_ [Array<OpenSSL::X509Certificate>] A chain of certs to
|
24
|
+
# present when the client attempts to connect to hostname.
|
25
|
+
# * _keytotest_ [OpenSSL::PKey::PKey] The key corresponding to the leaf
|
26
|
+
# node in _chaintotest_.
|
27
|
+
def initialize(tcpsocket, test_manager, logger=nil)
|
28
|
+
@test_manager = test_manager
|
29
|
+
|
30
|
+
if @test_manager.paused?
|
31
|
+
@paused = true
|
32
|
+
else
|
33
|
+
@paused = false
|
34
|
+
@test = @test_manager.current_test
|
35
|
+
@hosttotest = @test.hosttotest
|
36
|
+
chain = @test.certchain.dup
|
37
|
+
@hostcert = chain.shift
|
38
|
+
@hostkey = @test.keychain[0]
|
39
|
+
@extrachain = chain
|
40
|
+
end
|
41
|
+
# Use the goodca for hosts we don't care to test against.
|
42
|
+
super(tcpsocket, @test_manager.goodcacert, @test_manager.goodcakey, logger)
|
43
|
+
|
44
|
+
@test_status = :running
|
45
|
+
@testing_host = false
|
46
|
+
end
|
47
|
+
|
48
|
+
# Checks whether the initial original destination certificate (without SNI
|
49
|
+
# hostname) matches the test hostname. We do this with post_init to have
|
50
|
+
# the check happen after the parent class already added a re-signed
|
51
|
+
# certificate to +@ctx+.
|
52
|
+
def post_init
|
53
|
+
check_for_hosttotest(@ctx)
|
54
|
+
end
|
55
|
+
|
56
|
+
# Checks whether the original destination certificate after we handle the
|
57
|
+
# SNI hostname matches the test hostname. Super already replaced the
|
58
|
+
# context with a certificate based on the remote host's certificate.
|
59
|
+
def servername_cb(sslsock, hostname)
|
60
|
+
check_for_hosttotest(super(sslsock, hostname))
|
61
|
+
end
|
62
|
+
|
63
|
+
# Replaces the certificates used in the SSLContext with the test
|
64
|
+
# certificates if the destination matches the hostname we wish to test
|
65
|
+
# against. Otherwise, it leaves the context alone.
|
66
|
+
#
|
67
|
+
# Additionally, if it matches, it sets @testing_host to true to check
|
68
|
+
# whether the test succeeds or not.
|
69
|
+
def check_for_hosttotest(actx)
|
70
|
+
if @paused
|
71
|
+
logdebug "Testing is paused, not checking whether this is the host to test", :certcubject => actx.cert.subject
|
72
|
+
elsif TestListener.cert_matches_host(actx.cert, @hosttotest)
|
73
|
+
logdebug "Destination matches host-to-test", :hosttotest => @hosttotest, :certsubject => actx.cert.subject, :testname => @test.id
|
74
|
+
actx.cert = @hostcert
|
75
|
+
actx.key = @hostkey
|
76
|
+
actx.extra_chain_cert = @extrachain
|
77
|
+
@testing_host = true
|
78
|
+
else
|
79
|
+
logdebug "Destination does not match host-to-test", :hosttotest => @hosttotest, :certsubject => actx.cert.subject
|
80
|
+
end
|
81
|
+
actx
|
82
|
+
end
|
83
|
+
|
84
|
+
# Return true if _cert_'s CNAME or subjectAltName matches hostname,
|
85
|
+
# otherwise return false.
|
86
|
+
def self.cert_matches_host(cert, hostname)
|
87
|
+
OpenSSL::SSL.verify_certificate_identity(cert, hostname)
|
88
|
+
end
|
89
|
+
|
90
|
+
# If the client completes connecting, we might consider that trusting our
|
91
|
+
# certificate chain. However, at least Java's SSL client classes don't
|
92
|
+
# reject until after completing the handshake.
|
93
|
+
def tls_successful_handshake
|
94
|
+
super
|
95
|
+
logdebug "successful handshake"
|
96
|
+
if @testing_host
|
97
|
+
@test_status = :connected
|
98
|
+
if @test_manager.testing_method == 'tlshandshake'
|
99
|
+
@test_manager.test_completed(@test, @test_status)
|
100
|
+
@testing_host = false
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
# If the handshake failed, then the client rejected our cert chain.
|
106
|
+
def tls_failed_handshake(e)
|
107
|
+
super
|
108
|
+
logdebug "failed handshake"
|
109
|
+
if @testing_host
|
110
|
+
@test_status = :rejected
|
111
|
+
@test_manager.test_completed(@test, @test_status)
|
112
|
+
@testing_host = false
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
# Report our result.
|
117
|
+
def unbind
|
118
|
+
super
|
119
|
+
logdebug "unbind"
|
120
|
+
if @testing_host
|
121
|
+
@test_manager.test_completed(@test, @test_status)
|
122
|
+
@testing_host = false
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
# client_recv means that the client sent data over the TLS connection,
|
127
|
+
# meaning they definately trusted our certificate chain.
|
128
|
+
def client_recv(data)
|
129
|
+
if @testing_host
|
130
|
+
@test_status = :sentdata
|
131
|
+
if @test_manager.testing_method == 'senddata'
|
132
|
+
@test_manager.test_completed(@test, @test_status)
|
133
|
+
@testing_host = false
|
134
|
+
end
|
135
|
+
end
|
136
|
+
super(data)
|
137
|
+
end
|
138
|
+
|
139
|
+
end
|
140
|
+
end
|