/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 | } |