auth: add support for RFC7616 - HTTP Digest access authentication

Signed-off-by: Florin <petriuc.florin@gmail.com>
This commit is contained in:
Florin 2017-09-30 20:30:55 +02:00 committed by Daniel Stenberg
parent 1d0c8dea95
commit 2b5b37cb91
No known key found for this signature in database
GPG key ID: 5CC908FDB71E12C2
7 changed files with 455 additions and 46 deletions

View file

@ -19,6 +19,7 @@
* KIND, either express or implied.
*
* RFC2831 DIGEST-MD5 authentication
* RFC7616 DIGEST-SHA256, DIGEST-SHA512-256 authentication
*
***************************************************************************/
@ -34,6 +35,7 @@
#include "curl_base64.h"
#include "curl_hmac.h"
#include "curl_md5.h"
#include "curl_sha256.h"
#include "vtls/vtls.h"
#include "warnless.h"
#include "strtok.h"
@ -144,6 +146,15 @@ static void auth_digest_md5_to_ascii(unsigned char *source, /* 16 bytes */
snprintf((char *) &dest[i * 2], 3, "%02x", source[i]);
}
/* Convert sha256 chunk to RFC7616 -suitable ascii string*/
static void auth_digest_sha256_to_ascii(unsigned char *source, /* 32 bytes */
unsigned char *dest) /* 65 bytes */
{
int i;
for(i = 0; i < 32; i++)
snprintf((char *) &dest[i * 2], 3, "%02x", source[i]);
}
/* Perform quoted-string escaping as described in RFC2616 and its errata */
static char *auth_digest_string_quoted(const char *source)
{
@ -602,9 +613,22 @@ CURLcode Curl_auth_decode_digest_http_message(const char *chlg,
digest->algo = CURLDIGESTALGO_MD5SESS;
else if(strcasecompare(content, "MD5"))
digest->algo = CURLDIGESTALGO_MD5;
else if(strcasecompare(content, "SHA-256"))
digest->algo = CURLDIGESTALGO_SHA256;
else if(strcasecompare(content, "SHA-256-SESS"))
digest->algo = CURLDIGESTALGO_SHA256SESS;
else if(strcasecompare(content, "SHA-512-256"))
digest->algo = CURLDIGESTALGO_SHA512_256;
else if(strcasecompare(content, "SHA-512-256-SESS"))
digest->algo = CURLDIGESTALGO_SHA512_256SESS;
else
return CURLE_BAD_CONTENT_ENCODING;
}
else if(strcasecompare(value, "userhash")) {
if(strcasecompare(content, "true")) {
digest->userhash = TRUE;
}
}
else {
/* Unknown specifier, ignore it! */
}
@ -635,7 +659,7 @@ CURLcode Curl_auth_decode_digest_http_message(const char *chlg,
}
/*
* Curl_auth_create_digest_http_message()
* _Curl_auth_create_digest_http_message()
*
* This is used to generate a HTTP DIGEST response message ready for sending
* to the recipient.
@ -654,20 +678,24 @@ CURLcode Curl_auth_decode_digest_http_message(const char *chlg,
*
* Returns CURLE_OK on success.
*/
CURLcode Curl_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)
static CURLcode _Curl_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 *),
void (*hash)(unsigned char *, const unsigned char *))
{
CURLcode result;
unsigned char md5buf[16]; /* 16 bytes/128 bits */
unsigned char request_digest[33];
unsigned char *md5this;
unsigned char ha1[33]; /* 32 digits and 1 zero byte */
unsigned char ha2[33]; /* 32 digits and 1 zero byte */
unsigned char hashbuf[32]; /* 32 bytes/256 bits */
unsigned char request_digest[65];
unsigned char *hashthis;
unsigned char ha1[65]; /* 64 digits and 1 zero byte */
unsigned char ha2[65]; /* 64 digits and 1 zero byte */
char userh[65];
char cnoncebuf[33];
char *cnonce = NULL;
size_t cnonce_sz = 0;
@ -692,6 +720,17 @@ CURLcode Curl_auth_create_digest_http_message(struct Curl_easy *data,
digest->cnonce = cnonce;
}
if(digest->userhash) {
hashthis = (unsigned char *) aprintf("%s:%s", userp, digest->realm);
if(!hashthis)
return CURLE_OUT_OF_MEMORY;
CURL_OUTPUT_DIGEST_CONV(data, hashthis);
hash(hashbuf, hashthis);
free(hashthis);
convert_to_ascii(hashbuf, (unsigned char *)userh);
}
/*
If the algorithm is "MD5" or unspecified (which then defaults to MD5):
@ -703,26 +742,29 @@ CURLcode Curl_auth_create_digest_http_message(struct Curl_easy *data,
unq(nonce-value) ":" unq(cnonce-value)
*/
md5this = (unsigned char *)
aprintf("%s:%s:%s", userp, digest->realm, passwdp);
if(!md5this)
hashthis = (unsigned char *)
aprintf("%s:%s:%s", digest->userhash ? userh : userp,
digest->realm, passwdp);
if(!hashthis)
return CURLE_OUT_OF_MEMORY;
CURL_OUTPUT_DIGEST_CONV(data, md5this); /* convert on non-ASCII machines */
Curl_md5it(md5buf, md5this);
free(md5this);
auth_digest_md5_to_ascii(md5buf, ha1);
CURL_OUTPUT_DIGEST_CONV(data, hashthis); /* convert on non-ASCII machines */
hash(hashbuf, hashthis);
free(hashthis);
convert_to_ascii(hashbuf, ha1);
if(digest->algo == CURLDIGESTALGO_MD5SESS) {
if(digest->algo == CURLDIGESTALGO_MD5SESS ||
digest->algo == CURLDIGESTALGO_SHA256SESS ||
digest->algo == CURLDIGESTALGO_SHA512_256SESS) {
/* nonce and cnonce are OUTSIDE the hash */
tmp = aprintf("%s:%s:%s", ha1, digest->nonce, digest->cnonce);
if(!tmp)
return CURLE_OUT_OF_MEMORY;
CURL_OUTPUT_DIGEST_CONV(data, tmp); /* Convert on non-ASCII machines */
Curl_md5it(md5buf, (unsigned char *) tmp);
hash(hashbuf, (unsigned char *) tmp);
free(tmp);
auth_digest_md5_to_ascii(md5buf, ha1);
convert_to_ascii(hashbuf, ha1);
}
/*
@ -738,27 +780,32 @@ CURLcode Curl_auth_create_digest_http_message(struct Curl_easy *data,
5.1.1 of RFC 2616)
*/
md5this = (unsigned char *) aprintf("%s:%s", request, uripath);
hashthis = (unsigned char *) aprintf("%s:%s", request, uripath);
if(digest->qop && strcasecompare(digest->qop, "auth-int")) {
/* We don't support auth-int for PUT or POST at the moment.
TODO: replace md5 of empty string with entity-body for PUT/POST */
unsigned char *md5this2 = (unsigned char *)
aprintf("%s:%s", md5this, "d41d8cd98f00b204e9800998ecf8427e");
free(md5this);
md5this = md5this2;
TODO: replace hash of empty string with entity-body for PUT/POST */
char hashed[65];
unsigned char *hashthis2;
hash(hashbuf, (const unsigned char *)"");
convert_to_ascii(hashbuf, (unsigned char *)hashed);
hashthis2 = (unsigned char *)aprintf("%s:%s", hashthis, hashed);
free(hashthis);
hashthis = hashthis2;
}
if(!md5this)
if(!hashthis)
return CURLE_OUT_OF_MEMORY;
CURL_OUTPUT_DIGEST_CONV(data, md5this); /* convert on non-ASCII machines */
Curl_md5it(md5buf, md5this);
free(md5this);
auth_digest_md5_to_ascii(md5buf, ha2);
CURL_OUTPUT_DIGEST_CONV(data, hashthis); /* convert on non-ASCII machines */
hash(hashbuf, hashthis);
free(hashthis);
convert_to_ascii(hashbuf, ha2);
if(digest->qop) {
md5this = (unsigned char *) aprintf("%s:%s:%08x:%s:%s:%s",
hashthis = (unsigned char *) aprintf("%s:%s:%08x:%s:%s:%s",
ha1,
digest->nonce,
digest->nc,
@ -767,19 +814,19 @@ CURLcode Curl_auth_create_digest_http_message(struct Curl_easy *data,
ha2);
}
else {
md5this = (unsigned char *) aprintf("%s:%s:%s",
hashthis = (unsigned char *) aprintf("%s:%s:%s",
ha1,
digest->nonce,
ha2);
}
if(!md5this)
if(!hashthis)
return CURLE_OUT_OF_MEMORY;
CURL_OUTPUT_DIGEST_CONV(data, md5this); /* convert on non-ASCII machines */
Curl_md5it(md5buf, md5this);
free(md5this);
auth_digest_md5_to_ascii(md5buf, request_digest);
CURL_OUTPUT_DIGEST_CONV(data, hashthis); /* convert on non-ASCII machines */
hash(hashbuf, hashthis);
free(hashthis);
convert_to_ascii(hashbuf, request_digest);
/* For test case 64 (snooped from a Mozilla 1.3a request)
@ -794,7 +841,7 @@ CURLcode Curl_auth_create_digest_http_message(struct Curl_easy *data,
characters. algorithm and qop with standard values only contain web-safe
characters.
*/
userp_quoted = auth_digest_string_quoted(userp);
userp_quoted = auth_digest_string_quoted(digest->userhash ? userh : userp);
if(!userp_quoted)
return CURLE_OUT_OF_MEMORY;
@ -858,6 +905,16 @@ CURLcode Curl_auth_create_digest_http_message(struct Curl_easy *data,
response = tmp;
}
if(digest->userhash) {
/* Append the userhash */
tmp = aprintf("%s, userhash=true", response);
free(response);
if(!tmp)
return CURLE_OUT_OF_MEMORY;
response = tmp;
}
/* Return the output */
*outptr = response;
*outlen = strlen(response);
@ -865,6 +922,58 @@ CURLcode Curl_auth_create_digest_http_message(struct Curl_easy *data,
return CURLE_OK;
}
/*
* Curl_auth_create_digest_http_message()
*
* This is used to generate a HTTP DIGEST response message ready for sending
* to the recipient.
*
* Parameters:
*
* data [in] - The session handle.
* userp [in] - The user name.
* passdwp [in] - The user's password.
* request [in] - The HTTP request.
* uripath [in] - The path of the HTTP uri.
* digest [in/out] - The digest data struct being used and modified.
* outptr [in/out] - The address where a pointer to newly allocated memory
* holding the result will be stored upon completion.
* outlen [out] - The length of the output message.
*
* Returns CURLE_OK on success.
*/
CURLcode Curl_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)
{
switch(digest->algo) {
case CURLDIGESTALGO_MD5:
case CURLDIGESTALGO_MD5SESS:
return _Curl_auth_create_digest_http_message(data, userp, passwdp,
request, uripath, digest,
outptr, outlen,
auth_digest_md5_to_ascii,
Curl_md5it);
case CURLDIGESTALGO_SHA256:
case CURLDIGESTALGO_SHA256SESS:
case CURLDIGESTALGO_SHA512_256:
case CURLDIGESTALGO_SHA512_256SESS:
return _Curl_auth_create_digest_http_message(data, userp, passwdp,
request, uripath, digest,
outptr, outlen,
auth_digest_sha256_to_ascii,
Curl_sha256it);
default:
return CURLE_UNSUPPORTED_PROTOCOL;
}
}
/*
* Curl_auth_digest_cleanup()
*
@ -887,6 +996,7 @@ void Curl_auth_digest_cleanup(struct digestdata *digest)
digest->nc = 0;
digest->algo = CURLDIGESTALGO_MD5; /* default algorithm */
digest->stale = FALSE; /* default means normal, not stale */
digest->userhash = FALSE;
}
#endif /* !USE_WINDOWS_SSPI */

View file

@ -31,7 +31,11 @@
enum {
CURLDIGESTALGO_MD5,
CURLDIGESTALGO_MD5SESS
CURLDIGESTALGO_MD5SESS,
CURLDIGESTALGO_SHA256,
CURLDIGESTALGO_SHA256SESS,
CURLDIGESTALGO_SHA512_256,
CURLDIGESTALGO_SHA512_256SESS
};
/* This is used to extract the realm from a challenge message */