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 */
50bool 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 */
89CURLcode 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 * authzid [in] - The authorization identity if some.
242 * chlg [in] - The optional challenge message.
243 * krb5 [in/out] - The Kerberos 5 data struct being used and modified.
244 * out [out] - The result storage.
245 *
246 * Returns CURLE_OK on success.
247 */
248CURLcode Curl_auth_create_gssapi_security_message(struct Curl_easy *data,
249 const char *authzid,
250 const struct bufref *chlg,
251 struct kerberos5data *krb5,
252 struct bufref *out)
253{
254 size_t offset = 0;
255 size_t messagelen = 0;
256 size_t appdatalen = 0;
257 unsigned char *trailer = NULL;
258 unsigned char *message = NULL;
259 unsigned char *padding = NULL;
260 unsigned char *appdata = NULL;
261 SecBuffer input_buf[2];
262 SecBuffer wrap_buf[3];
263 SecBufferDesc input_desc;
264 SecBufferDesc wrap_desc;
265 unsigned char *indata;
266 unsigned long qop = 0;
267 unsigned long sec_layer = 0;
268 unsigned long max_size = 0;
269 SecPkgContext_Sizes sizes;
270 SECURITY_STATUS status;
271
272#if defined(CURL_DISABLE_VERBOSE_STRINGS)
273 (void) data;
274#endif
275
276 /* Ensure we have a valid challenge message */
277 if(!Curl_bufref_len(chlg)) {
278 infof(data, "GSSAPI handshake failure (empty security message)");
279 return CURLE_BAD_CONTENT_ENCODING;
280 }
281
282 /* Get our response size information */
283 status = s_pSecFn->QueryContextAttributes(krb5->context,
284 SECPKG_ATTR_SIZES,
285 &sizes);
286
287 if(status == SEC_E_INSUFFICIENT_MEMORY)
288 return CURLE_OUT_OF_MEMORY;
289
290 if(status != SEC_E_OK)
291 return CURLE_AUTH_ERROR;
292
293 /* Setup the "input" security buffer */
294 input_desc.ulVersion = SECBUFFER_VERSION;
295 input_desc.cBuffers = 2;
296 input_desc.pBuffers = input_buf;
297 input_buf[0].BufferType = SECBUFFER_STREAM;
298 input_buf[0].pvBuffer = (void *) Curl_bufref_ptr(chlg);
299 input_buf[0].cbBuffer = curlx_uztoul(Curl_bufref_len(chlg));
300 input_buf[1].BufferType = SECBUFFER_DATA;
301 input_buf[1].pvBuffer = NULL;
302 input_buf[1].cbBuffer = 0;
303
304 /* Decrypt the inbound challenge and obtain the qop */
305 status = s_pSecFn->DecryptMessage(krb5->context, &input_desc, 0, &qop);
306 if(status != SEC_E_OK) {
307 infof(data, "GSSAPI handshake failure (empty security message)");
308 return CURLE_BAD_CONTENT_ENCODING;
309 }
310
311 /* Not 4 octets long so fail as per RFC4752 Section 3.1 */
312 if(input_buf[1].cbBuffer != 4) {
313 infof(data, "GSSAPI handshake failure (invalid security data)");
314 return CURLE_BAD_CONTENT_ENCODING;
315 }
316
317 /* Extract the security layer and the maximum message size */
318 indata = input_buf[1].pvBuffer;
319 sec_layer = indata[0];
320 max_size = (indata[1] << 16) | (indata[2] << 8) | indata[3];
321
322 /* Free the challenge as it is not required anymore */
323 s_pSecFn->FreeContextBuffer(input_buf[1].pvBuffer);
324
325 /* Process the security layer */
326 if(!(sec_layer & KERB_WRAP_NO_ENCRYPT)) {
327 infof(data, "GSSAPI handshake failure (invalid security layer)");
328 return CURLE_BAD_CONTENT_ENCODING;
329 }
330 sec_layer &= KERB_WRAP_NO_ENCRYPT; /* We do not support a security layer */
331
332 /* Process the maximum message size the server can receive */
333 if(max_size > 0) {
334 /* The server has told us it supports a maximum receive buffer, however, as
335 we don't require one unless we are encrypting data, we tell the server
336 our receive buffer is zero. */
337 max_size = 0;
338 }
339
340 /* Allocate the trailer */
341 trailer = malloc(sizes.cbSecurityTrailer);
342 if(!trailer)
343 return CURLE_OUT_OF_MEMORY;
344
345 /* Allocate our message */
346 messagelen = 4;
347 if(authzid)
348 messagelen += strlen(authzid);
349 message = malloc(messagelen);
350 if(!message) {
351 free(trailer);
352
353 return CURLE_OUT_OF_MEMORY;
354 }
355
356 /* Populate the message with the security layer and client supported receive
357 message size. */
358 message[0] = sec_layer & 0xFF;
359 message[1] = (max_size >> 16) & 0xFF;
360 message[2] = (max_size >> 8) & 0xFF;
361 message[3] = max_size & 0xFF;
362
363 /* If given, append the authorization identity. */
364
365 if(authzid && *authzid)
366 memcpy(message + 4, authzid, messagelen - 4);
367
368 /* Allocate the padding */
369 padding = malloc(sizes.cbBlockSize);
370 if(!padding) {
371 free(message);
372 free(trailer);
373
374 return CURLE_OUT_OF_MEMORY;
375 }
376
377 /* Setup the "authentication data" security buffer */
378 wrap_desc.ulVersion = SECBUFFER_VERSION;
379 wrap_desc.cBuffers = 3;
380 wrap_desc.pBuffers = wrap_buf;
381 wrap_buf[0].BufferType = SECBUFFER_TOKEN;
382 wrap_buf[0].pvBuffer = trailer;
383 wrap_buf[0].cbBuffer = sizes.cbSecurityTrailer;
384 wrap_buf[1].BufferType = SECBUFFER_DATA;
385 wrap_buf[1].pvBuffer = message;
386 wrap_buf[1].cbBuffer = curlx_uztoul(messagelen);
387 wrap_buf[2].BufferType = SECBUFFER_PADDING;
388 wrap_buf[2].pvBuffer = padding;
389 wrap_buf[2].cbBuffer = sizes.cbBlockSize;
390
391 /* Encrypt the data */
392 status = s_pSecFn->EncryptMessage(krb5->context, KERB_WRAP_NO_ENCRYPT,
393 &wrap_desc, 0);
394 if(status != SEC_E_OK) {
395 free(padding);
396 free(message);
397 free(trailer);
398
399 if(status == SEC_E_INSUFFICIENT_MEMORY)
400 return CURLE_OUT_OF_MEMORY;
401
402 return CURLE_AUTH_ERROR;
403 }
404
405 /* Allocate the encryption (wrap) buffer */
406 appdatalen = wrap_buf[0].cbBuffer + wrap_buf[1].cbBuffer +
407 wrap_buf[2].cbBuffer;
408 appdata = malloc(appdatalen);
409 if(!appdata) {
410 free(padding);
411 free(message);
412 free(trailer);
413
414 return CURLE_OUT_OF_MEMORY;
415 }
416
417 /* Populate the encryption buffer */
418 memcpy(appdata, wrap_buf[0].pvBuffer, wrap_buf[0].cbBuffer);
419 offset += wrap_buf[0].cbBuffer;
420 memcpy(appdata + offset, wrap_buf[1].pvBuffer, wrap_buf[1].cbBuffer);
421 offset += wrap_buf[1].cbBuffer;
422 memcpy(appdata + offset, wrap_buf[2].pvBuffer, wrap_buf[2].cbBuffer);
423
424 /* Free all of our local buffers */
425 free(padding);
426 free(message);
427 free(trailer);
428
429 /* Return the response. */
430 Curl_bufref_set(out, appdata, appdatalen, curl_free);
431 return CURLE_OK;
432}
433
434/*
435 * Curl_auth_cleanup_gssapi()
436 *
437 * This is used to clean up the GSSAPI (Kerberos V5) specific data.
438 *
439 * Parameters:
440 *
441 * krb5 [in/out] - The Kerberos 5 data struct being cleaned up.
442 *
443 */
444void Curl_auth_cleanup_gssapi(struct kerberos5data *krb5)
445{
446 /* Free our security context */
447 if(krb5->context) {
448 s_pSecFn->DeleteSecurityContext(krb5->context);
449 free(krb5->context);
450 krb5->context = NULL;
451 }
452
453 /* Free our credentials handle */
454 if(krb5->credentials) {
455 s_pSecFn->FreeCredentialsHandle(krb5->credentials);
456 free(krb5->credentials);
457 krb5->credentials = NULL;
458 }
459
460 /* Free our identity */
461 Curl_sspi_free_identity(krb5->p_identity);
462 krb5->p_identity = NULL;
463
464 /* Free the SPN and output token */
465 Curl_safefree(krb5->spn);
466 Curl_safefree(krb5->output_token);
467
468 /* Reset any variables */
469 krb5->token_max = 0;
470}
471
472#endif /* USE_WINDOWS_SSPI && USE_KERBEROS5*/
473