|
|
5b8408 |
From 1bb55df94c0db8376f88a568c331802bd282f097 Mon Sep 17 00:00:00 2001
|
|
|
5b8408 |
From: Hans Zandbelt <hans.zandbelt@zmartzone.eu>
|
|
|
5b8408 |
Date: Wed, 1 Aug 2018 11:41:57 +0200
|
|
|
5b8408 |
Subject: [PATCH 06/11] add OIDCStateMaxNumberOfCookies to limit nr of state
|
|
|
5b8408 |
cookies; see #331
|
|
|
5b8408 |
|
|
|
5b8408 |
Signed-off-by: Hans Zandbelt <hans.zandbelt@zmartzone.eu>
|
|
|
5b8408 |
(cherry picked from commit 5041e0f679f03525f106eaf0edc7c7d245b1d89c)
|
|
|
5b8408 |
---
|
|
|
5b8408 |
ChangeLog | 3 +++
|
|
|
5b8408 |
src/config.c | 19 +++++++++++++
|
|
|
5b8408 |
src/mod_auth_openidc.c | 61 ++++++++++++++++++++++++++++++++++--------
|
|
|
5b8408 |
src/mod_auth_openidc.h | 2 ++
|
|
|
5b8408 |
4 files changed, 74 insertions(+), 11 deletions(-)
|
|
|
5b8408 |
|
|
|
5b8408 |
diff --git a/ChangeLog b/ChangeLog
|
|
|
5b8408 |
index af9380e..b6ac513 100644
|
|
|
5b8408 |
--- a/ChangeLog
|
|
|
5b8408 |
+++ b/ChangeLog
|
|
|
5b8408 |
@@ -1,3 +1,6 @@
|
|
|
5b8408 |
+08/01/2018
|
|
|
5b8408 |
+- add option to set an upper limit to the number of concurrent state cookies via OIDCStateMaxNumberOfCookies; see #331
|
|
|
5b8408 |
+
|
|
|
5b8408 |
07/06/2018
|
|
|
5b8408 |
- abort when string length for remote user name substitution is larger than 255 characters
|
|
|
5b8408 |
- release 2.3.7
|
|
|
5b8408 |
diff --git a/src/config.c b/src/config.c
|
|
|
5b8408 |
index 0185cff..2fd63ea 100644
|
|
|
5b8408 |
--- a/src/config.c
|
|
|
5b8408 |
+++ b/src/config.c
|
|
|
5b8408 |
@@ -104,6 +104,8 @@
|
|
|
5b8408 |
#define OIDC_DEFAULT_SESSION_CLIENT_COOKIE_CHUNK_SIZE 4000
|
|
|
5b8408 |
/* timeout in seconds after which state expires */
|
|
|
5b8408 |
#define OIDC_DEFAULT_STATE_TIMEOUT 300
|
|
|
5b8408 |
+/* maximum number of parallel state cookies; 0 means unlimited, until the browser or server gives up */
|
|
|
5b8408 |
+#define OIDC_DEFAULT_MAX_NUMBER_OF_STATE_COOKIES 0
|
|
|
5b8408 |
/* default session inactivity timeout */
|
|
|
5b8408 |
#define OIDC_DEFAULT_SESSION_INACTIVITY_TIMEOUT 300
|
|
|
5b8408 |
/* default session max duration */
|
|
|
5b8408 |
@@ -227,6 +229,7 @@
|
|
|
5b8408 |
#define OIDCHTTPTimeoutLong "OIDCHTTPTimeoutLong"
|
|
|
5b8408 |
#define OIDCHTTPTimeoutShort "OIDCHTTPTimeoutShort"
|
|
|
5b8408 |
#define OIDCStateTimeout "OIDCStateTimeout"
|
|
|
5b8408 |
+#define OIDCStateMaxNumberOfCookies "OIDCStateMaxNumberOfCookies"
|
|
|
5b8408 |
#define OIDCSessionInactivityTimeout "OIDCSessionInactivityTimeout"
|
|
|
5b8408 |
#define OIDCMetadataDir "OIDCMetadataDir"
|
|
|
5b8408 |
#define OIDCSessionCacheFallbackToCookie "OIDCSessionCacheFallbackToCookie"
|
|
|
5b8408 |
@@ -994,6 +997,12 @@ static const char *oidc_set_client_auth_bearer_token(cmd_parms *cmd,
|
|
|
5b8408 |
return NULL;
|
|
|
5b8408 |
}
|
|
|
5b8408 |
|
|
|
5b8408 |
+int oidc_cfg_max_number_of_state_cookies(oidc_cfg *cfg) {
|
|
|
5b8408 |
+ if (cfg->max_number_of_state_cookies == OIDC_CONFIG_POS_INT_UNSET)
|
|
|
5b8408 |
+ return OIDC_DEFAULT_MAX_NUMBER_OF_STATE_COOKIES;
|
|
|
5b8408 |
+ return cfg->max_number_of_state_cookies;
|
|
|
5b8408 |
+}
|
|
|
5b8408 |
+
|
|
|
5b8408 |
/*
|
|
|
5b8408 |
* create a new server config record with defaults
|
|
|
5b8408 |
*/
|
|
|
5b8408 |
@@ -1102,6 +1111,7 @@ void *oidc_create_server_config(apr_pool_t *pool, server_rec *svr) {
|
|
|
5b8408 |
c->http_timeout_long = OIDC_DEFAULT_HTTP_TIMEOUT_LONG;
|
|
|
5b8408 |
c->http_timeout_short = OIDC_DEFAULT_HTTP_TIMEOUT_SHORT;
|
|
|
5b8408 |
c->state_timeout = OIDC_DEFAULT_STATE_TIMEOUT;
|
|
|
5b8408 |
+ c->max_number_of_state_cookies = OIDC_CONFIG_POS_INT_UNSET;
|
|
|
5b8408 |
c->session_inactivity_timeout = OIDC_DEFAULT_SESSION_INACTIVITY_TIMEOUT;
|
|
|
5b8408 |
|
|
|
5b8408 |
c->cookie_domain = NULL;
|
|
|
5b8408 |
@@ -1416,6 +1426,10 @@ void *oidc_merge_server_config(apr_pool_t *pool, void *BASE, void *ADD) {
|
|
|
5b8408 |
c->state_timeout =
|
|
|
5b8408 |
add->state_timeout != OIDC_DEFAULT_STATE_TIMEOUT ?
|
|
|
5b8408 |
add->state_timeout : base->state_timeout;
|
|
|
5b8408 |
+ c->max_number_of_state_cookies =
|
|
|
5b8408 |
+ add->max_number_of_state_cookies != OIDC_CONFIG_POS_INT_UNSET ?
|
|
|
5b8408 |
+ add->max_number_of_state_cookies :
|
|
|
5b8408 |
+ base->max_number_of_state_cookies;
|
|
|
5b8408 |
c->session_inactivity_timeout =
|
|
|
5b8408 |
add->session_inactivity_timeout
|
|
|
5b8408 |
!= OIDC_DEFAULT_SESSION_INACTIVITY_TIMEOUT ?
|
|
|
5b8408 |
@@ -2627,6 +2641,11 @@ const command_rec oidc_config_cmds[] = {
|
|
|
5b8408 |
(void*)APR_OFFSETOF(oidc_cfg, state_timeout),
|
|
|
5b8408 |
RSRC_CONF,
|
|
|
5b8408 |
"Time to live in seconds for state parameter (cq. interval in which the authorization request and the corresponding response need to be completed)."),
|
|
|
5b8408 |
+ AP_INIT_TAKE1(OIDCStateMaxNumberOfCookies,
|
|
|
5b8408 |
+ oidc_set_int_slot,
|
|
|
5b8408 |
+ (void*)APR_OFFSETOF(oidc_cfg, max_number_of_state_cookies),
|
|
|
5b8408 |
+ RSRC_CONF,
|
|
|
5b8408 |
+ "Maximun number of parallel state cookies i.e. outstanding authorization requests."),
|
|
|
5b8408 |
AP_INIT_TAKE1(OIDCSessionInactivityTimeout,
|
|
|
5b8408 |
oidc_set_session_inactivity_timeout,
|
|
|
5b8408 |
(void*)APR_OFFSETOF(oidc_cfg, session_inactivity_timeout),
|
|
|
5b8408 |
diff --git a/src/mod_auth_openidc.c b/src/mod_auth_openidc.c
|
|
|
5b8408 |
index 74f206b..c0f65c6 100644
|
|
|
5b8408 |
--- a/src/mod_auth_openidc.c
|
|
|
5b8408 |
+++ b/src/mod_auth_openidc.c
|
|
|
5b8408 |
@@ -686,8 +686,14 @@ static apr_byte_t oidc_unsolicited_proto_state(request_rec *r, oidc_cfg *c,
|
|
|
5b8408 |
return TRUE;
|
|
|
5b8408 |
}
|
|
|
5b8408 |
|
|
|
5b8408 |
-static void oidc_clean_expired_state_cookies(request_rec *r, oidc_cfg *c,
|
|
|
5b8408 |
+/*
|
|
|
5b8408 |
+ * clean state cookies that have expired i.e. for outstanding requests that will never return
|
|
|
5b8408 |
+ * successfully and return the number of remaining valid cookies/outstanding-requests while
|
|
|
5b8408 |
+ * doing so
|
|
|
5b8408 |
+ */
|
|
|
5b8408 |
+static int oidc_clean_expired_state_cookies(request_rec *r, oidc_cfg *c,
|
|
|
5b8408 |
const char *currentCookieName) {
|
|
|
5b8408 |
+ int number_of_valid_state_cookies = 0;
|
|
|
5b8408 |
char *cookie, *tokenizerCtx;
|
|
|
5b8408 |
char *cookies = apr_pstrdup(r->pool, oidc_util_hdr_in_cookie_get(r));
|
|
|
5b8408 |
if (cookies != NULL) {
|
|
|
5b8408 |
@@ -715,6 +721,8 @@ static void oidc_clean_expired_state_cookies(request_rec *r, oidc_cfg *c,
|
|
|
5b8408 |
cookieName);
|
|
|
5b8408 |
oidc_util_set_cookie(r, cookieName, "", 0,
|
|
|
5b8408 |
NULL);
|
|
|
5b8408 |
+ } else {
|
|
|
5b8408 |
+ number_of_valid_state_cookies++;
|
|
|
5b8408 |
}
|
|
|
5b8408 |
oidc_proto_state_destroy(proto_state);
|
|
|
5b8408 |
}
|
|
|
5b8408 |
@@ -724,6 +732,7 @@ static void oidc_clean_expired_state_cookies(request_rec *r, oidc_cfg *c,
|
|
|
5b8408 |
cookie = apr_strtok(NULL, OIDC_STR_SEMI_COLON, &tokenizerCtx);
|
|
|
5b8408 |
}
|
|
|
5b8408 |
}
|
|
|
5b8408 |
+ return number_of_valid_state_cookies;
|
|
|
5b8408 |
}
|
|
|
5b8408 |
|
|
|
5b8408 |
/*
|
|
|
5b8408 |
@@ -796,7 +805,7 @@ static apr_byte_t oidc_restore_proto_state(request_rec *r, oidc_cfg *c,
|
|
|
5b8408 |
* set the state that is maintained between an authorization request and an authorization response
|
|
|
5b8408 |
* in a cookie in the browser that is cryptographically bound to that state
|
|
|
5b8408 |
*/
|
|
|
5b8408 |
-static apr_byte_t oidc_authorization_request_set_cookie(request_rec *r,
|
|
|
5b8408 |
+static int oidc_authorization_request_set_cookie(request_rec *r,
|
|
|
5b8408 |
oidc_cfg *c, const char *state, oidc_proto_state_t *proto_state) {
|
|
|
5b8408 |
/*
|
|
|
5b8408 |
* create a cookie consisting of 8 elements:
|
|
|
5b8408 |
@@ -805,10 +814,32 @@ static apr_byte_t oidc_authorization_request_set_cookie(request_rec *r,
|
|
|
5b8408 |
*/
|
|
|
5b8408 |
char *cookieValue = oidc_proto_state_to_cookie(r, c, proto_state);
|
|
|
5b8408 |
if (cookieValue == NULL)
|
|
|
5b8408 |
- return FALSE;
|
|
|
5b8408 |
+ return HTTP_INTERNAL_SERVER_ERROR;
|
|
|
5b8408 |
|
|
|
5b8408 |
- /* clean expired state cookies to avoid pollution */
|
|
|
5b8408 |
- oidc_clean_expired_state_cookies(r, c, NULL);
|
|
|
5b8408 |
+ /*
|
|
|
5b8408 |
+ * clean expired state cookies to avoid pollution and optionally
|
|
|
5b8408 |
+ * try to avoid the number of state cookies exceeding a max
|
|
|
5b8408 |
+ */
|
|
|
5b8408 |
+ int number_of_cookies = oidc_clean_expired_state_cookies(r, c, NULL);
|
|
|
5b8408 |
+ int max_number_of_cookies = oidc_cfg_max_number_of_state_cookies(c);
|
|
|
5b8408 |
+ if ((max_number_of_cookies > 0)
|
|
|
5b8408 |
+ && (number_of_cookies >= max_number_of_cookies)) {
|
|
|
5b8408 |
+ oidc_warn(r,
|
|
|
5b8408 |
+ "the number of existing, valid state cookies (%d) has exceeded the limit (%d), no additional authorization request + state cookie can be generated, aborting the request",
|
|
|
5b8408 |
+ number_of_cookies, max_number_of_cookies);
|
|
|
5b8408 |
+ /*
|
|
|
5b8408 |
+ * TODO: the html_send code below caters for the case that there's a user behind a
|
|
|
5b8408 |
+ * browser generating this request, rather than a piece of XHR code; how would an
|
|
|
5b8408 |
+ * XHR client handle this?
|
|
|
5b8408 |
+ */
|
|
|
5b8408 |
+
|
|
|
5b8408 |
+ return oidc_util_html_send_error(r, c->error_template,
|
|
|
5b8408 |
+ "Too Many Outstanding Requests",
|
|
|
5b8408 |
+ apr_psprintf(r->pool,
|
|
|
5b8408 |
+ "No authentication request could be generated since there are too many outstanding authentication requests already; you may have to wait up to %d seconds to be able to create a new request",
|
|
|
5b8408 |
+ c->state_timeout),
|
|
|
5b8408 |
+ HTTP_SERVICE_UNAVAILABLE);
|
|
|
5b8408 |
+ }
|
|
|
5b8408 |
|
|
|
5b8408 |
/* assemble the cookie name for the state cookie */
|
|
|
5b8408 |
const char *cookieName = oidc_get_state_cookie_name(r, state);
|
|
|
5b8408 |
@@ -817,9 +848,7 @@ static apr_byte_t oidc_authorization_request_set_cookie(request_rec *r,
|
|
|
5b8408 |
oidc_util_set_cookie(r, cookieName, cookieValue, -1,
|
|
|
5b8408 |
c->cookie_same_site ? OIDC_COOKIE_EXT_SAME_SITE_LAX : NULL);
|
|
|
5b8408 |
|
|
|
5b8408 |
- //free(s_value);
|
|
|
5b8408 |
-
|
|
|
5b8408 |
- return TRUE;
|
|
|
5b8408 |
+ return HTTP_OK;
|
|
|
5b8408 |
}
|
|
|
5b8408 |
|
|
|
5b8408 |
/*
|
|
|
5b8408 |
@@ -2245,12 +2274,19 @@ static int oidc_authenticate_user(request_rec *r, oidc_cfg *c,
|
|
|
5b8408 |
/* get a hash value that fingerprints the browser concatenated with the random input */
|
|
|
5b8408 |
char *state = oidc_get_browser_state_hash(r, nonce);
|
|
|
5b8408 |
|
|
|
5b8408 |
- /* create state that restores the context when the authorization response comes in; cryptographically bind it to the browser */
|
|
|
5b8408 |
- if (oidc_authorization_request_set_cookie(r, c, state, proto_state) == FALSE)
|
|
|
5b8408 |
- return HTTP_INTERNAL_SERVER_ERROR;
|
|
|
5b8408 |
+ /*
|
|
|
5b8408 |
+ * create state that restores the context when the authorization response comes in
|
|
|
5b8408 |
+ * and cryptographically bind it to the browser
|
|
|
5b8408 |
+ */
|
|
|
5b8408 |
+ int rc = oidc_authorization_request_set_cookie(r, c, state, proto_state);
|
|
|
5b8408 |
+ if (rc != HTTP_OK) {
|
|
|
5b8408 |
+ oidc_proto_state_destroy(proto_state);
|
|
|
5b8408 |
+ return rc;
|
|
|
5b8408 |
+ }
|
|
|
5b8408 |
|
|
|
5b8408 |
/*
|
|
|
5b8408 |
* printout errors if Cookie settings are not going to work
|
|
|
5b8408 |
+ * TODO: separate this code out into its own function
|
|
|
5b8408 |
*/
|
|
|
5b8408 |
apr_uri_t o_uri;
|
|
|
5b8408 |
memset(&o_uri, 0, sizeof(apr_uri_t));
|
|
|
5b8408 |
@@ -2263,6 +2299,7 @@ static int oidc_authenticate_user(request_rec *r, oidc_cfg *c,
|
|
|
5b8408 |
oidc_error(r,
|
|
|
5b8408 |
"the URL scheme (%s) of the configured " OIDCRedirectURI " does not match the URL scheme of the URL being accessed (%s): the \"state\" and \"session\" cookies will not be shared between the two!",
|
|
|
5b8408 |
r_uri.scheme, o_uri.scheme);
|
|
|
5b8408 |
+ oidc_proto_state_destroy(proto_state);
|
|
|
5b8408 |
return HTTP_INTERNAL_SERVER_ERROR;
|
|
|
5b8408 |
}
|
|
|
5b8408 |
|
|
|
5b8408 |
@@ -2273,6 +2310,7 @@ static int oidc_authenticate_user(request_rec *r, oidc_cfg *c,
|
|
|
5b8408 |
oidc_error(r,
|
|
|
5b8408 |
"the URL hostname (%s) of the configured " OIDCRedirectURI " does not match the URL hostname of the URL being accessed (%s): the \"state\" and \"session\" cookies will not be shared between the two!",
|
|
|
5b8408 |
r_uri.hostname, o_uri.hostname);
|
|
|
5b8408 |
+ oidc_proto_state_destroy(proto_state);
|
|
|
5b8408 |
return HTTP_INTERNAL_SERVER_ERROR;
|
|
|
5b8408 |
}
|
|
|
5b8408 |
}
|
|
|
5b8408 |
@@ -2281,6 +2319,7 @@ static int oidc_authenticate_user(request_rec *r, oidc_cfg *c,
|
|
|
5b8408 |
oidc_error(r,
|
|
|
5b8408 |
"the domain (%s) configured in " OIDCCookieDomain " does not match the URL hostname (%s) of the URL being accessed (%s): setting \"state\" and \"session\" cookies will not work!!",
|
|
|
5b8408 |
c->cookie_domain, o_uri.hostname, original_url);
|
|
|
5b8408 |
+ oidc_proto_state_destroy(proto_state);
|
|
|
5b8408 |
return HTTP_INTERNAL_SERVER_ERROR;
|
|
|
5b8408 |
}
|
|
|
5b8408 |
}
|
|
|
5b8408 |
diff --git a/src/mod_auth_openidc.h b/src/mod_auth_openidc.h
|
|
|
5b8408 |
index 48bb8fe..5162ed4 100644
|
|
|
5b8408 |
--- a/src/mod_auth_openidc.h
|
|
|
5b8408 |
+++ b/src/mod_auth_openidc.h
|
|
|
5b8408 |
@@ -379,6 +379,7 @@ typedef struct oidc_cfg {
|
|
|
5b8408 |
int http_timeout_long;
|
|
|
5b8408 |
int http_timeout_short;
|
|
|
5b8408 |
int state_timeout;
|
|
|
5b8408 |
+ int max_number_of_state_cookies;
|
|
|
5b8408 |
int session_inactivity_timeout;
|
|
|
5b8408 |
int session_cache_fallback_to_cookie;
|
|
|
5b8408 |
|
|
|
5b8408 |
@@ -686,6 +687,7 @@ int oidc_cfg_cache_encrypt(request_rec *r);
|
|
|
5b8408 |
int oidc_cfg_session_cache_fallback_to_cookie(request_rec *r);
|
|
|
5b8408 |
const char *oidc_parse_pkce_type(apr_pool_t *pool, const char *arg, oidc_proto_pkce_t **type);
|
|
|
5b8408 |
const char *oidc_cfg_claim_prefix(request_rec *r);
|
|
|
5b8408 |
+int oidc_cfg_max_number_of_state_cookies(oidc_cfg *cfg);
|
|
|
5b8408 |
|
|
|
5b8408 |
// oidc_util.c
|
|
|
5b8408 |
int oidc_strnenvcmp(const char *a, const char *b, int len);
|
|
|
5b8408 |
--
|
|
|
5b8408 |
2.26.2
|
|
|
5b8408 |
|