Skip to content

Commit

Permalink
* src/ne_auth.c: Abstract out hash algorithm handling for Digest.
Browse files Browse the repository at this point in the history
  (struct hashalg): New structure, replacing auth_algorithm enum,
  alg_to_hash and alg_to_name arrays.
  (struct auth_challenge): Use hashalg pointer.
  (get_digest_h_urp, digest_challenge): Adjust accordingly.

* test/auth.c (digest_failures, fail_challenge): Tweak tests
  to reliably trigger desired failure cases.
  • Loading branch information
notroj committed Jul 20, 2022
1 parent 93faea8 commit 2b8e2e4
Show file tree
Hide file tree
Showing 2 changed files with 58 additions and 78 deletions.
122 changes: 49 additions & 73 deletions src/ne_auth.c
Original file line number Diff line number Diff line change
Expand Up @@ -89,35 +89,22 @@
#define HOOK_SERVER_ID "http://webdav.org/neon/hooks/server-auth"
#define HOOK_PROXY_ID "http://webdav.org/neon/hooks/proxy-auth"

typedef enum {
auth_alg_md5 = 0,
auth_alg_md5_sess,
auth_alg_sha256,
auth_alg_sha256_sess,
auth_alg_sha512_256,
auth_alg_sha512_256_sess,
auth_alg_unknown
} auth_algorithm;

