puffing-billy 0.10.1 → 0.11.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,41 @@
1
+ # encoding: utf-8
2
+ # frozen_string_literal: true
3
+
4
+ require 'fileutils'
5
+
6
+ module Billy
7
+ # This class is dedicated to the generation of a certificate chain in the
8
+ # PEM format. Fortunately we just have to concatenate the given certificates
9
+ # in the given order and write them to temporary file which will last until
10
+ # the current process terminates.
11
+ #
12
+ # We do not have to generate a certificate chain to make puffing billy work
13
+ # on modern browser like Chrome 59+ or Firefox 55+, but its good to ship it
14
+ # anyways. This mimics the behaviour of the mighty mitmproxy.
15
+ class CertificateChain
16
+ include Billy::CertificateHelpers
17
+
18
+ attr_reader :certificates, :domain
19
+
20
+ # Just pass all certificates into the new instance. We use the variadic
21
+ # argument feature here to ease the usage and improve the readability.
22
+ #
23
+ # Example:
24
+ #
25
+ # certs_chain_file = Billy::CertificateChain.new('localhost',
26
+ # cert1,
27
+ # cert2, ..).file
28
+ def initialize(domain, *certs)
29
+ @domain = domain
30
+ @certificates = [certs].flatten
31
+ end
32
+
33
+ # Write out the certificates chain file and pass the path back. This will
34
+ # produce a temporary file which will be remove after the current process
35
+ # terminates.
36
+ def file
37
+ contents = certificates.map { |cert| cert.to_pem }.join
38
+ write_file("chain-#{domain}.pem", contents)
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,36 @@
1
+ # encoding: utf-8
2
+ # frozen_string_literal: true
3
+
4
+ require 'openssl'
5
+ require 'fileutils'
6
+
7
+ module Billy
8
+ # A set of common certificate helper methods.
9
+ module CertificateHelpers
10
+
11
+ # Give back the date from now plus given days.
12
+ def days_from_now(days)
13
+ Time.now + (days * 24 * 60 * 60)
14
+ end
15
+
16
+ # Give back the date from now minus given days.
17
+ def days_ago(days)
18
+ Time.now - (days * 24 * 60 * 60)
19
+ end
20
+
21
+ # Generate a random serial number for a certificate.
22
+ def serial
23
+ rand(1_000_000..100_000_000_000)
24
+ end
25
+
26
+ # Create/Overwrite a new file with the given name
27
+ # and ensure the location is safely created. Pass
28
+ # back the resulting path.
29
+ def write_file(name, contents)
30
+ path = File.join(Billy.config.certs_path, name)
31
+ FileUtils.mkdir_p(File.dirname(path))
32
+ File.write(path, contents)
33
+ path
34
+ end
35
+ end
36
+ end
@@ -1,3 +1,3 @@
1
1
  module Billy
2
- VERSION = '0.10.1'
2
+ VERSION = '0.11.0'
3
3
  end
@@ -27,12 +27,12 @@ Gem::Specification.new do |gem|
27
27
  gem.add_development_dependency 'rb-inotify'
28
28
  gem.add_development_dependency 'pry'
29
29
  gem.add_development_dependency 'cucumber'
30
- gem.add_development_dependency 'watir-webdriver'
30
+ gem.add_development_dependency 'watir-webdriver', '0.9.1'
31
31
  # addressable 2.5.0 drops support for ruby 1.9.3
32
- gem.add_runtime_dependency 'addressable', '~> 2.4.0'
33
- gem.add_runtime_dependency 'eventmachine', '~> 1.0.4'
32
+ gem.add_runtime_dependency 'addressable', '~> 2.4', '>= 2.4.0'
33
+ gem.add_runtime_dependency 'eventmachine', '~> 1.0', '>= 1.0.4'
34
34
  gem.add_runtime_dependency 'em-synchrony'
35
- gem.add_runtime_dependency 'em-http-request', '~> 1.1.0'
35
+ gem.add_runtime_dependency 'em-http-request', '~> 1.1', '>= 1.1.0'
36
36
  gem.add_runtime_dependency 'eventmachine_httpserver'
37
37
  gem.add_runtime_dependency 'http_parser.rb', '~> 0.6.0'
38
38
  gem.add_runtime_dependency 'multi_json'
