diff options
author | Joe Orton <joe@manyfish.uk> | 2020-06-08 19:47:10 +0100 |
---|---|---|
committer | Joe Orton <jorton@apache.org> | 2020-06-18 08:01:27 +0100 |
commit | 86cd1877471ae0cb701167e737124ebc916313ae (patch) | |
tree | 00ba09401e7697015c1e556b3ba0f2ee6dc2e352 | |
parent | 4f59da23e8d08bd6cf640f83812073ca968af527 (diff) | |
download | neon-git-86cd1877471ae0cb701167e737124ebc916313ae.tar.gz |
Support hashed usernames per RFC7616.
* src/ne_auth.c (struct auth_challenge): Add userhash field.
(auth_session): Likewise.
(digest_challenge): Calculate userhash if challenge
used userhash=true.
(request_digest): Use userhash, userhash=true as appropriate.
(auth_challenge): Parse userhash parameter.
(clean_session): Free userhash.
* test/auth.c (verify_digest_header, make_digest_header,
serve_digest): Add userhash support.
(digest): Test for userhash=true and userhash=false.
-rw-r--r-- | src/ne_auth.c | 29 | ||||
-rw-r--r-- | test/auth.c | 35 |
2 files changed, 61 insertions, 3 deletions
diff --git a/src/ne_auth.c b/src/ne_auth.c index 9c3321a..a9580e2 100644 --- a/src/ne_auth.c +++ b/src/ne_auth.c @@ -122,6 +122,7 @@ struct auth_challenge { unsigned int stale; /* if stale=true */ unsigned int got_qop; /* we were given a qop directive */ unsigned int qop_auth; /* "auth" token in qop attrib */ + unsigned int userhash; /* got userhash=true */ auth_algorithm alg; struct auth_challenge *next; }; @@ -194,6 +195,7 @@ typedef struct { char *opaque; char **domains; /* list of paths given as domain. */ size_t ndomains; /* size of domains array */ + char *userhash; auth_qop qop; auth_algorithm alg; unsigned int nonce_count; @@ -277,8 +279,9 @@ static void clean_session(auth_session *sess) if (sess->cnonce) ne_free(sess->cnonce); if (sess->opaque) ne_free(sess->opaque); if (sess->realm) ne_free(sess->realm); + if (sess->userhash) ne_free(sess->userhash); sess->realm = sess->basic = sess->cnonce = sess->nonce = - sess->opaque = NULL; + sess->opaque = sess->userhash = NULL; if (sess->stored_rdig) { ne_md5_destroy_ctx(sess->stored_rdig); sess->stored_rdig = NULL; @@ -840,6 +843,21 @@ static int digest_challenge(auth_session *sess, int attempt, /* Failed to get credentials */ return -1; } + + /* Calculate userhash for this (realm, username) if required. + * https://tools.ietf.org/html/rfc7616#section-3.4.4 */ + if (parms->userhash) { + struct ne_md5_ctx *tmp; + char digest[33]; + + tmp = ne_md5_create_ctx(); + ne_md5_process_bytes(sess->username, strlen(sess->username), tmp); + ne_md5_process_bytes(":", 1, tmp); + ne_md5_process_bytes(sess->realm, strlen(sess->realm), tmp); + ne_md5_finish_ascii(tmp, digest); + + sess->userhash = ne_strdup(digest); + } } else { /* Stale challenge: accept a new nonce or opaque. */ @@ -998,7 +1016,8 @@ static char *request_digest(auth_session *sess, struct auth_request *req) ret = ne_buffer_create(); ne_buffer_concat(ret, - "Digest username=\"", sess->username, "\", " + "Digest username=\"", + sess->userhash ? sess->userhash : sess->username, "\", " "realm=\"", sess->realm, "\", " "nonce=\"", sess->nonce, "\", " "uri=\"", req->uri, "\", " @@ -1016,6 +1035,9 @@ static char *request_digest(auth_session *sess, struct auth_request *req) "nc=", nc_value, ", " "qop=\"", qop_value, "\"", NULL); } + if (sess->userhash) { + ne_buffer_czappend(ret, ", userhash=true"); + } ne_buffer_zappend(ret, "\r\n"); @@ -1390,6 +1412,9 @@ static int auth_challenge(auth_session *sess, int attempt, else if (ne_strcasecmp(key, "domain") == 0) { chall->domain = val; } + else if (ne_strcasecmp(key, "userhash") == 0) { + chall->userhash = strcmp(val, "true") == 0; + } } sess->protocol = NULL; diff --git a/test/auth.c b/test/auth.c index 3055165..bdd1867 100644 --- a/test/auth.c +++ b/test/auth.c @@ -380,6 +380,8 @@ static void dup_header(char *header) #define PARM_NEXTNONCE (0x0002) #define PARM_RFC2617 (0x0004) #define PARM_AINFO (0x0008) +#define PARM_USERHASH (0x0010) /* userhash=true */ +#define PARM_UHFALSE (0x0020) /* userhash=false */ struct digest_parms { const char *realm, *nonce, *opaque, *domain; @@ -409,9 +411,11 @@ struct digest_parms { struct digest_state { const char *realm, *nonce, *uri, *username, *password, *algorithm, *qop, *method, *opaque; + char userhash[33]; char *cnonce, *digest, *ncval; long nc; int count; + int uhash_bool; }; #ifdef HAVE_OPENSSL11 @@ -580,6 +584,9 @@ static int verify_digest_header(struct digest_state *state, else if (ne_strcasecmp(name, "response") == 0) { state->digest = ne_strdup(val); } + else if (ne_strcasecmp(name, "userhash") == 0 ) { + newstate.uhash_bool = strcmp(val, "true") == 0; + } } ONN("cnonce param missing or short for 2617-style auth", @@ -587,8 +594,18 @@ static int verify_digest_header(struct digest_state *state, && (newstate.cnonce == NULL || strlen(newstate.cnonce) < 32)); + if (parms->flags & PARM_USERHASH) { + ONN("userhash missing", !newstate.uhash_bool); + + ONCMP(state->userhash, newstate.username, + "Digest username (userhash) field", "userhash"); + } + else { + ONN("unexpected userhash=true sent", newstate.uhash_bool); + DIGCMP(username); + } + DIGCMP(realm); - DIGCMP(username); if (!parms->domain) DIGCMP(uri); DIGCMP(nonce); @@ -698,6 +715,13 @@ static char *make_digest_header(struct digest_state *state, ne_buffer_concat(buf, "domain=\"", parms->domain, "\", ", NULL); } + if (parms->flags & PARM_USERHASH) { + ne_buffer_czappend(buf, "userhash=true, "); + } + else if (parms->flags & PARM_UHFALSE) { + ne_buffer_czappend(buf, "userhash=false, "); + } + if (parms->failure == fail_req0_stale || parms->failure == fail_req0_2069_stale || parms->stale == parms->num_requests) { @@ -737,6 +761,10 @@ static int serve_digest(ne_socket *sock, void *userdata) } state.qop = "auth"; + if (parms->flags & PARM_USERHASH) { + hash(parms, state.userhash, username, ":", parms->realm, NULL); + } + state.cnonce = state.digest = state.ncval = NULL; parms->num_requests += parms->stale ? 1 : 0; @@ -871,6 +899,11 @@ static int digest(void) /* 2069 + stale */ { "WallyWorld", "this-is-a-nonce", NULL, NULL, ALG_MD5, PARM_AINFO, 3, 2, fail_not }, + /* RFC 7616-style */ + { "WallyWorld", "new-day-new-nonce", "new-opaque", NULL, ALG_MD5, PARM_RFC2617 | PARM_USERHASH, 1, 0, fail_not }, + /* ... userhash=false */ + { "WallyWorld", "just-another-nonce", "new-opaque", NULL, ALG_MD5, PARM_RFC2617 | PARM_UHFALSE, 1, 0, fail_not }, + /* RFC 2069-style */ { "WallyWorld", "lah-di-da-di-dah", NULL, NULL, ALG_MD5, 0, 1, 0, fail_not }, { "WallyWorld", "fee-fi-fo-fum", "opaque-string", NULL, ALG_MD5, 0, 1, 0, fail_not }, |