使用Python smtplib仅凭证书文件发送邮件,无需密钥文件
我正在尝试用一个证书文件发送邮件,使用的脚本如下:
import smtplib
client = smtplib.SMTP(myhost, myport)
client.ehlo()
client.starttls(certfile=mycertfile)
client.ehlo()
client.login(myusername, mypassword)
client.sendmail(sender, receiver, Message)
client.quit()
但是我遇到了以下错误:
SSLError: error:140B0009:SSL routines:SSL_CTX_use_PrivateKey_file:PEM lib
我查了一下文档(smtplib.html 和 ssl.html),上面说我需要提供一个私钥。但我只有证书文件(是base64的PEM格式)。我的运维人员说在这种情况下不需要私钥,因为我不需要确认连接的本地身份。
问题
有没有办法在不提供私钥的情况下发送邮件?如果需要私钥,那是为什么呢?
2 个回答
这里有一段来自这个页面的猴子补丁:
class SMTPExt(smtplib.SMTP):
"""
This class extends smtplib.SMTP and overrides the starttls method
allowing extra parameters and forwarding them to ssl.wrap_socket.
"""
def starttls(self, keyfile=None, certfile=None, **kwargs):
self.ehlo_or_helo_if_needed()
if not self.has_extn("starttls"):
raise SMTPException("STARTTLS extension not supported by server.")
(resp, reply) = self.docmd("STARTTLS")
if resp == 220:
self.sock = ssl.wrap_socket(self.sock, keyfile, certfile, **kwargs)
self.file = SSLFakeFile(self.sock)
# RFC 3207:
# The client MUST discard any knowledge obtained from
# the server, such as the list of SMTP service extensions,
# which was not obtained from the TLS negotiation itself.
self.helo_resp = None
self.ehlo_resp = None
self.esmtp_features = {}
self.does_esmtp = 0
return (resp, reply)
使用来自requests的根证书
使用SSL/TLS有两种方式:一种是客户端认证,另一种是“基本”连接,客户端不需要认证。在客户端认证的连接中,服务器和客户端都会互相发送证书。而在“基本”连接中,只有服务器会发送证书。
如果你没有提供证书或密钥文件,smtplib
会使用基本连接,这种情况下客户端不需要认证。
如果你使用了证书,那么这就会变成客户端认证的连接。在这种情况下,服务器会要求客户端证明它拥有这个证书,方法是用私钥签署一个握手消息。为了做到这一点,客户端还需要私钥,这个私钥可以在证书文件里,也可以是一个单独的密钥文件。
简单来说,如果你想使用客户端证书,就必须同时使用一个密钥。如果不想用证书和密钥,那就可以都留空。
另一方面,也许你有一个服务器证书文件或者CA列表想要在连接中使用?
在这种情况下,你需要把它传递给ssl.wrap_socket
的ca_certs
参数。由于你使用的是Python 2.6,所以没有简单的方法可以通过smtplib
来做到这一点(Python 3.3及以上版本在starttls
中有一个context
参数)。
如何解决这个问题取决于你的应用。例如,如果你不需要ssl
做其他事情,一个比较“黑科技”的解决方案是用一个提供你的ca_cert
的函数来替换ssl.wrap_socket
(可能还需要cert_reqs=CERT_REQUIRED
)。
一个更全面的解决方案是扩展smtplib.SMTP
,创建你自己的版本,以便允许传入这些参数。