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 ** If \fIset\fP (the default), Madmutt will attempt to use STARTTLS on servers
38 ** advertising the capability. When \fIunset\fP, Madmutt will not attempt to
39 ** use STARTTLS regardless of the server's capabilities.
44 ** This variables specifies whether to attempt to use SSLv3 in the
45 ** SSL authentication process.
48 path_t cert_file = luaM_pathnew("~/.cache/madmutt/certificates");
51 ** This variable specifies the file where the certificates you trust
52 ** are saved. When an unknown certificate is encountered, you are asked
53 ** if you accept it or not. If you accept it, the certificate can also
54 ** be saved in this file and further connections are automatically
57 ** You can also manually add CA certificates in this file. Any server
58 ** certificate that is signed with one of these CA certificates are
59 ** also automatically accepted.
61 ** Example: \fTset certificate_file=~/.madmutt/certificates\fP
64 path_t ca_certificates_file = NULL;
67 ** This variable specifies a file containing trusted CA certificates.
68 ** Any server certificate that is signed with one of these CA
69 ** certificates are also automatically accepted.
71 ** Example: \fTset ssl_ca_certificates_file=/etc/ssl/certs/ca-certificates.crt\fP
75 typedef struct _tlssockdata {
77 gnutls_certificate_credentials xcred;
80 /* local prototypes */
81 static int tls_socket_read (CONNECTION * conn, char *buf, ssize_t len);
82 static int tls_socket_write (CONNECTION * conn, const char *buf, ssize_t len);
83 static int tls_socket_open (CONNECTION * conn);
84 static int tls_socket_close (CONNECTION * conn);
85 static int tls_starttls_close (CONNECTION * conn);
87 static int tls_init (void);
88 static int tls_negotiate (CONNECTION * conn);
89 static int tls_check_certificate (CONNECTION * conn);
92 static int tls_init (void)
94 static unsigned char init_complete = 0;
100 err = gnutls_global_init ();
102 mutt_error (_("gnutls_global_init: %s"), gnutls_strerror (err));
111 int mutt_ssl_socket_setup (CONNECTION * conn)
116 conn->conn_open = tls_socket_open;
117 conn->conn_read = tls_socket_read;
118 conn->conn_write = tls_socket_write;
119 conn->conn_close = tls_socket_close;
124 static int tls_socket_read (CONNECTION * conn, char *buf, ssize_t len)
126 tlssockdata *data = conn->sockdata;
130 mutt_error (_("Error: no TLS socket open"));
135 ret = gnutls_record_recv (data->state, buf, len);
136 if (ret < 0 && gnutls_error_is_fatal (ret) == 1) {
137 mutt_error (_("tls_socket_read (%s)"), gnutls_strerror (ret));
144 static int tls_socket_write (CONNECTION * conn, const char *buf, ssize_t len)
146 tlssockdata *data = conn->sockdata;
150 mutt_error (_("Error: no TLS socket open"));
155 ret = gnutls_record_send (data->state, buf, len);
156 if (ret < 0 && gnutls_error_is_fatal (ret) == 1) {
157 mutt_error (_("tls_socket_write (%s)"), gnutls_strerror (ret));
164 static int tls_socket_open (CONNECTION * conn)
166 if (raw_socket_open (conn) < 0)
169 if (tls_negotiate (conn) < 0) {
170 tls_socket_close (conn);
177 int mutt_ssl_starttls (CONNECTION * conn)
182 if (tls_negotiate (conn) < 0)
185 conn->conn_read = tls_socket_read;
186 conn->conn_write = tls_socket_write;
187 conn->conn_close = tls_starttls_close;
192 static int protocol_priority[] = { GNUTLS_TLS1, GNUTLS_SSL3, 0 };
194 /* tls_negotiate: After TLS state has been initialised, attempt to negotiate
195 * TLS over the wire, including certificate checks. */
196 static int tls_negotiate (CONNECTION * conn)
201 data = p_new(tlssockdata, 1);
202 conn->sockdata = data;
203 err = gnutls_certificate_allocate_credentials (&data->xcred);
205 p_delete(&conn->sockdata);
206 mutt_error (_("gnutls_certificate_allocate_credentials: %s"),
207 gnutls_strerror (err));
212 gnutls_certificate_set_x509_trust_file (data->xcred, mod_ssl.cert_file,
213 GNUTLS_X509_FMT_PEM);
214 /* ignore errors, maybe file doesn't exist yet */
216 if (mod_ssl.ca_certificates_file) {
217 gnutls_certificate_set_x509_trust_file (data->xcred,
218 mod_ssl.ca_certificates_file,
219 GNUTLS_X509_FMT_PEM);
223 gnutls_set_x509_client_key (data->xcred, "", "");
224 gnutls_set_x509_cert_callback (data->xcred, cert_callback);
227 gnutls_init (&data->state, GNUTLS_CLIENT);
230 gnutls_transport_set_ptr (data->state, (gnutls_transport_ptr)(intptr_t)conn->fd);
232 /* disable TLS/SSL protocols as needed */
233 if (!mod_ssl.use_sslv3) {
234 protocol_priority[0] = GNUTLS_TLS1;
235 protocol_priority[1] = 0;
239 use the list set above
242 /* We use default priorities (see gnutls documentation),
243 except for protocol version */
244 gnutls_set_default_priority (data->state);
245 gnutls_protocol_set_priority (data->state, protocol_priority);
248 gnutls_set_cred (data->state, GNUTLS_ANON, NULL);
251 gnutls_credentials_set (data->state, GNUTLS_CRD_CERTIFICATE, data->xcred);
253 err = gnutls_handshake (data->state);
255 while (err == GNUTLS_E_AGAIN || err == GNUTLS_E_INTERRUPTED) {
256 err = gnutls_handshake (data->state);
259 if (err == GNUTLS_E_FATAL_ALERT_RECEIVED) {
260 mutt_error (_("gnutls_handshake: %s(%s)"), gnutls_strerror (err),
261 gnutls_alert_get_name (gnutls_alert_get (data->state)));
264 mutt_error (_("gnutls_handshake: %s"), gnutls_strerror (err));
270 if (!tls_check_certificate (conn))
273 /* set Security Strength Factor (SSF) for SASL */
274 /* NB: gnutls_cipher_get_key_size() returns key length in bytes */
276 gnutls_cipher_get_key_size (gnutls_cipher_get (data->state)) * 8;
278 mutt_message (_("SSL/TLS connection using %s (%s/%s/%s)"),
279 gnutls_protocol_get_name (gnutls_protocol_get_version
281 gnutls_kx_get_name (gnutls_kx_get (data->state)),
282 gnutls_cipher_get_name (gnutls_cipher_get (data->state)),
283 gnutls_mac_get_name (gnutls_mac_get (data->state)));
289 gnutls_certificate_free_credentials (data->xcred);
290 gnutls_deinit (data->state);
291 p_delete(&conn->sockdata);
295 static int tls_socket_close (CONNECTION * conn)
297 tlssockdata *data = conn->sockdata;
300 gnutls_bye (data->state, GNUTLS_SHUT_RDWR);
302 gnutls_certificate_free_credentials (data->xcred);
303 gnutls_deinit (data->state);
304 p_delete(&conn->sockdata);
307 return raw_socket_close (conn);
310 static int tls_starttls_close (CONNECTION * conn)
314 rc = tls_socket_close (conn);
315 conn->conn_read = raw_socket_read;
316 conn->conn_write = raw_socket_write;
317 conn->conn_close = raw_socket_close;
322 #define CERT_SEP "-----BEGIN"
324 /* this bit is based on read_ca_file() in gnutls */
325 static int tls_compare_certificates (const gnutls_datum * peercert)
331 gnutls_datum b64_data;
332 unsigned char *b64_data_data;
333 struct stat filestat;
335 if (stat(mod_ssl.cert_file, &filestat) == -1)
338 b64_data.size = filestat.st_size + 1;
339 b64_data_data = p_new(unsigned char, b64_data.size);
340 b64_data_data[b64_data.size - 1] = '\0';
341 b64_data.data = b64_data_data;
343 fd1 = fopen(mod_ssl.cert_file, "r");
348 b64_data.size = fread (b64_data.data, 1, b64_data.size, fd1);
352 ret = gnutls_pem_base64_decode_alloc (NULL, &b64_data, &cert);
354 p_delete(&b64_data_data);
358 ptr = (unsigned char *) strstr ((char*) b64_data.data, CERT_SEP) + 1;
359 ptr = (unsigned char *) strstr ((char*) ptr, CERT_SEP);
361 b64_data.size = b64_data.size - (ptr - b64_data.data);
364 if (cert.size == peercert->size) {
365 if (memcmp (cert.data, peercert->data, cert.size) == 0) {
367 gnutls_free (cert.data);
368 p_delete(&b64_data_data);
373 gnutls_free (cert.data);
374 } while (ptr != NULL);
377 p_delete(&b64_data_data);
381 static void tls_fingerprint (gnutls_digest_algorithm algo,
382 char *s, int l, const gnutls_datum * data)
390 if (gnutls_fingerprint(algo, data, md, (size_t *)&n) < 0) {
391 snprintf (s, l, _("[unable to calculate]"));
394 for (j = 0; j < (int) n; j++) {
397 snprintf (ch, 8, "%02X%s", md[j], (j % 2 ? " " : ""));
400 s[2 * n + n / 2 - 1] = '\0'; /* don't want trailing space */
404 static char *tls_make_date (time_t t, char *s, ssize_t len)
406 struct tm *l = gmtime(&t);
410 loc = setlocale(LC_TIME, "C");
411 strftime(s, len, "%a, %d %b %Y %T UTC", l);
412 setlocale(LC_TIME, loc);
414 m_strcpy(s, len, _("[invalid date]"));
420 static int tls_check_stored_hostname (const gnutls_datum * cert,
421 const char *hostname)
425 char *linestr = NULL;
429 regmatch_t pmatch[3];
431 /* try checking against names stored in stored certs file */
432 if ((fp = fopen(mod_ssl.cert_file, "r"))) {
435 "^#H ([a-zA-Z0-9_\\.-]+) ([0-9A-F]{4}( [0-9A-F]{4}){7})[ \t]*$",
436 REG_ICASE | REG_EXTENDED) != 0) {
442 tls_fingerprint (GNUTLS_DIG_MD5, buf, sizeof (buf), cert);
444 mutt_read_line (linestr, &linestrsize, fp, &linenum)) != NULL) {
445 if (linestr[0] == '#' && linestr[1] == 'H') {
446 if (regexec (&preg, linestr, 3, pmatch, 0) == 0) {
447 linestr[pmatch[1].rm_eo] = '\0';
448 linestr[pmatch[2].rm_eo] = '\0';
449 if (m_strcmp(linestr + pmatch[1].rm_so, hostname) == 0 &&
450 m_strcmp(linestr + pmatch[2].rm_so, buf) == 0) {
464 /* not found a matching name */
468 static int tls_check_certificate (CONNECTION * conn)
470 tlssockdata *data = conn->sockdata;
471 gnutls_session state = data->state;
474 char dn_common_name[STRING];
475 char dn_email[STRING];
476 char dn_organization[STRING];
477 char dn_organizational_unit[STRING];
478 char dn_locality[STRING];
479 char dn_province[STRING];
480 char dn_country[STRING];
482 int done, row, i, ret;
485 const gnutls_datum *cert_list;
486 unsigned int cert_list_size = 0;
487 gnutls_certificate_status_t certstat;
489 gnutls_x509_crt cert;
490 gnutls_datum pemdata;
491 int certerr_expired = 0;
492 int certerr_notyetvalid = 0;
493 int certerr_hostname = 0;
494 int certerr_nottrusted = 0;
495 int certerr_revoked = 0;
496 int certerr_signernotca = 0;
498 if (gnutls_auth_get_type (state) != GNUTLS_CRD_CERTIFICATE) {
499 mutt_error (_("Unable to get certificate from peer"));
504 if (gnutls_certificate_verify_peers2(state, &certstat) < 0) {
505 mutt_error (_("Certificate verification error (%s)"),
506 gnutls_strerror(certstat));
511 /* We only support X.509 certificates (not OpenPGP) at the moment */
512 if (gnutls_certificate_type_get (state) != GNUTLS_CRT_X509) {
513 mutt_error (_("Certificate is not X.509"));
518 if (gnutls_x509_crt_init (&cert) < 0) {
519 mutt_error (_("Error initialising gnutls certificate data"));
524 cert_list = gnutls_certificate_get_peers (state, &cert_list_size);
526 mutt_error (_("Unable to get certificate from peer"));
531 /* FIXME: Currently only check first certificate in chain. */
532 if (gnutls_x509_crt_import (cert, &cert_list[0], GNUTLS_X509_FMT_DER) < 0) {
533 mutt_error (_("Error processing certificate data"));
538 if (gnutls_x509_crt_get_expiration_time (cert) < time (NULL)) {
542 if (gnutls_x509_crt_get_activation_time (cert) > time (NULL)) {
543 certerr_notyetvalid = 1;
546 if (!gnutls_x509_crt_check_hostname (cert, conn->account.host) &&
547 !tls_check_stored_hostname (&cert_list[0], conn->account.host)) {
548 certerr_hostname = 1;
551 /* see whether certificate is in our cache (certificates file) */
552 if (tls_compare_certificates (&cert_list[0])) {
553 if (certstat & GNUTLS_CERT_INVALID) {
554 /* doesn't matter - have decided is valid because server
555 certificate is in our trusted cache */
556 certstat ^= GNUTLS_CERT_INVALID;
559 if (certstat & GNUTLS_CERT_SIGNER_NOT_FOUND) {
560 /* doesn't matter that we haven't found the signer, since
561 certificate is in our trusted cache */
562 certstat ^= GNUTLS_CERT_SIGNER_NOT_FOUND;
565 if (certstat & GNUTLS_CERT_SIGNER_NOT_CA) {
566 /* Hmm. Not really sure how to handle this, but let's say
567 that we don't care if the CA certificate hasn't got the
568 correct X.509 basic constraints if server certificate is
570 certstat ^= GNUTLS_CERT_SIGNER_NOT_CA;
575 if (certstat & GNUTLS_CERT_REVOKED) {
577 certstat ^= GNUTLS_CERT_REVOKED;
580 if (certstat & GNUTLS_CERT_INVALID) {
581 certerr_nottrusted = 1;
582 certstat ^= GNUTLS_CERT_INVALID;
585 if (certstat & GNUTLS_CERT_SIGNER_NOT_FOUND) {
586 /* NB: already cleared if cert in cache */
587 certerr_nottrusted = 1;
588 certstat ^= GNUTLS_CERT_SIGNER_NOT_FOUND;
591 if (certstat & GNUTLS_CERT_SIGNER_NOT_CA) {
592 /* NB: already cleared if cert in cache */
593 certerr_signernotca = 1;
594 certstat ^= GNUTLS_CERT_SIGNER_NOT_CA;
597 /* OK if signed by (or is) a trusted certificate */
598 /* we've been zeroing the interesting bits in certstat -
599 don't return OK if there are any unhandled bits we don't
601 if (!(certerr_expired || certerr_notyetvalid ||
602 certerr_hostname || certerr_nottrusted) && certstat == 0) {
603 gnutls_x509_crt_deinit (cert);
608 /* interactive check from user */
609 menu = mutt_new_menu ();
611 menu->dialog = p_new(char*, menu->max);
612 for (i = 0; i < menu->max; i++)
613 menu->dialog[i] = p_new(char, STRING);
616 m_strcpy(menu->dialog[row], STRING,
617 _("This certificate belongs to:"));
620 buflen = sizeof (dn_common_name);
621 if (gnutls_x509_crt_get_dn_by_oid (cert, GNUTLS_OID_X520_COMMON_NAME, 0, 0,
622 dn_common_name, (size_t *)&buflen) != 0)
623 dn_common_name[0] = '\0';
624 buflen = sizeof (dn_email);
625 if (gnutls_x509_crt_get_dn_by_oid (cert, GNUTLS_OID_PKCS9_EMAIL, 0, 0,
626 dn_email, (size_t *)&buflen) != 0)
628 buflen = sizeof (dn_organization);
629 if (gnutls_x509_crt_get_dn_by_oid
630 (cert, GNUTLS_OID_X520_ORGANIZATION_NAME, 0, 0, dn_organization,
631 (size_t *)&buflen) != 0)
632 dn_organization[0] = '\0';
633 buflen = sizeof (dn_organizational_unit);
634 if (gnutls_x509_crt_get_dn_by_oid
635 (cert, GNUTLS_OID_X520_ORGANIZATIONAL_UNIT_NAME, 0, 0,
636 dn_organizational_unit, (size_t *)&buflen) != 0)
637 dn_organizational_unit[0] = '\0';
638 buflen = sizeof (dn_locality);
639 if (gnutls_x509_crt_get_dn_by_oid
640 (cert, GNUTLS_OID_X520_LOCALITY_NAME, 0, 0, dn_locality, (size_t *)&buflen) != 0)
641 dn_locality[0] = '\0';
642 buflen = sizeof (dn_province);
643 if (gnutls_x509_crt_get_dn_by_oid
644 (cert, GNUTLS_OID_X520_STATE_OR_PROVINCE_NAME, 0, 0, dn_province,
645 (size_t *)&buflen) != 0)
646 dn_province[0] = '\0';
647 buflen = sizeof (dn_country);
648 if (gnutls_x509_crt_get_dn_by_oid (cert, GNUTLS_OID_X520_COUNTRY_NAME, 0, 0,
649 dn_country, (size_t *)&buflen) != 0)
650 dn_country[0] = '\0';
652 snprintf (menu->dialog[row++], STRING, " %s %s", dn_common_name,
654 snprintf (menu->dialog[row++], STRING, " %s", dn_organization);
655 snprintf (menu->dialog[row++], STRING, " %s",
656 dn_organizational_unit);
657 snprintf (menu->dialog[row++], STRING, " %s %s %s", dn_locality,
658 dn_province, dn_country);
661 m_strcpy(menu->dialog[row], STRING,
662 _("This certificate was issued by:"));
665 buflen = sizeof (dn_common_name);
666 if (gnutls_x509_crt_get_issuer_dn_by_oid
667 (cert, GNUTLS_OID_X520_COMMON_NAME, 0, 0, dn_common_name, (size_t *)&buflen) != 0)
668 dn_common_name[0] = '\0';
669 buflen = sizeof (dn_email);
670 if (gnutls_x509_crt_get_issuer_dn_by_oid
671 (cert, GNUTLS_OID_PKCS9_EMAIL, 0, 0, dn_email, (size_t *)&buflen) != 0)
673 buflen = sizeof (dn_organization);
674 if (gnutls_x509_crt_get_issuer_dn_by_oid
675 (cert, GNUTLS_OID_X520_ORGANIZATION_NAME, 0, 0, dn_organization,
676 (size_t *)&buflen) != 0)
677 dn_organization[0] = '\0';
678 buflen = sizeof (dn_organizational_unit);
679 if (gnutls_x509_crt_get_issuer_dn_by_oid
680 (cert, GNUTLS_OID_X520_ORGANIZATIONAL_UNIT_NAME, 0, 0,
681 dn_organizational_unit, (size_t *)&buflen) != 0)
682 dn_organizational_unit[0] = '\0';
683 buflen = sizeof (dn_locality);
684 if (gnutls_x509_crt_get_issuer_dn_by_oid
685 (cert, GNUTLS_OID_X520_LOCALITY_NAME, 0, 0, dn_locality, (size_t *)&buflen) != 0)
686 dn_locality[0] = '\0';
687 buflen = sizeof (dn_province);
688 if (gnutls_x509_crt_get_issuer_dn_by_oid
689 (cert, GNUTLS_OID_X520_STATE_OR_PROVINCE_NAME, 0, 0, dn_province,
690 (size_t *)&buflen) != 0)
691 dn_province[0] = '\0';
692 buflen = sizeof (dn_country);
693 if (gnutls_x509_crt_get_issuer_dn_by_oid
694 (cert, GNUTLS_OID_X520_COUNTRY_NAME, 0, 0, dn_country, (size_t *)&buflen) != 0)
695 dn_country[0] = '\0';
697 snprintf (menu->dialog[row++], STRING, " %s %s", dn_common_name,
699 snprintf (menu->dialog[row++], STRING, " %s", dn_organization);
700 snprintf (menu->dialog[row++], STRING, " %s",
701 dn_organizational_unit);
702 snprintf (menu->dialog[row++], STRING, " %s %s %s", dn_locality,
703 dn_province, dn_country);
706 snprintf (menu->dialog[row++], STRING,
707 _("This certificate is valid"));
709 t = gnutls_x509_crt_get_activation_time (cert);
710 snprintf (menu->dialog[row++], STRING, _(" from %s"),
711 tls_make_date (t, datestr, 30));
713 t = gnutls_x509_crt_get_expiration_time (cert);
714 snprintf (menu->dialog[row++], STRING, _(" to %s"),
715 tls_make_date (t, datestr, 30));
718 tls_fingerprint (GNUTLS_DIG_SHA, fpbuf, sizeof (fpbuf), &cert_list[0]);
719 snprintf (menu->dialog[row++], STRING, _("SHA1 Fingerprint: %s"),
722 tls_fingerprint (GNUTLS_DIG_MD5, fpbuf, sizeof (fpbuf), &cert_list[0]);
723 snprintf (menu->dialog[row++], STRING, _("MD5 Fingerprint: %s"),
726 if (certerr_notyetvalid) {
728 m_strcpy(menu->dialog[row], STRING,
729 _("WARNING: Server certificate is not yet valid"));
731 if (certerr_expired) {
733 m_strcpy(menu->dialog[row], STRING,
734 _("WARNING: Server certificate has expired"));
736 if (certerr_revoked) {
738 m_strcpy(menu->dialog[row], STRING,
739 _("WARNING: Server certificate has been revoked"));
741 if (certerr_hostname) {
743 m_strcpy(menu->dialog[row], STRING,
744 _("WARNING: Server hostname does not match certificate"));
746 if (certerr_signernotca) {
748 m_strcpy(menu->dialog[row], STRING,
749 _("WARNING: Signer of server certificate is not a CA"));
752 menu->title = _("TLS/SSL Certificate check");
753 /* certificates with bad dates, or that are revoked, must be
754 accepted manually each and every time */
755 if (mod_ssl.cert_file && !certerr_expired && !certerr_notyetvalid
756 && !certerr_revoked) {
757 menu->prompt = _("(r)eject, accept (o)nce, (a)ccept always");
758 menu->keys = _("roa");
761 menu->prompt = _("(r)eject, accept (o)nce");
762 menu->keys = _("ro");
766 set_option (OPTUNBUFFEREDINPUT);
768 switch (mutt_menuLoop (menu)) {
770 case OP_MAX + 1: /* reject */
774 case OP_MAX + 3: /* accept always */
776 if ((fp = fopen(mod_ssl.cert_file, "a"))) {
777 /* save hostname if necessary */
778 if (certerr_hostname) {
779 fprintf (fp, "#H %s %s\n", conn->account.host, fpbuf);
782 if (certerr_nottrusted) {
784 ret = gnutls_pem_base64_encode_alloc ("CERTIFICATE", &cert_list[0],
787 if (fwrite (pemdata.data, pemdata.size, 1, fp) == 1) {
790 gnutls_free (pemdata.data);
796 mutt_error (_("Warning: Couldn't save certificate"));
800 mutt_message (_("Certificate saved"));
804 case OP_MAX + 2: /* accept once */
809 unset_option (OPTUNBUFFEREDINPUT);
810 mutt_menuDestroy (&menu);
811 gnutls_x509_crt_deinit (cert);