CVE-2026-86415: auto client cert connection reuse mixup
What the report claims
libcurl's connection cache reuses a Schannel connection authenticated with a client certificate to a different origin after a redirect, bypassing the per-request auto_client_cert=FALSE check.
Compare with curl's recorded outcome
Known vulnerability
A real vulnerability: CVE-2026-86415 auto client cert connection reuse mixup
Shown for comparison after analysis. The pipeline did not see this label.
ProveIt's finding
The code supports the reported flaw. A maintainer should review its impact and decide what to do next.
Why this finding?
- auto_client_cert is computed per-request based on origin vs initial_origin (vtls_config.c:236-238) to prevent presenting client certs to redirected hosts
- Connection cache matcher (match_ssl_primary_config) only compares ssl_primary_config fields, which excludes auto_client_cert (vtls_config.c:136-169, vtls_config.h:62-79, urldata.h:322)
- A redirect from attacker-controlled host A to host B causes reuse of B's cached authenticated connection because the matcher doesn't see that auto_client_cert should be FALSE for the redirected request
- Schannel uses auto_client_cert at handshake time (schannel.c:783-786, 981) but connection reuse bypasses handshake entirely
- The flaw was introduced by commit d69bfad3fa3daf5e72331f6870667607828d5891 which added the per-origin check but didn't update the matcher, and persists at master (e26f6e408a80)
How this analysis ran 5 claims · 17 citations verified
Checked against curl master on 2026-09-03 (e26f6e408a80). Dev evaluation split. Citation verification checks that a referenced line was in the evidence; it does not prove the model interpreted it correctly.
Review a draft reply to the reporter
Edit and verify before sending.
Thank you for the detailed report. The code confirms the flaw: `auto_client_cert` is correctly derived per-request to block client certs on redirects (vtls_config.c:236-238), but `match_ssl_primary_config()` only compares `ssl_primary_config` fields (vtls_config.c:136-169), which lacks this flag. A cached Schannel connection established with `auto_client_cert=TRUE` can be reused for a redirected request where it should be `FALSE`, bypassing the handshake where Schannel would suppress the cert (schannel.c:783-786, 981). This is a valid security issue on Windows/Schannel with `CURLSSLOPT_AUTO_CLIENT_CERT`.
Inspect all claims and source code 5 claims · 17 code excerpts
Report claims
The connection cache matcher never checks the per-request auto_client_cert flag, allowing reuse of a cached connection that was established with auto_client_cert=TRUE for a request that should have auto_client_cert=FALSE.
Read the reasoning
The code confirms auto_client_cert is derived per-request (line 236-238) but connection matching only uses ssl_primary_config which lacks this field. The raw ssl_options bitmask is compared but it doesn't capture the per-origin derivation.
The flag auto_client_cert is stored in struct ssl_config_data but connections are matched using only struct ssl_primary_config, so match_ssl_primary_config() cannot distinguish differing auto_client_cert values.
Read the reasoning
The struct layout shows auto_client_cert is in ssl_config_data but not in ssl_primary_config, and connections only retain the primary struct. The matcher therefore cannot see auto_client_cert.
A redirect from an attacker-controlled host causes a request to reuse an existing cached connection to a different host that was established with client certificate authentication.
Read the reasoning
A redirect changes origin but not initial_origin, so auto_client_cert becomes FALSE for the redirected request. However, the connection matcher doesn't check this flag, so a cached connection established with auto_client_cert=TRUE (for initial_origin) can be reused.
The client certificate is sent to the original host (B) instead of the redirect target (A), allowing the attacker to obtain resources or actions intended for the authenticated host.
Read the reasoning
When a cached connection is reused, no new handshake occurs, so the schannel flags that would suppress client cert presentation are never evaluated. The attacker-controlled redirect target (A) causes reuse of B's authenticated session, letting A steer authenticated requests to B.
Create a redirect chain that leads to a host B for which a cached Schannel connection with auto_client_cert=TRUE exists, then follow the redirect with a client that has CURLSSLOPT_AUTO_CLIENT_CERT set; the cached connection is reused without a new handshake.
Read the reasoning
The reproduction steps follow directly from the code: establish a connection to B with auto_client_cert=TRUE (B == initial_origin), then have A redirect to B. The redirected request computes auto_client_cert=FALSE but the matcher ignores it, reusing B's authenticated connection.
Read the original report (excerpt)
Source code
| 211 | CLONE_STRING(CRLfile); |
| 212 | /* SSL credentials: client certificate */ |
| 213 | CLONE_STRING(clientcert); |
| 214 | CLONE_STRING(cert_type); |
| 215 | CLONE_STRING(key); |
| 216 | CLONE_STRING(key_type); |
| 217 | CLONE_STRING(key_passwd); |
| 218 | CLONE_BLOB(key_blob); |
| 219 | return TRUE; |
| 220 | } |
| 221 | |
| 222 | static void ssl_easy_config_compl_options(struct Curl_peer *origin, |
| 223 | struct Curl_peer *initial_origin, |
| 224 | struct ssl_config_data *sslc) |
| 225 | { |
| 226 | uint8_t options = sslc->primary.ssl_options; |
| 227 | /* If set via CURLOPT_(PROXY_)SSL_OPTIONS, we definitely use it. |
| 228 | * If not, we switch it on for supported backends if no custom |
| 229 | * CA settings exist. */ |
| 230 | sslc->primary.native_ca_store = !!(options & CURLSSLOPT_NATIVE_CA); |
| 231 | sslc->enable_beast = !!(options & CURLSSLOPT_ALLOW_BEAST); |
| 232 | sslc->no_partialchain = !!(options & CURLSSLOPT_NO_PARTIALCHAIN); |
| 233 | sslc->no_revoke = !!(options & CURLSSLOPT_NO_REVOKE); |
| 234 | sslc->revoke_best_effort = !!(options & CURLSSLOPT_REVOKE_BEST_EFFORT); |
| 235 | sslc->earlydata = !!(options & CURLSSLOPT_EARLYDATA); |
| 236 | |
| C1: auto_client_cert computed per-request based on origin vs initial_origin | |
| C3: auto_client_cert becomes FALSE when origin != initial_origin | |
| C5: auto_client_cert derivation | |
| 237 | sslc->auto_client_cert = Curl_peer_equal(origin, initial_origin) && |
| 238 | !!(options & CURLSSLOPT_AUTO_CLIENT_CERT); |
| 239 | } |
| 240 | |
| 241 | static char *ssl_easy_steal(struct Curl_easy *data, enum dupstring id) |
| 242 | { |
| 243 | /* For connection matching, we borrow string references from data |
| 244 | * THIS IS NOT REALLY NICE. */ |
| 245 | return CURL_UNCONST(CURL_EASY_STR(data, id)); |
| 246 | } |
| 247 | |
| 248 | CURLcode Curl_ssl_easy_config_complete(struct Curl_easy *data, |
| 249 | struct Curl_peer *origin) |
| 250 | { |
| 251 | struct ssl_config_data *sslc = &data->set.ssl; |
| 252 | #if defined(CURL_CA_PATH) || defined(CURL_CA_BUNDLE) |
| 253 | CURLcode result; |
| 254 | #endif |
| 255 | |
| 256 | ssl_easy_config_compl_options(origin, data->state.initial_origin, sslc); |
| 257 | |
| 258 | if(Curl_ssl_backend() != CURLSSLBACKEND_SCHANNEL) { |
| 259 | #if defined(USE_APPLE_SECTRUST) || defined(CURL_CA_NATIVE) |
| 260 | if(!sslc->custom_capath && !sslc->custom_cafile && !sslc->custom_cablob) |
| 261 | sslc->primary.native_ca_store = TRUE; |
| 262 | #endif |
| 263 | #ifdef CURL_CA_PATH |
| 264 | if(!sslc->custom_capath && !CURL_EASY_STR(data, STRING_SSL_CAPATH)) { |
| 265 | result = Curl_setstropt(data, STRING_SSL_CAPATH, CURL_CA_PATH); |
| 266 | if(result) |
| 267 | return result; |
| 268 | } |
| 269 | #endif |
| 270 | #ifdef CURL_CA_BUNDLE |
| 271 | if(!sslc->custom_cafile && !CURL_EASY_STR(data, STRING_SSL_CAFILE)) { |
| 272 | result = Curl_setstropt(data, STRING_SSL_CAFILE, CURL_CA_BUNDLE); |
| 273 | if(result) |
| 274 | return result; |
| 275 | } |
| 276 | #endif |
| 277 | } |
| 278 | sslc->primary.CAfile = ssl_easy_steal(data, STRING_SSL_CAFILE); |
| 279 | sslc->primary.CRLfile = ssl_easy_steal(data, STRING_SSL_CRLFILE); |
| 280 | sslc->primary.CApath = ssl_easy_steal(data, STRING_SSL_CAPATH); |
| 281 | sslc->primary.cipher_list = ssl_easy_steal(data, STRING_SSL_CIPHER_LIST); |
| 282 | sslc->primary.cipher_list13 = ssl_easy_steal(data, STRING_SSL_CIPHER13_LIST); |
| 283 | sslc->primary.signature_algorithms = |
| 284 | ssl_easy_steal(data, STRING_SSL_SIGNATURE_ALGORITHMS); |
| 285 | sslc->primary.ca_info_blob = data->set.blobs[BLOB_CAINFO]; |
| 286 | sslc->primary.curves = ssl_easy_steal(data, STRING_SSL_EC_CURVES); |
| 287 | /* Maybe these should not be used for another origin. But for |
| 288 | * backwards compatibility, keep them in. */ |
| 289 | sslc->primary.issuercert = ssl_easy_steal(data, STRING_SSL_ISSUERCERT); |
| 290 | sslc->primary.issuercert_blob = data->set.blobs[BLOB_SSL_ISSUERCERT]; |
| 291 | |
| 292 | if(Curl_peer_equal(data->state.initial_origin, origin)) { |
| 293 | sslc->primary.pinned_key = |
| 294 | ssl_easy_steal(data, STRING_SSL_PINNEDPUBLICKEY); |
| 295 | sslc->primary.cert_blob = data->set.blobs[BLOB_CERT]; |
| 296 | sslc->primary.cert_type = ssl_easy_steal(data, STRING_CERT_TYPE); |
| 297 | sslc->primary.key = ssl_easy_steal(data, STRING_KEY); |
| 298 | sslc->primary.key_type = ssl_easy_steal(data, STRING_KEY_TYPE); |
| 299 | sslc->primary.key_passwd = ssl_easy_steal(data, STRING_KEY_PASSWD); |
| 300 | sslc->primary.clientcert = ssl_easy_steal(data, STRING_CERT); |
| 301 | sslc->primary.key_blob = data->set.blobs[BLOB_KEY]; |
| 302 | } |
| 303 | else { |
| 304 | sslc->primary.pinned_key = NULL; |
| 305 | sslc->primary.cert_blob = NULL; |
| 306 | sslc->primary.cert_type = NULL; |
| 307 | sslc->primary.key = NULL; |
| 308 | sslc->primary.key_type = NULL; |
| 309 | sslc->primary.key_passwd = NULL; |
| 310 | sslc->primary.clientcert = NULL; |
| 311 | sslc->primary.key_blob = NULL; |
| 312 | } |
| 313 | |
| 314 | #ifndef CURL_DISABLE_PROXY |
| 315 | sslc = &data->set.proxy_ssl; |
| 316 | /* no initial origin for proxy, it is not changed for redirects */ |
| 317 | ssl_easy_config_compl_options(NULL, NULL, sslc); |
| 318 | |
| 319 | if(Curl_ssl_backend() != CURLSSLBACKEND_SCHANNEL) { |
| 320 | #if defined(USE_APPLE_SECTRUST) || defined(CURL_CA_NATIVE) |
| 321 | if(!sslc->custom_capath && !sslc->custom_cafile && !sslc->custom_cablob) |
| 322 | sslc->primary.native_ca_store = TRUE; |
| 323 | #endif |
| 324 | #ifdef CURL_CA_PATH |
| 325 | if(!sslc->custom_capath && |
| 326 | !CURL_EASY_STR(data, STRING_SSL_CAPATH_PROXY)) { |
| 327 | result = Curl_setstropt(data, STRING_SSL_CAPATH_PROXY, CURL_CA_PATH); |
| 328 | if(result) |
| 329 | return result; |
| 330 | } |
| 331 | #endif |
| 332 | #ifdef CURL_CA_BUNDLE |
| 333 | if(!sslc->custom_cafile && |
| 334 | !CURL_EASY_STR(data, STRING_SSL_CAFILE_PROXY)) { |
| 335 | result = Curl_setstropt(data, STRING_SSL_CAFILE_PROXY, CURL_CA_BUNDLE); |
| 336 | if(result) |
| 337 | return result; |
| 338 | } |
| 339 | #endif |
| 340 | } |
| 341 | sslc->primary.CAfile = ssl_easy_steal(data, STRING_SSL_CAFILE_PROXY); |
| 342 | sslc->primary.CApath = ssl_easy_steal(data, STRING_SSL_CAPATH_PROXY); |
| 343 | sslc->primary.cipher_list = |
| 344 | ssl_easy_steal(data, STRING_SSL_CIPHER_LIST_PROXY); |
| 345 | sslc->primary.cipher_list13 = |
| 346 | ssl_easy_steal(data, STRING_SSL_CIPHER13_LIST_PROXY); |
| 347 | sslc->primary.pinned_key = |
| 348 | ssl_easy_steal(data, STRING_SSL_PINNEDPUBLICKEY_PROXY); |
| 349 | sslc->primary.cert_blob = data->set.blobs[BLOB_CERT_PROXY]; |
| 350 | sslc->primary.ca_info_blob = data->set.blobs[BLOB_CAINFO_PROXY]; |
| 351 | sslc->primary.issuercert = ssl_easy_steal(data, STRING_SSL_ISSUERCERT_PROXY); |
| 352 | sslc->primary.issuercert_blob = data->set.blobs[BLOB_SSL_ISSUERCERT_PROXY]; |
| 353 | sslc->primary.CRLfile = ssl_easy_steal(data, STRING_SSL_CRLFILE_PROXY); |
| 354 | sslc->primary.cert_type = ssl_easy_steal(data, STRING_CERT_TYPE_PROXY); |
| 355 | sslc->primary.key = ssl_easy_steal(data, STRING_KEY_PROXY); |
| 356 | sslc->primary.key_type = ssl_easy_steal(data, STRING_KEY_TYPE_PROXY); |
| 357 | sslc->primary.key_passwd = ssl_easy_steal(data, STRING_KEY_PASSWD_PROXY); |
| 358 | sslc->primary.clientcert = ssl_easy_steal(data, STRING_CERT_PROXY); |
| 359 | sslc->primary.key_blob = data->set.blobs[BLOB_KEY_PROXY]; |
| 360 | #endif /* CURL_DISABLE_PROXY */ |
| 361 | |
| 362 | return CURLE_OK; |
| 363 | } |
| 49 | struct curl_blob *key_blob; |
| 50 | char *curves; /* list of curves to use */ |
| 51 | uint32_t version_max; /* max supported version the client wants to use */ |
| 52 | uint8_t ssl_options; /* the CURLOPT_SSL_OPTIONS bitmask */ |
| 53 | uint8_t version; /* what version the client wants to use */ |
| 54 | BIT(verifypeer); /* set TRUE if this is desired */ |
| 55 | BIT(verifyhost); /* set TRUE if CN/SAN must match hostname */ |
| 56 | BIT(verifystatus); /* set TRUE if certificate status must be checked */ |
| 57 | BIT(native_ca_store); /* use the native CA store of operating system */ |
| 58 | BIT(cache_session); /* cache session or not */ |
| 59 | BIT(deep_copy); /* members are deep copies, eg. owned here */ |
| 60 | }; |
| 61 | |
| 62 | struct ssl_config_data { |
| C2: ssl_config_data contains primary (ssl_primary_config) and auto_client_cert as separate fields | |
| 63 | struct ssl_primary_config primary; |
| 64 | long certverifyresult; /* result from the certificate verification */ |
| 65 | curl_ssl_ctx_callback fsslctx; /* function to initialize SSL ctx */ |
| 66 | void *fsslctxp; /* parameter for call back */ |
| 67 | BIT(certinfo); /* gather lots of certificate info */ |
| 68 | BIT(earlydata); /* use TLS 1.3 early data */ |
| 69 | BIT(enable_beast); /* allow this flaw for interoperability's sake */ |
| 70 | BIT(no_revoke); /* disable SSL certificate revocation checks */ |
| 71 | BIT(no_partialchain); /* do not accept partial certificate chains */ |
| 72 | BIT(revoke_best_effort); /* ignore SSL revocation offline/missing revocation |
| 73 | list errors */ |
| 74 | BIT(auto_client_cert); /* automatically locate and use a client |
| C1: auto_client_cert defined in ssl_config_data, not ssl_primary_config | |
| 75 | certificate for authentication (Schannel) */ |
| 76 | BIT(custom_cafile); /* application has set custom CA file */ |
| 77 | BIT(custom_capath); /* application has set custom CA path */ |
| 78 | BIT(custom_cablob); /* application has set custom CA blob */ |
| 79 | }; |
| 80 | |
| 81 | struct ssl_general_config { |
| 82 | int ca_cache_timeout; /* Certificate store cache timeout (seconds) */ |
| 83 | }; |
| 84 | |
| 85 | void Curl_ssl_config_init(struct ssl_primary_config *sslc); |
| 86 | void Curl_ssl_config_cleanup(struct ssl_primary_config *sslc); |
| 87 | |
| 88 | /** |
| 89 | * Init the `data->set.ssl` and `data->set.proxy_ssl` for |
| 90 | * connection matching use. |
| 91 | */ |
| 92 | CURLcode Curl_ssl_easy_config_complete(struct Curl_easy *data, |
| 93 | struct Curl_peer *origin); |
| 94 | |
| 95 | /** |
| 96 | * Init SSL configs (main + proxy) for a new connection from the easy handle. |
| 97 | */ |
| 98 | CURLcode Curl_ssl_conn_config_init(struct Curl_easy *data, |
| 99 | struct connectdata *conn); |
| 100 |
| 297 | timediff_t lastchecked_ms; /* when last checked alive status */ |
| 298 | timediff_t lastupkeep_ms; /* when last done conn_upkeep */ |
| 299 | |
| 300 | #ifndef CURL_DISABLE_PROXY |
| 301 | struct proxy_info socks_proxy; |
| 302 | struct proxy_info http_proxy; |
| 303 | #endif |
| 304 | |
| 305 | struct Curl_cfilter *cfilter[2]; /* connection filters */ |
| 306 | Curl_recv *recv[2]; |
| 307 | Curl_send *send[2]; |
| 308 | /* A connection can have one or two sockets and connection filters. |
| 309 | * The protocol using the 2nd one is FTP for CONTROL+DATA sockets */ |
| 310 | curl_socket_t sock[2]; |
| 311 | |
| 312 | #define CONN_SOCK_IDX_VALID(i) (((i) >= 0) && ((i) < 2)) |
| 313 | |
| 314 | struct { |
| 315 | timediff_t start_ms[2]; /* when filter shutdown started */ |
| 316 | timediff_t timeout_ms; /* 0 means no timeout */ |
| 317 | } shutdown; |
| 318 | |
| 319 | curl_closesocket_callback fclosesocket; /* function closing the socket(s) */ |
| 320 | void *closesocket_client; |
| 321 | |
| 322 | struct ssl_primary_config ssl_config; |
| C1: connection stores only ssl_primary_config | |
| C2: connectdata has only ssl_primary_config ssl_config | |
| 323 | #ifndef CURL_DISABLE_PROXY |
| 324 | struct ssl_primary_config proxy_ssl_config; |
| 325 | #endif |
| 326 | char *options; /* options string, allocated */ |
| 327 | |
| 328 | /*************** Request - specific items ************/ |
| 329 | #ifdef USE_WINDOWS_SSPI |
| 330 | CtxtHandle *sslContext; |
| 331 | #endif |
| 332 | |
| 333 | #ifdef USE_NTLM |
| 334 | curlntlm http_ntlm_state; |
| 335 | curlntlm proxy_ntlm_state; |
| 336 | #endif |
| 337 | |
| 338 | #ifdef USE_SPNEGO |
| 339 | curlnegotiate http_negotiate_state; |
| 340 | curlnegotiate proxy_negotiate_state; |
| 341 | #endif |
| 342 | |
| 343 | /* When this connection is created, store the conditions for the local end |
| 344 | bind. This is stored before the actual bind and before any connection is |
| 345 | made and will serve the purpose of being used for comparison reasons so |
| 346 | that subsequent bound-requested connections are not accidentally reusing |
| 347 | wrong connections. */ |
| 111 | void Curl_ssl_config_cleanup(struct ssl_primary_config *sslc) |
| 112 | { |
| 113 | if(sslc->deep_copy) { |
| 114 | curlx_safefree(sslc->CApath); |
| 115 | curlx_safefree(sslc->CAfile); |
| 116 | curlx_safefree(sslc->issuercert); |
| 117 | curlx_safefree(sslc->clientcert); |
| 118 | curlx_safefree(sslc->cipher_list); |
| 119 | curlx_safefree(sslc->cipher_list13); |
| 120 | curlx_safefree(sslc->pinned_key); |
| 121 | curlx_safefree(sslc->cert_blob); |
| 122 | curlx_safefree(sslc->ca_info_blob); |
| 123 | curlx_safefree(sslc->issuercert_blob); |
| 124 | curlx_safefree(sslc->key_blob); |
| 125 | curlx_safefree(sslc->curves); |
| 126 | curlx_safefree(sslc->signature_algorithms); |
| 127 | curlx_safefree(sslc->CRLfile); |
| 128 | curlx_safefree(sslc->cert_type); |
| 129 | curlx_safefree(sslc->key); |
| 130 | curlx_safefree(sslc->key_type); |
| 131 | curlx_safefree(sslc->key_passwd); |
| 132 | sslc->deep_copy = FALSE; |
| 133 | } |
| 134 | } |
| 135 | |
| 136 | static bool match_ssl_primary_config(struct Curl_easy *data, |
| C1: match_ssl_primary_config only compares ssl_primary_config fields, not auto_client_cert | |
| C2: match_ssl_primary_config signature takes ssl_primary_config* | |
| C3: matcher doesn't check auto_client_cert | |
| C4: cached connection reuse bypasses handshake where these flags would apply | |
| C5: matcher omission | |
| 137 | struct ssl_primary_config *c1, |
| 138 | struct ssl_primary_config *c2) |
| 139 | { |
| 140 | (void)data; |
| 141 | if((c1->version == c2->version) && |
| 142 | (c1->version_max == c2->version_max) && |
| 143 | (c1->ssl_options == c2->ssl_options) && |
| 144 | (c1->native_ca_store == c2->native_ca_store) && |
| 145 | (c1->verifypeer == c2->verifypeer) && |
| 146 | (c1->verifyhost == c2->verifyhost) && |
| 147 | (c1->verifystatus == c2->verifystatus) && |
| 148 | blobcmp(c1->cert_blob, c2->cert_blob) && |
| 149 | blobcmp(c1->ca_info_blob, c2->ca_info_blob) && |
| 150 | blobcmp(c1->issuercert_blob, c2->issuercert_blob) && |
| 151 | blobcmp(c1->key_blob, c2->key_blob) && |
| 152 | Curl_safecmp(c1->CApath, c2->CApath) && |
| 153 | Curl_safecmp(c1->CAfile, c2->CAfile) && |
| 154 | Curl_safecmp(c1->issuercert, c2->issuercert) && |
| 155 | Curl_safecmp(c1->clientcert, c2->clientcert) && |
| 156 | curl_strequal(c1->cipher_list, c2->cipher_list) && |
| 157 | curl_strequal(c1->cipher_list13, c2->cipher_list13) && |
| 158 | curl_strequal(c1->curves, c2->curves) && |
| 159 | curl_strequal(c1->signature_algorithms, c2->signature_algorithms) && |
| 160 | Curl_safecmp(c1->CRLfile, c2->CRLfile) && |
| 161 | Curl_safecmp(c1->pinned_key, c2->pinned_key) && |
| 162 | curl_strequal(c1->cert_type, c2->cert_type) && |
| 163 | Curl_safecmp(c1->key, c2->key) && |
| 164 | curl_strequal(c1->key_type, c2->key_type) && |
| 165 | !Curl_timestrcmp(c1->key_passwd, c2->key_passwd)) |
| 166 | return TRUE; |
| 167 | |
| 168 | return FALSE; |
| 169 | } |
| 170 | |
| 171 | bool Curl_ssl_conn_config_match(struct Curl_easy *data, |
| 172 | struct connectdata *candidate, |
| 173 | bool proxy) |
| 174 | { |
| 175 | #ifndef CURL_DISABLE_PROXY |
| 176 | if(proxy) |
| 177 | return match_ssl_primary_config(data, &data->set.proxy_ssl.primary, |
| 178 | &candidate->proxy_ssl_config); |
| 179 | #else |
| 180 | (void)proxy; |
| 181 | #endif |
| 182 | return match_ssl_primary_config(data, &data->set.ssl.primary, |
| 183 | &candidate->ssl_config); |
| 184 | } |
| 3389 | } |
| 3390 | else if(k->httpcode < 300) |
| 3391 | data->state.resume_from = 0; /* get everything */ |
| 3392 | } |
| 3393 | return CURLE_OK; |
| 3394 | } |
| 3395 | |
| 3396 | /* |
| 3397 | * http_header_l() parses a single response header starting with L. |
| 3398 | */ |
| 3399 | static CURLcode http_header_l(struct Curl_easy *data, |
| 3400 | const char *hd, size_t hdlen) |
| 3401 | { |
| 3402 | struct connectdata *conn = data->conn; |
| 3403 | struct SingleRequest *k = &data->req; |
| 3404 | const char *v = (!k->http_bodyless && |
| 3405 | (data->set.timecondition || data->set.get_filetime)) ? |
| 3406 | HD_VAL(hd, hdlen, "Last-Modified:") : NULL; |
| 3407 | if(v) { |
| 3408 | if(Curl_getdate_capped(v, &k->timeofdoc)) |
| 3409 | k->timeofdoc = 0; |
| 3410 | if(data->set.get_filetime) |
| 3411 | data->info.filetime = k->timeofdoc; |
| 3412 | return CURLE_OK; |
| 3413 | } |
| 3414 | if(HD_IS(hd, hdlen, "Location:")) { |
| C3: http_header_l handles Location: header for redirects | |
| C5: redirect handling in http_header_l | |
| 3415 | /* this is the URL that the server advises us to use instead */ |
| 3416 | char *location = Curl_copy_header_value(hd); |
| 3417 | if(!location) |
| 3418 | return CURLE_OUT_OF_MEMORY; |
| 3419 | if(!*location || |
| 3420 | (data->req.location && !strcmp(data->req.location, location))) { |
| 3421 | /* ignore empty header, or exact repeat of a previous one */ |
| 3422 | curlx_free(location); |
| 3423 | return CURLE_OK; |
| 3424 | } |
| 3425 | else { |
| 3426 | /* has value and is not an exact repeat */ |
| 3427 | if(data->req.location) { |
| 3428 | failf(data, "Multiple Location headers"); |
| 3429 | curlx_free(location); |
| 3430 | return CURLE_WEIRD_SERVER_REPLY; |
| 3431 | } |
| 3432 | data->req.location = location; |
| 3433 | |
| 3434 | if((k->httpcode >= 300 && k->httpcode < 400) && |
| C3: on 3xx with follow_mode, sets newurl and this_is_a_follow | |
| 3435 | data->set.http_follow_mode) { |
| 3436 | CURLcode result; |
| 3437 | DEBUGASSERT(!data->req.newurl); |
| 3438 | data->req.newurl = curlx_strdup(data->req.location); /* clone */ |
| 3439 | if(!data->req.newurl) |
| 3440 | return CURLE_OUT_OF_MEMORY; |
| 3441 | |
| 3442 | /* some cases of POST and PUT etc needs to rewind the data |
| 3443 | stream at this point */ |
| 3444 | result = http_perhapsrewind(data, conn); |
| 3445 | if(result) |
| 3446 | return result; |
| 3447 | |
| 3448 | /* mark the next request as a followed location: */ |
| 3449 | data->state.this_is_a_follow = TRUE; |
| 3450 | } |
| 3451 | } |
| 3452 | } |
| 3453 | return CURLE_OK; |
| 3454 | } |
| 3455 | |
| 3456 | /* |
| 3457 | * http_header_p() parses a single response header starting with P. |
| 3458 | */ |
| 3459 | static CURLcode http_header_p(struct Curl_easy *data, |
| 3460 | const char *hd, size_t hdlen) |
| 3461 | { |
| 3462 | struct SingleRequest *k = &data->req; |
| 3463 | |
| 3464 | #ifndef CURL_DISABLE_PROXY |
| 758 | SCH_CRED_IGNORE_REVOCATION_OFFLINE | |
| 759 | SCH_CRED_REVOCATION_CHECK_CHAIN; |
| 760 | |
| 761 | DEBUGF(infof(data, "schannel: ignore revocation offline errors")); |
| 762 | } |
| 763 | else { |
| 764 | flags |= SCH_CRED_REVOCATION_CHECK_CHAIN; |
| 765 | |
| 766 | DEBUGF(infof(data, "schannel: checking server certificate revocation")); |
| 767 | } |
| 768 | } |
| 769 | else { |
| 770 | flags = SCH_CRED_MANUAL_CRED_VALIDATION | |
| 771 | SCH_CRED_IGNORE_NO_REVOCATION_CHECK | |
| 772 | SCH_CRED_IGNORE_REVOCATION_OFFLINE; |
| 773 | DEBUGF(infof(data, "schannel: disabled server cert revocation checks")); |
| 774 | } |
| 775 | |
| 776 | if(!conn_config->verifyhost) { |
| 777 | flags |= SCH_CRED_NO_SERVERNAME_CHECK; |
| 778 | DEBUGF(infof(data, "schannel: verifyhost setting prevents Schannel from " |
| 779 | "comparing the supplied target name with the subject " |
| 780 | "names in server certificates.")); |
| 781 | } |
| 782 | |
| 783 | if(!ssl_config->auto_client_cert) { |
| C4: auto_client_cert=FALSE disables automatic client cert via SCH_CRED_NO_DEFAULT_CREDS | |
| 784 | flags &= ~(DWORD)SCH_CRED_USE_DEFAULT_CREDS; |
| 785 | flags |= SCH_CRED_NO_DEFAULT_CREDS; |
| 786 | infof(data, "schannel: disabled automatic use of client certificate"); |
| 787 | } |
| 788 | else |
| 789 | infof(data, "schannel: enabled automatic use of client certificate"); |
| 790 | |
| 791 | DEBUGASSERT(conn_config->version != CURL_SSLVERSION_DEFAULT); |
| 792 | switch(conn_config->version) { |
| 793 | case CURL_SSLVERSION_TLSv1: |
| 794 | case CURL_SSLVERSION_TLSv1_0: |
| 795 | case CURL_SSLVERSION_TLSv1_1: |
| 796 | case CURL_SSLVERSION_TLSv1_2: |
| 797 | case CURL_SSLVERSION_TLSv1_3: { |
| 798 | result = schannel_set_ssl_version_min_max(&enabled_protocols, cf, data); |
| 799 | if(result) |
| 800 | return result; |
| 801 | break; |
| 802 | } |
| 803 | case CURL_SSLVERSION_SSLv3: |
| 804 | case CURL_SSLVERSION_SSLv2: |
| 805 | failf(data, "SSL versions not supported"); |
| 806 | return CURLE_NOT_BUILT_IN; |
| 807 | default: |
| 808 | failf(data, "Unrecognized parameter passed via CURLOPT_SSLVERSION"); |
| 809 | return CURLE_SSL_CONNECT_ERROR; |
| 810 | } |
| 811 |
| 956 | |
| 957 | *list_len = curlx_uitous(cur - list_start_index); |
| 958 | *extension_len = (unsigned int)(*list_len + |
| 959 | sizeof(unsigned int) + sizeof(unsigned short)); |
| 960 | |
| 961 | InitSecBuffer(&inbuf, SECBUFFER_APPLICATION_PROTOCOLS, alpn_buffer, cur); |
| 962 | InitSecBufferDesc(&inbuf_desc, &inbuf, 1); |
| 963 | |
| 964 | Curl_alpn_to_proto_str(&proto, connssl->alpn); |
| 965 | infof(data, VTLS_INFOF_ALPN_OFFER_1STR, proto.data); |
| 966 | } |
| 967 | else { |
| 968 | InitSecBuffer(&inbuf, SECBUFFER_EMPTY, NULL, 0); |
| 969 | InitSecBufferDesc(&inbuf_desc, &inbuf, 1); |
| 970 | } |
| 971 | |
| 972 | /* setup output buffer */ |
| 973 | InitSecBuffer(&outbuf, SECBUFFER_EMPTY, NULL, 0); |
| 974 | InitSecBufferDesc(&outbuf_desc, &outbuf, 1); |
| 975 | |
| 976 | /* security request flags */ |
| 977 | backend->req_flags = |
| 978 | ISC_REQ_SEQUENCE_DETECT | ISC_REQ_REPLAY_DETECT | |
| 979 | ISC_REQ_CONFIDENTIALITY | ISC_REQ_ALLOCATE_MEMORY | |
| 980 | ISC_REQ_STREAM | |
| 981 | (!ssl_config->auto_client_cert ? ISC_REQ_USE_SUPPLIED_CREDS : 0); |
| C4: auto_client_cert=FALSE sets ISC_REQ_USE_SUPPLIED_CREDS | |
| 982 | |
| 983 | /* allocate memory for the security context handle */ |
| 984 | backend->ctxt = (struct Curl_schannel_ctxt *) |
| 985 | curlx_calloc(1, sizeof(struct Curl_schannel_ctxt)); |
| 986 | if(!backend->ctxt) { |
| 987 | failf(data, "schannel: unable to allocate memory"); |
| 988 | return CURLE_OUT_OF_MEMORY; |
| 989 | } |
| 990 | |
| 991 | /* Schannel InitializeSecurityContext: |
| 992 | https://learn.microsoft.com/windows/win32/api/sspi/nf-sspi-initializesecuritycontextw |
| 993 | |
| 994 | At the moment we do not pass inbuf unless we are using ALPN since we only |
| 995 | use it for that, and WINE (for which we currently disable ALPN) is giving |
| 996 | us problems with inbuf regardless. https://github.com/curl/curl/issues/983 |
| 997 | */ |
| 998 | sspi_status = Curl_pSecFn->InitializeSecurityContext( |
| 999 | &backend->cred->cred_handle, NULL, backend->cred->sni_hostname, |
| 1000 | backend->req_flags, 0, 0, |
| 1001 | (backend->use_alpn ? &inbuf_desc : NULL), |
| 1002 | 0, &backend->ctxt->ctxt_handle, |
| 1003 | &outbuf_desc, &backend->ret_flags, NULL); |
| 1004 | |
| 1005 | if(sspi_status != SEC_I_CONTINUE_NEEDED) { |
| 1006 | char buffer[STRERROR_LEN]; |
Show 7 lines (not cited by the judge)
| 321 | bool Curl_peer_equal(struct Curl_peer *p1, struct Curl_peer *p2) |
| 322 | { |
| 323 | return (p1 == p2) || |
| 324 | (p1 && p2 && |
| 325 | (p1->scheme == p2->scheme) && |
| 326 | Curl_peer_same_destination(p1, p2)); |
| 327 | } |
Show 1 lines (not cited by the judge)
| 1036 | #define BIT(x) curl_bit x:1 |
Show 1 lines (not cited by the judge)
| 971 | #define CURLSSLOPT_AUTO_CLIENT_CERT (1L << 5) |
Show 1 lines (not cited by the judge)
| 1048 | #define FALSE false |
Show 2 lines (not cited by the judge)
| 3214 | #define HD_IS(hd, hdlen, n) \ |
| 3215 | (((hdlen) >= (sizeof(n) - 1)) && curl_strnequal(n, hd, sizeof(n) - 1)) |
Show 1 lines (not cited by the judge)
| 1045 | #define TRUE true |
Show 11 lines (not cited by the judge)
| 851 | CURLcode curl_easy_perform(CURL *curl) |
| 852 | { |
| 853 | struct Curl_eapi_guard guard = { 0 }; |
| 854 | CURLcode result; |
| 855 | |
| 856 | if(CURL_EAPI_ENTER(&guard, curl, easy_perform, &result)) { |
| 857 | result = easy_perform(curl, FALSE); |
| 858 | } |
| 859 | CURL_EAPI_LEAVE(&guard); |
| 860 | return result; |
| 861 | } |
Show 1 lines (not cited by the judge)
| 1470 | #define curlx_strdup(ptr) curl_dbg_strdup(ptr, __LINE__, __FILE__) |
Show 5 lines (not cited by the judge)
| 142 | #define infof(data, ...) \ |
| 143 | do { \ |
| 144 | if(Curl_trc_is_verbose(data)) \ |
| 145 | Curl_infof(data, __VA_ARGS__); \ |
| 146 | } while(0) |
Show 9 lines (not cited by the judge)
| 273 | int main(void) |
| 274 | { |
| 275 | size_t i; |
| 276 | |
| 277 | for(i = 0; i < CURL_ARRAYSIZE(disabled); i++) |
| 278 | puts(disabled[i]); |
| 279 | |
| 280 | return 0; |
| 281 | } |