/src/mod_auth_openidc/src/cfg/parse.c
Line | Count | Source |
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-2026 ZmartZone Holding BV |
22 | | * All rights reserved. |
23 | | * |
24 | | * DISCLAIMER OF WARRANTIES: |
25 | | * |
26 | | * THE SOFTWARE PROVIDED HEREUNDER IS PROVIDED ON AN "AS IS" BASIS, WITHOUT |
27 | | * ANY WARRANTIES OR REPRESENTATIONS EXPRESS, IMPLIED OR STATUTORY; INCLUDING, |
28 | | * WITHOUT LIMITATION, WARRANTIES OF QUALITY, PERFORMANCE, NONINFRINGEMENT, |
29 | | * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. NOR ARE THERE ANY |
30 | | * WARRANTIES CREATED BY A COURSE OR DEALING, COURSE OF PERFORMANCE OR TRADE |
31 | | * USAGE. FURTHERMORE, THERE ARE NO WARRANTIES THAT THE SOFTWARE WILL MEET |
32 | | * YOUR NEEDS OR BE FREE FROM ERRORS, OR THAT THE OPERATION OF THE SOFTWARE |
33 | | * WILL BE UNINTERRUPTED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR |
34 | | * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
35 | | * EXEMPLARY, OR CONSEQUENTIAL DAMAGES HOWEVER CAUSED AND ON ANY THEORY OF |
36 | | * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING |
37 | | * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
38 | | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
39 | | * |
40 | | * @Author: Hans Zandbelt - hans.zandbelt@openidc.com |
41 | | */ |
42 | | |
43 | | #include "cfg/parse.h" |
44 | | #include "cfg/dir.h" |
45 | | #include "const.h" |
46 | | #include "proto/proto.h" |
47 | | #include "util/util.h" |
48 | | #include <apr_base64.h> |
49 | | #include <apr_file_io.h> |
50 | | #include <apr_strings.h> |
51 | | |
52 | | /* separators used in "flattened" string/option lists */ |
53 | 12.4k | #define OIDC_LIST_OPTIONS_START "[" |
54 | 12.4k | #define OIDC_LIST_OPTIONS_END "]" |
55 | 72.2k | #define OIDC_LIST_OPTIONS_SEPARATOR "|" |
56 | 194k | #define OIDC_LIST_OPTIONS_QUOTE "'" |
57 | | |
58 | | /* |
59 | | * flatten the provided list of n {value, string} options into a "['a'|'b']" display string |
60 | | */ |
61 | 12.4k | char *oidc_cfg_parse_options_flatten(apr_pool_t *pool, const oidc_cfg_option_t options[], int n) { |
62 | 12.4k | char *result = NULL; |
63 | 12.4k | if (n <= 0) |
64 | 0 | return OIDC_LIST_OPTIONS_START OIDC_LIST_OPTIONS_END; |
65 | 12.4k | result = apr_psprintf(pool, "%s%s%s%s", OIDC_LIST_OPTIONS_QUOTE, options[--n].str, OIDC_LIST_OPTIONS_QUOTE, |
66 | 12.4k | OIDC_LIST_OPTIONS_END); |
67 | 84.6k | for (--n; n >= 0; --n) |
68 | 72.2k | result = apr_psprintf(pool, "%s%s%s%s%s", OIDC_LIST_OPTIONS_QUOTE, options[n].str, |
69 | 72.2k | OIDC_LIST_OPTIONS_QUOTE, OIDC_LIST_OPTIONS_SEPARATOR, result); |
70 | 12.4k | return apr_psprintf(pool, "%s%s", OIDC_LIST_OPTIONS_START, result); |
71 | 12.4k | } |
72 | | |
73 | | /* |
74 | | * flatten the provided NULL-terminated list of plain string options into the same |
75 | | * "['a'|'b']" display format, delegating to oidc_cfg_parse_options_flatten |
76 | | */ |
77 | 12.3k | static char *oidc_cfg_parse_string_options_flatten(apr_pool_t *pool, const char *options[]) { |
78 | 12.3k | int n = 0; |
79 | 96.7k | while (options[n] != NULL) |
80 | 84.4k | n++; |
81 | 12.3k | oidc_cfg_option_t *opts = apr_pcalloc(pool, n * sizeof(oidc_cfg_option_t)); |
82 | 96.7k | for (int i = 0; i < n; i++) { |
83 | 84.4k | opts[i].val = i; |
84 | 84.4k | opts[i].str = options[i]; |
85 | 84.4k | } |
86 | 12.3k | return oidc_cfg_parse_options_flatten(pool, opts, n); |
87 | 12.3k | } |
88 | | |
89 | | /* |
90 | | * check if arg is a valid option in the list of provided string options |
91 | | */ |
92 | 14.2k | const char *oidc_cfg_parse_is_valid_option(apr_pool_t *pool, const char *arg, const char *options[]) { |
93 | 14.2k | int i = 0; |
94 | 104k | while (options[i] != NULL) { |
95 | 92.5k | if (_oidc_strcmp(arg, options[i]) == 0) |
96 | 1.95k | break; |
97 | 90.6k | i++; |
98 | 90.6k | } |
99 | 14.2k | if (options[i] == NULL) { |
100 | 12.3k | return apr_psprintf(pool, "invalid value %s%s%s, must be one of %s", OIDC_LIST_OPTIONS_QUOTE, arg, |
101 | 12.3k | OIDC_LIST_OPTIONS_QUOTE, oidc_cfg_parse_string_options_flatten(pool, options)); |
102 | 12.3k | } |
103 | 1.95k | return NULL; |
104 | 14.2k | } |
105 | | |
106 | | /* |
107 | | * parse an value provided as an option string into the corresponding integer/enum |
108 | | */ |
109 | | static char *oidc_cfg_parse_option_impl(apr_pool_t *pool, const oidc_cfg_option_t options[], int n, const char *arg, |
110 | 12.4k | int *v, int (*fstrcmp)(const char *, const char *)) { |
111 | 12.4k | int i = 0; |
112 | 28.5k | while ((i < n) && (fstrcmp(arg, options[i].str) != 0)) |
113 | 16.0k | i++; |
114 | 12.4k | if (i < n) { |
115 | 12.3k | *v = options[i].val; |
116 | 12.3k | return NULL; |
117 | 12.3k | } |
118 | 105 | return apr_psprintf(pool, "invalid value %s%s%s, must be one of %s", OIDC_LIST_OPTIONS_QUOTE, arg, |
119 | 105 | OIDC_LIST_OPTIONS_QUOTE, oidc_cfg_parse_options_flatten(pool, options, n)); |
120 | 12.4k | } |
121 | | |
122 | | /* |
123 | | * parse an value provided as an option string into the corresponding integer/enum case sensitive |
124 | | */ |
125 | 12.4k | char *oidc_cfg_parse_option(apr_pool_t *pool, const oidc_cfg_option_t options[], int n, const char *arg, int *v) { |
126 | 12.4k | return oidc_cfg_parse_option_impl(pool, options, n, arg, v, _oidc_strcmp); |
127 | 12.4k | } |
128 | | |
129 | | /* |
130 | | * parse an value provided as an option string into the corresponding integer/enum case insensitive |
131 | | */ |
132 | | char *oidc_cfg_parse_option_ignore_case(apr_pool_t *pool, const oidc_cfg_option_t options[], int n, const char *arg, |
133 | 0 | int *v) { |
134 | 0 | return oidc_cfg_parse_option_impl(pool, options, n, arg, v, _oidc_strnatcasecmp); |
135 | 0 | } |
136 | | |
137 | | /* |
138 | | * check if the provided integer value is between a specified minimum and maximum |
139 | | */ |
140 | 35.5k | const char *oidc_cfg_parse_is_valid_int(apr_pool_t *pool, int value, int min_value, int max_value) { |
141 | 35.5k | if (value < min_value) { |
142 | 151 | return apr_psprintf(pool, "integer value %d is smaller than the minimum allowed value %d", value, |
143 | 151 | min_value); |
144 | 151 | } |
145 | 35.3k | if (value > max_value) { |
146 | 50 | return apr_psprintf(pool, "integer value %d is greater than the maximum allowed value %d", value, |
147 | 50 | max_value); |
148 | 50 | } |
149 | 35.3k | return NULL; |
150 | 35.3k | } |
151 | | |
152 | | /* |
153 | | * parse a string into a boolean |
154 | | */ |
155 | 23 | const char *oidc_cfg_parse_boolean(apr_pool_t *pool, const char *arg, int *bool_value) { |
156 | 23 | if ((_oidc_strnatcasecmp(arg, "true") == 0) || (_oidc_strnatcasecmp(arg, "on") == 0) || |
157 | 19 | (_oidc_strnatcasecmp(arg, "yes") == 0) || (_oidc_strnatcasecmp(arg, "1") == 0)) { |
158 | 6 | *bool_value = TRUE; |
159 | 6 | return NULL; |
160 | 6 | } |
161 | 17 | if ((_oidc_strnatcasecmp(arg, "false") == 0) || (_oidc_strnatcasecmp(arg, "off") == 0) || |
162 | 16 | (_oidc_strnatcasecmp(arg, "no") == 0) || (_oidc_strnatcasecmp(arg, "0") == 0)) { |
163 | 3 | *bool_value = FALSE; |
164 | 3 | return NULL; |
165 | 3 | } |
166 | 14 | return apr_psprintf(pool, "oidc_parse_boolean: could not parse boolean value from \"%s\"", arg); |
167 | 17 | } |
168 | | |
169 | | /* |
170 | | * parse a string into an integer |
171 | | */ |
172 | 0 | const char *oidc_cfg_parse_int(apr_pool_t *pool, const char *arg, int *int_value) { |
173 | 0 | if ((arg == NULL) || (*arg == '\0')) |
174 | 0 | return apr_psprintf(pool, "no integer value"); |
175 | | /* the shared core rejects non-numeric input, trailing junk ("300x") and overflow, all of which |
176 | | * the previous sscanf("%d") accepted silently */ |
177 | 0 | if (_oidc_str_to_int_checked(arg, int_value) == FALSE) |
178 | 0 | return apr_psprintf(pool, "invalid or out-of-range integer value: %s", arg); |
179 | 0 | return NULL; |
180 | 0 | } |
181 | | |
182 | | /* |
183 | | * parse a string into an integer if it is in a valid min/max range |
184 | | */ |
185 | | const char *oidc_cfg_parse_int_min_max(apr_pool_t *pool, const char *arg, int *int_value, int min_value, |
186 | 0 | int max_value) { |
187 | 0 | int v = 0; |
188 | 0 | const char *rv = NULL; |
189 | 0 | rv = oidc_cfg_parse_int(pool, arg, &v); |
190 | 0 | if (rv != NULL) |
191 | 0 | return rv; |
192 | 0 | rv = oidc_cfg_parse_is_valid_int(pool, v, min_value, max_value); |
193 | 0 | if (rv != NULL) |
194 | 0 | return rv; |
195 | 0 | *int_value = v; |
196 | 0 | return NULL; |
197 | 0 | } |
198 | | |
199 | | /* |
200 | | * parse an integer value that must lie in [min_value, max_value], or be exactly 0, |
201 | | * which turns the feature off rather than sizing it; the range would otherwise |
202 | | * reject the very value that documents how to disable it |
203 | | */ |
204 | | const char *oidc_cfg_parse_int_min_max_or_zero(apr_pool_t *pool, const char *arg, int *int_value, int min_value, |
205 | 0 | int max_value) { |
206 | 0 | int v = 0; |
207 | 0 | const char *rv = NULL; |
208 | 0 | rv = oidc_cfg_parse_int(pool, arg, &v); |
209 | 0 | if (rv != NULL) |
210 | 0 | return rv; |
211 | 0 | if (v != 0) { |
212 | 0 | rv = oidc_cfg_parse_is_valid_int(pool, v, min_value, max_value); |
213 | 0 | if (rv != NULL) |
214 | 0 | return rv; |
215 | 0 | } |
216 | 0 | *int_value = v; |
217 | 0 | return NULL; |
218 | 0 | } |
219 | | |
220 | | /* |
221 | | * parse a timeout string via ap_timeout_parameter_parse into an |
222 | | * apr_interval_time_t if it is in a valid min/max range |
223 | | */ |
224 | | const char *oidc_cfg_parse_timeout_min_max(apr_pool_t *pool, const char *arg, apr_interval_time_t *timeout_value, |
225 | 0 | apr_interval_time_t min_value, apr_interval_time_t max_value) { |
226 | 0 | #if AP_MODULE_MAGIC_AT_LEAST(20080920, 2) |
227 | 0 | apr_interval_time_t timeout; |
228 | | #else |
229 | | char *endptr; |
230 | | apr_int64_t timeout; |
231 | | #endif |
232 | |
|
233 | 0 | #if AP_MODULE_MAGIC_AT_LEAST(20080920, 2) |
234 | 0 | if (ap_timeout_parameter_parse(arg, &timeout, "s") != APR_SUCCESS) { |
235 | 0 | return apr_psprintf(pool, "not a valid timeout parameter: %s", arg); |
236 | 0 | } |
237 | | #else |
238 | | timeout = apr_strtoi64(arg, &endptr, 10); |
239 | | if (errno != 0 || *endptr != '\0') { |
240 | | return apr_psprintf(pool, "not a valid timeout parameter: %s", arg); |
241 | | } |
242 | | timeout = apr_time_from_sec(timeout); |
243 | | #endif |
244 | | |
245 | 0 | if (timeout < min_value) { |
246 | 0 | return apr_psprintf(pool, |
247 | 0 | "timeout value %" APR_TIME_T_FMT |
248 | 0 | " is smaller than the minimum allowed value %" APR_TIME_T_FMT, |
249 | 0 | timeout, min_value); |
250 | 0 | } |
251 | 0 | if (timeout > max_value) { |
252 | 0 | return apr_psprintf(pool, |
253 | 0 | "timeout value %" APR_TIME_T_FMT |
254 | 0 | " is greater than the maximum allowed value %" APR_TIME_T_FMT, |
255 | 0 | timeout, max_value); |
256 | 0 | } |
257 | 0 | *timeout_value = timeout; |
258 | 0 | return NULL; |
259 | 0 | } |
260 | | |
261 | | /* |
262 | | * check if a string is a valid URL starting with either scheme1 or scheme2 (if not NULL) |
263 | | */ |
264 | | static const char *oidc_cfg_parse_is_valid_url_scheme(apr_pool_t *pool, const char *arg, const char *scheme1, |
265 | 6.97k | const char *scheme2) { |
266 | | |
267 | 6.97k | apr_uri_t uri; |
268 | | |
269 | 6.97k | if (arg == NULL) |
270 | 0 | return apr_psprintf(pool, "input cannot be empty"); |
271 | | |
272 | 6.97k | if (apr_uri_parse(pool, arg, &uri) != APR_SUCCESS) |
273 | 304 | return apr_psprintf(pool, "'%s' cannot be parsed as a URL", arg); |
274 | | |
275 | 6.67k | if (uri.scheme == NULL) |
276 | 5.78k | return apr_psprintf(pool, "'%s' cannot be parsed as a URL (no scheme set)", arg); |
277 | | |
278 | 889 | if ((scheme1 != NULL) && (_oidc_strnatcasecmp(uri.scheme, scheme1) != 0)) { |
279 | 410 | if ((scheme2 != NULL) && (_oidc_strnatcasecmp(uri.scheme, scheme2) != 0)) { |
280 | 146 | return apr_psprintf(pool, "'%s' cannot be parsed as a \"%s\" or \"%s\" URL (scheme == %s)!", |
281 | 146 | arg, scheme1, scheme2, uri.scheme); |
282 | 264 | } else if (scheme2 == NULL) { |
283 | 86 | return apr_psprintf(pool, "'%s' cannot be parsed as a \"%s\" URL (scheme == %s)!", arg, scheme1, |
284 | 86 | uri.scheme); |
285 | 86 | } |
286 | 410 | } |
287 | | |
288 | 657 | if (uri.hostname == NULL) |
289 | 86 | return apr_psprintf(pool, "'%s' cannot be parsed as a valid URL (no hostname set, check your slashes)", |
290 | 86 | arg); |
291 | | |
292 | 571 | return NULL; |
293 | 657 | } |
294 | | |
295 | | /* |
296 | | * check if a string is a valid URL string with the specified scheme |
297 | | */ |
298 | 3.26k | const char *oidc_cfg_parse_is_valid_url(apr_pool_t *pool, const char *arg, const char *scheme) { |
299 | 3.26k | return oidc_cfg_parse_is_valid_url_scheme(pool, arg, scheme, NULL); |
300 | 3.26k | } |
301 | | |
302 | | /* |
303 | | * check if a string is a valid http or https URL |
304 | | */ |
305 | 3.70k | const char *oidc_cfg_parse_is_valid_http_url(apr_pool_t *pool, const char *arg) { |
306 | 3.70k | return oidc_cfg_parse_is_valid_url_scheme(pool, arg, "https", "http"); |
307 | 3.70k | } |
308 | | |
309 | 7 | #define OIDC_CFG_PARSE_STR_ERROR_MAX 128 |
310 | | |
311 | | /* |
312 | | * return an error retrieved from apr_strerror as a config error |
313 | | */ |
314 | | static const char *oidc_cfg_parse_io_error(apr_pool_t *pool, const char *action, const char *type, const char *name, |
315 | 7 | apr_status_t rc) { |
316 | 7 | char s_err[OIDC_CFG_PARSE_STR_ERROR_MAX]; |
317 | 7 | return apr_psprintf(pool, "cannot %s %s %s: %s", action, type, name, |
318 | 7 | apr_strerror(rc, s_err, OIDC_CFG_PARSE_STR_ERROR_MAX)); |
319 | 7 | } |
320 | | |
321 | | /* |
322 | | * parse a string into a directory name if it exists and is accessible |
323 | | */ |
324 | 0 | const char *oidc_cfg_parse_dirname(apr_pool_t *pool, const char *arg, char **value) { |
325 | 0 | apr_status_t rc = APR_SUCCESS; |
326 | 0 | apr_dir_t *dir = NULL; |
327 | 0 | if (arg == NULL) |
328 | 0 | return apr_psprintf(pool, "directory name cannot be empty"); |
329 | 0 | if ((rc = apr_dir_open(&dir, arg, pool)) != APR_SUCCESS) |
330 | 0 | return oidc_cfg_parse_io_error(pool, "access", "directory", arg, rc); |
331 | 0 | if ((rc = apr_dir_close(dir)) != APR_SUCCESS) |
332 | 0 | return oidc_cfg_parse_io_error(pool, "close", "directory", arg, rc); |
333 | 0 | *value = apr_pstrdup(pool, arg); |
334 | 0 | return NULL; |
335 | 0 | } |
336 | | |
337 | | /* |
338 | | * parse a string into a file name if it exists and is accessible |
339 | | */ |
340 | 9 | const char *oidc_cfg_parse_filename(apr_pool_t *pool, const char *arg, char **value) { |
341 | 9 | apr_file_t *fd = NULL; |
342 | 9 | apr_status_t rc = APR_SUCCESS; |
343 | 9 | if (arg == NULL) |
344 | 0 | return apr_psprintf(pool, "file name cannot be empty"); |
345 | 9 | const char *filename = ap_server_root_relative(pool, arg); |
346 | 9 | if ((rc = apr_file_open(&fd, filename, APR_FOPEN_READ, APR_OS_DEFAULT, pool)) != APR_SUCCESS) |
347 | 7 | return oidc_cfg_parse_io_error(pool, "access", "file", filename, rc); |
348 | 2 | if ((rc = apr_file_close(fd)) != APR_SUCCESS) |
349 | 0 | return oidc_cfg_parse_io_error(pool, "close", "file", filename, rc); |
350 | 2 | *value = apr_pstrdup(pool, filename); |
351 | 2 | return NULL; |
352 | 2 | } |
353 | | |
354 | | /* |
355 | | * parse a string a relative path or an absolute http/https URL |
356 | | */ |
357 | 0 | const char *oidc_cfg_parse_relative_or_absolute_url(apr_pool_t *pool, const char *arg, char **value) { |
358 | 0 | const char *rv = NULL; |
359 | 0 | apr_uri_t uri; |
360 | |
|
361 | 0 | if (arg == NULL) |
362 | 0 | return "input cannot be empty"; |
363 | | |
364 | 0 | if (arg[0] == OIDC_CHAR_FORWARD_SLASH) { |
365 | | // relative uri |
366 | 0 | if (apr_uri_parse(pool, arg, &uri) == APR_SUCCESS) |
367 | 0 | *value = apr_pstrdup(pool, arg); |
368 | 0 | else |
369 | 0 | rv = apr_psprintf(pool, "could not parse relative URI \"%s\"", arg); |
370 | 0 | } else { |
371 | | // absolute uri |
372 | 0 | rv = oidc_cfg_parse_is_valid_http_url(pool, arg); |
373 | 0 | if (rv == NULL) |
374 | 0 | *value = apr_pstrdup(pool, arg); |
375 | 0 | } |
376 | 0 | return rv; |
377 | 0 | } |
378 | | |
379 | | /* |
380 | | * check if the provided OAuth/OIDC response type is supported |
381 | | */ |
382 | 449k | const char *oidc_cfg_parse_is_valid_response_type(apr_pool_t *pool, const char *arg) { |
383 | 449k | if (oidc_proto_flow_is_supported(pool, arg) == FALSE) { |
384 | 444k | return apr_psprintf(pool, "oidc_valid_response_type: type must be one of %s", |
385 | 444k | apr_array_pstrcat(pool, oidc_proto_supported_flows(pool), OIDC_CHAR_PIPE)); |
386 | 444k | } |
387 | 5.28k | return NULL; |
388 | 449k | } |
389 | | |
390 | | /* |
391 | | * check if the provided OAuth 2.0 response mode is supported |
392 | | */ |
393 | 320 | const char *oidc_cfg_parse_is_valid_response_mode(apr_pool_t *pool, const char *arg) { |
394 | 320 | static const char *options[] = {OIDC_PROTO_RESPONSE_MODE_FRAGMENT, OIDC_PROTO_RESPONSE_MODE_QUERY, |
395 | 320 | OIDC_PROTO_RESPONSE_MODE_FORM_POST, NULL}; |
396 | 320 | return oidc_cfg_parse_is_valid_option(pool, arg, options); |
397 | 320 | } |
398 | | |
399 | | /* |
400 | | * check if the provided JWT signature algorithm is supported |
401 | | */ |
402 | 59 | const char *oidc_cfg_parse_is_valid_signed_response_alg(apr_pool_t *pool, const char *arg) { |
403 | 59 | if (oidc_jose_jws_algorithm_is_supported(pool, arg) == FALSE) { |
404 | 47 | return apr_psprintf(pool, "unsupported/invalid signing algorithm '%s'; must be one of [%s]", arg, |
405 | 47 | apr_array_pstrcat(pool, oidc_jose_jws_supported_algorithms(pool), OIDC_CHAR_PIPE)); |
406 | 47 | } |
407 | 12 | return NULL; |
408 | 59 | } |
409 | | |
410 | | /* |
411 | | * check if the provided JWT content key encryption algorithm is supported |
412 | | */ |
413 | 54 | const char *oidc_cfg_parse_is_valid_encrypted_response_alg(apr_pool_t *pool, const char *arg) { |
414 | 54 | if (oidc_jose_jwe_algorithm_is_supported(pool, arg) == FALSE) { |
415 | 48 | return apr_psprintf(pool, "unsupported/invalid encryption algorithm '%s'; must be one of [%s]", arg, |
416 | 48 | apr_array_pstrcat(pool, oidc_jose_jwe_supported_algorithms(pool), OIDC_CHAR_PIPE)); |
417 | 48 | } |
418 | 6 | return NULL; |
419 | 54 | } |
420 | | |
421 | | /* |
422 | | * check if the provided JWT encryption cipher is supported |
423 | | */ |
424 | 98 | const char *oidc_cfg_parse_is_valid_encrypted_response_enc(apr_pool_t *pool, const char *arg) { |
425 | 98 | if (oidc_jose_jwe_encryption_is_supported(pool, arg) == FALSE) { |
426 | 91 | return apr_psprintf(pool, "unsupported/invalid encryption type '%s'; must be one of [%s]", arg, |
427 | 91 | apr_array_pstrcat(pool, oidc_jose_jwe_supported_encryptions(pool), OIDC_CHAR_PIPE)); |
428 | 91 | } |
429 | 7 | return NULL; |
430 | 98 | } |
431 | | |
432 | | /* |
433 | | * parse a base64url encoded binary value from the provided string |
434 | | */ |
435 | 130 | static char *oidc_cfg_parse_base64url(apr_pool_t *pool, const char *input, char **output, int *output_len) { |
436 | 130 | *output_len = oidc_util_base64url_decode(pool, output, input); |
437 | 130 | if (*output_len <= 0) |
438 | 79 | return apr_psprintf(pool, "base64url-decoding of \"%s\" failed", input); |
439 | 51 | return NULL; |
440 | 130 | } |
441 | | |
442 | | /* |
443 | | * parse a hexadecimal encoded binary value from the provided string |
444 | | */ |
445 | 62 | static char *oidc_cfg_parse_hex(apr_pool_t *pool, const char *input, char **output, int *output_len) { |
446 | 62 | size_t input_len = _oidc_strlen(input); |
447 | 62 | if ((input_len % 2) != 0) |
448 | 3 | return apr_psprintf(pool, "hex-decoding failed: input length (%" APR_SIZE_T_FMT ") is not even", |
449 | 3 | input_len); |
450 | 59 | *output_len = (int)(input_len / 2); |
451 | 59 | const char *pos = input; |
452 | 59 | unsigned char *val = apr_pcalloc(pool, *output_len); |
453 | 13.9k | for (size_t count = 0; count < (*output_len) / sizeof(unsigned char); count++) { |
454 | 13.9k | if (sscanf(pos, "%2hhx", &val[count]) != 1) |
455 | 47 | return apr_psprintf(pool, "hex-decoding failed at offset %" APR_SIZE_T_FMT ": non-hex input", |
456 | 47 | count * 2); |
457 | 13.9k | pos += 2; |
458 | 13.9k | } |
459 | 12 | *output = (char *)val; |
460 | 12 | return NULL; |
461 | 59 | } |
462 | | |
463 | 1.13k | #define OIDC_KEY_ENCODING_BASE64 "b64" |
464 | 1.11k | #define OIDC_KEY_ENCODING_BASE64_URL "b64url" |
465 | 982 | #define OIDC_KEY_ENCODING_HEX "hex" |
466 | 920 | #define OIDC_KEY_ENCODING_PLAIN "plain" |
467 | | |
468 | | /* |
469 | | * parse a key value based on the provided encoding: b64|b64url|hex|plain |
470 | | */ |
471 | | static const char *oidc_cfg_parse_key_value(apr_pool_t *pool, const char *enc, const char *input, char **key, |
472 | 566 | int *key_len) { |
473 | 566 | static const char *options[] = {OIDC_KEY_ENCODING_BASE64, OIDC_KEY_ENCODING_BASE64_URL, OIDC_KEY_ENCODING_HEX, |
474 | 566 | OIDC_KEY_ENCODING_PLAIN, NULL}; |
475 | 566 | if (_oidc_strcmp(enc, OIDC_KEY_ENCODING_BASE64) == 0) |
476 | 20 | return oidc_util_base64_decode(pool, input, key, key_len); |
477 | 546 | if (_oidc_strcmp(enc, OIDC_KEY_ENCODING_BASE64_URL) == 0) |
478 | 130 | return oidc_cfg_parse_base64url(pool, input, key, key_len); |
479 | 416 | if (_oidc_strcmp(enc, OIDC_KEY_ENCODING_HEX) == 0) |
480 | 62 | return oidc_cfg_parse_hex(pool, input, key, key_len); |
481 | 354 | if (_oidc_strcmp(enc, OIDC_KEY_ENCODING_PLAIN) == 0) { |
482 | 3 | *key = apr_pstrdup(pool, input); |
483 | 3 | *key_len = (int)_oidc_strlen(*key); |
484 | 3 | return NULL; |
485 | 3 | } |
486 | | // NB: when we get here we'll return an error displaying the valid options |
487 | 351 | return oidc_cfg_parse_is_valid_option(pool, enc, options); |
488 | 354 | } |
489 | | |
490 | 28.1k | #define OIDC_KEY_TUPLE_SEPARATOR "#" |
491 | 4.60k | #define OIDC_KEY_SIG_PREFIX OIDC_JOSE_JWK_SIG_STR ":" |
492 | 4.51k | #define OIDC_KEY_ENC_PREFIX OIDC_JOSE_JWK_ENC_STR ":" |
493 | 3.04k | #define OIDC_KEY_ALG_SEPARATOR OIDC_STR_AT |
494 | 990 | #define OIDC_KEY_ALG_LIST_SEPARATOR "+" |
495 | | |
496 | | /* |
497 | | * the JOSE "alg" names the key tuple prefix may carry (RFC 7518 and registered successors), |
498 | | * matched in full: a kid or filename segment that merely resembles one ("RSbank", "key") must |
499 | | * not pass, which rules out oidc_alg2kty()'s two-character matching here |
500 | | */ |
501 | 976 | static apr_byte_t oidc_cfg_parse_key_alg_is_known(const char *alg) { |
502 | 976 | static const char *known[] = {"RS256", "RS384", "RS512", "PS256", "PS384", |
503 | 976 | "PS512", "HS256", "HS384", "HS512", "ES256", |
504 | 976 | "ES384", "ES512", "ES256K", "EdDSA", "dir", |
505 | 976 | "RSA1_5", "RSA-OAEP", "RSA-OAEP-256", "RSA-OAEP-384", "RSA-OAEP-512", |
506 | 976 | "A128KW", "A192KW", "A256KW", "A128GCMKW", "A192GCMKW", |
507 | 976 | "A256GCMKW", "ECDH-ES", "PBES2-HS256", "PBES2-HS384", "PBES2-HS512"}; |
508 | 16.9k | for (unsigned int i = 0; i < sizeof(known) / sizeof(known[0]); i++) |
509 | 16.6k | if (_oidc_strcmp(alg, known[i]) == 0) |
510 | 708 | return TRUE; |
511 | 268 | return FALSE; |
512 | 976 | } |
513 | | |
514 | | /* |
515 | | * Treat text before @ as an algorithm list only when every token is known and no kid/path |
516 | | * separator occurs; otherwise @ remains part of the key ID or filename. |
517 | | */ |
518 | 3.03k | static const char *oidc_cfg_parse_key_alg_prefix(apr_pool_t *pool, const char *tuple, char **alg) { |
519 | 3.03k | const char *at = _oidc_strstr(tuple, OIDC_KEY_ALG_SEPARATOR); |
520 | 3.03k | char *last = NULL; |
521 | 3.03k | int n = 0; |
522 | 3.03k | if ((at == NULL) || (at == tuple)) |
523 | 2.66k | return tuple; |
524 | 21.7k | for (const char *c = tuple; c < at; c++) |
525 | 21.4k | if ((*c == OIDC_KEY_TUPLE_SEPARATOR[0]) || (*c == OIDC_CHAR_FORWARD_SLASH)) |
526 | 88 | return tuple; |
527 | 282 | char *candidate = apr_pstrndup(pool, tuple, at - tuple); |
528 | 282 | for (const char *tok = apr_strtok(apr_pstrdup(pool, candidate), OIDC_KEY_ALG_LIST_SEPARATOR, &last); |
529 | 990 | tok != NULL; tok = apr_strtok(NULL, OIDC_KEY_ALG_LIST_SEPARATOR, &last), n++) |
530 | 976 | if (oidc_cfg_parse_key_alg_is_known(tok) == FALSE) |
531 | 268 | return tuple; |
532 | 14 | if (n == 0) |
533 | 2 | return tuple; |
534 | 12 | *alg = candidate; |
535 | 12 | return at + _oidc_strlen(OIDC_KEY_ALG_SEPARATOR); |
536 | 14 | } |
537 | | |
538 | | /* |
539 | | * parse a [<use>:][<alg>[+<alg>...]@][<key-identifier>#]<key> tuple (or, when format is TRUE, a |
540 | | * [<use>:]<encoding>#<key-identifier>#<key> tuple); the optional "<alg>[+<alg>...]@" list is only |
541 | | * recognized when a non-NULL alg out-parameter is passed |
542 | | */ |
543 | | const char *oidc_cfg_parse_key_record(apr_pool_t *pool, const char *tuple, char **kid, char **key, int *key_len, |
544 | 6.15k | char **use, char **alg, oidc_key_record_format_t format) { |
545 | 6.15k | const char *rv = NULL; |
546 | 6.15k | char *s = NULL; |
547 | 6.15k | char *p = NULL; |
548 | 6.15k | char *q = NULL; |
549 | 6.15k | const char *enc = NULL; |
550 | | |
551 | 6.15k | if ((tuple == NULL) || (_oidc_strcmp(tuple, "") == 0)) |
552 | 88 | return "tuple value not set"; |
553 | | |
554 | 6.06k | if (use) { |
555 | 4.54k | if (_oidc_strstr(tuple, OIDC_KEY_SIG_PREFIX) == tuple) { |
556 | 54 | *use = OIDC_JOSE_JWK_SIG_STR; |
557 | 54 | tuple += _oidc_strlen(OIDC_KEY_SIG_PREFIX); |
558 | 4.49k | } else if (_oidc_strstr(tuple, OIDC_KEY_ENC_PREFIX) == tuple) { |
559 | 24 | *use = OIDC_JOSE_JWK_ENC_STR; |
560 | 24 | tuple += _oidc_strlen(OIDC_KEY_ENC_PREFIX); |
561 | 24 | } |
562 | 4.54k | } |
563 | | |
564 | | /* optional "<alg>[+<alg>...]@" list preceding the "[<kid>#]<key>" record */ |
565 | 6.06k | if (alg) |
566 | 3.03k | tuple = oidc_cfg_parse_key_alg_prefix(pool, tuple, alg); |
567 | | |
568 | 6.06k | s = apr_pstrdup(pool, tuple); |
569 | 6.06k | p = _oidc_strstr(s, OIDC_KEY_TUPLE_SEPARATOR); |
570 | 6.06k | if (p && format) |
571 | 590 | q = _oidc_strstr(p + 1, OIDC_KEY_TUPLE_SEPARATOR); |
572 | | |
573 | 6.06k | if (p) { |
574 | 1.18k | if (q) { |
575 | 566 | *p = '\0'; |
576 | 566 | *q = '\0'; |
577 | 566 | enc = s; |
578 | 566 | p++; |
579 | 566 | if (p != q) |
580 | 68 | *kid = apr_pstrdup(pool, p); |
581 | 566 | rv = oidc_cfg_parse_key_value(pool, enc, q + 1, key, key_len); |
582 | 614 | } else { |
583 | 614 | *p = '\0'; |
584 | 614 | *kid = s; |
585 | 614 | *key = p + 1; |
586 | 614 | *key_len = (int)_oidc_strlen(*key); |
587 | 614 | } |
588 | 4.88k | } else { |
589 | 4.88k | *kid = NULL; |
590 | 4.88k | *key = s; |
591 | 4.88k | *key_len = (int)_oidc_strlen(*key); |
592 | 4.88k | } |
593 | | |
594 | 6.06k | return rv; |
595 | 6.15k | } |
596 | | |
597 | 0 | #define OIDC_ON_ERROR_502_STR "502_on_error" |
598 | 0 | #define OIDC_ON_ERROR_LOGOUT_STR "logout_on_error" |
599 | 0 | #define OIDC_ON_ERROR_AUTH_STR "authenticate_on_error" |
600 | | |
601 | | /* |
602 | | * parse an "on access token refresh error" value from the provided strings |
603 | | */ |
604 | | const char *oidc_cfg_parse_action_on_error_refresh_as(apr_pool_t *pool, const char *arg, |
605 | 0 | oidc_on_error_action_t *action) { |
606 | 0 | static const oidc_cfg_option_t options[] = {{OIDC_ON_ERROR_502, OIDC_ON_ERROR_502_STR}, |
607 | 0 | {OIDC_ON_ERROR_LOGOUT, OIDC_ON_ERROR_LOGOUT_STR}, |
608 | 0 | {OIDC_ON_ERROR_AUTH, OIDC_ON_ERROR_AUTH_STR}}; |
609 | 0 | return oidc_cfg_parse_option(pool, options, OIDC_CFG_OPTIONS_SIZE(options), arg, (int *)action); |
610 | 0 | } |
611 | | |
612 | | /* |
613 | | * set a string value in the server config with exec support |
614 | | */ |
615 | 60 | const char *oidc_cfg_parse_passphrase(apr_pool_t *pool, const char *arg, char **passphrase) { |
616 | 60 | char **argv = NULL; |
617 | 60 | const char *result = NULL; |
618 | 60 | int arglen = (int)_oidc_strlen(arg); |
619 | | /* Based on code from mod_session_crypto. */ |
620 | 60 | if (arglen > 5 && _oidc_strncmp(arg, "exec:", 5) == 0) { |
621 | 3 | if (apr_tokenize_to_argv(arg + 5, &argv, pool) != APR_SUCCESS) { |
622 | 0 | return apr_pstrcat(pool, "Unable to parse exec arguments from ", arg + 5, NULL); |
623 | 0 | } |
624 | 3 | argv[0] = ap_server_root_relative(pool, argv[0]); |
625 | 3 | if (!argv[0]) { |
626 | 1 | return apr_pstrcat(pool, "Invalid exec location:", arg + 5, NULL); |
627 | 1 | } |
628 | 2 | result = ap_get_exec_line(pool, argv[0], (const char *const *)argv); |
629 | 2 | if (!result) { |
630 | 2 | return apr_pstrcat(pool, "Unable to get passphrase from exec of ", arg + 5, NULL); |
631 | 2 | } |
632 | 0 | if (_oidc_strlen(result) == 0) |
633 | 0 | return apr_pstrdup(pool, "the output of the passphrase generation command is empty " |
634 | 0 | "(perhaps you need to pass it to bash -c \"<cmd>\"?)"); |
635 | 0 | *passphrase = apr_pstrdup(pool, result); |
636 | 57 | } else { |
637 | 57 | *passphrase = apr_pstrdup(pool, arg); |
638 | 57 | } |
639 | 57 | return NULL; |
640 | 60 | } |
641 | | |
642 | | /* |
643 | | * parse the PEM key in file "fname" into a JWK with key identifier "kid" (auto-derived when NULL) |
644 | | */ |
645 | | static const char *oidc_cfg_parse_pem_key(apr_pool_t *pool, apr_byte_t is_private, const char *kid, const char *fname, |
646 | 0 | oidc_jwk_t **jwk) { |
647 | 0 | oidc_jose_error_t err; |
648 | 0 | apr_byte_t rv = is_private ? oidc_jwk_parse_pem_private_key(pool, kid, fname, jwk, &err) |
649 | 0 | : oidc_jwk_parse_pem_public_key(pool, kid, fname, jwk, &err); |
650 | 0 | if (rv == FALSE) |
651 | 0 | return apr_psprintf(pool, "oidc_jwk_parse_pem_%s_key failed for (kid=%s) \"%s\": %s", |
652 | 0 | is_private ? "private" : "public", kid ? kid : "", fname, oidc_jose_e2s(pool, err)); |
653 | 0 | return NULL; |
654 | 0 | } |
655 | | |
656 | | /* |
657 | | * Parse [<use>:][<alg>[+<alg>...]@][<kid>#]<filename>. Multiple algorithms publish the key once |
658 | | * per algorithm with distinct derived key IDs; no algorithm produces one key. |
659 | | */ |
660 | | static const char *oidc_cfg_parse_key_files(apr_pool_t *pool, const char *arg, apr_array_header_t **keys, |
661 | 0 | apr_byte_t is_private) { |
662 | 0 | oidc_jwk_t *jwk = NULL; |
663 | 0 | char *use = NULL; |
664 | 0 | char *alg = NULL; |
665 | 0 | char *kid = NULL; |
666 | 0 | char *name = NULL; |
667 | 0 | char *fname = NULL; |
668 | 0 | int fname_len; |
669 | 0 | char *last = NULL; |
670 | |
|
671 | 0 | const char *rv = |
672 | 0 | oidc_cfg_parse_key_record(pool, arg, &kid, &name, &fname_len, &use, &alg, OIDC_KEY_RECORD_PAIR); |
673 | 0 | if (rv != NULL) |
674 | 0 | return rv; |
675 | | |
676 | 0 | rv = oidc_cfg_parse_filename(pool, name, &fname); |
677 | 0 | if (rv != NULL) |
678 | 0 | return rv; |
679 | | |
680 | | /* split the optional "+"-separated algorithm list; an empty list yields a single pass with alg == NULL */ |
681 | 0 | apr_array_header_t *algs = apr_array_make(pool, 2, sizeof(char *)); |
682 | 0 | for (char *tok = alg ? apr_strtok(alg, OIDC_KEY_ALG_LIST_SEPARATOR, &last) : NULL; tok != NULL; |
683 | 0 | tok = apr_strtok(NULL, OIDC_KEY_ALG_LIST_SEPARATOR, &last)) |
684 | 0 | APR_ARRAY_PUSH(algs, char *) = tok; |
685 | 0 | if (algs->nelts == 0) |
686 | 0 | APR_ARRAY_PUSH(algs, char *) = NULL; |
687 | |
|
688 | 0 | apr_byte_t multi = (algs->nelts > 1); |
689 | | |
690 | | /* when duplicating a key without an explicit kid, derive the shared base kid from the key material once */ |
691 | 0 | const char *base_kid = kid; |
692 | 0 | if ((base_kid == NULL) && (multi == TRUE)) { |
693 | 0 | rv = oidc_cfg_parse_pem_key(pool, is_private, NULL, fname, &jwk); |
694 | 0 | if (rv != NULL) |
695 | 0 | return rv; |
696 | | /* keep a pool copy of the derived kid; the probe JWK itself is re-parsed per algorithm below */ |
697 | 0 | base_kid = apr_pstrdup(pool, jwk->kid); |
698 | 0 | oidc_jwk_destroy(jwk); |
699 | 0 | jwk = NULL; |
700 | 0 | } |
701 | | |
702 | 0 | if (*keys == NULL) |
703 | 0 | *keys = apr_array_make(pool, 4, sizeof(const oidc_jwk_t *)); |
704 | |
|
705 | 0 | for (int i = 0; i < algs->nelts; i++) { |
706 | 0 | char *a = APR_ARRAY_IDX(algs, i, char *); |
707 | | /* keep kids distinct across the per-alg duplicates; a single key keeps its (explicit or derived) kid */ |
708 | 0 | const char *this_kid = |
709 | 0 | ((base_kid != NULL) && (multi == TRUE)) ? apr_psprintf(pool, "%s-%s", base_kid, a) : base_kid; |
710 | |
|
711 | 0 | rv = oidc_cfg_parse_pem_key(pool, is_private, this_kid, fname, &jwk); |
712 | 0 | if (rv != NULL) |
713 | 0 | return rv; |
714 | | |
715 | 0 | if (use) |
716 | 0 | jwk->use = apr_pstrdup(pool, use); |
717 | 0 | if (a != NULL) { |
718 | 0 | if (oidc_alg2kty(a) != jwk->kty) { |
719 | 0 | const char *msg = apr_psprintf( |
720 | 0 | pool, "algorithm \"%s\" is not compatible with the key type of \"%s\"", a, fname); |
721 | 0 | oidc_jwk_destroy(jwk); |
722 | 0 | return msg; |
723 | 0 | } |
724 | 0 | jwk->alg = apr_pstrdup(pool, a); |
725 | 0 | } |
726 | 0 | APR_ARRAY_PUSH(*keys, const oidc_jwk_t *) = jwk; |
727 | 0 | } |
728 | | |
729 | 0 | return NULL; |
730 | 0 | } |
731 | | |
732 | | /* |
733 | | * add a public key from an X.509 file to our list of JWKs with public keys |
734 | | */ |
735 | 0 | const char *oidc_cfg_parse_public_key_files(apr_pool_t *pool, const char *arg, apr_array_header_t **keys) { |
736 | 0 | return oidc_cfg_parse_key_files(pool, arg, keys, FALSE); |
737 | 0 | } |
738 | | |
739 | | /* |
740 | | * add a private key from an RSA/EC private key file to our list of JWKs with private keys |
741 | | */ |
742 | 0 | const char *oidc_cfg_parse_private_key_files(apr_pool_t *pool, const char *arg, apr_array_header_t **keys) { |
743 | 0 | return oidc_cfg_parse_key_files(pool, arg, keys, TRUE); |
744 | 0 | } |
745 | | |
746 | | /* |
747 | | * parse a format of 3 provided config values into a remote_user_claim struct |
748 | | */ |
749 | | const char *oidc_parse_remote_user_claim(apr_pool_t *pool, const char *v1, const char *v2, const char *v3, |
750 | 0 | oidc_remote_user_claim_t *remote_user_claim) { |
751 | 0 | remote_user_claim->claim_name = v1; |
752 | 0 | if (v2) |
753 | 0 | remote_user_claim->reg_exp = v2; |
754 | 0 | if (v3) |
755 | 0 | remote_user_claim->replace = v3; |
756 | 0 | return NULL; |
757 | 0 | } |
758 | | |
759 | | /* |
760 | | * parse a format of 3 provided config values into a http_timeout struct |
761 | | */ |
762 | | const char *oidc_cfg_parse_http_timeout(apr_pool_t *pool, const char *arg1, const char *arg2, const char *arg3, |
763 | 0 | oidc_http_timeout_t *http_timeout) { |
764 | 0 | const char *rv = NULL; |
765 | 0 | const char *s = NULL; |
766 | 0 | char *p = NULL; |
767 | | /* validate strictly rather than defaulting a typo silently to 0 (= an infinite curl timeout) */ |
768 | 0 | if (arg1) { |
769 | 0 | rv = oidc_cfg_parse_int(pool, arg1, &http_timeout->request_timeout); |
770 | 0 | if (rv != NULL) |
771 | 0 | return rv; |
772 | 0 | } |
773 | 0 | if (arg2) { |
774 | 0 | rv = oidc_cfg_parse_int(pool, arg2, &http_timeout->connect_timeout); |
775 | 0 | if (rv != NULL) |
776 | 0 | return rv; |
777 | 0 | } |
778 | 0 | if (arg3) { |
779 | 0 | s = apr_pstrdup(pool, arg3); |
780 | 0 | p = _oidc_strstr(s, OIDC_STR_COLON); |
781 | 0 | if (p) { |
782 | 0 | *p = '\0'; |
783 | 0 | p++; |
784 | 0 | rv = oidc_cfg_parse_int(pool, p, &http_timeout->retry_interval); |
785 | 0 | if (rv != NULL) |
786 | 0 | return rv; |
787 | 0 | } |
788 | 0 | rv = oidc_cfg_parse_int(pool, s, &http_timeout->retries); |
789 | 0 | if (rv != NULL) |
790 | 0 | return rv; |
791 | 0 | } |
792 | 0 | return NULL; |
793 | 0 | } |