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