From 6ef3b839729f7e8ccd0a8c88374567b497088512 Mon Sep 17 00:00:00 2001 From: Rodrigo Arias Mallo Date: Thu, 21 Dec 2023 21:23:50 +0100 Subject: Move mbedTLS implementation to tls_mbedtls.c --- src/IO/tls.c | 1217 -------------------------------------------------- src/IO/tls.h | 50 --- src/IO/tls_mbedtls.c | 1217 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/IO/tls_mbedtls.h | 50 +++ 4 files changed, 1267 insertions(+), 1267 deletions(-) delete mode 100644 src/IO/tls.c delete mode 100644 src/IO/tls.h create mode 100644 src/IO/tls_mbedtls.c create mode 100644 src/IO/tls_mbedtls.h (limited to 'src') diff --git a/src/IO/tls.c b/src/IO/tls.c deleted file mode 100644 index c6b04b8f..00000000 --- a/src/IO/tls.c +++ /dev/null @@ -1,1217 +0,0 @@ -/* - * File: tls.c - * - * Copyright (C) 2011 Benjamin Johnson - * (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 -#include - -#include "../../dlib/dlib.h" -#include "../dialog.hh" -#include "../klist.h" -#include "iowatch.hh" -#include "tls.h" -#include "Url.h" - -#include /* WORKAROUND: mbed TLS 2.3.0 ssl.h needs it */ -#include -#include /* random number generator */ -#include -#include -#include -#include -#include /* 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 */ diff --git a/src/IO/tls.h b/src/IO/tls.h deleted file mode 100644 index a3bf9ba5..00000000 --- a/src/IO/tls.h +++ /dev/null @@ -1,50 +0,0 @@ -#ifndef __TLS_H__ -#define __TLS_H__ - -#ifdef __cplusplus -extern "C" { -#endif - -#include "../url.h" - -#define TLS_CONNECT_NEVER -1 -#define TLS_CONNECT_NOT_YET 0 -#define TLS_CONNECT_READY 1 - -void a_Tls_init(); - - -#ifdef ENABLE_SSL -int a_Tls_certificate_is_clean(const DilloUrl *url); -int a_Tls_connect_ready(const DilloUrl *url); -void a_Tls_reset_server_state(const DilloUrl *url); - -/* Use to initiate a TLS connection. */ -void a_Tls_connect(int fd, const DilloUrl *url); - -void *a_Tls_connection(int fd); - -void a_Tls_freeall(); - -void a_Tls_close_by_fd(int fd); -int a_Tls_read(void *conn, void *buf, size_t len); -int a_Tls_write(void *conn, void *buf, size_t len); -#else - -#define a_Tls_certificate_is_clean(host) 0 -#define a_Tls_connect_ready(url) TLS_CONNECT_NEVER -#define a_Tls_reset_server_state(url) ; -#define a_Tls_handshake(fd, url) ; -#define a_Tls_connect(fd, url) ; -#define a_Tls_connection(fd) NULL -#define a_Tls_freeall() ; -#define a_Tls_close_by_fd(fd) ; -#define a_Tls_read(conn, buf, len) 0 -#define a_Tls_write(conn, buf, len) 0 -#endif -#ifdef __cplusplus -} -#endif - -#endif /* __TLS_H__ */ - 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 + * (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 +#include + +#include "../../dlib/dlib.h" +#include "../dialog.hh" +#include "../klist.h" +#include "iowatch.hh" +#include "tls.h" +#include "Url.h" + +#include /* WORKAROUND: mbed TLS 2.3.0 ssl.h needs it */ +#include +#include /* random number generator */ +#include +#include +#include +#include +#include /* 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 */ diff --git a/src/IO/tls_mbedtls.h b/src/IO/tls_mbedtls.h new file mode 100644 index 00000000..a3bf9ba5 --- /dev/null +++ b/src/IO/tls_mbedtls.h @@ -0,0 +1,50 @@ +#ifndef __TLS_H__ +#define __TLS_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +#include "../url.h" + +#define TLS_CONNECT_NEVER -1 +#define TLS_CONNECT_NOT_YET 0 +#define TLS_CONNECT_READY 1 + +void a_Tls_init(); + + +#ifdef ENABLE_SSL +int a_Tls_certificate_is_clean(const DilloUrl *url); +int a_Tls_connect_ready(const DilloUrl *url); +void a_Tls_reset_server_state(const DilloUrl *url); + +/* Use to initiate a TLS connection. */ +void a_Tls_connect(int fd, const DilloUrl *url); + +void *a_Tls_connection(int fd); + +void a_Tls_freeall(); + +void a_Tls_close_by_fd(int fd); +int a_Tls_read(void *conn, void *buf, size_t len); +int a_Tls_write(void *conn, void *buf, size_t len); +#else + +#define a_Tls_certificate_is_clean(host) 0 +#define a_Tls_connect_ready(url) TLS_CONNECT_NEVER +#define a_Tls_reset_server_state(url) ; +#define a_Tls_handshake(fd, url) ; +#define a_Tls_connect(fd, url) ; +#define a_Tls_connection(fd) NULL +#define a_Tls_freeall() ; +#define a_Tls_close_by_fd(fd) ; +#define a_Tls_read(conn, buf, len) 0 +#define a_Tls_write(conn, buf, len) 0 +#endif +#ifdef __cplusplus +} +#endif + +#endif /* __TLS_H__ */ + -- cgit v1.2.3