1 /***************************************************************************
3 * Project ___| | | | _ \| |
5 * | (__| |_| | _ <| |___
6 * \___|\___/|_| \_\_____|
8 * Copyright (C) 1998 - 2008, Daniel Stenberg, <daniel@haxx.se>, et al.
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.
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.
18 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
19 * KIND, either express or implied.
21 * $Id: http_digest.c,v 1.1.1.1 2008-09-23 16:32:05 hoffman Exp $
22 ***************************************************************************/
25 #if !defined(CURL_DISABLE_HTTP) && !defined(CURL_DISABLE_CRYPTO_AUTH)
26 /* -- WIN32 approved -- */
36 #include "curl_base64.h"
38 #include "http_digest.h"
40 #include "url.h" /* for Curl_safefree() */
42 #include "easyif.h" /* included for Curl_convert_... prototypes */
44 #define _MPRINTF_REPLACE /* use our functions only */
45 #include <curl/mprintf.h>
47 /* The last #include file should be: */
50 /* Test example headers:
52 WWW-Authenticate: Digest realm="testrealm", nonce="1053604598"
53 Proxy-Authenticate: Digest realm="testrealm", nonce="1053604598"
57 CURLdigest
Curl_input_digest(struct connectdata
*conn
,
59 const char *header
) /* rest of the *-authenticate:
65 bool foundAuth
= FALSE
;
66 bool foundAuthInt
= FALSE
;
67 struct SessionHandle
*data
=conn
->data
;
68 bool before
= FALSE
; /* got a nonce before */
72 d
= &data
->state
.proxydigest
;
75 d
= &data
->state
.digest
;
78 /* skip initial whitespaces */
79 while(*header
&& ISSPACE(*header
))
82 if(checkprefix("Digest", header
)) {
83 header
+= strlen("Digest");
85 /* If we already have received a nonce, keep that in mind */
89 /* clear off any former leftovers and init to defaults */
90 Curl_digest_cleanup_one(d
);
97 while(*header
&& ISSPACE(*header
))
100 /* how big can these strings be? */
101 if((2 == sscanf(header
, "%255[^=]=\"%1023[^\"]\"",
103 /* try the same scan but without quotes around the content but don't
104 include the possibly trailing comma, newline or carriage return */
105 (2 == sscanf(header
, "%255[^=]=%1023[^\r\n,]",
107 if(strequal(value
, "nonce")) {
108 d
->nonce
= strdup(content
);
110 return CURLDIGEST_NOMEM
;
112 else if(strequal(value
, "stale")) {
113 if(strequal(content
, "true")) {
115 d
->nc
= 1; /* we make a new nonce now */
118 else if(strequal(value
, "realm")) {
119 d
->realm
= strdup(content
);
121 return CURLDIGEST_NOMEM
;
123 else if(strequal(value
, "opaque")) {
124 d
->opaque
= strdup(content
);
126 return CURLDIGEST_NOMEM
;
128 else if(strequal(value
, "qop")) {
130 /* tokenize the list and choose auth if possible, use a temporary
131 clone of the buffer since strtok_r() ruins it */
132 tmp
= strdup(content
);
134 return CURLDIGEST_NOMEM
;
135 token
= strtok_r(tmp
, ",", &tok_buf
);
136 while(token
!= NULL
) {
137 if(strequal(token
, "auth")) {
140 else if(strequal(token
, "auth-int")) {
143 token
= strtok_r(NULL
, ",", &tok_buf
);
146 /*select only auth o auth-int. Otherwise, ignore*/
148 d
->qop
= strdup("auth");
150 return CURLDIGEST_NOMEM
;
152 else if(foundAuthInt
) {
153 d
->qop
= strdup("auth-int");
155 return CURLDIGEST_NOMEM
;
158 else if(strequal(value
, "algorithm")) {
159 d
->algorithm
= strdup(content
);
161 return CURLDIGEST_NOMEM
;
162 if(strequal(content
, "MD5-sess"))
163 d
->algo
= CURLDIGESTALGO_MD5SESS
;
164 else if(strequal(content
, "MD5"))
165 d
->algo
= CURLDIGESTALGO_MD5
;
167 return CURLDIGEST_BADALGO
;
170 /* unknown specifier, ignore it! */
172 totlen
= strlen(value
)+strlen(content
)+1;
174 if(header
[strlen(value
)+1] == '\"')
175 /* the contents were within quotes, then add 2 for them to the
180 break; /* we're done here */
183 /* pass all additional spaces here */
184 while(*header
&& ISSPACE(*header
))
187 /* allow the list to be comma-separated */
190 /* We had a nonce since before, and we got another one now without
191 'stale=true'. This means we provided bad credentials in the previous
193 if(before
&& !d
->stale
)
194 return CURLDIGEST_BAD
;
196 /* We got this header without a nonce, that's a bad Digest line! */
198 return CURLDIGEST_BAD
;
201 /* else not a digest, get out */
202 return CURLDIGEST_NONE
;
204 return CURLDIGEST_FINE
;
207 /* convert md5 chunk to RFC2617 (section 3.1.3) -suitable ascii string*/
208 static void md5_to_ascii(unsigned char *source
, /* 16 bytes */
209 unsigned char *dest
) /* 33 bytes */
213 snprintf((char *)&dest
[i
*2], 3, "%02x", source
[i
]);
216 CURLcode
Curl_output_digest(struct connectdata
*conn
,
218 const unsigned char *request
,
219 const unsigned char *uripath
)
221 /* We have a Digest setup for this, use it! Now, to get all the details for
222 this sorted out, I must urge you dear friend to read up on the RFC2617
224 unsigned char md5buf
[16]; /* 16 bytes/128 bits */
225 unsigned char request_digest
[33];
226 unsigned char *md5this
;
228 unsigned char ha2
[33];/* 32 digits and 1 zero byte */
239 struct SessionHandle
*data
= conn
->data
;
240 struct digestdata
*d
;
241 #ifdef CURL_DOES_CONVERSIONS
243 /* The CURL_OUTPUT_DIGEST_CONV macro below is for non-ASCII machines.
244 It converts digest text to ASCII so the MD5 will be correct for
245 what ultimately goes over the network.
247 #define CURL_OUTPUT_DIGEST_CONV(a, b) \
248 rc = Curl_convert_to_network(a, (char *)b, strlen((const char*)b)); \
249 if(rc != CURLE_OK) { \
254 #define CURL_OUTPUT_DIGEST_CONV(a, b)
255 #endif /* CURL_DOES_CONVERSIONS */
258 d
= &data
->state
.proxydigest
;
259 allocuserpwd
= &conn
->allocptr
.proxyuserpwd
;
260 userp
= conn
->proxyuser
;
261 passwdp
= conn
->proxypasswd
;
262 authp
= &data
->state
.authproxy
;
265 d
= &data
->state
.digest
;
266 allocuserpwd
= &conn
->allocptr
.userpwd
;
268 passwdp
= conn
->passwd
;
269 authp
= &data
->state
.authhost
;
273 Curl_safefree(*allocuserpwd
);
274 *allocuserpwd
= NULL
;
277 /* not set means empty */
294 /* Generate a cnonce */
296 snprintf(cnoncebuf
, sizeof(cnoncebuf
), "%06ld", now
.tv_sec
);
297 if(Curl_base64_encode(data
, cnoncebuf
, strlen(cnoncebuf
), &cnonce
))
300 return CURLE_OUT_OF_MEMORY
;
304 if the algorithm is "MD5" or unspecified (which then defaults to MD5):
306 A1 = unq(username-value) ":" unq(realm-value) ":" passwd
308 if the algorithm is "MD5-sess" then:
310 A1 = H( unq(username-value) ":" unq(realm-value) ":" passwd )
311 ":" unq(nonce-value) ":" unq(cnonce-value)
314 md5this
= (unsigned char *)
315 aprintf("%s:%s:%s", userp
, d
->realm
, passwdp
);
317 return CURLE_OUT_OF_MEMORY
;
319 CURL_OUTPUT_DIGEST_CONV(data
, md5this
); /* convert on non-ASCII machines */
320 Curl_md5it(md5buf
, md5this
);
321 free(md5this
); /* free this again */
323 ha1
= (unsigned char *)malloc(33); /* 32 digits and 1 zero byte */
325 return CURLE_OUT_OF_MEMORY
;
327 md5_to_ascii(md5buf
, ha1
);
329 if(d
->algo
== CURLDIGESTALGO_MD5SESS
) {
330 /* nonce and cnonce are OUTSIDE the hash */
331 tmp
= aprintf("%s:%s:%s", ha1
, d
->nonce
, d
->cnonce
);
333 return CURLE_OUT_OF_MEMORY
;
334 CURL_OUTPUT_DIGEST_CONV(data
, tmp
); /* convert on non-ASCII machines */
335 Curl_md5it(md5buf
, (unsigned char *)tmp
);
336 free(tmp
); /* free this again */
337 md5_to_ascii(md5buf
, ha1
);
341 If the "qop" directive's value is "auth" or is unspecified, then A2 is:
343 A2 = Method ":" digest-uri-value
345 If the "qop" value is "auth-int", then A2 is:
347 A2 = Method ":" digest-uri-value ":" H(entity-body)
349 (The "Method" value is the HTTP request method as specified in section
353 md5this
= (unsigned char *)aprintf("%s:%s", request
, uripath
);
356 return CURLE_OUT_OF_MEMORY
;
359 if(d
->qop
&& strequal(d
->qop
, "auth-int")) {
360 /* We don't support auth-int at the moment. I can't see a easy way to get
362 /* TODO: Append H(entity-body)*/
364 CURL_OUTPUT_DIGEST_CONV(data
, md5this
); /* convert on non-ASCII machines */
365 Curl_md5it(md5buf
, md5this
);
366 free(md5this
); /* free this again */
367 md5_to_ascii(md5buf
, ha2
);
370 md5this
= (unsigned char *)aprintf("%s:%s:%08x:%s:%s:%s",
379 md5this
= (unsigned char *)aprintf("%s:%s:%s",
386 return CURLE_OUT_OF_MEMORY
;
388 CURL_OUTPUT_DIGEST_CONV(data
, md5this
); /* convert on non-ASCII machines */
389 Curl_md5it(md5buf
, md5this
);
390 free(md5this
); /* free this again */
391 md5_to_ascii(md5buf
, request_digest
);
393 /* for test case 64 (snooped from a Mozilla 1.3a request)
395 Authorization: Digest username="testuser", realm="testrealm", \
396 nonce="1053604145", uri="/64", response="c55f7f30d83d774a3d2dcacf725abaca"
401 aprintf( "%sAuthorization: Digest "
414 uripath
, /* this is the PATH part of the URL */
420 if(strequal(d
->qop
, "auth"))
421 d
->nc
++; /* The nc (from RFC) has to be a 8 hex digit number 0 padded
422 which tells to the server how many times you are using the
423 same nonce in the qop=auth mode. */
427 aprintf( "%sAuthorization: Digest "
437 uripath
, /* this is the PATH part of the URL */
441 return CURLE_OUT_OF_MEMORY
;
443 /* Add optional fields */
446 tmp
= aprintf("%s, opaque=\"%s\"", *allocuserpwd
, d
->opaque
);
448 return CURLE_OUT_OF_MEMORY
;
454 /* append algorithm */
455 tmp
= aprintf("%s, algorithm=\"%s\"", *allocuserpwd
, d
->algorithm
);
457 return CURLE_OUT_OF_MEMORY
;
462 /* append CRLF to the userpwd header */
463 tmp
= (char*) realloc(*allocuserpwd
, strlen(*allocuserpwd
) + 3 + 1);
465 return CURLE_OUT_OF_MEMORY
;
472 void Curl_digest_cleanup_one(struct digestdata
*d
)
499 d
->algo
= CURLDIGESTALGO_MD5
; /* default algorithm */
500 d
->stale
= FALSE
; /* default means normal, not stale */
504 void Curl_digest_cleanup(struct SessionHandle
*data
)
506 Curl_digest_cleanup_one(&data
->state
.digest
);
507 Curl_digest_cleanup_one(&data
->state
.proxydigest
);