http 0.7.2 → 0.7.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: 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: