1 /*
2  * Licensed to the Apache Software Foundation (ASF) under one
3  * or more contributor license agreements.  See the NOTICE file
4  * distributed with this work for additional information
5  * regarding copyright ownership.  The ASF licenses this file
6  * to you under the Apache License, Version 2.0 (the
7  * "License"); you may not use this file except in compliance
8  * with the License.  You may obtain a copy of the License at
9  *
10  *   http://www.apache.org/licenses/LICENSE-2.0
11  *
12  * Unless required by applicable law or agreed to in writing,
13  * software distributed under the License is distributed on an
14  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15  * KIND, either express or implied.  See the License for the
16  * specific language governing permissions and limitations
17  * under the License.
18  */
19 
20 /***************************************************************************
21  * Copyright (C) 2017-2021 ZmartZone Holding BV
22  * Copyright (C) 2013-2017 Ping Identity Corporation
23  * All rights reserved.
24  *
25  * DISCLAIMER OF WARRANTIES:
26  *
27  * THE SOFTWARE PROVIDED HEREUNDER IS PROVIDED ON AN "AS IS" BASIS, WITHOUT
28  * ANY WARRANTIES OR REPRESENTATIONS EXPRESS, IMPLIED OR STATUTORY; INCLUDING,
29  * WITHOUT LIMITATION, WARRANTIES OF QUALITY, PERFORMANCE, NONINFRINGEMENT,
30  * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.  NOR ARE THERE ANY
31  * WARRANTIES CREATED BY A COURSE OR DEALING, COURSE OF PERFORMANCE OR TRADE
32  * USAGE.  FURTHERMORE, THERE ARE NO WARRANTIES THAT THE SOFTWARE WILL MEET
33  * YOUR NEEDS OR BE FREE FROM ERRORS, OR THAT THE OPERATION OF THE SOFTWARE
34  * WILL BE UNINTERRUPTED.  IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR
35  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
36  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES HOWEVER CAUSED AND ON ANY THEORY OF
37  * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
38  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
39  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
40  *
41  * @Author: Hans Zandbelt - hans.zandbelt@zmartzone.eu
42  */
43 
44 #include <apr_lib.h>
45 
46 #include <httpd.h>
47 #include <http_config.h>
48 #include <http_log.h>
49 #include <http_request.h>
50 
51 #include "mod_auth_openidc.h"
52 #include "parse.h"
53 
oidc_oauth_metadata_provider_retrieve(request_rec * r,oidc_cfg * cfg,const char * issuer,const char * url,json_t ** j_metadata,char ** response)54 apr_byte_t oidc_oauth_metadata_provider_retrieve(request_rec *r, oidc_cfg *cfg,
55 		const char *issuer, const char *url, json_t **j_metadata,
56 		char **response) {
57 
58 	/* get provider metadata from the specified URL with the specified parameters */
59 	if (oidc_util_http_get(r, url, NULL, NULL, NULL,
60 			cfg->oauth.ssl_validate_server, response, cfg->http_timeout_short,
61 			cfg->outgoing_proxy, oidc_dir_cfg_pass_cookies(r),
62 			NULL, NULL) == FALSE)
63 		return FALSE;
64 
65 	/* decode and see if it is not an error response somehow */
66 	if (oidc_util_decode_json_and_check_error(r, *response, j_metadata) == FALSE) {
67 		oidc_error(r, "JSON parsing of retrieved Discovery document failed");
68 		return FALSE;
69 	}
70 
71 	/* check to see if it is valid metadata */
72 	// TODO:
73 	/*
74 	 if (oidc_oauth_metadata_provider_is_valid(r, cfg, *j_metadata, issuer) == FALSE)
75 	 return FALSE;
76 	 */
77 
78 	/* all OK */
79 	return TRUE;
80 }
81 
oidc_oauth_provider_config(request_rec * r,oidc_cfg * c)82 static apr_byte_t oidc_oauth_provider_config(request_rec *r, oidc_cfg *c) {
83 
84 	json_t *j_provider = NULL;
85 	char *s_json = NULL;
86 
87 	/* see if we should configure a static provider based on external (cached) metadata */
88 	if (c->oauth.metadata_url == NULL)
89 		return TRUE;
90 
91 	oidc_cache_get_oauth_provider(r, c->oauth.metadata_url, &s_json);
92 
93 	if (s_json == NULL) {
94 
95 		if (oidc_oauth_metadata_provider_retrieve(r, c, NULL,
96 				c->oauth.metadata_url, &j_provider, &s_json) == FALSE) {
97 			oidc_error(r, "could not retrieve metadata from url: %s",
98 					c->oauth.metadata_url);
99 			return FALSE;
100 		}
101 
102 		oidc_cache_set_oauth_provider(r, c->oauth.metadata_url, s_json,
103 				apr_time_now() + (c->provider_metadata_refresh_interval <= 0 ? apr_time_from_sec( OIDC_CACHE_PROVIDER_METADATA_EXPIRY_DEFAULT) : c->provider_metadata_refresh_interval));
104 
105 	} else {
106 
107 		oidc_util_decode_json_object(r, s_json, &j_provider);
108 
109 		/* check to see if it is valid metadata */
110 		/*
111 		 if (oidc_oauth_metadata_provider_is_valid(r, c, j_provider, NULL) == FALSE) {
112 		 oidc_error(r,
113 		 "cache corruption detected: invalid metadata from url: %s",
114 		 c->provider.metadata_url);
115 		 return FALSE;
116 		 }
117 		 */
118 	}
119 
120 	if (oidc_oauth_metadata_provider_parse(r, c, j_provider) == FALSE) {
121 		oidc_error(r, "could not parse metadata from url: %s",
122 				c->oauth.metadata_url);
123 		if (j_provider)
124 			json_decref(j_provider);
125 		return FALSE;
126 	}
127 
128 	json_decref(j_provider);
129 
130 	return TRUE;
131 }
132 
133 /*
134  * validate an access token against the validation endpoint of the Authorization server and gets a response back
135  */
oidc_oauth_validate_access_token(request_rec * r,oidc_cfg * c,const char * token,char ** response)136 static apr_byte_t oidc_oauth_validate_access_token(request_rec *r, oidc_cfg *c,
137 		const char *token, char **response) {
138 
139 	oidc_debug(r, "enter");
140 
141 	char *basic_auth = NULL;
142 	char *bearer_auth = NULL;
143 
144 	/* assemble parameters to call the token endpoint for validation */
145 	apr_table_t *params = apr_table_make(r->pool, 4);
146 
147 	/* add any configured extra static parameters to the introspection endpoint */
148 	oidc_util_table_add_query_encoded_params(r->pool, params,
149 			c->oauth.introspection_endpoint_params);
150 
151 	/* add the access_token itself */
152 	apr_table_addn(params, c->oauth.introspection_token_param_name, token);
153 
154 	const char *bearer_access_token_auth =
155 			((c->oauth.introspection_client_auth_bearer_token != NULL)
156 					&& strcmp(c->oauth.introspection_client_auth_bearer_token,
157 							"") == 0) ?
158 									token : c->oauth.introspection_client_auth_bearer_token;
159 
160 	/* add the token endpoint authentication credentials */
161 	if (oidc_proto_token_endpoint_auth(r, c,
162 			c->oauth.introspection_endpoint_auth, c->oauth.client_id,
163 			c->oauth.client_secret, NULL, c->oauth.introspection_endpoint_url,
164 			params, bearer_access_token_auth, &basic_auth,
165 			&bearer_auth) == FALSE)
166 		return FALSE;
167 
168 	/* call the endpoint with the constructed parameter set and return the resulting response */
169 	return apr_strnatcmp(c->oauth.introspection_endpoint_method,
170 			OIDC_INTROSPECTION_METHOD_GET) == 0 ?
171 					oidc_util_http_get(r, c->oauth.introspection_endpoint_url, params,
172 							basic_auth, bearer_auth, c->oauth.ssl_validate_server,
173 							response, c->http_timeout_long, c->outgoing_proxy,
174 							oidc_dir_cfg_pass_cookies(r),
175 							oidc_util_get_full_path(r->pool,
176 									c->oauth.introspection_endpoint_tls_client_cert),
177 									oidc_util_get_full_path(r->pool,
178 											c->oauth.introspection_endpoint_tls_client_key)) :
179 											oidc_util_http_post_form(r, c->oauth.introspection_endpoint_url,
180 													params, basic_auth, bearer_auth,
181 													c->oauth.ssl_validate_server, response,
182 													c->http_timeout_long, c->outgoing_proxy,
183 													oidc_dir_cfg_pass_cookies(r),
184 													oidc_util_get_full_path(r->pool,
185 															c->oauth.introspection_endpoint_tls_client_cert),
186 															oidc_util_get_full_path(r->pool,
187 																	c->oauth.introspection_endpoint_tls_client_key));
188 }
189 
190 /*
191  * get the authorization header that should contain a bearer token
192  */
oidc_oauth_get_bearer_token(request_rec * r,const char ** access_token)193 apr_byte_t oidc_oauth_get_bearer_token(request_rec *r,
194 		const char **access_token) {
195 
196 	/* get the directory specific setting on how the token can be passed in */
197 	apr_byte_t accept_token_in = oidc_cfg_dir_accept_token_in(r);
198 	const char *cookie_name = oidc_cfg_dir_accept_token_in_option(r,
199 			OIDC_OAUTH_ACCEPT_TOKEN_IN_OPTION_COOKIE_NAME);
200 
201 	oidc_debug(r, "accept_token_in=%d", accept_token_in);
202 
203 	*access_token = NULL;
204 
205 	const apr_byte_t accept_header = (accept_token_in
206 			& OIDC_OAUTH_ACCEPT_TOKEN_IN_HEADER)
207 					|| (accept_token_in == OIDC_OAUTH_ACCEPT_TOKEN_IN_DEFAULT);
208 
209 	if ((accept_header)
210 			|| (accept_token_in & OIDC_OAUTH_ACCEPT_TOKEN_IN_BASIC)) {
211 
212 		/* get the authorization header */
213 		const char *auth_line = oidc_util_hdr_in_authorization_get(r);
214 		if (auth_line) {
215 			oidc_debug(r, "authorization header found");
216 
217 			apr_byte_t known_scheme = 0;
218 
219 			/* look for the Bearer keyword */
220 			if ((apr_strnatcasecmp(
221 					ap_getword(r->pool, &auth_line, OIDC_CHAR_SPACE),
222 					OIDC_PROTO_BEARER) == 0) && accept_header) {
223 
224 				/* skip any spaces after the Bearer keyword */
225 				while (apr_isspace(*auth_line)) {
226 					auth_line++;
227 				}
228 
229 				/* copy the result in to the access_token */
230 				*access_token = apr_pstrdup(r->pool, auth_line);
231 
232 				known_scheme = 1;
233 
234 			} else if (accept_token_in & OIDC_OAUTH_ACCEPT_TOKEN_IN_BASIC) {
235 
236 				char *decoded_line;
237 				int decoded_len;
238 				if (oidc_parse_base64(r->pool, auth_line, &decoded_line,
239 						&decoded_len) == NULL) {
240 					decoded_line[decoded_len] = '\0';
241 
242 					if (strchr(decoded_line, ':') != NULL) {
243 						/* Strip the username and colon and take just the password */
244 						ap_getword_nulls(r->pool, (const char**) &decoded_line,
245 								':');
246 						*access_token = decoded_line;
247 
248 						known_scheme = 1;
249 					}
250 				}
251 			}
252 
253 			if (known_scheme == 0) {
254 				oidc_warn(r,
255 						"client used unsupported authentication scheme: %s",
256 						r->uri);
257 			}
258 		}
259 	}
260 
261 	if ((*access_token == NULL) && (r->method_number == M_POST)
262 			&& (accept_token_in & OIDC_OAUTH_ACCEPT_TOKEN_IN_POST)) {
263 		apr_table_t *params = apr_table_make(r->pool, 8);
264 		if (oidc_util_read_post_params(r, params, TRUE,
265 				OIDC_PROTO_ACCESS_TOKEN) == TRUE) {
266 			*access_token = apr_table_get(params, OIDC_PROTO_ACCESS_TOKEN);
267 		}
268 	}
269 
270 	if ((*access_token == NULL)
271 			&& (accept_token_in & OIDC_OAUTH_ACCEPT_TOKEN_IN_QUERY)) {
272 		apr_table_t *params = apr_table_make(r->pool, 8);
273 		oidc_util_read_form_encoded_params(r, params, r->args);
274 		*access_token = apr_table_get(params, OIDC_PROTO_ACCESS_TOKEN);
275 	}
276 
277 	if ((*access_token == NULL)
278 			&& (accept_token_in & OIDC_OAUTH_ACCEPT_TOKEN_IN_COOKIE)) {
279 		const char *auth_line = oidc_util_get_cookie(r, cookie_name);
280 		if (auth_line != NULL) {
281 
282 			/* copy the result in to the access_token */
283 			*access_token = apr_pstrdup(r->pool, auth_line);
284 
285 		} else {
286 			oidc_warn(r, "no cookie found with name: %s", cookie_name);
287 		}
288 	}
289 
290 	if (*access_token == NULL) {
291 		oidc_debug(r, "no bearer token found in the allowed methods: %s",
292 				oidc_accept_oauth_token_in2str(r->pool, accept_token_in));
293 		return FALSE;
294 	}
295 
296 	/* log some stuff */
297 	oidc_debug(r, "bearer token: %s", *access_token);
298 	return TRUE;
299 }
300 
301 /*
302  * parse (custom/configurable) token expiry claim in introspection result
303  */
oidc_oauth_parse_and_cache_token_expiry(request_rec * r,oidc_cfg * c,json_t * introspection_response,const char * expiry_claim_name,int expiry_format_absolute,int expiry_claim_is_mandatory,apr_time_t * cache_until)304 static apr_byte_t oidc_oauth_parse_and_cache_token_expiry(request_rec *r,
305 		oidc_cfg *c, json_t *introspection_response,
306 		const char *expiry_claim_name, int expiry_format_absolute,
307 		int expiry_claim_is_mandatory, apr_time_t *cache_until) {
308 
309 	oidc_debug(r,
310 			"expiry_claim_name=%s, expiry_format_absolute=%d, expiry_claim_is_mandatory=%d",
311 			expiry_claim_name, expiry_format_absolute,
312 			expiry_claim_is_mandatory);
313 
314 	json_t *expiry = json_object_get(introspection_response, expiry_claim_name);
315 
316 	if (expiry == NULL) {
317 		if (expiry_claim_is_mandatory) {
318 			oidc_error(r,
319 					"introspection response JSON object did not contain an \"%s\" claim",
320 					expiry_claim_name);
321 			return FALSE;
322 		}
323 		return TRUE;
324 	}
325 
326 	if (!json_is_integer(expiry)) {
327 		if (expiry_claim_is_mandatory) {
328 			oidc_error(r,
329 					"introspection response JSON object contains a \"%s\" claim but it is not a JSON integer",
330 					expiry_claim_name);
331 			return FALSE;
332 		}
333 		oidc_warn(r,
334 				"introspection response JSON object contains a \"%s\" claim that is not an (optional) JSON integer: the introspection result will NOT be cached",
335 				expiry_claim_name);
336 		return TRUE;
337 	}
338 
339 	json_int_t value = json_integer_value(expiry);
340 	if (value <= 0) {
341 		oidc_warn(r,
342 				"introspection response JSON object integer number value <= 0 (%ld); introspection result will not be cached",
343 				(long )value);
344 		return TRUE;
345 	}
346 
347 	*cache_until = apr_time_from_sec(value);
348 	if (expiry_format_absolute == FALSE)
349 		(*cache_until) += apr_time_now();
350 
351 	return TRUE;
352 }
353 
354 #define OIDC_OAUTH_CACHE_KEY_RESPONSE  "r"
355 #define OIDC_OAUTH_CACHE_KEY_TIMESTAMP "t"
356 
oidc_oauth_cache_access_token(request_rec * r,oidc_cfg * c,apr_time_t cache_until,const char * access_token,json_t * json)357 static apr_byte_t oidc_oauth_cache_access_token(request_rec *r, oidc_cfg *c,
358 		apr_time_t cache_until, const char *access_token, json_t *json) {
359 
360 	/* no cache mode */
361 	int token_introspection_interval = oidc_cfg_token_introspection_interval(r);
362 	if (token_introspection_interval == -1) {
363 		oidc_debug(r, "not caching introspection result");
364 		return TRUE;
365 	}
366 
367 	oidc_debug(r, "caching introspection result");
368 
369 	json_t *cache_entry = json_object();
370 	json_object_set(cache_entry, OIDC_OAUTH_CACHE_KEY_RESPONSE, json);
371 	json_object_set_new(cache_entry, OIDC_OAUTH_CACHE_KEY_TIMESTAMP,
372 			json_integer(apr_time_sec(apr_time_now())));
373 	char *cache_value = oidc_util_encode_json_object(r, cache_entry,
374 			JSON_COMPACT);
375 
376 	/* set it in the cache so subsequent request don't need to validate the access_token and get the claims anymore */
377 	oidc_cache_set_access_token(r, access_token, cache_value, cache_until);
378 
379 	json_decref(cache_entry);
380 
381 	return TRUE;
382 }
383 
oidc_oauth_get_cached_access_token(request_rec * r,oidc_cfg * c,const char * access_token,json_t ** json)384 static apr_byte_t oidc_oauth_get_cached_access_token(request_rec *r,
385 		oidc_cfg *c, const char *access_token, json_t **json) {
386 	json_t *cache_entry = NULL;
387 	char *s_cache_entry = NULL;
388 
389 	/* no cache mode */
390 	int token_introspection_interval = oidc_cfg_token_introspection_interval(r);
391 	if (token_introspection_interval == -1) {
392 		return FALSE;
393 	}
394 
395 	/* see if we've got the claims for this access_token cached already */
396 	oidc_cache_get_access_token(r, access_token, &s_cache_entry);
397 
398 	if (s_cache_entry == NULL)
399 		return FALSE;
400 
401 	/* json decode the cache entry */
402 	if (oidc_util_decode_json_object(r, s_cache_entry, &cache_entry) == FALSE) {
403 		*json = NULL;
404 		return FALSE;
405 	}
406 
407 	/* compare the timestamp against the freshness requirement */
408 	json_t *v = json_object_get(cache_entry, OIDC_OAUTH_CACHE_KEY_TIMESTAMP);
409 	apr_time_t now = apr_time_sec(apr_time_now());
410 	if ((token_introspection_interval > 0)
411 			&& (now > json_integer_value(v) + token_introspection_interval)) {
412 
413 		/* printout info about the event */
414 		char buf[APR_RFC822_DATE_LEN + 1];
415 		apr_rfc822_date(buf, apr_time_from_sec(json_integer_value(v)));
416 		oidc_debug(r,
417 				"token that was validated/cached at: [%s], does not meet token freshness requirement: %d)",
418 				buf, token_introspection_interval);
419 
420 		/* invalidate the cache entry */
421 		*json = NULL;
422 		json_decref(cache_entry);
423 		return FALSE;
424 	}
425 
426 	oidc_debug(r,
427 			"returning cached introspection result that meets freshness requirements: %s",
428 			s_cache_entry);
429 
430 	/* we've got a cached introspection result that is still valid for this path's requirements */
431 	*json = json_deep_copy(
432 			json_object_get(cache_entry, OIDC_OAUTH_CACHE_KEY_RESPONSE));
433 
434 	json_decref(cache_entry);
435 	return TRUE;
436 }
437 
438 /*
439  * resolve and validate an access_token against the configured Authorization Server
440  */
oidc_oauth_resolve_access_token(request_rec * r,oidc_cfg * c,const char * access_token,json_t ** token,char ** response)441 static apr_byte_t oidc_oauth_resolve_access_token(request_rec *r, oidc_cfg *c,
442 		const char *access_token, json_t **token, char **response) {
443 
444 	json_t *result = NULL;
445 
446 	/* see if we've got the claims for this access_token cached already */
447 	oidc_oauth_get_cached_access_token(r, c, access_token, &result);
448 
449 	if (result == NULL) {
450 
451 		char *s_json = NULL;
452 
453 		/* not cached, go out and validate the access_token against the Authorization server and get the JSON claims back */
454 		if (oidc_oauth_validate_access_token(r, c, access_token,
455 				&s_json) == FALSE) {
456 			oidc_error(r,
457 					"could not get a validation response from the Authorization server");
458 			return FALSE;
459 		}
460 
461 		/* decode and see if it is not an error response somehow */
462 		if (oidc_util_decode_json_and_check_error(r, s_json, &result) == FALSE)
463 			return FALSE;
464 
465 		/* check the token binding ID in the introspection result */
466 		if (oidc_util_json_validate_cnf(r, result,
467 				c->oauth.access_token_binding_policy) == FALSE)
468 			return FALSE;
469 
470 		json_t *active = json_object_get(result, OIDC_PROTO_ACTIVE);
471 		apr_time_t cache_until;
472 		if (active != NULL) {
473 
474 			if (json_is_boolean(active)) {
475 				if (!json_is_true(active)) {
476 					oidc_debug(r,
477 							"\"%s\" boolean object with value \"false\" found in response JSON object",
478 							OIDC_PROTO_ACTIVE);
479 					json_decref(result);
480 					return FALSE;
481 				}
482 			} else if (json_is_string(active)) {
483 				if (apr_strnatcasecmp(json_string_value(active), "true") != 0) {
484 					oidc_debug(r,
485 							"\"%s\" string object with value that is not equal to \"true\" found in response JSON object: %s",
486 							OIDC_PROTO_ACTIVE, json_string_value(active));
487 					json_decref(result);
488 					return FALSE;
489 				}
490 			} else {
491 				oidc_debug(r,
492 						"no \"%s\" boolean or string object found in response JSON object",
493 						OIDC_PROTO_ACTIVE);
494 				json_decref(result);
495 				return FALSE;
496 			}
497 
498 			if (oidc_oauth_parse_and_cache_token_expiry(r, c, result,
499 					OIDC_CLAIM_EXP,
500 					TRUE, FALSE, &cache_until) == FALSE) {
501 				json_decref(result);
502 				return FALSE;
503 			}
504 
505 			/* set it in the cache so subsequent request don't need to validate the access_token and get the claims anymore */
506 			oidc_oauth_cache_access_token(r, c, cache_until, access_token,
507 					result);
508 
509 		} else {
510 
511 			if (oidc_oauth_parse_and_cache_token_expiry(r, c, result,
512 					c->oauth.introspection_token_expiry_claim_name,
513 					apr_strnatcmp(
514 							c->oauth.introspection_token_expiry_claim_format,
515 							OIDC_CLAIM_FORMAT_ABSOLUTE) == 0,
516 							c->oauth.introspection_token_expiry_claim_required,
517 							&cache_until) == FALSE) {
518 				json_decref(result);
519 				return FALSE;
520 			}
521 
522 			/* set it in the cache so subsequent request don't need to validate the access_token and get the claims anymore */
523 			oidc_oauth_cache_access_token(r, c, cache_until, access_token,
524 					result);
525 
526 		}
527 
528 	}
529 
530 	/* return the access_token JSON object */
531 	json_t *tkn = json_object_get(result, OIDC_PROTO_ACCESS_TOKEN);
532 	if ((tkn != NULL) && (json_is_object(tkn))) {
533 
534 		/*
535 		 * assume PingFederate validation: copy over those claims from the access_token
536 		 * that are relevant for authorization purposes
537 		 */
538 		json_object_set(tkn, OIDC_PROTO_CLIENT_ID,
539 				json_object_get(result, OIDC_PROTO_CLIENT_ID));
540 		json_object_set(tkn, OIDC_PROTO_SCOPE,
541 				json_object_get(result, OIDC_PROTO_SCOPE));
542 
543 		//oidc_oauth_spaced_string_to_array(r, result, OIDC_PROTO_SCOPE, tkn, "scopes");
544 
545 		/* return only the pimped access_token results */
546 		*token = json_deep_copy(tkn);
547 
548 		json_decref(result);
549 
550 	} else {
551 
552 		//oidc_oauth_spaced_string_to_array(r, result, OIDC_PROTO_SCOPE, result, "scopes");
553 
554 		/* assume spec compliant introspection */
555 		*token = result;
556 
557 	}
558 
559 	/* stringify the response */
560 	*response = oidc_util_encode_json_object(r, *token, JSON_COMPACT);
561 
562 	return TRUE;
563 }
564 
565 /*
566  * validate a JWT access token (locally)
567  *
568  * TODO: document that we're reusing the following settings from the OIDC config section:
569  *       - JWKs URI refresh interval
570  *       - decryption key material (OIDCPrivateKeyFiles)
571  *
572  * OIDCOAuthRemoteUserClaim client_id
573  * # 32x 61 hex
574  * OIDCOAuthVerifySharedKeys aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
575  */
oidc_oauth_validate_jwt_access_token(request_rec * r,oidc_cfg * c,const char * access_token,json_t ** token,char ** response)576 static apr_byte_t oidc_oauth_validate_jwt_access_token(request_rec *r,
577 		oidc_cfg *c, const char *access_token, json_t **token, char **response) {
578 
579 	oidc_debug(r, "enter: JWT access_token header=%s",
580 			oidc_proto_peek_jwt_header(r, access_token, NULL));
581 
582 	oidc_jose_error_t err;
583 	oidc_jwk_t *jwk = NULL;
584 
585 	// TODO: replace this OIDC client secret with OIDCOAuthDecryptSharedKeys
586 	if (oidc_util_create_symmetric_key(r, c->provider.client_secret, 0, NULL,
587 			TRUE, &jwk) == FALSE)
588 		return FALSE;
589 
590 	oidc_jwt_t *jwt = NULL;
591 	if (oidc_jwt_parse(r->pool, access_token, &jwt,
592 			oidc_util_merge_symmetric_key(r->pool, c->private_keys, jwk),
593 			&err) == FALSE) {
594 		oidc_error(r, "could not parse JWT from access_token: %s",
595 				oidc_jose_e2s(r->pool, err));
596 		return FALSE;
597 	}
598 
599 	oidc_jwk_destroy(jwk);
600 	oidc_debug(r, "successfully parsed JWT with header: %s",
601 			jwt->header.value.str);
602 
603 	/*
604 	 * validate the access token JWT by validating the (optional) exp claim
605 	 * don't enforce anything around iat since it doesn't make much sense for access tokens
606 	 */
607 	if (oidc_proto_validate_jwt(r, jwt, NULL, FALSE, FALSE, -1,
608 			c->oauth.access_token_binding_policy) == FALSE) {
609 		oidc_jwt_destroy(jwt);
610 		return FALSE;
611 	}
612 
613 	oidc_debug(r,
614 			"verify JWT against %d statically configured public keys and %d shared keys, with JWKs URI set to %s",
615 			c->oauth.verify_public_keys ?
616 					apr_hash_count(c->oauth.verify_public_keys) : 0,
617 					c->oauth.verify_shared_keys ?
618 							apr_hash_count(c->oauth.verify_shared_keys) : 0,
619 							c->oauth.verify_jwks_uri);
620 
621 	oidc_jwks_uri_t jwks_uri = { c->oauth.verify_jwks_uri,
622 			c->provider.jwks_refresh_interval, c->oauth.ssl_validate_server };
623 	if (oidc_proto_jwt_verify(r, c, jwt, &jwks_uri,
624 			oidc_util_merge_key_sets_hash(r->pool, c->oauth.verify_public_keys,
625 					c->oauth.verify_shared_keys), NULL) == FALSE) {
626 		oidc_error(r,
627 				"JWT access token signature could not be validated, aborting");
628 		oidc_jwt_destroy(jwt);
629 		return FALSE;
630 	}
631 
632 	oidc_debug(r, "successfully verified JWT access token: %s",
633 			jwt->payload.value.str);
634 
635 	*token = json_deep_copy(jwt->payload.value.json);
636 	*response = jwt->payload.value.str;
637 
638 	oidc_jwt_destroy(jwt);
639 
640 	return TRUE;
641 }
642 
643 /*
644  * set the WWW-Authenticate response header according to https://tools.ietf.org/html/rfc6750#section-3
645  */
oidc_oauth_return_www_authenticate(request_rec * r,const char * error,const char * error_description)646 int oidc_oauth_return_www_authenticate(request_rec *r, const char *error,
647 		const char *error_description) {
648 	apr_byte_t accept_token_in = oidc_cfg_dir_accept_token_in(r);
649 	char *hdr;
650 	if (accept_token_in == OIDC_OAUTH_ACCEPT_TOKEN_IN_BASIC) {
651 		hdr = apr_psprintf(r->pool, "%s", OIDC_PROTO_BASIC);
652 	} else {
653 		hdr = apr_psprintf(r->pool, "%s", OIDC_PROTO_BEARER);
654 	}
655 
656 	if (ap_auth_name(r) != NULL)
657 		hdr = apr_psprintf(r->pool, "%s %s=\"%s\"", hdr, OIDC_PROTO_REALM,
658 				ap_auth_name(r));
659 	if (error != NULL)
660 		hdr = apr_psprintf(r->pool, "%s%s %s=\"%s\"", hdr,
661 				(ap_auth_name(r) ? "," : ""), OIDC_PROTO_ERROR, error);
662 	if (error_description != NULL)
663 		hdr = apr_psprintf(r->pool, "%s, %s=\"%s\"", hdr,
664 				OIDC_PROTO_ERROR_DESCRIPTION, error_description);
665 	oidc_util_hdr_err_out_add(r, OIDC_HTTP_HDR_WWW_AUTHENTICATE, hdr);
666 	return HTTP_UNAUTHORIZED;
667 }
668 
669 /*
670  * set the unique user identifier that will be propagated in the Apache r->user and REMOTE_USER variables
671  */
oidc_oauth_set_request_user(request_rec * r,oidc_cfg * c,json_t * token)672 static apr_byte_t oidc_oauth_set_request_user(request_rec *r, oidc_cfg *c,
673 		json_t *token) {
674 	char *remote_user = NULL;
675 
676 	if (oidc_get_remote_user(r, c->oauth.remote_user_claim.claim_name,
677 			c->oauth.remote_user_claim.reg_exp,
678 			c->oauth.remote_user_claim.replace, token, &remote_user) == FALSE) {
679 		oidc_error(r,
680 				"" OIDCOAuthRemoteUserClaim " is set to \"%s\", but could not set the remote user based the available claims for the user",
681 				c->oauth.remote_user_claim.claim_name);
682 		return FALSE;
683 	}
684 
685 	r->user = apr_pstrdup(r->pool, remote_user);
686 	oidc_debug(r, "set user to \"%s\" based on claim: \"%s\"%s", r->user,
687 			c->oauth.remote_user_claim.claim_name,
688 			c->oauth.remote_user_claim.reg_exp ?
689 					apr_psprintf(r->pool,
690 							" and expression: \"%s\" and replace string: \"%s\"",
691 							c->oauth.remote_user_claim.reg_exp,
692 							c->oauth.remote_user_claim.replace) :
693 							"");
694 	return TRUE;
695 }
696 
697 /*
698  * main routine: handle OAuth 2.0 authentication/authorization
699  */
oidc_oauth_check_userid(request_rec * r,oidc_cfg * c,const char * access_token)700 int oidc_oauth_check_userid(request_rec *r, oidc_cfg *c,
701 		const char *access_token) {
702 
703 	/* check if this is a sub-request or an initial request */
704 	if (!ap_is_initial_req(r)) {
705 
706 		if (r->main != NULL)
707 			r->user = r->main->user;
708 		else if (r->prev != NULL)
709 			r->user = r->prev->user;
710 
711 		if (r->user != NULL) {
712 
713 			/* this is a sub-request and we have a session */
714 			oidc_debug(r,
715 					"recycling user '%s' from initial request for sub-request",
716 					r->user);
717 
718 			/* strip any cookies that we need to */
719 			oidc_strip_cookies(r);
720 
721 			return OK;
722 		}
723 
724 		/* check if this is a request to the "special" handler (Redirect URI) */
725 	} else if (oidc_util_request_matches_url(r, oidc_get_redirect_uri(r, c))) {
726 
727 		/* check if this is a request for the public (encryption) keys */
728 		if (oidc_util_request_has_parameter(r,
729 				OIDC_REDIRECT_URI_REQUEST_JWKS)) {
730 
731 			return oidc_handle_jwks(r, c);
732 
733 			/* check if this is a request to remove the access token from the cache */
734 		} else if (oidc_util_request_has_parameter(r,
735 				OIDC_REDIRECT_URI_REQUEST_REMOVE_AT_CACHE)) {
736 
737 			/* handle request to invalidate access token cache */
738 			return oidc_handle_remove_at_cache(r, c);
739 		}
740 	}
741 
742 	/* we don't have a session yet */
743 
744 	/* obtain/refresh metadata from OAuth metadata document URL if configured */
745 	oidc_oauth_provider_config(r, c);
746 
747 	/* get the bearer access token from the Authorization header */
748 	if (access_token == NULL) {
749 		if (oidc_oauth_get_bearer_token(r, &access_token) == FALSE) {
750 			if (r->method_number == M_OPTIONS) {
751 				r->user = "";
752 				return OK;
753 			}
754 			return oidc_oauth_return_www_authenticate(r,
755 					OIDC_PROTO_ERR_INVALID_REQUEST,
756 					"No bearer token found in the request");
757 		}
758 	}
759 
760 	/* validate the obtained access token against the OAuth AS validation endpoint */
761 	json_t *token = NULL;
762 	char *s_token = NULL;
763 
764 	/* check if an introspection endpoint is set */
765 	if (c->oauth.introspection_endpoint_url != NULL) {
766 		/* we'll validate the token remotely */
767 		if (oidc_oauth_resolve_access_token(r, c, access_token, &token,
768 				&s_token) == FALSE)
769 			return oidc_oauth_return_www_authenticate(r,
770 					OIDC_PROTO_ERR_INVALID_TOKEN,
771 					"Reference token could not be introspected");
772 	} else {
773 		/* no introspection endpoint is set, assume the token is a JWT and validate it locally */
774 		if (oidc_oauth_validate_jwt_access_token(r, c, access_token, &token,
775 				&s_token) == FALSE)
776 			return oidc_oauth_return_www_authenticate(r,
777 					OIDC_PROTO_ERR_INVALID_TOKEN, "JWT token could not be validated");
778 	}
779 
780 	/* check that we've got something back */
781 	if (token == NULL) {
782 		oidc_error(r, "could not resolve claims (token == NULL)");
783 		return oidc_oauth_return_www_authenticate(r,
784 				OIDC_PROTO_ERR_INVALID_TOKEN,
785 				"No claims could be parsed from the token");
786 	}
787 
788 	/* store the parsed token (cq. the claims from the response) in the request state so it can be accessed by the authz routines */
789 	oidc_request_state_set(r, OIDC_REQUEST_STATE_KEY_CLAIMS,
790 			(const char*) s_token);
791 
792 	/* set the request user */
793 	if (oidc_oauth_set_request_user(r, c, token) == FALSE) {
794 		json_decref(token);
795 		oidc_error(r,
796 				"remote user could not be set, aborting with HTTP_UNAUTHORIZED");
797 		return oidc_oauth_return_www_authenticate(r,
798 				OIDC_PROTO_ERR_INVALID_TOKEN, "Could not set remote user");
799 	}
800 
801 	/*
802 	 * we're going to pass the information that we have to the application,
803 	 * but first we need to scrub the headers that we're going to use for security reasons
804 	 */
805 	oidc_scrub_headers(r);
806 
807 	/* set the user authentication HTTP header if set and required */
808 	char *authn_header = oidc_cfg_dir_authn_header(r);
809 	apr_byte_t pass_headers = oidc_cfg_dir_pass_info_in_headers(r);
810 	apr_byte_t pass_envvars = oidc_cfg_dir_pass_info_in_envvars(r);
811 	apr_byte_t pass_base64url = oidc_cfg_dir_pass_info_base64url(r);
812 
813 	if ((r->user != NULL) && (authn_header != NULL))
814 		oidc_util_hdr_in_set(r, authn_header, r->user);
815 
816 	/* set the resolved claims in the HTTP headers for the target application */
817 	oidc_util_set_app_infos(r, token, oidc_cfg_claim_prefix(r),
818 			c->claim_delimiter, pass_headers, pass_envvars, pass_base64url);
819 
820 	/* set the access_token in the app headers */
821 	if (access_token != NULL) {
822 		oidc_util_set_app_info(r, OIDC_APP_INFO_ACCESS_TOKEN, access_token,
823 				OIDC_DEFAULT_HEADER_PREFIX, pass_headers, pass_envvars, pass_base64url);
824 	}
825 
826 	/* free JSON resources */
827 	json_decref(token);
828 
829 	/* strip any cookies that we need to */
830 	oidc_strip_cookies(r);
831 
832 	return OK;
833 }
834