Coverage Report

Created: 2026-11-12 07:05

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/mod_auth_openidc/src/util/util.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
 * Copyright (C) 2013-2017 Ping Identity Corporation
23
 * All rights reserved.
24
 *
25
 * DISCLAIMER OF WARRANTIES:
26
 *
27
 * THE SOFTWARE PROVIDED HEREUNDER IS PROVIDED ON AN "AS IS" BASIS, WITHOUT
28
 * ANY WARRANTIES OR REPRESENTATIONS EXPRESS, IMPLIED OR STATUTORY; INCLUDING,
29
 * WITHOUT LIMITATION, WARRANTIES OF QUALITY, PERFORMANCE, NONINFRINGEMENT,
30
 * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.  NOR ARE THERE ANY
31
 * WARRANTIES CREATED BY A COURSE OR DEALING, COURSE OF PERFORMANCE OR TRADE
32
 * USAGE.  FURTHERMORE, THERE ARE NO WARRANTIES THAT THE SOFTWARE WILL MEET
33
 * YOUR NEEDS OR BE FREE FROM ERRORS, OR THAT THE OPERATION OF THE SOFTWARE
34
 * WILL BE UNINTERRUPTED.  IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR
35
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
36
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES HOWEVER CAUSED AND ON ANY THEORY OF
37
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
38
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
39
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
40
 *
41
 * @Author: Hans Zandbelt - hans.zandbelt@openidc.com
42
 */
43
44
#include "util/util.h"
45
#include "util/request_state.h"
46
#include "util/util_cfg.h"
47
48
#include <apr_lib.h>
49
50
#include <http_protocol.h>
51
52
#include <openssl/crypto.h>   /* CRYPTO_memcmp() in oidc_util_strcmp_const_time() */
53
#include <openssl/opensslv.h> /* OPENSSL_VERSION_STR / OPENSSL_VERSION_TEXT in oidc_util_openssl_version() */
54
55
/*
56
 * convert a character to an ENVIRONMENT-variable-safe variant
57
 */
58
16.8k
static int oidc_util_char_to_env(int c) {
59
16.8k
  return apr_isalnum(c) ? apr_toupper(c) : '_';
60
16.8k
}
61
62
/* Compare strings after oidc_char_to_env conversion; a negative len compares the full strings. */
63
3.61k
int oidc_util_strnenvcmp(const char *a, const char *b, int len) {
64
3.61k
  int d = 0;
65
3.61k
  int i = 0;
66
9.62k
  while (1) {
67
    /* If len < 0 then we don't stop based on length */
68
9.62k
    if (len >= 0 && i >= len)
69
1.20k
      return 0;
70
71
    /* If we're at the end of both strings, they're equal */
72
8.42k
    if (!*a && !*b)
73
0
      return 0;
74
75
    /* If the second string is shorter, pick it: */
76
8.42k
    if (*a && !*b)
77
0
      return 1;
78
79
    /* If the first string is shorter, pick it: */
80
8.42k
    if (!*a && *b)
81
0
      return -1;
82
83
    /* Normalize the characters as for conversion to an
84
     * environment variable. */
85
8.42k
    d = oidc_util_char_to_env(*a) - oidc_util_char_to_env(*b);
86
8.42k
    if (d)
87
2.41k
      return d;
88
89
6.01k
    a++;
90
6.01k
    b++;
91
6.01k
    i++;
92
6.01k
  }
93
3.61k
}
94
95
/*
96
 * find a needle (s2) in a haystack (s1) using case-insensitive string compare
97
 */
98
19.8k
const char *oidc_util_strcasestr(const char *s1, const char *s2) {
99
19.8k
  const char *s = s1;
100
19.8k
  const char *p = s2;
101
19.8k
  if ((s == NULL) || (p == NULL))
102
2.78k
    return NULL;
103
32.4M
  do {
104
32.4M
    if (!*p)
105
334
      return s1;
106
32.4M
    if ((*p == *s) || (tolower(*p) == tolower(*s))) {
107
564k
      ++p;
108
564k
      ++s;
109
31.8M
    } else {
110
31.8M
      p = s2;
111
31.8M
      if (!*s)
112
16.7k
        return NULL;
113
31.8M
      s = ++s1;
114
31.8M
    }
115
32.4M
  } while (1);
116
17.0k
}
117
118
/*
119
 * sends data to the user agent
120
 */
