pax_global_header00006660000000000000000000000064147672173650014534gustar00rootroot0000000000000052 comment=d1d6a6535d1550d1ad267092980d8dcc7da793b0 mod_auth_openidc-2.4.16.10/000077500000000000000000000000001476721736500153465ustar00rootroot00000000000000mod_auth_openidc-2.4.16.10/.clang-format000066400000000000000000000003061476721736500177200ustar00rootroot00000000000000BasedOnStyle: LLVM ColumnLimit: 120 IndentWidth: 8 UseTab: Always BreakBeforeBraces: Attach AllowShortIfStatementsOnASingleLine: false IndentCaseLabels: false AllowShortFunctionsOnASingleLine: None mod_auth_openidc-2.4.16.10/.github/000077500000000000000000000000001476721736500167065ustar00rootroot00000000000000mod_auth_openidc-2.4.16.10/.github/ISSUE_TEMPLATE/000077500000000000000000000000001476721736500210715ustar00rootroot00000000000000mod_auth_openidc-2.4.16.10/.github/ISSUE_TEMPLATE/config.yml000066400000000000000000000004131476721736500230570ustar00rootroot00000000000000blank_issues_enabled: false contact_links: - name: Questions and Suggestions url: https://github.com/OpenIDC/mod_auth_openidc/discussions about: This issue tracker is not for end users, please provide your question/suggestion in the Discussions forum here. mod_auth_openidc-2.4.16.10/.github/workflows/000077500000000000000000000000001476721736500207435ustar00rootroot00000000000000mod_auth_openidc-2.4.16.10/.github/workflows/build.yml000066400000000000000000000011731476721736500225670ustar00rootroot00000000000000name: Build on: [push, pull_request] jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Dependencies run: | sudo apt-get update -y sudo apt-get install -y apache2-dev libcjose-dev libssl-dev check pkg-config sudo apt-get install -y libjansson-dev libcurl4-openssl-dev libhiredis-dev libpcre2-dev - name: Configure run: | ./autogen.sh ./configure - name: Make run: make - name: Test run: make check || (cat test-suite.log && exit -1) - name: Distcheck run: make distcheck DESTDIR=/tmp/mod_auth_openidc mod_auth_openidc-2.4.16.10/.github/workflows/codeql-analysis.yml000066400000000000000000000020151476721736500245540ustar00rootroot00000000000000name: "CodeQL" on: [push, pull_request] jobs: analyze: name: Analyze runs-on: ubuntu-latest permissions: actions: read contents: read security-events: write strategy: fail-fast: false matrix: language: [ 'cpp' ] # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed steps: - name: Checkout repository uses: actions/checkout@v4 - name: Install packages run: | sudo apt-get update sudo apt-get install -y apache2-dev libcjose-dev libssl-dev sudo apt-get install -y libjansson-dev libcurl4-openssl-dev libhiredis-dev - name: Initialize CodeQL uses: github/codeql-action/init@v3 with: languages: ${{ matrix.language }} - run: | ./autogen.sh ./configure make check - name: Perform CodeQL Analysis uses: github/codeql-action/analyze@v3 mod_auth_openidc-2.4.16.10/.github/workflows/coverity.yml000066400000000000000000000044351476721736500233400ustar00rootroot00000000000000name: Coverity on: schedule: - cron: '0 18 * * SUN' workflow_dispatch: #on: # push: # branches: [ master, coverity ] # pull_request: # types: [opened, synchronize, reopened] jobs: build: name: Build and analyze runs-on: ubuntu-latest if: github.repository == 'OpenIDC/mod_auth_openidc' steps: - uses: actions/checkout@v4 with: fetch-depth: 0 - name: Dependencies run: | sudo apt-get update -y sudo apt-get install -y apache2-dev libcjose-dev libssl-dev check pkg-config sudo apt-get install -y libjansson-dev libcurl4-openssl-dev libhiredis-dev libpcre2-dev - name: Download Coverity Build Tool env: TOKEN: ${{ secrets.COVERITY_SCAN_TOKEN }} run: | wget -q https://scan.coverity.com/download/cxx/linux64 --post-data "token=$TOKEN&project=OpenIDC%2Fmod_auth_openidc" -O cov-analysis-linux64.tar.gz mkdir cov-analysis-linux64 tar xzf cov-analysis-linux64.tar.gz --strip 1 -C cov-analysis-linux64 - name: Configure run: | ./autogen.sh ./configure - name: Make with cov-build run: | pwd export PATH=`pwd`/cov-analysis-linux64/bin:$PATH cov-build --dir cov-int make check - name: Submit to Coverity Scan env: TOKEN: ${{ secrets.COVERITY_SCAN_TOKEN }} run: | tar czvf mod_auth_openidc.tgz cov-int curl \ --form project=OpenIDC%2Fmod_auth_openidc \ --form token=$TOKEN \ --form email=hans.zandbelt@zmartzone.eu \ --form file=@mod_auth_openidc.tgz \ --form version=master \ --form description="`git rev-parse --abbrev-ref HEAD` `git rev-parse --short HEAD`" \ https://scan.coverity.com/builds?project=OpenIDC%2Fmod_auth_openidc # - name: Coverity Scan # uses: blackduck-inc/black-duck-security-scan@v2.0.0 # with: # coverity_url: ${{ vars.COVERITY_URL }} # coverity_project_name: ${{ vars.COVERITY_PROJECT_NAME }} # coverity_user: ${{ vars.COVERITY_USER }} # coverity_passphrase: ${{ secrets.COVERITY_PASSPHRASE }} # coverity_build_command: make all # coverity_clean_command: make clean mod_auth_openidc-2.4.16.10/.github/workflows/issues.yml000066400000000000000000000007721476721736500230070ustar00rootroot00000000000000on: issues: types: [opened, reopened] jobs: check: if: github.event.issue.user.login != 'zandbelt' runs-on: ubuntu-latest steps: - if: github.event.action == 'opened' uses: actions-ecosystem/action-add-labels@v1 name: Label Invalid with: labels: invalid - uses: peter-evans/close-issue@v2 name: Close Issue with: comment: "https://github.com/OpenIDC/mod_auth_openidc/wiki#20-why-is-my-ticket-closed-as-invalid" mod_auth_openidc-2.4.16.10/.github/workflows/sonarqube.yml.save000066400000000000000000000024261476721736500244260ustar00rootroot00000000000000name: SonarQube on: push: branches: - master pull_request: types: [opened, synchronize, reopened] jobs: build: name: Build and analyze runs-on: ubuntu-latest env: BUILD_WRAPPER_OUT_DIR: build_wrapper_output_directory # Directory where build-wrapper output will be placed steps: - uses: actions/checkout@v4 with: fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis - name: Dependencies run: | sudo apt-get update -y sudo apt-get install -y apache2-dev libcjose-dev libssl-dev check pkg-config sudo apt-get install -y libjansson-dev libcurl4-openssl-dev libhiredis-dev libpcre2-dev - name: Install sonar-scanner and build-wrapper uses: SonarSource/sonarcloud-github-c-cpp@v3 - name: Run build-wrapper run: | ./autogen.sh ./configure build-wrapper-linux-x86-64 --out-dir ${{ env.BUILD_WRAPPER_OUT_DIR }} make clean all - name: Run sonar-scanner env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} run: | sonar-scanner --define sonar.cfamily.compile-commands="${{ env.BUILD_WRAPPER_OUT_DIR }}/compile_commands.json" mod_auth_openidc-2.4.16.10/.gitignore000066400000000000000000000005351476721736500173410ustar00rootroot00000000000000/autom4te.cache/ /.vscode/ /.libs/ /.settings/ /m4/ /.project /.cproject /.dockerignore /aclocal.m4 /ar-lib /compile /config.guess /config.guess~ /config.log /config.status /config.sub /config.sub~ /configure /configure~ /depcomp /*.rpm /install-sh /install-sh~ /*.la /libtool /ltmain.sh /Makefile /Makefile.in /missing /test-driver /test-suite.log mod_auth_openidc-2.4.16.10/AUTHORS000066400000000000000000000102401476721736500164130ustar00rootroot00000000000000The primary author of mod_auth_openidc is: Hans Zandbelt Thanks to the following people for contributing to mod_auth_openidc by reporting bugs, providing fixes, suggesting useful features or other: Dániel SÜTTŐ Roland Hedberg Bill Simon Jim Fox Martin Srom Dejan Latinovic Hiroyuki Wada Gunnar Scherf Terrence Fleury Jeremy Archer Forkbomber Kanthi Vaidya szakharchenko John Bradley Stefano Vercelli David Bernick Joseph Bester Daniel Pfile Rebecka Gulliksson Ryan Kelly John R. Dennis steve-dave glatzert Amit Joshi Andy Curtis solsson drdivano AliceWonderMiscreations Wouter Hund Hans Keeler Moritz Schlarb remi-cc hihellobolke Horatiu Eugen Vlad cristichiru Bono de Visser Patrick Uiterwijk Marcel Kottmann timpuri Eldar Zaitov Gergan Penkov Florian Weimer Aaron Donovan Hans Petter Bieker archzone Petteri Stenius Lance Fannin Ricardo Martin Camarero Filip Vujicic Janusz Ulanowski Aimoto Norihito Andy Lindeman Stefan Wachter Paolo Battino absynth76 Aaron Jones Bryan Ingram Tim Deisser Peter Hurtenbach Paul Spangler Chris Pawling Matthias Fleschütz Harri Rautila Tatsuhiko Yasumatsu Adam Stadler Steffen Greber Iain Heggie Dirk Kok Meheni abg35 Nathan Neulinger Simon Studer juur sebastian-goeldi rajeevn1 Daan Bakker smanolache blackwhiser1 Ruediger Pluem Nikhil Chaudhari Quentin Gillet Brent van Laere Mads Freek Petersen Stefan Richter Mattias Åsander adg-mh David P. Discher ryanwilliamnicholls Dmitrii Ermakov tarteens mod_auth_openidc-2.4.16.10/ChangeLog000066400000000000000000003202221476721736500171210ustar00rootroot0000000000000003/21/2025 - core: complete case-insensitive protocol/hostname/domain-name comparisons - release 2.4.16.10 03/20/2025 - core: compare hostnames and domains in a case insensitive way in: oidc_request_check_cookie_domain oidc_util_cookie_domain_valid oidc_validate_redirect_url oidc_cfg_parse_is_valid_url_scheme oidc_discovery_target_link_uri_match - cookie: fix oidc_util_cookie_domain_valid so that it checks the incoming request against OIDCCookieDomain rather than the OIDCRedirectURI and displays the correct error message if they don't match - bump to 2.4.16.10dev 03/19/2025 - release 2.4.16.9 03/18/2025 - cookie: use case insensitive hostname/domain comparison in oidc_check_cookie_domain - authz: remove the Location header from HTML based step up authentication redirects as it may conflict with its HTTP 200 status code and confuse middle boxes - metrics: avoid double-free on shutdown by not calling pthread_exit; fixes #1207; thanks @studersi - metrics: upon exit, do write cached metrics into shared memory before exiting - bump to 2.4.16.9dev 02/17/2025 - release 2.4.16.8 02/13/2025 - metrics: add support for claim value counters in OIDCMetricsData - metrics: do not reset Prometheus counters by default, only when explicitly specified - metrics: reset to 0 in case of an integer overflow - bump to 2.4.16.8rc0 01/29/2025 - add OIDCProfile to configure OpenID Connect profile behaviours for, so far "FAPI20" only, which configures: Authentication Request method, DPoP, PKCE, ID token aud values requirements token endpoint JWT authentication "aud" values, "iss" parameter requirement in authentication reponses - release 2.4.16.7 01/24/2025 - fix OIDCProviderRevocationEndpoint when not setting it to ""; see #1301; thanks @tarteens 01/19/2025 - jose: prevent memory leaks when zlib compressing (deflate) fails in oidc_jose_zlib_compress 01/02/2025 - add a configuration check for public/private keys when using DPoP; closes #1293; thanks @ahus1 - update copyright year to 2025 12/17/2024 - code: address SonarQube warnings in src/cache/*.c 12/16/2024 - http: report errors when curl_easy_setopt fails and improve macro usage - address warnings from static code analysis tool Coverity - code: declare enum members as int so they can be set to OIDC_CONFIG_POS_INT_UNSET without warning - code: declare memcache members as int so they can be set to OIDC_CONFIG_POS_INT_UNSET without warning - code: declare introspection_endpoint_method member as int so it can be set to OIDC_CONFIG_POS_INT_UNSET without warning - code: check return value of oidc_get_provider_from_session and oidc_refresh_token_grant in logout.c - code: avoid potential crash on non-conformant literal IPv6 adresses in oidc_util_current_url_host - code: apply boundary checks on oidc_metrics_shm_size and use a global static for performance reasons 12/15/2024 - add Coverity Github action 12/13/2024 - address warnings from static code analysis tool Coverity - code: avoid potentional memory leak on cURL handle if curl_easy_escape/curl_easy_unescape fails - code: correct the check for the optional token_type parameter returned from a token endpoint request - code: initialize oidc_jose_error_t err variable in oidc_util_create_symmetric_key - code: refactor oidc_util_port_from_host and avoid potential crash on non-conformant literal IPv6 addresses - code: add and use _oidc_strncpy for section key string copy in shm.c - code: correct check for private key return value from oidc_proto_jwt_create_from_first_pkey and avoid NULL pointer dereferencing when no private keys have been configured - code: correct check for *static_template_content in oidc_util_html_send_in_template in util.c 12/11/2024 - address warnings from static code analysis tool SonarQube - code: loop over authz arrays with index instead of pointer - code: avoid embedding defines in macro arguments - code: avoid cast warnings - code: add comment to empty functions - code: remove any side effects from right hand operands of logical && operator 12/10/2024 - github: add SonarQube analysis to Github workflows - address warnings from static code analysis tool SonarQube - code: use snprintf instead of sprintf - code: move _snprintf define to const.h - bump to 2.4.16.7dev 12/09/2024 - release 2.4.16.6 12/05/2024 - metadata: fix caching of JWKs from jwks_uri when using the default expiry setting (i.e. not using OIDCJWKSRefreshInterval) and avoid fetching JWKs from the jwks_uri for each user login; also addresses Redis cache error entries the log [ERR invalid expire time in 'setex' command] - avoid segfault and improve error reporting in case apr_temp_dir_get fails when a temp directory cannot be found on the system upon initalizing cache mutexes and file cache; see #1288; thanks @ErmakovDmitriy 11/21/2024 - add option to set local address for outgoing HTTP requests; see #1283; thanks @studersi using e.g. SetEnvIfExpr true OIDC_CURL_INTERFACE=192.168.10.2 - try and address metris cleanup segmentation fault on shutdown; see #1207 by not flushing metrics to the shared memory segment upon exit 11/14/2024 - allow specific settings Strict|Lax|None|Disabled for OIDCCookieSameSite in addition to On(=Lax)|Off(=None) - fix: default behaviour Lax - fix: apply OIDCCookieSameSite Off/None properly to state cookies instead of always setting Lax - re-introduces the option to configure a Strict SameSite session cookie policy, which will turn the initial Lax session cookie - set upon receving the response to the Redirect URI - into a Strict session cookie immediately after the first application request - allows for a "Disabled" value that does not set any SameSite flag on the cookies, in which case a browser falls back to its default browser behaviour (which should be Lax by spec) 11/07/2024 - info: fix requests to the info hook with extend_session=false; see #1279; thanks @fnieri-cdp - properly reflect the (unmodified) inactivity timeout in the response ("timeout") - avoid refreshing an access token (since the session is not saved) - avoid refreshing claims from the user info endpoint, and possibly refreshing the access token 10/23/2024 - metadata: allow plain HTTP URLs in metadata elements `jwks_uri` and `signed_jwks_uri` to ensure backwards compatibility with <=2.4.15.7 and to support private/test deployments 10/22/2024 - address warnings from static code analysis tool CodeChecker - bump to 2.4.16.6dev 10/16/2024 - release 2.4.16.5 10/04/2024 - ensure backwards compatibility with versions <2.4.16.x when a JSON array of string values is provided in the "aud" claim of the ID token; required by (at least) Oracle IDCS see #1272 and #1273; thanks @lufik and @tydalforce - add OIDCIDTokenAudValues configuration primitive that allows for explicit (and exhaustive) configuration of the list of accepted values in the "aud" claim of the ID token e.g. as required for passing FAPI 2 conformance testing - bump to 2.4.16.5rc0 09/29/2024 - release 2.4.16.4 09/27/2024 - correct usage of free() for json_dumps return values instead of cjose_get_dealloc()() - use compact encoding and preserve order where appropriate for most calls to json_dumps - replace json_dumps/free combos with oidc_util_encode_json - refactor oidc_jwk_to_json - bump to 2.4.16.4rc3 09/26/2024 - fix oidc_jwk_copy wrt. "x5t", which broke private_key_jwt authentication to Azure AD since 2.4.13 see #1269; thanks @uoe-pjackson - bump to 2.4.16.4rc2 09/21/2024 - refactor state and userinfo 09/11/2024 - change warnings about not passing unknown claim types into debug messages; see #1263; thanks @nclarkau - bump to 2.4.16.4rc1 09/09/2024 - fix accepting custom cookie names in OIDCOAuthAcceptTokenAs cookie:; see #1261; thanks @bbartke - bump to 2.4.16.4rc0 - improve basic authentication parsing when using OIDCOAuthAcceptTokenAs basic 09/06/2024 - allow overriding globally set OIDCCacheType back to shm in vhosts - correct typo in child initialization routines when using multiple vhosts; closes #1208; thanks @studersi this fixes possible segmentation faults when using Redis and Metrics settings in vhosts - release 2.4.16.3 09/05/2024 - fix OIDCCacheShmMax min/max settings; see #1260; thanks @bbartke - bump to 2.4.16.3dev 08/30/2024 - release 2.4.16.2 08/29/2024 - fix setting OIDCPKCEMethod none; closes #1256; thanks @eoliphan 08/28/2024 - re-introduce OIDCSessionMaxDuration 0; see #1252 - bump to 2.4.16.2dev - add some resilience when both Forwarded and X-Forwarded-* are configured - fix disabled OIDCStateCookiePrefix command; closes #1254; thanks @damisanet - remove support for OIDCHTMLErrorTemplate, deprecated since 2.4.14 08/26/2024 - fix parsing OIDCXForwardedHeaders; closes #1250; thanks @maltesmann - release 2.4.16.1 08/23/2024 - release 2.4.16 07/03/2024 - cfg/provider: use oidc_jwk_list_copy when merging client_keys 06/18/2024 - memcache: correct dead server check on APR_NOTFOUND; see #1230; thanks @rpluem-vf 06/08/2024 - support DPoP nonces to the userinfo endpoint 06/07/2024 - return response headers from outgoing HTTP requests to callers - support DPoP nonces to the token endpoint 06/06/2024 - add OIDCDPoPMode [off|optional|required] primitive - store the token_type in the session 06/05/2024 - add "nbf" claim in the Request Object as per https://openid.net/specs/openid-financial-api-part-2-1_0-final.html#rfc.section.5.2.2 06/04/2024 - add (client) support for RFC 9449 OAuth 2.0 Demonstrating Proof of Possession (DPoP) - replace multi-provider .conf "issuer_specific_redirect_uri" boolean with "response_require_iss" boolean - tighten up the "aud" claim validation in ID tokens - add support for the FAPI 2.0 Security Profile https://openid.net/specs/fapi-2_0-security-profile-ID2.html 05/30/2024 - add support for RFC 9126 OAuth 2.0 Pushed Authorization Requests 04/23/2024 - disable support for the RSA PKCS v1.5 JWE encryption algorithm as it is deemed unsafe due to the Marvin attack and is removed from libcjose as well 04/05/2024 - add debug printout for OIDCUnAuthAction expression evaluation 04/03/2024 - when an expression is configured for OIDCUnAuthAction (i.e. in the 2nd argument), also apply it to OIDCUnAutzAction so that it can be used to enable step-up authentication for SPAs with non-conformant browsers (some versions of Safari) and in (potentially insecure) iframes see #1205; thanks @ryanwilliamnicholls 04/02/2024 - major rewrite of config primitive handling: - split out over different files, use header files consistently - encapsulate config record with getters/setters - allow overriding defined global configuration primitives to their default value on the individual vhost level - apply input/boundary checking on all configuration values, shared with provider metadata parsing - various fixes to applying default config values and allowing primitives in vhost/directory scopes - return HTTTP 502 when refreshing acces token or userinfo fails (default: "502_on_error") - use a singleton token refresh mutex - add support for OIDCOAuthIntrospectionEndpointKeyPassword - bump to 2.4.16dev 04/01/2024 - release 2.4.15.7 03/29/2024 - fix OIDCUserInfoRefreshInterval, interval seconds would be interpreted as microseconds 03/14/2024 - fix userinfo refresh interval parsing; closes #1200; thanks @HolgerHees avoid refreshing userinfo on each request until access token expiry - store interval as JSON integer in session - use SameSite=Lax when OIDCCookieSameSite is On (also by default) instead of Strict as overriding from Lax to Strict does not work reliably anymore (Chrome) - release 2.4.15.6 03/13/2024 - fix compilation without libhiredis; closes #1195 ; thanks @HolgerHees conditionally define oidc_set_redis_connect_timeout - fix `OIDCPassClaimsAs environment` bug introduced in 2.4.15.4; see #1196; thanks @HolgerHees - release 2.4.15.5 03/12/2024 - release 2.4.15.4 - fix setting the default PCKE method to "none" in a multi-provider setup - bump to 2.4.15.5dev 03/11/2024 - avoid warning about setting a self-provided default value in the provider config 03/07/2024 - OIDCProviderSignedJwksUri: accept verification key set formatted as either JWK or JWKS see #1191; thanks @psteniusubi - properly handle parse errors in Require claim integer statements 03/06/2024 - add support for JSON real and null value matching in Require claim statements - fix evaluation Require statements for nested array claims - refactor handle/authz.c 03/04/2024 - refactor integer and timestamp handling - fix default HTTP short retry interval setting - accept 0 in OIDCUserInfoRefreshInterval - userinfo refresh: don't try to refresh the access token and retry when a connectivity error has occurred - logout: don't try to revoke tokens on post-access-token-refresh or post-userinfo-refresh-errors logouts 03/01/2024 - accept strings as well as integers in the "expires_in" claim from the token endpoint - fix for "expires_in" string values returned from the token endpoint that would be interpreted as 0 this fixes using OIDCRefreshAccessTokenBeforeExpiry or OIDCUserInfoRefreshInterval with (older) Azure AD configs that would result in a token refresh on every request since 2.4.15 or a 401 in 2.4.14.4 - fix setting the "exp" claim in userinfo signed JWTs (exp would be now+0) when no expires_in is returned by the OP - fix signed JWT caching when ttl set to 0 or "" which should apply the exp claim as the cache ttl 02/29/2024 - hash the cache key if it is larger than 512 bytes so large cache key entries (i.e. for JWT tokens) are no longer a problem in unencrypted SHM cache configs, i.e. the default shared memory cache setup; see issues/discussions on "could not construct cache key since key size is too large" this is relevant for OAuth 2.0 RS configs with JWT access tokens and OpenID Connect RP configs where OIDCRefreshAccessTokenBeforeExpiry/OIDCUserInfoRefreshInterval is set and a JWT refresh token is returned - fix debug printout of cache key in oidc_cache_get 02/28/2024 - optimize performance by skipping JSON processing when `OIDCPassClaimsAs none` is set 02/25/2024 - implement oidc_util_apr_hash_clear so clearing hashtables works with older versions of libapr - refactor/extract HTTP functions 02/22/2024 - enable TCP keepalive on Redis connections by default and make it configurable in `OIDCRedisCacheConnectTimeout [0|]` - refactor Redis connect routines 02/21/2024 - OIDCProviderSignedJwksUri: make exp claim optional in signed jwks; see #1182; thanks @psteniusubi interop with OpenID Federation specification https://openid.net/specs/openid-federation-1_0-32.html#section-5.2.1 02/15/2024 - refactor zlib compression routines and add error checks - bump to 2.4.15.4dev 02/13/2024 - release 2.4.15.3 02/12/2024 - set Redis default retry interval time to 300 milliseconds (instead of 0.5ms) and make it configurable - rewrite handling of parallel refresh token grant requests - temporarily cache the results of the refresh token grant for other (almost) parallel callers - fixes handing on the same server, and improves clustered handling through a best-effort distributed cached lock - improves handling of non-rollover refresh tokens since it avoids unnecessary repeated calls to the token endpoint, unnecessary token issuance and possibly corruption because different tokens "live" temporarily in the same (conceptual) session in parallel before the session is stored (and the last one wins) - bump to 2.4.15.3dev 02/06/2024 - CVE-2024-24814: prevent DoS when `OIDCSessionType client-cookie` is set and a crafted Cookie header is supplied https://github.com/OpenIDC/mod_auth_openidc/security/advisories/GHSA-hxr6-w4gc-7vvv - release 2.4.15.2 01/31/2024 - avoid crash when Forwarded is not present but OIDCXForwardedHeaders is configured for it; see #1171; thanks @daviddpd - bump to 2.4.15.2dev 01/30/2024 - release 2.4.15.1 01/22/2024 - refactor metrics and fix Prometheus output overlap; closes #1161; see #1162 and #1160; thanks @studersi - bump to 2.4.15.1rc0 01/17/2024 - use `apr_file_rename` in file cache backend to fix issue with renaming files on windows 01/09/2024 - release 2.4.15 12/29/2023 - fix Redis connnect retries and make it configurable through environment variable OIDC_REDIS_MAX_TRIES - bump to 2.4.15rc14 12/28/2023 - set memory alignment of shm cache structs to 64 bytes; see #1067 should fix running on Raspberry PI / ARMv7 32 bits (arm32v7) - make sure the shm cache entry size is a multiple of 8 bytes, see #1067; thanks @sanzinger - bump to 2.4.15rc13 12/22/2023 - generate 20-byte lowercase hexadecimal session identifiers 12/20/2023 - generate or propagate traceparent header using OIDCTraceParent; closes #1152; thanks @studersi - include hostname,port and process id in User-Agent header on outgoing requests - bump to 2.4.15rc12 12/19/2023 - metrics update: - rename "requests" class to "provider" - don't include label name in metric name - add HTTP response code and connectivity counters - reset counters in shared memory rather than removing - performance - bump to 2.4.15rc11 12/18/2023 - metrics refactoring and extension - bump to 2.4.15rc10 12/15/2023 - add (and fix) more metrics, including provider requests, authorization and cache - bump to 2.4.15rc9 12/14/2023 - add metrics collection capability, configured with OIDCMetricsData and retrieved through OIDCMetricsPublish - bump to 2.4.15rc8 11/30/2023 - fix SSL server certificate validation when revoking tokens apply `OIDCSSLValidateServer` setting rather than `OIDCOAuthSSLValidateServer` in `oidc_revoke_tokens` see https://github.com/OpenIDC/mod_auth_openidc/discussions/1141; thanks @mschmidt72 - bump to 2.4.15rc7 11/27/2023 - use clang-format-17 for code formatting and reformat all code 11/23/2023 - add iat and exp claims to request object; closes #1137 - bump to 2.4.15rc6 11/22/2023 - populate User-Agent header in outgoing HTTP requests with mod_auth_openidc, libcurl and OpenSSL version information and log it for debugging purposes - bump to 2.4.15rc5 11/08/2023 - improve error message in case of curl timeouts - bump to 2.4.15rc4 11/02/2023 - apply ISO-8859-1 ("latin1") as default encoding mechanism for claim values passed in headers and environment variables to comply with https://www.rfc-editor.org/rfc/rfc5987; see #957; use "OIDCPassClaimsAs none" for backwards compatibility - bump to 2.4.15rc3 11/01/2023 - avoid warnings on cache misses (regression introduced in 2.4.15rc1) - bump to 2.4.15rc2 10/31/2023 - add capability to seamlessly rollover OIDCCryptoPassphrase using a (temporary) 2nd value that holds the old one - bump to 2.4.15rc1 - remove obsolete support for Token Binding https://www.rfc-editor.org/rfc/rfc8471.html (id_token, access_token, session cookie) - use only the User-Agent header as input for the state browser fingerprinting by default (no X-Forwarded-For) as cloud environments increasingly use dynamic proxy IPs in front - use PKCE S256 by default; disable by configuring "OIDCPKCEMethod none" - use SameSite cookies Strict by default; disable by configuring "OIDCCookieSameSite Off" 10/30/2023 - do not apply logout_on_error and authenticate_on_error when a parallel refresh token request is detected see https://github.com/OpenIDC/mod_auth_openidc/discussions/1132; thanks @esunke - restore backwards compatibility wrt. allowing parallel refresh token requests by default, and add an option to prevent that (i.e. in case of rolling refresh tokens) using envvar OIDC_PARALLEL_REFRESH_NOT_ALLOWED - return HTTP 500 on token refresh errors instead of HTTP 401 - retry failed outgoing HTTP requests and add options to configure it in OIDCHTTPTimeoutLong/OIDCHTTPTimeoutShort - bump to 2.4.15rc0 10/12/2023 - release 2.4.14.4 10/11/2023 - improve behaviour when parallel refresh token grant requests occur on the same Apache server/host and rolling refresh tokens are issued; synchronize using a global refresh token lock and avoid corrupting the session by storing/overwriting an expired refresh token - bump to 2.4.14.4rc6 09/22/2023 - performance: store userinfo refresh interval in session to avoid parsing JSON on each request - fix memory leak in oidc_refresh_token_grant: free the parsed id_token that is returned - bump to 2.4.14.4rc5 09/20/2023 - performance: skip re-validating cached provider metadata - performance: use process based locking instead of global locking for Redis caching - avoid potential process lifetime memory leak when mutex lock/unlock fails 09/19/2023 - fix performance issue with latin1 encoding when using OIDCPassClaimsAs latin1 - add options for authentication to OIDCOutgoingProxy; thanks @drzraf see https://github.com/OpenIDC/mod_auth_openidc/discussions/1107 - add support for custom preserve/restore POST data templates with OIDCPreservePostTemplates to be used when OIDCPreservePost is set to On; the hard-coded internal templates are added to the test directory as an example; closes #195 (yeah...); thanks @kerrermanisNL and @spiazzi - bump to 2.4.14.4rc3 09/14/2023 - fix `OIDCRefreshAccessTokenBeforeExpiry` when using it with `logout_on_error` or `authenticate_on_error` see #1111; thanks @brandonk10 - bump to 2.4.14.4rc0 09/05/2023 - release 2.4.14.3 08/25/2023 - add support for adding extra parameters to the Logout Request to the OP with OIDCLogoutRequestParams see: https://github.com/OpenIDC/mod_auth_openidc/discussions/1096 - bump to 2.4.14.3rc7 08/13/2023 - increase performance of JQ filtering by caching JQ filtering results default cache ttl is 10 min, configured through environment variable OIDC_JQ_FILTER_CACHE_TTL - bump to 2.4.14.3rc5 07/25/2023 - support "authenticate_on_error" 2nd parameter value in OIDCRefreshAccessTokenBeforeExpiry to reauthenticate the user when refreshing the access token fails see: https://github.com/OpenIDC/mod_auth_openidc/discussions/1084; thanks @xrammit - add logout_on_error and authenticate_on_error 2nd parameter option to OIDCUserInfoRefreshInterval - bump to 2.4.14.3rc4 07/18/2023 - allow relative values in OIDCDefaultURL and OIDCDefaultLoggedOutURL - bump to 2.4.14.3rc3 07/14/2023 - fix session updates on userinfo requests; see https://github.com/OpenIDC/mod_auth_openidc/discussions/1077 this bug was introduced in v2.4.11 with d9fff154ee6ee8a7e4e969dd6a68cbaf18354598 - bump to 2.4.14.3rc2 07/12/2023 - add a sanity alg/enc check on self-encrypted AES GCM JWTs - add `OIDCPassAccessToken Off` option to disable (the default of) passing the access token and its expiry in the OIDC_access_token/OIDC_access_token_expires header/environment variables; thanks @mattias-asander - bump to 2.4.14.3rc1 06/05/2023 - avoid using encryption keys as signing keys for request objects and private_key_jwt token endpoint auth - log the first Redis error as a warning before retrying - release 2.4.14.2 05/30/2023 - revert all 401/403/302/stepup behaviour to <= 2.4.13.2 - bump to 2.4.14.2rc1 05/25/2023 - add support for extend_session=false query parameter to the info hook to avoid extending the session on calls to the info hook - bump to 2.4.14.2rc0 05/24/2023 - fix RequireAny behaviour on 401/403/302: revert 9d6192b2ab0716d8f7d2a29754a80b6ab1e804eb for non-stepup authentication cases - make OIDCUnautzAction 302|auth (i.e. step up authentication) work with multiple/complex Require expressions e.g. RequireAny - release 2.4.14.1 05/17/2023 - fix refreshing claims from the userinfo endpoint when no id_token claims are stored in the session - fix memory leak when refreshing claims from the userinfo endpoint - bump to 2.4.14.1rc0 - fix docs on OIDCUnAutzAction 403 in auth_openidc.conf because we no longer rely on Apache 2.4's AuthzSendForbiddenOnFailure On to return 403 05/15/2023 - release 2.4.14 05/05/2023 - add exec support to OIDCClientSecret; see #1056; thanks @sealor 05/04/2023 - cater for libapr/libapr-util version 1.2.x - bump to 2.4.14rc13 04/27/2023 - increase default OIDCCacheShmMax to 10000 04/25/2023 - add options to avoid revoking tokens before logout as some OPs may kill their SSO session that would make subsequent logout fail; configure an empty string in OIDCProviderRevocationEndpoint or set the OIDC_DONT_REVOKE_TOKENS_BEFORE_LOGOUT environment variable - bump to 2.4.14rc12 04/24/2023 - preserve linefeeds in text areas with OIDCPreservePost On by changing the order of the "type=hidden" in the restore Javascript page see: https://github.com/OpenIDC/mod_auth_openidc/discussions/717 thanks @paulQdata and @jansmets 04/23/2023 - add environment variable OIDC_DONT_STORE_ID_TOKEN_CLAIMS_IN_SESSION option to not store the claims in the id_token in the session, to avoid storing claims that are not used anyway and/or overlap with userinfo claims - bump to 2.4.14rc11 04/21/2023 - use compressed serialized JSON for encrypted cache entries instead of signed JWTs, reducing cache entry size - refactor (internal) encrypted JWTs handling - remove support for obsoleted internal signed JWTs - add resilience for corrupted metadata and jwks_uri cache entries 04/20/2023 - allow defining OIDCPassIDTokenAs on a per-location/directory level; also fixes resetting back to "claims" in vhosts - turn of caching by default for `OIDCPassUserInfoAs signed_jwt` - use compressed serialized JSON for encrypted state and session cookies, reducing their size; thanks @hihellobolke - bump to 2.4.14rc10 04/19/2023 - add support for using Elliptic Curve keys with `OIDCPassUserInfoAs signed_jwt` - bump to 2.4.14rc9 04/18/2023 - support calling the refresh token grant before doing RP-initiated logout when the environment variable OIDC_REFRESH_TOKENS_BEFORE_LOGOUT is set may be used to supply a (fresh or non-cached) id_token_hint logout request parameter - add support for returning the serialized id_token as id_token_hint from the info hook - fix crash when using a multi-provider setup and Provider has signed_jwks_uri set but the conf file does not define signed_jwks_uri_key - correct return value from oidc_cache_shm_destroy to avoid misleading "cache destroy function failed" error messages - bump to 2.4.14rc8 04/16/2023 - OIDCUnAutzAction auth for stepup authentication now immediately returns a 302 instead of a 200 HTML page with a meta refresh tag and a Location header - also fix OIDCUnAutzAction 403 so it does not rely on AuthzSendForbiddenOnFailure - bump to 2.4.14rc7 04/13/2023 - fix session cookie decompression error with OIDCSessionType client-cookie; closes #1046; thanks @oss-aimoto this would occur when the uncompressed JWT is larger than 4 times the compressed JWT oidc_util_jwt_verify: parsing JWT failed: [src/jose.c:901: oidc_jose_zlib_uncompress]: inflate failed oidc_session_decode: could not verify secure JWT: cache value possibly corrupted https://github.com/OpenIDC/mod_auth_openidc/pull/1047 - add option to strip the AES GCM header from encrypted state/session JWTs with env var OIDC_JWT_STRIP_HDR=true - bump to 2.4.14rc6 04/11/2023 - add caching of signed userinfo JWTs; default cache time is set to the "exp" claim, can be configured/disabled with: SetEnvIfExpr true "OIDC_USERINFO_SIGNED_JWT_CACHE_TTL=0" be careful when setting "jti", "nbf", "iat" and" "exp" claims in the OIDCUserInfoClaimsExpr filter since they may overload the cache with entries per-user/per-timestamp if the result differs from the previous request - bump to 2.4.14rc5 04/11/2023 - add OIDCFilterClaimsExpr that allows for processing claims in the both the id_token and claims from the userinfo endpoint before storing them in the session, after applying (optional) blacklisting/whitelisting on the toplevel keys; only available when compiled/linked with libjq - fix memory access error using default value for OIDCPassUserInfoAs - bump to 2.4.14rc4 04/10/2023 - add support for OIDCUserInfoClaimsExpr that allows for processing claims returned from the userinfo endpoint with a JQ-based expression before propagating them according to OIDCPassUserInfoAs claims|json|signed_jwt (ie. does not work for "OIDCPassUserInfoAs jwt"), and is only available when compiled/linked with libjq - allow OIDCPassUserInfoAs directive in Location/Directory contexts - fix memory leak when using JQ-based expressions in "Require claims_expr" - bump to 2.4.14rc3 04/09/2023 - make sure mod_auth_openidc runs before mod_proxy so calls to the redirect URI are never proxied and no separate Location directive or ProxyPass exception for OIDCRedirectURI is required (anymore) - handle discovery in the content handler so regular Apache processing applies to the HTTP/HTML response - bump to 2.4.14rc2 04/09/2023 - return 40x instead of 200 on all (authorization) error responses - correct backwards compatibility with <2.4.14 for state mismatch/timeout handling - bump to 2.4.14rc1 04/07/2023 - deprecate OIDCHTMLErrorTemplate and rely on standard Apache error handling capabilities by default environment variable strings REDIRECT_OIDC_ERROR and REDIRECT_OIDC_ERROR_DESC are available in ErrorDocument backwards compatibility is retained by setting "OIDCHTMLErrorTemplate deprecated" - bump to 2.4.14rc0 04/07/2023 - add support for passing on claims resolved from the userinfo endpoint in a JWT signed by mod_auth_openidc using `OIDCPassUserInfoAs signed_jwt[:]` with the keys configured in OIDCPrivateKeyFiles/OIDCPublicKeyFiles - add support for overriding the default header/environment variable names in `OIDCPassUserInfoAs json:` (default: "OIDC_userinfo_json") and `OIDCPassUserInfoAs jwt:` (default: "OIDC_userinfo_jwt") - bump to 2.4.13.3rc3 04/06/2023 - merge client_signing_keys and client_encryption_keys into client_keys since we detect the usage type correctly now - bump to 2.4.13.3rc2 04/04/2023 - support configuration of dedicated signing and encryption keys in the primitives: OIDCPublicKeyFiles, OIDCPrivateKeyFiles, OIDCProviderVerifyCertFiles, OIDCOAuthVerifySharedKeys and OIDCOAuthVerifyCertFiles by using the prefix "sig:" or "enc:" in the value; using this in OIDCPublicKeyFiles also publishes separate "use: sig" and/or "use: enc" keys on the client jwks_uri ?jwks=rsa - fix: don't immediately refresh of JWKs from (signed)_jwks_uri if "kid" was not set in JWT, but try the cache first - fix: properly respect "use" attribute (sig/enc) in signing, verification and encryption - bump to 2.4.13.3rc1 04/03/2023 - generate Elliptic Curve "kid" using curve identifier with htonl in network byte order so "make check" works on big endian platforms - include openssl/err.h in config.c to avoid compiler warning with OpenSSL 1.0.x - bump to 2.4.13.3rc0 04/03/2023 - release 2.4.13.2 04/01/2023 - allow target_link_uri's without a path in 3rd-party-init SSO with a multi-provider setup - correct error log in target_link_uri matching 03/28/2023 - CVE-2023-28625: prevent core dump when OIDCStripCookies is set and a crafted Cookie header is supplied https://github.com/OpenIDC/mod_auth_openidc/security/advisories/GHSA-f5xw-rvfr-24qr - replace apr_strnatcmp/strcmp with _oidc_strcmp and replace strncmp with _oidc_strncmp - handle OpenSSL initialization in new oidc_pre_config_init function: this allows omitting "kid" in OIDCPublicKeyFiles (ao.) when linked against OpenSSL 1.0.x 03/27/2023 - fix code scanning alerts - bump to 2.4.13.2rc2 03/24/2023 - add support for Elliptic Curve signing/encryption keys in addtion to RSA keys, i.e. client keys configured in OIDCPrivateKeyFiles/OIDCPublicKeyFiles, published on OIDCClientJwksUri and used in private_key_jwt authentication, encrypted id_token's, request objects/uri's, but also statically configured provider keys in OIDCOAuthVerifyCertFiles and OIDCProviderVerifyCertFiles - refactor Docker tests make targets; add test/Makefile - bump to 2.4.13.2rc1 03/24/2023 - record authorization errors in environment variable OIDC_AUTHZ_ERROR so its value can be used in logs e.g. with HTTP 401 responses: LogFormat "%h %l %u %t %U %401{OIDC_AUTHZ_ERROR}e %>s %b" combined - log authorization errors with oidc_debug instead of oidc_info - bump to 2.4.13.2rc0 03/10/2023 - fix oidc_jwk_list_copy and usage of OIDCProviderVerifyCertFiles - release 2.4.13.1 03/10/2023 - shm cache: increase default maximum number of active sessions from 500 to 2000 - shm cache: allow configuration of max 1Mb of session data for a single session - use deep-copy and cleanup functions for server and provider configs; fixes overriding server-level keys in vhost configs - release 2.4.13 03/09/2023 - add support for OP "signed_jwks_uri" with "OIDCProviderSignedJwksUri " - don't pull JWKs when the id_token was signed with a symmetric key - don't immediately refresh of JWKs from (signed)_jwks_uri if "kid" was not set in JWT, but try the cache first - warn about incorrect configurations not setting OIDCCryptoPassphrase; see https://github.com/OpenIDC/mod_auth_openidc/discussions/1030 - bump to 2.4.13rc5 03/08/2023 - move repo to OpenIDC github organization 03/02/2023 - allow setting minumum and maximum versions of TLS used in HTTPs calls via libcurl environment variable CURLOPT_SSL_OPTIONS e.g.: SetEnvIfExpr true "CURLOPT_SSL_OPTIONS=CURL_SSLVERSION_TLSv1_3 CURL_SSLVERSION_MAX_TLSv1_3" ; bump to 2.4.13rc3 - bump to 2.4.13rc3 03/01/2023 - revert accidentally removed libbrotli code in jose.c - bump to 2.4.13rc2 02/19/2023 - add optional - compilation time support - for brotli compression of session and state cookies 02/17/2023 - avoid (small) memory leak when using OpenSSL 3.x when setting public/private keys (over graceful restarts) in the config and/or importing JWKs with x5c specs - compress session and state cookies; add zlib as a dependency - bump to 2.4.13rc0 01/27/2023 - increase maximum allowed size of HTTP responses (e.g. from token endpoint) to 10Mb; see #998; thanks @mikehearn - do a sanity check on the individual size of claim values stored in the session, warn about blacklisting if > 8Kb - bump to 2.4.12.4rc2 01/23/2023 - release 2.4.12.3 01/20/2023 - add OIDCProviderVerifyCertFiles option to statically configure ID token validation keys; see #989; thanks @madsfreek - fix bug in OIDCOAuthVerifyCertFiles where cert(s) would be cast to apr_hash_t instead of apr_array_header_t; see #990; thanks @bommo1 - bump to 2.4.12.3rc0 12/28/2022 - update sample/test Dockerfile to Ubuntu Jammy 12/13/2022 - CVE-2022-23527: prevent open redirect in default setup when OIDCRedirectURLsAllowed is not configured see: https://github.com/zmartzone/mod_auth_openidc/security/advisories/GHSA-q6f2-285m-gr53 - release 2.4.12.2 12/08/2022 - simplify redis context code - bump to 2.4.12.2rc1 11/18/2022 - allow overriding the type of lock used at compile time with OIDC_LOCK - bump to 2.4.12.2rc0 11/15/2022 - release 2.4.12.1 11/13/2022 - switch to using apr_generate_random_bytes instead of apr_uuid_get to generate session identifiers so there's no longer a (rather implicit) dependency on a libapr that is compiled againt libuuid on Linux platforms; see #431, #603 and #694; thanks @amitnarang28 - cache file backend fix: delete the correct file upon logout; closes #955; thanks @damisanet - bump to 2.4.12.1rc5 11/08/2022 - add option to use ISO-8859-1 encoding for propagated claim values by adding "latin1" option to OIDCPassClaimsAs <> latin1; see #957; thanks @nvchaudhari1991 Note that the encoding - including the existing "base64url" - apply to both header and environment variables as well now. - bump to 2.4.12.1rc4 10/26/2022 - OIDCProviderMetadataRefreshInterval was interpreted in microseconds instead of the documented and intended seconds; setting in to seconds would effectively turn of caching and pull the configuration document on each request - bump to 2.4.12.1rc3 10/25/2022 - define APLOG_TRACE1 if it does not exist - bump to 2.4.12.1rc2 10/20/2022 - CI: add memory and semaphore checks on various distro's - correct ap_hook_insert_filter function signature in stub.c, part 3; see #784 - fix printout of cache mutex errors in cache/common.c - prefer APR_LOCK_POSIXSEM over APR_LOCK_DEFAULT in apr_global_mutex_create which is apparently required for (some) ARM based builds (and CI) - bump to 2.4.12.1rc1 - fix potential memory leak in proto.c when oidc_util_create_symmetric_key fails - fix potential memory leak in proto.c when oidc_proto_validate_access_token fails (at_hash validation) 10/19/2022 - fix cleanup of semaphores on graceful restarts; see #522, closes #458 simplify mutex/shm cleanup without semaphores because we track the parent process anyway; - bump to 2.4.12.1rc0 10/17/2022 - release 2.4.12 10/15/2022 - add option to set a username for Redis authentication via OIDCRedisCacheUsername - bump to 2.4.11.4rc7 10/14/2022 - set minimum number of default memcache threads to 0 to retain backwards compatibility see #916 - support OIDCSessionInactivityTimeout values greater than 30 days when using Memcache see #936, thanks @takesson - bump to 2.4.11.4rc6 10/03/2022 - add -fPIC to test and test-cmd compilation; see #925 - bump to 2.4.11.4rc5 09/23/2022 - allow for step-up discovery with an external URL using HTML refresh fixes behaviour on CentOS 7/8 when combined with ProxyPass - bump to 2.4.11.4rc4 09/12/2022 - add options to retrieve the configuration document only or pull keys from the JWKS URI; for certification purposes - check ID token signed response algorithm on backchannel logout_token and retrieve its configuration value from the client metadata file; for certification purposes - register request_object_signing_alg in dynamic client registration when using request_uri; for certification purposes - bump to 2.4.11.4rc3 09/08/2022 - store access token obtained from backchannel in session over the one returned in the frontchannel for "code token" and "code id_token token" flows; for certification purposes - apply exact length matching for at_hash and c_hash validation; for certification purposes - increase size of the output buffer when using libpcre2 for substitution; closes #915 - bump to 2.4.11.4rc2 - allow setting connection pool parameters for Memcache server connections; see #916; thanks @rpluem-vf 08/24/2022 - avoid using $< in Makefile - allow storing the id_token in a client-cookie based session; see #812 and #888 - bump to 2.4.11.4rc1 08/22/2022 - add oidc_util_strcasestr - bump to 2.4.11.4rc0 08/22/2022 - release 2.4.11.3 08/15/2022 - avoid memory leak when using PCRE2 regular expressions with array matching; closes #902; thanks @smanolache - avoid memory leak when cjose_jws_get_plaintext fails; closes #903; thanks @smanolache - bump to 2.4.11.3rc4 05/20/2022 - fix handling of IPv6 based logout URLs; thanks @@codemaker219 - bump to 2.4.11.3rc1 05/16/2022 - Use optionally provided sid and iss request parameters during front channel logout; see #855; thanks @rpluem-vf 05/06/2022 - support Forwarded header in addition to X-Forwarded-*; see #853; thanks @studersi - bump to 2.4.11.3rc0 05/05/2022 - release 2.4.11.2 05/04/2022 - add support for Apache expressions in OIDCPathAuthRequestParams and OIDCPathScope; see #594 - bump to 2.4.11.2rc2 04/22/2022 - add no Cache-Control headers to logout request response; see #846; thanks @blackwhiser1 - bump to 2.4.11.2rc1 04/06/2022 - don't strip the header from encrypted JWTs as future versions of cjose may use compact encoding for JWEs; this slightly increases state cookie size, by-value session cookies and encrypted cache contents again at the benefit of forward cjose compatibility - bump to 2.4.11.2rc0 03/29/2022 - release 2.4.11.1 03/28/2022 - correct registration_endpoint_json naming in auth_openidc.conf documentation 03/21/2022 - fix OIDCUnAuthAction pass, see #790 - bump to 2.4.11.1rc5 03/18/2022 - fix make check; add @smanolache to the AUTHORS file - bump to 2.4.11.1rc4 03/17/2022 - fix memory leaks over graceful restarts: use s->process->pconf pool instead of the s->process->pool in oidc_slog and oidc_cache_shm_cfg_create closes #823 and #824; thanks @smanolache 03/14/2022 - fix temporary cache file naming; see #777 03/08/2022 - fix a 2nd race condition in the file cache backend; see #777; thanks @dbakker and @blackwhiser1 - bump to 2.4.11.1rc3 03/04/2022 - add support for OpenSSL 3.0 - remove test-cmd jwk2cert command - bump to 2.4.11.1rc2 02/28/2022 - add a check to make sure URLs do not contain unencoded Unicode characters; see #796; thanks @cnico - bump to 2.4.11.1rc1 02/27/2022 - document Apache 2.4 behavior on OIDCUnAutzAction 403; see #795; thanks @candlerb 02/04/2022 - correct ap_hook_insert_filter function signature in stub.c, part 2; closes #784; thanks @stroeder 02/03/2022 - add Valgrind Github action - warn about mismatch between incoming X-Forwarded-* headers and OIDCXForwardedHeaders configuration - avoid using %llu print formatter and switch to %lu for unsigned long so it works cross platform - bump to 2.4.11.1rc0 01/26/2022 - improve handling session duration expiry when combined with OIDCUnAuthAction or Discovery also clear r->user in oidc_session_kill for such cases; see #778 - release 2.4.11 01/24/2022 - fix race condition in file cache backend reading truncated files under load; see #777; thanks @dbakker - bump to 2.4.11rc7 01/23/2022 - fix regular expressions in Require statements - bump to 2.4.11rc6 01/22/2022 - no longer defer Discovery to the content handler to allow RequireAll and Require not directives see #770; closes #775; thanks @rajeevn1 - bump to 2.4.11rc5 01/17/2022 - terminate on startup when the crypto passphrase generated by "exec:" is empty; see #767 - bump to 2.4.11rc4 01/15/2022 - correct printout of session id and remote user tuple for new sessions - avoid debug printout of payload as header when the latter is stripped 01/14/2022 - fix: avoid crash when using pcre2 for claims matching: don't pass NULL for errorstr - add administrative session revocation capability ?revoke_session= - bump to 2.4.11rc3 01/12/2022 - add AM_PROG_CC_C_O to configure.ac (at least for RHEL 7.7); see #765; thanks @bitmagewb - include in jose.c to compile with OpenSSL 1.0.x - fix parameters to get_current_url in oidc_handle_unauthorized_user22 - bump to 2.4.11rc2 01/06/2022 - improve detection of suspicious redirect URLs; add test list - bump to 2.4.11rc1 12/24/2021 - make interpretation of X-Forwarded-* headers configurable, defaulting to none so mod_auth_openidc running behind a reverse proxy that sets X-Forwarded-* headers needs explicit configuration of OIDCXForwardedHeaders - bump to 2.4.11rc0 12/21/2021 - add "x5t" to JWT header in private_key_jwt client assertions; for interop with Azure AD - add CI Github workflow over Travis - bump to 2.4.10.1rc4 12/16/2021 - make X-Frame-Options header returned on OIDC front-channel logout requests configurable through OIDCLogoutXFrameOptions; closes #464 - bump to 2.4.10.1rc3 12/15/2021 - remove typedef for oidc_pcre to avoid compiler errors 12/02/2021 - add support for libpcre2; see #740 - bump to 2.4.10.1rc2 12/01/2021 - allow authorization on info requests, see #746 - bump to 2.4.10rc1 11/28/2021 - install taking into account DESTDIR; see #674; thanks @alerque 11/11/2021 - correct ap_hook_insert_filter function signature in stub.c; closes #732; thanks @stroeder - bump to 2.4.10.1rc0 11/10/2021 - release 2.4.10 11/03/2021 - add redirect/text options to OIDCUnAutzAction; see #715; thanks @chrisinmtown - bump to 2.4.10rc1 11/02/2021 - add check for Sec-Fetch-Dest header != "document" value to auto-detect requests that are not capable of handling an authentication round trip to the Provider; see https://github.com/zmartzone/mod_auth_openidc/discussions/714; thanks @studersi - bump to 2.4.10rc0 10/28/2021 - use apxs to link the module in Makefile.am - bump to 2.4.9.5rc8 10/27/2021 - fix regexp substition crash using OIDCRemoteUserClaim; thanks @nneul; closes #720 - backport ap_get_exec_line, supporting the "exec:" option in OIDCCryptoPassphrase - add check for Sec-Fetch-Mode header != "navigate" value to auto-detect XML HTTP Requests - bump to 2.4.9.5rc7 10/22/2021 - complete usage of autoconf/automake; see #674 - bump to 2.4.9.5rc4 10/20/2021 - fix parallel builds (on Debian) for now - bump to 2.4.9.5rc1 10/19/2021 - log require claims failure on info level - bump to 2.4.9.5rc0 09/09/2021 - fix memory leak when parsing JWT access token fails (in RS mode) 09/07/2021 - reorganize Redis code for extensibility 09/03/2021 - return HTTP 200 for OPTIONS requests in auth-openidc mixed mode - don't apply claims based authorization for OPTIONS requests so paths protected with Require claim directives will now also return HTTP 200 for OPTIONS requests - fix typo in 2.2 authorization routine 09/03/2021 - don't apply authz in discovery process; fixes 2.4.9.3 - apply OIDCRedirectURLsAllowed setting to target_link_uri; closes #672; thanks @Meheni - release 2.4.9.4 08/26/2021 - don't apply authz to the redirect URI; fixes ac5686495a51bc93e257e42bfdc9c9c46252feb1 - bump to 2.4.9.3 08/20/2021 - fix graceful restart (regression); see #458; thanks @Foxite - bump to 2.4.9.2 08/18/2021 - preserve session cookie in the event of a cache backend failure - update the id_token in the session cache if one is provided while refreshing the access token 08/13/2021 - fix retried Redis commands after a reconnect; thanks @iainh - release 2.4.9.1 07/22/2021 - use redisvCommand to avoid crash with crafted key when using Redis without encryption; thanks @thomas-chauchefoin-sonarsource - replace potentially harmful backslashes with forward slashes when validating redirection URLs; thanks @thomas-chauchefoin-sonarsource - release 2.4.9 - don't use DEFAULT_LIMIT_REQUEST_LINE constant; since it does not exist in Apache 2.2.x 07/15/2021 - verify that "alg" is not none in logout_token explicitly - make session not found on backchannel logout produce a log warning instead of error - don't clear POST params authn on token revocation; thanks @iainh - bump to 2.4.9rc0 07/02/2021 - handle discovery in the content handler - return OK in the content handler for calls to the redirect URI and when preserving POST data 06/25/2021 - avoid XSS vulnerability when using OIDCPreservePost On and supplying URLs that contain single quotes thanks @oss-aimoto 06/21/2021 - strip A256GCM JWT header from encrypted JWTS used for state cookies, cache encryption and by-value session cookies resulting in smaller cookies and reduced cache content size 06/10/2021 - use encrypted JWTs for storing encrypted cache contents and avoid using static AAD/IV; thanks @niebardzo - bump to 2.4.9-dev 06/04/2021 - fix a problem where the host and port are calculated incorrectly, when you use literal ipv6 address. 06/02/2021 - do not send state timeout HTML document when OIDCDefaultURL is set; this can be overridden by using e.g.: SetEnvIfExpr true OIDC_NO_DEFAULT_URL_ON_STATE_TIMEOUT=true - release 2.4.8.4 06/01/2021 - avoid Apache 2.4 appending 400/302(200/404) HTML document text to state timeout HTML info page see also f5959d767b0eec4856d561cbaa6d2262a52da551 and #484; at least Debian Buster was affected - release 2.4.8.3 05/18/2021 - make error "session corrupted: no issuer found in session" a warning only so a logout call for a non-existing session no longer produces error messages 05/08/2021 - store timestamps in session in seconds to avoid string conversion problems on some (libapr-1) platform build/run combinations, causing "maximum session duration exceeded" errors - bump to 2.4.8.2 05/07/2021 - add OIDCClientTokenEndpointKeyPassword option to allow the use of an encrypted private key - release 2.4.8.1 04/30/2021 - fix potential crash when Content-Type is not set in POST requests; thanks Tatsuhiko Yasumatsu of JPCERT/CC - release 2.4.8 04/21/2021 - on OAuth 2.0 RS token scope/claim 401 error, add environment variable for usage with mod_headers, instead of adding a header ourselves; see #572; usage, e.g; Header always append WWW-Authenticate %{OIDC_OAUTH_BEARER_SCOPE_ERROR}e "expr=(%{REQUEST_STATUS} == 401) && (-n reqenv('OIDC_OAUTH_BEARER_SCOPE_ERROR'))" - bump to 2.4.8-dev 04/13/2021 - add OIDCRedisCacheConnectTimeout and OIDCRedisCacheTimeout options to configure Redis timeouts - bump to 2.4.7.2 04/12/2021 - fix memory leaks when caching fails - bump to 2.4.7.1 04/04/2021 - improve documentation on OIDCPreservePost - release 2.4.7 04/01/2021 - bump to 2.4.7rc1 02/16/2021 - remove session from cache before clearing it. 02/12/2021 - add maximum session lifetime (exp), inactivity timeout (timeout) and remote_user to OIDCInfoHook - bump to 2.4.7-dev 02/08/2021 - return 400 instead of 500 when state cookie matching fails - release 2.4.6 02/03/2021 - avoid displaying the client_secret in debug logs 01/28/2021 - avoid segmentation fault when hitting an endpoint configured with AuthType openid-connect in an OAuth 2.0 only setup; see #529 01/23/2021 - fix semaphore cleanup on graceful restarts; see #522 01/12/2021 - fix inconsistent public/private keys loading order; closes #515 12/17/2020 - remove support for https://tools.ietf.org/html/draft-bradley-oauth-jwt-encoded-state 12/10/2020 - add "base64url" option to OIDCPassClaimsAs primitive; closes #417 12/09/2020 - add Redis database selection option with OIDCRedisCacheDatabase; closes #423 - optimize Redis AUTH execution once per connection 12/07/2020 - don't set SameSite=None on cookies when on plain http 12/03/2020 - add environment variable to control libcURL CURLOPT_SSL_OPTIONS behaviors e.g.: SetEnvIfExpr true CURLOPT_SSL_OPTIONS=CURLSSLOPT_NO_REVOKE 11/23/2020 - release 2.4.5 - make sure the module compiles with Apache 2.2 for passphrase exec: - bump to 2.4.6-dev 11/19/2020 - ensure that "sub" is returned from the userinfo endpoint following https://openid.net/specs/openid-connect-core-1_0.html#UserInfoResponse prevents potential ID spoofing; thanks Christian Fries of Ruhr-University Bochum - don't printout JSON errors about NULL characters in error log; thanks Christian Fries of Ruhr-University Bochum - restrict printout of JSON parsing errors to 4096 bytes; thanks Christian Fries of Ruhr-University Bochum - bump to 2.4.5rc6 11/5/2020 - fix content processing for info and JWKs handler so mod_headers etc. works; closes #497 - bump to 2.4.5rc5 11/2/2020 - improve sanity checking on Redis reply - bump to 2.4.5rc4 10/30/2020 - disable caching token introspection results by setting OIDCOAuthTokenIntrospectionInterval to -1; thanks @wadahiro - bump to 2.4.5rc3 10/27/2020 - config check on OIDCCryptoPassphrase in OAuth 2.0 RS setup with cache encryption enabled - bump to 2.4.5rc2 10/22/2020 - hash define expression option to OIDCUnAuthAction so it compiles for Apache 2.2; fixes 1461634 - bump to 2.4.5rc1 - add exec support to OIDCCryptoPassphrase 10/19/2020 - delete stale session cookies that aren't in the cache - allow OIDCDiscoverURL to be a relative URL 10/08/2020 - add OIDCCABundlePath for configuring path to curl CA bundle 09/22/2020 - avoid Apache 2.4 appending 401 HTML document text to step-up authentication HTML refresh page; closes #484 - bump to 2.4.5rc0 09/21/2020 - populate AUTH_TYPE when performing authentication; thanks @spanglerco 09/19/2020 - enable authentication of sub-requests when the main request doesn't require authentication; thanks @spanglerco 09/03/2020 - add SameSite attribute on cookie clearance / logout; thanks @v0gler - bump to 2.4.4.1 09/01/2020 - forward port Tufin patches - always set session cookie same site policy to Lax - disable cookie domain check - unset host headers for metadata URL retrieval - bump to 2.4.4-tufin 09/01/2020 - avoid GCC 9 compiler warnings - release 2.4.4 08/28/2020 - allow Content-Type check on backchannel logout to have postfixes (utf-8 etc) - terminate backchannel logout with DONE instead of OK to avoid authz error 500 - bump to 2.4.4rc8 08/18/2020 - add recommended cache headers on backchannel logout response https://openid.net/specs/openid-connect-backchannel-1_0.html#rfc.section.2.8 - bump to 2.4.4rc7 08/10/2020 - add new OIDCStateCookiePrefix primitive for the state cookie prefix 08/01/2020 - add conditional expression to OIDCUnAuthAction; see #479; thanks @raro42 and @marcstern - bump to 2.4.4rc6 07/31/2020 - reverse order of creating HTML response and adding session cookie; thanks @deisser - bump to 2.4.4rc5 07/30/2020 - fix doubled Set-Cookie behaviour when using `client-cookie`, calling the session info hook and writing out a session update (twice); thanks @deisser - bump to 2.4.4rc4 07/27/2020 - prevent XSS and open redirect on OIDC session managemement OP iframe with OIDCRedirectURLsAllowed thanks Andrew Brady - bump to 2.4.4rc3 07/22/2020 - delete state cookie when it cannot be decoded/decrypted - bump to 2.4.4rc2 07/03/2020 - fix for loop initial declarations to not require c99 for compilation (RHEL 6) - add ap_expr.h include in stub.c (RHEL 6) - bump to 2.4.4rc1 06/30/2020 - add grant_types to dynamic client registration request - don't send access_token in user info request when method is set to POST; conform OIDC test suite 4.0.5 - bump to 2.4.4rc0 06/10/2020 - prevent open redirect on refresh token requests add new OIDCRedirectURLsAllowed primitive to handle post logout and refresh-return-to validation addresses #453; closes #466 - release 2.4.3 06/09/2020 - fix complex expressions crash when compiled from source with libjq; closes #472 thanks vincentscharf0803 introduced by OIDCStateInputHeaders addition in 2.4.3rc0 - bump to 2.4.3rc1 05/11/2020 - added OIDCValidateIssuer to allow for disabling of issuer matching. helps to support multi-tenant applications. 05/02/2020 - when stripping cookies, add a space between cookies in the resulting header (required by RFC 6265) - move oidc_parse_config inside MODULE_MAGIC_NUMBER_MAJOR to make sure the module compiles with Apache 2.0 04/25/2020 - add OIDCStateInputHeaders that allows configuring the header values used to calculate the fingerprint of the state during authentication - bump to 2.4.3rc0 03/25/2020 - oops: fix json_deep_copy of claims - release 2.4.2.1 03/24/2020 - fix memory leak in OAuth 2.0 JWT validation; closes #470; thanks Conrad Thukral - fix configured private/public key cleanup on process exit 03/21/2020 - allow for expressions in Require statements, see #469; thanks @wwaaron also see: https://github.com/zmartzone/mod_auth_openidc/wiki/Authorization#expressions-in-require-statements - bump to 2.4.2rc5 03/19/2020 - always refresh keys from jwks_uri when there is no kid in the JWT header - bump to 2.4.2rc4 03/15/2020 - destroy shared memory segments only in parent process; see #458 - bump to 2.4.2rc3 03/10/2020 - fix memory leaks introduced by #457 - bump to 2.4.2rc2 02/19/2020 - if content was already returned via html/http send then don't return 500 but send 200 to avoid extraneous internal error document text to be sent on some Apache 2.4.x versions e.g. CentOS 7 - bump to 2.4.2rc1 02/03/2020 - if OIDCPublicKeyFiles contains a certificate, the corresponding x5c, x5t and x5t#256 parameters will be added to the generated jwkset available at "?jwks=rsa" thanks @absynth76 - fix: also add SameSite=None to by-value session cookies - bump to 2.4.2rc0 01/30/2020 - try to fix graceful restart crash; see #458 - release 2.4.1 01/29/2020 - always add a SameSite value to the Set-Cookie header to satisfy upcoming Chrome/Firefox changes this can be overridden by using, e.g.: SetEnvIf User-Agent ".*IOS.*" OIDC_SET_COOKIE_APPEND=; - release 2.4.1rc6 01/22/2020 - URL encode logout url in session management JS; thanks Paolo Battino - bump to 2.4.1rc5 01/15/2020 - add value of OIDC_SET_COOKIE_APPEND env var to Set-Cookie headers useful for handling changing/upcoming SameSite behaviors across different browsers, e.g.: SetEnvIf User-Agent ".*IOS.*" OIDC_SET_COOKIE_APPEND=SameSite=None - bump to 2.4.1rc4 01/08/2020 - support 407 option on OIDCUnAuthAction 12/09/2019 - fix parsing of values from metadata files when the default is non-NULL (e.g. UNSET) - enforce OIDCIDTokenSignedResponseAlg and OIDCUserInfoSignedResponseAlg; see #435 - bump to 2.4.1rc2 - support login with OIDC session management; address #456 - bump to 2.4.1rc3 12/05/2019 - add the possibility to use a public key instead of a certificate for OIDCPublicKeyFiles parameter - added an alpine dockerfile =~ 20MB container size 12/04/2019 - return 200 OK for backchannel logout if session not found - bump to 2.4.1rc1 11/19/2019 - make cleaning of expired state cookies log with a warning rather than an error; thanks Pavel Drobov - bump to 2.4.1rc0 10/03/2019 - improve validation of the post-logout URL parameter on logout; thanks AIMOTO Norihito; closes #449 - release 2.4.0.3 - clear any existing chunked cookies when setting a non-chunked cookie; prevents login loops in some scenarios 08/28/2019 - fixes #447 #441 : changed storing POST params from localStorage to sessionStorage due to some issue of losing data in localStorage in Firefox (private mode) 08/22/2019 - release 2.4.0 08/16/2019 - revert 3d95b4a3fbc493c6acc745626ac33143eb4968bf: don't return early from the content handler 08/15/2019 - be smart about picking the token endpoint authentication method when not configured explicitly: don't choose the first one published by the OP but prefer client_secret_basic if that is listed as well see: panva/node-oidc-provider#514; thanks @richard-drummond and @panva - bump to 2.4.0rc24 08/12/2019 - fix not clearing claims in session when setting claims to null; closes #445; thanks @FilipVujicic 08/12/2019 - fix JWT decryption crashing on non-null terminated input - bump to 2.4.0rc23 08/09/2019 - add logout_on_error option to OIDCRefreshAccessTokenBeforeExpiry to kill the session when refreshing an access token fails; thanks @rickyepoderi - bump to 2.4.0rc22 08/08/2019 - no longer use the fixup handler for environment variable setting but do it as part of the authn handler - bump to 2.4.0rc21 08/04/2019 - avoid decoding non-form-encoded POST data; closes #443 - bump to 2.4.0rc20 08/02/2019 - return DONE from the content handler early to prevent triggering other content handlers - fix `OIDCOAuthAcceptTokenAs post` so POST data is propagated and not lost; see #443 - bump to 2.4.0rc19 07/10/2019 - fix RSA JWK "x5c" parsing issue (e.g. when parsing "n" fails): explicitly set the "kid" into to JWK - bump to 2.4.0rc18 06/19/2019 - fix regression bug that includes a HTTP 500 message after rendering content - bump to 2.4.0rc17 06/14/2019 - fix regression bug when no per-provider keys have been configured and private_key_jwt is used - bump to 2.4.0rc15 06/06/2019 - use per-provider signing keys in private_key_jwt authentication towards token endpoint - bump to 2.4.0rc14 06/05/2019 - avoid passing empty key set for JWT decryption (solve but introduced in 2.4.0rc12) - bump to 2.4.0rc13 06/03/2019 - enable per-provider signing and encryption keys; limitations: - for request object signing and id_token decryption only - take the first configured key, no kid specification - no publishing of key information on client endpoints - no userinfo JWT decryption - no composite claims decryption - no backchannel logout with encrypted logout token (inherent) - bump to 2.4.0rc12 05/31/2019 - make sure the content handler is called for every request to the configured Redirect URI so all Apache processing is executed (e.g. setting headers with mod_headers) before returning the response thanks Don Sengpiehl (NB: this may affect browser behavior and backwards compatibility) - add ability to view session info in HTML via the session info hook: auth_request_method; closes #382; thanks @jdennis - bump to 2.3.8rc4 08/14/2018 - allow usage with LibreSSL; closes #380; thanks @hihellobolke - bump to 2.3.8rc3 08/04/2018 - don't return content with 503 since it will turn the HTTP status code into a 200; see #331 - bump to 2.3.8rc2 08/03/2018 - add option to set an upper limit to the number of concurrent state cookies via OIDCStateMaxNumberOfCookies; see #331 - make the default maximum number of parallel state cookies 7 instead of unlimited; see #331 - bump to 2.3.8rc1 07/30/2018 - fix using access token as endpoint auth method in introspection calls; closes #377; thanks @skauffmann 07/25/2018 - fix reading access_token form POST parameters when combined with `AuthType auth-openidc`; see #376; thanks Nicolas Salerno - bump to 2.3.8rc0 07/06/2018 - abort when string length for remote user name substitution is larger than 255 characters - release 2.3.7 07/04/2018 - fix Redis concurrency issue when used with multiple vhosts - bump to 2.3.7rc4 and 2.3.7rc5 06/29/2018 - add support for authorization server metadata with OIDCOAuthServerMetadataURL as in RFC 8414 - bump to 2.3.7rc3 06/23/2018 - refactor session object creation - bump to 2.3.7rc2 06/22/2018 - clear session cookie and contents if cache corruption is detected - bump to 2.3.7rc0 - use apr_pstrdup when setting r->user - reserve 255 characters in remote username substition instead of 50 - bump to 2.3.7rc1 06/15/2018 - add check to detect session cache corruption for server-based caches and cached static metadata - release 2.3.6 05/29/2018 - avoid using pipelining for Redis - bump to 2.3.6rc4 05/28/2018 - send Basic header in OAuth www-authenticate response if that's the only accepted method; thanks @puiterwijk 05/28/2018 - refactor Redis cache backend to solve issues on AUTH errors: a) memory leak and b) redisGetReply lagging behind - adjust copyright year/org - bump to 2.3.6rc3 05/23/2018 - fix buffer overflow in shm cache key set strcpy; thanks @kyprizel - bump to 2.3.6rc2 05/22/2018 - turn missing session_state from warning into a debug statement - fix missing "return" on error return from the OP; see #345; thanks @gergan - bump to 2.3.6rc1 05/19/2018 - explicitly set encryption kid so we're compatible with cjose >= 0.6.0 - bump to 2.3.6rc0 05/18/2018 - fix encoding of preserved POST data; see #338; thanks @timpuri - avoid buffer overflow in shm cache key construction; thanks @kyprizel - release 2.3.5 05/08/2018 - compile with with Libressl; closes #358; thanks @hihellobolke - bump to 2.3.5rc0 04/27/2018 - avoid crash when a relative logout URL parameter is passed in; thanks Vivien Delenne - release 2.3.4 03/22/2018 - interpret X-Forwarded-Host when doing XSRF protection on the after-logout URL; see #341; thanks @pepe79 - bump to 2.3.4rc4 02/06/2018 - add support for passing an access token in a HTTP Basic authentication password; thanks @puiterwijk - bump to 2.3.4rc3 01/26/2018 - send session management Javascript logging to debug; thanks @kerrermanisNL 01/25/2018 - add Cache-Control no-cache header to authorization requests to avoid replays of state/nonce; see #321 - bump to 2.3.4rc2 01/23/2018 - add explicit endpoint authentication method "bearer_access_token" 12/29/2017 - correct documentation on kid usage for OIDCOAuthVerifyCertFiles; closes #318 12/21/2017 - fix compiler warnings for OpenSSL 1.1.x - bump to 2.3.4rc1 11/21/2017 - fix bug where endpoint authentication method "private_key_jwt" would not co-exist with "none" - bump to 2.3.4rc0 11/16/2017 - add support for passing userinfo as a JSON object or JWT; see #311 - release 2.3.3 11/13/2017 - add support for authentication to the introspection endpoint with a bearer token using OIDCOAuthIntrospectionClientAuthBearerToken; thanks @cristichiru - bump to 2.3.3rc3 11/08/2017 - address a number of static code analysis issues - bump to 2.3.3rc2 10/10/2017 - avoid crash when no scheme is set on OIDCProviderMetadataURL; closes #303; thanks @iconoeugen - bump to 2.3.3rc1 10/6/2017 - avoid crash when no OIDCOAuthClientID is set for remote access token validation - don't enforce "iat" slack checks on locally validaed JWT access tokens - bump to 2.3.3rc0 09/18/2017 - release 2.3.2 09/11/2017 - fix "graceful" restart for shm/redis cache backends; see #296 - bump to 2.3.2rc8 09/05/2017 - optionally remove request object parameters from the authorization request URL with "copy_and_remove_from_request"; see #294 - bump to 2.3.2rc7 08/29/2017 - properly support JSON boolean values in metadata .conf files - add regex substitution for *RemoteUserClaim; thanks @hihellobolke - bump to 2.3.2rc6 08/27/2017 - add issuer specific redirect URI option ("issuer_specific_redirect_uri") for multi-provider setups to mitigate IDP mixup - bump to 2.3.2rc5 08/20/2017 - fix public clients; add endpoint authentication method "none" - bump to 2.3.2rc4 08/02/2017 - update experimental token binding support to https://tools.ietf.org/html/draft-ietf-tokbind-ttrp-01 and use header names prefixed with "Sec-"; depends on mod_token_binding >= 0.3.4 now - bump to 2.3.2rc3 08/01/2017 - don't abort when mutex operations fail - printout textual descriptions of errors returned by mutex operations - bump to 2.3.2rc2 07/28/2017 - fix issue with the combination of shared memory (shm) cache and using encryption (OIDCCacheEncrypt On) where the cache value would be corrupted after the first (successful) retrieval - bump to 2.3.2rc1 07/27/2017 - support paths that are relative to the Apache root dir for: OIDCHTMLErrorTemplate, OIDCPublicKeyFiles, OIDCPrivateKeyFiles, OIDCOAuthVerifyCertFiles, OIDCClientTokenEndpointCert, OIDCClientTokenEndpointKey, OIDCOAuthIntrospectionEndpointCert and OIDCOAuthIntrospectionEndpointKey - bump to 2.3.2rc0 07/19/2017 - handle multiple values in X-Forwarded-* headers as to better support chains of reverse proxies in front of mod_auth_openidc - log request headers in oidc_util_hdr_in_get - release 2.3.1 07/13/2017 - remove A128GCM/A192GCM from the supported algorithms in docs/auth_openidc.conf because cjose doesn't support A128GCM and A192GCM (yet) - bump to 2.3.1rc5 07/09/2017 - refactor oidc_get_current_url_port so that it assumes the default port when X-Forwarded-Proto has been set; closes #282 and may address #278 - bump to 2.3.1rc4 07/07/2017 - use the defined name (`Provided-Token-Binding-ID`) for the provided token binding ID HTTP header see: https://tools.ietf.org/html/draft-campbell-tokbind-ttrp-00#section-2.1 depends on mod_token_binding >= 0.3.0 now - bump to 2.3.1rc3 06/29/2017 - support sending the authentication request via HTTP POST through HTML/Javascript autosubmit - bump to 2.3.1rc2 06/28/2017 - support private_key_jwt and client_secret_jwt as client authentication methods for token introspection - bump to 2.3.1rc1 06/22/2017 - fix bug where token_endpoint_auth set to private_key_jwt would fail to provide the credential if client_secret wasn't set - bump to 2.3.1rc0 06/13/2017 - release 2.3.0 06/07/2017 - fix file cache backend: allow caching of non-filename friendly keys such as configuration URLs and JWKs URIs - enable JQ-based claims expression matching when compiled from source with --with-jq=, e.g.: Require claims_expr '.aud == "ac_oic_client" and (.scope | index("profile") != null)' - normalize cache backend logging - bump to 2.3.0rc3 06/06/2017 - avoid cleaning our own state cookie twice when it is expired - bump to 2.3.0rc2 06/02/2017 - refactor remote user handling so it allows for postfixing with the issuer value after applying the regex - bump to 2.3.0rc1 05/31/2017 - add support for custom actions to take after authorization fails with OIDCUnAutzAction this enables stepup authentication scenarios when combined with the following: - add OIDCPathAuthRequestParams that is configurable on a per-path basis and use OIDCAuthRequestParams for the static per-provider value - add OIDCPathScope that is configurable on a per-path basis and concatenate with OIDCScope as static per-provider value - support 3rd-party-init-SSO with additional authentication request params when a single static provider has been configured - add support for an empty OIDCClaimPrefix; can be used with OIDCWhiteListedClaims to protect selected headers - bump to 2.3.0rc0 05/30/2017 - support sending Authorization Request as "request" object in addition to "request_uri"; thanks @suttod - support nested claim matching in Require directives; thanks @suttod - support explicitly setting the "kid" of the private key in OIDCPrivateKeyFiles; thanks @suttod 05/25/2017 - fix cache fallback so it happens (when enabled) only after failure 05/19/2017 - make OIDCStripCookies work on AuthType oauth20 paths; closes #273; thanks Michele Danieli - bump to 2.2.1rc6 05/18/2017 - fix parse function of OIDCRequestObject configuration option; thanks @suttod 05/17/2017 - avoid crash when the X-Forwarded-Proto header is not correctly set by a reverse proxy in front of mod_auth_openidc 05/14/2017 - support JWT verification against multiple keys with no provided kid by looping over the provided keys with cjose 0.5.0 - remove OIDC RP certification files; moved to separate repository 05/04/2017 - improve documentation for OIDCCryptoPassphrase; closes #268 04/30/2017 - fix wrong return value for cache_file_set in the file cache backend (OIDCCacheType file); thanks Ernani Joppert Pontes Martins - bump to 2.2.1rc5 04/29/2017 - correctly log success/failure in cache_file_set - avoid decoding a JSON object and logging an error when the input is NULL e.g. when claims have not been resolved because userinfo endpoint is not set 04/20/2017 - support relative RedirectURIs; closes #200; thanks @moschlar - don't assume that having OIDCCryptPassphrase set means we should validate the config for openid-connect since it can now also be used to encrypt (auth20) cache entries - bump to 2.2.1rc4 04/08/2017 - fix potential crash on prefork process exit when used with Redis cache backend (3x) - bump to 2.2.1rc3 04/06/2017 - change warn log about missing token binding ID to debug log 04/05/2017 - allow for high session inactivity timeout max value - improve error message in oidc_util_http_send when ap_pass_brigade fails and mention possible interference with mod_deflate - bump to 2.2.1rc0 03/30/2017 - merge feature branch back to master: - better support for Single Page Applications, see: https://github.com/zmartzone/mod_auth_openidc/wiki/Single-Page-Applications - add session info hook that is configurable through OIDCInfoHook - add "AuthType auth-openidc" option that allows both "oauth20" and "openid-connect" on the same path - add encryption for all cache entries instead of just session data through OIDCCacheEncrypt - add cookie SameSite flag/policy through OIDCCookieSameSite - return HTTP 200 on OPTIONS requests to (unauthenticated) "oauth20" paths - add fallback to a by-value session cookie if the primary session cache fails with OIDCSessionCacheFallbackToCookie - add support for black- and/or white-listing claims with OIDCBlackListedClaims and OIDCWhiteListedClaims - add prototype token binding support in conjunction with: https://github.com/zmartzone/mod_token_binding: - for state & session cookies, see: https://github.com/TokenBinding/Internet-Drafts - for ID tokens with OpenID Connect Token Bound Authentication support, see: http://openid.net/specs/openid-connect-token-bound-authentication-1_0.html - for Authorization Codes with OAuth 2.0 Token Binding for Authorization Codes, see: https://tools.ietf.org/html/draft-ietf-oauth-token-binding - refactoring: - refactor session state, proto state and headers into getters/setters functions - refactor PKCE support - fix removing session state from cache on logout - fix clearing chunked session cookies on logout; closes #246; thanks @Jharmuth - release 2.2.0 02/20/2017 - security fix: scrub headers for "AuthType oauth20" - release 2.1.6 02/15/2017 - improve logging of session max duration and session inactivity timeout - refactor so that the call to the refresh hook also resets the session inactivity timeout and passes tokens down 02/14/2017 - treat only "X-Requested-With: XMLHttpRequest" header as a non-browser client; closes #228 ; thanks @mguillem - improve error message on state timeout; closes #226; thanks @security4java 02/09/2017 - correctly parse "kid" in OIDCPublicKeyFiles and OIDCOAuthVerifyCertFiles; thanks Alessandro Papacci - bump to 2.1.6rc2 02/07/2017 - fix parsing of mandatory/optional attribute in OIDCOAuthTokenExpiryClaim; closes #225; thanks Alessandro Papacci - bump to 2.1.6rc1 02/06/2017 - improve logging around the availability of session management; closes #223 02/02/2017 - interpret OIDCUnAuthAction also when the maximum session duration has been exceeded; see #220 - bump to 2.1.6rc0 01/30/2017 - security fix: scrub headers when `OIDCUnAuthAction pass` is used for an unauthenticated user - release 2.1.5 01/29/2017 - fix error message about passing id_token with session type client-cookie; mentioned in #220 - bump to 2.1.5rc0 01/25/2017 - release 2.1.4 01/18/2017 - don't echo the query parameters on the error page when an invalid request is made to the Redirect URI; closes #212; thanks @LukasReschke 01/14/2017 - use dynamic memory buffer for writing HTTP call responses; solves curl/mpm-event interference; see #207 - bump to 2.1.4rc1 01/10/2017 - don't crash when data is POST-ed to the redirect URL, it has just 1 POST parameter and it is not "response_mode" 01/2/2017 - remove trailing linebreaks from input in test-cmd tool - bump copyright year to 2017 12/14/2016 - support Libre SSL, see #205, thanks @AliceWonderMiscreations - update OIDC logout support to Front-Channel Logout 1.0 draft 01: http://openid.net/specs/openid-connect-frontchannel-1_0.html - bump to 2.1.4rc0 12/13/2016 - release 2.1.3 12/12/2016 - don't rollover session id's and keep the same session cookie name for cache storage over session updates - bump to 2.1.3rc0 11/19/2016 - release 2.1.2 11/18/2016 - fix crash when searching for keys with a kid, there's no initial match and x5t values exist for the non-matching keys; closes #196 11/9/2016 - remove stale claims from session when refreshing them from the userinfo endpoint fails; addresses #194 - release 2.1.1 11/8/2016 - log readable error messages when memcache operations fail 11/6/2016 - fix memory leak when skipping jwks_uri keys with a non-matching "use" value 11/4/2016 - always restore id_token/claims on sub-requests so e.g. listing claims-protected subdirectories will work - remove obsolete functions for storing the session in the request state - bump to 2.1.1rc0 11/3/2016 - remove obsolete sessions from session cache; thanks @stevedave 11/1/2016 - release version 2.1.0 10/28/2016 - don't include encryption keys from the jwks_uri when verifying a JWT and no kid has been specified - fix memory leaks in composite claim handling 10/27/2016 - handle aggregated and distributed claims from the userinfo endpoint - only pick private_key_jwt token endpoint authentication if a private key is configured; closes #189 - bump to 2.0.1rc7 10/24/2016 - add OpenID Connect RP certification test script - handle non-integer exp/iat timestamps; closes #187; thanks @drdivano 10/21/2016 - bugfix: first truncate files before writing them - support refreshing provider metadata based on timestamp and OIDCProviderMetadataRefreshInterval 10/20/2016 - bugfix: correctly truncate encryption keys derived from client secret for algorithms that require a key size < 256 bits - add test/test-cmd tool - bugfix: return error on session cache failures; closes #185; thanks @solsson - bump to 2.0.1rc6 10/18/2016 - bugfix: JWTs with a header that doesn't specify a `kid` that would not validate when used with more than 1 key; closes #184; thanks @solsson - bump to 2.0.1rc5 10/13/2016 - urlencode provider URL cache key to fix file cache backend issue; closes #179, thanks @djahandarie 10/9/2016 - fix null pointer segfault in debug printout in oidc_util_read_form_encoded_params - fix OIDCOAuthAcceptTokenAs parsing flaw introduced in 2.0.0rc5 - bump to 2.0.1rc4 10/2/2016 - support presenting the access token to the userinfo endpoint in a POST parameter - bump to 2.0.1rc3 9/30/2016 - support WebFinger Discovery with URL-style user identifiers 9/28/2016 - fix memory leak in oidc_jwk_to_json - add "remove_at_cache" hook; addresses #177 - bump to 2.0.1rc2 9/27/2016 - add support for Request URI with signed and/or encrypted Request Objects - bump to 2.0.1rc1 9/22/2016 - refuse webfinger responses with an href value that is not on secure https - add userinfo JWT response verification and decryption 9/20/2016 - log the JWT header before optional decryption is applied 9/19/2016 - check that a sub claim returned from the userinfo endpoint matches the one in the id_token - fix issue in oidc_metadata_parse_url so that static default would not be honored - this only affected server-wide OIDCClientJwksUri usage in dynamic client registration - non-functional changes for OIDC RP certification: - explicitly log the client authentication method when calling the token endpoint - log the keys that are included for token verification - bump to 2.0.1rc0 9/9/2016 - fix overriding provider token endpoint auth with static config when not set in .conf file - don't add our own cookies to the incoming headers - allow stripping cookies from the Cookie header sent to the application/backend with OIDCStripCookies - release 2.0.0 9/5/2016 - encapsulate (sub-)directory config handling and fix merging so values can be set back to default values in subdirs - bump to 2.0.0rc5 9/2/2016 - fix JWK creation when no client secret is set e.g. in Implicit flows; closes #168; thanks @asc1 - bump to 2.0.0rc4 9/1/2016 - fix HTML decoding of OIDCPreservePost data; closes #165 - limit max POST data size to 1Mb - allow chunked data in POST handling; revise handler - change preserve POST JSON data format to urlencoded for performance reasons 8/31/2016 - allow setting the token endpoint authentication method in the .conf file (for dynamic client registration that sets the .client) 8/30/2016 - pass refresh token in header/environment variable with OIDCPassRefreshToken; thanks Amit Joshi - fix front-channel img-style logout with newer versions of PingFederate that don't send an Accept: image/png header 8/29/2016 - preserve POST data across authentication requests and discovery with OIDCPreservePost - bump to 2.0.0rc3 8/24/2016 - fix parsing of OIDCOAuthAcceptTokenAs to accept options following ":" - bump to 2.0.0rc2 8/5/2016 - delete the debian directory - rename OIDCOAuthTokenEndpointCert/Key to OIDCOAuthIntrospectionEndpointCert/Key - pre-release 2.0.0rc1 7/30/2016 - encrypt state/session JWT cookies and session JWT cache values for non-shm storages 7/29/2016 - use cjose - https://github.com/cisco/cjose (master) - for JOSE functions - use stricter input parsing functions for configuration values - bump to 2.0.0rc0 7/21/2016 - support TLS client authentication to token and introspection endpoints - bump to 1.9.0rc3 7/19/2016 - add support for chunked session cookies; closes #153; thanks @glatzert - bump to 1.9.0rc2 7/9/2016 - fix Elliptic Curve signature verification for corrupted input - support OpenSSL 1.1.x - bump to 1.9.0rc1 7/5/2016 - use AUTHZ_DENIED instead of HTTP_UNAUTHORIZED in oidc_authz_checker; closes #151; thanks @gwollman - use signed JWTs for state/session cookies - achieve smaller client-cookie sizes for regular cases; no id_token is stored in the session: - (optional) id_token_hint no longer available in session management calls (logout/prompt=none) with "OIDCSessionType client-cookie" - "OIDCPassIDTokenAs serialized" is not available with "OIDCSessionType client-cookie" - bump to 1.9.0rc0 6/27/2016 - use EVP_CIPHER_CTX_new to avoid compilation errors with OpenSSL 1.1.0 - release 1.8.10 6/22/2016 - don't use local port setting for current URL determination when X-Forwarded-Host has been set - bump to 1.8.10rc4 6/20/2016 - fix memory leak in OAuth access token introspection result caching (introduced only in 1.8.10rc0) - fix setting private_key_jwt or client_secret_jwt with OIDCProviderTokenEndpointAuth - bump to 1.8.10rc3 6/19/2016 - allow setting OIDCRemoteUserClaim with values obtained from the userinfo endpoint; thanks @steve-dave - fix OIDCUnAuthAction pass mode for Apache 2.4 and in case `Require claim` primitives used for 2.4 and 2.2; thanks @steve-dave - bump to 1.8.10rc2 6/15/2016 - add support for JWT based client authentication to the token endpoint (client_secret_jwt, private_key_jwt) - bump to 1.8.10rc1 6/9/2016 - add per-path configurable token introspection result cache expiry with OIDCOAuthTokenIntrospectionInterval - bump to 1.8.10rc0 6/5/2016 - release 1.8.9 5/9/2016 - support 410 option on OIDCUnAuthAction; closes #141 - bump to 1.8.9rc6 5/1/2016 - avoid segmentation fault on invalid OIDC configuration when OIDCRedirectURI is not set; fixes #138; thanks @brianwcook - bump to 1.8.9rc5 4/18/2016 - fix get_current_url (proxy) case where r->parsed_uri.path would be null 4/13/2016 - improve X-Forwarded-Host handling over Host in a) port detection and b) remove port from host value - bump to 1.8.9rc4 4/10/2016 - do not require OIDCClientSecret in configs; allows for Implicit grant without setting a dummy client secret; closes #130 - allow for public clients calling the token endpoint - bump to 1.8.9rc3 4/9/2016 - ensure that claims from id_token are available for authz also when OIDCPassIDTokenAs does not contain "claims"; closes #129 - bump to 1.8.9rc2 4/3/2016 - return WWW-Authenticate header and error messages on OAuth paths where access is not granted; closes #124; thanks @spinto - bump to 1.8.9rc1 4/1/2016 - apr_jwe_decrypt_content_aesgcm() null terminate string, #128, thanks @jdennis - bump to 1.8.9rc0 3/10/2016 - release 1.8.8 3/7/2016 - issue a warning if the "openid" scope is not requested 3/6/2016 - sanitize the OIDCAuthNHeader value before setting the header; thanks @rfk - bump to 1.8.8rc7 3/5/2016 - log exact version of OpenSSL and EC/GCM/Redis support - tidy up auth_openidc.conf docs - bump to 1.8.8rc6 2/26/2016 - add option to refresh claims from the userinfo endpoint using OIDCUserInfoRefreshInterval; see #119 - merge id_token claims in to the set of claims used for authorization for Apache >=2.4; see #120 - bump to 1.8.8rc5 2/23/2016 - make state cookie a session cookie and clean expired cookies on entry (merge of fix-firefox-cookie-storage) - fix HTML error template initialization in vhosts - bump to 1.8.8rc4 2/22/2016 - don't authenticate (redirect/state) when X-Requested-With header exists; as suggested in #113 - bump to 1.8.8rc3 2/18/2016 - pass plain state to the token endpoint on code flows: https://tools.ietf.org/html/draft-jones-oauth-mix-up-mitigation-01 - fix loose (prefix-only) matching of cookie names - allow passing OAuth bearer token as a cookie (OIDCOAuthAcceptTokenAs extension for PingAccess) - bump to 1.8.8rc2 2/11/2016 - include token_endpoint_auth_method in dynamic client registration request, set to selected method from provider 2/10/2016 - Elliptic Curve support now requires OpenSSL 1.0.1 detection - bump to 1.8.8rc1 1/14/2016 - add support for passing in OAuth bearer tokens as one or more of: header, post param or query param (OIDCOAuthAcceptTokenAs) - bump to 1.8.8rc0 1/8/2016 - release 1.8.7 1/7/2016 - update copyright year 12/17/2015 - enforce strict matching of issuer in Discovery document against the originally requested issuer - check iss/client_id if present in an authentication response - push a hash of state to the token endpoint on code flows - bump to 1.8.7rc4 12/9/2015 - improve debug logging around session management capabilities (i.e. enabled/disabled) - return 404 for op/rp iframes if session management is not enabled - bump to 1.8.7rc3 12/4/2015 - add support for RFC 7636 PKCE plain & S256 https://tools.ietf.org/html/rfc7636 - bump to 1.8.7rc2 12/3/2015 - fix crash when using a custom error template and the error description is NULL - fix crash when target_link_uri is not a valid URI or parts are empty - fix memory corruption when using custom html template across different server requests; closes #106 - bump to 1.8.7rc1 11/18/2015 - fix compiler warning on double sizeof call; close #103; thanks to @dcb314 - bump to 1.8.7rc0 10/26/2015 - add option to make session cookie persistent; closes #97 - release 1.8.6 10/19/2015 - add support for applying a custom HTML error template with OIDCHTMLErrorTemplate - bump to 1.8.6rc3 10/12/2015 - check the cookie domain that the session was created for against the configured domain - log a warning if the Set-Cookie value length is greater than 4093 bytes - include and prioritize the X-Forwarded-Host header in hostname determination - allow for missing Host header i.e. HTTP 1.0 - return DONE instead of HTTP_UNAUTHORIZED with Discovery page (prevent double HTML in HTTP 1.0) - use apr_strnatcmp instead of strcmp in util.c and mod_auth_openidc.c - bump to 1.8.6rc2 10/9/2015 - support subdomain cookies in OIDCCookieDomain checks; PR #96, thanks @pfiled - bump to 1.8.6rc1 10/6/2015 - add key identifier ("kid") option to `OIDCOAuthVerifySharedKeys`, `OIDCOAuthVerifyCertFiles` and `OIDCPublicKeyFiles` configs - bump to 1.8.6rc0 9/21/2015 - support (non-sid-based) HTTP logout: http://openid.net/specs/openid-connect-logout-1_0.html - release 1.8.5 9/16/2015 - improve logging on provider/client/conf metadata parsing failures; closes #94 - bump to 1.8.5rc7 9/9/2015 - fix parsing of OIDCOAuthTokenExpiryClaim, thanks to @bester #90 - bump to 1.8.5rc6 9/4/2015 - add CSRF protection to external Discovery as well - allow browser back after authorization response, see #89 - handle invalid (expired) authorization response state by sending the user to OIDCDefaultURL, see #86 - bump to 1.8.5rc5 8/26/2015 - add CSRF protection to Discovery - bump to 1.8.5rc4 8/19/2015 - support encrypted JWTs using A192KW for key encryption and A192CBC-HS384 for content encryption - bump to 1.8.5rc3 8/15/2015 - support encrypted JWTs using RSA-OAEP for key encryption and A128GCM,A192GCM,A256GCM for content encryption - bump to 1.8.5rc2 8/4/2015 - support for OIDCUnAuthAction: how to act on unauthenticated requests (OIDCReturn401 is deprecated) - bump to 1.8.5rc1 7/15/2015 - add authentication option for Redis server with OIDCRedisCachePassword - bump to 1.8.5rc0 7/3/2015 - allow for compilation on Windows using VS 2013 - bump to 1.8.4 6/30/2015 - improve memcache logging: don't report cache misses as an error, thanks to @scottdear - work around JSON timestamp print modifier issue (%lld) on some platforms, thanks to @ralphvanetten - bump to 1.8.4rc3 6/24/2015 - support passing claims as environment variables (OIDCPassClaimsAs) - bump to 1.8.4rc1 6/22/2015 - correct debug printout in oidc_util_read_form_encoded_params 6/20/2015 - avoid double free of JWT after parsing errors have been encountered - bump to 1.8.4rc0 6/19/2015 - make public keys for encrypted access tokens available in OAuth-only configurations; fixes #74 - remove exceptions for accounts.google.com since Google is OpenID Connect compliant now - release 1.8.3 6/15/2015 - add a complete JWT sample that includes validation of "exp" and "iat" to the test suite 6/10/2015 - allow JSON string values for the "active" claim in access token validation responses - bump to 1.8.3rc4 6/7/2015 - improve error logging on non-supported JWT signing/encryption algorithms - bump to 1.8.3rc3 5/31/2015 - merge id_token ("iss"!) and user info claims for authz processing - bump to 1.8.3rc2 5/29/2015 - fix hash comparison when padded, thanks to @steverc as mentioned in #65 - fix post-logout URL being set to SSO URL - add post-logout URL validation, thanks to @davidbernick - bump to 1.8.3rc1 5/18/2015 - fix OpenSSL version detection for Elliptic Curve support in apr_jwt_signature_to_jwk_type: include opensslv.h - fix hash length calculation for Elliptic Curve algorithms - release 1.8.2 5/5/2015 - release 1.8.1 4/21/2015 - allow setting OIDCDiscoverURL inside of Directory and Location directives as well - bump to 1.8.1rc5 4/20/2015 - allow setting OIDCCookie outside of Directory and Location directives as well - bump to 1.8.1rc4 4/17/2015 - add support for applying regular expressions to OIDCRemoteUserClaim and OIDCOAuthRemoteUserClaim - bump to 1.8.1rc3 4/12/2015 - make token expiry parsing of introspection result configurable (OIDCOAuthTokenExpiryClaim) - increase SHM cache key size from 255 to 512 (allows for JWT access tokens cache keys for introspection result) - bump to 1.8.1rc2 4/1/2015 - avoid timing attacks on symmetric key signature/hash comparisons as pointed out by @timmclean - bump to 1.8.1rc1 3/19/2015 - merge #57: fix build with OpenSSL <1.0 re. apr_jws_signature_is_ec (thanks to @szakharchenko) 2/26/2015 - release 1.8.0 2/23/2015 - avoid including line feeds in header values (thanks to @forkbomber and @ekanthi) - bump to 1.8.0rc5 2/16/2015 - fix free() crash on simple-valued error printouts - fix returning keys without a "kid" - fix searching for keys with a "x5t" thumbprint - refactor response type handling; more strict matching of requested response type - make compiled in Redis support optional - fix oauth.introspection_endpoint_method in initialization - bump to 1.8.0rc4 2/15/2015 - add support for configurable introspection HTTP method (OIDCOAuthIntrospectionEndpointMethod) - add preliminary support for GET-style logout - bump to 1.8.0rc2 2/12/2015 - add support for configuration of maximum session duration - bump to 1.8.0rc1 2/9/2015 - check JWT signature against all configured keys (jwks_uri) if no "kid" is provided, not just the first one - revise JOSE JWS handling part 2 - complete support for local JWT access token validation - bump to 1.8.0rc0 2/5/2015 - fix symmetric key decryption of JWTs (A128CBC-HS256/A256CBC-HS512) - sha256 client secrets before using them as symmetric keys for decryption - extended decryption test coverage; avoid double printouts on error - refactor JWT header parsing - simplify JWK URI refresh handling - release 1.7.3 2/4/2015 - revise JOSE JWK handling part 1 - change change target_uri parameter name to target_link_uri following draft-bradley-oauth-jwt-encoded-state-03 - extended tests with stubs - refactor JWT validation (iss, exp, iat) - fix memory leak with RSA key conversion in apr_jwk.c - apr_jwk_rsa_bio_to_key - bump to 1.7.3rc4 1/25/2015 - Allow {... "error": null ...} in JSON responses. (@fatlotus) 1/22/2015 - fix configuration validation check where no config would be checked if OIDCProviderIssuer is set but both OIDRedirectURI and OIDCCryptoPassphrase are not set - add preliminary support for local JWT access token validation - bump to 1.7.3rc1 - sanitize set cookie syntax (get rid of extraneous ";") 1/21/2015 - add support for OIDCOAuthIntrospectionTokenParamName (incl. Google OAuth 2.0 access token validation) - add a sample OAuth 2.0 config for Google access tokens to README.md - release 1.7.2 - add APXS2_OPTS to configure.ac to accommodate RPM builds - bump to 1.7.3rc0 - fix JWT timestamp (iat/exp) initialization issue - fix README.md on Google's scope required for returning the "hd" claim 1/14/2015 - add Apache function stubs to enable extending the scope of tests - add tests for oidc_proto_validate_access_token and oidc_proto_validate_code - bump to 1.7.2rc3 1/12/2015 - improve accuracy of expired/invalid session handling 1/11/2015 - add error type and return values to JOSE functions - fix return result on error in function that decrypts CEK - bump to 1.7.2rc2 1/1/2015 - update copyright to 2015 - use json_int_t (seconds) for "exp" and "iat" fields, instead of apr_time_t (microseconds) - correct expiry debug printout - bump to 1.7.2rc1 12/15/2014 - fix Redis reconnect behavior: avoid keep reconnecting after first failure - bump to 1.7.2rc0 12/12/2014 - support passing cookies specified in OIDCPassCookies from browser on to OP/AS calls (for loadbalancing purposes) - release 1.7.1 - document OIDCPassCookies in auth_openidc.conf 12/10/2014 - reconnect to the Redis server after I/O failure as raised in #43 - bump to 1.7.1rc4 12/8/2014 - return http 500 when detecting requests that will loop on return - bump to 1.7.1rc3 12/3/2014 - require the expiring access_token on the refresh hook for XSRF protection - pass error codes back to the caller when the refresh hook fails - bump to 1.7.1rc2 12/2/2014 - improve handling of non-string (=non-compliant) error responses 11/26/2014 - make shared memory cache entry max size configurable through OIDCCacheShmEntrySizeMax - add OIDCReturn401 configuration primitive - bump to 1.7.1rc1 11/11/2014 - allow OIDCRedirectURI's with an empty path and fix crash; thanks to @CedricCabessa 11/9/2014 - support for adding configurable JSON parameters to Dynamic Client Registration requests 11/5/2014 - release 1.7.0 10/30/2014 - correct expires_in conversion - first stab at HTML templating and make all html HTML 4.01 Strict compliant - bump to 1.7.0rc4 10/29/2014 - document refresh flow 10/28/2014 - scrub all OIDC_ headers - add support for the refresh_token flow + hook - pass the expires_in as an absolute timestamp in OIDC_access_token_expires - use a global mutex for the Redis cache - bump to 1.7.0rc3 10/27/2014 - generalize support for OAuth 2.0 token introspection and conform to: https://tools.ietf.org/html/draft-ietf-oauth-introspection-00 10/26/2014 - support regular expressions in Require statements 10/24/2014 - add support for Redis cache backend; there's a dependency on hiredis headers/library now 10/21/2014 - refactor nonce generation and remove base64url padding from value 10/13/2014 - add libssl-dev to Debian control build dependencies - release 1.6.0 10/6/2014 - apply html encoding to error display - bump version number to 1.6.0rc4 10/2/2014 - avoid crash when downloading metadata from OIDCProviderMetadataURL fails - set OIDCProviderMetadataURL retrieval interval to 24 hours - return error on configurations mixing OIDCProviderMetadataURL and OIDCMetadataDir - bump version number to 1.6.0rc3 10/1/2014 - support provider configuration from a metadata URL (OIDCProviderMetadataURL) - bump version number to 1.6.0rc2 9/30/2014 - be less strict about issuer validation in metadata - refactor metadata.c - improve logging about userinfo endpoint 9/29/2014 - refactor cache so it is partitioned in to sections (i.e. avoid future key collisions) 9/25/2014 - add support for "x5c" JWK representation - return JWKS on jwks_uri with content-type "application/json" 9/17/2014 - remove support for the X-Frame-Options as it is not needed in 302s - create and use log macros that printout function name 9/16/2014 - support for passing runtime determined authentication request parameters in the discovery response - include name/version in logs and bump to 1.6.0rc1 - don't use the X-Frame-Options by default 9/13/2014 - add support for the X-Frame-Options header as recommended by the spec 9/12/2014 - set expiry on state cookies; don't clear session cookie after cache miss or corruption - fix JSON array memory leak in oauth.c 9/10/2014 - merge #34 (g10f), fix session management Javascript bug 9/3/2014 - improve error handling on hitting the redirect URI directly - fix set_slot functions for algorithm/url used as default for dynamic registration - rewording of auth_openidc.conf docs on JWK settings 9/1/2014 - add session management based on http://openid.net/specs/openid-connect-session-1_0.html (draft 21) 8/29/2014 - add configuration option to POST extra parameters to the token endpoint 8/26/2014 - correct cookie_path comparison - release 1.5.5 8/20/2014 - correctly error out on flows where no id_token was provided ("token") 8/19/2014 - fix debug printout on open redirect prevention - cleanup in-memory crypto context on shutdown - use default of "/" for OIDCCookiePath to simplify quickstart/simple deployments - disable OIDCMetadataDir in sample/default config file - clear session cookie after cache miss or corruption 8/18/2014 - add HttpOnly flag to cookies by default; can be disabled by config 8/14/2014 - support for passing the id_token in multiple formats (claims|payload|serialized) - release 1.5.4 8/13/2014 - pass the access_token in OIDC_access_token header to the application 8/9/2014 - merge #21 (Latinovic) to close #18 (big endian JWE issue) - merge #20 (wadahiro), support for "none" JWT signing algorithm 8/1/2014 - fix cache initialization/destroy leak - release 1.5.3 7/26/2014 - refactor http code; cleanup JSON encoding in client registration - refactor padding handling in base64url encoding/decoding 7/20/2014 - check for open redirect on passed target_link_uri - prevent JWE timing attacks on CEK; add JWE test - include client_id and scope values in resolved access_token 7/1/2014 - pass JSON objects in app HTTP headers as plain JSON - correct printout in id_token hash comparisons - add more tests - release 1.5.2 6/12/2014 - support third-party-initiated login as defined in the spec - release 1.5.1 - fix PF OAuth 2.0 RS functionality after upgrading to jansson 6/6/2014 - more changes for Debian packaging (1.5-3) 6/5/2014 - do not set Secure cookies for plain HTTP - add warning/errors when configured hosts/domains do not match - release 1.5 - changes for Debian packaging 6/4/2014 - fix passing integer claims on non-Mac OS X systems - fix claims-based authorization with integer values (@martinsrom) - fix getting the id_token from request state and error logging - add AUTHORS file with credits - migrate README to Markdown README.md 6/3/2014 - change JSON parser from https://github.com/moriyoshi/apr-json to http://www.digip.org/jansson/ 6/2/2014 - handle X-Forwarded-Proto/X-Forwarded-Port when running behind a proxy/load-balancer - release version 1.4 6/1/2014 - compile with OpenSSL <1.0 and but then disable Elliptic Curve verification - fix jwks_uri setting in nested vhosts - use OpenSSL_add_all_digests in initialization and EVP_cleanup on shutdown 5/31/2014 - README additions/improvements 5/29/2014 - correct big endian detection - allow for key identification in JWKs based on thumbprint (x5t) 5/24/2014 - add cache destroy function and destroy shm cache resources on shutdown 5/23/2014 - doc corrections to auth_openidc.conf 5/22/2014 - add implementation of OP-initiated-SSO based on: http://tools.ietf.org/html/draft-bradley-oauth-jwt-encoded-state-01 - fix nonce caching for replay prevention 5/21/2014 - correct README on enabling Google+ APIs before applying the sample Google configs - fix AuthNHeader setting and allow server-wide config too - avoid segfault on corrupted/non- JSON/JWT input 5/20/2014 - fix URL assembly when running on non-standard port - release 1.3 5/17/2014 - support outgoing proxy using OIDCOutgoingProxy - correct sample configs in documentation for missing OIDCCookiePath - support OIDCCookiePath in server-wide config as well 5/13/2014 - support configurable (custom) query parameters in the authorization request 5/12/2014 - support encrypted JWTs using A128KW and A256KW for the Content Encryption Key - support A256CBC-HS512 encrypted JWTs - support custom client JWKs URI 5/8/2014 - support encrypted JWTs using RSA1_5 and A128CBC-HS256 5/2/2014 - do not use ap_get_remote_host for browser fingerprinting 5/1/2014 - split out custom client config into separate .conf file - allow to override client_contact, client_name and registration_token in .conf file - remove OIDCRegistrationToken command for the static OP config 4/29/2014 - support JWT verification of ES256, ES384 and ES512 algorithms 4/28/2014 - support configurable response_mode (fragment, query or form_post) - use nonce in all flows except for OP Google and flows "code" or "code token" 4/26/2014 - make client secret optional (support self-issued OP) 4/25/2014 - support Hybrid flows 4/24/2014 - fix using Bearer token Authorization header on JSON POST calls - support using a Bearer token on client registration calls 4/22/2014 - match request and response type - check at_hash value on "token id_token" implicit flow - use shared memory caching by default - release 1.2 4/19/2014 - store response_type in state and make state a JSON object 4/18/2014 - support RSASSA-PSS token signing algorithms (PS256,PS384,PS512) 4/17/2014 - improve session inactivity timeout handling 4/16/2014 - set REMOTE_USER and HTTP headers on OAuth 2.0 protected paths 4/15/2014 - add session inactivity timeout - register all supported response_types during client registration and try to pick the one that matches the configured default - use long timeouts on JWK retrieval calls - allow for non-null but empty query parameters on implicit authorization response - simplify azp/aud and nonce handling - change session_type naming (to "server-cache"/"client-cookie") 4/14/2014 - factor out JOSE related code 4/3/2014 - add configurable claim name for the REMOTE_USER variable, optionally postfixed with the url-encoded issuer value; the default for the remote username is "sub@" now, makeing it unique across OPs - some refactoring of id_token validation functions - add INSTALL, move auth_openidc.conf to main directory - release 1.1 3/28/2014 - fix Require claim name mismatch for Apache 2.4 - fix hmac method/printout naming artifacts from earlier auto-search-and-replace - release v1.0.1 3/27/2014 - initial import named mod_auth_openidc - updated README - fix debian/changelog mod_auth_openidc-2.4.16.10/INSTALL000066400000000000000000000037431476721736500164060ustar00rootroot00000000000000Preferably you should use one of the pre-compiled binary packages, available for various platforms, see: https://github.com/OpenIDC/mod_auth_openidc/wiki#11-where-can-i-get-binary-packages and proceed with the Configuration section below. If your platform is not supported or you want to run the latest code, you can build from source as described below. Installation from source ======================== You will require development headers and tools for the following dependencies: Apache (>=2.0) cjose (>=0.4.1) OpenSSL (>=0.9.8) (>=1.0.1 for Elliptic Curve support) Curl (>=?) Jansson (>=2.0) (JSON parser for C) pcre3 (>=?) (Regular Expressions support) pkg-config and if you want Redis support: hiredis (>=0.9.0) (Redis client for C) Configure, make and install with: (run ./autogen.sh first if you work straight from the github source tree) ./configure --with-apxs=/opt/apache2/bin/apxs2 make make install Note that, depending on your distribution, apxs2 may be named apxs. FreeBSD users can use one of the following two options to install mod_auth_openidc: - To install the port: cd /usr/ports/www/mod_auth_openidc/ && make install clean - To add the package: pkg install ap24-mod_auth_openidc Configuration ============= Edit the configuration file for your web server. Depending on your distribution, it may be named '/etc/apache/httpd.conf' or something different. You need to add a LoadModule directive for mod_auth_openidc. This will look similar to this: LoadModule auth_openidc_module /usr/lib/apache2/modules/mod_auth_openidc.so To find the full path to mod_auth_openidc.so, you may run: apxs2 -q LIBEXECDIR This will print the path where Apache stores modules. mod_auth_openidc.so will be stored in that directory. After you have added the LoadModule directive, you must add the configuration for mod_auth_openidc. For a quickstart doing so, see the provided samples in the README.md file. For an exhaustive overview of all configuration primitives, see: auth_openidc.conf mod_auth_openidc-2.4.16.10/LICENSE.txt000066400000000000000000000261361476721736500172010ustar00rootroot00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. mod_auth_openidc-2.4.16.10/Makefile.am000066400000000000000000000066231476721736500174110ustar00rootroot00000000000000ACLOCAL_AMFLAGS=-I m4 noinst_LTLIBRARIES = libauth_openidc.la libauth_openidc_la_SOURCES = \ src/mod_auth_openidc.c \ src/state.c \ src/cfg/cfg.c \ src/cfg/cache.c \ src/cfg/provider.c \ src/cfg/oauth.c \ src/cfg/dir.c \ src/cfg/parse.c \ src/cfg/cmds.c \ src/cache/file.c \ src/cache/shm.c \ src/cache/common.c \ src/handle/authz.c \ src/handle/content.c \ src/handle/discovery.c \ src/handle/dpop.c \ src/handle/info.c \ src/handle/jwks.c \ src/handle/logout.c \ src/handle/refresh.c \ src/handle/request_uri.c \ src/handle/request.c \ src/handle/response.c \ src/handle/revoke.c \ src/handle/session_management.c \ src/handle/userinfo.c \ src/proto/auth.c \ src/proto/discovery.c \ src/proto/dpop.c \ src/proto/id_token.c \ src/proto/jwks.c \ src/proto/jwt.c \ src/proto/pkce.c \ src/proto/profile.c \ src/proto/proto.c \ src/proto/request.c \ src/proto/response.c \ src/proto/state.c \ src/proto/token.c \ src/proto/userinfo.c \ src/metrics.c \ src/oauth.c \ src/util.c \ src/http.c \ src/session.c \ src/metadata.c \ src/jose.c \ src/pcre_subst.c AM_CFLAGS = -DNAMEVER="@NAMEVER@" -I${top_srcdir}/src @APACHE_CFLAGS@ @OPENSSL_CFLAGS@ @CURL_CFLAGS@ @JANSSON_CFLAGS@ @CJOSE_CFLAGS@ @PCRE_CFLAGS@ AM_CPPFLAGS = @APACHE_CPPFLAGS@ AM_LDFLAGS = @APACHE_LDFLAGS@ LIBADD = @APACHE_LIBS@ @OPENSSL_LIBS@ @CURL_LIBS@ @JANSSON_LIBS@ @CJOSE_LIBS@ @PCRE_LIBS@ if HAVE_LIBHIREDIS libauth_openidc_la_SOURCES += \ src/cache/redis.c AM_CFLAGS += -DUSE_LIBHIREDIS @HIREDIS_CFLAGS@ LIBADD += @HIREDIS_LIBS@ endif if HAVE_MEMCACHE AM_CFLAGS += -DUSE_MEMCACHE libauth_openidc_la_SOURCES += \ src/cache/memcache.c endif if HAVE_LIBJQ AM_CFLAGS += -DUSE_LIBJQ @JQ_CFLAGS@ LIBADD += @JQ_LIBS@ endif if HAVE_LIBBROTLI AM_CFLAGS += -DUSE_LIBBROTLI @LIBBROTLIENC_CFLAGS@ @LIBBROTLIDEC_CFLAGS@ LIBADD += @LIBBROTLIENC_LIBS@ @LIBBROTLIDEC_LIBS@ endif if HAVE_LIBZ AM_CFLAGS += -DUSE_ZLIB @ZLIB_CFLAGS@ LIBADD += @ZLIB_LIBS@ endif noinst_HEADERS = \ src/cfg/cfg.h \ src/cfg/cfg_int.h \ src/cfg/cache.h \ src/cfg/provider.h \ src/cfg/oauth.h \ src/cfg/dir.h \ src/cfg/parse.h \ src/mod_auth_openidc.h \ src/state.h \ src/handle/handle.h \ src/proto/proto.h \ src/cache/cache.h \ src/oauth.h \ src/metadata.h \ src/session.h \ src/jose.h \ src/http.h \ src/util.h \ src/metrics.h \ src/const.h \ src/pcre_subst.h if HAVE_LIBHIREDIS noinst_HEADERS += \ src/cache/redis.h endif EXTRA_DIST = \ README.md \ ChangeLog \ INSTALL \ AUTHORS \ LICENSE.txt \ auth_openidc.conf \ test/ecpriv.key \ test/eccert.pem \ test/private.pem \ test/public.pem \ test/certificate.pem \ test/open-redirect-payload-list.txt noinst_DATA = mod_auth_openidc.la mod_auth_openidc.la: libauth_openidc.la ${APXS} -c -o $@ libauth_openidc.la ${AM_CFLAGS} ${LIBADD} install-exec-local: ${INSTALL} -d $(DESTDIR)@APACHE_MODULEDIR@ ${INSTALL} -p -m 755 .libs/mod_auth_openidc.so $(DESTDIR)@APACHE_MODULEDIR@/mod_auth_openidc.so uninstall-local: rm -f $(DESTDIR)@APACHE_MODULEDIR@/mod_auth_openidc.so mod_auth_openidc.la clean-local: rm -f mod_auth_openidc.la LDADD = libauth_openidc.la ${LIBADD} noinst_PROGRAMS = test/test-cmd test_test_cmd_SOURCES = test/test-cmd.c test/stub.c test_test_cmd_CFLAGS = ${AM_CFLAGS} -fPIC TESTS = test/test check_PROGRAMS = test/test test_test_SOURCES = test/test.c test/stub.c test_test_CFLAGS = ${AM_CFLAGS} -fPIC clang-format: clang-format -style=file -i $$(find . -name *.[ch] -maxdepth 3) mod_auth_openidc-2.4.16.10/README.md000066400000000000000000000223631476721736500166330ustar00rootroot00000000000000[![Build Status](https://github.com/OpenIDC/mod_auth_openidc/actions/workflows/build.yml/badge.svg)](https://github.com/OpenIDC/mod_auth_openidc/actions/workflows/build.yml) [OpenID Certification](https://openid.net/certification) [![CodeQL Analysis](https://github.com/OpenIDC/mod_auth_openidc/actions/workflows/codeql-analysis.yml/badge.svg)](https://github.com/OpenIDC/mod_auth_openidc/actions/workflows/codeql-analysis.yml) [![Coverity Scan Build Status](https://scan.coverity.com/projects/31119/badge.svg)](https://scan.coverity.com/projects/openidc-mod_auth_openidc) mod_auth_openidc ================ *mod_auth_openidc* is an OpenID Certified™ authentication and authorization module for the Apache 2.x HTTP server that implements the OpenID Connect 1.x and FAPI 2.x Relying Party functionality. Overview -------- This module enables an Apache 2.x web server to operate as an [OpenID Connect](http://openid.net/specs/openid-connect-core-1_0.html) *Relying Party* (RP) towards an OpenID Connect *Provider* (OP). It relays end user authentication to a Provider and receives user identity information from that Provider. It then passes on that identity information (a.k.a. claims) to applications protected by the Apache web server and establishes an authentication session for the identified user. The protected content, applications and services can be hosted by the Apache server itself or served from origin server(s) residing behind it by configuring Apache as a Reverse Proxy in front of those servers. The latter allows for adding OpenID Connect based authentication to existing applications/services/SPAs without modifying those applications, possibly migrating them away from legacy authentication mechanisms to standards-based OpenID Connect Single Sign On (SSO). By default the module sets the `REMOTE_USER` variable to the `id_token` `[sub]` claim, concatenated with the OP's Issuer identifier (`[sub]@[iss]`). Other `id_token` claims are passed in HTTP headers and/or environment variables together with those (optionally) obtained from the UserInfo endpoint. The provided HTTP headers and environment variables can be consumed by applications protected by the Apache server. Custom fine-grained authorization rules - based on Apache's `Require` primitives - can be specified to match against the set of claims provided in the `id_token`/ `userinfo` claims, see [here](https://github.com/OpenIDC/mod_auth_openidc/wiki/Authorization). Clustering for resilience and performance can be configured using one of the supported cache backends options as listed [here](https://github.com/OpenIDC/mod_auth_openidc/wiki/Caching). For a complete overview of all configuration options, see the file [`auth_openidc.conf`](https://github.com/OpenIDC/mod_auth_openidc/blob/master/auth_openidc.conf). This file can also serve as an include file for `httpd.conf`. Interoperability ---------------- *mod_auth_openidc* is [OpenID Certified™](https://openid.net/certification/#OPENID-RP-P) and supports the following specifications: - [OpenID Connect Core 1.0](http://openid.net/specs/openid-connect-core-1_0.html) *(Basic, Implicit, Hybrid and Refresh flows)* - [OpenID Connect Discovery 1.0](http://openid.net/specs/openid-connect-discovery-1_0.html) - [OpenID Connect Dynamic Client Registration 1.0](http://openid.net/specs/openid-connect-registration-1_0.html) - [RFC 7636 - Proof Key for Code Exchange by OAuth Public Clients](https://datatracker.ietf.org/doc/html/rfc7636) - [RFC 9126 - OAuth 2.0 Pushed Authorization Requests](https://datatracker.ietf.org/doc/html/rfc9126) - [RFC 9449 - OAuth 2.0 Demonstrating Proof of Possession (DPoP)](https://tools.ietf.org/html/rfc9449) - [FAPI 2.0 Security Profile](https://openid.net/specs/fapi-2_0-security-profile-ID2.html) - [OAuth 2.0 Form Post Response Mode 1.0](http://openid.net/specs/oauth-v2-form-post-response-mode-1_0.html) - [OAuth 2.0 Multiple Response Type Encoding Practices 1.0](http://openid.net/specs/oauth-v2-multiple-response-types-1_0.html) - [OpenID Connect Session Management 1.0](http://openid.net/specs/openid-connect-session-1_0.html) *see the [Wiki](https://github.com/OpenIDC/mod_auth_openidc/wiki/OpenID-Connect-Session-Management) for information on how to configure it)* - [OpenID Connect Front-Channel Logout 1.0](http://openid.net/specs/openid-connect-frontchannel-1_0.html) - [OpenID Connect Back-Channel Logout 1.0](https://openid.net/specs/openid-connect-backchannel-1_0.html) Support ------- #### Community Documentation can be found at the Wiki (including Frequently Asked Questions) at: [https://github.com/OpenIDC/mod_auth_openidc/wiki](https://github.com/OpenIDC/mod_auth_openidc/wiki) For questions, issues and suggestions use the Github Discussions forum at: [https://github.com/OpenIDC/mod_auth_openidc/discussions](https://github.com/OpenIDC/mod_auth_openidc/discussions) #### Commercial For commercial - subscription based - support and licensing please contact: [sales@openidc.com](mailto:sales@openidc.com) How to Use It ------------- ### OpenID Connect SSO with Google+ Sign-In Sample configuration for using Google as your OpenID Connect Provider running on `www.example.com` and `https://www.example.com/example/redirect_uri` registered as the *redirect_uri* for the client through the Google API Console. You will also have to enable the `Google+ API` under `APIs & auth` in the [Google API console](https://console.developers.google.com). ```apache OIDCProviderMetadataURL https://accounts.google.com/.well-known/openid-configuration OIDCClientID OIDCClientSecret # OIDCRedirectURI is a vanity URL that must point to a path protected by this module but must NOT point to any content OIDCRedirectURI https://www.example.com/example/redirect_uri OIDCCryptoPassphrase AuthType openid-connect Require valid-user ``` Note if you want to securely restrict logins to a specific Google Apps domain you would not only add the `hd=` setting to the `OIDCAuthRequestParams` primitive for skipping the Google Account Chooser screen, but you must also ask for the `email` scope using `OIDCScope` and use a `Require claim` authorization setting in the `Location` primitive similar to: ```apache OIDCScope "openid email" Require claim hd: ``` The above is an authorization example of an exact match of a provided claim against a string value. For more authorization options see the [Wiki page on Authorization](https://github.com/OpenIDC/mod_auth_openidc/wiki/Authorization). ### Quickstart with a generic OpenID Connect Provider 1. install and load `mod_auth_openidc.so` in your Apache server 1. configure your protected content/locations with `AuthType openid-connect` 1. set `OIDCRedirectURI` to a "vanity" URL within a location that is protected by mod_auth_openidc 1. register/generate a Client identifier and a secret with the OpenID Connect Provider and configure those in `OIDCClientID` and `OIDCClientSecret` respectively 1. and register the `OIDCRedirectURI` as the Redirect or Callback URI with your client at the Provider 1. configure `OIDCProviderMetadataURL` so it points to the Discovery metadata of your OpenID Connect Provider served on the `.well-known/openid-configuration` endpoint 1. configure a random password in `OIDCCryptoPassphrase` for session/state encryption purposes ```apache LoadModule auth_openidc_module modules/mod_auth_openidc.so OIDCProviderMetadataURL /.well-known/openid-configuration OIDCClientID OIDCClientSecret # OIDCRedirectURI is a vanity URL that must point to a path protected by this module but must NOT point to any content OIDCRedirectURI https:///secure/redirect_uri OIDCCryptoPassphrase AuthType openid-connect Require valid-user ``` For details on configuring multiple providers see the [Wiki](https://github.com/OpenIDC/mod_auth_openidc/wiki/Multiple-Providers). ### Quickstart for Other Providers See the [Wiki](https://github.com/OpenIDC/mod_auth_openidc/wiki) for configuration docs for other OpenID Connect Providers: - [GLUU Server](https://github.com/OpenIDC/mod_auth_openidc/wiki/Gluu-Server) - [Keycloak](https://github.com/OpenIDC/mod_auth_openidc/wiki/Keycloak) - [Microsoft Entra ID (Azure AD)](https://github.com/OpenIDC/mod_auth_openidc/wiki/Microsoft-Entra-ID--(Azure-AD)) - [Sign in with Apple](https://github.com/OpenIDC/mod_auth_openidc/wiki/Sign-in-with-Apple) - [Curity Identity Server](https://github.com/OpenIDC/mod_auth_openidc/wiki/Curity-Identity-Server) - [LemonLDAP::NG](https://github.com/OpenIDC/mod_auth_openidc/wiki/LemonLDAP::NG) - [GitLab](https://github.com/OpenIDC/mod_auth_openidc/wiki/GitLab-OAuth2) - [Globus](https://github.com/OpenIDC/mod_auth_openidc/wiki/Globus) and [more](https://github.com/OpenIDC/mod_auth_openidc/wiki/Useful-Links) Disclaimer ---------- *This software is open sourced by OpenIDC, a subsidiary of ZmartZone Holding B.V. For commercial services you can contact [OpenIDC](https://www.openidc.com) as described above in the [Support](#support) section.* mod_auth_openidc-2.4.16.10/SECURITY.md000066400000000000000000000032341476721736500171410ustar00rootroot00000000000000# Security Policy ## Supported Versions | Version | Supported | | ------- | ------------------ | | 2.4.x | :white_check_mark: | | < 2.4.0 | :x: | ## Reporting a Vulnerability Please send an e-mail to support@openidc.com with a description of: - a brief description of the vulnerability - how the vulnerability can be observed - optionally the type of vulnerability and any related OWASP category - non-destructive exploitation details ## Followup After submitting your vulnerability report, you will receive an acknowledgement reply usually within 24 working hours of your report being received. The team will triage the reported vulnerability, and respond as soon as possible to let you know whether further information is required, whether the vulnerability is in or out of scope, or is a duplicate report. Priority for bug fixes or mitigations is assessed by looking at the impact severity and exploit complexity. When the reported vulnerability is resolved, or remediation work is scheduled, the Support team will notify you, and invite you to confirm that the solution covers the vulnerability adequately. You are particularly invited to give us feedback on the disclosure handling process, the clarity and quality of the communication relationship, and of course the effectiveness of the vulnerability resolution. This feedback will be used in strict confidence to help us improve our processes for handling reports, developing services, and resolving vulnerabilities. Where a report qualifies, we will offer to include you on our thanks and acknowledgement page. We will ask you to confirm the details you want included before they are published. mod_auth_openidc-2.4.16.10/auth_openidc.conf000066400000000000000000002226541476721736500206720ustar00rootroot00000000000000######################################################################################## # # Common Settings # ######################################################################################## # (Mandatory) # The redirect_uri for this OpenID Connect client; this is a vanity URL # that must ONLY point to a path on your server protected by this module # but it must NOT point to any actual content that needs to be served. # You can use a relative URL like /protected/redirect_uri if you want to # support multiple vhosts that belong to the same security domain in a dynamic way #OIDCRedirectURI https://www.example.com/protected/redirect_uri # (Mandatory) # Set a password for crypto purposes, this is used for: # - encryption of the (temporary) state cookie # - encryption of cache entries, that may include the session cookie, see: OIDCCacheEncrypt and OIDCSessionType # Note that an encrypted cache mechanism can be shared between servers if they use the same OIDCCryptoPassphrase # If the value begins with exec: the resulting command will be executed and the # first line returned to standard output by the program will be used as the password, e.g.: # OIDCCryptoPassphrase "exec:/bin/bash -c \"head /dev/urandom | tr -dc A-Za-z0-9 | head -c 32\"" # (notice that the above typically only works in non-clustered environments) # The command may be absolute or relative to the web server root. # # A second value can be used temporarily in case of passphrase rollover: the first (i.e. new) passphrase # will be used for encryption of new values (including a "kid" in the JWEs during the time 2 values are defined), # both values will be used for verification (leveraging the "kid" if present); for seamless rollover one should # (at minimum) wait for OIDCSessionInActivityTimeout seconds before removing the 2nd (i.e. old) passprase again. #OIDCCryptoPassphrase [ | "exec:/path/to/otherProgram arg1" ] [ | "exec:/path/to/otherProgram arg2" ] # # All other entries below this are optional though some may be required in a # particular setup e.g. OAuth 2.0 Resource Server vs. OpenID Connect Relying Party # # When using multiple OpenID Connect Providers, possibly combined with Dynamic Client # Registration and account-based OP Discovery. # Specifies the directory that holds metadata files (must be writable for the Apache process/user). # When not specified, it is assumed that we use a single statically configured provider as # described under the section "OpenID Connect Provider" below, most likely using OIDCProviderMetadataURL. #OIDCMetadataDir /var/cache/apache2/mod_auth_openidc/metadata ######################################################################################## # # OpenID Connect Provider # # For configuration of a single static provider, not using OpenID Connect Provider Discovery. # ######################################################################################## # URL where OpenID Connect Provider metadata can be found (e.g. https://accounts.google.com/.well-known/openid-configuration) # The obtained metadata will be cached and refreshed every 24 hours. # If set, individual entries below will not have to be configured but can be used to add # extra entries/endpoints to settings obtained from the metadata. # If OIDCProviderMetadataURL is not set, the entries below it will have to be configured for a single # static OP configuration or OIDCMetadataDir will have to be set for configuration of multiple OPs. #OIDCProviderMetadataURL # OpenID Connect Provider issuer identifier (e.g. https://localhost:9031 or https://accounts.google.com) # Used when OIDCProviderMetadataURL is not defined or the metadata obtained from that URL does not set it. #OIDCProviderIssuer # OpenID Connect Provider Authorization Endpoint URL (e.g. https://localhost:9031/as/authorization.oauth2) # Used when OIDCProviderMetadataURL is not defined or the metadata obtained from that URL does not set it. #OIDCProviderAuthorizationEndpoint # OpenID Connect Provider JWKS URL (e.g. https://localhost:9031/pf/JWKS) # i.e. the URL on which the signing keys for this OP are hosted, in JWK formatting # Used when OIDCProviderMetadataURL is not defined or the metadata obtained from that URL does not set jwks_uri. #OIDCProviderJwksUri # OpenID Connect Provider Signed JWKS URL (e.g. https://localhost:9031/pf/JWKS) followed by the verification key set # formatted as either JWK or JWKS. The verification key set is used to verify the provided JWKs value. # Specifying multiple keys allows the OP rotate the key used for signing the JWKs. # I.e. this is the URL on which the ID Token signing keys for this OP are hosted, in verifiable JWT formatting # rather than relying on TLS for authentication and integrity protection. # Used when OIDCProviderMetadataURL is not defined or the metadata obtained from that URL does not set signed_jwks_uri. # When defined it takes precedence over OIDCProviderJwksUri # Examples: # OIDCProviderSignedJwksUri https://localhost:9031/pf/JWKS "{\"kty\":\"oct\", \"k\":\"AyM1SysPpbyDfgZld3umj1qzKObwVMkoqQ-EstJQLr_T-1qS0gZH75aKtMN3Yj0iPS4hcgUuTwjAzZr1Z9CAow\"}" # OIDCProviderSignedJwksUri https://localhost:9031/pf/JWKS "{\"keys\":[{\"kty\":\"oct\", \"k\":\"AyM1SysPpbyDfgZld3umj1qzKObwVMkoqQ-EstJQLr_T-1qS0gZH75aKtMN3Yj0iPS4hcgUuTwjAzZr1Z9CAow\"}]}" # NB: for multi-OP setups: # the 1st parameter is not used, it needs to be set anyhow (e.g. to "") if you wish to use the 2nd parameter # the 2nd parameter is the default verification JWK for content pulled from the signed_jwks_uri for all providers and # and its can be overridden with a per-provider key in the .conf file using the key: signed_jwks_uri_key #OIDCProviderSignedJwksUri [ | ] # The fully qualified names of the files that contain the X.509 certificates with the RSA/EC public # keys that can be used for ID Token verification. # NB: this is one or more key tuples where a key tuple consists of: # ["sig:"|"enc:"][#] # and the key identifier part is required when the ID Token contains a "kid" in its header. # Specify the prefix "sig:" or "enc:" to indicate a key is specifically to be used for signing or encryption. # When not defined, ID Token validation key material has to be obtained through OIDCProviderJwksUri or OIDCProviderMetadataURL #OIDCProviderVerifyCertFiles (["sig:"|"enc:"][#])+ # OpenID Connect Provider Token Endpoint URL (e.g. https://localhost:9031/as/token.oauth2) # Used when OIDCProviderMetadataURL is not defined or the metadata obtained from that URL does not set it. #OIDCProviderTokenEndpoint # Authentication method for the OpenID Connect Provider Token Endpoint. # When "private_key_jwt" is used, OIDCPrivateKeyFiles and OIDCPublicKeyFiles must have been set before this directive is applied. # When not defined the default method from the specification is used, i.e. "client_secret_basic". # Used when OIDCProviderMetadataURL is not defined or the metadata obtained from that URL does not set it. # NB: this can be overridden for dynamic client registration on a per-OP basis in the .conf file using the key: token_endpoint_auth #OIDCProviderTokenEndpointAuth [ client_secret_basic | client_secret_post | client_secret_jwt | private_key_jwt | none ] # Extra parameters that need to be passed in the POST request to the Token Endpoint. # Parameter names and values need to be provided in URL-encoded form. # When not defined no extra parameters will be passed. # NB: this can be overridden on a per-OP basis in the .conf file using the key: token_endpoint_params #OIDCProviderTokenEndpointParams =[&=]* # OpenID Connect Provider UserInfo Endpoint URL (e.g. https://localhost:9031/idp/userinfo.openid) # When not defined no claims will be resolved from such endpoint. # Used when OIDCProviderMetadataURL is not defined or the metadata obtained from that URL does not set it. #OIDCProviderUserInfoEndpoint # OpenID OP Check Session iFrame URL, for Session Management purposes. # When not defined, no Session Management will be applied. # Used when OIDCProviderMetadataURL is not defined or the metadata obtained from that URL does not set it. #OIDCProviderCheckSessionIFrame # OpenID OP End Session Endpoint URL, for Single Logout (Session Management) purposes. # When not defined, no logout to the OP will be performed. # Used when OIDCProviderMetadataURL is not defined or the metadata obtained from that URL does not set it. #OIDCProviderEndSessionEndpoint # Extra parameters that will be sent along with the Logout Request. # These must be URL-query-encoded as in: "client_id=myclient&prompt=none". # This is used against a statically configured (single) OP or serves as the default for discovered OPs. # The default is to not add extra parameters. # NB: this can be overridden on a per-OP basis in the .conf file using the key: logout_request_params #OIDCLogoutRequestParams # The RFC 7009 Token Revocation Endpoint URL. # When defined, the refresh token and access token stored in an OIDC session will be revoked on logout. # Used when OIDCProviderMetadataURL is not defined or the metadata obtained from that URL does not set it. #OIDCProviderRevocationEndpoint # The RFC 9126 Pushed Authorization Request endpoint URL. # When not defined, PAR cannot be used to send authentication requests, see also OIDCProviderAuthRequestMethod # Used when OIDCProviderMetadataURL is not defined or the metadata obtained from that URL does not set it. #OIDCProviderPushedAuthorizationRequestEndpoint # Define whether the OP supports OpenID Connect Back Channel Logout. # According to: https://openid.net/specs/openid-connect-backchannel-1_0.html # Used when OIDCProviderMetadataURL is not defined or the metadata obtained from that URL does not set it. #OIDCProviderBackChannelLogoutSupported [On|Off] # Extra JSON parameters that need to be passed in the registration request to the Registration Endpoint. # This setting serves as a default value for multiple OPs only. # Parameter names and values need to be provided in JSON form and will be merged in to the request. # When not defined no extra parameters will be passed. # NB: this can be overridden on a per-OP basis in the .conf file using the key: registration_endpoint_json #OIDCProviderRegistrationEndpointJson # Define the OpenID Connect scope that is requested from the OP (e.g. "openid email profile"). # When not defined, the bare minimal scope "openid" is used. # NB: multiple scope values must be enclosed in a single pair of double quotes # NB: this can be overridden on a per-OP basis in the .conf file using the key: scope #OIDCScope "" # Extra parameters that will be sent along with the Authorization Request. # These must be URL-query-encoded as in: "display=popup&prompt=consent" or # specific for Google's implementation: "approval_prompt=force". # This is used against a statically configured (single) OP or serves as the default for discovered OPs. # As an alternative to this option, one may choose to add the parameters as # part of the URL set in OIDCProviderAuthorizationEndpoint or "authorization_endpoint" # in the .provider metadata (though that would not work with Discovery OPs). # # One can pass on query parameters from the request to the authorization request by adding # e.g. "foo=#" which will dynamically pull in the query parameter value from the # request query parameter and add it to the authentication request to the OP. # # The default is to not add extra parameters. # NB: this can be overridden on a per-OP basis in the .conf file using the key: auth_request_params #OIDCAuthRequestParams # Require a valid SSL server certificate when communicating with the OP. # (i.e. on token endpoint, UserInfo endpoint and Dynamic Client Registration endpoint) # When not defined, the default value is "On". # NB: this can be overridden on a per-OP basis in the .conf file using the key: ssl_validate_server #OIDCSSLValidateServer [On|Off] # Sets the path to the CA bundle to be used by cURL # When not defined, the default bundle for libcurl is used as provided by the platform. #OIDCCABundlePath # Require configured issuer to match the issuer returned in id_token. # (Disable to support Microsoft Entra ID / Azure AD multi-tenant applications.) # When not defined, the default value is "On". #OIDCValidateIssuer [On|Off] # The refresh interval in seconds for the claims obtained from the userinfo endpoint # When not defined the claims are retrieved only once, at session creation time. # If refreshing fails, it is assumed that the access token is expired and an attempt will be made # to refresh the access token using the refresh token grant, after which a second attempt is made # to obtain claims from the userinfo endpoint with the new access token. # NB: this can be overridden on a per-OP basis in the .conf file using the key: userinfo_refresh_interval # The optional logout_on_error flag will make the user logout the current local session if the userinfo request fails. # The optional authenticate_on_error flag sends the user for authentication when the userinfo request fails. #OIDCUserInfoRefreshInterval [ logout_on_error | authenticate_on_error | 502_on_error ] # The refresh interval in seconds for the JWKs key set obtained from the jwks_uri and signed_jwks_uri. # When not defined the default is 3600 seconds. # NB: this can be overridden on a per-OP basis in the .conf file using the key: jwks_refresh_interval # NB: this refresh interval is shared with OIDCOAuthVerifyJwksUri #OIDCJWKSRefreshInterval # Defines the way in which the access token will be presented to the userinfo endpoint # "authz_header" means that the token will be presented in an "Authorization: Bearer" header using HTTP GET # "post_param" means that the token will be presented a form-encoded POST parameter using HTTP POST # When not defined the default is "authz_header". # NB: this can be overridden on a per-OP basis in the .conf file using the key: userinfo_token_method #OIDCUserInfoTokenMethod [authz_header|post_param] # Defines the HTTP method used to pass the parameters in the Authentication Request to the Authorization Endpoint. # "GET" means that the parameters will be passed as query parameters in an HTTP GET # "POST" means that the parameters will be passed as form-post parameters in an HTTP POST # "PAR" means that parameters will be sent to the Pushed Authorization Endpoint # When not defined the default is "GET". # NB: this can be overridden on a per-OP basis in the .conf file using the key: auth_request_method #OIDCProviderAuthRequestMethod [ GET | POST | PAR ] # The fully qualified names of the files that contain the PEM-formatted RSA/EC Public key or a X.509 certificates # that contain the RSA/EC public keys to be used for JWT (OP state/id_token) encryption by the OP. # One of these keys must correspond to the private keys defined in OIDCPrivateKeyFiles. # When not defined no encryption will be requested. # You can also prefix with a JWK key identifier to manually override the automatically # generated "kid" that will be used for this key in the JWKs derived from this certificate and # published at OIDCClientJwksUri. # Specify the prefix "sig:" or "enc:" to indicate a key is specifically to be used for signing or encryption. # NB: this can be overridden on a per-OP basis in the .conf file using the key "keys" whose value is a JWK set/array (use=sign) #OIDCPublicKeyFiles (["sig:"|"enc:"][#])+ # The fully qualified names of the files that contain the PEM-formatted RSA/EC private # keys that can be used to decrypt content sent to us by the OP. # These keys must correspond to the public keys defined in OIDCPublicKeyFiles. # When not defined no decryption will be possible. # NB: this can be overridden on a per-OP basis in the .conf file using the key "keys" whose value is a JWK set/array (use=enc) #OIDCPrivateKeyFiles (["sig:"|"enc:"][#])+ ######################################################################################## # # OpenID Connect Client # # Settings used by the client in communication with the OpenID Connect Provider(s), # i.e. in Authorization Requests, Dynamic Client Registration and UserInfo Endpoint access. # These settings are used when a single static provider is configured and serve as defaults # when multiple providers are configured. # ######################################################################################## # The response type (or OpenID Connect Flow) used (this serves as default value for discovered OPs too) # When not defined the "code" response type is used. # NB: this can be overridden on a per-OP basis in the .conf file using the key: response_type #OIDCResponseType ["code"|"id_token"|"id_token token"|"code id_token"|"code token"|"code id_token token"] # The response mode used (this serves as default value for discovered OPs too) # When not defined the default response mode for the requested flow (OIDCResponseType) is used. # NB: this can be overridden on a per-OP basis in the .conf file using the key: response_mode #OIDCResponseMode [fragment|query|form_post] # Only used for a single static provider has been configured, see below in OpenID Connect Provider. # Client identifier used in calls to the statically configured OpenID Connect Provider. #OIDCClientID # Only used for a single static provider has been configured, see below in OpenID Connect Provider. # Client secret used in calls to the statically configured OpenID Connect Provider. # (not used/required in the Implicit Client Profile, i.e. when OIDCResponseType is "id_token") # If the value begins with exec: the resulting command will be executed and the # first line returned to standard output by the program will be used as the # secret. The command may be absolute or relative to the web server root. #OIDCClientSecret [ | "exec:/path/to/otherProgram argument1" ] # Filename with the PEM-formatted client certificate used to authenticate the Client in calls to the # token endpoint of the OAuth 2.0 Authorization server. # NB: this can be overridden on a per-OP basis in the .conf file using the key: token_endpoint_tls_client_cert #OIDCClientTokenEndpointCert # Filename with the PEM-formatted private key that belongs to the client certificate used to authenticate the # Client in calls to the token endpoint of the OAuth 2.0 Authorization server. # NB: this can be overridden on a per-OP basis in the .conf file using the key: token_endpoint_tls_client_key #OIDCClientTokenEndpointKey # Password for the PEM-formatted private key that belongs to the client certificate used to authenticate the # Client in calls to the token endpoint of the OAuth 2.0 Authorization server. # If the value begins with exec: the resulting command will be executed and the # first line returned to standard output by the program will be used as the password. # The command may be absolute or relative to the web server root. # NB: this can be overridden on a per-OP basis in the .conf file using the key: token_endpoint_tls_client_key_pwd #OIDCClientTokenEndpointKeyPassword [ | "exec:/path/to/otherProgram arg1" ] # The client name that the client registers in dynamic registration with the OP. # When not defined, no client name will be sent with the registration request. # NB: this can be overridden on a per-OP basis in the .conf file using the key: client_name #OIDCClientName # The contacts that the client registers in dynamic registration with the OP. # Must be formatted as e-mail addresses by specification. # Single value only; when not defined, no contact e-mail address will be sent with the registration request. # NB: this can be overridden on a per-OP basis in the .conf file using the key: client_contact #OIDCClientContact # The PKCE method used (this serves as default value for multi-provider OPs too) # When not defined S256 is used. # NB: this can be overridden on a per-OP basis in the .conf file using the key: pkce_method #OIDCPKCEMethod [ S256 | plain | |none ] # The DPoP mode used (this serves as default value for multi-provider OPs too) # off: no DPoP token is requested from the OP # optional: a DPoP token is requested from the OP but we'll continue even if the returned token is Bearer # required: a DPoP token is requested from the OP and we'll fail if the returned token type is not DPoP # When not defined "off" is used. # To be able to request a DPoP token, OIDCPrivateKeyFiles/OIDCPublicKeyFiles settings require a RSA/EC private signing key. # NB: this can be overridden on a per-OP basis in the .conf file using the key: dpop_mode # The 2nd parameter is used to optionally enable an API for creating DPoP proofs on: # ?dpop=&url=[&method=] # When not defined "off" is used. #OIDCDPoPMode [off|optional|required] [on|off] # (used only in dynamic client registration) # Define the Client JWKs URL (e.g. https://localhost/protected/?jwks=rsa)") that will be # used during client registration to point to the JWK set with public keys for this client. # If not defined the default ?jwks=rsa will be used, on which a JWK set # is automatically published based on the OIDCPublicKeyFiles setting so normally you don't # need to touch this unless this client is on a (test) host that is not reachable from the internet. # NB: this can be overridden on a per-OP basis in the .conf file using the key: client_jwks_uri #OIDCClientJwksUri # The algorithm that the OP should use to sign the id_token. # When not defined the default that the OP should use by spec is RS256. # (ES??? algorithms only supported when using OpenSSL >= 1.0) # NB: this can be overridden on a per-OP basis in the .conf file using the key: id_token_signed_response_alg #OIDCIDTokenSignedResponseAlg [RS256|RS384|RS512|PS256|PS384|PS512|HS256|HS384|HS512|ES256|ES384|ES512] # The algorithm that the OP should use to encrypt the Content Encryption Key that is used to encrypt the id_token. # When not defined the default (by spec) is that the OP does not encrypt the id_token. # NB: this can be overridden on a per-OP basis in the .conf file using the key: id_token_encrypted_response_alg #OIDCIDTokenEncryptedResponseAlg [RSA1_5|A128KW|A256KW|RSA-OAEP] # The algorithm that the OP should use to encrypt to the id_token with the Content Encryption Key. # If OIDCIDTokenEncryptedResponseAlg is specified, the default for this value is A128CBC-HS256. # When OIDCIDTokenEncryptedResponseEnc is included, OIDCIDTokenEncryptedResponseAlg MUST also be provided. # (A256GCM algorithm only supported when using OpenSSL >= 1.0.1) # NB: this can be overridden on a per-OP basis in the .conf file using the key: id_token_encrypted_response_enc #OIDCIDTokenEncryptedResponseEnc [A128CBC-HS256|A256CBC-HS512|A256GCM] # The accepted value(s) of the "aud" claim in the ID token, restricted to only those values that have been defined here. # The convenience value "@" can be used to refer to the configured client id (i.e. in case of dynamic client registration). # When not defined the default is to accept any list of values (or a single string value) that includes value of OIDCClientID. # NB: this can be overridden on a per-OP basis in the .conf file using the key: id_token_aud_values with the value set to a JSON array of strings. #OIDCIDTokenAudValues + # The algorithm that the OP should use to sign the UserInfo response # When not defined the default (by spec) is that the OP does not sign the response. # (ES??? algorithms only supported when using OpenSSL >= 1.0) # NB: this can be overridden on a per-OP basis in the .conf file using the key: userinfo_signed_response_alg #OIDCUserInfoSignedResponseAlg RS256|RS384|RS512|PS256|PS384|PS512|HS256|HS384|HS512|ES256|ES384|ES512] # The algorithm that the OP should use to encrypt the Content Encryption Key that is used to encrypt the UserInfo response. # When not defined the default (by spec) is that the OP does not encrypt the response. # NB: this can be overridden on a per-OP basis in the .conf file using the key: userinfo_encrypted_response_alg #OIDCUserInfoEncryptedResponseAlg [RSA1_5|A128KW|A256KW|RSA-OAEP] # The algorithm that the OP should use to encrypt to encrypt the UserInfo response with the Content Encryption Key # If OIDCUserInfoEncryptedResponseAlg is specified, the default for this value is A128CBC-HS256. # When OIDCUserInfoEncryptedResponseEnc is included, OIDCUserInfoEncryptedResponseAlg MUST also be provided. # (A256GCM algorithm only supported when using OpenSSL >= 1.0.1) # NB: this can be overridden on a per-OP basis in the .conf file using the key: userinfo_encrypted_response_enc #OIDCUserInfoEncryptedResponseEnc [A128CBC-HS256|A256CBC-HS512|A256GCM] # The OpenID Connect (client) profile to adhere to, which configures settings for: # - Authentication Request method # - DPoP # - PKCE # - ID token aud values # - token endpoint JWT authentication "aud" values, # - "iss" parameter requirement in authentication reponses # FAPI20: configures settings for the FAPI 2.0 Security Profile i.e : # Auth Request Method: PAR, DPoP: Required, PKCE: S256, aud: client_id, aud: iss, iss: true # OIDC10: adheres to the core OpenID Connect spec v1.0 # When not default the default is OIDC10 #OIDCProfile [ OIDC10 | FAPI20 ] ######################################################################################## # # WARNING: # # THE OAUTH 2.0 RESOURCE SERVER FUNCTIONALITY IS DEPRECATED NOW AND SUPERSEDED # BY A SEPARATE MODULE, SEE: https://github.com/OpenIDC/mod_oauth2 # # OAuth 2.0 Resource Server Settings # # Used when this module functions as a Resource Server against an OAuth 2.0 Authorization # Server, introspecting/validating bearer Access Tokens. # ######################################################################################## # URL where Authorization Provider Provider metadata can be found (e.g. https://example.com/.well-known/oauth-authorization-server) # as defined in RFC 8414. The obtained metadata will be cached and refreshed every 24 hours. # If set, individual entries below will not have to be configured but can be used to add # extra entries/endpoints to settings obtained from the metadata. # If OIDCOAuthServerMetadataURL is not set, the endpoint entries below it will have to be configured. #OIDCOAuthServerMetadataURL # (Mandatory when introspecting opaque access tokens, Optional when performing local JWT access token validation) # OAuth 2.0 Authorization Server token introspection endpoint (e.g. https://localhost:9031/as/token.oauth2) #OIDCOAuthIntrospectionEndpoint # Client identifier used in token introspection calls to the OAuth 2.0 Authorization server. #OIDCOAuthClientID # Client secret used in token introspection calls to the OAuth 2.0 Authorization server. #OIDCOAuthClientSecret # Authentication method for the OAuth 2.0 Authorization Server introspection endpoint, # Used to authenticate the client to the introspection endpoint e.g. with a client_id/client_secret # when OIDCOAuthClientID and OIDCOAuthClientSecret have been set and "client_secret_basic" or "client_secret_post" # has been configured. # When "private_key_jwt" is used, OIDCPrivateKeyFiles and OIDCPublicKeyFiles must have been set. # When not defined "client_secret_basic" is used. #OIDCOAuthIntrospectionEndpointAuth [ client_secret_basic | client_secret_post | client_secret_jwt | private_key_jwt | bearer_access_token | none ] # Used when "OIDCOAuthIntrospectionEndpointAuth bearer_access_token" is configured. # Specifies a static token to be used for authorizing the call to the introspection endpoint. # If empty, the introspected token will be used for authorization as well. #OIDCOAuthIntrospectionClientAuthBearerToken [ a-static-bearer-token | ] # Filename that contains the PEM-formatted client certificate used to authenticate the # caller in token introspection calls to the OAuth 2.0 Authorization server. #OIDCOAuthIntrospectionEndpointCert # Filename that contains the PEM-formatted private key that belongs to the client certificate used # to authenticate the caller in token introspection calls to the OAuth 2.0 Authorization server. #OIDCOAuthIntrospectionEndpointKey # Password for the PEM-formatted private key that belongs to the client certificate used to authenticate the # Client in calls to the token introspection endpoint of the OAuth 2.0 Authorization server. # If the value begins with exec: the resulting command will be executed and the # first line returned to standard output by the program will be used as the password. # The command may be absolute or relative to the web server root. #OIDCOAuthIntrospectionEndpointKeyPassword [ | "exec:/path/to/otherProgram arg1" ] # Define the HTTP method to use for the introspection call. Must be GET or POST. # When not defined the default is POST. #OIDCOAuthIntrospectionEndpointMethod [POST|GET] # Extra parameters that need to be passed in the POST request to the Introspection Endpoint. # Parameter names and values need to be provided in URL-encoded form. # When not defined no extra parameters will be passed. #OIDCOAuthIntrospectionEndpointParams =[&=]* # Name of the parameter whose value carries the access token value in a validation request to the token introspection endpoint. # When not defined the default "token" is used. #OIDCOAuthIntrospectionTokenParamName # Defines the name of the claim that contains the token expiry timestamp, whether it is absolute (seconds since # 1970), relative (seconds from now after which the token will expire), and whether it is optional. # If the claim is optional and not found in the response, the introspection result will not be cached. # (which means that the overall performance may suffer) # # Only applies when the "active" claim is not found in the introspection response, which is interpreted as # an introspection method that does not conform to draft-ietf-oauth-introspection, but is custom. # # When not defined the default "expires_in" is used, the expiry is "relative" and mandatory, matching # Google and PingFederate's introspection behavior. #OIDCOAuthTokenExpiryClaim [absolute|relative] [mandatory|optional] # Define the interval in seconds after which a cached and introspected access token needs # to be refreshed by introspecting (and validating) it again against the Authorization Server. # (can be configured on a per-path basis) # When not defined the value is 0, which means it only expires after the `exp` (or alternative, # see OIDCOAuthTokenExpiryClaim) hint as returned by the Authorization Server. # When set to -1, caching of the introspection results is disabled and the token will be introspected # on each request presenting it. #OIDCOAuthTokenIntrospectionInterval # Require a valid SSL server certificate when communicating with the Authorization Server # on the token introspection endpoint. When not defined, the default value is "On". #OIDCOAuthSSLValidateServer [On|Off] # The symmetric shared key(s) that can be used for local JWT access token validation. # NB: this is one or more key tuples where a key tuple consists of: # ["sig:"|"enc:"]plain|b64|hex#[]# # Specify the prefix "sig:" or "enc:" to indicate a key is specifically to be used for signing or encryption. # When not defined, no access token validation with shared keys will be performed. # Examples: # - a plaintext secret and a key identifier (kid) # plain#1#mysecret # - a base64 encoded secret, no key identifier provided # b64##AF515DE== # - a hex encoded secret, no key identifier provided # hex##ede012 #OIDCOAuthVerifySharedKeys (["sig:"|"enc:"]plain|b64|hex#[#])+ # The fully qualified names of the files that contain the X.509 certificates with the RSA/EC public # keys that can be used for local JWT access token verification. # NB: this is one or more key tuples where a key tuple consists of: # ["sig:"|"enc:"][#] # and the key identifier part is required when the JWT access token contains a "kid" in its header. # Specify the prefix "sig:" or "enc:" to indicate a key is specifically to be used for signing or encryption. # When not defined, no access token validation with statically configured certificates will be performed. #OIDCOAuthVerifyCertFiles (["sig:"|"enc:"][#])+ # The JWKs URL on which the Authorization Server publishes the keys used to sign its JWT access tokens. # When not defined local validation of JWTs can still be done using statically configured keys, # by setting OIDCOAuthVerifyCertFiles and/or OIDCOAuthVerifySharedKeys. #OIDCOAuthVerifyJwksUri # The claim that is used when setting the REMOTE_USER variable on OAuth 2.0 protected paths. # When not defined the default "sub" is used. # # An optional regular expression can be added as a 2nd parameter that will be applied to the # claim value from the 1st parameter and the first match returned from that expression will # be set as the REMOTE_USER. E.g. to strip a domain from an e-mail style address you'd use ^(.*)@ # # An optional 3rd parameter can be added that would contain string with number backreferences. # Backreferences must be in the form $1, $2.. etc. # E.g. to extract username in the form DOMAIN\userid from e-mail style address you may use # ^(.*)@([^.]+)\..+$ $2\\$1 #OIDCOAuthRemoteUserClaim [] [substitution-string] # Define the way(s) in which bearer OAuth 2.0 access tokens can be passed to this Resource Server. # Must be one or several of: # "header" : an "Authorization: bearer" header # "post" : an HTTP Post parameter called "access_token" # "query" : as an HTTP query parameter called "access_token" # "cookie" : as a cookie header called "PA.global" or using the name specified after ":" # "basic": as a HTTP Basic Auth (RFC2617, section 2) password, with any username # When not defined the default "header" is used. #OIDCOAuthAcceptTokenAs [header|post|query|cookie[:|basic]+ ######################################################################################## # # Cookie Settings # ######################################################################################## # Define the cookie path for the "state" and "session" cookies. # When not defined the default is a server-wide "/". #OIDCCookiePath # Specify the domain for which the "state" and "session" cookies will be set. # This must match the OIDCRedirectURI and the URL on which you host your protected # application. Use the literal value of the domain name that will end up in the "Domain" # attribute value for the Set-Cookie header, no leading dot required. # Example domain- (instead of default host-)wide cookie: # OIDCCookieDomain example.org # When not defined the default is the server hostname that is currently accessed. #OIDCCookieDomain # Define the cookie name for the session cookie. # When not defined the default is "mod_auth_openidc_session". #OIDCCookie # OpenID Connect session cookie chunk size. # When using "OIDCSessionType client-cookie" the session cookie may become quite large if a lot of session # data needs to be stored, typically the size depends on the "scopes" of information you request. To work # around cookie size limitations for most web browsers (usually 4096 bytes), the "client-cookie" will be split # over a number of "chunked" cookies if the resulting session data is over a certain number of bytes, # If you want to prevent splitting the session cookie regardless of its size, set the value to 0. # When not defined the default chunk size is 4000 bytes #OIDCSessionCookieChunkSize # Defines whether the HttpOnly flag will be set on cookies. # When not defined the default is On. #OIDCCookieHTTPOnly [On|Off] # Defines the SameSite flag that will be set on cookies. # # When set to "On" (default) or "Lax" the following will apply: # session cookie: Lax # state cookie: Lax # x_csrf discovery: Lax # # When set to "Strict" the following will apply: # session cookie: Strict (first time: Lax) # state cookie: Lax # x_csrf discovery: Strict # # When set to "Off" or "None" the following will apply: # session cookie: None # state cookie: None # x_csrf discovery: None # # When set to "Disabled" no SameSite flag will be appended. # # The configured SameSite cookie appendix on `Set-Cookie` response headers can be # conditionally overridden using an environment variable in the Apache config as in: # SetEnvIf User-Agent ".*IOS.*" OIDC_SET_COOKIE_APPEND=; # # When not defined the default is On (Lax). #OIDCCookieSameSite [ On | Off | Strict | Lax | None | Disabled ] # Specify the names of cookies to pick up from the browser and send along on backchannel # calls to the OP and AS endpoints. This can be used for load-balancing purposes. # When not defined, no such cookies are sent. #OIDCPassCookies []+ # Specify the names of cookies to strip from the incoming request so they are not passed # on to the target application(s). This may prevent a large set of chunked session cookies to # be sent to the backend. In that case you'd set it to (when using the default OIDCCookie setting): # mod_auth_openidc_session mod_auth_openidc_session_chunks mod_auth_openidc_session_0 mod_auth_openidc_session_1 # When not defined, no cookies are stripped. #OIDCStripCookies []+ # Specify the maximum number of state cookies, i.e. the maximum number of parallel outstanding # authentication requests. See: https://github.com/OpenIDC/mod_auth_openidc/issues/331 # Setting this to 0 means unlimited, until the browser or server gives up which is the # behavior of mod_auth_openidc < 2.3.8, which did not have this configuration option. # # The optional second boolean parameter if the oldest state cookie(s) will be deleted, # even if still valid; see #399. # # When not defined, the default is 7 and "false", thus the oldest cookie(s) will not be deleted. #OIDCStateMaxNumberOfCookies [false|true] # Define the cookie prefix for the state cookie. # When not defined the default is "mod_auth_openidc_state_". #OIDCStateCookiePrefix ######################################################################################## # # Session Settings (only relevant in an OpenID Connect Relying Party setup) # ######################################################################################## # Interval in seconds after which the session will be invalidated when no interaction has occurred. # When not defined, the default is 300 seconds. #OIDCSessionInactivityTimeout # Maximum duration of the application session # When not defined the default is 8 hours (3600 * 8 seconds). # When set to 0, the session duration will be set equal to the expiry time of the ID token. # NB: this can be overridden on a per-OP basis in the .conf file using the key: session_max_duration #OIDCSessionMaxDuration # OpenID Connect session storage type. # "server-cache" server-side caching storage. # "client-cookie" uses browser-side sessions stored in a cookie; see also OIDCSessionCookieChunkSize next # A suffix ":persistent" can be added if you want to use a persistent cookie that survives browser restarts # instead of a session cookie that is tied to the lifetime of the browser session. # The "expires" value of the persistent cookie is controlled by the OIDCSessionInactivityTimeout setting. # A suffix ":store_id_token" can be added to "client-cookie" if you want the id_token to be stored # in the session to be used as id_token_hint in a logout request to the OP later. # When not defined the default "server-cache" is used. #OIDCSessionType server-cache[:persistent] | client-cookie[:persistent | :store_id_token | :persistent:store_id_token ] # Fallback to "OIDCSessionType client-cookie" when "OIDCSessionType server-cache" is set and the primary # cache mechanism (e.g. memcache or redis) fails. Note that this will come at a cost of: # a) performance # 1) since on each subsequent request the primary cache will still be polled and # failback will happen as soon as the primary cache is available again # 2) information other than sessions cannot be cached, e.g. resolved access tokens or metadata; see: OIDCCacheType # b) security, since nonce's and jti's are not cached, see: OIDCCacheType # c) (prototype) functionality, since request_uri's won't work anymore # When not defined the default is "Off". #OIDCSessionCacheFallbackToCookie [On|Off] ######################################################################################## # # Cache Settings # ######################################################################################## # Cache type, used for temporary storage that is shared across Apache processes/servers for: # - authenticated user session state # - nonce values from authorization requests (to prevent replay attacks) # - validated OAuth 2.0 access tokens # - refresh tokens during their usage in a refresh token request i.e. refreshing an access token and possible the refresh token itself # - JWK sets that have been retrieved from jwk_uri's (to validate id_token, logout_token, JWT access_token and JWT userinfo response) # - resolved OP metadata when using OIDCProviderMetadataUrl and/or OIDCOAuthServerMetadataURL # - jti values from logout_token when receiving Backchannel Logout requests # - temporary state associated with Request URI's # - signed JWTs when using OIDCPassUserInfoAs signed_jwt and environment variable OIDC_USERINFO_SIGNED_JWT_CACHE_TTL # - JQ filter results when using OIDCFilterClaimsExpr and/or OIDCUserInfoClaimsExpr and/or Require claims_expr # must be one of \"shm\", \"memcache\", \"file\" or, if Redis/Valkey support is compiled in, \"redis\" # When not defined, "shm" (shared memory) is used. #OIDCCacheType [shm|memcache|file[|redis]] # Indicate whether data in the cache backend should be encrypted. # When not defined the default is "Off" for the "shm" backend and "On" for all other cache backends #OIDCCacheEncrypt [On|Off] # When using OIDCCacheType "shm": # Specifies the maximum number of name/value pair entries that can be cached. # When caching a large number of entries, the cache size limit may be reached and the # least recently used entry will be overwritten. If this happens within 1 hour, # errors will be displayed in the error.log and the OIDCCacheShmMax value may be increased. # When not specified, a default of 10000 entries is used. #OIDCCacheShmMax # When using OIDCCacheType "shm": # Specifies the maximum size for a single cache entry in bytes with a minimum of 8736 bytes. # The value must a multiple of 8 bytes. # When caching large values such as numbers of attributes in a session or large metadata documents the # entry size limit may be overrun, in which case errors will be displayed in the error.log # and the OIDCCacheShmEntrySizeMax value has to be increased. # When not specified, a default entry size of 16928 bytes (16384 value + 512 key + 32 overhead) is used. #OIDCCacheShmEntrySizeMax # When using OIDCCacheType "file": # Directory that holds cache files; must be writable for the Apache process/user. # When not specified a system defined temporary directory (/tmp) will be used. #OIDCCacheDir /var/cache/apache2/mod_auth_openidc/cache # When using OIDCCacheType "file": # Cache file clean interval in seconds (only triggered on writes). # When not specified a default of 60 seconds is used. #OIDCCacheFileCleanInterval # Required when using OIDCCacheType "memcache": # Specifies the memcache servers used for caching as a space separated list of [:] tuples. #OIDCMemCacheServers "([:])+" # Minimum number of connections to each Memcache server per process. Defaults to # OIDCMemCacheConnectionsHMax. #OIDCMemCacheConnectionsMin # All connections above this limit will be closed if they have been idle for # more than OIDCMemCacheConnectionsTTL. Defaults to OIDCMemCacheConnectionsHMax. #OIDCMemCacheConnectionsSMax # Maximum number of connections to each Memcache server per process. Defaults to # ThreadsPerChild or if mod_http2 is loaded to ThreadsPerChild - 1 + H2MaxWorkers. #OIDCMemCacheConnectionsHMax # Maximum time in seconds a connection to a Memcache server can be idle before # being closed. Defaults to 60 seconds. # Only for Apache >= 2.4.x: By adding a postfix of ms, the timeout can be also # set in milliseconds. Defaults to 60 seconds. #OIDCMemCacheConnectionsTTL # Required if Redis/Valkey support is compiled in and when using OIDCCacheType "redis": # Specifies the Redis/Valkey server used for caching as a [:] tuple. #OIDCRedisCacheServer [:] # Password to be used if the Redis/Valkey server requires authentication: http://redis.io/commands/auth # When not specified, no authentication is performed. #OIDCRedisCachePassword # Username to be used if the Redis/Valkey server requires authentication: http://redis.io/commands/auth # NB: this can only be used with Redis/Valkey 6 (ACLs) or later. # When not specified, the implicit user "default" is used. #OIDCRedisCacheUsername # Logical database to select on the Redis/Valkey server: https://redis.io/commands/select # When not defined the default database 0 is used. #OIDCRedisCacheDatabase # Timeout (in seconds) for connecting to the Redis/Valkey server. # An optional 2nd parameter can be supplied to set the keepalive interval (in seconds) on the # TCP connection to the Redis/Valkey server. 0 disables keepalive. # NB: the interval setting only works when compiled and running with hiredis >= 1.2.0 # when compiled and running with hiredis < 1.2.0 any value > 0 will apply the default interval # When not defined the default connect timeout is 5 seconds and the default hiredis keepalive (15s) is applied. #OIDCRedisCacheConnectTimeout [0|] # Timeout waiting for a response of the Redis/Valkey server after a request was sent. # When not defined, the default timeout is 5 seconds. #OIDCRedisCacheTimeout ######################################################################################## # # Advanced Settings # ######################################################################################## # Defines an external OP Discovery page. That page will be called with: # ?oidc_callback= # additional parameters may be added, i.e. `target_link_uri`, `x_csrf` and `method`. # # An Issuer selection can be passed back to the callback URL as in: # ?iss=[${issuer}|${domain}|${e-mail-style-account-name}][parameters][&login_hint=][&scopes=][&auth_request_params=] # where the parameter contains the URL-encoded issuer value of # the selected Provider, or a URL-encoded account name for OpenID # Connect Discovery purposes (aka. e-mail style identifier), or a domain name. # [parameters] contains the additional parameters that were passed in on the discovery request (e.g. target_link_uri=&x_csrf=&method=&scopes=) # # When not defined the bare-bones internal OP Discovery page is used. #OIDCDiscoverURL # Defines a default URL to be used in case of 3rd-party-init-SSO when no explicit target_link_uri # has been provided. The user is also redirected to this URL in case an invalid authorization # response was received. # The default is to not redirect the browser to any URL but return an HTTP/HTML error to the user. #OIDCDefaultURL # Defines a default URL where the user is sent to after logout, which may be overridden explicitly during logout. # When not defined and no URL was passed explicitly, a default internal page will be shown. #OIDCDefaultLoggedOutURL # Define the OpenID Connect scope(s) that is requested from the OP (e.g. "admin edit") # on a per-path basis in addition to the per-provider configured scopes (OIDCScope). # Multiple scope values must be enclosed in a single pair of double quotes. # Apache expressions can be used to pass dynamic runtime determined values. # The default is to not add extra scopes. #OIDCPathScope "" # Extra parameters that will be sent along with the Authorization Request. # These must be URL-query-encoded as in: "display=popup&prompt=consent". # This can be configured on a per-path basis across all configured Providers. # One can pass on query parameters from the request to the authorization request by adding # e.g. "foo=#" which will dynamically pull in the query parameter value from the # request query parameter and add it to the authentication request to the OP. # Apache expressions can be used to pass dynamic runtime determined values. # The default is to not add extra parameters. #OIDCPathAuthRequestParams # Acceptable offset (before and after) for checking the \"iat\" (= issued at) timestamp in the id_token. # When not defined the default is 600 seconds. # NB: this can be overridden on a per-OP basis in the .conf file using the key: idtoken_iat_slack #OIDCIDTokenIatSlack # The prefix to use when setting claims (openid-connect or oauth20) in the HTTP headers/environment variables. # This prefix should not be set to "" except when combined with OIDCWhiteListedClaims to maintain a secure setup. # When not defined, the default "OIDC_CLAIM_" is used. #OIDCClaimPrefix # The delimiter to use when setting multi-valued claims (openid-connect or oauth20) in the HTTP headers/environment variables. # When not defined the default "," is used. #OIDCClaimDelimiter # The claim that is used when setting the REMOTE_USER variable on OpenID Connect protected paths. # If the claim name is post-fixed with a \"@\", the claim value will be post-fixed with the # \"iss\" value (with leading "https://" stripped) to make this value unique across different OPs. # When not defined the default "sub@" is used. # # An optional regular expression can be added as a 2nd parameter that will be applied to the # resulting value from the 1st parameter and the first match returned from that expression will # be set as the REMOTE_USER. E.g. to strip a domain from an e-mail style address you'd use ^(.*)@ # # An optional 3rd parameter can be added that would contain string with number backreferences. # Backreferences must be in the form $1, $2.. etc. # E.g. to extract username in the form DOMAIN\userid from e-mail style address you may use # ^(.*)@([^.]+)\..+$ $2\\$1 #OIDCRemoteUserClaim [@] [] [substitution-string] # Define the way(s) in which the id_token contents are passed to the application according to OIDCPassClaimsAs. # Must be one or several of: # "claims" : the claims in the id_token are passed in individual headers/environment variables # "payload" : the payload of the id_token is passed as a JSON object in the "OIDC_id_token_payload" header/environment variable # "serialized" : the complete id_token is passed in compact serialized format in the "OIDC_id_token" header/environment variable # Note that when OIDCSessionType client-cookie is set, the id_token itself is not stored in the session/cookie (unless explicitly # configured to do so) and as such the header for the "serialized" option will not be set. # Can be configured on a per Directory/Location basis. When not defined the default "claims" is used.. #OIDCPassIDTokenAs [claims|payload|serialized]+ # Define the way(s) in which the claims resolved from the userinfo endpoint are passed to the application according to OIDCPassClaimsAs. # Must be one or several of: # "claims" : # the userinfo claims are passed in individual headers/environment variables # "json[: header/environment variable # "jwt[: header/environment variable # "signed_jwt[: header/environment variable # - requires OIDCPrivateKeyFiles/OIDCPublicKeyFiles set with an RSA key (RS256) or a prime256v1 Elliptic Curve key(s) (ES256), # the first RSA/EC signing key in the configured list will be used # - the "expires_in" hint from the access_token is used in the "exp" claim; defaults to 60 seconds if not returned by the OP. # - caching of the signed JWT - use with care only - can be configured using: # SetEnvIfExpr true OIDC_USERINFO_SIGNED_JWT_CACHE_TTL= # or for the duration of the - possibly processed - "exp" claim when set to "0" # Can be configured on a per Directory/Location basis. When not defined the default "claims" is used.. #OIDCPassUserInfoAs [claims|json[:]|jwt[:]|signed_jwt[:]]+ # Only when compiled in with libjq (https://stedolan.github.io/jq/manual/) support: process the claims # returned from the userinfo endpoint with a JQ-based expression before propagating them according # to OIDCPassUserInfoAs claims|json|signed_jwt (i.e. it does not work for "OIDCPassUserInfoAs jwt") # # Overwrite the default (provider) "iss" claim, and delete the default "aud" and "name" claims: # '. + { iss: "https://myissuer.com" } | del(.aud, .name)' # Add new claim with a variable value obtained from an Apache expression https://httpd.apache.org/docs/2.4/expr.html: # (be aware that when used with "OIDCPassUserInfoAs signed_jwt" it results in a cached JWT per-user/per-path) # '. + { path: "%{REQUEST_URI}" }' # Keep sub only: # '{ sub: .sub }' # Filter out all elements in the "groups" array of strings that contain "DC=Company" : # '. + { groups: (.groups - (.groups | map(select(contains("DC=Company"))))) }' # Filter out all elements in the "groups" array of strings that match regular expression ^CN=test-.* : # '. + { groups: (.groups - (.groups | map(select(match("^CN=test-.*"; "g"))))) }' # Can be configured on a per Directory/Location basis. When not defined no processing will be applied. #OIDCUserInfoClaimsExpr # Only when compiled in with libjq (https://stedolan.github.io/jq/manual/) support: applies # a JQ filter to claims in the both the id_token and claims returned from the userinfo endpoint # before storing them in the session after applying (optional) top-level blacklisting/whitelisting # with OIDCBlackListedClaims/OIDCWhiteListedClaims, e.g.: # filter out all elements in the "groups" array of strings that match regular expression ^CN=test-.* # '. + { groups: (.groups - (.groups | map(select(match("^CN=test-.*"; "g"))))) }' # whitelist only "name" and "sub" claims: # '{name, sub}' # delete "groups", "exp" and "iat" # 'del(.groups,.exp,.iat)' # When not defined no processing will be applied and all claims will be stored in the session. #OIDCFilterClaimsExpr # Define the way in which the (processed) claims and tokens are passed to the application environment: # "none": no claims/tokens are passed # "environment": claims/tokens are passed as environment variables # "headers": claims/tokens are passed in headers (also useful in reverse proxy scenario's) # "both": claims/tokens are passed as both headers as well as environment variables (default) # # A second parameter can be specified that defines the encoding applied to all values passed in headers # and environment variables: # "latin1" applies ISO-8859-1 encoding: this may result in out of bound characters converted to the "?" character. # "base64url" applies base64url encoding # "none" applies no encoding and copies literal values from the claims into the headers/environment variables # When not defined the default is "both" and "latin1" encoding is applied to the header/environment values. # # The access token is passed in OIDC_access_token; the access token expiry is passed in OIDC_access_token_expires. # The refresh token is only passed in OIDC_refresh_token if enabled for that specific directory/location (see: OIDCPassRefreshToken) #OIDCPassClaimsAs [none|headers|environment|both] [latin1|base64url|none] # Specify the HTTP header variable name to set with the name of the authenticated user, # i.e. copy what is set in REMOTE_USER and configured in OIDCRemoteUserClaim or OIDCOAuthRemoteUserClaim. # When not defined no such header is added. # This setting can be configured for both the "openid-connect" and "oauth20" AuthType on # a server-wide or directory level. #OIDCAuthNHeader # Timeout in seconds for long duration HTTP calls. This defines the maximum duration that a request make take to # to complete and is used for most requests to remote endpoints/servers. # The optional parameter specifies the connect timeout in seconds, as part of the overall request timeout. # The optional parameter specifies the number of retry attempts in case of connectivity errors. # When not defined the default of 30 seconds is used, with a 10 second connect timeout, using 1 retry after # an interval of 500ms. #OIDCHTTPTimeoutLong [] [[:]] # Timeout in seconds for short duration HTTP calls. This defines the maximum duration that a request may take to # to complete and is used for Client Registration and OP Discovery requests. # The optional parameter specifies the connect timeout in seconds, as part of the overall request timeout. # The optional parameter specifies the number of retry attempts in case of connectivity errors. # When not defined the default of 5 seconds is used, with a 2 second connect timeout, using 1 retry with # an interval of 500ms. #OIDCHTTPTimeoutShort [] [[:]] # Time to live in seconds for state parameter, i.e. the interval in which the authorization request # and the corresponding response need to be processed. When not defined the default of 300 seconds is used. #OIDCStateTimeout # Specify an outgoing proxy for your network. When running on a platform with a recent version of # libcurl you can also specify the network protocol, see: https://curl.se/libcurl/c/CURLOPT_PROXY.html # When not defined no outgoing proxy is used. #OIDCOutgoingProxy [://][:] [:] [basic|digest|negotiate|ntlm|any] # Defines the action to be taken when an unauthenticated request is made. # # "auth" means that the user is redirected to the OpenID Connect Provider or Discovery page. # "401" means that HTTP 401 Unauthorized is returned. # "407" means that HTTP 407 Proxy Authentication Required is returned # "410" means that HTTP 410 Gone is returned # "pass" means that an unauthenticated request will pass but claims will still be passed when a user happens to be authenticated already # # Useful in Location/Directory/Proxy path contexts that serve AJAX/Javascript calls and for "anonymous access" # # When not defined the default is "auth" with auto-detection of requests that would not be able to complete # an authentication round trip to the OpenID Connect Provider, which would receive a 401. # The default auto-detection algorithm looks for the "X-Requested-With: XMLHttpRequest" header/value, or # the presence of a Sec-Fetch-Mode header with a value that is not equal to "navigate", or the presence of # a Sec-Fetch-Dest header with a value that is not equal to "document" or the absence of # an "Accept" header with any of the values "text/html" "application/xhtml+xml" or "*/*" # and returns 401 for such non-auth-capable requests, e.g. XML HTTP Requests, image loading requests etc. # that would create a state cookie but never return to delete it. # See: https://github.com/OpenIDC/mod_auth_openidc/wiki/Cookies#tldr # # Only for Apache >= 2.4.x: # Since version 2.4.4 a boolean Apache expression as the second parameter to specify which requests # need to match to return the configured value in the first parameter to override the default "auth". # See also: https://httpd.apache.org/docs/2.4/expr.html. # E.g. to only return 401 for cURL-based user agents and "auth" for any other browsers/user agents: # OIDCUnAuthAction 401 "%{HTTP_USER_AGENT} =~ /curl/" # to effectively override the default XML request detection algorithm by ignoring the Sec-Fetch-Mode, # Sec-Fetch-Dest and Accept headers: # OIDCUnAuthAction 401 "%{HTTP:X-Requested-With} == 'XMLHttpRequest'" # to return 401 for all user agents that do not send an Accept header that includes a "text/html" value: # OIDCUnAuthAction 401 "%{HTTP_ACCEPT} !~ m#text/html#" # or as a more complex example, which equals the default XML request detection algorithm: # OIDCUnAuthAction 401 "%{HTTP:X-Requested-With} == 'XMLHttpRequest' \ # || ( -n %{HTTP:Sec-Fetch-Mode} && %{HTTP:Sec-Fetch-Mode} != 'navigate' ) \ # || ( -n %{HTTP:Sec-Fetch-Dest} && %{HTTP:Sec-Fetch-Dest} != 'document' ) \ # || ( ( %{HTTP_ACCEPT} !~ m#text/html# ) \ # && ( %{HTTP_ACCEPT} !~ m#application/xhtml\+xml# ) \ # && ( %{HTTP_ACCEPT} !~ m#\*/\*# ) )" # To enable authentication in an iframe you need to change the Sec-Fetch-Dest part above in: # || ( -n %{HTTP:Sec-Fetch-Dest} && %{HTTP:Sec-Fetch-Dest} != 'iframe' && %{HTTP:Sec-Fetch-Dest} != 'document') \ # To disable auto-detection of XML HTTP request altogether and unconditionally return "auth" for all clients: # OIDCUnAuthAction auth true # Note that actually *any* expression value in "OIDCUnAuthAction auth " will *always* render "auth" # (even when set to "false"...) because of the default, so using an value (other than "true") only # makes sense in combination with one of the values other than "auth". #OIDCUnAuthAction [auth|pass|401|407|410] [] # Defines the action to be taken when an unauthorized request is made, i.e. the user is authenticated but # does not meet the `Require claim <>` directives or similar. # "401" return HTTP 401 Unauthorized with optional text message if specified in # "403" return HTTP 403 Forbidden with optional text message; NB: for Apache 2.4 this is controlled by the AuthzSendForbiddenOnFailure directive! # "302" redirect to the URL specified in the parameter # "auth" redirect the user to the OpenID Connect Provider or Discovery page for authentication ( is unused) # Useful in Location/Directory/Proxy path contexts that need to do step-up authentication # Be aware that this will only work in combination with a single Require statement or RequireAll, # so using RequireAny and multiple Require statements is not supported. # Also for "auth", the expression argument for OIDCUnAuthAction is re-used here to detect XHR requests. # When not defined the default "403" is used. However Apache 2.4 will change this to 401 unless you set "AuthzSendForbiddenOnFailure on" #OIDCUnAutzAction [401|403|302|auth] [] # Indicates whether POST data will be preserved across authentication requests (and discovery in case of multiple OPs). # This is designed to prevent data loss when a session timeout occurs in a (long) user filled HTML form. # It cannot handle arbitrary payloads for security (DOS) reasons, merely form-encoded user data. # Preservation is done via HTML 5 session storage: note that this can lead to private data exposure on shared terminals. # The default is "Off" (for security reasons). Can be configured on a per Directory/Location basis. #OIDCPreservePost [On|Off] # POST preserve and restore templates to be used with OIDCPreservePost # template needs to contain two "%s" characters # the first for the JSON formatted POST data, the second for the URL to redirect to after preserving # template needs to contain one "%s" # which contains the (original) URL to POST the restored data to # The default is to use internal templates #OIDCPreservePostTemplates # Indicates whether the access token and access token expiry will be passed to the application in a header/environment variable, according # to the OIDCPassClaimsAs directive. # Can be configured on a per Directory/Location basis. The default is "On". #OIDCPassAccessToken [On|Off] # # Indicates whether the refresh token will be passed to the application in a header/environment variable, according # to the OIDCPassClaimsAs directive. # Can be configured on a per Directory/Location basis. The default is "Off". #OIDCPassRefreshToken [On|Off] # Request Object/URI settings expressed as a string that is a "double-quote-escaped" JSON object. For example: # "{ \"copy_from_request\": [ \"claims\", \"response_type\", \"response_mode\", \"login_hint\", \"id_token_hint\", \"nonce\", \"state\", \"redirect_uri\", \"scope\", \"client_id\" ], \"static\": { \"some\": \"value\", \"some_nested\": { \"some_array\": [ 1,2,3] } }, \"crypto\": { \"sign_alg\": \"HS256\", \"crypt_alg\": \"A256KW\", \"crypt_enc\": \"A256CBC-HS512\" }, \"url\": \"https://www.openidc.com/protected/\", \"request_object_type\" : \"request\" }" # Parameters: # copy_from_request (array) : array of query parameter names copied from request # copy_and_remove_from_request (array) : array of parameter names copied from request and removed as query parameter # static (object) : parameter value is merged to the request object # ttl (number) : number of seconds before the request object expires (default is 30 seconds) # translates to the `exp` claim in the request object # crypto (object) : defines cryptography used to create request object # sign_alg (string) : algorithm used to sign request object (JWS alg parameter) # crypt_alg (string) : algorithm used to encrypt CEK of request object (JWE alg parameter) # crypt_enc (string) : algorithm used to encrypt request object (JWE enc parameter) # url (string) : use this url instead of redirect_uri for request_uri # request_object_type (string) : parameter used for sending authorization request object # "request_uri" (default) or "request" # NB: this can be overridden on a per-OP basis in the .conf file using the key: request_object #OIDCRequestObject # Provider metadata refresh interval for the metadata in a multi-provider setup (with OIDCMetadataDir). # When not defined the default is 0 seconds, i.e. it is never refreshed. # Also used in a single provider setup with OIDCProviderMetadatURL but 0 then means the default of 1 day. #OIDCProviderMetadataRefreshInterval # Define the data that will be returned upon calling the info hook. # The data can be JSON formatted using ?info=json, or HTML formatted, using ?info=html. # iat (int) : Unix timestamp indicating when this data was created # access_token (string) : the access token # access_token_expires (int) : the Unix timestamp which is a hint about when the access token will expire (as indicated by the OP) # id_token (object) : the claims presented in the ID token # id_token_hint (string) : the serialized ID token # userinfo (object) : the claims resolved from the UserInfo endpoint # refresh_token (string) : the refresh token (if returned by the OP) # exp (int) : the maximum session lifetime (Unix timestamp in seconds) # timeout (int) : the session inactivity timeout (Unix timestamp in seconds) # remote_user (string) : the remote user name # session (object) : (for debugging) mod_auth_openidc specific session data such as "remote user", "session expiry", "session id" and a "state" object # Note that when using "ProxyPass /" you may have to add a proxy exception for the Redirect URI # for this to work, e.g. "ProxyPass /redirect_uri !" # When not defined the session hook will not return any data but a HTTP 404. #OIDCInfoHook [iat|access_token|access_token_expires|id_token|id_token_hint|userinfo|refresh_token|exp|timeout|remote_user|session]+ # Specify metrics that you wish to collect and keep in shared memory for retrieval. # Supported metrics classes are: # authtype Request counter, overall and per AuthType: openid-connect, oauth20 and auth-openidc. # authn Authentication request creation and response processing. # authz Authorization errors per OIDCUnAutzAction (per Require statement, not overall). # require.claim Match/failure count of Require claim directives (per Require statement, not overall). # claim.* ID token / Userinfo claim name/value at login and refresh. # provider Requests to the provider [token, userinfo, metadata] endpoints. # session Existing session processing. # cache Cache read/write timings and errors. # redirect_uri Requests to the Redirect URI, per type. # content Requests to the content handler, per type of request: info, metrics, jwks, etc. # When not defined no metrics will be recorded. #OIDCMetricsData [ authtype | authn | authz | require.claim | claim.id_token.* | claim.userinfo.* | requests | session | cache | redirect_uri | content ]+ # Specify the path where metrics are published and can be consumed. # The format parameter can be passed to specify the format in which the collected data is returned. # format=prometheus Prometheus text-based exporter # format=json (non-standard) JSON with descriptions and names # format=status short text-based status message "OK" plus optional counter (&vhost=&counter=) # format=internal internal terse JSON for debugging purposes # The default is "prometheus". # Protect this path (e.g. Require host localhost) or serve it on an internal co-located vhost/port. # When not defined, no metrics will be published on the enclosing vhost. #OIDCMetricsPublish # Set a traceparent HTTP header on outgoing requests to the provider and proxied requests. # propagate: propagate any existing traceparent header on requests to the Provider (it's proxied as it is) # generate: generate a traceparent header, possibly overwriting an existing one # The default is "off": do not propagate, add (or overwrite) a traceparent header. #OIDCTraceParent off | generate | propagate # Specify claims that should be removed from the userinfo and/or id_token before storing them in the session. # Note that OIDCBlackListedClaims takes precedence over OIDCWhiteListedClaims # When not defined no claims are blacklisted and all claims are stored except when OIDCWhiteListedClaims is used. #OIDCBlackListedClaims []+ # Specify claims from the userinfo and/or id_token that should be stored in the session (all other claims will be discarded). # Note that OIDCBlackListedClaims takes precedence over OIDCWhiteListedClaims # When not defined no claims are whitelisted and all claims are stored except when blacklisted with OIDCBlackListedClaims. #OIDCWhiteListedClaims []+ # Specify the minimum time-to-live for the access token stored in the OIDC session. # When the access token expiry timestamp (at least the hint given to that) is less than this value, # an attempt will be made to refresh the access token using the refresh token grant type towards the OP. # This only has an effect if a refresh token was actually returned from the OP and an "expires_in" hint # was returned as part of the authorization response and subsequent refresh token responses. # When not defined no attempt is made to refresh the access token (unless implicitly through OIDCUserInfoRefreshInterval) # The optional logout_on_error flag makes the refresh logout the current local session if the refresh fails. # The optional authenticate_on_error flag sends the user for authentication when the refresh fails. #OIDCRefreshAccessTokenBeforeExpiry [logout_on_error | authenticate_on_error | 502_on_error] # Defines which headers will be used as the "state" input for calculating the fingerprint of the browser # during authentication. When not defined the default "user-agent" is used. #OIDCStateInputHeaders [user-agent|x-forwarded-for|both|none] # Define one or more regular expressions that specify URLs (or domains) allowed for post logout and # other redirects such as the "return_to" value on refresh token requests, the "login_uri" value # on session management-based logins through the OP iframe, and the "target_link_uri" parameter in # 3rd-party initiated logins, e.g.: # OIDCRedirectURLsAllowed ^https://www\.example\.com ^https://(\w+)\.example\.org ^https://example\.net/app # or: # OIDCRedirectURLsAllowed ^https://www\.example\.com/logout$ ^https://www\.example\.com/app/return_to$ # When not defined, the default is to match the hostname in the URL redirected to against # the hostname in the current request. #OIDCRedirectURLsAllowed []+ # Defines the value of the X-Frame-Options header returned on OIDC front-channel logout requests. # See also https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options. # For example: # OIDCLogoutXFrameOptions: sameorigin # or: # OIDCLogoutXFrameOptions: allow-from https://provider.example.com/ # When not defined the default is "DENY". #OIDCLogoutXFrameOptions # Define the X-Forwarded-* or Forwarded headers that will be considered as set by a reverse proxy # in front of mod_auth_openidc. Must be one or more of: # X-Forwarded-Host # X-Forwarded-Port # X-Forwarded-Proto # Forwarded # none # When not defined or "none", such headers will be ignored. #OIDCXForwardedHeaders
+ mod_auth_openidc-2.4.16.10/autogen.sh000077500000000000000000000000761476721736500173520ustar00rootroot00000000000000#!/bin/sh autoreconf --force --install rm -rf autom4te.cache/ mod_auth_openidc-2.4.16.10/configure.ac000066400000000000000000000127131476721736500176400ustar00rootroot00000000000000AC_INIT([mod_auth_openidc],[2.4.16.10],[hans.zandbelt@openidc.com]) AC_SUBST(NAMEVER, AC_PACKAGE_TARNAME()-AC_PACKAGE_VERSION()) AM_INIT_AUTOMAKE([-Wall -Werror foreign subdir-objects]) AC_CONFIG_HEADERS([src/config.h]) AC_CONFIG_MACRO_DIR([m4]) AC_PROG_CC AM_PROG_CC_C_O AM_PROG_AR LT_INIT([dlopen]) # Checks for apxs. AC_ARG_WITH([apxs], [AS_HELP_STRING([--with-apxs=PATH/NAME],[path to the apxs binary for Apache [[apxs]]])], [AC_SUBST(APXS, $with_apxs)], [AC_PATH_PROGS(APXS, [apxs apxs2],,)]) if test ! -x "$APXS"; then # $APXS isn't a executable file. AC_MSG_ERROR([ Could not find apxs. Please specify the path to apxs using the --with-apxs=/full/path/to/apxs option. The executable may also be named 'apxs2'. ]) fi APXS_CFLAGS=`${APXS} -q CFLAGS 2> /dev/null` APXS_CPPFLAGS=`${APXS} -q CPPFLAGS 2> /dev/null` APXS_LDFLAGS=`${APXS} -q LDFLAGS 2> /dev/null` APXS_LIBS=`${APXS} -q LIBS 2> /dev/null` APXS_LIBEXECDIR=`${APXS} -q LIBEXECDIR 2> /dev/null` APXS_INCLUDEDIR=`${APXS} -q INCLUDEDIR 2> /dev/null` APXS_INCLUDES="-I${APXS_INCLUDEDIR}" PKG_CHECK_MODULES(APR, [apr-1, apr-util-1]) # Apache libraries. APACHE_MODULEDIR="${APXS_LIBEXECDIR}" APACHE_INCLUDES="${APXS_INCLUDES} ${APR_INCLUDES}" APACHE_CFLAGS="${APXS_CFLAGS} ${APR_CFLAGS} ${APACHE_INCLUDES}" APACHE_CPPFLAGS="${APXS_CPPFLAGS} ${APR_CPPFLAGS} ${APACHE_INCLUDES}" APACHE_LDFLAGS="${APXS_LDFLAGS} ${APR_LDFLAGS}" APACHE_LIBS="${APXS_LIBS} ${APR_LIBS}" AC_SUBST(APACHE_MODULEDIR) AC_SUBST(APACHE_INCLUDES) AC_SUBST(APACHE_CFLAGS) AC_SUBST(APACHE_CPPFLAGS) AC_SUBST(APACHE_LDFLAGS) AC_SUBST(APACHE_LIBS) # We need the curl library for HTTP callouts. PKG_CHECK_MODULES(CURL, libcurl) AC_SUBST(CURL_CFLAGS) AC_SUBST(CURL_LIBS) # We need OpenSSL for crypto and HTTPS callouts. PKG_CHECK_MODULES(OPENSSL, openssl) AC_SUBST(OPENSSL_CFLAGS) AC_SUBST(OPENSSL_LIBS) # older versions of libapr may not have memcache support old_CPPFLAGS=$CPPFLAGS CPPFLAGS="${APACHE_CPPFLAGS} ${APACHE_CFLAGS} $CPPFLAGS" AC_CHECK_HEADER([apr_memcache.h], [HAVE_MEMCACHE=1], [HAVE_MEMCACHE=0]) AM_CONDITIONAL(HAVE_MEMCACHE,[test x"$HAVE_MEMCACHE" = "x1"]) CPPFLAGS=$old_CPPFLAGS # We need Jansson for JSON parsing. PKG_CHECK_MODULES(JANSSON, jansson) AC_SUBST(JANSSON_CFLAGS) AC_SUBST(JANSSON_LIBS) # cjose PKG_CHECK_MODULES(CJOSE, cjose) AC_SUBST(CJOSE_CFLAGS) AC_SUBST(CJOSE_LIBS) # PCRE PKG_CHECK_MODULES([PCRE2], libpcre2-8, [ PCRE_CFLAGS="$PCRE2_CFLAGS" PCRE_LIBS="$PCRE2_LIBS" AC_DEFINE([HAVE_LIBPCRE2], [1], [Define if libpcre2 is available.]) enable_pcre2=yes ], [ AC_CHECK_HEADER(pcre2.h, [ AC_CHECK_LIB(pcre2-8, pcre2_compile_8, [ PCRE_LIBS="-lpcre2-8" AC_DEFINE([HAVE_LIBPCRE2], 1, [Define if libpcre2 is available.]) enable_pcre2=yes ]) ]) ]) AS_IF([test "X$enable_pcre2" != "Xyes"],[ PKG_CHECK_MODULES([PCRE], libpcre, [ CFLAGS="$PCRE_CFLAGS $CFLAGS" AC_CHECK_HEADER(pcre.h, [ LIBS="$PCRE_LIBS $LIBS" AC_DEFINE([HAVE_LIBPCRE], [1], [Define if libpcre is available.]) enable_pcre=yes ]) ], [ AC_CHECK_HEADER(pcre.h, [ AC_CHECK_LIB(pcre, pcre_compile, [ PCRE_LIBS="-lpcre" AC_DEFINE([HAVE_LIBPCRE], 1, [Define if libpcre is available.]) ]) ]) ]) ]) AS_IF([test "X$enable_pcre2" = Xyes], [PCRE_INFO="yes, via libpcre2"], [test "X$enable_pcre" = Xyes], [PCRE_INFO="yes, via libpcre"], [PCRE_INFO=no]) AC_SUBST(PCRE_CFLAGS) AC_SUBST(PCRE_LIBS) AC_ARG_WITH(brotli, AS_HELP_STRING([--with-brotli], [enable brotli compression support]), ac_brotli=$withval, ac_brotli=no) if test x$ac_brotli != xno; then PKG_CHECK_MODULES(LIBBROTLIENC, [libbrotlienc >= 1.0.0], [with_libbrotlienc=yes], [with_libbrotlienc=no]) PKG_CHECK_MODULES(LIBBROTLIDEC, [libbrotlidec >= 1.0.0], [with_libbrotlidec=yes], [with_libbrotlidec=no]) fi AM_CONDITIONAL(HAVE_LIBBROTLI, [test "${with_libbrotlienc}" == "yes" && test "${with_libbrotlidec}" == "yes"]) AC_SUBST([LIBBROTLIENC_CFLAGS]) AC_SUBST([LIBBROTLIDEC_CFLAGS]) AC_SUBST([LIBBROTLIENC_LIBS]) AC_SUBST([LIBBROTLIDEC_LIBS]) if test "${with_libbrotlienc}" != "yes" || test "${with_libbrotlidec}" != "yes"; then PKG_CHECK_MODULES([ZLIB], [zlib], [HAVE_LIBZ=1], [HAVE_LIBZ=0]) fi AM_CONDITIONAL(HAVE_LIBZ, [test x"$HAVE_LIBZ" = "x1"]) AC_SUBST([ZLIB_CFLAGS]) AC_SUBST([ZLIB_LIBS]) # Redis AC_ARG_WITH([hiredis], [AS_HELP_STRING([--with-hiredis], [support Redis @<:@default=check@:>@])], [], [with_hiredis=yes]) AS_CASE(["$with_hiredis"], [yes], [ if test "$HIREDIS_LIBS" == ""; then PKG_CHECK_MODULES([HIREDIS], [hiredis], [HAVE_LIBHIREDIS=1], [HAVE_LIBHIREDIS=0]) ; else [HAVE_LIBHIREDIS=1] ; fi ], [no], [HAVE_LIBHIREDIS=0], [PKG_CHECK_MODULES([HIREDIS], [hiredis], [HAVE_LIBHIREDIS=1], [HAVE_LIBHIREDIS=0])]) AM_CONDITIONAL(HAVE_LIBHIREDIS, [test x"$HAVE_LIBHIREDIS" = "x1"]) AC_SUBST(HIREDIS_CFLAGS) AC_SUBST(HIREDIS_LIBS) # JQ HAVE_LIBJQ=0 AC_ARG_WITH([jq], AS_HELP_STRING([--with-jq=PATH], [location of your libjq installation])]) if test -n "$with_jq" then if test "$JQ_CFLAGS" == ""; then JQ_CFLAGS="-I$with_jq/include" fi if test "$JQ_LIBS" == ""; then JQ_LIBS="-L$with_jq/lib -ljq" fi CPPFLAGS="$JQ_CFLAGS $CPPFLAGS" AC_CHECK_HEADER([jq.h], [HAVE_LIBJQ=1], [HAVE_LIBJQ=0]) LDFLAGS="$JQ_LIBS $LDFLAGS" AC_CHECK_LIB([jq], [jq_init], [HAVE_LIBJQ=1], [HAVE_LIBJQ=0]) if test "x$have_jq" = "x0" ; then AC_MSG_WARN("cannot find library for -ljq.") fi fi AM_CONDITIONAL(HAVE_LIBJQ, [test x"$HAVE_LIBJQ" = "x1"]) AC_SUBST(JQ_CFLAGS) AC_SUBST(JQ_LIBS) # Create Makefile from Makefile.in AC_CONFIG_FILES([Makefile]) AC_OUTPUT mod_auth_openidc-2.4.16.10/sonar-project.properties000066400000000000000000000006141476721736500222530ustar00rootroot00000000000000sonar.projectKey=OpenIDC_mod_auth_openidc sonar.organization=openidc # This is the name and version displayed in the SonarCloud UI. #sonar.projectName=mod_auth_openidc #sonar.projectVersion=1.0 # Path is relative to the sonar-project.properties file. Replace "\" by "/" on Windows. #sonar.sources=. # Encoding of the source code. Default is default system encoding #sonar.sourceEncoding=UTF-8 mod_auth_openidc-2.4.16.10/src/000077500000000000000000000000001476721736500161355ustar00rootroot00000000000000mod_auth_openidc-2.4.16.10/src/.gitignore000066400000000000000000000001411476721736500201210ustar00rootroot00000000000000/*.lo /*.o /*.slo /*.la /.libs /.deps/ /.dirstamp /config.h /config.h.in /config.h.in~ /stamp-h1 mod_auth_openidc-2.4.16.10/src/cache/000077500000000000000000000000001476721736500172005ustar00rootroot00000000000000mod_auth_openidc-2.4.16.10/src/cache/.gitignore000066400000000000000000000000671476721736500211730ustar00rootroot00000000000000/*.lo /*.o /*.slo /.libs /.deps/ /.dirstamp /hiredis-* mod_auth_openidc-2.4.16.10/src/cache/cache.h000066400000000000000000000204241476721736500204160ustar00rootroot00000000000000/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ /*************************************************************************** * Copyright (C) 2017-2025 ZmartZone Holding BV * Copyright (C) 2013-2017 Ping Identity Corporation * All rights reserved. * * DISCLAIMER OF WARRANTIES: * * THE SOFTWARE PROVIDED HEREUNDER IS PROVIDED ON AN "AS IS" BASIS, WITHOUT * ANY WARRANTIES OR REPRESENTATIONS EXPRESS, IMPLIED OR STATUTORY; INCLUDING, * WITHOUT LIMITATION, WARRANTIES OF QUALITY, PERFORMANCE, NONINFRINGEMENT, * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. NOR ARE THERE ANY * WARRANTIES CREATED BY A COURSE OR DEALING, COURSE OF PERFORMANCE OR TRADE * USAGE. FURTHERMORE, THERE ARE NO WARRANTIES THAT THE SOFTWARE WILL MEET * YOUR NEEDS OR BE FREE FROM ERRORS, OR THAT THE OPERATION OF THE SOFTWARE * WILL BE UNINTERRUPTED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * mem_cache-like interface and semantics (string keys/values) using a storage backend * * @Author: Hans Zandbelt - hans.zandbelt@openidc.com */ #ifndef _MOD_AUTH_OPENIDC_CACHE_H_ #define _MOD_AUTH_OPENIDC_CACHE_H_ #include "const.h" // for the PACKAGE_* defines #include #include /* * The maximum cache key size: * must be greater than a base64url encoded sha256 hash result (32 bytes) and must * be a multiple of 8 since OIDC_CACHE_SHM_KEY_MAX is derived from it (memory alignment) */ #define OIDC_CACHE_KEY_SIZE_MAX 512 typedef void *(*oidc_cache_cfg_create)(apr_pool_t *pool); typedef int (*oidc_cache_post_config_function)(server_rec *s); typedef int (*oidc_cache_child_init_function)(apr_pool_t *p, server_rec *s); typedef apr_byte_t (*oidc_cache_get_function)(request_rec *r, const char *section, const char *key, char **value); typedef apr_byte_t (*oidc_cache_set_function)(request_rec *r, const char *section, const char *key, const char *value, apr_time_t expiry); typedef int (*oidc_cache_destroy_function)(server_rec *s); typedef struct oidc_cache_t { const char *name; int encrypt_by_default; oidc_cache_post_config_function post_config; oidc_cache_child_init_function child_init; oidc_cache_get_function get; oidc_cache_set_function set; oidc_cache_destroy_function destroy; } oidc_cache_t; typedef struct oidc_cache_mutex_t { apr_global_mutex_t *gmutex; apr_proc_mutex_t *pmutex; char *mutex_filename; apr_byte_t is_global; apr_byte_t is_parent; } oidc_cache_mutex_t; oidc_cache_mutex_t *oidc_cache_mutex_create(apr_pool_t *pool, apr_byte_t global); char *oidc_cache_status2str(apr_pool_t *p, apr_status_t statcode); apr_byte_t oidc_cache_mutex_post_config(server_rec *s, oidc_cache_mutex_t *m, const char *type); apr_status_t oidc_cache_mutex_child_init(apr_pool_t *p, server_rec *s, oidc_cache_mutex_t *m); apr_byte_t oidc_cache_mutex_lock(apr_pool_t *pool, server_rec *s, oidc_cache_mutex_t *m); apr_byte_t oidc_cache_mutex_unlock(apr_pool_t *pool, server_rec *s, oidc_cache_mutex_t *m); apr_byte_t oidc_cache_mutex_destroy(server_rec *s, oidc_cache_mutex_t *m); apr_byte_t oidc_cache_get(request_rec *r, const char *section, const char *key, char **value); apr_byte_t oidc_cache_set(request_rec *r, const char *section, const char *key, const char *value, apr_time_t expiry); #define OIDC_CACHE_SECTION_SESSION "s" #define OIDC_CACHE_SECTION_NONCE "n" #define OIDC_CACHE_SECTION_JWKS "j" #define OIDC_CACHE_SECTION_ACCESS_TOKEN "a" #define OIDC_CACHE_SECTION_REFRESH_TOKEN "e" #define OIDC_CACHE_SECTION_PROVIDER "p" #define OIDC_CACHE_SECTION_OAUTH_PROVIDER "o" #define OIDC_CACHE_SECTION_JTI "t" #define OIDC_CACHE_SECTION_REQUEST_URI "r" #define OIDC_CACHE_SECTION_SID "d" #define OIDC_CACHE_SECTION_USERINFO_SJWT "u" #define OIDC_CACHE_SECTION_JQ_FILTER "q" // TODO: now every section occupies the same space; we may want to differentiate // according to section-based size, at least for the shm backend #define oidc_cache_get_session(r, key, value) oidc_cache_get(r, OIDC_CACHE_SECTION_SESSION, key, value) #define oidc_cache_get_nonce(r, key, value) oidc_cache_get(r, OIDC_CACHE_SECTION_NONCE, key, value) #define oidc_cache_get_jwks(r, key, value) oidc_cache_get(r, OIDC_CACHE_SECTION_JWKS, key, value) #define oidc_cache_get_access_token(r, key, value) oidc_cache_get(r, OIDC_CACHE_SECTION_ACCESS_TOKEN, key, value) #define oidc_cache_get_refresh_token(r, key, value) oidc_cache_get(r, OIDC_CACHE_SECTION_REFRESH_TOKEN, key, value) #define oidc_cache_get_provider(r, key, value) oidc_cache_get(r, OIDC_CACHE_SECTION_PROVIDER, key, value) #define oidc_cache_get_oauth_provider(r, key, value) oidc_cache_get(r, OIDC_CACHE_SECTION_OAUTH_PROVIDER, key, value) #define oidc_cache_get_jti(r, key, value) oidc_cache_get(r, OIDC_CACHE_SECTION_JTI, key, value) #define oidc_cache_get_request_uri(r, key, value) oidc_cache_get(r, OIDC_CACHE_SECTION_REQUEST_URI, key, value) #define oidc_cache_get_sid(r, key, value) oidc_cache_get(r, OIDC_CACHE_SECTION_SID, key, value) #define oidc_cache_get_signed_jwt(r, key, value) oidc_cache_get(r, OIDC_CACHE_SECTION_USERINFO_SJWT, key, value) #define oidc_cache_get_jq_filter(r, key, value) oidc_cache_get(r, OIDC_CACHE_SECTION_JQ_FILTER, key, value) #define oidc_cache_set_session(r, key, value, expiry) oidc_cache_set(r, OIDC_CACHE_SECTION_SESSION, key, value, expiry) #define oidc_cache_set_nonce(r, key, value, expiry) oidc_cache_set(r, OIDC_CACHE_SECTION_NONCE, key, value, expiry) #define oidc_cache_set_jwks(r, key, value, expiry) oidc_cache_set(r, OIDC_CACHE_SECTION_JWKS, key, value, expiry) #define oidc_cache_set_access_token(r, key, value, expiry) \ oidc_cache_set(r, OIDC_CACHE_SECTION_ACCESS_TOKEN, key, value, expiry) #define oidc_cache_set_refresh_token(r, key, value, expiry) \ oidc_cache_set(r, OIDC_CACHE_SECTION_REFRESH_TOKEN, key, value, expiry) #define oidc_cache_set_provider(r, key, value, expiry) \ oidc_cache_set(r, OIDC_CACHE_SECTION_PROVIDER, key, value, expiry) #define oidc_cache_set_oauth_provider(r, key, value, expiry) \ oidc_cache_set(r, OIDC_CACHE_SECTION_OAUTH_PROVIDER, key, value, expiry) #define oidc_cache_set_jti(r, key, value, expiry) oidc_cache_set(r, OIDC_CACHE_SECTION_JTI, key, value, expiry) #define oidc_cache_set_request_uri(r, key, value, expiry) \ oidc_cache_set(r, OIDC_CACHE_SECTION_REQUEST_URI, key, value, expiry) #define oidc_cache_set_sid(r, key, value, expiry) oidc_cache_set(r, OIDC_CACHE_SECTION_SID, key, value, expiry) #define oidc_cache_set_signed_jwt(r, key, value, expiry) \ oidc_cache_set(r, OIDC_CACHE_SECTION_USERINFO_SJWT, key, value, expiry) #define oidc_cache_set_jq_filter(r, key, value, expiry) \ oidc_cache_set(r, OIDC_CACHE_SECTION_JQ_FILTER, key, value, expiry) extern oidc_cache_t oidc_cache_file; extern oidc_cache_t oidc_cache_shm; #ifdef USE_MEMCACHE extern oidc_cache_t oidc_cache_memcache; #endif #ifdef USE_LIBHIREDIS extern oidc_cache_t oidc_cache_redis; #endif #endif /* _MOD_AUTH_OPENIDC_CACHE_H_ */ mod_auth_openidc-2.4.16.10/src/cache/common.c000066400000000000000000000301031476721736500206310ustar00rootroot00000000000000/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ /*************************************************************************** * Copyright (C) 2017-2025 ZmartZone Holding BV * Copyright (C) 2013-2017 Ping Identity Corporation * All rights reserved. * * DISCLAIMER OF WARRANTIES: * * THE SOFTWARE PROVIDED HEREUNDER IS PROVIDED ON AN "AS IS" BASIS, WITHOUT * ANY WARRANTIES OR REPRESENTATIONS EXPRESS, IMPLIED OR STATUTORY; INCLUDING, * WITHOUT LIMITATION, WARRANTIES OF QUALITY, PERFORMANCE, NONINFRINGEMENT, * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. NOR ARE THERE ANY * WARRANTIES CREATED BY A COURSE OR DEALING, COURSE OF PERFORMANCE OR TRADE * USAGE. FURTHERMORE, THERE ARE NO WARRANTIES THAT THE SOFTWARE WILL MEET * YOUR NEEDS OR BE FREE FROM ERRORS, OR THAT THE OPERATION OF THE SOFTWARE * WILL BE UNINTERRUPTED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * core cache functions: locking, crypto and utils * * @Author: Hans Zandbelt - hans.zandbelt@openidc.com */ #ifndef WIN32 #include #endif #include "cache/cache.h" #include "cfg/cache.h" #include "cfg/cfg_int.h" #include "jose.h" #include "metrics.h" #include "util.h" #ifdef AP_NEED_SET_MUTEX_PERMS #include "unixd.h" #endif /* create the cache lock context */ oidc_cache_mutex_t *oidc_cache_mutex_create(apr_pool_t *pool, apr_byte_t global) { oidc_cache_mutex_t *ctx = apr_pcalloc(pool, sizeof(oidc_cache_mutex_t)); ctx->gmutex = NULL; ctx->pmutex = NULL; ctx->mutex_filename = NULL; ctx->is_parent = TRUE; ctx->is_global = global; return ctx; } #define OIDC_CACHE_ERROR_STR_MAX 255 /* * convert a apr status code to a string */ char *oidc_cache_status2str(apr_pool_t *p, apr_status_t statcode) { char buf[OIDC_CACHE_ERROR_STR_MAX]; apr_strerror(statcode, buf, OIDC_CACHE_ERROR_STR_MAX); return apr_pstrdup(p, buf); } apr_byte_t oidc_cache_mutex_post_config(server_rec *s, oidc_cache_mutex_t *m, const char *type) { apr_status_t rv = APR_SUCCESS; const char *dir; /* construct the mutex filename */ rv = apr_temp_dir_get(&dir, s->process->pool); if (rv != APR_SUCCESS) { oidc_serror(s, "apr_temp_dir_get failed: could not find a temp dir: %s", oidc_cache_status2str(s->process->pool, rv)); return FALSE; } m->mutex_filename = apr_psprintf(s->process->pool, "%s/mod_auth_openidc_%s_mutex.%ld.%pp", dir, type, (long int)getpid(), s); /* set the lock type */ apr_lockmech_e mech = #ifdef OIDC_LOCK OIDC_LOCK #elif APR_HAS_POSIXSEM_SERIALIZE APR_LOCK_POSIXSEM #else APR_LOCK_DEFAULT #endif ; /* create the mutex lock */ if (m->is_global) rv = apr_global_mutex_create(&m->gmutex, (const char *)m->mutex_filename, mech, s->process->pool); else rv = apr_proc_mutex_create(&m->pmutex, (const char *)m->mutex_filename, mech, s->process->pool); if (rv != APR_SUCCESS) { oidc_serror( s, "apr_global_mutex_create/apr_proc_mutex_create failed to create mutex (%d) on file %s: %s (%d)", mech, m->mutex_filename, oidc_cache_status2str(s->process->pool, rv), rv); return FALSE; } /* need this on Linux */ #ifdef AP_NEED_SET_MUTEX_PERMS if (m->is_global) { #if MODULE_MAGIC_NUMBER_MAJOR >= 20081201 rv = ap_unixd_set_global_mutex_perms(m->gmutex); #else rv = unixd_set_global_mutex_perms(m->gmutex); #endif if (rv != APR_SUCCESS) { oidc_serror(s, "unixd_set_global_mutex_perms failed; could not set permissions: %s (%d)", oidc_cache_status2str(s->process->pool, rv), rv); return FALSE; } } #endif oidc_slog(s, APLOG_TRACE1, "create: %pp (m=%pp,s=%pp, p=%d)", m, m->gmutex ? m->gmutex : 0, s, m->is_parent); return TRUE; } /* * initialize the cache lock in a child process */ apr_status_t oidc_cache_mutex_child_init(apr_pool_t *p, server_rec *s, oidc_cache_mutex_t *m) { oidc_slog(s, APLOG_TRACE1, "init: %pp (m=%pp,s=%pp, p=%d)", m, m->gmutex ? m->gmutex : 0, s, m->is_parent); if (m->is_parent == FALSE) return APR_SUCCESS; /* initialize the lock for the child process */ apr_status_t rv; if (m->is_global) rv = apr_global_mutex_child_init(&m->gmutex, (const char *)m->mutex_filename, p); else rv = apr_proc_mutex_child_init(&m->pmutex, (const char *)m->mutex_filename, p); if (rv != APR_SUCCESS) { oidc_serror( s, "apr_global_mutex_child_init/apr_proc_mutex_child_init failed to reopen mutex on file %s: %s (%d)", m->mutex_filename, oidc_cache_status2str(p, rv), rv); } m->is_parent = FALSE; return rv; } /* * mutex lock */ apr_byte_t oidc_cache_mutex_lock(apr_pool_t *pool, server_rec *s, oidc_cache_mutex_t *m) { apr_status_t rv; if (m->is_global) rv = apr_global_mutex_lock(m->gmutex); else rv = apr_proc_mutex_lock(m->pmutex); if (rv != APR_SUCCESS) oidc_serror(s, "apr_global_mutex_lock/apr_proc_mutex_lock failed: %s (%d)", oidc_cache_status2str(pool, rv), rv); return TRUE; } /* * mutex unlock */ apr_byte_t oidc_cache_mutex_unlock(apr_pool_t *pool, server_rec *s, oidc_cache_mutex_t *m) { apr_status_t rv; if (m->is_global) rv = apr_global_mutex_unlock(m->gmutex); else rv = apr_proc_mutex_unlock(m->pmutex); if (rv != APR_SUCCESS) oidc_serror(s, "apr_global_mutex_unlock/apr_proc_mutex_unlock failed: %s (%d)", oidc_cache_status2str(pool, rv), rv); return TRUE; } /* * destroy mutex */ apr_byte_t oidc_cache_mutex_destroy(server_rec *s, oidc_cache_mutex_t *m) { apr_status_t rv = APR_SUCCESS; oidc_slog(s, APLOG_TRACE1, "init: %pp (m=%pp,s=%pp, p=%d)", m, m->gmutex ? m->gmutex : 0, s, m->is_parent); if (m && (m->is_parent == TRUE)) { if ((m->is_global) && (m->gmutex)) { rv = apr_global_mutex_destroy(m->gmutex); m->gmutex = NULL; } else if (m->pmutex) { rv = apr_proc_mutex_destroy(m->pmutex); m->pmutex = NULL; } oidc_sdebug(s, "apr_global_mutex_destroy/apr_proc_mutex_destroy returned :%d", rv); } return (rv == APR_SUCCESS); } /* * AES GCM encrypt using the crypto passphrase as symmetric key */ static inline apr_byte_t oidc_cache_crypto_encrypt(request_rec *r, const char *plaintext, const oidc_crypto_passphrase_t *passphrase, char **result) { return oidc_util_jwt_create(r, passphrase, plaintext, result); } /* * AES GCM decrypt using the crypto passphrase as symmetric key */ static inline apr_byte_t oidc_cache_crypto_decrypt(request_rec *r, const char *cache_value, char *secret, char **plaintext) { oidc_crypto_passphrase_t passphrase; passphrase.secret1 = secret; passphrase.secret2 = NULL; return oidc_util_jwt_verify(r, &passphrase, cache_value, plaintext); } /* * hash a cache key, useful for large keys e.g. JWT access/refresh tokens */ static inline char *oidc_cache_get_hashed_key(request_rec *r, const char *key) { char *output = NULL; if (oidc_util_hash_string_and_base64url_encode(r, OIDC_JOSE_ALG_SHA256, key, &output) == FALSE) { oidc_error(r, "oidc_util_hash_string_and_base64url_encode returned an error"); return NULL; } return output; } /* * construct a cache key */ static inline apr_byte_t oidc_cache_get_key(request_rec *r, const char *s_key, const char *s_secret, int encrypted, const char **r_key) { /* see if encryption is turned on, so we'll hash passphrase+key */ if (encrypted == 1) { if (s_secret == NULL) { oidc_error(r, "could not decrypt cache entry because " OIDCCryptoPassphrase " is not set"); return FALSE; } *r_key = oidc_cache_get_hashed_key(r, apr_psprintf(r->pool, "%s:%s", s_secret, s_key)); } else if (_oidc_strlen(s_key) >= OIDC_CACHE_KEY_SIZE_MAX) { *r_key = oidc_cache_get_hashed_key(r, s_key); } else { *r_key = s_key; } return TRUE; } /* * get a key/value string pair from the cache, possibly decrypting it */ apr_byte_t oidc_cache_get(request_rec *r, const char *section, const char *key, char **value) { oidc_cfg_t *cfg = ap_get_module_config(r->server->module_config, &auth_openidc_module); int encrypted = oidc_cfg_cache_encrypt_get(cfg); apr_byte_t rc = FALSE; char *msg = NULL; const char *s_key = NULL; char *cache_value = NULL; char *s_secret = NULL; oidc_debug(r, "enter: %s (section=%s, decrypt=%d, type=%s)", key, section, encrypted, cfg->cache.impl->name); s_secret = cfg->crypto_passphrase.secret1; if (oidc_cache_get_key(r, key, s_secret, encrypted, &s_key) == FALSE) goto end; OIDC_METRICS_TIMING_START(r, cfg); /* get the value from the cache */ if (cfg->cache.impl->get(r, section, s_key, &cache_value) == FALSE) goto end; /* see if it is any good */ if ((cache_value == NULL) && (encrypted == 1) && (cfg->crypto_passphrase.secret2 != NULL)) { oidc_debug(r, "2nd try with previous passphrase"); s_secret = cfg->crypto_passphrase.secret2; if (oidc_cache_get_key(r, key, s_secret, encrypted, &s_key) == FALSE) goto end; if (cfg->cache.impl->get(r, section, s_key, &cache_value) == FALSE) goto end; } OIDC_METRICS_TIMING_ADD(r, cfg, OM_CACHE_READ); rc = TRUE; if (cache_value == NULL) goto end; /* see if encryption is turned on */ if (encrypted == 0) { *value = apr_pstrdup(r->pool, cache_value); goto end; } rc = oidc_cache_crypto_decrypt(r, cache_value, s_secret, value); end: /* log the result */ msg = apr_psprintf(r->pool, "from %s cache backend for %skey %s", cfg->cache.impl->name, encrypted ? "encrypted " : "", key); if (rc == TRUE) { if (*value != NULL) oidc_debug(r, "cache hit: return %d bytes %s", *value ? (int)_oidc_strlen(*value) : 0, msg); else oidc_debug(r, "cache miss %s", msg); } else { OIDC_METRICS_COUNTER_INC(r, cfg, OM_CACHE_ERROR); oidc_warn(r, "error retrieving value %s", msg); } return rc; } /* * store a key/value string pair in the cache, possibly in encrypted form */ apr_byte_t oidc_cache_set(request_rec *r, const char *section, const char *key, const char *value, apr_time_t expiry) { oidc_cfg_t *cfg = ap_get_module_config(r->server->module_config, &auth_openidc_module); int encrypted = oidc_cfg_cache_encrypt_get(cfg); char *encoded = NULL; apr_byte_t rc = FALSE; char *msg = NULL; const char *s_key = NULL; oidc_debug(r, "enter: %s (section=%s, len=%d, encrypt=%d, ttl(s)=%" APR_TIME_T_FMT ", type=%s)", key, section, value ? (int)_oidc_strlen(value) : 0, encrypted, apr_time_sec(expiry - apr_time_now()), cfg->cache.impl->name); if (oidc_cache_get_key(r, key, cfg->crypto_passphrase.secret1, encrypted, &s_key) == FALSE) goto end; /* see if we need to encrypt */ if ((encrypted == 1) && (value != NULL)) { if (oidc_cache_crypto_encrypt(r, value, &cfg->crypto_passphrase, &encoded) == FALSE) goto end; value = encoded; } OIDC_METRICS_TIMING_START(r, cfg); /* store the resulting value in the cache */ rc = cfg->cache.impl->set(r, section, s_key, value, expiry); OIDC_METRICS_TIMING_ADD(r, cfg, OM_CACHE_WRITE); end: /* log the result */ msg = apr_psprintf(r->pool, "%d bytes in %s cache backend for %skey %s", (value ? (int)_oidc_strlen(value) : 0), (cfg->cache.impl->name ? cfg->cache.impl->name : ""), (encrypted ? "encrypted " : ""), (key ? key : "")); if (rc == TRUE) { oidc_debug(r, "successfully stored %s", msg); } else { OIDC_METRICS_COUNTER_INC(r, cfg, OM_CACHE_ERROR); oidc_warn(r, "could NOT store %s", msg); } return rc; } mod_auth_openidc-2.4.16.10/src/cache/file.c000066400000000000000000000345631476721736500202760ustar00rootroot00000000000000/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ /*************************************************************************** * Copyright (C) 2017-2025 ZmartZone Holding BV * Copyright (C) 2013-2017 Ping Identity Corporation * All rights reserved. * * DISCLAIMER OF WARRANTIES: * * THE SOFTWARE PROVIDED HEREUNDER IS PROVIDED ON AN "AS IS" BASIS, WITHOUT * ANY WARRANTIES OR REPRESENTATIONS EXPRESS, IMPLIED OR STATUTORY; INCLUDING, * WITHOUT LIMITATION, WARRANTIES OF QUALITY, PERFORMANCE, NONINFRINGEMENT, * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. NOR ARE THERE ANY * WARRANTIES CREATED BY A COURSE OR DEALING, COURSE OF PERFORMANCE OR TRADE * USAGE. FURTHERMORE, THERE ARE NO WARRANTIES THAT THE SOFTWARE WILL MEET * YOUR NEEDS OR BE FREE FROM ERRORS, OR THAT THE OPERATION OF THE SOFTWARE * WILL BE UNINTERRUPTED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * caching using a file storage backend * * @Author: Hans Zandbelt - hans.zandbelt@openidc.com */ #include "cfg/cache.h" #include "cfg/cfg_int.h" #include "util.h" /* * header structure that holds the metadata info for a cache file entry */ typedef struct { /* length of the cached data */ apr_size_t len; /* cache expiry timestamp */ apr_time_t expire; } oidc_cache_file_info_t; /* * prefix that distinguishes mod_auth_openidc cache files from other files in the same directory (/tmp) */ #define OIDC_CACHE_FILE_PREFIX "mod-auth-openidc-" /* post config routine */ int oidc_cache_file_post_config(server_rec *s) { apr_status_t rv = APR_SUCCESS; oidc_cfg_t *cfg = (oidc_cfg_t *)ap_get_module_config(s->module_config, &auth_openidc_module); if (cfg->cache.file_dir == NULL) { /* by default we'll use the OS specified /tmp dir for cache files */ rv = apr_temp_dir_get((const char **)&cfg->cache.file_dir, s->process->pool); if (rv != APR_SUCCESS) { oidc_serror(s, "apr_temp_dir_get failed: could not find a temp dir: %s", oidc_cache_status2str(s->process->pool, rv)); return HTTP_INTERNAL_SERVER_ERROR; } } return OK; } /* * return the cache file name for a specified key */ static const char *oidc_cache_file_name(request_rec *r, const char *section, const char *key) { return apr_psprintf(r->pool, "%s%s-%s", OIDC_CACHE_FILE_PREFIX, section, oidc_http_url_encode(r, key)); } /* * return the fully qualified path name to a cache file for a specified key */ static const char *oidc_cache_file_path(request_rec *r, const char *section, const char *key) { oidc_cfg_t *cfg = ap_get_module_config(r->server->module_config, &auth_openidc_module); return apr_psprintf(r->pool, "%s/%s", cfg->cache.file_dir, oidc_cache_file_name(r, section, key)); } /* * read a specified number of bytes from a cache file in to a preallocated buffer */ static apr_status_t oidc_cache_file_read(request_rec *r, const char *path, apr_file_t *fd, void *buf, const apr_size_t len) { apr_status_t rc = APR_SUCCESS; apr_size_t bytes_read = 0; char s_err[128]; /* (blocking) read the requested number of bytes */ rc = apr_file_read_full(fd, buf, len, &bytes_read); /* test for system errors */ if (rc != APR_SUCCESS) { oidc_error(r, "could not read from: %s (%s)", path, apr_strerror(rc, s_err, sizeof(s_err))); } /* ensure that we've got the requested number of bytes */ if (bytes_read != len) { oidc_error(r, "could not read enough bytes from: \"%s\", bytes_read (%" APR_SIZE_T_FMT ") != len (%" APR_SIZE_T_FMT ")", path, bytes_read, len); rc = APR_EGENERAL; } return rc; } /* * write a specified number of bytes from a buffer to a cache file */ static apr_status_t oidc_cache_file_write(request_rec *r, const char *path, apr_file_t *fd, const void *buf, const apr_size_t len) { apr_status_t rc = APR_SUCCESS; apr_size_t bytes_written = 0; char s_err[128]; /* (blocking) write the number of bytes in the buffer */ rc = apr_file_write_full(fd, buf, len, &bytes_written); /* check for a system error */ if (rc != APR_SUCCESS) { oidc_error(r, "could not write to: \"%s\" (%s)", path, apr_strerror(rc, s_err, sizeof(s_err))); return rc; } /* check that all bytes from the header were written */ if (bytes_written != len) { oidc_error(r, "could not write enough bytes to: \"%s\", bytes_written (%" APR_SIZE_T_FMT ") != len (%" APR_SIZE_T_FMT ")", path, bytes_written, len); return APR_EGENERAL; } return rc; } /* * get a value for the specified key from the cache */ static apr_byte_t oidc_cache_file_get(request_rec *r, const char *section, const char *key, char **value) { apr_file_t *fd = NULL; apr_status_t rc = APR_SUCCESS; char s_err[128]; /* get the fully qualified path to the cache file based on the key name */ const char *path = oidc_cache_file_path(r, section, key); /* open the cache file if it exists, otherwise we just have a "regular" cache miss */ if (apr_file_open(&fd, path, APR_FOPEN_READ | APR_FOPEN_BUFFERED, APR_OS_DEFAULT, r->pool) != APR_SUCCESS) { oidc_debug(r, "cache miss for key \"%s\"", key); return TRUE; } /* the file exists, now lock it */ apr_file_lock(fd, APR_FLOCK_EXCLUSIVE); /* move the read pointer to the very start of the cache file */ apr_off_t begin = 0; apr_file_seek(fd, APR_SET, &begin); /* read a header with metadata */ oidc_cache_file_info_t info; if ((rc = oidc_cache_file_read(r, path, fd, &info, sizeof(oidc_cache_file_info_t))) != APR_SUCCESS) goto error_close; /* check if this cache entry has already expired */ if (apr_time_now() >= info.expire) { /* yep, expired: unlock and close before deleting the cache file */ apr_file_unlock(fd); apr_file_close(fd); /* log this event */ oidc_debug(r, "cache entry \"%s\" expired, removing file \"%s\"", key, path); /* and kill it */ if ((rc = apr_file_remove(path, r->pool)) != APR_SUCCESS) { oidc_error(r, "could not delete cache file \"%s\" (%s)", path, apr_strerror(rc, s_err, sizeof(s_err))); } /* nothing strange happened really */ return TRUE; } /* allocate space for the actual value based on the data size info in the header (+1 for \0 termination) */ *value = apr_palloc(r->pool, info.len); /* (blocking) read the requested data in to the buffer */ rc = oidc_cache_file_read(r, path, fd, (void *)(*value), info.len); /* barf on failure */ if (rc != APR_SUCCESS) { oidc_error(r, "could not read cache value from \"%s\"", path); goto error_close; } /* we're done, unlock and close the file */ apr_file_unlock(fd); apr_file_close(fd); return TRUE; error_close: apr_file_unlock(fd); apr_file_close(fd); oidc_error(r, "return error status %d (%s)", rc, apr_strerror(rc, s_err, sizeof(s_err))); return FALSE; } // TODO: make these configurable? #define OIDC_CACHE_FILE_LAST_CLEANED "last-cleaned" /* * delete all expired entries from the cache directory */ static apr_status_t oidc_cache_file_clean(request_rec *r) { apr_status_t rc = APR_SUCCESS; apr_dir_t *dir = NULL; apr_file_t *fd = NULL; apr_status_t i; apr_finfo_t fi; oidc_cache_file_info_t info; char s_err[128]; oidc_cfg_t *cfg = ap_get_module_config(r->server->module_config, &auth_openidc_module); /* get the path to the metadata file that holds "last cleaned" metadata info */ const char *metadata_path = oidc_cache_file_path(r, "cache-file", OIDC_CACHE_FILE_LAST_CLEANED); /* open the metadata file if it exists */ if (apr_stat(&fi, metadata_path, APR_FINFO_MTIME, r->pool) == APR_SUCCESS) { /* really only clean once per so much time, check that we haven not recently run */ if (apr_time_now() < fi.mtime + apr_time_from_sec(oidc_cfg_cache_file_clean_interval_get(cfg))) { oidc_debug( r, "last cleanup call was less than %d seconds ago (next one as early as in %" APR_TIME_T_FMT " secs)", oidc_cfg_cache_file_clean_interval_get(cfg), apr_time_sec(fi.mtime + apr_time_from_sec(oidc_cfg_cache_file_clean_interval_get(cfg)) - apr_time_now())); return APR_SUCCESS; } /* time to clean, reset the modification time of the metadata file to reflect the timestamp of this * cleaning cycle */ apr_file_mtime_set(metadata_path, apr_time_now(), r->pool); oidc_debug(r, "start cleaning cycle"); } else { /* no metadata file exists yet, create one (and open it) */ if ((rc = apr_file_open(&fd, metadata_path, (APR_FOPEN_WRITE | APR_FOPEN_CREATE), APR_OS_DEFAULT, r->pool)) != APR_SUCCESS) { oidc_error(r, "error creating cache timestamp file '%s' (%s)", metadata_path, apr_strerror(rc, s_err, sizeof(s_err))); return rc; } /* and cleanup... */ if ((rc = apr_file_close(fd)) != APR_SUCCESS) { oidc_error(r, "error closing cache timestamp file '%s' (%s)", metadata_path, apr_strerror(rc, s_err, sizeof(s_err))); } } /* time to clean, open the cache directory */ if ((rc = apr_dir_open(&dir, cfg->cache.file_dir, r->pool)) != APR_SUCCESS) { oidc_error(r, "error opening cache directory '%s' for cleaning (%s)", cfg->cache.file_dir, apr_strerror(rc, s_err, sizeof(s_err))); return rc; } /* loop trough the cache file entries */ do { /* read the next entry from the directory */ i = apr_dir_read(&fi, APR_FINFO_NAME, dir); if (i == APR_SUCCESS) { /* skip non-cache entries, cq. the ".", ".." and the metadata file */ if ((fi.name[0] == OIDC_CHAR_DOT) || (_oidc_strstr(fi.name, OIDC_CACHE_FILE_PREFIX) != fi.name) || (_oidc_strcmp(fi.name, oidc_cache_file_name(r, "cache-file", OIDC_CACHE_FILE_LAST_CLEANED)) == 0)) continue; /* get the fully qualified path to the cache file and open it */ const char *path = apr_psprintf(r->pool, "%s/%s", cfg->cache.file_dir, fi.name); if ((rc = apr_file_open(&fd, path, APR_FOPEN_READ, APR_OS_DEFAULT, r->pool)) != APR_SUCCESS) { oidc_error(r, "unable to open cache entry \"%s\" (%s)", path, apr_strerror(rc, s_err, sizeof(s_err))); continue; } /* read the header with cache metadata info */ apr_file_lock(fd, APR_FLOCK_EXCLUSIVE); rc = oidc_cache_file_read(r, path, fd, &info, sizeof(oidc_cache_file_info_t)); apr_file_unlock(fd); apr_file_close(fd); if (rc == APR_SUCCESS) { /* check if this entry expired, if not just continue to the next entry */ if (apr_time_now() < info.expire) continue; /* the cache entry expired, we're going to remove it so log that event */ oidc_debug(r, "cache entry (%s) expired, removing file \"%s\")", fi.name, path); } else { /* file open returned an error, log that */ oidc_error(r, "cache entry (%s) corrupted (%s), removing file \"%s\"", fi.name, apr_strerror(rc, s_err, sizeof(s_err)), path); } /* delete the cache file */ if ((rc = apr_file_remove(path, r->pool)) != APR_SUCCESS) { /* hrm, this will most probably happen again on the next run... */ oidc_error(r, "could not delete cache file \"%s\" (%s)", path, apr_strerror(rc, s_err, sizeof(s_err))); } } } while (i == APR_SUCCESS); apr_dir_close(dir); return APR_SUCCESS; } /* * write a value for the specified key to the cache */ static apr_byte_t oidc_cache_file_set(request_rec *r, const char *section, const char *key, const char *value, apr_time_t expiry) { apr_file_t *fd = NULL; apr_status_t rc = APR_SUCCESS; char s_err[128]; char *rnd = NULL; oidc_util_generate_random_string(r, &rnd, 12); /* get the fully qualified path to the cache file based on the key name */ const char *target = oidc_cache_file_path(r, section, key); const char *path = apr_psprintf(r->pool, "%s.%s.tmp", target, rnd); /* only on writes (not on reads) we clean the cache first (if not done recently) */ oidc_cache_file_clean(r); /* just remove cache file if value is NULL */ if (value == NULL) { if ((rc = apr_file_remove(target, r->pool)) != APR_SUCCESS) { oidc_error(r, "could not delete cache file \"%s\" (%s)", path, apr_strerror(rc, s_err, sizeof(s_err))); } return TRUE; } /* try to open the cache file for writing, creating it if it does not exist */ if ((rc = apr_file_open(&fd, path, (APR_FOPEN_WRITE | APR_FOPEN_CREATE), APR_OS_DEFAULT, r->pool)) != APR_SUCCESS) { oidc_error(r, "cache file \"%s\" could not be opened (%s)", path, apr_strerror(rc, s_err, sizeof(s_err))); return FALSE; } /* lock the file and move the write pointer to the start of it */ apr_file_lock(fd, APR_FLOCK_EXCLUSIVE); apr_off_t begin = 0; apr_file_trunc(fd, begin); apr_file_seek(fd, APR_SET, &begin); /* construct the metadata for this cache entry in the header info */ oidc_cache_file_info_t info; info.expire = expiry; info.len = _oidc_strlen(value) + 1; /* write the header */ if (oidc_cache_file_write(r, path, fd, &info, sizeof(oidc_cache_file_info_t)) != APR_SUCCESS) return FALSE; /* next write the value */ oidc_cache_file_write(r, path, fd, (const void *)value, info.len); /* unlock and close the written file */ apr_file_unlock(fd); apr_file_close(fd); if ((rc = apr_file_rename(path, target, r->pool)) != APR_SUCCESS) { oidc_error(r, "cache file: %s could not be renamed to: %s", path, target); return FALSE; } /* log our success/failure */ oidc_debug(r, "%s entry for key \"%s\" in file of %" APR_SIZE_T_FMT " bytes", (rc == APR_SUCCESS) ? "successfully stored" : "could not store", key, info.len); return (rc == APR_SUCCESS); } // clang-format off oidc_cache_t oidc_cache_file = { "file", 1, oidc_cache_file_post_config, NULL, oidc_cache_file_get, oidc_cache_file_set, NULL }; // clang-format on mod_auth_openidc-2.4.16.10/src/cache/memcache.c000066400000000000000000000251061476721736500211120ustar00rootroot00000000000000/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ /*************************************************************************** * Copyright (C) 2017-2025 ZmartZone Holding BV * Copyright (C) 2013-2017 Ping Identity Corporation * All rights reserved. * * DISCLAIMER OF WARRANTIES: * * THE SOFTWARE PROVIDED HEREUNDER IS PROVIDED ON AN "AS IS" BASIS, WITHOUT * ANY WARRANTIES OR REPRESENTATIONS EXPRESS, IMPLIED OR STATUTORY; INCLUDING, * WITHOUT LIMITATION, WARRANTIES OF QUALITY, PERFORMANCE, NONINFRINGEMENT, * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. NOR ARE THERE ANY * WARRANTIES CREATED BY A COURSE OR DEALING, COURSE OF PERFORMANCE OR TRADE * USAGE. FURTHERMORE, THERE ARE NO WARRANTIES THAT THE SOFTWARE WILL MEET * YOUR NEEDS OR BE FREE FROM ERRORS, OR THAT THE OPERATION OF THE SOFTWARE * WILL BE UNINTERRUPTED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * caching using a memcache backend * * @Author: Hans Zandbelt - hans.zandbelt@openidc.com */ #include "cfg/cache.h" #include "cfg/cfg_int.h" #include #include #include /* * avoid including mod_http2.h (assume the function signature is stable) */ APR_DECLARE_OPTIONAL_FN(void, http2_get_num_workers, (server_rec * s, int *minw, int *max)); typedef struct oidc_cache_cfg_memcache_t { /* cache_type = memcache: memcache ptr */ apr_memcache_t *cache_memcache; } oidc_cache_cfg_memcache_t; /* create the cache context */ static void *oidc_cache_memcache_cfg_create(apr_pool_t *pool) { oidc_cache_cfg_memcache_t *context = apr_pcalloc(pool, sizeof(oidc_cache_cfg_memcache_t)); context->cache_memcache = NULL; return context; } /* * initialize the memcache struct to a number of memcache servers */ static int oidc_cache_memcache_post_config(server_rec *s) { oidc_cfg_t *cfg = (oidc_cfg_t *)ap_get_module_config(s->module_config, &auth_openidc_module); if (cfg->cache.cfg != NULL) return APR_SUCCESS; oidc_cache_cfg_memcache_t *context = oidc_cache_memcache_cfg_create(s->process->pool); cfg->cache.cfg = context; apr_status_t rv = APR_SUCCESS; apr_uint16_t nservers = 0; char *split; char *tok; apr_pool_t *p = s->process->pool; APR_OPTIONAL_FN_TYPE(http2_get_num_workers) * get_h2_num_workers; int max_threads = 0; int minw = 0; int maxw = 0; apr_uint32_t min = 0; apr_uint32_t smax = 0; apr_uint32_t hmax = 0; apr_uint32_t ttl = 0; ; if (oidc_cfg_cache_memcache_servers_get(cfg) == NULL) { oidc_serror(s, "cache type is set to \"memcache\", but no valid " OIDCMemCacheServers " setting was found"); return HTTP_INTERNAL_SERVER_ERROR; } /* loop over the provided memcache servers to find out the number of servers configured */ char *cache_config = apr_pstrdup(p, oidc_cfg_cache_memcache_servers_get(cfg)); split = apr_strtok(cache_config, OIDC_STR_SPACE, &tok); while (split) { nservers++; split = apr_strtok(NULL, OIDC_STR_SPACE, &tok); } /* allocated space for the number of servers */ rv = apr_memcache_create(p, nservers, 0, &context->cache_memcache); if (rv != APR_SUCCESS) { oidc_serror(s, "failed to create memcache object of '%d' size", nservers); return HTTP_INTERNAL_SERVER_ERROR; } /* * When mod_http2 is loaded we might have more threads since it has * its own pool of processing threads. */ ap_mpm_query(AP_MPMQ_MAX_THREADS, &max_threads); get_h2_num_workers = APR_RETRIEVE_OPTIONAL_FN(http2_get_num_workers); if (get_h2_num_workers) { get_h2_num_workers(s, &minw, &maxw); /* So now the max is: * max_threads-1 threads for HTTP/1 each requiring one connection * + one thread for HTTP/2 requiring maxw connections */ max_threads = max_threads - 1 + maxw; } min = oidc_cfg_cache_memcache_min_get(cfg); smax = oidc_cfg_cache_memcache_smax_get(cfg); hmax = oidc_cfg_cache_memcache_hmax_get(cfg); ttl = oidc_cfg_cache_memcache_ttl_get(cfg); if (max_threads > 0 && hmax == 0) { hmax = max_threads; if (smax == 0) { smax = hmax; } // a default min value of 1 does not work at least on Mac OS X // so retain backwards compatibility for now with 0 // if (min == 0) { // min = hmax; //} } else { if (hmax == 0) { hmax = 1; } if (smax == 0) { smax = 1; } } if (ttl == 0) { ttl = apr_time_from_sec(60); } if (smax > hmax) { smax = hmax; } if (min > smax) { min = smax; } /* loop again over the provided servers */ cache_config = apr_pstrdup(p, oidc_cfg_cache_memcache_servers_get(cfg)); split = apr_strtok(cache_config, OIDC_STR_SPACE, &tok); while (split) { apr_memcache_server_t *st; char *host_str; char *scope_id; apr_port_t port; /* parse out host and port */ rv = apr_parse_addr_port(&host_str, &scope_id, &port, split, p); if (rv != APR_SUCCESS) { oidc_serror(s, "failed to parse cache server: '%s'", split); return HTTP_INTERNAL_SERVER_ERROR; } if (host_str == NULL) { oidc_serror(s, "failed to parse cache server, no hostname specified: '%s'", split); return HTTP_INTERNAL_SERVER_ERROR; } if (port == 0) port = 11211; oidc_sdebug(s, "creating server: %s:%d, min=%d, smax=%d, hmax=%d, ttl=%d", host_str, port, min, smax, hmax, ttl); /* create the memcache server struct */ rv = apr_memcache_server_create(p, host_str, port, min, smax, hmax, ttl, &st); if (rv != APR_SUCCESS) { oidc_serror(s, "failed to create cache server: %s:%d", host_str, port); return HTTP_INTERNAL_SERVER_ERROR; } /* add the memcache server struct to the list */ rv = apr_memcache_add_server(context->cache_memcache, st); if (rv != APR_SUCCESS) { oidc_serror(s, "failed to add cache server: %s:%d", host_str, port); return HTTP_INTERNAL_SERVER_ERROR; } /* go to the next entry */ split = apr_strtok(NULL, OIDC_STR_SPACE, &tok); } return OK; } #define OIDC_CACHE_MEMCACHE_STATUS_ERR_SIZE 64 /* * printout readable error messages about memcache failures */ static void oidc_cache_memcache_log_status_error(request_rec *r, const char *s, apr_status_t rv) { char s_err[OIDC_CACHE_MEMCACHE_STATUS_ERR_SIZE]; apr_strerror(rv, s_err, OIDC_CACHE_MEMCACHE_STATUS_ERR_SIZE); oidc_error(r, "%s returned an error: [%s]; check your that your memcache server is available/accessible.", s, s_err); } /* * assemble single key name based on section/key input */ static char *oidc_cache_memcache_get_key(apr_pool_t *pool, const char *section, const char *key) { return apr_psprintf(pool, "%s:%s", section, key); } /* * check dead/alive status for all servers */ static apr_byte_t oidc_cache_memcache_status(const oidc_cache_cfg_memcache_t *context) { for (int i = 0; i < context->cache_memcache->ntotal; i++) { if (context->cache_memcache->live_servers[i]->status != APR_MC_SERVER_DEAD) return TRUE; } return FALSE; } /* * get a name/value pair from memcache */ static apr_byte_t oidc_cache_memcache_get(request_rec *r, const char *section, const char *key, char **value) { oidc_cfg_t *cfg = ap_get_module_config(r->server->module_config, &auth_openidc_module); oidc_cache_cfg_memcache_t *context = (oidc_cache_cfg_memcache_t *)cfg->cache.cfg; apr_size_t len = 0; /* get it */ apr_status_t rv = apr_memcache_getp(context->cache_memcache, r->pool, oidc_cache_memcache_get_key(r->pool, section, key), value, &len, NULL); if (rv == APR_NOTFOUND) { /* * NB: workaround the fact that the apr_memcache returns APR_NOTFOUND if a server has been marked dead */ if (oidc_cache_memcache_status(context) == FALSE) { oidc_cache_memcache_log_status_error(r, "apr_memcache_getp", rv); return FALSE; } oidc_debug(r, "apr_memcache_getp: key %s not found in cache", oidc_cache_memcache_get_key(r->pool, section, key)); return TRUE; } else if (rv != APR_SUCCESS) { oidc_cache_memcache_log_status_error(r, "apr_memcache_getp", rv); return FALSE; } /* do sanity checking on the string value */ if ((*value) && (_oidc_strlen(*value) != len)) { oidc_error(r, "apr_memcache_getp returned less bytes than expected: _oidc_strlen(value) [%zu] != len " "[%" APR_SIZE_T_FMT "]", _oidc_strlen(*value), len); return FALSE; } return TRUE; } /* * store a name/value pair in memcache */ static apr_byte_t oidc_cache_memcache_set(request_rec *r, const char *section, const char *key, const char *value, apr_time_t expiry) { oidc_cfg_t *cfg = ap_get_module_config(r->server->module_config, &auth_openidc_module); oidc_cache_cfg_memcache_t *context = (oidc_cache_cfg_memcache_t *)cfg->cache.cfg; apr_status_t rv = APR_SUCCESS; /* see if we should be clearing this entry */ if (value == NULL) { rv = apr_memcache_delete(context->cache_memcache, oidc_cache_memcache_get_key(r->pool, section, key), 0); if (rv == APR_NOTFOUND) { oidc_debug(r, "apr_memcache_delete: key %s not found in cache", oidc_cache_memcache_get_key(r->pool, section, key)); rv = APR_SUCCESS; } else if (rv != APR_SUCCESS) { oidc_cache_memcache_log_status_error(r, "apr_memcache_delete", rv); } } else { /* calculate the timeout as a Unix timestamp which allows values > 30 days */ apr_uint32_t timeout = (apr_uint32_t)apr_time_sec(expiry); /* store it */ rv = apr_memcache_set(context->cache_memcache, oidc_cache_memcache_get_key(r->pool, section, key), (char *)value, _oidc_strlen(value), timeout, 0); if (rv != APR_SUCCESS) { oidc_cache_memcache_log_status_error(r, "apr_memcache_set", rv); } } return (rv == APR_SUCCESS); } // clang-format off oidc_cache_t oidc_cache_memcache = { "memcache", 1, oidc_cache_memcache_post_config, NULL, oidc_cache_memcache_get, oidc_cache_memcache_set, NULL }; // clang-format on mod_auth_openidc-2.4.16.10/src/cache/redis.c000066400000000000000000000426761476721736500204710ustar00rootroot00000000000000/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ /*************************************************************************** * Copyright (C) 2017-2025 ZmartZone Holding BV * Copyright (C) 2013-2017 Ping Identity Corporation * All rights reserved. * * DISCLAIMER OF WARRANTIES: * * THE SOFTWARE PROVIDED HEREUNDER IS PROVIDED ON AN "AS IS" BASIS, WITHOUT * ANY WARRANTIES OR REPRESENTATIONS EXPRESS, IMPLIED OR STATUTORY; INCLUDING, * WITHOUT LIMITATION, WARRANTIES OF QUALITY, PERFORMANCE, NONINFRINGEMENT, * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. NOR ARE THERE ANY * WARRANTIES CREATED BY A COURSE OR DEALING, COURSE OF PERFORMANCE OR TRADE * USAGE. FURTHERMORE, THERE ARE NO WARRANTIES THAT THE SOFTWARE WILL MEET * YOUR NEEDS OR BE FREE FROM ERRORS, OR THAT THE OPERATION OF THE SOFTWARE * WILL BE UNINTERRUPTED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * caching using a Redis backend * * @Author: Hans Zandbelt - hans.zandbelt@openidc.com */ #include "cache/redis.h" #include "cfg/cfg_int.h" // TODO: proper Redis error reporting (server unreachable etc.) #define REDIS_CONNECT_TIMEOUT_DEFAULT 5 #define REDIS_TIMEOUT_DEFAULT 5 #define REDIS_KEEPALIVE_DEFAULT -1 /* create the cache context */ static oidc_cache_cfg_redis_t *oidc_cache_redis_cfg_create(apr_pool_t *pool) { oidc_cache_cfg_redis_t *context = apr_pcalloc(pool, sizeof(oidc_cache_cfg_redis_t)); context->mutex = oidc_cache_mutex_create(pool, FALSE); context->username = NULL; context->passwd = NULL; context->database = -1; context->connect_timeout.tv_sec = REDIS_CONNECT_TIMEOUT_DEFAULT; context->connect_timeout.tv_usec = 0; context->keepalive = REDIS_KEEPALIVE_DEFAULT; context->timeout.tv_sec = REDIS_TIMEOUT_DEFAULT; context->timeout.tv_usec = 0; context->host_str = NULL; context->port = 0; context->rctx = NULL; return context; } int oidc_cache_redis_post_config(server_rec *s, oidc_cfg_t *cfg, const char *name) { oidc_cache_cfg_redis_t *context = oidc_cache_redis_cfg_create(s->process->pool); cfg->cache.cfg = context; /* parse the host:post tuple from the configuration */ if (cfg->cache.redis_server == NULL) { oidc_serror(s, "cache type is set to \"redis\", but no valid " OIDCRedisCacheServer " setting was found"); return HTTP_INTERNAL_SERVER_ERROR; } if (cfg->cache.redis_username != NULL) { context->username = apr_pstrdup(s->process->pool, cfg->cache.redis_username); } if (cfg->cache.redis_password != NULL) { context->passwd = apr_pstrdup(s->process->pool, cfg->cache.redis_password); } if (oidc_cfg_cache_redis_database_get(cfg) != OIDC_CONFIG_POS_INT_UNSET) context->database = oidc_cfg_cache_redis_database_get(cfg); if (oidc_cfg_cache_redis_connect_timeout_get(cfg) != OIDC_CONFIG_POS_INT_UNSET) context->connect_timeout.tv_sec = oidc_cfg_cache_redis_connect_timeout_get(cfg); if (oidc_cfg_cache_redis_keepalive_get(cfg) != OIDC_CONFIG_POS_INT_UNSET) context->keepalive = oidc_cfg_cache_redis_keepalive_get(cfg); if (oidc_cfg_cache_redis_timeout_get(cfg) != OIDC_CONFIG_POS_INT_UNSET) context->timeout.tv_sec = oidc_cfg_cache_redis_timeout_get(cfg); if (oidc_cache_mutex_post_config(s, context->mutex, name) == FALSE) return HTTP_INTERNAL_SERVER_ERROR; return OK; } static apr_status_t oidc_cache_redis_connect(request_rec *r, oidc_cache_cfg_redis_t *context); /* * free resources allocated for the per-process Redis connection context */ apr_status_t oidc_cache_redis_disconnect(oidc_cache_cfg_redis_t *context) { if ((context != NULL) && (context->rctx != NULL)) { redisFree(context->rctx); context->rctx = NULL; } return APR_SUCCESS; } /* * initialize the Redis struct the specified Redis server */ static int oidc_cache_redis_post_config_impl(server_rec *s) { apr_status_t rv = APR_SUCCESS; oidc_cache_cfg_redis_t *context = NULL; oidc_cfg_t *cfg = (oidc_cfg_t *)ap_get_module_config(s->module_config, &auth_openidc_module); if (cfg->cache.cfg != NULL) return OK; if (oidc_cache_redis_post_config(s, cfg, "redis") != OK) return HTTP_INTERNAL_SERVER_ERROR; context = (oidc_cache_cfg_redis_t *)cfg->cache.cfg; /* parse the host:post tuple from the configuration */ if (cfg->cache.redis_server == NULL) { oidc_serror(s, "cache type is set to \"redis\", but no valid " OIDCRedisCacheServer " setting was found"); return HTTP_INTERNAL_SERVER_ERROR; } char *scope_id; rv = apr_parse_addr_port(&context->host_str, &scope_id, &context->port, cfg->cache.redis_server, s->process->pool); if (rv != APR_SUCCESS) { oidc_serror(s, "failed to parse cache server: '%s'", cfg->cache.redis_server); return HTTP_INTERNAL_SERVER_ERROR; } if (context->host_str == NULL) { oidc_serror(s, "failed to parse cache server, no hostname specified: '%s'", cfg->cache.redis_server); return HTTP_INTERNAL_SERVER_ERROR; } if (context->port == 0) context->port = 6379; context->connect = oidc_cache_redis_connect; context->command = oidc_cache_redis_command; context->disconnect = oidc_cache_redis_disconnect; return OK; } /* * initialize the Redis cache in a child process */ int oidc_cache_redis_child_init(apr_pool_t *p, server_rec *s) { oidc_cfg_t *cfg = ap_get_module_config(s->module_config, &auth_openidc_module); oidc_cache_cfg_redis_t *context = (oidc_cache_cfg_redis_t *)cfg->cache.cfg; oidc_slog(s, APLOG_TRACE1, "init: %pp (t=%s, m=%pp, s=%pp, g=%d)", context, cfg->cache.impl->name, context ? context->mutex : 0, s, (context && context->mutex) ? context->mutex->is_global : -1); /* initialize the lock for the child process */ return oidc_cache_mutex_child_init(p, s, context->mutex); } /* * assemble single key name based on section/key input */ static char *oidc_cache_redis_get_key(apr_pool_t *pool, const char *section, const char *key) { return apr_psprintf(pool, "%s:%s", section, key); } /* * free and nullify a reply object */ static void oidc_cache_redis_reply_free(redisReply **reply) { if (*reply != NULL) { freeReplyObject(*reply); *reply = NULL; } } apr_byte_t oidc_cache_redis_set_keepalive(request_rec *r, redisContext *rctx, const int keepalive) { apr_byte_t rv = TRUE; // default is -1 if (keepalive == 0) { oidc_debug(r, "not setting redisEnableKeepAlive"); goto end; } #if HIREDIS_MAJOR >= 1 && HIREDIS_MINOR >= 2 if (keepalive == -1) { oidc_debug(r, "setting redisEnableKeepAlive to the default interval"); if (redisEnableKeepAlive(rctx) != REDIS_OK) { oidc_error(r, "redisEnableKeepAlive failed: %s", rctx->errstr); rv = FALSE; } goto end; } oidc_debug(r, "setting redisEnableKeepAliveWithInterval: %d", keepalive); if (redisEnableKeepAliveWithInterval(rctx, keepalive) != REDIS_OK) { oidc_error(r, "redisEnableKeepAliveWithInterval failed: %s", rctx->errstr); rv = FALSE; } #else // -1 or > 0 oidc_debug(r, "setting redisEnableKeepAlive to the default interval"); if (redisEnableKeepAlive(rctx) != REDIS_OK) { oidc_error(r, "redisEnableKeepAlive failed: %s", rctx->errstr); rv = FALSE; } #endif end: return rv; } apr_byte_t oidc_cache_redis_set_auth(request_rec *r, redisContext *rctx, const char *username, const char *password) { apr_byte_t rv = TRUE; redisReply *reply = NULL; if (password == NULL) goto end; if (username != NULL) reply = redisCommand(rctx, "AUTH %s %s", username, password); else reply = redisCommand(rctx, "AUTH %s", password); if ((reply == NULL) || (reply->type == REDIS_REPLY_ERROR)) { oidc_error(r, "Redis AUTH command failed: '%s' [%s]", rctx->errstr, reply ? reply->str : ""); rv = FALSE; goto end; } oidc_debug(r, "successfully authenticated to the Redis server: %s", reply ? reply->str : ""); end: oidc_cache_redis_reply_free(&reply); return rv; } apr_byte_t oidc_cache_redis_set_database(request_rec *r, redisContext *rctx, const int database) { apr_byte_t rv = TRUE; redisReply *reply = NULL; if (database == -1) goto end; reply = redisCommand(rctx, "SELECT %d", database); if ((reply == NULL) || (reply->type == REDIS_REPLY_ERROR)) { oidc_error(r, "Redis SELECT command failed: '%s' [%s]", rctx->errstr, reply ? reply->str : ""); rv = FALSE; goto end; } oidc_debug(r, "successfully selected database %d on the Redis server: %s", database, reply ? reply->str : ""); end: oidc_cache_redis_reply_free(&reply); return rv; } redisContext *oidc_cache_redis_connect_with_timeout(request_rec *r, const char *host, int port, struct timeval ct, struct timeval t, const char *msg) { redisContext *rctx = NULL; oidc_debug(r, "calling redisConnectWithTimeout: %d", (int)ct.tv_sec); rctx = redisConnectWithTimeout(host, port, ct); if ((rctx == NULL) || (rctx->err != 0)) { oidc_error(r, "failed to connect to Redis server (%s%s%s:%d): '%s'", msg ? msg : "", msg ? ":" : "", host, port, rctx != NULL ? rctx->errstr : ""); if (rctx) redisFree(rctx); return NULL; } oidc_debug(r, "successfully connected to Redis server (%s%s%s:%d)", msg ? msg : "", msg ? ":" : "", host, port); if (redisSetTimeout(rctx, t) != REDIS_OK) oidc_error(r, "redisSetTimeout failed: %s", rctx->errstr); return rctx; } /* * connect to Redis server */ static apr_status_t oidc_cache_redis_connect(request_rec *r, oidc_cache_cfg_redis_t *context) { apr_status_t rv = APR_EGENERAL; if (context->rctx != NULL) return APR_SUCCESS; context->rctx = oidc_cache_redis_connect_with_timeout(r, context->host_str, context->port, context->connect_timeout, context->timeout, NULL); if (context->rctx == NULL) goto end; if (oidc_cache_redis_set_keepalive(r, context->rctx, context->keepalive) == FALSE) goto end; if (oidc_cache_redis_set_auth(r, context->rctx, context->username, context->passwd) == FALSE) goto end; if (oidc_cache_redis_set_database(r, context->rctx, context->database) == FALSE) goto end; rv = APR_SUCCESS; end: if (rv != APR_SUCCESS) context->disconnect(context); return rv; } redisReply *oidc_cache_redis_command(request_rec *r, oidc_cache_cfg_redis_t *context, char **errstr, const char *format, va_list ap) { redisReply *reply = redisvCommand(context->rctx, format, ap); *errstr = apr_pstrdup(r->pool, context->rctx->errstr); return reply; } static int oidc_cache_redis_env2int(request_rec *r, const char *env_var_name, const int default_value) { const char *s = r->subprocess_env ? apr_table_get(r->subprocess_env, env_var_name) : NULL; return _oidc_str_to_int(s, default_value); } #define OIDC_REDIS_MAX_TRIES_ENV_VAR "OIDC_REDIS_MAX_TRIES" #define OIDC_REDIS_MAX_TRIES_DEFAULT 2 #define OIDC_REDIS_RETRY_INTERVAL_ENV_VAR "OIDC_REDIS_RETRY_INTERVAL" #define OIDC_REDIS_RETRY_INTERVAL_DEFAULT 300 #define OIDC_REDIS_WARN_OR_ERROR(cond, r, ...) \ if (cond) { \ oidc_warn(r, ##__VA_ARGS__); \ } else { \ oidc_error(r, ##__VA_ARGS__); \ } /* * execute Redis command and deal with return value */ static redisReply *oidc_cache_redis_exec(request_rec *r, oidc_cache_cfg_redis_t *context, const char *format, ...) { redisReply *reply = NULL; char *errstr = NULL; va_list ap; int retries = oidc_cache_redis_env2int(r, OIDC_REDIS_MAX_TRIES_ENV_VAR, OIDC_REDIS_MAX_TRIES_DEFAULT); apr_time_t interval = apr_time_from_msec( oidc_cache_redis_env2int(r, OIDC_REDIS_RETRY_INTERVAL_ENV_VAR, OIDC_REDIS_RETRY_INTERVAL_DEFAULT)); /* try to execute a command at max n times while reconnecting */ for (int i = 1; i <= retries; i++) { /* connect */ if (context->connect(r, context) != APR_SUCCESS) { OIDC_REDIS_WARN_OR_ERROR(i < retries, r, "Redis connect (attempt=%d/%d to %s:%d) failed", i, retries, context->host_str, context->port); if (i < retries) { oidc_debug(r, "wait before retrying: %" APR_TIME_T_FMT " (msec)", apr_time_as_msec(interval)); apr_sleep(interval); } continue; } va_start(ap, format); /* execute the actual command */ reply = context->command(r, context, &errstr, format, ap); va_end(ap); /* check for errors, need to return error replies for cache miss case REDIS_REPLY_NIL */ if ((reply != NULL) && (reply->type != REDIS_REPLY_ERROR)) /* break the loop and return the reply */ break; /* something went wrong, log it */ OIDC_REDIS_WARN_OR_ERROR( i < retries, r, "Redis command (attempt=%d/%d to %s:%d) failed, disconnecting: '%s' [%s]", i, retries, context->host_str, context->port, errstr, reply ? reply->str : ""); /* free the reply (if there is one allocated) */ oidc_cache_redis_reply_free(&reply); /* cleanup, we may try again (once) after reconnecting */ context->disconnect(context); } return reply; } /* * get a name/value pair from Redis */ apr_byte_t oidc_cache_redis_get(request_rec *r, const char *section, const char *key, char **value) { oidc_cfg_t *cfg = ap_get_module_config(r->server->module_config, &auth_openidc_module); oidc_cache_cfg_redis_t *context = (oidc_cache_cfg_redis_t *)cfg->cache.cfg; redisReply *reply = NULL; apr_byte_t rv = FALSE; /* grab the processlock */ if (oidc_cache_mutex_lock(r->pool, r->server, context->mutex) == FALSE) return FALSE; /* get */ reply = oidc_cache_redis_exec(r, context, "GET %s", oidc_cache_redis_get_key(r->pool, section, key)); if (reply == NULL) goto end; /* check that we got a string back */ if (reply->type == REDIS_REPLY_NIL) { /* this is a normal cache miss, so we'll return OK */ rv = TRUE; goto end; } if (reply->type != REDIS_REPLY_STRING) { oidc_error(r, "redisCommand reply type is not string: %d", reply->type); goto end; } /* do a sanity check on the returned value */ if ((reply->str == NULL) || (reply->len != _oidc_strlen(reply->str))) { oidc_error(r, "redisCommand reply->len (%d) != _oidc_strlen(reply->str): '%s'", (int)reply->len, reply->str); goto end; } /* copy it in to the request memory pool */ *value = apr_pstrdup(r->pool, reply->str); /* return success */ rv = TRUE; end: /* free the reply object resources */ oidc_cache_redis_reply_free(&reply); /* unlock the process mutex */ oidc_cache_mutex_unlock(r->pool, r->server, context->mutex); /* return the status */ return rv; } /* * store a name/value pair in Redis */ apr_byte_t oidc_cache_redis_set(request_rec *r, const char *section, const char *key, const char *value, apr_time_t expiry) { oidc_cfg_t *cfg = ap_get_module_config(r->server->module_config, &auth_openidc_module); oidc_cache_cfg_redis_t *context = (oidc_cache_cfg_redis_t *)cfg->cache.cfg; redisReply *reply = NULL; apr_byte_t rv = FALSE; apr_uint32_t timeout; /* grab the process lock */ if (oidc_cache_mutex_lock(r->pool, r->server, context->mutex) == FALSE) return FALSE; /* see if we should be clearing this entry */ if (value == NULL) { /* delete it */ reply = oidc_cache_redis_exec(r, context, "DEL %s", oidc_cache_redis_get_key(r->pool, section, key)); } else { /* calculate the timeout from now */ timeout = (apr_uint32_t)apr_time_sec(expiry - apr_time_now()); /* store it */ reply = oidc_cache_redis_exec(r, context, "SETEX %s %d %s", oidc_cache_redis_get_key(r->pool, section, key), timeout, value); } rv = (reply != NULL) && (reply->type != REDIS_REPLY_ERROR); /* free the reply object resources */ oidc_cache_redis_reply_free(&reply); /* unlock the process mutex */ oidc_cache_mutex_unlock(r->pool, r->server, context->mutex); /* return the status */ return rv; } static int oidc_cache_redis_destroy_impl(server_rec *s) { oidc_cfg_t *cfg = (oidc_cfg_t *)ap_get_module_config(s->module_config, &auth_openidc_module); oidc_cache_cfg_redis_t *context = (oidc_cache_cfg_redis_t *)cfg->cache.cfg; if (context != NULL) { oidc_cache_mutex_lock(s->process->pool, s, context->mutex); context->disconnect(context); oidc_cache_mutex_unlock(s->process->pool, s, context->mutex); if (oidc_cache_mutex_destroy(s, context->mutex) != TRUE) { oidc_serror(s, "oidc_cache_mutex_destroy on refresh mutex failed"); } cfg->cache.cfg = NULL; } return APR_SUCCESS; } // clang-format off oidc_cache_t oidc_cache_redis = { "redis", 1, oidc_cache_redis_post_config_impl, oidc_cache_redis_child_init, oidc_cache_redis_get, oidc_cache_redis_set, oidc_cache_redis_destroy_impl }; // clang-format on mod_auth_openidc-2.4.16.10/src/cache/redis.h000066400000000000000000000100701476721736500204550ustar00rootroot00000000000000/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ /*************************************************************************** * Copyright (C) 2017-2025 ZmartZone Holding BV * Copyright (C) 2013-2017 Ping Identity Corporation * All rights reserved. * * DISCLAIMER OF WARRANTIES: * * THE SOFTWARE PROVIDED HEREUNDER IS PROVIDED ON AN "AS IS" BASIS, WITHOUT * ANY WARRANTIES OR REPRESENTATIONS EXPRESS, IMPLIED OR STATUTORY; INCLUDING, * WITHOUT LIMITATION, WARRANTIES OF QUALITY, PERFORMANCE, NONINFRINGEMENT, * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. NOR ARE THERE ANY * WARRANTIES CREATED BY A COURSE OR DEALING, COURSE OF PERFORMANCE OR TRADE * USAGE. FURTHERMORE, THERE ARE NO WARRANTIES THAT THE SOFTWARE WILL MEET * YOUR NEEDS OR BE FREE FROM ERRORS, OR THAT THE OPERATION OF THE SOFTWARE * WILL BE UNINTERRUPTED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * caching using a Redis backend * * @Author: Hans Zandbelt - hans.zandbelt@openidc.com */ #ifndef _MOD_AUTH_OPENIDC_REDIS_H_ #define _MOD_AUTH_OPENIDC_REDIS_H_ #include "cfg/cache.h" #include "hiredis/hiredis.h" struct oidc_cache_cfg_redis_t; typedef apr_status_t (*oidc_cache_redis_connect_function_t)(request_rec *, struct oidc_cache_cfg_redis_t *); typedef redisReply *(*oidc_cache_redis_command_function_t)(request_rec *, struct oidc_cache_cfg_redis_t *, char **, const char *format, va_list ap); typedef apr_status_t (*oidc_cache_redis_disconnect_function_t)(struct oidc_cache_cfg_redis_t *); typedef struct oidc_cache_cfg_redis_t { oidc_cache_mutex_t *mutex; char *username; char *passwd; int database; struct timeval connect_timeout; int keepalive; struct timeval timeout; char *host_str; apr_port_t port; redisContext *rctx; oidc_cache_redis_connect_function_t connect; oidc_cache_redis_command_function_t command; oidc_cache_redis_disconnect_function_t disconnect; } oidc_cache_cfg_redis_t; int oidc_cache_redis_post_config(server_rec *s, oidc_cfg_t *cfg, const char *name); int oidc_cache_redis_child_init(apr_pool_t *p, server_rec *s); redisReply *oidc_cache_redis_command(request_rec *r, oidc_cache_cfg_redis_t *context, char **errstr, const char *format, va_list ap); apr_byte_t oidc_cache_redis_get(request_rec *r, const char *section, const char *key, char **value); apr_byte_t oidc_cache_redis_set(request_rec *r, const char *section, const char *key, const char *value, apr_time_t expiry); apr_status_t oidc_cache_redis_disconnect(oidc_cache_cfg_redis_t *context); apr_byte_t oidc_cache_redis_set_keepalive(request_rec *r, redisContext *rctx, const int keepalive); apr_byte_t oidc_cache_redis_set_auth(request_rec *r, redisContext *rctx, const char *username, const char *password); apr_byte_t oidc_cache_redis_set_database(request_rec *r, redisContext *rctx, const int database); redisContext *oidc_cache_redis_connect_with_timeout(request_rec *r, const char *host, int port, struct timeval ct, struct timeval t, const char *msg); #endif // _MOD_AUTH_OPENIDC_REDIS_H_ mod_auth_openidc-2.4.16.10/src/cache/shm.c000066400000000000000000000265241476721736500201440ustar00rootroot00000000000000/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ /*************************************************************************** * Copyright (C) 2017-2025 ZmartZone Holding BV * Copyright (C) 2013-2017 Ping Identity Corporation * All rights reserved. * * DISCLAIMER OF WARRANTIES: * * THE SOFTWARE PROVIDED HEREUNDER IS PROVIDED ON AN "AS IS" BASIS, WITHOUT * ANY WARRANTIES OR REPRESENTATIONS EXPRESS, IMPLIED OR STATUTORY; INCLUDING, * WITHOUT LIMITATION, WARRANTIES OF QUALITY, PERFORMANCE, NONINFRINGEMENT, * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. NOR ARE THERE ANY * WARRANTIES CREATED BY A COURSE OR DEALING, COURSE OF PERFORMANCE OR TRADE * USAGE. FURTHERMORE, THERE ARE NO WARRANTIES THAT THE SOFTWARE WILL MEET * YOUR NEEDS OR BE FREE FROM ERRORS, OR THAT THE OPERATION OF THE SOFTWARE * WILL BE UNINTERRUPTED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * caching using a shared memory backend, FIFO-style * based on mod_auth_mellon code * * @Author: Hans Zandbelt - hans.zandbelt@openidc.com */ #include "cache/cache.h" #include "cfg/cache.h" #include "cfg/cfg_int.h" #include typedef struct oidc_cache_cfg_shm_t { apr_shm_t *shm; oidc_cache_mutex_t *mutex; apr_byte_t is_parent; } oidc_cache_cfg_shm_t; /* size of key in cached key/value pairs */ #define OIDC_CACHE_SHM_KEY_MAX OIDC_CACHE_KEY_SIZE_MAX /* represents one (fixed size) cache entry, cq. name/value string pair */ typedef __attribute__((aligned(64))) struct oidc_cache_shm_entry_t { /* name of the cache entry */ char section_key[OIDC_CACHE_SHM_KEY_MAX]; /* last (read) access timestamp */ apr_time_t access; /* expiry timestamp */ apr_time_t expires; /* value of the cache entry */ char value[]; } oidc_cache_shm_entry_t; /* create the cache context */ static void *oidc_cache_shm_cfg_create(apr_pool_t *pool) { oidc_cache_cfg_shm_t *context = apr_pcalloc(pool, sizeof(oidc_cache_cfg_shm_t)); context->shm = NULL; context->mutex = oidc_cache_mutex_create(pool, TRUE); context->is_parent = TRUE; return context; } #define OIDC_CACHE_SHM_ADD_OFFSET(t, size) t = (oidc_cache_shm_entry_t *)((uint8_t *)t + size) /* * initialized the shared memory block in the parent process */ int oidc_cache_shm_post_config(server_rec *s) { oidc_cfg_t *cfg = (oidc_cfg_t *)ap_get_module_config(s->module_config, &auth_openidc_module); if (cfg->cache.cfg != NULL) return OK; oidc_cache_cfg_shm_t *context = oidc_cache_shm_cfg_create(s->process->pconf); cfg->cache.cfg = context; /* create the shared memory segment */ apr_status_t rv = apr_shm_create(&context->shm, (apr_size_t)cfg->cache.shm_entry_size_max * cfg->cache.shm_size_max, NULL, s->process->pconf); if (rv != APR_SUCCESS) { oidc_serror(s, "apr_shm_create failed to create shared memory segment"); return HTTP_INTERNAL_SERVER_ERROR; } /* initialize the whole segment to '/0' */ oidc_cache_shm_entry_t *t = apr_shm_baseaddr_get(context->shm); for (int i = 0; i < cfg->cache.shm_size_max; i++, OIDC_CACHE_SHM_ADD_OFFSET(t, cfg->cache.shm_entry_size_max)) { t->section_key[0] = '\0'; t->access = 0; } if (oidc_cache_mutex_post_config(s, context->mutex, "shm") == FALSE) return HTTP_INTERNAL_SERVER_ERROR; oidc_sdebug( s, "initialized shared memory with a cache size (# entries) of: %d, and a max (single) entry size of: %d", cfg->cache.shm_size_max, cfg->cache.shm_entry_size_max); oidc_slog(s, APLOG_TRACE1, "create: %pp (shm=%pp,s=%pp, p=%d)", context, context ? context->shm : 0, s, context ? context->is_parent : -1); return OK; } /* * initialize the shared memory segment in a child process */ int oidc_cache_shm_child_init(apr_pool_t *p, server_rec *s) { oidc_cfg_t *cfg = ap_get_module_config(s->module_config, &auth_openidc_module); oidc_cache_cfg_shm_t *context = (oidc_cache_cfg_shm_t *)cfg->cache.cfg; oidc_slog(s, APLOG_TRACE1, "init: %pp (shm=%pp,s=%pp, p=%d)", context, context ? context->shm : 0, s, context ? context->is_parent : -1); if (context->is_parent == FALSE) return APR_SUCCESS; context->is_parent = FALSE; /* initialize the lock for the child process */ return oidc_cache_mutex_child_init(p, s, context->mutex); } /* * assemble single key name based on section/key input */ static char *oidc_cache_shm_get_key(request_rec *r, const char *section, const char *key) { char *section_key = apr_psprintf(r->pool, "%s:%s", section, key); /* check that the passed in key is valid */ if (_oidc_strlen(section_key) >= OIDC_CACHE_SHM_KEY_MAX) { oidc_error(r, "could not construct cache key since key size is too large (%d >= %d) (%s)", (int)_oidc_strlen(section_key), OIDC_CACHE_SHM_KEY_MAX, section_key); return NULL; } return section_key; } /* * get a value from the shared memory cache */ static apr_byte_t oidc_cache_shm_get(request_rec *r, const char *section, const char *key, char **value) { oidc_cfg_t *cfg = ap_get_module_config(r->server->module_config, &auth_openidc_module); oidc_cache_cfg_shm_t *context = (oidc_cache_cfg_shm_t *)cfg->cache.cfg; int i; const char *section_key = oidc_cache_shm_get_key(r, section, key); if (section_key == NULL) return FALSE; *value = NULL; /* grab the global lock */ if (oidc_cache_mutex_lock(r->pool, r->server, context->mutex) == FALSE) return FALSE; /* get the pointer to the start of the shared memory block */ oidc_cache_shm_entry_t *t = apr_shm_baseaddr_get(context->shm); /* loop over the block, looking for the key */ for (i = 0; i < cfg->cache.shm_size_max; i++, OIDC_CACHE_SHM_ADD_OFFSET(t, cfg->cache.shm_entry_size_max)) { const char *tablekey = t->section_key; if ((tablekey != NULL) && (_oidc_strcmp(tablekey, section_key) == 0)) { /* found a match, check if it has expired */ if (t->expires > apr_time_now()) { /* update access timestamp */ t->access = apr_time_now(); *value = t->value; } else { /* clear the expired entry */ t->section_key[0] = '\0'; t->access = 0; } /* we safely can break now since we would not have found an expired match twice */ break; } } /* release the global lock */ oidc_cache_mutex_unlock(r->pool, r->server, context->mutex); return TRUE; } /* * store a value in the shared memory cache */ static apr_byte_t oidc_cache_shm_set(request_rec *r, const char *section, const char *key, const char *value, apr_time_t expiry) { oidc_cfg_t *cfg = ap_get_module_config(r->server->module_config, &auth_openidc_module); oidc_cache_cfg_shm_t *context = (oidc_cache_cfg_shm_t *)cfg->cache.cfg; oidc_cache_shm_entry_t *match = NULL; oidc_cache_shm_entry_t *free = NULL; oidc_cache_shm_entry_t *lru = NULL; oidc_cache_shm_entry_t *t = NULL; apr_time_t current_time = 0; apr_time_t age = 0; const char *section_key = oidc_cache_shm_get_key(r, section, key); if (section_key == NULL) return FALSE; /* check that the passed in value is valid */ if ((value != NULL) && (_oidc_strlen(value) > (cfg->cache.shm_entry_size_max - sizeof(oidc_cache_shm_entry_t)))) { oidc_error(r, "could not store value since value size is too large (%lu > %lu); consider " "increasing " OIDCCacheShmEntrySizeMax "", (unsigned long)_oidc_strlen(value), (unsigned long)(cfg->cache.shm_entry_size_max - sizeof(oidc_cache_shm_entry_t))); return FALSE; } /* grab the global lock */ if (oidc_cache_mutex_lock(r->pool, r->server, context->mutex) == FALSE) return FALSE; /* get a pointer to the shared memory block */ t = apr_shm_baseaddr_get(context->shm); /* get the current time */ current_time = apr_time_now(); /* loop over the block, looking for the key */ match = NULL; free = NULL; lru = t; for (int i = 0; i < cfg->cache.shm_size_max; i++, OIDC_CACHE_SHM_ADD_OFFSET(t, cfg->cache.shm_entry_size_max)) { /* see if this slot is free */ if (t->section_key[0] == '\0') { if (free == NULL) free = t; continue; } /* see if a value already exists for this key */ if (_oidc_strcmp(t->section_key, section_key) == 0) { match = t; break; } /* see if this slot has expired */ if (t->expires <= current_time) { if (free == NULL) free = t; continue; } /* see if this slot was less recently used than the current pointer */ if (t->access < lru->access) { lru = t; } } /* if we have no free slots, issue a warning about the LRU entry */ if (match == NULL && free == NULL) { age = (current_time - lru->access) / 1000000; if (age < 3600) { oidc_warn(r, "dropping LRU entry with age = %" APR_TIME_T_FMT "s, which is less than one hour; consider increasing the shared memory caching space " "(which is %d now) with the (global) " OIDCCacheShmMax " setting.", age, cfg->cache.shm_size_max); } } /* pick the best slot: choose one with a matching key over a free slot, over a least-recently-used one */ t = match ? match : (free ? free : lru); /* see if we need to clear or set the value */ if (value != NULL) { /* fill out the entry with the provided data */ _oidc_strncpy(t->section_key, section_key, OIDC_CACHE_SHM_KEY_MAX); _oidc_strcpy(t->value, value); t->expires = expiry; t->access = current_time; } else { t->section_key[0] = '\0'; t->access = 0; } /* release the global lock */ oidc_cache_mutex_unlock(r->pool, r->server, context->mutex); return TRUE; } static int oidc_cache_shm_destroy(server_rec *s) { oidc_cfg_t *cfg = (oidc_cfg_t *)ap_get_module_config(s->module_config, &auth_openidc_module); oidc_cache_cfg_shm_t *context = (oidc_cache_cfg_shm_t *)cfg->cache.cfg; apr_status_t rv = APR_SUCCESS; oidc_slog(s, APLOG_TRACE1, "destroy: %pp (shm=%pp,s=%pp, p=%d)", context, context ? context->shm : 0, s, context ? context->is_parent : -1); if (context && (context->is_parent == TRUE) && (context->shm) && (context->mutex)) { oidc_cache_mutex_lock(s->process->pool, s, context->mutex); rv = apr_shm_destroy(context->shm); oidc_sdebug(s, "apr_shm_destroy returned: %d", rv); context->shm = NULL; oidc_cache_mutex_unlock(s->process->pool, s, context->mutex); } if (context && (context->mutex)) { if (oidc_cache_mutex_destroy(s, context->mutex) != TRUE) rv = APR_EGENERAL; context->mutex = NULL; } return rv; } // clang-format off oidc_cache_t oidc_cache_shm = { "shm", 0, oidc_cache_shm_post_config, oidc_cache_shm_child_init, oidc_cache_shm_get, oidc_cache_shm_set, oidc_cache_shm_destroy }; // clang-format on mod_auth_openidc-2.4.16.10/src/cfg/000077500000000000000000000000001476721736500166745ustar00rootroot00000000000000mod_auth_openidc-2.4.16.10/src/cfg/.gitignore000066400000000000000000000000601476721736500206600ustar00rootroot00000000000000/cmds.inc /.deps/ /.dirstamp /.libs/ /*.lo /*.o mod_auth_openidc-2.4.16.10/src/cfg/cache.c000066400000000000000000000417761476721736500201220ustar00rootroot00000000000000/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ /*************************************************************************** * Copyright (C) 2017-2025 ZmartZone Holding BV * All rights reserved. * * DISCLAIMER OF WARRANTIES: * * THE SOFTWARE PROVIDED HEREUNDER IS PROVIDED ON AN "AS IS" BASIS, WITHOUT * ANY WARRANTIES OR REPRESENTATIONS EXPRESS, IMPLIED OR STATUTORY; INCLUDING, * WITHOUT LIMITATION, WARRANTIES OF QUALITY, PERFORMANCE, NONINFRINGEMENT, * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. NOR ARE THERE ANY * WARRANTIES CREATED BY A COURSE OR DEALING, COURSE OF PERFORMANCE OR TRADE * USAGE. FURTHERMORE, THERE ARE NO WARRANTIES THAT THE SOFTWARE WILL MEET * YOUR NEEDS OR BE FREE FROM ERRORS, OR THAT THE OPERATION OF THE SOFTWARE * WILL BE UNINTERRUPTED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * @Author: Hans Zandbelt - hans.zandbelt@openidc.com */ #include "cfg/cache.h" #include "cfg/cfg_int.h" #include "cfg/parse.h" /* * set the cache type */ const char *oidc_cmd_cache_type_set(cmd_parms *cmd, void *ptr, const char *arg) { oidc_cfg_t *cfg = (oidc_cfg_t *)ap_get_module_config(cmd->server->module_config, &auth_openidc_module); static const char *options[] = {"shm", "file", #ifdef USE_MEMCACHE "memcache", #endif #ifdef USE_LIBHIREDIS "redis", #endif NULL}; const char *rv = oidc_cfg_parse_is_valid_option(cmd->pool, arg, options); if (rv == NULL) { if (_oidc_strcmp(arg, oidc_cache_shm.name) == 0) { cfg->cache.impl = &oidc_cache_shm; } else if (_oidc_strcmp(arg, oidc_cache_file.name) == 0) { cfg->cache.impl = &oidc_cache_file; #ifdef USE_MEMCACHE } else if (_oidc_strcmp(arg, oidc_cache_memcache.name) == 0) { cfg->cache.impl = &oidc_cache_memcache; #endif #ifdef USE_LIBHIREDIS } else if (_oidc_strcmp(arg, oidc_cache_redis.name) == 0) { cfg->cache.impl = &oidc_cache_redis; #endif } else { rv = apr_psprintf(cmd->pool, "unsupported cache type value: %s", arg); } } return OIDC_CONFIG_DIR_RV(cmd, rv); } #define OIDC_CFG_MEMBER_FUNC_CACHE_TYPE_GET(member, type, def_val) \ type oidc_cfg_cache_##member##_get(oidc_cfg_t *cfg) { \ if (cfg->cache.member == OIDC_CONFIG_POS_INT_UNSET) \ return def_val; \ return cfg->cache.member; \ } #define OIDC_CFG_MEMBER_FUNC_CACHE_SET(member, valid) \ const char *oidc_cmd_cache_##member##_set(cmd_parms *cmd, void *ptr, const char *arg) { \ oidc_cfg_t *cfg = \ (oidc_cfg_t *)ap_get_module_config(cmd->server->module_config, &auth_openidc_module); \ const char *rv = valid; \ if (rv == NULL) \ cfg->cache.member = apr_pstrdup(cmd->pool, arg); \ return OIDC_CONFIG_DIR_RV(cmd, rv); \ } #define OIDC_CFG_MEMBER_FUNCS_CACHE_INT_EXT(member, parse, def_val) \ const char *oidc_cmd_cache_##member##_set(cmd_parms *cmd, void *ptr, const char *arg) { \ oidc_cfg_t *cfg = \ (oidc_cfg_t *)ap_get_module_config(cmd->server->module_config, &auth_openidc_module); \ int v = -1; \ const char *rv = parse; \ if (rv == NULL) \ cfg->cache.member = v; \ return OIDC_CONFIG_DIR_RV(cmd, rv); \ } \ \ OIDC_CFG_MEMBER_FUNC_CACHE_TYPE_GET(member, int, def_val) #define OIDC_CFG_MEMBER_FUNCS_CACHE_INT(member, min, max, def_val) \ OIDC_CFG_MEMBER_FUNCS_CACHE_INT_EXT(member, oidc_cfg_parse_int_min_max(cmd->pool, arg, &v, min, max), def_val) #define OIDC_CFG_MEMBER_FUNCS_CACHE_BOOL(member, def_val) \ OIDC_CFG_MEMBER_FUNCS_CACHE_INT_EXT(member, oidc_cfg_parse_boolean(cmd->pool, arg, &v), def_val) OIDC_CFG_MEMBER_FUNCS_CACHE_BOOL(encrypt, cfg->cache.impl->encrypt_by_default) #define OIDC_CFG_MEMBER_FUNCS_CACHE_STR_DEF(member, valid, def_val) \ OIDC_CFG_MEMBER_FUNC_CACHE_SET(member, valid) \ \ const char *oidc_cfg_cache_##member##_get(oidc_cfg_t *cfg) { \ return (cfg->cache.member != NULL) ? cfg->cache.member : def_val; \ } /* * shm */ /* minimum shm cache size i.e. minimum number of entries */ #define OIDC_CACHE_SHM_SIZE_MIN 128 /* maximum shm cache size i.e. maximum number of entries */ #define OIDC_CACHE_SHM_SIZE_MAX 1024 * 1024 * 1024 /* default shm cache size i.e. the number of pre-allocated entries in the shm cache */ #define OIDC_DEFAULT_CACHE_SHM_SIZE 10000 OIDC_CFG_MEMBER_FUNCS_CACHE_INT(shm_size_max, OIDC_CACHE_SHM_SIZE_MIN, OIDC_CACHE_SHM_SIZE_MAX, OIDC_DEFAULT_CACHE_SHM_SIZE) /* minimum size of a SHM cache entry */ #define OIDC_MINIMUM_CACHE_SHM_ENTRY_SIZE_MAX 8192 + 512 + 32 // 8Kb plus overhead /* maximum size of a SHM cache entry */ #define OIDC_MAXIMUM_CACHE_SHM_ENTRY_SIZE_MAX 1024 * 1024 // 1Mb incl. overhead /* default max cache entry size for shm: # value + # key + # overhead */ #define OIDC_DEFAULT_CACHE_SHM_ENTRY_SIZE_MAX 16384 + 512 + 32 /* * set the maximum size of a shared memory cache entry and enforces a minimum */ const char *oidc_cmd_cache_shm_entry_size_max_set(cmd_parms *cmd, void *ptr, const char *arg) { oidc_cfg_t *cfg = (oidc_cfg_t *)ap_get_module_config(cmd->server->module_config, &auth_openidc_module); const char *rv = oidc_cfg_parse_int_min_max(cmd->pool, arg, &cfg->cache.shm_entry_size_max, OIDC_MINIMUM_CACHE_SHM_ENTRY_SIZE_MAX, OIDC_MAXIMUM_CACHE_SHM_ENTRY_SIZE_MAX); if ((rv == NULL) && ((cfg->cache.shm_entry_size_max % 8) != 0)) rv = "the slot size must be a multiple of 8"; return OIDC_CONFIG_DIR_RV(cmd, rv); } OIDC_CFG_MEMBER_FUNC_CACHE_TYPE_GET(shm_entry_size_max, int, OIDC_DEFAULT_CACHE_SHM_ENTRY_SIZE_MAX) static void oidc_cfg_cache_shm_create_server_config(oidc_cfg_t *c) { c->cache.shm_size_max = OIDC_DEFAULT_CACHE_SHM_SIZE; c->cache.shm_entry_size_max = OIDC_DEFAULT_CACHE_SHM_ENTRY_SIZE_MAX; } static void oidc_cfg_cache_shm_merge_server_config(oidc_cfg_t *c, oidc_cfg_t *base, oidc_cfg_t *add) { c->cache.shm_size_max = add->cache.shm_size_max != OIDC_DEFAULT_CACHE_SHM_SIZE ? add->cache.shm_size_max : base->cache.shm_size_max; c->cache.shm_entry_size_max = add->cache.shm_entry_size_max != OIDC_DEFAULT_CACHE_SHM_ENTRY_SIZE_MAX ? add->cache.shm_entry_size_max : base->cache.shm_entry_size_max; } /* * file */ /* minimum cache files clean interval in seconds */ #define OIDC_CACHE_FILE_CLEAN_INTERVAL_MIN 0 /* maximum cache files clean interval in seconds */ #define OIDC_CACHE_FILE_CLEAN_INTERVAL_MAX 3600 * 24 * 7 /* default cache files clean interval in seconds */ #define OIDC_DEFAULT_CACHE_FILE_CLEAN_INTERVAL 60 OIDC_CFG_MEMBER_FUNCS_CACHE_INT(file_clean_interval, OIDC_CACHE_FILE_CLEAN_INTERVAL_MIN, OIDC_CACHE_FILE_CLEAN_INTERVAL_MAX, OIDC_DEFAULT_CACHE_FILE_CLEAN_INTERVAL) const char *oidc_cmd_cache_file_dir_set(cmd_parms *cmd, void *ptr, const char *arg) { oidc_cfg_t *cfg = (oidc_cfg_t *)ap_get_module_config(cmd->server->module_config, &auth_openidc_module); const char *rv = oidc_cfg_parse_dirname(cmd->pool, arg, &cfg->cache.file_dir); return OIDC_CONFIG_DIR_RV(cmd, rv); } static void oidc_cfg_cache_file_create_server_config(oidc_cfg_t *c) { c->cache.file_dir = NULL; c->cache.file_clean_interval = OIDC_CONFIG_POS_INT_UNSET; } static void oidc_cfg_cache_file_merge_server_config(oidc_cfg_t *c, oidc_cfg_t *base, oidc_cfg_t *add) { c->cache.file_dir = add->cache.file_dir != NULL ? add->cache.file_dir : base->cache.file_dir; c->cache.file_clean_interval = add->cache.file_clean_interval != OIDC_CONFIG_POS_INT_UNSET ? add->cache.file_clean_interval : base->cache.file_clean_interval; } /* * memcache */ #ifdef USE_MEMCACHE OIDC_CFG_MEMBER_FUNCS_CACHE_STR_DEF(memcache_servers, NULL, NULL) #define OIDC_CACHE_MEMCACHE_CONNECTIONS_MIN_MIN 0 #define OIDC_CACHE_MEMCACHE_CONNECTIONS_MIN_MAX 2048 #define OIDC_DEFAULT_CACHE_MEMCACHE_CONNECTIONS_MIN 0 OIDC_CFG_MEMBER_FUNCS_CACHE_INT(memcache_min, OIDC_CACHE_MEMCACHE_CONNECTIONS_MIN_MIN, OIDC_CACHE_MEMCACHE_CONNECTIONS_MIN_MAX, OIDC_DEFAULT_CACHE_MEMCACHE_CONNECTIONS_MIN) #define OIDC_CACHE_MEMCACHE_CONNECTIONS_SMAX_MIN 0 #define OIDC_CACHE_MEMCACHE_CONNECTIONS_SMAX_MAX 2048 #define OIDC_DEFAULT_CACHE_MEMCACHE_CONNECTIONS_SMAX 0 OIDC_CFG_MEMBER_FUNCS_CACHE_INT(memcache_smax, OIDC_CACHE_MEMCACHE_CONNECTIONS_SMAX_MIN, OIDC_CACHE_MEMCACHE_CONNECTIONS_SMAX_MAX, OIDC_DEFAULT_CACHE_MEMCACHE_CONNECTIONS_SMAX) #define OIDC_CACHE_MEMCACHE_CONNECTIONS_HMAX_MIN 0 #define OIDC_CACHE_MEMCACHE_CONNECTIONS_HMAX_MAX 2048 #define OIDC_DEFAULT_CACHE_MEMCACHE_CONNECTIONS_HMAX 0 OIDC_CFG_MEMBER_FUNCS_CACHE_INT(memcache_hmax, OIDC_CACHE_MEMCACHE_CONNECTIONS_HMAX_MIN, OIDC_CACHE_MEMCACHE_CONNECTIONS_HMAX_MAX, OIDC_DEFAULT_CACHE_MEMCACHE_CONNECTIONS_HMAX) #define OIDC_CACHE_MEMCACHE_CONNECTIONS_TTL_MIN 0 #define OIDC_CACHE_MEMCACHE_CONNECTIONS_TTL_MAX 3600 * 24 * 7 #define OIDC_DEFAULT_CACHE_MEMCACHE_CONNECTIONS_TTL 0 OIDC_CFG_MEMBER_FUNCS_CACHE_INT(memcache_ttl, OIDC_CACHE_MEMCACHE_CONNECTIONS_TTL_MIN, OIDC_CACHE_MEMCACHE_CONNECTIONS_TTL_MAX, OIDC_DEFAULT_CACHE_MEMCACHE_CONNECTIONS_TTL) static void oidc_cfg_cache_memcache_create_server_config(oidc_cfg_t *c) { c->cache.memcache_servers = NULL; c->cache.memcache_min = OIDC_CONFIG_POS_INT_UNSET; c->cache.memcache_smax = OIDC_CONFIG_POS_INT_UNSET; c->cache.memcache_hmax = OIDC_CONFIG_POS_INT_UNSET; c->cache.memcache_ttl = OIDC_CONFIG_POS_INT_UNSET; } static void oidc_cfg_cache_memcache_merge_server_config(oidc_cfg_t *c, oidc_cfg_t *base, oidc_cfg_t *add) { c->cache.memcache_servers = add->cache.memcache_servers != NULL ? add->cache.memcache_servers : base->cache.memcache_servers; c->cache.memcache_min = add->cache.memcache_min != OIDC_CONFIG_POS_INT_UNSET ? add->cache.memcache_min : base->cache.memcache_min; c->cache.memcache_smax = add->cache.memcache_smax != OIDC_CONFIG_POS_INT_UNSET ? add->cache.memcache_smax : base->cache.memcache_smax; c->cache.memcache_hmax = add->cache.memcache_hmax != OIDC_CONFIG_POS_INT_UNSET ? add->cache.memcache_hmax : base->cache.memcache_hmax; c->cache.memcache_ttl = add->cache.memcache_ttl != OIDC_CONFIG_POS_INT_UNSET ? add->cache.memcache_ttl : base->cache.memcache_ttl; } #endif /* * redis */ #ifdef USE_LIBHIREDIS OIDC_CFG_MEMBER_FUNCS_CACHE_STR_DEF(redis_server, NULL, NULL) OIDC_CFG_MEMBER_FUNCS_CACHE_STR_DEF(redis_username, NULL, NULL) OIDC_CFG_MEMBER_FUNCS_CACHE_STR_DEF(redis_password, NULL, NULL) #define OIDC_CACHE_REDIS_DATABASE_MIN 0 #define OIDC_CACHE_REDIS_DATABASE_MAX 1024 OIDC_CFG_MEMBER_FUNCS_CACHE_INT(redis_database, OIDC_CACHE_REDIS_DATABASE_MIN, OIDC_CACHE_REDIS_DATABASE_MAX, OIDC_CONFIG_POS_INT_UNSET) #define OIDC_REDIS_CONNECT_TIMEOUT_MIN 1 #define OIDC_REDIS_CONNECT_TIMEOUT_MAX 3600 // NB: zero for turning off TCP keepalive, which is enabled by default #define OIDC_REDIS_KEEPALIVE_TIMEOUT_MIN 0 #define OIDC_REDIS_KEEPALIVE_TIMEOUT_MAX 3600 const char *oidc_cmd_cache_redis_connect_timeout_set(cmd_parms *cmd, void *struct_ptr, const char *arg1, const char *arg2) { oidc_cfg_t *cfg = (oidc_cfg_t *)ap_get_module_config(cmd->server->module_config, &auth_openidc_module); const char *rv = NULL; if (arg1) rv = oidc_cfg_parse_int_min_max(cmd->pool, arg1, &cfg->cache.redis_connect_timeout, OIDC_REDIS_CONNECT_TIMEOUT_MIN, OIDC_REDIS_CONNECT_TIMEOUT_MAX); if ((rv == NULL) && (arg2)) rv = oidc_cfg_parse_int_min_max(cmd->pool, arg2, &cfg->cache.redis_keepalive, OIDC_REDIS_KEEPALIVE_TIMEOUT_MIN, OIDC_REDIS_KEEPALIVE_TIMEOUT_MAX); return OIDC_CONFIG_DIR_RV(cmd, rv); } OIDC_CFG_MEMBER_FUNC_CACHE_TYPE_GET(redis_connect_timeout, int, OIDC_CONFIG_POS_INT_UNSET) OIDC_CFG_MEMBER_FUNC_CACHE_TYPE_GET(redis_keepalive, int, OIDC_CONFIG_POS_INT_UNSET) #define OIDC_REDIS_TIMEOUT_MIN 1 #define OIDC_REDIS_TIMEOUT_MAX 3600 OIDC_CFG_MEMBER_FUNCS_CACHE_INT(redis_timeout, OIDC_REDIS_TIMEOUT_MIN, OIDC_REDIS_TIMEOUT_MAX, OIDC_CONFIG_POS_INT_UNSET) static void oidc_cfg_cache_redis_create_server_config(oidc_cfg_t *c) { c->cache.redis_server = NULL; c->cache.redis_username = NULL; c->cache.redis_password = NULL; c->cache.redis_database = OIDC_CONFIG_POS_INT_UNSET; c->cache.redis_connect_timeout = OIDC_CONFIG_POS_INT_UNSET; c->cache.redis_keepalive = OIDC_CONFIG_POS_INT_UNSET; c->cache.redis_timeout = OIDC_CONFIG_POS_INT_UNSET; } static void oidc_cfg_cache_redis_merge_server_config(oidc_cfg_t *c, oidc_cfg_t *base, oidc_cfg_t *add) { c->cache.redis_server = add->cache.redis_server != NULL ? add->cache.redis_server : base->cache.redis_server; c->cache.redis_username = add->cache.redis_username != NULL ? add->cache.redis_username : base->cache.redis_username; c->cache.redis_password = add->cache.redis_password != NULL ? add->cache.redis_password : base->cache.redis_password; c->cache.redis_database = add->cache.redis_database != OIDC_CONFIG_POS_INT_UNSET ? add->cache.redis_database : base->cache.redis_database; c->cache.redis_connect_timeout = add->cache.redis_connect_timeout != OIDC_CONFIG_POS_INT_UNSET ? add->cache.redis_connect_timeout : base->cache.redis_connect_timeout; c->cache.redis_keepalive = add->cache.redis_keepalive != OIDC_CONFIG_POS_INT_UNSET ? add->cache.redis_keepalive : base->cache.redis_keepalive; c->cache.redis_timeout = add->cache.redis_timeout != OIDC_CONFIG_POS_INT_UNSET ? add->cache.redis_timeout : base->cache.redis_timeout; } #endif /* * generic */ void oidc_cfg_cache_create_server_config(oidc_cfg_t *c) { c->cache.impl = NULL; c->cache.cfg = NULL; c->cache.encrypt = OIDC_CONFIG_POS_INT_UNSET; oidc_cfg_cache_shm_create_server_config(c); oidc_cfg_cache_file_create_server_config(c); #ifdef USE_MEMCACHE oidc_cfg_cache_memcache_create_server_config(c); #endif #ifdef USE_LIBHIREDIS oidc_cfg_cache_redis_create_server_config(c); #endif } void oidc_cfg_cache_merge_server_config(oidc_cfg_t *c, oidc_cfg_t *base, oidc_cfg_t *add) { c->cache.impl = (add->cache.impl != NULL) ? add->cache.impl : base->cache.impl; c->cache.encrypt = add->cache.encrypt != OIDC_CONFIG_POS_INT_UNSET ? add->cache.encrypt : base->cache.encrypt; c->cache.cfg = NULL; oidc_cfg_cache_shm_merge_server_config(c, base, add); oidc_cfg_cache_file_merge_server_config(c, base, add); #ifdef USE_MEMCACHE oidc_cfg_cache_memcache_merge_server_config(c, base, add); #endif #ifdef USE_LIBHIREDIS oidc_cfg_cache_redis_merge_server_config(c, base, add); #endif } mod_auth_openidc-2.4.16.10/src/cfg/cache.h000066400000000000000000000107071476721736500201150ustar00rootroot00000000000000/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ /*************************************************************************** * Copyright (C) 2017-2025 ZmartZone Holding BV * All rights reserved. * * DISCLAIMER OF WARRANTIES: * * THE SOFTWARE PROVIDED HEREUNDER IS PROVIDED ON AN "AS IS" BASIS, WITHOUT * ANY WARRANTIES OR REPRESENTATIONS EXPRESS, IMPLIED OR STATUTORY; INCLUDING, * WITHOUT LIMITATION, WARRANTIES OF QUALITY, PERFORMANCE, NONINFRINGEMENT, * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. NOR ARE THERE ANY * WARRANTIES CREATED BY A COURSE OR DEALING, COURSE OF PERFORMANCE OR TRADE * USAGE. FURTHERMORE, THERE ARE NO WARRANTIES THAT THE SOFTWARE WILL MEET * YOUR NEEDS OR BE FREE FROM ERRORS, OR THAT THE OPERATION OF THE SOFTWARE * WILL BE UNINTERRUPTED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * @Author: Hans Zandbelt - hans.zandbelt@openidc.com */ #ifndef _MOD_AUTH_OPENIDC_CFG_CACHE_H_ #define _MOD_AUTH_OPENIDC_CFG_CACHE_H_ #include "cfg/cfg.h" void oidc_cfg_cache_create_server_config(oidc_cfg_t *c); void oidc_cfg_cache_merge_server_config(oidc_cfg_t *c, oidc_cfg_t *base, oidc_cfg_t *add); // NB: need the primitive strings and the custom set routines // here because the commands are included in config. #define OIDCCacheType "OIDCCacheType" #define OIDCCacheEncrypt "OIDCCacheEncrypt" OIDC_CFG_MEMBER_FUNCS_DECL(cache_type, int) OIDC_CFG_MEMBER_FUNCS_DECL(cache_encrypt, int) /* * shm */ #define OIDCCacheShmMax "OIDCCacheShmMax" #define OIDCCacheShmEntrySizeMax "OIDCCacheShmEntrySizeMax" OIDC_CFG_MEMBER_FUNCS_DECL(cache_shm_size_max, int) OIDC_CFG_MEMBER_FUNCS_DECL(cache_shm_entry_size_max, int) /* * file */ #define OIDCCacheDir "OIDCCacheDir" #define OIDCCacheFileCleanInterval "OIDCCacheFileCleanInterval" OIDC_CFG_MEMBER_FUNCS_DECL(cache_file_clean_interval, int) OIDC_CFG_MEMBER_FUNCS_DECL(cache_file_dir, const char *) /* * memcache */ #ifdef USE_MEMCACHE #define OIDCMemCacheServers "OIDCMemCacheServers" #define OIDCMemCacheConnectionsMin "OIDCMemCacheConnectionsMin" #define OIDCMemCacheConnectionsSMax "OIDCMemCacheConnectionsSMax" #define OIDCMemCacheConnectionsHMax "OIDCMemCacheConnectionsHMax" #define OIDCMemCacheConnectionsTTL "OIDCMemCacheConnectionsTTL" OIDC_CFG_MEMBER_FUNCS_DECL(cache_memcache_servers, const char *) OIDC_CFG_MEMBER_FUNCS_DECL(cache_memcache_min, int) OIDC_CFG_MEMBER_FUNCS_DECL(cache_memcache_smax, int) OIDC_CFG_MEMBER_FUNCS_DECL(cache_memcache_hmax, int) OIDC_CFG_MEMBER_FUNCS_DECL(cache_memcache_ttl, int) #endif // USE_MEMCACHE /* * redis */ #ifdef USE_LIBHIREDIS #define OIDCRedisCacheServer "OIDCRedisCacheServer" #define OIDCRedisCacheUsername "OIDCRedisCacheUsername" #define OIDCRedisCachePassword "OIDCRedisCachePassword" #define OIDCRedisCacheDatabase "OIDCRedisCacheDatabase" #define OIDCRedisCacheConnectTimeout "OIDCRedisCacheConnectTimeout" #define OIDCRedisCacheTimeout "OIDCRedisCacheTimeout" OIDC_CFG_MEMBER_FUNCS_DECL(cache_redis_server, const char *) OIDC_CFG_MEMBER_FUNCS_DECL(cache_redis_username, const char *) OIDC_CFG_MEMBER_FUNCS_DECL(cache_redis_password, const char *) OIDC_CFG_MEMBER_FUNCS_DECL(cache_redis_database, int) OIDC_CFG_MEMBER_FUNCS_DECL(cache_redis_timeout, int) OIDC_CFG_MEMBER_FUNCS_DECL(cache_redis_connect_timeout, int, const char *) OIDC_CFG_MEMBER_FUNC_GET_DECL(cache_redis_keepalive, int) #endif // USE_LIBHIREDIS #endif // _MOD_AUTH_OPENIDC_CFG_CACHE_H_ mod_auth_openidc-2.4.16.10/src/cfg/cfg.c000066400000000000000000001234261476721736500176070ustar00rootroot00000000000000/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ /*************************************************************************** * Copyright (C) 2017-2025 ZmartZone Holding BV * All rights reserved. * * DISCLAIMER OF WARRANTIES: * * THE SOFTWARE PROVIDED HEREUNDER IS PROVIDED ON AN "AS IS" BASIS, WITHOUT * ANY WARRANTIES OR REPRESENTATIONS EXPRESS, IMPLIED OR STATUTORY; INCLUDING, * WITHOUT LIMITATION, WARRANTIES OF QUALITY, PERFORMANCE, NONINFRINGEMENT, * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. NOR ARE THERE ANY * WARRANTIES CREATED BY A COURSE OR DEALING, COURSE OF PERFORMANCE OR TRADE * USAGE. FURTHERMORE, THERE ARE NO WARRANTIES THAT THE SOFTWARE WILL MEET * YOUR NEEDS OR BE FREE FROM ERRORS, OR THAT THE OPERATION OF THE SOFTWARE * WILL BE UNINTERRUPTED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * @Author: Hans Zandbelt - hans.zandbelt@openidc.com */ #include "cfg/cache.h" #include "cfg/cfg_int.h" #include "cfg/dir.h" #include "cfg/oauth.h" #include "cfg/parse.h" #include "cfg/provider.h" #include "jose.h" #include "metrics.h" #include "proto/proto.h" #include "session.h" #include "util.h" const char *oidc_cfg_string_list_add(apr_pool_t *pool, apr_array_header_t **list, const char *arg) { if (*list == NULL) *list = apr_array_make(pool, 1, sizeof(const char *)); APR_ARRAY_PUSH(*list, const char *) = arg; return NULL; } #define OIDC_DEFAULT_ACTION_ON_USERINFO_ERROR OIDC_ON_ERROR_502 OIDC_CFG_MEMBER_FUNC_TYPE_GET(action_on_userinfo_error, oidc_on_error_action_t, OIDC_DEFAULT_ACTION_ON_USERINFO_ERROR) #define OIDC_CFG_MEMBER_FUNCS_HTTP_TIMEOUT(member, def_val) \ const char *oidc_cmd_##member##_set(cmd_parms *cmd, void *ptr, const char *arg1, const char *arg2, \ const char *arg3) { \ oidc_cfg_t *cfg = \ (oidc_cfg_t *)ap_get_module_config(cmd->server->module_config, &auth_openidc_module); \ const char *rv = oidc_cfg_parse_http_timeout(cmd->pool, arg1, arg2, arg3, &cfg->member); \ return OIDC_CONFIG_DIR_RV(cmd, rv); \ } \ \ oidc_http_timeout_t *oidc_cfg_##member##_get(oidc_cfg_t *cfg) { \ if (cfg->member.request_timeout == OIDC_CONFIG_POS_INT_UNSET) \ /* NB: we are modifying in-config but post_config/merge has finished by now */ \ cfg->member.request_timeout = def_val; \ return &cfg->member; \ } /* request timeout in seconds for HTTP calls that may take a long time */ #define OIDC_DEFAULT_HTTP_REQUEST_TIMEOUT_LONG 30 /* connect timeout in seconds for HTTP calls that may take a long time */ #define OIDC_DEFAULT_HTTP_CONNECT_TIMEOUT_LONG 10 /* nr of retries for HTTP calls that may take a long time */ #define OIDC_DEFAULT_HTTP_RETRIES_LONG 1 /* retry interval in milliseconds for HTTP calls that may take a long time */ #define OIDC_DEFAULT_HTTP_RETRY_INTERVAL_LONG 500 /* timeouts in seconds for HTTP calls that should take a short time (registry/discovery related) */ #define OIDC_DEFAULT_HTTP_REQUEST_TIMEOUT_SHORT 5 /* connect timeout in seconds for HTTP calls that may take a long time */ #define OIDC_DEFAULT_HTTP_CONNECT_TIMEOUT_SHORT 2 /* nr of retries for HTTP calls that should take a short time */ #define OIDC_DEFAULT_HTTP_RETRIES_SHORT 1 /* retry interval in milliseconds for HTTP calls that should take a short time */ #define OIDC_DEFAULT_HTTP_RETRY_INTERVAL_SHORT 300 OIDC_CFG_MEMBER_FUNCS_HTTP_TIMEOUT(http_timeout_long, OIDC_DEFAULT_HTTP_REQUEST_TIMEOUT_LONG) OIDC_CFG_MEMBER_FUNCS_HTTP_TIMEOUT(http_timeout_short, OIDC_DEFAULT_HTTP_REQUEST_TIMEOUT_SHORT) const char *oidc_cmd_crypto_passphrase_set(cmd_parms *cmd, void *struct_ptr, const char *arg1, const char *arg2) { oidc_cfg_t *cfg = (oidc_cfg_t *)ap_get_module_config(cmd->server->module_config, &auth_openidc_module); const char *rv = NULL; if (arg1) rv = oidc_cfg_parse_passphrase(cmd->pool, arg1, &cfg->crypto_passphrase.secret1); if ((rv == NULL) && (arg2 != NULL)) rv = oidc_cfg_parse_passphrase(cmd->pool, arg2, &cfg->crypto_passphrase.secret2); return rv; } const oidc_crypto_passphrase_t *oidc_cfg_crypto_passphrase_get(oidc_cfg_t *cfg) { return &cfg->crypto_passphrase; } const char *oidc_cfg_crypto_passphrase_secret1_get(oidc_cfg_t *cfg) { return cfg->crypto_passphrase.secret1; } const char *oidc_cmd_outgoing_proxy_set(cmd_parms *cmd, void *ptr, const char *arg1, const char *arg2, const char *arg3) { oidc_cfg_t *cfg = (oidc_cfg_t *)ap_get_module_config(cmd->server->module_config, &auth_openidc_module); const char *rv = NULL; if (arg1) cfg->outgoing_proxy.host_port = apr_pstrdup(cmd->pool, arg1); if (arg2) cfg->outgoing_proxy.username_password = apr_pstrdup(cmd->pool, arg2); if (arg3) { rv = oidc_cfg_parse_is_valid_option(cmd->pool, arg3, oidc_http_proxy_auth_options()); if (rv == NULL) cfg->outgoing_proxy.auth_type = oidc_http_proxy_s2auth(arg3); } return OIDC_CONFIG_DIR_RV(cmd, rv); } const oidc_http_outgoing_proxy_t *oidc_cfg_outgoing_proxy_get(oidc_cfg_t *cfg) { return &cfg->outgoing_proxy; } static const char *oidc_cfg_valid_cookie_domain(apr_pool_t *pool, const char *arg) { size_t sz, limit; char d; limit = _oidc_strlen(arg); for (sz = 0; sz < limit; sz++) { d = arg[sz]; if ((d < '0' || d > '9') && (d < 'a' || d > 'z') && (d < 'A' || d > 'Z') && d != '.' && d != '-') { return (apr_psprintf(pool, "invalid character '%c' in cookie domain value: %s", d, arg)); } } return NULL; } OIDC_CFG_MEMBER_FUNCS_TYPE(cookie_domain, const char *, oidc_cfg_valid_cookie_domain(cmd->pool, arg)) #define OIDC_SESSION_TYPE_SERVER_CACHE_STR "server-cache" #define OIDC_SESSION_TYPE_CLIENT_COOKIE_STR "client-cookie" #define OIDC_SESSION_TYPE_PERSISTENT "persistent" #define OIDC_SESSION_TYPE_STORE_ID_TOKEN "store_id_token" #define OIDC_SESSION_TYPE_SEPARATOR ":" const char *oidc_cmd_session_type_set(cmd_parms *cmd, void *ptr, const char *arg) { oidc_cfg_t *cfg = (oidc_cfg_t *)ap_get_module_config(cmd->server->module_config, &auth_openidc_module); static const char *options[] = { OIDC_SESSION_TYPE_SERVER_CACHE_STR, OIDC_SESSION_TYPE_SERVER_CACHE_STR OIDC_SESSION_TYPE_SEPARATOR OIDC_SESSION_TYPE_PERSISTENT, OIDC_SESSION_TYPE_CLIENT_COOKIE_STR, OIDC_SESSION_TYPE_CLIENT_COOKIE_STR OIDC_SESSION_TYPE_SEPARATOR OIDC_SESSION_TYPE_PERSISTENT, OIDC_SESSION_TYPE_CLIENT_COOKIE_STR OIDC_SESSION_TYPE_SEPARATOR OIDC_SESSION_TYPE_STORE_ID_TOKEN, OIDC_SESSION_TYPE_CLIENT_COOKIE_STR OIDC_SESSION_TYPE_SEPARATOR OIDC_SESSION_TYPE_PERSISTENT OIDC_SESSION_TYPE_SEPARATOR OIDC_SESSION_TYPE_STORE_ID_TOKEN, NULL}; const char *rv = oidc_cfg_parse_is_valid_option(cmd->pool, arg, options); if (rv != NULL) return OIDC_CONFIG_DIR_RV(cmd, rv); char *s = apr_pstrdup(cmd->pool, arg); char *p = _oidc_strstr(s, OIDC_SESSION_TYPE_SEPARATOR); if (p) { *p = '\0'; p++; } if (_oidc_strcmp(s, OIDC_SESSION_TYPE_SERVER_CACHE_STR) == 0) { cfg->session_type = OIDC_SESSION_TYPE_SERVER_CACHE; } else if (_oidc_strcmp(s, OIDC_SESSION_TYPE_CLIENT_COOKIE_STR) == 0) { cfg->session_type = OIDC_SESSION_TYPE_CLIENT_COOKIE; cfg->store_id_token = 0; } if (p) { if (_oidc_strcmp(p, OIDC_SESSION_TYPE_PERSISTENT) == 0) { cfg->persistent_session_cookie = 1; } else if (_oidc_strcmp(p, OIDC_SESSION_TYPE_STORE_ID_TOKEN) == 0) { // only for client-cookie cfg->store_id_token = 1; } else if (_oidc_strcmp(p, OIDC_SESSION_TYPE_SEPARATOR OIDC_SESSION_TYPE_PERSISTENT OIDC_SESSION_TYPE_SEPARATOR OIDC_SESSION_TYPE_STORE_ID_TOKEN) == 0) { // only for client-cookie cfg->persistent_session_cookie = 1; cfg->store_id_token = 1; } } return NULL; } #define OIDC_DEFAULT_SESSION_TYPE OIDC_SESSION_TYPE_SERVER_CACHE OIDC_CFG_MEMBER_FUNC_TYPE_GET(session_type, int, OIDC_DEFAULT_SESSION_TYPE) #define OIDC_DEFAULT_PERSISTENT_SESSION_COOKIE 0 OIDC_CFG_MEMBER_FUNC_TYPE_GET(persistent_session_cookie, int, OIDC_DEFAULT_PERSISTENT_SESSION_COOKIE) #define OIDC_DEFAULT_STORE_ID_TOKEN 1 OIDC_CFG_MEMBER_FUNC_TYPE_GET(store_id_token, int, OIDC_DEFAULT_STORE_ID_TOKEN) static const char *oidc_valid_endpoint_auth_method_impl(apr_pool_t *pool, const char *arg, apr_byte_t has_private_key) { static const char *options[] = {OIDC_ENDPOINT_AUTH_CLIENT_SECRET_POST, OIDC_ENDPOINT_AUTH_CLIENT_SECRET_BASIC, OIDC_ENDPOINT_AUTH_CLIENT_SECRET_JWT, OIDC_ENDPOINT_AUTH_NONE, OIDC_ENDPOINT_AUTH_BEARER_ACCESS_TOKEN, NULL, NULL}; if (has_private_key) options[5] = OIDC_ENDPOINT_AUTH_PRIVATE_KEY_JWT; return oidc_cfg_parse_is_valid_option(pool, arg, options); } const char *oidc_cfg_valid_endpoint_auth_method(apr_pool_t *pool, const char *arg) { return oidc_valid_endpoint_auth_method_impl(pool, arg, TRUE); } const char *oidc_cfg_valid_endpoint_auth_method_no_private_key(apr_pool_t *pool, const char *arg) { return oidc_valid_endpoint_auth_method_impl(pool, arg, FALSE); } /* * return the right token endpoint authentication method validation function, based on whether private keys are set */ oidc_valid_function_t oidc_cfg_get_valid_endpoint_auth_function(oidc_cfg_t *cfg) { return (cfg->private_keys != NULL) ? &oidc_cfg_valid_endpoint_auth_method : &oidc_cfg_valid_endpoint_auth_method_no_private_key; } #define OIDC_SESSION_INACTIVITY_TIMEOUT_MIN 10 #define OIDC_SESSION_INACTIVITY_TIMEOUT_MAX 3600 * 24 * 365 #define OIDC_DEFAULT_SESSION_INACTIVITY_TIMEOUT 300 OIDC_CFG_MEMBER_FUNCS_INT(session_inactivity_timeout, OIDC_SESSION_INACTIVITY_TIMEOUT_MIN, OIDC_SESSION_INACTIVITY_TIMEOUT_MAX, OIDC_DEFAULT_SESSION_INACTIVITY_TIMEOUT) const char *oidc_cmd_public_keys_set(cmd_parms *cmd, void *ptr, const char *arg) { oidc_cfg_t *cfg = (oidc_cfg_t *)ap_get_module_config(cmd->server->module_config, &auth_openidc_module); const char *rv = oidc_cfg_parse_public_key_files(cmd->pool, arg, &cfg->public_keys); return OIDC_CONFIG_DIR_RV(cmd, rv); } OIDC_CFG_MEMBER_FUNC_GET(public_keys, const apr_array_header_t *) /* * add a private key from an RSA/EC private key file to our list of JWKs with private keys */ const char *oidc_cmd_private_keys_set(cmd_parms *cmd, void *ptr, const char *arg) { oidc_cfg_t *cfg = (oidc_cfg_t *)ap_get_module_config(cmd->server->module_config, &auth_openidc_module); oidc_jwk_t *jwk = NULL; oidc_jose_error_t err; char *use = NULL; char *kid = NULL, *name = NULL, *fname = NULL; int fname_len; const char *rv = oidc_cfg_parse_key_record(cmd->pool, arg, &kid, &name, &fname_len, &use, FALSE); if (rv != NULL) goto end; rv = oidc_cfg_parse_filename(cmd->pool, name, &fname); if (rv != NULL) goto end; if (oidc_jwk_parse_pem_private_key(cmd->pool, kid, fname, &jwk, &err) == FALSE) { rv = apr_psprintf(cmd->pool, "oidc_jwk_parse_pem_private_key failed for (kid=%s) \"%s\": %s", kid, fname, oidc_jose_e2s(cmd->pool, err)); goto end; } if (cfg->private_keys == NULL) cfg->private_keys = apr_array_make(cmd->pool, 4, sizeof(oidc_jwk_t *)); if (use) jwk->use = apr_pstrdup(cmd->pool, use); APR_ARRAY_PUSH(cfg->private_keys, oidc_jwk_t *) = jwk; end: return OIDC_CONFIG_DIR_RV(cmd, rv); } OIDC_CFG_MEMBER_FUNC_GET(private_keys, const apr_array_header_t *) const char *oidc_cmd_remote_user_claim_set(cmd_parms *cmd, void *ptr, const char *v1, const char *v2, const char *v3) { oidc_cfg_t *cfg = (oidc_cfg_t *)ap_get_module_config(cmd->server->module_config, &auth_openidc_module); const char *rv = oidc_parse_remote_user_claim(cmd->pool, v1, v2, v3, &cfg->remote_user_claim); return OIDC_CONFIG_DIR_RV(cmd, rv); } const oidc_remote_user_claim_t *oidc_cfg_remote_user_claim_get(oidc_cfg_t *cfg) { return &cfg->remote_user_claim; } #define OIDC_DEFAULT_CLAIM_REMOTE_USER "sub@" const char *oidc_cfg_remote_user_claim_name_get(oidc_cfg_t *cfg) { return cfg->remote_user_claim.claim_name != NULL ? cfg->remote_user_claim.claim_name : OIDC_DEFAULT_CLAIM_REMOTE_USER; } #ifdef USE_LIBJQ const char *oidc_cmd_filter_claims_expr_set(cmd_parms *cmd, void *m, const char *arg) { oidc_cfg_t *cfg = (oidc_cfg_t *)ap_get_module_config(cmd->server->module_config, &auth_openidc_module); const char *rv = oidc_util_apr_expr_parse(cmd, arg, &cfg->filter_claims_expr, TRUE); return OIDC_CONFIG_DIR_RV(cmd, rv); } #endif OIDC_CFG_MEMBER_FUNC_GET(filter_claims_expr, oidc_apr_expr_t *) /* * define which data will be returned from the info hook */ const char *oidc_cmd_info_hook_data_set(cmd_parms *cmd, void *m, const char *arg) { oidc_cfg_t *cfg = (oidc_cfg_t *)ap_get_module_config(cmd->server->module_config, &auth_openidc_module); static const char *options[] = { OIDC_HOOK_INFO_TIMESTAMP, OIDC_HOOK_INFO_ACCES_TOKEN, OIDC_HOOK_INFO_ACCES_TOKEN_EXP, OIDC_HOOK_INFO_ID_TOKEN_HINT, OIDC_HOOK_INFO_ID_TOKEN, OIDC_HOOK_INFO_USER_INFO, OIDC_HOOK_INFO_REFRESH_TOKEN, OIDC_HOOK_INFO_SESSION_EXP, OIDC_HOOK_INFO_SESSION_TIMEOUT, OIDC_HOOK_INFO_SESSION_REMOTE_USER, OIDC_HOOK_INFO_SESSION, NULL}; const char *rv = oidc_cfg_parse_is_valid_option(cmd->pool, arg, options); if (rv != NULL) return OIDC_CONFIG_DIR_RV(cmd, rv); if (cfg->info_hook_data == NULL) cfg->info_hook_data = apr_hash_make(cmd->pool); apr_hash_set(cfg->info_hook_data, arg, APR_HASH_KEY_STRING, arg); return NULL; } OIDC_CFG_MEMBER_FUNC_GET(info_hook_data, apr_hash_t *) const char *oidc_cmd_metrics_hook_data_set(cmd_parms *cmd, void *m, const char *arg) { oidc_cfg_t *cfg = (oidc_cfg_t *)ap_get_module_config(cmd->server->module_config, &auth_openidc_module); const char *rv = NULL; char *valid_names = NULL; if (oidc_metrics_is_valid_classname(cmd->pool, arg, &valid_names) == TRUE) { if (cfg->metrics_hook_data == NULL) cfg->metrics_hook_data = apr_hash_make(cmd->pool); apr_hash_set(cfg->metrics_hook_data, arg, APR_HASH_KEY_STRING, arg); } else { rv = apr_psprintf(cmd->pool, "undefined metric class name: \"%s\", must be one of [%s]", arg, valid_names); } return OIDC_CONFIG_DIR_RV(cmd, rv); } OIDC_CFG_MEMBER_FUNC_GET(metrics_hook_data, apr_hash_t *) #define OIDC_TRACE_PARENT_OFF_STR "off" #define OIDC_TRACE_PARENT_PROPAGATE_STR "propagate" #define OIDC_TRACE_PARENT_GENERATE_STR "generate" const char *oidc_cmd_trace_parent_set(cmd_parms *cmd, void *struct_ptr, const char *arg) { oidc_cfg_t *cfg = (oidc_cfg_t *)ap_get_module_config(cmd->server->module_config, &auth_openidc_module); static const oidc_cfg_option_t options[] = {{OIDC_TRACE_PARENT_OFF, OIDC_TRACE_PARENT_OFF_STR}, {OIDC_TRACE_PARENT_PROPAGATE, OIDC_TRACE_PARENT_PROPAGATE_STR}, {OIDC_TRACE_PARENT_GENERATE, OIDC_TRACE_PARENT_GENERATE_STR}}; const char *rv = oidc_cfg_parse_option(cmd->pool, options, OIDC_CFG_OPTIONS_SIZE(options), arg, &cfg->trace_parent); return OIDC_CONFIG_DIR_RV(cmd, rv); } #define OIDC_DEFAULT_TRACE_PARENT OIDC_TRACE_PARENT_OFF OIDC_CFG_MEMBER_FUNC_TYPE_GET(trace_parent, oidc_trace_parent_t, OIDC_DEFAULT_TRACE_PARENT) #define OIDC_DEFAULT_DPOP_API_ENABLED 0 OIDC_CFG_MEMBER_FUNC_TYPE_GET(dpop_api_enabled, int, OIDC_DEFAULT_DPOP_API_ENABLED) const char *oidc_cmd_claim_prefix_set(cmd_parms *cmd, void *struct_ptr, const char *args) { oidc_cfg_t *cfg = (oidc_cfg_t *)ap_get_module_config(cmd->server->module_config, &auth_openidc_module); char *w = ap_getword_conf(cmd->pool, &args); if (*w == '\0' || *args != 0) cfg->claim_prefix = ""; else cfg->claim_prefix = w; return NULL; } #define OIDC_DEFAULT_CLAIM_PREFIX "OIDC_CLAIM_" const char *oidc_cfg_claim_prefix_get(oidc_cfg_t *cfg) { return (cfg->claim_prefix != NULL) ? cfg->claim_prefix : OIDC_DEFAULT_CLAIM_PREFIX; } #define OIDC_MAX_NUMBER_OF_STATE_COOKIES_MIN 0 #define OIDC_MAX_NUMBER_OF_STATE_COOKIES_MAX 255 const char *oidc_cmd_max_number_of_state_cookies_set(cmd_parms *cmd, void *struct_ptr, const char *arg1, const char *arg2) { oidc_cfg_t *cfg = (oidc_cfg_t *)ap_get_module_config(cmd->server->module_config, &auth_openidc_module); const char *rv = oidc_cfg_parse_int_min_max(cmd->pool, arg1, &cfg->max_number_of_state_cookies, OIDC_MAX_NUMBER_OF_STATE_COOKIES_MIN, OIDC_MAX_NUMBER_OF_STATE_COOKIES_MAX); if ((rv == NULL) && (arg2 != NULL)) rv = oidc_cfg_parse_boolean(cmd->pool, arg2, &cfg->delete_oldest_state_cookies); return OIDC_CONFIG_DIR_RV(cmd, rv); } #define OIDC_DEFAULT_MAX_NUMBER_OF_STATE_COOKIES 7 OIDC_CFG_MEMBER_FUNC_TYPE_GET(max_number_of_state_cookies, int, OIDC_DEFAULT_MAX_NUMBER_OF_STATE_COOKIES) #define OIDC_DEFAULT_DELETE_OLDEST_STATE_COOKIES 0 OIDC_CFG_MEMBER_FUNC_TYPE_GET(delete_oldest_state_cookies, int, OIDC_DEFAULT_DELETE_OLDEST_STATE_COOKIES) const char *oidc_cmd_x_forwarded_headers_set(cmd_parms *cmd, void *m, const char *arg) { oidc_cfg_t *cfg = (oidc_cfg_t *)ap_get_module_config(cmd->server->module_config, &auth_openidc_module); static const oidc_cfg_option_t options[] = {{OIDC_HDR_NONE, "none"}, {OIDC_HDR_X_FORWARDED_HOST, OIDC_HTTP_HDR_X_FORWARDED_HOST}, {OIDC_HDR_X_FORWARDED_PORT, OIDC_HTTP_HDR_X_FORWARDED_PORT}, {OIDC_HDR_X_FORWARDED_PROTO, OIDC_HTTP_HDR_X_FORWARDED_PROTO}, {OIDC_HDR_FORWARDED, OIDC_HTTP_HDR_FORWARDED}}; int v = OIDC_CONFIG_POS_INT_UNSET; const char *rv = oidc_cfg_parse_option(cmd->pool, options, OIDC_CFG_OPTIONS_SIZE(options), arg, &v); if ((rv == NULL) && (v != OIDC_CONFIG_POS_INT_UNSET)) { // NB: cannot use |= with UNSET/-1 ! if (cfg->x_forwarded_headers == OIDC_CONFIG_POS_INT_UNSET) cfg->x_forwarded_headers = OIDC_HDR_NONE; cfg->x_forwarded_headers |= v; } return OIDC_CONFIG_DIR_RV(cmd, rv); } #define OIDC_DEFAULT_X_FORWARDED_HEADERS OIDC_HDR_NONE OIDC_CFG_MEMBER_FUNC_TYPE_GET(x_forwarded_headers, oidc_hdr_x_forwarded_t, OIDC_DEFAULT_X_FORWARDED_HEADERS) static void oidc_check_x_forwarded_hdr(request_rec *r, const apr_byte_t x_forwarded_headers, const apr_byte_t hdr_type, const char *hdr_str, const char *(hdr_func)(const request_rec *r)) { if (hdr_func(r)) { if (!(x_forwarded_headers & hdr_type)) oidc_warn(r, "header %s received but %s not configured for it", hdr_str, OIDCXForwardedHeaders); } else { if (x_forwarded_headers & hdr_type) oidc_warn(r, "%s configured for header %s but not found in request", OIDCXForwardedHeaders, hdr_str); } } void oidc_cfg_x_forwarded_headers_check(request_rec *r, oidc_hdr_x_forwarded_t x_forwarded_headers) { oidc_check_x_forwarded_hdr(r, x_forwarded_headers, OIDC_HDR_X_FORWARDED_HOST, OIDC_HTTP_HDR_X_FORWARDED_HOST, oidc_http_hdr_in_x_forwarded_host_get); oidc_check_x_forwarded_hdr(r, x_forwarded_headers, OIDC_HDR_X_FORWARDED_PORT, OIDC_HTTP_HDR_X_FORWARDED_PORT, oidc_http_hdr_in_x_forwarded_port_get); oidc_check_x_forwarded_hdr(r, x_forwarded_headers, OIDC_HDR_X_FORWARDED_PROTO, OIDC_HTTP_HDR_X_FORWARDED_PROTO, oidc_http_hdr_in_x_forwarded_proto_get); oidc_check_x_forwarded_hdr(r, x_forwarded_headers, OIDC_HDR_FORWARDED, OIDC_HTTP_HDR_FORWARDED, oidc_http_hdr_in_forwarded_get); } #define OIDC_STATE_INPUT_HEADERS_AS_NONE "none" #define OIDC_STATE_INPUT_HEADERS_AS_USER_AGENT "user-agent" #define OIDC_STATE_INPUT_HEADERS_AS_X_FORWARDED_FOR "x-forwarded-for" #define OIDC_STATE_INPUT_HEADERS_AS_BOTH "both" /* * define which header we use for calculating the fingerprint of the state during authentication */ const char *oidc_cmd_state_input_headers_set(cmd_parms *cmd, void *m, const char *arg) { oidc_cfg_t *cfg = (oidc_cfg_t *)ap_get_module_config(cmd->server->module_config, &auth_openidc_module); static const oidc_cfg_option_t options[] = { {OIDC_STATE_INPUT_HEADERS_NONE, OIDC_STATE_INPUT_HEADERS_AS_NONE}, {OIDC_STATE_INPUT_HEADERS_USER_AGENT, OIDC_STATE_INPUT_HEADERS_AS_USER_AGENT}, {OIDC_STATE_INPUT_HEADERS_X_FORWARDED_FOR, OIDC_STATE_INPUT_HEADERS_AS_X_FORWARDED_FOR}, {OIDC_STATE_INPUT_HEADERS_USER_AGENT | OIDC_STATE_INPUT_HEADERS_X_FORWARDED_FOR, OIDC_STATE_INPUT_HEADERS_AS_BOTH}}; const char *rv = oidc_cfg_parse_option(cmd->pool, options, OIDC_CFG_OPTIONS_SIZE(options), arg, &cfg->state_input_headers); return OIDC_CONFIG_DIR_RV(cmd, rv); } #define OIDC_DEFAULT_STATE_INPUT_HEADERS OIDC_STATE_INPUT_HEADERS_USER_AGENT OIDC_CFG_MEMBER_FUNC_TYPE_GET(state_input_headers, oidc_state_input_hdrs_t, OIDC_DEFAULT_STATE_INPUT_HEADERS) const char *oidc_cmd_post_preserve_templates_set(cmd_parms *cmd, void *m, const char *arg1, const char *arg2) { oidc_cfg_t *cfg = (oidc_cfg_t *)ap_get_module_config(cmd->server->module_config, &auth_openidc_module); const char *rv = NULL; if (arg1 != NULL) rv = oidc_cfg_parse_filename(cmd->pool, arg1, &cfg->post_preserve_template); if ((rv == NULL) && (arg2 != NULL)) rv = oidc_cfg_parse_filename(cmd->pool, arg2, &cfg->post_restore_template); return OIDC_CONFIG_DIR_RV(cmd, rv); } OIDC_CFG_MEMBER_FUNC_GET(post_preserve_template, const char *) OIDC_CFG_MEMBER_FUNC_GET(post_restore_template, const char *) const char *oidc_cmd_ca_bundle_path_set(cmd_parms *cmd, void *ptr, const char *arg) { oidc_cfg_t *cfg = (oidc_cfg_t *)ap_get_module_config(cmd->server->module_config, &auth_openidc_module); const char *rv = oidc_cfg_parse_filename(cmd->pool, arg, &cfg->ca_bundle_path); return OIDC_CONFIG_DIR_RV(cmd, rv); } OIDC_CFG_MEMBER_FUNC_GET(ca_bundle_path, const char *) const char *oidc_cmd_metadata_dir_set(cmd_parms *cmd, void *ptr, const char *arg) { oidc_cfg_t *cfg = (oidc_cfg_t *)ap_get_module_config(cmd->server->module_config, &auth_openidc_module); const char *rv = oidc_cfg_parse_dirname(cmd->pool, arg, &cfg->metadata_dir); return OIDC_CONFIG_DIR_RV(cmd, rv); } OIDC_CFG_MEMBER_FUNC_GET(metadata_dir, const char *) #define OIDC_DEFAULT_COOKIE_HTTPONLY 1 OIDC_CFG_MEMBER_FUNCS_BOOL(cookie_http_only, OIDC_DEFAULT_COOKIE_HTTPONLY) #define OIDC_SAMESITE_COOKIE_OFF_STR "Off" #define OIDC_SAMESITE_COOKIE_ON_STR "On" #define OIDC_SAMESITE_COOKIE_DISABLED_STR "Disabled" #define OIDC_SAMESITE_COOKIE_NONE_STR "None" #define OIDC_SAMESITE_COOKIE_LAX_STR "Lax" #define OIDC_SAMESITE_COOKIE_STRICT_STR "Strict" /* * define which header we use for calculating the fingerprint of the state during authentication */ const char *oidc_cmd_cookie_same_site_set(cmd_parms *cmd, void *m, const char *arg) { oidc_cfg_t *cfg = (oidc_cfg_t *)ap_get_module_config(cmd->server->module_config, &auth_openidc_module); // NB: On is made equal to Lax here and Off is equal to None (backwards compatibility) static const oidc_cfg_option_t options[] = {{OIDC_SAMESITE_COOKIE_NONE, OIDC_SAMESITE_COOKIE_OFF_STR}, {OIDC_SAMESITE_COOKIE_LAX, OIDC_SAMESITE_COOKIE_ON_STR}, {OIDC_SAMESITE_COOKIE_DISABLED, OIDC_SAMESITE_COOKIE_DISABLED_STR}, {OIDC_SAMESITE_COOKIE_NONE, OIDC_SAMESITE_COOKIE_NONE_STR}, {OIDC_SAMESITE_COOKIE_LAX, OIDC_SAMESITE_COOKIE_LAX_STR}, {OIDC_SAMESITE_COOKIE_STRICT, OIDC_SAMESITE_COOKIE_STRICT_STR}}; const char *rv = oidc_cfg_parse_option_ignore_case(cmd->pool, options, OIDC_CFG_OPTIONS_SIZE(options), arg, &cfg->cookie_same_site); return OIDC_CONFIG_DIR_RV(cmd, rv); } #define OIDC_DEFAULT_COOKIE_SAME_SITE OIDC_SAMESITE_COOKIE_LAX OIDC_CFG_MEMBER_FUNC_TYPE_GET(cookie_same_site, oidc_samesite_cookie_t, OIDC_DEFAULT_COOKIE_SAME_SITE) #define OIDC_DEFAULT_SESSION_FALLBACK_TO_COOKIE 0 OIDC_CFG_MEMBER_FUNCS_BOOL(session_cache_fallback_to_cookie, OIDC_DEFAULT_SESSION_FALLBACK_TO_COOKIE) #define OIDC_DEFAULT_CLAIM_DELIMITER "," OIDC_CFG_MEMBER_FUNCS_STR_DEF(claim_delimiter, NULL, OIDC_DEFAULT_CLAIM_DELIMITER) OIDC_CFG_MEMBER_FUNCS_STR_DEF(metrics_path, NULL, NULL) #define OIDC_DEFAULT_LOGOUT_X_FRAME_OPTIONS "DENY" OIDC_CFG_MEMBER_FUNCS_STR_DEF(logout_x_frame_options, NULL, OIDC_DEFAULT_LOGOUT_X_FRAME_OPTIONS) #define OIDC_STATE_TIMEOUT_MIN 1 #define OIDC_STATE_TIMEOUT_MAX 3600 * 24 * 30 #define OIDC_DEFAULT_STATE_TIMEOUT 300 OIDC_CFG_MEMBER_FUNCS_INT(state_timeout, OIDC_STATE_TIMEOUT_MIN, OIDC_STATE_TIMEOUT_MAX, OIDC_DEFAULT_STATE_TIMEOUT) #define OIDC_SESSION_CLIENT_COOKIE_CHUNK_SIZE_MIN 256 #define OIDC_SESSION_CLIENT_COOKIE_CHUNK_SIZE_MAX 1024 * 64 #define OIDC_DEFAULT_SESSION_CLIENT_COOKIE_CHUNK_SIZE 4000 OIDC_CFG_MEMBER_FUNCS_INT(session_cookie_chunk_size, OIDC_SESSION_CLIENT_COOKIE_CHUNK_SIZE_MIN, OIDC_SESSION_CLIENT_COOKIE_CHUNK_SIZE_MAX, OIDC_DEFAULT_SESSION_CLIENT_COOKIE_CHUNK_SIZE) #define OIDC_PROVIDER_METADATA_REFRESH_INTERVAL_MIN 30 #define OIDC_PROVIDER_METADATA_REFRESH_INTERVAL_MAX 3600 * 24 * 365 #define OIDC_DEFAULT_PROVIDER_METADATA_REFRESH_INTERVAL 0 OIDC_CFG_MEMBER_FUNCS_INT(provider_metadata_refresh_interval, OIDC_PROVIDER_METADATA_REFRESH_INTERVAL_MIN, OIDC_PROVIDER_METADATA_REFRESH_INTERVAL_MAX, OIDC_DEFAULT_PROVIDER_METADATA_REFRESH_INTERVAL) #define OIDC_CFG_MEMBER_FUNCS_HASHTABLE(member) \ const char *oidc_cmd_##member##_set(cmd_parms *cmd, void *ptr, const char *arg) { \ oidc_cfg_t *cfg = \ (oidc_cfg_t *)ap_get_module_config(cmd->server->module_config, &auth_openidc_module); \ if (cfg->member == NULL) \ cfg->member = apr_hash_make(cmd->pool); \ apr_hash_set(cfg->member, arg, APR_HASH_KEY_STRING, arg); \ return NULL; \ } \ \ OIDC_CFG_MEMBER_FUNC_GET(member, apr_hash_t *) OIDC_CFG_MEMBER_FUNCS_HASHTABLE(white_listed_claims) OIDC_CFG_MEMBER_FUNCS_HASHTABLE(black_listed_claims) OIDC_CFG_MEMBER_FUNCS_HASHTABLE(redirect_urls_allowed) #define OIDC_CFG_MEMBER_FUNCS_ABS_OR_REL_URI(member) \ const char *oidc_cmd_##member##_set(cmd_parms *cmd, void *ptr, const char *arg) { \ oidc_cfg_t *cfg = \ (oidc_cfg_t *)ap_get_module_config(cmd->server->module_config, &auth_openidc_module); \ const char *rv = oidc_cfg_parse_relative_or_absolute_url(cmd->pool, arg, &cfg->member); \ return OIDC_CONFIG_DIR_RV(cmd, rv); \ } \ \ OIDC_CFG_MEMBER_FUNC_GET(member, const char *) OIDC_CFG_MEMBER_FUNCS_ABS_OR_REL_URI(redirect_uri) OIDC_CFG_MEMBER_FUNCS_ABS_OR_REL_URI(default_sso_url) OIDC_CFG_MEMBER_FUNCS_ABS_OR_REL_URI(default_slo_url) /* * destroy a server config record and its members */ static apr_status_t oidc_cfg_server_destroy(void *data) { oidc_cfg_t *cfg = (oidc_cfg_t *)data; oidc_cfg_provider_destroy(cfg->provider); oidc_cfg_oauth_destroy(cfg->oauth); oidc_jwk_list_destroy(cfg->public_keys); oidc_jwk_list_destroy(cfg->private_keys); return APR_SUCCESS; } /* * create a new server config record with defaults */ void *oidc_cfg_server_create(apr_pool_t *pool, server_rec *svr) { oidc_cfg_t *c = apr_pcalloc(pool, sizeof(oidc_cfg_t)); apr_pool_cleanup_register(pool, c, oidc_cfg_server_destroy, oidc_cfg_server_destroy); c->merged = FALSE; c->redirect_uri = NULL; c->default_sso_url = NULL; c->default_slo_url = NULL; c->public_keys = NULL; c->private_keys = NULL; c->provider = oidc_cfg_provider_create(pool); c->oauth = oidc_cfg_oauth_create(pool); oidc_cfg_cache_create_server_config(c); c->metadata_dir = NULL; c->session_type = OIDC_CONFIG_POS_INT_UNSET; c->session_cache_fallback_to_cookie = OIDC_CONFIG_POS_INT_UNSET; c->persistent_session_cookie = OIDC_CONFIG_POS_INT_UNSET; c->store_id_token = OIDC_CONFIG_POS_INT_UNSET; c->session_cookie_chunk_size = OIDC_CONFIG_POS_INT_UNSET; c->http_timeout_long.request_timeout = OIDC_CONFIG_POS_INT_UNSET; c->http_timeout_long.connect_timeout = OIDC_DEFAULT_HTTP_CONNECT_TIMEOUT_LONG; c->http_timeout_long.retries = OIDC_DEFAULT_HTTP_RETRIES_LONG; c->http_timeout_long.retry_interval = OIDC_DEFAULT_HTTP_RETRY_INTERVAL_LONG; c->http_timeout_short.request_timeout = OIDC_CONFIG_POS_INT_UNSET; c->http_timeout_short.connect_timeout = OIDC_DEFAULT_HTTP_CONNECT_TIMEOUT_SHORT; c->http_timeout_short.retries = OIDC_DEFAULT_HTTP_RETRIES_SHORT; c->http_timeout_short.retry_interval = OIDC_DEFAULT_HTTP_RETRY_INTERVAL_SHORT; c->state_timeout = OIDC_CONFIG_POS_INT_UNSET; c->max_number_of_state_cookies = OIDC_CONFIG_POS_INT_UNSET; c->delete_oldest_state_cookies = OIDC_CONFIG_POS_INT_UNSET; c->session_inactivity_timeout = OIDC_CONFIG_POS_INT_UNSET; c->cookie_domain = NULL; c->claim_delimiter = NULL; c->claim_prefix = NULL; c->remote_user_claim.claim_name = NULL; c->remote_user_claim.reg_exp = NULL; c->remote_user_claim.replace = NULL; c->cookie_http_only = OIDC_CONFIG_POS_INT_UNSET; c->cookie_same_site = OIDC_CONFIG_POS_INT_UNSET; c->outgoing_proxy.host_port = NULL; c->outgoing_proxy.username_password = NULL; c->outgoing_proxy.auth_type = OIDC_CONFIG_POS_INT_UNSET; c->crypto_passphrase.secret1 = NULL; c->crypto_passphrase.secret2 = NULL; c->post_preserve_template = NULL; c->post_restore_template = NULL; c->provider_metadata_refresh_interval = OIDC_CONFIG_POS_INT_UNSET; c->info_hook_data = NULL; c->metrics_hook_data = NULL; c->metrics_path = NULL; c->trace_parent = OIDC_CONFIG_POS_INT_UNSET; c->dpop_api_enabled = OIDC_CONFIG_POS_INT_UNSET; c->black_listed_claims = NULL; c->white_listed_claims = NULL; c->filter_claims_expr = NULL; c->state_input_headers = OIDC_CONFIG_POS_INT_UNSET; c->redirect_urls_allowed = NULL; c->ca_bundle_path = NULL; c->logout_x_frame_options = NULL; c->x_forwarded_headers = OIDC_CONFIG_POS_INT_UNSET; c->action_on_userinfo_error = OIDC_CONFIG_POS_INT_UNSET; return c; } /* * merge a new server config with a base one */ void *oidc_cfg_server_merge(apr_pool_t *pool, void *BASE, void *ADD) { oidc_cfg_t *base = (oidc_cfg_t *)BASE; oidc_cfg_t *add = (oidc_cfg_t *)ADD; oidc_cfg_t *c = apr_pcalloc(pool, sizeof(oidc_cfg_t)); apr_pool_cleanup_register(pool, c, oidc_cfg_server_destroy, oidc_cfg_server_destroy); c->provider = oidc_cfg_provider_create(pool); c->oauth = oidc_cfg_oauth_create(pool); c->merged = TRUE; oidc_cfg_provider_merge(pool, c->provider, base->provider, add->provider); oidc_cfg_oauth_merge(pool, c->oauth, base->oauth, add->oauth); oidc_cfg_cache_merge_server_config(c, base, add); c->redirect_uri = add->redirect_uri != NULL ? add->redirect_uri : base->redirect_uri; c->default_sso_url = add->default_sso_url != NULL ? add->default_sso_url : base->default_sso_url; c->default_slo_url = add->default_slo_url != NULL ? add->default_slo_url : base->default_slo_url; c->public_keys = oidc_jwk_list_copy(pool, add->public_keys != NULL ? add->public_keys : base->public_keys); c->private_keys = oidc_jwk_list_copy(pool, add->private_keys != NULL ? add->private_keys : base->private_keys); if (add->http_timeout_long.request_timeout != OIDC_CONFIG_POS_INT_UNSET) { c->http_timeout_long.request_timeout = add->http_timeout_long.request_timeout; c->http_timeout_long.connect_timeout = add->http_timeout_long.connect_timeout; c->http_timeout_long.retries = add->http_timeout_long.retries; c->http_timeout_long.retry_interval = add->http_timeout_long.retry_interval; } else { c->http_timeout_long.request_timeout = base->http_timeout_long.request_timeout; c->http_timeout_long.connect_timeout = base->http_timeout_long.connect_timeout; c->http_timeout_long.retries = base->http_timeout_long.retries; c->http_timeout_long.retry_interval = base->http_timeout_long.retry_interval; } if (add->http_timeout_short.request_timeout != OIDC_CONFIG_POS_INT_UNSET) { c->http_timeout_short.request_timeout = add->http_timeout_short.request_timeout; c->http_timeout_short.connect_timeout = add->http_timeout_short.connect_timeout; c->http_timeout_short.retries = add->http_timeout_short.retries; c->http_timeout_short.retry_interval = add->http_timeout_short.retry_interval; } else { c->http_timeout_short.request_timeout = base->http_timeout_short.request_timeout; c->http_timeout_short.connect_timeout = base->http_timeout_short.connect_timeout; c->http_timeout_short.retries = base->http_timeout_short.retries; c->http_timeout_short.retry_interval = base->http_timeout_short.retry_interval; } c->state_timeout = add->state_timeout != OIDC_CONFIG_POS_INT_UNSET ? add->state_timeout : base->state_timeout; c->max_number_of_state_cookies = add->max_number_of_state_cookies != OIDC_CONFIG_POS_INT_UNSET ? add->max_number_of_state_cookies : base->max_number_of_state_cookies; c->delete_oldest_state_cookies = add->delete_oldest_state_cookies != OIDC_CONFIG_POS_INT_UNSET ? add->delete_oldest_state_cookies : base->delete_oldest_state_cookies; c->session_inactivity_timeout = add->session_inactivity_timeout != OIDC_CONFIG_POS_INT_UNSET ? add->session_inactivity_timeout : base->session_inactivity_timeout; c->metadata_dir = add->metadata_dir != NULL ? add->metadata_dir : base->metadata_dir; c->session_type = add->session_type != OIDC_CONFIG_POS_INT_UNSET ? add->session_type : base->session_type; c->session_cache_fallback_to_cookie = add->session_cache_fallback_to_cookie != OIDC_CONFIG_POS_INT_UNSET ? add->session_cache_fallback_to_cookie : base->session_cache_fallback_to_cookie; c->persistent_session_cookie = add->persistent_session_cookie != OIDC_CONFIG_POS_INT_UNSET ? add->persistent_session_cookie : base->persistent_session_cookie; c->store_id_token = add->store_id_token != OIDC_CONFIG_POS_INT_UNSET ? add->store_id_token : base->store_id_token; c->session_cookie_chunk_size = add->session_cookie_chunk_size != OIDC_CONFIG_POS_INT_UNSET ? add->session_cookie_chunk_size : base->session_cookie_chunk_size; c->cookie_domain = add->cookie_domain != NULL ? add->cookie_domain : base->cookie_domain; c->claim_delimiter = add->claim_delimiter != NULL ? add->claim_delimiter : base->claim_delimiter; c->claim_prefix = add->claim_prefix != NULL ? add->claim_prefix : base->claim_prefix; if (add->remote_user_claim.claim_name != NULL) { c->remote_user_claim.claim_name = add->remote_user_claim.claim_name; c->remote_user_claim.reg_exp = add->remote_user_claim.reg_exp; c->remote_user_claim.replace = add->remote_user_claim.replace; } else { c->remote_user_claim.claim_name = base->remote_user_claim.claim_name; c->remote_user_claim.reg_exp = base->remote_user_claim.reg_exp; c->remote_user_claim.replace = base->remote_user_claim.replace; } c->cookie_http_only = add->cookie_http_only != OIDC_CONFIG_POS_INT_UNSET ? add->cookie_http_only : base->cookie_http_only; c->cookie_same_site = add->cookie_same_site != OIDC_CONFIG_POS_INT_UNSET ? add->cookie_same_site : base->cookie_same_site; if (add->outgoing_proxy.host_port != NULL) { c->outgoing_proxy.host_port = add->outgoing_proxy.host_port; c->outgoing_proxy.username_password = add->outgoing_proxy.username_password; c->outgoing_proxy.auth_type = add->outgoing_proxy.auth_type; } else { c->outgoing_proxy.host_port = base->outgoing_proxy.host_port; c->outgoing_proxy.username_password = base->outgoing_proxy.username_password; c->outgoing_proxy.auth_type = base->outgoing_proxy.auth_type; } if (add->crypto_passphrase.secret1 != NULL) { c->crypto_passphrase.secret1 = add->crypto_passphrase.secret1; c->crypto_passphrase.secret2 = add->crypto_passphrase.secret2; } else { c->crypto_passphrase.secret1 = base->crypto_passphrase.secret1; c->crypto_passphrase.secret2 = base->crypto_passphrase.secret2; } c->post_preserve_template = add->post_preserve_template != NULL ? add->post_preserve_template : base->post_preserve_template; c->post_restore_template = add->post_restore_template != NULL ? add->post_restore_template : base->post_restore_template; c->provider_metadata_refresh_interval = add->provider_metadata_refresh_interval != OIDC_CONFIG_POS_INT_UNSET ? add->provider_metadata_refresh_interval : base->provider_metadata_refresh_interval; c->info_hook_data = add->info_hook_data != NULL ? add->info_hook_data : base->info_hook_data; c->metrics_hook_data = add->metrics_hook_data != NULL ? add->metrics_hook_data : base->metrics_hook_data; c->metrics_path = add->metrics_path != NULL ? add->metrics_path : base->metrics_path; c->trace_parent = add->trace_parent != OIDC_CONFIG_POS_INT_UNSET ? add->trace_parent : base->trace_parent; c->dpop_api_enabled = add->dpop_api_enabled != OIDC_CONFIG_POS_INT_UNSET ? add->dpop_api_enabled : base->dpop_api_enabled; c->black_listed_claims = add->black_listed_claims != NULL ? add->black_listed_claims : base->black_listed_claims; c->white_listed_claims = add->white_listed_claims != NULL ? add->white_listed_claims : base->white_listed_claims; c->filter_claims_expr = add->filter_claims_expr != NULL ? add->filter_claims_expr : base->filter_claims_expr; c->state_input_headers = add->state_input_headers != OIDC_CONFIG_POS_INT_UNSET ? add->state_input_headers : base->state_input_headers; c->redirect_urls_allowed = add->redirect_urls_allowed != NULL ? add->redirect_urls_allowed : base->redirect_urls_allowed; c->ca_bundle_path = add->ca_bundle_path != NULL ? add->ca_bundle_path : base->ca_bundle_path; c->logout_x_frame_options = add->logout_x_frame_options != NULL ? add->logout_x_frame_options : base->logout_x_frame_options; c->x_forwarded_headers = add->x_forwarded_headers != OIDC_CONFIG_POS_INT_UNSET ? add->x_forwarded_headers : base->x_forwarded_headers; c->action_on_userinfo_error = add->action_on_userinfo_error != OIDC_CONFIG_POS_INT_UNSET ? add->action_on_userinfo_error : base->action_on_userinfo_error; return c; } #if OPENSSL_API_COMPAT < 0x10100000L #include #include #endif /* * initialize before the post config handler runs */ void oidc_pre_config_init() { #if OPENSSL_API_COMPAT < 0x10100000L ERR_load_crypto_strings(); OpenSSL_add_all_algorithms(); OpenSSL_add_all_digests(); #else OPENSSL_init_crypto(0, NULL); #endif } oidc_provider_t *oidc_cfg_provider_get(oidc_cfg_t *cfg) { return cfg->provider; } int oidc_cfg_merged_get(oidc_cfg_t *cfg) { return cfg->merged; } static oidc_cache_mutex_t *_oidc_refresh_mutex = NULL; oidc_cache_mutex_t *oidc_cfg_refresh_mutex_get(oidc_cfg_t *cfg) { return _oidc_refresh_mutex; } int oidc_cfg_post_config(oidc_cfg_t *cfg, server_rec *s) { if (cfg->cache.impl == NULL) cfg->cache.impl = &oidc_cache_shm; if (cfg->cache.impl->post_config != NULL) { if (cfg->cache.impl->post_config(s) != OK) return HTTP_INTERNAL_SERVER_ERROR; } if (_oidc_refresh_mutex == NULL) { _oidc_refresh_mutex = oidc_cache_mutex_create(s->process->pool, TRUE); if (oidc_cache_mutex_post_config(s, _oidc_refresh_mutex, "refresh") != TRUE) return HTTP_INTERNAL_SERVER_ERROR; } if (cfg->metrics_hook_data != NULL) { if (oidc_metrics_post_config(s) != TRUE) return HTTP_INTERNAL_SERVER_ERROR; } return OK; } void oidc_cfg_child_init(apr_pool_t *pool, oidc_cfg_t *cfg, server_rec *s) { if (cfg->cache.impl->child_init != NULL) { if (cfg->cache.impl->child_init(pool, s) != APR_SUCCESS) { oidc_serror(s, "cfg->cache->child_init failed"); } } if (_oidc_refresh_mutex != NULL) { if (oidc_cache_mutex_child_init(pool, s, _oidc_refresh_mutex) != APR_SUCCESS) { oidc_serror(s, "oidc_cache_mutex_child_init on refresh mutex failed"); } } if (cfg->metrics_hook_data != NULL) { if (oidc_metrics_child_init(pool, s) != APR_SUCCESS) { oidc_serror(s, "oidc_metrics_cache_child_init failed"); } } } void oidc_cfg_cleanup_child(oidc_cfg_t *cfg, server_rec *s) { if (cfg->cache.impl->destroy != NULL) { if (cfg->cache.impl->destroy(s) != APR_SUCCESS) { oidc_serror(s, "cache destroy function failed"); } } if (_oidc_refresh_mutex != NULL) { if (oidc_cache_mutex_destroy(s, _oidc_refresh_mutex) != TRUE) { oidc_serror(s, "oidc_cache_mutex_destroy on refresh mutex failed"); } // this is a singleton, make sure we call destroy only once _oidc_refresh_mutex = NULL; } if (cfg->metrics_hook_data != NULL) { if (oidc_metrics_cleanup(s) != APR_SUCCESS) { oidc_serror(s, "oidc_metrics_cleanup failed"); } } } mod_auth_openidc-2.4.16.10/src/cfg/cfg.h000066400000000000000000000267401476721736500176150ustar00rootroot00000000000000/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ /*************************************************************************** * Copyright (C) 2017-2025 ZmartZone Holding BV * All rights reserved. * * DISCLAIMER OF WARRANTIES: * * THE SOFTWARE PROVIDED HEREUNDER IS PROVIDED ON AN "AS IS" BASIS, WITHOUT * ANY WARRANTIES OR REPRESENTATIONS EXPRESS, IMPLIED OR STATUTORY; INCLUDING, * WITHOUT LIMITATION, WARRANTIES OF QUALITY, PERFORMANCE, NONINFRINGEMENT, * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. NOR ARE THERE ANY * WARRANTIES CREATED BY A COURSE OR DEALING, COURSE OF PERFORMANCE OR TRADE * USAGE. FURTHERMORE, THERE ARE NO WARRANTIES THAT THE SOFTWARE WILL MEET * YOUR NEEDS OR BE FREE FROM ERRORS, OR THAT THE OPERATION OF THE SOFTWARE * WILL BE UNINTERRUPTED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * @Author: Hans Zandbelt - hans.zandbelt@openidc.com */ #ifndef _MOD_AUTH_OPENIDC_CFG_CFG_H_ #define _MOD_AUTH_OPENIDC_CFG_CFG_H_ #include "const.h" // for the PACKAGE_* defines #include "http.h" #include #include #include #include "cache/cache.h" #define OIDC_CONFIG_POS_INT_UNSET -1 #define OIDCPublicKeyFiles "OIDCPublicKeyFiles" #define OIDCDefaultLoggedOutURL "OIDCDefaultLoggedOutURL" #define OIDCCookieHTTPOnly "OIDCCookieHTTPOnly" #define OIDCCookieSameSite "OIDCCookieSameSite" #define OIDCOutgoingProxy "OIDCOutgoingProxy" #define OIDCClaimDelimiter "OIDCClaimDelimiter" #define OIDCHTTPTimeoutLong "OIDCHTTPTimeoutLong" #define OIDCHTTPTimeoutShort "OIDCHTTPTimeoutShort" #define OIDCStateTimeout "OIDCStateTimeout" #define OIDCStateMaxNumberOfCookies "OIDCStateMaxNumberOfCookies" #define OIDCSessionInactivityTimeout "OIDCSessionInactivityTimeout" #define OIDCMetadataDir "OIDCMetadataDir" #define OIDCSessionCacheFallbackToCookie "OIDCSessionCacheFallbackToCookie" #define OIDCSessionCookieChunkSize "OIDCSessionCookieChunkSize" #define OIDCPreservePostTemplates "OIDCPreservePostTemplates" #define OIDCProviderMetadataRefreshInterval "OIDCProviderMetadataRefreshInterval" #define OIDCBlackListedClaims "OIDCBlackListedClaims" #define OIDCStateInputHeaders "OIDCStateInputHeaders" #define OIDCRedirectURLsAllowed "OIDCRedirectURLsAllowed" #define OIDCCABundlePath "OIDCCABundlePath" #define OIDCLogoutXFrameOptions "OIDCLogoutXFrameOptions" #define OIDCXForwardedHeaders "OIDCXForwardedHeaders" #define OIDCFilterClaimsExpr "OIDCFilterClaimsExpr" #define OIDCTraceParent "OIDCTraceParent" #define OIDCPrivateKeyFiles "OIDCPrivateKeyFiles" #define OIDCRedirectURI "OIDCRedirectURI" #define OIDCDefaultURL "OIDCDefaultURL" #define OIDCCookieDomain "OIDCCookieDomain" #define OIDCClaimPrefix "OIDCClaimPrefix" #define OIDCRemoteUserClaim "OIDCRemoteUserClaim" #define OIDCOAuthRemoteUserClaim "OIDCOAuthRemoteUserClaim" #define OIDCSessionType "OIDCSessionType" #define OIDCInfoHook "OIDCInfoHook" #define OIDCMetricsData "OIDCMetricsData" #define OIDCMetricsPublish "OIDCMetricsPublish" #define OIDCWhiteListedClaims "OIDCWhiteListedClaims" #define OIDCCryptoPassphrase "OIDCCryptoPassphrase" typedef enum { OIDC_STATE_INPUT_HEADERS_NONE = 0, OIDC_STATE_INPUT_HEADERS_USER_AGENT = 1, OIDC_STATE_INPUT_HEADERS_X_FORWARDED_FOR = 2 } oidc_state_input_hdrs_t; typedef enum { OIDC_HDR_NONE = 0, OIDC_HDR_X_FORWARDED_HOST = 1, OIDC_HDR_X_FORWARDED_PORT = 2, OIDC_HDR_X_FORWARDED_PROTO = 4, OIDC_HDR_FORWARDED = 8 } oidc_hdr_x_forwarded_t; typedef enum { OIDC_TRACE_PARENT_OFF = 0, OIDC_TRACE_PARENT_PROPAGATE = 1, OIDC_TRACE_PARENT_GENERATE = 2 } oidc_trace_parent_t; typedef enum { OIDC_SAMESITE_COOKIE_DISABLED = 0, OIDC_SAMESITE_COOKIE_NONE = 1, OIDC_SAMESITE_COOKIE_LAX = 2, OIDC_SAMESITE_COOKIE_STRICT = 3 } oidc_samesite_cookie_t; #define OIDC_ERROR_ENVVAR "OIDC_ERROR" #define OIDC_ERROR_DESC_ENVVAR "OIDC_ERROR_DESC" #define OIDC_HOOK_INFO_TIMESTAMP "iat" #define OIDC_HOOK_INFO_ACCES_TOKEN "access_token" #define OIDC_HOOK_INFO_ACCES_TOKEN_TYPE "access_token_type" #define OIDC_HOOK_INFO_ACCES_TOKEN_EXP "access_token_expires" #define OIDC_HOOK_INFO_ID_TOKEN_HINT "id_token_hint" #define OIDC_HOOK_INFO_ID_TOKEN "id_token" #define OIDC_HOOK_INFO_USER_INFO "userinfo" #define OIDC_HOOK_INFO_SESSION "session" #define OIDC_HOOK_INFO_SESSION_STATE "state" #define OIDC_HOOK_INFO_SESSION_UUID "uuid" #define OIDC_HOOK_INFO_SESSION_EXP "exp" #define OIDC_HOOK_INFO_SESSION_TIMEOUT "timeout" #define OIDC_HOOK_INFO_SESSION_REMOTE_USER "remote_user" #define OIDC_HOOK_INFO_REFRESH_TOKEN "refresh_token" #define OIDC_HTML_ERROR_TEMPLATE_DEPRECATED "deprecated" typedef struct oidc_apr_expr_t { #if HAVE_APACHE_24 ap_expr_info_t *expr; #endif char *str; } oidc_apr_expr_t; typedef struct oidc_crypto_passphrase_t { char *secret1; char *secret2; } oidc_crypto_passphrase_t; typedef struct oidc_remote_user_claim_t { const char *claim_name; const char *reg_exp; const char *replace; } oidc_remote_user_claim_t; typedef enum { /* pass userinfo as individual claims in headers (default) */ OIDC_PASS_USERINFO_AS_CLAIMS = 1, /* pass userinfo payload as JSON object in header */ OIDC_PASS_USERINFO_AS_JSON_OBJECT = 2, /* pass userinfo as a JWT in header (when returned as a JWT) */ OIDC_PASS_USERINFO_AS_JWT = 4, /* pass as re-signed JWT including id_token claims */ OIDC_PASS_USERINFO_AS_SIGNED_JWT = 8 } oidc_pass_userinfo_enum_t; typedef struct oidc_pass_user_info_as_t { oidc_pass_userinfo_enum_t type; char *name; } oidc_pass_user_info_as_t; /* actions to be taken on access token / userinfo refresh error */ typedef enum { OIDC_ON_ERROR_502 = 0, OIDC_ON_ERROR_LOGOUT = 1, OIDC_ON_ERROR_AUTH = 2 } oidc_on_error_action_t; #define OIDC_CFG_OPTIONS_SIZE(options) sizeof(options) / sizeof(oidc_cfg_option_t) typedef struct oidc_provider_t oidc_provider_t; typedef struct oidc_cfg_t oidc_cfg_t; void oidc_cfg_x_forwarded_headers_check(request_rec *r, oidc_hdr_x_forwarded_t x_forwarded_headers); const char *oidc_cfg_remote_user_claim_name_get(oidc_cfg_t *cfg); oidc_provider_t *oidc_cfg_provider_get(oidc_cfg_t *); int oidc_cfg_merged_get(oidc_cfg_t *cfg); void oidc_pre_config_init(); void *oidc_cfg_server_create(apr_pool_t *pool, server_rec *svr); void *oidc_cfg_server_merge(apr_pool_t *pool, void *BASE, void *ADD); int oidc_cfg_post_config(oidc_cfg_t *cfg, server_rec *s); void oidc_cfg_child_init(apr_pool_t *pool, oidc_cfg_t *cfg, server_rec *s); void oidc_cfg_cleanup_child(oidc_cfg_t *cfg, server_rec *s); const char *oidc_cfg_string_list_add(apr_pool_t *pool, apr_array_header_t **list, const char *arg); #define OIDC_CFG_MEMBER_FUNC_NAME(member, type, method) oidc_##type##_##member##_##method #define OIDC_CFG_MEMBER_FUNC_GET_DECL(member, type) type OIDC_CFG_MEMBER_FUNC_NAME(member, cfg, get)(oidc_cfg_t * cfg); #define OIDC_CMD_MEMBER_FUNC_DECL(member, ...) \ const char *OIDC_CFG_MEMBER_FUNC_NAME(member, cmd, set)(cmd_parms *, void *, ##__VA_ARGS__); #define OIDC_CFG_MEMBER_FUNCS_DECL(member, type, ...) \ OIDC_CMD_MEMBER_FUNC_DECL(member, const char *, ##__VA_ARGS__); \ OIDC_CFG_MEMBER_FUNC_GET_DECL(member, type) OIDC_CFG_MEMBER_FUNCS_DECL(delete_oldest_state_cookies, int) OIDC_CFG_MEMBER_FUNCS_DECL(action_on_userinfo_error, oidc_on_error_action_t) OIDC_CFG_MEMBER_FUNCS_DECL(crypto_passphrase_secret1, const char *) OIDC_CFG_MEMBER_FUNCS_DECL(refresh_mutex, oidc_cache_mutex_t *) OIDC_CFG_MEMBER_FUNCS_DECL(store_id_token, int) OIDC_CFG_MEMBER_FUNCS_DECL(post_preserve_template, const char *) OIDC_CFG_MEMBER_FUNCS_DECL(post_restore_template, const char *) OIDC_CFG_MEMBER_FUNCS_DECL(persistent_session_cookie, int) OIDC_CFG_MEMBER_FUNCS_DECL(public_keys, const apr_array_header_t *) OIDC_CFG_MEMBER_FUNCS_DECL(private_keys, const apr_array_header_t *) OIDC_CFG_MEMBER_FUNCS_DECL(redirect_uri, const char *) OIDC_CFG_MEMBER_FUNCS_DECL(default_sso_url, const char *) OIDC_CFG_MEMBER_FUNCS_DECL(default_slo_url, const char *) OIDC_CFG_MEMBER_FUNCS_DECL(cookie_domain, const char *) OIDC_CFG_MEMBER_FUNCS_DECL(cookie_http_only, int) OIDC_CFG_MEMBER_FUNCS_DECL(cookie_same_site, oidc_samesite_cookie_t) OIDC_CFG_MEMBER_FUNCS_DECL(claim_delimiter, const char *) OIDC_CFG_MEMBER_FUNCS_DECL(claim_prefix, const char *) OIDC_CFG_MEMBER_FUNCS_DECL(state_timeout, int) OIDC_CFG_MEMBER_FUNCS_DECL(session_inactivity_timeout, int) OIDC_CFG_MEMBER_FUNCS_DECL(metadata_dir, const char *) OIDC_CFG_MEMBER_FUNCS_DECL(session_type, int) OIDC_CFG_MEMBER_FUNCS_DECL(session_cache_fallback_to_cookie, int) OIDC_CFG_MEMBER_FUNCS_DECL(session_cookie_chunk_size, int) OIDC_CFG_MEMBER_FUNCS_DECL(html_error_template, const char *) OIDC_CFG_MEMBER_FUNCS_DECL(provider_metadata_refresh_interval, int) OIDC_CFG_MEMBER_FUNCS_DECL(info_hook_data, apr_hash_t *) OIDC_CFG_MEMBER_FUNCS_DECL(metrics_hook_data, apr_hash_t *) OIDC_CFG_MEMBER_FUNCS_DECL(metrics_path, const char *) OIDC_CFG_MEMBER_FUNCS_DECL(trace_parent, oidc_trace_parent_t) OIDC_CFG_MEMBER_FUNCS_DECL(black_listed_claims, apr_hash_t *) OIDC_CFG_MEMBER_FUNCS_DECL(white_listed_claims, apr_hash_t *) OIDC_CFG_MEMBER_FUNCS_DECL(state_input_headers, oidc_state_input_hdrs_t) OIDC_CFG_MEMBER_FUNCS_DECL(redirect_urls_allowed, apr_hash_t *) OIDC_CFG_MEMBER_FUNCS_DECL(ca_bundle_path, const char *) OIDC_CFG_MEMBER_FUNCS_DECL(logout_x_frame_options, const char *) OIDC_CFG_MEMBER_FUNCS_DECL(x_forwarded_headers, oidc_hdr_x_forwarded_t) OIDC_CFG_MEMBER_FUNCS_DECL(dpop_api_enabled, int) // 2 args OIDC_CFG_MEMBER_FUNCS_DECL(post_preserve_templates, const char *, const char *) OIDC_CFG_MEMBER_FUNCS_DECL(crypto_passphrase, const oidc_crypto_passphrase_t *, const char *) OIDC_CFG_MEMBER_FUNCS_DECL(max_number_of_state_cookies, int, const char *) // 3 args OIDC_CFG_MEMBER_FUNCS_DECL(remote_user_claim, const oidc_remote_user_claim_t *, const char *, const char *) OIDC_CFG_MEMBER_FUNCS_DECL(outgoing_proxy, const oidc_http_outgoing_proxy_t *, const char *, const char *) OIDC_CFG_MEMBER_FUNCS_DECL(http_timeout_short, oidc_http_timeout_t *, const char *, const char *) OIDC_CFG_MEMBER_FUNCS_DECL(http_timeout_long, oidc_http_timeout_t *, const char *, const char *) // ifdefs #ifdef USE_LIBJQ OIDC_CMD_MEMBER_FUNC_DECL(filter_claims_expr, const char *arg) #endif OIDC_CFG_MEMBER_FUNC_GET_DECL(filter_claims_expr, oidc_apr_expr_t *) extern const command_rec oidc_cfg_cmds[]; #ifdef APLOG_USE_MODULE APLOG_USE_MODULE(auth_openidc); #else extern module AP_MODULE_DECLARE_DATA auth_openidc_module; #endif #endif // _MOD_AUTH_OPENIDC_CFG_CFG_H_ mod_auth_openidc-2.4.16.10/src/cfg/cfg_int.h000066400000000000000000000256271476721736500204720ustar00rootroot00000000000000/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ /*************************************************************************** * Copyright (C) 2017-2025 ZmartZone Holding BV * All rights reserved. * * DISCLAIMER OF WARRANTIES: * * THE SOFTWARE PROVIDED HEREUNDER IS PROVIDED ON AN "AS IS" BASIS, WITHOUT * ANY WARRANTIES OR REPRESENTATIONS EXPRESS, IMPLIED OR STATUTORY; INCLUDING, * WITHOUT LIMITATION, WARRANTIES OF QUALITY, PERFORMANCE, NONINFRINGEMENT, * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. NOR ARE THERE ANY * WARRANTIES CREATED BY A COURSE OR DEALING, COURSE OF PERFORMANCE OR TRADE * USAGE. FURTHERMORE, THERE ARE NO WARRANTIES THAT THE SOFTWARE WILL MEET * YOUR NEEDS OR BE FREE FROM ERRORS, OR THAT THE OPERATION OF THE SOFTWARE * WILL BE UNINTERRUPTED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * @Author: Hans Zandbelt - hans.zandbelt@openidc.com */ #ifndef _MOD_AUTH_OPENIDC_CFG_CFG_INT_H_ #define _MOD_AUTH_OPENIDC_CFG_CFG_INT_H_ #include "cfg/cfg.h" #include "cfg/oauth.h" #include "cfg/provider.h" #include struct oidc_cfg_cache_t { /* pointer to cache functions */ oidc_cache_t *impl; /* implementation specific config context */ void *cfg; /* encrypt the stored values */ int encrypt; /* * file */ /* cache_type = shm: size of the shared memory segment (cq. max number of cached entries) */ int shm_size_max; /* cache_type = shm: maximum size in bytes of a cache entry */ int shm_entry_size_max; /* * shm */ /* cache_type = file: directory that holds the cache files (if not set, we'll try and use an OS defined one like * "/tmp" */ char *file_dir; /* cache_type = file: clean interval */ int file_clean_interval; /* * memcache */ #ifdef USE_MEMCACHE /* cache_type= memcache: list of memcache host/port servers to use */ char *memcache_servers; /* cache_type= memcache: minimum number of connections to each memcache server per process*/ int memcache_min; /* cache_type= memcache: soft maximum number of connections to each memcache server per process */ int memcache_smax; /* cache_type= memcache: hard maximum number of connections to each memcache server per process */ int memcache_hmax; /* cache_type= memcache: maximum time in microseconds a connection to a memcache server can be idle before being * closed */ int memcache_ttl; #endif /* * redis */ #ifdef USE_LIBHIREDIS /* cache_type= redis: Redis host/port server to use */ char *redis_server; char *redis_username; char *redis_password; int redis_database; int redis_connect_timeout; int redis_keepalive; int redis_timeout; #endif }; struct oidc_cfg_t { /* the redirect URI as configured with the OpenID Connect OP's that we talk to */ char *redirect_uri; /* secret key(s) used for encryption */ oidc_crypto_passphrase_t crypto_passphrase; /* (optional) default URL for 3rd-party initiated SSO */ char *default_sso_url; /* (optional) default URL to go to after logout */ char *default_slo_url; /* Javascript template to preserve POST data */ char *post_preserve_template; /* Javascript template to restore POST data */ char *post_restore_template; /* pointer to the cache implementation */ struct oidc_cfg_cache_t cache; /* a pointer to the (single) provider that we connect to */ /* NB: if metadata_dir is set, these settings will function as defaults for the metadata read from there) */ oidc_provider_t *provider; /* a pointer to the oauth server settings */ oidc_oauth_t *oauth; /* type of session management/storage */ int session_type; int session_cache_fallback_to_cookie; /* session cookie or persistent cookie */ int persistent_session_cookie; /* store the id_token in the session */ int store_id_token; /* session cookie chunk size */ int session_cookie_chunk_size; char *cookie_domain; int cookie_http_only; int cookie_same_site; int state_timeout; int max_number_of_state_cookies; int delete_oldest_state_cookies; int state_input_headers; int session_inactivity_timeout; int provider_metadata_refresh_interval; oidc_http_timeout_t http_timeout_long; oidc_http_timeout_t http_timeout_short; oidc_http_outgoing_proxy_t outgoing_proxy; char *claim_delimiter; char *claim_prefix; oidc_remote_user_claim_t remote_user_claim; /* public keys in JWK format, used by parters for encrypting JWTs sent to us */ apr_array_header_t *public_keys; /* private keys in JWK format used for decrypting encrypted JWTs sent to us */ apr_array_header_t *private_keys; apr_hash_t *black_listed_claims; apr_hash_t *white_listed_claims; oidc_apr_expr_t *filter_claims_expr; apr_hash_t *info_hook_data; apr_hash_t *redirect_urls_allowed; char *ca_bundle_path; char *logout_x_frame_options; int x_forwarded_headers; int action_on_userinfo_error; int trace_parent; apr_hash_t *metrics_hook_data; char *metrics_path; int dpop_api_enabled; /* directory that holds the provider & client metadata files */ char *metadata_dir; /* indicates whether this is a derived config, merged from a base one */ unsigned int merged; }; #define OIDC_CONFIG_DIR_RV(cmd, rv) \ rv != NULL ? apr_psprintf(cmd->pool, "Invalid value for directive '%s': %s", cmd->directive->directive, rv) \ : NULL #define OIDC_CFG_MEMBER_FUNC_GET(member, type) \ type oidc_cfg_##member##_get(oidc_cfg_t *cfg) { \ return cfg->member; \ } #define OIDC_CFG_MEMBER_FUNC_SET(member, valid) \ const char *oidc_cmd_##member##_set(cmd_parms *cmd, void *ptr, const char *arg) { \ oidc_cfg_t *cfg = \ (oidc_cfg_t *)ap_get_module_config(cmd->server->module_config, &auth_openidc_module); \ const char *rv = valid; \ if (rv == NULL) \ cfg->member = apr_pstrdup(cmd->pool, arg); \ return OIDC_CONFIG_DIR_RV(cmd, rv); \ } #define OIDC_CFG_MEMBER_FUNCS_TYPE(member, type, valid) \ OIDC_CFG_MEMBER_FUNC_SET(member, valid) \ \ OIDC_CFG_MEMBER_FUNC_GET(member, type) #define OIDC_CFG_MEMBER_FUNC_TYPE_GET(member, type, def_val) \ type oidc_cfg_##member##_get(oidc_cfg_t *cfg) { \ if (cfg->member == OIDC_CONFIG_POS_INT_UNSET) \ return def_val; \ return cfg->member; \ } #define OIDC_CFG_MEMBER_FUNCS_INT_EXT(member, parse, def_val) \ const char *oidc_cmd_##member##_set(cmd_parms *cmd, void *ptr, const char *arg) { \ oidc_cfg_t *cfg = \ (oidc_cfg_t *)ap_get_module_config(cmd->server->module_config, &auth_openidc_module); \ int v = -1; \ const char *rv = parse; \ if (rv == NULL) \ cfg->member = v; \ return OIDC_CONFIG_DIR_RV(cmd, rv); \ } \ \ OIDC_CFG_MEMBER_FUNC_TYPE_GET(member, int, def_val) #define OIDC_CFG_MEMBER_FUNCS_INT(member, min, max, def_val) \ OIDC_CFG_MEMBER_FUNCS_INT_EXT(member, oidc_cfg_parse_int_min_max(cmd->pool, arg, &v, min, max), def_val) #define OIDC_CFG_MEMBER_FUNCS_BOOL(member, def_val) \ OIDC_CFG_MEMBER_FUNCS_INT_EXT(member, oidc_cfg_parse_boolean(cmd->pool, arg, &v), def_val) #define OIDC_CFG_MEMBER_FUNCS_STR_DEF(member, valid, def_val) \ OIDC_CFG_MEMBER_FUNC_SET(member, valid) \ \ const char *oidc_cfg_##member##_get(oidc_cfg_t *cfg) { \ return (cfg->member != NULL) ? cfg->member : def_val; \ } #define OIDC_CFG_MEMBER_FUNCS_URL(member) \ OIDC_CFG_MEMBER_FUNCS_TYPE(member, const char *, oidc_valid_http_url(cmd->pool, arg)) #endif // _MOD_AUTH_OPENIDC_CFG_CFG_INT_H_ mod_auth_openidc-2.4.16.10/src/cfg/cmds.c000066400000000000000000000713071476721736500177760ustar00rootroot00000000000000/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ /*************************************************************************** * Copyright (C) 2017-2025 ZmartZone Holding BV * All rights reserved. * * DISCLAIMER OF WARRANTIES: * * THE SOFTWARE PROVIDED HEREUNDER IS PROVIDED ON AN "AS IS" BASIS, WITHOUT * ANY WARRANTIES OR REPRESENTATIONS EXPRESS, IMPLIED OR STATUTORY; INCLUDING, * WITHOUT LIMITATION, WARRANTIES OF QUALITY, PERFORMANCE, NONINFRINGEMENT, * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. NOR ARE THERE ANY * WARRANTIES CREATED BY A COURSE OR DEALING, COURSE OF PERFORMANCE OR TRADE * USAGE. FURTHERMORE, THERE ARE NO WARRANTIES THAT THE SOFTWARE WILL MEET * YOUR NEEDS OR BE FREE FROM ERRORS, OR THAT THE OPERATION OF THE SOFTWARE * WILL BE UNINTERRUPTED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * @Author: Hans Zandbelt - hans.zandbelt@openidc.com */ #include "cfg/cache.h" #include "cfg/cfg.h" #include "cfg/dir.h" #include "cfg/oauth.h" #include "cfg/provider.h" // clang-format off #define OIDC_CFG_CMD_DEF(take, prefix, cmd, member, scope, desc) \ take(cmd, oidc_cmd##prefix##_##member##_set, NULL, scope, desc) #define OIDC_CFG_CMD(take, cmd, member, desc) \ OIDC_CFG_CMD_DEF(take, , cmd, member, RSRC_CONF, desc) #define OIDC_CFG_CMD_PROVIDER(take, cmd, member, desc) \ OIDC_CFG_CMD_DEF(take, _provider, cmd, member, RSRC_CONF, desc) #define OIDC_CFG_CMD_OAUTH(take, cmd, member, desc) \ OIDC_CFG_CMD_DEF(take, _oauth, cmd, member, RSRC_CONF, desc) #define OIDC_CFG_CMD_DIR(take, cmd, member, desc) \ OIDC_CFG_CMD_DEF(take, _dir, cmd, member, RSRC_CONF | ACCESS_CONF | OR_AUTHCFG, desc) const command_rec oidc_cfg_cmds[] = { // base OIDC_CFG_CMD( AP_INIT_ITERATE, OIDCPublicKeyFiles, public_keys, "The fully qualified names of the files that contain the RSA/EC public keys or X.509 certificates that contains the RSA/EC public keys that can be used for signature validation or encryption by the OP."), OIDC_CFG_CMD( AP_INIT_ITERATE, OIDCPrivateKeyFiles, private_keys, "The AP_INIT_TAKE1,qualified names of the files that contain the RSA/EC private keys that can be used to decrypt content sent to us by the OP."), OIDC_CFG_CMD( AP_INIT_TAKE1, OIDCRedirectURI, redirect_uri, "Define the Redirect URI (e.g.: https://localhost:9031/protected/example/)"), OIDC_CFG_CMD( AP_INIT_TAKE1, OIDCDefaultURL, default_sso_url, "Defines the default URL where the user is directed to in case of 3rd-party initiated SSO."), OIDC_CFG_CMD( AP_INIT_TAKE1, OIDCDefaultLoggedOutURL, default_slo_url, "Defines the default URL where the user is directed to after logout."), OIDC_CFG_CMD( AP_INIT_TAKE1, OIDCCookieDomain, cookie_domain, "Specify domain element for OIDC session cookie."), OIDC_CFG_CMD( AP_INIT_TAKE1, OIDCCookieHTTPOnly, cookie_http_only, "Defines whether or not the cookie httponly flag is set on cookies."), OIDC_CFG_CMD( AP_INIT_TAKE1, OIDCCookieSameSite, cookie_same_site, "Defines whether or not the cookie Same-Site flag is set on cookies."), OIDC_CFG_CMD( AP_INIT_TAKE123, OIDCOutgoingProxy, outgoing_proxy, "Specify an outgoing proxy for your network ([:]."), OIDC_CFG_CMD( AP_INIT_TAKE12, OIDCCryptoPassphrase, crypto_passphrase, "Passphrase used for AES crypto on cookies and state."), OIDC_CFG_CMD( AP_INIT_TAKE1, OIDCClaimDelimiter, claim_delimiter, "The delimiter to use when setting multi-valued claims in the HTTP headers."), OIDC_CFG_CMD( AP_INIT_RAW_ARGS, OIDCClaimPrefix, claim_prefix, "The prefix to use when setting claims in the HTTP headers."), OIDC_CFG_CMD( AP_INIT_TAKE123, OIDCRemoteUserClaim, remote_user_claim, "The claim that is used when setting the REMOTE_USER variable for OpenID Connect protected paths."), OIDC_CFG_CMD( AP_INIT_TAKE123, OIDCHTTPTimeoutLong, http_timeout_long, "Timeout for long duration HTTP calls (default)."), OIDC_CFG_CMD( AP_INIT_TAKE123, OIDCHTTPTimeoutShort, http_timeout_short, "Timeout for short duration HTTP calls (registry/discovery)."), OIDC_CFG_CMD( AP_INIT_TAKE1, OIDCStateTimeout, state_timeout, "Time to live in seconds for state parameter (cq. interval in which the authorization request and the corresponding response need to be completed)."), OIDC_CFG_CMD( AP_INIT_TAKE12, OIDCStateMaxNumberOfCookies, max_number_of_state_cookies, "Maximum number of parallel state cookies i.e. outstanding authorization requests and whether to delete the oldest cookie(s)."), OIDC_CFG_CMD( AP_INIT_TAKE1, OIDCSessionInactivityTimeout, session_inactivity_timeout, "Inactivity interval after which the session is invalidated when no interaction has occurred."), OIDC_CFG_CMD( AP_INIT_TAKE1, OIDCMetadataDir, metadata_dir, "Directory that contains provider and client metadata files."), OIDC_CFG_CMD( AP_INIT_TAKE1, OIDCSessionType, session_type, "OpenID Connect session storage type (Apache 2.0/2.2 only). Must be one of \"server-cache\" or \"client-cookie\" with an optional suffix \":persistent\"."), OIDC_CFG_CMD( AP_INIT_TAKE1, OIDCSessionCacheFallbackToCookie, session_cache_fallback_to_cookie, "Fallback to client-side cookie session storage when server side cache fails."), OIDC_CFG_CMD( AP_INIT_TAKE1, OIDCSessionCookieChunkSize, session_cookie_chunk_size, "Chunk size for client-cookie session storage type in bytes. Defaults to 4k. Set 0 to suppress chunking."), OIDC_CFG_CMD( AP_INIT_TAKE2, OIDCPreservePostTemplates, post_preserve_templates, "Name of POST preserve and restore templates:" "1) preserve: needs to contain two \"%s\" characters, the first for the JSON POST data, the second for the URL to redirect to." "2) restore: needs to contain one \"%s\", which contains the (original) URL to POST the restored data to" ), OIDC_CFG_CMD( AP_INIT_TAKE1, OIDCProviderMetadataRefreshInterval, provider_metadata_refresh_interval, "Provider metadata refresh interval in seconds."), OIDC_CFG_CMD( AP_INIT_ITERATE, OIDCInfoHook, info_hook_data, "The data that will be returned from the info hook."), OIDC_CFG_CMD( AP_INIT_ITERATE, OIDCMetricsData, metrics_hook_data, "The data that will be returned from the metrics hook."), OIDC_CFG_CMD( AP_INIT_TAKE1, OIDCMetricsPublish, metrics_path, "Define the URL where the metrics will be published (e.g.: /metrics)"), OIDC_CFG_CMD( AP_INIT_TAKE1, OIDCTraceParent, trace_parent, "Propagate or generate a traceparent header"), OIDC_CFG_CMD( AP_INIT_ITERATE, OIDCBlackListedClaims, black_listed_claims, "Specify claims that should be removed from the userinfo and/or id_token before storing them in the session."), OIDC_CFG_CMD( AP_INIT_ITERATE, OIDCWhiteListedClaims, white_listed_claims, "Specify claims from the userinfo and/or id_token that should be stored in the session (all other claims will be discarded)."), OIDC_CFG_CMD( AP_INIT_TAKE1, OIDCStateInputHeaders, state_input_headers, "Specify header name which is used as the input for calculating the fingerprint of the state during authentication; must be one of \"none\", \"user-agent\", \"x-forwarded-for\" or \"both\" (default)."), OIDC_CFG_CMD( AP_INIT_ITERATE, OIDCRedirectURLsAllowed, redirect_urls_allowed, "Specify one or more regular expressions that define URLs allowed for post logout and other redirects."), OIDC_CFG_CMD( AP_INIT_TAKE1, OIDCCABundlePath, ca_bundle_path, "Sets the path to the CA bundle to be used by cURL."), OIDC_CFG_CMD( AP_INIT_TAKE1, OIDCLogoutXFrameOptions, logout_x_frame_options, "Sets the value of the X-Frame-Options header on front channel logout."), OIDC_CFG_CMD( AP_INIT_ITERATE, OIDCXForwardedHeaders, x_forwarded_headers, "Sets the value of the interpreted X-Forwarded-* headers."), #ifdef USE_LIBJQ OIDC_CFG_CMD( AP_INIT_TAKE1, OIDCFilterClaimsExpr, filter_claims_expr, "Sets the JQ expression to be executed on the claims from id_token/userinfo endpoint before storing them in the session"), #endif // cache #ifdef USE_MEMCACHE #define _OIDC_CMDS_CACHE_TYPE_MEMCACHE "'memcache'|" #else #define _OIDC_CMDS_CACHE_TYPE_MEMCACHE #endif #ifdef USE_LIBHIREDIS #define _OIDC_CMDS_CACHE_TYPE_REDIS "'redis'|" #else #define _OIDC_CMDS_CACHE_TYPE_REDIS #endif AP_INIT_TAKE1( OIDCCacheType, oidc_cmd_cache_type_set, NULL, RSRC_CONF, "cache backend must be one of ['shm'|" _OIDC_CMDS_CACHE_TYPE_MEMCACHE _OIDC_CMDS_CACHE_TYPE_REDIS "'file']."), OIDC_CFG_CMD( AP_INIT_TAKE1, OIDCCacheEncrypt, cache_encrypt, "Encrypt the data in the cache backend (On or Off)"), OIDC_CFG_CMD( AP_INIT_TAKE1, OIDCCacheShmMax, cache_shm_size_max, "Maximum number of cache entries to use for \"shm\" caching."), OIDC_CFG_CMD( AP_INIT_TAKE1, OIDCCacheShmEntrySizeMax, cache_shm_entry_size_max, "Maximum size of a single cache entry used for \"shm\" caching."), OIDC_CFG_CMD( AP_INIT_TAKE1, OIDCCacheDir, cache_file_dir, "Directory used for file-based caching."), OIDC_CFG_CMD( AP_INIT_TAKE1, OIDCCacheFileCleanInterval, cache_file_clean_interval, "Cache file clean interval in seconds."), #ifdef USE_MEMCACHE OIDC_CFG_CMD( AP_INIT_TAKE1, OIDCMemCacheServers, cache_memcache_servers, "Memcache servers used for caching (space separated list of [:] tuples)"), OIDC_CFG_CMD( AP_INIT_TAKE1, OIDCMemCacheConnectionsMin, cache_memcache_min, "Minimum number of connections to each Memcache server per process"), OIDC_CFG_CMD( AP_INIT_TAKE1, OIDCMemCacheConnectionsSMax, cache_memcache_smax, "Soft maximum number of connections to each Memcache server per process"), OIDC_CFG_CMD( AP_INIT_TAKE1, OIDCMemCacheConnectionsHMax, cache_memcache_hmax, "Hard maximum number of connections to each Memcache server per process"), OIDC_CFG_CMD( AP_INIT_TAKE1, OIDCMemCacheConnectionsTTL, cache_memcache_ttl, "Maximum time in seconds a connection to a Memcache server can be idle before being closed"), #endif #ifdef USE_LIBHIREDIS OIDC_CFG_CMD( AP_INIT_TAKE1, OIDCRedisCacheServer, cache_redis_server, "Redis server used for caching ([:])"), OIDC_CFG_CMD( AP_INIT_TAKE1, OIDCRedisCacheUsername, cache_redis_username, "Username for authentication to the Redis server."), OIDC_CFG_CMD( AP_INIT_TAKE1, OIDCRedisCachePassword, cache_redis_password, "Password for authentication to the Redis server."), OIDC_CFG_CMD( AP_INIT_TAKE1, OIDCRedisCacheDatabase, cache_redis_database, "Database to select on the Redis server."), OIDC_CFG_CMD( AP_INIT_TAKE12, OIDCRedisCacheConnectTimeout, cache_redis_connect_timeout, "Timeout for connecting to the Redis server."), OIDC_CFG_CMD( AP_INIT_TAKE1, OIDCRedisCacheTimeout, cache_redis_timeout, "Timeout waiting for a response of the Redis server."), #endif // provider OIDC_CFG_CMD_PROVIDER( AP_INIT_TAKE1, OIDCProviderMetadataURL, metadata_url, "OpenID Connect OP configuration metadata URL."), OIDC_CFG_CMD_PROVIDER( AP_INIT_TAKE1, OIDCProviderIssuer, issuer, "OpenID Connect OP issuer identifier."), OIDC_CFG_CMD_PROVIDER( AP_INIT_TAKE1, OIDCProviderAuthorizationEndpoint, authorization_endpoint_url, "Define the OpenID OP Authorization Endpoint URL (e.g.: https://localhost:9031/as/authorization.oauth2)"), OIDC_CFG_CMD_PROVIDER( AP_INIT_TAKE1, OIDCProviderTokenEndpoint, token_endpoint_url, "Define the OpenID OP Token Endpoint URL (e.g.: https://localhost:9031/as/token.oauth2)"), OIDC_CFG_CMD_PROVIDER( AP_INIT_TAKE1, OIDCProviderTokenEndpointAuth, token_endpoint_auth, "Specify an authentication method for the OpenID OP Token Endpoint (e.g.: client_secret_basic)"), OIDC_CFG_CMD_PROVIDER( AP_INIT_TAKE1, OIDCProviderTokenEndpointParams, token_endpoint_params, "Define extra parameters that will be posted to the OpenID OP Token Endpoint (e.g.: param1=value1¶m2=value2, all urlencoded)."), OIDC_CFG_CMD_PROVIDER( AP_INIT_TAKE1, OIDCProviderRegistrationEndpointJson, registration_endpoint_json, "Define a JSON object with parameters that will be merged into the client registration request to the OpenID OP Registration Endpoint (e.g.: { \"request_uris\" : [ \"https://example.com/uri\"] })."), OIDC_CFG_CMD_PROVIDER( AP_INIT_TAKE1, OIDCProviderUserInfoEndpoint, userinfo_endpoint_url, "Define the OpenID OP UserInfo Endpoint URL (e.g.: https://localhost:9031/idp/userinfo.openid)"), OIDC_CFG_CMD_PROVIDER( AP_INIT_RAW_ARGS, OIDCProviderRevocationEndpoint, revocation_endpoint_url, "Define the RFC 7009 Token Revocation Endpoint URL (e.g.: https://localhost:9031/as/revoke_token.oauth2)"), OIDC_CFG_CMD_PROVIDER( AP_INIT_TAKE1, OIDCProviderPushedAuthorizationRequestEndpoint, pushed_authorization_request_endpoint_url, "Define the OAuth 2.0 Pushed Authorization Endpoint URL (e.g.: https://localhost:9031/as/par.oauth2)"), OIDC_CFG_CMD_PROVIDER( AP_INIT_TAKE1, OIDCProviderCheckSessionIFrame, check_session_iframe, "Define the OpenID OP Check Session iFrame URL."), OIDC_CFG_CMD_PROVIDER( AP_INIT_TAKE1, OIDCProviderEndSessionEndpoint, end_session_endpoint, "Define the OpenID OP End Session Endpoint URL."), OIDC_CFG_CMD_PROVIDER( AP_INIT_TAKE1, OIDCProviderBackChannelLogoutSupported, backchannel_logout_supported, "Define whether the OP supports OpenID Connect Back Channel Logout."), OIDC_CFG_CMD_PROVIDER( AP_INIT_TAKE1, OIDCProviderJwksUri, jwks_uri, "Define the OpenID OP JWKS URL (e.g.: https://localhost:9031/pf/JWKS)"), OIDC_CFG_CMD_PROVIDER( AP_INIT_TAKE2, OIDCProviderSignedJwksUri, signed_jwks_uri, "Define the OpenID Connect OP Signed JWKS URI and a JWK that can be used to verify the data on that URL."), OIDC_CFG_CMD_PROVIDER( AP_INIT_ITERATE, OIDCProviderVerifyCertFiles, verify_public_keys, "The fully qualified names of the files that contain the X.509 certificates that contains the RSA/EC public keys that can be used for ID token validation."), OIDC_CFG_CMD_PROVIDER( AP_INIT_TAKE1, OIDCResponseType, response_type, "The response type (or OpenID Connect Flow) used; must be one of \"code\", \"id_token\", \"id_token token\", \"code id_token\", \"code token\" or \"code id_token token\" (serves as default value for discovered OPs too)"), OIDC_CFG_CMD_PROVIDER( AP_INIT_TAKE1, OIDCResponseMode, response_mode, "The response mode used; must be one of \"fragment\", \"query\" or \"form_post\" (serves as default value for discovered OPs too)"), OIDC_CFG_CMD_PROVIDER( AP_INIT_TAKE1, OIDCClientJwksUri, client_jwks_uri, "Define the Client JWKS URL (e.g.: https://localhost/protected/?jwks=rsa)"), OIDC_CFG_CMD_PROVIDER( AP_INIT_TAKE1, OIDCIDTokenSignedResponseAlg, id_token_signed_response_alg, "The algorithm that the OP must use to sign the ID token."), OIDC_CFG_CMD_PROVIDER( AP_INIT_TAKE1, OIDCIDTokenEncryptedResponseAlg, id_token_encrypted_response_alg, "The algorithm that the OP should use to encrypt the Content Encryption Key that is used to encrypt the id_token (used only in dynamic client registration); must be one of [RSA1_5|A128KW|A256KW|RSA-OAEP]"), OIDC_CFG_CMD_PROVIDER( AP_INIT_TAKE1, OIDCIDTokenEncryptedResponseEnc, id_token_encrypted_response_enc, "The algorithm that the OP should use to encrypt to the id_token with the Content Encryption Key (used only in dynamic client registration); must be one of [A128CBC-HS256|A256CBC-HS512|A256GCM]"), OIDC_CFG_CMD_PROVIDER( AP_INIT_ITERATE, OIDCIDTokenAudValues, id_token_aud_values, "Accepted \"aud\" claim values in the ID token."), OIDC_CFG_CMD_PROVIDER( AP_INIT_TAKE1, OIDCUserInfoSignedResponseAlg, userinfo_signed_response_alg, "The algorithm that the OP should use to sign the UserInfo response (used only in dynamic client registration); must be one of [RS256|RS384|RS512|PS256|PS384|PS512|HS256|HS384|HS512]"), OIDC_CFG_CMD_PROVIDER( AP_INIT_TAKE1, OIDCUserInfoEncryptedResponseAlg, userinfo_encrypted_response_alg, "The algorithm that the OP should use to encrypt the Content Encryption Key that is used to encrypt the UserInfo response (used only in dynamic client registration); must be one of [RSA1_5|A128KW|A256KW|RSA-OAEP]"), OIDC_CFG_CMD_PROVIDER( AP_INIT_TAKE1, OIDCUserInfoEncryptedResponseEnc, userinfo_encrypted_response_enc, "The algorithm that the OP should use to encrypt to encrypt the UserInfo response with the Content Encryption Key (used only in dynamic client registration); must be one of [A128CBC-HS256|A256CBC-HS512|A256GCM]"), OIDC_CFG_CMD_PROVIDER( AP_INIT_TAKE1, OIDCUserInfoTokenMethod, userinfo_token_method, "The method that is used to present the access token to the userinfo endpoint; must be one of [authz_header|post_param]"), OIDC_CFG_CMD_PROVIDER( AP_INIT_TAKE1, OIDCSSLValidateServer, ssl_validate_server, "Require validation of the OpenID Connect OP SSL server certificate for successful authentication (On or Off)"), OIDC_CFG_CMD_PROVIDER( AP_INIT_TAKE1, OIDCValidateIssuer, validate_issuer, "Require validation of token issuer for successful authentication (On or Off)"), OIDC_CFG_CMD_PROVIDER( AP_INIT_TAKE1, OIDCClientName, client_name, "Define the (client_name) name that the client uses for dynamic registration to the OP."), OIDC_CFG_CMD_PROVIDER( AP_INIT_TAKE1, OIDCClientContact, client_contact, "Define the contact that the client registers in dynamic registration with the OP."), OIDC_CFG_CMD_PROVIDER( AP_INIT_TAKE1, OIDCScope, scope, "Define the OpenID Connect scope that is requested from the OP."), OIDC_CFG_CMD_PROVIDER( AP_INIT_TAKE1, OIDCJWKSRefreshInterval, jwks_uri_refresh_interval, "Duration in seconds after which retrieved JWS should be refreshed."), OIDC_CFG_CMD_PROVIDER( AP_INIT_TAKE1, OIDCIDTokenIatSlack, idtoken_iat_slack, "Acceptable offset (both before and after) for checking the \"iat\" (= issued at) timestamp in the id_token."), OIDC_CFG_CMD_PROVIDER( AP_INIT_TAKE1, OIDCSessionMaxDuration, session_max_duration, "Maximum duration of a session in seconds."), OIDC_CFG_CMD_PROVIDER( AP_INIT_TAKE1, OIDCAuthRequestParams, auth_request_params, "Extra parameters that need to be sent in the Authorization Request (must be query-encoded like \"display=popup&prompt=consent\"."), OIDC_CFG_CMD_PROVIDER( AP_INIT_TAKE1, OIDCLogoutRequestParams, logout_request_params, "Extra parameters that need to be sent in the Logout Request (must be query-encoded like \"client_id=myclient&prompt=none\"."), OIDC_CFG_CMD_PROVIDER( AP_INIT_TAKE1, OIDCPKCEMethod, pkce, "The RFC 7636 PCKE mode used; must be one of \"plain\" or \"S256\""), OIDC_CFG_CMD_PROVIDER( AP_INIT_TAKE12, OIDCDPoPMode, dpop_mode, "The RFC 9449 DPoP mode used; must be one of \"off\", \"optional\" or \"required\""), OIDC_CFG_CMD_PROVIDER( AP_INIT_TAKE1, OIDCClientID, client_id, "Client identifier used in calls to OpenID Connect OP."), OIDC_CFG_CMD_PROVIDER( AP_INIT_TAKE1, OIDCClientSecret, client_secret, "Client secret used in calls to OpenID Connect OP."), OIDC_CFG_CMD_PROVIDER( AP_INIT_TAKE1, OIDCClientTokenEndpointCert, token_endpoint_tls_client_cert, "TLS client certificate used for calls to OpenID Connect OP token endpoint."), OIDC_CFG_CMD_PROVIDER( AP_INIT_TAKE1, OIDCClientTokenEndpointKey, token_endpoint_tls_client_key, "TLS client certificate private key used for calls to OpenID Connect OP token endpoint."), OIDC_CFG_CMD_PROVIDER( AP_INIT_TAKE1, OIDCClientTokenEndpointKeyPassword, token_endpoint_tls_client_key_pwd, "TLS client certificate private key password used for calls to OpenID Connect OP token endpoint."), OIDC_CFG_CMD_PROVIDER( AP_INIT_TAKE12, OIDCUserInfoRefreshInterval, userinfo_refresh_interval, "Duration in seconds after which retrieved claims from the userinfo endpoint should be refreshed."), OIDC_CFG_CMD_PROVIDER( AP_INIT_TAKE1, OIDCRequestObject, request_object, "The default request object settings"), OIDC_CFG_CMD_PROVIDER( AP_INIT_TAKE1, OIDCProviderAuthRequestMethod, auth_request_method, "HTTP method used to send the authentication request to the provider (GET or POST)."), OIDC_CFG_CMD_PROVIDER( AP_INIT_TAKE1, OIDCProfile, profile, "OpenID Connect Profile."), // oauth OIDC_CFG_CMD_OAUTH( AP_INIT_TAKE1, OIDCOAuthServerMetadataURL, metadata_url, "Authorization Server metadata URL."), OIDC_CFG_CMD_OAUTH( AP_INIT_TAKE1, OIDCOAuthClientID, client_id, "Client identifier used in calls to OAuth 2.0 Authorization server validation calls."), OIDC_CFG_CMD_OAUTH( AP_INIT_TAKE1, OIDCOAuthClientSecret, client_secret, "Client secret used in calls to OAuth 2.0 Authorization server validation calls."), OIDC_CFG_CMD_OAUTH( AP_INIT_TAKE1, OIDCOAuthIntrospectionEndpoint, introspection_endpoint_url, "Define the OAuth AS Introspection Endpoint URL (e.g.: https://localhost:9031/as/token.oauth2)"), OIDC_CFG_CMD_OAUTH( AP_INIT_TAKE1, OIDCOAuthIntrospectionEndpointMethod, introspection_endpoint_method, "Define the HTTP method to use for the introspection call: one of \"GET\" or \"POST\" (default)"), OIDC_CFG_CMD_OAUTH( AP_INIT_TAKE1, OIDCOAuthIntrospectionEndpointParams, introspection_endpoint_params, "Extra parameters that need to be sent in the token introspection request (must be query-encoded like \"grant_type=urn%3Apingidentity.com%3Aoauth2%3Agrant_type%3Avalidate_bearer\"."), OIDC_CFG_CMD_OAUTH( AP_INIT_TAKE1, OIDCOAuthIntrospectionEndpointAuth, introspection_endpoint_auth, "Specify an authentication method for the OAuth AS Introspection Endpoint (e.g.: client_secret_basic)"), OIDC_CFG_CMD_OAUTH( AP_INIT_RAW_ARGS, OIDCOAuthIntrospectionClientAuthBearerToken, introspection_client_auth_bearer_token, "Specify a bearer token to authorize against the OAuth AS Introspection Endpoint (e.g.: 55554ee-2491-11e3-be72-001fe2e44345 or empty to use the introspected token itself)"), OIDC_CFG_CMD_OAUTH( AP_INIT_TAKE1, OIDCOAuthIntrospectionEndpointCert, introspection_endpoint_tls_client_cert, "TLS client certificate used for calls to the OAuth 2.0 Authorization server introspection endpoint."), OIDC_CFG_CMD_OAUTH( AP_INIT_TAKE1, OIDCOAuthIntrospectionEndpointKey, introspection_endpoint_tls_client_key, "TLS client certificate private key used for calls to the OAuth 2.0 Authorization server introspection endpoint."), OIDC_CFG_CMD_OAUTH( AP_INIT_TAKE1, OIDCOAuthIntrospectionEndpointKeyPassword, introspection_endpoint_tls_client_key_pwd, "TLS client certificate private key password used for calls to the OAuth 2.0 Authorization server introspection endpoint."), OIDC_CFG_CMD_OAUTH( AP_INIT_TAKE1, OIDCOAuthIntrospectionTokenParamName, introspection_token_param_name, "Name of the parameter whose value carries the access token value in an validation request to the token introspection endpoint."), OIDC_CFG_CMD_OAUTH( AP_INIT_TAKE123, OIDCOAuthTokenExpiryClaim, token_expiry_claim, "Name of the claim that carries the token expiry value in the introspection result, optionally followed by absolute|relative, optionally followed by optional|mandatory"), OIDC_CFG_CMD_OAUTH( AP_INIT_TAKE1, OIDCOAuthSSLValidateServer, ssl_validate_server, "Require validation of the OAuth 2.0 AS Validation Endpoint SSL server certificate for successful authentication (On or Off)"), OIDC_CFG_CMD_OAUTH( AP_INIT_TAKE123, OIDCOAuthRemoteUserClaim, remote_user_claim, "The claim that is used when setting the REMOTE_USER variable for OAuth 2.0 protected paths."), OIDC_CFG_CMD_OAUTH( AP_INIT_ITERATE, OIDCOAuthVerifyCertFiles, verify_public_keys, "The fully qualified names of the files that contain the X.509 certificates that contains the RSA/EC public keys that can be used for access token validation."), OIDC_CFG_CMD_OAUTH( AP_INIT_ITERATE, OIDCOAuthVerifySharedKeys, verify_shared_keys, "Shared secret(s) that is/are used to verify signed JWT access tokens locally."), OIDC_CFG_CMD_OAUTH( AP_INIT_TAKE1, OIDCOAuthVerifyJwksUri, verify_jwks_uri, "The JWKs URL on which the Authorization publishes the keys used to sign its JWT access tokens."), // dir OIDC_CFG_CMD_DIR( AP_INIT_TAKE1, OIDCPathScope, path_scope, "Define the OpenID Connect scope that is sent in the authentication request for a specific path/context."), OIDC_CFG_CMD_DIR( AP_INIT_TAKE1, OIDCPathAuthRequestParams, path_auth_request_params, "Extra parameters that need to be sent in the authentication request: must be query-encoded like \"display=popup&prompt=consent\"."), OIDC_CFG_CMD_DIR( AP_INIT_TAKE1, OIDCDiscoverURL, discover_url, "Define an external IDP discovery page"), OIDC_CFG_CMD_DIR( AP_INIT_ITERATE, OIDCPassCookies, pass_cookies, "Specify cookies that need to be passed from the browser on to the backend to the OP/AS."), OIDC_CFG_CMD_DIR( AP_INIT_ITERATE, OIDCStripCookies, strip_cookies, "Specify cookies that should be stripped from the incoming request before passing it on to the backend."), OIDC_CFG_CMD_DIR( AP_INIT_TAKE1, OIDCAuthNHeader, authn_header, "Specify the HTTP header variable to set with the name of the authenticated user. By default no explicit header is added but Apache's default REMOTE_USER will be set."), OIDC_CFG_CMD_DIR( AP_INIT_TAKE1, OIDCCookiePath, cookie_path, "Define the cookie path for the session cookie."), OIDC_CFG_CMD_DIR( AP_INIT_TAKE1, OIDCStateCookiePrefix, state_cookie_prefix, "Define the cookie prefix for the state cookie."), OIDC_CFG_CMD_DIR( AP_INIT_TAKE1, OIDCCookie, cookie, "Define the cookie name for the session cookie."), OIDC_CFG_CMD_DIR( AP_INIT_TAKE12, OIDCUnAuthAction, unauth_action, "Set the action taken when an unauthenticated request occurs: must be one of auth | pass | 401 | 407 |410."), OIDC_CFG_CMD_DIR( AP_INIT_TAKE12, OIDCUnAutzAction, unautz_action, "Set the action taken when an unauthorized request occurs: must be one of: 401 [] | 403 [] | 302 [] | auth."), OIDC_CFG_CMD_DIR( AP_INIT_TAKE12, OIDCPassClaimsAs, pass_claims_as, "Specify how claims are passed to the application(s); must be one of: none | headers | environment | both."), OIDC_CFG_CMD_DIR( AP_INIT_ITERATE, OIDCOAuthAcceptTokenAs, accept_oauth_token_in, "The method in which an OAuth token can be presented; must be one or more of: header | post | query | cookie."), OIDC_CFG_CMD_DIR( AP_INIT_TAKE1, OIDCOAuthTokenIntrospectionInterval, token_introspection_interval, "Set the token introspection refresh interval."), OIDC_CFG_CMD_DIR( AP_INIT_TAKE1, OIDCPreservePost, preserve_post, "Indicates whether POST parameters will be preserved across authentication requests."), OIDC_CFG_CMD_DIR( AP_INIT_TAKE1, OIDCPassAccessToken, pass_access_token, "Pass the access token in a header and/or environment variable (On or Off)"), OIDC_CFG_CMD_DIR( AP_INIT_TAKE1, OIDCPassRefreshToken, pass_refresh_token, "Pass the refresh token in a header and/or environment variable (On or Off)"), OIDC_CFG_CMD_DIR( AP_INIT_TAKE123, OIDCPassIDTokenAs, pass_idtoken_as, "Set the format in which the id_token is passed in (a) header(s); must be one or more of: claims | payload | serialized"), OIDC_CFG_CMD_DIR( AP_INIT_TAKE12, OIDCRefreshAccessTokenBeforeExpiry, refresh_access_token_before_expiry, "Ensure the access token is valid for at least seconds by refreshing it if required; must be: [logout_on_error | authenticate_on_error]."), OIDC_CFG_CMD_DIR( AP_INIT_ITERATE, OIDCPassUserInfoAs, pass_userinfo_as, "The format in which the userinfo is passed in (a) header(s); must be one or more of: claims | json | jwt | signed_jwt"), #ifdef USE_LIBJQ OIDC_CFG_CMD_DIR( AP_INIT_TAKE1, OIDCUserInfoClaimsExpr, userinfo_claims_expr, "Sets the JQ expression to be executed on the claims from the userinfo endpoint stored in the session before propagating them"), #endif { NULL } }; // clang-format on mod_auth_openidc-2.4.16.10/src/cfg/dir.c000066400000000000000000000743421476721736500176300ustar00rootroot00000000000000/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ /*************************************************************************** * Copyright (C) 2017-2025 ZmartZone Holding BV * All rights reserved. * * DISCLAIMER OF WARRANTIES: * * THE SOFTWARE PROVIDED HEREUNDER IS PROVIDED ON AN "AS IS" BASIS, WITHOUT * ANY WARRANTIES OR REPRESENTATIONS EXPRESS, IMPLIED OR STATUTORY; INCLUDING, * WITHOUT LIMITATION, WARRANTIES OF QUALITY, PERFORMANCE, NONINFRINGEMENT, * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. NOR ARE THERE ANY * WARRANTIES CREATED BY A COURSE OR DEALING, COURSE OF PERFORMANCE OR TRADE * USAGE. FURTHERMORE, THERE ARE NO WARRANTIES THAT THE SOFTWARE WILL MEET * YOUR NEEDS OR BE FREE FROM ERRORS, OR THAT THE OPERATION OF THE SOFTWARE * WILL BE UNINTERRUPTED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * @Author: Hans Zandbelt - hans.zandbelt@openidc.com */ #include "cfg/dir.h" #include "cfg/cfg_int.h" #include "cfg/parse.h" #include "util.h" /* * directory related configuration */ struct oidc_dir_cfg_t { char *discover_url; char *cookie_path; char *cookie; char *authn_header; int unauth_action; int unautz_action; char *unauthz_arg; apr_array_header_t *pass_cookies; apr_array_header_t *strip_cookies; int pass_info_in; int pass_info_encoding; int oauth_accept_token_in; apr_hash_t *oauth_accept_token_options; int oauth_token_introspect_interval; int preserve_post; int pass_access_token; int pass_refresh_token; oidc_apr_expr_t *path_auth_request_expr; oidc_apr_expr_t *path_scope_expr; oidc_apr_expr_t *unauth_expression; oidc_apr_expr_t *userinfo_claims_expr; int refresh_access_token_before_expiry; int action_on_error_refresh; int action_on_userinfo_refresh; char *state_cookie_prefix; apr_array_header_t *pass_userinfo_as; int pass_idtoken_as; }; #define OIDC_PASS_ID_TOKEN_AS_CLAIMS_STR "claims" #define OIDC_PASS_IDTOKEN_AS_PAYLOAD_STR "payload" #define OIDC_PASS_IDTOKEN_AS_SERIALIZED_STR "serialized" /* * define how to pass the id_token/claims in HTTP headers */ const char *oidc_cmd_dir_pass_idtoken_as_set(cmd_parms *cmd, void *m, const char *v1, const char *v2, const char *v3) { oidc_dir_cfg_t *dir_cfg = (oidc_dir_cfg_t *)m; oidc_pass_idtoken_as_t type; const char *rv = NULL; static const oidc_cfg_option_t options[] = { {OIDC_PASS_IDTOKEN_AS_CLAIMS, OIDC_PASS_ID_TOKEN_AS_CLAIMS_STR}, {OIDC_PASS_IDTOKEN_AS_PAYLOAD, OIDC_PASS_IDTOKEN_AS_PAYLOAD_STR}, {OIDC_PASS_IDTOKEN_AS_SERIALIZED, OIDC_PASS_IDTOKEN_AS_SERIALIZED_STR}}; if (v1) { rv = oidc_cfg_parse_option(cmd->pool, options, OIDC_CFG_OPTIONS_SIZE(options), v1, (int *)&type); if (rv != NULL) return OIDC_CONFIG_DIR_RV(cmd, rv); // NB: assign the first to override the "unset" default dir_cfg->pass_idtoken_as = type; } if (v2) { rv = oidc_cfg_parse_option(cmd->pool, options, OIDC_CFG_OPTIONS_SIZE(options), v2, (int *)&type); if (rv != NULL) return OIDC_CONFIG_DIR_RV(cmd, rv); dir_cfg->pass_idtoken_as |= type; } if (v3) { rv = oidc_cfg_parse_option(cmd->pool, options, OIDC_CFG_OPTIONS_SIZE(options), v3, (int *)&type); if (rv != NULL) return OIDC_CONFIG_DIR_RV(cmd, rv); dir_cfg->pass_idtoken_as |= type; } return NULL; } #define OIDC_PASS_USERINFO_AS_CLAIMS_STR "claims" #define OIDC_PASS_USERINFO_AS_JSON_OBJECT_STR "json" #define OIDC_PASS_USERINFO_AS_JWT_STR "jwt" #define OIDC_PASS_USERINFO_AS_SIGNED_JWT_STR "signed_jwt" /* * parse a "pass id token as" value from the provided strings */ static const char *oidc_cfg_dir_parse_pass_userinfo_as(apr_pool_t *pool, const char *v, oidc_pass_user_info_as_t **result) { char *name = NULL; const char *rv = NULL; oidc_pass_userinfo_enum_t type; static const oidc_cfg_option_t options[] = { {OIDC_PASS_USERINFO_AS_CLAIMS, OIDC_PASS_USERINFO_AS_CLAIMS_STR}, {OIDC_PASS_USERINFO_AS_JSON_OBJECT, OIDC_PASS_USERINFO_AS_JSON_OBJECT_STR}, {OIDC_PASS_USERINFO_AS_JWT, OIDC_PASS_USERINFO_AS_JWT_STR}, {OIDC_PASS_USERINFO_AS_SIGNED_JWT, OIDC_PASS_USERINFO_AS_SIGNED_JWT_STR}}; name = _oidc_strstr(v, ":"); if (name) { *name = '\0'; name++; } rv = oidc_cfg_parse_option(pool, options, OIDC_CFG_OPTIONS_SIZE(options), v, (int *)&type); if (rv != NULL) return rv; *result = apr_pcalloc(pool, sizeof(oidc_pass_user_info_as_t)); (*result)->type = type; if (name) (*result)->name = apr_pstrdup(pool, name); return NULL; } /* * define how to pass the userinfo/claims in HTTP headers */ const char *oidc_cmd_dir_pass_userinfo_as_set(cmd_parms *cmd, void *m, const char *arg) { oidc_dir_cfg_t *dir_cfg = (oidc_dir_cfg_t *)m; const char *rv = NULL; oidc_pass_user_info_as_t *p = NULL; rv = oidc_cfg_dir_parse_pass_userinfo_as(cmd->pool, arg, &p); if (rv != NULL) return OIDC_CONFIG_DIR_RV(cmd, rv); if (dir_cfg->pass_userinfo_as == NULL) dir_cfg->pass_userinfo_as = apr_array_make(cmd->pool, 3, sizeof(oidc_pass_user_info_as_t *)); APR_ARRAY_PUSH(dir_cfg->pass_userinfo_as, oidc_pass_user_info_as_t *) = p; return NULL; } #define OIDC_OAUTH_ACCEPT_TOKEN_IN_HEADER_STR "header" #define OIDC_OAUTH_ACCEPT_TOKEN_IN_POST_STR "post" #define OIDC_OAUTH_ACCEPT_TOKEN_IN_QUERY_STR "query" #define OIDC_OAUTH_ACCEPT_TOKEN_IN_COOKIE_STR "cookie" #define OIDC_OAUTH_ACCEPT_TOKEN_IN_BASIC_STR "basic" static const oidc_cfg_option_t oidc_oauth_accept_token_in_options[] = { {OIDC_OAUTH_ACCEPT_TOKEN_IN_HEADER, OIDC_OAUTH_ACCEPT_TOKEN_IN_HEADER_STR}, {OIDC_OAUTH_ACCEPT_TOKEN_IN_POST, OIDC_OAUTH_ACCEPT_TOKEN_IN_POST_STR}, {OIDC_OAUTH_ACCEPT_TOKEN_IN_QUERY, OIDC_OAUTH_ACCEPT_TOKEN_IN_QUERY_STR}, {OIDC_OAUTH_ACCEPT_TOKEN_IN_COOKIE, OIDC_OAUTH_ACCEPT_TOKEN_IN_COOKIE_STR}, {OIDC_OAUTH_ACCEPT_TOKEN_IN_BASIC, OIDC_OAUTH_ACCEPT_TOKEN_IN_BASIC_STR}}; /* * convert an "accept OAuth 2.0 token in" byte value to a string representation */ const char *oidc_cfg_dir_accept_oauth_token_in2str(apr_pool_t *pool, oidc_oauth_accept_token_in_t v) { static oidc_cfg_option_t enabled[OIDC_CFG_OPTIONS_SIZE(oidc_oauth_accept_token_in_options)]; int i = 0, j = 0; for (j = 0; j < OIDC_CFG_OPTIONS_SIZE(oidc_oauth_accept_token_in_options); j++) { if (v & oidc_oauth_accept_token_in_options[j].val) { enabled[i] = oidc_oauth_accept_token_in_options[j]; i++; } } return oidc_cfg_parse_options_flatten(pool, enabled, i); } #define OIDC_OAUTH_ACCEPT_TOKEN_IN_COOKIE_NAME_DEFAULT "PA.global" #define OIDC_OAUTH_ACCEPT_TOKEN_IN_COOKIE_SEPARATOR ":" /* * define which method of pass an OAuth Bearer token is accepted */ const char *oidc_cmd_dir_accept_oauth_token_in_set(cmd_parms *cmd, void *m, const char *arg) { oidc_dir_cfg_t *dir_cfg = (oidc_dir_cfg_t *)m; int v = 0; const char *rv = NULL, *s = NULL; char *p = NULL; s = apr_pstrdup(cmd->pool, arg); p = _oidc_strstr(s, OIDC_OAUTH_ACCEPT_TOKEN_IN_COOKIE_SEPARATOR); if (p != NULL) { *p = '\0'; p++; } else { p = OIDC_OAUTH_ACCEPT_TOKEN_IN_COOKIE_NAME_DEFAULT; } rv = oidc_cfg_parse_option(cmd->pool, oidc_oauth_accept_token_in_options, OIDC_CFG_OPTIONS_SIZE(oidc_oauth_accept_token_in_options), s, &v); if (rv != NULL) return OIDC_CONFIG_DIR_RV(cmd, rv); if (dir_cfg->oauth_accept_token_in == OIDC_CONFIG_POS_INT_UNSET) dir_cfg->oauth_accept_token_in = v; else dir_cfg->oauth_accept_token_in |= v; if (v == OIDC_OAUTH_ACCEPT_TOKEN_IN_COOKIE) apr_hash_set(dir_cfg->oauth_accept_token_options, OIDC_OAUTH_ACCEPT_TOKEN_IN_OPTION_COOKIE_NAME, APR_HASH_KEY_STRING, p); return NULL; } /* * specify cookies names to pass/strip */ const char *oidc_cmd_dir_strip_cookies_set(cmd_parms *cmd, void *m, const char *arg) { oidc_dir_cfg_t *dir_cfg = (oidc_dir_cfg_t *)m; return oidc_cfg_string_list_add(cmd->pool, &dir_cfg->strip_cookies, arg); } const char *oidc_cmd_dir_pass_cookies_set(cmd_parms *cmd, void *m, const char *arg) { oidc_dir_cfg_t *dir_cfg = (oidc_dir_cfg_t *)m; return oidc_cfg_string_list_add(cmd->pool, &dir_cfg->pass_cookies, arg); } #define OIDC_CFG_DIR_MEMBER_FUNC_GET(member, type, def_val, unset_val) \ type oidc_cfg_dir_##member##_get(request_rec *r) { \ oidc_dir_cfg_t *dir_cfg = ap_get_module_config(r->per_dir_config, &auth_openidc_module); \ if (dir_cfg->member == unset_val) \ return def_val; \ return dir_cfg->member; \ } #define OIDC_CFG_DIR_MEMBER_FUNC_INT_GET(member, type, def_val) \ OIDC_CFG_DIR_MEMBER_FUNC_GET(member, type, def_val, OIDC_CONFIG_POS_INT_UNSET) #define OIDC_CFG_DIR_MEMBER_FUNCS_INT(member, type, parse, def_val) \ const char *oidc_cmd_dir_##member##_set(cmd_parms *cmd, void *m, const char *arg) { \ oidc_dir_cfg_t *dir_cfg = (oidc_dir_cfg_t *)m; \ int v = -1; \ const char *rv = parse; \ if (rv == NULL) \ dir_cfg->member = v; \ return OIDC_CONFIG_DIR_RV(cmd, rv); \ } \ OIDC_CFG_DIR_MEMBER_FUNC_INT_GET(member, type, def_val) /* default for preserving POST parameters across authentication requests */ #define OIDC_DEFAULT_PRESERVE_POST 0 OIDC_CFG_DIR_MEMBER_FUNCS_INT(preserve_post, int, oidc_cfg_parse_boolean(cmd->pool, arg, &v), OIDC_DEFAULT_PRESERVE_POST) #define OIDC_APPINFO_ENCODING_NONE_STR "none" #define OIDC_APPINFO_ENCODING_LATIN1_STR "latin1" #define OIDC_APPINFO_ENCODING_BASE64URL_STR "base64url" #define OIDC_APPINFO_PASS_NONE_STR "none" #define OIDC_APPINFO_PASS_HEADERS_STR "headers" #define OIDC_APPINFO_PASS_ENVVARS_STR "environment" #define OIDC_APPINFO_PASS_BOTH_STR "both" #define OIDC_APPINFO_PASS_BOTH (OIDC_APPINFO_PASS_HEADERS | OIDC_APPINFO_PASS_ENVVARS) /* * define how to pass claims information to the application: in headers and/or environment variables * and optionally specify the encoding applied to the values */ const char *oidc_cmd_dir_pass_claims_as_set(cmd_parms *cmd, void *m, const char *arg1, const char *arg2) { oidc_dir_cfg_t *dir_cfg = (oidc_dir_cfg_t *)m; const char *rv = NULL; static const oidc_cfg_option_t pass_options[] = {{OIDC_APPINFO_PASS_NONE, OIDC_APPINFO_PASS_NONE_STR}, {OIDC_APPINFO_PASS_HEADERS, OIDC_APPINFO_PASS_HEADERS_STR}, {OIDC_APPINFO_PASS_ENVVARS, OIDC_APPINFO_PASS_ENVVARS_STR}, {OIDC_APPINFO_PASS_BOTH, OIDC_APPINFO_PASS_BOTH_STR}}; rv = oidc_cfg_parse_option(cmd->pool, pass_options, OIDC_CFG_OPTIONS_SIZE(pass_options), arg1, &dir_cfg->pass_info_in); static const oidc_cfg_option_t encoding_options[] = { {OIDC_APPINFO_ENCODING_NONE, OIDC_APPINFO_ENCODING_NONE_STR}, {OIDC_APPINFO_ENCODING_BASE64URL, OIDC_APPINFO_ENCODING_BASE64URL_STR}, {OIDC_APPINFO_ENCODING_LATIN1, OIDC_APPINFO_ENCODING_LATIN1_STR}}; if ((rv == NULL) && (arg2 != NULL)) rv = oidc_cfg_parse_option(cmd->pool, encoding_options, OIDC_CFG_OPTIONS_SIZE(encoding_options), arg2, &dir_cfg->pass_info_encoding); return OIDC_CONFIG_DIR_RV(cmd, rv); } #define OIDC_UNAUTH_AUTHENTICATE_STR "auth" #define OIDC_UNAUTH_PASS_STR "pass" #define OIDC_UNAUTH_RETURN401_STR "401" #define OIDC_UNAUTH_RETURN410_STR "410" #define OIDC_UNAUTH_RETURN407_STR "407" static const oidc_cfg_option_t unauth_action_options[] = {{OIDC_UNAUTH_AUTHENTICATE, OIDC_UNAUTH_AUTHENTICATE_STR}, {OIDC_UNAUTH_PASS, OIDC_UNAUTH_PASS_STR}, {OIDC_UNAUTH_RETURN401, OIDC_UNAUTH_RETURN401_STR}, {OIDC_UNAUTH_RETURN410, OIDC_UNAUTH_RETURN410_STR}, {OIDC_UNAUTH_RETURN407, OIDC_UNAUTH_RETURN407_STR}}; static const char *oidc_cfg_dir_unauth_action2str(oidc_unauth_action_t action) { int i = 0; for (i = 0; i < OIDC_CFG_OPTIONS_SIZE(unauth_action_options); i++) { if (action == unauth_action_options[i].val) return unauth_action_options[i].str; } return NULL; } /* * define how to act on unauthenticated requests */ const char *oidc_cmd_dir_unauth_action_set(cmd_parms *cmd, void *m, const char *arg1, const char *arg2) { oidc_dir_cfg_t *dir_cfg = (oidc_dir_cfg_t *)m; const char *rv = oidc_cfg_parse_option(cmd->pool, unauth_action_options, OIDC_CFG_OPTIONS_SIZE(unauth_action_options), arg1, &dir_cfg->unauth_action); if (rv == NULL) rv = oidc_util_apr_expr_parse(cmd, arg2, &dir_cfg->unauth_expression, FALSE); return OIDC_CONFIG_DIR_RV(cmd, rv); } #define OIDC_UNAUTZ_RETURN403_STR "403" #define OIDC_UNAUTZ_RETURN401_STR "401" #define OIDC_UNAUTZ_AUTHENTICATE_STR "auth" #define OIDC_UNAUTZ_RETURN302_STR "302" /* * define how to act on unauthorized requests */ const char *oidc_cmd_dir_unautz_action_set(cmd_parms *cmd, void *m, const char *arg1, const char *arg2) { oidc_dir_cfg_t *dir_cfg = (oidc_dir_cfg_t *)m; static const oidc_cfg_option_t options[] = {{OIDC_UNAUTZ_RETURN403, OIDC_UNAUTZ_RETURN403_STR}, {OIDC_UNAUTZ_RETURN401, OIDC_UNAUTZ_RETURN401_STR}, {OIDC_UNAUTZ_AUTHENTICATE, OIDC_UNAUTZ_AUTHENTICATE_STR}, {OIDC_UNAUTZ_RETURN302, OIDC_UNAUTZ_RETURN302_STR}}; const char *rv = oidc_cfg_parse_option(cmd->pool, options, OIDC_CFG_OPTIONS_SIZE(options), arg1, &dir_cfg->unautz_action); if ((rv == NULL) && (arg2 != NULL)) { dir_cfg->unauthz_arg = apr_pstrdup(cmd->pool, arg2); } else if (dir_cfg->unautz_action == OIDC_UNAUTZ_RETURN302) { rv = apr_psprintf(cmd->temp_pool, "the (2nd) URL argument to %s must be set", cmd->directive->directive); return rv; } return OIDC_CONFIG_DIR_RV(cmd, rv); } #ifdef USE_LIBJQ const char *oidc_cmd_dir_userinfo_claims_expr_set(cmd_parms *cmd, void *m, const char *arg) { oidc_dir_cfg_t *dir_cfg = (oidc_dir_cfg_t *)m; const char *rv = oidc_util_apr_expr_parse(cmd, arg, &dir_cfg->userinfo_claims_expr, TRUE); return OIDC_CONFIG_DIR_RV(cmd, rv); } #endif const char *oidc_cmd_dir_path_auth_request_params_set(cmd_parms *cmd, void *m, const char *arg) { oidc_dir_cfg_t *dir_cfg = (oidc_dir_cfg_t *)m; const char *rv = NULL; rv = oidc_util_apr_expr_parse(cmd, arg, &dir_cfg->path_auth_request_expr, TRUE); return OIDC_CONFIG_DIR_RV(cmd, rv); } const char *oidc_cmd_dir_path_scope_set(cmd_parms *cmd, void *m, const char *arg) { oidc_dir_cfg_t *dir_cfg = (oidc_dir_cfg_t *)m; const char *rv = NULL; rv = oidc_util_apr_expr_parse(cmd, arg, &dir_cfg->path_scope_expr, TRUE); return OIDC_CONFIG_DIR_RV(cmd, rv); } #define OIDC_REFRESH_ACCESS_TOKEN_BEFORE_EXPIRY_MIN 0 #define OIDC_REFRESH_ACCESS_TOKEN_BEFORE_EXPIRY_MAX 3600 * 24 * 365 /* * set the time in seconds that the access token needs to be valid for */ const char *oidc_cmd_dir_refresh_access_token_before_expiry_set(cmd_parms *cmd, void *m, const char *arg1, const char *arg2) { oidc_dir_cfg_t *dir_cfg = (oidc_dir_cfg_t *)m; const char *rv = NULL; rv = oidc_cfg_parse_int_min_max(cmd->pool, arg1, &dir_cfg->refresh_access_token_before_expiry, OIDC_REFRESH_ACCESS_TOKEN_BEFORE_EXPIRY_MIN, OIDC_REFRESH_ACCESS_TOKEN_BEFORE_EXPIRY_MAX); if (rv != NULL) goto end; if (arg2) rv = oidc_cfg_parse_action_on_error_refresh_as( cmd->pool, arg2, (oidc_on_error_action_t *)&dir_cfg->action_on_error_refresh); end: return OIDC_CONFIG_DIR_RV(cmd, rv); } #define OIDC_CFG_DIR_MEMBER_FUNC_PTR(member, type, parse, def_val) \ const char *oidc_cmd_dir_##member##_set(cmd_parms *cmd, void *m, const char *arg) { \ oidc_dir_cfg_t *dir_cfg = (oidc_dir_cfg_t *)m; \ const char *rv = parse; \ return OIDC_CONFIG_DIR_RV(cmd, rv); \ } \ \ OIDC_CFG_DIR_MEMBER_FUNC_GET(member, type, def_val, NULL) #define OIDC_CFG_DIR_MEMBER_FUNC_STR(member, type, def_val) \ const char *oidc_cmd_dir_##member##_set(cmd_parms *cmd, void *m, const char *arg) { \ oidc_dir_cfg_t *dir_cfg = (oidc_dir_cfg_t *)m; \ dir_cfg->member = apr_pstrdup(cmd->pool, arg); \ return NULL; \ } \ \ OIDC_CFG_DIR_MEMBER_FUNC_GET(member, type, def_val, NULL) /* define the default number of seconds that the access token needs to be valid for; -1 = no refresh */ #define OIDC_DEFAULT_REFRESH_ACCESS_TOKEN_BEFORE_EXPIRY -1 OIDC_CFG_DIR_MEMBER_FUNC_INT_GET(refresh_access_token_before_expiry, int, OIDC_DEFAULT_REFRESH_ACCESS_TOKEN_BEFORE_EXPIRY) /* default action to be taken on access token refresh error */ #define OIDC_DEFAULT_ON_ERROR_REFRESH OIDC_ON_ERROR_502; OIDC_CFG_DIR_MEMBER_FUNC_INT_GET(action_on_error_refresh, oidc_on_error_action_t, OIDC_DEFAULT_ON_ERROR_REFRESH) /* default prefix of the state cookie that binds the state in the authorization request/response to the browser */ #define OIDC_DEFAULT_STATE_COOKIE_PREFIX "mod_auth_openidc_state_" OIDC_CFG_DIR_MEMBER_FUNC_STR(state_cookie_prefix, const char *, OIDC_DEFAULT_STATE_COOKIE_PREFIX) OIDC_CFG_DIR_MEMBER_FUNC_PTR(discover_url, const char *, oidc_cfg_parse_relative_or_absolute_url(cmd->pool, arg, &dir_cfg->discover_url), NULL) /* default name of the session cookie */ #define OIDC_DEFAULT_COOKIE "mod_auth_openidc_session" OIDC_CFG_DIR_MEMBER_FUNC_STR(cookie, const char *, OIDC_DEFAULT_COOKIE) /* default cookie path */ #define OIDC_DEFAULT_COOKIE_PATH "/" OIDC_CFG_DIR_MEMBER_FUNC_STR(cookie_path, const char *, OIDC_DEFAULT_COOKIE_PATH) /* default for the HTTP header name in which the remote user name is passed */ #define OIDC_DEFAULT_AUTHN_HEADER NULL OIDC_CFG_DIR_MEMBER_FUNC_STR(authn_header, const char *, OIDC_DEFAULT_AUTHN_HEADER) /* default for passing app info in headers */ #define OIDC_DEFAULT_PASS_APPINFO_IN OIDC_APPINFO_PASS_BOTH OIDC_CFG_DIR_MEMBER_FUNC_INT_GET(pass_info_in, oidc_appinfo_pass_in_t, OIDC_DEFAULT_PASS_APPINFO_IN) /* default for passing app info in a specific encoding */ #define OIDC_DEFAULT_APPINFO_ENCODING OIDC_APPINFO_ENCODING_LATIN1 OIDC_CFG_DIR_MEMBER_FUNC_INT_GET(pass_info_encoding, oidc_appinfo_encoding_t, OIDC_DEFAULT_APPINFO_ENCODING) /* default for passing the access token in a header/environment variable */ #define OIDC_DEFAULT_PASS_ACCESS_TOKEN 1 OIDC_CFG_DIR_MEMBER_FUNCS_INT(pass_access_token, apr_byte_t, oidc_cfg_parse_boolean(cmd->pool, arg, &v), OIDC_DEFAULT_PASS_ACCESS_TOKEN) /* default for passing the refresh token in a header/environment variable */ #define OIDC_DEFAULT_PASS_REFRESH_TOKEN 0 OIDC_CFG_DIR_MEMBER_FUNCS_INT(pass_refresh_token, apr_byte_t, oidc_cfg_parse_boolean(cmd->pool, arg, &v), OIDC_DEFAULT_PASS_REFRESH_TOKEN) #define OIDC_OAUTH_ACCEPT_TOKEN_IN_DEFAULT OIDC_OAUTH_ACCEPT_TOKEN_IN_HEADER OIDC_CFG_DIR_MEMBER_FUNC_INT_GET(oauth_accept_token_in, oidc_oauth_accept_token_in_t, OIDC_OAUTH_ACCEPT_TOKEN_IN_DEFAULT) const char *oidc_cfg_dir_accept_token_in_option_get(request_rec *r, const char *key) { oidc_dir_cfg_t *dir_cfg = ap_get_module_config(r->per_dir_config, &auth_openidc_module); return apr_hash_get(dir_cfg->oauth_accept_token_options, key, APR_HASH_KEY_STRING); } #define OIDC_OAUTH_ACCESS_TOKEN_INTROSPECTION_INTERVAL_MIN -1 #define OIDC_OAUTH_ACCESS_TOKEN_INTROSPECTION_INTERVAL_MAX 3600 * 24 * 365 /* default value for the token introspection interval (0 = disabled, no expiry of claims) */ #define OIDC_DEFAULT_TOKEN_INTROSPECTION_INTERVAL 0 const char *oidc_cmd_dir_token_introspection_interval_set(cmd_parms *cmd, void *m, const char *arg) { oidc_dir_cfg_t *dir_cfg = (oidc_dir_cfg_t *)m; const char *rv = oidc_cfg_parse_int_min_max(cmd->pool, arg, &dir_cfg->oauth_token_introspect_interval, OIDC_OAUTH_ACCESS_TOKEN_INTROSPECTION_INTERVAL_MIN, OIDC_OAUTH_ACCESS_TOKEN_INTROSPECTION_INTERVAL_MAX); return OIDC_CONFIG_DIR_RV(cmd, rv); } int oidc_cfg_dir_token_introspection_interval_get(request_rec *r) { oidc_dir_cfg_t *dir_cfg = ap_get_module_config(r->per_dir_config, &auth_openidc_module); // we use -2 here as an exception because -1 is a valid value if (dir_cfg->oauth_token_introspect_interval <= -2) return OIDC_DEFAULT_TOKEN_INTROSPECTION_INTERVAL; return dir_cfg->oauth_token_introspect_interval; } OIDC_CFG_DIR_MEMBER_FUNC_GET(pass_cookies, const apr_array_header_t *, NULL, NULL) OIDC_CFG_DIR_MEMBER_FUNC_GET(strip_cookies, const apr_array_header_t *, NULL, NULL) /* default action to take on an incoming unauthenticated request */ #define OIDC_DEFAULT_UNAUTH_ACTION OIDC_UNAUTH_AUTHENTICATE oidc_unauth_action_t oidc_cfg_dir_unauth_action_get(request_rec *r) { oidc_dir_cfg_t *dir_cfg = ap_get_module_config(r->per_dir_config, &auth_openidc_module); const char *s = NULL; oidc_unauth_action_t action = OIDC_CONFIG_POS_INT_UNSET; if (dir_cfg->unauth_action == OIDC_CONFIG_POS_INT_UNSET) { action = OIDC_DEFAULT_UNAUTH_ACTION; goto end; } if (dir_cfg->unauth_expression == NULL) { action = dir_cfg->unauth_action; goto end; } s = oidc_util_apr_expr_exec(r, dir_cfg->unauth_expression, FALSE); action = (s != NULL) ? dir_cfg->unauth_action : OIDC_DEFAULT_UNAUTH_ACTION; oidc_debug(r, "expression evaluation resulted in: %s (%s) for: %s", oidc_cfg_dir_unauth_action2str(action), s != NULL ? "true" : "false (default)", dir_cfg->unauth_expression->str); end: return action; } apr_byte_t oidc_cfg_dir_unauth_expr_is_set(request_rec *r) { oidc_dir_cfg_t *dir_cfg = ap_get_module_config(r->per_dir_config, &auth_openidc_module); return (dir_cfg->unauth_expression != NULL) ? TRUE : FALSE; } /* default action to take on an incoming authorized request */ #define OIDC_DEFAULT_UNAUTZ_ACTION OIDC_UNAUTZ_RETURN403 OIDC_CFG_DIR_MEMBER_FUNC_INT_GET(unautz_action, oidc_unautz_action_t, OIDC_DEFAULT_UNAUTZ_ACTION) const char *oidc_cfg_dir_unauthz_arg_get(request_rec *r) { oidc_dir_cfg_t *dir_cfg = ap_get_module_config(r->per_dir_config, &auth_openidc_module); return dir_cfg->unauthz_arg; } const char *oidc_cfg_dir_path_auth_request_params_get(request_rec *r) { oidc_dir_cfg_t *dir_cfg = ap_get_module_config(r->per_dir_config, &auth_openidc_module); return oidc_util_apr_expr_exec(r, dir_cfg->path_auth_request_expr, TRUE); } /* default pass user info as */ #define OIDC_DEFAULT_PASS_USERINFO_AS OIDC_PASS_USERINFO_AS_CLAIMS_STR static apr_array_header_t *pass_userinfo_as_default = NULL; const apr_array_header_t *oidc_cfg_dir_pass_userinfo_as_get(request_rec *r) { oidc_dir_cfg_t *dir_cfg = ap_get_module_config(r->per_dir_config, &auth_openidc_module); oidc_pass_user_info_as_t *p = NULL; if (dir_cfg->pass_userinfo_as == NULL) { if (pass_userinfo_as_default == NULL) { pass_userinfo_as_default = apr_array_make(r->server->process->pconf, 3, sizeof(oidc_pass_user_info_as_t *)); oidc_cfg_dir_parse_pass_userinfo_as(r->server->process->pconf, OIDC_DEFAULT_PASS_USERINFO_AS, &p); APR_ARRAY_PUSH(pass_userinfo_as_default, oidc_pass_user_info_as_t *) = p; } } return dir_cfg->pass_userinfo_as ? dir_cfg->pass_userinfo_as : pass_userinfo_as_default; } /* default pass id_token as */ #define OIDC_DEFAULT_PASS_IDTOKEN_AS OIDC_PASS_IDTOKEN_AS_CLAIMS OIDC_CFG_DIR_MEMBER_FUNC_INT_GET(pass_idtoken_as, oidc_pass_idtoken_as_t, OIDC_DEFAULT_PASS_IDTOKEN_AS) #ifdef USE_LIBJQ const char *oidc_cfg_dir_userinfo_claims_expr_get(request_rec *r) { oidc_dir_cfg_t *dir_cfg = ap_get_module_config(r->per_dir_config, &auth_openidc_module); return oidc_util_apr_expr_exec(r, dir_cfg->userinfo_claims_expr, TRUE); } #endif const char *oidc_cfg_dir_path_scope_get(request_rec *r) { oidc_dir_cfg_t *dir_cfg = ap_get_module_config(r->per_dir_config, &auth_openidc_module); return oidc_util_apr_expr_exec(r, dir_cfg->path_scope_expr, TRUE); } /* * create a new directory config record with defaults */ void *oidc_cfg_dir_config_create(apr_pool_t *pool, char *path) { oidc_dir_cfg_t *c = apr_pcalloc(pool, sizeof(oidc_dir_cfg_t)); c->discover_url = NULL; c->cookie = NULL; c->cookie_path = NULL; c->authn_header = NULL; c->unauth_action = OIDC_CONFIG_POS_INT_UNSET; c->unauth_expression = NULL; c->unautz_action = OIDC_CONFIG_POS_INT_UNSET; c->unauthz_arg = NULL; c->pass_cookies = NULL; c->strip_cookies = NULL; c->pass_info_in = OIDC_CONFIG_POS_INT_UNSET; c->pass_info_encoding = OIDC_CONFIG_POS_INT_UNSET; c->oauth_accept_token_in = OIDC_CONFIG_POS_INT_UNSET; c->oauth_accept_token_options = apr_hash_make(pool); c->oauth_token_introspect_interval = -2; c->preserve_post = OIDC_CONFIG_POS_INT_UNSET; c->pass_access_token = OIDC_CONFIG_POS_INT_UNSET; c->pass_refresh_token = OIDC_CONFIG_POS_INT_UNSET; c->path_auth_request_expr = NULL; c->path_scope_expr = NULL; c->userinfo_claims_expr = NULL; c->refresh_access_token_before_expiry = OIDC_CONFIG_POS_INT_UNSET; c->action_on_error_refresh = OIDC_CONFIG_POS_INT_UNSET; c->state_cookie_prefix = NULL; c->pass_userinfo_as = NULL; c->pass_idtoken_as = OIDC_CONFIG_POS_INT_UNSET; return (c); } /* * merge a new directory config with a base one */ void *oidc_cfg_dir_config_merge(apr_pool_t *pool, void *BASE, void *ADD) { oidc_dir_cfg_t *c = apr_pcalloc(pool, sizeof(oidc_dir_cfg_t)); oidc_dir_cfg_t *base = BASE; oidc_dir_cfg_t *add = ADD; c->discover_url = add->discover_url != NULL ? add->discover_url : base->discover_url; c->cookie = add->cookie != NULL ? add->cookie : base->cookie; c->cookie_path = add->cookie_path != NULL ? add->cookie_path : base->cookie_path; c->authn_header = add->authn_header != NULL ? add->authn_header : base->authn_header; c->unauth_action = add->unauth_action != OIDC_CONFIG_POS_INT_UNSET ? add->unauth_action : base->unauth_action; c->unauth_expression = add->unauth_expression != NULL ? add->unauth_expression : base->unauth_expression; c->unautz_action = add->unautz_action != OIDC_CONFIG_POS_INT_UNSET ? add->unautz_action : base->unautz_action; c->unauthz_arg = add->unauthz_arg != NULL ? add->unauthz_arg : base->unauthz_arg; c->pass_cookies = add->pass_cookies != NULL ? add->pass_cookies : base->pass_cookies; c->strip_cookies = add->strip_cookies != NULL ? add->strip_cookies : base->strip_cookies; c->pass_info_in = add->pass_info_in != OIDC_CONFIG_POS_INT_UNSET ? add->pass_info_in : base->pass_info_in; c->pass_info_encoding = add->pass_info_encoding != OIDC_CONFIG_POS_INT_UNSET ? add->pass_info_encoding : base->pass_info_encoding; c->oauth_accept_token_in = add->oauth_accept_token_in != OIDC_CONFIG_POS_INT_UNSET ? add->oauth_accept_token_in : base->oauth_accept_token_in; c->oauth_accept_token_options = apr_hash_count(add->oauth_accept_token_options) > 0 ? add->oauth_accept_token_options : base->oauth_accept_token_options; c->oauth_token_introspect_interval = add->oauth_token_introspect_interval >= -1 ? add->oauth_token_introspect_interval : base->oauth_token_introspect_interval; c->preserve_post = add->preserve_post != OIDC_CONFIG_POS_INT_UNSET ? add->preserve_post : base->preserve_post; c->pass_access_token = add->pass_access_token != OIDC_CONFIG_POS_INT_UNSET ? add->pass_access_token : base->pass_access_token; c->pass_refresh_token = add->pass_refresh_token != OIDC_CONFIG_POS_INT_UNSET ? add->pass_refresh_token : base->pass_refresh_token; c->path_auth_request_expr = add->path_auth_request_expr != NULL ? add->path_auth_request_expr : base->path_auth_request_expr; c->path_scope_expr = add->path_scope_expr != NULL ? add->path_scope_expr : base->path_scope_expr; c->userinfo_claims_expr = add->userinfo_claims_expr != NULL ? add->userinfo_claims_expr : base->userinfo_claims_expr; c->pass_userinfo_as = add->pass_userinfo_as != NULL ? add->pass_userinfo_as : base->pass_userinfo_as; c->pass_idtoken_as = add->pass_idtoken_as != OIDC_CONFIG_POS_INT_UNSET ? add->pass_idtoken_as : base->pass_idtoken_as; c->refresh_access_token_before_expiry = add->refresh_access_token_before_expiry != OIDC_CONFIG_POS_INT_UNSET ? add->refresh_access_token_before_expiry : base->refresh_access_token_before_expiry; c->action_on_error_refresh = add->action_on_error_refresh != OIDC_CONFIG_POS_INT_UNSET ? add->action_on_error_refresh : base->action_on_error_refresh; c->state_cookie_prefix = add->state_cookie_prefix != NULL ? add->state_cookie_prefix : base->state_cookie_prefix; return (c); } mod_auth_openidc-2.4.16.10/src/cfg/dir.h000066400000000000000000000171621476721736500176320ustar00rootroot00000000000000/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ /*************************************************************************** * Copyright (C) 2017-2025 ZmartZone Holding BV * All rights reserved. * * DISCLAIMER OF WARRANTIES: * * THE SOFTWARE PROVIDED HEREUNDER IS PROVIDED ON AN "AS IS" BASIS, WITHOUT * ANY WARRANTIES OR REPRESENTATIONS EXPRESS, IMPLIED OR STATUTORY; INCLUDING, * WITHOUT LIMITATION, WARRANTIES OF QUALITY, PERFORMANCE, NONINFRINGEMENT, * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. NOR ARE THERE ANY * WARRANTIES CREATED BY A COURSE OR DEALING, COURSE OF PERFORMANCE OR TRADE * USAGE. FURTHERMORE, THERE ARE NO WARRANTIES THAT THE SOFTWARE WILL MEET * YOUR NEEDS OR BE FREE FROM ERRORS, OR THAT THE OPERATION OF THE SOFTWARE * WILL BE UNINTERRUPTED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * @Author: Hans Zandbelt - hans.zandbelt@openidc.com */ #ifndef _MOD_AUTH_OPENIDC_CFG_DIR_H_ #define _MOD_AUTH_OPENIDC_CFG_DIR_H_ #include "cfg/cfg.h" #define OIDCPathScope "OIDCPathScope" #define OIDCPathAuthRequestParams "OIDCPathAuthRequestParams" #define OIDCDiscoverURL "OIDCDiscoverURL" #define OIDCPassCookies "OIDCPassCookies" #define OIDCStripCookies "OIDCStripCookies" #define OIDCAuthNHeader "OIDCAuthNHeader" #define OIDCCookie "OIDCCookie" #define OIDCUnAuthAction "OIDCUnAuthAction" #define OIDCUnAutzAction "OIDCUnAutzAction" #define OIDCPassClaimsAs "OIDCPassClaimsAs" #define OIDCOAuthAcceptTokenAs "OIDCOAuthAcceptTokenAs" #define OIDCOAuthTokenIntrospectionInterval "OIDCOAuthTokenIntrospectionInterval" #define OIDCPreservePost "OIDCPreservePost" #define OIDCPassAccessToken "OIDCPassAccessToken" #define OIDCPassRefreshToken "OIDCPassRefreshToken" #define OIDCRefreshAccessTokenBeforeExpiry "OIDCRefreshAccessTokenBeforeExpiry" #define OIDCStateCookiePrefix "OIDCStateCookiePrefix" #define OIDCPassIDTokenAs "OIDCPassIDTokenAs" #define OIDCPassUserInfoAs "OIDCPassUserInfoAs" #define OIDCUserInfoClaimsExpr "OIDCUserInfoClaimsExpr" #define OIDCCookiePath "OIDCCookiePath" typedef enum { /* pass id_token as individual claims in headers (default) */ OIDC_PASS_IDTOKEN_AS_CLAIMS = 1, /* pass id_token payload as JSON object in header */ OIDC_PASS_IDTOKEN_AS_PAYLOAD = 2, /* pass id_token in compact serialized format in header */ OIDC_PASS_IDTOKEN_AS_SERIALIZED = 4 } oidc_pass_idtoken_as_t; typedef enum { /* accept bearer token in header (default) */ OIDC_OAUTH_ACCEPT_TOKEN_IN_HEADER = 1, /* accept bearer token as a post parameter */ OIDC_OAUTH_ACCEPT_TOKEN_IN_POST = 2, /* accept bearer token as a query parameter */ OIDC_OAUTH_ACCEPT_TOKEN_IN_QUERY = 4, /* accept bearer token as a cookie parameter (PingAccess) */ OIDC_OAUTH_ACCEPT_TOKEN_IN_COOKIE = 8, /* accept bearer token as basic auth password (non-oauth clients) */ OIDC_OAUTH_ACCEPT_TOKEN_IN_BASIC = 16 } oidc_oauth_accept_token_in_t; typedef enum { OIDC_APPINFO_PASS_NONE = 0, OIDC_APPINFO_PASS_HEADERS = 1, OIDC_APPINFO_PASS_ENVVARS = 2, } oidc_appinfo_pass_in_t; typedef enum { OIDC_APPINFO_ENCODING_NONE = 0, OIDC_APPINFO_ENCODING_BASE64URL = 1, OIDC_APPINFO_ENCODING_LATIN1 = 2 } oidc_appinfo_encoding_t; /* the hash key of the cookie name value in the list of options */ #define OIDC_OAUTH_ACCEPT_TOKEN_IN_OPTION_COOKIE_NAME "cookie-name" typedef enum { OIDC_UNAUTH_AUTHENTICATE = 1, OIDC_UNAUTH_PASS = 2, OIDC_UNAUTH_RETURN401 = 3, OIDC_UNAUTH_RETURN410 = 4, OIDC_UNAUTH_RETURN407 = 5 } oidc_unauth_action_t; typedef enum { OIDC_UNAUTZ_RETURN403 = 1, OIDC_UNAUTZ_RETURN401 = 2, OIDC_UNAUTZ_AUTHENTICATE = 3, OIDC_UNAUTZ_RETURN302 = 4 } oidc_unautz_action_t; #define OIDC_CMD_DIR_MEMBER_FUNC_DECL(member, ...) \ const char *OIDC_CFG_MEMBER_FUNC_NAME(member, cmd_dir, set)(cmd_parms *, void *, const char *, ##__VA_ARGS__); #define OIDC_CFG_DIR_MEMBER_FUNC_GET_DECL(member, type) \ type OIDC_CFG_MEMBER_FUNC_NAME(member, cfg_dir, get)(request_rec * r); #define OIDC_CFG_DIR_MEMBER_FUNCS(member, type, ...) \ OIDC_CMD_DIR_MEMBER_FUNC_DECL(member, ##__VA_ARGS__) \ OIDC_CFG_DIR_MEMBER_FUNC_GET_DECL(member, type) OIDC_CFG_DIR_MEMBER_FUNCS(pass_userinfo_as, const apr_array_header_t *) OIDC_CFG_DIR_MEMBER_FUNCS(accept_oauth_token_in, int) OIDC_CFG_DIR_MEMBER_FUNCS(preserve_post, int) OIDC_CFG_DIR_MEMBER_FUNCS(pass_claims_as, int, const char *) OIDC_CFG_DIR_MEMBER_FUNCS(unauth_action, oidc_unauth_action_t, const char *) OIDC_CFG_DIR_MEMBER_FUNCS(path_scope, const char *) OIDC_CFG_DIR_MEMBER_FUNCS(path_auth_request_params, const char *) OIDC_CFG_DIR_MEMBER_FUNCS(authn_header, const char *) OIDC_CFG_DIR_MEMBER_FUNCS(cookie_path, const char *) OIDC_CFG_DIR_MEMBER_FUNCS(cookie, const char *) OIDC_CFG_DIR_MEMBER_FUNCS(pass_cookies, const apr_array_header_t *) OIDC_CFG_DIR_MEMBER_FUNCS(strip_cookies, const apr_array_header_t *) OIDC_CFG_DIR_MEMBER_FUNCS(token_introspection_interval, int) OIDC_CFG_DIR_MEMBER_FUNCS(pass_access_token, apr_byte_t) OIDC_CFG_DIR_MEMBER_FUNCS(pass_refresh_token, apr_byte_t) OIDC_CFG_DIR_MEMBER_FUNCS(discover_url, const char *) OIDC_CFG_DIR_MEMBER_FUNCS(state_cookie_prefix, const char *) // 2 args OIDC_CFG_DIR_MEMBER_FUNCS(unautz_action, oidc_unautz_action_t, const char *) OIDC_CFG_DIR_MEMBER_FUNCS(refresh_access_token_before_expiry, int, const char *) // 3 args OIDC_CFG_DIR_MEMBER_FUNCS(pass_idtoken_as, oidc_pass_idtoken_as_t, const char *, const char *) // ifdefs #ifdef USE_LIBJQ OIDC_CFG_DIR_MEMBER_FUNCS(userinfo_claims_expr, const char *) #endif // getters only OIDC_CFG_DIR_MEMBER_FUNC_GET_DECL(action_on_error_refresh, oidc_on_error_action_t) OIDC_CFG_DIR_MEMBER_FUNC_GET_DECL(pass_info_in, oidc_appinfo_pass_in_t) OIDC_CFG_DIR_MEMBER_FUNC_GET_DECL(pass_info_encoding, oidc_appinfo_encoding_t) OIDC_CFG_DIR_MEMBER_FUNC_GET_DECL(oauth_accept_token_in, oidc_oauth_accept_token_in_t) OIDC_CFG_DIR_MEMBER_FUNC_GET_DECL(unauthz_arg, const char *) // specials const char *OIDC_CFG_MEMBER_FUNC_NAME(accept_token_in_option, cfg_dir, get)(request_rec *r, const char *key); apr_byte_t OIDC_CFG_MEMBER_FUNC_NAME(unauth_expr, cfg_dir, is_set)(request_rec *r); const char *OIDC_CFG_MEMBER_FUNC_NAME(accept_oauth_token, cfg_dir, in2str)(apr_pool_t *pool, oidc_oauth_accept_token_in_t v); typedef struct oidc_dir_cfg_t oidc_dir_cfg_t; void *oidc_cfg_dir_config_create(apr_pool_t *, char *); void *oidc_cfg_dir_config_merge(apr_pool_t *, void *, void *); #endif // _MOD_AUTH_OPENIDC_CFG_DIR_H_ mod_auth_openidc-2.4.16.10/src/cfg/oauth.c000066400000000000000000000534161476721736500201710ustar00rootroot00000000000000/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ /*************************************************************************** * Copyright (C) 2017-2025 ZmartZone Holding BV * All rights reserved. * * DISCLAIMER OF WARRANTIES: * * THE SOFTWARE PROVIDED HEREUNDER IS PROVIDED ON AN "AS IS" BASIS, WITHOUT * ANY WARRANTIES OR REPRESENTATIONS EXPRESS, IMPLIED OR STATUTORY; INCLUDING, * WITHOUT LIMITATION, WARRANTIES OF QUALITY, PERFORMANCE, NONINFRINGEMENT, * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. NOR ARE THERE ANY * WARRANTIES CREATED BY A COURSE OR DEALING, COURSE OF PERFORMANCE OR TRADE * USAGE. FURTHERMORE, THERE ARE NO WARRANTIES THAT THE SOFTWARE WILL MEET * YOUR NEEDS OR BE FREE FROM ERRORS, OR THAT THE OPERATION OF THE SOFTWARE * WILL BE UNINTERRUPTED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * @Author: Hans Zandbelt - hans.zandbelt@openidc.com */ #include "cfg/oauth.h" #include "cfg/cfg_int.h" #include "cfg/parse.h" #include "jose.h" #include "proto/proto.h" struct oidc_oauth_t { char *metadata_url; char *verify_jwks_uri; apr_hash_t *verify_shared_keys; apr_array_header_t *verify_public_keys; char *client_id; char *client_secret; char *introspection_endpoint_url; int introspection_endpoint_method; char *introspection_token_param_name; char *introspection_endpoint_params; char *introspection_endpoint_auth; char *introspection_client_auth_bearer_token; char *introspection_endpoint_tls_client_key; char *introspection_endpoint_tls_client_key_pwd; char *introspection_endpoint_tls_client_cert; char *introspection_token_expiry_claim_name; oidc_oauth_introspection_token_expiry_claim_format_t introspection_token_expiry_claim_format; oidc_oauth_introspection_token_expiry_claim_required_t introspection_token_expiry_claim_required; oidc_remote_user_claim_t remote_user_claim; int ssl_validate_server; }; // helper #define OIDC_OAUTH_MEMBER_FUNC_GET(member, type) \ type oidc_cfg_oauth_##member##_get(oidc_cfg_t *cfg) { \ return cfg->oauth->member; \ } #define OIDC_OAUTH_MEMBER_FUNCS_TYPE(member, type, valid) \ const char *oidc_cmd_oauth_##member##_set(cmd_parms *cmd, void *ptr, const char *arg) { \ oidc_cfg_t *cfg = \ (oidc_cfg_t *)ap_get_module_config(cmd->server->module_config, &auth_openidc_module); \ const char *rv = valid; \ if (rv == NULL) \ cfg->oauth->member = apr_pstrdup(cmd->pool, arg); \ return OIDC_CONFIG_DIR_RV(cmd, rv); \ } \ OIDC_OAUTH_MEMBER_FUNC_GET(member, type) #define OIDC_OAUTH_MEMBER_FUNCS_INT(member, parse, type, def_val) \ const char *oidc_cmd_oauth_##member##_set(cmd_parms *cmd, void *ptr, const char *arg) { \ oidc_cfg_t *cfg = \ (oidc_cfg_t *)ap_get_module_config(cmd->server->module_config, &auth_openidc_module); \ int v = -1; \ const char *rv = parse; \ if (rv == NULL) \ cfg->oauth->member = v; \ return OIDC_CONFIG_DIR_RV(cmd, rv); \ } \ \ type oidc_cfg_oauth_##member##_get(oidc_cfg_t *cfg) { \ if (cfg->oauth->member == OIDC_CONFIG_POS_INT_UNSET) \ return def_val; \ return cfg->oauth->member; \ } #define OIDC_OAUTH_MEMBER_FUNCS_BOOL(member, def_val) \ OIDC_OAUTH_MEMBER_FUNCS_INT(member, oidc_cfg_parse_boolean(cmd->pool, arg, &v), int, def_val) #define OIDC_OAUTH_MEMBER_FUNCS_KEYS(member) \ const char *oidc_cmd_oauth_##member##_set(cmd_parms *cmd, void *ptr, const char *arg) { \ oidc_cfg_t *cfg = \ (oidc_cfg_t *)ap_get_module_config(cmd->server->module_config, &auth_openidc_module); \ return oidc_cfg_parse_public_key_files(cmd->pool, arg, &cfg->oauth->member); \ } \ OIDC_OAUTH_MEMBER_FUNC_GET(member, const apr_array_header_t *) #define OIDC_OAUTH_MEMBER_FUNCS_STR(member) OIDC_OAUTH_MEMBER_FUNCS_TYPE(member, const char *, NULL) #define OIDC_OAUTH_MEMBER_FUNCS_URL(member) \ OIDC_OAUTH_MEMBER_FUNCS_TYPE(member, const char *, oidc_cfg_parse_is_valid_http_url(cmd->pool, arg)) #define OIDC_OAUTH_MEMBER_FUNC_STR_GET_DEF(member, def_val) \ const char *oidc_cfg_oauth_##member##_get(oidc_cfg_t *cfg) { \ return cfg->oauth->member ? cfg->oauth->member : def_val; \ } #define OIDC_OAUTH_MEMBER_FUNCS_FILE(member) \ const char *oidc_cmd_oauth_##member##_set(cmd_parms *cmd, void *ptr, const char *arg) { \ oidc_cfg_t *cfg = \ (oidc_cfg_t *)ap_get_module_config(cmd->server->module_config, &auth_openidc_module); \ const char *rv = oidc_cfg_parse_filename(cmd->pool, arg, &cfg->oauth->member); \ return OIDC_CONFIG_DIR_RV(cmd, rv); \ } \ OIDC_OAUTH_MEMBER_FUNC_GET(member, const char *) #define OIDC_OAUTH_MEMBER_FUNCS_PASSPHRASE(member) \ const char *oidc_cmd_oauth_##member##_set(cmd_parms *cmd, void *ptr, const char *arg) { \ oidc_cfg_t *cfg = \ (oidc_cfg_t *)ap_get_module_config(cmd->server->module_config, &auth_openidc_module); \ const char *rv = oidc_cfg_parse_passphrase(cmd->pool, arg, &cfg->oauth->member); \ return OIDC_CONFIG_DIR_RV(cmd, rv); \ } \ OIDC_OAUTH_MEMBER_FUNC_GET(member, const char *) OIDC_OAUTH_MEMBER_FUNCS_URL(metadata_url) OIDC_OAUTH_MEMBER_FUNCS_STR(client_id) OIDC_OAUTH_MEMBER_FUNCS_STR(client_secret) OIDC_OAUTH_MEMBER_FUNCS_STR(introspection_endpoint_params) OIDC_OAUTH_MEMBER_FUNCS_FILE(introspection_endpoint_tls_client_cert) OIDC_OAUTH_MEMBER_FUNCS_FILE(introspection_endpoint_tls_client_key) OIDC_OAUTH_MEMBER_FUNCS_PASSPHRASE(introspection_endpoint_tls_client_key_pwd) OIDC_OAUTH_MEMBER_FUNCS_KEYS(verify_public_keys) OIDC_OAUTH_MEMBER_FUNC_GET(introspection_client_auth_bearer_token, const char *) OIDC_OAUTH_MEMBER_FUNC_GET(introspection_token_expiry_claim_format, oidc_oauth_introspection_token_expiry_claim_format_t) OIDC_OAUTH_MEMBER_FUNC_GET(introspection_token_expiry_claim_required, oidc_oauth_introspection_token_expiry_claim_required_t) OIDC_OAUTH_MEMBER_FUNC_GET(verify_shared_keys, apr_hash_t *) #define OIDC_DEFAULT_OAUTH_SSL_VALIDATE_SERVER 1 OIDC_OAUTH_MEMBER_FUNCS_BOOL(ssl_validate_server, OIDC_DEFAULT_OAUTH_SSL_VALIDATE_SERVER) #define OIDC_DEFAULT_OAUTH_EXPIRY_CLAIM_NAME OIDC_PROTO_EXPIRES_IN OIDC_OAUTH_MEMBER_FUNC_STR_GET_DEF(introspection_token_expiry_claim_name, OIDC_DEFAULT_OAUTH_EXPIRY_CLAIM_NAME) #define OIDC_OAUTH_MEMBER_FUNCS_STR_VALID(member, valid, def_val) \ const char *oidc_cfg_oauth_##member##_set(apr_pool_t *pool, oidc_cfg_t *cfg, const char *arg) { \ const char *rv = valid; \ if (rv == NULL) \ cfg->oauth->member = apr_pstrdup(pool, arg); \ return rv; \ } \ const char *oidc_cmd_oauth_##member##_set(cmd_parms *cmd, void *ptr, const char *arg) { \ oidc_cfg_t *cfg = \ (oidc_cfg_t *)ap_get_module_config(cmd->server->module_config, &auth_openidc_module); \ const char *rv = oidc_cfg_oauth_##member##_set(cmd->pool, cfg, arg); \ return OIDC_CONFIG_DIR_RV(cmd, rv); \ } \ OIDC_OAUTH_MEMBER_FUNC_STR_GET_DEF(member, def_val) OIDC_OAUTH_MEMBER_FUNCS_STR_VALID(introspection_endpoint_url, oidc_cfg_parse_is_valid_http_url(pool, arg), NULL) OIDC_OAUTH_MEMBER_FUNCS_STR_VALID(verify_jwks_uri, oidc_cfg_parse_is_valid_http_url(pool, arg), NULL) #define OIDC_DEFAULT_OAUTH_TOKEN_PARAM_NAME "token" OIDC_OAUTH_MEMBER_FUNCS_STR_VALID(introspection_token_param_name, NULL, OIDC_DEFAULT_OAUTH_TOKEN_PARAM_NAME) OIDC_OAUTH_MEMBER_FUNCS_STR_VALID(introspection_endpoint_auth, oidc_cfg_get_valid_endpoint_auth_function(cfg)(pool, arg), NULL) oidc_remote_user_claim_t *oidc_cfg_oauth_remote_user_claim_get(oidc_cfg_t *cfg) { return &cfg->oauth->remote_user_claim; } #define OIDC_DEFAULT_OAUTH_CLAIM_REMOTE_USER "sub" const char *oidc_cfg_oauth_remote_user_claim_name_get(oidc_cfg_t *cfg) { return cfg->oauth->remote_user_claim.claim_name != NULL ? cfg->oauth->remote_user_claim.claim_name : OIDC_DEFAULT_OAUTH_CLAIM_REMOTE_USER; } const char *oidc_cmd_oauth_remote_user_claim_set(cmd_parms *cmd, void *ptr, const char *v1, const char *v2, const char *v3) { oidc_cfg_t *cfg = (oidc_cfg_t *)ap_get_module_config(cmd->server->module_config, &auth_openidc_module); const char *rv = oidc_parse_remote_user_claim(cmd->pool, v1, v2, v3, &cfg->oauth->remote_user_claim); return OIDC_CONFIG_DIR_RV(cmd, rv); } #define OIDC_INTROSPECTION_METHOD_GET_STR "GET" #define OIDC_INTROSPECTION_METHOD_POST_STR "POST" static const char *oidc_parse_introspection_endpoint_method(apr_pool_t *pool, const char *arg, int *v) { static const oidc_cfg_option_t options[] = { {OIDC_INTROSPECTION_METHOD_GET, OIDC_INTROSPECTION_METHOD_GET_STR}, {OIDC_INTROSPECTION_METHOD_POST, OIDC_INTROSPECTION_METHOD_POST_STR}, }; return oidc_cfg_parse_option(pool, options, OIDC_CFG_OPTIONS_SIZE(options), arg, v); } #define OIDC_DEFAULT_OAUTH_ENDPOINT_METHOD OIDC_INTROSPECTION_METHOD_POST OIDC_OAUTH_MEMBER_FUNCS_INT(introspection_endpoint_method, oidc_parse_introspection_endpoint_method(cmd->pool, arg, &v), oidc_oauth_introspection_endpoint_method_t, OIDC_DEFAULT_OAUTH_ENDPOINT_METHOD) /* * set the introspection authorization static bearer token */ const char *oidc_cmd_oauth_introspection_client_auth_bearer_token_set(cmd_parms *cmd, void *struct_ptr, const char *args) { oidc_cfg_t *cfg = (oidc_cfg_t *)ap_get_module_config(cmd->server->module_config, &auth_openidc_module); char *w = ap_getword_conf(cmd->pool, &args); cfg->oauth->introspection_client_auth_bearer_token = (*w == '\0' || *args != 0) ? "" : w; return NULL; } #define OIDC_CLAIM_FORMAT_RELATIVE_STR "relative" #define OIDC_CLAIM_FORMAT_ABSOLUTE_STR "absolute" #define OIDC_CLAIM_REQUIRED_MANDATORY_STR "mandatory" #define OIDC_CLAIM_REQUIRED_OPTIONAL_STR "optional" /* * set the syntax of the token expiry claim in the introspection response */ const char *oidc_cmd_oauth_token_expiry_claim_set(cmd_parms *cmd, void *dummy, const char *claim_name, const char *claim_format, const char *claim_required) { oidc_cfg_t *cfg = (oidc_cfg_t *)ap_get_module_config(cmd->server->module_config, &auth_openidc_module); static const oidc_cfg_option_t claim_format_options[] = { {OIDC_TOKEN_EXPIRY_CLAIM_FORMAT_RELATIVE, OIDC_CLAIM_FORMAT_RELATIVE_STR}, {OIDC_TOKEN_EXPIRY_CLAIM_FORMAT_ABSOLUTE, OIDC_CLAIM_FORMAT_ABSOLUTE_STR}}; static const oidc_cfg_option_t claim_required_options[] = { {OIDC_TOKEN_EXPIRY_CLAIM_REQUIRED_MANDATORY, OIDC_CLAIM_REQUIRED_MANDATORY_STR}, {OIDC_TOKEN_EXPIRY_CLAIM_REQUIRED_OPTIONAL, OIDC_CLAIM_REQUIRED_OPTIONAL_STR}}; const char *rv = NULL; cfg->oauth->introspection_token_expiry_claim_name = apr_pstrdup(cmd->pool, claim_name); if ((rv == NULL) && (claim_format != NULL)) rv = oidc_cfg_parse_option(cmd->pool, claim_format_options, OIDC_CFG_OPTIONS_SIZE(claim_format_options), claim_format, (int *)&cfg->oauth->introspection_token_expiry_claim_format); if ((rv == NULL) && (claim_required != NULL)) rv = oidc_cfg_parse_option(cmd->pool, claim_required_options, OIDC_CFG_OPTIONS_SIZE(claim_required_options), claim_required, (int *)&cfg->oauth->introspection_token_expiry_claim_required); return OIDC_CONFIG_DIR_RV(cmd, rv); } /* * add a shared key to a list of JWKs with shared keys */ const char *oidc_cmd_oauth_verify_shared_keys_set(cmd_parms *cmd, void *struct_ptr, const char *arg) { oidc_jose_error_t err; oidc_jwk_t *jwk = NULL; char *use = NULL; oidc_cfg_t *cfg = (oidc_cfg_t *)ap_get_module_config(cmd->server->module_config, &auth_openidc_module); int offset = (int)(long)cmd->info; apr_hash_t **shared_keys = (apr_hash_t **)((char *)cfg + offset); char *kid = NULL, *secret = NULL; int key_len = 0; const char *rv = oidc_cfg_parse_key_record(cmd->pool, arg, &kid, &secret, &key_len, &use, TRUE); if (rv != NULL) return rv; jwk = oidc_jwk_create_symmetric_key(cmd->pool, kid, (const unsigned char *)secret, key_len, TRUE, &err); if (jwk == NULL) { return apr_psprintf(cmd->pool, "oidc_jwk_create_symmetric_key failed for (kid=%s) \"%s\": %s", kid, secret, oidc_jose_e2s(cmd->pool, err)); } if (*shared_keys == NULL) *shared_keys = apr_hash_make(cmd->pool); if (use) jwk->use = apr_pstrdup(cmd->pool, use); apr_hash_set(*shared_keys, jwk->kid, APR_HASH_KEY_STRING, jwk); return NULL; } /* default OAuth 2.0 non-spec compliant introspection expiry claim format */ #define OIDC_DEFAULT_OAUTH_EXPIRY_CLAIM_FORMAT OIDC_CLAIM_FORMAT_RELATIVE_STR /* default OAuth 2.0 non-spec compliant introspection expiry claim required */ #define OIDC_DEFAULT_OAUTH_EXPIRY_CLAIM_REQUIRED TRUE oidc_oauth_t *oidc_cfg_oauth_create(apr_pool_t *pool) { oidc_oauth_t *o = apr_pcalloc(pool, sizeof(oidc_oauth_t)); o->ssl_validate_server = OIDC_CONFIG_POS_INT_UNSET; o->metadata_url = NULL; o->client_id = NULL; o->client_secret = NULL; o->introspection_endpoint_tls_client_cert = NULL; o->introspection_endpoint_tls_client_key = NULL; o->introspection_endpoint_url = NULL; o->introspection_endpoint_method = OIDC_CONFIG_POS_INT_UNSET; o->introspection_endpoint_params = NULL; o->introspection_endpoint_auth = NULL; o->introspection_client_auth_bearer_token = NULL; o->introspection_token_param_name = NULL; o->introspection_token_expiry_claim_name = NULL; o->introspection_token_expiry_claim_format = OIDC_TOKEN_EXPIRY_CLAIM_FORMAT_RELATIVE; o->introspection_token_expiry_claim_required = OIDC_TOKEN_EXPIRY_CLAIM_REQUIRED_MANDATORY; o->remote_user_claim.claim_name = NULL; o->remote_user_claim.reg_exp = NULL; o->remote_user_claim.replace = NULL; o->verify_jwks_uri = NULL; o->verify_public_keys = NULL; o->verify_shared_keys = NULL; return o; } void oidc_cfg_oauth_merge(apr_pool_t *pool, oidc_oauth_t *dst, const oidc_oauth_t *base, const oidc_oauth_t *add) { dst->ssl_validate_server = add->ssl_validate_server != OIDC_CONFIG_POS_INT_UNSET ? add->ssl_validate_server : base->ssl_validate_server; dst->metadata_url = add->metadata_url != NULL ? add->metadata_url : base->metadata_url; dst->client_id = add->client_id != NULL ? add->client_id : base->client_id; dst->client_secret = add->client_secret != NULL ? add->client_secret : base->client_secret; dst->introspection_endpoint_tls_client_key = add->introspection_endpoint_tls_client_key != NULL ? add->introspection_endpoint_tls_client_key : base->introspection_endpoint_tls_client_key; dst->introspection_endpoint_tls_client_cert = add->introspection_endpoint_tls_client_cert != NULL ? add->introspection_endpoint_tls_client_cert : base->introspection_endpoint_tls_client_cert; dst->introspection_endpoint_url = add->introspection_endpoint_url != NULL ? add->introspection_endpoint_url : base->introspection_endpoint_url; dst->introspection_endpoint_method = add->introspection_endpoint_method != OIDC_CONFIG_POS_INT_UNSET ? add->introspection_endpoint_method : base->introspection_endpoint_method; dst->introspection_endpoint_params = add->introspection_endpoint_params != NULL ? add->introspection_endpoint_params : base->introspection_endpoint_params; dst->introspection_endpoint_auth = add->introspection_endpoint_auth != NULL ? add->introspection_endpoint_auth : base->introspection_endpoint_auth; dst->introspection_client_auth_bearer_token = add->introspection_client_auth_bearer_token != NULL ? add->introspection_client_auth_bearer_token : base->introspection_client_auth_bearer_token; dst->introspection_token_param_name = add->introspection_token_param_name != NULL ? add->introspection_token_param_name : base->introspection_token_param_name; if (add->introspection_token_expiry_claim_name != NULL) { dst->introspection_token_expiry_claim_name = add->introspection_token_expiry_claim_name; dst->introspection_token_expiry_claim_format = add->introspection_token_expiry_claim_format; dst->introspection_token_expiry_claim_required = add->introspection_token_expiry_claim_required; } else { dst->introspection_token_expiry_claim_name = base->introspection_token_expiry_claim_name; dst->introspection_token_expiry_claim_format = base->introspection_token_expiry_claim_format; dst->introspection_token_expiry_claim_required = base->introspection_token_expiry_claim_required; } if (add->remote_user_claim.claim_name != NULL) { dst->remote_user_claim.claim_name = add->remote_user_claim.claim_name; dst->remote_user_claim.reg_exp = add->remote_user_claim.reg_exp; dst->remote_user_claim.replace = add->remote_user_claim.replace; } else { dst->remote_user_claim.claim_name = base->remote_user_claim.claim_name; dst->remote_user_claim.reg_exp = base->remote_user_claim.reg_exp; dst->remote_user_claim.replace = base->remote_user_claim.replace; } dst->verify_jwks_uri = add->verify_jwks_uri != NULL ? add->verify_jwks_uri : base->verify_jwks_uri; dst->verify_public_keys = oidc_jwk_list_copy(pool, add->verify_public_keys != NULL ? add->verify_public_keys : base->verify_public_keys); dst->verify_shared_keys = add->verify_shared_keys != NULL ? add->verify_shared_keys : base->verify_shared_keys; } void oidc_cfg_oauth_destroy(oidc_oauth_t *o) { oidc_jwk_list_destroy(o->verify_public_keys); oidc_jwk_list_destroy_hash(o->verify_shared_keys); } mod_auth_openidc-2.4.16.10/src/cfg/oauth.h000066400000000000000000000160331476721736500201700ustar00rootroot00000000000000/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ /*************************************************************************** * Copyright (C) 2017-2025 ZmartZone Holding BV * All rights reserved. * * DISCLAIMER OF WARRANTIES: * * THE SOFTWARE PROVIDED HEREUNDER IS PROVIDED ON AN "AS IS" BASIS, WITHOUT * ANY WARRANTIES OR REPRESENTATIONS EXPRESS, IMPLIED OR STATUTORY; INCLUDING, * WITHOUT LIMITATION, WARRANTIES OF QUALITY, PERFORMANCE, NONINFRINGEMENT, * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. NOR ARE THERE ANY * WARRANTIES CREATED BY A COURSE OR DEALING, COURSE OF PERFORMANCE OR TRADE * USAGE. FURTHERMORE, THERE ARE NO WARRANTIES THAT THE SOFTWARE WILL MEET * YOUR NEEDS OR BE FREE FROM ERRORS, OR THAT THE OPERATION OF THE SOFTWARE * WILL BE UNINTERRUPTED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * @Author: Hans Zandbelt - hans.zandbelt@openidc.com */ #ifndef _MOD_AUTH_OPENIDC_CFG_OAUTH_H_ #define _MOD_AUTH_OPENIDC_CFG_OAUTH_H_ #include "cfg/cfg.h" #define OIDCOAuthServerMetadataURL "OIDCOAuthServerMetadataURL" #define OIDCOAuthClientID "OIDCOAuthClientID" #define OIDCOAuthClientSecret "OIDCOAuthClientSecret" #define OIDCOAuthIntrospectionClientAuthBearerToken "OIDCOAuthIntrospectionClientAuthBearerToken" #define OIDCOAuthIntrospectionEndpoint "OIDCOAuthIntrospectionEndpoint" #define OIDCOAuthIntrospectionEndpointMethod "OIDCOAuthIntrospectionEndpointMethod" #define OIDCOAuthIntrospectionEndpointParams "OIDCOAuthIntrospectionEndpointParams" #define OIDCOAuthIntrospectionEndpointAuth "OIDCOAuthIntrospectionEndpointAuth" #define OIDCOAuthIntrospectionEndpointCert "OIDCOAuthIntrospectionEndpointCert" #define OIDCOAuthIntrospectionEndpointKey "OIDCOAuthIntrospectionEndpointKey" #define OIDCOAuthIntrospectionEndpointKeyPassword "OIDCOAuthIntrospectionEndpointKeyPassword" #define OIDCOAuthIntrospectionTokenParamName "OIDCOAuthIntrospectionTokenParamName" #define OIDCOAuthTokenExpiryClaim "OIDCOAuthTokenExpiryClaim" #define OIDCOAuthSSLValidateServer "OIDCOAuthSSLValidateServer" #define OIDCOAuthVerifyCertFiles "OIDCOAuthVerifyCertFiles" #define OIDCOAuthVerifySharedKeys "OIDCOAuthVerifySharedKeys" #define OIDCOAuthVerifyJwksUri "OIDCOAuthVerifyJwksUri" typedef enum { OIDC_TOKEN_EXPIRY_CLAIM_FORMAT_RELATIVE = 1, OIDC_TOKEN_EXPIRY_CLAIM_FORMAT_ABSOLUTE = 2 } oidc_oauth_introspection_token_expiry_claim_format_t; typedef enum { OIDC_TOKEN_EXPIRY_CLAIM_REQUIRED_MANDATORY = 1, OIDC_TOKEN_EXPIRY_CLAIM_REQUIRED_OPTIONAL = 2 } oidc_oauth_introspection_token_expiry_claim_required_t; typedef enum { OIDC_INTROSPECTION_METHOD_GET = 1, OIDC_INTROSPECTION_METHOD_POST = 2 } oidc_oauth_introspection_endpoint_method_t; #define OIDC_CFG_OAUTH_MEMBER_FUNC_GET_DECL(member, type) \ type OIDC_CFG_MEMBER_FUNC_NAME(member, cfg_oauth, get)(oidc_cfg_t * cfg); #define OIDC_CMD_OAUTH_MEMBER_FUNC_DECL(member, ...) \ const char *OIDC_CFG_MEMBER_FUNC_NAME(member, cmd_oauth, set)(cmd_parms *, void *, ##__VA_ARGS__); #define OIDC_CFG_OAUTH_MEMBER_FUNCS_DECL(member, type, ...) \ OIDC_CMD_OAUTH_MEMBER_FUNC_DECL(member, const char *, ##__VA_ARGS__); \ OIDC_CFG_OAUTH_MEMBER_FUNC_GET_DECL(member, type) #define OIDC_CFG_OAUTH_MEMBER_FUNC_SET_DECL(member) \ const char *OIDC_CFG_MEMBER_FUNC_NAME(member, cfg_oauth, set)(apr_pool_t *, oidc_cfg_t *, const char *); OIDC_CFG_OAUTH_MEMBER_FUNCS_DECL(ssl_validate_server, int) OIDC_CFG_OAUTH_MEMBER_FUNCS_DECL(metadata_url, const char *) OIDC_CFG_OAUTH_MEMBER_FUNCS_DECL(introspection_endpoint_url, const char *) OIDC_CFG_OAUTH_MEMBER_FUNCS_DECL(introspection_endpoint_params, const char *) OIDC_CFG_OAUTH_MEMBER_FUNCS_DECL(introspection_endpoint_auth, const char *) OIDC_CFG_OAUTH_MEMBER_FUNCS_DECL(introspection_endpoint_method, oidc_oauth_introspection_endpoint_method_t) OIDC_CFG_OAUTH_MEMBER_FUNCS_DECL(introspection_token_param_name, const char *) OIDC_CFG_OAUTH_MEMBER_FUNCS_DECL(introspection_endpoint_tls_client_cert, const char *) OIDC_CFG_OAUTH_MEMBER_FUNCS_DECL(introspection_endpoint_tls_client_key, const char *) OIDC_CFG_OAUTH_MEMBER_FUNCS_DECL(introspection_endpoint_tls_client_key_pwd, const char *) OIDC_CFG_OAUTH_MEMBER_FUNCS_DECL(client_id, const char *) OIDC_CFG_OAUTH_MEMBER_FUNCS_DECL(client_secret, const char *) OIDC_CFG_OAUTH_MEMBER_FUNCS_DECL(verify_jwks_uri, const char *) OIDC_CFG_OAUTH_MEMBER_FUNCS_DECL(verify_shared_keys, apr_hash_t *) OIDC_CFG_OAUTH_MEMBER_FUNCS_DECL(verify_public_keys, const apr_array_header_t *) OIDC_CFG_OAUTH_MEMBER_FUNCS_DECL(introspection_client_auth_bearer_token, const char *) // remote user claim, 3 args, 1 getter OIDC_CFG_OAUTH_MEMBER_FUNCS_DECL(remote_user_claim, oidc_remote_user_claim_t *, const char *, const char *) OIDC_CFG_OAUTH_MEMBER_FUNC_GET_DECL(remote_user_claim_name, const char *) // token expiry claim, 3 args, 3 getters OIDC_CMD_OAUTH_MEMBER_FUNC_DECL(token_expiry_claim, const char *, const char *, const char *) OIDC_CFG_OAUTH_MEMBER_FUNC_GET_DECL(introspection_token_expiry_claim_name, const char *) OIDC_CFG_OAUTH_MEMBER_FUNC_GET_DECL(introspection_token_expiry_claim_format, oidc_oauth_introspection_token_expiry_claim_format_t) OIDC_CFG_OAUTH_MEMBER_FUNC_GET_DECL(introspection_token_expiry_claim_required, oidc_oauth_introspection_token_expiry_claim_required_t) // needed in metadata.c OIDC_CFG_OAUTH_MEMBER_FUNC_SET_DECL(introspection_endpoint_url) OIDC_CFG_OAUTH_MEMBER_FUNC_SET_DECL(verify_jwks_uri) OIDC_CFG_OAUTH_MEMBER_FUNC_SET_DECL(introspection_endpoint_auth) typedef struct oidc_oauth_t oidc_oauth_t; oidc_oauth_t *oidc_cfg_oauth_create(apr_pool_t *pool); void oidc_cfg_oauth_merge(apr_pool_t *pool, oidc_oauth_t *dst, const oidc_oauth_t *base, const oidc_oauth_t *add); void oidc_cfg_oauth_destroy(oidc_oauth_t *o); #endif // _MOD_AUTH_OPENIDC_CFG_OAUTH_H_ mod_auth_openidc-2.4.16.10/src/cfg/parse.c000066400000000000000000000524121476721736500201560ustar00rootroot00000000000000/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ /*************************************************************************** * Copyright (C) 2017-2025 ZmartZone Holding BV * All rights reserved. * * DISCLAIMER OF WARRANTIES: * * THE SOFTWARE PROVIDED HEREUNDER IS PROVIDED ON AN "AS IS" BASIS, WITHOUT * ANY WARRANTIES OR REPRESENTATIONS EXPRESS, IMPLIED OR STATUTORY; INCLUDING, * WITHOUT LIMITATION, WARRANTIES OF QUALITY, PERFORMANCE, NONINFRINGEMENT, * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. NOR ARE THERE ANY * WARRANTIES CREATED BY A COURSE OR DEALING, COURSE OF PERFORMANCE OR TRADE * USAGE. FURTHERMORE, THERE ARE NO WARRANTIES THAT THE SOFTWARE WILL MEET * YOUR NEEDS OR BE FREE FROM ERRORS, OR THAT THE OPERATION OF THE SOFTWARE * WILL BE UNINTERRUPTED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * @Author: Hans Zandbelt - hans.zandbelt@openidc.com */ #include "cfg/parse.h" #include "cfg/dir.h" #include "const.h" #include "proto/proto.h" #include "util.h" #include #include #include /* separators used in "flattened" string/option lists */ #define OIDC_LIST_OPTIONS_START "[" #define OIDC_LIST_OPTIONS_END "]" #define OIDC_LIST_OPTIONS_SEPARATOR "|" #define OIDC_LIST_OPTIONS_QUOTE "'" /* * flatten the provided list of string options */ char *oidc_cfg_parse_flatten_options(apr_pool_t *pool, const char *options[]) { int i = 0; char *result = OIDC_LIST_OPTIONS_START; while (options[i] != NULL) { if (i == 0) result = apr_psprintf(pool, "%s%s%s%s", OIDC_LIST_OPTIONS_START, OIDC_LIST_OPTIONS_QUOTE, options[i], OIDC_LIST_OPTIONS_QUOTE); else result = apr_psprintf(pool, "%s%s%s%s%s", result, OIDC_LIST_OPTIONS_SEPARATOR, OIDC_LIST_OPTIONS_QUOTE, options[i], OIDC_LIST_OPTIONS_QUOTE); i++; } result = apr_psprintf(pool, "%s%s", result, OIDC_LIST_OPTIONS_END); return result; } /* * check if arg is a valid option in the list of provided string options */ const char *oidc_cfg_parse_is_valid_option(apr_pool_t *pool, const char *arg, const char *options[]) { int i = 0; while (options[i] != NULL) { if (_oidc_strcmp(arg, options[i]) == 0) break; i++; } if (options[i] == NULL) { return apr_psprintf(pool, "invalid value %s%s%s, must be one of %s", OIDC_LIST_OPTIONS_QUOTE, arg, OIDC_LIST_OPTIONS_QUOTE, oidc_cfg_parse_flatten_options(pool, options)); } return NULL; } /* * flatten the provided list of n options */ char *oidc_cfg_parse_options_flatten(apr_pool_t *pool, const oidc_cfg_option_t options[], int n) { char *result = apr_psprintf(pool, "%s%s%s%s", OIDC_LIST_OPTIONS_QUOTE, options[--n].str, OIDC_LIST_OPTIONS_QUOTE, OIDC_LIST_OPTIONS_END); for (--n; n >= 0; --n) result = apr_psprintf(pool, "%s%s%s%s%s", OIDC_LIST_OPTIONS_QUOTE, options[n].str, OIDC_LIST_OPTIONS_QUOTE, OIDC_LIST_OPTIONS_SEPARATOR, result); return apr_psprintf(pool, "%s%s", OIDC_LIST_OPTIONS_START, result); } /* * parse an value provided as an option string into the corresponding integer/enum */ static char *oidc_cfg_parse_option_impl(apr_pool_t *pool, const oidc_cfg_option_t options[], int n, const char *arg, int *v, int (*fstrcmp)(const char *, const char *)) { int i = 0; while ((i < n) && (fstrcmp(arg, options[i].str) != 0)) i++; if (i < n) { *v = options[i].val; return NULL; } return apr_psprintf(pool, "invalid value %s%s%s, must be one of %s", OIDC_LIST_OPTIONS_QUOTE, arg, OIDC_LIST_OPTIONS_QUOTE, oidc_cfg_parse_options_flatten(pool, options, n)); } /* * parse an value provided as an option string into the corresponding integer/enum case sensitive */ char *oidc_cfg_parse_option(apr_pool_t *pool, const oidc_cfg_option_t options[], int n, const char *arg, int *v) { return oidc_cfg_parse_option_impl(pool, options, n, arg, v, _oidc_strcmp); } /* * parse an value provided as an option string into the corresponding integer/enum case insensitive */ char *oidc_cfg_parse_option_ignore_case(apr_pool_t *pool, const oidc_cfg_option_t options[], int n, const char *arg, int *v) { return oidc_cfg_parse_option_impl(pool, options, n, arg, v, _oidc_strnatcasecmp); } /* * check if the provided integer value is between a specified minimum and maximum */ const char *oidc_cfg_parse_is_valid_int(apr_pool_t *pool, int value, int min_value, int max_value) { if (value < min_value) { return apr_psprintf(pool, "integer value %d is smaller than the minimum allowed value %d", value, min_value); } if (value > max_value) { return apr_psprintf(pool, "integer value %d is greater than the maximum allowed value %d", value, max_value); } return NULL; } /* * parse a string into a boolean */ const char *oidc_cfg_parse_boolean(apr_pool_t *pool, const char *arg, int *bool_value) { if ((_oidc_strnatcasecmp(arg, "true") == 0) || (_oidc_strnatcasecmp(arg, "on") == 0) || (_oidc_strnatcasecmp(arg, "yes") == 0) || (_oidc_strnatcasecmp(arg, "1") == 0)) { *bool_value = TRUE; return NULL; } if ((_oidc_strnatcasecmp(arg, "false") == 0) || (_oidc_strnatcasecmp(arg, "off") == 0) || (_oidc_strnatcasecmp(arg, "no") == 0) || (_oidc_strnatcasecmp(arg, "0") == 0)) { *bool_value = FALSE; return NULL; } return apr_psprintf(pool, "oidc_parse_boolean: could not parse boolean value from \"%s\"", arg); } /* * parse a string into an integer */ const char *oidc_cfg_parse_int(apr_pool_t *pool, const char *arg, int *int_value) { int v = -1; if ((arg == NULL) || (*arg == '\0') || (_oidc_strcmp(arg, "") == 0)) return apr_psprintf(pool, "no integer value"); if (sscanf(arg, "%d", &v) != 1) return apr_psprintf(pool, "invalid integer value: %s", arg); *int_value = v; return NULL; } /* * parse a string into an integer if it is in a valid min/max range */ const char *oidc_cfg_parse_int_min_max(apr_pool_t *pool, const char *arg, int *int_value, int min_value, int max_value) { int v = 0; const char *rv = NULL; rv = oidc_cfg_parse_int(pool, arg, &v); if (rv != NULL) return rv; rv = oidc_cfg_parse_is_valid_int(pool, v, min_value, max_value); if (rv != NULL) return rv; *int_value = v; return NULL; } /* * check if a string is a valid URL starting with either scheme1 or scheme2 (if not NULL) */ static const char *oidc_cfg_parse_is_valid_url_scheme(apr_pool_t *pool, const char *arg, const char *scheme1, const char *scheme2) { apr_uri_t uri; if (arg == NULL) return apr_psprintf(pool, "input cannot be empty"); if (apr_uri_parse(pool, arg, &uri) != APR_SUCCESS) return apr_psprintf(pool, "'%s' cannot be parsed as a URL", arg); if (uri.scheme == NULL) return apr_psprintf(pool, "'%s' cannot be parsed as a URL (no scheme set)", arg); if ((scheme1 != NULL) && (_oidc_strnatcasecmp(uri.scheme, scheme1) != 0)) { if ((scheme2 != NULL) && (_oidc_strnatcasecmp(uri.scheme, scheme2) != 0)) { return apr_psprintf(pool, "'%s' cannot be parsed as a \"%s\" or \"%s\" URL (scheme == %s)!", arg, scheme1, scheme2, uri.scheme); } else if (scheme2 == NULL) { return apr_psprintf(pool, "'%s' cannot be parsed as a \"%s\" URL (scheme == %s)!", arg, scheme1, uri.scheme); } } if (uri.hostname == NULL) return apr_psprintf(pool, "'%s' cannot be parsed as a valid URL (no hostname set, check your slashes)", arg); return NULL; } /* * check if a string is a valid URL string with the specified scheme */ const char *oidc_cfg_parse_is_valid_url(apr_pool_t *pool, const char *arg, const char *scheme) { return oidc_cfg_parse_is_valid_url_scheme(pool, arg, scheme, NULL); } /* * check if a string is a valid http or https URL */ const char *oidc_cfg_parse_is_valid_http_url(apr_pool_t *pool, const char *arg) { return oidc_cfg_parse_is_valid_url_scheme(pool, arg, "https", "http"); } #define OIDC_CFG_PARSE_STR_ERROR_MAX 128 /* * return an error retrieved from apr_strerror as a config error */ static const char *oidc_cfg_parse_io_error(apr_pool_t *pool, const char *action, const char *type, const char *name, apr_status_t rc) { char s_err[OIDC_CFG_PARSE_STR_ERROR_MAX]; return apr_psprintf(pool, "cannot %s %s %s: %s", action, type, name, apr_strerror(rc, s_err, OIDC_CFG_PARSE_STR_ERROR_MAX)); } /* * parse a string into a directory name if it exists and is accessible */ const char *oidc_cfg_parse_dirname(apr_pool_t *pool, const char *arg, char **value) { apr_status_t rc = APR_SUCCESS; apr_dir_t *dir = NULL; if (arg == NULL) return apr_psprintf(pool, "directory name cannot be empty"); if ((rc = apr_dir_open(&dir, arg, pool)) != APR_SUCCESS) return oidc_cfg_parse_io_error(pool, "access", "directory", arg, rc); if ((rc = apr_dir_close(dir)) != APR_SUCCESS) return oidc_cfg_parse_io_error(pool, "close", "directory", arg, rc); *value = apr_pstrdup(pool, arg); return NULL; } /* * parse a string into a file name if it exists and is accessible */ const char *oidc_cfg_parse_filename(apr_pool_t *pool, const char *arg, char **value) { apr_file_t *fd = NULL; apr_status_t rc = APR_SUCCESS; if (arg == NULL) return apr_psprintf(pool, "file name cannot be empty"); const char *filename = ap_server_root_relative(pool, arg); if ((rc = apr_file_open(&fd, filename, APR_FOPEN_READ, APR_OS_DEFAULT, pool)) != APR_SUCCESS) return oidc_cfg_parse_io_error(pool, "access", "file", filename, rc); if ((rc = apr_file_close(fd)) != APR_SUCCESS) return oidc_cfg_parse_io_error(pool, "close", "file", filename, rc); *value = apr_pstrdup(pool, filename); return NULL; } /* * parse a string a relative path or an absolute http/https URL */ const char *oidc_cfg_parse_relative_or_absolute_url(apr_pool_t *pool, const char *arg, char **value) { const char *rv = NULL; apr_uri_t uri; if (arg == NULL) return "input cannot be empty"; if (arg[0] == OIDC_CHAR_FORWARD_SLASH) { // relative uri if (apr_uri_parse(pool, arg, &uri) == APR_SUCCESS) *value = apr_pstrdup(pool, arg); else rv = apr_psprintf(pool, "could not parse relative URI \"%s\"", arg); } else { // absolute uri rv = oidc_cfg_parse_is_valid_http_url(pool, arg); if (rv == NULL) *value = apr_pstrdup(pool, arg); } return rv; } /* * check if the provided OAuth/OIDC response type is supported */ const char *oidc_cfg_parse_is_valid_response_type(apr_pool_t *pool, const char *arg) { if (oidc_proto_flow_is_supported(pool, arg) == FALSE) { return apr_psprintf(pool, "oidc_valid_response_type: type must be one of %s", apr_array_pstrcat(pool, oidc_proto_supported_flows(pool), OIDC_CHAR_PIPE)); } return NULL; } /* * check if the provided OAuth 2.0 response mode is supported */ const char *oidc_cfg_parse_is_valid_response_mode(apr_pool_t *pool, const char *arg) { static const char *options[] = {OIDC_PROTO_RESPONSE_MODE_FRAGMENT, OIDC_PROTO_RESPONSE_MODE_QUERY, OIDC_PROTO_RESPONSE_MODE_FORM_POST, NULL}; return oidc_cfg_parse_is_valid_option(pool, arg, options); } /* * check if the provided JWT signature algorithm is supported */ const char *oidc_cfg_parse_is_valid_signed_response_alg(apr_pool_t *pool, const char *arg) { if (oidc_jose_jws_algorithm_is_supported(pool, arg) == FALSE) { return apr_psprintf(pool, "unsupported/invalid signing algorithm '%s'; must be one of [%s]", arg, apr_array_pstrcat(pool, oidc_jose_jws_supported_algorithms(pool), OIDC_CHAR_PIPE)); } return NULL; } /* * check if the provided JWT content key encryption algorithm is supported */ const char *oidc_cfg_parse_is_valid_encrypted_response_alg(apr_pool_t *pool, const char *arg) { if (oidc_jose_jwe_algorithm_is_supported(pool, arg) == FALSE) { return apr_psprintf(pool, "unsupported/invalid encryption algorithm '%s'; must be one of [%s]", arg, apr_array_pstrcat(pool, oidc_jose_jwe_supported_algorithms(pool), OIDC_CHAR_PIPE)); } return NULL; } /* * check if the provided JWT encryption cipher is supported */ const char *oidc_cfg_parse_is_valid_encrypted_response_enc(apr_pool_t *pool, const char *arg) { if (oidc_jose_jwe_encryption_is_supported(pool, arg) == FALSE) { return apr_psprintf(pool, "unsupported/invalid encryption type '%s'; must be one of [%s]", arg, apr_array_pstrcat(pool, oidc_jose_jwe_supported_encryptions(pool), OIDC_CHAR_PIPE)); } return NULL; } /* * parse a base64url encoded binary value from the provided string */ static char *oidc_cfg_parse_base64url(apr_pool_t *pool, const char *input, char **output, int *output_len) { *output_len = oidc_util_base64url_decode(pool, output, input); if (*output_len <= 0) return apr_psprintf(pool, "base64url-decoding of \"%s\" failed", input); return NULL; } /* * parse a hexadecimal encoded binary value from the provided string */ static char *oidc_cfg_parse_hex(apr_pool_t *pool, const char *input, char **output, int *output_len) { *output_len = _oidc_strlen(input) / 2; const char *pos = input; unsigned char *val = apr_pcalloc(pool, *output_len); size_t count = 0; for (count = 0; (count < (*output_len) / sizeof(unsigned char)) && (pos != NULL); count++) { sscanf(pos, "%2hhx", &val[count]); pos += 2; } *output = (char *)val; return NULL; } #define OIDC_KEY_ENCODING_BASE64 "b64" #define OIDC_KEY_ENCODING_BASE64_URL "b64url" #define OIDC_KEY_ENCODING_HEX "hex" #define OIDC_KEY_ENCODING_PLAIN "plain" /* * parse a key value based on the provided encoding: b64|b64url|hex|plain */ static const char *oidc_cfg_parse_key_value(apr_pool_t *pool, const char *enc, const char *input, char **key, int *key_len) { static const char *options[] = {OIDC_KEY_ENCODING_BASE64, OIDC_KEY_ENCODING_BASE64_URL, OIDC_KEY_ENCODING_HEX, OIDC_KEY_ENCODING_PLAIN, NULL}; if (_oidc_strcmp(enc, OIDC_KEY_ENCODING_BASE64) == 0) return oidc_util_base64_decode(pool, input, key, key_len); if (_oidc_strcmp(enc, OIDC_KEY_ENCODING_BASE64_URL) == 0) return oidc_cfg_parse_base64url(pool, input, key, key_len); if (_oidc_strcmp(enc, OIDC_KEY_ENCODING_HEX) == 0) return oidc_cfg_parse_hex(pool, input, key, key_len); if (_oidc_strcmp(enc, OIDC_KEY_ENCODING_PLAIN) == 0) { *key = apr_pstrdup(pool, input); *key_len = _oidc_strlen(*key); return NULL; } // NB: when we get here we'll return an error displaying the valid options return oidc_cfg_parse_is_valid_option(pool, enc, options); } #define OIDC_KEY_TUPLE_SEPARATOR "#" #define OIDC_KEY_SIG_PREFIX OIDC_JOSE_JWK_SIG_STR ":" #define OIDC_KEY_ENC_PREFIX OIDC_JOSE_JWK_ENC_STR ":" /* * parse a :## tuple */ const char *oidc_cfg_parse_key_record(apr_pool_t *pool, const char *tuple, char **kid, char **key, int *key_len, char **use, apr_byte_t triplet) { const char *rv = NULL; char *s = NULL, *p = NULL, *q = NULL, *enc = NULL; if ((tuple == NULL) || (_oidc_strcmp(tuple, "") == 0)) return "tuple value not set"; if (use) { if (_oidc_strstr(tuple, OIDC_KEY_SIG_PREFIX) == tuple) { *use = OIDC_JOSE_JWK_SIG_STR; tuple += _oidc_strlen(OIDC_KEY_SIG_PREFIX); } else if (_oidc_strstr(tuple, OIDC_KEY_ENC_PREFIX) == tuple) { *use = OIDC_JOSE_JWK_ENC_STR; tuple += _oidc_strlen(OIDC_KEY_ENC_PREFIX); } } s = apr_pstrdup(pool, tuple); p = _oidc_strstr(s, OIDC_KEY_TUPLE_SEPARATOR); if (p && triplet) q = _oidc_strstr(p + 1, OIDC_KEY_TUPLE_SEPARATOR); if (p) { if (q) { *p = '\0'; *q = '\0'; enc = s; p++; if (p != q) *kid = apr_pstrdup(pool, p); rv = oidc_cfg_parse_key_value(pool, enc, q + 1, key, key_len); } else { *p = '\0'; *kid = s; *key = p + 1; *key_len = _oidc_strlen(*key); } } else { *kid = NULL; *key = s; *key_len = _oidc_strlen(*key); } return rv; } #define OIDC_ON_ERROR_502_STR "502_on_error" #define OIDC_ON_ERROR_REFRESH_STR "logout_on_error" #define OIDC_ON_ERROR_AUTH_STR "authenticate_on_error" /* * parse an "on access token refresh error" value from the provided strings */ const char *oidc_cfg_parse_action_on_error_refresh_as(apr_pool_t *pool, const char *arg, oidc_on_error_action_t *action) { static const oidc_cfg_option_t options[] = {{OIDC_ON_ERROR_502, OIDC_ON_ERROR_502_STR}, {OIDC_ON_ERROR_LOGOUT, OIDC_ON_ERROR_REFRESH_STR}, {OIDC_ON_ERROR_AUTH, OIDC_ON_ERROR_AUTH_STR}}; return oidc_cfg_parse_option(pool, options, OIDC_CFG_OPTIONS_SIZE(options), arg, (int *)action); } #if !(HAVE_APACHE_24) static char *ap_get_exec_line(apr_pool_t *p, const char *cmd, const char *const *argv) { char buf[MAX_STRING_LEN]; apr_procattr_t *procattr; apr_proc_t *proc; apr_file_t *fp; apr_size_t nbytes = 1; char c; int k; if (apr_procattr_create(&procattr, p) != APR_SUCCESS) return NULL; if (apr_procattr_io_set(procattr, APR_FULL_BLOCK, APR_FULL_BLOCK, APR_FULL_BLOCK) != APR_SUCCESS) return NULL; if (apr_procattr_dir_set(procattr, ap_make_dirstr_parent(p, cmd)) != APR_SUCCESS) return NULL; if (apr_procattr_cmdtype_set(procattr, APR_PROGRAM) != APR_SUCCESS) return NULL; proc = apr_pcalloc(p, sizeof(apr_proc_t)); if (apr_proc_create(proc, cmd, argv, NULL, procattr, p) != APR_SUCCESS) return NULL; fp = proc->out; if (fp == NULL) return NULL; /* XXX: we are reading 1 byte at a time here */ for (k = 0; apr_file_read(fp, &c, &nbytes) == APR_SUCCESS && nbytes == 1 && (k < MAX_STRING_LEN - 1);) { if (c == '\n' || c == '\r') break; buf[k++] = c; } buf[k] = '\0'; apr_file_close(fp); return apr_pstrndup(p, buf, k); } #endif /* * set a string value in the server config with exec support */ const char *oidc_cfg_parse_passphrase(apr_pool_t *pool, const char *arg, char **passphrase) { char **argv = NULL; char *result = NULL; int arglen = _oidc_strlen(arg); /* Based on code from mod_session_crypto. */ if (arglen > 5 && _oidc_strncmp(arg, "exec:", 5) == 0) { if (apr_tokenize_to_argv(arg + 5, &argv, pool) != APR_SUCCESS) { return apr_pstrcat(pool, "Unable to parse exec arguments from ", arg + 5, NULL); } argv[0] = ap_server_root_relative(pool, argv[0]); if (!argv[0]) { return apr_pstrcat(pool, "Invalid exec location:", arg + 5, NULL); } result = ap_get_exec_line(pool, argv[0], (const char *const *)argv); if (!result) { return apr_pstrcat(pool, "Unable to get passphrase from exec of ", arg + 5, NULL); } if (_oidc_strlen(result) == 0) return apr_pstrdup(pool, "the output of the passphrase generation command is empty " "(perhaps you need to pass it to bash -c \"\"?)"); *passphrase = apr_pstrdup(pool, result); } else { *passphrase = apr_pstrdup(pool, arg); } return NULL; } /* * add a public key from an X.509 file to our list of JWKs with public keys */ const char *oidc_cfg_parse_public_key_files(apr_pool_t *pool, const char *arg, apr_array_header_t **keys) { oidc_jwk_t *jwk = NULL; oidc_jose_error_t err; char *use = NULL; char *kid = NULL, *name = NULL, *fname = NULL; int fname_len; const char *rv = oidc_cfg_parse_key_record(pool, arg, &kid, &name, &fname_len, &use, FALSE); if (rv != NULL) return rv; rv = oidc_cfg_parse_filename(pool, name, &fname); if (rv != NULL) return rv; if (oidc_jwk_parse_pem_public_key(pool, kid, fname, &jwk, &err) == FALSE) { return apr_psprintf(pool, "oidc_jwk_parse_pem_public_key failed for (kid=%s) \"%s\": %s", kid, fname, oidc_jose_e2s(pool, err)); } if (*keys == NULL) *keys = apr_array_make(pool, 4, sizeof(oidc_jwk_t *)); if (use) jwk->use = apr_pstrdup(pool, use); APR_ARRAY_PUSH(*keys, oidc_jwk_t *) = jwk; return NULL; } /* * parse a triplet of 3 provided config values into a remote_user_claim struct */ const char *oidc_parse_remote_user_claim(apr_pool_t *pool, const char *v1, const char *v2, const char *v3, oidc_remote_user_claim_t *remote_user_claim) { remote_user_claim->claim_name = v1; if (v2) remote_user_claim->reg_exp = v2; if (v3) remote_user_claim->replace = v3; return NULL; } /* * parse a triplet of 3 provided config values into a http_timeout struct */ const char *oidc_cfg_parse_http_timeout(apr_pool_t *pool, const char *arg1, const char *arg2, const char *arg3, oidc_http_timeout_t *http_timeout) { char *s = NULL, *p = NULL; if (arg1) http_timeout->request_timeout = _oidc_str_to_int(arg1, http_timeout->request_timeout); if (arg2) http_timeout->connect_timeout = _oidc_str_to_int(arg2, http_timeout->connect_timeout); if (arg3) { s = apr_pstrdup(pool, arg3); p = _oidc_strstr(s, OIDC_STR_COLON); if (p) { *p = '\0'; p++; http_timeout->retry_interval = _oidc_str_to_int(p, http_timeout->retry_interval); } http_timeout->retries = _oidc_str_to_int(s, http_timeout->retries); } return NULL; } mod_auth_openidc-2.4.16.10/src/cfg/parse.h000066400000000000000000000116321476721736500201620ustar00rootroot00000000000000/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ /*************************************************************************** * Copyright (C) 2017-2025 ZmartZone Holding BV * All rights reserved. * * DISCLAIMER OF WARRANTIES: * * THE SOFTWARE PROVIDED HEREUNDER IS PROVIDED ON AN "AS IS" BASIS, WITHOUT * ANY WARRANTIES OR REPRESENTATIONS EXPRESS, IMPLIED OR STATUTORY; INCLUDING, * WITHOUT LIMITATION, WARRANTIES OF QUALITY, PERFORMANCE, NONINFRINGEMENT, * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. NOR ARE THERE ANY * WARRANTIES CREATED BY A COURSE OR DEALING, COURSE OF PERFORMANCE OR TRADE * USAGE. FURTHERMORE, THERE ARE NO WARRANTIES THAT THE SOFTWARE WILL MEET * YOUR NEEDS OR BE FREE FROM ERRORS, OR THAT THE OPERATION OF THE SOFTWARE * WILL BE UNINTERRUPTED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * @Author: Hans Zandbelt - hans.zandbelt@openidc.com */ #ifndef _MOD_AUTH_OPENIDC_CFG_PARSE_H_ #define _MOD_AUTH_OPENIDC_CFG_PARSE_H_ #include "cfg/cfg.h" typedef struct oidc_cfg_option_t { int val; char *str; } oidc_cfg_option_t; char *oidc_cfg_parse_option(apr_pool_t *pool, const oidc_cfg_option_t options[], int n, const char *arg, int *v); char *oidc_cfg_parse_option_ignore_case(apr_pool_t *pool, const oidc_cfg_option_t options[], int n, const char *arg, int *v); char *oidc_cfg_parse_options_flatten(apr_pool_t *pool, const oidc_cfg_option_t options[], int n); char *oidc_cfg_parse_flatten_options(apr_pool_t *pool, const char *options[]); const char *oidc_cfg_parse_is_valid_option(apr_pool_t *pool, const char *arg, const char *options[]); const char *oidc_cfg_parse_is_valid_int(apr_pool_t *pool, int value, int min_value, int max_value); const char *oidc_cfg_parse_is_valid_url(apr_pool_t *pool, const char *arg, const char *scheme); const char *oidc_cfg_parse_is_valid_http_url(apr_pool_t *pool, const char *arg); const char *oidc_cfg_parse_is_valid_response_type(apr_pool_t *pool, const char *arg); const char *oidc_cfg_parse_is_valid_response_mode(apr_pool_t *pool, const char *arg); const char *oidc_cfg_parse_is_valid_signed_response_alg(apr_pool_t *pool, const char *arg); const char *oidc_cfg_parse_is_valid_encrypted_response_alg(apr_pool_t *pool, const char *arg); const char *oidc_cfg_parse_is_valid_encrypted_response_enc(apr_pool_t *pool, const char *arg); const char *oidc_cfg_parse_boolean(apr_pool_t *pool, const char *arg, int *bool_value); const char *oidc_cfg_parse_int(apr_pool_t *pool, const char *arg, int *int_value); const char *oidc_cfg_parse_int_min_max(apr_pool_t *pool, const char *arg, int *int_value, int min_value, int max_value); const char *oidc_cfg_parse_dirname(apr_pool_t *pool, const char *arg, char **value); const char *oidc_cfg_parse_filename(apr_pool_t *pool, const char *arg, char **value); const char *oidc_cfg_parse_relative_or_absolute_url(apr_pool_t *pool, const char *arg, char **value); const char *oidc_cfg_parse_key_record(apr_pool_t *pool, const char *tuple, char **kid, char **key, int *key_len, char **use, apr_byte_t triplet); const char *oidc_cfg_parse_action_on_error_refresh_as(apr_pool_t *pool, const char *arg, oidc_on_error_action_t *action); const char *oidc_cfg_parse_passphrase(apr_pool_t *pool, const char *arg, char **passphrase); const char *oidc_cfg_parse_public_key_files(apr_pool_t *pool, const char *arg, apr_array_header_t **keys); typedef const char *(*oidc_valid_function_t)(apr_pool_t *, const char *); oidc_valid_function_t oidc_cfg_get_valid_endpoint_auth_function(oidc_cfg_t *cfg); const char *oidc_parse_remote_user_claim(apr_pool_t *pool, const char *v1, const char *v2, const char *v3, oidc_remote_user_claim_t *remote_user_claim); const char *oidc_cfg_parse_http_timeout(apr_pool_t *pool, const char *arg1, const char *arg2, const char *arg3, oidc_http_timeout_t *http_timeout); #endif // _MOD_AUTH_OPENIDC_CFG_PARSE_H_ mod_auth_openidc-2.4.16.10/src/cfg/provider.c000066400000000000000000001304001476721736500206700ustar00rootroot00000000000000/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ /*************************************************************************** * Copyright (C) 2017-2025 ZmartZone Holding BV * All rights reserved. * * DISCLAIMER OF WARRANTIES: * * THE SOFTWARE PROVIDED HEREUNDER IS PROVIDED ON AN "AS IS" BASIS, WITHOUT * ANY WARRANTIES OR REPRESENTATIONS EXPRESS, IMPLIED OR STATUTORY; INCLUDING, * WITHOUT LIMITATION, WARRANTIES OF QUALITY, PERFORMANCE, NONINFRINGEMENT, * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. NOR ARE THERE ANY * WARRANTIES CREATED BY A COURSE OR DEALING, COURSE OF PERFORMANCE OR TRADE * USAGE. FURTHERMORE, THERE ARE NO WARRANTIES THAT THE SOFTWARE WILL MEET * YOUR NEEDS OR BE FREE FROM ERRORS, OR THAT THE OPERATION OF THE SOFTWARE * WILL BE UNINTERRUPTED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * @Author: Hans Zandbelt - hans.zandbelt@openidc.com */ #include "cfg/provider.h" #include "cfg/cfg_int.h" #include "cfg/parse.h" #include "proto/proto.h" struct oidc_provider_t { char *metadata_url; char *issuer; char *authorization_endpoint_url; char *token_endpoint_url; char *token_endpoint_auth; char *token_endpoint_params; char *userinfo_endpoint_url; char *revocation_endpoint_url; char *registration_endpoint_url; char *pushed_authorization_request_endpoint_url; char *check_session_iframe; char *end_session_endpoint; oidc_jwks_uri_t jwks_uri; apr_array_header_t *verify_public_keys; char *client_id; char *client_secret; char *token_endpoint_tls_client_key; char *token_endpoint_tls_client_key_pwd; char *token_endpoint_tls_client_cert; int backchannel_logout_supported; // the next ones function as global default settings too int ssl_validate_server; int validate_issuer; char *client_name; char *client_contact; char *registration_token; char *registration_endpoint_json; char *scope; char *response_type; char *response_mode; int idtoken_iat_slack; char *auth_request_params; char *logout_request_params; int session_max_duration; oidc_proto_pkce_t *pkce; oidc_dpop_mode_t dpop_mode; int userinfo_refresh_interval; apr_array_header_t *client_keys; char *client_jwks_uri; char *id_token_signed_response_alg; char *id_token_encrypted_response_alg; char *id_token_encrypted_response_enc; apr_array_header_t *id_token_aud_values; char *userinfo_signed_response_alg; char *userinfo_encrypted_response_alg; char *userinfo_encrypted_response_enc; oidc_userinfo_token_method_t userinfo_token_method; char *request_object; oidc_auth_request_method_t auth_request_method; oidc_profile_t profile; int response_require_iss; }; #define OIDC_PROVIDER_MEMBER_FUNCS_TYPE_DEF(member, type, def_val) \ \ const char *oidc_cmd_provider_##member##_set(cmd_parms *cmd, void *ptr, const char *arg) { \ oidc_cfg_t *cfg = \ (oidc_cfg_t *)ap_get_module_config(cmd->server->module_config, &auth_openidc_module); \ const char *rv = oidc_cfg_provider_##member##_set(cmd->pool, cfg->provider, arg); \ return OIDC_CONFIG_DIR_RV(cmd, rv); \ } \ \ type oidc_cfg_provider_##member##_get(oidc_provider_t *provider) { \ return provider->member != NULL ? provider->member : def_val; \ } // simple string #define OIDC_PROVIDER_MEMBER_FUNCS_STR(member, def_val) \ \ const char *oidc_cfg_provider_##member##_set(apr_pool_t *pool, oidc_provider_t *provider, const char *arg) { \ provider->member = apr_pstrdup(pool, arg); \ return NULL; \ }; \ \ OIDC_PROVIDER_MEMBER_FUNCS_TYPE_DEF(member, const char *, def_val) #define OIDC_PROVIDER_MEMBER_FUNCS_FILE(member) \ const char *oidc_cfg_provider_##member##_set(apr_pool_t *pool, oidc_provider_t *provider, const char *arg) { \ return oidc_cfg_parse_filename(pool, arg, &provider->member); \ }; \ \ OIDC_PROVIDER_MEMBER_FUNCS_TYPE_DEF(member, const char *, NULL) #define OIDC_PROVIDER_MEMBER_GET_INT_DEF(member, type, def_val) \ type oidc_cfg_provider_##member##_get(oidc_provider_t *provider) { \ return provider->member != OIDC_CONFIG_POS_INT_UNSET ? provider->member : def_val; \ } // array of strings, int index #define OIDC_PROVIDER_MEMBER_FUNCS_STR_INT(member, fparse, type, def_val) \ void oidc_cfg_provider_##member##_int_set(oidc_provider_t *provider, type arg) { \ provider->member = arg; \ } \ \ const char *oidc_cfg_provider_##member##_set(apr_pool_t *pool, oidc_provider_t *provider, const char *arg) { \ const char *rv = NULL; \ type v; \ rv = fparse(pool, arg, &v); \ if (rv == NULL) \ provider->member = v; \ else \ provider->member = def_val; \ return rv; \ } \ \ const char *oidc_cmd_provider_##member##_set(cmd_parms *cmd, void *ptr, const char *arg) { \ oidc_cfg_t *cfg = \ (oidc_cfg_t *)ap_get_module_config(cmd->server->module_config, &auth_openidc_module); \ const char *rv = oidc_cfg_provider_##member##_set(cmd->pool, cfg->provider, arg); \ return OIDC_CONFIG_DIR_RV(cmd, rv); \ } \ \ OIDC_PROVIDER_MEMBER_GET_INT_DEF(member, type, def_val) // string with validation routine (for metadata) #define OIDC_PROVIDER_MEMBER_FUNCS_PARSE_STR(member, fvalid, def_val) \ const char *oidc_cfg_provider_##member##_valid(apr_pool_t *pool, const char *arg) { \ return fvalid(pool, arg); \ } \ \ const char *oidc_cfg_provider_##member##_set(apr_pool_t *pool, oidc_provider_t *provider, const char *arg) { \ const char *rv = oidc_cfg_provider_##member##_valid(pool, arg); \ if (rv == NULL) \ provider->member = apr_pstrdup(pool, arg); \ return rv; \ } \ \ OIDC_PROVIDER_MEMBER_FUNCS_TYPE_DEF(member, const char *, def_val) #define OIDC_PROVIDER_MEMBER_FUNCS_INT(member, fparse, min_val, max_val, def_val) \ \ const char *oidc_cfg_provider_##member##_valid(apr_pool_t *pool, int arg) { \ return oidc_cfg_parse_is_valid_int(pool, arg, min_val, max_val); \ } \ \ const char *oidc_cfg_provider_##member##_set(apr_pool_t *pool, oidc_provider_t *provider, int arg) { \ const char *rv = oidc_cfg_provider_##member##_valid(pool, arg); \ if (rv == NULL) \ provider->member = arg; \ else \ provider->member = def_val; \ return rv; \ } \ \ const char *oidc_cmd_provider_##member##_set(cmd_parms *cmd, void *ptr, const char *arg) { \ oidc_cfg_t *cfg = \ (oidc_cfg_t *)ap_get_module_config(cmd->server->module_config, &auth_openidc_module); \ int v = -1; \ const char *rv = fparse(cmd->pool, arg, &v); \ if (rv == NULL) \ rv = oidc_cfg_provider_##member##_set(cmd->pool, cfg->provider, v); \ return OIDC_CONFIG_DIR_RV(cmd, rv); \ } \ \ OIDC_PROVIDER_MEMBER_GET_INT_DEF(member, int, def_val) #define OIDC_PROVIDER_MEMBER_FUNCS_URL(member) \ OIDC_PROVIDER_MEMBER_FUNCS_PARSE_STR(member, oidc_cfg_parse_is_valid_http_url, NULL) #define OIDC_PROVIDER_MEMBER_FUNCS_FLAG(member, def_val) \ OIDC_PROVIDER_MEMBER_FUNCS_INT(member, oidc_cfg_parse_boolean, 0, 1, def_val) /* * passphrases */ #define OIDC_PROVIDER_TYPE_MEMBER_FUNCS_PASSPHRASE(member) \ \ const char *oidc_cfg_provider_##member##_set(apr_pool_t *pool, oidc_provider_t *provider, const char *arg) { \ return oidc_cfg_parse_passphrase(pool, arg, &provider->member); \ } \ \ OIDC_PROVIDER_MEMBER_FUNCS_TYPE_DEF(member, const char *, NULL) OIDC_PROVIDER_TYPE_MEMBER_FUNCS_PASSPHRASE(client_secret) OIDC_PROVIDER_TYPE_MEMBER_FUNCS_PASSPHRASE(token_endpoint_tls_client_key_pwd) /* * keys */ #define OIDC_PROVIDER_MEMBER_FUNCS_KEYS(member) \ \ const char *oidc_cfg_provider_##member##_set_keys(apr_pool_t *pool, oidc_provider_t *provider, \ apr_array_header_t *arg) { \ provider->member = arg; \ return NULL; \ } \ \ const char *oidc_cfg_provider_##member##_set(apr_pool_t *pool, oidc_provider_t *provider, const char *arg) { \ return oidc_cfg_parse_public_key_files(pool, arg, &provider->member); \ } \ OIDC_PROVIDER_MEMBER_FUNCS_TYPE_DEF(member, const apr_array_header_t *, NULL) OIDC_PROVIDER_MEMBER_FUNCS_KEYS(verify_public_keys) OIDC_PROVIDER_MEMBER_FUNCS_KEYS(client_keys) /* * string list */ #define OIDC_PROVIDER_MEMBER_FUNCS_STR_LIST(member) \ \ const char *oidc_cfg_provider_##member##_set_str_list(apr_pool_t *pool, oidc_provider_t *provider, \ apr_array_header_t *arg) { \ provider->member = arg; \ return NULL; \ } \ \ const char *oidc_cfg_provider_##member##_set(apr_pool_t *pool, oidc_provider_t *provider, const char *arg) { \ return oidc_cfg_string_list_add(pool, &provider->member, arg); \ } \ OIDC_PROVIDER_MEMBER_FUNCS_TYPE_DEF(member, const apr_array_header_t *, NULL) /* * id token aud values */ OIDC_PROVIDER_MEMBER_FUNCS_STR_LIST(id_token_aud_values) /* * PKCE */ #define OIDC_DEFAULT_PROVIDER_PKCE &oidc_pkce_s256 const char *oidc_cfg_provider_pkce_set(apr_pool_t *pool, oidc_provider_t *provider, const char *arg) { static const char *options[] = {OIDC_PKCE_METHOD_PLAIN, OIDC_PKCE_METHOD_S256, OIDC_PKCE_METHOD_NONE, NULL}; if (_oidc_strcmp(arg, OIDC_PKCE_METHOD_PLAIN) == 0) { provider->pkce = &oidc_pkce_plain; return NULL; } else if (_oidc_strcmp(arg, OIDC_PKCE_METHOD_S256) == 0) { provider->pkce = &oidc_pkce_s256; return NULL; } else if (_oidc_strcmp(arg, OIDC_PKCE_METHOD_NONE) == 0) { provider->pkce = &oidc_pkce_none; return NULL; } return oidc_cfg_parse_is_valid_option(pool, arg, options); } OIDC_PROVIDER_MEMBER_FUNCS_TYPE_DEF(pkce, const oidc_proto_pkce_t *, OIDC_DEFAULT_PROVIDER_PKCE) /* * DPoP */ #define OIDC_DPOP_MODE_OFF_STR "off" #define OIDC_DPOP_MODE_OPTIONAL_STR "optional" #define OIDC_DPOP_MODE_REQUIRED_STR "required" static const char *oidc_cfg_provider_parse_dop_method(apr_pool_t *pool, const char *arg, oidc_dpop_mode_t *mode) { static const oidc_cfg_option_t options[] = { {OIDC_DPOP_MODE_OFF, OIDC_DPOP_MODE_OFF_STR}, {OIDC_DPOP_MODE_OPTIONAL, OIDC_DPOP_MODE_OPTIONAL_STR}, {OIDC_DPOP_MODE_REQUIRED, OIDC_DPOP_MODE_REQUIRED_STR}, }; return oidc_cfg_parse_option(pool, options, OIDC_CFG_OPTIONS_SIZE(options), arg, (int *)mode); } #define OIDC_DEFAULT_DPOP_MODE OIDC_DPOP_MODE_OFF OIDC_PROVIDER_MEMBER_GET_INT_DEF(dpop_mode, oidc_dpop_mode_t, OIDC_DEFAULT_DPOP_MODE) void oidc_cfg_provider_dpop_mode_int_set(oidc_provider_t *provider, oidc_dpop_mode_t arg) { provider->dpop_mode = arg; } const char *oidc_cfg_provider_dpop_mode_set(apr_pool_t *pool, oidc_provider_t *provider, const char *arg) { const char *rv = NULL; oidc_dpop_mode_t v; rv = oidc_cfg_provider_parse_dop_method(pool, arg, &v); if (rv == NULL) provider->dpop_mode = v; else provider->dpop_mode = OIDC_DEFAULT_DPOP_MODE; return rv; } const char *oidc_cmd_provider_dpop_mode_set(cmd_parms *cmd, void *ptr, const char *arg1, const char *arg2) { oidc_cfg_t *cfg = (oidc_cfg_t *)ap_get_module_config(cmd->server->module_config, &auth_openidc_module); const char *rv = oidc_cfg_provider_dpop_mode_set(cmd->pool, cfg->provider, arg1); if ((rv == NULL) && (arg2)) rv = oidc_cfg_parse_boolean(cmd->pool, arg2, &cfg->dpop_api_enabled); return OIDC_CONFIG_DIR_RV(cmd, rv); } OIDC_PROVIDER_MEMBER_FUNCS_STR(issuer, NULL) OIDC_PROVIDER_MEMBER_FUNCS_URL(authorization_endpoint_url) OIDC_PROVIDER_MEMBER_FUNCS_STR(auth_request_params, NULL) OIDC_PROVIDER_MEMBER_FUNCS_URL(token_endpoint_url) OIDC_PROVIDER_MEMBER_FUNCS_STR(token_endpoint_params, NULL) OIDC_PROVIDER_MEMBER_FUNCS_URL(userinfo_endpoint_url) OIDC_PROVIDER_MEMBER_FUNCS_URL(registration_endpoint_url) OIDC_PROVIDER_MEMBER_FUNCS_URL(pushed_authorization_request_endpoint_url) OIDC_PROVIDER_MEMBER_FUNCS_URL(check_session_iframe) OIDC_PROVIDER_MEMBER_FUNCS_URL(end_session_endpoint) OIDC_PROVIDER_MEMBER_FUNCS_STR(client_contact, NULL) OIDC_PROVIDER_MEMBER_FUNCS_STR(client_id, NULL) OIDC_PROVIDER_MEMBER_FUNCS_URL(client_jwks_uri) OIDC_PROVIDER_MEMBER_FUNCS_STR(logout_request_params, NULL) OIDC_PROVIDER_MEMBER_FUNCS_URL(metadata_url) OIDC_PROVIDER_MEMBER_FUNCS_STR(registration_endpoint_json, NULL) OIDC_PROVIDER_MEMBER_FUNCS_STR(request_object, NULL) OIDC_PROVIDER_MEMBER_FUNCS_FILE(token_endpoint_tls_client_cert) OIDC_PROVIDER_MEMBER_FUNCS_FILE(token_endpoint_tls_client_key) /* default scope requested from the OP */ #define OIDC_DEFAULT_SCOPE "openid" OIDC_PROVIDER_MEMBER_FUNCS_STR(scope, OIDC_DEFAULT_SCOPE) #define OIDC_DEFAULT_CLIENT_NAME "OpenID Connect Apache Module (mod_auth_openidc)" OIDC_PROVIDER_MEMBER_FUNCS_STR(client_name, OIDC_DEFAULT_CLIENT_NAME) // TODO: no longer used as sid is also stored for frontchannel logout flows OIDC_PROVIDER_MEMBER_FUNCS_FLAG(backchannel_logout_supported, 0) #define OIDC_DEFAULT_SSL_VALIDATE_SERVER 1 OIDC_PROVIDER_MEMBER_FUNCS_FLAG(ssl_validate_server, OIDC_DEFAULT_SSL_VALIDATE_SERVER) #define OIDC_DEFAULT_VALIDATE_ISSUER 1 OIDC_PROVIDER_MEMBER_FUNCS_FLAG(validate_issuer, OIDC_DEFAULT_VALIDATE_ISSUER) // define whether the iss parameter will be required in the response to the redirect uri by default to mitigate the IDP // mixup attack only used from metadata in multi-provider setups #define OIDC_DEFAULT_PROVIDER_RESPONSE_REQUIRE_ISS 0 OIDC_PROVIDER_MEMBER_FUNCS_FLAG(response_require_iss, OIDC_DEFAULT_PROVIDER_RESPONSE_REQUIRE_ISS) // only used from metadata in multi-provider setups OIDC_PROVIDER_MEMBER_FUNCS_STR(registration_token, NULL) OIDC_PROVIDER_MEMBER_FUNCS_PARSE_STR(response_mode, oidc_cfg_parse_is_valid_response_mode, NULL) #define OIDC_DEFAULT_RESPONSE_TYPE OIDC_PROTO_CODE OIDC_PROVIDER_MEMBER_FUNCS_PARSE_STR(response_type, oidc_cfg_parse_is_valid_response_type, OIDC_DEFAULT_RESPONSE_TYPE) OIDC_PROVIDER_MEMBER_FUNCS_PARSE_STR(id_token_signed_response_alg, oidc_cfg_parse_is_valid_signed_response_alg, NULL) OIDC_PROVIDER_MEMBER_FUNCS_PARSE_STR(id_token_encrypted_response_alg, oidc_cfg_parse_is_valid_encrypted_response_alg, NULL) OIDC_PROVIDER_MEMBER_FUNCS_PARSE_STR(id_token_encrypted_response_enc, oidc_cfg_parse_is_valid_encrypted_response_enc, NULL) OIDC_PROVIDER_MEMBER_FUNCS_PARSE_STR(userinfo_signed_response_alg, oidc_cfg_parse_is_valid_signed_response_alg, NULL) OIDC_PROVIDER_MEMBER_FUNCS_PARSE_STR(userinfo_encrypted_response_alg, oidc_cfg_parse_is_valid_encrypted_response_alg, NULL) OIDC_PROVIDER_MEMBER_FUNCS_PARSE_STR(userinfo_encrypted_response_enc, oidc_cfg_parse_is_valid_encrypted_response_enc, NULL) #define OIDC_AUTH_REQUEST_METHOD_GET_STR "GET" #define OIDC_AUTH_REQUEST_METHOD_POST_STR "POST" #define OIDC_AUTH_REQUEST_METHOD_PAR_STR "PAR" static const char *oidc_cfg_provider_parse_auth_request_method(apr_pool_t *pool, const char *arg, oidc_auth_request_method_t *method) { static const oidc_cfg_option_t options[] = { {OIDC_AUTH_REQUEST_METHOD_GET, OIDC_AUTH_REQUEST_METHOD_GET_STR}, {OIDC_AUTH_REQUEST_METHOD_POST, OIDC_AUTH_REQUEST_METHOD_POST_STR}, {OIDC_AUTH_REQUEST_METHOD_PAR, OIDC_AUTH_REQUEST_METHOD_PAR_STR}, }; return oidc_cfg_parse_option(pool, options, OIDC_CFG_OPTIONS_SIZE(options), arg, (int *)method); } #define OIDC_DEFAULT_AUTH_REQUEST_METHOD OIDC_AUTH_REQUEST_METHOD_GET OIDC_PROVIDER_MEMBER_FUNCS_STR_INT(auth_request_method, oidc_cfg_provider_parse_auth_request_method, oidc_auth_request_method_t, OIDC_DEFAULT_AUTH_REQUEST_METHOD) #define OIDC_USER_INFO_TOKEN_METHOD_HEADER_STR "authz_header" #define OIDC_USER_INFO_TOKEN_METHOD_POST_STR "post_param" const char *oidc_cfg_provider_parse_userinfo_token_method(apr_pool_t *pool, const char *arg, oidc_userinfo_token_method_t *method) { static const oidc_cfg_option_t options[] = { {OIDC_USER_INFO_TOKEN_METHOD_HEADER, OIDC_USER_INFO_TOKEN_METHOD_HEADER_STR}, {OIDC_USER_INFO_TOKEN_METHOD_POST, OIDC_USER_INFO_TOKEN_METHOD_POST_STR}}; return oidc_cfg_parse_option(pool, options, OIDC_CFG_OPTIONS_SIZE(options), arg, (int *)method); } #define OIDC_DEFAULT_USER_INFO_TOKEN_METHOD OIDC_USER_INFO_TOKEN_METHOD_HEADER OIDC_PROVIDER_MEMBER_FUNCS_STR_INT(userinfo_token_method, oidc_cfg_provider_parse_userinfo_token_method, oidc_userinfo_token_method_t, OIDC_DEFAULT_USER_INFO_TOKEN_METHOD) #define OIDC_IDTOKEN_IAT_SLACK_MIN 0 #define OIDC_IDTOKEN_IAT_SLACK_MAX 3600 #define OIDC_DEFAULT_IDTOKEN_IAT_SLACK 600 OIDC_PROVIDER_MEMBER_FUNCS_INT(idtoken_iat_slack, oidc_cfg_parse_int, OIDC_IDTOKEN_IAT_SLACK_MIN, OIDC_IDTOKEN_IAT_SLACK_MAX, OIDC_DEFAULT_IDTOKEN_IAT_SLACK) #define OIDC_SESSION_MAX_DURATION_MIN 15 #define OIDC_SESSION_MAX_DURATION_MAX 3600 * 24 * 365 #define OIDC_DEFAULT_SESSION_MAX_DURATION 3600 * 8 const char *oidc_cfg_provider_session_max_duration_set(apr_pool_t *pool, oidc_provider_t *provider, int arg) { const char *rv = NULL; if (arg != 0) rv = oidc_cfg_parse_is_valid_int(pool, arg, OIDC_SESSION_MAX_DURATION_MIN, OIDC_SESSION_MAX_DURATION_MAX); if (rv == NULL) provider->session_max_duration = arg; else provider->session_max_duration = OIDC_DEFAULT_SESSION_MAX_DURATION; return rv; } const char *oidc_cmd_provider_session_max_duration_set(cmd_parms *cmd, void *ptr, const char *arg) { oidc_cfg_t *cfg = (oidc_cfg_t *)ap_get_module_config(cmd->server->module_config, &auth_openidc_module); int v = -1; const char *rv = oidc_cfg_parse_int(cmd->pool, arg, &v); if (rv == NULL) rv = oidc_cfg_provider_session_max_duration_set(cmd->pool, cfg->provider, v); return OIDC_CONFIG_DIR_RV(cmd, rv); } OIDC_PROVIDER_MEMBER_GET_INT_DEF(session_max_duration, int, OIDC_DEFAULT_SESSION_MAX_DURATION) #define OIDC_JWKS_REFRESH_INTERVAL_MIN 300 #define OIDC_JWKS_REFRESH_INTERVAL_MAX 3600 * 24 * 365 #define OIDC_DEFAULT_JWKS_REFRESH_INTERVAL 3600 const char *oidc_cfg_provider_jwks_uri_refresh_interval_set(apr_pool_t *pool, oidc_provider_t *provider, int arg) { const char *rv = oidc_cfg_parse_is_valid_int(pool, arg, OIDC_JWKS_REFRESH_INTERVAL_MIN, OIDC_JWKS_REFRESH_INTERVAL_MAX); if (rv == NULL) provider->jwks_uri.refresh_interval = arg; else provider->jwks_uri.refresh_interval = OIDC_DEFAULT_JWKS_REFRESH_INTERVAL; return rv; } const char *oidc_cmd_provider_jwks_uri_refresh_interval_set(cmd_parms *cmd, void *ptr, const char *arg) { oidc_cfg_t *cfg = (oidc_cfg_t *)ap_get_module_config(cmd->server->module_config, &auth_openidc_module); int v; const char *rv = oidc_cfg_parse_int(cmd->pool, arg, &v); if (rv == NULL) rv = oidc_cfg_provider_jwks_uri_refresh_interval_set(cmd->pool, cfg->provider, v); return OIDC_CONFIG_DIR_RV(cmd, rv); } int oidc_cfg_jwks_uri_refresh_interval_get(const oidc_jwks_uri_t *jwks_uri) { return jwks_uri->refresh_interval != OIDC_CONFIG_POS_INT_UNSET ? jwks_uri->refresh_interval : OIDC_DEFAULT_JWKS_REFRESH_INTERVAL; } int oidc_cfg_provider_jwks_uri_refresh_interval_get(oidc_provider_t *provider) { return provider->jwks_uri.refresh_interval != OIDC_CONFIG_POS_INT_UNSET ? provider->jwks_uri.refresh_interval : OIDC_DEFAULT_JWKS_REFRESH_INTERVAL; } const oidc_jwks_uri_t *oidc_cfg_provider_jwks_uri_get(oidc_provider_t *provider) { return &provider->jwks_uri; } const char *oidc_cfg_provider_jwks_uri_uri_get(oidc_provider_t *provider) { return provider->jwks_uri.uri; } const char *oidc_cfg_provider_jwks_uri_set(apr_pool_t *pool, oidc_provider_t *provider, const char *arg) { const char *rv = oidc_cfg_parse_is_valid_http_url(pool, arg); if (rv == NULL) provider->jwks_uri.uri = apr_pstrdup(pool, arg); return rv; } const char *oidc_cmd_provider_jwks_uri_set(cmd_parms *cmd, void *ptr, const char *arg) { oidc_cfg_t *cfg = (oidc_cfg_t *)ap_get_module_config(cmd->server->module_config, &auth_openidc_module); const char *rv = oidc_cfg_provider_jwks_uri_set(cmd->pool, cfg->provider, arg); return OIDC_CONFIG_DIR_RV(cmd, rv); } const char *oidc_cfg_provider_signed_jwks_uri_get(oidc_provider_t *provider) { return provider->jwks_uri.signed_uri; } apr_array_header_t *oidc_cfg_provider_signed_jwks_uri_keys_get(oidc_provider_t *provider) { return provider->jwks_uri.jwk_list; } const char *oidc_cfg_provider_signed_jwks_uri_keys_set(apr_pool_t *pool, oidc_provider_t *provider, json_t *json, apr_array_header_t *def_val) { const char *rv = NULL; oidc_jose_error_t err; if (json == NULL) goto end; if (oidc_is_jwk(json)) { oidc_jwk_t *jwk = NULL; if (oidc_jwk_parse_json(pool, json, &jwk, &err) != TRUE) { rv = apr_psprintf(pool, "oidc_jwk_parse_json failed for the specified JWK: %s", oidc_jose_e2s(pool, err)); goto end; } provider->jwks_uri.jwk_list = apr_array_make(pool, 1, sizeof(oidc_jwk_t *)); APR_ARRAY_PUSH(provider->jwks_uri.jwk_list, oidc_jwk_t *) = jwk; goto end; } if (oidc_is_jwks(json)) { if (oidc_jwks_parse_json(pool, json, &provider->jwks_uri.jwk_list, &err) != TRUE) rv = apr_psprintf(pool, "oidc_jwks_parse_json failed for the specified JWKs: %s", oidc_jose_e2s(pool, err)); goto end; } rv = apr_psprintf(pool, "invalid JWK/JWKs argument"); end: if (rv != NULL) provider->jwks_uri.jwk_list = def_val; return rv; } const char *oidc_cfg_provider_signed_jwks_uri_set(apr_pool_t *pool, oidc_provider_t *provider, const char *arg1, const char *arg2) { const char *rv = NULL; json_error_t json_error; json_t *json = NULL; if ((arg1 != NULL) && (_oidc_strcmp(arg1, "") != 0)) { rv = oidc_cfg_parse_is_valid_http_url(pool, arg1); if (rv != NULL) goto end; provider->jwks_uri.signed_uri = apr_pstrdup(pool, arg1); } if ((arg2 == NULL) || (_oidc_strcmp(arg2, "") == 0)) goto end; json = json_loads(arg2, 0, &json_error); if (json == NULL) { rv = apr_psprintf(pool, "json_loads failed for the 2nd argument: %s", json_error.text); goto end; } rv = oidc_cfg_provider_signed_jwks_uri_keys_set(pool, provider, json, NULL); end: if (json) json_decref(json); return rv; } const char *oidc_cmd_provider_signed_jwks_uri_set(cmd_parms *cmd, void *ptr, const char *arg1, const char *arg2) { oidc_cfg_t *cfg = (oidc_cfg_t *)ap_get_module_config(cmd->server->module_config, &auth_openidc_module); const char *rv = oidc_cfg_provider_signed_jwks_uri_set(cmd->pool, cfg->provider, arg1, arg2); return OIDC_CONFIG_DIR_RV(cmd, rv); } const char *oidc_cfg_provider_token_endpoint_auth_set(apr_pool_t *pool, oidc_cfg_t *cfg, oidc_provider_t *provider, const char *arg) { const char *rv = oidc_cfg_get_valid_endpoint_auth_function(cfg)(pool, arg); if (rv == NULL) provider->token_endpoint_auth = apr_pstrdup(pool, arg); return rv; } const char *oidc_cmd_provider_token_endpoint_auth_set(cmd_parms *cmd, void *ptr, const char *arg) { oidc_cfg_t *cfg = (oidc_cfg_t *)ap_get_module_config(cmd->server->module_config, &auth_openidc_module); const char *rv = oidc_cfg_provider_token_endpoint_auth_set(cmd->pool, cfg, cfg->provider, arg); return OIDC_CONFIG_DIR_RV(cmd, rv); } const char *oidc_cfg_provider_token_endpoint_auth_get(oidc_provider_t *provider) { return provider->token_endpoint_auth; } #define OIDC_USERINFO_REFRESH_INTERVAL_MIN 0 #define OIDC_USERINFO_REFRESH_INTERVAL_MAX 3600 * 24 * 365 #define OIDC_DEFAULT_USERINFO_REFRESH_INTERVAL -1 const char *oidc_cfg_provider_userinfo_refresh_interval_set(apr_pool_t *pool, oidc_provider_t *provider, int arg) { const char *rv = oidc_cfg_parse_is_valid_int(pool, arg, OIDC_USERINFO_REFRESH_INTERVAL_MIN, OIDC_USERINFO_REFRESH_INTERVAL_MAX); if (rv == NULL) provider->userinfo_refresh_interval = arg; else provider->userinfo_refresh_interval = OIDC_DEFAULT_USERINFO_REFRESH_INTERVAL; return rv; } const char *oidc_cmd_provider_userinfo_refresh_interval_set(cmd_parms *cmd, void *ptr, const char *arg1, const char *arg2) { oidc_cfg_t *cfg = (oidc_cfg_t *)ap_get_module_config(cmd->server->module_config, &auth_openidc_module); int v; const char *rv = oidc_cfg_parse_int(cmd->pool, arg1, &v); if (rv == NULL) rv = oidc_cfg_provider_userinfo_refresh_interval_set(cmd->pool, cfg->provider, v); if ((rv == NULL) && (arg2)) rv = oidc_cfg_parse_action_on_error_refresh_as( cmd->pool, arg2, (oidc_on_error_action_t *)&cfg->action_on_userinfo_error); return OIDC_CONFIG_DIR_RV(cmd, rv); } int oidc_cfg_provider_userinfo_refresh_interval_get(oidc_provider_t *provider) { return provider->userinfo_refresh_interval != OIDC_CONFIG_POS_INT_UNSET ? provider->userinfo_refresh_interval : OIDC_DEFAULT_USERINFO_REFRESH_INTERVAL; } /* * revocation endpoint url, must allow empty string in base config */ const char *oidc_cfg_provider_revocation_endpoint_url_set(apr_pool_t *pool, oidc_provider_t *provider, const char *arg) { const char *rv = oidc_cfg_parse_is_valid_http_url(pool, arg); if (rv == NULL) provider->revocation_endpoint_url = apr_pstrdup(pool, arg); return rv; } const char *oidc_cmd_provider_revocation_endpoint_url_set(cmd_parms *cmd, void *ptr, const char *args) { oidc_cfg_t *cfg = (oidc_cfg_t *)ap_get_module_config(cmd->server->module_config, &auth_openidc_module); char *w = ap_getword_conf(cmd->pool, &args); if (*w == '\0' || *args != 0) { cfg->provider->revocation_endpoint_url = ""; return NULL; } return oidc_cfg_provider_revocation_endpoint_url_set(cmd->pool, cfg->provider, w); } const char *oidc_cfg_provider_revocation_endpoint_url_get(oidc_provider_t *provider) { return provider->revocation_endpoint_url; } #define OIDC_PROFILE_OIDC10_STR "OIDC10" #define OIDC_PROFILE_FAPI20_STR "FAPI20" const char *oidc_cfg_provider_parse_profile(apr_pool_t *pool, const char *arg, oidc_profile_t *profile) { static const oidc_cfg_option_t options[] = {{OIDC_PROFILE_OIDC10, OIDC_PROFILE_OIDC10_STR}, {OIDC_PROFILE_FAPI20, OIDC_PROFILE_FAPI20_STR}}; return oidc_cfg_parse_option(pool, options, OIDC_CFG_OPTIONS_SIZE(options), arg, (int *)profile); } #define OIDC_DEFAULT_PROFILE OIDC_PROFILE_OIDC10 OIDC_PROVIDER_MEMBER_FUNCS_STR_INT(profile, oidc_cfg_provider_parse_profile, oidc_profile_t, OIDC_DEFAULT_PROFILE) /* * base */ static void oidc_cfg_provider_init(oidc_provider_t *provider) { provider->metadata_url = NULL; provider->issuer = NULL; provider->authorization_endpoint_url = NULL; provider->token_endpoint_url = NULL; provider->token_endpoint_auth = NULL; provider->token_endpoint_params = NULL; provider->userinfo_endpoint_url = NULL; provider->revocation_endpoint_url = NULL; provider->client_id = NULL; provider->client_secret = NULL; provider->token_endpoint_tls_client_cert = NULL; provider->token_endpoint_tls_client_key = NULL; provider->token_endpoint_tls_client_key_pwd = NULL; provider->registration_endpoint_url = NULL; provider->registration_endpoint_json = NULL; provider->pushed_authorization_request_endpoint_url = NULL; provider->check_session_iframe = NULL; provider->end_session_endpoint = NULL; provider->jwks_uri.uri = NULL; provider->jwks_uri.refresh_interval = OIDC_CONFIG_POS_INT_UNSET; provider->jwks_uri.signed_uri = NULL; provider->jwks_uri.jwk_list = NULL; provider->verify_public_keys = NULL; provider->backchannel_logout_supported = OIDC_CONFIG_POS_INT_UNSET; provider->ssl_validate_server = OIDC_CONFIG_POS_INT_UNSET; provider->validate_issuer = OIDC_CONFIG_POS_INT_UNSET; provider->client_name = NULL; provider->client_contact = NULL; provider->registration_token = NULL; provider->scope = NULL; provider->response_type = NULL; provider->response_mode = NULL; provider->idtoken_iat_slack = OIDC_CONFIG_POS_INT_UNSET; provider->session_max_duration = OIDC_CONFIG_POS_INT_UNSET; provider->auth_request_params = NULL; provider->logout_request_params = NULL; provider->pkce = NULL; provider->dpop_mode = OIDC_CONFIG_POS_INT_UNSET; provider->client_jwks_uri = NULL; provider->client_keys = NULL; provider->id_token_signed_response_alg = NULL; provider->id_token_encrypted_response_alg = NULL; provider->id_token_encrypted_response_enc = NULL; provider->userinfo_signed_response_alg = NULL; provider->userinfo_encrypted_response_alg = NULL; provider->userinfo_encrypted_response_enc = NULL; provider->userinfo_token_method = OIDC_CONFIG_POS_INT_UNSET; provider->auth_request_method = OIDC_CONFIG_POS_INT_UNSET; provider->userinfo_refresh_interval = OIDC_CONFIG_POS_INT_UNSET; provider->request_object = NULL; provider->response_require_iss = OIDC_CONFIG_POS_INT_UNSET; provider->id_token_aud_values = NULL; provider->profile = OIDC_CONFIG_POS_INT_UNSET; } void oidc_cfg_provider_merge(apr_pool_t *pool, oidc_provider_t *dst, const oidc_provider_t *base, const oidc_provider_t *add) { dst->metadata_url = add->metadata_url != NULL ? add->metadata_url : base->metadata_url; dst->issuer = add->issuer != NULL ? add->issuer : base->issuer; dst->authorization_endpoint_url = add->authorization_endpoint_url != NULL ? add->authorization_endpoint_url : base->authorization_endpoint_url; dst->token_endpoint_url = add->token_endpoint_url != NULL ? add->token_endpoint_url : base->token_endpoint_url; dst->token_endpoint_auth = add->token_endpoint_auth != NULL ? add->token_endpoint_auth : base->token_endpoint_auth; dst->token_endpoint_params = add->token_endpoint_params != NULL ? add->token_endpoint_params : base->token_endpoint_params; dst->userinfo_endpoint_url = add->userinfo_endpoint_url != NULL ? add->userinfo_endpoint_url : base->userinfo_endpoint_url; dst->revocation_endpoint_url = add->revocation_endpoint_url != NULL ? add->revocation_endpoint_url : base->revocation_endpoint_url; dst->jwks_uri.uri = add->jwks_uri.uri != NULL ? add->jwks_uri.uri : base->jwks_uri.uri; dst->jwks_uri.refresh_interval = add->jwks_uri.refresh_interval != OIDC_CONFIG_POS_INT_UNSET ? add->jwks_uri.refresh_interval : base->jwks_uri.refresh_interval; dst->jwks_uri.signed_uri = add->jwks_uri.signed_uri != NULL ? add->jwks_uri.signed_uri : base->jwks_uri.signed_uri; dst->jwks_uri.jwk_list = oidc_jwk_list_copy(pool, add->jwks_uri.jwk_list != NULL ? add->jwks_uri.jwk_list : base->jwks_uri.jwk_list); dst->verify_public_keys = oidc_jwk_list_copy(pool, add->verify_public_keys != NULL ? add->verify_public_keys : base->verify_public_keys); dst->client_id = add->client_id != NULL ? add->client_id : base->client_id; dst->client_secret = add->client_secret != NULL ? add->client_secret : base->client_secret; dst->token_endpoint_tls_client_key = add->token_endpoint_tls_client_key != NULL ? add->token_endpoint_tls_client_key : base->token_endpoint_tls_client_key; dst->token_endpoint_tls_client_key_pwd = add->token_endpoint_tls_client_key_pwd != NULL ? add->token_endpoint_tls_client_key_pwd : base->token_endpoint_tls_client_key_pwd; dst->token_endpoint_tls_client_cert = add->token_endpoint_tls_client_cert != NULL ? add->token_endpoint_tls_client_cert : base->token_endpoint_tls_client_cert; dst->registration_endpoint_url = add->registration_endpoint_url != NULL ? add->registration_endpoint_url : base->registration_endpoint_url; dst->registration_endpoint_json = add->registration_endpoint_json != NULL ? add->registration_endpoint_json : base->registration_endpoint_json; dst->pushed_authorization_request_endpoint_url = add->pushed_authorization_request_endpoint_url != NULL ? add->pushed_authorization_request_endpoint_url : base->pushed_authorization_request_endpoint_url; dst->check_session_iframe = add->check_session_iframe != NULL ? add->check_session_iframe : base->check_session_iframe; dst->end_session_endpoint = add->end_session_endpoint != NULL ? add->end_session_endpoint : base->end_session_endpoint; dst->backchannel_logout_supported = add->backchannel_logout_supported != OIDC_CONFIG_POS_INT_UNSET ? add->backchannel_logout_supported : base->backchannel_logout_supported; dst->ssl_validate_server = add->ssl_validate_server != OIDC_CONFIG_POS_INT_UNSET ? add->ssl_validate_server : base->ssl_validate_server; dst->validate_issuer = add->validate_issuer != OIDC_CONFIG_POS_INT_UNSET ? add->validate_issuer : base->validate_issuer; dst->client_name = add->client_name != NULL ? add->client_name : base->client_name; dst->client_contact = add->client_contact != NULL ? add->client_contact : base->client_contact; dst->registration_token = add->registration_token != NULL ? add->registration_token : base->registration_token; dst->scope = add->scope != NULL ? add->scope : base->scope; dst->response_type = add->response_type != NULL ? add->response_type : base->response_type; dst->response_mode = add->response_mode != NULL ? add->response_mode : base->response_mode; dst->idtoken_iat_slack = add->idtoken_iat_slack != OIDC_CONFIG_POS_INT_UNSET ? add->idtoken_iat_slack : base->idtoken_iat_slack; dst->session_max_duration = add->session_max_duration != OIDC_CONFIG_POS_INT_UNSET ? add->session_max_duration : base->session_max_duration; dst->auth_request_params = add->auth_request_params != NULL ? add->auth_request_params : base->auth_request_params; dst->logout_request_params = add->logout_request_params != NULL ? add->logout_request_params : base->logout_request_params; dst->pkce = add->pkce != NULL ? add->pkce : base->pkce; dst->dpop_mode = add->dpop_mode != OIDC_CONFIG_POS_INT_UNSET ? add->dpop_mode : base->dpop_mode; dst->client_jwks_uri = add->client_jwks_uri != NULL ? add->client_jwks_uri : base->client_jwks_uri; dst->client_keys = oidc_jwk_list_copy(pool, add->client_keys != NULL ? add->client_keys : base->client_keys); dst->id_token_signed_response_alg = add->id_token_signed_response_alg != NULL ? add->id_token_signed_response_alg : base->id_token_signed_response_alg; dst->id_token_encrypted_response_alg = add->id_token_encrypted_response_alg != NULL ? add->id_token_encrypted_response_alg : base->id_token_encrypted_response_alg; dst->id_token_encrypted_response_enc = add->id_token_encrypted_response_enc != NULL ? add->id_token_encrypted_response_enc : base->id_token_encrypted_response_enc; dst->userinfo_signed_response_alg = add->userinfo_signed_response_alg != NULL ? add->userinfo_signed_response_alg : base->userinfo_signed_response_alg; dst->userinfo_encrypted_response_alg = add->userinfo_encrypted_response_alg != NULL ? add->userinfo_encrypted_response_alg : base->userinfo_encrypted_response_alg; dst->userinfo_encrypted_response_enc = add->userinfo_encrypted_response_enc != NULL ? add->userinfo_encrypted_response_enc : base->userinfo_encrypted_response_enc; dst->userinfo_token_method = add->userinfo_token_method != OIDC_CONFIG_POS_INT_UNSET ? add->userinfo_token_method : base->userinfo_token_method; dst->auth_request_method = add->auth_request_method != OIDC_CONFIG_POS_INT_UNSET ? add->auth_request_method : base->auth_request_method; dst->userinfo_refresh_interval = add->userinfo_refresh_interval != OIDC_CONFIG_POS_INT_UNSET ? add->userinfo_refresh_interval : base->userinfo_refresh_interval; dst->request_object = add->request_object != NULL ? add->request_object : base->request_object; dst->response_require_iss = add->response_require_iss != OIDC_CONFIG_POS_INT_UNSET ? add->response_require_iss : base->response_require_iss; dst->id_token_aud_values = add->id_token_aud_values != NULL ? add->id_token_aud_values : base->id_token_aud_values; dst->profile = add->profile != OIDC_CONFIG_POS_INT_UNSET ? add->profile : base->profile; } oidc_provider_t *oidc_cfg_provider_create(apr_pool_t *pool) { oidc_provider_t *provider = apr_pcalloc(pool, sizeof(oidc_provider_t)); oidc_cfg_provider_init(provider); return provider; } oidc_provider_t *oidc_cfg_provider_copy(apr_pool_t *pool, const oidc_provider_t *src) { oidc_provider_t *dst = oidc_cfg_provider_create(pool); oidc_cfg_provider_merge(pool, dst, dst, src); return dst; } void oidc_cfg_provider_destroy(oidc_provider_t *provider) { oidc_jwk_list_destroy(provider->jwks_uri.jwk_list); oidc_jwk_list_destroy(provider->verify_public_keys); oidc_jwk_list_destroy(provider->client_keys); } mod_auth_openidc-2.4.16.10/src/cfg/provider.h000066400000000000000000000343231476721736500207040ustar00rootroot00000000000000/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ /*************************************************************************** * Copyright (C) 2017-2025 ZmartZone Holding BV * All rights reserved. * * DISCLAIMER OF WARRANTIES: * * THE SOFTWARE PROVIDED HEREUNDER IS PROVIDED ON AN "AS IS" BASIS, WITHOUT * ANY WARRANTIES OR REPRESENTATIONS EXPRESS, IMPLIED OR STATUTORY; INCLUDING, * WITHOUT LIMITATION, WARRANTIES OF QUALITY, PERFORMANCE, NONINFRINGEMENT, * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. NOR ARE THERE ANY * WARRANTIES CREATED BY A COURSE OR DEALING, COURSE OF PERFORMANCE OR TRADE * USAGE. FURTHERMORE, THERE ARE NO WARRANTIES THAT THE SOFTWARE WILL MEET * YOUR NEEDS OR BE FREE FROM ERRORS, OR THAT THE OPERATION OF THE SOFTWARE * WILL BE UNINTERRUPTED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * @Author: Hans Zandbelt - hans.zandbelt@openidc.com */ #ifndef _MOD_AUTH_OPENIDC_CFG_PROVIDER_H_ #define _MOD_AUTH_OPENIDC_CFG_PROVIDER_H_ #include "cfg/cfg.h" typedef apr_byte_t (*oidc_proto_pkce_state)(request_rec *r, char **state); typedef apr_byte_t (*oidc_proto_pkce_challenge)(request_rec *r, const char *state, char **code_challenge); typedef apr_byte_t (*oidc_proto_pkce_verifier)(request_rec *r, const char *state, char **code_verifier); typedef struct oidc_proto_pkce_t { const char *method; oidc_proto_pkce_state state; oidc_proto_pkce_verifier verifier; oidc_proto_pkce_challenge challenge; } oidc_proto_pkce_t; #define OIDC_PKCE_METHOD_PLAIN "plain" #define OIDC_PKCE_METHOD_S256 "S256" #define OIDC_PKCE_METHOD_NONE "none" #define OIDC_ENDPOINT_AUTH_CLIENT_SECRET_BASIC "client_secret_basic" #define OIDC_ENDPOINT_AUTH_CLIENT_SECRET_POST "client_secret_post" #define OIDC_ENDPOINT_AUTH_CLIENT_SECRET_JWT "client_secret_jwt" #define OIDC_ENDPOINT_AUTH_PRIVATE_KEY_JWT "private_key_jwt" #define OIDC_ENDPOINT_AUTH_BEARER_ACCESS_TOKEN "bearer_access_token" #define OIDC_ENDPOINT_AUTH_NONE "none" /* HTTP methods to send authentication requests */ typedef enum { OIDC_AUTH_REQUEST_METHOD_GET = 1, OIDC_AUTH_REQUEST_METHOD_POST = 2, OIDC_AUTH_REQUEST_METHOD_PAR = 3, } oidc_auth_request_method_t; /* methods to send an access token in a userinfo request */ typedef enum { OIDC_USER_INFO_TOKEN_METHOD_HEADER = 1, OIDC_USER_INFO_TOKEN_METHOD_POST = 2, } oidc_userinfo_token_method_t; typedef enum { OIDC_DPOP_MODE_OFF = 1, OIDC_DPOP_MODE_OPTIONAL = 2, OIDC_DPOP_MODE_REQUIRED = 3, } oidc_dpop_mode_t; typedef struct oidc_jwks_uri_t { const char *uri; int refresh_interval; const char *signed_uri; apr_array_header_t *jwk_list; } oidc_jwks_uri_t; typedef enum { OIDC_PROFILE_OIDC10 = 1, OIDC_PROFILE_FAPI20 = 2, } oidc_profile_t; // NB: need the primitive strings and the declarations of the custom // set routines here because the commands are included in config.c. // via include "cmds.inc" #define OIDCProviderMetadataURL "OIDCProviderMetadataURL" #define OIDCProviderIssuer "OIDCProviderIssuer" #define OIDCProviderAuthorizationEndpoint "OIDCProviderAuthorizationEndpoint" #define OIDCProviderTokenEndpoint "OIDCProviderTokenEndpoint" #define OIDCProviderTokenEndpointAuth "OIDCProviderTokenEndpointAuth" #define OIDCProviderTokenEndpointParams "OIDCProviderTokenEndpointParams" #define OIDCProviderRegistrationEndpointJson "OIDCProviderRegistrationEndpointJson" #define OIDCProviderUserInfoEndpoint "OIDCProviderUserInfoEndpoint" #define OIDCProviderRevocationEndpoint "OIDCProviderRevocationEndpoint" #define OIDCProviderPushedAuthorizationRequestEndpoint "OIDCProviderPushedAuthorizationRequestEndpoint" #define OIDCProviderCheckSessionIFrame "OIDCProviderCheckSessionIFrame" #define OIDCProviderEndSessionEndpoint "OIDCProviderEndSessionEndpoint" #define OIDCProviderBackChannelLogoutSupported "OIDCProviderBackChannelLogoutSupported" #define OIDCProviderJwksUri "OIDCProviderJwksUri" #define OIDCProviderSignedJwksUri "OIDCProviderSignedJwksUri" #define OIDCProviderVerifyCertFiles "OIDCProviderVerifyCertFiles" #define OIDCResponseType "OIDCResponseType" #define OIDCProviderAuthRequestMethod "OIDCProviderAuthRequestMethod" #define OIDCProfile "OIDCProfile" #define OIDCPKCEMethod "OIDCPKCEMethod" #define OIDCDPoPMode "OIDCDPoPMode" #define OIDCResponseMode "OIDCResponseMode" #define OIDCClientJwksUri "OIDCClientJwksUri" #define OIDCIDTokenSignedResponseAlg "OIDCIDTokenSignedResponseAlg" #define OIDCIDTokenEncryptedResponseAlg "OIDCIDTokenEncryptedResponseAlg" #define OIDCIDTokenEncryptedResponseEnc "OIDCIDTokenEncryptedResponseEnc" #define OIDCIDTokenAudValues "OIDCIDTokenAudValues" #define OIDCUserInfoSignedResponseAlg "OIDCUserInfoSignedResponseAlg" #define OIDCUserInfoEncryptedResponseAlg "OIDCUserInfoEncryptedResponseAlg" #define OIDCUserInfoEncryptedResponseEnc "OIDCUserInfoEncryptedResponseEnc" #define OIDCUserInfoTokenMethod "OIDCUserInfoTokenMethod" #define OIDCSSLValidateServer "OIDCSSLValidateServer" #define OIDCValidateIssuer "OIDCValidateIssuer" #define OIDCClientName "OIDCClientName" #define OIDCClientContact "OIDCClientContact" #define OIDCScope "OIDCScope" #define OIDCJWKSRefreshInterval "OIDCJWKSRefreshInterval" #define OIDCIDTokenIatSlack "OIDCIDTokenIatSlack" #define OIDCSessionMaxDuration "OIDCSessionMaxDuration" #define OIDCAuthRequestParams "OIDCAuthRequestParams" #define OIDCLogoutRequestParams "OIDCLogoutRequestParams" #define OIDCClientID "OIDCClientID" #define OIDCClientSecret "OIDCClientSecret" #define OIDCClientTokenEndpointCert "OIDCClientTokenEndpointCert" #define OIDCClientTokenEndpointKey "OIDCClientTokenEndpointKey" #define OIDCClientTokenEndpointKeyPassword "OIDCClientTokenEndpointKeyPassword" #define OIDCUserInfoRefreshInterval "OIDCUserInfoRefreshInterval" #define OIDCRequestObject "OIDCRequestObject" #define OIDC_CMD_PROVIDER_MEMBER_FUNC_DECL(member, ...) \ const char *OIDC_CFG_MEMBER_FUNC_NAME(member, cmd_provider, set)(cmd_parms *, void *, const char *, \ ##__VA_ARGS__); #define OIDC_CFG_PROVIDER_MEMBER_FUNC_SET_DECL(member, type, ...) \ const char *OIDC_CFG_MEMBER_FUNC_NAME(member, cfg_provider, set)(apr_pool_t *, oidc_provider_t *, type, \ ##__VA_ARGS__); #define OIDC_CFG_PROVIDER_MEMBER_FUNC_GET_DECL(member, type) \ type OIDC_CFG_MEMBER_FUNC_NAME(member, cfg_provider, get)(oidc_provider_t *); #define OIDC_CFG_PROVIDER_MEMBER_FUNCS_DECL(member, itype, rtype, ...) \ OIDC_CMD_PROVIDER_MEMBER_FUNC_DECL(member, ##__VA_ARGS__) \ OIDC_CFG_PROVIDER_MEMBER_FUNC_SET_DECL(member, itype) \ OIDC_CFG_PROVIDER_MEMBER_FUNC_GET_DECL(member, rtype) #define OIDC_CFG_PROVIDER_MEMBER_FUNCS_INT_DECL(member, ...) \ OIDC_CFG_PROVIDER_MEMBER_FUNCS_DECL(member, int, int, ##__VA_ARGS__) #define OIDC_CFG_PROVIDER_MEMBER_FUNCS_TYPE_DECL(member, type, ...) \ OIDC_CFG_PROVIDER_MEMBER_FUNCS_DECL(member, const char *, type, ##__VA_ARGS__) #define OIDC_CFG_PROVIDER_MEMBER_FUNCS_STR_DECL(member, ...) \ OIDC_CFG_PROVIDER_MEMBER_FUNCS_TYPE_DECL(member, const char *, ##__VA_ARGS__) #define OIDC_CFG_PROVIDER_MEMBER_FUNCS_KEYS_DECL(member) \ OIDC_CFG_PROVIDER_MEMBER_FUNCS_TYPE_DECL(member, const apr_array_header_t *) \ const char *OIDC_CFG_MEMBER_FUNC_NAME(member, cfg_provider, set_keys)(apr_pool_t *, oidc_provider_t *, \ apr_array_header_t *); #define OIDC_CFG_PROVIDER_MEMBER_FUNCS_INT_INT_DECL(member, type) \ OIDC_CFG_PROVIDER_MEMBER_FUNCS_TYPE_DECL(member, type) \ void OIDC_CFG_MEMBER_FUNC_NAME(member, cfg_provider, int_set)(oidc_provider_t * provider, type arg); #define OIDC_CFG_PROVIDER_MEMBER_FUNCS_STR_LIST_DECL(member) \ OIDC_CFG_PROVIDER_MEMBER_FUNCS_TYPE_DECL(member, const apr_array_header_t *) \ const char *OIDC_CFG_MEMBER_FUNC_NAME(member, cfg_provider, set_str_list)(apr_pool_t *, oidc_provider_t *, \ apr_array_header_t *); OIDC_CFG_PROVIDER_MEMBER_FUNCS_STR_DECL(metadata_url) OIDC_CFG_PROVIDER_MEMBER_FUNCS_STR_DECL(issuer) OIDC_CFG_PROVIDER_MEMBER_FUNCS_STR_DECL(authorization_endpoint_url); OIDC_CFG_PROVIDER_MEMBER_FUNCS_STR_DECL(token_endpoint_url) OIDC_CFG_PROVIDER_MEMBER_FUNCS_STR_DECL(token_endpoint_params) OIDC_CFG_PROVIDER_MEMBER_FUNCS_STR_DECL(userinfo_endpoint_url) OIDC_CFG_PROVIDER_MEMBER_FUNCS_STR_DECL(revocation_endpoint_url) OIDC_CFG_PROVIDER_MEMBER_FUNCS_STR_DECL(registration_endpoint_url) OIDC_CFG_PROVIDER_MEMBER_FUNCS_STR_DECL(pushed_authorization_request_endpoint_url); OIDC_CFG_PROVIDER_MEMBER_FUNCS_STR_DECL(check_session_iframe) OIDC_CFG_PROVIDER_MEMBER_FUNCS_STR_DECL(end_session_endpoint) OIDC_CFG_PROVIDER_MEMBER_FUNCS_STR_DECL(client_id) OIDC_CFG_PROVIDER_MEMBER_FUNCS_STR_DECL(client_secret) OIDC_CFG_PROVIDER_MEMBER_FUNCS_STR_DECL(token_endpoint_tls_client_key) OIDC_CFG_PROVIDER_MEMBER_FUNCS_STR_DECL(token_endpoint_tls_client_key_pwd) OIDC_CFG_PROVIDER_MEMBER_FUNCS_STR_DECL(token_endpoint_tls_client_cert) OIDC_CFG_PROVIDER_MEMBER_FUNCS_STR_DECL(client_name) OIDC_CFG_PROVIDER_MEMBER_FUNCS_STR_DECL(client_contact) OIDC_CFG_PROVIDER_MEMBER_FUNCS_STR_DECL(registration_token) OIDC_CFG_PROVIDER_MEMBER_FUNCS_STR_DECL(registration_endpoint_json) OIDC_CFG_PROVIDER_MEMBER_FUNCS_STR_DECL(scope) OIDC_CFG_PROVIDER_MEMBER_FUNCS_STR_DECL(response_type) OIDC_CFG_PROVIDER_MEMBER_FUNCS_STR_DECL(response_mode) OIDC_CFG_PROVIDER_MEMBER_FUNCS_STR_DECL(auth_request_params) OIDC_CFG_PROVIDER_MEMBER_FUNCS_STR_DECL(logout_request_params) OIDC_CFG_PROVIDER_MEMBER_FUNCS_STR_DECL(client_jwks_uri) OIDC_CFG_PROVIDER_MEMBER_FUNCS_STR_DECL(id_token_signed_response_alg) OIDC_CFG_PROVIDER_MEMBER_FUNCS_STR_DECL(id_token_encrypted_response_alg) OIDC_CFG_PROVIDER_MEMBER_FUNCS_STR_DECL(id_token_encrypted_response_enc) OIDC_CFG_PROVIDER_MEMBER_FUNCS_STR_DECL(userinfo_signed_response_alg) OIDC_CFG_PROVIDER_MEMBER_FUNCS_STR_DECL(userinfo_encrypted_response_alg) OIDC_CFG_PROVIDER_MEMBER_FUNCS_STR_DECL(userinfo_encrypted_response_enc) OIDC_CFG_PROVIDER_MEMBER_FUNCS_STR_DECL(request_object) // string list OIDC_CFG_PROVIDER_MEMBER_FUNCS_STR_LIST_DECL(id_token_aud_values) // keys OIDC_CFG_PROVIDER_MEMBER_FUNCS_KEYS_DECL(verify_public_keys) OIDC_CFG_PROVIDER_MEMBER_FUNCS_KEYS_DECL(client_keys) // ints OIDC_CFG_PROVIDER_MEMBER_FUNCS_INT_DECL(jwks_uri_refresh_interval) int oidc_cfg_jwks_uri_refresh_interval_get(const oidc_jwks_uri_t *jwks_uri); OIDC_CFG_PROVIDER_MEMBER_FUNCS_INT_DECL(backchannel_logout_supported) OIDC_CFG_PROVIDER_MEMBER_FUNCS_INT_DECL(ssl_validate_server) OIDC_CFG_PROVIDER_MEMBER_FUNCS_INT_DECL(validate_issuer) OIDC_CFG_PROVIDER_MEMBER_FUNCS_INT_DECL(idtoken_iat_slack) OIDC_CFG_PROVIDER_MEMBER_FUNCS_INT_DECL(session_max_duration) OIDC_CFG_PROVIDER_MEMBER_FUNCS_INT_DECL(response_require_iss) // ints with 2 args OIDC_CFG_PROVIDER_MEMBER_FUNCS_INT_DECL(userinfo_refresh_interval, const char *) OIDC_CFG_PROVIDER_MEMBER_FUNCS_TYPE_DECL(dpop_mode, oidc_dpop_mode_t, const char *) void OIDC_CFG_MEMBER_FUNC_NAME(dpop_mode, cfg_provider, int_set)(oidc_provider_t *provider, oidc_dpop_mode_t arg); // for metadata.c OIDC_CFG_PROVIDER_MEMBER_FUNCS_INT_INT_DECL(userinfo_token_method, oidc_userinfo_token_method_t) OIDC_CFG_PROVIDER_MEMBER_FUNCS_INT_INT_DECL(auth_request_method, oidc_auth_request_method_t) OIDC_CFG_PROVIDER_MEMBER_FUNCS_INT_INT_DECL(profile, oidc_profile_t) // types OIDC_CFG_PROVIDER_MEMBER_FUNCS_TYPE_DECL(pkce, const oidc_proto_pkce_t *) OIDC_CFG_PROVIDER_MEMBER_FUNCS_TYPE_DECL(jwks_uri, const oidc_jwks_uri_t *) // getters OIDC_CFG_PROVIDER_MEMBER_FUNC_GET_DECL(jwks_uri_uri, const char *) // specials for signed_jwks_uri and signed_jwks_uri_keys OIDC_CMD_PROVIDER_MEMBER_FUNC_DECL(signed_jwks_uri, const char *) OIDC_CFG_PROVIDER_MEMBER_FUNC_SET_DECL(signed_jwks_uri, const char *, const char *) OIDC_CFG_PROVIDER_MEMBER_FUNC_GET_DECL(signed_jwks_uri, const char *) OIDC_CFG_PROVIDER_MEMBER_FUNC_SET_DECL(signed_jwks_uri_keys, json_t *, apr_array_header_t *) OIDC_CFG_PROVIDER_MEMBER_FUNC_GET_DECL(signed_jwks_uri_keys, apr_array_header_t *) // specials for token_endpoint_auth const char *OIDC_CFG_MEMBER_FUNC_NAME(token_endpoint_auth, cfg_provider, set)(apr_pool_t *pool, oidc_cfg_t *cfg, oidc_provider_t *provider, const char *arg); OIDC_CMD_PROVIDER_MEMBER_FUNC_DECL(token_endpoint_auth) OIDC_CFG_PROVIDER_MEMBER_FUNC_GET_DECL(token_endpoint_auth, const char *) oidc_provider_t *oidc_cfg_provider_create(apr_pool_t *pool); void oidc_cfg_provider_merge(apr_pool_t *pool, oidc_provider_t *dst, const oidc_provider_t *base, const oidc_provider_t *add); oidc_provider_t *oidc_cfg_provider_copy(apr_pool_t *pool, const oidc_provider_t *src); void oidc_cfg_provider_destroy(oidc_provider_t *provider); #endif // _MOD_AUTH_OPENIDC_CFG_PROVIDER_H_ mod_auth_openidc-2.4.16.10/src/const.h000066400000000000000000000143201476721736500174340ustar00rootroot00000000000000/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ /*************************************************************************** * Copyright (C) 2017-2025 ZmartZone Holding BV * Copyright (C) 2013-2017 Ping Identity Corporation * All rights reserved. * * DISCLAIMER OF WARRANTIES: * * THE SOFTWARE PROVIDED HEREUNDER IS PROVIDED ON AN "AS IS" BASIS, WITHOUT * ANY WARRANTIES OR REPRESENTATIONS EXPRESS, IMPLIED OR STATUTORY; INCLUDING, * WITHOUT LIMITATION, WARRANTIES OF QUALITY, PERFORMANCE, NONINFRINGEMENT, * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. NOR ARE THERE ANY * WARRANTIES CREATED BY A COURSE OR DEALING, COURSE OF PERFORMANCE OR TRADE * USAGE. FURTHERMORE, THERE ARE NO WARRANTIES THAT THE SOFTWARE WILL MEET * YOUR NEEDS OR BE FREE FROM ERRORS, OR THAT THE OPERATION OF THE SOFTWARE * WILL BE UNINTERRUPTED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * @Author: Hans Zandbelt - hans.zandbelt@openidc.com */ #ifndef _MOD_AUTH_OPENIDC_CONST_H_ #define _MOD_AUTH_OPENIDC_CONST_H_ #ifdef HAVE_CONFIG_H #include "config.h" #undef PACKAGE_NAME #undef PACKAGE_STRING #undef PACKAGE_TARNAME #undef PACKAGE_VERSION #undef PACKAGE_BUGREPORT #endif #include #define __STDC_WANT_LIB_EXT1__ 1 #include #include // clang-format off #include #include // clang-format on #ifdef __STDC_LIB_EXT1__ #define _oidc_memset(b, c, __len) memset_s(b, __len, c, __len) #define _oidc_memcpy(__dst, __src, __n) memcpy_s(__dst, __src, __n) #define _oidc_strcpy(__dst, __src) strcpy_s(__dst, __src) #else #define _oidc_memset(b, c, __len) memset(b, c, __len) #define _oidc_memcpy(__dst, __src, __n) memcpy(__dst, __src, __n) #define _oidc_strcpy(__dst, __src) strcpy(__dst, __src) #endif static inline size_t _oidc_strlen(const char *s) { return (s ? strlen(s) : 0); } static inline int _oidc_strcmp(const char *a, const char *b) { return ((a && b) ? apr_strnatcmp(a, b) : -1); } static inline int _oidc_strnatcasecmp(const char *a, const char *b) { return ((a && b) ? apr_strnatcasecmp(a, b) : -1); } static inline int _oidc_strncmp(const char *a, const char *b, size_t n) { return ((a && b) ? strncmp(a, b, n) : -1); } static inline char *_oidc_strstr(const char *a, const char *b) { return ((a && b) ? (char *)strstr(a, b) : NULL); } static inline char *_oidc_strncpy(char *destination, const char *source, size_t num) { return ((destination && source) ? strncpy(destination, source, num) : NULL); } static inline apr_time_t _oidc_str_to_time(const char *s, const apr_time_t default_value) { apr_time_t v = default_value; if (s) sscanf(s, "%" APR_TIME_T_FMT, &v); return v; } static inline int _oidc_str_to_int(const char *s, const int default_value) { int v = default_value; if (s) v = strtol(s, NULL, 10); return v; } #ifdef WIN32 #define snprintf _snprintf #endif #define HAVE_APACHE_24 MODULE_MAGIC_NUMBER_MAJOR >= 20100714 #ifndef OIDC_DEBUG #define OIDC_DEBUG APLOG_DEBUG #endif #ifndef APLOG_TRACE1 #define APLOG_TRACE1 APLOG_DEBUG #endif #ifndef apr_uintptr_t #define apr_uintptr_t apr_uint64_t #endif #ifndef APR_UINT32_MAX #define APR_UINT32_MAX UINT32_MAX #endif #ifndef APR_INT64_MAX #define APR_INT64_MAX INT64_MAX #endif #ifndef apr_time_from_msec #define apr_time_from_msec(msec) ((apr_time_t)(msec) * 1000) #endif #define oidc_log(r, level, fmt, ...) \ ap_log_rerror(APLOG_MARK, level, 0, r, "%s: %s", __FUNCTION__, apr_psprintf(r->pool, fmt, ##__VA_ARGS__)) #define oidc_slog(s, level, fmt, ...) \ ap_log_error(APLOG_MARK, level, 0, s, "%s: %s", __FUNCTION__, \ apr_psprintf(s->process->pconf, fmt, ##__VA_ARGS__)) #define oidc_debug(r, fmt, ...) oidc_log(r, OIDC_DEBUG, fmt, ##__VA_ARGS__) #define oidc_warn(r, fmt, ...) oidc_log(r, APLOG_WARNING, fmt, ##__VA_ARGS__) #define oidc_info(r, fmt, ...) oidc_log(r, APLOG_INFO, fmt, ##__VA_ARGS__) #define oidc_error(r, fmt, ...) oidc_log(r, APLOG_ERR, fmt, ##__VA_ARGS__) #define oidc_sdebug(s, fmt, ...) oidc_slog(s, OIDC_DEBUG, fmt, ##__VA_ARGS__) #define oidc_swarn(s, fmt, ...) oidc_slog(s, APLOG_WARNING, fmt, ##__VA_ARGS__) #define oidc_serror(s, fmt, ...) oidc_slog(s, APLOG_ERR, fmt, ##__VA_ARGS__) #ifndef NAMEVER #define NAMEVERSION "mod_auth_openidc-0.0.0" #else #define STRINGIFY(x) #x #define TOSTRING(x) STRINGIFY(x) #define NAMEVERSION TOSTRING(NAMEVER) #endif #define OIDC_CHAR_EQUAL '=' #define OIDC_CHAR_COLON ':' #define OIDC_CHAR_TILDE '~' #define OIDC_CHAR_SPACE ' ' #define OIDC_CHAR_COMMA ',' #define OIDC_CHAR_QUERY '?' #define OIDC_CHAR_DOT '.' #define OIDC_CHAR_AT '@' #define OIDC_CHAR_FORWARD_SLASH '/' #define OIDC_CHAR_PIPE '|' #define OIDC_CHAR_AMP '&' #define OIDC_CHAR_SEMI_COLON ';' #define OIDC_STR_SPACE " " #define OIDC_STR_EQUAL "=" #define OIDC_STR_AMP "&" #define OIDC_STR_QUERY "?" #define OIDC_STR_COLON ":" #define OIDC_STR_SEMI_COLON ";" #define OIDC_STR_FORWARD_SLASH "/" #define OIDC_STR_AT "@" #define OIDC_STR_COMMA "," #define OIDC_STR_HASH "#" #endif /* _MOD_AUTH_OPENIDC_CONST_H_ */ mod_auth_openidc-2.4.16.10/src/handle/000077500000000000000000000000001476721736500173705ustar00rootroot00000000000000mod_auth_openidc-2.4.16.10/src/handle/.gitignore000066400000000000000000000000461476721736500213600ustar00rootroot00000000000000/.deps/ /.libs/ /.dirstamp /*.lo /*.o mod_auth_openidc-2.4.16.10/src/handle/authz.c000066400000000000000000000643041476721736500206760ustar00rootroot00000000000000/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ /*************************************************************************** * Copyright (C) 2017-2025 ZmartZone Holding BV * All rights reserved. * * DISCLAIMER OF WARRANTIES: * * THE SOFTWARE PROVIDED HEREUNDER IS PROVIDED ON AN "AS IS" BASIS, WITHOUT * ANY WARRANTIES OR REPRESENTATIONS EXPRESS, IMPLIED OR STATUTORY; INCLUDING, * WITHOUT LIMITATION, WARRANTIES OF QUALITY, PERFORMANCE, NONINFRINGEMENT, * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. NOR ARE THERE ANY * WARRANTIES CREATED BY A COURSE OR DEALING, COURSE OF PERFORMANCE OR TRADE * USAGE. FURTHERMORE, THERE ARE NO WARRANTIES THAT THE SOFTWARE WILL MEET * YOUR NEEDS OR BE FREE FROM ERRORS, OR THAT THE OPERATION OF THE SOFTWARE * WILL BE UNINTERRUPTED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * @Author: Hans Zandbelt - hans.zandbelt@openidc.com */ #include "cfg/dir.h" #include "handle/handle.h" #include "http_protocol.h" #include "metrics.h" #include "mod_auth_openidc.h" #include "pcre_subst.h" #include "proto/proto.h" #include "util.h" static apr_byte_t oidc_authz_match_json_string(request_rec *r, const char *spec, json_t *val, const char *key) { return (_oidc_strcmp(json_string_value(val), spec) == 0); } static apr_byte_t oidc_authz_match_json_integer(request_rec *r, const char *spec, json_t *val, const char *key) { json_int_t i = 0; if ((spec == NULL) || (val == NULL)) return FALSE; if (sscanf(spec, "%" JSON_INTEGER_FORMAT, &i) != 1) { oidc_warn(r, "integer parsing error for spec input: %s", spec); return FALSE; } return (json_integer_value(val) == i); } static apr_byte_t oidc_authz_match_json_real(request_rec *r, const char *spec, json_t *val, const char *key) { double d = 0; if ((spec == NULL) || (val == NULL)) return FALSE; if (sscanf(spec, "%lf", &d) != 1) { oidc_warn(r, "double parsing error for spec input: %s", spec); return FALSE; } return (json_real_value(val) == d); } static apr_byte_t oidc_authz_match_json_true(request_rec *r, const char *spec, json_t *val, const char *key) { if ((spec == NULL) || (val == NULL)) return FALSE; return (_oidc_strcmp(spec, "true") == 0); } static apr_byte_t oidc_authz_match_json_false(request_rec *r, const char *spec, json_t *val, const char *key) { if ((spec == NULL) || (val == NULL)) return FALSE; return (_oidc_strcmp(spec, "false") == 0); } static apr_byte_t oidc_authz_match_json_null(request_rec *r, const char *spec, json_t *val, const char *key) { if ((spec == NULL) || (val == NULL)) return FALSE; return (_oidc_strcmp(spec, "null") == 0); } typedef apr_byte_t(oidc_match_json_function_t)(request_rec *r, const char *spec, json_t *val, const char *key); typedef struct oidc_authz_json_handler_t { int type; oidc_match_json_function_t *handler; } oidc_authz_json_handler_t; static apr_byte_t oidc_authz_match_json_array(request_rec *r, const char *spec, json_t *val, const char *key); // clang-format off static oidc_authz_json_handler_t _oidc_authz_json_handlers[] = { { JSON_ARRAY, oidc_authz_match_json_array }, { JSON_STRING, oidc_authz_match_json_string }, { JSON_INTEGER, oidc_authz_match_json_integer }, { JSON_REAL, oidc_authz_match_json_real }, { JSON_TRUE, oidc_authz_match_json_true }, { JSON_FALSE, oidc_authz_match_json_false }, { JSON_NULL, oidc_authz_match_json_null }, { 0, NULL} }; // clang-format on static apr_byte_t oidc_authz_match_json_array(request_rec *r, const char *spec, json_t *val, const char *key) { int i = 0; json_t *e = NULL; oidc_authz_json_handler_t *h = NULL; if ((spec == NULL) || (val == NULL) || (key == NULL)) return FALSE; // loop over the elements in the array, trying to find a match for (i = 0; i < json_array_size(val); i++) { e = json_array_get(val, i); // loop over the JSON object type handlers for (h = _oidc_authz_json_handlers; h->handler; h++) { if (h->type == json_typeof(e)) { // avoid recursing into a nested array; matching need to be done with the "." syntax if (json_typeof(e) == JSON_ARRAY) // we want h->handler to end up as NULL to printout a warning at the end continue; // break out of the loop because we found a handler, and possibly a match if (h->handler(r, spec, e, key) == TRUE) return TRUE; break; } } if (h->handler == NULL) oidc_warn(r, "unhandled in-array JSON object type [%d] for key \"%s\"", json_typeof(e), key); // else: just no match, continue with the next array element } return FALSE; } static apr_byte_t oidc_authz_match_value(request_rec *r, const char *spec, json_t *val, const char *key) { oidc_authz_json_handler_t *h = NULL; if ((spec == NULL) || (val == NULL) || (key == NULL)) return FALSE; oidc_debug(r, "matching: spec=%s, key=%s", spec, key); for (h = _oidc_authz_json_handlers; h->handler; h++) { if (h->type == json_typeof(val)) return h->handler(r, spec, val, key); } oidc_warn(r, "unhandled JSON object type [%d] for key \"%s\"", json_typeof(val), (const char *)key); return FALSE; } typedef apr_byte_t(oidc_match_pcre_function_t)(request_rec *r, const char *, json_t *, const char *, struct oidc_pcre *); typedef struct oidc_authz_pcre_handler_t { int type; oidc_match_pcre_function_t *handler; } oidc_authz_pcre_handler_t; static apr_byte_t oidc_authz_match_pcre_string(request_rec *r, const char *spec, json_t *val, const char *key, struct oidc_pcre *preg) { char *s_err = NULL; const char *s = json_string_value(val); if ((spec == NULL) || (val == NULL) || (key == NULL) || (preg == NULL)) return FALSE; if (oidc_pcre_exec(r->pool, preg, s, (int)_oidc_strlen(s), &s_err) <= 0) { if (s_err) oidc_debug(r, "oidc_pcre_exec error: %s", s_err); return FALSE; } oidc_debug(r, "value \"%s\" matched regex \"%s\" for key \"%s\"", s, spec, key); return TRUE; } static apr_byte_t oidc_authz_match_pcre_array(request_rec *r, const char *spec, json_t *val, const char *key, struct oidc_pcre *); // clang-format off #define OIDC_AUTHZ_PCRE_HANDLERS_NUMBER 2 static oidc_authz_pcre_handler_t _oidc_authz_pcre_handlers[] = { { JSON_ARRAY, oidc_authz_match_pcre_array }, { JSON_STRING, oidc_authz_match_pcre_string } }; // clang-format on static apr_byte_t oidc_authz_match_pcre_array(request_rec *r, const char *spec, json_t *val, const char *key, struct oidc_pcre *preg) { int i = 0; json_t *e = NULL; if ((spec == NULL) || (val == NULL) || (key == NULL) || (preg == NULL)) return FALSE; // loop over the elements in the array, trying to find a match for (i = 0; i < json_array_size(val); i++) { e = json_array_get(val, i); if (json_typeof(e) == JSON_STRING) { if (oidc_authz_match_pcre_string(r, spec, e, key, preg) == TRUE) return TRUE; // need to free any failed match to avoid a memory leak in subsequent calls to oidc_pcre_exec oidc_pcre_free_match(preg); continue; } oidc_warn(r, "unhandled non-string in-array JSON object type [%d] for key \"%s\"", json_typeof(e), key); } return FALSE; } static apr_byte_t oidc_authz_match_pcre(request_rec *r, const char *spec, json_t *val, const char *key) { apr_byte_t rc = FALSE; struct oidc_pcre *preg = NULL; char *s_err = NULL; int i = 0; if ((spec == NULL) || (val == NULL) || (key == NULL)) return FALSE; preg = oidc_pcre_compile(r->pool, spec, &s_err); if (preg == NULL) { oidc_error(r, "pattern [%s] is not a valid regular expression: %s", spec, s_err ? s_err : ""); return FALSE; } // loop over the JSON object PCRE handlers for (i = 0; i < OIDC_AUTHZ_PCRE_HANDLERS_NUMBER; i++) { if (_oidc_authz_pcre_handlers[i].type == json_typeof(val)) { // break out of the loop because we found a handler, and possibly a match if (_oidc_authz_pcre_handlers[i].handler(r, spec, val, key, preg) == TRUE) rc = TRUE; break; } } // see if we have found an object handler if (i == OIDC_AUTHZ_PCRE_HANDLERS_NUMBER) oidc_warn(r, "unhandled JSON object type [%d] for key \"%s\"", json_typeof(val), key); oidc_pcre_free(preg); return rc; } static apr_byte_t oidc_authz_separator_dot(request_rec *r, const char *spec, json_t *val, const char *key) { if ((spec == NULL) || (val == NULL) || (key == NULL)) return FALSE; if (json_is_object(val)) { oidc_debug(r, "attribute chunk matched, evaluating children of key: \"%s\".", key); return oidc_authz_match_claim(r, spec, val); } oidc_warn(r, "JSON key \"%s\" matched a \".\" and child nodes should be evaluated, but the corresponding JSON " "value is not an object", key); return FALSE; } // clang-format off #define OIDC_AUTHZ_SEPARATOR_HANDLERS_NUMBER 3 static oidc_authz_json_handler_t _oidc_authz_separator_handlers[] = { // there's some overloading going on here, applying a char as an int index { OIDC_CHAR_COLON, oidc_authz_match_value }, { OIDC_CHAR_TILDE, oidc_authz_match_pcre }, { OIDC_CHAR_DOT, oidc_authz_separator_dot } }; // clang-format on static apr_byte_t oidc_auth_handle_separator(request_rec *r, const char *key, json_t *val, const char *spec) { if ((spec == NULL) || (val == NULL) || (key == NULL)) return FALSE; for (int i = 0; i < OIDC_AUTHZ_SEPARATOR_HANDLERS_NUMBER; i++) { // there's some overloading going on here, applying a char as an int index if (_oidc_authz_separator_handlers[i].type == (*spec)) { // skip the separator spec++; if (_oidc_authz_separator_handlers[i].handler(r, spec, val, key) == TRUE) return TRUE; } } return FALSE; } /* * see if a the Require value matches with a set of provided claims */ apr_byte_t oidc_authz_match_claim(request_rec *r, const char *const attr_spec, json_t *claims) { const char *key = NULL, *attr_c = NULL, *spec_c = NULL; json_t *val = NULL; // if we don't have any claims, they can never match any Require claim primitive if (claims == NULL) return FALSE; // loop over all of the user claims to find one that matches the attr_spec void *iter = json_object_iter(claims); while (iter) { key = json_object_iter_key(iter); val = json_object_iter_value(iter); oidc_debug(r, "evaluating key \"%s\"", (const char *)key); // initialize pointers for traversing the attribute name and the Require spec attr_c = key; spec_c = attr_spec; // walk both strings until we get to the end of either or we find a differing character while ((*attr_c) && (*spec_c) && (*attr_c) == (*spec_c)) { attr_c++; spec_c++; } if ((!(*attr_c)) && (oidc_auth_handle_separator(r, key, val, spec_c) == TRUE)) return TRUE; iter = json_object_iter_next(claims, iter); } return FALSE; } #ifdef USE_LIBJQ /* * see if a the Require value matches a configured expression */ static apr_byte_t oidc_authz_match_claims_expr(request_rec *r, const char *const attr_spec, json_t *claims) { apr_byte_t rv = FALSE; const char *str = NULL; oidc_debug(r, "enter: '%s'", attr_spec); str = oidc_util_jq_filter(r, oidc_util_encode_json(r->pool, claims, JSON_PRESERVE_ORDER | JSON_COMPACT), attr_spec); rv = (_oidc_strcmp(str, "true") == 0); return rv; } #endif #define OIDC_AUTHZ_ERROR "OIDC_AUTHZ_ERROR" static void oidc_authz_error_add(request_rec *r, const char *msg) { const char *envvar = NULL; if (r->subprocess_env != NULL) { envvar = apr_table_get(r->subprocess_env, OIDC_AUTHZ_ERROR); oidc_debug(r, "adding %s to environment variable %s=%s", msg, OIDC_AUTHZ_ERROR, envvar); apr_table_set(r->subprocess_env, OIDC_AUTHZ_ERROR, apr_psprintf(r->pool, "%s%s%s", envvar ? envvar : "", envvar ? "," : "", msg ? msg : "")); } } /* * get the claims and id_token from request state */ static void oidc_authz_get_claims_and_idtoken(request_rec *r, json_t **claims, json_t **id_token) { const char *s_claims = oidc_request_state_get(r, OIDC_REQUEST_STATE_KEY_CLAIMS); if (s_claims != NULL) oidc_util_decode_json_object(r, s_claims, claims); const char *s_id_token = oidc_request_state_get(r, OIDC_REQUEST_STATE_KEY_IDTOKEN); if (s_id_token != NULL) oidc_util_decode_json_object(r, s_id_token, id_token); } #if HAVE_APACHE_24 /* * Apache >=2.4 authorization routine: match the claims from the authenticated user against the Require primitive */ authz_status oidc_authz_24_worker(request_rec *r, json_t *claims, const char *require_args, const void *parsed_require_args, oidc_authz_match_claim_fn_type match_claim_fn) { oidc_cfg_t *cfg = ap_get_module_config(r->server->module_config, &auth_openidc_module); int count_oauth_claims = 0; const char *t, *w, *err = NULL; const ap_expr_info_t *expr = parsed_require_args; /* needed for anonymous authentication */ if (r->user == NULL) return AUTHZ_DENIED_NO_USER; /* if no claims, impossible to satisfy */ if (!claims) return AUTHZ_DENIED; if (expr) { t = ap_expr_str_exec(r, expr, &err); if (err) { oidc_error(r, "could not evaluate expression '%s': %s", require_args, err); return AUTHZ_DENIED; } } else { t = require_args; } /* loop over the Required specifications */ while ((w = ap_getword_conf(r->pool, &t)) && w[0]) { count_oauth_claims++; oidc_debug(r, "evaluating claim/expr specification: %s", w); /* see if we can match any of out input claims against this Require'd value */ if (match_claim_fn(r, w, claims) == TRUE) { OIDC_METRICS_COUNTER_INC_VALUE(r, cfg, OM_AUTHZ_MATCH_REQUIRE_CLAIM, require_args); oidc_debug(r, "require claim/expr '%s' matched", w); return AUTHZ_GRANTED; } } /* if there wasn't anything after the Require claims directive... */ if (count_oauth_claims == 0) { oidc_warn(r, "'require claim/expr' missing specification(s) in configuration, denying"); } OIDC_METRICS_COUNTER_INC_VALUE(r, cfg, OM_AUTHZ_ERROR_REQUIRE_CLAIM, require_args); oidc_debug(r, "could not match require claim expression '%s'", require_args); oidc_authz_error_add(r, require_args); return AUTHZ_DENIED; } #define OIDC_OAUTH_BEARER_SCOPE_ERROR "OIDC_OAUTH_BEARER_SCOPE_ERROR" #define OIDC_OAUTH_BEARER_SCOPE_ERROR_VALUE \ "Bearer error=\"insufficient_scope\", error_description=\"Different scope(s) or other claims required\"" /* * find out which action we need to take when encountering an unauthorized request */ static authz_status oidc_authz_24_unauthorized_user(request_rec *r) { char *html_head = NULL; oidc_debug(r, "enter"); oidc_cfg_t *c = ap_get_module_config(r->server->module_config, &auth_openidc_module); if (_oidc_strnatcasecmp((const char *)ap_auth_type(r), OIDC_AUTH_TYPE_OPENID_OAUTH20) == 0) { OIDC_METRICS_COUNTER_INC(r, c, OM_AUTHZ_ERROR_OAUTH20); oidc_debug(r, "setting environment variable %s to \"%s\" for usage in mod_headers", OIDC_OAUTH_BEARER_SCOPE_ERROR, OIDC_OAUTH_BEARER_SCOPE_ERROR_VALUE); apr_table_set(r->subprocess_env, OIDC_OAUTH_BEARER_SCOPE_ERROR, OIDC_OAUTH_BEARER_SCOPE_ERROR_VALUE); return AUTHZ_DENIED; } /* see if we've configured OIDCUnAutzAction for this path */ switch (oidc_cfg_dir_unautz_action_get(r)) { case OIDC_UNAUTZ_RETURN403: case OIDC_UNAUTZ_RETURN401: OIDC_METRICS_COUNTER_INC(r, c, OM_AUTHZ_ACTION_401); oidc_util_html_send_error(r, "Authorization Error", oidc_cfg_dir_unauthz_arg_get(r), HTTP_UNAUTHORIZED); return AUTHZ_DENIED; case OIDC_UNAUTZ_RETURN302: OIDC_METRICS_COUNTER_INC(r, c, OM_AUTHZ_ACTION_302); html_head = apr_psprintf(r->pool, "", oidc_cfg_dir_unauthz_arg_get(r)); oidc_util_html_send(r, "Authorization Error Redirect", html_head, NULL, NULL, HTTP_UNAUTHORIZED); r->header_only = 1; return AUTHZ_DENIED; case OIDC_UNAUTZ_AUTHENTICATE: /* * exception handling: if this looks like an HTTP request that cannot * complete an authentication round trip to the provider, we * won't redirect the user and thus avoid creating a state cookie * * NB: when the expression argument to OIDCUnAuthAction is configured, * it is re-used here to detect XHR requests. */ if (oidc_cfg_dir_unauth_expr_is_set(r) == TRUE) { if (oidc_cfg_dir_unauth_action_get(r) != OIDC_UNAUTH_AUTHENTICATE) { OIDC_METRICS_COUNTER_INC(r, c, OM_AUTHZ_ACTION_401); return AUTHZ_DENIED; } } else if (oidc_is_auth_capable_request(r) == FALSE) { OIDC_METRICS_COUNTER_INC(r, c, OM_AUTHZ_ACTION_401); return AUTHZ_DENIED; } OIDC_METRICS_COUNTER_INC(r, c, OM_AUTHZ_ACTION_AUTH); break; } oidc_request_authenticate_user(r, c, NULL, oidc_util_current_url(r, oidc_cfg_x_forwarded_headers_get(c)), NULL, NULL, NULL, oidc_cfg_dir_path_auth_request_params_get(r), oidc_cfg_dir_path_scope_get(r)); const char *location = oidc_http_hdr_out_location_get(r); if ((oidc_request_state_get(r, OIDC_REQUEST_STATE_KEY_DISCOVERY) != NULL) && (location == NULL)) return AUTHZ_GRANTED; if (location != NULL) { oidc_debug(r, "send HTML refresh with authorization redirect: %s", location); oidc_http_hdr_out_location_set(r, NULL); html_head = apr_psprintf(r->pool, "", location); oidc_util_html_send(r, "Stepup Authentication", html_head, NULL, NULL, HTTP_UNAUTHORIZED); r->header_only = 1; } return AUTHZ_DENIED; } /* * generic Apache >=2.4 authorization hook for this module * handles both OpenID Connect or OAuth 2.0 in the same way, based on the claims stored in the session */ authz_status oidc_authz_24_checker(request_rec *r, const char *require_args, const void *parsed_require_args, oidc_authz_match_claim_fn_type match_claim_fn) { oidc_debug(r, "enter: (r->user=%s) require_args=\"%s\"", r->user, require_args); /* check for anonymous access and PASS mode */ if ((r->user != NULL) && (_oidc_strlen(r->user) == 0)) { if (oidc_cfg_dir_unauth_action_get(r) == OIDC_UNAUTH_PASS) return AUTHZ_GRANTED; if (oidc_request_state_get(r, OIDC_REQUEST_STATE_KEY_DISCOVERY) != NULL) return AUTHZ_GRANTED; if (r->method_number == M_OPTIONS) return AUTHZ_GRANTED; } /* get the set of claims from the request state (they've been set in the authentication part earlier */ json_t *claims = NULL, *id_token = NULL; oidc_authz_get_claims_and_idtoken(r, &claims, &id_token); /* merge id_token claims (e.g. "iss") in to claims json object */ if (claims) oidc_util_json_merge(r, id_token, claims); /* dispatch to the >=2.4 specific authz routine */ authz_status rc = oidc_authz_24_worker(r, claims ? claims : id_token, require_args, parsed_require_args, match_claim_fn); /* cleanup */ if (claims) json_decref(claims); if (id_token) json_decref(id_token); if ((rc == AUTHZ_DENIED) && ap_auth_type(r)) rc = oidc_authz_24_unauthorized_user(r); return rc; } authz_status oidc_authz_24_checker_claim(request_rec *r, const char *require_args, const void *parsed_require_args) { return oidc_authz_24_checker(r, require_args, parsed_require_args, oidc_authz_match_claim); } #ifdef USE_LIBJQ authz_status oidc_authz_24_checker_claims_expr(request_rec *r, const char *require_args, const void *parsed_require_args) { return oidc_authz_24_checker(r, require_args, parsed_require_args, oidc_authz_match_claims_expr); } #endif #else /* * Apache <2.4 authorization routine: match the claims from the authenticated user against the Require primitive */ static int oidc_authz_22_worker(request_rec *r, json_t *claims, const require_line *const reqs, int nelts) { const int m = r->method_number; const char *token; const char *requirement; int i; int have_oauthattr = 0; int count_oauth_claims = 0; oidc_authz_match_claim_fn_type match_claim_fn = NULL; /* go through applicable Require directives */ for (i = 0; i < nelts; ++i) { /* ignore this Require if it's in a section that exclude this method */ if (!(reqs[i].method_mask & (AP_METHOD_BIT << m))) { continue; } /* ignore if it's not a "Require claim ..." */ requirement = reqs[i].requirement; token = ap_getword_white(r->pool, &requirement); /* see if we've got anything meant for us */ if (_oidc_strnatcasecmp(token, OIDC_REQUIRE_CLAIM_NAME) == 0) { match_claim_fn = oidc_authz_match_claim; #ifdef USE_LIBJQ } else if (_oidc_strnatcasecmp(token, OIDC_REQUIRE_CLAIMS_EXPR_NAME) == 0) { match_claim_fn = oidc_authz_match_claims_expr; #endif } else { continue; } /* ok, we have a "Require claim/claims_expr" to satisfy */ have_oauthattr = 1; /* * If we have an applicable claim, but no claims were sent in the request, then we can * just stop looking here, because it's not satisfiable. The code after this loop will * give the appropriate response. */ if (!claims) { break; } /* * iterate over the claim specification strings in this require directive searching * for a specification that matches one of the claims/expressions. */ while (*requirement) { token = ap_getword_conf(r->pool, &requirement); count_oauth_claims++; oidc_debug(r, "evaluating claim/expr specification: %s", token); if (match_claim_fn(r, token, claims) == TRUE) { /* if *any* claim matches, then authorization has succeeded and all of the others are * ignored */ oidc_debug(r, "require claim/expr '%s' matched", token); return OK; } } oidc_authz_error_add(r, requirement); } /* if there weren't any "Require claim" directives, we're irrelevant */ if (!have_oauthattr) { oidc_debug(r, "no claim/expr statements found, not performing authz"); return DECLINED; } /* if there was a "Require claim", but no actual claims, that's cause to warn the admin of an iffy configuration */ if (count_oauth_claims == 0) { oidc_warn(r, "'require claim/expr' missing specification(s) in configuration, declining"); return DECLINED; } /* log the event, also in Apache speak */ oidc_debug(r, "authorization denied for require claims (0/%d): '%s'", nelts, nelts > 0 ? reqs[0].requirement : "(none)"); ap_note_auth_failure(r); return HTTP_UNAUTHORIZED; } /* * find out which action we need to take when encountering an unauthorized request */ static int oidc_authz_22_unauthorized_user(request_rec *r) { oidc_cfg_t *c = ap_get_module_config(r->server->module_config, &auth_openidc_module); if (_oidc_strnatcasecmp((const char *)ap_auth_type(r), OIDC_AUTH_TYPE_OPENID_OAUTH20) == 0) { OIDC_METRICS_COUNTER_INC(r, c, OM_AUTHZ_ERROR_OAUTH20); oidc_proto_return_www_authenticate(r, "insufficient_scope", "Different scope(s) or other claims required"); return HTTP_UNAUTHORIZED; } /* see if we've configured OIDCUnAutzAction for this path */ switch (oidc_cfg_dir_unautz_action_get(r)) { case OIDC_UNAUTZ_RETURN403: OIDC_METRICS_COUNTER_INC(r, c, OM_AUTHZ_ACTION_403); if (oidc_cfg_dir_unauthz_arg_get(r)) oidc_util_html_send(r, "Authorization Error", NULL, NULL, oidc_cfg_dir_unauthz_arg_get(r), HTTP_FORBIDDEN); return HTTP_FORBIDDEN; case OIDC_UNAUTZ_RETURN401: OIDC_METRICS_COUNTER_INC(r, c, OM_AUTHZ_ACTION_401); if (oidc_cfg_dir_unauthz_arg_get(r)) oidc_util_html_send(r, "Authorization Error", NULL, NULL, oidc_cfg_dir_unauthz_arg_get(r), HTTP_UNAUTHORIZED); return HTTP_UNAUTHORIZED; case OIDC_UNAUTZ_RETURN302: OIDC_METRICS_COUNTER_INC(r, c, OM_AUTHZ_ACTION_302); oidc_http_hdr_out_location_set(r, oidc_cfg_dir_unauthz_arg_get(r)); return HTTP_MOVED_TEMPORARILY; case OIDC_UNAUTZ_AUTHENTICATE: /* * exception handling: if this looks like a XMLHttpRequest call we * won't redirect the user and thus avoid creating a state cookie * for a non-browser (= Javascript) call that will never return from the OP */ if (oidc_is_auth_capable_request(r) == FALSE) { OIDC_METRICS_COUNTER_INC(r, c, OM_AUTHZ_ACTION_401); return HTTP_UNAUTHORIZED; } OIDC_METRICS_COUNTER_INC(r, c, OM_AUTHZ_ACTION_AUTH); } return oidc_request_authenticate_user(r, c, NULL, oidc_util_current_url(r, oidc_cfg_x_forwarded_headers_get(c)), NULL, NULL, NULL, oidc_cfg_dir_path_auth_request_params_get(r), oidc_cfg_dir_path_scope_get(r)); } /* * generic Apache <2.4 authorization hook for this module * handles both OpenID Connect and OAuth 2.0 in the same way, based on the claims stored in the request context */ int oidc_authz_22_checker(request_rec *r) { /* check for anonymous access and PASS mode */ if ((r->user != NULL) && (_oidc_strlen(r->user) == 0)) { r->user = NULL; if (oidc_cfg_dir_unauth_action_get(r) == OIDC_UNAUTH_PASS) return OK; if (oidc_request_state_get(r, OIDC_REQUEST_STATE_KEY_DISCOVERY) != NULL) return OK; if (r->method_number == M_OPTIONS) return OK; } /* get the set of claims from the request state (they've been set in the authentication part earlier */ json_t *claims = NULL, *id_token = NULL; oidc_authz_get_claims_and_idtoken(r, &claims, &id_token); /* get the Require statements */ const apr_array_header_t *const reqs_arr = ap_requires(r); /* see if we have any */ const require_line *const reqs = reqs_arr ? (require_line *)reqs_arr->elts : NULL; if (!reqs_arr) { oidc_debug(r, "no require statements found, so declining to perform authorization."); return DECLINED; } /* merge id_token claims (e.g. "iss") in to claims json object */ if (claims) oidc_util_json_merge(r, id_token, claims); /* dispatch to the <2.4 specific authz routine */ int rc = oidc_authz_22_worker(r, claims ? claims : id_token, reqs, reqs_arr->nelts); /* cleanup */ if (claims) json_decref(claims); if (id_token) json_decref(id_token); if ((rc == HTTP_UNAUTHORIZED) && ap_auth_type(r)) rc = oidc_authz_22_unauthorized_user(r); return rc; } #endif mod_auth_openidc-2.4.16.10/src/handle/content.c000066400000000000000000000113071476721736500212100ustar00rootroot00000000000000/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ /*************************************************************************** * Copyright (C) 2017-2025 ZmartZone Holding BV * All rights reserved. * * DISCLAIMER OF WARRANTIES: * * THE SOFTWARE PROVIDED HEREUNDER IS PROVIDED ON AN "AS IS" BASIS, WITHOUT * ANY WARRANTIES OR REPRESENTATIONS EXPRESS, IMPLIED OR STATUTORY; INCLUDING, * WITHOUT LIMITATION, WARRANTIES OF QUALITY, PERFORMANCE, NONINFRINGEMENT, * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. NOR ARE THERE ANY * WARRANTIES CREATED BY A COURSE OR DEALING, COURSE OF PERFORMANCE OR TRADE * USAGE. FURTHERMORE, THERE ARE NO WARRANTIES THAT THE SOFTWARE WILL MEET * YOUR NEEDS OR BE FREE FROM ERRORS, OR THAT THE OPERATION OF THE SOFTWARE * WILL BE UNINTERRUPTED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * @Author: Hans Zandbelt - hans.zandbelt@openidc.com */ #include "handle/handle.h" #include "metrics.h" #include "mod_auth_openidc.h" #include "util.h" /* * handle content generating requests */ int oidc_content_handler(request_rec *r) { oidc_cfg_t *c = ap_get_module_config(r->server->module_config, &auth_openidc_module); int rc = DECLINED; /* track if the session needs to be updated/saved into the cache */ apr_byte_t needs_save = FALSE; oidc_session_t *session = NULL; if ((r->parsed_uri.path != NULL) && (oidc_cfg_metrics_path_get(c) != NULL)) if (_oidc_strcmp(r->parsed_uri.path, oidc_cfg_metrics_path_get(c)) == 0) return oidc_metrics_handle_request(r); if (oidc_enabled(r) == FALSE) { OIDC_METRICS_COUNTER_INC(r, c, OM_CONTENT_REQUEST_DECLINED); return DECLINED; } if (oidc_util_request_matches_url(r, oidc_util_redirect_uri(r, c)) == TRUE) { /* requests to the redirect URI are handled and finished here */ rc = OK; if (oidc_util_request_has_parameter(r, OIDC_REDIRECT_URI_REQUEST_INFO)) { OIDC_METRICS_COUNTER_INC(r, c, OM_CONTENT_REQUEST_INFO); /* see if a session was retained in the request state */ apr_pool_userdata_get((void **)&session, OIDC_USERDATA_SESSION, r->pool); /* if no retained session was found, load it from the cache or create a new one*/ if (session == NULL) oidc_session_load(r, &session); /* * see if the request state indicates that the (retained) * session was modified and needs to be updated in the cache */ needs_save = (oidc_request_state_get(r, OIDC_REQUEST_STATE_KEY_SAVE) != NULL); /* handle request for session info */ rc = oidc_info_request(r, c, session, needs_save); /* free resources allocated for the session */ oidc_session_free(r, session); } else if (oidc_util_request_has_parameter(r, OIDC_REDIRECT_URI_REQUEST_DPOP)) { OIDC_METRICS_COUNTER_INC(r, c, OM_CONTENT_REQUEST_DPOP); /* handle request to create a DPoP proof */ rc = oidc_dpop_request(r, c); } else if (oidc_util_request_has_parameter(r, OIDC_REDIRECT_URI_REQUEST_JWKS)) { OIDC_METRICS_COUNTER_INC(r, c, OM_CONTENT_REQUEST_JWKS); /* handle JWKs request */ rc = oidc_jwks_request(r, c); } else { OIDC_METRICS_COUNTER_INC(r, c, OM_CONTENT_REQUEST_UNKNOWN); } } else if (oidc_request_state_get(r, OIDC_REQUEST_STATE_KEY_DISCOVERY) != NULL) { OIDC_METRICS_COUNTER_INC(r, c, OM_CONTENT_REQUEST_DISCOVERY); /* discovery may result in a 200 HTML page or a redirect to an external URL */ rc = oidc_discovery_request(r, c); } else if (oidc_request_state_get(r, OIDC_REQUEST_STATE_KEY_AUTHN) != NULL) { OIDC_METRICS_COUNTER_INC(r, c, OM_CONTENT_REQUEST_POST_PRESERVE); /* sending POST preserve */ rc = OK; } /* else: an authenticated request for which content is produced downstream */ return rc; } mod_auth_openidc-2.4.16.10/src/handle/discovery.c000066400000000000000000000445261476721736500215560ustar00rootroot00000000000000/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ /*************************************************************************** * Copyright (C) 2017-2025 ZmartZone Holding BV * All rights reserved. * * DISCLAIMER OF WARRANTIES: * * THE SOFTWARE PROVIDED HEREUNDER IS PROVIDED ON AN "AS IS" BASIS, WITHOUT * ANY WARRANTIES OR REPRESENTATIONS EXPRESS, IMPLIED OR STATUTORY; INCLUDING, * WITHOUT LIMITATION, WARRANTIES OF QUALITY, PERFORMANCE, NONINFRINGEMENT, * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. NOR ARE THERE ANY * WARRANTIES CREATED BY A COURSE OR DEALING, COURSE OF PERFORMANCE OR TRADE * USAGE. FURTHERMORE, THERE ARE NO WARRANTIES THAT THE SOFTWARE WILL MEET * YOUR NEEDS OR BE FREE FROM ERRORS, OR THAT THE OPERATION OF THE SOFTWARE * WILL BE UNINTERRUPTED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * @Author: Hans Zandbelt - hans.zandbelt@openidc.com */ #include "cfg/dir.h" #include "handle/handle.h" #include "metadata.h" #include "mod_auth_openidc.h" #include "proto/proto.h" #include "util.h" /* parameter name of the callback URL in the discovery response */ #define OIDC_DISC_CB_PARAM "oidc_callback" /* parameter name of the OP provider selection in the discovery response */ #define OIDC_DISC_OP_PARAM "iss" /* parameter name of the user URL in the discovery response */ #define OIDC_DISC_USER_PARAM "disc_user" /* parameter name of the original URL in the discovery response */ #define OIDC_DISC_RT_PARAM "target_link_uri" /* parameter name of login hint in the discovery response */ #define OIDC_DISC_LH_PARAM "login_hint" /* parameter name of parameters that need to be passed in the authentication request */ #define OIDC_DISC_AR_PARAM "auth_request_params" /* parameter name of the scopes required in the discovery response */ #define OIDC_DISC_SC_PARAM "scopes" /* * find out whether the request is a response from an IDP discovery page */ apr_byte_t oidc_is_discovery_response(request_rec *r, oidc_cfg_t *cfg) { /* * prereq: this is a call to the configured redirect_uri, now see if: * the OIDC_DISC_OP_PARAM is present */ return oidc_util_request_has_parameter(r, OIDC_DISC_OP_PARAM) || oidc_util_request_has_parameter(r, OIDC_DISC_USER_PARAM); } static const char *oidc_discovery_csrf_cookie_samesite(request_rec *r, oidc_cfg_t *c) { const char *rv = NULL; switch (oidc_cfg_cookie_same_site_get(c)) { case OIDC_SAMESITE_COOKIE_STRICT: rv = OIDC_HTTP_COOKIE_SAMESITE_STRICT; break; case OIDC_SAMESITE_COOKIE_LAX: rv = OIDC_HTTP_COOKIE_SAMESITE_LAX; break; case OIDC_SAMESITE_COOKIE_NONE: rv = OIDC_HTTP_COOKIE_SAMESITE_NONE(c, r); break; case OIDC_SAMESITE_COOKIE_DISABLED: break; default: break; } return rv; } /* define the name of the cookie/parameter for CSRF protection */ #define OIDC_CSRF_NAME "x_csrf" /* * present the user with an OP selection screen */ int oidc_discovery_request(request_rec *r, oidc_cfg_t *cfg) { oidc_debug(r, "enter"); /* obtain the URL we're currently accessing, to be stored in the state/session */ char *current_url = oidc_util_current_url(r, oidc_cfg_x_forwarded_headers_get(cfg)); const char *method = oidc_original_request_method(r, cfg, FALSE); /* generate CSRF token */ char *csrf = NULL; if (oidc_proto_generate_nonce(r, &csrf, 8) == FALSE) return HTTP_INTERNAL_SERVER_ERROR; const char *path_scopes = oidc_cfg_dir_path_scope_get(r); const char *path_auth_request_params = oidc_cfg_dir_path_auth_request_params_get(r); const char *discover_url = oidc_cfg_dir_discover_url_get(r); /* see if there's an external discovery page configured */ if (discover_url != NULL) { /* yes, assemble the parameters for external discovery */ char *url = apr_psprintf(r->pool, "%s%s%s=%s&%s=%s&%s=%s&%s=%s", discover_url, strchr(discover_url, OIDC_CHAR_QUERY) != NULL ? OIDC_STR_AMP : OIDC_STR_QUERY, OIDC_DISC_RT_PARAM, oidc_http_url_encode(r, current_url), OIDC_DISC_RM_PARAM, method, OIDC_DISC_CB_PARAM, oidc_http_url_encode(r, oidc_util_redirect_uri(r, cfg)), OIDC_CSRF_NAME, oidc_http_url_encode(r, csrf)); if (path_scopes != NULL) url = apr_psprintf(r->pool, "%s&%s=%s", url, OIDC_DISC_SC_PARAM, oidc_http_url_encode(r, path_scopes)); if (path_auth_request_params != NULL) url = apr_psprintf(r->pool, "%s&%s=%s", url, OIDC_DISC_AR_PARAM, oidc_http_url_encode(r, path_auth_request_params)); /* log what we're about to do */ oidc_debug(r, "redirecting to external discovery page: %s", url); /* set CSRF cookie */ oidc_http_set_cookie(r, OIDC_CSRF_NAME, csrf, -1, oidc_discovery_csrf_cookie_samesite(r, cfg)); /* see if we need to preserve POST parameters through Javascript/HTML5 storage */ if (oidc_response_post_preserve_javascript(r, url, NULL, NULL) == TRUE) return OK; /* do the actual redirect to an external discovery page */ oidc_http_hdr_out_location_set(r, url); return HTTP_MOVED_TEMPORARILY; } /* get a list of all providers configured in the metadata directory */ apr_array_header_t *arr = NULL; if (oidc_metadata_list(r, cfg, &arr) == FALSE) return oidc_util_html_send_error(r, "Configuration Error", "No configured providers found, contact your administrator", HTTP_UNAUTHORIZED); /* assemble a where-are-you-from IDP discovery HTML page */ const char *s = "

Select your OpenID Connect Identity Provider

\n"; /* list all configured providers in there */ int i; for (i = 0; i < arr->nelts; i++) { const char *issuer = APR_ARRAY_IDX(arr, i, const char *); // TODO: html escape (especially & character) char *href = apr_psprintf( r->pool, "%s?%s=%s&%s=%s&%s=%s&%s=%s", oidc_util_redirect_uri(r, cfg), OIDC_DISC_OP_PARAM, oidc_http_url_encode(r, issuer), OIDC_DISC_RT_PARAM, oidc_http_url_encode(r, current_url), OIDC_DISC_RM_PARAM, method, OIDC_CSRF_NAME, csrf); if (path_scopes != NULL) href = apr_psprintf(r->pool, "%s&%s=%s", href, OIDC_DISC_SC_PARAM, oidc_http_url_encode(r, path_scopes)); if (path_auth_request_params != NULL) href = apr_psprintf(r->pool, "%s&%s=%s", href, OIDC_DISC_AR_PARAM, oidc_http_url_encode(r, path_auth_request_params)); char *display = (_oidc_strstr(issuer, "https://") == NULL) ? apr_pstrdup(r->pool, issuer) : apr_pstrdup(r->pool, issuer + _oidc_strlen("https://")); /* strip port number */ // char *p = _oidc_strstr(display, ":"); // if (p != NULL) *p = '\0'; /* point back to the redirect_uri, where the selection is handled, with an IDP selection and return_to * URL */ s = apr_psprintf(r->pool, "%s

%s

\n", s, href, display); } /* add an option to enter an account or issuer name for dynamic OP discovery */ s = apr_psprintf(r->pool, "%s
\n", s, oidc_util_redirect_uri(r, cfg)); s = apr_psprintf(r->pool, "%s

\n", s, OIDC_DISC_RT_PARAM, current_url); s = apr_psprintf(r->pool, "%s

\n", s, OIDC_DISC_RM_PARAM, method); s = apr_psprintf(r->pool, "%s

\n", s, OIDC_CSRF_NAME, csrf); if (path_scopes != NULL) s = apr_psprintf(r->pool, "%s

\n", s, OIDC_DISC_SC_PARAM, path_scopes); if (path_auth_request_params != NULL) s = apr_psprintf(r->pool, "%s

\n", s, OIDC_DISC_AR_PARAM, path_auth_request_params); s = apr_psprintf(r->pool, "%s

Or enter your account name (eg. "mike@seed.gluu.org", or an IDP identifier " "(eg. "mitreid.org"):

\n", s); s = apr_psprintf(r->pool, "%s

\n", s, OIDC_DISC_OP_PARAM, ""); s = apr_psprintf(r->pool, "%s

\n", s); s = apr_psprintf(r->pool, "%s
\n", s); oidc_http_set_cookie(r, OIDC_CSRF_NAME, csrf, -1, oidc_discovery_csrf_cookie_samesite(r, cfg)); char *javascript = NULL, *javascript_method = NULL; char *html_head = ""; if (oidc_response_post_preserve_javascript(r, NULL, &javascript, &javascript_method) == TRUE) html_head = apr_psprintf(r->pool, "%s%s", html_head, javascript); /* now send the HTML contents to the user agent */ return oidc_util_html_send(r, "OpenID Connect Provider Discovery", html_head, javascript_method, s, OK); } /* * check if the target_link_uri matches to configuration settings to prevent an open redirect */ static int oidc_discovery_target_link_uri_match(request_rec *r, oidc_cfg_t *cfg, const char *target_link_uri) { apr_uri_t o_uri; apr_uri_parse(r->pool, target_link_uri, &o_uri); if (o_uri.hostname == NULL) { oidc_error(r, "could not parse the \"target_link_uri\" (%s) in to a valid URL: aborting.", target_link_uri); return FALSE; } apr_uri_t r_uri; apr_uri_parse(r->pool, oidc_util_redirect_uri(r, cfg), &r_uri); if (oidc_cfg_cookie_domain_get(cfg) == NULL) { /* cookie_domain set: see if the target_link_uri matches the redirect_uri host (because the session * cookie will be set host-wide) */ if (_oidc_strnatcasecmp(o_uri.hostname, r_uri.hostname) != 0) { const char *p = oidc_util_strcasestr(o_uri.hostname, r_uri.hostname); if ((p == NULL) || (_oidc_strnatcasecmp(r_uri.hostname, p) != 0)) { oidc_error(r, "the URL hostname (%s) of the configured " OIDCRedirectURI " does not match the URL hostname of the \"target_link_uri\" (%s): aborting " "to prevent an open redirect.", r_uri.hostname, o_uri.hostname); return FALSE; } } } else { /* cookie_domain set: see if the target_link_uri is within the cookie_domain */ const char *p = oidc_util_strcasestr(o_uri.hostname, oidc_cfg_cookie_domain_get(cfg)); if ((p == NULL) || (_oidc_strnatcasecmp(oidc_cfg_cookie_domain_get(cfg), p) != 0)) { oidc_error(r, "the domain (%s) configured in " OIDCCookieDomain " does not match the URL hostname (%s) of the \"target_link_uri\" (%s): aborting to " "prevent an open redirect.", oidc_cfg_cookie_domain_get(cfg), o_uri.hostname, target_link_uri); return FALSE; } } /* see if the cookie_path setting matches the target_link_uri path */ const char *cookie_path = oidc_cfg_dir_cookie_path_get(r); if (cookie_path != NULL) { char *p = (o_uri.path != NULL) ? _oidc_strstr(o_uri.path, cookie_path) : NULL; if (p != o_uri.path) { oidc_error(r, "the path (%s) configured in " OIDCCookiePath " does not match the URL path (%s) of the \"target_link_uri\" (%s): aborting to " "prevent an open redirect.", cookie_path, o_uri.path, target_link_uri); return FALSE; } else if (_oidc_strlen(o_uri.path) > _oidc_strlen(cookie_path)) { int n = _oidc_strlen(cookie_path); if (cookie_path[n - 1] == OIDC_CHAR_FORWARD_SLASH) n--; if (o_uri.path[n] != OIDC_CHAR_FORWARD_SLASH) { oidc_error(r, "the path (%s) configured in " OIDCCookiePath " does not match the URL path (%s) of the \"target_link_uri\" (%s): " "aborting to prevent an open redirect.", cookie_path, o_uri.path, target_link_uri); return FALSE; } } } return TRUE; } /* * handle a response from an IDP discovery page and/or handle 3rd-party initiated SSO */ int oidc_discovery_response(request_rec *r, oidc_cfg_t *c) { /* variables to hold the values returned in the response */ char *issuer = NULL, *target_link_uri = NULL, *login_hint = NULL, *auth_request_params = NULL, *csrf_cookie, *csrf_query = NULL, *user = NULL, *path_scopes; oidc_provider_t *provider = NULL; char *error_str = NULL; char *error_description = NULL; oidc_util_request_parameter_get(r, OIDC_DISC_OP_PARAM, &issuer); oidc_util_request_parameter_get(r, OIDC_DISC_USER_PARAM, &user); oidc_util_request_parameter_get(r, OIDC_DISC_RT_PARAM, &target_link_uri); oidc_util_request_parameter_get(r, OIDC_DISC_LH_PARAM, &login_hint); oidc_util_request_parameter_get(r, OIDC_DISC_SC_PARAM, &path_scopes); oidc_util_request_parameter_get(r, OIDC_DISC_AR_PARAM, &auth_request_params); oidc_util_request_parameter_get(r, OIDC_CSRF_NAME, &csrf_query); csrf_cookie = oidc_http_get_cookie(r, OIDC_CSRF_NAME); /* do CSRF protection if not 3rd party initiated SSO */ if (csrf_cookie) { /* clean CSRF cookie */ oidc_http_set_cookie(r, OIDC_CSRF_NAME, "", 0, OIDC_HTTP_COOKIE_SAMESITE_NONE(c, r)); /* compare CSRF cookie value with query parameter value */ if ((csrf_query == NULL) || _oidc_strcmp(csrf_query, csrf_cookie) != 0) { oidc_warn( r, "CSRF protection failed, no Discovery and dynamic client registration will be allowed"); csrf_cookie = NULL; } } // TODO: trim issuer/accountname/domain input and do more input validation oidc_debug(r, "issuer=\"%s\", target_link_uri=\"%s\", login_hint=\"%s\", user=\"%s\"", issuer, target_link_uri, login_hint, user); if (target_link_uri == NULL) { if (oidc_cfg_default_sso_url_get(c) == NULL) { return oidc_util_html_send_error(r, "Invalid Request", "SSO to this module without specifying a \"target_link_uri\" " "parameter is not possible because " OIDCDefaultURL " is not set.", HTTP_INTERNAL_SERVER_ERROR); } target_link_uri = apr_pstrdup(r->pool, oidc_util_absolute_url(r, c, oidc_cfg_default_sso_url_get(c))); } /* do open redirect prevention, step 1 */ if (oidc_discovery_target_link_uri_match(r, c, target_link_uri) == FALSE) { return oidc_util_html_send_error(r, "Invalid Request", "\"target_link_uri\" parameter does not match configuration settings, " "aborting to prevent an open redirect.", HTTP_UNAUTHORIZED); } /* do input validation on the target_link_uri parameter value, step 2 */ if (oidc_validate_redirect_url(r, c, target_link_uri, TRUE, &error_str, &error_description) == FALSE) { return oidc_util_html_send_error(r, error_str, error_description, HTTP_UNAUTHORIZED); } /* see if this is a static setup */ if (oidc_cfg_metadata_dir_get(c) == NULL) { if ((oidc_provider_static_config(r, c, &provider) == TRUE) && (issuer != NULL)) { if (_oidc_strcmp(oidc_cfg_provider_issuer_get(provider), issuer) != 0) { return oidc_util_html_send_error( r, "Invalid Request", apr_psprintf( r->pool, "The \"iss\" value must match the configured providers' one (%s != %s).", issuer, oidc_cfg_provider_issuer_get(oidc_cfg_provider_get(c))), HTTP_INTERNAL_SERVER_ERROR); } } return oidc_request_authenticate_user(r, c, NULL, target_link_uri, login_hint, NULL, NULL, auth_request_params, path_scopes); } /* find out if the user entered an account name or selected an OP manually */ if (user != NULL) { if (login_hint == NULL) login_hint = apr_pstrdup(r->pool, user); /* normalize the user identifier */ if (_oidc_strstr(user, "https://") != user) user = apr_psprintf(r->pool, "https://%s", user); /* got an user identifier as input, perform OP discovery with that */ if (oidc_proto_discovery_url_based(r, c, user, &issuer) == FALSE) { /* something did not work out, show a user facing error */ return oidc_util_html_send_error(r, "Invalid Request", "Could not resolve the provided user identifier to an OpenID " "Connect provider; check your syntax.", HTTP_NOT_FOUND); } /* issuer is set now, so let's continue as planned */ } else if (_oidc_strstr(issuer, OIDC_STR_AT) != NULL) { if (login_hint == NULL) { login_hint = apr_pstrdup(r->pool, issuer); // char *p = _oidc_strstr(issuer, OIDC_STR_AT); //*p = '\0'; } /* got an account name as input, perform OP discovery with that */ if (oidc_proto_discovery_account_based(r, c, issuer, &issuer) == FALSE) { /* something did not work out, show a user facing error */ return oidc_util_html_send_error(r, "Invalid Request", "Could not resolve the provided account name to an OpenID " "Connect provider; check your syntax.", HTTP_NOT_FOUND); } /* issuer is set now, so let's continue as planned */ } /* strip trailing '/' */ int n = _oidc_strlen(issuer); if (issuer[n - 1] == OIDC_CHAR_FORWARD_SLASH) issuer[n - 1] = '\0'; if (oidc_util_request_has_parameter(r, "test-config")) { json_t *j_provider = NULL; oidc_metadata_provider_get(r, c, issuer, &j_provider, csrf_cookie != NULL); if (j_provider) json_decref(j_provider); return OK; } /* try and get metadata from the metadata directories for the selected OP */ if ((oidc_metadata_get(r, c, issuer, &provider, csrf_cookie != NULL) == TRUE) && (provider != NULL)) { if (oidc_util_request_has_parameter(r, "test-jwks-uri")) { json_t *j_jwks = NULL; apr_byte_t force_refresh = TRUE; oidc_metadata_jwks_get(r, c, oidc_cfg_provider_jwks_uri_get(provider), oidc_cfg_provider_ssl_validate_server_get(provider), &j_jwks, &force_refresh); json_decref(j_jwks); return OK; } else { /* now we've got a selected OP, send the user there to authenticate */ return oidc_request_authenticate_user(r, c, provider, target_link_uri, login_hint, NULL, NULL, auth_request_params, path_scopes); } } /* something went wrong */ return oidc_util_html_send_error(r, "Invalid Request", "Could not find valid provider metadata for the selected OpenID Connect " "provider; contact the administrator", HTTP_NOT_FOUND); } mod_auth_openidc-2.4.16.10/src/handle/dpop.c000066400000000000000000000115341476721736500205020ustar00rootroot00000000000000/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ /*************************************************************************** * Copyright (C) 2017-2025 ZmartZone Holding BV * All rights reserved. * * DISCLAIMER OF WARRANTIES: * * THE SOFTWARE PROVIDED HEREUNDER IS PROVIDED ON AN "AS IS" BASIS, WITHOUT * ANY WARRANTIES OR REPRESENTATIONS EXPRESS, IMPLIED OR STATUTORY; INCLUDING, * WITHOUT LIMITATION, WARRANTIES OF QUALITY, PERFORMANCE, NONINFRINGEMENT, * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. NOR ARE THERE ANY * WARRANTIES CREATED BY A COURSE OR DEALING, COURSE OF PERFORMANCE OR TRADE * USAGE. FURTHERMORE, THERE ARE NO WARRANTIES THAT THE SOFTWARE WILL MEET * YOUR NEEDS OR BE FREE FROM ERRORS, OR THAT THE OPERATION OF THE SOFTWARE * WILL BE UNINTERRUPTED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * @Author: Hans Zandbelt - hans.zandbelt@openidc.com */ #include "handle/handle.h" #include "mod_auth_openidc.h" #include "proto/proto.h" #include "util.h" #include #define OIDC_DPOP_PARAM_URL "url" #define OIDC_DPOP_PARAM_NONCE "nonce" #define OIDC_DPOP_PARAM_METHOD "method" int oidc_dpop_request(request_rec *r, oidc_cfg_t *c) { int rc = HTTP_BAD_REQUEST; char *s_url = NULL; char *s_access_token = NULL; char *s_nonce = NULL; char *s_method = NULL; char *s_dpop = NULL; char *s_response = NULL; json_t *json = NULL; char *remote_ip = NULL; #if AP_MODULE_MAGIC_AT_LEAST(20111130, 0) remote_ip = r->useragent_ip; #else remote_ip = r->connection->remote_ip; #endif if (!oidc_cfg_dpop_api_enabled_get(c)) { oidc_error(r, "DPoP hook called but the DPoP API is not enabled in %s", OIDCDPoPMode); goto end; } /* try to make sure that the proof-of-possession semantics are preserved */ if ((_oidc_strnatcasecmp(remote_ip, r->connection->local_ip) != 0) && (apr_table_get(r->subprocess_env, "OIDC_DPOP_API_INSECURE") == 0)) { oidc_warn( r, "reject DPoP creation request from remote host: you should create a separate virtual (sub)host " "that requires client certificate authentication to allow and proxy this request (remote_ip=%s, " "r->connection->local_ip=%s)", remote_ip, r->connection->local_ip); rc = HTTP_UNAUTHORIZED; goto end; } /* retrieve the access token parameter */ oidc_util_request_parameter_get(r, OIDC_REDIRECT_URI_REQUEST_DPOP, &s_access_token); if (s_access_token == NULL) { oidc_error(r, "\"access_token\" value to the \"%s\" parameter is missing", OIDC_REDIRECT_URI_REQUEST_DPOP); goto end; } /* retrieve the URL parameter */ oidc_util_request_parameter_get(r, OIDC_DPOP_PARAM_URL, &s_url); if (s_url == NULL) { oidc_error(r, "\"url\" parameter is missing"); goto end; } /* retrieve the optional nonce parameter */ oidc_util_request_parameter_get(r, OIDC_DPOP_PARAM_NONCE, &s_nonce); /* parse the optional HTTP method parameter */ oidc_util_request_parameter_get(r, OIDC_DPOP_PARAM_METHOD, &s_method); if (_oidc_strnatcasecmp(s_method, "post") == 0) s_method = "POST"; else if ((_oidc_strnatcasecmp(s_method, "get") == 0) || (s_method == NULL)) s_method = "GET"; /* create the DPoP header value */ if ((oidc_proto_dpop_create(r, c, s_url, s_method, s_access_token, s_nonce, &s_dpop) == FALSE) || (s_dpop == NULL)) { oidc_error(r, "creating the DPoP proof value failed"); rc = HTTP_INTERNAL_SERVER_ERROR; goto end; } /* assemble and serialize the JSON response object */ json = json_object(); json_object_set_new(json, OIDC_HTTP_HDR_DPOP, json_string(s_dpop)); s_response = oidc_util_encode_json(r->pool, json, JSON_COMPACT | JSON_PRESERVE_ORDER); /* return the serialized JSON response */ rc = oidc_util_http_send(r, s_response, _oidc_strlen(s_response), OIDC_HTTP_CONTENT_TYPE_JSON, OK); end: if (json) json_decref(json); return rc; } mod_auth_openidc-2.4.16.10/src/handle/handle.h000066400000000000000000000153331476721736500210010ustar00rootroot00000000000000/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ /*************************************************************************** * Copyright (C) 2017-2025 ZmartZone Holding BV * All rights reserved. * * DISCLAIMER OF WARRANTIES: * * THE SOFTWARE PROVIDED HEREUNDER IS PROVIDED ON AN "AS IS" BASIS, WITHOUT * ANY WARRANTIES OR REPRESENTATIONS EXPRESS, IMPLIED OR STATUTORY; INCLUDING, * WITHOUT LIMITATION, WARRANTIES OF QUALITY, PERFORMANCE, NONINFRINGEMENT, * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. NOR ARE THERE ANY * WARRANTIES CREATED BY A COURSE OR DEALING, COURSE OF PERFORMANCE OR TRADE * USAGE. FURTHERMORE, THERE ARE NO WARRANTIES THAT THE SOFTWARE WILL MEET * YOUR NEEDS OR BE FREE FROM ERRORS, OR THAT THE OPERATION OF THE SOFTWARE * WILL BE UNINTERRUPTED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * @Author: Hans Zandbelt - hans.zandbelt@openidc.com */ #ifndef _MOD_AUTH_OPENIDC_HANDLE_H_ #define _MOD_AUTH_OPENIDC_HANDLE_H_ #include "cfg/dir.h" #include "const.h" // for the PACKAGE_* defines #include "jose.h" #include "session.h" #include #include #include // authz.c /* the name of the keyword that follows the Require primitive to indicate claims-based authorization */ #define OIDC_REQUIRE_CLAIM_NAME "claim" #ifdef USE_LIBJQ /* the name of the keyword that follows the Require primitive to indicate claims-expression-based authorization */ #define OIDC_REQUIRE_CLAIMS_EXPR_NAME "claims_expr" #endif typedef apr_byte_t (*oidc_authz_match_claim_fn_type)(request_rec *, const char *const, json_t *); apr_byte_t oidc_authz_match_claim(request_rec *r, const char *const attr_spec, json_t *claims); #if HAVE_APACHE_24 #ifdef USE_LIBJQ authz_status oidc_authz_24_checker_claims_expr(request_rec *r, const char *require_args, const void *parsed_require_args); #endif authz_status oidc_authz_24_checker_claim(request_rec *r, const char *require_args, const void *parsed_require_args); authz_status oidc_authz_24_worker(request_rec *r, json_t *claims, const char *require_args, const void *parsed_require_args, oidc_authz_match_claim_fn_type match_claim_fn); #else int oidc_authz_22_checker(request_rec *r); #endif // content.c int oidc_content_handler(request_rec *r); // discovery.c int oidc_discovery_request(request_rec *r, oidc_cfg_t *cfg); apr_byte_t oidc_is_discovery_response(request_rec *r, oidc_cfg_t *cfg); int oidc_discovery_response(request_rec *r, oidc_cfg_t *c); // dpop.c int oidc_dpop_request(request_rec *r, oidc_cfg_t *c); // info.c int oidc_info_request(request_rec *r, oidc_cfg_t *c, oidc_session_t *session, apr_byte_t needs_save); // jwks_c. int oidc_jwks_request(request_rec *r, oidc_cfg_t *c); // logout.c int oidc_logout(request_rec *r, oidc_cfg_t *c, oidc_session_t *session); int oidc_logout_request(request_rec *r, oidc_cfg_t *c, oidc_session_t *session, const char *url, apr_byte_t revoke_tokens); // refresh.c apr_byte_t oidc_refresh_token_grant(request_rec *r, oidc_cfg_t *c, oidc_session_t *session, oidc_provider_t *provider, char **new_access_token, char **new_access_token_type, char **new_id_token); int oidc_refresh_token_request(request_rec *r, oidc_cfg_t *c, oidc_session_t *session); apr_byte_t oidc_refresh_access_token_before_expiry(request_rec *r, oidc_cfg_t *cfg, oidc_session_t *session, int ttl_minimum, apr_byte_t *needs_save); // request_uri.c int oidc_request_uri(request_rec *r, oidc_cfg_t *c); // request.c int oidc_request_authenticate_user(request_rec *r, oidc_cfg_t *c, oidc_provider_t *provider, const char *original_url, const char *login_hint, const char *id_token_hint, const char *prompt, const char *auth_request_params, const char *path_scope); apr_byte_t oidc_request_check_cookie_domain(request_rec *r, oidc_cfg_t *c, const char *original_url); // response.c apr_byte_t oidc_response_post_preserve_javascript(request_rec *r, const char *location, char **javascript, char **javascript_method); char *oidc_response_make_sid_iss_unique(request_rec *r, const char *sid, const char *issuer); int oidc_response_authorization_redirect(request_rec *r, oidc_cfg_t *c, oidc_session_t *session); int oidc_response_authorization_post(request_rec *r, oidc_cfg_t *c, oidc_session_t *session); apr_byte_t oidc_response_save_in_session(request_rec *r, oidc_cfg_t *c, oidc_session_t *session, oidc_provider_t *provider, const char *remoteUser, const char *id_token, oidc_jwt_t *id_token_jwt, const char *claims, const char *access_token, const char *access_token_type, const int expires_in, const char *refresh_token, const char *session_state, const char *state, const char *original_url, const char *userinfo_jwt); // revoke.c int oidc_revoke_session(request_rec *r, oidc_cfg_t *c); int oidc_revoke_at_cache_remove(request_rec *r, oidc_cfg_t *c); // session_management.c int oidc_session_management(request_rec *r, oidc_cfg_t *c, oidc_session_t *session); // userinfo.c void oidc_userinfo_store_claims(request_rec *r, oidc_cfg_t *c, oidc_session_t *session, oidc_provider_t *provider, const char *claims, const char *userinfo_jwt); const char *oidc_userinfo_retrieve_claims(request_rec *r, oidc_cfg_t *c, oidc_provider_t *provider, const char *access_token, const char *access_token_type, oidc_session_t *session, char *id_token_sub, char **userinfo_jwt); apr_byte_t oidc_userinfo_refresh_claims(request_rec *r, oidc_cfg_t *cfg, oidc_session_t *session, apr_byte_t *needs_save); void oidc_userinfo_pass_as(request_rec *r, oidc_cfg_t *cfg, oidc_session_t *session, const char *s_claims, oidc_appinfo_pass_in_t pass_in, oidc_appinfo_encoding_t encoding); #endif // _MOD_AUTH_OPENIDC_HANDLE_H_ mod_auth_openidc-2.4.16.10/src/handle/info.c000066400000000000000000000241461476721736500204760ustar00rootroot00000000000000/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ /*************************************************************************** * Copyright (C) 2017-2025 ZmartZone Holding BV * All rights reserved. * * DISCLAIMER OF WARRANTIES: * * THE SOFTWARE PROVIDED HEREUNDER IS PROVIDED ON AN "AS IS" BASIS, WITHOUT * ANY WARRANTIES OR REPRESENTATIONS EXPRESS, IMPLIED OR STATUTORY; INCLUDING, * WITHOUT LIMITATION, WARRANTIES OF QUALITY, PERFORMANCE, NONINFRINGEMENT, * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. NOR ARE THERE ANY * WARRANTIES CREATED BY A COURSE OR DEALING, COURSE OF PERFORMANCE OR TRADE * USAGE. FURTHERMORE, THERE ARE NO WARRANTIES THAT THE SOFTWARE WILL MEET * YOUR NEEDS OR BE FREE FROM ERRORS, OR THAT THE OPERATION OF THE SOFTWARE * WILL BE UNINTERRUPTED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * @Author: Hans Zandbelt - hans.zandbelt@openidc.com */ #include "handle/handle.h" #include "mod_auth_openidc.h" #include "util.h" #define OIDC_INFO_PARAM_ACCESS_TOKEN_REFRESH_INTERVAL "access_token_refresh_interval" #define OIDC_HOOK_INFO_FORMAT_JSON "json" #define OIDC_HOOK_INFO_FORMAT_HTML "html" /* * handle request for session info */ int oidc_info_request(request_rec *r, oidc_cfg_t *c, oidc_session_t *session, apr_byte_t needs_save) { int rc = HTTP_UNAUTHORIZED; char *s_format = NULL; char *s_interval = NULL; char *s_extend_session = NULL; char *r_value = NULL; apr_byte_t b_extend_session = TRUE; apr_time_t t_interval = -1; oidc_util_request_parameter_get(r, OIDC_REDIRECT_URI_REQUEST_INFO, &s_format); oidc_util_request_parameter_get(r, OIDC_INFO_PARAM_ACCESS_TOKEN_REFRESH_INTERVAL, &s_interval); oidc_util_request_parameter_get(r, OIDC_INFO_PARAM_EXTEND_SESSION, &s_extend_session); if ((s_extend_session) && (_oidc_strcmp(s_extend_session, "false") == 0)) b_extend_session = FALSE; /* see if this is a request for a format that is supported */ if ((_oidc_strcmp(OIDC_HOOK_INFO_FORMAT_JSON, s_format) != 0) && (_oidc_strcmp(OIDC_HOOK_INFO_FORMAT_HTML, s_format) != 0)) { oidc_warn(r, "request for unknown format: %s", s_format); return HTTP_UNSUPPORTED_MEDIA_TYPE; } /* check that we actually have a user session and this is someone calling with a proper session cookie */ if (session->remote_user == NULL) { oidc_warn(r, "no user session found"); return HTTP_UNAUTHORIZED; } /* set the user in the main request for further (incl. sub-request and authz) processing */ r->user = apr_pstrdup(r->pool, session->remote_user); if (oidc_cfg_info_hook_data_get(c) == NULL) { oidc_warn(r, "no data configured to return in " OIDCInfoHook); return HTTP_NOT_FOUND; } /* see if we can and need to refresh the access token */ if ((s_interval != NULL) && (oidc_session_get_refresh_token(r, session) != NULL)) { t_interval = _oidc_str_to_time(s_interval, -1); if (t_interval > -1) { t_interval = apr_time_from_sec(t_interval); /* get the last refresh timestamp from the session info */ apr_time_t last_refresh = oidc_session_get_access_token_last_refresh(r, session); oidc_debug(r, "refresh needed in: %" APR_TIME_T_FMT " seconds", apr_time_sec(last_refresh + t_interval - apr_time_now())); /* see if we need to refresh again */ if (last_refresh + t_interval < apr_time_now()) { /* get the current provider info */ oidc_provider_t *provider = NULL; if (oidc_get_provider_from_session(r, c, session, &provider) == FALSE) return HTTP_INTERNAL_SERVER_ERROR; /* execute the actual refresh grant */ if (oidc_refresh_token_grant(r, c, session, provider, NULL, NULL, NULL) == FALSE) { oidc_warn(r, "access_token could not be refreshed"); return HTTP_INTERNAL_SERVER_ERROR; } needs_save = TRUE; } } } /* create the JSON object */ json_t *json = json_object(); /* add a timestamp of creation in there for the caller */ if (apr_hash_get(oidc_cfg_info_hook_data_get(c), OIDC_HOOK_INFO_TIMESTAMP, APR_HASH_KEY_STRING)) { json_object_set_new(json, OIDC_HOOK_INFO_TIMESTAMP, json_integer(apr_time_sec(apr_time_now()))); } /* * refresh the claims from the userinfo endpoint * side-effect is that this may refresh the access token if not already done * note that OIDCUserInfoRefreshInterval should be set to control the refresh policy */ if (b_extend_session) { if (oidc_userinfo_refresh_claims(r, c, session, &needs_save) == FALSE) { rc = HTTP_INTERNAL_SERVER_ERROR; goto end; } } /* include the access token in the session info */ if (apr_hash_get(oidc_cfg_info_hook_data_get(c), OIDC_HOOK_INFO_ACCES_TOKEN, APR_HASH_KEY_STRING)) { const char *access_token = oidc_session_get_access_token(r, session); if (access_token != NULL) json_object_set_new(json, OIDC_HOOK_INFO_ACCES_TOKEN, json_string(access_token)); const char *access_token_type = oidc_session_get_access_token_type(r, session); if (access_token_type != NULL) json_object_set_new(json, OIDC_HOOK_INFO_ACCES_TOKEN_TYPE, json_string(access_token_type)); } /* include the access token expiry timestamp in the session info */ if (apr_hash_get(oidc_cfg_info_hook_data_get(c), OIDC_HOOK_INFO_ACCES_TOKEN_EXP, APR_HASH_KEY_STRING)) { const char *access_token_expires = oidc_session_get_access_token_expires2str(r, session); if (access_token_expires != NULL) json_object_set_new(json, OIDC_HOOK_INFO_ACCES_TOKEN_EXP, json_string(access_token_expires)); } /* include the serialized id_token (id_token_hint) in the session info */ if (apr_hash_get(oidc_cfg_info_hook_data_get(c), OIDC_HOOK_INFO_ID_TOKEN_HINT, APR_HASH_KEY_STRING)) { const char *s_id_token = oidc_session_get_idtoken(r, session); if (s_id_token != NULL) json_object_set_new(json, OIDC_HOOK_INFO_ID_TOKEN_HINT, json_string(s_id_token)); } /* include the id_token claims in the session info */ if (apr_hash_get(oidc_cfg_info_hook_data_get(c), OIDC_HOOK_INFO_ID_TOKEN, APR_HASH_KEY_STRING)) { json_t *id_token = oidc_session_get_idtoken_claims_json(r, session); if (id_token) json_object_set_new(json, OIDC_HOOK_INFO_ID_TOKEN, id_token); } if (apr_hash_get(oidc_cfg_info_hook_data_get(c), OIDC_HOOK_INFO_USER_INFO, APR_HASH_KEY_STRING)) { /* include the claims from the userinfo endpoint the session info */ json_t *claims = oidc_session_get_userinfo_claims_json(r, session); if (claims) json_object_set_new(json, OIDC_HOOK_INFO_USER_INFO, claims); } /* include the maximum session lifetime in the session info */ if (apr_hash_get(oidc_cfg_info_hook_data_get(c), OIDC_HOOK_INFO_SESSION_EXP, APR_HASH_KEY_STRING)) { apr_time_t session_expires = oidc_session_get_session_expires(r, session); json_object_set_new(json, OIDC_HOOK_INFO_SESSION_EXP, json_integer(apr_time_sec(session_expires))); } /* include the inactivity timeout in the session info */ if (apr_hash_get(oidc_cfg_info_hook_data_get(c), OIDC_HOOK_INFO_SESSION_TIMEOUT, APR_HASH_KEY_STRING)) { json_object_set_new(json, OIDC_HOOK_INFO_SESSION_TIMEOUT, json_integer(apr_time_sec(session->expiry))); } /* include the remote_user in the session info */ if (apr_hash_get(oidc_cfg_info_hook_data_get(c), OIDC_HOOK_INFO_SESSION_REMOTE_USER, APR_HASH_KEY_STRING)) { json_object_set_new(json, OIDC_HOOK_INFO_SESSION_REMOTE_USER, json_string(session->remote_user)); } if (apr_hash_get(oidc_cfg_info_hook_data_get(c), OIDC_HOOK_INFO_SESSION, APR_HASH_KEY_STRING)) { json_t *j_session = json_object(); json_object_set(j_session, OIDC_HOOK_INFO_SESSION_STATE, session->state); json_object_set_new(j_session, OIDC_HOOK_INFO_SESSION_UUID, json_string(session->uuid)); json_object_set_new(json, OIDC_HOOK_INFO_SESSION, j_session); } if (apr_hash_get(oidc_cfg_info_hook_data_get(c), OIDC_HOOK_INFO_REFRESH_TOKEN, APR_HASH_KEY_STRING)) { /* include the refresh token in the session info */ const char *refresh_token = oidc_session_get_refresh_token(r, session); if (refresh_token != NULL) json_object_set_new(json, OIDC_HOOK_INFO_REFRESH_TOKEN, json_string(refresh_token)); } /* pass the tokens to the application and save the session, possibly updating the expiry */ if (oidc_session_pass_tokens(r, c, session, b_extend_session, &needs_save) == FALSE) oidc_warn(r, "error passing tokens"); /* check if something was updated in the session and we need to save it again */ if (b_extend_session && needs_save) { if (oidc_session_save(r, session, FALSE) == FALSE) { oidc_warn(r, "error saving session"); rc = HTTP_INTERNAL_SERVER_ERROR; goto end; } } if (_oidc_strcmp(OIDC_HOOK_INFO_FORMAT_JSON, s_format) == 0) { /* JSON-encode the result */ r_value = oidc_util_encode_json(r->pool, json, JSON_PRESERVE_ORDER); /* return the stringified JSON result */ rc = oidc_util_http_send(r, r_value, _oidc_strlen(r_value), OIDC_HTTP_CONTENT_TYPE_JSON, OK); } else if (_oidc_strcmp(OIDC_HOOK_INFO_FORMAT_HTML, s_format) == 0) { /* JSON-encode the result */ r_value = oidc_util_encode_json(r->pool, json, JSON_PRESERVE_ORDER | JSON_INDENT(2)); rc = oidc_util_html_send(r, "Session Info", NULL, NULL, apr_psprintf(r->pool, "
%s
", r_value), OK); } end: /* free the allocated resources */ json_decref(json); return rc; } mod_auth_openidc-2.4.16.10/src/handle/jwks.c000066400000000000000000000060671476721736500205230ustar00rootroot00000000000000/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ /*************************************************************************** * Copyright (C) 2017-2025 ZmartZone Holding BV * All rights reserved. * * DISCLAIMER OF WARRANTIES: * * THE SOFTWARE PROVIDED HEREUNDER IS PROVIDED ON AN "AS IS" BASIS, WITHOUT * ANY WARRANTIES OR REPRESENTATIONS EXPRESS, IMPLIED OR STATUTORY; INCLUDING, * WITHOUT LIMITATION, WARRANTIES OF QUALITY, PERFORMANCE, NONINFRINGEMENT, * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. NOR ARE THERE ANY * WARRANTIES CREATED BY A COURSE OR DEALING, COURSE OF PERFORMANCE OR TRADE * USAGE. FURTHERMORE, THERE ARE NO WARRANTIES THAT THE SOFTWARE WILL MEET * YOUR NEEDS OR BE FREE FROM ERRORS, OR THAT THE OPERATION OF THE SOFTWARE * WILL BE UNINTERRUPTED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * @Author: Hans Zandbelt - hans.zandbelt@openidc.com */ #include "handle/handle.h" /* * handle request for JWKs */ int oidc_jwks_request(request_rec *r, oidc_cfg_t *c) { /* pickup requested JWKs type */ // char *jwks_type = NULL; // oidc_util_get_request_parameter(r, OIDC_REDIRECT_URI_REQUEST_JWKS, &jwks_type); char *jwks = apr_pstrdup(r->pool, "{ \"keys\" : ["); int i = 0; apr_byte_t first = TRUE; const oidc_jwk_t *jwk = NULL; oidc_jose_error_t err; char *s_json = NULL; /* loop over the RSA/EC public keys */ for (i = 0; oidc_cfg_public_keys_get(c) && i < oidc_cfg_public_keys_get(c)->nelts; i++) { jwk = APR_ARRAY_IDX(oidc_cfg_public_keys_get(c), i, oidc_jwk_t *); if (oidc_jwk_to_json(r->pool, jwk, &s_json, &err) == TRUE) { jwks = apr_psprintf(r->pool, "%s%s %s ", jwks, first ? "" : ",", s_json); first = FALSE; } else { oidc_error(r, "could not convert RSA/EC JWK to JSON using oidc_jwk_to_json: %s", oidc_jose_e2s(r->pool, err)); } } // TODO: send stuff if first == FALSE? jwks = apr_psprintf(r->pool, "%s ] }", jwks); return oidc_util_http_send(r, jwks, _oidc_strlen(jwks), OIDC_HTTP_CONTENT_TYPE_JSON, OK); } mod_auth_openidc-2.4.16.10/src/handle/logout.c000066400000000000000000000462061476721736500210550ustar00rootroot00000000000000/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ /*************************************************************************** * Copyright (C) 2017-2025 ZmartZone Holding BV * All rights reserved. * * DISCLAIMER OF WARRANTIES: * * THE SOFTWARE PROVIDED HEREUNDER IS PROVIDED ON AN "AS IS" BASIS, WITHOUT * ANY WARRANTIES OR REPRESENTATIONS EXPRESS, IMPLIED OR STATUTORY; INCLUDING, * WITHOUT LIMITATION, WARRANTIES OF QUALITY, PERFORMANCE, NONINFRINGEMENT, * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. NOR ARE THERE ANY * WARRANTIES CREATED BY A COURSE OR DEALING, COURSE OF PERFORMANCE OR TRADE * USAGE. FURTHERMORE, THERE ARE NO WARRANTIES THAT THE SOFTWARE WILL MEET * YOUR NEEDS OR BE FREE FROM ERRORS, OR THAT THE OPERATION OF THE SOFTWARE * WILL BE UNINTERRUPTED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * @Author: Hans Zandbelt - hans.zandbelt@openidc.com */ #include "cfg/dir.h" #include "handle/handle.h" #include "mod_auth_openidc.h" #include "proto/proto.h" #include "util.h" #define OIDC_DONT_REVOKE_TOKENS_BEFORE_LOGOUT_ENVVAR "OIDC_DONT_REVOKE_TOKENS_BEFORE_LOGOUT" /* * revoke refresh token and access token stored in the session if the * OP has an RFC 7009 compliant token revocation endpoint */ static void oidc_logout_revoke_tokens(request_rec *r, oidc_cfg_t *c, oidc_session_t *session) { char *response = NULL; char *basic_auth = NULL; char *bearer_auth = NULL; apr_table_t *params = NULL; const char *token = NULL; oidc_provider_t *provider = NULL; oidc_debug(r, "enter"); if (oidc_get_provider_from_session(r, c, session, &provider) == FALSE) goto out; if (apr_table_get(r->subprocess_env, OIDC_DONT_REVOKE_TOKENS_BEFORE_LOGOUT_ENVVAR) != NULL) goto out; oidc_debug(r, "revocation_endpoint=%s", oidc_cfg_provider_revocation_endpoint_url_get(provider) ? oidc_cfg_provider_revocation_endpoint_url_get(provider) : "(null)"); if ((oidc_cfg_provider_revocation_endpoint_url_get(provider) == NULL) || (_oidc_strcmp(oidc_cfg_provider_revocation_endpoint_url_get(provider), "") == 0)) goto out; params = apr_table_make(r->pool, 4); // add the token endpoint authentication credentials to the revocation endpoint call... if (oidc_proto_token_endpoint_auth( r, c, oidc_cfg_provider_token_endpoint_auth_get(provider), oidc_cfg_provider_client_id_get(provider), oidc_cfg_provider_client_secret_get(provider), oidc_cfg_provider_client_keys_get(provider), oidc_proto_profile_token_endpoint_auth_aud(provider), params, NULL, &basic_auth, &bearer_auth) == FALSE) goto out; token = oidc_session_get_refresh_token(r, session); if (token != NULL) { apr_table_setn(params, OIDC_PROTO_TOKEN_TYPE_HINT, OIDC_PROTO_REFRESH_TOKEN); apr_table_setn(params, OIDC_PROTO_TOKEN, token); if (oidc_http_post_form(r, oidc_cfg_provider_revocation_endpoint_url_get(provider), params, basic_auth, bearer_auth, NULL, oidc_cfg_provider_ssl_validate_server_get(provider), &response, NULL, NULL, oidc_cfg_http_timeout_long_get(c), oidc_cfg_outgoing_proxy_get(c), oidc_cfg_dir_pass_cookies_get(r), NULL, NULL, NULL) == FALSE) { oidc_warn(r, "revoking refresh token failed"); } apr_table_unset(params, OIDC_PROTO_TOKEN_TYPE_HINT); apr_table_unset(params, OIDC_PROTO_TOKEN); } token = oidc_session_get_access_token(r, session); if (token != NULL) { apr_table_setn(params, OIDC_PROTO_TOKEN_TYPE_HINT, OIDC_PROTO_ACCESS_TOKEN); apr_table_setn(params, OIDC_PROTO_TOKEN, token); if (oidc_http_post_form(r, oidc_cfg_provider_revocation_endpoint_url_get(provider), params, basic_auth, bearer_auth, NULL, oidc_cfg_provider_ssl_validate_server_get(provider), &response, NULL, NULL, oidc_cfg_http_timeout_long_get(c), oidc_cfg_outgoing_proxy_get(c), oidc_cfg_dir_pass_cookies_get(r), NULL, NULL, NULL) == FALSE) { oidc_warn(r, "revoking access token failed"); } } out: oidc_debug(r, "leave"); } static apr_byte_t oidc_logout_cleanup_by_sid(request_rec *r, char *sid, oidc_cfg_t *cfg, oidc_provider_t *provider, apr_byte_t revoke_tokens) { char *uuid = NULL; oidc_session_t session; oidc_debug(r, "enter (sid=%s,iss=%s)", sid, oidc_cfg_provider_issuer_get(provider)); // TODO: when dealing with sub instead of a true sid, we'll be killing all sessions for // a specific user, across hosts that share the *same* cache backend // if those hosts haven't been configured with a different OIDCCryptoPassphrase // - perhaps that's even acceptable since non-memory caching is encrypted by default // and memory-based caching doesn't suffer from this (different shm segments)? // - it will result in 400 errors returned from backchannel logout calls to the other hosts... sid = oidc_response_make_sid_iss_unique(r, sid, oidc_cfg_provider_issuer_get(provider)); oidc_cache_get_sid(r, sid, &uuid); if (uuid == NULL) { // this may happen when we are the caller oidc_warn( r, "could not (or no longer) find a session based on sid/sub provided in logout token / parameter: %s", sid); r->user = ""; return TRUE; } // revoke tokens if we can get a handle on those if (oidc_cfg_session_type_get(cfg) != OIDC_SESSION_TYPE_CLIENT_COOKIE) { if ((oidc_session_load_cache_by_uuid(r, cfg, uuid, &session) != FALSE) && (revoke_tokens == TRUE)) if (oidc_session_extract(r, &session) != FALSE) oidc_logout_revoke_tokens(r, cfg, &session); } // clear the session cache oidc_cache_set_sid(r, sid, NULL, 0); oidc_cache_set_session(r, uuid, NULL, 0); r->user = ""; return FALSE; } static apr_uint32_t oidc_logout_transparent_pixel[17] = { 0x474e5089, 0x0a1a0a0d, 0x0d000000, 0x52444849, 0x01000000, 0x01000000, 0x00000408, 0x0c1cb500, 0x00000002, 0x4144490b, 0x639c7854, 0x0000cffa, 0x02010702, 0x71311c9a, 0x00000000, 0x444e4549, 0x826042ae}; static apr_byte_t oidc_logout_is_front_channel(const char *logout_param_value) { return ((logout_param_value != NULL) && ((_oidc_strcmp(logout_param_value, OIDC_GET_STYLE_LOGOUT_PARAM_VALUE) == 0) || (_oidc_strcmp(logout_param_value, OIDC_IMG_STYLE_LOGOUT_PARAM_VALUE) == 0))); } static apr_byte_t oidc_logout_is_back_channel(const char *logout_param_value) { return ((logout_param_value != NULL) && (_oidc_strcmp(logout_param_value, OIDC_BACKCHANNEL_STYLE_LOGOUT_PARAM_VALUE) == 0)); } /* * handle a local logout */ int oidc_logout_request(request_rec *r, oidc_cfg_t *c, oidc_session_t *session, const char *url, apr_byte_t revoke_tokens) { int no_session_provided = 1; oidc_debug(r, "enter (url=%s)", url); /* if there's no remote_user then there's no (stored) session to kill */ if (session->remote_user != NULL) { no_session_provided = 0; if (revoke_tokens) oidc_logout_revoke_tokens(r, c, session); } /* * remove session state (cq. cache entry and cookie) * always clear the session cookie because the cookie may be not sent (but still in the browser) * due to SameSite policies */ oidc_session_kill(r, session); /* see if this is the OP calling us */ if (oidc_logout_is_front_channel(url)) { /* * If no session was provided look for the sid and iss parameters in * the request as specified in * "OpenID Connect Front-Channel Logout 1.0 - draft 05" at * https://openid.net/specs/openid-connect-frontchannel-1_0.html * and try to clear the session based on sid / iss like in the * backchannel logout case. */ if (no_session_provided) { char *sid, *iss; oidc_provider_t *provider = NULL; if (oidc_util_request_parameter_get(r, OIDC_REDIRECT_URI_REQUEST_SID, &sid) != FALSE) { if (oidc_util_request_parameter_get(r, OIDC_REDIRECT_URI_REQUEST_ISS, &iss) != FALSE) { provider = oidc_get_provider_for_issuer(r, c, iss, FALSE); } else { /* * Microsoft Entra ID / Azure AD seems to such a non spec compliant provider. * In this case try our luck with the static config if possible. */ oidc_debug(r, "OP did not provide an iss as parameter"); if (oidc_provider_static_config(r, c, &provider) == FALSE) provider = NULL; } if (provider) { oidc_logout_cleanup_by_sid(r, sid, c, provider, revoke_tokens); } else { oidc_info(r, "No provider for front channel logout found"); } } } /* set recommended cache control headers */ oidc_http_hdr_err_out_add(r, OIDC_HTTP_HDR_CACHE_CONTROL, "no-cache, no-store"); oidc_http_hdr_err_out_add(r, OIDC_HTTP_HDR_PRAGMA, "no-cache"); oidc_http_hdr_err_out_add(r, OIDC_HTTP_HDR_P3P, "CAO PSA OUR"); oidc_http_hdr_err_out_add(r, OIDC_HTTP_HDR_EXPIRES, "0"); oidc_http_hdr_err_out_add(r, OIDC_HTTP_HDR_X_FRAME_OPTIONS, oidc_cfg_logout_x_frame_options_get(c)); /* see if this is PF-PA style logout in which case we return a transparent pixel */ const char *accept = oidc_http_hdr_in_accept_get(r); if ((_oidc_strcmp(url, OIDC_IMG_STYLE_LOGOUT_PARAM_VALUE) == 0) || ((accept) && _oidc_strstr(accept, OIDC_HTTP_CONTENT_TYPE_IMAGE_PNG))) { return oidc_util_http_send(r, (const char *)&oidc_logout_transparent_pixel, sizeof(oidc_logout_transparent_pixel), OIDC_HTTP_CONTENT_TYPE_IMAGE_PNG, OK); } /* standard HTTP based logout: should be called in an iframe from the OP */ return oidc_util_html_send(r, "Logged Out", NULL, NULL, "

Logged Out

", OK); } oidc_http_hdr_err_out_add(r, OIDC_HTTP_HDR_CACHE_CONTROL, "no-cache, no-store"); oidc_http_hdr_err_out_add(r, OIDC_HTTP_HDR_PRAGMA, "no-cache"); /* see if we don't need to go somewhere special after killing the session locally */ if (url == NULL) return oidc_util_html_send(r, "Logged Out", NULL, NULL, "

Logged Out

", OK); /* send the user to the specified where-to-go-after-logout URL */ oidc_http_hdr_out_location_set(r, url); return HTTP_MOVED_TEMPORARILY; } /* * handle a backchannel logout */ #define OIDC_EVENTS_BLOGOUT_KEY "http://schemas.openid.net/event/backchannel-logout" static int oidc_logout_backchannel(request_rec *r, oidc_cfg_t *cfg) { oidc_debug(r, "enter"); const char *logout_token = NULL; oidc_jwt_t *jwt = NULL; oidc_jose_error_t err; oidc_jwk_t *jwk = NULL; oidc_provider_t *provider = NULL; char *sid = NULL; int rc = HTTP_BAD_REQUEST; apr_table_t *params = apr_table_make(r->pool, 8); if (oidc_util_read_post_params(r, params, FALSE, NULL) == FALSE) { oidc_error(r, "could not read POST-ed parameters to the logout endpoint"); goto out; } logout_token = apr_table_get(params, OIDC_PROTO_LOGOUT_TOKEN); if (logout_token == NULL) { oidc_error(r, "backchannel lggout endpoint was called but could not find a parameter named \"%s\"", OIDC_PROTO_LOGOUT_TOKEN); goto out; } // TODO: jwk symmetric key based on provider if (oidc_jwt_parse(r->pool, logout_token, &jwt, oidc_util_merge_symmetric_key(r->pool, oidc_cfg_private_keys_get(cfg), NULL), FALSE, &err) == FALSE) { oidc_error(r, "oidc_jwt_parse failed: %s", oidc_jose_e2s(r->pool, err)); goto out; } if ((jwt->header.alg == NULL) || (_oidc_strcmp(jwt->header.alg, "none") == 0)) { oidc_error(r, "logout token is not signed"); goto out; } provider = oidc_get_provider_for_issuer(r, cfg, jwt->payload.iss, FALSE); if (provider == NULL) { oidc_error(r, "no provider found for issuer: %s", jwt->payload.iss); goto out; } if ((oidc_cfg_provider_id_token_signed_response_alg_get(provider) != NULL) && (_oidc_strcmp(oidc_cfg_provider_id_token_signed_response_alg_get(provider), jwt->header.alg) != 0)) { oidc_error(r, "logout token is signed using wrong algorithm: %s != %s", jwt->header.alg, oidc_cfg_provider_id_token_signed_response_alg_get(provider)); goto out; } // TODO: destroy the JWK used for decryption jwk = NULL; if (oidc_util_create_symmetric_key(r, oidc_cfg_provider_client_secret_get(provider), 0, NULL, TRUE, &jwk) == FALSE) return FALSE; if (oidc_proto_jwt_verify( r, cfg, jwt, oidc_cfg_provider_jwks_uri_get(provider), oidc_cfg_provider_ssl_validate_server_get(provider), oidc_util_merge_symmetric_key(r->pool, oidc_cfg_provider_verify_public_keys_get(provider), jwk), oidc_cfg_provider_id_token_signed_response_alg_get(provider)) == FALSE) { oidc_error(r, "id_token signature could not be validated, aborting"); goto out; } if (oidc_proto_jwt_validate( r, jwt, oidc_cfg_provider_validate_issuer_get(provider) ? oidc_cfg_provider_issuer_get(provider) : NULL, FALSE, FALSE, oidc_cfg_provider_idtoken_iat_slack_get(provider)) == FALSE) goto out; /* verify the "aud" and "azp" values */ if (oidc_proto_idtoken_validate_aud_and_azp(r, cfg, provider, &jwt->payload) == FALSE) goto out; json_t *events = json_object_get(jwt->payload.value.json, OIDC_CLAIM_EVENTS); if (events == NULL) { oidc_error(r, "\"%s\" claim could not be found in logout token", OIDC_CLAIM_EVENTS); goto out; } json_t *blogout = json_object_get(events, OIDC_EVENTS_BLOGOUT_KEY); if (!json_is_object(blogout)) { oidc_error(r, "\"%s\" object could not be found in \"%s\" claim", OIDC_EVENTS_BLOGOUT_KEY, OIDC_CLAIM_EVENTS); goto out; } char *nonce = NULL; oidc_util_json_object_get_string(r->pool, jwt->payload.value.json, OIDC_CLAIM_NONCE, &nonce, NULL); if (nonce != NULL) { oidc_error(r, "rejecting logout request/token since it contains a \"%s\" claim", OIDC_CLAIM_NONCE); goto out; } char *jti = NULL; oidc_util_json_object_get_string(r->pool, jwt->payload.value.json, OIDC_CLAIM_JTI, &jti, NULL); if (jti != NULL) { char *replay = NULL; oidc_cache_get_jti(r, jti, &replay); if (replay != NULL) { oidc_error(r, "the \"%s\" value (%s) passed in logout token was found in the cache already; " "possible replay attack!?", OIDC_CLAIM_JTI, jti); goto out; } } /* jti cache duration is the configured replay prevention window for token issuance plus 10 seconds for safety */ apr_time_t jti_cache_duration = apr_time_from_sec(oidc_cfg_provider_idtoken_iat_slack_get(provider) * 2 + 10); /* store it in the cache for the calculated duration */ oidc_cache_set_jti(r, jti, jti, apr_time_now() + jti_cache_duration); oidc_util_json_object_get_string(r->pool, jwt->payload.value.json, OIDC_CLAIM_EVENTS, &sid, NULL); // TODO: by-spec we should cater for the fact that "sid" has been provided // in the id_token returned in the authentication request, but "sub" // is used in the logout token but that requires a 2nd entry in the // cache and a separate session "sub" member, ugh; we'll just assume // that is "sid" is specified in the id_token, the OP will actually use // this for logout // (and probably call us multiple times or the same sub if needed) oidc_util_json_object_get_string(r->pool, jwt->payload.value.json, OIDC_CLAIM_SID, &sid, NULL); if (sid == NULL) sid = jwt->payload.sub; if (sid == NULL) { oidc_error(r, "no \"sub\" and no \"sid\" claim found in logout token"); goto out; } // a backchannel logout comes from the provider, so no need to revoke the tokens oidc_logout_cleanup_by_sid(r, sid, cfg, provider, FALSE); rc = OK; out: if (jwk != NULL) { oidc_jwk_destroy(jwk); jwk = NULL; } if (jwt != NULL) { oidc_jwt_destroy(jwt); jwt = NULL; } oidc_http_hdr_err_out_add(r, OIDC_HTTP_HDR_CACHE_CONTROL, "no-cache, no-store"); oidc_http_hdr_err_out_add(r, OIDC_HTTP_HDR_PRAGMA, "no-cache"); return rc; } #define OIDC_REFRESH_TOKENS_BEFORE_LOGOUT_ENVVAR "OIDC_REFRESH_TOKENS_BEFORE_LOGOUT" /* * perform (single) logout */ int oidc_logout(request_rec *r, oidc_cfg_t *c, oidc_session_t *session) { oidc_provider_t *provider = NULL; /* pickup the command or URL where the user wants to go after logout */ char *url = NULL; char *error_str = NULL; char *error_description = NULL; char *id_token_hint = NULL; char *s_logout_request = NULL; oidc_util_request_parameter_get(r, OIDC_REDIRECT_URI_REQUEST_LOGOUT, &url); oidc_debug(r, "enter (url=%s)", url); if (oidc_logout_is_front_channel(url)) { return oidc_logout_request(r, c, session, url, TRUE); } else if (oidc_logout_is_back_channel(url)) { return oidc_logout_backchannel(r, c); } if ((url == NULL) || (_oidc_strcmp(url, "") == 0)) { url = apr_pstrdup(r->pool, oidc_util_absolute_url(r, c, oidc_cfg_default_slo_url_get(c))); } else { /* do input validation on the logout parameter value */ if (oidc_validate_redirect_url(r, c, url, TRUE, &error_str, &error_description) == FALSE) { return oidc_util_html_send_error(r, error_str, error_description, HTTP_BAD_REQUEST); } } if (oidc_get_provider_from_session(r, c, session, &provider) == FALSE) oidc_warn(r, "oidc_get_provider_from_session failed"); if ((provider != NULL) && (oidc_cfg_provider_end_session_endpoint_get(provider) != NULL)) { if (apr_table_get(r->subprocess_env, OIDC_REFRESH_TOKENS_BEFORE_LOGOUT_ENVVAR) != NULL) { if (oidc_refresh_token_grant(r, c, session, provider, NULL, NULL, &id_token_hint) == FALSE) oidc_warn(r, "id_token_hint could not be refreshed before logout"); } else { id_token_hint = apr_pstrdup(r->pool, oidc_session_get_idtoken(r, session)); } s_logout_request = apr_pstrdup(r->pool, oidc_cfg_provider_end_session_endpoint_get(provider)); if (id_token_hint != NULL) { s_logout_request = apr_psprintf( r->pool, "%s%s" OIDC_PROTO_ID_TOKEN_HINT "=%s", s_logout_request, strchr(s_logout_request ? s_logout_request : "", OIDC_CHAR_QUERY) != NULL ? OIDC_STR_AMP : OIDC_STR_QUERY, oidc_http_url_encode(r, id_token_hint)); } if (url != NULL) { s_logout_request = apr_psprintf( r->pool, "%s%spost_logout_redirect_uri=%s", s_logout_request, strchr(s_logout_request ? s_logout_request : "", OIDC_CHAR_QUERY) != NULL ? OIDC_STR_AMP : OIDC_STR_QUERY, oidc_http_url_encode(r, url)); } if (oidc_cfg_provider_logout_request_params_get(provider) != NULL) { s_logout_request = apr_psprintf( r->pool, "%s%s%s", s_logout_request, strchr(s_logout_request ? s_logout_request : "", OIDC_CHAR_QUERY) != NULL ? OIDC_STR_AMP : OIDC_STR_QUERY, oidc_cfg_provider_logout_request_params_get(provider)); } // char *state = NULL; // oidc_proto_generate_nonce(r, &state, 8); // url = apr_psprintf(r->pool, "%s&state=%s", logout_request, state); url = s_logout_request; } return oidc_logout_request(r, c, session, url, TRUE); } mod_auth_openidc-2.4.16.10/src/handle/refresh.c000066400000000000000000000375121476721736500212020ustar00rootroot00000000000000/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ /*************************************************************************** * Copyright (C) 2017-2025 ZmartZone Holding BV * All rights reserved. * * DISCLAIMER OF WARRANTIES: * * THE SOFTWARE PROVIDED HEREUNDER IS PROVIDED ON AN "AS IS" BASIS, WITHOUT * ANY WARRANTIES OR REPRESENTATIONS EXPRESS, IMPLIED OR STATUTORY; INCLUDING, * WITHOUT LIMITATION, WARRANTIES OF QUALITY, PERFORMANCE, NONINFRINGEMENT, * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. NOR ARE THERE ANY * WARRANTIES CREATED BY A COURSE OR DEALING, COURSE OF PERFORMANCE OR TRADE * USAGE. FURTHERMORE, THERE ARE NO WARRANTIES THAT THE SOFTWARE WILL MEET * YOUR NEEDS OR BE FREE FROM ERRORS, OR THAT THE OPERATION OF THE SOFTWARE * WILL BE UNINTERRUPTED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * @Author: Hans Zandbelt - hans.zandbelt@openidc.com */ #include "handle/handle.h" #include "metrics.h" #include "mod_auth_openidc.h" #include "proto/proto.h" #include "session.h" #include "util.h" /* JSON object key for the value that holds the refresh token's refresh timestamp */ #define OIDC_REFRESH_TIMESTAMP "ts" /* * time-to-live (seconds) for the lock that prevents parallel callers to execute * a refresh grant for the same refresh token; this also presents the maximum time * that callers will be blocked, waiting for another process to finish the refresh * and populate the cache with the results */ #define OIDC_REFRESH_LOCK_TTL 5 /* * time-to-live (seconds) for the refresh token cache results * during that time other callers trying to execute a refresh grant with the same * refresh token will obtain their results from the cache rather than an actual refresh * request */ #define OIDC_REFRESH_CACHE_TTL 30 /* needs to be larger than a few characters for cache compression to work... */ #define OIDC_REFRESH_LOCK_VALUE "needstobelargerthanafewcharacters" /* * cache refresh token grant results for a while to avoid (almost) parallel requests */ static void oidc_refresh_token_cache_set(request_rec *r, oidc_cfg_t *c, const char *refresh_token, const char *s_access_token, const char *s_token_type, int expires_in, const char *s_id_token, const char *s_refresh_token, apr_time_t *ts) { char *s_json = NULL; /* create the JSON representation of the refresh grant results + timestamp */ json_t *json = json_object(); if (s_access_token) json_object_set_new(json, OIDC_PROTO_ACCESS_TOKEN, json_string(s_access_token)); if (s_token_type) json_object_set_new(json, OIDC_PROTO_TOKEN_TYPE, json_string(s_token_type)); json_object_set_new(json, OIDC_PROTO_EXPIRES_IN, json_integer(expires_in)); if (s_id_token) json_object_set_new(json, OIDC_PROTO_ID_TOKEN, json_string(s_id_token)); if (s_refresh_token) json_object_set_new(json, OIDC_PROTO_REFRESH_TOKEN, json_string(s_refresh_token)); *ts = apr_time_now(); json_object_set_new(json, OIDC_REFRESH_TIMESTAMP, json_integer(apr_time_sec(*ts))); /* stringify the JSON object and store it in the cache */ s_json = oidc_util_encode_json(r->pool, json, JSON_COMPACT); oidc_debug(r, "caching refresh_token (%s) grant results for %d seconds: %s", refresh_token, OIDC_REFRESH_CACHE_TTL, s_json); oidc_cache_set_refresh_token(r, refresh_token, s_json, apr_time_now() + apr_time_from_sec(OIDC_REFRESH_CACHE_TTL)); /* cleanup */ json_decref(json); } /* * obtain recent refresh token grant results from the cache */ static apr_byte_t oidc_refresh_token_cache_get(request_rec *r, oidc_cfg_t *c, const char *refresh_token, char **s_access_token, char **s_token_type, int *expires_in, char **s_id_token, char **s_refresh_token, apr_time_t *ts) { char *s_json = NULL; json_t *json = NULL, *v = NULL; apr_byte_t rv = FALSE; oidc_cache_mutex_lock(r->pool, r->server, oidc_cfg_refresh_mutex_get(c)); /* see if this token was already refreshed recently or is being refreshed */ oidc_cache_get_refresh_token(r, refresh_token, &s_json); if (s_json == NULL) goto no_cache_found; /* wait for the "other" caller to populate the refresh token response cache results */ while ((s_json != NULL) && (_oidc_strcmp(s_json, OIDC_REFRESH_LOCK_VALUE) == 0)) { oidc_warn(r, "existing refresh in progress for %s, back off for 0.5s before re-trying the cache", refresh_token); apr_sleep(apr_time_from_msec(500)); s_json = NULL; oidc_cache_get_refresh_token(r, refresh_token, &s_json); } /* check if we have run into a timeout */ if ((s_json == NULL) || (_oidc_strcmp(s_json, OIDC_REFRESH_LOCK_VALUE) == 0)) { oidc_warn(r, "timeout waiting for refresh grant cache results"); // TODO: now we are going to refresh ourselves with a refresh token that has already been // tried before; that is not great in rolling refresh token setups but I guess we have no // other choice anyhow... goto no_cache_found; } /* we should have valid cache results by now */ if (oidc_util_decode_json_object(r, s_json, &json) == FALSE) goto no_cache_found; oidc_debug(r, "using cached refresh_token (%s) grant results: %s", refresh_token, s_json); /* parse the results from the cache into the output parameters */ if ((v = json_object_get(json, OIDC_PROTO_ACCESS_TOKEN))) *s_access_token = apr_pstrdup(r->pool, json_string_value(v)); if ((v = json_object_get(json, OIDC_PROTO_TOKEN_TYPE))) *s_token_type = apr_pstrdup(r->pool, json_string_value(v)); if ((v = json_object_get(json, OIDC_PROTO_EXPIRES_IN))) *expires_in = json_integer_value(v); if ((v = json_object_get(json, OIDC_PROTO_ID_TOKEN))) *s_id_token = apr_pstrdup(r->pool, json_string_value(v)); if ((v = json_object_get(json, OIDC_PROTO_REFRESH_TOKEN))) *s_refresh_token = apr_pstrdup(r->pool, json_string_value(v)); if ((v = json_object_get(json, OIDC_REFRESH_TIMESTAMP))) *ts = apr_time_from_sec(json_integer_value(v)); /* cleanup */ json_decref(json); rv = TRUE; goto end; no_cache_found: oidc_debug(r, "locking cache and refreshing %s...", refresh_token); /* * best-effort distributed locking during our upcoming refresh grant execution * * note that a small chance/race-condition remains that in a parallel request on * another server in the same cluster another process just did the same in between * i.e. calling oidc_cache_get_refresh_token (on entry) and calling * oidc_cache_set_refresh_token (on exit) hereafter * * a process lock (refresh_mutex) in the calling function prevents this at least on the same machine */ oidc_cache_set_refresh_token(r, refresh_token, OIDC_REFRESH_LOCK_VALUE, apr_time_now() + apr_time_from_sec(OIDC_REFRESH_LOCK_TTL)); end: oidc_cache_mutex_unlock(r->pool, r->server, oidc_cfg_refresh_mutex_get(c)); return rv; } /* * execute refresh token grant to refresh the existing access token */ apr_byte_t oidc_refresh_token_grant(request_rec *r, oidc_cfg_t *c, oidc_session_t *session, oidc_provider_t *provider, char **new_access_token, char **new_access_token_type, char **new_id_token) { apr_byte_t rc = FALSE; char *s_id_token = NULL; int expires_in = -1; char *s_token_type = NULL; char *s_access_token = NULL; char *s_refresh_token = NULL; oidc_jwt_t *id_token_jwt = NULL; oidc_jose_error_t err; const char *refresh_token = NULL; apr_time_t ts = 0; oidc_debug(r, "enter"); /* get the refresh token that was stored in the session */ refresh_token = oidc_session_get_refresh_token(r, session); if (refresh_token == NULL) { oidc_warn(r, "refresh token routine called but no refresh_token found in the session"); goto end; } /* see if it was refreshed very recently and we can re-use the results from the cache */ if (oidc_refresh_token_cache_get(r, c, refresh_token, &s_access_token, &s_token_type, &expires_in, &s_id_token, &s_refresh_token, &ts) == TRUE) goto process; oidc_debug(r, "refreshing refresh_token: %s", refresh_token); OIDC_METRICS_TIMING_START(r, c); /* refresh the tokens by calling the token endpoint */ if (oidc_proto_token_refresh_request(r, c, provider, refresh_token, &s_id_token, &s_access_token, &s_token_type, &expires_in, &s_refresh_token) == FALSE) { OIDC_METRICS_COUNTER_INC(r, c, OM_PROVIDER_REFRESH_ERROR); oidc_error(r, "access_token could not be refreshed with refresh_token: %s", refresh_token); goto end; } OIDC_METRICS_TIMING_ADD(r, c, OM_PROVIDER_REFRESH); /* cache the results for other callers */ oidc_refresh_token_cache_set(r, c, refresh_token, s_access_token, s_token_type, expires_in, s_id_token, s_refresh_token, &ts); process: /* store the new access_token in the session and discard the old one */ oidc_session_set_access_token(r, session, s_access_token); oidc_session_set_access_token_type(r, session, s_token_type); oidc_session_set_access_token_expires(r, session, expires_in); /* reset the access token refresh timestamp */ oidc_session_set_access_token_last_refresh(r, session, ts); /* see if we need to return it as a parameter */ if (new_access_token != NULL) *new_access_token = s_access_token; if (new_access_token_type != NULL) *new_access_token_type = s_token_type; /* if we have a new refresh token (rolling refresh), store it in the session and overwrite the old one */ if (s_refresh_token != NULL) oidc_session_set_refresh_token(r, session, s_refresh_token); /* if we have a new id_token, store it in the session and update the session max lifetime if required */ if (s_id_token != NULL) { /* only store the serialized representation when configured so */ if (oidc_cfg_store_id_token_get(c)) oidc_session_set_idtoken(r, session, s_id_token); if (oidc_jwt_parse(r->pool, s_id_token, &id_token_jwt, NULL, FALSE, &err) == TRUE) { /* store the claims payload in the id_token for later reference */ oidc_session_set_idtoken_claims(r, session, id_token_jwt->payload.value.str); if (oidc_cfg_provider_session_max_duration_get(provider) == 0) { /* update the session expiry to match the expiry of the id_token */ apr_time_t session_expires = apr_time_from_sec(id_token_jwt->payload.exp); oidc_session_set_session_expires(r, session, session_expires); /* log message about the updated max session duration */ oidc_log_session_expires(r, "session max lifetime", session_expires); } /* see if we need to return it as a parameter */ if (new_id_token != NULL) *new_id_token = s_id_token; } else { oidc_warn(r, "parsing of id_token failed"); } if (id_token_jwt != NULL) oidc_jwt_destroy(id_token_jwt); } oidc_debug(r, "replaced refresh_token: %s with %s", refresh_token, s_refresh_token); rc = TRUE; end: return rc; } /* * handle refresh token request */ int oidc_refresh_token_request(request_rec *r, oidc_cfg_t *c, oidc_session_t *session) { char *return_to = NULL; char *r_access_token = NULL; char *error_code = NULL; char *error_str = NULL; char *error_description = NULL; apr_byte_t needs_save = TRUE; /* get the command passed to the session management handler */ oidc_util_request_parameter_get(r, OIDC_REDIRECT_URI_REQUEST_REFRESH, &return_to); oidc_util_request_parameter_get(r, OIDC_PROTO_ACCESS_TOKEN, &r_access_token); /* check the input parameters */ if (return_to == NULL) { oidc_error(r, "refresh token request handler called with no URL to return to"); return HTTP_INTERNAL_SERVER_ERROR; } /* do input validation on the return to parameter value */ if (oidc_validate_redirect_url(r, c, return_to, TRUE, &error_str, &error_description) == FALSE) { oidc_error(r, "return_to URL validation failed: %s: %s", error_str, error_description); return HTTP_INTERNAL_SERVER_ERROR; } if (r_access_token == NULL) { oidc_error(r, "refresh token request handler called with no access_token parameter"); error_code = "no_access_token"; goto end; } const char *s_access_token = oidc_session_get_access_token(r, session); if (s_access_token == NULL) { oidc_error(r, "no existing access_token found in the session, nothing to refresh"); error_code = "no_access_token_exists"; goto end; } /* compare the access_token parameter used for XSRF protection */ if (_oidc_strcmp(s_access_token, r_access_token) != 0) { oidc_error(r, "access_token passed in refresh request does not match the one stored in the session"); error_code = "no_access_token_match"; goto end; } /* get a handle to the provider configuration */ oidc_provider_t *provider = NULL; if (oidc_get_provider_from_session(r, c, session, &provider) == FALSE) { error_code = "session_corruption"; goto end; } /* execute the actual refresh grant */ if (oidc_refresh_token_grant(r, c, session, provider, NULL, NULL, NULL) == FALSE) { oidc_error(r, "access_token could not be refreshed"); error_code = "refresh_failed"; goto end; } /* pass the tokens to the application, possibly updating the expiry */ if (oidc_session_pass_tokens(r, c, session, TRUE, &needs_save) == FALSE) { error_code = "session_corruption"; goto end; } if (oidc_session_save(r, session, FALSE) == FALSE) { error_code = "error saving session"; goto end; } end: /* pass optional error message to the return URL */ if (error_code != NULL) return_to = apr_psprintf(r->pool, "%s%serror_code=%s", return_to, strchr(return_to, OIDC_CHAR_QUERY) ? OIDC_STR_AMP : OIDC_STR_QUERY, oidc_http_url_encode(r, error_code)); /* add the redirect location header */ oidc_http_hdr_out_location_set(r, return_to); return HTTP_MOVED_TEMPORARILY; } apr_byte_t oidc_refresh_access_token_before_expiry(request_rec *r, oidc_cfg_t *cfg, oidc_session_t *session, int ttl_minimum, apr_byte_t *needs_save) { apr_time_t t_expires = -1; oidc_provider_t *provider = NULL; oidc_debug(r, "ttl_minimum=%d", ttl_minimum); if (ttl_minimum < 0) return TRUE; t_expires = oidc_session_get_access_token_expires(r, session); if (t_expires <= 0) { oidc_debug(r, "no access token expires_in stored in the session (i.e. returned from in the " "authorization response), so cannot refresh the access token based on TTL requirement"); return FALSE; } // NB: this fails early: when no refresh token was returned, an error will be returned on // the first authenticated request, unrelated to the actual access token expiry timestamp if (oidc_session_get_refresh_token(r, session) == NULL) { oidc_debug(r, "no refresh token stored in the session, so cannot refresh the access token based on TTL " "requirement"); return FALSE; } t_expires = t_expires - apr_time_from_sec(ttl_minimum); oidc_debug(r, "refresh needed in: %" APR_TIME_T_FMT " seconds", apr_time_sec(t_expires - apr_time_now())); if (t_expires > apr_time_now()) return TRUE; if (oidc_get_provider_from_session(r, cfg, session, &provider) == FALSE) return FALSE; if (oidc_refresh_token_grant(r, cfg, session, provider, NULL, NULL, NULL) == FALSE) { oidc_warn(r, "access_token could not be refreshed"); *needs_save = FALSE; return FALSE; } *needs_save = TRUE; return TRUE; } mod_auth_openidc-2.4.16.10/src/handle/request.c000066400000000000000000000242151476721736500212300ustar00rootroot00000000000000/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ /*************************************************************************** * Copyright (C) 2017-2025 ZmartZone Holding BV * All rights reserved. * * DISCLAIMER OF WARRANTIES: * * THE SOFTWARE PROVIDED HEREUNDER IS PROVIDED ON AN "AS IS" BASIS, WITHOUT * ANY WARRANTIES OR REPRESENTATIONS EXPRESS, IMPLIED OR STATUTORY; INCLUDING, * WITHOUT LIMITATION, WARRANTIES OF QUALITY, PERFORMANCE, NONINFRINGEMENT, * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. NOR ARE THERE ANY * WARRANTIES CREATED BY A COURSE OR DEALING, COURSE OF PERFORMANCE OR TRADE * USAGE. FURTHERMORE, THERE ARE NO WARRANTIES THAT THE SOFTWARE WILL MEET * YOUR NEEDS OR BE FREE FROM ERRORS, OR THAT THE OPERATION OF THE SOFTWARE * WILL BE UNINTERRUPTED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * @Author: Hans Zandbelt - hans.zandbelt@openidc.com */ #include "handle/handle.h" #include "metrics.h" #include "mod_auth_openidc.h" #include "proto/proto.h" #include "state.h" #include "util.h" apr_byte_t oidc_request_check_cookie_domain(request_rec *r, oidc_cfg_t *c, const char *original_url) { /* * printout errors if Cookie settings are not going to work */ apr_uri_t o_uri; _oidc_memset(&o_uri, 0, sizeof(apr_uri_t)); apr_uri_t r_uri; _oidc_memset(&r_uri, 0, sizeof(apr_uri_t)); apr_uri_parse(r->pool, original_url, &o_uri); apr_uri_parse(r->pool, oidc_util_redirect_uri(r, c), &r_uri); if ((_oidc_strnatcasecmp(o_uri.scheme, r_uri.scheme) != 0) && (_oidc_strnatcasecmp(r_uri.scheme, "https") == 0)) { oidc_error(r, "the URL scheme (%s) of the configured " OIDCRedirectURI " does not match the URL scheme of the URL being accessed (%s): the \"state\" and " "\"session\" cookies will not be shared between the two!", r_uri.scheme, o_uri.scheme); return FALSE; } if (oidc_cfg_cookie_domain_get(c) == NULL) { if (_oidc_strnatcasecmp(o_uri.hostname, r_uri.hostname) != 0) { const char *p = oidc_util_strcasestr(o_uri.hostname, r_uri.hostname); if ((p == NULL) || (_oidc_strnatcasecmp(r_uri.hostname, p) != 0)) { oidc_error(r, "the URL hostname (%s) of the configured " OIDCRedirectURI " does not match the URL hostname of the URL being accessed (%s): the " "\"state\" and \"session\" cookies will not be shared between the two!", r_uri.hostname, o_uri.hostname); OIDC_METRICS_COUNTER_INC(r, c, OM_AUTHN_REQUEST_ERROR_URL); return FALSE; } } } else { if (!oidc_util_cookie_domain_valid(o_uri.hostname, oidc_cfg_cookie_domain_get(c))) { oidc_error(r, "the domain (%s) configured in " OIDCCookieDomain " does not match the URL hostname (%s) of the URL being accessed (%s): setting " "\"state\" and \"session\" cookies will not work!!", oidc_cfg_cookie_domain_get(c), o_uri.hostname, original_url); OIDC_METRICS_COUNTER_INC(r, c, OM_AUTHN_REQUEST_ERROR_URL); return FALSE; } } return TRUE; } static const char *oidc_request_samesite_cookie(request_rec *r, struct oidc_cfg_t *c) { const char *rv = NULL; switch (oidc_cfg_cookie_same_site_get(c)) { case OIDC_SAMESITE_COOKIE_STRICT: case OIDC_SAMESITE_COOKIE_LAX: rv = OIDC_HTTP_COOKIE_SAMESITE_LAX; break; case OIDC_SAMESITE_COOKIE_NONE: rv = OIDC_HTTP_COOKIE_SAMESITE_NONE(c, r); break; case OIDC_SAMESITE_COOKIE_DISABLED: default: break; } return rv; } /* * set the state that is maintained between an authorization request and an authorization response * in a cookie in the browser that is cryptographically bound to that state */ static int oidc_request_authorization_set_cookie(request_rec *r, oidc_cfg_t *c, const char *state, oidc_proto_state_t *proto_state) { /* * create a cookie consisting of 8 elements: * random value, original URL, original method, issuer, response_type, response_mod, prompt and timestamp * encoded as JSON, encrypting the resulting JSON value */ char *cookieValue = oidc_proto_state_to_cookie(r, c, proto_state); if (cookieValue == NULL) return HTTP_INTERNAL_SERVER_ERROR; /* * clean expired state cookies to avoid pollution and optionally * try to avoid the number of state cookies exceeding a max */ int number_of_cookies = oidc_state_cookies_clean_expired(r, c, NULL, oidc_cfg_delete_oldest_state_cookies_get(c)); int max_number_of_cookies = oidc_cfg_max_number_of_state_cookies_get(c); if ((max_number_of_cookies > 0) && (number_of_cookies >= max_number_of_cookies)) { oidc_warn(r, "the number of existing, valid state cookies (%d) has exceeded the limit (%d), no additional " "authorization request + state cookie can be generated, aborting the request", number_of_cookies, max_number_of_cookies); return HTTP_SERVICE_UNAVAILABLE; } /* assemble the cookie name for the state cookie */ const char *cookieName = oidc_state_cookie_name(r, state); /* set it as a cookie */ oidc_http_set_cookie(r, cookieName, cookieValue, -1, oidc_request_samesite_cookie(r, c)); return OK; } /* * authenticate the user to the selected OP, if the OP is not selected yet perform discovery first */ int oidc_request_authenticate_user(request_rec *r, oidc_cfg_t *c, oidc_provider_t *provider, const char *original_url, const char *login_hint, const char *id_token_hint, const char *prompt, const char *auth_request_params, const char *path_scope) { int rc; OIDC_METRICS_TIMING_START(r, c); oidc_debug(r, "enter"); if (provider == NULL) { // TODO: should we use an explicit redirect to the discovery endpoint (maybe a "discovery" param to the // redirect_uri)? if (oidc_cfg_metadata_dir_get(c) != NULL) { /* * No authentication done but request not allowed without authentication * by setting r->user */ oidc_request_state_set(r, OIDC_REQUEST_STATE_KEY_DISCOVERY, ""); oidc_debug(r, "defer discovery to the content handler, setting r->user=\"\""); r->user = ""; return OK; } /* we're not using multiple OP's configured in a metadata directory, pick the statically configured OP */ if (oidc_provider_static_config(r, c, &provider) == FALSE) { OIDC_METRICS_COUNTER_INC(r, c, OM_AUTHN_RESPONSE_ERROR_PROVIDER); return HTTP_INTERNAL_SERVER_ERROR; } } /* generate the random nonce value that correlates requests and responses */ char *nonce = NULL; if (oidc_proto_generate_nonce(r, &nonce, OIDC_PROTO_NONCE_LENGTH) == FALSE) return HTTP_INTERNAL_SERVER_ERROR; char *pkce_state = NULL; char *code_challenge = NULL; if ((oidc_util_spaced_string_contains(r->pool, oidc_cfg_provider_response_type_get(provider), OIDC_PROTO_CODE) == TRUE) && (oidc_proto_profile_pkce_get(provider) != &oidc_pkce_none)) { /* generate the code verifier value that correlates authorization requests and code exchange requests */ if (oidc_proto_profile_pkce_get(provider)->state(r, &pkce_state) == FALSE) return HTTP_INTERNAL_SERVER_ERROR; /* generate the PKCE code challenge */ if (oidc_proto_profile_pkce_get(provider)->challenge(r, pkce_state, &code_challenge) == FALSE) return HTTP_INTERNAL_SERVER_ERROR; } /* create the state between request/response */ oidc_proto_state_t *proto_state = oidc_proto_state_new(); oidc_proto_state_set_original_url(proto_state, original_url); if (oidc_proto_state_get_original_url(proto_state) == NULL) { oidc_error( r, "could not store the current URL in the state: most probably you need to ensure that it does " "not contain unencoded Unicode characters e.g. by forcing IE 11 to encode all URL characters"); return HTTP_INTERNAL_SERVER_ERROR; } oidc_proto_state_set_original_method(proto_state, oidc_original_request_method(r, c, TRUE)); oidc_proto_state_set_issuer(proto_state, oidc_cfg_provider_issuer_get(provider)); oidc_proto_state_set_response_type(proto_state, oidc_cfg_provider_response_type_get(provider)); oidc_proto_state_set_nonce(proto_state, nonce); oidc_proto_state_set_timestamp_now(proto_state); if (oidc_cfg_provider_response_mode_get(provider)) oidc_proto_state_set_response_mode(proto_state, oidc_cfg_provider_response_mode_get(provider)); if (prompt) oidc_proto_state_set_prompt(proto_state, prompt); if (pkce_state) oidc_proto_state_set_pkce_state(proto_state, pkce_state); /* get a hash value that fingerprints the browser concatenated with the random input */ const char *state = oidc_state_browser_fingerprint(r, c, nonce); /* * create state that restores the context when the authorization response comes in * and cryptographically bind it to the browser */ rc = oidc_request_authorization_set_cookie(r, c, state, proto_state); if (rc != OK) { oidc_proto_state_destroy(proto_state); return rc; } if (oidc_request_check_cookie_domain(r, c, original_url) == FALSE) { oidc_proto_state_destroy(proto_state); return HTTP_INTERNAL_SERVER_ERROR; } /* send off to the OpenID Connect Provider */ // TODO: maybe show intermediate/progress screen "redirecting to" rc = oidc_proto_request_auth(r, provider, login_hint, oidc_util_redirect_uri(r, c), state, proto_state, id_token_hint, code_challenge, auth_request_params, path_scope); OIDC_METRICS_TIMING_ADD(r, c, OM_AUTHN_REQUEST); return rc; } mod_auth_openidc-2.4.16.10/src/handle/request_uri.c000066400000000000000000000055111476721736500221050ustar00rootroot00000000000000/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ /*************************************************************************** * Copyright (C) 2017-2025 ZmartZone Holding BV * All rights reserved. * * DISCLAIMER OF WARRANTIES: * * THE SOFTWARE PROVIDED HEREUNDER IS PROVIDED ON AN "AS IS" BASIS, WITHOUT * ANY WARRANTIES OR REPRESENTATIONS EXPRESS, IMPLIED OR STATUTORY; INCLUDING, * WITHOUT LIMITATION, WARRANTIES OF QUALITY, PERFORMANCE, NONINFRINGEMENT, * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. NOR ARE THERE ANY * WARRANTIES CREATED BY A COURSE OR DEALING, COURSE OF PERFORMANCE OR TRADE * USAGE. FURTHERMORE, THERE ARE NO WARRANTIES THAT THE SOFTWARE WILL MEET * YOUR NEEDS OR BE FREE FROM ERRORS, OR THAT THE OPERATION OF THE SOFTWARE * WILL BE UNINTERRUPTED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * @Author: Hans Zandbelt - hans.zandbelt@openidc.com */ #include "handle/handle.h" #include "metadata.h" #include "mod_auth_openidc.h" #include "proto/proto.h" #include "util.h" /* * handle request object by reference request */ int oidc_request_uri(request_rec *r, oidc_cfg_t *c) { char *request_ref = NULL; oidc_util_request_parameter_get(r, OIDC_REDIRECT_URI_REQUEST_REQUEST_URI, &request_ref); if (request_ref == NULL) { oidc_error(r, "no \"%s\" parameter found", OIDC_REDIRECT_URI_REQUEST_REQUEST_URI); return HTTP_BAD_REQUEST; } char *jwt = NULL; oidc_cache_get_request_uri(r, request_ref, &jwt); if (jwt == NULL) { oidc_error(r, "no cached JWT found for %s reference: %s", OIDC_REDIRECT_URI_REQUEST_REQUEST_URI, request_ref); return HTTP_NOT_FOUND; } oidc_cache_set_request_uri(r, request_ref, NULL, 0); return oidc_util_http_send(r, jwt, _oidc_strlen(jwt), OIDC_HTTP_CONTENT_TYPE_JWT, OK); } mod_auth_openidc-2.4.16.10/src/handle/response.c000066400000000000000000000733451476721736500214060ustar00rootroot00000000000000/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ /*************************************************************************** * Copyright (C) 2017-2025 ZmartZone Holding BV * All rights reserved. * * DISCLAIMER OF WARRANTIES: * * THE SOFTWARE PROVIDED HEREUNDER IS PROVIDED ON AN "AS IS" BASIS, WITHOUT * ANY WARRANTIES OR REPRESENTATIONS EXPRESS, IMPLIED OR STATUTORY; INCLUDING, * WITHOUT LIMITATION, WARRANTIES OF QUALITY, PERFORMANCE, NONINFRINGEMENT, * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. NOR ARE THERE ANY * WARRANTIES CREATED BY A COURSE OR DEALING, COURSE OF PERFORMANCE OR TRADE * USAGE. FURTHERMORE, THERE ARE NO WARRANTIES THAT THE SOFTWARE WILL MEET * YOUR NEEDS OR BE FREE FROM ERRORS, OR THAT THE OPERATION OF THE SOFTWARE * WILL BE UNINTERRUPTED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * @Author: Hans Zandbelt - hans.zandbelt@openidc.com */ #include "cfg/dir.h" #include "handle/handle.h" #include "metrics.h" #include "mod_auth_openidc.h" #include "proto/proto.h" #include "state.h" #include "util.h" /* * redirect the browser to the session logout endpoint */ static int oidc_response_redirect_parent_window_to_logout(request_rec *r, oidc_cfg_t *c) { oidc_debug(r, "enter"); char *java_script = apr_psprintf(r->pool, " \n", oidc_util_javascript_escape(r->pool, oidc_util_redirect_uri(r, c))); return oidc_util_html_send(r, "Redirecting...", java_script, NULL, NULL, OK); } /* * handle an error returned by the OP */ static int oidc_response_authorization_error(request_rec *r, oidc_cfg_t *c, oidc_proto_state_t *proto_state, const char *error, const char *error_description) { const char *prompt = oidc_proto_state_get_prompt(proto_state); if (prompt != NULL) prompt = apr_pstrdup(r->pool, prompt); oidc_proto_state_destroy(proto_state); if ((prompt != NULL) && (_oidc_strcmp(prompt, OIDC_PROTO_PROMPT_NONE) == 0)) { return oidc_response_redirect_parent_window_to_logout(r, c); } return oidc_util_html_send_error(r, apr_psprintf(r->pool, "OpenID Connect Provider error: %s", error), error_description, HTTP_BAD_REQUEST); } /* handle the browser back on an authorization response */ static apr_byte_t oidc_response_browser_back(request_rec *r, const char *r_state, oidc_session_t *session) { /* see if we have an existing session and browser-back was used */ const char *s_state = NULL, *o_url = NULL; if (session->remote_user != NULL) { s_state = oidc_session_get_request_state(r, session); o_url = oidc_session_get_original_url(r, session); if ((r_state != NULL) && (s_state != NULL) && (_oidc_strcmp(r_state, s_state) == 0)) { /* log the browser back event detection */ oidc_warn(r, "browser back detected, redirecting to original URL: %s", o_url); /* go back to the URL that he originally tried to access */ oidc_http_hdr_out_location_set(r, o_url); return TRUE; } } return FALSE; } static char *_oidc_response_post_preserve_template_contents = NULL; /* * send an OpenID Connect authorization request to the specified provider preserving POST parameters using HTML5 storage */ apr_byte_t oidc_response_post_preserve_javascript(request_rec *r, const char *location, char **javascript, char **javascript_method) { if (oidc_cfg_dir_preserve_post_get(r) == 0) return FALSE; oidc_debug(r, "enter"); oidc_cfg_t *cfg = ap_get_module_config(r->server->module_config, &auth_openidc_module); const char *method = oidc_original_request_method(r, cfg, FALSE); if (_oidc_strcmp(method, OIDC_METHOD_FORM_POST) != 0) return FALSE; /* read the parameters that are POST-ed to us */ apr_table_t *params = apr_table_make(r->pool, 8); if (oidc_util_read_post_params(r, params, FALSE, NULL) == FALSE) { oidc_error(r, "something went wrong when reading the POST parameters"); return FALSE; } const apr_array_header_t *arr = apr_table_elts(params); const apr_table_entry_t *elts = (const apr_table_entry_t *)arr->elts; int i; char *json = ""; for (i = 0; i < arr->nelts; i++) { json = apr_psprintf(r->pool, "%s'%s': '%s'%s", json, oidc_http_url_encode(r, elts[i].key), oidc_http_url_encode(r, elts[i].val), i < arr->nelts - 1 ? "," : ""); } json = apr_psprintf(r->pool, "{ %s }", json); if (oidc_cfg_post_preserve_template_get(cfg) != NULL) if (oidc_util_html_send_in_template( r, oidc_cfg_post_preserve_template_get(cfg), &_oidc_response_post_preserve_template_contents, json, OIDC_POST_PRESERVE_ESCAPE_NONE, location, OIDC_POST_PRESERVE_ESCAPE_JAVASCRIPT, OK) == OK) return TRUE; const char *jmethod = "preserveOnLoad"; const char *jscript = apr_psprintf( r->pool, " \n", jmethod, json, location ? apr_psprintf(r->pool, "window.location='%s';\n", oidc_util_javascript_escape(r->pool, location)) : ""); if (location == NULL) { if (javascript_method) *javascript_method = apr_pstrdup(r->pool, jmethod); if (javascript) *javascript = apr_pstrdup(r->pool, jscript); } else { oidc_util_html_send(r, "Preserving...", jscript, jmethod, "

Preserving...

", OK); } return TRUE; } /* * restore POST parameters on original_url from HTML5 session storage */ static int oidc_response_post_preserved_restore(request_rec *r, const char *original_url) { oidc_debug(r, "enter: original_url=%s", original_url); const char *method = "postOnLoad"; const char *script = apr_psprintf(r->pool, " \n", method, oidc_util_javascript_escape(r->pool, original_url)); const char *body = "

Restoring...

\n" "
\n"; return oidc_util_html_send(r, "Restoring...", script, method, body, OK); } char *oidc_response_make_sid_iss_unique(request_rec *r, const char *sid, const char *issuer) { return apr_psprintf(r->pool, "%s@%s", sid, issuer); } /* * store resolved information in the session */ apr_byte_t oidc_response_save_in_session(request_rec *r, oidc_cfg_t *c, oidc_session_t *session, oidc_provider_t *provider, const char *remoteUser, const char *id_token, oidc_jwt_t *id_token_jwt, const char *claims, const char *access_token, const char *access_token_type, const int expires_in, const char *refresh_token, const char *session_state, const char *state, const char *original_url, const char *userinfo_jwt) { /* store the user in the session */ session->remote_user = apr_pstrdup(r->pool, remoteUser); /* set the session expiry to the inactivity timeout */ session->expiry = apr_time_now() + apr_time_from_sec(oidc_cfg_session_inactivity_timeout_get(c)); /* store the claims payload in the id_token for later reference */ oidc_session_set_idtoken_claims(r, session, id_token_jwt->payload.value.str); if (oidc_cfg_store_id_token_get(c)) { /* store the compact serialized representation of the id_token for later reference */ oidc_session_set_idtoken(r, session, id_token); } /* store the issuer in the session (at least needed for session mgmt and token refresh */ oidc_session_set_issuer(r, session, oidc_cfg_provider_issuer_get(provider)); /* store the state and original URL in the session for handling browser-back more elegantly */ oidc_session_set_request_state(r, session, state); oidc_session_set_original_url(r, session, original_url); if ((session_state != NULL) && (oidc_cfg_provider_check_session_iframe_get(provider) != NULL)) { /* store the session state and required parameters session management */ oidc_session_set_session_state(r, session, session_state); oidc_debug(r, "session management enabled: stored session_state (%s), check_session_iframe (%s) and " "client_id (%s) in the session", session_state, oidc_cfg_provider_check_session_iframe_get(provider), oidc_cfg_provider_client_id_get(provider)); } else if (oidc_cfg_provider_check_session_iframe_get(provider) == NULL) { oidc_debug( r, "session management disabled: \"check_session_iframe\" is not set in provider configuration"); } else { oidc_debug(r, "session management disabled: no \"session_state\" value is provided in the authentication " "response even though \"check_session_iframe\" (%s) is set in the provider configuration", oidc_cfg_provider_check_session_iframe_get(provider)); } /* store the, possibly, provider specific userinfo_refresh_interval for performance reasons */ oidc_session_set_userinfo_refresh_interval(r, session, oidc_cfg_provider_userinfo_refresh_interval_get(provider)); /* store claims resolved from userinfo endpoint */ oidc_userinfo_store_claims(r, c, session, provider, claims, userinfo_jwt); /* see if we have an access_token */ if (access_token != NULL) { /* store the access_token in the session context */ oidc_session_set_access_token(r, session, access_token); /* store the access_token in the session context */ oidc_session_set_access_token_type(r, session, access_token_type); /* store the associated expires_in value */ oidc_session_set_access_token_expires(r, session, expires_in); /* reset the access token refresh timestamp */ oidc_session_set_access_token_last_refresh(r, session, apr_time_now()); } /* see if we have a refresh_token */ if (refresh_token != NULL) { /* store the refresh_token in the session context */ oidc_session_set_refresh_token(r, session, refresh_token); } /* store max session duration in the session as a hard cut-off expiry timestamp */ apr_time_t session_expires = (oidc_cfg_provider_session_max_duration_get(provider) == 0) ? apr_time_from_sec(id_token_jwt->payload.exp) : (apr_time_now() + apr_time_from_sec(oidc_cfg_provider_session_max_duration_get(provider))); oidc_session_set_session_expires(r, session, session_expires); oidc_debug(r, "oidc_provider_session_max_duration_get(provider) = %d, session_expires=%" APR_TIME_T_FMT, oidc_cfg_provider_session_max_duration_get(provider), session_expires); /* log message about max session duration */ oidc_log_session_expires(r, "session max lifetime", session_expires); /* store the domain for which this session is valid */ oidc_session_set_cookie_domain(r, session, oidc_cfg_cookie_domain_get(c) ? oidc_cfg_cookie_domain_get(c) : oidc_util_current_url_host(r, oidc_cfg_x_forwarded_headers_get(c))); char *sid = NULL; oidc_debug(r, "provider->backchannel_logout_supported=%d", oidc_cfg_provider_backchannel_logout_supported_get(provider)); /* * Storing the sid in the session makes sense even if no backchannel logout * is supported as the front channel logout as specified in * "OpenID Connect Front-Channel Logout 1.0 - draft 05" at * https://openid.net/specs/openid-connect-frontchannel-1_0.html * might deliver a sid during front channel logout. */ oidc_jose_get_string(r->pool, id_token_jwt->payload.value.json, OIDC_CLAIM_SID, FALSE, &sid, NULL); if (sid == NULL) sid = id_token_jwt->payload.sub; session->sid = oidc_response_make_sid_iss_unique(r, sid, oidc_cfg_provider_issuer_get(provider)); /* indicate that this is a newly created session */ oidc_session_set_session_new(r, session, 1); /* store the session */ return oidc_session_save(r, session, TRUE); } /* * restore the state that was maintained between authorization request and response in an encrypted cookie */ static apr_byte_t oidc_response_proto_state_restore(request_rec *r, oidc_cfg_t *c, const char *state, oidc_proto_state_t **proto_state) { oidc_debug(r, "enter"); const char *cookieName = oidc_state_cookie_name(r, state); /* clean expired state cookies to avoid pollution */ oidc_state_cookies_clean_expired(r, c, cookieName, FALSE); /* get the state cookie value first */ char *cookieValue = oidc_http_get_cookie(r, cookieName); if (cookieValue == NULL) { oidc_error(r, "no \"%s\" state cookie found: check domain and samesite cookie settings", cookieName); return FALSE; } /* clear state cookie because we don't need it anymore */ oidc_http_set_cookie(r, cookieName, "", 0, OIDC_HTTP_COOKIE_SAMESITE_NONE(c, r)); *proto_state = oidc_proto_state_from_cookie(r, c, cookieValue); if (*proto_state == NULL) return FALSE; const char *nonce = oidc_proto_state_get_nonce(*proto_state); /* calculate the hash of the browser fingerprint concatenated with the nonce */ char *calc = oidc_state_browser_fingerprint(r, c, nonce); /* compare the calculated hash with the value provided in the authorization response */ if (_oidc_strcmp(calc, state) != 0) { oidc_error( r, "calculated state from cookie does not match state parameter passed back in URL: \"%s\" != \"%s\"", state, calc); oidc_proto_state_destroy(*proto_state); return FALSE; } apr_time_t ts = oidc_proto_state_get_timestamp(*proto_state); /* check that the timestamp is not beyond the valid interval */ if (apr_time_now() > ts + apr_time_from_sec(oidc_cfg_state_timeout_get(c))) { oidc_error(r, "state has expired"); if ((oidc_cfg_default_sso_url_get(c) == NULL) || (apr_table_get(r->subprocess_env, "OIDC_NO_DEFAULT_URL_ON_STATE_TIMEOUT") != NULL)) { oidc_util_html_send_error( r, "Invalid Authentication Response", apr_psprintf(r->pool, "This is due to a timeout; please restart your authentication session by " "re-entering the URL/bookmark you originally wanted to access: %s", oidc_proto_state_get_original_url(*proto_state)), OK); } oidc_proto_state_destroy(*proto_state); return FALSE; } /* add the state */ oidc_proto_state_set_state(*proto_state, state); /* log the restored state object */ oidc_debug(r, "restored state: %s", oidc_proto_state_to_string(r, *proto_state)); /* we've made it */ return TRUE; } /* * helper function for basic/implicit client flows upon receiving an authorization response: * check that it matches the state stored in the browser and return the variables associated * with the state, such as original_url and OP oidc_provider_t pointer. */ static apr_byte_t oidc_response_match_state(request_rec *r, oidc_cfg_t *c, const char *state, struct oidc_provider_t **provider, oidc_proto_state_t **proto_state) { oidc_debug(r, "enter (state=%s)", state); if ((state == NULL) || (_oidc_strcmp(state, "") == 0)) { oidc_error(r, "state parameter is not set"); return FALSE; } /* check the state parameter against what we stored in a cookie */ if (oidc_response_proto_state_restore(r, c, state, proto_state) == FALSE) { oidc_error(r, "unable to restore state"); return FALSE; } *provider = oidc_get_provider_for_issuer(r, c, oidc_proto_state_get_issuer(*proto_state), FALSE); if (*provider == NULL) { oidc_proto_state_destroy(*proto_state); *proto_state = NULL; return FALSE; } return TRUE; } /* * handle the different flows (hybrid, implicit, Authorization Code) */ static apr_byte_t oidc_response_flows(request_rec *r, oidc_cfg_t *c, oidc_proto_state_t *proto_state, oidc_provider_t *provider, apr_table_t *params, const char *response_mode, oidc_jwt_t **jwt) { apr_byte_t rc = FALSE; const char *requested_response_type = oidc_proto_state_get_response_type(proto_state); /* handle the requested response type/mode */ if (oidc_util_spaced_string_equals(r->pool, requested_response_type, OIDC_PROTO_RESPONSE_TYPE_CODE_IDTOKEN_TOKEN)) { rc = oidc_proto_response_code_idtoken_token(r, c, proto_state, provider, params, response_mode, jwt); } else if (oidc_util_spaced_string_equals(r->pool, requested_response_type, OIDC_PROTO_RESPONSE_TYPE_CODE_IDTOKEN)) { rc = oidc_proto_response_code_idtoken(r, c, proto_state, provider, params, response_mode, jwt); } else if (oidc_util_spaced_string_equals(r->pool, requested_response_type, OIDC_PROTO_RESPONSE_TYPE_CODE_TOKEN)) { rc = oidc_proto_response_code_token(r, c, proto_state, provider, params, response_mode, jwt); } else if (oidc_util_spaced_string_equals(r->pool, requested_response_type, OIDC_PROTO_RESPONSE_TYPE_CODE)) { rc = oidc_proto_response_code(r, c, proto_state, provider, params, response_mode, jwt); } else if (oidc_util_spaced_string_equals(r->pool, requested_response_type, OIDC_PROTO_RESPONSE_TYPE_IDTOKEN_TOKEN)) { rc = oidc_proto_response_idtoken_token(r, c, proto_state, provider, params, response_mode, jwt); } else if (oidc_util_spaced_string_equals(r->pool, requested_response_type, OIDC_PROTO_RESPONSE_TYPE_IDTOKEN)) { rc = oidc_proto_response_idtoken(r, c, proto_state, provider, params, response_mode, jwt); } else { oidc_error(r, "unsupported response type: \"%s\"", requested_response_type); } if ((rc == FALSE) && (*jwt != NULL)) { oidc_jwt_destroy(*jwt); *jwt = NULL; } return rc; } /* * set the unique user identifier that will be propagated in the Apache r->user and REMOTE_USER variables */ static apr_byte_t oidc_response_set_request_user(request_rec *r, oidc_cfg_t *c, oidc_provider_t *provider, oidc_jwt_t *jwt, const char *s_claims) { const char *issuer = oidc_cfg_provider_issuer_get(provider); char *claim_name = apr_pstrdup(r->pool, oidc_cfg_remote_user_claim_name_get(c)); int n = _oidc_strlen(claim_name); apr_byte_t post_fix_with_issuer = (claim_name[n - 1] == OIDC_CHAR_AT); if (post_fix_with_issuer == TRUE) { claim_name[n - 1] = '\0'; issuer = (_oidc_strstr(issuer, "https://") == NULL) ? apr_pstrdup(r->pool, issuer) : apr_pstrdup(r->pool, issuer + _oidc_strlen("https://")); } /* extract the username claim (default: "sub") from the id_token payload or user claims */ apr_byte_t rc = FALSE; char *remote_user = NULL; json_t *claims = NULL; oidc_util_decode_json_object(r, s_claims, &claims); if (claims == NULL) { rc = oidc_get_remote_user(r, claim_name, oidc_cfg_remote_user_claim_get(c)->reg_exp, oidc_cfg_remote_user_claim_get(c)->replace, jwt->payload.value.json, &remote_user); } else { oidc_util_json_merge(r, jwt->payload.value.json, claims); rc = oidc_get_remote_user(r, claim_name, oidc_cfg_remote_user_claim_get(c)->reg_exp, oidc_cfg_remote_user_claim_get(c)->replace, claims, &remote_user); json_decref(claims); } if ((rc == FALSE) || (remote_user == NULL)) { oidc_error(r, "" OIDCRemoteUserClaim " is set to \"%s\", but could not set the remote user based on the " "requested claim \"%s\" and the available claims for the user", oidc_cfg_remote_user_claim_name_get(c), claim_name); return FALSE; } if (post_fix_with_issuer == TRUE) remote_user = apr_psprintf(r->pool, "%s%s%s", remote_user, OIDC_STR_AT, issuer); r->user = apr_pstrdup(r->pool, remote_user); oidc_debug( r, "set remote_user to \"%s\" based on claim: \"%s\"%s", r->user, oidc_cfg_remote_user_claim_name_get(c), oidc_cfg_remote_user_claim_get(c)->reg_exp ? apr_psprintf(r->pool, " and expression: \"%s\" and replace string: \"%s\"", oidc_cfg_remote_user_claim_get(c)->reg_exp, oidc_cfg_remote_user_claim_get(c)->replace) : ""); return TRUE; } static char *_oidc_response_post_restore_template_contents = NULL; /* * complete the handling of an authorization response by obtaining, parsing and verifying the * id_token and storing the authenticated user state in the session */ static int oidc_response_process(request_rec *r, oidc_cfg_t *c, oidc_session_t *session, apr_table_t *params, const char *response_mode) { oidc_debug(r, "enter, response_mode=%s", response_mode); oidc_provider_t *provider = NULL; oidc_proto_state_t *proto_state = NULL; oidc_jwt_t *jwt = NULL; /* see if this response came from a browser-back event */ if (oidc_response_browser_back(r, apr_table_get(params, OIDC_PROTO_STATE), session) == TRUE) return HTTP_MOVED_TEMPORARILY; /* match the returned state parameter against the state stored in the browser */ if (oidc_response_match_state(r, c, apr_table_get(params, OIDC_PROTO_STATE), &provider, &proto_state) == FALSE) { if (oidc_cfg_default_sso_url_get(c) != NULL) { oidc_warn(r, "invalid authorization response state; a default SSO URL is set, sending the user " "there: %s", oidc_cfg_default_sso_url_get(c)); oidc_http_hdr_out_location_set(r, oidc_util_absolute_url(r, c, oidc_cfg_default_sso_url_get(c))); OIDC_METRICS_COUNTER_INC(r, c, OM_AUTHN_RESPONSE_ERROR_STATE_MISMATCH); return HTTP_MOVED_TEMPORARILY; } oidc_error(r, "invalid authorization response state and no default SSO URL is set, sending an error..."); // if error text was already produced (e.g. state timeout) then just return with a 400 if (apr_table_get(r->subprocess_env, OIDC_ERROR_ENVVAR) != NULL) { OIDC_METRICS_COUNTER_INC(r, c, OM_AUTHN_RESPONSE_ERROR_STATE_EXPIRED); return HTTP_BAD_REQUEST; } OIDC_METRICS_COUNTER_INC(r, c, OM_AUTHN_RESPONSE_ERROR_STATE_MISMATCH); return oidc_util_html_send_error(r, "Invalid Authorization Response", "Could not match the authorization response to an earlier request via " "the state parameter and corresponding state cookie", HTTP_BAD_REQUEST); } /* see if the response is an error response */ if (apr_table_get(params, OIDC_PROTO_ERROR) != NULL) { OIDC_METRICS_COUNTER_INC(r, c, OM_AUTHN_RESPONSE_ERROR_PROVIDER); return oidc_response_authorization_error(r, c, proto_state, apr_table_get(params, OIDC_PROTO_ERROR), apr_table_get(params, OIDC_PROTO_ERROR_DESCRIPTION)); } /* handle the code, implicit or hybrid flow */ if (oidc_response_flows(r, c, proto_state, provider, params, response_mode, &jwt) == FALSE) { OIDC_METRICS_COUNTER_INC(r, c, OM_AUTHN_RESPONSE_ERROR_PROTOCOL); return oidc_response_authorization_error(r, c, proto_state, "Error in handling response type.", NULL); } if (jwt == NULL) { oidc_error(r, "no id_token was provided"); return oidc_response_authorization_error(r, c, proto_state, "No id_token was provided.", NULL); } int expires_in = _oidc_str_to_int(apr_table_get(params, OIDC_PROTO_EXPIRES_IN), -1); char *userinfo_jwt = NULL; /* * optionally resolve additional claims against the userinfo endpoint * parsed claims are not actually used here but need to be parsed anyway for error checking purposes */ const char *claims = oidc_userinfo_retrieve_claims( r, c, provider, apr_table_get(params, OIDC_PROTO_ACCESS_TOKEN), apr_table_get(params, OIDC_PROTO_TOKEN_TYPE), NULL, jwt->payload.sub, &userinfo_jwt); /* restore the original protected URL that the user was trying to access */ const char *original_url = oidc_proto_state_get_original_url(proto_state); if (original_url != NULL) original_url = apr_pstrdup(r->pool, original_url); const char *original_method = oidc_proto_state_get_original_method(proto_state); if (original_method != NULL) original_method = apr_pstrdup(r->pool, original_method); const char *prompt = oidc_proto_state_get_prompt(proto_state); /* set the user */ if (oidc_response_set_request_user(r, c, provider, jwt, claims) == TRUE) { /* session management: if the user in the new response is not equal to the old one, error out */ if ((prompt != NULL) && (_oidc_strcmp(prompt, OIDC_PROTO_PROMPT_NONE) == 0)) { // TOOD: actually need to compare sub? (need to store it in the session separately then // const char *sub = NULL; // oidc_session_get(r, session, "sub", &sub); // if (_oidc_strcmp(sub, jwt->payload.sub) != 0) { if (_oidc_strcmp(session->remote_user, r->user) != 0) { oidc_warn(r, "user set from new id_token is different from current one"); oidc_jwt_destroy(jwt); return oidc_response_authorization_error(r, c, proto_state, "User changed!", NULL); } } /* store resolved information in the session */ if (oidc_response_save_in_session( r, c, session, provider, r->user, apr_table_get(params, OIDC_PROTO_ID_TOKEN), jwt, claims, apr_table_get(params, OIDC_PROTO_ACCESS_TOKEN), apr_table_get(params, OIDC_PROTO_TOKEN_TYPE), expires_in, apr_table_get(params, OIDC_PROTO_REFRESH_TOKEN), apr_table_get(params, OIDC_PROTO_SESSION_STATE), apr_table_get(params, OIDC_PROTO_STATE), original_url, userinfo_jwt) == FALSE) { oidc_proto_state_destroy(proto_state); oidc_jwt_destroy(jwt); return HTTP_INTERNAL_SERVER_ERROR; } oidc_debug(r, "set remote_user to \"%s\" in new session \"%s\"", r->user, session->uuid); } else { oidc_error(r, "remote user could not be set"); oidc_jwt_destroy(jwt); OIDC_METRICS_COUNTER_INC(r, c, OM_AUTHN_RESPONSE_ERROR_REMOTE_USER); return oidc_response_authorization_error( r, c, proto_state, "Remote user could not be set: contact the website administrator", NULL); } /* cleanup */ oidc_proto_state_destroy(proto_state); oidc_jwt_destroy(jwt); /* check that we've actually authenticated a user; functions as error handling for oidc_get_remote_user */ if (r->user == NULL) { OIDC_METRICS_COUNTER_INC(r, c, OM_AUTHN_RESPONSE_ERROR_REMOTE_USER); return HTTP_UNAUTHORIZED; } /* log the successful response */ oidc_debug(r, "session created and stored, returning to original URL: %s, original method: %s", original_url, original_method); /* check whether form post data was preserved; if so restore it */ if (_oidc_strcmp(original_method, OIDC_METHOD_FORM_POST) == 0) { if (oidc_cfg_post_restore_template_get(c) != NULL) if (oidc_util_html_send_in_template(r, oidc_cfg_post_restore_template_get(c), &_oidc_response_post_restore_template_contents, original_url, OIDC_POST_PRESERVE_ESCAPE_JAVASCRIPT, "", OIDC_POST_PRESERVE_ESCAPE_NONE, OK) == OK) return TRUE; return oidc_response_post_preserved_restore(r, original_url); } /* now we've authenticated the user so go back to the URL that he originally tried to access */ oidc_http_hdr_out_location_set(r, original_url); /* do the actual redirect to the original URL */ return HTTP_MOVED_TEMPORARILY; } /* * handle an OpenID Connect Authorization Response using the POST (+fragment->POST) response_mode */ int oidc_response_authorization_post(request_rec *r, oidc_cfg_t *c, oidc_session_t *session) { oidc_debug(r, "enter"); /* initialize local variables */ const char *response_mode = NULL; /* read the parameters that are POST-ed to us */ apr_table_t *params = apr_table_make(r->pool, 8); if (oidc_util_read_post_params(r, params, FALSE, NULL) == FALSE) { oidc_error(r, "something went wrong when reading the POST parameters"); return HTTP_INTERNAL_SERVER_ERROR; } /* see if we've got any POST-ed data at all */ if ((apr_table_elts(params)->nelts < 1) || ((apr_table_elts(params)->nelts == 1) && apr_table_get(params, OIDC_PROTO_RESPONSE_MODE) && (_oidc_strcmp(apr_table_get(params, OIDC_PROTO_RESPONSE_MODE), OIDC_PROTO_RESPONSE_MODE_FRAGMENT) == 0))) { return oidc_util_html_send_error( r, "Invalid Request", "You've hit an OpenID Connect Redirect URI with no parameters, this is an invalid request; you " "should not open this URL in your browser directly, or have the server administrator use a " "different " OIDCRedirectURI " setting.", HTTP_INTERNAL_SERVER_ERROR); } /* get the parameters */ response_mode = apr_table_get(params, OIDC_PROTO_RESPONSE_MODE); /* do the actual implicit work */ return oidc_response_process(r, c, session, params, response_mode ? response_mode : OIDC_PROTO_RESPONSE_MODE_FORM_POST); } /* * handle an OpenID Connect Authorization Response using the redirect response_mode */ int oidc_response_authorization_redirect(request_rec *r, oidc_cfg_t *c, oidc_session_t *session) { oidc_debug(r, "enter"); /* read the parameters from the query string */ apr_table_t *params = apr_table_make(r->pool, 8); oidc_util_read_form_encoded_params(r, params, r->args); /* do the actual work */ return oidc_response_process(r, c, session, params, OIDC_PROTO_RESPONSE_MODE_QUERY); } mod_auth_openidc-2.4.16.10/src/handle/revoke.c000066400000000000000000000061541476721736500210350ustar00rootroot00000000000000/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ /*************************************************************************** * Copyright (C) 2017-2025 ZmartZone Holding BV * All rights reserved. * * DISCLAIMER OF WARRANTIES: * * THE SOFTWARE PROVIDED HEREUNDER IS PROVIDED ON AN "AS IS" BASIS, WITHOUT * ANY WARRANTIES OR REPRESENTATIONS EXPRESS, IMPLIED OR STATUTORY; INCLUDING, * WITHOUT LIMITATION, WARRANTIES OF QUALITY, PERFORMANCE, NONINFRINGEMENT, * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. NOR ARE THERE ANY * WARRANTIES CREATED BY A COURSE OR DEALING, COURSE OF PERFORMANCE OR TRADE * USAGE. FURTHERMORE, THERE ARE NO WARRANTIES THAT THE SOFTWARE WILL MEET * YOUR NEEDS OR BE FREE FROM ERRORS, OR THAT THE OPERATION OF THE SOFTWARE * WILL BE UNINTERRUPTED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * @Author: Hans Zandbelt - hans.zandbelt@openidc.com */ #include "handle/handle.h" #include "mod_auth_openidc.h" int oidc_revoke_session(request_rec *r, oidc_cfg_t *c) { apr_byte_t rc = FALSE; char *session_id = NULL; oidc_util_request_parameter_get(r, OIDC_REDIRECT_URI_REQUEST_REVOKE_SESSION, &session_id); if (session_id == NULL) return HTTP_BAD_REQUEST; if (oidc_cfg_session_type_get(c) == OIDC_SESSION_TYPE_SERVER_CACHE) rc = oidc_cache_set_session(r, session_id, NULL, 0); else oidc_warn(r, "cannot revoke session because server side caching is not in use"); r->user = ""; return (rc == TRUE) ? OK : HTTP_INTERNAL_SERVER_ERROR; } /* * handle a request to invalidate a cached access token introspection result */ int oidc_revoke_at_cache_remove(request_rec *r, oidc_cfg_t *c) { char *access_token = NULL; oidc_util_request_parameter_get(r, OIDC_REDIRECT_URI_REQUEST_REMOVE_AT_CACHE, &access_token); char *cache_entry = NULL; oidc_cache_get_access_token(r, access_token, &cache_entry); if (cache_entry == NULL) { oidc_error(r, "no cached access token found for value: %s", access_token); return HTTP_NOT_FOUND; } oidc_cache_set_access_token(r, access_token, NULL, 0); return OK; } mod_auth_openidc-2.4.16.10/src/handle/session_management.c000066400000000000000000000211421476721736500234130ustar00rootroot00000000000000/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ /*************************************************************************** * Copyright (C) 2017-2025 ZmartZone Holding BV * All rights reserved. * * DISCLAIMER OF WARRANTIES: * * THE SOFTWARE PROVIDED HEREUNDER IS PROVIDED ON AN "AS IS" BASIS, WITHOUT * ANY WARRANTIES OR REPRESENTATIONS EXPRESS, IMPLIED OR STATUTORY; INCLUDING, * WITHOUT LIMITATION, WARRANTIES OF QUALITY, PERFORMANCE, NONINFRINGEMENT, * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. NOR ARE THERE ANY * WARRANTIES CREATED BY A COURSE OR DEALING, COURSE OF PERFORMANCE OR TRADE * USAGE. FURTHERMORE, THERE ARE NO WARRANTIES THAT THE SOFTWARE WILL MEET * YOUR NEEDS OR BE FREE FROM ERRORS, OR THAT THE OPERATION OF THE SOFTWARE * WILL BE UNINTERRUPTED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * @Author: Hans Zandbelt - hans.zandbelt@openidc.com */ #include "cfg/dir.h" #include "handle/handle.h" #include "metrics.h" #include "mod_auth_openidc.h" #include "util.h" static int oidc_session_management_iframe_op(request_rec *r, oidc_cfg_t *c, oidc_session_t *session, const char *check_session_iframe) { oidc_debug(r, "enter"); oidc_http_hdr_out_location_set(r, check_session_iframe); return HTTP_MOVED_TEMPORARILY; } static int oidc_session_management_iframe_rp(request_rec *r, oidc_cfg_t *c, oidc_session_t *session, const char *client_id, const char *check_session_iframe) { oidc_debug(r, "enter"); const char *java_script = " \n"; /* determine the origin for the check_session_iframe endpoint */ char *origin = apr_pstrdup(r->pool, check_session_iframe); apr_uri_t uri; apr_uri_parse(r->pool, check_session_iframe, &uri); char *p = _oidc_strstr(origin, uri.path); *p = '\0'; /* the element identifier for the OP iframe */ const char *op_iframe_id = "openidc-op"; /* restore the OP session_state from the session */ const char *session_state = oidc_session_get_session_state(r, session); if (session_state == NULL) { oidc_warn( r, "no session_state found in the session; the OP does probably not support session management!?"); // return OK; } char *s_poll_interval = NULL; oidc_util_request_parameter_get(r, "poll", &s_poll_interval); int poll_interval = _oidc_str_to_int(s_poll_interval, 0); if ((poll_interval <= 0) || (poll_interval > 3600 * 24)) poll_interval = 3000; char *login_uri = NULL, *error_str = NULL, *error_description = NULL; oidc_util_request_parameter_get(r, "login_uri", &login_uri); if ((login_uri != NULL) && (oidc_validate_redirect_url(r, c, login_uri, FALSE, &error_str, &error_description) == FALSE)) { return HTTP_BAD_REQUEST; } const char *redirect_uri = oidc_util_redirect_uri(r, c); java_script = apr_psprintf(r->pool, java_script, origin, client_id, session_state ? session_state : "", login_uri ? login_uri : "", op_iframe_id, poll_interval, redirect_uri, redirect_uri); return oidc_util_html_send(r, NULL, java_script, "setTimer", NULL, OK); } /* * handle session management request */ int oidc_session_management(request_rec *r, oidc_cfg_t *c, oidc_session_t *session) { char *cmd = NULL; const char *id_token_hint = NULL; oidc_provider_t *provider = NULL; /* get the command passed to the session management handler */ oidc_util_request_parameter_get(r, OIDC_REDIRECT_URI_REQUEST_SESSION, &cmd); if (cmd == NULL) { oidc_error(r, "session management handler called with no command"); return HTTP_INTERNAL_SERVER_ERROR; } /* see if this is a local logout during session management */ if (_oidc_strcmp("logout", cmd) == 0) { oidc_debug( r, "[session=logout] calling oidc_handle_logout_request because of session mgmt local logout call."); return oidc_logout_request(r, c, session, oidc_util_absolute_url(r, c, oidc_cfg_default_slo_url_get(c)), TRUE); } if (oidc_get_provider_from_session(r, c, session, &provider) == FALSE) { if ((oidc_provider_static_config(r, c, &provider) == FALSE) || (provider == NULL)) return HTTP_NOT_FOUND; } /* see if this is a request for the OP iframe */ if (_oidc_strcmp("iframe_op", cmd) == 0) { if (oidc_cfg_provider_check_session_iframe_get(provider) != NULL) { return oidc_session_management_iframe_op(r, c, session, oidc_cfg_provider_check_session_iframe_get(provider)); } return HTTP_NOT_FOUND; } /* see if this is a request for the RP iframe */ if (_oidc_strcmp("iframe_rp", cmd) == 0) { if ((oidc_cfg_provider_client_id_get(provider) != NULL) && (oidc_cfg_provider_check_session_iframe_get(provider) != NULL)) { return oidc_session_management_iframe_rp(r, c, session, oidc_cfg_provider_client_id_get(provider), oidc_cfg_provider_check_session_iframe_get(provider)); } oidc_debug(r, "iframe_rp command issued but no client (%s) and/or no check_session_iframe (%s) set", oidc_cfg_provider_client_id_get(provider), oidc_cfg_provider_check_session_iframe_get(provider)); return HTTP_NOT_FOUND; } /* see if this is a request check the login state with the OP */ if (_oidc_strcmp("check", cmd) == 0) { id_token_hint = oidc_session_get_idtoken(r, session); /* * TODO: this doesn't work with per-path provided auth_request_params and scopes * as oidc_dir_cfg_path_auth_request_params and oidc_dir_cfg_path_scope will pick * those for the redirect_uri itself; do we need to store those as part of the * session now? */ return oidc_request_authenticate_user( r, c, provider, apr_psprintf(r->pool, "%s?session=iframe_rp", oidc_util_redirect_uri(r, c)), NULL, id_token_hint, "none", oidc_cfg_dir_path_auth_request_params_get(r), oidc_cfg_dir_path_scope_get(r)); } /* handle failure in fallthrough */ oidc_error(r, "unknown command: %s", cmd); return HTTP_INTERNAL_SERVER_ERROR; } mod_auth_openidc-2.4.16.10/src/handle/userinfo.c000066400000000000000000000353041476721736500213730ustar00rootroot00000000000000/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ /*************************************************************************** * Copyright (C) 2017-2025 ZmartZone Holding BV * All rights reserved. * * DISCLAIMER OF WARRANTIES: * * THE SOFTWARE PROVIDED HEREUNDER IS PROVIDED ON AN "AS IS" BASIS, WITHOUT * ANY WARRANTIES OR REPRESENTATIONS EXPRESS, IMPLIED OR STATUTORY; INCLUDING, * WITHOUT LIMITATION, WARRANTIES OF QUALITY, PERFORMANCE, NONINFRINGEMENT, * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. NOR ARE THERE ANY * WARRANTIES CREATED BY A COURSE OR DEALING, COURSE OF PERFORMANCE OR TRADE * USAGE. FURTHERMORE, THERE ARE NO WARRANTIES THAT THE SOFTWARE WILL MEET * YOUR NEEDS OR BE FREE FROM ERRORS, OR THAT THE OPERATION OF THE SOFTWARE * WILL BE UNINTERRUPTED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * @Author: Hans Zandbelt - hans.zandbelt@openidc.com */ #include "cfg/provider.h" #include "handle/handle.h" #include "mod_auth_openidc.h" #include "proto/proto.h" #include "util.h" /* * store claims resolved from the userinfo endpoint in the session */ void oidc_userinfo_store_claims(request_rec *r, oidc_cfg_t *c, oidc_session_t *session, oidc_provider_t *provider, const char *claims, const char *userinfo_jwt) { oidc_debug(r, "enter"); /* see if we've resolved any claims */ if (claims != NULL) { /* * Successfully decoded a set claims from the response so we can store them * (well actually the stringified representation in the response) * in the session context safely now */ oidc_session_set_userinfo_claims(r, session, claims); if (oidc_cfg_session_type_get(c) != OIDC_SESSION_TYPE_CLIENT_COOKIE) { /* this will also clear the entry if a JWT was not returned at this point */ oidc_session_set_userinfo_jwt(r, session, userinfo_jwt); } } else { /* * clear the existing claims because we could not refresh them */ oidc_session_set_userinfo_claims(r, session, NULL); oidc_session_set_userinfo_jwt(r, session, NULL); } /* store the last refresh time if we've configured a userinfo refresh interval */ if (oidc_cfg_provider_userinfo_refresh_interval_get(provider) > -1) oidc_session_reset_userinfo_last_refresh(r, session); } /* * retrieve claims from the userinfo endpoint and return the stringified response */ const char *oidc_userinfo_retrieve_claims(request_rec *r, oidc_cfg_t *c, oidc_provider_t *provider, const char *access_token, const char *access_token_type, oidc_session_t *session, char *id_token_sub, char **userinfo_jwt) { char *result = NULL; char *refreshed_access_token = NULL; char *refreshed_access_token_type = NULL; json_t *id_token_claims = NULL; long response_code = 0; oidc_debug(r, "enter"); /* see if a userinfo endpoint is set, otherwise there's nothing to do for us */ if (oidc_cfg_provider_userinfo_endpoint_url_get(provider) == NULL) { oidc_debug(r, "not retrieving userinfo claims because userinfo_endpoint is not set"); goto end; } /* see if there's an access token, otherwise we can't call the userinfo endpoint at all */ if (access_token == NULL) { oidc_debug(r, "not retrieving userinfo claims because access_token is not provided"); goto end; } if ((id_token_sub == NULL) && (session != NULL)) { // when refreshing claims from the userinfo endpoint id_token_claims = oidc_session_get_idtoken_claims_json(r, session); if (id_token_claims != NULL) { oidc_jose_get_string(r->pool, id_token_claims, OIDC_CLAIM_SUB, FALSE, &id_token_sub, NULL); } else { oidc_debug(r, "no id_token_claims found in session"); } } // TODO: return code should indicate whether the token expired or some other error occurred // TODO: long-term: session storage should be JSON (with explicit types and less conversion, using standard // routines) /* try to get claims from the userinfo endpoint using the provided access token */ if (oidc_proto_userinfo_request(r, c, provider, id_token_sub, access_token, access_token_type, &result, userinfo_jwt, &response_code) == TRUE) goto end; /* see if this is the initial call to the user info endpoint upon receiving the authorization response */ if (session == NULL) { oidc_error(r, "resolving user info claims with the provided access token failed, nothing will be " "stored in the session"); result = NULL; goto end; } // a connectivity error rather than a HTTP error; may want to check for anything != 401 if (response_code == 0) { oidc_error(r, "resolving user info claims failed with a connectivity error, no attempt will be made to " "refresh the access token and try again"); result = NULL; goto end; } /* first call to user info endpoint failed, but this is for an existing session and the access token may have * just expired, so refresh it */ if (oidc_refresh_token_grant(r, c, session, provider, &refreshed_access_token, &refreshed_access_token_type, NULL) == FALSE) { oidc_error(r, "refreshing access token failed, claims will not be retrieved/refreshed from the " "userinfo endpoint"); result = NULL; goto end; } /* try again with the new access token */ if (oidc_proto_userinfo_request(r, c, provider, id_token_sub, refreshed_access_token, refreshed_access_token_type, &result, userinfo_jwt, NULL) == FALSE) { oidc_error(r, "resolving user info claims with the refreshed access token failed, nothing will be " "stored in the session"); result = NULL; goto end; } end: if (id_token_claims) json_decref(id_token_claims); oidc_debug(r, "return (%d)", result != NULL); return result; } /* * get (new) claims from the userinfo endpoint */ apr_byte_t oidc_userinfo_refresh_claims(request_rec *r, oidc_cfg_t *cfg, oidc_session_t *session, apr_byte_t *needs_save) { apr_byte_t rc = TRUE; oidc_provider_t *provider = NULL; const char *claims = NULL; const char *access_token = NULL; const char *access_token_type = NULL; char *userinfo_jwt = NULL; /* see int we can do anything here, i.e. a refresh interval is configured */ int interval = oidc_session_get_userinfo_refresh_interval(r, session); oidc_debug(r, "interval=%d", interval); if (interval > -1) { /* get the current provider info */ if (oidc_get_provider_from_session(r, cfg, session, &provider) == FALSE) { *needs_save = TRUE; return FALSE; } if (oidc_cfg_provider_userinfo_endpoint_url_get(provider) != NULL) { /* get the last refresh timestamp from the session info */ apr_time_t last_refresh = oidc_session_get_userinfo_last_refresh(r, session); oidc_debug(r, "refresh needed in: %" APR_TIME_T_FMT " seconds (last_refresh=%" APR_TIME_T_FMT ", interval=%d, now=%" APR_TIME_T_FMT ")", apr_time_sec(last_refresh + apr_time_from_sec(interval) - apr_time_now()), apr_time_sec(last_refresh), interval, apr_time_sec(apr_time_now())); /* see if we need to refresh again */ if (last_refresh + apr_time_from_sec(interval) < apr_time_now()) { /* get the current access token */ access_token = oidc_session_get_access_token(r, session); access_token_type = oidc_session_get_access_token_type(r, session); /* retrieve the current claims */ claims = oidc_userinfo_retrieve_claims(r, cfg, provider, access_token, access_token_type, session, NULL, &userinfo_jwt); /* store claims resolved from userinfo endpoint */ oidc_userinfo_store_claims(r, cfg, session, provider, claims, userinfo_jwt); if (claims == NULL) { *needs_save = FALSE; rc = FALSE; } else { /* indicated something changed */ *needs_save = TRUE; } } } } oidc_debug(r, "return: %d", rc); return rc; } #define OIDC_USERINFO_SIGNED_JWT_EXP_DEFAULT 60 #define OIDC_USERINFO_SIGNED_JWT_CACHE_TTL_DEFAULT -1 #define OIDC_USERINFO_SIGNED_JWT_CACHE_TTL_ENVVAR "OIDC_USERINFO_SIGNED_JWT_CACHE_TTL" /* * obtain the signed JWT cache TTL from the environment variables */ static int oidc_userinfo_signed_jwt_cache_ttl(request_rec *r) { const char *s_ttl = apr_table_get(r->subprocess_env, OIDC_USERINFO_SIGNED_JWT_CACHE_TTL_ENVVAR); return _oidc_str_to_int(s_ttl, OIDC_USERINFO_SIGNED_JWT_CACHE_TTL_DEFAULT); } /* * create a signed JWT with s_claims payload and return the serialized form in cser */ static apr_byte_t oidc_userinfo_create_signed_jwt(request_rec *r, oidc_cfg_t *cfg, oidc_session_t *session, const char *s_claims, char **cser) { apr_byte_t rv = FALSE; oidc_jwt_t *jwt = NULL; oidc_jwk_t *jwk = NULL; oidc_jose_error_t err; apr_time_t access_token_expires = -1; char *jti = NULL; char *key = NULL; json_t *json = NULL; int ttl = 0; int exp = 0; apr_time_t expiry = 0; oidc_debug(r, "enter: %s", s_claims); if (oidc_proto_jwt_create_from_first_pkey(r, cfg, &jwk, &jwt, FALSE) == FALSE) goto end; json_object_set_new(jwt->payload.value.json, OIDC_CLAIM_AUD, json_string(oidc_util_current_url(r, oidc_cfg_x_forwarded_headers_get(cfg)))); json_object_set_new(jwt->payload.value.json, OIDC_CLAIM_ISS, json_string(oidc_cfg_provider_issuer_get(oidc_cfg_provider_get(cfg)))); oidc_util_decode_json_object(r, s_claims, &json); if (json == NULL) goto end; if (oidc_util_json_merge(r, json, jwt->payload.value.json) == FALSE) goto end; s_claims = oidc_util_encode_json(r->pool, jwt->payload.value.json, JSON_PRESERVE_ORDER | JSON_COMPACT); if (oidc_jose_hash_and_base64url_encode(r->pool, OIDC_JOSE_ALG_SHA256, s_claims, _oidc_strlen(s_claims) + 1, &key, &err) == FALSE) { oidc_error(r, "oidc_jose_hash_and_base64url_encode failed: %s", oidc_jose_e2s(r->pool, err)); goto end; } ttl = oidc_userinfo_signed_jwt_cache_ttl(r); if (ttl > -1) oidc_cache_get_signed_jwt(r, key, cser); if (*cser != NULL) { oidc_debug(r, "signed JWT found in cache"); rv = TRUE; goto end; } if (json_object_get(jwt->payload.value.json, OIDC_CLAIM_JTI) == NULL) { oidc_util_generate_random_string(r, &jti, OIDC_PROTO_JWT_JTI_LEN); json_object_set_new(jwt->payload.value.json, OIDC_CLAIM_JTI, json_string(jti)); } if (json_object_get(jwt->payload.value.json, OIDC_CLAIM_IAT) == NULL) { json_object_set_new(jwt->payload.value.json, OIDC_CLAIM_IAT, json_integer(apr_time_sec(apr_time_now()))); } if (json_object_get(jwt->payload.value.json, OIDC_CLAIM_EXP) == NULL) { access_token_expires = oidc_session_get_access_token_expires(r, session); json_object_set_new(jwt->payload.value.json, OIDC_CLAIM_EXP, json_integer(access_token_expires > 0 ? apr_time_sec(access_token_expires) : apr_time_sec(apr_time_now()) + OIDC_USERINFO_SIGNED_JWT_EXP_DEFAULT)); } if (oidc_proto_jwt_sign_and_serialize(r, jwk, jwt, cser) == FALSE) goto end; rv = TRUE; if (ttl < 0) goto end; if (ttl == 0) { // need to get the cache ttl from the exp claim oidc_util_json_object_get_int(jwt->payload.value.json, OIDC_CLAIM_EXP, &exp, 0); // actually the exp claim always exists by now expiry = (exp > 0) ? apr_time_from_sec(exp) : apr_time_now() + apr_time_from_sec(OIDC_USERINFO_SIGNED_JWT_EXP_DEFAULT); } else { // ttl > 0 expiry = apr_time_now() + apr_time_from_sec(ttl); } oidc_debug(r, "caching signed JWT with ~ttl(%ld)", apr_time_sec(expiry - apr_time_now())); oidc_cache_set_signed_jwt(r, key, *cser, expiry); end: if (json) json_decref(json); if (jwt) oidc_jwt_destroy(jwt); return rv; } /* * pass the userinfo claims to headers and/or environment variables, encoded as configured */ void oidc_userinfo_pass_as(request_rec *r, oidc_cfg_t *cfg, oidc_session_t *session, const char *s_claims, oidc_appinfo_pass_in_t pass_in, oidc_appinfo_encoding_t encoding) { const apr_array_header_t *pass_userinfo_as = NULL; oidc_pass_user_info_as_t *p = NULL; int i = 0; char *cser = NULL; pass_userinfo_as = oidc_cfg_dir_pass_userinfo_as_get(r); #ifdef USE_LIBJQ s_claims = oidc_util_jq_filter(r, s_claims, oidc_cfg_dir_userinfo_claims_expr_get(r)); #endif for (i = 0; (pass_userinfo_as != NULL) && (i < pass_userinfo_as->nelts); i++) { p = APR_ARRAY_IDX(pass_userinfo_as, i, oidc_pass_user_info_as_t *); switch (p->type) { case OIDC_PASS_USERINFO_AS_CLAIMS: /* set the userinfo claims in the app headers */ oidc_set_app_claims(r, cfg, s_claims); break; case OIDC_PASS_USERINFO_AS_JSON_OBJECT: /* pass the userinfo JSON object to the app in a header or environment variable */ oidc_util_set_app_info(r, p->name ? p->name : OIDC_APP_INFO_USERINFO_JSON, s_claims, p->name ? "" : OIDC_DEFAULT_HEADER_PREFIX, pass_in, encoding); break; case OIDC_PASS_USERINFO_AS_JWT: if (oidc_cfg_session_type_get(cfg) != OIDC_SESSION_TYPE_CLIENT_COOKIE) { /* get the compact serialized JWT from the session */ const char *s_userinfo_jwt = oidc_session_get_userinfo_jwt(r, session); if (s_userinfo_jwt != NULL) { /* pass the compact serialized JWT to the app in a header or environment * variable */ oidc_util_set_app_info( r, p->name ? p->name : OIDC_APP_INFO_USERINFO_JWT, s_userinfo_jwt, p->name ? "" : OIDC_DEFAULT_HEADER_PREFIX, pass_in, encoding); } else { oidc_debug( r, "configured to pass userinfo in a JWT, but no such JWT was found in the " "session (probably no such JWT was returned from the userinfo endpoint)"); } } else { oidc_error(r, "session type \"client-cookie\" does not allow storing/passing a " "userinfo JWT; use \"" OIDCSessionType " server-cache\" for that"); } break; case OIDC_PASS_USERINFO_AS_SIGNED_JWT: if (oidc_userinfo_create_signed_jwt(r, cfg, session, s_claims, &cser) == TRUE) { oidc_util_set_app_info(r, p->name ? p->name : OIDC_APP_INFO_SIGNED_JWT, cser, p->name ? "" : OIDC_DEFAULT_HEADER_PREFIX, pass_in, encoding); } break; default: break; } } } mod_auth_openidc-2.4.16.10/src/http.c000066400000000000000000001273051476721736500172700ustar00rootroot00000000000000/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ /*************************************************************************** * Copyright (C) 2017-2025 ZmartZone Holding BV * Copyright (C) 2013-2017 Ping Identity Corporation * All rights reserved. * * DISCLAIMER OF WARRANTIES: * * THE SOFTWARE PROVIDED HEREUNDER IS PROVIDED ON AN "AS IS" BASIS, WITHOUT * ANY WARRANTIES OR REPRESENTATIONS EXPRESS, IMPLIED OR STATUTORY; INCLUDING, * WITHOUT LIMITATION, WARRANTIES OF QUALITY, PERFORMANCE, NONINFRINGEMENT, * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. NOR ARE THERE ANY * WARRANTIES CREATED BY A COURSE OR DEALING, COURSE OF PERFORMANCE OR TRADE * USAGE. FURTHERMORE, THERE ARE NO WARRANTIES THAT THE SOFTWARE WILL MEET * YOUR NEEDS OR BE FREE FROM ERRORS, OR THAT THE OPERATION OF THE SOFTWARE * WILL BE UNINTERRUPTED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * @Author: Hans Zandbelt - hans.zandbelt@openidc.com */ #include #ifndef WIN32 #include #endif #include #include #include #include "cfg/dir.h" #include "const.h" #include "http.h" #include "metrics.h" #include "proto/proto.h" #include "util.h" /* * URL-encode a string */ char *oidc_http_url_encode(const request_rec *r, const char *str) { /* * cuRL does not allow us to share the same handle in multiple threads * see: https://curl.se/libcurl/c/threadsafe.html * so we can not not use a global variable here and optimize performance */ char *rv = ""; char *result = NULL; CURL *curl = NULL; if (str == NULL) goto end; curl = curl_easy_init(); if (curl == NULL) { oidc_error(r, "curl_easy_init() error"); goto end; } result = curl_easy_escape(curl, str, 0); if (result == NULL) { oidc_error(r, "curl_easy_escape() error"); goto end; } rv = apr_pstrdup(r->pool, result); end: if (result) curl_free(result); if (curl) curl_easy_cleanup(curl); return rv; } /* * URL-decode a string */ char *oidc_http_url_decode(const request_rec *r, const char *str) { char *rv = ""; char *result = NULL; CURL *curl = NULL; int counter = 0; char *replaced = NULL; if (str == NULL) goto end; curl = curl_easy_init(); if (curl == NULL) { oidc_error(r, "curl_easy_init() error"); goto end; } replaced = apr_pstrdup(r->pool, str); while (replaced[counter] != '\0') { if (replaced[counter] == '+') { replaced[counter] = ' '; } counter++; } result = curl_easy_unescape(curl, replaced, 0, 0); if (result == NULL) { oidc_error(r, "curl_easy_unescape() error"); goto end; } rv = apr_pstrdup(r->pool, result); // oidc_debug(r, "input=\"%s\", output=\"%s\"", str, rv); end: if (result) curl_free(result); if (curl) curl_easy_cleanup(curl); return rv; } /* * obtain a HTTP request header value */ static const char *oidc_http_hdr_in_get(const request_rec *r, const char *name) { const char *value = apr_table_get(r->headers_in, name); if (value) oidc_debug(r, "%s=%s", name, value); return value; } /* * obtain the left-most element of a multi-valued HTTP header value */ static const char *oidc_http_hdr_in_get_left_most_only(const request_rec *r, const char *name, const char *separator) { char *last = NULL; const char *value = oidc_http_hdr_in_get(r, name); if (value) return apr_strtok(apr_pstrdup(r->pool, value), separator, &last); return NULL; } /* * check if a multi-valued HTTP request header contains a specified value */ static apr_byte_t oidc_http_hdr_in_contains(const request_rec *r, const char *name, const char *separator, const char postfix_separator, const char *needle) { char *ctx = NULL, *elem = NULL; const char *value = oidc_http_hdr_in_get(r, name); apr_byte_t rc = FALSE; if (value) { elem = apr_strtok(apr_pstrdup(r->pool, value), separator, &ctx); while (elem != NULL) { while (*elem == OIDC_CHAR_SPACE) elem++; if ((_oidc_strncmp(elem, needle, _oidc_strlen(needle)) == 0) && ((elem[_oidc_strlen(needle)] == '\0') || (elem[_oidc_strlen(needle)] == postfix_separator))) { rc = TRUE; break; } elem = apr_strtok(NULL, separator, &ctx); } } return rc; } /* * set a HTTP response header; table could be headers_out or err_headers_out */ static void oidc_http_hdr_table_set(const request_rec *r, apr_table_t *table, const char *name, const char *value) { if (value != NULL) { char *s_value = apr_pstrdup(r->pool, value); /* * sanitize the header value by replacing line feeds with spaces * just like the Apache header input algorithms do for incoming headers * * this makes it impossible to have line feeds in values but that is * compliant with RFC 7230 (and impossible for regular headers due to Apache's * parsing of headers anyway) and fixes a security vulnerability on * overwriting/setting outgoing headers when used in proxy mode */ char *p = NULL; while ((p = strchr(s_value, '\n'))) *p = OIDC_CHAR_SPACE; oidc_debug(r, "%s: %s", name, s_value); apr_table_set(table, name, s_value); } else { oidc_debug(r, "unset %s", name); apr_table_unset(table, name); } } /* * set a (regular) HTTP response header */ static void oidc_http_hdr_out_set(const request_rec *r, const char *name, const char *value) { oidc_http_hdr_table_set(r, r->headers_out, name, value); } /* * obtain a HTTP response header value */ static const char *oidc_http_hdr_out_get(const request_rec *r, const char *name) { return apr_table_get(r->headers_out, name); } /* * set a HTTP response header on all responses, included errors and redirects */ void oidc_http_hdr_err_out_add(const request_rec *r, const char *name, const char *value) { oidc_debug(r, "%s: %s", name, value); apr_table_add(r->err_headers_out, name, value); } /* * set an HTTP request header */ void oidc_http_hdr_in_set(const request_rec *r, const char *name, const char *value) { oidc_http_hdr_table_set(r, r->headers_in, name, value); } /* * obtain a HTTP request cookie value */ const char *oidc_http_hdr_in_cookie_get(const request_rec *r) { return oidc_http_hdr_in_get(r, OIDC_HTTP_HDR_COOKIE); } /* * set a HTTP request cookie value */ void oidc_http_hdr_in_cookie_set(const request_rec *r, const char *value) { oidc_http_hdr_in_set(r, OIDC_HTTP_HDR_COOKIE, value); } /* * obtain the User-Agent header value from the HTTP request */ const char *oidc_http_hdr_in_user_agent_get(const request_rec *r) { return oidc_http_hdr_in_get(r, OIDC_HTTP_HDR_USER_AGENT); } /* * obtain the X-Forwarded-For header value from the HTTP request */ const char *oidc_http_hdr_in_x_forwarded_for_get(const request_rec *r) { return oidc_http_hdr_in_get_left_most_only(r, OIDC_HTTP_HDR_X_FORWARDED_FOR, OIDC_STR_COMMA OIDC_STR_SPACE); } /* * obtain the Content-Type header value from the HTTP request */ const char *oidc_http_hdr_in_content_type_get(const request_rec *r) { return oidc_http_hdr_in_get(r, OIDC_HTTP_HDR_CONTENT_TYPE); } /* * obtain the Content-Length header value from the HTTP request */ const char *oidc_http_hdr_in_content_length_get(const request_rec *r) { return oidc_http_hdr_in_get(r, OIDC_HTTP_HDR_CONTENT_LENGTH); } /* * obtain the X-Requested-With header value from the HTTP request */ const char *oidc_http_hdr_in_x_requested_with_get(const request_rec *r) { return oidc_http_hdr_in_get(r, OIDC_HTTP_HDR_X_REQUESTED_WITH); } /* * obtain the Sec-Fetch-Mode header value from the HTTP request */ const char *oidc_http_hdr_in_sec_fetch_mode_get(const request_rec *r) { return oidc_http_hdr_in_get(r, OIDC_HTTP_HDR_SEC_FETCH_MODE); } /* * obtain the Sec-Fetch-Dest header value from the HTTP request */ const char *oidc_http_hdr_in_sec_fetch_dest_get(const request_rec *r) { return oidc_http_hdr_in_get(r, OIDC_HTTP_HDR_SEC_FETCH_DEST); } /* * obtain the Accept header value from the HTTP request */ const char *oidc_http_hdr_in_accept_get(const request_rec *r) { return oidc_http_hdr_in_get(r, OIDC_HTTP_HDR_ACCEPT); } /* * check if a specified value exists in the HTTP Accept request header */ apr_byte_t oidc_http_hdr_in_accept_contains(const request_rec *r, const char *needle) { return oidc_http_hdr_in_contains(r, OIDC_HTTP_HDR_ACCEPT, OIDC_STR_COMMA, OIDC_CHAR_SEMI_COLON, needle); } /* * obtain the Authorization header value from the HTTP request */ const char *oidc_http_hdr_in_authorization_get(const request_rec *r) { return oidc_http_hdr_in_get(r, OIDC_HTTP_HDR_AUTHORIZATION); } /* * obtain the X-Forwarded-Proto header value from the HTTP request */ const char *oidc_http_hdr_in_x_forwarded_proto_get(const request_rec *r) { return oidc_http_hdr_in_get_left_most_only(r, OIDC_HTTP_HDR_X_FORWARDED_PROTO, OIDC_STR_COMMA OIDC_STR_SPACE); } /* * obtain the X-Forwarded-Port header value from the HTTP request */ const char *oidc_http_hdr_in_x_forwarded_port_get(const request_rec *r) { return oidc_http_hdr_in_get_left_most_only(r, OIDC_HTTP_HDR_X_FORWARDED_PORT, OIDC_STR_COMMA OIDC_STR_SPACE); } /* * obtain the X-Forwarded-Host header value from the HTTP request */ const char *oidc_http_hdr_in_x_forwarded_host_get(const request_rec *r) { return oidc_http_hdr_in_get_left_most_only(r, OIDC_HTTP_HDR_X_FORWARDED_HOST, OIDC_STR_COMMA OIDC_STR_SPACE); } /* * obtain the Forwarded header value from the HTTP request */ const char *oidc_http_hdr_in_forwarded_get(const request_rec *r) { return oidc_http_hdr_in_get_left_most_only(r, OIDC_HTTP_HDR_FORWARDED, OIDC_STR_COMMA); } /* * obtain the Host header value from the HTTP request */ const char *oidc_http_hdr_in_host_get(const request_rec *r) { return oidc_http_hdr_in_get(r, OIDC_HTTP_HDR_HOST); } /* * obtain the traceparent header value from the HTTP request */ const char *oidc_http_hdr_in_traceparent_get(const request_rec *r) { return oidc_http_hdr_in_get(r, OIDC_HTTP_HDR_TRACE_PARENT); } /* * set the Location header value in the HTTP response */ void oidc_http_hdr_out_location_set(const request_rec *r, const char *value) { oidc_http_hdr_out_set(r, OIDC_HTTP_HDR_LOCATION, value); } /* * obtain the Location header value from the HTTP response */ const char *oidc_http_hdr_out_location_get(const request_rec *r) { return oidc_http_hdr_out_get(r, OIDC_HTTP_HDR_LOCATION); } /* * obtain a specified value from the Forwarded header in the HTTP request */ const char *oidc_http_hdr_forwarded_get(const request_rec *r, const char *elem) { const char *value = NULL; char *ptr = NULL; const char *item = apr_psprintf(r->pool, "%s=", elem); value = oidc_http_hdr_in_forwarded_get(r); value = oidc_util_strcasestr(value, item); if (value) { value += _oidc_strlen(item); ptr = _oidc_strstr(value, ";"); if (ptr) *ptr = '\0'; ptr = _oidc_strstr(value, " "); if (ptr) *ptr = '\0'; } return value ? apr_pstrdup(r->pool, value) : NULL; } /* * normalize a string for use as an HTTP Header Name. Any invalid * characters (per http://tools.ietf.org/html/rfc2616#section-4.2 and * http://tools.ietf.org/html/rfc2616#section-2.2) are replaced with * a dash ('-') character. */ char *oidc_http_hdr_normalize_name(const request_rec *r, const char *str) { /* token = 1* * CTL = * separators = "(" | ")" | "<" | ">" | "@" * | "," | ";" | ":" | "\" | <"> * | "/" | "[" | "]" | "?" | "=" * | "{" | "}" | SP | HT */ const char *separators = "()<>@,;:\\\"/[]?={} \t"; char *ns = apr_pstrdup(r->pool, str); size_t i; for (i = 0; i < _oidc_strlen(ns); i++) { if (ns[i] < 32 || ns[i] == 127) ns[i] = '-'; else if (strchr(separators, ns[i]) != NULL) ns[i] = '-'; } return ns; } /* buffer to hold HTTP call responses */ typedef struct oidc_curl_resp_data_ctx_t { request_rec *r; char *memory; size_t size; } oidc_curl_resp_data_ctx_t; /* maximum acceptable size of HTTP responses: 10 Mb */ #define OIDC_CURL_RESPONSE_DATA_SIZE_MAX 1024 * 1024 * 10 /* * callback for CURL to write bytes that come back from an HTTP call */ static size_t oidc_http_response_data(void *contents, size_t size, size_t nmemb, void *userp) { size_t realsize = size * nmemb; oidc_curl_resp_data_ctx_t *mem = (oidc_curl_resp_data_ctx_t *)userp; /* check if we don't run over the maximum buffer/memory size for HTTP responses */ if (mem->size + realsize > OIDC_CURL_RESPONSE_DATA_SIZE_MAX) { oidc_error( mem->r, "HTTP response larger than maximum allowed size: current size=%ld, additional size=%ld, max=%d", (long)mem->size, (long)realsize, OIDC_CURL_RESPONSE_DATA_SIZE_MAX); return 0; } /* allocate the new buffer for the current + new response bytes */ char *newptr = apr_palloc(mem->r->pool, mem->size + realsize + 1); if (newptr == NULL) { oidc_error(mem->r, "memory allocation for new buffer of %ld bytes failed", (long)(mem->size + realsize + 1)); return 0; } /* copy over the data from current memory plus the cURL buffer */ _oidc_memcpy(newptr, mem->memory, mem->size); _oidc_memcpy(&(newptr[mem->size]), contents, realsize); mem->size += realsize; mem->memory = newptr; mem->memory[mem->size] = 0; return realsize; } /* buffer to hold HTTP response headers */ typedef struct oidc_curl_resp_hdr_ctx_t { request_rec *r; apr_hash_t *hdrs; } oidc_curl_resp_hdr_ctx_t; /* * callback for CURL to write response headers that come back from an HTTP call */ static size_t oidc_http_response_header(char *buffer, size_t size, size_t nitems, void *userdata) { /* received header is nitems * size long in 'buffer' NOT ZERO TERMINATED */ oidc_curl_resp_hdr_ctx_t *ctx = (oidc_curl_resp_hdr_ctx_t *)userdata; char *hdr = NULL, *value = NULL, *h_name = NULL; apr_hash_index_t *hi = NULL; apr_ssize_t h_len = 0; int i = 0; /* see if there is a header to search for */ if ((ctx->hdrs == NULL) || (apr_hash_count(ctx->hdrs) == 0)) goto end; /* make hdr a \0 terminated string for easier processing */ hdr = apr_pstrndup(ctx->r->pool, buffer, nitems * size); /* search for a name: value pair */ value = _oidc_strstr(hdr, OIDC_STR_COLON); if (value == NULL) goto end; /* split the header name and value */ *value = '\0'; /* see if there's any header value characters at all after the colon */ if (_oidc_strlen(hdr) < nitems * size) { value++; /* skip spaces after the colon */ while (*value == ' ') value++; /* remove trailing /r/n */ i = _oidc_strlen(value) - 1; while ((value[i] == '\r') || (value[i] == '\n')) value[i--] = '\0'; } // TODO: would be faster to use all lowercase keys /* check if the caller is interested in the value of the current response header */ for (hi = apr_hash_first(NULL, ctx->hdrs); hi; hi = apr_hash_next(hi)) { apr_hash_this(hi, (const void **)&h_name, &h_len, NULL); if (_oidc_strnatcasecmp(hdr, h_name) == 0) { oidc_debug(ctx->r, "returning response header: %s: %s", h_name, value); apr_hash_set(ctx->hdrs, h_name, APR_HASH_KEY_STRING, apr_pstrdup(ctx->r->pool, value)); break; } } end: return nitems * size; } /* context structure for encoding parameters */ typedef struct oidc_http_encode_t { request_rec *r; char *encoded_params; } oidc_http_encode_t; /* * add a url-form-encoded name/value pair */ static int oidc_http_add_form_url_encoded_param(void *rec, const char *key, const char *value) { oidc_http_encode_t *ctx = (oidc_http_encode_t *)rec; oidc_debug(ctx->r, "processing: %s=%s", key, (_oidc_strncmp(key, OIDC_PROTO_CLIENT_SECRET, _oidc_strlen(OIDC_PROTO_CLIENT_SECRET)) == 0) ? "***" : (value ? value : "")); const char *sep = ctx->encoded_params ? OIDC_STR_AMP : ""; ctx->encoded_params = apr_psprintf(ctx->r->pool, "%s%s%s=%s", ctx->encoded_params ? ctx->encoded_params : "", sep, oidc_http_url_encode(ctx->r, key), oidc_http_url_encode(ctx->r, value)); return 1; } /* * construct a URL with query parameters */ char *oidc_http_query_encoded_url(request_rec *r, const char *url, const apr_table_t *params) { char *result = NULL; if (url == NULL) { oidc_error(r, "URL is NULL"); return NULL; } if ((params != NULL) && (apr_table_elts(params)->nelts > 0)) { oidc_http_encode_t data = {r, NULL}; apr_table_do(oidc_http_add_form_url_encoded_param, &data, params, NULL); const char *sep = NULL; if (data.encoded_params) sep = strchr(url, OIDC_CHAR_QUERY) != NULL ? OIDC_STR_AMP : OIDC_STR_QUERY; result = apr_psprintf(r->pool, "%s%s%s", url, sep ? sep : "", data.encoded_params ? data.encoded_params : ""); } else { result = apr_pstrdup(r->pool, url); } oidc_debug(r, "url=%s", result); return result; } /* * construct form-encoded POST data */ char *oidc_http_form_encoded_data(request_rec *r, const apr_table_t *params) { char *data = NULL; if ((params != NULL) && (apr_table_elts(params)->nelts > 0)) { oidc_http_encode_t encode_data = {r, NULL}; apr_table_do(oidc_http_add_form_url_encoded_param, &encode_data, params, NULL); data = encode_data.encoded_params; } oidc_debug(r, "data=%s", data); return data; } /* * call curl_easy_setopt with error checking and reporting */ #define OIDC_HTTP_CURL_SETOPT_PARMS(r, curl, code, option, ...) \ code = curl_easy_setopt(curl, option, __VA_ARGS__); \ if (code != CURLE_OK) \ oidc_error(r, "curl_easy_setopt(%s) failed with: %s", #option, curl_easy_strerror(code)) #define OIDC_HTTP_CURL_SETOPT(...) OIDC_HTTP_CURL_SETOPT_PARMS(r, curl, code, __VA_ARGS__) /* * set libcurl SSL options */ #define OIDC_CURLOPT_SSL_OPTIONS_ENV_VAR_NAME "CURLOPT_SSL_OPTIONS" #define OIDC_HTTP_CURL_SETOPT_SSL(option, value) \ if (_oidc_strstr(env_var_value, #value) != NULL) { \ oidc_debug(r, "curl_easy_setopt(%s): %s (%d)", #option, #value, value); \ OIDC_HTTP_CURL_SETOPT(option, value); \ } static void oidc_http_set_curl_ssl_options(request_rec *r, CURL *curl) { // NB: the variable names r, curl, code and env_var_value are used in the OIDC_HTTP_CURL_SETOPT_SSL macro const char *env_var_value = NULL; CURLcode code = CURLE_OK; if (r->subprocess_env != NULL) env_var_value = apr_table_get(r->subprocess_env, OIDC_CURLOPT_SSL_OPTIONS_ENV_VAR_NAME); if (env_var_value == NULL) return; oidc_debug(r, "SSL options environment variable %s=%s found", OIDC_CURLOPT_SSL_OPTIONS_ENV_VAR_NAME, env_var_value); #if LIBCURL_VERSION_NUM >= 0x071900 OIDC_HTTP_CURL_SETOPT_SSL(CURLOPT_SSL_OPTIONS, CURLSSLOPT_ALLOW_BEAST); #endif #if LIBCURL_VERSION_NUM >= 0x072c00 OIDC_HTTP_CURL_SETOPT_SSL(CURLOPT_SSL_OPTIONS, CURLSSLOPT_NO_REVOKE) #endif #if LIBCURL_VERSION_NUM >= 0x074400 OIDC_HTTP_CURL_SETOPT_SSL(CURLOPT_SSL_OPTIONS, CURLSSLOPT_NO_PARTIALCHAIN) #endif #if LIBCURL_VERSION_NUM >= 0x074600 OIDC_HTTP_CURL_SETOPT_SSL(CURLOPT_SSL_OPTIONS, CURLSSLOPT_REVOKE_BEST_EFFORT) #endif #if LIBCURL_VERSION_NUM >= 0x074700 OIDC_HTTP_CURL_SETOPT_SSL(CURLOPT_SSL_OPTIONS, CURLSSLOPT_NATIVE_CA) #endif #if LIBCURL_VERSION_NUM >= 0x072200 OIDC_HTTP_CURL_SETOPT_SSL(CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1_0) OIDC_HTTP_CURL_SETOPT_SSL(CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1_1) OIDC_HTTP_CURL_SETOPT_SSL(CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1_2) #endif #if LIBCURL_VERSION_NUM >= 0x073400 OIDC_HTTP_CURL_SETOPT_SSL(CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1_3) #endif #if LIBCURL_VERSION_NUM >= 0x073600 OIDC_HTTP_CURL_SETOPT_SSL(CURLOPT_SSLVERSION, CURL_SSLVERSION_MAX_TLSv1_0) OIDC_HTTP_CURL_SETOPT_SSL(CURLOPT_SSLVERSION, CURL_SSLVERSION_MAX_TLSv1_1) OIDC_HTTP_CURL_SETOPT_SSL(CURLOPT_SSLVERSION, CURL_SSLVERSION_MAX_TLSv1_2) OIDC_HTTP_CURL_SETOPT_SSL(CURLOPT_SSLVERSION, CURL_SSLVERSION_MAX_TLSv1_3) #endif } #define OIDC_USER_AGENT_ENV_VAR "OIDC_USER_AGENT" /* * construct our User-Agent header for outgoing requests */ static const char *oidc_http_user_agent(request_rec *r) { const char *s_useragent = apr_table_get(r->subprocess_env, OIDC_USER_AGENT_ENV_VAR); if (s_useragent == NULL) { s_useragent = apr_psprintf(r->pool, "[%s:%u:%lu] %s", r->server->server_hostname, r->connection->local_addr->port, (unsigned long)getpid(), NAMEVERSION); s_useragent = apr_psprintf(r->pool, "%s libcurl-%s %s", s_useragent, LIBCURL_VERSION, oidc_util_openssl_version(r->pool)); } return s_useragent; } #define OIDC_CURL_INTERFACE_ENV_VAR "OIDC_CURL_INTERFACE" /* * construct our local address/interface for outgoing requests */ static const char *oidc_http_interface(request_rec *r) { return apr_table_get(r->subprocess_env, OIDC_CURL_INTERFACE_ENV_VAR); } /* * execute a HTTP (GET or POST) request */ static apr_byte_t oidc_http_request(request_rec *r, const char *url, const char *data, const char *content_type, const char *basic_auth, const char *access_token, const char *dpop, int ssl_validate_server, char **response, long *response_code, apr_hash_t *response_hdrs, oidc_http_timeout_t *http_timeout, const oidc_http_outgoing_proxy_t *outgoing_proxy, const apr_array_header_t *pass_cookies, const char *ssl_cert, const char *ssl_key, const char *ssl_key_pwd) { // NB: the variable names r, curl, and code are used in the OIDC_HTTP_CURL_SETOPT macro CURL *curl = NULL; CURLcode code = CURLE_OK; char curl_err[CURL_ERROR_SIZE]; oidc_curl_resp_data_ctx_t d_buf = {r, NULL, 0}; oidc_curl_resp_hdr_ctx_t h_buf = {r, response_hdrs}; struct curl_slist *h_list = NULL; int i = 0; CURLcode res = CURLE_OK; long http_code = 0; apr_byte_t rv = FALSE; oidc_cfg_t *c = ap_get_module_config(r->server->module_config, &auth_openidc_module); /* do some logging about the inputs */ oidc_debug(r, "url=%s, data=%s, content_type=%s, basic_auth=%s, access_token=%s, dpop=%s, ssl_validate_server=%d, " "request_timeout=%d, connect_timeout=%d, retries=%d, retry_interval=%d, outgoing_proxy=%s:%s:%d, " "pass_cookies=%pp, ssl_cert=%s, ssl_key=%s, ssl_key_pwd=%s", url, data, content_type, basic_auth ? "****" : "null", access_token, dpop, ssl_validate_server, http_timeout->request_timeout, http_timeout->connect_timeout, http_timeout->retries, http_timeout->retry_interval, outgoing_proxy->host_port, outgoing_proxy->username_password ? "****" : "(null)", (int)outgoing_proxy->auth_type, pass_cookies, ssl_cert, ssl_key, ssl_key_pwd ? "****" : "(null)"); curl = curl_easy_init(); if (curl == NULL) { oidc_error(r, "curl_easy_init() error"); goto end; } /* set the error buffer as empty before performing a request */ curl_err[0] = 0; /* some of these are not really required */ OIDC_HTTP_CURL_SETOPT(CURLOPT_HEADER, 0L); OIDC_HTTP_CURL_SETOPT(CURLOPT_NOPROGRESS, 1L); OIDC_HTTP_CURL_SETOPT(CURLOPT_NOSIGNAL, 1L); OIDC_HTTP_CURL_SETOPT(CURLOPT_ERRORBUFFER, curl_err); OIDC_HTTP_CURL_SETOPT(CURLOPT_FOLLOWLOCATION, 1L); OIDC_HTTP_CURL_SETOPT(CURLOPT_MAXREDIRS, 5L); /* set the timeouts */ OIDC_HTTP_CURL_SETOPT(CURLOPT_TIMEOUT, http_timeout->request_timeout); OIDC_HTTP_CURL_SETOPT(CURLOPT_CONNECTTIMEOUT, http_timeout->connect_timeout); /* setup the buffer where the response data will be written to */ OIDC_HTTP_CURL_SETOPT(CURLOPT_WRITEFUNCTION, oidc_http_response_data); /* coverity[bad_sizeof] */ OIDC_HTTP_CURL_SETOPT(CURLOPT_WRITEDATA, &d_buf); /* setup the buffer where the response headers will be written to */ OIDC_HTTP_CURL_SETOPT(CURLOPT_HEADERFUNCTION, oidc_http_response_header); /* coverity[bad_sizeof] */ OIDC_HTTP_CURL_SETOPT(CURLOPT_HEADERDATA, &h_buf); #ifndef LIBCURL_NO_CURLPROTO #if LIBCURL_VERSION_NUM >= 0x075500 OIDC_HTTP_CURL_SETOPT(CURLOPT_REDIR_PROTOCOLS_STR, "http,https"); OIDC_HTTP_CURL_SETOPT(CURLOPT_PROTOCOLS_STR, "http,https"); #else OIDC_HTTP_CURL_SETOPT(CURLOPT_REDIR_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS); OIDC_HTTP_CURL_SETOPT(CURLOPT_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS); #endif #endif /* set the options for validating the SSL server certificate that the remote site presents */ OIDC_HTTP_CURL_SETOPT(CURLOPT_SSL_VERIFYPEER, (ssl_validate_server != FALSE ? 1L : 0L)); OIDC_HTTP_CURL_SETOPT(CURLOPT_SSL_VERIFYHOST, (ssl_validate_server != FALSE ? 2L : 0L)); oidc_http_set_curl_ssl_options(r, curl); if (oidc_cfg_ca_bundle_path_get(c) != NULL) { OIDC_HTTP_CURL_SETOPT(CURLOPT_CAINFO, oidc_cfg_ca_bundle_path_get(c)); } #ifdef WIN32 else { DWORD buflen; char *ptr = NULL; char *retval = (char *)malloc(sizeof(TCHAR) * (MAX_PATH + 1)); retval[0] = '\0'; buflen = SearchPath(NULL, "curl-ca-bundle.crt", NULL, MAX_PATH + 1, retval, &ptr); if (buflen > 0) { OIDC_HTTP_CURL_SETOPT(CURLOPT_CAINFO, retval); } else { oidc_warn(r, "no curl-ca-bundle.crt file found in path"); } free(retval); } #endif /* identify this HTTP client */ const char *s_useragent = oidc_http_user_agent(r); if ((s_useragent != NULL) && (_oidc_strcmp(s_useragent, "") != 0)) { oidc_debug(r, "set HTTP request header User-Agent to: %s", s_useragent); OIDC_HTTP_CURL_SETOPT(CURLOPT_USERAGENT, s_useragent); } /* set the local interface if defined */ const char *s_interface = oidc_http_interface(r); if ((s_interface != NULL) && (_oidc_strcmp(s_interface, "") != 0)) { #if LIBCURL_VERSION_NUM >= 0x073000 oidc_debug(r, "set local interface to: %s", s_interface); OIDC_HTTP_CURL_SETOPT(CURLOPT_INTERFACE, s_interface); #else oidc_warn( r, "local interface is configured to %s, but the cURL version in use does not support setting this", s_interface); #endif } /* set optional outgoing proxy for the local network */ if (outgoing_proxy->host_port) { OIDC_HTTP_CURL_SETOPT(CURLOPT_PROXY, outgoing_proxy->host_port); if (outgoing_proxy->username_password) { OIDC_HTTP_CURL_SETOPT(CURLOPT_PROXYUSERPWD, outgoing_proxy->username_password); } if (outgoing_proxy->auth_type != OIDC_CONFIG_POS_INT_UNSET) { OIDC_HTTP_CURL_SETOPT(CURLOPT_PROXYAUTH, outgoing_proxy->auth_type); } } /* see if we need to add token in the Bearer/DPoP Authorization header */ if (access_token != NULL) { h_list = curl_slist_append(h_list, apr_psprintf(r->pool, "%s: %s %s", OIDC_HTTP_HDR_AUTHORIZATION, dpop ? "DPoP" : "Bearer", access_token)); } /* see if we need to perform HTTP basic authentication to the remote site */ if (basic_auth != NULL) { OIDC_HTTP_CURL_SETOPT(CURLOPT_HTTPAUTH, CURLAUTH_BASIC); OIDC_HTTP_CURL_SETOPT(CURLOPT_USERPWD, basic_auth); } if (ssl_cert != NULL) { OIDC_HTTP_CURL_SETOPT(CURLOPT_SSLCERT, ssl_cert); } if (ssl_key != NULL) { OIDC_HTTP_CURL_SETOPT(CURLOPT_SSLKEY, ssl_key); } if (ssl_key_pwd != NULL) { OIDC_HTTP_CURL_SETOPT(CURLOPT_KEYPASSWD, ssl_key_pwd); } if (data != NULL) { /* set POST data */ OIDC_HTTP_CURL_SETOPT(CURLOPT_POSTFIELDS, data); /* set HTTP method to POST */ OIDC_HTTP_CURL_SETOPT(CURLOPT_POST, 1); } if (content_type != NULL) { /* set content type */ h_list = curl_slist_append(h_list, apr_psprintf(r->pool, "%s: %s", OIDC_HTTP_HDR_CONTENT_TYPE, content_type)); } const char *traceparent = oidc_http_hdr_in_traceparent_get(r); if (traceparent && oidc_cfg_trace_parent_get(c) != OIDC_TRACE_PARENT_OFF) { oidc_debug(r, "propagating traceparent header: %s", traceparent); h_list = curl_slist_append(h_list, apr_psprintf(r->pool, "%s: %s", OIDC_HTTP_HDR_TRACE_PARENT, traceparent)); } if (dpop != NULL) { oidc_debug(r, "appending DPoP header (len=%d)", (int)_oidc_strlen(dpop)); h_list = curl_slist_append(h_list, apr_psprintf(r->pool, "%s: %s", OIDC_HTTP_HDR_DPOP, dpop)); } /* see if we need to add any custom headers */ if (h_list != NULL) { OIDC_HTTP_CURL_SETOPT(CURLOPT_HTTPHEADER, h_list); } if (pass_cookies != NULL) { /* gather cookies that we need to pass on from the incoming request */ char *cookie_string = NULL; for (i = 0; i < pass_cookies->nelts; i++) { const char *cookie_name = APR_ARRAY_IDX(pass_cookies, i, const char *); char *cookie_value = oidc_http_get_cookie(r, cookie_name); if (cookie_value != NULL) { cookie_string = (cookie_string == NULL) ? apr_psprintf(r->pool, "%s=%s", cookie_name, cookie_value) : apr_psprintf(r->pool, "%s; %s=%s", cookie_string, cookie_name, cookie_value); } } /* see if we need to pass any cookies */ if (cookie_string != NULL) { oidc_debug(r, "passing browser cookies on backend call: %s", cookie_string); OIDC_HTTP_CURL_SETOPT(CURLOPT_COOKIE, cookie_string); } } /* set the target URL */ OIDC_HTTP_CURL_SETOPT(CURLOPT_URL, url); /* call it and record the result */ for (i = 0; i <= http_timeout->retries; i++) { res = curl_easy_perform(curl); if (res == CURLE_OK) { rv = TRUE; break; } if (res == CURLE_OPERATION_TIMEDOUT) { /* in case of a request/transfer timeout (which includes the connect timeout) we'll not * retry */ oidc_error(r, "curl_easy_perform failed with a timeout for %s: [%s]; won't retry", url, curl_err[0] ? curl_err : ""); OIDC_METRICS_COUNTER_INC_VALUE(r, c, OM_PROVIDER_CONNECT_ERROR, curl_err[0] ? curl_err : "timeout") break; } oidc_error(r, "curl_easy_perform(%d/%d) failed for %s with: [%s]", i + 1, http_timeout->retries + 1, url, curl_err[0] ? curl_err : ""); OIDC_METRICS_COUNTER_INC_VALUE(r, c, OM_PROVIDER_CONNECT_ERROR, curl_err[0] ? curl_err : "undefined") /* in case of a connectivity/network glitch we'll back off before retrying */ if (i < http_timeout->retries) apr_sleep(apr_time_from_msec(http_timeout->retry_interval)); } if (rv == FALSE) goto end; curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code); oidc_debug(r, "HTTP response code=%ld", http_code); OIDC_METRICS_COUNTER_INC_VALUE(r, c, OM_PROVIDER_HTTP_RESPONSE_CODE, apr_psprintf(r->pool, "%ld", http_code)); *response = apr_pstrmemdup(r->pool, d_buf.memory, d_buf.size); if (response_code) *response_code = http_code; /* set and log the response */ oidc_debug(r, "response=%s", *response ? *response : ""); end: /* cleanup and return the result */ if (h_list != NULL) curl_slist_free_all(h_list); if (curl != NULL) curl_easy_cleanup(curl); return rv; } /* * execute HTTP GET request */ apr_byte_t oidc_http_get(request_rec *r, const char *url, const apr_table_t *params, const char *basic_auth, const char *access_token, const char *dpop, int ssl_validate_server, char **response, long *response_code, apr_hash_t *response_hdrs, oidc_http_timeout_t *http_timeout, const oidc_http_outgoing_proxy_t *outgoing_proxy, const apr_array_header_t *pass_cookies, const char *ssl_cert, const char *ssl_key, const char *ssl_key_pwd) { char *query_url = oidc_http_query_encoded_url(r, url, params); return oidc_http_request(r, query_url, NULL, NULL, basic_auth, access_token, dpop, ssl_validate_server, response, response_code, response_hdrs, http_timeout, outgoing_proxy, pass_cookies, ssl_cert, ssl_key, ssl_key_pwd); } /* * execute HTTP POST request with form-encoded data */ apr_byte_t oidc_http_post_form(request_rec *r, const char *url, const apr_table_t *params, const char *basic_auth, const char *access_token, const char *dpop, int ssl_validate_server, char **response, long *response_code, apr_hash_t *response_hdrs, oidc_http_timeout_t *http_timeout, const oidc_http_outgoing_proxy_t *outgoing_proxy, const apr_array_header_t *pass_cookies, const char *ssl_cert, const char *ssl_key, const char *ssl_key_pwd) { char *data = oidc_http_form_encoded_data(r, params); return oidc_http_request(r, url, data, OIDC_HTTP_CONTENT_TYPE_FORM_ENCODED, basic_auth, access_token, dpop, ssl_validate_server, response, response_code, response_hdrs, http_timeout, outgoing_proxy, pass_cookies, ssl_cert, ssl_key, ssl_key_pwd); } /* * execute HTTP POST request with JSON-encoded data */ apr_byte_t oidc_http_post_json(request_rec *r, const char *url, json_t *json, const char *basic_auth, const char *access_token, const char *dpop, int ssl_validate_server, char **response, long *response_code, apr_hash_t *response_hdrs, oidc_http_timeout_t *http_timeout, const oidc_http_outgoing_proxy_t *outgoing_proxy, const apr_array_header_t *pass_cookies, const char *ssl_cert, const char *ssl_key, const char *ssl_key_pwd) { char *data = json != NULL ? oidc_util_encode_json(r->pool, json, JSON_PRESERVE_ORDER | JSON_COMPACT) : NULL; return oidc_http_request(r, url, data, OIDC_HTTP_CONTENT_TYPE_JSON, basic_auth, access_token, dpop, ssl_validate_server, response, response_code, response_hdrs, http_timeout, outgoing_proxy, pass_cookies, ssl_cert, ssl_key, ssl_key_pwd); } /* * get the current path from the request in a normalized way */ static char *oidc_http_get_path(request_rec *r) { size_t i; char *p; p = r->parsed_uri.path; if ((p == NULL) || (p[0] == '\0')) return apr_pstrdup(r->pool, OIDC_STR_FORWARD_SLASH); for (i = _oidc_strlen(p) - 1; i > 0; i--) if (p[i] == OIDC_CHAR_FORWARD_SLASH) break; return apr_pstrndup(r->pool, p, i + 1); } /* * get the cookie path setting and check that it matches the request path; cook it up if it is not set */ static const char *oidc_http_get_cookie_path(request_rec *r) { const char *rv = NULL; char *requestPath = oidc_http_get_path(r); const char *cookie_path = oidc_cfg_dir_cookie_path_get(r); if (cookie_path != NULL) { if (_oidc_strncmp(cookie_path, requestPath, _oidc_strlen(cookie_path)) == 0) rv = cookie_path; else { oidc_warn(r, "" OIDCCookiePath " (%s) is not a substring of request path, using request path (%s) for cookie", cookie_path, requestPath); rv = requestPath; } } else { rv = requestPath; } return rv; } #define OIDC_HTTP_COOKIE_FLAG_DOMAIN "Domain" #define OIDC_HTTP_COOKIE_FLAG_PATH "Path" #define OIDC_HTTP_COOKIE_FLAG_EXPIRES "Expires" #define OIDC_HTTP_COOKIE_FLAG_SECURE "Secure" #define OIDC_HTTP_COOKIE_FLAG_HTTP_ONLY "HttpOnly" #define OIDC_HTTP_COOKIE_MAX_SIZE 4093 #define OIDC_SET_COOKIE_APPEND_ENV_VAR "OIDC_SET_COOKIE_APPEND" /* * obtain the value configured in the OIDC_SET_COOKIE_APPEND environment variable * which is to be added to the HTTP Set-Cookie response header */ static const char *oidc_http_set_cookie_append_value(request_rec *r) { const char *env_var_value = NULL; if (r->subprocess_env != NULL) env_var_value = apr_table_get(r->subprocess_env, OIDC_SET_COOKIE_APPEND_ENV_VAR); if (env_var_value == NULL) { oidc_debug(r, "no cookie append environment variable %s found", OIDC_SET_COOKIE_APPEND_ENV_VAR); return NULL; } oidc_debug(r, "cookie append environment variable %s=%s found", OIDC_SET_COOKIE_APPEND_ENV_VAR, env_var_value); return env_var_value; } /* * set a cookie in the HTTP response headers */ void oidc_http_set_cookie(request_rec *r, const char *cookieName, const char *cookieValue, apr_time_t expires, const char *ext) { oidc_cfg_t *c = ap_get_module_config(r->server->module_config, &auth_openidc_module); char *headerString = NULL; char *expiresString = NULL; const char *appendString = NULL; /* see if we need to clear the cookie */ if (_oidc_strcmp(cookieValue, "") == 0) expires = 0; /* construct the expire value */ if (expires != -1) { expiresString = (char *)apr_pcalloc(r->pool, APR_RFC822_DATE_LEN); if (apr_rfc822_date(expiresString, expires) != APR_SUCCESS) { oidc_error(r, "could not set cookie expiry date"); } } /* construct the cookie value */ headerString = apr_psprintf(r->pool, "%s=%s", cookieName, cookieValue); headerString = apr_psprintf(r->pool, "%s; %s=%s", headerString, OIDC_HTTP_COOKIE_FLAG_PATH, oidc_http_get_cookie_path(r)); if (expiresString != NULL) headerString = apr_psprintf(r->pool, "%s; %s=%s", headerString, OIDC_HTTP_COOKIE_FLAG_EXPIRES, expiresString); if (oidc_cfg_cookie_domain_get(c) != NULL) headerString = apr_psprintf(r->pool, "%s; %s=%s", headerString, OIDC_HTTP_COOKIE_FLAG_DOMAIN, oidc_cfg_cookie_domain_get(c)); if (oidc_util_request_is_secure(r, c)) headerString = apr_psprintf(r->pool, "%s; %s", headerString, OIDC_HTTP_COOKIE_FLAG_SECURE); if (oidc_cfg_cookie_http_only_get(c) != FALSE) headerString = apr_psprintf(r->pool, "%s; %s", headerString, OIDC_HTTP_COOKIE_FLAG_HTTP_ONLY); appendString = oidc_http_set_cookie_append_value(r); if (appendString != NULL) headerString = apr_psprintf(r->pool, "%s; %s", headerString, appendString); else if (ext != NULL) headerString = apr_psprintf(r->pool, "%s; %s", headerString, ext); /* sanity check on overall cookie value size */ if (_oidc_strlen(headerString) > OIDC_HTTP_COOKIE_MAX_SIZE) { oidc_warn(r, "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!", (int)_oidc_strlen(headerString), OIDC_HTTP_COOKIE_MAX_SIZE); } /* use r->err_headers_out so we always print our headers (even on 302 redirect) - headers_out only * prints on 2xx responses */ oidc_http_hdr_err_out_add(r, OIDC_HTTP_HDR_SET_COOKIE, headerString); } /* * get a cookie from the HTTP request */ char *oidc_http_get_cookie(request_rec *r, const char *cookieName) { char *cookie = NULL; char *tokenizerCtx = NULL; char *rv = NULL; /* get the Cookie value */ char *cookies = apr_pstrdup(r->pool, oidc_http_hdr_in_cookie_get(r)); if (cookies != NULL) { /* tokenize on ; to find the cookie we want */ cookie = apr_strtok(cookies, OIDC_STR_SEMI_COLON, &tokenizerCtx); while (cookie != NULL) { while (*cookie == OIDC_CHAR_SPACE) cookie++; /* see if we've found the cookie that we're looking for */ if ((_oidc_strncmp(cookie, cookieName, _oidc_strlen(cookieName)) == 0) && (cookie[_oidc_strlen(cookieName)] == OIDC_CHAR_EQUAL)) { /* skip to the meat of the parameter (the value after the '=') */ cookie += (_oidc_strlen(cookieName) + 1); rv = apr_pstrdup(r->pool, cookie); break; } /* go to the next cookie */ cookie = apr_strtok(NULL, OIDC_STR_SEMI_COLON, &tokenizerCtx); } } /* log what we've found */ oidc_debug(r, "returning \"%s\" = %s", cookieName, rv ? apr_psprintf(r->pool, "\"%s\"", rv) : ""); return rv; } #define OIDC_HTTP_COOKIE_CHUNKS_SEPARATOR "_" #define OIDC_HTTP_COOKIE_CHUNKS_POSTFIX "chunks" /* * get the name of the cookie that contains the number of chunks */ static char *oidc_http_get_chunk_count_name(request_rec *r, const char *cookieName) { return apr_psprintf(r->pool, "%s%s%s", cookieName, OIDC_HTTP_COOKIE_CHUNKS_SEPARATOR, OIDC_HTTP_COOKIE_CHUNKS_POSTFIX); } /* * get the number of cookie chunks set by the browser */ static int oidc_http_get_chunked_count(request_rec *r, const char *cookieName) { int chunkCount = 0; char *chunkCountValue = oidc_http_get_cookie(r, oidc_http_get_chunk_count_name(r, cookieName)); chunkCount = _oidc_str_to_int(chunkCountValue, 0); return chunkCount; } /* * get the name of a chunk */ static char *oidc_http_get_chunk_cookie_name(request_rec *r, const char *cookieName, int i) { return apr_psprintf(r->pool, "%s%s%d", cookieName, OIDC_HTTP_COOKIE_CHUNKS_SEPARATOR, i); } /* * get a cookie value that is split over a number of chunked cookies */ char *oidc_http_get_chunked_cookie(request_rec *r, const char *cookieName, int chunkSize) { char *cookieValue = NULL, *chunkValue = NULL; int chunkCount = 0, i = 0; if (chunkSize == 0) return oidc_http_get_cookie(r, cookieName); chunkCount = oidc_http_get_chunked_count(r, cookieName); if (chunkCount == 0) return oidc_http_get_cookie(r, cookieName); if ((chunkCount < 0) || (chunkCount > 99)) { oidc_warn(r, "chunk count out of bounds: %d", chunkCount); return NULL; } for (i = 0; i < chunkCount; i++) { chunkValue = oidc_http_get_cookie(r, oidc_http_get_chunk_cookie_name(r, cookieName, i)); if (chunkValue == NULL) { oidc_warn(r, "could not find chunk %d; aborting", i); break; } cookieValue = apr_psprintf(r->pool, "%s%s", cookieValue ? cookieValue : "", chunkValue); } return cookieValue; } /* * unset all chunked cookies, including the counter cookie, if they exist */ static void oidc_http_clear_chunked_cookie(request_rec *r, const char *cookieName, apr_time_t expires, const char *ext) { int i = 0; int chunkCount = oidc_http_get_chunked_count(r, cookieName); if (chunkCount > 0) { for (i = 0; i < chunkCount; i++) oidc_http_set_cookie(r, oidc_http_get_chunk_cookie_name(r, cookieName, i), "", expires, ext); oidc_http_set_cookie(r, oidc_http_get_chunk_count_name(r, cookieName), "", expires, ext); } } /* * set a cookie value that is split over a number of chunked cookies */ void oidc_http_set_chunked_cookie(request_rec *r, const char *cookieName, const char *cookieValue, apr_time_t expires, int chunkSize, const char *ext) { int i = 0; int cookieLength = _oidc_strlen(cookieValue); char *chunkValue = NULL; /* see if we need to chunk at all */ if ((chunkSize == 0) || ((cookieLength > 0) && (cookieLength < chunkSize))) { oidc_http_set_cookie(r, cookieName, cookieValue, expires, ext); oidc_http_clear_chunked_cookie(r, cookieName, expires, ext); return; } /* see if we need to clear a possibly chunked cookie */ if (cookieLength == 0) { oidc_http_set_cookie(r, cookieName, "", expires, ext); oidc_http_clear_chunked_cookie(r, cookieName, expires, ext); return; } /* set a chunked cookie */ int chunkCountValue = cookieLength / chunkSize + 1; const char *ptr = cookieValue; for (i = 0; i < chunkCountValue; i++) { chunkValue = apr_pstrndup(r->pool, ptr, chunkSize); ptr += chunkSize; oidc_http_set_cookie(r, oidc_http_get_chunk_cookie_name(r, cookieName, i), chunkValue, expires, ext); } oidc_http_set_cookie(r, oidc_http_get_chunk_count_name(r, cookieName), apr_psprintf(r->pool, "%d", chunkCountValue), expires, ext); oidc_http_set_cookie(r, cookieName, "", expires, ext); } /* * construct the HTTP outgoing proxy options */ const char **oidc_http_proxy_auth_options(void) { static const char *options[] = {OIDC_HTTP_PROXY_AUTH_BASIC, OIDC_HTTP_PROXY_AUTH_DIGEST, OIDC_HTTP_PROXY_AUTH_NTLM, OIDC_HTTP_PROXY_AUTH_ANY, #ifdef CURLAUTH_NEGOTIATE OIDC_HTTP_PROXY_AUTH_NEGOTIATE, #endif NULL}; return options; } /* * return the CURL enum value for the HTTP outgoing proxy options */ unsigned long oidc_http_proxy_s2auth(const char *arg) { if (_oidc_strcmp(arg, OIDC_HTTP_PROXY_AUTH_BASIC) == 0) return CURLAUTH_BASIC; if (_oidc_strcmp(arg, OIDC_HTTP_PROXY_AUTH_DIGEST) == 0) return CURLAUTH_DIGEST; if (_oidc_strcmp(arg, OIDC_HTTP_PROXY_AUTH_NTLM) == 0) return CURLAUTH_NTLM; if (_oidc_strcmp(arg, OIDC_HTTP_PROXY_AUTH_ANY) == 0) return CURLAUTH_ANY; #ifdef CURLAUTH_NEGOTIATE if (_oidc_strcmp(arg, OIDC_HTTP_PROXY_AUTH_NEGOTIATE) == 0) return CURLAUTH_NEGOTIATE; #endif return CURLAUTH_NONE; } /* * initialize the HTTP/cURL environment */ void oidc_http_init(void) { curl_global_init(CURL_GLOBAL_ALL); } /* * clean up the HTTP/cURL environment */ void oidc_http_cleanup(void) { curl_global_cleanup(); } mod_auth_openidc-2.4.16.10/src/http.h000066400000000000000000000225501476721736500172710ustar00rootroot00000000000000/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ /*************************************************************************** * Copyright (C) 2017-2025 ZmartZone Holding BV * Copyright (C) 2013-2017 Ping Identity Corporation * All rights reserved. * * DISCLAIMER OF WARRANTIES: * * THE SOFTWARE PROVIDED HEREUNDER IS PROVIDED ON AN "AS IS" BASIS, WITHOUT * ANY WARRANTIES OR REPRESENTATIONS EXPRESS, IMPLIED OR STATUTORY; INCLUDING, * WITHOUT LIMITATION, WARRANTIES OF QUALITY, PERFORMANCE, NONINFRINGEMENT, * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. NOR ARE THERE ANY * WARRANTIES CREATED BY A COURSE OR DEALING, COURSE OF PERFORMANCE OR TRADE * USAGE. FURTHERMORE, THERE ARE NO WARRANTIES THAT THE SOFTWARE WILL MEET * YOUR NEEDS OR BE FREE FROM ERRORS, OR THAT THE OPERATION OF THE SOFTWARE * WILL BE UNINTERRUPTED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * @Author: Hans Zandbelt - hans.zandbelt@openidc.com */ #ifndef _MOD_AUTH_OPENIDC_HTTP_H_ #define _MOD_AUTH_OPENIDC_HTTP_H_ #include #include #include // clang-format off #include #include #include // clang-format on #include #define OIDC_HTTP_CONTENT_TYPE_JSON "application/json" #define OIDC_HTTP_CONTENT_TYPE_JWT "application/jwt" #define OIDC_HTTP_CONTENT_TYPE_FORM_ENCODED "application/x-www-form-urlencoded" #define OIDC_HTTP_CONTENT_TYPE_IMAGE_PNG "image/png" #define OIDC_HTTP_CONTENT_TYPE_TEXT_HTML "text/html" #define OIDC_HTTP_CONTENT_TYPE_APP_XHTML_XML "application/xhtml+xml" #define OIDC_HTTP_CONTENT_TYPE_ANY "*/*" /* HTTP header constants */ #define OIDC_HTTP_HDR_COOKIE "Cookie" #define OIDC_HTTP_HDR_SET_COOKIE "Set-Cookie" #define OIDC_HTTP_HDR_USER_AGENT "User-Agent" #define OIDC_HTTP_HDR_X_FORWARDED_FOR "X-Forwarded-For" #define OIDC_HTTP_HDR_CONTENT_TYPE "Content-Type" #define OIDC_HTTP_HDR_CONTENT_LENGTH "Content-Length" #define OIDC_HTTP_HDR_X_REQUESTED_WITH "X-Requested-With" #define OIDC_HTTP_HDR_SEC_FETCH_MODE "Sec-Fetch-Mode" #define OIDC_HTTP_HDR_SEC_FETCH_DEST "Sec-Fetch-Dest" #define OIDC_HTTP_HDR_ACCEPT "Accept" #define OIDC_HTTP_HDR_AUTHORIZATION "Authorization" #define OIDC_HTTP_HDR_X_FORWARDED_PROTO "X-Forwarded-Proto" #define OIDC_HTTP_HDR_X_FORWARDED_PORT "X-Forwarded-Port" #define OIDC_HTTP_HDR_X_FORWARDED_HOST "X-Forwarded-Host" #define OIDC_HTTP_HDR_FORWARDED "Forwarded" #define OIDC_HTTP_HDR_HOST "Host" #define OIDC_HTTP_HDR_LOCATION "Location" #define OIDC_HTTP_HDR_CACHE_CONTROL "Cache-Control" #define OIDC_HTTP_HDR_PRAGMA "Pragma" #define OIDC_HTTP_HDR_P3P "P3P" #define OIDC_HTTP_HDR_EXPIRES "Expires" #define OIDC_HTTP_HDR_X_FRAME_OPTIONS "X-Frame-Options" #define OIDC_HTTP_HDR_WWW_AUTHENTICATE "WWW-Authenticate" #define OIDC_HTTP_HDR_TRACE_PARENT "traceparent" #define OIDC_HTTP_HDR_DPOP "DPoP" #define OIDC_HTTP_HDR_DPOP_NONCE "DPoP-Nonce" #define OIDC_HTTP_HDR_VAL_XML_HTTP_REQUEST "XMLHttpRequest" #define OIDC_HTTP_HDR_VAL_NAVIGATE "navigate" #define OIDC_HTTP_HDR_VAL_DOCUMENT "document" #define OIDC_HTTP_COOKIE_SAMESITE_LAX "SameSite=Lax" #define OIDC_HTTP_COOKIE_SAMESITE_STRICT "SameSite=Strict" #define OIDC_HTTP_COOKIE_SAMESITE_NONE(c, r) oidc_util_request_is_secure(r, c) ? "SameSite=None" : NULL typedef struct oidc_http_timeout_t { int request_timeout; // in seconds int connect_timeout; // in seconds int retries; int retry_interval; // in milliseconds } oidc_http_timeout_t; #define OIDC_HTTP_PROXY_AUTH_BASIC "basic" #define OIDC_HTTP_PROXY_AUTH_DIGEST "digest" #define OIDC_HTTP_PROXY_AUTH_NTLM "ntlm" #define OIDC_HTTP_PROXY_AUTH_ANY "any" #define OIDC_HTTP_PROXY_AUTH_NEGOTIATE "negotiate" typedef struct oidc_http_outgoing_proxy_t { const char *host_port; const char *username_password; unsigned long auth_type; } oidc_http_outgoing_proxy_t; char *oidc_http_url_encode(const request_rec *r, const char *str); char *oidc_http_url_decode(const request_rec *r, const char *str); void oidc_http_hdr_err_out_add(const request_rec *r, const char *name, const char *value); void oidc_http_hdr_in_set(const request_rec *r, const char *name, const char *value); const char *oidc_http_hdr_in_cookie_get(const request_rec *r); void oidc_http_hdr_in_cookie_set(const request_rec *r, const char *value); const char *oidc_http_hdr_in_user_agent_get(const request_rec *r); const char *oidc_http_hdr_in_x_forwarded_for_get(const request_rec *r); const char *oidc_http_hdr_in_content_type_get(const request_rec *r); const char *oidc_http_hdr_in_content_length_get(const request_rec *r); const char *oidc_http_hdr_in_x_requested_with_get(const request_rec *r); const char *oidc_http_hdr_in_sec_fetch_mode_get(const request_rec *r); const char *oidc_http_hdr_in_sec_fetch_dest_get(const request_rec *r); const char *oidc_http_hdr_in_accept_get(const request_rec *r); apr_byte_t oidc_http_hdr_in_accept_contains(const request_rec *r, const char *needle); const char *oidc_http_hdr_in_authorization_get(const request_rec *r); const char *oidc_http_hdr_in_x_forwarded_proto_get(const request_rec *r); const char *oidc_http_hdr_in_x_forwarded_port_get(const request_rec *r); const char *oidc_http_hdr_in_x_forwarded_host_get(const request_rec *r); const char *oidc_http_hdr_in_forwarded_get(const request_rec *r); const char *oidc_http_hdr_in_host_get(const request_rec *r); const char *oidc_http_hdr_in_traceparent_get(const request_rec *r); void oidc_http_hdr_out_location_set(const request_rec *r, const char *value); const char *oidc_http_hdr_out_location_get(const request_rec *r); const char *oidc_http_hdr_forwarded_get(const request_rec *r, const char *elem); char *oidc_http_hdr_normalize_name(const request_rec *r, const char *str); apr_byte_t oidc_http_get(request_rec *r, const char *url, const apr_table_t *params, const char *basic_auth, const char *access_token, const char *dpop, int ssl_validate_server, char **response, long *response_code, apr_hash_t *response_hdrs, oidc_http_timeout_t *http_timeout, const oidc_http_outgoing_proxy_t *outgoing_proxy, const apr_array_header_t *pass_cookies, const char *ssl_cert, const char *ssl_key, const char *ssl_key_pwd); apr_byte_t oidc_http_post_form(request_rec *r, const char *url, const apr_table_t *params, const char *basic_auth, const char *access_token, const char *dpop, int ssl_validate_server, char **response, long *response_code, apr_hash_t *response_hdrs, oidc_http_timeout_t *http_timeout, const oidc_http_outgoing_proxy_t *outgoing_proxy, const apr_array_header_t *pass_cookies, const char *ssl_cert, const char *ssl_key, const char *ssl_key_pwd); apr_byte_t oidc_http_post_json(request_rec *r, const char *url, json_t *data, const char *basic_auth, const char *access_token, const char *dpop, int ssl_validate_server, char **response, long *response_code, apr_hash_t *response_hdrs, oidc_http_timeout_t *http_timeout, const oidc_http_outgoing_proxy_t *outgoing_proxy, const apr_array_header_t *pass_cookies, const char *ssl_cert, const char *ssl_key, const char *ssl_key_pwd); apr_byte_t oidc_util_request_has_parameter(request_rec *r, const char *param); apr_byte_t oidc_util_request_parameter_get(request_rec *r, char *name, char **value); int oidc_util_http_send(request_rec *r, const char *data, size_t data_len, const char *content_type, int success_rvalue); apr_byte_t oidc_util_read_form_encoded_params(request_rec *r, apr_table_t *table, char *data); apr_byte_t oidc_util_read_post_params(request_rec *r, apr_table_t *table, apr_byte_t propagate, const char *strip_param_name); char *oidc_http_query_encoded_url(request_rec *r, const char *url, const apr_table_t *params); char *oidc_http_form_encoded_data(request_rec *r, const apr_table_t *params); char *oidc_http_get_cookie(request_rec *r, const char *cookieName); void oidc_http_set_cookie(request_rec *r, const char *cookieName, const char *cookieValue, apr_time_t expires, const char *ext); char *oidc_http_get_chunked_cookie(request_rec *r, const char *cookieName, int chunkSize); void oidc_http_set_chunked_cookie(request_rec *r, const char *cookieName, const char *cookieValue, apr_time_t expires, int chunkSize, const char *ext); const char **oidc_http_proxy_auth_options(void); unsigned long oidc_http_proxy_s2auth(const char *arg); void oidc_http_init(void); void oidc_http_cleanup(void); #endif /* _MOD_AUTH_OPENIDC_HTTP_H_ */ mod_auth_openidc-2.4.16.10/src/jose.c000066400000000000000000001560231476721736500172500ustar00rootroot00000000000000/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ /*************************************************************************** * Copyright (C) 2017-2025 ZmartZone Holding BV * Copyright (C) 2013-2017 Ping Identity Corporation * All rights reserved. * * DISCLAIMER OF WARRANTIES: * * THE SOFTWARE PROVIDED HEREUNDER IS PROVIDED ON AN "AS IS" BASIS, WITHOUT * ANY WARRANTIES OR REPRESENTATIONS EXPRESS, IMPLIED OR STATUTORY; INCLUDING, * WITHOUT LIMITATION, WARRANTIES OF QUALITY, PERFORMANCE, NONINFRINGEMENT, * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. NOR ARE THERE ANY * WARRANTIES CREATED BY A COURSE OR DEALING, COURSE OF PERFORMANCE OR TRADE * USAGE. FURTHERMORE, THERE ARE NO WARRANTIES THAT THE SOFTWARE WILL MEET * YOUR NEEDS OR BE FREE FROM ERRORS, OR THAT THE OPERATION OF THE SOFTWARE * WILL BE UNINTERRUPTED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * JSON Web Token handling * * @Author: Hans Zandbelt - hans.zandbelt@openidc.com */ #include #define APR_WANT_BYTEFUNC #include #ifdef USE_LIBBROTLI #include #include #elif USE_ZLIB #include #endif #include "jose.h" #include #include #include #include #include #include #include #if OPENSSL_VERSION_NUMBER >= 0x30000000L #include #endif #include "util.h" /* * assemble an error report */ static void _oidc_jose_error_set(oidc_jose_error_t *error, const char *source, const int line, const char *function, const char *fmt, ...) { if (error == NULL) return; snprintf(error->source, OIDC_JOSE_ERROR_SOURCE_LENGTH, "%s", source); error->line = line; snprintf(error->function, OIDC_JOSE_ERROR_FUNCTION_LENGTH, "%s", function); va_list ap; va_start(ap, fmt); vsnprintf(error->text, OIDC_JOSE_ERROR_TEXT_LENGTH, fmt ? fmt : "(null)", ap); va_end(ap); } /* * extract a b64 encoded certificate representation as a single string */ static int oidc_jose_util_get_b64encoded_certificate_data(apr_pool_t *p, X509 *x509_cert, char **b64_encoded_certificate, oidc_jose_error_t *err) { int rc = 0; char *name = NULL, *header = NULL; long len = 0, b64_len = 0; BIO *bio = NULL; unsigned char *data = NULL; if ((bio = BIO_new(BIO_s_mem())) == NULL) { oidc_jose_error_openssl(err, "BIO_new"); goto end; } if (!PEM_write_bio_X509(bio, x509_cert)) { oidc_jose_error_openssl(err, "PEM_write_bio_X509"); goto end; } if (!PEM_read_bio(bio, &name, &header, &data, &len)) { oidc_jose_error_openssl(err, "PEM_read_bio"); goto end; } /* "For every 3 bytes of input provided 4 bytes of output data will be produced." */ b64_len = (((len + 2) / 3) * 4) + 1; *b64_encoded_certificate = (char *)apr_pcalloc(p, b64_len); if (!*b64_encoded_certificate) { oidc_jose_error_openssl(err, "apr_pcalloc"); goto end; }; rc = EVP_EncodeBlock((unsigned char *)*b64_encoded_certificate, data, len); end: if (bio) { BIO_free(bio); } if (name != NULL) { OPENSSL_free(name); } if (data != NULL) { OPENSSL_free(data); } if (header != NULL) { OPENSSL_free(header); } return rc; } /* * set a header value in a JWT */ static void oidc_jwt_hdr_set(oidc_jwt_t *jwt, const char *key, const char *value) { json_object_set_new(jwt->header.value.json, key, json_string(value)); } /* * create a new JWT */ oidc_jwt_t *oidc_jwt_new(apr_pool_t *pool, int create_header, int create_payload) { oidc_jwt_t *jwt = apr_pcalloc(pool, sizeof(oidc_jwt_t)); if (create_header) { jwt->header.value.json = json_object(); // oidc_jwt_hdr_set(jwt, "typ", "JWT"); } if (create_payload) { jwt->payload.value.json = json_object(); } return jwt; } /* * get a header value from a JWT */ const char *oidc_jwt_hdr_get(oidc_jwt_t *jwt, const char *key) { cjose_err cjose_err; cjose_header_t *hdr = cjose_jws_get_protected(jwt->cjose_jws); return hdr ? cjose_header_get(hdr, key, &cjose_err) : NULL; } /* * {"alg":"none"} */ #define OIDC_JOSE_HDR_ALG_NONE "eyJhbGciOiJub25lIn0" /* * perform compact serialization on a JWT and return the resulting string */ char *oidc_jose_jwt_serialize(apr_pool_t *pool, oidc_jwt_t *jwt, oidc_jose_error_t *err) { cjose_err cjose_err; char *result = NULL, *s_payload = NULL, *out = NULL; size_t out_len; if (_oidc_strcmp(jwt->header.alg, CJOSE_HDR_ALG_NONE) == 0) { s_payload = oidc_util_encode_json(pool, jwt->payload.value.json, JSON_PRESERVE_ORDER | JSON_COMPACT); if (s_payload == NULL) { oidc_jose_error(err, "oidc_util_encode_json failed"); return NULL; } // out is allocated by cjose and must be freed explicitly by cjose_get_dealloc()() if (cjose_base64url_encode((const uint8_t *)s_payload, _oidc_strlen(s_payload), &out, &out_len, &cjose_err) == FALSE) { oidc_jose_error(err, "cjose_base64url_encode failed: %s", oidc_cjose_e2s(pool, cjose_err)); return NULL; } result = apr_pstrmemdup(pool, out, out_len); cjose_get_dealloc()(out); result = apr_psprintf(pool, "%s.%s.", OIDC_JOSE_HDR_ALG_NONE, result); } else { // out: "the returned string pointer is owned by the JWS, the caller should not attempt to free it // directly" if (cjose_jws_export(jwt->cjose_jws, (const char **)&out, &cjose_err) == FALSE) { oidc_jose_error(err, "cjose_jws_export failed: %s", oidc_cjose_e2s(pool, cjose_err)); return NULL; } result = apr_pstrdup(pool, out); } return result; } /* * return the key type for an algorithm */ static int oidc_alg2kty(const char *alg) { if (_oidc_strcmp(alg, CJOSE_HDR_ALG_DIR) == 0) return CJOSE_JWK_KTY_OCT; if (_oidc_strncmp(alg, "RS", 2) == 0) return CJOSE_JWK_KTY_RSA; if (_oidc_strncmp(alg, "PS", 2) == 0) return CJOSE_JWK_KTY_RSA; if (_oidc_strncmp(alg, "HS", 2) == 0) return CJOSE_JWK_KTY_OCT; #if (OIDC_JOSE_EC_SUPPORT) if (_oidc_strncmp(alg, "ES", 2) == 0) return CJOSE_JWK_KTY_EC; #endif if ((_oidc_strcmp(alg, CJOSE_HDR_ALG_A128KW) == 0) || (_oidc_strcmp(alg, CJOSE_HDR_ALG_A192KW) == 0) || (_oidc_strcmp(alg, CJOSE_HDR_ALG_A256KW) == 0)) return CJOSE_JWK_KTY_OCT; if (_oidc_strcmp(alg, CJOSE_HDR_ALG_RSA_OAEP) == 0) return CJOSE_JWK_KTY_RSA; return -1; } /* * return the key type of a JWT */ int oidc_jwt_alg2kty(oidc_jwt_t *jwt) { return oidc_alg2kty(jwt->header.alg); } /* * return the key size for an algorithm */ unsigned int oidc_alg2keysize(const char *alg) { if (alg == NULL) return 0; if (_oidc_strcmp(alg, CJOSE_HDR_ALG_A128KW) == 0) return 16; if (_oidc_strcmp(alg, CJOSE_HDR_ALG_A192KW) == 0) return 24; if (_oidc_strcmp(alg, CJOSE_HDR_ALG_A256KW) == 0) return 32; if ((_oidc_strcmp(alg, CJOSE_HDR_ALG_RS256) == 0) || (_oidc_strcmp(alg, CJOSE_HDR_ALG_PS256) == 0) || (_oidc_strcmp(alg, CJOSE_HDR_ALG_HS256) == 0)) return 32; if ((_oidc_strcmp(alg, CJOSE_HDR_ALG_RS384) == 0) || (_oidc_strcmp(alg, CJOSE_HDR_ALG_PS384) == 0) || (_oidc_strcmp(alg, CJOSE_HDR_ALG_HS384) == 0)) return 48; if ((_oidc_strcmp(alg, CJOSE_HDR_ALG_RS512) == 0) || (_oidc_strcmp(alg, CJOSE_HDR_ALG_PS512) == 0) || (_oidc_strcmp(alg, CJOSE_HDR_ALG_HS512) == 0)) return 64; return 0; } /* * create a new JWK */ static oidc_jwk_t *oidc_jwk_new(apr_pool_t *pool) { oidc_jwk_t *jwk = apr_pcalloc(pool, sizeof(oidc_jwk_t)); return jwk; } static apr_byte_t _oidc_jwk_parse_x5c(apr_pool_t *pool, json_t *json, cjose_jwk_t **jwk, oidc_jose_error_t *err); #define OIDC_JOSE_HDR_KTY "kty" #define OIDC_JOSE_HDR_KTY_RSA "RSA" #define OIDC_JOSE_HDR_KTY_EC "EC" #define OIDC_JOSE_HDR_X5C "x5c" /* * parse a JSON object with an "x5c" JWK representation into a cjose JWK object */ static cjose_jwk_t *_oidc_jwk_parse_x5c_spec(apr_pool_t *pool, json_t *json, oidc_jose_error_t *err) { cjose_jwk_t *cjose_jwk = NULL; char *kty = NULL; oidc_jose_get_string(pool, json, OIDC_JOSE_HDR_KTY, FALSE, &kty, NULL); if (kty == NULL) { oidc_jose_error(err, "no key type \"" OIDC_JOSE_HDR_KTY "\" found in JWK JSON value"); goto end; } if ((_oidc_strcmp(kty, OIDC_JOSE_HDR_KTY_RSA) != 0) && (_oidc_strcmp(kty, OIDC_JOSE_HDR_KTY_EC) != 0)) { oidc_jose_error(err, "no \"" OIDC_JOSE_HDR_KTY_RSA "\" or \"" OIDC_JOSE_HDR_KTY_EC "\" key type found JWK JSON value"); goto end; } json_t *v = json_object_get(json, OIDC_JOSE_HDR_X5C); if (v == NULL) { oidc_jose_error(err, "no \"" OIDC_JOSE_HDR_X5C "\" key found in JWK JSON value"); goto end; } _oidc_jwk_parse_x5c(pool, json, &cjose_jwk, err); end: return cjose_jwk; } /* * create a JWK struct from a cjose_jwk object */ static oidc_jwk_t *oidc_jwk_from_cjose(apr_pool_t *pool, cjose_jwk_t *cjose_jwk, const char *use) { cjose_err cjose_err; oidc_jwk_t *jwk = oidc_jwk_new(pool); jwk->cjose_jwk = cjose_jwk; jwk->kid = apr_pstrdup(pool, cjose_jwk_get_kid(jwk->cjose_jwk, &cjose_err)); jwk->kty = cjose_jwk_get_kty(jwk->cjose_jwk, &cjose_err); jwk->use = apr_pstrdup(pool, use); return jwk; } /* * parse a JSON string to a JWK struct */ oidc_jwk_t *oidc_jwk_parse(apr_pool_t *pool, json_t *json, oidc_jose_error_t *err) { oidc_jwk_t *result = NULL; cjose_jwk_t *cjose_jwk = NULL; cjose_err cjose_err; oidc_jose_error_t x5c_err; char *use = NULL; json_t *v = NULL, *e = NULL; int i = 0; char *s_json = oidc_util_encode_json(pool, json, JSON_PRESERVE_ORDER | JSON_COMPACT); if (s_json == NULL) { oidc_jose_error(err, "could not serialize JWK"); goto end; } cjose_jwk = cjose_jwk_import(s_json, _oidc_strlen(s_json), &cjose_err); if (cjose_jwk == NULL) { // exception because x5c is not supported by cjose natively // ignore errors set by oidc_jwk_parse_x5c_spec cjose_jwk = _oidc_jwk_parse_x5c_spec(pool, json, &x5c_err); if (cjose_jwk == NULL) { oidc_jose_error(err, "JWK parsing failed: %s", oidc_cjose_e2s(pool, cjose_err)); goto end; } } oidc_jose_get_string(pool, json, OIDC_JOSE_JWK_USE_STR, FALSE, &use, NULL); result = oidc_jwk_from_cjose(pool, cjose_jwk, use); // set x5c array v = json_object_get(json, OIDC_JOSE_JWK_X5C_STR); if (v && json_is_array(v)) { result->x5c = apr_array_make(pool, json_array_size(v), sizeof(char *)); for (i = 0; i < json_array_size(v); i++) { e = json_array_get(v, i); if (json_is_string(e)) APR_ARRAY_PUSH(result->x5c, char *) = apr_pstrdup(pool, json_string_value(e)); } } // set x5t#256 v = json_object_get(json, OIDC_JOSE_JWK_X5T256_STR); if (v) result->x5t_S256 = apr_pstrdup(pool, json_string_value(v)); // set x5t v = json_object_get(json, OIDC_JOSE_JWK_X5T_STR); if (v) result->x5t = apr_pstrdup(pool, json_string_value(v)); end: return result; } /* * copy a JWK by converting oidc_jwk_t to JSON and parsing it back */ oidc_jwk_t *oidc_jwk_copy(apr_pool_t *pool, const oidc_jwk_t *src) { int i = 0; cjose_err err; oidc_jwk_t *dst = oidc_jwk_new(pool); dst->cjose_jwk = cjose_jwk_retain(src->cjose_jwk, &err); dst->kid = apr_pstrdup(pool, src->kid); dst->kty = src->kty; dst->use = apr_pstrdup(pool, src->use); dst->x5c = NULL; if (src->x5c) { dst->x5c = apr_array_make(pool, src->x5c->nelts, sizeof(char *)); for (i = 0; i < src->x5c->nelts; i++) APR_ARRAY_PUSH(dst->x5c, char *) = APR_ARRAY_IDX(src->x5c, i, char *); } dst->x5t = apr_pstrdup(pool, src->x5t); dst->x5t_S256 = apr_pstrdup(pool, src->x5t_S256); return dst; } /* * destroy resources allocated for a JWK struct */ void oidc_jwk_destroy(oidc_jwk_t *jwk) { if (jwk) { if (jwk->cjose_jwk) { cjose_jwk_release(jwk->cjose_jwk); jwk->cjose_jwk = NULL; } } } /* * destroy a list of JWKs structs */ void oidc_jwk_list_destroy_hash(apr_hash_t *keys) { apr_hash_index_t *hi = NULL; const void *key = NULL; apr_ssize_t klen = 0; if (keys == NULL) return; for (hi = apr_hash_first(NULL, keys); hi; hi = apr_hash_next(hi)) { oidc_jwk_t *jwk = NULL; apr_hash_this(hi, &key, &klen, (void **)&jwk); oidc_jwk_destroy(jwk); apr_hash_set(keys, key, klen, NULL); } } /* * copy a list (array) of JWKs */ apr_array_header_t *oidc_jwk_list_copy(apr_pool_t *pool, apr_array_header_t *src) { apr_array_header_t *dst = NULL; int i = 0; if (src == NULL) return NULL; dst = apr_array_make(pool, src->nelts, sizeof(oidc_jwk_t *)); for (i = 0; i < src->nelts; i++) APR_ARRAY_PUSH(dst, oidc_jwk_t *) = oidc_jwk_copy(pool, APR_ARRAY_IDX(src, i, oidc_jwk_t *)); return dst; } /* * destroy a list (array) of JWKs */ void oidc_jwk_list_destroy(apr_array_header_t *keys_list) { if (keys_list == NULL) return; oidc_jwk_t **jwk = NULL; while ((jwk = apr_array_pop(keys_list))) { oidc_jwk_destroy(*jwk); } } /* * parse a JSON object in to a JWK struct */ apr_byte_t oidc_jwk_parse_json(apr_pool_t *pool, json_t *json, oidc_jwk_t **jwk, oidc_jose_error_t *err) { *jwk = oidc_jwk_parse(pool, json, err); return (*jwk != NULL); } /* * parse a set of JWKs into a list (array) of JWK structs */ apr_byte_t oidc_jwks_parse_json(apr_pool_t *pool, json_t *json, apr_array_header_t **jwk_list, oidc_jose_error_t *err) { const json_t *keys = json_object_get(json, OIDC_JOSE_JWKS_KEYS_STR); if ((keys == NULL) || (!json_is_array(keys))) { oidc_jose_error(err, "JWKS did not contain \"" OIDC_JOSE_JWKS_KEYS_STR "\" array"); return FALSE; } *jwk_list = apr_array_make(pool, json_array_size(keys), sizeof(oidc_jwk_t *)); for (int i = 0; i < json_array_size(keys); i++) { json_t *elem = json_array_get(keys, i); if (elem == NULL) continue; oidc_jwk_t *jwk; if (oidc_jwk_parse_json(pool, elem, &jwk, err) != TRUE) { return FALSE; } APR_ARRAY_PUSH(*jwk_list, oidc_jwk_t *) = jwk; } return TRUE; } /* * check if a JSON object is a JWK */ apr_byte_t oidc_is_jwk(json_t *json) { const json_t *kty = json_object_get(json, OIDC_JOSE_JWK_KTY_STR); if ((kty == NULL) || (!json_is_string(kty))) { return FALSE; } return TRUE; } /* * check if a JSON object is a set JWKs */ apr_byte_t oidc_is_jwks(json_t *json) { const json_t *keys = json_object_get(json, OIDC_JOSE_JWKS_KEYS_STR); if ((keys == NULL) || (!json_is_array(keys))) { return FALSE; } return TRUE; } /* * produce the serialized JSON JWK representation from an oidc_jwk_t structure */ apr_byte_t oidc_jwk_to_json(apr_pool_t *pool, const oidc_jwk_t *jwk, char **s_json, oidc_jose_error_t *oidc_err) { apr_byte_t rv = FALSE; char *s_cjose = NULL; cjose_err err; json_t *json = NULL, *temp = NULL; json_error_t json_error; int i = 0; // input sanity checks if ((jwk == NULL) || (s_json == NULL)) goto end; // get the JWK string representation from cjose s_cjose = cjose_jwk_to_json(jwk->cjose_jwk, TRUE, &err); if (s_cjose == NULL) { oidc_jose_error(oidc_err, "oidc_jwk_to_json: cjose_jwk_to_json failed: %s", oidc_cjose_e2s(pool, err)); goto end; } json = json_loads(s_cjose, 0, &json_error); if (json == NULL) { oidc_jose_error(oidc_err, "oidc_jwk_to_json: json_loads failed"); goto end; } if (jwk->use) json_object_set_new(json, OIDC_JOSE_JWK_USE_STR, json_string(jwk->use)); // set x5c if ((jwk->x5c != NULL) && (jwk->x5c->nelts > 0)) { temp = json_array(); for (i = 0; i < jwk->x5c->nelts; i++) json_array_append_new(temp, json_string(APR_ARRAY_IDX(jwk->x5c, i, const char *))); json_object_set_new(json, OIDC_JOSE_JWK_X5C_STR, temp); } // set x5t#256 if (jwk->x5t_S256 != NULL) json_object_set_new(json, OIDC_JOSE_JWK_X5T256_STR, json_string(jwk->x5t_S256)); // set x5t if (jwk->x5t != NULL) json_object_set_new(json, OIDC_JOSE_JWK_X5T_STR, json_string(jwk->x5t)); // generate the string ... *s_json = oidc_util_encode_json(pool, json, JSON_ENCODE_ANY | JSON_COMPACT | JSON_PRESERVE_ORDER); rv = (*s_json != NULL); end: if (json) json_decref(json); if (s_cjose) cjose_get_dealloc()(s_cjose); return rv; } /* * hash a sequence of bytes with a specific algorithm and return the result as a base64url-encoded \0 terminated string */ apr_byte_t oidc_jose_hash_and_base64url_encode(apr_pool_t *pool, const char *openssl_hash_algo, const char *input, int input_len, char **output, oidc_jose_error_t *err) { unsigned char *hashed = NULL; unsigned int hashed_len = 0; if (oidc_jose_hash_bytes(pool, openssl_hash_algo, (const unsigned char *)input, input_len, &hashed, &hashed_len, err) == FALSE) return FALSE; char *out = NULL; size_t out_len; cjose_err cjose_err; if (cjose_base64url_encode(hashed, hashed_len, &out, &out_len, &cjose_err) == FALSE) { oidc_jose_error(err, "cjose_base64url_encode failed: %s", oidc_cjose_e2s(pool, cjose_err)); return FALSE; } *output = apr_pstrmemdup(pool, out, out_len); cjose_get_dealloc()(out); return TRUE; } /* * set a specified key identifier or generate a key identifier and set it */ static apr_byte_t oidc_jwk_set_or_generate_kid(apr_pool_t *pool, cjose_jwk_t *cjose_jwk, const char *s_kid, const char *key_params, int key_params_len, oidc_jose_error_t *err) { char *jwk_kid = NULL; if (s_kid != NULL) { jwk_kid = apr_pstrdup(pool, s_kid); } else { /* calculate a unique key identifier (kid) by fingerprinting the key params */ if (oidc_jose_hash_and_base64url_encode(pool, OIDC_JOSE_ALG_SHA256, key_params, key_params_len, &jwk_kid, err) == FALSE) { // oidc_jose_error(err, "oidc_jose_hash_and_base64urlencode failed"); return FALSE; } } cjose_err cjose_err; if (cjose_jwk_set_kid(cjose_jwk, jwk_kid, _oidc_strlen(jwk_kid), &cjose_err) == FALSE) { oidc_jose_error(err, "cjose_jwk_set_kid failed: %s", oidc_cjose_e2s(pool, cjose_err)); return FALSE; } return TRUE; } /* * create an "oct" symmetric JWK */ oidc_jwk_t *oidc_jwk_create_symmetric_key(apr_pool_t *pool, const char *skid, const unsigned char *key, unsigned int key_len, apr_byte_t set_kid, oidc_jose_error_t *err) { cjose_err cjose_err; cjose_jwk_t *cjose_jwk = cjose_jwk_create_oct_spec(key, key_len, &cjose_err); if (cjose_jwk == NULL) { oidc_jose_error(err, "cjose_jwk_create_oct_spec failed: %s", oidc_cjose_e2s(pool, cjose_err)); return NULL; } if (set_kid == TRUE) { if (oidc_jwk_set_or_generate_kid(pool, cjose_jwk, skid, (const char *)key, key_len, err) == FALSE) { cjose_jwk_release(cjose_jwk); return NULL; } } oidc_jwk_t *jwk = oidc_jwk_new(pool); jwk->cjose_jwk = cjose_jwk; jwk->kid = apr_pstrdup(pool, cjose_jwk_get_kid(jwk->cjose_jwk, &cjose_err)); jwk->kty = cjose_jwk_get_kty(jwk->cjose_jwk, &cjose_err); return jwk; } /* * check if a string is an element of an array of strings */ static apr_byte_t oidc_jose_array_has_string(apr_array_header_t *haystack, const char *needle) { int i = 0; while (i < haystack->nelts) { if (_oidc_strcmp(APR_ARRAY_IDX(haystack, i, const char *), needle) == 0) return TRUE; i++; } return FALSE; } /* * return all supported signing algorithms */ apr_array_header_t *oidc_jose_jws_supported_algorithms(apr_pool_t *pool) { apr_array_header_t *result = apr_array_make(pool, 12, sizeof(const char *)); APR_ARRAY_PUSH(result, const char *) = CJOSE_HDR_ALG_RS256; APR_ARRAY_PUSH(result, const char *) = CJOSE_HDR_ALG_RS384; APR_ARRAY_PUSH(result, const char *) = CJOSE_HDR_ALG_RS512; APR_ARRAY_PUSH(result, const char *) = CJOSE_HDR_ALG_PS256; APR_ARRAY_PUSH(result, const char *) = CJOSE_HDR_ALG_PS384; APR_ARRAY_PUSH(result, const char *) = CJOSE_HDR_ALG_PS512; APR_ARRAY_PUSH(result, const char *) = CJOSE_HDR_ALG_HS256; APR_ARRAY_PUSH(result, const char *) = CJOSE_HDR_ALG_HS384; APR_ARRAY_PUSH(result, const char *) = CJOSE_HDR_ALG_HS512; #if (OIDC_JOSE_EC_SUPPORT) APR_ARRAY_PUSH(result, const char *) = CJOSE_HDR_ALG_ES256; APR_ARRAY_PUSH(result, const char *) = CJOSE_HDR_ALG_ES384; APR_ARRAY_PUSH(result, const char *) = CJOSE_HDR_ALG_ES512; #endif APR_ARRAY_PUSH(result, const char *) = CJOSE_HDR_ALG_NONE; return result; } /* * check if the provided signing algorithm is supported */ apr_byte_t oidc_jose_jws_algorithm_is_supported(apr_pool_t *pool, const char *alg) { return oidc_jose_array_has_string(oidc_jose_jws_supported_algorithms(pool), alg); } /* * return all supported content encryption key algorithms */ apr_array_header_t *oidc_jose_jwe_supported_algorithms(apr_pool_t *pool) { apr_array_header_t *result = apr_array_make(pool, 4, sizeof(const char *)); APR_ARRAY_PUSH(result, const char *) = CJOSE_HDR_ALG_A128KW; APR_ARRAY_PUSH(result, const char *) = CJOSE_HDR_ALG_A192KW; APR_ARRAY_PUSH(result, const char *) = CJOSE_HDR_ALG_A256KW; APR_ARRAY_PUSH(result, const char *) = CJOSE_HDR_ALG_RSA_OAEP; return result; } /* * check if the provided content encryption key algorithm is supported */ apr_byte_t oidc_jose_jwe_algorithm_is_supported(apr_pool_t *pool, const char *alg) { return oidc_jose_array_has_string(oidc_jose_jwe_supported_algorithms(pool), alg); } /* * return all supported encryption algorithms */ apr_array_header_t *oidc_jose_jwe_supported_encryptions(apr_pool_t *pool) { apr_array_header_t *result = apr_array_make(pool, 5, sizeof(const char *)); APR_ARRAY_PUSH(result, const char *) = CJOSE_HDR_ENC_A128CBC_HS256; APR_ARRAY_PUSH(result, const char *) = CJOSE_HDR_ENC_A192CBC_HS384; APR_ARRAY_PUSH(result, const char *) = CJOSE_HDR_ENC_A256CBC_HS512; #if (OIDC_JOSE_GCM_SUPPORT) APR_ARRAY_PUSH(result, const char *) = CJOSE_HDR_ENC_A256GCM; #endif return result; } /* * check if the provided encryption algorithm is supported */ apr_byte_t oidc_jose_jwe_encryption_is_supported(apr_pool_t *pool, const char *enc) { return oidc_jose_array_has_string(oidc_jose_jwe_supported_encryptions(pool), enc); } /* * get (optional) string from JWT */ apr_byte_t oidc_jose_get_string(apr_pool_t *pool, json_t *json, const char *claim_name, apr_byte_t is_mandatory, char **result, oidc_jose_error_t *err) { json_t *v = json_object_get(json, claim_name); if (v != NULL) { if (json_is_string(v)) { *result = apr_pstrdup(pool, json_string_value(v)); } else if (is_mandatory) { oidc_jose_error(err, "mandatory JSON key \"%s\" was found but the type is not a string", claim_name); return FALSE; } } else if (is_mandatory) { oidc_jose_error(err, "mandatory JSON key \"%s\" could not be found", claim_name); return FALSE; } return TRUE; } /* * parse (optional) timestamp from payload */ static apr_byte_t oidc_jose_get_timestamp(apr_pool_t *pool, json_t *json, const char *claim_name, apr_byte_t is_mandatory, double *result, oidc_jose_error_t *err) { *result = OIDC_JWT_CLAIM_TIME_EMPTY; json_t *v = json_object_get(json, claim_name); if (v != NULL) { if (json_is_number(v)) { *result = json_number_value(v); } else if (is_mandatory) { oidc_jose_error(err, "mandatory JSON key \"%s\" was found but the type is not a number", claim_name); return FALSE; } } else if (is_mandatory) { oidc_jose_error(err, "mandatory JSON key \"%s\" could not be found", claim_name); return FALSE; } return TRUE; } #define OIDC_JOSE_JWT_ISS "iss" #define OIDC_JOSE_JWT_SUB "sub" #define OIDC_JOSE_JWT_EXP "exp" #define OIDC_JOSE_JWT_IAT "iat" /* * parse JWT payload */ static apr_byte_t oidc_jose_parse_payload(apr_pool_t *pool, const char *s_payload, size_t s_payload_len, oidc_jwt_payload_t *payload, oidc_jose_error_t *err) { /* decode the string in to a JSON structure into value->json */ json_error_t json_error; payload->value.str = apr_pstrndup(pool, s_payload, s_payload_len); payload->value.json = json_loads(payload->value.str, 0, &json_error); /* check that we've actually got a JSON value back */ if (payload->value.json == NULL) { oidc_jose_error(err, "JSON parsing (json_loads) failed: %s (%s)", json_error.text, s_payload); return FALSE; } /* check that the value is a JSON object */ if (!json_is_object(payload->value.json)) { oidc_jose_error(err, "JSON value is not an object"); return FALSE; } /* get the (optional) "iss" value from the JSON payload */ oidc_jose_get_string(pool, payload->value.json, OIDC_JOSE_JWT_ISS, FALSE, &payload->iss, NULL); /* get the (optional) "exp" value from the JSON payload */ oidc_jose_get_timestamp(pool, payload->value.json, OIDC_JOSE_JWT_EXP, FALSE, &payload->exp, NULL); /* get the (optional) "iat" value from the JSON payload */ oidc_jose_get_timestamp(pool, payload->value.json, OIDC_JOSE_JWT_IAT, FALSE, &payload->iat, NULL); /* get the (optional) "sub" value from the JSON payload */ oidc_jose_get_string(pool, payload->value.json, OIDC_JOSE_JWT_SUB, FALSE, &payload->sub, NULL); return TRUE; } /* * decrypt a JWT and return the plaintext */ static uint8_t *oidc_jwe_decrypt_impl(apr_pool_t *pool, cjose_jwe_t *jwe, apr_hash_t *keys, size_t *content_len, oidc_jose_error_t *err) { uint8_t *decrypted = NULL; oidc_jwk_t *jwk = NULL; apr_hash_index_t *hi; cjose_err cjose_err; cjose_header_t *hdr = cjose_jwe_get_protected(jwe); const char *kid = cjose_header_get(hdr, CJOSE_HDR_KID, &cjose_err); const char *alg = cjose_header_get(hdr, CJOSE_HDR_ALG, &cjose_err); if ((keys == NULL) || (apr_hash_count(keys) == 0)) { oidc_jose_error(err, "no decryption keys configured"); return NULL; } if (kid != NULL) { jwk = apr_hash_get(keys, kid, APR_HASH_KEY_STRING); if (jwk != NULL) { if ((jwk->use == NULL) || (_oidc_strcmp(jwk->use, OIDC_JOSE_JWK_ENC_STR) == 0)) { decrypted = cjose_jwe_decrypt(jwe, jwk->cjose_jwk, content_len, &cjose_err); if (decrypted == NULL) oidc_jose_error(err, "encrypted JWT could not be decrypted with kid %s: %s", kid, oidc_cjose_e2s(pool, cjose_err)); } else { oidc_jose_error(err, "cannot use non-encryption (\"use=enc\") key with kid: %s", kid); } } else { oidc_jose_error(err, "could not find key with kid: %s", kid); } } else { for (hi = apr_hash_first(pool, keys); hi; hi = apr_hash_next(hi)) { apr_hash_this(hi, NULL, NULL, (void **)&jwk); if (jwk->kty != oidc_alg2kty(alg)) continue; if ((jwk->use) && (_oidc_strcmp(jwk->use, OIDC_JOSE_JWK_ENC_STR) != 0)) continue; decrypted = cjose_jwe_decrypt(jwe, jwk->cjose_jwk, content_len, &cjose_err); if (decrypted != NULL) break; } if (decrypted == NULL) oidc_jose_error(err, "encrypted JWT could not be decrypted with any of the %d keys: error for last " "tried key is: %s", apr_hash_count(keys), oidc_cjose_e2s(pool, cjose_err)); } return decrypted; } /* * decrypt a JSON Web Token */ apr_byte_t oidc_jwe_decrypt(apr_pool_t *pool, const char *input_json, apr_hash_t *keys, char **plaintext, int *plaintext_len, oidc_jose_error_t *err, apr_byte_t import_must_succeed) { cjose_err cjose_err; cjose_jwe_t *jwe = cjose_jwe_import(input_json, _oidc_strlen(input_json), &cjose_err); if (jwe != NULL) { size_t content_len = 0; uint8_t *decrypted = oidc_jwe_decrypt_impl(pool, jwe, keys, &content_len, err); if (decrypted != NULL) { *plaintext = apr_pcalloc(pool, content_len + 1); _oidc_memcpy(*plaintext, decrypted, content_len); (*plaintext)[content_len] = '\0'; cjose_get_dealloc()(decrypted); if (plaintext_len) *plaintext_len = content_len; } cjose_jwe_release(jwe); } else if (import_must_succeed == FALSE) { *plaintext = apr_pstrdup(pool, input_json); if (plaintext_len) *plaintext_len = _oidc_strlen(input_json); } else { oidc_jose_error(err, "cjose_jwe_import failed: %s", oidc_cjose_e2s(pool, cjose_err)); } return (*plaintext != NULL); } #ifdef USE_LIBBROTLI /* * deflate using libbrotli */ static apr_byte_t oidc_jose_brotli_compress(apr_pool_t *pool, const char *input, int input_len, char **output, int *output_len, oidc_jose_error_t *err) { size_t len = BrotliEncoderMaxCompressedSize(input_len); *output = apr_pcalloc(pool, len); if (BrotliEncoderCompress(BROTLI_DEFAULT_QUALITY, BROTLI_DEFAULT_WINDOW, BROTLI_MODE_TEXT, input_len, (const uint8_t *)input, &len, (uint8_t *)*output) != BROTLI_TRUE) { oidc_jose_error(err, "BrotliEncoderCompress failed: compression error or buffer too small"); return FALSE; } *output_len = len; return TRUE; } /* * inflate using libbrotli */ static apr_byte_t oidc_jose_brotli_uncompress(apr_pool_t *pool, const char *input, int input_len, char **output, int *output_len, oidc_jose_error_t *err) { size_t len = 4 * input_len; *output = apr_pcalloc(pool, len); if (BrotliDecoderDecompress(input_len, (const uint8_t *)input, &len, (uint8_t *)*output) != BROTLI_DECODER_RESULT_SUCCESS) { oidc_jose_error(err, "BrotliDecoderDecompress failed: decompression error or buffer too small"); return FALSE; } *output_len = len; return TRUE; } #elif USE_ZLIB /* * deflate using zlib */ static apr_byte_t oidc_jose_zlib_compress(apr_pool_t *pool, const char *input, int input_len, char **output, int *output_len, oidc_jose_error_t *err) { apr_byte_t rv = FALSE; int status = Z_OK; z_stream zlib; zlib.zalloc = Z_NULL; zlib.zfree = Z_NULL; zlib.opaque = Z_NULL; zlib.next_in = (Bytef *)input; zlib.avail_in = input_len; *output = apr_pcalloc(pool, input_len * 2); zlib.next_out = (Bytef *)(*output); zlib.avail_out = input_len * 2; status = deflateInit(&zlib, Z_BEST_COMPRESSION); if (status != Z_OK) { oidc_jose_error(err, "deflateInit() failed: %d", status); goto end; } status = deflate(&zlib, Z_FINISH); if (status != Z_STREAM_END) { oidc_jose_error(err, "deflate() failed: %d", status); goto end; } *output_len = (int)zlib.total_out; rv = TRUE; end: deflateEnd(&zlib); return rv; } #define OIDC_CJOSE_UNCOMPRESS_CHUNK 8192 /* * inflate using zlib */ static apr_byte_t oidc_jose_zlib_uncompress(apr_pool_t *pool, const char *input, int input_len, char **output, int *output_len, oidc_jose_error_t *err) { apr_byte_t rv = FALSE; int status = Z_OK; size_t len = OIDC_CJOSE_UNCOMPRESS_CHUNK; char *tmp = NULL, *buf = apr_pcalloc(pool, len); z_stream zlib; zlib.zalloc = Z_NULL; zlib.zfree = Z_NULL; zlib.opaque = Z_NULL; zlib.avail_in = (uInt)input_len; zlib.next_in = (Bytef *)input; zlib.total_out = 0; status = inflateInit(&zlib); if (status != Z_OK) { oidc_jose_error(err, "inflateInit() failed: %d", status); goto end; } while (status == Z_OK) { if (zlib.total_out >= OIDC_CJOSE_UNCOMPRESS_CHUNK) { tmp = apr_pcalloc(pool, len + OIDC_CJOSE_UNCOMPRESS_CHUNK); _oidc_memcpy(tmp, buf, len); len += OIDC_CJOSE_UNCOMPRESS_CHUNK; buf = tmp; } zlib.next_out = (Bytef *)(buf + zlib.total_out); zlib.avail_out = (uInt)len - zlib.total_out; status = inflate(&zlib, Z_SYNC_FLUSH); } if (status != Z_STREAM_END) { oidc_jose_error(err, "inflate() failed: %d", status); goto end; } *output_len = (int)zlib.total_out; *output = buf; rv = TRUE; end: inflateEnd(&zlib); return rv; } #endif /* * compress using (compile-time) zlib or libbrotli, otherwise just plain copy */ apr_byte_t oidc_jose_compress(apr_pool_t *pool, const char *input, int input_len, char **output, int *output_len, oidc_jose_error_t *err) { #ifdef USE_LIBBROTLI return oidc_jose_brotli_compress(pool, input, input_len, output, output_len, err); #elif USE_ZLIB return oidc_jose_zlib_compress(pool, input, input_len, output, output_len, err); #else *output = apr_pmemdup(pool, input, input_len); *output_len = input_len; return TRUE; #endif } /* * decompress using (compile-time) zlib or libbrotli, otherwise just plain copy */ apr_byte_t oidc_jose_uncompress(apr_pool_t *pool, const char *input, int input_len, char **output, int *output_len, oidc_jose_error_t *err) { #ifdef USE_LIBBROTLI return oidc_jose_brotli_uncompress(pool, input, input_len, output, output_len, err); #elif USE_ZLIB return oidc_jose_zlib_uncompress(pool, input, input_len, output, output_len, err); #else *output = apr_pmemdup(pool, input, input_len); *output_len = input_len; return TRUE; #endif } /* * parse and (optionally) decrypt a JSON Web Token */ apr_byte_t oidc_jwt_parse(apr_pool_t *pool, const char *input_json, oidc_jwt_t **j_jwt, apr_hash_t *keys, apr_byte_t compress, oidc_jose_error_t *err) { cjose_err cjose_err; char *s_json = NULL; if (oidc_jwe_decrypt(pool, input_json, keys, &s_json, NULL, err, FALSE) == FALSE) return FALSE; *j_jwt = oidc_jwt_new(pool, FALSE, FALSE); if (*j_jwt == NULL) return FALSE; oidc_jwt_t *jwt = *j_jwt; jwt->cjose_jws = cjose_jws_import(s_json, _oidc_strlen(s_json), &cjose_err); if (jwt->cjose_jws == NULL) { oidc_jose_error(err, "cjose_jws_import failed: %s", oidc_cjose_e2s(pool, cjose_err)); oidc_jwt_destroy(jwt); *j_jwt = NULL; return FALSE; } cjose_header_t *hdr = cjose_jws_get_protected(jwt->cjose_jws); jwt->header.value.json = json_deep_copy((json_t *)hdr); jwt->header.value.str = oidc_util_encode_json(pool, jwt->header.value.json, JSON_PRESERVE_ORDER | JSON_COMPACT); jwt->header.alg = apr_pstrdup(pool, cjose_header_get(hdr, CJOSE_HDR_ALG, &cjose_err)); jwt->header.enc = apr_pstrdup(pool, cjose_header_get(hdr, CJOSE_HDR_ENC, &cjose_err)); jwt->header.kid = apr_pstrdup(pool, cjose_header_get(hdr, CJOSE_HDR_KID, &cjose_err)); uint8_t *plaintext = NULL; size_t plaintext_len = 0; if (cjose_jws_get_plaintext(jwt->cjose_jws, &plaintext, &plaintext_len, &cjose_err) == FALSE) { oidc_jwt_destroy(jwt); *j_jwt = NULL; oidc_jose_error(err, "cjose_jws_get_plaintext failed: %s", oidc_cjose_e2s(pool, cjose_err)); return FALSE; } if (compress == TRUE) { char *payload = NULL; int payload_len = 0; if (oidc_jose_uncompress(pool, (char *)plaintext, plaintext_len, &payload, &payload_len, err) == FALSE) { oidc_jwt_destroy(jwt); *j_jwt = NULL; return FALSE; } plaintext = (uint8_t *)payload; plaintext_len = payload_len; } if (oidc_jose_parse_payload(pool, (const char *)plaintext, plaintext_len, &jwt->payload, err) == FALSE) { oidc_jwt_destroy(jwt); *j_jwt = NULL; return FALSE; } return TRUE; } /* * destroy resources allocated for JWT */ void oidc_jwt_destroy(oidc_jwt_t *jwt) { if (jwt) { if (jwt->header.value.json) { json_decref(jwt->header.value.json); jwt->header.value.json = NULL; jwt->header.value.str = NULL; } if (jwt->payload.value.json) { json_decref(jwt->payload.value.json); jwt->payload.value.json = NULL; jwt->payload.value.str = NULL; } if (jwt->cjose_jws) { cjose_jws_release(jwt->cjose_jws); jwt->cjose_jws = NULL; } } } /* * sign a JWT */ apr_byte_t oidc_jwt_sign(apr_pool_t *pool, oidc_jwt_t *jwt, oidc_jwk_t *jwk, apr_byte_t compress, oidc_jose_error_t *err) { cjose_header_t *hdr = (cjose_header_t *)jwt->header.value.json; if (jwt->header.alg) oidc_jwt_hdr_set(jwt, CJOSE_HDR_ALG, jwt->header.alg); if (jwt->header.kid) oidc_jwt_hdr_set(jwt, CJOSE_HDR_KID, jwt->header.kid); if (jwt->header.enc) oidc_jwt_hdr_set(jwt, CJOSE_HDR_ENC, jwt->header.enc); if (jwt->header.x5t) oidc_jwt_hdr_set(jwt, OIDC_JOSE_JWK_X5T_STR, jwt->header.x5t); if (jwt->cjose_jws) cjose_jws_release(jwt->cjose_jws); cjose_err cjose_err; char *plaintext = oidc_util_encode_json(pool, jwt->payload.value.json, JSON_PRESERVE_ORDER | JSON_COMPACT); char *s_payload = NULL; int payload_len = 0; if (compress == TRUE) { if (oidc_jose_compress(pool, (char *)plaintext, _oidc_strlen(plaintext), &s_payload, &payload_len, err) == FALSE) { return FALSE; } } else { s_payload = plaintext; payload_len = _oidc_strlen(plaintext); jwt->payload.value.str = plaintext; } jwt->cjose_jws = cjose_jws_sign(jwk->cjose_jwk, hdr, (const uint8_t *)s_payload, payload_len, &cjose_err); if (jwt->cjose_jws == NULL) { oidc_jose_error(err, "cjose_jws_sign failed: %s", oidc_cjose_e2s(pool, cjose_err)); return FALSE; } return TRUE; } #if (OPENSSL_VERSION_NUMBER < 0x10100000) || defined(LIBRESSL_VERSION_NUMBER) EVP_MD_CTX *EVP_MD_CTX_new() { return malloc(sizeof(EVP_MD_CTX)); } void EVP_MD_CTX_free(EVP_MD_CTX *ctx) { if (ctx) free(ctx); } #endif /* * encrypt a JWT */ apr_byte_t oidc_jwt_encrypt(apr_pool_t *pool, oidc_jwt_t *jwe, oidc_jwk_t *jwk, const char *payload, int payload_len, char **serialized, oidc_jose_error_t *err) { cjose_header_t *hdr = (cjose_header_t *)jwe->header.value.json; if (jwe->header.alg) oidc_jwt_hdr_set(jwe, CJOSE_HDR_ALG, jwe->header.alg); if (jwe->header.kid) oidc_jwt_hdr_set(jwe, CJOSE_HDR_KID, jwe->header.kid); if (jwe->header.enc) oidc_jwt_hdr_set(jwe, CJOSE_HDR_ENC, jwe->header.enc); cjose_err cjose_err; cjose_jwe_t *cjose_jwe = cjose_jwe_encrypt(jwk->cjose_jwk, hdr, (const uint8_t *)payload, payload_len, &cjose_err); if (cjose_jwe == NULL) { oidc_jose_error(err, "cjose_jwe_encrypt failed: %s", oidc_cjose_e2s(pool, cjose_err)); return FALSE; } char *cser = cjose_jwe_export(cjose_jwe, &cjose_err); if (cser == NULL) { oidc_jose_error(err, "cjose_jwe_export failed: %s", oidc_cjose_e2s(pool, cjose_err)); return FALSE; } *serialized = apr_pstrdup(pool, cser); cjose_get_dealloc()(cser); cjose_jwe_release(cjose_jwe); return TRUE; } #define OIDC_JOSE_CJOSE_VERSION_DEPRECATED "0.4." /* * check for a version of cjose < 0.5.0 that has a version of * cjose_jws_verify that resources after a verification failure */ apr_byte_t oidc_jose_version_deprecated(apr_pool_t *pool) { char *version = apr_pstrdup(pool, cjose_version()); return (_oidc_strstr(version, OIDC_JOSE_CJOSE_VERSION_DEPRECATED) == version); } /* * verify the signature of a JWT */ apr_byte_t oidc_jwt_verify(apr_pool_t *pool, oidc_jwt_t *jwt, apr_hash_t *keys, oidc_jose_error_t *err) { apr_byte_t rc = FALSE; oidc_jwk_t *jwk = NULL; apr_hash_index_t *hi; cjose_err cjose_err; if (jwt->header.kid != NULL) { jwk = apr_hash_get(keys, jwt->header.kid, APR_HASH_KEY_STRING); if (jwk != NULL) { rc = cjose_jws_verify(jwt->cjose_jws, jwk->cjose_jwk, &cjose_err); if (rc == FALSE) { oidc_jose_error(err, "cjose_jws_verify failed: %s", oidc_cjose_e2s(pool, cjose_err)); if (oidc_jose_version_deprecated(pool)) jwt->cjose_jws = NULL; } } else { oidc_jose_error(err, "could not find key with kid: %s", jwt->header.kid); rc = FALSE; } } else { for (hi = apr_hash_first(pool, keys); hi; hi = apr_hash_next(hi)) { apr_hash_this(hi, NULL, NULL, (void **)&jwk); if (jwk->kty == oidc_jwt_alg2kty(jwt)) { rc = cjose_jws_verify(jwt->cjose_jws, jwk->cjose_jwk, &cjose_err); if (rc == FALSE) { oidc_jose_error(err, "cjose_jws_verify failed: %s", oidc_cjose_e2s(pool, cjose_err)); if (oidc_jose_version_deprecated(pool)) jwt->cjose_jws = NULL; } } if ((rc == TRUE) || (jwt->cjose_jws == NULL)) break; } if (rc == FALSE) oidc_jose_error( err, "could not verify signature against any of the (%d) provided keys%s", apr_hash_count(keys), apr_hash_count(keys) > 0 ? "" : apr_psprintf( pool, "; you have probably provided no or incorrect keys/key-types for algorithm: %s", jwt->header.alg)); } return rc; } /* * hash a byte sequence with the specified algorithm */ apr_byte_t oidc_jose_hash_bytes(apr_pool_t *pool, const char *s_digest, const unsigned char *input, unsigned int input_len, unsigned char **output, unsigned int *output_len, oidc_jose_error_t *err) { unsigned char md_value[EVP_MAX_MD_SIZE]; EVP_MD_CTX *ctx = EVP_MD_CTX_new(); EVP_MD_CTX_init(ctx); const EVP_MD *evp_digest = NULL; if ((evp_digest = EVP_get_digestbyname(s_digest)) == NULL) { oidc_jose_error(err, "no OpenSSL digest algorithm found for algorithm \"%s\"", s_digest); return FALSE; } if (!EVP_DigestInit_ex(ctx, evp_digest, NULL)) { oidc_jose_error_openssl(err, "EVP_DigestInit_ex"); return FALSE; } if (!EVP_DigestUpdate(ctx, input, input_len)) { oidc_jose_error_openssl(err, "EVP_DigestUpdate"); return FALSE; } if (!EVP_DigestFinal(ctx, md_value, output_len)) { oidc_jose_error_openssl(err, "EVP_DigestFinal"); return FALSE; } EVP_MD_CTX_free(ctx); *output = apr_pmemdup(pool, md_value, *output_len); return TRUE; } /* * return the OpenSSL hash algorithm associated with a specified JWT algorithm */ static char *oidc_jose_alg_to_openssl_digest(const char *alg) { if ((_oidc_strcmp(alg, CJOSE_HDR_ALG_RS256) == 0) || (_oidc_strcmp(alg, CJOSE_HDR_ALG_PS256) == 0) || (_oidc_strcmp(alg, CJOSE_HDR_ALG_HS256) == 0) || (_oidc_strcmp(alg, CJOSE_HDR_ALG_ES256) == 0)) { return LN_sha256; } if ((_oidc_strcmp(alg, CJOSE_HDR_ALG_RS384) == 0) || (_oidc_strcmp(alg, CJOSE_HDR_ALG_PS384) == 0) || (_oidc_strcmp(alg, CJOSE_HDR_ALG_HS384) == 0) || (_oidc_strcmp(alg, CJOSE_HDR_ALG_ES384) == 0)) { return LN_sha384; } if ((_oidc_strcmp(alg, CJOSE_HDR_ALG_RS512) == 0) || (_oidc_strcmp(alg, CJOSE_HDR_ALG_PS512) == 0) || (_oidc_strcmp(alg, CJOSE_HDR_ALG_HS512) == 0) || (_oidc_strcmp(alg, CJOSE_HDR_ALG_ES512) == 0)) { return LN_sha512; } return NULL; } /* * hash a string value with the specified algorithm */ apr_byte_t oidc_jose_hash_string(apr_pool_t *pool, const char *alg, const char *msg, char **hash, unsigned int *hash_len, oidc_jose_error_t *err) { char *s_digest = oidc_jose_alg_to_openssl_digest(alg); if (s_digest == NULL) { oidc_jose_error(err, "no OpenSSL digest algorithm name found for algorithm \"%s\"", alg); return FALSE; } return oidc_jose_hash_bytes(pool, s_digest, (const unsigned char *)msg, _oidc_strlen(msg), (unsigned char **)hash, hash_len, err); } /* * return hash length for the specified JOSE algorithm */ int oidc_jose_hash_length(const char *alg) { if ((_oidc_strcmp(alg, CJOSE_HDR_ALG_RS256) == 0) || (_oidc_strcmp(alg, CJOSE_HDR_ALG_PS256) == 0) || (_oidc_strcmp(alg, CJOSE_HDR_ALG_HS256) == 0) || (_oidc_strcmp(alg, CJOSE_HDR_ALG_ES256) == 0)) { return 32; } if ((_oidc_strcmp(alg, CJOSE_HDR_ALG_RS384) == 0) || (_oidc_strcmp(alg, CJOSE_HDR_ALG_PS384) == 0) || (_oidc_strcmp(alg, CJOSE_HDR_ALG_HS384) == 0) || (_oidc_strcmp(alg, CJOSE_HDR_ALG_ES384) == 0)) { return 48; } if ((_oidc_strcmp(alg, CJOSE_HDR_ALG_RS512) == 0) || (_oidc_strcmp(alg, CJOSE_HDR_ALG_PS512) == 0) || (_oidc_strcmp(alg, CJOSE_HDR_ALG_HS512) == 0) || (_oidc_strcmp(alg, CJOSE_HDR_ALG_ES512) == 0)) { return 64; } return 0; } /* * read an x509 certificate and its public key from the provided input */ static apr_byte_t oidc_jwk_x509_read(apr_pool_t *pool, BIO *input, char **encoded_certificate, EVP_PKEY **pkey, X509 **rx509, oidc_jose_error_t *err) { apr_byte_t rv = FALSE; X509 *x509 = NULL; int encoded_cert_len = 0; /* read the X.509 struct - assume input is no public key */ if ((x509 = PEM_read_bio_X509_AUX(input, NULL, NULL, NULL)) == NULL) { oidc_jose_error_openssl(err, "PEM_read_bio_X509_AUX"); goto end; } if (pkey) { /* get the public key struct from the X.509 struct */ if ((*pkey = X509_get_pubkey(x509)) == NULL) { oidc_jose_error_openssl(err, "X509_get_pubkey"); goto end; } } /* populate x5c certificate */ encoded_cert_len = oidc_jose_util_get_b64encoded_certificate_data(pool, x509, encoded_certificate, err); rv = (encoded_certificate != NULL) && (encoded_cert_len > 0); end: if (x509) { if (rx509) *rx509 = x509; else X509_free(x509); } return rv; } /* * extract a JWK struct and a fingerprint from an OpenSSL RSA key */ static apr_byte_t _oidc_jwk_rsa_key_to_jwk(apr_pool_t *pool, EVP_PKEY *pkey, oidc_jwk_t **oidc_jwk, char **fp, int *fp_len, oidc_jose_error_t *err) { apr_byte_t rv = FALSE; cjose_err cjose_err; BIGNUM *rsa_n = NULL, *rsa_e = NULL, *rsa_d = NULL; cjose_jwk_rsa_keyspec key_spec; _oidc_memset(&key_spec, 0, sizeof(cjose_jwk_rsa_keyspec)); #if OPENSSL_VERSION_NUMBER >= 0x30000000L EVP_PKEY_get_bn_param(pkey, OSSL_PKEY_PARAM_RSA_N, &rsa_n); EVP_PKEY_get_bn_param(pkey, OSSL_PKEY_PARAM_RSA_E, &rsa_e); EVP_PKEY_get_bn_param(pkey, OSSL_PKEY_PARAM_RSA_D, &rsa_d); #else /* get the RSA key from the public key struct */ RSA *rsa = (RSA *)EVP_PKEY_get1_RSA(pkey); if (rsa == NULL) { oidc_jose_error_openssl(err, "EVP_PKEY_get1_RSA"); goto end; } #if OPENSSL_VERSION_NUMBER >= 0x10100005L && !defined(LIBRESSL_VERSION_NUMBER) RSA_get0_key(rsa, (const BIGNUM **)&rsa_n, (const BIGNUM **)&rsa_e, (const BIGNUM **)&rsa_d); #else rsa_n = rsa->n; rsa_e = rsa->e; rsa_d = rsa->d; #endif RSA_free(rsa); #endif /* convert the modulus bignum in to a key/len */ key_spec.nlen = BN_num_bytes(rsa_n); key_spec.n = apr_pcalloc(pool, key_spec.nlen); BN_bn2bin(rsa_n, key_spec.n); /* convert the exponent bignum in to a key/len */ key_spec.elen = BN_num_bytes(rsa_e); key_spec.e = apr_pcalloc(pool, key_spec.elen); BN_bn2bin(rsa_e, key_spec.e); /* convert the private exponent bignum in to a key/len */ if (rsa_d != NULL) { key_spec.dlen = BN_num_bytes(rsa_d); key_spec.d = apr_pcalloc(pool, key_spec.dlen); BN_bn2bin(rsa_d, key_spec.d); } (*oidc_jwk)->cjose_jwk = cjose_jwk_create_RSA_spec(&key_spec, &cjose_err); if ((*oidc_jwk)->cjose_jwk == NULL) { oidc_jose_error(err, "cjose_jwk_create_RSA_spec failed: %s", oidc_cjose_e2s(pool, cjose_err)); goto end; } *fp_len = key_spec.nlen + key_spec.elen; *fp = apr_pcalloc(pool, *fp_len); _oidc_memcpy(*fp, key_spec.n, key_spec.nlen); _oidc_memcpy(*fp + key_spec.nlen, key_spec.e, key_spec.elen); rv = TRUE; end: #if OPENSSL_VERSION_NUMBER >= 0x30000000L if (rsa_n) BN_clear_free(rsa_n); if (rsa_e) BN_clear_free(rsa_e); if (rsa_d) BN_clear_free(rsa_d); #endif return rv; } #if (OIDC_JOSE_EC_SUPPORT) /* * extract a JWK struct and a fingerprint from an OpenSSL Elliptic Curve key */ static apr_byte_t _oidc_jwk_ec_key_to_jwk(apr_pool_t *pool, EVP_PKEY *pkey, oidc_jwk_t **oidc_jwk, char **fp, int *fp_len, oidc_jose_error_t *err) { apr_byte_t rv = FALSE; cjose_err cjose_err; cjose_jwk_ec_keyspec ec_keyspec; int crv = 0; BIGNUM *ec_x = NULL, *ec_y = NULL, *ec_d = NULL; #if OPENSSL_VERSION_NUMBER >= 0x30000000L char curve_name[64]; size_t curve_name_len = 0; EVP_PKEY_get_bn_param(pkey, OSSL_PKEY_PARAM_EC_PUB_X, &ec_x); EVP_PKEY_get_bn_param(pkey, OSSL_PKEY_PARAM_EC_PUB_Y, &ec_y); EVP_PKEY_get_bn_param(pkey, OSSL_PKEY_PARAM_PRIV_KEY, &ec_d); if (!EVP_PKEY_get_utf8_string_param(pkey, OSSL_PKEY_PARAM_GROUP_NAME, curve_name, sizeof(curve_name), &curve_name_len)) { oidc_jose_error_openssl(err, "EVP_PKEY_get_utf8_string_param(OSSL_PKEY_PARAM_GROUP_NAME)"); goto end; } crv = OBJ_sn2nid(curve_name); #else EC_KEY *eckey = (EC_KEY *)EVP_PKEY_get1_EC_KEY(pkey); if (eckey == NULL) { oidc_jose_error_openssl(err, "EVP_PKEY_get1_EC_KEY"); goto end; } const EC_GROUP *ec_group = EC_KEY_get0_group(eckey); const EC_POINT *ecpoint = EC_KEY_get0_public_key(eckey); crv = EC_GROUP_get_curve_name(ec_group); ec_x = BN_new(); ec_y = BN_new(); if (EC_POINT_get_affine_coordinates_GFp(ec_group, ecpoint, ec_x, ec_y, NULL) == 0) { oidc_jose_error_openssl(err, "EC_POINT_get_affine_coordinates_GFp"); goto end; } ec_d = (BIGNUM *)EC_KEY_get0_private_key(eckey); if (crv == 0) { oidc_jose_error_openssl(err, "EC_GROUP_get_curve_name"); goto end; } EC_KEY_free(eckey); #endif _oidc_memset(&ec_keyspec, 0, sizeof(cjose_jwk_ec_keyspec)); ec_keyspec.crv = crv; ec_keyspec.xlen = BN_num_bytes(ec_x); ec_keyspec.x = apr_pcalloc(pool, ec_keyspec.xlen); BN_bn2bin(ec_x, ec_keyspec.x); ec_keyspec.ylen = BN_num_bytes(ec_y); ec_keyspec.y = apr_pcalloc(pool, ec_keyspec.ylen); BN_bn2bin(ec_y, ec_keyspec.y); if (ec_d != NULL) { ec_keyspec.dlen = BN_num_bytes(ec_d); ec_keyspec.d = apr_pcalloc(pool, ec_keyspec.dlen); BN_bn2bin(ec_d, ec_keyspec.d); } (*oidc_jwk)->cjose_jwk = cjose_jwk_create_EC_spec(&ec_keyspec, &cjose_err); if ((*oidc_jwk)->cjose_jwk == NULL) { oidc_jose_error(err, "cjose_jwk_create_EC_spec failed: %s", oidc_cjose_e2s(pool, cjose_err)); goto end; } apr_uint32_t b = htonl(crv); *fp_len = sizeof(b) + ec_keyspec.xlen + ec_keyspec.ylen; *fp = apr_pcalloc(pool, *fp_len); _oidc_memcpy(*fp, &b, sizeof(b)); _oidc_memcpy(*fp + sizeof(b), ec_keyspec.x, ec_keyspec.xlen); _oidc_memcpy(*fp + sizeof(b) + ec_keyspec.xlen, ec_keyspec.y, ec_keyspec.ylen); rv = TRUE; end: if (ec_x) BN_clear_free(ec_x); if (ec_y) BN_clear_free(ec_y); #if OPENSSL_VERSION_NUMBER >= 0x30000000L if (ec_d) BN_clear_free(ec_d); #endif return rv; } #endif /* * convert the PEM public key - possibly in a X.509 certificate - in the BIO pointed to * by "input" to a JSON Web Key object */ apr_byte_t oidc_jwk_pem_bio_to_jwk(apr_pool_t *pool, BIO *input, const char *kid, oidc_jwk_t **oidc_jwk, int is_private_key, oidc_jose_error_t *err) { cjose_err cjose_err; X509 *x509 = NULL; EVP_PKEY *pkey = NULL; int pkey_type = 0; apr_byte_t rv = FALSE; char *x509_pem_encoded_certificate = NULL; unsigned char *x509_bytes = NULL; int x509_cert_length = 0; char *fp = NULL; int fp_len = 0; *oidc_jwk = oidc_jwk_new(pool); if (is_private_key) { /* get the private key struct from the BIO */ if ((pkey = PEM_read_bio_PrivateKey(input, NULL, NULL, NULL)) == NULL) { oidc_jose_error_openssl(err, "PEM_read_bio_PrivateKey"); goto end; } } else { /* read public key */ if ((pkey = PEM_read_bio_PUBKEY(input, NULL, NULL, NULL)) == NULL) { /* not a public key - reset the buffer */ BIO_reset(input); if (oidc_jwk_x509_read(pool, input, &x509_pem_encoded_certificate, &pkey, &x509, err) == FALSE) goto end; /* certificate is present, fill the jwkset with certificate entries */ /* populate first x5c certificate */ (*oidc_jwk)->x5c = apr_array_make(pool, 1, sizeof(char *)); if ((*oidc_jwk)->x5c == NULL) { oidc_jose_error(err, "apr_array_make failed"); goto end; } APR_ARRAY_PUSH((*oidc_jwk)->x5c, char *) = x509_pem_encoded_certificate; /* populate thumbprints entries */ #if OPENSSL_VERSION_NUMBER < 0x000907000L // openssl below 0.9.7 does not allocate memory for you :o x509_cert_length = i2d_X509(x509, NULL); if (x509_cert_length <= 0) { oidc_jose_error_openssl(err, "i2d_X509"); goto end; } x509_bytes = (unsigned char *)OPENSSL_malloc(pool, x509_cert_length + 1); const unsigned char *p = x509_bytes; x509_cert_length = i2d_X509(x509, &p); #else x509_cert_length = i2d_X509(x509, &x509_bytes); #endif if (x509_cert_length < 0) { oidc_jose_error_openssl(err, "i2d_X509"); goto end; } /* populate x5t */ oidc_jose_hash_and_base64url_encode(pool, OIDC_JOSE_ALG_SHA1, (const char *)x509_bytes, x509_cert_length, &(*oidc_jwk)->x5t, err); /* populate x5t_S256 */ oidc_jose_hash_and_base64url_encode(pool, OIDC_JOSE_ALG_SHA256, (const char *)x509_bytes, x509_cert_length, &(*oidc_jwk)->x5t_S256, err); while (oidc_jwk_x509_read(pool, input, &x509_pem_encoded_certificate, NULL, NULL, err) == TRUE) APR_ARRAY_PUSH((*oidc_jwk)->x5c, char *) = x509_pem_encoded_certificate; } } #if OPENSSL_VERSION_NUMBER >= 0x30000000L pkey_type = EVP_PKEY_get_base_id(pkey); #elif (OPENSSL_VERSION_NUMBER > 0x10100000) pkey_type = EVP_PKEY_base_id(pkey); #else pkey_type = EVP_PKEY_type(pkey->type); #endif switch (pkey_type) { case EVP_PKEY_RSA: if (_oidc_jwk_rsa_key_to_jwk(pool, pkey, oidc_jwk, &fp, &fp_len, err) == FALSE) goto end; break; #if (OIDC_JOSE_EC_SUPPORT) case EVP_PKEY_EC: if (_oidc_jwk_ec_key_to_jwk(pool, pkey, oidc_jwk, &fp, &fp_len, err) == FALSE) goto end; break; #endif default: oidc_jose_error(err, "unhandled key type: %d", pkey_type); break; } if (oidc_jwk_set_or_generate_kid(pool, (*oidc_jwk)->cjose_jwk, kid, fp, fp_len, err) == FALSE) { goto end; } (*oidc_jwk)->kid = apr_pstrdup(pool, cjose_jwk_get_kid((*oidc_jwk)->cjose_jwk, &cjose_err)); (*oidc_jwk)->kty = cjose_jwk_get_kty((*oidc_jwk)->cjose_jwk, &cjose_err); rv = TRUE; end: if (x509_bytes) OPENSSL_free(x509_bytes); if (pkey) EVP_PKEY_free(pkey); if (x509) X509_free(x509); return rv; } /* * parse a PEM-formatted public or private key from the specified file */ static apr_byte_t oidc_jwk_parse_pem_key(apr_pool_t *pool, int is_private_key, const char *kid, const char *filename, oidc_jwk_t **jwk, oidc_jose_error_t *err) { BIO *input = NULL; apr_byte_t rv = FALSE; if ((input = BIO_new(BIO_s_file())) == NULL) { oidc_jose_error_openssl(err, "BIO_new/BIO_s_file"); goto end; } if (BIO_read_filename(input, filename) <= 0) { oidc_jose_error_openssl(err, "BIO_read_filename"); goto end; } if (oidc_jwk_pem_bio_to_jwk(pool, input, kid, jwk, is_private_key, err) == FALSE) goto end; rv = TRUE; end: if (input) BIO_free(input); return rv; } #define OIDC_JOSE_CERT_BEGIN "-----BEGIN CERTIFICATE-----" #define OIDC_JOSE_CERT_END "-----END CERTIFICATE-----" /* * parse a PEM-formatted key from a JSON object in to a cjose JWK object */ static apr_byte_t _oidc_jwk_parse_x5c(apr_pool_t *pool, json_t *json, cjose_jwk_t **jwk, oidc_jose_error_t *err) { apr_byte_t rv = FALSE; const char *kid = NULL; oidc_jwk_t *oidc_jwk = NULL; /* get the "x5c" array element from the JSON object */ json_t *v = json_object_get(json, OIDC_JOSE_HDR_X5C); if (v == NULL) { oidc_jose_error(err, "JSON key \"%s\" could not be found", OIDC_JOSE_HDR_X5C); return FALSE; } if (!json_is_array(v)) { oidc_jose_error(err, "JSON key \"%s\" was found but its value is not a JSON array", OIDC_JOSE_HDR_X5C); return FALSE; } /* take the first element of the array */ v = json_array_get(v, 0); if (v == NULL) { oidc_jose_error(err, "first element in JSON array is \"null\""); return FALSE; } if (!json_is_string(v)) { oidc_jose_error(err, "first element in array is not a JSON string"); return FALSE; } const char *s_x5c = json_string_value(v); /* PEM-format it */ const int len = 75; int i = 0; char *s = apr_psprintf(pool, "%s\n", OIDC_JOSE_CERT_BEGIN); while (i < _oidc_strlen(s_x5c)) { s = apr_psprintf(pool, "%s%s\n", s, apr_pstrmemdup(pool, s_x5c + i, len)); i += len; } s = apr_psprintf(pool, "%s%s\n", s, OIDC_JOSE_CERT_END); BIO *input = NULL; /* put it in BIO memory */ if ((input = BIO_new(BIO_s_mem())) == NULL) { oidc_jose_error_openssl(err, "memory allocation BIO_new/BIO_s_mem"); return FALSE; } if (BIO_puts(input, s) <= 0) { BIO_free(input); oidc_jose_error_openssl(err, "BIO_puts"); return FALSE; } v = json_object_get(json, CJOSE_HDR_KID); if ((v != NULL) && json_is_string(v)) { kid = json_string_value(v); } /* do the actual parsing */ rv = oidc_jwk_pem_bio_to_jwk(pool, input, kid, &oidc_jwk, FALSE, err); *jwk = oidc_jwk->cjose_jwk; BIO_free(input); return rv; } /* * parse a PEM formatted private key to a JWK */ apr_byte_t oidc_jwk_parse_pem_private_key(apr_pool_t *pool, const char *kid, const char *filename, oidc_jwk_t **jwk, oidc_jose_error_t *err) { return oidc_jwk_parse_pem_key(pool, TRUE, kid, filename, jwk, err); } /* * parse a PEM formatted public key file to a JWK */ apr_byte_t oidc_jwk_parse_pem_public_key(apr_pool_t *pool, const char *kid, const char *filename, oidc_jwk_t **jwk, oidc_jose_error_t *err) { return oidc_jwk_parse_pem_key(pool, FALSE, kid, filename, jwk, err); } mod_auth_openidc-2.4.16.10/src/jose.h000066400000000000000000000264711476721736500172600ustar00rootroot00000000000000/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ /*************************************************************************** * Copyright (C) 2017-2025 ZmartZone Holding BV * Copyright (C) 2013-2017 Ping Identity Corporation * All rights reserved. * * DISCLAIMER OF WARRANTIES: * * THE SOFTWARE PROVIDED HEREUNDER IS PROVIDED ON AN "AS IS" BASIS, WITHOUT * ANY WARRANTIES OR REPRESENTATIONS EXPRESS, IMPLIED OR STATUTORY; INCLUDING, * WITHOUT LIMITATION, WARRANTIES OF QUALITY, PERFORMANCE, NONINFRINGEMENT, * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. NOR ARE THERE ANY * WARRANTIES CREATED BY A COURSE OR DEALING, COURSE OF PERFORMANCE OR TRADE * USAGE. FURTHERMORE, THERE ARE NO WARRANTIES THAT THE SOFTWARE WILL MEET * YOUR NEEDS OR BE FREE FROM ERRORS, OR THAT THE OPERATION OF THE SOFTWARE * WILL BE UNINTERRUPTED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * JSON Object Signing and Encryption * * @Author: Hans Zandbelt - hans.zandbelt@openidc.com */ #ifndef _MOD_AUTH_OPENIDC_JOSE_H_ #define _MOD_AUTH_OPENIDC_JOSE_H_ #include "const.h" #include #include #include #include #include #include #ifndef APR_ARRAY_IDX #define APR_ARRAY_IDX(ary, i, type) (((type *)(ary)->elts)[i]) #endif #ifndef APR_ARRAY_PUSH #define APR_ARRAY_PUSH(ary, type) (*((type *)apr_array_push(ary))) #endif #define OIDC_JOSE_ALG_SHA1 "sha1" #define OIDC_JOSE_ALG_SHA256 "sha256" /* indicate support for OpenSSL version dependent features */ #define OIDC_JOSE_EC_SUPPORT OPENSSL_VERSION_NUMBER >= 0x1000100f #define OIDC_JOSE_GCM_SUPPORT OPENSSL_VERSION_NUMBER >= 0x1000100f /* error message element sizes */ #define OIDC_JOSE_ERROR_TEXT_LENGTH 200 #define OIDC_JOSE_ERROR_SOURCE_LENGTH 80 #define OIDC_JOSE_ERROR_FUNCTION_LENGTH 80 /* the OIDC jwk fields as references in RFC 5741 */ #define OIDC_JOSE_JWK_KID_STR "kid" // Key ID #define OIDC_JOSE_JWK_KTY_STR "kty" // Key type #define OIDC_JOSE_JWK_USE_STR "use" // Key usage (enc|sig) #define OIDC_JOSE_JWK_X5C_STR "x5c" // X509 certificate chain #define OIDC_JOSE_JWK_X5T_STR "x5t" // X509 SHA-1 thumbprint #define OIDC_JOSE_JWK_X5T256_STR "x5t#S256" // X509 SHA-256 thumbprint #define OIDC_JOSE_JWK_SIG_STR "sig" // use signature type #define OIDC_JOSE_JWK_ENC_STR "enc" // use encryption type /* the OIDC jwks fields from RFC 5741 */ #define OIDC_JOSE_JWKS_KEYS_STR "keys" // Array of JWKs /* struct for returning errors to the caller */ typedef struct { char source[OIDC_JOSE_ERROR_SOURCE_LENGTH]; int line; char function[OIDC_JOSE_ERROR_FUNCTION_LENGTH]; char text[OIDC_JOSE_ERROR_TEXT_LENGTH]; } oidc_jose_error_t; /* * error handling functions */ #define oidc_jose_error(err, msg, ...) _oidc_jose_error_set(err, __FILE__, __LINE__, __FUNCTION__, msg, ##__VA_ARGS__) #define oidc_jose_error_openssl(err, msg, ...) \ _oidc_jose_error_set(err, __FILE__, __LINE__, __FUNCTION__, "%s() failed: %s", msg, \ ERR_error_string(ERR_get_error(), NULL), ##__VA_ARGS__) #define oidc_jose_e2s(pool, err) apr_psprintf(pool, "[%s:%d: %s]: %s", err.source, err.line, err.function, err.text) #define oidc_cjose_e2s(pool, cjose_err) \ apr_psprintf(pool, "%s [file: %s, function: %s, line: %ld]", cjose_err.message, cjose_err.file, \ cjose_err.function, cjose_err.line) /* * helper functions */ /* helpers to find out about the supported ala/enc algorithms */ apr_array_header_t *oidc_jose_jws_supported_algorithms(apr_pool_t *pool); apr_byte_t oidc_jose_jws_algorithm_is_supported(apr_pool_t *pool, const char *alg); apr_array_header_t *oidc_jose_jwe_supported_algorithms(apr_pool_t *pool); apr_byte_t oidc_jose_jwe_algorithm_is_supported(apr_pool_t *pool, const char *alg); apr_array_header_t *oidc_jose_jwe_supported_encryptions(apr_pool_t *pool); apr_byte_t oidc_jose_jwe_encryption_is_supported(apr_pool_t *pool, const char *enc); /* hash helpers */ apr_byte_t oidc_jose_hash_string(apr_pool_t *pool, const char *alg, const char *msg, char **hash, unsigned int *hash_len, oidc_jose_error_t *err); int oidc_jose_hash_length(const char *alg); apr_byte_t oidc_jose_hash_bytes(apr_pool_t *pool, const char *s_digest, const unsigned char *input, unsigned int input_len, unsigned char **output, unsigned int *output_len, oidc_jose_error_t *err); apr_byte_t oidc_jose_hash_and_base64url_encode(apr_pool_t *pool, const char *openssl_hash_algo, const char *input, int input_len, char **output, oidc_jose_error_t *err); /* return a string claim value from a JSON object */ apr_byte_t oidc_jose_get_string(apr_pool_t *pool, json_t *json, const char *claim_name, apr_byte_t is_mandatory, char **result, oidc_jose_error_t *err); apr_byte_t oidc_jose_compress(apr_pool_t *pool, const char *input, int input_len, char **output, int *output_len, oidc_jose_error_t *err); apr_byte_t oidc_jose_uncompress(apr_pool_t *pool, const char *input, int input_len, char **output, int *output_len, oidc_jose_error_t *err); /* a parsed JWK/JWT JSON object */ typedef struct oidc_jose_json_t { /* parsed JSON struct representation */ json_t *json; /* string representation */ char *str; } oidc_jose_json_t; /* * JSON Web Key handling */ /* parsed JWK */ typedef struct oidc_jwk_t { /* use type */ char *use; /* key type */ int kty; /* key identifier */ char *kid; /* X.509 Certificate Chain */ apr_array_header_t *x5c; /* X.509 Certificate SHA-1 Thumbprint */ char *x5t; /* X.509 Certificate SHA-256 Thumbprint */ char *x5t_S256; /* cjose JWK structure */ cjose_jwk_t *cjose_jwk; } oidc_jwk_t; /* decrypt a JWT */ apr_byte_t oidc_jwe_decrypt(apr_pool_t *pool, const char *input_json, apr_hash_t *keys, char **plaintext, int *plaintext_len, oidc_jose_error_t *err, apr_byte_t import_must_succeed); /* parse a JSON string (JWK) to a JWK struct */ oidc_jwk_t *oidc_jwk_parse(apr_pool_t *pool, json_t *json, oidc_jose_error_t *err); oidc_jwk_t *oidc_jwk_copy(apr_pool_t *pool, const oidc_jwk_t *jwk); /* parse a JSON object (JWK) in to a JWK struct */ apr_byte_t oidc_jwk_parse_json(apr_pool_t *pool, json_t *json, oidc_jwk_t **jwk, oidc_jose_error_t *err); /* parse a JSON object (JWKS) to a list of JWK structs */ apr_byte_t oidc_jwks_parse_json(apr_pool_t *pool, json_t *json, apr_array_header_t **jwk_list, oidc_jose_error_t *err); /* test if JSON object looks like JWK */ apr_byte_t oidc_is_jwk(json_t *json); /* test if JSON object looks like JWKS */ apr_byte_t oidc_is_jwks(json_t *json); /* convert a JWK struct to a JSON string */ apr_byte_t oidc_jwk_to_json(apr_pool_t *pool, const oidc_jwk_t *jwk, char **s_json, oidc_jose_error_t *err); /* destroy resources allocated for a JWK struct */ void oidc_jwk_destroy(oidc_jwk_t *jwk); /* destroy a list of JWKs structs */ void oidc_jwk_list_destroy_hash(apr_hash_t *key); apr_array_header_t *oidc_jwk_list_copy(apr_pool_t *pool, apr_array_header_t *src); void oidc_jwk_list_destroy(apr_array_header_t *keys_list); /* create an "oct" symmetric JWK */ oidc_jwk_t *oidc_jwk_create_symmetric_key(apr_pool_t *pool, const char *kid, const unsigned char *key, unsigned int key_len, apr_byte_t set_kid, oidc_jose_error_t *err); /* parse an X.509 PEM formatted certificate file with a public key to a JWK struct */ apr_byte_t oidc_jwk_parse_pem_public_key(apr_pool_t *pool, const char *kid, const char *filename, oidc_jwk_t **jwk, oidc_jose_error_t *err); /* parse an X.509 PEM formatted private key file to a JWK */ apr_byte_t oidc_jwk_parse_pem_private_key(apr_pool_t *pool, const char *kid, const char *filename, oidc_jwk_t **jwk, oidc_jose_error_t *err); /* * JSON Web Token handling */ /* represents NULL timestamp */ #define OIDC_JWT_CLAIM_TIME_EMPTY -1 /* a parsed JWT header */ typedef struct oidc_jwt_hdr_t { /* parsed header value */ oidc_jose_json_t value; /* JWT "alg" claim value; signing algorithm */ char *alg; /* JWT "kid" claim value; key identifier */ char *kid; /* JWT "enc" claim value; encryption algorithm */ char *enc; /* JWT "x5t" thumbprint */ char *x5t; } oidc_jwt_hdr_t; /* parsed JWT payload */ typedef struct oidc_jwt_payload_t { /* parsed payload value */ oidc_jose_json_t value; /* JWT "iss" claim value; JWT issuer */ char *iss; /* JWT "sub" claim value; subject/principal */ char *sub; /* parsed JWT "exp" claim value; token expiry */ double exp; /* parsed JWT "iat" claim value; issued-at timestamp */ double iat; } oidc_jwt_payload_t; /* parsed JWT */ typedef struct oidc_jwt_t { /* parsed JWT header */ oidc_jwt_hdr_t header; /* parsed JWT payload */ oidc_jwt_payload_t payload; /* cjose JWS structure */ cjose_jws_t *cjose_jws; } oidc_jwt_t; /* parse a string into a JSON Web Token struct and (optionally) decrypt it */ apr_byte_t oidc_jwt_parse(apr_pool_t *pool, const char *s_json, oidc_jwt_t **j_jwt, apr_hash_t *keys, apr_byte_t compress, oidc_jose_error_t *err); /* sign a JWT with a JWK */ apr_byte_t oidc_jwt_sign(apr_pool_t *pool, oidc_jwt_t *jwt, oidc_jwk_t *jwk, apr_byte_t compress, oidc_jose_error_t *err); /* verify a JWT a key in a list of JWKs */ apr_byte_t oidc_jwt_verify(apr_pool_t *pool, oidc_jwt_t *jwt, apr_hash_t *keys, oidc_jose_error_t *err); /* perform compact serialization on a JWT and return the resulting string */ char *oidc_jose_jwt_serialize(apr_pool_t *pool, oidc_jwt_t *jwt, oidc_jose_error_t *err); /* encrypt JWT */ apr_byte_t oidc_jwt_encrypt(apr_pool_t *pool, oidc_jwt_t *jwe, oidc_jwk_t *jwk, const char *payload, int payload_len, char **serialized, oidc_jose_error_t *err); /* create a new JWT */ oidc_jwt_t *oidc_jwt_new(apr_pool_t *pool, int create_header, int create_payload); /* destroy resources allocated for JWT */ void oidc_jwt_destroy(oidc_jwt_t *); /* get a header value from a JWT */ const char *oidc_jwt_hdr_get(oidc_jwt_t *jwt, const char *key); /* return the key type of a JWT */ int oidc_jwt_alg2kty(oidc_jwt_t *jwt); /* return the key size for an algorithm */ unsigned int oidc_alg2keysize(const char *alg); apr_byte_t oidc_jwk_pem_bio_to_jwk(apr_pool_t *pool, BIO *input, const char *kid, oidc_jwk_t **jwk, int is_private_key, oidc_jose_error_t *err); #endif /* _MOD_AUTH_OPENIDC_JOSE_H_ */ mod_auth_openidc-2.4.16.10/src/metadata.c000066400000000000000000001754331476721736500200760ustar00rootroot00000000000000/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ /*************************************************************************** * Copyright (C) 2017-2025 ZmartZone Holding BV * Copyright (C) 2013-2017 Ping Identity Corporation * All rights reserved. * * DISCLAIMER OF WARRANTIES: * * THE SOFTWARE PROVIDED HEREUNDER IS PROVIDED ON AN "AS IS" BASIS, WITHOUT * ANY WARRANTIES OR REPRESENTATIONS EXPRESS, IMPLIED OR STATUTORY; INCLUDING, * WITHOUT LIMITATION, WARRANTIES OF QUALITY, PERFORMANCE, NONINFRINGEMENT, * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. NOR ARE THERE ANY * WARRANTIES CREATED BY A COURSE OR DEALING, COURSE OF PERFORMANCE OR TRADE * USAGE. FURTHERMORE, THERE ARE NO WARRANTIES THAT THE SOFTWARE WILL MEET * YOUR NEEDS OR BE FREE FROM ERRORS, OR THAT THE OPERATION OF THE SOFTWARE * WILL BE UNINTERRUPTED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * OpenID Connect metadata handling routines, for both OP discovery and client registration * * @Author: Hans Zandbelt - hans.zandbelt@openidc.com */ #include "metadata.h" #include "cfg/dir.h" #include "cfg/oauth.h" #include "cfg/parse.h" #include "cfg/provider.h" #include "metrics.h" #include "mod_auth_openidc.h" #include "proto/proto.h" #include "util.h" #define OIDC_METADATA_SUFFIX_PROVIDER "provider" #define OIDC_METADATA_SUFFIX_CLIENT "client" #define OIDC_METADATA_SUFFIX_CONF "conf" #define OIDC_METADATA_ISSUER "issuer" #define OIDC_METADATA_RESPONSE_TYPES_SUPPORTED "response_types_supported" #define OIDC_METADATA_RESPONSE_MODES_SUPPORTED "response_modes_supported" #define OIDC_METADATA_AUTHORIZATION_ENDPOINT "authorization_endpoint" #define OIDC_METADATA_TOKEN_ENDPOINT "token_endpoint" #define OIDC_METADATA_INTROSPECTION_ENDPOINT "introspection_endpoint" #define OIDC_METADATA_USERINFO_ENDPOINT "userinfo_endpoint" #define OIDC_METADATA_REVOCATION_ENDPOINT "revocation_endpoint" #define OIDC_METADATA_PAR_ENDPOINT "pushed_authorization_request_endpoint" #define OIDC_METADATA_JWKS_URI "jwks_uri" #define OIDC_METADATA_SIGNED_JWKS_URI "signed_jwks_uri" #define OIDC_METADATA_TOKEN_ENDPOINT_AUTH_METHODS_SUPPORTED "token_endpoint_auth_methods_supported" #define OIDC_METADATA_INTROSPECTON_ENDPOINT_AUTH_METHODS_SUPPORTED "introspection_endpoint_auth_methods_supported" #define OIDC_METADATA_REGISTRATION_ENDPOINT "registration_endpoint" #define OIDC_METADATA_CHECK_SESSION_IFRAME "check_session_iframe" #define OIDC_METADATA_BACKCHANNEL_LOGOUT_SUPPORTED "backchannel_logout_supported" #define OIDC_METADATA_END_SESSION_ENDPOINT "end_session_endpoint" #define OIDC_METADATA_CLIENT_ID "client_id" #define OIDC_METADATA_CLIENT_SECRET "client_secret" #define OIDC_METADATA_CLIENT_SECRET_EXPIRES_AT "client_secret_expires_at" #define OIDC_METADATA_KEYS OIDC_JOSE_JWKS_KEYS_STR #define OIDC_METADATA_CLIENT_JWKS_URI "client_jwks_uri" #define OIDC_METADATA_ID_TOKEN_SIGNED_RESPONSE_ALG "id_token_signed_response_alg" #define OIDC_METADATA_ID_TOKEN_ENCRYPTED_RESPONSE_ALG "id_token_encrypted_response_alg" #define OIDC_METADATA_ID_TOKEN_ENCRYPTED_RESPONSE_ENC "id_token_encrypted_response_enc" #define OIDC_METADATA_ID_TOKEN_AUD_VALUES "id_token_aud_values" #define OIDC_METADATA_PROFILE "profile" #define OIDC_METADATA_USERINFO_SIGNED_RESPONSE_ALG "userinfo_signed_response_alg" #define OIDC_METADATA_USERINFO_ENCRYPTED_RESPONSE_ALG "userinfo_encrypted_response_alg" #define OIDC_METADATA_USERINFO_ENCRYPTED_RESPONSE_ENC "userinfo_encrypted_response_enc" #define OIDC_METADATA_CLIENT_NAME "client_name" #define OIDC_METADATA_REDIRECT_URIS "redirect_uris" #define OIDC_METADATA_RESPONSE_TYPES "response_types" #define OIDC_METADATA_GRANT_TYPES "grant_types" #define OIDC_METADATA_TOKEN_ENDPOINT_AUTH_METHOD "token_endpoint_auth_method" #define OIDC_METADATA_CONTACTS "contacts" #define OIDC_METADATA_INITIATE_LOGIN_URI "initiate_login_uri" #define OIDC_METADATA_FRONTCHANNEL_LOGOUT_URI "frontchannel_logout_uri" #define OIDC_METADATA_BACKCHANNEL_LOGOUT_URI "backchannel_logout_uri" #define OIDC_METADATA_POST_LOGOUT_REDIRECT_URIS "post_logout_redirect_uris" #define OIDC_METADATA_SSL_VALIDATE_SERVER "ssl_validate_server" #define OIDC_METADATA_VALIDATE_ISSUER "validate_issuer" #define OIDC_METADATA_SCOPE "scope" #define OIDC_METADATA_JWKS_REFRESH_INTERVAL "jwks_refresh_interval" #define OIDC_METADATA_IDTOKEN_IAT_SLACK "idtoken_iat_slack" #define OIDC_METADATA_SESSION_MAX_DURATION "session_max_duration" #define OIDC_METADATA_AUTH_REQUEST_PARAMS "auth_request_params" #define OIDC_METADATA_LOGOUT_REQUEST_PARAMS "logout_request_params" #define OIDC_METADATA_TOKEN_ENDPOINT_PARAMS "token_endpoint_params" #define OIDC_METADATA_RESPONSE_MODE "response_mode" #define OIDC_METADATA_PKCE_METHOD "pkce_method" #define OIDC_METADATA_DPOP_MODE "dpop_mode" #define OIDC_METADATA_CLIENT_CONTACT "client_contact" #define OIDC_METADATA_TOKEN_ENDPOINT_AUTH "token_endpoint_auth" #define OIDC_METADATA_REGISTRATION_TOKEN "registration_token" #define OIDC_METADATA_REGISTRATION_ENDPOINT_JSON "registration_endpoint_json" #define OIDC_METADATA_RESPONSE_TYPE "response_type" #define OIDC_METADATA_USERINFO_REFRESH_INTERVAL "userinfo_refresh_interval" #define OIDC_METADATA_TOKEN_ENDPOINT_TLS_CLIENT_CERT "token_endpoint_tls_client_cert" #define OIDC_METADATA_TOKEN_ENDPOINT_TLS_CLIENT_KEY "token_endpoint_tls_client_key" #define OIDC_METADATA_TOKEN_ENDPOINT_TLS_CLIENT_KEY_PWD "token_endpoint_tls_client_key_pwd" #define OIDC_METADATA_REQUEST_OBJECT "request_object" #define OIDC_METADATA_USERINFO_TOKEN_METHOD "userinfo_token_method" #define OIDC_METADATA_AUTH_REQUEST_METHOD "auth_request_method" #define OIDC_METADATA_RESPONSE_REQUIRE_ISS "response_require_iss" /* * get the metadata filename for a specified issuer (cq. urlencode it) */ static const char *oidc_metadata_issuer_to_filename(request_rec *r, const char *issuer) { /* strip leading https:// */ char *p = _oidc_strstr(issuer, "https://"); if (p == issuer) { p = apr_pstrdup(r->pool, issuer + _oidc_strlen("https://")); } else { p = _oidc_strstr(issuer, "http://"); if (p == issuer) { p = apr_pstrdup(r->pool, issuer + _oidc_strlen("http://")); } else { p = apr_pstrdup(r->pool, issuer); } } /* strip trailing '/' */ int n = _oidc_strlen(p); if (p[n - 1] == OIDC_CHAR_FORWARD_SLASH) p[n - 1] = '\0'; return oidc_http_url_encode(r, p); } /* * get the issuer from a metadata filename (cq. urldecode it) */ static const char *oidc_metadata_filename_to_issuer(request_rec *r, const char *filename) { char *result = apr_pstrdup(r->pool, filename); char *p = strrchr(result, OIDC_CHAR_DOT); *p = '\0'; p = oidc_http_url_decode(r, result); return apr_psprintf(r->pool, "https://%s", p); } /* * get the full path to the metadata file for a specified issuer and directory */ static const char *oidc_metadata_file_path(request_rec *r, oidc_cfg_t *cfg, const char *issuer, const char *type) { return apr_psprintf(r->pool, "%s/%s.%s", oidc_cfg_metadata_dir_get(cfg), oidc_metadata_issuer_to_filename(r, issuer), type); } /* * get the full path to the provider metadata file for a specified issuer */ static const char *oidc_metadata_provider_file_path(request_rec *r, const char *issuer) { oidc_cfg_t *cfg = ap_get_module_config(r->server->module_config, &auth_openidc_module); return oidc_metadata_file_path(r, cfg, issuer, OIDC_METADATA_SUFFIX_PROVIDER); } /* * get the full path to the client metadata file for a specified issuer */ static const char *oidc_metadata_client_file_path(request_rec *r, const char *issuer) { oidc_cfg_t *cfg = ap_get_module_config(r->server->module_config, &auth_openidc_module); return oidc_metadata_file_path(r, cfg, issuer, OIDC_METADATA_SUFFIX_CLIENT); } /* * get the full path to the custom config file for a specified issuer */ static const char *oidc_metadata_conf_path(request_rec *r, const char *issuer) { oidc_cfg_t *cfg = ap_get_module_config(r->server->module_config, &auth_openidc_module); return oidc_metadata_file_path(r, cfg, issuer, OIDC_METADATA_SUFFIX_CONF); } /* * get cache key for the JWKs file for a specified URI */ static const char *oidc_metadata_jwks_cache_key(const oidc_jwks_uri_t *jwks_uri) { return jwks_uri->signed_uri ? jwks_uri->signed_uri : jwks_uri->uri; } /* * read a JSON metadata file from disk */ static apr_byte_t oidc_metadata_file_read_json(request_rec *r, const char *path, json_t **result) { char *buf = NULL; /* read the file contents */ if (oidc_util_file_read(r, path, r->pool, &buf) == FALSE) return FALSE; /* decode the JSON contents of the buffer */ return oidc_util_decode_json_object(r, buf, result); } /* * check if the specified entry in metadata is a valid URI */ static apr_byte_t oidc_metadata_is_valid_uri(request_rec *r, const char *type, const char *issuer, json_t *json, const char *key, char **value, apr_byte_t is_mandatory) { char *s_value = NULL; oidc_util_json_object_get_string(r->pool, json, key, &s_value, NULL); if (s_value == NULL) { if (is_mandatory) { oidc_error(r, "%s (%s) JSON metadata does not contain the mandatory \"%s\" string entry", type, issuer, key); } return (!is_mandatory); } if (oidc_cfg_parse_is_valid_http_url(r->pool, s_value) != NULL) { oidc_warn(r, "\"%s\" is not a valid http URL for key \"%s\"", s_value, key); return FALSE; } if (value) *value = s_value; return TRUE; } /* * check if there's a valid entry in a string of arrays, with a preference */ static const char *oidc_metadata_valid_string_in_array(apr_pool_t *pool, json_t *json, const char *key, oidc_valid_function_t valid_function, char **value, apr_byte_t optional, const char *preference) { int i = 0; json_t *json_arr = json_object_get(json, key); apr_byte_t found = FALSE; if ((json_arr != NULL) && (json_is_array(json_arr))) { for (i = 0; i < json_array_size(json_arr); i++) { json_t *elem = json_array_get(json_arr, i); if (!json_is_string(elem)) continue; if (valid_function(pool, json_string_value(elem)) == NULL) { found = TRUE; if (value != NULL) { if ((preference != NULL) && (_oidc_strcmp(json_string_value(elem), preference) == 0)) { *value = apr_pstrdup(pool, json_string_value(elem)); break; } if (*value == NULL) { *value = apr_pstrdup(pool, json_string_value(elem)); } } } } if (found == FALSE) { return apr_psprintf(pool, "could not find a valid array string element for entry \"%s\"", key); } } else if (optional == FALSE) { return apr_psprintf(pool, "JSON object did not contain a \"%s\" array", key); } return NULL; } /* * check to see if JSON provider metadata is valid */ apr_byte_t oidc_metadata_provider_is_valid(request_rec *r, oidc_cfg_t *cfg, json_t *j_provider, const char *issuer) { /* get the "issuer" from the provider metadata and double-check that it matches what we looked for */ char *s_issuer = NULL; oidc_util_json_object_get_string(r->pool, j_provider, OIDC_METADATA_ISSUER, &s_issuer, NULL); if (s_issuer == NULL) { oidc_error(r, "provider (%s) JSON metadata did not contain an \"" OIDC_METADATA_ISSUER "\" string", issuer); return FALSE; } /* check that the issuer matches */ if (issuer != NULL) { if (oidc_util_issuer_match(issuer, s_issuer) == FALSE) { oidc_error(r, "requested issuer (%s) does not match the \"" OIDC_METADATA_ISSUER "\" value in the provider metadata file: %s", issuer, s_issuer); return FALSE; } } /* verify that the provider supports the a flow that we implement */ if (oidc_metadata_valid_string_in_array(r->pool, j_provider, OIDC_METADATA_RESPONSE_TYPES_SUPPORTED, oidc_cfg_parse_is_valid_response_type, NULL, FALSE, NULL) != NULL) { if (json_object_get(j_provider, OIDC_METADATA_RESPONSE_TYPES_SUPPORTED) != NULL) { oidc_error(r, "could not find a supported response type in provider metadata (%s) for entry " "\"" OIDC_METADATA_RESPONSE_TYPES_SUPPORTED "\"", issuer); return FALSE; } oidc_warn( r, "could not find (required) supported response types (\"" OIDC_METADATA_RESPONSE_TYPES_SUPPORTED "\") in provider metadata (%s); assuming that \"code\" flow is supported...", issuer); } /* verify that the provider supports a response_mode that we implement */ if (oidc_metadata_valid_string_in_array(r->pool, j_provider, OIDC_METADATA_RESPONSE_MODES_SUPPORTED, oidc_cfg_parse_is_valid_response_mode, NULL, TRUE, NULL) != NULL) { oidc_error(r, "could not find a supported response mode in provider metadata (%s) for entry " "\"" OIDC_METADATA_RESPONSE_MODES_SUPPORTED "\"", issuer); return FALSE; } /* check the required authorization endpoint */ if (oidc_metadata_is_valid_uri(r, OIDC_METADATA_SUFFIX_PROVIDER, issuer, j_provider, OIDC_METADATA_AUTHORIZATION_ENDPOINT, NULL, TRUE) == FALSE) return FALSE; /* check the optional token endpoint */ if (oidc_metadata_is_valid_uri(r, OIDC_METADATA_SUFFIX_PROVIDER, issuer, j_provider, OIDC_METADATA_TOKEN_ENDPOINT, NULL, FALSE) == FALSE) return FALSE; /* check the optional user info endpoint */ if (oidc_metadata_is_valid_uri(r, OIDC_METADATA_SUFFIX_PROVIDER, issuer, j_provider, OIDC_METADATA_USERINFO_ENDPOINT, NULL, FALSE) == FALSE) return FALSE; /* check the optional JWKs URI */ if (oidc_metadata_is_valid_uri(r, OIDC_METADATA_SUFFIX_PROVIDER, issuer, j_provider, OIDC_METADATA_JWKS_URI, NULL, FALSE) == FALSE) return FALSE; /* check the optional signed JWKs URI */ if (oidc_metadata_is_valid_uri(r, OIDC_METADATA_SUFFIX_PROVIDER, issuer, j_provider, OIDC_METADATA_SIGNED_JWKS_URI, NULL, FALSE) == FALSE) return FALSE; /* find out what type of authentication the token endpoint supports */ if (oidc_metadata_valid_string_in_array( r->pool, j_provider, OIDC_METADATA_TOKEN_ENDPOINT_AUTH_METHODS_SUPPORTED, oidc_cfg_get_valid_endpoint_auth_function(cfg), NULL, TRUE, NULL) != NULL) { oidc_error(r, "could not find a supported token endpoint authentication method in provider metadata (%s) " "for entry \"" OIDC_METADATA_TOKEN_ENDPOINT_AUTH_METHODS_SUPPORTED "\"", issuer); return FALSE; } return TRUE; } /* * check to see if dynamically registered JSON client metadata is valid and has not expired */ static apr_byte_t oidc_metadata_client_is_valid(request_rec *r, json_t *j_client, const char *issuer) { char *str; /* get a handle to the client_id we need to use for this provider */ str = NULL; oidc_util_json_object_get_string(r->pool, j_client, OIDC_METADATA_CLIENT_ID, &str, NULL); if (str == NULL) { oidc_error(r, "client (%s) JSON metadata did not contain a \"" OIDC_METADATA_CLIENT_ID "\" string", issuer); return FALSE; } /* get a handle to the client_secret we need to use for this provider */ str = NULL; oidc_util_json_object_get_string(r->pool, j_client, OIDC_METADATA_CLIENT_SECRET, &str, NULL); if (str == NULL) { oidc_warn(r, "client (%s) JSON metadata did not contain a \"" OIDC_METADATA_CLIENT_SECRET "\" string", issuer); } /* the expiry timestamp from the JSON object */ json_t *expires_at = json_object_get(j_client, OIDC_METADATA_CLIENT_SECRET_EXPIRES_AT); if ((expires_at == NULL) || (!json_is_integer(expires_at))) { oidc_debug( r, "client (%s) metadata did not contain a \"" OIDC_METADATA_CLIENT_SECRET_EXPIRES_AT "\" setting", issuer); /* assume that it never expires */ return TRUE; } /* see if it is unrestricted */ if (json_integer_value(expires_at) == 0) { oidc_debug(r, "client (%s) metadata never expires (" OIDC_METADATA_CLIENT_SECRET_EXPIRES_AT "=0)", issuer); return TRUE; } /* check if the value >= now */ if (apr_time_sec(apr_time_now()) > json_integer_value(expires_at)) { oidc_warn(r, "client (%s) secret expired", issuer); return FALSE; } oidc_debug(r, "client (%s) metadata is valid", issuer); return TRUE; } /* * checks if a parsed JWKs file is a valid one, cq. contains "keys" */ static apr_byte_t oidc_metadata_jwks_is_valid(request_rec *r, const char *url, const json_t *j_jwks) { const json_t *keys = json_object_get(j_jwks, OIDC_METADATA_KEYS); if ((keys == NULL) || (!json_is_array(keys))) { oidc_error( r, "JWKs JSON metadata obtained from URL \"%s\" did not contain a \"" OIDC_METADATA_KEYS "\" array", url); return FALSE; } return TRUE; } /* * check is a specified JOSE feature is supported */ static apr_byte_t oidc_metadata_conf_jose_is_supported(request_rec *r, json_t *j_conf, const char *issuer, const char *key, oidc_valid_function_t valid_function) { char *s_value = NULL; oidc_util_json_object_get_string(r->pool, j_conf, key, &s_value, NULL); if (s_value == NULL) return TRUE; const char *rv = valid_function(r->pool, s_value); if (rv != NULL) { oidc_error(r, "(%s) JSON conf data has \"%s\" entry but it contains an unsupported algorithm or " "encryption type: \"%s\" (%s)", issuer, key, s_value, rv); return FALSE; } return TRUE; } /* * check to see if JSON configuration data is valid */ static apr_byte_t oidc_metadata_conf_is_valid(request_rec *r, json_t *j_conf, const char *issuer) { if (oidc_metadata_conf_jose_is_supported(r, j_conf, issuer, OIDC_METADATA_ID_TOKEN_SIGNED_RESPONSE_ALG, oidc_cfg_parse_is_valid_signed_response_alg) == FALSE) return FALSE; if (oidc_metadata_conf_jose_is_supported(r, j_conf, issuer, OIDC_METADATA_ID_TOKEN_ENCRYPTED_RESPONSE_ALG, oidc_cfg_parse_is_valid_encrypted_response_alg) == FALSE) return FALSE; if (oidc_metadata_conf_jose_is_supported(r, j_conf, issuer, OIDC_METADATA_ID_TOKEN_ENCRYPTED_RESPONSE_ENC, oidc_cfg_parse_is_valid_encrypted_response_enc) == FALSE) return FALSE; if (oidc_metadata_conf_jose_is_supported(r, j_conf, issuer, OIDC_METADATA_USERINFO_SIGNED_RESPONSE_ALG, oidc_cfg_parse_is_valid_signed_response_alg) == FALSE) return FALSE; if (oidc_metadata_conf_jose_is_supported(r, j_conf, issuer, OIDC_METADATA_USERINFO_ENCRYPTED_RESPONSE_ALG, oidc_cfg_parse_is_valid_encrypted_response_alg) == FALSE) return FALSE; if (oidc_metadata_conf_jose_is_supported(r, j_conf, issuer, OIDC_METADATA_USERINFO_ENCRYPTED_RESPONSE_ENC, oidc_cfg_parse_is_valid_encrypted_response_enc) == FALSE) return FALSE; return TRUE; } /* * register the client with the OP using Dynamic Client Registration */ static apr_byte_t oidc_metadata_client_register(request_rec *r, oidc_cfg_t *cfg, oidc_provider_t *provider, json_t **j_client, char **response) { /* assemble the JSON registration request */ json_t *data = json_object(); json_object_set_new(data, OIDC_METADATA_CLIENT_NAME, json_string(oidc_cfg_provider_client_name_get(provider))); json_object_set_new(data, OIDC_METADATA_REDIRECT_URIS, json_pack("[s]", oidc_util_redirect_uri(r, cfg))); json_t *response_types = json_array(); apr_array_header_t *flows = oidc_proto_supported_flows(r->pool); int i = 0; for (i = 0; i < flows->nelts; i++) json_array_append_new(response_types, json_string(APR_ARRAY_IDX(flows, i, const char *))); json_object_set_new(data, OIDC_METADATA_RESPONSE_TYPES, response_types); json_object_set_new(data, OIDC_METADATA_GRANT_TYPES, json_pack("[s, s, s]", "authorization_code", "implicit", "refresh_token")); if (oidc_cfg_provider_token_endpoint_auth_get(provider) != NULL) { json_object_set_new(data, OIDC_METADATA_TOKEN_ENDPOINT_AUTH_METHOD, json_string(oidc_cfg_provider_token_endpoint_auth_get(provider))); } if (oidc_cfg_provider_client_contact_get(provider) != NULL) { json_object_set_new(data, OIDC_METADATA_CONTACTS, json_pack("[s]", oidc_cfg_provider_client_contact_get(provider))); } if (oidc_cfg_provider_client_jwks_uri_get(provider)) { json_object_set_new(data, OIDC_METADATA_JWKS_URI, json_string(oidc_cfg_provider_client_jwks_uri_get(provider))); } else if (oidc_cfg_public_keys_get(cfg) != NULL) { json_object_set_new(data, OIDC_METADATA_JWKS_URI, json_string(apr_psprintf(r->pool, "%s?%s=rsa", oidc_util_redirect_uri(r, cfg), OIDC_REDIRECT_URI_REQUEST_JWKS))); } if (oidc_cfg_provider_id_token_signed_response_alg_get(provider) != NULL) { json_object_set_new(data, OIDC_METADATA_ID_TOKEN_SIGNED_RESPONSE_ALG, json_string(oidc_cfg_provider_id_token_signed_response_alg_get(provider))); } if (oidc_cfg_provider_id_token_encrypted_response_alg_get(provider) != NULL) { json_object_set_new(data, OIDC_METADATA_ID_TOKEN_ENCRYPTED_RESPONSE_ALG, json_string(oidc_cfg_provider_id_token_encrypted_response_alg_get(provider))); } if (oidc_cfg_provider_id_token_encrypted_response_enc_get(provider) != NULL) { json_object_set_new(data, OIDC_METADATA_ID_TOKEN_ENCRYPTED_RESPONSE_ENC, json_string(oidc_cfg_provider_id_token_encrypted_response_enc_get(provider))); } if (oidc_cfg_provider_userinfo_signed_response_alg_get(provider) != NULL) { json_object_set_new(data, OIDC_METADATA_USERINFO_SIGNED_RESPONSE_ALG, json_string(oidc_cfg_provider_userinfo_signed_response_alg_get(provider))); } if (oidc_cfg_provider_userinfo_encrypted_response_alg_get(provider) != NULL) { json_object_set_new(data, OIDC_METADATA_USERINFO_ENCRYPTED_RESPONSE_ALG, json_string(oidc_cfg_provider_userinfo_encrypted_response_alg_get(provider))); } if (oidc_cfg_provider_userinfo_encrypted_response_enc_get(provider) != NULL) { json_object_set_new(data, OIDC_METADATA_USERINFO_ENCRYPTED_RESPONSE_ENC, json_string(oidc_cfg_provider_userinfo_encrypted_response_enc_get(provider))); } if (oidc_cfg_provider_request_object_get(provider) != NULL) { json_t *request_object_config = NULL; if (oidc_util_decode_json_object(r, oidc_cfg_provider_request_object_get(provider), &request_object_config) == TRUE) { json_t *crypto = json_object_get(request_object_config, "crypto"); char *alg = "none"; oidc_util_json_object_get_string(r->pool, crypto, "sign_alg", &alg, "none"); json_object_set_new(data, "request_object_signing_alg", json_string(alg)); json_decref(request_object_config); } } json_object_set_new(data, OIDC_METADATA_INITIATE_LOGIN_URI, json_string(oidc_util_redirect_uri(r, cfg))); json_object_set_new( data, OIDC_METADATA_FRONTCHANNEL_LOGOUT_URI, json_string(apr_psprintf(r->pool, "%s?%s=%s", oidc_util_redirect_uri(r, cfg), OIDC_REDIRECT_URI_REQUEST_LOGOUT, OIDC_GET_STYLE_LOGOUT_PARAM_VALUE))); // TODO: may want to add backchannel_logout_session_required json_object_set_new( data, OIDC_METADATA_BACKCHANNEL_LOGOUT_URI, json_string(apr_psprintf(r->pool, "%s?%s=%s", oidc_util_redirect_uri(r, cfg), OIDC_REDIRECT_URI_REQUEST_LOGOUT, OIDC_BACKCHANNEL_STYLE_LOGOUT_PARAM_VALUE))); if (oidc_cfg_default_slo_url_get(cfg) != NULL) { json_object_set_new( data, OIDC_METADATA_POST_LOGOUT_REDIRECT_URIS, json_pack("[s]", oidc_util_absolute_url(r, cfg, oidc_cfg_default_slo_url_get(cfg)))); } /* add any custom JSON in to the registration request */ if (oidc_cfg_provider_registration_endpoint_json_get(provider) != NULL) { json_t *json = NULL; if (oidc_util_decode_json_object(r, oidc_cfg_provider_registration_endpoint_json_get(provider), &json) == FALSE) return FALSE; oidc_util_json_merge(r, json, data); json_decref(json); } /* dynamically register the client with the specified parameters */ if (oidc_http_post_json(r, oidc_cfg_provider_registration_endpoint_url_get(provider), data, NULL, oidc_cfg_provider_registration_token_get(provider), NULL, oidc_cfg_provider_ssl_validate_server_get(provider), response, NULL, NULL, oidc_cfg_http_timeout_short_get(cfg), oidc_cfg_outgoing_proxy_get(cfg), oidc_cfg_dir_pass_cookies_get(r), NULL, NULL, NULL) == FALSE) { json_decref(data); return FALSE; } json_decref(data); /* decode and see if it is not an error response somehow */ if (oidc_util_decode_json_and_check_error(r, *response, j_client) == FALSE) { oidc_error(r, "JSON parsing of dynamic client registration response failed"); return FALSE; } return TRUE; } /* * helper function to get the JWKs for the specified issuer */ static apr_byte_t oidc_metadata_jwks_retrieve_and_cache(request_rec *r, oidc_cfg_t *cfg, const oidc_jwks_uri_t *jwks_uri, int ssl_validate_server, json_t **j_jwks) { char *response = NULL; const char *url = (jwks_uri->signed_uri != NULL) ? jwks_uri->signed_uri : jwks_uri->uri; /* get the JWKs from the specified URL with the specified parameters */ if (oidc_http_get(r, url, NULL, NULL, NULL, NULL, ssl_validate_server, &response, NULL, NULL, oidc_cfg_http_timeout_long_get(cfg), oidc_cfg_outgoing_proxy_get(cfg), oidc_cfg_dir_pass_cookies_get(r), NULL, NULL, NULL) == FALSE) return FALSE; if ((jwks_uri->signed_uri != NULL) && (jwks_uri->jwk_list != NULL)) { oidc_jwt_t *jwt = NULL; oidc_jose_error_t err; apr_hash_t *keys = apr_hash_make(r->pool); oidc_debug(r, "signed_jwks verifier keys count=%d", jwks_uri->jwk_list->nelts); for (int i = 0; i < jwks_uri->jwk_list->nelts; i++) { oidc_jwk_t *jwk = APR_ARRAY_IDX(jwks_uri->jwk_list, i, oidc_jwk_t *); if (jwk->kid != NULL) { oidc_debug(r, "signed_jwks verifier kid=%s", jwk->kid); apr_hash_set(keys, jwk->kid, APR_HASH_KEY_STRING, jwk); } else { const char *kid = apr_psprintf(r->pool, "%d", apr_hash_count(keys)); oidc_debug(r, "signed_jwks verifier kid=%s", kid); apr_hash_set(keys, kid, APR_HASH_KEY_STRING, jwk); } } if (oidc_jwt_parse(r->pool, response, &jwt, keys, FALSE, &err) == FALSE) { oidc_error(r, "parsing JWT failed: %s", oidc_jose_e2s(r->pool, err)); return FALSE; } oidc_debug(r, "successfully parsed JWT returned from \"signed_jwks_uri\" endpoint"); if (oidc_jwt_verify(r->pool, jwt, keys, &err) == FALSE) { oidc_error(r, "verifying JWT failed: %s", oidc_jose_e2s(r->pool, err)); oidc_jwt_destroy(jwt); return FALSE; } // TODO: add issuer? if (oidc_proto_jwt_validate(r, jwt, NULL, FALSE, FALSE, -1) == FALSE) return FALSE; oidc_debug(r, "successfully verified and validated JWKs JWT"); response = jwt->payload.value.str; oidc_jwt_destroy(jwt); } /* decode and see if it is not an error response somehow */ if (oidc_util_decode_json_and_check_error(r, response, j_jwks) == FALSE) { oidc_error(r, "JSON parsing of JWKs published at the jwks_uri failed"); return FALSE; } /* check to see if it is a set of valid JWKs */ if (oidc_metadata_jwks_is_valid(r, url, *j_jwks) == FALSE) return FALSE; /* store the JWKs in the cache */ oidc_cache_set_jwks(r, oidc_metadata_jwks_cache_key(jwks_uri), response, apr_time_now() + apr_time_from_sec(oidc_cfg_jwks_uri_refresh_interval_get(jwks_uri))); return TRUE; } /* * return JWKs for the specified issuer */ apr_byte_t oidc_metadata_jwks_get(request_rec *r, oidc_cfg_t *cfg, const oidc_jwks_uri_t *jwks_uri, int ssl_validate_server, json_t **j_jwks, apr_byte_t *refresh) { char *value = NULL; const char *url = jwks_uri->signed_uri ? jwks_uri->signed_uri : jwks_uri->uri; oidc_debug(r, "enter, %sjwks_uri=%s, refresh=%d", jwks_uri->signed_uri ? "signed_" : "", url, *refresh); /* see if we need to do a forced refresh */ if (*refresh == TRUE) { oidc_debug(r, "doing a forced refresh of the JWKs from URI \"%s\"", url); if (oidc_metadata_jwks_retrieve_and_cache(r, cfg, jwks_uri, ssl_validate_server, j_jwks) == TRUE) return TRUE; // else: fall back to any cached JWKs } /* see if the JWKs is cached */ if ((oidc_cache_get_jwks(r, oidc_metadata_jwks_cache_key(jwks_uri), &value) == TRUE) && (value != NULL)) { /* decode and see if it is not a cached error response somehow */ if (oidc_util_decode_json_and_check_error(r, value, j_jwks) == FALSE) { oidc_warn(r, "JSON parsing of cached JWKs data failed"); value = NULL; } } if (value == NULL) { /* it is non-existing, invalid or expired: do a forced refresh */ *refresh = TRUE; return oidc_metadata_jwks_retrieve_and_cache(r, cfg, jwks_uri, ssl_validate_server, j_jwks); } return TRUE; } /* * use OpenID Connect Discovery to get metadata for the specified issuer */ apr_byte_t oidc_metadata_provider_retrieve(request_rec *r, oidc_cfg_t *cfg, const char *issuer, const char *url, json_t **j_metadata, char **response) { OIDC_METRICS_TIMING_START(r, cfg); /* get provider metadata from the specified URL with the specified parameters */ if (oidc_http_get(r, url, NULL, NULL, NULL, NULL, oidc_cfg_provider_ssl_validate_server_get(oidc_cfg_provider_get(cfg)), response, NULL, NULL, oidc_cfg_http_timeout_short_get(cfg), oidc_cfg_outgoing_proxy_get(cfg), oidc_cfg_dir_pass_cookies_get(r), NULL, NULL, NULL) == FALSE) { OIDC_METRICS_COUNTER_INC(r, cfg, OM_PROVIDER_METADATA_ERROR); return FALSE; } OIDC_METRICS_TIMING_ADD(r, cfg, OM_PROVIDER_METADATA); /* decode and see if it is not an error response somehow */ if (oidc_util_decode_json_and_check_error(r, *response, j_metadata) == FALSE) { oidc_error(r, "JSON parsing of retrieved Discovery document failed"); return FALSE; } /* check to see if it is valid metadata */ if (oidc_metadata_provider_is_valid(r, cfg, *j_metadata, issuer) == FALSE) { json_decref(*j_metadata); return FALSE; } /* all OK */ return TRUE; } /* * see if we have provider metadata and check its validity * if not, use OpenID Connect Discovery to get it, check it and store it */ apr_byte_t oidc_metadata_provider_get(request_rec *r, oidc_cfg_t *cfg, const char *issuer, json_t **j_provider, apr_byte_t allow_discovery) { /* holds the response data/string/JSON from the OP */ char *response = NULL; /* get the full file path to the provider metadata for this issuer */ const char *provider_path = oidc_metadata_provider_file_path(r, issuer); /* check the last-modified timestamp */ apr_byte_t use_cache = TRUE; apr_finfo_t fi; json_t *j_cache = NULL; apr_byte_t have_cache = FALSE; /* see if we are refreshing metadata and we need a refresh */ if (oidc_cfg_provider_metadata_refresh_interval_get(cfg) > 0) { have_cache = (apr_stat(&fi, provider_path, APR_FINFO_MTIME, r->pool) == APR_SUCCESS); if (have_cache == TRUE) use_cache = (apr_time_now() < fi.mtime + apr_time_from_sec(oidc_cfg_provider_metadata_refresh_interval_get(cfg))); oidc_debug(r, "use_cache: %s", use_cache ? "yes" : "no"); } /* see if we have valid metadata already, if so, return it */ if (oidc_metadata_file_read_json(r, provider_path, &j_cache) == TRUE) { /* return the validation result */ if (use_cache == TRUE) { *j_provider = j_cache; return oidc_metadata_provider_is_valid(r, cfg, *j_provider, issuer); } } if ((have_cache == FALSE) && (!allow_discovery)) { oidc_warn(r, "no metadata found for the requested issuer (%s), and Discovery is not allowed", issuer); return FALSE; } /* assemble the URL to the .well-known OpenID metadata */ const char *url = apr_psprintf(r->pool, "%s", ((_oidc_strstr(issuer, "http://") == issuer) || (_oidc_strstr(issuer, "https://") == issuer)) ? issuer : apr_psprintf(r->pool, "https://%s", issuer)); url = apr_psprintf(r->pool, "%s%s.well-known/openid-configuration", url, (url && url[_oidc_strlen(url) - 1] != OIDC_CHAR_FORWARD_SLASH) ? OIDC_STR_FORWARD_SLASH : ""); /* get the metadata for the issuer using OpenID Connect Discovery and validate it */ if (oidc_metadata_provider_retrieve(r, cfg, issuer, url, j_provider, &response) == FALSE) { oidc_debug(r, "could not retrieve provider metadata; have_cache: %s (data=%pp)", have_cache ? "yes" : "no", j_cache); /* see if we can use at least the cache that may have expired by now */ if ((oidc_cfg_provider_metadata_refresh_interval_get(cfg) > 0) && (have_cache == TRUE) && (j_cache != NULL)) { /* reset the file-modified timestamp so it is cached for a while again */ apr_file_mtime_set(provider_path, apr_time_now(), r->pool); /* return the validated cached data */ *j_provider = j_cache; return oidc_metadata_provider_is_valid(r, cfg, *j_provider, issuer); } return FALSE; } /* since it is valid, write the obtained provider metadata file */ if (oidc_util_file_write(r, provider_path, response) == FALSE) return FALSE; return TRUE; } /* * see if we have config metadata */ static apr_byte_t oidc_metadata_conf_get(request_rec *r, const char *issuer, json_t **j_conf) { /* get the full file path to the conf metadata for this issuer */ const char *conf_path = oidc_metadata_conf_path(r, issuer); /* the .conf file is optional */ apr_finfo_t fi; if (apr_stat(&fi, conf_path, APR_FINFO_MTIME, r->pool) != APR_SUCCESS) return TRUE; /* see if we have valid metadata already, if so, return it */ if (oidc_metadata_file_read_json(r, conf_path, j_conf) == TRUE) { /* return the validation result */ return oidc_metadata_conf_is_valid(r, *j_conf, issuer); } return FALSE; } /* * see if we have client metadata and check its validity * if not, use OpenID Connect Client Registration to get it, check it and store it */ static apr_byte_t oidc_metadata_client_get(request_rec *r, oidc_cfg_t *cfg, const char *issuer, oidc_provider_t *provider, json_t **j_client) { /* get the full file path to the client metadata for this issuer */ const char *client_path = oidc_metadata_client_file_path(r, issuer); /* see if we have valid metadata already, if so, return it */ if (oidc_metadata_file_read_json(r, client_path, j_client) == TRUE) { /* if the client metadata is (still) valid, return it */ if (oidc_metadata_client_is_valid(r, *j_client, issuer) == TRUE) return TRUE; } /* at this point we have no valid client metadata, see if there's a registration endpoint for this provider */ if (oidc_cfg_provider_registration_endpoint_url_get(provider) == NULL) { oidc_error(r, "no (valid) client metadata exists for provider (%s) and provider JSON object did not " "contain a (valid) \"" OIDC_METADATA_REGISTRATION_ENDPOINT "\" string", issuer); return FALSE; } /* try and get client metadata by registering the client at the registration endpoint */ char *response = NULL; if (oidc_metadata_client_register(r, cfg, provider, j_client, &response) == FALSE) return FALSE; /* check to see if it is valid metadata */ if (oidc_metadata_client_is_valid(r, *j_client, issuer) == FALSE) return FALSE; /* since it is valid, write the obtained client metadata file */ if (oidc_util_file_write(r, client_path, response) == FALSE) return FALSE; return TRUE; } /* * get a list of configured OIDC providers based on the entries in the provider metadata directory */ apr_byte_t oidc_metadata_list(request_rec *r, oidc_cfg_t *cfg, apr_array_header_t **list) { apr_status_t rc; apr_dir_t *dir; apr_finfo_t fi; char s_err[128]; oidc_debug(r, "enter"); /* open the metadata directory */ if ((rc = apr_dir_open(&dir, oidc_cfg_metadata_dir_get(cfg), r->pool)) != APR_SUCCESS) { oidc_error(r, "error opening metadata directory '%s' (%s)", oidc_cfg_metadata_dir_get(cfg), apr_strerror(rc, s_err, sizeof(s_err))); return FALSE; } /* allocate some space in the array that will hold the list of providers */ *list = apr_array_make(r->pool, 5, sizeof(const char *)); /* BTW: we could estimate the number in the array based on # directory entries... */ /* loop over the entries in the provider metadata directory */ while (apr_dir_read(&fi, APR_FINFO_NAME, dir) == APR_SUCCESS) { /* skip "." and ".." entries */ if (fi.name[0] == OIDC_CHAR_DOT) continue; /* skip other non-provider entries */ const char *ext = strrchr(fi.name, OIDC_CHAR_DOT); if (ext == NULL) continue; ext++; if (_oidc_strcmp(ext, OIDC_METADATA_SUFFIX_PROVIDER) != 0) continue; /* get the issuer from the filename */ const char *issuer = oidc_metadata_filename_to_issuer(r, fi.name); /* get the provider and client metadata, do all checks and registration if possible */ oidc_provider_t *provider = NULL; if (oidc_metadata_get(r, cfg, issuer, &provider, FALSE) == TRUE) { /* push the decoded issuer filename in to the array */ APR_ARRAY_PUSH(*list, const char *) = oidc_cfg_provider_issuer_get(provider); } } /* we're done, cleanup now */ apr_dir_close(dir); return TRUE; } /* * parse boolean value from JSON configuration */ static void oidc_metadata_parse_boolean(request_rec *r, json_t *json, const char *key, int *value, int default_value) { int int_value = 0; char *s_value = NULL; if (oidc_util_json_object_get_bool(json, key, &int_value, default_value) == FALSE) { oidc_util_json_object_get_string(r->pool, json, key, &s_value, NULL); if (s_value != NULL) { const char *rv = oidc_cfg_parse_boolean(r->pool, s_value, &int_value); if (rv != NULL) { oidc_warn(r, "%s: %s", key, rv); int_value = default_value; } } else { oidc_util_json_object_get_int(json, key, &int_value, default_value); } } *value = (int_value != 0) ? TRUE : FALSE; } /* * parse URL value from JSON configuration */ static void oidc_metadata_parse_url(request_rec *r, const char *type, const char *issuer, json_t *json, const char *key, char **value, const char *default_value) { *value = NULL; if ((oidc_metadata_is_valid_uri(r, type, issuer, json, key, value, FALSE) == FALSE) || ((*value == NULL) && (default_value != NULL))) { *value = apr_pstrdup(r->pool, default_value); } } #define OIDC_METADATA_PROVIDER_SET(member, value, rv) \ if (value != NULL) { \ rv = oidc_cfg_provider_##member##_set(r->pool, provider, value); \ if (rv != NULL) \ oidc_error(r, "oidc_cfg_provider_%s_set: %s", TOSTRING(member), rv); \ } #define OIDC_METADATA_PROVIDER_SET_INT(provider, member, ivalue, rv) \ if (ivalue != OIDC_CONFIG_POS_INT_UNSET) { \ rv = oidc_cfg_provider_##member##_set(r->pool, provider, ivalue); \ if (rv != NULL) \ oidc_error(r, "oidc_cfg_provider_%s_set: %s", TOSTRING(member), rv); \ } /* * parse the JSON provider metadata in to a oidc_provider_t struct but do not override values already set */ apr_byte_t oidc_metadata_provider_parse(request_rec *r, oidc_cfg_t *cfg, json_t *j_provider, oidc_provider_t *provider) { const char *rv = NULL; char *value = NULL; int ivalue = OIDC_CONFIG_POS_INT_UNSET; if (oidc_cfg_provider_issuer_get(provider) == NULL) { /* get the "issuer" from the provider metadata */ oidc_util_json_object_get_string(r->pool, j_provider, OIDC_METADATA_ISSUER, &value, NULL); OIDC_METADATA_PROVIDER_SET(issuer, value, rv); } if (oidc_cfg_provider_authorization_endpoint_url_get(provider) == NULL) { oidc_metadata_parse_url(r, OIDC_METADATA_SUFFIX_PROVIDER, oidc_cfg_provider_issuer_get(provider), j_provider, OIDC_METADATA_AUTHORIZATION_ENDPOINT, &value, NULL); OIDC_METADATA_PROVIDER_SET(authorization_endpoint_url, value, rv) } if (oidc_cfg_provider_token_endpoint_url_get(provider) == NULL) { oidc_metadata_parse_url(r, OIDC_METADATA_SUFFIX_PROVIDER, oidc_cfg_provider_issuer_get(provider), j_provider, OIDC_METADATA_TOKEN_ENDPOINT, &value, NULL); OIDC_METADATA_PROVIDER_SET(token_endpoint_url, value, rv) } if (oidc_cfg_provider_userinfo_endpoint_url_get(provider) == NULL) { oidc_metadata_parse_url(r, OIDC_METADATA_SUFFIX_PROVIDER, oidc_cfg_provider_issuer_get(provider), j_provider, OIDC_METADATA_USERINFO_ENDPOINT, &value, NULL); OIDC_METADATA_PROVIDER_SET(userinfo_endpoint_url, value, rv) } if (oidc_cfg_provider_revocation_endpoint_url_get(provider) == NULL) { oidc_metadata_parse_url(r, OIDC_METADATA_SUFFIX_PROVIDER, oidc_cfg_provider_issuer_get(provider), j_provider, OIDC_METADATA_REVOCATION_ENDPOINT, &value, NULL); OIDC_METADATA_PROVIDER_SET(revocation_endpoint_url, value, rv) } if (oidc_cfg_provider_pushed_authorization_request_endpoint_url_get(provider) == NULL) { oidc_metadata_parse_url(r, OIDC_METADATA_SUFFIX_PROVIDER, oidc_cfg_provider_issuer_get(provider), j_provider, OIDC_METADATA_PAR_ENDPOINT, &value, NULL); OIDC_METADATA_PROVIDER_SET(pushed_authorization_request_endpoint_url, value, rv) } if (oidc_cfg_provider_jwks_uri_uri_get(provider) == NULL) { oidc_metadata_parse_url(r, OIDC_METADATA_SUFFIX_PROVIDER, oidc_cfg_provider_issuer_get(provider), j_provider, OIDC_METADATA_JWKS_URI, &value, NULL); OIDC_METADATA_PROVIDER_SET(jwks_uri, value, rv) } if (oidc_cfg_provider_signed_jwks_uri_get(provider) == NULL) { oidc_metadata_parse_url(r, OIDC_METADATA_SUFFIX_PROVIDER, oidc_cfg_provider_issuer_get(provider), j_provider, OIDC_METADATA_SIGNED_JWKS_URI, &value, NULL); if (value != NULL) { rv = oidc_cfg_provider_signed_jwks_uri_set(r->pool, provider, value, NULL); if (rv != NULL) oidc_error(r, "oidc_provider_signed_jwks_uri_set: %s", rv); } } if (oidc_cfg_provider_registration_endpoint_url_get(provider) == NULL) { oidc_metadata_parse_url(r, OIDC_METADATA_SUFFIX_PROVIDER, oidc_cfg_provider_issuer_get(provider), j_provider, OIDC_METADATA_REGISTRATION_ENDPOINT, &value, NULL); OIDC_METADATA_PROVIDER_SET(registration_endpoint_url, value, rv) } if (oidc_cfg_provider_check_session_iframe_get(provider) == NULL) { oidc_metadata_parse_url(r, OIDC_METADATA_SUFFIX_PROVIDER, oidc_cfg_provider_issuer_get(provider), j_provider, OIDC_METADATA_CHECK_SESSION_IFRAME, &value, NULL); OIDC_METADATA_PROVIDER_SET(check_session_iframe, value, rv) } if (oidc_cfg_provider_end_session_endpoint_get(provider) == NULL) { oidc_metadata_parse_url(r, OIDC_METADATA_SUFFIX_PROVIDER, oidc_cfg_provider_issuer_get(provider), j_provider, OIDC_METADATA_END_SESSION_ENDPOINT, &value, NULL); OIDC_METADATA_PROVIDER_SET(end_session_endpoint, value, rv) } // NB: here we don't actually override with the global setting/default, merely apply it when no value is // provided oidc_metadata_parse_boolean(r, j_provider, OIDC_METADATA_BACKCHANNEL_LOGOUT_SUPPORTED, &ivalue, oidc_cfg_provider_backchannel_logout_supported_get(provider)); OIDC_METADATA_PROVIDER_SET_INT(provider, backchannel_logout_supported, ivalue, rv) if (oidc_cfg_provider_token_endpoint_auth_get(provider) == NULL) { if (oidc_metadata_valid_string_in_array(r->pool, j_provider, OIDC_METADATA_TOKEN_ENDPOINT_AUTH_METHODS_SUPPORTED, oidc_cfg_get_valid_endpoint_auth_function(cfg), &value, TRUE, OIDC_ENDPOINT_AUTH_CLIENT_SECRET_BASIC) != NULL) { oidc_error(r, "could not find a supported token endpoint authentication method in provider" "metadata (%s) for entry \"" OIDC_METADATA_TOKEN_ENDPOINT_AUTH_METHODS_SUPPORTED "\"", oidc_cfg_provider_issuer_get(provider)); return FALSE; } rv = oidc_cfg_provider_token_endpoint_auth_set(r->pool, cfg, provider, value); if (rv != NULL) oidc_error(r, "oidc_provider_token_endpoint_auth_set: %s", rv); } return TRUE; } /* * parse the JSON OAuth 2.0 provider metadata in to the cfg->oauth struct */ apr_byte_t oidc_oauth_metadata_provider_parse(request_rec *r, oidc_cfg_t *c, json_t *j_provider) { char *issuer = NULL, *value = NULL; const char *rv = NULL; /* get the "issuer" from the provider metadata */ oidc_util_json_object_get_string(r->pool, j_provider, OIDC_METADATA_ISSUER, &issuer, NULL); // TOOD: should check for "if c->oauth.introspection_endpoint_url == NULL and // allocate the string from the process/config pool // // https://github.com/OpenIDC/mod_auth_openidc/commit/32321024ed5bdbc02ba8b5d61aabc4a4c3745c89 // https://groups.google.com/forum/#!topic/mod_auth_openidc/o1K_1Yh-TQA /* get a handle to the introspection endpoint */ oidc_util_json_object_get_string(r->pool, j_provider, OIDC_METADATA_INTROSPECTION_ENDPOINT, &value, NULL); if (value != NULL) { rv = oidc_cfg_oauth_introspection_endpoint_url_set(r->pool, c, value); if (rv != NULL) oidc_error(r, "oidc_oauth_introspection_endpoint_url_set error: %s", rv); } /* get a handle to the jwks_uri endpoint */ oidc_util_json_object_get_string(r->pool, j_provider, OIDC_METADATA_JWKS_URI, &value, NULL); if (value != NULL) { rv = oidc_cfg_oauth_verify_jwks_uri_set(r->pool, c, value); if (rv != NULL) oidc_error(r, "oidc_oauth_verify_jwks_uri_set error: %s", rv); } if (oidc_metadata_valid_string_in_array(r->pool, j_provider, OIDC_METADATA_INTROSPECTON_ENDPOINT_AUTH_METHODS_SUPPORTED, oidc_cfg_get_valid_endpoint_auth_function(c), &value, TRUE, OIDC_ENDPOINT_AUTH_CLIENT_SECRET_BASIC) != NULL) { oidc_error(r, "could not find a supported token endpoint authentication method in provider metadata (%s) " "for entry \"" OIDC_METADATA_INTROSPECTON_ENDPOINT_AUTH_METHODS_SUPPORTED "\"", issuer); return FALSE; } else { rv = oidc_cfg_oauth_introspection_endpoint_auth_set(r->pool, c, value); if (rv != NULL) oidc_error(r, "oidc_oauth_introspection_endpoint_auth_set error: %s", rv); } return TRUE; } /* * get a string value from a JSON object and see if it is a valid value according to the specified validation function */ void oidc_metadata_get_valid_string(request_rec *r, json_t *json, const char *key, oidc_valid_function_t valid_function, char **str_value, const char *default_str_value) { char *v = NULL; oidc_util_json_object_get_string(r->pool, json, key, &v, default_str_value); if (v != NULL) { const char *rv = valid_function(r->pool, v); if (rv != NULL) { oidc_warn(r, "string value %s for key \"%s\" is invalid: %s; using default: %s", v, key, rv, default_str_value); v = apr_pstrdup(r->pool, default_str_value); } } *str_value = v; } /* * parse a set of JWKs from a JSON metadata object */ static void oidc_metadata_get_jwks(request_rec *r, json_t *json, apr_array_header_t **jwk_list) { json_t *keys = NULL; int i = 0; oidc_jose_error_t err; oidc_jwk_t *jwk = NULL; json_t *elem = NULL; keys = json_object_get(json, OIDC_JOSE_JWKS_KEYS_STR); if (keys == NULL) return; if (!json_is_array(keys)) { oidc_error(r, "trying to parse a list of JWKs but the value for key \"%s\" is not a JSON array", OIDC_JOSE_JWKS_KEYS_STR); return; } for (i = 0; i < json_array_size(keys); i++) { elem = json_array_get(keys, i); if (oidc_jwk_parse_json(r->pool, elem, &jwk, &err) == FALSE) { oidc_warn(r, "oidc_jwk_parse_json failed: %s", oidc_jose_e2s(r->pool, err)); continue; } if (*jwk_list == NULL) *jwk_list = apr_array_make(r->pool, 4, sizeof(oidc_jwk_t *)); APR_ARRAY_PUSH(*jwk_list, oidc_jwk_t *) = jwk; } } /* * parse the JSON conf metadata in to a oidc_provider_t struct */ apr_byte_t oidc_metadata_conf_parse(request_rec *r, oidc_cfg_t *cfg, json_t *j_conf, oidc_provider_t *provider) { const char *rv = NULL; char *value = NULL; int ivalue = OIDC_CONFIG_POS_INT_UNSET; apr_array_header_t *keys = NULL, *auds = NULL; // NB: need this first so the profile - if explicitly configured - will override // potentially non-conformant / insecure settings oidc_util_json_object_get_string(r->pool, j_conf, OIDC_METADATA_PROFILE, &value, NULL); if (value) { rv = oidc_cfg_provider_profile_set(r->pool, provider, value); if (rv != NULL) oidc_error(r, "oidc_cfg_provider_profile_set: %s", rv); } else { oidc_cfg_provider_profile_int_set(provider, oidc_cfg_provider_profile_get(oidc_cfg_provider_get(cfg))); } oidc_util_json_object_get_string(r->pool, j_conf, OIDC_METADATA_CLIENT_JWKS_URI, &value, oidc_cfg_provider_client_jwks_uri_get(oidc_cfg_provider_get(cfg))); OIDC_METADATA_PROVIDER_SET(client_jwks_uri, value, rv) oidc_metadata_get_jwks(r, j_conf, &keys); if (keys != NULL) { rv = oidc_cfg_provider_client_keys_set_keys(r->pool, provider, keys); if (rv != NULL) oidc_error(r, "oidc_cfg_provider_client_keys_set: %s", rv); } rv = oidc_cfg_provider_signed_jwks_uri_keys_set( r->pool, provider, json_object_get(j_conf, "signed_jwks_uri_key"), oidc_cfg_provider_signed_jwks_uri_keys_get(oidc_cfg_provider_get(cfg))); if (rv != NULL) oidc_error(r, "oidc_cfg_provider_signed_jwks_uri_keys_set: %s", rv); /* get the (optional) signing & encryption settings for the id_token */ oidc_util_json_object_get_string( r->pool, j_conf, OIDC_METADATA_ID_TOKEN_SIGNED_RESPONSE_ALG, &value, oidc_cfg_provider_id_token_signed_response_alg_get(oidc_cfg_provider_get(cfg))); OIDC_METADATA_PROVIDER_SET(id_token_signed_response_alg, value, rv) oidc_util_json_object_get_string( r->pool, j_conf, OIDC_METADATA_ID_TOKEN_ENCRYPTED_RESPONSE_ALG, &value, oidc_cfg_provider_id_token_encrypted_response_alg_get(oidc_cfg_provider_get(cfg))); OIDC_METADATA_PROVIDER_SET(id_token_encrypted_response_alg, value, rv) oidc_util_json_object_get_string( r->pool, j_conf, OIDC_METADATA_ID_TOKEN_ENCRYPTED_RESPONSE_ENC, &value, oidc_cfg_provider_id_token_encrypted_response_enc_get(oidc_cfg_provider_get(cfg))); OIDC_METADATA_PROVIDER_SET(id_token_encrypted_response_enc, value, rv) oidc_util_json_object_get_string_array( r->pool, j_conf, OIDC_METADATA_ID_TOKEN_AUD_VALUES, &auds, oidc_proto_profile_id_token_aud_values_get(r->pool, oidc_cfg_provider_get(cfg))); if (auds != NULL) { rv = oidc_cfg_provider_id_token_aud_values_set_str_list(r->pool, provider, auds); if (rv != NULL) oidc_error(r, "oidc_cfg_provider_aud_values_set: %s", rv); } /* get the (optional) signing & encryption settings for the userinfo response */ oidc_util_json_object_get_string( r->pool, j_conf, OIDC_METADATA_USERINFO_SIGNED_RESPONSE_ALG, &value, oidc_cfg_provider_userinfo_signed_response_alg_get(oidc_cfg_provider_get(cfg))); OIDC_METADATA_PROVIDER_SET(userinfo_signed_response_alg, value, rv) oidc_util_json_object_get_string( r->pool, j_conf, OIDC_METADATA_USERINFO_ENCRYPTED_RESPONSE_ALG, &value, oidc_cfg_provider_userinfo_encrypted_response_alg_get(oidc_cfg_provider_get(cfg))); OIDC_METADATA_PROVIDER_SET(userinfo_encrypted_response_alg, value, rv) oidc_util_json_object_get_string( r->pool, j_conf, OIDC_METADATA_USERINFO_ENCRYPTED_RESPONSE_ENC, &value, oidc_cfg_provider_userinfo_encrypted_response_enc_get(oidc_cfg_provider_get(cfg))); OIDC_METADATA_PROVIDER_SET(userinfo_encrypted_response_enc, value, rv) /* find out if we need to perform SSL server certificate validation on the token_endpoint and user_info_endpoint * for this provider */ oidc_metadata_parse_boolean(r, j_conf, OIDC_METADATA_SSL_VALIDATE_SERVER, &ivalue, oidc_cfg_provider_ssl_validate_server_get(oidc_cfg_provider_get(cfg))); OIDC_METADATA_PROVIDER_SET_INT(provider, ssl_validate_server, ivalue, rv) oidc_metadata_parse_boolean(r, j_conf, OIDC_METADATA_VALIDATE_ISSUER, &ivalue, oidc_cfg_provider_validate_issuer_get(oidc_cfg_provider_get(cfg))); OIDC_METADATA_PROVIDER_SET_INT(provider, validate_issuer, ivalue, rv) /* find out what scopes we should be requesting from this provider */ // TODO: use the provider "scopes_supported" to mix-and-match with what we've configured for the client // TODO: check that "openid" is always included in the configured scopes, right? oidc_util_json_object_get_string(r->pool, j_conf, OIDC_METADATA_SCOPE, &value, oidc_cfg_provider_scope_get(oidc_cfg_provider_get(cfg))); OIDC_METADATA_PROVIDER_SET(scope, value, rv) /* see if we've got a custom JWKs refresh interval */ oidc_util_json_object_get_int(j_conf, OIDC_METADATA_JWKS_REFRESH_INTERVAL, &ivalue, oidc_cfg_provider_jwks_uri_refresh_interval_get(oidc_cfg_provider_get(cfg))); OIDC_METADATA_PROVIDER_SET_INT(provider, jwks_uri_refresh_interval, ivalue, rv) /* see if we've got a custom IAT slack interval */ oidc_util_json_object_get_int(j_conf, OIDC_METADATA_IDTOKEN_IAT_SLACK, &ivalue, oidc_cfg_provider_idtoken_iat_slack_get(oidc_cfg_provider_get(cfg))); OIDC_METADATA_PROVIDER_SET_INT(provider, idtoken_iat_slack, ivalue, rv) /* see if we've got a custom max session duration */ oidc_util_json_object_get_int(j_conf, OIDC_METADATA_SESSION_MAX_DURATION, &ivalue, oidc_cfg_provider_session_max_duration_get(oidc_cfg_provider_get(cfg))); OIDC_METADATA_PROVIDER_SET_INT(provider, session_max_duration, ivalue, rv) /* see if we've got custom authentication request parameter values */ oidc_util_json_object_get_string(r->pool, j_conf, OIDC_METADATA_AUTH_REQUEST_PARAMS, &value, oidc_cfg_provider_auth_request_params_get(oidc_cfg_provider_get(cfg))); OIDC_METADATA_PROVIDER_SET(auth_request_params, value, rv) /* see if we've got custom logout request parameter values */ oidc_util_json_object_get_string(r->pool, j_conf, OIDC_METADATA_LOGOUT_REQUEST_PARAMS, &value, oidc_cfg_provider_logout_request_params_get(oidc_cfg_provider_get(cfg))); OIDC_METADATA_PROVIDER_SET(logout_request_params, value, rv) /* see if we've got custom token endpoint parameter values */ oidc_util_json_object_get_string(r->pool, j_conf, OIDC_METADATA_TOKEN_ENDPOINT_PARAMS, &value, oidc_cfg_provider_token_endpoint_params_get(oidc_cfg_provider_get(cfg))); OIDC_METADATA_PROVIDER_SET(token_endpoint_params, value, rv) /* get the response mode to use */ oidc_util_json_object_get_string(r->pool, j_conf, OIDC_METADATA_RESPONSE_MODE, &value, oidc_cfg_provider_response_mode_get(oidc_cfg_provider_get(cfg))); OIDC_METADATA_PROVIDER_SET(response_mode, value, rv) /* get the PKCE method to use */ oidc_util_json_object_get_string(r->pool, j_conf, OIDC_METADATA_PKCE_METHOD, &value, oidc_proto_profile_pkce_get(provider)->method); OIDC_METADATA_PROVIDER_SET(pkce, value, rv) /* see if we've got a custom DPoP mode */ oidc_util_json_object_get_string(r->pool, j_conf, OIDC_METADATA_DPOP_MODE, &value, NULL); if (value) { rv = oidc_cfg_provider_dpop_mode_set(r->pool, provider, value); if (rv != NULL) oidc_error(r, "oidc_cfg_provider_dpop_mode_set: %s", rv); } else { oidc_cfg_provider_dpop_mode_int_set(provider, oidc_proto_profile_dpop_mode_get(provider)); } /* get the client name */ oidc_util_json_object_get_string(r->pool, j_conf, OIDC_METADATA_CLIENT_NAME, &value, oidc_cfg_provider_client_name_get(oidc_cfg_provider_get(cfg))); OIDC_METADATA_PROVIDER_SET(client_name, value, rv) /* get the client contact */ oidc_util_json_object_get_string(r->pool, j_conf, OIDC_METADATA_CLIENT_CONTACT, &value, oidc_cfg_provider_client_contact_get(oidc_cfg_provider_get(cfg))); OIDC_METADATA_PROVIDER_SET(client_contact, value, rv) /* get the token endpoint authentication method */ oidc_util_json_object_get_string(r->pool, j_conf, OIDC_METADATA_TOKEN_ENDPOINT_AUTH, &value, oidc_cfg_provider_token_endpoint_auth_get(oidc_cfg_provider_get(cfg))); if (value != NULL) { rv = oidc_cfg_provider_token_endpoint_auth_set(r->pool, cfg, provider, value); if (rv != NULL) oidc_error(r, "oidc_cfg_provider_token_endpoint_auth_set: %s", rv); } /* get the dynamic client registration token */ oidc_util_json_object_get_string(r->pool, j_conf, OIDC_METADATA_REGISTRATION_TOKEN, &value, oidc_cfg_provider_registration_token_get(oidc_cfg_provider_get(cfg))); OIDC_METADATA_PROVIDER_SET(registration_token, value, rv) /* see if we've got custom registration request parameter values */ oidc_util_json_object_get_string(r->pool, j_conf, OIDC_METADATA_REGISTRATION_ENDPOINT_JSON, &value, oidc_cfg_provider_registration_endpoint_json_get(oidc_cfg_provider_get(cfg))); OIDC_METADATA_PROVIDER_SET(registration_endpoint_json, value, rv) /* get the flow to use; let the .client file set it otherwise (pass NULL as default value) */ oidc_util_json_object_get_string(r->pool, j_conf, OIDC_METADATA_RESPONSE_TYPE, &value, oidc_cfg_provider_response_type_get(oidc_cfg_provider_get(cfg))); OIDC_METADATA_PROVIDER_SET(response_type, value, rv) /* see if we've got a custom user info refresh interval */ oidc_util_json_object_get_int(j_conf, OIDC_METADATA_USERINFO_REFRESH_INTERVAL, &ivalue, oidc_cfg_provider_userinfo_refresh_interval_get(oidc_cfg_provider_get(cfg))); OIDC_METADATA_PROVIDER_SET_INT(provider, userinfo_refresh_interval, ivalue, rv) /* TLS client cert auth settings */ oidc_util_json_object_get_string( r->pool, j_conf, OIDC_METADATA_TOKEN_ENDPOINT_TLS_CLIENT_CERT, &value, oidc_cfg_provider_token_endpoint_tls_client_cert_get(oidc_cfg_provider_get(cfg))); OIDC_METADATA_PROVIDER_SET(token_endpoint_tls_client_cert, value, rv) oidc_util_json_object_get_string( r->pool, j_conf, OIDC_METADATA_TOKEN_ENDPOINT_TLS_CLIENT_KEY, &value, oidc_cfg_provider_token_endpoint_tls_client_key_get(oidc_cfg_provider_get(cfg))); OIDC_METADATA_PROVIDER_SET(token_endpoint_tls_client_key, value, rv) oidc_util_json_object_get_string( r->pool, j_conf, OIDC_METADATA_TOKEN_ENDPOINT_TLS_CLIENT_KEY_PWD, &value, oidc_cfg_provider_token_endpoint_tls_client_key_pwd_get(oidc_cfg_provider_get(cfg))); OIDC_METADATA_PROVIDER_SET(token_endpoint_tls_client_key_pwd, value, rv) oidc_util_json_object_get_string(r->pool, j_conf, OIDC_METADATA_REQUEST_OBJECT, &value, oidc_cfg_provider_request_object_get(oidc_cfg_provider_get(cfg))); OIDC_METADATA_PROVIDER_SET(request_object, value, rv) /* see if we've got a custom userinfo endpoint token presentation method */ oidc_util_json_object_get_string(r->pool, j_conf, OIDC_METADATA_USERINFO_TOKEN_METHOD, &value, NULL); if (value) { rv = oidc_cfg_provider_userinfo_token_method_set(r->pool, provider, value); if (rv != NULL) oidc_error(r, "oidc_cfg_provider_userinfo_token_method_set: %s", rv); } else { oidc_cfg_provider_userinfo_token_method_int_set( provider, oidc_cfg_provider_userinfo_token_method_get(oidc_cfg_provider_get(cfg))); } /* see if we've got a custom HTTP method for passing the auth request */ oidc_util_json_object_get_string(r->pool, j_conf, OIDC_METADATA_AUTH_REQUEST_METHOD, &value, NULL); if (value) { rv = oidc_cfg_provider_auth_request_method_set(r->pool, provider, value); if (rv != NULL) oidc_error(r, "oidc_cfg_provider_auth_request_method_set: %s", rv); } else { oidc_cfg_provider_auth_request_method_int_set(provider, oidc_proto_profile_auth_request_method_get(provider)); } /* get the issuer specific redirect URI option */ oidc_metadata_parse_boolean(r, j_conf, OIDC_METADATA_RESPONSE_REQUIRE_ISS, &ivalue, oidc_proto_profile_response_require_iss_get(provider)); OIDC_METADATA_PROVIDER_SET_INT(provider, response_require_iss, ivalue, rv) return TRUE; } /* * parse the JSON client metadata in to a oidc_provider_t struct */ apr_byte_t oidc_metadata_client_parse(request_rec *r, oidc_cfg_t *cfg, json_t *j_client, oidc_provider_t *provider) { const char *rv = NULL; char *value = NULL; /* get a handle to the client_id we need to use for this provider */ oidc_util_json_object_get_string(r->pool, j_client, OIDC_METADATA_CLIENT_ID, &value, NULL); OIDC_METADATA_PROVIDER_SET(client_id, value, rv) /* get a handle to the client_secret we need to use for this provider */ oidc_util_json_object_get_string(r->pool, j_client, OIDC_METADATA_CLIENT_SECRET, &value, NULL); OIDC_METADATA_PROVIDER_SET(client_secret, value, rv) /* see if the token endpoint auth method defined in the client metadata overrides the provider one */ oidc_util_json_object_get_string(r->pool, j_client, OIDC_METADATA_TOKEN_ENDPOINT_AUTH_METHOD, &value, NULL); if (value != NULL) { rv = oidc_cfg_provider_token_endpoint_auth_set(r->pool, cfg, provider, value); if (rv != NULL) oidc_error(r, "oidc_provider_token_endpoint_auth_set: %s", value); } /* determine the response type if not set by .conf */ if (oidc_cfg_provider_response_type_get(provider) == NULL) { oidc_cfg_provider_response_type_set(r->pool, provider, oidc_cfg_provider_response_type_get(oidc_cfg_provider_get(cfg))); // "response_types" is an array in the client metadata as by spec json_t *j_response_types = json_object_get(j_client, OIDC_METADATA_RESPONSE_TYPES); if ((j_response_types != NULL) && (json_is_array(j_response_types))) { // if there's an array we'll prefer the configured response_type if supported if (oidc_util_json_array_has_value(r, j_response_types, oidc_cfg_provider_response_type_get(provider)) == FALSE) { // if the configured response_type is not supported, we'll fallback to the first one // that is listed json_t *j_response_type = json_array_get(j_response_types, 0); if (json_is_string(j_response_type)) { value = apr_pstrdup(r->pool, json_string_value(j_response_type)); OIDC_METADATA_PROVIDER_SET(response_type, value, rv) } } } } oidc_util_json_object_get_string( r->pool, j_client, OIDC_METADATA_ID_TOKEN_SIGNED_RESPONSE_ALG, &value, oidc_cfg_provider_id_token_signed_response_alg_get(oidc_cfg_provider_get(cfg))); OIDC_METADATA_PROVIDER_SET(id_token_signed_response_alg, value, rv) // TODO: id_token_encrypted_response_alg etc.? return TRUE; } /* * get the metadata for a specified issuer * * this fill the oidc_provider_t struct based on the issuer filename by reading and merging * contents from both provider metadata directory and client metadata directory */ apr_byte_t oidc_metadata_get(request_rec *r, oidc_cfg_t *cfg, const char *issuer, oidc_provider_t **provider, apr_byte_t allow_discovery) { apr_byte_t rc = FALSE; /* pointers to the parsed JSON metadata */ json_t *j_provider = NULL; json_t *j_client = NULL; json_t *j_conf = NULL; /* allocate space for a parsed-and-merged metadata struct */ *provider = oidc_cfg_provider_create(r->pool); /* * read and parse the provider, conf and client metadata respectively * NB: order is important here */ if (oidc_metadata_provider_get(r, cfg, issuer, &j_provider, allow_discovery) == FALSE) goto end; if (oidc_metadata_provider_parse(r, cfg, j_provider, *provider) == FALSE) goto end; if (oidc_metadata_conf_get(r, issuer, &j_conf) == FALSE) goto end; if (oidc_metadata_conf_parse(r, cfg, j_conf, *provider) == FALSE) goto end; if (oidc_metadata_client_get(r, cfg, issuer, *provider, &j_client) == FALSE) goto end; if (oidc_metadata_client_parse(r, cfg, j_client, *provider) == FALSE) goto end; rc = TRUE; end: if (j_provider) json_decref(j_provider); if (j_conf) json_decref(j_conf); if (j_client) json_decref(j_client); return rc; } mod_auth_openidc-2.4.16.10/src/metadata.h000066400000000000000000000062471476721736500200770ustar00rootroot00000000000000/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ /*************************************************************************** * Copyright (C) 2017-2025 ZmartZone Holding BV * All rights reserved. * * DISCLAIMER OF WARRANTIES: * * THE SOFTWARE PROVIDED HEREUNDER IS PROVIDED ON AN "AS IS" BASIS, WITHOUT * ANY WARRANTIES OR REPRESENTATIONS EXPRESS, IMPLIED OR STATUTORY; INCLUDING, * WITHOUT LIMITATION, WARRANTIES OF QUALITY, PERFORMANCE, NONINFRINGEMENT, * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. NOR ARE THERE ANY * WARRANTIES CREATED BY A COURSE OR DEALING, COURSE OF PERFORMANCE OR TRADE * USAGE. FURTHERMORE, THERE ARE NO WARRANTIES THAT THE SOFTWARE WILL MEET * YOUR NEEDS OR BE FREE FROM ERRORS, OR THAT THE OPERATION OF THE SOFTWARE * WILL BE UNINTERRUPTED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * @Author: Hans Zandbelt - hans.zandbelt@openidc.com */ #ifndef _MOD_AUTH_OPENIDC_METADATA_H_ #define _MOD_AUTH_OPENIDC_METADATA_H_ #include "cfg/cfg.h" #include "cfg/provider.h" apr_byte_t oidc_metadata_provider_get(request_rec *r, oidc_cfg_t *cfg, const char *issuer, json_t **j_provider, apr_byte_t allow_discovery); apr_byte_t oidc_metadata_provider_retrieve(request_rec *r, oidc_cfg_t *cfg, const char *issuer, const char *url, json_t **j_metadata, char **response); apr_byte_t oidc_metadata_provider_parse(request_rec *r, oidc_cfg_t *cfg, json_t *j_provider, oidc_provider_t *provider); apr_byte_t oidc_metadata_provider_is_valid(request_rec *r, oidc_cfg_t *cfg, json_t *j_provider, const char *issuer); apr_byte_t oidc_metadata_list(request_rec *r, oidc_cfg_t *cfg, apr_array_header_t **arr); apr_byte_t oidc_metadata_get(request_rec *r, oidc_cfg_t *cfg, const char *selected, oidc_provider_t **provider, apr_byte_t allow_discovery); apr_byte_t oidc_metadata_jwks_get(request_rec *r, oidc_cfg_t *cfg, const oidc_jwks_uri_t *jwks_uri, int ssl_validate_server, json_t **j_jwks, apr_byte_t *refresh); apr_byte_t oidc_oauth_metadata_provider_parse(request_rec *r, oidc_cfg_t *c, json_t *j_provider); #endif /* _MOD_AUTH_OPENIDC_METADATA_H_ */ mod_auth_openidc-2.4.16.10/src/metrics.c000066400000000000000000001553771476721736500177710ustar00rootroot00000000000000/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ /*************************************************************************** * Copyright (C) 2023-2025 ZmartZone Holding BV * All rights reserved. * * DISCLAIMER OF WARRANTIES: * * THE SOFTWARE PROVIDED HEREUNDER IS PROVIDED ON AN "AS IS" BASIS, WITHOUT * ANY WARRANTIES OR REPRESENTATIONS EXPRESS, IMPLIED OR STATUTORY; INCLUDING, * WITHOUT LIMITATION, WARRANTIES OF QUALITY, PERFORMANCE, NONINFRINGEMENT, * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. NOR ARE THERE ANY * WARRANTIES CREATED BY A COURSE OR DEALING, COURSE OF PERFORMANCE OR TRADE * USAGE. FURTHERMORE, THERE ARE NO WARRANTIES THAT THE SOFTWARE WILL MEET * YOUR NEEDS OR BE FREE FROM ERRORS, OR THAT THE OPERATION OF THE SOFTWARE * WILL BE UNINTERRUPTED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * @Author: Hans Zandbelt - hans.zandbelt@openidc.com */ // clang-format off #include "util.h" #include "metrics.h" #include #include #include // NB: formatting matters for docs script from here until clang-format on // KEEP THIS: start-of-classes #define OM_CLASS_AUTH_TYPE "authtype" // Request counter, overall and per AuthType: openid-connect, oauth20 and auth-openidc. #define OM_CLASS_AUTHN "authn" // Authentication request creation and response processing. #define OM_CLASS_AUTHZ "authz" // Authorization errors per OIDCUnAuthzAction (per Require statement, not overall). #define OM_CLASS_REQUIRE_CLAIM "require.claim" // Match/failure count of Require claim directives (per Require statement, not overall). #define OM_CLASS_CLAIM "claim" // Claims per value #define OM_CLASS_PROVIDER "provider" // Requests to the provider [token, userinfo, metadata] endpoints. #define OM_CLASS_SESSION "session" // Existing session processing. #define OM_CLASS_CACHE "cache" // Cache read/write timings and errors. #define OM_CLASS_REDIRECT_URI "redirect_uri" // Requests to the Redirect URI, per type. #define OM_CLASS_CONTENT "content" // Requests to the content handler, per type of request: info, metrics, jwks, etc. // KEEP THIS: end-of-classes // NB: order must match the oidc_metrics_timing_type_t enum type in metrics.h const oidc_metrics_timing_info_t _oidc_metrics_timings_info[] = { // KEEP THIS: start-of-timers { OM_CLASS_AUTH_TYPE, "handler", "the overall authz+authz processing time" }, { OM_CLASS_AUTHN, "request", "authentication requests" }, { OM_CLASS_AUTHN, "response", "authentication responses" }, { OM_CLASS_SESSION, "valid", "successfully validated existing sessions" }, { OM_CLASS_PROVIDER, "metadata", "provider discovery document requests" }, { OM_CLASS_PROVIDER, "token", "provider token requests" }, { OM_CLASS_PROVIDER, "refresh", "provider refresh token requests" }, { OM_CLASS_PROVIDER, "userinfo", "provider userinfo requests" }, { OM_CLASS_CACHE, "read", "cache read requests" }, { OM_CLASS_CACHE, "write", "cache write requests" }, // KEEP THIS: end-of-timers }; // NB: order must match the oidc_metrics_counter_type_t enum type in metrics.h const oidc_metrics_counter_info_t _oidc_metrics_counters_info[] = { // KEEP THIS: start-of-counters { OM_CLASS_AUTH_TYPE, "mod_auth_openidc", "requests handled by mod_auth_openidc" }, { OM_CLASS_AUTH_TYPE, "openid-connect", "requests handled by AuthType openid-connect" }, { OM_CLASS_AUTH_TYPE, "oauth20", "requests handled by AuthType oauth20" }, { OM_CLASS_AUTH_TYPE, "auth-openidc", "requests handled by AuthType auth-openidc" }, { OM_CLASS_AUTH_TYPE, "declined", "requests not handled by mod_auth_openidc"}, { OM_CLASS_AUTHN, "request.error.url", "errors matching the incoming request URL against the configuration" }, { OM_CLASS_AUTHN, "response.error.state-mismatch", "state mismatch errors in authentication responses" }, { OM_CLASS_AUTHN, "response.error.state-expired", "state expired errors in authentication responses" }, { OM_CLASS_AUTHN, "response.error.provider", "errors returned by the provider in authentication responses" }, { OM_CLASS_AUTHN, "response.error.protocol", "protocol errors handling authentication responses" }, { OM_CLASS_AUTHN, "response.error.remote-user", "errors identifying the remote user based on provided claims" }, { OM_CLASS_AUTHZ, "action.auth", "step-up authentication requests" }, { OM_CLASS_AUTHZ, "action.401", "401 authorization errors" }, { OM_CLASS_AUTHZ, "action.403", "403 authorization errors" }, { OM_CLASS_AUTHZ, "action.302", "302 authorization errors" }, { OM_CLASS_AUTHZ, "error.oauth20", "AuthType oauth20 (401) authorization errors" }, { OM_CLASS_REQUIRE_CLAIM, "match", "(per-) Require claim authorization matches" }, { OM_CLASS_REQUIRE_CLAIM, "error", "(per-) Require claim authorization errors" }, { OM_CLASS_CLAIM, "id_token", "claim values in the ID Token" }, { OM_CLASS_CLAIM, "userinfo", "claim values returned from the Userinfo Endpoint" }, { OM_CLASS_PROVIDER, "metadata.error", "errors retrieving a provider discovery document" }, { OM_CLASS_PROVIDER, "token.error", "errors making a token request to a provider" }, { OM_CLASS_PROVIDER, "refresh.error", "errors refreshing the access token at the token endpoint" }, { OM_CLASS_PROVIDER, "userinfo.error", "errors calling a provider userinfo endpoint" }, { OM_CLASS_PROVIDER, "http.connect.error", "(libcurl) provider/network connectivity errors" }, { OM_CLASS_PROVIDER, "http.response.code", "HTTP response code calling a provider endpoint" }, { OM_CLASS_SESSION, "error.cookie-domain", "cookie domain validation errors for existing sessions" }, { OM_CLASS_SESSION, "error.expired", "sessions that exceeded the maximum duration" }, { OM_CLASS_SESSION, "error.refresh-access-token", "errors refreshing the access token before expiry in existing sessions" }, { OM_CLASS_SESSION, "error.refresh-user-info", "errors refreshing claims from the userinfo endpoint in existing sessions" }, { OM_CLASS_SESSION, "error.general", "existing sessions that failed validation" }, { OM_CLASS_CACHE, "cache.error", "cache read/write errors" }, { OM_CLASS_REDIRECT_URI, "authn.response.redirect", "authentication responses received in a redirect", }, { OM_CLASS_REDIRECT_URI, "authn.response.post", "authentication responses received in a HTTP POST", }, { OM_CLASS_REDIRECT_URI, "authn.response.implicit", "(presumed) implicit authentication responses to the redirect URI", }, { OM_CLASS_REDIRECT_URI, "discovery.response", "discovery responses to the redirect URI", }, { OM_CLASS_REDIRECT_URI, "request.logout", "logout requests to the redirect URI", }, { OM_CLASS_REDIRECT_URI, "request.jwks", "JWKs retrieval requests to the redirect URI", }, { OM_CLASS_REDIRECT_URI, "request.session", "session management requests to the redirect URI", }, { OM_CLASS_REDIRECT_URI, "request.refresh", "refresh access token requests to the redirect URI", }, { OM_CLASS_REDIRECT_URI, "request.request_uri", "Request URI calls to the redirect URI", }, { OM_CLASS_REDIRECT_URI, "request.remove_at_cache", "access token cache removal requests to the redirect URI", }, { OM_CLASS_REDIRECT_URI, "request.session", "revoke session requests to the redirect URI", }, { OM_CLASS_REDIRECT_URI, "request.info", "info hook requests to the redirect URI", }, { OM_CLASS_REDIRECT_URI, "request.dpop", "DPoP requests to the redirect URI", }, { OM_CLASS_REDIRECT_URI, "error.provider", "provider authentication response errors received at the redirect URI", }, { OM_CLASS_REDIRECT_URI, "error.invalid", "invalid requests to the redirect URI", }, { OM_CLASS_CONTENT, "request.declined", "requests declined by the content handler" }, { OM_CLASS_CONTENT, "request.info", "info hook requests to the content handler" }, { OM_CLASS_CONTENT, "request.dpop", "DPoP requests to the content handler" }, { OM_CLASS_CONTENT, "request.jwks", "JWKs requests to the content handler" }, { OM_CLASS_CONTENT, "request.discovery", "discovery requests to the content handler" }, { OM_CLASS_CONTENT, "request.post-preserve", "HTTP POST preservation requests to the content handler" }, { OM_CLASS_CONTENT, "request.unknown", "unknown requests to the content handler" }, // KEEP THIS: end-of-counters }; // clang-format on typedef struct oidc_metrics_t { apr_hash_t *counters; apr_hash_t *timings; } oidc_metrics_t; // pointer to the shared memory segment that holds the JSON metrics data static apr_shm_t *_oidc_metrics_cache = NULL; // flag to record if we are a parent process or a child process static apr_byte_t _oidc_metrics_is_parent = FALSE; // flag to signal the metrics write thread to exit static apr_byte_t _oidc_metrics_thread_exit = FALSE; // mutex to protect the shared memory storage static oidc_cache_mutex_t *_oidc_metrics_global_mutex = NULL; // pointer to the thread that periodically writes the locally gathered metrics to shared memory static apr_thread_t *_oidc_metrics_thread = NULL; // local in-memory cached metrics static oidc_metrics_t _oidc_metrics = {NULL, NULL}; // mutex to protect the local metrics hash table static oidc_cache_mutex_t *_oidc_metrics_process_mutex = NULL; // default shared memory write interval in seconds #define OIDC_METRICS_CACHE_STORAGE_INTERVAL_DEFAULT 5000 // maximum length of the string representation of the global JSON metrics data in shared memory // 1024 sample size (compact, long keys, large json_int values, no description), timing + counter // 256 number of individual metrics collected // 4 number of vhosts supported #define OIDC_METRICS_CACHE_JSON_MAX_DEFAULT 1024 * 256 * 4 typedef struct oidc_metrics_bucket_t { const char *name; const char *label; apr_time_t threshold; } oidc_metrics_bucket_t; // clang-format off static oidc_metrics_bucket_t _oidc_metric_buckets[] = { //{ "le005", "bucket{le=\"0.05\"}", 50 }, { "le01", "le=\"0.1\"", 100 }, { "le05", "le=\"0.5\"", 500 }, { "le1", "le=\"1\"", apr_time_from_msec(1) }, { "le5", "le=\"5\"", apr_time_from_msec(5) }, { "le10", "le=\"10\"", apr_time_from_msec(10) }, { "le50", "le=\"50\"", apr_time_from_msec(50) }, { "le100", "le=\"100\"", apr_time_from_msec(100) }, { "le500", "le=\"500\"", apr_time_from_msec(500) }, { "le1000", "le=\"1000\"", apr_time_from_msec(1000) }, { "le5000", "le=\"5000\"", apr_time_from_msec(5000) }, { "inf", "le=\"+Inf\"", 0 } }; // clang-format on #define OIDC_METRICS_BUCKET_NUM sizeof(_oidc_metric_buckets) / sizeof(oidc_metrics_bucket_t) // NB: matters for Prometheus formatting #define OIDC_METRICS_SUM "sum" #define OIDC_METRICS_COUNT "count" #define OIDC_METRICS_SPECS "specs" #define OIDC_METRICS_JSON_CLASS_NAME "class" #define OIDC_METRICS_JSON_METRIC_NAME "name" #define OIDC_METRICS_JSON_DESC "desc" #define OIDC_METRICS_TIMINGS "timings" #define OIDC_METRICS_COUNTERS "counters" /* * convert a Jansson number to a string: JSON_INTEGER_FORMAT does not work with apr_psprintf !? */ static inline char *_json_int2str(apr_pool_t *pool, json_int_t n) { char s[255]; snprintf(s, 255, "%" JSON_INTEGER_FORMAT, n); return apr_pstrdup(pool, s); } #if JSON_INTEGER_IS_LONG_LONG #define OIDC_METRICS_INT_MAX LLONG_MAX #else #define OIDC_METRICS_INT_MAX LONG_MAX #endif /* * check Jansson specific integer/long number overrun */ static inline int _is_overflow(server_rec *s, json_int_t cur, json_int_t add) { if ((add > OIDC_METRICS_INT_MAX - cur)) { oidc_swarn(s, "reset metrics since the size (%s) of the integer value would be larger than the " "JSON/libjansson maximum " "(%s)", _json_int2str(s->process->pool, add), _json_int2str(s->process->pool, OIDC_METRICS_INT_MAX)); return 1; } return 0; } // single counter container typedef struct oidc_metrics_counter_t { json_int_t count; } oidc_metrics_counter_t; // single timing stats container typedef struct oidc_metrics_timing_t { json_int_t buckets[OIDC_METRICS_BUCKET_NUM]; apr_time_t sum; json_int_t count; } oidc_metrics_timing_t; // context holder for parsing valid classnames typedef struct oidc_metrics_add_classname_ctx_t { apr_pool_t *pool; char **valid_names; } oidc_metrics_add_classname_ctx_t; /* * loop function for parsing valid classnames */ static int oidc_metrics_add_classnames(void *rec, const char *key, const char *value) { oidc_metrics_add_classname_ctx_t *ctx = (oidc_metrics_add_classname_ctx_t *)rec; *ctx->valid_names = apr_psprintf(ctx->pool, "%s%s%s", *ctx->valid_names ? *ctx->valid_names : "", *ctx->valid_names ? " | " : "", value); return 1; } /* * check if the provided value is a valid classname */ apr_byte_t oidc_metrics_is_valid_classname(apr_pool_t *pool, const char *name, char **valid_names) { int i = 0; int n = 0; apr_table_t *names = apr_table_make(pool, 1); oidc_metrics_add_classname_ctx_t ctx = {pool, valid_names}; n = sizeof(_oidc_metrics_timings_info) / sizeof(oidc_metrics_timing_info_t); for (i = 0; i < n; i++) { apr_table_set(names, _oidc_metrics_timings_info[i].class_name, _oidc_metrics_timings_info[i].class_name); } n = sizeof(_oidc_metrics_counters_info) / sizeof(oidc_metrics_counter_info_t); for (i = 0; i < n; i++) { // TODO: instead of using hardcoded single "claim" name/value option, make this a static list if (_oidc_strcmp(_oidc_metrics_counters_info[i].class_name, "claim") != 0) apr_table_set(names, _oidc_metrics_counters_info[i].class_name, _oidc_metrics_counters_info[i].class_name); } *valid_names = NULL; apr_table_do(oidc_metrics_add_classnames, &ctx, names, NULL); *valid_names = apr_psprintf(pool, "%s%s%s", *valid_names ? *valid_names : "", *valid_names ? " | " : "", "claim.id_token.* | claim.userinfo.*"); return apr_table_get(names, name) ? TRUE : ((_oidc_strstr(name, "claim.id_token.") != NULL) || (_oidc_strstr(name, "claim.userinfo.") != NULL)); } /* * collection thread */ /* * retrieve the (JSON) serialized (global) metrics data from shared memory */ static inline char *_oidc_metrics_storage_get(server_rec *s) { char *p = (char *)apr_shm_baseaddr_get(_oidc_metrics_cache); return ((p) && (*p != 0)) ? apr_pstrdup(s->process->pool, p) : NULL; } /* * retrieve environment variable integer with default setting */ static inline int _oidc_metrics_get_env_int(const char *name, int dval) { return _oidc_str_to_int(getenv(name), dval); } #define OIDC_METRICS_CACHE_JSON_MAX_ENV_VAR "OIDC_METRICS_CACHE_JSON_MAX" static apr_size_t _g_oidc_metrics_shm_size = 0; /* * get the size of the to-be-allocated shared memory segment */ static inline apr_size_t _oidc_metrics_shm_size(server_rec *s) { if (_g_oidc_metrics_shm_size == 0) { int n = _oidc_metrics_get_env_int(OIDC_METRICS_CACHE_JSON_MAX_ENV_VAR, OIDC_METRICS_CACHE_JSON_MAX_DEFAULT); if ((n < 1) || (n > 1024 * 256 * 4 * 100)) { oidc_serror(s, "environment value %s out of bounds, fallback to default", OIDC_METRICS_CACHE_JSON_MAX_ENV_VAR); _g_oidc_metrics_shm_size = OIDC_METRICS_CACHE_JSON_MAX_DEFAULT; } else { _g_oidc_metrics_shm_size = n; } } return _g_oidc_metrics_shm_size; } /* * store the serialized (global) metrics data in shared memory */ static inline void _oidc_metrics_storage_set(server_rec *s, const char *value) { char *p = apr_shm_baseaddr_get(_oidc_metrics_cache); if (value) { int n = _oidc_strlen(value) + 1; if (n > _oidc_metrics_shm_size(s)) oidc_serror(s, "json value too large: set or increase system environment variable %s to a value " "larger than %" APR_SIZE_T_FMT, OIDC_METRICS_CACHE_JSON_MAX_ENV_VAR, _oidc_metrics_shm_size(s)); else _oidc_memcpy(p, value, n); } else { *p = 0; } } /* * parse a string into a JSON object */ static json_t *oidc_metrics_json_load(char *s_json, json_error_t *json_error) { if (s_json == NULL) s_json = "{}"; return json_loads(s_json, 0, json_error); } /* * parse a string into a JSON object in a server_rec context */ static json_t *oidc_metrics_json_parse_s(server_rec *s, char *s_json) { json_error_t json_error; json_t *json = oidc_metrics_json_load(s_json, &json_error); if (json == NULL) oidc_serror(s, "JSON parsing failed: %s", json_error.text); return json; } /* * reset the serialized (global) metrics data in shared memory */ static inline void oidc_metrics_storage_reset(server_rec *s) { char *s_json = NULL; json_t *json = NULL, *j_server = NULL, *j_entries = NULL, *j_entry = NULL, *j_val = NULL; void *i1 = NULL, *i2 = NULL, *i3 = NULL, *i4 = NULL; int i = 0; /* get the global stringified JSON metrics */ s_json = _oidc_metrics_storage_get(s); /* parse the metrics string to JSON */ json = oidc_metrics_json_parse_s(s, s_json); if (json == NULL) json = json_object(); i1 = json_object_iter(json); while (i1) { j_server = json_object_iter_value(i1); // counters j_entries = json_object_get(j_server, OIDC_METRICS_COUNTERS); i2 = json_object_iter(j_entries); while (i2) { j_entry = json_object_iter_value(i2); if (json_is_integer(j_entry)) { json_integer_set(j_entry, 0); } else { i3 = json_object_iter(j_entry); while (i3) { j_val = json_object_iter_value(i3); if (json_is_integer(j_val)) { json_integer_set(j_val, 0); } else { i4 = json_object_iter(j_val); while (i4) { json_integer_set(json_object_iter_value(i4), 0); i4 = json_object_iter_next(j_val, i4); } } i3 = json_object_iter_next(j_entry, i3); } } i2 = json_object_iter_next(j_entries, i2); } // timers j_entries = json_object_get(j_server, OIDC_METRICS_TIMINGS); i2 = json_object_iter(j_entries); while (i2) { j_entry = json_object_iter_value(i2); for (i = 0; i < OIDC_METRICS_BUCKET_NUM; i++) json_object_set_new(j_entry, _oidc_metric_buckets[i].name, json_integer(0)); json_object_set_new(j_entry, OIDC_METRICS_SUM, json_integer(0)); json_object_set_new(j_entry, OIDC_METRICS_COUNT, json_integer(0)); i2 = json_object_iter_next(j_entries, i2); } i1 = json_object_iter_next(json, i1); } /* serialize the metrics data, preserve order is required for Prometheus */ s_json = oidc_util_encode_json(s->process->pool, json, JSON_COMPACT | JSON_PRESERVE_ORDER); /* free the JSON data */ json_decref(json); /* store the serialized metrics data in shared memory */ _oidc_metrics_storage_set(s, s_json); } /* * create a new timings entry in the collected JSON data */ static json_t *oidc_metrics_timings_new(server_rec *s, const oidc_metrics_timing_t *timing) { int i = 0; json_t *entry = json_object(); for (i = 0; i < OIDC_METRICS_BUCKET_NUM; i++) json_object_set_new(entry, _oidc_metric_buckets[i].name, json_integer(timing->buckets[i])); json_object_set_new(entry, OIDC_METRICS_SUM, json_integer(apr_time_as_msec(timing->sum))); json_object_set_new(entry, OIDC_METRICS_COUNT, json_integer(timing->count)); return entry; } /* * update an entry in the collected JSON data */ static void oidc_metrics_timings_update(server_rec *s, const json_t *entry, const oidc_metrics_timing_t *timing) { json_t *j_member = NULL; json_int_t n = 0, v = 0; int i = 0; for (i = 0; i < OIDC_METRICS_BUCKET_NUM; i++) { j_member = json_object_get(entry, _oidc_metric_buckets[i].name); json_integer_set(j_member, json_integer_value(j_member) + timing->buckets[i]); } j_member = json_object_get(entry, OIDC_METRICS_SUM); n = json_integer_value(j_member); v = apr_time_as_msec(timing->sum); if (_is_overflow(s, n, v)) n = 0; json_integer_set(j_member, n + v); j_member = json_object_get(entry, OIDC_METRICS_COUNT); n = json_integer_value(j_member); json_integer_set(j_member, n + timing->count); } #define OIDC_METRICS_VALUE_DEFAULT "_" /* * value helper to make sure it is not empty */ static inline const char *_metrics_value2key(const char *value) { return (value && _oidc_strcmp(value, "") != 0) ? value : OIDC_METRICS_VALUE_DEFAULT; } /* * create a new counter entry in the collected JSON data */ static json_t *oidc_metrics_counter_new(server_rec *s, apr_hash_t *htable) { apr_hash_index_t *hi = NULL; oidc_metrics_counter_t *counter = NULL; char *value = NULL; json_t *j_values = NULL; for (hi = apr_hash_first(s->process->pool, htable); hi; hi = apr_hash_next(hi)) { apr_hash_this(hi, (const void **)&value, NULL, (void **)&counter); if (_oidc_strcmp(value, OIDC_METRICS_VALUE_DEFAULT) == 0) { j_values = json_integer(counter->count); } else { if (j_values == NULL) j_values = json_object(); json_object_set_new(j_values, value, json_integer(counter->count)); } } return j_values; } /* * update a counter entry in the collected JSON data */ static void oidc_metrics_counter_update(server_rec *s, json_t *j_counter, apr_hash_t *htable) { json_int_t v = 0; apr_hash_index_t *hi = NULL; oidc_metrics_counter_t *counter = NULL; char *value = NULL; json_t *j_value = NULL; for (hi = apr_hash_first(s->process->pool, htable); hi; hi = apr_hash_next(hi)) { apr_hash_this(hi, (const void **)&value, NULL, (void **)&counter); if (_oidc_strcmp(value, OIDC_METRICS_VALUE_DEFAULT) == 0) { j_value = j_counter; } else { j_value = json_object_get(j_counter, value); if (j_value == NULL) { json_object_set_new(j_counter, value, json_integer(counter->count)); continue; } } v = json_integer_value(j_value); if (_is_overflow(s, v, counter->count)) v = 0; json_integer_set(j_value, v + counter->count); } } /* * get or create the vhost entry in the global metrics */ static json_t *oidc_metrics_server_get(json_t *json, const char *name) { json_t *j_server = json_object_get(json, name); if (j_server == NULL) { j_server = json_object(); json_object_set_new(j_server, OIDC_METRICS_COUNTERS, json_object()); json_object_set_new(j_server, OIDC_METRICS_TIMINGS, json_object()); json_object_set_new(json, name, j_server); } return j_server; } /* * convert an enum type value to its corresponding string */ static inline char *_oidc_metrics_type_name2key(apr_pool_t *pool, unsigned int type, const char *name) { return (name == NULL) ? apr_psprintf(pool, "%u", type) : apr_psprintf(pool, "%u.%s", type, name); } /* * convert a string key type to an enum type */ static inline unsigned int _oidc_metrics_key2type(const char *key) { unsigned int type = 0; sscanf(key, "%u", &type); return type; } /* * flush the locally gathered metrics data into the global data kept in shared memory */ static void oidc_metrics_store(server_rec *s) { char *s_json = NULL; json_t *json = NULL, *j_server = NULL, *j_timer = NULL, *j_counters = NULL, *j_counter = NULL, *j_timings = NULL, *j_names = NULL; apr_hash_index_t *hi1 = NULL, *hi2 = NULL; const char *name = NULL, *key = NULL; char *p = NULL; apr_hash_t *server_hash = NULL, *counter_hash = NULL; oidc_metrics_timing_t *timing = NULL; if ((apr_hash_count(_oidc_metrics.counters) == 0) && (apr_hash_count(_oidc_metrics.timings) == 0)) return; /* lock the shared memory for other processes */ oidc_cache_mutex_lock(s->process->pool, s, _oidc_metrics_global_mutex); /* get the global stringified JSON metrics */ s_json = _oidc_metrics_storage_get(s); /* parse the metrics string to JSON */ json = oidc_metrics_json_parse_s(s, s_json); if (json == NULL) json = json_object(); for (hi1 = apr_hash_first(s->process->pool, _oidc_metrics.counters); hi1; hi1 = apr_hash_next(hi1)) { apr_hash_this(hi1, (const void **)&name, NULL, (void **)&server_hash); j_server = oidc_metrics_server_get(json, name); j_counters = json_object_get(j_server, OIDC_METRICS_COUNTERS); /* loop over the individual metrics */ for (hi2 = apr_hash_first(s->process->pool, server_hash); hi2; hi2 = apr_hash_next(hi2)) { apr_hash_this(hi2, (const void **)&key, NULL, (void **)&counter_hash); key = apr_pstrdup(s->process->pool, key); p = _oidc_strstr(key, "."); if (p == NULL) { /* get or create the corresponding metric entry in the global metrics */ j_counter = json_object_get(j_counters, key); if (j_counter != NULL) oidc_metrics_counter_update(s, j_counter, counter_hash); else json_object_set_new(j_counters, key, oidc_metrics_counter_new(s, counter_hash)); } else { *p = '\0'; p++; // p now points to the name, key points to the class j_names = json_object_get(j_counters, key); if (j_names == NULL) { j_names = json_object(); json_object_set_new(j_names, p, oidc_metrics_counter_new(s, counter_hash)); json_object_set_new(j_counters, key, j_names); } else { j_counter = json_object_get(j_names, p); if (j_counter != NULL) { oidc_metrics_counter_update(s, j_counter, counter_hash); } else { json_object_set_new(j_names, p, oidc_metrics_counter_new(s, counter_hash)); } } } } } /* loop over the locally cached metrics from this process */ for (hi1 = apr_hash_first(s->process->pool, _oidc_metrics.timings); hi1; hi1 = apr_hash_next(hi1)) { apr_hash_this(hi1, (const void **)&name, NULL, (void **)&server_hash); j_server = oidc_metrics_server_get(json, name); j_timings = json_object_get(j_server, OIDC_METRICS_TIMINGS); /* loop over the individual metrics */ for (hi2 = apr_hash_first(s->process->pool, server_hash); hi2; hi2 = apr_hash_next(hi2)) { apr_hash_this(hi2, (const void **)&key, NULL, (void **)&timing); /* get or create the corresponding metric entry in the global metrics */ j_timer = json_object_get(j_timings, key); if (j_timer != NULL) oidc_metrics_timings_update(s, j_timer, timing); else json_object_set_new(j_timings, key, oidc_metrics_timings_new(s, timing)); } } /* serialize the metrics data, preserve order is required for Prometheus */ s_json = oidc_util_encode_json(s->process->pool, json, JSON_COMPACT | JSON_PRESERVE_ORDER); /* free the JSON data */ json_decref(json); /* store the serialized metrics data in shared memory */ _oidc_metrics_storage_set(s, s_json); /* unlock the shared memory for other processes */ oidc_cache_mutex_unlock(s->process->pool, s, _oidc_metrics_global_mutex); } #define OIDC_METRICS_CACHE_STORAGE_INTERVAL_ENV_VAR "OIDC_METRICS_CACHE_STORAGE_INTERVAL" /* * obtain the metrics flush interval from the environment variables */ static inline apr_interval_time_t _oidc_metrics_interval(server_rec *s) { return apr_time_from_msec(_oidc_metrics_get_env_int(OIDC_METRICS_CACHE_STORAGE_INTERVAL_ENV_VAR, OIDC_METRICS_CACHE_STORAGE_INTERVAL_DEFAULT)); } /* * generate a random integer value in the specified modulo range */ static unsigned int oidc_metric_random_int(unsigned int mod) { unsigned int v; oidc_util_random_bytes((unsigned char *)&v, sizeof(v)); return v % mod; } #define OIDC_METRICS_POLL_INTERVAL 250 /* * thread that periodically writes the local data into the shared memory */ static void *APR_THREAD_FUNC oidc_metrics_thread_run(apr_thread_t *thread, void *data) { server_rec *s = (server_rec *)data; /* sleep for a short random time <1s so child processes write-lock on a different frequency */ apr_sleep(apr_time_from_msec(oidc_metric_random_int(1000))); /* calculate the number of short sleep intervals */ int n = _oidc_metrics_interval(s) / apr_time_from_msec(OIDC_METRICS_POLL_INTERVAL); /* see if we are asked to exit */ while (_oidc_metrics_thread_exit == FALSE) { /* break up the sleep interval in short intervals so we can exit timely fashion without confusing Apache * at shutdown */ for (int i = 0; i < n; i++) { apr_sleep(apr_time_from_msec(OIDC_METRICS_POLL_INTERVAL)); if (_oidc_metrics_thread_exit == TRUE) break; } // NB: no exit here because we need to write our local metrics into the cache before exiting /* lock the mutex that protects the locally cached metrics */ oidc_cache_mutex_lock(s->process->pool, s, _oidc_metrics_process_mutex); /* flush the locally cached metrics into the global shared memory */ oidc_metrics_store(s); /* reset the local hashtables */ oidc_util_apr_hash_clear(_oidc_metrics.counters); oidc_util_apr_hash_clear(_oidc_metrics.timings); /* unlock the mutex that protects the locally cached metrics */ oidc_cache_mutex_unlock(s->process->pool, s, _oidc_metrics_process_mutex); } /* NB: don't call apr_thread_exit here because it seems that Apache is cleaning up its own threads */ // apr_thread_exit(thread, APR_SUCCESS); return NULL; } /* * server config handlers */ /* * NB: global, yet called for each vhost that has metrics enabled! */ apr_byte_t oidc_metrics_post_config(server_rec *s) { /* make sure it gets executed exactly once! */ if (_oidc_metrics_cache != NULL) return TRUE; /* create the shared memory segment that holds the stringified JSON formatted metrics data */ if (apr_shm_create(&_oidc_metrics_cache, _oidc_metrics_shm_size(s), NULL, s->process->pconf) != APR_SUCCESS) return FALSE; if (_oidc_metrics_cache == NULL) return FALSE; /* initialize the shared memory segment to 0 */ char *p = apr_shm_baseaddr_get(_oidc_metrics_cache); _oidc_memset(p, 0, _oidc_metrics_shm_size(s)); /* flag this as the parent, for shared memory cleanup purposes and "multiple child-init calls" detection */ _oidc_metrics_is_parent = TRUE; /* create the thread that will periodically flush the local metrics data to shared memory */ if (apr_thread_create(&_oidc_metrics_thread, NULL, oidc_metrics_thread_run, s, s->process->pool) != APR_SUCCESS) return FALSE; /* create the hashtable that holds local metrics data */ _oidc_metrics.counters = apr_hash_make(s->process->pool); _oidc_metrics.timings = apr_hash_make(s->process->pool); /* create and initialize the mutex that guards _oidc_metrics_hash */ _oidc_metrics_global_mutex = oidc_cache_mutex_create(s->process->pool, TRUE); if (_oidc_metrics_global_mutex == NULL) return FALSE; if (oidc_cache_mutex_post_config(s, _oidc_metrics_global_mutex, "metrics-global") == FALSE) return FALSE; /* create and initialize the mutex that guards the shared memory */ _oidc_metrics_process_mutex = oidc_cache_mutex_create(s->process->pool, FALSE); if (_oidc_metrics_process_mutex == NULL) return FALSE; if (oidc_cache_mutex_post_config(s, _oidc_metrics_process_mutex, "metrics-process") == FALSE) return FALSE; return TRUE; } /* * NB: global, yet called for each vhost that has metrics enabled! */ apr_status_t oidc_metrics_child_init(apr_pool_t *p, server_rec *s) { /* make sure this executes only once per child */ if (_oidc_metrics_is_parent == FALSE) return APR_SUCCESS; if (oidc_cache_mutex_child_init(p, s, _oidc_metrics_global_mutex) != APR_SUCCESS) return APR_EGENERAL; if (oidc_cache_mutex_child_init(p, s, _oidc_metrics_process_mutex) != APR_SUCCESS) return APR_EGENERAL; /* the metrics flush thread is not inherited from the parent, so re-create it in the child */ if (apr_thread_create(&_oidc_metrics_thread, NULL, oidc_metrics_thread_run, s, s->process->pool) != APR_SUCCESS) return APR_EGENERAL; /* flag this is a child */ _oidc_metrics_is_parent = FALSE; return APR_SUCCESS; } /* * NB: global, yet called for each vhost that has metrics enabled! */ apr_status_t oidc_metrics_cleanup(server_rec *s) { apr_status_t rv = APR_SUCCESS; /* make sure it gets executed exactly once! */ if ((_oidc_metrics_cache == NULL) || (_oidc_metrics_thread_exit == TRUE) || (_oidc_metrics_thread == NULL)) return APR_SUCCESS; /* signal the collector thread to exit */ _oidc_metrics_thread_exit = TRUE; apr_thread_join(&rv, _oidc_metrics_thread); if (rv != APR_SUCCESS) oidc_serror(s, "apr_thread_join failed"); _oidc_metrics_thread = NULL; /* delete the shared memory segment if we are in the parent process */ if (_oidc_metrics_is_parent == TRUE) apr_shm_destroy(_oidc_metrics_cache); _oidc_metrics_cache = NULL; /* delete the process mutex that guards the local metrics data */ if (oidc_cache_mutex_destroy(s, _oidc_metrics_process_mutex) == FALSE) return APR_EGENERAL; _oidc_metrics_process_mutex = NULL; /* delete the process mutex that guards the global shared memory segment */ if (oidc_cache_mutex_destroy(s, _oidc_metrics_global_mutex) == FALSE) return APR_EGENERAL; _oidc_metrics_global_mutex = NULL; return APR_SUCCESS; } /* * sampling */ /* * obtain the local metrics hashtable for the current vhost */ static inline apr_hash_t *_oidc_metrics_server_hash(request_rec *r, apr_hash_t *table) { apr_hash_t *server_hash = NULL; char *name = "_default_"; /* obtain the server name */ if (r->server->server_hostname) name = r->server->server_hostname; /* get the entry to the vhost record, or newly create it */ server_hash = apr_hash_get(table, name, APR_HASH_KEY_STRING); if (server_hash == NULL) { // NB: process pool! server_hash = apr_hash_make(r->server->process->pool); apr_hash_set(table, name, APR_HASH_KEY_STRING, server_hash); } return server_hash; } /* * retrieve or create a local timing for the specified type */ static inline oidc_metrics_timing_t *_oidc_metrics_timing_get(request_rec *r, unsigned int type) { oidc_metrics_timing_t *result = NULL; const char *key = _oidc_metrics_type_name2key(r->server->process->pool, type, NULL); apr_hash_t *server_hash = _oidc_metrics_server_hash(r, _oidc_metrics.timings); /* get the entry to the specified metric */ result = apr_hash_get(server_hash, key, APR_HASH_KEY_STRING); if (result == NULL) { /* allocate the timing structure in the process pool */ result = apr_pcalloc(r->server->process->pool, sizeof(oidc_metrics_timing_t)); apr_hash_set(server_hash, key, APR_HASH_KEY_STRING, result); } return result; } /* * retrieve or create a counter from a hashtable of values */ static inline oidc_metrics_counter_t *_oidc_metrics_counter_value_get(request_rec *r, apr_hash_t *table, const char *value) { /* get the entry to the specified metric */ oidc_metrics_counter_t *result = apr_hash_get(table, value, APR_HASH_KEY_STRING); if (result == NULL) { result = apr_pcalloc(r->server->process->pool, sizeof(oidc_metrics_counter_t)); apr_hash_set(table, apr_pstrdup(r->server->process->pool, value), APR_HASH_KEY_STRING, result); } return result; } /* * retrieve or create a local counter for the specified type and name */ static inline apr_hash_t *_oidc_metrics_counter_get(request_rec *r, unsigned int type, const char *name) { apr_hash_t *result = NULL; const char *key = _oidc_metrics_type_name2key(r->server->process->pool, type, name); apr_hash_t *server_hash = _oidc_metrics_server_hash(r, _oidc_metrics.counters); /* get the entry to the specified metric */ result = apr_hash_get(server_hash, key, APR_HASH_KEY_STRING); if (result == NULL) { /* allocate the values hashtable in the process pool */ result = apr_hash_make(r->server->process->pool); apr_hash_set(server_hash, key, APR_HASH_KEY_STRING, result); } return result; } /* * add/increase a counter metric in the locally cached data */ void oidc_metrics_counter_inc(request_rec *r, oidc_metrics_counter_type_t type, const char *name, const char *value) { oidc_metrics_counter_t *counter = NULL; /* lock the local metrics cache hashtable */ oidc_cache_mutex_lock(r->pool, r->server, _oidc_metrics_process_mutex); /* obtain or create the entry for the specified key */ counter = _oidc_metrics_counter_value_get(r, _oidc_metrics_counter_get(r, type, name), _metrics_value2key(value)); /* performance */ if (counter->count <= 0) { // new counter was created just now or reset earlier counter->count = 1; } else { // increase after checking possible overflow if (_is_overflow(r->server, counter->count, 1)) counter->count = 0; counter->count++; } /* unlock the local metrics cache hashtable */ oidc_cache_mutex_unlock(r->pool, r->server, _oidc_metrics_process_mutex); } /* * add a metrics timing sample to the locally cached data */ void oidc_metrics_timing_add(request_rec *r, oidc_metrics_timing_type_t type, apr_time_t elapsed) { oidc_metrics_timing_t *timing = NULL; int i = 0; /* TODO: how can this happen? */ if (elapsed < 0) { oidc_warn(r, "discarding metrics timing [%s.%s]: elapsed (%" APR_TIME_T_FMT ") < 0", _oidc_metrics_timings_info[type].class_name, _oidc_metrics_timings_info[type].metric_name, elapsed); return; } /* lock the local metrics cache hashtable */ oidc_cache_mutex_lock(r->pool, r->server, _oidc_metrics_process_mutex); /* obtain or create the entry for the specified key */ timing = _oidc_metrics_timing_get(r, type); /* performance */ if (timing->count <= 0) { // new timing was created just now or reset earlier for (i = 0; i < OIDC_METRICS_BUCKET_NUM; i++) { if ((elapsed < _oidc_metric_buckets[i].threshold) || (_oidc_metric_buckets[i].threshold == 0)) { // fill out the remaining buckets and break, as they are ordered for (; i < OIDC_METRICS_BUCKET_NUM; i++) timing->buckets[i] = 1; break; } } timing->sum = elapsed; timing->count = 1; } else { if (_is_overflow(r->server, timing->sum, elapsed)) { timing->count = 0; timing->sum = 0; for (i = 0; i < OIDC_METRICS_BUCKET_NUM; i++) timing->buckets[i] = 0; } for (i = 0; i < OIDC_METRICS_BUCKET_NUM; i++) { if ((elapsed < _oidc_metric_buckets[i].threshold) || (_oidc_metric_buckets[i].threshold == 0)) { // fill out the remaining buckets and break, as they are ordered for (; i < OIDC_METRICS_BUCKET_NUM; i++) timing->buckets[i]++; break; } } timing->sum += elapsed; timing->count++; } /* unlock the local metrics cache hashtable */ oidc_cache_mutex_unlock(r->pool, r->server, _oidc_metrics_process_mutex); } /* * representation handlers */ /* * convert in integer counter enum type to its corresponding string name */ static inline char *_oidc_metrics_counter_type2s(apr_pool_t *pool, unsigned int type) { return apr_psprintf(pool, "%s.%s", _oidc_metrics_counters_info[type].class_name, _oidc_metrics_counters_info[type].metric_name); } /* * convert in integer timings enum type to its corresponding string name */ static inline char *_oidc_metrics_timing_type2s(apr_pool_t *pool, unsigned int type) { return apr_psprintf(pool, "%s.%s", _oidc_metrics_timings_info[type].class_name, _oidc_metrics_timings_info[type].metric_name); } /* * parse a string into a JSON object in the request_rec context */ static json_t *oidc_metrics_json_parse_r(request_rec *r, char *s_json) { json_error_t json_error; json_t *json = oidc_metrics_json_load(s_json, &json_error); if (json == NULL) oidc_error(r, "JSON parsing failed: %s", json_error.text); return json; } /* * JSON with extended descriptions/names */ static int oidc_metrics_handle_json(request_rec *r, char *s_json) { json_t *json = NULL, *j_server = NULL, *j_timings, *j_counters, *j_timing = NULL, *j_counter = NULL; json_t *o_json = NULL, *o_server = NULL, *o_counters = NULL, *o_counter = NULL, *o_timings = NULL, *o_timing = NULL; const char *s_server = NULL; unsigned int type = 0; void *i1 = NULL, *i2 = NULL; /* parse the metrics string to JSON */ json = oidc_metrics_json_parse_r(r, s_json); if (json == NULL) goto end; o_json = json_object(); i1 = json_object_iter(json); while (i1) { s_server = json_object_iter_key(i1); j_server = json_object_iter_value(i1); o_server = json_object(); json_object_set_new(o_json, s_server, o_server); j_counters = json_object_get(j_server, OIDC_METRICS_COUNTERS); o_counters = json_object(); json_object_set_new(o_server, OIDC_METRICS_COUNTERS, o_counters); i2 = json_object_iter(j_counters); while (i2) { type = _oidc_metrics_key2type(json_object_iter_key(i2)); j_counter = json_object_iter_value(i2); o_counter = json_object(); if (json_is_integer(j_counter)) json_object_set(o_counter, "count", j_counter); else json_object_set_new(o_counter, "values", json_deep_copy(j_counter)); json_object_set_new(o_counter, OIDC_METRICS_JSON_CLASS_NAME, json_string(_oidc_metrics_counters_info[type].class_name)); json_object_set_new(o_counter, OIDC_METRICS_JSON_METRIC_NAME, json_string(_oidc_metrics_counters_info[type].metric_name)); json_object_set_new(o_counter, OIDC_METRICS_JSON_DESC, json_string(_oidc_metrics_counters_info[type].desc)); json_object_set_new(o_counters, _oidc_metrics_counter_type2s(r->pool, type), o_counter); i2 = json_object_iter_next(j_counters, i2); } j_timings = json_object_get(j_server, OIDC_METRICS_TIMINGS); o_timings = json_object(); json_object_set_new(o_server, OIDC_METRICS_TIMINGS, o_timings); i2 = json_object_iter(j_timings); while (i2) { type = _oidc_metrics_key2type(json_object_iter_key(i2)); j_timing = json_object_iter_value(i2); o_timing = json_deep_copy(j_timing); json_object_set_new(o_timing, OIDC_METRICS_JSON_CLASS_NAME, json_string(_oidc_metrics_timings_info[type].class_name)); json_object_set_new(o_timing, OIDC_METRICS_JSON_METRIC_NAME, json_string(_oidc_metrics_timings_info[type].metric_name)); json_object_set_new(o_timing, OIDC_METRICS_JSON_DESC, json_string(_oidc_metrics_timings_info[type].desc)); json_object_set_new(o_timings, _oidc_metrics_timing_type2s(r->pool, type), o_timing); i2 = json_object_iter_next(j_timings, i2); } i1 = json_object_iter_next(json, i1); } s_json = oidc_util_encode_json(r->pool, o_json, JSON_COMPACT | JSON_PRESERVE_ORDER); json_decref(o_json); json_decref(json); end: /* return the data to the caller */ return oidc_util_http_send(r, s_json, _oidc_strlen(s_json), OIDC_HTTP_CONTENT_TYPE_JSON, OK); } /* * dump the internal shared memory segment */ static int oidc_metrics_handle_internal(request_rec *r, char *s_json) { if (s_json == NULL) return HTTP_NOT_FOUND; return oidc_util_http_send(r, s_json, _oidc_strlen(s_json), OIDC_HTTP_CONTENT_TYPE_JSON, OK); } #define OIDC_METRICS_SERVER_PARAM "server_name" #define OIDC_METRICS_COUNTER_PARAM "counter" #define OIDC_METRICS_NAME_PARAM "name" #define OIDC_METRICS_VALUE_PARAM "value" /* * return status updates */ static int oidc_metrics_handle_status(request_rec *r, char *s_json) { char *msg = "OK\n"; char *s_metric_param = NULL, *s_server_param = NULL, *s_name_param = NULL, *s_value_param = NULL; json_t *json = NULL, *j_server = NULL, *j_counters = NULL, *j_counter = NULL, *j_values = NULL, *j_value = NULL; const char *s_key = NULL, *s_name = NULL; unsigned int type = 0; void *iter = NULL; oidc_util_request_parameter_get(r, OIDC_METRICS_SERVER_PARAM, &s_server_param); oidc_util_request_parameter_get(r, OIDC_METRICS_COUNTER_PARAM, &s_metric_param); oidc_util_request_parameter_get(r, OIDC_METRICS_NAME_PARAM, &s_name_param); oidc_util_request_parameter_get(r, OIDC_METRICS_VALUE_PARAM, &s_value_param); if (s_server_param == NULL) s_server_param = "localhost"; if (s_metric_param == NULL) goto end; json = oidc_metrics_json_parse_r(r, s_json); if (json == NULL) goto end; j_server = json_object_get(json, s_server_param); if (j_server == NULL) goto end; j_counters = json_object_get(j_server, OIDC_METRICS_COUNTERS); if (j_counters == NULL) goto end; iter = json_object_iter(j_counters); while (iter) { s_key = json_object_iter_key(iter); j_counter = json_object_iter_value(iter); type = _oidc_metrics_key2type(s_key); s_name = _oidc_metrics_counter_type2s(r->pool, type); if (_oidc_strcmp(s_name, s_metric_param) == 0) { if (json_is_integer(j_counter)) { j_value = j_counter; } else if (s_value_param != NULL) { if (s_name_param != NULL) { j_values = json_object_get(j_counter, s_name_param); if (j_values != NULL) j_value = json_object_get(j_values, s_value_param); } else { j_value = json_object_get(j_counter, s_value_param); } } if (j_value) msg = apr_psprintf(r->pool, "OK: %s\n", _json_int2str(r->pool, json_integer_value(j_value))); break; } iter = json_object_iter_next(j_counters, iter); } end: if (json) json_decref(json); return oidc_util_http_send(r, msg, _oidc_strlen(msg), "text/plain", OK); } /* * return the Prometheus label name for a bucket */ static const char *oidc_metrics_prometheus_bucket_label(const char *json_name) { const char *name = NULL; int i = 0; for (i = 0; i < OIDC_METRICS_BUCKET_NUM; i++) { if (_oidc_strcmp(_oidc_metric_buckets[i].name, json_name) == 0) { name = _oidc_metric_buckets[i].label; break; } } return name; } #define OIDC_METRICS_PROMETHEUS_PREFIX "oidc" /* * normalize a metric name to something that Prometheus accepts */ static const char *oidc_metric_prometheus_normalize_name(apr_pool_t *pool, const char *name) { char *label = apr_psprintf(pool, "%s", name); int i = 0; for (i = 0; i < _oidc_strlen(label); i++) if (apr_isalnum(label[i]) == 0) label[i] = '_'; return apr_psprintf(pool, "%s_%s", OIDC_METRICS_PROMETHEUS_PREFIX, label); } #define OIDC_METRICS_PROMETHEUS_CONTENT_TYPE "text/plain; version=0.0.4" #define OIDC_METRICS_PROMETHEUS_SERVER "server_name" #define OIDC_METRICS_PROMETHEUS_BUCKET "bucket" #define OIDC_METRICS_PROMETHEUS_VALUE "value" #define OIDC_METRICS_PROMETHEUS_NAME "name" // loop context for Prometheus output typedef struct oidc_metric_prometheus_callback_ctx_t { char *s_result; apr_pool_t *pool; } oidc_metric_prometheus_callback_ctx_t; /* * loop function for converting counter metrics to Prometheus output */ static int oidc_metrics_prometheus_counters(oidc_metric_prometheus_callback_ctx_t *ctx, const char *key, json_t *value) { const char *s_server = NULL, *s_key = NULL, *s_value = NULL, *s_start = NULL; json_t *j_counter = NULL, *j_value = NULL; json_t *o_counter = value; void *i1 = NULL, *i2 = NULL, *i3 = NULL; unsigned int type = _oidc_metrics_key2type(key); const char *s_label = oidc_metric_prometheus_normalize_name(ctx->pool, _oidc_metrics_counter_type2s(ctx->pool, type)); char *s_text = apr_psprintf(ctx->pool, "# HELP %s The number of %s.\n", s_label, _oidc_metrics_counters_info[type].desc); s_text = apr_psprintf(ctx->pool, "%s# TYPE %s counter\n", s_text, s_label); i1 = json_object_iter(o_counter); while (i1) { s_server = json_object_iter_key(i1); j_counter = json_object_iter_value(i1); s_start = apr_psprintf(ctx->pool, "%s{%s=\"%s\"", s_label, OIDC_METRICS_PROMETHEUS_SERVER, s_server); if (json_is_integer(j_counter)) { s_text = apr_psprintf(ctx->pool, "%s%s} %s\n", s_text, s_start, _json_int2str(ctx->pool, json_integer_value(j_counter))); } else { i2 = json_object_iter(j_counter); while (i2) { s_key = json_object_iter_key(i2); j_value = json_object_iter_value(i2); if (json_is_integer(j_value)) { s_text = apr_psprintf(ctx->pool, "%s%s,%s=\"%s\"} %s\n", s_text, s_start, OIDC_METRICS_PROMETHEUS_VALUE, s_key, _json_int2str(ctx->pool, json_integer_value(j_value))); } else { i3 = json_object_iter(j_value); while (i3) { s_value = json_object_iter_key(i3); s_text = apr_psprintf( ctx->pool, "%s%s,%s=\"%s\",%s=\"%s\"} %s\n", s_text, s_start, OIDC_METRICS_PROMETHEUS_NAME, s_key, OIDC_METRICS_PROMETHEUS_VALUE, s_value, _json_int2str(ctx->pool, json_integer_value(json_object_iter_value(i3)))); i3 = json_object_iter_next(j_value, i3); } } i2 = json_object_iter_next(j_counter, i2); } } i1 = json_object_iter_next(o_counter, i1); } ctx->s_result = apr_pstrcat(ctx->pool, ctx->s_result, s_text, "\n", NULL); json_decref(o_counter); return 1; } /* * loop function for converting timing metrics to Prometheus output */ static int oidc_metrics_prometheus_timings(oidc_metric_prometheus_callback_ctx_t *ctx, const char *key, json_t *value) { const char *s_server = NULL, *s_key = NULL, *s_bucket = NULL; json_t *j_timing = NULL, *j_member = NULL; json_t *o_timer = value; unsigned int type = _oidc_metrics_key2type(key); const char *s_label = oidc_metric_prometheus_normalize_name(ctx->pool, _oidc_metrics_timing_type2s(ctx->pool, type)); char *s_text = apr_psprintf(ctx->pool, "# HELP %s A histogram of %s.\n", s_label, _oidc_metrics_timings_info[type].desc); s_text = apr_psprintf(ctx->pool, "%s# TYPE %s histogram\n", s_text, s_label); void *iter1 = json_object_iter(o_timer); while (iter1) { s_server = json_object_iter_key(iter1); j_timing = json_object_iter_value(iter1); void *iter3 = json_object_iter(j_timing); while (iter3) { s_key = json_object_iter_key(iter3); j_member = json_object_iter_value(iter3); s_bucket = oidc_metrics_prometheus_bucket_label(s_key); if (s_bucket) s_text = apr_psprintf(ctx->pool, "%s%s_%s{%s,", s_text, s_label, OIDC_METRICS_PROMETHEUS_BUCKET, s_bucket); else s_text = apr_psprintf(ctx->pool, "%s%s_%s{", s_text, s_label, s_key); s_text = apr_psprintf(ctx->pool, "%s%s=\"%s\"} %s\n", s_text, OIDC_METRICS_PROMETHEUS_SERVER, s_server, _json_int2str(ctx->pool, json_integer_value(j_member))); iter3 = json_object_iter_next(j_timing, iter3); } iter1 = json_object_iter_next(o_timer, iter1); } ctx->s_result = apr_pstrcat(ctx->pool, ctx->s_result, s_text, "\n", NULL); json_decref(o_timer); return 1; } /* * take a list of metrics from a server indexed list and add it to a type indexed list */ static void oidc_metrics_prometheus_convert(apr_hash_t *hash, const char *server, json_t *list) { const char *type = NULL; json_t *src = NULL, *dst = NULL; void *iter = json_object_iter(list); while (iter) { type = json_object_iter_key(iter); src = json_object_iter_value(iter); dst = (json_t *)apr_hash_get(hash, type, APR_HASH_KEY_STRING); if (dst) { json_object_set(dst, server, src); } else { dst = json_object(); json_object_set(dst, server, src); apr_hash_set(hash, type, APR_HASH_KEY_STRING, dst); } iter = json_object_iter_next(list, iter); } } /* * generate output in Prometheus formatting */ static int oidc_metrics_handle_prometheus(request_rec *r, char *s_json) { json_t *json = NULL, *j_server = NULL; const char *s_server = NULL; apr_hash_t *t_counters = apr_hash_make(r->pool); apr_hash_t *t_timings = apr_hash_make(r->pool); apr_hash_index_t *hi = NULL; const char *name = NULL; void *value = NULL; oidc_metric_prometheus_callback_ctx_t ctx = {"", r->pool}; void *iter = NULL; /* parse the metrics string to JSON */ json = oidc_metrics_json_parse_r(r, s_json); if (json == NULL) return OK; iter = json_object_iter(json); while (iter) { s_server = json_object_iter_key(iter); j_server = json_object_iter_value(iter); oidc_metrics_prometheus_convert(t_counters, s_server, json_object_get(j_server, OIDC_METRICS_COUNTERS)); oidc_metrics_prometheus_convert(t_timings, s_server, json_object_get(j_server, OIDC_METRICS_TIMINGS)); iter = json_object_iter_next(json, iter); } for (hi = apr_hash_first(r->pool, t_counters); hi; hi = apr_hash_next(hi)) { apr_hash_this(hi, (const void **)&name, NULL, &value); oidc_metrics_prometheus_counters(&ctx, name, value); } for (hi = apr_hash_first(r->pool, t_timings); hi; hi = apr_hash_next(hi)) { apr_hash_this(hi, (const void **)&name, NULL, &value); oidc_metrics_prometheus_timings(&ctx, name, value); } json_decref(json); return oidc_util_http_send(r, ctx.s_result, _oidc_strlen(ctx.s_result), OIDC_METRICS_PROMETHEUS_CONTENT_TYPE, OK); } /* * definitions for handler callbacks */ typedef int (*oidc_metrics_handler_function_t)(request_rec *, char *); // holder for output function callback context typedef struct oidc_metrics_handler_t { const char *format; oidc_metrics_handler_function_t callback; int reset; } oidc_metrics_content_handler_t; // output handlers const oidc_metrics_content_handler_t _oidc_metrics_handlers[] = { // first is default {"prometheus", oidc_metrics_handle_prometheus, 0}, {"json", oidc_metrics_handle_json, 1}, {"internal", oidc_metrics_handle_internal, 0}, {"status", oidc_metrics_handle_status, 0}, }; #define OIDC_CONTENT_HANDLER_MAX sizeof(_oidc_metrics_handlers) / sizeof(oidc_metrics_content_handler_t) #define OIDC_METRICS_RESET_PARAM "reset" /* * see if we are going to reset the cache after this */ static int oidc_metric_reset(request_rec *r, int dvalue) { char *s_reset = NULL; char svalue[16]; int value = 0; oidc_util_request_parameter_get(r, OIDC_METRICS_RESET_PARAM, &s_reset); if (s_reset == NULL) return dvalue; sscanf(s_reset, "%s", svalue); if (_oidc_strnatcasecmp(svalue, "true") == 0) value = 1; else if (_oidc_strnatcasecmp(svalue, "false") == 0) value = 0; return value; } #define OIDC_METRICS_FORMAT_PARAM "format" /* * find the format handler */ const oidc_metrics_content_handler_t *oidc_metrics_find_handler(request_rec *r) { const oidc_metrics_content_handler_t *handler = NULL; char *s_format = NULL; int i = 0; /* get the specified format */ oidc_util_request_parameter_get(r, OIDC_METRICS_FORMAT_PARAM, &s_format); if (s_format == NULL) return &_oidc_metrics_handlers[0]; for (i = 0; i < OIDC_CONTENT_HANDLER_MAX; i++) { if (_oidc_strcmp(s_format, _oidc_metrics_handlers[i].format) == 0) { handler = &_oidc_metrics_handlers[i]; break; } } if (handler == NULL) oidc_warn(r, "could not find a metrics handler for format: %s", s_format); return handler; } /* * return the metrics to the caller and flush the storage */ int oidc_metrics_handle_request(request_rec *r) { char *s_json = NULL; const oidc_metrics_content_handler_t *handler = NULL; /* get the content handler for the format */ handler = oidc_metrics_find_handler(r); if (handler == NULL) return HTTP_NOT_FOUND; /* lock the global shared memory */ oidc_cache_mutex_lock(r->pool, r->server, _oidc_metrics_global_mutex); /* retrieve the JSON formatted metrics as a string */ s_json = _oidc_metrics_storage_get(r->server); /* now that the metrics have been consumed, clear the shared memory segment */ if (oidc_metric_reset(r, handler->reset)) // oidc_metrics_storage_set(r->server, NULL); oidc_metrics_storage_reset(r->server); /* unlock the global shared memory */ oidc_cache_mutex_unlock(r->pool, r->server, _oidc_metrics_global_mutex); /* handle the specified format */ return handler->callback(r, s_json); } mod_auth_openidc-2.4.16.10/src/metrics.h000066400000000000000000000225401476721736500177570ustar00rootroot00000000000000/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ /*************************************************************************** * Copyright (C) 2017-2025 ZmartZone Holding BV * All rights reserved. * * DISCLAIMER OF WARRANTIES: * * THE SOFTWARE PROVIDED HEREUNDER IS PROVIDED ON AN "AS IS" BASIS, WITHOUT * ANY WARRANTIES OR REPRESENTATIONS EXPRESS, IMPLIED OR STATUTORY; INCLUDING, * WITHOUT LIMITATION, WARRANTIES OF QUALITY, PERFORMANCE, NONINFRINGEMENT, * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. NOR ARE THERE ANY * WARRANTIES CREATED BY A COURSE OR DEALING, COURSE OF PERFORMANCE OR TRADE * USAGE. FURTHERMORE, THERE ARE NO WARRANTIES THAT THE SOFTWARE WILL MEET * YOUR NEEDS OR BE FREE FROM ERRORS, OR THAT THE OPERATION OF THE SOFTWARE * WILL BE UNINTERRUPTED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * @Author: Hans Zandbelt - hans.zandbelt@openidc.com */ #ifndef _MOD_AUTH_OPENIDC_METRICS_H_ #define _MOD_AUTH_OPENIDC_METRICS_H_ #include "const.h" // for the PACKAGE_* defines #include #include apr_byte_t oidc_metrics_is_valid_classname(apr_pool_t *pool, const char *name, char **valid_names); apr_byte_t oidc_metrics_post_config(server_rec *s); apr_status_t oidc_metrics_child_init(apr_pool_t *p, server_rec *s); apr_status_t oidc_metrics_cleanup(server_rec *s); int oidc_metrics_handle_request(request_rec *r); // NB: order must match what is defined in metrics.c in array _oidc_metrics_timings_info typedef enum { OM_MOD_AUTH_OPENIDC = 0, OM_AUTHN_REQUEST, OM_AUTHN_RESPONSE, OM_SESSION_VALID, OM_PROVIDER_METADATA, OM_PROVIDER_TOKEN, OM_PROVIDER_REFRESH, OM_PROVIDER_USERINFO, OM_CACHE_READ, OM_CACHE_WRITE, } oidc_metrics_timing_type_t; typedef struct oidc_metrics_timing_info_t { char *class_name; char *metric_name; char *desc; } oidc_metrics_timing_info_t; extern const oidc_metrics_timing_info_t _oidc_metrics_timings_info[]; void oidc_metrics_timing_add(request_rec *r, oidc_metrics_timing_type_t type, apr_time_t elapsed); #define OIDC_METRICS_TIMING_VAR apr_time_t _oidc_metrics_tstart = 0; #define OIDC_METRICS_TIMING_START(r, cfg) \ OIDC_METRICS_TIMING_VAR \ if (oidc_cfg_metrics_hook_data_get(cfg) != NULL) { \ _oidc_metrics_tstart = apr_time_now(); \ } #define OIDC_METRICS_TIMING_ADD(r, cfg, type) \ if (oidc_cfg_metrics_hook_data_get(cfg) != NULL) { \ if (apr_hash_get(oidc_cfg_metrics_hook_data_get(cfg), _oidc_metrics_timings_info[type].class_name, \ APR_HASH_KEY_STRING) != NULL) { \ oidc_metrics_timing_add(r, type, apr_time_now() - _oidc_metrics_tstart); \ } \ } #define OIDC_METRICS_REQUEST_STATE_TIMER_KEY "oidc-metrics-request-timer" #define OIDC_METRICS_TIMING_REQUEST_START(r, cfg) \ if (oidc_cfg_metrics_hook_data_get(cfg) != NULL) { \ oidc_request_state_set(r, OIDC_METRICS_REQUEST_STATE_TIMER_KEY, \ apr_psprintf(r->pool, "%" APR_TIME_T_FMT, apr_time_now())); \ } #define OIDC_METRICS_TIMING_REQUEST_ADD(r, cfg, type) \ OIDC_METRICS_TIMING_VAR \ if (oidc_cfg_metrics_hook_data_get(cfg) != NULL) { \ _oidc_metrics_tstart = \ _oidc_str_to_time(oidc_request_state_get(r, OIDC_METRICS_REQUEST_STATE_TIMER_KEY), -1); \ if (_oidc_metrics_tstart > -1) { \ OIDC_METRICS_TIMING_ADD(r, cfg, type); \ } else { \ oidc_warn(r, \ "metrics: could not add timing because start timer was not found in request state"); \ } \ } // NB: order must match what is defined in metrics.c in array _oidc_metrics_counters_info typedef enum { OM_AUTHTYPE_MOD_AUTH_OPENIDC = 0, OM_AUTHTYPE_OPENID_CONNECT, OM_AUTHTYPE_OAUTH20, OM_AUTHTYPE_AUTH_OPENIDC, OM_AUTHTYPE_DECLINED, OM_AUTHN_REQUEST_ERROR_URL, OM_AUTHN_RESPONSE_ERROR_STATE_MISMATCH, OM_AUTHN_RESPONSE_ERROR_STATE_EXPIRED, OM_AUTHN_RESPONSE_ERROR_PROVIDER, OM_AUTHN_RESPONSE_ERROR_PROTOCOL, OM_AUTHN_RESPONSE_ERROR_REMOTE_USER, OM_AUTHZ_ACTION_AUTH, OM_AUTHZ_ACTION_401, OM_AUTHZ_ACTION_403, OM_AUTHZ_ACTION_302, OM_AUTHZ_ERROR_OAUTH20, OM_AUTHZ_MATCH_REQUIRE_CLAIM, OM_AUTHZ_ERROR_REQUIRE_CLAIM, OM_CLAIM_ID_TOKEN, OM_CLAIM_USER_INFO, OM_PROVIDER_METADATA_ERROR, OM_PROVIDER_TOKEN_ERROR, OM_PROVIDER_REFRESH_ERROR, OM_PROVIDER_USERINFO_ERROR, OM_PROVIDER_CONNECT_ERROR, OM_PROVIDER_HTTP_RESPONSE_CODE, OM_SESSION_ERROR_COOKIE_DOMAIN, OM_SESSION_ERROR_EXPIRED, OM_SESSION_ERROR_REFRESH_ACCESS_TOKEN, OM_SESSION_ERROR_REFRESH_USERINFO, OM_SESSION_ERROR_GENERAL, OM_CACHE_ERROR, OM_REDIRECT_URI_AUTHN_RESPONSE_REDIRECT, OM_REDIRECT_URI_AUTHN_RESPONSE_POST, OM_REDIRECT_URI_AUTHN_RESPONSE_IMPLICIT, OM_REDIRECT_URI_DISCOVERY_RESPONSE, OM_REDIRECT_URI_REQUEST_LOGOUT, OM_REDIRECT_URI_REQUEST_JWKS, OM_REDIRECT_URI_REQUEST_SESSION, OM_REDIRECT_URI_REQUEST_REFRESH, OM_REDIRECT_URI_REQUEST_REQUEST_URI, OM_REDIRECT_URI_REQUEST_REMOVE_AT_CACHE, OM_REDIRECT_URI_REQUEST_REVOKE_SESSION, OM_REDIRECT_URI_REQUEST_INFO, OM_REDIRECT_URI_REQUEST_DPOP, OM_REDIRECT_URI_ERROR_PROVIDER, OM_REDIRECT_URI_ERROR_INVALID, OM_CONTENT_REQUEST_DECLINED, OM_CONTENT_REQUEST_INFO, OM_CONTENT_REQUEST_DPOP, OM_CONTENT_REQUEST_JWKS, OM_CONTENT_REQUEST_DISCOVERY, OM_CONTENT_REQUEST_POST_PRESERVE, OM_CONTENT_REQUEST_UNKNOWN, } oidc_metrics_counter_type_t; typedef struct oidc_metrics_counter_info_t { char *class_name; char *metric_name; char *desc; } oidc_metrics_counter_info_t; extern const oidc_metrics_counter_info_t _oidc_metrics_counters_info[]; void oidc_metrics_counter_inc(request_rec *r, oidc_metrics_counter_type_t type, const char *name, const char *value); // TODO: name is a bit overloaded here, since when not NULL, it will also result in including the metric_name static inline const char *_oidc_metrics_type_name2s(apr_pool_t *pool, unsigned int type, const char *name) { return apr_psprintf(pool, "%s%s%s%s%s", _oidc_metrics_counters_info[type].class_name, name ? "." : "", name ? _oidc_metrics_counters_info[type].metric_name : "", name ? "." : "", name ? name : ""); } #define OIDC_METRICS_COUNTER_INC_NAME_VALUE(r, cfg, type, name, value) \ if (oidc_cfg_metrics_hook_data_get(cfg) != NULL) { \ if (apr_hash_get(oidc_cfg_metrics_hook_data_get(cfg), _oidc_metrics_type_name2s(r->pool, type, name), \ APR_HASH_KEY_STRING) != NULL) { \ oidc_metrics_counter_inc(r, type, name, value); \ } \ } #define OIDC_METRICS_COUNTER_INC_VALUE(r, cfg, type, value) \ OIDC_METRICS_COUNTER_INC_NAME_VALUE(r, cfg, type, NULL, value) #define OIDC_METRICS_COUNTER_INC(r, cfg, type) OIDC_METRICS_COUNTER_INC_NAME_VALUE(r, cfg, type, NULL, NULL) #endif /* _MOD_AUTH_OPENIDC_METRICS_H_ */ mod_auth_openidc-2.4.16.10/src/mod_auth_openidc.c000066400000000000000000002017641476721736500216140ustar00rootroot00000000000000/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ /*************************************************************************** * Copyright (C) 2017-2025 ZmartZone Holding BV * Copyright (C) 2013-2017 Ping Identity Corporation * All rights reserved. * * DISCLAIMER OF WARRANTIES: * * THE SOFTWARE PROVIDED HEREUNDER IS PROVIDED ON AN "AS IS" BASIS, WITHOUT * ANY WARRANTIES OR REPRESENTATIONS EXPRESS, IMPLIED OR STATUTORY; INCLUDING, * WITHOUT LIMITATION, WARRANTIES OF QUALITY, PERFORMANCE, NONINFRINGEMENT, * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. NOR ARE THERE ANY * WARRANTIES CREATED BY A COURSE OR DEALING, COURSE OF PERFORMANCE OR TRADE * USAGE. FURTHERMORE, THERE ARE NO WARRANTIES THAT THE SOFTWARE WILL MEET * YOUR NEEDS OR BE FREE FROM ERRORS, OR THAT THE OPERATION OF THE SOFTWARE * WILL BE UNINTERRUPTED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * Initially based on mod_auth_cas.c: * https://github.com/Jasig/mod_auth_cas * * Other code copied/borrowed/adapted: * shared memory caching: mod_auth_mellon * * @Author: Hans Zandbelt - hans.zandbelt@openidc.com * **************************************************************************/ #include "mod_auth_openidc.h" #include "cfg/cache.h" #include "cfg/dir.h" #include "cfg/oauth.h" #include "handle/handle.h" #include "metadata.h" #include "metrics.h" #include "oauth.h" #include "proto/proto.h" #include "util.h" #define OPENSSL_THREAD_DEFINES #include #include #include #include #if (OPENSSL_VERSION_NUMBER < 0x01000000) #define OPENSSL_NO_THREADID #endif #include /* * clean any suspicious headers in the HTTP request sent by the user agent */ static void oidc_scrub_request_headers(request_rec *r, const char *claim_prefix, apr_hash_t *scrub) { const int prefix_len = claim_prefix ? _oidc_strlen(claim_prefix) : 0; /* get an array representation of the incoming HTTP headers */ const apr_array_header_t *const h = apr_table_elts(r->headers_in); /* table to keep the non-suspicious headers */ apr_table_t *clean_headers = apr_table_make(r->pool, h->nelts); /* loop over the incoming HTTP headers */ const apr_table_entry_t *const e = (const apr_table_entry_t *)h->elts; int i; for (i = 0; i < h->nelts; i++) { const char *const k = e[i].key; /* is this header's name equivalent to a header that needs scrubbing? */ const char *hdr = (k != NULL) && (scrub != NULL) ? apr_hash_get(scrub, k, APR_HASH_KEY_STRING) : NULL; const int header_matches = (hdr != NULL) && (oidc_util_strnenvcmp(k, hdr, -1) == 0); /* * would this header be interpreted as a mod_auth_openidc attribute? Note * that prefix_len will be zero if no attr_prefix is defined, * so this will always be false. Also note that we do not * scrub headers if the prefix is empty because every header * would match. */ const int prefix_matches = (k != NULL) && prefix_len && (oidc_util_strnenvcmp(k, claim_prefix, prefix_len) == 0); /* add to the clean_headers if non-suspicious, skip and report otherwise */ if (!prefix_matches && !header_matches) { apr_table_addn(clean_headers, k, e[i].val); } else { oidc_warn(r, "scrubbed suspicious request header (%s: %.32s)", k, e[i].val); } } /* overwrite the incoming headers with the cleaned result */ r->headers_in = clean_headers; } /* * scrub all mod_auth_openidc related headers */ void oidc_scrub_headers(request_rec *r) { oidc_cfg_t *cfg = ap_get_module_config(r->server->module_config, &auth_openidc_module); const char *prefix = oidc_cfg_claim_prefix_get(cfg); apr_hash_t *hdrs = apr_hash_make(r->pool); if (_oidc_strcmp(prefix, "") == 0) { if ((oidc_cfg_white_listed_claims_get(cfg) != NULL) && (apr_hash_count(oidc_cfg_white_listed_claims_get(cfg)) > 0)) hdrs = apr_hash_overlay(r->pool, oidc_cfg_white_listed_claims_get(cfg), hdrs); else oidc_warn(r, "both " OIDCClaimPrefix " and " OIDCWhiteListedClaims " are empty: this renders an insecure setup!"); } const char *authn_hdr = oidc_cfg_dir_authn_header_get(r); if (authn_hdr != NULL) apr_hash_set(hdrs, authn_hdr, APR_HASH_KEY_STRING, authn_hdr); /* * scrub all headers starting with OIDC_ first */ oidc_scrub_request_headers(r, OIDC_DEFAULT_HEADER_PREFIX, hdrs); /* * then see if the claim headers need to be removed on top of that * (i.e. the prefix does not start with the default OIDC_) */ if ((_oidc_strstr(prefix, OIDC_DEFAULT_HEADER_PREFIX) != prefix)) { oidc_scrub_request_headers(r, prefix, NULL); } } /* * strip the session cookie from the headers sent to the application/backend */ void oidc_strip_cookies(request_rec *r) { char *cookie, *ctx, *result = NULL; const char *name = NULL; int i; const apr_array_header_t *strip = oidc_cfg_dir_strip_cookies_get(r); char *cookies = apr_pstrdup(r->pool, oidc_http_hdr_in_cookie_get(r)); if ((cookies != NULL) && (strip != NULL)) { oidc_debug(r, "looking for the following cookies to strip from cookie header: %s", apr_array_pstrcat(r->pool, strip, OIDC_CHAR_COMMA)); cookie = apr_strtok(cookies, OIDC_STR_SEMI_COLON, &ctx); do { while (cookie != NULL && *cookie == OIDC_CHAR_SPACE) cookie++; if (cookie == NULL) break; for (i = 0; i < strip->nelts; i++) { name = APR_ARRAY_IDX(strip, i, const char *); if ((_oidc_strncmp(cookie, name, _oidc_strlen(name)) == 0) && (cookie[_oidc_strlen(name)] == OIDC_CHAR_EQUAL)) { oidc_debug(r, "stripping: %s", name); break; } } if (i == strip->nelts) { result = result ? apr_psprintf(r->pool, "%s%s %s", result, OIDC_STR_SEMI_COLON, cookie) : cookie; } cookie = apr_strtok(NULL, OIDC_STR_SEMI_COLON, &ctx); } while (cookie != NULL); oidc_http_hdr_in_cookie_set(r, result); } } /* * check if s_json is valid provider metadata */ static apr_byte_t oidc_provider_validate_metadata_str(request_rec *r, oidc_cfg_t *c, const char *s_json, json_t **j_provider, apr_byte_t decode_only) { if (oidc_util_decode_json_object(r, s_json, j_provider) == FALSE) return FALSE; if (decode_only == TRUE) return TRUE; /* check to see if it is valid metadata */ if (oidc_metadata_provider_is_valid(r, c, *j_provider, NULL) == FALSE) { oidc_warn(r, "cache corruption detected: invalid metadata from url: %s", oidc_cfg_provider_metadata_url_get(oidc_cfg_provider_get(c))); json_decref(*j_provider); return FALSE; } return TRUE; } /* * return the static provider configuration, i.e. from a metadata URL or configuration primitives */ apr_byte_t oidc_provider_static_config(request_rec *r, oidc_cfg_t *c, oidc_provider_t **provider) { json_t *j_provider = NULL; char *s_json = NULL; /* see if we should configure a static provider based on external (cached) metadata */ if ((oidc_cfg_metadata_dir_get(c) != NULL) || (oidc_cfg_provider_metadata_url_get(oidc_cfg_provider_get(c)) == NULL)) { *provider = oidc_cfg_provider_get(c); return TRUE; } oidc_cache_get_provider(r, oidc_cfg_provider_metadata_url_get(oidc_cfg_provider_get(c)), &s_json); if (s_json != NULL) oidc_provider_validate_metadata_str(r, c, s_json, &j_provider, TRUE); if (j_provider == NULL) { if (oidc_metadata_provider_retrieve(r, c, NULL, oidc_cfg_provider_metadata_url_get(oidc_cfg_provider_get(c)), &j_provider, &s_json) == FALSE) { oidc_error(r, "could not retrieve metadata from url: %s", oidc_cfg_provider_metadata_url_get(oidc_cfg_provider_get(c))); return FALSE; } json_decref(j_provider); if (oidc_provider_validate_metadata_str(r, c, s_json, &j_provider, FALSE) == FALSE) return FALSE; oidc_cache_set_provider( r, oidc_cfg_provider_metadata_url_get(oidc_cfg_provider_get(c)), s_json, apr_time_now() + apr_time_from_sec(oidc_cfg_provider_metadata_refresh_interval_get(c) <= 0 ? OIDC_CACHE_PROVIDER_METADATA_EXPIRY_DEFAULT : oidc_cfg_provider_metadata_refresh_interval_get(c))); } *provider = oidc_cfg_provider_copy(r->pool, oidc_cfg_provider_get(c)); if (oidc_metadata_provider_parse(r, c, j_provider, *provider) == FALSE) { oidc_error(r, "could not parse metadata from url: %s", oidc_cfg_provider_metadata_url_get(oidc_cfg_provider_get(c))); json_decref(j_provider); return FALSE; } json_decref(j_provider); return TRUE; } /* * return the oidc_provider_t struct for the specified issuer */ oidc_provider_t *oidc_get_provider_for_issuer(request_rec *r, oidc_cfg_t *c, const char *issuer, apr_byte_t allow_discovery) { /* by default we'll assume that we're dealing with a single statically configured OP */ oidc_provider_t *provider = NULL; if (oidc_provider_static_config(r, c, &provider) == FALSE) return NULL; /* unless a metadata directory was configured, so we'll try and get the provider settings from there */ if (oidc_cfg_metadata_dir_get(c) != NULL) { /* try and get metadata from the metadata directory for the OP that sent this response */ if ((oidc_metadata_get(r, c, issuer, &provider, allow_discovery) == FALSE) || (provider == NULL)) { /* don't know nothing about this OP/issuer */ oidc_error(r, "no provider metadata found for issuer \"%s\"", issuer); return NULL; } } return provider; } /* * return the HTTP method being called: only for POST data persistence purposes */ const char *oidc_original_request_method(request_rec *r, oidc_cfg_t *cfg, apr_byte_t handle_discovery_response) { const char *method = OIDC_METHOD_GET; char *m = NULL; if ((handle_discovery_response == TRUE) && (oidc_util_request_matches_url(r, oidc_util_redirect_uri(r, cfg))) && (oidc_is_discovery_response(r, cfg))) { oidc_util_request_parameter_get(r, OIDC_DISC_RM_PARAM, &m); if (m != NULL) method = apr_pstrdup(r->pool, m); } else { /* * if POST preserve is not enabled for this location, there's no point in preserving * the method either which would result in POSTing empty data on return; * so we revert to legacy behavior */ if (oidc_cfg_dir_preserve_post_get(r) == 0) return OIDC_METHOD_GET; const char *content_type = oidc_http_hdr_in_content_type_get(r); if ((r->method_number == M_POST) && (_oidc_strcmp(content_type, OIDC_HTTP_CONTENT_TYPE_FORM_ENCODED) == 0)) method = OIDC_METHOD_FORM_POST; } oidc_debug(r, "return: %s", method); return method; } /* * get the mod_auth_openidc related context from the (userdata in the) request * (used for passing state between various Apache request processing stages and hook callbacks) */ static apr_table_t *oidc_request_state(request_rec *rr) { /* our state is always stored in the main request */ request_rec *r = (rr->main != NULL) ? rr->main : rr; /* our state is a table, get it */ apr_table_t *state = NULL; apr_pool_userdata_get((void **)&state, OIDC_USERDATA_KEY, r->pool); /* if it does not exist, we'll create a new table */ if (state == NULL) { state = apr_table_make(r->pool, 5); apr_pool_userdata_set(state, OIDC_USERDATA_KEY, NULL, r->pool); } /* return the resulting table, always non-null now */ return state; } /* * set a name/value pair in the mod_auth_openidc-specific request context * (used for passing state between various Apache request processing stages and hook callbacks) */ void oidc_request_state_set(request_rec *r, const char *key, const char *value) { /* get a handle to the global state, which is a table */ apr_table_t *state = oidc_request_state(r); /* put the name/value pair in that table */ apr_table_set(state, key, value); } /* * get a name/value pair from the mod_auth_openidc-specific request context * (used for passing state between various Apache request processing stages and hook callbacks) */ const char *oidc_request_state_get(request_rec *r, const char *key) { /* get a handle to the global state, which is a table */ apr_table_t *state = oidc_request_state(r); /* return the value from the table */ return apr_table_get(state, key); } /* * set the claims from a JSON object (c.q. id_token or user_info response) stored * in the session in to HTTP headers passed on to the application */ apr_byte_t oidc_set_app_claims(request_rec *r, oidc_cfg_t *cfg, const char *s_claims) { json_t *j_claims = NULL; oidc_appinfo_pass_in_t pass_in = oidc_cfg_dir_pass_info_in_get(r); // optimize performance when `OIDCPassClaimsAs none` is set if (pass_in == OIDC_APPINFO_PASS_NONE) return TRUE; /* decode the string-encoded attributes in to a JSON structure */ if (s_claims != NULL) { if (oidc_util_decode_json_object(r, s_claims, &j_claims) == FALSE) return FALSE; } /* set the resolved claims a HTTP headers for the application */ if (j_claims != NULL) { oidc_util_set_app_infos(r, j_claims, oidc_cfg_claim_prefix_get(cfg), oidc_cfg_claim_delimiter_get(cfg), pass_in, oidc_cfg_dir_pass_info_encoding_get(r)); /* release resources */ json_decref(j_claims); } return TRUE; } /* * log message about max session duration */ void oidc_log_session_expires(request_rec *r, const char *msg, apr_time_t session_expires) { char buf[APR_RFC822_DATE_LEN + 1]; apr_rfc822_date(buf, session_expires); oidc_debug(r, "%s: %s (in %" APR_TIME_T_FMT " secs from now)", msg, buf, apr_time_sec(session_expires - apr_time_now())); } /* * see if this is a request that is capable of completing an authentication round trip to the Provider */ apr_byte_t oidc_is_auth_capable_request(request_rec *r) { if ((oidc_http_hdr_in_x_requested_with_get(r) != NULL) && (_oidc_strnatcasecmp(oidc_http_hdr_in_x_requested_with_get(r), OIDC_HTTP_HDR_VAL_XML_HTTP_REQUEST) == 0)) return FALSE; if ((oidc_http_hdr_in_sec_fetch_mode_get(r) != NULL) && (_oidc_strnatcasecmp(oidc_http_hdr_in_sec_fetch_mode_get(r), OIDC_HTTP_HDR_VAL_NAVIGATE) != 0)) return FALSE; if ((oidc_http_hdr_in_sec_fetch_dest_get(r) != NULL) && (_oidc_strnatcasecmp(oidc_http_hdr_in_sec_fetch_dest_get(r), OIDC_HTTP_HDR_VAL_DOCUMENT) != 0)) return FALSE; if ((oidc_http_hdr_in_accept_contains(r, OIDC_HTTP_CONTENT_TYPE_TEXT_HTML) == FALSE) && (oidc_http_hdr_in_accept_contains(r, OIDC_HTTP_CONTENT_TYPE_APP_XHTML_XML) == FALSE) && (oidc_http_hdr_in_accept_contains(r, OIDC_HTTP_CONTENT_TYPE_ANY) == FALSE)) return FALSE; return TRUE; } /* * find out which action we need to take when encountering an unauthenticated request */ static int oidc_handle_unauthenticated_user(request_rec *r, oidc_cfg_t *c) { /* see if we've configured OIDCUnAuthAction for this path */ switch (oidc_cfg_dir_unauth_action_get(r)) { case OIDC_UNAUTH_RETURN410: return HTTP_GONE; case OIDC_UNAUTH_RETURN407: return HTTP_PROXY_AUTHENTICATION_REQUIRED; case OIDC_UNAUTH_RETURN401: return HTTP_UNAUTHORIZED; case OIDC_UNAUTH_PASS: r->user = ""; /* * we're not going to pass information about an authenticated user to the application, * but we do need to scrub the headers that mod_auth_openidc would set for security reasons */ oidc_scrub_headers(r); return OK; case OIDC_UNAUTH_AUTHENTICATE: /* * exception handling: if this looks like a XMLHttpRequest call we * won't redirect the user and thus avoid creating a state cookie * for a non-browser (= Javascript) call that will never return from the OP */ if ((oidc_cfg_dir_unauth_expr_is_set(r) == FALSE) && (oidc_is_auth_capable_request(r) == FALSE)) return HTTP_UNAUTHORIZED; } /* * else: no session (regardless of whether it is main or sub-request), * and we need to authenticate the user */ return oidc_request_authenticate_user(r, c, NULL, oidc_util_current_url(r, oidc_cfg_x_forwarded_headers_get(c)), NULL, NULL, NULL, oidc_cfg_dir_path_auth_request_params_get(r), oidc_cfg_dir_path_scope_get(r)); } /* * check if maximum session duration was exceeded */ static apr_byte_t oidc_check_max_session_duration(request_rec *r, oidc_cfg_t *cfg, oidc_session_t *session, int *rc) { /* get the session expiry from the session data */ apr_time_t session_expires = oidc_session_get_session_expires(r, session); /* check the expire timestamp against the current time */ if (apr_time_now() > session_expires) { oidc_warn(r, "maximum session duration exceeded for user: %s", session->remote_user); oidc_session_kill(r, session); *rc = oidc_handle_unauthenticated_user(r, cfg); return FALSE; } /* log message about max session duration */ oidc_log_session_expires(r, "session max lifetime", session_expires); *rc = OK; return TRUE; } /* * validate received session cookie against the domain it was issued for: * * this handles the case where the cache configured is a the same single memcache, Redis, or file * backend for different (virtual) hosts, or a client-side cookie protected with the same secret * * it also handles the case that a cookie is unexpectedly shared across multiple hosts in * name-based virtual hosting even though the OP(s) would be the same */ apr_byte_t oidc_check_cookie_domain(request_rec *r, oidc_cfg_t *cfg, oidc_session_t *session) { const char *c_cookie_domain = oidc_cfg_cookie_domain_get(cfg) ? oidc_cfg_cookie_domain_get(cfg) : oidc_util_current_url_host(r, oidc_cfg_x_forwarded_headers_get(cfg)); const char *s_cookie_domain = oidc_session_get_cookie_domain(r, session); if ((s_cookie_domain == NULL) || (_oidc_strnatcasecmp(c_cookie_domain, s_cookie_domain) != 0)) { oidc_warn(r, "aborting: detected attempt to play cookie against a different domain/host than issued for! " "(issued=%s, current=%s)", s_cookie_domain, c_cookie_domain); return FALSE; } return TRUE; } /* * get a handle to the provider configuration via the "issuer" stored in the session */ apr_byte_t oidc_get_provider_from_session(request_rec *r, oidc_cfg_t *c, oidc_session_t *session, oidc_provider_t **provider) { oidc_debug(r, "enter"); /* get the issuer value from the session state */ const char *issuer = oidc_session_get_issuer(r, session); if (issuer == NULL) { oidc_warn(r, "empty or invalid session: no issuer found"); return FALSE; } /* get the provider info associated with the issuer value */ oidc_provider_t *p = oidc_get_provider_for_issuer(r, c, issuer, FALSE); if (p == NULL) { oidc_error(r, "session corrupted: no provider found for issuer: %s", issuer); return FALSE; } *provider = p; return TRUE; } /* * copy the claims and id_token from the session to the request state and optionally return them */ static void oidc_copy_tokens_to_request_state(request_rec *r, oidc_session_t *session, const char **s_id_token, const char **s_claims) { const char *id_token = oidc_session_get_idtoken_claims(r, session); const char *claims = oidc_session_get_userinfo_claims(r, session); oidc_debug(r, "id_token=%s claims=%s", id_token, claims); if (id_token != NULL) { oidc_request_state_set(r, OIDC_REQUEST_STATE_KEY_IDTOKEN, id_token); if (s_id_token != NULL) *s_id_token = id_token; } if (claims != NULL) { oidc_request_state_set(r, OIDC_REQUEST_STATE_KEY_CLAIMS, claims); if (s_claims != NULL) *s_claims = claims; } } /* * pass refresh_token, access_token and access_token_expires as headers/environment variables to the application */ apr_byte_t oidc_session_pass_tokens(request_rec *r, oidc_cfg_t *cfg, oidc_session_t *session, apr_byte_t extend_session, apr_byte_t *needs_save) { oidc_appinfo_pass_in_t pass_in = oidc_cfg_dir_pass_info_in_get(r); oidc_appinfo_encoding_t encoding = oidc_cfg_dir_pass_info_encoding_get(r); /* set the refresh_token in the app headers/variables, if enabled for this location/directory */ const char *refresh_token = oidc_session_get_refresh_token(r, session); if ((oidc_cfg_dir_pass_refresh_token_get(r) != 0) && (refresh_token != NULL)) { /* pass it to the app in a header or environment variable */ oidc_util_set_app_info(r, OIDC_APP_INFO_REFRESH_TOKEN, refresh_token, OIDC_DEFAULT_HEADER_PREFIX, pass_in, encoding); } /* set the access_token in the app headers/variables */ const char *access_token = oidc_session_get_access_token(r, session); if ((oidc_cfg_dir_pass_access_token_get(r) != 0) && access_token != NULL) { /* pass it to the app in a header or environment variable */ oidc_util_set_app_info(r, OIDC_APP_INFO_ACCESS_TOKEN, access_token, OIDC_DEFAULT_HEADER_PREFIX, pass_in, encoding); } /* set the access_token type in the app headers/variables */ const char *access_token_type = oidc_session_get_access_token_type(r, session); if ((oidc_cfg_dir_pass_access_token_get(r) != 0) && access_token_type != NULL) { /* pass it to the app in a header or environment variable */ oidc_util_set_app_info(r, OIDC_APP_INFO_ACCESS_TOKEN_TYPE, access_token_type, OIDC_DEFAULT_HEADER_PREFIX, pass_in, encoding); } /* set the expiry timestamp in the app headers/variables */ const char *access_token_expires = oidc_session_get_access_token_expires2str(r, session); if ((oidc_cfg_dir_pass_access_token_get(r) != 0) && access_token_expires != NULL) { /* pass it to the app in a header or environment variable */ oidc_util_set_app_info(r, OIDC_APP_INFO_ACCESS_TOKEN_EXP, access_token_expires, OIDC_DEFAULT_HEADER_PREFIX, pass_in, encoding); } if (extend_session) { /* * reset the session inactivity timer * but only do this once per 10% of the inactivity timeout interval (with a max to 60 seconds) * for performance reasons * * now there's a small chance that the session ends 10% (or a minute) earlier than configured/expected * cq. when there's a request after a recent save (so no update) and then no activity happens until * a request comes in just before the session should expire * ("recent" and "just before" refer to 10%-with-a-max-of-60-seconds of the inactivity interval after * the start/last-update and before the expiry of the session respectively) * * this is be deemed acceptable here because of performance gain */ apr_time_t interval = apr_time_from_sec(oidc_cfg_session_inactivity_timeout_get(cfg)); apr_time_t now = apr_time_now(); apr_time_t slack = interval / 10; if (slack > apr_time_from_sec(60)) slack = apr_time_from_sec(60); if (session->expiry - now < interval - slack) { session->expiry = now + interval; *needs_save = TRUE; } } // if this is a newly created session, we'll write it again to update the samesite setting on the session cookie if (oidc_session_get_session_new(r, session)) { *needs_save = TRUE; oidc_session_set_session_new(r, session, 0); } /* log message about session expiry */ oidc_log_session_expires(r, "session inactivity timeout", session->expiry); return TRUE; } /* * handle the case where we have identified an existing authentication session for a user */ static int oidc_handle_existing_session(request_rec *r, oidc_cfg_t *cfg, oidc_session_t *session, apr_byte_t extend_session, apr_byte_t *needs_save) { apr_byte_t rv = FALSE; int rc = OK; const char *s_claims = NULL; const char *s_id_token = NULL; oidc_debug(r, "enter"); /* set the user in the main request for further (incl. sub-request) processing */ r->user = apr_pstrdup(r->pool, session->remote_user); oidc_debug(r, "set remote_user to \"%s\" in existing session \"%s\"", r->user, session->uuid); /* get the header name in which the remote user name needs to be passed */ const char *authn_header = oidc_cfg_dir_authn_header_get(r); oidc_appinfo_pass_in_t pass_in = oidc_cfg_dir_pass_info_in_get(r); oidc_appinfo_encoding_t encoding = oidc_cfg_dir_pass_info_encoding_get(r); /* verify current cookie domain against issued cookie domain */ if (oidc_check_cookie_domain(r, cfg, session) == FALSE) { *needs_save = FALSE; OIDC_METRICS_COUNTER_INC(r, cfg, OM_SESSION_ERROR_COOKIE_DOMAIN); return HTTP_UNAUTHORIZED; } /* * we're going to pass the information that we have to the application, * but first we need to scrub the headers that we're going to use for security reasons * NB: need it before oidc_check_max_session_duration since OIDCUnAuthAction pass may be set */ oidc_scrub_headers(r); /* check if the maximum session duration was exceeded */ if (oidc_check_max_session_duration(r, cfg, session, &rc) == FALSE) { *needs_save = FALSE; OIDC_METRICS_COUNTER_INC(r, cfg, OM_SESSION_ERROR_EXPIRED); // NB: rc was set (e.g. to a 302 auth redirect) by the call to oidc_check_max_session_duration return rc; } if (extend_session) { /* if needed, refresh the access token */ rv = oidc_refresh_access_token_before_expiry( r, cfg, session, oidc_cfg_dir_refresh_access_token_before_expiry_get(r), needs_save); if (rv == FALSE) { *needs_save = FALSE; oidc_debug(r, "dir_action_on_error_refresh: %d", oidc_cfg_dir_action_on_error_refresh_get(r)); OIDC_METRICS_COUNTER_INC(r, cfg, OM_SESSION_ERROR_REFRESH_ACCESS_TOKEN); if (oidc_cfg_dir_action_on_error_refresh_get(r) == OIDC_ON_ERROR_LOGOUT) { return oidc_logout_request( r, cfg, session, oidc_util_absolute_url(r, cfg, oidc_cfg_default_slo_url_get(cfg)), FALSE); } if (oidc_cfg_dir_action_on_error_refresh_get(r) == OIDC_ON_ERROR_AUTH) { oidc_session_kill(r, session); return oidc_handle_unauthenticated_user(r, cfg); } return HTTP_BAD_GATEWAY; } /* if needed, refresh claims from the user info endpoint */ rv = oidc_userinfo_refresh_claims(r, cfg, session, needs_save); if (rv == FALSE) { *needs_save = FALSE; oidc_debug(r, "action_on_userinfo_error: %d", oidc_cfg_action_on_userinfo_error_get(cfg)); OIDC_METRICS_COUNTER_INC(r, cfg, OM_SESSION_ERROR_REFRESH_USERINFO); if (oidc_cfg_action_on_userinfo_error_get(cfg) == OIDC_ON_ERROR_LOGOUT) { return oidc_logout_request( r, cfg, session, oidc_util_absolute_url(r, cfg, oidc_cfg_default_slo_url_get(cfg)), FALSE); } if (oidc_cfg_action_on_userinfo_error_get(cfg) == OIDC_ON_ERROR_AUTH) { oidc_session_kill(r, session); return oidc_handle_unauthenticated_user(r, cfg); } return HTTP_BAD_GATEWAY; } } /* set the user authentication HTTP header if set and required */ if ((r->user != NULL) && (authn_header != NULL)) oidc_http_hdr_in_set(r, authn_header, r->user); /* copy id_token and claims from session to request state and obtain their values */ oidc_copy_tokens_to_request_state(r, session, &s_id_token, &s_claims); if ((oidc_cfg_dir_pass_idtoken_as_get(r) & OIDC_PASS_IDTOKEN_AS_CLAIMS)) { /* set the id_token in the app headers */ if (oidc_set_app_claims(r, cfg, s_id_token) == FALSE) return HTTP_INTERNAL_SERVER_ERROR; } if ((oidc_cfg_dir_pass_idtoken_as_get(r) & OIDC_PASS_IDTOKEN_AS_PAYLOAD)) { /* pass the id_token JSON object to the app in a header or environment variable */ oidc_util_set_app_info(r, OIDC_APP_INFO_ID_TOKEN_PAYLOAD, s_id_token, OIDC_DEFAULT_HEADER_PREFIX, pass_in, encoding); } if ((oidc_cfg_dir_pass_idtoken_as_get(r) & OIDC_PASS_IDTOKEN_AS_SERIALIZED)) { /* get the compact serialized JWT from the session */ s_id_token = oidc_session_get_idtoken(r, session); if (s_id_token) { /* pass the compact serialized JWT to the app in a header or environment variable */ oidc_util_set_app_info(r, OIDC_APP_INFO_ID_TOKEN, s_id_token, OIDC_DEFAULT_HEADER_PREFIX, pass_in, encoding); } else { oidc_warn(r, "id_token was not found in the session so it cannot be passed on"); } } /* pass the at, rt and at expiry to the application, possibly update the session expiry */ if (oidc_session_pass_tokens(r, cfg, session, extend_session, needs_save) == FALSE) return HTTP_INTERNAL_SERVER_ERROR; oidc_userinfo_pass_as(r, cfg, session, s_claims, pass_in, encoding); /* return "user authenticated" status */ return OK; } /* * get the r->user for this request based on the configuration for OIDC/OAuth */ apr_byte_t oidc_get_remote_user(request_rec *r, const char *claim_name, const char *reg_exp, const char *replace, json_t *json, char **request_user) { /* get the claim value from the JSON object */ json_t *username = json_object_get(json, claim_name); if ((username == NULL) || (!json_is_string(username))) { oidc_warn(r, "JSON object did not contain a \"%s\" string", claim_name); return FALSE; } *request_user = apr_pstrdup(r->pool, json_string_value(username)); if (reg_exp != NULL) { char *error_str = NULL; if (replace == NULL) { if (oidc_util_regexp_first_match(r->pool, *request_user, reg_exp, request_user, &error_str) == FALSE) { oidc_error(r, "oidc_util_regexp_first_match failed: %s", error_str); *request_user = NULL; return FALSE; } } else if (oidc_util_regexp_substitute(r->pool, *request_user, reg_exp, replace, request_user, &error_str) == FALSE) { oidc_error(r, "oidc_util_regexp_substitute failed: %s", error_str); *request_user = NULL; return FALSE; } } return TRUE; } #define OIDC_MAX_URL_LENGTH 8192 * 2 /* * avoid cross site request forgery on the redirect_to_url */ apr_byte_t oidc_validate_redirect_url(request_rec *r, oidc_cfg_t *c, const char *redirect_to_url, apr_byte_t restrict_to_host, char **err_str, char **err_desc) { apr_uri_t uri; const char *c_host = NULL; apr_hash_index_t *hi = NULL; size_t i = 0; char *url = apr_pstrndup(r->pool, redirect_to_url, OIDC_MAX_URL_LENGTH); char *url_ipv6_aware = NULL; // replace potentially harmful backslashes with forward slashes for (i = 0; i < _oidc_strlen(url); i++) if (url[i] == '\\') url[i] = '/'; if (apr_uri_parse(r->pool, url, &uri) != APR_SUCCESS) { *err_str = apr_pstrdup(r->pool, "Malformed URL"); *err_desc = apr_psprintf(r->pool, "not a valid URL value: %s", url); oidc_error(r, "%s: %s", *err_str, *err_desc); return FALSE; } if (oidc_cfg_redirect_urls_allowed_get(c) != NULL) { for (hi = apr_hash_first(NULL, oidc_cfg_redirect_urls_allowed_get(c)); hi; hi = apr_hash_next(hi)) { apr_hash_this(hi, (const void **)&c_host, NULL, NULL); if (oidc_util_regexp_first_match(r->pool, url, c_host, NULL, err_str) == TRUE) break; } if (hi == NULL) { *err_str = apr_pstrdup(r->pool, "URL not allowed"); *err_desc = apr_psprintf(r->pool, "value does not match the list of allowed redirect URLs: %s", url); oidc_error(r, "%s: %s", *err_str, *err_desc); return FALSE; } } else if ((uri.hostname != NULL) && (restrict_to_host == TRUE)) { c_host = oidc_util_current_url_host(r, oidc_cfg_x_forwarded_headers_get(c)); if (strchr(uri.hostname, ':')) { /* v6 literal */ url_ipv6_aware = apr_pstrcat(r->pool, "[", uri.hostname, "]", NULL); } else { url_ipv6_aware = uri.hostname; } if ((oidc_util_strcasestr(c_host, url_ipv6_aware) == NULL) || (oidc_util_strcasestr(url_ipv6_aware, c_host) == NULL)) { *err_str = apr_pstrdup(r->pool, "Invalid Request"); *err_desc = apr_psprintf( r->pool, "URL value \"%s\" does not match the hostname of the current request \"%s\"", apr_uri_unparse(r->pool, &uri, 0), c_host); oidc_error(r, "%s: %s", *err_str, *err_desc); return FALSE; } } if ((uri.hostname == NULL) && (_oidc_strstr(url, "/") != url)) { *err_str = apr_pstrdup(r->pool, "Malformed URL"); *err_desc = apr_psprintf( r->pool, "No hostname was parsed and it does not seem to be relative, i.e starting with '/': %s", url); oidc_error(r, "%s: %s", *err_str, *err_desc); return FALSE; } else if ((uri.hostname == NULL) && (_oidc_strstr(url, "//") == url)) { *err_str = apr_pstrdup(r->pool, "Malformed URL"); *err_desc = apr_psprintf(r->pool, "No hostname was parsed and starting with '//': %s", url); oidc_error(r, "%s: %s", *err_str, *err_desc); return FALSE; } else if ((uri.hostname == NULL) && (_oidc_strstr(url, "/\\") == url)) { *err_str = apr_pstrdup(r->pool, "Malformed URL"); *err_desc = apr_psprintf(r->pool, "No hostname was parsed and starting with '/\\': %s", url); oidc_error(r, "%s: %s", *err_str, *err_desc); return FALSE; } /* validate the URL to prevent HTTP header splitting */ if (((_oidc_strstr(url, "\n") != NULL) || _oidc_strstr(url, "\r") != NULL)) { *err_str = apr_pstrdup(r->pool, "Invalid URL"); *err_desc = apr_psprintf(r->pool, "URL value \"%s\" contains illegal \"\n\" or \"\r\" character(s)", url); oidc_error(r, "%s: %s", *err_str, *err_desc); return FALSE; } if ((_oidc_strstr(url, "/%09") != NULL) || (oidc_util_strcasestr(url, "/%2f") != NULL) || (_oidc_strstr(url, "/\t") != NULL) || (_oidc_strstr(url, "/%68") != NULL) || (oidc_util_strcasestr(url, "/http:") != NULL) || (oidc_util_strcasestr(url, "/https:") != NULL) || (oidc_util_strcasestr(url, "/javascript:") != NULL) || (_oidc_strstr(url, "/〱") != NULL) || (_oidc_strstr(url, "/〵") != NULL) || (_oidc_strstr(url, "/ゝ") != NULL) || (_oidc_strstr(url, "/ー") != NULL) || (_oidc_strstr(url, "/ー") != NULL) || (_oidc_strstr(url, "/<") != NULL) || (oidc_util_strcasestr(url, "%01javascript:") != NULL) || (_oidc_strstr(url, "/%5c") != NULL) || (_oidc_strstr(url, "/\\") != NULL)) { *err_str = apr_pstrdup(r->pool, "Invalid URL"); *err_desc = apr_psprintf(r->pool, "URL value \"%s\" contains illegal character(s)", url); oidc_error(r, "%s: %s", *err_str, *err_desc); return FALSE; } return TRUE; } /* * return the Javascript code used to handle an Implicit grant type * i.e. that posts the data returned by the OP in the URL fragment to the OIDCRedirectURI */ static int oidc_javascript_implicit(request_rec *r, oidc_cfg_t *c) { oidc_debug(r, "enter"); const char *java_script = " \n"; const char *html_body = "

Submitting...

\n" "
\n" "

\n" " \n" "

\n" "
\n"; return oidc_util_html_send(r, "Submitting...", java_script, "postOnLoad", html_body, OK); } /* * handle all requests to the redirect_uri */ int oidc_handle_redirect_uri_request(request_rec *r, oidc_cfg_t *c, oidc_session_t *session) { apr_byte_t needs_save = FALSE; char *s_extend_session = NULL; int rc = OK; OIDC_METRICS_TIMING_START(r, c); if (oidc_proto_response_is_redirect(r, c)) { OIDC_METRICS_COUNTER_INC(r, c, OM_REDIRECT_URI_AUTHN_RESPONSE_REDIRECT); /* this is an authorization response from the OP using the Basic Client profile or a Hybrid flow*/ rc = oidc_response_authorization_redirect(r, c, session); OIDC_METRICS_TIMING_ADD(r, c, OM_AUTHN_RESPONSE); return rc; /* * * Note that we are checking for logout *before* checking for a POST authorization response * to handle backchannel POST-based logout * * so any POST to the Redirect URI that does not have a logout query parameter will be handled * as an authorization response; alternatively we could assume that a POST response has no * parameters */ } else if (oidc_util_request_has_parameter(r, OIDC_REDIRECT_URI_REQUEST_LOGOUT)) { OIDC_METRICS_COUNTER_INC(r, c, OM_REDIRECT_URI_REQUEST_LOGOUT); /* handle logout */ rc = oidc_logout(r, c, session); return rc; } else if (oidc_proto_response_is_post(r, c)) { OIDC_METRICS_COUNTER_INC(r, c, OM_REDIRECT_URI_AUTHN_RESPONSE_POST); /* this is an authorization response using the fragment(+POST) response_mode with the Implicit Client * profile */ rc = oidc_response_authorization_post(r, c, session); OIDC_METRICS_TIMING_ADD(r, c, OM_AUTHN_RESPONSE); return rc; } else if (oidc_is_discovery_response(r, c)) { OIDC_METRICS_COUNTER_INC(r, c, OM_REDIRECT_URI_DISCOVERY_RESPONSE); /* this is response from the OP discovery page */ rc = oidc_discovery_response(r, c); return rc; } else if (oidc_util_request_has_parameter(r, OIDC_REDIRECT_URI_REQUEST_JWKS)) { OIDC_METRICS_COUNTER_INC(r, c, OM_REDIRECT_URI_REQUEST_JWKS); /* * Will be handled in the content handler; avoid: * No authentication done but request not allowed without authentication * by setting r->user */ r->user = ""; return OK; } else if (oidc_util_request_has_parameter(r, OIDC_REDIRECT_URI_REQUEST_SESSION)) { OIDC_METRICS_COUNTER_INC(r, c, OM_REDIRECT_URI_REQUEST_SESSION); /* handle session management request */ rc = oidc_session_management(r, c, session); return rc; } else if (oidc_util_request_has_parameter(r, OIDC_REDIRECT_URI_REQUEST_REFRESH)) { OIDC_METRICS_COUNTER_INC(r, c, OM_REDIRECT_URI_REQUEST_REFRESH); /* handle refresh token request */ rc = oidc_refresh_token_request(r, c, session); return rc; } else if (oidc_util_request_has_parameter(r, OIDC_REDIRECT_URI_REQUEST_REQUEST_URI)) { OIDC_METRICS_COUNTER_INC(r, c, OM_REDIRECT_URI_REQUEST_REQUEST_URI); /* handle request object by reference request */ rc = oidc_request_uri(r, c); return rc; } else if (oidc_util_request_has_parameter(r, OIDC_REDIRECT_URI_REQUEST_REMOVE_AT_CACHE)) { OIDC_METRICS_COUNTER_INC(r, c, OM_REDIRECT_URI_REQUEST_REMOVE_AT_CACHE); /* handle request to invalidate access token cache */ rc = oidc_revoke_at_cache_remove(r, c); return rc; } else if (oidc_util_request_has_parameter(r, OIDC_REDIRECT_URI_REQUEST_REVOKE_SESSION)) { OIDC_METRICS_COUNTER_INC(r, c, OM_REDIRECT_URI_REQUEST_REVOKE_SESSION); /* handle request to revoke a user session */ rc = oidc_revoke_session(r, c); return rc; } else if (oidc_util_request_has_parameter(r, OIDC_REDIRECT_URI_REQUEST_DPOP)) { OIDC_METRICS_COUNTER_INC(r, c, OM_REDIRECT_URI_REQUEST_DPOP); r->user = ""; return OK; } else if (oidc_util_request_has_parameter(r, OIDC_REDIRECT_URI_REQUEST_INFO)) { if (session->remote_user == NULL) return HTTP_UNAUTHORIZED; OIDC_METRICS_COUNTER_INC(r, c, OM_REDIRECT_URI_REQUEST_INFO); oidc_util_request_parameter_get(r, OIDC_INFO_PARAM_EXTEND_SESSION, &s_extend_session); // need to establish user/claims for authorization purposes rc = oidc_handle_existing_session( r, c, session, (s_extend_session == NULL) || (_oidc_strcmp(s_extend_session, "false") != 0), &needs_save); // retain this session across the authentication and content handler phases // by storing it in the request state apr_pool_userdata_set(session, OIDC_USERDATA_SESSION, NULL, r->pool); // record whether the session was modified and needs to be saved in the cache if (needs_save) oidc_request_state_set(r, OIDC_REQUEST_STATE_KEY_SAVE, ""); return rc; } else if ((r->args == NULL) || (_oidc_strcmp(r->args, "") == 0)) { OIDC_METRICS_COUNTER_INC(r, c, OM_REDIRECT_URI_AUTHN_RESPONSE_IMPLICIT); /* this is a "bare" request to the redirect URI, indicating implicit flow using the fragment * response_mode */ rc = oidc_javascript_implicit(r, c); return rc; } /* this is not an authorization response or logout request */ /* check for "error" response */ if (oidc_util_request_has_parameter(r, OIDC_PROTO_ERROR)) { OIDC_METRICS_COUNTER_INC(r, c, OM_REDIRECT_URI_ERROR_PROVIDER); // char *error = NULL, *descr = NULL; // oidc_util_get_request_parameter(r, "error", &error); // oidc_util_get_request_parameter(r, "error_description", &descr); // // /* send user facing error to browser */ // return oidc_util_html_send_error(r, error, descr, OK); rc = oidc_response_authorization_redirect(r, c, session); return rc; } OIDC_METRICS_COUNTER_INC(r, c, OM_REDIRECT_URI_ERROR_INVALID); oidc_error( r, "The OpenID Connect callback URL received an invalid request: %s; returning HTTP_INTERNAL_SERVER_ERROR", r->args); /* something went wrong */ return oidc_util_html_send_error( r, "Invalid Request", apr_psprintf(r->pool, "The OpenID Connect callback URL received an invalid request"), HTTP_INTERNAL_SERVER_ERROR); } /* * main routine: handle OpenID Connect authentication */ static int oidc_check_userid_openidc(request_rec *r, oidc_cfg_t *c) { OIDC_METRICS_TIMING_START(r, c); if (oidc_util_redirect_uri(r, c) == NULL) { oidc_error(r, "configuration error: the authentication type is set to \"" OIDC_AUTH_TYPE_OPENID_CONNECT "\" but " OIDCRedirectURI " has not been set"); return HTTP_INTERNAL_SERVER_ERROR; } /* check if this is a sub-request or an initial request */ if (!ap_is_initial_req(r)) { /* not an initial request, try to recycle what we've already established in the main request */ if (r->main != NULL) r->user = r->main->user; else if (r->prev != NULL) r->user = r->prev->user; if (r->user != NULL) { /* this is a sub-request and we have a session (headers will have been scrubbed and set already) */ oidc_debug(r, "recycling user '%s' from initial request for sub-request", r->user); /* * apparently request state can get lost in sub-requests, so let's see * if we need to restore id_token and/or claims from the session cache */ const char *s_id_token = oidc_request_state_get(r, OIDC_REQUEST_STATE_KEY_IDTOKEN); if (s_id_token == NULL) { oidc_session_t *session = NULL; oidc_session_load(r, &session); oidc_copy_tokens_to_request_state(r, session, NULL, NULL); /* free resources allocated for the session */ oidc_session_free(r, session); } /* strip any cookies that we need to */ oidc_strip_cookies(r); return OK; } /* * else: not initial request, but we could not find a session, so: * try to load a new session as if this were the initial request */ } int rc = OK; apr_byte_t needs_save = FALSE; /* load the session from the request state; this will be a new "empty" session if no state exists */ oidc_session_t *session = NULL, *retain = NULL; oidc_session_load(r, &session); /* see if the initial request is to the redirect URI; this handles potential logout too */ if (oidc_util_request_matches_url(r, oidc_util_redirect_uri(r, c))) { /* handle request to the redirect_uri */ rc = oidc_handle_redirect_uri_request(r, c, session); /* see if the session needs to be retained for the content handler phase */ apr_pool_userdata_get((void **)&retain, OIDC_USERDATA_SESSION, r->pool); /* free resources allocated for the session */ if (retain == NULL) oidc_session_free(r, session); return rc; /* initial request to non-redirect URI, check if we have an existing session */ } else if (session->remote_user != NULL) { /* this is initial request and we already have a session */ rc = oidc_handle_existing_session(r, c, session, TRUE, &needs_save); if (rc == OK) { /* check if something was updated in the session and we need to save it again */ if (needs_save) { if (oidc_session_save(r, session, FALSE) == FALSE) { oidc_warn(r, "error saving session"); rc = HTTP_INTERNAL_SERVER_ERROR; } } } /* free resources allocated for the session */ oidc_session_free(r, session); /* strip any cookies that we need to */ oidc_strip_cookies(r); if (rc == OK) { OIDC_METRICS_TIMING_ADD(r, c, OM_SESSION_VALID); } else { OIDC_METRICS_COUNTER_INC(r, c, OM_SESSION_ERROR_GENERAL); } return rc; } /* free resources allocated for the session */ oidc_session_free(r, session); /* * else: we have no session and it is not an authorization or * discovery response: just hit the default flow for unauthenticated users */ return oidc_handle_unauthenticated_user(r, c); } /* * main routine: handle "mixed" OIDC/OAuth authentication */ static int oidc_check_mixed_userid_oauth(request_rec *r, oidc_cfg_t *c) { /* get the bearer access token from the Authorization header */ const char *access_token = NULL; if (oidc_oauth_get_bearer_token(r, &access_token) == TRUE) { r->ap_auth_type = apr_pstrdup(r->pool, OIDC_AUTH_TYPE_OPENID_OAUTH20); return oidc_oauth_check_userid(r, c, access_token); } if (r->method_number == M_OPTIONS) { r->user = ""; return OK; } /* no bearer token found: then treat this as a regular OIDC browser request */ r->ap_auth_type = apr_pstrdup(r->pool, OIDC_AUTH_TYPE_OPENID_CONNECT); return oidc_check_userid_openidc(r, c); } int oidc_fixups(request_rec *r) { oidc_cfg_t *c = ap_get_module_config(r->server->module_config, &auth_openidc_module); if (oidc_enabled(r) == TRUE) { OIDC_METRICS_TIMING_REQUEST_ADD(r, c, OM_MOD_AUTH_OPENIDC); return OK; } return DECLINED; } /* * generic Apache authentication hook for this module: dispatches to OpenID Connect or OAuth 2.0 specific routines */ int oidc_check_user_id(request_rec *r) { oidc_cfg_t *c = ap_get_module_config(r->server->module_config, &auth_openidc_module); int rv = DECLINED; OIDC_METRICS_TIMING_REQUEST_START(r, c); /* log some stuff about the incoming HTTP request */ oidc_debug(r, "incoming request: \"%s?%s\", ap_is_initial_req(r)=%d", r->parsed_uri.path, r->args, ap_is_initial_req(r)); if (oidc_enabled(r) == FALSE) { OIDC_METRICS_COUNTER_INC(r, c, OM_AUTHTYPE_DECLINED); return DECLINED; } oidc_util_set_trace_parent(r, c, NULL); OIDC_METRICS_COUNTER_INC(r, c, OM_AUTHTYPE_MOD_AUTH_OPENIDC); /* see if we've configured OpenID Connect user authentication for this request */ if (_oidc_strnatcasecmp(ap_auth_type(r), OIDC_AUTH_TYPE_OPENID_CONNECT) == 0) { OIDC_METRICS_COUNTER_INC(r, c, OM_AUTHTYPE_OPENID_CONNECT); r->ap_auth_type = apr_pstrdup(r->pool, ap_auth_type(r)); rv = oidc_check_userid_openidc(r, c); /* see if we've configured OAuth 2.0 access control for this request */ } else if (_oidc_strnatcasecmp(ap_auth_type(r), OIDC_AUTH_TYPE_OPENID_OAUTH20) == 0) { OIDC_METRICS_COUNTER_INC(r, c, OM_AUTHTYPE_OAUTH20); r->ap_auth_type = apr_pstrdup(r->pool, ap_auth_type(r)); rv = oidc_oauth_check_userid(r, c, NULL); /* see if we've configured "mixed mode" for this request */ } else if (_oidc_strnatcasecmp(ap_auth_type(r), OIDC_AUTH_TYPE_OPENID_BOTH) == 0) { OIDC_METRICS_COUNTER_INC(r, c, OM_AUTHTYPE_AUTH_OPENIDC); rv = oidc_check_mixed_userid_oauth(r, c); } return rv; } /* * check of mod_auth_openidc needs to handle this request */ apr_byte_t oidc_enabled(request_rec *r) { if (ap_auth_type(r) == NULL) return FALSE; if (_oidc_strnatcasecmp((const char *)ap_auth_type(r), OIDC_AUTH_TYPE_OPENID_CONNECT) == 0) return TRUE; if (_oidc_strnatcasecmp((const char *)ap_auth_type(r), OIDC_AUTH_TYPE_OPENID_OAUTH20) == 0) return TRUE; if (_oidc_strnatcasecmp((const char *)ap_auth_type(r), OIDC_AUTH_TYPE_OPENID_BOTH) == 0) return TRUE; return FALSE; } /* * report a config error */ static int oidc_check_config_error(server_rec *s, const char *config_str) { oidc_serror(s, "mandatory parameter '%s' is not set", config_str); return HTTP_INTERNAL_SERVER_ERROR; } /* * check the config required for the OpenID Connect RP role */ static int oidc_check_config_openid_openidc(server_rec *s, oidc_cfg_t *c) { apr_uri_t r_uri; apr_byte_t redirect_uri_is_relative; if ((oidc_cfg_metadata_dir_get(c) == NULL) && (oidc_cfg_provider_issuer_get(oidc_cfg_provider_get(c)) == NULL) && (oidc_cfg_provider_metadata_url_get(oidc_cfg_provider_get(c)) == NULL)) { oidc_serror(s, "one of '" OIDCProviderIssuer "', '" OIDCProviderMetadataURL "' or '" OIDCMetadataDir "' must be set"); return HTTP_INTERNAL_SERVER_ERROR; } if (oidc_cfg_redirect_uri_get(c) == NULL) return oidc_check_config_error(s, OIDCRedirectURI); redirect_uri_is_relative = (oidc_cfg_redirect_uri_get(c)[0] == OIDC_CHAR_FORWARD_SLASH); if (oidc_cfg_crypto_passphrase_secret1_get(c) == NULL) return oidc_check_config_error(s, OIDCCryptoPassphrase); if (oidc_cfg_metadata_dir_get(c) == NULL) { if (oidc_cfg_provider_metadata_url_get(oidc_cfg_provider_get(c)) == NULL) { if (oidc_cfg_provider_issuer_get(oidc_cfg_provider_get(c)) == NULL) return oidc_check_config_error(s, OIDCProviderIssuer); if (oidc_cfg_provider_authorization_endpoint_url_get(oidc_cfg_provider_get(c)) == NULL) return oidc_check_config_error(s, OIDCProviderAuthorizationEndpoint); } else { apr_uri_parse(s->process->pconf, oidc_cfg_provider_metadata_url_get(oidc_cfg_provider_get(c)), &r_uri); if ((r_uri.scheme == NULL) || (_oidc_strnatcasecmp(r_uri.scheme, "https") != 0)) { oidc_swarn(s, "the URL scheme (%s) of the configured " OIDCProviderMetadataURL " SHOULD be \"https\" for security reasons!", r_uri.scheme); } } if (oidc_cfg_provider_client_id_get(oidc_cfg_provider_get(c)) == NULL) return oidc_check_config_error(s, OIDCClientID); } else { if (oidc_cfg_provider_metadata_url_get(oidc_cfg_provider_get(c)) != NULL) { oidc_serror(s, "only one of '" OIDCProviderMetadataURL "' or '" OIDCMetadataDir "' should be set"); return HTTP_INTERNAL_SERVER_ERROR; } } apr_uri_parse(s->process->pconf, oidc_cfg_redirect_uri_get(c), &r_uri); if (!redirect_uri_is_relative) { if (_oidc_strnatcasecmp(r_uri.scheme, "https") != 0) { oidc_swarn(s, "the URL scheme (%s) of the configured " OIDCRedirectURI " SHOULD be \"https\" for security reasons (moreover: some Providers may reject " "non-HTTPS URLs)", r_uri.scheme); } } if (oidc_cfg_cookie_domain_get(c) != NULL) { if (redirect_uri_is_relative) { oidc_swarn(s, "if the configured " OIDCRedirectURI " is relative, " OIDCCookieDomain " SHOULD be empty"); } else if (!oidc_util_cookie_domain_valid(r_uri.hostname, oidc_cfg_cookie_domain_get(c))) { oidc_serror(s, "the domain (%s) configured in " OIDCCookieDomain " does not match the URL hostname (%s) of the configured " OIDCRedirectURI " (%s): setting \"state\" and \"session\" cookies will not work!", oidc_cfg_cookie_domain_get(c), r_uri.hostname, oidc_cfg_redirect_uri_get(c)); return HTTP_INTERNAL_SERVER_ERROR; } } if (oidc_proto_profile_dpop_mode_get(oidc_cfg_provider_get(c)) != OIDC_DPOP_MODE_OFF) { if (oidc_util_key_list_first(oidc_cfg_private_keys_get(c), -1, OIDC_JOSE_JWK_SIG_STR) == NULL) { oidc_serror(s, "'" OIDCDPoPMode "' is configured but the required signing keys have not been " "provided in '" OIDCPrivateKeyFiles "'/'" OIDCPublicKeyFiles "'"); return HTTP_INTERNAL_SERVER_ERROR; } } return OK; } /* * check the config required for the OAuth 2.0 RS role */ static int oidc_check_config_oauth(server_rec *s, oidc_cfg_t *c) { apr_uri_t r_uri; oidc_swarn(s, "The OAuth 2.0 Resource Server functionality is deprecated and superseded by a new module, see: " "https://github.com/OpenIDC/mod_oauth2!"); if (oidc_cfg_oauth_metadata_url_get(c) != NULL) { apr_uri_parse(s->process->pconf, oidc_cfg_oauth_metadata_url_get(c), &r_uri); if ((r_uri.scheme == NULL) || (_oidc_strnatcasecmp(r_uri.scheme, "https") != 0)) { oidc_swarn(s, "the URL scheme (%s) of the configured " OIDCOAuthServerMetadataURL " SHOULD be \"https\" for security reasons!", r_uri.scheme); } return OK; } if (oidc_cfg_oauth_introspection_endpoint_url_get(c) == NULL) { if ((oidc_cfg_oauth_verify_jwks_uri_get(c) == NULL) && (oidc_cfg_oauth_verify_public_keys_get(c) == NULL) && (oidc_cfg_oauth_verify_shared_keys_get(c) == NULL)) { oidc_serror(s, "one of '" OIDCOAuthServerMetadataURL "', '" OIDCOAuthIntrospectionEndpoint "', '" OIDCOAuthVerifyJwksUri "', '" OIDCOAuthVerifySharedKeys "' or '" OIDCOAuthVerifyCertFiles "' must be set"); return HTTP_INTERNAL_SERVER_ERROR; } } else if ((oidc_cfg_oauth_verify_jwks_uri_get(c) != NULL) || (oidc_cfg_oauth_verify_public_keys_get(c) != NULL) || (oidc_cfg_oauth_verify_shared_keys_get(c) != NULL)) { oidc_serror(s, "only '" OIDCOAuthIntrospectionEndpoint "' OR one (or more) out of ('" OIDCOAuthVerifyJwksUri "', '" OIDCOAuthVerifySharedKeys "' or '" OIDCOAuthVerifyCertFiles "') must be set"); return HTTP_INTERNAL_SERVER_ERROR; } if ((oidc_cfg_cache_encrypt_get(c) == 1) && (oidc_cfg_crypto_passphrase_secret1_get(c) == NULL)) return oidc_check_config_error(s, OIDCCryptoPassphrase); return OK; } /* * check the config of a vhost */ static int oidc_config_check_vhost_config(apr_pool_t *pool, server_rec *s) { oidc_cfg_t *cfg = ap_get_module_config(s->module_config, &auth_openidc_module); oidc_sdebug(s, "enter"); if ((oidc_cfg_metadata_dir_get(cfg) != NULL) || (oidc_cfg_provider_issuer_get(oidc_cfg_provider_get(cfg)) != NULL) || (oidc_cfg_provider_metadata_url_get(oidc_cfg_provider_get(cfg)) != NULL)) { if (oidc_check_config_openid_openidc(s, cfg) != OK) return HTTP_INTERNAL_SERVER_ERROR; } if ((oidc_cfg_oauth_metadata_url_get(cfg) != NULL) || (oidc_cfg_oauth_client_id_get(cfg) != NULL) || (oidc_cfg_oauth_client_secret_get(cfg) != NULL) || (oidc_cfg_oauth_introspection_endpoint_url_get(cfg) != NULL) || (oidc_cfg_oauth_verify_jwks_uri_get(cfg) != NULL) || (oidc_cfg_oauth_verify_public_keys_get(cfg) != NULL) || (oidc_cfg_oauth_verify_shared_keys_get(cfg) != NULL)) { if (oidc_check_config_oauth(s, cfg) != OK) return HTTP_INTERNAL_SERVER_ERROR; } return OK; } /* * check the config of a merged vhost */ static int oidc_config_check_merged_vhost_configs(apr_pool_t *pool, server_rec *s) { int status = OK; while (s != NULL && status == OK) { oidc_cfg_t *cfg = ap_get_module_config(s->module_config, &auth_openidc_module); if (oidc_cfg_merged_get(cfg)) { status = oidc_config_check_vhost_config(pool, s); } s = s->next; } return status; } /* * check if any merged vhost configs exist */ static int oidc_config_merged_vhost_configs_exist(server_rec *s) { while (s != NULL) { oidc_cfg_t *cfg = ap_get_module_config(s->module_config, &auth_openidc_module); if (oidc_cfg_merged_get(cfg)) { return TRUE; } s = s->next; } return FALSE; } /* * SSL initialization magic copied from mod_auth_cas */ #if ((OPENSSL_VERSION_NUMBER < 0x10100000) && defined(OPENSSL_THREADS) && APR_HAS_THREADS) static apr_thread_mutex_t **ssl_locks; static int ssl_num_locks; static void oidc_ssl_locking_callback(int mode, int type, const char *file, int line) { if (type < ssl_num_locks) { if (mode & CRYPTO_LOCK) apr_thread_mutex_lock(ssl_locks[type]); else apr_thread_mutex_unlock(ssl_locks[type]); } } #ifdef OPENSSL_NO_THREADID static unsigned long oidc_ssl_id_callback(void) { return (unsigned long)apr_os_thread_current(); } #else static void oidc_ssl_id_callback(CRYPTO_THREADID *id) { CRYPTO_THREADID_set_numeric(id, (unsigned long)apr_os_thread_current()); } #endif /* OPENSSL_NO_THREADID */ #endif /* defined(OPENSSL_THREADS) && APR_HAS_THREADS */ /* * cleanup resources allocated in a child process */ static apr_status_t oidc_cleanup_child(void *data) { server_rec *sp = (server_rec *)data; while (sp != NULL) { oidc_cfg_t *cfg = (oidc_cfg_t *)ap_get_module_config(sp->module_config, &auth_openidc_module); oidc_cfg_cleanup_child(cfg, sp); sp = sp->next; } return APR_SUCCESS; } /* * cleanup resources allocated in a parent process */ static apr_status_t oidc_cleanup_parent(void *data) { oidc_cleanup_child(data); #if ((OPENSSL_VERSION_NUMBER < 0x10100000) && defined(OPENSSL_THREADS) && APR_HAS_THREADS) if (CRYPTO_get_locking_callback() == oidc_ssl_locking_callback) CRYPTO_set_locking_callback(NULL); #ifdef OPENSSL_NO_THREADID if (CRYPTO_get_id_callback() == oidc_ssl_id_callback) CRYPTO_set_id_callback(NULL); #else if (CRYPTO_THREADID_get_callback() == oidc_ssl_id_callback) CRYPTO_THREADID_set_callback(NULL); #endif /* OPENSSL_NO_THREADID */ #endif /* (OPENSSL_VERSION_NUMBER < 0x10100000) && defined (OPENSSL_THREADS) && APR_HAS_THREADS */ EVP_cleanup(); oidc_http_cleanup(); ap_log_error(APLOG_MARK, APLOG_INFO, 0, (server_rec *)data, "%s - shutdown", NAMEVERSION); return APR_SUCCESS; } /* * handler that is called (twice) after the configuration phase; check if everything is OK */ static int oidc_post_config(apr_pool_t *pool, apr_pool_t *p1, apr_pool_t *p2, server_rec *s) { const char *userdata_key = "oidc_post_config"; void *data = NULL; /* Since the post_config hook is invoked twice (once * for 'sanity checking' of the config and once for * the actual server launch, we have to use a hack * to not run twice */ apr_pool_userdata_get(&data, userdata_key, s->process->pool); if (data == NULL) { apr_pool_userdata_set((const void *)1, userdata_key, apr_pool_cleanup_null, s->process->pool); return OK; } #ifdef USE_MEMCACHE #define _OIDC_USE_MEMCACHE "yes" #else #define _OIDC_USE_MEMCACHE "no" #endif #ifdef USE_LIBHIREDIS #define _OIDC_USE_REDIS "yes" #else #define _OIDC_USE_REDIS "no" #endif #ifdef USE_LIBJQ #define _OIDC_USE_JQ "yes" #else #define _OIDC_USE_JQ "no" #endif ap_log_error(APLOG_MARK, APLOG_INFO, 0, s, "%s - init - cjose %s, %s, EC=%s, GCM=%s, Memcache=%s, Redis=%s, JQ=%s", NAMEVERSION, cjose_version(), oidc_util_openssl_version(s->process->pool), OIDC_JOSE_EC_SUPPORT ? "yes" : "no", OIDC_JOSE_GCM_SUPPORT ? "yes" : "no", _OIDC_USE_MEMCACHE, _OIDC_USE_REDIS, _OIDC_USE_JQ); oidc_http_init(); #if ((OPENSSL_VERSION_NUMBER < 0x10100000) && defined(OPENSSL_THREADS) && APR_HAS_THREADS) ssl_num_locks = CRYPTO_num_locks(); ssl_locks = apr_pcalloc(s->process->pool, ssl_num_locks * sizeof(*ssl_locks)); int i; for (i = 0; i < ssl_num_locks; i++) apr_thread_mutex_create(&(ssl_locks[i]), APR_THREAD_MUTEX_DEFAULT, s->process->pool); #ifdef OPENSSL_NO_THREADID if (CRYPTO_get_locking_callback() == NULL && CRYPTO_get_id_callback() == NULL) { CRYPTO_set_locking_callback(oidc_ssl_locking_callback); CRYPTO_set_id_callback(oidc_ssl_id_callback); } #else if (CRYPTO_get_locking_callback() == NULL && CRYPTO_THREADID_get_callback() == NULL) { CRYPTO_set_locking_callback(oidc_ssl_locking_callback); CRYPTO_THREADID_set_callback(oidc_ssl_id_callback); } #endif /* OPENSSL_NO_THREADID */ #endif /* (OPENSSL_VERSION_NUMBER < 0x10100000) && defined (OPENSSL_THREADS) && APR_HAS_THREADS */ apr_pool_cleanup_register(pool, s, oidc_cleanup_parent, apr_pool_cleanup_null); server_rec *sp = s; while (sp != NULL) { oidc_cfg_t *cfg = (oidc_cfg_t *)ap_get_module_config(sp->module_config, &auth_openidc_module); if (oidc_cfg_post_config(cfg, sp) != OK) return HTTP_INTERNAL_SERVER_ERROR; sp = sp->next; } /* * Apache has a base vhost that true vhosts derive from. * There are two startup scenarios: * * 1. Only the base vhost contains OIDC settings. * No server configs have been merged. * Only the base vhost needs to be checked. * * 2. The base vhost contains zero or more OIDC settings. * One or more vhosts override these. * These vhosts have a merged config. * All merged configs need to be checked. */ if (!oidc_config_merged_vhost_configs_exist(s)) { /* nothing merged, only check the base vhost */ return oidc_config_check_vhost_config(pool, s); } return oidc_config_check_merged_vhost_configs(pool, s); } #if HAVE_APACHE_24 /* * parse an Apache expression in the configured require value */ static const char *oidc_parse_config(cmd_parms *cmd, const char *require_line, const void **parsed_require_line) { const char *expr_err = NULL; ap_expr_info_t *expr; expr = ap_expr_parse_cmd(cmd, require_line, AP_EXPR_FLAG_STRING_RESULT, &expr_err, NULL); if (expr_err) return apr_pstrcat(cmd->temp_pool, "Cannot parse expression in require line: ", expr_err, NULL); *parsed_require_line = expr; return NULL; } static const authz_provider oidc_authz_claim_provider = { &oidc_authz_24_checker_claim, &oidc_parse_config, }; #ifdef USE_LIBJQ static const authz_provider oidc_authz_claims_expr_provider = { &oidc_authz_24_checker_claims_expr, NULL, }; #endif #endif /* * initialize cache context in child process if required */ static void oidc_child_init(apr_pool_t *p, server_rec *s) { server_rec *sp = s; while (sp != NULL) { oidc_cfg_t *cfg = (oidc_cfg_t *)ap_get_module_config(sp->module_config, &auth_openidc_module); oidc_cfg_child_init(p, cfg, sp); sp = sp->next; } /* * NB: don't pass oidc_cleanup_child as the child cleanup routine parameter * because that does not actually get called upon child cleanup... */ apr_pool_cleanup_register(p, s, oidc_cleanup_child, apr_pool_cleanup_null); } static const char oidcFilterName[] = "oidc_filter_in_filter"; /* * add filter for inserting POST data */ static void oidc_filter_in_insert_filter(request_rec *r) { if (oidc_enabled(r) == FALSE) return; if (ap_is_initial_req(r) == 0) return; apr_table_t *userdata_post_params = NULL; apr_pool_userdata_get((void **)&userdata_post_params, OIDC_USERDATA_POST_PARAMS_KEY, r->pool); if (userdata_post_params == NULL) return; ap_add_input_filter(oidcFilterName, NULL, r, r->connection); } typedef struct oidc_filter_in_context { apr_bucket_brigade *pbbTmp; apr_size_t nbytes; } oidc_filter_in_context; /* * execute filter for inserting POST data */ static apr_status_t oidc_filter_in_filter(ap_filter_t *f, apr_bucket_brigade *brigade, ap_input_mode_t mode, apr_read_type_e block, apr_off_t nbytes) { oidc_filter_in_context *ctx = NULL; apr_bucket *b_in = NULL, *b_out = NULL; char *buf = NULL; apr_table_t *userdata_post_params = NULL; apr_status_t rc = APR_SUCCESS; if (!(ctx = f->ctx)) { f->ctx = ctx = apr_palloc(f->r->pool, sizeof *ctx); ctx->pbbTmp = apr_brigade_create(f->r->pool, f->r->connection->bucket_alloc); ctx->nbytes = 0; } if (APR_BRIGADE_EMPTY(ctx->pbbTmp)) { rc = ap_get_brigade(f->next, ctx->pbbTmp, mode, block, nbytes); if (mode == AP_MODE_EATCRLF || rc != APR_SUCCESS) return rc; } while (!APR_BRIGADE_EMPTY(ctx->pbbTmp)) { b_in = APR_BRIGADE_FIRST(ctx->pbbTmp); if (APR_BUCKET_IS_EOS(b_in)) { APR_BUCKET_REMOVE(b_in); apr_pool_userdata_get((void **)&userdata_post_params, OIDC_USERDATA_POST_PARAMS_KEY, f->r->pool); if (userdata_post_params != NULL) { buf = apr_psprintf(f->r->pool, "%s%s", ctx->nbytes > 0 ? "&" : "", oidc_http_form_encoded_data(f->r, userdata_post_params)); b_out = apr_bucket_heap_create(buf, _oidc_strlen(buf), 0, f->r->connection->bucket_alloc); APR_BRIGADE_INSERT_TAIL(brigade, b_out); ctx->nbytes += _oidc_strlen(buf); if (oidc_http_hdr_in_content_length_get(f->r) != NULL) oidc_http_hdr_in_set(f->r, OIDC_HTTP_HDR_CONTENT_LENGTH, apr_psprintf(f->r->pool, "%ld", (long)ctx->nbytes)); apr_pool_userdata_set(NULL, OIDC_USERDATA_POST_PARAMS_KEY, NULL, f->r->pool); } APR_BRIGADE_INSERT_TAIL(brigade, b_in); break; } APR_BUCKET_REMOVE(b_in); APR_BRIGADE_INSERT_TAIL(brigade, b_in); ctx->nbytes += b_in->length; } return rc; } /* * register our authentication and authorization functions */ static void oidc_register_hooks(apr_pool_t *pool) { oidc_pre_config_init(); ap_hook_post_config(oidc_post_config, NULL, NULL, APR_HOOK_LAST); ap_hook_child_init(oidc_child_init, NULL, NULL, APR_HOOK_MIDDLE); ap_hook_fixups(oidc_fixups, NULL, NULL, APR_HOOK_MIDDLE); static const char *const proxySucc[] = {"mod_proxy.c", NULL}; ap_hook_handler(oidc_content_handler, NULL, proxySucc, APR_HOOK_FIRST); ap_hook_insert_filter(oidc_filter_in_insert_filter, NULL, NULL, APR_HOOK_MIDDLE); ap_register_input_filter(oidcFilterName, oidc_filter_in_filter, NULL, AP_FTYPE_RESOURCE); #if HAVE_APACHE_24 ap_hook_check_authn(oidc_check_user_id, NULL, NULL, APR_HOOK_MIDDLE, AP_AUTH_INTERNAL_PER_CONF); ap_register_auth_provider(pool, AUTHZ_PROVIDER_GROUP, OIDC_REQUIRE_CLAIM_NAME, "0", &oidc_authz_claim_provider, AP_AUTH_INTERNAL_PER_CONF); #ifdef USE_LIBJQ ap_register_auth_provider(pool, AUTHZ_PROVIDER_GROUP, OIDC_REQUIRE_CLAIMS_EXPR_NAME, "0", &oidc_authz_claims_expr_provider, AP_AUTH_INTERNAL_PER_CONF); #endif #else static const char *const authzSucc[] = {"mod_authz_user.c", NULL}; ap_hook_check_user_id(oidc_check_user_id, NULL, NULL, APR_HOOK_MIDDLE); ap_hook_auth_checker(oidc_authz_22_checker, NULL, authzSucc, APR_HOOK_MIDDLE); #endif } // clang-format off module AP_MODULE_DECLARE_DATA auth_openidc_module = { STANDARD20_MODULE_STUFF, oidc_cfg_dir_config_create, oidc_cfg_dir_config_merge, oidc_cfg_server_create, oidc_cfg_server_merge, oidc_cfg_cmds, oidc_register_hooks }; // clang-format on mod_auth_openidc-2.4.16.10/src/mod_auth_openidc.h000066400000000000000000000161631476721736500216160ustar00rootroot00000000000000/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ /*************************************************************************** * Copyright (C) 2017-2025 ZmartZone Holding BV * Copyright (C) 2013-2017 Ping Identity Corporation * All rights reserved. * * DISCLAIMER OF WARRANTIES: * * THE SOFTWARE PROVIDED HEREUNDER IS PROVIDED ON AN "AS IS" BASIS, WITHOUT * ANY WARRANTIES OR REPRESENTATIONS EXPRESS, IMPLIED OR STATUTORY; INCLUDING, * WITHOUT LIMITATION, WARRANTIES OF QUALITY, PERFORMANCE, NONINFRINGEMENT, * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. NOR ARE THERE ANY * WARRANTIES CREATED BY A COURSE OR DEALING, COURSE OF PERFORMANCE OR TRADE * USAGE. FURTHERMORE, THERE ARE NO WARRANTIES THAT THE SOFTWARE WILL MEET * YOUR NEEDS OR BE FREE FROM ERRORS, OR THAT THE OPERATION OF THE SOFTWARE * WILL BE UNINTERRUPTED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * @Author: Hans Zandbelt - hans.zandbelt@openidc.com */ #ifndef _MOD_AUTH_OPENIDC_H_ #define _MOD_AUTH_OPENIDC_H_ #include "cfg/cfg.h" #include "cfg/provider.h" #include "session.h" #define OIDC_AUTH_TYPE_OPENID_CONNECT "openid-connect" #define OIDC_AUTH_TYPE_OPENID_OAUTH20 "oauth20" #define OIDC_AUTH_TYPE_OPENID_BOTH "auth-openidc" /* keys for storing info in the request state */ #define OIDC_REQUEST_STATE_KEY_IDTOKEN "i" #define OIDC_REQUEST_STATE_KEY_CLAIMS "c" #define OIDC_REQUEST_STATE_KEY_DISCOVERY "d" #define OIDC_REQUEST_STATE_KEY_AUTHN "a" #define OIDC_REQUEST_STATE_KEY_SAVE "s" #define OIDC_REQUEST_STATE_TRACE_ID "t" /* parameter name of the original method in the discovery response */ #define OIDC_DISC_RM_PARAM "method" /* default prefix for information passed in HTTP headers */ #define OIDC_DEFAULT_HEADER_PREFIX "OIDC_" /* the (global) key for the mod_auth_openidc related state that is stored in the request userdata context */ #define OIDC_USERDATA_KEY "mod_auth_openidc_state" #define OIDC_USERDATA_SESSION "mod_auth_openidc_session" #define OIDC_USERDATA_POST_PARAMS_KEY "oidc_userdata_post_params" #define OIDC_POST_PRESERVE_ESCAPE_NONE 0 #define OIDC_POST_PRESERVE_ESCAPE_HTML 1 #define OIDC_POST_PRESERVE_ESCAPE_JAVASCRIPT 2 /* defines for how long provider metadata will be cached */ #define OIDC_CACHE_PROVIDER_METADATA_EXPIRY_DEFAULT 86400 /* define the parameter value for the "logout" request that indicates a GET-style logout call from the OP */ #define OIDC_GET_STYLE_LOGOUT_PARAM_VALUE "get" #define OIDC_IMG_STYLE_LOGOUT_PARAM_VALUE "img" #define OIDC_BACKCHANNEL_STYLE_LOGOUT_PARAM_VALUE "backchannel" /* http methods */ #define OIDC_METHOD_GET "get" #define OIDC_METHOD_FORM_POST "form_post" #define OIDC_REDIRECT_URI_REQUEST_INFO "info" #define OIDC_REDIRECT_URI_REQUEST_DPOP "dpop" #define OIDC_REDIRECT_URI_REQUEST_LOGOUT "logout" #define OIDC_REDIRECT_URI_REQUEST_JWKS "jwks" #define OIDC_REDIRECT_URI_REQUEST_SESSION "session" #define OIDC_REDIRECT_URI_REQUEST_REFRESH "refresh" #define OIDC_REDIRECT_URI_REQUEST_REMOVE_AT_CACHE "remove_at_cache" #define OIDC_REDIRECT_URI_REQUEST_REVOKE_SESSION "revoke_session" #define OIDC_REDIRECT_URI_REQUEST_REQUEST_URI "request_uri" #define OIDC_REDIRECT_URI_REQUEST_SID "sid" #define OIDC_REDIRECT_URI_REQUEST_ISS "iss" #define OIDC_INFO_PARAM_EXTEND_SESSION "extend_session" #define OIDC_CLAIM_ISS "iss" #define OIDC_CLAIM_AUD "aud" #define OIDC_CLAIM_AZP "azp" #define OIDC_CLAIM_SUB "sub" #define OIDC_CLAIM_JTI "jti" #define OIDC_CLAIM_EXP "exp" #define OIDC_CLAIM_IAT "iat" #define OIDC_CLAIM_NBF "nbf" #define OIDC_CLAIM_NONCE "nonce" #define OIDC_CLAIM_AT_HASH "at_hash" #define OIDC_CLAIM_C_HASH "c_hash" #define OIDC_CLAIM_RFP "rfp" #define OIDC_CLAIM_TARGET_LINK_URI "target_link_uri" #define OIDC_CLAIM_SID "sid" #define OIDC_CLAIM_EVENTS "events" #define OIDC_CLAIM_TYP "typ" #define OIDC_CLAIM_JWK "jwk" #define OIDC_CLAIM_HTM "htm" #define OIDC_CLAIM_HTU "htu" #define OIDC_CLAIM_ATH "ath" #define OIDC_APP_INFO_REFRESH_TOKEN "refresh_token" #define OIDC_APP_INFO_ACCESS_TOKEN "access_token" #define OIDC_APP_INFO_ACCESS_TOKEN_TYPE "access_token_type" #define OIDC_APP_INFO_ACCESS_TOKEN_EXP "access_token_expires" #define OIDC_APP_INFO_ID_TOKEN "id_token" #define OIDC_APP_INFO_ID_TOKEN_PAYLOAD "id_token_payload" #define OIDC_APP_INFO_USERINFO_JSON "userinfo_json" #define OIDC_APP_INFO_USERINFO_JWT "userinfo_jwt" #define OIDC_APP_INFO_SIGNED_JWT "signed_jwt" int oidc_check_user_id(request_rec *r); int oidc_fixups(request_rec *r); apr_byte_t oidc_enabled(request_rec *r); void oidc_request_state_set(request_rec *r, const char *key, const char *value); const char *oidc_request_state_get(request_rec *r, const char *key); void oidc_scrub_headers(request_rec *r); void oidc_strip_cookies(request_rec *r); apr_byte_t oidc_get_remote_user(request_rec *r, const char *claim_name, const char *replace, const char *reg_exp, json_t *json, char **request_user); apr_byte_t oidc_get_provider_from_session(request_rec *r, oidc_cfg_t *c, oidc_session_t *session, oidc_provider_t **provider); apr_byte_t oidc_check_cookie_domain(request_rec *r, oidc_cfg_t *cfg, oidc_session_t *session); apr_byte_t oidc_session_pass_tokens(request_rec *r, oidc_cfg_t *cfg, oidc_session_t *session, apr_byte_t extend_session, apr_byte_t *needs_save); void oidc_log_session_expires(request_rec *r, const char *msg, apr_time_t session_expires); apr_byte_t oidc_provider_static_config(request_rec *r, oidc_cfg_t *c, oidc_provider_t **provider); const char *oidc_original_request_method(request_rec *r, oidc_cfg_t *cfg, apr_byte_t handle_discovery_response); oidc_provider_t *oidc_get_provider_for_issuer(request_rec *r, oidc_cfg_t *c, const char *issuer, apr_byte_t allow_discovery); int oidc_clean_expired_state_cookies(request_rec *r, oidc_cfg_t *c, const char *currentCookieName, int delete_oldest); apr_byte_t oidc_is_auth_capable_request(request_rec *r); apr_byte_t oidc_validate_redirect_url(request_rec *r, oidc_cfg_t *c, const char *redirect_to_url, apr_byte_t restrict_to_host, char **err_str, char **err_desc); apr_byte_t oidc_set_app_claims(request_rec *r, oidc_cfg_t *cfg, const char *s_claims); #endif /* _MOD_AUTH_OPENIDC_H_ */ mod_auth_openidc-2.4.16.10/src/oauth.c000066400000000000000000000670661476721736500174400ustar00rootroot00000000000000/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ /*************************************************************************** * Copyright (C) 2017-2025 ZmartZone Holding BV * Copyright (C) 2013-2017 Ping Identity Corporation * All rights reserved. * * DISCLAIMER OF WARRANTIES: * * THE SOFTWARE PROVIDED HEREUNDER IS PROVIDED ON AN "AS IS" BASIS, WITHOUT * ANY WARRANTIES OR REPRESENTATIONS EXPRESS, IMPLIED OR STATUTORY; INCLUDING, * WITHOUT LIMITATION, WARRANTIES OF QUALITY, PERFORMANCE, NONINFRINGEMENT, * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. NOR ARE THERE ANY * WARRANTIES CREATED BY A COURSE OR DEALING, COURSE OF PERFORMANCE OR TRADE * USAGE. FURTHERMORE, THERE ARE NO WARRANTIES THAT THE SOFTWARE WILL MEET * YOUR NEEDS OR BE FREE FROM ERRORS, OR THAT THE OPERATION OF THE SOFTWARE * WILL BE UNINTERRUPTED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * @Author: Hans Zandbelt - hans.zandbelt@openidc.com */ #include "cfg/oauth.h" #include "cfg/dir.h" #include "cfg/parse.h" #include "handle/handle.h" #include "metadata.h" #include "metrics.h" #include "mod_auth_openidc.h" #include "proto/proto.h" #include "util.h" #include /* * retrieve the OAuth 2.0 metadata discovery document from the specified URL */ apr_byte_t oidc_oauth_metadata_provider_retrieve(request_rec *r, oidc_cfg_t *cfg, const char *issuer, const char *url, json_t **j_metadata, char **response) { /* get provider metadata from the specified URL with the specified parameters */ if (oidc_http_get(r, url, NULL, NULL, NULL, NULL, oidc_cfg_oauth_ssl_validate_server_get(cfg), response, NULL, NULL, oidc_cfg_http_timeout_short_get(cfg), oidc_cfg_outgoing_proxy_get(cfg), oidc_cfg_dir_pass_cookies_get(r), NULL, NULL, NULL) == FALSE) return FALSE; /* decode and see if it is not an error response somehow */ if (oidc_util_decode_json_and_check_error(r, *response, j_metadata) == FALSE) { oidc_error(r, "JSON parsing of retrieved Discovery document failed"); return FALSE; } /* check to see if it is valid metadata */ // TODO: /* if (oidc_oauth_metadata_provider_is_valid(r, cfg, *j_metadata, issuer) == FALSE) return FALSE; */ /* all OK */ return TRUE; } /* * obtain the OAuth 2.0 configuration settings, possibly by retrieving the metadata document */ static apr_byte_t oidc_oauth_provider_config(request_rec *r, oidc_cfg_t *c) { json_t *j_provider = NULL; char *s_json = NULL; /* see if we should configure a static provider based on external (cached) metadata */ if (oidc_cfg_oauth_metadata_url_get(c) == NULL) return TRUE; oidc_cache_get_oauth_provider(r, oidc_cfg_oauth_metadata_url_get(c), &s_json); if (s_json == NULL) { if (oidc_oauth_metadata_provider_retrieve(r, c, NULL, oidc_cfg_oauth_metadata_url_get(c), &j_provider, &s_json) == FALSE) { oidc_error(r, "could not retrieve metadata from url: %s", oidc_cfg_oauth_metadata_url_get(c)); return FALSE; } oidc_cache_set_oauth_provider(r, oidc_cfg_oauth_metadata_url_get(c), s_json, apr_time_now() + (oidc_cfg_provider_metadata_refresh_interval_get(c) <= 0 ? apr_time_from_sec(OIDC_CACHE_PROVIDER_METADATA_EXPIRY_DEFAULT) : oidc_cfg_provider_metadata_refresh_interval_get(c))); } else { oidc_util_decode_json_object(r, s_json, &j_provider); /* check to see if it is valid metadata */ /* if (oidc_oauth_metadata_provider_is_valid(r, c, j_provider, NULL) == FALSE) { oidc_error(r, "cache corruption detected: invalid metadata from url: %s", c->provider.metadata_url); return FALSE; } */ } if (oidc_oauth_metadata_provider_parse(r, c, j_provider) == FALSE) { oidc_error(r, "could not parse metadata from url: %s", oidc_cfg_oauth_metadata_url_get(c)); if (j_provider) json_decref(j_provider); return FALSE; } json_decref(j_provider); return TRUE; } /* * validate an access token against the validation endpoint of the Authorization server and gets a response back */ static apr_byte_t oidc_oauth_validate_access_token(request_rec *r, oidc_cfg_t *c, const char *token, char **response) { oidc_debug(r, "enter"); char *basic_auth = NULL; char *bearer_auth = NULL; /* assemble parameters to call the token endpoint for validation */ apr_table_t *params = apr_table_make(r->pool, 4); /* add any configured extra static parameters to the introspection endpoint */ oidc_util_table_add_query_encoded_params(r->pool, params, oidc_cfg_oauth_introspection_endpoint_params_get(c)); /* add the access_token itself */ apr_table_addn(params, oidc_cfg_oauth_introspection_token_param_name_get(c), token); const char *bearer_access_token_auth = ((oidc_cfg_oauth_introspection_client_auth_bearer_token_get(c) != NULL) && _oidc_strcmp(oidc_cfg_oauth_introspection_client_auth_bearer_token_get(c), "") == 0) ? token : oidc_cfg_oauth_introspection_client_auth_bearer_token_get(c); /* add the token endpoint authentication credentials */ if (oidc_proto_token_endpoint_auth(r, c, oidc_cfg_oauth_introspection_endpoint_auth_get(c), oidc_cfg_oauth_client_id_get(c), oidc_cfg_oauth_client_secret_get(c), NULL, oidc_cfg_oauth_introspection_endpoint_url_get(c), params, bearer_access_token_auth, &basic_auth, &bearer_auth) == FALSE) return FALSE; /* call the endpoint with the constructed parameter set and return the resulting response */ return oidc_cfg_oauth_introspection_endpoint_method_get(c) == OIDC_INTROSPECTION_METHOD_GET ? oidc_http_get(r, oidc_cfg_oauth_introspection_endpoint_url_get(c), params, basic_auth, bearer_auth, NULL, oidc_cfg_oauth_ssl_validate_server_get(c), response, NULL, NULL, oidc_cfg_http_timeout_long_get(c), oidc_cfg_outgoing_proxy_get(c), oidc_cfg_dir_pass_cookies_get(r), oidc_cfg_oauth_introspection_endpoint_tls_client_cert_get(c), oidc_cfg_oauth_introspection_endpoint_tls_client_key_get(c), oidc_cfg_oauth_introspection_endpoint_tls_client_key_pwd_get(c)) : oidc_http_post_form(r, oidc_cfg_oauth_introspection_endpoint_url_get(c), params, basic_auth, bearer_auth, NULL, oidc_cfg_oauth_ssl_validate_server_get(c), response, NULL, NULL, oidc_cfg_http_timeout_long_get(c), oidc_cfg_outgoing_proxy_get(c), oidc_cfg_dir_pass_cookies_get(r), oidc_cfg_oauth_introspection_endpoint_tls_client_cert_get(c), oidc_cfg_oauth_introspection_endpoint_tls_client_key_get(c), oidc_cfg_oauth_introspection_endpoint_tls_client_key_pwd_get(c)); } /* * get the authorization header that should contain a bearer token */ apr_byte_t oidc_oauth_get_bearer_token(request_rec *r, const char **access_token) { /* get the directory specific setting on how the token can be passed in */ oidc_oauth_accept_token_in_t accept_token_in = oidc_cfg_dir_oauth_accept_token_in_get(r); const char *cookie_name = oidc_cfg_dir_accept_token_in_option_get(r, OIDC_OAUTH_ACCEPT_TOKEN_IN_OPTION_COOKIE_NAME); oidc_debug(r, "accept_token_in=%d", accept_token_in); *access_token = NULL; if (((accept_token_in & OIDC_OAUTH_ACCEPT_TOKEN_IN_HEADER)) || (accept_token_in & OIDC_OAUTH_ACCEPT_TOKEN_IN_BASIC)) { /* get the authorization header */ const char *auth_line = oidc_http_hdr_in_authorization_get(r); if (auth_line) { oidc_debug(r, "authorization header found"); apr_byte_t known_scheme = 0; char *scheme = ap_getword(r->pool, &auth_line, OIDC_CHAR_SPACE); /* look for the Bearer keyword */ if ((_oidc_strnatcasecmp(scheme, OIDC_PROTO_BEARER) == 0) && (accept_token_in & OIDC_OAUTH_ACCEPT_TOKEN_IN_HEADER)) { /* skip any spaces after the Bearer keyword */ while (apr_isspace(*auth_line)) { auth_line++; } /* copy the result in to the access_token */ *access_token = apr_pstrdup(r->pool, auth_line); known_scheme = 1; } else if ((_oidc_strnatcasecmp(scheme, OIDC_PROTO_BASIC) == 0) && (accept_token_in & OIDC_OAUTH_ACCEPT_TOKEN_IN_BASIC)) { char *decoded_line; int decoded_len; if (oidc_util_base64_decode(r->pool, auth_line, &decoded_line, &decoded_len) == NULL) { decoded_line[decoded_len] = '\0'; if (strchr(decoded_line, ':') != NULL) { /* Strip the username and colon and take just the password */ ap_getword_nulls(r->pool, (const char **)&decoded_line, ':'); *access_token = decoded_line; known_scheme = 1; } } } if (known_scheme == 0) { oidc_warn(r, "client used unsupported authentication scheme: %s", scheme); } } } if ((*access_token == NULL) && (r->method_number == M_POST) && (accept_token_in & OIDC_OAUTH_ACCEPT_TOKEN_IN_POST)) { apr_table_t *params = apr_table_make(r->pool, 8); if (oidc_util_read_post_params(r, params, TRUE, OIDC_PROTO_ACCESS_TOKEN) == TRUE) { *access_token = apr_table_get(params, OIDC_PROTO_ACCESS_TOKEN); } } if ((*access_token == NULL) && (accept_token_in & OIDC_OAUTH_ACCEPT_TOKEN_IN_QUERY)) { apr_table_t *params = apr_table_make(r->pool, 8); oidc_util_read_form_encoded_params(r, params, r->args); *access_token = apr_table_get(params, OIDC_PROTO_ACCESS_TOKEN); } if ((*access_token == NULL) && (accept_token_in & OIDC_OAUTH_ACCEPT_TOKEN_IN_COOKIE)) { const char *auth_line = oidc_http_get_cookie(r, cookie_name); if (auth_line != NULL) { /* copy the result in to the access_token */ *access_token = apr_pstrdup(r->pool, auth_line); } else { oidc_warn(r, "no cookie found with name: %s", cookie_name); } } if (*access_token == NULL) { oidc_debug(r, "no bearer token found in the allowed methods: %s", oidc_cfg_dir_accept_oauth_token_in2str(r->pool, accept_token_in)); return FALSE; } /* log some stuff */ oidc_debug(r, "bearer token: %s", *access_token); return TRUE; } /* * parse (custom/configurable) token expiry claim in introspection result */ static apr_byte_t oidc_oauth_parse_and_cache_token_expiry(request_rec *r, oidc_cfg_t *c, json_t *introspection_response, const char *expiry_claim_name, int expiry_format_absolute, int expiry_claim_is_mandatory, apr_time_t *cache_until) { oidc_debug(r, "expiry_claim_name=%s, expiry_format_absolute=%d, expiry_claim_is_mandatory=%d", expiry_claim_name, expiry_format_absolute, expiry_claim_is_mandatory); json_t *expiry = json_object_get(introspection_response, expiry_claim_name); if (expiry == NULL) { if (expiry_claim_is_mandatory) { oidc_error(r, "introspection response JSON object did not contain an \"%s\" claim", expiry_claim_name); return FALSE; } return TRUE; } if (!json_is_integer(expiry)) { if (expiry_claim_is_mandatory) { oidc_error( r, "introspection response JSON object contains a \"%s\" claim but it is not a JSON integer", expiry_claim_name); return FALSE; } oidc_warn(r, "introspection response JSON object contains a \"%s\" claim that is not an (optional) JSON " "integer: the introspection result will NOT be cached", expiry_claim_name); return TRUE; } json_int_t value = json_integer_value(expiry); if (value <= 0) { oidc_warn(r, "introspection response JSON object integer number value <= 0 (%ld); introspection result " "will not be cached", (long)value); return TRUE; } *cache_until = apr_time_from_sec(value); if (expiry_format_absolute == FALSE) (*cache_until) += apr_time_now(); return TRUE; } #define OIDC_OAUTH_CACHE_KEY_RESPONSE "r" #define OIDC_OAUTH_CACHE_KEY_TIMESTAMP "t" /* * cache the OAuth 2.0 introspection results for the specified access token */ static apr_byte_t oidc_oauth_cache_access_token(request_rec *r, oidc_cfg_t *c, apr_time_t cache_until, const char *access_token, json_t *json) { /* no cache mode */ int token_introspection_interval = oidc_cfg_dir_token_introspection_interval_get(r); if (token_introspection_interval == -1) { oidc_debug(r, "not caching introspection result"); return TRUE; } oidc_debug(r, "caching introspection result"); json_t *cache_entry = json_object(); json_object_set(cache_entry, OIDC_OAUTH_CACHE_KEY_RESPONSE, json); json_object_set_new(cache_entry, OIDC_OAUTH_CACHE_KEY_TIMESTAMP, json_integer(apr_time_sec(apr_time_now()))); char *cache_value = oidc_util_encode_json(r->pool, cache_entry, JSON_PRESERVE_ORDER | JSON_COMPACT); /* set it in the cache so subsequent request don't need to validate the access_token and get the claims anymore */ oidc_cache_set_access_token(r, access_token, cache_value, cache_until); json_decref(cache_entry); return TRUE; } /* * retrieve the OAuth 2.0 introspection results from the cache, for a previously introspected access token */ static apr_byte_t oidc_oauth_get_cached_access_token(request_rec *r, oidc_cfg_t *c, const char *access_token, json_t **json) { json_t *cache_entry = NULL; char *s_cache_entry = NULL; /* no cache mode */ int token_introspection_interval = oidc_cfg_dir_token_introspection_interval_get(r); if (token_introspection_interval == -1) { return FALSE; } /* see if we've got the claims for this access_token cached already */ oidc_cache_get_access_token(r, access_token, &s_cache_entry); if (s_cache_entry == NULL) return FALSE; /* json decode the cache entry */ if (oidc_util_decode_json_object(r, s_cache_entry, &cache_entry) == FALSE) { *json = NULL; return FALSE; } /* compare the timestamp against the freshness requirement */ json_t *v = json_object_get(cache_entry, OIDC_OAUTH_CACHE_KEY_TIMESTAMP); apr_time_t now = apr_time_sec(apr_time_now()); if ((token_introspection_interval > 0) && (now > json_integer_value(v) + token_introspection_interval)) { /* printout info about the event */ char buf[APR_RFC822_DATE_LEN + 1]; apr_rfc822_date(buf, apr_time_from_sec(json_integer_value(v))); oidc_debug(r, "token that was validated/cached at: [%s], does not meet token freshness requirement: %d)", buf, token_introspection_interval); /* invalidate the cache entry */ *json = NULL; json_decref(cache_entry); return FALSE; } oidc_debug(r, "returning cached introspection result that meets freshness requirements: %s", s_cache_entry); /* we've got a cached introspection result that is still valid for this path's requirements */ *json = json_deep_copy(json_object_get(cache_entry, OIDC_OAUTH_CACHE_KEY_RESPONSE)); json_decref(cache_entry); return TRUE; } /* * resolve and validate an access_token against the configured Authorization Server */ static apr_byte_t oidc_oauth_resolve_access_token(request_rec *r, oidc_cfg_t *c, const char *access_token, json_t **token, char **response) { json_t *result = NULL; /* see if we've got the claims for this access_token cached already */ oidc_oauth_get_cached_access_token(r, c, access_token, &result); if (result == NULL) { char *s_json = NULL; /* not cached, go out and validate the access_token against the Authorization server and get the JSON * claims back */ if (oidc_oauth_validate_access_token(r, c, access_token, &s_json) == FALSE) { oidc_error(r, "could not get a validation response from the Authorization server"); return FALSE; } /* decode and see if it is not an error response somehow */ if (oidc_util_decode_json_and_check_error(r, s_json, &result) == FALSE) return FALSE; json_t *active = json_object_get(result, OIDC_PROTO_ACTIVE); apr_time_t cache_until = apr_time_now() + apr_time_from_sec(60); if (active != NULL) { if (json_is_boolean(active)) { if (!json_is_true(active)) { oidc_debug( r, "\"%s\" boolean object with value \"false\" found in response JSON object", OIDC_PROTO_ACTIVE); json_decref(result); return FALSE; } } else if (json_is_string(active)) { if (_oidc_strnatcasecmp(json_string_value(active), "true") != 0) { oidc_debug(r, "\"%s\" string object with value that is not equal to \"true\" " "found in response JSON object: %s", OIDC_PROTO_ACTIVE, json_string_value(active)); json_decref(result); return FALSE; } } else { oidc_debug(r, "no \"%s\" boolean or string object found in response JSON object", OIDC_PROTO_ACTIVE); json_decref(result); return FALSE; } if (oidc_oauth_parse_and_cache_token_expiry(r, c, result, OIDC_CLAIM_EXP, TRUE, FALSE, &cache_until) == FALSE) { json_decref(result); return FALSE; } /* set it in the cache so subsequent request don't need to validate the access_token and get the * claims anymore */ oidc_oauth_cache_access_token(r, c, cache_until, access_token, result); } else { if (oidc_oauth_parse_and_cache_token_expiry( r, c, result, oidc_cfg_oauth_introspection_token_expiry_claim_name_get(c), oidc_cfg_oauth_introspection_token_expiry_claim_format_get(c) == OIDC_TOKEN_EXPIRY_CLAIM_FORMAT_ABSOLUTE, oidc_cfg_oauth_introspection_token_expiry_claim_required_get(c) == OIDC_TOKEN_EXPIRY_CLAIM_REQUIRED_MANDATORY, &cache_until) == FALSE) { json_decref(result); return FALSE; } /* set it in the cache so subsequent request don't need to validate the access_token and get the * claims anymore */ oidc_oauth_cache_access_token(r, c, cache_until, access_token, result); } } /* return the access_token JSON object */ json_t *tkn = json_object_get(result, OIDC_PROTO_ACCESS_TOKEN); if ((tkn != NULL) && (json_is_object(tkn))) { /* * assume PingFederate validation: copy over those claims from the access_token * that are relevant for authorization purposes */ json_object_set(tkn, OIDC_PROTO_CLIENT_ID, json_object_get(result, OIDC_PROTO_CLIENT_ID)); json_object_set(tkn, OIDC_PROTO_SCOPE, json_object_get(result, OIDC_PROTO_SCOPE)); // oidc_oauth_spaced_string_to_array(r, result, OIDC_PROTO_SCOPE, tkn, "scopes"); /* return only the pimped access_token results */ *token = json_deep_copy(tkn); json_decref(result); } else { // oidc_oauth_spaced_string_to_array(r, result, OIDC_PROTO_SCOPE, result, "scopes"); /* assume spec compliant introspection */ *token = result; } /* stringify the response */ *response = oidc_util_encode_json(r->pool, *token, JSON_PRESERVE_ORDER | JSON_COMPACT); return TRUE; } /* * validate a JWT access token (locally) * * TODO: document that we're reusing the following settings from the OIDC config section: * - JWKs URI refresh interval * - decryption key material (OIDCPrivateKeyFiles) * * OIDCOAuthRemoteUserClaim client_id * # 32x 61 hex * OIDCOAuthVerifySharedKeys aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa */ static apr_byte_t oidc_oauth_validate_jwt_access_token(request_rec *r, oidc_cfg_t *c, const char *access_token, json_t **token, char **response) { oidc_debug(r, "enter: JWT access_token header=%s", oidc_proto_jwt_header_peek(r, access_token, NULL, NULL, NULL)); oidc_jose_error_t err; oidc_jwk_t *jwk = NULL; // TODO: replace this OIDC client secret with OIDCOAuthDecryptSharedKeys if (oidc_util_create_symmetric_key(r, oidc_cfg_provider_client_secret_get(oidc_cfg_provider_get(c)), 0, NULL, TRUE, &jwk) == FALSE) return FALSE; oidc_jwt_t *jwt = NULL; if (oidc_jwt_parse(r->pool, access_token, &jwt, oidc_util_merge_symmetric_key(r->pool, oidc_cfg_private_keys_get(c), jwk), FALSE, &err) == FALSE) { oidc_error(r, "could not parse JWT from access_token: %s", oidc_jose_e2s(r->pool, err)); oidc_jwk_destroy(jwk); return FALSE; } oidc_jwk_destroy(jwk); oidc_debug(r, "successfully parsed JWT with header: %s", jwt->header.value.str); /* * validate the access token JWT by validating the (optional) exp claim * don't enforce anything around iat since it doesn't make much sense for access tokens */ if (oidc_proto_jwt_validate(r, jwt, NULL, FALSE, FALSE, -1) == FALSE) { oidc_jwt_destroy(jwt); return FALSE; } oidc_debug( r, "verify JWT against %d statically configured public keys and %d shared keys, with JWKs URI set to %s", oidc_cfg_oauth_verify_public_keys_get(c) ? oidc_cfg_oauth_verify_public_keys_get(c)->nelts : 0, oidc_cfg_oauth_verify_shared_keys_get(c) ? apr_hash_count(oidc_cfg_oauth_verify_shared_keys_get(c)) : 0, oidc_cfg_oauth_verify_jwks_uri_get(c)); // TODO: we're re-using the OIDC provider JWKs refresh interval here... oidc_jwks_uri_t jwks_uri = {(char *)oidc_cfg_oauth_verify_jwks_uri_get(c), oidc_cfg_provider_userinfo_refresh_interval_get(oidc_cfg_provider_get(c)), NULL, NULL}; if (oidc_proto_jwt_verify(r, c, jwt, &jwks_uri, oidc_cfg_oauth_ssl_validate_server_get(c), oidc_util_merge_key_sets(r->pool, oidc_cfg_oauth_verify_shared_keys_get(c), oidc_cfg_oauth_verify_public_keys_get(c)), NULL) == FALSE) { oidc_error(r, "JWT access token signature could not be validated, aborting"); oidc_jwt_destroy(jwt); return FALSE; } oidc_debug(r, "successfully verified JWT access token: %s", jwt->payload.value.str); *token = json_deep_copy(jwt->payload.value.json); *response = jwt->payload.value.str; oidc_jwt_destroy(jwt); return TRUE; } /* * set the unique user identifier that will be propagated in the Apache r->user and REMOTE_USER variables */ static apr_byte_t oidc_oauth_set_request_user(request_rec *r, oidc_cfg_t *c, json_t *token) { char *remote_user = NULL; if (oidc_get_remote_user(r, oidc_cfg_oauth_remote_user_claim_name_get(c), oidc_cfg_oauth_remote_user_claim_get(c)->reg_exp, oidc_cfg_oauth_remote_user_claim_get(c)->replace, token, &remote_user) == FALSE) { oidc_error( r, "" OIDCOAuthRemoteUserClaim " is set to \"%s\", but could not set the remote user based the available claims for the user", oidc_cfg_oauth_remote_user_claim_name_get(c)); return FALSE; } r->user = apr_pstrdup(r->pool, remote_user); oidc_debug(r, "set user to \"%s\" based on claim: \"%s\"%s", r->user, oidc_cfg_oauth_remote_user_claim_name_get(c), oidc_cfg_oauth_remote_user_claim_get(c)->reg_exp ? apr_psprintf(r->pool, " and expression: \"%s\" and replace string: \"%s\"", oidc_cfg_oauth_remote_user_claim_get(c)->reg_exp, oidc_cfg_oauth_remote_user_claim_get(c)->replace) : ""); return TRUE; } /* * main routine: handle OAuth 2.0 authentication/authorization */ int oidc_oauth_check_userid(request_rec *r, oidc_cfg_t *c, const char *access_token) { /* check if this is a sub-request or an initial request */ if (!ap_is_initial_req(r)) { if (r->main != NULL) r->user = r->main->user; else if (r->prev != NULL) r->user = r->prev->user; if (r->user != NULL) { /* this is a sub-request and we have a session */ oidc_debug(r, "recycling user '%s' from initial request for sub-request", r->user); /* strip any cookies that we need to */ oidc_strip_cookies(r); return OK; } /* check if this is a request to the "special" handler (Redirect URI) */ } else if (oidc_util_request_matches_url(r, oidc_util_redirect_uri(r, c))) { /* check if this is a request for the public (encryption) keys */ if (oidc_util_request_has_parameter(r, OIDC_REDIRECT_URI_REQUEST_JWKS)) { OIDC_METRICS_COUNTER_INC(r, c, OM_REDIRECT_URI_REQUEST_JWKS); /* * Will be handled in the content handler; avoid: * No authentication done but request not allowed without authentication * by setting r->user */ r->user = ""; return OK; /* check if this is a request to remove the access token from the cache */ } else if (oidc_util_request_has_parameter(r, OIDC_REDIRECT_URI_REQUEST_REMOVE_AT_CACHE)) { /* handle request to invalidate access token cache */ return oidc_revoke_at_cache_remove(r, c); } } /* we don't have a session yet */ /* obtain/refresh metadata from OAuth metadata document URL if configured */ oidc_oauth_provider_config(r, c); /* get the bearer access token from the Authorization header */ if (access_token == NULL) { if (oidc_oauth_get_bearer_token(r, &access_token) == FALSE) { if (r->method_number == M_OPTIONS) { r->user = ""; return OK; } return oidc_proto_return_www_authenticate(r, OIDC_PROTO_ERR_INVALID_REQUEST, "No bearer token found in the request"); } } oidc_util_set_trace_parent(r, c, access_token); /* validate the obtained access token against the OAuth AS validation endpoint */ json_t *token = NULL; char *s_token = NULL; /* check if an introspection endpoint is set */ if (oidc_cfg_oauth_introspection_endpoint_url_get(c) != NULL) { /* we'll validate the token remotely */ if (oidc_oauth_resolve_access_token(r, c, access_token, &token, &s_token) == FALSE) return oidc_proto_return_www_authenticate(r, OIDC_PROTO_ERR_INVALID_TOKEN, "Reference token could not be introspected"); } else { /* no introspection endpoint is set, assume the token is a JWT and validate it locally */ if (oidc_oauth_validate_jwt_access_token(r, c, access_token, &token, &s_token) == FALSE) return oidc_proto_return_www_authenticate(r, OIDC_PROTO_ERR_INVALID_TOKEN, "JWT token could not be validated"); } /* check that we've got something back */ if (token == NULL) { oidc_error(r, "could not resolve claims (token == NULL)"); return oidc_proto_return_www_authenticate(r, OIDC_PROTO_ERR_INVALID_TOKEN, "No claims could be parsed from the token"); } /* store the parsed token (cq. the claims from the response) in the request state so it can be accessed by the * authz routines */ oidc_request_state_set(r, OIDC_REQUEST_STATE_KEY_CLAIMS, (const char *)s_token); /* set the request user */ if (oidc_oauth_set_request_user(r, c, token) == FALSE) { json_decref(token); oidc_error(r, "remote user could not be set, aborting with HTTP_UNAUTHORIZED"); return oidc_proto_return_www_authenticate(r, OIDC_PROTO_ERR_INVALID_TOKEN, "Could not set remote user"); } /* * we're going to pass the information that we have to the application, * but first we need to scrub the headers that we're going to use for security reasons */ oidc_scrub_headers(r); /* set the user authentication HTTP header if set and required */ const char *authn_header = oidc_cfg_dir_authn_header_get(r); oidc_appinfo_pass_in_t pass_in = oidc_cfg_dir_pass_info_in_get(r); oidc_appinfo_encoding_t encoding = oidc_cfg_dir_pass_info_encoding_get(r); if ((r->user != NULL) && (authn_header != NULL)) oidc_http_hdr_in_set(r, authn_header, r->user); /* set the resolved claims in the HTTP headers for the target application */ oidc_util_set_app_infos(r, token, oidc_cfg_claim_prefix_get(c), oidc_cfg_claim_delimiter_get(c), pass_in, encoding); /* set the access_token in the app headers */ if (access_token != NULL) { oidc_util_set_app_info(r, OIDC_APP_INFO_ACCESS_TOKEN, access_token, OIDC_DEFAULT_HEADER_PREFIX, pass_in, encoding); } /* free JSON resources */ json_decref(token); /* strip any cookies that we need to */ oidc_strip_cookies(r); return OK; } mod_auth_openidc-2.4.16.10/src/oauth.h000066400000000000000000000044151476721736500174320ustar00rootroot00000000000000/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ /*************************************************************************** * Copyright (C) 2017-2025 ZmartZone Holding BV * All rights reserved. * * DISCLAIMER OF WARRANTIES: * * THE SOFTWARE PROVIDED HEREUNDER IS PROVIDED ON AN "AS IS" BASIS, WITHOUT * ANY WARRANTIES OR REPRESENTATIONS EXPRESS, IMPLIED OR STATUTORY; INCLUDING, * WITHOUT LIMITATION, WARRANTIES OF QUALITY, PERFORMANCE, NONINFRINGEMENT, * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. NOR ARE THERE ANY * WARRANTIES CREATED BY A COURSE OR DEALING, COURSE OF PERFORMANCE OR TRADE * USAGE. FURTHERMORE, THERE ARE NO WARRANTIES THAT THE SOFTWARE WILL MEET * YOUR NEEDS OR BE FREE FROM ERRORS, OR THAT THE OPERATION OF THE SOFTWARE * WILL BE UNINTERRUPTED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * @Author: Hans Zandbelt - hans.zandbelt@openidc.com */ #ifndef _MOD_AUTH_OPENIDC_OAUTH_H_ #define _MOD_AUTH_OPENIDC_OAUTH_H_ #include "cfg/cfg.h" int oidc_oauth_check_userid(request_rec *r, oidc_cfg_t *c, const char *access_token); apr_byte_t oidc_oauth_get_bearer_token(request_rec *r, const char **access_token); #endif /* _MOD_AUTH_OPENIDC_OAUTH_H_ */ mod_auth_openidc-2.4.16.10/src/pcre_subst.c000066400000000000000000000236111476721736500204550ustar00rootroot00000000000000/************************************************* * PCRE string replacement * *************************************************/ /* PCRE is a library of functions to support regular expressions whose syntax and semantics are as close as possible to those of the Perl 5 language. pcre_subst is a wrapper around pcre_exec designed to make it easier to perform PERL style replacements with PCRE. Written by: Bert Driehuis Copyright (c) 2000 Bert Driehuis ----------------------------------------------------------------------------- Permission is granted to anyone to use this software for any purpose on any computer system, and to redistribute it freely, subject to the following restrictions: 1. This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 2. The origin of this software must not be misrepresented, either by explicit claim or by omission. 3. Altered versions must be plainly marked as such, and must not be misrepresented as being the original software. 4. If PCRE is embedded in any software that is released under the GNU General Purpose Licence (GPL), then the terms of that licence shall supersede any condition above with which it is incompatible. */ #include #include #include "pcre_subst.h" #ifdef HAVE_LIBPCRE2 #define PCRE2_CODE_UNIT_WIDTH 8 #include #else #include #endif /* * gcc -DDEBUG_BUILD=1 -DDEBUG_PCRE_SUBST=1 -I/opt/local/include/apr-1 -I/opt/local/include -o pcre_subst * src/pcre_subst.c -L/opt/local/lib -lpcre -lapr-1 */ struct oidc_pcre { #ifdef HAVE_LIBPCRE2 pcre2_code *preg; pcre2_match_data *match_data; #else int subStr[OIDC_UTIL_REGEXP_MATCH_SIZE]; pcre *preg; #endif }; #ifndef HAVE_LIBPCRE2 #ifdef DEBUG_PCRE_SUBST static void dumpstr(const char *str, int len, int start, int end) { int i; for (i = 0; i < _oidc_strlen(str); i++) { if (i >= start && i < end) putchar(str[i]); else putchar('-'); } putchar('\n'); } static void dumpmatch(const char *str, int len, const char *rep, int nmat, const int *ovec) { int i; printf("%s Input\n", str); printf("nmat=%d", nmat); for (i = 0; i < nmat * 2; i++) printf(" %d", ovec[i]); printf("\n"); for (i = 0; i < nmat * 2; i += 2) dumpstr(str, len, ovec[i], ovec[i + 1]); printf("\n"); } #endif static int findreplen(const char *rep, int nmat, const int *replen) { int len = 0; int val; char *cp = (char *)rep; while (*cp) { if (*cp == '$' && isdigit(cp[1])) { val = strtoul(&cp[1], &cp, 10); if (val && val <= nmat + 1) len += replen[val - 1]; else fprintf(stderr, "repl %d out of range\n", val); } else { cp++; len++; } } return len; } static void doreplace(char *out, const char *rep, int nmat, int *replen, const char **repstr) { int val; char *cp = (char *)rep; if ((out == NULL) || (replen == NULL) || (repstr == NULL)) return; while (*cp) { if (*cp == '$' && isdigit(cp[1])) { val = strtoul(&cp[1], &cp, 10); if (val && val <= nmat + 1) { strncpy(out, repstr[val - 1], replen[val - 1]); out += replen[val - 1]; } } else { *out++ = *cp++; } } } static char *edit(const char *str, int len, const char *rep, int nmat, const int *ovec) { int i, slen, rlen; const int *mvec = ovec; char *res, *cp; int replen[OIDC_PCRE_MAXCAPTURE]; const char *repstr[OIDC_PCRE_MAXCAPTURE]; _oidc_memset(repstr, '\0', OIDC_PCRE_MAXCAPTURE); if ((str == NULL) || (mvec == NULL)) return NULL; nmat--; ovec += 2; for (i = 0; i < nmat; i++) { replen[i] = ovec[i * 2 + 1] - ovec[i * 2]; repstr[i] = &str[ovec[i * 2]]; #ifdef DEBUG_PCRE_SUBST printf(">>>%d %d %.*s\n", i, replen[i], replen[i], repstr[i]); #endif } slen = len; len -= mvec[1] - mvec[0]; len += rlen = findreplen(rep, nmat, replen); #ifdef DEBUG_PCRE_SUBST printf("resulting length %d (srclen=%d)\n", len, slen); #endif cp = res = pcre_malloc(len + 1); if (cp == NULL) return NULL; if (mvec[0] > 0) { strncpy(cp, str, mvec[0]); cp += mvec[0]; } doreplace(cp, rep, nmat, replen, repstr); cp += rlen; if ((mvec[1] < slen) && (cp != NULL)) _oidc_strcpy(cp, &str[mvec[1]]); res[len] = 0; return res; } char *pcre_subst(const pcre *ppat, const pcre_extra *extra, const char *str, int len, int offset, int options, const char *rep) { int nmat; int ovec[OIDC_PCRE_MAXCAPTURE * 3]; nmat = pcre_exec(ppat, extra, str, len, offset, options, ovec, OIDC_PCRE_MAXCAPTURE * 3); #ifdef DEBUG_PCRE_SUBST dumpmatch(str, len, rep, nmat, ovec); #endif if (nmat <= 0) return NULL; return (edit(str, len, rep, nmat, ovec)); } #endif char *oidc_pcre_subst(apr_pool_t *pool, const struct oidc_pcre *pcre, const char *str, int len, const char *rep) { char *rv = NULL; #ifdef HAVE_LIBPCRE2 PCRE2_UCHAR *output = (PCRE2_UCHAR *)malloc(sizeof(PCRE2_UCHAR) * OIDC_PCRE_MAXCAPTURE * 3); PCRE2_SIZE outlen = OIDC_PCRE_MAXCAPTURE * 3; PCRE2_SPTR subject = (PCRE2_SPTR)str; PCRE2_SIZE length = (PCRE2_SIZE)len; PCRE2_SPTR replacement = (PCRE2_SPTR)rep; if (pcre2_substitute(pcre->preg, subject, length, 0, PCRE2_SUBSTITUTE_GLOBAL, 0, 0, replacement, PCRE2_ZERO_TERMINATED, output, &outlen) > 0) rv = apr_pstrdup(pool, (const char *)output); free(output); #else char *substituted = NULL; substituted = pcre_subst(pcre->preg, 0, str, len, 0, 0, rep); rv = apr_pstrdup(pool, substituted); pcre_free(substituted); #endif return rv; } struct oidc_pcre *oidc_pcre_compile(apr_pool_t *pool, const char *regexp, char **error_str) { struct oidc_pcre *pcre = NULL; if (regexp == NULL) return NULL; pcre = apr_pcalloc(pool, sizeof(struct oidc_pcre)); #ifdef HAVE_LIBPCRE2 int errorcode; PCRE2_SIZE erroroffset; pcre->preg = pcre2_compile((PCRE2_SPTR)regexp, (PCRE2_SIZE)_oidc_strlen(regexp), 0, &errorcode, &erroroffset, NULL); #else const char *errorptr = NULL; int erroffset; pcre->preg = pcre_compile(regexp, 0, &errorptr, &erroffset, NULL); #endif if (pcre->preg == NULL) { *error_str = apr_psprintf(pool, "pattern [%s] is not a valid regular expression", regexp); pcre = NULL; } return pcre; } void oidc_pcre_free(struct oidc_pcre *pcre) { #ifdef HAVE_LIBPCRE2 if (pcre->match_data) pcre2_match_data_free(pcre->match_data); if (pcre->preg) pcre2_code_free(pcre->preg); #else pcre_free(pcre->preg); #endif } void oidc_pcre_free_match(struct oidc_pcre *pcre) { #ifdef HAVE_LIBPCRE2 if (pcre->match_data) { pcre2_match_data_free(pcre->match_data); pcre->match_data = NULL; } #endif } int oidc_pcre_get_substring(apr_pool_t *pool, const struct oidc_pcre *pcre, const char *input, int rc, char **sub_str, char **error_str) { int rv = 0; #ifdef HAVE_LIBPCRE2 PCRE2_UCHAR *buf = NULL; PCRE2_SIZE buflen = 0; if ((rv = pcre2_substring_get_bynumber(pcre->match_data, OIDC_UTIL_REGEXP_MATCH_NR, &buf, &buflen)) < 0) { switch (rc) { case PCRE2_ERROR_NOSUBSTRING: *error_str = apr_psprintf(pool, "there are no groups of that number"); break; case PCRE2_ERROR_UNAVAILABLE: *error_str = apr_psprintf(pool, "the ovector was too small for that group"); break; case PCRE2_ERROR_UNSET: *error_str = apr_psprintf(pool, "the group did not participate in the match"); break; case PCRE2_ERROR_NOMEMORY: *error_str = apr_psprintf(pool, "memory could not be obtained"); break; default: *error_str = apr_psprintf(pool, "pcre2_substring_get_bynumber failed (rv=%d)", rv); break; } } else { *sub_str = apr_pstrndup(pool, (const char *)buf, buflen); pcre2_substring_free(buf); rv = 1; } #else const char *buf = NULL; if ((rv = pcre_get_substring(input, (int *)pcre->subStr, rc, OIDC_UTIL_REGEXP_MATCH_NR, &buf)) <= 0) { *error_str = apr_psprintf(pool, "pcre_get_substring failed (rv=%d)", rv); } else { *sub_str = apr_pstrdup(pool, buf); pcre_free_substring(buf); } #endif return rv; } int oidc_pcre_exec(apr_pool_t *pool, struct oidc_pcre *pcre, const char *input, int len, char **error_str) { int rc = 0; #ifdef HAVE_LIBPCRE2 pcre->match_data = pcre2_match_data_create_from_pattern(pcre->preg, NULL); if ((rc = pcre2_match(pcre->preg, (PCRE2_SPTR)input, (PCRE2_SIZE)len, 0, 0, pcre->match_data, NULL)) < 0) { switch (rc) { case PCRE2_ERROR_NOMATCH: *error_str = apr_pstrdup(pool, "string did not match the pattern"); break; default: *error_str = apr_psprintf(pool, "unknown error: %d", rc); break; } } #else if ((rc = pcre_exec(pcre->preg, NULL, input, len, 0, 0, pcre->subStr, OIDC_UTIL_REGEXP_MATCH_SIZE)) < 0) { switch (rc) { case PCRE_ERROR_NOMATCH: *error_str = apr_pstrdup(pool, "string did not match the pattern"); break; case PCRE_ERROR_NULL: *error_str = apr_pstrdup(pool, "something was null"); break; case PCRE_ERROR_BADOPTION: *error_str = apr_pstrdup(pool, "a bad option was passed"); break; case PCRE_ERROR_BADMAGIC: *error_str = apr_pstrdup(pool, "magic number bad (compiled re corrupt?)"); break; case PCRE_ERROR_UNKNOWN_NODE: *error_str = apr_pstrdup(pool, "something kooky in the compiled re"); break; case PCRE_ERROR_NOMEMORY: *error_str = apr_pstrdup(pool, "ran out of memory"); break; default: *error_str = apr_psprintf(pool, "unknown error: %d", rc); break; } } #endif return rc; } #ifndef HAVE_LIBPCRE2 #ifdef DEBUG_BUILD int main() { char *pat = "quick\\s(\\w+)\\s(fox)"; char *rep = "$1ish $2"; char *str = "The quick brown foxy"; char *newstr; const char *err; int erroffset; pcre_extra *extra; pcre *ppat = pcre_compile(pat, 0, &err, &erroffset, NULL); if (ppat == NULL) { fprintf(stderr, "%s at %d\n", err, erroffset); exit(1); } extra = pcre_study(ppat, 0, &err); if (err != NULL) fprintf(stderr, "Study %s failed: %s\n", pat, err); newstr = pcre_subst(ppat, extra, str, _oidc_strlen(str), 0, 0, rep); if (newstr) { printf("Newstr\t%s\n", newstr); pcre_free(newstr); } else { printf("No match\n"); } pcre_free(extra); pcre_free(ppat); return 0; } #endif #endif mod_auth_openidc-2.4.16.10/src/pcre_subst.h000066400000000000000000000042341476721736500204620ustar00rootroot00000000000000/************************************************* * PCRE string replacement * *************************************************/ /* PCRE is a library of functions to support regular expressions whose syntax and semantics are as close as possible to those of the Perl 5 language. pcre_subst is a wrapper around pcre_exec designed to make it easier to perform PERL style replacements with PCRE. Written by: Bert Driehuis Copyright (c) 2000 Bert Driehuis ----------------------------------------------------------------------------- Permission is granted to anyone to use this software for any purpose on any computer system, and to redistribute it freely, subject to the following restrictions: 1. This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 2. The origin of this software must not be misrepresented, either by explicit claim or by omission. 3. Altered versions must be plainly marked as such, and must not be misrepresented as being the original software. 4. If PCRE is embedded in any software that is released under the GNU General Purpose Licence (GPL), then the terms of that licence shall supersede any condition above with which it is incompatible. */ #ifndef _MOD_AUTH_OPENIDC_PCRE_SUBST_H_ #define _MOD_AUTH_OPENIDC_PCRE_SUBST_H_ #include "const.h" #include #include #define OIDC_PCRE_MAXCAPTURE 255 #define OIDC_UTIL_REGEXP_MATCH_SIZE 30 #define OIDC_UTIL_REGEXP_MATCH_NR 1 struct oidc_pcre; struct oidc_pcre *oidc_pcre_compile(apr_pool_t *pool, const char *regexp, char **error_str); char *oidc_pcre_subst(apr_pool_t *pool, const struct oidc_pcre *, const char *, int, const char *); int oidc_pcre_exec(apr_pool_t *, struct oidc_pcre *, const char *, int, char **); void oidc_pcre_free(struct oidc_pcre *); void oidc_pcre_free_match(struct oidc_pcre *); int oidc_pcre_get_substring(apr_pool_t *pool, const struct oidc_pcre *, const char *input, int rc, char **sub_str, char **error_str); #endif /* _MOD_AUTH_OPENIDC_PCRE_SUBST_H_ */ mod_auth_openidc-2.4.16.10/src/proto/000077500000000000000000000000001476721736500173005ustar00rootroot00000000000000mod_auth_openidc-2.4.16.10/src/proto/.gitignore000066400000000000000000000000461476721736500212700ustar00rootroot00000000000000/.deps/ /.libs/ /*.lo /*.o /.dirstamp mod_auth_openidc-2.4.16.10/src/proto/auth.c000066400000000000000000000251511476721736500204110ustar00rootroot00000000000000/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ /*************************************************************************** * Copyright (C) 2017-2025 ZmartZone Holding BV * All rights reserved. * * DISCLAIMER OF WARRANTIES: * * THE SOFTWARE PROVIDED HEREUNDER IS PROVIDED ON AN "AS IS" BASIS, WITHOUT * ANY WARRANTIES OR REPRESENTATIONS EXPRESS, IMPLIED OR STATUTORY; INCLUDING, * WITHOUT LIMITATION, WARRANTIES OF QUALITY, PERFORMANCE, NONINFRINGEMENT, * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. NOR ARE THERE ANY * WARRANTIES CREATED BY A COURSE OR DEALING, COURSE OF PERFORMANCE OR TRADE * USAGE. FURTHERMORE, THERE ARE NO WARRANTIES THAT THE SOFTWARE WILL MEET * YOUR NEEDS OR BE FREE FROM ERRORS, OR THAT THE OPERATION OF THE SOFTWARE * WILL BE UNINTERRUPTED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * @Author: Hans Zandbelt - hans.zandbelt@openidc.com */ #include "cfg/parse.h" #include "mod_auth_openidc.h" #include "proto/proto.h" #include "util.h" /* * setup for an endpoint call without authentication */ static apr_byte_t oidc_proto_endpoint_auth_none(request_rec *r, const char *client_id, apr_table_t *params) { apr_table_set(params, OIDC_PROTO_CLIENT_ID, client_id); return TRUE; } /* * setup for an endpoint call with OIDC client_secret_basic authentication */ static apr_byte_t oidc_proto_endpoint_client_secret_basic(request_rec *r, const char *client_id, const char *client_secret, char **basic_auth_str) { oidc_debug(r, "enter"); if (client_secret == NULL) { oidc_error(r, "no client secret is configured"); return FALSE; } *basic_auth_str = apr_psprintf(r->pool, "%s:%s", oidc_http_url_encode(r, client_id), oidc_http_url_encode(r, client_secret)); return TRUE; } /* * setup for an endpoint call with OIDC client_secret_post authentication */ static apr_byte_t oidc_proto_endpoint_client_secret_post(request_rec *r, const char *client_id, const char *client_secret, apr_table_t *params) { oidc_debug(r, "enter"); if (client_secret == NULL) { oidc_error(r, "no client secret is configured"); return FALSE; } apr_table_set(params, OIDC_PROTO_CLIENT_ID, client_id); apr_table_set(params, OIDC_PROTO_CLIENT_SECRET, client_secret); return TRUE; } /* * helper function to create a JWT assertion for endpoint authentication */ static apr_byte_t oidc_proto_jwt_create(request_rec *r, const char *client_id, const char *audience, oidc_jwt_t **out) { *out = oidc_jwt_new(r->pool, TRUE, TRUE); oidc_jwt_t *jwt = *out; char *jti = NULL; oidc_util_generate_random_string(r, &jti, OIDC_PROTO_JWT_JTI_LEN); json_object_set_new(jwt->payload.value.json, OIDC_CLAIM_ISS, json_string(client_id)); json_object_set_new(jwt->payload.value.json, OIDC_CLAIM_SUB, json_string(client_id)); json_object_set_new(jwt->payload.value.json, OIDC_CLAIM_AUD, json_string(audience)); json_object_set_new(jwt->payload.value.json, OIDC_CLAIM_JTI, json_string(jti)); json_object_set_new(jwt->payload.value.json, OIDC_CLAIM_EXP, json_integer(apr_time_sec(apr_time_now()) + 60)); json_object_set_new(jwt->payload.value.json, OIDC_CLAIM_IAT, json_integer(apr_time_sec(apr_time_now()))); return TRUE; } /* * helper function to add a JWT assertion to the HTTP request as endpoint authentication */ static apr_byte_t oidc_proto_jwt_sign_and_add(request_rec *r, apr_table_t *params, oidc_jwt_t *jwt, oidc_jwk_t *jwk) { char *cser = NULL; if (oidc_proto_jwt_sign_and_serialize(r, jwk, jwt, &cser) == FALSE) return FALSE; apr_table_setn(params, OIDC_PROTO_CLIENT_ASSERTION_TYPE, OIDC_PROTO_CLIENT_ASSERTION_TYPE_JWT_BEARER); apr_table_set(params, OIDC_PROTO_CLIENT_ASSERTION, cser); return TRUE; } #define OIDC_PROTO_JWT_ASSERTION_SYMMETRIC_ALG CJOSE_HDR_ALG_HS256 /* * create a JWT assertion signed with the client secret and add it to the HTTP request as endpoint authentication */ static apr_byte_t oidc_proto_endpoint_auth_client_secret_jwt(request_rec *r, const char *client_id, const char *client_secret, const char *audience, apr_table_t *params) { oidc_jwt_t *jwt = NULL; oidc_jose_error_t err; oidc_debug(r, "enter"); if (oidc_proto_jwt_create(r, client_id, audience, &jwt) == FALSE) return FALSE; oidc_jwk_t *jwk = oidc_jwk_create_symmetric_key(r->pool, NULL, (const unsigned char *)client_secret, _oidc_strlen(client_secret), FALSE, &err); if (jwk == NULL) { oidc_error(r, "parsing of client secret into JWK failed: %s", oidc_jose_e2s(r->pool, err)); oidc_jwt_destroy(jwt); return FALSE; } jwt->header.alg = apr_pstrdup(r->pool, OIDC_PROTO_JWT_ASSERTION_SYMMETRIC_ALG); oidc_proto_jwt_sign_and_add(r, params, jwt, jwk); oidc_jwt_destroy(jwt); oidc_jwk_destroy(jwk); return TRUE; } /* * helper function that returns the bearer access token as the endpoint authentication method if configured */ static apr_byte_t oidc_proto_endpoint_access_token_bearer(request_rec *r, oidc_cfg_t *cfg, const char *bearer_access_token, char **bearer_auth_str) { apr_byte_t rv = TRUE; if (bearer_access_token != NULL) { *bearer_auth_str = apr_psprintf(r->pool, "%s", bearer_access_token); } else { oidc_error(r, "endpoint auth method set to bearer access token but no token is provided"); rv = FALSE; } return rv; } #define OIDC_PROTO_JWT_ASSERTION_ASYMMETRIC_ALG CJOSE_HDR_ALG_RS256 /* * create a JWT assertion signed with the configured private key and add it to the HTTP request as endpoint * authentication */ static apr_byte_t oidc_proto_endpoint_auth_private_key_jwt(request_rec *r, oidc_cfg_t *cfg, const char *client_id, const apr_array_header_t *client_keys, const char *audience, apr_table_t *params) { oidc_jwt_t *jwt = NULL; oidc_jwk_t *jwk = NULL; const oidc_jwk_t *jwk_pub = NULL; oidc_debug(r, "enter"); if (oidc_proto_jwt_create(r, client_id, audience, &jwt) == FALSE) return FALSE; if ((client_keys != NULL) && (client_keys->nelts > 0)) { jwk = oidc_util_key_list_first(client_keys, CJOSE_JWK_KTY_RSA, OIDC_JOSE_JWK_SIG_STR); if (jwk && jwk->x5t) jwt->header.x5t = apr_pstrdup(r->pool, jwk->x5t); } else if ((oidc_cfg_private_keys_get(cfg) != NULL) && (oidc_cfg_private_keys_get(cfg)->nelts > 0)) { jwk = oidc_util_key_list_first(oidc_cfg_private_keys_get(cfg), CJOSE_JWK_KTY_RSA, OIDC_JOSE_JWK_SIG_STR); jwk_pub = oidc_util_key_list_first(oidc_cfg_public_keys_get(cfg), CJOSE_JWK_KTY_RSA, OIDC_JOSE_JWK_SIG_STR); if (jwk_pub && jwk_pub->x5t) // populate x5t; at least required for Microsoft Entra ID / Azure AD jwt->header.x5t = apr_pstrdup(r->pool, jwk_pub->x5t); } if (jwk == NULL) { oidc_error(r, "no private signing keys have been configured to use for private_key_jwt client " "authentication (" OIDCPrivateKeyFiles ")"); oidc_jwt_destroy(jwt); return FALSE; } jwt->header.kid = apr_pstrdup(r->pool, jwk->kid); jwt->header.alg = apr_pstrdup(r->pool, CJOSE_HDR_ALG_RS256); oidc_proto_jwt_sign_and_add(r, params, jwt, jwk); oidc_jwt_destroy(jwt); return TRUE; } /* * add the configured token endpoint authentication method to the request (or return it in the *_auth_str parameters) */ apr_byte_t oidc_proto_token_endpoint_auth(request_rec *r, oidc_cfg_t *cfg, const char *token_endpoint_auth, const char *client_id, const char *client_secret, const apr_array_header_t *client_keys, const char *audience, apr_table_t *params, const char *bearer_access_token, char **basic_auth_str, char **bearer_auth_str) { oidc_debug(r, "token_endpoint_auth=%s", token_endpoint_auth); if (client_id == NULL) { oidc_debug(r, "no client ID set: assume we don't need to authenticate"); return TRUE; } // default is client_secret_basic, but only if a client_secret is set, // otherwise we are a public client if ((token_endpoint_auth == NULL) && (client_secret != NULL)) token_endpoint_auth = OIDC_PROTO_CLIENT_SECRET_BASIC; if ((token_endpoint_auth == NULL) || (_oidc_strcmp(token_endpoint_auth, OIDC_PROTO_ENDPOINT_AUTH_NONE) == 0)) { oidc_debug( r, "no client secret is configured or the token endpoint auth method was set to \"%s\"; calling the " "token endpoint without client authentication; only public clients are supported", OIDC_PROTO_ENDPOINT_AUTH_NONE); return oidc_proto_endpoint_auth_none(r, client_id, params); } // if no client_secret is set and we don't authenticate using private_key_jwt, // we can only be a public client since the other methods require a client_secret if ((client_secret == NULL) && (_oidc_strcmp(token_endpoint_auth, OIDC_PROTO_PRIVATE_KEY_JWT) != 0)) { oidc_debug(r, "no client secret set and not using private_key_jwt, assume we are a public client"); return oidc_proto_endpoint_auth_none(r, client_id, params); } if (_oidc_strcmp(token_endpoint_auth, OIDC_PROTO_CLIENT_SECRET_BASIC) == 0) return oidc_proto_endpoint_client_secret_basic(r, client_id, client_secret, basic_auth_str); if (_oidc_strcmp(token_endpoint_auth, OIDC_PROTO_CLIENT_SECRET_POST) == 0) return oidc_proto_endpoint_client_secret_post(r, client_id, client_secret, params); if (_oidc_strcmp(token_endpoint_auth, OIDC_PROTO_CLIENT_SECRET_JWT) == 0) return oidc_proto_endpoint_auth_client_secret_jwt(r, client_id, client_secret, audience, params); if (_oidc_strcmp(token_endpoint_auth, OIDC_PROTO_PRIVATE_KEY_JWT) == 0) return oidc_proto_endpoint_auth_private_key_jwt(r, cfg, client_id, client_keys, audience, params); if (_oidc_strcmp(token_endpoint_auth, OIDC_PROTO_BEARER_ACCESS_TOKEN) == 0) { return oidc_proto_endpoint_access_token_bearer(r, cfg, bearer_access_token, bearer_auth_str); } oidc_error(r, "uhm, shouldn't be here..."); return FALSE; } mod_auth_openidc-2.4.16.10/src/proto/discovery.c000066400000000000000000000135061476721736500214600ustar00rootroot00000000000000/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ /*************************************************************************** * Copyright (C) 2017-2025 ZmartZone Holding BV * All rights reserved. * * DISCLAIMER OF WARRANTIES: * * THE SOFTWARE PROVIDED HEREUNDER IS PROVIDED ON AN "AS IS" BASIS, WITHOUT * ANY WARRANTIES OR REPRESENTATIONS EXPRESS, IMPLIED OR STATUTORY; INCLUDING, * WITHOUT LIMITATION, WARRANTIES OF QUALITY, PERFORMANCE, NONINFRINGEMENT, * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. NOR ARE THERE ANY * WARRANTIES CREATED BY A COURSE OR DEALING, COURSE OF PERFORMANCE OR TRADE * USAGE. FURTHERMORE, THERE ARE NO WARRANTIES THAT THE SOFTWARE WILL MEET * YOUR NEEDS OR BE FREE FROM ERRORS, OR THAT THE OPERATION OF THE SOFTWARE * WILL BE UNINTERRUPTED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * @Author: Hans Zandbelt - hans.zandbelt@openidc.com */ #include "cfg/dir.h" #include "cfg/parse.h" #include "mod_auth_openidc.h" #include "proto/proto.h" #include "util.h" /* * based on a resource perform OpenID Connect Provider Issuer Discovery to find out the issuer and obtain and store its * metadata */ static apr_byte_t oidc_proto_webfinger_discovery(request_rec *r, oidc_cfg_t *cfg, const char *resource, const char *domain, char **issuer) { const char *url = apr_psprintf(r->pool, "https://%s/.well-known/webfinger", domain); apr_table_t *params = apr_table_make(r->pool, 1); apr_table_setn(params, "resource", resource); apr_table_setn(params, "rel", "http://openid.net/specs/connect/1.0/issuer"); char *response = NULL; if (oidc_http_get(r, url, params, NULL, NULL, NULL, oidc_cfg_provider_ssl_validate_server_get(oidc_cfg_provider_get(cfg)), &response, NULL, NULL, oidc_cfg_http_timeout_short_get(cfg), oidc_cfg_outgoing_proxy_get(cfg), oidc_cfg_dir_pass_cookies_get(r), NULL, NULL, NULL) == FALSE) { /* errors will have been logged by now */ return FALSE; } /* decode and see if it is not an error response somehow */ json_t *j_response = NULL; if (oidc_util_decode_json_and_check_error(r, response, &j_response) == FALSE) return FALSE; /* get the links parameter */ json_t *j_links = json_object_get(j_response, "links"); if ((j_links == NULL) || (!json_is_array(j_links))) { oidc_error(r, "response JSON object did not contain a \"links\" array"); json_decref(j_response); return FALSE; } /* get the one-and-only object in the "links" array */ json_t *j_object = json_array_get(j_links, 0); if ((j_object == NULL) || (!json_is_object(j_object))) { oidc_error( r, "response JSON object did not contain a JSON object as the first element in the \"links\" array"); json_decref(j_response); return FALSE; } /* get the href from that object, which is the issuer value */ json_t *j_href = json_object_get(j_object, "href"); if ((j_href == NULL) || (!json_is_string(j_href))) { oidc_error( r, "response JSON object did not contain a \"href\" element in the first \"links\" array object"); json_decref(j_response); return FALSE; } /* check that the link is on secure HTTPs */ if (oidc_cfg_parse_is_valid_url(r->pool, json_string_value(j_href), "https") != NULL) { oidc_error(r, "response JSON object contains an \"href\" value that is not a valid \"https\" URL: %s", json_string_value(j_href)); json_decref(j_response); return FALSE; } *issuer = apr_pstrdup(r->pool, json_string_value(j_href)); oidc_debug(r, "returning issuer \"%s\" for resource \"%s\" after doing successful webfinger-based discovery", *issuer, resource); json_decref(j_response); return TRUE; } /* * based on an account name, perform OpenID Connect Provider Issuer Discovery to find out the issuer and obtain and * store its metadata */ apr_byte_t oidc_proto_discovery_account_based(request_rec *r, oidc_cfg_t *cfg, const char *acct, char **issuer) { // TODO: maybe show intermediate/progress screen "discovering..." oidc_debug(r, "enter, acct=%s", acct); const char *resource = apr_psprintf(r->pool, "acct:%s", acct); const char *domain = strrchr(acct, OIDC_CHAR_AT); if (domain == NULL) { oidc_error(r, "invalid account name"); return FALSE; } domain++; return oidc_proto_webfinger_discovery(r, cfg, resource, domain, issuer); } /* * based on user identifier URL, perform OpenID Connect Provider Issuer Discovery to find out the issuer and obtain and * store its metadata */ apr_byte_t oidc_proto_discovery_url_based(request_rec *r, oidc_cfg_t *cfg, const char *url, char **issuer) { oidc_debug(r, "enter, url=%s", url); apr_uri_t uri; apr_uri_parse(r->pool, url, &uri); char *domain = uri.hostname; if (uri.port_str != NULL) domain = apr_psprintf(r->pool, "%s:%s", domain, uri.port_str); return oidc_proto_webfinger_discovery(r, cfg, url, domain, issuer); } mod_auth_openidc-2.4.16.10/src/proto/dpop.c000066400000000000000000000115571476721736500204170ustar00rootroot00000000000000/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ /*************************************************************************** * Copyright (C) 2017-2025 ZmartZone Holding BV * All rights reserved. * * DISCLAIMER OF WARRANTIES: * * THE SOFTWARE PROVIDED HEREUNDER IS PROVIDED ON AN "AS IS" BASIS, WITHOUT * ANY WARRANTIES OR REPRESENTATIONS EXPRESS, IMPLIED OR STATUTORY; INCLUDING, * WITHOUT LIMITATION, WARRANTIES OF QUALITY, PERFORMANCE, NONINFRINGEMENT, * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. NOR ARE THERE ANY * WARRANTIES CREATED BY A COURSE OR DEALING, COURSE OF PERFORMANCE OR TRADE * USAGE. FURTHERMORE, THERE ARE NO WARRANTIES THAT THE SOFTWARE WILL MEET * YOUR NEEDS OR BE FREE FROM ERRORS, OR THAT THE OPERATION OF THE SOFTWARE * WILL BE UNINTERRUPTED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * @Author: Hans Zandbelt - hans.zandbelt@openidc.com */ #include "mod_auth_openidc.h" #include "proto/proto.h" #include "util.h" #define OIDC_PROTO_DPOP_JWT_TYP "dpop+jwt" apr_byte_t oidc_proto_dpop_use_nonce(request_rec *r, oidc_cfg_t *cfg, json_t *j_result, apr_hash_t *response_hdrs, const char *url, const char *method, const char *access_token, char **dpop) { apr_byte_t rv = FALSE; char *dpop_nonce = NULL; json_t *j_error = json_object_get(j_result, OIDC_PROTO_ERROR); if ((j_error == NULL) || (!json_is_string(j_error)) || (_oidc_strcmp(json_string_value(j_error), OIDC_PROTO_DPOP_USE_NONCE) != 0)) goto end; /* try again with a DPoP nonce provided by the server */ dpop_nonce = (char *)apr_hash_get(response_hdrs, OIDC_HTTP_HDR_DPOP_NONCE, APR_HASH_KEY_STRING); if (dpop_nonce == NULL) { oidc_error(r, "error is \"%s\" but no \"%s\" header found", OIDC_PROTO_DPOP_USE_NONCE, OIDC_HTTP_HDR_DPOP_NONCE); goto end; } rv = oidc_proto_dpop_create(r, cfg, url, method, access_token, dpop_nonce, dpop); end: oidc_debug(r, "leave: %d, dpop=%s", rv, *dpop ? "true" : "false"); return rv; } /* * generate a DPoP proof for the specified URL/method/access_token */ apr_byte_t oidc_proto_dpop_create(request_rec *r, oidc_cfg_t *cfg, const char *url, const char *method, const char *access_token, const char *nonce, char **dpop) { apr_byte_t rv = FALSE; oidc_jwt_t *jwt = NULL; oidc_jwk_t *jwk = NULL; oidc_jose_error_t err; char *jti = NULL; cjose_err cjose_err; char *s_jwk = NULL; char *ath = NULL; oidc_debug(r, "enter"); if (oidc_proto_jwt_create_from_first_pkey(r, cfg, &jwk, &jwt, TRUE) == FALSE) goto end; json_object_set_new(jwt->header.value.json, OIDC_CLAIM_TYP, json_string(OIDC_PROTO_DPOP_JWT_TYP)); s_jwk = cjose_jwk_to_json(jwk->cjose_jwk, 0, &cjose_err); cjose_header_set_raw(jwt->header.value.json, OIDC_CLAIM_JWK, s_jwk, &cjose_err); oidc_util_generate_random_string(r, &jti, OIDC_PROTO_JWT_JTI_LEN); json_object_set_new(jwt->payload.value.json, OIDC_CLAIM_JTI, json_string(jti)); json_object_set_new(jwt->payload.value.json, OIDC_CLAIM_HTM, json_string(method)); json_object_set_new(jwt->payload.value.json, OIDC_CLAIM_HTU, json_string(url)); json_object_set_new(jwt->payload.value.json, OIDC_CLAIM_IAT, json_integer(apr_time_sec(apr_time_now()))); if (access_token != NULL) { if (oidc_jose_hash_and_base64url_encode(r->pool, OIDC_JOSE_ALG_SHA256, access_token, _oidc_strlen(access_token), &ath, &err) == FALSE) { oidc_error(r, "oidc_jose_hash_and_base64url_encode failed: %s", oidc_jose_e2s(r->pool, err)); goto end; } json_object_set_new(jwt->payload.value.json, OIDC_CLAIM_ATH, json_string(ath)); } if (nonce != NULL) json_object_set_new(jwt->payload.value.json, OIDC_CLAIM_NONCE, json_string(nonce)); if (oidc_proto_jwt_sign_and_serialize(r, jwk, jwt, dpop) == FALSE) goto end; rv = TRUE; end: if (s_jwk) cjose_get_dealloc()(s_jwk); if (jwt) oidc_jwt_destroy(jwt); return rv; } mod_auth_openidc-2.4.16.10/src/proto/id_token.c000066400000000000000000000404001476721736500212360ustar00rootroot00000000000000/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ /*************************************************************************** * Copyright (C) 2017-2025 ZmartZone Holding BV * All rights reserved. * * DISCLAIMER OF WARRANTIES: * * THE SOFTWARE PROVIDED HEREUNDER IS PROVIDED ON AN "AS IS" BASIS, WITHOUT * ANY WARRANTIES OR REPRESENTATIONS EXPRESS, IMPLIED OR STATUTORY; INCLUDING, * WITHOUT LIMITATION, WARRANTIES OF QUALITY, PERFORMANCE, NONINFRINGEMENT, * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. NOR ARE THERE ANY * WARRANTIES CREATED BY A COURSE OR DEALING, COURSE OF PERFORMANCE OR TRADE * USAGE. FURTHERMORE, THERE ARE NO WARRANTIES THAT THE SOFTWARE WILL MEET * YOUR NEEDS OR BE FREE FROM ERRORS, OR THAT THE OPERATION OF THE SOFTWARE * WILL BE UNINTERRUPTED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * @Author: Hans Zandbelt - hans.zandbelt@openidc.com */ #include "mod_auth_openidc.h" #include "proto/proto.h" #include "util.h" /* * if a nonce was passed in the authorization request (and stored in the browser state), * check that it matches the nonce value in the id_token payload */ // non-static for test.c apr_byte_t oidc_proto_idtoken_validate_nonce(request_rec *r, oidc_cfg_t *cfg, oidc_provider_t *provider, const char *nonce, oidc_jwt_t *jwt) { oidc_jose_error_t err; /* see if we have this nonce cached already */ char *replay = NULL; oidc_cache_get_nonce(r, nonce, &replay); if (replay != NULL) { oidc_error(r, "the nonce value (%s) passed in the browser state was found in the cache already; possible " "replay attack!?", nonce); return FALSE; } /* get the "nonce" value in the id_token payload */ char *j_nonce = NULL; if (oidc_jose_get_string(r->pool, jwt->payload.value.json, OIDC_CLAIM_NONCE, TRUE, &j_nonce, &err) == FALSE) { oidc_error(r, "id_token JSON payload did not contain a \"%s\" string: %s", OIDC_CLAIM_NONCE, oidc_jose_e2s(r->pool, err)); return FALSE; } /* see if the nonce in the id_token matches the one that we sent in the authorization request */ if (_oidc_strcmp(nonce, j_nonce) != 0) { oidc_error( r, "the nonce value (%s) in the id_token did not match the one stored in the browser session (%s)", j_nonce, nonce); return FALSE; } /* * nonce cache duration (replay prevention window) is the 2x the configured * slack on the timestamp (+-) for token issuance plus 10 seconds for safety */ apr_time_t nonce_cache_duration = apr_time_from_sec(oidc_cfg_provider_idtoken_iat_slack_get(provider) * 2 + 10); /* store it in the cache for the calculated duration */ oidc_cache_set_nonce(r, nonce, nonce, apr_time_now() + nonce_cache_duration); oidc_debug(r, "nonce \"%s\" validated successfully and is now cached for %" APR_TIME_T_FMT " seconds", nonce, apr_time_sec(nonce_cache_duration)); return TRUE; } #define OIDC_PROTO_IDTOKEN_AUD_CLIENT_ID_SPECIAL_VALUE "@" /* * validate the "aud" and "azp" claims in the id_token payload */ apr_byte_t oidc_proto_idtoken_validate_aud_and_azp(request_rec *r, oidc_cfg_t *cfg, oidc_provider_t *provider, oidc_jwt_payload_t *id_token_payload) { char *azp = NULL; const char *s_aud = NULL; const apr_array_header_t *arr = NULL; int i = 0; oidc_jose_get_string(r->pool, id_token_payload->value.json, OIDC_CLAIM_AZP, FALSE, &azp, NULL); /* * the "azp" claim is only needed when the id_token has a single audience value and that audience * is different than the authorized party; it MAY be included even when the authorized party is * the same as the sole audience. */ if ((azp != NULL) && (_oidc_strcmp(azp, oidc_cfg_provider_client_id_get(provider)) != 0)) { oidc_error(r, "the \"%s\" claim (%s) is present in the id_token, but is not equal to the configured " "client_id (%s)", OIDC_CLAIM_AZP, azp, oidc_cfg_provider_client_id_get(provider)); return FALSE; } /* get the "aud" value from the JSON payload */ json_t *aud = json_object_get(id_token_payload->value.json, OIDC_CLAIM_AUD); if (aud != NULL) { arr = oidc_proto_profile_id_token_aud_values_get(r->pool, provider); /* check if it is a single-value */ if (json_is_string(aud)) { if (arr == NULL) { /* a single-valued audience must be equal to our client_id */ if (_oidc_strcmp(json_string_value(aud), oidc_cfg_provider_client_id_get(provider)) != 0) { oidc_error(r, "the configured client_id (%s) did not match the \"%s\" claim value " "(%s) in " "the id_token", oidc_cfg_provider_client_id_get(provider), OIDC_CLAIM_AUD, json_string_value(aud)); return FALSE; } } else { for (i = 0; i < arr->nelts; i++) { s_aud = APR_ARRAY_IDX(arr, i, const char *); if (_oidc_strcmp(s_aud, OIDC_PROTO_IDTOKEN_AUD_CLIENT_ID_SPECIAL_VALUE) == 0) s_aud = oidc_cfg_provider_client_id_get(provider); if (_oidc_strcmp(json_string_value(aud), s_aud) == 0) break; } if (i == arr->nelts) { oidc_error( r, "none of our configured audience values could be found in \"%s\" claim", OIDC_CLAIM_AUD); return FALSE; } } /* check if this is a multi-valued audience */ } else if (json_is_array(aud)) { if (arr == NULL) { if ((json_array_size(aud) > 1) && (azp == NULL)) { oidc_warn(r, "the \"%s\" claim value in the id_token is an array with more than 1 " "element, but \"%s\" claim is not present (a SHOULD in the spec...)", OIDC_CLAIM_AUD, OIDC_CLAIM_AZP); } if (oidc_util_json_array_has_value(r, aud, oidc_cfg_provider_client_id_get(provider)) == FALSE) { oidc_error( r, "our configured client_id (%s) could not be found in the array of values " "for \"%s\" claim", oidc_cfg_provider_client_id_get(provider), OIDC_CLAIM_AUD); return FALSE; } } else { /* handle explicit and exhaustive configuration of acceptable audience values */ for (i = 0; i < arr->nelts; i++) { s_aud = APR_ARRAY_IDX(arr, i, const char *); if (_oidc_strcmp(s_aud, OIDC_PROTO_IDTOKEN_AUD_CLIENT_ID_SPECIAL_VALUE) == 0) s_aud = oidc_cfg_provider_client_id_get(provider); if (oidc_util_json_array_has_value(r, aud, s_aud) == FALSE) { oidc_error(r, "our configured audience value (%s) could not be found in " "the array of values " "for \"%s\" claim", APR_ARRAY_IDX(arr, i, const char *), OIDC_CLAIM_AUD); return FALSE; } } if (json_array_size(aud) > arr->nelts) { oidc_error( r, "our configured audience values are all present in the array of values " "for \"%s\" claim, but there are other unknown/untrusted values included " "as well", OIDC_CLAIM_AUD); return FALSE; } } } else { oidc_error(r, "id_token JSON payload \"%s\" claim is not a string nor an array", OIDC_CLAIM_AUD); return FALSE; } } else { oidc_error(r, "id_token JSON payload did not contain an \"%s\" claim", OIDC_CLAIM_AUD); return FALSE; } return TRUE; } /* * check a provided hash value (at_hash|c_hash) against a corresponding hash calculated for a specified value and * algorithm */ static apr_byte_t oidc_proto_validate_hash(request_rec *r, const char *alg, const char *hash, const char *value, const char *type) { char *calc = NULL; unsigned int calc_len = 0; unsigned int hash_len = oidc_jose_hash_length(alg) / 2; oidc_jose_error_t err; /* hash the provided access_token */ if (oidc_jose_hash_string(r->pool, alg, value, &calc, &calc_len, &err) == FALSE) { oidc_error(r, "oidc_jose_hash_string failed: %s", oidc_jose_e2s(r->pool, err)); return FALSE; } /* calculate the base64url-encoded value of the hash */ char *decoded = NULL; unsigned int decoded_len = oidc_util_base64url_decode(r->pool, &decoded, hash); if (decoded_len <= 0) { oidc_error(r, "oidc_base64url_decode returned an error"); return FALSE; } oidc_debug(r, "hash_len=%d, decoded_len=%d, calc_len=%d", hash_len, decoded_len, calc_len); /* compare the calculated hash against the provided hash */ if ((decoded_len != hash_len) || (calc_len < hash_len) || (memcmp(decoded, calc, hash_len) != 0)) { oidc_error(r, "provided \"%s\" hash value (%s) does not match the calculated value", type, hash); return FALSE; } oidc_debug(r, "successfully validated the provided \"%s\" hash value (%s) against the calculated value", type, hash); return TRUE; } /* * check a hash value in the id_token against the corresponding hash calculated over a provided value */ static apr_byte_t oidc_proto_validate_hash_value(request_rec *r, oidc_provider_t *provider, oidc_jwt_t *jwt, const char *response_type, const char *value, const char *key, apr_array_header_t *required_for_flows) { /* * get the hash value from the id_token */ char *hash = NULL; oidc_jose_get_string(r->pool, jwt->payload.value.json, key, FALSE, &hash, NULL); /* * check if the hash was present */ if (hash == NULL) { /* no hash..., now see if the flow required it */ int i; for (i = 0; i < required_for_flows->nelts; i++) { if (oidc_util_spaced_string_equals(r->pool, response_type, APR_ARRAY_IDX(required_for_flows, i, const char *))) { oidc_warn(r, "flow is \"%s\", but no %s found in id_token", response_type, key); return FALSE; } } /* no hash but it was not required anyway */ return TRUE; } /* * we have a hash, validate it and return the result */ return oidc_proto_validate_hash(r, jwt->header.alg, hash, value, key); } /* * check the at_hash value in the id_token against the access_token */ apr_byte_t oidc_proto_idtoken_validate_access_token(request_rec *r, oidc_provider_t *provider, oidc_jwt_t *jwt, const char *response_type, const char *access_token) { apr_array_header_t *required_for_flows = apr_array_make(r->pool, 2, sizeof(const char *)); APR_ARRAY_PUSH(required_for_flows, const char *) = OIDC_PROTO_RESPONSE_TYPE_IDTOKEN_TOKEN; APR_ARRAY_PUSH(required_for_flows, const char *) = OIDC_PROTO_RESPONSE_TYPE_CODE_IDTOKEN_TOKEN; if (oidc_proto_validate_hash_value(r, provider, jwt, response_type, access_token, OIDC_CLAIM_AT_HASH, required_for_flows) == FALSE) { oidc_error(r, "could not validate access token against \"%s\" claim value", OIDC_CLAIM_AT_HASH); return FALSE; } return TRUE; } /* * check the c_hash value in the id_token against the code */ apr_byte_t oidc_proto_idtoken_validate_code(request_rec *r, oidc_provider_t *provider, oidc_jwt_t *jwt, const char *response_type, const char *code) { apr_array_header_t *required_for_flows = apr_array_make(r->pool, 2, sizeof(const char *)); APR_ARRAY_PUSH(required_for_flows, const char *) = OIDC_PROTO_RESPONSE_TYPE_CODE_IDTOKEN; APR_ARRAY_PUSH(required_for_flows, const char *) = OIDC_PROTO_RESPONSE_TYPE_CODE_IDTOKEN_TOKEN; if (oidc_proto_validate_hash_value(r, provider, jwt, response_type, code, OIDC_CLAIM_C_HASH, required_for_flows) == FALSE) { oidc_error(r, "could not validate code against \"%s\" claim value", OIDC_CLAIM_C_HASH); return FALSE; } return TRUE; } /* * check whether the provided JWT is a valid id_token for the specified "provider" */ static apr_byte_t oidc_proto_validate_idtoken(request_rec *r, oidc_provider_t *provider, oidc_jwt_t *jwt, const char *nonce) { oidc_cfg_t *cfg = ap_get_module_config(r->server->module_config, &auth_openidc_module); oidc_debug(r, "enter, jwt.header=\"%s\", jwt.payload=\"%s\", nonce=\"%s\"", jwt->header.value.str, jwt->payload.value.str, nonce); /* if a nonce is not passed, we're doing a ("code") flow where the nonce is optional */ if (nonce != NULL) { /* if present, verify the nonce */ if (oidc_proto_idtoken_validate_nonce(r, cfg, provider, nonce, jwt) == FALSE) return FALSE; } /* validate the ID Token JWT, requiring iss match, and valid exp + iat */ if (oidc_proto_jwt_validate( r, jwt, oidc_cfg_provider_validate_issuer_get(provider) ? oidc_cfg_provider_issuer_get(provider) : NULL, TRUE, TRUE, oidc_cfg_provider_idtoken_iat_slack_get(provider)) == FALSE) return FALSE; /* check if the required-by-spec "sub" claim is present */ if (jwt->payload.sub == NULL) { oidc_error(r, "id_token JSON payload did not contain the required-by-spec \"%s\" string value", OIDC_CLAIM_SUB); return FALSE; } /* verify the "aud" and "azp" values */ if (oidc_proto_idtoken_validate_aud_and_azp(r, cfg, provider, &jwt->payload) == FALSE) return FALSE; return TRUE; } /* * check whether the provided string is a valid id_token and return its parsed contents */ apr_byte_t oidc_proto_idtoken_parse(request_rec *r, oidc_cfg_t *cfg, oidc_provider_t *provider, const char *id_token, const char *nonce, oidc_jwt_t **jwt, apr_byte_t is_code_flow) { char *alg = NULL; oidc_debug(r, "enter: id_token header=%s", oidc_proto_jwt_header_peek(r, id_token, &alg, NULL, NULL)); apr_hash_t *decryption_keys = NULL; char buf[APR_RFC822_DATE_LEN + 1]; oidc_jose_error_t err; oidc_jwk_t *jwk = NULL; if (oidc_util_create_symmetric_key(r, oidc_cfg_provider_client_secret_get(provider), oidc_alg2keysize(alg), OIDC_JOSE_ALG_SHA256, TRUE, &jwk) == FALSE) return FALSE; decryption_keys = oidc_util_merge_symmetric_key(r->pool, oidc_cfg_private_keys_get(cfg), jwk); if (oidc_cfg_provider_client_keys_get(provider)) decryption_keys = oidc_util_merge_key_sets(r->pool, decryption_keys, oidc_cfg_provider_client_keys_get(provider)); if (oidc_jwt_parse(r->pool, id_token, jwt, decryption_keys, FALSE, &err) == FALSE) { oidc_error(r, "oidc_jwt_parse failed: %s", oidc_jose_e2s(r->pool, err)); oidc_jwt_destroy(*jwt); *jwt = NULL; return FALSE; } oidc_jwk_destroy(jwk); oidc_debug(r, "successfully parsed (and possibly decrypted) JWT with header=%s, and payload=%s", (*jwt)->header.value.str, (*jwt)->payload.value.str); // make signature validation exception for 'code' flow and the algorithm NONE if (is_code_flow == FALSE || _oidc_strcmp((*jwt)->header.alg, "none") != 0) { jwk = NULL; if (oidc_util_create_symmetric_key(r, oidc_cfg_provider_client_secret_get(provider), 0, NULL, TRUE, &jwk) == FALSE) { oidc_jwt_destroy(*jwt); *jwt = NULL; return FALSE; } if (oidc_proto_jwt_verify( r, cfg, *jwt, oidc_cfg_provider_jwks_uri_get(provider), oidc_cfg_provider_ssl_validate_server_get(provider), oidc_util_merge_symmetric_key(r->pool, oidc_cfg_provider_verify_public_keys_get(provider), jwk), oidc_cfg_provider_id_token_signed_response_alg_get(provider)) == FALSE) { oidc_error(r, "id_token signature could not be validated, aborting"); oidc_jwt_destroy(*jwt); *jwt = NULL; oidc_jwk_destroy(jwk); return FALSE; } oidc_jwk_destroy(jwk); } /* this is where the meat is */ if (oidc_proto_validate_idtoken(r, provider, *jwt, nonce) == FALSE) { oidc_error(r, "id_token payload could not be validated, aborting"); oidc_jwt_destroy(*jwt); *jwt = NULL; return FALSE; } /* log our results */ apr_rfc822_date(buf, apr_time_from_sec((*jwt)->payload.exp)); oidc_debug(r, "valid id_token for user \"%s\" expires: [%s], in %ld secs from now)", (*jwt)->payload.sub, buf, (long)((*jwt)->payload.exp - apr_time_sec(apr_time_now()))); /* since we've made it so far, we may as well say it is a valid id_token */ return TRUE; } mod_auth_openidc-2.4.16.10/src/proto/jwks.c000066400000000000000000000164421476721736500204310ustar00rootroot00000000000000/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ /*************************************************************************** * Copyright (C) 2017-2025 ZmartZone Holding BV * All rights reserved. * * DISCLAIMER OF WARRANTIES: * * THE SOFTWARE PROVIDED HEREUNDER IS PROVIDED ON AN "AS IS" BASIS, WITHOUT * ANY WARRANTIES OR REPRESENTATIONS EXPRESS, IMPLIED OR STATUTORY; INCLUDING, * WITHOUT LIMITATION, WARRANTIES OF QUALITY, PERFORMANCE, NONINFRINGEMENT, * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. NOR ARE THERE ANY * WARRANTIES CREATED BY A COURSE OR DEALING, COURSE OF PERFORMANCE OR TRADE * USAGE. FURTHERMORE, THERE ARE NO WARRANTIES THAT THE SOFTWARE WILL MEET * YOUR NEEDS OR BE FREE FROM ERRORS, OR THAT THE OPERATION OF THE SOFTWARE * WILL BE UNINTERRUPTED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * @Author: Hans Zandbelt - hans.zandbelt@openidc.com */ #include "metadata.h" #include "proto/proto.h" #include "util.h" /* * get the key from the JWKs that corresponds with the key specified in the header */ static apr_byte_t oidc_proto_jwks_key_get(request_rec *r, oidc_jwt_t *jwt, json_t *j_jwks, apr_hash_t *result) { apr_byte_t rc = TRUE; oidc_jwk_t *jwk = NULL; oidc_jose_error_t err; char *jwk_json = NULL; /* get the (optional) thumbprint for comparison */ const char *x5t = oidc_jwt_hdr_get(jwt, OIDC_JOSE_JWK_X5T_STR); oidc_debug(r, "search for kid \"%s\" or thumbprint x5t \"%s\"", jwt->header.kid, x5t); /* get the "keys" JSON array from the JWKs object */ json_t *keys = json_object_get(j_jwks, OIDC_JOSE_JWKS_KEYS_STR); if ((keys == NULL) || !(json_is_array(keys))) { oidc_error(r, "\"%s\" array element is not a JSON array", OIDC_JOSE_JWKS_KEYS_STR); return FALSE; } int i; for (i = 0; i < json_array_size(keys); i++) { /* get the next element in the array */ json_t *elem = json_array_get(keys, i); if (oidc_jwk_parse_json(r->pool, elem, &jwk, &err) == FALSE) { oidc_warn(r, "oidc_jwk_parse_json failed: %s", oidc_jose_e2s(r->pool, err)); continue; } /* get the key type and see if it is the type that we are looking for */ if (oidc_jwt_alg2kty(jwt) != jwk->kty) { oidc_debug( r, "skipping non matching kty=%d for kid=%s because it doesn't match requested kty=%d, kid=%s", jwk->kty, jwk->kid, oidc_jwt_alg2kty(jwt), jwt->header.kid); oidc_jwk_destroy(jwk); continue; } /* see if we were looking for a specific kid, if not we'll include any key that matches the type */ if ((jwt->header.kid == NULL) && (x5t == NULL)) { const char *use = json_string_value(json_object_get(elem, OIDC_JOSE_JWK_USE_STR)); if ((use != NULL) && (_oidc_strcmp(use, OIDC_JOSE_JWK_SIG_STR) != 0)) { oidc_debug(r, "skipping key because of non-matching \"%s\": \"%s\"", OIDC_JOSE_JWK_USE_STR, use); oidc_jwk_destroy(jwk); } else { oidc_jwk_to_json(r->pool, jwk, &jwk_json, &err); oidc_debug(r, "no kid/x5t to match, include matching key type: %s", jwk_json); if (jwk->kid != NULL) apr_hash_set(result, jwk->kid, APR_HASH_KEY_STRING, jwk); else // can do this because we never remove anything from the list apr_hash_set(result, apr_psprintf(r->pool, "%d", apr_hash_count(result)), APR_HASH_KEY_STRING, jwk); } continue; } /* we are looking for a specific kid, get the kid from the current element */ /* compare the requested kid against the current element */ if ((jwt->header.kid != NULL) && (jwk->kid != NULL) && (_oidc_strcmp(jwt->header.kid, jwk->kid) == 0)) { oidc_jwk_to_json(r->pool, jwk, &jwk_json, &err); oidc_debug(r, "found matching kid: \"%s\" for jwk: %s", jwt->header.kid, jwk_json); apr_hash_set(result, jwt->header.kid, APR_HASH_KEY_STRING, jwk); break; } /* we are looking for a specific x5t, get the x5t from the current element */ char *s_x5t = NULL; oidc_util_json_object_get_string(r->pool, elem, OIDC_JOSE_JWK_X5T_STR, &s_x5t, NULL); /* compare the requested thumbprint against the current element */ if ((s_x5t != NULL) && (x5t != NULL) && (_oidc_strcmp(x5t, s_x5t) == 0)) { oidc_jwk_to_json(r->pool, jwk, &jwk_json, &err); oidc_debug(r, "found matching %s: \"%s\" for jwk: %s", OIDC_JOSE_JWK_X5T_STR, x5t, jwk_json); apr_hash_set(result, x5t, APR_HASH_KEY_STRING, jwk); break; } /* the right key type but no matching kid/x5t */ oidc_jwk_destroy(jwk); } return rc; } /* * get the keys from the (possibly cached) set of JWKs on the jwk_uri that corresponds with the key specified in the * header */ apr_byte_t oidc_proto_jwks_uri_keys(request_rec *r, oidc_cfg_t *cfg, oidc_jwt_t *jwt, const oidc_jwks_uri_t *jwks_uri, int ssl_validate_server, apr_hash_t *keys, apr_byte_t *force_refresh) { json_t *j_jwks = NULL; /* get the set of JSON Web Keys for this provider (possibly by downloading them from the specified * provider->jwk_uri) */ oidc_metadata_jwks_get(r, cfg, jwks_uri, ssl_validate_server, &j_jwks, force_refresh); if (j_jwks == NULL) { oidc_error(r, "could not %s JSON Web Keys", *force_refresh ? "refresh" : "get"); return FALSE; } /* * get the key corresponding to the kid from the header, referencing the key that * was used to sign this message (or get all keys in case no kid was set) * * we don't check the error return value because we'll treat "error" in the same * way as "key not found" i.e. by refreshing the keys from the JWKs URI if not * already done */ oidc_proto_jwks_key_get(r, jwt, j_jwks, keys); /* no need anymore for the parsed json_t contents, release the it */ json_decref(j_jwks); /* if we've got no keys and we did not do a fresh download, then the cache may be stale */ if ((apr_hash_count(keys) < 1) && (*force_refresh == FALSE)) { /* we did not get a key, but we have not refreshed the JWKs from the jwks_uri yet */ oidc_warn(r, "could not find a key in the cached JSON Web Keys, doing a forced refresh in case keys " "were rolled over"); /* get the set of JSON Web Keys forcing a fresh download from the specified JWKs URI */ *force_refresh = TRUE; return oidc_proto_jwks_uri_keys(r, cfg, jwt, jwks_uri, ssl_validate_server, keys, force_refresh); } oidc_debug(r, "returning %d key(s) obtained from the (possibly cached) JWKs URI", apr_hash_count(keys)); return TRUE; } mod_auth_openidc-2.4.16.10/src/proto/jwt.c000066400000000000000000000245441476721736500202610ustar00rootroot00000000000000/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ /*************************************************************************** * Copyright (C) 2017-2025 ZmartZone Holding BV * All rights reserved. * * DISCLAIMER OF WARRANTIES: * * THE SOFTWARE PROVIDED HEREUNDER IS PROVIDED ON AN "AS IS" BASIS, WITHOUT * ANY WARRANTIES OR REPRESENTATIONS EXPRESS, IMPLIED OR STATUTORY; INCLUDING, * WITHOUT LIMITATION, WARRANTIES OF QUALITY, PERFORMANCE, NONINFRINGEMENT, * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. NOR ARE THERE ANY * WARRANTIES CREATED BY A COURSE OR DEALING, COURSE OF PERFORMANCE OR TRADE * USAGE. FURTHERMORE, THERE ARE NO WARRANTIES THAT THE SOFTWARE WILL MEET * YOUR NEEDS OR BE FREE FROM ERRORS, OR THAT THE OPERATION OF THE SOFTWARE * WILL BE UNINTERRUPTED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * @Author: Hans Zandbelt - hans.zandbelt@openidc.com */ #include "mod_auth_openidc.h" #include "proto/proto.h" #include "util.h" /* * validate "iat" claim in JWT */ static apr_byte_t oidc_proto_validate_iat(request_rec *r, oidc_jwt_t *jwt, apr_byte_t is_mandatory, int slack) { /* get the current time */ apr_time_t now = apr_time_sec(apr_time_now()); /* sanity check for iat being set */ if (jwt->payload.iat == OIDC_JWT_CLAIM_TIME_EMPTY) { if (is_mandatory) { oidc_error(r, "JWT did not contain an \"%s\" number value", OIDC_CLAIM_IAT); return FALSE; } return TRUE; } /* see if we are asked to enforce a time window at all */ if (slack < 0) { oidc_debug(r, "slack for JWT set < 0, do not enforce boundary check"); return TRUE; } /* check if this id_token has been issued just now +- slack (default 10 minutes) */ if ((now - slack) > jwt->payload.iat) { oidc_error(r, "\"iat\" validation failure (%ld): JWT was issued more than %d seconds ago", (long)jwt->payload.iat, slack); return FALSE; } if ((now + slack) < jwt->payload.iat) { oidc_error(r, "\"iat\" validation failure (%ld): JWT was issued more than %d seconds in the future", (long)jwt->payload.iat, slack); return FALSE; } return TRUE; } /* * validate "exp" claim in JWT */ static apr_byte_t oidc_proto_validate_exp(request_rec *r, oidc_jwt_t *jwt, apr_byte_t is_mandatory) { /* get the current time */ apr_time_t now = apr_time_sec(apr_time_now()); /* sanity check for exp being set */ if (jwt->payload.exp == OIDC_JWT_CLAIM_TIME_EMPTY) { if (is_mandatory) { oidc_error(r, "JWT did not contain an \"%s\" number value", OIDC_CLAIM_EXP); return FALSE; } return TRUE; } /* see if now is beyond the JWT expiry timestamp */ apr_time_t expires = jwt->payload.exp; if (now > expires) { oidc_error(r, "\"exp\" validation failure (%ld): JWT expired %ld seconds ago", (long)expires, (long)(now - expires)); return FALSE; } return TRUE; } /* * validate a JSON Web token */ apr_byte_t oidc_proto_jwt_validate(request_rec *r, oidc_jwt_t *jwt, const char *iss, apr_byte_t exp_is_mandatory, apr_byte_t iat_is_mandatory, int iat_slack) { if (iss != NULL) { /* issuer is set and must match */ if (jwt->payload.iss == NULL) { oidc_error(r, "JWT did not contain an \"%s\" string (requested value: %s)", OIDC_CLAIM_ISS, iss); return FALSE; } /* check if the issuer matches the requested value */ if (oidc_util_issuer_match(iss, jwt->payload.iss) == FALSE) { oidc_error(r, "requested issuer (%s) does not match received \"%s\" value in id_token (%s)", iss, OIDC_CLAIM_ISS, jwt->payload.iss); return FALSE; } } /* check exp */ if (oidc_proto_validate_exp(r, jwt, exp_is_mandatory) == FALSE) return FALSE; /* check iat */ if (oidc_proto_validate_iat(r, jwt, iat_is_mandatory, iat_slack) == FALSE) return FALSE; return TRUE; } /* * verify the signature on a JWT using the dynamically obtained and statically configured keys */ apr_byte_t oidc_proto_jwt_verify(request_rec *r, oidc_cfg_t *cfg, oidc_jwt_t *jwt, const oidc_jwks_uri_t *jwks_uri, int ssl_validate_server, apr_hash_t *static_keys, const char *alg) { oidc_jose_error_t err; apr_hash_t *dynamic_keys = NULL; apr_byte_t force_refresh = FALSE; apr_byte_t rv = FALSE; if (alg != NULL) { if (_oidc_strcmp(jwt->header.alg, alg) != 0) { oidc_error(r, "JWT was not signed with the expected configured algorithm: %s != %s", jwt->header.alg, alg); return FALSE; } } dynamic_keys = apr_hash_make(r->pool); /* see if we've got a JWKs URI set for signature validation with dynamically obtained asymmetric keys */ if ((jwks_uri->uri == NULL) && (jwks_uri->signed_uri == NULL)) { oidc_debug(r, "\"jwks_uri\" and \"signed_jwks_uri\" are not set, signature validation will only be " "performed against statically configured keys"); /* the JWKs URI was provided, but let's see if it makes sense to pull down keys, i.e. if it is an * asymmetric signature */ } else if (oidc_jwt_alg2kty(jwt) == CJOSE_JWK_KTY_OCT) { oidc_debug(r, "\"%s\" is set, but the JWT has a symmetric signature so we won't pull/use keys from there", (jwks_uri->signed_uri != NULL) ? "signed_jwks_uri" : "jwks_uri"); } else { /* get the key from the JWKs that corresponds with the key specified in the header */ force_refresh = FALSE; if (oidc_proto_jwks_uri_keys(r, cfg, jwt, jwks_uri, ssl_validate_server, dynamic_keys, &force_refresh) == FALSE) { oidc_jwk_list_destroy_hash(dynamic_keys); return FALSE; } } /* do the actual JWS verification with the locally and remotely provided key material */ // TODO: now static keys "win" if the same `kid` was used in both local and remote key sets rv = oidc_jwt_verify(r->pool, jwt, oidc_util_merge_key_sets_hash(r->pool, static_keys, dynamic_keys), &err); /* if no kid was provided we may have used stale keys from the cache, so we'll refresh it */ if ((rv == FALSE) && (jwt->header.kid == NULL)) { oidc_warn( r, "JWT signature verification failed (%s) for JWT with no kid, re-trying with forced refresh now", oidc_jose_e2s(r->pool, err)); force_refresh = TRUE; /* destroy the list to avoid memory leaks when keys with the same kid are retrieved */ oidc_jwk_list_destroy_hash(dynamic_keys); oidc_proto_jwks_uri_keys(r, cfg, jwt, jwks_uri, ssl_validate_server, dynamic_keys, &force_refresh); rv = oidc_jwt_verify(r->pool, jwt, oidc_util_merge_key_sets_hash(r->pool, static_keys, dynamic_keys), &err); } if (rv == FALSE) { oidc_error(r, "JWT signature verification failed: %s", oidc_jose_e2s(r->pool, err)); oidc_jwk_list_destroy_hash(dynamic_keys); return FALSE; } oidc_debug(r, "JWT signature verification with algorithm \"%s\" was successful", jwt->header.alg); oidc_jwk_list_destroy_hash(dynamic_keys); return TRUE; } /* * return the compact-encoded JWT header contents */ char *oidc_proto_jwt_header_peek(request_rec *r, const char *compact_encoded_jwt, char **alg, char **enc, char **kid) { char *input = NULL, *result = NULL; char *p = _oidc_strstr(compact_encoded_jwt ? compact_encoded_jwt : "", "."); if (p == NULL) { oidc_warn(r, "could not parse first element separated by \".\" from input"); return NULL; } input = apr_pstrmemdup(r->pool, compact_encoded_jwt, _oidc_strlen(compact_encoded_jwt) - _oidc_strlen(p)); if (oidc_util_base64url_decode(r->pool, &result, input) <= 0) { oidc_warn(r, "oidc_base64url_decode returned an error"); return NULL; } if ((alg != NULL) || (enc != NULL)) { json_t *json = NULL; oidc_util_decode_json_object(r, result, &json); if (json) { if (alg) *alg = apr_pstrdup(r->pool, json_string_value(json_object_get(json, CJOSE_HDR_ALG))); if (enc) *enc = apr_pstrdup(r->pool, json_string_value(json_object_get(json, CJOSE_HDR_ENC))); if (kid) *kid = apr_pstrdup(r->pool, json_string_value(json_object_get(json, CJOSE_HDR_KID))); } json_decref(json); } return result; } apr_byte_t oidc_proto_jwt_create_from_first_pkey(request_rec *r, oidc_cfg_t *cfg, oidc_jwk_t **jwk, oidc_jwt_t **jwt, apr_byte_t use_psa_for_rsa) { apr_byte_t rv = FALSE; oidc_debug(r, "enter"); *jwk = oidc_util_key_list_first(oidc_cfg_private_keys_get(cfg), -1, OIDC_JOSE_JWK_SIG_STR); if (*jwk == NULL) { oidc_error(r, "no RSA/EC private signing keys have been configured (in " OIDCPrivateKeyFiles ")"); goto end; } *jwt = oidc_jwt_new(r->pool, TRUE, TRUE); if (*jwt == NULL) goto end; (*jwt)->header.kid = apr_pstrdup(r->pool, (*jwk)->kid); if ((*jwk)->kty == CJOSE_JWK_KTY_RSA) (*jwt)->header.alg = apr_pstrdup(r->pool, use_psa_for_rsa ? CJOSE_HDR_ALG_PS256 : CJOSE_HDR_ALG_RS256); else if ((*jwk)->kty == CJOSE_JWK_KTY_EC) (*jwt)->header.alg = apr_pstrdup(r->pool, CJOSE_HDR_ALG_ES256); else { oidc_error(r, "no usable RSA/EC signing keys has been configured (in " OIDCPrivateKeyFiles ")"); goto end; } rv = TRUE; end: // also in case of errors, jwt will be destroyed in the caller function return rv; } apr_byte_t oidc_proto_jwt_sign_and_serialize(request_rec *r, oidc_jwk_t *jwk, oidc_jwt_t *jwt, char **cser) { apr_byte_t rv = FALSE; oidc_jose_error_t err; if (oidc_jwt_sign(r->pool, jwt, jwk, FALSE, &err) == FALSE) { oidc_error(r, "oidc_jwt_sign failed: %s", oidc_jose_e2s(r->pool, err)); goto end; } *cser = oidc_jose_jwt_serialize(r->pool, jwt, &err); if (*cser == NULL) { oidc_error(r, "oidc_jose_jwt_serialize failed: %s", oidc_jose_e2s(r->pool, err)); goto end; } rv = TRUE; end: return rv; } mod_auth_openidc-2.4.16.10/src/proto/pkce.c000066400000000000000000000075511476721736500203760ustar00rootroot00000000000000/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ /*************************************************************************** * Copyright (C) 2017-2025 ZmartZone Holding BV * All rights reserved. * * DISCLAIMER OF WARRANTIES: * * THE SOFTWARE PROVIDED HEREUNDER IS PROVIDED ON AN "AS IS" BASIS, WITHOUT * ANY WARRANTIES OR REPRESENTATIONS EXPRESS, IMPLIED OR STATUTORY; INCLUDING, * WITHOUT LIMITATION, WARRANTIES OF QUALITY, PERFORMANCE, NONINFRINGEMENT, * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. NOR ARE THERE ANY * WARRANTIES CREATED BY A COURSE OR DEALING, COURSE OF PERFORMANCE OR TRADE * USAGE. FURTHERMORE, THERE ARE NO WARRANTIES THAT THE SOFTWARE WILL MEET * YOUR NEEDS OR BE FREE FROM ERRORS, OR THAT THE OPERATION OF THE SOFTWARE * WILL BE UNINTERRUPTED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * @Author: Hans Zandbelt - hans.zandbelt@openidc.com */ #include "proto/proto.h" #include "util.h" /* * PCKE "plain" proto state */ static apr_byte_t oidc_proto_pkce_state_plain(request_rec *r, char **state) { return oidc_util_generate_random_string(r, state, OIDC_PROTO_CODE_VERIFIER_LENGTH); } /* * PCKE "plain" code_challenge */ static apr_byte_t oidc_proto_pkce_challenge_plain(request_rec *r, const char *state, char **code_challenge) { *code_challenge = apr_pstrdup(r->pool, state); return TRUE; } /* * PCKE "plain" code_verifier */ static apr_byte_t oidc_proto_pkce_verifier_plain(request_rec *r, const char *state, char **code_verifier) { *code_verifier = apr_pstrdup(r->pool, state); return TRUE; } /* * PCKE "s256" proto state */ static apr_byte_t oidc_proto_pkce_state_s256(request_rec *r, char **state) { return oidc_util_generate_random_string(r, state, OIDC_PROTO_CODE_VERIFIER_LENGTH); } /* * PCKE "s256" code_challenge */ static apr_byte_t oidc_proto_pkce_challenge_s256(request_rec *r, const char *state, char **code_challenge) { if (oidc_util_hash_string_and_base64url_encode(r, OIDC_JOSE_ALG_SHA256, state, code_challenge) == FALSE) { oidc_error(r, "oidc_util_hash_string_and_base64url_encode returned an error for the code verifier"); return FALSE; } return TRUE; } /* * PCKE "s256" code_verifier */ static apr_byte_t oidc_proto_pkce_verifier_s256(request_rec *r, const char *state, char **code_verifier) { *code_verifier = apr_pstrdup(r->pool, state); return TRUE; } /* * PKCE plain */ oidc_proto_pkce_t oidc_pkce_plain = {OIDC_PKCE_METHOD_PLAIN, oidc_proto_pkce_state_plain, oidc_proto_pkce_verifier_plain, oidc_proto_pkce_challenge_plain}; /* * PKCE s256 */ oidc_proto_pkce_t oidc_pkce_s256 = {OIDC_PKCE_METHOD_S256, oidc_proto_pkce_state_s256, oidc_proto_pkce_verifier_s256, oidc_proto_pkce_challenge_s256}; /* * PKCE none */ oidc_proto_pkce_t oidc_pkce_none = {OIDC_PKCE_METHOD_NONE, NULL, NULL, NULL}; mod_auth_openidc-2.4.16.10/src/proto/profile.c000066400000000000000000000105531476721736500211100ustar00rootroot00000000000000/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ /*************************************************************************** * Copyright (C) 2017-2025 ZmartZone Holding BV * All rights reserved. * * DISCLAIMER OF WARRANTIES: * * THE SOFTWARE PROVIDED HEREUNDER IS PROVIDED ON AN "AS IS" BASIS, WITHOUT * ANY WARRANTIES OR REPRESENTATIONS EXPRESS, IMPLIED OR STATUTORY; INCLUDING, * WITHOUT LIMITATION, WARRANTIES OF QUALITY, PERFORMANCE, NONINFRINGEMENT, * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. NOR ARE THERE ANY * WARRANTIES CREATED BY A COURSE OR DEALING, COURSE OF PERFORMANCE OR TRADE * USAGE. FURTHERMORE, THERE ARE NO WARRANTIES THAT THE SOFTWARE WILL MEET * YOUR NEEDS OR BE FREE FROM ERRORS, OR THAT THE OPERATION OF THE SOFTWARE * WILL BE UNINTERRUPTED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * @Author: Hans Zandbelt - hans.zandbelt@openidc.com */ #include "proto/proto.h" /* * returns the "aud" claim to insert into the JWT used for client * authentication towards the token endpoint using private_key_jwt/client_secret_jwt */ const char *oidc_proto_profile_token_endpoint_auth_aud(oidc_provider_t *provider) { if (oidc_cfg_provider_profile_get(provider) == OIDC_PROFILE_FAPI20) { return oidc_cfg_provider_issuer_get(provider); } return oidc_cfg_provider_token_endpoint_url_get(provider); } /* * returns the method to be used when sending the authorization request to the Provider */ oidc_auth_request_method_t oidc_proto_profile_auth_request_method_get(oidc_provider_t *provider) { if (oidc_cfg_provider_profile_get(provider) == OIDC_PROFILE_FAPI20) return OIDC_AUTH_REQUEST_METHOD_PAR; return oidc_cfg_provider_auth_request_method_get(provider); } /* * returns the acceptable "aud" values in the ID token */ const apr_array_header_t *oidc_proto_profile_id_token_aud_values_get(apr_pool_t *pool, oidc_provider_t *provider) { const apr_array_header_t *values = oidc_cfg_provider_id_token_aud_values_get(provider); // TODO: so we actually do allow overriding the acceptable "aud" values but we sort of assume the client_id // is in there in that case; perhaps check that - in the config check? - for FAPI20 if (values == NULL) { if (oidc_cfg_provider_profile_get(provider) == OIDC_PROFILE_FAPI20) { apr_array_header_t *list = NULL; oidc_cfg_string_list_add(pool, &list, oidc_cfg_provider_client_id_get(provider)); return list; } } return values; } /* * returns the PKCE mode */ const oidc_proto_pkce_t *oidc_proto_profile_pkce_get(oidc_provider_t *provider) { if (oidc_cfg_provider_profile_get(provider) == OIDC_PROFILE_FAPI20) return &oidc_pkce_s256; return oidc_cfg_provider_pkce_get(provider); } /* * returns the DPoP mode */ oidc_dpop_mode_t oidc_proto_profile_dpop_mode_get(oidc_provider_t *provider) { if (oidc_cfg_provider_profile_get(provider) == OIDC_PROFILE_FAPI20) return OIDC_DPOP_MODE_REQUIRED; return oidc_cfg_provider_dpop_mode_get(provider); } /* * returns whether the Provider is required to pass back an "iss" parameter * together with the authorization response sent to the Redirect URI */ int oidc_proto_profile_response_require_iss_get(oidc_provider_t *provider) { if (oidc_cfg_provider_profile_get(provider) == OIDC_PROFILE_FAPI20) return 1; return oidc_cfg_provider_response_require_iss_get(provider); } mod_auth_openidc-2.4.16.10/src/proto/proto.c000066400000000000000000000106401476721736500206100ustar00rootroot00000000000000/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ /*************************************************************************** * Copyright (C) 2017-2025 ZmartZone Holding BV * All rights reserved. * * DISCLAIMER OF WARRANTIES: * * THE SOFTWARE PROVIDED HEREUNDER IS PROVIDED ON AN "AS IS" BASIS, WITHOUT * ANY WARRANTIES OR REPRESENTATIONS EXPRESS, IMPLIED OR STATUTORY; INCLUDING, * WITHOUT LIMITATION, WARRANTIES OF QUALITY, PERFORMANCE, NONINFRINGEMENT, * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. NOR ARE THERE ANY * WARRANTIES CREATED BY A COURSE OR DEALING, COURSE OF PERFORMANCE OR TRADE * USAGE. FURTHERMORE, THERE ARE NO WARRANTIES THAT THE SOFTWARE WILL MEET * YOUR NEEDS OR BE FREE FROM ERRORS, OR THAT THE OPERATION OF THE SOFTWARE * WILL BE UNINTERRUPTED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * @Author: Hans Zandbelt - hans.zandbelt@openidc.com */ #include "proto/proto.h" #include "cfg/dir.h" #include "cfg/parse.h" #include "handle/handle.h" #include "metadata.h" #include "metrics.h" #include "mod_auth_openidc.h" #include "util.h" #include #include /* * generate a random value (nonce) to correlate request/response through browser state */ apr_byte_t oidc_proto_generate_nonce(request_rec *r, char **nonce, int len) { return oidc_util_generate_random_string(r, nonce, len); } /* * return the supported flows */ apr_array_header_t *oidc_proto_supported_flows(apr_pool_t *pool) { apr_array_header_t *result = apr_array_make(pool, 6, sizeof(const char *)); APR_ARRAY_PUSH(result, const char *) = OIDC_PROTO_RESPONSE_TYPE_CODE; APR_ARRAY_PUSH(result, const char *) = OIDC_PROTO_RESPONSE_TYPE_IDTOKEN; APR_ARRAY_PUSH(result, const char *) = OIDC_PROTO_RESPONSE_TYPE_IDTOKEN_TOKEN; APR_ARRAY_PUSH(result, const char *) = OIDC_PROTO_RESPONSE_TYPE_CODE_IDTOKEN; APR_ARRAY_PUSH(result, const char *) = OIDC_PROTO_RESPONSE_TYPE_CODE_TOKEN; APR_ARRAY_PUSH(result, const char *) = OIDC_PROTO_RESPONSE_TYPE_CODE_IDTOKEN_TOKEN; return result; } /* * check if a particular OpenID Connect flow is supported */ apr_byte_t oidc_proto_flow_is_supported(apr_pool_t *pool, const char *flow) { apr_array_header_t *flows = oidc_proto_supported_flows(pool); int i; for (i = 0; i < flows->nelts; i++) { if (oidc_util_spaced_string_equals(pool, flow, APR_ARRAY_IDX(flows, i, const char *))) return TRUE; } return FALSE; } /* * set the WWW-Authenticate response header according to https://tools.ietf.org/html/rfc6750#section-3 */ int oidc_proto_return_www_authenticate(request_rec *r, const char *error, const char *error_description) { apr_byte_t accept_token_in = oidc_cfg_dir_oauth_accept_token_in_get(r); char *hdr; if (accept_token_in == OIDC_OAUTH_ACCEPT_TOKEN_IN_BASIC) { hdr = apr_psprintf(r->pool, "%s", OIDC_PROTO_BASIC); } else { hdr = apr_psprintf(r->pool, "%s", OIDC_PROTO_BEARER); } if (ap_auth_name(r) != NULL) hdr = apr_psprintf(r->pool, "%s %s=\"%s\"", hdr, OIDC_PROTO_REALM, ap_auth_name(r)); if (error != NULL) hdr = apr_psprintf(r->pool, "%s%s %s=\"%s\"", hdr, (ap_auth_name(r) ? "," : ""), OIDC_PROTO_ERROR, error); if (error_description != NULL) hdr = apr_psprintf(r->pool, "%s, %s=\"%s\"", hdr, OIDC_PROTO_ERROR_DESCRIPTION, error_description); oidc_http_hdr_err_out_add(r, OIDC_HTTP_HDR_WWW_AUTHENTICATE, hdr); return HTTP_UNAUTHORIZED; } mod_auth_openidc-2.4.16.10/src/proto/proto.h000066400000000000000000000333271476721736500206240ustar00rootroot00000000000000/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ /*************************************************************************** * Copyright (C) 2017-2025 ZmartZone Holding BV * All rights reserved. * * DISCLAIMER OF WARRANTIES: * * THE SOFTWARE PROVIDED HEREUNDER IS PROVIDED ON AN "AS IS" BASIS, WITHOUT * ANY WARRANTIES OR REPRESENTATIONS EXPRESS, IMPLIED OR STATUTORY; INCLUDING, * WITHOUT LIMITATION, WARRANTIES OF QUALITY, PERFORMANCE, NONINFRINGEMENT, * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. NOR ARE THERE ANY * WARRANTIES CREATED BY A COURSE OR DEALING, COURSE OF PERFORMANCE OR TRADE * USAGE. FURTHERMORE, THERE ARE NO WARRANTIES THAT THE SOFTWARE WILL MEET * YOUR NEEDS OR BE FREE FROM ERRORS, OR THAT THE OPERATION OF THE SOFTWARE * WILL BE UNINTERRUPTED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * @Author: Hans Zandbelt - hans.zandbelt@openidc.com */ #ifndef _MOD_AUTH_OPENIDC_PROTO_H_ #define _MOD_AUTH_OPENIDC_PROTO_H_ #include "cfg/provider.h" #include "jose.h" #define OIDC_PROTO_ISS "iss" #define OIDC_PROTO_CODE "code" #define OIDC_PROTO_CLIENT_ID "client_id" #define OIDC_PROTO_CLIENT_SECRET "client_secret" #define OIDC_PROTO_CLIENT_ASSERTION "client_assertion" #define OIDC_PROTO_CLIENT_ASSERTION_TYPE "client_assertion_type" #define OIDC_PROTO_ACCESS_TOKEN "access_token" #define OIDC_PROTO_ID_TOKEN "id_token" #define OIDC_PROTO_STATE "state" #define OIDC_PROTO_GRANT_TYPE "grant_type" #define OIDC_PROTO_REDIRECT_URI "redirect_uri" #define OIDC_PROTO_CODE_VERIFIER "code_verifier" #define OIDC_PROTO_CODE_CHALLENGE "code_challenge" #define OIDC_PROTO_CODE_CHALLENGE_METHOD "code_challenge_method" #define OIDC_PROTO_SCOPE "scope" #define OIDC_PROTO_REFRESH_TOKEN "refresh_token" #define OIDC_PROTO_TOKEN_TYPE "token_type" #define OIDC_PROTO_TOKEN_TYPE_HINT "token_type_hint" #define OIDC_PROTO_TOKEN "token" #define OIDC_PROTO_EXPIRES_IN "expires_in" #define OIDC_PROTO_RESPONSE_TYPE "response_type" #define OIDC_PROTO_RESPONSE_MODE "response_mode" #define OIDC_PROTO_NONCE "nonce" #define OIDC_PROTO_PROMPT "prompt" #define OIDC_PROTO_LOGIN_HINT "login_hint" #define OIDC_PROTO_ID_TOKEN_HINT "id_token_hint" #define OIDC_PROTO_REQUEST_URI "request_uri" #define OIDC_PROTO_REQUEST_OBJECT "request" #define OIDC_PROTO_SESSION_STATE "session_state" #define OIDC_PROTO_ACTIVE "active" #define OIDC_PROTO_LOGOUT_TOKEN "logout_token" #define OIDC_PROTO_RESPONSE_TYPE_CODE "code" #define OIDC_PROTO_RESPONSE_TYPE_IDTOKEN "id_token" #define OIDC_PROTO_RESPONSE_TYPE_IDTOKEN_TOKEN "id_token token" #define OIDC_PROTO_RESPONSE_TYPE_CODE_IDTOKEN "code id_token" #define OIDC_PROTO_RESPONSE_TYPE_CODE_TOKEN "code token" #define OIDC_PROTO_RESPONSE_TYPE_CODE_IDTOKEN_TOKEN "code id_token token" #define OIDC_PROTO_RESPONSE_TYPE_TOKEN "token" #define OIDC_PROTO_RESPONSE_MODE_QUERY "query" #define OIDC_PROTO_RESPONSE_MODE_FRAGMENT "fragment" #define OIDC_PROTO_RESPONSE_MODE_FORM_POST "form_post" #define OIDC_PROTO_SCOPE_OPENID "openid" #define OIDC_PROTO_PROMPT_NONE "none" #define OIDC_PROTO_ERROR "error" #define OIDC_PROTO_ERROR_DESCRIPTION "error_description" #define OIDC_PROTO_REALM "realm" #define OIDC_PROTO_ERR_INVALID_TOKEN "invalid_token" #define OIDC_PROTO_ERR_INVALID_REQUEST "invalid_request" #define OIDC_PROTO_GRANT_TYPE_AUTHZ_CODE "authorization_code" #define OIDC_PROTO_GRANT_TYPE_REFRESH_TOKEN "refresh_token" #define OIDC_PROTO_CLIENT_ASSERTION_TYPE_JWT_BEARER "urn:ietf:params:oauth:client-assertion-type:jwt-bearer" #define OIDC_PROTO_CLIENT_SECRET_BASIC "client_secret_basic" #define OIDC_PROTO_CLIENT_SECRET_POST "client_secret_post" #define OIDC_PROTO_CLIENT_SECRET_JWT "client_secret_jwt" #define OIDC_PROTO_PRIVATE_KEY_JWT "private_key_jwt" #define OIDC_PROTO_BEARER_ACCESS_TOKEN "bearer_access_token" #define OIDC_PROTO_ENDPOINT_AUTH_NONE "none" #define OIDC_PROTO_BEARER "Bearer" #define OIDC_PROTO_BASIC "Basic" #define OIDC_PROTO_DPOP "DPoP" #define OIDC_PROTO_DPOP_USE_NONCE "use_dpop_nonce" /* nonce bytes length */ #define OIDC_PROTO_NONCE_LENGTH 32 typedef json_t oidc_proto_state_t; // profile.c oidc_auth_request_method_t oidc_proto_profile_auth_request_method_get(oidc_provider_t *provider); const char *oidc_proto_profile_token_endpoint_auth_aud(oidc_provider_t *provider); const apr_array_header_t *oidc_proto_profile_id_token_aud_values_get(apr_pool_t *pool, oidc_provider_t *provider); const oidc_proto_pkce_t *oidc_proto_profile_pkce_get(oidc_provider_t *provider); oidc_dpop_mode_t oidc_proto_profile_dpop_mode_get(oidc_provider_t *provider); int oidc_proto_profile_response_require_iss_get(oidc_provider_t *provider); // auth.c apr_byte_t oidc_proto_token_endpoint_auth(request_rec *r, oidc_cfg_t *cfg, const char *token_endpoint_auth, const char *client_id, const char *client_secret, const apr_array_header_t *client_keys, const char *audience, apr_table_t *params, const char *bearer_access_token, char **basic_auth_str, char **bearer_auth_str); // discovery.c apr_byte_t oidc_proto_discovery_account_based(request_rec *r, oidc_cfg_t *cfg, const char *acct, char **issuer); apr_byte_t oidc_proto_discovery_url_based(request_rec *r, oidc_cfg_t *cfg, const char *url, char **issuer); // dpop.c apr_byte_t oidc_proto_dpop_create(request_rec *r, oidc_cfg_t *cfg, const char *url, const char *method, const char *access_token, const char *nonce, char **dpop); apr_byte_t oidc_proto_dpop_use_nonce(request_rec *r, oidc_cfg_t *cfg, json_t *j_result, apr_hash_t *response_hdrs, const char *url, const char *method, const char *access_token, char **dpop); // id_token.c apr_byte_t oidc_proto_idtoken_parse(request_rec *r, oidc_cfg_t *cfg, oidc_provider_t *provider, const char *id_token, const char *nonce, oidc_jwt_t **jwt, apr_byte_t is_code_flow); apr_byte_t oidc_proto_idtoken_validate_aud_and_azp(request_rec *r, oidc_cfg_t *cfg, oidc_provider_t *provider, oidc_jwt_payload_t *id_token_payload); // non-static for test.c apr_byte_t oidc_proto_idtoken_validate_access_token(request_rec *r, oidc_provider_t *provider, oidc_jwt_t *jwt, const char *response_type, const char *access_token); apr_byte_t oidc_proto_idtoken_validate_code(request_rec *r, oidc_provider_t *provider, oidc_jwt_t *jwt, const char *response_type, const char *code); apr_byte_t oidc_proto_idtoken_validate_nonce(request_rec *r, oidc_cfg_t *cfg, oidc_provider_t *provider, const char *nonce, oidc_jwt_t *jwt); // jwks.c apr_byte_t oidc_proto_jwks_uri_keys(request_rec *r, oidc_cfg_t *cfg, oidc_jwt_t *jwt, const oidc_jwks_uri_t *jwks_uri, int ssl_validate_server, apr_hash_t *keys, apr_byte_t *force_refresh); // jwt.c #define OIDC_PROTO_JWT_JTI_LEN 16 apr_byte_t oidc_proto_jwt_verify(request_rec *r, oidc_cfg_t *cfg, oidc_jwt_t *jwt, const oidc_jwks_uri_t *jwks_uri, int ssl_validate_server, apr_hash_t *symmetric_keys, const char *alg); apr_byte_t oidc_proto_jwt_validate(request_rec *r, oidc_jwt_t *jwt, const char *iss, apr_byte_t exp_is_mandatory, apr_byte_t iat_is_mandatory, int iat_slack); char *oidc_proto_jwt_header_peek(request_rec *r, const char *jwt, char **alg, char **enc, char **kid); apr_byte_t oidc_proto_jwt_create_from_first_pkey(request_rec *r, oidc_cfg_t *cfg, oidc_jwk_t **jwk, oidc_jwt_t **jwt, apr_byte_t use_psa_for_rsa); apr_byte_t oidc_proto_jwt_sign_and_serialize(request_rec *r, oidc_jwk_t *jwk, oidc_jwt_t *jwt, char **cser); // pkce.c #define OIDC_PKCE_METHOD_PLAIN "plain" #define OIDC_PKCE_METHOD_S256 "S256" #define OIDC_PKCE_METHOD_NONE "none" /* code verifier length */ #define OIDC_PROTO_CODE_VERIFIER_LENGTH 32 extern oidc_proto_pkce_t oidc_pkce_plain; extern oidc_proto_pkce_t oidc_pkce_s256; extern oidc_proto_pkce_t oidc_pkce_none; const char *oidc_proto_state_get_pkce_state(oidc_proto_state_t *proto_state); void oidc_proto_state_set_pkce_state(oidc_proto_state_t *proto_state, const char *pkce_state); // proto.c apr_byte_t oidc_proto_generate_nonce(request_rec *r, char **nonce, int len); apr_array_header_t *oidc_proto_supported_flows(apr_pool_t *pool); apr_byte_t oidc_proto_flow_is_supported(apr_pool_t *pool, const char *flow); int oidc_proto_return_www_authenticate(request_rec *r, const char *error, const char *error_description); // request.c int oidc_proto_request_auth(request_rec *r, struct oidc_provider_t *provider, const char *login_hint, const char *redirect_uri, const char *state, oidc_proto_state_t *proto_state, const char *id_token_hint, const char *code_challenge, const char *auth_request_params, const char *path_scope); // response.c apr_byte_t oidc_proto_response_is_post(request_rec *r, oidc_cfg_t *cfg); apr_byte_t oidc_proto_response_is_redirect(request_rec *r, oidc_cfg_t *cfg); apr_byte_t oidc_proto_response_code_idtoken_token(request_rec *r, oidc_cfg_t *c, oidc_proto_state_t *proto_state, oidc_provider_t *provider, apr_table_t *params, const char *response_mode, oidc_jwt_t **jwt); apr_byte_t oidc_proto_response_code_idtoken(request_rec *r, oidc_cfg_t *c, oidc_proto_state_t *proto_state, oidc_provider_t *provider, apr_table_t *params, const char *response_mode, oidc_jwt_t **jwt); apr_byte_t oidc_proto_response_code_token(request_rec *r, oidc_cfg_t *c, oidc_proto_state_t *proto_state, oidc_provider_t *provider, apr_table_t *params, const char *response_mode, oidc_jwt_t **jwt); apr_byte_t oidc_proto_response_code(request_rec *r, oidc_cfg_t *c, oidc_proto_state_t *proto_state, oidc_provider_t *provider, apr_table_t *params, const char *response_mode, oidc_jwt_t **jwt); apr_byte_t oidc_proto_response_idtoken_token(request_rec *r, oidc_cfg_t *c, oidc_proto_state_t *proto_state, oidc_provider_t *provider, apr_table_t *params, const char *response_mode, oidc_jwt_t **jwt); apr_byte_t oidc_proto_response_idtoken(request_rec *r, oidc_cfg_t *c, oidc_proto_state_t *proto_state, oidc_provider_t *provider, apr_table_t *params, const char *response_mode, oidc_jwt_t **jwt); // state.c oidc_proto_state_t *oidc_proto_state_new(); void oidc_proto_state_destroy(oidc_proto_state_t *proto_state); oidc_proto_state_t *oidc_proto_state_from_cookie(request_rec *r, oidc_cfg_t *c, const char *cookieValue); char *oidc_proto_state_to_cookie(request_rec *r, oidc_cfg_t *c, oidc_proto_state_t *proto_state); char *oidc_proto_state_to_string(request_rec *r, oidc_proto_state_t *proto_state); const char *oidc_proto_state_get_issuer(oidc_proto_state_t *proto_state); const char *oidc_proto_state_get_nonce(oidc_proto_state_t *proto_state); apr_time_t oidc_proto_state_get_timestamp(oidc_proto_state_t *proto_state); const char *oidc_proto_state_get_state(oidc_proto_state_t *proto_state); const char *oidc_proto_state_get_original_url(oidc_proto_state_t *proto_state); const char *oidc_proto_state_get_prompt(oidc_proto_state_t *proto_state); const char *oidc_proto_state_get_response_type(oidc_proto_state_t *proto_state); const char *oidc_proto_state_get_response_mode(oidc_proto_state_t *proto_state); const char *oidc_proto_state_get_original_url(oidc_proto_state_t *proto_state); const char *oidc_proto_state_get_original_method(oidc_proto_state_t *proto_state); void oidc_proto_state_set_state(oidc_proto_state_t *proto_state, const char *state); void oidc_proto_state_set_issuer(oidc_proto_state_t *proto_state, const char *issuer); void oidc_proto_state_set_original_url(oidc_proto_state_t *proto_state, const char *original_url); void oidc_proto_state_set_original_method(oidc_proto_state_t *proto_state, const char *original_method); void oidc_proto_state_set_response_mode(oidc_proto_state_t *proto_state, const char *response_mode); void oidc_proto_state_set_response_type(oidc_proto_state_t *proto_state, const char *response_type); void oidc_proto_state_set_nonce(oidc_proto_state_t *proto_state, const char *nonce); void oidc_proto_state_set_prompt(oidc_proto_state_t *proto_state, const char *prompt); void oidc_proto_state_set_timestamp_now(oidc_proto_state_t *proto_state); // token.c apr_byte_t oidc_proto_token_endpoint_request(request_rec *r, oidc_cfg_t *cfg, oidc_provider_t *provider, apr_table_t *params, char **id_token, char **access_token, char **token_type, int *expires_in, char **refresh_token); apr_byte_t oidc_proto_token_refresh_request(request_rec *r, oidc_cfg_t *cfg, oidc_provider_t *provider, const char *rtoken, char **id_token, char **access_token, char **token_type, int *expires_in, char **refresh_token); // userinfo.c apr_byte_t oidc_proto_userinfo_request(request_rec *r, oidc_cfg_t *cfg, oidc_provider_t *provider, const char *id_token_sub, const char *access_token, const char *access_token_type, char **response, char **userinfo_jwt, long *response_code); #endif /* _MOD_AUTH_OPENIDC_PROTO_H_ */ mod_auth_openidc-2.4.16.10/src/proto/request.c000066400000000000000000000660451476721736500211470ustar00rootroot00000000000000/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ /*************************************************************************** * Copyright (C) 2017-2025 ZmartZone Holding BV * All rights reserved. * * DISCLAIMER OF WARRANTIES: * * THE SOFTWARE PROVIDED HEREUNDER IS PROVIDED ON AN "AS IS" BASIS, WITHOUT * ANY WARRANTIES OR REPRESENTATIONS EXPRESS, IMPLIED OR STATUTORY; INCLUDING, * WITHOUT LIMITATION, WARRANTIES OF QUALITY, PERFORMANCE, NONINFRINGEMENT, * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. NOR ARE THERE ANY * WARRANTIES CREATED BY A COURSE OR DEALING, COURSE OF PERFORMANCE OR TRADE * USAGE. FURTHERMORE, THERE ARE NO WARRANTIES THAT THE SOFTWARE WILL MEET * YOUR NEEDS OR BE FREE FROM ERRORS, OR THAT THE OPERATION OF THE SOFTWARE * WILL BE UNINTERRUPTED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * @Author: Hans Zandbelt - hans.zandbelt@openidc.com */ #include "cfg/dir.h" #include "handle/handle.h" #include "metadata.h" #include "mod_auth_openidc.h" #include "proto/proto.h" #include "util.h" /* * add extra configured authentication request parameters (global or per-path) */ static void oidc_proto_request_auth_params_add(request_rec *r, apr_table_t *params, const char *auth_request_params) { char *key = NULL; char *val = NULL; if (auth_request_params == NULL) return; while (*auth_request_params) { val = ap_getword(r->pool, &auth_request_params, OIDC_CHAR_AMP); if (val == NULL) break; key = ap_getword(r->pool, (const char **)&val, OIDC_CHAR_EQUAL); ap_unescape_url(key); ap_unescape_url(val); if (_oidc_strcmp(val, OIDC_STR_HASH) != 0) { apr_table_add(params, key, val); continue; } if (oidc_util_request_has_parameter(r, key) == TRUE) { oidc_util_request_parameter_get(r, key, &val); apr_table_add(params, key, val); } } } /* * send a Pushed Authorization Request (PAR) to the Provider */ static int oidc_proto_request_auth_push(request_rec *r, struct oidc_provider_t *provider, apr_table_t *params) { oidc_cfg_t *cfg = ap_get_module_config(r->server->module_config, &auth_openidc_module); char *response = NULL, *basic_auth = NULL, *bearer_auth = NULL; char *request_uri = NULL; int expires_in = 0; char *authorization_request = NULL; json_t *j_result = NULL; int rv = HTTP_INTERNAL_SERVER_ERROR; const char *endpoint_url = oidc_cfg_provider_pushed_authorization_request_endpoint_url_get(provider); oidc_debug(r, "enter"); if (endpoint_url == NULL) { oidc_error(r, "the Provider's OAuth 2.0 Pushed Authorization Request endpoint URL is not set, PAR " "cannot be used"); goto out; } /* add the token endpoint authentication credentials to the pushed authorization request */ if (oidc_proto_token_endpoint_auth( r, cfg, oidc_cfg_provider_token_endpoint_auth_get(provider), oidc_cfg_provider_client_id_get(provider), oidc_cfg_provider_client_secret_get(provider), oidc_cfg_provider_client_keys_get(provider), oidc_proto_profile_token_endpoint_auth_aud(provider), params, NULL, &basic_auth, &bearer_auth) == FALSE) goto out; if (oidc_http_post_form(r, endpoint_url, params, basic_auth, bearer_auth, NULL, oidc_cfg_provider_ssl_validate_server_get(provider), &response, NULL, NULL, oidc_cfg_http_timeout_long_get(cfg), oidc_cfg_outgoing_proxy_get(cfg), oidc_cfg_dir_pass_cookies_get(r), NULL, NULL, NULL) == FALSE) goto out; /* check for errors, the response itself will have been logged already */ if (oidc_util_decode_json_and_check_error(r, response, &j_result) == FALSE) goto out; /* get the request_uri from the parsed response */ oidc_util_json_object_get_string(r->pool, j_result, OIDC_PROTO_REQUEST_URI, &request_uri, NULL); /* get the expires_in value from the parsed response */ oidc_util_json_object_get_int(j_result, OIDC_PROTO_EXPIRES_IN, &expires_in, 60); /* assemble the resulting authentication request and redirect */ apr_table_clear(params); apr_table_setn(params, OIDC_PROTO_CLIENT_ID, oidc_cfg_provider_client_id_get(provider)); apr_table_setn(params, OIDC_PROTO_REQUEST_URI, request_uri); authorization_request = oidc_http_query_encoded_url(r, oidc_cfg_provider_authorization_endpoint_url_get(provider), params); oidc_http_hdr_out_location_set(r, authorization_request); rv = HTTP_MOVED_TEMPORARILY; out: if (j_result) json_decref(j_result); return rv; } /* context structure for encoding parameters */ typedef struct oidc_proto_form_post_ctx_t { request_rec *r; const char *html_body; } oidc_proto_form_post_ctx_t; /* * add a key/value pair post parameter */ static int oidc_proto_request_form_post_param_add(void *rec, const char *key, const char *value) { oidc_proto_form_post_ctx_t *ctx = (oidc_proto_form_post_ctx_t *)rec; oidc_debug(ctx->r, "processing: %s=%s", key, value); ctx->html_body = apr_psprintf(ctx->r->pool, "%s \n", ctx->html_body, oidc_util_html_escape(ctx->r->pool, key), oidc_util_html_escape(ctx->r->pool, value)); return 1; } /* * make the browser POST parameters through Javascript auto-submit */ static int oidc_proto_request_html_post(request_rec *r, const char *url, apr_table_t *params) { oidc_debug(r, "enter"); const char *html_body = apr_psprintf(r->pool, "

Submitting Authentication Request...

\n" "
\n" "

\n", url); oidc_proto_form_post_ctx_t data = {r, html_body}; apr_table_do(oidc_proto_request_form_post_param_add, &data, params, NULL); html_body = apr_psprintf(r->pool, "%s%s", data.html_body, "

\n" "
\n"); return oidc_util_html_send(r, "Submitting...", NULL, "document.forms[0].submit", html_body, OK); } #define OIDC_REQUEST_OJBECT_COPY_FROM_REQUEST "copy_from_request" #define OIDC_REQUEST_OJBECT_COPY_AND_REMOVE_FROM_REQUEST "copy_and_remove_from_request" #define OIDC_REQUEST_OJBECT_TTL "ttl" #define OIDC_REQUEST_OBJECT_TTL_DEFAULT 30 /* * indicates whether a request parameter from the authorization request needs to be * copied and/or deleted to/from the protected request object based on the settings specified * in the "copy_from_request"/"copy_and_remove_from_request" JSON array in the request object */ static apr_byte_t oidc_proto_request_uri_param_needs_action(json_t *request_object_config, const char *parameter_name, const char *action) { json_t *copy_from_request = json_object_get(request_object_config, action); size_t index = 0; while (index < json_array_size(copy_from_request)) { json_t *value = json_array_get(copy_from_request, index); if ((json_is_string(value)) && (_oidc_strcmp(json_string_value(value), parameter_name) == 0)) { return TRUE; } index++; } return FALSE; } /* context structure for copying request parameters */ typedef struct oidc_request_uri_copy_req_ctx_t { request_rec *r; json_t *request_object_config; oidc_jwt_t *request_object; apr_table_t *params2; } oidc_request_uri_copy_req_ctx_t; /* * copy a parameter key/value from the authorizion request to the * request object if the configuration setting says to include it */ static int oidc_request_uri_copy_from_request(void *rec, const char *name, const char *value) { oidc_request_uri_copy_req_ctx_t *ctx = (oidc_request_uri_copy_req_ctx_t *)rec; oidc_debug(ctx->r, "processing name: %s, value: %s", name, value); if (oidc_proto_request_uri_param_needs_action(ctx->request_object_config, name, OIDC_REQUEST_OJBECT_COPY_FROM_REQUEST) || oidc_proto_request_uri_param_needs_action(ctx->request_object_config, name, OIDC_REQUEST_OJBECT_COPY_AND_REMOVE_FROM_REQUEST)) { json_t *result = NULL; json_error_t json_error; result = json_loads(value, JSON_DECODE_ANY, &json_error); if (result == NULL) /* assume string */ result = json_string(value); if (result) { json_object_set_new(ctx->request_object->payload.value.json, name, json_deep_copy(result)); json_decref(result); } if (oidc_proto_request_uri_param_needs_action(ctx->request_object_config, name, OIDC_REQUEST_OJBECT_COPY_AND_REMOVE_FROM_REQUEST)) { apr_table_set(ctx->params2, name, name); } } return 1; } /* * delete a parameter key/value from the authorizion request if the configuration setting says to remove it */ static int oidc_request_uri_delete_from_request(void *rec, const char *name, const char *value) { oidc_request_uri_copy_req_ctx_t *ctx = (oidc_request_uri_copy_req_ctx_t *)rec; oidc_debug(ctx->r, "deleting from query parameters: name: %s, value: %s", name, value); if (oidc_proto_request_uri_param_needs_action(ctx->request_object_config, name, OIDC_REQUEST_OJBECT_COPY_AND_REMOVE_FROM_REQUEST)) { apr_table_unset(ctx->params2, name); } return 1; } /* * obtain the public key for a provider to encrypt the request object with */ static apr_byte_t oidc_request_uri_encryption_jwk_by_type(request_rec *r, oidc_cfg_t *cfg, struct oidc_provider_t *provider, int key_type, oidc_jwk_t **jwk) { oidc_jose_error_t err; json_t *j_jwks = NULL; apr_byte_t force_refresh = TRUE; oidc_jwk_t *key = NULL; char *jwk_json = NULL; int i = 0; /* TODO: forcefully refresh now; we may want to relax that */ oidc_metadata_jwks_get(r, cfg, oidc_cfg_provider_jwks_uri_get(provider), oidc_cfg_provider_ssl_validate_server_get(provider), &j_jwks, &force_refresh); if (j_jwks == NULL) { oidc_error(r, "could not retrieve JSON Web Keys"); return FALSE; } json_t *keys = json_object_get(j_jwks, OIDC_JOSE_JWKS_KEYS_STR); if ((keys == NULL) || !(json_is_array(keys))) { oidc_error(r, "\"%s\" array element is not a JSON array", OIDC_JOSE_JWKS_KEYS_STR); return FALSE; } /* walk the set of published keys to find the first that has a matching type */ for (i = 0; i < json_array_size(keys); i++) { json_t *elem = json_array_get(keys, i); const char *use = json_string_value(json_object_get(elem, OIDC_JOSE_JWK_USE_STR)); if ((use != NULL) && (_oidc_strcmp(use, OIDC_JOSE_JWK_ENC_STR) != 0)) { oidc_debug(r, "skipping key because of non-matching \"%s\": \"%s\"", OIDC_JOSE_JWK_USE_STR, use); continue; } if (oidc_jwk_parse_json(r->pool, elem, &key, &err) == FALSE) { oidc_warn(r, "oidc_jwk_parse_json failed: %s", oidc_jose_e2s(r->pool, err)); continue; } if (key_type == key->kty) { oidc_jwk_to_json(r->pool, key, &jwk_json, &err); oidc_debug(r, "found matching encryption key type for key: %s", jwk_json); *jwk = key; break; } oidc_jwk_destroy(key); } /* no need anymore for the parsed json_t contents, release the it */ json_decref(j_jwks); return (*jwk != NULL); } /* * generate a request object */ static char *oidc_request_uri_request_object(request_rec *r, struct oidc_provider_t *provider, json_t *request_object_config, apr_table_t *params, int ttl) { oidc_jwk_t *sjwk = NULL; int jwk_needs_destroy = 0; oidc_debug(r, "enter"); oidc_cfg_t *cfg = ap_get_module_config(r->server->module_config, &auth_openidc_module); /* create the request object value */ oidc_jwt_t *request_object = oidc_jwt_new(r->pool, TRUE, TRUE); /* set basic values: iss, aud, iat and exp */ json_object_set_new(request_object->payload.value.json, OIDC_CLAIM_ISS, json_string(oidc_cfg_provider_client_id_get(provider))); json_object_set_new(request_object->payload.value.json, OIDC_CLAIM_AUD, json_string(oidc_cfg_provider_issuer_get(provider))); json_object_set_new(request_object->payload.value.json, OIDC_CLAIM_IAT, json_integer(apr_time_sec(apr_time_now()))); json_object_set_new(request_object->payload.value.json, OIDC_CLAIM_NBF, json_integer(apr_time_sec(apr_time_now()))); json_object_set_new(request_object->payload.value.json, OIDC_CLAIM_EXP, json_integer(apr_time_sec(apr_time_now()) + ttl)); /* add static values to the request object as configured in the .conf file; may override iss/aud */ oidc_util_json_merge(r, json_object_get(request_object_config, "static"), request_object->payload.value.json); /* copy parameters from the authorization request as configured in the .conf file */ apr_table_t *delete_from_query_params = apr_table_make(r->pool, 0); oidc_request_uri_copy_req_ctx_t data = {r, request_object_config, request_object, delete_from_query_params}; apr_table_do(oidc_request_uri_copy_from_request, &data, params, NULL); /* delete parameters from the query parameters of the authorization request as configured in the .conf file */ data.params2 = params; apr_table_do(oidc_request_uri_delete_from_request, &data, delete_from_query_params, NULL); /* debug logging */ oidc_debug( r, "request object: %s", oidc_util_encode_json(r->pool, request_object->payload.value.json, JSON_PRESERVE_ORDER | JSON_COMPACT)); char *serialized_request_object = NULL; oidc_jose_error_t err; int kty = -1; /* get the crypto settings from the configuration */ json_t *crypto = json_object_get(request_object_config, "crypto"); oidc_util_json_object_get_string(r->pool, crypto, "sign_alg", &request_object->header.alg, "none"); /* see if we need to sign the request object */ if (_oidc_strcmp(request_object->header.alg, "none") != 0) { sjwk = NULL; jwk_needs_destroy = 0; kty = oidc_jwt_alg2kty(request_object); switch (kty) { case CJOSE_JWK_KTY_RSA: case CJOSE_JWK_KTY_EC: if ((oidc_cfg_provider_client_keys_get(provider) != NULL) || (oidc_cfg_private_keys_get(cfg) != NULL)) { sjwk = oidc_cfg_provider_client_keys_get(provider) ? oidc_util_key_list_first(oidc_cfg_provider_client_keys_get(provider), kty, OIDC_JOSE_JWK_SIG_STR) : oidc_util_key_list_first(oidc_cfg_private_keys_get(cfg), kty, OIDC_JOSE_JWK_SIG_STR); if (sjwk && sjwk->kid) request_object->header.kid = apr_pstrdup(r->pool, sjwk->kid); else oidc_error(r, "could not find a usable signing key"); } else { oidc_error(r, "no global or per-provider private keys have been configured to use for " "request object signing"); } break; case CJOSE_JWK_KTY_OCT: oidc_util_create_symmetric_key(r, oidc_cfg_provider_client_secret_get(provider), 0, NULL, FALSE, &sjwk); jwk_needs_destroy = 1; break; default: oidc_error(r, "unsupported signing algorithm, no key type for algorithm: %s", request_object->header.alg); break; } if (sjwk == NULL) { oidc_jwt_destroy(request_object); json_decref(request_object_config); return NULL; } if (oidc_jwt_sign(r->pool, request_object, sjwk, FALSE, &err) == FALSE) { oidc_error(r, "signing Request Object failed: %s", oidc_jose_e2s(r->pool, err)); if (jwk_needs_destroy) oidc_jwk_destroy(sjwk); oidc_jwt_destroy(request_object); json_decref(request_object_config); return NULL; } if (jwk_needs_destroy) oidc_jwk_destroy(sjwk); } oidc_jwt_t *jwe = oidc_jwt_new(r->pool, TRUE, FALSE); if (jwe == NULL) { oidc_error(r, "creating JWE failed"); oidc_jwt_destroy(request_object); json_decref(request_object_config); return NULL; } oidc_util_json_object_get_string(r->pool, crypto, "crypt_alg", &jwe->header.alg, NULL); oidc_util_json_object_get_string(r->pool, crypto, "crypt_enc", &jwe->header.enc, NULL); char *cser = oidc_jose_jwt_serialize(r->pool, request_object, &err); /* see if we need to encrypt the request object */ if (jwe->header.alg != NULL) { oidc_jwk_t *ejwk = NULL; switch (oidc_jwt_alg2kty(jwe)) { case CJOSE_JWK_KTY_RSA: case CJOSE_JWK_KTY_EC: oidc_request_uri_encryption_jwk_by_type(r, cfg, provider, oidc_jwt_alg2kty(jwe), &ejwk); break; case CJOSE_JWK_KTY_OCT: oidc_util_create_symmetric_key(r, oidc_cfg_provider_client_secret_get(provider), oidc_alg2keysize(jwe->header.alg), OIDC_JOSE_ALG_SHA256, FALSE, &ejwk); break; default: oidc_error(r, "unsupported encryption algorithm, no key type for algorithm: %s", request_object->header.alg); break; } if (ejwk == NULL) { oidc_jwt_destroy(jwe); oidc_jwt_destroy(request_object); json_decref(request_object_config); return NULL; } if (jwe->header.enc == NULL) jwe->header.enc = apr_pstrdup(r->pool, CJOSE_HDR_ENC_A128CBC_HS256); if (ejwk->kid != NULL) jwe->header.kid = ejwk->kid; if (oidc_jwt_encrypt(r->pool, jwe, ejwk, cser, _oidc_strlen(cser) + 1, &serialized_request_object, &err) == FALSE) { oidc_error(r, "encrypting JWT failed: %s", oidc_jose_e2s(r->pool, err)); oidc_jwk_destroy(ejwk); oidc_jwt_destroy(jwe); oidc_jwt_destroy(request_object); json_decref(request_object_config); return NULL; } oidc_jwk_destroy(ejwk); } else { /* should be sign only or "none" */ serialized_request_object = cser; } oidc_jwt_destroy(request_object); oidc_jwt_destroy(jwe); json_decref(request_object_config); oidc_debug(r, "serialized request object JWT header = \"%s\"", oidc_proto_jwt_header_peek(r, serialized_request_object, NULL, NULL, NULL)); oidc_debug(r, "serialized request object = \"%s\"", serialized_request_object); return serialized_request_object; } #define OIDC_PROTO_REQUEST_URI_REF_LEN 16 /* * generate a request object and pass it by reference in the authorization request */ static char *oidc_proto_request_uri_create(request_rec *r, struct oidc_provider_t *provider, json_t *request_object_config, const char *redirect_uri, apr_table_t *params, int ttl) { oidc_debug(r, "enter"); /* see if we need to override the resolver URL, mostly for test purposes */ char *resolver_url = NULL; if (json_object_get(request_object_config, "url") != NULL) resolver_url = apr_pstrdup(r->pool, json_string_value(json_object_get(request_object_config, "url"))); else resolver_url = apr_pstrdup(r->pool, redirect_uri); char *serialized_request_object = oidc_request_uri_request_object(r, provider, request_object_config, params, ttl); /* generate a temporary reference, store the request object in the cache and generate a Request URI that * references it */ char *request_uri = NULL; if (serialized_request_object != NULL) { char *request_ref = NULL; if (oidc_util_generate_random_string(r, &request_ref, OIDC_PROTO_REQUEST_URI_REF_LEN) == TRUE) { oidc_cache_set_request_uri(r, request_ref, serialized_request_object, apr_time_now() + apr_time_from_sec(ttl)); request_uri = apr_psprintf(r->pool, "%s?%s=%s", resolver_url, OIDC_PROTO_REQUEST_URI, oidc_http_url_encode(r, request_ref)); } } return request_uri; } /* * Generic function to generate request/request_object parameter with value */ static void oidc_proto_request_uri_request_param_add(request_rec *r, struct oidc_provider_t *provider, const char *redirect_uri, apr_table_t *params) { /* parse the request object configuration from a string in to a JSON structure */ json_t *request_object_config = NULL; if (oidc_util_decode_json_object(r, oidc_cfg_provider_request_object_get(provider), &request_object_config) == FALSE) return; /* request_uri is used as default parameter for sending Request Object */ char *parameter = OIDC_PROTO_REQUEST_URI; /* get request_object_type parameter from config */ json_t *request_object_type = json_object_get(request_object_config, "request_object_type"); if (request_object_type != NULL) { const char *request_object_type_str = json_string_value(request_object_type); if (request_object_type_str == NULL) { oidc_error(r, "Value of request_object_type in request_object config is not a string"); return; } /* ensure parameter variable to have a valid value */ if (_oidc_strcmp(request_object_type_str, OIDC_PROTO_REQUEST_OBJECT) == 0) { parameter = OIDC_PROTO_REQUEST_OBJECT; } else if (_oidc_strcmp(request_object_type_str, OIDC_PROTO_REQUEST_URI) != 0) { oidc_error(r, "Bad request_object_type in config: %s", request_object_type_str); return; } } /* create request value */ char *value = NULL; int ttl = OIDC_REQUEST_OBJECT_TTL_DEFAULT; oidc_util_json_object_get_int(request_object_config, "ttl", &ttl, OIDC_REQUEST_OBJECT_TTL_DEFAULT); if (_oidc_strcmp(parameter, OIDC_PROTO_REQUEST_URI) == 0) { /* parameter is "request_uri" */ value = oidc_proto_request_uri_create(r, provider, request_object_config, redirect_uri, params, ttl); apr_table_set(params, OIDC_PROTO_REQUEST_URI, value); } else { /* parameter is "request" */ value = oidc_request_uri_request_object(r, provider, request_object_config, params, ttl); apr_table_set(params, OIDC_PROTO_REQUEST_OBJECT, value); } } /* * send an OpenID Connect authorization request to the specified provider */ int oidc_proto_request_auth(request_rec *r, struct oidc_provider_t *provider, const char *login_hint, const char *redirect_uri, const char *state, oidc_proto_state_t *proto_state, const char *id_token_hint, const char *code_challenge, const char *auth_request_params, const char *path_scope) { /* log some stuff */ oidc_debug(r, "enter, issuer=%s, redirect_uri=%s, state=%s, proto_state=%s, code_challenge=%s, " "auth_request_params=%s, path_scope=%s", oidc_cfg_provider_issuer_get(provider), redirect_uri, state, oidc_proto_state_to_string(r, proto_state), code_challenge, auth_request_params, path_scope); int rv = OK; char *authorization_request = NULL; /* assemble parameters to call the token endpoint for validation */ apr_table_t *params = apr_table_make(r->pool, 4); /* add the response type */ apr_table_setn(params, OIDC_PROTO_RESPONSE_TYPE, oidc_proto_state_get_response_type(proto_state)); /* concat the per-path scopes with the per-provider scopes */ const char *scope = oidc_cfg_provider_scope_get(provider); if (path_scope != NULL) scope = ((scope != NULL) && (_oidc_strcmp(scope, "") != 0)) ? apr_pstrcat(r->pool, scope, OIDC_STR_SPACE, path_scope, NULL) : path_scope; if (scope != NULL) { if (!oidc_util_spaced_string_contains(r->pool, scope, OIDC_PROTO_SCOPE_OPENID)) { oidc_warn(r, "the configuration for the \"%s\" parameter does not include the \"%s\" scope, your " "provider may not return an \"id_token\": %s", OIDC_PROTO_SCOPE, OIDC_PROTO_SCOPE_OPENID, scope); } apr_table_setn(params, OIDC_PROTO_SCOPE, scope); } if (oidc_cfg_provider_client_id_get(provider) == NULL) { oidc_error(r, "no Client ID set for the provider: perhaps you are accessing an endpoint protected with " "\"AuthType openid-connect\" instead of \"AuthType oauth20\"?)"); return HTTP_INTERNAL_SERVER_ERROR; } /* add the client ID */ apr_table_setn(params, OIDC_PROTO_CLIENT_ID, oidc_cfg_provider_client_id_get(provider)); /* add the state */ apr_table_setn(params, OIDC_PROTO_STATE, state); /* add the redirect uri */ apr_table_setn(params, OIDC_PROTO_REDIRECT_URI, redirect_uri); /* add the nonce if set */ const char *nonce = oidc_proto_state_get_nonce(proto_state); if (nonce != NULL) apr_table_setn(params, OIDC_PROTO_NONCE, nonce); /* add PKCE code challenge if set */ if ((code_challenge != NULL) && (oidc_proto_profile_pkce_get(provider) != &oidc_pkce_none)) { apr_table_setn(params, OIDC_PROTO_CODE_CHALLENGE, code_challenge); apr_table_setn(params, OIDC_PROTO_CODE_CHALLENGE_METHOD, oidc_proto_profile_pkce_get(provider)->method); } /* add the response_mode if explicitly set */ const char *response_mode = oidc_proto_state_get_response_mode(proto_state); if (response_mode != NULL) apr_table_setn(params, OIDC_PROTO_RESPONSE_MODE, response_mode); /* add the login_hint if provided */ if (login_hint != NULL) apr_table_setn(params, OIDC_PROTO_LOGIN_HINT, login_hint); /* add the id_token_hint if provided */ if (id_token_hint != NULL) apr_table_setn(params, OIDC_PROTO_ID_TOKEN_HINT, id_token_hint); /* add the prompt setting if provided (e.g. "none" for no-GUI checks) */ const char *prompt = oidc_proto_state_get_prompt(proto_state); if (prompt != NULL) apr_table_setn(params, OIDC_PROTO_PROMPT, prompt); /* add any statically configured custom authorization request parameters */ oidc_proto_request_auth_params_add(r, params, oidc_cfg_provider_auth_request_params_get(provider)); /* add any dynamically configured custom authorization request parameters */ oidc_proto_request_auth_params_add(r, params, auth_request_params); /* add request parameter (request or request_uri) if set */ if (oidc_cfg_provider_request_object_get(provider) != NULL) oidc_proto_request_uri_request_param_add(r, provider, redirect_uri, params); /* send the full authentication request via POST or GET */ if (oidc_proto_profile_auth_request_method_get(provider) == OIDC_AUTH_REQUEST_METHOD_POST) { /* construct a HTML POST auto-submit page with the authorization request parameters */ rv = oidc_proto_request_html_post(r, oidc_cfg_provider_authorization_endpoint_url_get(provider), params); } else if (oidc_proto_profile_auth_request_method_get(provider) == OIDC_AUTH_REQUEST_METHOD_PAR) { rv = oidc_proto_request_auth_push(r, provider, params); } else if (oidc_proto_profile_auth_request_method_get(provider) == OIDC_AUTH_REQUEST_METHOD_GET) { /* construct the full authorization request URL */ authorization_request = oidc_http_query_encoded_url(r, oidc_cfg_provider_authorization_endpoint_url_get(provider), params); // TODO: should also enable this when using the POST binding for the auth request /* see if we need to preserve POST parameters through Javascript/HTML5 storage */ if (oidc_response_post_preserve_javascript(r, authorization_request, NULL, NULL) == FALSE) { /* add the redirect location header */ oidc_http_hdr_out_location_set(r, authorization_request); /* and tell Apache to return an HTTP Redirect (302) message */ rv = HTTP_MOVED_TEMPORARILY; } else { /* signal this to the content handler */ oidc_request_state_set(r, OIDC_REQUEST_STATE_KEY_AUTHN, ""); r->user = ""; rv = OK; } } else { oidc_error(r, "oidc_cfg_provider_auth_request_method_get(provider) set to an unknown value: %d", oidc_proto_profile_auth_request_method_get(provider)); return HTTP_INTERNAL_SERVER_ERROR; } /* cleanup */ oidc_proto_state_destroy(proto_state); /* no cache */ oidc_http_hdr_err_out_add(r, OIDC_HTTP_HDR_CACHE_CONTROL, "no-cache, no-store, max-age=0"); /* log our exit code */ oidc_debug(r, "return: %d", rv); return rv; } mod_auth_openidc-2.4.16.10/src/proto/response.c000066400000000000000000000520211476721736500213020ustar00rootroot00000000000000/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ /*************************************************************************** * Copyright (C) 2017-2025 ZmartZone Holding BV * All rights reserved. * * DISCLAIMER OF WARRANTIES: * * THE SOFTWARE PROVIDED HEREUNDER IS PROVIDED ON AN "AS IS" BASIS, WITHOUT * ANY WARRANTIES OR REPRESENTATIONS EXPRESS, IMPLIED OR STATUTORY; INCLUDING, * WITHOUT LIMITATION, WARRANTIES OF QUALITY, PERFORMANCE, NONINFRINGEMENT, * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. NOR ARE THERE ANY * WARRANTIES CREATED BY A COURSE OR DEALING, COURSE OF PERFORMANCE OR TRADE * USAGE. FURTHERMORE, THERE ARE NO WARRANTIES THAT THE SOFTWARE WILL MEET * YOUR NEEDS OR BE FREE FROM ERRORS, OR THAT THE OPERATION OF THE SOFTWARE * WILL BE UNINTERRUPTED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * @Author: Hans Zandbelt - hans.zandbelt@openidc.com */ #include "metrics.h" #include "proto/proto.h" #include "util.h" /* * indicate whether the incoming HTTP POST request is an OpenID Connect Authorization Response */ apr_byte_t oidc_proto_response_is_post(request_rec *r, oidc_cfg_t *cfg) { /* prereq: this is a call to the configured redirect_uri; see if it is a POST */ return (r->method_number == M_POST); } /* * indicate whether the incoming HTTP GET request is an OpenID Connect Authorization Response */ apr_byte_t oidc_proto_response_is_redirect(request_rec *r, oidc_cfg_t *cfg) { /* prereq: this is a call to the configured redirect_uri; see if it is a GET with id_token or code * parameters */ return ((r->method_number == M_GET) && (oidc_util_request_has_parameter(r, OIDC_PROTO_ID_TOKEN) || oidc_util_request_has_parameter(r, OIDC_PROTO_CODE))); } /* * check the required parameters for the various flows after resolving the authorization code */ static apr_byte_t oidc_proto_validate_code_response(request_rec *r, const char *response_type, char *id_token, char *access_token, char *token_type) { oidc_debug(r, "enter"); /* * check id_token parameter */ if (!oidc_util_spaced_string_contains(r->pool, response_type, OIDC_PROTO_RESPONSE_TYPE_IDTOKEN)) { if (id_token == NULL) { oidc_error(r, "requested flow is \"%s\" but no \"%s\" parameter found in the code response", response_type, OIDC_PROTO_ID_TOKEN); return FALSE; } } else { if (id_token != NULL) { oidc_warn(r, "requested flow is \"%s\" but there is an \"%s\" parameter in the code response that " "will be dropped", response_type, OIDC_PROTO_ID_TOKEN); } } /* * check access_token parameter */ if (!oidc_util_spaced_string_contains(r->pool, response_type, OIDC_PROTO_RESPONSE_TYPE_TOKEN)) { if (access_token == NULL) { oidc_error(r, "requested flow is \"%s\" but no \"%s\" parameter found in the code response", response_type, OIDC_PROTO_ACCESS_TOKEN); return FALSE; } if (token_type == NULL) { oidc_error(r, "requested flow is \"%s\" but no \"%s\" parameter found in the code response", response_type, OIDC_PROTO_TOKEN_TYPE); return FALSE; } } else { if (access_token != NULL) { oidc_warn(r, "requested flow is \"%s\" but there is an \"%s\" parameter in the code response that " "will be dropped", response_type, OIDC_PROTO_ACCESS_TOKEN); } if (token_type != NULL) { oidc_warn(r, "requested flow is \"%s\" but there is a \"%s\" parameter in the code response that " "will be dropped", response_type, OIDC_PROTO_TOKEN_TYPE); } } return TRUE; } /* * validate the response parameters provided by the OP against the requested response type */ static apr_byte_t oidc_proto_validate_response_type(request_rec *r, const char *requested_response_type, const char *code, const char *id_token, const char *access_token) { if (oidc_util_spaced_string_contains(r->pool, requested_response_type, OIDC_PROTO_RESPONSE_TYPE_CODE)) { if (code == NULL) { oidc_error( r, "the requested response type was (%s) but the response does not contain a \"%s\" parameter", requested_response_type, OIDC_PROTO_CODE); return FALSE; } } else if (code != NULL) { oidc_error(r, "the requested response type was (%s) but the response contains a \"%s\" parameter", requested_response_type, OIDC_PROTO_CODE); return FALSE; } if (oidc_util_spaced_string_contains(r->pool, requested_response_type, OIDC_PROTO_RESPONSE_TYPE_IDTOKEN)) { if (id_token == NULL) { oidc_error(r, "the requested response type was (%s) but the response does not contain an \"%s\" " "parameter", requested_response_type, OIDC_PROTO_ID_TOKEN); return FALSE; } } else if (id_token != NULL) { oidc_error(r, "the requested response type was (%s) but the response contains an \"%s\" parameter", requested_response_type, OIDC_PROTO_ID_TOKEN); return FALSE; } if (oidc_util_spaced_string_contains(r->pool, requested_response_type, OIDC_PROTO_RESPONSE_TYPE_TOKEN)) { if (access_token == NULL) { oidc_error(r, "the requested response type was (%s) but the response does not contain an \"%s\" " "parameter", requested_response_type, OIDC_PROTO_ACCESS_TOKEN); return FALSE; } } else if (access_token != NULL) { oidc_error(r, "the requested response type was (%s) but the response contains an \"%s\" parameter", requested_response_type, OIDC_PROTO_ACCESS_TOKEN); return FALSE; } return TRUE; } /* * validate the response mode used by the OP against the requested response mode */ static apr_byte_t oidc_proto_validate_response_mode(request_rec *r, oidc_proto_state_t *proto_state, const char *response_mode, const char *default_response_mode) { const char *requested_response_mode = oidc_proto_state_get_response_mode(proto_state); if (requested_response_mode == NULL) requested_response_mode = default_response_mode; if (_oidc_strcmp(requested_response_mode, response_mode) != 0) { oidc_error(r, "requested response mode (%s) does not match the response mode used by the OP (%s)", requested_response_mode, response_mode); return FALSE; } return TRUE; } /* * validate the client_id/iss provided by the OP against the client_id/iss registered with the provider that the request * was sent to */ static apr_byte_t oidc_proto_validate_issuer_client_id(request_rec *r, const char *configured_issuer, const char *response_issuer, int require_issuer, const char *configured_client_id, const char *response_client_id) { if (response_issuer != NULL) { if (_oidc_strcmp(configured_issuer, response_issuer) != 0) { oidc_error( r, "configured issuer (%s) does not match the issuer provided in the response by the OP (%s)", configured_issuer, response_issuer); return FALSE; } } else if (require_issuer) { oidc_error(r, "no required \"iss\" parameter provided in the response by the OP"); return FALSE; } if (response_client_id != NULL) { if (_oidc_strcmp(configured_client_id, response_client_id) != 0) { oidc_error(r, "configured client_id (%s) does not match the client_id provided in the response by " "the OP (%s)", configured_client_id, response_client_id); return FALSE; } } oidc_debug(r, "iss and/or client_id matched OK: %s, %s, %s, %s", response_issuer, configured_issuer, response_client_id, configured_client_id); return TRUE; } /* * helper function to validate both the response type and the response mode in a single function call */ static apr_byte_t oidc_proto_validate_response_type_mode_issuer(request_rec *r, const char *requested_response_type, apr_table_t *params, oidc_proto_state_t *proto_state, const char *response_mode, const char *default_response_mode, const char *issuer, int require_issuer, const char *c_client_id) { const char *code = apr_table_get(params, OIDC_PROTO_CODE); const char *id_token = apr_table_get(params, OIDC_PROTO_ID_TOKEN); const char *access_token = apr_table_get(params, OIDC_PROTO_ACCESS_TOKEN); const char *iss = apr_table_get(params, OIDC_PROTO_ISS); const char *client_id = apr_table_get(params, OIDC_PROTO_CLIENT_ID); if (oidc_proto_validate_issuer_client_id(r, issuer, iss, require_issuer, c_client_id, client_id) == FALSE) return FALSE; if (oidc_proto_validate_response_type(r, requested_response_type, code, id_token, access_token) == FALSE) return FALSE; if (oidc_proto_validate_response_mode(r, proto_state, response_mode, default_response_mode) == FALSE) return FALSE; return TRUE; } /* * parse and id_token and check the c_hash if the code is provided */ static apr_byte_t oidc_proto_parse_idtoken_and_validate_code(request_rec *r, oidc_cfg_t *c, oidc_proto_state_t *proto_state, oidc_provider_t *provider, const char *response_type, apr_table_t *params, oidc_jwt_t **jwt, apr_byte_t must_validate_code) { const char *code = apr_table_get(params, OIDC_PROTO_CODE); const char *id_token = apr_table_get(params, OIDC_PROTO_ID_TOKEN); apr_byte_t is_code_flow = (oidc_util_spaced_string_contains(r->pool, response_type, OIDC_PROTO_RESPONSE_TYPE_CODE) == TRUE) && (oidc_util_spaced_string_contains(r->pool, response_type, OIDC_PROTO_RESPONSE_TYPE_IDTOKEN) == FALSE); const char *nonce = oidc_proto_state_get_nonce(proto_state); if (oidc_proto_idtoken_parse(r, c, provider, id_token, nonce, jwt, is_code_flow) == FALSE) return FALSE; if ((must_validate_code == TRUE) && (oidc_proto_idtoken_validate_code(r, provider, *jwt, response_type, code) == FALSE)) { oidc_jwt_destroy(*jwt); *jwt = NULL; return FALSE; } return TRUE; } /* * resolves the code received from the OP in to an id_token, access_token and refresh_token */ static apr_byte_t oidc_proto_resolve_code(request_rec *r, oidc_cfg_t *cfg, oidc_provider_t *provider, const char *code, const char *code_verifier, char **id_token, char **access_token, char **token_type, int *expires_in, char **refresh_token, const char *state) { oidc_debug(r, "enter"); /* assemble the parameters for a call to the token endpoint */ apr_table_t *params = apr_table_make(r->pool, 5); apr_table_setn(params, OIDC_PROTO_GRANT_TYPE, OIDC_PROTO_GRANT_TYPE_AUTHZ_CODE); apr_table_setn(params, OIDC_PROTO_CODE, code); apr_table_set(params, OIDC_PROTO_REDIRECT_URI, oidc_util_redirect_uri(r, cfg)); if (code_verifier) apr_table_setn(params, OIDC_PROTO_CODE_VERIFIER, code_verifier); /* add state to mitigate IDP mixup attacks, only useful in a multi-provider setup */ if ((oidc_cfg_metadata_dir_get(cfg) != NULL) && (state)) apr_table_setn(params, OIDC_PROTO_STATE, state); return oidc_proto_token_endpoint_request(r, cfg, provider, params, id_token, access_token, token_type, expires_in, refresh_token); } /* * resolve the code against the token endpoint and validate the response that is returned by the OP */ static apr_byte_t oidc_proto_resolve_code_and_validate_response(request_rec *r, oidc_cfg_t *c, oidc_provider_t *provider, const char *response_type, apr_table_t *params, oidc_proto_state_t *proto_state) { char *id_token = NULL; char *access_token = NULL; char *token_type = NULL; int expires_in = -1; char *refresh_token = NULL; char *code_verifier = NULL; if (oidc_proto_profile_pkce_get(provider) != &oidc_pkce_none) oidc_proto_profile_pkce_get(provider)->verifier(r, oidc_proto_state_get_pkce_state(proto_state), &code_verifier); const char *state = oidc_proto_state_get_state(proto_state); if (oidc_proto_resolve_code(r, c, provider, apr_table_get(params, OIDC_PROTO_CODE), code_verifier, &id_token, &access_token, &token_type, &expires_in, &refresh_token, state) == FALSE) { oidc_error(r, "failed to resolve the code"); OIDC_METRICS_COUNTER_INC(r, c, OM_PROVIDER_TOKEN_ERROR); return FALSE; } if (oidc_proto_validate_code_response(r, response_type, id_token, access_token, token_type) == FALSE) { oidc_error(r, "code response validation failed"); return FALSE; } /* don't override parameters that may already have been (rightfully) set in the authorization response */ if ((apr_table_get(params, OIDC_PROTO_ID_TOKEN) == NULL) && (id_token != NULL)) { apr_table_set(params, OIDC_PROTO_ID_TOKEN, id_token); } /* override access token if returned from the token endpoint in the backchannel */ if (access_token != NULL) { apr_table_set(params, OIDC_PROTO_ACCESS_TOKEN, access_token); if (token_type != NULL) apr_table_set(params, OIDC_PROTO_TOKEN_TYPE, token_type); if (expires_in != -1) apr_table_setn(params, OIDC_PROTO_EXPIRES_IN, apr_psprintf(r->pool, "%d", expires_in)); } /* refresh token should not have been set before */ if (refresh_token != NULL) { apr_table_set(params, OIDC_PROTO_REFRESH_TOKEN, refresh_token); } return TRUE; } /* * handle the "code id_token" response type */ apr_byte_t oidc_proto_response_code_idtoken(request_rec *r, oidc_cfg_t *c, oidc_proto_state_t *proto_state, oidc_provider_t *provider, apr_table_t *params, const char *response_mode, oidc_jwt_t **jwt) { oidc_debug(r, "enter"); static const char *response_type = OIDC_PROTO_RESPONSE_TYPE_CODE_IDTOKEN; if (oidc_proto_validate_response_type_mode_issuer( r, response_type, params, proto_state, response_mode, OIDC_PROTO_RESPONSE_MODE_FRAGMENT, oidc_cfg_provider_issuer_get(provider), oidc_proto_profile_response_require_iss_get(provider), oidc_cfg_provider_client_id_get(provider)) == FALSE) return FALSE; if (oidc_proto_parse_idtoken_and_validate_code(r, c, proto_state, provider, response_type, params, jwt, TRUE) == FALSE) return FALSE; /* clear parameters that should only be set from the token endpoint */ apr_table_unset(params, OIDC_PROTO_ACCESS_TOKEN); apr_table_unset(params, OIDC_PROTO_TOKEN_TYPE); apr_table_unset(params, OIDC_PROTO_EXPIRES_IN); apr_table_unset(params, OIDC_PROTO_REFRESH_TOKEN); if (oidc_proto_resolve_code_and_validate_response(r, c, provider, response_type, params, proto_state) == FALSE) return FALSE; return TRUE; } /* * handle the "code token" response type */ apr_byte_t oidc_proto_response_code_token(request_rec *r, oidc_cfg_t *c, oidc_proto_state_t *proto_state, oidc_provider_t *provider, apr_table_t *params, const char *response_mode, oidc_jwt_t **jwt) { oidc_debug(r, "enter"); static const char *response_type = OIDC_PROTO_RESPONSE_TYPE_CODE_TOKEN; if (oidc_proto_validate_response_type_mode_issuer( r, response_type, params, proto_state, response_mode, OIDC_PROTO_RESPONSE_MODE_FRAGMENT, oidc_cfg_provider_issuer_get(provider), oidc_proto_profile_response_require_iss_get(provider), oidc_cfg_provider_client_id_get(provider)) == FALSE) return FALSE; /* clear parameters that should only be set from the token endpoint */ apr_table_unset(params, OIDC_PROTO_ID_TOKEN); apr_table_unset(params, OIDC_PROTO_REFRESH_TOKEN); if (oidc_proto_resolve_code_and_validate_response(r, c, provider, response_type, params, proto_state) == FALSE) return FALSE; if (oidc_proto_parse_idtoken_and_validate_code(r, c, proto_state, provider, response_type, params, jwt, FALSE) == FALSE) return FALSE; return TRUE; } /* * handle the "code" response type */ apr_byte_t oidc_proto_response_code(request_rec *r, oidc_cfg_t *c, oidc_proto_state_t *proto_state, oidc_provider_t *provider, apr_table_t *params, const char *response_mode, oidc_jwt_t **jwt) { oidc_debug(r, "enter"); static const char *response_type = OIDC_PROTO_RESPONSE_TYPE_CODE; if (oidc_proto_validate_response_type_mode_issuer( r, response_type, params, proto_state, response_mode, OIDC_PROTO_RESPONSE_MODE_QUERY, oidc_cfg_provider_issuer_get(provider), oidc_proto_profile_response_require_iss_get(provider), oidc_cfg_provider_client_id_get(provider)) == FALSE) return FALSE; /* clear parameters that should only be set from the token endpoint */ apr_table_unset(params, OIDC_PROTO_ACCESS_TOKEN); apr_table_unset(params, OIDC_PROTO_TOKEN_TYPE); apr_table_unset(params, OIDC_PROTO_EXPIRES_IN); apr_table_unset(params, OIDC_PROTO_ID_TOKEN); apr_table_unset(params, OIDC_PROTO_REFRESH_TOKEN); if (oidc_proto_resolve_code_and_validate_response(r, c, provider, response_type, params, proto_state) == FALSE) return FALSE; /* * in this flow it is actually optional to check the code token against the c_hash */ if (oidc_proto_parse_idtoken_and_validate_code(r, c, proto_state, provider, response_type, params, jwt, TRUE) == FALSE) return FALSE; /* * in this flow it is actually optional to check the access token against the at_hash */ if ((apr_table_get(params, OIDC_PROTO_ACCESS_TOKEN) != NULL) && (oidc_proto_idtoken_validate_access_token(r, provider, *jwt, response_type, apr_table_get(params, OIDC_PROTO_ACCESS_TOKEN)) == FALSE)) { oidc_jwt_destroy(*jwt); *jwt = NULL; return FALSE; } return TRUE; } /* * helper function for implicit flows: shared code for "id_token token" and "id_token" */ static apr_byte_t oidc_proto_handle_implicit_flow(request_rec *r, oidc_cfg_t *c, const char *response_type, oidc_proto_state_t *proto_state, oidc_provider_t *provider, apr_table_t *params, const char *response_mode, oidc_jwt_t **jwt) { if (oidc_proto_validate_response_type_mode_issuer( r, response_type, params, proto_state, response_mode, OIDC_PROTO_RESPONSE_MODE_FRAGMENT, oidc_cfg_provider_issuer_get(provider), oidc_proto_profile_response_require_iss_get(provider), oidc_cfg_provider_client_id_get(provider)) == FALSE) return FALSE; if (oidc_proto_parse_idtoken_and_validate_code(r, c, proto_state, provider, response_type, params, jwt, TRUE) == FALSE) return FALSE; return TRUE; } /* * handle the "code id_token token" response type */ apr_byte_t oidc_proto_response_code_idtoken_token(request_rec *r, oidc_cfg_t *c, oidc_proto_state_t *proto_state, oidc_provider_t *provider, apr_table_t *params, const char *response_mode, oidc_jwt_t **jwt) { oidc_debug(r, "enter"); static const char *response_type = OIDC_PROTO_RESPONSE_TYPE_CODE_IDTOKEN_TOKEN; if (oidc_proto_handle_implicit_flow(r, c, response_type, proto_state, provider, params, response_mode, jwt) == FALSE) return FALSE; if (oidc_proto_idtoken_validate_access_token(r, provider, *jwt, response_type, apr_table_get(params, OIDC_PROTO_ACCESS_TOKEN)) == FALSE) return FALSE; /* clear parameters that should only be set from the token endpoint */ apr_table_unset(params, OIDC_PROTO_REFRESH_TOKEN); if (oidc_proto_resolve_code_and_validate_response(r, c, provider, response_type, params, proto_state) == FALSE) return FALSE; return TRUE; } /* * handle the "id_token token" response type */ apr_byte_t oidc_proto_response_idtoken_token(request_rec *r, oidc_cfg_t *c, oidc_proto_state_t *proto_state, oidc_provider_t *provider, apr_table_t *params, const char *response_mode, oidc_jwt_t **jwt) { oidc_debug(r, "enter"); static const char *response_type = OIDC_PROTO_RESPONSE_TYPE_IDTOKEN_TOKEN; if (oidc_proto_handle_implicit_flow(r, c, response_type, proto_state, provider, params, response_mode, jwt) == FALSE) return FALSE; if (oidc_proto_idtoken_validate_access_token(r, provider, *jwt, response_type, apr_table_get(params, OIDC_PROTO_ACCESS_TOKEN)) == FALSE) return FALSE; /* clear parameters that should not be part of this flow */ apr_table_unset(params, OIDC_PROTO_REFRESH_TOKEN); return TRUE; } /* * handle the "id_token" response type */ apr_byte_t oidc_proto_response_idtoken(request_rec *r, oidc_cfg_t *c, oidc_proto_state_t *proto_state, oidc_provider_t *provider, apr_table_t *params, const char *response_mode, oidc_jwt_t **jwt) { oidc_debug(r, "enter"); static const char *response_type = OIDC_PROTO_RESPONSE_TYPE_IDTOKEN; if (oidc_proto_handle_implicit_flow(r, c, response_type, proto_state, provider, params, response_mode, jwt) == FALSE) return FALSE; /* clear parameters that should not be part of this flow */ apr_table_unset(params, OIDC_PROTO_TOKEN_TYPE); apr_table_unset(params, OIDC_PROTO_EXPIRES_IN); apr_table_unset(params, OIDC_PROTO_REFRESH_TOKEN); return TRUE; } mod_auth_openidc-2.4.16.10/src/proto/state.c000066400000000000000000000225721476721736500205740ustar00rootroot00000000000000/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ /*************************************************************************** * Copyright (C) 2017-2025 ZmartZone Holding BV * All rights reserved. * * DISCLAIMER OF WARRANTIES: * * THE SOFTWARE PROVIDED HEREUNDER IS PROVIDED ON AN "AS IS" BASIS, WITHOUT * ANY WARRANTIES OR REPRESENTATIONS EXPRESS, IMPLIED OR STATUTORY; INCLUDING, * WITHOUT LIMITATION, WARRANTIES OF QUALITY, PERFORMANCE, NONINFRINGEMENT, * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. NOR ARE THERE ANY * WARRANTIES CREATED BY A COURSE OR DEALING, COURSE OF PERFORMANCE OR TRADE * USAGE. FURTHERMORE, THERE ARE NO WARRANTIES THAT THE SOFTWARE WILL MEET * YOUR NEEDS OR BE FREE FROM ERRORS, OR THAT THE OPERATION OF THE SOFTWARE * WILL BE UNINTERRUPTED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * @Author: Hans Zandbelt - hans.zandbelt@openidc.com */ #include "proto/proto.h" #include "util.h" #define OIDC_PROTO_STATE_ISSUER "i" #define OIDC_PROTO_STATE_ORIGINAL_URL "ou" #define OIDC_PROTO_STATE_ORIGINAL_METHOD "om" #define OIDC_PROTO_STATE_RESPONSE_MODE "rm" #define OIDC_PROTO_STATE_RESPONSE_TYPE "rt" #define OIDC_PROTO_STATE_NONCE "n" #define OIDC_PROTO_STATE_TIMESTAMP "t" #define OIDC_PROTO_STATE_PROMPT "pr" #define OIDC_PROTO_STATE_PKCE_STATE "ps" #define OIDC_PROTO_STATE_STATE "s" /* * retrieve a string from the state object */ static const char *oidc_proto_state_get_string_value(oidc_proto_state_t *proto_state, const char *name) { json_t *v = json_object_get(proto_state, name); return v ? json_string_value(v) : NULL; } /* * set a string value in the state object */ static void oidc_proto_state_set_string_value(oidc_proto_state_t *proto_state, const char *name, const char *value) { json_object_set_new(proto_state, name, json_string(value)); } /* * create a new state object */ oidc_proto_state_t *oidc_proto_state_new() { return json_object(); } /* * free up resources allocated for a state object */ void oidc_proto_state_destroy(oidc_proto_state_t *proto_state) { json_decref(proto_state); } /* * serialize a state object to a string (for logging/debugging purposes) */ char *oidc_proto_state_to_string(request_rec *r, oidc_proto_state_t *proto_state) { return oidc_util_encode_json(r->pool, proto_state, JSON_COMPACT); } /* * retrieve the issuer value from the state object */ const char *oidc_proto_state_get_issuer(oidc_proto_state_t *proto_state) { return oidc_proto_state_get_string_value(proto_state, OIDC_PROTO_STATE_ISSUER); } /* * retrieve the nonce value from the state object */ const char *oidc_proto_state_get_nonce(oidc_proto_state_t *proto_state) { return oidc_proto_state_get_string_value(proto_state, OIDC_PROTO_STATE_NONCE); } /* * retrieve the timestamp value from the state object */ apr_time_t oidc_proto_state_get_timestamp(oidc_proto_state_t *proto_state) { json_t *v = json_object_get(proto_state, OIDC_PROTO_STATE_TIMESTAMP); return v ? apr_time_from_sec(json_integer_value(v)) : -1; } /* * retrieve the prompt value from the state object */ const char *oidc_proto_state_get_prompt(oidc_proto_state_t *proto_state) { return oidc_proto_state_get_string_value(proto_state, OIDC_PROTO_STATE_PROMPT); } /* * retrieve the response type value from the state object */ const char *oidc_proto_state_get_response_type(oidc_proto_state_t *proto_state) { return oidc_proto_state_get_string_value(proto_state, OIDC_PROTO_STATE_RESPONSE_TYPE); } /* * retrieve the response mode value from the state object */ const char *oidc_proto_state_get_response_mode(oidc_proto_state_t *proto_state) { return oidc_proto_state_get_string_value(proto_state, OIDC_PROTO_STATE_RESPONSE_MODE); } /* * retrieve the original URL value from the state object */ const char *oidc_proto_state_get_original_url(oidc_proto_state_t *proto_state) { return oidc_proto_state_get_string_value(proto_state, OIDC_PROTO_STATE_ORIGINAL_URL); } /* * retrieve the original HTTP method value from the state object */ const char *oidc_proto_state_get_original_method(oidc_proto_state_t *proto_state) { return oidc_proto_state_get_string_value(proto_state, OIDC_PROTO_STATE_ORIGINAL_METHOD); } /* * retrieve the state (URL parameter) value from the state object */ const char *oidc_proto_state_get_state(oidc_proto_state_t *proto_state) { return oidc_proto_state_get_string_value(proto_state, OIDC_PROTO_STATE_STATE); } /* * retrieve the PKCE state value from the state object */ const char *oidc_proto_state_get_pkce_state(oidc_proto_state_t *proto_state) { return oidc_proto_state_get_string_value(proto_state, OIDC_PROTO_STATE_PKCE_STATE); } /* * set the state (URL parameter) value in the state object */ void oidc_proto_state_set_state(oidc_proto_state_t *proto_state, const char *state) { oidc_proto_state_set_string_value(proto_state, OIDC_PROTO_STATE_STATE, state); } /* * set the issuer value in the state object */ void oidc_proto_state_set_issuer(oidc_proto_state_t *proto_state, const char *issuer) { oidc_proto_state_set_string_value(proto_state, OIDC_PROTO_STATE_ISSUER, issuer); } /* * set the original URL value in the state object */ void oidc_proto_state_set_original_url(oidc_proto_state_t *proto_state, const char *original_url) { oidc_proto_state_set_string_value(proto_state, OIDC_PROTO_STATE_ORIGINAL_URL, original_url); } /* * set the original HTTP method value in the state object */ void oidc_proto_state_set_original_method(oidc_proto_state_t *proto_state, const char *original_method) { oidc_proto_state_set_string_value(proto_state, OIDC_PROTO_STATE_ORIGINAL_METHOD, original_method); } /* * set the response mode value in the state object */ void oidc_proto_state_set_response_mode(oidc_proto_state_t *proto_state, const char *response_mode) { oidc_proto_state_set_string_value(proto_state, OIDC_PROTO_STATE_RESPONSE_MODE, response_mode); } /* * set the response type value in the state object */ void oidc_proto_state_set_response_type(oidc_proto_state_t *proto_state, const char *response_type) { oidc_proto_state_set_string_value(proto_state, OIDC_PROTO_STATE_RESPONSE_TYPE, response_type); } /* * set the nonce value in the state object */ void oidc_proto_state_set_nonce(oidc_proto_state_t *proto_state, const char *nonce) { oidc_proto_state_set_string_value(proto_state, OIDC_PROTO_STATE_NONCE, nonce); } /* * set the prompt value in the state object */ void oidc_proto_state_set_prompt(oidc_proto_state_t *proto_state, const char *prompt) { oidc_proto_state_set_string_value(proto_state, OIDC_PROTO_STATE_PROMPT, prompt); } /* * set the PKCE state value in the state object */ void oidc_proto_state_set_pkce_state(oidc_proto_state_t *proto_state, const char *pkce_state) { oidc_proto_state_set_string_value(proto_state, OIDC_PROTO_STATE_PKCE_STATE, pkce_state); } /* * set the current time as timestamp value in the state object */ void oidc_proto_state_set_timestamp_now(oidc_proto_state_t *proto_state) { json_object_set_new(proto_state, OIDC_PROTO_STATE_TIMESTAMP, json_integer(apr_time_sec(apr_time_now()))); } /* * sanity check on the configuration of OIDCCryptoPassphrase */ static apr_byte_t oidc_proto_check_crypto_passphrase(request_rec *r, oidc_cfg_t *c, const char *action) { if (oidc_cfg_crypto_passphrase_secret1_get(c) == NULL) { oidc_error(r, "cannot %s state cookie because " OIDCCryptoPassphrase " is not set; please check your OIDC Provider configuration as well or avoid using AuthType " "openid-connect", action); return FALSE; } return TRUE; } /* * parse a state object from the provided cookie value */ oidc_proto_state_t *oidc_proto_state_from_cookie(request_rec *r, oidc_cfg_t *c, const char *cookieValue) { char *s_payload = NULL; json_t *result = NULL; if (oidc_proto_check_crypto_passphrase(r, c, "parse") == FALSE) return NULL; oidc_util_jwt_verify(r, oidc_cfg_crypto_passphrase_get(c), cookieValue, &s_payload); oidc_util_decode_json_object(r, s_payload, &result); return result; } /* * serialize a state object to a signed JWT cookie value */ char *oidc_proto_state_to_cookie(request_rec *r, oidc_cfg_t *c, oidc_proto_state_t *proto_state) { char *cookieValue = NULL; if (oidc_proto_check_crypto_passphrase(r, c, "create") == FALSE) return NULL; oidc_util_jwt_create(r, oidc_cfg_crypto_passphrase_get(c), oidc_util_encode_json(r->pool, proto_state, JSON_COMPACT), &cookieValue); return cookieValue; } mod_auth_openidc-2.4.16.10/src/proto/token.c000066400000000000000000000224071476721736500205710ustar00rootroot00000000000000/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ /*************************************************************************** * Copyright (C) 2017-2025 ZmartZone Holding BV * All rights reserved. * * DISCLAIMER OF WARRANTIES: * * THE SOFTWARE PROVIDED HEREUNDER IS PROVIDED ON AN "AS IS" BASIS, WITHOUT * ANY WARRANTIES OR REPRESENTATIONS EXPRESS, IMPLIED OR STATUTORY; INCLUDING, * WITHOUT LIMITATION, WARRANTIES OF QUALITY, PERFORMANCE, NONINFRINGEMENT, * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. NOR ARE THERE ANY * WARRANTIES CREATED BY A COURSE OR DEALING, COURSE OF PERFORMANCE OR TRADE * USAGE. FURTHERMORE, THERE ARE NO WARRANTIES THAT THE SOFTWARE WILL MEET * YOUR NEEDS OR BE FREE FROM ERRORS, OR THAT THE OPERATION OF THE SOFTWARE * WILL BE UNINTERRUPTED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * @Author: Hans Zandbelt - hans.zandbelt@openidc.com */ #include "metrics.h" #include "proto/proto.h" #include "util.h" /* * check that the access_token type is supported */ static apr_byte_t oidc_proto_validate_token_type(request_rec *r, oidc_provider_t *provider, const char *token_type) { /* we only support bearer/Bearer and DPoP/dpop */ if ((token_type != NULL) && (_oidc_strnatcasecmp(token_type, OIDC_PROTO_BEARER) != 0) && (_oidc_strnatcasecmp(token_type, OIDC_PROTO_DPOP) != 0) && (oidc_cfg_provider_userinfo_endpoint_url_get(provider) != NULL)) { oidc_error(r, "token_type is \"%s\" and UserInfo endpoint (%s) for issuer \"%s\" is set: can only deal " "with \"%s\" or \"%s\" authentication against a UserInfo endpoint!", token_type, oidc_cfg_provider_userinfo_endpoint_url_get(provider), oidc_cfg_provider_issuer_get(provider), OIDC_PROTO_BEARER, OIDC_PROTO_DPOP); return FALSE; } return TRUE; } /* * send the request to the token endpoint */ static apr_byte_t oidc_proto_token_endpoint_call(request_rec *r, oidc_cfg_t *cfg, oidc_provider_t *provider, apr_table_t *params, const char *basic_auth, const char *bearer_auth, const char *dpop, char **response, apr_hash_t *response_hdrs) { OIDC_METRICS_TIMING_START(r, cfg); // oidc_debug(r, "cert=%s, key=%s, pwd=%s", oidc_cfg_provider_token_endpoint_tls_client_cert_get(provider), // oidc_cfg_provider_token_endpoint_tls_client_key_get(provider), // oidc_cfg_provider_token_endpoint_tls_client_key_pwd_get(provider)); if (oidc_http_post_form(r, oidc_cfg_provider_token_endpoint_url_get(provider), params, basic_auth, bearer_auth, dpop, oidc_cfg_provider_ssl_validate_server_get(provider), response, NULL, response_hdrs, oidc_cfg_http_timeout_long_get(cfg), oidc_cfg_outgoing_proxy_get(cfg), oidc_cfg_dir_pass_cookies_get(r), oidc_cfg_provider_token_endpoint_tls_client_cert_get(provider), oidc_cfg_provider_token_endpoint_tls_client_key_get(provider), oidc_cfg_provider_token_endpoint_tls_client_key_pwd_get(provider)) == FALSE) { oidc_error(r, "error when calling the token endpoint (%s)", oidc_cfg_provider_token_endpoint_url_get(provider)); return FALSE; } OIDC_METRICS_TIMING_ADD(r, cfg, OM_PROVIDER_TOKEN); return TRUE; } /* * send a code/refresh request to the token endpoint and return the parsed contents */ apr_byte_t oidc_proto_token_endpoint_request(request_rec *r, oidc_cfg_t *cfg, oidc_provider_t *provider, apr_table_t *params, char **id_token, char **access_token, char **token_type, int *expires_in, char **refresh_token) { apr_byte_t rv = FALSE; char *basic_auth = NULL; char *bearer_auth = NULL; char *response = NULL; char *dpop = NULL; apr_hash_t *response_hdrs = NULL; json_t *j_result = NULL, *j_expires_in = NULL; /* add the token endpoint authentication credentials */ if (oidc_proto_token_endpoint_auth( r, cfg, oidc_cfg_provider_token_endpoint_auth_get(provider), oidc_cfg_provider_client_id_get(provider), oidc_cfg_provider_client_secret_get(provider), oidc_cfg_provider_client_keys_get(provider), oidc_proto_profile_token_endpoint_auth_aud(provider), params, NULL, &basic_auth, &bearer_auth) == FALSE) goto end; /* add any configured extra static parameters to the token endpoint */ oidc_util_table_add_query_encoded_params(r->pool, params, oidc_cfg_provider_token_endpoint_params_get(provider)); if (oidc_proto_profile_dpop_mode_get(provider) != OIDC_DPOP_MODE_OFF) { response_hdrs = apr_hash_make(r->pool); apr_hash_set(response_hdrs, OIDC_HTTP_HDR_AUTHORIZATION, APR_HASH_KEY_STRING, ""); apr_hash_set(response_hdrs, OIDC_HTTP_HDR_DPOP_NONCE, APR_HASH_KEY_STRING, ""); apr_hash_set(response_hdrs, OIDC_HTTP_HDR_CONTENT_TYPE, APR_HASH_KEY_STRING, ""); if ((oidc_proto_dpop_create(r, cfg, oidc_cfg_provider_token_endpoint_url_get(provider), "POST", NULL, NULL, &dpop) == FALSE) && (oidc_proto_profile_dpop_mode_get(provider) == OIDC_DPOP_MODE_REQUIRED)) goto end; } /* send the request to the token endpoint */ if (oidc_proto_token_endpoint_call(r, cfg, provider, params, basic_auth, bearer_auth, dpop, &response, response_hdrs) == FALSE) goto end; /* decode the response into a JSON object */ if (oidc_util_decode_json_object_err(r, response, &j_result, TRUE) == FALSE) goto end; /* check for errors, the response itself will have been logged already */ if (oidc_util_check_json_error(r, j_result) == TRUE) { dpop = NULL; if (oidc_proto_dpop_use_nonce(r, cfg, j_result, response_hdrs, oidc_cfg_provider_token_endpoint_url_get(provider), "POST", NULL, &dpop) == FALSE) goto end; if (oidc_proto_token_endpoint_call(r, cfg, provider, params, basic_auth, bearer_auth, dpop, &response, response_hdrs) == FALSE) goto end; json_decref(j_result); if (oidc_util_decode_json_and_check_error(r, response, &j_result) == FALSE) goto end; } /* get the id_token from the parsed response */ oidc_util_json_object_get_string(r->pool, j_result, OIDC_PROTO_ID_TOKEN, id_token, NULL); /* get the access_token from the parsed response */ oidc_util_json_object_get_string(r->pool, j_result, OIDC_PROTO_ACCESS_TOKEN, access_token, NULL); /* get the token type from the parsed response */ oidc_util_json_object_get_string(r->pool, j_result, OIDC_PROTO_TOKEN_TYPE, token_type, NULL); /* check if DPoP is required */ if ((oidc_proto_profile_dpop_mode_get(provider) == OIDC_DPOP_MODE_REQUIRED) && (_oidc_strnatcasecmp(*token_type, OIDC_PROTO_DPOP) != 0)) { oidc_error(r, "access token type is \"%s\" but \"%s\" is required", *token_type, OIDC_PROTO_DPOP); goto end; } /* check the new token type */ if (*token_type != NULL) { if (oidc_proto_validate_token_type(r, provider, *token_type) == FALSE) { oidc_warn(r, "access token type \"%s\" did not validate, dropping it", *token_type); *access_token = NULL; *token_type = NULL; } } /* get the access token expires_in value */ *expires_in = -1; j_expires_in = json_object_get(j_result, OIDC_PROTO_EXPIRES_IN); if (j_expires_in != NULL) { /* cater for string values (old Microsoft Entra ID / Azure AD) */ if (json_is_string(j_expires_in)) *expires_in = _oidc_str_to_int(json_string_value(j_expires_in), -1); else if (json_is_integer(j_expires_in)) *expires_in = json_integer_value(j_expires_in); } /* get the refresh_token from the parsed response */ oidc_util_json_object_get_string(r->pool, j_result, OIDC_PROTO_REFRESH_TOKEN, refresh_token, NULL); rv = TRUE; end: if (j_result) json_decref(j_result); return rv; } /* * refreshes the access_token/id_token /refresh_token received from the OP using the refresh_token */ apr_byte_t oidc_proto_token_refresh_request(request_rec *r, oidc_cfg_t *cfg, oidc_provider_t *provider, const char *rtoken, char **id_token, char **access_token, char **token_type, int *expires_in, char **refresh_token) { oidc_debug(r, "enter"); /* assemble the parameters for a call to the token endpoint */ apr_table_t *params = apr_table_make(r->pool, 5); apr_table_setn(params, OIDC_PROTO_GRANT_TYPE, OIDC_PROTO_GRANT_TYPE_REFRESH_TOKEN); apr_table_setn(params, OIDC_PROTO_REFRESH_TOKEN, rtoken); apr_table_setn(params, OIDC_PROTO_SCOPE, oidc_cfg_provider_scope_get(provider)); return oidc_proto_token_endpoint_request(r, cfg, provider, params, id_token, access_token, token_type, expires_in, refresh_token); } mod_auth_openidc-2.4.16.10/src/proto/userinfo.c000066400000000000000000000336221476721736500213040ustar00rootroot00000000000000/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ /*************************************************************************** * Copyright (C) 2017-2025 ZmartZone Holding BV * All rights reserved. * * DISCLAIMER OF WARRANTIES: * * THE SOFTWARE PROVIDED HEREUNDER IS PROVIDED ON AN "AS IS" BASIS, WITHOUT * ANY WARRANTIES OR REPRESENTATIONS EXPRESS, IMPLIED OR STATUTORY; INCLUDING, * WITHOUT LIMITATION, WARRANTIES OF QUALITY, PERFORMANCE, NONINFRINGEMENT, * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. NOR ARE THERE ANY * WARRANTIES CREATED BY A COURSE OR DEALING, COURSE OF PERFORMANCE OR TRADE * USAGE. FURTHERMORE, THERE ARE NO WARRANTIES THAT THE SOFTWARE WILL MEET * YOUR NEEDS OR BE FREE FROM ERRORS, OR THAT THE OPERATION OF THE SOFTWARE * WILL BE UNINTERRUPTED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * @Author: Hans Zandbelt - hans.zandbelt@openidc.com */ #include "metrics.h" #include "mod_auth_openidc.h" #include "proto/proto.h" #include "util.h" /* * parse a JWT response from the userinfo endpoint: at this point the response is not a JSON object * if the response is an encrypted and/or signed JWT, decrypt/verify it before validating it */ static apr_byte_t oidc_proto_userinfo_response_jwt_parse(request_rec *r, oidc_cfg_t *cfg, oidc_provider_t *provider, char **response, json_t **claims, char **userinfo_jwt) { apr_byte_t rv = FALSE; oidc_jose_error_t err; oidc_jwk_t *jwk = NULL; oidc_jwt_t *jwt = NULL; char *alg = NULL; char *payload = NULL; char *s_jwt_hdr = oidc_proto_jwt_header_peek(r, *response, &alg, NULL, NULL); if (s_jwt_hdr == NULL) { oidc_error(r, "no JSON/JWT could be parsed from the userinfo endpoint response"); goto end; } oidc_debug(r, "enter: JWT header=%s, userinfo_signed_response_alg=%s, userinfo_encrypted_response_alg=%s, " "userinfo_encrypted_response_enc=%s", s_jwt_hdr, oidc_cfg_provider_userinfo_signed_response_alg_get(provider), oidc_cfg_provider_userinfo_encrypted_response_alg_get(provider), oidc_cfg_provider_userinfo_encrypted_response_enc_get(provider)); if (oidc_util_create_symmetric_key(r, oidc_cfg_provider_client_secret_get(provider), oidc_alg2keysize(alg), OIDC_JOSE_ALG_SHA256, TRUE, &jwk) == FALSE) goto end; if (oidc_cfg_provider_userinfo_encrypted_response_alg_get(provider) != NULL) { if (oidc_jwe_decrypt(r->pool, *response, oidc_util_merge_symmetric_key(r->pool, oidc_cfg_private_keys_get(cfg), jwk), &payload, NULL, &err, TRUE) == FALSE) { oidc_error(r, "oidc_jwe_decrypt failed: %s", oidc_jose_e2s(r->pool, err)); goto end; } oidc_debug(r, "successfully decrypted JWE returned from userinfo endpoint: %s", payload); *response = payload; } if (oidc_cfg_provider_userinfo_signed_response_alg_get(provider) == NULL) { oidc_error(r, "no signed userinfo response algorithm configured to verify the JWT returned from the " "userinfo endpoint"); goto end; } if (oidc_jwt_parse(r->pool, *response, &jwt, oidc_util_merge_symmetric_key(r->pool, oidc_cfg_private_keys_get(cfg), jwk), FALSE, &err) == FALSE) { oidc_error(r, "oidc_jwt_parse failed: %s", oidc_jose_e2s(r->pool, err)); goto end; } oidc_debug(r, "successfully parsed JWT with header=%s, and payload=%s", jwt->header.value.str, jwt->payload.value.str); // discard the encryption key and load the signing key oidc_jwk_destroy(jwk); jwk = NULL; if (oidc_util_create_symmetric_key(r, oidc_cfg_provider_client_secret_get(provider), 0, NULL, TRUE, &jwk) == FALSE) goto end; if (oidc_proto_jwt_verify(r, cfg, jwt, oidc_cfg_provider_jwks_uri_get(provider), oidc_cfg_provider_ssl_validate_server_get(provider), oidc_util_merge_symmetric_key(r->pool, NULL, jwk), oidc_cfg_provider_userinfo_signed_response_alg_get(provider)) == FALSE) { oidc_error(r, "JWT signature could not be validated, aborting"); goto end; } oidc_debug(r, "successfully verified signed JWT returned from userinfo endpoint: %s", jwt->payload.value.str); *userinfo_jwt = apr_pstrdup(r->pool, *response); *claims = json_deep_copy(jwt->payload.value.json); *response = apr_pstrdup(r->pool, jwt->payload.value.str); rv = TRUE; end: if (jwt) oidc_jwt_destroy(jwt); if (jwk) oidc_jwk_destroy(jwk); return rv; } #define OIDC_COMPOSITE_CLAIM_NAMES "_claim_names" #define OIDC_COMPOSITE_CLAIM_SOURCES "_claim_sources" #define OIDC_COMPOSITE_CLAIM_JWT "JWT" #define OIDC_COMPOSITE_CLAIM_ACCESS_TOKEN OIDC_PROTO_ACCESS_TOKEN #define OIDC_COMPOSITE_CLAIM_ENDPOINT "endpoint" /* * if the userinfo response contains composite claims then resolve those */ static apr_byte_t oidc_proto_userinfo_request_composite_claims(request_rec *r, oidc_cfg_t *cfg, json_t *claims) { const char *key; json_t *value; void *iter; json_t *sources, *names, *decoded; oidc_jose_error_t err; oidc_jwk_t *jwk = NULL; oidc_debug(r, "enter"); names = json_object_get(claims, OIDC_COMPOSITE_CLAIM_NAMES); if ((names == NULL) || (!json_is_object(names))) return FALSE; sources = json_object_get(claims, OIDC_COMPOSITE_CLAIM_SOURCES); if ((sources == NULL) || (!json_is_object(sources))) { oidc_debug(r, "%s found, but no %s found", OIDC_COMPOSITE_CLAIM_NAMES, OIDC_COMPOSITE_CLAIM_SOURCES); return FALSE; } decoded = json_object(); iter = json_object_iter(sources); while (iter) { key = json_object_iter_key(iter); value = json_object_iter_value(iter); if ((value != NULL) && (json_is_object(value))) { json_t *jwt = json_object_get(value, OIDC_COMPOSITE_CLAIM_JWT); char *s_json = NULL; if ((jwt != NULL) && (json_is_string(jwt))) { s_json = apr_pstrdup(r->pool, json_string_value(jwt)); } else { const char *access_token = json_string_value(json_object_get(value, OIDC_COMPOSITE_CLAIM_ACCESS_TOKEN)); const char *endpoint = json_string_value(json_object_get(value, OIDC_COMPOSITE_CLAIM_ENDPOINT)); if ((access_token != NULL) && (endpoint != NULL)) { oidc_http_get( r, endpoint, NULL, NULL, access_token, NULL, oidc_cfg_provider_ssl_validate_server_get(oidc_cfg_provider_get(cfg)), &s_json, NULL, NULL, oidc_cfg_http_timeout_long_get(cfg), oidc_cfg_outgoing_proxy_get(cfg), oidc_cfg_dir_pass_cookies_get(r), NULL, NULL, NULL); } } if ((s_json != NULL) && (_oidc_strcmp(s_json, "") != 0)) { oidc_jwt_t *jwt = NULL; if (oidc_jwt_parse( r->pool, s_json, &jwt, oidc_util_merge_symmetric_key(r->pool, oidc_cfg_private_keys_get(cfg), jwk), FALSE, &err) == FALSE) { oidc_error(r, "could not parse JWT from aggregated claim \"%s\": %s", key, oidc_jose_e2s(r->pool, err)); } else { json_t *v = json_object_get(decoded, key); if (v == NULL) { v = json_object(); json_object_set_new(decoded, key, v); } oidc_util_json_merge(r, jwt->payload.value.json, v); } oidc_jwt_destroy(jwt); } } iter = json_object_iter_next(sources, iter); } iter = json_object_iter(names); while (iter) { key = json_object_iter_key(iter); const char *s_value = json_string_value(json_object_iter_value(iter)); if (s_value != NULL) { oidc_debug(r, "processing: %s: %s", key, s_value); json_t *values = json_object_get(decoded, s_value); if (values != NULL) { json_object_set(claims, key, json_object_get(values, key)); } else { oidc_warn(r, "no values for source \"%s\" found", s_value); } } else { oidc_warn(r, "no string value found for claim \"%s\"", key); } iter = json_object_iter_next(names, iter); } json_object_del(claims, OIDC_COMPOSITE_CLAIM_NAMES); json_object_del(claims, OIDC_COMPOSITE_CLAIM_SOURCES); json_decref(decoded); return TRUE; } /* * send the request to the userinfo endpoint */ static apr_byte_t oidc_proto_userinfo_endpoint_call(request_rec *r, oidc_cfg_t *cfg, oidc_provider_t *provider, const char *access_token, const char *dpop, char **response, long *response_code, apr_hash_t *response_hdrs) { OIDC_METRICS_TIMING_START(r, cfg); /* get the JSON response */ if (oidc_cfg_provider_userinfo_token_method_get(provider) == OIDC_USER_INFO_TOKEN_METHOD_HEADER) { if (oidc_http_get(r, oidc_cfg_provider_userinfo_endpoint_url_get(provider), NULL, NULL, access_token, dpop, oidc_cfg_provider_ssl_validate_server_get(provider), response, response_code, response_hdrs, oidc_cfg_http_timeout_long_get(cfg), oidc_cfg_outgoing_proxy_get(cfg), oidc_cfg_dir_pass_cookies_get(r), NULL, NULL, NULL) == FALSE) { OIDC_METRICS_COUNTER_INC(r, cfg, OM_PROVIDER_USERINFO_ERROR); return FALSE; } } else if (oidc_cfg_provider_userinfo_token_method_get(provider) == OIDC_USER_INFO_TOKEN_METHOD_POST) { apr_table_t *params = apr_table_make(r->pool, 4); apr_table_setn(params, OIDC_PROTO_ACCESS_TOKEN, access_token); if (oidc_http_post_form(r, oidc_cfg_provider_userinfo_endpoint_url_get(provider), params, NULL, NULL, dpop, oidc_cfg_provider_ssl_validate_server_get(provider), response, response_code, response_hdrs, oidc_cfg_http_timeout_long_get(cfg), oidc_cfg_outgoing_proxy_get(cfg), oidc_cfg_dir_pass_cookies_get(r), NULL, NULL, NULL) == FALSE) { OIDC_METRICS_COUNTER_INC(r, cfg, OM_PROVIDER_USERINFO_ERROR); return FALSE; } } else { oidc_error(r, "unsupported userinfo token presentation method: %d", oidc_cfg_provider_userinfo_token_method_get(provider)); return FALSE; } OIDC_METRICS_TIMING_ADD(r, cfg, OM_PROVIDER_USERINFO); return TRUE; } /* * get claims from the OP UserInfo endpoint using the provided access_token */ apr_byte_t oidc_proto_userinfo_request(request_rec *r, oidc_cfg_t *cfg, oidc_provider_t *provider, const char *id_token_sub, const char *access_token, const char *access_token_type, char **response, char **userinfo_jwt, long *response_code) { apr_byte_t rv = FALSE; char *dpop = NULL; apr_hash_t *response_hdrs = NULL; json_t *j_result = NULL; const char *method = oidc_cfg_provider_userinfo_token_method_get(provider) == OIDC_USER_INFO_TOKEN_METHOD_POST ? "POST" : "GET"; oidc_debug(r, "enter, endpoint=%s, access_token=%s, token_type=%s", oidc_cfg_provider_userinfo_endpoint_url_get(provider), access_token, access_token_type); if (_oidc_strnatcasecmp(access_token_type, OIDC_PROTO_DPOP) == 0) { response_hdrs = apr_hash_make(r->pool); apr_hash_set(response_hdrs, OIDC_HTTP_HDR_AUTHORIZATION, APR_HASH_KEY_STRING, ""); apr_hash_set(response_hdrs, OIDC_HTTP_HDR_DPOP_NONCE, APR_HASH_KEY_STRING, ""); apr_hash_set(response_hdrs, OIDC_HTTP_HDR_CONTENT_TYPE, APR_HASH_KEY_STRING, ""); if (oidc_proto_dpop_create(r, cfg, oidc_cfg_provider_userinfo_endpoint_url_get(provider), method, access_token, NULL, &dpop) == FALSE) goto end; } if (oidc_proto_userinfo_endpoint_call(r, cfg, provider, access_token, dpop, response, response_code, response_hdrs) == FALSE) goto end; if (oidc_util_decode_json_object_err(r, *response, &j_result, FALSE) == FALSE) { // must be a JWT if (oidc_proto_userinfo_response_jwt_parse(r, cfg, provider, response, &j_result, userinfo_jwt) == FALSE) goto end; } else if (oidc_util_check_json_error(r, j_result) == TRUE) { if (oidc_proto_dpop_use_nonce(r, cfg, j_result, response_hdrs, oidc_cfg_provider_userinfo_endpoint_url_get(provider), method, access_token, &dpop) == FALSE) // a regular error response goto end; if (oidc_proto_userinfo_endpoint_call(r, cfg, provider, access_token, dpop, response, response_code, response_hdrs) == FALSE) goto end; json_decref(j_result); if (oidc_util_decode_json_object_err(r, *response, &j_result, FALSE) == FALSE) { // must be a JWT if (oidc_proto_userinfo_response_jwt_parse(r, cfg, provider, response, &j_result, userinfo_jwt) == FALSE) goto end; } if (oidc_util_check_json_error(r, j_result) == TRUE) goto end; } if (oidc_proto_userinfo_request_composite_claims(r, cfg, j_result) == TRUE) *response = oidc_util_encode_json(r->pool, j_result, JSON_PRESERVE_ORDER | JSON_COMPACT); char *user_info_sub = NULL; oidc_jose_get_string(r->pool, j_result, OIDC_CLAIM_SUB, FALSE, &user_info_sub, NULL); oidc_debug(r, "id_token_sub=%s, user_info_sub=%s", id_token_sub, user_info_sub); if ((user_info_sub == NULL) && (apr_table_get(r->subprocess_env, "OIDC_NO_USERINFO_SUB") == NULL)) { oidc_error(r, "mandatory claim (\"%s\") was not returned from userinfo endpoint " "(https://openid.net/specs/openid-connect-core-1_0.html#UserInfoResponse)", OIDC_CLAIM_SUB); goto end; } if ((id_token_sub != NULL) && (user_info_sub != NULL)) { if (_oidc_strcmp(id_token_sub, user_info_sub) != 0) { oidc_error(r, "\"%s\" claim (\"%s\") returned from userinfo endpoint does not match the one in " "the id_token (\"%s\")", OIDC_CLAIM_SUB, user_info_sub, id_token_sub); goto end; } } rv = TRUE; end: if (j_result) json_decref(j_result); return rv; } mod_auth_openidc-2.4.16.10/src/session.c000066400000000000000000000661621476721736500177770ustar00rootroot00000000000000/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ /*************************************************************************** * Copyright (C) 2017-2025 ZmartZone Holding BV * Copyright (C) 2013-2017 Ping Identity Corporation * All rights reserved. * * DISCLAIMER OF WARRANTIES: * * THE SOFTWARE PROVIDED HEREUNDER IS PROVIDED ON AN "AS IS" BASIS, WITHOUT * ANY WARRANTIES OR REPRESENTATIONS EXPRESS, IMPLIED OR STATUTORY; INCLUDING, * WITHOUT LIMITATION, WARRANTIES OF QUALITY, PERFORMANCE, NONINFRINGEMENT, * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. NOR ARE THERE ANY * WARRANTIES CREATED BY A COURSE OR DEALING, COURSE OF PERFORMANCE OR TRADE * USAGE. FURTHERMORE, THERE ARE NO WARRANTIES THAT THE SOFTWARE WILL MEET * YOUR NEEDS OR BE FREE FROM ERRORS, OR THAT THE OPERATION OF THE SOFTWARE * WILL BE UNINTERRUPTED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * @Author: Hans Zandbelt - hans.zandbelt@openidc.com */ #include "cfg/dir.h" #include "metrics.h" #include "mod_auth_openidc.h" #include "util.h" /* the name of the remote-user attribute in the session */ #define OIDC_SESSION_REMOTE_USER_KEY "r" /* the name of the session expiry attribute in the session */ #define OIDC_SESSION_EXPIRY_KEY "e" /* the name of the session identifier in the session */ #define OIDC_SESSION_SESSION_ID "i" /* the name of the sub attribute in the session */ #define OIDC_SESSION_SUB_KEY "sub" /* the name of the sid attribute in the session */ #define OIDC_SESSION_SID_KEY "sid" /* * encode/serialize the session object/data into a string, possibly a serialized encrypted JWT when encryption is * requested */ static apr_byte_t oidc_session_encode(request_rec *r, oidc_cfg_t *c, oidc_session_t *z, char **s_value, apr_byte_t encrypt) { if (encrypt == FALSE) { *s_value = oidc_util_encode_json(r->pool, z->state, JSON_COMPACT); return (*s_value != NULL); } else if (oidc_cfg_crypto_passphrase_secret1_get(c) == NULL) { oidc_error(r, "cannot encrypt session state because " OIDCCryptoPassphrase " is not set"); return FALSE; } if (oidc_util_jwt_create(r, oidc_cfg_crypto_passphrase_get(c), oidc_util_encode_json(r->pool, z->state, JSON_COMPACT), s_value) == FALSE) return FALSE; return TRUE; } /* * parse a session object from the provided string, which may be an encrypted JWT is encryption is on */ static apr_byte_t oidc_session_decode(request_rec *r, oidc_cfg_t *c, oidc_session_t *z, const char *s_json, apr_byte_t encrypt) { char *s_payload = NULL; if (encrypt == FALSE) { return oidc_util_decode_json_object(r, s_json, &z->state); } else if (oidc_cfg_crypto_passphrase_secret1_get(c) == NULL) { oidc_error(r, "cannot decrypt session state because " OIDCCryptoPassphrase " is not set"); return FALSE; } if (oidc_util_jwt_verify(r, oidc_cfg_crypto_passphrase_get(c), s_json, &s_payload) == FALSE) { oidc_error(r, "could not verify secure JWT: cache value possibly corrupted"); return FALSE; } return oidc_util_decode_json_object(r, s_payload, &z->state); } /* * generate a unique identifier for a session */ void oidc_session_id_new(request_rec *r, oidc_session_t *z) { oidc_util_generate_random_hex_string(r, &z->uuid, 20); } /* * clear contents of a session */ static void oidc_session_clear(request_rec *r, oidc_session_t *z) { z->remote_user = NULL; // NB: don't clear sid or uuid z->expiry = 0; if (z->state) { json_decref(z->state); z->state = NULL; } } /* * load the session from the session cache, indexed by its uuid session id */ apr_byte_t oidc_session_load_cache_by_uuid(request_rec *r, oidc_cfg_t *c, const char *uuid, oidc_session_t *z) { char *stored_uuid = NULL; char *s_json = NULL; apr_byte_t rc = FALSE; rc = oidc_cache_get_session(r, uuid, &s_json); if ((rc == TRUE) && (s_json != NULL)) { rc = oidc_session_decode(r, c, z, s_json, FALSE); if (rc == TRUE) { z->uuid = apr_pstrdup(r->pool, uuid); /* compare the session id in the cache value so it allows us to detect cache corruption */ oidc_session_get(r, z, OIDC_SESSION_SESSION_ID, &stored_uuid); if ((stored_uuid == NULL) || (_oidc_strcmp(stored_uuid, uuid) != 0)) { oidc_error(r, "cache corruption detected: stored session id (%s) is not equal to " "requested session id (%s)", stored_uuid, uuid); /* delete the cache entry */ oidc_cache_set_session(r, z->uuid, NULL, 0); /* clear the session */ oidc_session_clear(r, z); rc = FALSE; } } } return rc; } /* * load the session from the cache using the cookie as the index */ static apr_byte_t oidc_session_load_cache(request_rec *r, oidc_session_t *z) { oidc_cfg_t *c = ap_get_module_config(r->server->module_config, &auth_openidc_module); apr_byte_t rc = FALSE; /* get the cookie that should be our uuid/key */ char *uuid = oidc_http_get_cookie(r, oidc_cfg_dir_cookie_get(r)); /* get the string-encoded session from the cache based on the key; decryption is based on the cache backend * config */ if (uuid != NULL) { rc = oidc_session_load_cache_by_uuid(r, c, uuid, z); /* cache backend experienced an error while attempting lookup */ if (rc == FALSE) { oidc_error(r, "cache backend failure for key %s", uuid); return FALSE; } /* cache backend does not contain an entry for the given key */ if (z->state == NULL) { /* delete the session cookie */ oidc_http_set_cookie(r, oidc_cfg_dir_cookie_get(r), "", 0, OIDC_HTTP_COOKIE_SAMESITE_NONE(c, r)); } } return rc; } static const char *oidc_session_samesite_cookie(request_rec *r, struct oidc_cfg_t *c, int first_time) { const char *rv = NULL; switch (oidc_cfg_cookie_same_site_get(c)) { case OIDC_SAMESITE_COOKIE_STRICT: rv = first_time ? OIDC_HTTP_COOKIE_SAMESITE_LAX : OIDC_HTTP_COOKIE_SAMESITE_STRICT; break; case OIDC_SAMESITE_COOKIE_LAX: rv = OIDC_HTTP_COOKIE_SAMESITE_LAX; break; case OIDC_SAMESITE_COOKIE_NONE: rv = OIDC_HTTP_COOKIE_SAMESITE_NONE(c, r); break; case OIDC_SAMESITE_COOKIE_DISABLED: break; default: break; } return rv; } /* * save the session to the cache using a cookie for the index */ static apr_byte_t oidc_session_save_cache(request_rec *r, oidc_session_t *z, apr_byte_t first_time) { oidc_cfg_t *c = ap_get_module_config(r->server->module_config, &auth_openidc_module); apr_byte_t rc = TRUE; if (z->state != NULL) { if (z->sid != NULL) { oidc_cache_set_sid(r, z->sid, z->uuid, z->expiry); oidc_session_set(r, z, OIDC_SESSION_SID_KEY, z->sid); } /* store the string-encoded session in the cache; encryption depends on cache backend settings */ char *s_value = NULL; if (oidc_session_encode(r, c, z, &s_value, FALSE) == FALSE) return FALSE; rc = oidc_cache_set_session(r, z->uuid, s_value, z->expiry); if (rc == TRUE) /* set the uuid in the cookie */ oidc_http_set_cookie(r, oidc_cfg_dir_cookie_get(r), z->uuid, oidc_cfg_persistent_session_cookie_get(c) ? z->expiry : -1, oidc_session_samesite_cookie(r, c, first_time)); } else { if (z->sid != NULL) oidc_cache_set_sid(r, z->sid, NULL, 0); /* clear the cookie */ oidc_http_set_cookie(r, oidc_cfg_dir_cookie_get(r), "", 0, OIDC_HTTP_COOKIE_SAMESITE_NONE(c, r)); /* remove the session from the cache */ rc = oidc_cache_set_session(r, z->uuid, NULL, 0); } return rc; } /* * load the session from a self-contained client-side cookie */ static apr_byte_t oidc_session_load_cookie(request_rec *r, oidc_cfg_t *c, oidc_session_t *z) { char *cookieValue = oidc_http_get_chunked_cookie(r, oidc_cfg_dir_cookie_get(r), oidc_cfg_session_cookie_chunk_size_get(c)); if ((cookieValue != NULL) && (oidc_session_decode(r, c, z, cookieValue, TRUE) == FALSE)) return FALSE; return TRUE; } /* * store the session in a self-contained client-side-only cookie storage */ static apr_byte_t oidc_session_save_cookie(request_rec *r, oidc_session_t *z, apr_byte_t first_time) { oidc_cfg_t *c = ap_get_module_config(r->server->module_config, &auth_openidc_module); char *cookieValue = ""; if ((z->state != NULL) && (oidc_session_encode(r, c, z, &cookieValue, TRUE) == FALSE)) return FALSE; oidc_http_set_chunked_cookie( r, oidc_cfg_dir_cookie_get(r), cookieValue, oidc_cfg_persistent_session_cookie_get(c) ? z->expiry : -1, oidc_cfg_session_cookie_chunk_size_get(c), (z->state == NULL) ? OIDC_HTTP_COOKIE_SAMESITE_NONE(c, r) : oidc_session_samesite_cookie(r, c, first_time)); return TRUE; } /* * retrieve an integer from the session state */ static inline int oidc_session_get_int(request_rec *r, oidc_session_t *z, const char *key, int def_val) { int v; oidc_util_json_object_get_int(z->state, key, &v, def_val); return v; } /* * retrieve a timestamp from the session state */ static inline apr_time_t oidc_session_get_key2timestamp(request_rec *r, oidc_session_t *z, const char *key) { int value = -1; oidc_util_json_object_get_int(z->state, key, &value, -1); return (value > -1) ? apr_time_from_sec(value) : -1; } /* * parse data from the session state into the session struct members */ apr_byte_t oidc_session_extract(request_rec *r, oidc_session_t *z) { apr_byte_t rc = FALSE; if (z->state == NULL) goto out; /* check whether it has expired */ z->expiry = oidc_session_get_key2timestamp(r, z, OIDC_SESSION_EXPIRY_KEY); if (apr_time_now() > z->expiry) { oidc_warn(r, "session restored from cache has expired"); oidc_session_clear(r, z); goto out; } oidc_session_get(r, z, OIDC_SESSION_REMOTE_USER_KEY, &z->remote_user); oidc_session_get(r, z, OIDC_SESSION_SID_KEY, &z->sid); oidc_session_get(r, z, OIDC_SESSION_SESSION_ID, &z->uuid); rc = TRUE; out: return rc; } /* * load a session from the cache/cookie */ apr_byte_t oidc_session_load(request_rec *r, oidc_session_t **zz) { oidc_cfg_t *c = ap_get_module_config(r->server->module_config, &auth_openidc_module); apr_byte_t rc = FALSE; /* allocate space for the session object and fill it */ oidc_session_t *z = (*zz = apr_pcalloc(r->pool, sizeof(oidc_session_t))); oidc_session_clear(r, z); oidc_session_id_new(r, z); z->sid = NULL; if (oidc_cfg_session_type_get(c) == OIDC_SESSION_TYPE_SERVER_CACHE) /* load the session from the cache */ rc = oidc_session_load_cache(r, z); /* if we get here we configured client-cookie or retrieving from the cache failed */ if ((oidc_cfg_session_type_get(c) == OIDC_SESSION_TYPE_CLIENT_COOKIE) || ((rc == FALSE) && oidc_cfg_session_cache_fallback_to_cookie_get(c))) /* load the session from a self-contained cookie */ rc = oidc_session_load_cookie(r, c, z); if (rc == TRUE) rc = oidc_session_extract(r, z); oidc_util_set_trace_parent(r, c, z->uuid); return rc; } /* * store an integer value into the session state */ static void oidc_session_set_int(request_rec *r, oidc_session_t *z, const char *key, int v) { if (z->state == NULL) z->state = json_object(); json_object_set_new(z->state, key, json_integer(v)); } /* * store a timestamp value into the session state */ static void oidc_session_set_timestamp(request_rec *r, oidc_session_t *z, const char *key, const apr_time_t timestamp) { if (timestamp > -1) oidc_session_set_int(r, z, key, apr_time_sec(timestamp)); } /* * save a session to cache/cookie */ apr_byte_t oidc_session_save(request_rec *r, oidc_session_t *z, apr_byte_t first_time) { oidc_cfg_t *c = ap_get_module_config(r->server->module_config, &auth_openidc_module); apr_byte_t rc = FALSE; if (z->state != NULL) { oidc_session_set(r, z, OIDC_SESSION_REMOTE_USER_KEY, z->remote_user); oidc_session_set_timestamp(r, z, OIDC_SESSION_EXPIRY_KEY, z->expiry); oidc_session_set(r, z, OIDC_SESSION_SESSION_ID, z->uuid); } if (oidc_cfg_session_type_get(c) == OIDC_SESSION_TYPE_SERVER_CACHE) /* store the session in the cache */ rc = oidc_session_save_cache(r, z, first_time); /* if we get here we configured client-cookie or saving in the cache failed */ if ((oidc_cfg_session_type_get(c) == OIDC_SESSION_TYPE_CLIENT_COOKIE) || ((rc == FALSE) && oidc_cfg_session_cache_fallback_to_cookie_get(c))) /* store the session in a self-contained cookie */ rc = oidc_session_save_cookie(r, z, first_time); return rc; } /* * free resources allocated for a session */ apr_byte_t oidc_session_free(request_rec *r, oidc_session_t *z) { oidc_session_clear(r, z); return TRUE; } /* * terminate a session */ apr_byte_t oidc_session_kill(request_rec *r, oidc_session_t *z) { r->user = NULL; if (z->state) { json_decref(z->state); z->state = NULL; } oidc_session_save(r, z, FALSE); return oidc_session_free(r, z); } /* * get a value from the session based on the name from a name/value pair */ apr_byte_t oidc_session_get(request_rec *r, oidc_session_t *z, const char *key, char **value) { /* just return the value for the key */ oidc_util_json_object_get_string(r->pool, z->state, key, (char **)value, NULL); return TRUE; } /* * set a name/value key pair in the session */ apr_byte_t oidc_session_set(request_rec *r, oidc_session_t *z, const char *key, const char *value) { /* only set it if non-NULL, otherwise delete the entry */ if (value) { if (z->state == NULL) z->state = json_object(); json_object_set_new(z->state, key, json_string(value)); } else if (z->state != NULL) { json_object_del(z->state, key); } return TRUE; } /* * session object keys */ /* key for storing the userinfo claims in the session context */ #define OIDC_SESSION_KEY_USERINFO_CLAIMS "uic" /* key for storing the userinfo JWT in the session context */ #define OIDC_SESSION_KEY_USERINFO_JWT "uij" /* key for storing the id_token in the session context */ #define OIDC_SESSION_KEY_IDTOKEN_CLAIMS "idc" /* key for storing the raw id_token in the session context */ #define OIDC_SESSION_KEY_IDTOKEN "idt" /* key for storing the access_token in the session context */ #define OIDC_SESSION_KEY_ACCESSTOKEN "at" /* key for storing the access_token type in the session context */ #define OIDC_SESSION_KEY_ACCESSTOKEN_TYPE "att" /* key for storing the access_token expiry in the session context */ #define OIDC_SESSION_KEY_ACCESSTOKEN_EXPIRES "ate" /* key for storing the refresh_token in the session context */ #define OIDC_SESSION_KEY_REFRESH_TOKEN "rt" /* key for storing maximum session duration in the session context */ #define OIDC_SESSION_KEY_SESSION_EXPIRES "se" /* key for storing the cookie domain in the session context */ #define OIDC_SESSION_KEY_COOKIE_DOMAIN "cd" /* key for storing last user info refresh timestamp in the session context */ #define OIDC_SESSION_KEY_USERINFO_LAST_REFRESH "uilr" /* key for storing last access token refresh timestamp in the session context */ #define OIDC_SESSION_KEY_ACCESS_TOKEN_LAST_REFRESH "atlr" /* key for storing request state */ #define OIDC_SESSION_KEY_REQUEST_STATE "rs" /* key for storing the original URL */ #define OIDC_SESSION_KEY_ORIGINAL_URL "ou" /* key for storing the session_state in the session context */ #define OIDC_SESSION_KEY_SESSION_STATE "ss" /* key for storing the issuer in the session context */ #define OIDC_SESSION_KEY_ISSUER "iss" /* key for storing the provider specific user info refresh interval */ #define OIDC_SESSION_KEY_USERINFO_REFRESH_INTERVAL "uir" /* key for storing whether this is a newly created session or not */ #define OIDC_SESSION_KEY_SESSION_IS_NEW "sn" /* * helper functions */ typedef const char *(*oidc_session_get_str_function)(request_rec *r, oidc_session_t *z); static json_t *oidc_session_get_str2json(request_rec *r, oidc_session_t *z, oidc_session_get_str_function session_get_str_fn) { json_t *json = NULL; const char *str = session_get_str_fn(r, z); if (str != NULL) oidc_util_decode_json_object(r, str, &json); return json; } static const char *oidc_session_get_key2string(request_rec *r, oidc_session_t *z, const char *key) { char *s_value = NULL; oidc_session_get(r, z, key, &s_value); return s_value; } #define OIDC_SESSION_WARN_CLAIM_SIZE 1024 * 8 #define OIDC_SESSION_WARN_CLAIM_SIZE_VAR "OIDC_SESSION_WARN_CLAIM_SIZE" /* * apply whitelisting/blacklisting and a JQ filter to the provided (serialized JSON) claims * session_key may refer to id_token claims or userinfo claims */ void oidc_session_set_filtered_claims(request_rec *r, oidc_session_t *z, const char *session_key, const char *claims) { oidc_cfg_t *c = ap_get_module_config(r->server->module_config, &auth_openidc_module); const char *name; json_t *src = NULL, *dst = NULL, *value = NULL; void *iter = NULL; apr_byte_t is_allowed; int warn_claim_size = OIDC_SESSION_WARN_CLAIM_SIZE; const char *s = NULL; if (oidc_util_decode_json_object(r, claims, &src) == FALSE) { oidc_session_set(r, z, session_key, NULL); return; } if (r->subprocess_env != NULL) { s = apr_table_get(r->subprocess_env, OIDC_SESSION_WARN_CLAIM_SIZE_VAR); if (s) { warn_claim_size = _oidc_str_to_int(s, OIDC_SESSION_WARN_CLAIM_SIZE); oidc_debug(r, "warn_claim_size set to %d in environment variable %s", warn_claim_size, OIDC_SESSION_WARN_CLAIM_SIZE_VAR); } } dst = json_object(); iter = json_object_iter(src); while (iter) { is_allowed = TRUE; name = json_object_iter_key(iter); value = json_object_iter_value(iter); if ((oidc_cfg_black_listed_claims_get(c) != NULL) && (apr_hash_get(oidc_cfg_black_listed_claims_get(c), name, APR_HASH_KEY_STRING) != NULL)) { oidc_debug(r, "removing blacklisted claim [%s]: '%s'", session_key, name); is_allowed = FALSE; } if ((is_allowed == TRUE) && (oidc_cfg_white_listed_claims_get(c) != NULL) && (apr_hash_get(oidc_cfg_white_listed_claims_get(c), name, APR_HASH_KEY_STRING) == NULL)) { oidc_debug(r, "removing non-whitelisted claim [%s]: '%s'", session_key, name); is_allowed = FALSE; } if (is_allowed == TRUE) { s = value ? oidc_util_encode_json(r->pool, value, JSON_PRESERVE_ORDER | JSON_COMPACT | JSON_ENCODE_ANY) : ""; if (_oidc_strlen(s) > warn_claim_size) oidc_warn(r, "(encoded) value size of [%s] claim \"%s\" is larger than %d; consider " "blacklisting it in OIDCBlackListedClaims " "or increase the warning limit with environment variable %s", session_key, name, warn_claim_size, OIDC_SESSION_WARN_CLAIM_SIZE_VAR); json_object_set(dst, name, value); if (_oidc_strcmp(session_key, OIDC_SESSION_KEY_USERINFO_CLAIMS) == 0) { OIDC_METRICS_COUNTER_INC_NAME_VALUE(r, c, OM_CLAIM_USER_INFO, name, json_is_string(value) ? json_string_value(value) : s); } else { OIDC_METRICS_COUNTER_INC_NAME_VALUE(r, c, OM_CLAIM_ID_TOKEN, name, json_is_string(value) ? json_string_value(value) : s); } } iter = json_object_iter_next(src, iter); } const char *filtered_claims = oidc_util_encode_json(r->pool, dst, JSON_PRESERVE_ORDER | JSON_COMPACT); filtered_claims = oidc_util_jq_filter(r, filtered_claims, oidc_util_apr_expr_exec(r, oidc_cfg_filter_claims_expr_get(c), TRUE)); json_decref(dst); json_decref(src); oidc_session_set(r, z, session_key, filtered_claims); } /* * userinfo claims */ void oidc_session_set_userinfo_claims(request_rec *r, oidc_session_t *z, const char *claims) { oidc_session_set_filtered_claims(r, z, OIDC_SESSION_KEY_USERINFO_CLAIMS, claims); } const char *oidc_session_get_userinfo_claims(request_rec *r, oidc_session_t *z) { return oidc_session_get_key2string(r, z, OIDC_SESSION_KEY_USERINFO_CLAIMS); } json_t *oidc_session_get_userinfo_claims_json(request_rec *r, oidc_session_t *z) { return oidc_session_get_str2json(r, z, oidc_session_get_userinfo_claims); } void oidc_session_set_userinfo_jwt(request_rec *r, oidc_session_t *z, const char *s_userinfo_jwt) { oidc_session_set(r, z, OIDC_SESSION_KEY_USERINFO_JWT, s_userinfo_jwt); } const char *oidc_session_get_userinfo_jwt(request_rec *r, oidc_session_t *z) { return oidc_session_get_key2string(r, z, OIDC_SESSION_KEY_USERINFO_JWT); } /* * id_token claims */ void oidc_session_set_idtoken_claims(request_rec *r, oidc_session_t *z, const char *idtoken_claims) { if (apr_table_get(r->subprocess_env, "OIDC_DONT_STORE_ID_TOKEN_CLAIMS_IN_SESSION") == NULL) oidc_session_set_filtered_claims(r, z, OIDC_SESSION_KEY_IDTOKEN_CLAIMS, idtoken_claims); } const char *oidc_session_get_idtoken_claims(request_rec *r, oidc_session_t *z) { return oidc_session_get_key2string(r, z, OIDC_SESSION_KEY_IDTOKEN_CLAIMS); } json_t *oidc_session_get_idtoken_claims_json(request_rec *r, oidc_session_t *z) { return oidc_session_get_str2json(r, z, oidc_session_get_idtoken_claims); } /* * compact serialized id_token */ void oidc_session_set_idtoken(request_rec *r, oidc_session_t *z, const char *s_id_token) { oidc_debug(r, "storing id_token in the session"); oidc_session_set(r, z, OIDC_SESSION_KEY_IDTOKEN, s_id_token); } const char *oidc_session_get_idtoken(request_rec *r, oidc_session_t *z) { return oidc_session_get_key2string(r, z, OIDC_SESSION_KEY_IDTOKEN); } /* * access token */ void oidc_session_set_access_token(request_rec *r, oidc_session_t *z, const char *access_token) { oidc_session_set(r, z, OIDC_SESSION_KEY_ACCESSTOKEN, access_token); } const char *oidc_session_get_access_token(request_rec *r, oidc_session_t *z) { return oidc_session_get_key2string(r, z, OIDC_SESSION_KEY_ACCESSTOKEN); } /* * access token type */ void oidc_session_set_access_token_type(request_rec *r, oidc_session_t *z, const char *token_type) { oidc_session_set(r, z, OIDC_SESSION_KEY_ACCESSTOKEN_TYPE, token_type); } const char *oidc_session_get_access_token_type(request_rec *r, oidc_session_t *z) { return oidc_session_get_key2string(r, z, OIDC_SESSION_KEY_ACCESSTOKEN_TYPE); } /* * access token expires */ void oidc_session_set_access_token_expires(request_rec *r, oidc_session_t *z, const int expires_in) { if (expires_in > -1) { oidc_debug(r, "storing access token expires_in in the session: %d", expires_in); oidc_session_set_timestamp(r, z, OIDC_SESSION_KEY_ACCESSTOKEN_EXPIRES, apr_time_now() + apr_time_from_sec(expires_in)); } } apr_time_t oidc_session_get_access_token_expires(request_rec *r, oidc_session_t *z) { return oidc_session_get_key2timestamp(r, z, OIDC_SESSION_KEY_ACCESSTOKEN_EXPIRES); } const char *oidc_session_get_access_token_expires2str(request_rec *r, oidc_session_t *z) { apr_time_t expires = oidc_session_get_access_token_expires(r, z); return (expires > -1) ? apr_psprintf(r->pool, "%" APR_TIME_T_FMT, apr_time_sec(expires)) : NULL; } /* * refresh token */ void oidc_session_set_refresh_token(request_rec *r, oidc_session_t *z, const char *refresh_token) { oidc_session_set(r, z, OIDC_SESSION_KEY_REFRESH_TOKEN, refresh_token); } const char *oidc_session_get_refresh_token(request_rec *r, oidc_session_t *z) { return oidc_session_get_key2string(r, z, OIDC_SESSION_KEY_REFRESH_TOKEN); } /* * session expires */ void oidc_session_set_session_expires(request_rec *r, oidc_session_t *z, const apr_time_t expires) { oidc_session_set_timestamp(r, z, OIDC_SESSION_KEY_SESSION_EXPIRES, expires); } apr_time_t oidc_session_get_session_expires(request_rec *r, oidc_session_t *z) { return oidc_session_get_key2timestamp(r, z, OIDC_SESSION_KEY_SESSION_EXPIRES); } /* * cookie domain */ void oidc_session_set_cookie_domain(request_rec *r, oidc_session_t *z, const char *cookie_domain) { oidc_session_set(r, z, OIDC_SESSION_KEY_COOKIE_DOMAIN, cookie_domain); } const char *oidc_session_get_cookie_domain(request_rec *r, oidc_session_t *z) { return oidc_session_get_key2string(r, z, OIDC_SESSION_KEY_COOKIE_DOMAIN); } /* * userinfo last refresh */ void oidc_session_set_userinfo_refresh_interval(request_rec *r, oidc_session_t *z, const int interval) { oidc_session_set_int(r, z, OIDC_SESSION_KEY_USERINFO_REFRESH_INTERVAL, interval); } int oidc_session_get_userinfo_refresh_interval(request_rec *r, oidc_session_t *z) { return oidc_session_get_int(r, z, OIDC_SESSION_KEY_USERINFO_REFRESH_INTERVAL, -1); } void oidc_session_reset_userinfo_last_refresh(request_rec *r, oidc_session_t *z) { oidc_session_set_timestamp(r, z, OIDC_SESSION_KEY_USERINFO_LAST_REFRESH, apr_time_now()); } apr_time_t oidc_session_get_userinfo_last_refresh(request_rec *r, oidc_session_t *z) { return oidc_session_get_key2timestamp(r, z, OIDC_SESSION_KEY_USERINFO_LAST_REFRESH); } /* * access_token last refresh */ void oidc_session_set_access_token_last_refresh(request_rec *r, oidc_session_t *z, apr_time_t ts) { oidc_session_set_timestamp(r, z, OIDC_SESSION_KEY_ACCESS_TOKEN_LAST_REFRESH, ts); } apr_time_t oidc_session_get_access_token_last_refresh(request_rec *r, oidc_session_t *z) { return oidc_session_get_key2timestamp(r, z, OIDC_SESSION_KEY_ACCESS_TOKEN_LAST_REFRESH); } /* * request state */ void oidc_session_set_request_state(request_rec *r, oidc_session_t *z, const char *request_state) { oidc_session_set(r, z, OIDC_SESSION_KEY_REQUEST_STATE, request_state); } const char *oidc_session_get_request_state(request_rec *r, oidc_session_t *z) { return oidc_session_get_key2string(r, z, OIDC_SESSION_KEY_REQUEST_STATE); } /* * original url */ void oidc_session_set_original_url(request_rec *r, oidc_session_t *z, const char *original_url) { oidc_session_set(r, z, OIDC_SESSION_KEY_ORIGINAL_URL, original_url); } const char *oidc_session_get_original_url(request_rec *r, oidc_session_t *z) { return oidc_session_get_key2string(r, z, OIDC_SESSION_KEY_ORIGINAL_URL); } /* * session state */ void oidc_session_set_session_state(request_rec *r, oidc_session_t *z, const char *session_state) { oidc_session_set(r, z, OIDC_SESSION_KEY_SESSION_STATE, session_state); } const char *oidc_session_get_session_state(request_rec *r, oidc_session_t *z) { return oidc_session_get_key2string(r, z, OIDC_SESSION_KEY_SESSION_STATE); } /* * issuer */ void oidc_session_set_issuer(request_rec *r, oidc_session_t *z, const char *issuer) { oidc_session_set(r, z, OIDC_SESSION_KEY_ISSUER, issuer); } const char *oidc_session_get_issuer(request_rec *r, oidc_session_t *z) { return oidc_session_get_key2string(r, z, OIDC_SESSION_KEY_ISSUER); } /* * new session */ void oidc_session_set_session_new(request_rec *r, oidc_session_t *z, const int is_new) { if (z->state == NULL) z->state = json_object(); if (is_new) json_object_set_new(z->state, OIDC_SESSION_KEY_SESSION_IS_NEW, json_integer(1)); else json_object_del(z->state, OIDC_SESSION_KEY_SESSION_IS_NEW); } int oidc_session_get_session_new(request_rec *r, oidc_session_t *z) { return oidc_session_get_int(r, z, OIDC_SESSION_KEY_SESSION_IS_NEW, 0); } mod_auth_openidc-2.4.16.10/src/session.h000066400000000000000000000154151476721736500177770ustar00rootroot00000000000000/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ /*************************************************************************** * Copyright (C) 2017-2025 ZmartZone Holding BV * All rights reserved. * * DISCLAIMER OF WARRANTIES: * * THE SOFTWARE PROVIDED HEREUNDER IS PROVIDED ON AN "AS IS" BASIS, WITHOUT * ANY WARRANTIES OR REPRESENTATIONS EXPRESS, IMPLIED OR STATUTORY; INCLUDING, * WITHOUT LIMITATION, WARRANTIES OF QUALITY, PERFORMANCE, NONINFRINGEMENT, * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. NOR ARE THERE ANY * WARRANTIES CREATED BY A COURSE OR DEALING, COURSE OF PERFORMANCE OR TRADE * USAGE. FURTHERMORE, THERE ARE NO WARRANTIES THAT THE SOFTWARE WILL MEET * YOUR NEEDS OR BE FREE FROM ERRORS, OR THAT THE OPERATION OF THE SOFTWARE * WILL BE UNINTERRUPTED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * @Author: Hans Zandbelt - hans.zandbelt@openidc.com */ #ifndef _MOD_AUTH_OPENIDC_SESSION_H_ #define _MOD_AUTH_OPENIDC_SESSION_H_ #include "cfg/cfg.h" #include typedef struct { char *uuid; /* unique id */ char *remote_user; /* user who owns this particular session */ json_t *state; /* the state for this session, encoded in a JSON object */ apr_time_t expiry; /* if > 0, the time of expiry of this session */ char *sid; } oidc_session_t; /* value that indicates to use server-side cache based session tracking */ #define OIDC_SESSION_TYPE_SERVER_CACHE 0 /* value that indicates to use client cookie based session tracking */ #define OIDC_SESSION_TYPE_CLIENT_COOKIE 1 apr_byte_t oidc_session_load(request_rec *r, oidc_session_t **z); apr_byte_t oidc_session_get(request_rec *r, oidc_session_t *z, const char *key, char **value); apr_byte_t oidc_session_set(request_rec *r, oidc_session_t *z, const char *key, const char *value); apr_byte_t oidc_session_save(request_rec *r, oidc_session_t *z, apr_byte_t first_time); apr_byte_t oidc_session_kill(request_rec *r, oidc_session_t *z); apr_byte_t oidc_session_free(request_rec *r, oidc_session_t *z); apr_byte_t oidc_session_extract(request_rec *r, oidc_session_t *z); apr_byte_t oidc_session_load_cache_by_uuid(request_rec *r, oidc_cfg_t *c, const char *uuid, oidc_session_t *z); void oidc_session_id_new(request_rec *r, oidc_session_t *z); void oidc_session_set_userinfo_jwt(request_rec *r, oidc_session_t *z, const char *userinfo_jwt); const char *oidc_session_get_userinfo_jwt(request_rec *r, oidc_session_t *z); void oidc_session_set_userinfo_claims(request_rec *r, oidc_session_t *z, const char *claims); const char *oidc_session_get_userinfo_claims(request_rec *r, oidc_session_t *z); json_t *oidc_session_get_userinfo_claims_json(request_rec *r, oidc_session_t *z); void oidc_session_set_idtoken_claims(request_rec *r, oidc_session_t *z, const char *idtoken_claims); const char *oidc_session_get_idtoken_claims(request_rec *r, oidc_session_t *z); json_t *oidc_session_get_idtoken_claims_json(request_rec *r, oidc_session_t *z); void oidc_session_set_idtoken(request_rec *r, oidc_session_t *z, const char *s_id_token); const char *oidc_session_get_idtoken(request_rec *r, oidc_session_t *z); void oidc_session_set_access_token(request_rec *r, oidc_session_t *z, const char *access_token); const char *oidc_session_get_access_token(request_rec *r, oidc_session_t *z); void oidc_session_set_access_token_type(request_rec *r, oidc_session_t *z, const char *token_type); const char *oidc_session_get_access_token_type(request_rec *r, oidc_session_t *z); void oidc_session_set_access_token_expires(request_rec *r, oidc_session_t *z, const int expires_in); apr_time_t oidc_session_get_access_token_expires(request_rec *r, oidc_session_t *z); const char *oidc_session_get_access_token_expires2str(request_rec *r, oidc_session_t *z); void oidc_session_set_refresh_token(request_rec *r, oidc_session_t *z, const char *refresh_token); const char *oidc_session_get_refresh_token(request_rec *r, oidc_session_t *z); void oidc_session_set_session_expires(request_rec *r, oidc_session_t *z, const apr_time_t expires); apr_time_t oidc_session_get_session_expires(request_rec *r, oidc_session_t *z); void oidc_session_set_cookie_domain(request_rec *r, oidc_session_t *z, const char *cookie_domain); const char *oidc_session_get_cookie_domain(request_rec *r, oidc_session_t *z); void oidc_session_reset_userinfo_last_refresh(request_rec *r, oidc_session_t *z); void oidc_session_set_userinfo_refresh_interval(request_rec *r, oidc_session_t *z, const int interval); int oidc_session_get_userinfo_refresh_interval(request_rec *r, oidc_session_t *z); apr_time_t oidc_session_get_userinfo_last_refresh(request_rec *r, oidc_session_t *z); void oidc_session_set_access_token_last_refresh(request_rec *r, oidc_session_t *z, apr_time_t ts); apr_time_t oidc_session_get_access_token_last_refresh(request_rec *r, oidc_session_t *z); void oidc_session_set_request_state(request_rec *r, oidc_session_t *z, const char *request_state); const char *oidc_session_get_request_state(request_rec *r, oidc_session_t *z); void oidc_session_set_original_url(request_rec *r, oidc_session_t *z, const char *original_url); const char *oidc_session_get_original_url(request_rec *r, oidc_session_t *z); void oidc_session_set_session_state(request_rec *r, oidc_session_t *z, const char *session_state); const char *oidc_session_get_session_state(request_rec *r, oidc_session_t *z); void oidc_session_set_issuer(request_rec *r, oidc_session_t *z, const char *issuer); const char *oidc_session_get_issuer(request_rec *r, oidc_session_t *z); void oidc_session_set_client_id(request_rec *r, oidc_session_t *z, const char *client_id); void oidc_session_set_session_new(request_rec *r, oidc_session_t *z, const int is_new); int oidc_session_get_session_new(request_rec *r, oidc_session_t *z); #endif /* _MOD_AUTH_OPENIDC_SESSION_H_ */ mod_auth_openidc-2.4.16.10/src/state.c000066400000000000000000000201021476721736500174140ustar00rootroot00000000000000/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ /*************************************************************************** * Copyright (C) 2017-2025 ZmartZone Holding BV * All rights reserved. * * DISCLAIMER OF WARRANTIES: * * THE SOFTWARE PROVIDED HEREUNDER IS PROVIDED ON AN "AS IS" BASIS, WITHOUT * ANY WARRANTIES OR REPRESENTATIONS EXPRESS, IMPLIED OR STATUTORY; INCLUDING, * WITHOUT LIMITATION, WARRANTIES OF QUALITY, PERFORMANCE, NONINFRINGEMENT, * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. NOR ARE THERE ANY * WARRANTIES CREATED BY A COURSE OR DEALING, COURSE OF PERFORMANCE OR TRADE * USAGE. FURTHERMORE, THERE ARE NO WARRANTIES THAT THE SOFTWARE WILL MEET * YOUR NEEDS OR BE FREE FROM ERRORS, OR THAT THE OPERATION OF THE SOFTWARE * WILL BE UNINTERRUPTED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * @Author: Hans Zandbelt - hans.zandbelt@openidc.com */ #include "state.h" #include "mod_auth_openidc.h" #include "proto/proto.h" #include "util.h" #include #define OIDC_STATE_SHA1_LEN 20 /* * return the name for the state cookie */ char *oidc_state_cookie_name(request_rec *r, const char *state) { return apr_psprintf(r->pool, "%s%s", oidc_cfg_dir_state_cookie_prefix_get(r), state); } /* * calculates a hash value based on request fingerprint plus a provided nonce string. */ char *oidc_state_browser_fingerprint(request_rec *r, oidc_cfg_t *c, const char *nonce) { unsigned char hash[OIDC_STATE_SHA1_LEN]; /* helper to hold to header values */ const char *value = NULL; /* the hash context */ apr_sha1_ctx_t sha1; char *result = NULL; oidc_debug(r, "enter"); /* Initialize the hash context */ apr_sha1_init(&sha1); if (oidc_cfg_state_input_headers_get(c) & OIDC_STATE_INPUT_HEADERS_X_FORWARDED_FOR) { /* get the X-FORWARDED-FOR header value */ value = oidc_http_hdr_in_x_forwarded_for_get(r); /* if we have a value for this header, concat it to the hash input */ if (value != NULL) apr_sha1_update(&sha1, value, _oidc_strlen(value)); } if (oidc_cfg_state_input_headers_get(c) & OIDC_STATE_INPUT_HEADERS_USER_AGENT) { /* get the USER-AGENT header value */ value = oidc_http_hdr_in_user_agent_get(r); /* if we have a value for this header, concat it to the hash input */ if (value != NULL) apr_sha1_update(&sha1, value, _oidc_strlen(value)); } /* get the remote client IP address or host name */ /* int remotehost_is_ip; value = ap_get_remote_host(r->connection, r->per_dir_config, REMOTE_NOLOOKUP, &remotehost_is_ip); apr_sha1_update(&sha1, value, _oidc_strlen(value)); */ /* concat the nonce parameter to the hash input */ apr_sha1_update(&sha1, nonce, _oidc_strlen(nonce)); /* finalize the hash input and calculate the resulting hash output */ apr_sha1_final(hash, &sha1); /* base64url-encode the resulting hash and return it */ oidc_util_base64url_encode(r, &result, (const char *)hash, OIDC_STATE_SHA1_LEN, TRUE); return result; } // element in a list of state cookies typedef struct oidc_state_cookies_t { char *name; apr_time_t timestamp; struct oidc_state_cookies_t *next; } oidc_state_cookies_t; /* * delete superfluous state cookies i.e. exceeding the maximum, starting with the oldest ones */ static int oidc_state_cookies_delete_oldest(request_rec *r, oidc_cfg_t *c, int number_of_valid_state_cookies, int max_number_of_state_cookies, oidc_state_cookies_t *first) { oidc_state_cookies_t *cur = NULL, *prev = NULL, *prev_oldest = NULL, *oldest = NULL; // loop over the list of state cookies, deleting the oldest one until we reach an acceptable number while (number_of_valid_state_cookies >= max_number_of_state_cookies) { oldest = first; prev_oldest = NULL; prev = first; cur = first ? first->next : NULL; // find the oldest state cookie in the list (stored in "oldest") while (cur) { if ((cur->timestamp < oldest->timestamp)) { oldest = cur; prev_oldest = prev; } prev = cur; cur = cur->next; } if (oldest) { oidc_warn(r, "deleting oldest state cookie: %s (time until expiry %" APR_TIME_T_FMT " seconds)", oldest->name, apr_time_sec(oldest->timestamp - apr_time_now())); oidc_http_set_cookie(r, oldest->name, "", 0, OIDC_HTTP_COOKIE_SAMESITE_NONE(c, r)); if (prev_oldest) prev_oldest->next = oldest->next; else first = first->next; } number_of_valid_state_cookies--; } return number_of_valid_state_cookies; } /* * clean state cookies that have expired i.e. for outstanding requests that will never return * successfully and return the number of remaining valid cookies/outstanding-requests while * doing so */ int oidc_state_cookies_clean_expired(request_rec *r, oidc_cfg_t *c, const char *currentCookieName, int delete_oldest) { int number_of_valid_state_cookies = 0; oidc_state_cookies_t *first = NULL, *last = NULL; char *cookie = NULL, *tokenizerCtx = NULL, *cookieName = NULL; char *cookies = apr_pstrdup(r->pool, oidc_http_hdr_in_cookie_get(r)); if (cookies != NULL) { cookie = apr_strtok(cookies, OIDC_STR_SEMI_COLON, &tokenizerCtx); while (cookie != NULL) { while (*cookie == OIDC_CHAR_SPACE) cookie++; if (_oidc_strstr(cookie, oidc_cfg_dir_state_cookie_prefix_get(r)) == cookie) { cookieName = cookie; while (cookie != NULL && *cookie != OIDC_CHAR_EQUAL) cookie++; if (*cookie == OIDC_CHAR_EQUAL) { *cookie = '\0'; cookie++; if ((currentCookieName == NULL) || (_oidc_strcmp(cookieName, currentCookieName) != 0)) { oidc_proto_state_t *proto_state = oidc_proto_state_from_cookie(r, c, cookie); if (proto_state != NULL) { json_int_t ts = oidc_proto_state_get_timestamp(proto_state); if (apr_time_now() > ts + apr_time_from_sec(oidc_cfg_state_timeout_get(c))) { oidc_warn( r, "state (%s) has expired (original_url=%s)", cookieName, oidc_proto_state_get_original_url(proto_state)); oidc_http_set_cookie( r, cookieName, "", 0, OIDC_HTTP_COOKIE_SAMESITE_NONE(c, r)); } else { if (first == NULL) { first = apr_pcalloc( r->pool, sizeof(oidc_state_cookies_t)); last = first; } else { last->next = apr_pcalloc( r->pool, sizeof(oidc_state_cookies_t)); last = last->next; } last->name = cookieName; last->timestamp = ts; last->next = NULL; number_of_valid_state_cookies++; } oidc_proto_state_destroy(proto_state); } else { oidc_warn( r, "state cookie could not be retrieved/decoded, deleting: %s", cookieName); oidc_http_set_cookie(r, cookieName, "", 0, OIDC_HTTP_COOKIE_SAMESITE_NONE(c, r)); } } } } cookie = apr_strtok(NULL, OIDC_STR_SEMI_COLON, &tokenizerCtx); } } if (delete_oldest > 0) number_of_valid_state_cookies = oidc_state_cookies_delete_oldest( r, c, number_of_valid_state_cookies, oidc_cfg_max_number_of_state_cookies_get(c), first); return number_of_valid_state_cookies; } mod_auth_openidc-2.4.16.10/src/state.h000066400000000000000000000045641476721736500174370ustar00rootroot00000000000000/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ /*************************************************************************** * Copyright (C) 2017-2025 ZmartZone Holding BV * All rights reserved. * * DISCLAIMER OF WARRANTIES: * * THE SOFTWARE PROVIDED HEREUNDER IS PROVIDED ON AN "AS IS" BASIS, WITHOUT * ANY WARRANTIES OR REPRESENTATIONS EXPRESS, IMPLIED OR STATUTORY; INCLUDING, * WITHOUT LIMITATION, WARRANTIES OF QUALITY, PERFORMANCE, NONINFRINGEMENT, * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. NOR ARE THERE ANY * WARRANTIES CREATED BY A COURSE OR DEALING, COURSE OF PERFORMANCE OR TRADE * USAGE. FURTHERMORE, THERE ARE NO WARRANTIES THAT THE SOFTWARE WILL MEET * YOUR NEEDS OR BE FREE FROM ERRORS, OR THAT THE OPERATION OF THE SOFTWARE * WILL BE UNINTERRUPTED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * @Author: Hans Zandbelt - hans.zandbelt@openidc.com */ #ifndef _MOD_AUTH_OPENIDC_STATE_H_ #define _MOD_AUTH_OPENIDC_STATE_H_ #include "cfg/cfg.h" char *oidc_state_cookie_name(request_rec *r, const char *state); char *oidc_state_browser_fingerprint(request_rec *r, oidc_cfg_t *c, const char *nonce); int oidc_state_cookies_clean_expired(request_rec *r, oidc_cfg_t *c, const char *currentCookieName, int delete_oldest); #endif /* _MOD_AUTH_OPENIDC_STATE_H_ */ mod_auth_openidc-2.4.16.10/src/util.c000066400000000000000000001774011476721736500172700ustar00rootroot00000000000000/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ /*************************************************************************** * Copyright (C) 2017-2025 ZmartZone Holding BV * Copyright (C) 2013-2017 Ping Identity Corporation * All rights reserved. * * DISCLAIMER OF WARRANTIES: * * THE SOFTWARE PROVIDED HEREUNDER IS PROVIDED ON AN "AS IS" BASIS, WITHOUT * ANY WARRANTIES OR REPRESENTATIONS EXPRESS, IMPLIED OR STATUTORY; INCLUDING, * WITHOUT LIMITATION, WARRANTIES OF QUALITY, PERFORMANCE, NONINFRINGEMENT, * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. NOR ARE THERE ANY * WARRANTIES CREATED BY A COURSE OR DEALING, COURSE OF PERFORMANCE OR TRADE * USAGE. FURTHERMORE, THERE ARE NO WARRANTIES THAT THE SOFTWARE WILL MEET * YOUR NEEDS OR BE FREE FROM ERRORS, OR THAT THE OPERATION OF THE SOFTWARE * WILL BE UNINTERRUPTED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * @Author: Hans Zandbelt - hans.zandbelt@openidc.com */ #include "util.h" #include "cfg/dir.h" #include "mod_auth_openidc.h" #include "proto/proto.h" #include "metrics.h" #include "pcre_subst.h" #ifdef USE_LIBJQ #include "jq.h" #endif #include #include #include #include #ifdef USE_URANDOM #include #include #include #define DEV_RANDOM "/dev/urandom" #endif /* * generate a number of random bytes, either using libapr or urandom (no per-request logging) */ apr_byte_t oidc_util_random_bytes(unsigned char *buf, apr_size_t length) { apr_byte_t rv = TRUE; #ifndef USE_URANDOM rv = (apr_generate_random_bytes(buf, length) == APR_SUCCESS); #else int fd = -1; do { apr_ssize_t rc; if (fd == -1) { fd = open(DEV_RANDOM, O_RDONLY); if (fd == -1) return errno; } do { rc = read(fd, buf, length); } while (rc == -1 && errno == EINTR); if (rc < 0) { int errnum = errno; close(fd); return errnum; } else if (rc == 0) { close(fd); fd = -1; /* force open() again */ } else { buf += rc; length -= rc; } } while (length > 0); close(fd); rv = TRUE; #endif return rv; } /* * generate a number of random bytes, either using libapr or urandom */ apr_byte_t oidc_util_generate_random_bytes(request_rec *r, unsigned char *buf, apr_size_t length) { apr_byte_t rv = TRUE; const char *gen = NULL; #ifndef USE_URANDOM gen = "apr"; #else gen = DEV_RANDOM; #endif oidc_debug(r, "oidc_util_random_bytes [%s] call for %" APR_SIZE_T_FMT " bytes", gen, length); rv = oidc_util_random_bytes(buf, length); oidc_debug(r, "oidc_util_random_bytes returned: %d", rv); return rv; } /* * generate a random string of (lowercase) hexadecimal characters, representing byte_len bytes */ apr_byte_t oidc_util_generate_random_hex_string(request_rec *r, char **hex_str, int byte_len) { unsigned char *bytes = apr_pcalloc(r->pool, byte_len); int i = 0; if (oidc_util_generate_random_bytes(r, bytes, byte_len) != TRUE) { oidc_error(r, "oidc_util_generate_random_bytes returned an error"); return FALSE; } *hex_str = ""; for (i = 0; i < byte_len; i++) *hex_str = apr_psprintf(r->pool, "%s%02x", *hex_str, bytes[i]); return TRUE; } /* * generate a random string value value of a specified byte length */ apr_byte_t oidc_util_generate_random_string(request_rec *r, char **output, int len) { unsigned char *bytes = apr_pcalloc(r->pool, len); if (oidc_util_generate_random_bytes(r, bytes, len) != TRUE) { oidc_error(r, "oidc_util_generate_random_bytes returned an error"); return FALSE; } if (oidc_util_base64url_encode(r, output, (const char *)bytes, len, TRUE) <= 0) { oidc_error(r, "oidc_base64url_encode returned an error"); return FALSE; } return TRUE; } /* * base64url encode a string */ int oidc_util_base64url_encode(request_rec *r, char **dst, const char *src, int src_len, int remove_padding) { if ((src == NULL) || (src_len <= 0)) { oidc_error(r, "not encoding anything; src=NULL and/or src_len<1"); return -1; } unsigned int enc_len = apr_base64_encode_len(src_len); char *enc = apr_palloc(r->pool, enc_len); apr_base64_encode(enc, src, src_len); unsigned int i = 0; while (enc[i] != '\0') { if (enc[i] == '+') enc[i] = '-'; if (enc[i] == '/') enc[i] = '_'; if (enc[i] == '=') enc[i] = ','; i++; } if (remove_padding) { /* remove /0 and padding */ if (enc_len > 0) enc_len--; if ((enc_len > 0) && (enc[enc_len - 1] == ',')) enc_len--; if ((enc_len > 0) && (enc[enc_len - 1] == ',')) enc_len--; enc[enc_len] = '\0'; } *dst = enc; return enc_len; } /* * parse a base64 encoded binary value from the provided string */ char *oidc_util_base64_decode(apr_pool_t *pool, const char *input, char **output, int *output_len) { int len = apr_base64_decode_len(input); *output = apr_pcalloc(pool, len); *output_len = apr_base64_decode(*output, input); if (*output_len <= 0) return apr_psprintf(pool, "base64-decoding of \"%s\" failed", input); return NULL; } /* * base64url decode a string */ int oidc_util_base64url_decode(apr_pool_t *pool, char **dst, const char *src) { if (src == NULL) { return -1; } char *dec = apr_pstrdup(pool, src); int i = 0; while (dec[i] != '\0') { if (dec[i] == '-') dec[i] = '+'; if (dec[i] == '_') dec[i] = '/'; if (dec[i] == ',') dec[i] = '='; i++; } switch (_oidc_strlen(dec) % 4) { case 0: break; case 2: dec = apr_pstrcat(pool, dec, "==", NULL); break; case 3: dec = apr_pstrcat(pool, dec, "=", NULL); break; default: return 0; } int dlen = -1; oidc_util_base64_decode(pool, dec, dst, &dlen); return dlen; } /* * return the serialized header part of a A256GCM encrypted JWT (input) */ static const char *oidc_util_jwt_hdr_dir_a256gcm(request_rec *r, char *input) { char *compact_encoded_jwt = NULL; char *p = NULL; static const char *_oidc_jwt_hdr_dir_a256gcm = NULL; static oidc_crypto_passphrase_t passphrase; if (_oidc_jwt_hdr_dir_a256gcm != NULL) return _oidc_jwt_hdr_dir_a256gcm; if (input == NULL) { passphrase.secret1 = "needs_non_empty_string"; passphrase.secret2 = NULL; oidc_util_jwt_create(r, &passphrase, "some_string", &compact_encoded_jwt); } else { compact_encoded_jwt = input; } p = _oidc_strstr(compact_encoded_jwt, ".."); if (p) { _oidc_jwt_hdr_dir_a256gcm = apr_pstrndup(r->server->process->pconf, compact_encoded_jwt, _oidc_strlen(compact_encoded_jwt) - _oidc_strlen(p) + 2); oidc_debug(r, "saved _oidc_jwt_hdr_dir_a256gcm header: %s", _oidc_jwt_hdr_dir_a256gcm); } return _oidc_jwt_hdr_dir_a256gcm; } /* * helper function to override a variable value with an optionally provided environment variable */ static apr_byte_t oidc_util_env_var_override(request_rec *r, const char *env_var_name, apr_byte_t return_when_set) { const char *s = NULL; if (r->subprocess_env == NULL) return !return_when_set; s = apr_table_get(r->subprocess_env, env_var_name); return (s != NULL) && (_oidc_strcmp(s, "true") == 0) ? return_when_set : !return_when_set; } #define OIDC_JWT_INTERNAL_NO_COMPRESS_ENV_VAR "OIDC_JWT_INTERNAL_NO_COMPRESS" /* * check if we need to compress (internal) encrypted JWTs or not */ static apr_byte_t oidc_util_jwt_internal_compress(request_rec *r) { // avoid compressing JWTs that need to be compatible with external producers/consumers return oidc_util_env_var_override(r, OIDC_JWT_INTERNAL_NO_COMPRESS_ENV_VAR, FALSE); } #define OIDC_JWT_INTERNAL_STRIP_HDR_ENV_VAR "OIDC_JWT_INTERNAL_STRIP_HDR" /* * check if we need to strip the header from (internal) encrypted JWTs or not */ static apr_byte_t oidc_util_jwt_internal_strip_header(request_rec *r) { // avoid stripping JWT headers that need to be compatible with external producers/consumers return oidc_util_env_var_override(r, OIDC_JWT_INTERNAL_STRIP_HDR_ENV_VAR, TRUE); } /* * create an encrypted JWT for internal purposes (i.e. state cookie, session cookie, or encrypted cache value) */ apr_byte_t oidc_util_jwt_create(request_rec *r, const oidc_crypto_passphrase_t *passphrase, const char *s_payload, char **compact_encoded_jwt) { apr_byte_t rv = FALSE; oidc_jose_error_t err; char *cser = NULL; int cser_len = 0; oidc_jwk_t *jwk = NULL; oidc_jwt_t *jwe = NULL; if (passphrase->secret1 == NULL) { oidc_error(r, "secret is not set"); goto end; } if (oidc_util_create_symmetric_key(r, passphrase->secret1, 0, OIDC_JOSE_ALG_SHA256, FALSE, &jwk) == FALSE) goto end; if (oidc_util_jwt_internal_compress(r)) { if (oidc_jose_compress(r->pool, s_payload, _oidc_strlen(s_payload), &cser, &cser_len, &err) == FALSE) { oidc_error(r, "oidc_jose_compress failed: %s", oidc_jose_e2s(r->pool, err)); goto end; } } else { cser = apr_pstrdup(r->pool, s_payload); cser_len = _oidc_strlen(s_payload); } jwe = oidc_jwt_new(r->pool, TRUE, FALSE); if (jwe == NULL) { oidc_error(r, "creating JWE failed"); goto end; } jwe->header.alg = apr_pstrdup(r->pool, CJOSE_HDR_ALG_DIR); jwe->header.enc = apr_pstrdup(r->pool, CJOSE_HDR_ENC_A256GCM); if (passphrase->secret2 != NULL) jwe->header.kid = apr_pstrdup(r->pool, "1"); if (oidc_jwt_encrypt(r->pool, jwe, jwk, cser, cser_len, compact_encoded_jwt, &err) == FALSE) { oidc_error(r, "encrypting JWT failed: %s", oidc_jose_e2s(r->pool, err)); goto end; } if ((*compact_encoded_jwt != NULL) && (oidc_util_jwt_internal_strip_header(r))) *compact_encoded_jwt += _oidc_strlen(oidc_util_jwt_hdr_dir_a256gcm(r, *compact_encoded_jwt)); rv = TRUE; end: if (jwe != NULL) oidc_jwt_destroy(jwe); if (jwk != NULL) oidc_jwk_destroy(jwk); return rv; } /* * verify an encrypted JWT for internal purposes */ apr_byte_t oidc_util_jwt_verify(request_rec *r, const oidc_crypto_passphrase_t *passphrase, const char *compact_encoded_jwt, char **s_payload) { apr_byte_t rv = FALSE; oidc_jose_error_t err; oidc_jwk_t *jwk = NULL; char *payload = NULL; int payload_len = 0; char *plaintext = NULL; int plaintext_len = 0; apr_hash_t *keys = NULL; char *alg = NULL; char *enc = NULL; char *kid = NULL; if (oidc_util_jwt_internal_strip_header(r)) compact_encoded_jwt = apr_pstrcat(r->pool, oidc_util_jwt_hdr_dir_a256gcm(r, NULL), compact_encoded_jwt, NULL); oidc_proto_jwt_header_peek(r, compact_encoded_jwt, &alg, &enc, &kid); if ((_oidc_strcmp(alg, CJOSE_HDR_ALG_DIR) != 0) || (_oidc_strcmp(enc, CJOSE_HDR_ENC_A256GCM) != 0)) { oidc_error(r, "corrupted JWE header, alg=\"%s\" enc=\"%s\"", alg, enc); goto end; } keys = apr_hash_make(r->pool); if ((passphrase->secret2 != NULL) && (kid == NULL)) { if (oidc_util_create_symmetric_key(r, passphrase->secret2, 0, OIDC_JOSE_ALG_SHA256, FALSE, &jwk) == FALSE) goto end; } else { if (oidc_util_create_symmetric_key(r, passphrase->secret1, 0, OIDC_JOSE_ALG_SHA256, FALSE, &jwk) == FALSE) goto end; } apr_hash_set(keys, "1", APR_HASH_KEY_STRING, jwk); if (oidc_jwe_decrypt(r->pool, compact_encoded_jwt, keys, &plaintext, &plaintext_len, &err, FALSE) == FALSE) { oidc_error(r, "decrypting JWE failed: %s", oidc_jose_e2s(r->pool, err)); goto end; } if (oidc_util_jwt_internal_compress(r)) { if (oidc_jose_uncompress(r->pool, (char *)plaintext, plaintext_len, &payload, &payload_len, &err) == FALSE) { oidc_error(r, "oidc_jose_uncompress failed: %s", oidc_jose_e2s(r->pool, err)); goto end; } } else { payload = plaintext; payload_len = plaintext_len; } *s_payload = apr_pstrndup(r->pool, payload, payload_len); rv = TRUE; end: if (jwk != NULL) oidc_jwk_destroy(jwk); return rv; } /* * convert a character to an ENVIRONMENT-variable-safe variant */ static int oidc_util_char_to_env(int c) { return apr_isalnum(c) ? apr_toupper(c) : '_'; } /* * compare two strings based on how they would be converted to an * environment variable, as per oidc_char_to_env. If len is specified * as less than zero, then the full strings will be compared. Returns * less than, equal to, or greater than zero based on whether the * first argument's conversion to an environment variable is less * than, equal to, or greater than the second. */ int oidc_util_strnenvcmp(const char *a, const char *b, int len) { int d = 0; int i = 0; while (1) { /* If len < 0 then we don't stop based on length */ if (len >= 0 && i >= len) return 0; /* If we're at the end of both strings, they're equal */ if (!*a && !*b) return 0; /* If the second string is shorter, pick it: */ if (*a && !*b) return 1; /* If the first string is shorter, pick it: */ if (!*a && *b) return -1; /* Normalize the characters as for conversion to an * environment variable. */ d = oidc_util_char_to_env(*a) - oidc_util_char_to_env(*b); if (d) return d; a++; b++; i++; } } /* * HTML escape a string */ char *oidc_util_html_escape(apr_pool_t *pool, const char *s) { // TODO: this has performance/memory issues for large chunks of HTML const char chars[6] = {'&', '\'', '\"', '>', '<', '\0'}; const char *const replace[] = { "&", "'", """, ">", "<", }; unsigned int i = 0; unsigned int j = 0; unsigned int k = 0; unsigned int n = 0; unsigned int m = 0; const char *ptr = chars; unsigned int len = _oidc_strlen(ptr); char *r = apr_pcalloc(pool, _oidc_strlen(s) * 6); for (i = 0; i < _oidc_strlen(s); i++) { for (n = 0; n < len; n++) { if (s[i] == chars[n]) { m = (unsigned int)_oidc_strlen(replace[n]); for (k = 0; k < m; k++) r[j + k] = replace[n][k]; j += m; break; } } if (n == len) { r[j] = s[i]; j++; } } r[j] = '\0'; return apr_pstrdup(pool, r); } /* * JavaScript escape a string */ char *oidc_util_javascript_escape(apr_pool_t *pool, const char *s) { const char *cp = NULL; char *output = NULL; int outputlen = 0; int i = 0; if (s == NULL) { return NULL; } outputlen = 0; for (cp = s; *cp; cp++) { switch (*cp) { case '\'': case '"': case '\\': case '/': case 0x0D: case 0x0A: outputlen += 2; break; case '<': case '>': outputlen += 4; break; default: outputlen += 1; break; } } i = 0; output = apr_pcalloc(pool, outputlen + 1); for (cp = s; *cp; cp++) { switch (*cp) { case '\'': if (i <= outputlen - 2) (void)_oidc_strcpy(&output[i], "\\'"); i += 2; break; case '"': if (i <= outputlen - 2) (void)_oidc_strcpy(&output[i], "\\\""); i += 2; break; case '\\': if (i <= outputlen - 2) (void)_oidc_strcpy(&output[i], "\\\\"); i += 2; break; case '/': if (i <= outputlen - 2) (void)_oidc_strcpy(&output[i], "\\/"); i += 2; break; case 0x0D: if (i <= outputlen - 2) (void)_oidc_strcpy(&output[i], "\\r"); i += 2; break; case 0x0A: if (i <= outputlen - 2) (void)_oidc_strcpy(&output[i], "\\n"); i += 2; break; case '<': if (i <= outputlen - 4) (void)_oidc_strcpy(&output[i], "\\x3c"); i += 4; break; case '>': if (i <= outputlen - 4) (void)_oidc_strcpy(&output[i], "\\x3e"); i += 4; break; default: if (i <= outputlen - 1) output[i] = *cp; i += 1; break; } } output[i] = '\0'; return output; } /* * find a needle (s2) in a haystack (s1) using case-insensitive string compare */ const char *oidc_util_strcasestr(const char *s1, const char *s2) { const char *s = s1; const char *p = s2; if ((s == NULL) || (p == NULL)) return NULL; do { if (!*p) return s1; if ((*p == *s) || (tolower(*p) == tolower(*s))) { ++p; ++s; } else { p = s2; if (!*s) return NULL; s = ++s1; } } while (1); } /* * get the URL scheme that is currently being accessed */ static const char *oidc_util_current_url_scheme(const request_rec *r, oidc_hdr_x_forwarded_t x_forwarded_headers) { /* first see if there's a proxy/load-balancer in front of us */ const char *scheme_str = NULL; if (x_forwarded_headers & OIDC_HDR_FORWARDED) scheme_str = oidc_http_hdr_forwarded_get(r, "proto"); if ((scheme_str == NULL) && (x_forwarded_headers & OIDC_HDR_X_FORWARDED_PROTO)) scheme_str = oidc_http_hdr_in_x_forwarded_proto_get(r); /* if not we'll determine the scheme used to connect to this server */ if (scheme_str == NULL) { #ifdef APACHE2_0 scheme_str = (char *)ap_http_method(r); #else scheme_str = ap_http_scheme(r); #endif } if ((scheme_str == NULL) || ((_oidc_strnatcasecmp(scheme_str, "http") != 0) && (_oidc_strnatcasecmp(scheme_str, "https") != 0))) { oidc_warn(r, "detected HTTP scheme \"%s\" is not \"http\" nor \"https\"; perhaps your reverse proxy " "passes a wrongly configured \"%s\" header: falling back to default \"https\"", scheme_str, OIDC_HTTP_HDR_X_FORWARDED_PROTO); scheme_str = "https"; } return scheme_str; } /* * get the port from a Host or X-Forwarded-Host header */ static const char *oidc_util_port_from_host_hdr(const char *host_hdr) { const char *p = NULL; // check for an IPv6 literal addresses if (host_hdr && host_hdr[0] == '[') p = strchr(host_hdr, ']'); else p = host_hdr; if (p) { p = strchr(p, OIDC_CHAR_COLON); // skip over the ":" to point to the actual port number if (p) p++; } return p; } /* * get the URL port that is currently being accessed */ static const char *oidc_get_current_url_port(const request_rec *r, const char *scheme_str, int x_forwarded_headers) { const char *host_hdr = NULL; const char *port_str = NULL; /* * first see if there's a proxy/load-balancer in front of us * that sets X-Forwarded-Port */ if (x_forwarded_headers & OIDC_HDR_X_FORWARDED_PORT) port_str = oidc_http_hdr_in_x_forwarded_port_get(r); if (port_str) return port_str; /* * see if we can get the port from the "X-Forwarded-Host" or "Forwarded" header * and if that header was set we'll assume defaults */ if (x_forwarded_headers & OIDC_HDR_FORWARDED) host_hdr = oidc_http_hdr_forwarded_get(r, "host"); if ((host_hdr == NULL) && (x_forwarded_headers & OIDC_HDR_X_FORWARDED_HOST)) host_hdr = oidc_http_hdr_in_x_forwarded_host_get(r); if (host_hdr) return oidc_util_port_from_host_hdr(host_hdr); /* * see if we can get the port from the "Host" header; if not * we'll determine the port locally */ host_hdr = oidc_http_hdr_in_host_get(r); if (host_hdr) return oidc_util_port_from_host_hdr(host_hdr); /* * if X-Forwarded-Proto assume the default port otherwise the * port should have been set in the X-Forwarded-Port header */ if ((x_forwarded_headers & OIDC_HDR_X_FORWARDED_PROTO) && (oidc_http_hdr_in_x_forwarded_proto_get(r))) return NULL; /* * do the same for the Forwarded: proto= header */ if ((x_forwarded_headers & OIDC_HDR_FORWARDED) && (oidc_http_hdr_forwarded_get(r, "proto"))) return NULL; /* * if no port was set in the Host header and no X-Forwarded-Proto was set, we'll * determine the port locally and don't print it when it's the default for the protocol */ const apr_port_t port = r->connection->local_addr->port; if ((_oidc_strnatcasecmp(scheme_str, "https") == 0) && port == 443) return NULL; else if ((_oidc_strnatcasecmp(scheme_str, "http") == 0) && port == 80) return NULL; port_str = apr_psprintf(r->pool, "%u", port); return port_str; } /* * get the hostname part of the URL that is currently being accessed */ const char *oidc_util_current_url_host(request_rec *r, oidc_hdr_x_forwarded_t x_forwarded_headers) { const char *host_str = NULL; char *p = NULL; if (x_forwarded_headers & OIDC_HDR_FORWARDED) host_str = oidc_http_hdr_forwarded_get(r, "host"); if ((host_str == NULL) && (x_forwarded_headers & OIDC_HDR_X_FORWARDED_HOST)) host_str = oidc_http_hdr_in_x_forwarded_host_get(r); if (host_str == NULL) host_str = oidc_http_hdr_in_host_get(r); if (host_str) { host_str = apr_pstrdup(r->pool, host_str); if (host_str[0] == '[') { p = strchr(host_str, ']'); if (p) p = strchr(p, OIDC_CHAR_COLON); } else { p = strchr(host_str, OIDC_CHAR_COLON); } if (p != NULL) *p = '\0'; } else { /* no Host header, HTTP 1.0 */ host_str = ap_get_server_name(r); } return host_str; } /* * get the base part of the current URL (scheme + host (+ port)) */ static const char *oidc_get_current_url_base(request_rec *r, oidc_hdr_x_forwarded_t x_forwarded_headers) { const char *scheme_str = NULL; const char *host_str = NULL; const char *port_str = NULL; oidc_cfg_x_forwarded_headers_check(r, x_forwarded_headers); scheme_str = oidc_util_current_url_scheme(r, x_forwarded_headers); host_str = oidc_util_current_url_host(r, x_forwarded_headers); port_str = oidc_get_current_url_port(r, scheme_str, x_forwarded_headers); port_str = port_str ? apr_psprintf(r->pool, ":%s", port_str) : ""; char *url = apr_pstrcat(r->pool, scheme_str, "://", host_str, port_str, NULL); return url; } /* * get the URL that is currently being accessed */ char *oidc_util_current_url(request_rec *r, oidc_hdr_x_forwarded_t x_forwarded_headers) { char *url = NULL; char *path = NULL; apr_uri_t uri; path = r->uri; /* check if we're dealing with a forward proxying secenario i.e. a non-relative URL */ if ((path) && (path[0] != '/')) { _oidc_memset(&uri, 0, sizeof(apr_uri_t)); if (apr_uri_parse(r->pool, r->uri, &uri) == APR_SUCCESS) path = apr_pstrcat(r->pool, uri.path, (r->args != NULL && *r->args != '\0' ? "?" : ""), r->args, NULL); else oidc_warn(r, "apr_uri_parse failed on non-relative URL: %s", r->uri); } else { /* make sure we retain URL-encoded characters original URL that we send the user back to */ path = r->unparsed_uri; } url = apr_pstrcat(r->pool, oidc_get_current_url_base(r, x_forwarded_headers), path, NULL); oidc_debug(r, "current URL '%s'", url); return url; } /* * infer a full absolute URL from the (optional) relative one */ const char *oidc_util_absolute_url(request_rec *r, oidc_cfg_t *cfg, const char *url) { if ((url != NULL) && (url[0] == OIDC_CHAR_FORWARD_SLASH)) { url = apr_pstrcat(r->pool, oidc_get_current_url_base(r, oidc_cfg_x_forwarded_headers_get(cfg)), url, NULL); oidc_debug(r, "determined absolute url: %s", url); } return url; } /* * check if the request is on a secure HTTPs (TLS) connection */ apr_byte_t oidc_util_request_is_secure(request_rec *r, oidc_cfg_t *c) { return (_oidc_strnatcasecmp("https", oidc_util_current_url_scheme(r, oidc_cfg_x_forwarded_headers_get(c))) == 0); } /* * return absolute Redirect URI */ const char *oidc_util_redirect_uri(request_rec *r, oidc_cfg_t *cfg) { return oidc_util_absolute_url(r, cfg, oidc_cfg_redirect_uri_get(cfg)); } /* * see if the currently accessed path matches a path from a defined URL */ apr_byte_t oidc_util_request_matches_url(request_rec *r, const char *url) { apr_uri_t uri; _oidc_memset(&uri, 0, sizeof(apr_uri_t)); if ((url == NULL) || (apr_uri_parse(r->pool, url, &uri) != APR_SUCCESS)) return FALSE; oidc_debug(r, "comparing \"%s\"==\"%s\"", r->parsed_uri.path, uri.path); if ((r->parsed_uri.path == NULL) || (uri.path == NULL)) return (r->parsed_uri.path == uri.path); return (_oidc_strcmp(r->parsed_uri.path, uri.path) == 0); } /* * see if the currently accessed path has a certain query parameter */ apr_byte_t oidc_util_request_has_parameter(request_rec *r, const char *param) { if (r->args == NULL) return FALSE; const char *option1 = apr_psprintf(r->pool, "%s=", param); const char *option2 = apr_psprintf(r->pool, "&%s=", param); return ((_oidc_strstr(r->args, option1) == r->args) || (_oidc_strstr(r->args, option2) != NULL)) ? TRUE : FALSE; } /* * get a query parameter */ apr_byte_t oidc_util_request_parameter_get(request_rec *r, char *name, char **value) { char *tokenizer_ctx = NULL; char *p = NULL; char *args = NULL; const char *k_param = apr_psprintf(r->pool, "%s=", name); const size_t k_param_sz = _oidc_strlen(k_param); *value = NULL; if (r->args == NULL || _oidc_strlen(r->args) == 0) return FALSE; /* not sure why we do this, but better be safe than sorry */ args = apr_pstrmemdup(r->pool, r->args, _oidc_strlen(r->args)); p = apr_strtok(args, OIDC_STR_AMP, &tokenizer_ctx); do { if (p && _oidc_strncmp(p, k_param, k_param_sz) == 0) { *value = apr_pstrdup(r->pool, p + k_param_sz); *value = oidc_http_url_decode(r, *value); } p = apr_strtok(NULL, OIDC_STR_AMP, &tokenizer_ctx); } while (p); return (*value != NULL ? TRUE : FALSE); } /* * printout a JSON string value */ static apr_byte_t oidc_util_json_string_print(request_rec *r, json_t *result, const char *key, const char *log) { json_t *value = json_object_get(result, key); if (value != NULL && !json_is_null(value)) { oidc_error(r, "%s: response contained an \"%s\" entry with value: \"%s\"", log, key, oidc_util_encode_json(r->pool, value, JSON_PRESERVE_ORDER | JSON_COMPACT | JSON_ENCODE_ANY)); return TRUE; } return FALSE; } /* * check a JSON object for "error" results and printout */ apr_byte_t oidc_util_check_json_error(request_rec *r, json_t *json) { if (oidc_util_json_string_print(r, json, OIDC_PROTO_ERROR, "oidc_util_check_json_error") == TRUE) { oidc_util_json_string_print(r, json, OIDC_PROTO_ERROR_DESCRIPTION, "oidc_util_check_json_error"); return TRUE; } return FALSE; } #define OIDC_JSON_MAX_ERROR_STR 4096 /* * parse a JSON object */ apr_byte_t oidc_util_decode_json_object_err(request_rec *r, const char *str, json_t **json, apr_byte_t log_err) { if (str == NULL) return FALSE; json_error_t json_error; *json = json_loads(str, 0, &json_error); /* decode the JSON contents of the buffer */ if (*json == NULL) { if (log_err) { /* something went wrong */ #if JANSSON_VERSION_HEX >= 0x020B00 if (json_error_code(&json_error) == json_error_null_character) { oidc_error(r, "JSON parsing returned an error: %s", json_error.text); } else { #endif oidc_error(r, "JSON parsing returned an error: %s (%s)", json_error.text, apr_pstrndup(r->pool, str, OIDC_JSON_MAX_ERROR_STR)); #if JANSSON_VERSION_HEX >= 0x020B00 } #endif } return FALSE; } if (!json_is_object(*json)) { /* oops, no JSON */ if (log_err) { oidc_error(r, "parsed JSON did not contain a JSON object"); json_decref(*json); *json = NULL; return FALSE; } return TRUE; } return TRUE; } apr_byte_t oidc_util_decode_json_object(request_rec *r, const char *str, json_t **json) { return oidc_util_decode_json_object_err(r, str, json, TRUE); } /* * encode a JSON object */ char *oidc_util_encode_json(apr_pool_t *pool, json_t *json, size_t flags) { if (json == NULL) return NULL; char *s = json_dumps(json, flags); char *s_value = apr_pstrdup(pool, s); free(s); return s_value; } /* * decode a JSON string, check for "error" results and printout */ apr_byte_t oidc_util_decode_json_and_check_error(request_rec *r, const char *str, json_t **json) { if (oidc_util_decode_json_object(r, str, json) == FALSE) return FALSE; // see if it is not an error response somehow if (oidc_util_check_json_error(r, *json) == TRUE) { json_decref(*json); *json = NULL; return FALSE; } return TRUE; } /* * sends content to the user agent */ int oidc_util_http_send(request_rec *r, const char *data, size_t data_len, const char *content_type, int success_rvalue) { ap_set_content_type(r, content_type); apr_bucket_brigade *bb = apr_brigade_create(r->pool, r->connection->bucket_alloc); apr_bucket *b = apr_bucket_transient_create(data, data_len, r->connection->bucket_alloc); APR_BRIGADE_INSERT_TAIL(bb, b); b = apr_bucket_eos_create(r->connection->bucket_alloc); APR_BRIGADE_INSERT_TAIL(bb, b); int rc = ap_pass_brigade(r->output_filters, bb); if (rc != APR_SUCCESS) { oidc_error(r, "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 no-gzip", rc); return HTTP_INTERNAL_SERVER_ERROR; } /* *r->status = success_rvalue; */ if ((success_rvalue == OK) && (r->user == NULL)) { /* * satisfy Apache 2.4 mod_authz_core: * prevent it to return HTTP 500 after sending content */ r->user = ""; } return success_rvalue; } /* * send HTML content to the user agent */ int 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) { char *html = "\n" "\n" " \n" " \n" " %s\n" " %s\n" " \n" " \n" "%s\n" " \n" "\n"; html = apr_psprintf( r->pool, html, title ? oidc_util_html_escape(r->pool, title) : "", html_head ? html_head : "", on_load ? apr_psprintf(r->pool, " onload=\"%s()\"", on_load) : "", html_body ? html_body : "

"); return oidc_util_http_send(r, html, _oidc_strlen(html), OIDC_HTTP_CONTENT_TYPE_TEXT_HTML, status_code); } /* * escape characters in an HTML/Javascript template */ static char *oidc_util_template_escape(request_rec *r, const char *arg, int escape) { char *rv = NULL; if (escape == OIDC_POST_PRESERVE_ESCAPE_HTML) { rv = oidc_util_html_escape(r->pool, arg ? arg : ""); } else if (escape == OIDC_POST_PRESERVE_ESCAPE_JAVASCRIPT) { rv = oidc_util_javascript_escape(r->pool, arg ? arg : ""); } else { rv = apr_pstrdup(r->pool, arg); } return rv; } /* * fill and send a HTML template */ int oidc_util_html_send_in_template(request_rec *r, const char *filename, char **static_template_content, const char *arg1, int arg1_esc, const char *arg2, int arg2_esc, int status_code) { char *html = NULL; int rc = status_code; if (*static_template_content == NULL) { // NB: templates go into the server process pool if (oidc_util_file_read(r, filename, r->server->process->pool, static_template_content) == FALSE) { oidc_error(r, "could not read template: %s", filename); *static_template_content = NULL; } } if (*static_template_content) { html = apr_psprintf(r->pool, *static_template_content, oidc_util_template_escape(r, arg1, arg1_esc), oidc_util_template_escape(r, arg2, arg2_esc)); rc = oidc_util_http_send(r, html, _oidc_strlen(html), OIDC_HTTP_CONTENT_TYPE_TEXT_HTML, status_code); } return rc; } /* * send a user-facing error to the browser */ int oidc_util_html_send_error(request_rec *r, const char *error, const char *description, int status_code) { oidc_debug(r, "setting " OIDC_ERROR_ENVVAR " environment variable to: %s", error); apr_table_set(r->subprocess_env, OIDC_ERROR_ENVVAR, error ? error : ""); oidc_debug(r, "setting " OIDC_ERROR_DESC_ENVVAR " environment variable to: %s", description); apr_table_set(r->subprocess_env, OIDC_ERROR_DESC_ENVVAR, description ? description : ""); return status_code; } /* the maximum size of data that we accept in a single POST value: 1MB */ #define OIDC_MAX_POST_DATA_LEN 1024 * 1024 /* * read all bytes from the HTTP request */ static apr_byte_t oidc_util_read(request_rec *r, char **rbuf) { apr_size_t bytes_read; apr_size_t bytes_left; apr_size_t len; long read_length; if (ap_setup_client_block(r, REQUEST_CHUNKED_DECHUNK) != OK) return FALSE; len = ap_should_client_block(r) ? r->remaining : 0; if (len > OIDC_MAX_POST_DATA_LEN) { oidc_error(r, "POST parameter value is too large: %lu bytes (max=%d)", (unsigned long)len, OIDC_MAX_POST_DATA_LEN); return FALSE; } *rbuf = (char *)apr_palloc(r->pool, len + 1); if (*rbuf == NULL) { oidc_error(r, "could not allocate memory for %lu bytes of POST data.", (unsigned long)len); return FALSE; } (*rbuf)[len] = '\0'; bytes_read = 0; bytes_left = len; while (bytes_left > 0) { read_length = ap_get_client_block(r, &(*rbuf)[bytes_read], bytes_left); if (read_length == 0) { (*rbuf)[bytes_read] = '\0'; break; } else if (read_length < 0) { oidc_error(r, "failed to read POST data from client"); return FALSE; } bytes_read += read_length; bytes_left -= read_length; } return TRUE; } /* * read form-encoded parameters from a string in to a table */ apr_byte_t oidc_util_read_form_encoded_params(request_rec *r, apr_table_t *table, char *data) { const char *key = NULL; const char *val = NULL; const char *p = data; while (p && (*p)) { val = ap_getword(r->pool, &p, OIDC_CHAR_AMP); if (val == NULL) break; key = ap_getword(r->pool, &val, OIDC_CHAR_EQUAL); key = oidc_http_url_decode(r, key); val = oidc_http_url_decode(r, val); oidc_debug(r, "read: %s=%s", key, val); apr_table_set(table, key, val); } oidc_debug(r, "parsed: %d bytes into %d elements", data ? (int)_oidc_strlen(data) : 0, apr_table_elts(table)->nelts); return TRUE; } static void oidc_util_userdata_set_post_param(request_rec *r, const char *post_param_name, const char *post_param_value) { apr_table_t *userdata_post_params = NULL; apr_pool_userdata_get((void **)&userdata_post_params, OIDC_USERDATA_POST_PARAMS_KEY, r->pool); if (userdata_post_params == NULL) userdata_post_params = apr_table_make(r->pool, 1); apr_table_set(userdata_post_params, post_param_name, post_param_value); apr_pool_userdata_set(userdata_post_params, OIDC_USERDATA_POST_PARAMS_KEY, NULL, r->pool); } /* * read the POST parameters in to a table */ apr_byte_t oidc_util_read_post_params(request_rec *r, apr_table_t *table, apr_byte_t propagate, const char *strip_param_name) { apr_byte_t rc = FALSE; char *data = NULL; const apr_array_header_t *arr = NULL; const apr_table_entry_t *elts = NULL; int i = 0; const char *content_type = NULL; content_type = oidc_http_hdr_in_content_type_get(r); if ((r->method_number != M_POST) || (content_type == NULL) || (_oidc_strstr(content_type, OIDC_HTTP_CONTENT_TYPE_FORM_ENCODED) != content_type)) { oidc_debug(r, "required content-type %s not found", OIDC_HTTP_CONTENT_TYPE_FORM_ENCODED); goto end; } if (oidc_util_read(r, &data) != TRUE) goto end; rc = oidc_util_read_form_encoded_params(r, table, data); if (rc != TRUE) goto end; if (propagate == FALSE) goto end; arr = apr_table_elts(table); elts = (const apr_table_entry_t *)arr->elts; for (i = 0; i < arr->nelts; i++) if (_oidc_strcmp(elts[i].key, strip_param_name) != 0) oidc_util_userdata_set_post_param(r, elts[i].key, elts[i].val); end: return rc; } /* * read a file from a path on disk */ apr_byte_t oidc_util_file_read(request_rec *r, const char *path, apr_pool_t *pool, char **result) { apr_file_t *fd = NULL; apr_status_t rc = APR_SUCCESS; char s_err[128]; apr_finfo_t finfo; /* open the file if it exists */ if ((rc = apr_file_open(&fd, path, APR_FOPEN_READ | APR_FOPEN_BUFFERED, APR_OS_DEFAULT, r->pool)) != APR_SUCCESS) { oidc_warn(r, "no file found at: \"%s\" (%s)", path, apr_strerror(rc, s_err, sizeof(s_err))); return FALSE; } /* the file exists, now lock it */ apr_file_lock(fd, APR_FLOCK_EXCLUSIVE); /* move the read pointer to the very start of the cache file */ apr_off_t begin = 0; apr_file_seek(fd, APR_SET, &begin); /* get the file info so we know its size */ if ((rc = apr_file_info_get(&finfo, APR_FINFO_SIZE, fd)) != APR_SUCCESS) { oidc_error(r, "error calling apr_file_info_get on file: \"%s\" (%s)", path, apr_strerror(rc, s_err, sizeof(s_err))); goto error_close; } /* now that we have the size of the file, allocate a buffer that can contain its contents */ *result = apr_palloc(pool, finfo.size + 1); /* read the file in to the buffer */ apr_size_t bytes_read = 0; if ((rc = apr_file_read_full(fd, *result, finfo.size, &bytes_read)) != APR_SUCCESS) { oidc_error(r, "apr_file_read_full on (%s) returned an error: %s", path, apr_strerror(rc, s_err, sizeof(s_err))); goto error_close; } /* just to be sure, we set a \0 (we allocated space for it anyway) */ (*result)[bytes_read] = '\0'; /* check that we've got all of it */ if (bytes_read != finfo.size) { oidc_error(r, "apr_file_read_full on (%s) returned less bytes (%" APR_SIZE_T_FMT ") than expected: (%" APR_OFF_T_FMT ")", path, bytes_read, finfo.size); goto error_close; } /* we're done, unlock and close the file */ apr_file_unlock(fd); apr_file_close(fd); /* log successful content retrieval */ oidc_debug(r, "file read successfully \"%s\"", path); return TRUE; error_close: apr_file_unlock(fd); apr_file_close(fd); oidc_error(r, "return error"); return FALSE; } /* * write data to a file */ apr_byte_t oidc_util_file_write(request_rec *r, const char *path, const char *data) { apr_file_t *fd = NULL; apr_status_t rc = APR_SUCCESS; apr_size_t bytes_written = 0; char s_err[128]; /* try to open the metadata file for writing, creating it if it does not exist */ if ((rc = apr_file_open(&fd, path, (APR_FOPEN_WRITE | APR_FOPEN_CREATE | APR_FOPEN_TRUNCATE), APR_OS_DEFAULT, r->pool)) != APR_SUCCESS) { oidc_error(r, "file \"%s\" could not be opened (%s)", path, apr_strerror(rc, s_err, sizeof(s_err))); return FALSE; } /* lock the file and move the write pointer to the start of it */ apr_file_lock(fd, APR_FLOCK_EXCLUSIVE); apr_off_t begin = 0; apr_file_seek(fd, APR_SET, &begin); /* calculate the length of the data, which is a string length */ apr_size_t len = _oidc_strlen(data); /* (blocking) write the number of bytes in the buffer */ rc = apr_file_write_full(fd, data, len, &bytes_written); /* check for a system error */ if (rc != APR_SUCCESS) { oidc_error(r, "could not write to: \"%s\" (%s)", path, apr_strerror(rc, s_err, sizeof(s_err))); return FALSE; } /* check that all bytes from the header were written */ if (bytes_written != len) { oidc_error(r, "could not write enough bytes to: \"%s\", bytes_written (%" APR_SIZE_T_FMT ") != len (%" APR_SIZE_T_FMT ")", path, bytes_written, len); return FALSE; } /* unlock and close the written file */ apr_file_unlock(fd); apr_file_close(fd); oidc_debug(r, "file \"%s\" written; number of bytes (%" APR_SIZE_T_FMT ")", path, len); return TRUE; } /* * see if two provided issuer identifiers match (cq. ignore trailing slash) */ apr_byte_t oidc_util_issuer_match(const char *a, const char *b) { /* check the "issuer" value against the one configure for the provider we got this id_token from */ if (_oidc_strcmp(a, b) != 0) { /* no strict match, but we are going to accept if the difference is only a trailing slash */ int n1 = _oidc_strlen(a); int n2 = _oidc_strlen(b); int n = ((n1 == n2 + 1) && (a[n1 - 1] == OIDC_CHAR_FORWARD_SLASH)) ? n2 : (((n2 == n1 + 1) && (b[n2 - 1] == OIDC_CHAR_FORWARD_SLASH)) ? n1 : 0); if ((n == 0) || (_oidc_strncmp(a, b, n) != 0)) return FALSE; } return TRUE; } /* * see if a certain string value is part of a JSON array with string elements */ apr_byte_t oidc_util_json_array_has_value(request_rec *r, json_t *haystack, const char *needle) { if ((haystack == NULL) || (!json_is_array(haystack))) return FALSE; int i; for (i = 0; i < json_array_size(haystack); i++) { json_t *elem = json_array_get(haystack, i); if (!json_is_string(elem)) { oidc_error(r, "unhandled in-array JSON non-string object type [%d]", elem->type); continue; } if (_oidc_strcmp(json_string_value(elem), needle) == 0) { break; } } /* oidc_debug(r, * "returning (%d=%d)", i, * haystack->value.array->nelts); */ return (i == json_array_size(haystack)) ? FALSE : TRUE; } /* * convert a claim value from UTF-8 to the Latin1 character set */ static char *oidc_util_utf8_to_latin1(request_rec *r, const char *src) { char *dst = NULL; unsigned int cp = 0; unsigned char ch; int i = 0; if (src == NULL) return NULL; dst = apr_pcalloc(r->pool, _oidc_strlen(src) + 1); while (*src != '\0') { ch = (unsigned char)(*src); if (ch <= 0x7f) cp = ch; else if (ch <= 0xbf) cp = (cp << 6) | (ch & 0x3f); else if (ch <= 0xdf) cp = ch & 0x1f; else if (ch <= 0xef) cp = ch & 0x0f; else cp = ch & 0x07; ++src; if (((*src & 0xc0) != 0x80) && (cp <= 0x10ffff)) { if (cp <= 255) { dst[i] = (unsigned char)cp; } else { // no encoding possible dst[i] = '?'; } i++; } } dst[i] = '\0'; return dst; } /* * set a HTTP header and/or environment variable to pass information to the application */ void oidc_util_set_app_info(request_rec *r, const char *s_key, const char *s_value, const char *claim_prefix, oidc_appinfo_pass_in_t pass_in, oidc_appinfo_encoding_t encoding) { /* construct the header name, cq. put the prefix in front of a normalized key name */ const char *s_name = apr_psprintf(r->pool, "%s%s", claim_prefix, oidc_http_hdr_normalize_name(r, s_key)); char *d_value = NULL; if (s_value != NULL) { if (encoding == OIDC_APPINFO_ENCODING_BASE64URL) { oidc_util_base64url_encode(r, &d_value, s_value, _oidc_strlen(s_value), TRUE); } else if (encoding == OIDC_APPINFO_ENCODING_LATIN1) { d_value = oidc_util_utf8_to_latin1(r, s_value); } } if (pass_in & OIDC_APPINFO_PASS_HEADERS) { oidc_http_hdr_in_set(r, s_name, (d_value != NULL) ? d_value : s_value); } if (pass_in & OIDC_APPINFO_PASS_ENVVARS) { /* do some logging about this event */ oidc_debug(r, "setting environment variable \"%s: %s\"", s_name, (d_value != NULL) ? d_value : s_value); apr_table_set(r->subprocess_env, s_name, (d_value != NULL) ? d_value : s_value); } } /* * set the user/claims information from the session in HTTP headers passed on to the application */ void oidc_util_set_app_infos(request_rec *r, json_t *j_attrs, const char *claim_prefix, const char *claim_delimiter, oidc_appinfo_pass_in_t pass_in, oidc_appinfo_encoding_t encoding) { char s_int[255]; json_t *j_value = NULL; const char *s_key = NULL; /* if not attributes are set, nothing needs to be done */ if (j_attrs == NULL) { oidc_debug(r, "no attributes to set"); return; } /* loop over the claims in the JSON structure */ void *iter = json_object_iter((json_t *)j_attrs); while (iter) { /* get the next key/value entry */ s_key = json_object_iter_key(iter); j_value = json_object_iter_value(iter); /* check if it is a single value string */ if (json_is_string(j_value)) { /* set the single string in the application header whose name is based on the key and the prefix */ oidc_util_set_app_info(r, s_key, json_string_value(j_value), claim_prefix, pass_in, encoding); } else if (json_is_boolean(j_value)) { /* set boolean value in the application header whose name is based on the key and the prefix */ oidc_util_set_app_info(r, s_key, (json_is_true(j_value) ? "1" : "0"), claim_prefix, pass_in, encoding); } else if (json_is_integer(j_value)) { if (snprintf(s_int, 255, "%ld", (long)json_integer_value(j_value)) > 0) { /* set long value in the application header whose name is based on the key and the * prefix */ oidc_util_set_app_info(r, s_key, s_int, claim_prefix, pass_in, encoding); } else { oidc_warn(r, "could not convert JSON number to string (> 255 characters?), skipping"); } } else if (json_is_real(j_value)) { /* set float value in the application header whose name is based on the key and the prefix */ oidc_util_set_app_info(r, s_key, apr_psprintf(r->pool, "%lf", json_real_value(j_value)), claim_prefix, pass_in, encoding); } else if (json_is_object(j_value)) { /* set json value in the application header whose name is based on the key and the prefix */ oidc_util_set_app_info( r, s_key, oidc_util_encode_json(r->pool, j_value, JSON_PRESERVE_ORDER | JSON_COMPACT), claim_prefix, pass_in, encoding); /* check if it is a multi-value string */ } else if (json_is_array(j_value)) { /* some logging about what we're going to do */ oidc_debug(r, "parsing attribute array for key \"%s\" (#nr-of-elems: %lu)", s_key, (unsigned long)json_array_size(j_value)); /* string to hold the concatenated array string values */ char *s_concat = apr_pstrdup(r->pool, ""); size_t i = 0; /* loop over the array */ for (i = 0; i < json_array_size(j_value); i++) { /* get the current element */ json_t *elem = json_array_get(j_value, i); /* check if it is a string */ if (json_is_string(elem)) { /* concatenate the string to the s_concat value using the configured separator * char */ // TODO: escape the delimiter in the values (maybe reuse/extract url-formatted // code from oidc_session_identity_encode) if (_oidc_strcmp(s_concat, "") != 0) { s_concat = apr_psprintf(r->pool, "%s%s%s", s_concat, claim_delimiter, json_string_value(elem)); } else { s_concat = apr_psprintf(r->pool, "%s", json_string_value(elem)); } } else if (json_is_boolean(elem)) { if (_oidc_strcmp(s_concat, "") != 0) { s_concat = apr_psprintf(r->pool, "%s%s%s", s_concat, claim_delimiter, json_is_true(elem) ? "1" : "0"); } else { s_concat = apr_psprintf(r->pool, "%s", json_is_true(elem) ? "1" : "0"); } } else { /* don't know how to handle a non-string array element */ oidc_debug(r, "unhandled in-array JSON object type [%d] for key \"%s\" when " "parsing claims array elements", elem->type, s_key); } } /* set the concatenated string */ oidc_util_set_app_info(r, s_key, s_concat, claim_prefix, pass_in, encoding); } else { /* no string and no array, so unclear how to handle this */ oidc_debug(r, "unhandled JSON object type [%d] for key \"%s\" when parsing claims", j_value->type, s_key); } iter = json_object_iter_next(j_attrs, iter); } } /* * parse a space separated string in to a hash table */ apr_hash_t *oidc_util_spaced_string_to_hashtable(apr_pool_t *pool, const char *str) { char *val; const char *data = apr_pstrdup(pool, str); apr_hash_t *result = apr_hash_make(pool); while (data && (*data)) { val = ap_getword_white(pool, &data); if (val == NULL) break; apr_hash_set(result, val, APR_HASH_KEY_STRING, val); } return result; } /* * compare two space separated value types */ apr_byte_t oidc_util_spaced_string_equals(apr_pool_t *pool, const char *a, const char *b) { const void *k = NULL; void *v = NULL; /* parse both entries as hash tables */ apr_hash_t *ht_a = oidc_util_spaced_string_to_hashtable(pool, a); apr_hash_t *ht_b = oidc_util_spaced_string_to_hashtable(pool, b); /* first compare the length of both response_types */ if (apr_hash_count(ht_a) != apr_hash_count(ht_b)) return FALSE; /* then loop over all entries */ apr_hash_index_t *hi; for (hi = apr_hash_first(NULL, ht_a); hi; hi = apr_hash_next(hi)) { apr_hash_this(hi, &k, NULL, &v); if (apr_hash_get(ht_b, k, APR_HASH_KEY_STRING) == NULL) return FALSE; } /* if we've made it this far, a an b are equal in length and every element in a is in b */ return TRUE; } /* * see if a particular value is part of a space separated value */ apr_byte_t oidc_util_spaced_string_contains(apr_pool_t *pool, const char *str, const char *match) { apr_hash_t *ht = oidc_util_spaced_string_to_hashtable(pool, str); return (apr_hash_get(ht, match, APR_HASH_KEY_STRING) != NULL); } /* * get (optional) string from a JSON object */ apr_byte_t oidc_util_json_object_get_string(apr_pool_t *pool, json_t *json, const char *name, char **value, const char *default_value) { *value = default_value ? apr_pstrdup(pool, default_value) : NULL; if (json != NULL) { json_t *v = json_object_get(json, name); if ((v != NULL) && (json_is_string(v))) { *value = apr_pstrdup(pool, json_string_value(v)); } } return TRUE; } /* * get (optional) string array from a JSON object */ apr_byte_t oidc_util_json_object_get_string_array(apr_pool_t *pool, json_t *json, const char *name, apr_array_header_t **value, const apr_array_header_t *default_value) { json_t *v = NULL, *arr = NULL; size_t i = 0; *value = (default_value != NULL) ? apr_array_copy(pool, default_value) : NULL; if (json != NULL) { arr = json_object_get(json, name); if ((arr != NULL) && (json_is_array(arr))) { *value = apr_array_make(pool, json_array_size(arr), sizeof(const char *)); for (i = 0; i < json_array_size(arr); i++) { v = json_array_get(arr, i); APR_ARRAY_PUSH(*value, const char *) = apr_pstrdup(pool, json_string_value(v)); } } } return TRUE; } /* * get (optional) int from a JSON object */ apr_byte_t oidc_util_json_object_get_int(const json_t *json, const char *name, int *value, const int default_value) { const json_t *v = NULL; *value = default_value; if (json != NULL) { v = json_object_get(json, name); if ((v != NULL) && (json_is_integer(v))) { *value = json_integer_value(v); } } return TRUE; } /* * get (optional) boolean from a JSON object */ apr_byte_t oidc_util_json_object_get_bool(const json_t *json, const char *name, int *value, const int default_value) { const json_t *v = NULL; *value = default_value; if (json != NULL) { v = json_object_get(json, name); if ((v != NULL) && (json_is_boolean(v))) { *value = json_is_true(v); return TRUE; } } return FALSE; } /* * merge two JSON objects */ apr_byte_t oidc_util_json_merge(request_rec *r, json_t *src, json_t *dst) { const char *key; json_t *value = NULL; void *iter = NULL; if ((src == NULL) || (dst == NULL)) return FALSE; oidc_debug(r, "src=%s, dst=%s", oidc_util_encode_json(r->pool, src, JSON_PRESERVE_ORDER | JSON_COMPACT), oidc_util_encode_json(r->pool, dst, JSON_PRESERVE_ORDER | JSON_COMPACT)); iter = json_object_iter(src); while (iter) { key = json_object_iter_key(iter); value = json_object_iter_value(iter); json_object_set(dst, key, value); iter = json_object_iter_next(src, iter); } oidc_debug(r, "result dst=%s", oidc_util_encode_json(r->pool, dst, JSON_PRESERVE_ORDER | JSON_COMPACT)); return TRUE; } /* * add query encoded parameters to a table */ void oidc_util_table_add_query_encoded_params(apr_pool_t *pool, apr_table_t *table, const char *params) { char *key = NULL; char *value = NULL; const char *v = NULL; const char *p = params; while (p && (*p)) { v = ap_getword(pool, &p, OIDC_CHAR_AMP); if (v == NULL) break; key = apr_pstrdup(pool, ap_getword(pool, &v, OIDC_CHAR_EQUAL)); ap_unescape_url(key); value = apr_pstrdup(pool, v); ap_unescape_url(value); apr_table_addn(table, key, value); } } /* * create a symmetric key from a client_secret */ apr_byte_t 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) { oidc_jose_error_t err = {{'\0'}, 0, {'\0'}, {'\0'}}; unsigned char *key = NULL; unsigned int key_len; if ((client_secret != NULL) && (_oidc_strlen(client_secret) > 0)) { if (hash_algo == NULL) { key = (unsigned char *)client_secret; key_len = _oidc_strlen(client_secret); } else { /* hash the client_secret first, this is OpenID Connect specific */ oidc_jose_hash_bytes(r->pool, hash_algo, (const unsigned char *)client_secret, _oidc_strlen(client_secret), &key, &key_len, &err); } if ((key != NULL) && (key_len > 0)) { if ((r_key_len != 0) && (key_len >= r_key_len)) key_len = r_key_len; oidc_debug(r, "key_len=%d", key_len); *jwk = oidc_jwk_create_symmetric_key(r->pool, NULL, key, key_len, set_kid, &err); } if (*jwk == NULL) { oidc_error(r, "could not create JWK from the provided secret: %s", oidc_jose_e2s(r->pool, err)); return FALSE; } } return TRUE; } /* * merge provided keys and client secret in to a single hashtable */ apr_hash_t *oidc_util_merge_symmetric_key(apr_pool_t *pool, const apr_array_header_t *keys, oidc_jwk_t *jwk) { apr_hash_t *result = apr_hash_make(pool); const oidc_jwk_t *elem = NULL; int i = 0; if (keys != NULL) { for (i = 0; i < keys->nelts; i++) { elem = APR_ARRAY_IDX(keys, i, oidc_jwk_t *); apr_hash_set(result, elem->kid, APR_HASH_KEY_STRING, elem); } } if (jwk != NULL) { apr_hash_set(result, jwk->kid, APR_HASH_KEY_STRING, jwk); } return result; } /* * openssl hash and base64 encode */ apr_byte_t oidc_util_hash_string_and_base64url_encode(request_rec *r, const char *openssl_hash_algo, const char *input, char **output) { oidc_jose_error_t err; unsigned char *hashed = NULL; unsigned int hashed_len = 0; if (oidc_jose_hash_bytes(r->pool, openssl_hash_algo, (const unsigned char *)input, _oidc_strlen(input), &hashed, &hashed_len, &err) == FALSE) { oidc_error(r, "oidc_jose_hash_bytes returned an error: %s", err.text); return FALSE; } if (oidc_util_base64url_encode(r, output, (const char *)hashed, hashed_len, TRUE) <= 0) { oidc_error(r, "oidc_base64url_encode returned an error: %s", err.text); return FALSE; } return TRUE; } /* * merge the provided array of keys (k2) into a hash table of keys (k1) */ apr_hash_t *oidc_util_merge_key_sets(apr_pool_t *pool, apr_hash_t *k1, const apr_array_header_t *k2) { apr_hash_t *rv = k1 ? apr_hash_copy(pool, k1) : apr_hash_make(pool); const oidc_jwk_t *jwk = NULL; int i = 0; if (k2 != NULL) { for (i = 0; i < k2->nelts; i++) { jwk = APR_ARRAY_IDX(k2, i, oidc_jwk_t *); apr_hash_set(rv, jwk->kid, APR_HASH_KEY_STRING, jwk); } } return rv; } /* * merge two hash tables with key sets */ apr_hash_t *oidc_util_merge_key_sets_hash(apr_pool_t *pool, apr_hash_t *k1, apr_hash_t *k2) { if (k1 == NULL) { if (k2 == NULL) return apr_hash_make(pool); return k2; } if (k2 == NULL) return k1; return apr_hash_overlay(pool, k1, k2); } /* * regexp substitute * Example: * regex: "^.*([0-9]+).*$" * replace: "$1" * text_original: "match 292 numbers" * text_replaced: "292" */ apr_byte_t oidc_util_regexp_substitute(apr_pool_t *pool, const char *input, const char *regexp, const char *replace, char **output, char **error_str) { char *substituted = NULL; apr_byte_t rc = FALSE; struct oidc_pcre *preg = oidc_pcre_compile(pool, regexp, error_str); if (preg == NULL) { *error_str = apr_psprintf(pool, "pattern [%s] is not a valid regular expression: %s", regexp, *error_str); goto out; } if (_oidc_strlen(input) >= OIDC_PCRE_MAXCAPTURE - 1) { *error_str = apr_psprintf(pool, "string length (%d) is larger than the maximum allowed for pcre_subst (%d)", (int)_oidc_strlen(input), OIDC_PCRE_MAXCAPTURE - 1); goto out; } substituted = oidc_pcre_subst(pool, preg, input, (int)_oidc_strlen(input), replace); if (substituted == NULL) { *error_str = apr_psprintf( pool, "unknown error could not match string [%s] using pattern [%s] and replace matches in [%s]", input, regexp, replace); goto out; } *output = apr_pstrdup(pool, substituted); rc = TRUE; out: if (preg) oidc_pcre_free(preg); return rc; } /* * regexp match */ apr_byte_t oidc_util_regexp_first_match(apr_pool_t *pool, const char *input, const char *regexp, char **output, char **error_str) { apr_byte_t rv = FALSE; int rc = 0; struct oidc_pcre *preg = oidc_pcre_compile(pool, regexp, error_str); if (preg == NULL) { *error_str = apr_psprintf(pool, "pattern [%s] is not a valid regular expression: %s", regexp, *error_str); goto out; } if ((rc = oidc_pcre_exec(pool, preg, input, (int)_oidc_strlen(input), error_str)) < 0) goto out; if (output && (oidc_pcre_get_substring(pool, preg, input, rc, output, error_str) <= 0)) { *error_str = apr_psprintf(pool, "pcre_get_substring failed: %s", *error_str); goto out; } rv = TRUE; out: if (preg) oidc_pcre_free(preg); return rv; } /* * check if the provided cookie domain value is valid */ apr_byte_t oidc_util_cookie_domain_valid(const char *hostname, const char *cookie_domain) { const char *p = NULL; const char *check_cookie = cookie_domain; // Skip past the first char of a cookie_domain that starts // with a ".", ASCII 46 if (check_cookie[0] == 46) check_cookie++; p = oidc_util_strcasestr(hostname, check_cookie); if ((p == NULL) || (_oidc_strnatcasecmp(check_cookie, p) != 0)) { return FALSE; } return TRUE; } /* * return the first JWK that matches a provided key type and use from an array of JWKs */ oidc_jwk_t *oidc_util_key_list_first(const apr_array_header_t *key_list, int kty, const char *use) { oidc_jwk_t *rv = NULL; int i = 0; oidc_jwk_t *jwk = NULL; for (i = 0; (key_list) && (i < key_list->nelts); i++) { jwk = APR_ARRAY_IDX(key_list, i, oidc_jwk_t *); if ((kty != -1) && (jwk->kty != kty)) continue; if (((use == NULL) || (jwk->use == NULL) || (_oidc_strncmp(jwk->use, use, _oidc_strlen(use)) == 0))) { rv = jwk; break; } } return rv; } #ifdef USE_LIBJQ /* * execute a JQ expression */ static const char *oidc_util_jq_exec(request_rec *r, jq_state *jq, struct jv_parser *parser) { const char *rv = NULL; jv value, elem, str, msg; while (jv_is_valid((value = jv_parser_next(parser)))) { jq_start(jq, value, 0); while (jv_is_valid(elem = jq_next(jq))) { str = jv_dump_string(elem, 0); rv = apr_pstrdup(r->pool, jv_string_value(str)); oidc_debug(r, "jv_dump_string: %s", rv); jv_free(str); } jv_free(elem); } if (jv_invalid_has_msg(jv_copy(value))) { msg = jv_invalid_get_msg(value); oidc_error(r, "invalid: %s", jv_string_value(msg)); jv_free(msg); } else { jv_free(value); } return rv; } #define OIDC_JQ_FILTER_EXPIRE_DEFAULT 600 #define OIDC_JQ_FILTER_CACHE_TTL_ENVVAR "OIDC_JQ_FILTER_CACHE_TTL" /* * return the JQ expression result cache expiry */ static int oidc_jq_filter_cache_ttl(request_rec *r) { const char *s_ttl = apr_table_get(r->subprocess_env, OIDC_JQ_FILTER_CACHE_TTL_ENVVAR); return _oidc_str_to_int(s_ttl, OIDC_JQ_FILTER_EXPIRE_DEFAULT); } #endif /* * apply a JQ expression/filter to the provided JSON input */ const char *oidc_util_jq_filter(request_rec *r, const char *input, const char *filter) { const char *result = input; #ifdef USE_LIBJQ jq_state *jq = NULL; struct jv_parser *parser = NULL; int ttl = 0; char *key = NULL; char *value = NULL; if (filter == NULL) { oidc_debug(r, "filter is NULL, abort"); goto end; } if (input == NULL) { oidc_debug(r, "input is NULL, set to empty object"); input = "{}"; } oidc_debug(r, "processing input: %s", input); oidc_debug(r, "processing filter: %s", filter); ttl = oidc_jq_filter_cache_ttl(r); if (ttl > 0) { if (oidc_util_hash_string_and_base64url_encode( r, OIDC_JOSE_ALG_SHA256, apr_pstrcat(r->pool, input, filter, NULL), &key) == FALSE) { oidc_error(r, "oidc_util_hash_string_and_base64url_encode returned an error"); goto end; } oidc_cache_get_jq_filter(r, key, &value); if (value != NULL) { oidc_debug(r, "return cached result: %s", value); result = value; goto end; } } jq = jq_init(); if (jq == NULL) { oidc_error(r, "jq_init returned NULL"); goto end; } if (jq_compile(jq, filter) == 0) { oidc_error(r, "jq_compile returned an error"); goto end; } parser = jv_parser_new(0); if (parser == NULL) { oidc_error(r, "jv_parser_new returned NULL"); goto end; } jv_parser_set_buf(parser, input, _oidc_strlen(input), 0); result = oidc_util_jq_exec(r, jq, parser); if ((result != NULL) && (ttl != 0)) { oidc_debug(r, "caching result: %s", result); oidc_cache_set_jq_filter(r, key, result, apr_time_now() + apr_time_from_sec(ttl)); } end: if (parser) jv_parser_free(parser); if (jq) jq_teardown(&jq); #endif return result; } #define OIDC_TP_TRACE_ID_LEN 16 #define OIDC_TP_PARENT_ID_LEN 8 /* The following version-format definition is used for version 00. version-format = trace-id "-" parent-id "-" trace-flags trace-id = 32HEXDIGLC ; 16 bytes array identifier. All zeroes forbidden parent-id = 16HEXDIGLC ; 8 bytes array identifier. All zeroes forbidden trace-flags = 2HEXDIGLC ; 8 bit flags. Currently, only one bit is used. */ void oidc_util_set_trace_parent(request_rec *r, oidc_cfg_t *c, const char *span) { // apr_table_get(r->subprocess_env, "UNIQUE_ID"); unsigned char trace_id[OIDC_TP_TRACE_ID_LEN]; unsigned char parent_id[OIDC_TP_PARENT_ID_LEN]; unsigned char trace_flags = 0; char *s_parent_id = "", *s_trace_id = ""; const char *v = NULL; int i = 0; char *hostname = "localhost"; const uint64_t P1 = 7; const uint64_t P2 = 31; uint64_t hash = P1; if (oidc_cfg_trace_parent_get(c) != OIDC_TRACE_PARENT_GENERATE) return; if (r->server->server_hostname) hostname = r->server->server_hostname; v = oidc_request_state_get(r, OIDC_REQUEST_STATE_TRACE_ID); if (span == NULL) { _oidc_memset(parent_id, 0, OIDC_TP_PARENT_ID_LEN); _oidc_memcpy(parent_id, hostname, _oidc_strlen(hostname) < OIDC_TP_PARENT_ID_LEN ? _oidc_strlen(hostname) : OIDC_TP_PARENT_ID_LEN); } else { if (v == NULL) oidc_warn(r, "parameter \"span\" is set, but no \"trace-id\" [%s] found in the request state", OIDC_REQUEST_STATE_TRACE_ID); else oidc_debug(r, "changing \"parent-id\" of current traceparent"); for (const char *p = span; *p != 0; p++) hash = hash * P2 + *p; _oidc_memcpy(parent_id, &hash, OIDC_TP_PARENT_ID_LEN); } for (i = 0; i < OIDC_TP_PARENT_ID_LEN; i++) s_parent_id = apr_psprintf(r->pool, "%s%02x", s_parent_id, parent_id[i]); if (v == NULL) { apr_generate_random_bytes(trace_id, OIDC_TP_TRACE_ID_LEN); for (i = 0; i < OIDC_TP_TRACE_ID_LEN; i++) s_trace_id = apr_psprintf(r->pool, "%s%02x", s_trace_id, trace_id[i]); oidc_request_state_set(r, OIDC_REQUEST_STATE_TRACE_ID, s_trace_id); } else { s_trace_id = apr_pstrdup(r->pool, v); } if (oidc_cfg_metrics_hook_data_get(c) != NULL) trace_flags = trace_flags | 0x01; oidc_http_hdr_in_set(r, OIDC_HTTP_HDR_TRACE_PARENT, apr_psprintf(r->pool, "00-%s-%s-%02x", s_trace_id, s_parent_id, trace_flags)); } /* * clear the contents of a hash table (used for older versions of libapr missing this) */ void oidc_util_apr_hash_clear(apr_hash_t *ht) { apr_hash_index_t *hi = NULL; const void *key = NULL; apr_ssize_t klen = 0; for (hi = apr_hash_first(NULL, ht); hi; hi = apr_hash_next(hi)) { apr_hash_this(hi, &key, &klen, NULL); apr_hash_set(ht, key, klen, NULL); } } /* * return the OpenSSL version we compiled against */ char *oidc_util_openssl_version(apr_pool_t *pool) { char *s_version = NULL; #ifdef OPENSSL_VERSION_STR s_version = apr_psprintf(pool, "openssl-%s", OPENSSL_VERSION_STR); #else s_version = OPENSSL_VERSION_TEXT; #endif return s_version; } /* * parse an Apache expression */ char *oidc_util_apr_expr_parse(cmd_parms *cmd, const char *str, oidc_apr_expr_t **expr, apr_byte_t result_is_str) { char *rv = NULL; if ((str == NULL) || (expr == NULL)) return NULL; *expr = apr_pcalloc(cmd->pool, sizeof(oidc_apr_expr_t)); (*expr)->str = apr_pstrdup(cmd->pool, str); #if HAVE_APACHE_24 const char *expr_err = NULL; unsigned int flags = AP_EXPR_FLAG_DONT_VARY & AP_EXPR_FLAG_RESTRICTED; if (result_is_str) flags += AP_EXPR_FLAG_STRING_RESULT; (*expr)->expr = ap_expr_parse_cmd(cmd, str, flags, &expr_err, NULL); if (expr_err != NULL) { rv = apr_pstrcat(cmd->temp_pool, "cannot parse expression: ", expr_err, NULL); *expr = NULL; } #endif return rv; } /* * execute an Apache expression */ const char *oidc_util_apr_expr_exec(request_rec *r, const oidc_apr_expr_t *expr, apr_byte_t result_is_str) { const char *expr_result = NULL; if (expr == NULL) return NULL; #if HAVE_APACHE_24 const char *expr_err = NULL; if (result_is_str) { expr_result = ap_expr_str_exec(r, expr->expr, &expr_err); } else { expr_result = ap_expr_exec(r, expr->expr, &expr_err) ? "" : NULL; } if (expr_err) { oidc_error(r, "executing expression \"%s\" failed: %s", expr->str, expr_err); expr_result = NULL; } #else expr_result = expr->str; #endif return expr_result; } mod_auth_openidc-2.4.16.10/src/util.h000066400000000000000000000200031476721736500172560ustar00rootroot00000000000000/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ /*************************************************************************** * Copyright (C) 2017-2025 ZmartZone Holding BV * All rights reserved. * * DISCLAIMER OF WARRANTIES: * * THE SOFTWARE PROVIDED HEREUNDER IS PROVIDED ON AN "AS IS" BASIS, WITHOUT * ANY WARRANTIES OR REPRESENTATIONS EXPRESS, IMPLIED OR STATUTORY; INCLUDING, * WITHOUT LIMITATION, WARRANTIES OF QUALITY, PERFORMANCE, NONINFRINGEMENT, * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. NOR ARE THERE ANY * WARRANTIES CREATED BY A COURSE OR DEALING, COURSE OF PERFORMANCE OR TRADE * USAGE. FURTHERMORE, THERE ARE NO WARRANTIES THAT THE SOFTWARE WILL MEET * YOUR NEEDS OR BE FREE FROM ERRORS, OR THAT THE OPERATION OF THE SOFTWARE * WILL BE UNINTERRUPTED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * @Author: Hans Zandbelt - hans.zandbelt@openidc.com */ #ifndef _MOD_AUTH_OPENIDC_UTIL_H_ #define _MOD_AUTH_OPENIDC_UTIL_H_ #include "cfg/cfg.h" #include "cfg/dir.h" #include "jose.h" apr_byte_t oidc_util_generate_random_string(request_rec *r, char **output, int len); apr_byte_t oidc_util_jwt_create(request_rec *r, const oidc_crypto_passphrase_t *passphrase, const char *s_payload, char **compact_encoded_jwt); apr_byte_t oidc_util_jwt_verify(request_rec *r, const oidc_crypto_passphrase_t *passphrase, const char *compact_encoded_jwt, char **s_payload); apr_byte_t oidc_util_hash_string_and_base64url_encode(request_rec *r, const char *openssl_hash_algo, const char *input, char **output); apr_byte_t 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); char *oidc_util_encode_json(apr_pool_t *pool, json_t *json, size_t flags); apr_byte_t oidc_util_decode_json_object_err(request_rec *r, const char *str, json_t **json, apr_byte_t log_err); apr_byte_t oidc_util_decode_json_object(request_rec *r, const char *str, json_t **json); apr_byte_t oidc_util_check_json_error(request_rec *r, json_t *json); apr_byte_t oidc_util_random_bytes(unsigned char *buf, apr_size_t length); apr_byte_t oidc_util_generate_random_bytes(request_rec *r, unsigned char *buf, apr_size_t length); apr_byte_t oidc_util_generate_random_hex_string(request_rec *r, char **hex_str, int byte_len); int oidc_util_strnenvcmp(const char *a, const char *b, int len); char *oidc_util_base64_decode(apr_pool_t *pool, const char *input, char **output, int *output_len); int oidc_util_base64url_encode(request_rec *r, char **dst, const char *src, int src_len, int remove_padding); int oidc_util_base64url_decode(apr_pool_t *pool, char **dst, const char *src); const char *oidc_util_current_url_host(request_rec *r, oidc_hdr_x_forwarded_t x_forwarded_headers); apr_byte_t oidc_util_request_matches_url(request_rec *r, const char *url); char *oidc_util_current_url(request_rec *r, oidc_hdr_x_forwarded_t x_forwarded_headers); const char *oidc_util_absolute_url(request_rec *r, oidc_cfg_t *cfg, const char *url); const char *oidc_util_redirect_uri(request_rec *r, oidc_cfg_t *c); apr_byte_t oidc_util_request_is_secure(request_rec *r, oidc_cfg_t *c); char *oidc_util_openssl_version(apr_pool_t *pool); apr_byte_t oidc_util_request_matches_url(request_rec *r, const char *url); apr_byte_t oidc_util_decode_json_and_check_error(request_rec *r, const char *str, json_t **json); int 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); apr_byte_t oidc_util_file_read(request_rec *r, const char *path, apr_pool_t *pool, char **result); apr_byte_t oidc_util_file_write(request_rec *r, const char *path, const char *data); apr_byte_t oidc_util_issuer_match(const char *a, const char *b); int oidc_util_html_send_error(request_rec *r, const char *error, const char *description, int status_code); apr_byte_t oidc_util_json_array_has_value(request_rec *r, json_t *haystack, const char *needle); void oidc_util_set_app_info(request_rec *r, const char *s_key, const char *s_value, const char *claim_prefix, oidc_appinfo_pass_in_t pass_in, oidc_appinfo_encoding_t encoding); void oidc_util_set_app_infos(request_rec *r, json_t *j_attrs, const char *claim_prefix, const char *claim_delimiter, oidc_appinfo_pass_in_t pass_in, oidc_appinfo_encoding_t encoding); apr_hash_t *oidc_util_spaced_string_to_hashtable(apr_pool_t *pool, const char *str); apr_byte_t oidc_util_spaced_string_equals(apr_pool_t *pool, const char *a, const char *b); apr_byte_t oidc_util_spaced_string_contains(apr_pool_t *pool, const char *str, const char *match); apr_byte_t oidc_util_json_object_get_string(apr_pool_t *pool, json_t *json, const char *name, char **value, const char *default_value); apr_byte_t oidc_util_json_object_get_string_array(apr_pool_t *pool, json_t *json, const char *name, apr_array_header_t **value, const apr_array_header_t *default_value); apr_byte_t oidc_util_json_object_get_int(const json_t *json, const char *name, int *value, const int default_value); apr_byte_t oidc_util_json_object_get_bool(const json_t *json, const char *name, int *value, const int default_value); char *oidc_util_html_escape(apr_pool_t *pool, const char *input); char *oidc_util_javascript_escape(apr_pool_t *pool, const char *input); void oidc_util_table_add_query_encoded_params(apr_pool_t *pool, apr_table_t *table, const char *params); apr_hash_t *oidc_util_merge_key_sets(apr_pool_t *pool, apr_hash_t *k1, const apr_array_header_t *k2); apr_hash_t *oidc_util_merge_key_sets_hash(apr_pool_t *pool, apr_hash_t *k1, apr_hash_t *k2); apr_byte_t oidc_util_regexp_substitute(apr_pool_t *pool, const char *input, const char *regexp, const char *replace, char **output, char **error_str); apr_byte_t oidc_util_regexp_first_match(apr_pool_t *pool, const char *input, const char *regexp, char **output, char **error_str); apr_byte_t oidc_util_json_merge(request_rec *r, json_t *src, json_t *dst); apr_byte_t oidc_util_cookie_domain_valid(const char *hostname, const char *cookie_domain); apr_hash_t *oidc_util_merge_symmetric_key(apr_pool_t *pool, const apr_array_header_t *keys, oidc_jwk_t *jwk); const char *oidc_util_strcasestr(const char *s1, const char *s2); oidc_jwk_t *oidc_util_key_list_first(const apr_array_header_t *key_list, int kty, const char *use); const char *oidc_util_jq_filter(request_rec *r, const char *input, const char *filter); void oidc_util_set_trace_parent(request_rec *r, oidc_cfg_t *c, const char *span); void oidc_util_apr_hash_clear(apr_hash_t *ht); int oidc_util_html_send_in_template(request_rec *r, const char *filename, char **static_template_content, const char *arg1, int arg1_esc, const char *arg2, int arg2_esc, int status_code); char *oidc_util_apr_expr_parse(cmd_parms *cmd, const char *str, oidc_apr_expr_t **expr, apr_byte_t result_is_str); const char *oidc_util_apr_expr_exec(request_rec *r, const oidc_apr_expr_t *expr, apr_byte_t result_is_str); #endif /* _MOD_AUTH_OPENIDC_UTIL_H_ */ mod_auth_openidc-2.4.16.10/test/000077500000000000000000000000001476721736500163255ustar00rootroot00000000000000mod_auth_openidc-2.4.16.10/test/.gitignore000066400000000000000000000001211476721736500203070ustar00rootroot00000000000000/test /test-cmd /*.lo /*.o /*.slo /.libs/ /.deps/ /test.log /test.trs /.dirstamp mod_auth_openidc-2.4.16.10/test/certificate.pem000066400000000000000000000017111476721736500213120ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIICnTCCAYUCBgFuk1+FLDANBgkqhkiG9w0BAQsFADASMRAwDgYDVQQDDAd2aW5j ZW50MB4XDTE5MTEyMjEzNDcyMVoXDTI5MTEyMjEzNDkwMVowEjEQMA4GA1UEAwwH dmluY2VudDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAIhnk1231eWz Kace6O6jCwrlSCqmwWv6jswYjTaXtCvK44O/tc/Rgrkpam2bTNP+QUOmxqJ50jw/ vj6MIRXYr0uFjQN9ztCpdbUNMHR90zp8LniDvWoX1uKtARhbzDm53ivrY8IjTI9Z fnGbfKb7kvty7U1iMwvoU2TOHGlJsuaJZuT1XZq7ugulea8ZG2ATyExUs5eZqbqP wukVfzGEcAIetIIbNjhLyFg6yZGZ2Ghe7IxwvY/uJH3DOaGO2YYPCrh8paLnWDc5 ao1QD3dDG5C5IdaWvH5h7JzenIH12LRSu2fFo2A1AIUx9SY2QlUhTeeQPudXYA+H EDc4nixBJCcCAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAfAo40il4qw7DfOkke0p1 ZFAgLQQS3J5hYNDSRvVv+vxkk9o/N++zTMoHbfcDcU5BdVH6Qsr/12PXPX7Ur5WY Dq+bWGAK3MAaGtZlmycFeVhoVRfab4TUWUy43H3VyFUNqjGRAVJ/VD1RW3fJ18Kr QTN2fcKSd88Jqt5TvjROKghq95+8BQtlhrR/sQVrjgYwc+eU9ljWI56MQXbpHstl 9IewMXnusSPxKRTbutjaxzKaoXRTUncPL6ga0SSxOTdKksM4ZYpPnq0B93silb+0 qs8aJraGzjAmLE30opfufP+roth19VJxAfYsW5mgAmXP9kEAF+iWB8FB4/Q4noNG 8Q== -----END CERTIFICATE----- mod_auth_openidc-2.4.16.10/test/eccert.pem000066400000000000000000000016051476721736500202770ustar00rootroot00000000000000openssl req -x509 -newkey EC:<(openssl ecparam -name secp521r1) -keyout ecpriv.key -out eccert.pem -days 3650 -nodes -subj '/CN=localhost' -----BEGIN CERTIFICATE----- MIICBDCCAWagAwIBAgIUdYpkXaCal7IwjHix3n1PP9/O6OcwCgYIKoZIzj0EAwIw FDESMBAGA1UEAwwJbG9jYWxob3N0MB4XDTIzMDMyMzIwNDU1MFoXDTMzMDMyMDIw NDU1MFowFDESMBAGA1UEAwwJbG9jYWxob3N0MIGbMBAGByqGSM49AgEGBSuBBAAj A4GGAAQBHoSH1WF0sQD6ublZHT9PQuMrKwm4GRKhdpHwaOmEe+g5OuWNwAJGxWVM RL0UyRvFs0Szgl3E+A8mX0b571YQlN4BqaaB+Vlom5J3Jwr9xxReRJeM1B3w2yom 6/0sWmTlizU4CKKtabq6S1cbwAs1LFJ99y6sGrzOpnJnXjV1XqpLMpCjUzBRMB0G A1UdDgQWBBTKfLLXyRVQpnXFf19Bs7eXRPlRmzAfBgNVHSMEGDAWgBTKfLLXyRVQ pnXFf19Bs7eXRPlRmzAPBgNVHRMBAf8EBTADAQH/MAoGCCqGSM49BAMCA4GLADCB hwJBGkoifMDYwsSLSmnnVdFftqTwxrjdgrtPMRzetz/w/D9KkM4Mlufgv5jBXuWc EiP9ray2ZgAGhdkvoOfsc8g1l6ICQgEJ+9R5K2WKlDTEydmiHiSYQHSVyS61PFsk m537AqrLVSRu80Sezu2W4m8IF2UbbRZiUPaHPIx9Xe3GdpqIEmPFfA== -----END CERTIFICATE----- mod_auth_openidc-2.4.16.10/test/ecpriv.key000066400000000000000000000006001476721736500203230ustar00rootroot00000000000000-----BEGIN PRIVATE KEY----- MIHuAgEAMBAGByqGSM49AgEGBSuBBAAjBIHWMIHTAgEBBEIAoXDJYBnYWJNMSh9d c4LojYYE9B4TIKexXOjTvmEI5x5tiAunNKPzFZ+ROrGxSrwUXlzGUpLePKbeqCy5 XQRvTB2hgYkDgYYABAEehIfVYXSxAPq5uVkdP09C4ysrCbgZEqF2kfBo6YR76Dk6 5Y3AAkbFZUxEvRTJG8WzRLOCXcT4DyZfRvnvVhCU3gGppoH5WWibkncnCv3HFF5E l4zUHfDbKibr/SxaZOWLNTgIoq1purpLVxvACzUsUn33LqwavM6mcmdeNXVeqksy kA== -----END PRIVATE KEY----- mod_auth_openidc-2.4.16.10/test/open-redirect-payload-list.txt000066400000000000000000000667431476721736500242460ustar00rootroot00000000000000/%09/example.com / /example.com /%2f%2fexample.com /%2f%2f%2fbing.com%2f%3fwww.omise.co /%2f%5c%2f%67%6f%6f%67%6c%65%2e%63%6f%6d/ /%5cexample.com /%68%74%74%70%3a%2f%2f%67%6f%6f%67%6c%65%2e%63%6f%6d //%09/example.com //%5cexample.com ///%09/example.com ///%5cexample.com ////%09/example.com ////%5cexample.com /////example.com /////example.com/ ////\;@example.com ////example.com/ ////example.com/%2e%2e ////example.com/%2e%2e%2f ////example.com/%2f%2e%2e ////example.com/%2f.. ////example.com// ///\;@example.com ///example.com ///example.com/ //google.com/%2f.. //www.whitelisteddomain.tld@google.com/%2f.. ///google.com/%2f.. ///www.whitelisteddomain.tld@google.com/%2f.. ////google.com/%2f.. ////www.whitelisteddomain.tld@google.com/%2f.. https://google.com/%2f.. https://www.whitelisteddomain.tld@google.com/%2f.. /https://google.com/%2f.. /https://www.whitelisteddomain.tld@google.com/%2f.. //www.google.com/%2f%2e%2e //www.whitelisteddomain.tld@www.google.com/%2f%2e%2e ///www.google.com/%2f%2e%2e ///www.whitelisteddomain.tld@www.google.com/%2f%2e%2e ////www.google.com/%2f%2e%2e ////www.whitelisteddomain.tld@www.google.com/%2f%2e%2e https://www.google.com/%2f%2e%2e https://www.whitelisteddomain.tld@www.google.com/%2f%2e%2e /https://www.google.com/%2f%2e%2e /https://www.whitelisteddomain.tld@www.google.com/%2f%2e%2e //google.com/ //www.whitelisteddomain.tld@google.com/ ///google.com/ ///www.whitelisteddomain.tld@google.com/ ////google.com/ ////www.whitelisteddomain.tld@google.com/ https://google.com/ https://www.whitelisteddomain.tld@google.com/ /https://google.com/ /https://www.whitelisteddomain.tld@google.com/ //google.com// //www.whitelisteddomain.tld@google.com// ///google.com// ///www.whitelisteddomain.tld@google.com// ////google.com// ////www.whitelisteddomain.tld@google.com// https://google.com// https://www.whitelisteddomain.tld@google.com// //https://google.com// //https://www.whitelisteddomain.tld@google.com// //www.google.com/%2e%2e%2f //www.whitelisteddomain.tld@www.google.com/%2e%2e%2f ///www.google.com/%2e%2e%2f ///www.whitelisteddomain.tld@www.google.com/%2e%2e%2f ////www.google.com/%2e%2e%2f ////www.whitelisteddomain.tld@www.google.com/%2e%2e%2f https://www.google.com/%2e%2e%2f https://www.whitelisteddomain.tld@www.google.com/%2e%2e%2f //https://www.google.com/%2e%2e%2f //https://www.whitelisteddomain.tld@www.google.com/%2e%2e%2f ///www.google.com/%2e%2e ///www.whitelisteddomain.tld@www.google.com/%2e%2e ////www.google.com/%2e%2e ////www.whitelisteddomain.tld@www.google.com/%2e%2e https:///www.google.com/%2e%2e https:///www.whitelisteddomain.tld@www.google.com/%2e%2e //https:///www.google.com/%2e%2e //www.whitelisteddomain.tld@https:///www.google.com/%2e%2e /https://www.google.com/%2e%2e /https://www.whitelisteddomain.tld@www.google.com/%2e%2e ///www.google.com/%2f%2e%2e ///www.whitelisteddomain.tld@www.google.com/%2f%2e%2e ////www.google.com/%2f%2e%2e ////www.whitelisteddomain.tld@www.google.com/%2f%2e%2e https:///www.google.com/%2f%2e%2e https:///www.whitelisteddomain.tld@www.google.com/%2f%2e%2e /https://www.google.com/%2f%2e%2e /https://www.whitelisteddomain.tld@www.google.com/%2f%2e%2e /https:///www.google.com/%2f%2e%2e /https:///www.whitelisteddomain.tld@www.google.com/%2f%2e%2e /%09/google.com /%09/www.whitelisteddomain.tld@google.com //%09/google.com //%09/www.whitelisteddomain.tld@google.com ///%09/google.com ///%09/www.whitelisteddomain.tld@google.com ////%09/google.com ////%09/www.whitelisteddomain.tld@google.com https://%09/google.com https://%09/www.whitelisteddomain.tld@google.com /%5cgoogle.com /%5cwww.whitelisteddomain.tld@google.com //%5cgoogle.com //%5cwww.whitelisteddomain.tld@google.com ///%5cgoogle.com ///%5cwww.whitelisteddomain.tld@google.com ////%5cgoogle.com ////%5cwww.whitelisteddomain.tld@google.com https://%5cgoogle.com https://%5cwww.whitelisteddomain.tld@google.com /https://%5cgoogle.com /https://%5cwww.whitelisteddomain.tld@google.com https://google.com https://www.whitelisteddomain.tld@google.com javascript:alert(1); javascript:alert(1) //javascript:alert(1); /javascript:alert(1); //javascript:alert(1) /javascript:alert(1) /%5cjavascript:alert(1); /%5cjavascript:alert(1) //%5cjavascript:alert(1); //%5cjavascript:alert(1) /%09/javascript:alert(1); /%09/javascript:alert(1) java%0d%0ascript%0d%0a:alert(0) //google.com https:google.com //google%E3%80%82com \/\/google.com/ /\/google.com/ //google%00.com https://www.whitelisteddomain.tld/https://www.google.com/ ";alert(0);// javascript://www.whitelisteddomain.tld?%a0alert%281%29 http://0xd8.0x3a.0xd6.0xce http://www.whitelisteddomain.tld@0xd8.0x3a.0xd6.0xce http://3H6k7lIAiqjfNeN@0xd8.0x3a.0xd6.0xce http://XY>.7d8T\205pZM@0xd8.0x3a.0xd6.0xce http://0xd83ad6ce http://www.whitelisteddomain.tld@0xd83ad6ce http://3H6k7lIAiqjfNeN@0xd83ad6ce http://XY>.7d8T\205pZM@0xd83ad6ce http://3627734734 http://www.whitelisteddomain.tld@3627734734 http://3H6k7lIAiqjfNeN@3627734734 http://XY>.7d8T\205pZM@3627734734 http://472.314.470.462 http://www.whitelisteddomain.tld@472.314.470.462 http://3H6k7lIAiqjfNeN@472.314.470.462 http://XY>.7d8T\205pZM@472.314.470.462 http://0330.072.0326.0316 http://www.whitelisteddomain.tld@0330.072.0326.0316 http://3H6k7lIAiqjfNeN@0330.072.0326.0316 http://XY>.7d8T\205pZM@0330.072.0326.0316 http://00330.00072.0000326.00000316 http://www.whitelisteddomain.tld@00330.00072.0000326.00000316 http://3H6k7lIAiqjfNeN@00330.00072.0000326.00000316 http://XY>.7d8T\205pZM@00330.00072.0000326.00000316 http://[::216.58.214.206] http://www.whitelisteddomain.tld@[::216.58.214.206] http://3H6k7lIAiqjfNeN@[::216.58.214.206] http://XY>.7d8T\205pZM@[::216.58.214.206] http://[::ffff:216.58.214.206] http://www.whitelisteddomain.tld@[::ffff:216.58.214.206] http://3H6k7lIAiqjfNeN@[::ffff:216.58.214.206] http://XY>.7d8T\205pZM@[::ffff:216.58.214.206] http://0xd8.072.54990 http://www.whitelisteddomain.tld@0xd8.072.54990 http://3H6k7lIAiqjfNeN@0xd8.072.54990 http://XY>.7d8T\205pZM@0xd8.072.54990 http://0xd8.3856078 http://www.whitelisteddomain.tld@0xd8.3856078 http://3H6k7lIAiqjfNeN@0xd8.3856078 http://XY>.7d8T\205pZM@0xd8.3856078 http://00330.3856078 http://www.whitelisteddomain.tld@00330.3856078 http://3H6k7lIAiqjfNeN@00330.3856078 http://XY>.7d8T\205pZM@00330.3856078 http://00330.0x3a.54990 http://www.whitelisteddomain.tld@00330.0x3a.54990 http://3H6k7lIAiqjfNeN@00330.0x3a.54990 http://XY>.7d8T\205pZM@00330.0x3a.54990 http:0xd8.0x3a.0xd6.0xce http:www.whitelisteddomain.tld@0xd8.0x3a.0xd6.0xce http:3H6k7lIAiqjfNeN@0xd8.0x3a.0xd6.0xce http:XY>.7d8T\205pZM@0xd8.0x3a.0xd6.0xce http:0xd83ad6ce http:www.whitelisteddomain.tld@0xd83ad6ce http:3H6k7lIAiqjfNeN@0xd83ad6ce http:XY>.7d8T\205pZM@0xd83ad6ce http:3627734734 http:www.whitelisteddomain.tld@3627734734 http:3H6k7lIAiqjfNeN@3627734734 http:XY>.7d8T\205pZM@3627734734 http:472.314.470.462 http:www.whitelisteddomain.tld@472.314.470.462 http:3H6k7lIAiqjfNeN@472.314.470.462 http:XY>.7d8T\205pZM@472.314.470.462 http:0330.072.0326.0316 http:www.whitelisteddomain.tld@0330.072.0326.0316 http:3H6k7lIAiqjfNeN@0330.072.0326.0316 http:XY>.7d8T\205pZM@0330.072.0326.0316 http:00330.00072.0000326.00000316 http:www.whitelisteddomain.tld@00330.00072.0000326.00000316 http:3H6k7lIAiqjfNeN@00330.00072.0000326.00000316 http:XY>.7d8T\205pZM@00330.00072.0000326.00000316 http:[::216.58.214.206] http:www.whitelisteddomain.tld@[::216.58.214.206] http:3H6k7lIAiqjfNeN@[::216.58.214.206] http:XY>.7d8T\205pZM@[::216.58.214.206] http:[::ffff:216.58.214.206] http:www.whitelisteddomain.tld@[::ffff:216.58.214.206] http:3H6k7lIAiqjfNeN@[::ffff:216.58.214.206] http:XY>.7d8T\205pZM@[::ffff:216.58.214.206] http:0xd8.072.54990 http:www.whitelisteddomain.tld@0xd8.072.54990 http:3H6k7lIAiqjfNeN@0xd8.072.54990 http:XY>.7d8T\205pZM@0xd8.072.54990 http:0xd8.3856078 http:www.whitelisteddomain.tld@0xd8.3856078 http:3H6k7lIAiqjfNeN@0xd8.3856078 http:XY>.7d8T\205pZM@0xd8.3856078 http:00330.3856078 http:www.whitelisteddomain.tld@00330.3856078 http:3H6k7lIAiqjfNeN@00330.3856078 http:XY>.7d8T\205pZM@00330.3856078 http:00330.0x3a.54990 http:www.whitelisteddomain.tld@00330.0x3a.54990 http:3H6k7lIAiqjfNeN@00330.0x3a.54990 http:XY>.7d8T\205pZM@00330.0x3a.54990 〱google.com 〵google.com ゝgoogle.com ーgoogle.com ーgoogle.com /〱google.com /〵google.com /ゝgoogle.com /ーgoogle.com /ーgoogle.com %68%74%74%70%3a%2f%2f%67%6f%6f%67%6c%65%2e%63%6f%6d http://%67%6f%6f%67%6c%65%2e%63%6f%6d <>javascript:alert(1); <>//google.com //google.com\@www.whitelisteddomain.tld https://:@google.com\@www.whitelisteddomain.tld http://google.com:80#@www.whitelisteddomain.tld/ http://google.com:80?@www.whitelisteddomain.tld/ ///example.com/%2e%2e ///example.com/%2e%2e%2f ///example.com/%2f%2e%2e ///example.com/%2f.. ///example.com// //example.com //example.com/ //example.com/%2e%2e //example.com/%2e%2e%2f //example.com/%2f%2e%2e //example.com/%2f.. //example.com// //google%00.com //google%E3%80%82com //https:///example.com/%2e%2e //https://example.com/%2e%2e%2f //https://example.com// /<>//example.com /\/\/example.com/ /\/example.com/ /example.com/%2f%2e%2e /http://%67%6f%6f%67%6c%65%2e%63%6f%6d /http://example.com /http:/example.com /https:/%5cexample.com/ /https://%09/example.com /https://%5cexample.com /https:///example.com/%2e%2e /https:///example.com/%2f%2e%2e /https://example.com /https://example.com/ /https://example.com/%2e%2e /https://example.com/%2e%2e%2f /https://example.com/%2f%2e%2e /https://example.com/%2f.. /https://example.com// /https:example.com //%2fxgoogle.com //localdomain.pw/%2f.. //www.whitelisteddomain.tld@localdomain.pw/%2f.. ///localdomain.pw/%2f.. ///www.whitelisteddomain.tld@localdomain.pw/%2f.. ////localdomain.pw/%2f.. ////www.whitelisteddomain.tld@localdomain.pw/%2f.. https://localdomain.pw/%2f.. https://www.whitelisteddomain.tld@localdomain.pw/%2f.. /https://localdomain.pw/%2f.. /https://www.whitelisteddomain.tld@localdomain.pw/%2f.. //localdomain.pw/%2f%2e%2e //www.whitelisteddomain.tld@localdomain.pw/%2f%2e%2e ///localdomain.pw/%2f%2e%2e ///www.whitelisteddomain.tld@localdomain.pw/%2f%2e%2e ////localdomain.pw/%2f%2e%2e ////www.whitelisteddomain.tld@localdomain.pw/%2f%2e%2e https://localdomain.pw/%2f%2e%2e https://www.whitelisteddomain.tld@localdomain.pw/%2f%2e%2e /https://localdomain.pw/%2f%2e%2e /https://www.whitelisteddomain.tld@localdomain.pw/%2f%2e%2e //localdomain.pw/ //www.whitelisteddomain.tld@localdomain.pw/ ///localdomain.pw/ ///www.whitelisteddomain.tld@localdomain.pw/ ////localdomain.pw/ ////www.whitelisteddomain.tld@localdomain.pw/ https://localdomain.pw/ https://www.whitelisteddomain.tld@localdomain.pw/ /https://localdomain.pw/ /https://www.whitelisteddomain.tld@localdomain.pw/ //localdomain.pw// //www.whitelisteddomain.tld@localdomain.pw// ///localdomain.pw// ///www.whitelisteddomain.tld@localdomain.pw// ////localdomain.pw// ////www.whitelisteddomain.tld@localdomain.pw// https://localdomain.pw// https://www.whitelisteddomain.tld@localdomain.pw// //https://localdomain.pw// //https://www.whitelisteddomain.tld@localdomain.pw// //localdomain.pw/%2e%2e%2f //www.whitelisteddomain.tld@localdomain.pw/%2e%2e%2f ///localdomain.pw/%2e%2e%2f ///www.whitelisteddomain.tld@localdomain.pw/%2e%2e%2f ////localdomain.pw/%2e%2e%2f ////www.whitelisteddomain.tld@localdomain.pw/%2e%2e%2f https://localdomain.pw/%2e%2e%2f https://www.whitelisteddomain.tld@localdomain.pw/%2e%2e%2f //https://localdomain.pw/%2e%2e%2f //https://www.whitelisteddomain.tld@localdomain.pw/%2e%2e%2f ///localdomain.pw/%2e%2e ///www.whitelisteddomain.tld@localdomain.pw/%2e%2e ////localdomain.pw/%2e%2e ////www.whitelisteddomain.tld@localdomain.pw/%2e%2e https:///localdomain.pw/%2e%2e https:///www.whitelisteddomain.tld@localdomain.pw/%2e%2e //https:///localdomain.pw/%2e%2e //www.whitelisteddomain.tld@https:///localdomain.pw/%2e%2e /https://localdomain.pw/%2e%2e /https://www.whitelisteddomain.tld@localdomain.pw/%2e%2e ///localdomain.pw/%2f%2e%2e ///www.whitelisteddomain.tld@localdomain.pw/%2f%2e%2e ////localdomain.pw/%2f%2e%2e ////www.whitelisteddomain.tld@localdomain.pw/%2f%2e%2e https:///localdomain.pw/%2f%2e%2e https:///www.whitelisteddomain.tld@localdomain.pw/%2f%2e%2e /https://localdomain.pw/%2f%2e%2e /https://www.whitelisteddomain.tld@localdomain.pw/%2f%2e%2e /https:///localdomain.pw/%2f%2e%2e /https:///www.whitelisteddomain.tld@localdomain.pw/%2f%2e%2e /%09/localdomain.pw /%09/www.whitelisteddomain.tld@localdomain.pw //%09/localdomain.pw //%09/www.whitelisteddomain.tld@localdomain.pw ///%09/localdomain.pw ///%09/www.whitelisteddomain.tld@localdomain.pw ////%09/localdomain.pw ////%09/www.whitelisteddomain.tld@localdomain.pw https://%09/localdomain.pw https://%09/www.whitelisteddomain.tld@localdomain.pw /%5clocaldomain.pw /%5cwww.whitelisteddomain.tld@localdomain.pw //%5clocaldomain.pw //%5cwww.whitelisteddomain.tld@localdomain.pw ///%5clocaldomain.pw ///%5cwww.whitelisteddomain.tld@localdomain.pw ////%5clocaldomain.pw ////%5cwww.whitelisteddomain.tld@localdomain.pw https://%5clocaldomain.pw https://%5cwww.whitelisteddomain.tld@localdomain.pw /https://%5clocaldomain.pw /https://%5cwww.whitelisteddomain.tld@localdomain.pw https://localdomain.pw https://www.whitelisteddomain.tld@localdomain.pw javascript:alert(1); javascript:alert(1) //javascript:alert(1); /javascript:alert(1); //javascript:alert(1) /javascript:alert(1) /%5cjavascript:alert(1); /%5cjavascript:alert(1) //%5cjavascript:alert(1); //%5cjavascript:alert(1) /%09/javascript:alert(1); /%09/javascript:alert(1) java%0d%0ascript%0d%0a:alert(0) //localdomain.pw https:localdomain.pw //localdomain%E3%80%82pw \/\/localdomain.pw/ /\/localdomain.pw/ /%2f%5c%2f%67%6f%6f%67%6c%65%2e%63%6f%6d/ //localdomain%00.pw https://www.whitelisteddomain.tld/https://localdomain.pw/ ";alert(0);// javascript://www.whitelisteddomain.tld?%a0alert%281%29 http://0xd8.0x3a.0xd6.0xce http://www.whitelisteddomain.tld@0xd8.0x3a.0xd6.0xce http://3H6k7lIAiqjfNeN@0xd8.0x3a.0xd6.0xce http://XY>.7d8T\205pZM@0xd8.0x3a.0xd6.0xce http://0xd83ad6ce http://www.whitelisteddomain.tld@0xd83ad6ce http://3H6k7lIAiqjfNeN@0xd83ad6ce http://XY>.7d8T\205pZM@0xd83ad6ce http://3627734734 http://www.whitelisteddomain.tld@3627734734 http://3H6k7lIAiqjfNeN@3627734734 http://XY>.7d8T\205pZM@3627734734 http://472.314.470.462 http://www.whitelisteddomain.tld@472.314.470.462 http://3H6k7lIAiqjfNeN@472.314.470.462 http://XY>.7d8T\205pZM@472.314.470.462 http://0330.072.0326.0316 http://www.whitelisteddomain.tld@0330.072.0326.0316 http://3H6k7lIAiqjfNeN@0330.072.0326.0316 http://XY>.7d8T\205pZM@0330.072.0326.0316 http://00330.00072.0000326.00000316 http://www.whitelisteddomain.tld@00330.00072.0000326.00000316 http://3H6k7lIAiqjfNeN@00330.00072.0000326.00000316 http://XY>.7d8T\205pZM@00330.00072.0000326.00000316 http://[::216.58.214.206] http://www.whitelisteddomain.tld@[::216.58.214.206] http://3H6k7lIAiqjfNeN@[::216.58.214.206] http://XY>.7d8T\205pZM@[::216.58.214.206] http://[::ffff:216.58.214.206] http://www.whitelisteddomain.tld@[::ffff:216.58.214.206] http://3H6k7lIAiqjfNeN@[::ffff:216.58.214.206] http://XY>.7d8T\205pZM@[::ffff:216.58.214.206] http://0xd8.072.54990 http://www.whitelisteddomain.tld@0xd8.072.54990 http://3H6k7lIAiqjfNeN@0xd8.072.54990 http://XY>.7d8T\205pZM@0xd8.072.54990 http://0xd8.3856078 http://www.whitelisteddomain.tld@0xd8.3856078 http://3H6k7lIAiqjfNeN@0xd8.3856078 http://XY>.7d8T\205pZM@0xd8.3856078 http://00330.3856078 http://www.whitelisteddomain.tld@00330.3856078 http://3H6k7lIAiqjfNeN@00330.3856078 http://XY>.7d8T\205pZM@00330.3856078 http://00330.0x3a.54990 http://www.whitelisteddomain.tld@00330.0x3a.54990 http://3H6k7lIAiqjfNeN@00330.0x3a.54990 http://XY>.7d8T\205pZM@00330.0x3a.54990 http:0xd8.0x3a.0xd6.0xce http:www.whitelisteddomain.tld@0xd8.0x3a.0xd6.0xce http:3H6k7lIAiqjfNeN@0xd8.0x3a.0xd6.0xce http:XY>.7d8T\205pZM@0xd8.0x3a.0xd6.0xce http:0xd83ad6ce http:www.whitelisteddomain.tld@0xd83ad6ce http:3H6k7lIAiqjfNeN@0xd83ad6ce http:XY>.7d8T\205pZM@0xd83ad6ce http:3627734734 http:www.whitelisteddomain.tld@3627734734 http:3H6k7lIAiqjfNeN@3627734734 http:XY>.7d8T\205pZM@3627734734 http:472.314.470.462 http:www.whitelisteddomain.tld@472.314.470.462 http:3H6k7lIAiqjfNeN@472.314.470.462 http:XY>.7d8T\205pZM@472.314.470.462 http:0330.072.0326.0316 http:www.whitelisteddomain.tld@0330.072.0326.0316 http:3H6k7lIAiqjfNeN@0330.072.0326.0316 http:XY>.7d8T\205pZM@0330.072.0326.0316 http:00330.00072.0000326.00000316 http:www.whitelisteddomain.tld@00330.00072.0000326.00000316 http:3H6k7lIAiqjfNeN@00330.00072.0000326.00000316 http:XY>.7d8T\205pZM@00330.00072.0000326.00000316 http:[::216.58.214.206] http:www.whitelisteddomain.tld@[::216.58.214.206] http:3H6k7lIAiqjfNeN@[::216.58.214.206] http:XY>.7d8T\205pZM@[::216.58.214.206] http:[::ffff:216.58.214.206] http:www.whitelisteddomain.tld@[::ffff:216.58.214.206] http:3H6k7lIAiqjfNeN@[::ffff:216.58.214.206] http:XY>.7d8T\205pZM@[::ffff:216.58.214.206] http:0xd8.072.54990 http:www.whitelisteddomain.tld@0xd8.072.54990 http:3H6k7lIAiqjfNeN@0xd8.072.54990 http:XY>.7d8T\205pZM@0xd8.072.54990 http:0xd8.3856078 http:www.whitelisteddomain.tld@0xd8.3856078 http:3H6k7lIAiqjfNeN@0xd8.3856078 http:XY>.7d8T\205pZM@0xd8.3856078 http:00330.3856078 http:www.whitelisteddomain.tld@00330.3856078 http:3H6k7lIAiqjfNeN@00330.3856078 http:XY>.7d8T\205pZM@00330.3856078 http:00330.0x3a.54990 http:www.whitelisteddomain.tld@00330.0x3a.54990 http:3H6k7lIAiqjfNeN@00330.0x3a.54990 http:XY>.7d8T\205pZM@00330.0x3a.54990 〱localdomain.pw 〵localdomain.pw ゝlocaldomain.pw ーlocaldomain.pw ーlocaldomain.pw /〱localdomain.pw /〵localdomain.pw /ゝlocaldomain.pw /ーlocaldomain.pw /ーlocaldomain.pw %68%74%74%70%3a%2f%2f%67%6f%6f%67%6c%65%2e%63%6f%6d http://%67%6f%6f%67%6c%65%2e%63%6f%6d <>javascript:alert(1); <>//localdomain.pw //localdomain.pw\@www.whitelisteddomain.tld https://:@localdomain.pw\@www.whitelisteddomain.tld http://localdomain.pw:80#@www.whitelisteddomain.tld/ http://localdomain.pw:80?@www.whitelisteddomain.tld/ http://3H6k7lIAiqjfNeN@www.whitelisteddomain.tld+@localdomain.pw/ http://XY>.7d8T\205pZM@www.whitelisteddomain.tld+@localdomain.pw/ http://3H6k7lIAiqjfNeN@www.whitelisteddomain.tld@localdomain.pw/ http://XY>.7d8T\205pZM@www.whitelisteddomain.tld@localdomain.pw/ http://www.whitelisteddomain.tld+&@localdomain.pw#+@www.whitelisteddomain.tld/ http://localdomain.pw\twww.whitelisteddomain.tld/ //localdomain.pw:80#@www.whitelisteddomain.tld/ //localdomain.pw:80?@www.whitelisteddomain.tld/ //3H6k7lIAiqjfNeN@www.whitelisteddomain.tld+@localdomain.pw/ //XY>.7d8T\205pZM@www.whitelisteddomain.tld+@localdomain.pw/ //3H6k7lIAiqjfNeN@www.whitelisteddomain.tld@localdomain.pw/ //XY>.7d8T\205pZM@www.whitelisteddomain.tld@localdomain.pw/ //www.whitelisteddomain.tld+&@localdomain.pw#+@www.whitelisteddomain.tld/ //localdomain.pw\twww.whitelisteddomain.tld/ //;@localdomain.pw http://;@localdomain.pw @localdomain.pw javascript://https://www.whitelisteddomain.tld/?z=%0Aalert(1) data:text/html;base64,PHNjcmlwdD5hbGVydCgiWFNTIik8L3NjcmlwdD4= http://localdomain.pw%2f%2f.www.whitelisteddomain.tld/ http://localdomain.pw%5c%5c.www.whitelisteddomain.tld/ http://localdomain.pw%3F.www.whitelisteddomain.tld/ http://localdomain.pw%23.www.whitelisteddomain.tld/ http://www.whitelisteddomain.tld:80%40localdomain.pw/ http://www.whitelisteddomain.tld%2elocaldomain.pw/ /x:1/:///%01javascript:alert(document.cookie)/ /https:/%5clocaldomain.pw/ javascripT://anything%0D%0A%0D%0Awindow.alert(document.cookie) /http://localdomain.pw /%2f%2flocaldomain.pw /localdomain.pw/%2f%2e%2e /http:/localdomain.pw http://.localdomain.pw .localdomain.pw ///\;@localdomain.pw ///localdomain.pw /////localdomain.pw/ /////localdomain.pw java%0ascript:alert(1) java%09script:alert(1) java%0dscript:alert(1) javascript://%0aalert(1) Javas%26%2399;ript:alert(1) data:www.whitelisteddomain.tld;text/html;charset=UTF-8, jaVAscript://www.whitelisteddomain.tld//%0d%0aalert(1);// http://www.localdomain.pw\.www.whitelisteddomain.tld %19Jav%09asc%09ript:https%20://www.whitelisteddomain.tld/%250Aconfirm%25281%2529 //example.com@google.com/%2f.. ///google.com/%2f.. ///example.com@google.com/%2f.. ////google.com/%2f.. ////example.com@google.com/%2f.. https://google.com/%2f.. https://example.com@google.com/%2f.. /https://google.com/%2f.. /https://example.com@google.com/%2f.. //google.com/%2f%2e%2e //example.com@google.com/%2f%2e%2e ///google.com/%2f%2e%2e ///example.com@google.com/%2f%2e%2e ////google.com/%2f%2e%2e ////example.com@google.com/%2f%2e%2e https://google.com/%2f%2e%2e https://example.com@google.com/%2f%2e%2e /https://google.com/%2f%2e%2e /https://example.com@google.com/%2f%2e%2e //google.com/ //example.com@google.com/ ///google.com/ ///example.com@google.com/ ////google.com/ ////example.com@google.com/ https://google.com/ https://example.com@google.com/ /https://google.com/ /https://example.com@google.com/ //google.com// //example.com@google.com// ///google.com// ///example.com@google.com// ////google.com// ////example.com@google.com// https://google.com// https://example.com@google.com// //https://google.com// //https://example.com@google.com// //google.com/%2e%2e%2f //example.com@google.com/%2e%2e%2f ///google.com/%2e%2e%2f ///example.com@google.com/%2e%2e%2f ////google.com/%2e%2e%2f ////example.com@google.com/%2e%2e%2f https://google.com/%2e%2e%2f https://example.com@google.com/%2e%2e%2f //https://google.com/%2e%2e%2f //https://example.com@google.com/%2e%2e%2f ///google.com/%2e%2e ///example.com@google.com/%2e%2e ////google.com/%2e%2e ////example.com@google.com/%2e%2e https:///google.com/%2e%2e https:///example.com@google.com/%2e%2e //https:///google.com/%2e%2e //example.com@https:///google.com/%2e%2e /https://google.com/%2e%2e /https://example.com@google.com/%2e%2e ///google.com/%2f%2e%2e ///example.com@google.com/%2f%2e%2e ////google.com/%2f%2e%2e ////example.com@google.com/%2f%2e%2e https:///google.com/%2f%2e%2e https:///example.com@google.com/%2f%2e%2e /https://google.com/%2f%2e%2e /https://example.com@google.com/%2f%2e%2e /https:///google.com/%2f%2e%2e /https:///example.com@google.com/%2f%2e%2e /%09/google.com /%09/example.com@google.com //%09/google.com //%09/example.com@google.com ///%09/google.com ///%09/example.com@google.com ////%09/google.com ////%09/example.com@google.com https://%09/google.com https://%09/example.com@google.com /%5cgoogle.com /%5cexample.com@google.com //%5cgoogle.com //%5cexample.com@google.com ///%5cgoogle.com ///%5cexample.com@google.com ////%5cgoogle.com ////%5cexample.com@google.com https://%5cgoogle.com https://%5cexample.com@google.com /https://%5cgoogle.com /https://%5cexample.com@google.com https://google.com https://example.com@google.com javascript:alert(1); javascript:alert(1) //javascript:alert(1); /javascript:alert(1); //javascript:alert(1) /javascript:alert(1) /%5cjavascript:alert(1); /%5cjavascript:alert(1) //%5cjavascript:alert(1); //%5cjavascript:alert(1) /%09/javascript:alert(1); /%09/javascript:alert(1) java%0d%0ascript%0d%0a:alert(0) //google.com https:google.com //google%E3%80%82com \/\/google.com/ /\/google.com/ //google%00.com https://example.com/https://google.com/ ";alert(0);// javascript://example.com?%a0alert%281%29 http://0xd8.0x3a.0xd6.0xce http://example.com@0xd8.0x3a.0xd6.0xce http://3H6k7lIAiqjfNeN@0xd8.0x3a.0xd6.0xce http://XY>.7d8T\205pZM@0xd8.0x3a.0xd6.0xce http://0xd83ad6ce http://example.com@0xd83ad6ce http://3H6k7lIAiqjfNeN@0xd83ad6ce http://XY>.7d8T\205pZM@0xd83ad6ce http://3627734734 http://example.com@3627734734 http://3H6k7lIAiqjfNeN@3627734734 http://XY>.7d8T\205pZM@3627734734 http://472.314.470.462 http://example.com@472.314.470.462 http://3H6k7lIAiqjfNeN@472.314.470.462 http://XY>.7d8T\205pZM@472.314.470.462 http://0330.072.0326.0316 http://example.com@0330.072.0326.0316 http://3H6k7lIAiqjfNeN@0330.072.0326.0316 http://XY>.7d8T\205pZM@0330.072.0326.0316 http://00330.00072.0000326.00000316 http://example.com@00330.00072.0000326.00000316 http://3H6k7lIAiqjfNeN@00330.00072.0000326.00000316 http://XY>.7d8T\205pZM@00330.00072.0000326.00000316 http://[::216.58.214.206] http://example.com@[::216.58.214.206] http://3H6k7lIAiqjfNeN@[::216.58.214.206] http://XY>.7d8T\205pZM@[::216.58.214.206] http://[::ffff:216.58.214.206] http://example.com@[::ffff:216.58.214.206] http://3H6k7lIAiqjfNeN@[::ffff:216.58.214.206] http://XY>.7d8T\205pZM@[::ffff:216.58.214.206] http://0xd8.072.54990 http://example.com@0xd8.072.54990 http://3H6k7lIAiqjfNeN@0xd8.072.54990 http://XY>.7d8T\205pZM@0xd8.072.54990 http://0xd8.3856078 http://example.com@0xd8.3856078 http://3H6k7lIAiqjfNeN@0xd8.3856078 http://XY>.7d8T\205pZM@0xd8.3856078 http://00330.3856078 http://example.com@00330.3856078 http://3H6k7lIAiqjfNeN@00330.3856078 http://XY>.7d8T\205pZM@00330.3856078 http://00330.0x3a.54990 http://example.com@00330.0x3a.54990 http://3H6k7lIAiqjfNeN@00330.0x3a.54990 http://XY>.7d8T\205pZM@00330.0x3a.54990 http:0xd8.0x3a.0xd6.0xce http:example.com@0xd8.0x3a.0xd6.0xce http:3H6k7lIAiqjfNeN@0xd8.0x3a.0xd6.0xce http:XY>.7d8T\205pZM@0xd8.0x3a.0xd6.0xce http:0xd83ad6ce http:example.com@0xd83ad6ce http:3H6k7lIAiqjfNeN@0xd83ad6ce http:XY>.7d8T\205pZM@0xd83ad6ce http:3627734734 http:example.com@3627734734 http:3H6k7lIAiqjfNeN@3627734734 http:XY>.7d8T\205pZM@3627734734 http:472.314.470.462 http:example.com@472.314.470.462 http:3H6k7lIAiqjfNeN@472.314.470.462 http:XY>.7d8T\205pZM@472.314.470.462 http:0330.072.0326.0316 http:example.com@0330.072.0326.0316 http:3H6k7lIAiqjfNeN@0330.072.0326.0316 http:XY>.7d8T\205pZM@0330.072.0326.0316 http:00330.00072.0000326.00000316 http:example.com@00330.00072.0000326.00000316 http:3H6k7lIAiqjfNeN@00330.00072.0000326.00000316 http:XY>.7d8T\205pZM@00330.00072.0000326.00000316 http:[::216.58.214.206] http:example.com@[::216.58.214.206] http:3H6k7lIAiqjfNeN@[::216.58.214.206] http:XY>.7d8T\205pZM@[::216.58.214.206] http:[::ffff:216.58.214.206] http:example.com@[::ffff:216.58.214.206] http:3H6k7lIAiqjfNeN@[::ffff:216.58.214.206] http:XY>.7d8T\205pZM@[::ffff:216.58.214.206] http:0xd8.072.54990 http:example.com@0xd8.072.54990 http:3H6k7lIAiqjfNeN@0xd8.072.54990 http:XY>.7d8T\205pZM@0xd8.072.54990 http:0xd8.3856078 http:example.com@0xd8.3856078 http:3H6k7lIAiqjfNeN@0xd8.3856078 http:XY>.7d8T\205pZM@0xd8.3856078 http:00330.3856078 http:example.com@00330.3856078 http:3H6k7lIAiqjfNeN@00330.3856078 http:XY>.7d8T\205pZM@00330.3856078 http:00330.0x3a.54990 http:example.com@00330.0x3a.54990 http:3H6k7lIAiqjfNeN@00330.0x3a.54990 http:XY>.7d8T\205pZM@00330.0x3a.54990 〱google.com 〵google.com ゝgoogle.com ーgoogle.com ーgoogle.com /〱google.com /〵google.com /ゝgoogle.com /ーgoogle.com /ーgoogle.com %68%74%74%70%3a%2f%2f%67%6f%6f%67%6c%65%2e%63%6f%6d http://%67%6f%6f%67%6c%65%2e%63%6f%6d <>javascript:alert(1); <>//google.com //google.com\@example.com https://:@google.com\@example.com http://google.com:80#@example.com/ http://google.com:80?@example.com/ http://3H6k7lIAiqjfNeN@example.com+@google.com/ http://XY>.7d8T\205pZM@example.com+@google.com/ http://3H6k7lIAiqjfNeN@example.com@google.com/ http://XY>.7d8T\205pZM@example.com@google.com/ http://example.com+&@google.com#+@example.com/ http://google.com\texample.com/ //google.com:80#@example.com/ //google.com:80?@example.com/ //3H6k7lIAiqjfNeN@example.com+@google.com/ //XY>.7d8T\205pZM@example.com+@google.com/ //3H6k7lIAiqjfNeN@example.com@google.com/ //XY>.7d8T\205pZM@example.com@google.com/ //example.com+&@google.com#+@example.com/ //google.com\texample.com/ //;@google.com http://;@google.com @google.com javascript://https://example.com/?z=%0Aalert(1) data:text/html;base64,PHNjcmlwdD5hbGVydCgiWFNTIik8L3NjcmlwdD4= http://google.com%2f%2f.example.com/ http://google.com%5c%5c.example.com/ http://google.com%3F.example.com/ http://google.com%23.example.com/ http://example.com:80%40google.com/ http://example.com%2egoogle.com/ /x:1/:///%01javascript:alert(document.cookie)/ /https:/%5cgoogle.com/ javascripT://anything%0D%0A%0D%0Awindow.alert(document.cookie) /http://google.com /%2f%2fgoogle.com /google.com/%2f%2e%2e /http:/google.com ///\;@google.com ///google.com /////google.com/mod_auth_openidc-2.4.16.10/test/post_preserve.template000066400000000000000000000007511476721736500227650ustar00rootroot00000000000000 Preserving...

Preserving...

mod_auth_openidc-2.4.16.10/test/post_restore.template000066400000000000000000000021461476721736500226150ustar00rootroot00000000000000 Restoring...

Restoring...

mod_auth_openidc-2.4.16.10/test/private.pem000066400000000000000000000032131476721736500205010ustar00rootroot00000000000000-----BEGIN RSA PRIVATE KEY----- MIIEowIBAAKCAQEAiGeTXbfV5bMppx7o7qMLCuVIKqbBa/qOzBiNNpe0K8rjg7+1 z9GCuSlqbZtM0/5BQ6bGonnSPD++PowhFdivS4WNA33O0Kl1tQ0wdH3TOnwueIO9 ahfW4q0BGFvMObneK+tjwiNMj1l+cZt8pvuS+3LtTWIzC+hTZM4caUmy5olm5PVd mru6C6V5rxkbYBPITFSzl5mpuo/C6RV/MYRwAh60ghs2OEvIWDrJkZnYaF7sjHC9 j+4kfcM5oY7Zhg8KuHyloudYNzlqjVAPd0MbkLkh1pa8fmHsnN6cgfXYtFK7Z8Wj YDUAhTH1JjZCVSFN55A+51dgD4cQNzieLEEkJwIDAQABAoIBAF3PXfpGREUFQtA8 4dW9LAsCRO+QX9XzK+IRwIybKL41euNRJakXXeAaK6fV9rCVXC06tcFoJr5o2F4L 4XU04Nn/r0uHaoT3BozN1VVIc8z1OsCHWe1tF8wtT2OBPqM0wSdTa/hIbo7n7Z4U YVY2DpAAKlPeBV1bGn9pgQCoPvFs61sdjHlo3y3BO/Q5ki/SMNmq3ilURpSnXSCD 5x2/jMPRwe2QslUwtb+bKoUD8uqj6PFx3UI7vxGEZSDjJLFPJHG9tEf3s4m/6x7l cCVZUzZOjLxVCI+Gsl3V9d0MuJEwUYkDO6bpj2sqRBOAqwH01j9AGJxMM/xRpbjq TGTc6AECgYEA0ENWGZHWN84M1ztxH1DSL+po7MxEdKcwGSqye6379px0ximKr+8+ DWKS8xeWsBc1OupdrzO2p7lvWwYBstlJoLfVMVEHeLEhkN2fdBjeRzDf2RbbtZnd NAYHNSX4xv9B8DyJs7js0uVVmOuuojNkoQFqh3NcIFsC7OuGfEhR48ECgYEAp6uo rIDqGD2YCL+Bx6lEf96Jlcmu5zwB3jnKqSxA5PnkbE4ZS3RidM0HSrUABcq3n4So 61lwdbGtnjyhBXrEYbpNokVye6RV5DwXZN9LVeA/aZWlAccUHSoDuwovSsowfoG4 xj8yyUUFE8/+xgM0CnqaoWP1tCkrhXk5mX0w4ecCgYEAhp7wOekOOtZjcIFI90As DbMNjfvgSDOGIM57vvzRATFTPoC92Enip45PhPl7e2oVC3dRhZ389OAl/gWc9XoF YPFTyuQg20BMfTL1DnvAuu351H81GGdUGHvJDu7zp9Z6Tgsjy9u+ofiCYy39nXVx F64tqU7Ff1i1RGZecVniLUECgYBdyeyhCb9obdPEWPNMbweNCzsk2VsHp45X8zXE qadnLc0zNAB8L47/TMyeYl6v3rQV+8vNUgtRGmFGmR1tBj4heGgCtBwUw1j0QRTI 7Qqj77so4XcaZnR+18iccFcB29WCfieQZTuQUBZF/dvCgXozvl8Ole6Tp0/b6nJo xBl60wKBgDHdWW2qVche9wuZf6iDyVpE1DouMeg3sUxq77KCUXsmIHUuGgqlhuIj wLIgkuBOf4dEmSlwB/4onxTx+DRcvK89oZjFUnL9CR8jaE1HQACKj3xMn1ubkXqH z6itCDUZ0fP+LLYSREyBHlnaYOtXJgxKKK2yC1kTJmBs9HUMaLNk -----END RSA PRIVATE KEY----- mod_auth_openidc-2.4.16.10/test/public.pem000066400000000000000000000007031476721736500203060ustar00rootroot00000000000000-----BEGIN PUBLIC KEY----- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAiGeTXbfV5bMppx7o7qML CuVIKqbBa/qOzBiNNpe0K8rjg7+1z9GCuSlqbZtM0/5BQ6bGonnSPD++PowhFdiv S4WNA33O0Kl1tQ0wdH3TOnwueIO9ahfW4q0BGFvMObneK+tjwiNMj1l+cZt8pvuS +3LtTWIzC+hTZM4caUmy5olm5PVdmru6C6V5rxkbYBPITFSzl5mpuo/C6RV/MYRw Ah60ghs2OEvIWDrJkZnYaF7sjHC9j+4kfcM5oY7Zhg8KuHyloudYNzlqjVAPd0Mb kLkh1pa8fmHsnN6cgfXYtFK7Z8WjYDUAhTH1JjZCVSFN55A+51dgD4cQNzieLEEk JwIDAQAB -----END PUBLIC KEY----- mod_auth_openidc-2.4.16.10/test/stub.c000066400000000000000000000176101476721736500174530ustar00rootroot00000000000000#include #include #include // clang-format off #include #include #include #include // clang-format on #define HAVE_APACHE_24 MODULE_MAGIC_NUMBER_MAJOR >= 20100714 #define ap_HOOK_check_user_id_t void AP_DECLARE(void) ap_hook_check_authn(ap_HOOK_check_user_id_t *pf, const char *const *aszPre, const char *const *aszSucc, int nOrder, int type) { // comment explaining why the method is empty } AP_DECLARE(apr_status_t) ap_register_auth_provider(apr_pool_t *pool, const char *provider_group, const char *provider_name, const char *provider_version, const void *provider, int type) { return 0; } AP_DECLARE(apr_status_t) ap_unixd_set_global_mutex_perms(apr_global_mutex_t *gmutex) { return 0; } AP_DECLARE(const char *) ap_auth_type(request_rec *r) { return "openid-connect"; } AP_DECLARE(const char *) ap_auth_name(request_rec *r) { return NULL; } AP_DECLARE(long) ap_get_client_block(request_rec *r, char *buffer, apr_size_t bufsiz) { return 0; } AP_DECLARE(char *) ap_getword(apr_pool_t *atrans, const char **line, char stop) { const char *pos = *line; int len; char *res; while ((*pos != stop) && *pos) { ++pos; } len = pos - *line; res = apr_pstrmemdup(atrans, *line, len); if (stop) { while (*pos == stop) { ++pos; } } *line = pos; return res; } static char *substring_conf(apr_pool_t *p, const char *start, int len, char quote) { char *result = apr_palloc(p, len + 1); char *resp = result; int i; for (i = 0; i < len; ++i) { if (start[i] == '\\' && (start[i + 1] == '\\' || (quote && start[i + 1] == quote))) *resp++ = start[++i]; else *resp++ = start[i]; } *resp++ = '\0'; #if RESOLVE_ENV_PER_TOKEN return (char *)ap_resolve_env(p, result); #else return result; #endif } AP_DECLARE(char *) ap_getword_conf(apr_pool_t *p, const char **line) { const char *str = *line, *strend; char *res; char quote; while (apr_isspace(*str)) ++str; if (!*str) { *line = str; return ""; } if ((quote = *str) == '"' || quote == '\'') { strend = str + 1; while (*strend && *strend != quote) { if (*strend == '\\' && strend[1] && (strend[1] == quote || strend[1] == '\\')) { strend += 2; } else { ++strend; } } res = substring_conf(p, str + 1, strend - str - 1, quote); if (*strend == quote) ++strend; } else { strend = str; while (*strend && !apr_isspace(*strend)) ++strend; res = substring_conf(p, str, strend - str, 0); } while (apr_isspace(*strend)) ++strend; *line = strend; return res; } AP_DECLARE(char *) ap_getword_nulls(apr_pool_t *p, const char **line, char stop) { return ""; } AP_DECLARE(char *) ap_getword_white(apr_pool_t *p, const char **line) { return 0; } AP_DECLARE(int) ap_hook_check_user_id(request_rec *r) { return 0; } AP_DECLARE(int) ap_hook_auth_checker(request_rec *r) { return 0; } AP_DECLARE(void) ap_hook_fixups(int (*handler)(request_rec *r), const char *const *aszPre, const char *const *aszSucc, int nOrder) { // comment explaining why the method is empty } AP_DECLARE(void) ap_hook_insert_filter(void (*insert_filter)(request_rec *r), const char *const *aszPre, const char *const *aszSucc, int nOrder) { // comment explaining why the method is empty } AP_DECLARE(void) ap_hook_post_config(int (*post_config)(apr_pool_t *pool, apr_pool_t *p1, apr_pool_t *p2, server_rec *s), const char *const *aszPre, const char *const *aszSucc, int nOrder) { // comment explaining why the method is empty } AP_DECLARE(void) ap_hook_child_init(void (*child_init)(apr_pool_t *p, server_rec *s), const char *const *aszPre, const char *const *aszSucc, int nOrder) { // comment explaining why the method is empty } AP_DECLARE(void) ap_hook_handler(int (*handler)(request_rec *r), const char *const *aszPre, const char *const *aszSucc, int nOrder) { // comment explaining why the method is empty } AP_DECLARE(int) ap_is_initial_req(request_rec *r) { return 0; } #if HAVE_APACHE_24 AP_DECLARE(ap_expr_info_t *) ap_expr_parse_cmd_mi(const cmd_parms *cmd, const char *expr, unsigned int flags, const char **err, ap_expr_lookup_fn_t *lookup_fn, int module_index) { return NULL; } AP_DECLARE(const char *) ap_expr_str_exec(request_rec *r, const ap_expr_info_t *expr, const char **err) { if (err) *err = NULL; return expr->filename; } AP_DECLARE(char *) ap_get_exec_line(apr_pool_t *p, const char *cmd, const char *const *argv) { return NULL; } AP_DECLARE(void) ap_log_error_(const char *file, int line, int module_index, int level, apr_status_t status, const server_rec *s, const char *fmt, ...) { #else AP_DECLARE(void) ap_log_error(const char *file, int line, int level, apr_status_t status, const server_rec *s, const char *fmt, ...) { #endif if (level < APLOG_DEBUG) { fprintf(stderr, "%s:%d [%d] [%d] ", file, line, level, status); va_list ap; va_start(ap, fmt); vfprintf(stderr, fmt, ap); va_end(ap); fprintf(stderr, "\n"); } } #if HAVE_APACHE_24 AP_DECLARE(void) ap_log_rerror_(const char *file, int line, int module_index, int level, apr_status_t status, const request_rec *r, const char *fmt, ...) { #else AP_DECLARE(void) ap_log_rerror(const char *file, int line, int level, apr_status_t status, const request_rec *r, const char *fmt, ...) { #endif if (level < APLOG_DEBUG) { fprintf(stderr, "%s:%d [%d] [%d] ", file, line, level, status); va_list ap; va_start(ap, fmt); vfprintf(stderr, fmt, ap); va_end(ap); fprintf(stderr, "\n"); } } AP_DECLARE(void) ap_note_auth_failure(request_rec *r) { // comment explaining why the method is empty } AP_DECLARE(apr_status_t) ap_pass_brigade(ap_filter_t *filter, apr_bucket_brigade *bucket) { return APR_SUCCESS; } AP_DECLARE(const apr_array_header_t *) ap_requires(request_rec *r) { return NULL; } const char *ap_run_http_scheme(const request_rec *r) { char *rv; apr_pool_userdata_get((void **)&rv, "scheme", r->pool); return (const char *)rv; } AP_DECLARE(void) ap_set_content_type(request_rec *r, const char *ct) { // comment explaining why the method is empty } AP_DECLARE_NONSTD(const char *) ap_set_flag_slot(cmd_parms *cmd, void *struct_ptr, int arg) { return ""; } AP_DECLARE_NONSTD(const char *) ap_set_string_slot(cmd_parms *cmd, void *struct_ptr, const char *arg) { return ""; } AP_DECLARE_NONSTD(const char *) ap_set_int_slot(cmd_parms *cmd, void *struct_ptr, const char *arg) { return ""; } AP_DECLARE(int) ap_setup_client_block(request_rec *r, int read_policy) { return 0; } AP_DECLARE(int) ap_should_client_block(request_rec *r) { return 0; } AP_DECLARE(int) ap_unescape_url(char *url) { return 0; } AP_DECLARE(apr_status_t) unixd_set_global_mutex_perms(apr_global_mutex_t *gmutex) { return APR_SUCCESS; } AP_DECLARE(const char *) ap_get_server_name(request_rec *r) { return "www.example.com"; } AP_DECLARE(char *) ap_server_root_relative(apr_pool_t *p, const char *file) { return ""; } AP_DECLARE(ap_filter_t *) ap_add_input_filter(const char *name, void *ctx, request_rec *r, conn_rec *c) { return NULL; } AP_DECLARE(apr_status_t) ap_get_brigade(ap_filter_t *filter, apr_bucket_brigade *bucket, ap_input_mode_t mode, apr_read_type_e block, apr_off_t readbytes) { return APR_SUCCESS; } AP_DECLARE(ap_filter_rec_t *) ap_register_input_filter(const char *name, ap_in_filter_func filter_func, ap_init_filter_func filter_init, ap_filter_type ftype) { return NULL; } AP_DECLARE(char *) ap_make_dirstr_parent(apr_pool_t *p, const char *s) { return NULL; } AP_DECLARE(apr_status_t) ap_mpm_query(int query_code, int *result) { *result = 1; return APR_SUCCESS; } #if AP_MODULE_MAGIC_AT_LEAST(20080920, 2) AP_DECLARE(apr_status_t) ap_timeout_parameter_parse(const char *timeout_parameter, apr_interval_time_t *timeout, const char *default_time_unit) { *timeout = 0; return APR_SUCCESS; } #endif #if HAVE_APACHE_24 AP_DECLARE(int) ap_expr_exec(request_rec *r, const ap_expr_info_t *expr, const char **err) { return 0; } #endif mod_auth_openidc-2.4.16.10/test/test-cmd.c000066400000000000000000000367401476721736500202230ustar00rootroot00000000000000/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ /*************************************************************************** * Copyright (C) 2017-2025 ZmartZone Holding BV * Copyright (C) 2013-2017 Ping Identity Corporation * All rights reserved. * * DISCLAIMER OF WARRANTIES: * * THE SOFTWARE PROVIDED HEREUNDER IS PROVIDED ON AN "AS IS" BASIS, WITHOUT * ANY WARRANTIES OR REPRESENTATIONS EXPRESS, IMPLIED OR STATUTORY; INCLUDING, * WITHOUT LIMITATION, WARRANTIES OF QUALITY, PERFORMANCE, NONINFRINGEMENT, * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. NOR ARE THERE ANY * WARRANTIES CREATED BY A COURSE OR DEALING, COURSE OF PERFORMANCE OR TRADE * USAGE. FURTHERMORE, THERE ARE NO WARRANTIES THAT THE SOFTWARE WILL MEET * YOUR NEEDS OR BE FREE FROM ERRORS, OR THAT THE OPERATION OF THE SOFTWARE * WILL BE UNINTERRUPTED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * @Author: Hans Zandbelt - hans.zandbelt@openidc.com * **************************************************************************/ #include "cfg/cfg.h" #include "cfg/cfg_int.h" #include "cfg/dir.h" #include "cfg/provider.h" #include "jose.h" #include "session.h" #include "util.h" #include #include int usage(int argc, char **argv, const char *msg) { fprintf(stderr, "Usage: %s %s\n", argv[0], msg ? msg : "[ sign | verify | decrypt | key2jwk | enckey | hash_base64url | timestamp | uuid ] "); return -1; } int file_read(apr_pool_t *pool, const char *path, char **rbuf) { apr_file_t *fd = NULL; char s_err[128]; int rc; apr_size_t bytes_read = 0; apr_finfo_t finfo; apr_size_t len; rc = apr_file_open(&fd, path, APR_FOPEN_READ | APR_FOPEN_BUFFERED, APR_OS_DEFAULT, pool); if (rc != APR_SUCCESS) { fprintf(stderr, "could not open file %s: %s", path, apr_strerror(rc, s_err, sizeof(s_err))); return -1; } apr_file_info_get(&finfo, APR_FINFO_NORM, fd); len = (apr_size_t)finfo.size; *rbuf = apr_pcalloc(pool, len + 1); rc = apr_file_read_full(fd, *rbuf, len, &bytes_read); if ((rc != APR_SUCCESS) || (bytes_read < 1)) { fprintf(stderr, "could not read file %s: %s", path, apr_strerror(rc, s_err, sizeof(s_err))); return -1; } (*rbuf)[bytes_read] = '\0'; bytes_read--; while ((bytes_read > 0) && ((*rbuf)[bytes_read] == '\n')) { (*rbuf)[bytes_read] = '\0'; bytes_read--; } apr_file_close(fd); return 0; } int sign(int argc, char **argv, apr_pool_t *pool) { if (argc <= 4) return usage(argc, argv, "sign "); char *s_jwt = NULL, *s_jwk = NULL; const char *cser = NULL; if (file_read(pool, argv[3], &s_jwt) != 0) return -1; if (file_read(pool, argv[4], &s_jwk) != 0) return -1; cjose_err cjose_err; cjose_header_t *hdr = cjose_header_new(&cjose_err); cjose_header_set(hdr, "alg", argv[2], &cjose_err); cjose_jwk_t *jwk = cjose_jwk_import(s_jwk, _oidc_strlen(s_jwk), &cjose_err); if (jwk == NULL) { fprintf(stderr, "could not import JWK: %s [file: %s, function: %s, line: %ld]\n", cjose_err.message, cjose_err.file, cjose_err.function, cjose_err.line); return -1; } cjose_jws_t *jws = cjose_jws_sign(jwk, hdr, (const uint8_t *)s_jwt, _oidc_strlen(s_jwt), &cjose_err); if (jws == NULL) { fprintf(stderr, "could not sign JWS: %s [file: %s, function: %s, line: %ld]\n", cjose_err.message, cjose_err.file, cjose_err.function, cjose_err.line); return -1; } if (cjose_jws_export(jws, &cser, &cjose_err) == FALSE) { fprintf(stderr, "could not serialize JWS: %s [file: %s, function: %s, line: %ld]\n", cjose_err.message, cjose_err.file, cjose_err.function, cjose_err.line); return -1; } fprintf(stdout, "%s\n", cser); cjose_jws_release(jws); cjose_jwk_release(jwk); return 0; } int verify(int argc, char **argv, apr_pool_t *pool) { if (argc <= 3) return usage(argc, argv, "verify "); char *s_jwt = NULL, *s_jwk = NULL; if (file_read(pool, argv[2], &s_jwt) != 0) return -1; if (file_read(pool, argv[3], &s_jwk) != 0) return -1; cjose_err cjose_err; cjose_jws_t *jws = cjose_jws_import(s_jwt, _oidc_strlen(s_jwt), &cjose_err); if (jws == NULL) { fprintf(stderr, "could not import JWS: %s [file: %s, function: %s, line: %ld]\n", cjose_err.message, cjose_err.file, cjose_err.function, cjose_err.line); return -1; } json_error_t json_error; json_t *json = json_loads(s_jwk, 0, &json_error); oidc_jose_error_t oidc_err; oidc_jwk_t *jwk = oidc_jwk_parse(pool, json, &oidc_err); if (jwk == NULL) { fprintf(stderr, "could not import JWK: %s [file: %s, function: %s, line: %d]\n", oidc_err.text, oidc_err.source, oidc_err.function, oidc_err.line); return -1; } if (cjose_jws_verify(jws, jwk->cjose_jwk, &cjose_err) == FALSE) { fprintf(stderr, "could not verify JWS: %s [file: %s, function: %s, line: %ld]\n", cjose_err.message, cjose_err.file, cjose_err.function, cjose_err.line); return -1; } uint8_t *plaintext = NULL; size_t plaintext_len = 0; if (cjose_jws_get_plaintext(jws, &plaintext, &plaintext_len, &cjose_err) == FALSE) { fprintf(stderr, "could not get plaintext: %s [file: %s, function: %s, line: %ld]\n", cjose_err.message, cjose_err.file, cjose_err.function, cjose_err.line); return -1; } fprintf(stdout, "%s", plaintext); cjose_jws_release(jws); oidc_jwk_destroy(jwk); json_decref(json); return 0; } int decrypt(int argc, char **argv, apr_pool_t *pool) { if (argc <= 3) return usage(argc, argv, "decrypt "); char *s_jwt = NULL, *s_jwk = NULL; if (file_read(pool, argv[2], &s_jwt) != 0) return -1; if (file_read(pool, argv[3], &s_jwk) != 0) return -1; apr_hash_t *keys = apr_hash_make(pool); json_error_t json_error; json_t *json = json_loads(s_jwk, 0, &json_error); oidc_jose_error_t oidc_err; oidc_jwk_t *jwk = oidc_jwk_parse(pool, json, &oidc_err); if (jwk == NULL) { fprintf(stderr, "could not import JWK: %s [file: %s, function: %s, line: %d]\n", oidc_err.text, oidc_err.source, oidc_err.function, oidc_err.line); return -1; } apr_hash_set(keys, jwk->kid ? jwk->kid : "dummy", APR_HASH_KEY_STRING, jwk); char *plaintext = NULL; if (oidc_jwe_decrypt(pool, s_jwt, keys, &plaintext, NULL, &oidc_err, TRUE) == FALSE) { fprintf(stderr, "oidc_jwe_decrypt failed: %s [file: %s, function: %s, line: %d]\n", oidc_err.text, oidc_err.source, oidc_err.function, oidc_err.line); return -1; } fprintf(stdout, "%s", plaintext); oidc_jwk_destroy(jwk); json_decref(json); return 0; } int key2jwk(int argc, char **argv, apr_pool_t *pool) { if (argc <= 2) return usage(argc, argv, "key2jwk "); oidc_jwk_t *jwk = NULL; oidc_jose_error_t err; int is_private_key = (argc > 3); if (is_private_key) { if (oidc_jwk_parse_pem_private_key(pool, NULL, argv[2], &jwk, &err) == FALSE) { fprintf(stderr, "oidc_jwk_parse_pem_private_key failed: %s", oidc_jose_e2s(pool, err)); return -1; } } else { if (oidc_jwk_parse_pem_public_key(pool, NULL, argv[2], &jwk, &err) == FALSE) { fprintf(stderr, "oidc_jwk_parse_pem_public_key failed: %s", oidc_jose_e2s(pool, err)); return -1; } } char *s_json = NULL; if (oidc_jwk_to_json(pool, jwk, &s_json, &err) == FALSE) { fprintf(stderr, "oidc_jwk_to_json failed: %s", oidc_jose_e2s(pool, err)); return -1; } fprintf(stdout, "%s", s_json); oidc_jwk_destroy(jwk); return 0; } static request_rec *request_setup(apr_pool_t *pool) { const unsigned int kIdx = 0; const unsigned int kEls = kIdx + 1; request_rec *request = (request_rec *)apr_pcalloc(pool, sizeof(request_rec)); request->pool = pool; request->headers_in = apr_table_make(request->pool, 0); request->headers_out = apr_table_make(request->pool, 0); request->err_headers_out = apr_table_make(request->pool, 0); apr_table_set(request->headers_in, "Host", "www.example.com"); apr_table_set(request->headers_in, "OIDC_foo", "some-value"); apr_table_set(request->headers_in, "Cookie", "foo=bar; " "mod_auth_openidc_session" "=0123456789abcdef; baz=zot"); request->server = apr_pcalloc(request->pool, sizeof(struct server_rec)); request->server->process = apr_pcalloc(request->pool, sizeof(struct process_rec)); request->server->process->pool = request->pool; request->server->process->pconf = request->pool; request->connection = apr_pcalloc(request->pool, sizeof(struct conn_rec)); request->connection->local_addr = apr_pcalloc(request->pool, sizeof(apr_sockaddr_t)); apr_pool_userdata_set("https", "scheme", NULL, request->pool); request->server->server_hostname = "www.example.com"; request->connection->local_addr->port = 443; request->unparsed_uri = "/bla?foo=bar¶m1=value1"; request->args = "foo=bar¶m1=value1"; apr_uri_parse(request->pool, "https://www.example.com/bla?foo=bar¶m1=value1", &request->parsed_uri); auth_openidc_module.module_index = kIdx; oidc_cfg_t *cfg = oidc_cfg_server_create(request->pool, request->server); oidc_cfg_provider_issuer_set(pool, oidc_cfg_provider_get(cfg), "https://idp.example.com"); oidc_cfg_provider_authorization_endpoint_url_set(pool, oidc_cfg_provider_get(cfg), "https://idp.example.com/authorize"); oidc_cfg_provider_scope_set(pool, oidc_cfg_provider_get(cfg), "openid"); oidc_cfg_provider_client_id_set(pool, oidc_cfg_provider_get(cfg), "client_id"); cfg->redirect_uri = "https://www.example.com/protected/"; oidc_dir_cfg_t *d_cfg = oidc_cfg_dir_config_create(request->pool, NULL); request->server->module_config = apr_pcalloc(request->pool, sizeof(void) * kEls); request->per_dir_config = apr_pcalloc(request->pool, sizeof(void) * kEls); ap_set_module_config(request->server->module_config, &auth_openidc_module, cfg); ap_set_module_config(request->per_dir_config, &auth_openidc_module, d_cfg); cfg->cache.impl = &oidc_cache_shm; cfg->cache.cfg = NULL; cfg->cache.shm_size_max = 500; cfg->cache.shm_entry_size_max = 16384 + 255 + 17; if (cfg->cache.impl->post_config(request->server) != OK) { printf("cfg->cache->post_config failed!\n"); exit(-1); } return request; } int enckey(int argc, char **argv, apr_pool_t *pool) { if (argc <= 2) return usage(argc, argv, "enckey [hash] [key-length]"); request_rec *r = request_setup(pool); oidc_jwk_t *jwk = NULL; if (oidc_util_create_symmetric_key(r, argv[2], argc > 4 ? _oidc_str_to_int(argv[4], 0) : 0, argc > 3 ? argv[3] : NULL, FALSE, &jwk) == FALSE) { fprintf(stderr, "oidc_util_create_symmetric_key failed"); return -1; } oidc_jose_error_t err; char *s_json = NULL; if (oidc_jwk_to_json(pool, jwk, &s_json, &err) == FALSE) { fprintf(stderr, "oidc_jwk_to_json failed"); return -1; } cjose_err cjose_err; int src_len = cjose_jwk_get_keysize(jwk->cjose_jwk, &cjose_err) / 8; int enc_len = apr_base64_encode_len(src_len); char *b64 = apr_palloc(r->pool, enc_len); apr_base64_encode(b64, (const char *)cjose_jwk_get_keydata(jwk->cjose_jwk, &cjose_err), src_len); fprintf(stdout, "\nJWK:\n%s\n\nbase64:\n%s\n\n", s_json, b64); return 0; } int hash_base64url(int argc, char **argv, apr_pool_t *pool) { if (argc <= 2) return usage(argc, argv, "hash_base64url [algo] [base64url-decode-first]"); char *algo = argc > 3 ? argv[3] : "sha256"; int base64url_decode_first = argc > 4 ? (_oidc_strcmp(argv[4], "yes") == 0) : 0; char *output = NULL; request_rec *r = request_setup(pool); if (base64url_decode_first) { uint8_t *bytes = NULL; size_t outlen = 0; cjose_err cjose_err; if (cjose_base64url_decode(argv[2], _oidc_strlen(argv[2]), &bytes, &outlen, &cjose_err) == FALSE) { fprintf(stderr, "cjose_base64_decode failed: %s", cjose_err.message); return -1; } oidc_jose_error_t err; if (oidc_jose_hash_and_base64url_encode(r->pool, algo, (const char *)bytes, outlen, &output, &err) == FALSE) { fprintf(stderr, "oidc_jose_hash_and_base64url_encode failed: %s", err.text); return -1; } } else { if (oidc_util_hash_string_and_base64url_encode(r, algo, argv[2], &output) == FALSE) { fprintf(stderr, "oidc_util_hash_string_and_base64url_encode failed"); return -1; } } fprintf(stdout, "%s\n", output); return 0; } int timestamp(int argc, char **argv, apr_pool_t *pool) { if (argc <= 2) return usage(argc, argv, "timestamp "); int delta = _oidc_str_to_int(argv[2], 0); apr_time_t t1 = apr_time_now() + apr_time_from_sec(delta); char *s = apr_psprintf(pool, "%" APR_TIME_T_FMT, t1); fprintf(stderr, "timestamp (1) = %s\n", s); apr_time_t t2 = _oidc_str_to_time(s, -1); fprintf(stderr, "timestamp (2) = %" APR_TIME_T_FMT "\n", t2); char buf[APR_RFC822_DATE_LEN + 1]; apr_rfc822_date(buf, t2); fprintf(stderr, "timestamp (3): %s (%" APR_TIME_T_FMT " secs from now)\n", buf, apr_time_sec(t2 - apr_time_now())); return 0; } int uuid(int argc, char **argv, apr_pool_t *pool) { const unsigned long e = 1000000; unsigned long n = 25000000; unsigned long i = 0; oidc_session_t z; if (argc > 2) { n = _oidc_str_to_int(argv[2], n); if (n > 25000000 * 10) n = 25000000; } request_rec *r = request_setup(pool); apr_hash_t *entries = apr_hash_make(pool); while (i < n) { z.uuid = NULL; oidc_session_id_new(r, &z); if (apr_hash_get(entries, (const void *)&z.uuid, APR_HASH_KEY_STRING) != NULL) { fprintf(stderr, "duplicate found: %s\n", z.uuid); exit(-1); } else { apr_hash_set(entries, (const void *)apr_pstrdup(pool, z.uuid), APR_HASH_KEY_STRING, (const void *)1); } i++; if (i % e == 0) fprintf(stderr, "\r %lu (%s)", i / e, z.uuid); } fprintf(stderr, "\n"); return 0; } int main(int argc, char **argv, char **env) { if (argc <= 1) return usage(argc, argv, NULL); if (apr_app_initialize(&argc, (const char *const **)argv, (const char *const **)env) != APR_SUCCESS) { printf("apr_app_initialize failed\n"); return -1; } oidc_pre_config_init(); apr_pool_t *pool = NULL; apr_pool_create(&pool, NULL); if (_oidc_strcmp(argv[1], "sign") == 0) return sign(argc, argv, pool); if (_oidc_strcmp(argv[1], "verify") == 0) return verify(argc, argv, pool); if (_oidc_strcmp(argv[1], "decrypt") == 0) return decrypt(argc, argv, pool); if (_oidc_strcmp(argv[1], "key2jwk") == 0) return key2jwk(argc, argv, pool); if (_oidc_strcmp(argv[1], "enckey") == 0) return enckey(argc, argv, pool); if (_oidc_strcmp(argv[1], "hash_base64url") == 0) return hash_base64url(argc, argv, pool); if (_oidc_strcmp(argv[1], "timestamp") == 0) return timestamp(argc, argv, pool); if (_oidc_strcmp(argv[1], "uuid") == 0) return uuid(argc, argv, pool); EVP_cleanup(); apr_pool_destroy(pool); apr_terminate(); return usage(argc, argv, NULL); } mod_auth_openidc-2.4.16.10/test/test.c000066400000000000000000002605451476721736500174640ustar00rootroot00000000000000/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ /*************************************************************************** * Copyright (C) 2017-2025 ZmartZone Holding BV * Copyright (C) 2013-2017 Ping Identity Corporation * All rights reserved. * * DISCLAIMER OF WARRANTIES: * * THE SOFTWARE PROVIDED HEREUNDER IS PROVIDED ON AN "AS IS" BASIS, WITHOUT * ANY WARRANTIES OR REPRESENTATIONS EXPRESS, IMPLIED OR STATUTORY; INCLUDING, * WITHOUT LIMITATION, WARRANTIES OF QUALITY, PERFORMANCE, NONINFRINGEMENT, * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. NOR ARE THERE ANY * WARRANTIES CREATED BY A COURSE OR DEALING, COURSE OF PERFORMANCE OR TRADE * USAGE. FURTHERMORE, THERE ARE NO WARRANTIES THAT THE SOFTWARE WILL MEET * YOUR NEEDS OR BE FREE FROM ERRORS, OR THAT THE OPERATION OF THE SOFTWARE * WILL BE UNINTERRUPTED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * @Author: Hans Zandbelt - hans.zandbelt@openidc.com * **************************************************************************/ #include "handle/handle.h" #include "mod_auth_openidc.h" #include "proto/proto.h" #include "cfg/cfg_int.h" #include "cfg/dir.h" #include "util.h" #include #include static int test_nr_run = 0; static char TST_ERR_MSG[4096]; static int TST_RC; #define TST_FORMAT(fmt) " # %s: error in %s: result \"" fmt "\" != expected \"" fmt "\"" #define TST_ASSERT(message, expression) \ TST_RC = (expression); \ if (!TST_RC) { \ snprintf(TST_ERR_MSG, 4096, TST_FORMAT("%d"), __FUNCTION__, message, TST_RC, 1); \ return TST_ERR_MSG; \ } #define TST_ASSERT_ERR(message, expression, pool, err) \ TST_RC = (expression); \ if (!TST_RC) { \ snprintf(TST_ERR_MSG, 4096, TST_FORMAT("%d") " %s", __FUNCTION__, message, TST_RC, 1, \ oidc_jose_e2s(pool, err)); \ return TST_ERR_MSG; \ } #define TST_ASSERT_CJOSE_ERR(message, expression, pool, cjose_err) \ TST_RC = (expression); \ if (!TST_RC) { \ snprintf(TST_ERR_MSG, 4096, TST_FORMAT("%d") " %s", __FUNCTION__, message, TST_RC, 1, \ oidc_cjose_e2s(pool, cjose_err)); \ return TST_ERR_MSG; \ } #define TST_ASSERT_STR(message, result, expected) \ TST_RC = \ (result && expected) ? (_oidc_strcmp(result, expected) != 0) : ((result != NULL) || (expected != NULL)); \ if (TST_RC) { \ snprintf(TST_ERR_MSG, 4096, TST_FORMAT("%s"), __FUNCTION__, message, result ? result : "(null)", \ expected ? expected : "(null)"); \ return TST_ERR_MSG; \ } #define TST_ASSERT_STRN(message, result, expected, len) \ TST_RC = (result && expected) ? (_oidc_strncmp(result, expected, len) != 0) \ : ((result != NULL) || (expected != NULL)); \ if (TST_RC) { \ snprintf(TST_ERR_MSG, 4096, TST_FORMAT("%s"), __FUNCTION__, message, result ? result : "(null)", \ expected ? expected : "(null)"); \ return TST_ERR_MSG; \ } #define TST_ASSERT_LONG(message, result, expected) \ if (result != expected) { \ snprintf(TST_ERR_MSG, 4096, TST_FORMAT("%ld"), __FUNCTION__, message, result, expected); \ return TST_ERR_MSG; \ } #define TST_ASSERT_BYTE(message, result, expected) \ if (result != expected) { \ snprintf(TST_ERR_MSG, 4096, TST_FORMAT("%s"), __FUNCTION__, message, result ? "TRUE" : "FALSE", \ expected ? "TRUE" : "FALSE"); \ return TST_ERR_MSG; \ } #define TST_RUN(test, pool) \ message = test(pool); \ test_nr_run++; \ if (message) \ return message; static char *_jwk_parse(apr_pool_t *pool, const char *s, oidc_jwk_t **jwk, oidc_jose_error_t *err) { json_error_t json_err; json_t *json = json_loads(s, 0, &json_err); oidc_jwk_t *k = oidc_jwk_parse(pool, json, err); TST_ASSERT_ERR("oidc_jwk_parse", k != NULL, pool, (*err)); *jwk = k; json_decref(json); return 0; } static char *test_private_key_parse(apr_pool_t *pool) { oidc_jose_error_t err = {{'\0'}, 0, {'\0'}, {'\0'}}; BIO *input = NULL; oidc_jwk_t *jwk = NULL; int isPrivateKey = 1; int result; char *json = NULL; const char rsaPrivateKeyFile[512]; const char ecPrivateKeyFile[512]; char *dir = getenv("srcdir") ? getenv("srcdir") : "."; snprintf((char *)rsaPrivateKeyFile, 512, "%s/%s", dir, "/test/private.pem"); snprintf((char *)ecPrivateKeyFile, 512, "%s/%s", dir, "/test/ecpriv.key"); input = BIO_new(BIO_s_file()); TST_ASSERT_ERR("test_private_key_parse_BIO_new_RSA_private_key", input != NULL, pool, err); TST_ASSERT_ERR("test_private_key_parse_BIOread_filename_RSA_private_key", result = BIO_read_filename(input, rsaPrivateKeyFile), pool, err); TST_ASSERT_ERR("oidc_jwk_pem_bio_to_jwk", oidc_jwk_pem_bio_to_jwk(pool, input, NULL, &jwk, isPrivateKey, &err), pool, err); BIO_free(input); TST_ASSERT_ERR("oidc_jwk_to_json with RSA private key", oidc_jwk_to_json(pool, jwk, &json, &err), pool, err); TST_ASSERT_STR( "oidc_jwk_to_json with RSA private key output test", json, "{\"kty\":\"RSA\",\"kid\":\"IbLjLR7-C1q0-ypkueZxGIJwBQNaLg46DZMpnPW1kps\",\"e\":\"AQAB\",\"n\":" "\"iGeTXbfV5bMppx7o7qMLCuVIKqbBa_qOzBiNNpe0K8rjg7-1z9GCuSlqbZtM0_5BQ6bGonnSPD--" "PowhFdivS4WNA33O0Kl1tQ0wdH3TOnwueIO9ahfW4q0BGFvMObneK-tjwiNMj1l-cZt8pvuS-3LtTWIzC-" "hTZM4caUmy5olm5PVdmru6C6V5rxkbYBPITFSzl5mpuo_C6RV_MYRwAh60ghs2OEvIWDrJkZnYaF7sjHC9j-" "4kfcM5oY7Zhg8KuHyloudYNzlqjVAPd0MbkLkh1pa8fmHsnN6cgfXYtFK7Z8WjYDUAhTH1JjZCVSFN55A-51dgD4cQNzieLEEkJw\"," "\"d\":\"Xc9d-kZERQVC0Dzh1b0sCwJE75Bf1fMr4hHAjJsovjV641ElqRdd4Borp9X2sJVcLTq1wWgmvmjYXgvhdTTg2f-" "vS4dqhPcGjM3VVUhzzPU6wIdZ7W0XzC1PY4E-ozTBJ1Nr-EhujuftnhRhVjYOkAAqU94FXVsaf2mBAKg-" "8WzrWx2MeWjfLcE79DmSL9Iw2areKVRGlKddIIPnHb-Mw9HB7ZCyVTC1v5sqhQPy6qPo8XHdQju_EYRlIOMksU8kcb20R_ezib_" "rHuVwJVlTNk6MvFUIj4ayXdX13Qy4kTBRiQM7pumPaypEE4CrAfTWP0AYnEwz_FGluOpMZNzoAQ\"}"); oidc_jwk_destroy(jwk); input = BIO_new(BIO_s_file()); TST_ASSERT_ERR("test_private_key_parse_BIO_new_EC_private_key", input != NULL, pool, err); TST_ASSERT_ERR("test_private_key_parse_BIOread_filename_EC_private_key", result = BIO_read_filename(input, ecPrivateKeyFile), pool, err); TST_ASSERT_ERR("oidc_jwk_pem_bio_to_jwk", oidc_jwk_pem_bio_to_jwk(pool, input, NULL, &jwk, isPrivateKey, &err), pool, err); BIO_free(input); TST_ASSERT_ERR("oidc_jwk_to_json with EC private key", oidc_jwk_to_json(pool, jwk, &json, &err), pool, err); TST_ASSERT_STR( "oidc_jwk_to_json with EC private key output test", json, "{\"kty\":\"EC\",\"kid\":\"-THDTumMGazABrYTb8xJoYOK2OPiWmho3D-nPC1dSYg\",\"crv\":\"P-521\",\"x\":" "\"AR6Eh9VhdLEA-rm5WR0_T0LjKysJuBkSoXaR8GjphHvoOTrljcACRsVlTES9FMkbxbNEs4JdxPgPJl9G-e9WEJTe\",\"y\":" "\"AammgflZaJuSdycK_ccUXkSXjNQd8NsqJuv9LFpk5Ys1OAiirWm6uktXG8ALNSxSffcurBq8zqZyZ141dV6qSzKQ\",\"d\":" "\"AKFwyWAZ2FiTTEofXXOC6I2GBPQeEyCnsVzo075hCOcebYgLpzSj8xWfkTqxsUq8FF5cxlKS3jym3qgsuV0Eb0wd\"}"); oidc_jwk_destroy(jwk); return 0; } static char *test_public_key_parse(apr_pool_t *pool) { oidc_jose_error_t err = {{'\0'}, 0, {'\0'}, {'\0'}}; oidc_jwk_t *jwk, *jwkCert = NULL; BIO *input, *inputCert = NULL; char *json = NULL; int isPrivateKey = 0; int result; const char publicKeyFile[512]; const char certificateFile[512]; const char ecCertificateFile[512]; char *dir = getenv("srcdir") ? getenv("srcdir") : "."; snprintf((char *)publicKeyFile, 512, "%s/%s", dir, "/test/public.pem"); snprintf((char *)certificateFile, 512, "%s/%s", dir, "/test/certificate.pem"); snprintf((char *)ecCertificateFile, 512, "%s/%s", dir, "/test/eccert.pem"); input = BIO_new(BIO_s_file()); TST_ASSERT_ERR("test_public_key_parse_BIO_new_public_key", input != NULL, pool, err); TST_ASSERT_ERR("test_public_key_parse_BIOread_filename_public_key", result = BIO_read_filename(input, publicKeyFile), pool, err); TST_ASSERT_ERR("oidc_jwk_pem_bio_to_jwk", oidc_jwk_pem_bio_to_jwk(pool, input, NULL, &jwk, isPrivateKey, &err), pool, err); BIO_free(input); inputCert = BIO_new(BIO_s_file()); TST_ASSERT_ERR("test_public_key_parse_BIO_new_certificate", inputCert != NULL, pool, err); TST_ASSERT_ERR("test_public_key_parse_BIOread_filename_certificate", BIO_read_filename(inputCert, certificateFile), pool, err); TST_ASSERT_ERR("oidc_jwk_pem_bio_to_jwk", oidc_jwk_pem_bio_to_jwk(pool, inputCert, NULL, &jwkCert, isPrivateKey, &err), pool, err); BIO_free(inputCert); TST_ASSERT_ERR("oidc_jwk_to_json with public key", oidc_jwk_to_json(pool, jwk, &json, &err), pool, err); TST_ASSERT_STR( "oidc_jwk_to_json with public key output test", json, "{\"kty\":\"RSA\",\"kid\":\"IbLjLR7-C1q0-ypkueZxGIJwBQNaLg46DZMpnPW1kps\",\"e\":\"AQAB\",\"n\":" "\"iGeTXbfV5bMppx7o7qMLCuVIKqbBa_qOzBiNNpe0K8rjg7-1z9GCuSlqbZtM0_5BQ6bGonnSPD--" "PowhFdivS4WNA33O0Kl1tQ0wdH3TOnwueIO9ahfW4q0BGFvMObneK-tjwiNMj1l-cZt8pvuS-3LtTWIzC-" "hTZM4caUmy5olm5PVdmru6C6V5rxkbYBPITFSzl5mpuo_C6RV_MYRwAh60ghs2OEvIWDrJkZnYaF7sjHC9j-" "4kfcM5oY7Zhg8KuHyloudYNzlqjVAPd0MbkLkh1pa8fmHsnN6cgfXYtFK7Z8WjYDUAhTH1JjZCVSFN55A-51dgD4cQNzieLEEkJw\"}"); oidc_jwk_destroy(jwk); TST_ASSERT_ERR("oidc_jwk_to_json with certificate", oidc_jwk_to_json(pool, jwkCert, &json, &err), pool, err); TST_ASSERT_STR("oidc_jwk_to_json with certificate output test", json, "{\"kty\":\"RSA\",\"kid\":\"IbLjLR7-C1q0-ypkueZxGIJwBQNaLg46DZMpnPW1kps\",\"e\":\"AQAB\",\"n\":" "\"iGeTXbfV5bMppx7o7qMLCuVIKqbBa_qOzBiNNpe0K8rjg7-1z9GCuSlqbZtM0_5BQ6bGonnSPD--" "PowhFdivS4WNA33O0Kl1tQ0wdH3TOnwueIO9ahfW4q0BGFvMObneK-tjwiNMj1l-cZt8pvuS-3LtTWIzC-" "hTZM4caUmy5olm5PVdmru6C6V5rxkbYBPITFSzl5mpuo_C6RV_MYRwAh60ghs2OEvIWDrJkZnYaF7sjHC9j-" "4kfcM5oY7Zhg8KuHyloudYNzlqjVAPd0MbkLkh1pa8fmHsnN6cgfXYtFK7Z8WjYDUAhTH1JjZCVSFN55A-" "51dgD4cQNzieLEEkJw\",\"x5c\":[\"MIICnTCCAYUCBgFuk1+" "FLDANBgkqhkiG9w0BAQsFADASMRAwDgYDVQQDDAd2aW5jZW50MB4XDTE5MTEyMjEzNDcyMVoXDTI5MTEyMjEzNDkwMVowEj" "EQMA4GA1UEAwwHdmluY2VudDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAIhnk1231eWzKace6O6jCwrlSCqmw" "Wv6jswYjTaXtCvK44O/tc/Rgrkpam2bTNP+QUOmxqJ50jw/" "vj6MIRXYr0uFjQN9ztCpdbUNMHR90zp8LniDvWoX1uKtARhbzDm53ivrY8IjTI9ZfnGbfKb7kvty7U1iMwvoU2TOHGlJsua" "JZuT1XZq7ugulea8ZG2ATyExUs5eZqbqPwukVfzGEcAIetIIbNjhLyFg6yZGZ2Ghe7IxwvY/" "uJH3DOaGO2YYPCrh8paLnWDc5ao1QD3dDG5C5IdaWvH5h7JzenIH12LRSu2fFo2A1AIUx9SY2QlUhTeeQPudXYA+" "HEDc4nixBJCcCAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAfAo40il4qw7DfOkke0p1ZFAgLQQS3J5hYNDSRvVv+vxkk9o/" "N++zTMoHbfcDcU5BdVH6Qsr/12PXPX7Ur5WYDq+bWGAK3MAaGtZlmycFeVhoVRfab4TUWUy43H3VyFUNqjGRAVJ/" "VD1RW3fJ18KrQTN2fcKSd88Jqt5TvjROKghq95+8BQtlhrR/" "sQVrjgYwc+eU9ljWI56MQXbpHstl9IewMXnusSPxKRTbutjaxzKaoXRTUncPL6ga0SSxOTdKksM4ZYpPnq0B93silb+" "0qs8aJraGzjAmLE30opfufP+roth19VJxAfYsW5mgAmXP9kEAF+iWB8FB4/" "Q4noNG8Q==\"],\"x5t#S256\":\"hMVJ55Mqi4uAQIztPKUmL2MSfy6iN1Lr3J1CNGAIBms\",\"x5t\":\"0oN6Bx-" "eh6VAmNw1I7o3Dd9JPwE\"}"); oidc_jwk_destroy(jwkCert); inputCert = BIO_new(BIO_s_file()); TST_ASSERT_ERR("test_public_key_parse_BIO_new_EC_certificate", inputCert != NULL, pool, err); TST_ASSERT_ERR("test_public_key_parse_BIOread_filename_EC_certificate", BIO_read_filename(inputCert, ecCertificateFile), pool, err); TST_ASSERT_ERR("oidc_jwk_pem_bio_to_jwk", oidc_jwk_pem_bio_to_jwk(pool, inputCert, NULL, &jwkCert, isPrivateKey, &err), pool, err); BIO_free(inputCert); TST_ASSERT_ERR("oidc_jwk_to_json with EC certificate", oidc_jwk_to_json(pool, jwkCert, &json, &err), pool, err); TST_ASSERT_STR( "oidc_jwk_to_json with EC certificate output test", json, "{\"kty\":\"EC\",\"kid\":\"-THDTumMGazABrYTb8xJoYOK2OPiWmho3D-nPC1dSYg\",\"crv\":\"P-521\",\"x\":" "\"AR6Eh9VhdLEA-rm5WR0_T0LjKysJuBkSoXaR8GjphHvoOTrljcACRsVlTES9FMkbxbNEs4JdxPgPJl9G-e9WEJTe\",\"y\":" "\"AammgflZaJuSdycK_ccUXkSXjNQd8NsqJuv9LFpk5Ys1OAiirWm6uktXG8ALNSxSffcurBq8zqZyZ141dV6qSzKQ\",\"x5c\":[" "\"MIICBDCCAWagAwIBAgIUdYpkXaCal7IwjHix3n1PP9/" "O6OcwCgYIKoZIzj0EAwIwFDESMBAGA1UEAwwJbG9jYWxob3N0MB4XDTIzMDMyMzIwNDU1MFoXDTMzMDMyMDIwNDU1MFowFDESMBAGA1UEA" "wwJbG9jYWxob3N0MIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQBHoSH1WF0sQD6ublZHT9PQuMrKwm4GRKhdpHwaOmEe+" "g5OuWNwAJGxWVMRL0UyRvFs0Szgl3E+A8mX0b571YQlN4BqaaB+Vlom5J3Jwr9xxReRJeM1B3w2yom6/" "0sWmTlizU4CKKtabq6S1cbwAs1LFJ99y6sGrzOpnJnXjV1XqpLMpCjUzBRMB0GA1UdDgQWBBTKfLLXyRVQpnXFf19Bs7eXRPlRmzAfBgNV" "HSMEGDAWgBTKfLLXyRVQpnXFf19Bs7eXRPlRmzAPBgNVHRMBAf8EBTADAQH/" "MAoGCCqGSM49BAMCA4GLADCBhwJBGkoifMDYwsSLSmnnVdFftqTwxrjdgrtPMRzetz/w/" "D9KkM4Mlufgv5jBXuWcEiP9ray2ZgAGhdkvoOfsc8g1l6ICQgEJ+" "9R5K2WKlDTEydmiHiSYQHSVyS61PFskm537AqrLVSRu80Sezu2W4m8IF2UbbRZiUPaHPIx9Xe3GdpqIEmPFfA==\"],\"x5t#S256\":" "\"yCl_u4GL5GrTkf8xvqdF2aixUIhjDdsMFhLUz7O6gVA\",\"x5t\":\"waxmjjAAhxGY5XvH6ufxVxwYGDw\"}"); oidc_jwk_destroy(jwkCert); return 0; } static char *test_jwt_parse(apr_pool_t *pool) { // from http://tools.ietf.org/html/draft-ietf-oauth-json-web-token-20 // 3.1. Example JWT char *s = apr_pstrdup( pool, "eyJ0eXAiOiJKV1QiLA0KICJhbGciOiJIUzI1NiJ9" ".eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ" ".dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk"); oidc_jose_error_t err; oidc_jwt_t *jwt = NULL; TST_ASSERT_ERR("oidc_jwt_parse", oidc_jwt_parse(pool, s, &jwt, NULL, FALSE, &err), pool, err); TST_ASSERT_STR("header.alg", jwt->header.alg, "HS256"); TST_ASSERT_STR("header.enc", jwt->header.enc, NULL); TST_ASSERT_STR("header.kid", jwt->header.kid, NULL); TST_ASSERT_STR("payload.iss", jwt->payload.iss, "joe"); TST_ASSERT_LONG("payload.exp", (long)jwt->payload.exp, 1300819380L); apr_hash_t *keys = apr_hash_make(pool); oidc_jwk_t *jwk; const char *k = "{\"kty\":\"oct\", " "\"k\":\"AyM1SysPpbyDfgZld3umj1qzKObwVMkoqQ-EstJQLr_T-1qS0gZH75aKtMN3Yj0iPS4hcgUuTwjAzZr1Z9CAow\"}"; jwk = NULL; TST_ASSERT_ERR("oidc_jwk_parse", _jwk_parse(pool, k, &jwk, &err) == 0, pool, err); apr_hash_set(keys, "dummy", APR_HASH_KEY_STRING, jwk); TST_ASSERT_ERR("oidc_jwt_verify", oidc_jwt_verify(pool, jwt, keys, &err), pool, err); oidc_jwt_destroy(jwt); oidc_jwk_destroy(jwk); s[5] = OIDC_CHAR_DOT; TST_ASSERT_ERR("corrupted header (1) oidc_jwt_parse", oidc_jwt_parse(pool, s, &jwt, NULL, FALSE, &err) == FALSE, pool, err); oidc_jwt_destroy(jwt); s[0] = '\0'; TST_ASSERT_ERR("corrupted header (2) oidc_jwt_parse", oidc_jwt_parse(pool, s, &jwt, NULL, FALSE, &err) == FALSE, pool, err); oidc_jwt_destroy(jwt); return 0; } #if (OIDC_JOSE_EC_SUPPORT) static char *test_jwt_verify_ec(apr_pool_t *pool) { // { // "sub": "joe", // "aud": "ac_oic_client", // "jti": "oDWivWPJB47zkjOm2cygDv", // "iss": "https://localhost:9031", // "iat": 1467997207, // "exp": 1467997507, // "nonce": "WLxmv5StYyUk9JlWI8SaXTLPkGZ0Vs8aSTdj_VQ6rao" // } char *s_jwt = apr_pstrdup(pool, "eyJhbGciOiJFUzI1NiIsImtpZCI6ImY2cXRqIn0." "eyJzdWIiOiJqb2UiLCJhdWQiOiJhY19vaWNfY2xpZW50IiwianRpIjoib0RXaXZXUEpCNDd6a2pPbTJjeWdEdiIs" "ImlzcyI6Imh0dHBzOlwvXC9sb2NhbGhvc3Q6OTAzMSIsImlhdCI6MTQ2Nzk5NzIwNywiZXhwIjoxNDY3OTk3NTA3" "LCJub25jZSI6IldMeG12NVN0WXlVazlKbFdJOFNhWFRMUGtHWjBWczhhU1Rkal9WUTZyYW8ifQ." "2kqX56QNow37gOlnfLn0SIzwie4mLLIUx_p9OSQa0hiUXKQWQLmMYBjIp5qGh2-R-KPHwNEBxqXwuPgXG4Y7Eg"); oidc_jwt_t *jwt = NULL; oidc_jose_error_t err; TST_ASSERT_ERR("oidc_jwt_parse (ec0)", oidc_jwt_parse(pool, s_jwt, &jwt, NULL, FALSE, &err), pool, err); char *s_key = "{" "\"kty\": \"EC\"," "\"kid\": \"f6qtj\"," "\"use\": \"sig\"," "\"x\": \"iARwFlN3B3xa8Zn_O-CVfqry68tXIhO9DckKo1yrNg0\"," "\"y\": \"583S_mPS7YVZtLCjx2O69G_JzQPnMxjieOli-9cc_6Q\"," "\"crv\": \"P-256\"" "}"; apr_hash_t *keys = apr_hash_make(pool); oidc_jwk_t *jwk = NULL; TST_ASSERT_ERR("oidc_jwk_parse", _jwk_parse(pool, s_key, &jwk, &err) == 0, pool, err); apr_hash_set(keys, "f6qtj", APR_HASH_KEY_STRING, jwk); TST_ASSERT_ERR("oidc_jwt_verify (ec0)", oidc_jwt_verify(pool, jwt, keys, &err), pool, err); oidc_jwt_destroy(jwt); s_jwt = apr_pstrdup(pool, "eyJhbGciOiJFUzI1NiIsImtpZCI6ImY2cXRqIn0." "eyJzdWIiOiJqb2UiLCJhdWQiOiJhY19vaWNfY2xpZW50IiwianRpIjoib0RXaXZXUEpCNDd6a2pPbTJjeWdEdiIs" "ImlzcyI6Imh0dHBzOlwvXC9sb2NhbGhvc3Q6OTAzMSIsImlhdCI6MTQ2Nzk5NzIwNywiZXhwIjoxNDY3OTk3NTA3" "LCJub25jZSI6IldMeG12NVN0WXlVazlKbFdJOFNhWFRMUGtHWjBWczhhU1Rkal9WUTZyYW8ifQ." "2kqX56QNow37gOlnfLn0SIzwie4mLLIUx_p9OSQa0hiUXKQWQLmMYBjIp5qGh2-R-KPHwNEBxqXwuPgXG4Y7EG"); jwt = NULL; TST_ASSERT_ERR("oidc_jwt_parse (ec1)", oidc_jwt_parse(pool, s_jwt, &jwt, NULL, FALSE, &err), pool, err); TST_ASSERT_ERR("oidc_jwt_verify (ec1)", oidc_jwt_verify(pool, jwt, keys, &err) == FALSE, pool, err); oidc_jwt_destroy(jwt); s_jwt = apr_pstrdup(pool, "eyJhbGciOiJFUzI1NiIsImtpZCI6ImY2cXRqIn0." "eyJzdWIiOiJqb2UiLCJHdWQiOiJhY19vaWNfY2xpZW50IiwianRpIjoib0RXaXZXUEpCNDd6a2pPbTJjeWdEdiIs" "ImlzcyI6Imh0dHBzOlwvXC9sb2NhbGhvc3Q6OTAzMSIsImlhdCI6MTQ2Nzk5NzIwNywiZXhwIjoxNDY3OTk3NTA3" "LCJub25jZSI6IldMeG12NVN0WXlVazlKbFdJOFNhWFRMUGtHWjBWczhhU1Rkal9WUTZyYW8ifQ." "2kqX56QNow37gOlnfLn0SIzwie4mLLIUx_p9OSQa0hiUXKQWQLmMYBjIp5qGh2-R-KPHwNEBxqXwuPgXG4Y7Eg"); jwt = NULL; TST_ASSERT_ERR("oidc_jwt_parse (ec2)", oidc_jwt_parse(pool, s_jwt, &jwt, NULL, FALSE, &err), pool, err); TST_ASSERT_ERR("oidc_jwt_verify (ec2)", oidc_jwt_verify(pool, jwt, keys, &err) == FALSE, pool, err); oidc_jwt_destroy(jwt); oidc_jwk_destroy(jwk); return 0; } #endif static char *test_jwt_verify_rsa(apr_pool_t *pool) { /* * { * "typ": "JWT", * "alg": "RS256", * "x5t": "Z1NCjojeiHAib-Gm8vFE6ya6lPM" * } * { * "nonce": "avSk7S69G4kEE8Km4bPiOjrfChHt6nO4Z397Lp_bQnc,", * "iat": 1411580876, * "at_hash": "yTqsoONZbuWbN6TbgevuDQ", * "sub": "6343a29c-5399-44a7-9b35-4990f4377c96", * "amr": "password", * "auth_time": 1411577267, * "idp": "idsrv", * "name": "ksonaty", * "iss": "https://agsync.com", * "aud": "agsync_implicit", * "exp": 1411584475, * "nbf": 1411580875 * } */ char *s_jwt = "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6IloxTkNqb2plaUhBaWItR204dkZFNnlhNmxQTSJ9." "eyJub25jZSI6ImF2U2s3UzY5RzRrRUU4S200YlBpT2pyZkNoSHQ2bk80WjM5N0xwX2JRbmMsIiwiaWF0IjoxNDExNTgwODc2LCJhdF9oYX" "NoIjoieVRxc29PTlpidVdiTjZUYmdldnVEUSIsInN1YiI6IjYzNDNhMjljLTUzOTktNDRhNy05YjM1LTQ5OTBmNDM3N2M5NiIsImFtciI6" "InBhc3N3b3JkIiwiYXV0aF90aW1lIjoxNDExNTc3MjY3LCJpZHAiOiJpZHNydiIsIm5hbWUiOiJrc29uYXR5IiwiaXNzIjoiaHR0cHM6Ly" "9hZ3N5bmMuY29tIiwiYXVkIjoiYWdzeW5jX2ltcGxpY2l0IiwiZXhwIjoxNDExNTg0NDc1LCJuYmYiOjE0MTE1ODA4NzV9.lEG-" "DgHHa0JuOEuOTBvCqyexjRVcKXBnJJm289o2HyTgclpH80DsOMED9RlXCFfuDY7nw9i2cxUmIMAV42AdTxkMPomK3chytcajvpAZJirlk6" "53bo9GTDXJSKZr5fwyEu--qahsoT5t9qvoWyFdYkvmMHFw1-" "mAHDGgVe23voc9jPuFFIhRRqIn4e8ikzN4VQeEV1UXJD02kYYFn2TRWURgiFyVeTr2r0MTn-auCEsFS_AfR1Bl_" "kmpMfqwrsicf5MTBvfPJeuSMt3t3d3LOGBkg36_z21X-ZRN7wy1KTjagr7iQ_y5csIpmtqs_QM55TTB9dW1HIosJPhiuMEJEA"; oidc_jwt_t *jwt = NULL; oidc_jose_error_t err; TST_ASSERT_ERR("oidc_jwt_parse", oidc_jwt_parse(pool, s_jwt, &jwt, NULL, FALSE, &err), pool, err); char *s_key = "{" "\"kty\": \"RSA\"," "\"n\": " "\"3lDyn_ZvG32Pw5kYbRuVxHsPfe9Xt8s9vOXnt8z7_T-hZZvealNhCxz9VEwTJ7TsZ9CLi5c30FjoEJYFkKddLAdxKo0oOXWc_" "AWrQvPwht9a-o6dX2fL_9CmXW1hGHXMH0qiLMrFqMSzZeh-GUY6F1woE_eKsAo6LOhP8X77FlEQT2Eu71wu8KC4B3sH_9QTco50KNw14-" "bRY5j2V2TZelvsXJnvrN4lXtEVYWFkREKeXzMH8DhDyZzh0NcHa7dFBa7rDusyfIHjuP6uAju_" "Ao6hhdOGjlKePMVtfusWBAI7MWDChLTqiCTvlZnCpkpTTh5m-i7TbE1TwmdbLceq1w\"," "\"e\": \"AQAB\"" "}"; apr_hash_t *keys = apr_hash_make(pool); oidc_jwk_t *jwk = NULL; TST_ASSERT_ERR("oidc_jwk_parse", _jwk_parse(pool, s_key, &jwk, &err) == 0, pool, err); apr_hash_set(keys, "dummy", APR_HASH_KEY_STRING, jwk); TST_ASSERT_ERR("oidc_jwt_verify", oidc_jwt_verify(pool, jwt, keys, &err), pool, err); oidc_jwt_destroy(jwt); jwt = NULL; s_jwt = "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6IloxTkNqb2plaUhBaWItR204dkZFNnlhNmxQTSJ9." "eyJub25jZSI6ImF2U2s3UzY5RzRrRUU4S200YlBpT2pyZkNoSHQ2bk80WjM5N0xwX2JRbmMsIiwiaWF0IjoxNDExNTgwODc2LCJhdF9oYX" "NoIjoieVRxc29PTlpidVdiTjZUYmdldnVEUSIsInN1YiI6IjYzNDNhMjljLTUzOTktNDRhNy05YjM1LTQ5OTBmNDM3N2M5NiIsImFtciI6" "InBhc3N3b3JkIiwiYXV0aF90aW1lIjoxNDExNTc3MjY3LCJpZHAiOiJpZHNydiIsIm5hbWUiOiJrc29uYXR5IiwiaXNzIjoiaHR0cHM6Ly" "9hZ3N5bmMuY29tIiwiYXVkIjoiYWdzeW5jX2ltcGxpY2l0IiwiZXhwIjoxNDExNTg0NDc1LCJuYmYiOjE1MTE1ODA4NzV9.lEG-" "DgHHa0JuOEuOTBvCqyexjRVcKXBnJJm289o2HyTgclpH80DsOMED9RlXCFfuDY7nw9i2cxUmIMAV42AdTxkMPomK3chytcajvpAZJirlk6" "53bo9GTDXJSKZr5fwyEu--qahsoT5t9qvoWyFdYkvmMHFw1-" "mAHDGgVe23voc9jPuFFIhRRqIn4e8ikzN4VQeEV1UXJD02kYYFn2TRWURgiFyVeTr2r0MTn-auCEsFS_AfR1Bl_" "kmpMfqwrsicf5MTBvfPJeuSMt3t3d3LOGBkg36_z21X-ZRN7wy1KTjagr7iQ_y5csIpmtqs_QM55TTB9dW1HIosJPhiuMEJEA"; TST_ASSERT_ERR("oidc_jwt_parse (rsa1)", oidc_jwt_parse(pool, s_jwt, &jwt, NULL, FALSE, &err), pool, err); TST_ASSERT_ERR("oidc_jwt_verify (rsa1)", oidc_jwt_verify(pool, jwt, keys, &err) == FALSE, pool, err); oidc_jwt_destroy(jwt); jwt = NULL; s_jwt = "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6IloxTkNqb2plaUhBaWItR204dkZFNnlhNmxQTSJ9." "eyJub25jZSI6ImF2U2s3UzY5RzRrRUU4S200YlBpT2pyZkNoSHQ2bk80WjM5N0xwX2JRbmMsIiwiaWF0IjoxNDExNTgwODc2LCJhdF9oYX" "NoIjoieVRxc29PTlpidVdiTjZUYmdldnVEUSIsInN1YiI6IjYzNDNhMjljLTUzOTktNDRhNy05YjM1LTQ5OTBmNDM3N2M5NiIsImFtciI6" "InBhc3N3b3JkIiwiYXV0aF90aW1lIjoxNDExNTc3MjY3LCJpZHAiOiJpZHNydiIsIm5hbWUiOiJrc29uYXR5IiwiaXNzIjoiaHR0cHM6Ly" "9hZ3N5bmMuY29tIiwiYXVkIjoiYWdzeW5jX2ltcGxpY2l0IiwiZXhwIjoxNDExNTg0NDc1LCJuYmYiOjE0MTE1ODA4NzV9.lEG-" "DgHHa0JuOEuOTBvCqyexjRVcKXBnJJm289o2HyTgclpH80DsOMED9RlXCFfuDY7nw9i2cxUmIMAV42AdTxkMPomK3chytcajvpAZJirlk6" "53bo9GTDXJSKZr5fwyEu--qahsoT5t9qvoWyFdYkvmMHFw1-" "mAHDGgVe23voc9jPuFFIhRRqIn4e8ikzN4VQeEV1UXJD02kYYFn2TRWURgiFyVeTr2r0MTn-auCEsFS_AfR1Bl_" "kmpMfqwrsicf5MTBvfPJeuSMt3t3d3LOGBkg36_z21X-ZRN7wy1KTjagr7iQ_y5csIpmtqs_QM55TTB9dW1HIosJPhiuMEJEa"; TST_ASSERT_ERR("oidc_jwt_parse (rsa2)", oidc_jwt_parse(pool, s_jwt, &jwt, NULL, FALSE, &err), pool, err); TST_ASSERT_ERR("oidc_jwt_verify (rsa2)", oidc_jwt_verify(pool, jwt, keys, &err) == FALSE, pool, err); oidc_jwt_destroy(jwt); oidc_jwk_destroy(jwk); return 0; } static char *test_jwt_sign_verify(apr_pool_t *pool) { oidc_jwt_t *jwt = NULL; oidc_jwk_t *jwk = NULL; char *cser = NULL; oidc_jose_error_t err; char *s_key = "{" "\"kty\" : \"RSA\"," "\"n\": " "\"ym7jipmB37CgdonwGFVRuZmRfCl3lVh91fmm5CXHcNlUFZNR3D6Q9r63PpGRnfSsX3dOweh8BXd2AJ3mxvcE4z9xH--" "tA5EaOGI7IVF0Ip_i3flGg85xOADlb8rX3ez1NqkqMVJeeJypKhCCDNfvu_MXSdPLglU969YQF5xKAK8VFRfI6EfxxrZ_" "3Dvt2CKDV4LTPPJe9KI2_LuLQFBJ3MzlCTVxY6gyaljrWaDq7q5Lt3GB1KYS0Yd8COEQwsclOLm0Tddhg4cle-" "DfaTMi7xsTZsPKyac5x17Y4N4isHhZULuWHX7o1bs809xcj-_-YCRq6C61je_mzFhuF4pczw\"," "\"e\": \"AQAB\"," "\"d\": " "\"qvxW_" "e8DoCnUn8uLHUKTsS1hkXqFI4SHZYFl0jeG6m7ncwHolxvR3ljg9tyGHuFX55sizu7MMuHgrkyxbUWgv0ILD2qmvOiHOTDfuRjP-" "58JRW0UfqiVQTSgl3jCNRW9WdoxZU-ptD6_NGSVNLwAJsUB2r4mm4PctaMuHINKjp_TnuD-5vfi9Tj88hbqvX_0j8T62ZaLRdERb1KGDM_" "8bnqQpnLZ0MZQnpLQ8cKIcjj7p0II6pzvqgdO1RqfYx7qG0cbcIRh26rnB9X4rp5BrbvDzKe6NOqacZUcNUmbPzI01-" "hiT0HgJvV592CBOxt2T31ltQ4wCEdzhQeT3n9_wQ\"" "}"; apr_hash_t *keys = apr_hash_make(pool); TST_ASSERT_ERR("oidc_jwk_parse", _jwk_parse(pool, s_key, &jwk, &err) == 0, pool, err); apr_hash_set(keys, "dummy", APR_HASH_KEY_STRING, jwk); jwt = oidc_jwt_new(pool, TRUE, TRUE); json_object_set_new(jwt->payload.value.json, "iss", json_string("https://example.org")); json_object_set_new(jwt->payload.value.json, "sub", json_string("https://example.org")); json_object_set_new(jwt->payload.value.json, "aud", json_string("sample_client")); json_object_set_new(jwt->payload.value.json, "exp", json_integer(apr_time_sec(apr_time_now()) + 60)); json_object_set_new(jwt->payload.value.json, "iat", json_integer(apr_time_sec(apr_time_now()))); jwt->header.alg = apr_pstrdup(pool, CJOSE_HDR_ALG_RS256); TST_ASSERT_ERR("oidc_jwt_sign (rsa)", oidc_jwt_sign(pool, jwt, jwk, FALSE, &err), pool, err); cser = oidc_jose_jwt_serialize(pool, jwt, &err); TST_ASSERT_ERR("oidc_jose_jwt_serialize (rsa)", cser != NULL, pool, err); oidc_jwt_t *rsa_jwt = NULL; TST_ASSERT_ERR("oidc_jwt_parse (rsa)", oidc_jwt_parse(pool, cser, &rsa_jwt, NULL, FALSE, &err), pool, err); TST_ASSERT_ERR("oidc_jwt_verify (rsa)", oidc_jwt_verify(pool, rsa_jwt, keys, &err), pool, err); oidc_jwt_destroy(rsa_jwt); oidc_jwk_destroy(jwk); const char *secret = "my_secret4321"; jwk = oidc_jwk_create_symmetric_key(pool, NULL, (const unsigned char *)secret, _oidc_strlen(secret), FALSE, &err); TST_ASSERT_ERR("oidc_jwk_create_symmetric_key", jwk != NULL, pool, err); apr_hash_set(keys, "dummy", APR_HASH_KEY_STRING, jwk); jwt->header.alg = apr_pstrdup(pool, "HS256"); TST_ASSERT_ERR("oidc_jwt_sign (hmac)", oidc_jwt_sign(pool, jwt, jwk, FALSE, &err), pool, err); cser = oidc_jose_jwt_serialize(pool, jwt, &err); TST_ASSERT_ERR("oidc_jose_jwt_serialize (hmac)", cser != NULL, pool, err); oidc_jwt_t *hmac_jwt = NULL; TST_ASSERT_ERR("oidc_jwt_parse (rsa)", oidc_jwt_parse(pool, cser, &hmac_jwt, NULL, FALSE, &err), pool, err); TST_ASSERT_ERR("oidc_jwt_verify (rsa)", oidc_jwt_verify(pool, hmac_jwt, keys, &err), pool, err); oidc_jwt_destroy(hmac_jwt); oidc_jwk_destroy(jwk); oidc_jwt_destroy(jwt); return 0; } static char *test_plaintext_jwt_parse(apr_pool_t *pool) { // from http://tools.ietf.org/html/draft-ietf-oauth-json-web-token-20 // 6.1. Example Plaintext JWT char *s = apr_pstrdup( pool, "eyJhbGciOiJub25lIn0" ".eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ" "."); oidc_jose_error_t err; oidc_jwt_t *jwt = NULL; TST_ASSERT_ERR("oidc_jwt_parse", oidc_jwt_parse(pool, s, &jwt, NULL, FALSE, &err), pool, err); TST_ASSERT_STR("header.alg", jwt->header.alg, "none"); TST_ASSERT_STR("payload.iss", jwt->payload.iss, "joe"); TST_ASSERT_LONG("payload.exp", (long)jwt->payload.exp, 1300819380L); oidc_jwt_destroy(jwt); return 0; } static char *test_jwt_get_string(apr_pool_t *pool) { // oidc_jose_get_string const char *s = "eyJ0eXAiOiJKV1QiLA0KICJhbGciOiJIUzI1NiJ9" ".eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ" ".dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk"; oidc_jwt_t *jwt = NULL; oidc_jose_error_t err; TST_ASSERT_ERR("oidc_jwt_parse", oidc_jwt_parse(pool, s, &jwt, NULL, FALSE, &err), pool, err); char *dst = NULL; TST_ASSERT("oidc_jose_get_string (1a)", oidc_jose_get_string(pool, jwt->header.value.json, "typ", TRUE, &dst, &err)); TST_ASSERT_STR("oidc_jose_get_string (1b)", dst, "JWT"); dst = NULL; TST_ASSERT("oidc_jose_get_string (2a)", oidc_jose_get_string(pool, jwt->header.value.json, "alg", TRUE, &dst, &err)); TST_ASSERT_STR("oidc_jose_get_string (2b)", dst, "HS256"); dst = NULL; TST_ASSERT("oidc_jose_get_string (3a)", oidc_jose_get_string(pool, jwt->header.value.json, "dummy", FALSE, &dst, &err)); TST_ASSERT_STR("oidc_jose_get_string (3b)", dst, NULL); oidc_jwt_destroy(jwt); return 0; } static char *test_jwk_parse_json(apr_pool_t *pool) { const char *s = "{\"kty\":\"EC\",\"use\":\"sig\"," "\"kid\":\"the key\"," "\"x\":\"amuk6RkDZi-48mKrzgBN_zUZ_9qupIwTZHJjM03qL-4\"," "\"y\":\"ZOESj6_dpPiZZR-fJ-XVszQta28Cjgti7JudooQJ0co\",\"crv\":\"P-256\"}"; oidc_jose_error_t err; oidc_jwk_t *jwk; jwk = NULL; TST_ASSERT_ERR("oidc_jwk_parse (1)", _jwk_parse(pool, s, &jwk, &err) == 0, pool, err); oidc_jwk_destroy(jwk); // https://tools.ietf.org/html/draft-ietf-jose-json-web-key-41#appendix-A.3 // A.3. Example Symmetric Keys #1 s = "{" "\"kty\":\"oct\"," "\"alg\":\"A128KW\"," "\"k\" :\"GawgguFyGrWKav7AX4VKUg\"" "}"; jwk = NULL; TST_ASSERT_ERR("oidc_jwk_parse (draft-ietf-jose-json-web-key-41#appendix-A.3 #1)", _jwk_parse(pool, s, &jwk, &err) == 0, pool, err); oidc_jwk_destroy(jwk); // TST_ASSERT_LONG("#1 jwk->type", (long )jwk->type, (long )APR_JWK_KEY_OCT); // TST_ASSERT_LONG("#1 jwk->key.oct->k_len", (long )jwk->key.oct->k_len, 16L); // https://tools.ietf.org/html/draft-ietf-jose-json-web-key-41#appendix-A.3 // A.3. Example Symmetric Keys #2 s = "{" "\"kty\":\"oct\"," "\"k\" :\"AyM1SysPpbyDfgZld3umj1qzKObwVMkoqQ-EstJQLr_T-1qS0gZH75aKtMN3Yj0iPS4hcgUuTwjAzZr1Z9CAow\"," "\"kid\":\"HMAC key used in JWS A.1 example\"" "}"; jwk = NULL; TST_ASSERT_ERR("oidc_jwk_parse (draft-ietf-jose-json-web-key-41#appendix-A.3 #2)", _jwk_parse(pool, s, &jwk, &err) == 0, pool, err); oidc_jwk_destroy(jwk); // TST_ASSERT_LONG("#2 jwk->type", (long )jwk->type, (long )APR_JWK_KEY_OCT); // TST_ASSERT_LONG("#2 jwk->key.oct->k_len", (long )jwk->key.oct->k_len, 64L); // https://tools.ietf.org/html/draft-ietf-jose-cookbook-08#section-3.1 // 3.1. EC Public Key s = "{" "\"kty\": \"EC\"," "\"kid\": \"bilbo.baggins@hobbiton.example\"," "\"use\": \"sig\"," "\"crv\": \"P-521\"," "\"x\": \"AHKZLLOsCOzz5cY97ewNUajB957y-C-U88c3v13nmGZx6sYl_oJXu9A5RkTKqjqvjyekWF-7ytDyRXYgCF5cj0Kt\"," "\"y\": \"AdymlHvOiLxXkEhayXQnNCvDX4h9htZaCJN34kfmC6pV5OhQHiraVySsUdaQkAgDPrwQrJmbnX9cwlGfP-HqHZR1\"" "}"; jwk = NULL; TST_ASSERT_ERR("oidc_jwk_parse (draft-ietf-jose-cookbook-08#section-3.1, EC Public Key)", _jwk_parse(pool, s, &jwk, &err) == 0, pool, err); oidc_jwk_destroy(jwk); // https://tools.ietf.org/html/draft-ietf-jose-cookbook-08#section-3.2 // 3.2. EC Private Key s = "{" "\"kty\": \"EC\"," "\"kid\": \"bilbo.baggins@hobbiton.example\"," "\"use\": \"sig\"," "\"crv\": \"P-521\"," "\"x\": \"AHKZLLOsCOzz5cY97ewNUajB957y-C-U88c3v13nmGZx6sYl_oJXu9A5RkTKqjqvjyekWF-7ytDyRXYgCF5cj0Kt\"," "\"y\": \"AdymlHvOiLxXkEhayXQnNCvDX4h9htZaCJN34kfmC6pV5OhQHiraVySsUdaQkAgDPrwQrJmbnX9cwlGfP-HqHZR1\"," "\"d\": \"AAhRON2r9cqXX1hg-RoI6R1tX5p2rUAYdmpHZoC1XNM56KtscrX6zbKipQrCW9CGZH3T4ubpnoTKLDYJ_fF3_rJt\"" "}"; jwk = NULL; TST_ASSERT_ERR("oidc_jwk_parse (draft-ietf-jose-cookbook-08#section-3.2, EC Private Key)", _jwk_parse(pool, s, &jwk, &err) == 0, pool, err); oidc_jwk_destroy(jwk); // https://tools.ietf.org/html/draft-ietf-jose-cookbook-08#section-3.3 // 3.3. RSA Public Key s = "{" "\"kty\": \"RSA\"," "\"kid\": \"bilbo.baggins@hobbiton.example\"," "\"use\": \"sig\"," "\"n\": \"n4EPtAOCc9AlkeQHPzHStgAbgs7bTZLwUBZdR8_KuKPEHLd4rHVTeT" "-O-XV2jRojdNhxJWTDvNd7nqQ0VEiZQHz_AJmSCpMaJMRBSFKrKb2wqV" "wGU_NsYOYL-QtiWN2lbzcEe6XC0dApr5ydQLrHqkHHig3RBordaZ6Aj-" "oBHqFEHYpPe7Tpe-OfVfHd1E6cS6M1FZcD1NNLYD5lFHpPI9bTwJlsde" "3uhGqC0ZCuEHg8lhzwOHrtIQbS0FVbb9k3-tVTU4fg_3L_vniUFAKwuC" "LqKnS2BYwdq_mzSnbLY7h_qixoR7jig3__kRhuaxwUkRz5iaiQkqgc5g" "HdrNP5zw\"," "\"e\": \"AQAB\"" "}"; jwk = NULL; TST_ASSERT_ERR("oidc_jwk_parse (draft-ietf-jose-cookbook-08#section-3.3, RSA Public Key)", _jwk_parse(pool, s, &jwk, &err) == 0, pool, err); oidc_jwk_destroy(jwk); // https://tools.ietf.org/html/draft-ietf-jose-cookbook-08#section-3.4 // 3.4. RSA Private Key s = "{" "\"kty\": \"RSA\"," "\"kid\": \"bilbo.baggins@hobbiton.example\"," "\"use\": \"sig\"," "\"n\": \"n4EPtAOCc9AlkeQHPzHStgAbgs7bTZLwUBZdR8_KuKPEHLd4rHVTeT" "-O-XV2jRojdNhxJWTDvNd7nqQ0VEiZQHz_AJmSCpMaJMRBSFKrKb2wqV" "wGU_NsYOYL-QtiWN2lbzcEe6XC0dApr5ydQLrHqkHHig3RBordaZ6Aj-" "oBHqFEHYpPe7Tpe-OfVfHd1E6cS6M1FZcD1NNLYD5lFHpPI9bTwJlsde" "3uhGqC0ZCuEHg8lhzwOHrtIQbS0FVbb9k3-tVTU4fg_3L_vniUFAKwuC" "LqKnS2BYwdq_mzSnbLY7h_qixoR7jig3__kRhuaxwUkRz5iaiQkqgc5g" "HdrNP5zw\"," "\"e\": \"AQAB\"," "\"d\": \"bWUC9B-EFRIo8kpGfh0ZuyGPvMNKvYWNtB_ikiH9k20eT-O1q_I78e" "iZkpXxXQ0UTEs2LsNRS-8uJbvQ-A1irkwMSMkK1J3XTGgdrhCku9gRld" "Y7sNA_AKZGh-Q661_42rINLRCe8W-nZ34ui_qOfkLnK9QWDDqpaIsA-b" "MwWWSDFu2MUBYwkHTMEzLYGqOe04noqeq1hExBTHBOBdkMXiuFhUq1BU" "6l-DqEiWxqg82sXt2h-LMnT3046AOYJoRioz75tSUQfGCshWTBnP5uDj" "d18kKhyv07lhfSJdrPdM5Plyl21hsFf4L_mHCuoFau7gdsPfHPxxjVOc" "OpBrQzwQ\"," "\"p\": \"3Slxg_DwTXJcb6095RoXygQCAZ5RnAvZlno1yhHtnUex_fp7AZ_9nR" "aO7HX_-SFfGQeutao2TDjDAWU4Vupk8rw9JR0AzZ0N2fvuIAmr_WCsmG" "peNqQnev1T7IyEsnh8UMt-n5CafhkikzhEsrmndH6LxOrvRJlsPp6Zv8" "bUq0k\"," "\"q\": \"uKE2dh-cTf6ERF4k4e_jy78GfPYUIaUyoSSJuBzp3Cubk3OCqs6grT" "8bR_cu0Dm1MZwWmtdqDyI95HrUeq3MP15vMMON8lHTeZu2lmKvwqW7an" "V5UzhM1iZ7z4yMkuUwFWoBvyY898EXvRD-hdqRxHlSqAZ192zB3pVFJ0" "s7pFc\"," "\"dp\": \"B8PVvXkvJrj2L-GYQ7v3y9r6Kw5g9SahXBwsWUzp19TVlgI-YV85q" "1NIb1rxQtD-IsXXR3-TanevuRPRt5OBOdiMGQp8pbt26gljYfKU_E9xn" "-RULHz0-ed9E9gXLKD4VGngpz-PfQ_q29pk5xWHoJp009Qf1HvChixRX" "59ehik\"," "\"dq\": \"CLDmDGduhylc9o7r84rEUVn7pzQ6PF83Y-iBZx5NT-TpnOZKF1pEr" "AMVeKzFEl41DlHHqqBLSM0W1sOFbwTxYWZDm6sI6og5iTbwQGIC3gnJK" "bi_7k_vJgGHwHxgPaX2PnvP-zyEkDERuf-ry4c_Z11Cq9AqC2yeL6kdK" "T1cYF8\"," "\"qi\": \"3PiqvXQN0zwMeE-sBvZgi289XP9XCQF3VWqPzMKnIgQp7_Tugo6-N" "ZBKCQsMf3HaEGBjTVJs_jcK8-TRXvaKe-7ZMaQj8VfBdYkssbu0NKDDh" "jJ-GtiseaDVWt7dcH0cfwxgFUHpQh7FoCrjFJ6h6ZEpMF6xmujs4qMpP" "z8aaI4\"" "}"; jwk = NULL; TST_ASSERT_ERR("oidc_jwk_parse (draft-ietf-jose-cookbook-08#section-3.4, RSA Private Key)", _jwk_parse(pool, s, &jwk, &err) == 0, pool, err); oidc_jwk_destroy(jwk); s = "{\"kty\":\"RSA\",\"kid\":\"IbLjLR7-C1q0-ypkueZxGIJwBQNaLg46DZMpnPW1kps\",\"e\":\"AQAB\",\"n\":" "\"iGeTXbfV5bMppx7o7qMLCuVIKqbBa_qOzBiNNpe0K8rjg7-1z9GCuSlqbZtM0_5BQ6bGonnSPD--" "PowhFdivS4WNA33O0Kl1tQ0wdH3TOnwueIO9ahfW4q0BGFvMObneK-tjwiNMj1l-cZt8pvuS-3LtTWIzC-" "hTZM4caUmy5olm5PVdmru6C6V5rxkbYBPITFSzl5mpuo_C6RV_MYRwAh60ghs2OEvIWDrJkZnYaF7sjHC9j-" "4kfcM5oY7Zhg8KuHyloudYNzlqjVAPd0MbkLkh1pa8fmHsnN6cgfXYtFK7Z8WjYDUAhTH1JjZCVSFN55A-" "51dgD4cQNzieLEEkJw\",\"x5c\":[\"MIICnTCCAYUCBgFuk1+" "FLDANBgkqhkiG9w0BAQsFADASMRAwDgYDVQQDDAd2aW5jZW50MB4XDTE5MTEyMjEzNDcyMVoXDTI5MTEyMjEzNDkwMVowEj" "EQMA4GA1UEAwwHdmluY2VudDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAIhnk1231eWzKace6O6jCwrlSCqmw" "Wv6jswYjTaXtCvK44O/tc/Rgrkpam2bTNP+QUOmxqJ50jw/" "vj6MIRXYr0uFjQN9ztCpdbUNMHR90zp8LniDvWoX1uKtARhbzDm53ivrY8IjTI9ZfnGbfKb7kvty7U1iMwvoU2TOHGlJsua" "JZuT1XZq7ugulea8ZG2ATyExUs5eZqbqPwukVfzGEcAIetIIbNjhLyFg6yZGZ2Ghe7IxwvY/" "uJH3DOaGO2YYPCrh8paLnWDc5ao1QD3dDG5C5IdaWvH5h7JzenIH12LRSu2fFo2A1AIUx9SY2QlUhTeeQPudXYA+" "HEDc4nixBJCcCAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAfAo40il4qw7DfOkke0p1ZFAgLQQS3J5hYNDSRvVv+vxkk9o/" "N++zTMoHbfcDcU5BdVH6Qsr/12PXPX7Ur5WYDq+bWGAK3MAaGtZlmycFeVhoVRfab4TUWUy43H3VyFUNqjGRAVJ/" "VD1RW3fJ18KrQTN2fcKSd88Jqt5TvjROKghq95+8BQtlhrR/" "sQVrjgYwc+eU9ljWI56MQXbpHstl9IewMXnusSPxKRTbutjaxzKaoXRTUncPL6ga0SSxOTdKksM4ZYpPnq0B93silb+" "0qs8aJraGzjAmLE30opfufP+roth19VJxAfYsW5mgAmXP9kEAF+iWB8FB4/" "Q4noNG8Q==\"],\"x5t#S256\":\"hMVJ55Mqi4uAQIztPKUmL2MSfy6iN1Lr3J1CNGAIBms\",\"x5t\":\"0oN6Bx-" "eh6VAmNw1I7o3Dd9JPwE\"}"; jwk = NULL; TST_ASSERT_ERR("oidc_jwk_parse (x5c)", _jwk_parse(pool, s, &jwk, &err) == 0, pool, err); TST_ASSERT_STR("oidc_jwk_parse (x5c)", APR_ARRAY_IDX(jwk->x5c, 0, char *), "MIICnTCCAYUCBgFuk1+" "FLDANBgkqhkiG9w0BAQsFADASMRAwDgYDVQQDDAd2aW5jZW50MB4XDTE5MTEyMjEzNDcyMVoXDTI5MTEyMjEzNDkwMVowEj" "EQMA4GA1UEAwwHdmluY2VudDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAIhnk1231eWzKace6O6jCwrlSCqmw" "Wv6jswYjTaXtCvK44O/tc/Rgrkpam2bTNP+QUOmxqJ50jw/" "vj6MIRXYr0uFjQN9ztCpdbUNMHR90zp8LniDvWoX1uKtARhbzDm53ivrY8IjTI9ZfnGbfKb7kvty7U1iMwvoU2TOHGlJsua" "JZuT1XZq7ugulea8ZG2ATyExUs5eZqbqPwukVfzGEcAIetIIbNjhLyFg6yZGZ2Ghe7IxwvY/" "uJH3DOaGO2YYPCrh8paLnWDc5ao1QD3dDG5C5IdaWvH5h7JzenIH12LRSu2fFo2A1AIUx9SY2QlUhTeeQPudXYA+" "HEDc4nixBJCcCAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAfAo40il4qw7DfOkke0p1ZFAgLQQS3J5hYNDSRvVv+vxkk9o/" "N++zTMoHbfcDcU5BdVH6Qsr/12PXPX7Ur5WYDq+bWGAK3MAaGtZlmycFeVhoVRfab4TUWUy43H3VyFUNqjGRAVJ/" "VD1RW3fJ18KrQTN2fcKSd88Jqt5TvjROKghq95+8BQtlhrR/" "sQVrjgYwc+eU9ljWI56MQXbpHstl9IewMXnusSPxKRTbutjaxzKaoXRTUncPL6ga0SSxOTdKksM4ZYpPnq0B93silb+" "0qs8aJraGzjAmLE30opfufP+roth19VJxAfYsW5mgAmXP9kEAF+iWB8FB4/" "Q4noNG8Q=="); oidc_jwk_destroy(jwk); return 0; } static char *test_jwk_copy(apr_pool_t *pool) { oidc_jose_error_t err; char *s = NULL; oidc_jwk_t *jwk1 = NULL; oidc_jwk_t *jwk2 = NULL; s = "{" "\"kty\": \"RSA\"," "\"kid\": \"bilbo.baggins@hobbiton.example\"," "\"use\": \"sig\"," "\"n\": \"n4EPtAOCc9AlkeQHPzHStgAbgs7bTZLwUBZdR8_KuKPEHLd4rHVTeT" "-O-XV2jRojdNhxJWTDvNd7nqQ0VEiZQHz_AJmSCpMaJMRBSFKrKb2wqV" "wGU_NsYOYL-QtiWN2lbzcEe6XC0dApr5ydQLrHqkHHig3RBordaZ6Aj-" "oBHqFEHYpPe7Tpe-OfVfHd1E6cS6M1FZcD1NNLYD5lFHpPI9bTwJlsde" "3uhGqC0ZCuEHg8lhzwOHrtIQbS0FVbb9k3-tVTU4fg_3L_vniUFAKwuC" "LqKnS2BYwdq_mzSnbLY7h_qixoR7jig3__kRhuaxwUkRz5iaiQkqgc5g" "HdrNP5zw\"," "\"e\": \"AQAB\"," "\"x5t\": \"myx5t\"," "\"x5t#S256\": \"myx5t#S256\"" "}"; jwk1 = NULL; TST_ASSERT_ERR("oidc_jwk_parse", _jwk_parse(pool, s, &jwk1, &err) == 0, pool, err); jwk2 = oidc_jwk_copy(pool, jwk1); TST_ASSERT_STR("oidc_jwk_parse (x5t)", jwk1->x5t, "myx5t"); TST_ASSERT_STR("oidc_jwk_parse (x5t#S256)", jwk1->x5t_S256, "myx5t#S256"); TST_ASSERT_STR("oidc_jwk_copy (x5t)", jwk2->x5t, "myx5t"); TST_ASSERT_STR("oidc_jwk_copy (x5t#S256)", jwk2->x5t_S256, "myx5t#S256"); oidc_jwk_destroy(jwk2); oidc_jwk_destroy(jwk1); return 0; } static char *test_plaintext_decrypt_symmetric(apr_pool_t *pool) { oidc_jose_error_t err; apr_hash_t *keys = apr_hash_make(pool); oidc_jwk_t *jwk; // http://tools.ietf.org/html/draft-ietf-jose-json-web-encryption-40#appendix-A.3 // A.3. Example JWE using AES Key Wrap and AES_128_CBC_HMAC_SHA_256 const char *k = "{\"kty\":\"oct\", \"k\":\"GawgguFyGrWKav7AX4VKUg\"}"; jwk = NULL; TST_ASSERT_ERR("oidc_jwk_parse", _jwk_parse(pool, k, &jwk, &err) == 0, pool, err); apr_hash_set(keys, "dummy", APR_HASH_KEY_STRING, jwk); const char *s = "eyJhbGciOiJBMTI4S1ciLCJlbmMiOiJBMTI4Q0JDLUhTMjU2In0." "6KB707dM9YTIgHtLvtgWQ8mKwboJW3of9locizkDTHzBC2IlrT1oOQ." "AxY8DCtDaGlsbGljb3RoZQ." "KDlTtXchhZTGufMYmOYGS4HffxPSUrfmqCHXaI9wOGY." "U0m_YmjN04DJvceFICbCVQ"; cjose_err cjose_err; cjose_jwe_t *jwe = cjose_jwe_import(s, _oidc_strlen(s), &cjose_err); TST_ASSERT_CJOSE_ERR("cjose_jwe_import", jwe != NULL, pool, cjose_err); size_t content_len = 0; uint8_t *decrypted = cjose_jwe_decrypt(jwe, jwk->cjose_jwk, &content_len, &cjose_err); TST_ASSERT_CJOSE_ERR("cjose_jwe_decrypt", decrypted != NULL, pool, cjose_err); TST_ASSERT_STRN("decrypted", (const char *)decrypted, "Live long and prosper.", content_len); cjose_get_dealloc()(decrypted); oidc_jwk_destroy(jwk); cjose_jwe_release(jwe); return 0; } #if (OPENSSL_VERSION_NUMBER >= 0x1000100f) static char *test_jwt_decrypt_gcm(apr_pool_t *pool) { // https://tools.ietf.org/html/rfc7516#appendix-A.1 // A.1. Example JWE using RSAES-OAEP and AES GCM char *s = apr_pstrdup(pool, "eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkEyNTZHQ00ifQ." "OKOawDo13gRp2ojaHV7LFpZcgV7T6DVZKTyKOMTYUmKoTCVJRgckCL9kiMT03JGe" "ipsEdY3mx_etLbbWSrFr05kLzcSr4qKAq7YN7e9jwQRb23nfa6c9d-StnImGyFDb" "Sv04uVuxIp5Zms1gNxKKK2Da14B8S4rzVRltdYwam_lDp5XnZAYpQdb76FdIKLaV" "mqgfwX7XWRxv2322i-vDxRfqNzo_tETKzpVLzfiwQyeyPGLBIO56YJ7eObdv0je8" "1860ppamavo35UgoRdbYaBcoh9QcfylQr66oc6vFWXRcZ_ZT2LawVCWTIy3brGPi" "6UklfCpIMfIjf7iGdXKHzg." "48V1_ALb6US04U3b." "5eym8TW_c8SuK0ltJ3rpYIzOeDQz7TALvtu6UG9oMo4vpzs9tX_EFShS8iB7j6ji" "SdiwkIr3ajwQzaBtQD_A." "XFBoMYUZodetZdvTiFvSkQ"); char *k = "{\"kty\":\"RSA\"," "\"n\":\"oahUIoWw0K0usKNuOR6H4wkf4oBUXHTxRvgb48E-BVvxkeDNjbC4he8rUW" "cJoZmds2h7M70imEVhRU5djINXtqllXI4DFqcI1DgjT9LewND8MW2Krf3S" "psk_ZkoFnilakGygTwpZ3uesH-PFABNIUYpOiN15dsQRkgr0vEhxN92i2a" "sbOenSZeyaxziK72UwxrrKoExv6kc5twXTq4h-QChLOln0_mtUZwfsRaMS" "tPs6mS6XrgxnxbWhojf663tuEQueGC-FCMfra36C9knDFGzKsNa7LZK2dj" "YgyD3JR_MB_4NUJW_TqOQtwHYbxevoJArm-L5StowjzGy-_bq6Gw\"," "\"e\":\"AQAB\"," "\"d\":\"kLdtIj6GbDks_ApCSTYQtelcNttlKiOyPzMrXHeI-yk1F7-kpDxY4-WY5N" "WV5KntaEeXS1j82E375xxhWMHXyvjYecPT9fpwR_M9gV8n9Hrh2anTpTD9" "3Dt62ypW3yDsJzBnTnrYu1iwWRgBKrEYY46qAZIrA2xAwnm2X7uGR1hghk" "qDp0Vqj3kbSCz1XyfCs6_LehBwtxHIyh8Ripy40p24moOAbgxVw3rxT_vl" "t3UVe4WO3JkJOzlpUf-KTVI2Ptgm-dARxTEtE-id-4OJr0h-K-VFs3VSnd" "VTIznSxfyrj8ILL6MG_Uv8YAu7VILSB3lOW085-4qE3DzgrTjgyQ\"," "\"p\":\"1r52Xk46c-LsfB5P442p7atdPUrxQSy4mti_tZI3Mgf2EuFVbUoDBvaRQ-" "SWxkbkmoEzL7JXroSBjSrK3YIQgYdMgyAEPTPjXv_hI2_1eTSPVZfzL0lf" "fNn03IXqWF5MDFuoUYE0hzb2vhrlN_rKrbfDIwUbTrjjgieRbwC6Cl0\"," "\"q\":\"wLb35x7hmQWZsWJmB_vle87ihgZ19S8lBEROLIsZG4ayZVe9Hi9gDVCOBm" "UDdaDYVTSNx_8Fyw1YYa9XGrGnDew00J28cRUoeBB_jKI1oma0Orv1T9aX" "IWxKwd4gvxFImOWr3QRL9KEBRzk2RatUBnmDZJTIAfwTs0g68UZHvtc\"," "\"dp\":\"ZK-YwE7diUh0qR1tR7w8WHtolDx3MZ_OTowiFvgfeQ3SiresXjm9gZ5KL" "hMXvo-uz-KUJWDxS5pFQ_M0evdo1dKiRTjVw_x4NyqyXPM5nULPkcpU827" "rnpZzAJKpdhWAgqrXGKAECQH0Xt4taznjnd_zVpAmZZq60WPMBMfKcuE\"," "\"dq\":\"Dq0gfgJ1DdFGXiLvQEZnuKEN0UUmsJBxkjydc3j4ZYdBiMRAy86x0vHCj" "ywcMlYYg4yoC4YZa9hNVcsjqA3FeiL19rk8g6Qn29Tt0cj8qqyFpz9vNDB" "UfCAiJVeESOjJDZPYHdHY8v1b-o-Z2X5tvLx-TCekf7oxyeKDUqKWjis\"," "\"qi\":\"VIMpMYbPf47dT1w_zDUXfPimsSegnMOA1zTaX7aGk_8urY6R8-ZW1FxU7" "AlWAyLWybqq6t16VFd7hQd0y6flUK4SlOydB61gwanOsXGOAOv82cHq0E3" "eL4HrtZkUuKvnPrMnsUUFlfUdybVzxyjz9JF_XyaY14ardLSjf4L_FNY\"" "}"; oidc_jose_error_t err; apr_hash_t *keys = apr_hash_make(pool); oidc_jwk_t *jwk = NULL; TST_ASSERT_ERR("oidc_jwk_parse", _jwk_parse(pool, k, &jwk, &err) == 0, pool, err); apr_hash_set(keys, "dummy", APR_HASH_KEY_STRING, jwk); cjose_err cjose_err; cjose_jwe_t *jwe = cjose_jwe_import(s, _oidc_strlen(s), &cjose_err); TST_ASSERT_CJOSE_ERR("cjose_jwe_import", jwe != NULL, pool, cjose_err); size_t content_len = 0; uint8_t *decrypted = cjose_jwe_decrypt(jwe, jwk->cjose_jwk, &content_len, &cjose_err); TST_ASSERT_CJOSE_ERR("cjose_jwe_decrypt", decrypted != NULL, pool, cjose_err); TST_ASSERT_STRN("decrypted", (const char *)decrypted, "The true sign of intelligence is not knowledge but imagination.", content_len); cjose_get_dealloc()(decrypted); cjose_jwe_release(jwe); oidc_jwk_destroy(jwk); return 0; } #endif static char *test_proto_validate_access_token(request_rec *r) { // from http://openid.net/specs/openid-connect-core-1_0.html#id_token-tokenExample // A.3 Example using response_type=id_token token const char *s = "eyJraWQiOiIxZTlnZGs3IiwiYWxnIjoiUlMyNTYifQ.ewogIml" "zcyI6ICJodHRwOi8vc2VydmVyLmV4YW1wbGUuY29tIiwKICJzdWIiOiAiMjQ" "4Mjg5NzYxMDAxIiwKICJhdWQiOiAiczZCaGRSa3F0MyIsCiAibm9uY2UiOiA" "ibi0wUzZfV3pBMk1qIiwKICJleHAiOiAxMzExMjgxOTcwLAogImlhdCI6IDE" "zMTEyODA5NzAsCiAiYXRfaGFzaCI6ICI3N1FtVVB0alBmeld0RjJBbnBLOVJ" "RIgp9.F9gRev0Dt2tKcrBkHy72cmRqnLdzw9FLCCSebV7mWs7o_sv2O5s6zM" "ky2kmhHTVx9HmdvNnx9GaZ8XMYRFeYk8L5NZ7aYlA5W56nsG1iWOou_-gji0" "ibWIuuf4Owaho3YSoi7EvsTuLFz6tq-dLyz0dKABMDsiCmJ5wqkPUDTE3QTX" "jzbUmOzUDli-gCh5QPuZAq0cNW3pf_2n4zpvTYtbmj12cVcxGIMZby7TMWES" "RjQ9_o3jvhVNcCGcE0KAQXejhA1ocJhNEvQNqMFGlBb6_0RxxKjDZ-Oa329e" "GDidOvvp0h5hoES4a8IuGKS7NOcpp-aFwp0qVMDLI-Xnm-Pg"; oidc_jose_error_t err; oidc_jwt_t *jwt = NULL; TST_ASSERT_ERR("oidc_jwt_parse", oidc_jwt_parse(r->pool, s, &jwt, NULL, FALSE, &err), r->pool, err); const char *access_token = "jHkWEdUXMU1BwAsC4vtUsZwnNvTIxEl0z9K3vx5KF0Y"; TST_ASSERT("oidc_proto_validate_access_token", oidc_proto_idtoken_validate_access_token(r, NULL, jwt, "id_token token", access_token)); oidc_jwt_destroy(jwt); return 0; } static char *test_proto_validate_code(request_rec *r) { // from http://openid.net/specs/openid-connect-core-1_0.html#code-id_tokenExample // A.4 Example using response_type=code id_token const char *s = "eyJraWQiOiIxZTlnZGs3IiwiYWxnIjoiUlMyNTYifQ.ewogIml" "zcyI6ICJodHRwOi8vc2VydmVyLmV4YW1wbGUuY29tIiwKICJzdWIiOiAiMjQ" "4Mjg5NzYxMDAxIiwKICJhdWQiOiAiczZCaGRSa3F0MyIsCiAibm9uY2UiOiA" "ibi0wUzZfV3pBMk1qIiwKICJleHAiOiAxMzExMjgxOTcwLAogImlhdCI6IDE" "zMTEyODA5NzAsCiAiY19oYXNoIjogIkxEa3RLZG9RYWszUGswY25YeENsdEE" "iCn0.XW6uhdrkBgcGx6zVIrCiROpWURs-4goO1sKA4m9jhJIImiGg5muPUcN" "egx6sSv43c5DSn37sxCRrDZZm4ZPBKKgtYASMcE20SDgvYJdJS0cyuFw7Ijp" "_7WnIjcrl6B5cmoM6ylCvsLMwkoQAxVublMwH10oAxjzD6NEFsu9nipkszWh" "sPePf_rM4eMpkmCbTzume-fzZIi5VjdWGGEmzTg32h3jiex-r5WTHbj-u5HL" "7u_KP3rmbdYNzlzd1xWRYTUs4E8nOTgzAUwvwXkIQhOh5TPcSMBYy6X3E7-_" "gr9Ue6n4ND7hTFhtjYs3cjNKIA08qm5cpVYFMFMG6PkhzLQ"; oidc_jose_error_t err; oidc_jwt_t *jwt = NULL; TST_ASSERT_ERR("oidc_jwt_parse", oidc_jwt_parse(r->pool, s, &jwt, NULL, FALSE, &err), r->pool, err); const char *code = "Qcb0Orv1zh30vL1MPRsbm-diHiMwcLyZvn1arpZv-Jxf_11jnpEX3Tgfvk"; TST_ASSERT("oidc_proto_validate_code", oidc_proto_idtoken_validate_code(r, NULL, jwt, "code id_token", code)); oidc_jwt_destroy(jwt); return 0; } static char *test_proto_authorization_request(request_rec *r) { oidc_provider_t *provider = oidc_cfg_provider_create(r->pool); oidc_cfg_provider_issuer_set(r->pool, provider, "https://idp.example.com"); oidc_cfg_provider_authorization_endpoint_url_set(r->pool, provider, "https://idp.example.com/authorize"); oidc_cfg_provider_client_id_set(r->pool, provider, "client_id"); oidc_cfg_provider_auth_request_params_set(r->pool, provider, "jan=piet&foo=#"); const char *redirect_uri = "https://www.example.com/protected/"; const char *state = "12345"; oidc_proto_state_t *proto_state = oidc_proto_state_new(); oidc_proto_state_set_nonce(proto_state, "anonce"); oidc_proto_state_set_original_url(proto_state, "https://localhost/protected/index.php"); oidc_proto_state_set_original_method(proto_state, OIDC_METHOD_GET); oidc_proto_state_set_issuer(proto_state, oidc_cfg_provider_issuer_get(provider)); oidc_proto_state_set_response_type(proto_state, oidc_cfg_provider_response_type_get(provider)); oidc_proto_state_set_timestamp_now(proto_state); TST_ASSERT("oidc_proto_request_auth (1)", oidc_proto_request_auth(r, provider, NULL, redirect_uri, state, proto_state, NULL, NULL, NULL, NULL) == HTTP_MOVED_TEMPORARILY); TST_ASSERT_STR("oidc_proto_request_auth (2)", apr_table_get(r->headers_out, "Location"), "https://idp.example.com/" "authorize?response_type=code&scope=openid&client_id=client_id&state=12345&redirect_uri=https%" "3A%2F%2Fwww.example.com%2Fprotected%2F&nonce=anonce&jan=piet&foo=bar"); return 0; } static char *test_logout_request(request_rec *r) { oidc_cfg_t *c = ap_get_module_config(r->server->module_config, &auth_openidc_module); oidc_session_t *session = NULL; oidc_session_load(r, &session); oidc_session_set_issuer(r, session, oidc_cfg_provider_issuer_get(oidc_cfg_provider_get(c))); oidc_cfg_provider_end_session_endpoint_set(r->pool, oidc_cfg_provider_get(c), "https://idp.example.com/endsession"); oidc_cfg_provider_logout_request_params_set(r->pool, oidc_cfg_provider_get(c), "client_id=myclient&foo=bar"); r->args = "logout=https%3A%2F%2Fwww.example.com%2Floggedout"; TST_ASSERT("oidc_handle_logout (1)", oidc_logout(r, c, session) == HTTP_MOVED_TEMPORARILY); TST_ASSERT_STR( "oidc_handle_logout (2)", apr_table_get(r->headers_out, "Location"), "https://idp.example.com/" "endsession?post_logout_redirect_uri=https%3A%2F%2Fwww.example.com%2Floggedout&client_id=myclient&foo=bar"); oidc_session_free(r, session); return 0; } static char *test_proto_validate_nonce(request_rec *r) { oidc_cfg_t *c = ap_get_module_config(r->server->module_config, &auth_openidc_module); const char *nonce = "avSk7S69G4kEE8Km4bPiOjrfChHt6nO4Z397Lp_bQnc,"; /* * { * "typ": "JWT", * "alg": "RS256", * "x5t": "Z1NCjojeiHAib-Gm8vFE6ya6lPM" * } * { * "nonce": "avSk7S69G4kEE8Km4bPiOjrfChHt6nO4Z397Lp_bQnc,", * "iat": 1411580876, * "at_hash": "yTqsoONZbuWbN6TbgevuDQ", * "sub": "6343a29c-5399-44a7-9b35-4990f4377c96", * "amr": "password", * "auth_time": 1411577267, * "idp": "idsrv", * "name": "ksonaty", * "iss": "https://agsync.com", * "aud": "agsync_implicit", * "exp": 1411584475, * "nbf": 1411580875 * } */ char *s_jwt = apr_pstrdup( r->pool, "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6IloxTkNqb2plaUhBaWItR204dkZFNnlhNmxQTSJ9." "eyJub25jZSI6ImF2U2s3UzY5RzRrRUU4S200YlBpT2pyZkNoSHQ2bk80WjM5N0xwX2JRbmMsIiwiaWF0IjoxNDExNTgwODc2LCJhdF9oYX" "NoIjoieVRxc29PTlpidVdiTjZUYmdldnVEUSIsInN1YiI6IjYzNDNhMjljLTUzOTktNDRhNy05YjM1LTQ5OTBmNDM3N2M5NiIsImFtciI6" "InBhc3N3b3JkIiwiYXV0aF90aW1lIjoxNDExNTc3MjY3LCJpZHAiOiJpZHNydiIsIm5hbWUiOiJrc29uYXR5IiwiaXNzIjoiaHR0cHM6Ly" "9hZ3N5bmMuY29tIiwiYXVkIjoiYWdzeW5jX2ltcGxpY2l0IiwiZXhwIjoxNDExNTg0NDc1LCJuYmYiOjE0MTE1ODA4NzV9.lEG-" "DgHHa0JuOEuOTBvCqyexjRVcKXBnJJm289o2HyTgclpH80DsOMED9RlXCFfuDY7nw9i2cxUmIMAV42AdTxkMPomK3chytcajvpAZJirlk6" "53bo9GTDXJSKZr5fwyEu--qahsoT5t9qvoWyFdYkvmMHFw1-" "mAHDGgVe23voc9jPuFFIhRRqIn4e8ikzN4VQeEV1UXJD02kYYFn2TRWURgiFyVeTr2r0MTn-auCEsFS_AfR1Bl_" "kmpMfqwrsicf5MTBvfPJeuSMt3t3d3LOGBkg36_z21X-ZRN7wy1KTjagr7iQ_y5csIpmtqs_QM55TTB9dW1HIosJPhiuMEJEA"); oidc_jwt_t *jwt = NULL; oidc_jose_error_t err; TST_ASSERT_ERR("oidc_jwt_parse", oidc_jwt_parse(r->pool, s_jwt, &jwt, NULL, FALSE, &err), r->pool, err); TST_ASSERT("oidc_proto_idtoken_validate_nonce (1)", oidc_proto_idtoken_validate_nonce(r, c, oidc_cfg_provider_get(c), nonce, jwt)); TST_ASSERT("oidc_proto_idtoken_validate_nonce (2)", oidc_proto_idtoken_validate_nonce(r, c, oidc_cfg_provider_get(c), nonce, jwt) == FALSE); oidc_jwt_destroy(jwt); return 0; } static char *test_proto_validate_jwt(request_rec *r) { oidc_jwt_t *jwt = NULL; oidc_jose_error_t err; const char *s_secret = "secret"; const char *s_issuer = "https://localhost"; apr_time_t now = apr_time_sec(apr_time_now()); const char *s_jwt_header = "{" "\"alg\": \"HS256\"" "}"; const char *s_jwt_payload = "{" "\"nonce\": \"543210,\"," "\"iat\": %" APR_TIME_T_FMT "," "\"sub\": \"alice\"," "\"iss\": \"%s\"," "\"aud\": \"bob\"," "\"exp\": %" APR_TIME_T_FMT "}"; s_jwt_payload = apr_psprintf(r->pool, s_jwt_payload, now, s_issuer, now + 600); char *s_jwt_header_encoded = NULL; oidc_util_base64url_encode(r, &s_jwt_header_encoded, s_jwt_header, _oidc_strlen(s_jwt_header), 1); char *s_jwt_payload_encoded = NULL; oidc_util_base64url_encode(r, &s_jwt_payload_encoded, s_jwt_payload, _oidc_strlen(s_jwt_payload), 1); char *s_jwt_message = apr_psprintf(r->pool, "%s.%s", s_jwt_header_encoded, s_jwt_payload_encoded); unsigned int md_len = 0; unsigned char md[EVP_MAX_MD_SIZE]; const EVP_MD *digest = EVP_get_digestbyname("sha256"); TST_ASSERT("HMAC", HMAC(digest, (const unsigned char *)s_secret, _oidc_strlen(s_secret), (const unsigned char *)s_jwt_message, _oidc_strlen(s_jwt_message), md, &md_len) != 0); char *s_jwt_signature_encoded = NULL; oidc_util_base64url_encode(r, &s_jwt_signature_encoded, (const char *)md, md_len, 1); char *s_jwt = apr_psprintf(r->pool, "%s.%s.%s", s_jwt_header_encoded, s_jwt_payload_encoded, s_jwt_signature_encoded); TST_ASSERT_ERR("oidc_jwt_parse", oidc_jwt_parse(r->pool, s_jwt, &jwt, NULL, FALSE, &err), r->pool, err); oidc_jwk_t *jwk = NULL; TST_ASSERT_ERR("oidc_util_create_symmetric_key", oidc_util_create_symmetric_key(r, s_secret, 0, NULL, TRUE, &jwk) == TRUE, r->pool, err); TST_ASSERT_ERR("oidc_util_create_symmetric_key (jwk)", jwk != NULL, r->pool, err); TST_ASSERT_ERR("oidc_jwt_verify", oidc_jwt_verify(r->pool, jwt, oidc_util_merge_symmetric_key(r->pool, NULL, jwk), &err), r->pool, err); TST_ASSERT_ERR("oidc_proto_validate_jwt", oidc_proto_jwt_validate(r, jwt, s_issuer, TRUE, TRUE, 10), r->pool, err); oidc_jwk_destroy(jwk); oidc_jwt_destroy(jwt); return 0; } static char *test_current_url(request_rec *r) { char *url = NULL; r->uri = "/test"; r->unparsed_uri = apr_pstrcat(r->pool, r->uri, "?", r->args, NULL); url = oidc_util_current_url(r, 0); TST_ASSERT_STR("test_current_url (1)", url, "https://www.example.com/test?foo=bar¶m1=value1"); apr_table_set(r->headers_in, "X-Forwarded-Host", "www.outer.com"); url = oidc_util_current_url(r, 0); TST_ASSERT_STR("test_current_url (2a)", url, "https://www.example.com/test?foo=bar¶m1=value1"); url = oidc_util_current_url(r, OIDC_HDR_X_FORWARDED_HOST); TST_ASSERT_STR("test_current_url (2b)", url, "https://www.outer.com/test?foo=bar¶m1=value1"); apr_table_set(r->headers_in, "X-Forwarded-Host", "www.outer.com:654"); url = oidc_util_current_url(r, OIDC_HDR_X_FORWARDED_HOST); TST_ASSERT_STR("test_current_url (3)", url, "https://www.outer.com:654/test?foo=bar¶m1=value1"); apr_table_set(r->headers_in, "X-Forwarded-Port", "321"); url = oidc_util_current_url(r, 0); TST_ASSERT_STR("test_current_url (4a)", url, "https://www.example.com/test?foo=bar¶m1=value1"); url = oidc_util_current_url(r, OIDC_HDR_X_FORWARDED_HOST); TST_ASSERT_STR("test_current_url (4b)", url, "https://www.outer.com:654/test?foo=bar¶m1=value1"); url = oidc_util_current_url(r, OIDC_HDR_X_FORWARDED_HOST | OIDC_HDR_X_FORWARDED_PORT); TST_ASSERT_STR("test_current_url (4)", url, "https://www.outer.com:321/test?foo=bar¶m1=value1"); apr_table_set(r->headers_in, "X-Forwarded-Proto", "http"); url = oidc_util_current_url(r, 0); TST_ASSERT_STR("test_current_url (5a)", url, "https://www.example.com/test?foo=bar¶m1=value1"); url = oidc_util_current_url(r, OIDC_HDR_X_FORWARDED_HOST); TST_ASSERT_STR("test_current_url (5b)", url, "https://www.outer.com:654/test?foo=bar¶m1=value1"); url = oidc_util_current_url(r, OIDC_HDR_X_FORWARDED_HOST | OIDC_HDR_X_FORWARDED_PORT); TST_ASSERT_STR("test_current_url (5c)", url, "https://www.outer.com:321/test?foo=bar¶m1=value1"); url = oidc_util_current_url(r, OIDC_HDR_X_FORWARDED_HOST | OIDC_HDR_X_FORWARDED_PORT | OIDC_HDR_X_FORWARDED_PROTO); TST_ASSERT_STR("test_current_url (5d)", url, "http://www.outer.com:321/test?foo=bar¶m1=value1"); apr_table_set(r->headers_in, "X-Forwarded-Proto", "https , http"); url = oidc_util_current_url(r, OIDC_HDR_X_FORWARDED_HOST | OIDC_HDR_X_FORWARDED_PORT | OIDC_HDR_X_FORWARDED_PROTO); TST_ASSERT_STR("test_current_url (6)", url, "https://www.outer.com:321/test?foo=bar¶m1=value1"); apr_table_unset(r->headers_in, "X-Forwarded-Host"); apr_table_unset(r->headers_in, "X-Forwarded-Port"); url = oidc_util_current_url(r, OIDC_HDR_X_FORWARDED_PROTO); TST_ASSERT_STR("test_current_url (7)", url, "https://www.example.com/test?foo=bar¶m1=value1"); apr_table_set(r->headers_in, "X-Forwarded-Proto", "http "); apr_table_set(r->headers_in, "Host", "remotehost:8380"); r->uri = "http://remotehost:8380/private/"; url = oidc_util_current_url(r, OIDC_HDR_X_FORWARDED_PROTO); TST_ASSERT_STR("test_current_url (8)", url, "http://remotehost:8380/private/?foo=bar¶m1=value1"); apr_table_set(r->headers_in, "Host", "[fd04:41b1:1170:28:16b0:446b:9fb7:7118]:8380"); url = oidc_util_current_url(r, OIDC_HDR_X_FORWARDED_PROTO); TST_ASSERT_STR("test_current_url (9)", url, "http://[fd04:41b1:1170:28:16b0:446b:9fb7:7118]:8380/private/?foo=bar¶m1=value1"); apr_table_set(r->headers_in, "Host", "[fd04:41b1:1170:28:16b0:446b:9fb7:7118]"); url = oidc_util_current_url(r, OIDC_HDR_X_FORWARDED_PROTO); TST_ASSERT_STR("test_current_url (10)", url, "http://[fd04:41b1:1170:28:16b0:446b:9fb7:7118]/private/?foo=bar¶m1=value1"); apr_table_unset(r->headers_in, "X-Forwarded-Proto"); apr_table_unset(r->headers_in, "Host"); apr_table_set(r->headers_in, "Forwarded", "host=www.outer.com"); url = oidc_util_current_url(r, OIDC_HDR_FORWARDED); TST_ASSERT_STR("test_current_url (11)", url, "https://www.outer.com/private/?foo=bar¶m1=value1"); apr_table_set(r->headers_in, "Forwarded", "proto=http"); url = oidc_util_current_url(r, OIDC_HDR_FORWARDED); TST_ASSERT_STR("test_current_url (12)", url, "http://www.example.com/private/?foo=bar¶m1=value1"); apr_table_set(r->headers_in, "Forwarded", "host=www.outer.com:8443"); url = oidc_util_current_url(r, OIDC_HDR_FORWARDED); TST_ASSERT_STR("test_current_url (13)", url, "https://www.outer.com:8443/private/?foo=bar¶m1=value1"); apr_table_set(r->headers_in, "Forwarded", "proto=http; host=www.outer.com:8080"); url = oidc_util_current_url(r, OIDC_HDR_FORWARDED); TST_ASSERT_STR("test_current_url (14)", url, "http://www.outer.com:8080/private/?foo=bar¶m1=value1"); apr_table_set(r->headers_in, "Forwarded", "host=www.outer.com:8080; proto=http"); url = oidc_util_current_url(r, OIDC_HDR_FORWARDED); TST_ASSERT_STR("test_current_url (15)", url, "http://www.outer.com:8080/private/?foo=bar¶m1=value1"); apr_table_unset(r->headers_in, "Forwarded"); // it should not crash when Forwarded is not present url = oidc_util_current_url(r, OIDC_HDR_FORWARDED); TST_ASSERT_STR("test_current_url (16)", url, "https://www.example.com/private/?foo=bar¶m1=value1"); apr_table_set(r->headers_in, "Host", "www.example.com"); return 0; } static char *test_accept(request_rec *r) { // ie 9/10/11 apr_table_set(r->headers_in, "Accept", "text/html, application/xhtml+xml, */*"); TST_ASSERT("Accept: text/html (ie 9/10/11)", oidc_http_hdr_in_accept_contains(r, "text/html") != 0); TST_ASSERT("Accept: application/json (ie 9/10/11)", oidc_http_hdr_in_accept_contains(r, "application/json") == 0); // firefox apr_table_set(r->headers_in, "Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"); TST_ASSERT("Accept: text/html (firefox)", oidc_http_hdr_in_accept_contains(r, "text/html") != 0); TST_ASSERT("Accept: application/json (firefox)", oidc_http_hdr_in_accept_contains(r, "application/json") == 0); // chrome/safari apr_table_set(r->headers_in, "Accept", "application/xml,application/xhtml+xml,text/html;q=0.9, text/plain;q=0.8,image/png,*/*;q=0.5"); TST_ASSERT("Accept: text/html (chrome/safari)", oidc_http_hdr_in_accept_contains(r, "text/html") != 0); TST_ASSERT("Accept: application/json (chrome/safari)", oidc_http_hdr_in_accept_contains(r, "application/json") == 0); // safari 5 apr_table_set(r->headers_in, "Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"); TST_ASSERT("Accept: text/html (safari 5)", oidc_http_hdr_in_accept_contains(r, "text/html") != 0); TST_ASSERT("Accept: application/json (safari 5)", oidc_http_hdr_in_accept_contains(r, "application/json") == 0); // ie 8 apr_table_set(r->headers_in, "Accept", "image/jpeg, application/x-ms-application, image/gif, application/xaml+xml, image/pjpeg, " "application/x-ms-xbap, application/x-shockwave-flash, application/msword, */*"); TST_ASSERT("Accept: text/html (ie 8)", oidc_http_hdr_in_accept_contains(r, "text/html") == 0); TST_ASSERT("Accept: */* (ie 8)", oidc_http_hdr_in_accept_contains(r, "*/*") != 0); TST_ASSERT("Accept: application/json (ie 8)", oidc_http_hdr_in_accept_contains(r, "application/json") == 0); // edge apr_table_set(r->headers_in, "Accept", "text/html, application/xhtml+xml, image/jxr, */*"); TST_ASSERT("Accept: text/html (edge)", oidc_http_hdr_in_accept_contains(r, "text/html") != 0); TST_ASSERT("Accept: application/json (edge)", oidc_http_hdr_in_accept_contains(r, "application/json") == 0); // opera apr_table_set(r->headers_in, "Accept", "text/html, application/xml;q=0.9, application/xhtml+xml, image/png, image/webp, image/jpeg, " "image/gif, image/x-xbitmap, */*;q=0.1"); TST_ASSERT("Accept: text/html (opera)", oidc_http_hdr_in_accept_contains(r, "text/html") != 0); TST_ASSERT("Accept: application/json (opera)", oidc_http_hdr_in_accept_contains(r, "application/json") == 0); // xmlhttprequest apr_table_set(r->headers_in, "Accept", "application/json"); TST_ASSERT("Accept: text/html (opera)", oidc_http_hdr_in_accept_contains(r, "text/html") == 0); TST_ASSERT("Accept: application/json (opera)", oidc_http_hdr_in_accept_contains(r, "application/json") != 0); return 0; } #if HAVE_APACHE_24 static char *test_authz_worker(request_rec *r) { authz_status rc; char *require_args = NULL; ap_expr_info_t *parsed_require_args = (ap_expr_info_t *)apr_pcalloc(r->pool, sizeof(ap_expr_info_t)); ; json_error_t err; json_t *json = NULL; char *claims = NULL; r->user = "dummy"; // clang-format off claims = "{" "\"sub\": \"stef\"," "\"areal\": 1.1," "\"anull\": null," "\"anint\": 99," "\"anegativeint\": -99," "\"aminusoneint\": -1," "\"nested\": {" "\"level1\": {" "\"level2\": \"hans\"" "}," "\"nestedarray\": [" "\"b\"," "\"c\"," "true," "\"false\"," "[" "\"d\"," "\"e\"" "]" "]," "\"somebool\": false" "}," "\"somearray\": [" "\"one\"," "\"two\"," "\"three\"" "]," "\"somebool\": false," "\"realm_access\": {" "\"roles\": [" "\"someRole1\"," "\"someRole2\"" "]" "}," "\"resource_access\": {" "\"someClient\": {" "\"roles\": [" "\"someRole3\"," "\"someRole4\"" "]" "}" "}," "\"https://test.com/pay\": \"alot\"," "\"https://company.com/productAccess\": [" "\"snake2\"," "\"snake2ref\"," "\"fxt\"" "]" "}" ; // clang-format on json = json_loads(claims, 0, &err); TST_ASSERT(apr_psprintf(r->pool, "JSON parsed [%s]", json ? "ok" : err.text), json != NULL); require_args = "Require claim sub:hans"; parsed_require_args->filename = require_args; rc = oidc_authz_24_worker(r, json, require_args, parsed_require_args, oidc_authz_match_claim); TST_ASSERT("auth status (1: simple sub claim)", rc == AUTHZ_DENIED); require_args = "Require claim sub:stef"; parsed_require_args->filename = require_args; rc = oidc_authz_24_worker(r, json, require_args, parsed_require_args, oidc_authz_match_claim); TST_ASSERT("auth status (2: simple sub claim)", rc == AUTHZ_GRANTED); require_args = "Require claim nested.level1.level2:hans"; parsed_require_args->filename = require_args; rc = oidc_authz_24_worker(r, json, require_args, parsed_require_args, oidc_authz_match_claim); TST_ASSERT("auth status (3: nested claim)", rc == AUTHZ_GRANTED); require_args = "Require claim nested.nestedarray:a"; parsed_require_args->filename = require_args; rc = oidc_authz_24_worker(r, json, require_args, parsed_require_args, oidc_authz_match_claim); TST_ASSERT("auth status (4: nested array)", rc == AUTHZ_DENIED); require_args = "Require claim nested.nestedarray:c"; parsed_require_args->filename = require_args; rc = oidc_authz_24_worker(r, json, require_args, parsed_require_args, oidc_authz_match_claim); TST_ASSERT("auth status (5: nested array)", rc == AUTHZ_GRANTED); require_args = "Require claim nested.level1:a"; parsed_require_args->filename = require_args; rc = oidc_authz_24_worker(r, json, require_args, parsed_require_args, oidc_authz_match_claim); TST_ASSERT("auth status (6: nested non-string)", rc == AUTHZ_DENIED); require_args = "Require claim somebool:a"; parsed_require_args->filename = require_args; rc = oidc_authz_24_worker(r, json, require_args, parsed_require_args, oidc_authz_match_claim); TST_ASSERT("auth status (7: non-array)", rc == AUTHZ_DENIED); require_args = "Require claim somebool.level1:a"; parsed_require_args->filename = require_args; rc = oidc_authz_24_worker(r, json, require_args, parsed_require_args, oidc_authz_match_claim); TST_ASSERT("auth status (8: nested non-array)", rc == AUTHZ_DENIED); require_args = "Require claim realm_access.roles:someRole1"; parsed_require_args->filename = require_args; rc = oidc_authz_24_worker(r, json, require_args, parsed_require_args, oidc_authz_match_claim); TST_ASSERT("auth status (9: keycloak sample 1)", rc == AUTHZ_GRANTED); require_args = "Require claim resource_access.someClient.roles:someRole4"; parsed_require_args->filename = require_args; rc = oidc_authz_24_worker(r, json, require_args, parsed_require_args, oidc_authz_match_claim); TST_ASSERT("auth status (10: keycloak sample 2)", rc == AUTHZ_GRANTED); require_args = "Require claim https://test.com/pay:alot"; parsed_require_args->filename = require_args; rc = oidc_authz_24_worker(r, json, require_args, parsed_require_args, oidc_authz_match_claim); TST_ASSERT("auth status (11: namespaced key)", rc == AUTHZ_GRANTED); require_args = "Require claim nested.level1.level2~.an."; parsed_require_args->filename = require_args; rc = oidc_authz_24_worker(r, json, require_args, parsed_require_args, oidc_authz_match_claim); TST_ASSERT("auth status (12: nested pcre expression)", rc == AUTHZ_GRANTED); require_args = "Require claim nested.level1.level2~zan."; parsed_require_args->filename = require_args; rc = oidc_authz_24_worker(r, json, require_args, parsed_require_args, oidc_authz_match_claim); TST_ASSERT("auth status (13: nested pcre expression)", rc == AUTHZ_DENIED); require_args = "Require claim nested.nestedarray~."; parsed_require_args->filename = require_args; rc = oidc_authz_24_worker(r, json, require_args, parsed_require_args, oidc_authz_match_claim); TST_ASSERT("auth status (14: nested array pcre expression)", rc == AUTHZ_GRANTED); require_args = "Require claim nested.nestedarray~.b"; parsed_require_args->filename = require_args; rc = oidc_authz_24_worker(r, json, require_args, parsed_require_args, oidc_authz_match_claim); TST_ASSERT("auth status (15: nested array pcre expression)", rc == AUTHZ_DENIED); require_args = "Require claim email~...$"; parsed_require_args->filename = require_args; rc = oidc_authz_24_worker(r, json, require_args, parsed_require_args, oidc_authz_match_claim); TST_ASSERT("auth status (16: pcre expression)", rc == AUTHZ_DENIED); require_args = "Require claim sub~...$"; parsed_require_args->filename = require_args; rc = oidc_authz_24_worker(r, json, require_args, parsed_require_args, oidc_authz_match_claim); TST_ASSERT("auth status (17: pcre expression)", rc == AUTHZ_GRANTED); require_args = "Require claim https://company.com/productAccess:snake2"; parsed_require_args->filename = require_args; rc = oidc_authz_24_worker(r, json, require_args, parsed_require_args, oidc_authz_match_claim); TST_ASSERT("auth status (18: key in namespaced array)", rc == AUTHZ_GRANTED); require_args = "Require claim areal:1.1"; parsed_require_args->filename = require_args; rc = oidc_authz_24_worker(r, json, require_args, parsed_require_args, oidc_authz_match_claim); TST_ASSERT("auth status (19: simple real claim)", rc == AUTHZ_GRANTED); require_args = "Require claim anull:null"; parsed_require_args->filename = require_args; rc = oidc_authz_24_worker(r, json, require_args, parsed_require_args, oidc_authz_match_claim); TST_ASSERT("auth status (20: simple null claim)", rc == AUTHZ_GRANTED); require_args = "Require claim areal:null"; parsed_require_args->filename = require_args; rc = oidc_authz_24_worker(r, json, require_args, parsed_require_args, oidc_authz_match_claim); TST_ASSERT("auth status (21: simple not null claim)", rc == AUTHZ_DENIED); require_args = "Require claim anint:99"; parsed_require_args->filename = require_args; rc = oidc_authz_24_worker(r, json, require_args, parsed_require_args, oidc_authz_match_claim); TST_ASSERT("auth status (22: simple int claim)", rc == AUTHZ_GRANTED); require_args = "Require claim anint:100"; parsed_require_args->filename = require_args; rc = oidc_authz_24_worker(r, json, require_args, parsed_require_args, oidc_authz_match_claim); TST_ASSERT("auth status (23: simple int claim)", rc == AUTHZ_DENIED); require_args = "Require claim anegativeint:-99"; parsed_require_args->filename = require_args; rc = oidc_authz_24_worker(r, json, require_args, parsed_require_args, oidc_authz_match_claim); TST_ASSERT("auth status (24: simple negative int claim)", rc == AUTHZ_GRANTED); require_args = "Require claim anegativeint:$99"; parsed_require_args->filename = require_args; rc = oidc_authz_24_worker(r, json, require_args, parsed_require_args, oidc_authz_match_claim); TST_ASSERT("auth status (25: simple int parse error claim)", rc == AUTHZ_DENIED); require_args = "Require claim aminusoneint:-1"; parsed_require_args->filename = require_args; rc = oidc_authz_24_worker(r, json, require_args, parsed_require_args, oidc_authz_match_claim); TST_ASSERT("auth status (26: simple -1 int claim)", rc == AUTHZ_GRANTED); json_decref(json); return 0; } #endif static char *test_decode_json_object(request_rec *r) { apr_byte_t rc = FALSE; json_t *json = NULL; rc = oidc_util_decode_json_object(r, "nojson", &json); TST_ASSERT("test invalid JSON", rc == FALSE); rc = oidc_util_decode_json_object(r, "{ \"n\": \"\\u0000\"}", &json); TST_ASSERT("test JSON with NULL value", rc == FALSE); rc = oidc_util_decode_json_object( r, "tmjcbnuvyrtygbtbyizkfuabiddgixcvnvupjuwnvxznpspmjaqrlpgmggixxovrpwntkvsvxjtkjjggnevyfyemdrlxtnmzjstmjuyquy" "yjzzwsfrazgzbdojkcfaeiqawltqsiwwzzgpiikpqoxixhsqtnfbchrcgxbgiaynkscvbvfnpuddrpjbgdtxxlebrswrtukzxqyyfrmwrr" "tfhcxhjfdoswjzvcchlufkqdaiqakvhyssegikcdkxvqxjrxukllhjduhokudtmhkqhqjheedxnlpbtybpwwogynneilkyffixdchdcjop" "xtdnhgsinwwktpqxfhmlfwucbtlbojaatocwqxsivdwwxrscsviwtllrqakzyogvseiackzzkkioactssxcglqqgavcpxmfokufechkdjk" "wbvdcnyboqbinbitixuqxeafdhrzndljsnqdlvxwvzggltmelmdcfouthhhzjuehfoejfyjvrpakmgmhaigkidmpmjtwrgezmwyvkgirhk" "xwrakbaizldjrcjbwieewdnxmrlgcmnuvhidvkqdokguvphmzywgqgfuwshouxcadbtkoxesyoqikuotloiowjpvztunkguyyrnbmsnwbp" "ghharmvkoqjjoanbejbwdlewiebwkzsuxpxozqzteuozboxdaukiwbqduhdhdlgiewmgeeqwtgyvhexcsdthriprrxvpuqvlcgqlldpnjd" "tbcuxncoikjxxistytsdlzdmtevmxhoafdcwqqixzxnxnrcoqlkwosdxsktgdejqsopuzaqourcixwktuwjzqagtdjheqgkpeavaoxpech" "kkdxcrwdtuxvdwpyjgjtsgppnoudyjyslwzxcqtbeqdtppeoelefkumpaamkxjnmdbuzotdzjrwzjspgabxczvxiuogvtmkmvxitdezsrq" "skorsybwndftfoqrylgsnhetyksfjdctmmarhtxpjukhwxhzjorxgnbpskdzgmskiumcsyquuwknrmkvdxhwgztbbbgiogeynlertjprol" "kavghiatjaddwlacrlibdnbbhykaqepwujqkmylnnqcfmxqanfvxgmitmsgwotolcwqrxgaftsryrmvlnabnouwqubtocwhkerhvsyzgir" "kitehbpcvitmjndqwerqopsdhwenihhkybemtmrafhcqbbfrfhemzvklvzxykkczjdokblktoxknolzxgblsjplggysbutvddygjgaqntc" "avbopwanbdvijawdwuepvzcaoesbkdaaqevbaupyokttlgcplpfijaubzphwutmslimkdxtrgwrfhhvdygznabvhvfhhpuuzwkhaxurnnq" "rmrhmddkckxzrmzxavvfpyuagjfyjorlfwprlpmwyjbtyzidvabtjckfqesnumqddnrdkunxljarqxcokikkqvrwxfedfumbadugcvzigb" "dulrcmzihpepztbplwrcvunyrhlwdutawmroajvomwtnbntcdgeqnyjxqlgjcdkaxlezmrldewjvyfjljeskmebuepmbzlhludyzwfnjom" "fnbgdcseqvqdlxrsuyyakdfsssiobahylumrwzsztpwxvfxyqfhtubamboeykqjltmpdjzhrsdjfebwjhlsyrafmvhefyddxhkchaqxptr" "qewevrnqftswhhxezdxyowtstejeexrgiujxbxvrvukotxdtlbgdpmorbaglozcmkzyrgnsjauxybuwvozcatacoyrvuxpiqkppoiogpui" "ykdpxkefxvqwutsbdeerbhztiidxqxsahcbbbrhlqwwnstpsefwtunismgvhbcpholzedkivglyjoperxpfavaxqohcxrcmnfvlzqtflrs" "lurdbxnyoswimovpteleifarnhrivasnxetxnwyumoiwomilmnmpnpfekpwiwpmlqumkfagjbxpswraxctsyvsfspoesfhfdifcexfknbb" "szjfznpwkftkescltnmmodctjmkwdlrgorauknioeqexwwqdkdhdielfewwenuhsosehfksfaybktumfaprcjgvgenfdffbgduawryrvlb" "phdjsryjzpspymqhflowqzvesljaasfjvheeqikfocyxuopsgsojcqmyfkwkspnspjewmsxljvezucnpuobfqblakqwbixswbbtuboneix" "djhdbzdckkcarsswtutwhkjijwdtsyjirdcibtavigjnehntnpdpdugwryxybsqoboayifiwxyuiqlbjksjpuhwdglymyisjdhkovwexkr" "oooorosiudclbrldesiolbrbeusyrpuuypioziymwdguquitxuiaxeayabeccbrislwinxxyumylbncyshsywkzdyqoaqtwbulnseyngho" "omiypzefykoexnzhwdcqoctptqabzbuiwxyahaptodtufofabylxtlggldckhuicuqdbueuuoknehkkuypnjwqfhocshmofgbmokiuxvcd" "onwuwqbjxgdukuubswwvxxqugjxnngnbnvtcophhamvbcuxdfkqubfiftrtfyljjulmansvnhnaoczrmxlqokbovddmpydamzicfzkmwcg" "ecljucaezvhrcssupxkleiximldarmtszakjuficulnaefldyogmnavrfscsptejaexghcrqtnluzmxvxrixxgjtyujoruzzzrfgtymtfs" "dyrmhvvggyeeykdaglptsuqmqbknimmxnuftjjolcvhpdumqehmyevqlsotrsldgedxmuablgarjfwoqyuakqxbqewdlsfkamxrteaebqw" "wcsupefoshtwlghkqtqjddikfrwjilzuqfwpcxalauddfotnwlsdwpxynlqjpfcuodeyubenztxsjwxycrdhlluauutfrvgqzacvnfawqi" "bftocxrksltmjvlnyjfeagoxpcepioattazmuuklhjxhpbughkmyjdgrxwqwdafgbnerlhlngeqojhjuvwwmqdradedjuugcqzkfozddct" "ctckaqoruksjsqjpzdbvmocutxpnacbkihboujejmjdhorkpyzubhaxpksjfpzmwauwxkwyjisfsqdjdkvsifgxjygclbdtzultcejefye" "phhhgbazkqqdvhtkkllyopdcsnbqjcpyvienhqqkwyxfvjrgsymuxxsejqerumcldjuitavdgogcsvjgbwllaijavuaeqxvndvzyrlmmzr" "hijywctihqizqmjfosrddqqdyilnfzyvwkkqgnnhlajqgdsnhhzpphfjtkeafxlgaarcfdapicfmukyyzvbgoatibulenkkwtyrnzgbmcy" "tazrieabuerwkxoffzuohdhqqhfbxqmqestunudduaywtrmdmbvidyvterebgvxwbhlbsqmghcktujvhfwmhralmodiywvyzvurzghwcpx" "qtzwmhnderhpognxnynmrfraklflrgppszmuxtwddrkzvqyvvmhlwcnzmspekcwhphabtmzdgvfyrvdzimpxbrkjbntiixkgxhepzqrugn" "mjyfbkcacbdgxbhraauoagihygwkiikuyximjjdnvslnfaouofwgdacnndhramvxazuzksploibonvneeixkykpwjmrlwkvbesxaklqkoa" "ulskwdfstelqxyyrpvnzkjcmhvxbjvbmrgdoyiwlzubaovirtciwcptmrdggpcgtxptkfwjsnhbxprqjiezncmmypjfbgljzrawwdikhoa" "qggoizoixpnykwyotofdrduvgfcwxvzjuacxolorrfpunnkzltgbdkztiwjctjedtupmckbjajwcjnkbmywilylfhckksaowsbvhnktfek" "laekpflbtsqpyxhrcwmjjgnqtmoumvcswredhtexnaojzjagrvwcieizjfvvmzzxmzwwvqthmrqvtviuyiqffjdpqmeknhwylmteliysia" "enkhkiuojxdwscvtacbwfixhrcaxlfeakidxgrmgitrmrzdzhwjyazzikrclajgksENDxxxx", &json); TST_ASSERT("test invalid long JSON", rc == FALSE); rc = oidc_util_decode_json_object(r, "{}", &json); TST_ASSERT("test valid JSON", rc == TRUE); json_decref(json); return 0; } static char *test_remote_user(request_rec *r) { apr_byte_t rc = FALSE; char *remote_user = NULL; char *s = NULL; json_t *json = NULL; s = "{\"upn\":\"nneul@umsystem.edu\"}"; rc = oidc_util_decode_json_object(r, s, &json); TST_ASSERT("test remote user (1) valid JSON", rc == TRUE); rc = oidc_get_remote_user(r, "upn", "^(.*)@umsystem\\.edu", NULL, json, &remote_user); TST_ASSERT_STR("remote_user (0) string", remote_user, "nneul"); rc = oidc_get_remote_user(r, "upn", "^(.*)@umsystem\\.edu", "$1", json, &remote_user); TST_ASSERT("test remote user (1) function result", rc == TRUE); TST_ASSERT_STR("remote_user (1) string", remote_user, "nneul"); json_decref(json); s = "{\"email\":\"nneul@umsystem.edu\"}"; rc = oidc_util_decode_json_object(r, s, &json); TST_ASSERT("test remote user (2) valid JSON", rc == TRUE); rc = oidc_get_remote_user(r, "email", "^(.*)@([^.]+)\\..+$", "$2\\$1", json, &remote_user); TST_ASSERT("test remote user (2) function result", rc == TRUE); TST_ASSERT_STR("remote_user (2) string", remote_user, "umsystem\\nneul"); json_decref(json); s = "{ \"name\": \"Dominik František Bučík\" }"; rc = oidc_util_decode_json_object(r, s, &json); TST_ASSERT("test remote user (3) valid JSON", rc == TRUE); rc = oidc_get_remote_user(r, "name", "^(.*)$", "$1@test.com", json, &remote_user); TST_ASSERT("test remote user (3) function result", rc == TRUE); TST_ASSERT_STR("remote_user (3) string", remote_user, "Dominik František Bučík@test.com"); json_decref(json); s = "{ \"preferred_username\": \"dbucik\" }"; rc = oidc_util_decode_json_object(r, s, &json); TST_ASSERT("test remote user (4) valid JSON", rc == TRUE); rc = oidc_get_remote_user(r, "preferred_username", "^(.*)$", "$1@test.com", json, &remote_user); TST_ASSERT("test remote user (4) function result", rc == TRUE); TST_ASSERT_STR("remote_user (4) string", remote_user, "dbucik@test.com"); json_decref(json); return 0; } static char *test_is_auth_capable_request(request_rec *r) { apr_byte_t rc = FALSE; apr_table_set(r->headers_in, "Accept", "*/*"); rc = oidc_is_auth_capable_request(r); TST_ASSERT("test oidc_is_auth_capable_request (1)", rc == TRUE); apr_table_set(r->headers_in, "X-Requested-With", "XMLHttpRequest"); rc = oidc_is_auth_capable_request(r); TST_ASSERT("test oidc_is_auth_capable_request (2)", rc == FALSE); apr_table_unset(r->headers_in, "X-Requested-With"); apr_table_set(r->headers_in, "Sec-Fetch-Mode", "navigate"); rc = oidc_is_auth_capable_request(r); TST_ASSERT("test oidc_is_auth_capable_request (3)", rc == TRUE); apr_table_unset(r->headers_in, "Sec-Fetch-Mode"); apr_table_set(r->headers_in, "Sec-Fetch-Mode", "cors"); rc = oidc_is_auth_capable_request(r); TST_ASSERT("test oidc_is_auth_capable_request (4)", rc == FALSE); apr_table_unset(r->headers_in, "Sec-Fetch-Mode"); apr_table_set(r->headers_in, "Sec-Fetch-Dest", "iframe"); rc = oidc_is_auth_capable_request(r); TST_ASSERT("test oidc_is_auth_capable_request (5)", rc == FALSE); apr_table_unset(r->headers_in, "Sec-Fetch-Dest"); apr_table_set(r->headers_in, "Sec-Fetch-Dest", "image"); rc = oidc_is_auth_capable_request(r); TST_ASSERT("test oidc_is_auth_capable_request (6)", rc == FALSE); apr_table_unset(r->headers_in, "Sec-Fetch-Dest"); apr_table_set(r->headers_in, "Sec-Fetch-Dest", "document"); rc = oidc_is_auth_capable_request(r); TST_ASSERT("test oidc_is_auth_capable_request (7)", rc == TRUE); apr_table_unset(r->headers_in, "Sec-Fetch-Dest"); apr_table_set(r->headers_in, "Accept", "application/json"); rc = oidc_is_auth_capable_request(r); TST_ASSERT("test oidc_is_auth_capable_request (8)", rc == FALSE); apr_table_unset(r->headers_in, "Accept"); return 0; } #define TST_OPEN_REDIRECT(url, result) \ err_str = NULL; \ err_desc = NULL; \ rc = oidc_validate_redirect_url(r, c, url, TRUE, &err_str, &err_desc); \ msg = apr_psprintf(r->pool, "test validate_redirect_url (%s): %s: %s", url, err_str, err_desc); \ TST_ASSERT_BYTE(msg, rc, result); static char *test_open_redirect(request_rec *r) { apr_byte_t rc = FALSE; char *err_str = NULL; char *err_desc = NULL; const char *msg = NULL; const char *filename = NULL; char line_buf[8096]; apr_file_t *f; size_t line_s; char *ptr = line_buf; char *dir = getenv("srcdir") ? getenv("srcdir") : "."; // https://github.com/payloadbox/open-redirect-payload-list filename = apr_psprintf(r->pool, "%s/%s", dir, "/test/open-redirect-payload-list.txt"); oidc_cfg_t *c = ap_get_module_config(r->server->module_config, &auth_openidc_module); TST_OPEN_REDIRECT("https://www.example.com/somewhere", TRUE); TST_OPEN_REDIRECT("https://evil.example.com/somewhere", FALSE); apr_file_open(&f, filename, APR_READ, APR_OS_DEFAULT, r->pool); while (1) { if (apr_file_gets(line_buf, sizeof(line_buf), f) != APR_SUCCESS) break; line_s = _oidc_strlen(ptr); line_buf[--line_s] = '\0'; TST_OPEN_REDIRECT(line_buf, FALSE); } apr_file_close(f); return 0; } static char *test_set_app_infos(request_rec *r) { apr_byte_t rc = FALSE; json_t *claims = NULL; rc = oidc_util_decode_json_object(r, "{" "\"simple\":\"hans\"," "\"name\": \"GÜnther\"," "\"dagger\": \"D†gger\"" "}", &claims); TST_ASSERT("valid JSON", rc == TRUE); oidc_util_set_app_infos(r, claims, "OIDC_CLAIM_", ",", OIDC_APPINFO_PASS_HEADERS, OIDC_APPINFO_ENCODING_NONE); TST_ASSERT_STR("header plain simple", apr_table_get(r->headers_in, "OIDC_CLAIM_simple"), "hans"); TST_ASSERT_STR("header plain name", apr_table_get(r->headers_in, "OIDC_CLAIM_name"), "G\u00DCnther"); TST_ASSERT_STR("header plain dagger", apr_table_get(r->headers_in, "OIDC_CLAIM_dagger"), "D\u2020gger"); oidc_util_set_app_infos(r, claims, "OIDC_CLAIM_", ",", OIDC_APPINFO_PASS_HEADERS, OIDC_APPINFO_ENCODING_BASE64URL); TST_ASSERT_STR("header base64url simple", apr_table_get(r->headers_in, "OIDC_CLAIM_simple"), "aGFucw"); TST_ASSERT_STR("header base64url name", apr_table_get(r->headers_in, "OIDC_CLAIM_name"), "R8OcbnRoZXI"); TST_ASSERT_STR("header base64url dagger", apr_table_get(r->headers_in, "OIDC_CLAIM_dagger"), "ROKAoGdnZXI"); oidc_util_set_app_infos(r, claims, "OIDC_CLAIM_", ",", OIDC_APPINFO_PASS_HEADERS, OIDC_APPINFO_ENCODING_LATIN1); TST_ASSERT_STR("header latin1 simple", apr_table_get(r->headers_in, "OIDC_CLAIM_simple"), "hans"); TST_ASSERT_STR("header latin1 name", apr_table_get(r->headers_in, "OIDC_CLAIM_name"), "G\xDCnther"); TST_ASSERT_STR("header latin1 dagger", apr_table_get(r->headers_in, "OIDC_CLAIM_dagger"), "D?gger"); json_decref(claims); return 0; } static char *test_check_cookie_domain(request_rec *r) { apr_byte_t rv = FALSE; oidc_cfg_t *c = ap_get_module_config(r->server->module_config, &auth_openidc_module); oidc_session_t *session = NULL; oidc_session_load(r, &session); oidc_session_set_cookie_domain(r, session, "ab001sb161djbn.xyz.com"); apr_table_set(r->headers_in, "Host", "ab001SB161djbn.xyz.com"); rv = oidc_check_cookie_domain(r, c, session); TST_ASSERT_BYTE("oidc_check_cookie_domain", rv, TRUE); rv = oidc_request_check_cookie_domain(r, c, "https://WWW.example.com/protected/index.html"); TST_ASSERT_BYTE("oidc_request_check_cookie_domain", rv, TRUE); c->cookie_domain = ".XYZ.com"; rv = oidc_request_check_cookie_domain(r, c, "https://ab001sb161djbn.xyz.com/protected/index.html"); TST_ASSERT_BYTE("oidc_request_check_cookie_domain", rv, TRUE); c->cookie_domain = "ab001SB161djbn.xyz.com"; rv = oidc_request_check_cookie_domain(r, c, "https://ab001sb161djbn.xyz.com/protected/index.html"); TST_ASSERT_BYTE("oidc_request_check_cookie_domain", rv, TRUE); c->cookie_domain = NULL; oidc_session_free(r, session); return 0; } static char *all_tests(apr_pool_t *pool, request_rec *r) { char *message; TST_RUN(test_private_key_parse, pool); TST_RUN(test_public_key_parse, pool); TST_RUN(test_jwt_parse, pool); TST_RUN(test_plaintext_jwt_parse, pool); TST_RUN(test_jwt_get_string, pool); TST_RUN(test_jwk_parse_json, pool); TST_RUN(test_jwk_copy, pool); TST_RUN(test_plaintext_decrypt_symmetric, pool); #if (OPENSSL_VERSION_NUMBER >= 0x1000100f) TST_RUN(test_jwt_decrypt_gcm, pool); #endif #if (OIDC_JOSE_EC_SUPPORT) TST_RUN(test_jwt_verify_ec, pool); #endif TST_RUN(test_jwt_verify_rsa, pool); TST_RUN(test_jwt_sign_verify, pool); TST_RUN(test_proto_validate_access_token, r); TST_RUN(test_proto_validate_code, r); TST_RUN(test_proto_authorization_request, r); TST_RUN(test_proto_validate_nonce, r); TST_RUN(test_proto_validate_jwt, r); TST_RUN(test_current_url, r); TST_RUN(test_accept, r); TST_RUN(test_decode_json_object, r); TST_RUN(test_remote_user, r); TST_RUN(test_is_auth_capable_request, r); TST_RUN(test_open_redirect, r); TST_RUN(test_set_app_infos, r); #if HAVE_APACHE_24 TST_RUN(test_authz_worker, r); #endif TST_RUN(test_logout_request, r); TST_RUN(test_check_cookie_domain, r); return 0; } static request_rec *test_setup(apr_pool_t *pool) { const unsigned int kIdx = 0; const unsigned int kEls = kIdx + 1; request_rec *request = (request_rec *)apr_pcalloc(pool, sizeof(request_rec)); request->pool = pool; request->subprocess_env = apr_table_make(request->pool, 0); request->headers_in = apr_table_make(request->pool, 0); request->headers_out = apr_table_make(request->pool, 0); request->err_headers_out = apr_table_make(request->pool, 0); apr_table_set(request->headers_in, "Host", "www.example.com"); apr_table_set(request->headers_in, "OIDC_foo", "some-value"); apr_table_set(request->headers_in, "Cookie", "foo=bar; " "mod_auth_openidc_session" "=0123456789abcdef; baz=zot"); request->server = apr_pcalloc(request->pool, sizeof(struct server_rec)); request->server->process = apr_pcalloc(request->pool, sizeof(struct process_rec)); request->server->process->pool = request->pool; request->server->process->pconf = request->pool; request->connection = apr_pcalloc(request->pool, sizeof(struct conn_rec)); request->connection->bucket_alloc = apr_bucket_alloc_create(request->pool); request->connection->local_addr = apr_pcalloc(request->pool, sizeof(apr_sockaddr_t)); apr_pool_userdata_set("https", "scheme", NULL, request->pool); request->server->server_hostname = "www.example.com"; request->connection->local_addr->port = 443; request->unparsed_uri = "/bla?foo=bar¶m1=value1"; request->args = "foo=bar¶m1=value1"; apr_uri_parse(request->pool, "https://www.example.com/bla?foo=bar¶m1=value1", &request->parsed_uri); auth_openidc_module.module_index = kIdx; oidc_cfg_t *cfg = oidc_cfg_server_create(request->pool, request->server); oidc_cfg_provider_issuer_set(pool, oidc_cfg_provider_get(cfg), "https://idp.example.com"); oidc_cfg_provider_authorization_endpoint_url_set(pool, oidc_cfg_provider_get(cfg), "https://idp.example.com/authorize"); oidc_cfg_provider_client_id_set(pool, oidc_cfg_provider_get(cfg), "client_id"); cfg->redirect_uri = "https://www.example.com/protected/"; oidc_dir_cfg_t *d_cfg = oidc_cfg_dir_config_create(request->pool, NULL); request->server->module_config = apr_pcalloc(request->pool, sizeof(void) * kEls); request->per_dir_config = apr_pcalloc(request->pool, sizeof(void) * kEls); ap_set_module_config(request->server->module_config, &auth_openidc_module, cfg); ap_set_module_config(request->per_dir_config, &auth_openidc_module, d_cfg); cfg->crypto_passphrase.secret1 = "12345678901234567890123456789012"; cfg->cache.impl = &oidc_cache_shm; cfg->cache.cfg = NULL; cfg->cache.shm_size_max = 500; cfg->cache.shm_entry_size_max = 16384 + 255 + 17; cfg->cache.encrypt = 1; if (cfg->cache.impl->post_config(request->server) != OK) { printf("cfg->cache.impl->post_config failed!\n"); exit(-1); } return request; } int main(int argc, char **argv, char **env) { if (apr_app_initialize(&argc, (const char *const **)argv, (const char *const **)env) != APR_SUCCESS) { printf("apr_app_initialize failed\n"); return -1; } oidc_pre_config_init(); apr_pool_t *pool = NULL; apr_pool_create(&pool, NULL); request_rec *r = test_setup(pool); char *result = all_tests(pool, r); if (result != 0) { printf("Failed: %s\n", result); } else { printf("All %d tests passed!\n", test_nr_run); } EVP_cleanup(); apr_pool_destroy(pool); apr_terminate(); return result != 0; }