NeoMutt  2025-12-11-694-ga89709
Teaching an old dog new tricks
DOXYGEN
Loading...
Searching...
No Matches
gnutls.c
Go to the documentation of this file.
1
24
30
31#include "config.h"
32#include <gnutls/gnutls.h>
33#include <gnutls/x509.h>
34#include <stdbool.h>
35#include <stdio.h>
36#include <string.h>
37#include <sys/stat.h>
38#include <time.h>
39#include "private.h"
40#include "mutt/lib.h"
41#include "config/lib.h"
42#include "core/lib.h"
43#include "lib.h"
44#include "connaccount.h"
45#include "connection.h"
46#include "globals.h"
47#include "muttlib.h"
48#include "ssl.h"
49
50int gnutls_protocol_set_priority(gnutls_session_t session, const int *list);
51
52// clang-format off
53#define CERTERR_VALID 0
54#define CERTERR_EXPIRED (1 << 0)
55#define CERTERR_NOTYETVALID (1 << 1)
56#define CERTERR_REVOKED (1 << 2)
57#define CERTERR_NOTTRUSTED (1 << 3)
58#define CERTERR_HOSTNAME (1 << 4)
59#define CERTERR_SIGNERNOTCA (1 << 5)
60#define CERTERR_INSECUREALG (1 << 6)
61#define CERTERR_OTHER (1 << 7)
62// clang-format on
63
65#define CERT_SEP "-----BEGIN"
66
67#ifndef HAVE_GNUTLS_PRIORITY_SET_DIRECT
76static int ProtocolPriority[] = { GNUTLS_TLS1_2, GNUTLS_TLS1_1, GNUTLS_TLS1, GNUTLS_SSL3, 0 };
77#endif
78
83{
84 gnutls_session_t session;
85 gnutls_certificate_credentials_t xcred;
86};
87
93static int tls_init(void)
94{
95 static bool init_complete = false;
96 int err;
97
98 if (init_complete)
99 return 0;
100
101 err = gnutls_global_init();
102 if (err < 0)
103 {
104 mutt_error("gnutls_global_init: %s", gnutls_strerror(err));
105 return -1;
106 }
107
108 init_complete = true;
109 return 0;
110}
111
123static int tls_verify_peers(gnutls_session_t tlsstate, gnutls_certificate_status_t *certstat)
124{
125 /* gnutls_certificate_verify_peers2() chains to
126 * gnutls_x509_trust_list_verify_crt2(). That function's documentation says:
127 *
128 * When a certificate chain of cert_list_size with more than one
129 * certificates is provided, the verification status will apply to
130 * the first certificate in the chain that failed
131 * verification. The verification process starts from the end of
132 * the chain(from CA to end certificate). The first certificate
133 * in the chain must be the end-certificate while the rest of the
134 * members may be sorted or not.
135 *
136 * This is why tls_check_certificate() loops from CA to host in that order,
137 * calling the menu, and recalling tls_verify_peers() for each approved
138 * cert in the chain.
139 */
140 int rc = gnutls_certificate_verify_peers2(tlsstate, certstat);
141
142 /* certstat was set */
143 if (rc == 0)
144 return 0;
145
146 if (rc == GNUTLS_E_NO_CERTIFICATE_FOUND)
147 mutt_error(_("Unable to get certificate from peer"));
148 else
149 mutt_error(_("Certificate verification error (%s)"), gnutls_strerror(rc));
150
151 return rc;
152}
153
160static void tls_fingerprint(gnutls_digest_algorithm_t algo, struct Buffer *buf,
161 const gnutls_datum_t *data)
162{
163 unsigned char md[128] = { 0 };
164 size_t n = 64;
165
166 if (gnutls_fingerprint(algo, data, (char *) md, &n) < 0)
167 {
168 buf_strcpy(buf, _("[unable to calculate]"));
169 return;
170 }
171
172 for (size_t i = 0; i < n; i++)
173 {
174 buf_add_printf(buf, "%02X", md[i]);
175
176 // Put a space after a pair of bytes (except for the last one)
177 if (((i % 2) == 1) && (i < (n - 1)))
178 buf_addch(buf, ' ');
179 }
180}
181
189static bool tls_check_stored_hostname(const gnutls_datum_t *cert, const char *hostname)
190{
191 char *linestr = NULL;
192 size_t linestrsize = 0;
193
194 /* try checking against names stored in stored certs file */
195 const char *const c_certificate_file = cs_subset_path(NeoMutt->sub, "certificate_file");
196 FILE *fp = mutt_file_fopen(c_certificate_file, "r");
197 if (!fp)
198 return false;
199
200 struct Buffer *buf = buf_pool_get();
201
202 tls_fingerprint(GNUTLS_DIG_MD5, buf, cert);
203 while ((linestr = mutt_file_read_line(linestr, &linestrsize, fp, NULL, MUTT_RL_NO_FLAGS)))
204 {
205 regmatch_t *match = mutt_prex_capture(PREX_GNUTLS_CERT_HOST_HASH, linestr);
206 if (match)
207 {
208 regmatch_t *mhost = &match[PREX_GNUTLS_CERT_HOST_HASH_MATCH_HOST];
209 regmatch_t *mhash = &match[PREX_GNUTLS_CERT_HOST_HASH_MATCH_HASH];
210 linestr[mutt_regmatch_end(mhost)] = '\0';
211 linestr[mutt_regmatch_end(mhash)] = '\0';
212 if ((mutt_str_equal(linestr + mutt_regmatch_start(mhost), hostname)) &&
213 (mutt_str_equal(linestr + mutt_regmatch_start(mhash), buf_string(buf))))
214 {
215 FREE(&linestr);
216 mutt_file_fclose(&fp);
217 buf_pool_release(&buf);
218 return true;
219 }
220 }
221 }
222
223 mutt_file_fclose(&fp);
224 buf_pool_release(&buf);
225
226 /* not found a matching name */
227 return false;
228}
229
236static bool tls_compare_certificates(const gnutls_datum_t *peercert)
237{
238 gnutls_datum_t cert = { 0 };
239 unsigned char *ptr = NULL;
240 gnutls_datum_t b64_data = { 0 };
241 unsigned char *b64_data_data = NULL;
242 struct stat st = { 0 };
243
244 const char *const c_certificate_file = cs_subset_path(NeoMutt->sub, "certificate_file");
245 if (stat(c_certificate_file, &st) == -1)
246 return false;
247
248 b64_data.size = st.st_size;
249 b64_data_data = MUTT_MEM_CALLOC(b64_data.size + 1, unsigned char);
250 b64_data.data = b64_data_data;
251
252 FILE *fp = mutt_file_fopen(c_certificate_file, "r");
253 if (!fp)
254 {
255 FREE(&b64_data_data);
256 return false;
257 }
258
259 b64_data.size = fread(b64_data.data, 1, b64_data.size, fp);
260 b64_data.data[b64_data.size] = '\0';
261 mutt_file_fclose(&fp);
262
263 do
264 {
265 const int rc = gnutls_pem_base64_decode_alloc(NULL, &b64_data, &cert);
266 if (rc != 0)
267 {
268 FREE(&b64_data_data);
269 return false;
270 }
271
272 /* find start of cert, skipping junk */
273 ptr = (unsigned char *) strstr((char *) b64_data.data, CERT_SEP);
274 if (!ptr)
275 {
276 gnutls_free(cert.data);
277 FREE(&b64_data_data);
278 return false;
279 }
280 /* find start of next cert */
281 ptr = (unsigned char *) strstr((char *) ptr + 1, CERT_SEP);
282
283 b64_data.size = b64_data.size - (ptr - b64_data.data);
284 b64_data.data = ptr;
285
286 if (cert.size == peercert->size)
287 {
288 if (memcmp(cert.data, peercert->data, cert.size) == 0)
289 {
290 /* match found */
291 gnutls_free(cert.data);
292 FREE(&b64_data_data);
293 return true;
294 }
295 }
296
297 gnutls_free(cert.data);
298 } while (ptr);
299
300 /* no match found */
301 FREE(&b64_data_data);
302 return false;
303}
304
316static int tls_check_preauth(const gnutls_datum_t *certdata,
317 gnutls_certificate_status_t certstat, const char *hostname,
318 int chainidx, int *certerr, int *savedcert)
319{
320 gnutls_x509_crt_t cert;
321
322 *certerr = CERTERR_VALID;
323 *savedcert = 0;
324
325 if (gnutls_x509_crt_init(&cert) < 0)
326 {
327 mutt_error(_("Error initialising gnutls certificate data"));
328 return -1;
329 }
330
331 if (gnutls_x509_crt_import(cert, certdata, GNUTLS_X509_FMT_DER) < 0)
332 {
333 mutt_error(_("Error processing certificate data"));
334 gnutls_x509_crt_deinit(cert);
335 return -1;
336 }
337
338 /* Note: tls_negotiate() contains a call to
339 * gnutls_certificate_set_verify_flags() with a flag disabling
340 * GnuTLS checking of the dates. So certstat shouldn't have the
341 * GNUTLS_CERT_EXPIRED and GNUTLS_CERT_NOT_ACTIVATED bits set. */
342 const bool c_ssl_verify_dates = cs_subset_bool(NeoMutt->sub, "ssl_verify_dates");
343 if (c_ssl_verify_dates != MUTT_NO)
344 {
345 if (gnutls_x509_crt_get_expiration_time(cert) < mutt_date_now())
346 *certerr |= CERTERR_EXPIRED;
347 if (gnutls_x509_crt_get_activation_time(cert) > mutt_date_now())
348 *certerr |= CERTERR_NOTYETVALID;
349 }
350
351 const bool c_ssl_verify_host = cs_subset_bool(NeoMutt->sub, "ssl_verify_host");
352 if ((chainidx == 0) && (c_ssl_verify_host != MUTT_NO) &&
353 !gnutls_x509_crt_check_hostname(cert, hostname) &&
354 !tls_check_stored_hostname(certdata, hostname))
355 {
356 *certerr |= CERTERR_HOSTNAME;
357 }
358
359 if (certstat & GNUTLS_CERT_REVOKED)
360 {
361 *certerr |= CERTERR_REVOKED;
362 certstat ^= GNUTLS_CERT_REVOKED;
363 }
364
365 /* see whether certificate is in our cache (certificates file) */
366 if (tls_compare_certificates(certdata))
367 {
368 *savedcert = 1;
369
370 /* We check above for certs with bad dates or that are revoked.
371 * These must be accepted manually each time. Otherwise, we
372 * accept saved certificates as valid. */
373 if (*certerr == CERTERR_VALID)
374 {
375 gnutls_x509_crt_deinit(cert);
376 return 0;
377 }
378 }
379
380 if (certstat & GNUTLS_CERT_INVALID)
381 {
382 *certerr |= CERTERR_NOTTRUSTED;
383 certstat ^= GNUTLS_CERT_INVALID;
384 }
385
386 if (certstat & GNUTLS_CERT_SIGNER_NOT_FOUND)
387 {
388 /* NB: already cleared if cert in cache */
389 *certerr |= CERTERR_NOTTRUSTED;
390 certstat ^= GNUTLS_CERT_SIGNER_NOT_FOUND;
391 }
392
393 if (certstat & GNUTLS_CERT_SIGNER_NOT_CA)
394 {
395 /* NB: already cleared if cert in cache */
396 *certerr |= CERTERR_SIGNERNOTCA;
397 certstat ^= GNUTLS_CERT_SIGNER_NOT_CA;
398 }
399
400 if (certstat & GNUTLS_CERT_INSECURE_ALGORITHM)
401 {
402 /* NB: already cleared if cert in cache */
403 *certerr |= CERTERR_INSECUREALG;
404 certstat ^= GNUTLS_CERT_INSECURE_ALGORITHM;
405 }
406
407 /* we've been zeroing the interesting bits in certstat -
408 * don't return OK if there are any unhandled bits we don't
409 * understand */
410 if (certstat != 0)
411 *certerr |= CERTERR_OTHER;
412
413 gnutls_x509_crt_deinit(cert);
414
415 if (*certerr == CERTERR_VALID)
416 return 0;
417
418 return -1;
419}
420
428static void add_cert(const char *title, gnutls_x509_crt_t cert, bool issuer,
429 struct StringArray *carr)
430{
431 static const char *part[] = {
432 GNUTLS_OID_X520_COMMON_NAME, // CN
433 GNUTLS_OID_PKCS9_EMAIL, // Email
434 GNUTLS_OID_X520_ORGANIZATION_NAME, // O
435 GNUTLS_OID_X520_ORGANIZATIONAL_UNIT_NAME, // OU
436 GNUTLS_OID_X520_LOCALITY_NAME, // L
437 GNUTLS_OID_X520_STATE_OR_PROVINCE_NAME, // ST
438 GNUTLS_OID_X520_COUNTRY_NAME, // C
439 };
440
441 char buf[128] = { 0 };
442 int rc;
443
444 // Allocate formatted strings and let the array take ownership
445 ARRAY_ADD(carr, mutt_str_dup(title));
446
447 for (size_t i = 0; i < countof(part); i++)
448 {
449 size_t buflen = sizeof(buf);
450 if (issuer)
451 rc = gnutls_x509_crt_get_issuer_dn_by_oid(cert, part[i], 0, 0, buf, &buflen);
452 else
453 rc = gnutls_x509_crt_get_dn_by_oid(cert, part[i], 0, 0, buf, &buflen);
454 if (rc != 0)
455 continue;
456
457 char *line = NULL;
458 mutt_str_asprintf(&line, " %s", buf);
459 ARRAY_ADD(carr, line);
460 }
461}
462
473static int tls_check_one_certificate(const gnutls_datum_t *certdata,
474 gnutls_certificate_status_t certstat,
475 const char *hostname, int idx, size_t len)
476{
477 struct StringArray carr = ARRAY_HEAD_INITIALIZER;
478 int certerr, savedcert;
479 gnutls_x509_crt_t cert;
480 struct Buffer *fpbuf = NULL;
481 time_t t;
482 char datestr[30] = { 0 };
483 char title[256] = { 0 };
484 gnutls_datum_t pemdata = { 0 };
485
486 if (tls_check_preauth(certdata, certstat, hostname, idx, &certerr, &savedcert) == 0)
487 return 1;
488
489 if (!OptGui)
490 {
491 mutt_debug(LL_DEBUG1, "unable to prompt for certificate in batch mode\n");
492 mutt_error(_("Untrusted server certificate"));
493 return 0;
494 }
495
496 /* interactive check from user */
497 if (gnutls_x509_crt_init(&cert) < 0)
498 {
499 mutt_error(_("Error initialising gnutls certificate data"));
500 return 0;
501 }
502
503 if (gnutls_x509_crt_import(cert, certdata, GNUTLS_X509_FMT_DER) < 0)
504 {
505 mutt_error(_("Error processing certificate data"));
506 gnutls_x509_crt_deinit(cert);
507 return 0;
508 }
509
510 add_cert(_("This certificate belongs to:"), cert, false, &carr);
511 ARRAY_ADD(&carr, NULL);
512 add_cert(_("This certificate was issued by:"), cert, true, &carr);
513
514 ARRAY_ADD(&carr, NULL);
515 ARRAY_ADD(&carr, mutt_str_dup(_("This certificate is valid")));
516
517 char *line = NULL;
518 t = gnutls_x509_crt_get_activation_time(cert);
519 mutt_date_make_tls(datestr, sizeof(datestr), t);
520 mutt_str_asprintf(&line, _(" from %s"), datestr);
521 ARRAY_ADD(&carr, line);
522
523 t = gnutls_x509_crt_get_expiration_time(cert);
524 mutt_date_make_tls(datestr, sizeof(datestr), t);
525 mutt_str_asprintf(&line, _(" to %s"), datestr);
526 ARRAY_ADD(&carr, line);
527 ARRAY_ADD(&carr, NULL);
528
529 fpbuf = buf_pool_get();
530 tls_fingerprint(GNUTLS_DIG_SHA, fpbuf, certdata);
531 mutt_str_asprintf(&line, _("SHA1 Fingerprint: %s"), buf_string(fpbuf));
532 ARRAY_ADD(&carr, line);
533
534 buf_reset(fpbuf);
535 tls_fingerprint(GNUTLS_DIG_SHA256, fpbuf, certdata);
536 fpbuf->data[39] = '\0'; // Divide into two lines of output
537 mutt_str_asprintf(&line, "%s%s", _("SHA256 Fingerprint: "), buf_string(fpbuf));
538 ARRAY_ADD(&carr, line);
539 mutt_str_asprintf(&line, "%*s%s", (int) mutt_str_len(_("SHA256 Fingerprint: ")),
540 "", fpbuf->data + 40);
541 ARRAY_ADD(&carr, line);
542
543 if (certerr)
544 ARRAY_ADD(&carr, NULL);
545
546 if (certerr & CERTERR_NOTYETVALID)
547 {
548 ARRAY_ADD(&carr, mutt_str_dup(_("WARNING: Server certificate is not yet valid")));
549 }
550 if (certerr & CERTERR_EXPIRED)
551 {
552 ARRAY_ADD(&carr, mutt_str_dup(_("WARNING: Server certificate has expired")));
553 }
554 if (certerr & CERTERR_REVOKED)
555 {
556 ARRAY_ADD(&carr, mutt_str_dup(_("WARNING: Server certificate has been revoked")));
557 }
558 if (certerr & CERTERR_HOSTNAME)
559 {
560 ARRAY_ADD(&carr, mutt_str_dup(_("WARNING: Server hostname does not match certificate")));
561 }
562 if (certerr & CERTERR_SIGNERNOTCA)
563 {
564 ARRAY_ADD(&carr, mutt_str_dup(_("WARNING: Signer of server certificate is not a CA")));
565 }
566 if (certerr & CERTERR_INSECUREALG)
567 {
568 ARRAY_ADD(&carr, mutt_str_dup(_("Warning: Server certificate was signed using an insecure algorithm")));
569 }
570
571 const char **line_ptr = ARRAY_LAST(&carr);
572 if (line_ptr && !*line_ptr)
573 ARRAY_SHRINK(&carr, 1);
574
575 snprintf(title, sizeof(title),
576 _("SSL Certificate check (certificate %zu of %zu in chain)"), len - idx, len);
577
578 const char *const c_certificate_file = cs_subset_path(NeoMutt->sub, "certificate_file");
579 const bool allow_always = (c_certificate_file && !savedcert &&
581 int rc = dlg_certificate(title, &carr, allow_always, false);
582 if (rc == 3) // Accept always
583 {
584 bool saved = false;
585 FILE *fp = mutt_file_fopen(c_certificate_file, "a");
586 if (fp)
587 {
588 if (certerr & CERTERR_HOSTNAME) // Save hostname if necessary
589 {
590 buf_reset(fpbuf);
591 tls_fingerprint(GNUTLS_DIG_MD5, fpbuf, certdata);
592 fprintf(fp, "#H %s %s\n", hostname, buf_string(fpbuf));
593 saved = true;
594 }
595 if (certerr ^ CERTERR_HOSTNAME) // Save the cert for all other errors
596 {
597 int rc2 = gnutls_pem_base64_encode_alloc("CERTIFICATE", certdata, &pemdata);
598 if (rc2 == 0)
599 {
600 if (fwrite(pemdata.data, pemdata.size, 1, fp) == 1)
601 {
602 saved = true;
603 }
604 gnutls_free(pemdata.data);
605 }
606 }
607 mutt_file_fclose(&fp);
608 }
609 if (saved)
610 mutt_message(_("Certificate saved"));
611 else
612 mutt_error(_("Warning: Couldn't save certificate"));
613 }
614
615 buf_pool_release(&fpbuf);
616 string_array_clear(&carr);
617 gnutls_x509_crt_deinit(cert);
618 return (rc > 1);
619}
620
627static int tls_check_certificate(struct Connection *conn)
628{
629 struct TlsSockData *data = conn->sockdata;
630 gnutls_session_t session = data->session;
631 const gnutls_datum_t *cert_list = NULL;
632 unsigned int cert_list_size = 0;
633 gnutls_certificate_status_t certstat;
634 int certerr, savedcert, rc = 0;
635 int max_preauth_pass = -1;
636
637 /* tls_verify_peers() calls gnutls_certificate_verify_peers2(),
638 * which verifies the auth_type is GNUTLS_CRD_CERTIFICATE
639 * and that get_certificate_type() for the server is GNUTLS_CRT_X509.
640 * If it returns 0, certstat will be set with failure codes for the first
641 * cert in the chain(from CA to host) with an error.
642 */
643 if (tls_verify_peers(session, &certstat) != 0)
644 return 0;
645
646 cert_list = gnutls_certificate_get_peers(session, &cert_list_size);
647 if (!cert_list || (cert_list_size == 0))
648 {
649 mutt_error(_("Unable to get certificate from peer"));
650 return 0;
651 }
652
653 /* tls_verify_peers doesn't check hostname or expiration, so walk
654 * from most specific to least checking these. If we see a saved certificate,
655 * its status short-circuits the remaining checks. */
656 int preauthrc = 0;
657 for (int i = 0; i < cert_list_size; i++)
658 {
659 rc = tls_check_preauth(&cert_list[i], certstat, conn->account.host, i,
660 &certerr, &savedcert);
661 preauthrc += rc;
662 if (!preauthrc)
663 max_preauth_pass = i;
664
665 if (savedcert)
666 {
667 if (preauthrc == 0)
668 return 1;
669 break;
670 }
671 }
672
673 /* then check interactively, starting from chain root */
674 for (int i = cert_list_size - 1; i >= 0; i--)
675 {
676 rc = tls_check_one_certificate(&cert_list[i], certstat, conn->account.host,
677 i, cert_list_size);
678
679 /* Stop checking if the menu cert is aborted or rejected. */
680 if (rc == 0)
681 break;
682
683 /* add signers to trust set, then reverify */
684 if (i)
685 {
686 int rcsettrust = gnutls_certificate_set_x509_trust_mem(data->xcred, &cert_list[i],
687 GNUTLS_X509_FMT_DER);
688 if (rcsettrust != 1)
689 mutt_debug(LL_DEBUG1, "error trusting certificate %d: %d\n", i, rcsettrust);
690
691 if (tls_verify_peers(session, &certstat) != 0)
692 return 0;
693
694 /* If the cert chain now verifies, and all lower certs already
695 * passed preauth, we are done. */
696 if (!certstat && (max_preauth_pass >= (i - 1)))
697 return 1;
698 }
699 }
700
701 return rc;
702}
703
711static void tls_get_client_cert(struct Connection *conn)
712{
713 struct TlsSockData *data = conn->sockdata;
714 gnutls_x509_crt_t clientcrt;
715 char *cn = NULL;
716 size_t cnlen = 0;
717 int rc;
718
719 /* get our cert CN if we have one */
720 const gnutls_datum_t *crtdata = gnutls_certificate_get_ours(data->session);
721 if (!crtdata)
722 return;
723
724 if (gnutls_x509_crt_init(&clientcrt) < 0)
725 {
726 mutt_debug(LL_DEBUG1, "Failed to init gnutls crt\n");
727 return;
728 }
729
730 if (gnutls_x509_crt_import(clientcrt, crtdata, GNUTLS_X509_FMT_DER) < 0)
731 {
732 mutt_debug(LL_DEBUG1, "Failed to import gnutls client crt\n");
733 goto err;
734 }
735
736 /* get length of CN, then grab it. */
737 rc = gnutls_x509_crt_get_dn_by_oid(clientcrt, GNUTLS_OID_X520_COMMON_NAME, 0,
738 0, NULL, &cnlen);
739 if (((rc >= 0) || (rc == GNUTLS_E_SHORT_MEMORY_BUFFER)) && (cnlen > 0))
740 {
741 cn = MUTT_MEM_CALLOC(cnlen, char);
742 if (gnutls_x509_crt_get_dn_by_oid(clientcrt, GNUTLS_OID_X520_COMMON_NAME, 0,
743 0, cn, &cnlen) < 0)
744 {
745 goto err;
746 }
747 mutt_debug(LL_DEBUG2, "client certificate CN: %s\n", cn);
748 }
749
750err:
751 FREE(&cn);
752 gnutls_x509_crt_deinit(clientcrt);
753}
754
755#ifdef HAVE_GNUTLS_PRIORITY_SET_DIRECT
762static int tls_set_priority(struct TlsSockData *data)
763{
764 size_t nproto = 2;
765 int rv = -1;
766
767 struct Buffer *priority = buf_pool_get();
768
769 const char *const c_ssl_ciphers = cs_subset_string(NeoMutt->sub, "ssl_ciphers");
770 if (c_ssl_ciphers)
771 buf_strcpy(priority, c_ssl_ciphers);
772 else
773 buf_strcpy(priority, "NORMAL");
774
775 const bool c_ssl_use_tlsv1_3 = cs_subset_bool(NeoMutt->sub, "ssl_use_tlsv1_3");
776 if (!c_ssl_use_tlsv1_3)
777 {
778 nproto--;
779 buf_addstr(priority, ":-VERS-TLS1.3");
780 }
781 const bool c_ssl_use_tlsv1_2 = cs_subset_bool(NeoMutt->sub, "ssl_use_tlsv1_2");
782 if (!c_ssl_use_tlsv1_2)
783 {
784 nproto--;
785 buf_addstr(priority, ":-VERS-TLS1.2");
786 }
787
788 // Deprecated protocols
789 buf_addstr(priority, ":-VERS-TLS1.1");
790 buf_addstr(priority, ":-VERS-TLS1.0");
791 buf_addstr(priority, ":-VERS-SSL3.0");
792
793 if (nproto == 0)
794 {
795 mutt_error(_("All available protocols for TLS/SSL connection disabled"));
796 goto cleanup;
797 }
798
799 int err = gnutls_priority_set_direct(data->session, buf_string(priority), NULL);
800 if (err < 0)
801 {
802 mutt_error("gnutls_priority_set_direct(%s): %s", buf_string(priority),
803 gnutls_strerror(err));
804 goto cleanup;
805 }
806
807 rv = 0;
808
809cleanup:
810 buf_pool_release(&priority);
811 return rv;
812}
813
814#else
822{
823 size_t nproto = 0; /* number of tls/ssl protocols */
824
825 const bool c_ssl_use_tlsv1_2 = cs_subset_bool(NeoMutt->sub, "ssl_use_tlsv1_2");
826 if (c_ssl_use_tlsv1_2)
827 ProtocolPriority[nproto++] = GNUTLS_TLS1_2;
828 ProtocolPriority[nproto] = 0;
829
830 if (nproto == 0)
831 {
832 mutt_error(_("All available protocols for TLS/SSL connection disabled"));
833 return -1;
834 }
835
836 const char *const c_ssl_ciphers = cs_subset_string(NeoMutt->sub, "ssl_ciphers");
837 if (c_ssl_ciphers)
838 {
839 mutt_error(_("Explicit ciphersuite selection via $ssl_ciphers not supported"));
840 }
841
842 /* We use default priorities (see gnutls documentation),
843 * except for protocol version */
844 gnutls_set_default_priority(data->session);
846 return 0;
847}
848#endif
849
857{
858 const short c_socket_timeout = cs_subset_number(NeoMutt->sub, "socket_timeout");
859 const int poll_rc = raw_socket_poll(conn, c_socket_timeout);
860 if (poll_rc <= 0)
861 {
862 // Timeout or error
863 if (poll_rc == 0)
864 mutt_error(_("Connection to %s timed out"), conn->account.host);
865
866 return false;
867 }
868
869 return true;
870}
871
881static int tls_negotiate(struct Connection *conn)
882{
883 struct TlsSockData *data = MUTT_MEM_CALLOC(1, struct TlsSockData);
884 conn->sockdata = data;
885 int err = gnutls_certificate_allocate_credentials(&data->xcred);
886 if (err < 0)
887 {
888 FREE(&conn->sockdata);
889 mutt_error("gnutls_certificate_allocate_credentials: %s", gnutls_strerror(err));
890 return -1;
891 }
892
893 const char *const c_certificate_file = cs_subset_path(NeoMutt->sub, "certificate_file");
894 gnutls_certificate_set_x509_trust_file(data->xcred, c_certificate_file, GNUTLS_X509_FMT_PEM);
895 /* ignore errors, maybe file doesn't exist yet */
896
897 const char *const c_ssl_ca_certificates_file = cs_subset_path(NeoMutt->sub, "ssl_ca_certificates_file");
898 if (c_ssl_ca_certificates_file)
899 {
900 gnutls_certificate_set_x509_trust_file(data->xcred, c_ssl_ca_certificates_file,
901 GNUTLS_X509_FMT_PEM);
902 }
903
904 const char *const c_ssl_client_cert = cs_subset_path(NeoMutt->sub, "ssl_client_cert");
905 if (c_ssl_client_cert)
906 {
907 mutt_debug(LL_DEBUG2, "Using client certificate %s\n", c_ssl_client_cert);
908 gnutls_certificate_set_x509_key_file(data->xcred, c_ssl_client_cert,
909 c_ssl_client_cert, GNUTLS_X509_FMT_PEM);
910 }
911
912#ifdef HAVE_DECL_GNUTLS_VERIFY_DISABLE_TIME_CHECKS
913 /* disable checking certificate activation/expiration times
914 * in gnutls, we do the checks ourselves */
915 gnutls_certificate_set_verify_flags(data->xcred, GNUTLS_VERIFY_DISABLE_TIME_CHECKS);
916#endif
917
918 err = gnutls_init(&data->session, GNUTLS_CLIENT);
919 if (err)
920 {
921 mutt_error("gnutls_init: %s", gnutls_strerror(err));
922 goto fail;
923 }
924
925 /* set socket */
926 gnutls_transport_set_ptr(data->session, (gnutls_transport_ptr_t) (long) conn->fd);
927
928 if (gnutls_server_name_set(data->session, GNUTLS_NAME_DNS, conn->account.host,
929 mutt_str_len(conn->account.host)))
930 {
931 mutt_error(_("Warning: unable to set TLS SNI host name"));
932 }
933
934 if (tls_set_priority(data) < 0)
935 {
936 goto fail;
937 }
938
939 const short c_ssl_min_dh_prime_bits = cs_subset_number(NeoMutt->sub, "ssl_min_dh_prime_bits");
940 if (c_ssl_min_dh_prime_bits > 0)
941 {
942 gnutls_dh_set_prime_bits(data->session, c_ssl_min_dh_prime_bits);
943 }
944
945 gnutls_credentials_set(data->session, GNUTLS_CRD_CERTIFICATE, data->xcred);
946
947 do
948 {
949 err = gnutls_handshake(data->session);
950 if (err == GNUTLS_E_AGAIN)
951 {
952 // Socket would block, wait for data to become available
954 goto fail;
955 }
956 } while ((err == GNUTLS_E_AGAIN) || (err == GNUTLS_E_INTERRUPTED));
957
958 if (err < 0)
959 {
960 if (err == GNUTLS_E_FATAL_ALERT_RECEIVED)
961 {
962 mutt_error("gnutls_handshake: %s(%s)", gnutls_strerror(err),
963 gnutls_alert_get_name(gnutls_alert_get(data->session)));
964 }
965 else
966 {
967 mutt_error("gnutls_handshake: %s", gnutls_strerror(err));
968 }
969 goto fail;
970 }
971
972 if (tls_check_certificate(conn) == 0)
973 goto fail;
974
975 /* set Security Strength Factor (SSF) for SASL */
976 /* NB: gnutls_cipher_get_key_size() returns key length in bytes */
977 conn->ssf = gnutls_cipher_get_key_size(gnutls_cipher_get(data->session)) * 8;
978
980
981 if (OptGui)
982 {
983 mutt_message(_("SSL/TLS connection using %s (%s/%s/%s)"),
984 gnutls_protocol_get_name(gnutls_protocol_get_version(data->session)),
985 gnutls_kx_get_name(gnutls_kx_get(data->session)),
986 gnutls_cipher_get_name(gnutls_cipher_get(data->session)),
987 gnutls_mac_get_name(gnutls_mac_get(data->session)));
988 mutt_sleep(0);
989 }
990
991 return 0;
992
993fail:
994 gnutls_certificate_free_credentials(data->xcred);
995 gnutls_deinit(data->session);
996 FREE(&conn->sockdata);
997 return -1;
998}
999
1003static int tls_socket_poll(struct Connection *conn, time_t wait_secs)
1004{
1005 struct TlsSockData *data = conn->sockdata;
1006 if (!data)
1007 return -1;
1008
1009 if (gnutls_record_check_pending(data->session))
1010 return 1;
1011
1012 return raw_socket_poll(conn, wait_secs);
1013}
1014
1018static int tls_socket_close(struct Connection *conn)
1019{
1020 struct TlsSockData *data = conn->sockdata;
1021 if (data)
1022 {
1023 /* shut down only the write half to avoid hanging waiting for the remote to respond.
1024 *
1025 * RFC5246 7.2.1. "Closure Alerts"
1026 *
1027 * It is not required for the initiator of the close to wait for the
1028 * responding close_notify alert before closing the read side of the
1029 * connection. */
1030 gnutls_bye(data->session, GNUTLS_SHUT_WR);
1031
1032 gnutls_certificate_free_credentials(data->xcred);
1033 gnutls_deinit(data->session);
1034 FREE(&conn->sockdata);
1035 }
1036
1037 return raw_socket_close(conn);
1038}
1039
1043static int tls_socket_open(struct Connection *conn)
1044{
1045 if (raw_socket_open(conn) < 0)
1046 return -1;
1047
1048 if (tls_negotiate(conn) < 0)
1049 {
1050 tls_socket_close(conn);
1051 return -1;
1052 }
1053
1054 return 0;
1055}
1056
1060static int tls_socket_read(struct Connection *conn, char *buf, size_t count)
1061{
1062 struct TlsSockData *data = conn->sockdata;
1063 if (!data)
1064 {
1065 mutt_error(_("Error: no TLS socket open"));
1066 return -1;
1067 }
1068
1069 int rc;
1070 do
1071 {
1072 rc = gnutls_record_recv(data->session, buf, count);
1073 if (rc == GNUTLS_E_AGAIN)
1074 {
1075 // Socket would block, wait for data to become available
1077 return -1;
1078 }
1079 } while ((rc == GNUTLS_E_AGAIN) || (rc == GNUTLS_E_INTERRUPTED));
1080
1081 if (rc < 0)
1082 {
1083 mutt_error("tls_socket_read (%s)", gnutls_strerror(rc));
1084 return -1;
1085 }
1086
1087 return rc;
1088}
1089
1093static int tls_socket_write(struct Connection *conn, const char *buf, size_t count)
1094{
1095 struct TlsSockData *data = conn->sockdata;
1096 size_t sent = 0;
1097
1098 if (!data)
1099 {
1100 mutt_error(_("Error: no TLS socket open"));
1101 return -1;
1102 }
1103
1104 do
1105 {
1106 int rc;
1107 do
1108 {
1109 rc = gnutls_record_send(data->session, buf + sent, count - sent);
1110 if (rc == GNUTLS_E_AGAIN)
1111 {
1112 // Socket would block, wait for data to become available
1114 return -1;
1115 }
1116 } while ((rc == GNUTLS_E_AGAIN) || (rc == GNUTLS_E_INTERRUPTED));
1117
1118 if (rc < 0)
1119 {
1120 mutt_error("tls_socket_write (%s)", gnutls_strerror(rc));
1121 return -1;
1122 }
1123
1124 sent += rc;
1125 } while (sent < count);
1126
1127 return sent;
1128}
1129
1133static int tls_starttls_close(struct Connection *conn)
1134{
1135 int rc;
1136
1137 rc = tls_socket_close(conn);
1138 conn->read = raw_socket_read;
1139 conn->write = raw_socket_write;
1140 conn->close = raw_socket_close;
1141 conn->poll = raw_socket_poll;
1142
1143 return rc;
1144}
1145
1153{
1154 if (tls_init() < 0)
1155 return -1;
1156
1157 conn->open = tls_socket_open;
1158 conn->read = tls_socket_read;
1159 conn->write = tls_socket_write;
1160 conn->close = tls_socket_close;
1161 conn->poll = tls_socket_poll;
1162
1163 return 0;
1164}
1165
1173{
1174 if (tls_init() < 0)
1175 return -1;
1176
1177 if (tls_negotiate(conn) < 0)
1178 return -1;
1179
1180 conn->read = tls_socket_read;
1181 conn->write = tls_socket_write;
1182 conn->close = tls_starttls_close;
1183 conn->poll = tls_socket_poll;
1184
1185 return 0;
1186}
#define ARRAY_ADD(head, elem)
Add an element at the end of the array.
Definition array.h:157
#define ARRAY_LAST(head)
Convenience method to get the last element.
Definition array.h:145
#define ARRAY_HEAD_INITIALIZER
Static initializer for arrays.
Definition array.h:58
#define ARRAY_SHRINK(head, n)
Mark a number of slots at the end of the array as unused.
Definition array.h:174
int buf_add_printf(struct Buffer *buf, const char *fmt,...)
Format a string appending a Buffer.
Definition buffer.c:204
void buf_reset(struct Buffer *buf)
Reset an existing Buffer.
Definition buffer.c:76
size_t buf_addch(struct Buffer *buf, char c)
Add a single character to a Buffer.
Definition buffer.c:241
size_t buf_addstr(struct Buffer *buf, const char *s)
Add a string to a Buffer.
Definition buffer.c:226
size_t buf_strcpy(struct Buffer *buf, const char *s)
Copy a string into a Buffer.
Definition buffer.c:395
static const char * buf_string(const struct Buffer *buf)
Convert a buffer to a const char * "string".
Definition buffer.h:96
const char * cs_subset_string(const struct ConfigSubset *sub, const char *name)
Get a string config item by name.
Definition helpers.c:291
short cs_subset_number(const struct ConfigSubset *sub, const char *name)
Get a number config item by name.
Definition helpers.c:143
const char * cs_subset_path(const struct ConfigSubset *sub, const char *name)
Get a path config item by name.
Definition helpers.c:168
bool cs_subset_bool(const struct ConfigSubset *sub, const char *name)
Get a boolean config item by name.
Definition helpers.c:47
Convenience wrapper for the config headers.
Connection Library.
Shared functions that are private to Connections.
Connection Credentials.
An open network connection (socket)
Convenience wrapper for the core headers.
char * mutt_file_read_line(char *line, size_t *size, FILE *fp, int *line_num, ReadLineFlags flags)
Read a line from a file.
Definition file.c:678
#define mutt_file_fclose(FP)
Definition file.h:139
#define mutt_file_fopen(PATH, MODE)
Definition file.h:138
#define MUTT_RL_NO_FLAGS
No flags are set.
Definition file.h:40
bool OptGui
(pseudo) when the gui (and curses) are started
Definition globals.c:48
Global variables.
#define CERTERR_INSECUREALG
Certificate uses insecure algorithm.
Definition gnutls.c:60
#define CERTERR_VALID
Certificate is valid.
Definition gnutls.c:53
static int tls_check_preauth(const gnutls_datum_t *certdata, gnutls_certificate_status_t certstat, const char *hostname, int chainidx, int *certerr, int *savedcert)
Prepare a certificate for authentication.
Definition gnutls.c:316
static int tls_check_certificate(struct Connection *conn)
Check a connection's certificate.
Definition gnutls.c:627
#define CERTERR_HOSTNAME
Certificate hostname does not match.
Definition gnutls.c:58
#define CERTERR_EXPIRED
Certificate is expired.
Definition gnutls.c:54
int mutt_ssl_starttls(struct Connection *conn)
Negotiate TLS over an already opened connection.
Definition gnutls.c:1172
#define CERT_SEP
Certificate separator string.
Definition gnutls.c:65
static bool tls_check_stored_hostname(const gnutls_datum_t *cert, const char *hostname)
Does the hostname match a stored certificate?
Definition gnutls.c:189
static bool tls_socket_poll_with_timeout(struct Connection *conn)
Poll a socket with configured timeout.
Definition gnutls.c:856
#define CERTERR_NOTTRUSTED
Certificate issuer is not trusted.
Definition gnutls.c:57
static int tls_verify_peers(gnutls_session_t tlsstate, gnutls_certificate_status_t *certstat)
Wrapper for gnutls_certificate_verify_peers()
Definition gnutls.c:123
#define CERTERR_NOTYETVALID
Certificate is not yet valid.
Definition gnutls.c:55
static void add_cert(const char *title, gnutls_x509_crt_t cert, bool issuer, struct StringArray *carr)
Look up certificate info and save it to a list.
Definition gnutls.c:428
static int ProtocolPriority[]
This array needs to be large enough to hold all the possible values support by NeoMutt.
Definition gnutls.c:76
int gnutls_protocol_set_priority(gnutls_session_t session, const int *list)
static bool tls_compare_certificates(const gnutls_datum_t *peercert)
Compare certificates against $certificate_file
Definition gnutls.c:236
static void tls_fingerprint(gnutls_digest_algorithm_t algo, struct Buffer *buf, const gnutls_datum_t *data)
Create a fingerprint of a TLS Certificate.
Definition gnutls.c:160
static int tls_init(void)
Set up Gnu TLS.
Definition gnutls.c:93
#define CERTERR_OTHER
Other certificate error.
Definition gnutls.c:61
static void tls_get_client_cert(struct Connection *conn)
Get the client certificate for a TLS connection.
Definition gnutls.c:711
static int tls_check_one_certificate(const gnutls_datum_t *certdata, gnutls_certificate_status_t certstat, const char *hostname, int idx, size_t len)
Check a GnuTLS certificate.
Definition gnutls.c:473
static int tls_negotiate(struct Connection *conn)
Negotiate TLS connection.
Definition gnutls.c:881
int mutt_ssl_socket_setup(struct Connection *conn)
Set up SSL socket mulitplexor.
Definition gnutls.c:1152
#define CERTERR_SIGNERNOTCA
Certificate signer is not a CA.
Definition gnutls.c:59
static int tls_set_priority(struct TlsSockData *data)
Set the priority of various protocols.
Definition gnutls.c:821
#define CERTERR_REVOKED
Certificate has been revoked.
Definition gnutls.c:56
static int tls_starttls_close(struct Connection *conn)
Close a TLS connection - Implements Connection::close() -.
Definition gnutls.c:1133
static int tls_socket_close(struct Connection *conn)
Close a TLS socket - Implements Connection::close() -.
Definition gnutls.c:1018
int raw_socket_close(struct Connection *conn)
Close a socket - Implements Connection::close() -.
Definition raw.c:393
static int tls_socket_open(struct Connection *conn)
Open a TLS socket - Implements Connection::open() -.
Definition gnutls.c:1043
int raw_socket_open(struct Connection *conn)
Open a socket - Implements Connection::open() -.
Definition raw.c:148
static int tls_socket_poll(struct Connection *conn, time_t wait_secs)
Check if any data is waiting on a socket - Implements Connection::poll() -.
Definition gnutls.c:1003
int raw_socket_poll(struct Connection *conn, time_t wait_secs)
Check if any data is waiting on a socket - Implements Connection::poll() -.
Definition raw.c:355
static int tls_socket_read(struct Connection *conn, char *buf, size_t count)
Read data from a TLS socket - Implements Connection::read() -.
Definition gnutls.c:1060
int raw_socket_read(struct Connection *conn, char *buf, size_t len)
Read data from a socket - Implements Connection::read() -.
Definition raw.c:295
int raw_socket_write(struct Connection *conn, const char *buf, size_t count)
Write data to a socket - Implements Connection::write() -.
Definition raw.c:325
static int tls_socket_write(struct Connection *conn, const char *buf, size_t count)
Write data to a TLS socket - Implements Connection::write() -.
Definition gnutls.c:1093
int dlg_certificate(const char *title, struct StringArray *carr, bool allow_always, bool allow_skip)
Ask the user to validate the certificate -.
#define mutt_error(...)
Definition logging2.h:94
#define mutt_message(...)
Definition logging2.h:93
#define mutt_debug(LEVEL,...)
Definition logging2.h:91
@ LL_DEBUG2
Log at debug level 2.
Definition logging2.h:46
@ LL_DEBUG1
Log at debug level 1.
Definition logging2.h:45
#define countof(x)
Definition memory.h:49
#define FREE(x)
Free memory and set the pointer to NULL.
Definition memory.h:68
#define MUTT_MEM_CALLOC(n, type)
Definition memory.h:52
int mutt_date_make_tls(char *buf, size_t buflen, time_t timestamp)
Format date in TLS certificate verification style.
Definition date.c:838
time_t mutt_date_now(void)
Return the number of seconds since the Unix epoch.
Definition date.c:457
Convenience wrapper for the library headers.
#define _(a)
Definition message.h:28
char * mutt_str_dup(const char *str)
Copy a string, safely.
Definition string.c:257
int mutt_str_asprintf(char **strp, const char *fmt,...)
Definition string.c:808
bool mutt_str_equal(const char *a, const char *b)
Compare two strings.
Definition string.c:665
void string_array_clear(struct StringArray *arr)
Free all memory of a StringArray.
Definition string.c:936
size_t mutt_str_len(const char *a)
Calculate the length of a string, safely.
Definition string.c:503
void mutt_sleep(short s)
Sleep for a while.
Definition muttlib.c:787
Some miscellaneous functions.
struct Buffer * buf_pool_get(void)
Get a Buffer from the pool.
Definition pool.c:91
void buf_pool_release(struct Buffer **ptr)
Return a Buffer to the pool.
Definition pool.c:111
regmatch_t * mutt_prex_capture(enum Prex which, const char *str)
Match a precompiled regex against a string.
Definition prex.c:301
@ PREX_GNUTLS_CERT_HOST_HASH
[#H foo.com A76D 954B EB79 1F49 5B3A 0A0E 0681 65B1]
Definition prex.h:37
@ PREX_GNUTLS_CERT_HOST_HASH_MATCH_HASH
#H foo.com [A76D ... 65B1]
Definition prex.h:112
@ PREX_GNUTLS_CERT_HOST_HASH_MATCH_HOST
#H [foo.com] A76D ... 65B1
Definition prex.h:111
@ MUTT_NO
User answered 'No', or assume 'No'.
Definition quad.h:38
static regoff_t mutt_regmatch_end(const regmatch_t *match)
Return the end of a match.
Definition regex3.h:66
static regoff_t mutt_regmatch_start(const regmatch_t *match)
Return the start of a match.
Definition regex3.h:56
Handling of SSL encryption.
String manipulation buffer.
Definition buffer.h:36
char * data
Pointer to data.
Definition buffer.h:37
char host[128]
Server to login to.
Definition connaccount.h:54
void * sockdata
Backend-specific socket data.
Definition connection.h:55
int(* poll)(struct Connection *conn, time_t wait_secs)
Definition connection.h:105
int(* write)(struct Connection *conn, const char *buf, size_t count)
Definition connection.h:92
unsigned int ssf
Security strength factor, in bits (see notes)
Definition connection.h:50
int(* close)(struct Connection *conn)
Definition connection.h:116
struct ConnAccount account
Account details: username, password, etc.
Definition connection.h:49
int(* open)(struct Connection *conn)
Definition connection.h:66
int fd
Socket file descriptor.
Definition connection.h:53
int(* read)(struct Connection *conn, char *buf, size_t count)
Definition connection.h:79
Container for Accounts, Notifications.
Definition neomutt.h:41
struct ConfigSubset * sub
Inherited config items.
Definition neomutt.h:49
TLS socket data -.
Definition gnutls.c:83
gnutls_certificate_credentials_t xcred
GNUTLS certificate credentials.
Definition gnutls.c:85
gnutls_session_t session
GNUTLS session.
Definition gnutls.c:84