Do not explicitly disable SSLv3 if GnuTLS does it already.
[claws.git] / src / common / ssl.c
index 50ac5a32d92a082331f015e0d822457cd54477e4..e25a42301fb73752a1d71b1cd7b6f1108ab31ce8 100644 (file)
 #include <errno.h>
 #include <pthread.h>
 
-#if GNUTLS_VERSION_NUMBER <= 0x020b00
-#include <gcrypt.h>
-GCRY_THREAD_OPTION_PTHREAD_IMPL;
-#endif
-
 #include "claws.h"
 #include "utils.h"
 #include "ssl.h"
 #include "ssl_certificate.h"
 #include "hooks.h"
 
+#if GNUTLS_VERSION_NUMBER <= 0x020b00
+#include <gcrypt.h>
+GCRY_THREAD_OPTION_PTHREAD_IMPL;
+#endif
+
 #ifdef HAVE_LIBETPAN
 #include <libetpan/mailstream_ssl.h>
 #endif
@@ -57,10 +57,17 @@ typedef struct _thread_data {
 } thread_data;
 #endif
 
+#if GNUTLS_VERSION_NUMBER <= 0x020c00
 static int gnutls_client_cert_cb(gnutls_session_t session,
                                const gnutls_datum_t *req_ca_rdn, int nreqs,
                                const gnutls_pk_algorithm_t *sign_algos,
                                int sign_algos_length, gnutls_retr_st *st)
