From: Gu1ll4um3r0m41n <aeroxteam@gmail.com>
Description: Fix verification of SSL certificates by calling gnutls verify
    callback, see CVE-2011-1428.
Origin: upstream, http://git.savannah.gnu.org/gitweb/?p=weechat.git;a=commit;h=c265cad1c95b84abfd4e8d861f25926ef13b5d91
Bug: http://savannah.nongnu.org/patch/?7459
Forwarded: not-needed
Last-Update: 2012-12-29
--- a/src/core/wee-hook.c
+++ b/src/core/wee-hook.c
@@ -1558,7 +1558,40 @@
 }
 
 /*
- * hook_connect_gnutls_set_certificates: set gnutls
+ * hook_connect_gnutls_verify_certificates: verify certificates
+ */
+
+#ifdef HAVE_GNUTLS
+int
+hook_connect_gnutls_verify_certificates (gnutls_session_t tls_session)
+{
+    struct t_hook *ptr_hook;
+    int rc;
+    
+    rc = -1;
+    ptr_hook = weechat_hooks[HOOK_TYPE_CONNECT];
+    while (ptr_hook)
+    {
+        /* looking for the right hook using to the gnutls session pointer */
+        if (!ptr_hook->deleted
+            && HOOK_CONNECT(ptr_hook, gnutls_sess)
+            && (*(HOOK_CONNECT(ptr_hook, gnutls_sess)) == tls_session))
+        {
+            rc = (int) (HOOK_CONNECT(ptr_hook, gnutls_cb))
+                (ptr_hook->callback_data, tls_session, NULL, 0,
+                 NULL, 0, NULL,
+                 WEECHAT_HOOK_CONNECT_GNUTLS_CB_VERIFY_CERT);
+            break;
+        }
+        ptr_hook = ptr_hook->next_hook;
+    }
+    
+    return rc;
+}
+#endif
+
+/*
+ * hook_connect_gnutls_set_certificates: set certificates
  */
 
 #ifdef HAVE_GNUTLS
@@ -1583,7 +1616,8 @@
         {
             rc = (int) (HOOK_CONNECT(ptr_hook, gnutls_cb))
                 (ptr_hook->callback_data, tls_session, req_ca, nreq,
-                 pk_algos, pk_algos_len, answer);
+                 pk_algos, pk_algos_len, answer,
+                 WEECHAT_HOOK_CONNECT_GNUTLS_CB_SET_CERT);
             break;
         }
         ptr_hook = ptr_hook->next_hook;
--- a/src/core/wee-hook.h
+++ b/src/core/wee-hook.h
@@ -189,7 +189,8 @@
 typedef int (gnutls_callback_t)(void *data, gnutls_session_t tls_session,
                                 const gnutls_datum_t *req_ca, int nreq,
                                 const gnutls_pk_algorithm_t *pk_algos,
-                                int pk_algos_len, gnutls_retr_st *answer);
+                                int pk_algos_len, gnutls_retr_st *answer,
+                                int action);
 #endif
 
 struct t_hook_connect
@@ -364,6 +365,7 @@
                                     t_hook_callback_connect *callback,
                                     void *callback_data);
 #ifdef HAVE_GNUTLS
+extern int hook_connect_gnutls_verify_certificates (gnutls_session_t tls_session);
 extern int hook_connect_gnutls_set_certificates (gnutls_session_t tls_session,
                                                  const gnutls_datum_t *req_ca, int nreq,
                                                  const gnutls_pk_algorithm_t *pk_algos,
--- a/src/core/wee-network.c
+++ b/src/core/wee-network.c
@@ -65,7 +65,6 @@
 #endif
 #endif
 
-
 /*
  * network_init: init network
  */
@@ -92,6 +91,10 @@
         }
         free (ca_path);
     }
+#if LIBGNUTLS_VERSION_NUMBER >= 0x02090a
+    gnutls_certificate_set_verify_function (gnutls_xcred,
+                                            &hook_connect_gnutls_verify_certificates);
+#endif
     gnutls_certificate_client_set_retrieve_function (gnutls_xcred,
                                                      &hook_connect_gnutls_set_certificates);
     network_init_ok = 1;
@@ -807,6 +810,26 @@
                         free (ip_address);
                     return WEECHAT_RC_OK;
                 }