121
int oidc_util_http_send(request_rec *r, const char *data, size_t data_len, const char *content_type,
122
17
      int success_rvalue) {
123
17
  ap_set_content_type(r, content_type);
124
17
  apr_bucket_brigade *bb = apr_brigade_create(r->pool, r->connection->bucket_alloc);
125
17
  apr_bucket *b = apr_bucket_transient_create(data, data_len, r->connection->bucket_alloc);
126
17
  APR_BRIGADE_INSERT_TAIL(bb, b);
127
17
  b = apr_bucket_eos_create(r->connection->bucket_alloc);
128
17
  APR_BRIGADE_INSERT_TAIL(bb, b);
129
17
  int rc = ap_pass_brigade(r->output_filters, bb);
130
17
  if (rc != APR_SUCCESS) {
131
0
    oidc_error(r,
132
0
         "ap_pass_brigade returned an error: %d; if you're using this module combined with "
133
0
         "mod_deflate try make an exception for the " OIDCRedirectURI
134
0
         " e.g. using SetEnvIf Request_URI <url> no-gzip",
135
0
         rc);
136
0
    return HTTP_INTERNAL_SERVER_ERROR;
137
0
  }
138
139
17
  if ((success_rvalue == OK) && (r->user == NULL)) {
140
    /*
141
     * satisfy Apache 2.4 mod_authz_core:
142
     * prevent it to return HTTP 500 after sending content
143
     */
144
0
    r->user = "";
145
0
  }
146
147
17
  return success_rvalue;
148
17
}
149
150
/*
151
 * called from the authentication handler:
152
 * prepares data to be sent to the user agent in the content handler
153
 */
154
10
int oidc_util_http_content_prep(request_rec *r, const char *data, size_t data_len, const char *content_type) {
155
  /* store data, data_len and content-type in the request state, possibly deleting leftovers from a previous
156
   * request */
157
10
  oidc_request_state_set(r, "data", NULL);
158
10
  if (data)
159
10
    oidc_request_state_set(r, "data", data);
160
10
  oidc_request_state_set(r, "data_len", NULL);
161
10
  if (data_len)
162
10
    oidc_request_state_set(r, "data_len", apr_psprintf(r->pool, "%d", (int)data_len));
163
10
  oidc_request_state_set(r, "content_type", NULL);
164
10
  if (content_type)
165
10
    oidc_request_state_set(r, "content_type", content_type);
166
  /* signal that there's HTTP data to be sent in the content handler */
167
10
  oidc_request_state_set(r, OIDC_REQUEST_STATE_KEY_HTTP, "");
168
  /* make sure that we pass the authorization phase since we have to return data from the content handler */
169
10
  r->user = "";
170
  /* return OK to make sure that we continue in the content handler */
171
10
  return OK;
172
10
}
173
174
/*
175
 * called from the content handler:
176
 * sends data that was prepared in oidc_util_http_content_prep to the user agent
177
 */
178
3
int oidc_util_http_content_send(request_rec *r) {
179
3
  const char *data = oidc_request_state_get(r, "data");
180
3
  int data_len = _oidc_str_to_int(oidc_request_state_get(r, "data_len"), 0);
181
3
  const char *content_type = oidc_request_state_get(r, "content_type");
182
3
  return oidc_util_http_send(r, data, data_len, content_type, OK);
183
3
}
184
185
/* the maximum size of data that we accept in a single POST value: 1MB */
186
6.78k
#define OIDC_MAX_POST_DATA_LEN (1024 * 1024)
187
188
/*
189
 * read all bytes from the HTTP request
190
 */
