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