2 * Copyright notice from original mutt:
3 * Copyright (C) 2001 Marco d'Itri <md@linux.it>
4 * Copyright (C) 2001-2004 Andrew McDonald <andrew@mcdonald.org.uk>
6 * This file is part of mutt-ng, see http://www.muttng.org/.
7 * It's licensed under the GNU General Public License,
8 * please see the file GPL in the top level source directory.
17 #include <gnutls/gnutls.h>
18 #include <gnutls/x509.h>
19 #ifdef HAVE_GNUTLS_OPENSSL_H
20 #include <gnutls/openssl.h>
23 #include <lib-lib/mem.h>
24 #include <lib-lib/str.h>
25 #include <lib-lib/macros.h>
26 #include <lib-lib/file.h>
28 #include <lib-ui/curses.h>
29 #include <lib-ui/menu.h>
32 #include "mutt_socket.h"
37 typedef struct _tlssockdata {
39 gnutls_certificate_credentials xcred;
42 /* local prototypes */
43 static int tls_socket_read (CONNECTION * conn, char *buf, size_t len);
44 static int tls_socket_write (CONNECTION * conn, const char *buf, size_t len);
45 static int tls_socket_open (CONNECTION * conn);
46 static int tls_socket_close (CONNECTION * conn);
47 static int tls_starttls_close (CONNECTION * conn);
49 static int tls_init (void);
50 static int tls_negotiate (CONNECTION * conn);
51 static int tls_check_certificate (CONNECTION * conn);
54 static int tls_init (void)
56 static unsigned char init_complete = 0;
62 err = gnutls_global_init ();
64 mutt_error (_("gnutls_global_init: %s"), gnutls_strerror (err));
73 int mutt_ssl_socket_setup (CONNECTION * conn)
78 conn->conn_open = tls_socket_open;
79 conn->conn_read = tls_socket_read;
80 conn->conn_write = tls_socket_write;
81 conn->conn_close = tls_socket_close;
86 static int tls_socket_read (CONNECTION * conn, char *buf, size_t len)
88 tlssockdata *data = conn->sockdata;
92 mutt_error (_("Error: no TLS socket open"));
97 ret = gnutls_record_recv (data->state, buf, len);
98 if (gnutls_error_is_fatal (ret) == 1) {
99 mutt_error (_("tls_socket_read (%s)"), gnutls_strerror (ret));
106 static int tls_socket_write (CONNECTION * conn, const char *buf, size_t len)
108 tlssockdata *data = conn->sockdata;
112 mutt_error (_("Error: no TLS socket open"));
117 ret = gnutls_record_send (data->state, buf, len);
118 if (gnutls_error_is_fatal (ret) == 1) {
119 mutt_error (_("tls_socket_write (%s)"), gnutls_strerror (ret));
126 static int tls_socket_open (CONNECTION * conn)
128 if (raw_socket_open (conn) < 0)
131 if (tls_negotiate (conn) < 0) {
132 tls_socket_close (conn);
139 int mutt_ssl_starttls (CONNECTION * conn)
144 if (tls_negotiate (conn) < 0)
147 conn->conn_read = tls_socket_read;
148 conn->conn_write = tls_socket_write;
149 conn->conn_close = tls_starttls_close;
154 static int protocol_priority[] = { GNUTLS_TLS1, GNUTLS_SSL3, 0 };
156 /* tls_negotiate: After TLS state has been initialised, attempt to negotiate
157 * TLS over the wire, including certificate checks. */
158 static int tls_negotiate (CONNECTION * conn)
163 data = p_new(tlssockdata, 1);
164 conn->sockdata = data;
165 err = gnutls_certificate_allocate_credentials (&data->xcred);
167 p_delete(&conn->sockdata);
168 mutt_error (_("gnutls_certificate_allocate_credentials: %s"),
169 gnutls_strerror (err));
174 gnutls_certificate_set_x509_trust_file (data->xcred, SslCertFile,
175 GNUTLS_X509_FMT_PEM);
176 /* ignore errors, maybe file doesn't exist yet */
179 gnutls_certificate_set_x509_trust_file (data->xcred, SslCACertFile,
180 GNUTLS_X509_FMT_PEM);
184 gnutls_set_x509_client_key (data->xcred, "", "");
185 gnutls_set_x509_cert_callback (data->xcred, cert_callback);
188 gnutls_init (&data->state, GNUTLS_CLIENT);
191 gnutls_transport_set_ptr (data->state, (gnutls_transport_ptr)(intptr_t)conn->fd);
193 /* disable TLS/SSL protocols as needed */
194 if (!option (OPTTLSV1) && !option (OPTSSLV3)) {
195 mutt_error (_("All available protocols for TLS/SSL connection disabled"));
198 else if (!option (OPTTLSV1)) {
199 protocol_priority[0] = GNUTLS_SSL3;
200 protocol_priority[1] = 0;
202 else if (!option (OPTSSLV3)) {
203 protocol_priority[0] = GNUTLS_TLS1;
204 protocol_priority[1] = 0;
208 use the list set above
211 /* We use default priorities (see gnutls documentation),
212 except for protocol version */
213 gnutls_set_default_priority (data->state);
214 gnutls_protocol_set_priority (data->state, protocol_priority);
216 if (SslDHPrimeBits > 0) {
217 gnutls_dh_set_prime_bits (data->state, SslDHPrimeBits);
221 gnutls_set_cred (data->state, GNUTLS_ANON, NULL);
224 gnutls_credentials_set (data->state, GNUTLS_CRD_CERTIFICATE, data->xcred);
226 err = gnutls_handshake (data->state);
228 while (err == GNUTLS_E_AGAIN || err == GNUTLS_E_INTERRUPTED) {
229 err = gnutls_handshake (data->state);
232 if (err == GNUTLS_E_FATAL_ALERT_RECEIVED) {
233 mutt_error (_("gnutls_handshake: %s(%s)"), gnutls_strerror (err),
234 gnutls_alert_get_name (gnutls_alert_get (data->state)));
237 mutt_error (_("gnutls_handshake: %s"), gnutls_strerror (err));
243 if (!tls_check_certificate (conn))
246 /* set Security Strength Factor (SSF) for SASL */
247 /* NB: gnutls_cipher_get_key_size() returns key length in bytes */
249 gnutls_cipher_get_key_size (gnutls_cipher_get (data->state)) * 8;
251 mutt_message (_("SSL/TLS connection using %s (%s/%s/%s)"),
252 gnutls_protocol_get_name (gnutls_protocol_get_version
254 gnutls_kx_get_name (gnutls_kx_get (data->state)),
255 gnutls_cipher_get_name (gnutls_cipher_get (data->state)),
256 gnutls_mac_get_name (gnutls_mac_get (data->state)));
262 gnutls_certificate_free_credentials (data->xcred);
263 gnutls_deinit (data->state);
264 p_delete(&conn->sockdata);
268 static int tls_socket_close (CONNECTION * conn)
270 tlssockdata *data = conn->sockdata;
273 gnutls_bye (data->state, GNUTLS_SHUT_RDWR);
275 gnutls_certificate_free_credentials (data->xcred);
276 gnutls_deinit (data->state);
277 p_delete(&conn->sockdata);
280 return raw_socket_close (conn);
283 static int tls_starttls_close (CONNECTION * conn)
287 rc = tls_socket_close (conn);
288 conn->conn_read = raw_socket_read;
289 conn->conn_write = raw_socket_write;
290 conn->conn_close = raw_socket_close;
295 #define CERT_SEP "-----BEGIN"
297 /* this bit is based on read_ca_file() in gnutls */
298 static int tls_compare_certificates (const gnutls_datum * peercert)
304 gnutls_datum b64_data;
305 unsigned char *b64_data_data;
306 struct stat filestat;
308 if (stat (SslCertFile, &filestat) == -1)
311 b64_data.size = filestat.st_size + 1;
312 b64_data_data = p_new(unsigned char, b64_data.size);
313 b64_data_data[b64_data.size - 1] = '\0';
314 b64_data.data = b64_data_data;
316 fd1 = fopen (SslCertFile, "r");
321 b64_data.size = fread (b64_data.data, 1, b64_data.size, fd1);
325 ret = gnutls_pem_base64_decode_alloc (NULL, &b64_data, &cert);
327 p_delete(&b64_data_data);
331 ptr = (unsigned char *) strstr ((char*) b64_data.data, CERT_SEP) + 1;
332 ptr = (unsigned char *) strstr ((char*) ptr, CERT_SEP);
334 b64_data.size = b64_data.size - (ptr - b64_data.data);
337 if (cert.size == peercert->size) {
338 if (memcmp (cert.data, peercert->data, cert.size) == 0) {
340 gnutls_free (cert.data);
341 p_delete(&b64_data_data);
346 gnutls_free (cert.data);
347 } while (ptr != NULL);
350 p_delete(&b64_data_data);
354 static void tls_fingerprint (gnutls_digest_algorithm algo,
355 char *s, int l, const gnutls_datum * data)
357 unsigned char md[36];
363 if (gnutls_fingerprint (algo, data, (char *) md, &n) < 0) {
364 snprintf (s, l, _("[unable to calculate]"));
367 for (j = 0; j < (int) n; j++) {
370 snprintf (ch, 8, "%02X%s", md[j], (j % 2 ? " " : ""));
373 s[2 * n + n / 2 - 1] = '\0'; /* don't want trailing space */
377 static char *tls_make_date (time_t t, char *s, size_t len)
379 struct tm *l = gmtime (&t);
382 snprintf (s, len, "%s, %d %s %d %02d:%02d:%02d UTC",
383 Weekdays[l->tm_wday], l->tm_mday, Months[l->tm_mon],
384 l->tm_year + 1900, l->tm_hour, l->tm_min, l->tm_sec);
386 m_strcpy(s, len, _("[invalid date]"));
391 static int tls_check_stored_hostname (const gnutls_datum * cert,
392 const char *hostname)
396 char *linestr = NULL;
400 regmatch_t pmatch[3];
402 /* try checking against names stored in stored certs file */
403 if ((fp = fopen (SslCertFile, "r"))) {
406 "^#H ([a-zA-Z0-9_\\.-]+) ([0-9A-F]{4}( [0-9A-F]{4}){7})[ \t]*$",
407 REG_ICASE | REG_EXTENDED) != 0) {
413 tls_fingerprint (GNUTLS_DIG_MD5, buf, sizeof (buf), cert);
415 mutt_read_line (linestr, &linestrsize, fp, &linenum)) != NULL) {
416 if (linestr[0] == '#' && linestr[1] == 'H') {
417 if (regexec (&preg, linestr, 3, pmatch, 0) == 0) {
418 linestr[pmatch[1].rm_eo] = '\0';
419 linestr[pmatch[2].rm_eo] = '\0';
420 if (m_strcmp(linestr + pmatch[1].rm_so, hostname) == 0 &&
421 m_strcmp(linestr + pmatch[2].rm_so, buf) == 0) {
435 /* not found a matching name */
439 static int tls_check_certificate (CONNECTION * conn)
441 tlssockdata *data = conn->sockdata;
442 gnutls_session state = data->state;
443 char helpstr[SHORT_STRING];
444 char buf[SHORT_STRING];
445 char fpbuf[SHORT_STRING];
447 char dn_common_name[SHORT_STRING];
448 char dn_email[SHORT_STRING];
449 char dn_organization[SHORT_STRING];
450 char dn_organizational_unit[SHORT_STRING];
451 char dn_locality[SHORT_STRING];
452 char dn_province[SHORT_STRING];
453 char dn_country[SHORT_STRING];
455 int done, row, i, ret;
458 const gnutls_datum *cert_list;
459 unsigned int cert_list_size = 0;
460 gnutls_certificate_status_t certstat;
462 gnutls_x509_crt cert;
463 gnutls_datum pemdata;
464 int certerr_expired = 0;
465 int certerr_notyetvalid = 0;
466 int certerr_hostname = 0;
467 int certerr_nottrusted = 0;
468 int certerr_revoked = 0;
469 int certerr_signernotca = 0;
471 if (gnutls_auth_get_type (state) != GNUTLS_CRD_CERTIFICATE) {
472 mutt_error (_("Unable to get certificate from peer"));
477 if (gnutls_certificate_verify_peers2(state, &certstat) < 0) {
478 mutt_error (_("Certificate verification error (%s)"),
479 gnutls_strerror(certstat));
484 /* We only support X.509 certificates (not OpenPGP) at the moment */
485 if (gnutls_certificate_type_get (state) != GNUTLS_CRT_X509) {
486 mutt_error (_("Certificate is not X.509"));
491 if (gnutls_x509_crt_init (&cert) < 0) {
492 mutt_error (_("Error initialising gnutls certificate data"));
497 cert_list = gnutls_certificate_get_peers (state, &cert_list_size);
499 mutt_error (_("Unable to get certificate from peer"));
504 /* FIXME: Currently only check first certificate in chain. */
505 if (gnutls_x509_crt_import (cert, &cert_list[0], GNUTLS_X509_FMT_DER) < 0) {
506 mutt_error (_("Error processing certificate data"));
511 if (gnutls_x509_crt_get_expiration_time (cert) < time (NULL)) {
515 if (gnutls_x509_crt_get_activation_time (cert) > time (NULL)) {
516 certerr_notyetvalid = 1;
519 if (!gnutls_x509_crt_check_hostname (cert, conn->account.host) &&
520 !tls_check_stored_hostname (&cert_list[0], conn->account.host)) {
521 certerr_hostname = 1;
524 /* see whether certificate is in our cache (certificates file) */
525 if (tls_compare_certificates (&cert_list[0])) {
526 if (certstat & GNUTLS_CERT_INVALID) {
527 /* doesn't matter - have decided is valid because server
528 certificate is in our trusted cache */
529 certstat ^= GNUTLS_CERT_INVALID;
532 if (certstat & GNUTLS_CERT_SIGNER_NOT_FOUND) {
533 /* doesn't matter that we haven't found the signer, since
534 certificate is in our trusted cache */
535 certstat ^= GNUTLS_CERT_SIGNER_NOT_FOUND;
538 if (certstat & GNUTLS_CERT_SIGNER_NOT_CA) {
539 /* Hmm. Not really sure how to handle this, but let's say
540 that we don't care if the CA certificate hasn't got the
541 correct X.509 basic constraints if server certificate is
543 certstat ^= GNUTLS_CERT_SIGNER_NOT_CA;
548 if (certstat & GNUTLS_CERT_REVOKED) {
550 certstat ^= GNUTLS_CERT_REVOKED;
553 if (certstat & GNUTLS_CERT_INVALID) {
554 certerr_nottrusted = 1;
555 certstat ^= GNUTLS_CERT_INVALID;
558 if (certstat & GNUTLS_CERT_SIGNER_NOT_FOUND) {
559 /* NB: already cleared if cert in cache */
560 certerr_nottrusted = 1;
561 certstat ^= GNUTLS_CERT_SIGNER_NOT_FOUND;
564 if (certstat & GNUTLS_CERT_SIGNER_NOT_CA) {
565 /* NB: already cleared if cert in cache */
566 certerr_signernotca = 1;
567 certstat ^= GNUTLS_CERT_SIGNER_NOT_CA;
570 /* OK if signed by (or is) a trusted certificate */
571 /* we've been zeroing the interesting bits in certstat -
572 don't return OK if there are any unhandled bits we don't
574 if (!(certerr_expired || certerr_notyetvalid ||
575 certerr_hostname || certerr_nottrusted) && certstat == 0) {
576 gnutls_x509_crt_deinit (cert);
581 /* interactive check from user */
582 menu = mutt_new_menu ();
584 menu->dialog = p_new(char*, menu->max);
585 for (i = 0; i < menu->max; i++)
586 menu->dialog[i] = p_new(char, SHORT_STRING);
589 m_strcpy(menu->dialog[row], SHORT_STRING,
590 _("This certificate belongs to:"));
593 buflen = sizeof (dn_common_name);
594 if (gnutls_x509_crt_get_dn_by_oid (cert, GNUTLS_OID_X520_COMMON_NAME, 0, 0,
595 dn_common_name, &buflen) != 0)
596 dn_common_name[0] = '\0';
597 buflen = sizeof (dn_email);
598 if (gnutls_x509_crt_get_dn_by_oid (cert, GNUTLS_OID_PKCS9_EMAIL, 0, 0,
599 dn_email, &buflen) != 0)
601 buflen = sizeof (dn_organization);
602 if (gnutls_x509_crt_get_dn_by_oid
603 (cert, GNUTLS_OID_X520_ORGANIZATION_NAME, 0, 0, dn_organization,
605 dn_organization[0] = '\0';
606 buflen = sizeof (dn_organizational_unit);
607 if (gnutls_x509_crt_get_dn_by_oid
608 (cert, GNUTLS_OID_X520_ORGANIZATIONAL_UNIT_NAME, 0, 0,
609 dn_organizational_unit, &buflen) != 0)
610 dn_organizational_unit[0] = '\0';
611 buflen = sizeof (dn_locality);
612 if (gnutls_x509_crt_get_dn_by_oid
613 (cert, GNUTLS_OID_X520_LOCALITY_NAME, 0, 0, dn_locality, &buflen) != 0)
614 dn_locality[0] = '\0';
615 buflen = sizeof (dn_province);
616 if (gnutls_x509_crt_get_dn_by_oid
617 (cert, GNUTLS_OID_X520_STATE_OR_PROVINCE_NAME, 0, 0, dn_province,
619 dn_province[0] = '\0';
620 buflen = sizeof (dn_country);
621 if (gnutls_x509_crt_get_dn_by_oid (cert, GNUTLS_OID_X520_COUNTRY_NAME, 0, 0,
622 dn_country, &buflen) != 0)
623 dn_country[0] = '\0';
625 snprintf (menu->dialog[row++], SHORT_STRING, " %s %s", dn_common_name,
627 snprintf (menu->dialog[row++], SHORT_STRING, " %s", dn_organization);
628 snprintf (menu->dialog[row++], SHORT_STRING, " %s",
629 dn_organizational_unit);
630 snprintf (menu->dialog[row++], SHORT_STRING, " %s %s %s", dn_locality,
631 dn_province, dn_country);
634 m_strcpy(menu->dialog[row], SHORT_STRING,
635 _("This certificate was issued by:"));
638 buflen = sizeof (dn_common_name);
639 if (gnutls_x509_crt_get_issuer_dn_by_oid
640 (cert, GNUTLS_OID_X520_COMMON_NAME, 0, 0, dn_common_name, &buflen) != 0)
641 dn_common_name[0] = '\0';
642 buflen = sizeof (dn_email);
643 if (gnutls_x509_crt_get_issuer_dn_by_oid
644 (cert, GNUTLS_OID_PKCS9_EMAIL, 0, 0, dn_email, &buflen) != 0)
646 buflen = sizeof (dn_organization);
647 if (gnutls_x509_crt_get_issuer_dn_by_oid
648 (cert, GNUTLS_OID_X520_ORGANIZATION_NAME, 0, 0, dn_organization,
650 dn_organization[0] = '\0';
651 buflen = sizeof (dn_organizational_unit);
652 if (gnutls_x509_crt_get_issuer_dn_by_oid
653 (cert, GNUTLS_OID_X520_ORGANIZATIONAL_UNIT_NAME, 0, 0,
654 dn_organizational_unit, &buflen) != 0)
655 dn_organizational_unit[0] = '\0';
656 buflen = sizeof (dn_locality);
657 if (gnutls_x509_crt_get_issuer_dn_by_oid
658 (cert, GNUTLS_OID_X520_LOCALITY_NAME, 0, 0, dn_locality, &buflen) != 0)
659 dn_locality[0] = '\0';
660 buflen = sizeof (dn_province);
661 if (gnutls_x509_crt_get_issuer_dn_by_oid
662 (cert, GNUTLS_OID_X520_STATE_OR_PROVINCE_NAME, 0, 0, dn_province,
664 dn_province[0] = '\0';
665 buflen = sizeof (dn_country);
666 if (gnutls_x509_crt_get_issuer_dn_by_oid
667 (cert, GNUTLS_OID_X520_COUNTRY_NAME, 0, 0, dn_country, &buflen) != 0)
668 dn_country[0] = '\0';
670 snprintf (menu->dialog[row++], SHORT_STRING, " %s %s", dn_common_name,
672 snprintf (menu->dialog[row++], SHORT_STRING, " %s", dn_organization);
673 snprintf (menu->dialog[row++], SHORT_STRING, " %s",
674 dn_organizational_unit);
675 snprintf (menu->dialog[row++], SHORT_STRING, " %s %s %s", dn_locality,
676 dn_province, dn_country);
679 snprintf (menu->dialog[row++], SHORT_STRING,
680 _("This certificate is valid"));
682 t = gnutls_x509_crt_get_activation_time (cert);
683 snprintf (menu->dialog[row++], SHORT_STRING, _(" from %s"),
684 tls_make_date (t, datestr, 30));
686 t = gnutls_x509_crt_get_expiration_time (cert);
687 snprintf (menu->dialog[row++], SHORT_STRING, _(" to %s"),
688 tls_make_date (t, datestr, 30));
691 tls_fingerprint (GNUTLS_DIG_SHA, fpbuf, sizeof (fpbuf), &cert_list[0]);
692 snprintf (menu->dialog[row++], SHORT_STRING, _("SHA1 Fingerprint: %s"),
695 tls_fingerprint (GNUTLS_DIG_MD5, fpbuf, sizeof (fpbuf), &cert_list[0]);
696 snprintf (menu->dialog[row++], SHORT_STRING, _("MD5 Fingerprint: %s"),
699 if (certerr_notyetvalid) {
701 m_strcpy(menu->dialog[row], SHORT_STRING,
702 _("WARNING: Server certificate is not yet valid"));
704 if (certerr_expired) {
706 m_strcpy(menu->dialog[row], SHORT_STRING,
707 _("WARNING: Server certificate has expired"));
709 if (certerr_revoked) {
711 m_strcpy(menu->dialog[row], SHORT_STRING,
712 _("WARNING: Server certificate has been revoked"));
714 if (certerr_hostname) {
716 m_strcpy(menu->dialog[row], SHORT_STRING,
717 _("WARNING: Server hostname does not match certificate"));
719 if (certerr_signernotca) {
721 m_strcpy(menu->dialog[row], SHORT_STRING,
722 _("WARNING: Signer of server certificate is not a CA"));
725 menu->title = _("TLS/SSL Certificate check");
726 /* certificates with bad dates, or that are revoked, must be
727 accepted manually each and every time */
728 if (SslCertFile && !certerr_expired && !certerr_notyetvalid
729 && !certerr_revoked) {
730 menu->prompt = _("(r)eject, accept (o)nce, (a)ccept always");
731 menu->keys = _("roa");
734 menu->prompt = _("(r)eject, accept (o)nce");
735 menu->keys = _("ro");
739 mutt_make_help (buf, sizeof (buf), _("Exit "), MENU_GENERIC, OP_EXIT);
740 strncat (helpstr, buf, sizeof (helpstr));
741 mutt_make_help (buf, sizeof (buf), _("Help"), MENU_GENERIC, OP_HELP);
742 strncat (helpstr, buf, sizeof (helpstr));
743 menu->help = helpstr;
746 set_option (OPTUNBUFFEREDINPUT);
748 switch (mutt_menuLoop (menu)) {
750 case OP_MAX + 1: /* reject */
754 case OP_MAX + 3: /* accept always */
756 if ((fp = fopen (SslCertFile, "a"))) {
757 /* save hostname if necessary */
758 if (certerr_hostname) {
759 fprintf (fp, "#H %s %s\n", conn->account.host, fpbuf);
762 if (certerr_nottrusted) {
764 ret = gnutls_pem_base64_encode_alloc ("CERTIFICATE", &cert_list[0],
767 if (fwrite (pemdata.data, pemdata.size, 1, fp) == 1) {
770 gnutls_free (pemdata.data);
776 mutt_error (_("Warning: Couldn't save certificate"));
780 mutt_message (_("Certificate saved"));
784 case OP_MAX + 2: /* accept once */
789 unset_option (OPTUNBUFFEREDINPUT);
790 mutt_menuDestroy (&menu);
791 gnutls_x509_crt_deinit (cert);
795 #endif /* USE_GNUTLS */