Faraday always trusts the OpenSSL system root CAs, even when a :ca_file or a :ca_path are specified, eg to implement CA pinning, or to reduce the number of trusted certificates.
Faraday.new('https://www.google.com', ssl: { ca_file: '/not/used/by/google/ca.pem' }).get('/') # => #<Faraday::Response:0x007ffd580b19d8 ...
An error about server certificate certificate validation, because the website's certificate does not match the :ca_file
Within the net_http adapter, ssl_cert_store will create a certificate store that includes the OpenSSL system root CAs if :cert_store is not specified:
https://github.com/lostisland/faraday/blob/master/lib/faraday/adapter/net_http.rb#L105
def ssl_cert_store(ssl)
return ssl[:cert_store] if ssl[:cert_store]
# Use the default cert store by default, i.e. system ca certs
cert_store = OpenSSL::X509::Store.new
cert_store.set_default_paths
cert_store
end
I would think that Faraday should only set a default :cert_store if there is no :ca_file, no :ca_path, and no :cert_store specified.
Thanks for the nice report. I get the problem; however, I have the feeling that people right now are using ca_file to provide an extra custom certificate on top of system CA certs. Flipping the switch on this behavior would be backwards-incompatible.
How about that you can choose to disable the default cert store if you deliberately want to do CA pinning? E.g.
Faraday.new('...', ssl: { ca_file: 'ca.pem', cert_store: false })
Would that satisfy your needs?
We would need to investigate how current HTTP libs (including net/http) behave in this regard: are we able to turn off the default system certs by passing no cert store object? If you have time and will to test this, it would be great.
Hey @mislav thanks for the response.
One immediate remediation I am currently using is to set the :cert_store to an empty certificate store:
cert_store = OpenSSL::X509::Store.new
Faraday.new('...', ssl: { ca_file: 'ca.pem', cert_store: cert_store })
Or to just use a cert_store:
cert_store = OpenSSL::X509::Store.new
cert_store.add_file('ca.pem')
Faraday.new('...', ssl: { cert_store: cert_store })
However, the only adapters that currently support :cert_store are:
httpclientnet_httpnet_http_persistantThe httpclient adapter does not add a default certificate store, and the net_http_persistant adapter does the same thing as net_http (although it currently supports fewer SSL config options).
I'll take a little time to see how the other HTTP libs behave when just a :ca_file is specified, eg to see if they still trust the CA root. (The curl command, when built with openssl support, disables the system root when I specify a CA file, but I haven't tested the other Ruby HTTP libs yet aside from net/http).
Most helpful comment
Hey @mislav thanks for the response.
One immediate remediation I am currently using is to set the
:cert_storeto an empty certificate store:Or to just use a cert_store:
However, the only adapters that currently support
:cert_storeare:httpclientnet_httpnet_http_persistantThe
httpclientadapter does not add a default certificate store, and thenet_http_persistantadapter does the same thing asnet_http(although it currently supports fewer SSL config options).I'll take a little time to see how the other HTTP libs behave when just a
:ca_fileis specified, eg to see if they still trust the CA root. (Thecurlcommand, when built with openssl support, disables the system root when I specify a CA file, but I haven't tested the other Ruby HTTP libs yet aside from net/http).