@@ -1,18 +1,18 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe Billy::Cache do
4
- describe 'format_url' do
5
- let(:cache) { Billy::Cache.instance }
6
- let(:params) { '?foo=bar' }
7
- let(:callback) { '&callback=quux' }
8
- let(:fragment) { '#baz' }
9
- let(:base_url) { 'http://example.com' }
10
- let(:pipe_url) { 'https://fonts.googleapis.com:443/css?family=Cabin+Sketch:400,700|Love+Ya+Like+A+Sister' }
11
- let(:fragment_url) { "#{base_url}/#{fragment}" }
12
- let(:params_url) { "#{base_url}#{params}" }
13
- let(:params_url_with_callback) { "#{base_url}#{params}#{callback}" }
14
- let(:params_fragment_url) { "#{base_url}#{params}#{fragment}" }
4
+ let(:cache) { Billy::Cache.instance }
5
+ let(:params) { '?foo=bar' }
6
+ let(:callback) { '&callback=quux' }
7
+ let(:fragment) { '#baz' }
8
+ let(:base_url) { 'http://example.com' }
9
+ let(:pipe_url) { 'https://fonts.googleapis.com:443/css?family=Cabin+Sketch:400,700|Love+Ya+Like+A+Sister' }
10
+ let(:fragment_url) { "#{base_url}/#{fragment}" }
11
+ let(:params_url) { "#{base_url}#{params}" }
12
+ let(:params_url_with_callback) { "#{base_url}#{params}#{callback}" }
13
+ let(:params_fragment_url) { "#{base_url}#{params}#{fragment}" }
15
14
 
15
+ describe 'format_url' do
16
16
  context 'with ignore_params set to false' do
17
17
  it 'is a no-op if there are no params' do
18
18
  expect(cache.format_url(base_url)).to eq base_url
@@ -115,4 +115,44 @@ describe Billy::Cache do
115
115
  end
116
116
  end
117
117
  end
118
+
119
+ describe 'key' do
120
+ context 'with use_ignore_params set to false' do
121
+ before do
122
+ allow(Billy.config).to receive(:use_ignore_params) { false }
123
+ end
124
+
125
+ it "should use the same cache key if the base url IS NOT whitelisted in allow_params" do
126
+ key1 = cache.key('put', params_url, 'body')
127
+ key2 = cache.key('put', params_url, 'body')
128
+ expect(key1).to eq key2
129
+ end
130
+
131
+ it "should have the same cache key if the base IS whitelisted in allow_params" do
132
+ allow(Billy.config).to receive(:allow_params) { [base_url] }
133
+ key1 = cache.key('put', params_url, 'body')
134
+ key2 = cache.key('put', params_url, 'body')
135
+ expect(key1).to eq key2
136
+ end
137
+
138
+ it "should have different cache keys if the base url is added in between two requests" do
139
+ key1 = cache.key('put', params_url, 'body')
140
+ allow(Billy.config).to receive(:allow_params) { [base_url] }
141
+ key2 = cache.key('put', params_url, 'body')
142
+ expect(key1).not_to eq key2
143
+ end
144
+
145
+ it "should not use ignore_params when whitelisted" do
146
+ allow(Billy.config).to receive(:allow_params) { [base_url] }
147
+ expect(cache).to receive(:format_url).once.with(params_url, true).and_call_original
148
+ expect(cache).to receive(:format_url).once.with(params_url, false).and_call_original
149
+ key1 = cache.key('put', params_url, 'body')
150
+ end
151
+
152
+ it "should use ignore_params when not whitelisted" do
153
+ expect(cache).to receive(:format_url).twice.with(params_url, true).and_call_original
154
+ cache.key('put', params_url, 'body')
155
+ end
156
+ end
157
+ end
118
158
  end