191
6.78k
static apr_byte_t oidc_util_read(request_rec *r, char **rbuf) {
192
6.78k
  apr_size_t bytes_read;
193
6.78k
  apr_size_t bytes_left;
194
6.78k
  apr_size_t len;
195
6.78k
  long read_length;
196
197
6.78k
  if (ap_setup_client_block(r, REQUEST_CHUNKED_DECHUNK) != OK)
198
0
    return FALSE;
199
200
6.78k
  len = ap_should_client_block(r) ? r->remaining : 0;
201
202
6.78k
  if (len > OIDC_MAX_POST_DATA_LEN) {
203
20
    oidc_error(r, "POST parameter value is too large: %lu bytes (max=%d)", (unsigned long)len,
204
20
         OIDC_MAX_POST_DATA_LEN);
205
20
    return FALSE;
206
20
  }
207
208
6.76k
  *rbuf = (char *)apr_palloc(r->pool, len + 1);
209
6.76k
  if (*rbuf == NULL) {
210
0
    oidc_error(r, "could not allocate memory for %lu bytes of POST data.", (unsigned long)len);
211
0
    return FALSE;
212
0
  }
213
6.76k
  (*rbuf)[len] = '\0';
214
215
6.76k
  bytes_read = 0;
216
6.76k
  bytes_left = len;
217
12.7k
  while (bytes_left > 0) {
218
6.01k
    read_length = ap_get_client_block(r, &(*rbuf)[bytes_read], bytes_left);
219
6.01k
    if (read_length == 0) {
220
0
      (*rbuf)[bytes_read] = '\0';
221
0
      break;
222
6.01k
    } else if (read_length < 0) {
223
0
      oidc_error(r, "failed to read POST data from client");
224
0
      return FALSE;
225
0
    }
226
6.01k
    bytes_read += read_length;
227
6.01k
    bytes_left -= read_length;
228
6.01k
  }
229
230
6.76k
  return TRUE;
231
6.76k
}
232
233
/*
234
 * TRUE when key is one of the NUL-terminated no_repeat parameter names (a NULL list matches nothing)
235
 */
236
50.1k
static apr_byte_t oidc_util_param_must_not_repeat(const char *const *no_repeat, const char *key) {
237
50.1k
  if (no_repeat == NULL)
238
24.2k
    return FALSE;
239
69.4k
  for (const char *const *n = no_repeat; *n != NULL; n++)
240
52.3k
    if (_oidc_strcmp(*n, key) == 0)
241
8.80k
      return TRUE;
242
17.0k
  return FALSE;
243
25.8k
}
244
245
/* Parse form parameters, optionally rejecting repeated names from no_repeat. */
246
static apr_byte_t oidc_util_read_form_encoded_params_impl(request_rec *r, apr_table_t *table, const char *data,
247
17.2k
                const char *const *no_repeat) {
248
17.2k
  const char *key = NULL;
249
17.2k
  const char *val = NULL;
250
17.2k
  const char *p = data;
251
252
67.3k
  while (p && (*p)) {
253
50.1k
    val = ap_getword(r->pool, &p, OIDC_CHAR_AMP);
254
50.1k
    if (val == NULL)
255
0
      break;
256
50.1k
    key = ap_getword(r->pool, &val, OIDC_CHAR_EQUAL);
257
50.1k
    key = oidc_http_url_decode(r, key);
258
50.1k
    val = oidc_http_url_decode(r, val);
259
50.1k
    if (oidc_util_param_must_not_repeat(no_repeat, key) && (apr_table_get(table, key) != NULL)) {
260
47
      oidc_error(r, "duplicate \"%s\" parameter is not allowed", key);
261
47
      return FALSE;
262
47
    }
263
    /* this parses the front-channel authorization response and the back-channel logout
264
     * request, so the values include the authorization code and bare tokens */
265
50.0k
    oidc_debug(r, "read: %s=%s", key, oidc_http_param_is_sensitive(key) ? "***" : val);
266
50.0k
    apr_table_set(table, key, val);
267
50.0k
  }
268
269
17.2k
  oidc_debug(r, "parsed: %d bytes into %d elements", data ? (int)_oidc_strlen(data) : 0,
270
17.2k
       apr_table_elts(table)->nelts);
271
272
17.2k
  return TRUE;
273
17.2k
}
274
275
6.77k
apr_byte_t oidc_util_read_form_encoded_params(request_rec *r, apr_table_t *table, const char *data) {
276
6.77k
  return oidc_util_read_form_encoded_params_impl(r, table, data, NULL);
277
6.77k
}
278
279
apr_byte_t oidc_util_read_form_encoded_params_reject_dup(request_rec *r, apr_table_t *table, const char *data,
280
3.72k
               const char *const *no_repeat) {
281
3.72k
  return oidc_util_read_form_encoded_params_impl(r, table, data, no_repeat);
282
3.72k
}
283
284
static void oidc_util_userdata_set_post_param(request_rec *r, const char *post_param_name,
285
1.05k
                const char *post_param_value) {
286
1.05k
  apr_table_t *userdata_post_params = NULL;
287
1.05k
  apr_pool_userdata_get((void **)&userdata_post_params, OIDC_USERDATA_POST_PARAMS_KEY, r->pool);
288
1.05k
  if (userdata_post_params == NULL)
289
467
    userdata_post_params = apr_table_make(r->pool, 1);
290
1.05k
  apr_table_set(userdata_post_params, post_param_name, post_param_value);
291
1.05k
  apr_pool_userdata_set(userdata_post_params, OIDC_USERDATA_POST_PARAMS_KEY, NULL, r->pool);
292
1.05k
}
293
294
/*
295
 * read the POST parameters in to a table
296
 */
