Home | History | Annotate | Download | only in vauth
      1 /***************************************************************************
      2  *                                  _   _ ____  _
      3  *  Project                     ___| | | |  _ \| |
      4  *                             / __| | | | |_) | |
      5  *                            | (__| |_| |  _ <| |___
      6  *                             \___|\___/|_| \_\_____|
      7  *
      8  * Copyright (C) 2014 - 2016, Steve Holme, <steve_holme (at) hotmail.com>.
      9  *
     10  * This software is licensed as described in the file COPYING, which
     11  * you should have received as part of this distribution. The terms
     12  * are also available at https://curl.haxx.se/docs/copyright.html.
     13  *
     14  * You may opt to use, copy, modify, merge, publish, distribute and/or sell
     15  * copies of the Software, and permit persons to whom the Software is
     16  * furnished to do so, under the terms of the COPYING file.
     17  *
     18  * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
     19  * KIND, either express or implied.
     20  *
     21  * RFC4752 The Kerberos V5 ("GSSAPI") SASL Mechanism
     22  *
     23  ***************************************************************************/
     24 
     25 #include "curl_setup.h"
     26 
     27 #if defined(USE_WINDOWS_SSPI) && defined(USE_KERBEROS5)
     28 
     29 #include <curl/curl.h>
     30 
     31 #include "vauth/vauth.h"
     32 #include "urldata.h"
     33 #include "curl_base64.h"
     34 #include "warnless.h"
     35 #include "curl_multibyte.h"
     36 #include "sendf.h"
     37 
     38 /* The last #include files should be: */
     39 #include "curl_memory.h"
     40 #include "memdebug.h"
     41 
     42 /*
     43  * Curl_auth_create_gssapi_user_message()
     44  *
     45  * This is used to generate an already encoded GSSAPI (Kerberos V5) user token
     46  * message ready for sending to the recipient.
     47  *
     48  * Parameters:
     49  *
     50  * data        [in]     - The session handle.
     51  * userp       [in]     - The user name in the format User or Domain\User.
     52  * passdwp     [in]     - The user's password.
     53  * service     [in]     - The service type such as http, smtp, pop or imap.
     54  * host        [in]     - The host name.
     55  * mutual_auth [in]     - Flag specifing whether or not mutual authentication
     56  *                        is enabled.
     57  * chlg64      [in]     - The optional base64 encoded challenge message.
     58  * krb5        [in/out] - The Kerberos 5 data struct being used and modified.
     59  * outptr      [in/out] - The address where a pointer to newly allocated memory
     60  *                        holding the result will be stored upon completion.
     61  * outlen      [out]    - The length of the output message.
     62  *
     63  * Returns CURLE_OK on success.
     64  */
     65 CURLcode Curl_auth_create_gssapi_user_message(struct Curl_easy *data,
     66                                               const char *userp,
     67                                               const char *passwdp,
     68                                               const char *service,
     69                                               const char *host,
     70                                               const bool mutual_auth,
     71                                               const char *chlg64,
     72                                               struct kerberos5data *krb5,
     73                                               char **outptr, size_t *outlen)
     74 {
     75   CURLcode result = CURLE_OK;
     76   size_t chlglen = 0;
     77   unsigned char *chlg = NULL;
     78   CtxtHandle context;
     79   PSecPkgInfo SecurityPackage;
     80   SecBuffer chlg_buf;
     81   SecBuffer resp_buf;
     82   SecBufferDesc chlg_desc;
     83   SecBufferDesc resp_desc;
     84   SECURITY_STATUS status;
     85   unsigned long attrs;
     86   TimeStamp expiry; /* For Windows 9x compatibility of SSPI calls */
     87 
     88   if(!krb5->spn) {
     89     /* Generate our SPN */
     90     krb5->spn = Curl_auth_build_spn(service, host, NULL);
     91     if(!krb5->spn)
     92       return CURLE_OUT_OF_MEMORY;
     93   }
     94 
     95   if(!krb5->output_token) {
     96     /* Query the security package for Kerberos */
     97     status = s_pSecFn->QuerySecurityPackageInfo((TCHAR *)
     98                                                 TEXT(SP_NAME_KERBEROS),
     99                                                 &SecurityPackage);
    100     if(status != SEC_E_OK) {
    101       return CURLE_NOT_BUILT_IN;
    102     }
    103 
    104     krb5->token_max = SecurityPackage->cbMaxToken;
    105 
    106     /* Release the package buffer as it is not required anymore */
    107     s_pSecFn->FreeContextBuffer(SecurityPackage);
    108 
    109     /* Allocate our response buffer */
    110     krb5->output_token = malloc(krb5->token_max);
    111     if(!krb5->output_token)
    112       return CURLE_OUT_OF_MEMORY;
    113   }
    114 
    115   if(!krb5->credentials) {
    116     /* Do we have credientials to use or are we using single sign-on? */
    117     if(userp && *userp) {
    118       /* Populate our identity structure */
    119       result = Curl_create_sspi_identity(userp, passwdp, &krb5->identity);
    120       if(result)
    121         return result;
    122 
    123       /* Allow proper cleanup of the identity structure */
    124       krb5->p_identity = &krb5->identity;
    125     }
    126     else
    127       /* Use the current Windows user */
    128       krb5->p_identity = NULL;
    129 
    130     /* Allocate our credentials handle */
    131     krb5->credentials = malloc(sizeof(CredHandle));
    132     if(!krb5->credentials)
    133       return CURLE_OUT_OF_MEMORY;
    134 
    135     memset(krb5->credentials, 0, sizeof(CredHandle));
    136 
    137     /* Acquire our credentials handle */
    138     status = s_pSecFn->AcquireCredentialsHandle(NULL,
    139                                                 (TCHAR *)
    140                                                 TEXT(SP_NAME_KERBEROS),
    141                                                 SECPKG_CRED_OUTBOUND, NULL,
    142                                                 krb5->p_identity, NULL, NULL,
    143                                                 krb5->credentials, &expiry);
    144     if(status != SEC_E_OK)
    145       return CURLE_LOGIN_DENIED;
    146 
    147     /* Allocate our new context handle */
    148     krb5->context = malloc(sizeof(CtxtHandle));
    149     if(!krb5->context)
    150       return CURLE_OUT_OF_MEMORY;
    151 
    152     memset(krb5->context, 0, sizeof(CtxtHandle));
    153   }
    154 
    155   if(chlg64 && *chlg64) {
    156     /* Decode the base-64 encoded challenge message */
    157     if(*chlg64 != '=') {
    158       result = Curl_base64_decode(chlg64, &chlg, &chlglen);
    159       if(result)
    160         return result;
    161     }
    162 
    163     /* Ensure we have a valid challenge message */
    164     if(!chlg) {
    165       infof(data, "GSSAPI handshake failure (empty challenge message)\n");
    166 
    167       return CURLE_BAD_CONTENT_ENCODING;
    168     }
    169 
    170     /* Setup the challenge "input" security buffer */
    171     chlg_desc.ulVersion = SECBUFFER_VERSION;
    172     chlg_desc.cBuffers  = 1;
    173     chlg_desc.pBuffers  = &chlg_buf;
    174     chlg_buf.BufferType = SECBUFFER_TOKEN;
    175     chlg_buf.pvBuffer   = chlg;
    176     chlg_buf.cbBuffer   = curlx_uztoul(chlglen);
    177   }
    178 
    179   /* Setup the response "output" security buffer */
    180   resp_desc.ulVersion = SECBUFFER_VERSION;
    181   resp_desc.cBuffers  = 1;
    182   resp_desc.pBuffers  = &resp_buf;
    183   resp_buf.BufferType = SECBUFFER_TOKEN;
    184   resp_buf.pvBuffer   = krb5->output_token;
    185   resp_buf.cbBuffer   = curlx_uztoul(krb5->token_max);
    186 
    187   /* Generate our challenge-response message */
    188   status = s_pSecFn->InitializeSecurityContext(krb5->credentials,
    189                                                chlg ? krb5->context : NULL,
    190                                                krb5->spn,
    191                                                (mutual_auth ?
    192                                                 ISC_REQ_MUTUAL_AUTH : 0),
    193                                                0, SECURITY_NATIVE_DREP,
    194                                                chlg ? &chlg_desc : NULL, 0,
    195                                                &context,
    196                                                &resp_desc, &attrs,
    197                                                &expiry);
    198 
    199   /* Free the decoded challenge as it is not required anymore */
    200   free(chlg);
    201 
    202   if(status != SEC_E_OK && status != SEC_I_CONTINUE_NEEDED) {
    203     return CURLE_RECV_ERROR;
    204   }
    205 
    206   if(memcmp(&context, krb5->context, sizeof(context))) {
    207     s_pSecFn->DeleteSecurityContext(krb5->context);
    208 
    209     memcpy(krb5->context, &context, sizeof(context));
    210   }
    211 
    212   if(resp_buf.cbBuffer) {
    213     /* Base64 encode the response */
    214     result = Curl_base64_encode(data, (char *) resp_buf.pvBuffer,
    215                                 resp_buf.cbBuffer, outptr, outlen);
    216   }
    217   else if(mutual_auth) {
    218     *outptr = strdup("");
    219     if(!*outptr)
    220       result = CURLE_OUT_OF_MEMORY;
    221   }
    222 
    223   return result;
    224 }
    225 
    226 /*
    227  * Curl_auth_create_gssapi_security_message()
    228  *
    229  * This is used to generate an already encoded GSSAPI (Kerberos V5) security
    230  * token message ready for sending to the recipient.
    231  *
    232  * Parameters:
    233  *
    234  * data    [in]     - The session handle.
    235  * chlg64  [in]     - The optional base64 encoded challenge message.
    236  * krb5    [in/out] - The Kerberos 5 data struct being used and modified.
    237  * outptr  [in/out] - The address where a pointer to newly allocated memory
    238  *                    holding the result will be stored upon completion.
    239  * outlen  [out]    - The length of the output message.
    240  *
    241  * Returns CURLE_OK on success.
    242  */
    243 CURLcode Curl_auth_create_gssapi_security_message(struct Curl_easy *data,
    244                                                   const char *chlg64,
    245                                                   struct kerberos5data *krb5,
    246                                                   char **outptr,
    247                                                   size_t *outlen)
    248 {
    249   CURLcode result = CURLE_OK;
    250   size_t offset = 0;
    251   size_t chlglen = 0;
    252   size_t messagelen = 0;
    253   size_t appdatalen = 0;
    254   unsigned char *chlg = NULL;
    255   unsigned char *trailer = NULL;
    256   unsigned char *message = NULL;
    257   unsigned char *padding = NULL;
    258   unsigned char *appdata = NULL;
    259   SecBuffer input_buf[2];
    260   SecBuffer wrap_buf[3];
    261   SecBufferDesc input_desc;
    262   SecBufferDesc wrap_desc;
    263   unsigned long indata = 0;
    264   unsigned long outdata = 0;
    265   unsigned long qop = 0;
    266   unsigned long sec_layer = 0;
    267   unsigned long max_size = 0;
    268   SecPkgContext_Sizes sizes;
    269   SecPkgCredentials_Names names;
    270   SECURITY_STATUS status;
    271   char *user_name;
    272 
    273   /* Decode the base-64 encoded input message */
    274   if(strlen(chlg64) && *chlg64 != '=') {
    275     result = Curl_base64_decode(chlg64, &chlg, &chlglen);
    276     if(result)
    277       return result;
    278   }
    279 
    280   /* Ensure we have a valid challenge message */
    281   if(!chlg) {
    282     infof(data, "GSSAPI handshake failure (empty security message)\n");
    283 
    284     return CURLE_BAD_CONTENT_ENCODING;
    285   }
    286 
    287   /* Get our response size information */
    288   status = s_pSecFn->QueryContextAttributes(krb5->context,
    289                                             SECPKG_ATTR_SIZES,
    290                                             &sizes);
    291   if(status != SEC_E_OK) {
    292     free(chlg);
    293 
    294     return CURLE_OUT_OF_MEMORY;
    295   }
    296 
    297   /* Get the fully qualified username back from the context */
    298   status = s_pSecFn->QueryCredentialsAttributes(krb5->credentials,
    299                                                 SECPKG_CRED_ATTR_NAMES,
    300                                                 &names);
    301   if(status != SEC_E_OK) {
    302     free(chlg);
    303 
    304     return CURLE_RECV_ERROR;
    305   }
    306 
    307   /* Setup the "input" security buffer */
    308   input_desc.ulVersion = SECBUFFER_VERSION;
    309   input_desc.cBuffers = 2;
    310   input_desc.pBuffers = input_buf;
    311   input_buf[0].BufferType = SECBUFFER_STREAM;
    312   input_buf[0].pvBuffer = chlg;
    313   input_buf[0].cbBuffer = curlx_uztoul(chlglen);
    314   input_buf[1].BufferType = SECBUFFER_DATA;
    315   input_buf[1].pvBuffer = NULL;
    316   input_buf[1].cbBuffer = 0;
    317 
    318   /* Decrypt the inbound challenge and obtain the qop */
    319   status = s_pSecFn->DecryptMessage(krb5->context, &input_desc, 0, &qop);
    320   if(status != SEC_E_OK) {
    321     infof(data, "GSSAPI handshake failure (empty security message)\n");
    322 
    323     free(chlg);
    324 
    325     return CURLE_BAD_CONTENT_ENCODING;
    326   }
    327 
    328   /* Not 4 octets long so fail as per RFC4752 Section 3.1 */
    329   if(input_buf[1].cbBuffer != 4) {
    330     infof(data, "GSSAPI handshake failure (invalid security data)\n");
    331 
    332     free(chlg);
    333 
    334     return CURLE_BAD_CONTENT_ENCODING;
    335   }
    336 
    337   /* Copy the data out and free the challenge as it is not required anymore */
    338   memcpy(&indata, input_buf[1].pvBuffer, 4);
    339   s_pSecFn->FreeContextBuffer(input_buf[1].pvBuffer);
    340   free(chlg);
    341 
    342   /* Extract the security layer */
    343   sec_layer = indata & 0x000000FF;
    344   if(!(sec_layer & KERB_WRAP_NO_ENCRYPT)) {
    345     infof(data, "GSSAPI handshake failure (invalid security layer)\n");
    346 
    347     return CURLE_BAD_CONTENT_ENCODING;
    348   }
    349 
    350   /* Extract the maximum message size the server can receive */
    351   max_size = ntohl(indata & 0xFFFFFF00);
    352   if(max_size > 0) {
    353     /* The server has told us it supports a maximum receive buffer, however, as
    354        we don't require one unless we are encrypting data, we tell the server
    355        our receive buffer is zero. */
    356     max_size = 0;
    357   }
    358 
    359   /* Allocate the trailer */
    360   trailer = malloc(sizes.cbSecurityTrailer);
    361   if(!trailer)
    362     return CURLE_OUT_OF_MEMORY;
    363 
    364   /* Convert the user name to UTF8 when operating with Unicode */
    365   user_name = Curl_convert_tchar_to_UTF8(names.sUserName);
    366   if(!user_name) {
    367     free(trailer);
    368 
    369     return CURLE_OUT_OF_MEMORY;
    370   }
    371 
    372   /* Allocate our message */
    373   messagelen = sizeof(outdata) + strlen(user_name) + 1;
    374   message = malloc(messagelen);
    375   if(!message) {
    376     free(trailer);
    377     Curl_unicodefree(user_name);
    378 
    379     return CURLE_OUT_OF_MEMORY;
    380   }
    381 
    382   /* Populate the message with the security layer, client supported receive
    383      message size and authorization identity including the 0x00 based
    384      terminator. Note: Despite RFC4752 Section 3.1 stating "The authorization
    385      identity is not terminated with the zero-valued (%x00) octet." it seems
    386      necessary to include it. */
    387   outdata = htonl(max_size) | sec_layer;
    388   memcpy(message, &outdata, sizeof(outdata));
    389   strcpy((char *) message + sizeof(outdata), user_name);
    390   Curl_unicodefree(user_name);
    391 
    392   /* Allocate the padding */
    393   padding = malloc(sizes.cbBlockSize);
    394   if(!padding) {
    395     free(message);
    396     free(trailer);
    397 
    398     return CURLE_OUT_OF_MEMORY;
    399   }
    400 
    401   /* Setup the "authentication data" security buffer */
    402   wrap_desc.ulVersion    = SECBUFFER_VERSION;
    403   wrap_desc.cBuffers     = 3;
    404   wrap_desc.pBuffers     = wrap_buf;
    405   wrap_buf[0].BufferType = SECBUFFER_TOKEN;
    406   wrap_buf[0].pvBuffer   = trailer;
    407   wrap_buf[0].cbBuffer   = sizes.cbSecurityTrailer;
    408   wrap_buf[1].BufferType = SECBUFFER_DATA;
    409   wrap_buf[1].pvBuffer   = message;
    410   wrap_buf[1].cbBuffer   = curlx_uztoul(messagelen);
    411   wrap_buf[2].BufferType = SECBUFFER_PADDING;
    412   wrap_buf[2].pvBuffer   = padding;
    413   wrap_buf[2].cbBuffer   = sizes.cbBlockSize;
    414 
    415   /* Encrypt the data */
    416   status = s_pSecFn->EncryptMessage(krb5->context, KERB_WRAP_NO_ENCRYPT,
    417                                     &wrap_desc, 0);
    418   if(status != SEC_E_OK) {
    419     free(padding);
    420     free(message);
    421     free(trailer);
    422 
    423     return CURLE_OUT_OF_MEMORY;
    424   }
    425 
    426   /* Allocate the encryption (wrap) buffer */
    427   appdatalen = wrap_buf[0].cbBuffer + wrap_buf[1].cbBuffer +
    428                wrap_buf[2].cbBuffer;
    429   appdata = malloc(appdatalen);
    430   if(!appdata) {
    431     free(padding);
    432     free(message);
    433     free(trailer);
    434 
    435     return CURLE_OUT_OF_MEMORY;
    436   }
    437 
    438   /* Populate the encryption buffer */
    439   memcpy(appdata, wrap_buf[0].pvBuffer, wrap_buf[0].cbBuffer);
    440   offset += wrap_buf[0].cbBuffer;
    441   memcpy(appdata + offset, wrap_buf[1].pvBuffer, wrap_buf[1].cbBuffer);
    442   offset += wrap_buf[1].cbBuffer;
    443   memcpy(appdata + offset, wrap_buf[2].pvBuffer, wrap_buf[2].cbBuffer);
    444 
    445   /* Base64 encode the response */
    446   result = Curl_base64_encode(data, (char *) appdata, appdatalen, outptr,
    447                               outlen);
    448 
    449   /* Free all of our local buffers */
    450   free(appdata);
    451   free(padding);
    452   free(message);
    453   free(trailer);
    454 
    455   return result;
    456 }
    457 
    458 /*
    459  * Curl_auth_gssapi_cleanup()
    460  *
    461  * This is used to clean up the GSSAPI (Kerberos V5) specific data.
    462  *
    463  * Parameters:
    464  *
    465  * krb5     [in/out] - The Kerberos 5 data struct being cleaned up.
    466  *
    467  */
    468 void Curl_auth_gssapi_cleanup(struct kerberos5data *krb5)
    469 {
    470   /* Free our security context */
    471   if(krb5->context) {
    472     s_pSecFn->DeleteSecurityContext(krb5->context);
    473     free(krb5->context);
    474     krb5->context = NULL;
    475   }
    476 
    477   /* Free our credentials handle */
    478   if(krb5->credentials) {
    479     s_pSecFn->FreeCredentialsHandle(krb5->credentials);
    480     free(krb5->credentials);
    481     krb5->credentials = NULL;
    482   }
    483 
    484   /* Free our identity */
    485   Curl_sspi_free_identity(krb5->p_identity);
    486   krb5->p_identity = NULL;
    487 
    488   /* Free the SPN and output token */
    489   Curl_safefree(krb5->spn);
    490   Curl_safefree(krb5->output_token);
    491 
    492   /* Reset any variables */
    493   krb5->token_max = 0;
    494 }
    495 
    496 #endif /* USE_WINDOWS_SSPI && USE_KERBEROS5*/
    497