http 0.7.2 → 0.7.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: f805ef1ff977da3de5e63d6f83d4f8bca9acef53
4
- data.tar.gz: c2cfbb035716d369fd9374029b277c414ac32280
3
+ metadata.gz: cb95fe4445c50c6c1af546a7bb70ece6eccb1f78
4
+ data.tar.gz: e5b2eb9695d742302e54f46f222d81c8921f13c0
5
5
  SHA512:
6
- metadata.gz: d2fb7cf922b2154d82b1446046e79bfffae11bff2ac9315d140859c85bc52f66bbc91b70e580fbd28d620b889c7fcb72053e27ba15d01ca3fb804add736b1ce0
7
- data.tar.gz: b999027d340c9dc7a48192253b59f08066e0b0f15ec0087648bbdfaefa2939578481201dc70fad0c2b8712741c70ed6625b6a13e3f37a7b71b13473df92a9ea7
6
+ metadata.gz: 267033f1a355e9023d4f62c1978a275f26f67313c0702036ceed2332d585e33c42e91f2bf2318c4631b157e8c1f9f2b1cd486d44c1d084e39c8792f06f735893
7
+ data.tar.gz: d20251b75cc74fec4643f31c27401f833b6eec2ba1e65d47c7edc9326525ccda4c27c8cad34e2d53f88707361446df6bd1eca6f37d6a9f0c6b2ae4ba0b1e6c80
@@ -3,7 +3,7 @@ Metrics/BlockNesting:
3
3
 
4
4
  Metrics/ClassLength:
5
5
  CountComments: false
6
- Max: 100
6
+ Max: 110
7
7
 
8
8
  Metrics/CyclomaticComplexity:
9
9
  Max: 8 # TODO: Lower to 6
data/CHANGES.md CHANGED
@@ -1,3 +1,10 @@
1
+ ## 0.7.3 (2015-03-24)
2
+
3
+ * SECURITY FIX: http.rb failed to call the #post_connection_check method
4
+ on SSL connections. This method implements hostname verification, and
5
+ without it http.rb was vulnerable to MitM attacks. The problem was
6
+ corrected by calling #post_connection_check (CVE-2015-1828)
7
+
1
8
  ## 0.7.2 (2015-03-02)
2
9
 
3
10
  * Swap from `form_data` to `http-form_data` (changed gem name).
data/Gemfile CHANGED
@@ -26,6 +26,7 @@ group :test do
26
26
  gem 'rubocop', '~> 0.25.0'
27
27
  gem 'simplecov', '>= 0.9'
28
28
  gem 'yardstick'
29
+ gem 'certificate_authority'
29
30
  end
30
31
 
31
32
  group :doc do
@@ -52,7 +52,7 @@ module HTTP
52
52
 
53
53
  # TODO: keep-alive support
54
54
  @socket = options[:socket_class].open(req.socket_host, req.socket_port)
55
- @socket = start_tls(@socket, options) if uri.is_a?(URI::HTTPS) && !req.using_proxy?
55
+ @socket = start_tls(@socket, uri.host, options) if uri.is_a?(URI::HTTPS) && !req.using_proxy?
56
56
 
57
57
  req.stream @socket
58
58
 
@@ -90,12 +90,17 @@ module HTTP
90
90
  private
91
91
 
92
92
  # Initialize TLS connection
93
- def start_tls(socket, options)
93
+ def start_tls(socket, host, options)
94
94
  # TODO: abstract away SSLContexts so we can use other TLS libraries
95
95
  context = options[:ssl_context] || OpenSSL::SSL::SSLContext.new
96
96
  socket = options[:ssl_socket_class].new(socket, context)
97
97
 
98
98
  socket.connect
99
+
100
+ if context.verify_mode == OpenSSL::SSL::VERIFY_PEER
101
+ socket.post_connection_check(host)
102
+ end
103
+
99
104
  socket
100
105
  end
101
106
 
@@ -1,3 +1,3 @@
1
1
  module HTTP
2
- VERSION = '0.7.2'
2
+ VERSION = '0.7.3'
3
3
  end
@@ -1,5 +1,9 @@
1
+ require 'support/dummy_server'
2
+
1
3
  RSpec.describe HTTP::Client do
2
4
  let(:test_endpoint) { "http://#{ExampleServer::ADDR}" }
5
+ run_server(:dummy) { DummyServer.new }
6
+ run_server(:dummy_ssl) { DummyServer.new(:ssl => true) }
3
7
 
