diff options
author | Björn Stenberg <bjorn@haxx.se> | 2019-08-20 14:41:20 +0200 |
---|---|---|
committer | Daniel Stenberg <daniel@haxx.se> | 2019-08-20 14:41:20 +0200 |
commit | 54941ff781e11a7da3a584003647c7b01ca1454f (patch) | |
tree | 1b37ca18fc9c669fd34da57dd8ebe611bff9608d | |
parent | 862393243d16870347e56195e93dc47274e32601 (diff) | |
download | curl-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-x | configure.ac | 98 | ||||
-rw-r--r-- | docs/libcurl/curl_version_info.3 | 3 | ||||
-rw-r--r-- | docs/libcurl/symbols-in-versions | 1 | ||||
-rw-r--r-- | include/curl/curl.h | 1 | ||||
-rw-r--r-- | lib/url.c | 49 | ||||
-rw-r--r-- | lib/urldata.h | 7 | ||||
-rw-r--r-- | lib/version.c | 8 | ||||
-rw-r--r-- | src/tool_help.c | 1 | ||||
-rw-r--r-- | tests/data/Makefile.inc | 2 | ||||
-rw-r--r-- | tests/data/hsts.dafsa | 2 | ||||
-rw-r--r-- | tests/data/hsts.json | 9 | ||||
-rw-r--r-- | tests/data/test2087 | 46 | ||||
-rw-r--r-- | tests/data/test2088 | 45 | ||||
-rwxr-xr-x | tests/runtests.pl | 6 |
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() @@ -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 |