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