+#else
+static int gnutls_cert_cb(gnutls_session_t session,
+                               const gnutls_datum_t *req_ca_rdn, int nreqs,
+                               const gnutls_pk_algorithm_t *sign_algos,
+                               int sign_algos_length, gnutls_retr2_st *st)
+#endif
 {
        SSLClientCertHookData hookdata;
        SockInfo *sockinfo = (SockInfo *)gnutls_session_get_ptr(session);
@@ -76,8 +83,10 @@ static int gnutls_client_cert_cb(gnutls_session_t session,
        hookdata.is_smtp = sockinfo->is_smtp;
        hooks_invoke(SSLCERT_GET_CLIENT_CERT_HOOKLIST, &hookdata);      
 
-       if (hookdata.cert_path == NULL)
+       if (hookdata.cert_path == NULL) {
+               g_free(hookdata.password);
                return 0;
+       }
 
        sockinfo->client_crt = ssl_certificate_get_x509_from_pem_file(hookdata.cert_path);
        sockinfo->client_key = ssl_certificate_get_pkey_from_pem_file(hookdata.cert_path);
@@ -91,17 +100,24 @@ static int gnutls_client_cert_cb(gnutls_session_t session,
 
        if (type == GNUTLS_CRT_X509 && sockinfo->client_crt && sockinfo->client_key) {
                st->ncerts = 1;
+#if GNUTLS_VERSION_NUMBER <= 0x020c00
                st->type = type;
+#else
+               st->key_type = type;
+#endif
                st->cert.x509 = &(sockinfo->client_crt);
                st->key.x509 = sockinfo->client_key;
                st->deinit_all = 0;
+               g_free(hookdata.password);
                return 0;
        }
+       g_free(hookdata.password);
        return 0;
 }
 
 const gchar *claws_ssl_get_cert_file(void)
 {
+#ifndef G_OS_WIN32
        const char *cert_files[]={
                "/etc/pki/tls/certs/ca-bundle.crt",
                "/etc/certs/ca-bundle.crt",
@@ -115,9 +131,12 @@ const gchar *claws_ssl_get_cert_file(void)
                "/usr/lib/ssl/cert.pem",
                NULL};
        int i;
-       
+#endif
+
+       /* We honor this environment variable on all platforms. */
        if (g_getenv("SSL_CERT_FILE"))
                return g_getenv("SSL_CERT_FILE");
+
 #ifndef G_OS_WIN32
        for (i = 0; cert_files[i]; i++) {
                if (is_file_exist(cert_files[i]))
@@ -125,7 +144,7 @@ const gchar *claws_ssl_get_cert_file(void)
        }
        return NULL;
 #else
-       return get_cert_file();
+       return w32_get_cert_file();
 #endif
 }
 
@@ -244,19 +263,59 @@ static gint SSL_connect_nb(gnutls_session_t ssl)
 #endif
 }
 
-gboolean ssl_init_socket(SockInfo *sockinfo)
+gnutls_x509_crt_t *ssl_get_certificate_chain(gnutls_session_t session, gint *list_len)
 {
-       return ssl_init_socket_with_method(sockinfo, SSL_METHOD_SSLv23);
+       const gnutls_datum_t *raw_cert_list;
+       gnutls_x509_crt_t *certs = NULL;
+       gboolean result = TRUE;
+
+       *list_len = -1;
+       if (!session)
+               return NULL;
+
+       raw_cert_list = gnutls_certificate_get_peers(session, list_len);
+
+       if (raw_cert_list && gnutls_certificate_type_get(session) == GNUTLS_CRT_X509) {
+               int i = 0;
+
+               if (*list_len > 128)
+                       *list_len = 128;
+
+               certs = g_malloc(sizeof(gnutls_x509_crt_t) * (*list_len));
+
+               for(i = 0 ; i < (*list_len) ; i++) {
+                       int r;
+
+                       gnutls_x509_crt_init(&certs[i]);
+                       r = gnutls_x509_crt_import(certs[i], &raw_cert_list[i], GNUTLS_X509_FMT_DER);
+                       if (r < 0) {
+                               g_warning("cert get failure: %d %s", r, gnutls_strerror(r));
+
+                               result = FALSE;
+                               i--;
+                               break;
+                       }
+               }
+               if (!result) {
+                       for (; i >= 0; i--)
+                               gnutls_x509_crt_deinit(certs[i]);
+
+                       g_free(certs);
+                       *list_len = -1;
+
+                       return NULL;
+               }
+       }
+
+       return certs;
 }
 
-gboolean ssl_init_socket_with_method(SockInfo *sockinfo, SSLMethod method)
+gboolean ssl_init_socket(SockInfo *sockinfo)
 {
        gnutls_session_t session;
-       int r;
-       const gnutls_datum_t *raw_cert_list;
-       unsigned int raw_cert_list_length;
-       gnutls_x509_crt_t cert = NULL;
-       guint status;
+       int r, i;
+       unsigned int cert_list_length;
+       gnutls_x509_crt_t *certs = NULL;
        gnutls_certificate_credentials_t xcred;
 
        if (gnutls_certificate_allocate_credentials (&xcred) != 0)
@@ -266,10 +325,16 @@ gboolean ssl_init_socket_with_method(SockInfo *sockinfo, SSLMethod method)
        if (session == NULL || r != 0)
                return FALSE;
 
-       if (method == 0)
-               gnutls_priority_set_direct(session, "NORMAL:-VERS-TLS1.0:-VERS-TLS1.1:-VERS-TLS1.2", NULL);
-       else
-               gnutls_priority_set_direct(session, "NORMAL", NULL);
+       if (sockinfo->gnutls_priority && strlen(sockinfo->gnutls_priority)) {
+               r = gnutls_priority_set_direct(session, sockinfo->gnutls_priority, NULL);
+               debug_print("Setting GnuTLS priority to %s, status = %d\n",
+                           sockinfo->gnutls_priority, r);
+       }
+#ifdef GNUTLS_VERSION_NUMBER < 0x030400
+       else {
+               gnutls_priority_set_direct(session, "NORMAL:-VERS-SSL3.0", NULL);
+       }
+#endif
        gnutls_record_disable_padding(session);
 
        gnutls_credentials_set(session, GNUTLS_CRD_CERTIFICATE, xcred);
@@ -277,7 +342,7 @@ gboolean ssl_init_socket_with_method(SockInfo *sockinfo, SSLMethod method)
        if (claws_ssl_get_cert_file()) {
                r = gnutls_certificate_set_x509_trust_file(xcred, claws_ssl_get_cert_file(),  GNUTLS_X509_FMT_PEM);
                if (r < 0)
-                       g_warning("Can't read SSL_CERT_FILE %s: %s\n",
+                       g_warning("Can't read SSL_CERT_FILE '%s': %s",
                                claws_ssl_get_cert_file(), 
                                gnutls_strerror(r));
        } else {
@@ -287,10 +352,11 @@ gboolean ssl_init_socket_with_method(SockInfo *sockinfo, SSLMethod method)
 
        gnutls_transport_set_ptr(session, (gnutls_transport_ptr_t) GINT_TO_POINTER(sockinfo->sock));
        gnutls_session_set_ptr(session, sockinfo);
-       /* TODO: gnutls_certificate_client_set_retrieve_function() is deprecated and should be replaced with
-        * gnutls_certificate_set_retrieve_function() which was introduced in gnutls 2.12 in March 2011
-        * getting this right with defines is not easy, so how long do we need compatibility to gntls <= 2.10? */
+#if GNUTLS_VERSION_NUMBER <= 0x020c00
        gnutls_certificate_client_set_retrieve_function(xcred, gnutls_client_cert_cb);
+#else
+       gnutls_certificate_set_retrieve_function(xcred, gnutls_cert_cb);
+#endif
 
        gnutls_dh_set_prime_bits(session, 512);
 
@@ -302,28 +368,27 @@ gboolean ssl_init_socket_with_method(SockInfo *sockinfo, SSLMethod method)
        }
 
        /* Get server's certificate (note: beware of dynamic allocation) */
-       raw_cert_list = gnutls_certificate_get_peers(session, &raw_cert_list_length);
+       certs = ssl_get_certificate_chain(session, &cert_list_length);
 
-       if (!raw_cert_list 
-       ||  gnutls_certificate_type_get(session) != GNUTLS_CRT_X509
-       ||  (r = gnutls_x509_crt_init(&cert)) < 0
-       ||  (r = gnutls_x509_crt_import(cert, &raw_cert_list[0], GNUTLS_X509_FMT_DER)) < 0) {
-               g_warning("cert get failure: %d %s\n", r, gnutls_strerror(r));
+       if (!certs) {
                gnutls_certificate_free_credentials(xcred);
                gnutls_deinit(session);
                return FALSE;
        }
 
-       r = gnutls_certificate_verify_peers2(session, &status);
-
-       if (r < 0 || !ssl_certificate_check(cert, status, sockinfo->hostname, sockinfo->port)) {
-               gnutls_x509_crt_deinit(cert);
+       if (!ssl_certificate_check_chain(certs, cert_list_length, sockinfo->hostname, sockinfo->port,
+                                        sockinfo->ssl_cert_auto_accept)) {
+               for (i = 0; i < cert_list_length; i++)
+                       gnutls_x509_crt_deinit(certs[i]);
+               g_free(certs);
                gnutls_certificate_free_credentials(xcred);
                gnutls_deinit(session);
                return FALSE;
        }
 
-       gnutls_x509_crt_deinit(cert);
+       for (i = 0; i < cert_list_length; i++)
+               gnutls_x509_crt_deinit(certs[i]);
+       g_free(certs);
 
        sockinfo->ssl = session;
        sockinfo->xcred = xcred;
@@ -333,7 +398,8 @@ gboolean ssl_init_socket_with_method(SockInfo *sockinfo, SSLMethod method)
 void ssl_done_socket(SockInfo *sockinfo)
 {
        if (sockinfo && sockinfo->ssl) {
-               gnutls_certificate_free_credentials(sockinfo->xcred);
+               if (sockinfo->xcred)
+                       gnutls_certificate_free_credentials(sockinfo->xcred);
                gnutls_deinit(sockinfo->ssl);
                if (sockinfo->client_crt)
                        gnutls_x509_crt_deinit(sockinfo->client_crt);
@@ -341,6 +407,7 @@ void ssl_done_socket(SockInfo *sockinfo)
                        gnutls_x509_privkey_deinit(sockinfo->client_key);
                sockinfo->client_key = NULL;
                sockinfo->client_crt = NULL;
+               sockinfo->xcred = NULL;
                sockinfo->ssl = NULL;
        }
 }