summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBjörn Stenberg <bjorn@haxx.se>2019-08-20 14:41:20 +0200
committerDaniel Stenberg <daniel@haxx.se>2019-08-20 14:41:20 +0200
commit54941ff781e11a7da3a584003647c7b01ca1454f (patch)
tree1b37ca18fc9c669fd34da57dd8ebe611bff9608d
parent862393243d16870347e56195e93dc47274e32601 (diff)
downloadcurl-bagder/hsts-rebased.tar.gz
HSTS: support for HTTP Strict Transport Security using libhstsbagder/hsts-rebased
This patch adds two new configure parameters: --with-libhsts=PATH to point to libhsts --with-hsts-file=FILE to specify location of the dafsa file that contains the domain database
-rwxr-xr-xconfigure.ac98
-rw-r--r--docs/libcurl/curl_version_info.33
-rw-r--r--docs/libcurl/symbols-in-versions1
-rw-r--r--include/curl/curl.h1
-rw-r--r--lib/url.c49
-rw-r--r--lib/urldata.h7
-rw-r--r--lib/version.c8
-rw-r--r--src/tool_help.c1
-rw-r--r--tests/data/Makefile.inc2
-rw-r--r--tests/data/hsts.dafsa2
-rw-r--r--tests/data/hsts.json9
-rw-r--r--tests/data/test208746
-rw-r--r--tests/data/test208845
-rwxr-xr-xtests/runtests.pl6
14 files changed, 275 insertions, 3 deletions
diff --git a/configure.ac b/configure.ac
index bd837e6a5..3e74e3a5f 100755
--- a/configure.ac
+++ b/configure.ac
@@ -175,6 +175,7 @@ curl_verbose_msg="enabled (--disable-verbose)"
curl_rtmp_msg="no (--with-librtmp)"
curl_mtlnk_msg="no (--with-libmetalink)"
curl_psl_msg="no (--with-libpsl)"
+ curl_hsts_msg="no (--with-libhsts)"
ssl_backends=
@@ -3584,6 +3585,102 @@ if test X"$want_quiche" != Xno; then
fi
dnl **********************************************************************
+dnl Check for libhsts
+dnl **********************************************************************
+dnl libhsts project home page: https://gitlab.com/rockdaboot/libhsts
+OPT_HSTS=off
+AC_ARG_WITH(libhsts,dnl
+AC_HELP_STRING([--with-libhsts=PATH],[Where to look for libhsts, PATH points to the libhsts installation; when possible, set the PKG_CONFIG_PATH environment variable instead of using this option])
+AC_HELP_STRING([--without-libhsts], [disable HSTS]),
+ OPT_HSTS=$withval)
+
+if test X"$OPT_HSTS" != Xno; then
+ dnl backup the pre-hsts variables
+ CLEANLDFLAGS="$LDFLAGS"
+ CLEANCPPFLAGS="$CPPFLAGS"
+ CLEANLIBS="$LIBS"
+
+ case "$OPT_HSTS" in
+ yes)
+ dnl --with-hsts (without path) used
+ CURL_CHECK_PKGCONFIG(libhsts)
+
+ if test "$PKGCONFIG" != "no" ; then
+ LIB_HSTS=`$PKGCONFIG --libs-only-l libhsts`
+ LD_HSTS=`$PKGCONFIG --libs-only-L libhsts`
+ CPP_HSTS=`$PKGCONFIG --cflags-only-I libhsts`
+ version=`$PKGCONFIG --modversion libhsts`
+ DIR_HSTS=`echo $LD_HSTS | $SED -e 's/-L//'`
+ fi
+
+ ;;
+ off)
+ dnl no --with-hsts option given, just check default places
+ ;;
+ *)
+ dnl use the given --with-hsts spot
+ PREFIX_HSTS=$OPT_HSTS
+ ;;
+ esac
+
+ dnl if given with a prefix, we set -L and -I based on that
+ if test -n "$PREFIX_HSTS"; then
+ LIB_HSTS="-lhsts"
+ LD_HSTS=-L${PREFIX_HSTS}/lib$libsuff
+ CPP_HSTS=-I${PREFIX_HSTS}/include
+ DIR_HSTS=${PREFIX_HSTS}/lib$libsuff
+ fi
+
+ LDFLAGS="$LDFLAGS $LD_HSTS"
+ CPPFLAGS="$CPPFLAGS $CPP_HSTS"
+ LIBS="$LIB_HSTS $LIBS"
+
+ AC_CHECK_LIB(hsts, hsts_search)
+
+ AC_CHECK_HEADERS(libhsts.h,
+ curl_hsts_msg="enabled (libhsts)"
+ HAVE_HSTS=1
+ AC_DEFINE(USE_HSTS, 1, [if HSTS is in use])
+ AC_SUBST(USE_HSTS, [1])
+ )
+
+ if test X"$OPT_HSTS" != Xoff &&
+ test "$HAVE_HSTS" != "1"; then
+ AC_MSG_ERROR([HSTS libs and/or directories were not found where specified!])
+ fi
+
+ if test "$HAVE_HSTS" = "1"; then
+ if test -n "$DIR_HSTS"; then
+ dnl when the hsts shared libs were found in a path that the run-time
+ dnl linker doesn't search through, we need to add it to LD_LIBRARY_PATH
+ dnl to prevent further configure tests to fail due to this
+
+ if test "x$cross_compiling" != "xyes"; then
+ LD_LIBRARY_PATH="$LD_LIBRARY_PATH:$DIR_HSTS"
+ export LD_LIBRARY_PATH
+ AC_MSG_NOTICE([Added $DIR_HSTS to LD_LIBRARY_PATH])
+ fi
+ fi
+
+ AC_ARG_WITH(hsts-file,
+ AC_HELP_STRING([--with-hsts-file=FILE],
+ [Path to hsts.dafsa file]),
+ [ HSTS_FILE="$withval" ] )
+ if test -n "$HSTS_FILE" ; then
+ AC_DEFINE_UNQUOTED(HSTS_FILE, "$HSTS_FILE",
+ [Path to hsts.dafsa file] )
+ else
+ AC_MSG_ERROR([When enabling HSTS, you also need to specify --with-hsts-file pointing to the hsts.dafsa file])
+ fi
+ else
+ dnl no hsts, revert back to clean variables
+ LDFLAGS=$CLEANLDFLAGS
+ CPPFLAGS=$CLEANCPPFLAGS
+ LIBS=$CLEANLIBS
+ fi
+fi
+
+dnl **********************************************************************
dnl Check for zsh completion path
dnl **********************************************************************
@@ -4722,6 +4819,7 @@ AC_MSG_NOTICE([Configured to build curl/libcurl:
Alt-svc: ${curl_altsvc_msg}
HTTP2: ${curl_h2_msg}
HTTP3: ${curl_h3_msg}
+ HSTS: ${curl_hsts_msg} (using ${HSTS_FILE})
Protocols: ${SUPPORT_PROTOCOLS}
Features: ${SUPPORT_FEATURES}
])
diff --git a/docs/libcurl/curl_version_info.3 b/docs/libcurl/curl_version_info.3
index de56c4c6d..449368715 100644
--- a/docs/libcurl/curl_version_info.3
+++ b/docs/libcurl/curl_version_info.3
@@ -131,6 +131,9 @@ to use the current user credentials without the app having to pass them on.
(Added in 7.38.0)
.IP CURL_VERSION_GSSNEGOTIATE
supports HTTP GSS-Negotiate (added in 7.10.6)
+.IP CURL_VERSION_HSTS
+libcurl was built with support for HSTS
+(Added in 7.67.0)
.IP CURL_VERSION_HTTPS_PROXY
libcurl was built with support for HTTPS-proxy.
(Added in 7.52.0)
diff --git a/docs/libcurl/symbols-in-versions b/docs/libcurl/symbols-in-versions
index 9daad949f..210f40ff4 100644
--- a/docs/libcurl/symbols-in-versions
+++ b/docs/libcurl/symbols-in-versions
@@ -927,6 +927,7 @@ CURL_VERSION_CURLDEBUG 7.19.6
CURL_VERSION_DEBUG 7.10.6
CURL_VERSION_GSSAPI 7.38.0
CURL_VERSION_GSSNEGOTIATE 7.10.6 7.38.0
+CURL_VERSION_HSTS 7.67.0
CURL_VERSION_HTTP2 7.33.0
CURL_VERSION_HTTP3 7.66.0
CURL_VERSION_HTTPS_PROXY 7.52.0
diff --git a/include/curl/curl.h b/include/curl/curl.h
index f94afd7a3..70da1b3c8 100644
--- a/include/curl/curl.h
+++ b/include/curl/curl.h
@@ -2799,6 +2799,7 @@ typedef struct {
#define CURL_VERSION_BROTLI (1<<23) /* Brotli features are present. */
#define CURL_VERSION_ALTSVC (1<<24) /* Alt-Svc handling built-in */
#define CURL_VERSION_HTTP3 (1<<25) /* HTTP3 support built-in */
+#define CURL_VERSION_HSTS (1<<26) /* HSTS features are present */
/*
* NAME curl_version_info()
diff --git a/lib/url.c b/lib/url.c
index 30263258f..f62e556b2 100644
--- a/lib/url.c
+++ b/lib/url.c
@@ -122,6 +122,9 @@ bool curl_win32_idn_to_ascii(const char *in, char **out);
#include "strdup.h"
#include "setopt.h"
#include "altsvc.h"
+#ifdef USE_HSTS
+#include <libhsts.h>
+#endif
/* The last 3 #include files should be in this order */
#include "curl_printf.h"
@@ -291,6 +294,10 @@ void Curl_freeset(struct Curl_easy *data)
data->change.url = NULL;
Curl_mime_cleanpart(&data->set.mimepost);
+
+#ifdef USE_HSTS
+ hsts_free(data->set.hsts);
+#endif
}
/* free the URL pieces */
@@ -616,6 +623,27 @@ CURLcode Curl_open(struct Curl_easy **curl)
Curl_http2_init_state(&data->state);
}
+
+#ifdef USE_HSTS
+ {
+ hsts_status_t hsts_status = HSTS_ERR_INPUT_FAILURE;
+#if DEBUGBUILD
+ char *debug_hsts_file = curl_getenv("CURL_HSTSFILE");
+ if(debug_hsts_file) {
+ DEBUGF(fprintf(stderr, "DEBUG: HSTS file override: %s\n",
+ debug_hsts_file));
+ hsts_status = hsts_load_file(debug_hsts_file, &data->set.hsts);
+ free(debug_hsts_file);
+ }
+ else
+#endif
+ hsts_status = hsts_load_file(HSTS_FILE, &data->set.hsts);
+ if(hsts_status < HSTS_SUCCESS) {
+ fprintf(stderr, "Failed loading HSTS database, error code %d!\n",
+ hsts_status);
+ }
+ }
+#endif
}
if(result) {
@@ -1819,22 +1847,37 @@ const struct Curl_handler *Curl_builtin_scheme(const char *scheme)
{
const struct Curl_handler * const *pp;
const struct Curl_handler *p;
- /* Scan protocol handler table and match against 'scheme'. The handler may
- be changed later when the protocol specific setup function is called. */
+
+ /* Scan protocol handler table and match against 'protostr' to set a few
+ variables based on the URL. Now that the handler may be changed later
+ when the protocol specific setup function is called. */
for(pp = protocols; (p = *pp) != NULL; pp++)
if(strcasecompare(p->scheme, scheme))
/* Protocol found in table. Check if allowed */
return p;
+
return NULL; /* not found */
}
-
static CURLcode findprotocol(struct Curl_easy *data,
struct connectdata *conn,
const char *protostr)
{
const struct Curl_handler *p = Curl_builtin_scheme(protostr);
+#ifdef USE_HSTS
+ /* HSTS means we override any http access with https if the domain
+ is listed in the HSTS database */
+ if(data->set.hsts && strcasecompare(protostr, "http")) {
+ hsts_status_t hsts_status = hsts_search(data->set.hsts,
+ conn->host.name, 0, NULL);
+ if(hsts_status == HSTS_SUCCESS) {
+ infof(data, "Domain found in HSTS database, upgrading to https\n");
+ p = Curl_builtin_scheme("https");
+ }
+ }
+#endif
+
if(p && /* Protocol found in table. Check if allowed */
(data->set.allowed_protocols & p->protocol)) {
diff --git a/lib/urldata.h b/lib/urldata.h
index 7f26a9561..f3ff8f8ab 100644
--- a/lib/urldata.h
+++ b/lib/urldata.h
@@ -316,6 +316,10 @@ typedef enum {
#include <iconv.h>
#endif
+#ifdef USE_HSTS
+#include <libhsts.h>
+#endif
+
/* Struct used for GSSAPI (Kerberos V5) authentication */
#if defined(USE_KERBEROS5)
struct kerberos5data {
@@ -1763,6 +1767,9 @@ struct UserDefined {
bit doh:1; /* DNS-over-HTTPS enabled */
bit doh_get:1; /* use GET for DoH requests, instead of POST */
bit http09_allowed:1; /* allow HTTP/0.9 responses */
+#ifdef USE_HSTS
+ hsts_t *hsts; /* libhsts handle */
+#endif
};
struct Names {
diff --git a/lib/version.c b/lib/version.c
index ae2b09d31..c3df3fef6 100644
--- a/lib/version.c
+++ b/lib/version.c
@@ -204,6 +204,11 @@ char *curl_version(void)
*/
}
#endif
+#ifdef USE_HSTS
+ len = msnprintf(ptr, left, " libhsts/%s", hsts_get_version());
+ left -= len;
+ ptr += len;
+#endif
/* Silent scan-build even if librtmp is not enabled. */
(void) left;
@@ -369,6 +374,9 @@ static curl_version_info_data version_info = {
#if defined(USE_ALTSVC)
| CURL_VERSION_ALTSVC
#endif
+#ifdef USE_HSTS
+ | CURL_VERSION_HSTS
+#endif
,
NULL, /* ssl_version */
0, /* ssl_version_num, this is kept at zero */
diff --git a/src/tool_help.c b/src/tool_help.c
index cb0afef56..742a76d81 100644
--- a/src/tool_help.c
+++ b/src/tool_help.c
@@ -540,6 +540,7 @@ static const struct feat feats[] = {
{"MultiSSL", CURL_VERSION_MULTI_SSL},
{"PSL", CURL_VERSION_PSL},
{"alt-svc", CURL_VERSION_ALTSVC},
+ {"HSTS", CURL_VERSION_HSTS},
};
void tool_help(void)
diff --git a/tests/data/Makefile.inc b/tests/data/Makefile.inc
index fc22c2f17..0b1344374 100644
--- a/tests/data/Makefile.inc
+++ b/tests/data/Makefile.inc
@@ -203,6 +203,8 @@ test2064 test2065 test2066 test2067 test2068 test2069 \
test2071 test2072 test2073 test2074 test2075 test2076 test2077 \
test2078 \
test2080 \
+test2087 test2088 \
+\
test2100 \
\
test3000 test3001
diff --git a/tests/data/hsts.dafsa b/tests/data/hsts.dafsa
new file mode 100644
index 000000000..447f9540a
--- /dev/null
+++ b/tests/data/hsts.dafsa
@@ -0,0 +1,2 @@
+.DAFSA@HSTS_0
+hstsdomain.fake \ No newline at end of file
diff --git a/tests/data/hsts.json b/tests/data/hsts.json
new file mode 100644
index 000000000..f506f8953
--- /dev/null
+++ b/tests/data/hsts.json
@@ -0,0 +1,9 @@
+{
+ "entries": [
+ { "name": "hstsdomain.fake",
+ "policy": "test",
+ "include_subdomains": true,
+ "mode": "force-https"
+ }
+ ]
+}
diff --git a/tests/data/test2087 b/tests/data/test2087
new file mode 100644
index 000000000..c9f7e6f1d
--- /dev/null
+++ b/tests/data/test2087
@@ -0,0 +1,46 @@
+<testcase>
+<info>
+<keywords>
+HSTS
+HTTP
+</keywords>
+</info>
+
+# Client-side
+<client>
+<features>
+HSTS
+debug
+</features>
+<server>
+http
+http-proxy
+</server>
+<name>
+Check that HSTS does not upgrade domain without record
+</name>
+<setenv>
+CURL_HSTSFILE=data/hsts.dafsa
+</setenv>
+<command>
+-x %HOSTIP:%PROXYPORT http://nohstsdomain.fake/
+</command>
+</client>
+
+# Verify data after the test has been "shot"
+<verify>
+<strip>
+^User-Agent:.*
+</strip>
+<proxy>
+GET http://nohstsdomain.fake/ HTTP/1.1
+Host: nohstsdomain.fake
+Accept: */*
+Proxy-Connection: Keep-Alive
+
+</proxy>
+<errorcode>
+0
+</errorcode>
+</verify>
+</testcase>
diff --git a/tests/data/test2088 b/tests/data/test2088
new file mode 100644
index 000000000..569ac02f0
--- /dev/null
+++ b/tests/data/test2088
@@ -0,0 +1,45 @@
+<testcase>
+<info>
+<keywords>
+HSTS
+HTTPS
+</keywords>
+</info>
+
+# Client-side
+<client>
+<features>
+HSTS
+debug
+</features>
+<server>
+https
+http-proxy
+</server>
+<name>
+Check that HSTS upgrades http to https
+</name>
+<setenv>
+CURL_HSTSFILE=data/hsts.dafsa
+</setenv>
+<command>
+-x %HOSTIP:%PROXYPORT http://hstsdomain.fake/
+</command>
+</client>
+
+# Verify data after the test has been "shot"
+<verify>
+<strip>
+^User-Agent:.*
+</strip>
+<proxy>
+CONNECT hstsdomain.fake:443 HTTP/1.1
+Host: hstsdomain.fake:443
+Proxy-Connection: Keep-Alive
+
+</proxy>
+<errorcode>
+56
+</errorcode>
+</verify>
+</testcase>
diff --git a/tests/runtests.pl b/tests/runtests.pl
index 9413f0d41..13b58cba6 100755
--- a/tests/runtests.pl
+++ b/tests/runtests.pl
@@ -237,6 +237,7 @@ my $has_altsvc; # set if libcurl is built with alt-svc support
my $has_ldpreload; # set if curl is built for systems supporting LD_PRELOAD
my $has_multissl; # set if curl is build with MultiSSL support
my $has_manual; # set if curl is built with built-in manual
+my $has_hsts; # set if curl is built with HSTS support
# this version is decided by the particular nghttp2 library that is being used
my $h2cver = "h2c";
@@ -2600,6 +2601,7 @@ sub setupfeatures {
$feature{"alt-svc"} = $has_altsvc;
$feature{"manual"} = $has_manual;
$feature{"unix-sockets"} = $has_unix;
+ $feature{"HSTS"} = $has_hsts;
# make each protocol an enabled "feature"
for my $p (@protocols) {
@@ -2847,6 +2849,10 @@ sub checksystem {
push @protocols, 'http/2';
}
+ if($feat =~ /HSTS/) {
+ # HSTS enabled
+ $has_hsts=1;
+ }
}
#
# Test harness currently uses a non-stunnel server in order to