1/***************************************************************************
2 *                                  _   _ ____  _
3 *  Project                     ___| | | |  _ \| |
4 *                             / __| | | | |_) | |
5 *                            | (__| |_| |  _ <| |___
6 *                             \___|\___/|_| \_\_____|
7 *
8 * Copyright (C) 2012 - 2013, Daniel Stenberg, <daniel@haxx.se>, et al.
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 http://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 * RFC2195 CRAM-MD5 authentication
22 * RFC2831 DIGEST-MD5 authentication
23 * RFC4422 Simple Authentication and Security Layer (SASL)
24 * RFC4616 PLAIN authentication
25 *
26 ***************************************************************************/
27
28#include "curl_setup.h"
29
30#include <curl/curl.h>
31#include "urldata.h"
32
33#include "curl_base64.h"
34#include "curl_md5.h"
35#include "curl_rand.h"
36#include "curl_hmac.h"
37#include "curl_ntlm_msgs.h"
38#include "curl_sasl.h"
39#include "warnless.h"
40#include "curl_memory.h"
41
42#define _MPRINTF_REPLACE /* use our functions only */
43#include <curl/mprintf.h>
44
45/* The last #include file should be: */
46#include "memdebug.h"
47
48#ifndef CURL_DISABLE_CRYPTO_AUTH
49/* Retrieves the value for a corresponding key from the challenge string
50 * returns TRUE if the key could be found, FALSE if it does not exists
51 */
52static bool sasl_digest_get_key_value(const unsigned char *chlg,
53                                      const char *key,
54                                      char *value,
55                                      size_t max_val_len,
56                                      char end_char)
57{
58  char *find_pos;
59  size_t i;
60
61  find_pos = strstr((const char *) chlg, key);
62  if(!find_pos)
63    return FALSE;
64
65  find_pos += strlen(key);
66
67  for(i = 0; *find_pos && *find_pos != end_char && i < max_val_len - 1; ++i)
68    value[i] = *find_pos++;
69  value[i] = '\0';
70
71  return TRUE;
72}
73#endif
74
75/*
76 * Curl_sasl_create_plain_message()
77 *
78 * This is used to generate an already encoded PLAIN message ready
79 * for sending to the recipient.
80 *
81 * Parameters:
82 *
83 * data    [in]     - The session handle.
84 * userp   [in]     - The user name.
85 * passdwp [in]     - The user's password.
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_sasl_create_plain_message(struct SessionHandle *data,
93                                        const char *userp,
94                                        const char *passwdp,
95                                        char **outptr, size_t *outlen)
96{
97  char plainauth[2 * MAX_CURL_USER_LENGTH + MAX_CURL_PASSWORD_LENGTH];
98  size_t ulen;
99  size_t plen;
100
101  ulen = strlen(userp);
102  plen = strlen(passwdp);
103
104  if(2 * ulen + plen + 2 > sizeof(plainauth)) {
105    *outlen = 0;
106    *outptr = NULL;
107
108    /* Plainauth too small */
109    return CURLE_OUT_OF_MEMORY;
110  }
111
112  /* Calculate the reply */
113  memcpy(plainauth, userp, ulen);
114  plainauth[ulen] = '\0';
115  memcpy(plainauth + ulen + 1, userp, ulen);
116  plainauth[2 * ulen + 1] = '\0';
117  memcpy(plainauth + 2 * ulen + 2, passwdp, plen);
118
119  /* Base64 encode the reply */
120  return Curl_base64_encode(data, plainauth, 2 * ulen + plen + 2, outptr,
121                            outlen);
122}
123
124/*
125 * Curl_sasl_create_login_message()
126 *
127 * This is used to generate an already encoded LOGIN message containing the
128 * user name or password ready for sending to the recipient.
129 *
130 * Parameters:
131 *
132 * data    [in]     - The session handle.
133 * valuep  [in]     - The user name or user's password.
134 * outptr  [in/out] - The address where a pointer to newly allocated memory
135 *                    holding the result will be stored upon completion.
136 * outlen  [out]    - The length of the output message.
137 *
138 * Returns CURLE_OK on success.
139 */
140CURLcode Curl_sasl_create_login_message(struct SessionHandle *data,
141                                        const char *valuep, char **outptr,
142                                        size_t *outlen)
143{
144  size_t vlen = strlen(valuep);
145
146  if(!vlen) {
147    /* Calculate an empty reply */
148    *outptr = strdup("=");
149    if(*outptr) {
150      *outlen = (size_t) 1;
151      return CURLE_OK;
152    }
153
154    *outlen = 0;
155    return CURLE_OUT_OF_MEMORY;
156  }
157
158  /* Base64 encode the value */
159  return Curl_base64_encode(data, valuep, vlen, outptr, outlen);
160}
161
162#ifndef CURL_DISABLE_CRYPTO_AUTH
163/*
164 * Curl_sasl_create_cram_md5_message()
165 *
166 * This is used to generate an already encoded CRAM-MD5 response message ready
167 * for sending to the recipient.
168 *
169 * Parameters:
170 *
171 * data    [in]     - The session handle.
172 * chlg64  [in]     - Pointer to the base64 encoded challenge buffer.
173 * userp   [in]     - The user name.
174 * passdwp [in]     - The user's password.
175 * outptr  [in/out] - The address where a pointer to newly allocated memory
176 *                    holding the result will be stored upon completion.
177 * outlen  [out]    - The length of the output message.
178 *
179 * Returns CURLE_OK on success.
180 */
181CURLcode Curl_sasl_create_cram_md5_message(struct SessionHandle *data,
182                                           const char *chlg64,
183                                           const char *userp,
184                                           const char *passwdp,
185                                           char **outptr, size_t *outlen)
186{
187  CURLcode result = CURLE_OK;
188  size_t chlg64len = strlen(chlg64);
189  unsigned char *chlg = (unsigned char *) NULL;
190  size_t chlglen = 0;
191  HMAC_context *ctxt;
192  unsigned char digest[MD5_DIGEST_LEN];
193  char response[MAX_CURL_USER_LENGTH + 2 * MD5_DIGEST_LEN + 1];
194
195  /* Decode the challenge if necessary */
196  if(chlg64len && *chlg64 != '=') {
197    result = Curl_base64_decode(chlg64, &chlg, &chlglen);
198
199    if(result)
200      return result;
201  }
202
203  /* Compute the digest using the password as the key */
204  ctxt = Curl_HMAC_init(Curl_HMAC_MD5,
205                        (const unsigned char *) passwdp,
206                        curlx_uztoui(strlen(passwdp)));
207
208  if(!ctxt) {
209    Curl_safefree(chlg);
210    return CURLE_OUT_OF_MEMORY;
211  }
212
213  /* Update the digest with the given challenge */
214  if(chlglen > 0)
215    Curl_HMAC_update(ctxt, chlg, curlx_uztoui(chlglen));
216
217  Curl_safefree(chlg);
218
219  /* Finalise the digest */
220  Curl_HMAC_final(ctxt, digest);
221
222  /* Prepare the response */
223  snprintf(response, sizeof(response),
224      "%s %02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x",
225           userp, digest[0], digest[1], digest[2], digest[3], digest[4],
226           digest[5], digest[6], digest[7], digest[8], digest[9], digest[10],
227           digest[11], digest[12], digest[13], digest[14], digest[15]);
228
229  /* Base64 encode the reply */
230  return Curl_base64_encode(data, response, 0, outptr, outlen);
231}
232
233/*
234 * Curl_sasl_create_digest_md5_message()
235 *
236 * This is used to generate an already encoded DIGEST-MD5 response message
237 * ready for sending to the recipient.
238 *
239 * Parameters:
240 *
241 * data    [in]     - The session handle.
242 * chlg64  [in]     - Pointer to the base64 encoded challenge buffer.
243 * userp   [in]     - The user name.
244 * passdwp [in]     - The user's password.
245 * service [in]     - The service type such as www, smtp or pop
246 * outptr  [in/out] - The address where a pointer to newly allocated memory
247 *                    holding the result will be stored upon completion.
248 * outlen  [out]    - The length of the output message.
249 *
250 * Returns CURLE_OK on success.
251 */
252CURLcode Curl_sasl_create_digest_md5_message(struct SessionHandle *data,
253                                             const char *chlg64,
254                                             const char *userp,
255                                             const char *passwdp,
256                                             const char *service,
257                                             char **outptr, size_t *outlen)
258{
259  static const char table16[] = "0123456789abcdef";
260
261  CURLcode result = CURLE_OK;
262  unsigned char *chlg = (unsigned char *) NULL;
263  size_t chlglen = 0;
264  size_t i;
265  MD5_context *ctxt;
266  unsigned char digest[MD5_DIGEST_LEN];
267  char HA1_hex[2 * MD5_DIGEST_LEN + 1];
268  char HA2_hex[2 * MD5_DIGEST_LEN + 1];
269  char resp_hash_hex[2 * MD5_DIGEST_LEN + 1];
270
271  char nonce[64];
272  char realm[128];
273  char alg[64];
274  char nonceCount[] = "00000001";
275  char cnonce[]     = "12345678"; /* will be changed */
276  char method[]     = "AUTHENTICATE";
277  char qop[]        = "auth";
278  char uri[128];
279  char response[512];
280
281  result = Curl_base64_decode(chlg64, &chlg, &chlglen);
282
283  if(result)
284    return result;
285
286  if(!chlg)
287    return CURLE_LOGIN_DENIED;
288
289  /* Retrieve nonce string from the challenge */
290  if(!sasl_digest_get_key_value(chlg, "nonce=\"", nonce,
291                                sizeof(nonce), '\"')) {
292    Curl_safefree(chlg);
293    return CURLE_LOGIN_DENIED;
294  }
295
296  /* Retrieve realm string from the challenge */
297  if(!sasl_digest_get_key_value(chlg, "realm=\"", realm,
298                                sizeof(realm), '\"')) {
299    /* Challenge does not have a realm, set empty string [RFC2831] page 6 */
300    strcpy(realm, "");
301  }
302
303  /* Retrieve algorithm string from the challenge */
304  if(!sasl_digest_get_key_value(chlg, "algorithm=", alg, sizeof(alg), ',')) {
305    Curl_safefree(chlg);
306    return CURLE_LOGIN_DENIED;
307  }
308
309  Curl_safefree(chlg);
310
311  /* We do not support other algorithms */
312  if(strcmp(alg, "md5-sess") != 0)
313    return CURLE_LOGIN_DENIED;
314
315  /* Generate 64 bits of random data */
316  for(i = 0; i < 8; i++)
317    cnonce[i] = table16[Curl_rand()%16];
318
319  /* So far so good, now calculate A1 and H(A1) according to RFC 2831 */
320  ctxt = Curl_MD5_init(Curl_DIGEST_MD5);
321  if(!ctxt)
322    return CURLE_OUT_OF_MEMORY;
323
324  Curl_MD5_update(ctxt, (const unsigned char *) userp,
325                  curlx_uztoui(strlen(userp)));
326  Curl_MD5_update(ctxt, (const unsigned char *) ":", 1);
327  Curl_MD5_update(ctxt, (const unsigned char *) realm,
328                  curlx_uztoui(strlen(realm)));
329  Curl_MD5_update(ctxt, (const unsigned char *) ":", 1);
330  Curl_MD5_update(ctxt, (const unsigned char *) passwdp,
331                  curlx_uztoui(strlen(passwdp)));
332  Curl_MD5_final(ctxt, digest);
333
334  ctxt = Curl_MD5_init(Curl_DIGEST_MD5);
335  if(!ctxt)
336    return CURLE_OUT_OF_MEMORY;
337
338  Curl_MD5_update(ctxt, (const unsigned char *) digest, MD5_DIGEST_LEN);
339  Curl_MD5_update(ctxt, (const unsigned char *) ":", 1);
340  Curl_MD5_update(ctxt, (const unsigned char *) nonce,
341                  curlx_uztoui(strlen(nonce)));
342  Curl_MD5_update(ctxt, (const unsigned char *) ":", 1);
343  Curl_MD5_update(ctxt, (const unsigned char *) cnonce,
344                  curlx_uztoui(strlen(cnonce)));
345  Curl_MD5_final(ctxt, digest);
346
347  /* Convert calculated 16 octet hex into 32 bytes string */
348  for(i = 0; i < MD5_DIGEST_LEN; i++)
349    snprintf(&HA1_hex[2 * i], 3, "%02x", digest[i]);
350
351  /* Prepare the URL string */
352  snprintf(uri, sizeof(uri), "%s/%s", service, realm);
353
354  /* Calculate H(A2) */
355  ctxt = Curl_MD5_init(Curl_DIGEST_MD5);
356  if(!ctxt)
357    return CURLE_OUT_OF_MEMORY;
358
359  Curl_MD5_update(ctxt, (const unsigned char *) method,
360                  curlx_uztoui(strlen(method)));
361  Curl_MD5_update(ctxt, (const unsigned char *) ":", 1);
362  Curl_MD5_update(ctxt, (const unsigned char *) uri,
363                  curlx_uztoui(strlen(uri)));
364  Curl_MD5_final(ctxt, digest);
365
366  for(i = 0; i < MD5_DIGEST_LEN; i++)
367    snprintf(&HA2_hex[2 * i], 3, "%02x", digest[i]);
368
369  /* Now calculate the response hash */
370  ctxt = Curl_MD5_init(Curl_DIGEST_MD5);
371  if(!ctxt)
372    return CURLE_OUT_OF_MEMORY;
373
374  Curl_MD5_update(ctxt, (const unsigned char *) HA1_hex, 2 * MD5_DIGEST_LEN);
375  Curl_MD5_update(ctxt, (const unsigned char *) ":", 1);
376  Curl_MD5_update(ctxt, (const unsigned char *) nonce,
377                  curlx_uztoui(strlen(nonce)));
378  Curl_MD5_update(ctxt, (const unsigned char *) ":", 1);
379
380  Curl_MD5_update(ctxt, (const unsigned char *) nonceCount,
381                  curlx_uztoui(strlen(nonceCount)));
382  Curl_MD5_update(ctxt, (const unsigned char *) ":", 1);
383  Curl_MD5_update(ctxt, (const unsigned char *) cnonce,
384                  curlx_uztoui(strlen(cnonce)));
385  Curl_MD5_update(ctxt, (const unsigned char *) ":", 1);
386  Curl_MD5_update(ctxt, (const unsigned char *) qop,
387                  curlx_uztoui(strlen(qop)));
388  Curl_MD5_update(ctxt, (const unsigned char *) ":", 1);
389
390  Curl_MD5_update(ctxt, (const unsigned char *) HA2_hex, 2 * MD5_DIGEST_LEN);
391  Curl_MD5_final(ctxt, digest);
392
393  for(i = 0; i < MD5_DIGEST_LEN; i++)
394    snprintf(&resp_hash_hex[2 * i], 3, "%02x", digest[i]);
395
396  snprintf(response, sizeof(response),
397           "username=\"%s\",realm=\"%s\",nonce=\"%s\","
398           "cnonce=\"%s\",nc=\"%s\",digest-uri=\"%s\",response=%s",
399           userp, realm, nonce,
400           cnonce, nonceCount, uri, resp_hash_hex);
401
402  /* Base64 encode the reply */
403  return Curl_base64_encode(data, response, 0, outptr, outlen);
404}
405#endif
406
407#ifdef USE_NTLM
408/*
409 * Curl_sasl_create_ntlm_type1_message()
410 *
411 * This is used to generate an already encoded NTLM type-1 message ready for
412 * sending to the recipient.
413 *
414 * Note: This is a simple wrapper of the NTLM function which means that any
415 * SASL based protocols don't have to include the NTLM functions directly.
416 *
417 * Parameters:
418 *
419 * userp   [in]     - The user name in the format User or Domain\User.
420 * passdwp [in]     - The user's password.
421 * ntlm    [in/out] - The ntlm data struct being used and modified.
422 * outptr  [in/out] - The address where a pointer to newly allocated memory
423 *                    holding the result will be stored upon completion.
424 * outlen  [out]    - The length of the output message.
425 *
426 * Returns CURLE_OK on success.
427 */
428CURLcode Curl_sasl_create_ntlm_type1_message(const char *userp,
429                                             const char *passwdp,
430                                             struct ntlmdata *ntlm,
431                                             char **outptr, size_t *outlen)
432{
433  return Curl_ntlm_create_type1_message(userp, passwdp, ntlm, outptr,
434                                        outlen);
435}
436
437/*
438 * Curl_sasl_create_ntlm_type3_message()
439 *
440 * This is used to generate an already encoded NTLM type-3 message ready for
441 * sending to the recipient.
442 *
443 * Parameters:
444 *
445 * data    [in]     - Pointer to session handle.
446 * header  [in]     - Pointer to the base64 encoded type-2 message buffer.
447 * userp   [in]     - The user name in the format User or Domain\User.
448 * passdwp [in]     - The user's password.
449 * ntlm    [in/out] - The ntlm data struct being used and modified.
450 * outptr  [in/out] - The address where a pointer to newly allocated memory
451 *                    holding the result will be stored upon completion.
452 * outlen  [out]    - The length of the output message.
453 *
454 * Returns CURLE_OK on success.
455 */
456CURLcode Curl_sasl_create_ntlm_type3_message(struct SessionHandle *data,
457                                             const char *header,
458                                             const char *userp,
459                                             const char *passwdp,
460                                             struct ntlmdata *ntlm,
461                                             char **outptr, size_t *outlen)
462{
463  CURLcode result = Curl_ntlm_decode_type2_message(data, header, ntlm);
464
465  if(!result)
466    result = Curl_ntlm_create_type3_message(data, userp, passwdp, ntlm,
467                                            outptr, outlen);
468
469  return result;
470}
471#endif /* USE_NTLM */
472
473/*
474 * Curl_sasl_cleanup()
475 *
476 * This is used to cleanup any libraries or curl modules used by the sasl
477 * functions.
478 *
479 * Parameters:
480 *
481 * conn     [in]     - Pointer to the connection data.
482 * authused [in]     - The authentication mechanism used.
483 */
484void Curl_sasl_cleanup(struct connectdata *conn, unsigned int authused)
485{
486#ifdef USE_NTLM
487  /* Cleanup the ntlm structure */
488  if(authused == SASL_MECH_NTLM) {
489    Curl_ntlm_sspi_cleanup(&conn->ntlm);
490  }
491  (void)conn;
492#else
493  /* Reserved for future use */
494  (void)conn;
495  (void)authused;
496#endif
497}
498