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