changeset 4615:2e8517dcee8a

use mbed TLS
author corvid
date So, 03 Jul 2016 16:09:21 +0000
parents 1520e1386da7
children 53c2580b50c5
files AUTHORS ChangeLog configure.ac src/IO/IO.c src/IO/tls.c
diffstat 5 files changed, 683 insertions(+), 767 deletions(-) [+]
line wrap: on
line diff
--- a/AUTHORS	So Jul 03 15:42:49 2016 +0000
+++ b/AUTHORS	So Jul 03 16:09:21 2016 +0000
@@ -87,5 +87,3 @@
 * src/md5.[ch] contain code by L. Peter Deutsch whose copyright is held by
   Aladdin Enterprises.
 * src/tipwin.cc contains code by Greg Ercolano.
-* src/IO/tls.c contains code from wget whose copyright is held by the
-  Free Software Foundation.
--- a/ChangeLog	So Jul 03 15:42:49 2016 +0000
+++ b/ChangeLog	So Jul 03 16:09:21 2016 +0000
@@ -35,10 +35,9 @@
    Patches: corvid
 +- Doxygen fixes.
    Patch: Jeremy Henty
-+- Move HTTPS from dpi into the browser, enable SNI, add certificate hostname
-   checking from wget, check more locations for CA bundles and add
-   --with-ca-certs-file and --with-ca-certs-dir to configure, some improvement
-   to security warning popups, etc.
++- Move HTTPS from dpi into the browser, enable SNI, check more locations for
+   CA bundles and add --with-ca-certs-file and --with-ca-certs-dir to
+   configure, some improvement to security warning popups, etc.
    Patch: corvid, Benjamin Johnson
 +- Fix bookmarks DPI crash.
  - Fix OSX compilation issue with xembed.
--- a/configure.ac	So Jul 03 15:42:49 2016 +0000
+++ b/configure.ac	So Jul 03 16:09:21 2016 +0000
@@ -24,7 +24,7 @@
                     , enable_gprof=no)
 AC_ARG_ENABLE(insure, [  --enable-insure         Try to compile and run with Insure++],
                     , enable_insure=no)