+#if LIBGNUTLS_VERSION_NUMBER < 0x02090a
+                /*
+                 * gnutls only has the gnutls_certificate_set_verify_function()
+                 * function since version 2.9.10. We need to call our verify
+                 * function manually after the handshake for old gnutls versions
+                 */
+                if (hook_connect_gnutls_verify_certificates (*HOOK_CONNECT(hook_connect, gnutls_sess)) != 0)
+                {
+                    (void) (HOOK_CONNECT(hook_connect, callback))
+                        (hook_connect->callback_data,
+                         WEECHAT_HOOK_CONNECT_GNUTLS_HANDSHAKE_ERROR,
+                         rc,
+                         "Error in the certificate.",
+                         ip_address);
+                    unhook (hook_connect);
+                    if (ip_address)
+                        free (ip_address);
+                    return WEECHAT_RC_OK;
+                }
+#endif
             }
 #endif
         }
--- a/src/plugins/irc/irc-server.c
+++ b/src/plugins/irc/irc-server.c
@@ -2384,7 +2384,8 @@
 irc_server_gnutls_callback (void *data, gnutls_session_t tls_session,
                             const gnutls_datum_t *req_ca, int nreq,
                             const gnutls_pk_algorithm_t *pk_algos,
-                            int pk_algos_len, gnutls_retr_st *answer)
+                            int pk_algos_len, gnutls_retr_st *answer,
+                            int action)
 {
     struct t_irc_server *server;
     gnutls_retr_st tls_struct;
@@ -2395,7 +2396,7 @@
     time_t cert_time;
     char *cert_path0, *cert_path1, *cert_path2, *cert_str, *hostname;
     const char *weechat_dir;
-    int rc, i, j, hostname_match;
+    int rc, ret, i, j, hostname_match;
 #if LIBGNUTLS_VERSION_NUMBER >= 0x010706
     gnutls_datum_t cinfo;
     int rinfo;
@@ -2416,187 +2417,214 @@
     hostname = server->addresses_array[server->index_current_address];
     hostname_match = 0;
     
-    weechat_printf (server->buffer,
-                    _("gnutls: connected using %d-bit Diffie-Hellman shared "
-                      "secret exchange"),
-                    IRC_SERVER_OPTION_INTEGER (server,
-                                               IRC_SERVER_OPTION_SSL_DHKEY_SIZE));
-    if (gnutls_certificate_verify_peers2 (tls_session, &status) < 0)
+    if (action == WEECHAT_HOOK_CONNECT_GNUTLS_CB_VERIFY_CERT)
     {
         weechat_printf (server->buffer,
-                        _("%sgnutls: error while checking peer's certificate"),
-                        weechat_prefix ("error"));
-        rc = -1;
-    }
-    else
-    {
-        /* some checks */
-        if (status & GNUTLS_CERT_INVALID)
+                        _("gnutls: connected using %d-bit Diffie-Hellman shared "
+                          "secret exchange"),
+                        IRC_SERVER_OPTION_INTEGER (server,
+                                                   IRC_SERVER_OPTION_SSL_DHKEY_SIZE));
+        if (gnutls_certificate_verify_peers2 (tls_session, &status) < 0)
         {
             weechat_printf (server->buffer,
-                            _("%sgnutls: peer's certificate is NOT trusted"),
+                            _("%sgnutls: error while checking peer's certificate"),
                             weechat_prefix ("error"));
             rc = -1;
         }
         else
         {
-            weechat_printf (server->buffer,
-                            _("gnutls: peer's certificate is trusted"));
-        }
-        if (status & GNUTLS_CERT_SIGNER_NOT_FOUND)
-        {
-            weechat_printf (server->buffer,
-                            _("%sgnutls: peer's certificate issuer is unknown"),
-                            weechat_prefix ("error"));
-            rc = -1;
-        }
-        if (status & GNUTLS_CERT_REVOKED)
-        {
-            weechat_printf (server->buffer,
-                            _("%sgnutls: the certificate has been revoked"),
-                            weechat_prefix ("error"));
-            rc = -1;
-        }
-        
-        /* check certificates */
-        if (gnutls_x509_crt_init (&cert_temp) >= 0)
-        {
-            cert_list = gnutls_certificate_get_peers (tls_session, &cert_list_len);
-            if (cert_list)
+            /* some checks */
+            if (status & GNUTLS_CERT_INVALID)
             {
                 weechat_printf (server->buffer,
-                                NG_("gnutls: receiving %d certificate",
-                                    "gnutls: receiving %d certificates",
-                                    cert_list_len),
-                                cert_list_len);
-                for (i = 0, j = (int) cert_list_len; i < j; i++)
+                                _("%sgnutls: peer's certificate is NOT trusted"),
+                                weechat_prefix ("error"));
+                rc = -1;
+            }
+            else
+            {
+                weechat_printf (server->buffer,
+                                _("gnutls: peer's certificate is trusted"));
+            }
+            if (status & GNUTLS_CERT_SIGNER_NOT_FOUND)
+            {
+                weechat_printf (server->buffer,
+                                _("%sgnutls: peer's certificate issuer is unknown"),
+                                weechat_prefix ("error"));
+                rc = -1;
+            }
+            if (status & GNUTLS_CERT_REVOKED)
+            {
+                weechat_printf (server->buffer,
+                                _("%sgnutls: the certificate has been revoked"),
+                                weechat_prefix ("error"));
+                rc = -1;
+            }
+            
+            /* check certificates */
+            if (gnutls_x509_crt_init (&cert_temp) >= 0)
+            {
+                cert_list = gnutls_certificate_get_peers (tls_session, &cert_list_len);
+                if (cert_list)
                 {
-                    if (gnutls_x509_crt_import (cert_temp, &cert_list[i], GNUTLS_X509_FMT_DER) >= 0)
+                    weechat_printf (server->buffer,
+                                    NG_("gnutls: receiving %d certificate",
+                                        "gnutls: receiving %d certificates",
+                                        cert_list_len),
+                                    cert_list_len);
+                    for (i = 0, j = (int) cert_list_len; i < j; i++)
                     {
-                        /* checking if hostname matches in the first certificate */
-                        if (i == 0 && gnutls_x509_crt_check_hostname (cert_temp, hostname) != 0)
+                        if (gnutls_x509_crt_import (cert_temp, &cert_list[i], GNUTLS_X509_FMT_DER) >= 0)
                         {
-                            hostname_match = 1;
-                        }
+                            /* checking if hostname matches in the first certificate */
+                            if ((i == 0) && (gnutls_x509_crt_check_hostname (cert_temp, hostname) != 0))
+                            {
+                                hostname_match = 1;
+                            }
 #if LIBGNUTLS_VERSION_NUMBER >= 0x010706
-                        /* displaying infos about certificate */
+                            /* displaying infos about certificate */
 #if LIBGNUTLS_VERSION_NUMBER < 0x020400
-                        rinfo = gnutls_x509_crt_print (cert_temp, GNUTLS_X509_CRT_ONELINE, &cinfo);
+                            rinfo = gnutls_x509_crt_print (cert_temp, GNUTLS_X509_CRT_ONELINE, &cinfo);
 #else
-                        rinfo = gnutls_x509_crt_print (cert_temp, GNUTLS_CRT_PRINT_ONELINE, &cinfo);
+                            rinfo = gnutls_x509_crt_print (cert_temp, GNUTLS_CRT_PRINT_ONELINE, &cinfo);
 #endif
-                        if (rinfo == 0)
-                        {
-                            weechat_printf (server->buffer,
-                                            _(" - certificate[%d] info:"), i + 1);
-                            weechat_printf (server->buffer,
-                                            "   - %s", cinfo.data);
-                            gnutls_free (cinfo.data);
-                        }
+                            if (rinfo == 0)
+                            {
+                                weechat_printf (server->buffer,
+                                                _(" - certificate[%d] info:"), i + 1);
+                                weechat_printf (server->buffer,
+                                                "   - %s", cinfo.data);
+                                gnutls_free (cinfo.data);
+                            }
 #endif
-                        /* check expiration date */
-                        cert_time = gnutls_x509_crt_get_expiration_time (cert_temp);
-                        if (cert_time < time(NULL))
-                        {
-                            weechat_printf (server->buffer,
-                                            _("%sgnutls: certificate has expired"),
-                                            weechat_prefix ("error"));
-                            rc = -1;
-                        }
-                        /* check expiration date */
-                        cert_time = gnutls_x509_crt_get_activation_time (cert_temp);
-                        if (cert_time > time(NULL))
-                        {
-                            weechat_printf (server->buffer,
-                                            _("%sgnutls: certificate is not yet activated"),
-                                            weechat_prefix ("error"));
-                            rc = -1;
+                            /* check expiration date */
+                            cert_time = gnutls_x509_crt_get_expiration_time (cert_temp);
+                            if (cert_time < time (NULL))
+                            {
+                                weechat_printf (server->buffer,
+                                                _("%sgnutls: certificate has expired"),
+                                                weechat_prefix ("error"));
+                                rc = -1;
+                            }
+                            /* check activation date */
+                            cert_time = gnutls_x509_crt_get_activation_time (cert_temp);
+                            if (cert_time > time (NULL))
+                            {
+                                weechat_printf (server->buffer,
+                                                _("%sgnutls: certificate is not yet activated"),
+                                                weechat_prefix ("error"));
+                                rc = -1;
+                            }
                         }
                     }
-                }
-                if (hostname_match == 0)
-                {
-                    weechat_printf (server->buffer,
-                                    _("%sgnutls: the hostname in the "
-                                      "certificate does NOT match \"%s\""),
-                                    weechat_prefix ("error"), hostname);
-                    rc = -1;
+                    if (hostname_match == 0)
+                    {
+                        weechat_printf (server->buffer,
+                                        _("%sgnutls: the hostname in the "
+                                          "certificate does NOT match \"%s\""),
+                                        weechat_prefix ("error"), hostname);
+                        rc = -1;
+                    }
                 }
             }
         }
     }
