digest: handle quotes in the path

- The 'uri' component needs to be escaped as well
- Rewrote the quote function to use dynbuf
- Build the digest at least partly with dynbuf
- Use goto as a general error mechanism
- Make test 64 use a double quote in the URL

Closes #20295
This commit is contained in:
Daniel Stenberg 2026-01-13 15:31:06 +01:00
parent 2949faa93c
commit 134fb66121
No known key found for this signature in database
GPG key ID: 5CC908FDB71E12C2
2 changed files with 146 additions and 145 deletions

View file

@ -148,35 +148,26 @@ static void auth_digest_sha256_to_ascii(unsigned char *source, /* 32 bytes */
}
/* Perform quoted-string escaping as described in RFC2616 and its errata */
static char *auth_digest_string_quoted(const char *source)
static char *auth_digest_string_quoted(const char *s)
{
char *dest;
const char *s = source;
size_t n = 1; /* null-terminator */
/* Calculate size needed */
struct dynbuf out;
curlx_dyn_init(&out, 2048);
if(!*s) /* for zero length input, make sure we return an empty string */
return curlx_strdup("");
while(*s) {
++n;
CURLcode result;
if(*s == '"' || *s == '\\') {
++n;
result = curlx_dyn_addn(&out, "\\", 1);
if(!result)
result = curlx_dyn_addn(&out, s, 1);
}
++s;
else
result = curlx_dyn_addn(&out, s, 1);
if(result)
return NULL;
s++;
}
dest = curlx_malloc(n);
if(dest) {
char *d = dest;
s = source;
while(*s) {
if(*s == '"' || *s == '\\') {
*d++ = '\\';
}
*d++ = *s++;
}
*d = '\0';
}
return dest;
return curlx_dyn_ptr(&out);
}
/* Retrieves the value for a corresponding key from the challenge string
@ -672,16 +663,15 @@ CURLcode Curl_auth_decode_digest_http_message(const char *chlg,
* Returns CURLE_OK on success.
*/
static CURLcode auth_create_digest_http_message(
struct Curl_easy *data,
const char *userp,
const char *passwdp,
const unsigned char *request,
const unsigned char *uripath,
struct digestdata *digest,
char **outptr, size_t *outlen,
void (*convert_to_ascii)(unsigned char *, unsigned char *),
CURLcode (*hash)(unsigned char *, const unsigned char *,
const size_t))
struct Curl_easy *data,
const char *userp,
const char *passwdp,
const unsigned char *request,
const unsigned char *uripath,
struct digestdata *digest,
char **outptr, size_t *outlen,
void (*convert_to_ascii)(unsigned char *, unsigned char *),
CURLcode (*hash)(unsigned char *, const unsigned char *, const size_t))
{
CURLcode result;
unsigned char hashbuf[32]; /* 32 bytes/256 bits */
@ -691,12 +681,15 @@ static CURLcode auth_create_digest_http_message(
char userh[65];
char *cnonce = NULL;
size_t cnonce_sz = 0;
char *userp_quoted;
char *realm_quoted;
char *nonce_quoted;
char *response = NULL;
char *userp_quoted = NULL;
char *realm_quoted = NULL;
char *nonce_quoted = NULL;
char *hashthis = NULL;
char *tmp = NULL;
char *uri_quoted = NULL;
struct dynbuf response;
*outptr = NULL;
curlx_dyn_init(&response, 4096); /* arbitrary max */
memset(hashbuf, 0, sizeof(hashbuf));
if(!digest->nc)
@ -710,27 +703,27 @@ static CURLcode auth_create_digest_http_message(
#endif
(unsigned char *)cnoncebuf,
sizeof(cnoncebuf));
if(!result)
result = curlx_base64_encode((uint8_t *)cnoncebuf, sizeof(cnoncebuf),
&cnonce, &cnonce_sz);
if(result)
return result;
result = curlx_base64_encode((uint8_t *)cnoncebuf, sizeof(cnoncebuf),
&cnonce, &cnonce_sz);
if(result)
return result;
goto oom;
digest->cnonce = cnonce;
}
if(digest->userhash) {
hashthis = curl_maprintf("%s:%s", userp,
digest->realm ? digest->realm : "");
if(!hashthis)
return CURLE_OUT_OF_MEMORY;
char *hasht = curl_maprintf("%s:%s", userp,
digest->realm ? digest->realm : "");
if(!hasht) {
result = CURLE_OUT_OF_MEMORY;
goto oom;
}
result = hash(hashbuf, (unsigned char *)hashthis, strlen(hashthis));
curlx_free(hashthis);
result = hash(hashbuf, (unsigned char *)hasht, strlen(hasht));
curlx_free(hasht);
if(result)
return result;
goto oom;
convert_to_ascii(hashbuf, (unsigned char *)userh);
}
@ -745,27 +738,31 @@ static CURLcode auth_create_digest_http_message(
unq(nonce-value) ":" unq(cnonce-value)
*/
hashthis = curl_maprintf("%s:%s:%s", userp,
digest->realm ? digest->realm : "", passwdp);
if(!hashthis)
return CURLE_OUT_OF_MEMORY;
hashthis = curl_maprintf("%s:%s:%s", userp, digest->realm ?
digest->realm : "", passwdp);
if(!hashthis) {
result = CURLE_OUT_OF_MEMORY;
goto oom;
}
result = hash(hashbuf, (unsigned char *)hashthis, strlen(hashthis));
curlx_free(hashthis);
if(result)
return result;
goto oom;
convert_to_ascii(hashbuf, ha1);
if(digest->algo & SESSION_ALGO) {
/* nonce and cnonce are OUTSIDE the hash */
tmp = curl_maprintf("%s:%s:%s", ha1, digest->nonce, digest->cnonce);
if(!tmp)
return CURLE_OUT_OF_MEMORY;
char *tmp = curl_maprintf("%s:%s:%s", ha1, digest->nonce, digest->cnonce);
if(!tmp) {
result = CURLE_OUT_OF_MEMORY;
goto oom;
}
result = hash(hashbuf, (unsigned char *)tmp, strlen(tmp));
curlx_free(tmp);
if(result)
return result;
goto oom;
convert_to_ascii(hashbuf, ha1);
}
@ -782,9 +779,17 @@ static CURLcode auth_create_digest_http_message(
5.1.1 of RFC 2616)
*/
uri_quoted = auth_digest_string_quoted((const char *)uripath);
if(!uri_quoted) {
result = CURLE_OUT_OF_MEMORY;
goto oom;
}
hashthis = curl_maprintf("%s:%s", request, uripath);
if(!hashthis)
return CURLE_OUT_OF_MEMORY;
if(!hashthis) {
result = CURLE_OUT_OF_MEMORY;
goto oom;
}
if(digest->qop && curl_strequal(digest->qop, "auth-int")) {
/* We do not support auth-int for PUT or POST */
@ -792,41 +797,40 @@ static CURLcode auth_create_digest_http_message(
char *hashthis2;
result = hash(hashbuf, (const unsigned char *)"", 0);
if(result) {
curlx_free(hashthis);
return result;
}
if(result)
goto oom;
convert_to_ascii(hashbuf, (unsigned char *)hashed);
hashthis2 = curl_maprintf("%s:%s", hashthis, hashed);
curlx_free(hashthis);
hashthis = hashthis2;
if(!hashthis) {
result = CURLE_OUT_OF_MEMORY;
goto oom;
}
}
if(!hashthis)
return CURLE_OUT_OF_MEMORY;
result = hash(hashbuf, (unsigned char *)hashthis, strlen(hashthis));
curlx_free(hashthis);
if(result)
return result;
goto oom;
convert_to_ascii(hashbuf, ha2);
if(digest->qop) {
if(digest->qop)
hashthis = curl_maprintf("%s:%s:%08x:%s:%s:%s", ha1, digest->nonce,
digest->nc, digest->cnonce, digest->qop, ha2);
}
else {
else
hashthis = curl_maprintf("%s:%s:%s", ha1, digest->nonce, ha2);
}
if(!hashthis)
return CURLE_OUT_OF_MEMORY;
if(!hashthis) {
result = CURLE_OUT_OF_MEMORY;
goto oom;
}
result = hash(hashbuf, (unsigned char *)hashthis, strlen(hashthis));
curlx_free(hashthis);
if(result)
return result;
goto oom;
convert_to_ascii(hashbuf, request_digest);
/* For test case 64 (snooped from a Mozilla 1.3a request)
@ -843,8 +847,10 @@ static CURLcode auth_create_digest_http_message(
characters.
*/
userp_quoted = auth_digest_string_quoted(digest->userhash ? userh : userp);
if(!userp_quoted)
return CURLE_OUT_OF_MEMORY;
if(!userp_quoted) {
result = CURLE_OUT_OF_MEMORY;
goto oom;
}
if(digest->realm)
realm_quoted = auth_digest_string_quoted(digest->realm);
else {
@ -853,98 +859,93 @@ static CURLcode auth_create_digest_http_message(
realm_quoted[0] = 0;
}
if(!realm_quoted) {
curlx_free(userp_quoted);
return CURLE_OUT_OF_MEMORY;
result = CURLE_OUT_OF_MEMORY;
goto oom;
}
nonce_quoted = auth_digest_string_quoted(digest->nonce);
if(!nonce_quoted) {
curlx_free(realm_quoted);
curlx_free(userp_quoted);
return CURLE_OUT_OF_MEMORY;
result = CURLE_OUT_OF_MEMORY;
goto oom;
}
if(digest->qop) {
response = curl_maprintf("username=\"%s\", "
"realm=\"%s\", "
"nonce=\"%s\", "
"uri=\"%s\", "
"cnonce=\"%s\", "
"nc=%08x, "
"qop=%s, "
"response=\"%s\"",
userp_quoted,
realm_quoted,
nonce_quoted,
uripath,
digest->cnonce,
digest->nc,
digest->qop,
request_digest);
result = curlx_dyn_addf(&response, "username=\"%s\", "
"realm=\"%s\", "
"nonce=\"%s\", "
"uri=\"%s\", "
"cnonce=\"%s\", "
"nc=%08x, "
"qop=%s, "
"response=\"%s\"",
userp_quoted,
realm_quoted,
nonce_quoted,
uri_quoted,
digest->cnonce,
digest->nc,
digest->qop,
request_digest);
/* Increment nonce-count to use another nc value for the next request */
digest->nc++;
}
else {
response = curl_maprintf("username=\"%s\", "
"realm=\"%s\", "
"nonce=\"%s\", "
"uri=\"%s\", "
"response=\"%s\"",
userp_quoted,
realm_quoted,
nonce_quoted,
uripath,
request_digest);
result = curlx_dyn_addf(&response, "username=\"%s\", "
"realm=\"%s\", "
"nonce=\"%s\", "
"uri=\"%s\", "
"response=\"%s\"",
userp_quoted,
realm_quoted,
nonce_quoted,
uri_quoted,
request_digest);
}
curlx_free(nonce_quoted);
curlx_free(realm_quoted);
curlx_free(userp_quoted);
if(!response)
return CURLE_OUT_OF_MEMORY;
if(result)
goto oom;
/* Add the optional fields */
if(digest->opaque) {
char *opaque_quoted;
/* Append the opaque */
opaque_quoted = auth_digest_string_quoted(digest->opaque);
char *opaque_quoted = auth_digest_string_quoted(digest->opaque);
if(!opaque_quoted) {
curlx_free(response);
return CURLE_OUT_OF_MEMORY;
result = CURLE_OUT_OF_MEMORY;
goto oom;
}
tmp = curl_maprintf("%s, opaque=\"%s\"", response, opaque_quoted);
curlx_free(response);
result = curlx_dyn_addf(&response, ", opaque=\"%s\"", opaque_quoted);
curlx_free(opaque_quoted);
if(!tmp)
return CURLE_OUT_OF_MEMORY;
response = tmp;
if(result)
goto oom;
}
if(digest->algorithm) {
/* Append the algorithm */
tmp = curl_maprintf("%s, algorithm=%s", response, digest->algorithm);
curlx_free(response);
if(!tmp)
return CURLE_OUT_OF_MEMORY;
response = tmp;
result = curlx_dyn_addf(&response, ", algorithm=%s", digest->algorithm);
if(result)
goto oom;
}
if(digest->userhash) {
/* Append the userhash */
tmp = curl_maprintf("%s, userhash=true", response);
curlx_free(response);
if(!tmp)
return CURLE_OUT_OF_MEMORY;
response = tmp;
result = curlx_dyn_add(&response, ", userhash=true");
if(result)
goto oom;
}
/* Return the output */
*outptr = response;
*outlen = strlen(response);
*outptr = curlx_dyn_ptr(&response);
*outlen = curlx_dyn_len(&response);
result = CURLE_OK;
return CURLE_OK;
oom:
curlx_free(nonce_quoted);
curlx_free(realm_quoted);
curlx_free(uri_quoted);
curlx_free(userp_quoted);
if(result)
curlx_dyn_free(&response);
return result;
}
/*

View file

@ -61,21 +61,21 @@ digest
HTTP with Digest authorization
</name>
<command>
http://%HOSTIP:%HTTPPORT/%TESTNUMBER -u testuser:testpass --digest
'http://%HOSTIP:%HTTPPORT/%TESTNUMBER"' -u testuser:testpass --digest
</command>
</client>
# Verify data after the test has been "shot"
<verify>
<protocol crlf="headers">
GET /%TESTNUMBER HTTP/1.1
GET /%TESTNUMBER" HTTP/1.1
Host: %HOSTIP:%HTTPPORT
User-Agent: curl/%VERSION
Accept: */*
GET /%TESTNUMBER HTTP/1.1
GET /%TESTNUMBER" HTTP/1.1
Host: %HOSTIP:%HTTPPORT
Authorization: Digest username="testuser", realm="testrealm", nonce="1053604145", uri="/%TESTNUMBER", response="c55f7f30d83d774a3d2dcacf725abaca"
Authorization: Digest username="testuser", realm="testrealm", nonce="1053604145", uri="/%TESTNUMBER\"", response="1ee14b238b3259f17602e9ce41491ef9"
User-Agent: curl/%VERSION
Accept: */*