diff options
Diffstat (limited to 'src/IO/tls.c')
-rw-r--r-- | src/IO/tls.c | 1389 |
1 files changed, 649 insertions, 740 deletions
diff --git a/src/IO/tls.c b/src/IO/tls.c index 8fed45ba..62e160b1 100644 --- a/src/IO/tls.c +++ b/src/IO/tls.c @@ -1,33 +1,23 @@ /* * File: tls.c * - * Copyright 2004 Garrett Kajmowicz <gkajmowi@tbaytel.net> - * (for some bits derived from the https dpi, e.g., certificate handling) - * Copyright (C) 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, - * 2009, 2010, 2011, 2012 Free Software Foundation, Inc. - * (for the certificate hostname checking from wget) * Copyright (C) 2011 Benjamin Johnson <obeythepenguin@users.sourceforge.net> * (for the https code offered from dplus browser that formed the basis...) + * Copyright 2016 corvid * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * - * As a special exception, permission is granted to link Dillo with the OpenSSL - * or LibreSSL library, and distribute the linked executables without - * including the source code for OpenSSL or LibreSSL in the source - * distribution. You must obey the GNU General Public License, version 3, in - * all respects for all of the code used other than OpenSSL or LibreSSL. - */ - -/* https://www.ssllabs.com/ssltest/viewMyClient.html - * https://github.com/lgarron/badssl.com */ /* - * Using TLS in Applications: http://datatracker.ietf.org/wg/uta/documents/ - * TLS: http://datatracker.ietf.org/wg/tls/documents/ + * 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" @@ -43,13 +33,8 @@ void a_Tls_init() #else #include <assert.h> - -#include <sys/stat.h> -#include <sys/types.h> - -#include <ctype.h> /* tolower for wget stuff */ -#include <stdio.h> #include <errno.h> + #include "../../dlib/dlib.h" #include "../dialog.hh" #include "../klist.h" @@ -57,10 +42,13 @@ void a_Tls_init() #include "tls.h" #include "Url.h" -#include <openssl/ssl.h> -#include <openssl/rand.h> -#include <openssl/err.h> -#include <openssl/x509v3.h> /* for hostname checking */ +#include <mbedtls/ssl.h> +#include <mbedtls/ctr_drbg.h> /* random number generator */ +#include <mbedtls/entropy.h> +#include <mbedtls/error.h> +#include <mbedtls/oid.h> +#include <mbedtls/x509.h> +#include <mbedtls/net.h> /* net_send, net_recv */ #define CERT_STATUS_NONE 0 #define CERT_STATUS_RECEIVING 1 @@ -75,6 +63,11 @@ typedef struct { } Server_t; typedef struct { + char *name; + Dlist *servers; +} CertAuth_t; + +typedef struct { int fd; int connkey; } FdMapEntry_t; @@ -85,18 +78,21 @@ typedef struct { typedef struct { int fd; DilloUrl *url; - SSL *ssl; + mbedtls_ssl_context *ssl; bool_t connecting; } Conn_t; /* List of active TLS connections */ static Klist_t *conn_list = NULL; -/* - * If ssl_context is still NULL, this corresponds to TLS being disabled. - */ -static SSL_CTX *ssl_context; +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_connect_cb(int fd, void *vconnkey); @@ -164,43 +160,95 @@ void *a_Tls_connection(int fd) /* * Add a new TLS connection information node. */ -static int Tls_conn_new(int fd, const DilloUrl *url, SSL *ssl) +static Conn_t *Tls_conn_new(int fd, const DilloUrl *url, + mbedtls_ssl_context *ssl) { - int key; - Conn_t *conn = dNew0(Conn_t, 1); conn->fd = fd; conn->url = a_Url_dup(url); conn->ssl = ssl; conn->connecting = TRUE; + return conn; +} - key = a_Klist_insert(&conn_list, conn); +static int Tls_make_conn_key(Conn_t *conn) +{ + int key = a_Klist_insert(&conn_list, conn); - Tls_fd_map_add_entry(fd, key); + Tls_fd_map_add_entry(conn->fd, key); return key; } /* - * Let's monitor for TLS alerts. + * 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_info_cb(const SSL *ssl, int where, int ret) +static void Tls_load_certificates_from_path(const char *const pathname) { - if (where & SSL_CB_ALERT) { - const char *str = SSL_alert_desc_string_long(ret); + int ret = mbedtls_x509_crt_parse_path(&cacerts, pathname); - if (strcmp(str, "close notify")) - MSG("TLS ALERT on %s: %s\n", (where & SSL_CB_READ) ? "read" : "write", - str); + 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. - * This is like using SSL_CTX_load_verify_locations() but permitting more - * than one bundle and more than one directory. Due to the notoriously - * abysmal openssl documentation, this was worked out from reading discussion - * on the web and then reading openssl source to see what it normally does. */ static void Tls_load_certificates() { @@ -212,6 +260,8 @@ static void Tls_load_certificates() */ 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", @@ -226,120 +276,135 @@ static void Tls_load_certificates() CA_CERTS_DIR }; - X509_STORE *store = SSL_CTX_get_cert_store(ssl_context); - X509_LOOKUP *lookup = X509_STORE_add_lookup(store, X509_LOOKUP_file()); - - for (u = 0; u < sizeof(ca_files) / sizeof(ca_files[0]); u++) { + for (u = 0; u < sizeof(ca_files)/sizeof(ca_files[0]); u++) { if (*ca_files[u]) - X509_LOOKUP_load_file(lookup, ca_files[u], X509_FILETYPE_PEM); + Tls_load_certificates_from_file(ca_files[u]); } - lookup = X509_STORE_add_lookup(store, X509_LOOKUP_hash_dir()); for (u = 0; u < sizeof(ca_paths)/sizeof(ca_paths[0]); u++) { - if (*ca_paths[u]) - X509_LOOKUP_add_dir(lookup, ca_paths[u], X509_FILETYPE_PEM); + if (*ca_paths[u]) { + Tls_load_certificates_from_path(ca_paths[u]); + } } userpath = dStrconcat(dGethomedir(), "/.dillo/certs/", NULL); - X509_LOOKUP_add_dir(lookup, userpath, X509_FILETYPE_PEM); + Tls_load_certificates_from_path(userpath); dFree(userpath); - /* Clear out errors in the queue (file not found, etc.) */ - while(ERR_get_error()) - ; + 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"); } /* - * Initialize the OpenSSL library. + * Remove the pre-shared key ciphersuites. There are lots of them, + * and we aren't making any use of them. */ -void a_Tls_init(void) +static void Tls_remove_psk_ciphersuites() { - SSL_library_init(); - SSL_load_error_strings(); - if (RAND_status() != 1) { - /* The standard solution is to provide it with more entropy, but this - * involves knowing very well that you are doing exactly the right thing. - */ - MSG_ERR("Disabling HTTPS: Insufficient entropy for openssl.\n"); - return; + 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++; } - - /* Create SSL context */ - ssl_context = SSL_CTX_new(SSLv23_client_method()); - if (ssl_context == NULL) { - MSG_ERR("Disabling HTTPS: Error creating SSL context.\n"); - return; + 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; - SSL_CTX_set_info_callback(ssl_context, Tls_info_cb); + mbedtls_ssl_conf_ciphersuites(&ssl_conf, our_ciphers); +} - /* Don't want: eNULL, which has no encryption; aNULL, which has no - * authentication; LOW, which as of 2014 use 64 or 56-bit encryption; - * EXPORT40, which uses 40-bit encryption; RC4, for which methods were - * found in 2013 to defeat it somewhat too easily. - */ - SSL_CTX_set_cipher_list(ssl_context, - "ALL:!aNULL:!eNULL:!LOW:!EXPORT40:!RC4"); +/* + * Initialize the mbed TLS library. + */ +void a_Tls_init(void) +{ + int ret; - /* SSL2 has been known to be insecure forever, disabling SSL3 is in response - * to POODLE, and disabling compression is in response to CRIME. - */ - SSL_CTX_set_options(ssl_context, - SSL_OP_NO_SSLv2|SSL_OP_NO_SSLv3|SSL_OP_NO_COMPRESSION); + /* 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, + }; - /* This lets us deal with self-signed certificates */ - SSL_CTX_set_verify(ssl_context, SSL_VERIFY_NONE, NULL); + mbedtls_ssl_config_init(&ssl_conf); - Tls_load_certificates(); + 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); + + 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); -/* - * Save certificate with a hashed filename. - * Return: 0 on success, 1 on failure. - */ -static int Tls_save_certificate_home(X509 * cert) -{ - char buf[4096]; - - FILE * fp = NULL; - uint_t i = 0; - int ret = 1; - - /* Attempt to create .dillo/certs blindly - check later */ - snprintf(buf, 4096, "%s/.dillo/", dGethomedir()); - mkdir(buf, 01777); - snprintf(buf, 4096, "%s/.dillo/certs/", dGethomedir()); - mkdir(buf, 01777); - - do { - snprintf(buf, 4096, "%s/.dillo/certs/%lx.%u", - dGethomedir(), X509_subject_name_hash(cert), i); - - fp=fopen(buf, "r"); - if (fp == NULL){ - /* File name doesn't exist so we can use it safely */ - fp=fopen(buf, "w"); - if (fp == NULL){ - MSG("Unable to open cert save file in home dir\n"); - break; - } else { - PEM_write_X509(fp, cert); - fclose(fp); - MSG("Wrote certificate\n"); - ret = 0; - break; - } - } else { - fclose(fp); - } - i++; - /* Don't loop too many times - just give up */ - } while (i < 1024); - - return ret; + Tls_load_certificates(); } /* @@ -381,7 +446,7 @@ int a_Tls_connect_ready(const DilloUrl *url) Server_t *s; int ret = TLS_CONNECT_READY; - dReturn_val_if_fail(ssl_context, TLS_CONNECT_NEVER); + 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) @@ -427,379 +492,234 @@ int a_Tls_certificate_is_clean(const DilloUrl *url) return Tls_cert_status(url) == CERT_STATUS_CLEAN; } +#if 0 /* - * We are both checking whether the certificates are using a strong enough - * hash algorithm and key as well as printing out certificate information the - * first time that we see it. Mixing these two actions together is generally - * not good practice, but feels justified by the fact that it's so much - * trouble to get this information out of openssl even once. - * - * Return FALSE if MD5 (MD*) hash is found and user does not accept it, - * otherwise TRUE. + * Print certificate and its chain of issuer certificates. */ -static bool_t Tls_check_cert_strength(SSL *ssl, Server_t *srv, int *choice) +static void Tls_print_cert_chain(const mbedtls_x509_crt *cert) { /* print for first connection to server */ - const bool_t print_chain = srv->cert_status == CERT_STATUS_RECEIVING; - bool_t success = TRUE; + 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"); + } - STACK_OF(X509) *sk = SSL_get_peer_cert_chain(ssl); + if (mbedtls_oid_get_sig_alg_desc(&cert->sig_oid, &sigalg)) + sigalg = "(??" ")"; - if (sk) { - const uint_t buflen = 4096; - char buf[buflen]; - int rc, i, n = sk_X509_num(sk); - X509 *cert = NULL; - EVP_PKEY *public_key; - int key_type, key_bits; - const char *type_str; - BIO *b; + 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); - for (i = 0; i < n; i++) { - cert = sk_X509_value(sk, i); - public_key = X509_get_pubkey(cert); + last_cert = cert; + cert = cert->next; + } + if (last_cert) { + mbedtls_x509_dn_gets(buf, buflen, &last_cert->issuer); + MSG("root: %s\n", buf); + } +} +#endif - /* We are trying to find a way to get the hash function used - * with a certificate. This way, which is not very pleasant, puts - * a string such as "sha256WithRSAEncryption" in our buffer and we - * then trim off the "With..." part. - */ - b = BIO_new(BIO_s_mem()); - rc = i2a_ASN1_OBJECT(b, cert->sig_alg->algorithm); +/* + * 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; - if (rc > 0) { - rc = BIO_gets(b, buf, buflen); - } - if (rc <= 0) { - strcpy(buf, "(unknown)"); - buf[buflen-1] = '\0'; - } else { - char *s = strstr(buf, "With"); - - if (s) { - *s = '\0'; - if (!strcmp(buf, "sha1")) { - if (print_chain) - MSG_WARN("In 2015, browsers have begun to deprecate SHA1 " - "certificates.\n"); - } else if (!strncmp(buf, "md", 2) && success == TRUE) { - const char *msg = "A certificate in the chain uses the MD5 " - "signature algorithm, which is too weak " - "to trust."; - *choice = a_Dialog_choice("Dillo TLS security warning", msg, - "Continue", "Cancel", NULL); - if (*choice != 1) - success = FALSE; - } - } - } - BIO_free(b); - - if (print_chain) - MSG("%s ", buf); - - key_type = EVP_PKEY_type(public_key->type); - type_str = key_type == EVP_PKEY_RSA ? "RSA" : - key_type == EVP_PKEY_DSA ? "DSA" : - key_type == EVP_PKEY_DH ? "DH" : - key_type == EVP_PKEY_EC ? "EC" : "???"; - key_bits = EVP_PKEY_bits(public_key); - X509_NAME_oneline(X509_get_subject_name(cert), buf, buflen); - buf[buflen-1] = '\0'; - if (print_chain) - MSG("%d-bit %s: %s\n", key_bits, type_str, buf); - EVP_PKEY_free(public_key); - - if (key_type == EVP_PKEY_RSA && key_bits <= 1024) { - if (print_chain) - MSG_WARN("In 2014/5, browsers have been deprecating 1024-bit " - "RSA keys.\n"); - } - } + 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; - if (cert) { - X509_NAME_oneline(X509_get_issuer_name(cert), buf, buflen); - buf[buflen-1] = '\0'; - if (print_chain) - MSG("root: %s\n", buf); + 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); } } - return success; + dStr_append(ds, ".\n"); } -/******************** BEGINNING OF STUFF DERIVED FROM wget-1.16.3 */ +/* + * 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]; -#define ASTERISK_EXCLUDES_DOT /* mandated by rfc2818 */ + while (cert->next) + cert = cert->next; + mbedtls_x509_dn_gets(buf, buflen, &cert->issuer); -/* Return true is STRING (case-insensitively) matches PATTERN, false - otherwise. The recognized wildcard character is "*", which matches - any character in STRING except ".". Any number of the "*" wildcard - may be present in the pattern. + 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); +} - This is used to match of hosts as indicated in rfc2818: "Names may - contain the wildcard character * which is considered to match any - single domain name component or component fragment. E.g., *.a.com - matches foo.a.com but not bar.foo.a.com. f*.com matches foo.com but - not bar.com [or foo.bar.com]." +/* + * 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; - If the pattern contain no wildcards, pattern_match(a, b) is - equivalent to !strcasecmp(a, b). */ + 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); +} -static bool_t pattern_match (const char *pattern, const char *string) +/* + * 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 *p = pattern, *n = string; - char c; - for (; (c = tolower (*p++)) != '\0'; n++) - if (c == '*') - { - for (c = tolower (*p); c == '*'; c = tolower (*++p)) - ; - for (; *n != '\0'; n++) - if (tolower (*n) == c && pattern_match (p, n)) - return TRUE; -#ifdef ASTERISK_EXCLUDES_DOT - else if (*n == '.') - return FALSE; -#endif - return c == '\0'; - } - else - { - if (c != tolower (*n)) - return FALSE; - } - return *n == '\0'; + const char *hash = (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); } /* - * Check that the certificate corresponds to the site it's presented for. - * - * Return TRUE if the hostname matched or the user indicated acceptance. - * FALSE on failure. + * Generate dialog msg when public key algorithm (RSA, ECDSA) is not accepted. */ -static bool_t Tls_check_cert_hostname(X509 *cert, const char *host, - int *choice) +static void Tls_cert_bad_pk_alg(const mbedtls_x509_crt *cert, Dstr *ds) { - dReturn_val_if_fail(cert && host, FALSE); - - char *msg; - GENERAL_NAMES *subjectAltNames; - bool_t success = TRUE, alt_name_checked = FALSE;; - char common_name[256]; - - /* Check that HOST matches the common name in the certificate. - #### The following remains to be done: - - - When matching against common names, it should loop over all - common names and choose the most specific one, i.e. the last - one, not the first one, which the current code picks. - - - Ensure that ASN1 strings from the certificate are encoded as - UTF-8 which can be meaningfully compared to HOST. */ - - subjectAltNames = X509_get_ext_d2i (cert, NID_subject_alt_name, NULL, NULL); - - if (subjectAltNames) - { - /* Test subject alternative names */ - - Dstr *err = dStr_new(""); - dStr_sprintf(err, "Hostname %s does not match any of certificate's " - "Subject Alternative Names: ", host); - - /* Do we want to check for dNSNAmes or ipAddresses (see RFC 2818)? - * Signal it by host_in_octet_string. */ - ASN1_OCTET_STRING *host_in_octet_string = a2i_IPADDRESS (host); - - int numaltnames = sk_GENERAL_NAME_num (subjectAltNames); - int i; - for (i=0; i < numaltnames; i++) - { - const GENERAL_NAME *name = - sk_GENERAL_NAME_value (subjectAltNames, i); - if (name) - { - if (host_in_octet_string) - { - if (name->type == GEN_IPADD) - { - /* Check for ipAddress */ - /* TODO: Should we convert between IPv4-mapped IPv6 - * addresses and IPv4 addresses? */ - alt_name_checked = TRUE; - if (!ASN1_STRING_cmp (host_in_octet_string, - name->d.iPAddress)) - break; - dStr_sprintfa(err, "%s ", name->d.iPAddress); - } - } - else if (name->type == GEN_DNS) - { - /* dNSName should be IA5String (i.e. ASCII), however who - * does trust CA? Convert it into UTF-8 for sure. */ - unsigned char *name_in_utf8 = NULL; - - /* Check for dNSName */ - alt_name_checked = TRUE; - - if (0 <= ASN1_STRING_to_UTF8 (&name_in_utf8, name->d.dNSName)) - { - /* Compare and check for NULL attack in ASN1_STRING */ - if (pattern_match ((char *)name_in_utf8, host) && - (strlen ((char *)name_in_utf8) == - (size_t)ASN1_STRING_length (name->d.dNSName))) - { - OPENSSL_free (name_in_utf8); - break; - } - dStr_sprintfa(err, "%s ", name_in_utf8); - OPENSSL_free (name_in_utf8); - } - } - } - } - sk_GENERAL_NAME_pop_free(subjectAltNames, GENERAL_NAME_free); - if (host_in_octet_string) - ASN1_OCTET_STRING_free(host_in_octet_string); - - if (alt_name_checked == TRUE && i >= numaltnames) - { - success = FALSE; - *choice = a_Dialog_choice("Dillo TLS security warning", - err->str, "Continue", "Cancel", NULL); - - switch (*choice){ - case 1: - success = TRUE; - break; - case 2: - break; - default: - break; - } - } - dStr_free(err, 1); - } - - if (alt_name_checked == FALSE) - { - /* Test commomName */ - X509_NAME *xname = X509_get_subject_name(cert); - common_name[0] = '\0'; - X509_NAME_get_text_by_NID (xname, NID_commonName, common_name, - sizeof (common_name)); - - if (!pattern_match (common_name, host)) - { - success = FALSE; - msg = dStrconcat("Certificate common name ", common_name, - " doesn't match requested host name ", host, NULL); - *choice = a_Dialog_choice("Dillo TLS security warning", - msg, "Continue", "Cancel", NULL); - dFree(msg); - - switch (*choice){ - case 1: - success = TRUE; - break; - case 2: - break; - default: - break; - } - } - else - { - /* We now determine the length of the ASN1 string. If it - * differs from common_name's length, then there is a \0 - * before the string terminates. This can be an instance of a - * null-prefix attack. - * - * https://www.blackhat.com/html/bh-usa-09/bh-usa-09-archives.html#Marlinspike - * */ - - int i = -1, j; - X509_NAME_ENTRY *xentry; - ASN1_STRING *sdata; - - if (xname) { - for (;;) - { - j = X509_NAME_get_index_by_NID (xname, NID_commonName, i); - if (j == -1) break; - i = j; - } - } - - xentry = X509_NAME_get_entry(xname,i); - sdata = X509_NAME_ENTRY_get_data(xentry); - if (strlen (common_name) != (size_t)ASN1_STRING_length (sdata)) - { - success = FALSE; - msg = dStrconcat("Certificate common name is invalid (contains a NUL " - "character). This may be an indication that the " - "host is not who it claims to be -- that is, not " - "the real ", host, NULL); - *choice = a_Dialog_choice("Dillo TLS security warning", - msg, "Continue", "Cancel", NULL); - dFree(msg); - - switch (*choice){ - case 1: - success = TRUE; - break; - case 2: - break; - default: - break; - } - } - } - } - return success; + 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); } -/******************** END OF STUFF DERIVED FROM wget-1.16.3 */ +/* + * 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); +} /* - * Get the certificate at the end of the chain, or NULL on failure. - * - * Rumor has it that the stack can be NULL if a connection has been reused - * and that the stack can then be reconstructed if necessary, but it doesn't - * sound like a case we'll encounter. + * Make a dialog msg containing warnings about problems with the certificate. */ -static X509 *Tls_get_end_of_chain(SSL *ssl) +static char *Tls_make_bad_cert_msg(const mbedtls_x509_crt *cert,uint32_t flags) { - STACK_OF(X509) *sk = SSL_get_peer_cert_chain(ssl); + 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; - return sk ? sk_X509_value(sk, sk_X509_num(sk) - 1) : NULL; + 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 void Tls_get_issuer_name(X509 *cert, char *buf, uint_t buflen) +static int Tls_cert_auth_cmp(const void *v1, const void *v2) { - if (cert) { - X509_NAME_oneline(X509_get_issuer_name(cert), buf, buflen); - } else { - strcpy(buf, "(unknown)"); - buf[buflen-1] = '\0'; - } + const CertAuth_t *c1 = (CertAuth_t *)v1, *c2 = (CertAuth_t *)v2; + + return strcmp(c1->name, c2->name); } -static void Tls_get_expiration_str(X509 *cert, char *buf, uint_t buflen) +static int Tls_cert_auth_cmp_by_name(const void *v1, const void *v2) { - ASN1_TIME *exp_date = X509_get_notAfter(cert); - BIO *b = BIO_new(BIO_s_mem()); - int rc = ASN1_TIME_print(b, exp_date); + const CertAuth_t *c = (CertAuth_t *)v1; + const char *name = (char *)v2; - if (rc > 0) { - rc = BIO_gets(b, buf, buflen); - } - if (rc <= 0) { - strcpy(buf, "(unknown)"); - buf[buflen-1] = '\0'; + 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); } - BIO_free(b); + dList_append(ca->servers, srv); } /* @@ -807,196 +727,53 @@ static void Tls_get_expiration_str(X509 *cert, char *buf, uint_t buflen) * to do. * Return: -1 if connection should be canceled, or 0 if it should continue. */ -static int Tls_examine_certificate(SSL *ssl, Server_t *srv) +static int Tls_examine_certificate(mbedtls_ssl_context *ssl, Server_t *srv) { - X509 *remote_cert; - long st; - const uint_t buflen = 4096; - char buf[buflen], *cn, *msg; + const mbedtls_x509_crt *cert; + uint32_t st; int choice = -1, ret = -1; char *title = dStrconcat("Dillo TLS security warning: ",srv->hostname,NULL); - remote_cert = SSL_get_peer_certificate(ssl); - if (remote_cert == NULL){ + cert = mbedtls_ssl_get_peer_cert(ssl); + if (cert == NULL){ /* Inform user that remote system cannot be trusted */ choice = a_Dialog_choice(title, - "The remote system is not presenting a certificate. " - "This site cannot be trusted. Sending data is not safe.", + "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 if (Tls_check_cert_strength(ssl, srv, &choice) && - Tls_check_cert_hostname(remote_cert, srv->hostname, &choice)) { - /* Figure out if (and why) the remote system can't be trusted */ - st = SSL_get_verify_result(ssl); - switch (st) { - case X509_V_OK: - ret = 0; - break; - case X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT: - /* Either self signed and untrusted */ - /* Extract CN from certificate name information */ - if ((cn = strstr(remote_cert->name, "/CN=")) == NULL) { - strcpy(buf, "(no CN given)"); - } else { - char *cn_end; - - cn += 4; - - if ((cn_end = strstr(cn, "/")) == NULL ) - cn_end = cn + strlen(cn); - - strncpy(buf, cn, (size_t) (cn_end - cn)); - buf[cn_end - cn] = '\0'; - } - msg = dStrconcat("The remote certificate is self-signed and " - "untrusted. For address: ", buf, NULL); - choice = a_Dialog_choice(title, - msg, "Continue", "Cancel", "Save Certificate", NULL); - dFree(msg); - - switch (choice){ - case 1: - ret = 0; - break; - case 2: - break; - case 3: - /* Save certificate to a file */ - Tls_save_certificate_home(remote_cert); - ret = 0; - break; - default: - break; - } - break; - case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT: - case X509_V_ERR_UNABLE_TO_DECODE_ISSUER_PUBLIC_KEY: - choice = a_Dialog_choice(title, - "The issuer for the remote certificate cannot be found. " - "The authenticity of the remote certificate cannot be trusted.", - "Continue", "Cancel", NULL); - - if (choice == 1) { - ret = 0; - } - break; - - case X509_V_ERR_UNABLE_TO_DECRYPT_CERT_SIGNATURE: - case X509_V_ERR_UNABLE_TO_DECRYPT_CRL_SIGNATURE: - case X509_V_ERR_CERT_SIGNATURE_FAILURE: - case X509_V_ERR_CRL_SIGNATURE_FAILURE: - choice = a_Dialog_choice(title, - "The remote certificate signature could not be read " - "or is invalid and should not be trusted", - "Continue", "Cancel", NULL); - - 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); } - break; - case X509_V_ERR_CERT_NOT_YET_VALID: - case X509_V_ERR_CRL_NOT_YET_VALID: - choice = a_Dialog_choice(title, - "Part of the remote certificate is not yet valid. " - "Certificates usually have a range of dates over which " - "they are to be considered valid, and the certificate " - "presented has a starting validity after today's date " - "You should be cautious about using this site", - "Continue", "Cancel", NULL); + 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; } - break; - case X509_V_ERR_CERT_HAS_EXPIRED: - case X509_V_ERR_CRL_HAS_EXPIRED: - Tls_get_expiration_str(remote_cert, buf, buflen); - msg = dStrconcat("The remote certificate expired on: ", buf, - ". This site can no longer be trusted.", NULL); - - choice = a_Dialog_choice(title, msg, "Continue", "Cancel", NULL); - if (choice == 1) { - ret = 0; - } - dFree(msg); - break; - case X509_V_ERR_ERROR_IN_CERT_NOT_BEFORE_FIELD: - case X509_V_ERR_ERROR_IN_CERT_NOT_AFTER_FIELD: - case X509_V_ERR_ERROR_IN_CRL_LAST_UPDATE_FIELD: - case X509_V_ERR_ERROR_IN_CRL_NEXT_UPDATE_FIELD: - choice = a_Dialog_choice(title, - "There was an error in the certificate presented. " - "Some of the certificate data was improperly formatted " - "making it impossible to determine if the certificate " - "is valid. You should not trust this certificate.", - "Continue", "Cancel", NULL); - if (choice == 1) { - ret = 0; - } - break; - case X509_V_ERR_INVALID_CA: - case X509_V_ERR_INVALID_PURPOSE: - case X509_V_ERR_CERT_UNTRUSTED: - case X509_V_ERR_CERT_REJECTED: - case X509_V_ERR_KEYUSAGE_NO_CERTSIGN: - choice = a_Dialog_choice(title, - "One of the certificates in the chain is being used " - "incorrectly (possibly due to configuration problems " - "with the remote system. The connection should not " - "be trusted", - "Continue", "Cancel", NULL); - if (choice == 1) { - ret = 0; - } - break; - case X509_V_ERR_SUBJECT_ISSUER_MISMATCH: - case X509_V_ERR_AKID_SKID_MISMATCH: - case X509_V_ERR_AKID_ISSUER_SERIAL_MISMATCH: - choice = a_Dialog_choice(title, - "Some of the information presented by the remote system " - "does not match other information presented. " - "This may be an attempt to eavesdrop on communications", - "Continue", "Cancel", NULL); - if (choice == 1) { - ret = 0; - } - break; - case X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN: - Tls_get_issuer_name(Tls_get_end_of_chain(ssl), buf, buflen); - msg = dStrconcat("Certificate chain led to a self-signed certificate " - "instead of a trusted root. Name: ", buf , NULL); - choice = a_Dialog_choice(title, msg, "Continue", "Cancel", NULL); - if (choice == 1) { - ret = 0; - } - dFree(msg); - break; - case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY: - Tls_get_issuer_name(Tls_get_end_of_chain(ssl), buf, buflen); - msg = dStrconcat("The issuer certificate of an untrusted certificate " - "cannot be found. Issuer: ", buf, NULL); - choice = a_Dialog_choice(title, msg, "Continue", "Cancel", NULL); - if (choice == 1) { - ret = 0; - } - dFree(msg); - break; - default: /* Need to add more options later */ - snprintf(buf, 80, - "The remote certificate cannot be verified (code %ld)", st); - choice = a_Dialog_choice(title, - buf, "Continue", "Cancel", NULL); - /* abort on anything but "Continue" */ - if (choice == 1){ - ret = 0; - } + dFree(dialog_warning_msg); } - X509_free(remote_cert); - remote_cert = 0; } dFree(title); @@ -1005,10 +782,7 @@ static int Tls_examine_certificate(SSL *ssl, Server_t *srv) } else if (choice == 1) { srv->cert_status = CERT_STATUS_USER_ACCEPTED; /* clicked Continue */ } else { - /* 2 for Cancel, or 0 when window closed. Treating 0 as meaning 'No' is - * probably not exactly correct, but adding complexity to handle this - * obscure case does not seem justifiable. - */ + /* 2 for Cancel, or 0 when window closed. */ srv->cert_status = CERT_STATUS_BAD; } return ret; @@ -1041,13 +815,9 @@ static void Tls_close_by_key(int connkey) a_IOwatch_remove_fd(c->fd, -1); dClose(c->fd); } - if (!SSL_in_init(c->ssl)) { - /* openssl 1.0.2f does not like shutdown being called during - * handshake, resulting in ssl_undefined_function in the error queue. - */ - SSL_shutdown(c->ssl); - } - SSL_free(c->ssl); + 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); @@ -1057,6 +827,88 @@ static void Tls_close_by_key(int connkey) } /* + * 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. */ @@ -1071,71 +923,62 @@ static void Tls_connect(int fd, int connkey) return; } - assert(!ERR_get_error()); + if (conn->ssl->state != MBEDTLS_SSL_HANDSHAKE_OVER) { + ret = mbedtls_ssl_handshake(conn->ssl); - ret = SSL_connect(conn->ssl); - - if (ret <= 0) { - int err1_ret = SSL_get_error(conn->ssl, ret); - if (err1_ret == SSL_ERROR_WANT_READ || - err1_ret == SSL_ERROR_WANT_WRITE) { - int want = err1_ret == SSL_ERROR_WANT_READ ? DIO_READ : DIO_WRITE; + 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, - err1_ret == SSL_ERROR_WANT_READ ? "read" : "write"); + ret == MBEDTLS_ERR_SSL_WANT_READ ? "read" : "write"); a_IOwatch_remove_fd(fd, -1); a_IOwatch_add_fd(fd, want, Tls_connect_cb, INT2VOIDP(connkey)); ongoing = TRUE; failed = FALSE; - } else if (err1_ret == SSL_ERROR_SYSCALL || err1_ret == SSL_ERROR_SSL) { - unsigned long err2_ret = ERR_get_error(); - - if (err2_ret) { - do { - MSG("SSL_connect() failed: %s\n", - ERR_error_string(err2_ret, NULL)); - } while ((err2_ret = ERR_get_error())); - } else { - /* nothing in the error queue */ - if (ret == 0) { - MSG("TLS connect error: \"an EOF was observed that violates " - "the protocol\"\n"); - /* - * I presume we took too long on our side and the server grew - * impatient. - */ - } else if (ret == -1) { - MSG("TLS connect error: %s\n", dStrerror(errno)); - - /* If the following can happen, I'll add code to handle it, but - * I don't want to add code blindly if it isn't getting used - */ - assert(errno != EAGAIN && errno != EINTR); - } else { - MSG_ERR("According to the man page for SSL_get_error(), this " - "was not a possibility (ret %d).\n", ret); - } + } 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: %s, cipher %s\n", URL_AUTHORITY(conn->url), 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("SSL_get_error() returned %d on a connect.\n", err1_ret); - } - } else { - 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. */ - SSL *ssl = conn->ssl; - const char *version = SSL_get_version(ssl); - const SSL_CIPHER *cipher = SSL_get_current_cipher(ssl); - - MSG("%s: %s, cipher %s\n", URL_AUTHORITY(conn->url), version, - SSL_CIPHER_get_name(cipher)); - } - - if (srv->cert_status == CERT_STATUS_USER_ACCEPTED || - (Tls_examine_certificate(conn->ssl, srv) != -1)) { - failed = FALSE; + MSG("mbedtls_ssl_handshake() failed with error -0x%04x\n", -ret); } } @@ -1150,7 +993,7 @@ static void Tls_connect(int fd, int connkey) if (failed) { Tls_close_by_key(connkey); } - a_IOwatch_remove_fd(fd, DIO_READ|DIO_WRITE); + 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"); @@ -1168,46 +1011,35 @@ static void Tls_connect_cb(int fd, void *vconnkey) */ void a_Tls_handshake(int fd, const DilloUrl *url) { - SSL *ssl; + mbedtls_ssl_context *ssl = dNew0(mbedtls_ssl_context, 1); bool_t success = TRUE; int connkey = -1; + int ret; - if (!ssl_context) + if (!ssl_enabled) success = FALSE; if (success && Tls_user_said_no(url)) { success = FALSE; } - assert(!ERR_get_error()); - - if (success && !(ssl = SSL_new(ssl_context))) { - unsigned long err_ret = ERR_get_error(); - do { - MSG("SSL_new() failed: %s\n", ERR_error_string(err_ret, NULL)); - } while ((err_ret = ERR_get_error())); + 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 && !SSL_set_fd(ssl, fd)) { - unsigned long err_ret = ERR_get_error(); - do { - MSG("SSL_set_fd() failed: %s\n", ERR_error_string(err_ret, NULL)); - } while ((err_ret = ERR_get_error())); - success = FALSE; + 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) - connkey = Tls_conn_new(fd, url, ssl); - -#ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME - /* Server Name Indication. From the openssl changelog, it looks like this - * came along in 2010. - */ - if (success && !a_Url_host_is_ip(URL_HOST(url))) - SSL_set_tlsext_host_name(ssl, URL_HOST(url)); -#endif + 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); @@ -1223,7 +1055,22 @@ void a_Tls_handshake(int fd, const DilloUrl *url) int a_Tls_read(void *conn, void *buf, size_t len) { Conn_t *c = (Conn_t*)conn; - return SSL_read(c->ssl, buf, len); + 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; } /* @@ -1232,7 +1079,12 @@ int a_Tls_read(void *conn, void *buf, size_t len) int a_Tls_write(void *conn, void *buf, size_t len) { Conn_t *c = (Conn_t*)conn; - return SSL_write(c->ssl, buf, len); + 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) @@ -1245,6 +1097,62 @@ void a_Tls_close_by_fd(int fd) } } +static void Tls_cert_authorities_print_summary() +{ + const int ca_len = dList_length(cert_authorities); + int i, j; + + if (ca_len) + MSG("TLS: Trusted 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; + MSG("- %s for: ", ca_name); + + for (j = 0; j < servers_len; j++) { + Server_t *s = dList_nth_data(ca->servers, j); + + MSG("%s:%d ", s->hostname, s->port); + } + MSG("\n"); + } + +} + +/* + * 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) { @@ -1275,13 +1183,14 @@ static void Tls_fd_map_remove_all() } /* - * Clean up the OpenSSL library + * Clean up */ void a_Tls_freeall(void) { - if (ssl_context) - SSL_CTX_free(ssl_context); + Tls_cert_authorities_print_summary(); + Tls_fd_map_remove_all(); + Tls_cert_authorities_freeall(); Tls_servers_freeall(); } |