-AC_ARG_ENABLE(ssl,    [  --enable-ssl            Enable SSL/HTTPS/TLS (EXPERIMENTAL CODE)],
+AC_ARG_ENABLE(ssl,    [  --enable-ssl            Enable SSL/HTTPS/TLS],
                     , enable_ssl=no)
 AC_ARG_WITH(ca-certs-file, [  --with-ca-certs-file=FILE  Specify where to find a bundle of trusted CA certificates for TLS], CA_CERTS_FILE=$withval)
 AC_ARG_WITH(ca-certs-dir, [  --with-ca-certs-dir=DIR     Specify where to find a directory containing trusted CA certificates for TLS], CA_CERTS_DIR=$withval)
@@ -294,19 +294,18 @@
 dnl --------------------------
 dnl
 if test "x$enable_ssl" = "xyes"; then
-  AC_CHECK_HEADER(openssl/ssl.h, ssl_ok=yes, ssl_ok=no)
+  AC_CHECK_HEADER(mbedtls/ssl.h, ssl_ok=yes, ssl_ok=no)
 
   if test "x$ssl_ok" = "xyes"; then
     old_libs="$LIBS"
-    AC_CHECK_LIB(ssl, SSL_library_init, ssl_ok=yes, ssl_ok=no, -lcrypto)
+    AC_CHECK_LIB(mbedtls, mbedtls_ssl_init, ssl_ok=yes, ssl_ok=no, -lmbedx509 -lmbedcrypto)
     LIBS="$old_libs"
   fi
 
   if test "x$ssl_ok" = "xyes"; then
-    LIBSSL_LIBS="-lcrypto -lssl"
-    AC_MSG_WARN([*** Enabling SSL/HTTPS/TLS support. THIS IS EXPERIMENTAL CODE ***])
+    LIBSSL_LIBS="-lmbedtls -lmbedx509 -lmbedcrypto"
   else
-    AC_MSG_WARN([*** No libssl found. Disabling SSL/HTTPS/TLS support. ***])
+    AC_MSG_WARN([*** mbed TLS 2 not found. Disabling SSL/HTTPS/TLS support. ***])
   fi
 fi
 
--- a/src/IO/IO.c	So Jul 03 15:42:49 2016 +0000
+++ b/src/IO/IO.c	So Jul 03 16:09:21 2016 +0000
@@ -37,7 +37,7 @@
    int Op;                /* IORead | IOWrite */
    int FD;                /* Current File Descriptor */
    int Flags;             /* Flag array (look definitions above) */
-   int Status;            /* errno code */
+   int Status;            /* nonzero upon IO failure */
    Dstr *Buf;             /* Internal buffer */
 
    void *Info;            /* CCC Info structure for this IO */
@@ -184,9 +184,14 @@
             ret = TRUE;
             break;
          } else {
-            io->Status = errno;
-            MSG("READ Failed: %s\n", strerror(errno));
-            break;
+            if (conn) {
+               io->Status = St;
+               break;
+            } else {
+               io->Status = errno;
+               MSG("READ Failed with %d: %s\n", St, strerror(errno));
+               break;
+            }
          }
       } else { /* St == 0 */
          break;
@@ -234,8 +239,14 @@
             ret = TRUE;
             break;
          } else {
-            io->Status = errno;
-            break;
+            if (conn) {
+               io->Status = St;
+               break;
+            } else {
+               io->Status = errno;
+               MSG("WRITE Failed with %d: %s\n", St, strerror(errno));
+               break;
+            }
          }
       } else if (St < io->Buf->len) {
          /* Not all data written */
--- a/src/IO/tls.c	So Jul 03 15:42:49 2016 +0000
+++ b/src/IO/tls.c	So Jul 03 16:09:21 2016 +0000
@@ -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 @@
 #else
 
 #include <assert.h>
+#include <errno.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 @@
 #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 @@
 } Server_t;
 
 typedef struct {
+   char *name;
+   Dlist *servers;
+} CertAuth_t;
+
+typedef struct {
    int fd;
    int connkey;
 } FdMapEntry_t;
@@ -85,18 +78,21 @@
 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 @@
 /*
  * 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_info_cb(const SSL *ssl, int where, int ret)
+static void Tls_load_certificates_from_file(const char *const filename)
 {
-   if (where & SSL_CB_ALERT) {
-      const char *str = SSL_alert_desc_string_long(ret);
+   int ret = mbedtls_x509_crt_parse_file(&cacerts, filename);
 
-      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_PK_FILE_IO_ERROR) {
+         /* can't read from file */
+      } else {
+         MSG("Failed to parse certificates from %s (returned -0x%04x)\n",
+             filename, -ret);
+      }
+   }
+}
+
+/*
+ * Load certificates from a given pathname.
+ */
+static void Tls_load_certificates_from_path(const char *const pathname)
+{
+   int ret = mbedtls_x509_crt_parse_path(&cacerts, pathname);
+
+   if (ret < 0) {
+      if (ret == MBEDTLS_ERR_X509_FILE_IO_ERROR) {
+         /* can't read from path */
+      } else {
+         MSG("Failed to parse certificates from %s (returned -0x%04x)\n",
+             pathname, -ret);
+      }
+   }
+}
+
+/*
+ * Remove duplicate certificates.
+ */
+static void Tls_remove_duplicate_certificates()
+{
+   mbedtls_x509_crt *cp, *curr = &cacerts;
+
+   while (curr) {
+      cp = curr;
+      while (cp->next) {
+         if (curr->serial.len == cp->next->serial.len &&
+             !memcmp(curr->serial.p, cp->next->serial.p, curr->serial.len) &&
+             curr->subject_raw.len == cp->next->subject_raw.len &&
+             !memcmp(curr->subject_raw.p, cp->next->subject_raw.p,
+                     curr->subject_raw.len)) {
+            mbedtls_x509_crt *duplicate = cp->next;
+
+            cp->next = duplicate->next;
+
+            /* clearing the next field prevents it from freeing the whole
+             * chain of certificates
+             */
+            duplicate->next = NULL;
+            mbedtls_x509_crt_free(duplicate);
+            dFree(duplicate);
+         } else {
+            cp = cp->next;
+         }
+      }
+      curr = curr->next;
    }
 }
 
 /*
  * Load trusted certificates.
- * 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 @@
     */
    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 @@
       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.
