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,192 @@
1
+
2
+
3
+ module PacketThief
4
+ module Impl
5
+ # Implementation that uses PF's +rdr+ (old style rules) and parses pfctl's
6
+ # output to interoperate with Mac OS X 10.7 Lion.
7
+ #
8
+ # It assumes that the system running PacketThief is already set up to
9
+ # reroute traffic like a NAT (On Mac OS X, you can do this by enabling
10
+ # Internet Sharing).
11
+ #
12
+ # To use it, you need to add the following rule to your /etc/pf.conf file
13
+ # in the "Translation" section.
14
+ #
15
+ # rdr-anchor "packetthief"
16
+ #
17
+ # This rule probably should be inserted after any NAT rules, but before any
18
+ # other redirect rules (such as Apple's +rdr-anchor+ rule). Once you have
19
+ # added it, you can reload the core ruleset:
20
+ #
21
+ # pfctl -f /etc/pf.conf
22
+ #
23
+ # == Design
24
+ #
25
+ # Lion (and Mountain Lion) provide an older implementation of PF (circa
26
+ # OpenBSD 4.3 and earlier) that does not contain the +divert-to+ action.
27
+ # Furthermore, Apple has decided to not make pfvars.h a public header file,
28
+ # making it marginally difficult to compile C code that can look up the
29
+ # original destination. Instead, we use pfctl to look up the state table,
30
+ # and we parse its output to find the current connection and acquire its
31
+ # original destination.
32
+ #
33
+ # Redirect rules look like the following:
34
+ #
35
+ # rdr on en1 proto tcp from any to any port 443 -> 127.0.0.1 port 54321
36
+ #
37
+ # The pfctl state table output looks something like:
38
+ #
39
+ # $ sudo pfctl -s states
40
+ # No ALTQ support in kernel
41
+ # ALTQ related functions disabled
42
+ # ALL tcp 74.125.224.68:80 <- 192.168.2.2:52995 ESTABLISHED:ESTABLISHED
43
+ # ALL tcp 192.168.2.2:52995 -> 10.0.1.2:33596 -> 74.125.224.68:80 ESTABLISHED:ESTABLISHED
44
+ # ALL tcp 127.0.0.1:54321 <- 173.194.79.147:443 <- 192.168.2.2:52998 CLOSED:SYN_SENT
45
+ # ALL tcp 127.0.0.1:54321 <- 173.194.79.147:443 <- 192.168.2.2:52999 CLOSED:SYN_SENT
46
+ # ALL tcp 127.0.0.1:54321 <- 173.194.79.147:443 <- 192.168.2.2:53000 CLOSED:SYN_SENT
47
+ # ALL tcp 127.0.0.1:54321 <- 173.194.79.147:443 <- 192.168.2.2:53001 CLOSED:SYN_SENT
48
+ # ALL udp 192.168.2.1:53 <- 192.168.2.2:53690 SINGLE:MULTIPLE
49
+ # ALL tcp 74.125.224.105:80 <- 192.168.2.2:53002 ESTABLISHED:ESTABLISHED
50
+ # ALL tcp 192.168.2.2:53002 -> 10.0.1.2:38466 -> 74.125.224.105:80 ESTABLISHED:ESTABLISHED
51
+ # ALL udp 224.0.0.251:5353 <- 10.0.1.4:5353 NO_TRAFFIC:SINGLE
52
+ # ALL udp ff02::fb[5353] <- fe80::72de:e2ff:fe41:5ddd[5353] NO_TRAFFIC:SINGLE
53
+ #
54
+ # /^(?<iface>\S+)\s+(?<proto>\S+)\s+(?<dest>\S+)\s+<-\s+(?<origdest>\S+)\s+<-\s+(?<src>\S+)\s+(?<status>\S+)$/
55
+ #
56
+ class PFRdr
57
+ module PFRdrRuleHandler
58
+ include Logging
59
+ attr_accessor :active_rules
60
+
61
+ # Executes a rule and holds onto it for later removal.
62
+ def run(rule)
63
+ @active_rules ||= []
64
+
65
+ @active_rules << rule
66
+ rulestrs = @active_rules.map { |r| r.to_pf_command.join(" ") }
67
+
68
+ rulestrs.each { |rule| logdebug rule }
69
+ args = %W{pfctl -q -a packetthief -f -}
70
+ IO.popen(args, "w+") do |pfctlio|
71
+ rulestrs.each { |rule| pfctlio.puts rule }
72
+ end
73
+ unless $?.exitstatus == 0
74
+ raise "Command #{args.inspect} exited with error code #{$?.inspect}"
75
+ end
76
+
77
+ end
78
+
79
+ # Reverts all executed rules that this handler knows about.
80
+ def revert
81
+ return if @active_rules == nil or @active_rules.empty?
82
+
83
+ args = %W{pfctl -q -a packetthief -F all}
84
+ unless system(*args)
85
+ raise "Command #{args.inspect} exited with error code #{$?.inspect}"
86
+ end
87
+ # end
88
+
89
+ @active_rules = []
90
+ end
91
+ end
92
+ extend PFRdrRuleHandler
93
+
94
+ class PFRdrRule < RedirectRule
95
+
96
+ attr_accessor :rule_number
97
+
98
+ def initialize(handler, rule_number=nil)
99
+ super(handler)
100
+ @rule_number = rule_number
101
+ end
102
+
103
+ def to_pf_command
104
+ args = []
105
+
106
+ args << "rdr"
107
+
108
+ if self.rulespec
109
+ args << 'on' << self.rulespec[:in_interface].to_s if self.rulespec.has_key? :in_interface
110
+
111
+ args << "proto" << self.rulespec.fetch(:protocol,'ip').to_s
112
+
113
+ args << 'from'
114
+ args << self.rulespec.fetch(:source_address, 'any').to_s
115
+ args << 'port' << self.rulespec[:source_port].to_s if self.rulespec.has_key? :source_port
116
+
117
+ args << 'to'
118
+ args << self.rulespec.fetch(:dest_address, 'any').to_s
119
+ args << 'port' << self.rulespec[:dest_port].to_s if self.rulespec.has_key? :dest_port
120
+ end
121
+
122
+ if self.redirectspec
123
+ if self.redirectspec.has_key? :to_ports
124
+ args << '->'
125
+ args << "127.0.0.1"
126
+ args << 'port' << self.redirectspec[:to_ports].to_s if self.redirectspec.has_key? :to_ports
127
+ else
128
+ raise "Rule lacks a valid redirect: #{self.inspect}"
129
+ end
130
+ end
131
+
132
+
133
+ args
134
+ end
135
+ end
136
+
137
+ def self.redirect(args={})
138
+ rule = PFRdrRule.new(self)
139
+ rule.redirect(args)
140
+ end
141
+
142
+ RDR_PAT = /^(?<iface>\S+)\s+(?<proto>\S+)\s+(?<dest>\S+)\s+<-\s+(?<origdest>\S+)\s+<-\s+(?<src>\S+)\s+(?<status>\S+)$/
143
+
144
+ # Returns the [port, host] for the original destination of +sock+.
145
+ #
146
+ # +Sock+ can be a Ruby socket or an EventMachine::Connection (including
147
+ # handler modules, which are mixed in to an anonymous descendent of
148
+ # EM::Connection).
149
+ #
150
+ # When PF uses a nat/rdr rule, it stores the original destination in a
151
+ # table that can be queried using an ioctl() call on the /dev/pf device.
152
+ # Unfortunately for Mac OS X 10.7 and 10.8, Apple does not provide the
153
+ # necessary pfvars.h header for querying pf, although it exists in the XNU
154
+ # kernel source, which marks it as a "private" header. Apple's version is
155
+ # also "different" from the version supported by most BSDs these days,
156
+ # creating additional headaches.
157
+ #
158
+ # To work around this, we instead use +pfctl -s states+ to get the nat
159
+ # state information.
160
+ def self.original_dest(sock)
161
+ if sock.respond_to? :getsockname
162
+ sockname = sock.getsockname
163
+ elsif sock.respond_to? :get_sockname
164
+ sockname = sock.get_sockname
165
+ else
166
+ raise ArgumentError, "#{sock.inspect} supports neither :getsockname nor :get_sockname!"
167
+ end
168
+ if sock.respond_to? :getpeername
169
+ peername = sock.getpeername
170
+ elsif sock.respond_to? :get_peername
171
+ peername = sock.get_peername
172
+ else
173
+ raise ArgumentError, "#{sock.inspect} supports neither :getpeername nor :get_peername!"
174
+ end
175
+ dest = Socket.unpack_sockaddr_in(sockname)
176
+ src = Socket.unpack_sockaddr_in(peername)
177
+
178
+ state_table = `pfctl -q -s state`
179
+ rdr_lines = state_table.split("\n").map { |l| l.match(RDR_PAT) }.compact
180
+ matched_conns = rdr_lines.select { |l| l[:dest] == "#{dest[1]}:#{dest[0]}" and l[:src] == "#{src[1]}:#{src[0]}" }
181
+ raise "multiple conns matched: #{matched_conns.inspect}" unless matched_conns.length == 1
182
+ host, port = matched_conns[0][:origdest].split(':', 2)
183
+ port = port.to_i
184
+
185
+ logdebug "original_dest:", :port => port, :host => host
186
+ [port, host]
187
+ end
188
+
189
+ end
190
+
191
+ end
192
+ end
@@ -0,0 +1,49 @@
1
+ module PacketThief
2
+ # Mix-in that provides some private convenience logging functions. Uses the
3
+ # @logger instance variable.
4
+ module Logging
5
+ # An optional Ruby Logger for debugging output. If it is unset, the log
6
+ # methods will be silent.
7
+ attr_accessor :logger
8
+
9
+ private
10
+
11
+ # Print a message at the specified log severity (prefixed with the component),
12
+ # and display any additional arguments. For example:
13
+ #
14
+ # dlog(Logger::DEBUG, SomeClass, "hello", :somevalue => 'that value")
15
+ #
16
+ # Will print the message: "SomeClass: hello: somevalue=that value" at the
17
+ # debug log level.
18
+ def log(level, component, msg, args={})
19
+ if @logger
20
+ unless args.empty?
21
+ kvstr = args.map { |pair| pair[0].to_s + ': ' + pair[1].inspect }.sort.join(', ')
22
+ msg += ": " + kvstr
23
+ end
24
+ @logger.log(level, component.to_s + ': ' + msg)
25
+ end
26
+ end
27
+
28
+ def logdebug(msg, args={})
29
+ log(Logger::DEBUG, (([Module, Class].include? self.class) ? self.name : self.class), msg, args)
30
+ end
31
+
32
+ def loginfo(msg, args={})
33
+ log(Logger::INFO, self.class, msg, args)
34
+ end
35
+
36
+ def logwarn(msg, args={})
37
+ log(Logger::WARN, self.class, msg, args)
38
+ end
39
+
40
+ def logerror(msg, args={})
41
+ log(Logger::ERROR, self.class, msg, args)
42
+ end
43
+
44
+ def logfatal(msg, args={})
45
+ log(Logger::FATAL, self.class, msg, args)
46
+ end
47
+
48
+ end
49
+ end
@@ -0,0 +1,29 @@
1
+
2
+ module PacketThief
3
+ class RedirectRule
4
+ attr_accessor :handler
5
+ attr_accessor :rulespec
6
+ attr_accessor :redirectspec
7
+
8
+ def initialize(handler)
9
+ @handler = handler
10
+ end
11
+
12
+ # specify an original destination
13
+ def where(args)
14
+ rule = clone
15
+ rule.rulespec = args
16
+ rule
17
+ end
18
+
19
+ def redirect(args)
20
+ rule = clone
21
+ rule.redirectspec = args
22
+ rule
23
+ end
24
+
25
+ def run
26
+ @handler.run(self)
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,36 @@
1
+ module PacketThief
2
+ # Some utility methods, currently just used by the examples.
3
+ module Util
4
+ class << self
5
+ # Extracts all PEM encoded certificates out of a raw string and returns
6
+ # each raw PEM encoded certificate in an array.
7
+ def split_chain(raw)
8
+ chain = []
9
+ remaining = raw
10
+ certpat = /-----BEGIN CERTIFICATE-----(.*?)-----END CERTIFICATE-----/m
11
+ while m = certpat.match(remaining)
12
+ remaining = m.post_match
13
+ chain << m[0].strip
14
+ end
15
+ chain
16
+ end
17
+
18
+ # Extracts all PEM encoded certs from a raw string and returns a list of
19
+ # X509 certificate objects in the order they appear in the file.
20
+ #
21
+ # This can be helpful for loading a chain of certificates, eg for a
22
+ # server.
23
+ #
24
+ # Usage:
25
+ #
26
+ # chain = cert_chain(File.read("chain.pem"))
27
+ # p chain # => [#<OpenSSL::X509::Certificate subject=/C=US/CN=my.hostname.com, issuer=/C=US/CN=Trusted CA...>,
28
+ # #<OpenSSL::X509::Certificate subject=/C=US/CN=Trusted CA ... >]
29
+ def cert_chain(raw)
30
+ rawchain = split_chain(raw)
31
+ rawchain.map { |rawcert| OpenSSL::X509::Certificate.new(rawcert) }
32
+ end
33
+
34
+ end
35
+ end
36
+ end
data/lib/ssl_test.rb ADDED
@@ -0,0 +1,21 @@
1
+ require 'packetthief'
2
+ require 'eventmachine'
3
+ require 'optparse'
4
+ require 'termios'
5
+ require 'logger'
6
+
7
+ require 'ssl_test/ext_core/io_raw_input'
8
+
9
+ module SSLTest
10
+ autoload :AppContext, 'ssl_test/app_context'
11
+ autoload :CertificateManager, 'ssl_test/certificate_manager'
12
+ autoload :Config, 'ssl_test/config'
13
+ autoload :InputHandler, 'ssl_test/input_handler'
14
+ autoload :RunnerOptions, 'ssl_test/runner_options'
15
+ autoload :Runner, 'ssl_test/runner'
16
+ autoload :SSLTestCase, 'ssl_test/ssl_test_case'
17
+ autoload :SSLTestReport, 'ssl_test/ssl_test_report'
18
+ autoload :SSLTestResult, 'ssl_test/ssl_test_result'
19
+ autoload :TestListener, 'ssl_test/test_listener'
20
+ autoload :TestManager, 'ssl_test/test_manager'
21
+ end
@@ -0,0 +1,17 @@
1
+ module SSLTest
2
+
3
+ # Class to hold onto application-wide values in a single place and to track
4
+ # application state.
5
+ class AppContext
6
+
7
+ attr_accessor :config
8
+ attr_accessor :cert_manager
9
+ attr_accessor :logger
10
+
11
+ def initialize(config, cert_manager, logger)
12
+ @config = config
13
+ @cert_manager = cert_manager
14
+ @logger = logger
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,33 @@
1
+ module SSLTest
2
+ # Handles the loading and caching of certificates and private keys.
3
+ class CertificateManager
4
+
5
+ def initialize(certinfo)
6
+ @certinfo = certinfo
7
+ end
8
+
9
+ def get_raw_cert(name)
10
+ File.read(File.join('certs',name+"cert.pem"))
11
+ end
12
+
13
+ def get_cert(name)
14
+ OpenSSL::X509::Certificate.new(get_raw_cert(name))
15
+ end
16
+
17
+ def get_raw_key(name)
18
+ File.read(File.join('certs',name+"key.pem"))
19
+ end
20
+
21
+ def get_key(name)
22
+ OpenSSL::PKey.read(get_raw_key(name))
23
+ end
24
+
25
+ def get_chain(list)
26
+ list.map { |name| get_cert(name) }
27
+ end
28
+
29
+ def get_keychain(list)
30
+ list.map { |name| get_key(name) }
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,79 @@
1
+ module SSLTest
2
+ # Loads and interprets the configuration file.
3
+ class Config
4
+
5
+ DEFAULT = "config.yml"
6
+
7
+ attr_reader :raw
8
+ attr_reader :certs
9
+
10
+ def self.load_conf(opts)
11
+ Config.new(opts)
12
+ end
13
+
14
+ # TODO: do some basic type validation on the config file.
15
+ def initialize(opts)
16
+ @opts = opts
17
+
18
+ @raw = YAML.load_file(@opts[:config])
19
+ @certs = @raw['certs']
20
+ end
21
+
22
+ def tests
23
+ @raw['tests']
24
+ end
25
+
26
+ def hosttotest
27
+ @raw['hostname']
28
+ end
29
+
30
+ def listener_port
31
+ @raw['listener_port']
32
+ end
33
+
34
+ def testing_method
35
+ @raw['testing_method']
36
+ end
37
+
38
+ def packetthief
39
+ pt = @raw['packetthief'].dup
40
+ newvals = {}
41
+ pt.each_pair do |k,v|
42
+ if k.kind_of? String
43
+ newvals[k.to_sym] = v
44
+ end
45
+ end
46
+ pt.merge! newvals
47
+ pt
48
+ end
49
+
50
+ def pause?
51
+ @opts[:pause]
52
+ end
53
+
54
+ def action
55
+ @opts[:action]
56
+ end
57
+
58
+ def loglevel
59
+ levelstr = if @opts.has_key? :loglevel
60
+ @opts[:loglevel].upcase
61
+ elsif @raw.has_key? 'log' and @raw['log'].has_key? 'level'
62
+ @raw['log']['level'].upcase
63
+ else
64
+ 'INFO'
65
+ end
66
+ Logger.const_get(levelstr)
67
+ end
68
+
69
+ def logfile
70
+ if @opts[:logfile] == '-'
71
+ STDOUT
72
+ else
73
+ @opts[:logfile]
74
+ end
75
+ end
76
+
77
+
78
+ end
79
+ end