title: Self signed TLS cert not accepted only in ruby
body: <p>I'm securing an internal web server using stunnel and easyRSA.</p>
  <p>I've tried to use the default x509 types in easyRSA but my probes failed because the server cert does not have the <code>clientAuth</code>, I've added a new type of cert which adds the server and client auth</p>
  <pre><code>extendedKeyUsage = serverAuth,clientAuth
  </code></pre>
  <p>Stunnel is configured with <code>verify=2</code> which should also check client certificates</p>
  <p>This is my signing certs structure:</p>
  <pre><code>Root.CA
    | Signer.CA
    |  | server.crt
    |  | clientA.crt
    |  \ clientB.crt
    | WebClient.CA
    |  | server.crt
    |  | user.alice.crt
    |  \ user.bob.crt
    | RPC.CA
    |  | server.crt
    |  | rpcClient1.crt
    |  | rpcClient2.crt
    \  \ rpcClient3.crt
  </code></pre>
  <p>The idea is to use client certificates on webpages and have a central CA that will be trusted everywhere it is needed. This works fine with nginx client certs</p>
  <p>But the rpc client in ruby can't verify the server cert. I'm using this code to debug the connection</p>
  <pre class="lang-rb prettyprint-override"><code>require 'openssl'
  require 'net/http'
  
  host = 127.0.0.1 # Not really but for example
  port = 443
  
  ruby = File.join(RbConfig::CONFIG['bindir'], RbConfig::CONFIG['ruby_install_name'])
  ruby_version = RUBY_VERSION
  if patch = RbConfig::CONFIG['PATCHLEVEL']
    ruby_version += &quot;-p#{patch}&quot;
  end
  puts &quot;%s (%s)&quot; % [ruby, ruby_version]
  
  openssl_dir = OpenSSL::X509::DEFAULT_CERT_AREA
  mac_openssl = '/System/Library/OpenSSL' == openssl_dir
  puts &quot;%s: %s&quot; % [OpenSSL::OPENSSL_VERSION, openssl_dir]
  [OpenSSL::X509::DEFAULT_CERT_DIR_ENV, OpenSSL::X509::DEFAULT_CERT_FILE_ENV].each do |key|
    puts &quot;%s=%s&quot; % [key, ENV[key].to_s.inspect]
  end
  
  ca_file = ENV[OpenSSL::X509::DEFAULT_CERT_FILE_ENV] || OpenSSL::X509::DEFAULT_CERT_FILE
  ca_path = (ENV[OpenSSL::X509::DEFAULT_CERT_DIR_ENV] || OpenSSL::X509::DEFAULT_CERT_DIR).chomp('/')
  
  puts &quot;\nHEAD https://#{host}:#{port}&quot;
  http = Net::HTTP.new(host, port)
  http.use_ssl = true
  
  # Explicitly setting cert_store like this is not needed in most cases but it
  # seems necessary in edge cases such as when using `verify_callback` in some
  # combination of Ruby + OpenSSL versions.
  http.cert_store = OpenSSL::X509::Store.new
  http.cert_store.set_default_paths
  
  http.verify_mode = OpenSSL::SSL::VERIFY_PEER
  http.verify_depth = 5
  failed_cert = failed_cert_reason = nil
  
  # http.ca_file = File.read('ca.crt')
  http.cert = OpenSSL::X509::Certificate.new(File.read('tls.crt'))
  http.key = OpenSSL::PKey::RSA.new(File.read('tls.key'))
  
  if mac_openssl
    warn &quot;warning: will not be able show failed certificate info on OS X's OpenSSL&quot;
    # This drives me absolutely nuts. It seems that on Rubies compiled against OS X's
    # system OpenSSL, the mere fact of defining a `verify_callback` makes the
    # cert verification fail for requests that would otherwise be successful.
  else
    http.verify_callback = lambda { |verify_ok, store_context|
      if !verify_ok
        failed_cert = store_context.current_cert
        failed_cert_reason = &quot;%d: %s&quot; % [ store_context.error, store_context.error_string ]
      end
      verify_ok
    }
  end
  
  user_agent = &quot;net/http #{ruby_version}&quot;
  req = Net::HTTP::Head.new(&quot;/?#{rand()}&quot;, 'user-agent' =&gt; user_agent)
  
  begin
    res = http.start { http.request(req) }
    abort res.inspect if res.code.to_i &gt;= 500
    puts &quot;OK&quot;
  rescue Errno::ECONNREFUSED
    puts &quot;Error: connection refused&quot;
    exit 1
  rescue OpenSSL::SSL::SSLError =&gt; e
    puts &quot;#{e.class}: #{e.message}&quot;
  
    if failed_cert
      puts &quot;\nThe server presented a certificate that could not be verified:&quot;
      puts &quot;  subject: #{failed_cert.subject}&quot;
      puts &quot;  issuer: #{failed_cert.issuer}&quot;
      puts &quot;  error code %s&quot; % failed_cert_reason
    end
  
    ca_file_missing = !File.exist?(ca_file) &amp;&amp; !mac_openssl
    ca_path_empty = Dir[&quot;#{ca_path}/*&quot;].empty?
  
    if ca_file_missing || ca_path_empty
      puts &quot;\nPossible causes:&quot;
      puts &quot;  `%s' does not exist&quot; % ca_file if ca_file_missing
      puts &quot;  `%s/' is empty&quot; % ca_path if ca_path_empty
    end
  
    exit 1
  end
  </code></pre>
  <p>This is the output:</p>
  <pre class="lang-sh prettyprint-override"><code>$ ruby client.rb
  /Users/user/.rvm/rubies/ruby-2.7.0/bin/ruby (2.7.0-p0)
  OpenSSL 1.1.1h  22 Sep 2020: /usr/local/etc/openssl@1.1
  SSL_CERT_DIR=&quot;&quot;
  SSL_CERT_FILE=&quot;&quot;
  
  HEAD https://127.0.0.1:443
  OpenSSL::SSL::SSLError: SSL_connect returned=1 errno=0 state=error: certificate verify failed (self signed certificate in certificate chain)
  
  The server presented a certificate that could not be verified:
    subject: /C=US/ST=State/L=Location/O=My Certificates/OU=Security/CN=myCN/emailAddress=signer@cn.app
    issuer: /C=US/ST=State/L=Location/O=My Certificates/OU=Security/CN=myCN/emailAddress=signer@cn.app
    error code 19: self signed certificate in certificate chain
  
  Possible causes:
    `/usr/local/etc/openssl@1.1/certs/' is empty
  </code></pre>
  <p>If I create a cert bundle with <code>cat root.crt rpc.crt &gt; bundle.crt</code> and set the <code>SSL_CET_FILE</code> to it I get:</p>
  <pre class="lang-sh prettyprint-override"><code>$ SSL_CERT_FILE=bundle.crt ruby client.rb
  
  /Users/user/.rvm/rubies/ruby-2.7.0/bin/ruby (2.7.0-p0)
  OpenSSL 1.1.1h  22 Sep 2020: /usr/local/etc/openssl@1.1
  SSL_CERT_DIR=&quot;&quot;
  SSL_CERT_FILE=&quot;/Users/user/easyrsa/certs/rpc/bundle.crt&quot;
  
  HEAD https://127.0.0.1:443
  OpenSSL::SSL::SSLError: SSL_connect returned=1 errno=0 state=error: certificate verify failed (certificate rejected)
  
  The server presented a certificate that could not be verified:
  
  
  
    subject: /C=US/ST=State/L=Location/O=My Certificates/OU=Security/CN=server@rpc/emailAddress=signer@cn.app
    issuer: /C=US/ST=State/L=Location/O=My Certificates/OU=Security/CN=rpc@ca/emailAddress=signer@cn.app
    error code 0: ok
  
  Possible causes:
    `/usr/local/etc/openssl@1.1/certs/' is empty
  </code></pre>
  <p>Which tells me it can connect, but it is not being accepted anyway</p>
  <p>I've tried to verify the connection with <code>OpenSSL</code> and I can connect and get a response:</p>
  <pre class="lang-sh prettyprint-override"><code>openssl s_client -host 127.0.0.1 -port 443 -state -debug \
    -cert tls.crt \
    -key key.crt \
  -CAfile &lt;(kubectl get secret tls-explorer -n explorer --output=&quot;jsonpath={ .data.ca\.crt }&quot; | base64 -d)
  
  # If I then type &quot;GET / HTTP/1.1&quot; it returns me the contents
  </code></pre>
  <p>Also I'm using Paw as a postman replacement, and with it I can connect just fine with any of the rpc client cert</p>
  <p>So what could be the problem?</p>