297
static apr_byte_t oidc_util_read_post_params_impl(request_rec *r, apr_table_t *table, apr_byte_t propagate,
298
6.78k
              const char *strip_param_name, const char *const *no_repeat) {
299
6.78k
  apr_byte_t rc = FALSE;
300
6.78k
  char *data = NULL;
301
6.78k
  const apr_array_header_t *arr = NULL;
302
6.78k
  const apr_table_entry_t *elts = NULL;
303
6.78k
  const char *content_type = NULL;
304
305
6.78k
  content_type = oidc_http_hdr_in_content_type_get(r);
306
6.78k
  if ((r->method_number != M_POST) || (content_type == NULL) ||
307
6.78k
      (_oidc_strstr(content_type, OIDC_HTTP_CONTENT_TYPE_FORM_ENCODED) != content_type)) {
308
1
    oidc_debug(r, "required content-type %s not found", OIDC_HTTP_CONTENT_TYPE_FORM_ENCODED);
309
1
    goto end;
310
1
  }
311
312
6.78k
  if (oidc_util_read(r, &data) != TRUE)
313
20
    goto end;
314
315
6.76k
  rc = oidc_util_read_form_encoded_params_impl(r, table, data, no_repeat);
316
6.76k
  if (rc != TRUE)
317
14
    goto end;
318
319
6.75k
  if (propagate == FALSE)
320
5.64k
    goto end;
321
322
1.11k
  arr = apr_table_elts(table);
323
1.11k
  elts = (const apr_table_entry_t *)arr->elts;
324
2.21k
  for (int i = 0; i < arr->nelts; i++)
325
1.10k
    if (_oidc_strcmp(elts[i].key, strip_param_name) != 0)
326
1.05k
      oidc_util_userdata_set_post_param(r, elts[i].key, elts[i].val);
327
328
6.78k
end:
329
330
6.78k
  return rc;
331
1.11k
}
332
333
apr_byte_t oidc_util_read_post_params(request_rec *r, apr_table_t *table, apr_byte_t propagate,
334
757
              const char *strip_param_name) {
335
757
  return oidc_util_read_post_params_impl(r, table, propagate, strip_param_name, NULL);
336
757
}
337
338
apr_byte_t oidc_util_read_post_params_reject_dup(request_rec *r, apr_table_t *table, apr_byte_t propagate,
339
6.03k
             const char *strip_param_name, const char *const *no_repeat) {
340
6.03k
  return oidc_util_read_post_params_impl(r, table, propagate, strip_param_name, no_repeat);
341
6.03k
}
342
343
/*
344
 * see if two provided issuer identifiers match (cq. ignore trailing slash)
345
 */
346
6.66k
apr_byte_t oidc_util_issuer_match(const char *a, const char *b) {
347
348
  /* check the "issuer" value against the one configure for the provider we got this id_token from */
349
6.66k
  if (_oidc_strcmp(a, b) != 0) {
350
351
    /* no strict match, but we are going to accept if the difference is only a trailing slash */
352
4.41k
    int n1 = (int)_oidc_strlen(a);
353
4.41k
    int n2 = (int)_oidc_strlen(b);
354
4.41k
    int n = 0;
355
4.41k
    if ((n1 == n2 + 1) && (a[n1 - 1] == OIDC_CHAR_FORWARD_SLASH))
356
132
      n = n2;
357
4.28k
    else if ((n2 == n1 + 1) && (b[n2 - 1] == OIDC_CHAR_FORWARD_SLASH))
358
354
      n = n1;
359
4.41k
    if ((n == 0) || (_oidc_strncmp(a, b, n) != 0))
360
4.41k
      return FALSE;
361
4.41k
  }
362
363
2.25k
  return TRUE;
364
6.66k
}
365
366
/*
367
 * parse a space separated string in to a hash table
368
 */
