From e6d49295129c495981596fa0b1bd62d21601bf86 Mon Sep 17 00:00:00 2001 From: Joe Orton Date: Tue, 2 Jun 2026 17:01:45 +0100 Subject: [PATCH 1/2] * modules/aaa/mod_auth_digest.c: Remove "weird" override of AuthName directive, which adds complexity for little benefit (avoids putting 20 bytes through SHA1 for each auth attempt). (set_realm): Remove function. (gen_nonce_hash): Create the nonce hash here from scratch. --- modules/aaa/mod_auth_digest.c | 43 ++--------------------------------- 1 file changed, 2 insertions(+), 41 deletions(-) diff --git a/modules/aaa/mod_auth_digest.c b/modules/aaa/mod_auth_digest.c index 2edbc4d3601..6f0780f98f0 100644 --- a/modules/aaa/mod_auth_digest.c +++ b/modules/aaa/mod_auth_digest.c @@ -86,7 +86,6 @@ typedef struct digest_config_struct { const char *dir_name; authn_provider_list *providers; apr_array_header_t *qop_list; - apr_sha1_ctx_t nonce_ctx; apr_time_t nonce_lifetime; int check_nc; const char *algorithm; @@ -481,36 +480,6 @@ static void *create_digest_dir_config(apr_pool_t *p, char *dir) return conf; } - -/* - * The realm is no longer precomputed because it may be an expression, which - * makes this hooking of AuthName quite weird. - */ -static const char *set_realm(cmd_parms *cmd, void *config, const char *realm) -{ - digest_config_rec *conf = (digest_config_rec *) config; -#ifdef AP_DEBUG - int i; - - /* check that we got random numbers */ - for (i = 0; i < SECRET_LEN; i++) { - if (secret[i] != 0) - break; - } - ap_assert(i < SECRET_LEN); -#endif - - /* we precompute the part of the nonce hash that is constant (well, - * the host:port would be too, but that varies for .htaccess files - * and directives outside a virtual host section) - */ - apr_sha1_init(&conf->nonce_ctx); - apr_sha1_update_binary(&conf->nonce_ctx, secret, SECRET_LEN); - - - return DECLINE_CMD; -} - static const char *add_authn_provider(cmd_parms *cmd, void *config, const char *arg) { @@ -679,8 +648,6 @@ static const char *set_shmem_size(cmd_parms *cmd, void *config, static const command_rec digest_cmds[] = { - AP_INIT_TAKE1("AuthName", set_realm, NULL, OR_AUTHCFG, - "The authentication realm (e.g. \"Members Only\")"), AP_INIT_ITERATE("AuthDigestProvider", add_authn_provider, NULL, OR_AUTHCFG, "specify the auth providers for a directory or location"), AP_INIT_ITERATE("AuthDigestQop", set_qop, NULL, OR_AUTHCFG, @@ -1076,14 +1043,8 @@ static void gen_nonce_hash(char hash[NONCE_HASH_LEN+1], const char *timestr, con unsigned char sha1[APR_SHA1_DIGESTSIZE]; apr_sha1_ctx_t ctx; - memcpy(&ctx, &conf->nonce_ctx, sizeof(ctx)); - /* - apr_sha1_update_binary(&ctx, (const unsigned char *) server->server_hostname, - strlen(server->server_hostname)); - apr_sha1_update_binary(&ctx, (const unsigned char *) &server->port, - sizeof(server->port)); - */ - + apr_sha1_init(&ctx); + apr_sha1_update_binary(&ctx, secret, SECRET_LEN); apr_sha1_update_binary(&ctx, (const unsigned char *) realm, strlen(realm)); apr_sha1_update_binary(&ctx, (const unsigned char *) timestr, strlen(timestr)); From f2e7c64d55ea3a8735d5d8131ac24fb88035f645 Mon Sep 17 00:00:00 2001 From: Joe Orton Date: Tue, 2 Jun 2026 17:41:08 +0100 Subject: [PATCH 2/2] mod_auth_digest: Drop RFC 2069 and configurable qop support. (RFC 2617, which replaced 2069, is now 26 years old) * modules/aaa/mod_auth_digest.c (digest_config_rec): Remove qop_list field. (create_digest_dir_config): Remove qop_list initialization. (set_qop): Deprecate AuthDigestQop, only "auth" is supported. (note_digest_auth_failure): Always send qop="auth". (check_nc): Remove handling for qop=none. (old_digest): Remove function. (authenticate_digest_user): Reject requests with missing or non-"auth" qop value rather than falling back to RFC 2069. Co-Authored-By: Claude Opus 4.6 --- modules/aaa/mod_auth_digest.c | 112 +++++----------------------------- 1 file changed, 14 insertions(+), 98 deletions(-) diff --git a/modules/aaa/mod_auth_digest.c b/modules/aaa/mod_auth_digest.c index 6f0780f98f0..03488686591 100644 --- a/modules/aaa/mod_auth_digest.c +++ b/modules/aaa/mod_auth_digest.c @@ -85,7 +85,6 @@ typedef struct digest_config_struct { const char *dir_name; authn_provider_list *providers; - apr_array_header_t *qop_list; apr_time_t nonce_lifetime; int check_nc; const char *algorithm; @@ -471,7 +470,6 @@ static void *create_digest_dir_config(apr_pool_t *p, char *dir) conf = (digest_config_rec *) apr_pcalloc(p, sizeof(digest_config_rec)); if (conf) { - conf->qop_list = apr_array_make(p, 2, sizeof(char *)); conf->nonce_lifetime = DFLT_NONCE_LIFE; conf->dir_name = apr_pstrdup(p, dir); conf->algorithm = DFLT_ALGORITHM; @@ -527,23 +525,10 @@ static const char *add_authn_provider(cmd_parms *cmd, void *config, static const char *set_qop(cmd_parms *cmd, void *config, const char *op) { - digest_config_rec *conf = (digest_config_rec *) config; - - if (!ap_cstr_casecmp(op, "none")) { - apr_array_clear(conf->qop_list); - *(const char **)apr_array_push(conf->qop_list) = "none"; - return NULL; - } - - if (!ap_cstr_casecmp(op, "auth-int")) { - return "AuthDigestQop auth-int is not implemented"; - } - else if (ap_cstr_casecmp(op, "auth")) { - return apr_pstrcat(cmd->pool, "Unrecognized qop: ", op, NULL); + if (!ap_cstr_casecmp(op, "auth")) { + return "AuthDigestQop is deprecated: only 'auth' is supported"; } - *(const char **)apr_array_push(conf->qop_list) = op; - return NULL; } @@ -1140,18 +1125,7 @@ static void note_digest_auth_failure(request_rec *r, const char *qop, *opaque, *opaque_param, *domain, *nonce; /* Setup qop */ - if (apr_is_empty_array(conf->qop_list)) { - qop = ", qop=\"auth\""; - } - else if (!ap_cstr_casecmp(*(const char **)(conf->qop_list->elts), "none")) { - qop = ""; - } - else { - qop = apr_pstrcat(r->pool, ", qop=\"", - apr_array_pstrcat(r->pool, conf->qop_list, ','), - "\"", - NULL); - } + qop = ", qop=\"auth\""; /* Setup opaque */ @@ -1341,19 +1315,6 @@ static int check_nc(const request_rec *r, const digest_header_rec *resp, return OK; } - if (!apr_is_empty_array(conf->qop_list) && - !ap_cstr_casecmp(*(const char **)(conf->qop_list->elts), "none")) { - /* qop is none, client must not send a nonce count */ - if (snc != NULL) { - ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01772) - "invalid nc %s received - no nonce count allowed when qop=none", - snc); - return !OK; - } - /* qop is none, cannot check nonce count */ - return OK; - } - nc = strtol(snc, &endptr, 16); if (endptr < (snc+strlen(snc)) && !apr_isspace(*endptr)) { ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01773) @@ -1434,19 +1395,6 @@ static int check_nonce(request_rec *r, digest_header_rec *resp, /* The actual MD5 code... whee */ -/* RFC-2069 */ -static const char *old_digest(const request_rec *r, - const digest_header_rec *resp) -{ - const char *ha2; - - ha2 = ap_md5(r->pool, (unsigned char *)apr_pstrcat(r->pool, resp->method, ":", - resp->uri, NULL)); - return ap_md5(r->pool, - (unsigned char *)apr_pstrcat(r->pool, resp->ha1, ":", - resp->nonce, ":", ha2, NULL)); -} - /* RFC-2617 */ static const char *new_digest(const request_rec *r, digest_header_rec *resp) @@ -1719,39 +1667,18 @@ static int authenticate_digest_user(request_rec *r) return HTTP_INTERNAL_SERVER_ERROR; } - if (resp->message_qop == NULL) { - /* old (rfc-2069) style digest */ - if (!ap_memeq_timingsafe(old_digest(r, resp), resp->digest, MD5_DIGEST_LEN)) { - ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01792) - "user %s: password mismatch: %s", r->user, - r->uri); - note_digest_auth_failure(r, conf, resp, 0); - return HTTP_UNAUTHORIZED; - } + if (resp->message_qop == NULL + || !ap_cstr_casecmp(resp->message_qop, "auth")) { + /* RFC 2069-style Digest is no longer supported. */ + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(10560) + "invalid or missing qop value '%s', RFC 2069 is " + "no longer supported: %s", resp->message_qop, r->uri); + note_digest_auth_failure(r, conf, resp, 0); + return HTTP_UNAUTHORIZED; } else { - const char *exp_digest; - int match = 0, idx; - const char **tmp = (const char **)(conf->qop_list->elts); - for (idx = 0; idx < conf->qop_list->nelts; idx++) { - if (!ap_cstr_casecmp(*tmp, resp->message_qop)) { - match = 1; - break; - } - ++tmp; - } - - if (!match - && !(apr_is_empty_array(conf->qop_list) - && !ap_cstr_casecmp(resp->message_qop, "auth"))) { - ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01793) - "invalid qop `%s' received: %s", - resp->message_qop, r->uri); - note_digest_auth_failure(r, conf, resp, 0); - return HTTP_UNAUTHORIZED; - } - - exp_digest = new_digest(r, resp); + /* RFC 2617 (or 7616)-style Digest hash calculation. */ + const char *exp_digest = new_digest(r, resp); if (!exp_digest) { /* we failed to allocate a client struct */ return HTTP_INTERNAL_SERVER_ERROR; @@ -1797,9 +1724,6 @@ static int add_auth_info(request_rec *r) return OK; } - /* 2069-style entity-digest is not supported (it's too hard, and - * there are no clients which support 2069 but not 2617). */ - /* setup nextnonce */ if (conf->nonce_lifetime > 0) { @@ -1822,15 +1746,7 @@ static int add_auth_info(request_rec *r) /* else nonce never expires, hence no nextnonce */ - /* do rfc-2069 digest - */ - if (!apr_is_empty_array(conf->qop_list) && - !ap_cstr_casecmp(*(const char **)(conf->qop_list->elts), "none") - && resp->message_qop == NULL) { - /* use only RFC-2069 format */ - ai = nextnonce; - } - else { + { const char *resp_dig, *ha1, *a2, *ha2; /* calculate rspauth attribute