+ */
+static void Tls_remove_psk_ciphersuites()
+{
+   const mbedtls_ssl_ciphersuite_t *cs_info;
+   int *our_ciphers, *q;
+   int n = 0;
+
+   const int *default_ciphers = mbedtls_ssl_list_ciphersuites(),
+             *p = default_ciphers;
+
+   /* count how many we will want */
+   while (*p) {
+      cs_info = mbedtls_ssl_ciphersuite_from_id(*p);
+      if (!mbedtls_ssl_ciphersuite_uses_psk(cs_info))
+         n++;
+      p++;
+   }
+   n++;
+   our_ciphers = dNew(int, n);
+
+   /* iterate through again and copy them over */
+   p = default_ciphers;
+   q = our_ciphers;
+   while (*p) {
+      cs_info = mbedtls_ssl_ciphersuite_from_id(*p);
+
+      if (!mbedtls_ssl_ciphersuite_uses_psk(cs_info))
+         *q++ = *p;
+      p++;
+   }
+   *q = 0;
+
+   mbedtls_ssl_conf_ciphersuites(&ssl_conf, our_ciphers);
+}
+
+/*
+ * Initialize the mbed TLS library.
  */
 void a_Tls_init(void)
 {
-   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;
-   }
+   int ret;
 
-   /* Create SSL context */
-   ssl_context = SSL_CTX_new(SSLv23_client_method());
-   if (ssl_context == NULL) {
-      MSG_ERR("Disabling HTTPS: Error creating SSL context.\n");
+    /* As of 2.3.0 in 2016, the 'default' profile allows SHA1, RIPEMD160,
+     * and SHA224 (in addition to the stronger ones), and the 'next' profile
+     * doesn't allow anything below SHA256. Since we're never going to hear
+     * when/if RIPEMD160 and SHA224 are deprecated, and they're obscure enough
+     * not to encounter, let's not allow those.
+     * These profiles are for certificates, and mbed tls points out that these
+     * have nothing to do with hashes during handshakes.
+     * Their 'next' profile only allows "Curves at or above 128-bit security
+     * level". For now, we follow 'default' and allow all curves.
+     */
+   static const mbedtls_x509_crt_profile prof = {
+    MBEDTLS_X509_ID_FLAG( MBEDTLS_MD_SHA1 ) |
+    MBEDTLS_X509_ID_FLAG( MBEDTLS_MD_SHA256 ) |
+    MBEDTLS_X509_ID_FLAG( MBEDTLS_MD_SHA384 ) |
+    MBEDTLS_X509_ID_FLAG( MBEDTLS_MD_SHA512 ),
+    0xFFFFFFF, /* Any PK alg    */
+    0xFFFFFFF, /* Any curve     */
+    2048,
+   };
+
+   mbedtls_ssl_config_init(&ssl_conf);
+
+   mbedtls_ssl_config_defaults(&ssl_conf, MBEDTLS_SSL_IS_CLIENT,
+                               MBEDTLS_SSL_TRANSPORT_STREAM,
+                               MBEDTLS_SSL_PRESET_DEFAULT);
+   mbedtls_ssl_conf_cert_profile(&ssl_conf, &prof);
+
+   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;
    }
 
