diff options
author | Daniel Stenberg <daniel@haxx.se> | 2020-11-02 23:17:01 +0100 |
---|---|---|
committer | Daniel Stenberg <daniel@haxx.se> | 2020-11-03 16:08:42 +0100 |
commit | 7385610d0c74c6a254fea5e4cd6e1d559d848c8c (patch) | |
tree | 3b572bcf972062b7cc1315ac23fdb547e7216463 /tests | |
parent | 9f43b28f783cc8f7464492a0b5b9dd35c1625fde (diff) | |
download | curl-7385610d0c74c6a254fea5e4cd6e1d559d848c8c.tar.gz |
hsts: add support for Strict-Transport-Security
- enable in the build (configure)
- header parsing
- host name lookup
- unit tests for the above
- CI build
- CURL_VERSION_HSTS bit
- curl_version_info support
- curl -V output
- curl-config --features
- CURLOPT_HSTS_CTRL
- man page for CURLOPT_HSTS_CTRL
- curl --hsts (sets CURLOPT_HSTS_CTRL and works with --libcurl)
- man page for --hsts
- save cache to disk
- load cache from disk
- CURLOPT_HSTS
- man page for CURLOPT_HSTS
- added docs/HSTS.md
- fixed --version docs
- adjusted curl_easy_duphandle
Closes #5896
Diffstat (limited to 'tests')
-rw-r--r-- | tests/data/Makefile.inc | 1 | ||||
-rw-r--r-- | tests/data/test1660 | 81 | ||||
-rwxr-xr-x | tests/runtests.pl | 5 | ||||
-rw-r--r-- | tests/unit/Makefile.inc | 5 | ||||
-rw-r--r-- | tests/unit/unit1660.c | 172 |
5 files changed, 263 insertions, 1 deletions
diff --git a/tests/data/Makefile.inc b/tests/data/Makefile.inc index 2e0c092ad..04e23c6fc 100644 --- a/tests/data/Makefile.inc +++ b/tests/data/Makefile.inc @@ -197,6 +197,7 @@ test1620 test1621 \ test1630 test1631 test1632 test1633 \ \ test1650 test1651 test1652 test1653 test1654 test1655 \ +test1660 \ \ test1700 test1701 test1702 \ \ diff --git a/tests/data/test1660 b/tests/data/test1660 new file mode 100644 index 000000000..f64765c3f --- /dev/null +++ b/tests/data/test1660 @@ -0,0 +1,81 @@ +<testcase> +<info> +<keywords> +unittest +HSTS +</keywords> +</info> + +<client> +<server> +none +</server> +<features> +unittest +HSTS +</features> + +<file name="log/input1660"> +# Your HSTS cache. https://curl.haxx.se/docs/hsts.html +# This file was generated by libcurl! Edit at your own risk. +.readfrom.example "20211001 04:47:41" +.old.example "20161001 04:47:41" +</file> + +# This date is exactly "20190124 22:34:21" UTC +<setenv> +CURL_TIME=1548369261 +</setenv> +<name> +HSTS +</name> +<command> +- +</command> +</client> + +<verify> +<stdout> +readfrom.example [readfrom.example]: 1633063661 includeSubDomains +'old.example' is not HSTS +'readfrom.example' is not HSTS +example.com [example.com]: 1579905261 +example.com [example.com]: 1569905261 +example.com [example.com]: 1569905261 +example.com [example.com]: 1569905261 includeSubDomains +example.org [example.org]: 1579905261 +Input 8: error 43 +Input 9: error 43 +this.example [this.example]: 1548400797 +'this.example' is not HSTS +Input 12: error 43 +Input 13: error 43 +Input 14: error 43 +3.example.com [example.com]: 1569905261 includeSubDomains +3.example.com [example.com]: 1569905261 includeSubDomains +foo.example.com [example.com]: 1569905261 includeSubDomains +'foo.xample.com' is not HSTS +'forexample.net' is not HSTS +'forexample.net' is not HSTS +'example.net' is not HSTS +expire.example [expire.example]: 1548369268 +Number of entries: 3 +expire.example [expire.example]: 1548369268 +expire.example [expire.example]: 1548369268 +expire.example [expire.example]: 1548369268 +expire.example [expire.example]: 1548369268 +expire.example [expire.example]: 1548369268 +expire.example [expire.example]: 1548369268 +expire.example [expire.example]: 1548369268 +'expire.example' is not HSTS +'expire.example' is not HSTS +'expire.example' is not HSTS +</stdout> +<file name="log/hsts1660"> +# Your HSTS cache. https://curl.haxx.se/docs/hsts.html +# This file was generated by libcurl! Edit at your own risk. +.example.com "20191001 04:47:41" +example.org "20200124 22:34:21" +</file> +</verify> +</testcase> diff --git a/tests/runtests.pl b/tests/runtests.pl index 4bcf61bbf..a4e330661 100755 --- a/tests/runtests.pl +++ b/tests/runtests.pl @@ -256,6 +256,7 @@ my $has_cares; # set if built with c-ares my $has_threadedres;# set if built with threaded resolver my $has_psl; # set if libcurl is built with PSL support my $has_altsvc; # set if libcurl is built with alt-svc support +my $has_hsts; # set if libcurl is built with HSTS 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 @@ -2762,6 +2763,7 @@ sub compare { sub setupfeatures { $feature{"alt-svc"} = $has_altsvc; + $feature{"HSTS"} = $has_hsts; $feature{"brotli"} = $has_brotli; $feature{"crypto"} = $has_crypto; $feature{"debug"} = $debug_build; @@ -3035,6 +3037,9 @@ sub checksystem { # alt-svc enabled $has_altsvc=1; } + if($feat =~ /HSTS/i) { + $has_hsts=1; + } if($feat =~ /AsynchDNS/i) { if(!$has_cares) { # this means threaded resolver diff --git a/tests/unit/Makefile.inc b/tests/unit/Makefile.inc index f63724f91..ee6816823 100644 --- a/tests/unit/Makefile.inc +++ b/tests/unit/Makefile.inc @@ -34,7 +34,8 @@ UNITPROGS = unit1300 unit1301 unit1302 unit1303 unit1304 unit1305 unit1307 \ unit1600 unit1601 unit1602 unit1603 unit1604 unit1605 unit1606 unit1607 \ unit1608 unit1609 unit1610 unit1611 unit1612 \ unit1620 unit1621 \ - unit1650 unit1651 unit1652 unit1653 unit1654 unit1655 + unit1650 unit1651 unit1652 unit1653 unit1654 unit1655 \ + unit1660 unit1300_SOURCES = unit1300.c $(UNITFILES) unit1300_CPPFLAGS = $(AM_CPPFLAGS) @@ -154,3 +155,5 @@ unit1654_CPPFLAGS = $(AM_CPPFLAGS) unit1655_SOURCES = unit1655.c $(UNITFILES) unit1655_CPPFLAGS = $(AM_CPPFLAGS) +unit1660_SOURCES = unit1660.c $(UNITFILES) +unit1660_CPPFLAGS = $(AM_CPPFLAGS) diff --git a/tests/unit/unit1660.c b/tests/unit/unit1660.c new file mode 100644 index 000000000..1687cafa1 --- /dev/null +++ b/tests/unit/unit1660.c @@ -0,0 +1,172 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) 2020, Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.haxx.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ***************************************************************************/ +#include "curlcheck.h" + +#include "urldata.h" +#include "hsts.h" + +static CURLcode +unit_setup(void) +{ + return CURLE_OK; +} + +static void +unit_stop(void) +{ + curl_global_cleanup(); +} + +#if defined(CURL_DISABLE_HTTP) || !defined(USE_HSTS) +UNITTEST_START +{ + return 0; /* nothing to do when HTTP or HSTS are disabled */ +} +UNITTEST_STOP +#else + +struct testit { + const char *host; + const char *chost; /* if non-NULL, use to lookup with */ + const char *hdr; /* if NULL, just do the lookup */ + const CURLcode result; /* parse result */ +}; + +static const struct testit headers[] = { + /* two entries read from disk cache, verify first */ + { "-", "readfrom.example", NULL, CURLE_OK}, + { "-", "old.example", NULL, CURLE_OK}, + /* delete the remaining one read from disk */ + { "readfrom.example", NULL, "max-age=\"0\"", CURLE_OK}, + + { "example.com", NULL, "max-age=\"31536000\"\r\n", CURLE_OK }, + { "example.com", NULL, "max-age=\"21536000\"\r\n", CURLE_OK }, + { "example.com", NULL, "max-age=\"21536000\"; \r\n", CURLE_OK }, + { "example.com", NULL, "max-age=\"21536000\"; includeSubDomains\r\n", + CURLE_OK }, + { "example.org", NULL, "max-age=\"31536000\"\r\n", CURLE_OK }, + { "this.example", NULL, "max=\"31536\";", CURLE_BAD_FUNCTION_ARGUMENT }, + { "this.example", NULL, "max-age=\"31536", CURLE_BAD_FUNCTION_ARGUMENT }, + { "this.example", NULL, "max-age=31536\"", CURLE_OK }, + /* max-age=0 removes the entry */ + { "this.example", NULL, "max-age=0", CURLE_OK }, + { "another.example", NULL, "includeSubDomains; ", + CURLE_BAD_FUNCTION_ARGUMENT }, + + /* Two max-age is illegal */ + { "example.com", NULL, + "max-age=\"21536000\"; includeSubDomains; max-age=\"3\";", + CURLE_BAD_FUNCTION_ARGUMENT }, + /* Two includeSubDomains is illegal */ + { "2.example.com", NULL, + "max-age=\"21536000\"; includeSubDomains; includeSubDomains;", + CURLE_BAD_FUNCTION_ARGUMENT }, + /* use a unknown directive "include" that should be ignored */ + { "3.example.com", NULL, "max-age=\"21536000\"; include; includeSubDomains;", + CURLE_OK }, + /* remove the "3.example.com" one, should still match the example.com */ + { "3.example.com", NULL, "max-age=\"0\"; includeSubDomains;", + CURLE_OK }, + { "-", "foo.example.com", NULL, CURLE_OK}, + { "-", "foo.xample.com", NULL, CURLE_OK}, + + /* should not match */ + { "example.net", "forexample.net", "max-age=\"31536000\"\r\n", CURLE_OK }, + + /* should not match either, since forexample.net is not in the example.net + domain */ + { "example.net", "forexample.net", + "max-age=\"31536000\"; includeSubDomains\r\n", CURLE_OK }, + /* remove example.net again */ + { "example.net", NULL, "max-age=\"0\"; includeSubDomains\r\n", CURLE_OK }, + + /* make this live for 7 seconds */ + { "expire.example", NULL, "max-age=\"7\"\r\n", CURLE_OK }, + { NULL, NULL, NULL, 0 } +}; + +static void showsts(struct stsentry *e, const char *chost) +{ + if(!e) + printf("'%s' is not HSTS\n", chost); + else { + printf("%s [%s]: %" CURL_FORMAT_CURL_OFF_T "%s\n", + chost, e->host, e->expires, + e->includeSubDomains ? " includeSubDomains" : ""); + } +} + +UNITTEST_START +{ + CURLcode result; + struct stsentry *e; + struct hsts *h = Curl_hsts_init(); + int i; + const char *chost; + CURL *easy; + if(!h) + return 1; + + Curl_hsts_load(h, "log/input1660"); + + for(i = 0; headers[i].host ; i++) { + if(headers[i].hdr) { + result = Curl_hsts_parse(h, headers[i].host, headers[i].hdr); + + if(result != headers[i].result) { + fprintf(stderr, "Curl_hsts_parse(%s) failed: %d\n", + headers[i].hdr, result); + unitfail++; + continue; + } + else if(result) { + printf("Input %u: error %d\n", i, (int) result); + continue; + } + } + + chost = headers[i].chost ? headers[i].chost : headers[i].host; + e = Curl_hsts(h, chost, TRUE); + showsts(e, chost); + } + + printf("Number of entries: %d\n", h->list.size); + + /* verify that it is exists for 7 seconds */ + chost = "expire.example"; + for(i = 100; i < 110; i++) { + e = Curl_hsts(h, chost, TRUE); + showsts(e, chost); + deltatime++; /* another second passed */ + } + + easy = curl_easy_init(); + if(easy) { + (void)Curl_hsts_save(easy, h, "log/hsts1660"); + curl_easy_cleanup(easy); + } + + Curl_hsts_cleanup(&h); + return unitfail; +} +UNITTEST_STOP +#endif |