369
3.51M
apr_hash_t *oidc_util_spaced_string_to_hashtable(apr_pool_t *pool, const char *str) {
370
3.51M
  const char *val;
371
3.51M
  const char *data = apr_pstrdup(pool, str);
372
3.51M
  apr_hash_t *result = apr_hash_make(pool);
373
9.89M
  while (data && (*data)) {
374
6.38M
    val = ap_getword_white(pool, &data);
375
6.38M
    if (val == NULL)
376
0
      break;
377
6.38M
    apr_hash_set(result, val, APR_HASH_KEY_STRING, val);
378
6.38M
  }
379
3.51M
  return result;
380
3.51M
}
381
382
/*
383
 * compare two space separated value types
384
 */
385
1.74M
apr_byte_t oidc_util_spaced_string_equals(apr_pool_t *pool, const char *a, const char *b) {
386
387
1.74M
  const void *k = NULL;
388
1.74M
  void *v = NULL;
389
390
  /* parse both entries as hash tables */
391
1.74M
  apr_hash_t *ht_a = oidc_util_spaced_string_to_hashtable(pool, a);
392
1.74M
  apr_hash_t *ht_b = oidc_util_spaced_string_to_hashtable(pool, b);
393
394
  /* first compare the length of both response_types */
395
1.74M
  if (apr_hash_count(ht_a) != apr_hash_count(ht_b))
396
1.18M
    return FALSE;
397
398
  /* then loop over all entries */
399
567k
  for (apr_hash_index_t *hi = apr_hash_first(NULL, ht_a); hi; hi = apr_hash_next(hi)) {
400
561k
    apr_hash_this(hi, &k, NULL, &v);
401
561k
    if (apr_hash_get(ht_b, k, APR_HASH_KEY_STRING) == NULL)
402
553k
      return FALSE;
403
561k
  }
404
405
  /* if we've made it this far, a an b are equal in length and every element in a is in b */
406
6.25k
  return TRUE;
407
559k
}
408
409
/*
410
 * see if a particular value is part of a space separated value
411
 */
412
26.5k
apr_byte_t oidc_util_spaced_string_contains(apr_pool_t *pool, const char *str, const char *match) {
413
26.5k
  if ((str == NULL) || (match == NULL))
414
11.9k
    return FALSE;
415
14.5k
  apr_hash_t *ht = oidc_util_spaced_string_to_hashtable(pool, str);
416
14.5k
  return (apr_hash_get(ht, match, APR_HASH_KEY_STRING) != NULL);
417
26.5k
}
418
419
/*
420
 * add query encoded parameters to a table
421
 */
422
2.02k
void oidc_util_table_add_query_encoded_params(apr_pool_t *pool, apr_table_t *table, const char *params) {
423
2.02k
  char *key = NULL;
424
2.02k
  char *value = NULL;
425
2.02k
  const char *v = NULL;
426
2.02k
  const char *p = params;
427
94.1k
  while (p && (*p)) {
428
92.1k
    v = ap_getword(pool, &p, OIDC_CHAR_AMP);
429
92.1k
    if (v == NULL)
430
0
      break;
431
92.1k
    key = apr_pstrdup(pool, ap_getword(pool, &v, OIDC_CHAR_EQUAL));
432
92.1k
    ap_unescape_url(key);
433
92.1k
    value = apr_pstrdup(pool, v);
434
92.1k
    ap_unescape_url(value);
435
92.1k
    apr_table_addn(table, key, value);
436
92.1k
  }
437
2.02k
}
438
439
9.74k
char *oidc_util_hex_encode(apr_pool_t *pool, const unsigned char *bytes, unsigned int len) {
440
9.74k
  static const char hex[] = "0123456789abcdef";
441
9.74k
  char *s = apr_palloc(pool, ((size_t)len * 2) + 1);
442
204k
  for (unsigned int i = 0; i < len; i++) {
443
194k
    s[i * 2] = hex[bytes[i] >> 4];
444
194k
    s[(i * 2) + 1] = hex[bytes[i] & 0x0f];
445
194k
  }
446
9.74k
  s[(size_t)len * 2] = '\0';
447
9.74k
  return s;
448
9.74k
}
449
450
27.0k
#define OIDC_UTIL_MASK_VALUE_PREFIX_LEN 4
451
452
/*
453
 * redact a secret/token value for logging purposes, keeping a short prefix and the
454
 * length so log lines can still be correlated across requests without exposing the
455
 * value itself
456
 */