-   SSL_CTX_set_info_callback(ssl_context, Tls_info_cb);
-
-   /* 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");
-
-   /* 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);
-
-   /* This lets us deal with self-signed certificates */
-   SSL_CTX_set_verify(ssl_context, SSL_VERIFY_NONE, NULL);
-
-   Tls_load_certificates();
+   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);
-}
-
-/*
- * 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);
+   cert_authorities = dList_new(12);
 
-   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 @@
    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 @@
    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;
-
-   STACK_OF(X509) *sk = SSL_get_peer_cert_chain(ssl);
-
-   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;
-
-      for (i = 0; i < n; i++) {
-         cert = sk_X509_value(sk, i);
-         public_key = X509_get_pubkey(cert);
-
-         /* 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);
-
-         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");
+   const mbedtls_x509_crt *last_cert;
+   const uint_t buflen = 2048;
+   char buf[buflen];
+   int key_bits;
+   const char *sigalg;
 
-            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");
-         }
+   while (cert) {
+      if (cert->sig_md == MBEDTLS_MD_SHA1) {
+         MSG_WARN("In 2015, browsers have begun to deprecate SHA1 "
+                  "certificates.\n");
       }
 
-      if (cert) {
-         X509_NAME_oneline(X509_get_issuer_name(cert), buf, buflen);
-         buf[buflen-1] = '\0';
-         if (print_chain)
-            MSG("root: %s\n", buf);
-      }
-   }
-   return success;
-}
-
-/******************** BEGINNING OF STUFF DERIVED FROM wget-1.16.3 */
-
-#define ASTERISK_EXCLUDES_DOT   /* mandated by rfc2818 */
-
-/* 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.
-
-   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]."
+      if (mbedtls_oid_get_sig_alg_desc(&cert->sig_oid, &sigalg))
+         sigalg = "(??" ")";
 
-   If the pattern contain no wildcards, pattern_match(a, b) is
-   equivalent to !strcasecmp(a, b).  */
-
-static bool_t pattern_match (const char *pattern, const char *string)
-{
+      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);
 
-  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;
+      last_cert = cert;
+      cert = cert->next;
+   }
+   if (last_cert) {
+      mbedtls_x509_dn_gets(buf, buflen, &last_cert->issuer);
+      MSG("root: %s\n", buf);
+   }
+}
 #endif
-        return c == '\0';
-      }
-    else
-      {
-        if (c != tolower (*n))
-          return FALSE;
-      }
-  return *n == '\0';
+
+/*
+ * Generate dialog msg for expired cert.
+ */
+static void Tls_cert_expired(const mbedtls_x509_crt *cert, Dstr *ds)
+{
+   const mbedtls_x509_time *date = &cert->valid_to;
+
+   dStr_sprintfa(ds,"Certificate expired at: %04d/%02d/%02d %02d:%02d:%02d.\n",
+                 date->year, date->mon, date->day, date->hour, date->min,
+                 date->sec);
 }
 
 /*
- * 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 certificate is not for this host.
  */
-static bool_t Tls_check_cert_hostname(X509 *cert, const char *host,
-                                      int *choice)
+static void Tls_cert_cn_mismatch(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);
+   const uint_t buflen = 2048;
+   char cert_info_buf[buflen];
+   char *san, *s;
 
-      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);
+   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);
 
-         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;
+   if ((san = strstr(cert_info_buf, "subject alt name  : "))) {
+      san += 20;
+      s = strchr(san, '\n');
+      if (s) {
+         *s = '\0';
+         dStr_sprintfa(ds, " (%s)", san);
+      }
+   }
+   dStr_append(ds, ".\n");
 }
 
-/******************** END OF STUFF DERIVED FROM wget-1.16.3 */
-
 /*
- * 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.
+ * Generate dialog msg when certificate is not trusted.
  */
