From 260d8d2fe599fd0486013c6f96b5eaaaf1afe59b Mon Sep 17 00:00:00 2001 From: Matt Pasquini Date: Mon, 6 Jul 2020 18:09:44 -0700 Subject: [PATCH] Add min/max_version for supporting adapters --- lib/httpi/adapter/curb.rb | 3 +++ lib/httpi/adapter/excon.rb | 2 ++ lib/httpi/adapter/http.rb | 2 ++ lib/httpi/adapter/httpclient.rb | 3 +++ lib/httpi/adapter/net_http.rb | 2 ++ lib/httpi/auth/ssl.rb | 33 +++++++++++++++++++++++++++ spec/httpi/adapter/curb_spec.rb | 10 ++++++++ spec/httpi/adapter/httpclient_spec.rb | 11 +++++++++ spec/httpi/auth/ssl_spec.rb | 31 +++++++++++++++++++++++++ spec/integration/excon_spec.rb | 10 ++++++++ spec/integration/http_spec.rb | 10 ++++++++ spec/integration/net_http_spec.rb | 10 ++++++++ 12 files changed, 127 insertions(+) diff --git a/lib/httpi/adapter/curb.rb b/lib/httpi/adapter/curb.rb index 99fc3b08..d83d704c 100644 --- a/lib/httpi/adapter/curb.rb +++ b/lib/httpi/adapter/curb.rb @@ -128,6 +128,9 @@ def setup_ssl_auth when :SSLv23 then 2 when :SSLv3 then 3 end + if ssl.min_version || ssl.max_version + raise NotImplementedError, "Method Not Implemented" + end end def respond_with(client) diff --git a/lib/httpi/adapter/excon.rb b/lib/httpi/adapter/excon.rb index a03cd8f3..15705264 100644 --- a/lib/httpi/adapter/excon.rb +++ b/lib/httpi/adapter/excon.rb @@ -73,6 +73,8 @@ def client_opts end opts[:ssl_version] = ssl.ssl_version if ssl.ssl_version + opts[:ssl_min_version] = ssl.min_version if ssl.min_version + opts[:ssl_max_version] = ssl.max_version if ssl.max_version opts end diff --git a/lib/httpi/adapter/http.rb b/lib/httpi/adapter/http.rb index 8b702c02..61389c30 100644 --- a/lib/httpi/adapter/http.rb +++ b/lib/httpi/adapter/http.rb @@ -58,6 +58,8 @@ def create_client context.cert = @request.auth.ssl.cert context.key = @request.auth.ssl.cert_key context.ssl_version = @request.auth.ssl.ssl_version if @request.auth.ssl.ssl_version != nil + context.min_version = @request.auth.ssl.min_version if @request.auth.ssl.min_version != nil + context.max_version = @request.auth.ssl.max_version if @request.auth.ssl.max_version != nil context.verify_mode = @request.auth.ssl.openssl_verify_mode client = ::HTTP::Client.new(:ssl_context => context) diff --git a/lib/httpi/adapter/httpclient.rb b/lib/httpi/adapter/httpclient.rb index 91658057..d2558ec4 100644 --- a/lib/httpi/adapter/httpclient.rb +++ b/lib/httpi/adapter/httpclient.rb @@ -78,6 +78,9 @@ def setup_ssl_auth end @client.ssl_config.ssl_version = ssl.ssl_version.to_s if ssl.ssl_version + if ssl.min_version || ssl.max_version + raise NotImplementedError, "Method Not Implemented" + end end def respond_with(response) diff --git a/lib/httpi/adapter/net_http.rb b/lib/httpi/adapter/net_http.rb index 8376cd14..9972d0c7 100644 --- a/lib/httpi/adapter/net_http.rb +++ b/lib/httpi/adapter/net_http.rb @@ -182,6 +182,8 @@ def setup_ssl_auth end @client.ssl_version = ssl.ssl_version if ssl.ssl_version + @client.min_version = ssl.min_version if ssl.min_version + @client.max_version = ssl.max_version if ssl.max_version end def ssl_cert_store(ssl) diff --git a/lib/httpi/auth/ssl.rb b/lib/httpi/auth/ssl.rb index f998a5a8..efa73556 100644 --- a/lib/httpi/auth/ssl.rb +++ b/lib/httpi/auth/ssl.rb @@ -20,6 +20,9 @@ class SSL ssl_context::METHODS.reject { |method| method.match(/server|client/) } end.sort.reverse + # Returns OpenSSL::SSL::*_VERSION values for min_version and max_version + MIN_MAX_VERSIONS = OpenSSL::SSL.constants.select{|constant| constant =~/_VERSION$/}.map{|version| version[0..-9].to_sym}.reverse + # Returns whether SSL configuration is present. def present? (verify_mode == :none) || (cert && cert_key) || ca_cert_file @@ -90,6 +93,36 @@ def ssl_version=(version) @ssl_version = version end + # Returns the SSL min_version number. Defaults to nil (auto-negotiate). + def min_version + @min_version ||= nil + end + + # Sets the SSL min_version number. Expects one of HTTPI::Auth::SSL::SSL_VERSIONS. + def min_version=(version) + unless MIN_MAX_VERSIONS.include? version + raise ArgumentError, "Invalid SSL min_version #{version.inspect}\n" + + "Please specify one of #{MIN_MAX_VERSIONS.inspect}" + end + + @min_version = version + end + + # Returns the SSL min_version number. Defaults to nil (auto-negotiate). + def max_version + @max_version ||= nil + end + + # Sets the SSL min_version number. Expects one of HTTPI::Auth::SSL::SSL_VERSIONS. + def max_version=(version) + unless MIN_MAX_VERSIONS.include? version + raise ArgumentError, "Invalid SSL max_version #{version.inspect}\n" + + "Please specify one of #{MIN_MAX_VERSIONS.inspect}" + end + + @max_version = version + end + # Returns an OpenSSL::X509::Certificate for the +cert_file+. def cert @cert ||= (OpenSSL::X509::Certificate.new File.read(cert_file) if cert_file) diff --git a/spec/httpi/adapter/curb_spec.rb b/spec/httpi/adapter/curb_spec.rb index ec931b75..8ce74ee6 100644 --- a/spec/httpi/adapter/curb_spec.rb +++ b/spec/httpi/adapter/curb_spec.rb @@ -278,6 +278,16 @@ adapter.request(:get) end end + it 'raises error when min_version not nil' do + request.auth.ssl.min_version = :TLS1_2 + expect{ adapter.request(:get) }. + to raise_error(HTTPI::NotImplementedError, 'Method Not Implemented') + end + it 'raises error when max_version not nil' do + request.auth.ssl.max_version = :TLS1_2 + expect{ adapter.request(:get) }. + to raise_error(HTTPI::NotImplementedError, 'Method Not Implemented') + end end context "(for SSL client auth)" do diff --git a/spec/httpi/adapter/httpclient_spec.rb b/spec/httpi/adapter/httpclient_spec.rb index e6371a3e..83dbef0f 100644 --- a/spec/httpi/adapter/httpclient_spec.rb +++ b/spec/httpi/adapter/httpclient_spec.rb @@ -178,6 +178,17 @@ adapter.request(:get) end + + it 'raises error when min_version not nil' do + request.auth.ssl.min_version = :TLS1_2 + expect{ adapter.request(:get) }. + to raise_error(HTTPI::NotImplementedError, 'Method Not Implemented') + end + it 'raises error when max_version not nil' do + request.auth.ssl.max_version = :TLS1_2 + expect{ adapter.request(:get) }. + to raise_error(HTTPI::NotImplementedError, 'Method Not Implemented') + end end context "(for SSL client auth with a verify mode of :none with no certs provided)" do diff --git a/spec/httpi/auth/ssl_spec.rb b/spec/httpi/auth/ssl_spec.rb index fe5e722c..10618241 100644 --- a/spec/httpi/auth/ssl_spec.rb +++ b/spec/httpi/auth/ssl_spec.rb @@ -4,6 +4,7 @@ describe HTTPI::Auth::SSL do before(:all) do @ssl_versions = HTTPI::Auth::SSL::SSL_VERSIONS + @min_max_versions = HTTPI::Auth::SSL::MIN_MAX_VERSIONS end describe "VERIFY_MODES" do @@ -158,6 +159,36 @@ end end + describe "#min_version" do + subject { HTTPI::Auth::SSL.new } + + it "returns the min_version" do + subject.min_version = @min_max_versions.first + expect(subject.min_version).to eq(@min_max_versions.first) + end + + it 'raises ArgumentError if the version is unsupported' do + expect { ssl.min_version = :ssl_fail }. + to raise_error(ArgumentError, "Invalid SSL min_version :ssl_fail\n" + + "Please specify one of #{@min_max_versions}") + end + end + + describe "#max_version" do + subject { HTTPI::Auth::SSL.new } + + it "returns the SSL version" do + subject.max_version = @min_max_versions.first + expect(subject.max_version).to eq(@min_max_versions.first) + end + + it 'raises ArgumentError if the version is unsupported' do + expect { ssl.max_version = :ssl_fail }. + to raise_error(ArgumentError, "Invalid SSL max_version :ssl_fail\n" + + "Please specify one of #{@min_max_versions}") + end + end + def ssl ssl = HTTPI::Auth::SSL.new ssl.cert_key_file = "spec/fixtures/client_key.pem" diff --git a/spec/integration/excon_spec.rb b/spec/integration/excon_spec.rb index 5d3abc2f..c574f1d4 100644 --- a/spec/integration/excon_spec.rb +++ b/spec/integration/excon_spec.rb @@ -129,6 +129,16 @@ expect(response.body).to eq("get") end + it "works with min_version/max_version" do + request = HTTPI::Request.new(@server.url) + request.auth.ssl.ca_cert_file = IntegrationServer.ssl_ca_file + request.auth.ssl.min_version = "TLS1_2" + request.auth.ssl.max_version = "TLS1_2" + + response = HTTPI.get(request, adapter) + expect(response.body).to eq("get") + end + it "works with client cert and key provided as file path" do request = HTTPI::Request.new(@server.url) request.auth.ssl.ca_cert_file = IntegrationServer.ssl_ca_file diff --git a/spec/integration/http_spec.rb b/spec/integration/http_spec.rb index 7d1157bc..b055df0d 100644 --- a/spec/integration/http_spec.rb +++ b/spec/integration/http_spec.rb @@ -131,6 +131,16 @@ response = HTTPI.get(request, adapter) expect(response.body).to eq("get") end + + it "works with min_version/max_version" do + request = HTTPI::Request.new(@server.url) + request.auth.ssl.ca_cert_file = IntegrationServer.ssl_ca_file + request.auth.ssl.min_version = "TLS1_2" + request.auth.ssl.max_version = "TLS1_2" + + response = HTTPI.get(request, adapter) + expect(response.body).to eq("get") + end end end diff --git a/spec/integration/net_http_spec.rb b/spec/integration/net_http_spec.rb index 9b34fe85..dd6f8734 100644 --- a/spec/integration/net_http_spec.rb +++ b/spec/integration/net_http_spec.rb @@ -217,6 +217,16 @@ response = HTTPI.get(request, adapter) expect(response.body).to eq("get") end + + it "works with min_version/max_version" do + request = HTTPI::Request.new(@server.url) + request.auth.ssl.ca_cert_file = IntegrationServer.ssl_ca_file + request.auth.ssl.min_version = "TLS1_2" + request.auth.ssl.max_version = "TLS1_2" + + response = HTTPI.get(request, adapter) + expect(response.body).to eq("get") + end end end