457
/*
458
 * whether secrets are masked in the log for this request's server; OIDCDebugMaskSecrets is
459
 * RSRC_CONF, so this is a per-virtual-host answer and cannot be relaxed from a directory or
460
 * location section
461
 */
462
17.1k
apr_byte_t oidc_util_log_mask_secrets(request_rec *r) {
463
17.1k
  const oidc_cfg_t *cfg = NULL;
464
17.1k
  if (r == NULL)
465
0
    return TRUE;
466
17.1k
  cfg = ap_get_module_config(r->server->module_config, &auth_openidc_module);
467
17.1k
  if (cfg == NULL)
468
0
    return TRUE;
469
17.1k
  return (oidc_cfg_debug_mask_secrets_get(cfg) != 0) ? TRUE : FALSE;
470
17.1k
}
471
472
13.5k
const char *oidc_util_mask_value(request_rec *r, const char *value) {
473
13.5k
  apr_size_t len = 0;
474
13.5k
  if (value == NULL)
475
0
    return "(null)";
476
  /* OIDCDebugMaskSecrets Off: hand back the value itself, which is the whole point of it */
477
13.5k
  if (oidc_util_log_mask_secrets(r) == FALSE)
478
0
    return value;
479
13.5k
  len = _oidc_strlen(value);
480
13.5k
  if (len <= OIDC_UTIL_MASK_VALUE_PREFIX_LEN)
481
31
    return "***";
482
13.5k
  return apr_psprintf(r->pool, "%.*s...(%" APR_SIZE_T_FMT " chars)", OIDC_UTIL_MASK_VALUE_PREFIX_LEN, value, len);
483
13.5k
}
484
485
/*
486
 * compare two strings in constant time with respect to their content, so a failing
487
 * comparison against a request-supplied value (state, XSRF token, CSRF token, ...)
488
 * cannot be used to learn how many leading bytes matched; NULL is only considered
489
 * equal to NULL
490
 */
491
5.81k
apr_byte_t oidc_util_strcmp_const_time(const char *a, const char *b) {
492
5.81k
  apr_size_t len_a = 0;
493
5.81k
  apr_size_t len_b = 0;
494
495
5.81k
  if ((a == NULL) || (b == NULL))
496
24
    return (a == b) ? TRUE : FALSE;
497
498
5.78k
  len_a = _oidc_strlen(a);
499
5.78k
  len_b = _oidc_strlen(b);
500
501
5.78k
  if (len_a != len_b)
502
1.81k
    return FALSE;
503
504
3.97k
  return (CRYPTO_memcmp(a, b, len_a) == 0) ? TRUE : FALSE;
505
5.78k
}
506
507
/*
508
 * openssl hash and base64 encode
509
 */
510
apr_byte_t oidc_util_hash_string_and_base64url_encode(request_rec *r, const char *openssl_hash_algo, const char *input,
511
19.6k
                  char **output) {
512
19.6k
  oidc_jose_error_t err;
513
19.6k
  unsigned char *hashed = NULL;
514
19.6k
  unsigned int hashed_len = 0;
515
19.6k
  if (oidc_jose_hash_bytes(r->pool, openssl_hash_algo, (const unsigned char *)input,
516
19.6k
         (unsigned int)_oidc_strlen(input), &hashed, &hashed_len, &err) == FALSE) {
517
0
    oidc_error(r, "oidc_jose_hash_bytes returned an error: %s", err.text);
518
0
    return FALSE;
519
0
  }
520
521
19.6k
  if (oidc_util_base64url_encode(r, output, (const char *)hashed, hashed_len, OIDC_BASE64URL_PADDING_STRIP) <=
522
19.6k
      0) {
523
0
    oidc_error(r, "oidc_base64url_encode returned an error");
524
0
    return FALSE;
525
0
  }
526
19.6k
  return TRUE;
527
19.6k
}
528
529
/*
530
 * return TRUE when "hostname" is equal to "suffix" or is a proper subdomain of it,
531
 * i.e. the part of "hostname" that precedes "suffix" ends with a dot; comparison
532
 * is case-insensitive
533
 */
