NeoMutt  2024-03-23-23-gec7045
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
160static void tls_fingerprint(gnutls_digest_algorithm_t algo, char *buf,
161 size_t buflen, const gnutls_datum_t *data)
162{
163 unsigned char md[64];
164 size_t n;
165
166 n = 64;
167
168 if (gnutls_fingerprint(algo, data, (char *) md, &n) < 0)
169 {
170 snprintf(buf, buflen, _("[unable to calculate]"));
171 }
172 else
173 {
174 for (int i = 0; i < (int) n; i++)
175 {
176 char ch[8] = { 0 };
177 snprintf(ch, 8, "%02X%s", md[i], ((i % 2) ? " " : ""));
178 mutt_str_cat(buf, buflen, ch);
179 }
180 buf[2 * n + n / 2 - 1] = '\0'; /* don't want trailing space */
181 }
182}
183
191static bool tls_check_stored_hostname(const gnutls_datum_t *cert, const char *hostname)
192{
193 char *linestr = NULL;
194 size_t linestrsize = 0;
195
196 /* try checking against names stored in stored certs file */
197 const char *const c_certificate_file = cs_subset_path(NeoMutt->sub, "certificate_file");
198 FILE *fp = mutt_file_fopen(c_certificate_file, "r");
199 if (!fp)
200 return false;
201
202 char buf[80] = { 0 };
203 buf[0] = '\0';
204 tls_fingerprint(GNUTLS_DIG_MD5, buf, sizeof(buf), cert);
205 while ((linestr = mutt_file_read_line(linestr, &linestrsize, fp, NULL, MUTT_RL_NO_FLAGS)))
206 {
207 regmatch_t *match = mutt_prex_capture(PREX_GNUTLS_CERT_HOST_HASH, linestr);
208 if (match)
209 {
210 regmatch_t *mhost = &match[PREX_GNUTLS_CERT_HOST_HASH_MATCH_HOST];
211 regmatch_t *mhash = &match[PREX_GNUTLS_CERT_HOST_HASH_MATCH_HASH];
212 linestr[mutt_regmatch_end(mhost)] = '\0';
213 linestr[mutt_regmatch_end(mhash)] = '\0';
214 if ((mutt_str_equal(linestr + mutt_regmatch_start(mhost), hostname)) &&
215 (mutt_str_equal(linestr + mutt_regmatch_start(mhash), buf)))
216 {
217 FREE(&linestr);
218 mutt_file_fclose(&fp);
219 return true;
220 }
221 }
222 }
223
224 mutt_file_fclose(&fp);
225
226 /* not found a matching name */
227 return false;
228}
229
236static int 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 0;
247
248 b64_data.size = st.st_size;
249 b64_data_data = mutt_mem_calloc(1, b64_data.size + 1);
250 b64_data.data = b64_data_data;
251
252 FILE *fp = mutt_file_fopen(c_certificate_file, "r");
253 if (!fp)
254 return 0;
255
256 b64_data.size = fread(b64_data.data, 1, b64_data.size, fp);
257 b64_data.data[b64_data.size] = '\0';
258 mutt_file_fclose(&fp);
259
260 do
261 {
262 const int rc = gnutls_pem_base64_decode_alloc(NULL, &b64_data, &cert);
263 if (rc != 0)
264 {
265 FREE(&b64_data_data);
266 return 0;
267 }
268
269 /* find start of cert, skipping junk */
270 ptr = (unsigned char *) strstr((char *) b64_data.data, CERT_SEP);
271 if (!ptr)
272 {
273 gnutls_free(cert.data);
274 FREE(&b64_data_data);
275 return 0;
276 }
277 /* find start of next cert */
278 ptr = (unsigned char *) strstr((char *) ptr + 1, CERT_SEP);
279
280 b64_data.size = b64_data.size - (ptr - b64_data.data);
281 b64_data.data = ptr;
282
283 if (cert.size == peercert->size)
284 {
285 if (memcmp(cert.data, peercert->data, cert.size) == 0)
286 {
287 /* match found */
288 gnutls_free(cert.data);
289 FREE(&b64_data_data);
290 return 1;
291 }
292 }
293
294 gnutls_free(cert.data);
295 } while (ptr);
296
297 /* no match found */
298 FREE(&b64_data_data);
299 return 0;
300}
301
313static int tls_check_preauth(const gnutls_datum_t *certdata,
314 gnutls_certificate_status_t certstat, const char *hostname,
315 int chainidx, int *certerr, int *savedcert)
316{
317 gnutls_x509_crt_t cert;
318
319 *certerr = CERTERR_VALID;
320 *savedcert = 0;
321
322 if (gnutls_x509_crt_init(&cert) < 0)
323 {
324 mutt_error(_("Error initialising gnutls certificate data"));
325 return -1;
326 }
327
328 if (gnutls_x509_crt_import(cert, certdata, GNUTLS_X509_FMT_DER) < 0)
329 {
330 mutt_error(_("Error processing certificate data"));
331 gnutls_x509_crt_deinit(cert);
332 return -1;
333 }
334
335 /* Note: tls_negotiate() contains a call to
336 * gnutls_certificate_set_verify_flags() with a flag disabling
337 * GnuTLS checking of the dates. So certstat shouldn't have the
338 * GNUTLS_CERT_EXPIRED and GNUTLS_CERT_NOT_ACTIVATED bits set. */
339 const bool c_ssl_verify_dates = cs_subset_bool(NeoMutt->sub, "ssl_verify_dates");
340 if (c_ssl_verify_dates != MUTT_NO)
341 {
342 if (gnutls_x509_crt_get_expiration_time(cert) < mutt_date_now())
343 *certerr |= CERTERR_EXPIRED;
344 if (gnutls_x509_crt_get_activation_time(cert) > mutt_date_now())
345 *certerr |= CERTERR_NOTYETVALID;
346 }
347
348 const bool c_ssl_verify_host = cs_subset_bool(NeoMutt->sub, "ssl_verify_host");
349 if ((chainidx == 0) && (c_ssl_verify_host != MUTT_NO) &&
350 !gnutls_x509_crt_check_hostname(cert, hostname) &&
351 !tls_check_stored_hostname(certdata, hostname))
352 {
353 *certerr |= CERTERR_HOSTNAME;
354 }
355
356 if (certstat & GNUTLS_CERT_REVOKED)
357 {
358 *certerr |= CERTERR_REVOKED;
359 certstat ^= GNUTLS_CERT_REVOKED;
360 }
361
362 /* see whether certificate is in our cache (certificates file) */
363 if (tls_compare_certificates(certdata))
364 {
365 *savedcert = 1;
366
367 /* We check above for certs with bad dates or that are revoked.
368 * These must be accepted manually each time. Otherwise, we
369 * accept saved certificates as valid. */
370 if (*certerr == CERTERR_VALID)
371 {
372 gnutls_x509_crt_deinit(cert);
373 return 0;
374 }
375 }
376
377 if (certstat & GNUTLS_CERT_INVALID)
378 {
379 *certerr |= CERTERR_NOTTRUSTED;
380 certstat ^= GNUTLS_CERT_INVALID;
381 }
382
383 if (certstat & GNUTLS_CERT_SIGNER_NOT_FOUND)
384 {
385 /* NB: already cleared if cert in cache */
386 *certerr |= CERTERR_NOTTRUSTED;
387 certstat ^= GNUTLS_CERT_SIGNER_NOT_FOUND;
388 }
389
390 if (certstat & GNUTLS_CERT_SIGNER_NOT_CA)
391 {
392 /* NB: already cleared if cert in cache */
393 *certerr |= CERTERR_SIGNERNOTCA;
394 certstat ^= GNUTLS_CERT_SIGNER_NOT_CA;
395 }
396
397 if (certstat & GNUTLS_CERT_INSECURE_ALGORITHM)
398 {
399 /* NB: already cleared if cert in cache */
400 *certerr |= CERTERR_INSECUREALG;
401 certstat ^= GNUTLS_CERT_INSECURE_ALGORITHM;
402 }
403
404 /* we've been zeroing the interesting bits in certstat -
405 * don't return OK if there are any unhandled bits we don't
406 * understand */
407 if (certstat != 0)
408 *certerr |= CERTERR_OTHER;
409
410 gnutls_x509_crt_deinit(cert);
411
412 if (*certerr == CERTERR_VALID)
413 return 0;
414
415 return -1;
416}
417
425static void add_cert(const char *title, gnutls_x509_crt_t cert, bool issuer,
426 struct CertArray *carr)
427{
428 static const char *part[] = {
429 GNUTLS_OID_X520_COMMON_NAME, // CN
430 GNUTLS_OID_PKCS9_EMAIL, // Email
431 GNUTLS_OID_X520_ORGANIZATION_NAME, // O
432 GNUTLS_OID_X520_ORGANIZATIONAL_UNIT_NAME, // OU
433 GNUTLS_OID_X520_LOCALITY_NAME, // L
434 GNUTLS_OID_X520_STATE_OR_PROVINCE_NAME, // ST
435 GNUTLS_OID_X520_COUNTRY_NAME, // C
436 };
437
438 char buf[128] = { 0 };
439 int rc;
440
441 // Allocate formatted strings and let the array take ownership
442 ARRAY_ADD(carr, mutt_str_dup(title));
443
444 for (size_t i = 0; i < mutt_array_size(part); i++)
445 {
446 size_t buflen = sizeof(buf);
447 if (issuer)
448 rc = gnutls_x509_crt_get_issuer_dn_by_oid(cert, part[i], 0, 0, buf, &buflen);
449 else
450 rc = gnutls_x509_crt_get_dn_by_oid(cert, part[i], 0, 0, buf, &buflen);
451 if (rc != 0)
452 continue;
453
454 char *line = NULL;
455 mutt_str_asprintf(&line, " %s", buf);
456 ARRAY_ADD(carr, line);
457 }
458}
459
470static int tls_check_one_certificate(const gnutls_datum_t *certdata,
471 gnutls_certificate_status_t certstat,
472 const char *hostname, int idx, size_t len)
473{
474 struct CertArray carr = ARRAY_HEAD_INITIALIZER;
475 int certerr, savedcert;
476 gnutls_x509_crt_t cert;
477 char fpbuf[128] = { 0 };
478 time_t t;
479 char datestr[30] = { 0 };
480 char title[256] = { 0 };
481 gnutls_datum_t pemdata = { 0 };
482
483 if (tls_check_preauth(certdata, certstat, hostname, idx, &certerr, &savedcert) == 0)
484 return 1;
485
486 /* interactive check from user */
487 if (gnutls_x509_crt_init(&cert) < 0)
488 {
489 mutt_error(_("Error initialising gnutls certificate data"));
490 return 0;
491 }
492
493 if (gnutls_x509_crt_import(cert, certdata, GNUTLS_X509_FMT_DER) < 0)
494 {
495 mutt_error(_("Error processing certificate data"));
496 gnutls_x509_crt_deinit(cert);
497 return 0;
498 }
499
500 add_cert(_("This certificate belongs to:"), cert, false, &carr);
501 ARRAY_ADD(&carr, NULL);
502 add_cert(_("This certificate was issued by:"), cert, true, &carr);
503
504 ARRAY_ADD(&carr, NULL);
505 ARRAY_ADD(&carr, mutt_str_dup(_("This certificate is valid")));
506
507 char *line = NULL;
508 t = gnutls_x509_crt_get_activation_time(cert);
509 mutt_date_make_tls(datestr, sizeof(datestr), t);
510 mutt_str_asprintf(&line, _(" from %s"), datestr);
511 ARRAY_ADD(&carr, line);
512
513 t = gnutls_x509_crt_get_expiration_time(cert);
514 mutt_date_make_tls(datestr, sizeof(datestr), t);
515 mutt_str_asprintf(&line, _(" to %s"), datestr);
516 ARRAY_ADD(&carr, line);
517 ARRAY_ADD(&carr, NULL);
518
519 fpbuf[0] = '\0';
520 tls_fingerprint(GNUTLS_DIG_SHA, fpbuf, sizeof(fpbuf), certdata);
521 mutt_str_asprintf(&line, _("SHA1 Fingerprint: %s"), fpbuf);
522 ARRAY_ADD(&carr, line);
523 fpbuf[0] = '\0';
524 fpbuf[40] = '\0'; /* Ensure the second printed line is null terminated */
525 tls_fingerprint(GNUTLS_DIG_SHA256, fpbuf, sizeof(fpbuf), certdata);
526 fpbuf[39] = '\0'; /* Divide into two carr of output */
527 mutt_str_asprintf(&line, "%s%s", _("SHA256 Fingerprint: "), fpbuf);
528 ARRAY_ADD(&carr, line);
529 mutt_str_asprintf(&line, "%*s%s", (int) mutt_str_len(_("SHA256 Fingerprint: ")),
530 "", fpbuf + 40);
531 ARRAY_ADD(&carr, line);
532
533 if (certerr)
534 ARRAY_ADD(&carr, NULL);
535
536 if (certerr & CERTERR_NOTYETVALID)
537 {
538 ARRAY_ADD(&carr, mutt_str_dup(_("WARNING: Server certificate is not yet valid")));
539 }
540 if (certerr & CERTERR_EXPIRED)
541 {
542 ARRAY_ADD(&carr, mutt_str_dup(_("WARNING: Server certificate has expired")));
543 }
544 if (certerr & CERTERR_REVOKED)
545 {
546 ARRAY_ADD(&carr, mutt_str_dup(_("WARNING: Server certificate has been revoked")));
547 }
548 if (certerr & CERTERR_HOSTNAME)
549 {
550 ARRAY_ADD(&carr, mutt_str_dup(_("WARNING: Server hostname does not match certificate")));
551 }
552 if (certerr & CERTERR_SIGNERNOTCA)
553 {
554 ARRAY_ADD(&carr, mutt_str_dup(_("WARNING: Signer of server certificate is not a CA")));
555 }
556 if (certerr & CERTERR_INSECUREALG)
557 {
558 ARRAY_ADD(&carr, mutt_str_dup(_("Warning: Server certificate was signed using an insecure algorithm")));
559 }
560
561 snprintf(title, sizeof(title),
562 _("SSL Certificate check (certificate %zu of %zu in chain)"), len - idx, len);
563
564 const char *const c_certificate_file = cs_subset_path(NeoMutt->sub, "certificate_file");
565 const bool allow_always = (c_certificate_file && !savedcert &&
567 int rc = dlg_certificate(title, &carr, allow_always, false);
568 if (rc == 3) // Accept always
569 {
570 bool saved = false;
571 FILE *fp = mutt_file_fopen(c_certificate_file, "a");
572 if (fp)
573 {
574 if (certerr & CERTERR_HOSTNAME) // Save hostname if necessary
575 {
576 fpbuf[0] = '\0';
577 tls_fingerprint(GNUTLS_DIG_MD5, fpbuf, sizeof(fpbuf), certdata);
578 fprintf(fp, "#H %s %s\n", hostname, fpbuf);
579 saved = true;
580 }
581 if (certerr ^ CERTERR_HOSTNAME) // Save the cert for all other errors
582 {
583 int rc2 = gnutls_pem_base64_encode_alloc("CERTIFICATE", certdata, &pemdata);
584 if (rc2 == 0)
585 {
586 if (fwrite(pemdata.data, pemdata.size, 1, fp) == 1)
587 {
588 saved = true;
589 }
590 gnutls_free(pemdata.data);
591 }
592 }
593 mutt_file_fclose(&fp);
594 }
595 if (saved)
596 mutt_message(_("Certificate saved"));
597 else
598 mutt_error(_("Warning: Couldn't save certificate"));
599 }
600
601 cert_array_clear(&carr);
602 ARRAY_FREE(&carr);
603 gnutls_x509_crt_deinit(cert);
604 return (rc > 1);
605}
606
613static int tls_check_certificate(struct Connection *conn)
614{
615 struct TlsSockData *data = conn->sockdata;
616 gnutls_session_t session = data->session;
617 const gnutls_datum_t *cert_list = NULL;
618 unsigned int cert_list_size = 0;
619 gnutls_certificate_status_t certstat;
620 int certerr, savedcert, rc = 0;
621 int max_preauth_pass = -1;
622
623 /* tls_verify_peers() calls gnutls_certificate_verify_peers2(),
624 * which verifies the auth_type is GNUTLS_CRD_CERTIFICATE
625 * and that get_certificate_type() for the server is GNUTLS_CRT_X509.
626 * If it returns 0, certstat will be set with failure codes for the first
627 * cert in the chain(from CA to host) with an error.
628 */
629 if (tls_verify_peers(session, &certstat) != 0)
630 return 0;
631
632 cert_list = gnutls_certificate_get_peers(session, &cert_list_size);
633 if (!cert_list)
634 {
635 mutt_error(_("Unable to get certificate from peer"));
636 return 0;
637 }
638
639 /* tls_verify_peers doesn't check hostname or expiration, so walk
640 * from most specific to least checking these. If we see a saved certificate,
641 * its status short-circuits the remaining checks. */
642 int preauthrc = 0;
643 for (int i = 0; i < cert_list_size; i++)
644 {
645 rc = tls_check_preauth(&cert_list[i], certstat, conn->account.host, i,
646 &certerr, &savedcert);
647 preauthrc += rc;
648 if (!preauthrc)
649 max_preauth_pass = i;
650
651 if (savedcert)
652 {
653 if (preauthrc == 0)
654 return 1;
655 break;
656 }
657 }
658
659 /* then check interactively, starting from chain root */
660 for (int i = cert_list_size - 1; i >= 0; i--)
661 {
662 rc = tls_check_one_certificate(&cert_list[i], certstat, conn->account.host,
663 i, cert_list_size);
664
665 /* Stop checking if the menu cert is aborted or rejected. */
666 if (rc == 0)
667 break;
668
669 /* add signers to trust set, then reverify */
670 if (i)
671 {
672 int rcsettrust = gnutls_certificate_set_x509_trust_mem(data->xcred, &cert_list[i],
673 GNUTLS_X509_FMT_DER);
674 if (rcsettrust != 1)
675 mutt_debug(LL_DEBUG1, "error trusting certificate %d: %d\n", i, rcsettrust);
676
677 if (tls_verify_peers(session, &certstat) != 0)
678 return 0;
679
680 /* If the cert chain now verifies, and all lower certs already
681 * passed preauth, we are done. */
682 if (!certstat && (max_preauth_pass >= (i - 1)))
683 return 1;
684 }
685 }
686
687 return rc;
688}
689
697static void tls_get_client_cert(struct Connection *conn)
698{
699 struct TlsSockData *data = conn->sockdata;
700 gnutls_x509_crt_t clientcrt;
701 char *cn = NULL;
702 size_t cnlen = 0;
703 int rc;
704
705 /* get our cert CN if we have one */
706 const gnutls_datum_t *crtdata = gnutls_certificate_get_ours(data->session);
707 if (!crtdata)
708 return;
709
710 if (gnutls_x509_crt_init(&clientcrt) < 0)
711 {
712 mutt_debug(LL_DEBUG1, "Failed to init gnutls crt\n");
713 return;
714 }
715
716 if (gnutls_x509_crt_import(clientcrt, crtdata, GNUTLS_X509_FMT_DER) < 0)
717 {
718 mutt_debug(LL_DEBUG1, "Failed to import gnutls client crt\n");
719 goto err;
720 }
721
722 /* get length of CN, then grab it. */
723 rc = gnutls_x509_crt_get_dn_by_oid(clientcrt, GNUTLS_OID_X520_COMMON_NAME, 0,
724 0, NULL, &cnlen);
725 if (((rc >= 0) || (rc == GNUTLS_E_SHORT_MEMORY_BUFFER)) && (cnlen > 0))
726 {
727 cn = mutt_mem_calloc(1, cnlen);
728 if (gnutls_x509_crt_get_dn_by_oid(clientcrt, GNUTLS_OID_X520_COMMON_NAME, 0,
729 0, cn, &cnlen) < 0)
730 {
731 goto err;
732 }
733 mutt_debug(LL_DEBUG2, "client certificate CN: %s\n", cn);
734
735 /* if we are using a client cert, SASL may expect an external auth name */
736 if (mutt_account_getuser(&conn->account) < 0)
737 mutt_debug(LL_DEBUG1, "Couldn't get user info\n");
738 }
739
740err:
741 FREE(&cn);
742 gnutls_x509_crt_deinit(clientcrt);
743}
744
745#ifdef HAVE_GNUTLS_PRIORITY_SET_DIRECT
752static int tls_set_priority(struct TlsSockData *data)
753{
754 size_t nproto = 5;
755 int rv = -1;
756
757 struct Buffer *priority = buf_pool_get();
758
759 const char *const c_ssl_ciphers = cs_subset_string(NeoMutt->sub, "ssl_ciphers");
760 if (c_ssl_ciphers)
761 buf_strcpy(priority, c_ssl_ciphers);
762 else
763 buf_strcpy(priority, "NORMAL");
764
765 const bool c_ssl_use_tlsv1_3 = cs_subset_bool(NeoMutt->sub, "ssl_use_tlsv1_3");
766 if (!c_ssl_use_tlsv1_3)
767 {
768 nproto--;
769 buf_addstr(priority, ":-VERS-TLS1.3");
770 }
771 const bool c_ssl_use_tlsv1_2 = cs_subset_bool(NeoMutt->sub, "ssl_use_tlsv1_2");
772 if (!c_ssl_use_tlsv1_2)
773 {
774 nproto--;
775 buf_addstr(priority, ":-VERS-TLS1.2");
776 }
777 const bool c_ssl_use_tlsv1_1 = cs_subset_bool(NeoMutt->sub, "ssl_use_tlsv1_1");
778 if (!c_ssl_use_tlsv1_1)
779 {
780 nproto--;
781 buf_addstr(priority, ":-VERS-TLS1.1");
782 }
783 const bool c_ssl_use_tlsv1 = cs_subset_bool(NeoMutt->sub, "ssl_use_tlsv1");
784 if (!c_ssl_use_tlsv1)
785 {
786 nproto--;
787 buf_addstr(priority, ":-VERS-TLS1.0");
788 }
789 const bool c_ssl_use_sslv3 = cs_subset_bool(NeoMutt->sub, "ssl_use_sslv3");
790 if (!c_ssl_use_sslv3)
791 {
792 nproto--;
793 buf_addstr(priority, ":-VERS-SSL3.0");
794 }
795
796 if (nproto == 0)
797 {
798 mutt_error(_("All available protocols for TLS/SSL connection disabled"));
799 goto cleanup;
800 }
801
802 int err = gnutls_priority_set_direct(data->session, buf_string(priority), NULL);
803 if (err < 0)
804 {
805 mutt_error("gnutls_priority_set_direct(%s): %s", buf_string(priority),
806 gnutls_strerror(err));
807 goto cleanup;
808 }
809
810 rv = 0;
811
812cleanup:
813 buf_pool_release(&priority);
814 return rv;
815}
816
817#else
825{
826 size_t nproto = 0; /* number of tls/ssl protocols */
827
828 const bool c_ssl_use_tlsv1_2 = cs_subset_bool(NeoMutt->sub, "ssl_use_tlsv1_2");
829 if (c_ssl_use_tlsv1_2)
830 ProtocolPriority[nproto++] = GNUTLS_TLS1_2;
831 const bool c_ssl_use_tlsv1_1 = cs_subset_bool(NeoMutt->sub, "ssl_use_tlsv1_1");
832 if (c_ssl_use_tlsv1_1)
833 ProtocolPriority[nproto++] = GNUTLS_TLS1_1;
834 const bool c_ssl_use_tlsv1 = cs_subset_bool(NeoMutt->sub, "ssl_use_tlsv1");
835 if (c_ssl_use_tlsv1)
836 ProtocolPriority[nproto++] = GNUTLS_TLS1;
837 const bool c_ssl_use_sslv3 = cs_subset_bool(NeoMutt->sub, "ssl_use_sslv3");
838 if (c_ssl_use_sslv3)
839 ProtocolPriority[nproto++] = GNUTLS_SSL3;
840 ProtocolPriority[nproto] = 0;
841
842 if (nproto == 0)
843 {
844 mutt_error(_("All available protocols for TLS/SSL connection disabled"));
845 return -1;
846 }
847
848 const char *const c_ssl_ciphers = cs_subset_string(NeoMutt->sub, "ssl_ciphers");
849 if (c_ssl_ciphers)
850 {
851 mutt_error(_("Explicit ciphersuite selection via $ssl_ciphers not supported"));
852 }
853
854 /* We use default priorities (see gnutls documentation),
855 * except for protocol version */
856 gnutls_set_default_priority(data->session);
858 return 0;
859}
860#endif
861
871static int tls_negotiate(struct Connection *conn)
872{
873 struct TlsSockData *data = mutt_mem_calloc(1, sizeof(struct TlsSockData));
874 conn->sockdata = data;
875 int err = gnutls_certificate_allocate_credentials(&data->xcred);
876 if (err < 0)
877 {
878 FREE(&conn->sockdata);
879 mutt_error("gnutls_certificate_allocate_credentials: %s", gnutls_strerror(err));
880 return -1;
881 }
882
883 const char *const c_certificate_file = cs_subset_path(NeoMutt->sub, "certificate_file");
884 gnutls_certificate_set_x509_trust_file(data->xcred, c_certificate_file, GNUTLS_X509_FMT_PEM);
885 /* ignore errors, maybe file doesn't exist yet */
886
887 const char *const c_ssl_ca_certificates_file = cs_subset_path(NeoMutt->sub, "ssl_ca_certificates_file");
888 if (c_ssl_ca_certificates_file)
889 {
890 gnutls_certificate_set_x509_trust_file(data->xcred, c_ssl_ca_certificates_file,
891 GNUTLS_X509_FMT_PEM);
892 }
893
894 const char *const c_ssl_client_cert = cs_subset_path(NeoMutt->sub, "ssl_client_cert");
895 if (c_ssl_client_cert)
896 {
897 mutt_debug(LL_DEBUG2, "Using client certificate %s\n", c_ssl_client_cert);
898 gnutls_certificate_set_x509_key_file(data->xcred, c_ssl_client_cert,
899 c_ssl_client_cert, GNUTLS_X509_FMT_PEM);
900 }
901
902#ifdef HAVE_DECL_GNUTLS_VERIFY_DISABLE_TIME_CHECKS
903 /* disable checking certificate activation/expiration times
904 * in gnutls, we do the checks ourselves */
905 gnutls_certificate_set_verify_flags(data->xcred, GNUTLS_VERIFY_DISABLE_TIME_CHECKS);
906#endif
907
908 err = gnutls_init(&data->session, GNUTLS_CLIENT);
909 if (err)
910 {
911 mutt_error("gnutls_init: %s", gnutls_strerror(err));
912 goto fail;
913 }
914
915 /* set socket */
916 gnutls_transport_set_ptr(data->session, (gnutls_transport_ptr_t) (long) conn->fd);
917
918 if (gnutls_server_name_set(data->session, GNUTLS_NAME_DNS, conn->account.host,
919 mutt_str_len(conn->account.host)))
920 {
921 mutt_error(_("Warning: unable to set TLS SNI host name"));
922 }
923
924 if (tls_set_priority(data) < 0)
925 {
926 goto fail;
927 }
928
929 const short c_ssl_min_dh_prime_bits = cs_subset_number(NeoMutt->sub, "ssl_min_dh_prime_bits");
930 if (c_ssl_min_dh_prime_bits > 0)
931 {
932 gnutls_dh_set_prime_bits(data->session, c_ssl_min_dh_prime_bits);
933 }
934
935 gnutls_credentials_set(data->session, GNUTLS_CRD_CERTIFICATE, data->xcred);
936
937 do
938 {
939 err = gnutls_handshake(data->session);
940 } while ((err == GNUTLS_E_AGAIN) || (err == GNUTLS_E_INTERRUPTED));
941
942 if (err < 0)
943 {
944 if (err == GNUTLS_E_FATAL_ALERT_RECEIVED)
945 {
946 mutt_error("gnutls_handshake: %s(%s)", gnutls_strerror(err),
947 gnutls_alert_get_name(gnutls_alert_get(data->session)));
948 }
949 else
950 {
951 mutt_error("gnutls_handshake: %s", gnutls_strerror(err));
952 }
953 goto fail;
954 }
955
956 if (tls_check_certificate(conn) == 0)
957 goto fail;
958
959 /* set Security Strength Factor (SSF) for SASL */
960 /* NB: gnutls_cipher_get_key_size() returns key length in bytes */
961 conn->ssf = gnutls_cipher_get_key_size(gnutls_cipher_get(data->session)) * 8;
962
964
965 if (!OptNoCurses)
966 {
967 mutt_message(_("SSL/TLS connection using %s (%s/%s/%s)"),
968 gnutls_protocol_get_name(gnutls_protocol_get_version(data->session)),
969 gnutls_kx_get_name(gnutls_kx_get(data->session)),
970 gnutls_cipher_get_name(gnutls_cipher_get(data->session)),
971 gnutls_mac_get_name(gnutls_mac_get(data->session)));
972 mutt_sleep(0);
973 }
974
975 return 0;
976
977fail:
978 gnutls_certificate_free_credentials(data->xcred);
979 gnutls_deinit(data->session);
980 FREE(&conn->sockdata);
981 return -1;
982}
983
987static int tls_socket_poll(struct Connection *conn, time_t wait_secs)
988{
989 struct TlsSockData *data = conn->sockdata;
990 if (!data)
991 return -1;
992
993 if (gnutls_record_check_pending(data->session))
994 return 1;
995
996 return raw_socket_poll(conn, wait_secs);
997}
998
1002static int tls_socket_close(struct Connection *conn)
1003{
1004 struct TlsSockData *data = conn->sockdata;
1005 if (data)
1006 {
1007 /* shut down only the write half to avoid hanging waiting for the remote to respond.
1008 *
1009 * RFC5246 7.2.1. "Closure Alerts"
1010 *
1011 * It is not required for the initiator of the close to wait for the
1012 * responding close_notify alert before closing the read side of the
1013 * connection. */
1014 gnutls_bye(data->session, GNUTLS_SHUT_WR);
1015
1016 gnutls_certificate_free_credentials(data->xcred);
1017 gnutls_deinit(data->session);
1018 FREE(&conn->sockdata);
1019 }
1020
1021 return raw_socket_close(conn);
1022}
1023
1027static int tls_socket_open(struct Connection *conn)
1028{
1029 if (raw_socket_open(conn) < 0)
1030 return -1;
1031
1032 if (tls_negotiate(conn) < 0)
1033 {
1034 tls_socket_close(conn);
1035 return -1;
1036 }
1037
1038 return 0;
1039}
1040
1044static int tls_socket_read(struct Connection *conn, char *buf, size_t count)
1045{
1046 struct TlsSockData *data = conn->sockdata;
1047 if (!data)
1048 {
1049 mutt_error(_("Error: no TLS socket open"));
1050 return -1;
1051 }
1052
1053 int rc;
1054 do
1055 {
1056 rc = gnutls_record_recv(data->session, buf, count);
1057 } while ((rc == GNUTLS_E_AGAIN) || (rc == GNUTLS_E_INTERRUPTED));
1058
1059 if (rc < 0)
1060 {
1061 mutt_error("tls_socket_read (%s)", gnutls_strerror(rc));
1062 return -1;
1063 }
1064
1065 return rc;
1066}
1067
1071static int tls_socket_write(struct Connection *conn, const char *buf, size_t count)
1072{
1073 struct TlsSockData *data = conn->sockdata;
1074 size_t sent = 0;
1075
1076 if (!data)
1077 {
1078 mutt_error(_("Error: no TLS socket open"));
1079 return -1;
1080 }
1081
1082 do
1083 {
1084 int rc;
1085 do
1086 {
1087 rc = gnutls_record_send(data->session, buf + sent, count - sent);
1088 } while ((rc == GNUTLS_E_AGAIN) || (rc == GNUTLS_E_INTERRUPTED));
1089
1090 if (rc < 0)
1091 {
1092 mutt_error("tls_socket_write (%s)", gnutls_strerror(rc));
1093 return -1;
1094 }
1095
1096 sent += rc;
1097 } while (sent < count);
1098
1099 return sent;
1100}
1101
1105static int tls_starttls_close(struct Connection *conn)
1106{
1107 int rc;
1108
1109 rc = tls_socket_close(conn);
1110 conn->read = raw_socket_read;
1111 conn->write = raw_socket_write;
1112 conn->close = raw_socket_close;
1113 conn->poll = raw_socket_poll;
1114
1115 return rc;
1116}
1117
1125{
1126 if (tls_init() < 0)
1127 return -1;
1128
1129 conn->open = tls_socket_open;
1130 conn->read = tls_socket_read;
1131 conn->write = tls_socket_write;
1132 conn->close = tls_socket_close;
1133 conn->poll = tls_socket_poll;
1134
1135 return 0;
1136}
1137
1145{
1146 if (tls_init() < 0)
1147 return -1;
1148
1149 if (tls_negotiate(conn) < 0)
1150 return -1;
1151
1152 conn->read = tls_socket_read;
1153 conn->write = tls_socket_write;
1154 conn->close = tls_starttls_close;
1155 conn->poll = tls_socket_poll;
1156
1157 return 0;
1158}
#define ARRAY_ADD(head, elem)
Add an element at the end of the array.
Definition: array.h:156
#define ARRAY_FREE(head)
Release all memory.
Definition: array.h:204
#define ARRAY_HEAD_INITIALIZER
Static initializer for arrays.
Definition: array.h:58
size_t buf_addstr(struct Buffer *buf, const char *s)
Add a string to a Buffer.
Definition: buffer.c:243
size_t buf_strcpy(struct Buffer *buf, const char *s)
Copy a string into a Buffer.
Definition: buffer.c:412
static const char * buf_string(const struct Buffer *buf)
Convert a buffer to a const char * "string".
Definition: buffer.h:97
const char * cs_subset_string(const struct ConfigSubset *sub, const char *name)
Get a string config item by name.
Definition: helpers.c:292
short cs_subset_number(const struct ConfigSubset *sub, const char *name)
Get a number config item by name.
Definition: helpers.c:144
const char * cs_subset_path(const struct ConfigSubset *sub, const char *name)
Get a path config item by name.
Definition: helpers.c:169
bool cs_subset_bool(const struct ConfigSubset *sub, const char *name)
Get a boolean config item by name.
Definition: helpers.c:48
Convenience wrapper for the config headers.
int mutt_account_getuser(struct ConnAccount *cac)
Retrieve username into ConnAccount, if necessary.
Definition: connaccount.c:52
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:801
#define mutt_file_fclose(FP)
Definition: file.h:148
#define mutt_file_fopen(PATH, MODE)
Definition: file.h:147
#define MUTT_RL_NO_FLAGS
No flags are set.
Definition: file.h:40
bool OptNoCurses
(pseudo) when sending in batch mode
Definition: globals.c:72
#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:313
static int tls_check_certificate(struct Connection *conn)
Check a connection's certificate.
Definition: gnutls.c:613
#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:1144
#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:191
#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 int tls_compare_certificates(const gnutls_datum_t *peercert)
Compare certificates against $certificate_file
Definition: gnutls.c:236
static int tls_init(void)
Set up Gnu TLS.
Definition: gnutls.c:92
static void tls_fingerprint(gnutls_digest_algorithm_t algo, char *buf, size_t buflen, const gnutls_datum_t *data)
Create a fingerprint of a TLS Certificate.
Definition: gnutls.c:160
#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:697
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:470
static int tls_negotiate(struct Connection *conn)
Negotiate TLS connection.
Definition: gnutls.c:871
int mutt_ssl_socket_setup(struct Connection *conn)
Set up SSL socket mulitplexor.
Definition: gnutls.c:1124
#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:425
static int tls_set_priority(struct TlsSockData *data)
Set the priority of various protocols.
Definition: gnutls.c:824
#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:1105
static int tls_socket_close(struct Connection *conn)
Close a TLS socket - Implements Connection::close() -.
Definition: gnutls.c:1002
int raw_socket_close(struct Connection *conn)
Close a socket - Implements Connection::close() -.
Definition: raw.c:374
static int tls_socket_open(struct Connection *conn)
Open a TLS socket - Implements Connection::open() -.
Definition: gnutls.c:1027
int raw_socket_open(struct Connection *conn)
Open a socket - Implements Connection::open() -.
Definition: raw.c:129
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:987
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:336
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:1044
int raw_socket_read(struct Connection *conn, char *buf, size_t len)
Read data from a socket - Implements Connection::read() -.
Definition: raw.c:276
int raw_socket_write(struct Connection *conn, const char *buf, size_t count)
Write data to a socket - Implements Connection::write() -.
Definition: raw.c:306
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:1071
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:92
#define mutt_message(...)
Definition: logging2.h:91
#define mutt_debug(LEVEL,...)
Definition: logging2.h:89
@ LL_DEBUG2
Log at debug level 2.
Definition: logging2.h:44
@ LL_DEBUG1
Log at debug level 1.
Definition: logging2.h:43
void * mutt_mem_calloc(size_t nmemb, size_t size)
Allocate zeroed memory on the heap.
Definition: memory.c:50
#define FREE(x)
Definition: memory.h:45
#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:839
time_t mutt_date_now(void)
Return the number of seconds since the Unix epoch.
Definition: date.c:455
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:253
int mutt_str_asprintf(char **strp, const char *fmt,...)
Definition: string.c:852
bool mutt_str_equal(const char *a, const char *b)
Compare two strings.
Definition: string.c:709
size_t mutt_str_len(const char *a)
Calculate the length of a string, safely.
Definition: string.c:545
char * mutt_str_cat(char *buf, size_t buflen, const char *s)
Concatenate two strings.
Definition: string.c:268
void mutt_sleep(short s)
Sleep for a while.
Definition: muttlib.c:878
Some miscellaneous functions.
struct Buffer * buf_pool_get(void)
Get a Buffer from the pool.
Definition: pool.c:81
void buf_pool_release(struct Buffer **ptr)
Return a Buffer to the pool.
Definition: pool.c:94
regmatch_t * mutt_prex_capture(enum Prex which, const char *str)
Match a precompiled regex against a string.
Definition: prex.c:295
@ 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
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:41
struct ConfigSubset * sub
Inherited config items.
Definition: neomutt.h:45
TLS socket data -.
Definition: gnutls.c:82
gnutls_certificate_credentials_t xcred
Definition: gnutls.c:84
gnutls_session_t session
Definition: gnutls.c:83