hsts: duplicate live HSTS data in curl_easy_duphandle
No binary (build failed)
—
(log yok)
| @@ -42,7 +42,9 @@ SSL sessions and no cookies. It also does not inherit any share object states | |
| or options (created as if CURLOPT_SHARE(3) was set to NULL). | |
| If the source handle has HSTS or alt-svc enabled, the duplicate gets data read | |
| - | data from the main filename to populate the cache. |
| + | from the main filename to populate the cache. For HSTS, any entries learned at |
| + | runtime (E.g. `Strict-Transport-Security` response headers) are also copied to |
| + | the duplicate handle. |
| In multi-threaded programs, this function must be called in a synchronous way, | |
| the input handle may not be in use when cloned. | |
| @@ -1052,6 +1052,11 @@ CURL *curl_easy_duphandle(CURL *curl) | |
| (void)Curl_hsts_loadfile(outcurl, | |
| outcurl->hsts, outcurl->set.str[STRING_HSTS]); | |
| (void)Curl_hsts_loadcb(outcurl, outcurl->hsts); | |
| + | |
| + | /* Copy entries learned at runtime. (E.g. Strict-Transport-Security |
| + | headers.) */ |
| + | if(Curl_hsts_copy(outcurl->hsts, data->hsts)) |
| + | goto fail; |
| } | |
| #endif | |
| @@ -130,6 +130,25 @@ static CURLcode hsts_create(struct hsts *h, | |
| return CURLE_OK; | |
| } | |
| + | /* Copy all live entries from src into dst. Used by curl_easy_duphandle so the |
| + | * clone inherits entries learned at runtime. E.g. Strict-Transport-Security. |
| + | */ |
| + | CURLcode Curl_hsts_copy(struct hsts *dst, struct hsts *src) |
| + | { |
| + | struct Curl_llist_node *e; |
| + | time_t now = time(NULL); |
| + | for(e = Curl_llist_head(&src->list); e; e = Curl_node_next(e)) { |
| + | struct stsentry *sts = Curl_node_elem(e); |
| + | if(sts->expires > now) { |
| + | CURLcode result = hsts_create(dst, sts->host, strlen(sts->host), |
| + | sts->includeSubDomains != 0, sts->expires); |
| + | if(result) |
| + | return result; |
| + | } |
| + | } |
| + | return CURLE_OK; |
| + | } |
| + | |
| /* | |
| * Return the matching HSTS entry, or NULL if the given hostname is not | |
| * currently an HSTS one. | |
| @@ -54,6 +54,7 @@ struct hsts { | |
| struct hsts *Curl_hsts_init(void); | |
| void Curl_hsts_cleanup(struct hsts **hp); | |
| + | CURLcode Curl_hsts_copy(struct hsts *dst, struct hsts *src); |
| CURLcode Curl_hsts_parse(struct hsts *h, const char *hostname, | |
| const char *header); | |
| CURLcode Curl_hsts_save(struct Curl_easy *data, struct hsts *h, | |
| @@ -231,7 +231,7 @@ test1800 test1801 test1802 test1847 test1848 test1849 test1850 test1851 \ | |
| \ | |
| test1900 test1901 test1902 test1903 test1904 test1905 test1906 test1907 \ | |
| test1908 test1909 test1910 test1911 test1912 test1913 test1914 test1915 \ | |
| - | test1916 test1917 test1918 test1919 test1920 test1921 \ |
| + | test1916 test1917 test1918 test1919 test1920 test1921 test1922 \ |
| \ | |
| test1933 test1934 test1935 test1936 test1937 test1938 test1939 test1940 \ | |
| test1941 test1942 test1943 test1944 test1945 test1946 test1947 test1948 \ | |
| @@ -0,0 +1,91 @@ | |
| + | <?xml version="1.0" encoding="US-ASCII"?> |
| + | <testcase> |
| + | <info> |
| + | <keywords> |
| + | HTTP |
| + | HTTP proxy |
| + | HSTS |
| + | curl_easy_duphandle |
| + | </keywords> |
| + | </info> |
| + | |
| + | <reply> |
| + | <!-- Response to the original handle's direct HTTP request. |
| + | Strict-Transport-Security header populates the live HSTS cache. --> |
| + | <data nocheck="yes" crlf="headers"> |
| + | HTTP/1.1 200 OK |
| + | Date: Tue, 09 Nov 2010 14:49:00 GMT |
| + | Server: test-server/fake |
| + | Content-Type: text/plain |
| + | Content-Length: 5 |
| + | Strict-Transport-Security: max-age=31536000 |
| + | |
| + | Hello |
| + | </data> |
| + | |
| + | <!-- Response to the dup handle's proxy CONNECT attempt. |
| + | The CONNECT to port 443 proves the copied |
| + | HSTS cache upgraded the dup's HTTP URL to HTTPS. --> |
| + | <connect crlf="headers"> |
| + | HTTP/1.1 403 Forbidden |
| + | Content-Length: 0 |
| + | Connection: close |
| + | |
| + | </connect> |
| + | </reply> |
| + | |
| + | <client> |
| + | <features> |
| + | HSTS |
| + | https |
| + | Debug |
| + | proxy |
| + | </features> |
| + | <server> |
| + | http |
| + | http-proxy |
| + | </server> |
| + | <setenv> |
| + | CURL_HSTS_HTTP=yes |
| + | </setenv> |
| + | <name> |
| + | curl_easy_duphandle copies HSTS cache, auto upgrading HTTP to HTTPS. |
| + | </name> |
| + | <tool> |
| + | lib%TESTNUMBER |
| + | </tool> |
| + | <command> |
| + | - %HOSTIP %HTTPPORT %PROXYPORT |
| + | </command> |
| + | </client> |
| + | |
| + | <verify> |
| + | # First request: original handle GETs from the http server; the response |
| + | # carries Strict-Transport-Security, populating the live HSTS cache that |
| + | # the dup inherits. |
| + | <protocol crlf="headers"> |
| + | GET /%TESTNUMBER HTTP/1.1 |
| + | Host: hsts.example.com:%HTTPPORT |
| + | Accept: */* |
| + | |
| + | </protocol> |
| + | # Second request: dup handle upgraded HTTP to HTTPS by copied HSTS cache, |
| + | # proxy receives CONNECT to port 443 proving the upgrade happened |
| + | <proxy crlf="headers"> |
| + | CONNECT hsts.example.com:443 HTTP/1.1 |
| + | Host: hsts.example.com:443 |
| + | Proxy-Connection: Keep-Alive |
| + | |
| + | </proxy> |
| + | <stdout> |
| + | First request: HTTPS cache populated |
| + | Dup effective URL: https://hsts.example.com/%TESTNUMBER |
| + | </stdout> |
| + | # CURLE_COULDNT_CONNECT (7) is intentional: The proxy rejects the CONNECT |
| + | # to port 443, collapsing the tunnel. All that is being validated is the |
| + | # CONNECT to port 443 itself. |
| + | <errorcode> |
| + | 7 |
| + | </errorcode> |
| + | </verify> |
| + | </testcase> |
| @@ -104,6 +104,7 @@ TESTS_C = \ | |
| lib1900.c lib1901.c lib1902.c lib1903.c lib1905.c lib1906.c lib1907.c \ | |
| lib1908.c lib1910.c lib1911.c lib1912.c lib1913.c \ | |
| lib1915.c lib1916.c lib1918.c lib1919.c lib1920.c lib1921.c \ | |
| + | lib1922.c \ |
| lib1933.c lib1934.c lib1935.c lib1936.c lib1937.c lib1938.c lib1939.c \ | |
| lib1940.c lib1945.c \ | |
| lib1947.c lib1948.c \ | |
| @@ -0,0 +1,123 @@ | |
| + | /*************************************************************************** |
| + | * _ _ ____ _ |
| + | * Project ___| | | | _ \| | |
| + | * / __| | | | |_) | | |
| + | * | (__| |_| | _ <| |___ |
| + | * \___|\___/|_| \_\_____| |
| + | * |
| + | * Copyright (C) Daniel Stenberg, <[email protected]>, 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.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. |
| + | * |
| + | * SPDX-License-Identifier: curl |
| + | * |
| + | ***************************************************************************/ |
| + | #include "first.h" |
| + | |
| + | static size_t test_lib1922_discard_write(char *ptr, size_t size, size_t nmemb, |
| + | void *ud) |
| + | { |
| + | (void)ptr; (void)ud; |
| + | return size * nmemb; |
| + | } |
| + | |
| + | static CURLcode test_lib1922(const char *URL) |
| + | { |
| + | CURLcode result = CURLE_OK; |
| + | CURL *curl = NULL; |
| + | CURL *dup = NULL; |
| + | struct curl_slist *resolve = NULL; |
| + | char resolve_entry[256]; |
| + | char direct_url[256]; |
| + | char http_url[256]; |
| + | char proxy_url[256]; |
| + | const char *effective = NULL; |
| + | const char *host = libtest_arg2; /* %HOSTIP */ |
| + | const char *httpport = libtest_arg3; /* %HTTPPORT */ |
| + | const char *proxyport = libtest_arg4;/* %PROXYPORT */ |
| + | |
| + | (void)URL; |
| + | |
| + | if(!host || !httpport || !proxyport) { |
| + | curl_mfprintf(stderr, |
| + | "Usage: lib1922 - <host> <httpport> <proxyport>\n"); |
| + | return TEST_ERR_MAJOR_BAD; |
| + | } |
| + | |
| + | /* Synthetic DNS so hsts.example.com resolves to the test server. */ |
| + | curl_msnprintf(resolve_entry, sizeof(resolve_entry), |
| + | "hsts.example.com:%s:%s", httpport, host); |
| + | resolve = curl_slist_append(NULL, resolve_entry); |
| + | if(!resolve) { |
| + | return CURLE_OUT_OF_MEMORY; |
| + | } |
| + | |
| + | curl_msnprintf(direct_url, sizeof(direct_url), |
| + | "http://hsts.example.com:%s/%d", httpport, 1922); |
| + | curl_msnprintf(http_url, sizeof(http_url), |
| + | "http://hsts.example.com/%d", 1922); |
| + | curl_msnprintf(proxy_url, sizeof(proxy_url), |
| + | "http://%s:%s", host, proxyport); |
| + | |
| + | global_init(CURL_GLOBAL_ALL); |
| + | easy_init(curl); |
| + | |
| + | easy_setopt(curl, CURLOPT_WRITEFUNCTION, test_lib1922_discard_write); |
| + | easy_setopt(curl, CURLOPT_RESOLVE, resolve); |
| + | easy_setopt(curl, CURLOPT_URL, direct_url); |
| + | easy_setopt(curl, CURLOPT_HSTS_CTRL, CURLHSTS_ENABLE); |
| + | |
| + | /* Direct HTTP request: Server returns Strict-Transport-Security. |
| + | * CURL_HSTS_HTTP env var (set in the test) allows processing it over |
| + | * HTTP in debug builds, populating the live HSTS cache. */ |
| + | result = curl_easy_perform(curl); |
| + | if(result) { |
| + | curl_mfprintf(stderr, "First perform failed: %d (%s)\n", |
| + | result, curl_easy_strerror(result)); |
| + | goto test_cleanup; |
| + | } |
| + | curl_mprintf("First request: HTTPS cache populated\n"); |
| + | |
| + | dup = curl_easy_duphandle(curl); |
| + | if(!dup) { |
| + | result = CURLE_FAILED_INIT; |
| + | goto test_cleanup; |
| + | } |
| + | |
| + | /* Point the dup at the plain HTTP URL for the same hostname, via a proxy. |
| + | * The copied HSTS cache upgrades the URL to HTTPS, causing a CONNECT to |
| + | * port 443. The test proxy rejects CONNECT with 403, so curl returns |
| + | * CURLE_COULDNT_CONNECT (7). The CONNECT to port 443 is itself the proof |
| + | * of the upgrade. */ |
| + | easy_setopt(dup, CURLOPT_URL, http_url); |
| + | easy_setopt(dup, CURLOPT_PROXY, proxy_url); |
| + | |
| + | result = curl_easy_perform(dup); |
| + | if(result != CURLE_COULDNT_CONNECT) { |
| + | curl_mfprintf(stderr, "Dup perform unexpected result: %d (%s)\n", |
| + | result, curl_easy_strerror(result)); |
| + | goto test_cleanup; |
| + | } |
| + | |
| + | /* Confirm the dup's URL was upgraded to HTTPS by the copied HSTS cache. */ |
| + | curl_easy_getinfo(dup, CURLINFO_EFFECTIVE_URL, &effective); |
| + | if(effective) { |
| + | curl_mprintf("Dup effective URL: %s\n", effective); |
| + | } |
| + | |
| + | test_cleanup: |
| + | curl_easy_cleanup(curl); |
| + | curl_easy_cleanup(dup); |
| + | curl_slist_free_all(resolve); |
| + | curl_global_cleanup(); |
| + | return result; |
| + | } |