错误信息
state=error: certificate verify failed (unable to get local issuer certificate)
也可能输出为
- OpenSSL::SSL::SSLError: SSL_connect returned=1 errno=0 peeraddr=127.0.0.1:8443 state=error: certificate verify failed (unable to get local issuer certificate)
完整消息是“SSL_connect returned=1 errno=0 peeraddr=<ip>:<port> state=error: certificate verify failed (unable to get local issuer certificate)”;在我们的测试中,openssl gem 3.0.1 到 4.0.2 的消息中都包含 peeraddr。Faraday 把它包装为消息相同的 Faraday::SSLError;直接调用 Net::HTTP 的库则会原样抛出 OpenSSL::SSL::SSLError。
Ruby openssl (Net::HTTP, Faraday, HTTParty, RubyGems) — OpenSSL::SSL::SSLError from SSL_connect
含义
Ruby 的 OpenSSL 绑定(Net::HTTP 使用它,因此大多数 Ruby HTTP 客户端也使用它)在已加载的 CA 中找不到服务器证书链的颁发者。括号中的文本是 OpenSSL 的验证原因;它前面的所有内容描述的是失败发生在 SSL_connect 的哪个环节。通常是 CA 为私有 CA 或位于代理之后,或者这个 Ruby 查找的 CA 证书包中没有它。
原因
私有 CA 或执行 TLS 检查的代理
常见证书链终止于一张不在 Ruby 所用 OpenSSL 证书存储中的根证书。
基于自带 OpenSSL 构建的 Ruby(macOS 上的 rbenv、asdf、rvm)
常见这类 Ruby 使用 Homebrew OpenSSL 或自带 OpenSSL 的 CA 证书包,而不是 macOS 钥匙串;IT 部门添加到钥匙串中的根证书对它们不起作用。
服务器没有发送中间证书
偶见OpenSSL 不会获取缺失的中间证书,因此只发送叶子证书的服务器在这里会验证失败,尽管浏览器接受它。
Windows 上的 RubyInstaller
偶见Windows 上的 Ruby 自带 CA 证书包;Windows 证书存储中的公司根证书不会自动包含在内。
修复方法
- 1.
把根证书安装到 Ruby 的 OpenSSL 查找的位置
OpenSSL::X509::DEFAULT_CERT_FILE 和 DEFAULT_CERT_DIR 给出了对应的文件和目录路径;在 Debian 和 Ubuntu 上,update-ca-certificates 会更新其中的证书。
bash # Ruby's OpenSSL reads the system bundle (see its default paths) ruby -ropenssl -e 'p OpenSSL::X509::DEFAULT_CERT_FILE, OpenSSL::X509::DEFAULT_CERT_DIR' sudo cp corp-root-ca.pem /usr/local/share/ca-certificates/corp-root-ca.crt && sudo update-ca-certificates - 2.
为进程设置 SSL_CERT_FILE
OpenSSL 会遵循 SSL_CERT_FILE 和 SSL_CERT_DIR;把它们指向同时包含公共根证书和你的 CA 的证书包。
bash # One process / environment: point OpenSSL at a bundle with the needed roots export SSL_CERT_FILE=/etc/ssl/private-ca/corp-ca-bundle.pem ruby app.rb - 3.
为单个客户端提供独立的证书存储
先调用 set_default_paths 再调用 add_file 的 OpenSSL::X509::Store,既保留公共根证书,又加入私有 CA,并保持完整验证。
ruby # One client: default roots plus a private CA store = OpenSSL::X509::Store.new store.set_default_paths store.add_file("/etc/ssl/private-ca/corp-root-ca.pem") http = Net::HTTP.new("api.example.com", 443) http.use_ssl = true http.cert_store = store - 4.
Faraday 等封装库:传入 ca_file 或 cert_store
大多数 Ruby HTTP 库都接受 ssl 选项,这些选项对应 Net::HTTP 的 ca_file、ca_path 或 cert_store。
ruby conn = Faraday.new("https://api.example.com", ssl: { ca_file: "/etc/ssl/private-ca/corp-root-ca.pem" })
相关错误
state=error: certificate verify failed (self-signed certificate in certificate…Ruby openssl (Net::HTTP, Faraday, RubyGems)Could not verify the SSL certificate for https://gems.example.com/.Bundlercurl: (60) SSL certificate problem: unable to get local issuer certificatecurl (OpenSSL backend)[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local…Python ssl module (OpenSSL)