问题描述

定位问题

用Python验证服务正常?

使用openssl测试

回到golang代码,我们查看与openssl的区别

通过代码分析

解决问题

总结

通过分析golang代码,处理无法通过腾讯云SMTP发送邮件的问题

小葵

2024-12-03

🏷

go

本文发表于入职啦(公众号: ruzhila) 大家可以访问入职啦学习更多的编程实战。

发现问题,用工具定位和分析问题,再通过代码找到解决方案,是每个工程师必备的技能,分享一个实际的案例,通过分析golang代码,处理无法通过腾讯云SMTP发送邮件的问题。

问题描述

最近在使用golang发送邮件的时候,发现无法通过腾讯云SMTP发送邮件,代码如下: 发送邮件代码

表现就是始终会得到一个错误:

--- FAIL: TestSMTP (0.19s)
    /Users/mpi/workspace/ruzhila/ruzhila-app/notify_test.go:93: 
            Error Trace:	/Users/mpi/workspace/ruzhila/ruzhila-app/notify_test.go:93
            Error:      	Expected nil, but got: &tls.permanentError{err:(*net.OpError)(0x14000240e60)}
            Test:       	TestSMTP

这个错误看起来是一个tls错误,在这个之前,科普一下SMTP端口的问题:

  • 25 端口是SMTP协议的默认端口,用于发送邮件, 但是不安全,是明文传输
  • 465 端口是SMTP协议的加密端口,使用SSL加密,但是现在已经不推荐使用了
  • 587 端口是SMTP协议的加密端口,使用TLS加密,是目前推荐使用的端口

根据腾讯云官网的配置,腾讯云的SMTP服务器是支持465的。并且不支持25端口。

实际情况呢?

腾讯云是支持46525端口的,并且25是支持STARTTLS的,也就是可以使用TLS加密,但是这个错误看起来是一个tls错误,看起来是tls握手失败了。

定位问题

我们既然确定腾讯云支持25465那么就简单很多了,我们分别写代码测试看看, 看看是哪个环节出问题了

我们先定几个出现问题可能性:

  1. 服务器就是不能工作, 就是不支持TLS
  2. 服务器的配置有问题
  3. 客户端库有问题
  4. 客户端的配置有问题

我们第一步先测试25端口,不加密的情况下能否正常工作,然后再测试465端口,加密的情况下能否正常工作。

并且我们要最简单的测试方式,我们先用python测试一下,这样能同时验证问题1和3

用Python验证服务正常?

  1. 第一步测试25端口能否正常工作, 是否支持TLS python测试25端口

我们发现通过25端口,不加密的情况下是可以发送邮件的,那么就是说明腾讯云的25端口是能正常工作的,那么问题就出在tls加密上了。

  1. 测试465端口能否正常工作
client = smtplib.SMTP_SSL("smtp.qcloudmail.com", 465)
client.login("admin@ruzhila.cn", "YYYYY")
client.sendmail("admin@ruzhila.cn", ["kui@ruzhila.cn"], "Subject: test\n\nHello, world!")

我们得到了一个异常:

self._sslobj.do_handshake()
ssl.SSLError: [SSL: SSLV3_ALERT_HANDSHAKE_FAILURE] sslv3 alert handshake failure (_ssl.c:1000)

看起来是go和python的tls都不能正常工作,那么我们就是下一步,定位服务器是不是真的有问题?

现在的结论就是服务能工作,就是TLS不正常,不管是go还是python都不能正常工作,那么问题就出在TLS上了。

使用openssl测试

第一步我们先测试25的STTLS是否正常工作, 我们通过自带的openssl测试一下:

openssl s_client -connect smtp.qcloudmail.com:25 -starttls smtp

openssl测试25端口

我们发现是支持的,同时也测试了465端口:

openssl s_client -connect smtp.qcloudmail.com:465 

openssl测试465端口

也就是说服务器的ssl是能正常工作的,那就是问题一下子范围就缩小到了go的tls库上了。

  1. 服务器就是不能工作, 就是不支持TLS ✅ 服务器是能工作的,并且有TLS
  2. 服务器的配置有问题 ✅ 服务器的配置是正常的,至少openssl是能正常工作的
  3. 客户端库有问题 ❌ python和go都有问题,但是这些库都是标准库,不应该怀疑标准库的实现
  4. 客户端的配置有问题 ❌ 客户端没有做任何的配置,那就是默认的配置出现问题

