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>
15 #include <gnutls/gnutls.h>
16 #include <gnutls/x509.h>
17 #ifdef HAVE_GNUTLS_OPENSSL_H
18 #include <gnutls/openssl.h>
21 #include <lib-ui/curses.h>
22 #include <lib-ui/menu.h>
25 #include "mutt_socket.h"
28 typedef struct _tlssockdata {
30 gnutls_certificate_credentials xcred;
33 /* local prototypes */
34 static int tls_socket_read (CONNECTION * conn, char *buf, ssize_t len);
35 static int tls_socket_write (CONNECTION * conn, const char *buf, ssize_t len);
36 static int tls_socket_open (CONNECTION * conn);
37 static int tls_socket_close (CONNECTION * conn);
38 static int tls_starttls_close (CONNECTION * conn);
40 static int tls_init (void);
41 static int tls_negotiate (CONNECTION * conn);
42 static int tls_check_certificate (CONNECTION * conn);
45 static int tls_init (void)
47 static unsigned char init_complete = 0;
53 err = gnutls_global_init ();
55 mutt_error (_("gnutls_global_init: %s"), gnutls_strerror (err));
64 int mutt_ssl_socket_setup (CONNECTION * conn)
69 conn->conn_open = tls_socket_open;
70 conn->conn_read = tls_socket_read;
71 conn->conn_write = tls_socket_write;
72 conn->conn_close = tls_socket_close;
77 static int tls_socket_read (CONNECTION * conn, char *buf, ssize_t len)
79 tlssockdata *data = conn->sockdata;
83 mutt_error (_("Error: no TLS socket open"));
88 ret = gnutls_record_recv (data->state, buf, len);
89 if (gnutls_error_is_fatal (ret) == 1) {
90 mutt_error (_("tls_socket_read (%s)"), gnutls_strerror (ret));
97 static int tls_socket_write (CONNECTION * conn, const char *buf, ssize_t len)
99 tlssockdata *data = conn->sockdata;
103 mutt_error (_("Error: no TLS socket open"));
108 ret = gnutls_record_send (data->state, buf, len);
109 if (gnutls_error_is_fatal (ret) == 1) {
110 mutt_error (_("tls_socket_write (%s)"), gnutls_strerror (ret));
117 static int tls_socket_open (CONNECTION * conn)
119 if (raw_socket_open (conn) < 0)
122 if (tls_negotiate (conn) < 0) {
123 tls_socket_close (conn);
130 int mutt_ssl_starttls (CONNECTION * conn)
135 if (tls_negotiate (conn) < 0)
138 conn->conn_read = tls_socket_read;
139 conn->conn_write = tls_socket_write;
140 conn->conn_close = tls_starttls_close;
145 static int protocol_priority[] = { GNUTLS_TLS1, GNUTLS_SSL3, 0 };
147 /* tls_negotiate: After TLS state has been initialised, attempt to negotiate
148 * TLS over the wire, including certificate checks. */
149 static int tls_negotiate (CONNECTION * conn)
154 data = p_new(tlssockdata, 1);
155 conn->sockdata = data;
156 err = gnutls_certificate_allocate_credentials (&data->xcred);
158 p_delete(&conn->sockdata);
159 mutt_error (_("gnutls_certificate_allocate_credentials: %s"),
160 gnutls_strerror (err));
165 gnutls_certificate_set_x509_trust_file (data->xcred, SslCertFile,
166 GNUTLS_X509_FMT_PEM);
167 /* ignore errors, maybe file doesn't exist yet */
170 gnutls_certificate_set_x509_trust_file (data->xcred, SslCACertFile,
171 GNUTLS_X509_FMT_PEM);
175 gnutls_set_x509_client_key (data->xcred, "", "");
176 gnutls_set_x509_cert_callback (data->xcred, cert_callback);
179 gnutls_init (&data->state, GNUTLS_CLIENT);
182 gnutls_transport_set_ptr (data->state, (gnutls_transport_ptr)(intptr_t)conn->fd);
184 /* disable TLS/SSL protocols as needed */
185 if (!option (OPTTLSV1) && !option (OPTSSLV3)) {
186 mutt_error (_("All available protocols for TLS/SSL connection disabled"));
189 else if (!option (OPTTLSV1)) {
190 protocol_priority[0] = GNUTLS_SSL3;
191 protocol_priority[1] = 0;
193 else if (!option (OPTSSLV3)) {
194 protocol_priority[0] = GNUTLS_TLS1;
195 protocol_priority[1] = 0;
199 use the list set above
202 /* We use default priorities (see gnutls documentation),
203 except for protocol version */
204 gnutls_set_default_priority (data->state);
205 gnutls_protocol_set_priority (data->state, protocol_priority);
207 if (SslDHPrimeBits > 0) {
208 gnutls_dh_set_prime_bits (data->state, SslDHPrimeBits);
212 gnutls_set_cred (data->state, GNUTLS_ANON, NULL);
215 gnutls_credentials_set (data->state, GNUTLS_CRD_CERTIFICATE, data->xcred);
217 err = gnutls_handshake (data->state);
219 while (err == GNUTLS_E_AGAIN || err == GNUTLS_E_INTERRUPTED) {
220 err = gnutls_handshake (data->state);
223 if (err == GNUTLS_E_FATAL_ALERT_RECEIVED) {
224 mutt_error (_("gnutls_handshake: %s(%s)"), gnutls_strerror (err),
225 gnutls_alert_get_name (gnutls_alert_get (data->state)));
228 mutt_error (_("gnutls_handshake: %s"), gnutls_strerror (err));
234 if (!tls_check_certificate (conn))
237 /* set Security Strength Factor (SSF) for SASL */
238 /* NB: gnutls_cipher_get_key_size() returns key length in bytes */
240 gnutls_cipher_get_key_size (gnutls_cipher_get (data->state)) * 8;
242 mutt_message (_("SSL/TLS connection using %s (%s/%s/%s)"),
243 gnutls_protocol_get_name (gnutls_protocol_get_version
245 gnutls_kx_get_name (gnutls_kx_get (data->state)),
246 gnutls_cipher_get_name (gnutls_cipher_get (data->state)),
247 gnutls_mac_get_name (gnutls_mac_get (data->state)));
253 gnutls_certificate_free_credentials (data->xcred);
254 gnutls_deinit (data->state);
255 p_delete(&conn->sockdata);
259 static int tls_socket_close (CONNECTION * conn)
261 tlssockdata *data = conn->sockdata;
264 gnutls_bye (data->state, GNUTLS_SHUT_RDWR);
266 gnutls_certificate_free_credentials (data->xcred);
267 gnutls_deinit (data->state);
268 p_delete(&conn->sockdata);
271 return raw_socket_close (conn);
274 static int tls_starttls_close (CONNECTION * conn)
278 rc = tls_socket_close (conn);
279 conn->conn_read = raw_socket_read;
280 conn->conn_write = raw_socket_write;
281 conn->conn_close = raw_socket_close;
286 #define CERT_SEP "-----BEGIN"
288 /* this bit is based on read_ca_file() in gnutls */
289 static int tls_compare_certificates (const gnutls_datum * peercert)
295 gnutls_datum b64_data;
296 unsigned char *b64_data_data;
297 struct stat filestat;
299 if (stat (SslCertFile, &filestat) == -1)
302 b64_data.size = filestat.st_size + 1;
303 b64_data_data = p_new(unsigned char, b64_data.size);
304 b64_data_data[b64_data.size - 1] = '\0';
305 b64_data.data = b64_data_data;
307 fd1 = fopen (SslCertFile, "r");
312 b64_data.size = fread (b64_data.data, 1, b64_data.size, fd1);
316 ret = gnutls_pem_base64_decode_alloc (NULL, &b64_data, &cert);
318 p_delete(&b64_data_data);
322 ptr = (unsigned char *) strstr ((char*) b64_data.data, CERT_SEP) + 1;
323 ptr = (unsigned char *) strstr ((char*) ptr, CERT_SEP);
325 b64_data.size = b64_data.size - (ptr - b64_data.data);
328 if (cert.size == peercert->size) {
329 if (memcmp (cert.data, peercert->data, cert.size) == 0) {
331 gnutls_free (cert.data);
332 p_delete(&b64_data_data);
337 gnutls_free (cert.data);
338 } while (ptr != NULL);
341 p_delete(&b64_data_data);
345 static void tls_fingerprint (gnutls_digest_algorithm algo,
346 char *s, int l, const gnutls_datum * data)
354 if (gnutls_fingerprint(algo, data, md, (size_t *)&n) < 0) {
355 snprintf (s, l, _("[unable to calculate]"));
358 for (j = 0; j < (int) n; j++) {
361 snprintf (ch, 8, "%02X%s", md[j], (j % 2 ? " " : ""));
364 s[2 * n + n / 2 - 1] = '\0'; /* don't want trailing space */
368 static char *tls_make_date (time_t t, char *s, ssize_t len)
370 struct tm *l = gmtime (&t);
373 snprintf (s, len, "%s, %d %s %d %02d:%02d:%02d UTC",
374 Weekdays[l->tm_wday], l->tm_mday, Months[l->tm_mon],
375 l->tm_year + 1900, l->tm_hour, l->tm_min, l->tm_sec);
377 m_strcpy(s, len, _("[invalid date]"));
382 static int tls_check_stored_hostname (const gnutls_datum * cert,
383 const char *hostname)
387 char *linestr = NULL;
391 regmatch_t pmatch[3];
393 /* try checking against names stored in stored certs file */
394 if ((fp = fopen (SslCertFile, "r"))) {
397 "^#H ([a-zA-Z0-9_\\.-]+) ([0-9A-F]{4}( [0-9A-F]{4}){7})[ \t]*$",
398 REG_ICASE | REG_EXTENDED) != 0) {
404 tls_fingerprint (GNUTLS_DIG_MD5, buf, sizeof (buf), cert);
406 mutt_read_line (linestr, &linestrsize, fp, &linenum)) != NULL) {
407 if (linestr[0] == '#' && linestr[1] == 'H') {
408 if (regexec (&preg, linestr, 3, pmatch, 0) == 0) {
409 linestr[pmatch[1].rm_eo] = '\0';
410 linestr[pmatch[2].rm_eo] = '\0';
411 if (m_strcmp(linestr + pmatch[1].rm_so, hostname) == 0 &&
412 m_strcmp(linestr + pmatch[2].rm_so, buf) == 0) {
426 /* not found a matching name */
430 static int tls_check_certificate (CONNECTION * conn)
432 tlssockdata *data = conn->sockdata;
433 gnutls_session state = data->state;
434 char helpstr[STRING];
438 char dn_common_name[STRING];
439 char dn_email[STRING];
440 char dn_organization[STRING];
441 char dn_organizational_unit[STRING];
442 char dn_locality[STRING];
443 char dn_province[STRING];
444 char dn_country[STRING];
446 int done, row, i, ret;
449 const gnutls_datum *cert_list;
450 unsigned int cert_list_size = 0;
451 gnutls_certificate_status_t certstat;
453 gnutls_x509_crt cert;
454 gnutls_datum pemdata;
455 int certerr_expired = 0;
456 int certerr_notyetvalid = 0;
457 int certerr_hostname = 0;
458 int certerr_nottrusted = 0;
459 int certerr_revoked = 0;
460 int certerr_signernotca = 0;
462 if (gnutls_auth_get_type (state) != GNUTLS_CRD_CERTIFICATE) {
463 mutt_error (_("Unable to get certificate from peer"));
468 if (gnutls_certificate_verify_peers2(state, &certstat) < 0) {
469 mutt_error (_("Certificate verification error (%s)"),
470 gnutls_strerror(certstat));
475 /* We only support X.509 certificates (not OpenPGP) at the moment */
476 if (gnutls_certificate_type_get (state) != GNUTLS_CRT_X509) {
477 mutt_error (_("Certificate is not X.509"));
482 if (gnutls_x509_crt_init (&cert) < 0) {
483 mutt_error (_("Error initialising gnutls certificate data"));
488 cert_list = gnutls_certificate_get_peers (state, &cert_list_size);
490 mutt_error (_("Unable to get certificate from peer"));
495 /* FIXME: Currently only check first certificate in chain. */
496 if (gnutls_x509_crt_import (cert, &cert_list[0], GNUTLS_X509_FMT_DER) < 0) {
497 mutt_error (_("Error processing certificate data"));
502 if (gnutls_x509_crt_get_expiration_time (cert) < time (NULL)) {
506 if (gnutls_x509_crt_get_activation_time (cert) > time (NULL)) {
507 certerr_notyetvalid = 1;
510 if (!gnutls_x509_crt_check_hostname (cert, conn->account.host) &&
511 !tls_check_stored_hostname (&cert_list[0], conn->account.host)) {
512 certerr_hostname = 1;
515 /* see whether certificate is in our cache (certificates file) */
516 if (tls_compare_certificates (&cert_list[0])) {
517 if (certstat & GNUTLS_CERT_INVALID) {
518 /* doesn't matter - have decided is valid because server
519 certificate is in our trusted cache */
520 certstat ^= GNUTLS_CERT_INVALID;
523 if (certstat & GNUTLS_CERT_SIGNER_NOT_FOUND) {
524 /* doesn't matter that we haven't found the signer, since
525 certificate is in our trusted cache */
526 certstat ^= GNUTLS_CERT_SIGNER_NOT_FOUND;
529 if (certstat & GNUTLS_CERT_SIGNER_NOT_CA) {
530 /* Hmm. Not really sure how to handle this, but let's say
531 that we don't care if the CA certificate hasn't got the
532 correct X.509 basic constraints if server certificate is
534 certstat ^= GNUTLS_CERT_SIGNER_NOT_CA;
539 if (certstat & GNUTLS_CERT_REVOKED) {
541 certstat ^= GNUTLS_CERT_REVOKED;
544 if (certstat & GNUTLS_CERT_INVALID) {
545 certerr_nottrusted = 1;
546 certstat ^= GNUTLS_CERT_INVALID;
549 if (certstat & GNUTLS_CERT_SIGNER_NOT_FOUND) {
550 /* NB: already cleared if cert in cache */
551 certerr_nottrusted = 1;
552 certstat ^= GNUTLS_CERT_SIGNER_NOT_FOUND;
555 if (certstat & GNUTLS_CERT_SIGNER_NOT_CA) {
556 /* NB: already cleared if cert in cache */
557 certerr_signernotca = 1;
558 certstat ^= GNUTLS_CERT_SIGNER_NOT_CA;
561 /* OK if signed by (or is) a trusted certificate */
562 /* we've been zeroing the interesting bits in certstat -
563 don't return OK if there are any unhandled bits we don't
565 if (!(certerr_expired || certerr_notyetvalid ||
566 certerr_hostname || certerr_nottrusted) && certstat == 0) {
567 gnutls_x509_crt_deinit (cert);
572 /* interactive check from user */
573 menu = mutt_new_menu ();
575 menu->dialog = p_new(char*, menu->max);
576 for (i = 0; i < menu->max; i++)
577 menu->dialog[i] = p_new(char, STRING);
580 m_strcpy(menu->dialog[row], STRING,
581 _("This certificate belongs to:"));
584 buflen = sizeof (dn_common_name);
585 if (gnutls_x509_crt_get_dn_by_oid (cert, GNUTLS_OID_X520_COMMON_NAME, 0, 0,
586 dn_common_name, (size_t *)&buflen) != 0)
587 dn_common_name[0] = '\0';
588 buflen = sizeof (dn_email);
589 if (gnutls_x509_crt_get_dn_by_oid (cert, GNUTLS_OID_PKCS9_EMAIL, 0, 0,
590 dn_email, (size_t *)&buflen) != 0)
592 buflen = sizeof (dn_organization);
593 if (gnutls_x509_crt_get_dn_by_oid
594 (cert, GNUTLS_OID_X520_ORGANIZATION_NAME, 0, 0, dn_organization,
595 (size_t *)&buflen) != 0)
596 dn_organization[0] = '\0';
597 buflen = sizeof (dn_organizational_unit);
598 if (gnutls_x509_crt_get_dn_by_oid
599 (cert, GNUTLS_OID_X520_ORGANIZATIONAL_UNIT_NAME, 0, 0,
600 dn_organizational_unit, (size_t *)&buflen) != 0)
601 dn_organizational_unit[0] = '\0';
602 buflen = sizeof (dn_locality);
603 if (gnutls_x509_crt_get_dn_by_oid
604 (cert, GNUTLS_OID_X520_LOCALITY_NAME, 0, 0, dn_locality, (size_t *)&buflen) != 0)
605 dn_locality[0] = '\0';
606 buflen = sizeof (dn_province);
607 if (gnutls_x509_crt_get_dn_by_oid
608 (cert, GNUTLS_OID_X520_STATE_OR_PROVINCE_NAME, 0, 0, dn_province,
609 (size_t *)&buflen) != 0)
610 dn_province[0] = '\0';
611 buflen = sizeof (dn_country);
612 if (gnutls_x509_crt_get_dn_by_oid (cert, GNUTLS_OID_X520_COUNTRY_NAME, 0, 0,
613 dn_country, (size_t *)&buflen) != 0)
614 dn_country[0] = '\0';
616 snprintf (menu->dialog[row++], STRING, " %s %s", dn_common_name,
618 snprintf (menu->dialog[row++], STRING, " %s", dn_organization);
619 snprintf (menu->dialog[row++], STRING, " %s",
620 dn_organizational_unit);
621 snprintf (menu->dialog[row++], STRING, " %s %s %s", dn_locality,
622 dn_province, dn_country);
625 m_strcpy(menu->dialog[row], STRING,
626 _("This certificate was issued by:"));
629 buflen = sizeof (dn_common_name);
630 if (gnutls_x509_crt_get_issuer_dn_by_oid
631 (cert, GNUTLS_OID_X520_COMMON_NAME, 0, 0, dn_common_name, (size_t *)&buflen) != 0)
632 dn_common_name[0] = '\0';
633 buflen = sizeof (dn_email);
634 if (gnutls_x509_crt_get_issuer_dn_by_oid
635 (cert, GNUTLS_OID_PKCS9_EMAIL, 0, 0, dn_email, (size_t *)&buflen) != 0)
637 buflen = sizeof (dn_organization);
638 if (gnutls_x509_crt_get_issuer_dn_by_oid
639 (cert, GNUTLS_OID_X520_ORGANIZATION_NAME, 0, 0, dn_organization,
640 (size_t *)&buflen) != 0)
641 dn_organization[0] = '\0';
642 buflen = sizeof (dn_organizational_unit);
643 if (gnutls_x509_crt_get_issuer_dn_by_oid
644 (cert, GNUTLS_OID_X520_ORGANIZATIONAL_UNIT_NAME, 0, 0,
645 dn_organizational_unit, (size_t *)&buflen) != 0)
646 dn_organizational_unit[0] = '\0';
647 buflen = sizeof (dn_locality);
648 if (gnutls_x509_crt_get_issuer_dn_by_oid
649 (cert, GNUTLS_OID_X520_LOCALITY_NAME, 0, 0, dn_locality, (size_t *)&buflen) != 0)
650 dn_locality[0] = '\0';
651 buflen = sizeof (dn_province);
652 if (gnutls_x509_crt_get_issuer_dn_by_oid
653 (cert, GNUTLS_OID_X520_STATE_OR_PROVINCE_NAME, 0, 0, dn_province,
654 (size_t *)&buflen) != 0)
655 dn_province[0] = '\0';
656 buflen = sizeof (dn_country);
657 if (gnutls_x509_crt_get_issuer_dn_by_oid
658 (cert, GNUTLS_OID_X520_COUNTRY_NAME, 0, 0, dn_country, (size_t *)&buflen) != 0)
659 dn_country[0] = '\0';
661 snprintf (menu->dialog[row++], STRING, " %s %s", dn_common_name,
663 snprintf (menu->dialog[row++], STRING, " %s", dn_organization);
664 snprintf (menu->dialog[row++], STRING, " %s",
665 dn_organizational_unit);
666 snprintf (menu->dialog[row++], STRING, " %s %s %s", dn_locality,
667 dn_province, dn_country);
670 snprintf (menu->dialog[row++], STRING,
671 _("This certificate is valid"));
673 t = gnutls_x509_crt_get_activation_time (cert);
674 snprintf (menu->dialog[row++], STRING, _(" from %s"),
675 tls_make_date (t, datestr, 30));
677 t = gnutls_x509_crt_get_expiration_time (cert);
678 snprintf (menu->dialog[row++], STRING, _(" to %s"),
679 tls_make_date (t, datestr, 30));
682 tls_fingerprint (GNUTLS_DIG_SHA, fpbuf, sizeof (fpbuf), &cert_list[0]);
683 snprintf (menu->dialog[row++], STRING, _("SHA1 Fingerprint: %s"),
686 tls_fingerprint (GNUTLS_DIG_MD5, fpbuf, sizeof (fpbuf), &cert_list[0]);
687 snprintf (menu->dialog[row++], STRING, _("MD5 Fingerprint: %s"),
690 if (certerr_notyetvalid) {
692 m_strcpy(menu->dialog[row], STRING,
693 _("WARNING: Server certificate is not yet valid"));
695 if (certerr_expired) {
697 m_strcpy(menu->dialog[row], STRING,
698 _("WARNING: Server certificate has expired"));
700 if (certerr_revoked) {
702 m_strcpy(menu->dialog[row], STRING,
703 _("WARNING: Server certificate has been revoked"));
705 if (certerr_hostname) {
707 m_strcpy(menu->dialog[row], STRING,
708 _("WARNING: Server hostname does not match certificate"));
710 if (certerr_signernotca) {
712 m_strcpy(menu->dialog[row], STRING,
713 _("WARNING: Signer of server certificate is not a CA"));
716 menu->title = _("TLS/SSL Certificate check");
717 /* certificates with bad dates, or that are revoked, must be
718 accepted manually each and every time */
719 if (SslCertFile && !certerr_expired && !certerr_notyetvalid
720 && !certerr_revoked) {
721 menu->prompt = _("(r)eject, accept (o)nce, (a)ccept always");
722 menu->keys = _("roa");
725 menu->prompt = _("(r)eject, accept (o)nce");
726 menu->keys = _("ro");
730 mutt_make_help (buf, sizeof (buf), _("Exit "), MENU_GENERIC, OP_EXIT);
731 strncat (helpstr, buf, sizeof (helpstr));
732 mutt_make_help (buf, sizeof (buf), _("Help"), MENU_GENERIC, OP_HELP);
733 strncat (helpstr, buf, sizeof (helpstr));
734 menu->help = helpstr;
737 set_option (OPTUNBUFFEREDINPUT);
739 switch (mutt_menuLoop (menu)) {
741 case OP_MAX + 1: /* reject */
745 case OP_MAX + 3: /* accept always */
747 if ((fp = fopen (SslCertFile, "a"))) {
748 /* save hostname if necessary */
749 if (certerr_hostname) {
750 fprintf (fp, "#H %s %s\n", conn->account.host, fpbuf);
753 if (certerr_nottrusted) {
755 ret = gnutls_pem_base64_encode_alloc ("CERTIFICATE", &cert_list[0],
758 if (fwrite (pemdata.data, pemdata.size, 1, fp) == 1) {
761 gnutls_free (pemdata.data);
767 mutt_error (_("Warning: Couldn't save certificate"));
771 mutt_message (_("Certificate saved"));
775 case OP_MAX + 2: /* accept once */
780 unset_option (OPTUNBUFFEREDINPUT);
781 mutt_menuDestroy (&menu);
782 gnutls_x509_crt_deinit (cert);
786 #endif /* USE_GNUTLS */