From 1dd1e64a18222091e7f5a1028289b850389163ca Mon Sep 17 00:00:00 2001 From: Olle Jonsson Date: Thu, 1 Aug 2024 08:40:59 +0200 Subject: [PATCH] Support setting SSL client cert as a an array, to configure extra_chain_cert (#42) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix test: tap into SSLOptions setting client_cert Tested locally using 3.1.4 and got: warning: Passing only keyword arguments to Struct#initialize will behave differently from Ruby 3.2. Please use a Hash literal like .new({k: v}) instead of .new(k: v). Checked the keyword vs literal on 3.3.4, Yes it behaves differently. irb(main):008> ssl_options_without_literal_hash = Faraday::SSLOptions.new(client_cert: "this is my string") => # ssl_options_with_literal_hash = Faraday::SSLOptions.new({client_cert: "this is my string"}) => ... irb(main):010> ssl_options_with_literal_hash => verify={:client_cert=>"this is my string"}, verify_hostname=nil, ca_file=nil, ca_path=nil, verify_mode=nil, cert_store=nil, client_cert=nil, client_key=nil, certificate=nil, private_key=nil, verify_depth=nil, version=nil, min_version=nil, max_version=nil> Looked closer at other tests. Thanks for all the good examples. Found a way that should be more compatible: (Well, it worked on my machine with 3.1.4 and 3.3.4 :-D ) let(:ssl_options) do Faraday::SSLOptions.new.tap do |ssl_options| ssl_options.client_cert = cert end end --------- Co-authored-by: Lars Kronfält --- lib/faraday/adapter/net_http.rb | 5 ++- spec/faraday/adapter/net_http_spec.rb | 44 +++++++++++++++++++++++++++ 2 files changed, 48 insertions(+), 1 deletion(-) diff --git a/lib/faraday/adapter/net_http.rb b/lib/faraday/adapter/net_http.rb index ad75c25..f0bbe81 100644 --- a/lib/faraday/adapter/net_http.rb +++ b/lib/faraday/adapter/net_http.rb @@ -133,7 +133,10 @@ def configure_ssl(http, ssl) http.verify_mode = ssl_verify_mode(ssl) http.cert_store = ssl_cert_store(ssl) - http.cert = ssl[:client_cert] if ssl[:client_cert] + cert, *extra_chain_cert = ssl[:client_cert] + http.cert = cert if cert + http.extra_chain_cert = extra_chain_cert if extra_chain_cert.any? + http.key = ssl[:client_key] if ssl[:client_key] http.ca_file = ssl[:ca_file] if ssl[:ca_file] http.ca_path = ssl[:ca_path] if ssl[:ca_path] diff --git a/spec/faraday/adapter/net_http_spec.rb b/spec/faraday/adapter/net_http_spec.rb index ef3f3fe..16696af 100644 --- a/spec/faraday/adapter/net_http_spec.rb +++ b/spec/faraday/adapter/net_http_spec.rb @@ -147,4 +147,48 @@ it { expect(response.body.encoding).to eq(::Encoding::UTF_8) } end end + + context 'client certificate' do + let(:adapter) { described_class.new } + let(:url) { URI('https://example.com') } + let(:http) { adapter.send(:connection, url: url, request: {}, ssl: ssl_options) } + + before do + stub_request(:any, 'https://example.com') + end + + context 'when client_cert is provided as an array' do + let(:cert_array) { [OpenSSL::X509::Certificate.new, OpenSSL::X509::Certificate.new] } + let(:ssl_options) do + Faraday::SSLOptions.new.tap do |ssl_options| + ssl_options.client_cert = cert_array + end + end + + it 'sets the first cert as cert and the rest as extra_chain_cert' do + adapter.send(:configure_ssl, http, ssl_options) + end + + it { expect(http.cert).to eq(cert_array.first) } + + it { expect(http.extra_chain_cert).to eq(cert_array[1..]) } + end + + context 'when client_cert is provided as a single cert' do + let(:cert) { OpenSSL::X509::Certificate.new } + let(:ssl_options) do + Faraday::SSLOptions.new.tap do |ssl_options| + ssl_options.client_cert = cert + end + end + + it 'sets the cert as cert' do + adapter.send(:configure_ssl, http, ssl_options) + end + + it { expect(http.cert).to eq(cert) } + + it { expect(http.extra_chain_cert).to be_nil } + end + end end