#34504 closed Bug (needsinfo)
SSLCertVerificationError on outgoing emails for some mailboxes
Reported by: | Kamen Kalchev | Owned by: | nobody |
---|---|---|---|
Component: | Core (Mail) | Version: | 4.2 |
Severity: | Normal | Keywords: | smtplib, ssl, Django4.2 |
Cc: | Adam Johnson | Triage Stage: | Unreviewed |
Has patch: | no | Needs documentation: | no |
Needs tests: | no | Patch needs improvement: | no |
Easy pickings: | no | UI/UX: | no |
Description (last modified by )
It looks like this was previously reported in a different scenario and marked as fixed but is still not working as expected. Downgrading Django to 4.1.7 fixes the problem with outgoing messages to the mailbox.
Environment:
Python3.10.6 Django4.2 EMAIL_USE_SSL=True EMAIL_USE_TLS=False EMAIL_SSL_CERTFILE=Does not make a difference if we provide a file location or not EMAIL_SSL_KEYFILE=Does not make a difference if we provide a file location or not
Link to previously reported issue:
https://code.djangoproject.com/ticket/34386
Stacktrace:
File "/usr/local/lib/env3/lib/python3.10/site-packages/django/core/mail/message.py" line 298 in send [args] [locals] return self.get_connection(fail_silently).send_messages([self]) File "/usr/local/lib/env3/lib/python3.10/site-packages/django/core/mail/backends/smtp.py" line 127 in send_messages [args] [locals] new_conn_created = self.open() File "/usr/local/lib/env3/lib/python3.10/site-packages/django/core/mail/backends/smtp.py" line 85 in open [args] [locals] self.connection = self.connection_class( File "/usr/lib/python3.10/smtplib.py" line 1050 in __init__ [args] [locals] SMTP.__init__(self, host, port, local_hostname, timeout, File "/usr/lib/python3.10/smtplib.py" line 255 in __init__ [args] [locals] (code, msg) = self.connect(host, port) File "/usr/lib/python3.10/smtplib.py" line 341 in connect [args] [locals] self.sock = self._get_socket(host, port, self.timeout) File "/usr/lib/python3.10/smtplib.py" line 1057 in _get_socket [args] [locals] new_socket = self.context.wrap_socket(new_socket, File "/usr/lib/python3.10/ssl.py" line 513 in wrap_socket [args] [locals] return self.sslsocket_class._create( File "/usr/lib/python3.10/ssl.py" line 1071 in _create [args] [locals] self.do_handshake() File "/usr/lib/python3.10/ssl.py" line 1342 in do_handshake [args] [locals] self._sslobj.do_handshake() SSLCertVerificationError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate (_ssl.c:997)
Change History (9)
comment:1 by , 19 months ago
Description: | modified (diff) |
---|
follow-up: 3 comment:2 by , 19 months ago
Resolution: | → needsinfo |
---|---|
Status: | new → closed |
comment:3 by , 19 months ago
Hello and thank you for the prompt answer, Mariusz.
We have played around with your suggestion and what worked in our case was adding check_hostname = False and verify_mode = ssl.CERT_NONE in the else clause (since we are not passing in a specific cert/ key file) in an overridden ssl_context method for a child class of EmailBackend.
To be honest, we are not sure if this should be changed for everyone but it looks like something was omitted in creating the default ssl context, as in Django version 4.1.7 we did not have to manually set those params and it worked. Anyway, we really appreciate your response and wish you a good week ahead.
@cached_property def ssl_context(self): if self.ssl_certfile or self.ssl_keyfile: ssl_context = ssl.SSLContext(protocol=ssl.PROTOCOL_TLS_CLIENT) ssl_context.load_cert_chain(self.ssl_certfile, self.ssl_keyfile) return ssl_context else: ssl_context = ssl.create_default_context() ssl_context.check_hostname = False ssl_context.verify_mode = ssl.CERT_NONE return ssl_context
Replying to Mariusz Felisiak:
Thanks for the ticket, however I don't see much difference between the current implementation and Python < 3.12 behavior in creating a default SSL context. The only difference is that now
check_hostname
is set toTrue
, does it work for you with the following diff?
django/core/mail/backends/smtp.py
diff --git a/django/core/mail/backends/smtp.py b/django/core/mail/backends/smtp.py index 1ee48269ae..132bed29be 100644
a b class EmailBackend(BaseEmailBackend): 60 60 if self.ssl_certfile or self.ssl_keyfile: 61 61 ssl_context = ssl.SSLContext(protocol=ssl.PROTOCOL_TLS_CLIENT) 62 62 ssl_context.load_cert_chain(self.ssl_certfile, self.ssl_keyfile) 63 ssl_context.check_hostname = False 63 64 return ssl_context 64 65 else: 65 66 return ssl.create_default_context() I'm not sure we'd like to change that.
comment:4 by , 19 months ago
Cc: | added |
---|
Yes, sorry check_hostname = False
should be in the "default context" branch.
It's from ssl._create_unverified_context()
that is "less restrict than create_default_context()
to increase backward compatibility.". It worked because the host wasn't checked. As far as I'm aware, this shows a problem with verification of host certificate chain.
follow-up: 9 comment:6 by , 19 months ago
Kamen, what do you think about adding a backward incompatibility note? see PR.
comment:9 by , 19 months ago
Looks good, Mariusz. Thank you once again for looking into this ticket.
Replying to Mariusz Felisiak:
Kamen, what do you think about adding a backward incompatibility note? see PR.
Thanks for the ticket, however I don't see much difference between the current implementation and Python < 3.12 behavior in creating a default SSL context. The only difference is that now
check_hostname
is set toTrue
, does it work for you with the following diff?django/core/mail/backends/smtp.py
I'm not sure we'd like to change that.