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