4
8
  StubbedClient = Class.new(HTTP::Client) do
5
9
  def perform(request, options)
@@ -142,6 +146,38 @@ RSpec.describe HTTP::Client do
142
146
  end
143
147
  end
144
148
 
149
+ describe 'SSL' do
150
+ let(:client) do
151
+ described_class.new(
152
+ :ssl_context => OpenSSL::SSL::SSLContext.new.tap do |context|
153
+ context.options = OpenSSL::SSL::SSLContext::DEFAULT_PARAMS[:options]
154
+
155
+ context.verify_mode = OpenSSL::SSL::VERIFY_PEER
156
+ context.ca_file = File.join(certs_dir, 'ca.crt')
157
+ context.cert = OpenSSL::X509::Certificate.new(
158
+ File.read(File.join(certs_dir, 'client.crt'))
159
+ )
160
+ context.key = OpenSSL::PKey::RSA.new(
161
+ File.read(File.join(certs_dir, 'client.key'))
162
+ )
163
+ context
164
+ end
165
+ )
166
+ end
167
+
168
+ it 'works via SSL' do
169
+ response = client.get(dummy_ssl.endpoint)
170
+ expect(response.body.to_s).to eq('<!doctype html>')
171
+ end
172
+
173
+ context 'with a mismatch host' do
174
+ it 'errors' do
175
+ expect { client.get(dummy_ssl.endpoint.gsub('127.0.0.1', 'localhost')) }
176
+ .to raise_error(OpenSSL::SSL::SSLError, /does not match/)
177
+ end
178
+ end
179
+ end
180
+
145
181
  describe '#perform' do
146
182
  let(:client) { described_class.new }
147
183
 
@@ -19,6 +19,13 @@ require 'support/example_server'
19
19
  require 'support/proxy_server'
20
20
  require 'support/capture_warning'
21
21
 
22
+ # Allow testing against a SSL server
23
+ def certs_dir
24
+ Pathname.new File.expand_path('../../tmp/certs', __FILE__)
25
+ end
26
+
27
+ require 'support/create_certs'
28
+
22
29
  # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
23
30
  RSpec.configure do |config|
24
31
  config.expect_with :rspec do |expectations|
