Opened 4 months ago

Last modified 3 months ago

#35653 assigned New feature

Support EMAIL_SSL_CERTFILE for private certificate authority

Reported by: dkaylor Owned by: Igor Scheller
Component: Core (Mail) Version: 4.2
Severity: Normal Keywords:
Cc: Mike Edmunds, Mariusz Felisiak Triage Stage: Accepted
Has patch: yes Needs documentation: no
Needs tests: no Patch needs improvement: yes
Easy pickings: no UI/UX: no

Description

We have an SMTP server that is not signed by a public CA. Sending email with no SSL settings results in an "SSL: CERTIFICATE_VERIFY_FAILED" error.

If we set EMAIL_SSL_CERTFILE, we receive the same error. We do not have access to the key file to test with EMAIL_SSL_KEYFILE. Clients often do not have access to keys so this shouldn't be required.

Django is loading the cert files with load_cert_chain, but I believe load_verify_locations would be more appropriate:

https://github.com/django/django/blob/main/django/core/mail/backends/smtp.py#L63

The examples in the Python docs use the former for servers and the latter for clients:

https://docs.python.org/3/library/ssl.html

I wrote a simple test with load_cert_chain and it fails with the same SSL error:

ssl_context.load_cert_chain(cacert)

If I change to load_verify_locations it works

ssl_context.load_verify_locations(cacert)

Change History (13)

comment:1 by dkaylor, 4 months ago

Version: 5.04.2

comment:2 by Sarah Boyce, 4 months ago

Cc: Mike Edmunds added

comment:3 by Mike Edmunds, 4 months ago

This makes sense to me, but Python's SSL/TLS is a little outside my expertise. It would be good to get Mariusz's input.

Django 4.2 changed to use ssl.create_default_context() if neither certfile nor keyfile is set. This enables certificate validation and hostname checking, and is a Python ssl security best practice.

I wonder if we shouldn't also be using ssl.create_default_context(capath=...) when an EMAIL_SSL_CERTFILE is provided, for exactly the same reasons? Followed by load_cert_chain() when necessary. (This would require a release note similar to the one in 4.2.)

See also ticket:34550 and this StackOverflow answer.

comment:4 by Sarah Boyce, 4 months ago

Cc: Mariusz Felisiak added

comment:5 by Sarah Boyce, 3 months ago

Resolution: duplicate
Status: newclosed
Summary: SSL error sending mailSSL error sending mail when EMAIL_SSL_CERTFILE is set without EMAIL_SSL_KEYFILE

In the 4.2 release notes:

EmailBackend now verifies a hostname and certificates. If you need the previous behavior that is less restrictive and not recommended, subclass EmailBackend and override the ssl_context property.

Sounds like a duplicate of #34504

comment:6 by Mike Edmunds, 3 months ago

Resolution: duplicate
Status: closednew
Summary: SSL error sending mail when EMAIL_SSL_CERTFILE is set without EMAIL_SSL_KEYFILESupport EMAIL_SSL_CERTFILE for private certificate authority
Type: BugNew feature

I don't think this is a duplicate of #34504. That ticket wanted to disable hostname checking and certificate verification, which is indeed not recommended.

This ticket is trying to use a private certificate authority with hostname checking and certificate verification enabled. I'd think we'd want to encourage that when using a private CA. Django's SMTP EmailBackend makes that difficult right now, by requiring subclassing and overriding an undocumented property.

I would treat this as a feature request for Django's SMTP EmailBackend to support setting EMAIL_SSL_CERTIFICATE to a private CA or self-signed certificate, with all recommended security enabled.

If we don't want to do that, we should probably add some documentation along the lines of:

  • EMAIL_SSL_CERTIFICATE is meant only for client authentication, and therefore must either include the private key or be used together with EMAIL_SSL_KEYFILE.
  • To use a private certificate authority or self-signed certificate with your SMTP server, don't use Django's EMAIL_SSL_CERTIFICATE. Instead, add your private CA to your system's OpenSSL ca-path or set the SSL_CERT_FILE and/or SSL_CERT_DIR environment variables to point to it. (See Python's ssl.get_default_verify_paths().)

(Also, I might be misunderstanding, but it looks like when EMAIL_SSL_CERTIFICATE is set the SMTP backend creates a less-secure SSLContext with checking disabled. And I see we've actually documented that behavior.)

comment:7 by Mike Edmunds, 3 months ago

(Also, since the docs for EMAIL_SSL_CERTIFICATE already say "certificate chain file," it seems reasonable to expect that could be a private CA bundle. Which would make this a bug.)

comment:8 by Sarah Boyce, 3 months ago

Triage Stage: UnreviewedAccepted

comment:9 by Igor Scheller, 3 months ago

Has patch: set
Owner: set to Igor Scheller
Status: newassigned

I added a PR to implement it as an additional feature (altho changing the current behaviour might be an option too)

comment:10 by Sarah Boyce, 3 months ago

Patch needs improvement: set

comment:11 by Igor Scheller, 3 months ago

As noted in the PR discussion, adding another option should be avoided in favour of the upcoming EMAIL_PROVIDERS setting, discussed in #35514.
The PR now reflects that change by allowing the setting to be set in the constructor and later from above providers config.

Last edited 3 months ago by Igor Scheller (previous) (diff)

comment:12 by Mike Edmunds, 3 months ago

This seems like a useful addition, given that:

  • Internal private CAs are not all that exotic.
  • Django's current documentation seems to suggest that EMAIL_SSL_CERTIFICATE can be set to a private CA bundle, but this doesn't actually work.
  • Although the problem can be solved by subclassing smtp.EmailBackend to override ssl_context, that seems to be error prone. A lot of high-ranking solutions disable certificate checking entirely or introduce other security issues. (Another common recommendation is downgrading to Django 4.1.)

Question: am I understanding correctly that the proposed ssl_cafile option would also work to securely verify self-signed certs? (That seems like another semi-common Django email question that generates a lot of less-secure answers.)

comment:13 by Igor Scheller, 3 months ago

Yes, it allows you to add a root-CA certificate / bundle of certificates which is used as the trust anchor, it can either be your own self-signed one or the one your organisation set up as a private CA.

Note: See TracTickets for help on using tickets.
Back to Top