aboutsummaryrefslogtreecommitdiff
path: root/src/IO/tls_mbedtls.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/IO/tls_mbedtls.c')
-rw-r--r--src/IO/tls_mbedtls.c1217
1 files changed, 1217 insertions, 0 deletions
diff --git a/src/IO/tls_mbedtls.c b/src/IO/tls_mbedtls.c
new file mode 100644
index 00000000..c6b04b8f
--- /dev/null
+++ b/src/IO/tls_mbedtls.c
@@ -0,0 +1,1217 @@
+/*
+ * File: tls.c
+ *
+ * Copyright (C) 2011 Benjamin Johnson <obeythepenguin@users.sourceforge.net>
+ * (for the https code offered from dplus browser that formed the basis...)
+ * Copyright 2016 corvid
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ */
+
+/*
+ * https://www.ssllabs.com/ssltest/viewMyClient.html
+ * https://badssl.com
+ *
+ * Using TLS in Applications: https://datatracker.ietf.org/wg/uta/documents/
+ * TLS: https://datatracker.ietf.org/wg/tls/documents/
+ */
+
+#include "config.h"
+#include "../msg.h"
+
+#ifndef ENABLE_SSL
+
+void a_Tls_init()
+{
+ MSG("TLS: Disabled at compilation time.\n");
+}
+
+#else
+
+#include <assert.h>
+#include <errno.h>
+
+#include "../../dlib/dlib.h"
+#include "../dialog.hh"
+#include "../klist.h"
+#include "iowatch.hh"
+#include "tls.h"
+#include "Url.h"
+
+#include <mbedtls/platform.h> /* WORKAROUND: mbed TLS 2.3.0 ssl.h needs it */
+#include <mbedtls/ssl.h>
+#include <mbedtls/ctr_drbg.h> /* random number generator */
+#include <mbedtls/entropy.h>
+#include <mbedtls/error.h>
+#include <mbedtls/oid.h>
+#include <mbedtls/x509.h>
+#include <mbedtls/net.h> /* net_send, net_recv */
+
+#define CERT_STATUS_NONE 0
+#define CERT_STATUS_RECEIVING 1
+#define CERT_STATUS_CLEAN 2
+#define CERT_STATUS_BAD 3
+#define CERT_STATUS_USER_ACCEPTED 4
+
+typedef struct {
+ char *hostname;
+ int port;
+ int cert_status;
+} Server_t;
+
+typedef struct {
+ char *name;
+ Dlist *servers;
+} CertAuth_t;
+
+typedef struct {
+ int fd;
+ int connkey;
+} FdMapEntry_t;
+
+/*
+ * Data type for TLS connection information
+ */
+typedef struct {
+ int fd;
+ DilloUrl *url;
+ mbedtls_ssl_context *ssl;
+ bool_t connecting;
+} Conn_t;
+
+/* List of active TLS connections */
+static Klist_t *conn_list = NULL;
+
+static bool_t ssl_enabled = TRUE;
+static mbedtls_ssl_config ssl_conf;
+static mbedtls_x509_crt cacerts;
+static mbedtls_ctr_drbg_context ctr_drbg;
+static mbedtls_entropy_context entropy;
+
+static Dlist *servers;
+static Dlist *cert_authorities;
+static Dlist *fd_map;
+
+static void Tls_handshake_cb(int fd, void *vconnkey);
+
+/*
+ * Compare by FD.
+ */
+static int Tls_fd_map_cmp(const void *v1, const void *v2)
+{
+ int fd = VOIDP2INT(v2);
+ const FdMapEntry_t *e = v1;
+
+ return (fd != e->fd);
+}
+
+static void Tls_fd_map_add_entry(int fd, int connkey)
+{
+ FdMapEntry_t *e = dNew0(FdMapEntry_t, 1);
+ e->fd = fd;
+ e->connkey = connkey;
+
+ if (dList_find_custom(fd_map, INT2VOIDP(e->fd), Tls_fd_map_cmp)) {
+ MSG_ERR("TLS FD ENTRY ALREADY FOUND FOR %d\n", e->fd);
+ assert(0);
+ }
+
+ dList_append(fd_map, e);
+//MSG("ADD ENTRY %d %s\n", e->fd, URL_STR(sd->url));
+}
+
+/*
+ * Remove and free entry from fd_map.
+ */
+static void Tls_fd_map_remove_entry(int fd)
+{
+ void *data = dList_find_custom(fd_map, INT2VOIDP(fd), Tls_fd_map_cmp);
+
+//MSG("REMOVE ENTRY %d\n", fd);
+ if (data) {
+ dList_remove_fast(fd_map, data);
+ dFree(data);
+ } else {
+ MSG("TLS FD ENTRY NOT FOUND FOR %d\n", fd);
+ }
+}
+
+/*
+ * Return TLS connection information for a given file
+ * descriptor, or NULL if no TLS connection was found.
+ */
+void *a_Tls_connection(int fd)
+{
+ Conn_t *conn;
+
+ if (fd_map) {
+ FdMapEntry_t *fme = dList_find_custom(fd_map, INT2VOIDP(fd),
+ Tls_fd_map_cmp);
+
+ if (fme && (conn = a_Klist_get_data(conn_list, fme->connkey)))
+ return conn;
+ }
+ return NULL;
+}
+
+/*
+ * Add a new TLS connection information node.
+ */
+static Conn_t *Tls_conn_new(int fd, const DilloUrl *url,
+ mbedtls_ssl_context *ssl)
+{
+ Conn_t *conn = dNew0(Conn_t, 1);
+ conn->fd = fd;
+ conn->url = a_Url_dup(url);
+ conn->ssl = ssl;
+ conn->connecting = TRUE;
+ return conn;
+}
+
+static int Tls_make_conn_key(Conn_t *conn)
+{
+ int key = a_Klist_insert(&conn_list, conn);
+
+ Tls_fd_map_add_entry(conn->fd, key);
+
+ return key;
+}
+
+/*
+ * Load certificates from a given filename.
+ */
+static void Tls_load_certificates_from_file(const char *const filename)
+{
+ int ret = mbedtls_x509_crt_parse_file(&cacerts, filename);
+
+ if (ret < 0) {
+ if (ret == MBEDTLS_ERR_PK_FILE_IO_ERROR) {
+ /* can't read from file */
+ } else {
+ MSG("Failed to parse certificates from %s (returned -0x%04x)\n",
+ filename, -ret);
+ }
+ }
+}
+
+/*
+ * Load certificates from a given pathname.
+ */
+static void Tls_load_certificates_from_path(const char *const pathname)
+{
+ int ret = mbedtls_x509_crt_parse_path(&cacerts, pathname);
+
+ if (ret < 0) {
+ if (ret == MBEDTLS_ERR_X509_FILE_IO_ERROR) {
+ /* can't read from path */
+ } else {
+ MSG("Failed to parse certificates from %s (returned -0x%04x)\n",
+ pathname, -ret);
+ }
+ }
+}
+
+/*
+ * Remove duplicate certificates.
+ */
+static void Tls_remove_duplicate_certificates()
+{
+ mbedtls_x509_crt *cp, *curr = &cacerts;
+
+ while (curr) {
+ cp = curr;
+ while (cp->next) {
+ if (curr->serial.len == cp->next->serial.len &&
+ !memcmp(curr->serial.p, cp->next->serial.p, curr->serial.len) &&
+ curr->subject_raw.len == cp->next->subject_raw.len &&
+ !memcmp(curr->subject_raw.p, cp->next->subject_raw.p,
+ curr->subject_raw.len)) {
+ mbedtls_x509_crt *duplicate = cp->next;
+
+ cp->next = duplicate->next;
+
+ /* clearing the next field prevents it from freeing the whole
+ * chain of certificates
+ */
+ duplicate->next = NULL;
+ mbedtls_x509_crt_free(duplicate);
+ dFree(duplicate);
+ } else {
+ cp = cp->next;
+ }
+ }
+ curr = curr->next;
+ }
+}
+
+/*
+ * Load trusted certificates.
+ */
+static void Tls_load_certificates()
+{
+ /* curl-7.37.1 says that the following bundle locations are used on "Debian
+ * systems", "Redhat and Mandriva", "old(er) Redhat", "FreeBSD", and
+ * "OpenBSD", respectively -- and that the /etc/ssl/certs/ path is needed on
+ * "SUSE". No doubt it's all changed some over time, but this gives us
+ * something to work with.
+ */
+ uint_t u;
+ char *userpath;
+ mbedtls_x509_crt *curr;
+
+ static const char *const ca_files[] = {
+ "/etc/ssl/certs/ca-certificates.crt",
+ "/etc/pki/tls/certs/ca-bundle.crt",
+ "/usr/share/ssl/certs/ca-bundle.crt",
+ "/usr/local/share/certs/ca-root.crt",
+ "/etc/ssl/cert.pem",
+ CA_CERTS_FILE
+ };
+
+ static const char *const ca_paths[] = {
+ "/etc/ssl/certs/",
+ CA_CERTS_DIR
+ };
+
+ for (u = 0; u < sizeof(ca_files)/sizeof(ca_files[0]); u++) {
+ if (*ca_files[u])
+ Tls_load_certificates_from_file(ca_files[u]);
+ }
+
+ for (u = 0; u < sizeof(ca_paths)/sizeof(ca_paths[0]); u++) {
+ if (*ca_paths[u]) {
+ Tls_load_certificates_from_path(ca_paths[u]);
+ }
+ }
+
+ userpath = dStrconcat(dGethomedir(), "/.dillo/certs/", NULL);
+ Tls_load_certificates_from_path(userpath);
+ dFree(userpath);
+
+ Tls_remove_duplicate_certificates();
+
+ /* Count our trusted certificates */
+ u = 0;
+ if (cacerts.next) {
+ u++;
+ for (curr = cacerts.next; curr; curr = curr->next)
+ u++;
+ } else {
+ mbedtls_x509_crt empty;
+ mbedtls_x509_crt_init(&empty);
+
+ if (memcmp(&cacerts, &empty, sizeof(mbedtls_x509_crt)))
+ u++;
+ }
+
+ MSG("Trusting %u TLS certificate%s.\n", u, u==1 ? "" : "s");
+}
+
+/*
+ * Remove the pre-shared key ciphersuites. There are lots of them,
+ * and we aren't making any use of them.
+ */
+static void Tls_remove_psk_ciphersuites()
+{
+ const mbedtls_ssl_ciphersuite_t *cs_info;
+ int *our_ciphers, *q;
+ int n = 0;
+
+ const int *default_ciphers = mbedtls_ssl_list_ciphersuites(),
+ *p = default_ciphers;
+
+ /* count how many we will want */
+ while (*p) {
+ cs_info = mbedtls_ssl_ciphersuite_from_id(*p);
+ if (!mbedtls_ssl_ciphersuite_uses_psk(cs_info))
+ n++;
+ p++;
+ }
+ n++;
+ our_ciphers = dNew(int, n);
+
+ /* iterate through again and copy them over */
+ p = default_ciphers;
+ q = our_ciphers;
+ while (*p) {
+ cs_info = mbedtls_ssl_ciphersuite_from_id(*p);
+
+ if (!mbedtls_ssl_ciphersuite_uses_psk(cs_info))
+ *q++ = *p;
+ p++;
+ }
+ *q = 0;
+
+ mbedtls_ssl_conf_ciphersuites(&ssl_conf, our_ciphers);
+}
+
+/*
+ * Initialize the mbed TLS library.
+ */
+void a_Tls_init(void)
+{
+ int ret;
+
+ /* As of 2.3.0 in 2016, the 'default' profile allows SHA1, RIPEMD160,
+ * and SHA224 (in addition to the stronger ones), and the 'next' profile
+ * doesn't allow anything below SHA256. Since we're never going to hear
+ * when/if RIPEMD160 and SHA224 are deprecated, and they're obscure enough
+ * not to encounter, let's not allow those.
+ * These profiles are for certificates, and mbed tls points out that these
+ * have nothing to do with hashes during handshakes.
+ * Their 'next' profile only allows "Curves at or above 128-bit security
+ * level". For now, we follow 'default' and allow all curves.
+ */
+ static const mbedtls_x509_crt_profile prof = {
+ MBEDTLS_X509_ID_FLAG( MBEDTLS_MD_SHA1 ) |
+ MBEDTLS_X509_ID_FLAG( MBEDTLS_MD_SHA256 ) |
+ MBEDTLS_X509_ID_FLAG( MBEDTLS_MD_SHA384 ) |
+ MBEDTLS_X509_ID_FLAG( MBEDTLS_MD_SHA512 ),
+ 0xFFFFFFF, /* Any PK alg */
+ 0xFFFFFFF, /* Any curve */
+ 2048,
+ };
+
+ mbedtls_ssl_config_init(&ssl_conf);
+
+ mbedtls_ssl_config_defaults(&ssl_conf, MBEDTLS_SSL_IS_CLIENT,
+ MBEDTLS_SSL_TRANSPORT_STREAM,
+ MBEDTLS_SSL_PRESET_DEFAULT);
+ mbedtls_ssl_conf_cert_profile(&ssl_conf, &prof);
+
+ /*
+ * There are security concerns surrounding session tickets --
+ * wrecking forward security, for instance.
+ */
+ mbedtls_ssl_conf_session_tickets(&ssl_conf,
+ MBEDTLS_SSL_SESSION_TICKETS_DISABLED);
+
+ Tls_remove_psk_ciphersuites();
+
+ mbedtls_x509_crt_init(&cacerts); /* trusted root certificates */
+ mbedtls_ctr_drbg_init(&ctr_drbg); /* Counter mode Deterministic Random Byte
+ * Generator */
+ mbedtls_entropy_init(&entropy);
+
+ if((ret = mbedtls_ctr_drbg_seed(&ctr_drbg, mbedtls_entropy_func, &entropy,
+ (unsigned char*)"dillo tls", 9))) {
+ ssl_enabled = FALSE;
+ MSG_ERR("tls: mbedtls_ctr_drbg_seed() failed. TLS disabled.\n");
+ return;
+ }
+
+ mbedtls_ssl_conf_authmode(&ssl_conf, MBEDTLS_SSL_VERIFY_OPTIONAL);
+ mbedtls_ssl_conf_ca_chain(&ssl_conf, &cacerts, NULL);
+ mbedtls_ssl_conf_rng(&ssl_conf, mbedtls_ctr_drbg_random, &ctr_drbg);
+
+ fd_map = dList_new(20);
+ servers = dList_new(8);
+ cert_authorities = dList_new(12);
+
+ Tls_load_certificates();
+}
+
+/*
+ * Ordered comparison of servers.
+ */
+static int Tls_servers_cmp(const void *v1, const void *v2)
+{
+ const Server_t *s1 = (const Server_t *)v1, *s2 = (const Server_t *)v2;
+ int cmp = dStrAsciiCasecmp(s1->hostname, s2->hostname);
+
+ if (!cmp)
+ cmp = s1->port - s2->port;
+ return cmp;
+}
+/*
+ * Ordered comparison of server with URL.
+ */
+static int Tls_servers_by_url_cmp(const void *v1, const void *v2)
+{
+ const Server_t *s = (const Server_t *)v1;
+ const DilloUrl *url = (const DilloUrl *)v2;
+
+ int cmp = dStrAsciiCasecmp(s->hostname, URL_HOST(url));
+
+ if (!cmp)
+ cmp = s->port - URL_PORT(url);
+ return cmp;
+}
+
+/*
+ * The purpose here is to permit a single initial connection to a server.
+ * Once we have the certificate, know whether we like it -- and whether the
+ * user accepts it -- HTTP can run through queued sockets as normal.
+ *
+ * Return: TLS_CONNECT_READY or TLS_CONNECT_NOT_YET or TLS_CONNECT_NEVER.
+ */
+int a_Tls_connect_ready(const DilloUrl *url)
+{
+ Server_t *s;
+ int ret = TLS_CONNECT_READY;
+
+ dReturn_val_if_fail(ssl_enabled, TLS_CONNECT_NEVER);
+
+ if ((s = dList_find_sorted(servers, url, Tls_servers_by_url_cmp))) {
+ if (s->cert_status == CERT_STATUS_RECEIVING)
+ ret = TLS_CONNECT_NOT_YET;
+ else if (s->cert_status == CERT_STATUS_BAD)
+ ret = TLS_CONNECT_NEVER;
+
+ if (s->cert_status == CERT_STATUS_NONE)
+ s->cert_status = CERT_STATUS_RECEIVING;
+ } else {
+ s = dNew(Server_t, 1);
+
+ s->hostname = dStrdup(URL_HOST(url));
+ s->port = URL_PORT(url);
+ s->cert_status = CERT_STATUS_RECEIVING;
+ dList_insert_sorted(servers, s, Tls_servers_cmp);
+ }
+ return ret;
+}
+
+static int Tls_cert_status(const DilloUrl *url)
+{
+ Server_t *s = dList_find_sorted(servers, url, Tls_servers_by_url_cmp);
+
+ return s ? s->cert_status : CERT_STATUS_NONE;
+}
+
+/*
+ * Did we find problems with the certificate, and did the user proceed to
+ * reject the connection?
+ */
+static int Tls_user_said_no(const DilloUrl *url)
+{
+ return Tls_cert_status(url) == CERT_STATUS_BAD;
+}
+
+/*
+ * Did everything seem proper with the certificate -- no warnings to
+ * click through?
+ */
+int a_Tls_certificate_is_clean(const DilloUrl *url)
+{
+ return Tls_cert_status(url) == CERT_STATUS_CLEAN;
+}
+
+#if 0
+/*
+ * Print certificate and its chain of issuer certificates.
+ */
+static void Tls_print_cert_chain(const mbedtls_x509_crt *cert)
+{
+ /* print for first connection to server */
+ const mbedtls_x509_crt *last_cert;
+ const uint_t buflen = 2048;
+ char buf[buflen];
+ int key_bits;
+ const char *sigalg;
+
+ while (cert) {
+ if (cert->sig_md == MBEDTLS_MD_SHA1) {
+ MSG_WARN("In 2015, browsers have begun to deprecate SHA1 "
+ "certificates.\n");
+ }
+
+ if (mbedtls_oid_get_sig_alg_desc(&cert->sig_oid, &sigalg))
+ sigalg = "(??" ")";
+
+ key_bits = mbedtls_pk_get_bitlen(&cert->pk);
+ mbedtls_x509_dn_gets(buf, buflen, &cert->subject);
+ MSG("%d-bit %s: %s\n", key_bits, sigalg, buf);
+
+ last_cert = cert;
+ cert = cert->next;
+ }
+ if (last_cert) {
+ mbedtls_x509_dn_gets(buf, buflen, &last_cert->issuer);
+ MSG("root: %s\n", buf);
+ }
+}
+#endif
+
+/*
+ * Generate dialog msg for expired cert.
+ */
+static void Tls_cert_expired(const mbedtls_x509_crt *cert, Dstr *ds)
+{
+ const mbedtls_x509_time *date = &cert->valid_to;
+
+ dStr_sprintfa(ds,"Certificate expired at: %04d/%02d/%02d %02d:%02d:%02d.\n",
+ date->year, date->mon, date->day, date->hour, date->min,
+ date->sec);
+}
+
+/*
+ * Generate dialog msg when certificate is not for this host.
+ */
+static void Tls_cert_cn_mismatch(const mbedtls_x509_crt *cert, Dstr *ds)
+{
+ const uint_t buflen = 2048;
+ char cert_info_buf[buflen];
+ char *san, *s;
+
+ dStr_append(ds, "This host is not one of the hostnames listed on the TLS "
+ "certificate that it sent");
+ /*
+ *
+ * Taking the human-readable certificate info and scraping it is brittle
+ * and horrible, but the alternative is to mimic
+ * x509_info_subject_alt_name(), an option that seems equally brittle and
+ * horrible.
+ *
+ * Once I find a case where SAN isn't used, I can add code to work with
+ * the subject field as well.
+ *
+ */
+ mbedtls_x509_crt_info(cert_info_buf, buflen, "", cert);
+
+ if ((san = strstr(cert_info_buf, "subject alt name : "))) {
+ san += 20;
+ s = strchr(san, '\n');
+ if (s) {
+ *s = '\0';
+ dStr_sprintfa(ds, " (%s)", san);
+ }
+ }
+ dStr_append(ds, ".\n");
+}
+
+/*
+ * Generate dialog msg when certificate is not trusted.
+ */
+static void Tls_cert_trust_chain_failed(const mbedtls_x509_crt *cert, Dstr *ds)
+{
+ const uint_t buflen = 2048;
+ char buf[buflen];
+
+ while (cert->next)
+ cert = cert->next;
+ mbedtls_x509_dn_gets(buf, buflen, &cert->issuer);
+
+ dStr_sprintfa(ds, "Couldn't reach any trusted root certificate from "
+ "supplied certificate. The issuer at the end of the "
+ "chain was: \"%s\"\n", buf);
+}
+
+/*
+ * Generate dialog msg when certificate start date is in the future.
+ */
+static void Tls_cert_not_valid_yet(const mbedtls_x509_crt *cert, Dstr *ds)
+{
+ const mbedtls_x509_time *date = &cert->valid_to;
+
+ dStr_sprintfa(ds, "Certificate validity begins in the future at: "
+ "%04d/%02d/%02d %02d:%02d:%02d.\n",
+ date->year, date->mon, date->day, date->hour, date->min,
+ date->sec);
+}
+
+/*
+ * Generate dialog msg when certificate hash algorithm is not accepted.
+ */
+static void Tls_cert_bad_hash(const mbedtls_x509_crt *cert, Dstr *ds)
+{
+ const char *hash = (cert->sig_md == MBEDTLS_MD_MD5) ? "MD5" :
+ (cert->sig_md == MBEDTLS_MD_MD4) ? "MD4" :
+ (cert->sig_md == MBEDTLS_MD_MD2) ? "MD2" :
+ (cert->sig_md == MBEDTLS_MD_SHA1) ? "SHA1" :
+ (cert->sig_md == MBEDTLS_MD_SHA224) ? "SHA224" :
+ (cert->sig_md == MBEDTLS_MD_RIPEMD160) ? "RIPEMD160" :
+ (cert->sig_md == MBEDTLS_MD_SHA256) ? "SHA256" :
+ (cert->sig_md == MBEDTLS_MD_SHA384) ? "SHA384" :
+ (cert->sig_md == MBEDTLS_MD_SHA512) ? "SHA512" :
+ "Unrecognized";
+
+ dStr_sprintfa(ds, "This certificate's hash algorithm is not accepted "
+ "(%s).\n", hash);
+}
+
+/*
+ * Generate dialog msg when public key algorithm (RSA, ECDSA) is not accepted.
+ */
+static void Tls_cert_bad_pk_alg(const mbedtls_x509_crt *cert, Dstr *ds)
+{
+ const char *type_str = mbedtls_pk_get_name(&cert->pk);
+
+ dStr_sprintfa(ds, "This certificate's public key algorithm is not accepted "
+ "(%s).\n", type_str);
+}
+
+/*
+ * Generate dialog msg when the public key is not acceptable. As of 2016,
+ * this was triggered by RSA keys below 2048 bits, if I recall correctly.
+ */
+static void Tls_cert_bad_key(const mbedtls_x509_crt *cert, Dstr *ds) {
+ int key_bits = mbedtls_pk_get_bitlen(&cert->pk);
+ const char *type_str = mbedtls_pk_get_name(&cert->pk);
+
+ dStr_sprintfa(ds, "This certificate's key is not accepted, which generally "
+ "means it's too weak (%d-bit %s).\n", key_bits, type_str);
+}
+
+/*
+ * Make a dialog msg containing warnings about problems with the certificate.
+ */
+static char *Tls_make_bad_cert_msg(const mbedtls_x509_crt *cert,uint32_t flags)
+{
+ static const struct certerr {
+ int val;
+ void (*cert_err_fn)(const mbedtls_x509_crt *cert, Dstr *ds);
+ } cert_error [] = {
+ { MBEDTLS_X509_BADCERT_EXPIRED, Tls_cert_expired},
+ { MBEDTLS_X509_BADCERT_CN_MISMATCH, Tls_cert_cn_mismatch},
+ { MBEDTLS_X509_BADCERT_NOT_TRUSTED, Tls_cert_trust_chain_failed},
+ { MBEDTLS_X509_BADCERT_FUTURE, Tls_cert_not_valid_yet},
+ { MBEDTLS_X509_BADCERT_BAD_MD, Tls_cert_bad_hash},
+ { MBEDTLS_X509_BADCERT_BAD_PK, Tls_cert_bad_pk_alg},
+ { MBEDTLS_X509_BADCERT_BAD_KEY, Tls_cert_bad_key}
+ };
+ const uint_t ncert_errors = sizeof(cert_error) /sizeof(cert_error[0]);
+ char *ret;
+ Dstr *ds = dStr_new(NULL);
+ uint_t u;
+
+ for (u = 0; u < ncert_errors; u++) {
+ if (flags & cert_error[u].val) {
+ flags &= ~cert_error[u].val;
+ cert_error[u].cert_err_fn(cert, ds);
+ }
+ }
+ if (flags)
+ dStr_sprintfa(ds, "Unknown certificate error(s): flag value 0x%04x",
+ flags);
+ ret = ds->str;
+ dStr_free(ds, 0);
+ return ret;
+}
+
+static int Tls_cert_auth_cmp(const void *v1, const void *v2)
+{
+ const CertAuth_t *c1 = (CertAuth_t *)v1, *c2 = (CertAuth_t *)v2;
+
+ return strcmp(c1->name, c2->name);
+}
+
+static int Tls_cert_auth_cmp_by_name(const void *v1, const void *v2)
+{
+ const CertAuth_t *c = (CertAuth_t *)v1;
+ const char *name = (char *)v2;
+
+ return strcmp(c->name, name);
+}
+
+/*
+ * Keep account of on whose authority we are trusting servers.
+ */
+static void Tls_update_cert_authorities_data(const mbedtls_x509_crt *cert,
+ Server_t *srv)
+{
+ const uint_t buflen = 512;
+ char buf[buflen];
+ const mbedtls_x509_crt *last = cert;
+
+ while (last->next)
+ last = last->next;
+
+ mbedtls_x509_dn_gets(buf, buflen, &last->issuer);
+
+ CertAuth_t *ca = dList_find_custom(cert_authorities, buf,
+ Tls_cert_auth_cmp_by_name);
+ if (!ca) {
+ ca = dNew(CertAuth_t, 1);
+ ca->name = dStrdup(buf);
+ ca->servers = dList_new(8);
+ dList_insert_sorted(cert_authorities, ca, Tls_cert_auth_cmp);
+ }
+ dList_append(ca->servers, srv);
+}
+
+/*
+ * Examine the certificate, and, if problems are detected, ask the user what
+ * to do.
+ * Return: -1 if connection should be canceled, or 0 if it should continue.
+ */
+static int Tls_examine_certificate(mbedtls_ssl_context *ssl, Server_t *srv)
+{
+ const mbedtls_x509_crt *cert;
+ uint32_t st;
+ int choice = -1, ret = -1;
+ char *title = dStrconcat("Dillo TLS security warning: ",srv->hostname,NULL);
+
+ cert = mbedtls_ssl_get_peer_cert(ssl);
+ if (cert == NULL){
+ /* Inform user that remote system cannot be trusted */
+ choice = a_Dialog_choice(title,
+ "No certificate received from this site. Can't verify who it is.",
+ "Continue", "Cancel", NULL);
+
+ /* Abort on anything but "Continue" */
+ if (choice == 1){
+ ret = 0;
+ }
+ } else {
+ /* check the certificate */
+ st = mbedtls_ssl_get_verify_result(ssl);
+ if (st == 0) {
+ if (srv->cert_status == CERT_STATUS_RECEIVING) {
+ /* first connection to server */
+#if 0
+ Tls_print_cert_chain(cert);
+#endif
+ Tls_update_cert_authorities_data(cert, srv);
+ }
+ ret = 0;
+ } else if (st == 0xFFFFFFFF) {
+ /* "result is not available (eg because the handshake was aborted too
+ * early)" is what the documentation says. Maybe it's only what
+ * happens if you call get_verify_result() too early or when the
+ * handshake failed. But just in case...
+ */
+ MSG_ERR("mbedtls_ssl_get_verify_result: result is not available");
+ } else {
+ char *dialog_warning_msg = Tls_make_bad_cert_msg(cert, st);
+
+ choice = a_Dialog_choice(title, dialog_warning_msg, "Continue",
+ "Cancel", NULL);
+ if (choice == 1) {
+ ret = 0;
+ }
+ dFree(dialog_warning_msg);
+ }
+ }
+ dFree(title);
+
+ if (choice == -1) {
+ srv->cert_status = CERT_STATUS_CLEAN; /* no warning popups */
+ } else if (choice == 1) {
+ srv->cert_status = CERT_STATUS_USER_ACCEPTED; /* clicked Continue */
+ } else {
+ /* 2 for Cancel, or 0 when window closed. */
+ srv->cert_status = CERT_STATUS_BAD;
+ }
+ return ret;
+}
+
+/*
+ * If the connection was closed before we got the certificate, we need to
+ * reset state so that we'll try again.
+ */
+void a_Tls_reset_server_state(const DilloUrl *url)
+{
+ if (servers) {
+ Server_t *s = dList_find_sorted(servers, url, Tls_servers_by_url_cmp);
+
+ if (s && s->cert_status == CERT_STATUS_RECEIVING)
+ s->cert_status = CERT_STATUS_NONE;
+ }
+}
+
+/*
+ * Close an open TLS connection.
+ */
+static void Tls_close_by_key(int connkey)
+{
+ Conn_t *c;
+
+ if ((c = a_Klist_get_data(conn_list, connkey))) {
+ a_Tls_reset_server_state(c->url);
+ if (c->connecting) {
+ a_IOwatch_remove_fd(c->fd, -1);
+ dClose(c->fd);
+ }
+ mbedtls_ssl_close_notify(c->ssl);
+ mbedtls_ssl_free(c->ssl);
+ dFree(c->ssl);
+
+ a_Url_free(c->url);
+ Tls_fd_map_remove_entry(c->fd);
+ a_Klist_remove(conn_list, connkey);
+ dFree(c);
+ }
+}
+
+/*
+ * Print a message about the fatal alert.
+ *
+ * The values have gaps, and a few are never fatal error values, and some may
+ * never be sent to clients, but let's go ahead and translate every value that
+ * we recognize.
+ */
+static void Tls_fatal_error_msg(int error_type)
+{
+ const char *errmsg;
+
+ if (error_type == MBEDTLS_SSL_ALERT_MSG_CLOSE_NOTIFY)
+ errmsg = "close notify";
+ else if (error_type == MBEDTLS_SSL_ALERT_MSG_UNEXPECTED_MESSAGE)
+ errmsg = "unexpected message received";
+ else if (error_type == MBEDTLS_SSL_ALERT_MSG_BAD_RECORD_MAC)
+ errmsg = "record received with incorrect MAC";
+ else if (error_type == MBEDTLS_SSL_ALERT_MSG_DECRYPTION_FAILED) {
+ /* last used in TLS 1.1 */
+ errmsg = "decryption failed";
+ } else if (error_type == MBEDTLS_SSL_ALERT_MSG_RECORD_OVERFLOW)
+ errmsg = "\"A TLSCiphertext record was received that had a length more "
+ "than 2^14+2048 bytes, or a record decrypted to a TLSCompressed"
+ " record with more than 2^14+1024 bytes.\"";
+ else if (error_type == MBEDTLS_SSL_ALERT_MSG_DECOMPRESSION_FAILURE)
+ errmsg = "\"decompression function received improper input\"";
+ else if (error_type == MBEDTLS_SSL_ALERT_MSG_HANDSHAKE_FAILURE)
+ errmsg = "\"sender was unable to negotiate an acceptable set of security"
+ " parameters given the options available\"";
+ else if (error_type == MBEDTLS_SSL_ALERT_MSG_NO_CERT)
+ errmsg = "no cert (an obsolete alert last used in SSL3)";
+ else if (error_type == MBEDTLS_SSL_ALERT_MSG_BAD_CERT)
+ errmsg = "bad certificate";
+ else if (error_type == MBEDTLS_SSL_ALERT_MSG_UNSUPPORTED_CERT)
+ errmsg = "certificate of unsupported type";
+ else if (error_type == MBEDTLS_SSL_ALERT_MSG_CERT_REVOKED)
+ errmsg = "certificate revoked by its signer";
+ else if (error_type == MBEDTLS_SSL_ALERT_MSG_CERT_EXPIRED)
+ errmsg = "certificate expired or not currently valid";
+ else if (error_type == MBEDTLS_SSL_ALERT_MSG_CERT_UNKNOWN)
+ errmsg = "certificate error of an unknown sort";
+ else if (error_type == MBEDTLS_SSL_ALERT_MSG_ILLEGAL_PARAMETER)
+ errmsg = "illegal parameter in handshake";
+ else if (error_type == MBEDTLS_SSL_ALERT_MSG_UNKNOWN_CA)
+ errmsg = "unknown CA";
+ else if (error_type == MBEDTLS_SSL_ALERT_MSG_ACCESS_DENIED)
+ errmsg = "access denied";
+ else if (error_type == MBEDTLS_SSL_ALERT_MSG_DECODE_ERROR)
+ errmsg = "decode error";
+ else if (error_type == MBEDTLS_SSL_ALERT_MSG_DECRYPT_ERROR)
+ errmsg = "decrypt error";
+ else if (error_type == MBEDTLS_SSL_ALERT_MSG_EXPORT_RESTRICTION) {
+ /* last used in TLS 1.0 */
+ errmsg = "export restriction";
+ } else if (error_type == MBEDTLS_SSL_ALERT_MSG_PROTOCOL_VERSION)
+ errmsg = "protocol version is recognized but not supported";
+ else if (error_type == MBEDTLS_SSL_ALERT_MSG_INSUFFICIENT_SECURITY)
+ errmsg = "server requires ciphers more secure than those supported by "
+ "the client";
+ else if (error_type == MBEDTLS_SSL_ALERT_MSG_INTERNAL_ERROR)
+ errmsg = "internal error (not the client's fault)";
+ else if (error_type == MBEDTLS_SSL_ALERT_MSG_INAPROPRIATE_FALLBACK)
+ errmsg = "inappropriate fallback";
+ else if (error_type == MBEDTLS_SSL_ALERT_MSG_USER_CANCELED)
+ errmsg = "\"handshake is being canceled for some reason unrelated to a "
+ "protocol failure\"";
+ else if (error_type == MBEDTLS_SSL_ALERT_MSG_NO_RENEGOTIATION)
+ errmsg = "no renegotiation";
+ else if (error_type == MBEDTLS_SSL_ALERT_MSG_UNSUPPORTED_EXT)
+ errmsg = "unsupported ext";
+ else if (error_type == MBEDTLS_SSL_ALERT_MSG_UNRECOGNIZED_NAME)
+ errmsg = "unrecognized name";
+ else if (error_type == MBEDTLS_SSL_ALERT_MSG_UNKNOWN_PSK_IDENTITY)
+ errmsg = "unknown psk identity";
+ else if (error_type == MBEDTLS_SSL_ALERT_MSG_NO_APPLICATION_PROTOCOL)
+ errmsg = "no application protocol";
+ else errmsg = "unknown alert value";
+
+ MSG_WARN("mbedtls_ssl_handshake() received TLS fatal alert %d (%s)\n",
+ error_type, errmsg);
+}
+
+/*
+ * Connect, set a callback if it's still not completed. If completed, check
+ * the certificate and report back to http.
+ */
+static void Tls_handshake(int fd, int connkey)
+{
+ int ret;
+ bool_t ongoing = FALSE, failed = TRUE;
+ Conn_t *conn;
+
+ if (!(conn = a_Klist_get_data(conn_list, connkey))) {
+ MSG("Tls_connect: conn for fd %d not valid\n", fd);
+ return;
+ }
+
+ if (conn->ssl->state != MBEDTLS_SSL_HANDSHAKE_OVER) {
+ ret = mbedtls_ssl_handshake(conn->ssl);
+
+ if (ret == MBEDTLS_ERR_SSL_WANT_READ ||
+ ret == MBEDTLS_ERR_SSL_WANT_WRITE) {
+ int want = ret == MBEDTLS_ERR_SSL_WANT_READ ? DIO_READ : DIO_WRITE;
+
+ _MSG("iowatching fd %d for tls -- want %s\n", fd,
+ ret == MBEDTLS_ERR_SSL_WANT_READ ? "read" : "write");
+ a_IOwatch_remove_fd(fd, -1);
+ a_IOwatch_add_fd(fd, want, Tls_handshake_cb, INT2VOIDP(connkey));
+ ongoing = TRUE;
+ failed = FALSE;
+ } else if (ret == 0) {
+ Server_t *srv = dList_find_sorted(servers, conn->url,
+ Tls_servers_by_url_cmp);
+
+ if (srv->cert_status == CERT_STATUS_RECEIVING) {
+ /* Making first connection with the server. Show cipher used. */
+ mbedtls_ssl_context *ssl = conn->ssl;
+ const char *version = mbedtls_ssl_get_version(ssl),
+ *cipher = mbedtls_ssl_get_ciphersuite(ssl);
+
+ MSG("%s", URL_AUTHORITY(conn->url));
+ if (URL_PORT(conn->url) != URL_HTTPS_PORT)
+ MSG(":%d", URL_PORT(conn->url));
+ MSG(" %s, cipher %s\n", version, cipher);
+ }
+ if (srv->cert_status == CERT_STATUS_USER_ACCEPTED ||
+ (Tls_examine_certificate(conn->ssl, srv) != -1)) {
+ failed = FALSE;
+ }
+ } else if (ret == MBEDTLS_ERR_NET_SEND_FAILED) {
+ MSG("mbedtls_ssl_handshake() send failed. Server may not be accepting"
+ " connections.\n");
+ } else if (ret == MBEDTLS_ERR_NET_CONNECT_FAILED) {
+ MSG("mbedtls_ssl_handshake() connect failed.\n");
+ } else if (ret == MBEDTLS_ERR_SSL_FATAL_ALERT_MESSAGE) {
+ /* Paul Bakker, the mbed tls guy, says "beware, this might change in
+ * future versions" and "ssl->in_msg[1] is not going to change anytime
+ * soon, unless there are radical changes". It seems to be the best of
+ * the alternatives.
+ */
+ Tls_fatal_error_msg(conn->ssl->in_msg[1]);
+ } else if (ret == MBEDTLS_ERR_SSL_INVALID_RECORD) {
+ MSG("mbedtls_ssl_handshake() failed upon receiving 'an invalid "
+ "record'.\n");
+ } else if (ret == MBEDTLS_ERR_SSL_FEATURE_UNAVAILABLE) {
+ MSG("mbedtls_ssl_handshake() failed: 'The requested feature is not "
+ "available.'\n");
+ } else if (ret == MBEDTLS_ERR_SSL_BAD_HS_SERVER_KEY_EXCHANGE) {
+ MSG("mbedtls_ssl_handshake() failed: 'Processing of the "
+ "ServerKeyExchange handshake message failed.'\n");
+ } else if (ret == MBEDTLS_ERR_SSL_CONN_EOF) {
+ MSG("mbedtls_ssl_handshake() failed: Read EOF. Connection closed by "
+ "server.\n");
+ } else {
+ MSG("mbedtls_ssl_handshake() failed with error -0x%04x\n", -ret);
+ }
+ }
+
+ /*
+ * If there were problems with the certificate, the connection may have
+ * been closed by the server if the user responded too slowly to a popup.
+ */
+
+ if (!ongoing) {
+ if (a_Klist_get_data(conn_list, connkey)) {
+ conn->connecting = FALSE;
+ if (failed) {
+ Tls_close_by_key(connkey);
+ }
+ a_IOwatch_remove_fd(fd, -1);
+ a_Http_connect_done(fd, failed ? FALSE : TRUE);
+ } else {
+ MSG("Connection disappeared. Too long with a popup popped up?\n");
+ }
+ }
+}
+
+static void Tls_handshake_cb(int fd, void *vconnkey)
+{
+ Tls_handshake(fd, VOIDP2INT(vconnkey));
+}
+
+/*
+ * Make TLS connection over a connect()ed socket.
+ */
+void a_Tls_connect(int fd, const DilloUrl *url)
+{
+ mbedtls_ssl_context *ssl = dNew0(mbedtls_ssl_context, 1);
+ bool_t success = TRUE;
+ int connkey = -1;
+ int ret;
+
+ if (!ssl_enabled)
+ success = FALSE;
+
+ if (success && Tls_user_said_no(url)) {
+ success = FALSE;
+ }
+
+ if (success && (ret = mbedtls_ssl_setup(ssl, &ssl_conf))) {
+ MSG("mbedtls_ssl_setup failed %d\n", ret);
+ success = FALSE;
+ }
+
+ /* assign TLS connection to this file descriptor */
+ if (success) {
+ Conn_t *conn = Tls_conn_new(fd, url, ssl);
+ connkey = Tls_make_conn_key(conn);
+ mbedtls_ssl_set_bio(ssl, &conn->fd, mbedtls_net_send, mbedtls_net_recv,
+ NULL);
+ }
+
+ if (success && (ret = mbedtls_ssl_set_hostname(ssl, URL_HOST(url)))) {
+ MSG("mbedtls_ssl_set_hostname failed %d\n", ret);
+ success = FALSE;
+ }
+
+ if (!success) {
+ a_Tls_reset_server_state(url);
+ a_Http_connect_done(fd, success);
+ } else {
+ Tls_handshake(fd, connkey);
+ }
+}
+
+/*
+ * Read data from an open TLS connection.
+ */
+int a_Tls_read(void *conn, void *buf, size_t len)
+{
+ Conn_t *c = (Conn_t*)conn;
+ int ret = mbedtls_ssl_read(c->ssl, buf, len);
+
+ if (ret < 0) {
+ if (ret == MBEDTLS_ERR_SSL_PEER_CLOSE_NOTIFY) {
+ /* treat it as EOF */
+ ret = 0;
+ } else if (ret == MBEDTLS_ERR_SSL_WANT_READ) {
+ ret = -1;
+ errno = EAGAIN; /* already happens to be set, but let's make sure */
+ } else if (ret == MBEDTLS_ERR_NET_CONN_RESET) {
+ MSG("READ failed: TLS connection reset by server.\n");
+ } else {
+ MSG("READ failed with -0x%04x: an mbed tls error.\n", -ret);
+ }
+ }
+ return ret;
+}
+
+/*
+ * Write data to an open TLS connection.
+ */
+int a_Tls_write(void *conn, void *buf, size_t len)
+{
+ Conn_t *c = (Conn_t*)conn;
+ int ret = mbedtls_ssl_write(c->ssl, buf, len);
+
+ if (ret < 0) {
+ MSG("WRITE failed with -0x%04x: an mbed tls error\n", -ret);
+ }
+ return ret;
+}
+
+void a_Tls_close_by_fd(int fd)
+{
+ FdMapEntry_t *fme = dList_find_custom(fd_map, INT2VOIDP(fd),
+ Tls_fd_map_cmp);
+
+ if (fme) {
+ Tls_close_by_key(fme->connkey);
+ }
+}
+
+static void Tls_cert_authorities_print_summary()
+{
+ const int ca_len = dList_length(cert_authorities);
+ Dstr *ds = dStr_new("");
+ int i, j;
+
+ if (ca_len)
+ dStr_append(ds, "TLS: Certificate chain roots during this session:\n");
+
+ for (i = 0; i < ca_len; i++) {
+ CertAuth_t *ca = (CertAuth_t *)dList_nth_data(cert_authorities, i);
+ const int servers_len = ca->servers ? dList_length(ca->servers) : 0;
+ char *ca_name = strstr(ca->name, "CN=");
+
+ if (!ca_name)
+ ca_name = strstr(ca->name, "OU=");
+
+ if (ca_name)
+ ca_name += 3;
+ else
+ ca_name = ca->name;
+ dStr_sprintfa(ds, "- %s for: ", ca_name);
+
+ for (j = 0; j < servers_len; j++) {
+ Server_t *s = dList_nth_data(ca->servers, j);
+ bool_t ipv6 = a_Url_host_type(s->hostname) == URL_HOST_IPV6;
+
+ dStr_sprintfa(ds, "%s%s%s", ipv6?"[":"", s->hostname, ipv6?"]":"");
+ if (s->port != URL_HTTPS_PORT)
+ dStr_sprintfa(ds, ":%d", s->port);
+ dStr_append_c(ds, ' ');
+ }
+ dStr_append_c(ds, '\n');
+ }
+ MSG("%s", ds->str);
+ dStr_free(ds, 1);
+}
+
+/*
+ * Free mbed tls's chain of certificates and free our data on which root
+ * certificates caused us to trust which servers.
+ */
+static void Tls_cert_authorities_freeall()
+{
+ if (cacerts.next)
+ mbedtls_x509_crt_free(cacerts.next);
+
+ if (cert_authorities) {
+ CertAuth_t *ca;
+ int i, n = dList_length(cert_authorities);
+
+ for (i = 0; i < n; i++) {
+ ca = (CertAuth_t *) dList_nth_data(cert_authorities, i);
+ dFree(ca->name);
+ if (ca->servers)
+ dList_free(ca->servers);
+ dFree(ca);
+ }
+ dList_free(cert_authorities);
+ }
+}
+
+static void Tls_servers_freeall()
+{
+ if (servers) {
+ Server_t *s;
+ int i, n = dList_length(servers);
+
+ for (i = 0; i < n; i++) {
+ s = (Server_t *) dList_nth_data(servers, i);
+ dFree(s->hostname);
+ dFree(s);
+ }
+ dList_free(servers);
+ }
+}
+
+static void Tls_fd_map_remove_all()
+{
+ if (fd_map) {
+ FdMapEntry_t *fme;
+ int i, n = dList_length(fd_map);
+
+ for (i = 0; i < n; i++) {
+ fme = (FdMapEntry_t *) dList_nth_data(fd_map, i);
+ dFree(fme);
+ }
+ dList_free(fd_map);
+ }
+}
+
+/*
+ * Clean up
+ */
+void a_Tls_freeall(void)
+{
+ if (prefs.show_msg)
+ Tls_cert_authorities_print_summary();
+
+ Tls_fd_map_remove_all();
+ Tls_cert_authorities_freeall();
+ Tls_servers_freeall();
+}
+
+#endif /* ENABLE_SSL */