Re: [Exim] CRL support for exim 4.30 with gnutls 1.0.4 (patc…

Top Page
Delete this message
Reply to this message
Author: Mark Foster
Date:  
To: exim-users
Subject: Re: [Exim] CRL support for exim 4.30 with gnutls 1.0.4 (patch)
--
--
On Mon, Feb 02, 2004 at 12:31:07PM +0000, Philip Hazel wrote:
> On Fri, 23 Jan 2004, Vivek wrote:
>
> > Hi there: I needed CRL support for exim 4, and I noted that gnutls had
> > added CRL support at some point, so I cobbled together this patch
> > (for the debian package) and sent it to the maintainer, who suggested I
> > post it here.
>
> Thanks. I've saved it to look at when I get a moment.
>
> > I also have a patch against openssl that I ported from one that someone
> > at work did, but it's more complicated and I haven't tested that one yet,
> > and it needs a little work anyway to make it work in a similar manner
> > to the gnutls patch.
>
> That would also be useful.


Philip, Sheldon, et al...
I tested the openssl patch that Vivek sent to me offlist.
It works! What happens is, if the certificate is revoked (listed in CRL) the
STARTTLS fails and the session falls back to vanilla TCP.

That was a little surprising, in that I thought it would just leave
$tls_certificate_verified false.

But now that I have seen the reaction, I am convinced this is also the proper
behavior to exhibit in response to an expired certificate (mentioned here-

After discovering the (apparent) failure of an expired certificate to set
$tls_certificate_verified false, and testing the CRL mechanism, I believe the that an expired certificate should cause the same result - invalidate the TLS
session. Here is a link to my blog which shows the results of my testing...
http://mark.foster.cc/blog/ca8b5174dd3054fe8d1c64ef762edae4/

BTW, the patch must be applied in conjuntion with (but after) the gnutls patch,
since that is what sets up access to the tls_crl configure variable.
-mdf

--
Some days it's just not worth chewing through the restraints...
Mark Foster <mark@???> http://mark.foster.cc/

--
--- src/tls-openssl.c    Fri Jan 23 18:33:13 2004
+++ src/tls-openssl.c    Fri Jan 23 19:41:52 2004
@@ -41,8 +41,11 @@
 static int  ssl_session_timeout = 200;
 static BOOL verify_optional = FALSE;


-
-
+/* +CRL support */
+static X509_STORE *Revocation_store=NULL;
+X509_STORE *SSL_X509_STORE_create(char *cpFile, char *cpPath);
+int ssl_callback_SSLVerify_CRL(int ok, X509_STORE_CTX *ctx);
+/* -CRL support */


/*************************************************
@@ -182,6 +185,23 @@
if (!verify_callback_called) tls_certificate_verified = TRUE;
verify_callback_called = TRUE;

+/* +CRL support */
+/* Additionally perform CRL-based revocation checks as per Apache's mod_ssl */
+if (state == 1)
+  {
+  DEBUG(D_tls) debug_printf("About to call ssl_callback_SSLVerify_CRL\n");
+  state = ssl_callback_SSLVerify_CRL(state, x509ctx);
+  if (state == 0)
+    {
+    log_write(1, LOG_MAIN, "SSL CRL error: depth=%d error=%s cert=%s",
+              x509ctx->error_depth,
+              X509_verify_cert_error_string(x509ctx->error),
+              txt);
+    return 0;   /* reject */
+    }
+  }
+/* -CRL support */
+
 return 1;   /* accept */
 }


@@ -455,13 +475,17 @@
*/

static int
-setup_certs(uschar *certs, host_item *host, BOOL optional)
+setup_certs(uschar *certs, uschar *crls, host_item *host, BOOL optional)
{
uschar *expcerts;
-
+uschar *expcrl = NULL;
+
if (!expand_check(certs, US"tls_verify_certificates", &expcerts))
return DEFER;

+if (crls && !expand_check(crls, US"tls_crl", &expcrl))
+  return DEFER;
+
 if (expcerts != NULL)
   {
   struct stat statbuf;
@@ -483,6 +507,36 @@
       return tls_error(US"SSL_CTX_load_verify_locations", host);
     if (file != NULL)
       SSL_CTX_set_client_CA_list(ctx, SSL_load_client_CA_file(CS file));
+/* +CRL support */
+/* Init CRL stuff from tls_crl */
+    if ( expcrl != NULL )
+      {
+          /* Free up the old store, as we've been called again */
+      if (Revocation_store != NULL)
+        {
+        X509_STORE_free(Revocation_store);
+        Revocation_store = NULL;
+        }
+
+      /* stat the CRLs to see if they're a dir or a file */
+      if (Ustat(expcrl, &statbuf) < 0)
+        {
+        log_write(0, LOG_MAIN|LOG_PANIC, "failed to stat %s for CRLs", expcrl);
+        }
+      else
+        {
+        if ((statbuf.st_mode & S_IFMT) == S_IFDIR)
+          { file = NULL; dir = expcrl; }
+        else
+          { file = expcrl; dir = NULL; }
+        DEBUG(D_tls)
+          debug_printf("About to call SSL_X509_STORE_create(%s, %s)\n",
+                       file, dir);
+        if ((Revocation_store = SSL_X509_STORE_create(file, dir)) == NULL)
+          return tls_error("SSL_X509_STORE_create", host);
+        }
+      }
+/* -CRL support */
     }


/* If verification is optional, don't fail if no certificate */
@@ -543,13 +597,13 @@

 if (verify_check_host(&tls_verify_hosts) == OK)
   {
-  rc = setup_certs(tls_verify_certificates, NULL, FALSE);
+  rc = setup_certs(tls_verify_certificates, tls_crl, NULL, FALSE);
   if (rc != OK) return rc;
   verify_optional = FALSE;
   }
 else if (verify_check_host(&tls_try_verify_hosts) == OK)
   {
-  rc = setup_certs(tls_verify_certificates, NULL, TRUE);
+  rc = setup_certs(tls_verify_certificates, tls_crl, NULL, TRUE);
   if (rc != OK) return rc;
   verify_optional = TRUE;
   }
@@ -676,7 +730,7 @@
     return tls_error(US"SSL_CTX_set_cipher_list", host);
   }


-rc = setup_certs(verify_certs, host, FALSE);
+rc = setup_certs(verify_certs, NULL, host, FALSE);
if (rc != OK) return rc;

if ((ssl = SSL_new(ctx)) == NULL) return tls_error(US"SSL_new", host);
@@ -912,5 +966,257 @@

 tls_active = -1;
 }