534
10.7k
apr_byte_t oidc_util_hostname_endswith(const char *hostname, const char *suffix) {
535
10.7k
  size_t hlen;
536
10.7k
  size_t slen;
537
10.7k
  const char *tail = NULL;
538
10.7k
  if ((hostname == NULL) || (suffix == NULL))
539
0
    return FALSE;
540
10.7k
  hlen = _oidc_strlen(hostname);
541
10.7k
  slen = _oidc_strlen(suffix);
542
10.7k
  if ((slen == 0) || (hlen < slen))
543
3.00k
    return FALSE;
544
7.73k
  tail = hostname + (hlen - slen);
545
7.73k
  if (_oidc_strcasecmp(tail, suffix) != 0)
546
4.05k
    return FALSE;
547
3.67k
  return ((tail == hostname) || (*(tail - 1) == '.')) ? TRUE : FALSE;
548
7.73k
}
549
550
/*
551
 * check if the provided cookie domain value is valid
552
 */
553
3.55k
apr_byte_t oidc_util_cookie_domain_valid(const char *hostname, const char *cookie_domain) {
554
3.55k
  const char *check_cookie = cookie_domain;
555
3.55k
  if (check_cookie == NULL)
556
0
    return FALSE;
557
  // Skip past the first char of a cookie_domain that starts
558
  // with a ".", ASCII 46
559
3.55k
  if (check_cookie[0] == 46)
560
1.80k
    check_cookie++;
561
3.55k
  return oidc_util_hostname_endswith(hostname, check_cookie);
562
3.55k
}
563
564
0
#define OIDC_TP_TRACE_ID_LEN 16
565
0
#define OIDC_TP_PARENT_ID_LEN 8
566
567
0
#define OIDC_TRACE_ID_LOG_VAR "OIDC_TRACE_ID"
568
569
/*
570
The following version-format definition is used for version 00.
571
version-format   = trace-id "-" parent-id "-" trace-flags
572
trace-id         = 32HEXDIGLC  ; 16 bytes array identifier. All zeroes forbidden
573
parent-id        = 16HEXDIGLC  ; 8 bytes array identifier. All zeroes forbidden
574
trace-flags      = 2HEXDIGLC   ; 8 bit flags. Currently, only one bit is used.
575
 */
576
12.2k
void oidc_util_set_trace_parent(request_rec *r, const oidc_cfg_t *c, const char *span) {
577
12.2k
  unsigned char trace_id[OIDC_TP_TRACE_ID_LEN];
578
12.2k
  unsigned char parent_id[OIDC_TP_PARENT_ID_LEN];
579
12.2k
  unsigned char trace_flags = 0;
580
12.2k
  char *s_parent_id = "";
581
12.2k
  char *s_trace_id = "";
582
12.2k
  const char *v = NULL;
583
12.2k
  int i = 0;
584
12.2k
  const char *hostname = "localhost";
585
12.2k
  const uint64_t P1 = 7;
586
12.2k
  const uint64_t P2 = 31;
587
12.2k
  uint64_t hash = P1;
588
589
12.2k
  if (oidc_cfg_trace_parent_get(c) != OIDC_TRACE_PARENT_GENERATE)
590
12.2k
    return;
591
592
0
  if (r->server->server_hostname)
593
0
    hostname = r->server->server_hostname;
594
595
0
  v = oidc_request_state_get(r, OIDC_REQUEST_STATE_TRACE_ID);
596
597
0
  if (span == NULL) {
598
0
    _oidc_memset(parent_id, 0, OIDC_TP_PARENT_ID_LEN);
599
0
    _oidc_memcpy(parent_id, hostname,
600
0
           _oidc_strlen(hostname) < OIDC_TP_PARENT_ID_LEN ? _oidc_strlen(hostname)
601
0
                      : OIDC_TP_PARENT_ID_LEN);
602
0
  } else {
603
0
    if (v == NULL)
604
0
      oidc_warn(r, "parameter \"span\" is set, but no \"trace-id\" [%s] found in the request state",
605
0
          OIDC_REQUEST_STATE_TRACE_ID);
606
0
    else
607
0
      oidc_debug(r, "changing \"parent-id\" of current traceparent");
608
0
    for (const char *p = span; *p != 0; p++)
609
0
      hash = hash * P2 + *p;
610
0
    _oidc_memcpy(parent_id, &hash, OIDC_TP_PARENT_ID_LEN);
611
0
  }
612
0
  for (i = 0; i < OIDC_TP_PARENT_ID_LEN; i++)
613
0
    s_parent_id = apr_psprintf(r->pool, "%s%02x", s_parent_id, parent_id[i]);
614
615
0
  if (v == NULL) {
616
0
    if (apr_generate_random_bytes(trace_id, OIDC_TP_TRACE_ID_LEN) != APR_SUCCESS) {
617
0
      oidc_warn(r, "apr_generate_random_bytes failed: no \"%s\" header will be set",
618
0
          OIDC_HTTP_HDR_TRACE_PARENT);
619
0
      return;
620
0
    }
621
0
    for (i = 0; i < OIDC_TP_TRACE_ID_LEN; i++)
622
0
      s_trace_id = apr_psprintf(r->pool, "%s%02x", s_trace_id, trace_id[i]);
623
0
    oidc_request_state_set(r, OIDC_REQUEST_STATE_TRACE_ID, s_trace_id);
624
0
  } else {
625
0
    s_trace_id = apr_pstrdup(r->pool, v);
626
0
  }
627
628
  /* export the generated trace-id into the request notes and environment so log lines can be
629
   * correlated per request with ErrorLogFormat %{OIDC_TRACE_ID}n and LogFormat %{OIDC_TRACE_ID}e */
630
0
  if (r->notes != NULL)
631
0
    apr_table_set(r->notes, OIDC_TRACE_ID_LOG_VAR, s_trace_id);
632
0
  if (r->subprocess_env != NULL)
633
0
    apr_table_set(r->subprocess_env, OIDC_TRACE_ID_LOG_VAR, s_trace_id);
634
635
  /* the generated trace is always recorded by this module (the trace-id is exported into the
636
   * request notes/environment for log correlation above), so the W3C "sampled" flag is set
637
   * unconditionally; before 2.4.20.4 it was set only when metrics collection was enabled */
638
0
  trace_flags = trace_flags | 0x01;
639
640
0
  oidc_http_hdr_in_set(r, OIDC_HTTP_HDR_TRACE_PARENT,
641
0
           apr_psprintf(r->pool, "00-%s-%s-%02x", s_trace_id, s_parent_id, trace_flags));
642
0
}
643
644
/*
645
 * clear the contents of a hash table (used for older versions of libapr missing this)
646
 */
