1 /*************************************************************************** 2 * _ _ ____ _ 3 * Project ___| | | | _ \| | 4 * / __| | | | |_) | | 5 * | (__| |_| | _ <| |___ 6 * \___|\___/|_| \_\_____| 7 * 8 * Copyright (C) 2014 - 2015, 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 http://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 "curl_sasl.h" 33 #include "urldata.h" 34 #include "curl_base64.h" 35 #include "curl_gssapi.h" 36 #include "sendf.h" 37 #include "curl_printf.h" 38 39 /* The last #include files should be: */ 40 #include "curl_memory.h" 41 #include "memdebug.h" 42 43 /* 44 * Curl_sasl_build_gssapi_spn() 45 * 46 * This is used to build a SPN string in the format service@host. 47 * 48 * Parameters: 49 * 50 * serivce [in] - The service type such as www, smtp, pop or imap. 51 * host [in] - The host name or realm. 52 * 53 * Returns a pointer to the newly allocated SPN. 54 */ 55 char *Curl_sasl_build_gssapi_spn(const char *service, const char *host) 56 { 57 /* Generate and return our SPN */ 58 return aprintf("%s@%s", service, host); 59 } 60 61 /* 62 * Curl_sasl_create_gssapi_user_message() 63 * 64 * This is used to generate an already encoded GSSAPI (Kerberos V5) user token 65 * message ready for sending to the recipient. 66 * 67 * Parameters: 68 * 69 * data [in] - The session handle. 70 * userp [in] - The user name. 71 * passdwp [in] - The user's password. 72 * service [in] - The service type such as www, smtp, pop or imap. 73 * mutual_auth [in] - Flag specifing whether or not mutual authentication 74 * is enabled. 75 * chlg64 [in] - Pointer to the optional base64 encoded challenge 76 * message. 77 * krb5 [in/out] - The gssapi data struct being used and modified. 78 * outptr [in/out] - The address where a pointer to newly allocated memory 79 * holding the result will be stored upon completion. 80 * outlen [out] - The length of the output message. 81 * 82 * Returns CURLE_OK on success. 83 */ 84 CURLcode Curl_sasl_create_gssapi_user_message(struct SessionHandle *data, 85 const char *userp, 86 const char *passwdp, 87 const char *service, 88 const bool mutual_auth, 89 const char *chlg64, 90 struct kerberos5data *krb5, 91 char **outptr, size_t *outlen) 92 { 93 CURLcode result = CURLE_OK; 94 size_t chlglen = 0; 95 unsigned char *chlg = NULL; 96 OM_uint32 gss_status; 97 OM_uint32 gss_major_status; 98 OM_uint32 gss_minor_status; 99 gss_buffer_desc spn_token = GSS_C_EMPTY_BUFFER; 100 gss_buffer_desc input_token = GSS_C_EMPTY_BUFFER; 101 gss_buffer_desc output_token = GSS_C_EMPTY_BUFFER; 102 103 (void) userp; 104 (void) passwdp; 105 106 if(krb5->context == GSS_C_NO_CONTEXT) { 107 /* Generate our SPN */ 108 char *spn = Curl_sasl_build_gssapi_spn(service, 109 data->easy_conn->host.name); 110 if(!spn) 111 return CURLE_OUT_OF_MEMORY; 112 113 /* Populate the SPN structure */ 114 spn_token.value = spn; 115 spn_token.length = strlen(spn); 116 117 /* Import the SPN */ 118 gss_major_status = gss_import_name(&gss_minor_status, &spn_token, 119 GSS_C_NT_HOSTBASED_SERVICE, &krb5->spn); 120 if(GSS_ERROR(gss_major_status)) { 121 Curl_gss_log_error(data, gss_minor_status, "gss_import_name() failed: "); 122 123 free(spn); 124 125 return CURLE_OUT_OF_MEMORY; 126 } 127 128 free(spn); 129 } 130 else { 131 /* Decode the base-64 encoded challenge message */ 132 if(strlen(chlg64) && *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 gss_major_status = Curl_gss_init_sec_context(data, 151 &gss_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(input_token.value); 162 163 if(GSS_ERROR(gss_major_status)) { 164 if(output_token.value) 165 gss_release_buffer(&gss_status, &output_token); 166 167 Curl_gss_log_error(data, gss_minor_status, 168 "gss_init_sec_context() failed: "); 169 170 return CURLE_RECV_ERROR; 171 } 172 173 if(output_token.value && output_token.length) { 174 /* Base64 encode the response */ 175 result = Curl_base64_encode(data, (char *) output_token.value, 176 output_token.length, outptr, outlen); 177 178 gss_release_buffer(&gss_status, &output_token); 179 } 180 181 return result; 182 } 183 184 /* 185 * Curl_sasl_create_gssapi_security_message() 186 * 187 * This is used to generate an already encoded GSSAPI (Kerberos V5) security 188 * token message ready for sending to the recipient. 189 * 190 * Parameters: 191 * 192 * data [in] - The session handle. 193 * chlg64 [in] - Pointer to the optional base64 encoded challenge message. 194 * krb5 [in/out] - The gssapi data struct being used and modified. 195 * outptr [in/out] - The address where a pointer to newly allocated memory 196 * holding the result will be stored upon completion. 197 * outlen [out] - The length of the output message. 198 * 199 * Returns CURLE_OK on success. 200 */ 201 CURLcode Curl_sasl_create_gssapi_security_message(struct SessionHandle *data, 202 const char *chlg64, 203 struct kerberos5data *krb5, 204 char **outptr, 205 size_t *outlen) 206 { 207 CURLcode result = CURLE_OK; 208 size_t chlglen = 0; 209 size_t messagelen = 0; 210 unsigned char *chlg = NULL; 211 unsigned char *message = NULL; 212 OM_uint32 gss_status; 213 OM_uint32 gss_major_status; 214 OM_uint32 gss_minor_status; 215 gss_buffer_desc input_token = GSS_C_EMPTY_BUFFER; 216 gss_buffer_desc output_token = GSS_C_EMPTY_BUFFER; 217 unsigned int indata = 0; 218 unsigned int outdata = 0; 219 gss_qop_t qop = GSS_C_QOP_DEFAULT; 220 unsigned int sec_layer = 0; 221 unsigned int max_size = 0; 222 gss_name_t username = GSS_C_NO_NAME; 223 gss_buffer_desc username_token; 224 225 /* Decode the base-64 encoded input message */ 226 if(strlen(chlg64) && *chlg64 != '=') { 227 result = Curl_base64_decode(chlg64, &chlg, &chlglen); 228 if(result) 229 return result; 230 } 231 232 /* Ensure we have a valid challenge message */ 233 if(!chlg) { 234 infof(data, "GSSAPI handshake failure (empty security message)\n"); 235 236 return CURLE_BAD_CONTENT_ENCODING; 237 } 238 239 /* Get the fully qualified username back from the context */ 240 gss_major_status = gss_inquire_context(&gss_minor_status, krb5->context, 241 &username, NULL, NULL, NULL, NULL, 242 NULL, NULL); 243 if(GSS_ERROR(gss_major_status)) { 244 Curl_gss_log_error(data, gss_minor_status, 245 "gss_inquire_context() failed: "); 246 247 free(chlg); 248 249 return CURLE_OUT_OF_MEMORY; 250 } 251 252 /* Convert the username from internal format to a displayable token */ 253 gss_major_status = gss_display_name(&gss_minor_status, username, 254 &username_token, NULL); 255 if(GSS_ERROR(gss_major_status)) { 256 Curl_gss_log_error(data, gss_minor_status, "gss_display_name() failed: "); 257 258 free(chlg); 259 260 return CURLE_OUT_OF_MEMORY; 261 } 262 263 /* Setup the challenge "input" security buffer */ 264 input_token.value = chlg; 265 input_token.length = chlglen; 266 267 /* Decrypt the inbound challenge and obtain the qop */ 268 gss_major_status = gss_unwrap(&gss_minor_status, krb5->context, &input_token, 269 &output_token, NULL, &qop); 270 if(GSS_ERROR(gss_major_status)) { 271 Curl_gss_log_error(data, gss_minor_status, "gss_unwrap() failed: "); 272 273 gss_release_buffer(&gss_status, &username_token); 274 free(chlg); 275 276 return CURLE_BAD_CONTENT_ENCODING; 277 } 278 279 /* Not 4 octets long so fail as per RFC4752 Section 3.1 */ 280 if(output_token.length != 4) { 281 infof(data, "GSSAPI handshake failure (invalid security data)\n"); 282 283 gss_release_buffer(&gss_status, &username_token); 284 free(chlg); 285 286 return CURLE_BAD_CONTENT_ENCODING; 287 } 288 289 /* Copy the data out and free the challenge as it is not required anymore */ 290 memcpy(&indata, output_token.value, 4); 291 gss_release_buffer(&gss_status, &output_token); 292 free(chlg); 293 294 /* Extract the security layer */ 295 sec_layer = indata & 0x000000FF; 296 if(!(sec_layer & GSSAUTH_P_NONE)) { 297 infof(data, "GSSAPI handshake failure (invalid security layer)\n"); 298 299 gss_release_buffer(&gss_status, &username_token); 300 301 return CURLE_BAD_CONTENT_ENCODING; 302 } 303 304 /* Extract the maximum message size the server can receive */ 305 max_size = ntohl(indata & 0xFFFFFF00); 306 if(max_size > 0) { 307 /* The server has told us it supports a maximum receive buffer, however, as 308 we don't require one unless we are encrypting data, we tell the server 309 our receive buffer is zero. */ 310 max_size = 0; 311 } 312 313 /* Allocate our message */ 314 messagelen = sizeof(outdata) + username_token.length + 1; 315 message = malloc(messagelen); 316 if(!message) { 317 gss_release_buffer(&gss_status, &username_token); 318 319 return CURLE_OUT_OF_MEMORY; 320 } 321 322 /* Populate the message with the security layer, client supported receive 323 message size and authorization identity including the 0x00 based 324 terminator. Note: Dispite RFC4752 Section 3.1 stating "The authorization 325 identity is not terminated with the zero-valued (%x00) octet." it seems 326 necessary to include it. */ 327 outdata = htonl(max_size) | sec_layer; 328 memcpy(message, &outdata, sizeof(outdata)); 329 memcpy(message + sizeof(outdata), username_token.value, 330 username_token.length); 331 message[messagelen - 1] = '\0'; 332 333 /* Free the username token as it is not required anymore */ 334 gss_release_buffer(&gss_status, &username_token); 335 336 /* Setup the "authentication data" security buffer */ 337 input_token.value = message; 338 input_token.length = messagelen; 339 340 /* Encrypt the data */ 341 gss_major_status = gss_wrap(&gss_minor_status, krb5->context, 0, 342 GSS_C_QOP_DEFAULT, &input_token, NULL, 343 &output_token); 344 if(GSS_ERROR(gss_major_status)) { 345 Curl_gss_log_error(data, gss_minor_status, "gss_wrap() failed: "); 346 347 free(message); 348 349 return CURLE_OUT_OF_MEMORY; 350 } 351 352 /* Base64 encode the response */ 353 result = Curl_base64_encode(data, (char *) output_token.value, 354 output_token.length, outptr, outlen); 355 356 /* Free the output buffer */ 357 gss_release_buffer(&gss_status, &output_token); 358 359 /* Free the message buffer */ 360 free(message); 361 362 return result; 363 } 364 365 /* 366 * Curl_sasl_gssapi_cleanup() 367 * 368 * This is used to clean up the gssapi specific data. 369 * 370 * Parameters: 371 * 372 * krb5 [in/out] - The kerberos 5 data struct being cleaned up. 373 * 374 */ 375 void Curl_sasl_gssapi_cleanup(struct kerberos5data *krb5) 376 { 377 OM_uint32 minor_status; 378 379 /* Free our security context */ 380 if(krb5->context != GSS_C_NO_CONTEXT) { 381 gss_delete_sec_context(&minor_status, &krb5->context, GSS_C_NO_BUFFER); 382 krb5->context = GSS_C_NO_CONTEXT; 383 } 384 385 /* Free the SPN */ 386 if(krb5->spn != GSS_C_NO_NAME) { 387 gss_release_name(&minor_status, &krb5->spn); 388 krb5->spn = GSS_C_NO_NAME; 389 } 390 } 391 392 #endif /* HAVE_GSSAPI && USE_KERBEROS5 */ 393