-
-
Notifications
You must be signed in to change notification settings - Fork 331
/
Copy pathmod_auth_openidc.c
4405 lines (3625 loc) · 149 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
/***************************************************************************
21
* Copyright (C) 2017-2023 ZmartZone Holding BV
22
* Copyright (C) 2013-2017 Ping Identity Corporation
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
* All rights reserved.
*
* DISCLAIMER OF WARRANTIES:
*
* THE SOFTWARE PROVIDED HEREUNDER IS PROVIDED ON AN "AS IS" BASIS, WITHOUT
* ANY WARRANTIES OR REPRESENTATIONS EXPRESS, IMPLIED OR STATUTORY; INCLUDING,
* WITHOUT LIMITATION, WARRANTIES OF QUALITY, PERFORMANCE, NONINFRINGEMENT,
* MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. NOR ARE THERE ANY
* WARRANTIES CREATED BY A COURSE OR DEALING, COURSE OF PERFORMANCE OR TRADE
* USAGE. FURTHERMORE, THERE ARE NO WARRANTIES THAT THE SOFTWARE WILL MEET
* YOUR NEEDS OR BE FREE FROM ERRORS, OR THAT THE OPERATION OF THE SOFTWARE
* WILL BE UNINTERRUPTED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* Initially based on mod_auth_cas.c:
* https://github.com/Jasig/mod_auth_cas
*
* Other code copied/borrowed/adapted:
* shared memory caching: mod_auth_mellon
*
47
* @Author: Hans Zandbelt - hans.zandbelt@openidc.com
48
49
50
*
**************************************************************************/
51
#include "mod_auth_openidc.h"
52
53
#define OIDC_REFRESH_ERROR 2
54
55
56
57
static int oidc_handle_logout_request(request_rec *r, oidc_cfg *c,
oidc_session_t *session, const char *url);
58
59
60
61
62
63
// TODO:
// - sort out oidc_cfg vs. oidc_dir_cfg stuff
// - rigid input checking on discovery responses
// - check self-issued support
// - README.quickstart
// - refresh metadata once-per too? (for non-signing key changes)
64
65
66
67
68
69
extern module AP_MODULE_DECLARE_DATA auth_openidc_module;
/*
* clean any suspicious headers in the HTTP request sent by the user agent
*/
70
71
static void oidc_scrub_request_headers(request_rec *r, const char *claim_prefix,
apr_hash_t *scrub) {
72
73
const int prefix_len = claim_prefix ? _oidc_strlen(claim_prefix) : 0;
74
75
/* get an array representation of the incoming HTTP headers */
76
const apr_array_header_t *const h = apr_table_elts(r->headers_in);
77
78
79
80
81
/* table to keep the non-suspicious headers */
apr_table_t *clean_headers = apr_table_make(r->pool, h->nelts);
/* loop over the incoming HTTP headers */
82
const apr_table_entry_t *const e = (const apr_table_entry_t*) h->elts;
83
84
int i;
for (i = 0; i < h->nelts; i++) {
85
const char *const k = e[i].key;
86
87
88
89
90
91
92
/* is this header's name equivalent to a header that needs scrubbing? */
const char *hdr =
(k != NULL) && (scrub != NULL) ?
apr_hash_get(scrub, k, APR_HASH_KEY_STRING) : NULL;
const int header_matches = (hdr != NULL)
&& (oidc_strnenvcmp(k, hdr, -1) == 0);
93
94
95
96
97
98
99
100
101
102
103
104
/*
* would this header be interpreted as a mod_auth_openidc attribute? Note
* that prefix_len will be zero if no attr_prefix is defined,
* so this will always be false. Also note that we do not
* scrub headers if the prefix is empty because every header
* would match.
*/
const int prefix_matches = (k != NULL) && prefix_len
&& (oidc_strnenvcmp(k, claim_prefix, prefix_len) == 0);
/* add to the clean_headers if non-suspicious, skip and report otherwise */
105
if (!prefix_matches && !header_matches) {
106
107
apr_table_addn(clean_headers, k, e[i].val);
} else {
108
109
oidc_warn(r, "scrubbed suspicious request header (%s: %.32s)", k,
e[i].val);
110
111
112
113
114
115
116
}
}
/* overwrite the incoming headers with the cleaned result */
r->headers_in = clean_headers;
}
117
118
119
/*
* scrub all mod_auth_openidc related headers
*/
120
void oidc_scrub_headers(request_rec *r) {
121
122
123
oidc_cfg *cfg = ap_get_module_config(r->server->module_config,
&auth_openidc_module);
124
125
126
const char *prefix = oidc_cfg_claim_prefix(r);
apr_hash_t *hdrs = apr_hash_make(r->pool);
127
if (_oidc_strcmp(prefix, "") == 0) {
128
129
if ((cfg->white_listed_claims != NULL)
&& (apr_hash_count(cfg->white_listed_claims) > 0))
130
hdrs = apr_hash_overlay(r->pool, cfg->white_listed_claims, hdrs);
131
132
133
134
else
oidc_warn(r,
"both " OIDCClaimPrefix " and " OIDCWhiteListedClaims " are empty: this renders an insecure setup!");
}
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
char *authn_hdr = oidc_cfg_dir_authn_header(r);
if (authn_hdr != NULL)
apr_hash_set(hdrs, authn_hdr, APR_HASH_KEY_STRING, authn_hdr);
/*
* scrub all headers starting with OIDC_ first
*/
oidc_scrub_request_headers(r, OIDC_DEFAULT_HEADER_PREFIX, hdrs);
/*
* then see if the claim headers need to be removed on top of that
* (i.e. the prefix does not start with the default OIDC_)
*/
if ((strstr(prefix, OIDC_DEFAULT_HEADER_PREFIX) != prefix)) {
oidc_scrub_request_headers(r, prefix, NULL);
151
152
153
}
}
154
155
156
/*
* strip the session cookie from the headers sent to the application/backend
*/
157
void oidc_strip_cookies(request_rec *r) {
158
159
160
161
162
163
164
char *cookie, *ctx, *result = NULL;
const char *name = NULL;
int i;
apr_array_header_t *strip = oidc_dir_cfg_strip_cookies(r);
165
char *cookies = apr_pstrdup(r->pool, oidc_util_hdr_in_cookie_get(r));
166
167
168
169
170
if ((cookies != NULL) && (strip != NULL)) {
oidc_debug(r,
"looking for the following cookies to strip from cookie header: %s",
171
apr_array_pstrcat(r->pool, strip, OIDC_CHAR_COMMA));
172
173
cookie = apr_strtok(cookies, OIDC_STR_SEMI_COLON, &ctx);
174
175
do {
176
while (cookie != NULL && *cookie == OIDC_CHAR_SPACE)
177
cookie++;
178
179
if (cookie == NULL)
break;
180
181
182
for (i = 0; i < strip->nelts; i++) {
name = ((const char**) strip->elts)[i];
183
if ((_oidc_strncmp(cookie, name, _oidc_strlen(name)) == 0)
184
&& (cookie[_oidc_strlen(name)] == OIDC_CHAR_EQUAL)) {
185
186
187
188
189
190
oidc_debug(r, "stripping: %s", name);
break;
}
}
if (i == strip->nelts) {
191
result = result ? apr_psprintf(r->pool, "%s%s %s", result,
192
193
OIDC_STR_SEMI_COLON, cookie) :
cookie;
194
195
}
196
cookie = apr_strtok(NULL, OIDC_STR_SEMI_COLON, &ctx);
197
198
} while (cookie != NULL);
199
oidc_util_hdr_in_cookie_set(r, result);
200
201
202
}
}
203
204
#define OIDC_SHA1_LEN 20
205
/*
206
* calculates a hash value based on request fingerprint plus a provided nonce string.
207
*/
208
static char* oidc_get_browser_state_hash(request_rec *r, oidc_cfg *c,
209
const char *nonce) {
210
211
oidc_debug(r, "enter");
212
213
214
215
216
217
218
219
220
/* helper to hold to header values */
const char *value = NULL;
/* the hash context */
apr_sha1_ctx_t sha1;
/* Initialize the hash context */
apr_sha1_init(&sha1);
221
222
223
224
225
if (c->state_input_headers & OIDC_STATE_INPUT_HEADERS_X_FORWARDED_FOR) {
/* get the X-FORWARDED-FOR header value */
value = oidc_util_hdr_in_x_forwarded_for_get(r);
/* if we have a value for this header, concat it to the hash input */
if (value != NULL)
226
apr_sha1_update(&sha1, value, _oidc_strlen(value));
227
}
228
229
230
231
232
233
if (c->state_input_headers & OIDC_STATE_INPUT_HEADERS_USER_AGENT) {
/* get the USER-AGENT header value */
value = oidc_util_hdr_in_user_agent_get(r);
/* if we have a value for this header, concat it to the hash input */
if (value != NULL)
234
apr_sha1_update(&sha1, value, _oidc_strlen(value));
235
}
236
237
/* get the remote client IP address or host name */
238
/*
239
240
241
int remotehost_is_ip;
value = ap_get_remote_host(r->connection, r->per_dir_config,
REMOTE_NOLOOKUP, &remotehost_is_ip);
242
apr_sha1_update(&sha1, value, _oidc_strlen(value));
243
*/
244
245
/* concat the nonce parameter to the hash input */
246
apr_sha1_update(&sha1, nonce, _oidc_strlen(nonce));
247
248
/* concat the token binding ID if present */
249
value = oidc_util_get_provided_token_binding_id(r);
250
251
if (value != NULL) {
oidc_debug(r,
252
"Provided Token Binding ID environment variable found; adding its value to the state");
253
apr_sha1_update(&sha1, value, _oidc_strlen(value));
254
255
}
256
/* finalize the hash input and calculate the resulting hash output */
257
unsigned char hash[OIDC_SHA1_LEN];
258
259
apr_sha1_final(hash, &sha1);
260
261
/* base64url-encode the resulting hash and return it */
char *result = NULL;
262
oidc_base64url_encode(r, &result, (const char*) hash, OIDC_SHA1_LEN, TRUE);
263
264
265
return result;
}
266
267
268
/*
* return the name for the state cookie
*/
269
static char* oidc_get_state_cookie_name(request_rec *r, const char *state) {
270
271
return apr_psprintf(r->pool, "%s%s", oidc_cfg_dir_state_cookie_prefix(r),
state);
272
273
}
274
275
276
/*
* return the static provider configuration, i.e. from a metadata URL or configuration primitives
*/
277
278
static apr_byte_t oidc_provider_static_config(request_rec *r, oidc_cfg *c,
oidc_provider_t **provider) {
279
280
json_t *j_provider = NULL;
281
char *s_json = NULL;
282
283
/* see if we should configure a static provider based on external (cached) metadata */
284
285
286
287
if ((c->metadata_dir != NULL) || (c->provider.metadata_url == NULL)) {
*provider = &c->provider;
return TRUE;
}
288
289
oidc_cache_get_provider(r, c->provider.metadata_url, &s_json);
290
291
292
293
294
295
296
if (s_json == NULL) {
if (oidc_metadata_provider_retrieve(r, c, NULL,
c->provider.metadata_url, &j_provider, &s_json) == FALSE) {
oidc_error(r, "could not retrieve metadata from url: %s",
c->provider.metadata_url);
297
return FALSE;
298
299
}
300
oidc_cache_set_provider(r, c->provider.metadata_url, s_json,
301
apr_time_now() + apr_time_from_sec(c->provider_metadata_refresh_interval <= 0 ? OIDC_CACHE_PROVIDER_METADATA_EXPIRY_DEFAULT : c->provider_metadata_refresh_interval));
302
303
304
} else {
305
oidc_util_decode_json_object(r, s_json, &j_provider);
306
307
308
309
310
311
312
313
/* check to see if it is valid metadata */
if (oidc_metadata_provider_is_valid(r, c, j_provider, NULL) == FALSE) {
oidc_error(r,
"cache corruption detected: invalid metadata from url: %s",
c->provider.metadata_url);
return FALSE;
}
314
315
}
316
*provider = oidc_cfg_provider_copy(r->pool, &c->provider);
317
318
if (oidc_metadata_provider_parse(r, c, j_provider, *provider) == FALSE) {
319
320
321
322
oidc_error(r, "could not parse metadata from url: %s",
c->provider.metadata_url);
if (j_provider)
json_decref(j_provider);
323
return FALSE;
324
325
326
327
}
json_decref(j_provider);
328
return TRUE;
329
330
}
331
332
333
/*
* return the oidc_provider_t struct for the specified issuer
*/
334
static oidc_provider_t* oidc_get_provider_for_issuer(request_rec *r,
335
oidc_cfg *c, const char *issuer, apr_byte_t allow_discovery) {
336
337
/* by default we'll assume that we're dealing with a single statically configured OP */
338
339
340
oidc_provider_t *provider = NULL;
if (oidc_provider_static_config(r, c, &provider) == FALSE)
return NULL;
341
342
343
344
345
/* unless a metadata directory was configured, so we'll try and get the provider settings from there */
if (c->metadata_dir != NULL) {
/* try and get metadata from the metadata directory for the OP that sent this response */
346
347
if ((oidc_metadata_get(r, c, issuer, &provider, allow_discovery)
== FALSE) || (provider == NULL)) {
348
349
/* don't know nothing about this OP/issuer */
350
oidc_error(r, "no provider metadata found for issuer \"%s\"",
351
352
353
354
355
356
357
358
359
issuer);
return NULL;
}
}
return provider;
}
360
361
362
363
364
365
366
367
/*
* find out whether the request is a response from an IDP discovery page
*/
static apr_byte_t oidc_is_discovery_response(request_rec *r, oidc_cfg *cfg) {
/*
* prereq: this is a call to the configured redirect_uri, now see if:
* the OIDC_DISC_OP_PARAM is present
*/
368
369
return oidc_util_request_has_parameter(r, OIDC_DISC_OP_PARAM)
|| oidc_util_request_has_parameter(r, OIDC_DISC_USER_PARAM);
370
371
372
373
374
}
/*
* return the HTTP method being called: only for POST data persistence purposes
*/
375
static const char* oidc_original_request_method(request_rec *r, oidc_cfg *cfg,
376
377
378
379
380
apr_byte_t handle_discovery_response) {
const char *method = OIDC_METHOD_GET;
char *m = NULL;
if ((handle_discovery_response == TRUE)
381
&& (oidc_util_request_matches_url(r, oidc_get_redirect_uri(r, cfg)))
382
383
384
385
386
387
388
389
390
391
392
&& (oidc_is_discovery_response(r, cfg))) {
oidc_util_get_request_parameter(r, OIDC_DISC_RM_PARAM, &m);
if (m != NULL)
method = apr_pstrdup(r->pool, m);
} else {
/*
* if POST preserve is not enabled for this location, there's no point in preserving
* the method either which would result in POSTing empty data on return;
* so we revert to legacy behavior
*/
393
if (oidc_cfg_dir_preserve_post(r) == 0)
394
395
return OIDC_METHOD_GET;
396
const char *content_type = oidc_util_hdr_in_content_type_get(r);
397
if ((r->method_number == M_POST) && (_oidc_strcmp(content_type,
398
OIDC_CONTENT_TYPE_FORM_ENCODED) == 0))
399
400
401
402
403
404
405
406
407
408
409
410
411
412
method = OIDC_METHOD_FORM_POST;
}
oidc_debug(r, "return: %s", method);
return method;
}
/*
* send an OpenID Connect authorization request to the specified provider preserving POST parameters using HTML5 storage
*/
apr_byte_t oidc_post_preserve_javascript(request_rec *r, const char *location,
char **javascript, char **javascript_method) {
413
if (oidc_cfg_dir_preserve_post(r) == 0)
414
415
416
417
418
419
420
421
422
return FALSE;
oidc_debug(r, "enter");
oidc_cfg *cfg = ap_get_module_config(r->server->module_config,
&auth_openidc_module);
const char *method = oidc_original_request_method(r, cfg, FALSE);
423
if (_oidc_strcmp(method, OIDC_METHOD_FORM_POST) != 0)
424
425
426
427
return FALSE;
/* read the parameters that are POST-ed to us */
apr_table_t *params = apr_table_make(r->pool, 8);
428
if (oidc_util_read_post_params(r, params, FALSE, NULL) == FALSE) {
429
430
431
432
433
434
435
436
437
438
oidc_error(r, "something went wrong when reading the POST parameters");
return FALSE;
}
const apr_array_header_t *arr = apr_table_elts(params);
const apr_table_entry_t *elts = (const apr_table_entry_t*) arr->elts;
int i;
char *json = "";
for (i = 0; i < arr->nelts; i++) {
json = apr_psprintf(r->pool, "%s'%s': '%s'%s", json,
439
440
oidc_util_escape_string(r, elts[i].key),
oidc_util_escape_string(r, elts[i].val),
441
442
443
444
445
446
447
448
449
i < arr->nelts - 1 ? "," : "");
}
json = apr_psprintf(r->pool, "{ %s }", json);
const char *jmethod = "preserveOnLoad";
const char *jscript =
apr_psprintf(r->pool,
" <script type=\"text/javascript\">\n"
" function %s() {\n"
450
" sessionStorage.setItem('mod_auth_openidc_preserve_post_params', JSON.stringify(%s));\n"
451
452
453
454
455
" %s"
" }\n"
" </script>\n", jmethod, json,
location ?
apr_psprintf(r->pool, "window.location='%s';\n",
456
oidc_util_javascript_escape(r->pool, location)) :
457
458
459
460
461
462
463
464
"");
if (location == NULL) {
if (javascript_method)
*javascript_method = apr_pstrdup(r->pool, jmethod);
if (javascript)
*javascript = apr_pstrdup(r->pool, jscript);
} else {
oidc_util_html_send(r, "Preserving...", jscript, jmethod,
465
"<p>Preserving...</p>", OK);
466
467
468
469
470
471
}
return TRUE;
}
/*
472
* restore POST parameters on original_url from HTML5 session storage
473
474
475
476
477
478
479
480
481
482
*/
static int oidc_request_post_preserved_restore(request_rec *r,
const char *original_url) {
oidc_debug(r, "enter: original_url=%s", original_url);
const char *method = "postOnLoad";
const char *script =
apr_psprintf(r->pool,
" <script type=\"text/javascript\">\n"
483
484
485
486
487
488
489
490
" function str_decode(string) {\n"
" try {\n"
" result = decodeURIComponent(string);\n"
" } catch (e) {\n"
" result = unescape(string);\n"
" }\n"
" return result;\n"
" }\n"
491
" function %s() {\n"
492
493
" var mod_auth_openidc_preserve_post_params = JSON.parse(sessionStorage.getItem('mod_auth_openidc_preserve_post_params'));\n"
" sessionStorage.removeItem('mod_auth_openidc_preserve_post_params');\n"
494
495
" for (var key in mod_auth_openidc_preserve_post_params) {\n"
" var input = document.createElement(\"input\");\n"
496
497
" input.name = str_decode(key);\n"
" input.value = str_decode(mod_auth_openidc_preserve_post_params[key]);\n"
498
499
500
" input.type = \"hidden\";\n"
" document.forms[0].appendChild(input);\n"
" }\n"
501
" document.forms[0].action = \"%s\";\n"
502
503
" document.forms[0].submit();\n"
" }\n"
504
" </script>\n", method, oidc_util_javascript_escape(r->pool, original_url));
505
506
507
508
509
const char *body = " <p>Restoring...</p>\n"
" <form method=\"post\"></form>\n";
return oidc_util_html_send(r, "Restoring...", script, method, body,
510
OK);
511
512
}
513
514
515
516
517
518
typedef struct oidc_state_cookies_t {
char *name;
apr_time_t timestamp;
struct oidc_state_cookies_t *next;
} oidc_state_cookies_t;
519
static int oidc_delete_oldest_state_cookies(request_rec *r, oidc_cfg *c,
520
521
int number_of_valid_state_cookies, int max_number_of_state_cookies,
oidc_state_cookies_t *first) {
522
oidc_state_cookies_t *cur = NULL, *prev = NULL, *prev_oldest = NULL, *oldest = NULL;
523
524
525
526
while (number_of_valid_state_cookies >= max_number_of_state_cookies) {
oldest = first;
prev_oldest = NULL;
prev = first;
527
cur = first ? first->next : NULL;
528
529
530
531
532
533
534
535
while (cur) {
if ((cur->timestamp < oldest->timestamp)) {
oldest = cur;
prev_oldest = prev;
}
prev = cur;
cur = cur->next;
}
536
537
oidc_warn(r, "deleting oldest state cookie: %s (time until expiry %" APR_TIME_T_FMT " seconds)", oldest->name, apr_time_sec(oldest->timestamp - apr_time_now()));
oidc_util_set_cookie(r, oldest->name, "", 0, OIDC_COOKIE_EXT_SAME_SITE_NONE(c, r));
538
539
540
if (prev_oldest)
prev_oldest->next = oldest->next;
else
541
first = first ? first->next : NULL;
542
543
544
545
546
number_of_valid_state_cookies--;
}
return number_of_valid_state_cookies;
}
547
548
549
550
551
552
/*
* clean state cookies that have expired i.e. for outstanding requests that will never return
* successfully and return the number of remaining valid cookies/outstanding-requests while
* doing so
*/
static int oidc_clean_expired_state_cookies(request_rec *r, oidc_cfg *c,
553
const char *currentCookieName, int delete_oldest) {
554
int number_of_valid_state_cookies = 0;
555
556
oidc_state_cookies_t *first = NULL, *last = NULL;
char *cookie, *tokenizerCtx = NULL;
557
char *cookies = apr_pstrdup(r->pool, oidc_util_hdr_in_cookie_get(r));
558
if (cookies != NULL) {
559
cookie = apr_strtok(cookies, OIDC_STR_SEMI_COLON, &tokenizerCtx);
560
561
while (cookie != NULL) {
while (*cookie == OIDC_CHAR_SPACE)
562
cookie++;
563
if (strstr(cookie, oidc_cfg_dir_state_cookie_prefix(r)) == cookie) {
564
char *cookieName = cookie;
565
while (cookie != NULL && *cookie != OIDC_CHAR_EQUAL)
566
cookie++;
567
if (*cookie == OIDC_CHAR_EQUAL) {
568
569
*cookie = '\0';
cookie++;
570
if ((currentCookieName == NULL)
571
|| (_oidc_strcmp(cookieName, currentCookieName) != 0)) {
572
573
574
oidc_proto_state_t *proto_state =
oidc_proto_state_from_cookie(r, c, cookie);
if (proto_state != NULL) {
575
json_int_t ts = oidc_proto_state_get_timestamp(proto_state);
576
if (apr_time_now() > ts + apr_time_from_sec(c->state_timeout)) {
577
578
oidc_warn(r, "state (%s) has expired (original_url=%s)", cookieName, oidc_proto_state_get_original_url(proto_state));
oidc_util_set_cookie(r, cookieName, "", 0, OIDC_COOKIE_EXT_SAME_SITE_NONE(c, r));
579
} else {
580
if (first == NULL) {
581
first = apr_pcalloc(r->pool, sizeof(oidc_state_cookies_t));
582
583
last = first;
} else {
584
last->next = apr_pcalloc(r->pool, sizeof(oidc_state_cookies_t));
585
586
587
588
589
last = last->next;
}
last->name = cookieName;
last->timestamp = ts;
last->next = NULL;
590
number_of_valid_state_cookies++;
591
592
}
oidc_proto_state_destroy(proto_state);
593
} else {
594
595
oidc_warn(r, "state cookie could not be retrieved/decoded, deleting: %s", cookieName);
oidc_util_set_cookie(r, cookieName, "", 0, OIDC_COOKIE_EXT_SAME_SITE_NONE(c, r));
596
597
598
599
}
}
}
}
600
cookie = apr_strtok(NULL, OIDC_STR_SEMI_COLON, &tokenizerCtx);
601
}
602
}
603
604
if (delete_oldest > 0)
605
606
number_of_valid_state_cookies =
oidc_delete_oldest_state_cookies(r, c, number_of_valid_state_cookies, c->max_number_of_state_cookies, first);
607
608
return number_of_valid_state_cookies;
609
610
}
611
/*
612
* restore the state that was maintained between authorization request and response in an encrypted cookie
613
*/
614
615
static apr_byte_t oidc_restore_proto_state(request_rec *r, oidc_cfg *c, const char *state,
oidc_proto_state_t **proto_state) {
616
617
oidc_debug(r, "enter");
618
619
620
const char *cookieName = oidc_get_state_cookie_name(r, state);
621
/* clean expired state cookies to avoid pollution */
622
oidc_clean_expired_state_cookies(r, c, cookieName, FALSE);
623
624
/* get the state cookie value first */
625
char *cookieValue = oidc_util_get_cookie(r, cookieName);
626
if (cookieValue == NULL) {
627
oidc_error(r, "no \"%s\" state cookie found: check domain and samesite cookie settings", cookieName);
628
return FALSE;
629
630
631
}
/* clear state cookie because we don't need it anymore */
632
oidc_util_set_cookie(r, cookieName, "", 0, OIDC_COOKIE_EXT_SAME_SITE_NONE(c, r));
633
634
*proto_state = oidc_proto_state_from_cookie(r, c, cookieValue);
635
636
if (*proto_state == NULL)
return FALSE;
637
638
const char *nonce = oidc_proto_state_get_nonce(*proto_state);
639
640
/* calculate the hash of the browser fingerprint concatenated with the nonce */
641
char *calc = oidc_get_browser_state_hash(r, c, nonce);
642
/* compare the calculated hash with the value provided in the authorization response */
643
if (_oidc_strcmp(calc, state) != 0) {
644
oidc_error(r, "calculated state from cookie does not match state parameter passed back in URL: \"%s\" != \"%s\"", state, calc);
645
oidc_proto_state_destroy(*proto_state);
646
647
648
return FALSE;
}
649
apr_time_t ts = oidc_proto_state_get_timestamp(*proto_state);
650
651
/* check that the timestamp is not beyond the valid interval */
652
if (apr_time_now() > ts + apr_time_from_sec(c->state_timeout)) {
653
oidc_error(r, "state has expired");
654
655
656
657
658
659
660
661
662
663
if ((c->default_sso_url == NULL)
|| (apr_table_get(r->subprocess_env, "OIDC_NO_DEFAULT_URL_ON_STATE_TIMEOUT") != NULL)) {
oidc_util_html_send_error(r, c->error_template, "Invalid Authentication Response", apr_psprintf(r->pool, "This is due to a timeout; please restart your authentication session by re-entering the URL/bookmark you originally wanted to access: %s", oidc_proto_state_get_original_url(*proto_state)),
OK);
/*
* a hack for Apache 2.4 to prevent it from writing its own 500/400/302 HTML document
* text by making ap_send_error_response in http_protocol.c return early...
*/
r->header_only = 1;
}
664
oidc_proto_state_destroy(*proto_state);
665
666
667
return FALSE;
}
668
/* add the state */
669
oidc_proto_state_set_state(*proto_state, state);
670
671
/* log the restored state object */
672
oidc_debug(r, "restored state: %s", oidc_proto_state_to_string(r, *proto_state));
673
674
675
676
677
678
/* we've made it */
return TRUE;
}
/*
679
680
* set the state that is maintained between an authorization request and an authorization response
* in a cookie in the browser that is cryptographically bound to that state
681
*/
682
683
static int oidc_authorization_request_set_cookie(request_rec *r, oidc_cfg *c,
const char *state, oidc_proto_state_t *proto_state) {
684
/*
685
686
* create a cookie consisting of 8 elements:
* random value, original URL, original method, issuer, response_type, response_mod, prompt and timestamp
687
* encoded as JSON, encrypting the resulting JSON value
688
*/
689
690
char *cookieValue = oidc_proto_state_to_cookie(r, c, proto_state);
if (cookieValue == NULL)
691
return HTTP_INTERNAL_SERVER_ERROR;
692
693
694
/*
* clean expired state cookies to avoid pollution and optionally
695
* try to avoid the number of state cookies exceeding a max
696
*/
697
698
int number_of_cookies = oidc_clean_expired_state_cookies(r, c, NULL,
oidc_cfg_delete_oldest_state_cookies(c));
699
700
701
int max_number_of_cookies = oidc_cfg_max_number_of_state_cookies(c);
if ((max_number_of_cookies > 0)
&& (number_of_cookies >= max_number_of_cookies)) {
702
703
704
705
706
707
708
709
710
711
oidc_warn(r,
"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",
number_of_cookies, max_number_of_cookies);
/*
* TODO: the html_send code below caters for the case that there's a user behind a
* browser generating this request, rather than a piece of XHR code; how would an
* XHR client handle this?
*/
712
713
714
715
716
/*
* it appears that sending content with a 503 turns the HTTP status code
* into a 200 so we'll avoid that for now: the user will see Apache specific
* readable text anyway
*
717
718
719
720
721
722
723
return oidc_util_html_send_error(r, c->error_template,
"Too Many Outstanding Requests",
apr_psprintf(r->pool,
"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",
c->state_timeout),
HTTP_SERVICE_UNAVAILABLE);
*/
724
725
return HTTP_SERVICE_UNAVAILABLE;
726
}
727
728
729
730
/* assemble the cookie name for the state cookie */
const char *cookieName = oidc_get_state_cookie_name(r, state);
731
/* set it as a cookie */
732
oidc_util_set_cookie(r, cookieName, cookieValue, -1,
733
OIDC_COOKIE_SAMESITE_LAX(c, r));
734
735
return OK;
736
737
738
739
740
741
}
/*
* get the mod_auth_openidc related context from the (userdata in the) request
* (used for passing state between various Apache request processing stages and hook callbacks)
*/
742
static apr_table_t* oidc_request_state(request_rec *rr) {
743
744
745
746
747
748
/* our state is always stored in the main request */
request_rec *r = (rr->main != NULL) ? rr->main : rr;
/* our state is a table, get it */
apr_table_t *state = NULL;
749
apr_pool_userdata_get((void**) &state, OIDC_USERDATA_KEY, r->pool);
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
/* if it does not exist, we'll create a new table */
if (state == NULL) {
state = apr_table_make(r->pool, 5);
apr_pool_userdata_set(state, OIDC_USERDATA_KEY, NULL, r->pool);
}
/* return the resulting table, always non-null now */
return state;
}
/*
* set a name/value pair in the mod_auth_openidc-specific request context
* (used for passing state between various Apache request processing stages and hook callbacks)
*/
void oidc_request_state_set(request_rec *r, const char *key, const char *value) {
/* get a handle to the global state, which is a table */
apr_table_t *state = oidc_request_state(r);
/* put the name/value pair in that table */
771
apr_table_set(state, key, value);
772
773
774
775
776
777
}
/*
* get a name/value pair from the mod_auth_openidc-specific request context
* (used for passing state between various Apache request processing stages and hook callbacks)
*/
778
const char* oidc_request_state_get(request_rec *r, const char *key) {
779
780
781
782
783
784
785
786
/* get a handle to the global state, which is a table */
apr_table_t *state = oidc_request_state(r);
/* return the value from the table */
return apr_table_get(state, key);
}
787
788
789
790
/*
* set the claims from a JSON object (c.q. id_token or user_info response) stored
* in the session in to HTTP headers passed on to the application
*/
791
792
static apr_byte_t oidc_set_app_claims(request_rec *r, const oidc_cfg *const cfg,
oidc_session_t *session, const char *s_claims) {
793
794
json_t *j_claims = NULL;
795
796
/* decode the string-encoded attributes in to a JSON structure */
797
if (s_claims != NULL) {
798
if (oidc_util_decode_json_object(r, s_claims, &j_claims) == FALSE)
799
return FALSE;
800
801
}
802
/* set the resolved claims a HTTP headers for the application */
803
if (j_claims != NULL) {
804
oidc_util_set_app_infos(r, j_claims, oidc_cfg_claim_prefix(r),
805
cfg->claim_delimiter, oidc_cfg_dir_pass_info_in_headers(r),
806
oidc_cfg_dir_pass_info_in_envvars(r),
807
oidc_cfg_dir_pass_info_encoding(r));
808
809
810
/* release resources */
json_decref(j_claims);
811
812
813
814
815
}
return TRUE;
}
816
817
818
static int oidc_authenticate_user(request_rec *r, oidc_cfg *c,
oidc_provider_t *provider, const char *original_url,
const char *login_hint, const char *id_token_hint, const char *prompt,
819
const char *auth_request_params, const char *path_scope);
820
821
822
823
/*
* log message about max session duration
*/
824
825
static void oidc_log_session_expires(request_rec *r, const char *msg,
apr_time_t session_expires) {
826
827
char buf[APR_RFC822_DATE_LEN + 1];
apr_rfc822_date(buf, session_expires);
828
829
oidc_debug(r, "%s: %s (in %" APR_TIME_T_FMT " secs from now)", msg, buf,
apr_time_sec(session_expires - apr_time_now()));
830
831
}
832
/*
833
* see if this is a request that is capable of completing an authentication round trip to the Provider
834
*/
835
apr_byte_t oidc_is_auth_capable_request(request_rec *r) {
836
837
838
839
if ((oidc_util_hdr_in_x_requested_with_get(r) != NULL)
&& (apr_strnatcasecmp(oidc_util_hdr_in_x_requested_with_get(r),
OIDC_HTTP_HDR_VAL_XML_HTTP_REQUEST) == 0))
840
return FALSE;
841
842
843
844
if ((oidc_util_hdr_in_sec_fetch_mode_get(r) != NULL)
&& (apr_strnatcasecmp(oidc_util_hdr_in_sec_fetch_mode_get(r),
OIDC_HTTP_HDR_VAL_NAVIGATE) != 0))
845
846
847
848
849
850
return FALSE;
if ((oidc_util_hdr_in_sec_fetch_dest_get(r) != NULL)
&& (apr_strnatcasecmp(oidc_util_hdr_in_sec_fetch_dest_get(r),
OIDC_HTTP_HDR_VAL_DOCUMENT) != 0))
return FALSE;
851
852
853
854
855
856
if ((oidc_util_hdr_in_accept_contains(r, OIDC_CONTENT_TYPE_TEXT_HTML)
== FALSE) && (oidc_util_hdr_in_accept_contains(r,
OIDC_CONTENT_TYPE_APP_XHTML_XML) == FALSE)
&& (oidc_util_hdr_in_accept_contains(r,
OIDC_CONTENT_TYPE_ANY) == FALSE))
857
return FALSE;
858
859
return TRUE;
860
861
}
862
863
864
865
866
867
868
869
870
/*
* find out which action we need to take when encountering an unauthenticated request
*/
static int oidc_handle_unauthenticated_user(request_rec *r, oidc_cfg *c) {
/* see if we've configured OIDCUnAuthAction for this path */
switch (oidc_dir_cfg_unauth_action(r)) {
case OIDC_UNAUTH_RETURN410:
return HTTP_GONE;
871
872
case OIDC_UNAUTH_RETURN407:
return HTTP_PROXY_AUTHENTICATION_REQUIRED;
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
case OIDC_UNAUTH_RETURN401:
return HTTP_UNAUTHORIZED;
case OIDC_UNAUTH_PASS:
r->user = "";
/*
* we're not going to pass information about an authenticated user to the application,
* but we do need to scrub the headers that mod_auth_openidc would set for security reasons
*/
oidc_scrub_headers(r);
return OK;
case OIDC_UNAUTH_AUTHENTICATE:
/*
* exception handling: if this looks like a XMLHttpRequest call we
* won't redirect the user and thus avoid creating a state cookie
* for a non-browser (= Javascript) call that will never return from the OP
*/
893
if ((oidc_dir_cfg_unauth_expr_is_set(r) == FALSE)
894
&& (oidc_is_auth_capable_request(r) == FALSE))
895
896
897
898
899
900
901
return HTTP_UNAUTHORIZED;
}
/*
* else: no session (regardless of whether it is main or sub-request),
* and we need to authenticate the user
*/
902
return oidc_authenticate_user(r, c, NULL, oidc_get_current_url(r, c->x_forwarded_headers), NULL,
903
904
NULL, NULL, oidc_dir_cfg_path_auth_request_params(r),
oidc_dir_cfg_path_scope(r));
905
906
}
907
908
909
/*
* check if maximum session duration was exceeded
*/
910
911
static apr_byte_t oidc_check_max_session_duration(request_rec *r, oidc_cfg *cfg,
oidc_session_t *session, int *rc) {
912
913
/* get the session expiry from the session data */
914
apr_time_t session_expires = oidc_session_get_session_expires(r, session);
915
916
917
/* check the expire timestamp against the current time */
if (apr_time_now() > session_expires) {
918
919
oidc_warn(r, "maximum session duration exceeded for user: %s",
session->remote_user);
920
oidc_session_kill(r, session);
921
922
*rc = oidc_handle_unauthenticated_user(r, cfg);
return FALSE;
923
924
925
}
/* log message about max session duration */
926
oidc_log_session_expires(r, "session max lifetime", session_expires);
927
928
929
930
*rc = OK;
return TRUE;
931
932
}
933
934
935
936
937
938
939
940
941
942
/*
* validate received session cookie against the domain it was issued for:
*
* this handles the case where the cache configured is a the same single memcache, Redis, or file
* backend for different (virtual) hosts, or a client-side cookie protected with the same secret
*
* it also handles the case that a cookie is unexpectedly shared across multiple hosts in
* name-based virtual hosting even though the OP(s) would be the same
*/
static apr_byte_t oidc_check_cookie_domain(request_rec *r, oidc_cfg *cfg,
943
oidc_session_t *session) {
944
945
const char *c_cookie_domain =
cfg->cookie_domain ?
946
cfg->cookie_domain : oidc_get_current_url_host(r, cfg->x_forwarded_headers);
947
const char *s_cookie_domain = oidc_session_get_cookie_domain(r, session);
948
if ((s_cookie_domain == NULL)
949
|| (_oidc_strcmp(c_cookie_domain, s_cookie_domain) != 0)) {
950
951
952
953
954
955
956
957
958
oidc_warn(r,
"aborting: detected attempt to play cookie against a different domain/host than issued for! (issued=%s, current=%s)",
s_cookie_domain, c_cookie_domain);
return FALSE;
}
return TRUE;
}
959
960
961
/*
* get a handle to the provider configuration via the "issuer" stored in the session
*/
962
963
apr_byte_t oidc_get_provider_from_session(request_rec *r, oidc_cfg *c,
oidc_session_t *session, oidc_provider_t **provider) {
964
965
966
967
oidc_debug(r, "enter");
/* get the issuer value from the session state */
968
const char *issuer = oidc_session_get_issuer(r, session);
969
if (issuer == NULL) {
970
oidc_warn(r, "empty or invalid session: no issuer found");
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
return FALSE;
}
/* get the provider info associated with the issuer value */
oidc_provider_t *p = oidc_get_provider_for_issuer(r, c, issuer, FALSE);
if (p == NULL) {
oidc_error(r, "session corrupted: no provider found for issuer: %s",
issuer);
return FALSE;
}
*provider = p;
return TRUE;
}
/*
* store claims resolved from the userinfo endpoint in the session
*/
990
991
992
static void oidc_store_userinfo_claims(request_rec *r, oidc_cfg *c,
oidc_session_t *session, oidc_provider_t *provider, const char *claims,
const char *userinfo_jwt) {
993
994
995
oidc_debug(r, "enter");
996
997
998
999
1000
/* see if we've resolved any claims */
if (claims != NULL) {
/*
* Successfully decoded a set claims from the response so we can store them
* (well actually the stringified representation in the response)