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>
27 #include <lib-lib/rx.h>
29 #include <lib-ui/curses.h>
30 #include <lib-ui/menu.h>
33 #include "mutt_socket.h"
36 typedef struct _tlssockdata {
38 gnutls_certificate_credentials xcred;
41 /* local prototypes */
42 static int tls_socket_read (CONNECTION * conn, char *buf, size_t len);
43 static int tls_socket_write (CONNECTION * conn, const char *buf, size_t len);
44 static int tls_socket_open (CONNECTION * conn);
45 static int tls_socket_close (CONNECTION * conn);
46 static int tls_starttls_close (CONNECTION * conn);
48 static int tls_init (void);
49 static int tls_negotiate (CONNECTION * conn);
50 static int tls_check_certificate (CONNECTION * conn);
53 static int tls_init (void)
55 static unsigned char init_complete = 0;
61 err = gnutls_global_init ();
63 mutt_error (_("gnutls_global_init: %s"), gnutls_strerror (err));
72 int mutt_ssl_socket_setup (CONNECTION * conn)
77 conn->conn_open = tls_socket_open;
78 conn->conn_read = tls_socket_read;
79 conn->conn_write = tls_socket_write;
80 conn->conn_close = tls_socket_close;
85 static int tls_socket_read (CONNECTION * conn, char *buf, size_t len)
87 tlssockdata *data = conn->sockdata;
91 mutt_error (_("Error: no TLS socket open"));
96 ret = gnutls_record_recv (data->state, buf, len);
97 if (gnutls_error_is_fatal (ret) == 1) {
98 mutt_error (_("tls_socket_read (%s)"), gnutls_strerror (ret));
105 static int tls_socket_write (CONNECTION * conn, const char *buf, size_t len)
107 tlssockdata *data = conn->sockdata;
111 mutt_error (_("Error: no TLS socket open"));
116 ret = gnutls_record_send (data->state, buf, len);
117 if (gnutls_error_is_fatal (ret) == 1) {
118 mutt_error (_("tls_socket_write (%s)"), gnutls_strerror (ret));
125 static int tls_socket_open (CONNECTION * conn)
127 if (raw_socket_open (conn) < 0)
130 if (tls_negotiate (conn) < 0) {
131 tls_socket_close (conn);
138 int mutt_ssl_starttls (CONNECTION * conn)
143 if (tls_negotiate (conn) < 0)
146 conn->conn_read = tls_socket_read;
147 conn->conn_write = tls_socket_write;
148 conn->conn_close = tls_starttls_close;
153 static int protocol_priority[] = { GNUTLS_TLS1, GNUTLS_SSL3, 0 };
155 /* tls_negotiate: After TLS state has been initialised, attempt to negotiate
156 * TLS over the wire, including certificate checks. */
157 static int tls_negotiate (CONNECTION * conn)
162 data = p_new(tlssockdata, 1);
163 conn->sockdata = data;
164 err = gnutls_certificate_allocate_credentials (&data->xcred);
166 p_delete(&conn->sockdata);
167 mutt_error (_("gnutls_certificate_allocate_credentials: %s"),
168 gnutls_strerror (err));
173 gnutls_certificate_set_x509_trust_file (data->xcred, SslCertFile,
174 GNUTLS_X509_FMT_PEM);
175 /* ignore errors, maybe file doesn't exist yet */
178 gnutls_certificate_set_x509_trust_file (data->xcred, SslCACertFile,
179 GNUTLS_X509_FMT_PEM);
183 gnutls_set_x509_client_key (data->xcred, "", "");
184 gnutls_set_x509_cert_callback (data->xcred, cert_callback);
187 gnutls_init (&data->state, GNUTLS_CLIENT);
190 gnutls_transport_set_ptr (data->state, (gnutls_transport_ptr)(intptr_t)conn->fd);
192 /* disable TLS/SSL protocols as needed */
193 if (!option (OPTTLSV1) && !option (OPTSSLV3)) {
194 mutt_error (_("All available protocols for TLS/SSL connection disabled"));
197 else if (!option (OPTTLSV1)) {
198 protocol_priority[0] = GNUTLS_SSL3;
199 protocol_priority[1] = 0;
201 else if (!option (OPTSSLV3)) {
202 protocol_priority[0] = GNUTLS_TLS1;
203 protocol_priority[1] = 0;
207 use the list set above
210 /* We use default priorities (see gnutls documentation),
211 except for protocol version */
212 gnutls_set_default_priority (data->state);
213 gnutls_protocol_set_priority (data->state, protocol_priority);
215 if (SslDHPrimeBits > 0) {
216 gnutls_dh_set_prime_bits (data->state, SslDHPrimeBits);
220 gnutls_set_cred (data->state, GNUTLS_ANON, NULL);
223 gnutls_credentials_set (data->state, GNUTLS_CRD_CERTIFICATE, data->xcred);
225 err = gnutls_handshake (data->state);
227 while (err == GNUTLS_E_AGAIN || err == GNUTLS_E_INTERRUPTED) {
228 err = gnutls_handshake (data->state);
231 if (err == GNUTLS_E_FATAL_ALERT_RECEIVED) {
232 mutt_error (_("gnutls_handshake: %s(%s)"), gnutls_strerror (err),
233 gnutls_alert_get_name (gnutls_alert_get (data->state)));
236 mutt_error (_("gnutls_handshake: %s"), gnutls_strerror (err));
242 if (!tls_check_certificate (conn))
245 /* set Security Strength Factor (SSF) for SASL */
246 /* NB: gnutls_cipher_get_key_size() returns key length in bytes */
248 gnutls_cipher_get_key_size (gnutls_cipher_get (data->state)) * 8;
250 mutt_message (_("SSL/TLS connection using %s (%s/%s/%s)"),
251 gnutls_protocol_get_name (gnutls_protocol_get_version
253 gnutls_kx_get_name (gnutls_kx_get (data->state)),
254 gnutls_cipher_get_name (gnutls_cipher_get (data->state)),
255 gnutls_mac_get_name (gnutls_mac_get (data->state)));
261 gnutls_certificate_free_credentials (data->xcred);
262 gnutls_deinit (data->state);
263 p_delete(&conn->sockdata);
267 static int tls_socket_close (CONNECTION * conn)
269 tlssockdata *data = conn->sockdata;
272 gnutls_bye (data->state, GNUTLS_SHUT_RDWR);
274 gnutls_certificate_free_credentials (data->xcred);
275 gnutls_deinit (data->state);
276 p_delete(&conn->sockdata);
279 return raw_socket_close (conn);
282 static int tls_starttls_close (CONNECTION * conn)
286 rc = tls_socket_close (conn);
287 conn->conn_read = raw_socket_read;
288 conn->conn_write = raw_socket_write;
289 conn->conn_close = raw_socket_close;
294 #define CERT_SEP "-----BEGIN"
296 /* this bit is based on read_ca_file() in gnutls */
297 static int tls_compare_certificates (const gnutls_datum * peercert)
303 gnutls_datum b64_data;
304 unsigned char *b64_data_data;
305 struct stat filestat;
307 if (stat (SslCertFile, &filestat) == -1)
310 b64_data.size = filestat.st_size + 1;
311 b64_data_data = p_new(unsigned char, b64_data.size);
312 b64_data_data[b64_data.size - 1] = '\0';
313 b64_data.data = b64_data_data;
315 fd1 = fopen (SslCertFile, "r");
320 b64_data.size = fread (b64_data.data, 1, b64_data.size, fd1);
324 ret = gnutls_pem_base64_decode_alloc (NULL, &b64_data, &cert);
326 p_delete(&b64_data_data);
330 ptr = (unsigned char *) strstr ((char*) b64_data.data, CERT_SEP) + 1;
331 ptr = (unsigned char *) strstr ((char*) ptr, CERT_SEP);
333 b64_data.size = b64_data.size - (ptr - b64_data.data);
336 if (cert.size == peercert->size) {
337 if (memcmp (cert.data, peercert->data, cert.size) == 0) {
339 gnutls_free (cert.data);
340 p_delete(&b64_data_data);
345 gnutls_free (cert.data);
346 } while (ptr != NULL);
349 p_delete(&b64_data_data);
353 static void tls_fingerprint (gnutls_digest_algorithm algo,
354 char *s, int l, const gnutls_datum * data)
356 unsigned char md[36];
362 if (gnutls_fingerprint (algo, data, (char *) md, &n) < 0) {
363 snprintf (s, l, _("[unable to calculate]"));
366 for (j = 0; j < (int) n; j++) {
369 snprintf (ch, 8, "%02X%s", md[j], (j % 2 ? " " : ""));
372 s[2 * n + n / 2 - 1] = '\0'; /* don't want trailing space */
376 static char *tls_make_date (time_t t, char *s, size_t len)
378 struct tm *l = gmtime (&t);
381 snprintf (s, len, "%s, %d %s %d %02d:%02d:%02d UTC",
382 Weekdays[l->tm_wday], l->tm_mday, Months[l->tm_mon],
383 l->tm_year + 1900, l->tm_hour, l->tm_min, l->tm_sec);
385 m_strcpy(s, len, _("[invalid date]"));
390 static int tls_check_stored_hostname (const gnutls_datum * cert,
391 const char *hostname)
395 char *linestr = NULL;
399 regmatch_t pmatch[3];
401 /* try checking against names stored in stored certs file */
402 if ((fp = fopen (SslCertFile, "r"))) {
405 "^#H ([a-zA-Z0-9_\\.-]+) ([0-9A-F]{4}( [0-9A-F]{4}){7})[ \t]*$",
406 REG_ICASE | REG_EXTENDED) != 0) {
412 tls_fingerprint (GNUTLS_DIG_MD5, buf, sizeof (buf), cert);
414 mutt_read_line (linestr, &linestrsize, fp, &linenum)) != NULL) {
415 if (linestr[0] == '#' && linestr[1] == 'H') {
416 if (regexec (&preg, linestr, 3, pmatch, 0) == 0) {
417 linestr[pmatch[1].rm_eo] = '\0';
418 linestr[pmatch[2].rm_eo] = '\0';
419 if (m_strcmp(linestr + pmatch[1].rm_so, hostname) == 0 &&
420 m_strcmp(linestr + pmatch[2].rm_so, buf) == 0) {
434 /* not found a matching name */
438 static int tls_check_certificate (CONNECTION * conn)
440 tlssockdata *data = conn->sockdata;
441 gnutls_session state = data->state;
442 char helpstr[SHORT_STRING];
443 char buf[SHORT_STRING];
444 char fpbuf[SHORT_STRING];
446 char dn_common_name[SHORT_STRING];
447 char dn_email[SHORT_STRING];
448 char dn_organization[SHORT_STRING];
449 char dn_organizational_unit[SHORT_STRING];
450 char dn_locality[SHORT_STRING];
451 char dn_province[SHORT_STRING];
452 char dn_country[SHORT_STRING];
454 int done, row, i, ret;
457 const gnutls_datum *cert_list;
458 unsigned int cert_list_size = 0;
459 gnutls_certificate_status_t certstat;
461 gnutls_x509_crt cert;
462 gnutls_datum pemdata;
463 int certerr_expired = 0;
464 int certerr_notyetvalid = 0;
465 int certerr_hostname = 0;
466 int certerr_nottrusted = 0;
467 int certerr_revoked = 0;
468 int certerr_signernotca = 0;
470 if (gnutls_auth_get_type (state) != GNUTLS_CRD_CERTIFICATE) {
471 mutt_error (_("Unable to get certificate from peer"));
476 if (gnutls_certificate_verify_peers2(state, &certstat) < 0) {
477 mutt_error (_("Certificate verification error (%s)"),
478 gnutls_strerror(certstat));
483 /* We only support X.509 certificates (not OpenPGP) at the moment */
484 if (gnutls_certificate_type_get (state) != GNUTLS_CRT_X509) {
485 mutt_error (_("Certificate is not X.509"));
490 if (gnutls_x509_crt_init (&cert) < 0) {
491 mutt_error (_("Error initialising gnutls certificate data"));
496 cert_list = gnutls_certificate_get_peers (state, &cert_list_size);
498 mutt_error (_("Unable to get certificate from peer"));
503 /* FIXME: Currently only check first certificate in chain. */
504 if (gnutls_x509_crt_import (cert, &cert_list[0], GNUTLS_X509_FMT_DER) < 0) {
505 mutt_error (_("Error processing certificate data"));
510 if (gnutls_x509_crt_get_expiration_time (cert) < time (NULL)) {
514 if (gnutls_x509_crt_get_activation_time (cert) > time (NULL)) {
515 certerr_notyetvalid = 1;
518 if (!gnutls_x509_crt_check_hostname (cert, conn->account.host) &&
519 !tls_check_stored_hostname (&cert_list[0], conn->account.host)) {
520 certerr_hostname = 1;
523 /* see whether certificate is in our cache (certificates file) */
524 if (tls_compare_certificates (&cert_list[0])) {
525 if (certstat & GNUTLS_CERT_INVALID) {
526 /* doesn't matter - have decided is valid because server
527 certificate is in our trusted cache */
528 certstat ^= GNUTLS_CERT_INVALID;
531 if (certstat & GNUTLS_CERT_SIGNER_NOT_FOUND) {
532 /* doesn't matter that we haven't found the signer, since
533 certificate is in our trusted cache */
534 certstat ^= GNUTLS_CERT_SIGNER_NOT_FOUND;
537 if (certstat & GNUTLS_CERT_SIGNER_NOT_CA) {
538 /* Hmm. Not really sure how to handle this, but let's say
539 that we don't care if the CA certificate hasn't got the
540 correct X.509 basic constraints if server certificate is
542 certstat ^= GNUTLS_CERT_SIGNER_NOT_CA;
547 if (certstat & GNUTLS_CERT_REVOKED) {
549 certstat ^= GNUTLS_CERT_REVOKED;
552 if (certstat & GNUTLS_CERT_INVALID) {
553 certerr_nottrusted = 1;
554 certstat ^= GNUTLS_CERT_INVALID;
557 if (certstat & GNUTLS_CERT_SIGNER_NOT_FOUND) {
558 /* NB: already cleared if cert in cache */
559 certerr_nottrusted = 1;
560 certstat ^= GNUTLS_CERT_SIGNER_NOT_FOUND;
563 if (certstat & GNUTLS_CERT_SIGNER_NOT_CA) {
564 /* NB: already cleared if cert in cache */
565 certerr_signernotca = 1;
566 certstat ^= GNUTLS_CERT_SIGNER_NOT_CA;
569 /* OK if signed by (or is) a trusted certificate */
570 /* we've been zeroing the interesting bits in certstat -
571 don't return OK if there are any unhandled bits we don't
573 if (!(certerr_expired || certerr_notyetvalid ||
574 certerr_hostname || certerr_nottrusted) && certstat == 0) {
575 gnutls_x509_crt_deinit (cert);
580 /* interactive check from user */
581 menu = mutt_new_menu ();
583 menu->dialog = p_new(char*, menu->max);
584 for (i = 0; i < menu->max; i++)
585 menu->dialog[i] = p_new(char, SHORT_STRING);
588 m_strcpy(menu->dialog[row], SHORT_STRING,
589 _("This certificate belongs to:"));
592 buflen = sizeof (dn_common_name);
593 if (gnutls_x509_crt_get_dn_by_oid (cert, GNUTLS_OID_X520_COMMON_NAME, 0, 0,
594 dn_common_name, &buflen) != 0)
595 dn_common_name[0] = '\0';
596 buflen = sizeof (dn_email);
597 if (gnutls_x509_crt_get_dn_by_oid (cert, GNUTLS_OID_PKCS9_EMAIL, 0, 0,
598 dn_email, &buflen) != 0)
600 buflen = sizeof (dn_organization);
601 if (gnutls_x509_crt_get_dn_by_oid
602 (cert, GNUTLS_OID_X520_ORGANIZATION_NAME, 0, 0, dn_organization,
604 dn_organization[0] = '\0';
605 buflen = sizeof (dn_organizational_unit);
606 if (gnutls_x509_crt_get_dn_by_oid
607 (cert, GNUTLS_OID_X520_ORGANIZATIONAL_UNIT_NAME, 0, 0,
608 dn_organizational_unit, &buflen) != 0)
609 dn_organizational_unit[0] = '\0';
610 buflen = sizeof (dn_locality);
611 if (gnutls_x509_crt_get_dn_by_oid
612 (cert, GNUTLS_OID_X520_LOCALITY_NAME, 0, 0, dn_locality, &buflen) != 0)
613 dn_locality[0] = '\0';
614 buflen = sizeof (dn_province);
615 if (gnutls_x509_crt_get_dn_by_oid
616 (cert, GNUTLS_OID_X520_STATE_OR_PROVINCE_NAME, 0, 0, dn_province,
618 dn_province[0] = '\0';
619 buflen = sizeof (dn_country);
620 if (gnutls_x509_crt_get_dn_by_oid (cert, GNUTLS_OID_X520_COUNTRY_NAME, 0, 0,
621 dn_country, &buflen) != 0)
622 dn_country[0] = '\0';
624 snprintf (menu->dialog[row++], SHORT_STRING, " %s %s", dn_common_name,
626 snprintf (menu->dialog[row++], SHORT_STRING, " %s", dn_organization);
627 snprintf (menu->dialog[row++], SHORT_STRING, " %s",
628 dn_organizational_unit);
629 snprintf (menu->dialog[row++], SHORT_STRING, " %s %s %s", dn_locality,
630 dn_province, dn_country);
633 m_strcpy(menu->dialog[row], SHORT_STRING,
634 _("This certificate was issued by:"));
637 buflen = sizeof (dn_common_name);
638 if (gnutls_x509_crt_get_issuer_dn_by_oid
639 (cert, GNUTLS_OID_X520_COMMON_NAME, 0, 0, dn_common_name, &buflen) != 0)
640 dn_common_name[0] = '\0';
641 buflen = sizeof (dn_email);
642 if (gnutls_x509_crt_get_issuer_dn_by_oid
643 (cert, GNUTLS_OID_PKCS9_EMAIL, 0, 0, dn_email, &buflen) != 0)
645 buflen = sizeof (dn_organization);
646 if (gnutls_x509_crt_get_issuer_dn_by_oid
647 (cert, GNUTLS_OID_X520_ORGANIZATION_NAME, 0, 0, dn_organization,
649 dn_organization[0] = '\0';
650 buflen = sizeof (dn_organizational_unit);
651 if (gnutls_x509_crt_get_issuer_dn_by_oid
652 (cert, GNUTLS_OID_X520_ORGANIZATIONAL_UNIT_NAME, 0, 0,
653 dn_organizational_unit, &buflen) != 0)
654 dn_organizational_unit[0] = '\0';
655 buflen = sizeof (dn_locality);
656 if (gnutls_x509_crt_get_issuer_dn_by_oid
657 (cert, GNUTLS_OID_X520_LOCALITY_NAME, 0, 0, dn_locality, &buflen) != 0)
658 dn_locality[0] = '\0';
659 buflen = sizeof (dn_province);
660 if (gnutls_x509_crt_get_issuer_dn_by_oid
661 (cert, GNUTLS_OID_X520_STATE_OR_PROVINCE_NAME, 0, 0, dn_province,
663 dn_province[0] = '\0';
664 buflen = sizeof (dn_country);
665 if (gnutls_x509_crt_get_issuer_dn_by_oid
666 (cert, GNUTLS_OID_X520_COUNTRY_NAME, 0, 0, dn_country, &buflen) != 0)
667 dn_country[0] = '\0';
669 snprintf (menu->dialog[row++], SHORT_STRING, " %s %s", dn_common_name,
671 snprintf (menu->dialog[row++], SHORT_STRING, " %s", dn_organization);
672 snprintf (menu->dialog[row++], SHORT_STRING, " %s",
673 dn_organizational_unit);
674 snprintf (menu->dialog[row++], SHORT_STRING, " %s %s %s", dn_locality,
675 dn_province, dn_country);
678 snprintf (menu->dialog[row++], SHORT_STRING,
679 _("This certificate is valid"));
681 t = gnutls_x509_crt_get_activation_time (cert);
682 snprintf (menu->dialog[row++], SHORT_STRING, _(" from %s"),
683 tls_make_date (t, datestr, 30));
685 t = gnutls_x509_crt_get_expiration_time (cert);
686 snprintf (menu->dialog[row++], SHORT_STRING, _(" to %s"),
687 tls_make_date (t, datestr, 30));
690 tls_fingerprint (GNUTLS_DIG_SHA, fpbuf, sizeof (fpbuf), &cert_list[0]);
691 snprintf (menu->dialog[row++], SHORT_STRING, _("SHA1 Fingerprint: %s"),
694 tls_fingerprint (GNUTLS_DIG_MD5, fpbuf, sizeof (fpbuf), &cert_list[0]);
695 snprintf (menu->dialog[row++], SHORT_STRING, _("MD5 Fingerprint: %s"),
698 if (certerr_notyetvalid) {
700 m_strcpy(menu->dialog[row], SHORT_STRING,
701 _("WARNING: Server certificate is not yet valid"));
703 if (certerr_expired) {
705 m_strcpy(menu->dialog[row], SHORT_STRING,
706 _("WARNING: Server certificate has expired"));
708 if (certerr_revoked) {
710 m_strcpy(menu->dialog[row], SHORT_STRING,
711 _("WARNING: Server certificate has been revoked"));
713 if (certerr_hostname) {
715 m_strcpy(menu->dialog[row], SHORT_STRING,
716 _("WARNING: Server hostname does not match certificate"));
718 if (certerr_signernotca) {
720 m_strcpy(menu->dialog[row], SHORT_STRING,
721 _("WARNING: Signer of server certificate is not a CA"));
724 menu->title = _("TLS/SSL Certificate check");
725 /* certificates with bad dates, or that are revoked, must be
726 accepted manually each and every time */
727 if (SslCertFile && !certerr_expired && !certerr_notyetvalid
728 && !certerr_revoked) {
729 menu->prompt = _("(r)eject, accept (o)nce, (a)ccept always");
730 menu->keys = _("roa");
733 menu->prompt = _("(r)eject, accept (o)nce");
734 menu->keys = _("ro");
738 mutt_make_help (buf, sizeof (buf), _("Exit "), MENU_GENERIC, OP_EXIT);
739 strncat (helpstr, buf, sizeof (helpstr));
740 mutt_make_help (buf, sizeof (buf), _("Help"), MENU_GENERIC, OP_HELP);
741 strncat (helpstr, buf, sizeof (helpstr));
742 menu->help = helpstr;
745 set_option (OPTUNBUFFEREDINPUT);
747 switch (mutt_menuLoop (menu)) {
749 case OP_MAX + 1: /* reject */
753 case OP_MAX + 3: /* accept always */
755 if ((fp = fopen (SslCertFile, "a"))) {
756 /* save hostname if necessary */
757 if (certerr_hostname) {
758 fprintf (fp, "#H %s %s\n", conn->account.host, fpbuf);
761 if (certerr_nottrusted) {
763 ret = gnutls_pem_base64_encode_alloc ("CERTIFICATE", &cert_list[0],
766 if (fwrite (pemdata.data, pemdata.size, 1, fp) == 1) {
769 gnutls_free (pemdata.data);
775 mutt_error (_("Warning: Couldn't save certificate"));
779 mutt_message (_("Certificate saved"));
783 case OP_MAX + 2: /* accept once */
788 unset_option (OPTUNBUFFEREDINPUT);
789 mutt_menuDestroy (&menu);
790 gnutls_x509_crt_deinit (cert);
794 #endif /* USE_GNUTLS */