1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one
3 * or more contributor license agreements. See the NOTICE file
4 * distributed with this work for additional information
5 * regarding copyright ownership. The ASF licenses this file
6 * to you under the Apache License, Version 2.0 (the
7 * "License"); you may not use this file except in compliance
8 * with the License. You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing,
13 * software distributed under the License is distributed on an
14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 * KIND, either express or implied. See the License for the
16 * specific language governing permissions and limitations
17 * under the License.
18 */
19
20 /***************************************************************************
21 * Copyright (C) 2017-2021 ZmartZone Holding BV
22 * Copyright (C) 2013-2017 Ping Identity Corporation
23 * All rights reserved.
24 *
25 * DISCLAIMER OF WARRANTIES:
26 *
27 * THE SOFTWARE PROVIDED HEREUNDER IS PROVIDED ON AN "AS IS" BASIS, WITHOUT
28 * ANY WARRANTIES OR REPRESENTATIONS EXPRESS, IMPLIED OR STATUTORY; INCLUDING,
29 * WITHOUT LIMITATION, WARRANTIES OF QUALITY, PERFORMANCE, NONINFRINGEMENT,
30 * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. NOR ARE THERE ANY
31 * WARRANTIES CREATED BY A COURSE OR DEALING, COURSE OF PERFORMANCE OR TRADE
32 * USAGE. FURTHERMORE, THERE ARE NO WARRANTIES THAT THE SOFTWARE WILL MEET
33 * YOUR NEEDS OR BE FREE FROM ERRORS, OR THAT THE OPERATION OF THE SOFTWARE
34 * WILL BE UNINTERRUPTED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR
35 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
36 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES HOWEVER CAUSED AND ON ANY THEORY OF
37 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
38 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
39 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
40 *
41 * @Author: Hans Zandbelt - hans.zandbelt@zmartzone.eu
42 */
43
44 #include <apr_strings.h>
45 #include <apr_base64.h>
46 #include <apr_lib.h>
47
48 #include <httpd.h>
49 #include <http_config.h>
50 #include <http_log.h>
51 #include <http_request.h>
52 #include "http_protocol.h"
53
54 #include <curl/curl.h>
55
56 #include "mod_auth_openidc.h"
57
58 #include <pcre.h>
59 #include "pcre_subst.h"
60
61 /* hrm, should we get rid of this by adding parameters to the (3) functions? */
62 extern module AP_MODULE_DECLARE_DATA auth_openidc_module;
63
64 /*
65 * base64url encode a string
66 */
oidc_base64url_encode(request_rec * r,char ** dst,const char * src,int src_len,int remove_padding)67 int oidc_base64url_encode(request_rec *r, char **dst, const char *src,
68 int src_len, int remove_padding) {
69 if ((src == NULL) || (src_len <= 0)) {
70 oidc_error(r, "not encoding anything; src=NULL and/or src_len<1");
71 return -1;
72 }
73 unsigned int enc_len = apr_base64_encode_len(src_len);
74 char *enc = apr_palloc(r->pool, enc_len);
75 apr_base64_encode(enc, (const char*) src, src_len);
76 unsigned int i = 0;
77 while (enc[i] != '\0') {
78 if (enc[i] == '+')
79 enc[i] = '-';
80 if (enc[i] == '/')
81 enc[i] = '_';
82 if (enc[i] == '=')
83 enc[i] = ',';
84 i++;
85 }
86 if (remove_padding) {
87 /* remove /0 and padding */
88 if (enc_len > 0)
89 enc_len--;
90 if ((enc_len > 0) && (enc[enc_len - 1] == ','))
91 enc_len--;
92 if ((enc_len > 0) && (enc[enc_len - 1] == ','))
93 enc_len--;
94 enc[enc_len] = '\0';
95 }
96 *dst = enc;
97 return enc_len;
98 }
99
100 /*
101 * base64url decode a string
102 */
oidc_base64url_decode(apr_pool_t * pool,char ** dst,const char * src)103 int oidc_base64url_decode(apr_pool_t *pool, char **dst, const char *src) {
104 if (src == NULL) {
105 return -1;
106 }
107 char *dec = apr_pstrdup(pool, src);
108 int i = 0;
109 while (dec[i] != '\0') {
110 if (dec[i] == '-')
111 dec[i] = '+';
112 if (dec[i] == '_')
113 dec[i] = '/';
114 if (dec[i] == ',')
115 dec[i] = '=';
116 i++;
117 }
118 switch (strlen(dec) % 4) {
119 case 0:
120 break;
121 case 2:
122 dec = apr_pstrcat(pool, dec, "==", NULL);
123 break;
124 case 3:
125 dec = apr_pstrcat(pool, dec, "=", NULL);
126 break;
127 default:
128 return 0;
129 }
130 int dlen = apr_base64_decode_len(dec);
131 *dst = apr_palloc(pool, dlen);
132 return apr_base64_decode(*dst, dec);
133 }
134
oidc_util_jwt_create(request_rec * r,const char * secret,json_t * payload,char ** compact_encoded_jwt)135 apr_byte_t oidc_util_jwt_create(request_rec *r, const char *secret,
136 json_t *payload, char **compact_encoded_jwt) {
137
138 apr_byte_t rv = FALSE;
139 oidc_jose_error_t err;
140
141 oidc_jwk_t *jwk = NULL;
142 oidc_jwt_t *jwt = NULL;
143 oidc_jwt_t *jwe = NULL;
144
145 if (oidc_util_create_symmetric_key(r, secret, 0, OIDC_JOSE_ALG_SHA256,
146 FALSE, &jwk) == FALSE)
147 goto end;
148
149 jwt = oidc_jwt_new(r->pool, TRUE, FALSE);
150 if (jwt == NULL) {
151 oidc_error(r, "creating JWT failed");
152 goto end;
153 }
154
155 jwt->header.alg = apr_pstrdup(r->pool, CJOSE_HDR_ALG_HS256);
156 jwt->payload.value.json = payload;
157
158 if (oidc_jwt_sign(r->pool, jwt, jwk, &err) == FALSE) {
159 oidc_error(r, "signing JWT failed: %s", oidc_jose_e2s(r->pool, err));
160 goto end;
161 }
162
163 jwe = oidc_jwt_new(r->pool, TRUE, FALSE);
164 if (jwe == NULL) {
165 oidc_error(r, "creating JWE failed");
166 goto end;
167 }
168
169 jwe->header.alg = apr_pstrdup(r->pool, CJOSE_HDR_ALG_DIR);
170 jwe->header.enc = apr_pstrdup(r->pool, CJOSE_HDR_ENC_A256GCM);
171
172 const char *cser = oidc_jwt_serialize(r->pool, jwt, &err);
173 if (oidc_jwt_encrypt(r->pool, jwe, jwk, cser, compact_encoded_jwt, &err)
174 == FALSE) {
175 oidc_error(r, "encrypting JWT failed: %s", oidc_jose_e2s(r->pool, err));
176 goto end;
177 }
178
179 rv = TRUE;
180
181 end:
182
183 if (jwe != NULL)
184 oidc_jwt_destroy(jwe);
185 if (jwk != NULL)
186 oidc_jwk_destroy(jwk);
187 if (jwt != NULL) {
188 jwt->payload.value.json = NULL;
189 oidc_jwt_destroy(jwt);
190 }
191
192 return rv;
193 }
194
oidc_util_jwt_verify(request_rec * r,const char * secret,const char * compact_encoded_jwt,json_t ** result)195 apr_byte_t oidc_util_jwt_verify(request_rec *r, const char *secret,
196 const char *compact_encoded_jwt, json_t **result) {
197
198 oidc_debug(r, "enter: JWT header=%s",
199 oidc_proto_peek_jwt_header(r, compact_encoded_jwt, NULL));
200
201 apr_byte_t rv = FALSE;
202 oidc_jose_error_t err;
203
204 oidc_jwk_t *jwk = NULL;
205 oidc_jwt_t *jwt = NULL;
206
207 if (oidc_util_create_symmetric_key(r, secret, 0, OIDC_JOSE_ALG_SHA256,
208 FALSE, &jwk) == FALSE)
209 goto end;
210
211 apr_hash_t *keys = apr_hash_make(r->pool);
212 apr_hash_set(keys, "", APR_HASH_KEY_STRING, jwk);
213
214 if (oidc_jwt_parse(r->pool, compact_encoded_jwt, &jwt, keys, &err)
215 == FALSE) {
216 oidc_error(r, "parsing JWT failed: %s", oidc_jose_e2s(r->pool, err));
217 goto end;
218 }
219
220 if (oidc_jwt_verify(r->pool, jwt, keys, &err) == FALSE) {
221 oidc_error(r, "verifying JWT failed: %s", oidc_jose_e2s(r->pool, err));
222 goto end;
223 }
224
225 *result = json_deep_copy(jwt->payload.value.json);
226
227 rv = TRUE;
228
229 end:
230
231 if (jwk != NULL)
232 oidc_jwk_destroy(jwk);
233 if (jwt != NULL)
234 oidc_jwt_destroy(jwt);
235
236 return rv;
237 }
238
239 /*
240 * convert a character to an ENVIRONMENT-variable-safe variant
241 */
oidc_char_to_env(int c)242 int oidc_char_to_env(int c) {
243 return apr_isalnum(c) ? apr_toupper(c) : '_';
244 }
245
246 /*
247 * compare two strings based on how they would be converted to an
248 * environment variable, as per oidc_char_to_env. If len is specified
249 * as less than zero, then the full strings will be compared. Returns
250 * less than, equal to, or greater than zero based on whether the
251 * first argument's conversion to an environment variable is less
252 * than, equal to, or greater than the second.
253 */
oidc_strnenvcmp(const char * a,const char * b,int len)254 int oidc_strnenvcmp(const char *a, const char *b, int len) {
255 int d, i = 0;
256 while (1) {
257 /* If len < 0 then we don't stop based on length */
258 if (len >= 0 && i >= len)
259 return 0;
260
261 /* If we're at the end of both strings, they're equal */
262 if (!*a && !*b)
263 return 0;
264
265 /* If the second string is shorter, pick it: */
266 if (*a && !*b)
267 return 1;
268
269 /* If the first string is shorter, pick it: */
270 if (!*a && *b)
271 return -1;
272
273 /* Normalize the characters as for conversion to an
274 * environment variable. */
275 d = oidc_char_to_env(*a) - oidc_char_to_env(*b);
276 if (d)
277 return d;
278
279 a++;
280 b++;
281 i++;
282 }
283 return 0;
284 }
285
286 /*
287 * escape a string
288 */
oidc_util_escape_string(const request_rec * r,const char * str)289 char* oidc_util_escape_string(const request_rec *r, const char *str) {
290 CURL *curl = curl_easy_init();
291 if (curl == NULL) {
292 oidc_error(r, "curl_easy_init() error");
293 return NULL;
294 }
295 char *result = curl_easy_escape(curl, str, 0);
296 if (result == NULL) {
297 oidc_error(r, "curl_easy_escape() error");
298 return NULL;
299 }
300 char *rv = apr_pstrdup(r->pool, result);
301 curl_free(result);
302 curl_easy_cleanup(curl);
303 return rv;
304 }
305
306 /*
307 * escape a string
308 */
oidc_util_unescape_string(const request_rec * r,const char * str)309 char* oidc_util_unescape_string(const request_rec *r, const char *str) {
310 CURL *curl = curl_easy_init();
311 if (curl == NULL) {
312 oidc_error(r, "curl_easy_init() error");
313 return NULL;
314 }
315 int counter = 0;
316 char *replaced = (char*) str;
317 while (str[counter] != '\0') {
318 if (str[counter] == '+') {
319 replaced[counter] = ' ';
320 }
321 counter++;
322 }
323 char *result = curl_easy_unescape(curl, replaced, 0, 0);
324 if (result == NULL) {
325 oidc_error(r, "curl_easy_unescape() error");
326 return NULL;
327 }
328 char *rv = apr_pstrdup(r->pool, result);
329 curl_free(result);
330 curl_easy_cleanup(curl);
331 //oidc_debug(r, "input=\"%s\", output=\"%s\"", str, rv);
332 return rv;
333 }
334
335 /*
336 * HTML escape a string
337 */
oidc_util_html_escape(apr_pool_t * pool,const char * s)338 char* oidc_util_html_escape(apr_pool_t *pool, const char *s) {
339 // TODO: this has performance/memory issues for large chunks of HTML
340 const char chars[6] = { '&', '\'', '\"', '>', '<', '\0' };
341 const char *const replace[] =
342 { "&", "'", """, ">", "<", };
343 unsigned int i, j = 0, k, n = 0, len = strlen(chars);
344 unsigned int m = 0;
345 char *r = apr_pcalloc(pool, strlen(s) * 6);
346 for (i = 0; i < strlen(s); i++) {
347 for (n = 0; n < len; n++) {
348 if (s[i] == chars[n]) {
349 m = (unsigned int) strlen(replace[n]);
350 for (k = 0; k < m; k++)
351 r[j + k] = replace[n][k];
352 j += m;
353 break;
354 }
355 }
356 if (n == len) {
357 r[j] = s[i];
358 j++;
359 }
360 }
361 r[j] = '\0';
362 return apr_pstrdup(pool, r);
363 }
364
365 /*
366 * get the URL scheme that is currently being accessed
367 */
oidc_get_current_url_scheme(const request_rec * r)368 static const char* oidc_get_current_url_scheme(const request_rec *r) {
369 /* first see if there's a proxy/load-balancer in front of us */
370 const char *scheme_str = oidc_util_hdr_in_x_forwarded_proto_get(r);
371 /* if not we'll determine the scheme used to connect to this server */
372 if (scheme_str == NULL) {
373 #ifdef APACHE2_0
374 scheme_str = (char *) ap_http_method(r);
375 #else
376 scheme_str = (char*) ap_http_scheme(r);
377 #endif
378 }
379 if ((scheme_str == NULL)
380 || ((apr_strnatcmp(scheme_str, "http") != 0)
381 && (apr_strnatcmp(scheme_str, "https") != 0))) {
382 oidc_warn(r,
383 "detected HTTP scheme \"%s\" is not \"http\" nor \"https\"; perhaps your reverse proxy passes a wrongly configured \"%s\" header: falling back to default \"https\"",
384 scheme_str, OIDC_HTTP_HDR_X_FORWARDED_PROTO);
385 scheme_str = "https";
386 }
387 return scheme_str;
388 }
389
390 /*
391 * get the URL port that is currently being accessed
392 */
oidc_get_current_url_port(const request_rec * r,const char * scheme_str)393 static const char* oidc_get_current_url_port(const request_rec *r,
394 const char *scheme_str) {
395
396 /*
397 * first see if there's a proxy/load-balancer in front of us
398 * that sets X-Forwarded-Port
399 */
400 const char *port_str = oidc_util_hdr_in_x_forwarded_port_get(r);
401 if (port_str)
402 return port_str;
403
404 /*
405 * see if we can get the port from the "X-Forwarded-Host" header
406 * and if that header was set we'll assume defaults
407 */
408 const char *host_hdr = oidc_util_hdr_in_x_forwarded_host_get(r);
409 if (host_hdr) {
410 port_str = strchr(host_hdr, OIDC_CHAR_COLON);
411 if (port_str)
412 port_str++;
413 return port_str;
414 }
415
416 /*
417 * see if we can get the port from the "Host" header; if not
418 * we'll determine the port locally
419 */
420 host_hdr = oidc_util_hdr_in_host_get(r);
421 if (host_hdr) {
422 port_str = strchr(host_hdr, OIDC_CHAR_COLON);
423 if (port_str) {
424 port_str++;
425 return port_str;
426 }
427 }
428
429 /*
430 * if X-Forwarded-Proto assume the default port otherwise the
431 * port should have been set in the X-Forwarded-Port header
432 */
433 if (oidc_util_hdr_in_x_forwarded_proto_get(r))
434 return NULL;
435
436 /*
437 * if no port was set in the Host header and no X-Forwarded-Proto was set, we'll
438 * determine the port locally and don't print it when it's the default for the protocol
439 */
440 const apr_port_t port = r->connection->local_addr->port;
441 if ((apr_strnatcmp(scheme_str, "https") == 0) && port == 443)
442 return NULL;
443 else if ((apr_strnatcmp(scheme_str, "http") == 0) && port == 80)
444 return NULL;
445
446 port_str = apr_psprintf(r->pool, "%u", port);
447 return port_str;
448 }
449
450 /*
451 * get the hostname part of the URL that is currently being accessed
452 */
oidc_get_current_url_host(request_rec * r)453 const char* oidc_get_current_url_host(request_rec *r) {
454 const char *host_str = oidc_util_hdr_in_x_forwarded_host_get(r);
455 if (host_str == NULL)
456 host_str = oidc_util_hdr_in_host_get(r);
457 if (host_str) {
458 host_str = apr_pstrdup(r->pool, host_str);
459 char *p = strchr(host_str, OIDC_CHAR_COLON);
460 if (p != NULL)
461 *p = '\0';
462 } else {
463 /* no Host header, HTTP 1.0 */
464 host_str = ap_get_server_name(r);
465 }
466 return host_str;
467 }
468
469 /*
470 * get the base part of the current URL (scheme + host (+ port))
471 */
oidc_get_current_url_base(request_rec * r)472 static const char* oidc_get_current_url_base(request_rec *r) {
473
474 const char *scheme_str = oidc_get_current_url_scheme(r);
475 const char *host_str = oidc_get_current_url_host(r);
476 const char *port_str = oidc_get_current_url_port(r, scheme_str);
477 port_str = port_str ? apr_psprintf(r->pool, ":%s", port_str) : "";
478
479 char *url = apr_pstrcat(r->pool, scheme_str, "://", host_str, port_str,
480 NULL);
481
482 return url;
483 }
484
485 /*
486 * get the URL that is currently being accessed
487 */
oidc_get_current_url(request_rec * r)488 char* oidc_get_current_url(request_rec *r) {
489 char *url = NULL, *path = NULL;
490 apr_uri_t uri;
491
492 path = r->uri;
493
494 /* check if we're dealing with a forward proxying secenario i.e. a non-relative URL */
495 if ((path) && (path[0] != '/')) {
496 memset(&uri, 0, sizeof(apr_uri_t));
497 if (apr_uri_parse(r->pool, r->uri, &uri) == APR_SUCCESS)
498 path = apr_pstrcat(r->pool, uri.path,
499 (r->args != NULL && *r->args != '\0' ? "?" : ""), r->args,
500 NULL);
501 else
502 oidc_warn(r, "apr_uri_parse failed on non-relative URL: %s",
503 r->uri);
504 } else {
505 /* make sure we retain URL-encoded characters original URL that we send the user back to */
506 path = r->unparsed_uri;
507 }
508
509 url = apr_pstrcat(r->pool, oidc_get_current_url_base(r), path, NULL);
510
511 oidc_debug(r, "current URL '%s'", url);
512
513 return url;
514 }
515
516 /*
517 * determine absolute redirect uri
518 */
oidc_get_redirect_uri(request_rec * r,oidc_cfg * cfg)519 const char* oidc_get_redirect_uri(request_rec *r, oidc_cfg *cfg) {
520
521 char *redirect_uri = cfg->redirect_uri;
522
523 if ((redirect_uri != NULL)
524 && (redirect_uri[0] == OIDC_CHAR_FORWARD_SLASH)) {
525 // relative redirect uri
526
527 redirect_uri = apr_pstrcat(r->pool, oidc_get_current_url_base(r),
528 cfg->redirect_uri, NULL);
529
530 oidc_debug(r, "determined absolute redirect uri: %s", redirect_uri);
531 }
532 return redirect_uri;
533 }
534
535 /*
536 * determine absolute redirect uri that is issuer specific
537 */
oidc_get_redirect_uri_iss(request_rec * r,oidc_cfg * cfg,oidc_provider_t * provider)538 const char* oidc_get_redirect_uri_iss(request_rec *r, oidc_cfg *cfg,
539 oidc_provider_t *provider) {
540 const char *redirect_uri = oidc_get_redirect_uri(r, cfg);
541 if (provider->issuer_specific_redirect_uri != 0) {
542 redirect_uri = apr_psprintf(r->pool, "%s%s%s=%s", redirect_uri,
543 strchr(redirect_uri ? redirect_uri : "",
544 OIDC_CHAR_QUERY) != NULL ?
545 OIDC_STR_AMP :
546 OIDC_STR_QUERY,
547 OIDC_PROTO_ISS, oidc_util_escape_string(r, provider->issuer));
548 // OIDC_PROTO_CLIENT_ID,
549 // oidc_util_escape_string(r, provider->client_id));
550 oidc_debug(r, "determined issuer specific redirect uri: %s",
551 redirect_uri);
552 }
553 return redirect_uri;
554 }
555
556 /* buffer to hold HTTP call responses */
557 typedef struct oidc_curl_buffer {
558 request_rec *r;
559 char *memory;
560 size_t size;
561 } oidc_curl_buffer;
562
563 /* maximum acceptable size of HTTP responses: 1 Mb */
564 #define OIDC_CURL_MAX_RESPONSE_SIZE 1024 * 1024
565
566 /*
567 * callback for CURL to write bytes that come back from an HTTP call
568 */
oidc_curl_write(void * contents,size_t size,size_t nmemb,void * userp)569 size_t oidc_curl_write(void *contents, size_t size, size_t nmemb, void *userp) {
570 size_t realsize = size * nmemb;
571 oidc_curl_buffer *mem = (oidc_curl_buffer*) userp;
572
573 /* check if we don't run over the maximum buffer/memory size for HTTP responses */
574 if (mem->size + realsize > OIDC_CURL_MAX_RESPONSE_SIZE) {
575 oidc_error(mem->r,
576 "HTTP response larger than maximum allowed size: current size=%ld, additional size=%ld, max=%d",
577 mem->size, realsize, OIDC_CURL_MAX_RESPONSE_SIZE);
578 return 0;
579 }
580
581 /* allocate the new buffer for the current + new response bytes */
582 char *newptr = apr_palloc(mem->r->pool, mem->size + realsize + 1);
583 if (newptr == NULL) {
584 oidc_error(mem->r,
585 "memory allocation for new buffer of %ld bytes failed",
586 mem->size + realsize + 1);
587 return 0;
588 }
589
590 /* copy over the data from current memory plus the cURL buffer */
591 memcpy(newptr, mem->memory, mem->size);
592 memcpy(&(newptr[mem->size]), contents, realsize);
593 mem->size += realsize;
594 mem->memory = newptr;
595 mem->memory[mem->size] = 0;
596
597 return realsize;
598 }
599
600 /* context structure for encoding parameters */
601 typedef struct oidc_http_encode_t {
602 request_rec *r;
603 char *encoded_params;
604 } oidc_http_encode_t;
605
606 /*
607 * add a url-form-encoded name/value pair
608 */
oidc_util_http_add_form_url_encoded_param(void * rec,const char * key,const char * value)609 static int oidc_util_http_add_form_url_encoded_param(void *rec, const char *key,
610 const char *value) {
611 oidc_http_encode_t *ctx = (oidc_http_encode_t*) rec;
612 oidc_debug(ctx->r, "processing: %s=%s", key,
613 (strncmp(key, OIDC_PROTO_CLIENT_SECRET, strlen(OIDC_PROTO_CLIENT_SECRET)) == 0) ? "***" : value);
614 const char *sep = ctx->encoded_params ? OIDC_STR_AMP : "";
615 ctx->encoded_params = apr_psprintf(ctx->r->pool, "%s%s%s=%s",
616 ctx->encoded_params ? ctx->encoded_params : "", sep,
617 oidc_util_escape_string(ctx->r, key),
618 oidc_util_escape_string(ctx->r, value));
619 return 1;
620 }
621
622 /*
623 * construct a URL with query parameters
624 */
oidc_util_http_query_encoded_url(request_rec * r,const char * url,const apr_table_t * params)625 char* oidc_util_http_query_encoded_url(request_rec *r, const char *url,
626 const apr_table_t *params) {
627 char *result = NULL;
628 if ((params != NULL) && (apr_table_elts(params)->nelts > 0)) {
629 oidc_http_encode_t data = { r, NULL };
630 apr_table_do(oidc_util_http_add_form_url_encoded_param, &data, params,
631 NULL);
632 const char *sep = NULL;
633 if (data.encoded_params)
634 sep = strchr(url ? url : "", OIDC_CHAR_QUERY) != NULL ?
635 OIDC_STR_AMP :
636 OIDC_STR_QUERY;
637 result = apr_psprintf(r->pool, "%s%s%s", url, sep ? sep : "",
638 data.encoded_params ? data.encoded_params : "");
639 } else {
640 result = apr_pstrdup(r->pool, url);
641 }
642 oidc_debug(r, "url=%s", result);
643 return result;
644 }
645
646 /*
647 * construct form-encoded POST data
648 */
oidc_util_http_form_encoded_data(request_rec * r,const apr_table_t * params)649 char* oidc_util_http_form_encoded_data(request_rec *r,
650 const apr_table_t *params) {
651 char *data = NULL;
652 if ((params != NULL) && (apr_table_elts(params)->nelts > 0)) {
653 oidc_http_encode_t encode_data = { r, NULL };
654 apr_table_do(oidc_util_http_add_form_url_encoded_param, &encode_data,
655 params,
656 NULL);
657 data = encode_data.encoded_params;
658 }
659 oidc_debug(r, "data=%s", data);
660 return data;
661 }
662
663 /*
664 * execute a HTTP (GET or POST) request
665 */
oidc_util_http_call(request_rec * r,const char * url,const char * data,const char * content_type,const char * basic_auth,const char * bearer_token,int ssl_validate_server,char ** response,int timeout,const char * outgoing_proxy,apr_array_header_t * pass_cookies,const char * ssl_cert,const char * ssl_key)666 static apr_byte_t oidc_util_http_call(request_rec *r, const char *url,
667 const char *data, const char *content_type, const char *basic_auth,
668 const char *bearer_token, int ssl_validate_server, char **response,
669 int timeout, const char *outgoing_proxy,
670 apr_array_header_t *pass_cookies, const char *ssl_cert,
671 const char *ssl_key) {
672 char curlError[CURL_ERROR_SIZE];
673 oidc_curl_buffer curlBuffer;
674 CURL *curl;
675 struct curl_slist *h_list = NULL;
676 int i;
677 oidc_cfg *c = ap_get_module_config(r->server->module_config,
678 &auth_openidc_module);
679
680 /* do some logging about the inputs */
681 oidc_debug(r,
682 "url=%s, data=%s, content_type=%s, basic_auth=%s, bearer_token=%s, ssl_validate_server=%d, timeout=%d, outgoing_proxy=%s, pass_cookies=%pp, ssl_cert=%s, ssl_key=%s",
683 url, data, content_type, basic_auth ? "****" : "null", bearer_token,
684 ssl_validate_server, timeout, outgoing_proxy, pass_cookies,
685 ssl_cert, ssl_key);
686
687 curl = curl_easy_init();
688 if (curl == NULL) {
689 oidc_error(r, "curl_easy_init() error");
690 return FALSE;
691 }
692
693 /* set the error buffer as empty before performing a request */
694 curlError[0] = 0;
695
696 /* some of these are not really required */
697 curl_easy_setopt(curl, CURLOPT_HEADER, 0L);
698 curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 1L);
699 curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1L);
700 curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, curlError);
701 curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
702 curl_easy_setopt(curl, CURLOPT_MAXREDIRS, 5L);
703
704 /* set the timeout */
705 curl_easy_setopt(curl, CURLOPT_TIMEOUT, timeout);
706
707 /* setup the buffer where the response will be written to */
708 curlBuffer.r = r;
709 curlBuffer.memory = NULL;
710 curlBuffer.size = 0;
711 curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, oidc_curl_write);
712 curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void* )&curlBuffer);
713
714 #ifndef LIBCURL_NO_CURLPROTO
715 curl_easy_setopt(curl, CURLOPT_REDIR_PROTOCOLS,
716 CURLPROTO_HTTP|CURLPROTO_HTTPS);
717 curl_easy_setopt(curl, CURLOPT_PROTOCOLS, CURLPROTO_HTTP|CURLPROTO_HTTPS);
718 #endif
719
720 /* set the options for validating the SSL server certificate that the remote site presents */
721 curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER,
722 (ssl_validate_server != FALSE ? 1L : 0L));
723 curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST,
724 (ssl_validate_server != FALSE ? 2L : 0L));
725
726 #if LIBCURL_VERSION_NUM >= 0x071900
727 if (r->subprocess_env != NULL) {
728 const char *env_var_value = apr_table_get(r->subprocess_env,
729 "CURLOPT_SSL_OPTIONS");
730 if (env_var_value != NULL) {
731 oidc_debug(r, "SSL options environment variable %s=%s found",
732 "CURLOPT_SSL_OPTIONS", env_var_value);
733 if (strstr(env_var_value, "CURLSSLOPT_ALLOW_BEAST")) {
734 oidc_debug(r,
735 "curl_easy_setopt CURLOPT_SSL_OPTIONS CURLSSLOPT_ALLOW_BEAST");
736 curl_easy_setopt(curl, CURLOPT_SSL_OPTIONS,
737 CURLSSLOPT_ALLOW_BEAST);
738 }
739 #if LIBCURL_VERSION_NUM >= 0x072c00
740 if (strstr(env_var_value, "CURLSSLOPT_NO_REVOKE")) {
741 oidc_debug(r,
742 "curl_easy_setopt CURLOPT_SSL_OPTIONS CURLSSLOPT_NO_REVOKE");
743 curl_easy_setopt(curl, CURLOPT_SSL_OPTIONS,
744 CURLSSLOPT_NO_REVOKE);
745 }
746 #endif
747 #if LIBCURL_VERSION_NUM >= 0x074400
748 if (strstr(env_var_value, "CURLSSLOPT_NO_PARTIALCHAIN")) {
749 oidc_debug(r,
750 "curl_easy_setopt CURLOPT_SSL_OPTIONS CURLSSLOPT_NO_PARTIALCHAIN");
751 curl_easy_setopt(curl, CURLOPT_SSL_OPTIONS,
752 CURLSSLOPT_NO_PARTIALCHAIN);
753 }
754 #endif
755 #if LIBCURL_VERSION_NUM >= 0x074600
756 if (strstr(env_var_value, "CURLSSLOPT_REVOKE_BEST_EFFORT")) {
757 oidc_debug(r,
758 "curl_easy_setopt CURLOPT_SSL_OPTIONS CURLSSLOPT_REVOKE_BEST_EFFORT");
759 curl_easy_setopt(curl, CURLOPT_SSL_OPTIONS,
760 CURLSSLOPT_REVOKE_BEST_EFFORT);
761 }
762 #endif
763 #if LIBCURL_VERSION_NUM >= 0x074700
764 if (strstr(env_var_value, "CURLSSLOPT_NATIVE_CA")) {
765 oidc_debug(r,
766 "curl_easy_setopt CURLOPT_SSL_OPTIONS CURLSSLOPT_NATIVE_CA");
767 curl_easy_setopt(curl, CURLOPT_SSL_OPTIONS,
768 CURLSSLOPT_NATIVE_CA);
769 }
770 #endif
771 }
772 }
773 #endif
774
775 if (c->ca_bundle_path != NULL)
776 curl_easy_setopt(curl, CURLOPT_CAINFO, c->ca_bundle_path);
777
778 #ifdef WIN32
779 else {
780 DWORD buflen;
781 char *ptr = NULL;
782 char *retval = (char *) malloc(sizeof (TCHAR) * (MAX_PATH + 1));
783 retval[0] = '\0';
784 buflen = SearchPath(NULL, "curl-ca-bundle.crt", NULL, MAX_PATH+1, retval, &ptr);
785 if (buflen > 0)
786 curl_easy_setopt(curl, CURLOPT_CAINFO, retval);
787 else
788 oidc_warn(r, "no curl-ca-bundle.crt file found in path");
789 free(retval);
790 }
791 #endif
792
793 /* identify this HTTP client */
794 curl_easy_setopt(curl, CURLOPT_USERAGENT, "mod_auth_openidc");
795
796 /* set optional outgoing proxy for the local network */
797 if (outgoing_proxy) {
798 curl_easy_setopt(curl, CURLOPT_PROXY, outgoing_proxy);
799 }
800
801 /* see if we need to add token in the Bearer Authorization header */
802 if (bearer_token != NULL) {
803 h_list = curl_slist_append(h_list,
804 apr_psprintf(r->pool, "Authorization: Bearer %s",
805 bearer_token));
806 }
807
808 /* see if we need to perform HTTP basic authentication to the remote site */
809 if (basic_auth != NULL) {
810 curl_easy_setopt(curl, CURLOPT_HTTPAUTH, CURLAUTH_BASIC);
811 curl_easy_setopt(curl, CURLOPT_USERPWD, basic_auth);
812 }
813
814 if (ssl_cert != NULL)
815 curl_easy_setopt(curl, CURLOPT_SSLCERT, ssl_cert);
816 if (ssl_key != NULL)
817 curl_easy_setopt(curl, CURLOPT_SSLKEY, ssl_key);
818
819 if (data != NULL) {
820 /* set POST data */
821 curl_easy_setopt(curl, CURLOPT_POSTFIELDS, data);
822 /* set HTTP method to POST */
823 curl_easy_setopt(curl, CURLOPT_POST, 1);
824 }
825
826 if (content_type != NULL) {
827 /* set content type */
828 h_list = curl_slist_append(h_list,
829 apr_psprintf(r->pool, "%s: %s", OIDC_HTTP_HDR_CONTENT_TYPE,
830 content_type));
831 }
832
833 /* see if we need to add any custom headers */
834 if (h_list != NULL)
835 curl_easy_setopt(curl, CURLOPT_HTTPHEADER, h_list);
836
837 if (pass_cookies != NULL) {
838 /* gather cookies that we need to pass on from the incoming request */
839 char *cookie_string = NULL;
840 for (i = 0; i < pass_cookies->nelts; i++) {
841 const char *cookie_name = ((const char**) pass_cookies->elts)[i];
842 char *cookie_value = oidc_util_get_cookie(r, cookie_name);
843 if (cookie_value != NULL) {
844 cookie_string =
845 (cookie_string == NULL) ?
846 apr_psprintf(r->pool, "%s=%s", cookie_name,
847 cookie_value) :
848 apr_psprintf(r->pool, "%s; %s=%s",
849 cookie_string, cookie_name,
850 cookie_value);
851 }
852 }
853
854 /* see if we need to pass any cookies */
855 if (cookie_string != NULL) {
856 oidc_debug(r, "passing browser cookies on backend call: %s",
857 cookie_string);
858 curl_easy_setopt(curl, CURLOPT_COOKIE, cookie_string);
859 }
860 }
861
862 /* set the target URL */
863 curl_easy_setopt(curl, CURLOPT_URL, url);
864
865 /* call it and record the result */
866 int rv = TRUE;
867 if (curl_easy_perform(curl) != CURLE_OK) {
868 oidc_error(r, "curl_easy_perform() failed on: %s (%s)", url,
869 curlError[0] ? curlError : "");
870 rv = FALSE;
871 goto out;
872 }
873
874 long response_code;
875 curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &response_code);
876 oidc_debug(r, "HTTP response code=%ld", response_code);
877
878 *response = apr_pstrmemdup(r->pool, curlBuffer.memory, curlBuffer.size);
879
880 /* set and log the response */
881 oidc_debug(r, "response=%s", *response ? *response : "");
882
883 out:
884
885 /* cleanup and return the result */
886 if (h_list != NULL)
887 curl_slist_free_all(h_list);
888 curl_easy_cleanup(curl);
889
890 return rv;
891 }
892
893 /*
894 * execute HTTP GET request
895 */
oidc_util_http_get(request_rec * r,const char * url,const apr_table_t * params,const char * basic_auth,const char * bearer_token,int ssl_validate_server,char ** response,int timeout,const char * outgoing_proxy,apr_array_header_t * pass_cookies,const char * ssl_cert,const char * ssl_key)896 apr_byte_t oidc_util_http_get(request_rec *r, const char *url,
897 const apr_table_t *params, const char *basic_auth,
898 const char *bearer_token, int ssl_validate_server, char **response,
899 int timeout, const char *outgoing_proxy,
900 apr_array_header_t *pass_cookies, const char *ssl_cert,
901 const char *ssl_key) {
902 char *query_url = oidc_util_http_query_encoded_url(r, url, params);
903 return oidc_util_http_call(r, query_url, NULL, NULL, basic_auth,
904 bearer_token, ssl_validate_server, response, timeout,
905 outgoing_proxy, pass_cookies, ssl_cert, ssl_key);
906 }
907
908 /*
909 * execute HTTP POST request with form-encoded data
910 */
oidc_util_http_post_form(request_rec * r,const char * url,const apr_table_t * params,const char * basic_auth,const char * bearer_token,int ssl_validate_server,char ** response,int timeout,const char * outgoing_proxy,apr_array_header_t * pass_cookies,const char * ssl_cert,const char * ssl_key)911 apr_byte_t oidc_util_http_post_form(request_rec *r, const char *url,
912 const apr_table_t *params, const char *basic_auth,
913 const char *bearer_token, int ssl_validate_server, char **response,
914 int timeout, const char *outgoing_proxy,
915 apr_array_header_t *pass_cookies, const char *ssl_cert,
916 const char *ssl_key) {
917 char *data = oidc_util_http_form_encoded_data(r, params);
918 return oidc_util_http_call(r, url, data,
919 OIDC_CONTENT_TYPE_FORM_ENCODED, basic_auth, bearer_token,
920 ssl_validate_server, response, timeout, outgoing_proxy,
921 pass_cookies, ssl_cert, ssl_key);
922 }
923
924 /*
925 * execute HTTP POST request with JSON-encoded data
926 */
oidc_util_http_post_json(request_rec * r,const char * url,json_t * json,const char * basic_auth,const char * bearer_token,int ssl_validate_server,char ** response,int timeout,const char * outgoing_proxy,apr_array_header_t * pass_cookies,const char * ssl_cert,const char * ssl_key)927 apr_byte_t oidc_util_http_post_json(request_rec *r, const char *url,
928 json_t *json, const char *basic_auth, const char *bearer_token,
929 int ssl_validate_server, char **response, int timeout,
930 const char *outgoing_proxy, apr_array_header_t *pass_cookies,
931 const char *ssl_cert, const char *ssl_key) {
932 char *data =
933 json != NULL ?
934 oidc_util_encode_json_object(r, json, JSON_COMPACT) : NULL;
935 return oidc_util_http_call(r, url, data, OIDC_CONTENT_TYPE_JSON, basic_auth,
936 bearer_token, ssl_validate_server, response, timeout,
937 outgoing_proxy, pass_cookies, ssl_cert, ssl_key);
938 }
939
940 /*
941 * get the current path from the request in a normalized way
942 */
oidc_util_get_path(request_rec * r)943 static char* oidc_util_get_path(request_rec *r) {
944 size_t i;
945 char *p;
946 p = r->parsed_uri.path;
947 if ((p == NULL) || (p[0] == '\0'))
948 return apr_pstrdup(r->pool, OIDC_STR_FORWARD_SLASH);
949 for (i = strlen(p) - 1; i > 0; i--)
950 if (p[i] == OIDC_CHAR_FORWARD_SLASH)
951 break;
952 return apr_pstrndup(r->pool, p, i + 1);
953 }
954
955 /*
956 * get the cookie path setting and check that it matches the request path; cook it up if it is not set
957 */
oidc_util_get_cookie_path(request_rec * r)958 static char* oidc_util_get_cookie_path(request_rec *r) {
959 char *rv = NULL, *requestPath = oidc_util_get_path(r);
960 char *cookie_path = oidc_cfg_dir_cookie_path(r);
961 if (cookie_path != NULL) {
962 if (strncmp(cookie_path, requestPath, strlen(cookie_path)) == 0)
963 rv = cookie_path;
964 else {
965 oidc_warn(r,
966 "" OIDCCookiePath " (%s) is not a substring of request path, using request path (%s) for cookie",
967 cookie_path, requestPath);
968 rv = requestPath;
969 }
970 } else {
971 rv = requestPath;
972 }
973 return (rv);
974 }
975
976 #define OIDC_COOKIE_FLAG_DOMAIN "Domain"
977 #define OIDC_COOKIE_FLAG_PATH "Path"
978 #define OIDC_COOKIE_FLAG_EXPIRES "Expires"
979 #define OIDC_COOKIE_FLAG_SECURE "Secure"
980 #define OIDC_COOKIE_FLAG_HTTP_ONLY "HttpOnly"
981
982 #define OIDC_COOKIE_MAX_SIZE 4093
983
984 #define OIDC_SET_COOKIE_APPEND_ENV_VAR "OIDC_SET_COOKIE_APPEND"
985
oidc_util_set_cookie_append_value(request_rec * r,oidc_cfg * c)986 const char* oidc_util_set_cookie_append_value(request_rec *r, oidc_cfg *c) {
987 const char *env_var_value = NULL;
988
989 if (r->subprocess_env != NULL)
990 env_var_value = apr_table_get(r->subprocess_env,
991 OIDC_SET_COOKIE_APPEND_ENV_VAR);
992
993 if (env_var_value == NULL) {
994 oidc_debug(r, "no cookie append environment variable %s found",
995 OIDC_SET_COOKIE_APPEND_ENV_VAR);
996 return NULL;
997 }
998
999 oidc_debug(r, "cookie append environment variable %s=%s found",
1000 OIDC_SET_COOKIE_APPEND_ENV_VAR, env_var_value);
1001
1002 return env_var_value;
1003 }
1004
oidc_util_request_is_secure(request_rec * r)1005 apr_byte_t oidc_util_request_is_secure(request_rec *r) {
1006 return (apr_strnatcasecmp("https", oidc_get_current_url_scheme(r)) == 0);
1007 }
1008
1009 /*
1010 * set a cookie in the HTTP response headers
1011 */
oidc_util_set_cookie(request_rec * r,const char * cookieName,const char * cookieValue,apr_time_t expires,const char * ext)1012 void oidc_util_set_cookie(request_rec *r, const char *cookieName,
1013 const char *cookieValue, apr_time_t expires, const char *ext) {
1014
1015 oidc_cfg *c = ap_get_module_config(r->server->module_config,
1016 &auth_openidc_module);
1017 char *headerString, *expiresString = NULL;
1018 const char *appendString = NULL;
1019
1020 /* see if we need to clear the cookie */
1021 if (apr_strnatcmp(cookieValue, "") == 0)
1022 expires = 0;
1023
1024 /* construct the expire value */
1025 if (expires != -1) {
1026 expiresString = (char*) apr_pcalloc(r->pool, APR_RFC822_DATE_LEN);
1027 if (apr_rfc822_date(expiresString, expires) != APR_SUCCESS) {
1028 oidc_error(r, "could not set cookie expiry date");
1029 }
1030 }
1031
1032 /* construct the cookie value */
1033 headerString = apr_psprintf(r->pool, "%s=%s", cookieName, cookieValue);
1034
1035 headerString = apr_psprintf(r->pool, "%s; %s=%s", headerString,
1036 OIDC_COOKIE_FLAG_PATH, oidc_util_get_cookie_path(r));
1037
1038 if (expiresString != NULL)
1039 headerString = apr_psprintf(r->pool, "%s; %s=%s", headerString,
1040 OIDC_COOKIE_FLAG_EXPIRES, expiresString);
1041
1042 if (c->cookie_domain != NULL)
1043 headerString = apr_psprintf(r->pool, "%s; %s=%s", headerString,
1044 OIDC_COOKIE_FLAG_DOMAIN, c->cookie_domain);
1045
1046 if (oidc_util_request_is_secure(r))
1047 headerString = apr_psprintf(r->pool, "%s; %s", headerString,
1048 OIDC_COOKIE_FLAG_SECURE);
1049
1050 if (c->cookie_http_only != FALSE)
1051 headerString = apr_psprintf(r->pool, "%s; %s", headerString,
1052 OIDC_COOKIE_FLAG_HTTP_ONLY);
1053
1054 appendString = oidc_util_set_cookie_append_value(r, c);
1055 if (appendString != NULL)
1056 headerString = apr_psprintf(r->pool, "%s; %s", headerString,
1057 appendString);
1058 else if (ext != NULL)
1059 headerString = apr_psprintf(r->pool, "%s; %s", headerString, ext);
1060
1061 /* sanity check on overall cookie value size */
1062 if (strlen(headerString) > OIDC_COOKIE_MAX_SIZE) {
1063 oidc_warn(r,
1064 "the length of the cookie value (%d) is greater than %d(!) bytes, this may not work with all browsers/server combinations: consider switching to a server side caching!",
1065 (int )strlen(headerString), OIDC_COOKIE_MAX_SIZE);
1066 }
1067
1068 /* use r->err_headers_out so we always print our headers (even on 302 redirect) - headers_out only prints on 2xx responses */
1069 oidc_util_hdr_err_out_add(r, OIDC_HTTP_HDR_SET_COOKIE, headerString);
1070 }
1071
1072 /*
1073 * get a cookie from the HTTP request
1074 */
oidc_util_get_cookie(request_rec * r,const char * cookieName)1075 char* oidc_util_get_cookie(request_rec *r, const char *cookieName) {
1076 char *cookie, *tokenizerCtx, *rv = NULL;
1077
1078 /* get the Cookie value */
1079 char *cookies = apr_pstrdup(r->pool, oidc_util_hdr_in_cookie_get(r));
1080
1081 if (cookies != NULL) {
1082
1083 /* tokenize on ; to find the cookie we want */
1084 cookie = apr_strtok(cookies, OIDC_STR_SEMI_COLON, &tokenizerCtx);
1085
1086 while (cookie != NULL) {
1087
1088 while (*cookie == OIDC_CHAR_SPACE)
1089 cookie++;
1090
1091 /* see if we've found the cookie that we're looking for */
1092 if ((strncmp(cookie, cookieName, strlen(cookieName)) == 0)
1093 && (cookie[strlen(cookieName)] == OIDC_CHAR_EQUAL)) {
1094
1095 /* skip to the meat of the parameter (the value after the '=') */
1096 cookie += (strlen(cookieName) + 1);
1097 rv = apr_pstrdup(r->pool, cookie);
1098
1099 break;
1100 }
1101
1102 /* go to the next cookie */
1103 cookie = apr_strtok(NULL, OIDC_STR_SEMI_COLON, &tokenizerCtx);
1104 }
1105 }
1106
1107 /* log what we've found */
1108 oidc_debug(r, "returning \"%s\" = %s", cookieName,
1109 rv ? apr_psprintf(r->pool, "\"%s\"", rv) : "<null>");
1110
1111 return rv;
1112 }
1113
1114 #define OIDC_COOKIE_CHUNKS_SEPARATOR "_"
1115 #define OIDC_COOKIE_CHUNKS_POSTFIX "chunks"
1116
1117 /*
1118 * get the name of the cookie that contains the number of chunks
1119 */
oidc_util_get_chunk_count_name(request_rec * r,const char * cookieName)1120 static char* oidc_util_get_chunk_count_name(request_rec *r,
1121 const char *cookieName) {
1122 return apr_psprintf(r->pool, "%s%s%s", cookieName,
1123 OIDC_COOKIE_CHUNKS_SEPARATOR, OIDC_COOKIE_CHUNKS_POSTFIX);
1124 }
1125
1126 /*
1127 * get the number of cookie chunks set by the browser
1128 */
oidc_util_get_chunked_count(request_rec * r,const char * cookieName)1129 static int oidc_util_get_chunked_count(request_rec *r, const char *cookieName) {
1130 int chunkCount = 0;
1131 char *chunkCountValue = oidc_util_get_cookie(r,
1132 oidc_util_get_chunk_count_name(r, cookieName));
1133 if (chunkCountValue != NULL) {
1134 char *endptr = NULL;
1135 chunkCount = strtol(chunkCountValue, &endptr, 10);
1136 if ((*chunkCountValue == '\0') || (*endptr != '\0'))
1137 chunkCount = 0;
1138 }
1139 return chunkCount;
1140 }
1141
1142 /*
1143 * get the name of a chunk
1144 */
oidc_util_get_chunk_cookie_name(request_rec * r,const char * cookieName,int i)1145 static char* oidc_util_get_chunk_cookie_name(request_rec *r,
1146 const char *cookieName, int i) {
1147 return apr_psprintf(r->pool, "%s%s%d", cookieName,
1148 OIDC_COOKIE_CHUNKS_SEPARATOR, i);
1149 }
1150
1151 /*
1152 * get a cookie value that is split over a number of chunked cookies
1153 */
oidc_util_get_chunked_cookie(request_rec * r,const char * cookieName,int chunkSize)1154 char* oidc_util_get_chunked_cookie(request_rec *r, const char *cookieName,
1155 int chunkSize) {
1156 char *cookieValue = NULL;
1157 char *chunkValue = NULL;
1158 int i = 0;
1159 if (chunkSize == 0) {
1160 cookieValue = oidc_util_get_cookie(r, cookieName);
1161 } else {
1162 int chunkCount = oidc_util_get_chunked_count(r, cookieName);
1163 if (chunkCount > 0) {
1164 cookieValue = "";
1165 for (i = 0; i < chunkCount; i++) {
1166 chunkValue = oidc_util_get_cookie(r,
1167 oidc_util_get_chunk_cookie_name(r, cookieName, i));
1168 if (chunkValue != NULL)
1169 cookieValue = apr_psprintf(r->pool, "%s%s", cookieValue,
1170 chunkValue);
1171 }
1172 } else {
1173 cookieValue = oidc_util_get_cookie(r, cookieName);
1174 }
1175 }
1176 return cookieValue;
1177 }
1178
1179 /*
1180 * unset all chunked cookies, including the counter cookie, if they exist
1181 */
oidc_util_clear_chunked_cookie(request_rec * r,const char * cookieName,apr_time_t expires,const char * ext)1182 static void oidc_util_clear_chunked_cookie(request_rec *r,
1183 const char *cookieName, apr_time_t expires, const char *ext) {
1184 int i = 0;
1185 int chunkCount = oidc_util_get_chunked_count(r, cookieName);
1186 if (chunkCount > 0) {
1187 for (i = 0; i < chunkCount; i++)
1188 oidc_util_set_cookie(r,
1189 oidc_util_get_chunk_cookie_name(r, cookieName, i), "",
1190 expires, ext);
1191 oidc_util_set_cookie(r, oidc_util_get_chunk_count_name(r, cookieName),
1192 "", expires, ext);
1193 }
1194 }
1195
1196 /*
1197 * set a cookie value that is split over a number of chunked cookies
1198 */
oidc_util_set_chunked_cookie(request_rec * r,const char * cookieName,const char * cookieValue,apr_time_t expires,int chunkSize,const char * ext)1199 void oidc_util_set_chunked_cookie(request_rec *r, const char *cookieName,
1200 const char *cookieValue, apr_time_t expires, int chunkSize,
1201 const char *ext) {
1202 int i = 0;
1203 int cookieLength = strlen(cookieValue);
1204 char *chunkValue = NULL;
1205
1206 /* see if we need to chunk at all */
1207 if ((chunkSize == 0)
1208 || ((cookieLength > 0) && (cookieLength < chunkSize))) {
1209 oidc_util_set_cookie(r, cookieName, cookieValue, expires, ext);
1210 oidc_util_clear_chunked_cookie(r, cookieName, expires, ext);
1211 return;
1212 }
1213
1214 /* see if we need to clear a possibly chunked cookie */
1215 if (cookieLength == 0) {
1216 oidc_util_set_cookie(r, cookieName, "", expires, ext);
1217 oidc_util_clear_chunked_cookie(r, cookieName, expires, ext);
1218 return;
1219 }
1220
1221 /* set a chunked cookie */
1222 int chunkCountValue = cookieLength / chunkSize + 1;
1223 const char *ptr = cookieValue;
1224 for (i = 0; i < chunkCountValue; i++) {
1225 chunkValue = apr_pstrndup(r->pool, ptr, chunkSize);
1226 ptr += chunkSize;
1227 oidc_util_set_cookie(r,
1228 oidc_util_get_chunk_cookie_name(r, cookieName, i), chunkValue,
1229 expires, ext);
1230 };
1231 oidc_util_set_cookie(r, oidc_util_get_chunk_count_name(r, cookieName),
1232 apr_psprintf(r->pool, "%d", chunkCountValue), expires, ext);
1233 oidc_util_set_cookie(r, cookieName, "", expires, ext);
1234 }
1235
1236 /*
1237 * normalize a string for use as an HTTP Header Name. Any invalid
1238 * characters (per http://tools.ietf.org/html/rfc2616#section-4.2 and
1239 * http://tools.ietf.org/html/rfc2616#section-2.2) are replaced with
1240 * a dash ('-') character.
1241 */
oidc_normalize_header_name(const request_rec * r,const char * str)1242 char* oidc_normalize_header_name(const request_rec *r, const char *str) {
1243 /* token = 1*<any CHAR except CTLs or separators>
1244 * CTL = <any US-ASCII control character
1245 * (octets 0 - 31) and DEL (127)>
1246 * separators = "(" | ")" | "<" | ">" | "@"
1247 * | "," | ";" | ":" | "\" | <">
1248 * | "/" | "[" | "]" | "?" | "="
1249 * | "{" | "}" | SP | HT */
1250 const char *separators = "()<>@,;:\\\"/[]?={} \t";
1251
1252 char *ns = apr_pstrdup(r->pool, str);
1253 size_t i;
1254 for (i = 0; i < strlen(ns); i++) {
1255 if (ns[i] < 32 || ns[i] == 127)
1256 ns[i] = '-';
1257 else if (strchr(separators, ns[i]) != NULL)
1258 ns[i] = '-';
1259 }
1260 return ns;
1261 }
1262
1263 /*
1264 * see if the currently accessed path matches a path from a defined URL
1265 */
oidc_util_request_matches_url(request_rec * r,const char * url)1266 apr_byte_t oidc_util_request_matches_url(request_rec *r, const char *url) {
1267 apr_uri_t uri;
1268 memset(&uri, 0, sizeof(apr_uri_t));
1269 if ((url == NULL) || (apr_uri_parse(r->pool, url, &uri) != APR_SUCCESS))
1270 return FALSE;
1271 oidc_debug(r, "comparing \"%s\"==\"%s\"", r->parsed_uri.path, uri.path);
1272 if ((r->parsed_uri.path == NULL) || (uri.path == NULL))
1273 return (r->parsed_uri.path == uri.path);
1274 return (apr_strnatcmp(r->parsed_uri.path, uri.path) == 0);
1275 }
1276
1277 /*
1278 * see if the currently accessed path has a certain query parameter
1279 */
oidc_util_request_has_parameter(request_rec * r,const char * param)1280 apr_byte_t oidc_util_request_has_parameter(request_rec *r, const char *param) {
1281 if (r->args == NULL)
1282 return FALSE;
1283 const char *option1 = apr_psprintf(r->pool, "%s=", param);
1284 const char *option2 = apr_psprintf(r->pool, "&%s=", param);
1285 return ((strstr(r->args, option1) == r->args)
1286 || (strstr(r->args, option2) != NULL)) ? TRUE : FALSE;
1287 }
1288
1289 /*
1290 * get a query parameter
1291 */
oidc_util_get_request_parameter(request_rec * r,char * name,char ** value)1292 apr_byte_t oidc_util_get_request_parameter(request_rec *r, char *name,
1293 char **value) {
1294 char *tokenizer_ctx, *p, *args;
1295 const char *k_param = apr_psprintf(r->pool, "%s=", name);
1296 const size_t k_param_sz = strlen(k_param);
1297
1298 *value = NULL;
1299
1300 if (r->args == NULL || strlen(r->args) == 0)
1301 return FALSE;
1302
1303 /* not sure why we do this, but better be safe than sorry */
1304 args = apr_pstrmemdup(r->pool, r->args, strlen(r->args));
1305
1306 p = apr_strtok(args, OIDC_STR_AMP, &tokenizer_ctx);
1307 do {
1308 if (p && strncmp(p, k_param, k_param_sz) == 0) {
1309 *value = apr_pstrdup(r->pool, p + k_param_sz);
1310 *value = oidc_util_unescape_string(r, *value);
1311 }
1312 p = apr_strtok(NULL, OIDC_STR_AMP, &tokenizer_ctx);
1313 } while (p);
1314
1315 return (*value != NULL ? TRUE : FALSE);
1316 }
1317
1318 /*
1319 * printout a JSON string value
1320 */
oidc_util_json_string_print(request_rec * r,json_t * result,const char * key,const char * log)1321 static apr_byte_t oidc_util_json_string_print(request_rec *r, json_t *result,
1322 const char *key, const char *log) {
1323 json_t *value = json_object_get(result, key);
1324 if (value != NULL && !json_is_null(value)) {
1325 oidc_error(r,
1326 "%s: response contained an \"%s\" entry with value: \"%s\"",
1327 log, key,
1328 oidc_util_encode_json_object(r, value, JSON_ENCODE_ANY));
1329 return TRUE;
1330 }
1331 return FALSE;
1332 }
1333
1334 /*
1335 * check a JSON object for "error" results and printout
1336 */
oidc_util_check_json_error(request_rec * r,json_t * json)1337 static apr_byte_t oidc_util_check_json_error(request_rec *r, json_t *json) {
1338 if (oidc_util_json_string_print(r, json, OIDC_PROTO_ERROR,
1339 "oidc_util_check_json_error") == TRUE) {
1340 oidc_util_json_string_print(r, json, OIDC_PROTO_ERROR_DESCRIPTION,
1341 "oidc_util_check_json_error");
1342 return TRUE;
1343 }
1344 return FALSE;
1345 }
1346
1347 #define OIDC_JSON_MAX_ERROR_STR 4096
1348
1349 /*
1350 * parse a JSON object
1351 */
oidc_util_decode_json_object(request_rec * r,const char * str,json_t ** json)1352 apr_byte_t oidc_util_decode_json_object(request_rec *r, const char *str,
1353 json_t **json) {
1354
1355 if (str == NULL)
1356 return FALSE;
1357
1358 json_error_t json_error;
1359 *json = json_loads(str, 0, &json_error);
1360
1361 /* decode the JSON contents of the buffer */
1362 if (*json == NULL) {
1363 /* something went wrong */
1364 #if JANSSON_VERSION_HEX >= 0x020B00
1365 if (json_error_code(&json_error) == json_error_null_character) {
1366 oidc_error(r, "JSON parsing returned an error: %s",
1367 json_error.text);
1368 } else {
1369 #endif
1370 oidc_error(r, "JSON parsing returned an error: %s (%s)",
1371 json_error.text,
1372 apr_pstrndup(r->pool, str, OIDC_JSON_MAX_ERROR_STR));
1373 #if JANSSON_VERSION_HEX >= 0x020B00
1374 }
1375 #endif
1376 return FALSE;
1377 }
1378
1379 if (!json_is_object(*json)) {
1380 /* oops, no JSON */
1381 oidc_error(r, "parsed JSON did not contain a JSON object");
1382 json_decref(*json);
1383 *json = NULL;
1384 return FALSE;
1385 }
1386
1387 return TRUE;
1388 }
1389
1390 /*
1391 * encode a JSON object
1392 */
oidc_util_encode_json_object(request_rec * r,json_t * json,size_t flags)1393 char* oidc_util_encode_json_object(request_rec *r, json_t *json, size_t flags) {
1394 char *s = json_dumps(json, flags);
1395 char *s_value = apr_pstrdup(r->pool, s);
1396 free(s);
1397 return s_value;
1398 }
1399
1400 /*
1401 * decode a JSON string, check for "error" results and printout
1402 */
oidc_util_decode_json_and_check_error(request_rec * r,const char * str,json_t ** json)1403 apr_byte_t oidc_util_decode_json_and_check_error(request_rec *r,
1404 const char *str, json_t **json) {
1405
1406 if (oidc_util_decode_json_object(r, str, json) == FALSE)
1407 return FALSE;
1408
1409 // see if it is not an error response somehow
1410 if (oidc_util_check_json_error(r, *json) == TRUE) {
1411 json_decref(*json);
1412 *json = NULL;
1413 return FALSE;
1414 }
1415
1416 return TRUE;
1417 }
1418
1419 /*
1420 * sends content to the user agent
1421 */
oidc_util_http_send(request_rec * r,const char * data,size_t data_len,const char * content_type,int success_rvalue)1422 int oidc_util_http_send(request_rec *r, const char *data, size_t data_len,
1423 const char *content_type, int success_rvalue) {
1424 ap_set_content_type(r, content_type);
1425 apr_bucket_brigade *bb = apr_brigade_create(r->pool,
1426 r->connection->bucket_alloc);
1427 apr_bucket *b = apr_bucket_transient_create(data, data_len,
1428 r->connection->bucket_alloc);
1429 APR_BRIGADE_INSERT_TAIL(bb, b);
1430 b = apr_bucket_eos_create(r->connection->bucket_alloc);
1431 APR_BRIGADE_INSERT_TAIL(bb, b);
1432 int rc = ap_pass_brigade(r->output_filters, bb);
1433 if (rc != APR_SUCCESS) {
1434 oidc_error(r,
1435 "ap_pass_brigade returned an error: %d; if you're using this module combined with mod_deflate try make an exception for the " OIDCRedirectURI " e.g. using SetEnvIf Request_URI <url> no-gzip",
1436 rc);
1437 return HTTP_INTERNAL_SERVER_ERROR;
1438 }
1439 //r->status = success_rvalue;
1440
1441 if ((success_rvalue == OK) && (r->user == NULL)) {
1442 /*
1443 * satisfy Apache 2.4 mod_authz_core:
1444 * prevent it to return HTTP 500 after sending content
1445 */
1446 r->user = "";
1447 }
1448
1449 return success_rvalue;
1450 }
1451
1452 /*
1453 * send HTML content to the user agent
1454 */
oidc_util_html_send(request_rec * r,const char * title,const char * html_head,const char * on_load,const char * html_body,int status_code)1455 int oidc_util_html_send(request_rec *r, const char *title,
1456 const char *html_head, const char *on_load, const char *html_body,
1457 int status_code) {
1458
1459 char *html =
1460 "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">\n"
1461 "<html>\n"
1462 " <head>\n"
1463 " <meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\n"
1464 " <title>%s</title>\n"
1465 " %s\n"
1466 " </head>\n"
1467 " <body%s>\n"
1468 "%s\n"
1469 " </body>\n"
1470 "</html>\n";
1471
1472 html = apr_psprintf(r->pool, html,
1473 title ? oidc_util_html_escape(r->pool, title) : "",
1474 html_head ? html_head : "",
1475 on_load ? apr_psprintf(r->pool, " onload=\"%s()\"", on_load) : "",
1476 html_body ? html_body : "<p></p>");
1477
1478 return oidc_util_http_send(r, html, strlen(html),
1479 OIDC_CONTENT_TYPE_TEXT_HTML, status_code);
1480 }
1481
1482 static char *html_error_template_contents = NULL;
1483
1484 /*
1485 * get the full path to a file based on an (already) absolute filename or a filename
1486 * that is relative to the Apache root directory
1487 */
oidc_util_get_full_path(apr_pool_t * pool,const char * abs_or_rel_filename)1488 char* oidc_util_get_full_path(apr_pool_t *pool, const char *abs_or_rel_filename) {
1489 return (abs_or_rel_filename) ?
1490 ap_server_root_relative(pool, abs_or_rel_filename) : NULL;
1491 }
1492
1493 /*
1494 * send a user-facing error to the browser
1495 */
oidc_util_html_send_error(request_rec * r,const char * html_template,const char * error,const char * description,int status_code)1496 int oidc_util_html_send_error(request_rec *r, const char *html_template,
1497 const char *error, const char *description, int status_code) {
1498
1499 char *html = "";
1500
1501 if (html_template != NULL) {
1502
1503 html_template = oidc_util_get_full_path(r->pool, html_template);
1504
1505 if (html_error_template_contents == NULL) {
1506 int rc = oidc_util_file_read(r, html_template,
1507 r->server->process->pool, &html_error_template_contents);
1508 if (rc == FALSE) {
1509 oidc_error(r, "could not read HTML error template: %s",
1510 html_template);
1511 html_error_template_contents = NULL;
1512 }
1513 }
1514
1515 if (html_error_template_contents) {
1516 html = apr_psprintf(r->pool, html_error_template_contents,
1517 oidc_util_html_escape(r->pool, error ? error : ""),
1518 oidc_util_html_escape(r->pool,
1519 description ? description : ""));
1520
1521 return oidc_util_http_send(r, html, strlen(html),
1522 OIDC_CONTENT_TYPE_TEXT_HTML, status_code);
1523 }
1524 }
1525
1526 if (error != NULL) {
1527 html = apr_psprintf(r->pool, "%s<p>Error: <pre>%s</pre></p>", html,
1528 oidc_util_html_escape(r->pool, error));
1529 }
1530 if (description != NULL) {
1531 html = apr_psprintf(r->pool, "%s<p>Description: <pre>%s</pre></p>",
1532 html, oidc_util_html_escape(r->pool, description));
1533 }
1534
1535 return oidc_util_html_send(r, "Error", NULL, NULL, html, status_code);
1536 }
1537
1538 /*
1539 * read all bytes from the HTTP request
1540 */
oidc_util_read(request_rec * r,char ** rbuf)1541 static apr_byte_t oidc_util_read(request_rec *r, char **rbuf) {
1542 apr_size_t bytes_read;
1543 apr_size_t bytes_left;
1544 apr_size_t len;
1545 long read_length;
1546
1547 if (ap_setup_client_block(r, REQUEST_CHUNKED_DECHUNK) != OK)
1548 return FALSE;
1549
1550 len = ap_should_client_block(r) ? r->remaining : 0;
1551
1552 if (len > OIDC_MAX_POST_DATA_LEN) {
1553 oidc_error(r, "POST parameter value is too large: %lu bytes (max=%d)",
1554 (unsigned long ) len, OIDC_MAX_POST_DATA_LEN);
1555 return FALSE;
1556 }
1557
1558 *rbuf = (char*) apr_palloc(r->pool, len + 1);
1559 if (*rbuf == NULL) {
1560 oidc_error(r, "could not allocate memory for %lu bytes of POST data.",
1561 (unsigned long )len);
1562 return FALSE;
1563 }
1564 (*rbuf)[len] = '\0';
1565
1566 bytes_read = 0;
1567 bytes_left = len;
1568 while (bytes_left > 0) {
1569 read_length = ap_get_client_block(r, &(*rbuf)[bytes_read], bytes_left);
1570 if (read_length == 0) {
1571 (*rbuf)[bytes_read] = '\0';
1572 break;
1573 } else if (read_length < 0) {
1574 oidc_error(r, "failed to read POST data from client");
1575 return FALSE;
1576 }
1577 bytes_read += read_length;
1578 bytes_left -= read_length;
1579 }
1580
1581 return TRUE;
1582 }
1583
1584 /*
1585 * read form-encoded parameters from a string in to a table
1586 */
oidc_util_read_form_encoded_params(request_rec * r,apr_table_t * table,char * data)1587 apr_byte_t oidc_util_read_form_encoded_params(request_rec *r,
1588 apr_table_t *table, char *data) {
1589 const char *key, *val, *p = data;
1590
1591 while (p && *p && (val = ap_getword(r->pool, &p, OIDC_CHAR_AMP))) {
1592 key = ap_getword(r->pool, &val, OIDC_CHAR_EQUAL);
1593 key = oidc_util_unescape_string(r, key);
1594 val = oidc_util_unescape_string(r, val);
1595 oidc_debug(r, "read: %s=%s", key, val);
1596 apr_table_set(table, key, val);
1597 }
1598
1599 oidc_debug(r, "parsed: %d bytes into %d elements",
1600 data ? (int )strlen(data) : 0, apr_table_elts(table)->nelts);
1601
1602 return TRUE;
1603 }
1604
oidc_userdata_set_post_param(request_rec * r,const char * post_param_name,const char * post_param_value)1605 static void oidc_userdata_set_post_param(request_rec *r,
1606 const char *post_param_name, const char *post_param_value) {
1607 apr_table_t *userdata_post_params = NULL;
1608 apr_pool_userdata_get((void**) &userdata_post_params,
1609 OIDC_USERDATA_POST_PARAMS_KEY, r->pool);
1610 if (userdata_post_params == NULL)
1611 userdata_post_params = apr_table_make(r->pool, 1);
1612 apr_table_set(userdata_post_params, post_param_name, post_param_value);
1613 apr_pool_userdata_set(userdata_post_params, OIDC_USERDATA_POST_PARAMS_KEY,
1614 NULL, r->pool);
1615
1616 }
1617
1618 /*
1619 * read the POST parameters in to a table
1620 */
oidc_util_read_post_params(request_rec * r,apr_table_t * table,apr_byte_t propagate,const char * strip_param_name)1621 apr_byte_t oidc_util_read_post_params(request_rec *r, apr_table_t *table,
1622 apr_byte_t propagate, const char *strip_param_name) {
1623 apr_byte_t rc = FALSE;
1624 char *data = NULL;
1625 const apr_array_header_t *arr = NULL;
1626 const apr_table_entry_t *elts = NULL;
1627 int i = 0;
1628 const char *content_type = NULL;
1629
1630 content_type = oidc_util_hdr_in_content_type_get(r);
1631 if ((r->method_number != M_POST) || (strstr(content_type,
1632 OIDC_CONTENT_TYPE_FORM_ENCODED) != content_type)) {
1633 oidc_debug(r, "required content-type %s not found",
1634 OIDC_CONTENT_TYPE_FORM_ENCODED);
1635 goto end;
1636 }
1637
1638 if (oidc_util_read(r, &data) != TRUE)
1639 goto end;
1640
1641 rc = oidc_util_read_form_encoded_params(r, table, data);
1642 if (rc != TRUE)
1643 goto end;
1644
1645 if (propagate == FALSE)
1646 goto end;
1647
1648 arr = apr_table_elts(table);
1649 elts = (const apr_table_entry_t*) arr->elts;
1650 for (i = 0; i < arr->nelts; i++)
1651 if (apr_strnatcmp(elts[i].key, strip_param_name) != 0)
1652 oidc_userdata_set_post_param(r, elts[i].key, elts[i].val);
1653
1654 end:
1655
1656 return rc;
1657 }
1658
1659 /*
1660 * read a file from a path on disk
1661 */
oidc_util_file_read(request_rec * r,const char * path,apr_pool_t * pool,char ** result)1662 apr_byte_t oidc_util_file_read(request_rec *r, const char *path,
1663 apr_pool_t *pool, char **result) {
1664 apr_file_t *fd = NULL;
1665 apr_status_t rc = APR_SUCCESS;
1666 char s_err[128];
1667 apr_finfo_t finfo;
1668
1669 /* open the file if it exists */
1670 if ((rc = apr_file_open(&fd, path, APR_FOPEN_READ | APR_FOPEN_BUFFERED,
1671 APR_OS_DEFAULT, r->pool)) != APR_SUCCESS) {
1672 oidc_warn(r, "no file found at: \"%s\"", path);
1673 return FALSE;
1674 }
1675
1676 /* the file exists, now lock it */
1677 apr_file_lock(fd, APR_FLOCK_EXCLUSIVE);
1678
1679 /* move the read pointer to the very start of the cache file */
1680 apr_off_t begin = 0;
1681 apr_file_seek(fd, APR_SET, &begin);
1682
1683 /* get the file info so we know its size */
1684 if ((rc = apr_file_info_get(&finfo, APR_FINFO_SIZE, fd)) != APR_SUCCESS) {
1685 oidc_error(r, "error calling apr_file_info_get on file: \"%s\" (%s)",
1686 path, apr_strerror(rc, s_err, sizeof(s_err)));
1687 goto error_close;
1688 }
1689
1690 /* now that we have the size of the file, allocate a buffer that can contain its contents */
1691 *result = apr_palloc(pool, finfo.size + 1);
1692
1693 /* read the file in to the buffer */
1694 apr_size_t bytes_read = 0;
1695 if ((rc = apr_file_read_full(fd, *result, finfo.size, &bytes_read))
1696 != APR_SUCCESS) {
1697 oidc_error(r, "apr_file_read_full on (%s) returned an error: %s", path,
1698 apr_strerror(rc, s_err, sizeof(s_err)));
1699 goto error_close;
1700 }
1701
1702 /* just to be sure, we set a \0 (we allocated space for it anyway) */
1703 (*result)[bytes_read] = '\0';
1704
1705 /* check that we've got all of it */
1706 if (bytes_read != finfo.size) {
1707 oidc_error(r,
1708 "apr_file_read_full on (%s) returned less bytes (%" APR_SIZE_T_FMT ") than expected: (%" APR_OFF_T_FMT ")",
1709 path, bytes_read, finfo.size);
1710 goto error_close;
1711 }
1712
1713 /* we're done, unlock and close the file */
1714 apr_file_unlock(fd);
1715 apr_file_close(fd);
1716
1717 /* log successful content retrieval */
1718 oidc_debug(r, "file read successfully \"%s\"", path);
1719
1720 return TRUE;
1721
1722 error_close:
1723
1724 apr_file_unlock(fd);
1725 apr_file_close(fd);
1726
1727 oidc_error(r, "return error");
1728
1729 return FALSE;
1730 }
1731
1732 /*
1733 * write data to a file
1734 */
oidc_util_file_write(request_rec * r,const char * path,const char * data)1735 apr_byte_t oidc_util_file_write(request_rec *r, const char *path,
1736 const char *data) {
1737
1738 apr_file_t *fd = NULL;
1739 apr_status_t rc = APR_SUCCESS;
1740 apr_size_t bytes_written = 0;
1741 char s_err[128];
1742
1743 /* try to open the metadata file for writing, creating it if it does not exist */
1744 if ((rc = apr_file_open(&fd, path,
1745 (APR_FOPEN_WRITE | APR_FOPEN_CREATE | APR_FOPEN_TRUNCATE),
1746 APR_OS_DEFAULT, r->pool)) != APR_SUCCESS) {
1747 oidc_error(r, "file \"%s\" could not be opened (%s)", path,
1748 apr_strerror(rc, s_err, sizeof(s_err)));
1749 return FALSE;
1750 }
1751
1752 /* lock the file and move the write pointer to the start of it */
1753 apr_file_lock(fd, APR_FLOCK_EXCLUSIVE);
1754 apr_off_t begin = 0;
1755 apr_file_seek(fd, APR_SET, &begin);
1756
1757 /* calculate the length of the data, which is a string length */
1758 apr_size_t len = strlen(data);
1759
1760 /* (blocking) write the number of bytes in the buffer */
1761 rc = apr_file_write_full(fd, data, len, &bytes_written);
1762
1763 /* check for a system error */
1764 if (rc != APR_SUCCESS) {
1765 oidc_error(r, "could not write to: \"%s\" (%s)", path,
1766 apr_strerror(rc, s_err, sizeof(s_err)));
1767 return FALSE;
1768 }
1769
1770 /* check that all bytes from the header were written */
1771 if (bytes_written != len) {
1772 oidc_error(r,
1773 "could not write enough bytes to: \"%s\", bytes_written (%" APR_SIZE_T_FMT ") != len (%" APR_SIZE_T_FMT ")",
1774 path, bytes_written, len);
1775 return FALSE;
1776 }
1777
1778 /* unlock and close the written file */
1779 apr_file_unlock(fd);
1780 apr_file_close(fd);
1781
1782 oidc_debug(r, "file \"%s\" written; number of bytes (%" APR_SIZE_T_FMT ")",
1783 path, len);
1784
1785 return TRUE;
1786 }
1787
1788 /*
1789 * see if two provided issuer identifiers match (cq. ignore trailing slash)
1790 */
oidc_util_issuer_match(const char * a,const char * b)1791 apr_byte_t oidc_util_issuer_match(const char *a, const char *b) {
1792
1793 /* check the "issuer" value against the one configure for the provider we got this id_token from */
1794 if (apr_strnatcmp(a, b) != 0) {
1795
1796 /* no strict match, but we are going to accept if the difference is only a trailing slash */
1797 int n1 = strlen(a);
1798 int n2 = strlen(b);
1799 int n = ((n1 == n2 + 1) && (a[n1 - 1] == OIDC_CHAR_FORWARD_SLASH)) ?
1800 n2 :
1801 (((n2 == n1 + 1) && (b[n2 - 1] == OIDC_CHAR_FORWARD_SLASH)) ?
1802 n1 : 0);
1803 if ((n == 0) || (strncmp(a, b, n) != 0))
1804 return FALSE;
1805 }
1806
1807 return TRUE;
1808 }
1809
1810 /*
1811 * see if a certain string value is part of a JSON array with string elements
1812 */
oidc_util_json_array_has_value(request_rec * r,json_t * haystack,const char * needle)1813 apr_byte_t oidc_util_json_array_has_value(request_rec *r, json_t *haystack,
1814 const char *needle) {
1815
1816 if ((haystack == NULL) || (!json_is_array(haystack)))
1817 return FALSE;
1818
1819 int i;
1820 for (i = 0; i < json_array_size(haystack); i++) {
1821 json_t *elem = json_array_get(haystack, i);
1822 if (!json_is_string(elem)) {
1823 oidc_error(r, "unhandled in-array JSON non-string object type [%d]",
1824 elem->type);
1825 continue;
1826 }
1827 if (apr_strnatcmp(json_string_value(elem), needle) == 0) {
1828 break;
1829 }
1830 }
1831
1832 // oidc_debug(r,
1833 // "returning (%d=%d)", i,
1834 // haystack->value.array->nelts);
1835
1836 return (i == json_array_size(haystack)) ? FALSE : TRUE;
1837 }
1838
1839 /*
1840 * set a HTTP header and/or environment variable to pass information to the application
1841 */
oidc_util_set_app_info(request_rec * r,const char * s_key,const char * s_value,const char * claim_prefix,apr_byte_t as_header,apr_byte_t as_env_var,apr_byte_t base64url)1842 void oidc_util_set_app_info(request_rec *r, const char *s_key,
1843 const char *s_value, const char *claim_prefix, apr_byte_t as_header,
1844 apr_byte_t as_env_var, apr_byte_t base64url) {
1845
1846 /* construct the header name, cq. put the prefix in front of a normalized key name */
1847 const char *s_name = apr_psprintf(r->pool, "%s%s", claim_prefix,
1848 oidc_normalize_header_name(r, s_key));
1849 char *d_value = NULL;
1850
1851 if (as_header) {
1852 if ((base64url == TRUE) && (s_value != NULL)) {
1853 oidc_base64url_encode(r, &d_value, s_value, strlen(s_value), TRUE);
1854 }
1855 oidc_util_hdr_in_set(r, s_name, (d_value != NULL) ? d_value : s_value);
1856 }
1857
1858 if (as_env_var) {
1859
1860 /* do some logging about this event */
1861 oidc_debug(r, "setting environment variable \"%s: %s\"", s_name,
1862 s_value);
1863
1864 apr_table_set(r->subprocess_env, s_name, s_value);
1865 }
1866 }
1867
1868 /*
1869 * set the user/claims information from the session in HTTP headers passed on to the application
1870 */
oidc_util_set_app_infos(request_rec * r,const json_t * j_attrs,const char * claim_prefix,const char * claim_delimiter,apr_byte_t as_header,apr_byte_t as_env_var,apr_byte_t base64url)1871 void oidc_util_set_app_infos(request_rec *r, const json_t *j_attrs,
1872 const char *claim_prefix, const char *claim_delimiter,
1873 apr_byte_t as_header, apr_byte_t as_env_var, apr_byte_t base64url) {
1874
1875 char s_int[255];
1876 json_t *j_value = NULL;
1877 const char *s_key = NULL;
1878
1879 /* if not attributes are set, nothing needs to be done */
1880 if (j_attrs == NULL) {
1881 oidc_debug(r, "no attributes to set");
1882 return;
1883 }
1884
1885 /* loop over the claims in the JSON structure */
1886 void *iter = json_object_iter((json_t*) j_attrs);
1887 while (iter) {
1888
1889 /* get the next key/value entry */
1890 s_key = json_object_iter_key(iter);
1891 j_value = json_object_iter_value(iter);
1892
1893 // char *s_value= json_dumps(j_value, JSON_ENCODE_ANY);
1894 // oidc_util_set_app_info(r, s_key, s_value, claim_prefix);
1895 // free(s_value);
1896
1897 /* check if it is a single value string */
1898 if (json_is_string(j_value)) {
1899
1900 /* set the single string in the application header whose name is based on the key and the prefix */
1901 oidc_util_set_app_info(r, s_key, json_string_value(j_value),
1902 claim_prefix, as_header, as_env_var, base64url);
1903
1904 } else if (json_is_boolean(j_value)) {
1905
1906 /* set boolean value in the application header whose name is based on the key and the prefix */
1907 oidc_util_set_app_info(r, s_key,
1908 (json_is_true(j_value) ? "1" : "0"), claim_prefix,
1909 as_header, as_env_var, base64url);
1910
1911 } else if (json_is_integer(j_value)) {
1912
1913 if (sprintf(s_int, "%ld", (long) json_integer_value(j_value)) > 0) {
1914 /* set long value in the application header whose name is based on the key and the prefix */
1915 oidc_util_set_app_info(r, s_key, s_int, claim_prefix, as_header,
1916 as_env_var, base64url);
1917 } else {
1918 oidc_warn(r,
1919 "could not convert JSON number to string (> 255 characters?), skipping");
1920 }
1921
1922 } else if (json_is_real(j_value)) {
1923
1924 /* set float value in the application header whose name is based on the key and the prefix */
1925 oidc_util_set_app_info(r, s_key,
1926 apr_psprintf(r->pool, "%lf", json_real_value(j_value)),
1927 claim_prefix, as_header, as_env_var, base64url);
1928
1929 } else if (json_is_object(j_value)) {
1930
1931 /* set json value in the application header whose name is based on the key and the prefix */
1932 oidc_util_set_app_info(r, s_key,
1933 oidc_util_encode_json_object(r, j_value, 0), claim_prefix,
1934 as_header, as_env_var, base64url);
1935
1936 /* check if it is a multi-value string */
1937 } else if (json_is_array(j_value)) {
1938
1939 /* some logging about what we're going to do */
1940 oidc_debug(r,
1941 "parsing attribute array for key \"%s\" (#nr-of-elems: %llu)",
1942 s_key, (unsigned long long )json_array_size(j_value));
1943
1944 /* string to hold the concatenated array string values */
1945 char *s_concat = apr_pstrdup(r->pool, "");
1946 size_t i = 0;
1947
1948 /* loop over the array */
1949 for (i = 0; i < json_array_size(j_value); i++) {
1950
1951 /* get the current element */
1952 json_t *elem = json_array_get(j_value, i);
1953
1954 /* check if it is a string */
1955 if (json_is_string(elem)) {
1956
1957 /* concatenate the string to the s_concat value using the configured separator char */
1958 // TODO: escape the delimiter in the values (maybe reuse/extract url-formatted code from oidc_session_identity_encode)
1959 if (apr_strnatcmp(s_concat, "") != 0) {
1960 s_concat = apr_psprintf(r->pool, "%s%s%s", s_concat,
1961 claim_delimiter, json_string_value(elem));
1962 } else {
1963 s_concat = apr_psprintf(r->pool, "%s",
1964 json_string_value(elem));
1965 }
1966
1967 } else if (json_is_boolean(elem)) {
1968
1969 if (apr_strnatcmp(s_concat, "") != 0) {
1970 s_concat = apr_psprintf(r->pool, "%s%s%s", s_concat,
1971 claim_delimiter,
1972 json_is_true(elem) ? "1" : "0");
1973 } else {
1974 s_concat = apr_psprintf(r->pool, "%s",
1975 json_is_true(elem) ? "1" : "0");
1976 }
1977
1978 } else {
1979
1980 /* don't know how to handle a non-string array element */
1981 oidc_warn(r,
1982 "unhandled in-array JSON object type [%d] for key \"%s\" when parsing claims array elements",
1983 elem->type, s_key);
1984 }
1985 }
1986
1987 /* set the concatenated string */
1988 oidc_util_set_app_info(r, s_key, s_concat, claim_prefix, as_header,
1989 as_env_var, base64url);
1990
1991 } else {
1992
1993 /* no string and no array, so unclear how to handle this */
1994 oidc_warn(r,
1995 "unhandled JSON object type [%d] for key \"%s\" when parsing claims",
1996 j_value->type, s_key);
1997 }
1998
1999 iter = json_object_iter_next((json_t*) j_attrs, iter);
2000 }
2001 }
2002
2003 /*
2004 * parse a space separated string in to a hash table
2005 */
oidc_util_spaced_string_to_hashtable(apr_pool_t * pool,const char * str)2006 apr_hash_t* oidc_util_spaced_string_to_hashtable(apr_pool_t *pool,
2007 const char *str) {
2008 char *val;
2009 const char *data = apr_pstrdup(pool, str);
2010 apr_hash_t *result = apr_hash_make(pool);
2011 while (*data && (val = ap_getword_white(pool, &data))) {
2012 apr_hash_set(result, val, APR_HASH_KEY_STRING, val);
2013 }
2014 return result;
2015 }
2016
2017 /*
2018 * compare two space separated value types
2019 */
oidc_util_spaced_string_equals(apr_pool_t * pool,const char * a,const char * b)2020 apr_byte_t oidc_util_spaced_string_equals(apr_pool_t *pool, const char *a,
2021 const char *b) {
2022
2023 /* parse both entries as hash tables */
2024 apr_hash_t *ht_a = oidc_util_spaced_string_to_hashtable(pool, a);
2025 apr_hash_t *ht_b = oidc_util_spaced_string_to_hashtable(pool, b);
2026
2027 /* first compare the length of both response_types */
2028 if (apr_hash_count(ht_a) != apr_hash_count(ht_b))
2029 return FALSE;
2030
2031 /* then loop over all entries */
2032 apr_hash_index_t *hi;
2033 for (hi = apr_hash_first(NULL, ht_a); hi; hi = apr_hash_next(hi)) {
2034 const char *k;
2035 const char *v;
2036 apr_hash_this(hi, (const void**) &k, NULL, (void**) &v);
2037 if (apr_hash_get(ht_b, k, APR_HASH_KEY_STRING) == NULL)
2038 return FALSE;
2039 }
2040
2041 /* if we've made it this far, a an b are equal in length and every element in a is in b */
2042 return TRUE;
2043 }
2044
2045 /*
2046 * see if a particular value is part of a space separated value
2047 */
oidc_util_spaced_string_contains(apr_pool_t * pool,const char * str,const char * match)2048 apr_byte_t oidc_util_spaced_string_contains(apr_pool_t *pool, const char *str,
2049 const char *match) {
2050 apr_hash_t *ht = oidc_util_spaced_string_to_hashtable(pool, str);
2051 return (apr_hash_get(ht, match, APR_HASH_KEY_STRING) != NULL);
2052 }
2053
2054 /*
2055 * get (optional) string from a JSON object
2056 */
oidc_json_object_get_string(apr_pool_t * pool,json_t * json,const char * name,char ** value,const char * default_value)2057 apr_byte_t oidc_json_object_get_string(apr_pool_t *pool, json_t *json,
2058 const char *name, char **value, const char *default_value) {
2059 *value = default_value ? apr_pstrdup(pool, default_value) : NULL;
2060 if (json != NULL) {
2061 json_t *v = json_object_get(json, name);
2062 if ((v != NULL) && (json_is_string(v))) {
2063 *value = apr_pstrdup(pool, json_string_value(v));
2064 }
2065 }
2066 return TRUE;
2067 }
2068
2069 /*
2070 * get (optional) int from a JSON object
2071 */
oidc_json_object_get_int(apr_pool_t * pool,json_t * json,const char * name,int * value,const int default_value)2072 apr_byte_t oidc_json_object_get_int(apr_pool_t *pool, json_t *json,
2073 const char *name, int *value, const int default_value) {
2074 *value = default_value;
2075 if (json != NULL) {
2076 json_t *v = json_object_get(json, name);
2077 if ((v != NULL) && (json_is_integer(v))) {
2078 *value = json_integer_value(v);
2079 }
2080 }
2081 return TRUE;
2082 }
2083
2084 /*
2085 * get (optional) boolean from a JSON object
2086 */
oidc_json_object_get_bool(apr_pool_t * pool,json_t * json,const char * name,int * value,const int default_value)2087 apr_byte_t oidc_json_object_get_bool(apr_pool_t *pool, json_t *json,
2088 const char *name, int *value, const int default_value) {
2089 *value = default_value;
2090 if (json != NULL) {
2091 json_t *v = json_object_get(json, name);
2092 if ((v != NULL) && (json_is_boolean(v))) {
2093 *value = json_is_true(v);
2094 return TRUE;
2095 }
2096 }
2097 return FALSE;
2098 }
2099
2100 /*
2101 * merge two JSON objects
2102 */
oidc_util_json_merge(request_rec * r,json_t * src,json_t * dst)2103 apr_byte_t oidc_util_json_merge(request_rec *r, json_t *src, json_t *dst) {
2104
2105 const char *key;
2106 json_t *value = NULL;
2107 void *iter = NULL;
2108
2109 if ((src == NULL) || (dst == NULL))
2110 return FALSE;
2111
2112 oidc_debug(r, "src=%s, dst=%s",
2113 oidc_util_encode_json_object(r, src, JSON_COMPACT),
2114 oidc_util_encode_json_object(r, dst, JSON_COMPACT));
2115
2116 iter = json_object_iter(src);
2117 while (iter) {
2118 key = json_object_iter_key(iter);
2119 value = json_object_iter_value(iter);
2120 json_object_set(dst, key, value);
2121 iter = json_object_iter_next(src, iter);
2122 }
2123
2124 oidc_debug(r, "result dst=%s",
2125 oidc_util_encode_json_object(r, dst, JSON_COMPACT));
2126
2127 return TRUE;
2128 }
2129
2130 /*
2131 * add query encoded parameters to a table
2132 */
oidc_util_table_add_query_encoded_params(apr_pool_t * pool,apr_table_t * table,const char * params)2133 void oidc_util_table_add_query_encoded_params(apr_pool_t *pool,
2134 apr_table_t *table, const char *params) {
2135 if (params != NULL) {
2136 const char *key, *val;
2137 const char *p = params;
2138 while (*p && (val = ap_getword(pool, &p, OIDC_CHAR_AMP))) {
2139 key = ap_getword(pool, &val, OIDC_CHAR_EQUAL);
2140 ap_unescape_url((char*) key);
2141 ap_unescape_url((char*) val);
2142 apr_table_add(table, key, val);
2143 }
2144 }
2145 }
2146
2147 /*
2148 * create a symmetric key from a client_secret
2149 */
oidc_util_create_symmetric_key(request_rec * r,const char * client_secret,unsigned int r_key_len,const char * hash_algo,apr_byte_t set_kid,oidc_jwk_t ** jwk)2150 apr_byte_t oidc_util_create_symmetric_key(request_rec *r,
2151 const char *client_secret, unsigned int r_key_len,
2152 const char *hash_algo, apr_byte_t set_kid, oidc_jwk_t **jwk) {
2153 oidc_jose_error_t err;
2154 unsigned char *key = NULL;
2155 unsigned int key_len;
2156
2157 if ((client_secret != NULL) && (strlen(client_secret) > 0)) {
2158
2159 if (hash_algo == NULL) {
2160 key = (unsigned char*) client_secret;
2161 key_len = strlen(client_secret);
2162 } else {
2163 /* hash the client_secret first, this is OpenID Connect specific */
2164 oidc_jose_hash_bytes(r->pool, hash_algo,
2165 (const unsigned char*) client_secret, strlen(client_secret),
2166 &key, &key_len, &err);
2167 }
2168
2169 if ((key != NULL) && (key_len > 0)) {
2170 if ((r_key_len != 0) && (key_len >= r_key_len))
2171 key_len = r_key_len;
2172 oidc_debug(r, "key_len=%d", key_len);
2173 *jwk = oidc_jwk_create_symmetric_key(r->pool, NULL, key, key_len,
2174 set_kid, &err);
2175 }
2176
2177 if (*jwk == NULL) {
2178 oidc_error(r, "could not create JWK from the provided secret: %s",
2179 oidc_jose_e2s(r->pool, err));
2180 return FALSE;
2181 }
2182 }
2183
2184 return TRUE;
2185 }
2186
2187 /*
2188 * merge provided keys and client secret in to a single hashtable
2189 */
oidc_util_merge_symmetric_key(apr_pool_t * pool,const apr_array_header_t * keys,oidc_jwk_t * jwk)2190 apr_hash_t* oidc_util_merge_symmetric_key(apr_pool_t *pool,
2191 const apr_array_header_t *keys, oidc_jwk_t *jwk) {
2192 apr_hash_t *result = apr_hash_make(pool);
2193 int i = 0;
2194 if (keys != NULL) {
2195 for (i = 0; i < keys->nelts; i++) {
2196 const oidc_jwk_t *elem = ((const oidc_jwk_t**) keys->elts)[i];
2197 apr_hash_set(result, elem->kid, APR_HASH_KEY_STRING, elem);
2198 }
2199 }
2200 if (jwk != NULL) {
2201 apr_hash_set(result, jwk->kid, APR_HASH_KEY_STRING, jwk);
2202 }
2203 return result;
2204 }
2205
2206 /*
2207 * openssl hash and base64 encode
2208 */
oidc_util_hash_string_and_base64url_encode(request_rec * r,const char * openssl_hash_algo,const char * input,char ** output)2209 apr_byte_t oidc_util_hash_string_and_base64url_encode(request_rec *r,
2210 const char *openssl_hash_algo, const char *input, char **output) {
2211 oidc_jose_error_t err;
2212 unsigned char *hashed = NULL;
2213 unsigned int hashed_len = 0;
2214 if (oidc_jose_hash_bytes(r->pool, openssl_hash_algo,
2215 (const unsigned char*) input, strlen(input), &hashed, &hashed_len,
2216 &err) == FALSE) {
2217 oidc_error(r, "oidc_jose_hash_bytes returned an error: %s", err.text);
2218 return FALSE;
2219 }
2220
2221 if (oidc_base64url_encode(r, output, (const char*) hashed, hashed_len, TRUE)
2222 <= 0) {
2223 oidc_error(r, "oidc_base64url_encode returned an error: %s", err.text);
2224 return FALSE;
2225 }
2226 return TRUE;
2227 }
2228
2229 /*
2230 * merge two key sets
2231 */
oidc_util_merge_key_sets(apr_pool_t * pool,apr_hash_t * k1,const apr_array_header_t * k2)2232 apr_hash_t* oidc_util_merge_key_sets(apr_pool_t *pool, apr_hash_t *k1,
2233 const apr_array_header_t *k2) {
2234 apr_hash_t *rv = k1 ? apr_hash_copy(pool, k1) : apr_hash_make(pool);
2235 int i = 0;
2236 if (k2 != NULL) {
2237 for (i = 0; i < k2->nelts; i++) {
2238 const oidc_jwk_t *jwk = ((const oidc_jwk_t**) k2->elts)[i];
2239 apr_hash_set(rv, jwk->kid, APR_HASH_KEY_STRING, jwk);
2240 }
2241 }
2242 return rv;
2243 }
2244
oidc_util_merge_key_sets_hash(apr_pool_t * pool,apr_hash_t * k1,apr_hash_t * k2)2245 apr_hash_t* oidc_util_merge_key_sets_hash(apr_pool_t *pool, apr_hash_t *k1,
2246 apr_hash_t *k2) {
2247 if (k1 == NULL) {
2248 if (k2 == NULL)
2249 return apr_hash_make(pool);
2250 return k2;
2251 }
2252 if (k2 == NULL)
2253 return k1;
2254 return apr_hash_overlay(pool, k1, k2);
2255 }
2256
2257 /*
2258 * regexp substitute
2259 * Example:
2260 * regex: "^.*([0-9]+).*$"
2261 * replace: "$1"
2262 * text_original: "match 292 numbers"
2263 * text_replaced: "292"
2264 */
oidc_util_regexp_substitute(apr_pool_t * pool,const char * input,const char * regexp,const char * replace,char ** output,char ** error_str)2265 apr_byte_t oidc_util_regexp_substitute(apr_pool_t *pool, const char *input,
2266 const char *regexp, const char *replace, char **output,
2267 char **error_str) {
2268
2269 const char *errorptr = NULL;
2270 int erroffset;
2271 char *substituted = NULL;
2272 apr_byte_t rc = FALSE;
2273
2274 pcre *preg = pcre_compile(regexp, 0, &errorptr, &erroffset, NULL);
2275 if (preg == NULL) {
2276 *error_str = apr_psprintf(pool,
2277 "pattern [%s] is not a valid regular expression", regexp);
2278 goto out;
2279 }
2280
2281 if (strlen(input) >= OIDC_PCRE_MAXCAPTURE - 1) {
2282 *error_str =
2283 apr_psprintf(pool,
2284 "string length (%d) is larger than the maximum allowed for pcre_subst (%d)",
2285 (int) strlen(input), OIDC_PCRE_MAXCAPTURE - 1);
2286 goto out;
2287 }
2288
2289 substituted = pcre_subst(preg, NULL, input, (int) strlen(input), 0, 0,
2290 replace);
2291 if (substituted == NULL) {
2292 *error_str =
2293 apr_psprintf(pool,
2294 "unknown error could not match string [%s] using pattern [%s] and replace matches in [%s]",
2295 input, regexp, replace);
2296 goto out;
2297 }
2298
2299 *output = apr_pstrdup(pool, substituted);
2300 rc = TRUE;
2301
2302 out: if (substituted)
2303 pcre_free(substituted);
2304 if (preg)
2305 pcre_free(preg);
2306
2307 return rc;
2308 }
2309
2310 /*
2311 * regexp match
2312 */
2313 #define OIDC_UTIL_REGEXP_MATCH_SIZE 30
2314 #define OIDC_UTIL_REGEXP_MATCH_NR 1
2315
oidc_util_regexp_first_match(apr_pool_t * pool,const char * input,const char * regexp,char ** output,char ** error_str)2316 apr_byte_t oidc_util_regexp_first_match(apr_pool_t *pool, const char *input,
2317 const char *regexp, char **output, char **error_str) {
2318 const char *errorptr = NULL;
2319 int erroffset;
2320 int rc = 0;
2321 int subStr[OIDC_UTIL_REGEXP_MATCH_SIZE];
2322 const char *psubStrMatchStr = NULL;
2323 apr_byte_t rv = FALSE;
2324
2325 pcre *preg = pcre_compile(regexp, 0, &errorptr, &erroffset, NULL);
2326 if (preg == NULL) {
2327 *error_str = apr_psprintf(pool,
2328 "pattern [%s] is not a valid regular expression", regexp);
2329 goto out;
2330 }
2331
2332 if ((rc = pcre_exec(preg, NULL, input, (int) strlen(input), 0, 0, subStr,
2333 OIDC_UTIL_REGEXP_MATCH_SIZE)) < 0) {
2334 switch (rc) {
2335 case PCRE_ERROR_NOMATCH:
2336 *error_str = apr_pstrdup(pool, "string did not match the pattern");
2337 break;
2338 case PCRE_ERROR_NULL:
2339 *error_str = apr_pstrdup(pool, "something was null");
2340 break;
2341 case PCRE_ERROR_BADOPTION:
2342 *error_str = apr_pstrdup(pool, "a bad option was passed");
2343 break;
2344 case PCRE_ERROR_BADMAGIC:
2345 *error_str = apr_pstrdup(pool,
2346 "magic number bad (compiled re corrupt?)");
2347 break;
2348 case PCRE_ERROR_UNKNOWN_NODE:
2349 *error_str = apr_pstrdup(pool,
2350 "something kooky in the compiled re");
2351 break;
2352 case PCRE_ERROR_NOMEMORY:
2353 *error_str = apr_pstrdup(pool, "ran out of memory");
2354 break;
2355 default:
2356 *error_str = apr_psprintf(pool, "unknown error: %d", rc);
2357 break;
2358 }
2359 goto out;
2360 }
2361
2362 if (output) {
2363
2364 if (pcre_get_substring(input, subStr, rc, OIDC_UTIL_REGEXP_MATCH_NR,
2365 &(psubStrMatchStr)) <= 0) {
2366 *error_str = apr_psprintf(pool, "pcre_get_substring failed (rc=%d)",
2367 rc);
2368 goto out;
2369 }
2370
2371 *output = apr_pstrdup(pool, psubStrMatchStr);
2372 }
2373
2374 rv = TRUE;
2375
2376 out:
2377
2378 if (psubStrMatchStr)
2379 pcre_free_substring(psubStrMatchStr);
2380 if (preg)
2381 pcre_free(preg);
2382
2383 return rv;
2384 }
2385
oidc_util_cookie_domain_valid(const char * hostname,char * cookie_domain)2386 int oidc_util_cookie_domain_valid(const char *hostname, char *cookie_domain) {
2387 char *p = NULL;
2388 char *check_cookie = cookie_domain;
2389 // Skip past the first char of a cookie_domain that starts
2390 // with a ".", ASCII 46
2391 if (check_cookie[0] == 46) {
2392 p = strstr(hostname, ++check_cookie);
2393 } else {
2394 p = strstr(hostname, check_cookie);
2395 }
2396 if ((p == NULL) || (apr_strnatcmp(check_cookie, p) != 0)) {
2397 return FALSE;
2398 }
2399 return TRUE;
2400 }
2401
oidc_util_hdr_in_get(const request_rec * r,const char * name)2402 static const char* oidc_util_hdr_in_get(const request_rec *r, const char *name) {
2403 const char *value = apr_table_get(r->headers_in, name);
2404 if (value)
2405 oidc_debug(r, "%s=%s", name, value);
2406 return value;
2407 }
2408
oidc_util_hdr_in_get_left_most_only(const request_rec * r,const char * name,const char * separator)2409 static const char* oidc_util_hdr_in_get_left_most_only(const request_rec *r,
2410 const char *name, const char *separator) {
2411 char *last = NULL;
2412 const char *value = oidc_util_hdr_in_get(r, name);
2413 if (value)
2414 return apr_strtok(apr_pstrdup(r->pool, value), separator, &last);
2415 return NULL;
2416 }
2417
oidc_util_hdr_in_contains(const request_rec * r,const char * name,const char * separator,const char postfix_separator,const char * needle)2418 static apr_byte_t oidc_util_hdr_in_contains(const request_rec *r,
2419 const char *name, const char *separator, const char postfix_separator,
2420 const char *needle) {
2421 char *ctx = NULL, *elem = NULL;
2422 const char *value = oidc_util_hdr_in_get(r, name);
2423 apr_byte_t rc = FALSE;
2424 if (value) {
2425 elem = apr_strtok(apr_pstrdup(r->pool, value), separator, &ctx);
2426 while (elem != NULL) {
2427 while (*elem == OIDC_CHAR_SPACE)
2428 elem++;
2429 if ((strncmp(elem, needle, strlen(needle)) == 0)
2430 && ((elem[strlen(needle)] == '\0')
2431 || (elem[strlen(needle)] == postfix_separator))) {
2432 rc = TRUE;
2433 break;
2434 }
2435 elem = apr_strtok(NULL, separator, &ctx);
2436 }
2437 }
2438 return rc;
2439 }
2440
oidc_util_hdr_table_set(const request_rec * r,apr_table_t * table,const char * name,const char * value)2441 static void oidc_util_hdr_table_set(const request_rec *r, apr_table_t *table,
2442 const char *name, const char *value) {
2443
2444 if (value != NULL) {
2445
2446 char *s_value = apr_pstrdup(r->pool, value);
2447
2448 /*
2449 * sanitize the header value by replacing line feeds with spaces
2450 * just like the Apache header input algorithms do for incoming headers
2451 *
2452 * this makes it impossible to have line feeds in values but that is
2453 * compliant with RFC 7230 (and impossible for regular headers due to Apache's
2454 * parsing of headers anyway) and fixes a security vulnerability on
2455 * overwriting/setting outgoing headers when used in proxy mode
2456 */
2457 char *p = NULL;
2458 while ((p = strchr(s_value, '\n')))
2459 *p = OIDC_CHAR_SPACE;
2460
2461 oidc_debug(r, "%s: %s", name, s_value);
2462 apr_table_set(table, name, s_value);
2463
2464 } else {
2465
2466 oidc_debug(r, "unset %s", name);
2467 apr_table_unset(table, name);
2468
2469 }
2470 }
2471
oidc_util_hdr_out_set(const request_rec * r,const char * name,const char * value)2472 static void oidc_util_hdr_out_set(const request_rec *r, const char *name,
2473 const char *value) {
2474 oidc_util_hdr_table_set(r, r->headers_out, name, value);
2475 }
2476
oidc_util_hdr_out_get(const request_rec * r,const char * name)2477 static const char* oidc_util_hdr_out_get(const request_rec *r, const char *name) {
2478 return apr_table_get(r->headers_out, name);
2479 }
2480
oidc_util_hdr_err_out_add(const request_rec * r,const char * name,const char * value)2481 void oidc_util_hdr_err_out_add(const request_rec *r, const char *name,
2482 const char *value) {
2483 oidc_debug(r, "%s: %s", name, value);
2484 apr_table_add(r->err_headers_out, name, value);
2485 }
2486
oidc_util_hdr_in_set(const request_rec * r,const char * name,const char * value)2487 void oidc_util_hdr_in_set(const request_rec *r, const char *name,
2488 const char *value) {
2489 oidc_util_hdr_table_set(r, r->headers_in, name, value);
2490 }
2491
oidc_util_hdr_in_cookie_get(const request_rec * r)2492 const char* oidc_util_hdr_in_cookie_get(const request_rec *r) {
2493 return oidc_util_hdr_in_get(r, OIDC_HTTP_HDR_COOKIE);
2494 }
2495
oidc_util_hdr_in_cookie_set(const request_rec * r,const char * value)2496 void oidc_util_hdr_in_cookie_set(const request_rec *r, const char *value) {
2497 oidc_util_hdr_in_set(r, OIDC_HTTP_HDR_COOKIE, value);
2498 }
2499
oidc_util_hdr_in_user_agent_get(const request_rec * r)2500 const char* oidc_util_hdr_in_user_agent_get(const request_rec *r) {
2501 return oidc_util_hdr_in_get(r, OIDC_HTTP_HDR_USER_AGENT);
2502 }
2503
oidc_util_hdr_in_x_forwarded_for_get(const request_rec * r)2504 const char* oidc_util_hdr_in_x_forwarded_for_get(const request_rec *r) {
2505 return oidc_util_hdr_in_get_left_most_only(r, OIDC_HTTP_HDR_X_FORWARDED_FOR,
2506 OIDC_STR_COMMA OIDC_STR_SPACE);
2507 }
2508
oidc_util_hdr_in_content_type_get(const request_rec * r)2509 const char* oidc_util_hdr_in_content_type_get(const request_rec *r) {
2510 return oidc_util_hdr_in_get(r, OIDC_HTTP_HDR_CONTENT_TYPE);
2511 }
2512
oidc_util_hdr_in_content_length_get(const request_rec * r)2513 const char* oidc_util_hdr_in_content_length_get(const request_rec *r) {
2514 return oidc_util_hdr_in_get(r, OIDC_HTTP_HDR_CONTENT_LENGTH);
2515 }
2516
oidc_util_hdr_in_x_requested_with_get(const request_rec * r)2517 const char* oidc_util_hdr_in_x_requested_with_get(const request_rec *r) {
2518 return oidc_util_hdr_in_get(r, OIDC_HTTP_HDR_X_REQUESTED_WITH);
2519 }
2520
oidc_util_hdr_in_accept_get(const request_rec * r)2521 const char* oidc_util_hdr_in_accept_get(const request_rec *r) {
2522 return oidc_util_hdr_in_get(r, OIDC_HTTP_HDR_ACCEPT);
2523 }
2524
oidc_util_hdr_in_accept_contains(const request_rec * r,const char * needle)2525 apr_byte_t oidc_util_hdr_in_accept_contains(const request_rec *r,
2526 const char *needle) {
2527 return oidc_util_hdr_in_contains(r, OIDC_HTTP_HDR_ACCEPT, OIDC_STR_COMMA,
2528 OIDC_CHAR_SEMI_COLON, needle);
2529 }
2530
oidc_util_hdr_in_authorization_get(const request_rec * r)2531 const char* oidc_util_hdr_in_authorization_get(const request_rec *r) {
2532 return oidc_util_hdr_in_get(r, OIDC_HTTP_HDR_AUTHORIZATION);
2533 }
2534
oidc_util_hdr_in_x_forwarded_proto_get(const request_rec * r)2535 const char* oidc_util_hdr_in_x_forwarded_proto_get(const request_rec *r) {
2536 return oidc_util_hdr_in_get_left_most_only(r,
2537 OIDC_HTTP_HDR_X_FORWARDED_PROTO, OIDC_STR_COMMA OIDC_STR_SPACE);
2538 }
2539
oidc_util_hdr_in_x_forwarded_port_get(const request_rec * r)2540 const char* oidc_util_hdr_in_x_forwarded_port_get(const request_rec *r) {
2541 return oidc_util_hdr_in_get_left_most_only(r,
2542 OIDC_HTTP_HDR_X_FORWARDED_PORT, OIDC_STR_COMMA OIDC_STR_SPACE);
2543 }
2544
oidc_util_hdr_in_x_forwarded_host_get(const request_rec * r)2545 const char* oidc_util_hdr_in_x_forwarded_host_get(const request_rec *r) {
2546 return oidc_util_hdr_in_get_left_most_only(r,
2547 OIDC_HTTP_HDR_X_FORWARDED_HOST, OIDC_STR_COMMA OIDC_STR_SPACE);
2548 }
2549
oidc_util_hdr_in_host_get(const request_rec * r)2550 const char* oidc_util_hdr_in_host_get(const request_rec *r) {
2551 return oidc_util_hdr_in_get(r, OIDC_HTTP_HDR_HOST);
2552 }
2553
oidc_util_hdr_out_location_set(const request_rec * r,const char * value)2554 void oidc_util_hdr_out_location_set(const request_rec *r, const char *value) {
2555 oidc_util_hdr_out_set(r, OIDC_HTTP_HDR_LOCATION, value);
2556 }
2557
oidc_util_hdr_out_location_get(const request_rec * r)2558 const char* oidc_util_hdr_out_location_get(const request_rec *r) {
2559 return oidc_util_hdr_out_get(r, OIDC_HTTP_HDR_LOCATION);
2560 }
2561
oidc_util_get_provided_token_binding_id(const request_rec * r)2562 const char* oidc_util_get_provided_token_binding_id(const request_rec *r) {
2563 const char *result = NULL;
2564 if (r->subprocess_env != NULL)
2565 result = apr_table_get(r->subprocess_env, OIDC_TB_CFG_PROVIDED_ENV_VAR);
2566 return result;
2567 }
2568
oidc_util_get_client_cert_fingerprint(request_rec * r)2569 const char* oidc_util_get_client_cert_fingerprint(request_rec *r) {
2570 const char *fingerprint = NULL;
2571
2572 if (r->subprocess_env == NULL)
2573 goto end;
2574
2575 fingerprint = apr_table_get(r->subprocess_env,
2576 OIDC_TB_CFG_FINGERPRINT_ENV_VAR);
2577 if (fingerprint == NULL) {
2578 oidc_debug(r, "no %s environment variable found",
2579 OIDC_TB_CFG_FINGERPRINT_ENV_VAR);
2580 goto end;
2581 }
2582
2583 end:
2584
2585 return fingerprint;
2586 }
2587
oidc_util_json_validate_cnf_tbh(request_rec * r,int token_binding_policy,const char * tbh_str)2588 apr_byte_t oidc_util_json_validate_cnf_tbh(request_rec *r,
2589 int token_binding_policy, const char *tbh_str) {
2590 const char *tbp_str = NULL;
2591 char *tbp = NULL;
2592 int tbp_len = -1;
2593 unsigned char *tbp_hash = NULL;
2594 unsigned int tbp_hash_len = -1;
2595 char *tbh = NULL;
2596 int tbh_len = -1;
2597
2598 tbp_str = oidc_util_get_provided_token_binding_id(r);
2599 if (tbp_str == NULL) {
2600 oidc_debug(r,
2601 "no Provided Token Binding ID environment variable found");
2602 goto out_err;
2603 }
2604
2605 tbp_len = oidc_base64url_decode(r->pool, &tbp, tbp_str);
2606 if (tbp_len <= 0) {
2607 oidc_warn(r,
2608 "Provided Token Binding ID environment variable could not be decoded");
2609 goto out_err;
2610 }
2611
2612 if (oidc_jose_hash_bytes(r->pool, OIDC_JOSE_ALG_SHA256,
2613 (const unsigned char*) tbp, tbp_len, &tbp_hash, &tbp_hash_len,
2614 NULL) == FALSE) {
2615 oidc_warn(r,
2616 "hashing Provided Token Binding ID environment variable failed");
2617 goto out_err;
2618 }
2619
2620 tbh_len = oidc_base64url_decode(r->pool, &tbh, tbh_str);
2621 if (tbh_len <= 0) {
2622 oidc_warn(r, "cnf[\"tbh\"] provided but it could not be decoded");
2623 goto out_err;
2624 }
2625
2626 if (tbp_hash_len != tbh_len) {
2627 oidc_warn(r,
2628 "hash length of provided token binding ID environment variable: %d does not match length of cnf[\"tbh\"]: %d",
2629 tbp_hash_len, tbh_len);
2630 goto out_err;
2631 }
2632
2633 if (memcmp(tbp_hash, tbh, tbh_len) != 0) {
2634 oidc_warn(r,
2635 "hash of provided token binding ID environment variable does not match cnf[\"tbh\"]");
2636 goto out_err;
2637 }
2638
2639 oidc_debug(r,
2640 "hash of provided token binding ID environment variable matches cnf[\"tbh\"]");
2641
2642 return TRUE;
2643
2644 out_err:
2645
2646 if (token_binding_policy == OIDC_TOKEN_BINDING_POLICY_OPTIONAL)
2647 return TRUE;
2648 if (token_binding_policy == OIDC_TOKEN_BINDING_POLICY_ENFORCED)
2649 return FALSE;
2650
2651 // token_binding_policy == OIDC_TOKEN_BINDING_POLICY_REQURIED
2652 return (tbp_str == NULL);
2653 }
2654
oidc_util_json_validate_cnf_x5t_s256(request_rec * r,int token_binding_policy,const char * x5t_256_str)2655 apr_byte_t oidc_util_json_validate_cnf_x5t_s256(request_rec *r,
2656 int token_binding_policy, const char *x5t_256_str) {
2657 const char *fingerprint = NULL;
2658
2659 fingerprint = oidc_util_get_client_cert_fingerprint(r);
2660 if (fingerprint == NULL) {
2661 oidc_debug(r, "no certificate (fingerprint) provided");
2662 goto out_err;
2663 }
2664
2665 if (apr_strnatcmp(fingerprint, x5t_256_str) != 0) {
2666 oidc_warn(r,
2667 "fingerprint of provided cert (%s) does not match cnf[\"x5t#S256\"] (%s)",
2668 fingerprint, x5t_256_str);
2669 goto out_err;
2670 }
2671
2672 oidc_debug(r, "fingerprint of provided cert (%s) matches cnf[\"x5t#S256\"]",
2673 fingerprint);
2674
2675 return TRUE;
2676
2677 out_err:
2678
2679 if (token_binding_policy == OIDC_TOKEN_BINDING_POLICY_OPTIONAL)
2680 return TRUE;
2681 if (token_binding_policy == OIDC_TOKEN_BINDING_POLICY_ENFORCED)
2682 return FALSE;
2683
2684 // token_binding_policy == OIDC_TOKEN_BINDING_POLICY_REQURIED
2685 return (fingerprint == NULL);
2686 }
2687
2688 /*
2689 * validate the "cnf" claim in a JWT payload
2690 */
oidc_util_json_validate_cnf(request_rec * r,json_t * jwt,int token_binding_policy)2691 apr_byte_t oidc_util_json_validate_cnf(request_rec *r, json_t *jwt,
2692 int token_binding_policy) {
2693 char *tbh_str = NULL;
2694
2695 oidc_debug(r, "enter: policy=%s",
2696 oidc_token_binding_policy2str(r->pool, token_binding_policy));
2697
2698 if (token_binding_policy == OIDC_TOKEN_BINDING_POLICY_DISABLED)
2699 return TRUE;
2700
2701 json_t *cnf = json_object_get(jwt, OIDC_CLAIM_CNF);
2702 if (cnf == NULL) {
2703 oidc_debug(r, "no \"%s\" claim found in the token", OIDC_CLAIM_CNF);
2704 goto out_err;
2705 }
2706
2707 oidc_jose_get_string(r->pool, cnf, OIDC_CLAIM_CNF_TBH, FALSE, &tbh_str,
2708 NULL);
2709 if (tbh_str != NULL)
2710 return oidc_util_json_validate_cnf_tbh(r, token_binding_policy, tbh_str);
2711
2712 oidc_jose_get_string(r->pool, cnf, OIDC_CLAIM_CNF_X5T_S256, FALSE, &tbh_str,
2713 NULL);
2714 if (tbh_str != NULL)
2715 return oidc_util_json_validate_cnf_x5t_s256(r, token_binding_policy,
2716 tbh_str);
2717
2718 oidc_debug(r,
2719 " \"%s\" claim found in the token but no \"%s\" or \"%s\" key found inside",
2720 OIDC_CLAIM_CNF, OIDC_CLAIM_CNF_TBH, OIDC_CLAIM_CNF_X5T_S256);
2721
2722 out_err:
2723
2724 if (token_binding_policy == OIDC_TOKEN_BINDING_POLICY_OPTIONAL)
2725 return TRUE;
2726 if (token_binding_policy == OIDC_TOKEN_BINDING_POLICY_ENFORCED)
2727 return FALSE;
2728
2729 // token_binding_policy == OIDC_TOKEN_BINDING_POLICY_REQUIRED
2730 // TODO: we don't know which token binding the client supports, do we ?
2731 return FALSE;
2732 }
2733