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