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.
11 #include <lib-lib/lib-lib.h>
13 #include <gnutls/gnutls.h>
14 #include <gnutls/x509.h>
16 #include <lib-ui/lib-ui.h>
17 #include <lib-ui/menu.h>
20 #include "mutt_socket.h"
22 @import "../lib-lua/base.cpkg"
28 ** If this variable is \fIset\fP, Madmutt will require that all connections
29 ** to remote servers be encrypted. Furthermore it will attempt to
30 ** negotiate TLS even if the server does not advertise the capability,
31 ** since it would otherwise have to abort the connection anyway. This
32 ** option supersedes ``$$ssl_starttls''.
37 ** This variables specifies whether to attempt to use SSLv3 in the
38 ** SSL authentication process.
41 path_t cert_file = luaM_pathnew("~/.cache/madmutt/certificates");
44 ** This variable specifies the file where the certificates you trust
45 ** are saved. When an unknown certificate is encountered, you are asked
46 ** if you accept it or not. If you accept it, the certificate can also
47 ** be saved in this file and further connections are automatically
50 ** You can also manually add CA certificates in this file. Any server
51 ** certificate that is signed with one of these CA certificates are
52 ** also automatically accepted.
54 ** Example: \fTset certificate_file=~/.madmutt/certificates\fP
57 path_t ca_certificates_file = NULL;
60 ** This variable specifies a file containing trusted CA certificates.
61 ** Any server certificate that is signed with one of these CA
62 ** certificates are also automatically accepted.
64 ** Example: \fTset ssl_ca_certificates_file=/etc/ssl/certs/ca-certificates.crt\fP
68 typedef struct _tlssockdata {
70 gnutls_certificate_credentials xcred;
73 /* local prototypes */
74 static int tls_socket_read (CONNECTION * conn, char *buf, ssize_t len);
75 static int tls_socket_write (CONNECTION * conn, const char *buf, ssize_t len);
76 static int tls_socket_open (CONNECTION * conn);
77 static int tls_socket_close (CONNECTION * conn);
78 static int tls_starttls_close (CONNECTION * conn);
80 static int tls_init (void);
81 static int tls_negotiate (CONNECTION * conn);
82 static int tls_check_certificate (CONNECTION * conn);
85 static int tls_init (void)
87 static unsigned char init_complete = 0;
93 err = gnutls_global_init ();
95 mutt_error (_("gnutls_global_init: %s"), gnutls_strerror (err));
104 int mutt_ssl_socket_setup (CONNECTION * conn)
109 conn->conn_open = tls_socket_open;
110 conn->conn_read = tls_socket_read;
111 conn->conn_write = tls_socket_write;
112 conn->conn_close = tls_socket_close;
117 static int tls_socket_read (CONNECTION * conn, char *buf, ssize_t len)
119 tlssockdata *data = conn->sockdata;
123 mutt_error (_("Error: no TLS socket open"));
128 ret = gnutls_record_recv (data->state, buf, len);
129 if (ret < 0 && gnutls_error_is_fatal (ret) == 1) {
130 mutt_error (_("tls_socket_read (%s)"), gnutls_strerror (ret));
137 static int tls_socket_write (CONNECTION * conn, const char *buf, ssize_t len)
139 tlssockdata *data = conn->sockdata;
143 mutt_error (_("Error: no TLS socket open"));
148 ret = gnutls_record_send (data->state, buf, len);
149 if (ret < 0 && gnutls_error_is_fatal (ret) == 1) {
150 mutt_error (_("tls_socket_write (%s)"), gnutls_strerror (ret));
157 static int tls_socket_open (CONNECTION * conn)
159 if (raw_socket_open (conn) < 0)
162 if (tls_negotiate (conn) < 0) {
163 tls_socket_close (conn);
170 int mutt_ssl_starttls (CONNECTION * conn)
175 if (tls_negotiate (conn) < 0)
178 conn->conn_read = tls_socket_read;
179 conn->conn_write = tls_socket_write;
180 conn->conn_close = tls_starttls_close;
185 static int protocol_priority[] = { GNUTLS_TLS1, GNUTLS_SSL3, 0 };
187 /* tls_negotiate: After TLS state has been initialised, attempt to negotiate
188 * TLS over the wire, including certificate checks. */
189 static int tls_negotiate (CONNECTION * conn)
194 data = p_new(tlssockdata, 1);
195 conn->sockdata = data;
196 err = gnutls_certificate_allocate_credentials (&data->xcred);
198 p_delete(&conn->sockdata);
199 mutt_error (_("gnutls_certificate_allocate_credentials: %s"),
200 gnutls_strerror (err));
205 gnutls_certificate_set_x509_trust_file (data->xcred, mod_ssl.cert_file,
206 GNUTLS_X509_FMT_PEM);
207 /* ignore errors, maybe file doesn't exist yet */
209 if (mod_ssl.ca_certificates_file) {
210 gnutls_certificate_set_x509_trust_file (data->xcred,
211 mod_ssl.ca_certificates_file,
212 GNUTLS_X509_FMT_PEM);
216 gnutls_set_x509_client_key (data->xcred, "", "");
217 gnutls_set_x509_cert_callback (data->xcred, cert_callback);
220 gnutls_init (&data->state, GNUTLS_CLIENT);
223 gnutls_transport_set_ptr (data->state, (gnutls_transport_ptr)(intptr_t)conn->fd);
225 /* disable TLS/SSL protocols as needed */
226 if (!mod_ssl.use_sslv3) {
227 protocol_priority[0] = GNUTLS_TLS1;
228 protocol_priority[1] = 0;
232 use the list set above
235 /* We use default priorities (see gnutls documentation),
236 except for protocol version */
237 gnutls_set_default_priority (data->state);
238 gnutls_protocol_set_priority (data->state, protocol_priority);
241 gnutls_set_cred (data->state, GNUTLS_ANON, NULL);
244 gnutls_credentials_set (data->state, GNUTLS_CRD_CERTIFICATE, data->xcred);
246 err = gnutls_handshake (data->state);
248 while (err == GNUTLS_E_AGAIN || err == GNUTLS_E_INTERRUPTED) {
249 err = gnutls_handshake (data->state);
252 if (err == GNUTLS_E_FATAL_ALERT_RECEIVED) {
253 mutt_error (_("gnutls_handshake: %s(%s)"), gnutls_strerror (err),
254 gnutls_alert_get_name (gnutls_alert_get (data->state)));
257 mutt_error (_("gnutls_handshake: %s"), gnutls_strerror (err));
263 if (!tls_check_certificate (conn))
266 /* set Security Strength Factor (SSF) for SASL */
267 /* NB: gnutls_cipher_get_key_size() returns key length in bytes */
269 gnutls_cipher_get_key_size (gnutls_cipher_get (data->state)) * 8;
271 mutt_message (_("SSL/TLS connection using %s (%s/%s/%s)"),
272 gnutls_protocol_get_name (gnutls_protocol_get_version
274 gnutls_kx_get_name (gnutls_kx_get (data->state)),
275 gnutls_cipher_get_name (gnutls_cipher_get (data->state)),
276 gnutls_mac_get_name (gnutls_mac_get (data->state)));
282 gnutls_certificate_free_credentials (data->xcred);
283 gnutls_deinit (data->state);
284 p_delete(&conn->sockdata);
288 static int tls_socket_close (CONNECTION * conn)
290 tlssockdata *data = conn->sockdata;
293 gnutls_bye (data->state, GNUTLS_SHUT_RDWR);
295 gnutls_certificate_free_credentials (data->xcred);
296 gnutls_deinit (data->state);
297 p_delete(&conn->sockdata);
300 return raw_socket_close (conn);
303 static int tls_starttls_close (CONNECTION * conn)
307 rc = tls_socket_close (conn);
308 conn->conn_read = raw_socket_read;
309 conn->conn_write = raw_socket_write;
310 conn->conn_close = raw_socket_close;
315 #define CERT_SEP "-----BEGIN"
317 /* this bit is based on read_ca_file() in gnutls */
318 static int tls_compare_certificates (const gnutls_datum * peercert)
324 gnutls_datum b64_data;
325 unsigned char *b64_data_data;
326 struct stat filestat;
328 if (stat(mod_ssl.cert_file, &filestat) == -1)
331 b64_data.size = filestat.st_size + 1;
332 b64_data_data = p_new(unsigned char, b64_data.size);
333 b64_data_data[b64_data.size - 1] = '\0';
334 b64_data.data = b64_data_data;
336 fd1 = fopen(mod_ssl.cert_file, "r");
341 b64_data.size = fread (b64_data.data, 1, b64_data.size, fd1);
345 ret = gnutls_pem_base64_decode_alloc (NULL, &b64_data, &cert);
347 p_delete(&b64_data_data);
351 ptr = (unsigned char *) strstr ((char*) b64_data.data, CERT_SEP) + 1;
352 ptr = (unsigned char *) strstr ((char*) ptr, CERT_SEP);
354 b64_data.size = b64_data.size - (ptr - b64_data.data);
357 if (cert.size == peercert->size) {
358 if (memcmp (cert.data, peercert->data, cert.size) == 0) {
360 gnutls_free (cert.data);
361 p_delete(&b64_data_data);
366 gnutls_free (cert.data);
367 } while (ptr != NULL);
370 p_delete(&b64_data_data);
374 static void tls_fingerprint (gnutls_digest_algorithm algo,
375 char *s, int l, const gnutls_datum * data)
383 if (gnutls_fingerprint(algo, data, md, (size_t *)&n) < 0) {
384 snprintf (s, l, _("[unable to calculate]"));
387 for (j = 0; j < (int) n; j++) {
390 snprintf (ch, 8, "%02X%s", md[j], (j % 2 ? " " : ""));
393 s[2 * n + n / 2 - 1] = '\0'; /* don't want trailing space */
397 static char *tls_make_date (time_t t, char *s, ssize_t len)
399 struct tm *l = gmtime(&t);
403 loc = setlocale(LC_TIME, "C");
404 strftime(s, len, "%a, %d %b %Y %T UTC", l);
405 setlocale(LC_TIME, loc);
407 m_strcpy(s, len, _("[invalid date]"));
413 static int tls_check_stored_hostname (const gnutls_datum * cert,
414 const char *hostname)
418 char *linestr = NULL;
422 regmatch_t pmatch[3];
424 /* try checking against names stored in stored certs file */
425 if ((fp = fopen(mod_ssl.cert_file, "r"))) {
428 "^#H ([a-zA-Z0-9_\\.-]+) ([0-9A-F]{4}( [0-9A-F]{4}){7})[ \t]*$",
429 REG_ICASE | REG_EXTENDED) != 0) {
435 tls_fingerprint (GNUTLS_DIG_MD5, buf, sizeof (buf), cert);
437 mutt_read_line (linestr, &linestrsize, fp, &linenum)) != NULL) {
438 if (linestr[0] == '#' && linestr[1] == 'H') {
439 if (regexec (&preg, linestr, 3, pmatch, 0) == 0) {
440 linestr[pmatch[1].rm_eo] = '\0';
441 linestr[pmatch[2].rm_eo] = '\0';
442 if (m_strcmp(linestr + pmatch[1].rm_so, hostname) == 0 &&
443 m_strcmp(linestr + pmatch[2].rm_so, buf) == 0) {
457 /* not found a matching name */
461 static int tls_check_certificate (CONNECTION * conn)
463 tlssockdata *data = conn->sockdata;
464 gnutls_session state = data->state;
467 char dn_common_name[STRING];
468 char dn_email[STRING];
469 char dn_organization[STRING];
470 char dn_organizational_unit[STRING];
471 char dn_locality[STRING];
472 char dn_province[STRING];
473 char dn_country[STRING];
475 int done, row, i, ret;
478 const gnutls_datum *cert_list;
479 unsigned int cert_list_size = 0;
480 gnutls_certificate_status_t certstat;
482 gnutls_x509_crt cert;
483 gnutls_datum pemdata;
484 int certerr_expired = 0;
485 int certerr_notyetvalid = 0;
486 int certerr_hostname = 0;
487 int certerr_nottrusted = 0;
488 int certerr_revoked = 0;
489 int certerr_signernotca = 0;
491 if (gnutls_auth_get_type (state) != GNUTLS_CRD_CERTIFICATE) {
492 mutt_error (_("Unable to get certificate from peer"));
497 if (gnutls_certificate_verify_peers2(state, &certstat) < 0) {
498 mutt_error (_("Certificate verification error (%s)"),
499 gnutls_strerror(certstat));
504 /* We only support X.509 certificates (not OpenPGP) at the moment */
505 if (gnutls_certificate_type_get (state) != GNUTLS_CRT_X509) {
506 mutt_error (_("Certificate is not X.509"));
511 if (gnutls_x509_crt_init (&cert) < 0) {
512 mutt_error (_("Error initialising gnutls certificate data"));
517 cert_list = gnutls_certificate_get_peers (state, &cert_list_size);
519 mutt_error (_("Unable to get certificate from peer"));
524 /* FIXME: Currently only check first certificate in chain. */
525 if (gnutls_x509_crt_import (cert, &cert_list[0], GNUTLS_X509_FMT_DER) < 0) {
526 mutt_error (_("Error processing certificate data"));
531 if (gnutls_x509_crt_get_expiration_time (cert) < time (NULL)) {
535 if (gnutls_x509_crt_get_activation_time (cert) > time (NULL)) {
536 certerr_notyetvalid = 1;
539 if (!gnutls_x509_crt_check_hostname (cert, conn->account.host) &&
540 !tls_check_stored_hostname (&cert_list[0], conn->account.host)) {
541 certerr_hostname = 1;
544 /* see whether certificate is in our cache (certificates file) */
545 if (tls_compare_certificates (&cert_list[0])) {
546 if (certstat & GNUTLS_CERT_INVALID) {
547 /* doesn't matter - have decided is valid because server
548 certificate is in our trusted cache */
549 certstat ^= GNUTLS_CERT_INVALID;
552 if (certstat & GNUTLS_CERT_SIGNER_NOT_FOUND) {
553 /* doesn't matter that we haven't found the signer, since
554 certificate is in our trusted cache */
555 certstat ^= GNUTLS_CERT_SIGNER_NOT_FOUND;
558 if (certstat & GNUTLS_CERT_SIGNER_NOT_CA) {
559 /* Hmm. Not really sure how to handle this, but let's say
560 that we don't care if the CA certificate hasn't got the
561 correct X.509 basic constraints if server certificate is
563 certstat ^= GNUTLS_CERT_SIGNER_NOT_CA;
568 if (certstat & GNUTLS_CERT_REVOKED) {
570 certstat ^= GNUTLS_CERT_REVOKED;
573 if (certstat & GNUTLS_CERT_INVALID) {
574 certerr_nottrusted = 1;
575 certstat ^= GNUTLS_CERT_INVALID;
578 if (certstat & GNUTLS_CERT_SIGNER_NOT_FOUND) {
579 /* NB: already cleared if cert in cache */
580 certerr_nottrusted = 1;
581 certstat ^= GNUTLS_CERT_SIGNER_NOT_FOUND;
584 if (certstat & GNUTLS_CERT_SIGNER_NOT_CA) {
585 /* NB: already cleared if cert in cache */
586 certerr_signernotca = 1;
587 certstat ^= GNUTLS_CERT_SIGNER_NOT_CA;
590 /* OK if signed by (or is) a trusted certificate */
591 /* we've been zeroing the interesting bits in certstat -
592 don't return OK if there are any unhandled bits we don't
594 if (!(certerr_expired || certerr_notyetvalid ||
595 certerr_hostname || certerr_nottrusted) && certstat == 0) {
596 gnutls_x509_crt_deinit (cert);
601 /* interactive check from user */
602 menu = mutt_new_menu ();
604 menu->dialog = p_new(char*, menu->max);
605 for (i = 0; i < menu->max; i++)
606 menu->dialog[i] = p_new(char, STRING);
609 m_strcpy(menu->dialog[row], STRING,
610 _("This certificate belongs to:"));
613 buflen = sizeof (dn_common_name);
614 if (gnutls_x509_crt_get_dn_by_oid (cert, GNUTLS_OID_X520_COMMON_NAME, 0, 0,
615 dn_common_name, (size_t *)&buflen) != 0)
616 dn_common_name[0] = '\0';
617 buflen = sizeof (dn_email);
618 if (gnutls_x509_crt_get_dn_by_oid (cert, GNUTLS_OID_PKCS9_EMAIL, 0, 0,
619 dn_email, (size_t *)&buflen) != 0)
621 buflen = sizeof (dn_organization);
622 if (gnutls_x509_crt_get_dn_by_oid
623 (cert, GNUTLS_OID_X520_ORGANIZATION_NAME, 0, 0, dn_organization,
624 (size_t *)&buflen) != 0)
625 dn_organization[0] = '\0';
626 buflen = sizeof (dn_organizational_unit);
627 if (gnutls_x509_crt_get_dn_by_oid
628 (cert, GNUTLS_OID_X520_ORGANIZATIONAL_UNIT_NAME, 0, 0,
629 dn_organizational_unit, (size_t *)&buflen) != 0)
630 dn_organizational_unit[0] = '\0';
631 buflen = sizeof (dn_locality);
632 if (gnutls_x509_crt_get_dn_by_oid
633 (cert, GNUTLS_OID_X520_LOCALITY_NAME, 0, 0, dn_locality, (size_t *)&buflen) != 0)
634 dn_locality[0] = '\0';
635 buflen = sizeof (dn_province);
636 if (gnutls_x509_crt_get_dn_by_oid
637 (cert, GNUTLS_OID_X520_STATE_OR_PROVINCE_NAME, 0, 0, dn_province,
638 (size_t *)&buflen) != 0)
639 dn_province[0] = '\0';
640 buflen = sizeof (dn_country);
641 if (gnutls_x509_crt_get_dn_by_oid (cert, GNUTLS_OID_X520_COUNTRY_NAME, 0, 0,
642 dn_country, (size_t *)&buflen) != 0)
643 dn_country[0] = '\0';
645 snprintf (menu->dialog[row++], STRING, " %s %s", dn_common_name,
647 snprintf (menu->dialog[row++], STRING, " %s", dn_organization);
648 snprintf (menu->dialog[row++], STRING, " %s",
649 dn_organizational_unit);
650 snprintf (menu->dialog[row++], STRING, " %s %s %s", dn_locality,
651 dn_province, dn_country);
654 m_strcpy(menu->dialog[row], STRING,
655 _("This certificate was issued by:"));
658 buflen = sizeof (dn_common_name);
659 if (gnutls_x509_crt_get_issuer_dn_by_oid
660 (cert, GNUTLS_OID_X520_COMMON_NAME, 0, 0, dn_common_name, (size_t *)&buflen) != 0)
661 dn_common_name[0] = '\0';
662 buflen = sizeof (dn_email);
663 if (gnutls_x509_crt_get_issuer_dn_by_oid
664 (cert, GNUTLS_OID_PKCS9_EMAIL, 0, 0, dn_email, (size_t *)&buflen) != 0)
666 buflen = sizeof (dn_organization);
667 if (gnutls_x509_crt_get_issuer_dn_by_oid
668 (cert, GNUTLS_OID_X520_ORGANIZATION_NAME, 0, 0, dn_organization,
669 (size_t *)&buflen) != 0)
670 dn_organization[0] = '\0';
671 buflen = sizeof (dn_organizational_unit);
672 if (gnutls_x509_crt_get_issuer_dn_by_oid
673 (cert, GNUTLS_OID_X520_ORGANIZATIONAL_UNIT_NAME, 0, 0,
674 dn_organizational_unit, (size_t *)&buflen) != 0)
675 dn_organizational_unit[0] = '\0';
676 buflen = sizeof (dn_locality);
677 if (gnutls_x509_crt_get_issuer_dn_by_oid
678 (cert, GNUTLS_OID_X520_LOCALITY_NAME, 0, 0, dn_locality, (size_t *)&buflen) != 0)
679 dn_locality[0] = '\0';
680 buflen = sizeof (dn_province);
681 if (gnutls_x509_crt_get_issuer_dn_by_oid
682 (cert, GNUTLS_OID_X520_STATE_OR_PROVINCE_NAME, 0, 0, dn_province,
683 (size_t *)&buflen) != 0)
684 dn_province[0] = '\0';
685 buflen = sizeof (dn_country);
686 if (gnutls_x509_crt_get_issuer_dn_by_oid
687 (cert, GNUTLS_OID_X520_COUNTRY_NAME, 0, 0, dn_country, (size_t *)&buflen) != 0)
688 dn_country[0] = '\0';
690 snprintf (menu->dialog[row++], STRING, " %s %s", dn_common_name,
692 snprintf (menu->dialog[row++], STRING, " %s", dn_organization);
693 snprintf (menu->dialog[row++], STRING, " %s",
694 dn_organizational_unit);
695 snprintf (menu->dialog[row++], STRING, " %s %s %s", dn_locality,
696 dn_province, dn_country);
699 snprintf (menu->dialog[row++], STRING,
700 _("This certificate is valid"));
702 t = gnutls_x509_crt_get_activation_time (cert);
703 snprintf (menu->dialog[row++], STRING, _(" from %s"),
704 tls_make_date (t, datestr, 30));
706 t = gnutls_x509_crt_get_expiration_time (cert);
707 snprintf (menu->dialog[row++], STRING, _(" to %s"),
708 tls_make_date (t, datestr, 30));
711 tls_fingerprint (GNUTLS_DIG_SHA, fpbuf, sizeof (fpbuf), &cert_list[0]);
712 snprintf (menu->dialog[row++], STRING, _("SHA1 Fingerprint: %s"),
715 tls_fingerprint (GNUTLS_DIG_MD5, fpbuf, sizeof (fpbuf), &cert_list[0]);
716 snprintf (menu->dialog[row++], STRING, _("MD5 Fingerprint: %s"),
719 if (certerr_notyetvalid) {
721 m_strcpy(menu->dialog[row], STRING,
722 _("WARNING: Server certificate is not yet valid"));
724 if (certerr_expired) {
726 m_strcpy(menu->dialog[row], STRING,
727 _("WARNING: Server certificate has expired"));
729 if (certerr_revoked) {
731 m_strcpy(menu->dialog[row], STRING,
732 _("WARNING: Server certificate has been revoked"));
734 if (certerr_hostname) {
736 m_strcpy(menu->dialog[row], STRING,
737 _("WARNING: Server hostname does not match certificate"));
739 if (certerr_signernotca) {
741 m_strcpy(menu->dialog[row], STRING,
742 _("WARNING: Signer of server certificate is not a CA"));
745 menu->title = _("TLS/SSL Certificate check");
746 /* certificates with bad dates, or that are revoked, must be
747 accepted manually each and every time */
748 if (mod_ssl.cert_file && !certerr_expired && !certerr_notyetvalid
749 && !certerr_revoked) {
750 menu->prompt = _("(r)eject, accept (o)nce, (a)ccept always");
751 menu->keys = _("roa");
754 menu->prompt = _("(r)eject, accept (o)nce");
755 menu->keys = _("ro");
759 set_option (OPTUNBUFFEREDINPUT);
761 switch (mutt_menuLoop (menu)) {
763 case OP_MAX + 1: /* reject */
767 case OP_MAX + 3: /* accept always */
769 if ((fp = fopen(mod_ssl.cert_file, "a"))) {
770 /* save hostname if necessary */
771 if (certerr_hostname) {
772 fprintf (fp, "#H %s %s\n", conn->account.host, fpbuf);
775 if (certerr_nottrusted) {
777 ret = gnutls_pem_base64_encode_alloc ("CERTIFICATE", &cert_list[0],
780 if (fwrite (pemdata.data, pemdata.size, 1, fp) == 1) {
783 gnutls_free (pemdata.data);
789 mutt_error (_("Warning: Couldn't save certificate"));
793 mutt_message (_("Certificate saved"));
797 case OP_MAX + 2: /* accept once */
802 unset_option (OPTUNBUFFEREDINPUT);
803 mutt_menuDestroy (&menu);
804 gnutls_x509_crt_deinit (cert);