sasl: Post DIGEST-MD5 SSPI code tidy up
[platform/upstream/curl.git] / lib / curl_sasl.c
1 /***************************************************************************
2  *                                  _   _ ____  _
3  *  Project                     ___| | | |  _ \| |
4  *                             / __| | | | |_) | |
5  *                            | (__| |_| |  _ <| |___
6  *                             \___|\___/|_| \_\_____|
7  *
8  * Copyright (C) 2012 - 2014, 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  * RFC6749 OAuth 2.0 Authorization Framework
26  * Draft   LOGIN SASL Mechanism <draft-murchison-sasl-login-00.txt>
27  *
28  ***************************************************************************/
29
30 #include "curl_setup.h"
31
32 #include <curl/curl.h>
33 #include "urldata.h"
34
35 #include "curl_base64.h"
36 #include "curl_md5.h"
37 #include "vtls/vtls.h"
38 #include "curl_hmac.h"
39 #include "curl_ntlm_msgs.h"
40 #include "curl_sasl.h"
41 #include "warnless.h"
42 #include "curl_memory.h"
43
44 #ifdef USE_NSS
45 #include "vtls/nssg.h" /* for Curl_nss_force_init() */
46 #endif
47
48 #define _MPRINTF_REPLACE /* use our functions only */
49 #include <curl/mprintf.h>
50
51 /* The last #include file should be: */
52 #include "memdebug.h"
53
54 #ifndef CURL_DISABLE_CRYPTO_AUTH
55 /* Retrieves the value for a corresponding key from the challenge string
56  * returns TRUE if the key could be found, FALSE if it does not exists
57  */
58 static bool sasl_digest_get_key_value(const char *chlg,
59                                       const char *key,
60                                       char *value,
61                                       size_t max_val_len,
62                                       char end_char)
63 {
64   char *find_pos;
65   size_t i;
66
67   find_pos = strstr(chlg, key);
68   if(!find_pos)
69     return FALSE;
70
71   find_pos += strlen(key);
72
73   for(i = 0; *find_pos && *find_pos != end_char && i < max_val_len - 1; ++i)
74     value[i] = *find_pos++;
75   value[i] = '\0';
76
77   return TRUE;
78 }
79 #endif
80
81 /*
82  * Curl_sasl_create_plain_message()
83  *
84  * This is used to generate an already encoded PLAIN message ready
85  * for sending to the recipient.
86  *
87  * Parameters:
88  *
89  * data    [in]     - The session handle.
90  * userp   [in]     - The user name.
91  * passdwp [in]     - The user's password.
92  * outptr  [in/out] - The address where a pointer to newly allocated memory
93  *                    holding the result will be stored upon completion.
94  * outlen  [out]    - The length of the output message.
95  *
96  * Returns CURLE_OK on success.
97  */
98 CURLcode Curl_sasl_create_plain_message(struct SessionHandle *data,
99                                         const char *userp,
100                                         const char *passwdp,
101                                         char **outptr, size_t *outlen)
102 {
103   CURLcode result;
104   char *plainauth;
105   size_t ulen;
106   size_t plen;
107
108   ulen = strlen(userp);
109   plen = strlen(passwdp);
110
111   plainauth = malloc(2 * ulen + plen + 2);
112   if(!plainauth) {
113     *outlen = 0;
114     *outptr = NULL;
115     return CURLE_OUT_OF_MEMORY;
116   }
117
118   /* Calculate the reply */
119   memcpy(plainauth, userp, ulen);
120   plainauth[ulen] = '\0';
121   memcpy(plainauth + ulen + 1, userp, ulen);
122   plainauth[2 * ulen + 1] = '\0';
123   memcpy(plainauth + 2 * ulen + 2, passwdp, plen);
124
125   /* Base64 encode the reply */
126   result = Curl_base64_encode(data, plainauth, 2 * ulen + plen + 2, outptr,
127                               outlen);
128   Curl_safefree(plainauth);
129   return result;
130 }
131
132 /*
133  * Curl_sasl_create_login_message()
134  *
135  * This is used to generate an already encoded LOGIN message containing the
136  * user name or password ready for sending to the recipient.
137  *
138  * Parameters:
139  *
140  * data    [in]     - The session handle.
141  * valuep  [in]     - The user name or user's password.
142  * outptr  [in/out] - The address where a pointer to newly allocated memory
143  *                    holding the result will be stored upon completion.
144  * outlen  [out]    - The length of the output message.
145  *
146  * Returns CURLE_OK on success.
147  */
148 CURLcode Curl_sasl_create_login_message(struct SessionHandle *data,
149                                         const char *valuep, char **outptr,
150                                         size_t *outlen)
151 {
152   size_t vlen = strlen(valuep);
153
154   if(!vlen) {
155     /* Calculate an empty reply */
156     *outptr = strdup("=");
157     if(*outptr) {
158       *outlen = (size_t) 1;
159       return CURLE_OK;
160     }
161
162     *outlen = 0;
163     return CURLE_OUT_OF_MEMORY;
164   }
165
166   /* Base64 encode the value */
167   return Curl_base64_encode(data, valuep, vlen, outptr, outlen);
168 }
169
170 #ifndef CURL_DISABLE_CRYPTO_AUTH
171  /*
172  * Curl_sasl_decode_cram_md5_message()
173  *
174  * This is used to decode an already encoded CRAM-MD5 challenge message.
175  *
176  * Parameters:
177  *
178  * chlg64  [in]     - Pointer to the base64 encoded challenge message.
179  * outptr  [in/out] - The address where a pointer to newly allocated memory
180  *                    holding the result will be stored upon completion.
181  * outlen  [out]    - The length of the output message.
182  *
183  * Returns CURLE_OK on success.
184  */
185 CURLcode Curl_sasl_decode_cram_md5_message(const char *chlg64, char **outptr,
186                                            size_t *outlen)
187 {
188   CURLcode result = CURLE_OK;
189   size_t chlg64len = strlen(chlg64);
190
191   *outptr = NULL;
192   *outlen = 0;
193
194   /* Decode the challenge if necessary */
195   if(chlg64len && *chlg64 != '=')
196     result = Curl_base64_decode(chlg64, (unsigned char **) outptr, outlen);
197
198     return result;
199  }
200
201  /*
202  * Curl_sasl_create_cram_md5_message()
203  *
204  * This is used to generate an already encoded CRAM-MD5 response message ready
205  * for sending to the recipient.
206  *
207  * Parameters:
208  *
209  * data    [in]     - The session handle.
210  * chlg    [in]     - The challenge.
211  * userp   [in]     - The user name.
212  * passdwp [in]     - The user's password.
213  * outptr  [in/out] - The address where a pointer to newly allocated memory
214  *                    holding the result will be stored upon completion.
215  * outlen  [out]    - The length of the output message.
216  *
217  * Returns CURLE_OK on success.
218  */
219 CURLcode Curl_sasl_create_cram_md5_message(struct SessionHandle *data,
220                                            const char *chlg,
221                                            const char *userp,
222                                            const char *passwdp,
223                                            char **outptr, size_t *outlen)
224 {
225   CURLcode result = CURLE_OK;
226   size_t chlglen = 0;
227   HMAC_context *ctxt;
228   unsigned char digest[MD5_DIGEST_LEN];
229   char *response;
230
231   if(chlg)
232     chlglen = strlen(chlg);
233
234   /* Compute the digest using the password as the key */
235   ctxt = Curl_HMAC_init(Curl_HMAC_MD5,
236                         (const unsigned char *) passwdp,
237                         curlx_uztoui(strlen(passwdp)));
238   if(!ctxt)
239     return CURLE_OUT_OF_MEMORY;
240
241   /* Update the digest with the given challenge */
242   if(chlglen > 0)
243     Curl_HMAC_update(ctxt, (const unsigned char *) chlg,
244                      curlx_uztoui(chlglen));
245
246   /* Finalise the digest */
247   Curl_HMAC_final(ctxt, digest);
248
249   /* Generate the response */
250   response = aprintf(
251       "%s %02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x",
252            userp, digest[0], digest[1], digest[2], digest[3], digest[4],
253            digest[5], digest[6], digest[7], digest[8], digest[9], digest[10],
254            digest[11], digest[12], digest[13], digest[14], digest[15]);
255   if(!response)
256     return CURLE_OUT_OF_MEMORY;
257
258   /* Base64 encode the response */
259   result = Curl_base64_encode(data, response, 0, outptr, outlen);
260
261   Curl_safefree(response);
262
263   return result;
264 }
265
266 #ifndef USE_WINDOWS_SSPI
267 /*
268  * sasl_decode_digest_md5_message()
269  *
270  * This is used internally to decode an already encoded DIGEST-MD5 challenge
271  * message into the seperate attributes.
272  *
273  * Parameters:
274  *
275  * chlg64  [in]     - Pointer to the base64 encoded challenge message.
276  * nonce   [in/out] - The buffer where the nonce will be stored.
277  * nlen    [in]     - The length of the nonce buffer.
278  * realm   [in/out] - The buffer where the realm will be stored.
279  * rlen    [in]     - The length of the realm buffer.
280  * alg     [in/out] - The buffer where the algorithm will be stored.
281  * alen    [in]     - The length of the algorithm buffer.
282  *
283  * Returns CURLE_OK on success.
284  */
285 static CURLcode sasl_decode_digest_md5_message(const char *chlg64,
286                                                char *nonce, size_t nlen,
287                                                char *realm, size_t rlen,
288                                                char *alg, size_t alen)
289 {
290   CURLcode result = CURLE_OK;
291   unsigned char *chlg = NULL;
292   size_t chlglen = 0;
293   size_t chlg64len = strlen(chlg64);
294
295   /* Decode the base-64 encoded challenge message */
296   if(chlg64len && *chlg64 != '=') {
297     result = Curl_base64_decode(chlg64, &chlg, &chlglen);
298     if(result)
299       return result;
300   }
301
302   /* Ensure we have a valid challenge message */
303   if(!chlg)
304     return CURLE_BAD_CONTENT_ENCODING;
305
306   /* Retrieve nonce string from the challenge */
307   if(!sasl_digest_get_key_value((char *)chlg, "nonce=\"", nonce, nlen, '\"')) {
308     Curl_safefree(chlg);
309     return CURLE_BAD_CONTENT_ENCODING;
310   }
311
312   /* Retrieve realm string from the challenge */
313   if(!sasl_digest_get_key_value((char *)chlg, "realm=\"", realm, rlen, '\"')) {
314     /* Challenge does not have a realm, set empty string [RFC2831] page 6 */
315     strcpy(realm, "");
316   }
317
318   /* Retrieve algorithm string from the challenge */
319   if(!sasl_digest_get_key_value((char *)chlg, "algorithm=", alg, alen, ',')) {
320     Curl_safefree(chlg);
321     return CURLE_BAD_CONTENT_ENCODING;
322   }
323
324   Curl_safefree(chlg);
325
326   return CURLE_OK;
327 }
328
329 /*
330  * Curl_sasl_create_digest_md5_message()
331  *
332  * This is used to generate an already encoded DIGEST-MD5 response message
333  * ready for sending to the recipient.
334  *
335  * Parameters:
336  *
337  * data    [in]     - The session handle.
338  * chlg64  [in]     - Pointer to the base64 encoded challenge message.
339  * userp   [in]     - The user name.
340  * passdwp [in]     - The user's password.
341  * service [in]     - The service type such as www, smtp, pop or imap.
342  * outptr  [in/out] - The address where a pointer to newly allocated memory
343  *                    holding the result will be stored upon completion.
344  * outlen  [out]    - The length of the output message.
345  *
346  * Returns CURLE_OK on success.
347  */
348 CURLcode Curl_sasl_create_digest_md5_message(struct SessionHandle *data,
349                                              const char *chlg64,
350                                              const char *userp,
351                                              const char *passwdp,
352                                              const char *service,
353                                              char **outptr, size_t *outlen)
354 {
355 #ifndef DEBUGBUILD
356   static const char table16[] = "0123456789abcdef";
357 #endif
358   CURLcode result = CURLE_OK;
359   size_t i;
360   MD5_context *ctxt;
361   char *response = NULL;
362   unsigned char digest[MD5_DIGEST_LEN];
363   char HA1_hex[2 * MD5_DIGEST_LEN + 1];
364   char HA2_hex[2 * MD5_DIGEST_LEN + 1];
365   char resp_hash_hex[2 * MD5_DIGEST_LEN + 1];
366
367   char nonce[64];
368   char realm[128];
369   char algorithm[64];
370   char nonceCount[] = "00000001";
371   char cnonce[]     = "12345678"; /* will be changed */
372   char method[]     = "AUTHENTICATE";
373   char qop[]        = "auth";
374   char uri[128];
375
376   /* Decode the challange message */
377   result = sasl_decode_digest_md5_message(chlg64, nonce, sizeof(nonce),
378                                           realm, sizeof(realm),
379                                           algorithm, sizeof(algorithm));
380   if(result)
381     return result;
382
383   /* We only support md5 sessions */
384   if(strcmp(algorithm, "md5-sess") != 0)
385      return CURLE_BAD_CONTENT_ENCODING;
386
387 #ifndef DEBUGBUILD
388   /* Generate 64 bits of random data */
389   for(i = 0; i < 8; i++)
390     cnonce[i] = table16[Curl_rand(data)%16];
391 #endif
392
393   /* So far so good, now calculate A1 and H(A1) according to RFC 2831 */
394   ctxt = Curl_MD5_init(Curl_DIGEST_MD5);
395   if(!ctxt)
396     return CURLE_OUT_OF_MEMORY;
397
398   Curl_MD5_update(ctxt, (const unsigned char *) userp,
399                   curlx_uztoui(strlen(userp)));
400   Curl_MD5_update(ctxt, (const unsigned char *) ":", 1);
401   Curl_MD5_update(ctxt, (const unsigned char *) realm,
402                   curlx_uztoui(strlen(realm)));
403   Curl_MD5_update(ctxt, (const unsigned char *) ":", 1);
404   Curl_MD5_update(ctxt, (const unsigned char *) passwdp,
405                   curlx_uztoui(strlen(passwdp)));
406   Curl_MD5_final(ctxt, digest);
407
408   ctxt = Curl_MD5_init(Curl_DIGEST_MD5);
409   if(!ctxt)
410     return CURLE_OUT_OF_MEMORY;
411
412   Curl_MD5_update(ctxt, (const unsigned char *) digest, MD5_DIGEST_LEN);
413   Curl_MD5_update(ctxt, (const unsigned char *) ":", 1);
414   Curl_MD5_update(ctxt, (const unsigned char *) nonce,
415                   curlx_uztoui(strlen(nonce)));
416   Curl_MD5_update(ctxt, (const unsigned char *) ":", 1);
417   Curl_MD5_update(ctxt, (const unsigned char *) cnonce,
418                   curlx_uztoui(strlen(cnonce)));
419   Curl_MD5_final(ctxt, digest);
420
421   /* Convert calculated 16 octet hex into 32 bytes string */
422   for(i = 0; i < MD5_DIGEST_LEN; i++)
423     snprintf(&HA1_hex[2 * i], 3, "%02x", digest[i]);
424
425   /* Prepare the URL string */
426   snprintf(uri, sizeof(uri), "%s/%s", service, realm);
427
428   /* Calculate H(A2) */
429   ctxt = Curl_MD5_init(Curl_DIGEST_MD5);
430   if(!ctxt)
431     return CURLE_OUT_OF_MEMORY;
432
433   Curl_MD5_update(ctxt, (const unsigned char *) method,
434                   curlx_uztoui(strlen(method)));
435   Curl_MD5_update(ctxt, (const unsigned char *) ":", 1);
436   Curl_MD5_update(ctxt, (const unsigned char *) uri,
437                   curlx_uztoui(strlen(uri)));
438   Curl_MD5_final(ctxt, digest);
439
440   for(i = 0; i < MD5_DIGEST_LEN; i++)
441     snprintf(&HA2_hex[2 * i], 3, "%02x", digest[i]);
442
443   /* Now calculate the response hash */
444   ctxt = Curl_MD5_init(Curl_DIGEST_MD5);
445   if(!ctxt)
446     return CURLE_OUT_OF_MEMORY;
447
448   Curl_MD5_update(ctxt, (const unsigned char *) HA1_hex, 2 * MD5_DIGEST_LEN);
449   Curl_MD5_update(ctxt, (const unsigned char *) ":", 1);
450   Curl_MD5_update(ctxt, (const unsigned char *) nonce,
451                   curlx_uztoui(strlen(nonce)));
452   Curl_MD5_update(ctxt, (const unsigned char *) ":", 1);
453
454   Curl_MD5_update(ctxt, (const unsigned char *) nonceCount,
455                   curlx_uztoui(strlen(nonceCount)));
456   Curl_MD5_update(ctxt, (const unsigned char *) ":", 1);
457   Curl_MD5_update(ctxt, (const unsigned char *) cnonce,
458                   curlx_uztoui(strlen(cnonce)));
459   Curl_MD5_update(ctxt, (const unsigned char *) ":", 1);
460   Curl_MD5_update(ctxt, (const unsigned char *) qop,
461                   curlx_uztoui(strlen(qop)));
462   Curl_MD5_update(ctxt, (const unsigned char *) ":", 1);
463
464   Curl_MD5_update(ctxt, (const unsigned char *) HA2_hex, 2 * MD5_DIGEST_LEN);
465   Curl_MD5_final(ctxt, digest);
466
467   for(i = 0; i < MD5_DIGEST_LEN; i++)
468     snprintf(&resp_hash_hex[2 * i], 3, "%02x", digest[i]);
469
470   /* Generate the response */
471   response = aprintf("username=\"%s\",realm=\"%s\",nonce=\"%s\","
472                      "cnonce=\"%s\",nc=\"%s\",digest-uri=\"%s\",response=%s",
473                      userp, realm, nonce,
474                      cnonce, nonceCount, uri, resp_hash_hex);
475   if(!response)
476     return CURLE_OUT_OF_MEMORY;
477
478   /* Base64 encode the response */
479   result = Curl_base64_encode(data, response, 0, outptr, outlen);
480
481   Curl_safefree(response);
482
483   return result;
484 }
485 #endif  /* USE_WINDOWS_SSPI */
486
487 #endif  /* CURL_DISABLE_CRYPTO_AUTH */
488
489 #ifdef USE_NTLM
490 /*
491  * Curl_sasl_create_ntlm_type1_message()
492  *
493  * This is used to generate an already encoded NTLM type-1 message ready for
494  * sending to the recipient.
495  *
496  * Note: This is a simple wrapper of the NTLM function which means that any
497  * SASL based protocols don't have to include the NTLM functions directly.
498  *
499  * Parameters:
500  *
501  * userp   [in]     - The user name in the format User or Domain\User.
502  * passdwp [in]     - The user's password.
503  * ntlm    [in/out] - The ntlm data struct being used and modified.
504  * outptr  [in/out] - The address where a pointer to newly allocated memory
505  *                    holding the result will be stored upon completion.
506  * outlen  [out]    - The length of the output message.
507  *
508  * Returns CURLE_OK on success.
509  */
510 CURLcode Curl_sasl_create_ntlm_type1_message(const char *userp,
511                                              const char *passwdp,
512                                              struct ntlmdata *ntlm,
513                                              char **outptr, size_t *outlen)
514 {
515   return Curl_ntlm_create_type1_message(userp, passwdp, ntlm, outptr, outlen);
516 }
517
518 /*
519  * Curl_sasl_decode_ntlm_type2_message()
520  *
521  * This is used to decode an already encoded NTLM type-2 message.
522  *
523  * Parameters:
524  *
525  * data     [in]     - Pointer to session handle.
526  * type2msg [in]     - Pointer to the base64 encoded type-2 message.
527  * ntlm     [in/out] - The ntlm data struct being used and modified.
528  *
529  * Returns CURLE_OK on success.
530  */
531 CURLcode Curl_sasl_decode_ntlm_type2_message(struct SessionHandle *data,
532                                              const char *type2msg,
533                                              struct ntlmdata *ntlm)
534 {
535 #ifdef USE_NSS
536   CURLcode result;
537
538   /* make sure the crypto backend is initialized */
539   result = Curl_nss_force_init(data);
540   if(result)
541     return result;
542 #endif
543
544   return Curl_ntlm_decode_type2_message(data, type2msg, ntlm);
545 }
546
547 /*
548  * Curl_sasl_create_ntlm_type3_message()
549  *
550  * This is used to generate an already encoded NTLM type-3 message ready for
551  * sending to the recipient.
552  *
553  * Parameters:
554  *
555  * data    [in]     - Pointer to session handle.
556  * userp   [in]     - The user name in the format User or Domain\User.
557  * passdwp [in]     - The user's password.
558  * ntlm    [in/out] - The ntlm data struct being used and modified.
559  * outptr  [in/out] - The address where a pointer to newly allocated memory
560  *                    holding the result will be stored upon completion.
561  * outlen  [out]    - The length of the output message.
562  *
563  * Returns CURLE_OK on success.
564  */
565 CURLcode Curl_sasl_create_ntlm_type3_message(struct SessionHandle *data,
566                                              const char *userp,
567                                              const char *passwdp,
568                                              struct ntlmdata *ntlm,
569                                              char **outptr, size_t *outlen)
570 {
571   return Curl_ntlm_create_type3_message(data, userp, passwdp, ntlm, outptr,
572                                         outlen);
573 }
574 #endif /* USE_NTLM */
575
576 /*
577  * Curl_sasl_create_xoauth2_message()
578  *
579  * This is used to generate an already encoded OAuth 2.0 message ready for
580  * sending to the recipient.
581  *
582  * Parameters:
583  *
584  * data    [in]     - The session handle.
585  * user    [in]     - The user name.
586  * bearer  [in]     - The bearer token.
587  * outptr  [in/out] - The address where a pointer to newly allocated memory
588  *                    holding the result will be stored upon completion.
589  * outlen  [out]    - The length of the output message.
590  *
591  * Returns CURLE_OK on success.
592  */
593 CURLcode Curl_sasl_create_xoauth2_message(struct SessionHandle *data,
594                                           const char *user,
595                                           const char *bearer,
596                                           char **outptr, size_t *outlen)
597 {
598   CURLcode result = CURLE_OK;
599   char *xoauth = NULL;
600
601   /* Generate the message */
602   xoauth = aprintf("user=%s\1auth=Bearer %s\1\1", user, bearer);
603   if(!xoauth)
604     return CURLE_OUT_OF_MEMORY;
605
606   /* Base64 encode the reply */
607   result = Curl_base64_encode(data, xoauth, strlen(xoauth), outptr, outlen);
608
609   Curl_safefree(xoauth);
610
611   return result;
612 }
613
614 /*
615  * Curl_sasl_cleanup()
616  *
617  * This is used to cleanup any libraries or curl modules used by the sasl
618  * functions.
619  *
620  * Parameters:
621  *
622  * conn     [in]     - Pointer to the connection data.
623  * authused [in]     - The authentication mechanism used.
624  */
625 void Curl_sasl_cleanup(struct connectdata *conn, unsigned int authused)
626 {
627 #ifdef USE_NTLM
628   /* Cleanup the ntlm structure */
629   if(authused == SASL_MECH_NTLM) {
630     Curl_ntlm_sspi_cleanup(&conn->ntlm);
631   }
632   (void)conn;
633 #else
634   /* Reserved for future use */
635   (void)conn;
636   (void)authused;
637 #endif
638 }