talons

Fork of Claws Mail https://www.claws-mail
Log | Files | Refs | README | LICENSE

ssl.c (9962B)


      1 /*
      2  * Claws Mail -- a GTK based, lightweight, and fast e-mail client
      3  * Copyright (C) 1999-2021 the Claws Mail team and Hiroyuki Yamamoto
      4  *
      5  * This program is free software; you can redistribute it and/or modify
      6  * it under the terms of the GNU General Public License as published by
      7  * the Free Software Foundation; either version 3 of the License, or
      8  * (at your option) any later version.
      9  *
     10  * This program is distributed in the hope that it will be useful,
     11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
     12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     13  * GNU General Public License for more details.
     14  *
     15  * You should have received a copy of the GNU General Public License
     16  * along with this program. If not, see <http://www.gnu.org/licenses/>.
     17  *
     18  */
     19 
     20 #include "defs.h"
     21 
     22 #include <stdlib.h>
     23 #include <glib.h>
     24 #include <glib/gi18n.h>
     25 #include <errno.h>
     26 #include <pthread.h>
     27 
     28 #include "claws.h"
     29 #include "utils.h"
     30 #include "ssl.h"
     31 #include "ssl_certificate.h"
     32 #include "hooks.h"
     33 
     34 #include <libetpan/mailstream_ssl.h>
     35 
     36 #include <pthread.h>
     37 
     38 typedef struct _thread_data {
     39 	gnutls_session_t ssl;
     40 	gboolean done;
     41 } thread_data;
     42 
     43 #define DEFAULT_GNUTLS_PRIORITY "NORMAL"
     44 
     45 static int gnutls_cert_cb(gnutls_session_t session,
     46 		const gnutls_datum_t *req_ca_rdn,
     47 		int nreqs,
     48 		const gnutls_pk_algorithm_t *pk_algos,
     49 		int pk_algos_length,
     50 		gnutls_pcert_st **pcert,
     51 		unsigned int *pcert_length,
     52 		gnutls_privkey_t *privkey)
     53 {
     54 	SSLClientCertHookData hookdata;
     55 	SockInfo *sockinfo = (SockInfo *)gnutls_session_get_ptr(session);
     56 	gnutls_datum_t tmp;
     57 	int r;
     58 
     59 	hookdata.account = sockinfo->account;
     60 	hookdata.cert_path = NULL;
     61 	hookdata.password = NULL;
     62 	hookdata.is_smtp = sockinfo->is_smtp;
     63 	hooks_invoke(SSLCERT_GET_CLIENT_CERT_HOOKLIST, &hookdata);
     64 
     65 	if (hookdata.cert_path == NULL) {
     66 		g_free(hookdata.password);
     67 		return 0;
     68 	}
     69 
     70 	if ((r = gnutls_load_file(hookdata.cert_path, &tmp)) != 0) {
     71 		debug_print("couldn't load file '%s': %d\n",
     72 				hookdata.cert_path, r);
     73 		g_free(hookdata.password);
     74 		return 0;
     75 	}
     76 	debug_print("trying to load client cert+key from file '%s'\n",
     77 			hookdata.cert_path);
     78 
     79 	if ((r = gnutls_pcert_import_x509_raw(&sockinfo->client_crt, &tmp,
     80 				GNUTLS_X509_FMT_PEM, 0)) != 0) {
     81 		debug_print("couldn't import x509 cert from PEM file '%s': %d\n",
     82 				hookdata.cert_path, r);
     83 		g_free(hookdata.password);
     84 		return 0;
     85 	}
     86 	debug_print("loaded client certificate...\n");
     87 
     88 	gnutls_privkey_init(&sockinfo->client_key);
     89 	if ((r = gnutls_privkey_import_x509_raw(sockinfo->client_key, &tmp,
     90 				GNUTLS_X509_FMT_PEM, hookdata.password, 0)) != 0) {
     91 		debug_print("couldn't import x509 pkey from PEM file '%s': %d\n",
     92 				hookdata.cert_path, r);
     93 		g_free(hookdata.password);
     94 		gnutls_privkey_deinit(sockinfo->client_key);
     95 		return 0;
     96 	}
     97 	debug_print("loaded client private key...\n");
     98 
     99 	gnutls_free(tmp.data);
    100 
    101 	*pcert_length = 1;
    102 	*pcert = &sockinfo->client_crt;
    103 	*privkey = sockinfo->client_key;
    104 
    105 	return 0;
    106 }
    107 
    108 const gchar *claws_ssl_get_cert_file(void)
    109 {
    110 	const char *cert_files[]={
    111 		"/etc/ssl/cert.pem",
    112 		"/etc/pki/tls/certs/ca-bundle.crt",
    113 		"/etc/certs/ca-bundle.crt",
    114 		"/etc/ssl/ca-bundle.pem",
    115 		"/usr/share/ssl/certs/ca-bundle.crt",
    116 		"/etc/ssl/certs/ca-certificates.crt",
    117 		"/usr/local/ssl/certs/ca-bundle.crt",
    118 		"/etc/apache/ssl.crt/ca-bundle.crt",
    119 		"/usr/share/curl/curl-ca-bundle.crt",
    120 		"/usr/share/curl/curl-ca-bundle.crt",
    121 		"/usr/lib/ssl/cert.pem",
    122 		NULL};
    123 	int i;
    124 
    125 	for (i = 0; cert_files[i]; i++) {
    126 		if (is_file_exist(cert_files[i]))
    127 			return cert_files[i];
    128 	}
    129 	return NULL;
    130 }
    131 
    132 const gchar *claws_ssl_get_cert_dir(void)
    133 {
    134 	const char *cert_dirs[]={
    135 		"/etc/pki/tls/certs",
    136 		"/etc/certs",
    137 		"/usr/share/ssl/certs",
    138 		"/etc/ssl/certs",
    139 		"/usr/local/ssl/certs",
    140 		"/etc/apache/ssl.crt",
    141 		"/usr/share/curl",
    142 		"/usr/lib/ssl/certs",
    143 		NULL};
    144 	int i;
    145 
    146 	for (i = 0; cert_dirs[i]; i++) {
    147 		if (is_dir_exist(cert_dirs[i]))
    148 			return cert_dirs[i];
    149 	}
    150 	return NULL;
    151 }
    152 
    153 void ssl_init(void)
    154 {
    155 	mailstream_gnutls_init_not_required();
    156 	gnutls_global_init();
    157 }
    158 
    159 void ssl_done(void)
    160 {
    161 	gnutls_global_deinit();
    162 }
    163 
    164 static void *SSL_connect_thread(void *data)
    165 {
    166 	thread_data *td = (thread_data *)data;
    167 	int result = -1;
    168 
    169 	pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
    170 	pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL);
    171 
    172 	do {
    173 		result = gnutls_handshake(td->ssl);
    174 	} while (result == GNUTLS_E_AGAIN || result == GNUTLS_E_INTERRUPTED);
    175 
    176 	td->done = TRUE; /* let the caller thread join() */
    177 	return GINT_TO_POINTER(result);
    178 }
    179 
    180 static gint SSL_connect_nb(gnutls_session_t ssl)
    181 {
    182 	int result;
    183 	thread_data *td = g_new0(thread_data, 1);
    184 	pthread_t pt;
    185 	void *res = NULL;
    186 	time_t start_time = time(NULL);
    187 	gboolean killed = FALSE;
    188 
    189 	td->ssl  = ssl;
    190 	td->done = FALSE;
    191 
    192 	/* try to create a thread to initialize the SSL connection,
    193 	 * fallback to blocking method in case of problem
    194 	 */
    195 	if (pthread_create(&pt, NULL, SSL_connect_thread, td) != 0) {
    196 		do {
    197 			result = gnutls_handshake(td->ssl);
    198 		} while (result == GNUTLS_E_AGAIN || result == GNUTLS_E_INTERRUPTED);
    199 		return result;
    200 	}
    201 	debug_print("waiting for SSL_connect thread...\n");
    202 	while(!td->done) {
    203 		/* don't let the interface freeze while waiting */
    204 		claws_do_idle();
    205 		if (time(NULL) - start_time > 30) {
    206 			pthread_cancel(pt);
    207 			td->done = TRUE;
    208 			killed = TRUE;
    209 		}
    210 	}
    211 
    212 	/* get the thread's return value and clean its resources */
    213 	pthread_join(pt, &res);
    214 	g_free(td);
    215 
    216 	if (killed) {
    217 		res = GINT_TO_POINTER(-1);
    218 	}
    219 	debug_print("SSL_connect thread returned %d\n",
    220 			GPOINTER_TO_INT(res));
    221 
    222 	return GPOINTER_TO_INT(res);
    223 }
    224 
    225 gnutls_x509_crt_t *ssl_get_certificate_chain(gnutls_session_t session, unsigned int *list_len)
    226 {
    227 	const gnutls_datum_t *raw_cert_list;
    228 	gnutls_x509_crt_t *certs = NULL;
    229 	gboolean result = TRUE;
    230 
    231 	*list_len = 0;
    232 	if (!session)
    233 		return NULL;
    234 
    235 	raw_cert_list = gnutls_certificate_get_peers(session, list_len);
    236 
    237 	if (raw_cert_list && (*list_len>0) && gnutls_certificate_type_get(session) == GNUTLS_CRT_X509) {
    238 		glong i = 0;
    239 
    240 		if (*list_len > 128)
    241 			*list_len = 128;
    242 
    243 		certs = g_malloc(sizeof(gnutls_x509_crt_t) * (*list_len));
    244 
    245 		for(i = 0 ; i < (glong)(*list_len) ; i++) {
    246 			int r;
    247 
    248 			gnutls_x509_crt_init(&certs[i]);
    249 			r = gnutls_x509_crt_import(certs[i], &raw_cert_list[i], GNUTLS_X509_FMT_DER);
    250 			if (r < 0) {
    251 				g_warning("cert get failure: %d %s", r, gnutls_strerror(r));
    252 
    253 				result = FALSE;
    254 				i--;
    255 				break;
    256 			}
    257 		}
    258 		if (!result) {
    259 			for (; i >= 0; i--)
    260 				gnutls_x509_crt_deinit(certs[i]);
    261 
    262 			g_free(certs);
    263 			*list_len = 0;
    264 
    265 			return NULL;
    266 		}
    267 	}
    268 
    269 	return certs;
    270 }
    271 
    272 gboolean ssl_init_socket(SockInfo *sockinfo)
    273 {
    274 	gnutls_session_t session;
    275 	int r;
    276 	unsigned int i, cert_list_length;
    277 	gnutls_x509_crt_t *certs = NULL;
    278 	gnutls_certificate_credentials_t xcred;
    279 
    280 	if (gnutls_certificate_allocate_credentials (&xcred) != 0)
    281 		return FALSE;
    282 
    283 	r = gnutls_init(&session, GNUTLS_CLIENT);
    284 	if (session == NULL || r != 0)
    285 		return FALSE;
    286 
    287 	if (sockinfo->gnutls_priority && strlen(sockinfo->gnutls_priority)) {
    288 		r = gnutls_priority_set_direct(session, sockinfo->gnutls_priority, NULL);
    289 		debug_print("Setting GnuTLS priority to %s, status = %d\n",
    290 			    sockinfo->gnutls_priority, r);
    291 	}
    292 	else {
    293 		gnutls_priority_set_direct(session, DEFAULT_GNUTLS_PRIORITY, NULL);
    294 	}
    295 
    296 	gnutls_record_disable_padding(session);
    297 
    298 	/* If we have a host name, rather than a numerical IP address, tell
    299 	 * gnutls to send it in the server name identification extension field,
    300 	 * to give the server a chance to select the correct certificate in the
    301 	 * virtual hosting case where multiple domain names are hosted on the
    302 	 * same IP address. */
    303 	if (sockinfo->use_tls_sni &&
    304 			sockinfo->hostname != NULL &&
    305 			!is_numeric_host_address(sockinfo->hostname)) {
    306 		r = gnutls_server_name_set(session, GNUTLS_NAME_DNS,
    307 				sockinfo->hostname, strlen(sockinfo->hostname));
    308 		debug_print("Set GnuTLS session server name indication to %s, status = %d\n",
    309 			    sockinfo->hostname, r);
    310 	}
    311 
    312 	gnutls_credentials_set(session, GNUTLS_CRD_CERTIFICATE, xcred);
    313 
    314 	if (claws_ssl_get_cert_file()) {
    315 		r = gnutls_certificate_set_x509_trust_file(xcred, claws_ssl_get_cert_file(),  GNUTLS_X509_FMT_PEM);
    316 		if (r < 0)
    317 			g_warning("get certificate file '%s': %s",
    318 				claws_ssl_get_cert_file(),
    319 				gnutls_strerror(r));
    320 	} else {
    321 		debug_print("Can't find SSL ca-certificates file\n");
    322 	}
    323 	gnutls_certificate_set_verify_flags (xcred, GNUTLS_VERIFY_ALLOW_X509_V1_CA_CRT);
    324 
    325 	gnutls_transport_set_ptr(session, (gnutls_transport_ptr_t) GINT_TO_POINTER(sockinfo->sock));
    326 
    327 	gnutls_session_set_ptr(session, sockinfo);
    328 
    329 	debug_print("setting certificate callback function\n");
    330 	gnutls_certificate_set_retrieve_function2(xcred, gnutls_cert_cb);
    331 
    332 	if ((r = SSL_connect_nb(session)) < 0) {
    333 		g_warning("TLS connection failed (%s)", gnutls_strerror(r));
    334 		gnutls_certificate_free_credentials(xcred);
    335 		gnutls_deinit(session);
    336 		return FALSE;
    337 	}
    338 
    339 	/* Get server's certificate (note: beware of dynamic allocation) */
    340 	certs = ssl_get_certificate_chain(session, &cert_list_length);
    341 
    342 	if (!certs) {
    343 		gnutls_certificate_free_credentials(xcred);
    344 		gnutls_deinit(session);
    345 		return FALSE;
    346 	}
    347 
    348 	if (!ssl_certificate_check_chain(certs, cert_list_length, sockinfo->hostname, sockinfo->port,
    349 					 sockinfo->ssl_cert_auto_accept)) {
    350 		for (i = 0; i < cert_list_length; i++)
    351 			gnutls_x509_crt_deinit(certs[i]);
    352 		g_free(certs);
    353 		gnutls_certificate_free_credentials(xcred);
    354 		gnutls_deinit(session);
    355 		return FALSE;
    356 	}
    357 
    358 	for (i = 0; i < cert_list_length; i++)
    359 		gnutls_x509_crt_deinit(certs[i]);
    360 	g_free(certs);
    361 
    362 	sockinfo->ssl = session;
    363 	sockinfo->xcred = xcred;
    364 	return TRUE;
    365 }
    366 
    367 void ssl_done_socket(SockInfo *sockinfo)
    368 {
    369 	if (sockinfo && sockinfo->ssl) {
    370 		if (sockinfo->xcred)
    371 			gnutls_certificate_free_credentials(sockinfo->xcred);
    372 		gnutls_deinit(sockinfo->ssl);
    373 		gnutls_pcert_deinit(&sockinfo->client_crt);
    374 		gnutls_privkey_deinit(sockinfo->client_key);
    375 		sockinfo->client_key = NULL;
    376 		sockinfo->xcred = NULL;
    377 		sockinfo->ssl = NULL;
    378 	}
    379 }