@@ -0,0 +1,5 @@
1
+ module BlackHole
2
+ def self.method_missing(*)
3
+ self
4
+ end
5
+ end
@@ -0,0 +1,57 @@
1
+ require 'fileutils'
2
+ require 'certificate_authority'
3
+
4
+ FileUtils.mkdir_p(certs_dir)
5
+
6
+ #
7
+ # Certificate Authority
8
+ #
9
+
10
+ ca = CertificateAuthority::Certificate.new
11
+
12
+ ca.subject.common_name = 'honestachmed.com'
13
+ ca.serial_number.number = 1
14
+ ca.key_material.generate_key
15
+ ca.signing_entity = true
16
+
17
+ ca.sign! 'extensions' => {'keyUsage' => {'usage' => %w(critical keyCertSign)}}
18
+
19
+ ca_cert_path = File.join(certs_dir, 'ca.crt')
20
+ ca_key_path = File.join(certs_dir, 'ca.key')
21
+
22
+ File.write ca_cert_path, ca.to_pem
23
+ File.write ca_key_path, ca.key_material.private_key.to_pem
24
+
25
+ #
26
+ # Server Certificate
27
+ #
28
+
29
+ server_cert = CertificateAuthority::Certificate.new
30
+ server_cert.subject.common_name = '127.0.0.1'
31
+ server_cert.serial_number.number = 1
32
+ server_cert.key_material.generate_key
33
+ server_cert.parent = ca
34
+ server_cert.sign!
35
+
36
+ server_cert_path = File.join(certs_dir, 'server.crt')
37
+ server_key_path = File.join(certs_dir, 'server.key')
38
+
39
+ File.write server_cert_path, server_cert.to_pem
40
+ File.write server_key_path, server_cert.key_material.private_key.to_pem
41
+
42
+ #
43
+ # Client Certificate
44
+ #
45
+
46
+ client_cert = CertificateAuthority::Certificate.new
47
+ client_cert.subject.common_name = '127.0.0.1'
48
+ client_cert.serial_number.number = 1
49
+ client_cert.key_material.generate_key
50
+ client_cert.parent = ca
51
+ client_cert.sign!
52
+
53
+ client_cert_path = File.join(certs_dir, 'client.crt')
54
+ client_key_path = File.join(certs_dir, 'client.key')
55
+
56
+ File.write client_cert_path, client_cert.to_pem
57
+ File.write client_key_path, client_cert.key_material.private_key.to_pem
@@ -0,0 +1,52 @@
1
+ require 'webrick'
2
+ require 'webrick/ssl'
3
+
4
+ require 'support/black_hole'
5
+ require 'support/dummy_server/servlet'
6
+ require 'support/servers/config'
7
+ require 'support/servers/runner'
8
+
9
+ class DummyServer < WEBrick::HTTPServer
10
+ include ServerConfig
11
+
12
+ CONFIG = {
13
+ :BindAddress => '127.0.0.1',
14
+ :Port => 0,
15
+ :AccessLog => BlackHole,
16
+ :Logger => BlackHole
17
+ }.freeze
18
+
19
+ def initialize(options = {})
20
+ if options[:ssl]
21
+ override_config = {
22
+ :SSLEnable => true,
23
+ :SSLStartImmediately => true
24
+ }
25
+ else
26
+ override_config = {}
27
+ end
28
+
29
+ super CONFIG.merge(override_config)
30
+
31
+ mount('/', Servlet)
32
+ end
33
+
34
+ def endpoint
35
+ "#{ssl? ? 'https' : 'http'}://#{addr}:#{port}"
36
+ end
37
+
38
+ def ssl_context
39
+ @ssl_context ||= begin
40
+ context = OpenSSL::SSL::SSLContext.new
41
+ context.verify_mode = OpenSSL::SSL::VERIFY_PEER
42
+ context.key = OpenSSL::PKey::RSA.new(
43
+ File.read(File.join(certs_dir, 'server.key'))
44
+ )
45
+ context.cert = OpenSSL::X509::Certificate.new(
46
+ File.read(File.join(certs_dir, 'server.crt'))
47
+ )
48
+ context.ca_file = File.join(certs_dir, 'ca.crt')
49
+ context
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,100 @@
1
+ class DummyServer < WEBrick::HTTPServer
2
+ class Servlet < WEBrick::HTTPServlet::AbstractServlet
3
+ def not_found(_req, res)
4
+ res.status = 404
5
+ res.body = 'Not Found'
6
+ end
7
+
8
+ def self.handlers
9
+ @handlers ||= {}
10
+ end
11
+
12
+ %w(get post head).each do |method|
13
+ class_eval <<-RUBY, __FILE__, __LINE__
14
+ def self.#{method}(path, &block)
15
+ handlers["#{method}:\#{path}"] = block
16
+ end
17
+
18
+ def do_#{method.upcase}(req, res)
19
+ handler = self.class.handlers["#{method}:\#{req.path}"]
20
+ return instance_exec(req, res, &handler) if handler
21
+ not_found
22
+ end
23
+ RUBY
24
+ end
25
+
26
+ get '/' do |req, res|
27
+ res.status = 200
28
+
29
+ case req['Accept']
30
+ when 'application/json'
31
+ res['Content-Type'] = 'application/json'
32
+ res.body = '{"json": true}'
33
+ else
34
+ res['Content-Type'] = 'text/html'
35
+ res.body = '<!doctype html>'
36
+ end
37
+ end
38
+
39
+ get '/params' do |req, res|
40
+ next not_found unless 'foo=bar' == req.query_string
41
+
42
+ res.status = 200
43
+ res.body = 'Params!'
44
+ end
45
+
46
+ get '/multiple-params' do |req, res|
47
+ params = CGI.parse req.query_string
48
+
49
+ next not_found unless {'foo' => ['bar'], 'baz' => ['quux']} == params
50
+
51
+ res.status = 200
52
+ res.body = 'More Params!'
53
+ end
54
+
55
+ get '/proxy' do |_req, res|
56
+ res.status = 200
57
+ res.body = 'Proxy!'
58
+ end
59
+
60
+ get '/not-found' do |_req, res|
61
+ res.status = 404
62
+ res.body = 'not found'
63
+ end
64
+
65
+ get '/redirect-301' do |_req, res|
66
+ res.status = 301
67
+ res['Location'] = "http://#{@server.config[:BindAddress]}:#{@server.config[:Port]}/"
68
+ end
69
+
70
+ get '/redirect-302' do |_req, res|
71
+ res.status = 302
72
+ res['Location'] = "http://#{@server.config[:BindAddress]}:#{@server.config[:Port]}/"
73
+ end
74
+
75
+ post '/form' do |req, res|
76
+ if 'testing-form' == req.query['example']
77
+ res.status = 200
78
+ res.body = 'passed :)'
79
+ else
80
+ res.status = 400
81
+ res.body = 'invalid! >:E'
82
+ end
83
+ end
84
+
85
+ post '/body' do |req, res|
86
+ if 'testing-body' == req.body
87
+ res.status = 200
88
+ res.body = 'passed :)'
89
+ else
90
+ res.status = 400
91
+ res.body = 'invalid! >:E'
92
+ end
93
+ end
94
+
95
+ head '/' do |_req, res|
96
+ res.status = 200
97
+ res['Content-Type'] = 'text/html'
98
+ end
99
+ end
100
+ end
@@ -0,0 +1,13 @@
1
+ module ServerConfig
2
+ def ssl?
3
+ !!config[:SSLEnable]
4
+ end
5
+
6
+ def addr
7
+ config[:BindAddress]
8
+ end
9
+
10
+ def port
11
+ config[:Port]
12
+ end
13
+ end
@@ -0,0 +1,17 @@
1
+ module ServerRunner
2
+ def run_server(name, &block)
3
+ let! name do
4
+ server = block.call
5
+
6
+ Thread.new { server.start }
7
+
8
+ server
9
+ end
10
+
11
+ after do
12
+ send(name).shutdown
13
+ end
14
+ end
15
+ end
16
+
17
+ RSpec.configure { |c| c.extend ServerRunner }
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: http
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.2
4
+ version: 0.7.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tony Arcieri
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2015-03-02 00:00:00.000000000 Z
13
+ date: 2015-03-25 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: http_parser.rb
@@ -117,10 +117,16 @@ files:
117
117
  - spec/lib/http/response_spec.rb
