ActionMailer SMTP“证书validation失败”

我想从我的Rails Web应用程序发送电子邮件,我不想禁用TLS证书validation。 但是由于某种原因,即使服务器证书有效,它也始终会因“SSLv3读取服务器证书B:证书validation失败”而失败。

我用openssl s_client (使用/etc/ssl/certs/ca-certificates.crt )检查了一下,并在rails控制台中运行以下命令也可以正常运行。

 smtp = Net::SMTP.new(host, port) smtp.enable_tls smtp.start("localhost", username, password, :login) do |smtp| smtp.send_message msgstr, from, to end 

服务器有Rails 4.2.6和Ruby 2.3.0

 config.action_mailer.smtp_setting = { address: port: 465, user_name: password: authentication: :login, openssl_verify_mode: OpenSSL::SSL::VERIFY_PEER, enable_starttls_auto: false, ssl: true } 

根据所描述的行为,我非常确定在控制台中尚未完成对等validation,并且您需要显式设置证书存储以validationRails配置中的对等证书

为什么它在控制台中“工作”以及如何在那里实际validation对等体:

从控制台起作用但不是来自Rails代码的观察是由于控制台代码中的 smtp.enable_tls 不会强制进行对等validation,而Rails配置显然是这样。 实际上,当您将命令写入控制台时,您会打印出SSLContext

 smtp.enable_tls # => # 

请注意, @verify_modenil因此SSLContext上默认不启用对等validation。

要在控制台中强制进行对等validation,以便您可以手动使用SSL设置,您需要使用自定义SSLContext并将其传递给enable_tls

 ssl_context = Net::SMTP.default_ssl_context ssl_context.set_params smtp.enable_tls(ssl_context) # => #, @extra_chain_cert=nil, @client_cert_cb=nil, @session_id_context=nil, @tmp_dh_callback=nil, @session_get_cb=nil, @session_new_cb=nil, @session_remove_cb=nil, @tmp_ecdh_callback=nil, @servername_cb=nil, @npn_protocols=nil, @alpn_protocols=nil, @alpn_select_cb=nil, @npn_select_cb=nil> 

仔细观察差异: SSLContext现在将verify_mode设置为1,并且具有用于定义的validation的证书存储。 这是(除其他外) SSLContext中的set_params方法所做的事情。

如何在Rails中配置证书存储区

现在,在为SMTP连接构造SSLContext时,Rails不会调用set_params方法。 相反,它根据选项设置其上的各个属性(请参阅此处和此处的源代码)。 您已正确配置要validation对等证书的Rails,但尚未配置证书存储以validation对等

这可以使用ca_fileca_path选项来完成,因此以下Rails配置应该适合您:

 config.action_mailer.smtp_setting = { ... ssl: true enable_starttls_auto: false, openssl_verify_mode: OpenSSL::SSL::VERIFY_PEER, ca_file: "/etc/ssl/certs/ca-certificates.crt", ... } 

我不知道为什么在Rails指南中没有正确记录这个…

这个Rails配置适合我(使用Ruby 2.2.2和Rails 5):

 ActionMailer::Base.smtp_setting = { ... enable_starttls_auto: true, openssl_verify_mode: OpenSSL::SSL::VERIFY_PEER, openssl_verify_depth: 3, # if your CA is a sub signer ca_file: "/etc/ssl/certs/ca-certificates.crt", ... }