@@ -0,0 +1,84 @@
1
+ require 'spec_helper'
2
+
3
+ describe Billy::Authority do
4
+ let(:auth1) { Billy::Authority.new }
5
+ let(:auth2) { Billy::Authority.new }
6
+
7
+ context('#key') do
8
+ it 'generates a new key each time' do
9
+ expect(auth1.key).not_to be(auth2.key)
10
+ end
11
+
12
+ it 'generates 2048 bit keys' do
13
+ expect(auth1.key.n.num_bytes * 8).to be(2048)
14
+ end
15
+ end
16
+
17
+ context('#cert') do
18
+ it 'generates a new certificate each time' do
19
+ expect(auth1.cert).not_to be(auth2.cert)
20
+ end
21
+
22
+ it 'generates unique serials' do
23
+ expect(auth1.cert.serial).not_to be(auth2.cert.serial)
24
+ end
25
+
26
+ it 'configures a start date some days ago' do
27
+ expect(auth1.cert.not_before).to \
28
+ be_between((Date.today - 3).to_time, Date.today.to_time)
29
+ end
30
+
31
+ it 'configures an end date in some days' do
32
+ expect(auth1.cert.not_after).to \
33
+ be_between(Date.today.to_time, (Date.today + 3).to_time)
34
+ end
35
+
36
+ it 'configures the subject' do
37
+ expect(auth1.cert.subject.to_s).to \
38
+ be_eql('/CN=Puffing Billy/O=Puffing Billy')
39
+ end
40
+
41
+ it 'configures the certificate authority constrain' do
42
+ expect(auth1.cert.extensions.first.to_s).to \
43
+ be_eql('basicConstraints = critical, CA:TRUE')
44
+ end
45
+
46
+ it 'configures SSLv3' do
47
+ # Zero-index version numbers. Yay.
48
+ expect(auth1.cert.version).to be(2)
49
+ end
50
+ end
51
+
52
+ context('#key_file') do
53
+ it 'pass back the path' do
54
+ expect(auth1.key_file).to match(/ca.key$/)
55
+ end
56
+
57
+ it 'creates a temporary file' do
58
+ expect(File.exist?(auth1.key_file)).to be(true)
59
+ end
60
+
61
+ it 'creates a PEM formatted certificate' do
62
+ expect(File.read(auth1.key_file)).to match(/^[A-Za-z0-9\-\+\/\=]+$/)
63
+ end
64
+
65
+ it 'writes out a private key' do
66
+ key = OpenSSL::PKey::RSA.new(File.read(auth1.key_file))
67
+ expect(key.private?).to be(true)
68
+ end
69
+ end
70
+
71
+ context('#cert_file') do
72
+ it 'pass back the path' do
73
+ expect(auth1.cert_file).to match(/ca.crt$/)
74
+ end
75
+
76
+ it 'creates a temporary file' do
77
+ expect(File.exist?(auth1.cert_file)).to be(true)
78
+ end
79
+
80
+ it 'creates a PEM formatted certificate' do
81
+ expect(File.read(auth1.cert_file)).to match(/^[A-Za-z0-9\-\+\/\=]+$/)
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,39 @@
1
+ require 'spec_helper'
2
+
3
+ describe Billy::CertificateChain do
4
+ let(:cert1) { Billy::Certificate.new('localhost') }
5
+ let(:cert2) { Billy::Certificate.new('localhost.localdomain') }
6
+ let(:chain) do
7
+ Billy::CertificateChain.new('localhost', cert1.cert, cert2.cert)
8
+ end
9
+
10
+ context('#initialize') do
11
+ it 'holds all certificates in order' do
12
+ expect(chain.certificates).to be_eql([cert1.cert, cert2.cert])
13
+ end
14
+
15
+ it 'holds the domain' do
16
+ expect(chain.domain).to be_eql('localhost')
17
+ end
18
+ end
19
+
20
+ context('#file') do
21
+ it 'pass back the path' do
22
+ expect(chain.file).to match(/chain-localhost.pem/)
23
+ end
24
+
25
+ it 'writes out all certificates' do
26
+ chain.certificates.each do |cert|
27
+ expect(File.read(chain.file)).to include(cert.to_pem)
28
+ end
29
+ end
30
+
31
+ it 'creates a temporary file' do
32
+ expect(File.exist?(chain.file)).to be(true)
33
+ end
34
+
35
+ it 'creates a PEM formatted certificate chain' do
36
+ expect(File.read(chain.file)).to match(/^[A-Za-z0-9\-\+\/\=]+$/)
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,89 @@
1
+ require 'spec_helper'
2
+
3
+ describe Billy::Certificate do
4
+ let(:cert1) { Billy::Certificate.new('localhost') }
5
+ let(:cert2) { Billy::Certificate.new('localhost.localdomain') }
6
+
7
+ context('#domain') do
8
+ it 'holds the domain' do
9
+ expect(Billy::Certificate.new('test.tld').domain).to be_eql('test.tld')
10
+ end
11
+ end
12
+
13
+ context('#key') do
14
+ it 'generates a new key each time' do
15
+ expect(cert1.key).not_to be(cert2.key)
16
+ end
17
+
18
+ it 'generates 2048 bit keys' do
19
+ expect(cert1.key.n.num_bytes * 8).to be(2048)
20
+ end
21
+ end
22
+
23
+ context('#cert') do
24
+ it 'generates a new certificate each time' do
25
+ expect(cert1.cert).not_to be(cert2.cert)
26
+ end
27
+
28
+ it 'generates unique serials' do
29
+ expect(cert1.cert.serial).not_to be(cert2.cert.serial)
30
+ end
31
+
32
+ it 'configures a start date some days ago' do
33
+ expect(cert1.cert.not_before).to \
34
+ be_between((Date.today - 3).to_time, Date.today.to_time)
35
+ end
36
+
37
+ it 'configures an end date in some days' do
38
+ expect(cert1.cert.not_after).to \
39
+ be_between(Date.today.to_time, (Date.today + 3).to_time)
40
+ end
41
+
42
+ it 'configures the correct subject' do
43
+ expect(cert1.cert.subject.to_s).to be_eql('/CN=localhost')
44
+ end
45
+
46
+ it 'configures the subject alternative names' do
47
+ expect(cert1.cert.extensions.first.to_s).to \
48
+ be_eql('subjectAltName = DNS:localhost')
49
+ end
50
+
51
+ it 'configures SSLv3' do
52
+ # Zero-index version numbers. Yay.
53
+ expect(cert1.cert.version).to be(2)
54
+ end
55
+ end
56
+
57
+ context('#key_file') do
58
+ it 'pass back the path' do
59
+ expect(cert1.key_file).to match(/request-localhost.key$/)
60
+ end
61
+
62
+ it 'creates a temporary file' do
63
+ expect(File.exist?(cert1.key_file)).to be(true)
64
+ end
65
+
66
+ it 'creates a PEM formatted certificate' do
67
+ expect(File.read(cert1.key_file)).to match(/^[A-Za-z0-9\-\+\/\=]+$/)
68
+ end
69
+
70
+ it 'writes out a private key' do
71
+ key = OpenSSL::PKey::RSA.new(File.read(cert1.key_file))
72
+ expect(key.private?).to be(true)
73
+ end
74
+ end
75
+
76
+ context('#cert_file') do
77
+ it 'pass back the path' do
78
+ expect(cert1.cert_file).to match(/request-localhost.crt$/)
79
+ end
80
+
81
+ it 'creates a temporary file' do
82
+ expect(File.exist?(cert1.cert_file)).to be(true)
83
+ end
84
+
85
+ it 'creates a PEM formatted certificate' do
86
+ expect(File.read(cert1.cert_file)).to match(/^[A-Za-z0-9\-\+\/\=]+$/)
87
+ end
88
+ end
89
+ end
@@ -297,9 +297,13 @@ describe Billy::Proxy do
297
297
  proxy: { uri: proxy.url },