回到golang代码,我们查看与openssl的区别

我们回到golang代码,我们发现golang的代码是这样的,我们精简一下测试的代码,直接访问465端口: golang测试465端口

这个问题依旧,也是就是默认的配置出现问题了,那么我们就要看看golang的tls配置和openssl的配置有什么不同了。

我们通过Wireshark抓包,看看golang和openssl的握手有什么不同。

有经验的工程师也可以用tcpdump, 但是还是推荐wireshark

我们对比一下,两个握手的区别: wireshark对比

发现最大的差别就是go的加密算法明显少了很多,我们再通过检查openssl握手之后采用的算法:

New, TLSv1/SSLv3, Cipher is AES256-SHA
Server public key is 2048 bit
Secure Renegotiation IS supported
Compression: NONE
Expansion: NONE
No ALPN negotiated
SSL-Session:
    Protocol  : TLSv1.2
    Cipher    : AES256-SHA
    Session-ID: 717C9AE5924DF91B3BD5A075FCE13A612DC07F4519E6832A680A609EF856DAA2
    Session-ID-ctx: 

openssl最终采用到了AES256-SHA,那么我们就要看看golang的tls配置是不是支持AES256-SHA, 根据wireshark的截图,我们并没发现AES256-SHA

所以问题就能直接定位就是go和python默认是没有采用AES256-SHA的,导致了握手失败。

通过代码分析

如果我们手工加上AES256-SHA,是不是也能解决问题?

手工加密算法

果然,我们将CipherSuites加上TLS_RSA_WITH_AES_128_CBC_SHA之后,问题就解决了。

那就是最终定位到是代码配置的问题,那么我们怎么解决这个问题?

解决问题

在 Go 中,TLS_RSA 密钥交换(KEX)算法的使用逐渐被弃用,主要是因为它的安全性较低。TLS_RSA 密钥交换算法在 TLS 1.3 中被完全移除,并且在 TLS 1.2 中也不再推荐使用。

TLS_RSA 密钥交换算法的主要问题在于它不提供前向安全性(Forward Secrecy)。前向安全性意味着即使服务器的长期密钥被泄露,过去的会话密钥也不会被泄露。相比之下,基于 Diffie-Hellman 的密钥交换算法(如 ECDHE)提供了前向安全性,因此被广泛推荐和使用。

也就是说,golang默认不支持TLS_RSA交换算法,我们如果手工指定也是可以的,但是这样是不推荐的,我们应该使用更加安全的算法。

我们去查看crypto/tls的代码,发现CipherSuites是一个[]uint16,如果我们不指定,那么默认是这样的: 在crypto/tls/handshake_client.go:98 默认的CipherSuites

也就是说握手的时候,会通过:

cipherSuites() -> defaultCipherSuites() -> cipherSuitesPreferenceOrder

最终选择默认的交互算法,也就是如果不指定tlsrsakex.Value(),默认是不会启用tls_rsa算法。

终于,我们定位到默认配置的问题,那么我们应该怎么解决这个问题呢?

现在有两种解决方案:

  • 手工指定tlsConfig到TLS_RSA_WITH_AES_256_CBC_SHA 手工指定CipherSuites

那么这样就可以确保我们的代码是能正常工作的

  • 通过修改godebug的环境变量,强制启用TLS_RSA算法
export GODEBUG=tlsrsakex=1
  • 代码里面设置这个变量
    os.Setenv("GODEBUG", "tlsrsakex=1")

这样我们就比较少改动的方式,让代码正常工作。

总结

除了写代码,定位问题是工程师必备的技能,通过分析golang代码,处理无法通过腾讯云SMTP发送邮件的问题,所有的问题,都需要很多工具和经验。

但是通过减少问题的范围,再做diff,再去看代码实现,这个过程是非常有意思的,大家对问题要知道原因并且用最可靠的方案修改

最后,腾讯云的安全配置也缺少文档和兼容性,毕竟这个问题,他们自己写个代码就能发现,但是过了这么多年,他们也没发现和更新问题,真的是耽误了大家很多的时间,希望腾讯云能够改进。

我们构建了一个编程实战群,大家可以扫码加入,一起学习编程

入群学习

也可以访问入职啦学习更多的编程实战

入职啦

心仪的工作马上入职啦

友情链接:

Copyright© 2024 杭州园中葵科技有限公司 版权所有