Add OpenBSD CA cert path.
[claws.git] / src / common / ssl.c
index 7e43c1b13df32ad1af8addb6039b2c23e9c3006e..bc8ab7de7bcf03424dcea7909dc20923d9da64cf 100644 (file)
@@ -19,6 +19,7 @@
 
 #ifdef HAVE_CONFIG_H
 #  include "config.h"
+#include "claws-features.h"
 #endif
 
 #ifdef USE_GNUTLS
 #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
@@ -51,21 +52,34 @@ GCRY_THREAD_OPTION_PTHREAD_IMPL;
 
 #ifdef USE_PTHREAD
 typedef struct _thread_data {
-       gnutls_session ssl;
+       gnutls_session_t ssl;
        gboolean done;
 } thread_data;
 #endif
 
-static int gnutls_client_cert_cb(gnutls_session session,
-                               const gnutls_datum *req_ca_rdn, int nreqs,
-                               const gnutls_pk_algorithm *sign_algos,
+#if GNUTLS_VERSION_NUMBER < 0x030400
+#define DEFAULT_GNUTLS_PRIORITY "NORMAL:-VERS-SSL3.0"
+#else
+#define DEFAULT_GNUTLS_PRIORITY "NORMAL"
+#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 type = gnutls_certificate_type_get(session);
-       gnutls_x509_crt crt;
-       gnutls_x509_privkey key;
+       gnutls_certificate_type_t type = gnutls_certificate_type_get(session);
+       gnutls_x509_crt_t crt;
+       gnutls_x509_privkey_t key;
 
        st->ncerts = 0;
 
@@ -75,8 +89,10 @@ static int gnutls_client_cert_cb(gnutls_session 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);
@@ -90,20 +106,29 @@ static int gnutls_client_cert_cb(gnutls_session 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/ssl/cert.pem",
                "/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",
@@ -113,9 +138,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]))
@@ -123,7 +151,7 @@ const gchar *claws_ssl_get_cert_file(void)
        }
        return NULL;
 #else
-       return get_cert_file();
+       return w32_get_cert_file();
 #endif
 }
 
@@ -150,7 +178,7 @@ const gchar *claws_ssl_get_cert_dir(void)
        }
        return NULL;
 #else
-       return "put_what_s_needed_here";
+       return NULL;
 #endif
 }
 
@@ -188,7 +216,7 @@ static void *SSL_connect_thread(void *data)
 }
 #endif
 
-static gint SSL_connect_nb(gnutls_session ssl)
+static gint SSL_connect_nb(gnutls_session_t ssl)
 {
        int result;
 #ifdef USE_PTHREAD
@@ -242,19 +270,59 @@ static gint SSL_connect_nb(gnutls_session 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 session;
-       int r;
-       const gnutls_datum *raw_cert_list;
-       unsigned int raw_cert_list_length;
-       gnutls_x509_crt cert = NULL;
-       guint status;
+       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)
@@ -264,13 +332,15 @@ gboolean ssl_init_socket_with_method(SockInfo *sockinfo, SSLMethod method)
        if (session == NULL || r != 0)
                return FALSE;
 
-#if GNUTLS_VERSION_NUMBER < 0x030003
-       gnutls_transport_set_lowat (session, 0); 
-#endif
-       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);
+       }
+       else {
+               gnutls_priority_set_direct(session, DEFAULT_GNUTLS_PRIORITY, NULL);
+       }
+
        gnutls_record_disable_padding(session);
 
        gnutls_credentials_set(session, GNUTLS_CRD_CERTIFICATE, xcred);
@@ -278,7 +348,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 {
@@ -286,9 +356,13 @@ gboolean ssl_init_socket_with_method(SockInfo *sockinfo, SSLMethod method)
        }
        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);
 
@@ -300,28 +374,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 (!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;
@@ -331,7 +404,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);
@@ -339,6 +413,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;
        }
 }