298
298
  request: { timeout: 1.0 }
299
299
  }
300
+ faraday_ssl_options = faraday_options.merge(ssl: {
301
+ verify: true,
302
+ ca_file: Billy.certificate_authority.cert_file
303
+ })
300
304
 
301
305
  @http = Faraday.new @http_url, faraday_options
302
- @https = Faraday.new @https_url, faraday_options.merge(ssl: { verify: false })
306
+ @https = Faraday.new @https_url, faraday_ssl_options
303
307
  @http_error = Faraday.new @error_url, faraday_options
304
308
  end
305
309
 
@@ -5,6 +5,7 @@ require 'billy/capybara/rspec'
5
5
  require 'billy/watir/rspec'
6
6
  require 'rack'
7
7
  require 'logger'
8
+ require 'fileutils'
8
9
 
9
10
  browser = Billy::Browsers::Watir.new :phantomjs
10
11
  Capybara.app = Rack::Directory.new(File.expand_path('../../examples', __FILE__))
@@ -20,6 +21,11 @@ RSpec.configure do |config|
20
21
  config.filter_run :focus
21
22
  config.order = 'random'
22
23
 
24
+ config.before :suite do
25
+ FileUtils.rm_rf(Billy.config.certs_path)
26
+ FileUtils.rm_rf(Billy.config.cache_path)
27
+ end
28
+
23
29
  config.before :all do
24
30
  start_test_servers
25
31
  @browser = browser
@@ -57,14 +57,20 @@ module Billy
57
57
  end
58
58
  end
59
59
 
60
+ def certificate_chain(domain)
61
+ ca = Billy.certificate_authority.cert
62
+ cert = Billy::Certificate.new(domain)
63
+ chain = Billy::CertificateChain.new(domain, cert.cert, ca)
64
+ { private_key_file: cert.key_file,
65
+ cert_chain_file: chain.file }
66
+
67
+ end
68
+
60
69
  def start_server(echo, ssl = false)
61
70
  http_server = Thin::Server.new '127.0.0.1', 0, echo
62
71
  if ssl
63
72
  http_server.ssl = true
64
- http_server.ssl_options = {
65
- private_key_file: File.expand_path('../../fixtures/test-server.key', __FILE__),
66
- cert_chain_file: File.expand_path('../../fixtures/test-server.crt', __FILE__)
67
- }
73
+ http_server.ssl_options = certificate_chain('localhost')
68
74
  end
69
75
  http_server.start
70
76
  http_server