Files
curl-impersonate/firefox/patches/curl-impersonate.patch
lwthiker 6e0e9b90da Fix Edge and Firefox HTTP headers
The HTTP headers for Edge and Firefox were slightly incorrect.

curl patches from
ee0a67058f
and
351dba44d0
2022-03-04 17:27:08 +02:00

767 lines
24 KiB
Diff

diff --git a/configure.ac b/configure.ac
index 63e320236..deb054300 100644
--- a/configure.ac
+++ b/configure.ac
@@ -2573,15 +2573,15 @@ if test X"$want_nghttp2" != Xno; then
if test "$PKGCONFIG" != "no" ; then
LIB_H2=`CURL_EXPORT_PCDIR([$want_nghttp2_path])
- $PKGCONFIG --libs-only-l libnghttp2`
+ $PKGCONFIG --static --libs-only-l libnghttp2`
AC_MSG_NOTICE([-l is $LIB_H2])
CPP_H2=`CURL_EXPORT_PCDIR([$want_nghttp2_path]) dnl
- $PKGCONFIG --cflags-only-I libnghttp2`
+ $PKGCONFIG --static --cflags-only-I libnghttp2`
AC_MSG_NOTICE([-I is $CPP_H2])
LD_H2=`CURL_EXPORT_PCDIR([$want_nghttp2_path])
- $PKGCONFIG --libs-only-L libnghttp2`
+ $PKGCONFIG --static --libs-only-L libnghttp2`
AC_MSG_NOTICE([-L is $LD_H2])
LDFLAGS="$LDFLAGS $LD_H2"
diff --git a/include/curl/curl.h b/include/curl/curl.h
index 7b69ce2d6..fe4bb36b9 100644
--- a/include/curl/curl.h
+++ b/include/curl/curl.h
@@ -2135,6 +2135,10 @@ typedef enum {
/* Set MIME option flags. */
CURLOPT(CURLOPT_MIME_OPTIONS, CURLOPTTYPE_LONG, 315),
+ /* curl-impersonate: A list of headers used by the impersonated browser.
+ * If given, merged with CURLOPT_HTTPHEADER. */
+ CURLOPT(CURLOPT_HTTPBASEHEADER, CURLOPTTYPE_SLISTPOINT, 316),
+
CURLOPT_LASTENTRY /* the last unused */
} CURLoption;
diff --git a/include/curl/easy.h b/include/curl/easy.h
index 2dbfb26b5..e0bf86169 100644
--- a/include/curl/easy.h
+++ b/include/curl/easy.h
@@ -41,6 +41,15 @@ CURL_EXTERN CURLcode curl_easy_setopt(CURL *curl, CURLoption option, ...);
CURL_EXTERN CURLcode curl_easy_perform(CURL *curl);
CURL_EXTERN void curl_easy_cleanup(CURL *curl);
+/*
+ * curl-impersonate: Tell libcurl to impersonate a browser.
+ * This is a wrapper function that calls curl_easy_setopt()
+ * multiple times with all the parameters required. That's also why it was
+ * created as a separate API function and not just as another option to
+ * curl_easy_setopt().
+ */
+CURL_EXTERN CURLcode curl_easy_impersonate(CURL *curl, const char *target);
+
/*
* NAME curl_easy_getinfo()
*
diff --git a/lib/easy.c b/lib/easy.c
index 20293a710..1a4d52d2f 100644
--- a/lib/easy.c
+++ b/lib/easy.c
@@ -80,6 +80,7 @@
#include "dynbuf.h"
#include "altsvc.h"
#include "hsts.h"
+#include "strcase.h"
/* The last 3 #include files should be in this order */
#include "curl_printf.h"
@@ -282,6 +283,162 @@ void curl_global_cleanup(void)
init_flags = 0;
}
+/*
+ * curl-impersonate: Options to be set for each supported target browser.
+ * Note: this does not include the HTTP headers, which are handled separately
+ * in Curl_http().
+ */
+#define IMPERSONATE_MAX_HEADERS 32
+static const struct impersonate_opts {
+ const char *target;
+ int httpversion;
+ int ssl_version;
+ const char *ciphers;
+ const char *http_headers[IMPERSONATE_MAX_HEADERS];
+ /* Other TLS options will come here in the future once they are
+ * configurable through curl_easy_setopt() */
+} impersonations[] = {
+ {
+ .target = "ff91esr",
+ .httpversion = CURL_HTTP_VERSION_2_0,
+ .ssl_version = CURL_SSLVERSION_TLSv1_2 | CURL_SSLVERSION_MAX_DEFAULT,
+ .ciphers =
+ "aes_128_gcm_sha_256,"
+ "chacha20_poly1305_sha_256,"
+ "aes_256_gcm_sha_384,"
+ "ecdhe_ecdsa_aes_128_gcm_sha_256,"
+ "ecdhe_rsa_aes_128_gcm_sha_256,"
+ "ecdhe_ecdsa_chacha20_poly1305_sha_256,"
+ "ecdhe_rsa_chacha20_poly1305_sha_256,"
+ "ecdhe_ecdsa_aes_256_gcm_sha_384,"
+ "ecdhe_rsa_aes_256_gcm_sha_384,"
+ "ecdhe_ecdsa_aes_256_sha,"
+ "ecdhe_ecdsa_aes_128_sha,"
+ "ecdhe_rsa_aes_128_sha,"
+ "ecdhe_rsa_aes_256_sha,"
+ "rsa_aes_128_gcm_sha_256,"
+ "rsa_aes_256_gcm_sha_384,"
+ "rsa_aes_128_sha,"
+ "rsa_aes_256_sha,"
+ "rsa_3des_ede_cbc_sha",
+ .http_headers = {
+ "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:91.0) Gecko/20100101 Firefox/91.0",
+ "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8",
+ "Accept-Language: en-US,en;q=0.5",
+ "Accept-Encoding: gzip, deflate, br",
+ "Upgrade-Insecure-Requests: 1",
+ "Sec-Fetch-Dest: document",
+ "Sec-Fetch-Mode: navigate",
+ "Sec-Fetch-Site: none",
+ "Sec-Fetch-User: ?1",
+ "TE: Trailers"
+ }
+ },
+ {
+ .target = "ff95",
+ .httpversion = CURL_HTTP_VERSION_2_0,
+ .ssl_version = CURL_SSLVERSION_TLSv1_2 | CURL_SSLVERSION_MAX_DEFAULT,
+ .ciphers =
+ "aes_128_gcm_sha_256,"
+ "chacha20_poly1305_sha_256,"
+ "aes_256_gcm_sha_384,"
+ "ecdhe_ecdsa_aes_128_gcm_sha_256,"
+ "ecdhe_rsa_aes_128_gcm_sha_256,"
+ "ecdhe_ecdsa_chacha20_poly1305_sha_256,"
+ "ecdhe_rsa_chacha20_poly1305_sha_256,"
+ "ecdhe_ecdsa_aes_256_gcm_sha_384,"
+ "ecdhe_rsa_aes_256_gcm_sha_384,"
+ "ecdhe_ecdsa_aes_256_sha,"
+ "ecdhe_ecdsa_aes_128_sha,"
+ "ecdhe_rsa_aes_128_sha,"
+ "ecdhe_rsa_aes_256_sha,"
+ "rsa_aes_128_gcm_sha_256,"
+ "rsa_aes_256_gcm_sha_384,"
+ "rsa_aes_128_sha,"
+ "rsa_aes_256_sha",
+ .http_headers = {
+ "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:95.0) Gecko/20100101 Firefox/95.0",
+ "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8",
+ "Accept-Language: en-US,en;q=0.5",
+ "Accept-Encoding: gzip, deflate, br",
+ "Upgrade-Insecure-Requests: 1",
+ "Sec-Fetch-Dest: document",
+ "Sec-Fetch-Mode: navigate",
+ "Sec-Fetch-Site: none",
+ "Sec-Fetch-User: ?1",
+ "TE: Trailers"
+ }
+ }
+};
+
+#define NUM_IMPERSONATIONS \
+ sizeof(impersonations) / sizeof(impersonations[0])
+
+/*
+ * curl-impersonate:
+ * Call curl_easy_setopt() with all the needed options as defined in the
+ * 'impersonations' array.
+ * */
+CURLcode curl_easy_impersonate(struct Curl_easy *data, const char *target)
+{
+ int i;
+ int ret;
+ const struct impersonate_opts *opts = NULL;
+ struct curl_slist *headers = NULL;
+
+ for(i = 0; i < NUM_IMPERSONATIONS; i++) {
+ if (Curl_strncasecompare(target,
+ impersonations[i].target,
+ strlen(impersonations[i].target))) {
+ opts = &impersonations[i];
+ break;
+ }
+ }
+
+ if(!opts) {
+ DEBUGF(fprintf(stderr, "Error: unknown impersonation target '%s'\n",
+ target));
+ return CURLE_BAD_FUNCTION_ARGUMENT;
+ }
+
+ if(opts->httpversion != CURL_HTTP_VERSION_NONE) {
+ ret = curl_easy_setopt(data, CURLOPT_HTTP_VERSION, opts->httpversion);
+ if(ret)
+ return ret;
+ }
+
+ if (opts->ssl_version != CURL_SSLVERSION_DEFAULT) {
+ ret = curl_easy_setopt(data, CURLOPT_SSLVERSION, opts->ssl_version);
+ if(ret)
+ return ret;
+ }
+
+ if(opts->ciphers) {
+ ret = curl_easy_setopt(data, CURLOPT_SSL_CIPHER_LIST, opts->ciphers);
+ if (ret)
+ return ret;
+ }
+
+ /* Build a linked list out of the static array of headers. */
+ for(i = 0; i < IMPERSONATE_MAX_HEADERS; i++) {
+ if(opts->http_headers[i]) {
+ headers = curl_slist_append(headers, opts->http_headers[i]);
+ if(!headers) {
+ return CURLE_OUT_OF_MEMORY;
+ }
+ }
+ }
+
+ if(headers) {
+ ret = curl_easy_setopt(data, CURLOPT_HTTPBASEHEADER, headers);
+ curl_slist_free_all(headers);
+ if(ret)
+ return ret;
+ }
+
+ return CURLE_OK;
+}
+
/*
* curl_easy_init() is the external interface to alloc, setup and init an
* easy handle that is returned. If anything goes wrong, NULL is returned.
@@ -290,6 +447,7 @@ struct Curl_easy *curl_easy_init(void)
{
CURLcode result;
struct Curl_easy *data;
+ char *target;
/* Make sure we inited the global SSL stuff */
if(!initialized) {
@@ -308,6 +466,22 @@ struct Curl_easy *curl_easy_init(void)
return NULL;
}
+ /*
+ * curl-impersonate: Hook into curl_easy_init() to set the required options
+ * from an environment variable.
+ * This is a bit hacky but allows seamless integration of libcurl-impersonate
+ * without code modifications to the app.
+ */
+ target = curl_getenv("CURL_IMPERSONATE");
+ if(target) {
+ result = curl_easy_impersonate(data, target);
+ free(target);
+ if(result) {
+ Curl_close(&data);
+ return NULL;
+ }
+ }
+
return data;
}
@@ -878,6 +1052,13 @@ struct Curl_easy *curl_easy_duphandle(struct Curl_easy *data)
outcurl->state.referer_alloc = TRUE;
}
+ if(data->state.base_headers) {
+ outcurl->state.base_headers =
+ Curl_slist_duplicate(data->state.base_headers);
+ if(!outcurl->state.base_headers)
+ goto fail;
+ }
+
/* Reinitialize an SSL engine for the new handle
* note: the engine name has already been copied by dupset */
if(outcurl->set.str[STRING_SSL_ENGINE]) {
diff --git a/lib/easyoptions.c b/lib/easyoptions.c
index 04871ad1e..cd5998146 100644
--- a/lib/easyoptions.c
+++ b/lib/easyoptions.c
@@ -130,6 +130,7 @@ struct curl_easyoption Curl_easyopts[] = {
{"HTTP200ALIASES", CURLOPT_HTTP200ALIASES, CURLOT_SLIST, 0},
{"HTTPAUTH", CURLOPT_HTTPAUTH, CURLOT_VALUES, 0},
{"HTTPGET", CURLOPT_HTTPGET, CURLOT_LONG, 0},
+ {"HTTPBASEHEADER", CURLOPT_HTTPBASEHEADER, CURLOT_SLIST, 0},
{"HTTPHEADER", CURLOPT_HTTPHEADER, CURLOT_SLIST, 0},
{"HTTPPOST", CURLOPT_HTTPPOST, CURLOT_OBJECT, 0},
{"HTTPPROXYTUNNEL", CURLOPT_HTTPPROXYTUNNEL, CURLOT_LONG, 0},
diff --git a/lib/http.c b/lib/http.c
index f08a343e3..879151dd2 100644
--- a/lib/http.c
+++ b/lib/http.c
@@ -84,6 +84,7 @@
#include "altsvc.h"
#include "hsts.h"
#include "c-hyper.h"
+#include "slist.h"
/* The last 3 #include files should be in this order */
#include "curl_printf.h"
@@ -1795,6 +1796,15 @@ CURLcode Curl_add_custom_headers(struct Curl_easy *data,
int numlists = 1; /* by default */
int i;
+ /*
+ * curl-impersonate: Use the merged list of headers if it exists (i.e. when
+ * the CURLOPT_HTTPBASEHEADER option was set.
+ */
+ struct curl_slist *noproxyheaders =
+ (data->state.merged_headers ?
+ data->state.merged_headers :
+ data->set.headers);
+
#ifndef CURL_DISABLE_PROXY
enum proxy_use proxy;
@@ -1806,10 +1816,10 @@ CURLcode Curl_add_custom_headers(struct Curl_easy *data,
switch(proxy) {
case HEADER_SERVER:
- h[0] = data->set.headers;
+ h[0] = noproxyheaders;
break;
case HEADER_PROXY:
- h[0] = data->set.headers;
+ h[0] = noproxyheaders;
if(data->set.sep_headers) {
h[1] = data->set.proxyheaders;
numlists++;
@@ -1819,12 +1829,12 @@ CURLcode Curl_add_custom_headers(struct Curl_easy *data,
if(data->set.sep_headers)
h[0] = data->set.proxyheaders;
else
- h[0] = data->set.headers;
+ h[0] = noproxyheaders;
break;
}
#else
(void)is_connect;
- h[0] = data->set.headers;
+ h[0] = noproxyheaders;
#endif
/* loop through one or two lists */
@@ -2059,6 +2069,92 @@ void Curl_http_method(struct Curl_easy *data, struct connectdata *conn,
*reqp = httpreq;
}
+/*
+ * curl-impersonate:
+ * Create a new linked list of headers.
+ * The new list is a merge between the "base" headers and the application given
+ * headers. The "base" headers contain curl-impersonate's list of headers
+ * used by default by the impersonated browser.
+ *
+ * The application given headers will override the "base" headers if supplied.
+ */
+CURLcode Curl_http_merge_headers(struct Curl_easy *data)
+{
+ int i;
+ int ret;
+ struct curl_slist *head;
+ struct curl_slist *dup = NULL;
+ struct curl_slist *new_list = NULL;
+
+ if (!data->state.base_headers)
+ return CURLE_OK;
+
+ /* Duplicate the list for temporary use. */
+ if (data->set.headers) {
+ dup = Curl_slist_duplicate(data->set.headers);
+ if(!dup)
+ return CURLE_OUT_OF_MEMORY;
+ }
+
+ for(head = data->state.base_headers; head; head = head->next) {
+ char *sep;
+ size_t prefix_len;
+ bool found = FALSE;
+ struct curl_slist *head2;
+
+ sep = strchr(head->data, ':');
+ if(!sep)
+ continue;
+
+ prefix_len = sep - head->data;
+
+ /* Check if this header was added by the application. */
+ for(head2 = dup; head2; head2 = head2->next) {
+ if(head2->data &&
+ strncasecompare(head2->data, head->data, prefix_len) &&
+ Curl_headersep(head2->data[prefix_len]) ) {
+ new_list = curl_slist_append(new_list, head2->data);
+ /* Free and set to NULL to mark that it's been added. */
+ Curl_safefree(head2->data);
+ found = TRUE;
+ break;
+ }
+ }
+
+ if (!found) {
+ new_list = curl_slist_append(new_list, head->data);
+ }
+
+ if (!new_list) {
+ ret = CURLE_OUT_OF_MEMORY;
+ goto fail;
+ }
+ }
+
+ /* Now go over any additional application-supplied headers. */
+ for(head = dup; head; head = head->next) {
+ if(head->data) {
+ new_list = curl_slist_append(new_list, head->data);
+ if(!new_list) {
+ ret = CURLE_OUT_OF_MEMORY;
+ goto fail;
+ }
+ }
+ }
+
+ curl_slist_free_all(dup);
+ /* Save the new, merged list separately, so it can be freed later. */
+ curl_slist_free_all(data->state.merged_headers);
+ data->state.merged_headers = new_list;
+
+ return CURLE_OK;
+
+fail:
+ Curl_safefree(dup);
+ curl_slist_free_all(new_list);
+ return ret;
+}
+
CURLcode Curl_http_useragent(struct Curl_easy *data)
{
/* The User-Agent string might have been allocated in url.c already, because
@@ -3067,6 +3163,11 @@ CURLcode Curl_http(struct Curl_easy *data, bool *done)
if(result)
return result;
+ /* curl-impersonate: Add HTTP headers to impersonate real browsers. */
+ result = Curl_http_merge_headers(data);
+ if (result)
+ return result;
+
result = Curl_http_useragent(data);
if(result)
return result;
diff --git a/lib/http2.c b/lib/http2.c
index e74400a4c..1f4d496f3 100644
--- a/lib/http2.c
+++ b/lib/http2.c
@@ -1818,7 +1818,8 @@ static ssize_t http2_recv(struct Curl_easy *data, int sockindex,
/* Index where :authority header field will appear in request header
field list. */
-#define AUTHORITY_DST_IDX 3
+/* curl-impersonate: Put the ":authority" header in the second place. */
+#define AUTHORITY_DST_IDX 2
/* USHRT_MAX is 65535 == 0xffff */
#define HEADER_OVERFLOW(x) \
diff --git a/lib/setopt.c b/lib/setopt.c
index 599ed5d99..1baa48e70 100644
--- a/lib/setopt.c
+++ b/lib/setopt.c
@@ -48,6 +48,7 @@
#include "multiif.h"
#include "altsvc.h"
#include "hsts.h"
+#include "slist.h"
/* The last 3 #include files should be in this order */
#include "curl_printf.h"
@@ -688,6 +689,23 @@ CURLcode Curl_vsetopt(struct Curl_easy *data, CURLoption option, va_list param)
va_arg(param, char *));
break;
+ case CURLOPT_HTTPBASEHEADER:
+ /*
+ * curl-impersonate:
+ * Set a list of "base" headers. These will be merged with any headers
+ * set by CURLOPT_HTTPHEADER. curl-impersonate uses this option in order
+ * to set a list of default browser headers.
+ *
+ * Unlike CURLOPT_HTTPHEADER,
+ * the list is copied and can be immediately freed by the user.
+ */
+ curl_slist_free_all(data->state.base_headers);
+ data->state.base_headers = \
+ Curl_slist_duplicate(va_arg(param, struct curl_slist *));
+ if (!data->state.base_headers)
+ result = CURLE_OUT_OF_MEMORY;
+ break;
+
case CURLOPT_HTTPHEADER:
/*
* Set a list with HTTP headers to use (or replace internals with)
diff --git a/lib/transfer.c b/lib/transfer.c
index 22704fa15..1e100140c 100644
--- a/lib/transfer.c
+++ b/lib/transfer.c
@@ -102,7 +102,15 @@ char *Curl_checkheaders(const struct Curl_easy *data,
DEBUGASSERT(thislen);
DEBUGASSERT(thisheader[thislen-1] != ':');
- for(head = data->set.headers; head; head = head->next) {
+ /*
+ * curl-impersonate:
+ * Check if we have overriden the user-supplied list of headers.
+ */
+ head = data->set.headers;
+ if (data->state.merged_headers)
+ head = data->state.merged_headers;
+
+ for(; head; head = head->next) {
if(strncasecompare(head->data, thisheader, thislen) &&
Curl_headersep(head->data[thislen]) )
return head->data;
diff --git a/lib/url.c b/lib/url.c
index 9f1013554..f0f266797 100644
--- a/lib/url.c
+++ b/lib/url.c
@@ -469,6 +469,11 @@ CURLcode Curl_close(struct Curl_easy **datap)
Curl_safefree(data->state.aptr.proxyuser);
Curl_safefree(data->state.aptr.proxypasswd);
+ /* curl-impersonate: Free the list set by CURLOPT_HTTPBASEHEADER. */
+ curl_slist_free_all(data->state.base_headers);
+ /* curl-impersonate: Free the dynamic list of headers. */
+ curl_slist_free_all(data->state.merged_headers);
+
#ifndef CURL_DISABLE_DOH
if(data->req.doh) {
Curl_dyn_free(&data->req.doh->probe[0].serverdoh);
diff --git a/lib/urldata.h b/lib/urldata.h
index cc9c88870..a35a20e10 100644
--- a/lib/urldata.h
+++ b/lib/urldata.h
@@ -1421,6 +1421,19 @@ struct UrlState {
CURLcode hresult; /* used to pass return codes back from hyper callbacks */
#endif
+ /*
+ * curl-impersonate:
+ * List of "base" headers set by CURLOPT_HTTPBASEHEADER.
+ */
+ struct curl_slist *base_headers;
+ /*
+ * curl-impersonate:
+ * Dynamically-constructed list of HTTP headers.
+ * This list is a merge of the default HTTP headers needed to impersonate a
+ * browser, together with any user-supplied headers.
+ */
+ struct curl_slist *merged_headers;
+
/* Dynamically allocated strings, MUST be freed before this struct is
killed. */
struct dynamically_allocated_data {
diff --git a/lib/vtls/nss.c b/lib/vtls/nss.c
index 2b44f0512..4c60797c7 100644
--- a/lib/vtls/nss.c
+++ b/lib/vtls/nss.c
@@ -143,6 +143,7 @@ static const struct cipher_s cipherlist[] = {
{"dhe_dss_3des_sha", SSL_DHE_DSS_WITH_3DES_EDE_CBC_SHA},
{"dhe_rsa_des_sha", SSL_DHE_RSA_WITH_DES_CBC_SHA},
{"dhe_dss_des_sha", SSL_DHE_DSS_WITH_DES_CBC_SHA},
+ {"rsa_3des_ede_cbc_sha", TLS_RSA_WITH_3DES_EDE_CBC_SHA},
/* TLS 1.0: Exportable 56-bit Cipher Suites. */
{"rsa_des_56_sha", TLS_RSA_EXPORT1024_WITH_DES_CBC_SHA},
{"rsa_rc4_56_sha", TLS_RSA_EXPORT1024_WITH_RC4_56_SHA},
@@ -378,6 +379,95 @@ static SECStatus set_ciphers(struct Curl_easy *data, PRFileDesc *model,
return SECSuccess;
}
+/* See nsSSLIOLayerSetOptions@nsNSSIOLayer.cpp, Firefox source code */
+const SSLNamedGroup named_groups[] = {
+ ssl_grp_ec_curve25519, ssl_grp_ec_secp256r1, ssl_grp_ec_secp384r1,
+ ssl_grp_ec_secp521r1, ssl_grp_ffdhe_2048, ssl_grp_ffdhe_3072};
+
+#define NUM_OF_NAMED_GROUPS sizeof(named_groups)/sizeof(named_groups[0])
+
+static SECStatus set_named_groups(PRFileDesc *model)
+{
+ /* This aligns TLS extension 10 (supported_groups) to what Firefox does. */
+ return SSL_NamedGroupConfig(model, named_groups, NUM_OF_NAMED_GROUPS);
+}
+
+static const SSLSignatureScheme signatures[] = {
+ ssl_sig_ecdsa_secp256r1_sha256, ssl_sig_ecdsa_secp384r1_sha384,
+ ssl_sig_ecdsa_secp521r1_sha512, ssl_sig_rsa_pss_sha256,
+ ssl_sig_rsa_pss_sha384, ssl_sig_rsa_pss_sha512,
+ ssl_sig_rsa_pkcs1_sha256, ssl_sig_rsa_pkcs1_sha384,
+ ssl_sig_rsa_pkcs1_sha512, ssl_sig_ecdsa_sha1,
+ ssl_sig_rsa_pkcs1_sha1
+};
+
+#define NUM_OF_SIGNATURES sizeof(signatures)/sizeof(signatures[0])
+
+static SECStatus set_additional_key_shares(PRFileDesc *model)
+{
+ /* This aligns TLS extension 51 (key_share) to what Firefox does. */
+ return SSL_SendAdditionalKeyShares(model, 1);
+}
+
+static SECStatus set_signatures(PRFileDesc *model)
+{
+ /* Align TLS extension 13 (signature_algorithms) to what Firefox does. */
+ return SSL_SignatureSchemePrefSet(model, signatures, NUM_OF_SIGNATURES);
+}
+
+static SECStatus set_ssl_options(PRFileDesc *model)
+{
+ SECStatus s;
+
+ /* Enable TLS 1.3 compat mode. Firefox does this, as can be seen at
+ * nsSSLIOLayerSetOptions()@nsNSSIOLayer.cpp.
+ * This has the side effect of NSS faking a TLS session ID.
+ * See ssl3_CreateClientHelloPreamble()@ssl3con.c
+ */
+ s = SSL_OptionSet(model, SSL_ENABLE_TLS13_COMPAT_MODE, PR_TRUE);
+ if (s != SECSuccess) {
+ return s;
+ }
+
+ /* Firefox sets the following options. I don't know what they do. */
+ s = SSL_OptionSet(model, SSL_REQUIRE_SAFE_NEGOTIATION, false);
+ if (s != SECSuccess) {
+ return s;
+ }
+ s = SSL_OptionSet(model, SSL_ENABLE_EXTENDED_MASTER_SECRET, true);
+ if (s != SECSuccess) {
+ return s;
+ }
+ s = SSL_OptionSet(model, SSL_ENABLE_HELLO_DOWNGRADE_CHECK, true);
+ if (s != SECSuccess) {
+ return s;
+ }
+ s = SSL_OptionSet(model, SSL_ENABLE_0RTT_DATA, true);
+ if (s != SECSuccess) {
+ return s;
+ }
+
+ /* This adds TLS extension 34 to the Client Hello. */
+ s = SSL_OptionSet(model, SSL_ENABLE_DELEGATED_CREDENTIALS, true);
+ if (s != SECSuccess) {
+ return s;
+ }
+
+ /* This adds TLS extension 5 (status_request) to the Client Hello. */
+ s = SSL_OptionSet(model, SSL_ENABLE_OCSP_STAPLING, true);
+ if (s != SECSuccess) {
+ return s;
+ }
+
+ /* Remove TLS extension 18 (signed_certificate_timestamp) */
+ s = SSL_OptionSet(model, SSL_ENABLE_SIGNED_CERT_TIMESTAMPS, false);
+ if (s != SECSuccess) {
+ return s;
+ }
+
+ return SSL_OptionSet(model, SSL_HANDSHAKE_AS_CLIENT, true);
+}
+
/*
* Return true if at least one cipher-suite is enabled. Used to determine
* if we need to call NSS_SetDomesticPolicy() to enable the default ciphers.
@@ -1320,6 +1410,24 @@ static CURLcode nss_load_module(SECMODModule **pmod, const char *library,
if(module)
SECMOD_DestroyModule(module);
+
+ /* Patch for Ubuntu - add a "nss/" suffix to the library name */
+ config_string = aprintf("library=/usr/lib/x86_64-linux-gnu/nss/%s name=%s", library, name);
+ if(!config_string)
+ return CURLE_OUT_OF_MEMORY;
+
+ module = SECMOD_LoadUserModule(config_string, NULL, PR_FALSE);
+ free(config_string);
+
+ if(module && module->loaded) {
+ /* loaded successfully */
+ *pmod = module;
+ return CURLE_OK;
+ }
+
+ if(module)
+ SECMOD_DestroyModule(module);
+
return CURLE_FAILED_INIT;
}
@@ -1921,6 +2029,12 @@ static CURLcode nss_setup_connect(struct Curl_easy *data,
if(SSL_OptionSet(model, SSL_NO_CACHE, ssl_no_cache) != SECSuccess)
goto error;
+ if(SSL_SET_OPTION(primary.sessionid)) {
+ if(SSL_OptionSet(model, SSL_ENABLE_SESSION_TICKETS,
+ PR_TRUE) != SECSuccess)
+ goto error;
+ }
+
/* enable/disable the requested SSL version(s) */
if(nss_init_sslver(&sslver, data, conn) != CURLE_OK)
goto error;
@@ -1960,6 +2074,14 @@ static CURLcode nss_setup_connect(struct Curl_easy *data,
}
}
+ if (set_named_groups(model) != SECSuccess ||
+ set_additional_key_shares(model) != SECSuccess ||
+ set_signatures(model) != SECSuccess ||
+ set_ssl_options(model) != SECSuccess) {
+ result = CURLE_SSL_CIPHER;
+ goto error;
+ }
+
if(!SSL_CONN_CONFIG(verifypeer) && SSL_CONN_CONFIG(verifyhost))
infof(data, "warning: ignoring value of ssl.verifyhost");
@@ -2113,6 +2235,10 @@ static CURLcode nss_setup_connect(struct Curl_easy *data,
int cur = 0;
unsigned char protocols[128];
+ protocols[cur++] = ALPN_HTTP_1_1_LENGTH;
+ memcpy(&protocols[cur], ALPN_HTTP_1_1, ALPN_HTTP_1_1_LENGTH);
+ cur += ALPN_HTTP_1_1_LENGTH;
+
#ifdef USE_HTTP2
if(data->state.httpwant >= CURL_HTTP_VERSION_2
#ifndef CURL_DISABLE_PROXY
@@ -2124,9 +2250,6 @@ static CURLcode nss_setup_connect(struct Curl_easy *data,
cur += ALPN_H2_LENGTH;
}
#endif
- protocols[cur++] = ALPN_HTTP_1_1_LENGTH;
- memcpy(&protocols[cur], ALPN_HTTP_1_1, ALPN_HTTP_1_1_LENGTH);
- cur += ALPN_HTTP_1_1_LENGTH;
if(SSL_SetNextProtoNego(backend->handle, protocols, cur) != SECSuccess)
goto error;
diff --git a/m4/curl-nss.m4 b/m4/curl-nss.m4
index 397ba71b1..abc09a91c 100644
--- a/m4/curl-nss.m4
+++ b/m4/curl-nss.m4
@@ -74,7 +74,7 @@ if test "x$OPT_NSS" != xno; then
# Without pkg-config, we'll kludge in some defaults
AC_MSG_WARN([Using hard-wired libraries and compilation flags for NSS.])
addld="-L$OPT_NSS/lib"
- addlib="-lssl3 -lsmime3 -lnss3 -lplds4 -lplc4 -lnspr4"
+ addlib="-Wl,-Bstatic -Wl,--start-group -lssl -lnss_static -lpk11wrap_static -lcertdb -lcerthi -lsmime -lnsspki -lnssdev -lsoftokn_static -lfreebl_static -lsha-x86_c_lib -lgcm-aes-x86_c_lib -lhw-acc-crypto-avx -lhw-acc-crypto-avx2 -lnssutil -lnssb -lcryptohi -l:libplc4.a -l:libplds4.a -l:libnspr4.a -lsqlite -Wl,--end-group -Wl,-Bdynamic -pthread -ldl"
addcflags="-I$OPT_NSS/include"
version="unknown"
nssprefix=$OPT_NSS
@@ -91,7 +91,7 @@ if test "x$OPT_NSS" != xno; then
fi
dnl The function SSL_VersionRangeSet() is needed to enable TLS > 1.0
- AC_CHECK_LIB(nss3, SSL_VersionRangeSet,
+ AC_CHECK_LIB(nss_static, SSL_VersionRangeSet,
[
AC_DEFINE(USE_NSS, 1, [if NSS is enabled])
AC_SUBST(USE_NSS, [1])