evil-proxy 0.0.2 → 0.0.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/README.md +40 -1
- data/bin/evil-proxy +8 -0
- data/evil-proxy.gemspec +3 -2
- data/lib/evil-proxy.rb +1 -0
- data/lib/evil-proxy/agentproxy.rb +54 -0
- data/lib/evil-proxy/httpproxy.rb +7 -5
- data/lib/evil-proxy/httprequest.rb +3 -9
- data/lib/evil-proxy/mitmproxy.rb +116 -0
- data/lib/evil-proxy/quickcert.rb +349 -0
- data/lib/evil-proxy/version.rb +1 -1
- metadata +24 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 53684305896f9e807ca7a800ce16365d1075047b
|
4
|
+
data.tar.gz: 9f3436b932cd284a97d572630b8eef0c82f8ccba
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f4ab48b584aedfb81bdd292a29764fbcd17a2b55f91ad648ef1c07f572ccc5a8552bcea270a088c5766dc806bbf6f1243b6d77e0c34e4e9ed9fcc70503ff15f8
|
7
|
+
data.tar.gz: 6f4f16d5c257a680e7e13a6b8090ffa2bc561f324e75e7b29d302ee36f5dcd146ba761333942066042ffdd6af54900b2e34f2e30da7cfe4ead31d7fa9f3852fa
|
data/.gitignore
CHANGED
data/README.md
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# EvilProxy
|
2
2
|
|
3
|
-
A ruby http proxy to do :imp: things.
|
3
|
+
A ruby http/https proxy, with SSL MITM support to do :imp: things.
|
4
4
|
|
5
5
|
## Installation
|
6
6
|
|
@@ -18,6 +18,45 @@ Or install it yourself as:
|
|
18
18
|
|
19
19
|
## Usage
|
20
20
|
|
21
|
+
#### MITMProxyServer
|
22
|
+
`MITMProxyServer` is a subclass of `HTTPProxyServer`, so it also has the callback & plugin system, this proxy will embed a mini CA, which generates certificates on the fly, so you may need to import the CA certificate (./certs/CA/cacert.pem) into your browser.
|
23
|
+
|
24
|
+
```ruby
|
25
|
+
require 'evil-proxy'
|
26
|
+
|
27
|
+
proxy = EvilProxy::MITMProxyServer.new Port: 8080
|
28
|
+
proxy.start
|
29
|
+
```
|
30
|
+
|
31
|
+
Without import the CA certificate
|
32
|
+
```shell
|
33
|
+
$ https_proxy=http://localhost:8080 curl https://github.com
|
34
|
+
# =>
|
35
|
+
# curl: (60) SSL certificate problem: Invalid certificate chain
|
36
|
+
# More details here: http://curl.haxx.se/docs/sslcerts.html
|
37
|
+
#
|
38
|
+
# curl performs SSL certificate verification by default, using a "bundle"
|
39
|
+
# of Certificate Authority (CA) public keys (CA certs). If the default
|
40
|
+
# bundle file isn't adequate, you can specify an alternate file
|
41
|
+
# using the --cacert option.
|
42
|
+
# If this HTTPS server uses a certificate signed by a CA represented in
|
43
|
+
# the bundle, the certificate verification probably failed due to a
|
44
|
+
# problem with the certificate (it might be expired, or the name might
|
45
|
+
# not match the domain name in the URL).
|
46
|
+
# If you'd like to turn off curl's verification of the certificate, use
|
47
|
+
# the -k (or --insecure) option.
|
48
|
+
```
|
49
|
+
|
50
|
+
```shell
|
51
|
+
$ https_proxy=http://localhost:8080 curl https://github.com --insecure
|
52
|
+
# =>
|
53
|
+
# <!DOCTYPE html>
|
54
|
+
# <html lang="en" class="">
|
55
|
+
# ...
|
56
|
+
```
|
57
|
+
|
58
|
+
So you can intercept and modify https traffic, ie: requests & responses.
|
59
|
+
|
21
60
|
#### Basic usage: hooks
|
22
61
|
|
23
62
|
```ruby
|
data/bin/evil-proxy
ADDED
data/evil-proxy.gemspec
CHANGED
@@ -8,8 +8,8 @@ Gem::Specification.new do |spec|
|
|
8
8
|
spec.version = EvilProxy::VERSION
|
9
9
|
spec.authors = ["Theo"]
|
10
10
|
spec.email = ["bbtfrr@gmail.com"]
|
11
|
-
spec.summary = %q{A ruby http proxy to do EVIL things.}
|
12
|
-
spec.description = %q{A ruby http proxy
|
11
|
+
spec.summary = %q{A ruby http/https proxy to do EVIL things.}
|
12
|
+
spec.description = %q{A ruby http/https proxy, with SSL MITM support.}
|
13
13
|
spec.homepage = "https://github.com/bbtfr/evil-proxy"
|
14
14
|
spec.license = "MIT"
|
15
15
|
|
@@ -20,4 +20,5 @@ Gem::Specification.new do |spec|
|
|
20
20
|
|
21
21
|
spec.add_development_dependency "bundler", "~> 1.6"
|
22
22
|
spec.add_development_dependency "rake"
|
23
|
+
spec.add_development_dependency "pry-byebug"
|
23
24
|
end
|
data/lib/evil-proxy.rb
CHANGED
@@ -0,0 +1,54 @@
|
|
1
|
+
require 'webrick'
|
2
|
+
require 'webrick/https'
|
3
|
+
require 'webrick/httpproxy'
|
4
|
+
require 'openssl'
|
5
|
+
|
6
|
+
require 'pry-byebug'
|
7
|
+
|
8
|
+
class EvilProxy::AgentProxyServer < EvilProxy::HTTPProxyServer
|
9
|
+
|
10
|
+
def initialize_callbacks config
|
11
|
+
@mitm_server = config[:MITMProxyServer]
|
12
|
+
end
|
13
|
+
|
14
|
+
def fire key, *args
|
15
|
+
@mitm_server.fire key, *args, self
|
16
|
+
end
|
17
|
+
|
18
|
+
def perform_proxy_request(req, res)
|
19
|
+
uri = req.request_uri
|
20
|
+
path = uri.path.dup
|
21
|
+
path << "?" << uri.query if uri.query
|
22
|
+
header = Hash.new
|
23
|
+
choose_header(req, header)
|
24
|
+
response = nil
|
25
|
+
|
26
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
27
|
+
http.use_ssl = true
|
28
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
29
|
+
http.start do
|
30
|
+
if @config[:ProxyTimeout]
|
31
|
+
################################## these issues are
|
32
|
+
http.open_timeout = 30 # secs # necessary (maybe because
|
33
|
+
http.read_timeout = 60 # secs # Ruby's bug, but why?)
|
34
|
+
##################################
|
35
|
+
end
|
36
|
+
|
37
|
+
response = yield(http, path, header)
|
38
|
+
end
|
39
|
+
|
40
|
+
# Persistent connection requirements are mysterious for me.
|
41
|
+
# So I will close the connection in every response.
|
42
|
+
res['proxy-connection'] = "close"
|
43
|
+
res['connection'] = "close"
|
44
|
+
|
45
|
+
# Convert Net::HTTP::HTTPResponse to WEBrick::HTTPResponse
|
46
|
+
res.status = response.code.to_i
|
47
|
+
choose_header(response, res)
|
48
|
+
set_cookie(response, res)
|
49
|
+
res.body = response.body
|
50
|
+
end
|
51
|
+
|
52
|
+
alias_method :service, :proxy_service
|
53
|
+
|
54
|
+
end
|
data/lib/evil-proxy/httpproxy.rb
CHANGED
@@ -2,12 +2,14 @@ require 'webrick'
|
|
2
2
|
require 'webrick/httpproxy'
|
3
3
|
|
4
4
|
class EvilProxy::HTTPProxyServer < WEBrick::HTTPProxyServer
|
5
|
+
attr_reader :callbacks
|
6
|
+
|
5
7
|
VALID_CALBACKS = Array.new
|
6
8
|
DEFAULT_CALLBACKS = Hash.new
|
7
9
|
|
8
|
-
def initialize
|
9
|
-
initialize_callbacks
|
10
|
-
fire :when_initialize,
|
10
|
+
def initialize config = {}, default = WEBrick::Config::HTTP
|
11
|
+
initialize_callbacks config
|
12
|
+
fire :when_initialize, config, default
|
11
13
|
super
|
12
14
|
end
|
13
15
|
|
@@ -27,7 +29,7 @@ class EvilProxy::HTTPProxyServer < WEBrick::HTTPProxyServer
|
|
27
29
|
end
|
28
30
|
end
|
29
31
|
|
30
|
-
def
|
32
|
+
def service req, res
|
31
33
|
fire :before_request, req
|
32
34
|
super
|
33
35
|
fire :before_response, req, res
|
@@ -64,7 +66,7 @@ class EvilProxy::HTTPProxyServer < WEBrick::HTTPProxyServer
|
|
64
66
|
end
|
65
67
|
|
66
68
|
private
|
67
|
-
def initialize_callbacks
|
69
|
+
def initialize_callbacks config
|
68
70
|
@callbacks = Hash.new
|
69
71
|
DEFAULT_CALLBACKS.each do |key, callbacks|
|
70
72
|
@callbacks[key] = callbacks.clone
|
@@ -1,11 +1,5 @@
|
|
1
|
-
|
2
|
-
alias_method :original_body, :body
|
3
|
-
def body
|
4
|
-
@evil_body || original_body
|
5
|
-
end
|
6
|
-
|
7
|
-
def body= body
|
8
|
-
@evil_body = body
|
9
|
-
end
|
1
|
+
require 'webrick/httprequest'
|
10
2
|
|
3
|
+
WEBrick::HTTPRequest.class_eval do
|
4
|
+
attr_writer :body, :unparsed_uri
|
11
5
|
end
|
@@ -0,0 +1,116 @@
|
|
1
|
+
require 'evil-proxy/httpproxy'
|
2
|
+
require 'evil-proxy/agentproxy'
|
3
|
+
require 'evil-proxy/quickcert'
|
4
|
+
|
5
|
+
class EvilProxy::MITMProxyServer < EvilProxy::HTTPProxyServer
|
6
|
+
|
7
|
+
def initialize config
|
8
|
+
super
|
9
|
+
@mitm_servers = {}
|
10
|
+
@mitm_port = 4433
|
11
|
+
end
|
12
|
+
|
13
|
+
def ca
|
14
|
+
return @ca if @ca
|
15
|
+
logger.info "Create CA"
|
16
|
+
|
17
|
+
ca_config = {}
|
18
|
+
ca_config[:hostname] = 'ca'
|
19
|
+
ca_config[:domainname] = 'mitm.proxy'
|
20
|
+
ca_config[:password] = 'password'
|
21
|
+
ca_config[:CA_dir] ||= File.join(Dir.pwd, "certs/CA")
|
22
|
+
|
23
|
+
ca_config[:keypair_file] ||= File.join ca_config[:CA_dir], "private/cakeypair.pem"
|
24
|
+
ca_config[:cert_file] ||= File.join ca_config[:CA_dir], "cacert.pem"
|
25
|
+
ca_config[:serial_file] ||= File.join ca_config[:CA_dir], "serial"
|
26
|
+
ca_config[:new_certs_dir] ||= File.join ca_config[:CA_dir], "newcerts"
|
27
|
+
ca_config[:new_keypair_dir] ||= File.join ca_config[:CA_dir], "private/keypair_backup"
|
28
|
+
ca_config[:crl_dir] ||= File.join ca_config[:CA_dir], "crl"
|
29
|
+
|
30
|
+
ca_config[:ca_cert_days] ||= 5 * 365 # five years
|
31
|
+
ca_config[:ca_rsa_key_length] ||= 2048
|
32
|
+
|
33
|
+
ca_config[:cert_days] ||= 365 # one year
|
34
|
+
ca_config[:cert_key_length_min] ||= 1024
|
35
|
+
ca_config[:cert_key_length_max] ||= 2048
|
36
|
+
|
37
|
+
ca_config[:crl_file] ||= File.join ca_config[:crl_dir], "#{ca_config[:hostname]}.crl"
|
38
|
+
ca_config[:crl_pem_file] ||= File.join ca_config[:crl_dir], "#{ca_config[:hostname]}.pem"
|
39
|
+
ca_config[:crl_days] ||= 14
|
40
|
+
|
41
|
+
if ca_config[:name].nil?
|
42
|
+
ca_config[:name] = [
|
43
|
+
['C', 'US', OpenSSL::ASN1::PRINTABLESTRING],
|
44
|
+
['O', ca_config[:domainname], OpenSSL::ASN1::UTF8STRING],
|
45
|
+
['OU', ca_config[:hostname], OpenSSL::ASN1::UTF8STRING],
|
46
|
+
]
|
47
|
+
end
|
48
|
+
|
49
|
+
@ca = QuickCert.new ca_config
|
50
|
+
end
|
51
|
+
|
52
|
+
def create_self_signed_cert host
|
53
|
+
cn = [["C", "US"], ["O", host], ["CN", host]]
|
54
|
+
comment = "Generated by Ruby/OpenSSL/MITMProxyServer"
|
55
|
+
name = OpenSSL::X509::Name.new(cn)
|
56
|
+
hostname = name.to_s.scan(/CN=([\w.]+)/)[0][0]
|
57
|
+
|
58
|
+
logger.info "Create cert for #{hostname}"
|
59
|
+
cert_config = { type: 'server', hostname: hostname }
|
60
|
+
cert_file, cert, key = ca.create_cert(cert_config)
|
61
|
+
|
62
|
+
return cert, key
|
63
|
+
end
|
64
|
+
|
65
|
+
def retry_start_agent_server config
|
66
|
+
mitm_server = nil
|
67
|
+
10.times do
|
68
|
+
begin
|
69
|
+
# XXX: ask system for an unused port
|
70
|
+
config = config.merge(Port: @mitm_port)
|
71
|
+
mitm_server = EvilProxy::AgentProxyServer.new config
|
72
|
+
rescue Errno::EADDRINUSE
|
73
|
+
ensure
|
74
|
+
@mitm_port += 1
|
75
|
+
return mitm_server if mitm_server
|
76
|
+
end
|
77
|
+
end
|
78
|
+
raise RuntimeError, "No avaliable port found, stop retrying"
|
79
|
+
end
|
80
|
+
|
81
|
+
def start_mitm_server unparsed_uri, host, port
|
82
|
+
if @mitm_servers[unparsed_uri]
|
83
|
+
return @mitm_servers[unparsed_uri].config[:Port]
|
84
|
+
else
|
85
|
+
cert, key = create_self_signed_cert host
|
86
|
+
agent_config = self.config.merge(
|
87
|
+
MITMProxyServer: self,
|
88
|
+
SSLEnable: true,
|
89
|
+
SSLVerifyClient: OpenSSL::SSL::VERIFY_NONE,
|
90
|
+
SSLCertificate: cert,
|
91
|
+
SSLPrivateKey: key,
|
92
|
+
)
|
93
|
+
mitm_server = retry_start_agent_server agent_config
|
94
|
+
|
95
|
+
@mitm_servers[unparsed_uri] = mitm_server
|
96
|
+
|
97
|
+
Thread.new do mitm_server.start end
|
98
|
+
return mitm_server.config[:Port]
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def do_MITM req, res
|
103
|
+
unparsed_uri = req.unparsed_uri
|
104
|
+
host, port = unparsed_uri.split(":")
|
105
|
+
port ||= 443
|
106
|
+
|
107
|
+
mitm_port = start_mitm_server unparsed_uri, host, port
|
108
|
+
req.unparsed_uri = "127.0.0.1:#{mitm_port}"
|
109
|
+
end
|
110
|
+
|
111
|
+
def do_CONNECT req, res
|
112
|
+
do_MITM req, res
|
113
|
+
super
|
114
|
+
end
|
115
|
+
|
116
|
+
end
|
@@ -0,0 +1,349 @@
|
|
1
|
+
require 'openssl'
|
2
|
+
# QuickCert from http://segment7.net/projects/ruby/QuickCert/
|
3
|
+
# QuickCert allows you to quickly and easily create SSL
|
4
|
+
# certificates. It uses a simple configuration file to generate
|
5
|
+
# self-signed client and server certificates.
|
6
|
+
#
|
7
|
+
# QuickCert is a compilation of NAKAMURA Hiroshi's post to
|
8
|
+
# ruby-talk number 89917:
|
9
|
+
#
|
10
|
+
# http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-talk/89917
|
11
|
+
#
|
12
|
+
# the example scripts referenced in the above post, and
|
13
|
+
# gen_csr.rb from Ruby's OpenSSL examples.
|
14
|
+
#
|
15
|
+
# A simple QuickCert configuration file looks like:
|
16
|
+
#
|
17
|
+
# full_hostname = `hostname`.strip
|
18
|
+
# domainname = full_hostname.split('.')[1..-1].join('.')
|
19
|
+
# hostname = full_hostname.split('.')[0]
|
20
|
+
#
|
21
|
+
# CA[:hostname] = hostname
|
22
|
+
# CA[:domainname] = domainname
|
23
|
+
# CA[:CA_dir] = File.join Dir.pwd, "CA"
|
24
|
+
# CA[:password] = '1234'
|
25
|
+
#
|
26
|
+
# CERTS << {
|
27
|
+
# :type => 'server',
|
28
|
+
# :hostname => 'uriel',
|
29
|
+
# :password => '5678',
|
30
|
+
# }
|
31
|
+
#
|
32
|
+
# CERTS << {
|
33
|
+
# :type => 'client',
|
34
|
+
# :user => 'drbrain',
|
35
|
+
# :email => 'drbrain@segment7.net',
|
36
|
+
# }
|
37
|
+
#
|
38
|
+
# This configuration will create a Certificate Authority in a
|
39
|
+
# 'CA' directory in the current directory, a server certificate
|
40
|
+
# with password '5678' for the server 'uriel' in a directory
|
41
|
+
# named 'uriel', and a client certificate for drbrain in the
|
42
|
+
# directory 'drbrain' with no password.
|
43
|
+
#
|
44
|
+
# There are additional SSL knobs you can tweak in the
|
45
|
+
# qc_defaults.rb file.
|
46
|
+
#
|
47
|
+
# To generate the certificates, simply create a qc_config file
|
48
|
+
# where you want the certificate directories to be created, then
|
49
|
+
# run QuickCert.
|
50
|
+
#
|
51
|
+
# QuickCert's homepage is:
|
52
|
+
# http://segment7.net/projects/ruby/QuickCert/
|
53
|
+
|
54
|
+
class QuickCert
|
55
|
+
|
56
|
+
##
|
57
|
+
# QuickCert Version
|
58
|
+
|
59
|
+
VERSION = "1.0.2"
|
60
|
+
CERT_DIR = File.join(Dir.pwd, "certs")
|
61
|
+
Dir.mkdir(CERT_DIR) unless File.exists?(CERT_DIR)
|
62
|
+
|
63
|
+
##
|
64
|
+
# Creates a new QuickCert instance using the Certificate
|
65
|
+
# Authority described in +ca_config+. If there is no CA at
|
66
|
+
# ca_config[:CA_dir], then QuickCert will initialize a new one.
|
67
|
+
|
68
|
+
def initialize(ca_config)
|
69
|
+
@ca_config = ca_config
|
70
|
+
|
71
|
+
create_ca
|
72
|
+
end
|
73
|
+
|
74
|
+
##
|
75
|
+
# Creates a new certificate from +cert_config+ that is signed
|
76
|
+
# by the CA.
|
77
|
+
|
78
|
+
def create_cert(cert_config)
|
79
|
+
dest = cert_config[:hostname] || cert_config[:user]
|
80
|
+
key_file = "#{CERT_DIR}/#{dest}/#{dest}_keypair.pem"
|
81
|
+
cert_file = "#{CERT_DIR}/#{dest}/cert_#{dest}.pem"
|
82
|
+
if File.exists?(cert_file) && File.exists?(key_file)
|
83
|
+
key = OpenSSL::PKey::RSA.new(File.read(key_file))
|
84
|
+
cert = OpenSSL::X509::Certificate.new(File.read(cert_file))
|
85
|
+
else
|
86
|
+
cert_keypair, key = create_key(cert_config)
|
87
|
+
cert_csr = create_csr(cert_config, cert_keypair)
|
88
|
+
cert_file, cert = sign_cert(cert_config, cert_keypair, cert_csr)
|
89
|
+
end
|
90
|
+
return cert_file, cert, key
|
91
|
+
end
|
92
|
+
|
93
|
+
##
|
94
|
+
# Creates a new Certificate Authority from @ca_config if it
|
95
|
+
# does not already exist at ca_config[:CA_dir].
|
96
|
+
|
97
|
+
def create_ca
|
98
|
+
return if File.exists? @ca_config[:CA_dir]
|
99
|
+
|
100
|
+
Dir.mkdir @ca_config[:CA_dir]
|
101
|
+
|
102
|
+
Dir.mkdir File.join(@ca_config[:CA_dir], 'private'), 0700
|
103
|
+
Dir.mkdir File.join(@ca_config[:CA_dir], 'newcerts')
|
104
|
+
Dir.mkdir File.join(@ca_config[:CA_dir], 'crl')
|
105
|
+
|
106
|
+
File.open @ca_config[:serial_file], 'w' do |f| f << "#{Time.now.to_i}" end
|
107
|
+
|
108
|
+
puts "Generating CA keypair" if $DEBUG
|
109
|
+
keypair = OpenSSL::PKey::RSA.new @ca_config[:ca_rsa_key_length]
|
110
|
+
|
111
|
+
cert = OpenSSL::X509::Certificate.new
|
112
|
+
name = @ca_config[:name].dup << ['CN', 'CA']
|
113
|
+
cert.subject = cert.issuer = OpenSSL::X509::Name.new(name)
|
114
|
+
cert.not_before = Time.now
|
115
|
+
cert.not_after = Time.now + @ca_config[:ca_cert_days] * 24 * 60 * 60
|
116
|
+
cert.public_key = keypair.public_key
|
117
|
+
cert.serial = 0x0
|
118
|
+
cert.version = 2 # X509v3
|
119
|
+
|
120
|
+
ef = OpenSSL::X509::ExtensionFactory.new
|
121
|
+
ef.subject_certificate = cert
|
122
|
+
ef.issuer_certificate = cert
|
123
|
+
cert.extensions = [
|
124
|
+
ef.create_extension("basicConstraints","CA:TRUE", true),
|
125
|
+
ef.create_extension("nsComment","Ruby/OpenSSL Generated Certificate"),
|
126
|
+
ef.create_extension("subjectKeyIdentifier", "hash"),
|
127
|
+
ef.create_extension("keyUsage", "cRLSign,keyCertSign", true),
|
128
|
+
]
|
129
|
+
cert.add_extension ef.create_extension("authorityKeyIdentifier",
|
130
|
+
"keyid:always,issuer:always")
|
131
|
+
cert.sign(keypair, OpenSSL::Digest::SHA1.new)
|
132
|
+
|
133
|
+
keypair_export = keypair.export(OpenSSL::Cipher::DES.new(:EDE3, :CBC), @ca_config[:password])
|
134
|
+
|
135
|
+
puts "Writing keypair to #{@ca_config[:keypair_file]}" if $DEBUG
|
136
|
+
File.open @ca_config[:keypair_file], "w", 0400 do |fp|
|
137
|
+
fp << keypair_export
|
138
|
+
end
|
139
|
+
|
140
|
+
puts "Writing cert to #{@ca_config[:cert_file]}" if $DEBUG
|
141
|
+
File.open @ca_config[:cert_file], "w", 0644 do |f|
|
142
|
+
f << cert.to_pem
|
143
|
+
end
|
144
|
+
|
145
|
+
puts "Done generating certificate for #{cert.subject}" if $DEBUG
|
146
|
+
end
|
147
|
+
|
148
|
+
##
|
149
|
+
# Creates a new RSA key from +cert_config+.
|
150
|
+
|
151
|
+
def create_key(cert_config)
|
152
|
+
dest = cert_config[:hostname] || cert_config[:user]
|
153
|
+
keypair_file = "#{CERT_DIR}/#{dest}/#{dest}_keypair.pem"
|
154
|
+
if File.exists?(keypair_file)
|
155
|
+
keypair = OpenSSL::PKey::RSA.new(File.read(keypair_file),
|
156
|
+
cert_config[:password])
|
157
|
+
return keypair_file, keypair
|
158
|
+
end
|
159
|
+
Dir.mkdir("#{CERT_DIR}/#{dest}", 0700) unless File.exists?("#{CERT_DIR}/#{dest}")
|
160
|
+
|
161
|
+
puts "Generating RSA keypair" if $DEBUG
|
162
|
+
keypair = OpenSSL::PKey::RSA.new 1024
|
163
|
+
|
164
|
+
if cert_config[:password].nil? then
|
165
|
+
File.open keypair_file, "w", 0400 do |f|
|
166
|
+
f << keypair.to_pem
|
167
|
+
end
|
168
|
+
else
|
169
|
+
keypair_export = keypair.export(OpenSSL::Cipher::DES.new(:EDE3, :CBC),
|
170
|
+
cert_config[:password])
|
171
|
+
|
172
|
+
puts "Writing keypair to #{keypair_file}" if $DEBUG
|
173
|
+
File.open keypair_file, "w", 0400 do |f|
|
174
|
+
f << keypair_export
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
return keypair_file, keypair
|
179
|
+
end
|
180
|
+
|
181
|
+
##
|
182
|
+
# Creates a new Certificate Signing Request for the keypair in
|
183
|
+
# +keypair_file+, generating and saving new keypair if nil.
|
184
|
+
|
185
|
+
def create_csr(cert_config, keypair_file = nil)
|
186
|
+
keypair = nil
|
187
|
+
dest = cert_config[:hostname] || cert_config[:user]
|
188
|
+
csr_file = "#{CERT_DIR}/#{dest}/csr_#{dest}.pem"
|
189
|
+
|
190
|
+
name = @ca_config[:name].dup
|
191
|
+
case cert_config[:type]
|
192
|
+
when 'server' then
|
193
|
+
name << ['OU', 'CA']
|
194
|
+
name << ['CN', cert_config[:hostname]]
|
195
|
+
when 'client' then
|
196
|
+
name << ['CN', cert_config[:user]]
|
197
|
+
name << ['emailAddress', cert_config[:email]]
|
198
|
+
end
|
199
|
+
name = OpenSSL::X509::Name.new name
|
200
|
+
|
201
|
+
if File.exists? keypair_file then
|
202
|
+
keypair = OpenSSL::PKey::RSA.new(File.read(keypair_file),
|
203
|
+
cert_config[:password])
|
204
|
+
else
|
205
|
+
keypair = create_key cert_config
|
206
|
+
end
|
207
|
+
|
208
|
+
puts "Generating CSR for #{name}" if $DEBUG
|
209
|
+
|
210
|
+
req = OpenSSL::X509::Request.new
|
211
|
+
req.version = 0
|
212
|
+
req.subject = name
|
213
|
+
req.public_key = keypair.public_key
|
214
|
+
req.sign keypair, OpenSSL::Digest::MD5.new
|
215
|
+
|
216
|
+
puts "Writing CSR to #{csr_file}" if $DEBUG
|
217
|
+
File.open csr_file, "w" do |f|
|
218
|
+
f << req.to_pem
|
219
|
+
end
|
220
|
+
|
221
|
+
return csr_file
|
222
|
+
end
|
223
|
+
|
224
|
+
##
|
225
|
+
# Signs the certificate described in +cert_config+ and
|
226
|
+
# +csr_file+, saving it to +cert_file+.
|
227
|
+
|
228
|
+
def sign_cert(cert_config, cert_file, csr_file)
|
229
|
+
csr = OpenSSL::X509::Request.new File.read(csr_file)
|
230
|
+
|
231
|
+
raise "CSR sign verification failed." unless csr.verify csr.public_key
|
232
|
+
|
233
|
+
if csr.public_key.n.num_bits < @ca_config[:cert_key_length_min] then
|
234
|
+
raise "Key length too short"
|
235
|
+
end
|
236
|
+
|
237
|
+
if csr.public_key.n.num_bits > @ca_config[:cert_key_length_max] then
|
238
|
+
raise "Key length too long"
|
239
|
+
end
|
240
|
+
|
241
|
+
if csr.subject.to_a[0, @ca_config[:name].size] != @ca_config[:name] then
|
242
|
+
raise "DN does not match"
|
243
|
+
end
|
244
|
+
|
245
|
+
# Only checks signature here. You must verify CSR according to your
|
246
|
+
# CP/CPS.
|
247
|
+
|
248
|
+
# CA setup
|
249
|
+
|
250
|
+
puts "Reading CA cert from #{@ca_config[:cert_file]}" if $DEBUG
|
251
|
+
ca = OpenSSL::X509::Certificate.new File.read(@ca_config[:cert_file])
|
252
|
+
|
253
|
+
puts "Reading CA keypair from #{@ca_config[:keypair_file]}" if $DEBUG
|
254
|
+
ca_keypair = OpenSSL::PKey::RSA.new File.read(@ca_config[:keypair_file]),
|
255
|
+
@ca_config[:password]
|
256
|
+
|
257
|
+
serial = File.read(@ca_config[:serial_file]).chomp.hex
|
258
|
+
File.open @ca_config[:serial_file], "w" do |f|
|
259
|
+
f << "%04X" % (serial + 1)
|
260
|
+
end
|
261
|
+
|
262
|
+
puts "Generating cert" if $DEBUG
|
263
|
+
|
264
|
+
cert = OpenSSL::X509::Certificate.new
|
265
|
+
from = Time.now
|
266
|
+
cert.subject = csr.subject
|
267
|
+
cert.issuer = ca.subject
|
268
|
+
cert.not_before = from
|
269
|
+
cert.not_after = from + @ca_config[:cert_days] * 24 * 60 * 60
|
270
|
+
cert.public_key = csr.public_key
|
271
|
+
cert.serial = serial
|
272
|
+
cert.version = 2 # X509v3
|
273
|
+
|
274
|
+
basic_constraint = nil
|
275
|
+
key_usage = []
|
276
|
+
ext_key_usage = []
|
277
|
+
|
278
|
+
case cert_config[:type]
|
279
|
+
when "ca" then
|
280
|
+
basic_constraint = "CA:TRUE"
|
281
|
+
key_usage << "cRLSign" << "keyCertSign"
|
282
|
+
when "terminalsubca" then
|
283
|
+
basic_constraint = "CA:TRUE,pathlen:0"
|
284
|
+
key_usage << "cRLSign" << "keyCertSign"
|
285
|
+
when "server" then
|
286
|
+
basic_constraint = "CA:FALSE"
|
287
|
+
key_usage << "digitalSignature" << "keyEncipherment"
|
288
|
+
ext_key_usage << "serverAuth"
|
289
|
+
when "ocsp" then
|
290
|
+
basic_constraint = "CA:FALSE"
|
291
|
+
key_usage << "nonRepudiation" << "digitalSignature"
|
292
|
+
ext_key_usage << "serverAuth" << "OCSPSigning"
|
293
|
+
when "client" then
|
294
|
+
basic_constraint = "CA:FALSE"
|
295
|
+
key_usage << "nonRepudiation" << "digitalSignature" << "keyEncipherment"
|
296
|
+
ext_key_usage << "clientAuth" << "emailProtection"
|
297
|
+
else
|
298
|
+
raise "unknonw cert type \"#{cert_config[:type]}\""
|
299
|
+
end
|
300
|
+
|
301
|
+
ef = OpenSSL::X509::ExtensionFactory.new
|
302
|
+
ef.subject_certificate = cert
|
303
|
+
ef.issuer_certificate = ca
|
304
|
+
ex = []
|
305
|
+
ex << ef.create_extension("basicConstraints", basic_constraint, true)
|
306
|
+
ex << ef.create_extension("nsComment",
|
307
|
+
"Ruby/OpenSSL Generated Certificate")
|
308
|
+
ex << ef.create_extension("subjectKeyIdentifier", "hash")
|
309
|
+
#ex << ef.create_extension("nsCertType", "client,email")
|
310
|
+
unless key_usage.empty? then
|
311
|
+
ex << ef.create_extension("keyUsage", key_usage.join(","))
|
312
|
+
end
|
313
|
+
#ex << ef.create_extension("authorityKeyIdentifier",
|
314
|
+
# "keyid:always,issuer:always")
|
315
|
+
#ex << ef.create_extension("authorityKeyIdentifier", "keyid:always")
|
316
|
+
unless ext_key_usage.empty? then
|
317
|
+
ex << ef.create_extension("extendedKeyUsage", ext_key_usage.join(","))
|
318
|
+
end
|
319
|
+
|
320
|
+
if @ca_config[:cdp_location] then
|
321
|
+
ex << ef.create_extension("crlDistributionPoints",
|
322
|
+
@ca_config[:cdp_location])
|
323
|
+
end
|
324
|
+
|
325
|
+
if @ca_config[:ocsp_location] then
|
326
|
+
ex << ef.create_extension("authorityInfoAccess",
|
327
|
+
"OCSP;" << @ca_config[:ocsp_location])
|
328
|
+
end
|
329
|
+
cert.extensions = ex
|
330
|
+
cert.sign(ca_keypair, OpenSSL::Digest::SHA1.new)
|
331
|
+
|
332
|
+
backup_cert_file = @ca_config[:new_certs_dir] + "/cert_#{cert.serial}.pem"
|
333
|
+
puts "Writing backup cert to #{backup_cert_file}" if $DEBUG
|
334
|
+
File.open backup_cert_file, "w", 0644 do |f|
|
335
|
+
f << cert.to_pem
|
336
|
+
end
|
337
|
+
|
338
|
+
# Write cert
|
339
|
+
dest = cert_config[:hostname] || cert_config[:user]
|
340
|
+
cert_file = "#{CERT_DIR}/#{dest}/cert_#{dest}.pem"
|
341
|
+
puts "Writing cert to #{cert_file}" if $DEBUG
|
342
|
+
File.open cert_file, "w", 0644 do |f|
|
343
|
+
f << cert.to_pem
|
344
|
+
end
|
345
|
+
|
346
|
+
return cert_file, cert
|
347
|
+
end
|
348
|
+
|
349
|
+
end # class QuickCert
|
data/lib/evil-proxy/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: evil-proxy
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Theo
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-
|
11
|
+
date: 2015-11-13 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -38,10 +38,25 @@ dependencies:
|
|
38
38
|
- - ">="
|
39
39
|
- !ruby/object:Gem::Version
|
40
40
|
version: '0'
|
41
|
-
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: pry-byebug
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
description: A ruby http/https proxy, with SSL MITM support.
|
42
56
|
email:
|
43
57
|
- bbtfrr@gmail.com
|
44
|
-
executables:
|
58
|
+
executables:
|
59
|
+
- evil-proxy
|
45
60
|
extensions: []
|
46
61
|
extra_rdoc_files: []
|
47
62
|
files:
|
@@ -50,11 +65,15 @@ files:
|
|
50
65
|
- LICENSE.txt
|
51
66
|
- README.md
|
52
67
|
- Rakefile
|
68
|
+
- bin/evil-proxy
|
53
69
|
- evil-proxy.gemspec
|
54
70
|
- lib/evil-proxy.rb
|
71
|
+
- lib/evil-proxy/agentproxy.rb
|
55
72
|
- lib/evil-proxy/async.rb
|
56
73
|
- lib/evil-proxy/httpproxy.rb
|
57
74
|
- lib/evil-proxy/httprequest.rb
|
75
|
+
- lib/evil-proxy/mitmproxy.rb
|
76
|
+
- lib/evil-proxy/quickcert.rb
|
58
77
|
- lib/evil-proxy/selenium.rb
|
59
78
|
- lib/evil-proxy/store.rb
|
60
79
|
- lib/evil-proxy/version.rb
|
@@ -81,5 +100,5 @@ rubyforge_project:
|
|
81
100
|
rubygems_version: 2.4.6
|
82
101
|
signing_key:
|
83
102
|
specification_version: 4
|
84
|
-
summary: A ruby http proxy to do EVIL things.
|
103
|
+
summary: A ruby http/https proxy to do EVIL things.
|
85
104
|
test_files: []
|