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_mbedtls.c | 1217 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1217 insertions(+) create mode 100644 src/IO/tls_mbedtls.c (limited to 'src/IO/tls_mbedtls.c') 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 */ -- cgit v1.2.3