1 /***************************************************************************
3 * Project ___| | | | _ \| |
5 * | (__| |_| | _ <| |___
6 * \___|\___/|_| \_\_____|
8 * Copyright (C) 1998 - 2007, 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.2 2007-03-15 19:22:13 andy Exp $
22 ***************************************************************************/
25 #if !defined(CURL_DISABLE_HTTP) && !defined(CURL_DISABLE_CRYPTO_AUTH)
26 /* -- WIN32 approved -- */
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 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
, "%31[^=]=\"%127[^\"]\"",
103 /* try the same scan but without quotes around the content but don't
104 include the possibly trailing comma */
105 (2 == sscanf(header
, "%31[^=]=%127[^,]",
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 */
184 /* allow the list to be comma-separated */
187 /* We had a nonce since before, and we got another one now without
188 'stale=true'. This means we provided bad credentials in the previous
190 if(before
&& !d
->stale
)
191 return CURLDIGEST_BAD
;
193 /* We got this header without a nonce, that's a bad Digest line! */
195 return CURLDIGEST_BAD
;
198 /* else not a digest, get out */
199 return CURLDIGEST_NONE
;
201 return CURLDIGEST_FINE
;
204 /* convert md5 chunk to RFC2617 (section 3.1.3) -suitable ascii string*/
205 static void md5_to_ascii(unsigned char *source
, /* 16 bytes */
206 unsigned char *dest
) /* 33 bytes */
210 snprintf((char *)&dest
[i
*2], 3, "%02x", source
[i
]);
213 CURLcode
Curl_output_digest(struct connectdata
*conn
,
215 unsigned char *request
,
216 unsigned char *uripath
)
218 /* We have a Digest setup for this, use it! Now, to get all the details for
219 this sorted out, I must urge you dear friend to read up on the RFC2617
221 unsigned char md5buf
[16]; /* 16 bytes/128 bits */
222 unsigned char request_digest
[33];
223 unsigned char *md5this
;
225 unsigned char ha2
[33];/* 32 digits and 1 zero byte */
236 struct SessionHandle
*data
= conn
->data
;
237 struct digestdata
*d
;
238 #ifdef CURL_DOES_CONVERSIONS
240 /* The CURL_OUTPUT_DIGEST_CONV macro below is for non-ASCII machines.
241 It converts digest text to ASCII so the MD5 will be correct for
242 what ultimately goes over the network.
244 #define CURL_OUTPUT_DIGEST_CONV(a, b) \
245 rc = Curl_convert_to_network(a, (char *)b, strlen((const char*)b)); \
246 if (rc != CURLE_OK) { \
251 #define CURL_OUTPUT_DIGEST_CONV(a, b)
252 #endif /* CURL_DOES_CONVERSIONS */
255 d
= &data
->state
.proxydigest
;
256 allocuserpwd
= &conn
->allocptr
.proxyuserpwd
;
257 userp
= conn
->proxyuser
;
258 passwdp
= conn
->proxypasswd
;
259 authp
= &data
->state
.authproxy
;
262 d
= &data
->state
.digest
;
263 allocuserpwd
= &conn
->allocptr
.userpwd
;
265 passwdp
= conn
->passwd
;
266 authp
= &data
->state
.authhost
;
269 /* not set means empty */
286 /* Generate a cnonce */
288 snprintf(cnoncebuf
, sizeof(cnoncebuf
), "%06ld", now
.tv_sec
);
289 if(Curl_base64_encode(data
, cnoncebuf
, strlen(cnoncebuf
), &cnonce
))
292 return CURLE_OUT_OF_MEMORY
;
296 if the algorithm is "MD5" or unspecified (which then defaults to MD5):
298 A1 = unq(username-value) ":" unq(realm-value) ":" passwd
300 if the algorithm is "MD5-sess" then:
302 A1 = H( unq(username-value) ":" unq(realm-value) ":" passwd )
303 ":" unq(nonce-value) ":" unq(cnonce-value)
306 md5this
= (unsigned char *)
307 aprintf("%s:%s:%s", userp
, d
->realm
, passwdp
);
309 return CURLE_OUT_OF_MEMORY
;
311 CURL_OUTPUT_DIGEST_CONV(data
, md5this
); /* convert on non-ASCII machines */
312 Curl_md5it(md5buf
, md5this
);
313 free(md5this
); /* free this again */
315 ha1
= (unsigned char *)malloc(33); /* 32 digits and 1 zero byte */
317 return CURLE_OUT_OF_MEMORY
;
319 md5_to_ascii(md5buf
, ha1
);
321 if(d
->algo
== CURLDIGESTALGO_MD5SESS
) {
322 /* nonce and cnonce are OUTSIDE the hash */
323 tmp
= aprintf("%s:%s:%s", ha1
, d
->nonce
, d
->cnonce
);
325 return CURLE_OUT_OF_MEMORY
;
326 CURL_OUTPUT_DIGEST_CONV(data
, tmp
); /* convert on non-ASCII machines */
327 Curl_md5it(md5buf
, (unsigned char *)tmp
);
328 free(tmp
); /* free this again */
329 md5_to_ascii(md5buf
, ha1
);
333 If the "qop" directive's value is "auth" or is unspecified, then A2 is:
335 A2 = Method ":" digest-uri-value
337 If the "qop" value is "auth-int", then A2 is:
339 A2 = Method ":" digest-uri-value ":" H(entity-body)
341 (The "Method" value is the HTTP request method as specified in section
345 md5this
= (unsigned char *)aprintf("%s:%s", request
, uripath
);
348 return CURLE_OUT_OF_MEMORY
;
351 if (d
->qop
&& strequal(d
->qop
, "auth-int")) {
352 /* We don't support auth-int at the moment. I can't see a easy way to get
354 /* TODO: Append H(entity-body)*/
356 CURL_OUTPUT_DIGEST_CONV(data
, md5this
); /* convert on non-ASCII machines */
357 Curl_md5it(md5buf
, md5this
);
358 free(md5this
); /* free this again */
359 md5_to_ascii(md5buf
, ha2
);
362 md5this
= (unsigned char *)aprintf("%s:%s:%08x:%s:%s:%s",
371 md5this
= (unsigned char *)aprintf("%s:%s:%s",
378 return CURLE_OUT_OF_MEMORY
;
380 CURL_OUTPUT_DIGEST_CONV(data
, md5this
); /* convert on non-ASCII machines */
381 Curl_md5it(md5buf
, md5this
);
382 free(md5this
); /* free this again */
383 md5_to_ascii(md5buf
, request_digest
);
385 /* for test case 64 (snooped from a Mozilla 1.3a request)
387 Authorization: Digest username="testuser", realm="testrealm", \
388 nonce="1053604145", uri="/64", response="c55f7f30d83d774a3d2dcacf725abaca"
391 Curl_safefree(*allocuserpwd
);
395 aprintf( "%sAuthorization: Digest "
408 uripath
, /* this is the PATH part of the URL */
414 if(strequal(d
->qop
, "auth"))
415 d
->nc
++; /* The nc (from RFC) has to be a 8 hex digit number 0 padded
416 which tells to the server how many times you are using the
417 same nonce in the qop=auth mode. */
421 aprintf( "%sAuthorization: Digest "
431 uripath
, /* this is the PATH part of the URL */
435 return CURLE_OUT_OF_MEMORY
;
437 /* Add optional fields */
440 tmp
= aprintf("%s, opaque=\"%s\"", *allocuserpwd
, d
->opaque
);
442 return CURLE_OUT_OF_MEMORY
;
448 /* append algorithm */
449 tmp
= aprintf("%s, algorithm=\"%s\"", *allocuserpwd
, d
->algorithm
);
451 return CURLE_OUT_OF_MEMORY
;
456 /* append CRLF to the userpwd header */
457 tmp
= (char*) realloc(*allocuserpwd
, strlen(*allocuserpwd
) + 3 + 1);
459 return CURLE_OUT_OF_MEMORY
;
466 void Curl_digest_cleanup_one(struct digestdata
*d
)
493 d
->algo
= CURLDIGESTALGO_MD5
; /* default algorithm */
494 d
->stale
= FALSE
; /* default means normal, not stale */
498 void Curl_digest_cleanup(struct SessionHandle
*data
)
500 Curl_digest_cleanup_one(&data
->state
.digest
);
501 Curl_digest_cleanup_one(&data
->state
.proxydigest
);