Coverage Report

Created: 2026-08-31 07:13

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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
}