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 */
Curl_auth_is_gssapi_supported(void)51 bool 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 * passwdp [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 */
Curl_auth_create_gssapi_user_message(struct Curl_easy * data,const char * userp,const char * passwdp,const char * service,const char * host,const bool mutual_auth,const char * chlg64,struct kerberos5data * krb5,char ** outptr,size_t * outlen)87 CURLcode 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 credentials 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 = calloc(1, sizeof(CredHandle));
154 if(!krb5->credentials)
155 return CURLE_OUT_OF_MEMORY;
156
157 /* Acquire our credentials handle */
158 status = s_pSecFn->AcquireCredentialsHandle(NULL,
159 (TCHAR *)
160 TEXT(SP_NAME_KERBEROS),
161 SECPKG_CRED_OUTBOUND, NULL,
162 krb5->p_identity, NULL, NULL,
163 krb5->credentials, &expiry);
164 if(status != SEC_E_OK)
165 return CURLE_LOGIN_DENIED;
166
167 /* Allocate our new context handle */
168 krb5->context = calloc(1, sizeof(CtxtHandle));
169 if(!krb5->context)
170 return CURLE_OUT_OF_MEMORY;
171 }
172
173 if(chlg64 && *chlg64) {
174 /* Decode the base-64 encoded challenge message */
175 if(*chlg64 != '=') {
176 result = Curl_base64_decode(chlg64, &chlg, &chlglen);
177 if(result)
178 return result;
179 }
180
181 /* Ensure we have a valid challenge message */
182 if(!chlg) {
183 infof(data, "GSSAPI handshake failure (empty challenge message)\n");
184
185 return CURLE_BAD_CONTENT_ENCODING;
186 }
187
188 /* Setup the challenge "input" security buffer */
189 chlg_desc.ulVersion = SECBUFFER_VERSION;
190 chlg_desc.cBuffers = 1;
191 chlg_desc.pBuffers = &chlg_buf;
192 chlg_buf.BufferType = SECBUFFER_TOKEN;
193 chlg_buf.pvBuffer = chlg;
194 chlg_buf.cbBuffer = curlx_uztoul(chlglen);
195 }
196
197 /* Setup the response "output" security buffer */
198 resp_desc.ulVersion = SECBUFFER_VERSION;
199 resp_desc.cBuffers = 1;
200 resp_desc.pBuffers = &resp_buf;
201 resp_buf.BufferType = SECBUFFER_TOKEN;
202 resp_buf.pvBuffer = krb5->output_token;
203 resp_buf.cbBuffer = curlx_uztoul(krb5->token_max);
204
205 /* Generate our challenge-response message */
206 status = s_pSecFn->InitializeSecurityContext(krb5->credentials,
207 chlg ? krb5->context : NULL,
208 krb5->spn,
209 (mutual_auth ?
210 ISC_REQ_MUTUAL_AUTH : 0),
211 0, SECURITY_NATIVE_DREP,
212 chlg ? &chlg_desc : NULL, 0,
213 &context,
214 &resp_desc, &attrs,
215 &expiry);
216
217 /* Free the decoded challenge as it is not required anymore */
218 free(chlg);
219
220 if(status != SEC_E_OK && status != SEC_I_CONTINUE_NEEDED) {
221 return CURLE_RECV_ERROR;
222 }
223
224 if(memcmp(&context, krb5->context, sizeof(context))) {
225 s_pSecFn->DeleteSecurityContext(krb5->context);
226
227 memcpy(krb5->context, &context, sizeof(context));
228 }
229
230 if(resp_buf.cbBuffer) {
231 /* Base64 encode the response */
232 result = Curl_base64_encode(data, (char *) resp_buf.pvBuffer,
233 resp_buf.cbBuffer, outptr, outlen);
234 }
235 else if(mutual_auth) {
236 *outptr = strdup("");
237 if(!*outptr)
238 result = CURLE_OUT_OF_MEMORY;
239 }
240
241 return result;
242 }
243
244 /*
245 * Curl_auth_create_gssapi_security_message()
246 *
247 * This is used to generate an already encoded GSSAPI (Kerberos V5) security
248 * token message ready for sending to the recipient.
249 *
250 * Parameters:
251 *
252 * data [in] - The session handle.
253 * chlg64 [in] - The optional base64 encoded challenge message.
254 * krb5 [in/out] - The Kerberos 5 data struct being used and modified.
255 * outptr [in/out] - The address where a pointer to newly allocated memory
256 * holding the result will be stored upon completion.
257 * outlen [out] - The length of the output message.
258 *
259 * Returns CURLE_OK on success.
260 */
Curl_auth_create_gssapi_security_message(struct Curl_easy * data,const char * chlg64,struct kerberos5data * krb5,char ** outptr,size_t * outlen)261 CURLcode Curl_auth_create_gssapi_security_message(struct Curl_easy *data,
262 const char *chlg64,
263 struct kerberos5data *krb5,
264 char **outptr,
265 size_t *outlen)
266 {
267 CURLcode result = CURLE_OK;
268 size_t offset = 0;
269 size_t chlglen = 0;
270 size_t messagelen = 0;
271 size_t appdatalen = 0;
272 unsigned char *chlg = NULL;
273 unsigned char *trailer = NULL;
274 unsigned char *message = NULL;
275 unsigned char *padding = NULL;
276 unsigned char *appdata = NULL;
277 SecBuffer input_buf[2];
278 SecBuffer wrap_buf[3];
279 SecBufferDesc input_desc;
280 SecBufferDesc wrap_desc;
281 unsigned long indata = 0;
282 unsigned long outdata = 0;
283 unsigned long qop = 0;
284 unsigned long sec_layer = 0;
285 unsigned long max_size = 0;
286 SecPkgContext_Sizes sizes;
287 SecPkgCredentials_Names names;
288 SECURITY_STATUS status;
289 char *user_name;
290
291 /* Decode the base-64 encoded input message */
292 if(strlen(chlg64) && *chlg64 != '=') {
293 result = Curl_base64_decode(chlg64, &chlg, &chlglen);
294 if(result)
295 return result;
296 }
297
298 /* Ensure we have a valid challenge message */
299 if(!chlg) {
300 infof(data, "GSSAPI handshake failure (empty security message)\n");
301
302 return CURLE_BAD_CONTENT_ENCODING;
303 }
304
305 /* Get our response size information */
306 status = s_pSecFn->QueryContextAttributes(krb5->context,
307 SECPKG_ATTR_SIZES,
308 &sizes);
309 if(status != SEC_E_OK) {
310 free(chlg);
311
312 return CURLE_OUT_OF_MEMORY;
313 }
314
315 /* Get the fully qualified username back from the context */
316 status = s_pSecFn->QueryCredentialsAttributes(krb5->credentials,
317 SECPKG_CRED_ATTR_NAMES,
318 &names);
319 if(status != SEC_E_OK) {
320 free(chlg);
321
322 return CURLE_RECV_ERROR;
323 }
324
325 /* Setup the "input" security buffer */
326 input_desc.ulVersion = SECBUFFER_VERSION;
327 input_desc.cBuffers = 2;
328 input_desc.pBuffers = input_buf;
329 input_buf[0].BufferType = SECBUFFER_STREAM;
330 input_buf[0].pvBuffer = chlg;
331 input_buf[0].cbBuffer = curlx_uztoul(chlglen);
332 input_buf[1].BufferType = SECBUFFER_DATA;
333 input_buf[1].pvBuffer = NULL;
334 input_buf[1].cbBuffer = 0;
335
336 /* Decrypt the inbound challenge and obtain the qop */
337 status = s_pSecFn->DecryptMessage(krb5->context, &input_desc, 0, &qop);
338 if(status != SEC_E_OK) {
339 infof(data, "GSSAPI handshake failure (empty security message)\n");
340
341 free(chlg);
342
343 return CURLE_BAD_CONTENT_ENCODING;
344 }
345
346 /* Not 4 octets long so fail as per RFC4752 Section 3.1 */
347 if(input_buf[1].cbBuffer != 4) {
348 infof(data, "GSSAPI handshake failure (invalid security data)\n");
349
350 free(chlg);
351
352 return CURLE_BAD_CONTENT_ENCODING;
353 }
354
355 /* Copy the data out and free the challenge as it is not required anymore */
356 memcpy(&indata, input_buf[1].pvBuffer, 4);
357 s_pSecFn->FreeContextBuffer(input_buf[1].pvBuffer);
358 free(chlg);
359
360 /* Extract the security layer */
361 sec_layer = indata & 0x000000FF;
362 if(!(sec_layer & KERB_WRAP_NO_ENCRYPT)) {
363 infof(data, "GSSAPI handshake failure (invalid security layer)\n");
364
365 return CURLE_BAD_CONTENT_ENCODING;
366 }
367
368 /* Extract the maximum message size the server can receive */
369 max_size = ntohl(indata & 0xFFFFFF00);
370 if(max_size > 0) {
371 /* The server has told us it supports a maximum receive buffer, however, as
372 we don't require one unless we are encrypting data, we tell the server
373 our receive buffer is zero. */
374 max_size = 0;
375 }
376
377 /* Allocate the trailer */
378 trailer = malloc(sizes.cbSecurityTrailer);
379 if(!trailer)
380 return CURLE_OUT_OF_MEMORY;
381
382 /* Convert the user name to UTF8 when operating with Unicode */
383 user_name = Curl_convert_tchar_to_UTF8(names.sUserName);
384 if(!user_name) {
385 free(trailer);
386
387 return CURLE_OUT_OF_MEMORY;
388 }
389
390 /* Allocate our message */
391 messagelen = sizeof(outdata) + strlen(user_name) + 1;
392 message = malloc(messagelen);
393 if(!message) {
394 free(trailer);
395 Curl_unicodefree(user_name);
396
397 return CURLE_OUT_OF_MEMORY;
398 }
399
400 /* Populate the message with the security layer, client supported receive
401 message size and authorization identity including the 0x00 based
402 terminator. Note: Despite RFC4752 Section 3.1 stating "The authorization
403 identity is not terminated with the zero-valued (%x00) octet." it seems
404 necessary to include it. */
405 outdata = htonl(max_size) | sec_layer;
406 memcpy(message, &outdata, sizeof(outdata));
407 strcpy((char *) message + sizeof(outdata), user_name);
408 Curl_unicodefree(user_name);
409
410 /* Allocate the padding */
411 padding = malloc(sizes.cbBlockSize);
412 if(!padding) {
413 free(message);
414 free(trailer);
415
416 return CURLE_OUT_OF_MEMORY;
417 }
418
419 /* Setup the "authentication data" security buffer */
420 wrap_desc.ulVersion = SECBUFFER_VERSION;
421 wrap_desc.cBuffers = 3;
422 wrap_desc.pBuffers = wrap_buf;
423 wrap_buf[0].BufferType = SECBUFFER_TOKEN;
424 wrap_buf[0].pvBuffer = trailer;
425 wrap_buf[0].cbBuffer = sizes.cbSecurityTrailer;
426 wrap_buf[1].BufferType = SECBUFFER_DATA;
427 wrap_buf[1].pvBuffer = message;
428 wrap_buf[1].cbBuffer = curlx_uztoul(messagelen);
429 wrap_buf[2].BufferType = SECBUFFER_PADDING;
430 wrap_buf[2].pvBuffer = padding;
431 wrap_buf[2].cbBuffer = sizes.cbBlockSize;
432
433 /* Encrypt the data */
434 status = s_pSecFn->EncryptMessage(krb5->context, KERB_WRAP_NO_ENCRYPT,
435 &wrap_desc, 0);
436 if(status != SEC_E_OK) {
437 free(padding);
438 free(message);
439 free(trailer);
440
441 return CURLE_OUT_OF_MEMORY;
442 }
443
444 /* Allocate the encryption (wrap) buffer */
445 appdatalen = wrap_buf[0].cbBuffer + wrap_buf[1].cbBuffer +
446 wrap_buf[2].cbBuffer;
447 appdata = malloc(appdatalen);
448 if(!appdata) {
449 free(padding);
450 free(message);
451 free(trailer);
452
453 return CURLE_OUT_OF_MEMORY;
454 }
455
456 /* Populate the encryption buffer */
457 memcpy(appdata, wrap_buf[0].pvBuffer, wrap_buf[0].cbBuffer);
458 offset += wrap_buf[0].cbBuffer;
459 memcpy(appdata + offset, wrap_buf[1].pvBuffer, wrap_buf[1].cbBuffer);
460 offset += wrap_buf[1].cbBuffer;
461 memcpy(appdata + offset, wrap_buf[2].pvBuffer, wrap_buf[2].cbBuffer);
462
463 /* Base64 encode the response */
464 result = Curl_base64_encode(data, (char *) appdata, appdatalen, outptr,
465 outlen);
466
467 /* Free all of our local buffers */
468 free(appdata);
469 free(padding);
470 free(message);
471 free(trailer);
472
473 return result;
474 }
475
476 /*
477 * Curl_auth_gssapi_cleanup()
478 *
479 * This is used to clean up the GSSAPI (Kerberos V5) specific data.
480 *
481 * Parameters:
482 *
483 * krb5 [in/out] - The Kerberos 5 data struct being cleaned up.
484 *
485 */
Curl_auth_gssapi_cleanup(struct kerberos5data * krb5)486 void Curl_auth_gssapi_cleanup(struct kerberos5data *krb5)
487 {
488 /* Free our security context */
489 if(krb5->context) {
490 s_pSecFn->DeleteSecurityContext(krb5->context);
491 free(krb5->context);
492 krb5->context = NULL;
493 }
494
495 /* Free our credentials handle */
496 if(krb5->credentials) {
497 s_pSecFn->FreeCredentialsHandle(krb5->credentials);
498 free(krb5->credentials);
499 krb5->credentials = NULL;
500 }
501
502 /* Free our identity */
503 Curl_sspi_free_identity(krb5->p_identity);
504 krb5->p_identity = NULL;
505
506 /* Free the SPN and output token */
507 Curl_safefree(krb5->spn);
508 Curl_safefree(krb5->output_token);
509
510 /* Reset any variables */
511 krb5->token_max = 0;
512 }
513
514 #endif /* USE_WINDOWS_SSPI && USE_KERBEROS5*/
515