-    
-    /* using client certificate if it exists */
-    cert_path0 = (char *) IRC_SERVER_OPTION_STRING(server,
-                                                   IRC_SERVER_OPTION_SSL_CERT);
-    if (cert_path0 && cert_path0[0])
+    else if (action == WEECHAT_HOOK_CONNECT_GNUTLS_CB_SET_CERT)
     {
-        weechat_dir = weechat_info_get ("weechat_dir", "");
-        cert_path1 = weechat_string_replace (cert_path0, "%h", weechat_dir);
-        cert_path2 = (cert_path1) ?
-            weechat_string_replace (cert_path1, "~", getenv ("HOME")) : NULL;
-
-        if (cert_path2)
-        {
-            cert_str = weechat_file_get_content (cert_path2);
-            if (cert_str)
+        /* using client certificate if it exists */
+        cert_path0 = (char *) IRC_SERVER_OPTION_STRING(server,
+                                                       IRC_SERVER_OPTION_SSL_CERT);
+        if (cert_path0 && cert_path0[0])
+        {
+            weechat_dir = weechat_info_get ("weechat_dir", "");
+            cert_path1 = weechat_string_replace (cert_path0, "%h", weechat_dir);
+            cert_path2 = (cert_path1) ?
+                weechat_string_replace (cert_path1, "~", getenv ("HOME")) : NULL;
+            
+            if (cert_path2)
             {
-                weechat_printf (server->buffer,
-                                _("gnutls: sending one certificate"));
-                
-                filedatum.data = (unsigned char *) cert_str;
-                filedatum.size = strlen (cert_str);
-                
-                /* certificate */
-                gnutls_x509_crt_init (&server->tls_cert);
-                gnutls_x509_crt_import (server->tls_cert, &filedatum,
-                                        GNUTLS_X509_FMT_PEM);
-                
-                /* key */
-                gnutls_x509_privkey_init (&server->tls_cert_key);
-                gnutls_x509_privkey_import (server->tls_cert_key, &filedatum,
+                cert_str = weechat_file_get_content (cert_path2);
+                if (cert_str)
+                {
+                    weechat_printf (server->buffer,
+                                    _("gnutls: sending one certificate"));
+                    
+                    filedatum.data = (unsigned char *) cert_str;
+                    filedatum.size = strlen (cert_str);
+                    
+                    /* certificate */
+                    gnutls_x509_crt_init (&server->tls_cert);
+                    gnutls_x509_crt_import (server->tls_cert, &filedatum,
                                             GNUTLS_X509_FMT_PEM);
-                
-                tls_struct.type = GNUTLS_CRT_X509;
-                tls_struct.ncerts = 1;
-                tls_struct.deinit_all = 0;
-                tls_struct.cert.x509 = &server->tls_cert;
-                tls_struct.key.x509 = server->tls_cert_key;
+                    
+                    /* key */
+                    gnutls_x509_privkey_init (&server->tls_cert_key);
+                    ret = gnutls_x509_privkey_import (server->tls_cert_key,
+                                                      &filedatum,
+                                                      GNUTLS_X509_FMT_PEM);
+                    if (ret < 0)
+                    {
+                        ret = gnutls_x509_privkey_import_pkcs8 (server->tls_cert_key,
+                                                                &filedatum,
+                                                                GNUTLS_X509_FMT_PEM,
+                                                                NULL,
+                                                                GNUTLS_PKCS_PLAIN);
+                    }
+                    if (ret < 0)
+                    {
+                        weechat_printf (server->buffer,
+                                        _("%sgnutls: invalid certificate \"%s\", "
+                                          "error: %s"),
+                                        weechat_prefix ("error"), cert_path2,
+                                        gnutls_strerror (ret));
+                        rc = -1;
+                    }
+                    else
+                    {
+                        tls_struct.type = GNUTLS_CRT_X509;
+                        tls_struct.ncerts = 1;
+                        tls_struct.deinit_all = 0;
+                        tls_struct.cert.x509 = &server->tls_cert;
+                        tls_struct.key.x509 = server->tls_cert_key;
 #if LIBGNUTLS_VERSION_NUMBER >= 0x010706
-                /* client certificate info */
+                        /* client certificate info */
 #if LIBGNUTLS_VERSION_NUMBER < 0x020400
-                rinfo = gnutls_x509_crt_print (cert_temp,
-                                               GNUTLS_X509_CRT_ONELINE,
-                                               &cinfo);
+                        rinfo = gnutls_x509_crt_print (server->tls_cert,
+                                                       GNUTLS_X509_CRT_ONELINE,
+                                                       &cinfo);
 #else
-                rinfo = gnutls_x509_crt_print (cert_temp,
-                                               GNUTLS_CRT_PRINT_ONELINE,
-                                               &cinfo);
+                        rinfo = gnutls_x509_crt_print (server->tls_cert,
+                                                       GNUTLS_CRT_PRINT_ONELINE,
+                                                       &cinfo);
 #endif
-                if (rinfo == 0)
+                        if (rinfo == 0)
+                        {
+                            weechat_printf (server->buffer,
+                                            _(" - client certificate info (%s):"),
+                                            cert_path2);
+                            weechat_printf (server->buffer, "  - %s", cinfo.data);
+                            gnutls_free (cinfo.data);
+                        }
+#endif
+                        memcpy (answer, &tls_struct, sizeof (gnutls_retr_st));
+                        free (cert_str);
+                    }
+                }
+                else
                 {
                     weechat_printf (server->buffer,
-                                    _(" - client certificate info (%s):"),
-                                    cert_path2);
-                    weechat_printf (server->buffer, "  - %s", cinfo.data);
-                    gnutls_free (cinfo.data);
+                                    _("%sgnutls: unable to read certifcate \"%s\""),
+                                    weechat_prefix ("error"), cert_path2);
                 }
-#endif
                 memcpy(answer, &tls_struct, sizeof (gnutls_retr_st));
                 free (cert_str);
             }
-            else
-            {
-                weechat_printf (server->buffer,
-                                _("%sgnutls: unable to read certifcate \"%s\""),
-                                weechat_prefix ("error"), cert_path2);
-            }
+            
+            if (cert_path1)
+                free (cert_path1);
+            if (cert_path2)
+                free (cert_path2);
         }
-        
-        if (cert_path1)
-            free (cert_path1);
-        if (cert_path2)
-            free (cert_path2);
     }
     
     /* an error should stop the handshake unless the user doesn't care */
--- a/src/plugins/weechat-plugin.h
+++ b/src/plugins/weechat-plugin.h
@@ -114,6 +114,10 @@
 #define WEECHAT_HOOK_CONNECT_GNUTLS_HANDSHAKE_ERROR 7
 #define WEECHAT_HOOK_CONNECT_MEMORY_ERROR           8
 
+/* action for gnutls callback: verify or set certificate */
+#define WEECHAT_HOOK_CONNECT_GNUTLS_CB_VERIFY_CERT  0
+#define WEECHAT_HOOK_CONNECT_GNUTLS_CB_SET_CERT     1
+
 /* type of data for signal hooked */
 #define WEECHAT_HOOK_SIGNAL_STRING                  "string"
 #define WEECHAT_HOOK_SIGNAL_INT                     "int"
