mallory 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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 2986cd40c0575541258ea30258a3647ba162d6c3
4
- data.tar.gz: ffbeddde71442cb1fc00cab05f48f560cd6b2e19
3
+ metadata.gz: 36c6e50ca46bfb32d70ae47130ca7c7eb2913ed1
4
+ data.tar.gz: d6775624b84fa2e2986746ca8f81ba9366236984
5
5
  SHA512:
6
- metadata.gz: 930f5970b48a7085b177b21da243ea8a4b598a150fdce183a1769036c7f8ede30d8f84080c230e883fc30d0204f687a434c3d5ac853bb3d97f6f9414357dc042
7
- data.tar.gz: 7d5bf8c37b5335631e7b61cce9b0402ca99a4dbb6d33980b5670f32fc8741a0f51f5e4cc16e66698bfbc09fa043a00e24ff9079ff201c03f431c4969810995ba
6
+ metadata.gz: a5ddbc9ad9ca0e0aacbc83abf303e51586dc476d1a980809531371088ff9fdb416260a05963b8668e73e484e0f5e69387868ce4cb66ea4f2b7420a01000eeec5
7
+ data.tar.gz: c80700a945705fdb35ed0146879948489af144d694bfdcdd053e312f553e4bf23778240042b909a7af2607817109d18efd046c554cd13c4b10aa70d63a6b6a97
data/.gitignore CHANGED
@@ -8,3 +8,6 @@ keys/*.key
8
8
  keys/*.csr
9
9
  proxies.txt
10
10
  mallory.log
11
+ debug.log
12
+ request.log
13
+ output.log
data/Gemfile CHANGED
@@ -1,3 +1,4 @@
1
1
  source "http://rubygems.org"
2
2
 
3
+ gem 'certificate_authority', :git => "git://github.com/cchandler/certificate_authority", :ref => '58161e4552cc1aeca846da3e25ed66721354ee11'
3
4
  gemspec
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2013 Marcin Sawicki, Maria Kacik
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
6
+ this software and associated documentation files (the "Software"), to deal in
7
+ the Software without restriction, including without limitation the rights to
8
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9
+ the Software, and to permit persons to whom the Software is furnished to do so,
10
+ subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
17
+ FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18
+ COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md CHANGED
@@ -5,22 +5,27 @@
5
5
  [![Dependency Status](https://gemnasium.com/odcinek/mallory.png?travis)](https://gemnasium.com/odcinek/mallory)
6
6
  [![Code Climate](https://codeclimate.com/github/odcinek/mallory.png)](https://codeclimate.com/github/odcinek/mallory)
7
7
 
8
- Man-in-the-middle http/https transparent http (CONNECT) proxy over bunch of (unreliable) backends.
9
- It is intended to be used for running test suits / scrapers. It basically shields the proxied application from low responsiveness / poor reliability of underlying proxies.
8
+ Man-in-the-middle transparent HTTP/HTTPS CONNECT proxy, supports
9
+ * both HTTP and HTTPS (via CONNECT https tunneling)
10
+ * load balancing over external (unreliable) backend proxies, with some added reliability and retry policies
10
11
 
11
- Proxy list is provided by external backend (ActiveRecord model, Redis set) and is refreshed periodically. Original use case involves separate proxy-gathering daemon (out of the scope of this project).
12
+ It is intended to be used for running test suits / scrapers. It basically shields the proxied application from low responsiveness / poor reliability of underlying proxies, while providing a full request log (both for HTTP and HTTPS).
13
+ If not backends specified, requests will be performed directly.
12
14
 
13
- For the mallory to work properly client certificate validation needs to be turned off.
15
+ Optional backend proxy list is fetched from Redis or flat file, and refreshed periodically. Original use case involves separate proxy-gathering daemon (out of the scope of this project).
16
+
17
+ For mallory to work properly custom CA needs to be added as trusted. Optionally client certificate validation can be turned off.
14
18
 
15
19
  ## Usage
16
20
 
17
21
  ### Command line
18
22
 
19
- ```bash
20
- ./keys/keygen.sh
21
- bundle exec ./bin/mallory -v -l 9999 #default (no proxy backend, direct requests)
22
- bundle exec ./bin/mallory -v -b file://proxies.txt -l 9999 #start with proxy file
23
- bundle exec ./bin/mallory -v -b redis://127.0.0.1:6379 -l 9999 #start with Redis backend
23
+ Generate keys with ```./keys/keygen.sh```
24
+
25
+ ```
26
+ bundle exec ./bin/mallory -v -p 9999 #default (no proxy backend, direct requests)
27
+ bundle exec ./bin/mallory -v -b file://proxies.txt -p 9999 #start with proxy file
28
+ bundle exec ./bin/mallory -v -b redis://127.0.0.1:6379 -p 9999 #start with Redis backend
24
29
  ```
25
30
 
26
31
  ```bash
@@ -28,6 +33,8 @@ curl --insecure --proxy 127.0.0.1:9999 https://www.dropbox.com/login
28
33
  phantomjs --debug=yes --ignore-ssl-errors=yes --ssl-protocol=sslv2 --proxy=127.0.0.1:9999 --proxy-type=http hello.js
29
34
  ```
30
35
 
36
+ Do ```bundle exec ./bin/mallory --help``` for help.
37
+
31
38
  ### Interface
32
39
 
33
40
  ```ruby
@@ -72,7 +79,3 @@ Redis key TODO
72
79
 
73
80
  - [Marcin Sawicki](https://github.com/odcinek)
74
81
  - [Maria Kacik](https://github.com/mkacik)
75
-
76
- ## License
77
-
78
- (The MIT License)
@@ -13,12 +13,20 @@ options = {}
13
13
  OptionParser.new do |opts|
14
14
  opts.banner = "Usage: proxybalancer [options]"
15
15
 
16
- opts.on("-l", "--listen PORT", Integer, "Port to listen on (default 9999)") do |v|
17
- options[:listen] = v
16
+ opts.on("-p", "--port PORT", Integer, "Port to listen on (default 9999)") do |v|
17
+ options[:port] = v
18
18
  end
19
19
 
20
20
  opts.on("-b", "--backend BACKEND", String, "Backend to use (default 'file://proxies.txt')") do |v|
21
- options[:listen] = v
21
+ options[:backend] = v
22
+ end
23
+
24
+ opts.on("-cac", "--certificate-authority-cert CERT", String, "CA cert (default './keys/ca.crt')") do |v|
25
+ options[:ca_cert] = v
26
+ end
27
+
28
+ opts.on("-cak", "--certificate-authority-key KEY", String, "CA key (default './keys/ca.key')") do |v|
29
+ options[:ca_key] = v
22
30
  end
23
31
 
24
32
  opts.on("-ct", "--connect-timeout SECONDS", Integer, "Proxy connect timeout (default 2s)") do |v|
@@ -29,37 +37,51 @@ OptionParser.new do |opts|
29
37
  options[:it] = v
30
38
  end
31
39
 
40
+ opts.on("-l", "--activity-log LOGFILE", String, "Log events & debug (default STDOUT)") do |v|
41
+ options[:activity_log] = v
42
+ end
43
+
44
+ opts.on("-r", "--request-log LOGFILE", String, "Log requests (default none)") do |v|
45
+ options[:request_log] = v
46
+ end
47
+
32
48
  opts.on("-v", "--verbose", "Run in debug mode") do |v|
33
49
  options[:verbose] = v
34
50
  end
35
51
  end.parse!
36
52
 
37
- def get_logger(verbose)
53
+ def get_logger(activity_log, request_log, verbose)
38
54
  # https://github.com/TwP/logging/blob/master/lib/logging/layouts/pattern.rb
55
+ Logging.init :request, :debug, :info
39
56
  layout = Logging::Layouts::Pattern.new({ :pattern => "%d %-5l : %m\n"})
40
57
  logger = Logging.logger['mallory']
41
- file_appender = Logging.appenders.file("mallory.log")
42
- file_appender.layout = layout
43
- file_appender.level = :info
44
- logger.add_appenders(file_appender)
45
- stdout_appender = Logging.appenders.stdout
46
- stdout_appender.layout = layout
47
- stdout_appender.level = verbose ? :debug : :info
48
- logger.add_appenders(
49
- stdout_appender,
50
- file_appender
51
- )
58
+
59
+ activity_appender = Logging.appenders.stdout
60
+ activity_appender = Logging.appenders.file(activity_log) if not activity_log.nil?
61
+ activity_appender.layout = layout
62
+ activity_appender.level = verbose ? :debug : :info
63
+ logger.add_appenders(activity_appender)
64
+
65
+ if not request_log.nil?
66
+ request_appender = Logging.appenders.file(request_log)
67
+ request_appender.layout = layout
68
+ request_appender.level = :request
69
+ logger.add_appenders(request_appender)
70
+ end
71
+
52
72
  logger
53
73
  end
54
74
 
55
75
  config = Mallory::Configuration.register do |c|
56
- c.logger = get_logger(options.delete(:verbose))
76
+ c.logger = get_logger(options.delete(:activity_log), options.delete(:request_log), options.delete(:verbose))
57
77
  c.backend = Mallory::Backend::Self.new()
58
- #c.backend = Mallory::Backend::File.new("#{Dir.pwd}/proxies.txt")
59
- # c.backend = Mallory::Backend::Redis.new("127.0.0.1", 6379)
78
+ ca = options.has_key?(:ca_cert) ? Mallory::SSL::CA.new(options.delete(:ca_cert), options.delete(:ca_key)) : Mallory::SSL::CA.new("./keys/ca.crt", "./keys/ca.key")
79
+ cf = Mallory::SSL::CertificateFactory.new(ca)
80
+ st = Mallory::SSL::MemoryStorage.new()
81
+ c.certificate_manager = Mallory::SSL::CertificateManager.new(cf, st)
60
82
  c.connect_timeout = options.delete(:connect_timeout) || 2
61
83
  c.inactivity_timeout = options.delete(:inactivity_timeout) || 2
62
- c.listen = options.delete(:listen) || 9999
84
+ c.port = options.delete(:port) || 9999
63
85
  end
64
86
 
65
87
  Mallory::Server.new(config).start!
@@ -0,0 +1,27 @@
1
+ #!/usr/bin/env ruby
2
+ lib = File.expand_path(File.dirname(__FILE__) + '/../lib')
3
+ $LOAD_PATH.unshift(lib) if File.directory?(lib) && !$LOAD_PATH.include?(lib)
4
+
5
+ require 'mallory'
6
+ require 'optparse'
7
+
8
+ key = OpenSSL::PKey::RSA.new 2048
9
+ ca = OpenSSL::X509::Certificate.new
10
+ ca.version = 2
11
+ ca.serial = 1
12
+ ca.subject = OpenSSL::X509::Name.parse "/CN=ROOT"
13
+ ca.issuer = ca.subject
14
+ ca.public_key = key.public_key
15
+ ca.not_before = Time.now
16
+ ca.not_after = ca.not_before + 365*24*3600
17
+ ef = OpenSSL::X509::ExtensionFactory.new
18
+ ef.subject_certificate = ca
19
+ ef.issuer_certificate = ca
20
+ ca.add_extension(ef.create_extension("basicConstraints","CA:TRUE",true))
21
+ ca.add_extension(ef.create_extension("keyUsage","keyCertSign, cRLSign", true))
22
+ ca.add_extension(ef.create_extension("subjectKeyIdentifier","hash",false))
23
+ ca.add_extension(ef.create_extension("authorityKeyIdentifier","keyid:always",false))
24
+ ca.sign(key, OpenSSL::Digest::SHA256.new)
25
+
26
+ File.open("./keys/ca.crt", 'w') {|file| file.write(ca.to_pem) }
27
+ File.open("./keys/ca.key", 'w') {|file| file.write(key.to_pem) }
@@ -2,7 +2,11 @@ require 'mallory/configuration'
2
2
  require 'mallory/backend/redis'
3
3
  require 'mallory/backend/file'
4
4
  require 'mallory/backend/self'
5
- require 'mallory/backend/activerecord'
5
+ require 'mallory/ssl/memory_storage'
6
+ require 'mallory/ssl/certificate'
7
+ require 'mallory/ssl/certificate_factory'
8
+ require 'mallory/ssl/certificate_manager'
9
+ require 'mallory/ssl/ca'
6
10
  require 'mallory/request'
7
11
  require 'mallory/response'
8
12
  require 'mallory/proxy'
@@ -29,12 +29,20 @@ module Mallory
29
29
  @settings[:backend] = other
30
30
  end
31
31
 
32
- def self.listen
33
- @settings[:listen]
32
+ def self.certificate_manager
33
+ @settings[:certificate_manager]
34
34
  end
35
35
 
36
- def self.listen=(other)
37
- @settings[:listen] = other
36
+ def self.certificate_manager=(other)
37
+ @settings[:certificate_manager] = other
38
+ end
39
+
40
+ def self.port
41
+ @settings[:port]
42
+ end
43
+
44
+ def self.port=(other)
45
+ @settings[:port] = other
38
46
  end
39
47
 
40
48
  def self.connect_timeout
@@ -5,10 +5,11 @@ require 'redis'
5
5
 
6
6
  module Mallory
7
7
  class Connection < EM::Connection
8
- def initialize(request_builder, proxy_builder, logger)
8
+ def initialize(request_builder, proxy_builder, logger, certificate_manager)
9
9
  @logger = logger
10
10
  @request_builder = request_builder
11
11
  @proxy_builder = proxy_builder
12
+ @certificate_manager = certificate_manager
12
13
  @start = Time.now
13
14
  @secure = false
14
15
  @proto = "http"
@@ -41,8 +42,17 @@ module Mallory
41
42
  return
42
43
  end
43
44
  if not @secure and request.method.eql?('connect')
45
+ #TEMPORARY FIXME
46
+ cc = @certificate_manager.get(request.host)
47
+ ca = File.read("./keys/ca.crt")
48
+ private_key_file = Tempfile.new('private_key_file')
49
+ private_key_file.write (cc.key)
50
+ private_key_file.close()
51
+ cert_chain_file = Tempfile.new('cert_chain_file')
52
+ cert_chain_file.write (cc.cert + ca)
53
+ cert_chain_file.close()
44
54
  send_data "HTTP/1.0 200 Connection established\r\n\r\n"
45
- start_tls :private_key_file => './keys/server.key', :cert_chain_file => './keys/server.crt', :verify_peer => false
55
+ start_tls :private_key_file => private_key_file.path, :cert_chain_file => cert_chain_file.path, :verify_peer => true
46
56
  return true
47
57
  end
48
58
  proxy = @proxy_builder.build
@@ -57,4 +67,4 @@ module Mallory
57
67
  proxy.perform(request)
58
68
  end
59
69
  end
60
- end
70
+ end
@@ -7,12 +7,13 @@ module Mallory
7
7
 
8
8
  include EventMachine::Deferrable
9
9
 
10
- def initialize(ct, it, backend, response_builder, logger)
10
+ def initialize(ct, it, backend, response_builder, logger, certificate_authority)
11
11
  @connect_timeout = ct
12
12
  @inactivity_timeout = it
13
13
  @backend = backend
14
14
  @response_builder = response_builder
15
15
  @logger = logger
16
+ @certificate_authority = certificate_authority
16
17
  @retries = 0
17
18
  @response = ''
18
19
  end
@@ -72,6 +73,7 @@ module Mallory
72
73
  http.callback {
73
74
  @logger.debug "Attempt #{@retries} - Success"
74
75
  response = @response_builder.build(http)
76
+ @logger.request response.body
75
77
  if response.status > 400
76
78
  @logger.debug "#{response.status} > 400"
77
79
  resubmit
@@ -5,6 +5,6 @@ class Mallory::ProxyBuilder
5
5
  end
6
6
 
7
7
  def build
8
- Mallory::Proxy.new(@config.connect_timeout, @config.inactivity_timeout, @config.backend, @response_builder, @config.logger)
8
+ Mallory::Proxy.new(@config.connect_timeout, @config.inactivity_timeout, @config.backend, @response_builder, @config.logger, @config.certificate_manager)
9
9
  end
10
10
  end
@@ -25,6 +25,10 @@ module Mallory
25
25
  "#{@protocol}://#{@request['host']}#{@request.path}"
26
26
  end
27
27
 
28
+ def host
29
+ @request['host'].split(":")[0]
30
+ end
31
+
28
32
  def method
29
33
  @request.request_method.downcase
30
34
  end
@@ -13,16 +13,17 @@ module Mallory
13
13
  class Server
14
14
  def initialize config
15
15
  @logger = config.logger
16
- @listen = config.listen
16
+ @port = config.port
17
17
  @request_builder = Mallory::RequestBuilder.new(config)
18
18
  response_builder = Mallory::ResponseBuilder.new(config)
19
19
  @proxy_builder = Mallory::ProxyBuilder.new(config, response_builder)
20
+ @certificate_manager = config.certificate_manager
20
21
  end
21
22
 
22
23
  def start!
23
24
  EventMachine.run {
24
25
  @logger.info "Starting mallory"
25
- EventMachine.start_server '127.0.0.1', @listen, Mallory::Connection, @request_builder, @proxy_builder, @logger
26
+ EventMachine.start_server '127.0.0.1', @port, Mallory::Connection, @request_builder, @proxy_builder, @logger, @certificate_manager
26
27
  }
27
28
  end
28
29
  end
@@ -0,0 +1,36 @@
1
+ #!/usr/bin/env ruby
2
+ module Mallory
3
+ module SSL
4
+ class CA
5
+ def initialize crt, key
6
+ @crt = OpenSSL::X509::Certificate.new(File.read(crt))
7
+ @key = OpenSSL::PKey::RSA.new(File.read(key))
8
+ end
9
+
10
+ def to_pem
11
+ @crt.to_pem
12
+ end
13
+
14
+ def sign csr
15
+ cert = OpenSSL::X509::Certificate.new
16
+ cert.serial = 12158693495562452430+rand(10000)
17
+ cert.version = 0 #2
18
+ cert.not_before = Time.now - 3600
19
+ cert.not_after = Time.now + 365*24*3600
20
+ cert.subject = csr.subject
21
+ cert.public_key = csr.public_key
22
+ cert.issuer = @crt.subject
23
+
24
+ ef = OpenSSL::X509::ExtensionFactory.new
25
+ ef.subject_certificate = cert
26
+ ef.issuer_certificate = @crt
27
+ ef.create_extension 'basicConstraints', 'CA:FALSE'
28
+ ef.create_extension 'keyUsage', 'keyEncipherment,dataEncipherment,digitalSignature'
29
+ ef.create_extension 'subjectKeyIdentifier', 'hash'
30
+
31
+ cert.sign @key, OpenSSL::Digest::SHA1.new
32
+ end
33
+
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,30 @@
1
+ module Mallory
2
+ module SSL
3
+ class Certificate
4
+
5
+ def initialize key, cert
6
+ @key = key
7
+ @cert = cert
8
+ end
9
+
10
+ def self.csr domain
11
+ key = OpenSSL::PKey::RSA.new 1024
12
+ csr = OpenSSL::X509::Request.new
13
+ csr.version = 0
14
+ csr.subject = OpenSSL::X509::Name.parse "/CN=#{domain}"
15
+ csr.public_key = key.public_key
16
+ signed = csr.sign key, OpenSSL::Digest::SHA1.new
17
+ return key, signed
18
+ end
19
+
20
+ def cert
21
+ @cert.to_pem
22
+ end
23
+
24
+ def key
25
+ @key.to_pem
26
+ end
27
+
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,17 @@
1
+ module Mallory
2
+ module SSL
3
+ class CertificateFactory
4
+
5
+ def initialize ca
6
+ @ca = ca
7
+ end
8
+
9
+ def get domain
10
+ key, csr = Mallory::SSL::Certificate.csr(domain)
11
+ signed = @ca.sign(csr)
12
+ Mallory::SSL::Certificate.new(key, signed)
13
+ end
14
+
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,22 @@
1
+ module Mallory
2
+ module SSL
3
+ class CertificateManager
4
+
5
+ def initialize storage, factory
6
+ @storage = storage
7
+ @factory = factory
8
+ end
9
+
10
+ def get domain
11
+ key = @storage.get(domain)
12
+ if not key.nil?
13
+ return key
14
+ end
15
+ key = @factory.get(domain)
16
+ @storage.put(key)
17
+ return key
18
+ end
19
+
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,20 @@
1
+ module Mallory
2
+ module SSL
3
+ class MemoryStorage
4
+
5
+ def initialize
6
+ @certs = {}
7
+ end
8
+
9
+ def get domain
10
+ @certs[domain]
11
+ end
12
+
13
+ def put cert
14
+ domain = cert.subject.to_a.select{|x|x[0]=="CN"}.first[1] #OpenSSL::X509::Name could have hash interface. Could.
15
+ @certs[domain] = cert
16
+ end
17
+
18
+ end
19
+ end
20
+ end
@@ -1,3 +1,3 @@
1
1
  module Mallory
2
- VERSION = "0.0.2"
2
+ VERSION = "0.0.3"
3
3
  end
@@ -17,6 +17,7 @@ Gem::Specification.new do |s|
17
17
  s.add_dependency "redis"
18
18
  s.add_dependency "em-http-request"
19
19
  s.add_dependency "logging"
20
+ s.add_dependency "certificate_authority"
20
21
  s.add_development_dependency "rspec"
21
22
  s.add_development_dependency "sinatra"
22
23
  s.add_development_dependency "sinatra-contrib"
@@ -0,0 +1,56 @@
1
+ require 'spec_helper'
2
+ require 'mallory/ssl/ca'
3
+ require 'mallory/ssl/certificate'
4
+
5
+ describe Mallory::SSL::CA do
6
+
7
+ let(:domain) { "example.com" }
8
+
9
+ before(:each) do
10
+ @crt = <<eos
11
+ -----BEGIN CERTIFICATE-----
12
+ MIIBrjCCAWqgAwIBAgIJAI9Jet0z2WxsMA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNV
13
+ BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX
14
+ aWRnaXRzIFB0eSBMdGQwHhcNMTMxMDI3MDkzODI4WhcNMTQxMDI3MDkzODI4WjBF
15
+ MQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50
16
+ ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMEkwDQYJKoZIhvcNAQEBBQADOAAwNQIuATk8
17
+ 6gLLDreN38mrzriz9YG7M+mac0+y+aIP1K8tdZ8YvUKMzlzeC4eKnD4FzwIDAQAB
18
+ o1AwTjAdBgNVHQ4EFgQU9WaQgAp6p34/notUvUlRK/dUlA8wHwYDVR0jBBgwFoAU
19
+ 9WaQgAp6p34/notUvUlRK/dUlA8wDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQUF
20
+ AAMvAAAfhV/bVru7TPf16ip7Q0vBYX1imJJXZV72aTOmHXuQ06wbYQ0zkBmxyNe/
21
+ jTQ=
22
+ -----END CERTIFICATE-----
23
+ eos
24
+ @key = <<eos
25
+ -----BEGIN RSA PRIVATE KEY-----
26
+ MIHkAgEAAi4BOTzqAssOt43fyavOuLP1gbsz6ZpzT7L5og/Ury11nxi9QozOXN4L
27
+ h4qcPgXPAgMBAAECLgCCtONlJPxQJbhzO+j388gHSWmBGfzx/guAg7RljGrPI1Ml
28
+ 0ZFD03gWr4oLNqkCFxmupXAH1DbNXgAj7XZ4qr4dG8uBzvNtAhcMMloyUncI47Ov
29
+ WT6Th8Fzkv+LT3ecqwIXCI9H/o3tchKCuQNAexL+vXyQLgTmyAUCFmJJYpIj+xyn
30
+ 2Fo31Q8N8ORstObwffcCFxlbbXtpwyj9lQcnH0hgQka0iyO2oe5P
31
+ -----END RSA PRIVATE KEY-----
32
+ eos
33
+ @dir = Dir.mktmpdir
34
+ @crt_file = "#{@dir}/test.crt"
35
+ @key_file = "#{@dir}/test.key"
36
+ File.open(@crt_file, 'w') { |file| file.write(@crt) }
37
+ File.open(@key_file, 'w') { |file| file.write(@key) }
38
+ end
39
+
40
+ after(:each) do
41
+ FileUtils.remove_entry_secure @dir
42
+ end
43
+
44
+ it "Reads a cert from a file" do
45
+ Mallory::SSL::CA.new(@crt_file, @key_file).to_pem.should eq(@crt)
46
+ end
47
+
48
+ it "Signs a given csr" do
49
+ key, csr = Mallory::SSL::Certificate.csr(domain)
50
+ ca = Mallory::SSL::CA.new(@crt_file, @key_file)
51
+ cert = ca.sign(csr)
52
+ cert.subject.should eq(OpenSSL::X509::Name.parse "/CN=#{domain}")
53
+ Mallory::SSL::Certificate.new(key, cert).cert
54
+ end
55
+
56
+ end
@@ -0,0 +1,12 @@
1
+ require 'spec_helper'
2
+ require 'mallory/ssl/certificate_factory'
3
+
4
+ describe Mallory::SSL::CertificateFactory do
5
+
6
+ it "Generate a cert" do
7
+ #ca = ""
8
+ #cf = Mallory::SSL::CertificateFactory.new(ca)
9
+ #cf.get(cert)
10
+ end
11
+
12
+ end
@@ -0,0 +1,27 @@
1
+ require 'spec_helper'
2
+ require 'mallory/ssl/certificate_manager'
3
+
4
+ describe Mallory::SSL::CertificateManager do
5
+
6
+ before(:each) do
7
+ @domain = "domain.com"
8
+ @cs = double("certificate_storage")
9
+ expect(@cs).to receive(:get).with(@domain).and_return(nil)
10
+ expect(@cs).to receive(:put)
11
+ @cf = double("certificate_factory")
12
+ expect(@cf).to receive(:get).with(@domain).and_return("CERT")
13
+ end
14
+
15
+ it "Gets a new cert" do
16
+ cf = Mallory::SSL::CertificateManager.new(@cs, @cf)
17
+ cf.get(@domain).should eq("CERT")
18
+ end
19
+
20
+ it "Gets the same cert" do
21
+ expect(@cs).to receive(:get).with(@domain).and_return("CERT")
22
+ cf = Mallory::SSL::CertificateManager.new(@cs, @cf)
23
+ cf.get(@domain).should eq("CERT")
24
+ cf.get(@domain).should eq("CERT")
25
+ end
26
+
27
+ end
@@ -0,0 +1,10 @@
1
+ require 'spec_helper'
2
+ require 'mallory/ssl/certificate'
3
+
4
+ describe Mallory::SSL::Certificate do
5
+
6
+ it "Generates a csr" do
7
+ Mallory::SSL::Certificate.csr("example.com")[1].to_s.should match /^-----BEGIN CERTIFICATE REQUEST.*END CERTIFICATE REQUEST-----$/m
8
+ end
9
+
10
+ end
@@ -0,0 +1,24 @@
1
+ require 'spec_helper'
2
+ require 'mallory/ssl/memory_storage'
3
+
4
+ describe Mallory::SSL::MemoryStorage do
5
+
6
+ let(:domain) { "example.com" }
7
+ let(:name) { OpenSSL::X509::Name.parse "/DC=unknown/CN=#{domain}" }
8
+ let(:cert) do
9
+ cert = OpenSSL::X509::Certificate.new
10
+ cert.subject = name
11
+ cert
12
+ end
13
+
14
+ it "Put a cert" do
15
+ Mallory::SSL::MemoryStorage.new().put(cert)
16
+ end
17
+
18
+ it "Get a cert" do
19
+ ms = Mallory::SSL::MemoryStorage.new()
20
+ ms.put(cert)
21
+ ms.get(domain).should eq(ms.get(domain))
22
+ end
23
+
24
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mallory
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.2
4
+ version: 0.0.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Marcin Sawicki
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2013-10-24 00:00:00.000000000 Z
11
+ date: 2013-10-27 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: eventmachine
@@ -66,6 +66,20 @@ dependencies:
66
66
  - - '>='
67
67
  - !ruby/object:Gem::Version
68
68
  version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: certificate_authority
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - '>='
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - '>='
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
69
83
  - !ruby/object:Gem::Dependency
70
84
  name: rspec
71
85
  requirement: !ruby/object:Gem::Requirement
@@ -142,6 +156,7 @@ email:
142
156
  - odcinek@gmail.com
143
157
  executables:
144
158
  - mallory
159
+ - mallory-ca
145
160
  extensions: []
146
161
  extra_rdoc_files: []
147
162
  files:
@@ -149,9 +164,11 @@ files:
149
164
  - .rspec
150
165
  - .travis.yml
151
166
  - Gemfile
167
+ - LICENSE
152
168
  - README.md
153
169
  - Rakefile
154
170
  - bin/mallory
171
+ - bin/mallory-ca
155
172
  - keys/keygen.sh
156
173
  - lib/mallory.rb
157
174
  - lib/mallory/backend/file.rb
@@ -166,12 +183,22 @@ files:
166
183
  - lib/mallory/response.rb
167
184
  - lib/mallory/response_builder.rb
168
185
  - lib/mallory/server.rb
186
+ - lib/mallory/ssl/ca.rb
187
+ - lib/mallory/ssl/certificate.rb
188
+ - lib/mallory/ssl/certificate_factory.rb
189
+ - lib/mallory/ssl/certificate_manager.rb
190
+ - lib/mallory/ssl/memory_storage.rb
169
191
  - lib/mallory/version.rb
170
192
  - mallory.gemspec
171
193
  - spec/mallory.rb
194
+ - spec/mallory/ca_spec.rb
195
+ - spec/mallory/certificate_factory_spec.rb
196
+ - spec/mallory/certificate_manager_spec.rb
197
+ - spec/mallory/certificate_spec.rb
172
198
  - spec/mallory/configuration_spec.rb
173
199
  - spec/mallory/connection_spec.rb
174
200
  - spec/mallory/file_backend_spec.rb
201
+ - spec/mallory/memory_storage_spec.rb
175
202
  - spec/mallory/proxy_spec.rb
176
203
  - spec/mallory/request_spec.rb
177
204
  - spec/mallory/response_spec.rb
@@ -204,9 +231,14 @@ summary: Man-in-the-middle http/https transparent http (CONNECT) proxy over bunc
204
231
  of (unreliable) backends
205
232
  test_files:
206
233
  - spec/mallory.rb
234
+ - spec/mallory/ca_spec.rb
235
+ - spec/mallory/certificate_factory_spec.rb
236
+ - spec/mallory/certificate_manager_spec.rb
237
+ - spec/mallory/certificate_spec.rb
207
238
  - spec/mallory/configuration_spec.rb
208
239
  - spec/mallory/connection_spec.rb
209
240
  - spec/mallory/file_backend_spec.rb
241
+ - spec/mallory/memory_storage_spec.rb
210
242
  - spec/mallory/proxy_spec.rb
211
243
  - spec/mallory/request_spec.rb
212
244
  - spec/mallory/response_spec.rb