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