在尝试开发一种使用IMAP访问Gmail的工具时,即使使用以下简单的启动代码,我也遇到了麻烦:
require 'net/imap'
imap = Net::IMAP.new('imap.gmail.com', ssl: true)
运行该命令,它会失败,如下所示(注意:为了便于显示,对其进行了轻松编辑):
Traceback (most recent call last):
5: from test-imap:2:in `<main>'
4: from test-imap:2:in `new'
3: from /.../.rvm/rubies/ruby-2.6.0/lib/ruby/2.6.0/net/imap.rb:1092:in `initialize'
2: from /.../.rvm/rubies/ruby-2.6.0/lib/ruby/2.6.0/net/imap.rb:1533:in `start_tls_session'
1: from /.../.rvm/rubies/ruby-2.6.0/lib/ruby/2.6.0/net/protocol.rb:44:in `ssl_socket_connect'
/.../.rvm/rubies/ruby-2.6.0/lib/ruby/2.6.0/net/protocol.rb:44:in
`connect_nonblock': SSL_connect returned=1 errno=0 state=error:
certificate verify failed (self signed certificate) (OpenSSL::SSL::SSLError)
四处搜寻,我发现了一些类似的问题,例如imap-backup issue #57,ruby/openssl issue #238(在下面分享答案后,仍然保持打开状态,在撰写本文时关闭了),还有rbenv/ruby-build issue #380 ...在SO上什么都没有。>
从以上资源中整理信息,我想出了以下命令来尝试:
openssl s_client -connect imap.gmail.com:993 \
-CAfile $(ruby -ropenssl -e 'puts OpenSSL::X509::DEFAULT_CERT_FILE') \
< /dev/null > /dev/null
哪个报告:
depth=2 /OU=GlobalSign Root CA - R2/O=GlobalSign/CN=GlobalSign
verify return:1
depth=1 /C=US/O=Google Trust Services/CN=Google Internet Authority G3
verify return:1
depth=0 /C=US/ST=California/L=Mountain View/O=Google LLC/CN=imap.gmail.com
verify return:1
DONE
因此,似乎SSL证书确实以这种方式验证了OK(如人们所期望的那样)。
我确实发现some instructions可以与net/imap
一起使用,并且在禁用主机检查的情况下将SSL与SSL结合使用,这确实有效……但我真的不愿意这样做。
我还找到了non-IMAP gmail interface,但是我的目的是可以与其他IMAP提供程序一起使用的工具,因此我在这里的具体目标是坚持使用IMAP。所以:
如何让net/imap
使用SSL成功连接,并且仍然验证证书(实际上是有效的)?
答案 0 :(得分:3)
升级到Ruby 2.6.3或更高版本应该可以解决问题,因为ruby pull #2077已合并到related issue #15594中,2.6.3 change list为commit that fixed imap-backup issue #57提供了修复程序。对于2.6.3,现在可以使用问题中的原始测试代码片段。
也就是说,如果出于某种原因无法进行升级,则{{3}}提供了一种解决方法。代替以下内容:
imap = Net::IMAP.new('imap.gmail.com', ssl: true)
尝试一下:
imap = Net::IMAP.new('imap.gmail.com', ssl: {ssl_version: :TLSv1_2})
即使在Ruby 2.6.0中,这似乎也可以使工作正常。
答案 1 :(得分:0)
在Ruby 2.4中,我不得不使用此补丁:
require 'net/imap'
class Net::IMAP
module UseSNI
def start_tls_session(*)
@sock.instance_variable_set(:@hostname, @host)
super
end
end
prepend UseSNI
end
class OpenSSL::SSL::SSLSocket
module UseSNI
def connect(*)
self.hostname = io.instance_variable_get(:@hostname)
super
end
end
prepend UseSNI
end