118
118
  - spec/lib/http_spec.rb
119
119
  - spec/spec_helper.rb
120
+ - spec/support/black_hole.rb
120
121
  - spec/support/capture_warning.rb
122
+ - spec/support/create_certs.rb
123
+ - spec/support/dummy_server.rb
124
+ - spec/support/dummy_server/servlet.rb
121
125
  - spec/support/example_server.rb
122
126
  - spec/support/example_server/servlet.rb
123
127
  - spec/support/proxy_server.rb
128
+ - spec/support/servers/config.rb
129
+ - spec/support/servers/runner.rb
124
130
  homepage: https://github.com/httprb/http.rb
125
131
  licenses:
126
132
  - MIT
@@ -141,9 +147,39 @@ required_rubygems_version: !ruby/object:Gem::Requirement
141
147
  version: '0'
142
148
  requirements: []
143
149
  rubyforge_project:
144
- rubygems_version: 2.2.2
150
+ rubygems_version: 2.4.6
145
151
  signing_key:
146
152
  specification_version: 4
147
153
  summary: HTTP should be easy
148
- test_files: []
154
+ test_files:
155
+ - spec/lib/http/client_spec.rb
156
+ - spec/lib/http/content_type_spec.rb
157
+ - spec/lib/http/headers/mixin_spec.rb
158
+ - spec/lib/http/headers_spec.rb
159
+ - spec/lib/http/options/body_spec.rb
160
+ - spec/lib/http/options/form_spec.rb
161
+ - spec/lib/http/options/headers_spec.rb
162
+ - spec/lib/http/options/json_spec.rb
163
+ - spec/lib/http/options/merge_spec.rb
164
+ - spec/lib/http/options/new_spec.rb
165
+ - spec/lib/http/options/proxy_spec.rb
166
+ - spec/lib/http/options_spec.rb
167
+ - spec/lib/http/redirector_spec.rb
168
+ - spec/lib/http/request/writer_spec.rb
169
+ - spec/lib/http/request_spec.rb
170
+ - spec/lib/http/response/body_spec.rb
171
+ - spec/lib/http/response/status_spec.rb
172
+ - spec/lib/http/response_spec.rb
173
+ - spec/lib/http_spec.rb
174
+ - spec/spec_helper.rb
175
+ - spec/support/black_hole.rb
176
+ - spec/support/capture_warning.rb
177
+ - spec/support/create_certs.rb
178
+ - spec/support/dummy_server.rb
179
+ - spec/support/dummy_server/servlet.rb
180
+ - spec/support/example_server.rb
181
+ - spec/support/example_server/servlet.rb
182
+ - spec/support/proxy_server.rb
183
+ - spec/support/servers/config.rb
184
+ - spec/support/servers/runner.rb
149
185
  has_rdoc: