Do not explicitly disable SSLv3 if GnuTLS does it already.
[claws.git] / src / common / ssl.c
index bdf2b3e9a893248296e4ef54f747b5a1bdf2e66f..e25a42301fb73752a1d71b1cd7b6f1108ab31ce8 100644 (file)
@@ -1,6 +1,6 @@
 /*
  * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
- * Copyright (C) 1999-2007 Hiroyuki Yamamoto and the Claws Mail team
+ * Copyright (C) 1999-2012 Hiroyuki Yamamoto and the Claws Mail team
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
 
 #ifdef HAVE_CONFIG_H
 #  include "config.h"
+#include "claws-features.h"
 #endif
 
-#if (defined(USE_OPENSSL) || defined (USE_GNUTLS))
+#ifdef USE_GNUTLS
 #include "defs.h"
 
+#include <stdlib.h>
 #include <glib.h>
 #include <glib/gi18n.h>
+#include <errno.h>
+#include <pthread.h>
 
 #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>
 
 #ifdef USE_PTHREAD
 typedef struct _thread_data {
-#ifdef USE_OPENSSL
-       SSL *ssl;
-#else
-       gnutls_session ssl;
-#endif
+       gnutls_session_t ssl;
        gboolean done;
 } 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);
+       gnutls_certificate_type_t type = gnutls_certificate_type_get(session);
+       gnutls_x509_crt_t crt;
+       gnutls_x509_privkey_t key;
+
+       st->ncerts = 0;
+
+       hookdata.account = sockinfo->account;
+       hookdata.cert_path = NULL;
+       hookdata.password = NULL;
+       hookdata.is_smtp = sockinfo->is_smtp;
+       hooks_invoke(SSLCERT_GET_CLIENT_CERT_HOOKLIST, &hookdata);      
+
+       if (hookdata.cert_path == NULL) {
+               g_free(hookdata.password);
+               return 0;
+       }
 
-#ifdef USE_OPENSSL
-static SSL_CTX *ssl_ctx;
+       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);
+       if (!(sockinfo->client_crt && sockinfo->client_key)) {
+               /* try pkcs12 format */
+               ssl_certificate_get_x509_and_pkey_from_p12_file(hookdata.cert_path, hookdata.password, 
+                       &crt, &key);
+               sockinfo->client_crt = crt;
+               sockinfo->client_key = key;
+       }
+
+       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;
+}
 
-void ssl_init(void)
+const gchar *claws_ssl_get_cert_file(void)
 {
-#ifdef USE_OPENSSL
-       SSL_METHOD *meth;
+#ifndef G_OS_WIN32
+       const char *cert_files[]={
+               "/etc/pki/tls/certs/ca-bundle.crt",
+               "/etc/certs/ca-bundle.crt",
+               "/etc/ssl/ca-bundle.pem",
+               "/usr/share/ssl/certs/ca-bundle.crt",
+               "/etc/ssl/certs/ca-certificates.crt",
+               "/usr/local/ssl/certs/ca-bundle.crt",
+               "/etc/apache/ssl.crt/ca-bundle.crt",
+               "/usr/share/curl/curl-ca-bundle.crt",
+               "/usr/share/curl/curl-ca-bundle.crt",
+               "/usr/lib/ssl/cert.pem",
+               NULL};
+       int i;
+#endif
 
-       /* Global system initialization*/
-       SSL_library_init();
-       SSL_load_error_strings();
+       /* We honor this environment variable on all platforms. */
+       if (g_getenv("SSL_CERT_FILE"))
+               return g_getenv("SSL_CERT_FILE");
 
-#ifdef HAVE_LIBETPAN
-       mailstream_openssl_init_not_required();
-#endif 
+#ifndef G_OS_WIN32
+       for (i = 0; cert_files[i]; i++) {
+               if (is_file_exist(cert_files[i]))
+                       return cert_files[i];
+       }
+       return NULL;
+#else
+       return w32_get_cert_file();
+#endif
+}
 
-       /* Create our context*/
-       meth = SSLv23_client_method();
-       ssl_ctx = SSL_CTX_new(meth);
+const gchar *claws_ssl_get_cert_dir(void)
+{
+       const char *cert_dirs[]={
+               "/etc/pki/tls/certs",
+               "/etc/certs",
+               "/usr/share/ssl/certs",
+               "/etc/ssl/certs",
+               "/usr/local/ssl/certs",
+               "/etc/apache/ssl.crt",
+               "/usr/share/curl",
+               "/usr/lib/ssl/certs",
+               NULL};
+       int i;
+       
+       if (g_getenv("SSL_CERT_DIR"))
+               return g_getenv("SSL_CERT_DIR");
+#ifndef G_OS_WIN32
+       for (i = 0; cert_dirs[i]; i++) {
+               if (is_dir_exist(cert_dirs[i]))
+                       return cert_dirs[i];
+       }
+       return NULL;
+#else
+       return NULL;
+#endif
+}
 
-       /* Set default certificate paths */
-       SSL_CTX_set_default_verify_paths(ssl_ctx);
-       
-#if (OPENSSL_VERSION_NUMBER < 0x0090600fL)
-       SSL_CTX_set_verify_depth(ssl_ctx,1);
+void ssl_init(void)
+{
+#if GNUTLS_VERSION_NUMBER <= 0x020b00
+       gcry_control (GCRYCTL_SET_THREAD_CBS, &gcry_threads_pthread);
 #endif
-#else
+#ifdef HAVE_LIBETPAN
+       mailstream_gnutls_init_not_required();
+#endif 
        gnutls_global_init();
-#endif
 }
 
 void ssl_done(void)
 {
-#if USE_OPENSSL
-       if (!ssl_ctx)
-               return;
-       
-       SSL_CTX_free(ssl_ctx);
-#else
        gnutls_global_deinit();
-#endif
 }
 
 #ifdef USE_PTHREAD
@@ -105,31 +200,23 @@ static void *SSL_connect_thread(void *data)
        pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
        pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL);
 
-#ifdef USE_OPENSSL
-       result = SSL_connect(td->ssl);
-#else
        do {
                result = gnutls_handshake(td->ssl);
        } while (result == GNUTLS_E_AGAIN || result == GNUTLS_E_INTERRUPTED);
-#endif
+
        td->done = TRUE; /* let the caller thread join() */
        return GINT_TO_POINTER(result);
 }
 #endif
 
-#ifdef USE_OPENSSL
-static gint SSL_connect_nb(SSL *ssl)
-#else
-static gint SSL_connect_nb(gnutls_session ssl)
-#endif
+static gint SSL_connect_nb(gnutls_session_t ssl)
 {
-#if (defined USE_PTHREAD && ((defined __GLIBC__ && (__GLIBC__ > 2 || (__GLIBC__ == 2 && __GLIBC_MINOR__ >= 3))) || !defined __GLIBC__))
+       int result;
+#ifdef USE_PTHREAD
        thread_data *td = g_new0(thread_data, 1);
        pthread_t pt;
+       pthread_attr_t pta;
        void *res = NULL;
-#ifdef USE_GNUTLS
-       int result;
-#endif
        time_t start_time = time(NULL);
        gboolean killed = FALSE;
        
@@ -139,15 +226,13 @@ static gint SSL_connect_nb(gnutls_session ssl)
        /* try to create a thread to initialize the SSL connection,
         * fallback to blocking method in case of problem 
         */
-       if (pthread_create(&pt, PTHREAD_CREATE_JOINABLE, 
-                       SSL_connect_thread, td) != 0) {
-#ifdef USE_OPENSSL
-               return SSL_connect(ssl);
-#else
+       if (pthread_attr_init(&pta) != 0 ||
+           pthread_attr_setdetachstate(&pta, PTHREAD_CREATE_JOINABLE) != 0 ||
+           pthread_create(&pt, &pta, SSL_connect_thread, td) != 0) {
                do {
                        result = gnutls_handshake(td->ssl);
                } while (result == GNUTLS_E_AGAIN || result == GNUTLS_E_INTERRUPTED);
-#endif
+               return result;
        }
        debug_print("waiting for SSL_connect thread...\n");
        while(!td->done) {
@@ -171,107 +256,66 @@ static gint SSL_connect_nb(gnutls_session ssl)
                        GPOINTER_TO_INT(res));
        
        return GPOINTER_TO_INT(res);
-#else
-#ifdef USE_OPENSSL
-       return SSL_connect(ssl);
-#else
+#else /* USE_PTHREAD */
        do {
-               result = gnutls_handshake(td->ssl);
+               result = gnutls_handshake(ssl);
        } while (result == GNUTLS_E_AGAIN || result == GNUTLS_E_INTERRUPTED);
 #endif
-#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;
 
-#ifdef USE_GNUTLS
-static const gchar *ssl_get_cert_file(void)
-{
-       if (g_getenv("SSL_CERT_FILE"))
-               return g_getenv("SSL_CERT_FILE");
-#ifndef G_OS_WIN32
-       return "/etc/ssl/certs/ca-certificates.crt";
-#else
-       return "put_what_s_needed_here";
-#endif
-}
-#endif
+       *list_len = -1;
+       if (!session)
+               return NULL;
 
-gboolean ssl_init_socket_with_method(SockInfo *sockinfo, SSLMethod method)
-{
-#ifdef USE_OPENSSL
-       X509 *server_cert;
-       SSL *ssl;
+       raw_cert_list = gnutls_certificate_get_peers(session, list_len);
 
-       ssl = SSL_new(ssl_ctx);
-       if (ssl == NULL) {
-               g_warning(_("Error creating ssl context\n"));
-               return FALSE;
-       }
+       if (raw_cert_list && gnutls_certificate_type_get(session) == GNUTLS_CRT_X509) {
+               int i = 0;
 
-       switch (method) {
-       case SSL_METHOD_SSLv23:
-               debug_print("Setting SSLv23 client method\n");
-               SSL_set_ssl_method(ssl, SSLv23_client_method());
-               break;
-       case SSL_METHOD_TLSv1:
-               debug_print("Setting TLSv1 client method\n");
-               SSL_set_ssl_method(ssl, TLSv1_client_method());
-               break;
-       default:
-               break;
-       }
+               if (*list_len > 128)
+                       *list_len = 128;
 
-       SSL_set_fd(ssl, sockinfo->sock);
-       if (SSL_connect_nb(ssl) == -1) {
-               g_warning(_("SSL connect failed (%s)\n"),
-                           ERR_error_string(ERR_get_error(), NULL));
-               SSL_free(ssl);
-               return FALSE;
-       }
+               certs = g_malloc(sizeof(gnutls_x509_crt_t) * (*list_len));
 
-       /* Get the cipher */
+               for(i = 0 ; i < (*list_len) ; i++) {
+                       int r;
 
-       debug_print("SSL connection using %s\n", SSL_get_cipher(ssl));
+                       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));
 
-       /* Get server's certificate (note: beware of dynamic allocation) */
-       if ((server_cert = SSL_get_peer_certificate(ssl)) == NULL) {
-               debug_print("server_cert is NULL ! this _should_not_ happen !\n");
-               SSL_free(ssl);
-               return FALSE;
-       }
+                               result = FALSE;
+                               i--;
+                               break;
+                       }
+               }
+               if (!result) {
+                       for (; i >= 0; i--)
+                               gnutls_x509_crt_deinit(certs[i]);
 
+                       g_free(certs);
+                       *list_len = -1;
 
-       if (!ssl_certificate_check(server_cert, sockinfo->canonical_name, sockinfo->hostname, sockinfo->port)) {
-               X509_free(server_cert);
-               SSL_free(ssl);
-               return FALSE;
+                       return NULL;
+               }
        }
 
+       return certs;
+}
 
