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