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 }