From ea14c266b84296761354f63d746a289db0bb4918 Mon Sep 17 00:00:00 2001 From: corvid Date: Thu, 28 May 2015 16:58:39 +0000 Subject: make http_max_conns truly per server/proxy rather than host And separate http from https for safety while we're at it. We were checking this where we needed to, but it would be easy to forget about in the future. Not that very much happens when you try http://example.com:443 or https://example.com:80, but I'm being careful nevertheless. --- src/IO/http.c | 156 ++++++++++++++++++++++++++++++++-------------------------- 1 file changed, 85 insertions(+), 71 deletions(-) (limited to 'src') diff --git a/src/IO/http.c b/src/IO/http.c index e5c459ee..22b2eaa6 100644 --- a/src/IO/http.c +++ b/src/IO/http.c @@ -57,13 +57,13 @@ static const int HTTP_SOCKET_SSL = 0x8; /* 'web' is just a reference (no need to deallocate it here). */ typedef struct { int SockFD; - uint_t connect_port; uint_t flags; DilloWeb *web; /* reference to client's web structure */ DilloUrl *url; Dlist *addr_list; /* Holds the DNS answer */ ChainLink *Info; /* Used for CCC asynchronous operations */ - char *connected_to; /* Used for per-host connection limit */ + char *connected_to; /* Used for per-server connection limit */ + uint_t connect_port; Dstr *https_proxy_reply; } SocketData_t; @@ -72,19 +72,22 @@ typedef struct { */ typedef struct { char *host; + uint_t port; + bool_t https; + int active_conns; Dlist *queue; -} HostConnection_t; +} Server_t; typedef struct { int fd; int skey; } FdMapEntry_t; -static void Http_socket_enqueue(HostConnection_t *hc, SocketData_t* sock); -static HostConnection_t *Http_host_connection_get(const char *host); -static void Http_host_connection_remove(HostConnection_t *hc); -static void Http_connect_socket(ChainLink *Info, HostConnection_t *hc); +static void Http_socket_enqueue(Server_t *srv, SocketData_t* sock); +static Server_t *Http_server_get(const char *host, uint_t port, bool_t https); +static void Http_server_remove(Server_t *srv); +static void Http_connect_socket(ChainLink *Info); static char *Http_get_connect_str(const DilloUrl *url); static void Http_send_query(SocketData_t *S); static void Http_socket_free(int SKey); @@ -97,7 +100,7 @@ static Klist_t *ValidSocks = NULL; /* Active sockets list. It holds pointers to static DilloUrl *HTTP_Proxy = NULL; static char *HTTP_Proxy_Auth_base64 = NULL; static char *HTTP_Language_hdr = NULL; -static Dlist *host_connections; +static Dlist *servers; /* TODO: If fd_map will stick around in its present form (FDs and SocketData_t) * then consider whether having both this and ValidSocks is necessary. @@ -127,7 +130,7 @@ int a_Http_init(void) HTTP_Proxy_Auth_base64 = a_Misc_encode_base64(prefs.http_proxyuser); */ - host_connections = dList_new(5); + servers = dList_new(5); fd_map = dList_new(20); return 0; @@ -228,23 +231,24 @@ void a_Http_connect_done(int fd, bool_t success) } } -static void Http_socket_activate(HostConnection_t *hc, SocketData_t *sd) +static void Http_socket_activate(Server_t *srv, SocketData_t *sd) { - dList_remove(hc->queue, sd); + dList_remove(srv->queue, sd); sd->flags &= ~HTTP_SOCKET_QUEUED; - hc->active_conns++; - sd->connected_to = hc->host; + srv->active_conns++; + sd->connected_to = srv->host; } -static void Http_connect_queued_sockets(HostConnection_t *hc) +static void Http_connect_queued_sockets(Server_t *srv) { SocketData_t *sd; int i; for (i = 0; - i < dList_length(hc->queue) && hc->active_conns < prefs.http_max_conns; + (i < dList_length(srv->queue) && + srv->active_conns < prefs.http_max_conns); i++) { - sd = dList_nth_data(hc->queue, i); + sd = dList_nth_data(srv->queue, i); if (!(sd->flags & HTTP_SOCKET_TO_BE_FREED)) { int connect_ready = SSL_CONNECT_READY; @@ -258,18 +262,19 @@ static void Http_connect_queued_sockets(HostConnection_t *hc) Http_socket_free(SKey); } else if (connect_ready == SSL_CONNECT_READY) { i--; - Http_socket_activate(hc, sd); - Http_connect_socket(sd->Info, hc); + Http_socket_activate(srv, sd); + Http_connect_socket(sd->Info); } } if (sd->flags & HTTP_SOCKET_TO_BE_FREED) { - dList_remove(hc->queue, sd); + dList_remove(srv->queue, sd); dFree(sd); i--; } } - _MSG("Queue %s len %d\n", hc->host, dList_length(hc->queue)); + _MSG("Queue http%s://%s:%u len %d\n", srv->https ? "s" : "", srv->host, + srv->port, dList_length(srv->queue)); } /* @@ -294,11 +299,12 @@ static void Http_socket_free(int SKey) if (S->connected_to) { a_Ssl_close_by_fd(S->SockFD); - HostConnection_t *hc = Http_host_connection_get(S->connected_to); - hc->active_conns--; - Http_connect_queued_sockets(hc); - if (hc->active_conns == 0) - Http_host_connection_remove(hc); + Server_t *srv = Http_server_get(S->connected_to, S->connect_port, + (S->flags & HTTP_SOCKET_SSL)); + srv->active_conns--; + Http_connect_queued_sockets(srv); + if (srv->active_conns == 0) + Http_server_remove(srv); } a_Url_free(S->url); dFree(S); @@ -504,7 +510,7 @@ static void Http_connect_ssl(ChainLink *info) * This function is called after the DNS succeeds in solving a hostname. * Task: Finish socket setup and start connecting the socket. */ -static void Http_connect_socket(ChainLink *Info, HostConnection_t *hc) +static void Http_connect_socket(ChainLink *Info) { int i, status; SocketData_t *S; @@ -542,7 +548,7 @@ static void Http_connect_socket(ChainLink *Info, HostConnection_t *hc) sin->sin_port = htons(S->connect_port); memcpy(&sin->sin_addr, dh->data, (size_t)dh->alen); if (a_Web_valid(S->web) && (S->web->flags & WEB_RootUrl)) - MSG("Connecting to %s:%d\n", inet_ntoa(sin->sin_addr), + MSG("Connecting to %s:%u\n", inet_ntoa(sin->sin_addr), S->connect_port); break; } @@ -557,7 +563,7 @@ static void Http_connect_socket(ChainLink *Info, HostConnection_t *hc) memcpy(&sin6->sin6_addr, dh->data, dh->alen); inet_ntop(dh->af, dh->data, buf, sizeof(buf)); if (a_Web_valid(S->web) && (S->web->flags & WEB_RootUrl)) - MSG("Connecting to %s:%d\n", buf, S->connect_port); + MSG("Connecting to %s:%u\n", buf, S->connect_port); break; } #endif @@ -658,7 +664,7 @@ static void Http_dns_cb(int Status, Dlist *addr_list, void *data) int SKey = VOIDP2INT(data); bool_t clean_up = TRUE; SocketData_t *S; - HostConnection_t *hc; + Server_t *srv; S = a_Klist_get_data(ValidSocks, SKey); if (S) { @@ -670,9 +676,10 @@ static void Http_dns_cb(int Status, Dlist *addr_list, void *data) /* Successful DNS answer; save the IP */ S->addr_list = addr_list; clean_up = FALSE; - hc = Http_host_connection_get(host); - Http_socket_enqueue(hc, S); - Http_connect_queued_sockets(hc); + srv = Http_server_get(host, S->connect_port, + (S->flags & HTTP_SOCKET_SSL)); + Http_socket_enqueue(srv, S); + Http_connect_queued_sockets(srv); } else { /* DNS wasn't able to resolve the hostname */ MSG_BW(S->web, 0, "ERROR: DNS can't resolve %s", host); @@ -734,15 +741,17 @@ static int Http_get(ChainLink *Info, void *Data1) /* * Can the old socket's fd be reused for the new socket? * - * NOTE: old and new must come from the same HostConnection_t. + * NOTE: old and new must come from the same Server_t. * This is not built to accept arbitrary sockets. */ static bool_t Http_socket_reuse_compatible(SocketData_t *old, SocketData_t *new) { + /* + * If we are using SSL through a proxy, we need to ensure that old and new + * are going through to the same host:port. + */ if (a_Web_valid(new->web) && - old->connect_port == new->connect_port && - ((old->flags & HTTP_SOCKET_SSL) == (new->flags & HTTP_SOCKET_SSL)) && ((old->flags & HTTP_SOCKET_SSL) == 0 || (old->flags & HTTP_SOCKET_USE_PROXY) == 0 || ((URL_PORT(old->url) == URL_PORT(new->url)) && @@ -760,11 +769,13 @@ static void Http_socket_reuse(int SKey) SocketData_t *new_sd, *old_sd = a_Klist_get_data(ValidSocks, SKey); if (old_sd) { - HostConnection_t *hc = Http_host_connection_get(old_sd->connected_to); - int i, n = dList_length(hc->queue); + Server_t *srv = Http_server_get(old_sd->connected_to, + old_sd->connect_port, + (old_sd->flags & HTTP_SOCKET_SSL)); + int i, n = dList_length(srv->queue); for (i = 0; i < n; i++) { - new_sd = dList_nth_data(hc->queue, i); + new_sd = dList_nth_data(srv->queue, i); if (!(new_sd->flags & HTTP_SOCKET_TO_BE_FREED) && Http_socket_reuse_compatible(old_sd, new_sd)) { @@ -773,11 +784,11 @@ static void Http_socket_reuse(int SKey) new_sd->SockFD = old_sd->SockFD; old_sd->connected_to = NULL; - hc->active_conns--; + srv->active_conns--; Http_socket_free(SKey); MSG("Reusing fd %d for %s\n", new_sd->SockFD,URL_STR(new_sd->url)); - Http_socket_activate(hc, new_sd); + Http_socket_activate(srv, new_sd); Http_fd_map_add_entry(new_sd); a_Http_connect_done(new_sd->SockFD, success); return; @@ -935,68 +946,71 @@ void a_Http_ccc(int Op, int Branch, int Dir, ChainLink *Info, * Add socket data to the queue. Pages/stylesheets/etc. have higher priority * than images. */ -static void Http_socket_enqueue(HostConnection_t *hc, SocketData_t* sock) +static void Http_socket_enqueue(Server_t *srv, SocketData_t* sock) { sock->flags |= HTTP_SOCKET_QUEUED; if ((sock->web->flags & WEB_Image) == 0) { - int i, n = dList_length(hc->queue); + int i, n = dList_length(srv->queue); for (i = 0; i < n; i++) { - SocketData_t *curr = dList_nth_data(hc->queue, i); + SocketData_t *curr = dList_nth_data(srv->queue, i); if (a_Web_valid(curr->web) && (curr->web->flags & WEB_Image)) { - dList_insert_pos(hc->queue, sock, i); + dList_insert_pos(srv->queue, sock, i); return; } } } - dList_append(hc->queue, sock); + dList_append(srv->queue, sock); } -static HostConnection_t *Http_host_connection_get(const char *host) +static Server_t *Http_server_get(const char *host, uint_t port, bool_t https) { int i; - HostConnection_t *hc; + Server_t *srv; - for (i = 0; i < dList_length(host_connections); i++) { - hc = (HostConnection_t*) dList_nth_data(host_connections, i); + for (i = 0; i < dList_length(servers); i++) { + srv = (Server_t*) dList_nth_data(servers, i); - if (dStrAsciiCasecmp(host, hc->host) == 0) - return hc; + if (port == srv->port && https == srv->https && + !dStrAsciiCasecmp(host, srv->host)) + return srv; } - hc = dNew0(HostConnection_t, 1); - hc->queue = dList_new(10); - hc->host = dStrdup(host); - dList_append(host_connections, hc); + srv = dNew0(Server_t, 1); + srv->queue = dList_new(10); + srv->host = dStrdup(host); + srv->port = port; + srv->https = https; + dList_append(servers, srv); - return hc; + return srv; } -static void Http_host_connection_remove(HostConnection_t *hc) +static void Http_server_remove(Server_t *srv) { - assert(dList_length(hc->queue) == 0); - dList_free(hc->queue); - dList_remove_fast(host_connections, hc); - dFree(hc->host); - dFree(hc); + assert(dList_length(srv->queue) == 0); + dList_free(srv->queue); + dList_remove_fast(servers, srv); + dFree(srv->host); + dFree(srv); } -static void Http_host_connection_remove_all() +static void Http_servers_remove_all() { - HostConnection_t *hc; + Server_t *srv; SocketData_t *sd; - while (dList_length(host_connections) > 0) { - hc = (HostConnection_t*) dList_nth_data(host_connections, 0); - while ((sd = dList_nth_data(hc->queue, 0))) { - dList_remove(hc->queue, sd); + while (dList_length(servers) > 0) { + srv = (Server_t*) dList_nth_data(servers, 0); + while ((sd = dList_nth_data(srv->queue, 0))) { + dList_remove(srv->queue, sd); dFree(sd); } - Http_host_connection_remove(hc); + Http_server_remove(srv); } - dList_free(host_connections); + dList_free(servers); } static void Http_fd_map_remove_all() @@ -1017,7 +1031,7 @@ static void Http_fd_map_remove_all() */ void a_Http_freeall(void) { - Http_host_connection_remove_all(); + Http_servers_remove_all(); Http_fd_map_remove_all(); a_Klist_free(&ValidSocks); a_Url_free(HTTP_Proxy); -- cgit v1.2.3 From ba68c40f16755a32bf70f49bdf1d39d86adc741e Mon Sep 17 00:00:00 2001 From: corvid Date: Thu, 28 May 2015 18:38:06 +0000 Subject: 'ssl' -> 'tls' where reasonable, given that ssl3 is dead and all I used 'hg rename' and expected (at least hoped) that 'hg diff' would do what I would naturally want, but no. --- AUTHORS | 2 +- configure.ac | 14 +- src/IO/IO.c | 10 +- src/IO/Makefile.am | 4 +- src/IO/http.c | 42 +- src/IO/ssl.c | 1110 ---------------------------------------------------- src/IO/ssl.h | 47 --- src/IO/tls.c | 1110 ++++++++++++++++++++++++++++++++++++++++++++++++++++ src/IO/tls.h | 47 +++ src/dillo.cc | 6 +- 10 files changed, 1196 insertions(+), 1196 deletions(-) delete mode 100644 src/IO/ssl.c delete mode 100644 src/IO/ssl.h create mode 100644 src/IO/tls.c create mode 100644 src/IO/tls.h (limited to 'src') diff --git a/AUTHORS b/AUTHORS index b22eb755..f92ad655 100644 --- a/AUTHORS +++ b/AUTHORS @@ -87,5 +87,5 @@ Non-Dillo code: * 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/ssl.c contains code from wget whose copyright is held by the +* src/IO/tls.c contains code from wget whose copyright is held by the Free Software Foundation. diff --git a/configure.ac b/configure.ac index b574f8d6..aee37a47 100644 --- a/configure.ac +++ b/configure.ac @@ -22,10 +22,10 @@ AC_ARG_ENABLE(gprof, [ --enable-gprof Try to compile and run with pro , 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 (ALPHA CODE)], +AC_ARG_ENABLE(ssl, [ --enable-ssl Enable SSL/HTTPS/TLS (EXPERIMENTAL CODE)], , 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 SSL], 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 SSL], CA_CERTS_DIR=$withval) +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) AC_ARG_ENABLE(ipv6, [ --enable-ipv6 Build with support for IPv6], , ) AC_ARG_ENABLE(cookies,[ --disable-cookies Don't compile support for cookies], , enable_cookies=yes) @@ -285,7 +285,7 @@ if test "x$enable_gif" = "xyes"; then fi dnl -------------------------- -dnl Test for support for SSL +dnl Test for support for SSL/TLS dnl -------------------------- dnl if test "x$enable_ssl" = "xyes"; then @@ -299,14 +299,14 @@ if test "x$enable_ssl" = "xyes"; then if test "x$ssl_ok" = "xyes"; then LIBSSL_LIBS="-lcrypto -lssl" - AC_MSG_WARN([*** Enabling ssl support. THIS IS ALPHA CODE!***]) + AC_MSG_WARN([*** Enabling SSL/HTTPS/TLS support. THIS IS EXPERIMENTAL CODE ***]) else - AC_MSG_WARN([*** No libssl found. Disabling ssl support.***]) + AC_MSG_WARN([*** No libssl found. Disabling SSL/HTTPS/TLS support. ***]) fi fi if test "x$ssl_ok" = "xyes"; then - AC_DEFINE([ENABLE_SSL], [1], [Enable SSL support]) + AC_DEFINE([ENABLE_SSL], [1], [Enable SSL/HTTPS/TLS support]) fi dnl -------------------------------------------------------------- diff --git a/src/IO/IO.c b/src/IO/IO.c index e5c5fc79..0cdb9499 100644 --- a/src/IO/IO.c +++ b/src/IO/IO.c @@ -21,7 +21,7 @@ #include "../klist.h" #include "IO.h" #include "iowatch.hh" -#include "ssl.h" +#include "tls.h" /* * Symbolic defines for shutdown() function @@ -163,7 +163,7 @@ static bool_t IO_read(IOData_t *io) ssize_t St; bool_t ret = FALSE; int io_key = io->Key; - void *conn = a_Ssl_connection(io->FD); + void *conn = a_Tls_connection(io->FD); _MSG(" IO_read\n"); @@ -172,7 +172,7 @@ static bool_t IO_read(IOData_t *io) io->Status = 0; while (1) { - St = conn ? a_Ssl_read(conn, Buf, IOBufLen) + St = conn ? a_Tls_read(conn, Buf, IOBufLen) : read(io->FD, Buf, IOBufLen); if (St > 0) { dStr_append_l(io->Buf, Buf, St); @@ -217,13 +217,13 @@ static bool_t IO_write(IOData_t *io) { ssize_t St; bool_t ret = FALSE; - void *conn = a_Ssl_connection(io->FD); + void *conn = a_Tls_connection(io->FD); _MSG(" IO_write\n"); io->Status = 0; while (1) { - St = conn ? a_Ssl_write(conn, io->Buf->str, io->Buf->len) + St = conn ? a_Tls_write(conn, io->Buf->str, io->Buf->len) : write(io->FD, io->Buf->str, io->Buf->len); if (St < 0) { /* Error */ diff --git a/src/IO/Makefile.am b/src/IO/Makefile.am index ff600521..d8fed40a 100644 --- a/src/IO/Makefile.am +++ b/src/IO/Makefile.am @@ -15,8 +15,8 @@ libDiof_a_SOURCES = \ about.c \ Url.h \ http.c \ - ssl.h \ - ssl.c \ + tls.h \ + tls.c \ dpi.c \ IO.c \ iowatch.cc \ diff --git a/src/IO/http.c b/src/IO/http.c index 22b2eaa6..7deab2e4 100644 --- a/src/IO/http.c +++ b/src/IO/http.c @@ -27,7 +27,7 @@ #include /* for inet_ntop */ #include "IO.h" -#include "ssl.h" +#include "tls.h" #include "Url.h" #include "../msg.h" #include "../klist.h" @@ -52,7 +52,7 @@ D_STMT_START { \ static const int HTTP_SOCKET_USE_PROXY = 0x1; static const int HTTP_SOCKET_QUEUED = 0x2; static const int HTTP_SOCKET_TO_BE_FREED = 0x4; -static const int HTTP_SOCKET_SSL = 0x8; +static const int HTTP_SOCKET_TLS = 0x8; /* 'web' is just a reference (no need to deallocate it here). */ typedef struct { @@ -251,16 +251,16 @@ static void Http_connect_queued_sockets(Server_t *srv) sd = dList_nth_data(srv->queue, i); if (!(sd->flags & HTTP_SOCKET_TO_BE_FREED)) { - int connect_ready = SSL_CONNECT_READY; + int connect_ready = TLS_CONNECT_READY; - if (sd->flags & HTTP_SOCKET_SSL) - connect_ready = a_Ssl_connect_ready(sd->url); + if (sd->flags & HTTP_SOCKET_TLS) + connect_ready = a_Tls_connect_ready(sd->url); - if (connect_ready == SSL_CONNECT_NEVER || !a_Web_valid(sd->web)) { + if (connect_ready == TLS_CONNECT_NEVER || !a_Web_valid(sd->web)) { int SKey = VOIDP2INT(sd->Info->LocalKey); Http_socket_free(SKey); - } else if (connect_ready == SSL_CONNECT_READY) { + } else if (connect_ready == TLS_CONNECT_READY) { i--; Http_socket_activate(srv, sd); Http_connect_socket(sd->Info); @@ -295,12 +295,12 @@ static void Http_socket_free(int SKey) } else { if (S->SockFD != -1) Http_fd_map_remove_entry(S->SockFD); - a_Ssl_reset_server_state(S->url); + a_Tls_reset_server_state(S->url); if (S->connected_to) { - a_Ssl_close_by_fd(S->SockFD); + a_Tls_close_by_fd(S->SockFD); Server_t *srv = Http_server_get(S->connected_to, S->connect_port, - (S->flags & HTTP_SOCKET_SSL)); + (S->flags & HTTP_SOCKET_TLS)); srv->active_conns--; Http_connect_queued_sockets(srv); if (srv->active_conns == 0) @@ -484,9 +484,9 @@ static void Http_send_query(SocketData_t *S) /* * Prepare an HTTPS connection. If necessary, tunnel it through a proxy. - * Then perform the SSL handshake. + * Then perform the TLS handshake. */ -static void Http_connect_ssl(ChainLink *info) +static void Http_connect_tls(ChainLink *info) { int SKey = VOIDP2INT(info->LocalKey); SocketData_t *S = a_Klist_get_data(ValidSocks, SKey); @@ -502,7 +502,7 @@ static void Http_connect_ssl(ChainLink *info) dFree(dbuf); dFree(connect_str); } else { - a_Ssl_handshake(S->SockFD, S->url); + a_Tls_handshake(S->SockFD, S->url); } } @@ -573,8 +573,8 @@ static void Http_connect_socket(ChainLink *Info) if (status == -1 && errno != EINPROGRESS) { MSG("Http_connect_socket ERROR: %s\n", dStrerror(errno)); a_Http_connect_done(S->SockFD, FALSE); - } else if (S->flags & HTTP_SOCKET_SSL) { - Http_connect_ssl(Info); + } else if (S->flags & HTTP_SOCKET_TLS) { + Http_connect_tls(Info); } else { a_Http_connect_done(S->SockFD, TRUE); } @@ -677,7 +677,7 @@ static void Http_dns_cb(int Status, Dlist *addr_list, void *data) S->addr_list = addr_list; clean_up = FALSE; srv = Http_server_get(host, S->connect_port, - (S->flags & HTTP_SOCKET_SSL)); + (S->flags & HTTP_SOCKET_TLS)); Http_socket_enqueue(srv, S); Http_connect_queued_sockets(srv); } else { @@ -725,7 +725,7 @@ static int Http_get(ChainLink *Info, void *Data1) S->connect_port = URL_PORT(url); S->url = a_Url_dup(S->web->url); if (!dStrAsciiCasecmp(URL_SCHEME(S->url), "https")) - S->flags |= HTTP_SOCKET_SSL; + S->flags |= HTTP_SOCKET_TLS; /* Let the user know what we'll do */ MSG_BW(S->web, 1, "DNS resolving %s", hostname); @@ -748,11 +748,11 @@ static bool_t Http_socket_reuse_compatible(SocketData_t *old, SocketData_t *new) { /* - * If we are using SSL through a proxy, we need to ensure that old and new + * If we are using TLS through a proxy, we need to ensure that old and new * are going through to the same host:port. */ if (a_Web_valid(new->web) && - ((old->flags & HTTP_SOCKET_SSL) == 0 || + ((old->flags & HTTP_SOCKET_TLS) == 0 || (old->flags & HTTP_SOCKET_USE_PROXY) == 0 || ((URL_PORT(old->url) == URL_PORT(new->url)) && !dStrAsciiCasecmp(URL_HOST(old->url), URL_HOST(new->url))))) @@ -771,7 +771,7 @@ static void Http_socket_reuse(int SKey) if (old_sd) { Server_t *srv = Http_server_get(old_sd->connected_to, old_sd->connect_port, - (old_sd->flags & HTTP_SOCKET_SSL)); + (old_sd->flags & HTTP_SOCKET_TLS)); int i, n = dList_length(srv->queue); for (i = 0; i < n; i++) { @@ -874,7 +874,7 @@ void a_Http_ccc(int Op, int Branch, int Dir, ChainLink *Info, sd->https_proxy_reply->str); dStr_free(sd->https_proxy_reply, 1); sd->https_proxy_reply = NULL; - a_Ssl_handshake(sd->SockFD, sd->url); + a_Tls_handshake(sd->SockFD, sd->url); } else { MSG_BW(sd->web, 1, "Can't connect through proxy to %s", URL_HOST(sd->url)); diff --git a/src/IO/ssl.c b/src/IO/ssl.c deleted file mode 100644 index 856d94b5..00000000 --- a/src/IO/ssl.c +++ /dev/null @@ -1,1110 +0,0 @@ -/* - * File: ssl.c - * - * Copyright 2004 Garrett Kajmowicz - * (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 - * (for the https code offered from dplus browser that formed the basis...) - * - * 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 */ - -/* - * Using TLS in Applications: http://datatracker.ietf.org/wg/uta/documents/ - * TLS: http://datatracker.ietf.org/wg/tls/documents/ - */ - -#include "config.h" -#include "../msg.h" - -#ifndef ENABLE_SSL - -void a_Ssl_init() -{ - MSG("SSL: Disabled at compilation time.\n"); -} - -#else - -#include - -#include -#include - -#include /* tolower for wget stuff */ -#include -#include -#include "../../dlib/dlib.h" -#include "../dialog.hh" -#include "../klist.h" -#include "iowatch.hh" -#include "ssl.h" -#include "Url.h" - -#include -#include -#include -#include /* for hostname checking */ - -#define CERT_STATUS_NONE 0 -#define CERT_STATUS_RECEIVING 1 -#define CERT_STATUS_GOOD 2 -#define CERT_STATUS_BAD 3 -#define CERT_STATUS_USER_ACCEPTED 4 - -typedef struct { - char *hostname; - int port; - int cert_status; -} Server_t; - -typedef struct { - int fd; - int connkey; -} FdMapEntry_t; - -/* - * Data type for SSL connection information - */ -typedef struct { - int fd; - DilloUrl *url; - SSL *ssl; - bool_t connecting; -} Conn_t; - -/* List of active SSL connections */ -static Klist_t *conn_list = NULL; - -/* - * If ssl_context is still NULL, this corresponds to SSL being disabled. - */ -static SSL_CTX *ssl_context; -static Dlist *servers; -static Dlist *fd_map; - -static void Ssl_connect_cb(int fd, void *vssl); - -/* - * Compare by FD. - */ -static int Ssl_fd_map_cmp(const void *v1, const void *v2) -{ - int fd = VOIDP2INT(v2); - const FdMapEntry_t *e = v1; - - return (fd != e->fd); -} - -static void Ssl_fd_map_add_entry(int fd, int connkey) -{ - FdMapEntry_t *e = dNew0(FdMapEntry_t, 1); - e->fd = fd; - e->connkey = connkey; - - if (dList_find_custom(fd_map, INT2VOIDP(e->fd), Ssl_fd_map_cmp)) { - MSG_ERR("SSL FD ENTRY ALREADY FOUND FOR %d\n", e->fd); - assert(0); - } - - dList_append(fd_map, e); -//MSG("ADD ENTRY %d %s\n", e->fd, URL_STR(sd->url)); -} - -/* - * Remove and free entry from fd_map. - */ -static void Ssl_fd_map_remove_entry(int fd) -{ - void *data = dList_find_custom(fd_map, INT2VOIDP(fd), Ssl_fd_map_cmp); - -//MSG("REMOVE ENTRY %d\n", fd); - if (data) { - dList_remove_fast(fd_map, data); - dFree(data); - } else { - MSG("SSL FD ENTRY NOT FOUND FOR %d\n", fd); - } -} - -/* - * Return SSL connection information for a given file - * descriptor, or NULL if no SSL connection was found. - */ -void *a_Ssl_connection(int fd) -{ - Conn_t *conn; - - if (fd_map) { - FdMapEntry_t *fme = dList_find_custom(fd_map, INT2VOIDP(fd), - Ssl_fd_map_cmp); - - if (fme && (conn = a_Klist_get_data(conn_list, fme->connkey))) - return conn; - } - return NULL; -} - -/* - * Add a new SSL connection information node. - */ -static int Ssl_conn_new(int fd, const DilloUrl *url, SSL *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; - - key = a_Klist_insert(&conn_list, conn); - - Ssl_fd_map_add_entry(fd, key); - - return key; -} - -/* - * Let's monitor for ssl alerts. - */ -static void Ssl_info_cb(const SSL *ssl, int where, int ret) -{ - if (where & SSL_CB_ALERT) { - MSG("SSL ALERT on %s: %s\n", (where & SSL_CB_READ) ? "read" : "write", - SSL_alert_desc_string_long(ret)); - } -} - -/* - * 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 Ssl_load_certificates() -{ - /* curl-7.37.1 says that the following bundle locations are used on "Debian - * systems", "Redhat and Mandriva", "old(er) Redhat", "FreeBSD", and - * "OpenBSD", respectively -- and that the /etc/ssl/certs/ path is needed on - * "SUSE". No doubt it's all changed some over time, but this gives us - * something to work with. - */ - uint_t u; - char *userpath; - static const char *ca_files[] = { - "/etc/ssl/certs/ca-certificates.crt", - "/etc/pki/tls/certs/ca-bundle.crt", - "/usr/share/ssl/certs/ca-bundle.crt", - "/usr/local/share/certs/ca-root.crt", - "/etc/ssl/cert.pem", - CA_CERTS_FILE - }; - - static const char *ca_paths[] = { - "/etc/ssl/certs/", - 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++) { - if (*ca_files[u]) - X509_LOOKUP_load_file(lookup, ca_files[u], X509_FILETYPE_PEM); - } - - 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); - } - - userpath = dStrconcat(dGethomedir(), "/.dillo/certs/", NULL); - X509_LOOKUP_add_dir(lookup, userpath, X509_FILETYPE_PEM); - dFree(userpath); - - /* Clear out errors in the queue (file not found, etc.) */ - while(ERR_get_error()) - ; -} - -/* - * Initialize the OpenSSL library. - */ -void a_Ssl_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; - } - - /* 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; - } - - SSL_CTX_set_info_callback(ssl_context, Ssl_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); - - Ssl_load_certificates(); - - fd_map = dList_new(20); - servers = dList_new(8); -} - -/* - * Save certificate with a hashed filename. - * Return: 0 on success, 1 on failure. - */ -static int Ssl_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; -} - -/* - * Test whether a URL corresponds to a server. - */ -static int Ssl_servers_cmp(const void *v1, const void *v2) -{ - Server_t *s = (Server_t *)v1; - const DilloUrl *url = (const DilloUrl *)v2; - const char *host = URL_HOST(url); - int port = URL_PORT(url); - - return (dStrAsciiCasecmp(s->hostname, host) || (port != s->port)); -} - -/* - * The purpose here is to permit a single initial connection to a server. - * Once we have the certificate, know whether we like it -- and whether the - * user accepts it -- HTTP can run through queued sockets as normal. - * - * Return: 1 means yes, 0 means not yet, -1 means never. - * TODO: Something clearer or different. - */ -int a_Ssl_connect_ready(const DilloUrl *url) -{ - Server_t *s; - int i, len; - const char *host = URL_HOST(url); - const int port = URL_PORT(url); - int ret = SSL_CONNECT_READY; - - dReturn_val_if_fail(ssl_context, SSL_CONNECT_NEVER); - - len = dList_length(servers); - - for (i = 0; i < len; i++) { - s = dList_nth_data(servers, i); - - if (!dStrAsciiCasecmp(s->hostname, host) && (port == s->port)) { - if (s->cert_status == CERT_STATUS_RECEIVING) - ret = SSL_CONNECT_NOT_YET; - else if (s->cert_status == CERT_STATUS_BAD) - ret = SSL_CONNECT_NEVER; - - if (s->cert_status == CERT_STATUS_NONE) - s->cert_status = CERT_STATUS_RECEIVING; - return ret; - } - } - s = dNew(Server_t, 1); - - s->port = port; - s->hostname = dStrdup(host); - s->cert_status = CERT_STATUS_RECEIVING; - dList_append(servers, s); - return ret; -} - -/* - * Did we find problems with the certificate, and did the user proceed to - * reject the connection? - */ -static int Ssl_user_said_no(const DilloUrl *url) -{ - Server_t *s = dList_find_custom(servers, url, Ssl_servers_cmp); - - if (!s) - return FALSE; - - return s->cert_status == CERT_STATUS_BAD; -} - -/* - * Did we find problems with the certificate, and did the user proceed to - * accept the connection anyway? - */ -static int Ssl_user_said_yes(const DilloUrl *url) -{ - Server_t *s = dList_find_custom(servers, url, Ssl_servers_cmp); - - if (!s) - return FALSE; - - return s->cert_status == CERT_STATUS_USER_ACCEPTED; -} - -/******************** 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 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) -{ - - 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'; -} - -static bool_t Ssl_check_cert_hostname(X509 *cert, const DilloUrl *url, - int *choice) -{ - dReturn_val_if_fail(cert && url, -1); - - char *msg; - const char *host = URL_HOST(url); - 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 */ - - /* 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; - } - } - 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; - } - 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; - msg = dStrconcat("No certificate subject alternative name matches" - " requested host name \n", host, NULL); - *choice = a_Dialog_choice("Dillo SSL security warning", - msg, "Continue", "Cancel", NULL); - dFree(msg); - - switch (*choice){ - case 1: - success = TRUE; - break; - case 2: - break; - default: - break; - } - } - } - - 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 SSL 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 SSL security warning", - msg, "Continue", "Cancel", NULL); - dFree(msg); - - switch (*choice){ - case 1: - success = TRUE; - break; - case 2: - break; - default: - break; - } - } - } - } - return success; -} - -/******************** END OF STUFF DERIVED FROM wget-1.16.3 */ - -/* - * Examine the certificate, and, if problems are detected, ask the user what - * to do. - * Return: -1 if connection should be canceled, or 0 if it should continue. - */ -static int Ssl_examine_certificate(SSL *ssl, const DilloUrl *url) -{ - X509 *remote_cert; - long st; - char buf[4096], *cn, *msg; - int choice = -1, ret = -1; - char *title = dStrconcat("Dillo SSL security warning: ",URL_HOST(url),NULL); - Server_t *srv = dList_find_custom(servers, url, Ssl_servers_cmp); - - remote_cert = SSL_get_peer_certificate(ssl); - if (remote_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.", - "Continue", "Cancel", NULL); - - /* Abort on anything but "Continue" */ - if (choice == 1){ - ret = 0; - } - - } else if (Ssl_check_cert_hostname(remote_cert, url, &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 here and recheck the chain */ - /* Potential security problems because we are writing - * to the filesystem */ - Ssl_save_certificate_home(remote_cert); - ret = 1; - 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); - - if (choice == 1) { - ret = 0; - } - break; - case X509_V_ERR_CERT_HAS_EXPIRED: - case X509_V_ERR_CRL_HAS_EXPIRED: - choice = a_Dialog_choice(title, - "The remote certificate has expired. The certificate " - "wasn't designed to last this long. You should avoid " - "this site.", - "Continue", "Cancel", NULL); - if (choice == 1) { - ret = 0; - } - 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: - choice = a_Dialog_choice(title, - "Self signed certificate in certificate chain. The certificate " - "chain could be built up using the untrusted certificates but the " - "root could not be found locally.", - "Continue", "Cancel", NULL); - if (choice == 1) { - ret = 0; - } - break; - case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY: - choice = a_Dialog_choice(title, - "Unable to get local issuer certificate. The issuer certificate " - "of an untrusted certificate cannot be found.", - "Continue", "Cancel", NULL); - if (choice == 1) { - ret = 0; - } - 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; - } - } - X509_free(remote_cert); - remote_cert = 0; - } - dFree(title); - - if (choice == 2) - srv->cert_status = CERT_STATUS_BAD; - else if (choice == -1) - srv->cert_status = CERT_STATUS_GOOD; - else - srv->cert_status = CERT_STATUS_USER_ACCEPTED; - - return ret; -} - -/* - * If the connection was closed before we got the certificate, we need to - * reset state so that we'll try again. - */ -void a_Ssl_reset_server_state(const DilloUrl *url) -{ - if (servers) { - Server_t *s = dList_find_custom(servers, url, Ssl_servers_cmp); - - if (s && s->cert_status == CERT_STATUS_RECEIVING) - s->cert_status = CERT_STATUS_NONE; - } -} - -/* - * Close an open SSL connection. - */ -static void Ssl_close_by_key(int connkey) -{ - Conn_t *c; - - if ((c = a_Klist_get_data(conn_list, connkey))) { - a_Ssl_reset_server_state(c->url); - if (c->connecting) { - a_IOwatch_remove_fd(c->fd, -1); - dClose(c->fd); - } - SSL_shutdown(c->ssl); - SSL_free(c->ssl); - - a_Url_free(c->url); - Ssl_fd_map_remove_entry(c->fd); - a_Klist_remove(conn_list, connkey); - dFree(c); - } -} - -/* - * Connect, set a callback if it's still not completed. If completed, check - * the certificate and report back to http. - */ -static void Ssl_connect(int fd, int connkey) -{ - int ret; - bool_t ongoing = FALSE, failed = TRUE; - Conn_t *conn; - - if (!(conn = a_Klist_get_data(conn_list, connkey))) { - MSG("Ssl_connect: conn for fd %d not valid\n", fd); - return; - } - - assert(!ERR_get_error()); - - 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; - - _MSG("iowatching fd %d for ssl -- want %s\n", fd, - err1_ret == SSL_ERROR_WANT_READ ? "read" : "write"); - a_IOwatch_remove_fd(fd, -1); - a_IOwatch_add_fd(fd, want, Ssl_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("SSL 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("SSL 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 { - MSG("SSL_get_error() returned %d on a connect.\n", err1_ret); - } - } else { - if (Ssl_user_said_yes(conn->url) || - (Ssl_examine_certificate(conn->ssl, conn->url) != -1)) - failed = FALSE; - } - - /* - * If there were problems with the certificate, the connection may have - * been closed by the server if the user responded too slowly to a popup. - */ - - if (!ongoing) { - if (a_Klist_get_data(conn_list, connkey)) { - conn->connecting = FALSE; - if (failed) { - Ssl_close_by_key(connkey); - } - a_IOwatch_remove_fd(fd, DIO_READ|DIO_WRITE); - a_Http_connect_done(fd, failed ? FALSE : TRUE); - } else { - MSG("Connection disappeared. Too long with a popup popped up?\n"); - } - } -} - -static void Ssl_connect_cb(int fd, void *vconnkey) -{ - Ssl_connect(fd, VOIDP2INT(vconnkey)); -} - -/* - * Perform the SSL handshake on an open socket. - */ -void a_Ssl_handshake(int fd, const DilloUrl *url) -{ - SSL *ssl; - bool_t success = TRUE; - int connkey = -1; - - if (!ssl_context) - success = FALSE; - - if (success && Ssl_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())); - success = FALSE; - } - - /* assign SSL 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) - connkey = Ssl_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) { - a_Ssl_reset_server_state(url); - a_Http_connect_done(fd, success); - } else { - Ssl_connect(fd, connkey); - } -} - -/* - * Read data from an open SSL connection. - */ -int a_Ssl_read(void *conn, void *buf, size_t len) -{ - Conn_t *c = (Conn_t*)conn; - return SSL_read(c->ssl, buf, len); -} - -/* - * Write data to an open SSL connection. - */ -int a_Ssl_write(void *conn, void *buf, size_t len) -{ - Conn_t *c = (Conn_t*)conn; - return SSL_write(c->ssl, buf, len); -} - -void a_Ssl_close_by_fd(int fd) -{ - FdMapEntry_t *fme = dList_find_custom(fd_map, INT2VOIDP(fd), - Ssl_fd_map_cmp); - - if (fme) { - Ssl_close_by_key(fme->connkey); - } -} - -static void Ssl_servers_freeall() -{ - if (servers) { - Server_t *s; - int i, n = dList_length(servers); - - for (i = 0; i < n; i++) { - s = (Server_t *) dList_nth_data(servers, i); - dFree(s->hostname); - dFree(s); - } - dList_free(servers); - } -} - -static void Ssl_fd_map_remove_all() -{ - if (fd_map) { - FdMapEntry_t *fme; - int i, n = dList_length(fd_map); - - for (i = 0; i < n; i++) { - fme = (FdMapEntry_t *) dList_nth_data(fd_map, i); - dFree(fme); - } - dList_free(fd_map); - } -} - -/* - * Clean up the OpenSSL library - */ -void a_Ssl_freeall(void) -{ - if (ssl_context) - SSL_CTX_free(ssl_context); - Ssl_fd_map_remove_all(); - Ssl_servers_freeall(); -} - -#endif /* ENABLE_SSL */ diff --git a/src/IO/ssl.h b/src/IO/ssl.h deleted file mode 100644 index f55479b2..00000000 --- a/src/IO/ssl.h +++ /dev/null @@ -1,47 +0,0 @@ -#ifndef __SSL_H__ -#define __SSL_H__ - -#ifdef __cplusplus -extern "C" { -#endif - -#include "../url.h" - -#define SSL_CONNECT_NEVER -1 -#define SSL_CONNECT_NOT_YET 0 -#define SSL_CONNECT_READY 1 - -void a_Ssl_init(); - - -#ifdef ENABLE_SSL -int a_Ssl_connect_ready(const DilloUrl *url); -void a_Ssl_reset_server_state(const DilloUrl *url); - -/* Use to initiate a SSL connection. */ -void a_Ssl_handshake(int fd, const DilloUrl *url); - -void *a_Ssl_connection(int fd); - -void a_Ssl_freeall(); - -void a_Ssl_close_by_fd(int fd); -int a_Ssl_read(void *conn, void *buf, size_t len); -int a_Ssl_write(void *conn, void *buf, size_t len); -#else - -#define a_Ssl_connect_ready(url) SSL_CONNECT_NEVER -#define a_Ssl_reset_server_state(url) ; -#define a_Ssl_handshake(fd, url) ; -#define a_Ssl_connection(fd) NULL -#define a_Ssl_freeall() ; -#define a_Ssl_close_by_fd(fd) ; -#define a_Ssl_read(conn, buf, len) 0 -#define a_Ssl_write(conn, buf, len) 0 -#endif -#ifdef __cplusplus -} -#endif - -#endif /* __SSL_H__ */ - diff --git a/src/IO/tls.c b/src/IO/tls.c new file mode 100644 index 00000000..39252635 --- /dev/null +++ b/src/IO/tls.c @@ -0,0 +1,1110 @@ +/* + * File: tls.c + * + * Copyright 2004 Garrett Kajmowicz + * (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 + * (for the https code offered from dplus browser that formed the basis...) + * + * 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 */ + +/* + * Using TLS in Applications: http://datatracker.ietf.org/wg/uta/documents/ + * TLS: http://datatracker.ietf.org/wg/tls/documents/ + */ + +#include "config.h" +#include "../msg.h" + +#ifndef ENABLE_SSL + +void a_Tls_init() +{ + MSG("TLS: Disabled at compilation time.\n"); +} + +#else + +#include + +#include +#include + +#include /* tolower for wget stuff */ +#include +#include +#include "../../dlib/dlib.h" +#include "../dialog.hh" +#include "../klist.h" +#include "iowatch.hh" +#include "tls.h" +#include "Url.h" + +#include +#include +#include +#include /* for hostname checking */ + +#define CERT_STATUS_NONE 0 +#define CERT_STATUS_RECEIVING 1 +#define CERT_STATUS_GOOD 2 +#define CERT_STATUS_BAD 3 +#define CERT_STATUS_USER_ACCEPTED 4 + +typedef struct { + char *hostname; + int port; + int cert_status; +} Server_t; + +typedef struct { + int fd; + int connkey; +} FdMapEntry_t; + +/* + * Data type for TLS connection information + */ +typedef struct { + int fd; + DilloUrl *url; + SSL *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 Dlist *servers; +static Dlist *fd_map; + +static void Tls_connect_cb(int fd, void *vconnkey); + +/* + * Compare by FD. + */ +static int Tls_fd_map_cmp(const void *v1, const void *v2) +{ + int fd = VOIDP2INT(v2); + const FdMapEntry_t *e = v1; + + return (fd != e->fd); +} + +static void Tls_fd_map_add_entry(int fd, int connkey) +{ + FdMapEntry_t *e = dNew0(FdMapEntry_t, 1); + e->fd = fd; + e->connkey = connkey; + + if (dList_find_custom(fd_map, INT2VOIDP(e->fd), Tls_fd_map_cmp)) { + MSG_ERR("TLS FD ENTRY ALREADY FOUND FOR %d\n", e->fd); + assert(0); + } + + dList_append(fd_map, e); +//MSG("ADD ENTRY %d %s\n", e->fd, URL_STR(sd->url)); +} + +/* + * Remove and free entry from fd_map. + */ +static void Tls_fd_map_remove_entry(int fd) +{ + void *data = dList_find_custom(fd_map, INT2VOIDP(fd), Tls_fd_map_cmp); + +//MSG("REMOVE ENTRY %d\n", fd); + if (data) { + dList_remove_fast(fd_map, data); + dFree(data); + } else { + MSG("TLS FD ENTRY NOT FOUND FOR %d\n", fd); + } +} + +/* + * Return TLS connection information for a given file + * descriptor, or NULL if no TLS connection was found. + */ +void *a_Tls_connection(int fd) +{ + Conn_t *conn; + + if (fd_map) { + FdMapEntry_t *fme = dList_find_custom(fd_map, INT2VOIDP(fd), + Tls_fd_map_cmp); + + if (fme && (conn = a_Klist_get_data(conn_list, fme->connkey))) + return conn; + } + return NULL; +} + +/* + * Add a new TLS connection information node. + */ +static int Tls_conn_new(int fd, const DilloUrl *url, SSL *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; + + key = a_Klist_insert(&conn_list, conn); + + Tls_fd_map_add_entry(fd, key); + + return key; +} + +/* + * Let's monitor for TLS alerts. + */ +static void Tls_info_cb(const SSL *ssl, int where, int ret) +{ + if (where & SSL_CB_ALERT) { + MSG("TLS ALERT on %s: %s\n", (where & SSL_CB_READ) ? "read" : "write", + SSL_alert_desc_string_long(ret)); + } +} + +/* + * 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() +{ + /* curl-7.37.1 says that the following bundle locations are used on "Debian + * systems", "Redhat and Mandriva", "old(er) Redhat", "FreeBSD", and + * "OpenBSD", respectively -- and that the /etc/ssl/certs/ path is needed on + * "SUSE". No doubt it's all changed some over time, but this gives us + * something to work with. + */ + uint_t u; + char *userpath; + static const char *ca_files[] = { + "/etc/ssl/certs/ca-certificates.crt", + "/etc/pki/tls/certs/ca-bundle.crt", + "/usr/share/ssl/certs/ca-bundle.crt", + "/usr/local/share/certs/ca-root.crt", + "/etc/ssl/cert.pem", + CA_CERTS_FILE + }; + + static const char *ca_paths[] = { + "/etc/ssl/certs/", + 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++) { + if (*ca_files[u]) + X509_LOOKUP_load_file(lookup, ca_files[u], X509_FILETYPE_PEM); + } + + 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); + } + + userpath = dStrconcat(dGethomedir(), "/.dillo/certs/", NULL); + X509_LOOKUP_add_dir(lookup, userpath, X509_FILETYPE_PEM); + dFree(userpath); + + /* Clear out errors in the queue (file not found, etc.) */ + while(ERR_get_error()) + ; +} + +/* + * Initialize the OpenSSL 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; + } + + /* 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; + } + + 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(); + + 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); + + 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; +} + +/* + * Test whether a URL corresponds to a server. + */ +static int Tls_servers_cmp(const void *v1, const void *v2) +{ + Server_t *s = (Server_t *)v1; + const DilloUrl *url = (const DilloUrl *)v2; + const char *host = URL_HOST(url); + int port = URL_PORT(url); + + return (dStrAsciiCasecmp(s->hostname, host) || (port != s->port)); +} + +/* + * The purpose here is to permit a single initial connection to a server. + * Once we have the certificate, know whether we like it -- and whether the + * user accepts it -- HTTP can run through queued sockets as normal. + * + * Return: 1 means yes, 0 means not yet, -1 means never. + * TODO: Something clearer or different. + */ +int a_Tls_connect_ready(const DilloUrl *url) +{ + Server_t *s; + int i, len; + const char *host = URL_HOST(url); + const int port = URL_PORT(url); + int ret = TLS_CONNECT_READY; + + dReturn_val_if_fail(ssl_context, TLS_CONNECT_NEVER); + + len = dList_length(servers); + + for (i = 0; i < len; i++) { + s = dList_nth_data(servers, i); + + if (!dStrAsciiCasecmp(s->hostname, host) && (port == s->port)) { + if (s->cert_status == CERT_STATUS_RECEIVING) + ret = TLS_CONNECT_NOT_YET; + else if (s->cert_status == CERT_STATUS_BAD) + ret = TLS_CONNECT_NEVER; + + if (s->cert_status == CERT_STATUS_NONE) + s->cert_status = CERT_STATUS_RECEIVING; + return ret; + } + } + s = dNew(Server_t, 1); + + s->port = port; + s->hostname = dStrdup(host); + s->cert_status = CERT_STATUS_RECEIVING; + dList_append(servers, s); + return ret; +} + +/* + * Did we find problems with the certificate, and did the user proceed to + * reject the connection? + */ +static int Tls_user_said_no(const DilloUrl *url) +{ + Server_t *s = dList_find_custom(servers, url, Tls_servers_cmp); + + if (!s) + return FALSE; + + return s->cert_status == CERT_STATUS_BAD; +} + +/* + * Did we find problems with the certificate, and did the user proceed to + * accept the connection anyway? + */ +static int Tls_user_said_yes(const DilloUrl *url) +{ + Server_t *s = dList_find_custom(servers, url, Tls_servers_cmp); + + if (!s) + return FALSE; + + return s->cert_status == CERT_STATUS_USER_ACCEPTED; +} + +/******************** 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 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) +{ + + 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'; +} + +static bool_t Tls_check_cert_hostname(X509 *cert, const DilloUrl *url, + int *choice) +{ + dReturn_val_if_fail(cert && url, -1); + + char *msg; + const char *host = URL_HOST(url); + 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 */ + + /* 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; + } + } + 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; + } + 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; + msg = dStrconcat("No certificate subject alternative name matches" + " requested host name \n", 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; + } + } + } + + 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; +} + +/******************** END OF STUFF DERIVED FROM wget-1.16.3 */ + +/* + * Examine the certificate, and, if problems are detected, ask the user what + * to do. + * Return: -1 if connection should be canceled, or 0 if it should continue. + */ +static int Tls_examine_certificate(SSL *ssl, const DilloUrl *url) +{ + X509 *remote_cert; + long st; + char buf[4096], *cn, *msg; + int choice = -1, ret = -1; + char *title = dStrconcat("Dillo TLS security warning: ",URL_HOST(url),NULL); + Server_t *srv = dList_find_custom(servers, url, Tls_servers_cmp); + + remote_cert = SSL_get_peer_certificate(ssl); + if (remote_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.", + "Continue", "Cancel", NULL); + + /* Abort on anything but "Continue" */ + if (choice == 1){ + ret = 0; + } + + } else if (Tls_check_cert_hostname(remote_cert, url, &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 here and recheck the chain */ + /* Potential security problems because we are writing + * to the filesystem */ + Tls_save_certificate_home(remote_cert); + ret = 1; + 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); + + if (choice == 1) { + ret = 0; + } + break; + case X509_V_ERR_CERT_HAS_EXPIRED: + case X509_V_ERR_CRL_HAS_EXPIRED: + choice = a_Dialog_choice(title, + "The remote certificate has expired. The certificate " + "wasn't designed to last this long. You should avoid " + "this site.", + "Continue", "Cancel", NULL); + if (choice == 1) { + ret = 0; + } + 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: + choice = a_Dialog_choice(title, + "Self signed certificate in certificate chain. The certificate " + "chain could be built up using the untrusted certificates but the " + "root could not be found locally.", + "Continue", "Cancel", NULL); + if (choice == 1) { + ret = 0; + } + break; + case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY: + choice = a_Dialog_choice(title, + "Unable to get local issuer certificate. The issuer certificate " + "of an untrusted certificate cannot be found.", + "Continue", "Cancel", NULL); + if (choice == 1) { + ret = 0; + } + 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; + } + } + X509_free(remote_cert); + remote_cert = 0; + } + dFree(title); + + if (choice == 2) + srv->cert_status = CERT_STATUS_BAD; + else if (choice == -1) + srv->cert_status = CERT_STATUS_GOOD; + else + srv->cert_status = CERT_STATUS_USER_ACCEPTED; + + return ret; +} + +/* + * If the connection was closed before we got the certificate, we need to + * reset state so that we'll try again. + */ +void a_Tls_reset_server_state(const DilloUrl *url) +{ + if (servers) { + Server_t *s = dList_find_custom(servers, url, Tls_servers_cmp); + + if (s && s->cert_status == CERT_STATUS_RECEIVING) + s->cert_status = CERT_STATUS_NONE; + } +} + +/* + * Close an open TLS connection. + */ +static void Tls_close_by_key(int connkey) +{ + Conn_t *c; + + if ((c = a_Klist_get_data(conn_list, connkey))) { + a_Tls_reset_server_state(c->url); + if (c->connecting) { + a_IOwatch_remove_fd(c->fd, -1); + dClose(c->fd); + } + SSL_shutdown(c->ssl); + SSL_free(c->ssl); + + a_Url_free(c->url); + Tls_fd_map_remove_entry(c->fd); + a_Klist_remove(conn_list, connkey); + dFree(c); + } +} + +/* + * Connect, set a callback if it's still not completed. If completed, check + * the certificate and report back to http. + */ +static void Tls_connect(int fd, int connkey) +{ + int ret; + bool_t ongoing = FALSE, failed = TRUE; + Conn_t *conn; + + if (!(conn = a_Klist_get_data(conn_list, connkey))) { + MSG("Tls_connect: conn for fd %d not valid\n", fd); + return; + } + + assert(!ERR_get_error()); + + 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; + + _MSG("iowatching fd %d for tls -- want %s\n", fd, + err1_ret == SSL_ERROR_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 { + MSG("SSL_get_error() returned %d on a connect.\n", err1_ret); + } + } else { + if (Tls_user_said_yes(conn->url) || + (Tls_examine_certificate(conn->ssl, conn->url) != -1)) + failed = FALSE; + } + + /* + * If there were problems with the certificate, the connection may have + * been closed by the server if the user responded too slowly to a popup. + */ + + if (!ongoing) { + if (a_Klist_get_data(conn_list, connkey)) { + conn->connecting = FALSE; + if (failed) { + Tls_close_by_key(connkey); + } + a_IOwatch_remove_fd(fd, DIO_READ|DIO_WRITE); + a_Http_connect_done(fd, failed ? FALSE : TRUE); + } else { + MSG("Connection disappeared. Too long with a popup popped up?\n"); + } + } +} + +static void Tls_connect_cb(int fd, void *vconnkey) +{ + Tls_connect(fd, VOIDP2INT(vconnkey)); +} + +/* + * Perform the TLS handshake on an open socket. + */ +void a_Tls_handshake(int fd, const DilloUrl *url) +{ + SSL *ssl; + bool_t success = TRUE; + int connkey = -1; + + if (!ssl_context) + 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())); + 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) + 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) { + a_Tls_reset_server_state(url); + a_Http_connect_done(fd, success); + } else { + Tls_connect(fd, connkey); + } +} + +/* + * Read data from an open TLS connection. + */ +int a_Tls_read(void *conn, void *buf, size_t len) +{ + Conn_t *c = (Conn_t*)conn; + return SSL_read(c->ssl, buf, len); +} + +/* + * Write data to an open TLS connection. + */ +int a_Tls_write(void *conn, void *buf, size_t len) +{ + Conn_t *c = (Conn_t*)conn; + return SSL_write(c->ssl, buf, len); +} + +void a_Tls_close_by_fd(int fd) +{ + FdMapEntry_t *fme = dList_find_custom(fd_map, INT2VOIDP(fd), + Tls_fd_map_cmp); + + if (fme) { + Tls_close_by_key(fme->connkey); + } +} + +static void Tls_servers_freeall() +{ + if (servers) { + Server_t *s; + int i, n = dList_length(servers); + + for (i = 0; i < n; i++) { + s = (Server_t *) dList_nth_data(servers, i); + dFree(s->hostname); + dFree(s); + } + dList_free(servers); + } +} + +static void Tls_fd_map_remove_all() +{ + if (fd_map) { + FdMapEntry_t *fme; + int i, n = dList_length(fd_map); + + for (i = 0; i < n; i++) { + fme = (FdMapEntry_t *) dList_nth_data(fd_map, i); + dFree(fme); + } + dList_free(fd_map); + } +} + +/* + * Clean up the OpenSSL library + */ +void a_Tls_freeall(void) +{ + if (ssl_context) + SSL_CTX_free(ssl_context); + Tls_fd_map_remove_all(); + Tls_servers_freeall(); +} + +#endif /* ENABLE_SSL */ diff --git a/src/IO/tls.h b/src/IO/tls.h new file mode 100644 index 00000000..e3892cb2 --- /dev/null +++ b/src/IO/tls.h @@ -0,0 +1,47 @@ +#ifndef __TLS_H__ +#define __TLS_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +#include "../url.h" + +#define TLS_CONNECT_NEVER -1 +#define TLS_CONNECT_NOT_YET 0 +#define TLS_CONNECT_READY 1 + +void a_Tls_init(); + + +#ifdef ENABLE_SSL +int a_Tls_connect_ready(const DilloUrl *url); +void a_Tls_reset_server_state(const DilloUrl *url); + +/* Use to initiate a TLS connection. */ +void a_Tls_handshake(int fd, const DilloUrl *url); + +void *a_Tls_connection(int fd); + +void a_Tls_freeall(); + +void a_Tls_close_by_fd(int fd); +int a_Tls_read(void *conn, void *buf, size_t len); +int a_Tls_write(void *conn, void *buf, size_t len); +#else + +#define a_Tls_connect_ready(url) TLS_CONNECT_NEVER +#define a_Tls_reset_server_state(url) ; +#define a_Tls_handshake(fd, url) ; +#define a_Tls_connection(fd) NULL +#define a_Tls_freeall() ; +#define a_Tls_close_by_fd(fd) ; +#define a_Tls_read(conn, buf, len) 0 +#define a_Tls_write(conn, buf, len) 0 +#endif +#ifdef __cplusplus +} +#endif + +#endif /* __TLS_H__ */ + diff --git a/src/dillo.cc b/src/dillo.cc index 2bfab238..6e28f155 100644 --- a/src/dillo.cc +++ b/src/dillo.cc @@ -45,7 +45,7 @@ #include "dns.h" #include "web.hh" -#include "IO/ssl.h" +#include "IO/tls.h" #include "IO/Url.h" #include "IO/mime.h" #include "capi.h" @@ -477,7 +477,7 @@ int main(int argc, char **argv) a_Dns_init(); a_Web_init(); a_Http_init(); - a_Ssl_init(); + a_Tls_init(); a_Mime_init(); a_Capi_init(); a_Dicache_init(); @@ -599,7 +599,7 @@ int main(int argc, char **argv) a_Cache_freeall(); a_Dicache_freeall(); a_Http_freeall(); - a_Ssl_freeall(); + a_Tls_freeall(); a_Dns_freeall(); a_History_freeall(); a_Prefs_freeall(); -- cgit v1.2.3 From 1970b812237a507a95394be1f31d3caa3242020b Mon Sep 17 00:00:00 2001 From: corvid Date: Fri, 29 May 2015 21:47:28 +0000 Subject: some more information for TLS warning popups --- src/IO/tls.c | 85 ++++++++++++++++++++++++++++++++++++++++++++++------------- src/dialog.cc | 20 +++++++------- 2 files changed, 77 insertions(+), 28 deletions(-) (limited to 'src') diff --git a/src/IO/tls.c b/src/IO/tls.c index 39252635..b16c2ed1 100644 --- a/src/IO/tls.c +++ b/src/IO/tls.c @@ -493,6 +493,10 @@ static bool_t Tls_check_cert_hostname(X509 *cert, const DilloUrl *url, { /* 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); @@ -516,6 +520,7 @@ static bool_t Tls_check_cert_hostname(X509 *cert, const DilloUrl *url, 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) @@ -537,6 +542,7 @@ static bool_t Tls_check_cert_hostname(X509 *cert, const DilloUrl *url, OPENSSL_free (name_in_utf8); break; } + dStr_sprintfa(err, "%s ", name_in_utf8); OPENSSL_free (name_in_utf8); } } @@ -549,11 +555,8 @@ static bool_t Tls_check_cert_hostname(X509 *cert, const DilloUrl *url, if (alt_name_checked == TRUE && i >= numaltnames) { success = FALSE; - msg = dStrconcat("No certificate subject alternative name matches" - " requested host name \n", host, NULL); *choice = a_Dialog_choice("Dillo TLS security warning", - msg, "Continue", "Cancel", NULL); - dFree(msg); + err->str, "Continue", "Cancel", NULL); switch (*choice){ case 1: @@ -565,6 +568,7 @@ static bool_t Tls_check_cert_hostname(X509 *cert, const DilloUrl *url, break; } } + dStr_free(err, 1); } if (alt_name_checked == FALSE) @@ -647,6 +651,46 @@ static bool_t Tls_check_cert_hostname(X509 *cert, const DilloUrl *url, /******************** 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. + */ +static X509 *Tls_get_end_of_chain(SSL *ssl) +{ + STACK_OF(X509) *sk = SSL_get_peer_cert_chain(ssl); + + return sk ? sk_X509_value(sk, sk_X509_num(sk) - 1) : NULL; +} + +static void Tls_get_issuer_name(X509 *cert, char *buf, uint_t buflen) +{ + if (cert) { + X509_NAME_oneline(X509_get_issuer_name(cert), buf, buflen); + } else { + strncpy(buf, "(unknown)", buflen); + buf[buflen-1] = '\0'; + } +} + +static void Tls_get_expiration_str(X509 *cert, char *buf, uint_t buflen) +{ + ASN1_TIME *exp_date = X509_get_notAfter(cert); + BIO *b = BIO_new(BIO_s_mem()); + int rc = ASN1_TIME_print(b, exp_date); + + if (rc > 0) { + rc = BIO_gets(b, buf, buflen); + } + if (rc <= 0) { + strncpy(buf, "(unknown)", buflen); + buf[buflen-1] = '\0'; + } + BIO_free(b); +} + /* * Examine the certificate, and, if problems are detected, ask the user what * to do. @@ -656,7 +700,8 @@ static int Tls_examine_certificate(SSL *ssl, const DilloUrl *url) { X509 *remote_cert; long st; - char buf[4096], *cn, *msg; + const uint_t buflen = 4096; + char buf[buflen], *cn, *msg; int choice = -1, ret = -1; char *title = dStrconcat("Dillo TLS security warning: ",URL_HOST(url),NULL); Server_t *srv = dList_find_custom(servers, url, Tls_servers_cmp); @@ -761,14 +806,15 @@ static int Tls_examine_certificate(SSL *ssl, const DilloUrl *url) break; case X509_V_ERR_CERT_HAS_EXPIRED: case X509_V_ERR_CRL_HAS_EXPIRED: - choice = a_Dialog_choice(title, - "The remote certificate has expired. The certificate " - "wasn't designed to last this long. You should avoid " - "this site.", - "Continue", "Cancel", NULL); + 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: @@ -812,23 +858,24 @@ static int Tls_examine_certificate(SSL *ssl, const DilloUrl *url) } break; case X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN: - choice = a_Dialog_choice(title, - "Self signed certificate in certificate chain. The certificate " - "chain could be built up using the untrusted certificates but the " - "root could not be found locally.", - "Continue", "Cancel", NULL); + 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: - choice = a_Dialog_choice(title, - "Unable to get local issuer certificate. The issuer certificate " - "of an untrusted certificate cannot be found.", - "Continue", "Cancel", NULL); + 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, diff --git a/src/dialog.cc b/src/dialog.cc index 10988c98..03949a1c 100644 --- a/src/dialog.cc +++ b/src/dialog.cc @@ -325,6 +325,7 @@ static void choice_cb(Fl_Widget *button, void *number) { choice_answer = VOIDP2INT(number); _MSG("choice_cb: %d\n", choice_answer); + button->window()->hide(); } @@ -358,16 +359,15 @@ int a_Dialog_choice(const char *title, const char *msg, ...) Fl_Window *window = new Fl_Window(ww, wh, title); window->set_modal(); window->begin(); - Fl_Group *ib = new Fl_Group(0, 0, window->w(), window->h()); - ib->begin(); - window->resizable(ib); - if (msg != NULL){ - Fl_Box *box = new Fl_Box(0, 0, ww, wh - bh, msg); - box->labelfont(FL_HELVETICA); - box->labelsize(14); - box->align(FL_ALIGN_WRAP); - } + Fl_Text_Buffer *buf = new Fl_Text_Buffer(); + buf->text(msg); + Fl_Text_Display *td = new Fl_Text_Display(0, 0, ww, wh - bh); + td->buffer(buf); + td->textsize((int) rint(14.0 * prefs.font_factor)); + td->wrap_mode(Fl_Text_Display::WRAP_AT_BOUNDS, 0); + + window->resizable(td); int xpos = gap; va_start(ap, msg); @@ -386,6 +386,8 @@ int a_Dialog_choice(const char *title, const char *msg, ...) while (window->shown()) Fl::wait(); _MSG("Dialog_choice answer = %d\n", answer); + td->buffer(NULL); + delete buf; delete window; return choice_answer; -- cgit v1.2.3 From 5b317d03a17331f400d93fb957216030a8c86fd2 Mon Sep 17 00:00:00 2001 From: corvid Date: Fri, 29 May 2015 22:14:32 +0000 Subject: documentation and not-currently-possible error case --- src/IO/tls.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/IO/tls.c b/src/IO/tls.c index b16c2ed1..dbdb1c92 100644 --- a/src/IO/tls.c +++ b/src/IO/tls.c @@ -466,10 +466,16 @@ static bool_t pattern_match (const char *pattern, const char *string) return *n == '\0'; } +/* + * 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. + */ static bool_t Tls_check_cert_hostname(X509 *cert, const DilloUrl *url, int *choice) { - dReturn_val_if_fail(cert && url, -1); + dReturn_val_if_fail(cert && url, FALSE); char *msg; const char *host = URL_HOST(url); -- cgit v1.2.3 From 3d8b68e1bfde841e6d994d830dbc922256a6b7cf Mon Sep 17 00:00:00 2001 From: corvid Date: Fri, 29 May 2015 23:57:53 +0000 Subject: print out TLS version and cipher agreed upon after first connection with server --- src/IO/tls.c | 42 +++++++++++++++++++----------------------- 1 file changed, 19 insertions(+), 23 deletions(-) (limited to 'src') diff --git a/src/IO/tls.c b/src/IO/tls.c index dbdb1c92..bab36644 100644 --- a/src/IO/tls.c +++ b/src/IO/tls.c @@ -407,20 +407,6 @@ static int Tls_user_said_no(const DilloUrl *url) return s->cert_status == CERT_STATUS_BAD; } -/* - * Did we find problems with the certificate, and did the user proceed to - * accept the connection anyway? - */ -static int Tls_user_said_yes(const DilloUrl *url) -{ - Server_t *s = dList_find_custom(servers, url, Tls_servers_cmp); - - if (!s) - return FALSE; - - return s->cert_status == CERT_STATUS_USER_ACCEPTED; -} - /******************** BEGINNING OF STUFF DERIVED FROM wget-1.16.3 */ #define ASTERISK_EXCLUDES_DOT /* mandated by rfc2818 */ @@ -472,13 +458,12 @@ static bool_t pattern_match (const char *pattern, const char *string) * Return TRUE if the hostname matched or the user indicated acceptance. * FALSE on failure. */ -static bool_t Tls_check_cert_hostname(X509 *cert, const DilloUrl *url, +static bool_t Tls_check_cert_hostname(X509 *cert, const char *host, int *choice) { - dReturn_val_if_fail(cert && url, FALSE); + dReturn_val_if_fail(cert && host, FALSE); char *msg; - const char *host = URL_HOST(url); GENERAL_NAMES *subjectAltNames; bool_t success = TRUE, alt_name_checked = FALSE;; char common_name[256]; @@ -702,15 +687,14 @@ 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, const DilloUrl *url) +static int Tls_examine_certificate(SSL *ssl, Server_t *srv,const char *host) { X509 *remote_cert; long st; const uint_t buflen = 4096; char buf[buflen], *cn, *msg; int choice = -1, ret = -1; - char *title = dStrconcat("Dillo TLS security warning: ",URL_HOST(url),NULL); - Server_t *srv = dList_find_custom(servers, url, Tls_servers_cmp); + char *title = dStrconcat("Dillo TLS security warning: ", host, NULL); remote_cert = SSL_get_peer_certificate(ssl); if (remote_cert == NULL){ @@ -725,7 +709,7 @@ static int Tls_examine_certificate(SSL *ssl, const DilloUrl *url) ret = 0; } - } else if (Tls_check_cert_hostname(remote_cert, url, &choice)) { + } else if (Tls_check_cert_hostname(remote_cert, host, &choice)) { /* Figure out if (and why) the remote system can't be trusted */ st = SSL_get_verify_result(ssl); switch (st) { @@ -1009,9 +993,21 @@ static void Tls_connect(int fd, int connkey) MSG("SSL_get_error() returned %d on a connect.\n", err1_ret); } } else { - if (Tls_user_said_yes(conn->url) || - (Tls_examine_certificate(conn->ssl, conn->url) != -1)) + Server_t *srv = dList_find_custom(servers, conn->url, Tls_servers_cmp); + + if (srv->cert_status == CERT_STATUS_RECEIVING) { + /* Making first connection with the server */ + const char *version = SSL_get_version(conn->ssl); + const SSL_CIPHER *cipher = SSL_get_current_cipher(conn->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, URL_HOST(conn->url))!=-1)) { failed = FALSE; + } } /* -- cgit v1.2.3 From d11a250d5739d106215d66700af3fc530566b604 Mon Sep 17 00:00:00 2001 From: corvid Date: Sat, 30 May 2015 00:07:13 +0000 Subject: let's not print tls alerts for 'close notify' --- src/IO/tls.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/IO/tls.c b/src/IO/tls.c index bab36644..2be2ebc0 100644 --- a/src/IO/tls.c +++ b/src/IO/tls.c @@ -185,8 +185,11 @@ static int Tls_conn_new(int fd, const DilloUrl *url, SSL *ssl) static void Tls_info_cb(const SSL *ssl, int where, int ret) { if (where & SSL_CB_ALERT) { - MSG("TLS ALERT on %s: %s\n", (where & SSL_CB_READ) ? "read" : "write", - SSL_alert_desc_string_long(ret)); + const char *str = SSL_alert_desc_string_long(ret); + + if (strcmp(str, "close notify")) + MSG("TLS ALERT on %s: %s\n", (where & SSL_CB_READ) ? "read" : "write", + str); } } -- cgit v1.2.3 From 608d1a61b202814080d680f9a1c33e72a926ae8c Mon Sep 17 00:00:00 2001 From: corvid Date: Sat, 30 May 2015 16:42:37 +0000 Subject: print certificate chain --- src/IO/tls.c | 48 +++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 45 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/IO/tls.c b/src/IO/tls.c index 2be2ebc0..9368d563 100644 --- a/src/IO/tls.c +++ b/src/IO/tls.c @@ -932,6 +932,46 @@ static void Tls_close_by_key(int connkey) } } +static void Tls_print_cert_chain(SSL *ssl) +{ + STACK_OF(X509) *sk = SSL_get_peer_cert_chain(ssl); + + if (sk) { + const uint_t buflen = 4096; + char buf[buflen]; + int i, n = sk_X509_num(sk); + X509 *cert; + EVP_PKEY *public_key; + int key_type, key_bits; + const char *type_str; + + for (i = 0; i < n; i++) { + cert = sk_X509_value(sk, i); + public_key = X509_get_pubkey(cert); + 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'; + 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) { + /* TODO: Gather warnings into one popup. */ + MSG_WARN("In 2014/5, browsers have been deprecating 1024-bit RSA " + "keys.\n"); + } + } + + X509_NAME_oneline(X509_get_issuer_name(cert), buf, buflen); + buf[buflen-1] = '\0'; + MSG("root: %s\n", buf); + } +} + /* * Connect, set a callback if it's still not completed. If completed, check * the certificate and report back to http. @@ -999,12 +1039,14 @@ static void Tls_connect(int fd, int connkey) Server_t *srv = dList_find_custom(servers, conn->url, Tls_servers_cmp); if (srv->cert_status == CERT_STATUS_RECEIVING) { - /* Making first connection with the server */ - const char *version = SSL_get_version(conn->ssl); - const SSL_CIPHER *cipher = SSL_get_current_cipher(conn->ssl); + /* Making first connection with the server. Show some information. */ + 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)); + Tls_print_cert_chain(ssl); } if (srv->cert_status == CERT_STATUS_USER_ACCEPTED || -- cgit v1.2.3 From 7bc101f0e1c65b688715bec4d1917a358ebd0d75 Mon Sep 17 00:00:00 2001 From: corvid Date: Sat, 30 May 2015 22:37:38 +0000 Subject: fix warning --- src/IO/tls.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/IO/tls.c b/src/IO/tls.c index 9368d563..6355f4f5 100644 --- a/src/IO/tls.c +++ b/src/IO/tls.c @@ -940,7 +940,7 @@ static void Tls_print_cert_chain(SSL *ssl) const uint_t buflen = 4096; char buf[buflen]; int i, n = sk_X509_num(sk); - X509 *cert; + X509 *cert = NULL; EVP_PKEY *public_key; int key_type, key_bits; const char *type_str; @@ -966,9 +966,11 @@ static void Tls_print_cert_chain(SSL *ssl) } } - X509_NAME_oneline(X509_get_issuer_name(cert), buf, buflen); - buf[buflen-1] = '\0'; - MSG("root: %s\n", buf); + if (cert) { + X509_NAME_oneline(X509_get_issuer_name(cert), buf, buflen); + buf[buflen-1] = '\0'; + MSG("root: %s\n", buf); + } } } -- cgit v1.2.3 From b7fbeaab0b05dd06440a83f1bf2575ff0fa628fc Mon Sep 17 00:00:00 2001 From: corvid Date: Sun, 31 May 2015 01:11:03 +0000 Subject: fix up socket queue --- src/IO/http.c | 37 ++++++++++++++++++++++++------------- 1 file changed, 24 insertions(+), 13 deletions(-) (limited to 'src') diff --git a/src/IO/http.c b/src/IO/http.c index 7deab2e4..5aca1703 100644 --- a/src/IO/http.c +++ b/src/IO/http.c @@ -76,6 +76,7 @@ typedef struct { bool_t https; int active_conns; + int running_the_queue; Dlist *queue; } Server_t; @@ -244,13 +245,19 @@ static void Http_connect_queued_sockets(Server_t *srv) SocketData_t *sd; int i; + srv->running_the_queue++; + for (i = 0; (i < dList_length(srv->queue) && srv->active_conns < prefs.http_max_conns); i++) { sd = dList_nth_data(srv->queue, i); - if (!(sd->flags & HTTP_SOCKET_TO_BE_FREED)) { + if (sd->flags & HTTP_SOCKET_TO_BE_FREED) { + dList_remove(srv->queue, sd); + dFree(sd); + i--; + } else { int connect_ready = TLS_CONNECT_READY; if (sd->flags & HTTP_SOCKET_TLS) @@ -266,15 +273,15 @@ static void Http_connect_queued_sockets(Server_t *srv) Http_connect_socket(sd->Info); } } - if (sd->flags & HTTP_SOCKET_TO_BE_FREED) { - dList_remove(srv->queue, sd); - dFree(sd); - i--; - } } _MSG("Queue http%s://%s:%u len %d\n", srv->https ? "s" : "", srv->host, srv->port, dList_length(srv->queue)); + + if (--srv->running_the_queue == 0) { + if (srv->active_conns == 0) + Http_server_remove(srv); + } } /* @@ -303,8 +310,6 @@ static void Http_socket_free(int SKey) (S->flags & HTTP_SOCKET_TLS)); srv->active_conns--; Http_connect_queued_sockets(srv); - if (srv->active_conns == 0) - Http_server_remove(srv); } a_Url_free(S->url); dFree(S); @@ -980,6 +985,7 @@ static Server_t *Http_server_get(const char *host, uint_t port, bool_t https) srv = dNew0(Server_t, 1); srv->queue = dList_new(10); + srv->running_the_queue = 0; srv->host = dStrdup(host); srv->port = port; srv->https = https; @@ -990,11 +996,16 @@ static Server_t *Http_server_get(const char *host, uint_t port, bool_t https) static void Http_server_remove(Server_t *srv) { - assert(dList_length(srv->queue) == 0); - dList_free(srv->queue); - dList_remove_fast(servers, srv); - dFree(srv->host); - dFree(srv); + SocketData_t *sd; + + while ((sd = dList_nth_data(srv->queue, 0))) { + dList_remove_fast(srv->queue, sd); + dFree(sd); + } + dList_free(srv->queue); + dList_remove_fast(servers, srv); + dFree(srv->host); + dFree(srv); } static void Http_servers_remove_all() -- cgit v1.2.3 From f8c3e19ac18075a45c48a5fc36cd2499defaf7e1 Mon Sep 17 00:00:00 2001 From: corvid Date: Sun, 31 May 2015 02:00:49 +0000 Subject: rm MSG --- src/cache.c | 11 ----------- 1 file changed, 11 deletions(-) (limited to 'src') diff --git a/src/cache.c b/src/cache.c index 2cc8c0aa..9ff1cf14 100644 --- a/src/cache.c +++ b/src/cache.c @@ -857,17 +857,6 @@ static void Cache_finish_msg(CacheEntry_t *entry) MSG("Expected size: %d, Transfer size: %d\n", entry->ExpectedSize, entry->TransferSize); } - if (!entry->TransferSize && !(entry->Flags & CA_Redirect) && - (entry->Flags & WEB_RootUrl)) { - char *eol = strchr(entry->Header->str, '\n'); - if (eol) { - char *status_line = dStrndup(entry->Header->str, - eol - entry->Header->str); - MSG_HTTP("Body of %s was empty. Server sent status: %s\n", - URL_STR_(entry->Url), status_line); - dFree(status_line); - } - } entry->Flags |= CA_GotData; entry->Flags &= ~CA_Stopped; /* it may catch up! */ if (entry->TransferDecoder) { -- cgit v1.2.3 From d654825f7fecb9c081b5a12fb7d288ca6dddbfd1 Mon Sep 17 00:00:00 2001 From: corvid Date: Sun, 31 May 2015 02:17:35 +0000 Subject: rm MSG --- src/cache.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) (limited to 'src') diff --git a/src/cache.c b/src/cache.c index 9ff1cf14..d8f1a123 100644 --- a/src/cache.c +++ b/src/cache.c @@ -756,6 +756,7 @@ static void Cache_parse_header(CacheEntry_t *entry) if (!web->requester || a_Url_same_organization(entry->Url, web->requester)) { + /* If cookies are third party, don't even consider them. */ char *server_date = Cache_parse_field(header, "Date"); a_Cookies_set(Cookies, entry->Url, server_date); @@ -764,10 +765,6 @@ static void Cache_parse_header(CacheEntry_t *entry) } } } - if (i >= dList_length(ClientQueue)) { - MSG("Cache: cookies not accepted from '%s'\n", URL_STR(entry->Url)); - } - for (i = 0; (data = dList_nth_data(Cookies, i)); ++i) dFree(data); dList_free(Cookies); -- cgit v1.2.3 From 442cb2c37f7c6a218b2bc87fe18381e96d4bfe5e Mon Sep 17 00:00:00 2001 From: corvid Date: Mon, 1 Jun 2015 03:26:40 +0000 Subject: url: rm dead code --- src/url.c | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) (limited to 'src') diff --git a/src/url.c b/src/url.c index aa211fb7..7112f461 100644 --- a/src/url.c +++ b/src/url.c @@ -213,7 +213,6 @@ void a_Url_free(DilloUrl *url) * Resolve the URL as RFC3986 suggests. */ static Dstr *Url_resolve_relative(const char *RelStr, - DilloUrl *BaseUrlPar, const char *BaseStr) { char *p, *s, *e; @@ -224,9 +223,7 @@ static Dstr *Url_resolve_relative(const char *RelStr, /* parse relative URL */ RelUrl = Url_object_new(RelStr); - if (BaseUrlPar) { - BaseUrl = BaseUrlPar; - } else if (RelUrl->scheme == NULL) { + if (RelUrl->scheme == NULL) { /* only required when there's no in RelStr */ BaseUrl = Url_object_new(BaseStr); } @@ -336,8 +333,7 @@ static Dstr *Url_resolve_relative(const char *RelStr, done: dStr_free(Path, TRUE); a_Url_free(RelUrl); - if (BaseUrl != BaseUrlPar) - a_Url_free(BaseUrl); + a_Url_free(BaseUrl); return SolvedUrl; } @@ -406,7 +402,7 @@ DilloUrl* a_Url_new(const char *url_str, const char *base_url) } /* Resolve the URL */ - SolvedUrl = Url_resolve_relative(urlstr, NULL, base_url); + SolvedUrl = Url_resolve_relative(urlstr, base_url); _MSG("SolvedUrl = %s\n", SolvedUrl->str); /* Fill url data */ -- cgit v1.2.3 From b4b85a7947a05841aa728a98d42c1e3ab66489d6 Mon Sep 17 00:00:00 2001 From: corvid Date: Mon, 1 Jun 2015 15:25:52 +0000 Subject: TLS servers sorted --- src/IO/tls.c | 69 ++++++++++++++++++++++++++++++++---------------------------- 1 file changed, 37 insertions(+), 32 deletions(-) (limited to 'src') diff --git a/src/IO/tls.c b/src/IO/tls.c index 6355f4f5..be8b7b92 100644 --- a/src/IO/tls.c +++ b/src/IO/tls.c @@ -341,16 +341,30 @@ static int Tls_save_certificate_home(X509 * cert) } /* - * Test whether a URL corresponds to a server. + * Ordered comparison of servers. */ static int Tls_servers_cmp(const void *v1, const void *v2) { - Server_t *s = (Server_t *)v1; + const Server_t *s1 = (const Server_t *)v1, *s2 = (const Server_t *)v2; + int cmp = dStrAsciiCasecmp(s1->hostname, s2->hostname); + + if (!cmp) + cmp = s1->port - s2->port; + return cmp; +} +/* + * Ordered comparison of server with URL. + */ +static int Tls_servers_by_url_cmp(const void *v1, const void *v2) +{ + const Server_t *s = (const Server_t *)v1; const DilloUrl *url = (const DilloUrl *)v2; - const char *host = URL_HOST(url); - int port = URL_PORT(url); - return (dStrAsciiCasecmp(s->hostname, host) || (port != s->port)); + int cmp = dStrAsciiCasecmp(s->hostname, URL_HOST(url)); + + if (!cmp) + cmp = s->port - URL_PORT(url); + return cmp; } /* @@ -358,41 +372,31 @@ static int Tls_servers_cmp(const void *v1, const void *v2) * Once we have the certificate, know whether we like it -- and whether the * user accepts it -- HTTP can run through queued sockets as normal. * - * Return: 1 means yes, 0 means not yet, -1 means never. - * TODO: Something clearer or different. + * Return: TLS_CONNECT_READY or TLS_CONNECT_NOT_YET or TLS_CONNECT_NEVER. */ int a_Tls_connect_ready(const DilloUrl *url) { Server_t *s; - int i, len; - const char *host = URL_HOST(url); - const int port = URL_PORT(url); int ret = TLS_CONNECT_READY; dReturn_val_if_fail(ssl_context, TLS_CONNECT_NEVER); - len = dList_length(servers); - - for (i = 0; i < len; i++) { - s = dList_nth_data(servers, i); + if ((s = dList_find_sorted(servers, url, Tls_servers_by_url_cmp))) { + if (s->cert_status == CERT_STATUS_RECEIVING) + ret = TLS_CONNECT_NOT_YET; + else if (s->cert_status == CERT_STATUS_BAD) + ret = TLS_CONNECT_NEVER; - if (!dStrAsciiCasecmp(s->hostname, host) && (port == s->port)) { - if (s->cert_status == CERT_STATUS_RECEIVING) - ret = TLS_CONNECT_NOT_YET; - else if (s->cert_status == CERT_STATUS_BAD) - ret = TLS_CONNECT_NEVER; + if (s->cert_status == CERT_STATUS_NONE) + s->cert_status = CERT_STATUS_RECEIVING; + } else { + s = dNew(Server_t, 1); - if (s->cert_status == CERT_STATUS_NONE) - s->cert_status = CERT_STATUS_RECEIVING; - return ret; - } + s->hostname = dStrdup(URL_HOST(url)); + s->port = URL_PORT(url); + s->cert_status = CERT_STATUS_RECEIVING; + dList_insert_sorted(servers, s, Tls_servers_cmp); } - s = dNew(Server_t, 1); - - s->port = port; - s->hostname = dStrdup(host); - s->cert_status = CERT_STATUS_RECEIVING; - dList_append(servers, s); return ret; } @@ -402,7 +406,7 @@ int a_Tls_connect_ready(const DilloUrl *url) */ static int Tls_user_said_no(const DilloUrl *url) { - Server_t *s = dList_find_custom(servers, url, Tls_servers_cmp); + Server_t *s = dList_find_sorted(servers, url, Tls_servers_by_url_cmp); if (!s) return FALSE; @@ -902,7 +906,7 @@ static int Tls_examine_certificate(SSL *ssl, Server_t *srv,const char *host) void a_Tls_reset_server_state(const DilloUrl *url) { if (servers) { - Server_t *s = dList_find_custom(servers, url, Tls_servers_cmp); + Server_t *s = dList_find_sorted(servers, url, Tls_servers_by_url_cmp); if (s && s->cert_status == CERT_STATUS_RECEIVING) s->cert_status = CERT_STATUS_NONE; @@ -1038,7 +1042,8 @@ static void Tls_connect(int fd, int connkey) MSG("SSL_get_error() returned %d on a connect.\n", err1_ret); } } else { - Server_t *srv = dList_find_custom(servers, conn->url, Tls_servers_cmp); + 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 some information. */ -- cgit v1.2.3 From 40989a38306d1dd5d356a6b049574e6434e1abb7 Mon Sep 17 00:00:00 2001 From: corvid Date: Mon, 1 Jun 2015 16:40:10 +0000 Subject: rm the old-style url alt stuff --- src/html.cc | 2 -- src/url.c | 14 -------------- src/url.h | 3 --- 3 files changed, 19 deletions(-) (limited to 'src') diff --git a/src/html.cc b/src/html.cc index d6b64a19..fccf6bfb 100644 --- a/src/html.cc +++ b/src/html.cc @@ -2514,8 +2514,6 @@ static void if ((attrbuf = a_Html_get_attr(html, tag, tagsize, "href"))) { url = a_Html_url_new(html, attrbuf, NULL, 0); dReturn_if_fail ( url != NULL ); - if ((attrbuf = a_Html_get_attr(html, tag, tagsize, "alt"))) - a_Url_set_alt(url, attrbuf); link = Html_set_new_link(html, &url); } diff --git a/src/url.c b/src/url.c index 7112f461..6fee97c3 100644 --- a/src/url.c +++ b/src/url.c @@ -204,7 +204,6 @@ void a_Url_free(DilloUrl *url) dFree((char *)url->hostname); dFree((char *)url->buffer); dStr_free(url->data, 1); - dFree((char *)url->alt); dFree(url); } } @@ -352,7 +351,6 @@ done: * port = 8080 * flags = URL_Get * data = Dstr * ("") - * alt = NULL * ismap_url_len = 0 * } * @@ -431,7 +429,6 @@ DilloUrl* a_Url_dup(const DilloUrl *ori) url->url_string = dStr_new(URL_STR(ori)); url->port = ori->port; url->flags = ori->flags; - url->alt = dStrdup(ori->alt); url->ismap_url_len = ori->ismap_url_len; url->illegal_chars = ori->illegal_chars; url->illegal_chars_spc = ori->illegal_chars_spc; @@ -490,17 +487,6 @@ void a_Url_set_data(DilloUrl *u, Dstr **data) } } -/* - * Set DilloUrl alt (alternate text to the URL. Used by image maps) - */ -void a_Url_set_alt(DilloUrl *u, const char *alt) -{ - if (u) { - dFree((char *)u->alt); - u->alt = dStrdup(alt); - } -} - /* * Set DilloUrl ismap coordinates * (this is optimized for not hogging the CPU) diff --git a/src/url.h b/src/url.h index 6920f769..a280fd84 100644 --- a/src/url.h +++ b/src/url.h @@ -47,7 +47,6 @@ #define URL_QUERY_(u) (u)->query #define URL_FRAGMENT_(u) (u)->fragment #define URL_HOST_(u) a_Url_hostname(u) -#define URL_ALT_(u) (u)->alt #define URL_STR_(u) a_Url_str(u) /* this returns a Dstr* */ #define URL_DATA_(u) (u)->data @@ -94,7 +93,6 @@ typedef struct { int port; int flags; Dstr *data; /* POST */ - const char *alt; /* "alt" text (used by image maps) */ int ismap_url_len; /* Used by server side image maps */ int illegal_chars; /* number of illegal chars */ int illegal_chars_spc; /* number of illegal space chars */ @@ -109,7 +107,6 @@ DilloUrl* a_Url_dup(const DilloUrl *u); int a_Url_cmp(const DilloUrl *A, const DilloUrl *B); void a_Url_set_flags(DilloUrl *u, int flags); void a_Url_set_data(DilloUrl *u, Dstr **data); -void a_Url_set_alt(DilloUrl *u, const char *alt); void a_Url_set_ismap_coords(DilloUrl *u, char *coord_str); char *a_Url_decode_hex_str(const char *str); char *a_Url_encode_hex_str(const char *str); -- cgit v1.2.3 From 67e71ab81f83ecd09e97c5e29cb240431450a7ec Mon Sep 17 00:00:00 2001 From: corvid Date: Mon, 1 Jun 2015 16:55:55 +0000 Subject: url: rm unused flags --- src/url.c | 1 - src/url.h | 7 +------ 2 files changed, 1 insertion(+), 7 deletions(-) (limited to 'src') diff --git a/src/url.c b/src/url.c index 6fee97c3..e2eac48a 100644 --- a/src/url.c +++ b/src/url.c @@ -498,7 +498,6 @@ void a_Url_set_ismap_coords(DilloUrl *u, char *coord_str) if (!u->ismap_url_len) { /* Save base-url length (without coords) */ u->ismap_url_len = URL_STR_(u) ? u->url_string->len : 0; - a_Url_set_flags(u, URL_FLAGS(u) | URL_Ismap); } if (u->url_string) { dStr_truncate(u->url_string, u->ismap_url_len); diff --git a/src/url.h b/src/url.h index a280fd84..93d198f8 100644 --- a/src/url.h +++ b/src/url.h @@ -22,12 +22,8 @@ */ #define URL_Get (1 << 0) #define URL_Post (1 << 1) -#define URL_ISindex (1 << 2) -#define URL_Ismap (1 << 3) -#define URL_RealmAccess (1 << 4) #define URL_E2EQuery (1 << 5) -#define URL_ReloadImages (1 << 6) #define URL_ReloadPage (1 << 7) #define URL_ReloadFromCache (1 << 8) @@ -68,9 +64,8 @@ #define URL_QUERY(u) NPTR2STR(URL_QUERY_(u)) #define URL_FRAGMENT(u) NPTR2STR(URL_FRAGMENT_(u)) #define URL_HOST(u) NPTR2STR(URL_HOST_(u)) -#define URL_DATA(u) URL_DATA_(u) -#define URL_ALT(u) NPTR2STR(URL_ALT_(u)) #define URL_STR(u) NPTR2STR(URL_STR_(u)) +#define URL_DATA(u) URL_DATA_(u) #define URL_PORT(u) URL_PORT_(u) #define URL_FLAGS(u) URL_FLAGS_(u) #define URL_ILLEGAL_CHARS(u) URL_ILLEGAL_CHARS_(u) -- cgit v1.2.3 From 3aa9e64c49310a0e47e63a1ccded6b9cd223519f Mon Sep 17 00:00:00 2001 From: corvid Date: Mon, 1 Jun 2015 17:15:12 +0000 Subject: const --- src/IO/tls.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/IO/tls.c b/src/IO/tls.c index be8b7b92..4ae40961 100644 --- a/src/IO/tls.c +++ b/src/IO/tls.c @@ -210,7 +210,7 @@ static void Tls_load_certificates() */ uint_t u; char *userpath; - static const char *ca_files[] = { + static const char *const ca_files[] = { "/etc/ssl/certs/ca-certificates.crt", "/etc/pki/tls/certs/ca-bundle.crt", "/usr/share/ssl/certs/ca-bundle.crt", @@ -219,7 +219,7 @@ static void Tls_load_certificates() CA_CERTS_FILE }; - static const char *ca_paths[] = { + static const char *const ca_paths[] = { "/etc/ssl/certs/", CA_CERTS_DIR }; -- cgit v1.2.3 From d15ff594d989c0fdd025b9f66da8d83ff4dd2629 Mon Sep 17 00:00:00 2001 From: corvid Date: Wed, 3 Jun 2015 01:13:09 +0000 Subject: show certificate hash algorithm (and complain feebly if it's weak) --- src/IO/tls.c | 36 +++++++++++++++++++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/IO/tls.c b/src/IO/tls.c index 4ae40961..f0f33215 100644 --- a/src/IO/tls.c +++ b/src/IO/tls.c @@ -943,15 +943,49 @@ static void Tls_print_cert_chain(SSL *ssl) if (sk) { const uint_t buflen = 4096; char buf[buflen]; - int i, n = sk_X509_num(sk); + 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"); + + if (s) { + *s = '\0'; + if (!strcmp(buf, "sha1")) { + MSG_WARN("In 2015, browsers have begun to deprecate SHA1 " + "certificates.\n"); + } else if (!strncmp(buf, "md", 2)) { + MSG_ERR("Browsers stopped accepting MD5 certificates around " + "2012.\n"); + } + } + } + BIO_free(b); + 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" : -- cgit v1.2.3 From 59b76c75b64578edac35d19c914067a0bd7791e9 Mon Sep 17 00:00:00 2001 From: corvid Date: Wed, 3 Jun 2015 07:54:16 +0000 Subject: web must be valid in order to continue in a_Http_connect_done A site triggers this with a background image where the style is deleted upon , and Capi_stop_client() finds that a_Cache_client_get_if_unique() is false, so nothing aborts the connection. And there's time for this to happen because we're doing TLS handshake. I don't know whether all of what triggered this is doing the right thing, but at least when it comes to capi, we can see that there's the idea of permitting it (with whether we ever actually want that in practice being yet another question). In any case, Http_make_query_str() definitely thinks the web is there. If we really decided that we wanted connections to continue without webs, we could stuff 1) what sort of thing are we requesting? 2) is this a third-party request? into the socket data. Making the query earlier is probably not advisable because we'd want the cookies available at the time that we send the query and not the cookies that were available somewhat earlier. --- src/IO/http.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/IO/http.c b/src/IO/http.c index 5aca1703..379d51c1 100644 --- a/src/IO/http.c +++ b/src/IO/http.c @@ -215,12 +215,14 @@ void a_Http_connect_done(int fd, bool_t success) if (fme && (sd = a_Klist_get_data(ValidSocks, fme->skey))) { ChainLink *info = sd->Info; + bool_t valid_web = a_Web_valid(sd->web); - if (success) { + if (success && valid_web) { a_Chain_bfcb(OpSend, info, &sd->SockFD, "FD"); Http_send_query(sd); } else { - MSG_BW(sd->web, 1, "Could not establish connection."); + if (valid_web) + MSG_BW(sd->web, 1, "Could not establish connection."); MSG("fd %d is done and failed\n", sd->SockFD); dClose(fd); Http_socket_free(VOIDP2INT(info->LocalKey)); /* free sd */ -- cgit v1.2.3