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