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  * Validation and parsing of configuration values.
42  *
43  * @Author: Hans Zandbelt - hans.zandbelt@zmartzone.eu
44  */
45 
46 #include <apr_base64.h>
47 #include "mod_auth_openidc.h"
48 #include "parse.h"
49 #include "jose.h"
50 
51 /*
52  * parse a URL according to one of two schemes (NULL for any)
53  */
oidc_valid_url_scheme(apr_pool_t * pool,const char * arg,const char * scheme1,const char * scheme2)54 static const char * oidc_valid_url_scheme(apr_pool_t *pool, const char *arg,
55 		const char *scheme1, const char *scheme2) {
56 
57 	apr_uri_t uri;
58 
59 	if (apr_uri_parse(pool, arg, &uri) != APR_SUCCESS) {
60 		return apr_psprintf(pool, "'%s' cannot be parsed as a URL", arg);
61 	}
62 
63 	if (uri.scheme == NULL) {
64 		return apr_psprintf(pool,
65 				"'%s' cannot be parsed as a URL (no scheme set)", arg);
66 	}
67 
68 	if ((scheme1 != NULL) && (apr_strnatcmp(uri.scheme, scheme1) != 0)) {
69 		if ((scheme2 != NULL) && (apr_strnatcmp(uri.scheme, scheme2) != 0)) {
70 			return apr_psprintf(pool,
71 					"'%s' cannot be parsed as a \"%s\" or \"%s\" URL (scheme == %s)!",
72 					arg, scheme1, scheme2, uri.scheme);
73 		} else if (scheme2 == NULL) {
74 			return apr_psprintf(pool,
75 					"'%s' cannot be parsed as a \"%s\" URL (scheme == %s)!",
76 					arg, scheme1, uri.scheme);
77 		}
78 	}
79 
80 	if (uri.hostname == NULL) {
81 		return apr_psprintf(pool,
82 				"'%s' cannot be parsed as a valid URL (no hostname set, check your slashes)",
83 				arg);
84 	}
85 
86 	return NULL;
87 }
88 
89 /*
90  * parse a URL according to a scheme
91  */
oidc_valid_url(apr_pool_t * pool,const char * arg,const char * scheme)92 const char *oidc_valid_url(apr_pool_t *pool, const char *arg,
93 		const char *scheme) {
94 	return oidc_valid_url_scheme(pool, arg, scheme, NULL);
95 }
96 
97 /*
98  * parse a URL that should conform to any HTTP scheme (http/https)
99  */
oidc_valid_http_url(apr_pool_t * pool,const char * arg)100 const char *oidc_valid_http_url(apr_pool_t *pool, const char *arg) {
101 	return oidc_valid_url_scheme(pool, arg, "https", "http");
102 }
103 
104 #define STR_ERROR_MAX 128
105 
106 /*
107  * check if arg is a valid directory on the file system
108  */
oidc_valid_dir(apr_pool_t * pool,const char * arg)109 const char *oidc_valid_dir(apr_pool_t *pool, const char *arg) {
110 	char s_err[STR_ERROR_MAX];
111 	apr_dir_t *dir = NULL;
112 	apr_status_t rc = APR_SUCCESS;
113 
114 	/* ensure the directory exists */
115 	if ((rc = apr_dir_open(&dir, arg, pool)) != APR_SUCCESS) {
116 		return apr_psprintf(pool, "cannot access directory '%s' (%s)", arg,
117 				apr_strerror(rc, s_err, STR_ERROR_MAX));
118 	}
119 
120 	/* and cleanup... */
121 	if ((rc = apr_dir_close(dir)) != APR_SUCCESS) {
122 		return apr_psprintf(pool, "cannot close directory '%s' (%s)", arg,
123 				apr_strerror(rc, s_err, STR_ERROR_MAX));
124 	}
125 
126 	return NULL;
127 }
128 
129 /*
130  * check if arg is a valid cookie domain value
131  */
oidc_valid_cookie_domain(apr_pool_t * pool,const char * arg)132 const char *oidc_valid_cookie_domain(apr_pool_t *pool, const char *arg) {
133 	size_t sz, limit;
134 	char d;
135 	limit = strlen(arg);
136 	for (sz = 0; sz < limit; sz++) {
137 		d = arg[sz];
138 		if ((d < '0' || d > '9') && (d < 'a' || d > 'z') && (d < 'A' || d > 'Z')
139 				&& d != '.' && d != '-') {
140 			return (apr_psprintf(pool,
141 					"invalid character '%c' in cookie domain value: %s", d, arg));
142 		}
143 	}
144 	return NULL;
145 }
146 
147 /*
148  * parse an integer value from a string
149  */
oidc_parse_int(apr_pool_t * pool,const char * arg,int * int_value)150 const char *oidc_parse_int(apr_pool_t *pool, const char *arg, int *int_value) {
151 	char *endptr;
152 	int v = strtol(arg, &endptr, 10);
153 	if ((*arg == '\0') || (*endptr != '\0')) {
154 		return apr_psprintf(pool, "invalid integer value: %s", arg);
155 	}
156 	*int_value = v;
157 	return NULL;
158 }
159 
160 /*
161  * check if the provided integer value is between a specified minimum and maximum
162  */
oidc_valid_int_min_max(apr_pool_t * pool,int value,int min_value,int max_value)163 static const char *oidc_valid_int_min_max(apr_pool_t *pool, int value,
164 		int min_value, int max_value) {
165 	if (value < min_value) {
166 		return apr_psprintf(pool,
167 				"integer value %d is smaller than the minimum allowed value %d",
168 				value, min_value);
169 	}
170 	if (value > max_value) {
171 		return apr_psprintf(pool,
172 				"integer value %d is greater than the maximum allowed value %d",
173 				value, max_value);
174 	}
175 	return NULL;
176 }
177 
178 /*
179  * parse an integer and check validity
180  */
oidc_parse_int_valid(apr_pool_t * pool,const char * arg,int * int_value,oidc_valid_int_function_t valid_int_function)181 static const char *oidc_parse_int_valid(apr_pool_t *pool, const char *arg,
182 		int *int_value, oidc_valid_int_function_t valid_int_function) {
183 	int v = 0;
184 	const char *rv = NULL;
185 	rv = oidc_parse_int(pool, arg, &v);
186 	if (rv != NULL)
187 		return rv;
188 	rv = valid_int_function(pool, v);
189 	if (rv != NULL)
190 		return rv;
191 	*int_value = v;
192 	return NULL;
193 }
194 
195 /*
196  * parse an integer value from a string that must be between a specified minimum and maximum
197  */
oidc_parse_int_min_max(apr_pool_t * pool,const char * arg,int * int_value,int min_value,int max_value)198 static const char *oidc_parse_int_min_max(apr_pool_t *pool, const char *arg,
199 		int *int_value, int min_value, int max_value) {
200 	int v = 0;
201 	const char *rv = NULL;
202 	rv = oidc_parse_int(pool, arg, &v);
203 	if (rv != NULL)
204 		return rv;
205 	rv = oidc_valid_int_min_max(pool, v, min_value, max_value);
206 	if (rv != NULL)
207 		return rv;
208 	*int_value = v;
209 	return NULL;
210 }
211 
212 #define OIDC_LIST_OPTIONS_START     "["
213 #define OIDC_LIST_OPTIONS_END       "]"
214 #define OIDC_LIST_OPTIONS_SEPARATOR "|"
215 #define OIDC_LIST_OPTIONS_QUOTE     "'"
216 
217 /*
218  * flatten the list of string options, separated by the specified separator char
219  */
oidc_flatten_list_options(apr_pool_t * pool,char * options[])220 static char *oidc_flatten_list_options(apr_pool_t *pool, char *options[]) {
221 	int i = 0;
222 	char *result = OIDC_LIST_OPTIONS_START;
223 	while (options[i] != NULL) {
224 		if (i == 0)
225 			result = apr_psprintf(pool, "%s%s%s%s", OIDC_LIST_OPTIONS_START,
226 					OIDC_LIST_OPTIONS_QUOTE, options[i],
227 					OIDC_LIST_OPTIONS_QUOTE);
228 		else
229 			result = apr_psprintf(pool, "%s%s%s%s%s", result,
230 					OIDC_LIST_OPTIONS_SEPARATOR, OIDC_LIST_OPTIONS_QUOTE, options[i],
231 					OIDC_LIST_OPTIONS_QUOTE);
232 		i++;
233 	}
234 	result = apr_psprintf(pool, "%s%s", result, OIDC_LIST_OPTIONS_END);
235 	return result;
236 }
237 
238 /*
239  * check if arg is a valid option in the list of provided string options
240  */
oidc_valid_string_option(apr_pool_t * pool,const char * arg,char * options[])241 static const char *oidc_valid_string_option(apr_pool_t *pool, const char *arg,
242 		char *options[]) {
243 	int i = 0;
244 	while (options[i] != NULL) {
245 		if (apr_strnatcmp(arg, options[i]) == 0)
246 			break;
247 		i++;
248 	}
249 	if (options[i] == NULL) {
250 		return apr_psprintf(pool, "invalid value %s%s%s, must be one of %s",
251 				OIDC_LIST_OPTIONS_QUOTE, arg, OIDC_LIST_OPTIONS_QUOTE,
252 				oidc_flatten_list_options(pool, options));
253 	}
254 	return NULL;
255 }
256 
257 #define OIDC_CACHE_TYPE_SHM      "shm"
258 #define OIDC_CACHE_TYPE_MEMCACHE "memcache"
259 #define OIDC_CACHE_TYPE_REDIS    "redis"
260 #define OIDC_CACHE_TYPE_FILE     "file"
261 
262 /*
263  * parse the cache backend type
264  */
oidc_parse_cache_type(apr_pool_t * pool,const char * arg,oidc_cache_t ** type)265 const char *oidc_parse_cache_type(apr_pool_t *pool, const char *arg,
266 		oidc_cache_t **type) {
267 	static char *options[] = {
268 			OIDC_CACHE_TYPE_SHM,
269 #ifdef USE_MEMCACHE
270 			OIDC_CACHE_TYPE_MEMCACHE,
271 #endif
272 #ifdef USE_LIBHIREDIS
273 			OIDC_CACHE_TYPE_REDIS,
274 #endif
275 			OIDC_CACHE_TYPE_FILE,
276 			NULL };
277 	const char *rv = oidc_valid_string_option(pool, arg, options);
278 	if (rv != NULL)
279 		return rv;
280 
281 	if (apr_strnatcmp(arg, OIDC_CACHE_TYPE_SHM) == 0) {
282 		*type = &oidc_cache_shm;
283 #ifdef USE_MEMCACHE
284 	} else if (apr_strnatcmp(arg, OIDC_CACHE_TYPE_MEMCACHE) == 0) {
285 		*type = &oidc_cache_memcache;
286 #endif
287 	} else if (apr_strnatcmp(arg, OIDC_CACHE_TYPE_FILE) == 0) {
288 		*type = &oidc_cache_file;
289 #ifdef USE_LIBHIREDIS
290 	} else if (apr_strnatcmp(arg, OIDC_CACHE_TYPE_REDIS) == 0) {
291 		*type = &oidc_cache_redis;
292 #endif
293 	}
294 
295 	return NULL;
296 }
297 
298 #define OIDC_SESSION_TYPE_SERVER_CACHE_STR  "server-cache"
299 #define OIDC_SESSION_TYPE_CLIENT_COOKIE_STR "client-cookie"
300 #define OIDC_SESSION_TYPE_PERSISTENT        "persistent"
301 #define OIDC_SESSION_TYPE_SEPARATOR         ":"
302 
303 /*
304  * parse the session mechanism type and the cookie persistency property
305  */
oidc_parse_session_type(apr_pool_t * pool,const char * arg,int * type,int * persistent)306 const char *oidc_parse_session_type(apr_pool_t *pool, const char *arg,
307 		int *type, int *persistent) {
308 	static char *options[] =
309 	{
310 			OIDC_SESSION_TYPE_SERVER_CACHE_STR,
311 			OIDC_SESSION_TYPE_SERVER_CACHE_STR OIDC_SESSION_TYPE_SEPARATOR OIDC_SESSION_TYPE_PERSISTENT,
312 			OIDC_SESSION_TYPE_CLIENT_COOKIE_STR,
313 			OIDC_SESSION_TYPE_CLIENT_COOKIE_STR OIDC_SESSION_TYPE_SEPARATOR OIDC_SESSION_TYPE_PERSISTENT,
314 			NULL };
315 	const char *rv = oidc_valid_string_option(pool, arg, options);
316 	if (rv != NULL)
317 		return rv;
318 
319 	char *s = apr_pstrdup(pool, arg);
320 	char *p = strstr(s, OIDC_SESSION_TYPE_SEPARATOR);
321 
322 	if (p) {
323 		*persistent = 1;
324 		*p = '\0';
325 	}
326 
327 	if (apr_strnatcmp(s, OIDC_SESSION_TYPE_SERVER_CACHE_STR) == 0) {
328 		*type = OIDC_SESSION_TYPE_SERVER_CACHE;
329 	} else if (apr_strnatcmp(s, OIDC_SESSION_TYPE_CLIENT_COOKIE_STR) == 0) {
330 		*type = OIDC_SESSION_TYPE_CLIENT_COOKIE;
331 	}
332 	return NULL;
333 }
334 
335 /* minimum size of a SHM cache entry */
336 #define OIDC_MINIMUM_CACHE_SHM_ENTRY_SIZE_MAX 8192 + 512 + 17 // 8Kb plus overhead
337 /* maximum size of a SHM cache entry */
338 #define OIDC_MAXIMUM_CACHE_SHM_ENTRY_SIZE_MAX 1024 * 512     // 512Kb incl. overhead
339 
340 /*
341  * parse the slot size of a SHM cache entry
342  */
oidc_parse_cache_shm_entry_size_max(apr_pool_t * pool,const char * arg,int * int_value)343 const char *oidc_parse_cache_shm_entry_size_max(apr_pool_t *pool,
344 		const char *arg, int *int_value) {
345 	return oidc_parse_int_min_max(pool, arg, int_value,
346 			OIDC_MINIMUM_CACHE_SHM_ENTRY_SIZE_MAX,
347 			OIDC_MAXIMUM_CACHE_SHM_ENTRY_SIZE_MAX);
348 }
349 
350 /*
351  * parse a boolean value from a provided string
352  */
oidc_parse_boolean(apr_pool_t * pool,const char * arg,int * bool_value)353 const char *oidc_parse_boolean(apr_pool_t *pool, const char *arg,
354 		int *bool_value) {
355 	if ((apr_strnatcasecmp(arg, "true") == 0)
356 			|| (apr_strnatcasecmp(arg, "on") == 0)
357 			|| (apr_strnatcasecmp(arg, "yes") == 0)
358 			|| (apr_strnatcasecmp(arg, "1") == 0)) {
359 		*bool_value = TRUE;
360 		return NULL;
361 	}
362 	if ((apr_strnatcasecmp(arg, "false") == 0)
363 			|| (apr_strnatcasecmp(arg, "off") == 0)
364 			|| (apr_strnatcasecmp(arg, "no") == 0)
365 			|| (apr_strnatcasecmp(arg, "0") == 0)) {
366 		*bool_value = FALSE;
367 		return NULL;
368 	}
369 	return apr_psprintf(pool,
370 			"oidc_parse_boolean: could not parse boolean value from \"%s\"",
371 			arg);
372 }
373 
374 #define OIDC_ENDPOINT_AUTH_CLIENT_SECRET_POST  "client_secret_post"
375 #define OIDC_ENDPOINT_AUTH_CLIENT_SECRET_JWT   "client_secret_jwt"
376 #define OIDC_ENDPOINT_AUTH_PRIVATE_KEY_JWT     "private_key_jwt"
377 #define OIDC_ENDPOINT_AUTH_BEARER_ACCESS_TOKEN "bearer_access_token"
378 #define OIDC_ENDPOINT_AUTH_NONE                "none"
379 
380 /*
381  * check if the provided endpoint authentication method is supported
382  */
oidc_valid_endpoint_auth_method_impl(apr_pool_t * pool,const char * arg,apr_byte_t has_private_key)383 static const char *oidc_valid_endpoint_auth_method_impl(apr_pool_t *pool,
384 		const char *arg, apr_byte_t has_private_key) {
385 	static char *options[] = {
386 			OIDC_ENDPOINT_AUTH_CLIENT_SECRET_POST,
387 			OIDC_ENDPOINT_AUTH_CLIENT_SECRET_BASIC,
388 			OIDC_ENDPOINT_AUTH_CLIENT_SECRET_JWT,
389 			OIDC_ENDPOINT_AUTH_NONE,
390 			OIDC_ENDPOINT_AUTH_BEARER_ACCESS_TOKEN,
391 			NULL,
392 			NULL };
393 	if (has_private_key)
394 		options[5] = OIDC_ENDPOINT_AUTH_PRIVATE_KEY_JWT;
395 
396 	return oidc_valid_string_option(pool, arg, options);
397 }
398 
oidc_valid_endpoint_auth_method(apr_pool_t * pool,const char * arg)399 const char *oidc_valid_endpoint_auth_method(apr_pool_t *pool, const char *arg) {
400 	return oidc_valid_endpoint_auth_method_impl(pool, arg, TRUE);
401 }
402 
oidc_valid_endpoint_auth_method_no_private_key(apr_pool_t * pool,const char * arg)403 const char *oidc_valid_endpoint_auth_method_no_private_key(apr_pool_t *pool,
404 		const char *arg) {
405 	return oidc_valid_endpoint_auth_method_impl(pool, arg, FALSE);
406 }
407 
408 /*
409  * check if the provided OAuth/OIDC response type is supported
410  */
oidc_valid_response_type(apr_pool_t * pool,const char * arg)411 const char *oidc_valid_response_type(apr_pool_t *pool, const char *arg) {
412 	if (oidc_proto_flow_is_supported(pool, arg) == FALSE) {
413 		return apr_psprintf(pool,
414 				"oidc_valid_response_type: type must be one of %s",
415 				apr_array_pstrcat(pool, oidc_proto_supported_flows(pool),
416 						OIDC_CHAR_PIPE));
417 	}
418 	return NULL;
419 }
420 
421 /*
422  * check if the provided PKCE method is supported
423  */
oidc_valid_pkce_method(apr_pool_t * pool,const char * arg)424 const char *oidc_valid_pkce_method(apr_pool_t *pool, const char *arg) {
425 	static char *options[] = {
426 			OIDC_PKCE_METHOD_PLAIN,
427 			OIDC_PKCE_METHOD_S256,
428 			OIDC_PKCE_METHOD_REFERRED_TB,
429 			NULL };
430 	return oidc_valid_string_option(pool, arg, options);
431 }
432 
433 #define OIDC_RESPONSE_TYPE_FRAGMENT   "fragment"
434 #define OIDC_RESPONSE_TYPE_QUERY      "query"
435 #define OIDC_RESPONSE_TYPE_FORM_POST  "form_post"
436 
437 /*
438  * check if the provided OAuth 2.0 response mode is supported
439  */
oidc_valid_response_mode(apr_pool_t * pool,const char * arg)440 const char *oidc_valid_response_mode(apr_pool_t *pool, const char *arg) {
441 	static char *options[] = {
442 			OIDC_RESPONSE_TYPE_FRAGMENT,
443 			OIDC_RESPONSE_TYPE_QUERY,
444 			OIDC_RESPONSE_TYPE_FORM_POST,
445 			NULL };
446 	return oidc_valid_string_option(pool, arg, options);
447 }
448 
449 /*
450  * check if the provided JWT signature algorithm is supported
451  */
oidc_valid_signed_response_alg(apr_pool_t * pool,const char * arg)452 const char *oidc_valid_signed_response_alg(apr_pool_t *pool, const char *arg) {
453 	if (oidc_jose_jws_algorithm_is_supported(pool, arg) == FALSE) {
454 		return apr_psprintf(pool,
455 				"unsupported/invalid signing algorithm '%s'; must be one of [%s]",
456 				arg,
457 				apr_array_pstrcat(pool,
458 						oidc_jose_jws_supported_algorithms(pool),
459 						OIDC_CHAR_PIPE));
460 	}
461 	return NULL;
462 }
463 
464 /*
465  * check if the provided JWT content key encryption algorithm is supported
466  */
oidc_valid_encrypted_response_alg(apr_pool_t * pool,const char * arg)467 const char *oidc_valid_encrypted_response_alg(apr_pool_t *pool, const char *arg) {
468 	if (oidc_jose_jwe_algorithm_is_supported(pool, arg) == FALSE) {
469 		return apr_psprintf(pool,
470 				"unsupported/invalid encryption algorithm '%s'; must be one of [%s]",
471 				arg,
472 				apr_array_pstrcat(pool,
473 						oidc_jose_jwe_supported_algorithms(pool),
474 						OIDC_CHAR_PIPE));
475 	}
476 	return NULL;
477 }
478 
479 /*
480  * check if the provided JWT encryption cipher is supported
481  */
oidc_valid_encrypted_response_enc(apr_pool_t * pool,const char * arg)482 const char *oidc_valid_encrypted_response_enc(apr_pool_t *pool, const char *arg) {
483 	if (oidc_jose_jwe_encryption_is_supported(pool, arg) == FALSE) {
484 		return apr_psprintf(pool,
485 				"unsupported/invalid encryption type '%s'; must be one of [%s]",
486 				arg,
487 				apr_array_pstrcat(pool,
488 						oidc_jose_jwe_supported_encryptions(pool),
489 						OIDC_CHAR_PIPE));
490 	}
491 	return NULL;
492 }
493 
494 #define OIDC_SESSION_INACTIVITY_TIMEOUT_MIN 10
495 #define OIDC_SESSION_INACTIVITY_TIMEOUT_MAX 3600 * 24 * 365
496 
497 /*
498  * parse a session inactivity timeout value from the provided string
499  */
oidc_parse_session_inactivity_timeout(apr_pool_t * pool,const char * arg,int * int_value)500 const char *oidc_parse_session_inactivity_timeout(apr_pool_t *pool,
501 		const char *arg, int *int_value) {
502 	return oidc_parse_int_min_max(pool, arg, int_value,
503 			OIDC_SESSION_INACTIVITY_TIMEOUT_MIN,
504 			OIDC_SESSION_INACTIVITY_TIMEOUT_MAX);
505 }
506 
507 #define OIDC_SESSION_MAX_DURATION_MIN 15
508 #define OIDC_SESSION_MAX_DURATION_MAX 3600 * 24 * 365
509 
510 /*
511  * check the boundaries for session max lifetime
512  */
oidc_valid_session_max_duration(apr_pool_t * pool,int v)513 const char *oidc_valid_session_max_duration(apr_pool_t *pool, int v) {
514 	if (v == 0) {
515 		return NULL;
516 	}
517 	if (v < OIDC_SESSION_MAX_DURATION_MIN) {
518 		return apr_psprintf(pool, "value must not be less than %d seconds",
519 				OIDC_SESSION_MAX_DURATION_MIN);
520 	}
521 	if (v > OIDC_SESSION_MAX_DURATION_MAX) {
522 		return apr_psprintf(pool, "value must not be greater than %d seconds",
523 				OIDC_SESSION_MAX_DURATION_MAX);
524 	}
525 	return NULL;
526 }
527 
528 #define OIDC_MAX_NUMBER_OF_STATE_COOKIES_MIN  0
529 #define OIDC_MAX_NUMBER_OF_STATE_COOKIES_MAX  255
530 
531 /*
532  * check the maximum number of parallel state cookies
533  */
oidc_valid_max_number_of_state_cookies(apr_pool_t * pool,int v)534 const char *oidc_valid_max_number_of_state_cookies(apr_pool_t *pool, int v) {
535 	if (v == 0) {
536 		return NULL;
537 	}
538 	if (v < OIDC_MAX_NUMBER_OF_STATE_COOKIES_MIN) {
539 		return apr_psprintf(pool, "maximum must not be less than %d",
540 				OIDC_MAX_NUMBER_OF_STATE_COOKIES_MIN);
541 	}
542 	if (v > OIDC_MAX_NUMBER_OF_STATE_COOKIES_MAX) {
543 		return apr_psprintf(pool, "maximum must not be greater than %d",
544 				OIDC_MAX_NUMBER_OF_STATE_COOKIES_MAX);
545 	}
546 	return NULL;
547 }
548 
549 /*
550  * parse a session max duration value from the provided string
551  */
oidc_parse_session_max_duration(apr_pool_t * pool,const char * arg,int * int_value)552 const char *oidc_parse_session_max_duration(apr_pool_t *pool, const char *arg,
553 		int *int_value) {
554 	return oidc_parse_int_valid(pool, arg, int_value,
555 			oidc_valid_session_max_duration);
556 }
557 
558 /*
559  * parse a base64 encoded binary value from the provided string
560  */
oidc_parse_base64(apr_pool_t * pool,const char * input,char ** output,int * output_len)561 char *oidc_parse_base64(apr_pool_t *pool, const char *input, char **output,
562 		int *output_len) {
563 	int len = apr_base64_decode_len(input);
564 	*output = apr_palloc(pool, len);
565 	*output_len = apr_base64_decode(*output, input);
566 	if (*output_len <= 0)
567 		return apr_psprintf(pool, "base64-decoding of \"%s\" failed", input);
568 	return NULL;
569 }
570 
571 /*
572  * parse a base64url encoded binary value from the provided string
573  */
oidc_parse_base64url(apr_pool_t * pool,const char * input,char ** output,int * output_len)574 static char *oidc_parse_base64url(apr_pool_t *pool, const char *input,
575 		char **output, int *output_len) {
576 	*output_len = oidc_base64url_decode(pool, output, input);
577 	if (*output_len <= 0)
578 		return apr_psprintf(pool, "base64url-decoding of \"%s\" failed", input);
579 	return NULL;
580 }
581 
582 /*
583  * parse a hexadecimal encoded binary value from the provided string
584  */
oidc_parse_hex(apr_pool_t * pool,const char * input,char ** output,int * output_len)585 static char *oidc_parse_hex(apr_pool_t *pool, const char *input, char **output,
586 		int *output_len) {
587 	*output_len = strlen(input) / 2;
588 	const char *pos = input;
589 	unsigned char *val = apr_palloc(pool, *output_len);
590 	size_t count = 0;
591 	for (count = 0; count < (*output_len) / sizeof(unsigned char); count++) {
592 		sscanf(pos, "%2hhx", &val[count]);
593 		pos += 2;
594 	}
595 	*output = (char*) val;
596 	return NULL;
597 }
598 
599 #define OIDC_KEY_ENCODING_BASE64     "b64"
600 #define OIDC_KEY_ENCODING_BASE64_URL "b64url"
601 #define OIDC_KEY_ENCODING_HEX        "hex"
602 #define OIDC_KEY_ENCODING_PLAIN      "plain"
603 
604 /*
605  * parse a key value based on the provided encoding: b64|b64url|hex|plain
606  */
oidc_parse_key_value(apr_pool_t * pool,const char * enc,const char * input,char ** key,int * key_len)607 static const char *oidc_parse_key_value(apr_pool_t *pool, const char *enc,
608 		const char *input, char **key, int *key_len) {
609 	static char *options[] = {
610 			OIDC_KEY_ENCODING_BASE64,
611 			OIDC_KEY_ENCODING_BASE64_URL,
612 			OIDC_KEY_ENCODING_HEX,
613 			OIDC_KEY_ENCODING_PLAIN,
614 			NULL };
615 	const char *rv = oidc_valid_string_option(pool, enc, options);
616 	if (rv != NULL)
617 		return rv;
618 	if (apr_strnatcmp(enc, OIDC_KEY_ENCODING_BASE64) == 0)
619 		return oidc_parse_base64(pool, input, key, key_len);
620 	if (apr_strnatcmp(enc, OIDC_KEY_ENCODING_BASE64_URL) == 0)
621 		return oidc_parse_base64url(pool, input, key, key_len);
622 	if (apr_strnatcmp(enc, OIDC_KEY_ENCODING_HEX) == 0)
623 		return oidc_parse_hex(pool, input, key, key_len);
624 	if (apr_strnatcmp(enc, OIDC_KEY_ENCODING_PLAIN) == 0) {
625 		*key = apr_pstrdup(pool, input);
626 		*key_len = strlen(*key);
627 	}
628 	return NULL;
629 }
630 
631 #define OIDC_KEY_TUPLE_SEPARATOR "#"
632 
633 /*
634  * parse a <encoding>#<key-identifier>#<key> tuple
635  */
oidc_parse_enc_kid_key_tuple(apr_pool_t * pool,const char * tuple,char ** kid,char ** key,int * key_len,apr_byte_t triplet)636 const char *oidc_parse_enc_kid_key_tuple(apr_pool_t *pool, const char *tuple,
637 		char **kid, char **key, int *key_len, apr_byte_t triplet) {
638 	const char *rv = NULL;
639 	char *s = NULL, *p = NULL, *q = NULL, *enc = NULL;
640 
641 	if ((tuple == NULL) || (apr_strnatcmp(tuple, "") == 0))
642 		return "tuple value not set";
643 
644 	s = apr_pstrdup(pool, tuple);
645 	p = strstr(s, OIDC_KEY_TUPLE_SEPARATOR);
646 	if (p && triplet)
647 		q = strstr(p + 1, OIDC_KEY_TUPLE_SEPARATOR);
648 
649 	if (p) {
650 		if (q) {
651 			*p = '\0';
652 			*q = '\0';
653 			enc = s;
654 			p++;
655 			if (p != q)
656 				*kid = apr_pstrdup(pool, p);
657 			rv = oidc_parse_key_value(pool, enc, q + 1, key, key_len);
658 		} else {
659 			*p = '\0';
660 			*kid = s;
661 			*key = p + 1;
662 			*key_len = strlen(*key);
663 		}
664 	} else {
665 		*kid = NULL;
666 		*key = s;
667 		*key_len = strlen(*key);
668 	}
669 
670 	return rv;
671 }
672 
673 #define OIDC_PASS_ID_TOKEN_AS_CLAIMS_STR    "claims"
674 #define OIDC_PASS_IDTOKEN_AS_PAYLOAD_STR    "payload"
675 #define OIDC_PASS_IDTOKEN_AS_SERIALIZED_STR "serialized"
676 
677 /*
678  * convert a "pass id token as" value to an integer
679  */
oidc_parse_pass_idtoken_as_str2int(const char * v)680 static int oidc_parse_pass_idtoken_as_str2int(const char *v) {
681 	if (apr_strnatcmp(v, OIDC_PASS_ID_TOKEN_AS_CLAIMS_STR) == 0)
682 		return OIDC_PASS_IDTOKEN_AS_CLAIMS;
683 	if (apr_strnatcmp(v, OIDC_PASS_IDTOKEN_AS_PAYLOAD_STR) == 0)
684 		return OIDC_PASS_IDTOKEN_AS_PAYLOAD;
685 	if (apr_strnatcmp(v, OIDC_PASS_IDTOKEN_AS_SERIALIZED_STR) == 0)
686 		return OIDC_PASS_IDTOKEN_AS_SERIALIZED;
687 	return -1;
688 }
689 
690 /*
691  * parse a "pass id token as" value from the provided strings
692  */
oidc_parse_pass_idtoken_as(apr_pool_t * pool,const char * v1,const char * v2,const char * v3,int * int_value)693 const char *oidc_parse_pass_idtoken_as(apr_pool_t *pool, const char *v1,
694 		const char *v2, const char *v3, int *int_value) {
695 	static char *options[] = {
696 			OIDC_PASS_ID_TOKEN_AS_CLAIMS_STR,
697 			OIDC_PASS_IDTOKEN_AS_PAYLOAD_STR,
698 			OIDC_PASS_IDTOKEN_AS_SERIALIZED_STR,
699 			NULL };
700 	const char *rv = NULL;
701 	rv = oidc_valid_string_option(pool, v1, options);
702 	if (rv != NULL)
703 		return rv;
704 	*int_value = oidc_parse_pass_idtoken_as_str2int(v1);
705 
706 	if (v2 == NULL)
707 		return NULL;
708 
709 	rv = oidc_valid_string_option(pool, v2, options);
710 	if (rv != NULL)
711 		return rv;
712 	*int_value |= oidc_parse_pass_idtoken_as_str2int(v2);
713 
714 	if (v3 == NULL)
715 		return NULL;
716 
717 	rv = oidc_valid_string_option(pool, v3, options);
718 	if (rv != NULL)
719 		return rv;
720 	*int_value |= oidc_parse_pass_idtoken_as_str2int(v3);
721 
722 	return NULL;
723 }
724 
725 #define OIDC_PASS_USERINFO_AS_CLAIMS_STR      "claims"
726 #define OIDC_PASS_USERINFO_AS_JSON_OBJECT_STR "json"
727 #define OIDC_PASS_USERINFO_AS_JWT_STR         "jwt"
728 
729 /*
730  * convert a "pass userinfo as" value to an integer
731  */
oidc_parse_pass_userinfo_as_str2int(const char * v)732 static int oidc_parse_pass_userinfo_as_str2int(const char *v) {
733 	if (apr_strnatcmp(v, OIDC_PASS_USERINFO_AS_CLAIMS_STR) == 0)
734 		return OIDC_PASS_USERINFO_AS_CLAIMS;
735 	if (apr_strnatcmp(v, OIDC_PASS_USERINFO_AS_JSON_OBJECT_STR) == 0)
736 		return OIDC_PASS_USERINFO_AS_JSON_OBJECT;
737 	if (apr_strnatcmp(v, OIDC_PASS_USERINFO_AS_JWT_STR) == 0)
738 		return OIDC_PASS_USERINFO_AS_JWT;
739 	return -1;
740 }
741 
742 /*
743  * parse a "pass id token as" value from the provided strings
744  */
oidc_parse_pass_userinfo_as(apr_pool_t * pool,const char * v1,const char * v2,const char * v3,int * int_value)745 const char *oidc_parse_pass_userinfo_as(apr_pool_t *pool, const char *v1,
746 		const char *v2, const char *v3, int *int_value) {
747 	static char *options[] = {
748 			OIDC_PASS_USERINFO_AS_CLAIMS_STR,
749 			OIDC_PASS_USERINFO_AS_JSON_OBJECT_STR,
750 			OIDC_PASS_USERINFO_AS_JWT_STR,
751 			NULL };
752 	const char *rv = NULL;
753 	rv = oidc_valid_string_option(pool, v1, options);
754 	if (rv != NULL)
755 		return rv;
756 	*int_value = oidc_parse_pass_userinfo_as_str2int(v1);
757 
758 	if (v2 == NULL)
759 		return NULL;
760 
761 	rv = oidc_valid_string_option(pool, v2, options);
762 	if (rv != NULL)
763 		return rv;
764 	*int_value |= oidc_parse_pass_userinfo_as_str2int(v2);
765 
766 	if (v3 == NULL)
767 		return NULL;
768 
769 	rv = oidc_valid_string_option(pool, v3, options);
770 	if (rv != NULL)
771 		return rv;
772 	*int_value |= oidc_parse_pass_userinfo_as_str2int(v3);
773 
774 	return NULL;
775 }
776 
777 #define OIDC_LOGOUT_ON_ERROR_REFRESH_STR "logout_on_error"
778 
779 /*
780  * convert a "logout_on_error" to an integer
781  */
oidc_parse_logout_on_error_refresh_as_str2int(const char * v)782 static int oidc_parse_logout_on_error_refresh_as_str2int(const char *v) {
783 	if (apr_strnatcmp(v, OIDC_LOGOUT_ON_ERROR_REFRESH_STR) == 0)
784 		return OIDC_LOGOUT_ON_ERROR_REFRESH;
785 	return OIDC_CONFIG_POS_INT_UNSET;
786 }
787 
788 /*
789  * parse a "logout_on_error" value from the provided strings
790  */
oidc_parse_logout_on_error_refresh_as(apr_pool_t * pool,const char * v1,int * int_value)791 const char *oidc_parse_logout_on_error_refresh_as(apr_pool_t *pool, const char *v1,
792 		int *int_value) {
793 	static char *options[] = {
794 			OIDC_LOGOUT_ON_ERROR_REFRESH_STR,
795 			NULL };
796 	const char *rv = NULL;
797 	rv = oidc_valid_string_option(pool, v1, options);
798 	if (rv != NULL)
799 		return rv;
800 	*int_value = oidc_parse_logout_on_error_refresh_as_str2int(v1);
801 
802 	return NULL;
803 }
804 
805 #define OIDC_OAUTH_ACCEPT_TOKEN_IN_HEADER_STR "header"
806 #define OIDC_OAUTH_ACCEPT_TOKEN_IN_POST_STR   "post"
807 #define OIDC_OAUTH_ACCEPT_TOKEN_IN_QUERY_STR  "query"
808 #define OIDC_OAUTH_ACCEPT_TOKEN_IN_COOKIE_STR "cookie"
809 #define OIDC_OAUTH_ACCEPT_TOKEN_IN_BASIC_STR  "basic"
810 
811 /*
812  * convert an "accept OAuth 2.0 token in" byte value to a string representation
813  */
oidc_accept_oauth_token_in2str(apr_pool_t * pool,apr_byte_t v)814 const char *oidc_accept_oauth_token_in2str(apr_pool_t *pool, apr_byte_t v) {
815 	static char *options[] = { NULL, NULL, NULL, NULL, NULL };
816 	int i = 0;
817 	if (v & OIDC_OAUTH_ACCEPT_TOKEN_IN_HEADER) {
818 		options[i] = OIDC_OAUTH_ACCEPT_TOKEN_IN_HEADER_STR;
819 		i++;
820 	}
821 	if (v & OIDC_OAUTH_ACCEPT_TOKEN_IN_POST) {
822 		options[i] = OIDC_OAUTH_ACCEPT_TOKEN_IN_POST_STR;
823 		i++;
824 	}
825 	if (v & OIDC_OAUTH_ACCEPT_TOKEN_IN_QUERY) {
826 		options[i] = OIDC_OAUTH_ACCEPT_TOKEN_IN_QUERY_STR;
827 		i++;
828 	}
829 	if (v & OIDC_OAUTH_ACCEPT_TOKEN_IN_COOKIE) {
830 		options[i] = OIDC_OAUTH_ACCEPT_TOKEN_IN_COOKIE_STR;
831 		i++;
832 	}
833 	if (v & OIDC_OAUTH_ACCEPT_TOKEN_IN_BASIC) {
834 		options[i] = OIDC_OAUTH_ACCEPT_TOKEN_IN_BASIC_STR;
835 		i++;
836 	}
837 	return oidc_flatten_list_options(pool, options);
838 }
839 
840 /*
841  * convert an "accept OAuth 2.0 token in" value to an integer
842  */
oidc_parse_oauth_accept_token_in_str2byte(const char * v)843 static apr_byte_t oidc_parse_oauth_accept_token_in_str2byte(const char *v) {
844 	if (apr_strnatcmp(v, OIDC_OAUTH_ACCEPT_TOKEN_IN_HEADER_STR) == 0)
845 		return OIDC_OAUTH_ACCEPT_TOKEN_IN_HEADER;
846 	if (apr_strnatcmp(v, OIDC_OAUTH_ACCEPT_TOKEN_IN_POST_STR) == 0)
847 		return OIDC_OAUTH_ACCEPT_TOKEN_IN_POST;
848 	if (apr_strnatcmp(v, OIDC_OAUTH_ACCEPT_TOKEN_IN_QUERY_STR) == 0)
849 		return OIDC_OAUTH_ACCEPT_TOKEN_IN_QUERY;
850 	if (strstr(v, OIDC_OAUTH_ACCEPT_TOKEN_IN_COOKIE_STR) == v)
851 		return OIDC_OAUTH_ACCEPT_TOKEN_IN_COOKIE;
852 	if (strstr(v, OIDC_OAUTH_ACCEPT_TOKEN_IN_BASIC_STR) == v)
853 		return OIDC_OAUTH_ACCEPT_TOKEN_IN_BASIC;
854 	return OIDC_OAUTH_ACCEPT_TOKEN_IN_DEFAULT;
855 }
856 
857 #define OIDC_OAUTH_ACCEPT_TOKEN_IN_COOKIE_NAME_DEFAULT "PA.global"
858 #define OIDC_OAUTH_ACCEPT_TOKEN_IN_COOKIE_SEPARATOR    ":"
859 
860 /*
861  * parse an "accept OAuth 2.0 token in" value from the provided string
862  */
oidc_parse_accept_oauth_token_in(apr_pool_t * pool,const char * arg,int * b_value,apr_hash_t * list_options)863 const char *oidc_parse_accept_oauth_token_in(apr_pool_t *pool, const char *arg,
864 		int *b_value, apr_hash_t *list_options) {
865 	static char *options[] = {
866 			OIDC_OAUTH_ACCEPT_TOKEN_IN_HEADER_STR,
867 			OIDC_OAUTH_ACCEPT_TOKEN_IN_POST_STR,
868 			OIDC_OAUTH_ACCEPT_TOKEN_IN_QUERY_STR,
869 			OIDC_OAUTH_ACCEPT_TOKEN_IN_COOKIE_STR,
870 			OIDC_OAUTH_ACCEPT_TOKEN_IN_BASIC_STR,
871 			NULL };
872 	const char *rv = NULL;
873 
874 	const char *s = apr_pstrdup(pool, arg);
875 	char *p = strstr(s, OIDC_OAUTH_ACCEPT_TOKEN_IN_COOKIE_SEPARATOR);
876 
877 	if (p != NULL) {
878 		*p = '\0';
879 		p++;
880 	} else {
881 		p = OIDC_OAUTH_ACCEPT_TOKEN_IN_COOKIE_NAME_DEFAULT;
882 	}
883 
884 	rv = oidc_valid_string_option(pool, s, options);
885 	if (rv != NULL)
886 		return rv;
887 
888 	int v = oidc_parse_oauth_accept_token_in_str2byte(s);
889 	if (*b_value == OIDC_CONFIG_POS_INT_UNSET)
890 		*b_value = v;
891 	else
892 		*b_value |= v;
893 
894 	if (v == OIDC_OAUTH_ACCEPT_TOKEN_IN_COOKIE) {
895 		apr_hash_set(list_options,
896 				OIDC_OAUTH_ACCEPT_TOKEN_IN_OPTION_COOKIE_NAME,
897 				APR_HASH_KEY_STRING, p);
898 	}
899 
900 	return NULL;
901 }
902 
903 /*
904  * check if the specified string is a valid claim formatting configuration value
905  */
oidc_valid_claim_format(apr_pool_t * pool,const char * arg)906 const char *oidc_valid_claim_format(apr_pool_t *pool, const char *arg) {
907 	static char *options[] = {
908 			OIDC_CLAIM_FORMAT_RELATIVE,
909 			OIDC_CLAIM_FORMAT_ABSOLUTE,
910 			NULL };
911 	return oidc_valid_string_option(pool, arg, options);
912 }
913 
914 /*
915  * parse a "claim required" value from the provided string
916  */
oidc_parse_claim_required(apr_pool_t * pool,const char * arg,int * is_required)917 const char *oidc_parse_claim_required(apr_pool_t *pool, const char *arg,
918 		int *is_required) {
919 	static char *options[] = {
920 			OIDC_CLAIM_REQUIRED_MANDATORY,
921 			OIDC_CLAIM_REQUIRED_OPTIONAL,
922 			NULL };
923 	const char *rv = oidc_valid_string_option(pool, arg, options);
924 	if (rv != NULL)
925 		return rv;
926 	*is_required = (apr_strnatcmp(arg, OIDC_CLAIM_REQUIRED_MANDATORY) == 0);
927 	return NULL;
928 }
929 
930 /*
931  * check if the provided string is a valid HTTP method for the OAuth token introspection endpoint
932  */
oidc_valid_introspection_method(apr_pool_t * pool,const char * arg)933 const char *oidc_valid_introspection_method(apr_pool_t *pool, const char *arg) {
934 	static char *options[] = {
935 			OIDC_INTROSPECTION_METHOD_GET,
936 			OIDC_INTROSPECTION_METHOD_POST,
937 			NULL };
938 	return oidc_valid_string_option(pool, arg, options);
939 }
940 
941 #define OIDC_PASS_CLAIMS_AS_BOTH    "both"
942 #define OIDC_PASS_CLAIMS_AS_HEADERS "headers"
943 #define OIDC_PASS_CLAIMS_AS_ENV     "environment"
944 #define OIDC_PASS_CLAIMS_AS_NONE    "none"
945 
946 /*
947  * parse a "set claims as" value from the provided string
948  */
oidc_parse_set_claims_as(apr_pool_t * pool,const char * arg,int * in_headers,int * in_env_vars)949 const char *oidc_parse_set_claims_as(apr_pool_t *pool, const char *arg,
950 		int *in_headers, int *in_env_vars) {
951 	static char *options[] = {
952 			OIDC_PASS_CLAIMS_AS_BOTH,
953 			OIDC_PASS_CLAIMS_AS_HEADERS,
954 			OIDC_PASS_CLAIMS_AS_ENV,
955 			OIDC_PASS_CLAIMS_AS_NONE,
956 			NULL };
957 	const char *rv = oidc_valid_string_option(pool, arg, options);
958 	if (rv != NULL)
959 		return rv;
960 
961 	if (apr_strnatcmp(arg, OIDC_PASS_CLAIMS_AS_BOTH) == 0) {
962 		*in_headers = 1;
963 		*in_env_vars = 1;
964 	} else if (apr_strnatcmp(arg, OIDC_PASS_CLAIMS_AS_HEADERS) == 0) {
965 		*in_headers = 1;
966 		*in_env_vars = 0;
967 	} else if (apr_strnatcmp(arg, OIDC_PASS_CLAIMS_AS_ENV) == 0) {
968 		*in_headers = 0;
969 		*in_env_vars = 1;
970 	} else if (apr_strnatcmp(arg, OIDC_PASS_CLAIMS_AS_NONE) == 0) {
971 		*in_headers = 0;
972 		*in_env_vars = 0;
973 	}
974 
975 	return NULL;
976 }
977 
978 #define OIDC_UNAUTH_ACTION_AUTH_STR "auth"
979 #define OIDC_UNAUTH_ACTION_PASS_STR "pass"
980 #define OIDC_UNAUTH_ACTION_401_STR  "401"
981 #define OIDC_UNAUTH_ACTION_407_STR  "407"
982 #define OIDC_UNAUTH_ACTION_410_STR  "410"
983 
984 /*
985  * parse an "unauthenticated action" value from the provided string
986  */
oidc_parse_unauth_action(apr_pool_t * pool,const char * arg,int * action)987 const char *oidc_parse_unauth_action(apr_pool_t *pool, const char *arg,
988 		int *action) {
989 	static char *options[] = {
990 			OIDC_UNAUTH_ACTION_AUTH_STR,
991 			OIDC_UNAUTH_ACTION_PASS_STR,
992 			OIDC_UNAUTH_ACTION_401_STR,
993 			OIDC_UNAUTH_ACTION_407_STR,
994 			OIDC_UNAUTH_ACTION_410_STR,
995 			NULL };
996 	const char *rv = oidc_valid_string_option(pool, arg, options);
997 	if (rv != NULL)
998 		return rv;
999 
1000 	if (apr_strnatcmp(arg, OIDC_UNAUTH_ACTION_AUTH_STR) == 0)
1001 		*action = OIDC_UNAUTH_AUTHENTICATE;
1002 	else if (apr_strnatcmp(arg, OIDC_UNAUTH_ACTION_PASS_STR) == 0)
1003 		*action = OIDC_UNAUTH_PASS;
1004 	else if (apr_strnatcmp(arg, OIDC_UNAUTH_ACTION_401_STR) == 0)
1005 		*action = OIDC_UNAUTH_RETURN401;
1006 	else if (apr_strnatcmp(arg, OIDC_UNAUTH_ACTION_407_STR) == 0)
1007 		*action = OIDC_UNAUTH_RETURN407;
1008 	else if (apr_strnatcmp(arg, OIDC_UNAUTH_ACTION_410_STR) == 0)
1009 		*action = OIDC_UNAUTH_RETURN410;
1010 
1011 	return NULL;
1012 }
1013 
1014 #define OIDC_UNAUTZ_ACTION_AUTH_STR "auth"
1015 #define OIDC_UNAUTZ_ACTION_401_STR  "401"
1016 #define OIDC_UNAUTZ_ACTION_403_STR  "403"
1017 
1018 /*
1019  * parse an "unauthorized action" value from the provided string
1020  */
oidc_parse_unautz_action(apr_pool_t * pool,const char * arg,int * action)1021 const char *oidc_parse_unautz_action(apr_pool_t *pool, const char *arg,
1022 		int *action) {
1023 	static char *options[] = {
1024 			OIDC_UNAUTZ_ACTION_AUTH_STR,
1025 			OIDC_UNAUTZ_ACTION_401_STR,
1026 			OIDC_UNAUTZ_ACTION_403_STR,
1027 			NULL };
1028 	const char *rv = oidc_valid_string_option(pool, arg, options);
1029 	if (rv != NULL)
1030 		return rv;
1031 
1032 	if (apr_strnatcmp(arg, OIDC_UNAUTZ_ACTION_AUTH_STR) == 0)
1033 		*action = OIDC_UNAUTZ_AUTHENTICATE;
1034 	else if (apr_strnatcmp(arg, OIDC_UNAUTZ_ACTION_401_STR) == 0)
1035 		*action = OIDC_UNAUTZ_RETURN401;
1036 	else if (apr_strnatcmp(arg, OIDC_UNAUTZ_ACTION_403_STR) == 0)
1037 		*action = OIDC_UNAUTZ_RETURN403;
1038 
1039 	return NULL;
1040 }
1041 
1042 /*
1043  * check if there's a valid entry in a string of arrays, with a preference
1044  */
oidc_valid_string_in_array(apr_pool_t * pool,json_t * json,const char * key,oidc_valid_function_t valid_function,char ** value,apr_byte_t optional,const char * preference)1045 const char *oidc_valid_string_in_array(apr_pool_t *pool, json_t *json,
1046 		const char *key, oidc_valid_function_t valid_function, char **value,
1047 		apr_byte_t optional, const char *preference) {
1048 	int i = 0;
1049 	json_t *json_arr = json_object_get(json, key);
1050 	apr_byte_t found = FALSE;
1051 	if ((json_arr != NULL) && (json_is_array(json_arr))) {
1052 		for (i = 0; i < json_array_size(json_arr); i++) {
1053 			json_t *elem = json_array_get(json_arr, i);
1054 			if (!json_is_string(elem)) {
1055 				return apr_psprintf(pool,
1056 						"unhandled in-array JSON non-string object type [%d]",
1057 						elem->type);
1058 				continue;
1059 			}
1060 			if (valid_function(pool, json_string_value(elem)) == NULL) {
1061 				found = TRUE;
1062 				if (value != NULL) {
1063 					if ((preference != NULL)
1064 							&& (apr_strnatcmp(json_string_value(elem),
1065 									preference) == 0)) {
1066 						*value = apr_pstrdup(pool, json_string_value(elem));
1067 						break;
1068 					}
1069 					if (*value == NULL) {
1070 						*value = apr_pstrdup(pool, json_string_value(elem));
1071 					}
1072 				}
1073 			}
1074 		}
1075 		if (found == FALSE) {
1076 			return apr_psprintf(pool,
1077 					"could not find a valid array string element for entry \"%s\"",
1078 					key);
1079 		}
1080 	} else if (optional == FALSE) {
1081 		return apr_psprintf(pool, "JSON object did not contain a \"%s\" array",
1082 				key);
1083 	}
1084 	return NULL;
1085 }
1086 
1087 #define OIDC_JWKS_REFRESH_INTERVAL_MIN 300
1088 #define OIDC_JWKS_REFRESH_INTERVAL_MAX 3600 * 24 * 365
1089 
1090 /*
1091  * check the boundaries for JWKs refresh interval
1092  */
oidc_valid_jwks_refresh_interval(apr_pool_t * pool,int v)1093 const char *oidc_valid_jwks_refresh_interval(apr_pool_t *pool, int v) {
1094 	return oidc_valid_int_min_max(pool, v, OIDC_JWKS_REFRESH_INTERVAL_MIN,
1095 			OIDC_JWKS_REFRESH_INTERVAL_MAX);
1096 }
1097 
1098 /*
1099  * parse a JWKs refresh interval from the provided string
1100  */
oidc_parse_jwks_refresh_interval(apr_pool_t * pool,const char * arg,int * int_value)1101 const char *oidc_parse_jwks_refresh_interval(apr_pool_t *pool, const char *arg,
1102 		int *int_value) {
1103 	return oidc_parse_int_valid(pool, arg, int_value,
1104 			oidc_valid_jwks_refresh_interval);
1105 }
1106 
1107 #define OIDC_IDTOKEN_IAT_SLACK_MIN 0
1108 #define OIDC_IDTOKEN_IAT_SLACK_MAX 3600
1109 
1110 /*
1111  * check the boundaries for ID token "issued-at" (iat) timestamp slack
1112  */
oidc_valid_idtoken_iat_slack(apr_pool_t * pool,int v)1113 const char *oidc_valid_idtoken_iat_slack(apr_pool_t *pool, int v) {
1114 	return oidc_valid_int_min_max(pool, v, OIDC_IDTOKEN_IAT_SLACK_MIN,
1115 			OIDC_IDTOKEN_IAT_SLACK_MAX);
1116 }
1117 
1118 /*
1119  * parse an ID token "iat" slack interval
1120  */
oidc_parse_idtoken_iat_slack(apr_pool_t * pool,const char * arg,int * int_value)1121 const char *oidc_parse_idtoken_iat_slack(apr_pool_t *pool, const char *arg,
1122 		int *int_value) {
1123 	return oidc_parse_int_valid(pool, arg, int_value,
1124 			oidc_valid_idtoken_iat_slack);
1125 }
1126 
1127 #define OIDC_USERINFO_REFRESH_INTERVAL_MIN 0
1128 #define OIDC_USERINFO_REFRESH_INTERVAL_MAX 3600 * 24 * 365
1129 
1130 /*
1131  * check the boundaries for the userinfo refresh interval
1132  */
oidc_valid_userinfo_refresh_interval(apr_pool_t * pool,int v)1133 const char *oidc_valid_userinfo_refresh_interval(apr_pool_t *pool, int v) {
1134 	return oidc_valid_int_min_max(pool, v, OIDC_USERINFO_REFRESH_INTERVAL_MIN,
1135 			OIDC_USERINFO_REFRESH_INTERVAL_MAX);
1136 }
1137 
1138 /*
1139  * parse a userinfo refresh interval from the provided string
1140  */
oidc_parse_userinfo_refresh_interval(apr_pool_t * pool,const char * arg,int * int_value)1141 const char *oidc_parse_userinfo_refresh_interval(apr_pool_t *pool,
1142 		const char *arg, int *int_value) {
1143 	return oidc_parse_int_valid(pool, arg, int_value,
1144 			oidc_valid_userinfo_refresh_interval);
1145 }
1146 
1147 #define OIDC_USER_INFO_TOKEN_METHOD_HEADER_STR "authz_header"
1148 #define OIDC_USER_INFO_TOKEN_METHOD_POST_STR   "post_param"
1149 
1150 /*
1151  * check if the provided string is a valid userinfo token presentation method
1152  */
oidc_valid_userinfo_token_method(apr_pool_t * pool,const char * arg)1153 const char *oidc_valid_userinfo_token_method(apr_pool_t *pool, const char *arg) {
1154 	static char *options[] = {
1155 			OIDC_USER_INFO_TOKEN_METHOD_HEADER_STR,
1156 			OIDC_USER_INFO_TOKEN_METHOD_POST_STR,
1157 			NULL };
1158 	return oidc_valid_string_option(pool, arg, options);
1159 }
1160 
1161 /*
1162  * parse a userinfo token method string value to an integer
1163  */
oidc_parse_userinfo_token_method(apr_pool_t * pool,const char * arg,int * int_value)1164 const char *oidc_parse_userinfo_token_method(apr_pool_t *pool, const char *arg,
1165 		int *int_value) {
1166 	const char *rv = oidc_valid_userinfo_token_method(pool, arg);
1167 	if (rv != NULL)
1168 		return rv;
1169 
1170 	if (apr_strnatcmp(arg, OIDC_USER_INFO_TOKEN_METHOD_HEADER_STR) == 0)
1171 		*int_value = OIDC_USER_INFO_TOKEN_METHOD_HEADER;
1172 	if (apr_strnatcmp(arg, OIDC_USER_INFO_TOKEN_METHOD_POST_STR) == 0)
1173 		*int_value = OIDC_USER_INFO_TOKEN_METHOD_POST;
1174 
1175 	return NULL;
1176 }
1177 
1178 /*
1179  * parse an "info hook data" value from the provided string
1180  */
oidc_parse_info_hook_data(apr_pool_t * pool,const char * arg,apr_hash_t ** hook_data)1181 const char *oidc_parse_info_hook_data(apr_pool_t *pool, const char *arg,
1182 		apr_hash_t **hook_data) {
1183 	static char *options[] = {
1184 			OIDC_HOOK_INFO_TIMESTAMP,
1185 			OIDC_HOOK_INFO_ACCES_TOKEN,
1186 			OIDC_HOOK_INFO_ACCES_TOKEN_EXP,
1187 			OIDC_HOOK_INFO_ID_TOKEN,
1188 			OIDC_HOOK_INFO_USER_INFO,
1189 			OIDC_HOOK_INFO_REFRESH_TOKEN,
1190 			OIDC_HOOK_INFO_SESSION_EXP,
1191 			OIDC_HOOK_INFO_SESSION_TIMEOUT,
1192 			OIDC_HOOK_INFO_SESSION_REMOTE_USER,
1193 			OIDC_HOOK_INFO_SESSION,
1194 			NULL };
1195 	const char *rv = oidc_valid_string_option(pool, arg, options);
1196 	if (rv != NULL)
1197 		return rv;
1198 	if (*hook_data == NULL)
1199 		*hook_data = apr_hash_make(pool);
1200 	apr_hash_set(*hook_data, arg, APR_HASH_KEY_STRING, arg);
1201 
1202 	return NULL;
1203 }
1204 
1205 #define OIDC_TOKEN_BINDING_POLICY_DISABLED_STR "disabled"
1206 #define OIDC_TOKEN_BINDING_POLICY_OPTIONAL_STR "optional"
1207 #define OIDC_TOKEN_BINDING_POLICY_REQUIRED_STR "required"
1208 #define OIDC_TOKEN_BINDING_POLICY_ENFORCED_STR "enforced"
1209 
oidc_token_binding_policy2str(apr_pool_t * pool,int v)1210 const char *oidc_token_binding_policy2str(apr_pool_t *pool, int v) {
1211 	if (v == OIDC_TOKEN_BINDING_POLICY_DISABLED)
1212 		return OIDC_TOKEN_BINDING_POLICY_DISABLED;
1213 	if (v == OIDC_TOKEN_BINDING_POLICY_OPTIONAL)
1214 		return OIDC_TOKEN_BINDING_POLICY_OPTIONAL_STR;
1215 	if (v == OIDC_TOKEN_BINDING_POLICY_REQUIRED)
1216 		return OIDC_TOKEN_BINDING_POLICY_REQUIRED_STR;
1217 	if (v == OIDC_TOKEN_BINDING_POLICY_ENFORCED)
1218 		return OIDC_TOKEN_BINDING_POLICY_ENFORCED_STR;
1219 	return NULL;
1220 }
1221 
1222 /*
1223  * check token binding policy string value
1224  */
oidc_valid_token_binding_policy(apr_pool_t * pool,const char * arg)1225 const char *oidc_valid_token_binding_policy(apr_pool_t *pool, const char *arg) {
1226 	static char *options[] = {
1227 			OIDC_TOKEN_BINDING_POLICY_DISABLED_STR,
1228 			OIDC_TOKEN_BINDING_POLICY_OPTIONAL_STR,
1229 			OIDC_TOKEN_BINDING_POLICY_REQUIRED_STR,
1230 			OIDC_TOKEN_BINDING_POLICY_ENFORCED_STR,
1231 			NULL };
1232 	return oidc_valid_string_option(pool, arg, options);
1233 }
1234 
1235 /*
1236  * parse token binding policy
1237  */
oidc_parse_token_binding_policy(apr_pool_t * pool,const char * arg,int * policy)1238 const char *oidc_parse_token_binding_policy(apr_pool_t *pool, const char *arg,
1239 		int *policy) {
1240 	const char *rv = oidc_valid_token_binding_policy(pool, arg);
1241 	if (rv != NULL)
1242 		return rv;
1243 
1244 	if (apr_strnatcmp(arg, OIDC_TOKEN_BINDING_POLICY_DISABLED_STR) == 0)
1245 		*policy = OIDC_TOKEN_BINDING_POLICY_DISABLED;
1246 	else if (apr_strnatcmp(arg, OIDC_TOKEN_BINDING_POLICY_OPTIONAL_STR) == 0)
1247 		*policy = OIDC_TOKEN_BINDING_POLICY_OPTIONAL;
1248 	else if (apr_strnatcmp(arg, OIDC_TOKEN_BINDING_POLICY_REQUIRED_STR) == 0)
1249 		*policy = OIDC_TOKEN_BINDING_POLICY_REQUIRED;
1250 	else if (apr_strnatcmp(arg, OIDC_TOKEN_BINDING_POLICY_ENFORCED_STR) == 0)
1251 		*policy = OIDC_TOKEN_BINDING_POLICY_ENFORCED;
1252 
1253 	return NULL;
1254 }
1255 
1256 #define OIDC_AUTH_REQUEST_METHOD_GET_STR  "GET"
1257 #define OIDC_AUTH_REQEUST_METHOD_POST_STR "POST"
1258 
1259 /*
1260  * parse method for sending the authentication request
1261  */
oidc_valid_auth_request_method(apr_pool_t * pool,const char * arg)1262 const char *oidc_valid_auth_request_method(apr_pool_t *pool, const char *arg) {
1263 	static char *options[] = {
1264 			OIDC_AUTH_REQUEST_METHOD_GET_STR,
1265 			OIDC_AUTH_REQEUST_METHOD_POST_STR,
1266 			NULL };
1267 	return oidc_valid_string_option(pool, arg, options);
1268 }
1269 
1270 /*
1271  * parse method for sending the authentication request
1272  */
oidc_parse_auth_request_method(apr_pool_t * pool,const char * arg,int * method)1273 const char *oidc_parse_auth_request_method(apr_pool_t *pool, const char *arg,
1274 		int *method) {
1275 	const char *rv = oidc_valid_auth_request_method(pool, arg);
1276 	if (rv != NULL)
1277 		return rv;
1278 
1279 	if (apr_strnatcmp(arg, OIDC_AUTH_REQUEST_METHOD_GET_STR) == 0)
1280 		*method = OIDC_AUTH_REQUEST_METHOD_GET;
1281 	else if (apr_strnatcmp(arg, OIDC_AUTH_REQEUST_METHOD_POST_STR) == 0)
1282 		*method = OIDC_AUTH_REQUEST_METHOD_POST;
1283 
1284 	return NULL;
1285 }
1286 
1287 /*
1288  * parse the maximum number of parallel state cookies
1289  */
oidc_parse_max_number_of_state_cookies(apr_pool_t * pool,const char * arg1,const char * arg2,int * int_value,int * bool_value)1290 const char *oidc_parse_max_number_of_state_cookies(apr_pool_t *pool,
1291 		const char *arg1, const char *arg2, int *int_value, int *bool_value) {
1292 	const char *rv = NULL;
1293 
1294 	rv = oidc_parse_int_valid(pool, arg1, int_value,
1295 			oidc_valid_max_number_of_state_cookies);
1296 	if ((rv == NULL) && (arg2 != NULL))
1297 		rv = oidc_parse_boolean(pool, arg2, bool_value);
1298 	return rv;
1299 }
1300 
1301 #define OIDC_REFRESH_ACCESS_TOKEN_BEFORE_EXPIRY_MIN 0
1302 #define OIDC_REFRESH_ACCESS_TOKEN_BEFORE_EXPIRY_MAX 3600 * 24 * 365
1303 
1304 /*
1305  * check the boundaries for the refresh access token expiry TTL
1306  */
oidc_valid_refresh_access_token_before_expiry(apr_pool_t * pool,int v)1307 const char *oidc_valid_refresh_access_token_before_expiry(apr_pool_t *pool,
1308 		int v) {
1309 	return oidc_valid_int_min_max(pool, v,
1310 			OIDC_REFRESH_ACCESS_TOKEN_BEFORE_EXPIRY_MIN,
1311 			OIDC_REFRESH_ACCESS_TOKEN_BEFORE_EXPIRY_MAX);
1312 }
1313 
1314 /*
1315  * parse an access token expiry TTL from the provided string
1316  */
oidc_parse_refresh_access_token_before_expiry(apr_pool_t * pool,const char * arg,int * int_value)1317 const char *oidc_parse_refresh_access_token_before_expiry(apr_pool_t *pool,
1318 		const char *arg, int *int_value) {
1319 	return oidc_parse_int_valid(pool, arg, int_value,
1320 			oidc_valid_refresh_access_token_before_expiry);
1321 }
1322 
1323 #define OIDC_STATE_INPUT_HEADERS_AS_BOTH            "both"
1324 #define OIDC_STATE_INPUT_HEADERS_AS_USER_AGENT      "user-agent"
1325 #define OIDC_STATE_INPUT_HEADERS_AS_X_FORWARDED_FOR "x-forwarded-for"
1326 #define OIDC_STATE_INPUT_HEADERS_AS_NONE            "none"
1327 
1328 /*
1329  * parse a "set state input headers as" value from the provided string
1330  */
oidc_parse_set_state_input_headers_as(apr_pool_t * pool,const char * arg,apr_byte_t * state_input_headers)1331 const char *oidc_parse_set_state_input_headers_as(apr_pool_t *pool, const char *arg,
1332 		apr_byte_t *state_input_headers) {
1333 	static char *options[] = {
1334 			OIDC_STATE_INPUT_HEADERS_AS_BOTH,
1335 			OIDC_STATE_INPUT_HEADERS_AS_USER_AGENT,
1336 			OIDC_STATE_INPUT_HEADERS_AS_X_FORWARDED_FOR,
1337 			OIDC_STATE_INPUT_HEADERS_AS_NONE,
1338 			NULL };
1339 	const char *rv = oidc_valid_string_option(pool, arg, options);
1340 	if (rv != NULL)
1341 		return rv;
1342 
1343 	if (apr_strnatcmp(arg, OIDC_STATE_INPUT_HEADERS_AS_BOTH) == 0) {
1344 		*state_input_headers = OIDC_STATE_INPUT_HEADERS_USER_AGENT | OIDC_STATE_INPUT_HEADERS_X_FORWARDED_FOR;
1345 	} else if (apr_strnatcmp(arg, OIDC_STATE_INPUT_HEADERS_AS_USER_AGENT) == 0) {
1346 		*state_input_headers = OIDC_STATE_INPUT_HEADERS_USER_AGENT;
1347 	} else if (apr_strnatcmp(arg, OIDC_STATE_INPUT_HEADERS_AS_X_FORWARDED_FOR) == 0) {
1348 		*state_input_headers = OIDC_STATE_INPUT_HEADERS_X_FORWARDED_FOR;
1349 	} else if (apr_strnatcmp(arg, OIDC_STATE_INPUT_HEADERS_AS_NONE) == 0) {
1350 		*state_input_headers = 0;
1351 	}
1352 
1353 	return NULL;
1354 }
1355