evil-proxy 0.0.2 → 0.0.3
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.
- 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: []
|