113498266Sopenharmony_ci/***************************************************************************
213498266Sopenharmony_ci *                                  _   _ ____  _
313498266Sopenharmony_ci *  Project                     ___| | | |  _ \| |
413498266Sopenharmony_ci *                             / __| | | | |_) | |
513498266Sopenharmony_ci *                            | (__| |_| |  _ <| |___
613498266Sopenharmony_ci *                             \___|\___/|_| \_\_____|
713498266Sopenharmony_ci *
813498266Sopenharmony_ci * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
913498266Sopenharmony_ci *
1013498266Sopenharmony_ci * This software is licensed as described in the file COPYING, which
1113498266Sopenharmony_ci * you should have received as part of this distribution. The terms
1213498266Sopenharmony_ci * are also available at https://curl.se/docs/copyright.html.
1313498266Sopenharmony_ci *
1413498266Sopenharmony_ci * You may opt to use, copy, modify, merge, publish, distribute and/or sell
1513498266Sopenharmony_ci * copies of the Software, and permit persons to whom the Software is
1613498266Sopenharmony_ci * furnished to do so, under the terms of the COPYING file.
1713498266Sopenharmony_ci *
1813498266Sopenharmony_ci * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
1913498266Sopenharmony_ci * KIND, either express or implied.
2013498266Sopenharmony_ci *
2113498266Sopenharmony_ci * SPDX-License-Identifier: curl
2213498266Sopenharmony_ci *
2313498266Sopenharmony_ci * RFC4178 Simple and Protected GSS-API Negotiation Mechanism
2413498266Sopenharmony_ci *
2513498266Sopenharmony_ci ***************************************************************************/
2613498266Sopenharmony_ci
2713498266Sopenharmony_ci#include "curl_setup.h"
2813498266Sopenharmony_ci
2913498266Sopenharmony_ci#if defined(USE_WINDOWS_SSPI) && defined(USE_SPNEGO)
3013498266Sopenharmony_ci
3113498266Sopenharmony_ci#include <curl/curl.h>
3213498266Sopenharmony_ci
3313498266Sopenharmony_ci#include "vauth/vauth.h"
3413498266Sopenharmony_ci#include "urldata.h"
3513498266Sopenharmony_ci#include "curl_base64.h"
3613498266Sopenharmony_ci#include "warnless.h"
3713498266Sopenharmony_ci#include "curl_multibyte.h"
3813498266Sopenharmony_ci#include "sendf.h"
3913498266Sopenharmony_ci#include "strerror.h"
4013498266Sopenharmony_ci
4113498266Sopenharmony_ci/* The last #include files should be: */
4213498266Sopenharmony_ci#include "curl_memory.h"
4313498266Sopenharmony_ci#include "memdebug.h"
4413498266Sopenharmony_ci
4513498266Sopenharmony_ci/*
4613498266Sopenharmony_ci * Curl_auth_is_spnego_supported()
4713498266Sopenharmony_ci *
4813498266Sopenharmony_ci * This is used to evaluate if SPNEGO (Negotiate) is supported.
4913498266Sopenharmony_ci *
5013498266Sopenharmony_ci * Parameters: None
5113498266Sopenharmony_ci *
5213498266Sopenharmony_ci * Returns TRUE if Negotiate is supported by Windows SSPI.
5313498266Sopenharmony_ci */
5413498266Sopenharmony_cibool Curl_auth_is_spnego_supported(void)
5513498266Sopenharmony_ci{
5613498266Sopenharmony_ci  PSecPkgInfo SecurityPackage;
5713498266Sopenharmony_ci  SECURITY_STATUS status;
5813498266Sopenharmony_ci
5913498266Sopenharmony_ci  /* Query the security package for Negotiate */
6013498266Sopenharmony_ci  status = s_pSecFn->QuerySecurityPackageInfo((TCHAR *)
6113498266Sopenharmony_ci                                              TEXT(SP_NAME_NEGOTIATE),
6213498266Sopenharmony_ci                                              &SecurityPackage);
6313498266Sopenharmony_ci
6413498266Sopenharmony_ci  /* Release the package buffer as it is not required anymore */
6513498266Sopenharmony_ci  if(status == SEC_E_OK) {
6613498266Sopenharmony_ci    s_pSecFn->FreeContextBuffer(SecurityPackage);
6713498266Sopenharmony_ci  }
6813498266Sopenharmony_ci
6913498266Sopenharmony_ci
7013498266Sopenharmony_ci  return (status == SEC_E_OK ? TRUE : FALSE);
7113498266Sopenharmony_ci}
7213498266Sopenharmony_ci
7313498266Sopenharmony_ci/*
7413498266Sopenharmony_ci * Curl_auth_decode_spnego_message()
7513498266Sopenharmony_ci *
7613498266Sopenharmony_ci * This is used to decode an already encoded SPNEGO (Negotiate) challenge
7713498266Sopenharmony_ci * message.
7813498266Sopenharmony_ci *
7913498266Sopenharmony_ci * Parameters:
8013498266Sopenharmony_ci *
8113498266Sopenharmony_ci * data        [in]     - The session handle.
8213498266Sopenharmony_ci * user        [in]     - The user name in the format User or Domain\User.
8313498266Sopenharmony_ci * password    [in]     - The user's password.
8413498266Sopenharmony_ci * service     [in]     - The service type such as http, smtp, pop or imap.
8513498266Sopenharmony_ci * host        [in]     - The host name.
8613498266Sopenharmony_ci * chlg64      [in]     - The optional base64 encoded challenge message.
8713498266Sopenharmony_ci * nego        [in/out] - The Negotiate data struct being used and modified.
8813498266Sopenharmony_ci *
8913498266Sopenharmony_ci * Returns CURLE_OK on success.
9013498266Sopenharmony_ci */
9113498266Sopenharmony_ciCURLcode Curl_auth_decode_spnego_message(struct Curl_easy *data,
9213498266Sopenharmony_ci                                         const char *user,
9313498266Sopenharmony_ci                                         const char *password,
9413498266Sopenharmony_ci                                         const char *service,
9513498266Sopenharmony_ci                                         const char *host,
9613498266Sopenharmony_ci                                         const char *chlg64,
9713498266Sopenharmony_ci                                         struct negotiatedata *nego)
9813498266Sopenharmony_ci{
9913498266Sopenharmony_ci  CURLcode result = CURLE_OK;
10013498266Sopenharmony_ci  size_t chlglen = 0;
10113498266Sopenharmony_ci  unsigned char *chlg = NULL;
10213498266Sopenharmony_ci  PSecPkgInfo SecurityPackage;
10313498266Sopenharmony_ci  SecBuffer chlg_buf[2];
10413498266Sopenharmony_ci  SecBuffer resp_buf;
10513498266Sopenharmony_ci  SecBufferDesc chlg_desc;
10613498266Sopenharmony_ci  SecBufferDesc resp_desc;
10713498266Sopenharmony_ci  unsigned long attrs;
10813498266Sopenharmony_ci  TimeStamp expiry; /* For Windows 9x compatibility of SSPI calls */
10913498266Sopenharmony_ci
11013498266Sopenharmony_ci#if defined(CURL_DISABLE_VERBOSE_STRINGS)
11113498266Sopenharmony_ci  (void) data;
11213498266Sopenharmony_ci#endif
11313498266Sopenharmony_ci
11413498266Sopenharmony_ci  if(nego->context && nego->status == SEC_E_OK) {
11513498266Sopenharmony_ci    /* We finished successfully our part of authentication, but server
11613498266Sopenharmony_ci     * rejected it (since we're again here). Exit with an error since we
11713498266Sopenharmony_ci     * can't invent anything better */
11813498266Sopenharmony_ci    Curl_auth_cleanup_spnego(nego);
11913498266Sopenharmony_ci    return CURLE_LOGIN_DENIED;
12013498266Sopenharmony_ci  }
12113498266Sopenharmony_ci
12213498266Sopenharmony_ci  if(!nego->spn) {
12313498266Sopenharmony_ci    /* Generate our SPN */
12413498266Sopenharmony_ci    nego->spn = Curl_auth_build_spn(service, host, NULL);
12513498266Sopenharmony_ci    if(!nego->spn)
12613498266Sopenharmony_ci      return CURLE_OUT_OF_MEMORY;
12713498266Sopenharmony_ci  }
12813498266Sopenharmony_ci
12913498266Sopenharmony_ci  if(!nego->output_token) {
13013498266Sopenharmony_ci    /* Query the security package for Negotiate */
13113498266Sopenharmony_ci    nego->status = s_pSecFn->QuerySecurityPackageInfo((TCHAR *)
13213498266Sopenharmony_ci                                                      TEXT(SP_NAME_NEGOTIATE),
13313498266Sopenharmony_ci                                                      &SecurityPackage);
13413498266Sopenharmony_ci    if(nego->status != SEC_E_OK) {
13513498266Sopenharmony_ci      failf(data, "SSPI: couldn't get auth info");
13613498266Sopenharmony_ci      return CURLE_AUTH_ERROR;
13713498266Sopenharmony_ci    }
13813498266Sopenharmony_ci
13913498266Sopenharmony_ci    nego->token_max = SecurityPackage->cbMaxToken;
14013498266Sopenharmony_ci
14113498266Sopenharmony_ci    /* Release the package buffer as it is not required anymore */
14213498266Sopenharmony_ci    s_pSecFn->FreeContextBuffer(SecurityPackage);
14313498266Sopenharmony_ci
14413498266Sopenharmony_ci    /* Allocate our output buffer */
14513498266Sopenharmony_ci    nego->output_token = malloc(nego->token_max);
14613498266Sopenharmony_ci    if(!nego->output_token)
14713498266Sopenharmony_ci      return CURLE_OUT_OF_MEMORY;
14813498266Sopenharmony_ci }
14913498266Sopenharmony_ci
15013498266Sopenharmony_ci  if(!nego->credentials) {
15113498266Sopenharmony_ci    /* Do we have credentials to use or are we using single sign-on? */
15213498266Sopenharmony_ci    if(user && *user) {
15313498266Sopenharmony_ci      /* Populate our identity structure */
15413498266Sopenharmony_ci      result = Curl_create_sspi_identity(user, password, &nego->identity);
15513498266Sopenharmony_ci      if(result)
15613498266Sopenharmony_ci        return result;
15713498266Sopenharmony_ci
15813498266Sopenharmony_ci      /* Allow proper cleanup of the identity structure */
15913498266Sopenharmony_ci      nego->p_identity = &nego->identity;
16013498266Sopenharmony_ci    }
16113498266Sopenharmony_ci    else
16213498266Sopenharmony_ci      /* Use the current Windows user */
16313498266Sopenharmony_ci      nego->p_identity = NULL;
16413498266Sopenharmony_ci
16513498266Sopenharmony_ci    /* Allocate our credentials handle */
16613498266Sopenharmony_ci    nego->credentials = calloc(1, sizeof(CredHandle));
16713498266Sopenharmony_ci    if(!nego->credentials)
16813498266Sopenharmony_ci      return CURLE_OUT_OF_MEMORY;
16913498266Sopenharmony_ci
17013498266Sopenharmony_ci    /* Acquire our credentials handle */
17113498266Sopenharmony_ci    nego->status =
17213498266Sopenharmony_ci      s_pSecFn->AcquireCredentialsHandle(NULL,
17313498266Sopenharmony_ci                                         (TCHAR *)TEXT(SP_NAME_NEGOTIATE),
17413498266Sopenharmony_ci                                         SECPKG_CRED_OUTBOUND, NULL,
17513498266Sopenharmony_ci                                         nego->p_identity, NULL, NULL,
17613498266Sopenharmony_ci                                         nego->credentials, &expiry);
17713498266Sopenharmony_ci    if(nego->status != SEC_E_OK)
17813498266Sopenharmony_ci      return CURLE_AUTH_ERROR;
17913498266Sopenharmony_ci
18013498266Sopenharmony_ci    /* Allocate our new context handle */
18113498266Sopenharmony_ci    nego->context = calloc(1, sizeof(CtxtHandle));
18213498266Sopenharmony_ci    if(!nego->context)
18313498266Sopenharmony_ci      return CURLE_OUT_OF_MEMORY;
18413498266Sopenharmony_ci  }
18513498266Sopenharmony_ci
18613498266Sopenharmony_ci  if(chlg64 && *chlg64) {
18713498266Sopenharmony_ci    /* Decode the base-64 encoded challenge message */
18813498266Sopenharmony_ci    if(*chlg64 != '=') {
18913498266Sopenharmony_ci      result = Curl_base64_decode(chlg64, &chlg, &chlglen);
19013498266Sopenharmony_ci      if(result)
19113498266Sopenharmony_ci        return result;
19213498266Sopenharmony_ci    }
19313498266Sopenharmony_ci
19413498266Sopenharmony_ci    /* Ensure we have a valid challenge message */
19513498266Sopenharmony_ci    if(!chlg) {
19613498266Sopenharmony_ci      infof(data, "SPNEGO handshake failure (empty challenge message)");
19713498266Sopenharmony_ci      return CURLE_BAD_CONTENT_ENCODING;
19813498266Sopenharmony_ci    }
19913498266Sopenharmony_ci
20013498266Sopenharmony_ci    /* Setup the challenge "input" security buffer */
20113498266Sopenharmony_ci    chlg_desc.ulVersion    = SECBUFFER_VERSION;
20213498266Sopenharmony_ci    chlg_desc.cBuffers     = 1;
20313498266Sopenharmony_ci    chlg_desc.pBuffers     = &chlg_buf[0];
20413498266Sopenharmony_ci    chlg_buf[0].BufferType = SECBUFFER_TOKEN;
20513498266Sopenharmony_ci    chlg_buf[0].pvBuffer   = chlg;
20613498266Sopenharmony_ci    chlg_buf[0].cbBuffer   = curlx_uztoul(chlglen);
20713498266Sopenharmony_ci
20813498266Sopenharmony_ci#ifdef SECPKG_ATTR_ENDPOINT_BINDINGS
20913498266Sopenharmony_ci    /* ssl context comes from Schannel.
21013498266Sopenharmony_ci    * When extended protection is used in IIS server,
21113498266Sopenharmony_ci    * we have to pass a second SecBuffer to the SecBufferDesc
21213498266Sopenharmony_ci    * otherwise IIS will not pass the authentication (401 response).
21313498266Sopenharmony_ci    * Minimum supported version is Windows 7.
21413498266Sopenharmony_ci    * https://docs.microsoft.com/en-us/security-updates
21513498266Sopenharmony_ci    * /SecurityAdvisories/2009/973811
21613498266Sopenharmony_ci    */
21713498266Sopenharmony_ci    if(nego->sslContext) {
21813498266Sopenharmony_ci      SEC_CHANNEL_BINDINGS channelBindings;
21913498266Sopenharmony_ci      SecPkgContext_Bindings pkgBindings;
22013498266Sopenharmony_ci      pkgBindings.Bindings = &channelBindings;
22113498266Sopenharmony_ci      nego->status = s_pSecFn->QueryContextAttributes(
22213498266Sopenharmony_ci          nego->sslContext,
22313498266Sopenharmony_ci          SECPKG_ATTR_ENDPOINT_BINDINGS,
22413498266Sopenharmony_ci          &pkgBindings
22513498266Sopenharmony_ci      );
22613498266Sopenharmony_ci      if(nego->status == SEC_E_OK) {
22713498266Sopenharmony_ci        chlg_desc.cBuffers++;
22813498266Sopenharmony_ci        chlg_buf[1].BufferType = SECBUFFER_CHANNEL_BINDINGS;
22913498266Sopenharmony_ci        chlg_buf[1].cbBuffer   = pkgBindings.BindingsLength;
23013498266Sopenharmony_ci        chlg_buf[1].pvBuffer   = pkgBindings.Bindings;
23113498266Sopenharmony_ci      }
23213498266Sopenharmony_ci    }
23313498266Sopenharmony_ci#endif
23413498266Sopenharmony_ci  }
23513498266Sopenharmony_ci
23613498266Sopenharmony_ci  /* Setup the response "output" security buffer */
23713498266Sopenharmony_ci  resp_desc.ulVersion = SECBUFFER_VERSION;
23813498266Sopenharmony_ci  resp_desc.cBuffers  = 1;
23913498266Sopenharmony_ci  resp_desc.pBuffers  = &resp_buf;
24013498266Sopenharmony_ci  resp_buf.BufferType = SECBUFFER_TOKEN;
24113498266Sopenharmony_ci  resp_buf.pvBuffer   = nego->output_token;
24213498266Sopenharmony_ci  resp_buf.cbBuffer   = curlx_uztoul(nego->token_max);
24313498266Sopenharmony_ci
24413498266Sopenharmony_ci  /* Generate our challenge-response message */
24513498266Sopenharmony_ci  nego->status = s_pSecFn->InitializeSecurityContext(nego->credentials,
24613498266Sopenharmony_ci                                                     chlg ? nego->context :
24713498266Sopenharmony_ci                                                            NULL,
24813498266Sopenharmony_ci                                                     nego->spn,
24913498266Sopenharmony_ci                                                     ISC_REQ_CONFIDENTIALITY,
25013498266Sopenharmony_ci                                                     0, SECURITY_NATIVE_DREP,
25113498266Sopenharmony_ci                                                     chlg ? &chlg_desc : NULL,
25213498266Sopenharmony_ci                                                     0, nego->context,
25313498266Sopenharmony_ci                                                     &resp_desc, &attrs,
25413498266Sopenharmony_ci                                                     &expiry);
25513498266Sopenharmony_ci
25613498266Sopenharmony_ci  /* Free the decoded challenge as it is not required anymore */
25713498266Sopenharmony_ci  free(chlg);
25813498266Sopenharmony_ci
25913498266Sopenharmony_ci  if(GSS_ERROR(nego->status)) {
26013498266Sopenharmony_ci    char buffer[STRERROR_LEN];
26113498266Sopenharmony_ci    failf(data, "InitializeSecurityContext failed: %s",
26213498266Sopenharmony_ci          Curl_sspi_strerror(nego->status, buffer, sizeof(buffer)));
26313498266Sopenharmony_ci
26413498266Sopenharmony_ci    if(nego->status == (DWORD)SEC_E_INSUFFICIENT_MEMORY)
26513498266Sopenharmony_ci      return CURLE_OUT_OF_MEMORY;
26613498266Sopenharmony_ci
26713498266Sopenharmony_ci    return CURLE_AUTH_ERROR;
26813498266Sopenharmony_ci  }
26913498266Sopenharmony_ci
27013498266Sopenharmony_ci  if(nego->status == SEC_I_COMPLETE_NEEDED ||
27113498266Sopenharmony_ci     nego->status == SEC_I_COMPLETE_AND_CONTINUE) {
27213498266Sopenharmony_ci    nego->status = s_pSecFn->CompleteAuthToken(nego->context, &resp_desc);
27313498266Sopenharmony_ci    if(GSS_ERROR(nego->status)) {
27413498266Sopenharmony_ci      char buffer[STRERROR_LEN];
27513498266Sopenharmony_ci      failf(data, "CompleteAuthToken failed: %s",
27613498266Sopenharmony_ci            Curl_sspi_strerror(nego->status, buffer, sizeof(buffer)));
27713498266Sopenharmony_ci
27813498266Sopenharmony_ci      if(nego->status == (DWORD)SEC_E_INSUFFICIENT_MEMORY)
27913498266Sopenharmony_ci        return CURLE_OUT_OF_MEMORY;
28013498266Sopenharmony_ci
28113498266Sopenharmony_ci      return CURLE_AUTH_ERROR;
28213498266Sopenharmony_ci    }
28313498266Sopenharmony_ci  }
28413498266Sopenharmony_ci
28513498266Sopenharmony_ci  nego->output_token_length = resp_buf.cbBuffer;
28613498266Sopenharmony_ci
28713498266Sopenharmony_ci  return result;
28813498266Sopenharmony_ci}
28913498266Sopenharmony_ci
29013498266Sopenharmony_ci/*
29113498266Sopenharmony_ci * Curl_auth_create_spnego_message()
29213498266Sopenharmony_ci *
29313498266Sopenharmony_ci * This is used to generate an already encoded SPNEGO (Negotiate) response
29413498266Sopenharmony_ci * message ready for sending to the recipient.
29513498266Sopenharmony_ci *
29613498266Sopenharmony_ci * Parameters:
29713498266Sopenharmony_ci *
29813498266Sopenharmony_ci * data        [in]     - The session handle.
29913498266Sopenharmony_ci * nego        [in/out] - The Negotiate data struct being used and modified.
30013498266Sopenharmony_ci * outptr      [in/out] - The address where a pointer to newly allocated memory
30113498266Sopenharmony_ci *                        holding the result will be stored upon completion.
30213498266Sopenharmony_ci * outlen      [out]    - The length of the output message.
30313498266Sopenharmony_ci *
30413498266Sopenharmony_ci * Returns CURLE_OK on success.
30513498266Sopenharmony_ci */
30613498266Sopenharmony_ciCURLcode Curl_auth_create_spnego_message(struct negotiatedata *nego,
30713498266Sopenharmony_ci                                         char **outptr, size_t *outlen)
30813498266Sopenharmony_ci{
30913498266Sopenharmony_ci  /* Base64 encode the already generated response */
31013498266Sopenharmony_ci  CURLcode result = Curl_base64_encode((const char *) nego->output_token,
31113498266Sopenharmony_ci                                       nego->output_token_length, outptr,
31213498266Sopenharmony_ci                                       outlen);
31313498266Sopenharmony_ci  if(!result && (!*outptr || !*outlen)) {
31413498266Sopenharmony_ci    free(*outptr);
31513498266Sopenharmony_ci    result = CURLE_REMOTE_ACCESS_DENIED;
31613498266Sopenharmony_ci  }
31713498266Sopenharmony_ci
31813498266Sopenharmony_ci  return result;
31913498266Sopenharmony_ci}
32013498266Sopenharmony_ci
32113498266Sopenharmony_ci/*
32213498266Sopenharmony_ci * Curl_auth_cleanup_spnego()
32313498266Sopenharmony_ci *
32413498266Sopenharmony_ci * This is used to clean up the SPNEGO (Negotiate) specific data.
32513498266Sopenharmony_ci *
32613498266Sopenharmony_ci * Parameters:
32713498266Sopenharmony_ci *
32813498266Sopenharmony_ci * nego     [in/out] - The Negotiate data struct being cleaned up.
32913498266Sopenharmony_ci *
33013498266Sopenharmony_ci */
33113498266Sopenharmony_civoid Curl_auth_cleanup_spnego(struct negotiatedata *nego)
33213498266Sopenharmony_ci{
33313498266Sopenharmony_ci  /* Free our security context */
33413498266Sopenharmony_ci  if(nego->context) {
33513498266Sopenharmony_ci    s_pSecFn->DeleteSecurityContext(nego->context);
33613498266Sopenharmony_ci    free(nego->context);
33713498266Sopenharmony_ci    nego->context = NULL;
33813498266Sopenharmony_ci  }
33913498266Sopenharmony_ci
34013498266Sopenharmony_ci  /* Free our credentials handle */
34113498266Sopenharmony_ci  if(nego->credentials) {
34213498266Sopenharmony_ci    s_pSecFn->FreeCredentialsHandle(nego->credentials);
34313498266Sopenharmony_ci    free(nego->credentials);
34413498266Sopenharmony_ci    nego->credentials = NULL;
34513498266Sopenharmony_ci  }
34613498266Sopenharmony_ci
34713498266Sopenharmony_ci  /* Free our identity */
34813498266Sopenharmony_ci  Curl_sspi_free_identity(nego->p_identity);
34913498266Sopenharmony_ci  nego->p_identity = NULL;
35013498266Sopenharmony_ci
35113498266Sopenharmony_ci  /* Free the SPN and output token */
35213498266Sopenharmony_ci  Curl_safefree(nego->spn);
35313498266Sopenharmony_ci  Curl_safefree(nego->output_token);
35413498266Sopenharmony_ci
35513498266Sopenharmony_ci  /* Reset any variables */
35613498266Sopenharmony_ci  nego->status = 0;
35713498266Sopenharmony_ci  nego->token_max = 0;
35813498266Sopenharmony_ci  nego->noauthpersist = FALSE;
35913498266Sopenharmony_ci  nego->havenoauthpersist = FALSE;
36013498266Sopenharmony_ci  nego->havenegdata = FALSE;
36113498266Sopenharmony_ci  nego->havemultiplerequests = FALSE;
36213498266Sopenharmony_ci}
36313498266Sopenharmony_ci
36413498266Sopenharmony_ci#endif /* USE_WINDOWS_SSPI && USE_SPNEGO */
365