-static X509 *Tls_get_end_of_chain(SSL *ssl)
+static void Tls_cert_trust_chain_failed(const mbedtls_x509_crt *cert, Dstr *ds)
 {
-   STACK_OF(X509) *sk = SSL_get_peer_cert_chain(ssl);
+   const uint_t buflen = 2048;
+   char buf[buflen];
 
-   return sk ? sk_X509_value(sk, sk_X509_num(sk) - 1) : NULL;
+   while (cert->next)
+      cert = cert->next;
+   mbedtls_x509_dn_gets(buf, buflen, &cert->issuer);
+
+   dStr_sprintfa(ds, "Couldn't reach any trusted root certificate from "
+                     "supplied certificate. The issuer at the end of the "
+                     "chain was: \"%s\"\n", buf);
 }
 
-static void Tls_get_issuer_name(X509 *cert, char *buf, uint_t buflen)
+/*
+ * 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)
 {
-   if (cert) {
-      X509_NAME_oneline(X509_get_issuer_name(cert), buf, buflen);
-   } else {
-      strcpy(buf, "(unknown)");
-      buf[buflen-1] = '\0';
-   }
+   const mbedtls_x509_time *date = &cert->valid_to;
+
+   dStr_sprintfa(ds, "Certificate validity begins in the future at: "
+                     "%04d/%02d/%02d %02d:%02d:%02d.\n",
+                     date->year, date->mon, date->day, date->hour, date->min,
+                     date->sec);
 }
 
-static void Tls_get_expiration_str(X509 *cert, char *buf, uint_t buflen)
+/*
+ * Generate dialog msg when certificate hash algorithm is not accepted.
+ */
+static void Tls_cert_bad_hash(const mbedtls_x509_crt *cert, Dstr *ds)
 {
-   ASN1_TIME *exp_date = X509_get_notAfter(cert);
-   BIO *b = BIO_new(BIO_s_mem());
-   int rc = ASN1_TIME_print(b, exp_date);
+   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";
 
-   if (rc > 0) {
-      rc = BIO_gets(b, buf, buflen);
+   dStr_sprintfa(ds, "This certificate's hash algorithm is not accepted "
+                     "(%s).\n", hash);
+}
+
+/*
+ * Generate dialog msg when public key algorithm (RSA, ECDSA) is not accepted.
+ */
+static void Tls_cert_bad_pk_alg(const mbedtls_x509_crt *cert, Dstr *ds)
+{
+   const char *type_str = mbedtls_pk_get_name(&cert->pk);
+
+   dStr_sprintfa(ds, "This certificate's public key algorithm is not accepted "
+                     "(%s).\n", type_str);
+}
+
+/*
+ * Generate dialog msg when the public key is not acceptable. As of 2016,
+ * this was triggered by RSA keys below 2048 bits, if I recall correctly.
+ */
+static void Tls_cert_bad_key(const mbedtls_x509_crt *cert, Dstr *ds) {
+   int key_bits = mbedtls_pk_get_bitlen(&cert->pk);
+   const char *type_str = mbedtls_pk_get_name(&cert->pk);
+
+   dStr_sprintfa(ds, "This certificate's key is not accepted, which generally "
+                     "means it's too weak (%d-bit %s).\n", key_bits, type_str);
+}
+
+/*
+ * Make a dialog msg containing warnings about problems with the certificate.
+ */
+static char *Tls_make_bad_cert_msg(const mbedtls_x509_crt *cert,uint32_t flags)
+{
+   static const struct certerr {
+      int val;
+      void (*cert_err_fn)(const mbedtls_x509_crt *cert, Dstr *ds);
+   } cert_error [] = {
+      { MBEDTLS_X509_BADCERT_EXPIRED, Tls_cert_expired},
+      { MBEDTLS_X509_BADCERT_CN_MISMATCH, Tls_cert_cn_mismatch},
+      { MBEDTLS_X509_BADCERT_NOT_TRUSTED, Tls_cert_trust_chain_failed},
+      { MBEDTLS_X509_BADCERT_FUTURE, Tls_cert_not_valid_yet},
+      { MBEDTLS_X509_BADCERT_BAD_MD, Tls_cert_bad_hash},
+      { MBEDTLS_X509_BADCERT_BAD_PK, Tls_cert_bad_pk_alg},
+      { MBEDTLS_X509_BADCERT_BAD_KEY, Tls_cert_bad_key}
+   };
+   const uint_t ncert_errors = sizeof(cert_error) /sizeof(cert_error[0]);
+   char *ret;
+   Dstr *ds = dStr_new(NULL);
+   uint_t u;
+
+   for (u = 0; u < ncert_errors; u++) {
+      if (flags & cert_error[u].val) {
+         flags &= ~cert_error[u].val;
+         cert_error[u].cert_err_fn(cert, ds);
+      }
    }
-   if (rc <= 0) {
-      strcpy(buf, "(unknown)");
-      buf[buflen-1] = '\0';
+   if (flags)
+      dStr_sprintfa(ds, "Unknown certificate error(s): flag value 0x%04x",
+                    flags);
+   ret = ds->str;
+   dStr_free(ds, 0);
+   return ret;
+}
+
+static int Tls_cert_auth_cmp(const void *v1, const void *v2)
+{
+   const CertAuth_t *c1 = (CertAuth_t *)v1, *c2 = (CertAuth_t *)v2;
+
+   return strcmp(c1->name, c2->name);
+}
+
+static int Tls_cert_auth_cmp_by_name(const void *v1, const void *v2)
+{
+   const CertAuth_t *c = (CertAuth_t *)v1;
+   const char *name = (char *)v2;
+
+   return strcmp(c->name, name);
+}
+
+/*
+ * Keep account of on whose authority we are trusting servers.
+ */
+static void Tls_update_cert_authorities_data(const mbedtls_x509_crt *cert,
+                                             Server_t *srv)
+{
+   const uint_t buflen = 512;
+   char buf[buflen];
+   const mbedtls_x509_crt *last = cert;
+
+   while (last->next)
+      last = last->next;
+
+   mbedtls_x509_dn_gets(buf, buflen, &last->issuer);
+
+   CertAuth_t *ca = dList_find_custom(cert_authorities, buf,
+                                      Tls_cert_auth_cmp_by_name);
+   if (!ca) {
+      ca = dNew(CertAuth_t, 1);
+      ca->name = dStrdup(buf);
+      ca->servers = dList_new(8);
+      dList_insert_sorted(cert_authorities, ca, Tls_cert_auth_cmp);
    }
-   BIO_free(b);
+   dList_append(ca->servers, srv);
 }
 
 /*
@@ -807,196 +727,53 @@
  * 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:
+   } else {
+      /* check the certificate */
+      st = mbedtls_ssl_get_verify_result(ssl);
+      if (st == 0) {
+         if (srv->cert_status == CERT_STATUS_RECEIVING) {
+            /* first connection to server */
+#if 0
+            Tls_print_cert_chain(cert);
+#endif
+            Tls_update_cert_authorities_data(cert, srv);
+         }
          ret = 0;
-         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);
+      } 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);
 
-         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;
-         }
-         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);
-
+         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 @@
    } 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 @@
          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 @@
 }
 
 /*
+ * 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 @@
       return;
    }
 
-   assert(!ERR_get_error());
-
-   ret = SSL_connect(conn->ssl);
+   if (conn->ssl->state != MBEDTLS_SSL_HANDSHAKE_OVER) {
+      ret = mbedtls_ssl_handshake(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));
+      } else if (ret == 0) {
+         Server_t *srv = dList_find_sorted(servers, conn->url,
+                                           Tls_servers_by_url_cmp);
 
-               /* 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);
-            }
+         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 @@
          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 @@
  */
 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 @@
 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_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 @@
    }
 }
 
+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 @@
 }
 
 /*
- * 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();
 }