Requiring SSL authentican for IMAP

For a while now, I've been looking at getting away from password authentication to my SSL server and wanting it to be SSL authentication only (without going to the extreme of deploying Kerberos.)

Finally I've managed to lick it, after picking it up and putting it down a couple of times. What made it so difficult? Having to pilot openssl. There are so many options with this program, it is very easy to make a wrong step, forget something, and have to start over. To make my life a tad easier, I disabled basic constraints on all of the certificates I generated below.

Building my own CA and certificates to use happened in 3 stages:

  • generating a certificate authority (CA) for my own use
  • generating a server certificate for the imap server and signing that with the CA cert
  • generating an email certificate for me to use, also signed by my CA
An easy mistake to make here is to use any email address more than once. So for email of the above, I have a different email address - root-AT-domain, root@imap-DOT-domain and me@domain. Why is this necessary? The email address is one of they key identifiers and needs to be unique (or at least according to my own openssl.cnf file.)

$bopenssl req -x509 -newkey rsa -out cacert.pem -outform PEM -days 10000
AU [AU]:
Victoria [Victoria]:
Melbourne [Melbourne]:
domainname [domainname]:
Organizational Unit Name (eg, section) [OU]:
Common Name (eg, YOUR name) [Deus Ex Machine]:Deus Ex Machina
Email Address [root@domainname]:root@domainname

$ mkdir private
$ mv privkey.pem private/cakey.pem

$ openssl req -newkey rsa -keyout imapd.pem -out imapd.pem
[CN] = imap.domainname

$ mkdir newcerts
$ touch index.txt
$ echo 01 > serial

$ openssl ca -in imapd.pem -notext -out imapd-cert.pem

$ openssl req -newkey rsa -keyout darren.pem -out darren.pem
[CN] = My Name

$ openssl ca -in user.pem -notext -out user-cert.pem

$ openssl rsa -in imapd.pem -out imapd-key.pem
Enter pass phrase for imapd.pem:
writing RSA key
$ cp imapd-key.pem /etc/openssl/private/imapd.pem

$ cp cacert.pem /etc/openssl/`openssl x509 -hash -in cacert.pem -noout`.0

$ cat user.pem user-cert.pem > ~/user-cert.pem
$ openssl pkcs12 -in ~/user-cert.pem -export -out ~/user.pkcs12

I should have added, that for sending email, I wanted to continue using the same mail server software that I'd been using, without risking changing any config files. Enter stunnel. The config file below is very simple (yay!), although there is one non-obvious aspect to it: if you provide both CApath and CAfile in the config file, it will produce an error relating to a certificate already being loaded. Solution? Only specify CApath. In retrospect, maybe I could have taken this approach with imap too...but a quick test reveals it doesn't work quite that easily.

debug = 5
cert = /etc/openssl/private/imapd.pem
CApath = /etc/openssl
verify = 3
[465]
        accept = 465
        connect = 127.0.0.1:25
        local = 127.0.0.1

The other part of the changes required was modifications to the UW IMAP server software (patches below.) Although the software package as it stands accepts SSL connections on port 993, it doesn't have any option for enforcing all of the SSL clients to have a valid certificate and/or dropping a connection if they don't. My hacks below are a tad extreme: they force all clients to have a valid certificate. But then this is my desire :) Why? Because I don't want to expose my IMAP server to password guessing via IMAP SSL connections. This way they need to first obtain a valid certificate :) Maybe not impossible but it changes the game slightly...

\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*
\*\*\* 223,231 \*\*\*\*
      return "SSL context failed";
    SSL_CTX_set_options (stream->context,0);
                                /\* disable certificate validation? \*/
!   if (flags & NET_NOVALIDATECERT)
      SSL_CTX_set_verify (stream->context,SSL_VERIFY_NONE,NIL);
!   else SSL_CTX_set_verify (stream->context,SSL_VERIFY_PEER,ssl_open_verify);
                                /\* set default paths to CAs \*/
    SSL_CTX_set_default_verify_paths (stream->context);
                                /\* want to send client certificate? \*/