static const unsigned int alg_to_hash[] = {
NE_HASH_MD5,
NE_HASH_MD5,
NE_HASH_SHA256,
NE_HASH_SHA256,
NE_HASH_SHA512_256,
NE_HASH_SHA512_256,
0
};
static const char *const alg_to_name[] = {
"MD5",
"MD5-sess",
"SHA-256",
"SHA-256-sess",
"SHA-512-256",
"SHA-512-256-sess",
"(unknown)",
static const struct hashalg {
const char *name;
unsigned int hash;
unsigned int sess; /* _session variant */
} hashalgs[] = {
{ "MD5", NE_HASH_MD5, 0 }, /* This must remain first in the array. */
{ "MD5-sess", NE_HASH_MD5, 1 },
{ "SHA-256", NE_HASH_SHA256, 0 },
{ "SHA-256-sess", NE_HASH_SHA256, 1 },
{ "SHA-512-256", NE_HASH_SHA512_256, 0 },
{ "SHA-512-256-sess", NE_HASH_SHA512_256, 1 }
};

#define HASHALG_MD5 (&hashalgs[0])
#define NUM_HASHALGS (sizeof(hashalgs)/sizeof(hashalgs[0]))

/* Selected method of qop which the client is using */
typedef enum {
auth_qop_none,
Expand Down Expand Up @@ -147,7 +134,7 @@ struct auth_challenge {
unsigned int got_qop; /* we were given a qop directive */
unsigned int qop_auth; /* "auth" token in qop attrib */
enum { userhash_none=0, userhash_true=1, userhash_false=2} userhash;
auth_algorithm alg;
const struct hashalg *alg;
struct auth_challenge *next;
};

Expand Down Expand Up @@ -228,7 +215,7 @@ typedef struct {
char *userhash;
char *username_star;
auth_qop qop;
auth_algorithm alg;
const struct hashalg *alg;
unsigned int nonce_count;
/* The hex representation of the H(A1) value */
char *h_a1;
Expand Down Expand Up @@ -929,8 +916,7 @@ static int unsafe_username(const char *username)
/* Returns the H(username:realm:password) used in the Digest H(A1)
* calculation. */
static char *get_digest_h_urp(auth_session *sess, ne_buffer **errmsg,
unsigned int hash, int attempt,
struct auth_challenge *parms)
int attempt, struct auth_challenge *parms)
{
char password[ABUFSIZE], *h_urp;

Expand All @@ -942,7 +928,7 @@ static char *get_digest_h_urp(auth_session *sess, ne_buffer **errmsg,
/* Calculate userhash for this (realm, username) if required.
* https://tools.ietf.org/html/rfc7616#section-3.4.4 */
if (parms->userhash == userhash_true) {
sess->userhash = ne_strhash(hash, sess->username, ":",
sess->userhash = ne_strhash(parms->alg->hash, sess->username, ":",
sess->realm, NULL);
}
else {
Expand All @@ -967,7 +953,7 @@ static char *get_digest_h_urp(auth_session *sess, ne_buffer **errmsg,

/* H(A1) calculation identical for 2069 or 2617/7616:
* https://tools.ietf.org/html/rfc7616#section-3.4.2 */
h_urp = ne_strhash(hash, sess->username, ":", sess->realm, ":",
h_urp = ne_strhash(parms->alg->hash, sess->username, ":", sess->realm, ":",
password, NULL);
ne__strzero(password, sizeof password);

Expand All @@ -980,14 +966,23 @@ static int digest_challenge(auth_session *sess, int attempt,
struct auth_challenge *parms,
const char *uri, ne_buffer **errmsg)
{
unsigned int hash;
char *p, *h_urp = NULL;

if (parms->alg == auth_alg_unknown) {
/* Handle 2069 legacy Digest case. */
if (parms->alg == NULL && !parms->got_qop) {
if ((parms->handler->protomask & NE_AUTH_LEGACY_DIGEST) == 0) {
challenge_error(errmsg, _("legacy Digest challenge not supported"));
return -1;
}
/* It can only be MD5. */
parms->alg = HASHALG_MD5;
}

if (parms->alg == NULL) {
challenge_error(errmsg, _("unknown algorithm in Digest challenge"));
return -1;
}
else if (parms->alg == auth_alg_md5_sess && !parms->qop_auth) {
else if (parms->alg->sess && !parms->qop_auth) {
challenge_error(errmsg, _("incompatible algorithm in Digest challenge"));
return -1;
}
Expand All @@ -1006,18 +1001,12 @@ static int digest_challenge(auth_session *sess, int attempt,
challenge_error(errmsg, _("stale Digest challenge with new algorithm or realm"));
return -1;
}
else if (!parms->got_qop
&& (parms->handler->protomask & NE_AUTH_LEGACY_DIGEST) == 0) {
challenge_error(errmsg, _("legacy Digest challenge not supported"));
return -1;
}

hash = alg_to_hash[parms->alg];
p = ne_strhash(hash, "", NULL);
p = ne_strhash(parms->alg->hash, "", NULL);
if (p == NULL) {
challenge_error(errmsg,
_("%s algorithm in Digest challenge not supported"),
alg_to_name[parms->alg]);
parms->alg->name);
return -1;
}
ne_free(p);
Expand All @@ -1038,7 +1027,7 @@ static int digest_challenge(auth_session *sess, int attempt,
sess->alg = parms->alg;
sess->cnonce = get_cnonce();

h_urp = get_digest_h_urp(sess, errmsg, hash, attempt, parms);
h_urp = get_digest_h_urp(sess, errmsg, attempt, parms);
if (h_urp == NULL) {
return -1;
}
Expand All @@ -1065,10 +1054,9 @@ static int digest_challenge(auth_session *sess, int attempt,
}

if (h_urp) {
if (sess->alg == auth_alg_md5_sess || sess->alg == auth_alg_sha256_sess
|| sess->alg == auth_alg_sha512_256_sess) {
sess->h_a1 = ne_strhash(hash, h_urp, ":", sess->nonce, ":",
sess->cnonce, NULL);
if (sess->alg->sess) {
sess->h_a1 = ne_strhash(parms->alg->hash, h_urp, ":",
sess->nonce, ":", sess->cnonce, NULL);
zero_and_free(h_urp);
NE_DEBUG(NE_DBG_HTTPAUTH, "auth: Session H(A1) is [%s]\n", sess->h_a1);
}
Expand Down Expand Up @@ -1119,7 +1107,7 @@ static char *request_digest(auth_session *sess, struct auth_request *req)
char nc_value[9] = {0};
const char *qop_value = "auth"; /* qop-value */
ne_buffer *ret;
unsigned int hash = alg_to_hash[sess->alg];
unsigned int hash = sess->alg->hash;

/* Do not submit credentials if an auth domain is defined and this
* request-uri fails outside it. */
Expand Down Expand Up @@ -1161,7 +1149,7 @@ static char *request_digest(auth_session *sess, struct auth_request *req)
"nonce=\"", sess->nonce, "\", "
"uri=\"", req->uri, "\", "
"response=\"", response, "\", "
"algorithm=\"", alg_to_name[sess->alg], "\"",
"algorithm=\"", sess->alg->name, "\"",
NULL);
if (sess->username_star) {
ne_buffer_concat(ret, ", username*=", sess->username_star, NULL);
Expand Down Expand Up @@ -1339,7 +1327,7 @@ static int verify_digest_response(struct auth_request *req, auth_session *sess,
* the response-digest field. */
if (qop == auth_qop_auth && ret == NE_OK) {
char *h_a2, *response;
unsigned int hash = alg_to_hash[sess->alg];
unsigned int hash = sess->alg->hash;

h_a2 = ne_strhash(hash, ":", req->uri, NULL);
response = ne_strhash(hash, sess->h_a1, ":", sess->response_rhs,
Expand Down Expand Up @@ -1532,27 +1520,15 @@ static int auth_challenge(auth_session *sess, int attempt, const char *uri,
/* Truth value */
chall->stale = (ne_strcasecmp(val, "true") == 0);
} else if (ne_strcasecmp(key, "algorithm") == 0) {
if (ne_strcasecmp(val, "md5") == 0) {
chall->alg = auth_alg_md5;
}
else if (ne_strcasecmp(val, "md5-sess") == 0) {
chall->alg = auth_alg_md5_sess;
}
else if (ne_strcasecmp(val, "sha-256") == 0) {
chall->alg = auth_alg_sha256;
}
else if (ne_strcasecmp(val, "sha-256-sess") == 0) {
chall->alg = auth_alg_sha256_sess;
}
else if (ne_strcasecmp(val, "sha-512-256") == 0) {
chall->alg = auth_alg_sha512_256;
}
else if (ne_strcasecmp(val, "sha-512-256-sess") == 0) {
chall->alg = auth_alg_sha512_256_sess;
}
else {
chall->alg = auth_alg_unknown;
}
unsigned int n;

chall->alg = NULL;
for (n = 0; n < NUM_HASHALGS; n++) {
if (ne_strcasecmp(val, hashalgs[n].name) == 0) {
chall->alg = &hashalgs[n];
break;
}
}
} else if (ne_strcasecmp(key, "qop") == 0) {
/* iterate over each token in the value */
do {
Expand Down
14 changes: 9 additions & 5 deletions test/auth.c
Original file line number Diff line number Diff line change
Expand Up @@ -1085,26 +1085,30 @@ static int digest_failures(void)
parms.realm = "WallyWorld";
parms.nonce = "random-invented-string";
parms.opaque = NULL;
parms.flags = PARM_AINFO;
parms.num_requests = 1;

for (n = 0; fails[n].message; n++) {
ne_session *sess;
int ret;
unsigned protocol = NE_AUTH_DIGEST;

parms.failure = fails[n].mode;
parms.flags = PARM_AINFO;

if (parms.failure == fail_req0_2069_stale) protocol |= NE_AUTH_LEGACY_DIGEST;

if (parms.failure == fail_req0_2069_stale || parms.failure == fail_2069_weak)
parms.flags &= ~PARM_RFC2617;
else
parms.flags |= PARM_RFC2617;

NE_DEBUG(NE_DBG_HTTP, ">>> New Digest failure test, "
"expecting failure '%s'\n", fails[n].message);
NE_DEBUG(NE_DBG_HTTP, ">>> New Digest failure test %u, "
"expecting failure '%s', protocol %x\n", n,
fails[n].message, protocol);

CALL(session_server(&sess, serve_digest, &parms));

ne_set_server_auth(sess, auth_cb, NULL);
ne_add_server_auth(sess, protocol, auth_cb, NULL);

ret = any_2xx_request(sess, "/fish");
ONV(ret == NE_OK,
Expand Down Expand Up @@ -1170,7 +1174,7 @@ static int fail_challenge(void)
"domain=\"http://[::1/\"", "could not parse domain" },

/* Multiple challenge failure cases: */
{ "Basic, Digest",
{ "Basic, Digest realm=\"foo\", algorithm=MD5, qop=auth",
"missing parameter in Digest challenge, missing realm in Basic challenge" },

{ "Digest realm=\"foo\", algorithm=MD5, qop=auth, nonce=\"foo\","
Expand Down

0 comments on commit 2b8e2e4

Please sign in to comment.