2

I'm using imaplib and poplib to perform email collection using IMAPS and POP3S for a secure connection. But from what I've been able to determine, neither library uses a CA to confirm the validity of the certificate received. It this true? If it is, is it possible to set imaplib or poplib to use a CA?

If it's not true and they do use a CA, can someone please tell me how imaplib/poplib do it?

Thanks.

user788462
  • 1,085
  • 2
  • 15
  • 24

4 Answers4

2

A quick check of imaplib.py shows that it uses ssl.wrap_socket() to implement the IMAP_SSL() call. The call to wrap_socket() call only provides 3-parameters, and does not pass the required parameter ca_cert which is what you need to validate the CA.

You could inherit from IMAP4_SSL, and override the open() method to pass in the required ca_cert. Check out http://docs.python.org/library/ssl.html for more info.

Perhaps something like:

class IMAP4_SSL_CA_CHECKER(IMAP4_SSL):
    def open(self, host = '', port = IMAP4_SSL_PORT, ca_certs = None):
        self.host = host
        self.port = port
        self.sock = socket.create_connection((host, port))
        self.sslobj = ssl.wrap_socket(self.sock, self.keyfile, 
                          self.certificate, ca_certs=ca_certs)
        self.file = self.sslobj.makefile('rb')
user590028
  • 11,364
  • 3
  • 40
  • 57
  • Thanks for that. I'm reasonable new to Python, so just to confirm I understand your suggestion correctly. I want to override imaplib's ssl.wrap_socket() to allow the "cert_reqs" parameter to be set (e.g. set it to "CERT_REQUIRED") then set "ca_certs" to point to a file (I construct) containing the CA's? (assuning I've understood correctly) that doesn't sound like too bad a workaround :) – user788462 Mar 15 '12 at 22:13
  • I'm not sure if you want to specify 'cert_reqs' (it's possible you do -- but that param addresses a subtly different issue). It tells the client side to require the server to provide a certificate and that it be valid. Arguably this is a a good-thing, if you are certain both sides will agree, but if you're going for something more generically useful, this assumption might fall down. – user590028 Mar 22 '12 at 14:58
  • Looks like `self.certificate` has to be replaced with `self.certfile` to make it work. – kasperd Aug 13 '17 at 16:23
  • Looks like this does not work. I tested with `ca_certs` pointing to a different root cert from the one used by the server I connect to and I connected to the server using a different hostname from what is used in the certificate. Either of those should have caused a certificate validation error, but it doesn't. – kasperd Aug 13 '17 at 16:40
  • Since `wrap_socket` is never told what the hostname is there is no way for it to verify the certificate. – kasperd Aug 13 '17 at 17:07
1

Because IMAP4SSL.open is called from IMAP.init the Solution above does not help, because the user does not call open(). You can overwrite IMAP.init to...

Short: Extending only the parameter for open() is not enough.

I used Injection:

def IMAP4SSL_open(self, host = '', port = imaplib.IMAP4_SSL_PORT):
    ... own implementation ...
    wrap_socket( ... cert_reqs=ssl.CERT_REQUIRED ... )
imaplib.IMAP4_SSL.__dict__['open']=IMAP4SSL_open
Gabor
  • 11
  • 3
1

One more thought has occurred to me to mention. The python ssl library is built atop OpenSSL. If you are going to start requiring the server to provide certificates and that they be valid, you will quickly run into issues on various flavors of Unix related to the certificate store.

If you happen to be working on a system that already has Mozilla/Firefox installed, the cert store will likely be setup correctly. But it not, you're going to struggle for a few days trying to get this to work correctly.

This link helped us immensely: http://www.madboa.com/geek/openssl/

Particularly pay attention to this link: http://www.madboa.com/geek/openssl/#verify-system

Any developers working w/ openssl should bookmark that site. It's a bit on the terse side, but every single entry is worth it's weight in gold!

user590028
  • 11,364
  • 3
  • 40
  • 57
  • Thanks again for your help. I'll have a througher read of those doc's to fully understand what I'm dealing with. I've implemented the class you mentioned with some additional bits, including:- adding cert_reqs=CERT_REQUIRED, ca_certs='/etc/.../ca-bundle.crt' to def open and passing them to self.sslobj. I also imported the required ssl bits and set up "imaplib.IMAP4_SSL = IMAP4_SSL_CA_CHECKER". – user788462 Mar 23 '12 at 00:53
  • It seems to be doing the right thing, but now I get "SSLError: [Errno 1] _ssl.c:503: error:14090086:SSL routines:SSL3_GET_SERVER_CERTIFICATE:certificate verify failed". So it's looking like I need to address my code accepting the certificate from my Dovecot server. Time to start reading OpenSSL :) – user788462 Mar 23 '12 at 00:54
0

I'm currently building something in this direction.

The following code adds starttls to IMAP. Simply call server.starttls() after connecting. Be sure to connect to the normal IMAP Port.

import imaplib,ssl
def IMAP_starttls(self, keyfile=None, certfile=None,cert_reqs=ssl.CERT_NONE,ca_certs=None):
  if not 'STARTTLS' in self.capabilities:
    raise self.error("STARTTLS extension not supported by server.")
  (resp, reply) = self._simple_command("STARTTLS")
  self.sock = ssl.wrap_socket(self.sock, keyfile, certfile,cert_reqs=cert_reqs,ca_certs=ca_certs)
  self.file = self.sock.makefile('rb')

imaplib.IMAP4.__dict__['starttls']=IMAP_starttls
imaplib.Commands['STARTTLS']=('NONAUTH',)

PS: I wanted to add this as a comment, but code was to long for a comment.

Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
Gabor
  • 11
  • 3