summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/IO/Makefile.am15
-rw-r--r--src/IO/tls.c175
-rw-r--r--src/IO/tls.h50
-rw-r--r--src/IO/tls_mbedtls.c84
-rw-r--r--src/IO/tls_mbedtls.h65
-rw-r--r--src/IO/tls_openssl.c1282
-rw-r--r--src/IO/tls_openssl.h49
-rw-r--r--src/capi.c3
-rw-r--r--src/hsts.c3
9 files changed, 1652 insertions, 74 deletions
diff --git a/src/IO/Makefile.am b/src/IO/Makefile.am
index d8fed40a..6d3a5a8b 100644
--- a/src/IO/Makefile.am
+++ b/src/IO/Makefile.am
@@ -9,6 +9,19 @@ AM_CXXFLAGS = @LIBFLTK_CXXFLAGS@
noinst_LIBRARIES = libDiof.a
+if USE_OPENSSL
+TLS_OPENSSL = tls_openssl.c tls_openssl.h
+else
+TLS_OPENSSL =
+endif
+
+if USE_MBEDTLS
+TLS_MBEDTLS = tls_mbedtls.c tls_mbedtls.h
+else
+TLS_MBEDTLS =
+endif
+
+
libDiof_a_SOURCES = \
mime.c \
mime.h \
@@ -17,6 +30,8 @@ libDiof_a_SOURCES = \
http.c \
tls.h \
tls.c \
+ $(TLS_OPENSSL) \
+ $(TLS_MBEDTLS) \
dpi.c \
IO.c \
iowatch.cc \
diff --git a/src/IO/tls.c b/src/IO/tls.c
new file mode 100644
index 00000000..2a27c1a6
--- /dev/null
+++ b/src/IO/tls.c
@@ -0,0 +1,175 @@
+/*
+ * File: tls.c
+ *
+ * Copyright (C) 2011 Benjamin Johnson <obeythepenguin@users.sourceforge.net>
+ * (for the https code offered from dplus browser that formed the basis...)
+ * Copyright 2016 corvid
+ * Copyright (C) 2023 Rodrigo Arias Mallo <rodarima@gmail.com>
+ *
+ * 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.
+ */
+
+#include "config.h"
+#include "../msg.h"
+
+#include "tls.h"
+#include "tls_openssl.h"
+#include "tls_mbedtls.h"
+
+void a_Tls_init()
+{
+#if ! defined(ENABLE_TLS)
+ MSG("TLS: Disabled at compilation time.\n");
+#elif defined(HAVE_OPENSSL)
+ a_Tls_openssl_init();
+#elif defined(HAVE_MBEDTLS)
+ a_Tls_mbedtls_init();
+#else
+# error "no TLS library found but ENABLE_TLS set"
+#endif
+}
+
+/*
+ * Return TLS connection information for a given file
+ * descriptor, or NULL if no TLS connection was found.
+ */
+void *a_Tls_connection(int fd)
+{
+#if ! defined(ENABLE_TLS)
+ return NULL;
+#elif defined(HAVE_OPENSSL)
+ return a_Tls_openssl_connection(fd);
+#elif defined(HAVE_MBEDTLS)
+ return a_Tls_mbedtls_connection(fd);
+#else
+# error "no TLS library found but ENABLE_TLS set"
+#endif
+}
+
+/*
+ * The purpose here is to permit a single initial connection to a server.
+ * Once we have the certificate, know whether we like it -- and whether the
+ * user accepts it -- HTTP can run through queued sockets as normal.
+ *
+ * Return: TLS_CONNECT_READY or TLS_CONNECT_NOT_YET or TLS_CONNECT_NEVER.
+ */
+int a_Tls_connect_ready(const DilloUrl *url)
+{
+#if ! defined(ENABLE_TLS)
+ return TLS_CONNECT_NEVER;
+#elif defined(HAVE_OPENSSL)
+ return a_Tls_openssl_connect_ready(url);
+#elif defined(HAVE_MBEDTLS)
+ return a_Tls_mbedtls_connect_ready(url);
+#else
+# error "no TLS library found but ENABLE_TLS set"
+#endif
+}
+
+/*
+ * Did everything seem proper with the certificate -- no warnings to
+ * click through?
+ */
+int a_Tls_certificate_is_clean(const DilloUrl *url)
+{
+#if ! defined(ENABLE_TLS)
+ return 0;
+#elif defined(HAVE_OPENSSL)
+ return a_Tls_openssl_certificate_is_clean(url);
+#elif defined(HAVE_MBEDTLS)
+ return a_Tls_mbedtls_certificate_is_clean(url);
+#else
+# error "no TLS library found but ENABLE_TLS set"
+#endif
+}
+
+/*
+ * Clean up the OpenSSL library
+ */
+void a_Tls_freeall(void)
+{
+#if ! defined(ENABLE_TLS)
+ return;
+#elif defined(HAVE_OPENSSL)
+ a_Tls_openssl_freeall();
+#elif defined(HAVE_MBEDTLS)
+ a_Tls_mbedtls_freeall();
+#else
+# error "no TLS library found but ENABLE_TLS set"
+#endif
+}
+
+
+void a_Tls_reset_server_state(const DilloUrl *url)
+{
+#if ! defined(ENABLE_TLS)
+ return;
+#elif defined(HAVE_OPENSSL)
+ a_Tls_openssl_reset_server_state(url);
+#elif defined(HAVE_MBEDTLS)
+ a_Tls_mbedtls_reset_server_state(url);
+#else
+# error "no TLS library found but ENABLE_TLS set"
+#endif
+}
+
+void a_Tls_connect(int fd, const DilloUrl *url)
+{
+#if ! defined(ENABLE_TLS)
+ return;
+#elif defined(HAVE_OPENSSL)
+ a_Tls_openssl_connect(fd, url);
+#elif defined(HAVE_MBEDTLS)
+ a_Tls_mbedtls_connect(fd, url);
+#else
+# error "no TLS library found but ENABLE_TLS set"
+#endif
+}
+
+void a_Tls_close_by_fd(int fd)
+{
+#if ! defined(ENABLE_TLS)
+ return;
+#elif defined(HAVE_OPENSSL)
+ a_Tls_openssl_close_by_fd(fd);
+#elif defined(HAVE_MBEDTLS)
+ a_Tls_mbedtls_close_by_fd(fd);
+#else
+# error "no TLS library found but ENABLE_TLS set"
+#endif
+}
+
+int a_Tls_read(void *conn, void *buf, size_t len)
+{
+#if ! defined(ENABLE_TLS)
+ return 0;
+#elif defined(HAVE_OPENSSL)
+ return a_Tls_openssl_read(conn, buf, len);
+#elif defined(HAVE_MBEDTLS)
+ return a_Tls_mbedtls_read(conn, buf, len);
+#else
+# error "no TLS library found but ENABLE_TLS set"
+#endif
+}
+
+int a_Tls_write(void *conn, void *buf, size_t len)
+{
+#if ! defined(ENABLE_TLS)
+ return 0;
+#elif defined(HAVE_OPENSSL)
+ return a_Tls_openssl_write(conn, buf, len);
+#elif defined(HAVE_MBEDTLS)
+ return a_Tls_mbedtls_write(conn, buf, len);
+#else
+# error "no TLS library found but ENABLE_TLS set"
+#endif
+}
diff --git a/src/IO/tls.h b/src/IO/tls.h
new file mode 100644
index 00000000..25da6ea6
--- /dev/null
+++ b/src/IO/tls.h
@@ -0,0 +1,50 @@
+/*
+ * File: tls.h
+ *
+ * Copyright (C) 2011 Benjamin Johnson <obeythepenguin@users.sourceforge.net>
+ * (for the https code offered from dplus browser that formed the basis...)
+ * Copyright 2016 corvid
+ * Copyright (C) 2023 Rodrigo Arias Mallo <rodarima@gmail.com>
+ *
+ * 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.
+ */
+
+#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();
+int a_Tls_certificate_is_clean(const DilloUrl *url);
+int a_Tls_connect_ready(const DilloUrl *url);
+void a_Tls_reset_server_state(const DilloUrl *url);
+void a_Tls_connect(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);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __TLS_H__ */
+
diff --git a/src/IO/tls_mbedtls.c b/src/IO/tls_mbedtls.c
index c6b04b8f..51d6f3a5 100644
--- a/src/IO/tls_mbedtls.c
+++ b/src/IO/tls_mbedtls.c
@@ -1,15 +1,15 @@
/*
- * File: tls.c
+ * File: tls_mbedtls.c
*
* Copyright (C) 2011 Benjamin Johnson <obeythepenguin@users.sourceforge.net>
* (for the https code offered from dplus browser that formed the basis...)
* Copyright 2016 corvid
+ * Copyright (C) 2023 Rodrigo Arias Mallo <rodarima@gmail.com>
*
* 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.
- *
*/
/*
@@ -23,14 +23,9 @@
#include "config.h"
#include "../msg.h"
-#ifndef ENABLE_SSL
-
-void a_Tls_init()
-{
- MSG("TLS: Disabled at compilation time.\n");
-}
-
-#else
+#ifndef ENABLE_TLS
+# error "ENABLE_TLS not defined"
+#endif
#include <assert.h>
#include <errno.h>
@@ -49,7 +44,11 @@ void a_Tls_init()
#include <mbedtls/error.h>
#include <mbedtls/oid.h>
#include <mbedtls/x509.h>
+#if MBEDTLS_VERSION_NUMBER < 0x03000000
#include <mbedtls/net.h> /* net_send, net_recv */
+#else
+#include <mbedtls/net_sockets.h> /* net_send, net_recv */
+#endif
#define CERT_STATUS_NONE 0
#define CERT_STATUS_RECEIVING 1
@@ -144,7 +143,7 @@ static void Tls_fd_map_remove_entry(int fd)
* Return TLS connection information for a given file
* descriptor, or NULL if no TLS connection was found.
*/
-void *a_Tls_connection(int fd)
+void *a_Tls_mbedtls_connection(int fd)
{
Conn_t *conn;
@@ -352,7 +351,7 @@ static void Tls_remove_psk_ciphersuites()
/*
* Initialize the mbed TLS library.
*/
-void a_Tls_init(void)
+void a_Tls_mbedtls_init(void)
{
int ret;
@@ -449,7 +448,7 @@ static int Tls_servers_by_url_cmp(const void *v1, const void *v2)
*
* Return: TLS_CONNECT_READY or TLS_CONNECT_NOT_YET or TLS_CONNECT_NEVER.
*/
-int a_Tls_connect_ready(const DilloUrl *url)
+int a_Tls_mbedtls_connect_ready(const DilloUrl *url)
{
Server_t *s;
int ret = TLS_CONNECT_READY;
@@ -495,7 +494,7 @@ static int Tls_user_said_no(const DilloUrl *url)
* Did everything seem proper with the certificate -- no warnings to
* click through?
*/
-int a_Tls_certificate_is_clean(const DilloUrl *url)
+int a_Tls_mbedtls_certificate_is_clean(const DilloUrl *url)
{
return Tls_cert_status(url) == CERT_STATUS_CLEAN;
}
@@ -618,15 +617,23 @@ static void Tls_cert_not_valid_yet(const mbedtls_x509_crt *cert, Dstr *ds)
*/
static void Tls_cert_bad_hash(const mbedtls_x509_crt *cert, Dstr *ds)
{
- const char *hash = (cert->sig_md == MBEDTLS_MD_MD5) ? "MD5" :
- (cert->sig_md == MBEDTLS_MD_MD4) ? "MD4" :
- (cert->sig_md == MBEDTLS_MD_MD2) ? "MD2" :
- (cert->sig_md == MBEDTLS_MD_SHA1) ? "SHA1" :
- (cert->sig_md == MBEDTLS_MD_SHA224) ? "SHA224" :
- (cert->sig_md == MBEDTLS_MD_RIPEMD160) ? "RIPEMD160" :
- (cert->sig_md == MBEDTLS_MD_SHA256) ? "SHA256" :
- (cert->sig_md == MBEDTLS_MD_SHA384) ? "SHA384" :
- (cert->sig_md == MBEDTLS_MD_SHA512) ? "SHA512" :
+#if MBEDTLS_VERSION_NUMBER < 0x03000000
+ mbedtls_md_type_t md = cert->sig_md;
+#else
+ mbedtls_md_type_t md = cert->MBEDTLS_PRIVATE(sig_md);
+#endif
+ const char *hash = (md == MBEDTLS_MD_MD5) ? "MD5" :
+ (md == MBEDTLS_MD_SHA1) ? "SHA1" :
+ (md == MBEDTLS_MD_SHA224) ? "SHA224" :
+ (md == MBEDTLS_MD_RIPEMD160) ? "RIPEMD160" :
+ (md == MBEDTLS_MD_SHA256) ? "SHA256" :
+ (md == MBEDTLS_MD_SHA384) ? "SHA384" :
+ (md == MBEDTLS_MD_SHA512) ? "SHA512" :
+#if MBEDTLS_VERSION_NUMBER < 0x03000000
+/* In version 3, these are removed: */
+ (md == MBEDTLS_MD_MD4) ? "MD4" :
+ (md == MBEDTLS_MD_MD2) ? "MD2" :
+#endif
"Unrecognized";
dStr_sprintfa(ds, "This certificate's hash algorithm is not accepted "
@@ -803,7 +810,7 @@ static int Tls_examine_certificate(mbedtls_ssl_context *ssl, Server_t *srv)
* 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)
+void a_Tls_mbedtls_reset_server_state(const DilloUrl *url)
{
if (servers) {
Server_t *s = dList_find_sorted(servers, url, Tls_servers_by_url_cmp);
@@ -821,7 +828,7 @@ 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);
+ a_Tls_mbedtls_reset_server_state(c->url);
if (c->connecting) {
a_IOwatch_remove_fd(c->fd, -1);
dClose(c->fd);
@@ -934,7 +941,12 @@ static void Tls_handshake(int fd, int connkey)
return;
}
- if (conn->ssl->state != MBEDTLS_SSL_HANDSHAKE_OVER) {
+#if MBEDTLS_VERSION_NUMBER < 0x03000000
+ int ssl_state = conn->ssl->state;
+#else
+ int ssl_state = conn->ssl->MBEDTLS_PRIVATE(state);
+#endif
+ if (ssl_state != MBEDTLS_SSL_HANDSHAKE_OVER) {
ret = mbedtls_ssl_handshake(conn->ssl);
if (ret == MBEDTLS_ERR_SSL_WANT_READ ||
@@ -977,16 +989,22 @@ static void Tls_handshake(int fd, int connkey)
* soon, unless there are radical changes". It seems to be the best of
* the alternatives.
*/
+#if MBEDTLS_VERSION_NUMBER < 0x03000000
Tls_fatal_error_msg(conn->ssl->in_msg[1]);
+#else
+ Tls_fatal_error_msg(conn->ssl->MBEDTLS_PRIVATE(in_msg[1]));
+#endif
} else if (ret == MBEDTLS_ERR_SSL_INVALID_RECORD) {
MSG("mbedtls_ssl_handshake() failed upon receiving 'an invalid "
"record'.\n");
} else if (ret == MBEDTLS_ERR_SSL_FEATURE_UNAVAILABLE) {
MSG("mbedtls_ssl_handshake() failed: 'The requested feature is not "
"available.'\n");
+#if MBEDTLS_VERSION_NUMBER < 0x03000000
} else if (ret == MBEDTLS_ERR_SSL_BAD_HS_SERVER_KEY_EXCHANGE) {
MSG("mbedtls_ssl_handshake() failed: 'Processing of the "
"ServerKeyExchange handshake message failed.'\n");
+#endif
} else if (ret == MBEDTLS_ERR_SSL_CONN_EOF) {
MSG("mbedtls_ssl_handshake() failed: Read EOF. Connection closed by "
"server.\n");
@@ -1022,7 +1040,7 @@ static void Tls_handshake_cb(int fd, void *vconnkey)
/*
* Make TLS connection over a connect()ed socket.
*/
-void a_Tls_connect(int fd, const DilloUrl *url)
+void a_Tls_mbedtls_connect(int fd, const DilloUrl *url)
{
mbedtls_ssl_context *ssl = dNew0(mbedtls_ssl_context, 1);
bool_t success = TRUE;
@@ -1055,7 +1073,7 @@ void a_Tls_connect(int fd, const DilloUrl *url)
}
if (!success) {
- a_Tls_reset_server_state(url);
+ a_Tls_mbedtls_reset_server_state(url);
a_Http_connect_done(fd, success);
} else {
Tls_handshake(fd, connkey);
@@ -1065,7 +1083,7 @@ void a_Tls_connect(int fd, const DilloUrl *url)
/*
* Read data from an open TLS connection.
*/
-int a_Tls_read(void *conn, void *buf, size_t len)
+int a_Tls_mbedtls_read(void *conn, void *buf, size_t len)
{
Conn_t *c = (Conn_t*)conn;
int ret = mbedtls_ssl_read(c->ssl, buf, len);
@@ -1089,7 +1107,7 @@ int a_Tls_read(void *conn, void *buf, size_t len)
/*
* Write data to an open TLS connection.
*/
-int a_Tls_write(void *conn, void *buf, size_t len)
+int a_Tls_mbedtls_write(void *conn, void *buf, size_t len)
{
Conn_t *c = (Conn_t*)conn;
int ret = mbedtls_ssl_write(c->ssl, buf, len);
@@ -1100,7 +1118,7 @@ int a_Tls_write(void *conn, void *buf, size_t len)
return ret;
}
-void a_Tls_close_by_fd(int fd)
+void a_Tls_mbedtls_close_by_fd(int fd)
{
FdMapEntry_t *fme = dList_find_custom(fd_map, INT2VOIDP(fd),
Tls_fd_map_cmp);
@@ -1204,7 +1222,7 @@ static void Tls_fd_map_remove_all()
/*
* Clean up
*/
-void a_Tls_freeall(void)
+void a_Tls_mbedtls_freeall(void)
{
if (prefs.show_msg)
Tls_cert_authorities_print_summary();
@@ -1213,5 +1231,3 @@ void a_Tls_freeall(void)
Tls_cert_authorities_freeall();
Tls_servers_freeall();
}
-
-#endif /* ENABLE_SSL */
diff --git a/src/IO/tls_mbedtls.h b/src/IO/tls_mbedtls.h
index a3bf9ba5..4a679698 100644
--- a/src/IO/tls_mbedtls.h
+++ b/src/IO/tls_mbedtls.h
@@ -1,5 +1,19 @@
-#ifndef __TLS_H__
-#define __TLS_H__
+/*
+ * File: tls_mbedtls.h
+ *
+ * Copyright (C) 2011 Benjamin Johnson <obeythepenguin@users.sourceforge.net>
+ * (for the https code offered from dplus browser that formed the basis...)
+ * Copyright 2016 corvid
+ * Copyright (C) 2023 Rodrigo Arias Mallo <rodarima@gmail.com>
+ *
+ * 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.
+ */
+
+#ifndef __TLS_MBEDTLS_H__
+#define __TLS_MBEDTLS_H__
#ifdef __cplusplus
extern "C" {
@@ -7,44 +21,19 @@ extern "C" {
#include "../url.h"
-#define TLS_CONNECT_NEVER -1
-#define TLS_CONNECT_NOT_YET 0
-#define TLS_CONNECT_READY 1
+void a_Tls_mbedtls_init();
+int a_Tls_mbedtls_certificate_is_clean(const DilloUrl *url);
+int a_Tls_mbedtls_connect_ready(const DilloUrl *url);
+void a_Tls_mbedtls_reset_server_state(const DilloUrl *url);
+void a_Tls_mbedtls_connect(int fd, const DilloUrl *url);
+void *a_Tls_mbedtls_connection(int fd);
+void a_Tls_mbedtls_freeall();
+void a_Tls_mbedtls_close_by_fd(int fd);
+int a_Tls_mbedtls_read(void *conn, void *buf, size_t len);
+int a_Tls_mbedtls_write(void *conn, void *buf, size_t len);
-void a_Tls_init();
-
-
-#ifdef ENABLE_SSL
-int a_Tls_certificate_is_clean(const DilloUrl *url);
-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_connect(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_certificate_is_clean(host) 0
-#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_connect(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__ */
-
+#endif /* __TLS_MBEDTLS_H__ */
diff --git a/src/IO/tls_openssl.c b/src/IO/tls_openssl.c
new file mode 100644
index 00000000..33cced84
--- /dev/null
+++ b/src/IO/tls_openssl.c
@@ -0,0 +1,1282 @@
+/*
+ * File: tls_openssl.c
+ *
+ * Copyright 2004 Garrett Kajmowicz <gkajmowi@tbaytel.net>
+ * (for some bits derived from the https dpi, e.g., certificate handling)
+ * Copyright (C) 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008,
+ * 2009, 2010, 2011, 2012 Free Software Foundation, Inc.
+ * (for the certificate hostname checking from wget)
+ * Copyright (C) 2011 Benjamin Johnson <obeythepenguin@users.sourceforge.net>
+ * (for the https code offered from dplus browser that formed the basis...)
+ * Copyright (C) 2023 Rodrigo Arias Mallo <rodarima@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * As a special exception, permission is granted to link Dillo with the OpenSSL
+ * or LibreSSL library, and distribute the linked executables without
+ * including the source code for OpenSSL or LibreSSL in the source
+ * distribution. You must obey the GNU General Public License, version 3, in
+ * all respects for all of the code used other than OpenSSL or LibreSSL.
+ */
+
+/* https://www.ssllabs.com/ssltest/viewMyClient.html
+ * https://github.com/lgarron/badssl.com
+ */
+
+/*
+ * Using TLS in Applications: http://datatracker.ietf.org/wg/uta/documents/
+ * TLS: http://datatracker.ietf.org/wg/tls/documents/
+ */
+
+#include "config.h"
+#include "../msg.h"
+
+#include <assert.h>
+
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include <ctype.h> /* tolower for wget stuff */
+#include <stdio.h>
+#include <errno.h>
+#include "../../dlib/dlib.h"
+#include "../dialog.hh"
+#include "../klist.h"
+#include "iowatch.hh"
+#include "tls.h"
+#include "Url.h"
+
+#include <openssl/ssl.h>
+#include <openssl/rand.h>
+#include <openssl/err.h>
+#include <openssl/x509v3.h> /* for hostname checking */
+
+#define CERT_STATUS_NONE 0
+#define CERT_STATUS_RECEIVING 1
+#define CERT_STATUS_CLEAN 2
+#define CERT_STATUS_BAD 3
+#define CERT_STATUS_USER_ACCEPTED 4
+
+typedef struct {
+ char *hostname;
+ int port;
+ int cert_status;
+} Server_t;
+
+typedef struct {
+ 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_openssl_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) {
+ 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);
+ }
+}
+
+/*
+ * 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 *const ca_files[] = {
+ "/etc/ssl/certs/ca-certificates.crt",
+ "/etc/pki/tls/certs/ca-bundle.crt",
+ "/usr/share/ssl/certs/ca-bundle.crt",
+ "/usr/local/share/certs/ca-root.crt",
+ "/etc/ssl/cert.pem",
+ CA_CERTS_FILE
+ };
+
+ static const char *const ca_paths[] = {
+ "/etc/ssl/certs/",
+ CA_CERTS_DIR
+ };
+
+ 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_openssl_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;
+}
+
+/*
+ * Ordered comparison of servers.
+ */
+static int Tls_servers_cmp(const void *v1, const void *v2)
+{
+ const Server_t *s1 = (const Server_t *)v1, *s2 = (const Server_t *)v2;
+ int cmp = dStrAsciiCasecmp(s1->hostname, s2->hostname);
+
+ if (!cmp)
+ cmp = s1->port - s2->port;
+ return cmp;
+}
+/*
+ * Ordered comparison of server with URL.
+ */
+static int Tls_servers_by_url_cmp(const void *v1, const void *v2)
+{
+ const Server_t *s = (const Server_t *)v1;
+ const DilloUrl *url = (const DilloUrl *)v2;
+
+ int cmp = dStrAsciiCasecmp(s->hostname, URL_HOST(url));
+
+ if (!cmp)
+ cmp = s->port - URL_PORT(url);
+ return cmp;
+}
+
+/*
+ * The purpose here is to permit a single initial connection to a server.
+ * Once we have the certificate, know whether we like it -- and whether the
+ * user accepts it -- HTTP can run through queued sockets as normal.
+ *
+ * Return: TLS_CONNECT_READY or TLS_CONNECT_NOT_YET or TLS_CONNECT_NEVER.
+ */
+int a_Tls_openssl_connect_ready(const DilloUrl *url)
+{
+ Server_t *s;
+ int ret = TLS_CONNECT_READY;
+
+ dReturn_val_if_fail(ssl_context, TLS_CONNECT_NEVER);
+
+ if ((s = dList_find_sorted(servers, url, Tls_servers_by_url_cmp))) {
+ if (s->cert_status == CERT_STATUS_RECEIVING)
+ ret = TLS_CONNECT_NOT_YET;
+ else if (s->cert_status == CERT_STATUS_BAD)
+ ret = TLS_CONNECT_NEVER;
+
+ if (s->cert_status == CERT_STATUS_NONE)
+ s->cert_status = CERT_STATUS_RECEIVING;
+ } else {
+ s = dNew(Server_t, 1);
+
+ s->hostname = dStrdup(URL_HOST(url));
+ s->port = URL_PORT(url);
+ s->cert_status = CERT_STATUS_RECEIVING;
+ dList_insert_sorted(servers, s, Tls_servers_cmp);
+ }
+ return ret;
+}
+
+static int Tls_cert_status(const DilloUrl *url)
+{
+ Server_t *s = dList_find_sorted(servers, url, Tls_servers_by_url_cmp);
+
+ return s ? s->cert_status : CERT_STATUS_NONE;
+}
+
+/*
+ * Did we find problems with the certificate, and did the user proceed to
+ * reject the connection?
+ */
+static int Tls_user_said_no(const DilloUrl *url)
+{
+ return Tls_cert_status(url) == CERT_STATUS_BAD;
+}
+
+/*
+ * Did everything seem proper with the certificate -- no warnings to
+ * click through?
+ */
+int a_Tls_openssl_certificate_is_clean(const DilloUrl *url)
+{
+ return Tls_cert_status(url) == CERT_STATUS_CLEAN;
+}
+
+/*
+ * We are both checking whether the certificates are using a strong enough
+ * hash algorithm and key as well as printing out certificate information the
+ * first time that we see it. Mixing these two actions together is generally
+ * not good practice, but feels justified by the fact that it's so much
+ * trouble to get this information out of openssl even once.
+ *
+ * Return FALSE if MD5 (MD*) hash is found and user does not accept it,
+ * otherwise TRUE.
+ */
+static bool_t Tls_check_cert_strength(SSL *ssl, Server_t *srv, int *choice)
+{
+ /* print for first connection to server */
+ const bool_t print_chain = srv->cert_status == CERT_STATUS_RECEIVING;
+ bool_t success = TRUE;
+
+ STACK_OF(X509) *sk = SSL_get_peer_cert_chain(ssl);
+
+ if (sk) {
+ const uint_t buflen = 4096;
+ char buf[buflen];
+ int rc, i, n = sk_X509_num(sk);
+ X509 *cert = NULL;
+ EVP_PKEY *public_key;
+ int key_type, key_bits;
+ const char *type_str;
+ BIO *b;
+
+ for (i = 0; i < n; i++) {
+ cert = sk_X509_value(sk, i);
+ public_key = X509_get_pubkey(cert);
+
+ /* We are trying to find a way to get the hash function used
+ * with a certificate. This way, which is not very pleasant, puts
+ * a string such as "sha256WithRSAEncryption" in our buffer and we
+ * then trim off the "With..." part.
+ */
+ b = BIO_new(BIO_s_mem());
+ const X509_ALGOR *sigalg = X509_get0_tbs_sigalg(cert);
+ rc = i2a_ASN1_OBJECT(b, sigalg->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")) {
+ if (print_chain)
+ MSG_WARN("In 2015, browsers have begun to deprecate SHA1 "
+ "certificates.\n");
+ } else if (!strncmp(buf, "md", 2) && success == TRUE) {
+ const char *msg = "A certificate in the chain uses the MD5 "
+ "signature algorithm, which is too weak "
+ "to trust.";
+ *choice = a_Dialog_choice("Dillo TLS security warning", msg,
+ "Continue", "Cancel", NULL);
+ if (*choice != 1)
+ success = FALSE;
+ }
+ }
+ }
+ BIO_free(b);
+
+ if (print_chain)
+ MSG("%s ", buf);
+
+ key_type = EVP_PKEY_type(EVP_PKEY_get_id(public_key));
+ type_str = key_type == EVP_PKEY_RSA ? "RSA" :
+ key_type == EVP_PKEY_DSA ? "DSA" :
+ key_type == EVP_PKEY_DH ? "DH" :
+ key_type == EVP_PKEY_EC ? "EC" : "???";
+ key_bits = EVP_PKEY_bits(public_key);
+ X509_NAME_oneline(X509_get_subject_name(cert), buf, buflen);
+ buf[buflen-1] = '\0';
+ if (print_chain)
+ MSG("%d-bit %s: %s\n", key_bits, type_str, buf);
+ EVP_PKEY_free(public_key);
+
+ if (key_type == EVP_PKEY_RSA && key_bits <= 1024) {
+ if (print_chain)
+ MSG_WARN("In 2014/5, browsers have been deprecating 1024-bit "
+ "RSA keys.\n");
+ }
+ }
+
+ if (cert) {
+ X509_NAME_oneline(X509_get_issuer_name(cert), buf, buflen);
+ buf[buflen-1] = '\0';
+ if (print_chain)
+ MSG("root: %s\n", buf);
+ }
+ }
+ return success;
+}
+
+/******************** BEGINNING OF STUFF DERIVED FROM wget-1.16.3 */
+
+#define ASTERISK_EXCLUDES_DOT /* mandated by rfc2818 */
+
+/* Return true is STRING (case-insensitively) matches PATTERN, false
+ otherwise. The recognized wildcard character is "*", which matches
+ any character in STRING except ".". Any number of the "*" wildcard
+ may be present in the pattern.
+
+ This is used to match of hosts as indicated in rfc2818: "Names may
+ contain the wildcard character * which is considered to match any
+ single domain name component or component fragment. E.g., *.a.com
+ matches foo.a.com but not bar.foo.a.com. f*.com matches foo.com but
+ not bar.com [or foo.bar.com]."
+
+ If 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';
+}
+
+/*
+ * 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 char *host,
+ int *choice)
+{
+ dReturn_val_if_fail(cert && host, FALSE);
+
+ char *msg;
+ GENERAL_NAMES *subjectAltNames;
+ bool_t success = TRUE, alt_name_checked = FALSE;;
+ char common_name[256];
+
+ /* Check that HOST matches the common name in the certificate.
+ #### The following remains to be done:
+
+ - When matching against common names, it should loop over all
+ common names and choose the most specific one, i.e. the last
+ one, not the first one, which the current code picks.
+
+ - Ensure that ASN1 strings from the certificate are encoded as
+ UTF-8 which can be meaningfully compared to HOST. */
+
+ subjectAltNames = X509_get_ext_d2i (cert, NID_subject_alt_name, NULL, NULL);
+
+ if (subjectAltNames)
+ {
+ /* Test subject alternative names */
+
+ Dstr *err = dStr_new("");
+ dStr_sprintf(err, "Hostname %s does not match any of certificate's "
+ "Subject Alternative Names: ", host);
+
+ /* Do we want to check for dNSNAmes or ipAddresses (see RFC 2818)?
+ * Signal it by host_in_octet_string. */
+ ASN1_OCTET_STRING *host_in_octet_string = a2i_IPADDRESS (host);
+
+ int numaltnames = sk_GENERAL_NAME_num (subjectAltNames);
+ int i;
+ for (i=0; i < numaltnames; i++)
+ {
+ const GENERAL_NAME *name =
+ sk_GENERAL_NAME_value (subjectAltNames, i);
+ if (name)
+ {
+ if (host_in_octet_string)
+ {
+ if (name->type == GEN_IPADD)
+ {
+ /* Check for ipAddress */
+ /* TODO: Should we convert between IPv4-mapped IPv6
+ * addresses and IPv4 addresses? */
+ alt_name_checked = TRUE;
+ if (!ASN1_STRING_cmp (host_in_octet_string,
+ name->d.iPAddress))
+ break;
+ dStr_sprintfa(err, "%s ", name->d.iPAddress);
+ }
+ }
+ else if (name->type == GEN_DNS)
+ {
+ /* dNSName should be IA5String (i.e. ASCII), however who
+ * does trust CA? Convert it into UTF-8 for sure. */
+ unsigned char *name_in_utf8 = NULL;
+
+ /* Check for dNSName */
+ alt_name_checked = TRUE;
+
+ if (0 <= ASN1_STRING_to_UTF8 (&name_in_utf8, name->d.dNSName))
+ {
+ /* Compare and check for NULL attack in ASN1_STRING */
+ if (pattern_match ((char *)name_in_utf8, host) &&
+ (strlen ((char *)name_in_utf8) ==
+ (size_t)ASN1_STRING_length (name->d.dNSName)))
+ {
+ OPENSSL_free (name_in_utf8);
+ break;
+ }
+ dStr_sprintfa(err, "%s ", name_in_utf8);
+ OPENSSL_free (name_in_utf8);
+ }
+ }
+ }
+ }
+ sk_GENERAL_NAME_pop_free(subjectAltNames, GENERAL_NAME_free);
+ if (host_in_octet_string)
+ ASN1_OCTET_STRING_free(host_in_octet_string);
+
+ if (alt_name_checked == TRUE && i >= numaltnames)
+ {
+ success = FALSE;
+ *choice = a_Dialog_choice("Dillo TLS security warning",
+ err->str, "Continue", "Cancel", NULL);
+
+ switch (*choice){
+ case 1:
+ success = TRUE;
+ break;
+ case 2:
+ break;
+ default:
+ break;
+ }
+ }
+ dStr_free(err, 1);
+ }
+
+ if (alt_name_checked == FALSE)
+ {
+ /* Test commomName */
+ X509_NAME *xname = X509_get_subject_name(cert);
+ common_name[0] = '\0';
+ X509_NAME_get_text_by_NID (xname, NID_commonName, common_name,
+ sizeof (common_name));
+
+ if (!pattern_match (common_name, host))
+ {
+ success = FALSE;
+ msg = dStrconcat("Certificate common name ", common_name,
+ " doesn't match requested host name ", host, NULL);
+ *choice = a_Dialog_choice("Dillo TLS security warning",
+ msg, "Continue", "Cancel", NULL);
+ dFree(msg);
+
+ switch (*choice){
+ case 1:
+ success = TRUE;
+ break;
+ case 2:
+ break;
+ default:
+ break;
+ }
+ }
+ else
+ {
+ /* We now determine the length of the ASN1 string. If it
+ * differs from common_name's length, then there is a \0
+ * before the string terminates. This can be an instance of a
+ * null-prefix attack.
+ *
+ * https://www.blackhat.com/html/bh-usa-09/bh-usa-09-archives.html#Marlinspike
+ * */
+
+ int i = -1, j;
+ X509_NAME_ENTRY *xentry;
+ ASN1_STRING *sdata;
+
+ if (xname) {
+ for (;;)
+ {
+ j = X509_NAME_get_index_by_NID (xname, NID_commonName, i);
+ if (j == -1) break;
+ i = j;
+ }
+ }
+
+ xentry = X509_NAME_get_entry(xname,i);
+ sdata = X509_NAME_ENTRY_get_data(xentry);
+ if (strlen (common_name) != (size_t)ASN1_STRING_length (sdata))
+ {
+ success = FALSE;
+ msg = dStrconcat("Certificate common name is invalid (contains a NUL "
+ "character). This may be an indication that the "
+ "host is not who it claims to be -- that is, not "
+ "the real ", host, NULL);
+ *choice = a_Dialog_choice("Dillo TLS security warning",
+ msg, "Continue", "Cancel", NULL);
+ dFree(msg);
+
+ switch (*choice){
+ case 1:
+ success = TRUE;
+ break;
+ case 2:
+ break;
+ default:
+ break;
+ }
+ }
+ }
+ }
+ return success;
+}
+
+/******************** 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 {
+ strcpy(buf, "(unknown)");
+ 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) {
+ strcpy(buf, "(unknown)");
+ buf[buflen-1] = '\0';
+ }
+ BIO_free(b);
+}
+
+/*
+ * 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, Server_t *srv)
+{
+ 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: ",srv->hostname,NULL);
+
+ 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_strength(ssl, srv, &choice) &&
+ Tls_check_cert_hostname(remote_cert, srv->hostname, &choice)) {
+ /* Figure out if (and why) the remote system can't be trusted */
+ st = SSL_get_verify_result(ssl);
+ switch (st) {
+ case X509_V_OK:
+ ret = 0;
+ break;
+ case X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT:
+ /* Either self signed and untrusted */
+ /* Extract CN from certificate name information */
+ X509_NAME *subject_name = X509_get_subject_name(remote_cert);
+ char *subj = X509_NAME_oneline(subject_name, NULL, 0);
+ if ((cn = strstr(subj, "/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';
+ }
+ OPENSSL_free(subj);
+ msg = dStrconcat("The remote certificate is self-signed and "
+ "untrusted. For address: ", buf, NULL);
+ choice = a_Dialog_choice(title,
+ msg, "Continue", "Cancel", "Save Certificate", NULL);
+ dFree(msg);
+
+ switch (choice){
+ case 1:
+ ret = 0;
+ break;
+ case 2:
+ break;
+ case 3:
+ /* Save certificate to a file */
+ Tls_save_certificate_home(remote_cert);
+ ret = 0;
+ break;
+ default:
+ break;
+ }
+ break;
+ case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT:
+ case X509_V_ERR_UNABLE_TO_DECODE_ISSUER_PUBLIC_KEY:
+ choice = a_Dialog_choice(title,
+ "The issuer for the remote certificate cannot be found. "
+ "The authenticity of the remote certificate cannot be trusted.",
+ "Continue", "Cancel", NULL);
+
+ if (choice == 1) {
+ ret = 0;
+ }
+ break;
+
+ case X509_V_ERR_UNABLE_TO_DECRYPT_CERT_SIGNATURE:
+ case X509_V_ERR_UNABLE_TO_DECRYPT_CRL_SIGNATURE:
+ case X509_V_ERR_CERT_SIGNATURE_FAILURE:
+ case X509_V_ERR_CRL_SIGNATURE_FAILURE:
+ choice = a_Dialog_choice(title,
+ "The remote certificate signature could not be read "
+ "or is invalid and should not be trusted",
+ "Continue", "Cancel", NULL);
+
+ if (choice == 1) {
+ ret = 0;
+ }
+ 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:
+ Tls_get_expiration_str(remote_cert, buf, buflen);
+ msg = dStrconcat("The remote certificate expired on: ", buf,
+ ". This site can no longer be trusted.", NULL);
+
+ choice = a_Dialog_choice(title, msg, "Continue", "Cancel", NULL);
+ if (choice == 1) {
+ ret = 0;
+ }
+ dFree(msg);
+ break;
+ case X509_V_ERR_ERROR_IN_CERT_NOT_BEFORE_FIELD:
+ case X509_V_ERR_ERROR_IN_CERT_NOT_AFTER_FIELD:
+ case X509_V_ERR_ERROR_IN_CRL_LAST_UPDATE_FIELD:
+ case X509_V_ERR_ERROR_IN_CRL_NEXT_UPDATE_FIELD:
+ choice = a_Dialog_choice(title,
+ "There was an error in the certificate presented. "
+ "Some of the certificate data was improperly formatted "
+ "making it impossible to determine if the certificate "
+ "is valid. You should not trust this certificate.",
+ "Continue", "Cancel", NULL);
+ if (choice == 1) {
+ ret = 0;
+ }
+ break;
+ case X509_V_ERR_INVALID_CA:
+ case X509_V_ERR_INVALID_PURPOSE:
+ case X509_V_ERR_CERT_UNTRUSTED:
+ case X509_V_ERR_CERT_REJECTED:
+ case X509_V_ERR_KEYUSAGE_NO_CERTSIGN:
+ choice = a_Dialog_choice(title,
+ "One of the certificates in the chain is being used "
+ "incorrectly (possibly due to configuration problems "
+ "with the remote system. The connection should not "
+ "be trusted",
+ "Continue", "Cancel", NULL);
+ if (choice == 1) {
+ ret = 0;
+ }
+ break;
+ case X509_V_ERR_SUBJECT_ISSUER_MISMATCH:
+ case X509_V_ERR_AKID_SKID_MISMATCH:
+ case X509_V_ERR_AKID_ISSUER_SERIAL_MISMATCH:
+ choice = a_Dialog_choice(title,
+ "Some of the information presented by the remote system "
+ "does not match other information presented. "
+ "This may be an attempt to eavesdrop on communications",
+ "Continue", "Cancel", NULL);
+ if (choice == 1) {
+ ret = 0;
+ }
+ break;
+ case X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN:
+ Tls_get_issuer_name(Tls_get_end_of_chain(ssl), buf, buflen);
+ msg = dStrconcat("Certificate chain led to a self-signed certificate "
+ "instead of a trusted root. Name: ", buf , NULL);
+ choice = a_Dialog_choice(title, msg, "Continue", "Cancel", NULL);
+ if (choice == 1) {
+ ret = 0;
+ }
+ dFree(msg);
+ break;
+ case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY:
+ Tls_get_issuer_name(Tls_get_end_of_chain(ssl), buf, buflen);
+ msg = dStrconcat("The issuer certificate of an untrusted certificate "
+ "cannot be found. Issuer: ", buf, NULL);
+ choice = a_Dialog_choice(title, msg, "Continue", "Cancel", NULL);
+ if (choice == 1) {
+ ret = 0;
+ }
+ dFree(msg);
+ break;
+ default: /* Need to add more options later */
+ snprintf(buf, 80,
+ "The remote certificate cannot be verified (code %ld)", st);
+ choice = a_Dialog_choice(title,
+ buf, "Continue", "Cancel", NULL);
+ /* abort on anything but "Continue" */
+ if (choice == 1){
+ ret = 0;
+ }
+ }
+ X509_free(remote_cert);
+ remote_cert = 0;
+ }
+ dFree(title);
+
+ if (choice == -1) {
+ srv->cert_status = CERT_STATUS_CLEAN; /* no warning popups */
+ } else if (choice == 1) {
+ srv->cert_status = CERT_STATUS_USER_ACCEPTED; /* clicked Continue */
+ } else {
+ /* 2 for Cancel, or 0 when window closed. Treating 0 as meaning 'No' is
+ * probably not exactly correct, but adding complexity to handle this
+ * obscure case does not seem justifiable.
+ */
+ srv->cert_status = CERT_STATUS_BAD;
+ }
+ return ret;
+}
+
+/*
+ * If the connection was closed before we got the certificate, we need to
+ * reset state so that we'll try again.
+ */
+void a_Tls_openssl_reset_server_state(const DilloUrl *url)
+{
+ if (servers) {
+ Server_t *s = dList_find_sorted(servers, url, Tls_servers_by_url_cmp);
+
+ if (s && s->cert_status == CERT_STATUS_RECEIVING)
+ s->cert_status = CERT_STATUS_NONE;
+ }
+}
+
+/*
+ * Close an open TLS connection.
+ */
+static void Tls_close_by_key(int connkey)
+{
+ Conn_t *c;
+
+ if ((c = a_Klist_get_data(conn_list, connkey))) {
+ a_Tls_openssl_reset_server_state(c->url);
+ if (c->connecting) {
+ a_IOwatch_remove_fd(c->fd, -1);
+ dClose(c->fd);
+ }
+ if (!SSL_in_init(c->ssl)) {
+ /* openssl 1.0.2f does not like shutdown being called during
+ * handshake, resulting in ssl_undefined_function in the error queue.
+ */
+ SSL_shutdown(c->ssl);
+ }
+ SSL_free(c->ssl);
+
+ 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 {
+ Server_t *srv = dList_find_sorted(servers, conn->url,
+ Tls_servers_by_url_cmp);
+
+ if (srv->cert_status == CERT_STATUS_RECEIVING) {
+ /* Making first connection with the server. Show cipher used. */
+ SSL *ssl = conn->ssl;
+ const char *version = SSL_get_version(ssl);
+ const SSL_CIPHER *cipher = SSL_get_current_cipher(ssl);
+
+ MSG("%s: %s, cipher %s\n", URL_AUTHORITY(conn->url), version,
+ SSL_CIPHER_get_name(cipher));
+ }
+
+ if (srv->cert_status == CERT_STATUS_USER_ACCEPTED ||
+ (Tls_examine_certificate(conn->ssl, srv) != -1)) {
+ failed = FALSE;
+ }
+ }
+
+ /*
+ * 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_openssl_connect(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_type(URL_HOST(url)) == URL_HOST_NAME)
+ SSL_set_tlsext_host_name(ssl, URL_HOST(url));
+#endif
+
+ if (!success) {
+ a_Tls_openssl_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_openssl_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_openssl_write(void *conn, void *buf, size_t len)
+{
+ Conn_t *c = (Conn_t*)conn;
+ return SSL_write(c->ssl, buf, len);
+}
+
+void a_Tls_openssl_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_openssl_freeall(void)
+{
+ if (ssl_context)
+ SSL_CTX_free(ssl_context);
+ Tls_fd_map_remove_all();
+ Tls_servers_freeall();
+}
diff --git a/src/IO/tls_openssl.h b/src/IO/tls_openssl.h
new file mode 100644
index 00000000..5cfd5dfd
--- /dev/null
+++ b/src/IO/tls_openssl.h
@@ -0,0 +1,49 @@
+/*
+ * File: tls_openssl.h
+ *
+ * Copyright 2004 Garrett Kajmowicz <gkajmowi@tbaytel.net>
+ * (for some bits derived from the https dpi, e.g., certificate handling)
+ * Copyright (C) 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008,
+ * 2009, 2010, 2011, 2012 Free Software Foundation, Inc.
+ * (for the certificate hostname checking from wget)
+ * Copyright (C) 2011 Benjamin Johnson <obeythepenguin@users.sourceforge.net>
+ * (for the https code offered from dplus browser that formed the basis...)
+ * Copyright (C) 2023 Rodrigo Arias Mallo <rodarima@gmail.com>
+ *
+ * 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.
+ */
+
+#ifndef __TLS_OPENSSL_H__
+#define __TLS_OPENSSL_H__
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include "../url.h"
+
+void a_Tls_openssl_init();
+int a_Tls_openssl_certificate_is_clean(const DilloUrl *url);
+int a_Tls_openssl_connect_ready(const DilloUrl *url);
+void a_Tls_openssl_reset_server_state(const DilloUrl *url);
+void a_Tls_openssl_connect(int fd, const DilloUrl *url);
+void *a_Tls_openssl_connection(int fd);
+void a_Tls_openssl_freeall();
+void a_Tls_openssl_close_by_fd(int fd);
+int a_Tls_openssl_read(void *conn, void *buf, size_t len);
+int a_Tls_openssl_write(void *conn, void *buf, size_t len);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __TLS_OPENSSL_H__ */
diff --git a/src/capi.c b/src/capi.c
index 594048d1..5dad8a67 100644
--- a/src/capi.c
+++ b/src/capi.c
@@ -2,6 +2,7 @@
* File: capi.c
*
* Copyright 2002-2007 Jorge Arellano Cid <jcid@dillo.org>
+ * Copyright 2023 Rodrigo Arias Mallo <rodarima@gmail.com>
*
* 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
@@ -454,7 +455,7 @@ int a_Capi_open_url(DilloWeb *web, CA_Callback_t Call, void *CbData)
!dStrAsciiCasecmp(scheme, "https")) {
/* http request */
-#ifndef ENABLE_SSL
+#ifndef ENABLE_TLS
if (!dStrAsciiCasecmp(scheme, "https")) {
if (web->flags & WEB_RootUrl)
a_UIcmd_set_msg(web->bw,
diff --git a/src/hsts.c b/src/hsts.c
index cc4d0dea..b11e9ffb 100644
--- a/src/hsts.c
+++ b/src/hsts.c
@@ -3,6 +3,7 @@
* HTTP Strict Transport Security
*
* Copyright 2015 corvid
+ * Copyright (C) 2023 Rodrigo Arias Mallo <rodarima@gmail.com>
*
* 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
@@ -195,7 +196,7 @@ static void Hsts_eat_value(const char **str)
*/
void a_Hsts_set(const char *header, const DilloUrl *url)
{
- long max_age;
+ long max_age = 0;
const char *host = URL_HOST(url);
bool_t max_age_valid = FALSE, subdomains = FALSE;