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