aboutsummaryrefslogtreecommitdiff
path: root/src/IO/tls.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/IO/tls.c')
-rw-r--r--src/IO/tls.c1389
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();
}