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