-       X509_free(server_cert);
-       sockinfo->ssl = ssl;
-#else
-       gnutls_session session;
-       int r;
-       const int cipher_prio[] = { GNUTLS_CIPHER_AES_128_CBC,
-                               GNUTLS_CIPHER_3DES_CBC,
-                               GNUTLS_CIPHER_AES_256_CBC,
-                               GNUTLS_CIPHER_ARCFOUR_128, 0 };
-       const int kx_prio[] = { GNUTLS_KX_DHE_RSA,
-                          GNUTLS_KX_RSA, 
-                          GNUTLS_KX_DHE_DSS, 0 };
-       const int mac_prio[] = { GNUTLS_MAC_SHA1,
-                               GNUTLS_MAC_MD5, 0 };
-       const int proto_prio[] = { GNUTLS_TLS1,
-                                 GNUTLS_SSL3, 0 };
-       const gnutls_datum *raw_cert_list;
-       unsigned int raw_cert_list_length;
-       gnutls_x509_crt cert = NULL;
-       guint status;
+gboolean ssl_init_socket(SockInfo *sockinfo)
+{
+       gnutls_session_t session;
+       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)
@@ -280,69 +324,92 @@ gboolean ssl_init_socket_with_method(SockInfo *sockinfo, SSLMethod method)
        r = gnutls_init(&session, GNUTLS_CLIENT);
        if (session == NULL || r != 0)
                return FALSE;
-  
-       gnutls_set_default_priority(session);
-       gnutls_protocol_set_priority (session, proto_prio);
-       gnutls_cipher_set_priority (session, cipher_prio);
-       gnutls_kx_set_priority (session, kx_prio);
-       gnutls_mac_set_priority (session, mac_prio);
 
-       gnutls_credentials_set(session, GNUTLS_CRD_CERTIFICATE, xcred);
+       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);
 
-       r = gnutls_certificate_set_x509_trust_file(xcred, ssl_get_cert_file(),  GNUTLS_X509_FMT_PEM);
-       if (r < 0)
-               g_warning("Can't read SSL_CERT_FILE %s: %s\n",
-                       ssl_get_cert_file(), 
-                       gnutls_strerror(r));
+       gnutls_credentials_set(session, GNUTLS_CRD_CERTIFICATE, xcred);
 
+       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",
+                               claws_ssl_get_cert_file(), 
+                               gnutls_strerror(r));
+       } else {
+               debug_print("Can't find SSL ca-certificates file\n");
+       }
        gnutls_certificate_set_verify_flags (xcred, GNUTLS_VERIFY_ALLOW_X509_V1_CA_CRT);
 
-       gnutls_transport_set_ptr(session, (gnutls_transport_ptr) 
-               sockinfo->sock);
+       gnutls_transport_set_ptr(session, (gnutls_transport_ptr_t) GINT_TO_POINTER(sockinfo->sock));
+       gnutls_session_set_ptr(session, sockinfo);
+#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);
 
        if ((r = SSL_connect_nb(session)) < 0) {
                g_warning("SSL connection failed (%s)", gnutls_strerror(r));
+               gnutls_certificate_free_credentials(xcred);
                gnutls_deinit(session);
                return FALSE;
        }
 
        /* 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 (!ssl_certificate_check(cert, status, sockinfo->canonical_name, 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;
-#endif
+       sockinfo->xcred = xcred;
        return TRUE;
 }
 
 void ssl_done_socket(SockInfo *sockinfo)
 {
        if (sockinfo && sockinfo->ssl) {
-#ifdef USE_OPENSSL
-               SSL_free(sockinfo->ssl);
-#else
+               if (sockinfo->xcred)
+                       gnutls_certificate_free_credentials(sockinfo->xcred);
                gnutls_deinit(sockinfo->ssl);
-#endif
+               if (sockinfo->client_crt)
+                       gnutls_x509_crt_deinit(sockinfo->client_crt);
+               if (sockinfo->client_key)
+                       gnutls_x509_privkey_deinit(sockinfo->client_key);
+               sockinfo->client_key = NULL;
+               sockinfo->client_crt = NULL;
+               sockinfo->xcred = NULL;
                sockinfo->ssl = NULL;
        }
 }
 
-#endif /* USE_OPENSSL */
+#endif /* USE_GNUTLS */