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