From cc6777d939976b2f322dcbe5ae76ef28c6b4632d Mon Sep 17 00:00:00 2001 From: "Song X. Gao" <39278329+xsgao-github@users.noreply.github.com> Date: Mon, 11 May 2026 12:45:15 -0400 Subject: [PATCH] spnego_sspi: honor CURLOPT_GSSAPI_DELEGATION for Windows SSPI Make CURLOPT_GSSAPI_DELEGATION effective on Windows builds that use SSPI (instead of a native GSS-API implementation), so Kerberos delegation can be requested during SPNEGO/Negotiate authentication. Closes #21528 --- lib/setopt.c | 2 +- lib/urldata.h | 5 +-- lib/vauth/spnego_sspi.c | 24 ++++++----- tests/data/Makefile.am | 2 +- tests/data/test3302 | 19 +++++++++ tests/unit/Makefile.inc | 2 +- tests/unit/unit3302.c | 89 +++++++++++++++++++++++++++++++++++++++++ 7 files changed, 128 insertions(+), 15 deletions(-) create mode 100644 tests/data/test3302 create mode 100644 tests/unit/unit3302.c diff --git a/lib/setopt.c b/lib/setopt.c index 24d0c42bcf..61d87be06f 100644 --- a/lib/setopt.c +++ b/lib/setopt.c @@ -1288,7 +1288,7 @@ static CURLcode setopt_long_misc(struct Curl_easy *data, CURLoption option, case CURLOPT_ALTSVC_CTRL: return Curl_altsvc_ctrl(data, arg); #endif -#ifdef HAVE_GSSAPI +#if defined(HAVE_GSSAPI) || defined(USE_WINDOWS_SSPI) case CURLOPT_GSSAPI_DELEGATION: s->gssapi_delegation = (unsigned char)arg & (CURLGSSAPI_DELEGATION_POLICY_FLAG | CURLGSSAPI_DELEGATION_FLAG); diff --git a/lib/urldata.h b/lib/urldata.h index 335e61c4a3..2889e936a4 100644 --- a/lib/urldata.h +++ b/lib/urldata.h @@ -1191,9 +1191,8 @@ struct UserDefined { uint8_t ipver; /* the CURL_IPRESOLVE_* defines in the public header file 0 - whatever, 1 - v2, 2 - v6 */ uint8_t upload_flags; /* flags set by CURLOPT_UPLOAD_FLAGS */ -#ifdef HAVE_GSSAPI - /* GSS-API credential delegation, see the documentation of - CURLOPT_GSSAPI_DELEGATION */ +#if defined(HAVE_GSSAPI) || defined(USE_WINDOWS_SSPI) + /* GSS-API/SSPI credential delegation, see CURLOPT_GSSAPI_DELEGATION */ uint8_t gssapi_delegation; #endif uint8_t http_follow_mode; /* follow HTTP redirects */ diff --git a/lib/vauth/spnego_sspi.c b/lib/vauth/spnego_sspi.c index 1baf59320a..eeae021484 100644 --- a/lib/vauth/spnego_sspi.c +++ b/lib/vauth/spnego_sspi.c @@ -223,15 +223,21 @@ CURLcode Curl_auth_decode_spnego_message(struct Curl_easy *data, resp_buf.cbBuffer = curlx_uztoul(nego->token_max); /* Generate our challenge-response message */ - nego->status = - Curl_pSecFn->InitializeSecurityContext(nego->credentials, - chlg ? nego->context : NULL, - nego->spn, - ISC_REQ_CONFIDENTIALITY, - 0, SECURITY_NATIVE_DREP, - chlg ? &chlg_desc : NULL, - 0, nego->context, - &resp_desc, &attrs, NULL); + { + DWORD sspi_flags = ISC_REQ_CONFIDENTIALITY; + if(data->set.gssapi_delegation & (CURLGSSAPI_DELEGATION_FLAG | + CURLGSSAPI_DELEGATION_POLICY_FLAG)) + sspi_flags |= ISC_REQ_DELEGATE | ISC_REQ_MUTUAL_AUTH; + nego->status = + Curl_pSecFn->InitializeSecurityContext(nego->credentials, + chlg ? nego->context : NULL, + nego->spn, + sspi_flags, + 0, SECURITY_NATIVE_DREP, + chlg ? &chlg_desc : NULL, + 0, nego->context, + &resp_desc, &attrs, NULL); + } /* Free the decoded challenge as it is not required anymore */ curlx_free(chlg); diff --git a/tests/data/Makefile.am b/tests/data/Makefile.am index 12f6bfbc0c..8dcf2d360c 100644 --- a/tests/data/Makefile.am +++ b/tests/data/Makefile.am @@ -287,7 +287,7 @@ test3200 test3201 test3202 test3203 test3204 test3205 test3206 test3207 \ test3208 test3209 test3210 test3211 test3212 test3213 test3214 test3215 \ test3216 test3217 test3218 test3219 test3220 \ \ -test3300 test3301 \ +test3300 test3301 test3302 \ \ test4000 test4001 diff --git a/tests/data/test3302 b/tests/data/test3302 new file mode 100644 index 0000000000..35ccfc79af --- /dev/null +++ b/tests/data/test3302 @@ -0,0 +1,19 @@ + + + + +unittest +CURLOPT_GSSAPI_DELEGATION + + + +# Client-side + + +unittest + + +CURLOPT_GSSAPI_DELEGATION stores flags in data->set on GSS-API and SSPI builds + + + diff --git a/tests/unit/Makefile.inc b/tests/unit/Makefile.inc index 102c15f3b2..f0ce3d4eef 100644 --- a/tests/unit/Makefile.inc +++ b/tests/unit/Makefile.inc @@ -47,4 +47,4 @@ TESTS_C = \ unit2600.c unit2601.c unit2602.c unit2603.c unit2604.c unit2605.c \ unit3200.c unit3205.c \ unit3211.c unit3212.c unit3213.c unit3214.c unit3216.c unit3219.c \ - unit3300.c unit3301.c + unit3300.c unit3301.c unit3302.c diff --git a/tests/unit/unit3302.c b/tests/unit/unit3302.c new file mode 100644 index 0000000000..6c0abed90c --- /dev/null +++ b/tests/unit/unit3302.c @@ -0,0 +1,89 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , 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 "unitcheck.h" +#include "urldata.h" + +static CURLcode test_unit3302(const char *arg) +{ + UNITTEST_BEGIN_SIMPLE + +#if defined(HAVE_GSSAPI) || defined(USE_WINDOWS_SSPI) + struct Curl_easy *easy; + CURLcode result; + + curl_global_init(CURL_GLOBAL_ALL); + easy = curl_easy_init(); + if(!easy) { + curl_global_cleanup(); + goto unit_test_abort; /* OOM during setup, not a test failure */ + } + + /* CURLGSSAPI_DELEGATION_FLAG must be stored */ + result = curl_easy_setopt(easy, CURLOPT_GSSAPI_DELEGATION, + CURLGSSAPI_DELEGATION_FLAG); + fail_unless(result == CURLE_OK, + "setopt DELEGATION_FLAG returned error"); + fail_unless(easy->set.gssapi_delegation == CURLGSSAPI_DELEGATION_FLAG, + "DELEGATION_FLAG not stored in data->set"); + + /* CURLGSSAPI_DELEGATION_POLICY_FLAG must be stored */ + result = curl_easy_setopt(easy, CURLOPT_GSSAPI_DELEGATION, + CURLGSSAPI_DELEGATION_POLICY_FLAG); + fail_unless(result == CURLE_OK, + "setopt DELEGATION_POLICY_FLAG returned error"); + fail_unless(easy->set.gssapi_delegation == CURLGSSAPI_DELEGATION_POLICY_FLAG, + "DELEGATION_POLICY_FLAG not stored in data->set"); + + /* both flags together */ + result = curl_easy_setopt(easy, CURLOPT_GSSAPI_DELEGATION, + CURLGSSAPI_DELEGATION_FLAG | + CURLGSSAPI_DELEGATION_POLICY_FLAG); + fail_unless(result == CURLE_OK, + "setopt both flags returned error"); + fail_unless(easy->set.gssapi_delegation == + (CURLGSSAPI_DELEGATION_FLAG | CURLGSSAPI_DELEGATION_POLICY_FLAG), + "both delegation flags not stored in data->set"); + + /* CURLGSSAPI_DELEGATION_NONE must clear the field */ + result = curl_easy_setopt(easy, CURLOPT_GSSAPI_DELEGATION, + CURLGSSAPI_DELEGATION_NONE); + fail_unless(result == CURLE_OK, + "setopt DELEGATION_NONE returned error"); + fail_unless(easy->set.gssapi_delegation == 0, + "gssapi_delegation not cleared by DELEGATION_NONE"); + + /* unknown bits must be masked off */ + result = curl_easy_setopt(easy, CURLOPT_GSSAPI_DELEGATION, 0xFFL); + fail_unless(result == CURLE_OK, + "setopt 0xFF returned error"); + fail_unless(easy->set.gssapi_delegation == + (CURLGSSAPI_DELEGATION_FLAG | CURLGSSAPI_DELEGATION_POLICY_FLAG), + "unknown bits not masked off"); + + curl_easy_cleanup(easy); + curl_global_cleanup(); +#endif /* HAVE_GSSAPI || USE_WINDOWS_SSPI */ + + UNITTEST_END_SIMPLE +}