spnego: block NTLM fallback in SPNEGO negotiation
No binary (build failed)
—
(log yok)
| @@ -1344,6 +1344,11 @@ if(CURL_USE_GSSAPI) | |
| elseif(GSS_VERSION) # MIT | |
| set(CURL_KRB5_VERSION "\"${GSS_VERSION}\"") | |
| endif() | |
| + | |
| + | cmake_push_check_state() |
| + | list(APPEND CMAKE_REQUIRED_LIBRARIES CURL::gss) |
| + | check_function_exists("gss_set_neg_mechs" HAVE_GSS_SET_NEG_MECHS) |
| + | cmake_pop_check_state() |
| else() | |
| message(WARNING "GSSAPI has been requested, but no supporting libraries found. Skipping.") | |
| endif() | |
| @@ -2097,6 +2097,7 @@ if test "$want_gss" = "yes"; then | |
| AC_MSG_RESULT([no]) | |
| AC_MSG_ERROR([--with-gssapi was specified, but a GSS-API library was not found.]) | |
| ]) | |
| + | AC_CHECK_FUNCS([gss_set_neg_mechs]) |
| fi | |
| build_libstubgss=no | |
| @@ -517,6 +517,7 @@ the parent project, ideally in the "extra" find package redirect file: | |
| Available variables: | |
| - `HAVE_DES_ECB_ENCRYPT`: `DES_ecb_encrypt` present in OpenSSL (or fork). | |
| + | - `HAVE_GSS_SET_NEG_MECHS`: `gss_set_neg_mechs` present in GSS-API library. |
| - `HAVE_LDAP_INIT_FD`: `ldap_init_fd` present in LDAP library. | |
| - `HAVE_LDAP_URL_PARSE`: `ldap_url_parse` present in LDAP library. | |
| - `HAVE_MBEDTLS_DES_CRYPT_ECB`: `mbedtls_des_crypt_ecb` present in mbedTLS <4. | |
| @@ -330,6 +330,9 @@ | |
| /* if you have the GNU gssapi libraries */ | |
| #cmakedefine HAVE_GSSGNU 1 | |
| + | /* if you have gss_set_neg_mechs */ |
| + | #cmakedefine HAVE_GSS_SET_NEG_MECHS 1 |
| + | |
| /* MIT Kerberos version */ | |
| #cmakedefine CURL_KRB5_VERSION ${CURL_KRB5_VERSION} | |
| @@ -81,7 +81,6 @@ enum min_err_code { | |
| /* libcurl is also passing this struct to these functions, which are not yet | |
| * stubbed: | |
| - | * gss_inquire_context() |
| * gss_unwrap() | |
| * gss_wrap() | |
| */ | |
| @@ -93,6 +92,12 @@ struct stub_gss_ctx_id_t_desc { | |
| char creds[250]; | |
| }; | |
| + | /* Stub credential: tracks which mechanisms are allowed */ |
| + | struct stub_gss_cred_id_t_desc { |
| + | int allow_krb5; |
| + | int allow_ntlm; |
| + | }; |
| + | |
| static OM_uint32 stub_gss_init_sec_context( | |
| OM_uint32 *min, | |
| gss_cred_id_t initiator_cred_handle, | |
| @@ -116,7 +121,6 @@ static OM_uint32 stub_gss_init_sec_context( | |
| char *token = NULL; | |
| const char *creds = NULL; | |
| - | (void)initiator_cred_handle; |
| (void)mech_type; | |
| (void)time_req; | |
| (void)input_chan_bindings; | |
| @@ -214,6 +218,16 @@ static OM_uint32 stub_gss_init_sec_context( | |
| if(strstr(creds, "NTLM")) | |
| ctx->have_ntlm = 1; | |
| + | /* If a credential restricts allowed mechs, honour it */ |
| + | if(initiator_cred_handle != GSS_C_NO_CREDENTIAL) { |
| + | struct stub_gss_cred_id_t_desc *cred = |
| + | (struct stub_gss_cred_id_t_desc *)initiator_cred_handle; |
| + | if(!cred->allow_krb5) |
| + | ctx->have_krb5 = 0; |
| + | if(!cred->allow_ntlm) |
| + | ctx->have_ntlm = 0; |
| + | } |
| + | |
| if(ctx->have_krb5) | |
| ctx->sent = STUB_GSS_KRB5; | |
| else if(ctx->have_ntlm) | |
| @@ -307,6 +321,174 @@ static OM_uint32 stub_gss_delete_sec_context( | |
| return GSS_S_COMPLETE; | |
| } | |
| + | |
| + | /* NTLMSSP OID: 1.3.6.1.4.1.311.2.2.10 */ |
| + | static gss_OID_desc stub_ntlmssp_oid = { |
| + | 10, CURL_UNCONST("\x2b\x06\x01\x04\x01\x82\x37\x02\x02\x0a") |
| + | }; |
| + | |
| + | static OM_uint32 stub_gss_inquire_context( |
| + | OM_uint32 *min, |
| + | struct stub_gss_ctx_id_t_desc *context, |
| + | gss_name_t *src_name, |
| + | gss_name_t *targ_name, |
| + | OM_uint32 *lifetime_rec, |
| + | gss_OID *mech_type, |
| + | OM_uint32 *ctx_flags, |
| + | int *locally_initiated, |
| + | int *open_context) |
| + | { |
| + | (void)src_name; |
| + | (void)targ_name; |
| + | (void)lifetime_rec; |
| + | (void)ctx_flags; |
| + | (void)locally_initiated; |
| + | (void)open_context; |
| + | |
| + | if(!min) |
| + | return GSS_S_FAILURE; |
| + | |
| + | if(!context) { |
| + | *min = STUB_GSS_INVALID_CTX; |
| + | return GSS_S_FAILURE; |
| + | } |
| + | |
| + | *min = 0; |
| + | if(mech_type) { |
| + | switch(context->sent) { |
| + | case STUB_GSS_NTLM1: |
| + | case STUB_GSS_NTLM3: |
| + | *mech_type = &stub_ntlmssp_oid; |
| + | break; |
| + | default: |
| + | *mech_type = (gss_OID)&Curl_krb5_mech_oid; |
| + | break; |
| + | } |
| + | } |
| + | |
| + | return GSS_S_COMPLETE; |
| + | } |
| + | static OM_uint32 stub_gss_acquire_cred( |
| + | OM_uint32 *min, |
| + | gss_name_t desired_name, |
| + | OM_uint32 time_req, |
| + | gss_OID_set desired_mechs, |
| + | gss_cred_usage_t cred_usage, |
| + | gss_cred_id_t *output_cred_handle, |
| + | gss_OID_set *actual_mechs, |
| + | OM_uint32 *time_rec) |
| + | { |
| + | (void)desired_name; |
| + | (void)time_req; |
| + | (void)desired_mechs; |
| + | (void)cred_usage; |
| + | (void)actual_mechs; |
| + | (void)time_rec; |
| + | |
| + | if(!min) |
| + | return GSS_S_FAILURE; |
| + | |
| + | *min = 0; |
| + | /* Allocate a stub credential that initially allows all mechanisms */ |
| + | if(output_cred_handle) { |
| + | struct stub_gss_cred_id_t_desc *cred = |
| + | curlx_calloc(1, sizeof(*cred)); |
| + | if(!cred) { |
| + | *min = STUB_GSS_NO_MEMORY; |
| + | return GSS_S_FAILURE; |
| + | } |
| + | cred->allow_krb5 = 1; |
| + | cred->allow_ntlm = 1; |
| + | *output_cred_handle = (gss_cred_id_t)cred; |
| + | } |
| + | return GSS_S_COMPLETE; |
| + | } |
| + | |
| + | static OM_uint32 stub_gss_indicate_mechs( |
| + | OM_uint32 *min, |
| + | gss_OID_set *mech_set) |
| + | { |
| + | const char *creds; |
| + | OM_uint32 major; |
| + | |
| + | if(!min) |
| + | return GSS_S_FAILURE; |
| + | |
| + | *min = 0; |
| + | creds = getenv("CURL_STUB_GSS_CREDS"); |
| + | if(!creds) { |
| + | *min = STUB_GSS_INVALID_CREDS; |
| + | return GSS_S_FAILURE; |
| + | } |
| + | |
| + | major = gss_create_empty_oid_set(min, mech_set); |
| + | if(GSS_ERROR(major)) |
| + | return major; |
| + | |
| + | /* Always include Kerberos */ |
| + | gss_add_oid_set_member(min, (gss_OID)&Curl_krb5_mech_oid, mech_set); |
| + | |
| + | /* Include NTLM if the stub creds contain NTLM */ |
| + | if(strstr(creds, "NTLM")) |
| + | gss_add_oid_set_member(min, &stub_ntlmssp_oid, mech_set); |
| + | |
| + | return GSS_S_COMPLETE; |
| + | } |
| + | |
| + | #ifdef HAVE_GSS_SET_NEG_MECHS |
| + | static OM_uint32 stub_gss_set_neg_mechs( |
| + | OM_uint32 *min, |
| + | gss_cred_id_t cred_handle, |
| + | const gss_OID_set mech_set) |
| + | { |
| + | struct stub_gss_cred_id_t_desc *cred; |
| + | size_t i; |
| + | int found_krb5 = 0; |
| + | int found_ntlm = 0; |
| + | |
| + | if(!min) |
| + | return GSS_S_FAILURE; |
| + | |
| + | *min = 0; |
| + | if(cred_handle == GSS_C_NO_CREDENTIAL) |
| + | return GSS_S_FAILURE; |
| + | |
| + | cred = (struct stub_gss_cred_id_t_desc *)cred_handle; |
| + | |
| + | /* Determine which mechs are in the allowed set */ |
| + | if(mech_set) { |
| + | for(i = 0; i < mech_set->count; i++) { |
| + | gss_OID oid = &mech_set->elements[i]; |
| + | if(oid->length == Curl_krb5_mech_oid.length && |
| + | !memcmp(oid->elements, Curl_krb5_mech_oid.elements, oid->length)) |
| + | found_krb5 = 1; |
| + | if(oid->length == stub_ntlmssp_oid.length && |
| + | !memcmp(oid->elements, stub_ntlmssp_oid.elements, oid->length)) |
| + | found_ntlm = 1; |
| + | } |
| + | } |
| + | |
| + | cred->allow_krb5 = found_krb5; |
| + | cred->allow_ntlm = found_ntlm; |
| + | return GSS_S_COMPLETE; |
| + | } |
| + | #endif /* HAVE_GSS_SET_NEG_MECHS */ |
| + | |
| + | static OM_uint32 stub_gss_release_cred( |
| + | OM_uint32 *min, |
| + | gss_cred_id_t *cred_handle) |
| + | { |
| + | if(!min) |
| + | return GSS_S_FAILURE; |
| + | |
| + | *min = 0; |
| + | if(cred_handle && *cred_handle != GSS_C_NO_CREDENTIAL) { |
| + | curlx_free(*cred_handle); |
| + | *cred_handle = GSS_C_NO_CREDENTIAL; |
| + | } |
| + | return GSS_S_COMPLETE; |
| + | } |
| + | |
| #endif /* CURL_GSS_STUB */ | |
| OM_uint32 Curl_gss_init_sec_context(struct Curl_easy *data, | |
| @@ -318,7 +500,8 @@ OM_uint32 Curl_gss_init_sec_context(struct Curl_easy *data, | |
| gss_buffer_t input_token, | |
| gss_buffer_t output_token, | |
| const bool mutual_auth, | |
| - | OM_uint32 *ret_flags) |
| + | OM_uint32 *ret_flags, |
| + | gss_cred_id_t cred_handle) |
| { | |
| OM_uint32 req_flags = GSS_C_REPLAY_FLAG; | |
| @@ -340,7 +523,7 @@ OM_uint32 Curl_gss_init_sec_context(struct Curl_easy *data, | |
| #ifdef CURL_GSS_STUB | |
| if(getenv("CURL_STUB_GSS_CREDS")) | |
| return stub_gss_init_sec_context(minor_status, | |
| - | GSS_C_NO_CREDENTIAL, /* cred_handle */ |
| + | cred_handle, |
| (struct stub_gss_ctx_id_t_desc **)context, | |
| target_name, | |
| mech_type, | |
| @@ -355,7 +538,7 @@ OM_uint32 Curl_gss_init_sec_context(struct Curl_easy *data, | |
| #endif /* CURL_GSS_STUB */ | |
| return gss_init_sec_context(minor_status, | |
| - | GSS_C_NO_CREDENTIAL, /* cred_handle */ |
| + | cred_handle, |
| context, | |
| target_name, | |
| mech_type, | |
| @@ -383,6 +566,80 @@ OM_uint32 Curl_gss_delete_sec_context(OM_uint32 *min, | |
| return gss_delete_sec_context(min, context, output_token); | |
| } | |
| + | OM_uint32 Curl_gss_inquire_context(OM_uint32 *minor_status, |
| + | gss_ctx_id_t context, |
| + | gss_OID *mech_type) |
| + | { |
| + | #ifdef CURL_GSS_STUB |
| + | if(getenv("CURL_STUB_GSS_CREDS")) |
| + | return stub_gss_inquire_context(minor_status, |
| + | (struct stub_gss_ctx_id_t_desc *)context, |
| + | NULL, NULL, NULL, mech_type, |
| + | NULL, NULL, NULL); |
| + | #endif /* CURL_GSS_STUB */ |
| + | |
| + | return gss_inquire_context(minor_status, context, |
| + | NULL, NULL, NULL, mech_type, |
| + | NULL, NULL, NULL); |
| + | } |
| + | |
| + | OM_uint32 Curl_gss_acquire_cred(OM_uint32 *minor_status, |
| + | gss_name_t desired_name, |
| + | OM_uint32 time_req, |
| + | gss_OID_set desired_mechs, |
| + | gss_cred_usage_t cred_usage, |
| + | gss_cred_id_t *output_cred_handle, |
| + | gss_OID_set *actual_mechs, |
| + | OM_uint32 *time_rec) |
| + | { |
| + | #ifdef CURL_GSS_STUB |
| + | if(getenv("CURL_STUB_GSS_CREDS")) |
| + | return stub_gss_acquire_cred(minor_status, desired_name, time_req, |
| + | desired_mechs, cred_usage, |
| + | output_cred_handle, actual_mechs, time_rec); |
| + | #endif /* CURL_GSS_STUB */ |
| + | |
| + | return gss_acquire_cred(minor_status, desired_name, time_req, |
| + | desired_mechs, cred_usage, |
| + | output_cred_handle, actual_mechs, time_rec); |
| + | } |
| + | |
| + | OM_uint32 Curl_gss_indicate_mechs(OM_uint32 *minor_status, |
| + | gss_OID_set *mech_set) |
| + | { |
| + | #ifdef CURL_GSS_STUB |
| + | if(getenv("CURL_STUB_GSS_CREDS")) |
| + | return stub_gss_indicate_mechs(minor_status, mech_set); |
| + | #endif /* CURL_GSS_STUB */ |
| + | |
| + | return gss_indicate_mechs(minor_status, mech_set); |
| + | } |
| + | |
| + | #ifdef HAVE_GSS_SET_NEG_MECHS |
| + | OM_uint32 Curl_gss_set_neg_mechs(OM_uint32 *minor_status, |
| + | gss_cred_id_t cred_handle, |
| + | const gss_OID_set mech_set) |
| + | { |
| + | #ifdef CURL_GSS_STUB |
| + | if(getenv("CURL_STUB_GSS_CREDS")) |
| + | return stub_gss_set_neg_mechs(minor_status, cred_handle, mech_set); |
| + | #endif /* CURL_GSS_STUB */ |
| + | |
| + | return gss_set_neg_mechs(minor_status, cred_handle, mech_set); |
| + | } |
| + | #endif /* HAVE_GSS_SET_NEG_MECHS */ |
| + | |
| + | OM_uint32 Curl_gss_release_cred(OM_uint32 *minor_status, |
| + | gss_cred_id_t *cred_handle) |
| + | { |
| + | #ifdef CURL_GSS_STUB |
| + | if(getenv("CURL_STUB_GSS_CREDS")) |
| + | return stub_gss_release_cred(minor_status, cred_handle); |
| + | #endif /* CURL_GSS_STUB */ |
| + | |
| + | return gss_release_cred(minor_status, cred_handle); |
| + | } |
| + | |
| #ifdef CURLVERBOSE | |
| #define GSS_LOG_BUFFER_LEN 1024 | |
| static size_t display_gss_error(OM_uint32 status, int type, | |
| @@ -41,12 +41,38 @@ OM_uint32 Curl_gss_init_sec_context(struct Curl_easy *data, | |
| gss_buffer_t input_token, | |
| gss_buffer_t output_token, | |
| const bool mutual_auth, | |
| - | OM_uint32 *ret_flags); |
| + | OM_uint32 *ret_flags, |
| + | gss_cred_id_t cred_handle); |
| OM_uint32 Curl_gss_delete_sec_context(OM_uint32 *min, | |
| gss_ctx_id_t *context, | |
| gss_buffer_t output_token); | |
| + | OM_uint32 Curl_gss_inquire_context(OM_uint32 *minor_status, |
| + | gss_ctx_id_t context, |
| + | gss_OID *mech_type); |
| + | |
| + | OM_uint32 Curl_gss_acquire_cred(OM_uint32 *minor_status, |
| + | gss_name_t desired_name, |
| + | OM_uint32 time_req, |
| + | gss_OID_set desired_mechs, |
| + | gss_cred_usage_t cred_usage, |
| + | gss_cred_id_t *output_cred_handle, |
| + | gss_OID_set *actual_mechs, |
| + | OM_uint32 *time_rec); |
| + | |
| + | OM_uint32 Curl_gss_indicate_mechs(OM_uint32 *minor_status, |
| + | gss_OID_set *mech_set); |
| + | |
| + | #ifdef HAVE_GSS_SET_NEG_MECHS |
| + | OM_uint32 Curl_gss_set_neg_mechs(OM_uint32 *minor_status, |
| + | gss_cred_id_t cred_handle, |
| + | const gss_OID_set mech_set); |
| + | #endif |
| + | |
| + | OM_uint32 Curl_gss_release_cred(OM_uint32 *minor_status, |
| + | gss_cred_id_t *cred_handle); |
| + | |
| #ifdef CURLVERBOSE | |
| /* Helper to log a GSS-API error status */ | |
| void Curl_gss_log_error(struct Curl_easy *data, const char *prefix, | |
| @@ -93,7 +93,7 @@ void Curl_sspi_global_cleanup(void) | |
| * Returns CURLE_OK on success. | |
| */ | |
| CURLcode Curl_create_sspi_identity(const char *userp, const char *passwdp, | |
| - | SEC_WINNT_AUTH_IDENTITY *identity) |
| + | SEC_WINNT_AUTH_IDENTITY_EX *identity) |
| { | |
| xcharp_u useranddomain; | |
| xcharp_u user, dup_user; | |
| @@ -105,6 +105,8 @@ CURLcode Curl_create_sspi_identity(const char *userp, const char *passwdp, | |
| /* Initialize the identity */ | |
| memset(identity, 0, sizeof(*identity)); | |
| + | identity->Version = SEC_WINNT_AUTH_IDENTITY_VERSION; |
| + | identity->Length = sizeof(*identity); |
| useranddomain.tchar_ptr = curlx_convert_UTF8_to_tchar(userp); | |
| if(!useranddomain.tchar_ptr) | |
| @@ -195,7 +197,7 @@ CURLcode Curl_create_sspi_identity(const char *userp, const char *passwdp, | |
| * | |
| * identity [in/out] - The identity structure. | |
| */ | |
| - | void Curl_sspi_free_identity(SEC_WINNT_AUTH_IDENTITY *identity) |
| + | void Curl_sspi_free_identity(SEC_WINNT_AUTH_IDENTITY_EX *identity) |
| { | |
| if(identity) { | |
| curlx_safefree(identity->User); | |
| @@ -34,14 +34,14 @@ void Curl_sspi_global_cleanup(void); | |
| /* This is used to populate the domain in an SSPI identity structure */ | |
| CURLcode Curl_override_sspi_http_realm(const char *chlg, | |
| - | SEC_WINNT_AUTH_IDENTITY *identity); |
| + | SEC_WINNT_AUTH_IDENTITY_EX *identity); |
| /* This is used to generate an SSPI identity structure */ | |
| CURLcode Curl_create_sspi_identity(const char *userp, const char *passwdp, | |
| - | SEC_WINNT_AUTH_IDENTITY *identity); |
| + | SEC_WINNT_AUTH_IDENTITY_EX *identity); |
| /* This is used to free an SSPI identity structure */ | |
| - | void Curl_sspi_free_identity(SEC_WINNT_AUTH_IDENTITY *identity); |
| + | void Curl_sspi_free_identity(SEC_WINNT_AUTH_IDENTITY_EX *identity); |
| /* Forward-declaration of global variables defined in curl_sspi.c */ | |
| extern PSecurityFunctionTable Curl_pSecFn; | |
| @@ -157,7 +157,7 @@ static ULONG ldap_win_bind_auth(LDAP *server, const char *user, | |
| const char *passwd, unsigned long authflags) | |
| { | |
| ULONG method = 0; | |
| - | SEC_WINNT_AUTH_IDENTITY cred; |
| + | SEC_WINNT_AUTH_IDENTITY_EX cred; |
| ULONG rc = LDAP_AUTH_METHOD_NOT_SUPPORTED; | |
| memset(&cred, 0, sizeof(cred)); | |
| @@ -176,7 +176,8 @@ static CURLcode socks5_gss_auth_loop(struct Curl_cfilter *cf, | |
| gss_token, | |
| &gss_send_token, | |
| TRUE, | |
| - | gss_ret_flags); |
| + | gss_ret_flags, |
| + | GSS_C_NO_CREDENTIAL); |
| if(gss_token != GSS_C_NO_BUFFER) { | |
| curlx_safefree(gss_recv_token.value); | |
| @@ -95,8 +95,8 @@ CURLcode Curl_auth_create_digest_md5_message(struct Curl_easy *data, | |
| CredHandle credentials; | |
| CtxtHandle context; | |
| PSecPkgInfo SecurityPackage; | |
| - | SEC_WINNT_AUTH_IDENTITY identity; |
| - | SEC_WINNT_AUTH_IDENTITY *p_identity; |
| + | SEC_WINNT_AUTH_IDENTITY_EX identity; |
| + | SEC_WINNT_AUTH_IDENTITY_EX *p_identity; |
| SecBuffer chlg_buf; | |
| SecBuffer resp_buf; | |
| SecBufferDesc chlg_desc; | |
| @@ -243,7 +243,7 @@ CURLcode Curl_auth_create_digest_md5_message(struct Curl_easy *data, | |
| * Returns CURLE_OK on success. | |
| */ | |
| CURLcode Curl_override_sspi_http_realm(const char *chlg, | |
| - | SEC_WINNT_AUTH_IDENTITY *identity) |
| + | SEC_WINNT_AUTH_IDENTITY_EX *identity) |
| { | |
| xcharp_u domain, dup_domain; | |
| @@ -465,8 +465,8 @@ CURLcode Curl_auth_create_digest_http_message(struct Curl_easy *data, | |
| if(!digest->http_context) { | |
| CredHandle credentials; | |
| - | SEC_WINNT_AUTH_IDENTITY identity; |
| - | SEC_WINNT_AUTH_IDENTITY *p_identity; |
| + | SEC_WINNT_AUTH_IDENTITY_EX identity; |
| + | SEC_WINNT_AUTH_IDENTITY_EX *p_identity; |
| SecBuffer resp_buf; | |
| SecBufferDesc resp_desc; | |
| unsigned long attrs; | |
| @@ -136,7 +136,8 @@ CURLcode Curl_auth_create_gssapi_user_message(struct Curl_easy *data, | |
| &input_token, | |
| &output_token, | |
| mutual_auth, | |
| - | NULL); |
| + | NULL, |
| + | GSS_C_NO_CREDENTIAL); |
| if(GSS_ERROR(major_status)) { | |
| if(output_token.value) | |
| @@ -158,6 +158,57 @@ CURLcode Curl_auth_decode_spnego_message(struct Curl_easy *data, | |
| } | |
| #endif | |
| + | #ifdef HAVE_GSS_SET_NEG_MECHS |
| + | /* Acquire explicit credentials and restrict SPNEGO sub-mechanisms to |
| + | * exclude NTLM. We enumerate all available mechanisms and filter out |
| + | * the NTLMSSP OID, matching SSPI's "!ntlm". */ |
| + | if(nego->cred == GSS_C_NO_CREDENTIAL) { |
| + | /* OID 1.3.6.1.4.1.311.2.2.10 (NTLMSSP) */ |
| + | static const gss_OID_desc ntlmssp_oid = { |
| + | 10, CURL_UNCONST("\x2b\x06\x01\x04\x01\x82\x37\x02\x02\x0a") |
| + | }; |
| + | gss_OID_set available_mechs = GSS_C_NO_OID_SET; |
| + | gss_OID_set filtered_mechs = GSS_C_NO_OID_SET; |
| + | |
| + | /* Acquire default credentials for SPNEGO */ |
| + | major_status = Curl_gss_acquire_cred(&minor_status, GSS_C_NO_NAME, |
| + | GSS_C_INDEFINITE, GSS_C_NO_OID_SET, |
| + | GSS_C_INITIATE, &nego->cred, NULL, NULL); |
| + | if(GSS_ERROR(major_status)) { |
| + | Curl_gss_log_error(data, "gss_acquire_cred() failed: ", |
| + | major_status, minor_status); |
| + | curlx_safefree(input_token.value); |
| + | return CURLE_AUTH_ERROR; |
| + | } |
| + | |
| + | /* Get all available mechanisms */ |
| + | major_status = Curl_gss_indicate_mechs(&minor_status, &available_mechs); |
| + | if(!GSS_ERROR(major_status)) { |
| + | /* Build a set excluding NTLMSSP */ |
| + | major_status = gss_create_empty_oid_set(&minor_status, &filtered_mechs); |
| + | if(!GSS_ERROR(major_status)) { |
| + | size_t i; |
| + | for(i = 0; i < available_mechs->count; i++) { |
| + | gss_OID oid = &available_mechs->elements[i]; |
| + | if(oid->length != ntlmssp_oid.length || |
| + | memcmp(oid->elements, ntlmssp_oid.elements, oid->length)) { |
| + | gss_add_oid_set_member(&minor_status, oid, &filtered_mechs); |
| + | } |
| + | } |
| + | /* Restrict SPNEGO to only use non-NTLM mechanisms */ |
| + | major_status = Curl_gss_set_neg_mechs(&minor_status, nego->cred, |
| + | filtered_mechs); |
| + | if(GSS_ERROR(major_status)) { |
| + | Curl_gss_log_error(data, "gss_set_neg_mechs() failed: ", |
| + | major_status, minor_status); |
| + | } |
| + | gss_release_oid_set(&minor_status, &filtered_mechs); |
| + | } |
| + | gss_release_oid_set(&minor_status, &available_mechs); |
| + | } |
| + | } |
| + | #endif /* HAVE_GSS_SET_NEG_MECHS */ |
| + | |
| /* Generate our challenge-response message */ | |
| major_status = Curl_gss_init_sec_context(data, | |
| &minor_status, | |
| @@ -168,7 +219,8 @@ CURLcode Curl_auth_decode_spnego_message(struct Curl_easy *data, | |
| &input_token, | |
| &output_token, | |
| TRUE, | |
| - | NULL); |
| + | NULL, |
| + | nego->cred); |
| /* Free the decoded challenge as it is not required anymore */ | |
| curlx_safefree(input_token.value); | |
| @@ -191,6 +243,29 @@ CURLcode Curl_auth_decode_spnego_message(struct Curl_easy *data, | |
| return CURLE_AUTH_ERROR; | |
| } | |
| + | /* Check if NTLM was selected and is disallowed */ |
| + | if(nego->context != GSS_C_NO_CONTEXT) { |
| + | /* OID 1.3.6.1.4.1.311.2.2.10 (NTLMSSP) */ |
| + | static const gss_OID_desc ntlmssp_oid = { |
| + | 10, CURL_UNCONST("\x2b\x06\x01\x04\x01\x82\x37\x02\x02\x0a") |
| + | }; |
| + | OM_uint32 inquire_major, inquire_minor; |
| + | gss_OID mech_type = GSS_C_NO_OID; |
| + | |
| + | inquire_major = Curl_gss_inquire_context(&inquire_minor, |
| + | nego->context, |
| + | &mech_type); |
| + | if(!GSS_ERROR(inquire_major) && mech_type && |
| + | mech_type->length == ntlmssp_oid.length && |
| + | !memcmp(mech_type->elements, ntlmssp_oid.elements, |
| + | ntlmssp_oid.length)) { |
| + | infof(data, "SPNEGO chose NTLM, but NTLM is not allowed"); |
| + | gss_release_buffer(&unused_status, &output_token); |
| + | Curl_auth_cleanup_spnego(nego); |
| + | return CURLE_AUTH_ERROR; |
| + | } |
| + | } |
| + | |
| /* Free previous token */ | |
| if(nego->output_token.length && nego->output_token.value) | |
| gss_release_buffer(&unused_status, &nego->output_token); | |
| @@ -280,6 +355,12 @@ void Curl_auth_cleanup_spnego(struct negotiatedata *nego) | |
| nego->spn = GSS_C_NO_NAME; | |
| } | |
| + | /* Free our credentials */ |
| + | if(nego->cred != GSS_C_NO_CREDENTIAL) { |
| + | Curl_gss_release_cred(&minor_status, &nego->cred); |
| + | nego->cred = GSS_C_NO_CREDENTIAL; |
| + | } |
| + | |
| /* Reset any variables */ | |
| nego->status = 0; | |
| nego->noauthpersist = FALSE; | |
| @@ -148,6 +148,32 @@ CURLcode Curl_auth_decode_spnego_message(struct Curl_easy *data, | |
| /* Use the current Windows user */ | |
| nego->p_identity = NULL; | |
| + | /* Exclude NTLM from SPNEGO negotiation via the PackageList field */ |
| + | if(!nego->p_identity) { |
| + | memset(&nego->identity, 0, sizeof(nego->identity)); |
| + | nego->identity.Version = SEC_WINNT_AUTH_IDENTITY_VERSION; |
| + | nego->identity.Length = sizeof(nego->identity); |
| + | nego->identity.Flags = |
| + | #ifdef UNICODE |
| + | SEC_WINNT_AUTH_IDENTITY_UNICODE; |
| + | #else |
| + | SEC_WINNT_AUTH_IDENTITY_ANSI; |
| + | #endif |
| + | nego->p_identity = &nego->identity; |
| + | } |
| + | |
| + | /* Use the special name "!ntlm" to prevent NTLM from being used: |
| + | * https://learn.microsoft.com/en-us/windows/win32/api/sspi/ns-sspi-sec_winnt_auth_identity_exa |
| + | */ |
| + | #ifdef UNICODE |
| + | nego->identity.PackageList = |
| + | (unsigned short *)CURL_UNCONST(TEXT("!ntlm")); |
| + | #else |
| + | nego->identity.PackageList = |
| + | (unsigned char *)CURL_UNCONST(TEXT("!ntlm")); |
| + | #endif |
| + | nego->identity.PackageListLength = 5; |
| + | |
| /* Allocate our credentials handle */ | |
| nego->credentials = curlx_calloc(1, sizeof(CredHandle)); | |
| if(!nego->credentials) | |
| @@ -169,8 +169,8 @@ struct ntlmdata { | |
| #endif | |
| CredHandle *credentials; | |
| CtxtHandle *context; | |
| - | SEC_WINNT_AUTH_IDENTITY identity; |
| - | SEC_WINNT_AUTH_IDENTITY *p_identity; |
| + | SEC_WINNT_AUTH_IDENTITY_EX identity; |
| + | SEC_WINNT_AUTH_IDENTITY_EX *p_identity; |
| size_t token_max; | |
| BYTE *output_token; | |
| BYTE *input_token; | |
| @@ -236,8 +236,8 @@ struct kerberos5data { | |
| CredHandle *credentials; | |
| CtxtHandle *context; | |
| TCHAR *spn; | |
| - | SEC_WINNT_AUTH_IDENTITY identity; |
| - | SEC_WINNT_AUTH_IDENTITY *p_identity; |
| + | SEC_WINNT_AUTH_IDENTITY_EX identity; |
| + | SEC_WINNT_AUTH_IDENTITY_EX *p_identity; |
| size_t token_max; | |
| BYTE *output_token; | |
| #else | |
| @@ -291,6 +291,7 @@ struct negotiatedata { | |
| OM_uint32 status; | |
| gss_ctx_id_t context; | |
| gss_name_t spn; | |
| + | gss_cred_id_t cred; |
| gss_buffer_desc output_token; | |
| #ifdef GSS_C_CHANNEL_BOUND_FLAG | |
| struct dynbuf channel_binding_data; | |
| @@ -303,8 +304,8 @@ struct negotiatedata { | |
| SECURITY_STATUS status; | |
| CredHandle *credentials; | |
| CtxtHandle *context; | |
| - | SEC_WINNT_AUTH_IDENTITY identity; |
| - | SEC_WINNT_AUTH_IDENTITY *p_identity; |
| + | SEC_WINNT_AUTH_IDENTITY_EX identity; |
| + | SEC_WINNT_AUTH_IDENTITY_EX *p_identity; |
| TCHAR *spn; | |
| size_t token_max; | |
| BYTE *output_token; | |
| @@ -252,7 +252,7 @@ test2056 test2057 test2058 test2059 test2060 test2061 test2062 test2063 \ | |
| test2064 test2065 test2066 test2067 test2068 test2069 test2070 test2071 \ | |
| test2072 test2073 test2074 test2075 test2076 test2077 test2078 test2079 \ | |
| test2080 test2081 test2082 test2083 test2084 test2085 test2086 test2087 \ | |
| - | test2088 test2089 test2090 test2091 test2092 test2093 \ |
| + | test2088 test2089 test2090 test2091 test2092 test2093 test2094 \ |
| test2100 test2101 test2102 test2103 test2104 test2105 test2106 test2107 \ | |
| test2108 test2109 test2110 test2113 test2114 test2115 test2116 test2117 \ | |
| \ | |
| @@ -5,45 +5,17 @@ | |
| HTTP | |
| HTTP GET | |
| HTTP Negotiate auth (stub ntlm) | |
| + | SPNEGO NTLM disallowed |
| </keywords> | |
| </info> | |
| # Server-side | |
| <reply> | |
| - | <!-- First request, expect 401 (ntlm challenge) --> |
| - | <data1> |
| - | HTTP/1.1 401 Authorization Required |
| - | Server: Microsoft-IIS/7.0 |
| - | Content-Type: text/html; charset=iso-8859-1 |
| - | WWW-Authenticate: Negotiate Qw== |
| - | Content-Length: 19 |
| + | <data nocheck="yes" crlf="headers"> |
| + | HTTP/1.1 200 OK swsclose |
| + | Content-Length: 23 |
| - | Still not yet sir! |
| - | </data1> |
| - | <!-- Second request, expect success --> |
| - | <data2> |
| - | HTTP/1.1 200 Things are fine in server land |
| - | Server: Microsoft-IIS/7.0 |
| - | Content-Type: text/html; charset=iso-8859-1 |
| - | WWW-Authenticate: Negotiate RA== |
| - | Content-Length: 15 |
| - | |
| - | Nice auth sir! |
| - | </data2> |
| - | <datacheck> |
| - | HTTP/1.1 401 Authorization Required |
| - | Server: Microsoft-IIS/7.0 |
| - | Content-Type: text/html; charset=iso-8859-1 |
| - | WWW-Authenticate: Negotiate Qw== |
| - | Content-Length: 19 |
| - | |
| - | HTTP/1.1 200 Things are fine in server land |
| - | Server: Microsoft-IIS/7.0 |
| - | Content-Type: text/html; charset=iso-8859-1 |
| - | WWW-Authenticate: Negotiate RA== |
| - | Content-Length: 15 |
| - | |
| - | Nice auth sir! |
| - | </datacheck> |
| + | This IS the real page! |
| + | </data> |
| </reply> | |
| # Client-side | |
| @@ -52,7 +24,7 @@ Nice auth sir! | |
| http | |
| </server> | |
| <name> | |
| - | HTTP Negotiate authentication (stub NTLM) |
| + | HTTP Negotiate authentication blocked for stub NTLM credentials |
| </name> | |
| <features> | |
| GSS-API | |
| @@ -68,16 +40,15 @@ CURL_STUB_GSS_CREDS="NTLM_Alice" | |
| # Verify data after the test has been "shot" | |
| <verify> | |
| + | <errorcode> |
| + | 0 |
| + | </errorcode> |
| + | # NTLM is blocked within SPNEGO, so when only NTLM credentials are |
| + | # available, negotiate auth silently fails and the request is sent |
| + | # without any Authorization header. |
| <protocol crlf="headers"> | |
| GET /%TESTNUMBER HTTP/1.1 | |
| Host: %HOSTIP:%HTTPPORT | |
| - | Authorization: Negotiate %b64["NTLM_Alice":[email protected]:2:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA]b64% |
| - | User-Agent: curl/%VERSION |
| - | Accept: */* |
| - | |
| - | GET /%TESTNUMBER HTTP/1.1 |
| - | Host: %HOSTIP:%HTTPPORT |
| - | Authorization: Negotiate %b64["NTLM_Alice":[email protected]:3:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA]b64% |
| User-Agent: curl/%VERSION | |
| Accept: */* | |
| @@ -0,0 +1,68 @@ | |
| + | <?xml version="1.0" encoding="US-ASCII"?> |
| + | <testcase> |
| + | <info> |
| + | <keywords> |
| + | HTTP |
| + | HTTP GET |
| + | HTTP Negotiate auth (stub krb5) |
| + | SPNEGO NTLM disallowed |
| + | </keywords> |
| + | </info> |
| + | |
| + | # Server-side |
| + | <reply> |
| + | <data1> |
| + | HTTP/1.1 200 Things are fine in server land |
| + | Server: Microsoft-IIS/7.0 |
| + | Content-Type: text/html; charset=iso-8859-1 |
| + | WWW-Authenticate: Negotiate RA== |
| + | Content-Length: 15 |
| + | |
| + | Nice auth sir! |
| + | </data1> |
| + | <datacheck> |
| + | HTTP/1.1 200 Things are fine in server land |
| + | Server: Microsoft-IIS/7.0 |
| + | Content-Type: text/html; charset=iso-8859-1 |
| + | WWW-Authenticate: Negotiate RA== |
| + | Content-Length: 15 |
| + | |
| + | Nice auth sir! |
| + | </datacheck> |
| + | </reply> |
| + | |
| + | # Client-side |
| + | <client> |
| + | <server> |
| + | http |
| + | </server> |
| + | <name> |
| + | SPNEGO with Kerberos still works when NTLM is blocked |
| + | </name> |
| + | <features> |
| + | GSS-API |
| + | Debug |
| + | </features> |
| + | <setenv> |
| + | CURL_STUB_GSS_CREDS="KRB5_Alice" |
| + | </setenv> |
| + | <command> |
| + | --negotiate http://%HOSTIP:%HTTPPORT/%TESTNUMBER |
| + | </command> |
| + | </client> |
| + | |
| + | # Verify data after the test has been "shot" |
| + | <verify> |
| + | <errorcode> |
| + | 0 |
| + | </errorcode> |
| + | <protocol crlf="headers"> |
| + | GET /%TESTNUMBER HTTP/1.1 |
| + | Host: %HOSTIP:%HTTPPORT |
| + | Authorization: Negotiate %b64["KRB5_Alice":[email protected]:1:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA]b64% |
| + | User-Agent: curl/%VERSION |
| + | Accept: */* |
| + | |
| + | </protocol> |
| + | </verify> |
| + | </testcase> |
| @@ -128,3 +128,14 @@ | |
| fun:inet_pton6 | |
| fun:ipv6_parse | |
| } | |
| + | |
| + | { |
| + | mit-krb5-gss_display_status-internal-leak |
| + | Memcheck:Leak |
| + | match-leak-kinds: definite |
| + | fun:realloc |
| + | ... |
| + | fun:gss_display_status |
| + | fun:display_gss_error |
| + | fun:Curl_gss_log_error |
| + | } |