smtplib
和 ssl.SSLContext
对象将邮件发送到 Gmail SMTP 服务器,但在 TLS 握手期间加载证书 PEM 文件时出现错误:
import ssl, smtplib, email
def send_email(subject, body, sender, recipients, password):
mml = email.message.EmailMessage()
mml.set_content(body)
mml['From'] = sender
mml['To'] = ', '.join(recipients)
mml['Subject'] = subject
sslctx = ssl.SSLContext(protocol=ssl.PROTOCOL_TLS_CLIENT)
sslctx.verify_mode = ssl.CERT_REQUIRED
assert sslctx.verify_mode == ssl.CERT_REQUIRED
sslctx.load_verify_locations(cafile='crt.pem', capath='.')
hdlr = smtplib.SMTP(host='smtp.gmail.com', port=587) # do not use SMTP_SSL
hdlr.starttls(context=sslctx)
hdlr.login('[email protected]', password)
hdlr.sendmail(sender, recipients, mml.as_string())
print("[INFO] Message sent!")
> gmail_demo.send_email(subject='someone testing this site', body='there <b>you</b> go', sender='[email protected]', recipients=['[email protected]'], password='abcdef123456')
>
> Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ssl.SSLError: [SSL] PEM lib (_ssl.c:3917)
从该错误消息中,我发现它是由cpython中的C扩展模块报告的,看来方法
SSLContext.load_cert_chain()
是在这里实现的。
我的问题:
SSLContext.load_cert_chain(...)
是否要求调用者提供有效的keyfile
参数?在文档中,参数keyfile
是可选的,但是在C实现中,此错误可能来自于SSL_CTX_use_PrivateKey_file(...)
的返回值。crt.pem
?该文件包含我从 gmail SMTP 服务器下载的证书,按照此 google 工作场所管理员帮助中的说明进行操作。关于环境:CPython 3.12.0
谢谢
仔细阅读这些文档和这个 stackoverflow 答案后,我发现了我犯的错误:
ssl.SSLContext
中,方法load_cert_chain(...)
是服务器端peer向CA签署证书,然后将服务器证书发送给客户端peer,客户端对它进行验证,在这种情况下需要私钥;但就我而言,不可能拥有 gmail 证书的私钥,我应该使用另一种方法load_verify_locations(...)
,它只加载证书链。smtp.gmail.com
、google trusted services 1c3
及其 CA google trusted services root R1
。如 python 文档 中所述,PEM 文件应如下所示:-----BEGIN CERTIFICATE-----
... (cert for the gmail smtp server)...
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
... (cert for google trusted services 1c3, which signs the gmail server cert)...
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
... (cert for google trusted services root R1, which signs google trusted services 1c3 cert)...
-----END CERTIFICATE-----