puffing-billy 0.10.1 → 0.11.0

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.
@@ -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