diff --git a/src/mod_auth.c b/src/mod_auth.c index d8811c09..1aff9762 100644 --- a/src/mod_auth.c +++ b/src/mod_auth.c @@ -721,6 +721,8 @@ static handler_t mod_auth_send_400_bad_request(request_st * const r) { return HANDLER_FINISHED; } + + __attribute_noinline__ static handler_t mod_auth_send_401_unauthorized_basic(request_st * const r, const buffer * const realm) { r->http_status = 401; @@ -838,7 +840,32 @@ static handler_t mod_auth_check_basic(request_st * const r, void *p_d, const str } -static void mod_auth_digest_mutate(http_auth_info_t *ai, const char *m, const char *uri, const char *nonce, const char *cnonce, const char *nc, const char *qop) { + +enum http_auth_digest_params_e { + e_username = 0 + ,e_realm + ,e_nonce + ,e_uri + ,e_algorithm + ,e_qop + ,e_cnonce + ,e_nc + ,e_response + ,http_auth_digest_params_sz /*(last item)*/ +}; + +typedef struct http_auth_digest_params_t { + const char *ptr[http_auth_digest_params_sz]; + uint16_t len[http_auth_digest_params_sz]; + time_t send_nextnonce_ts; + unsigned char rdigest[MD_DIGEST_LENGTH_MAX]; /*(last member)*/ +} http_auth_digest_params_t; + + +static void +mod_auth_digest_mutate (http_auth_info_t * const ai, const http_auth_digest_params_t * const dp, const char * const method) +{ + force_assert(method); li_md_iov_fn digest_iov = MD5_iov; /* (ai->dalgo & HTTP_AUTH_DIGEST_MD5) default */ #ifdef USE_LIB_CRYPTO @@ -863,27 +890,28 @@ static void mod_auth_digest_mutate(http_auth_info_t *ai, const char *m, const ch iov[0].iov_len = ai->dlen*2; iov[1].iov_base = ":"; iov[1].iov_len = 1; - iov[2].iov_base = nonce; - iov[2].iov_len = strlen(nonce); + iov[2].iov_base = dp->ptr[e_nonce]; + iov[2].iov_len = dp->len[e_nonce]; iov[3].iov_base = ":"; iov[3].iov_len = 1; - iov[4].iov_base = cnonce; - iov[4].iov_len = strlen(cnonce); + iov[4].iov_base = dp->ptr[e_cnonce]; + iov[4].iov_len = dp->len[e_cnonce]; digest_iov(ai->digest, iov, 5); li_tohex(a1, sizeof(a1), (const char *)ai->digest, ai->dlen); } /* calculate H(A2) */ - iov[0].iov_base = m; - iov[0].iov_len = strlen(m); + iov[0].iov_base = method; + iov[0].iov_len = strlen(method); iov[1].iov_base = ":"; iov[1].iov_len = 1; - iov[2].iov_base = uri; - iov[2].iov_len = strlen(uri); + iov[2].iov_base = dp->ptr[e_uri]; + iov[2].iov_len = dp->len[e_uri]; n = 3; #if 0 /* qop=auth-int not supported, already checked in caller */ - if (qop && buffer_eq_icase_ss(qop, strlen(qop), CONST_STR_LEN("auth-int"))){ + if (dp->ptr[e_qop] && buffer_eq_icase_ss(dp->ptr[e_qop], dp->len[e_qop], + CONST_STR_LEN("auth-int"))) { iov[3].iov_base = ":"; iov[3].iov_len = 1; iov[4].iov_base = [body checksum]; @@ -899,22 +927,22 @@ static void mod_auth_digest_mutate(http_auth_info_t *ai, const char *m, const ch iov[0].iov_len = ai->dlen*2; iov[1].iov_base = ":"; iov[1].iov_len = 1; - iov[2].iov_base = nonce; - iov[2].iov_len = strlen(nonce); + iov[2].iov_base = dp->ptr[e_nonce]; + iov[2].iov_len = dp->len[e_nonce]; iov[3].iov_base = ":"; iov[3].iov_len = 1; n = 4; - if (qop && *qop) { - iov[4].iov_base = nc; - iov[4].iov_len = strlen(nc); + if (dp->len[e_qop]) { + iov[4].iov_base = dp->ptr[e_nc]; + iov[4].iov_len = dp->len[e_nc]; iov[5].iov_base = ":"; iov[5].iov_len = 1; - iov[6].iov_base = cnonce; - iov[6].iov_len = strlen(cnonce); + iov[6].iov_base = dp->ptr[e_cnonce]; + iov[6].iov_len = dp->len[e_cnonce]; iov[7].iov_base = ":"; iov[7].iov_len = 1; - iov[8].iov_base = qop; - iov[8].iov_len = strlen(qop); + iov[8].iov_base = dp->ptr[e_qop]; + iov[8].iov_len = dp->len[e_qop]; iov[9].iov_base = ":"; iov[9].iov_len = 1; n = 10; @@ -924,7 +952,10 @@ static void mod_auth_digest_mutate(http_auth_info_t *ai, const char *m, const ch digest_iov(ai->digest, iov, n+1); } -static void mod_auth_append_nonce(buffer *b, time_t cur_ts, const struct http_auth_require_t *require, int dalgo, int *rndptr) { + +static void +mod_auth_append_nonce (buffer *b, time_t cur_ts, const struct http_auth_require_t *require, int dalgo, int *rndptr) +{ buffer_append_uint_hex(b, (uintmax_t)cur_ts); buffer_append_string_len(b, CONST_STR_LEN(":")); const buffer * const nonce_secret = require->nonce_secret; @@ -985,7 +1016,10 @@ static void mod_auth_append_nonce(buffer *b, time_t cur_ts, const struct http_au li_tohex(buffer_extend(b, n*2), n*2+1, (const char *)h, n); } -static void mod_auth_digest_www_authenticate(buffer *b, time_t cur_ts, const struct http_auth_require_t *require, int nonce_stale) { + +static void +mod_auth_digest_www_authenticate (buffer *b, time_t cur_ts, const struct http_auth_require_t *require, int nonce_stale) +{ int algos = nonce_stale ? nonce_stale : require->algorithm; int n = 0; int algoid[3]; @@ -1033,16 +1067,31 @@ static void mod_auth_digest_www_authenticate(buffer *b, time_t cur_ts, const str } } + __attribute_noinline__ -static handler_t mod_auth_send_401_unauthorized_digest(request_st *r, const struct http_auth_require_t *require, int nonce_stale); +static handler_t +mod_auth_send_401_unauthorized_digest(request_st * const r, const struct http_auth_require_t * const require, int nonce_stale) +{ + r->http_status = 401; + r->handler_module = NULL; + mod_auth_digest_www_authenticate( + http_header_response_set_ptr(r, HTTP_HEADER_WWW_AUTHENTICATE, + CONST_STR_LEN("WWW-Authenticate")), + log_epoch_secs, require, nonce_stale); + return HANDLER_FINISHED; +} -static void mod_auth_digest_authentication_info(buffer *b, time_t cur_ts, const struct http_auth_require_t *require, int dalgo) { + +static void +mod_auth_digest_authentication_info (buffer *b, time_t cur_ts, const struct http_auth_require_t *require, int dalgo) +{ buffer_clear(b); buffer_append_string_len(b, CONST_STR_LEN("nextnonce=\"")); mod_auth_append_nonce(b, cur_ts, require, dalgo, NULL); buffer_append_string_len(b, CONST_STR_LEN("\"")); } + static handler_t mod_auth_digest_get (request_st * const r, void *p_d, const struct http_auth_require_t * const require, const struct http_auth_backend_t * const backend, http_auth_info_t * const ai) { @@ -1092,321 +1141,298 @@ mod_auth_digest_get (request_st * const r, void *p_d, const struct http_auth_req return rc; } -typedef struct { - const char *key; - int key_len; - char **ptr; -} digest_kv; - -static handler_t mod_auth_check_digest(request_st * const r, void *p_d, const struct http_auth_require_t * const require, const struct http_auth_backend_t * const backend) { - char *username = NULL; - char *realm = NULL; - char *nonce = NULL; - char *uri = NULL; - char *algorithm = NULL; - char *qop = NULL; - char *cnonce = NULL; - char *nc = NULL; - char *respons = NULL; - - char *e, *c; - int i; - buffer *b; - http_auth_info_t ai; - unsigned char rdigest[MD_DIGEST_LENGTH_MAX]; - - - /* init pointers */ -#define S(x) \ - x, sizeof(x)-1, NULL - digest_kv dkv[10] = { - { S("username=") }, - { S("realm=") }, - { S("nonce=") }, - { S("uri=") }, - { S("algorithm=") }, - { S("qop=") }, - { S("cnonce=") }, - { S("nc=") }, - { S("response=") }, - - { NULL, 0, NULL } - }; -#undef S - - dkv[0].ptr = &username; - dkv[1].ptr = &realm; - dkv[2].ptr = &nonce; - dkv[3].ptr = &uri; - dkv[4].ptr = &algorithm; - dkv[5].ptr = &qop; - dkv[6].ptr = &cnonce; - dkv[7].ptr = &nc; - dkv[8].ptr = &respons; - - if (NULL == backend || NULL == backend->digest) { - if (NULL == backend) - log_error(r->conf.errh, __FILE__, __LINE__, - "auth.backend not configured for %s", r->uri.path.ptr); - else - log_error(r->conf.errh, __FILE__, __LINE__, - "auth.require \"method\" => \"digest\" invalid " - "(try \"basic\"?) for %s", - r->uri.path.ptr); - r->http_status = 500; - r->handler_module = NULL; - return HANDLER_FINISHED; - } - const buffer * const vb = - http_header_request_get(r, HTTP_HEADER_AUTHORIZATION, - CONST_STR_LEN("Authorization")); +__attribute_cold__ +static handler_t +mod_auth_digest_misconfigured (request_st * const r, const struct http_auth_backend_t * const backend) +{ + if (NULL == backend) + log_error(r->conf.errh, __FILE__, __LINE__, + "auth.backend not configured for %s", r->uri.path.ptr); + else + log_error(r->conf.errh, __FILE__, __LINE__, + "auth.require \"method\" => \"digest\" invalid " + "(try \"basic\"?) for %s", r->uri.path.ptr); + + r->http_status = 500; + r->handler_module = NULL; + return HANDLER_FINISHED; +} - if (NULL == vb || !buffer_eq_icase_ssn(vb->ptr, CONST_STR_LEN("Digest "))) { - return mod_auth_send_401_unauthorized_digest(r, require, 0); - } else { - size_t n = buffer_clen(vb); - #ifdef __COVERITY__ - if (n < sizeof("Digest ")-1) { - return mod_auth_send_400_bad_request(r); - } - #endif - n -= (sizeof("Digest ")-1); - b = buffer_init(); - buffer_copy_string_len(b,vb->ptr+sizeof("Digest ")-1,n); - } - /* parse credentials from client */ - for (c = b->ptr; *c; c++) { - /* skip whitespaces */ - while (*c == ' ' || *c == '\t') c++; - if (!*c) break; - - for (i = 0; dkv[i].key; i++) { - if ((0 == strncmp(c, dkv[i].key, dkv[i].key_len))) { - if ((c[dkv[i].key_len] == '"') && - (NULL != (e = strchr(c + dkv[i].key_len + 1, '"')))) { - /* value with "..." */ - *(dkv[i].ptr) = c + dkv[i].key_len + 1; - c = e; - - *e = '\0'; - } else if (NULL != (e = strchr(c + dkv[i].key_len, ','))) { - /* value without "...", terminated by ',' */ - *(dkv[i].ptr) = c + dkv[i].key_len; - c = e; - - *e = '\0'; - } else { - /* value without "...", terminated by EOL */ - *(dkv[i].ptr) = c + dkv[i].key_len; - c += strlen(c) - 1; - } - break; - } - } - } +static void +mod_auth_digest_parse_authorization (http_auth_digest_params_t * const dp, const buffer * const vb) +{ + struct digest_kv { + const char *key; + uint32_t klen; + enum http_auth_digest_params_e id; + }; - /* check if everything is transmitted */ - if (!username || - !realm || - !nonce || - !uri || - (qop && (!nc || !cnonce)) || - !respons ) { - /* missing field */ + static const struct digest_kv dkv[] = { + { CONST_STR_LEN("username="), e_username }, + { CONST_STR_LEN("realm="), e_realm }, + { CONST_STR_LEN("nonce="), e_nonce }, + { CONST_STR_LEN("uri="), e_uri }, + { CONST_STR_LEN("algorithm="), e_algorithm }, + { CONST_STR_LEN("qop="), e_qop }, + { CONST_STR_LEN("cnonce="), e_cnonce }, + { CONST_STR_LEN("nc="), e_nc }, + { CONST_STR_LEN("response="), e_response }, + + { NULL, 0, http_auth_digest_params_sz } + }; - log_error(r->conf.errh, __FILE__, __LINE__, - "digest: missing field"); + /* parse credentials from client */ + /* note: parsing does not recognize and handle BWS (bad whitespace) */ + /* XXX: might find end of token and strncmp() only when len matches */ + /* (caller already checked that vb->ptr begins with "Digest ") */ + /* coverity[overflow_sink : FALSE] */ + for (const char *c = vb->ptr+sizeof("Digest ")-1, *e; *c; c++) { + /* skip whitespaces */ + while (*c == ' ' || *c == '\t') c++; + if (!*c) break; + + for (int i = 0; dkv[i].key; ++i) { + if (0 != strncmp(c, dkv[i].key, dkv[i].klen)) + continue; - buffer_free(b); - return mod_auth_send_400_bad_request(r); - } + if ((c[dkv[i].klen] == '"') && + (NULL != (e = strchr(c + dkv[i].klen + 1, '"')))) { + /* value with "..." */ + c += dkv[i].klen + 1; + dp->ptr[dkv[i].id] = c; + dp->len[dkv[i].id] = (uint16_t)(e - c); + c = e; + } + else if (NULL != (e = strchr(c + dkv[i].klen, ','))) { + /* value without "...", terminated by ',' */ + c += dkv[i].klen; + dp->ptr[dkv[i].id] = c; + dp->len[dkv[i].id] = (uint16_t)(e - c); + c = e; + } + else { + /* value without "...", terminated by EOL */ + c += dkv[i].klen; + dp->ptr[dkv[i].id] = c; + c += (dp->len[dkv[i].id] = (uint16_t)strlen(c)) - 1; + } + break; + } + } +} - ai.username = username; - ai.ulen = strlen(username); - ai.realm = realm; - ai.rlen = strlen(realm); - if (!buffer_is_equal_string(require->realm, ai.realm, ai.rlen)) { - log_error(r->conf.errh, __FILE__, __LINE__, - "digest: realm mismatch"); - buffer_free(b); - return mod_auth_send_401_unauthorized_digest(r, require, 0); - } +static handler_t +mod_auth_digest_validate_params (request_st * const r, const struct http_auth_require_t * const require, http_auth_digest_params_t * const dp, http_auth_info_t * const ai) +{ + /* check for required parameters */ + if ((!dp->ptr[e_qop] || (dp->ptr[e_nc] && dp->ptr[e_cnonce])) + && dp->ptr[e_username] + && dp->ptr[e_realm] + && dp->ptr[e_nonce] + && dp->ptr[e_uri] + && dp->ptr[e_response]) { + ai->username = dp->ptr[e_username]; + ai->ulen = dp->len[e_username]; + ai->realm = dp->ptr[e_realm]; + ai->rlen = dp->len[e_realm]; + } + else { + log_error(r->conf.errh, __FILE__, __LINE__, + "digest: missing field"); + return mod_auth_send_400_bad_request(r); + } - if (!mod_auth_algorithm_parse(&ai, algorithm, strlen(algorithm)) - || !(require->algorithm & ai.dalgo & ~HTTP_AUTH_DIGEST_SESS)) { - log_error(r->conf.errh, __FILE__, __LINE__, - "digest: (%s): invalid", algorithm); - buffer_free(b); - return mod_auth_send_401_unauthorized_digest(r, require, 0); - } + if (!buffer_eq_slen(require->realm, ai->realm, ai->rlen)) { + log_error(r->conf.errh, __FILE__, __LINE__, + "digest: realm mismatch"); + return mod_auth_send_401_unauthorized_digest(r, require, 0); + } - /** - * protect the md5-sess against missing cnonce and nonce - */ - if ((ai.dalgo & HTTP_AUTH_DIGEST_SESS) && (!nonce || !cnonce)) { - log_error(r->conf.errh, __FILE__, __LINE__, - "digest: (%s): missing field", algorithm); + if (!mod_auth_algorithm_parse(ai,dp->ptr[e_algorithm],dp->len[e_algorithm]) + || !(require->algorithm & ai->dalgo & ~HTTP_AUTH_DIGEST_SESS)) { + log_error(r->conf.errh, __FILE__, __LINE__, + "digest: (%.*s): invalid", + (int)dp->len[e_algorithm], dp->ptr[e_algorithm]); + return mod_auth_send_401_unauthorized_digest(r, require, 0); + } - buffer_free(b); - return mod_auth_send_400_bad_request(r); - } + /* *-sess requires nonce and cnonce */ + if ((ai->dalgo & HTTP_AUTH_DIGEST_SESS) + && (!dp->ptr[e_nonce] || !dp->ptr[e_cnonce])) { + log_error(r->conf.errh, __FILE__, __LINE__, + "digest: (%.*s): missing field", + (int)dp->len[e_algorithm], dp->ptr[e_algorithm]); + return mod_auth_send_400_bad_request(r); + } - { - size_t resplen = strlen(respons); - if (0 != li_hex2bin(rdigest, sizeof(rdigest), respons, resplen) - || resplen != (ai.dlen << 1)) { - log_error(r->conf.errh, __FILE__, __LINE__, - "digest: (%s): invalid format", respons); - buffer_free(b); - return mod_auth_send_400_bad_request(r); - } - } + if (0 != li_hex2bin(dp->rdigest, sizeof(dp->rdigest), + dp->ptr[e_response], dp->len[e_response]) + || dp->len[e_response] != (ai->dlen << 1)) { + log_error(r->conf.errh, __FILE__, __LINE__, + "digest: (%s): invalid format", dp->ptr[e_response]); + return mod_auth_send_400_bad_request(r); + } - if (qop && buffer_eq_icase_ss(qop, strlen(qop), CONST_STR_LEN("auth-int"))){ - log_error(r->conf.errh, __FILE__, __LINE__, - "digest: qop=auth-int not supported"); + if (dp->ptr[e_qop]&& buffer_eq_icase_ss(dp->ptr[e_qop], dp->len[e_qop], + CONST_STR_LEN("auth-int"))) { + log_error(r->conf.errh, __FILE__, __LINE__, + "digest: qop=auth-int not supported"); + return mod_auth_send_400_bad_request(r); + } - buffer_free(b); - return mod_auth_send_400_bad_request(r); - } + /* detect if attacker is attempting to reuse valid digest for one uri + * on a different request uri. Might also happen if intermediate proxy + * altered client request line. (Altered request would not result in + * the same digest as that calculated by the client.) + * Internal redirects such as with mod_rewrite will modify request uri. + * Reauthentication is done to detect crossing auth realms, but this + * uri validation step is bypassed. r->target_orig is + * original request-target sent in client request. */ + if (!buffer_eq_slen(&r->target_orig, dp->ptr[e_uri], dp->len[e_uri])) { + log_error(r->conf.errh, __FILE__, __LINE__, + "digest: auth failed: uri mismatch (%s != %.*s), IP: %s", + r->target_orig.ptr, (int)dp->len[e_uri], dp->ptr[e_uri], + r->con->dst_addr_buf->ptr); + return mod_auth_send_400_bad_request(r); + } - /* detect if attacker is attempting to reuse valid digest for one uri - * on a different request uri. Might also happen if intermediate proxy - * altered client request line. (Altered request would not result in - * the same digest as that calculated by the client.) - * Internal redirects such as with mod_rewrite will modify request uri. - * Reauthentication is done to detect crossing auth realms, but this - * uri validation step is bypassed. r->target_orig is - * original request-target sent in client request. */ - { - const size_t ulen = strlen(uri); - if (!buffer_is_equal_string(&r->target_orig, uri, ulen)) { - log_error(r->conf.errh, __FILE__, __LINE__, - "digest: auth failed: uri mismatch (%s != %s), IP: %s", - r->target_orig.ptr, uri, r->con->dst_addr_buf->ptr); - buffer_free(b); - return mod_auth_send_400_bad_request(r); - } - } + return HANDLER_GO_ON; +} - /* check age of nonce. Note, random data is used in nonce generation - * in mod_auth_send_401_unauthorized_digest(). If that were replaced - * with nanosecond time, then nonce secret would remain unique enough - * for the purposes of Digest auth, and would be reproducible (and - * verifiable) if nanoseconds were included with seconds as part of the - * nonce "timestamp:secret". However, doing so would expose a high - * precision timestamp of the system to attackers. timestamp in nonce - * could theoretically be modified and still produce same md5sum, but - * that is highly unlikely within a 10 min (moving) window of valid - * time relative to current time (now). - * If it is desired to validate that nonces were generated by server, - * then specify auth.require = ( ... => ( "secret" => "..." ) ) - * When secret is specified, then instead of nanoseconds, the random - * data value (included for unique nonces) will be exposed in the nonce - * along with the timestamp, and the additional secret will be used to - * validate that the server generated the nonce using that secret. */ - time_t send_nextnonce; - { - time_t ts = 0; - const unsigned char * const nonce_uns = (unsigned char *)nonce; - for (i = 0; i < 8 && light_isxdigit(nonce_uns[i]); ++i) { - ts =(time_t)((uint32_t)ts << 4) | hex2int(nonce_uns[i]); - } - const time_t cur_ts = log_epoch_secs; - if (nonce[i] != ':' - || ts > cur_ts || cur_ts - ts > 600) { /*(10 mins)*/ - /* nonce is stale; have client regenerate digest */ - buffer_free(b); - return mod_auth_send_401_unauthorized_digest(r, require, ai.dalgo); - } - send_nextnonce = (cur_ts - ts > 540) ? cur_ts : 0; /*(9 mins)*/ +static handler_t +mod_auth_digest_validate_nonce (request_st * const r, const struct http_auth_require_t * const require, http_auth_digest_params_t * const dp, http_auth_info_t * const ai) +{ + /* check age of nonce. Note, random data is used in nonce generation + * in mod_auth_send_401_unauthorized_digest(). If that were replaced + * with nanosecond time, then nonce secret would remain unique enough + * for the purposes of Digest auth, and would be reproducible (and + * verifiable) if nanoseconds were included with seconds as part of the + * nonce "timestamp:secret". However, doing so would expose a high + * precision timestamp of the system to attackers. timestamp in nonce + * could theoretically be modified and still produce same md5sum, but + * that is highly unlikely within a 10 min (moving) window of valid + * time relative to current time (now). + * If it is desired to validate that nonces were generated by server, + * then specify auth.require = ( ... => ( "secret" => "..." ) ) + * When secret is specified, then instead of nanoseconds, the random + * data value (included for unique nonces) will be exposed in the nonce + * along with the timestamp, and the additional secret will be used to + * validate that the server generated the nonce using that secret. */ + time_t ts = 0; + const unsigned char * const nonce = (unsigned char *)dp->ptr[e_nonce]; + int i; + for (i = 0; i < 8 && light_isxdigit(nonce[i]); ++i) + ts =(time_t)((uint32_t)ts << 4) | hex2int(nonce[i]); + + const time_t cur_ts = log_epoch_secs; + if (nonce[i] != ':' || ts > cur_ts || cur_ts - ts > 600) { /*(10 mins)*/ + /* nonce is stale; have client regenerate digest */ + return mod_auth_send_401_unauthorized_digest(r, require, ai->dalgo); + } - if (require->nonce_secret) { - unsigned int rnd = 0; - for (int j = i+8; i < j && light_isxdigit(nonce_uns[i]); ++i) { - rnd = (rnd << 4) + hex2int(nonce_uns[i]); - } - if (nonce[i] != ':') { - /* nonce is invalid; - * expect extra field w/ require->nonce_secret */ - log_error(r->conf.errh, __FILE__, __LINE__, - "digest: nonce invalid"); - buffer_free(b); - return mod_auth_send_400_bad_request(r); - } - buffer * const tb = r->tmp_buf; - buffer_clear(tb); - mod_auth_append_nonce(tb, cur_ts, require, ai.dalgo, (int *)&rnd); - if (!buffer_eq_slen(tb, nonce, strlen(nonce))) { - /* nonce not generated using current require->nonce_secret */ - log_error(r->conf.errh, __FILE__, __LINE__, - "digest: nonce mismatch"); - buffer_free(b); - return mod_auth_send_401_unauthorized_digest(r, require, 0); - } - } - } + if (cur_ts - ts > 540) /*(9 mins)*/ + dp->send_nextnonce_ts = cur_ts; - handler_t rc; - rc = mod_auth_digest_get(r, p_d, require, backend, &ai); - if (__builtin_expect( (HANDLER_GO_ON != rc), 0)) { - buffer_free(b); - return rc; - } + if (require->nonce_secret) { + unsigned int rnd = 0; + for (int j = i+8; i < j && light_isxdigit(nonce[i]); ++i) { + rnd = (rnd << 4) + hex2int(nonce[i]); + } + if (nonce[i] != ':') { + /* nonce is invalid; + * expect extra field w/ require->nonce_secret */ + log_error(r->conf.errh, __FILE__, __LINE__, + "digest: nonce invalid"); + return mod_auth_send_400_bad_request(r); + } + buffer * const tb = r->tmp_buf; + buffer_clear(tb); + mod_auth_append_nonce(tb, cur_ts, require, ai->dalgo, (int *)&rnd); + if (!buffer_eq_slen(tb, dp->ptr[e_nonce], dp->len[e_nonce])) { + /* nonce not generated using current require->nonce_secret */ + log_error(r->conf.errh, __FILE__, __LINE__, + "digest: nonce mismatch"); + return mod_auth_send_401_unauthorized_digest(r, require, 0); + } + } - const char *m = get_http_method_name(r->http_method); - force_assert(m); + return HANDLER_GO_ON; +} - mod_auth_digest_mutate(&ai,m,uri,nonce,cnonce,nc,qop); - if (!ck_memeq_const_time_fixed_len(rdigest, ai.digest, ai.dlen)) { - /*ck_memzero(ai.digest, ai.dlen);*//*skip clear since mutated*/ - /* digest not ok */ - log_error(r->conf.errh, __FILE__, __LINE__, - "digest: auth failed for %s: wrong password, IP: %s", - username, r->con->dst_addr_buf->ptr); - r->keep_alive = -1; /*(disable keep-alive if bad password)*/ +static handler_t +mod_auth_check_digest (request_st * const r, void *p_d, const struct http_auth_require_t * const require, const struct http_auth_backend_t * const backend) +{ + if (NULL == backend || NULL == backend->digest) + return mod_auth_digest_misconfigured(r, backend); - buffer_free(b); - return mod_auth_send_401_unauthorized_digest(r, require, 0); - } - /*ck_memzero(ai.digest, ai.dlen);*//* skip clear since mutated */ + const buffer * const vb = + http_header_request_get(r, HTTP_HEADER_AUTHORIZATION, + CONST_STR_LEN("Authorization")); + if (NULL == vb || !buffer_eq_icase_ssn(vb->ptr, CONST_STR_LEN("Digest "))) + return mod_auth_send_401_unauthorized_digest(r, require, 0); + #ifdef __COVERITY__ + if (buffer_clen(vb) < sizeof("Digest ")-1) + return mod_auth_send_400_bad_request(r); + #endif - /* value is our allow-rules */ - if (!http_auth_match_rules(require, username, NULL, NULL)) { - buffer_free(b); - return mod_auth_send_401_unauthorized_digest(r, require, 0); - } + http_auth_digest_params_t dp; + http_auth_info_t ai; + handler_t rc; - if (send_nextnonce) { - /*(send nextnonce when expiration is approaching)*/ - mod_auth_digest_authentication_info( - http_header_response_set_ptr(r, HTTP_HEADER_OTHER, - CONST_STR_LEN("Authentication-Info")), - send_nextnonce /*(cur_ts)*/, require, ai.dalgo); - } + memset(&dp, 0, sizeof(dp) - sizeof(dp.rdigest)); - http_auth_setenv(r, ai.username, ai.ulen, CONST_STR_LEN("Digest")); + mod_auth_digest_parse_authorization(&dp, vb); - buffer_free(b); + rc = mod_auth_digest_validate_params(r, require, &dp, &ai); + if (__builtin_expect( (HANDLER_GO_ON != rc), 0)) + return rc; - return HANDLER_GO_ON; -} + rc = mod_auth_digest_validate_nonce(r, require, &dp, &ai); + if (__builtin_expect( (HANDLER_GO_ON != rc), 0)) + return rc; -static handler_t mod_auth_send_401_unauthorized_digest(request_st * const r, const struct http_auth_require_t * const require, int nonce_stale) { - r->http_status = 401; - r->handler_module = NULL; - mod_auth_digest_www_authenticate( - http_header_response_set_ptr(r, HTTP_HEADER_WWW_AUTHENTICATE, - CONST_STR_LEN("WWW-Authenticate")), - log_epoch_secs, require, nonce_stale); - return HANDLER_FINISHED; + rc = mod_auth_digest_get(r, p_d, require, backend, &ai); + if (__builtin_expect( (HANDLER_GO_ON != rc), 0)) + return rc; + + mod_auth_digest_mutate(&ai, &dp, get_http_method_name(r->http_method)); + + if (!ck_memeq_const_time_fixed_len(dp.rdigest, ai.digest, ai.dlen)) { + /*ck_memzero(ai.digest, ai.dlen);*//*skip clear since mutated*/ + /* digest not ok */ + log_error(r->conf.errh, __FILE__, __LINE__, + "digest: auth failed for %.*s: wrong password, IP: %s", + (int)ai.ulen, ai.username, r->con->dst_addr_buf->ptr); + r->keep_alive = -1; /*(disable keep-alive if bad password)*/ + return mod_auth_send_401_unauthorized_digest(r, require, 0); + } + /*ck_memzero(ai.digest, ai.dlen);*//* skip clear since mutated */ + + /* check authorization (authz); string args must be '\0'-terminated) */ + buffer * const tb = r->tmp_buf; + buffer_copy_string_len(tb, ai.username, ai.ulen); + if (!http_auth_match_rules(require, tb->ptr, NULL, NULL)) + return mod_auth_send_401_unauthorized_digest(r, require, 0); + + if (dp.send_nextnonce_ts) { + /*(send nextnonce when expiration is approaching)*/ + mod_auth_digest_authentication_info( + http_header_response_set_ptr(r, HTTP_HEADER_OTHER, + CONST_STR_LEN("Authentication-Info")), + dp.send_nextnonce_ts /*(cur_ts)*/, require, ai.dalgo); + } + + http_auth_setenv(r, ai.username, ai.ulen, CONST_STR_LEN("Digest")); + return HANDLER_GO_ON; } + + static handler_t mod_auth_check_extern(request_st * const r, void *p_d, const struct http_auth_require_t * const require, const struct http_auth_backend_t * const backend) { /* require REMOTE_USER already set */ const buffer *vb = http_header_env_get(r, CONST_STR_LEN("REMOTE_USER"));