Python's smtplib and server certificates

Posted on . Updated on .

Back when I wrote the post “Moving away from KMail” I mentioned I had also moved from my own Putmail to msmtp. I forgot to explain why, but it’s important and interesting. The reason is server certificate validation.

In short, the putmail.py program is a simple Python 2 script. Internally, it’s not object-oriented or even much structured with functions. It’s just a script that needs to perform a few operations in order, as simply as possible. It relies on Python’s email and smtplib packages. The latter provides a class to handle connections with SMTP servers, with its instances having a “starttls” method that allows switching to TLS mode by issuing a “STARTTLS” SMTP command as required by most modern and secure SMTP servers. Almost everybody uses the STARTTLS method nowadays instead of using full SSL encryption from the start.

When switching to TLS mode, the client is supposed to verify the server certificate so as to avoid man-in-the-middle attacks by making sure the server certificate is properly signed by a certificate authority and matches the domain name of the server you’re talking to. msmtp verifies the server certificate thanks to its “tls_certcheck” option. When active, it uses /etc/ssl/certs/ca-certificates.crt or a similar file, and refuses to connect if anything’s wrong.

With Python’s smtplib, on the contrary, no server certificate validation takes place and, while the username and password don’t travel in the clear anymore, the client is vulnerable to MITM attacks. The keyfile and certfile parameters to starttls are not used for this purpose. They belong to the client and are used to authenticate it.

If I wanted to validate the server certificate I’d have to inherit from the SMTP class and replace some methods, or monkey-patch smtplib. See for example this post from the mercurial-devel mailing list. There are more examples of people discussing this issue, easily found with a web search.

Given the simplicity of the putmail.py script, such modifications would increase the complexity of the code too much, so at that point I was better off using msmtp. After all, I only had two reasons to write putmail.py in the first place. First, to learn a bit more Python and the SMTP protocol. Second, to have a program that could be combined with Mutt, supported multiple accounts and the ability to choose the account automatically based on the “From:” header of the e-mail message.

With the first goal accomplished and msmtp later gaining its “--read-envelope-from” option, which does basically the same except it uses the “envelope from” address with the same results, msmtp is now a better choice.

Load comments