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,64 @@
|
|
1
|
+
= Setting Up Linux for Use With TLSPretense
|
2
|
+
|
3
|
+
To run TLSPretense on Linux, you need a system that has two network interfaces.
|
4
|
+
This Linux system could run from a virtual machine, or it could run on a laptop
|
5
|
+
and use the laptop's wifi card as a wireless access point to test.
|
6
|
+
|
7
|
+
The following setup will use a Linux host to run TLSPretense, and it will
|
8
|
+
configure netfilter to be a NAT with statically managed IP address on its
|
9
|
+
internal network.
|
10
|
+
|
11
|
+
TODO: How to host a DHCP server on certain systems.
|
12
|
+
|
13
|
+
== Configuring Linux to act as a NAT/router/gateway
|
14
|
+
|
15
|
+
The first step is to configure the Linux system's routing with IPTables. The
|
16
|
+
following script will turn the system into a NAT router. TLSPretense will then
|
17
|
+
later add and remove iptables rules as needed in order to intercept network
|
18
|
+
traffic during testing.
|
19
|
+
|
20
|
+
In this script, we assume +eth0+ is the external interface that can connect to
|
21
|
+
the original destination, and +eth1+ is the internal interface that the client
|
22
|
+
system will connect to.
|
23
|
+
|
24
|
+
#!/bin/sh
|
25
|
+
external=eth0
|
26
|
+
internal=eth1
|
27
|
+
|
28
|
+
iptables --flush
|
29
|
+
iptables --table nat --flush
|
30
|
+
iptables --delete-chain
|
31
|
+
iptables --table nat --delete-chain
|
32
|
+
|
33
|
+
|
34
|
+
echo "Manually setup the internal network's nic"
|
35
|
+
ifconfig $internal 192.168.42.1 netmask 255.255.255.0
|
36
|
+
|
37
|
+
echo "Enable packet forwarding"
|
38
|
+
echo 1 > /proc/sys/net/ipv4/ip_forward
|
39
|
+
|
40
|
+
echo "Apply basic iptables rules to create a NAT"
|
41
|
+
iptables -t nat -A POSTROUTING -o $external -j MASQUERADE
|
42
|
+
|
43
|
+
echo "Done! TLSPretense will automatically add and remove the rules for redirecting traffic."
|
44
|
+
|
45
|
+
Here we choose 192.168.42.* as the internal network IP address range.
|
46
|
+
|
47
|
+
Check to make sure that the TLSPretense host is able to access the internet
|
48
|
+
over the external interface, and that it can look up hostnames.
|
49
|
+
|
50
|
+
TODO: handling wireless ({although Mallory already has a
|
51
|
+
guide}[https://bitbucket.org/IntrepidusGroup/mallory/wiki/Wifi_Hotspot_Setup])
|
52
|
+
|
53
|
+
== Configure the Client's Host
|
54
|
+
|
55
|
+
Next, the system that will run the client code needs to be configured to use
|
56
|
+
the TLSPretense host as its gateway. This usually involves configuring the
|
57
|
+
system's network connection with information like the following:
|
58
|
+
|
59
|
+
Address: 192.168.42.100
|
60
|
+
Gateway: 192.168.42.1
|
61
|
+
Subnet mask: 255.255.255.0
|
62
|
+
|
63
|
+
You will also need to configure the client's DNS with a known good DNS server.
|
64
|
+
|
data/lib/certmaker.rb
ADDED
@@ -0,0 +1,61 @@
|
|
1
|
+
|
2
|
+
require 'fileutils'
|
3
|
+
require 'yaml'
|
4
|
+
require 'openssl'
|
5
|
+
|
6
|
+
require 'certmaker/ext_core/hash_indifferent_fetch'
|
7
|
+
require 'certmaker/certificate_factory'
|
8
|
+
require 'certmaker/certificate_suite_generator'
|
9
|
+
|
10
|
+
module CertMaker
|
11
|
+
|
12
|
+
|
13
|
+
# Generate certificates and keys using +config+.
|
14
|
+
#
|
15
|
+
# Config is usually a data structure derived from parsing a YAML file.
|
16
|
+
def make_certs(config, verbose=false)
|
17
|
+
FileUtils.mkdir_p config['certmaker']['outdir'], :verbose => verbose
|
18
|
+
|
19
|
+
certs = CertificateSuiteGenerator.new(config['certs'], config['hostname'], config['certmaker']).certificates
|
20
|
+
|
21
|
+
certs.each do |calias, ck|
|
22
|
+
File.open(File.join(config['certmaker']['outdir'],calias+"cert.pem"),"wb") { |f| f.write ck[:cert] }
|
23
|
+
File.open(File.join(config['certmaker']['outdir'],calias+"key.pem"),"wb") { |f| f.write ck[:key] }
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
module_function :make_certs
|
28
|
+
|
29
|
+
# Ensure that the custom ca exists.
|
30
|
+
def make_ca(config, verbose=false)
|
31
|
+
unless config['certmaker'].has_key? 'customgoodca'
|
32
|
+
puts "certmaker does not have a 'customgoodca' entry, so a CA will be regenerated every time."
|
33
|
+
return
|
34
|
+
end
|
35
|
+
|
36
|
+
cacert = config['certmaker']['customgoodca']['certfile']
|
37
|
+
cakey = config['certmaker']['customgoodca']['keyfile']
|
38
|
+
|
39
|
+
if File.exist? cacert and File.exist? cakey
|
40
|
+
puts "CA and CA's key already exist."
|
41
|
+
elsif File.exist? cacert and not File.exist? cakey
|
42
|
+
raise "CA certificate exists, but the key file does not exist?!"
|
43
|
+
elsif not File.exist? cacert and File.exist? cakey
|
44
|
+
raise "CA certificate does not exist, but the key file exists?!"
|
45
|
+
else
|
46
|
+
puts "Generating a new CA"
|
47
|
+
cacertdir = File.dirname(config['certmaker']['customgoodca']['certfile'])
|
48
|
+
FileUtils.mkdir_p cacertdir, :verbose => verbose
|
49
|
+
cakeydir = File.dirname(config['certmaker']['customgoodca']['keyfile'])
|
50
|
+
FileUtils.mkdir_p cakeydir, :verbose => verbose
|
51
|
+
csg = CertificateSuiteGenerator.new(config['certs'], config['hostname'], config['certmaker'])
|
52
|
+
csg.generate_certificate('goodca',config['certs']['goodca'])
|
53
|
+
cadata = csg.certificates['goodca']
|
54
|
+
File.open(cacert,"wb") { |f| f.write cadata[:cert] }
|
55
|
+
File.open(cakey,"wb") { |f| f.write cadata[:key] }
|
56
|
+
puts "New CA generated."
|
57
|
+
puts "Make sure you remove or comment out the passphrase in config.yml if you had one previously set!"
|
58
|
+
end
|
59
|
+
end
|
60
|
+
module_function :make_ca
|
61
|
+
end
|
@@ -0,0 +1,106 @@
|
|
1
|
+
module CertMaker
|
2
|
+
class CertificateFactory
|
3
|
+
|
4
|
+
attr_accessor :ca, :ca_key
|
5
|
+
attr_accessor :subject
|
6
|
+
attr_accessor :not_before, :not_after
|
7
|
+
attr_accessor :extensions
|
8
|
+
attr_accessor :key_type
|
9
|
+
attr_accessor :key_size
|
10
|
+
attr_accessor :signing_alg
|
11
|
+
|
12
|
+
def initialize
|
13
|
+
self.ca = :self
|
14
|
+
self.extensions = []
|
15
|
+
self.key_type = OpenSSL::PKey::RSA
|
16
|
+
self.key_size = 2048
|
17
|
+
self.signing_alg = :SHA1
|
18
|
+
end
|
19
|
+
|
20
|
+
# Handle special time values.
|
21
|
+
#
|
22
|
+
# now Time.now
|
23
|
+
# 30 30 days from now
|
24
|
+
# -30 30 days before now
|
25
|
+
def derive_time(timeval)
|
26
|
+
if ['now',:now].include? timeval
|
27
|
+
Time.now
|
28
|
+
elsif timeval.kind_of? Numeric
|
29
|
+
Time.now + timeval*24*60*60
|
30
|
+
else
|
31
|
+
raise "Unhandled time value: #{timeval}"
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
# Returns an array containing the certificate and associated key with the configured attributes, plus with the
|
36
|
+
# overridden attrs.
|
37
|
+
def create(args={})
|
38
|
+
|
39
|
+
# Make a key
|
40
|
+
kt = args.indifferent_fetch(:key_type, self.key_type)
|
41
|
+
begin
|
42
|
+
kt = OpenSSL::PKey.const_get(kt)
|
43
|
+
rescue TypeError
|
44
|
+
end
|
45
|
+
nk = kt.new self.key_size
|
46
|
+
|
47
|
+
# Certificate basics
|
48
|
+
nc = OpenSSL::X509::Certificate.new
|
49
|
+
nc.version = 2
|
50
|
+
nc.serial = args.indifferent_fetch(:serial, 1)
|
51
|
+
nc.subject = OpenSSL::X509::Name.parse(args.indifferent_fetch(:subject, self.subject))
|
52
|
+
nc.public_key = nk.public_key
|
53
|
+
|
54
|
+
# "now" is a special time value
|
55
|
+
nc.not_before = derive_time(args.indifferent_fetch(:not_before,self.not_before))
|
56
|
+
nc.not_after = derive_time(args.indifferent_fetch(:not_after,self.not_after))
|
57
|
+
|
58
|
+
# Prep for extensions
|
59
|
+
ef = OpenSSL::X509::ExtensionFactory.new
|
60
|
+
ef.subject_certificate = nc
|
61
|
+
|
62
|
+
self.ca = args.indifferent_fetch(:ca,self.ca)
|
63
|
+
# Issuer handling
|
64
|
+
if ['self',:self].include? self.ca
|
65
|
+
nc.issuer = nc.subject
|
66
|
+
ef.issuer_certificate = nc
|
67
|
+
signing_key = nk
|
68
|
+
else
|
69
|
+
nc.issuer = self.ca.subject
|
70
|
+
ef.issuer_certificate = self.ca
|
71
|
+
signing_key = args.indifferent_fetch(:ca_key,self.ca_key)
|
72
|
+
end
|
73
|
+
|
74
|
+
# Copy the extensions (we don't want to modify the original array later)
|
75
|
+
exts = args.indifferent_fetch(:extensions, self.extensions).dup
|
76
|
+
# filter out blocked extension patterns
|
77
|
+
if args.indifferent_fetch(:blockextensions, false)
|
78
|
+
args.indifferent_fetch(:blockextensions, nil).each do |badext|
|
79
|
+
exts = exts.select { |ext| ext.scan(badext).empty? }
|
80
|
+
end
|
81
|
+
end
|
82
|
+
# add any additional extensions
|
83
|
+
exts.concat args.indifferent_fetch(:addextensions,[])
|
84
|
+
|
85
|
+
# Add the extensions
|
86
|
+
exts.each do |ext|
|
87
|
+
nc.add_extension(ef.create_ext_from_string(ext))
|
88
|
+
end
|
89
|
+
|
90
|
+
# Look up the signing algorithm. If it is set to a symbol or string,
|
91
|
+
# we'll be able to look up a class. Otherwise we assume that the current
|
92
|
+
# signing_alg is a class symbol.
|
93
|
+
sa = args.indifferent_fetch(:signing_alg, self.signing_alg)
|
94
|
+
begin
|
95
|
+
sa = OpenSSL::Digest.const_get(sa)
|
96
|
+
rescue TypeError
|
97
|
+
end
|
98
|
+
|
99
|
+
nc.sign(signing_key, sa.new)
|
100
|
+
|
101
|
+
return [nc, nk]
|
102
|
+
end
|
103
|
+
|
104
|
+
end
|
105
|
+
|
106
|
+
end
|
@@ -0,0 +1,120 @@
|
|
1
|
+
|
2
|
+
module CertMaker
|
3
|
+
# Generates a suite of certificates from a configuration.
|
4
|
+
#
|
5
|
+
# Config should be a hash-like with each key being the alias to a hash-like
|
6
|
+
# that describes the certificate. Certificates can then refer to issuers or
|
7
|
+
# signing_keys by alias, and CertificateSuiteGenerator will ensure that they
|
8
|
+
# are generated in the correct order.
|
9
|
+
class CertificateSuiteGenerator
|
10
|
+
attr_accessor :certificates
|
11
|
+
|
12
|
+
# certinfos should be a hash-like where each entry describes a certificate.
|
13
|
+
# defaulthostname should be a string that will be inserted into %HOSTNAME%
|
14
|
+
# in certificate subject lines.
|
15
|
+
def initialize(certinfos, defaulthostname, config={})
|
16
|
+
@config = config
|
17
|
+
@defaulthostname = defaulthostname
|
18
|
+
@parenthostname = defaulthostname.sub(/^[\w-]+\./, '') # remove left-most label
|
19
|
+
@certinfos = certinfos
|
20
|
+
@certificates = {}
|
21
|
+
end
|
22
|
+
|
23
|
+
def certificates
|
24
|
+
generate_certificates if @certificates.empty?
|
25
|
+
@certificates
|
26
|
+
end
|
27
|
+
|
28
|
+
def generate_certificates
|
29
|
+
@certinfos.each_pair do |calias, certinfo|
|
30
|
+
puts ''
|
31
|
+
puts ''
|
32
|
+
puts calias
|
33
|
+
p certinfo
|
34
|
+
generate_certificate(calias, certinfo) unless @certificates.has_key? calias
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def generate_certificate(calias, certinfo)
|
39
|
+
puts "Generating #{calias}..."
|
40
|
+
|
41
|
+
# Check for customgoodca.
|
42
|
+
if calias == 'goodca' and @config.has_key? 'customgoodca'
|
43
|
+
certfile = @config['customgoodca']['certfile']
|
44
|
+
keyfile = @config['customgoodca']['keyfile']
|
45
|
+
keypass = @config['customgoodca'].fetch('keypass',nil)
|
46
|
+
if File.exist? certfile and File.exist? keyfile
|
47
|
+
puts "Customgoodca defined and the CA's files exist. We will use it instead of generating a new goodca."
|
48
|
+
rawcert = File.read(certfile)
|
49
|
+
rawkey = File.read(keyfile)
|
50
|
+
goodcert = OpenSSL::X509::Certificate.new(rawcert)
|
51
|
+
goodkey = OpenSSL::PKey.read(rawkey, keypass)
|
52
|
+
@certificates[calias] = { :cert => goodcert, :key => goodkey }
|
53
|
+
return
|
54
|
+
else
|
55
|
+
puts "#{certfile} or #{keyfile} does not exist! We will generate a new one."
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
cf = CertificateFactory.new
|
60
|
+
|
61
|
+
# configure the issuer
|
62
|
+
if certinfo['issuer'] == 'self'
|
63
|
+
puts "self signed"
|
64
|
+
cf.ca = :self
|
65
|
+
cf.ca_key = :self
|
66
|
+
else
|
67
|
+
issueralias = certinfo['issuer']
|
68
|
+
puts "issued by #{issueralias.inspect}"
|
69
|
+
raise "Issuer #{issueralias} is not in the list of certificates!" unless @certinfos.has_key? issueralias
|
70
|
+
generate_certificate(issueralias,@certinfos[issueralias]) unless @certificates.has_key? issueralias
|
71
|
+
ca = @certificates[issueralias]
|
72
|
+
cf.ca = ca[:cert]
|
73
|
+
cf.ca_key = ca[:key] # Use the issuer's key...
|
74
|
+
end
|
75
|
+
# ... but override the signing key if it is explicitly specified.
|
76
|
+
if certinfo.has_key? 'signing_key'
|
77
|
+
signeralias = certinfo['signing_key']
|
78
|
+
puts "Signed by #{signeralias.inspect}"
|
79
|
+
raise "Signer #{signeralias} is not in the list of certificates!" unless @certinfos.has_key? signeralias
|
80
|
+
generate_certificate(signeralias,@certinfos[signeralias]) unless @certificates.has_key? signeralias
|
81
|
+
cf.ca_key = @certificates[signeralias][:key]
|
82
|
+
end
|
83
|
+
# doctor the certinfo's subject line and any extensions.
|
84
|
+
certinfo['subject'] = certinfo['subject'].gsub(/%HOSTNAME%/, @defaulthostname).gsub(/%PARENTHOSTNAME%/, @parenthostname)
|
85
|
+
if certinfo.has_key? 'extensions'
|
86
|
+
certinfo['extensions'] = certinfo['extensions'].map { |ext| ext.gsub(/%HOSTNAME%/, @defaulthostname).gsub(/%PARENTHOSTNAME%/, @parenthostname) }
|
87
|
+
end
|
88
|
+
if certinfo.has_key? 'addextensions'
|
89
|
+
certinfo['addextensions'] = certinfo['addextensions'].map { |ext| ext.gsub(/%HOSTNAME%/, @defaulthostname).gsub(/%PARENTHOSTNAME%/, @parenthostname) }
|
90
|
+
end
|
91
|
+
# doctor the serial number.
|
92
|
+
if @config.has_key? 'missing_serial_generation'
|
93
|
+
unless certinfo.has_key? 'serial'
|
94
|
+
case @config['missing_serial_generation']
|
95
|
+
when "random"
|
96
|
+
certinfo['serial'] = randomserial
|
97
|
+
else
|
98
|
+
certinfo['serial'] = @config['missing_serial_generation']
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
cert, key = cf.create(certinfo)
|
104
|
+
puts "Created #{calias}"
|
105
|
+
|
106
|
+
@certificates[calias] = { :cert => cert, :key => key }
|
107
|
+
end
|
108
|
+
|
109
|
+
def randomserial
|
110
|
+
range = 2**30
|
111
|
+
begin
|
112
|
+
require 'securerandom'
|
113
|
+
SecureRandom.random_number range
|
114
|
+
rescue LoadError # no securerandom, so use weaker rand.
|
115
|
+
rand(range)
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
end
|
120
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
|
2
|
+
class Hash
|
3
|
+
# Light extension to hash to add indifferent fetching. Meant to be more
|
4
|
+
# lightweight than depending on ActiveSupport for HashWithIndifferentAccess.
|
5
|
+
def indifferent_fetch(key, *extra)
|
6
|
+
if key.class == Symbol
|
7
|
+
self.fetch(key, self.fetch(key.to_s, *extra))
|
8
|
+
else
|
9
|
+
self.fetch(key, self.fetch(key.to_sym, *extra))
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'certmaker'
|
2
|
+
require 'yaml'
|
3
|
+
require 'fileutils'
|
4
|
+
|
5
|
+
module CertMaker
|
6
|
+
class Runner
|
7
|
+
include FileUtils
|
8
|
+
|
9
|
+
#Checks for a CA, and generates it if needed.
|
10
|
+
def ca
|
11
|
+
y = YAML.load_file('config.yml')
|
12
|
+
CertMaker.make_ca y
|
13
|
+
end
|
14
|
+
|
15
|
+
#Generate a suite of test certificates
|
16
|
+
def certs
|
17
|
+
ca
|
18
|
+
y = YAML.load_file('config.yml')
|
19
|
+
CertMaker.make_certs y
|
20
|
+
end
|
21
|
+
|
22
|
+
#Clean up by deleting the 'certs' directory.
|
23
|
+
def clean
|
24
|
+
rm_r "certs"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
|
2
|
+
namespace :certs do
|
3
|
+
|
4
|
+
desc "Checks for a CA, and generates it if needed."
|
5
|
+
task :ca do
|
6
|
+
require 'certmaker/runner'
|
7
|
+
CertMaker::Runner.new.ca
|
8
|
+
end
|
9
|
+
|
10
|
+
desc "Generate a suite of test certificates"
|
11
|
+
task :generate => [ 'certs:ca' ] do
|
12
|
+
require 'certmaker/runner'
|
13
|
+
CertMaker::Runner.new.certs
|
14
|
+
end
|
15
|
+
|
16
|
+
desc "Clean up by deleting the 'certs' directory."
|
17
|
+
task :clean do
|
18
|
+
rm_r "certs"
|
19
|
+
end
|
20
|
+
end
|
data/lib/packetthief.rb
ADDED
@@ -0,0 +1,167 @@
|
|
1
|
+
require 'socket'
|
2
|
+
require 'logger'
|
3
|
+
|
4
|
+
require 'eventmachine'
|
5
|
+
|
6
|
+
# Framework for intercepting packets, redirecting them to a handler, and doing
|
7
|
+
# something with the "stolen" connection.
|
8
|
+
#
|
9
|
+
# == Description
|
10
|
+
#
|
11
|
+
# PacketThief is a little Ruby framework for programatically intercepting network
|
12
|
+
# traffic. It provides an abstraction around configuring various OS firewalls and
|
13
|
+
# for gathering information about the original connection, and it offers several
|
14
|
+
# sample handler classes (for use with EventMachine) for intercepting and
|
15
|
+
# forwarding traffic. If you want a full fledged security tool for intercepting
|
16
|
+
# and analyzing network traffic, check out
|
17
|
+
# {Mallory}[http://intrepidusgroup.com/insight/mallory/] instead.
|
18
|
+
#
|
19
|
+
# PacketThief is currently intended to be run on a computer that should be
|
20
|
+
# configured as the gateway for whatever network traffic you wish to intercept.
|
21
|
+
# You then use PacketThief to configure your OS firewall and network routing to
|
22
|
+
# send specified network traffic to a socket. The socket handling code can then
|
23
|
+
# read the data, modify it, send it on to the original destination (although the
|
24
|
+
# new connection will originate from the gateway), etc.
|
25
|
+
#
|
26
|
+
# Currently, PacketThief supports basic redirection using Ipfw (Mac OS X, BSD)
|
27
|
+
# and Netfilter (Linux). It also more than likely requires your PacketThief-based
|
28
|
+
# script to be run as root in order to modify your system's firewall.
|
29
|
+
#
|
30
|
+
# == Usage
|
31
|
+
#
|
32
|
+
# First, you must configure the system that will run PacketThief to be able to
|
33
|
+
# intercept network traffic. If that host system has two network interfaces (such
|
34
|
+
# as a laptop with ethernet and wifi), then you can turn the system into a router
|
35
|
+
# by configure one interface to be an external interface that can communicate
|
36
|
+
# with your main network and the Internet, and the other interface can be
|
37
|
+
# configured as the gateway for one or more devices/client systems that you would
|
38
|
+
# like to test.
|
39
|
+
#
|
40
|
+
# NATing multiple network interfaces can be easily done in Mac OS X by enabling
|
41
|
+
# Internet Sharing in the Sharing System Preference pane, which will also let you
|
42
|
+
# configure your wifi to be a wireless access point. For Linux, take a look at
|
43
|
+
# the +examples/setup_iptables.sh+ script, or {search the Internet for tutorials
|
44
|
+
# on various ways to set up Mallory}[https://bitbucket.org/IntrepidusGroup/mallory/wiki/Home].
|
45
|
+
#
|
46
|
+
# Basic use:
|
47
|
+
#
|
48
|
+
# require 'packetthief'
|
49
|
+
# # redirect tcp traffic destined for port 443 to localhost port 65432:
|
50
|
+
# PacketThief.redirect(:to_ports => 65432).where(:protocol => :tcp, :dest_port => 443).run
|
51
|
+
# at_exit { PacketThief.revert } # Remove our firewall rules when we exit
|
52
|
+
#
|
53
|
+
# This will set up Firewall/routing rules to redirect packets matching the .where
|
54
|
+
# clause to the localhost port specified by the redirect() clause. The
|
55
|
+
# Kernel#at_exit handler calls the .revert method which will remove the firewall
|
56
|
+
# rules added by PacketThief.
|
57
|
+
#
|
58
|
+
# You can then capture network traffic in your script by opening a listening
|
59
|
+
# socket your +:to_ports+ destination. When you create the socket, set the
|
60
|
+
# hostname to a blank string to have it listen on all interfaces -- this winds up
|
61
|
+
# being more compatible with different firewalls.
|
62
|
+
#
|
63
|
+
# Using a TCPServer:
|
64
|
+
#
|
65
|
+
# TCPServer.new('', 65432)
|
66
|
+
#
|
67
|
+
# or (untested):
|
68
|
+
#
|
69
|
+
# TCPServer.new(65432)
|
70
|
+
#
|
71
|
+
# Using EventMachine:
|
72
|
+
#
|
73
|
+
# EM.run {
|
74
|
+
# start_server '', 65432, MyHandler
|
75
|
+
# }
|
76
|
+
#
|
77
|
+
#
|
78
|
+
# In your listener code you can recover the original destination by passing in an
|
79
|
+
# accepted socket or an EventMachine::Connection-based handler.
|
80
|
+
#
|
81
|
+
# PacketThief.original_dest(socket_or_em_connection)
|
82
|
+
#
|
83
|
+
# PacketThief also provides several EventMachine handlers to help build
|
84
|
+
# interceptors. For example, PacketThief::Handlers::TransparentProxy provides a
|
85
|
+
# class that allows you to view or mangle arbitrary TCP traffic before passing it
|
86
|
+
# on to the original destination. The package also includes several handlers for
|
87
|
+
# dealing with SSL-based traffic. Unlike EventMachine's built-in SSL support
|
88
|
+
# (#start_tls), PacketThief::Handlers::SSLClient and
|
89
|
+
# PacketThief::Handlers::SSLServer give you direct access to the
|
90
|
+
# OpenSSL::SSL::SSLContext to configure certificates and callbacks, and
|
91
|
+
# PacketThief::Handlers::SSLSmartProxy will connect to the original destination
|
92
|
+
# in order to acquire its host certificate, which it then modifies for use with a
|
93
|
+
# configured CA. See the documentation and the example directory for more
|
94
|
+
# information.
|
95
|
+
#
|
96
|
+
# == Mac OS X Setup example
|
97
|
+
#
|
98
|
+
# * Share your wifi over your ethernet. Mac OS X will run natd with your wifi as the lan.
|
99
|
+
# * Connect a mobile or wifi device to this wifi.
|
100
|
+
# * Run your PacketThief code, and specify `:in_interface => 'en1'` (assuming
|
101
|
+
# your Airport/wifi is on en1) in the .where clause. The specified :to_ports
|
102
|
+
# port should start receiving incoming TCP connections.
|
103
|
+
#
|
104
|
+
# Note that connections initiated both on the Mac and on any device from the
|
105
|
+
# network will hit your socket. In the future, you will be able to narrow down
|
106
|
+
# what traffic is caught.
|
107
|
+
#
|
108
|
+
module PacketThief
|
109
|
+
autoload :RedirectRule, 'packetthief/redirect_rule'
|
110
|
+
autoload :Impl, 'packetthief/impl'
|
111
|
+
|
112
|
+
autoload :Handlers, 'packetthief/handlers'
|
113
|
+
autoload :Logging, 'packetthief/logging'
|
114
|
+
autoload :Util, 'packetthief/util'
|
115
|
+
|
116
|
+
class << self
|
117
|
+
include Logging
|
118
|
+
end
|
119
|
+
|
120
|
+
def self.implementation; @implementation ; end
|
121
|
+
|
122
|
+
def self.implementation=(newimpl)
|
123
|
+
logdebug "Set implementation to: #{newimpl}"
|
124
|
+
if newimpl == nil
|
125
|
+
@implementation = nil
|
126
|
+
elsif newimpl.kind_of? Module
|
127
|
+
@implementation = newimpl
|
128
|
+
else
|
129
|
+
PacketThief::Impl.constants.each do |c|
|
130
|
+
if c.downcase.to_sym == newimpl.downcase.to_sym
|
131
|
+
@implementation = PacketThief::Impl.const_get c
|
132
|
+
return @implementation
|
133
|
+
end
|
134
|
+
end
|
135
|
+
raise AttributeError, "Unknown implementation"
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
def self.guess_implementation
|
140
|
+
case RUBY_PLATFORM
|
141
|
+
when /linux/
|
142
|
+
Impl::Netfilter
|
143
|
+
when /darwin(10|[0-9]($|[^0]))/ # Mac OS X 10.6 and earlier.
|
144
|
+
Impl::Ipfw
|
145
|
+
when /darwin(11)/ # Mac OS X 10.7
|
146
|
+
Impl::PFRdr
|
147
|
+
else
|
148
|
+
raise "Platform #{RUBY_PLATFORM} not yet supported! If you know your network implementation, call it directly."
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
# Pass the call on to @implementation, or an OS-specific default, if one is known.
|
153
|
+
def self.method_missing(m, *args, &block)
|
154
|
+
logdebug "method_missing: #{m}", :args => args, :block => block
|
155
|
+
if self.implementation == nil
|
156
|
+
self.implementation = guess_implementation
|
157
|
+
end
|
158
|
+
implementation.logger = @logger if implementation.respond_to? :logger=
|
159
|
+
self.implementation.send(m, *args, &block)
|
160
|
+
end
|
161
|
+
|
162
|
+
# Quietly ignore .revert calls if implementation is nil.
|
163
|
+
def self.revert
|
164
|
+
implementation.revert if implementation
|
165
|
+
end
|
166
|
+
|
167
|
+
end
|