How to accept self-signed certificate from e-mail server via smtplib (TSL)?
Asked Answered
M

2

11

My script

from stmplib import SMTP
con = SMTP(server, port)
con.starttls()
con.login(user, pass)
con.quit()

falls with error: python2.7/ssl.py", line 847, in do_handshake self._sslobj.do_handshake()

When I run command openssl to this server it falls with error 21: Verify return code: 21 (unable to verify the first certificate).

I would like to know how to specify in smtplib of python option “always accept self-signed certificate when connect is established via tls to e-mail server"? Like I do in requests.get setting key verify=False.

Update This variant with custom smtp class and context = ssl._create_unverified_context() return the same error as above:

import smtplib
import ssl

class MySMTP(smtplib.SMTP):
    def __init__(self, host='', port=0, timeout=5):
        smtplib.SMTP.__init__(self, host, port, timeout=timeout)
        self._host = host

    def starttls(self, keyfile=None, certfile=None, context=None):
        from urllib import _have_ssl

        self.ehlo_or_helo_if_needed()
        if not self.has_extn("starttls"):
            raise SMTPNotSupportedError("STARTTLS extension not supported by server.")
        (resp, reply) = self.docmd("STARTTLS")
        if resp == 220:
            if not _have_ssl:
                raise RuntimeError("No SSL support included in this Python")
            if context is not None and keyfile is not None:
                raise ValueError("context and keyfile arguments are mutually "
                             "exclusive")
            if context is not None and certfile is not None:
                raise ValueError("context and certfile arguments are mutually "
                             "exclusive")
            if context is None:
                context = ssl._create_stdlib_context(certfile=certfile,
                                                 keyfile=keyfile)
            self.sock = context.wrap_socket(self.sock,
                                        server_hostname=self._host)
            self.file = None
            # 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)

con= MySMTP(server, port)
context  = ssl._create_unverified_context()
con.starttls(context = context)
con.login(user, pass)
con.quit()
Marcin answered 4/3, 2019 at 3:0 Comment(7)
Which version of Python are you using?Endres
The Question states 2.7Wassail
Is your self-signed certificate from a CA ( “certification authority”)?Crock
Did you try using ssl._https_verify_certificates(enable=False)? (although that shouldn't affect this scenario). For the 2nd variant try context = ssl._create_unverified_context(cert_reqs=ssl.CERT_NONE) (or the same thing perfrmed manually after context was created: context.verify_mode = ssl.CERT_NONE).Foggy
Thx for try but all failed.Marcin
Could you be so kind to add the commands and their output (including the full Pythpn traceback)? Also what OpenSSL version are you using? What about Python (2.7.?)? You're saying that you are able to connect to it using requests (if yes are you connecting to the same server app - on the same port)? I know it's a longshot, but is the server publicly available :d ?Foggy
You might want to take a look at the existing answer. Does it answer your question? Up / down vote it.Foggy
K
12

Late answer, but I fixed the ssl.SSLError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed (_ssl.c:852) on python 3.7 using ssl._create_unverified_context(), i.e.:

import smtplib, ssl
context = ssl._create_unverified_context()
with smtplib.SMTP_SSL("domain.tld", 465, context=context) as server:
    server.login(user, password)
    server.sendmail(sender_email, receiver_email, message.as_string())
Katinakatine answered 2/8, 2019 at 21:31 Comment(2)
You are lucky. I have try this method early but it had not help.Marcin
Thanks, it worked! even though I had to use it on "smtp.gmail.com"Distrust
E
3

It really seems that your approach with subclassing SMTP class and overriding starttls() method to accept context argument is the way to go - it's actually even introduced in Python 3.x.

However:

con = MySMTP(server, port)
context  = ssl.create_default_context(cafile=PATH_TO_CERTIFICATE_AUTHORITY_ROOT_CRT_FILE)
con.starttls(context=context)
con.login(user, pass)
con.quit()

should work instead of unverified context.

When you self signed your certificate you used your own certificate authority, so probably you created root CA certificate before.

This root certificate must be known to your SMTP client in order to verify your SMTP server certificate.
So find that file, put it in location accessible to your SMTP client and set this path as a value of PATH_TO_CERTIFICATE_AUTHORITY_ROOT_CRT_FILE.

What you did instead was:

ssl._create_stdlib_context(certfile=certfile, keyfile=keyfile)

but certfile and keyfile are client certificate and key - some servers won't accept connections from unverified client.

Useful links:
ssl.create_default_context() documentation
In case you can't find your root certificate you can start from scratch (just ignore all MacOS windows on pictures - your SMTP client wants access to root certificate file - the one they add to the browser in this guide)

Effortless answered 12/3, 2019 at 7:28 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.