647
0
void oidc_util_apr_hash_clear(apr_hash_t *ht) {
648
0
  const void *key = NULL;
649
0
  apr_ssize_t klen = 0;
650
0
  for (apr_hash_index_t *hi = apr_hash_first(NULL, ht); hi; hi = apr_hash_next(hi)) {
651
0
    apr_hash_this(hi, &key, &klen, NULL);
652
0
    apr_hash_set(ht, key, klen, NULL);
653
0
  }
654
0
}
655
656
/* the largest whole number of seconds apr_time_from_sec can represent */
657
4.73k
#define OIDC_UTIL_APR_TIME_SEC_MAX (APR_INT64_MAX / APR_USEC_PER_SEC)
658
659
/*
660
 * Convert seconds to apr_time_t without undefined casts or overflow in apr_time_from_sec().
661
 * Negative and non-finite values become 0; values above the APR range saturate.
662
 */
663
4.73k
apr_time_t oidc_util_apr_time_from_sec(double seconds) {
664
  /* NB: also catches NaN, which compares false against everything */
665
4.73k
  if (!(seconds > 0))
666
4
    return 0;
667
4.73k
  if (seconds >= (double)OIDC_UTIL_APR_TIME_SEC_MAX)
668
31
    return APR_INT64_MAX;
669
4.70k
  return apr_time_from_sec((apr_time_t)seconds);
670
4.73k
}
671
672
/*
673
 * add two non-negative apr_time_t values, saturating at APR_INT64_MAX rather than wrapping; used to
674
 * turn a relative expiry into an absolute one without a far-future value folding back into the past
675
 */
676
0
apr_time_t oidc_util_apr_time_add(apr_time_t a, apr_time_t b) {
677
0
  if ((a < 0) || (b < 0))
678
0
    return (a > b) ? a : b;
679
0
  if (a > APR_INT64_MAX - b)
680
0
    return APR_INT64_MAX;
681
0
  return a + b;
682
0
}
683
684
/*
685
 * return the OpenSSL version we compiled against
686
 */
687
0
char *oidc_util_openssl_version(apr_pool_t *pool) {
688
0
  char *s_version = NULL;
689
0
#ifdef OPENSSL_VERSION_STR
690
0
  s_version = apr_psprintf(pool, "openssl-%s", OPENSSL_VERSION_STR);
691
#else
692
  s_version = OPENSSL_VERSION_TEXT;
693
#endif
694
0
  return s_version;
695
0
}