1 /*************************************************************************** 2 * _ _ ____ _ 3 * Project ___| | | | _ \| | 4 * / __| | | | |_) | | 5 * | (__| |_| | _ <| |___ 6 * \___|\___/|_| \_\_____| 7 * 8 * Copyright (C) 2014 - 2016, Steve Holme, <steve_holme (at) hotmail.com>. 9 * Copyright (C) 2015, Daniel Stenberg, <daniel (at) haxx.se>, et al. 10 * 11 * This software is licensed as described in the file COPYING, which 12 * you should have received as part of this distribution. The terms 13 * are also available at https://curl.haxx.se/docs/copyright.html. 14 * 15 * You may opt to use, copy, modify, merge, publish, distribute and/or sell 16 * copies of the Software, and permit persons to whom the Software is 17 * furnished to do so, under the terms of the COPYING file. 18 * 19 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY 20 * KIND, either express or implied. 21 * 22 * RFC4752 The Kerberos V5 ("GSSAPI") SASL Mechanism 23 * 24 ***************************************************************************/ 25 26 #include "curl_setup.h" 27 28 #if defined(HAVE_GSSAPI) && defined(USE_KERBEROS5) 29 30 #include <curl/curl.h> 31 32 #include "vauth/vauth.h" 33 #include "curl_sasl.h" 34 #include "urldata.h" 35 #include "curl_base64.h" 36 #include "curl_gssapi.h" 37 #include "sendf.h" 38 #include "curl_printf.h" 39 40 /* The last #include files should be: */ 41 #include "curl_memory.h" 42 #include "memdebug.h" 43 44 /* 45 * Curl_auth_is_gssapi_supported() 46 * 47 * This is used to evaluate if GSSAPI (Kerberos V5) is supported. 48 * 49 * Parameters: None 50 * 51 * Returns TRUE if Kerberos V5 is supported by the GSS-API library. 52 */ 53 bool Curl_auth_is_gssapi_supported(void) 54 { 55 return TRUE; 56 } 57 58 /* 59 * Curl_auth_create_gssapi_user_message() 60 * 61 * This is used to generate an already encoded GSSAPI (Kerberos V5) user token 62 * message ready for sending to the recipient. 63 * 64 * Parameters: 65 * 66 * data [in] - The session handle. 67 * userp [in] - The user name. 68 * passdwp [in] - The user's password. 69 * service [in] - The service type such as http, smtp, pop or imap. 70 * host [in[ - The host name. 71 * mutual_auth [in] - Flag specifing whether or not mutual authentication 72 * is enabled. 73 * chlg64 [in] - Pointer to the optional base64 encoded challenge 74 * message. 75 * krb5 [in/out] - The Kerberos 5 data struct being used and modified. 76 * outptr [in/out] - The address where a pointer to newly allocated memory 77 * holding the result will be stored upon completion. 78 * outlen [out] - The length of the output message. 79 * 80 * Returns CURLE_OK on success. 81 */ 82 CURLcode Curl_auth_create_gssapi_user_message(struct Curl_easy *data, 83 const char *userp, 84 const char *passwdp, 85 const char *service, 86 const char *host, 87 const bool mutual_auth, 88 const char *chlg64, 89 struct kerberos5data *krb5, 90 char **outptr, size_t *outlen) 91 { 92 CURLcode result = CURLE_OK; 93 size_t chlglen = 0; 94 unsigned char *chlg = NULL; 95 OM_uint32 major_status; 96 OM_uint32 minor_status; 97 OM_uint32 unused_status; 98 gss_buffer_desc spn_token = GSS_C_EMPTY_BUFFER; 99 gss_buffer_desc input_token = GSS_C_EMPTY_BUFFER; 100 gss_buffer_desc output_token = GSS_C_EMPTY_BUFFER; 101 102 (void) userp; 103 (void) passwdp; 104 105 if(!krb5->spn) { 106 /* Generate our SPN */ 107 char *spn = Curl_auth_build_spn(service, NULL, host); 108 if(!spn) 109 return CURLE_OUT_OF_MEMORY; 110 111 /* Populate the SPN structure */ 112 spn_token.value = spn; 113 spn_token.length = strlen(spn); 114 115 /* Import the SPN */ 116 major_status = gss_import_name(&minor_status, &spn_token, 117 GSS_C_NT_HOSTBASED_SERVICE, &krb5->spn); 118 if(GSS_ERROR(major_status)) { 119 Curl_gss_log_error(data, "gss_import_name() failed: ", 120 major_status, minor_status); 121 122 free(spn); 123 124 return CURLE_OUT_OF_MEMORY; 125 } 126 127 free(spn); 128 } 129 130 if(chlg64 && *chlg64) { 131 /* Decode the base-64 encoded challenge message */ 132 if(*chlg64 != '=') { 133 result = Curl_base64_decode(chlg64, &chlg, &chlglen); 134 if(result) 135 return result; 136 } 137 138 /* Ensure we have a valid challenge message */ 139 if(!chlg) { 140 infof(data, "GSSAPI handshake failure (empty challenge message)\n"); 141 142 return CURLE_BAD_CONTENT_ENCODING; 143 } 144 145 /* Setup the challenge "input" security buffer */ 146 input_token.value = chlg; 147 input_token.length = chlglen; 148 } 149 150 major_status = Curl_gss_init_sec_context(data, 151 &minor_status, 152 &krb5->context, 153 krb5->spn, 154 &Curl_krb5_mech_oid, 155 GSS_C_NO_CHANNEL_BINDINGS, 156 &input_token, 157 &output_token, 158 mutual_auth, 159 NULL); 160 161 /* Free the decoded challenge as it is not required anymore */ 162 free(input_token.value); 163 164 if(GSS_ERROR(major_status)) { 165 if(output_token.value) 166 gss_release_buffer(&unused_status, &output_token); 167 168 Curl_gss_log_error(data, "gss_init_sec_context() failed: ", 169 major_status, minor_status); 170 171 return CURLE_RECV_ERROR; 172 } 173 174 if(output_token.value && output_token.length) { 175 /* Base64 encode the response */ 176 result = Curl_base64_encode(data, (char *) output_token.value, 177 output_token.length, outptr, outlen); 178 179 gss_release_buffer(&unused_status, &output_token); 180 } 181 else if(mutual_auth) { 182 *outptr = strdup(""); 183 if(!*outptr) 184 result = CURLE_OUT_OF_MEMORY; 185 } 186 187 return result; 188 } 189 190 /* 191 * Curl_auth_create_gssapi_security_message() 192 * 193 * This is used to generate an already encoded GSSAPI (Kerberos V5) security 194 * token message ready for sending to the recipient. 195 * 196 * Parameters: 197 * 198 * data [in] - The session handle. 199 * chlg64 [in] - Pointer to the optional base64 encoded challenge message. 200 * krb5 [in/out] - The Kerberos 5 data struct being used and modified. 201 * outptr [in/out] - The address where a pointer to newly allocated memory 202 * holding the result will be stored upon completion. 203 * outlen [out] - The length of the output message. 204 * 205 * Returns CURLE_OK on success. 206 */ 207 CURLcode Curl_auth_create_gssapi_security_message(struct Curl_easy *data, 208 const char *chlg64, 209 struct kerberos5data *krb5, 210 char **outptr, 211 size_t *outlen) 212 { 213 CURLcode result = CURLE_OK; 214 size_t chlglen = 0; 215 size_t messagelen = 0; 216 unsigned char *chlg = NULL; 217 unsigned char *message = NULL; 218 OM_uint32 major_status; 219 OM_uint32 minor_status; 220 OM_uint32 unused_status; 221 gss_buffer_desc input_token = GSS_C_EMPTY_BUFFER; 222 gss_buffer_desc output_token = GSS_C_EMPTY_BUFFER; 223 unsigned int indata = 0; 224 unsigned int outdata = 0; 225 gss_qop_t qop = GSS_C_QOP_DEFAULT; 226 unsigned int sec_layer = 0; 227 unsigned int max_size = 0; 228 gss_name_t username = GSS_C_NO_NAME; 229 gss_buffer_desc username_token; 230 231 /* Decode the base-64 encoded input message */ 232 if(strlen(chlg64) && *chlg64 != '=') { 233 result = Curl_base64_decode(chlg64, &chlg, &chlglen); 234 if(result) 235 return result; 236 } 237 238 /* Ensure we have a valid challenge message */ 239 if(!chlg) { 240 infof(data, "GSSAPI handshake failure (empty security message)\n"); 241 242 return CURLE_BAD_CONTENT_ENCODING; 243 } 244 245 /* Get the fully qualified username back from the context */ 246 major_status = gss_inquire_context(&minor_status, krb5->context, 247 &username, NULL, NULL, NULL, NULL, 248 NULL, NULL); 249 if(GSS_ERROR(major_status)) { 250 Curl_gss_log_error(data, "gss_inquire_context() failed: ", 251 major_status, minor_status); 252 253 free(chlg); 254 255 return CURLE_OUT_OF_MEMORY; 256 } 257 258 /* Convert the username from internal format to a displayable token */ 259 major_status = gss_display_name(&minor_status, username, 260 &username_token, NULL); 261 if(GSS_ERROR(major_status)) { 262 Curl_gss_log_error(data, "gss_display_name() failed: ", 263 major_status, minor_status); 264 265 free(chlg); 266 267 return CURLE_OUT_OF_MEMORY; 268 } 269 270 /* Setup the challenge "input" security buffer */ 271 input_token.value = chlg; 272 input_token.length = chlglen; 273 274 /* Decrypt the inbound challenge and obtain the qop */ 275 major_status = gss_unwrap(&minor_status, krb5->context, &input_token, 276 &output_token, NULL, &qop); 277 if(GSS_ERROR(major_status)) { 278 Curl_gss_log_error(data, "gss_unwrap() failed: ", 279 major_status, minor_status); 280 281 gss_release_buffer(&unused_status, &username_token); 282 free(chlg); 283 284 return CURLE_BAD_CONTENT_ENCODING; 285 } 286 287 /* Not 4 octets long so fail as per RFC4752 Section 3.1 */ 288 if(output_token.length != 4) { 289 infof(data, "GSSAPI handshake failure (invalid security data)\n"); 290 291 gss_release_buffer(&unused_status, &username_token); 292 free(chlg); 293 294 return CURLE_BAD_CONTENT_ENCODING; 295 } 296 297 /* Copy the data out and free the challenge as it is not required anymore */ 298 memcpy(&indata, output_token.value, 4); 299 gss_release_buffer(&unused_status, &output_token); 300 free(chlg); 301 302 /* Extract the security layer */ 303 sec_layer = indata & 0x000000FF; 304 if(!(sec_layer & GSSAUTH_P_NONE)) { 305 infof(data, "GSSAPI handshake failure (invalid security layer)\n"); 306 307 gss_release_buffer(&unused_status, &username_token); 308 309 return CURLE_BAD_CONTENT_ENCODING; 310 } 311 312 /* Extract the maximum message size the server can receive */ 313 max_size = ntohl(indata & 0xFFFFFF00); 314 if(max_size > 0) { 315 /* The server has told us it supports a maximum receive buffer, however, as 316 we don't require one unless we are encrypting data, we tell the server 317 our receive buffer is zero. */ 318 max_size = 0; 319 } 320 321 /* Allocate our message */ 322 messagelen = sizeof(outdata) + username_token.length + 1; 323 message = malloc(messagelen); 324 if(!message) { 325 gss_release_buffer(&unused_status, &username_token); 326 327 return CURLE_OUT_OF_MEMORY; 328 } 329 330 /* Populate the message with the security layer, client supported receive 331 message size and authorization identity including the 0x00 based 332 terminator. Note: Despite RFC4752 Section 3.1 stating "The authorization 333 identity is not terminated with the zero-valued (%x00) octet." it seems 334 necessary to include it. */ 335 outdata = htonl(max_size) | sec_layer; 336 memcpy(message, &outdata, sizeof(outdata)); 337 memcpy(message + sizeof(outdata), username_token.value, 338 username_token.length); 339 message[messagelen - 1] = '\0'; 340 341 /* Free the username token as it is not required anymore */ 342 gss_release_buffer(&unused_status, &username_token); 343 344 /* Setup the "authentication data" security buffer */ 345 input_token.value = message; 346 input_token.length = messagelen; 347 348 /* Encrypt the data */ 349 major_status = gss_wrap(&minor_status, krb5->context, 0, 350 GSS_C_QOP_DEFAULT, &input_token, NULL, 351 &output_token); 352 if(GSS_ERROR(major_status)) { 353 Curl_gss_log_error(data, "gss_wrap() failed: ", 354 major_status, minor_status); 355 356 free(message); 357 358 return CURLE_OUT_OF_MEMORY; 359 } 360 361 /* Base64 encode the response */ 362 result = Curl_base64_encode(data, (char *) output_token.value, 363 output_token.length, outptr, outlen); 364 365 /* Free the output buffer */ 366 gss_release_buffer(&unused_status, &output_token); 367 368 /* Free the message buffer */ 369 free(message); 370 371 return result; 372 } 373 374 /* 375 * Curl_auth_gssapi_cleanup() 376 * 377 * This is used to clean up the GSSAPI (Kerberos V5) specific data. 378 * 379 * Parameters: 380 * 381 * krb5 [in/out] - The Kerberos 5 data struct being cleaned up. 382 * 383 */ 384 void Curl_auth_gssapi_cleanup(struct kerberos5data *krb5) 385 { 386 OM_uint32 minor_status; 387 388 /* Free our security context */ 389 if(krb5->context != GSS_C_NO_CONTEXT) { 390 gss_delete_sec_context(&minor_status, &krb5->context, GSS_C_NO_BUFFER); 391 krb5->context = GSS_C_NO_CONTEXT; 392 } 393 394 /* Free the SPN */ 395 if(krb5->spn != GSS_C_NO_NAME) { 396 gss_release_name(&minor_status, &krb5->spn); 397 krb5->spn = GSS_C_NO_NAME; 398 } 399 } 400 401 #endif /* HAVE_GSSAPI && USE_KERBEROS5 */ 402