--- 363,371 ----
      return "SSL context failed";
    SSL_CTX_set_options (stream->context,0);
                                /\* disable certificate validation? \*/
!   /\*if (flags & NET_NOVALIDATECERT)
      SSL_CTX_set_verify (stream->context,SSL_VERIFY_NONE,NIL);
!   else\*/SSL_CTX_set_verify (stream->context,SSL_VERIFY_PEER,ssl_open_verify);
                                /\* set default paths to CAs \*/
    SSL_CTX_set_default_verify_paths (stream->context);
                                /\* want to send client certificate? \*/
\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*
\*\*\* 261,267 \*\*\*\*
    if (SSL_write (stream->con,"",0) < 0)
      return ssl_last_error ? ssl_last_error : "SSL negotiation failed";
                                /\* need to validate host names? \*/
!   if (!(flags & NET_NOVALIDATECERT) &&
        (err = ssl_validate_cert (cert = SSL_get_peer_certificate (stream->con),
                                host))) {
                                /\* application callback \*/
--- 401,407 ----
    if (SSL_write (stream->con,"",0) < 0)
      return ssl_last_error ? ssl_last_error : "SSL negotiation failed";
                                /\* need to validate host names? \*/
!   if (/\*!(flags & NET_NOVALIDATECERT) &&\*/
        (err = ssl_validate_cert (cert = SSL_get_peer_certificate (stream->con),
                                host))) {
                                /\* application callback \*/
\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*
\*\*\* 697,702 \*\*\*\*
--- 837,848 ----
        if (SSL_CTX_need_tmp_RSA (stream->context))
        SSL_CTX_set_tmp_rsa_callback (stream->context,ssl_genkey);
                                /\* create new SSL connection \*/
+       SSL_CTX_load_verify_locations(stream->context, NULL, "/etc/openssl");
+
+       SSL_CTX_set_verify(stream->context, SSL_VERIFY_PEER, NULL);
+
+       SSL_CTX_set_verify_depth(stream->context, 1);
+
        if (!(stream->con = SSL_new (stream->context)))
        syslog (LOG_ALERT,"Unable to create SSL connection, host=%.80s",
                tcp_clienthost ());
\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*
\*\*\* 707,712 \*\*\*\*
--- 853,870 ----
          syslog (LOG_INFO,"Unable to accept SSL connection, host=%.80s",
                  tcp_clienthost ());
        else {                  /\* server set up \*/
+
+         if (SSL_get_peer_certificate(stream->con) != NULL) {
+           if (SSL_get_verify_result(stream->con) == X509_V_OK)
+             syslog(LOG_ERR, "SSL client verification succeeded");
+           else {
+             syslog(LOG_ERR, "SSL client verification failed");
+               goto badclient;
+             }
+         } else {
+             syslog(LOG_ERR, "the peer certificate was not presented");
+               goto badclient;
+         }
          sslstdio = (SSLSTDIOSTREAM \*)
            memset (fs_get (sizeof(SSLSTDIOSTREAM)),0,sizeof (SSLSTDIOSTREAM));
          sslstdio->sslstream = stream;
\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*
\*\*\* 725,730 \*\*\*\*
--- 883,889 ----
        }
      }
    }
+ badclient:
    while (i = ERR_get_error ())        /\* SSL failure \*/
      syslog (LOG_ERR,"SSL error status: %.80s",ERR_error_string (i,NIL));
    ssl_close (stream);         /\* punt stream \*/
Comments:

I recently wrote a site on managing a CA with openssl - in particular a (mostly) PKIX compliant one. It can be a bit much, but this may help: http://www.phildev.net/ssl/ - Phil

Posted by Phil on December 08, 2006 at 04:20 AM PST #

Post a Comment:
Comments are closed for this entry.
About

avalon

Search

Archives
« April 2014
SunMonTueWedThuFriSat
  
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
   
       
Today