summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGlenn Strauss <gstrauss@gluelogic.com>2020-02-03 21:08:34 -0500
committerGlenn Strauss <gstrauss@gluelogic.com>2020-07-08 19:54:29 -0400
commite1188e770eb1939387cbbc214c9d16599cddc36b (patch)
treee8be7ad81b7e512ab76b685284e2929a3a0d2048
parent9fdf24468df6420517876d67f52ae0c84faa76d8 (diff)
downloadlighttpd-git-e1188e770eb1939387cbbc214c9d16599cddc36b.tar.gz
[mod_auth] "nonce_secret" option to validate nonce (fixes #2976)
"nonce_secret" option to validate nonce was generated by the server Marginally hardens HTTP Digest Auth. Necessary piece, but not sufficient, to restrict re-use of nonce (mitigations for replay or limiting nonce count reuse via nc=... are not implemented) x-ref: "Digest auth nonces are not validated" https://redmine.lighttpd.net/issues/2976
-rw-r--r--src/http_auth.h1
-rw-r--r--src/mod_auth.c185
2 files changed, 124 insertions, 62 deletions
diff --git a/src/http_auth.h b/src/http_auth.h
index 5c6b59d3..3ce9454c 100644
--- a/src/http_auth.h
+++ b/src/http_auth.h
@@ -29,6 +29,7 @@ struct http_auth_backend_t;
typedef struct http_auth_require_t {
const struct http_auth_scheme_t *scheme;
const buffer *realm;
+ const buffer *nonce_secret;
int valid_user;
int algorithm;
array user;
diff --git a/src/mod_auth.c b/src/mod_auth.c
index 3b517d27..442fed73 100644
--- a/src/mod_auth.c
+++ b/src/mod_auth.c
@@ -263,6 +263,7 @@ static handler_t mod_auth_require_parse_array(const array *value, array * const
size_t m;
data_array *da_file = (data_array *)value->data[n];
const buffer *method = NULL, *realm = NULL, *require = NULL;
+ const buffer *nonce_secret = NULL;
const http_auth_scheme_t *auth_scheme;
buffer *algos = NULL;
int algorithm = HTTP_AUTH_DIGEST_SESS;
@@ -286,6 +287,8 @@ static handler_t mod_auth_require_parse_array(const array *value, array * const
require = &ds->value;
} else if (buffer_is_equal_string(&ds->key, CONST_STR_LEN("algorithm"))) {
algos = &ds->value;
+ } else if (buffer_is_equal_string(&ds->key, CONST_STR_LEN("nonce_secret"))) {
+ nonce_secret = &ds->value;
} else {
log_error(errh, __FILE__, __LINE__,
"the field is unknown in: "
@@ -348,6 +351,7 @@ static handler_t mod_auth_require_parse_array(const array *value, array * const
dauth->require->scheme = auth_scheme;
dauth->require->algorithm = algorithm;
dauth->require->realm = realm;
+ dauth->require->nonce_secret = nonce_secret; /*(NULL is ok)*/
if (!mod_auth_require_parse(dauth->require, require, errh)) {
dauth->fn->free((data_unset *)dauth);
return HANDLER_ERROR;
@@ -662,7 +666,7 @@ static void mod_auth_digest_mutate_sha256(http_auth_info_t *ai, const char *m, c
SHA256_Final(ai->digest, &ctx);
}
-static void mod_auth_digest_nonce_sha256(buffer *b, time_t cur_ts, int rnd) {
+static void mod_auth_digest_nonce_sha256(buffer *b, time_t cur_ts, int rnd, const buffer *secret) {
SHA256_CTX ctx;
size_t len;
unsigned char h[HTTP_AUTH_DIGEST_SHA256_BINLEN];
@@ -673,6 +677,10 @@ static void mod_auth_digest_nonce_sha256(buffer *b, time_t cur_ts, int rnd) {
SHA256_Update(&ctx, (unsigned char *)hh, len);
len = li_itostrn(hh, sizeof(hh), rnd);
SHA256_Update(&ctx, (unsigned char *)hh, len);
+ if (secret) {
+ len = buffer_string_length(secret);
+ SHA256_Update(&ctx, (unsigned char *)secret->ptr, len);
+ }
SHA256_Final(h, &ctx);
li_tohex(hh, sizeof(hh), (const char *)h, sizeof(h));
buffer_append_string_len(b, hh, sizeof(hh)-1);
@@ -731,7 +739,7 @@ static void mod_auth_digest_mutate_sha512_256(http_auth_info_t *ai, const char *
SHA512_256_Final(ai->digest, &ctx);
}
-static void mod_auth_digest_nonce_sha512_256(buffer *b, time_t cur_ts, int rnd) {
+static void mod_auth_digest_nonce_sha512_256(buffer *b, time_t cur_ts, int rnd, const buffer *secret) {
SHA512_CTX ctx;
size_t len;
unsigned char h[HTTP_AUTH_DIGEST_SHA512_256_BINLEN];
@@ -742,6 +750,10 @@ static void mod_auth_digest_nonce_sha512_256(buffer *b, time_t cur_ts, int rnd)
SHA512_256_Update(&ctx, (unsigned char *)hh, len);
len = li_itostrn(hh, sizeof(hh), rnd);
SHA512_256_Update(&ctx, (unsigned char *)hh, len);
+ if (secret) {
+ len = buffer_string_length(secret);
+ SHA512_256_Update(&ctx, (unsigned char *)secret->ptr, len);
+ }
SHA512_256_Final(h, &ctx);
li_tohex(hh, sizeof(hh), (const char *)h, sizeof(h));
buffer_append_string_len(b, hh, sizeof(hh)-1);
@@ -804,7 +816,7 @@ static void mod_auth_digest_mutate_md5(http_auth_info_t *ai, const char *m, cons
li_MD5_Final(ai->digest, &ctx);
}
-static void mod_auth_digest_nonce_md5(buffer *b, time_t cur_ts, int rnd) {
+static void mod_auth_digest_nonce_md5(buffer *b, time_t cur_ts, int rnd, const buffer *secret) {
li_MD5_CTX ctx;
size_t len;
unsigned char h[HTTP_AUTH_DIGEST_MD5_BINLEN];
@@ -815,6 +827,10 @@ static void mod_auth_digest_nonce_md5(buffer *b, time_t cur_ts, int rnd) {
li_MD5_Update(&ctx, (unsigned char *)hh, len);
len = li_itostrn(hh, sizeof(hh), rnd);
li_MD5_Update(&ctx, (unsigned char *)hh, len);
+ if (secret) {
+ len = buffer_string_length(secret);
+ li_MD5_Update(&ctx, (unsigned char *)secret->ptr, len);
+ }
li_MD5_Final(h, &ctx);
li_tohex(hh, sizeof(hh), (const char *)h, sizeof(h));
buffer_append_string_len(b, hh, sizeof(hh)-1);
@@ -833,31 +849,62 @@ static void mod_auth_digest_mutate(http_auth_info_t *ai, const char *m, const ch
#endif
}
+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;
+ int rnd;
+ if (NULL == nonce_secret)
+ rnd = rndptr ? *rndptr : li_rand_pseudo();
+ else { /*(do not directly expose random number generator single value)*/
+ rndptr
+ ? (void)(rnd = *rndptr)
+ : li_rand_pseudo_bytes((unsigned char *)&rnd, sizeof(rnd));
+ buffer_append_uint_hex(b, (uintmax_t)rnd);
+ buffer_append_string_len(b, CONST_STR_LEN(":"));
+ }
+ switch (dalgo) {
+ #ifdef USE_OPENSSL_CRYPTO
+ #ifdef SHA512_256_DIGEST_LENGTH
+ case HTTP_AUTH_DIGEST_SHA512_256:
+ mod_auth_digest_nonce_sha512_256(b, cur_ts, rnd, nonce_secret);
+ break;
+ #endif
+ case HTTP_AUTH_DIGEST_SHA256:
+ mod_auth_digest_nonce_sha256(b, cur_ts, rnd, nonce_secret);
+ break;
+ #endif
+ /*case HTTP_AUTH_DIGEST_MD5:*/
+ default:
+ mod_auth_digest_nonce_md5(b, cur_ts, rnd, nonce_secret);
+ break;
+ }
+}
+
static void mod_auth_digest_www_authenticate(buffer *b, time_t cur_ts, const struct http_auth_require_t *require, int nonce_stale) {
- const int rnd = li_rand_pseudo();
int algos = nonce_stale ? nonce_stale : require->algorithm;
int n = 0;
- void(*append_nonce[3])(buffer *, time_t, int);
+ int algoid[3];
unsigned int algolen[3];
const char *algoname[3];
#ifdef USE_OPENSSL_CRYPTO
#ifdef SHA512_256_DIGEST_LENGTH
if (algos & HTTP_AUTH_DIGEST_SHA512_256) {
- append_nonce[n] = mod_auth_digest_nonce_sha512_256;
+ algoid[n] = HTTP_AUTH_DIGEST_SHA512_256;
algoname[n] = "SHA-512-256";
algolen[n] = sizeof("SHA-512-256")-1;
++n;
}
#endif
if (algos & HTTP_AUTH_DIGEST_SHA256) {
- append_nonce[n] = mod_auth_digest_nonce_sha256;
+ algoid[n] = HTTP_AUTH_DIGEST_SHA256;
algoname[n] = "SHA-256";
algolen[n] = sizeof("SHA-256")-1;
++n;
}
#endif
if (algos & HTTP_AUTH_DIGEST_MD5) {
- append_nonce[n] = mod_auth_digest_nonce_md5;
+ algoid[n] = HTTP_AUTH_DIGEST_MD5;
algoname[n] = "MD5";
algolen[n] = sizeof("MD5")-1;
++n;
@@ -873,9 +920,7 @@ static void mod_auth_digest_www_authenticate(buffer *b, time_t cur_ts, const str
buffer_append_string_len(b, CONST_STR_LEN("\", charset=\"UTF-8\", algorithm="));
buffer_append_string_len(b, algoname[i], algolen[i]);
buffer_append_string_len(b, CONST_STR_LEN(", nonce=\""));
- buffer_append_uint_hex(b, (uintmax_t)cur_ts);
- buffer_append_string_len(b, CONST_STR_LEN(":"));
- (append_nonce[i])(b, cur_ts, rnd);
+ mod_auth_append_nonce(b, cur_ts, require, algoid[i], NULL);
buffer_append_string_len(b, CONST_STR_LEN("\", qop=\"auth\""));
if (nonce_stale) {
buffer_append_string_len(b, CONST_STR_LEN(", stale=true"));
@@ -883,30 +928,10 @@ static void mod_auth_digest_www_authenticate(buffer *b, time_t cur_ts, const str
}
}
-static void mod_auth_digest_authentication_info(buffer *b, time_t cur_ts, int dalgo) {
- const int rnd = li_rand_pseudo();
- void(*append_nonce)(buffer *, time_t, int);
- switch (dalgo) {
- #ifdef USE_OPENSSL_CRYPTO
- #ifdef SHA512_256_DIGEST_LENGTH
- case HTTP_AUTH_DIGEST_SHA512_256:
- append_nonce = mod_auth_digest_nonce_sha512_256;
- break;
- #endif
- case HTTP_AUTH_DIGEST_SHA256:
- append_nonce = mod_auth_digest_nonce_sha256;
- break;
- #endif
- /*case HTTP_AUTH_DIGEST_MD5:*/
- default:
- append_nonce = mod_auth_digest_nonce_md5;
- break;
- }
+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=\""));
- buffer_append_uint_hex(b, (uintmax_t)cur_ts);
- buffer_append_string_len(b, CONST_STR_LEN(":"));
- (append_nonce)(b, cur_ts, rnd);
+ mod_auth_append_nonce(b, cur_ts, require, dalgo, NULL);
buffer_append_string_len(b, CONST_STR_LEN("\""));
}
@@ -932,7 +957,6 @@ static handler_t mod_auth_check_digest(request_st * const r, void *p_d, const st
char *respons = NULL;
char *e, *c;
- const char *m = NULL;
int i;
buffer *b;
http_auth_info_t ai;
@@ -1093,9 +1117,6 @@ static handler_t mod_auth_check_digest(request_st * const r, void *p_d, const st
return mod_auth_send_400_bad_request(r);
}
- m = get_http_method_name(r->http_method);
- force_assert(m);
-
/* 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
@@ -1115,6 +1136,65 @@ static handler_t mod_auth_check_digest(request_st * const r, void *p_d, const st
}
}
+ /* 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. */
+ int 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 = (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); /*(9 mins)*/
+
+ 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);
+ }
+ }
+ }
+
switch (backend->digest(r, backend->p_d, &ai)) {
case HANDLER_GO_ON:
break;
@@ -1131,6 +1211,9 @@ static handler_t mod_auth_check_digest(request_st * const r, void *p_d, const st
return mod_auth_send_401_unauthorized_digest(r, require, 0);
}
+ const char *m = get_http_method_name(r->http_method);
+ force_assert(m);
+
mod_auth_digest_mutate(&ai,m,uri,nonce,cnonce,nc,qop);
if (!http_auth_const_time_memeq(rdigest, ai.digest, ai.dlen)) {
@@ -1150,36 +1233,14 @@ static handler_t mod_auth_check_digest(request_st * const r, void *p_d, const st
return mod_auth_send_401_unauthorized_digest(r, require, 0);
}
- /* 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 inclued with seconds as part of the
- * nonce "timestamp:secret". Since that is not done, 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) */
- {
- 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 = (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);
- }
- else if (cur_ts - ts > 540) { /*(9 mins)*/
+ if (send_nextnonce) {
/*(send nextnonce when expiration is approaching)*/
buffer * const tb = r->tmp_buf;
- mod_auth_digest_authentication_info(tb, cur_ts, ai.dalgo);
+ const time_t cur_ts = log_epoch_secs;
+ mod_auth_digest_authentication_info(tb, cur_ts, require, ai.dalgo);
http_header_response_set(r, HTTP_HEADER_OTHER,
CONST_STR_LEN("Authentication-Info"),
CONST_BUF_LEN(tb));
- }
}
http_auth_setenv(r, ai.username, ai.ulen, CONST_STR_LEN("Digest"));