+
+/* +CRL support */
+/* ====================================================================
+ * Copyright (c) 1998-2001 Ralf S. Engelschall. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following
+ *    disclaimer in the documentation and/or other materials
+ *    provided with the distribution.
+ *
+ * 3. All advertising materials mentioning features or use of this
+ *    software must display the following acknowledgment:
+ *    "This product includes software developed by
+ *     Ralf S. Engelschall <rse@???> for use in the
+ *     mod_ssl project (http://www.modssl.org/)."
+ *
+ * 4. The names "mod_ssl" must not be used to endorse or promote
+ *    products derived from this software without prior written
+ *    permission. For written permission, please contact
+ *    rse@???.
+ *
+ * 5. Products derived from this software may not be called "mod_ssl"
+ *    nor may "mod_ssl" appear in their names without prior
+ *    written permission of Ralf S. Engelschall.
+ *
+ * 6. Redistributions of any form whatsoever must retain the following
+ *    acknowledgment:
+ *    "This product includes software developed by
+ *     Ralf S. Engelschall <rse@???> for use in the
+ *     mod_ssl project (http://www.modssl.org/)."
+ *
+ * THIS SOFTWARE IS PROVIDED BY RALF S. ENGELSCHALL ``AS IS'' AND ANY
+ * EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL RALF S. ENGELSCHALL OR
+ * HIS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+ * OF THE POSSIBILITY OF SUCH DAMAGE.
+ * ====================================================================
+ */
+
+/*  _________________________________________________________________
+**
+**  Certificate Revocation List (CRL) Storage
+**  _________________________________________________________________
+*/
+
+X509_STORE *SSL_X509_STORE_create(char *cpFile, char *cpPath)
+{
+    X509_STORE *pStore;
+    X509_LOOKUP *pLookup;
+
+    if (cpFile == NULL && cpPath == NULL)
+        return NULL;
+    if ((pStore = X509_STORE_new()) == NULL)
+        return NULL;
+    if (cpFile != NULL) {
+        if ((pLookup = X509_STORE_add_lookup(pStore, X509_LOOKUP_file())) == NULL) {
+            X509_STORE_free(pStore);
+            return NULL;
+        }
+        X509_LOOKUP_load_file(pLookup, cpFile, X509_FILETYPE_PEM);
+    }
+    if (cpPath != NULL) {
+        if ((pLookup = X509_STORE_add_lookup(pStore, X509_LOOKUP_hash_dir())) == NULL) {
+            X509_STORE_free(pStore);
+            return NULL;
+        }
+        X509_LOOKUP_add_dir(pLookup, cpPath, X509_FILETYPE_PEM);
+    }
+    return pStore;
+}
+
+int SSL_X509_STORE_lookup(X509_STORE *pStore, int nType,
+                          X509_NAME *pName, X509_OBJECT *pObj)
+{
+    X509_STORE_CTX pStoreCtx;
+    int rc;
+
+    X509_STORE_CTX_init(&pStoreCtx, pStore, NULL, NULL);
+    rc = X509_STORE_get_by_subject(&pStoreCtx, nType, pName, pObj);
+    X509_STORE_CTX_cleanup(&pStoreCtx);
+    return rc;
+}
+
+int ssl_callback_SSLVerify_CRL(
+    int ok, X509_STORE_CTX *ctx)
+{
+    X509_OBJECT obj;
+    X509_NAME *subject;
+    X509_NAME *issuer;
+    X509 *xs;
+    X509_CRL *crl;
+    X509_REVOKED *revoked;
+    long serial;
+    BIO *bio;
+    int i, n, rc;
+    char *cp;
+    char *cp2;
+
+    /*
+     * Determine certificate ingredients in advance
+     */
+    xs      = X509_STORE_CTX_get_current_cert(ctx);
+    subject = X509_get_subject_name(xs);
+    issuer  = X509_get_issuer_name(xs);
+
+    /*
+     * OpenSSL provides the general mechanism to deal with CRLs but does not
+     * use them automatically when verifying certificates, so we do it
+     * explicitly here. We will check the CRL for the currently checked
+     * certificate, if there is such a CRL in the store.
+     *
+     * We come through this procedure for each certificate in the certificate
+     * chain, starting with the root-CA's certificate. At each step we've to
+     * both verify the signature on the CRL (to make sure it's a valid CRL)
+     * and it's revocation list (to make sure the current certificate isn't
+     * revoked).  But because to check the signature on the CRL we need the
+     * public key of the issuing CA certificate (which was already processed
+     * one round before), we've a little problem. But we can both solve it and
+     * at the same time optimize the processing by using the following
+     * verification scheme (idea and code snippets borrowed from the GLOBUS
+     * project):
+     *
+     * 1. We'll check the signature of a CRL in each step when we find a CRL
+     *    through the _subject_ name of the current certificate. This CRL
+     *    itself will be needed the first time in the next round, of course.
+     *    But we do the signature processing one round before this where the
+     *    public key of the CA is available.
+     *
+     * 2. We'll check the revocation list of a CRL in each step when
+     *    we find a CRL through the _issuer_ name of the current certificate.
+     *    This CRLs signature was then already verified one round before.
+     *
+     * This verification scheme allows a CA to revoke its own certificate as
+     * well, of course.
+     */
+
+    /*
+     * Try to retrieve a CRL corresponding to the _subject_ of
+     * the current certificate in order to verify it's integrity.
+     */
+    memset((char *)&obj, 0, sizeof(obj));
+    rc = SSL_X509_STORE_lookup(Revocation_store, X509_LU_CRL, subject, &obj);
+    crl = obj.data.crl;
+    if (rc > 0 && crl != NULL) {
+        /*
+         * Log information about CRL
+         * (A little bit complicated because of ASN.1 and BIOs...)
+         */
+        DEBUG(D_tls) {
+            bio = BIO_new(BIO_s_mem());
+            BIO_printf(bio, "lastUpdate: ");
+            ASN1_UTCTIME_print(bio, X509_CRL_get_lastUpdate(crl));
+            BIO_printf(bio, ", nextUpdate: ");
+            ASN1_UTCTIME_print(bio, X509_CRL_get_nextUpdate(crl));
+            n = BIO_pending(bio);
+            cp = malloc(n+1);
+            n = BIO_read(bio, cp, n);
+            cp[n] = '\0';
+            BIO_free(bio);
+            cp2 = X509_NAME_oneline(subject, NULL, 0);
+            debug_printf("CA CRL: Issuer: %s, %s\n", cp2, cp);
+            free(cp2);
+            free(cp);
+        }
+
+        /*
+         * Verify the signature on this CRL
+         */
+        if (X509_CRL_verify(crl, X509_get_pubkey(xs)) <= 0) {
+            log_write(0, LOG_MAIN, "Invalid signature on CRL");
+            X509_STORE_CTX_set_error(ctx, X509_V_ERR_CRL_SIGNATURE_FAILURE);
+            X509_OBJECT_free_contents(&obj);
+            return FALSE;
+        }
+
+        /*
+         * Check date of CRL to make sure it's not expired
+         */
+        i = X509_cmp_current_time(X509_CRL_get_nextUpdate(crl));
+        if (i == 0) {
+            log_write(0, LOG_MAIN, "Found CRL has invalid nextUpdate field");
+            X509_STORE_CTX_set_error(ctx, X509_V_ERR_ERROR_IN_CRL_NEXT_UPDATE_FIELD);
+            X509_OBJECT_free_contents(&obj);
+            return FALSE;
+        }
+        if (i < 0) {
+            log_write(0, LOG_MAIN,
+                    "Found CRL is expired - "
+                    "revoking all certificates until you get updated CRL");
+            X509_STORE_CTX_set_error(ctx, X509_V_ERR_CRL_HAS_EXPIRED);
+            X509_OBJECT_free_contents(&obj);
+            return FALSE;
+        }
+        X509_OBJECT_free_contents(&obj);
+    }
+
+    /*
+     * Try to retrieve a CRL corresponding to the _issuer_ of
+     * the current certificate in order to check for revocation.
+     */
+    memset((char *)&obj, 0, sizeof(obj));
+    rc = SSL_X509_STORE_lookup(Revocation_store, X509_LU_CRL, issuer, &obj);
+    crl = obj.data.crl;
+    if (rc > 0 && crl != NULL) {
+        /*
+         * Check if the current certificate is revoked by this CRL
+         */
+#if SSL_LIBRARY_VERSION < 0x00904000
+        n = sk_num(X509_CRL_get_REVOKED(crl));
+#else
+        n = sk_X509_REVOKED_num(X509_CRL_get_REVOKED(crl));
+#endif
+        for (i = 0; i < n; i++) {
+#if SSL_LIBRARY_VERSION < 0x00904000
+            revoked = (X509_REVOKED *)sk_value(X509_CRL_get_REVOKED(crl), i);
+#else
+            revoked = sk_X509_REVOKED_value(X509_CRL_get_REVOKED(crl), i);
+#endif
+            if (ASN1_INTEGER_cmp(revoked->serialNumber, X509_get_serialNumber(xs)) == 0) {
+
+                serial = ASN1_INTEGER_get(revoked->serialNumber);
+                cp = X509_NAME_oneline(issuer, NULL, 0);
+                log_write(0, LOG_MAIN,
+                        "Certificate with serial %ld (0x%lX) "
+                        "revoked per CRL from issuer %s",
+                        serial, serial, cp);
+                free(cp);
+
+                X509_STORE_CTX_set_error(ctx, X509_V_ERR_CERT_REVOKED);
+                X509_OBJECT_free_contents(&obj);
+                return FALSE;
+            }
+        }
+        X509_OBJECT_free_contents(&obj);
+    }
+    return ok;
+ }
+/* -CRL support */


/* End of tls-openssl.c */
--
[ Content of type application/pgp-signature deleted ]
--