pax_global_header00006660000000000000000000000064145562053350014522gustar00rootroot0000000000000052 comment=19232c2e4e7b65bb0bb0fe6dd2f43d10dcde7448 mod_auth_openidc-2.4.15.1/000077500000000000000000000000001455620533500152535ustar00rootroot00000000000000mod_auth_openidc-2.4.15.1/.clang-format000066400000000000000000000003061455620533500176250ustar00rootroot00000000000000BasedOnStyle: LLVM ColumnLimit: 120 IndentWidth: 8 UseTab: Always BreakBeforeBraces: Attach AllowShortIfStatementsOnASingleLine: false IndentCaseLabels: false AllowShortFunctionsOnASingleLine: None mod_auth_openidc-2.4.15.1/.dockerignore000066400000000000000000000006661455620533500177370ustar00rootroot00000000000000/.project /.cproject /aclocal.m4 /config.log /config.status /configure /Makefile /Makefile.in /autom4te.cache/ /.libs/ /m4/ /compile /config.guess /install-sh /libtool /ltmain.sh /*.la /config.sub /depcomp /missing /.vscode/ /configure~ /config.guess~ /config.sub~ /test-driver /test-suite.log /ar-lib /.github /.gitignore /test/test /test/*.lo /test/*.o /test/*.slo /test/.libs/ /test/test-cmd /test/.deps/ /test/.libs/ /test/.gitignore mod_auth_openidc-2.4.15.1/.github/000077500000000000000000000000001455620533500166135ustar00rootroot00000000000000mod_auth_openidc-2.4.15.1/.github/ISSUE_TEMPLATE/000077500000000000000000000000001455620533500207765ustar00rootroot00000000000000mod_auth_openidc-2.4.15.1/.github/ISSUE_TEMPLATE/config.yml000066400000000000000000000004131455620533500227640ustar00rootroot00000000000000blank_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.15.1/.github/workflows/000077500000000000000000000000001455620533500206505ustar00rootroot00000000000000mod_auth_openidc-2.4.15.1/.github/workflows/build.yml000066400000000000000000000011731455620533500224740ustar00rootroot00000000000000name: 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.15.1/.github/workflows/codeql-analysis.yml000066400000000000000000000020151455620533500244610ustar00rootroot00000000000000name: "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@v2 with: languages: ${{ matrix.language }} - run: | ./autogen.sh ./configure make check - name: Perform CodeQL Analysis uses: github/codeql-action/analyze@v2 mod_auth_openidc-2.4.15.1/.github/workflows/issues.yml000066400000000000000000000007721455620533500227140ustar00rootroot00000000000000on: 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.15.1/.github/workflows/scorecard.yml000066400000000000000000000056441455620533500233510ustar00rootroot00000000000000# This workflow uses actions that are not certified by GitHub. They are provided # by a third-party and are governed by separate terms of service, privacy # policy, and support documentation. name: Scorecard supply-chain security on: # For Branch-Protection check. Only the default branch is supported. See # https://github.com/ossf/scorecard/blob/main/docs/checks.md#branch-protection branch_protection_rule: # To guarantee Maintained check is occasionally updated. See # https://github.com/ossf/scorecard/blob/main/docs/checks.md#maintained schedule: - cron: '35 16 * * 6' push: branches: [ "master" ] # Declare default permissions as read only. permissions: read-all jobs: analysis: name: Scorecard analysis runs-on: ubuntu-latest permissions: # Needed to upload the results to code-scanning dashboard. security-events: write # Needed to publish results and get a badge (see publish_results below). id-token: write # Uncomment the permissions below if installing in a private repository. # contents: read # actions: read steps: - name: "Checkout code" uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # v3.1.0 with: persist-credentials: false - name: "Run analysis" uses: ossf/scorecard-action@e38b1902ae4f44df626f11ba0734b14fb91f8f86 # v2.1.2 with: results_file: results.sarif results_format: sarif # (Optional) "write" PAT token. Uncomment the `repo_token` line below if: # - you want to enable the Branch-Protection check on a *public* repository, or # - you are installing Scorecard on a *private* repository # To create the PAT, follow the steps in https://github.com/ossf/scorecard-action#authentication-with-pat. # repo_token: ${{ secrets.SCORECARD_TOKEN }} # Public repositories: # - Publish results to OpenSSF REST API for easy access by consumers # - Allows the repository to include the Scorecard badge. # - See https://github.com/ossf/scorecard-action#publishing-results. # For private repositories: # - `publish_results` will always be set to `false`, regardless # of the value entered here. publish_results: true # Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF # format to the repository Actions tab. - name: "Upload artifact" uses: actions/upload-artifact@3cea5372237819ed00197afe530f5a7ea3e805c8 # v3.1.0 with: name: SARIF file path: results.sarif retention-days: 5 # Upload the results to GitHub's code scanning dashboard. - name: "Upload to code-scanning" uses: github/codeql-action/upload-sarif@17573ee1cc1b9d061760f3a006fc4aac4f944fd5 # v2.2.4 with: sarif_file: results.sarif mod_auth_openidc-2.4.15.1/.gitignore000066400000000000000000000005671455620533500172530ustar00rootroot00000000000000/.project /.cproject /aclocal.m4 /config.log /config.status /configure /Makefile /discover /metadata /build/ /.libs/ /m4/ /compile /config.guess /install-sh /libtool /ltmain.sh /*.la /config.sub /depcomp /missing /.settings/ /.autotools /.vscode/ /configure~ /Dockerfile-* /*.rpm /config.guess~ /config.sub~ /test-driver /test-suite.log /ar-lib /Makefile.in /autom4te.cache/ mod_auth_openidc-2.4.15.1/AUTHORS000066400000000000000000000077261455620533500163370ustar00rootroot00000000000000The 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 mod_auth_openidc-2.4.15.1/ChangeLog000066400000000000000000002607501455620533500170370ustar00rootroot0000000000000001/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.15.1/INSTALL000066400000000000000000000037431455620533500163130ustar00rootroot00000000000000Preferably 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.15.1/LICENSE.txt000066400000000000000000000261361455620533500171060ustar00rootroot00000000000000 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.15.1/Makefile.am000066400000000000000000000045621455620533500173160ustar00rootroot00000000000000ACLOCAL_AMFLAGS=-I m4 noinst_LTLIBRARIES = libauth_openidc.la libauth_openidc_la_SOURCES = \ src/mod_auth_openidc.c \ src/cache/file.c \ src/cache/shm.c \ src/cache/common.c \ src/metrics.c \ src/oauth.c \ src/proto.c \ src/config.c \ src/util.c \ src/authz.c \ src/session.c \ src/metadata.c \ src/jose.c \ src/parse.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/mod_auth_openidc.h \ src/const.h \ src/jose.h \ src/parse.h \ src/metrics.h \ src/cache/cache.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 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]` mod_auth_openidc-2.4.15.1/README.md000066400000000000000000000214621455620533500165370ustar00rootroot00000000000000[![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) 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 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 an exhaustive description 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/#RPs) 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) - [OAuth 2.0 Multiple Response Type Encoding Practices 1.0](http://openid.net/specs/oauth-v2-multiple-response-types-1_0.html) - [OAuth 2.0 Form Post Response Mode 1.0](http://openid.net/specs/oauth-v2-form-post-response-mode-1_0.html) - [RFC7 7636 - Proof Key for Code Exchange by OAuth Public Clients](https://tools.ietf.org/html/rfc7636) - [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 support contracts, professional services, training and use-case specific support 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) - [Azure AD](https://github.com/OpenIDC/mod_auth_openidc/wiki/Azure-Active-Directory-Authentication) - [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, 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.15.1/SECURITY.md000066400000000000000000000032341455620533500170460ustar00rootroot00000000000000# 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.15.1/auth_openidc.conf000066400000000000000000002121371455620533500205720ustar00rootroot00000000000000######################################################################################## # # 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 JWK formatted # key that can be used to verify the provided JWKs value. # 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 # Example: # OIDCProviderSignedJwksUri https://localhost:9031/pf/JWKS "{\"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 used 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. # 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 # 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 settings 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 (eg. "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 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 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 default is 0, i.e. the claims are retrieved only 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 ] # 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 # 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 ] # 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 discovered 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 [plain|S256|none] # (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 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] ######################################################################################## # # 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 # 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 an 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 backrefrences. # Backrefrences 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 whether the SameSite flag will be set on cookies. # When On the following will apply: # state cookie: Lax # session cookie: first time set Lax, updates (e.g. after inactivity timeout) Strict # x_csrf discovery: Strict: # # The default `SameSite=None` 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. #OIDCCookieSameSite [On|Off] # Specify the names of cookies to pickup 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 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 support is compiled in and when using OIDCCacheType "redis": # Specifies the Redis server used for caching as a [:] tuple. #OIDCRedisCacheServer [:] # Password to be used if the Redis server requires authentication: http://redis.io/commands/auth # When not specified, no authentication is performed. #OIDCRedisCachePassword # Username to be used if the Redis server requires authentication: http://redis.io/commands/auth # NB: this can only used with Redis 6 (ACLs) or later # When not specified, the implicit user "default" is used #OIDCRedisCacheUsername # Logical database to select on the Redis server: https://redis.io/commands/select # When not defined the default database 0 is used. #OIDCRedisCacheDatabase # Timeout for connecting to the Redis servers. # When not defined the default connect timeout is 5 seconds. #OIDCRedisCacheConnectTimeout # Timeout waiting for a response of the Redis servers 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, a.o. `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 (eg. "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 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 postfixed with a \"@\", the claim value will be post-fixed with the # \"iss\" value 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 backrefrences. # Backrefrences 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 a 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 "" # 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 (ie. 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) toplevel 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 encodong 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 woult 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 verson 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 uncondtionally 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 stepup 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. # 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 formattted 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 expires 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 OIDCUnAuthzAction (per Require statement, not overall). # require.claim Match/failure count of Require claim directives (per Require statement, not overall). # 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 | 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 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 to not add (or overwrite) a traceparent header. #OIDCTraceParent 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 (or at tleast 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 with the OP. # This only has 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 with 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] # 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 taken into account 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 # When not defined, such headers will be ignored. #OIDCXForwardedHeaders
+ mod_auth_openidc-2.4.15.1/autogen.sh000077500000000000000000000000761455620533500172570ustar00rootroot00000000000000#!/bin/sh autoreconf --force --install rm -rf autom4te.cache/ mod_auth_openidc-2.4.15.1/configure.ac000066400000000000000000000127121455620533500175440ustar00rootroot00000000000000AC_INIT([mod_auth_openidc],[2.4.15.1],[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.15.1/src/000077500000000000000000000000001455620533500160425ustar00rootroot00000000000000mod_auth_openidc-2.4.15.1/src/.gitignore000066400000000000000000000001511455620533500200270ustar00rootroot00000000000000/*.lo /*.o /*.slo /*.la /.libs /.deps/ /.dirstamp /config.h /config.h.in /config.h.in~ /stamp-h1 /util.c mod_auth_openidc-2.4.15.1/src/authz.c000066400000000000000000000321611455620533500173440ustar00rootroot00000000000000/* * 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-2024 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. * * mostly copied from mod_auth_cas * * @Author: Hans Zandbelt - hans.zandbelt@openidc.com */ #include "mod_auth_openidc.h" #include "metrics.h" #include "pcre_subst.h" static apr_byte_t oidc_authz_match_value(request_rec *r, const char *spec_c, const json_t *val, const char *key) { const json_t *elem = NULL; int i = 0; oidc_debug(r, "matching: spec_c=%s, key=%s", spec_c, key); /* see if it is a string and it (case-insensitively) matches the Require'd value */ if (json_is_string(val)) { if (_oidc_strcmp(json_string_value(val), spec_c) == 0) return TRUE; /* see if it is a integer and it equals the Require'd value */ } else if (json_is_integer(val)) { if (json_integer_value(val) == _oidc_str_to_int(spec_c)) return TRUE; /* see if it is a boolean and it (case-insensitively) matches the Require'd value */ } else if (json_is_boolean(val)) { if (_oidc_strcmp((json_is_true(val) ? "true" : "false"), spec_c) == 0) return TRUE; /* if it is an array, we'll walk it */ } else if (json_is_array(val)) { /* compare the claim values */ for (i = 0; i < json_array_size(val); i++) { elem = json_array_get(val, i); if (json_is_string(elem)) { /* * approximately compare the claim value (ignoring * whitespace). At this point, spec_c points to the * NULL-terminated value pattern. */ if (_oidc_strcmp(json_string_value(elem), spec_c) == 0) return TRUE; } else if (json_is_boolean(elem)) { if (_oidc_strcmp((json_is_true(elem) ? "true" : "false"), spec_c) == 0) return TRUE; } else if (json_is_integer(elem)) { if (json_integer_value(elem) == _oidc_str_to_int(spec_c)) return TRUE; } else { oidc_warn(r, "unhandled in-array JSON object type [%d] for key \"%s\"", elem->type, (const char *)key); } } } else { oidc_warn(r, "unhandled JSON object type [%d] for key \"%s\"", val->type, (const char *)key); } return FALSE; } static apr_byte_t oidc_authz_match_expression(request_rec *r, const char *spec_c, const json_t *val) { apr_byte_t rc = FALSE; struct oidc_pcre *preg = NULL; char *error_str = NULL; int i = 0; /* setup the regex; spec_c points to the NULL-terminated value pattern */ preg = oidc_pcre_compile(r->pool, spec_c, &error_str); if (preg == NULL) { oidc_error(r, "pattern [%s] is not a valid regular expression: %s", spec_c, error_str); goto end; } /* see if the claim is a literal string */ if (json_is_string(val)) { error_str = NULL; /* PCRE-compare the string value against the expression */ if (oidc_pcre_exec(r->pool, preg, json_string_value(val), (int)_oidc_strlen(json_string_value(val)), &error_str) > 0) { oidc_debug(r, "value \"%s\" matched regex \"%s\"", json_string_value(val), spec_c); rc = TRUE; goto end; } else if (error_str) { oidc_debug(r, "pcre error (string): %s", error_str); } /* see if the claim value is an array */ } else if (json_is_array(val)) { /* compare the claim values in the array against the expression */ for (i = 0; i < json_array_size(val); i++) { json_t *elem = json_array_get(val, i); if (json_is_string(elem)) { error_str = NULL; /* PCRE-compare the string value against the expression */ if (oidc_pcre_exec(r->pool, preg, json_string_value(elem), (int)_oidc_strlen(json_string_value(elem)), &error_str) > 0) { oidc_debug(r, "array value \"%s\" matched regex \"%s\"", json_string_value(elem), spec_c); rc = TRUE; goto end; } else if (error_str) { oidc_pcre_free_match(preg); oidc_debug(r, "pcre error (array): %s", error_str); } } } } end: if (preg) oidc_pcre_free(preg); return rc; } /* * 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; json_t *val; /* 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 */ 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); const char *attr_c = key; const char *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++; } /* The match is a success if we walked the whole claim name and the attr_spec is at a colon. */ if (!(*attr_c) && (*spec_c) == OIDC_CHAR_COLON) { /* skip the colon */ spec_c++; if (oidc_authz_match_value(r, spec_c, val, key) == TRUE) return TRUE; /* a tilde denotes a string PCRE match */ } else if (!(*attr_c) && (*spec_c) == OIDC_CHAR_TILDE) { /* skip the tilde */ spec_c++; if (oidc_authz_match_expression(r, spec_c, val) == TRUE) return TRUE; /* dot means child nodes must be evaluated */ } else if (!(*attr_c) && (*spec_c) == OIDC_CHAR_DOT) { /* skip the dot */ spec_c++; if (json_is_object(val)) { oidc_debug(r, "attribute chunk matched, evaluating children of key: \"%s\".", key); return oidc_authz_match_claim(r, spec_c, json_object_get(claims, key)); } else if (json_is_array(val)) { oidc_debug(r, "attribute chunk matched, evaluating array values of key: \"%s\".", key); return oidc_authz_match_value(r, spec_c, json_object_get(claims, key), key); } else { oidc_warn(r, "\"%s\" matched, and child nodes or array values should be evaluated, but " "value is not an object or array.", key); return FALSE; } } iter = json_object_iter_next(claims, iter); } return FALSE; } #ifdef USE_LIBJQ /* * see if a the Require value matches a configured expression */ 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_object(r, 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 : "")); } } #if !(HAVE_APACHE_24) /* * Apache <2.4 authorization routine: match the claims from the authenticated user against the Require primitive */ int oidc_authz_worker22(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; } #else /* * Apache >=2.4 authorization routine: match the claims from the authenticated user against the Require primitive */ authz_status oidc_authz_worker24(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 *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_SPEC(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_SPEC(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; } #endif mod_auth_openidc-2.4.15.1/src/cache/000077500000000000000000000000001455620533500171055ustar00rootroot00000000000000mod_auth_openidc-2.4.15.1/src/cache/.gitignore000066400000000000000000000000671455620533500211000ustar00rootroot00000000000000/*.lo /*.o /*.slo /.libs /.deps/ /.dirstamp /hiredis-* mod_auth_openidc-2.4.15.1/src/cache/cache.h000066400000000000000000000176771455620533500203430ustar00rootroot00000000000000/* * 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-2024 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 #include #include 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); 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.15.1/src/cache/common.c000066400000000000000000000271411455620533500205460ustar00rootroot00000000000000/* * 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-2024 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 "mod_auth_openidc.h" #include "metrics.h" #ifdef AP_NEED_SET_MUTEX_PERMS #include "unixd.h" #endif extern module AP_MODULE_DECLARE_DATA auth_openidc_module; /* 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 */ apr_temp_dir_get(&dir, s->process->pool); 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 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 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 and a crypto passphrase so the result is suitable as an randomized cache key */ static char *oidc_cache_get_hashed_key(request_rec *r, const char *passphrase, const char *key) { const char *input = apr_psprintf(r->pool, "%s:%s", passphrase, key); char *output = NULL; if (oidc_util_hash_string_and_base64url_encode(r, OIDC_JOSE_ALG_SHA256, input, &output) == FALSE) { oidc_error(r, "oidc_util_hash_string_and_base64url_encode returned an error"); return NULL; } return output; } /* * 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 *cfg = ap_get_module_config(r->server->module_config, &auth_openidc_module); int encrypted = oidc_cfg_cache_encrypt(r); apr_byte_t rc = TRUE; char *msg = NULL; const char *s_key = NULL; char *cache_value = NULL, *s_secret = NULL; oidc_debug(r, "enter: %s (section=%s, decrypt=%d, type=%s)", section, section, encrypted, cfg->cache->name); s_key = key; /* see if encryption is turned on */ if (encrypted == 1) { if (cfg->crypto_passphrase.secret1 == NULL) { oidc_error(r, "could not decrypt cache entry because " OIDCCryptoPassphrase " is not set"); goto out; } s_secret = cfg->crypto_passphrase.secret1; s_key = oidc_cache_get_hashed_key(r, s_secret, key); } OIDC_METRICS_TIMING_START(r, cfg); /* get the value from the cache */ if (cfg->cache->get(r, section, s_key, &cache_value) == FALSE) { rc = FALSE; goto out; } OIDC_METRICS_TIMING_ADD(r, cfg, OM_CACHE_READ); /* 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; s_key = oidc_cache_get_hashed_key(r, s_secret, key); if (cfg->cache->get(r, section, s_key, &cache_value) == FALSE) { rc = FALSE; goto out; } } if (cache_value == NULL) goto out; /* see if encryption is turned on */ if (encrypted == 0) { *value = apr_pstrdup(r->pool, cache_value); goto out; } rc = oidc_cache_crypto_decrypt(r, cache_value, s_secret, value); out: /* log the result */ msg = apr_psprintf(r->pool, "from %s cache backend for %skey %s", cfg->cache->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 *cfg = ap_get_module_config(r->server->module_config, &auth_openidc_module); int encrypted = oidc_cfg_cache_encrypt(r); char *encoded = NULL; apr_byte_t rc = FALSE; char *msg = 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->name); /* see if we need to encrypt */ if (encrypted == 1) { if (cfg->crypto_passphrase.secret1 == NULL) { oidc_error(r, "could not encrypt cache entry because " OIDCCryptoPassphrase " is not set"); goto out; } key = oidc_cache_get_hashed_key(r, cfg->crypto_passphrase.secret1, key); if (key == NULL) goto out; if (value != NULL) { if (oidc_cache_crypto_encrypt(r, value, &cfg->crypto_passphrase, &encoded) == FALSE) goto out; value = encoded; } } OIDC_METRICS_TIMING_START(r, cfg); /* store the resulting value in the cache */ rc = cfg->cache->set(r, section, key, value, expiry); OIDC_METRICS_TIMING_ADD(r, cfg, OM_CACHE_WRITE); out: /* 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->name ? cfg->cache->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.15.1/src/cache/file.c000066400000000000000000000340721455620533500201760ustar00rootroot00000000000000/* * 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-2024 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 "mod_auth_openidc.h" extern module AP_MODULE_DECLARE_DATA auth_openidc_module; /* * 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) { oidc_cfg *cfg = (oidc_cfg *)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 */ apr_temp_dir_get((const char **)&cfg->cache_file_dir, s->process->pool); } 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_util_escape_string(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 *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, 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 *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 ((rc = 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(cfg->cache_file_clean_interval)) { oidc_debug( r, "last cleanup call was less than %d seconds ago (next one as early as in %" APR_TIME_T_FMT " secs)", cfg->cache_file_clean_interval, apr_time_sec(fi.mtime + apr_time_from_sec(cfg->cache_file_clean_interval) - 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) || (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_proto_generate_nonce(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 ((rc = oidc_cache_file_write(r, path, fd, &info, sizeof(oidc_cache_file_info_t))) != APR_SUCCESS) return FALSE; /* next write the value */ rc = oidc_cache_file_write(r, path, fd, (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); } oidc_cache_t oidc_cache_file = {"file", 1, oidc_cache_file_post_config, NULL, oidc_cache_file_get, oidc_cache_file_set, NULL}; mod_auth_openidc-2.4.15.1/src/cache/memcache.c000066400000000000000000000246121455620533500210200ustar00rootroot00000000000000/* * 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-2024 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 "mod_auth_openidc.h" #include #include #include extern module AP_MODULE_DECLARE_DATA auth_openidc_module; /* * 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 *cfg = (oidc_cfg *)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; int 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, minw, maxw; apr_uint32_t min, smax, hmax, ttl; if (cfg->cache_memcache_servers == 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, cfg->cache_memcache_servers); 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 = cfg->cache_memcache_min; smax = cfg->cache_memcache_smax; hmax = cfg->cache_memcache_hmax; ttl = cfg->cache_memcache_ttl; 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, cfg->cache_memcache_servers); 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(request_rec *r, oidc_cache_cfg_memcache_t *context) { int rc = TRUE; int i; for (i = 0; rc && i < context->cache_memcache->ntotal; i++) rc = rc && (context->cache_memcache->live_servers[0]->status != APR_MC_SERVER_DEAD); return rc; } /* * 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 *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(r, 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 *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_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); } oidc_cache_t oidc_cache_memcache = { "memcache", 1, oidc_cache_memcache_post_config, NULL, oidc_cache_memcache_get, oidc_cache_memcache_set, NULL}; mod_auth_openidc-2.4.15.1/src/cache/redis.c000066400000000000000000000347051455620533500203700ustar00rootroot00000000000000/* * 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-2024 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 "redis.h" // TODO: proper Redis error reporting (server unreachable etc.) extern module AP_MODULE_DECLARE_DATA auth_openidc_module; #define REDIS_CONNECT_TIMEOUT_DEFAULT 5 #define REDIS_TIMEOUT_DEFAULT 5 /* 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->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 *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 (cfg->cache_redis_database != -1) context->database = cfg->cache_redis_database; if (cfg->cache_redis_connect_timeout != -1) context->connect_timeout.tv_sec = cfg->cache_redis_connect_timeout; if (cfg->cache_redis_timeout != -1) context->timeout.tv_sec = cfg->cache_redis_timeout; 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) { if (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 *cfg = (oidc_cfg *)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 *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; /* 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; } } /* * connect to Redis server */ static apr_status_t oidc_cache_redis_connect(request_rec *r, oidc_cache_cfg_redis_t *context) { redisReply *reply = NULL; if (context->rctx != NULL) goto end; /* no connection, connect to the configured Redis server */ oidc_debug(r, "calling redisConnectWithTimeout"); context->rctx = redisConnectWithTimeout(context->host_str, context->port, context->connect_timeout); /* check for errors */ if ((context->rctx == NULL) || (context->rctx->err != 0)) { oidc_error(r, "failed to connect to Redis server (%s:%d): '%s'", context->host_str, context->port, context->rctx != NULL ? context->rctx->errstr : ""); context->disconnect(context); goto end; } /* log the connection */ oidc_debug(r, "successfully connected to Redis server (%s:%d)", context->host_str, context->port); if (redisSetTimeout(context->rctx, context->timeout) != REDIS_OK) oidc_error(r, "redisSetTimeout failed: %s", context->rctx->errstr); /* see if we need to authenticate to the Redis server */ if (context->passwd != NULL) { if (context->username != NULL) { reply = redisCommand(context->rctx, "AUTH %s %s", context->username, context->passwd); } else { reply = redisCommand(context->rctx, "AUTH %s", context->passwd); } if ((reply == NULL) || (reply->type == REDIS_REPLY_ERROR)) oidc_error(r, "Redis AUTH command (%s:%d) failed: '%s' [%s]", context->host_str, context->port, context->rctx->errstr, reply ? reply->str : ""); else oidc_debug(r, "successfully authenticated to the Redis server: %s", reply ? reply->str : ""); /* free the auth answer */ oidc_cache_redis_reply_free(&reply); } /* see if we need to set the database */ if (context->database != -1) { reply = redisCommand(context->rctx, "SELECT %d", context->database); if ((reply == NULL) || (reply->type == REDIS_REPLY_ERROR)) oidc_error(r, "Redis SELECT command (%s:%d) failed: '%s' [%s]", context->host_str, context->port, context->rctx->errstr, reply ? reply->str : ""); else oidc_debug(r, "successfully selected database %d on the Redis server: %s", context->database, reply ? reply->str : ""); /* free the database answer */ oidc_cache_redis_reply_free(&reply); } end: return (context->rctx != NULL) ? APR_SUCCESS : APR_EGENERAL; } 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; } #define OIDC_REDIS_MAX_TRIES_ENV_VAR "OIDC_REDIS_MAX_TRIES" #define OIDC_REDIS_MAX_TRIES_DEFAULT 2 static int oidc_cache_redis_tries(request_rec *r) { const char *s = r->subprocess_env ? apr_table_get(r->subprocess_env, OIDC_REDIS_MAX_TRIES_ENV_VAR) : NULL; return s ? _oidc_str_to_int(s) : OIDC_REDIS_MAX_TRIES_DEFAULT; } /* * 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; int i = 0; va_list ap; int n = oidc_cache_redis_tries(r); /* try to execute a command at max n times while reconnecting */ for (i = 1; i <= n; i++) { /* connect */ if (context->connect(r, context) != APR_SUCCESS) { if (i < n) { oidc_warn(r, "Redis connect (attempt=%d/%d to %s:%d) failed", i, n, context->host_str, context->port); apr_sleep(500); } else { oidc_error(r, "Redis connect (attempt=%d/%d to %s:%d) failed", i, n, context->host_str, context->port); } 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 */ if (i < n) oidc_warn(r, "Redis command (attempt=%d/%d to %s:%d) failed, disconnecting: '%s' [%s]", i, n, context->host_str, context->port, errstr, reply ? reply->str : ""); else oidc_error(r, "Redis command (attempt=%d/%d to %s:%d) failed, disconnecting: '%s' [%s]", i, n, 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 *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 *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_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 *cfg = (oidc_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; 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); oidc_cache_mutex_destroy(s, context->mutex); cfg->cache_cfg = NULL; } return APR_SUCCESS; } 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}; mod_auth_openidc-2.4.15.1/src/cache/redis.h000066400000000000000000000067431455620533500203760ustar00rootroot00000000000000/* * 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-2024 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 "hiredis/hiredis.h" #include "mod_auth_openidc.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; 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 *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); mod_auth_openidc-2.4.15.1/src/cache/shm.c000066400000000000000000000263371455620533500200530ustar00rootroot00000000000000/* * 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-2024 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 "mod_auth_openidc.h" extern module AP_MODULE_DECLARE_DATA auth_openidc_module; 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 512 /* 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 *cfg = (oidc_cfg *)ap_get_module_config(s->module_config, &auth_openidc_module); if (cfg->cache_cfg != NULL) return APR_SUCCESS; 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' */ int i; oidc_cache_shm_entry_t *t = apr_shm_baseaddr_get(context->shm); for (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 *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 *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 *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, *free, *lru; oidc_cache_shm_entry_t *t; apr_time_t current_time; int i; apr_time_t age; 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 (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_strcpy(t->section_key, section_key); _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 *cfg = (oidc_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; 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; } 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}; mod_auth_openidc-2.4.15.1/src/config.c000066400000000000000000004472011455620533500174630ustar00rootroot00000000000000/* * 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-2024 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 */ // clang-format off #include "mod_auth_openidc.h" #include "metrics.h" // clang-format on #include #define OPENSSL_THREAD_DEFINES #include #include #include #include #if (OPENSSL_VERSION_NUMBER < 0x01000000) #define OPENSSL_NO_THREADID #endif /* validate SSL server certificates by default */ #define OIDC_DEFAULT_SSL_VALIDATE_SERVER 1 /* validate issuer by default */ #define OIDC_DEFAULT_VALIDATE_ISSUER 1 /* default scope requested from the OP */ #define OIDC_DEFAULT_SCOPE "openid" /* default claim delimiter for multi-valued claims passed in a HTTP header */ #define OIDC_DEFAULT_CLAIM_DELIMITER "," /* default prefix for claim names being passed in HTTP headers */ #define OIDC_DEFAULT_CLAIM_PREFIX "OIDC_CLAIM_" /* default name for the claim that will contain the REMOTE_USER value for OpenID Connect protected paths */ #define OIDC_DEFAULT_CLAIM_REMOTE_USER "sub@" /* default name for the claim that will contain the REMOTE_USER value for OAuth 2.0 protected paths */ #define OIDC_DEFAULT_OAUTH_CLAIM_REMOTE_USER "sub" /* default name of the session cookie */ #define OIDC_DEFAULT_COOKIE "mod_auth_openidc_session" /* default for the HTTP header name in which the remote user name is passed */ #define OIDC_DEFAULT_AUTHN_HEADER NULL /* default client_name the client uses for dynamic client registration */ #define OIDC_DEFAULT_CLIENT_NAME "OpenID Connect Apache Module (mod_auth_openidc)" /* 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 500 /* default session storage type */ #define OIDC_DEFAULT_SESSION_TYPE OIDC_SESSION_TYPE_SERVER_CACHE /* default client-cookie chunking size */ #define OIDC_DEFAULT_SESSION_CLIENT_COOKIE_CHUNK_SIZE 4000 /* timeout in seconds after which state expires */ #define OIDC_DEFAULT_STATE_TIMEOUT 300 /* maximum number of parallel state cookies; 0 means unlimited, until the browser or server gives up */ #define OIDC_DEFAULT_MAX_NUMBER_OF_STATE_COOKIES 7 /* default setting for deleting the oldest state cookies */ #define OIDC_DEFAULT_DELETE_OLDEST_STATE_COOKIES 0 /* default session inactivity timeout */ #define OIDC_DEFAULT_SESSION_INACTIVITY_TIMEOUT 300 /* default session max duration */ #define OIDC_DEFAULT_SESSION_MAX_DURATION 3600 * 8 /* default OpenID Connect authorization response type */ #define OIDC_DEFAULT_RESPONSE_TYPE OIDC_PROTO_CODE /* default duration in seconds after which retrieved JWS should be refreshed */ #define OIDC_DEFAULT_JWKS_REFRESH_INTERVAL 3600 /* default max cache size for shm */ #define OIDC_DEFAULT_CACHE_SHM_SIZE 10000 /* default max cache entry size for shm: # value + # key + # overhead */ #define OIDC_DEFAULT_CACHE_SHM_ENTRY_SIZE_MAX 16384 + 512 + 32 /* for issued-at timestamp (iat) checking */ #define OIDC_DEFAULT_IDTOKEN_IAT_SLACK 600 /* for file-based caching: clean interval in seconds */ #define OIDC_DEFAULT_CACHE_FILE_CLEAN_INTERVAL 60 /* set httponly flag on cookies */ #define OIDC_DEFAULT_COOKIE_HTTPONLY 1 /* set Same-Site flag on cookies */ #define OIDC_DEFAULT_COOKIE_SAME_SITE 1 /* default cookie path */ #define OIDC_DEFAULT_COOKIE_PATH "/" /* default OAuth 2.0 introspection token parameter name */ #define OIDC_DEFAULT_OAUTH_TOKEN_PARAM_NAME "token" /* default OAuth 2.0 introspection call HTTP method */ #define OIDC_DEFAULT_OAUTH_ENDPOINT_METHOD OIDC_INTROSPECTION_METHOD_POST /* default OAuth 2.0 non-spec compliant introspection expiry claim name */ #define OIDC_DEFAULT_OAUTH_EXPIRY_CLAIM_NAME OIDC_PROTO_EXPIRES_IN /* default OAuth 2.0 non-spec compliant introspection expiry claim format */ #define OIDC_DEFAULT_OAUTH_EXPIRY_CLAIM_FORMAT OIDC_CLAIM_FORMAT_RELATIVE /* default OAuth 2.0 non-spec compliant introspection expiry claim required */ #define OIDC_DEFAULT_OAUTH_EXPIRY_CLAIM_REQUIRED TRUE /* default refresh interval in seconds after which claims from the user info endpoint should be refreshed */ #define OIDC_DEFAULT_USERINFO_REFRESH_INTERVAL 0 /* default for preserving POST parameters across authentication requests */ #define OIDC_DEFAULT_PRESERVE_POST 0 /* default for passing the access token in a header/environment variable */ #define OIDC_DEFAULT_PASS_ACCESS_TOKEN 1 /* default for passing the refresh token in a header/environment variable */ #define OIDC_DEFAULT_PASS_REFRESH_TOKEN 0 /* default for passing app info in headers */ #define OIDC_DEFAULT_PASS_APP_INFO_IN_HEADERS 1 /* default for passing app info in environment variables */ #define OIDC_DEFAULT_PASS_APP_INFO_IN_ENVVARS 1 /* default for passing app info in base64 encoded format */ #define OIDC_DEFAULT_PASS_APP_INFO_HDR_AS OIDC_PASS_APP_INFO_AS_LATIN1 /* default value for the token introspection interval (0 = disabled, no expiry of claims) */ #define OIDC_DEFAULT_TOKEN_INTROSPECTION_INTERVAL 0 /* default action to take on an incoming unauthenticated request */ #define OIDC_DEFAULT_UNAUTH_ACTION OIDC_UNAUTH_AUTHENTICATE /* default action to take on an incoming authorized request */ #define OIDC_DEFAULT_UNAUTZ_ACTION OIDC_UNAUTZ_RETURN403 /* defines for how long provider metadata will be cached */ #define OIDC_DEFAULT_PROVIDER_METADATA_REFRESH_INTERVAL 0 /* define the default HTTP method used to send the authentication request to the provider */ #define OIDC_DEFAULT_AUTH_REQUEST_METHOD OIDC_AUTH_REQUEST_METHOD_GET /* define whether the issuer will be added to the redirect uri by default to mitigate the IDP mixup attack */ #define OIDC_DEFAULT_PROVIDER_ISSUER_SPECIFIC_REDIRECT_URI 0 /* 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 /* default setting for calculating the fingerprint of the state from request headers during authentication */ #define OIDC_DEFAULT_STATE_INPUT_HEADERS OIDC_STATE_INPUT_HEADERS_USER_AGENT /* 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_" /* default x-forwarded-* headers to be interpreted */ #define OIDC_DEFAULT_X_FORWARDED_HEADERS 0 /* default store id_token in session */ #define OIDC_DEFAULT_STORE_ID_TOKEN TRUE /* default pass user info as */ #define OIDC_DEFAULT_PASS_USERINFO_AS OIDC_PASS_USERINFO_AS_CLAIMS_STR /* default pass id_token as */ #define OIDC_DEFAULT_PASS_IDTOKEN_AS OIDC_PASS_IDTOKEN_AS_CLAIMS /* default action to be taken on access token refresh error */ #define OIDC_DEFAULT_ON_ERROR_REFRESH OIDC_ON_ERROR_CONTINUE; #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 OIDCProviderCheckSessionIFrame "OIDCProviderCheckSessionIFrame" #define OIDCProviderEndSessionEndpoint "OIDCProviderEndSessionEndpoint" #define OIDCProviderBackChannelLogoutSupported "OIDCProviderBackChannelLogoutSupported" #define OIDCProviderJwksUri "OIDCProviderJwksUri" #define OIDCProviderSignedJwksUri "OIDCProviderSignedJwksUri" #define OIDCProviderVerifyCertFiles "OIDCProviderVerifyCertFiles" #define OIDCResponseType "OIDCResponseType" #define OIDCResponseMode "OIDCResponseMode" #define OIDCPublicKeyFiles "OIDCPublicKeyFiles" #define OIDCClientJwksUri "OIDCClientJwksUri" #define OIDCIDTokenSignedResponseAlg "OIDCIDTokenSignedResponseAlg" #define OIDCIDTokenEncryptedResponseAlg "OIDCIDTokenEncryptedResponseAlg" #define OIDCIDTokenEncryptedResponseEnc "OIDCIDTokenEncryptedResponseEnc" #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 OIDCPathScope "OIDCPathScope" #define OIDCJWKSRefreshInterval "OIDCJWKSRefreshInterval" #define OIDCIDTokenIatSlack "OIDCIDTokenIatSlack" #define OIDCSessionMaxDuration "OIDCSessionMaxDuration" #define OIDCAuthRequestParams "OIDCAuthRequestParams" #define OIDCLogoutRequestParams "OIDCLogoutRequestParams" #define OIDCPathAuthRequestParams "OIDCPathAuthRequestParams" #define OIDCPKCEMethod "OIDCPKCEMethod" #define OIDCClientID "OIDCClientID" #define OIDCClientSecret "OIDCClientSecret" #define OIDCClientTokenEndpointCert "OIDCClientTokenEndpointCert" #define OIDCClientTokenEndpointKey "OIDCClientTokenEndpointKey" #define OIDCClientTokenEndpointKeyPassword "OIDCClientTokenEndpointKeyPassword" #define OIDCDefaultLoggedOutURL "OIDCDefaultLoggedOutURL" #define OIDCCookieHTTPOnly "OIDCCookieHTTPOnly" #define OIDCCookieSameSite "OIDCCookieSameSite" #define OIDCOutgoingProxy "OIDCOutgoingProxy" #define OIDCClaimDelimiter "OIDCClaimDelimiter" #define OIDCPassIDTokenAs "OIDCPassIDTokenAs" #define OIDCPassUserInfoAs "OIDCPassUserInfoAs" #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 OIDCOAuthIntrospectionTokenParamName "OIDCOAuthIntrospectionTokenParamName" #define OIDCOAuthTokenExpiryClaim "OIDCOAuthTokenExpiryClaim" #define OIDCOAuthSSLValidateServer "OIDCOAuthSSLValidateServer" #define OIDCOAuthVerifyCertFiles "OIDCOAuthVerifyCertFiles" #define OIDCOAuthVerifySharedKeys "OIDCOAuthVerifySharedKeys" #define OIDCOAuthVerifyJwksUri "OIDCOAuthVerifyJwksUri" #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 OIDCCacheType "OIDCCacheType" #define OIDCCacheEncrypt "OIDCCacheEncrypt" #define OIDCCacheDir "OIDCCacheDir" #define OIDCCacheFileCleanInterval "OIDCCacheFileCleanInterval" #define OIDCRedisCacheUsername "OIDCRedisCacheUsername" #define OIDCRedisCachePassword "OIDCRedisCachePassword" #define OIDCRedisCacheDatabase "OIDCRedisCacheDatabase" #define OIDCRedisCacheConnectTimeout "OIDCRedisCacheConnectTimeout" #define OIDCRedisCacheTimeout "OIDCRedisCacheTimeout" #define OIDCHTMLErrorTemplate "OIDCHTMLErrorTemplate" #define OIDCPreservePostTemplates "OIDCPreservePostTemplates" #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 OIDCUserInfoRefreshInterval "OIDCUserInfoRefreshInterval" #define OIDCOAuthTokenIntrospectionInterval "OIDCOAuthTokenIntrospectionInterval" #define OIDCPreservePost "OIDCPreservePost" #define OIDCPassAccessToken "OIDCPassAccessToken" #define OIDCPassRefreshToken "OIDCPassRefreshToken" #define OIDCRequestObject "OIDCRequestObject" #define OIDCProviderMetadataRefreshInterval "OIDCProviderMetadataRefreshInterval" #define OIDCProviderAuthRequestMethod "OIDCProviderAuthRequestMethod" #define OIDCBlackListedClaims "OIDCBlackListedClaims" #define OIDCOAuthServerMetadataURL "OIDCOAuthServerMetadataURL" #define OIDCRefreshAccessTokenBeforeExpiry "OIDCRefreshAccessTokenBeforeExpiry" #define OIDCStateInputHeaders "OIDCStateInputHeaders" #define OIDCRedirectURLsAllowed "OIDCRedirectURLsAllowed" #define OIDCStateCookiePrefix "OIDCStateCookiePrefix" #define OIDCCABundlePath "OIDCCABundlePath" #define OIDCLogoutXFrameOptions "OIDCLogoutXFrameOptions" #define OIDCXForwardedHeaders "OIDCXForwardedHeaders" #define OIDCUserInfoClaimsExpr "OIDCUserInfoClaimsExpr" #define OIDCFilterClaimsExpr "OIDCFilterClaimsExpr" #define OIDCTraceParent "OIDCTraceParent" extern module AP_MODULE_DECLARE_DATA auth_openidc_module; /* * directory related configuration */ typedef struct oidc_dir_cfg { 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_headers; int pass_info_in_env_vars; int pass_info_as; 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; } oidc_dir_cfg; #define OIDC_CONFIG_DIR_RV(cmd, rv) \ rv != NULL ? apr_psprintf(cmd->pool, "Invalid value for directive '%s': %s", cmd->directive->directive, rv) \ : NULL /* * set a boolean value in the server config */ static const char *oidc_set_flag_slot(cmd_parms *cmd, void *struct_ptr, int arg) { oidc_cfg *cfg = (oidc_cfg *)ap_get_module_config(cmd->server->module_config, &auth_openidc_module); return ap_set_flag_slot(cmd, cfg, arg); } /* * set a string value in the server config */ static const char *oidc_set_string_slot(cmd_parms *cmd, void *struct_ptr, const char *arg) { oidc_cfg *cfg = (oidc_cfg *)ap_get_module_config(cmd->server->module_config, &auth_openidc_module); return ap_set_string_slot(cmd, cfg, arg); } /* * set an integer value in the server config */ static const char *oidc_set_int_slot(cmd_parms *cmd, void *struct_ptr, const char *arg) { oidc_cfg *cfg = (oidc_cfg *)ap_get_module_config(cmd->server->module_config, &auth_openidc_module); return ap_set_int_slot(cmd, cfg, arg); } static const char *oidc_set_http_timeout_slot(cmd_parms *cmd, void *struct_ptr, const char *arg1, const char *arg2, const char *arg3) { oidc_cfg *cfg = (oidc_cfg *)ap_get_module_config(cmd->server->module_config, &auth_openidc_module); char *s = NULL, *p = NULL; int offset = (int)(long)cmd->info; oidc_http_timeout_t *http_timeout = (oidc_http_timeout_t *)((char *)cfg + offset); if (arg1) http_timeout->request_timeout = _oidc_str_to_int(arg1); if (arg2) http_timeout->connect_timeout = _oidc_str_to_int(arg2); if (arg3) { s = apr_pstrdup(cmd->pool, arg3); p = strstr(s, OIDC_STR_COLON); if (p) { *p = '\0'; p++; http_timeout->retry_interval = apr_time_from_msec(_oidc_str_to_int(p)); } http_timeout->retries = _oidc_str_to_int(s); } return NULL; } /* * set an apr_uint32_t value in the server config */ static const char *oidc_set_uint32_slot(cmd_parms *cmd, void *struct_ptr, const char *arg) { char *endptr; apr_int64_t value; oidc_cfg *cfg = (oidc_cfg *)ap_get_module_config(cmd->server->module_config, &auth_openidc_module); apr_uintptr_t offset = (apr_uintptr_t)cmd->info; value = apr_strtoi64(arg, &endptr, 10); if (errno != 0 || *endptr != '\0') { return OIDC_CONFIG_DIR_RV(cmd, arg); } if (value > APR_UINT32_MAX || value < 0) { return OIDC_CONFIG_DIR_RV(cmd, "Integer value out of range"); } *(apr_uint32_t *)((char *)cfg + offset) = (apr_uint32_t)value; return NULL; } /* * set an 32 bit uint timeout slot in the server config */ static const char *oidc_set_timeout_slot(cmd_parms *cmd, void *struct_ptr, const char *arg) { #if AP_MODULE_MAGIC_AT_LEAST(20080920, 2) apr_status_t rv; apr_interval_time_t timeout; #else char *endptr; apr_int64_t timeout; #endif oidc_cfg *cfg = (oidc_cfg *)ap_get_module_config(cmd->server->module_config, &auth_openidc_module); apr_uintptr_t offset = (apr_uintptr_t)cmd->info; #if AP_MODULE_MAGIC_AT_LEAST(20080920, 2) rv = ap_timeout_parameter_parse(arg, &timeout, "s"); if (rv != APR_SUCCESS) { return OIDC_CONFIG_DIR_RV(cmd, arg); } #else timeout = apr_strtoi64(arg, &endptr, 10); if (errno != 0 || *endptr != '\0') { return OIDC_CONFIG_DIR_RV(cmd, arg); } if (timeout > apr_time_sec(APR_INT64_MAX)) { return OIDC_CONFIG_DIR_RV(cmd, "Integer value out of range"); } timeout = apr_time_from_sec(timeout); #endif if (timeout > APR_UINT32_MAX) { return OIDC_CONFIG_DIR_RV(cmd, "Integer value out of range"); } *(apr_uint32_t *)((char *)cfg + offset) = (apr_uint32_t)timeout; return NULL; } /* * set a URL value in a config record */ static const char *oidc_set_url_slot_type(cmd_parms *cmd, void *ptr, const char *arg, const char *type) { const char *rv = type != NULL ? oidc_valid_url(cmd->pool, arg, type) : oidc_valid_http_url(cmd->pool, arg); if (rv == NULL) rv = ap_set_string_slot(cmd, ptr, arg); return rv; } /* * set a HTTPS value in the server config */ static const char *oidc_set_https_slot(cmd_parms *cmd, void *ptr, const char *arg) { oidc_cfg *cfg = (oidc_cfg *)ap_get_module_config(cmd->server->module_config, &auth_openidc_module); return oidc_set_url_slot_type(cmd, cfg, arg, "https"); } /* * set a HTTPS/HTTP value in the server config */ static const char *oidc_set_url_slot(cmd_parms *cmd, void *ptr, const char *arg) { oidc_cfg *cfg = (oidc_cfg *)ap_get_module_config(cmd->server->module_config, &auth_openidc_module); return oidc_set_url_slot_type(cmd, cfg, arg, NULL); } /* * set a relative or absolute URL value in a config rec */ static const char *oidc_set_relative_or_absolute_url_slot_dir_cfg(cmd_parms *cmd, void *ptr, const char *arg) { if (arg[0] == OIDC_CHAR_FORWARD_SLASH) { // relative uri apr_uri_t uri; if (apr_uri_parse(cmd->pool, arg, &uri) != APR_SUCCESS) { const char *rv = apr_psprintf(cmd->pool, "cannot parse '%s' as relative URI", arg); return OIDC_CONFIG_DIR_RV(cmd, rv); } else { return ap_set_string_slot(cmd, ptr, arg); } } else { // absolute uri return oidc_set_url_slot_type(cmd, ptr, arg, NULL); } } /* * set a relative or absolute URL value in the server config */ static const char *oidc_set_relative_or_absolute_url_slot(cmd_parms *cmd, void *ptr, const char *arg) { oidc_cfg *cfg = (oidc_cfg *)ap_get_module_config(cmd->server->module_config, &auth_openidc_module); return oidc_set_relative_or_absolute_url_slot_dir_cfg(cmd, cfg, arg); } /* * set a directory value in the server config */ // TODO: it's not really a syntax error... (could be fixed at runtime but then we'd have to restart the server) static const char *oidc_set_dir_slot(cmd_parms *cmd, void *ptr, const char *arg) { oidc_cfg *cfg = (oidc_cfg *)ap_get_module_config(cmd->server->module_config, &auth_openidc_module); const char *rv = oidc_valid_dir(cmd->pool, arg); if (rv == NULL) rv = ap_set_string_slot(cmd, cfg, arg); return rv; } /* * set a path value in the server config, converting to absolute if necessary */ static const char *oidc_set_path_slot(cmd_parms *cmd, void *ptr, const char *arg) { oidc_cfg *cfg = (oidc_cfg *)ap_get_module_config(cmd->server->module_config, &auth_openidc_module); const char *full_path = oidc_util_get_full_path(cmd->pool, arg); return ap_set_string_slot(cmd, cfg, full_path); } #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 static const char *oidc_set_outgoing_proxy_slot(cmd_parms *cmd, void *ptr, const char *arg1, const char *arg2, const char *arg3) { oidc_cfg *cfg = (oidc_cfg *)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_parse_outgoing_proxy_auth_type(cmd->pool, arg3, &cfg->outgoing_proxy.auth_type); return OIDC_CONFIG_DIR_RV(cmd, rv); } /* * set a string value in the server config with exec support */ static const char *oidc_parse_passphrase(cmd_parms *cmd, 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, cmd->temp_pool) != APR_SUCCESS) { return apr_pstrcat(cmd->pool, "Unable to parse exec arguments from ", arg + 5, NULL); } argv[0] = ap_server_root_relative(cmd->temp_pool, argv[0]); if (!argv[0]) { return apr_pstrcat(cmd->pool, "Invalid ", cmd->cmd->name, " exec location:", arg + 5, NULL); } result = ap_get_exec_line(cmd->pool, argv[0], (const char *const *)argv); if (!result) { return apr_pstrcat(cmd->pool, "Unable to get passphrase from exec of ", arg + 5, NULL); } if (_oidc_strlen(result) == 0) return apr_pstrdup(cmd->pool, "the output of the crypto passphrase generation command is empty " "(perhaps you need to pass it to bash -c \"\"?)"); *passphrase = apr_pstrdup(cmd->pool, result); } else { *passphrase = apr_pstrdup(cmd->pool, arg); } return NULL; } static const char *oidc_set_crypto_passphrase_slot(cmd_parms *cmd, void *struct_ptr, const char *arg1, const char *arg2) { oidc_cfg *cfg = (oidc_cfg *)ap_get_module_config(cmd->server->module_config, &auth_openidc_module); const char *rv = NULL; if (arg1) rv = oidc_parse_passphrase(cmd, arg1, &cfg->crypto_passphrase.secret1); if ((rv == NULL) && (arg2 != NULL)) rv = oidc_parse_passphrase(cmd, arg2, &cfg->crypto_passphrase.secret2); return NULL; } static const char *oidc_set_passphrase_slot(cmd_parms *cmd, void *struct_ptr, const char *arg) { oidc_cfg *cfg = (oidc_cfg *)ap_get_module_config(cmd->server->module_config, &auth_openidc_module); const char *rv = NULL; char *secret = NULL; rv = oidc_parse_passphrase(cmd, arg, &secret); if (rv == NULL) rv = ap_set_string_slot(cmd, cfg, secret); return rv; } /* * set the cookie domain in the server config and check it syntactically */ static const char *oidc_set_cookie_domain(cmd_parms *cmd, void *ptr, const char *value) { oidc_cfg *cfg = (oidc_cfg *)ap_get_module_config(cmd->server->module_config, &auth_openidc_module); const char *rv = oidc_valid_cookie_domain(cmd->pool, value); if (rv == NULL) cfg->cookie_domain = apr_pstrdup(cmd->pool, value); return OIDC_CONFIG_DIR_RV(cmd, rv); } /* * set the session storage type */ static const char *oidc_set_session_type(cmd_parms *cmd, void *ptr, const char *arg) { oidc_cfg *cfg = (oidc_cfg *)ap_get_module_config(cmd->server->module_config, &auth_openidc_module); const char *rv = oidc_parse_session_type(cmd->pool, arg, &cfg->session_type, &cfg->persistent_session_cookie, &cfg->store_id_token); return OIDC_CONFIG_DIR_RV(cmd, rv); } /* * set the maximum size of a shared memory cache entry and enforces a minimum */ static const char *oidc_set_cache_shm_entry_size_max(cmd_parms *cmd, void *ptr, const char *arg) { oidc_cfg *cfg = (oidc_cfg *)ap_get_module_config(cmd->server->module_config, &auth_openidc_module); const char *rv = oidc_parse_cache_shm_entry_size_max(cmd->pool, arg, &cfg->cache_shm_entry_size_max); return OIDC_CONFIG_DIR_RV(cmd, rv); } /* * set the cache type */ static const char *oidc_set_cache_type(cmd_parms *cmd, void *ptr, const char *arg) { oidc_cfg *cfg = (oidc_cfg *)ap_get_module_config(cmd->server->module_config, &auth_openidc_module); const char *rv = oidc_parse_cache_type(cmd->pool, arg, &cfg->cache); return OIDC_CONFIG_DIR_RV(cmd, rv); } /* * set SSL validation slot */ static const char *oidc_set_ssl_validate_slot(cmd_parms *cmd, void *struct_ptr, const char *arg) { oidc_cfg *cfg = (oidc_cfg *)ap_get_module_config(cmd->server->module_config, &auth_openidc_module); int b = 0; const char *rv = oidc_parse_boolean(cmd->pool, arg, &b); if (rv == NULL) rv = ap_set_flag_slot(cmd, cfg, b); return OIDC_CONFIG_DIR_RV(cmd, rv); } /* * set validate issuer slot */ static const char *oidc_set_validate_issuer_slot(cmd_parms *cmd, void *struct_ptr, const char *arg) { oidc_cfg *cfg = (oidc_cfg *)ap_get_module_config(cmd->server->module_config, &auth_openidc_module); int b = 0; const char *rv = oidc_parse_boolean(cmd->pool, arg, &b); if (rv == NULL) rv = ap_set_flag_slot(cmd, cfg, b); return OIDC_CONFIG_DIR_RV(cmd, rv); } /* * 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 *cfg) { return (cfg->private_keys != NULL) ? &oidc_valid_endpoint_auth_method : &oidc_valid_endpoint_auth_method_no_private_key; } /* * set an authentication method for an endpoint and check it is one that we support */ static const char *oidc_set_endpoint_auth_slot(cmd_parms *cmd, void *struct_ptr, const char *arg) { oidc_cfg *cfg = (oidc_cfg *)ap_get_module_config(cmd->server->module_config, &auth_openidc_module); const char *rv = oidc_cfg_get_valid_endpoint_auth_function(cfg)(cmd->pool, arg); if (rv == NULL) rv = ap_set_string_slot(cmd, cfg, arg); return OIDC_CONFIG_DIR_RV(cmd, rv); } /* * set the response type used */ static const char *oidc_set_response_type(cmd_parms *cmd, void *struct_ptr, const char *arg) { oidc_cfg *cfg = (oidc_cfg *)ap_get_module_config(cmd->server->module_config, &auth_openidc_module); const char *rv = oidc_valid_response_type(cmd->pool, arg); if (rv == NULL) rv = ap_set_string_slot(cmd, cfg, arg); return OIDC_CONFIG_DIR_RV(cmd, rv); } const char *oidc_parse_pkce_type(apr_pool_t *pool, const char *arg, oidc_proto_pkce_t **type) { const char *rv = oidc_valid_pkce_method(pool, arg); if (rv != NULL) return rv; if (_oidc_strcmp(arg, OIDC_PKCE_METHOD_PLAIN) == 0) { *type = &oidc_pkce_plain; } else if (_oidc_strcmp(arg, OIDC_PKCE_METHOD_S256) == 0) { *type = &oidc_pkce_s256; } else if (_oidc_strcmp(arg, OIDC_PKCE_METHOD_NONE) == 0) { *type = NULL; } return NULL; } /* * define the PCKE method to use */ static const char *oidc_set_pkce_method(cmd_parms *cmd, void *ptr, const char *arg) { oidc_cfg *cfg = (oidc_cfg *)ap_get_module_config(cmd->server->module_config, &auth_openidc_module); const char *rv = oidc_parse_pkce_type(cmd->pool, arg, &cfg->provider.pkce); return OIDC_CONFIG_DIR_RV(cmd, rv); } /* * set the response mode used */ static const char *oidc_set_response_mode(cmd_parms *cmd, void *struct_ptr, const char *arg) { oidc_cfg *cfg = (oidc_cfg *)ap_get_module_config(cmd->server->module_config, &auth_openidc_module); const char *rv = oidc_valid_response_mode(cmd->pool, arg); if (rv == NULL) rv = ap_set_string_slot(cmd, cfg, arg); return OIDC_CONFIG_DIR_RV(cmd, rv); } /* * set the signing algorithm to be used by the OP (id_token/user_info) */ static const char *oidc_set_signed_response_alg(cmd_parms *cmd, void *struct_ptr, const char *arg) { oidc_cfg *cfg = (oidc_cfg *)ap_get_module_config(cmd->server->module_config, &auth_openidc_module); const char *rv = oidc_valid_signed_response_alg(cmd->pool, arg); if (rv == NULL) rv = ap_set_string_slot(cmd, cfg, arg); return OIDC_CONFIG_DIR_RV(cmd, rv); } /* * set the Content Encryption Key encryption algorithm to be used by the OP (id_token/user_info) */ static const char *oidc_set_encrypted_response_alg(cmd_parms *cmd, void *struct_ptr, const char *arg) { oidc_cfg *cfg = (oidc_cfg *)ap_get_module_config(cmd->server->module_config, &auth_openidc_module); const char *rv = oidc_valid_encrypted_response_alg(cmd->pool, arg); if (rv == NULL) rv = ap_set_string_slot(cmd, cfg, arg); return OIDC_CONFIG_DIR_RV(cmd, rv); } /* * set the content encryption algorithm to be used by the OP (id_token/user_info) */ static const char *oidc_set_encrypted_response_enc(cmd_parms *cmd, void *struct_ptr, const char *arg) { oidc_cfg *cfg = (oidc_cfg *)ap_get_module_config(cmd->server->module_config, &auth_openidc_module); const char *rv = oidc_valid_encrypted_response_enc(cmd->pool, arg); if (rv == NULL) rv = ap_set_string_slot(cmd, cfg, arg); return OIDC_CONFIG_DIR_RV(cmd, rv); } /* * set the userinfo endpoint token presentation method */ static const char *oidc_set_userinfo_token_method(cmd_parms *cmd, void *struct_ptr, const char *arg) { oidc_cfg *cfg = (oidc_cfg *)ap_get_module_config(cmd->server->module_config, &auth_openidc_module); const char *rv = oidc_parse_userinfo_token_method(cmd->pool, arg, &cfg->provider.userinfo_token_method); return OIDC_CONFIG_DIR_RV(cmd, rv); } /* * set the session inactivity timeout */ static const char *oidc_set_session_inactivity_timeout(cmd_parms *cmd, void *struct_ptr, const char *arg) { oidc_cfg *cfg = (oidc_cfg *)ap_get_module_config(cmd->server->module_config, &auth_openidc_module); const char *rv = oidc_parse_session_inactivity_timeout(cmd->pool, arg, &cfg->session_inactivity_timeout); return OIDC_CONFIG_DIR_RV(cmd, rv); } /* * set the maximum session duration; 0 means take it from the ID token expiry time */ static const char *oidc_set_session_max_duration(cmd_parms *cmd, void *struct_ptr, const char *arg) { oidc_cfg *cfg = (oidc_cfg *)ap_get_module_config(cmd->server->module_config, &auth_openidc_module); const char *rv = oidc_parse_session_max_duration(cmd->pool, arg, &cfg->provider.session_max_duration); return OIDC_CONFIG_DIR_RV(cmd, rv); } /* * add a public key from an X.509 file to our list of JWKs with public keys */ static const char *oidc_set_public_key_files(cmd_parms *cmd, void *struct_ptr, const char *arg) { oidc_jwk_t *jwk = NULL; oidc_jose_error_t err; char *use = NULL; oidc_cfg *cfg = (oidc_cfg *)ap_get_module_config(cmd->server->module_config, &auth_openidc_module); int offset = (int)(long)cmd->info; apr_array_header_t **public_keys = (apr_array_header_t **)((char *)cfg + offset); char *kid = NULL, *fname = NULL; int fname_len; const char *rv = oidc_parse_use_enc_kid_key_tuple(cmd->pool, arg, &kid, &fname, &fname_len, &use, FALSE); if (rv != NULL) return rv; fname = oidc_util_get_full_path(cmd->pool, fname); if (oidc_jwk_parse_pem_public_key(cmd->pool, kid, fname, &jwk, &err) == FALSE) { return apr_psprintf(cmd->pool, "oidc_jwk_parse_pem_public_key failed for (kid=%s) \"%s\": %s", kid, fname, oidc_jose_e2s(cmd->pool, err)); } if (*public_keys == NULL) *public_keys = apr_array_make(cmd->pool, 4, sizeof(oidc_jwk_t *)); if (use) jwk->use = apr_pstrdup(cmd->pool, use); APR_ARRAY_PUSH(*public_keys, oidc_jwk_t *) = jwk; return NULL; } /* * add a shared key to a list of JWKs with shared keys */ static const char *oidc_set_shared_keys(cmd_parms *cmd, void *struct_ptr, const char *arg) { oidc_jose_error_t err; oidc_jwk_t *jwk = NULL; char *use = NULL; oidc_cfg *cfg = (oidc_cfg *)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_parse_use_enc_kid_key_tuple(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; } /* * add a private key from an RSA/EC private key file to our list of JWKs with private keys */ static const char *oidc_set_private_key_files_enc(cmd_parms *cmd, void *dummy, const char *arg) { oidc_cfg *cfg = (oidc_cfg *)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, *fname = NULL; int fname_len; const char *rv = oidc_parse_use_enc_kid_key_tuple(cmd->pool, arg, &kid, &fname, &fname_len, &use, FALSE); if (rv != NULL) return rv; fname = oidc_util_get_full_path(cmd->pool, fname); if (oidc_jwk_parse_pem_private_key(cmd->pool, kid, fname, &jwk, &err) == FALSE) { return apr_psprintf(cmd->pool, "oidc_jwk_parse_pem_private_key failed for (kid=%s) \"%s\": %s", kid, fname, oidc_jose_e2s(cmd->pool, err)); } 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; return NULL; } /* * define how to pass the id_token/claims in HTTP headers */ static const char *oidc_set_pass_idtoken_as(cmd_parms *cmd, void *m, const char *v1, const char *v2, const char *v3) { oidc_dir_cfg *dir_cfg = (oidc_dir_cfg *)m; const char *rv = oidc_parse_pass_idtoken_as(cmd->pool, v1, v2, v3, &dir_cfg->pass_idtoken_as); return OIDC_CONFIG_DIR_RV(cmd, rv); } /* * define how to pass the userinfo/claims in HTTP headers */ static const char *oidc_set_pass_userinfo_as(cmd_parms *cmd, void *m, const char *arg) { oidc_dir_cfg *dir_cfg = (oidc_dir_cfg *)m; const char *rv = NULL; oidc_pass_user_info_as_t *p = NULL; rv = oidc_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 which method of pass an OAuth Bearer token is accepted */ static const char *oidc_set_accept_oauth_token_in(cmd_parms *cmd, void *m, const char *arg) { oidc_dir_cfg *dir_cfg = (oidc_dir_cfg *)m; const char *rv = oidc_parse_accept_oauth_token_in(cmd->pool, arg, &dir_cfg->oauth_accept_token_in, dir_cfg->oauth_accept_token_options); return OIDC_CONFIG_DIR_RV(cmd, rv); } /* * set the syntax of the token expiry claim in the introspection response */ static const char *oidc_set_token_expiry_claim(cmd_parms *cmd, void *dummy, const char *claim_name, const char *claim_format, const char *claim_required) { oidc_cfg *cfg = (oidc_cfg *)ap_get_module_config(cmd->server->module_config, &auth_openidc_module); 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_valid_claim_format(cmd->pool, claim_format); if (rv == NULL) cfg->oauth.introspection_token_expiry_claim_format = apr_pstrdup(cmd->pool, claim_format); } if ((rv == NULL) && (claim_required != NULL)) { rv = oidc_parse_claim_required(cmd->pool, claim_required, &cfg->oauth.introspection_token_expiry_claim_required); } return OIDC_CONFIG_DIR_RV(cmd, rv); } /* * specify cookies names to pass/strip */ static const char *oidc_set_cookie_names(cmd_parms *cmd, void *m, const char *arg) { oidc_dir_cfg *dir_cfg = (oidc_dir_cfg *)m; int offset = (int)(long)cmd->info; apr_array_header_t **cookie_names = (apr_array_header_t **)((char *)dir_cfg + offset); if (*cookie_names == NULL) *cookie_names = apr_array_make(cmd->pool, 3, sizeof(const char *)); APR_ARRAY_PUSH(*cookie_names, const char *) = arg; return NULL; } /* * set the HTTP method to use in an OAuth 2.0 token introspection/validation call */ static const char *oidc_set_introspection_method(cmd_parms *cmd, void *m, const char *arg) { oidc_cfg *cfg = (oidc_cfg *)ap_get_module_config(cmd->server->module_config, &auth_openidc_module); const char *rv = oidc_valid_introspection_method(cmd->pool, arg); if (rv == NULL) rv = ap_set_string_slot(cmd, cfg, arg); return OIDC_CONFIG_DIR_RV(cmd, rv); } /* * set POST preservation behavior */ static const char *oidc_set_preserve_post(cmd_parms *cmd, void *m, const char *arg) { oidc_dir_cfg *dir_cfg = (oidc_dir_cfg *)m; int b = 0; const char *rv = oidc_parse_boolean(cmd->pool, arg, &b); if (rv == NULL) rv = ap_set_flag_slot(cmd, dir_cfg, b); return OIDC_CONFIG_DIR_RV(cmd, rv); } /* * set the remote user name claims, optionally plus the regular expression applied to it */ static const char *oidc_set_remote_user_claim(cmd_parms *cmd, void *struct_ptr, const char *v1, const char *v2, const char *v3) { oidc_cfg *cfg = (oidc_cfg *)ap_get_module_config(cmd->server->module_config, &auth_openidc_module); int offset = (int)(long)cmd->info; oidc_remote_user_claim_t *remote_user_claim = (oidc_remote_user_claim_t *)((char *)cfg + offset); remote_user_claim->claim_name = v1; if (v2) remote_user_claim->reg_exp = v2; if (v3) remote_user_claim->replace = v3; return NULL; } /* * define how to pass claims information to the application: in headers and/or environment variables * and optionally specify the encoding applied to the values */ static const char *oidc_set_pass_claims_as(cmd_parms *cmd, void *m, const char *arg1, const char *arg2) { oidc_dir_cfg *dir_cfg = (oidc_dir_cfg *)m; const char *rv = oidc_parse_set_claims_as(cmd->pool, arg1, &dir_cfg->pass_info_in_headers, &dir_cfg->pass_info_in_env_vars); if ((rv == NULL) && (arg2 != NULL)) rv = oidc_parse_pass_claims_as_encoding(cmd->pool, arg2, &dir_cfg->pass_info_as); return OIDC_CONFIG_DIR_RV(cmd, rv); } /* * define how to act on unauthenticated requests */ static const char *oidc_set_unauth_action(cmd_parms *cmd, void *m, const char *arg1, const char *arg2) { oidc_dir_cfg *dir_cfg = (oidc_dir_cfg *)m; const char *rv = oidc_parse_unauth_action(cmd->pool, 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); } #ifdef USE_LIBJQ static const char *oidc_set_userinfo_claims_expr(cmd_parms *cmd, void *m, const char *arg) { oidc_dir_cfg *dir_cfg = (oidc_dir_cfg *)m; const char *rv = oidc_util_apr_expr_parse(cmd, arg, &dir_cfg->userinfo_claims_expr, TRUE); return OIDC_CONFIG_DIR_RV(cmd, rv); } static const char *oidc_set_filtered_claims_expr(cmd_parms *cmd, void *m, const char *arg) { oidc_cfg *cfg = (oidc_cfg *)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 static const char *oidc_set_path_auth_request_params(cmd_parms *cmd, void *m, const char *arg) { oidc_dir_cfg *dir_cfg = (oidc_dir_cfg *)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); } static const char *oidc_set_path_scope(cmd_parms *cmd, void *m, const char *arg) { oidc_dir_cfg *dir_cfg = (oidc_dir_cfg *)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 how to act on unauthorized requests */ static const char *oidc_set_unautz_action(cmd_parms *cmd, void *m, const char *arg1, const char *arg2) { oidc_dir_cfg *dir_cfg = (oidc_dir_cfg *)m; const char *rv = oidc_parse_unautz_action(cmd->pool, 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); } /* * set the JWKS refresh interval */ static const char *oidc_set_jwks_refresh_interval(cmd_parms *cmd, void *struct_ptr, const char *arg) { oidc_cfg *cfg = (oidc_cfg *)ap_get_module_config(cmd->server->module_config, &auth_openidc_module); const char *rv = oidc_parse_jwks_refresh_interval(cmd->pool, arg, &cfg->provider.jwks_uri.refresh_interval); return OIDC_CONFIG_DIR_RV(cmd, rv); } /* * set the ID token "iat" slack */ static const char *oidc_set_idtoken_iat_slack(cmd_parms *cmd, void *struct_ptr, const char *arg) { oidc_cfg *cfg = (oidc_cfg *)ap_get_module_config(cmd->server->module_config, &auth_openidc_module); const char *rv = oidc_parse_idtoken_iat_slack(cmd->pool, arg, &cfg->provider.idtoken_iat_slack); return OIDC_CONFIG_DIR_RV(cmd, rv); } /* * set the userinfo refresh interval */ static const char *oidc_set_userinfo_refresh_interval(cmd_parms *cmd, void *struct_ptr, const char *arg1, const char *arg2) { oidc_cfg *cfg = (oidc_cfg *)ap_get_module_config(cmd->server->module_config, &auth_openidc_module); const char *rv = oidc_parse_userinfo_refresh_interval(cmd->pool, arg1, &cfg->provider.userinfo_refresh_interval); if ((rv == NULL) && (arg2)) { rv = oidc_parse_action_on_error_refresh_as(cmd->pool, arg2, &cfg->action_on_userinfo_error); } return OIDC_CONFIG_DIR_RV(cmd, rv); } /* * define which data will be returned from the info hook */ static const char *oidc_set_info_hook_data(cmd_parms *cmd, void *m, const char *arg) { oidc_cfg *cfg = (oidc_cfg *)ap_get_module_config(cmd->server->module_config, &auth_openidc_module); const char *rv = oidc_parse_info_hook_data(cmd->pool, arg, &cfg->info_hook_data); return OIDC_CONFIG_DIR_RV(cmd, rv); } static const char *oidc_set_metrics_hook_data(cmd_parms *cmd, void *m, const char *arg) { oidc_cfg *cfg = (oidc_cfg *)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); ; } static const char *oidc_set_trace_parent(cmd_parms *cmd, void *struct_ptr, const char *arg) { oidc_cfg *cfg = (oidc_cfg *)ap_get_module_config(cmd->server->module_config, &auth_openidc_module); const char *rv = oidc_parse_trace_parent(cmd->pool, arg, &cfg->trace_parent); return OIDC_CONFIG_DIR_RV(cmd, rv); } static const char *oidc_set_filtered_claims(cmd_parms *cmd, void *m, const char *arg) { oidc_cfg *cfg = (oidc_cfg *)ap_get_module_config(cmd->server->module_config, &auth_openidc_module); int offset = (int)(long)cmd->info; apr_hash_t **list = (apr_hash_t **)((char *)cfg + offset); if (*list == NULL) *list = apr_hash_make(cmd->pool); apr_hash_set(*list, arg, APR_HASH_KEY_STRING, arg); return NULL; } /* * set the claim prefix */ static const char *oidc_cfg_set_claim_prefix(cmd_parms *cmd, void *struct_ptr, const char *args) { oidc_cfg *cfg = (oidc_cfg *)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; } /* * get the claim prefix */ const char *oidc_cfg_claim_prefix(request_rec *r) { oidc_cfg *cfg = ap_get_module_config(r->server->module_config, &auth_openidc_module); if (cfg->claim_prefix == NULL) return OIDC_DEFAULT_CLAIM_PREFIX; return cfg->claim_prefix; } /* * set the HTTP method used to send the authentication request to the provider */ const char *oidc_set_auth_request_method(cmd_parms *cmd, void *struct_ptr, const char *arg) { oidc_cfg *cfg = (oidc_cfg *)ap_get_module_config(cmd->server->module_config, &auth_openidc_module); const char *rv = oidc_parse_auth_request_method(cmd->pool, arg, &cfg->provider.auth_request_method); return OIDC_CONFIG_DIR_RV(cmd, rv); } /* * set the introspection authorization static bearer token */ static const char *oidc_set_client_auth_bearer_token(cmd_parms *cmd, void *struct_ptr, const char *args) { oidc_cfg *cfg = (oidc_cfg *)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; } /* * set the maximum number of parallel state cookies */ static const char *oidc_set_max_number_of_state_cookies(cmd_parms *cmd, void *struct_ptr, const char *arg1, const char *arg2) { oidc_cfg *cfg = (oidc_cfg *)ap_get_module_config(cmd->server->module_config, &auth_openidc_module); const char *rv = oidc_parse_max_number_of_state_cookies( cmd->pool, arg1, arg2, &cfg->max_number_of_state_cookies, &cfg->delete_oldest_state_cookies); return OIDC_CONFIG_DIR_RV(cmd, rv); } /* * return the maximum number of parallel state cookies */ int oidc_cfg_max_number_of_state_cookies(oidc_cfg *cfg) { if (cfg->max_number_of_state_cookies == OIDC_CONFIG_POS_INT_UNSET) return OIDC_DEFAULT_MAX_NUMBER_OF_STATE_COOKIES; return cfg->max_number_of_state_cookies; } /* * return the number of oldest state cookies that need to be deleted */ int oidc_cfg_delete_oldest_state_cookies(oidc_cfg *cfg) { if (cfg->delete_oldest_state_cookies == OIDC_CONFIG_POS_INT_UNSET) return OIDC_DEFAULT_DELETE_OLDEST_STATE_COOKIES; return cfg->delete_oldest_state_cookies; } /* * set the time in seconds that the access token needs to be valid for */ static const char *oidc_set_refresh_access_token_before_expiry(cmd_parms *cmd, void *m, const char *arg1, const char *arg2) { oidc_dir_cfg *dir_cfg = (oidc_dir_cfg *)m; const char *rv1 = oidc_parse_refresh_access_token_before_expiry(cmd->pool, arg1, &dir_cfg->refresh_access_token_before_expiry); if (rv1 != NULL) return apr_psprintf(cmd->pool, "Invalid value for directive '%s': %s", cmd->directive->directive, rv1); if (arg2) { const char *rv2 = oidc_parse_action_on_error_refresh_as(cmd->pool, arg2, &dir_cfg->action_on_error_refresh); return OIDC_CONFIG_DIR_RV(cmd, rv2); } return NULL; } /* * define which header we use for calculating the fingerprint of the state during authentication */ static const char *oidc_set_state_input_headers_as(cmd_parms *cmd, void *m, const char *arg) { oidc_cfg *cfg = (oidc_cfg *)ap_get_module_config(cmd->server->module_config, &auth_openidc_module); const char *rv = oidc_parse_set_state_input_headers_as(cmd->pool, arg, &cfg->state_input_headers); return OIDC_CONFIG_DIR_RV(cmd, rv); } static const char *oidc_set_x_forwarded_headers(cmd_parms *cmd, void *m, const char *arg) { oidc_cfg *cfg = (oidc_cfg *)ap_get_module_config(cmd->server->module_config, &auth_openidc_module); const char *rv = oidc_parse_x_forwarded_headers(cmd->pool, arg, &cfg->x_forwarded_headers); return OIDC_CONFIG_DIR_RV(cmd, rv); } 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_config_check_x_forwarded(request_rec *r, const apr_byte_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_util_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_util_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_util_hdr_in_x_forwarded_proto_get); oidc_check_x_forwarded_hdr(r, x_forwarded_headers, OIDC_HDR_FORWARDED, OIDC_HTTP_HDR_FORWARDED, oidc_util_hdr_in_forwarded_get); } static const char *oidc_set_redirect_urls_allowed(cmd_parms *cmd, void *m, const char *arg) { oidc_cfg *cfg = (oidc_cfg *)ap_get_module_config(cmd->server->module_config, &auth_openidc_module); if (cfg->redirect_urls_allowed == NULL) cfg->redirect_urls_allowed = apr_hash_make(cmd->pool); apr_hash_set(cfg->redirect_urls_allowed, arg, APR_HASH_KEY_STRING, arg); return NULL; } static const char *oidc_set_signed_jwks_uri(cmd_parms *cmd, void *m, const char *arg1, const char *arg2) { oidc_cfg *cfg = (oidc_cfg *)ap_get_module_config(cmd->server->module_config, &auth_openidc_module); const char *rv = NULL; oidc_jose_error_t err; if (_oidc_strcmp(arg1, "") != 0) { rv = oidc_set_url_slot(cmd, cfg, arg1); if (rv != NULL) return OIDC_CONFIG_DIR_RV(cmd, rv); } cfg->provider.jwks_uri.jwk = oidc_jwk_parse(cmd->pool, arg2, &err); if (cfg->provider.jwks_uri.jwk == NULL) { return apr_psprintf(cmd->pool, "oidc_jwk_parse failed: %s", oidc_jose_e2s(cmd->pool, err)); } return NULL; } static const char *oidc_set_post_preserve_templates(cmd_parms *cmd, void *m, const char *arg1, const char *arg2) { oidc_cfg *cfg = (oidc_cfg *)ap_get_module_config(cmd->server->module_config, &auth_openidc_module); if (arg1) cfg->post_preserve_template = apr_pstrdup(cmd->pool, arg1); if (arg2) cfg->post_restore_template = apr_pstrdup(cmd->pool, arg2); return NULL; } static const char *oidc_set_token_revocation_endpoint(cmd_parms *cmd, void *struct_ptr, const char *args) { oidc_cfg *cfg = (oidc_cfg *)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_set_https_slot(cmd, struct_ptr, args); } int oidc_cfg_dir_refresh_access_token_before_expiry(request_rec *r) { oidc_dir_cfg *dir_cfg = ap_get_module_config(r->per_dir_config, &auth_openidc_module); if (dir_cfg->refresh_access_token_before_expiry == OIDC_CONFIG_POS_INT_UNSET) return OIDC_DEFAULT_REFRESH_ACCESS_TOKEN_BEFORE_EXPIRY; return dir_cfg->refresh_access_token_before_expiry; } int oidc_cfg_dir_action_on_error_refresh(request_rec *r) { oidc_dir_cfg *dir_cfg = ap_get_module_config(r->per_dir_config, &auth_openidc_module); if (dir_cfg->action_on_error_refresh == OIDC_CONFIG_POS_INT_UNSET) return OIDC_DEFAULT_ON_ERROR_REFRESH; return dir_cfg->action_on_error_refresh; } char *oidc_cfg_dir_state_cookie_prefix(request_rec *r) { oidc_dir_cfg *dir_cfg = ap_get_module_config(r->per_dir_config, &auth_openidc_module); if ((dir_cfg->state_cookie_prefix == NULL) || ((dir_cfg->state_cookie_prefix != NULL) && (_oidc_strcmp(dir_cfg->state_cookie_prefix, OIDC_CONFIG_STRING_UNSET) == 0))) return OIDC_DEFAULT_STATE_COOKIE_PREFIX; return dir_cfg->state_cookie_prefix; } static void oidc_cfg_provider_destroy(oidc_provider_t *provider) { if (provider->jwks_uri.jwk) oidc_jwk_destroy(provider->jwks_uri.jwk); oidc_jwk_list_destroy(provider->verify_public_keys); oidc_jwk_list_destroy(provider->client_keys); } static apr_status_t oidc_provider_config_cleanup(void *data) { oidc_provider_t *provider = (oidc_provider_t *)data; oidc_cfg_provider_destroy(provider); return APR_SUCCESS; } 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->check_session_iframe = NULL; provider->end_session_endpoint = NULL; provider->jwks_uri.uri = NULL; provider->jwks_uri.refresh_interval = OIDC_DEFAULT_JWKS_REFRESH_INTERVAL; provider->jwks_uri.signed_uri = NULL; provider->jwks_uri.jwk = NULL; provider->verify_public_keys = NULL; provider->backchannel_logout_supported = OIDC_CONFIG_POS_INT_UNSET; provider->ssl_validate_server = OIDC_DEFAULT_SSL_VALIDATE_SERVER; provider->validate_issuer = OIDC_DEFAULT_VALIDATE_ISSUER; provider->client_name = OIDC_DEFAULT_CLIENT_NAME; provider->client_contact = NULL; provider->registration_token = NULL; provider->scope = OIDC_DEFAULT_SCOPE; provider->response_type = OIDC_DEFAULT_RESPONSE_TYPE; provider->response_mode = NULL; provider->idtoken_iat_slack = OIDC_DEFAULT_IDTOKEN_IAT_SLACK; provider->session_max_duration = OIDC_DEFAULT_SESSION_MAX_DURATION; provider->auth_request_params = NULL; provider->logout_request_params = NULL; provider->pkce = &oidc_pkce_s256; 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_USER_INFO_TOKEN_METHOD_HEADER; provider->auth_request_method = OIDC_DEFAULT_AUTH_REQUEST_METHOD; } 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); apr_pool_cleanup_register(pool, provider, oidc_provider_config_cleanup, oidc_provider_config_cleanup); return provider; } static void oidc_merge_provider_config(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_DEFAULT_JWKS_REFRESH_INTERVAL ? 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 = oidc_jwk_copy(pool, add->jwks_uri.jwk != NULL ? add->jwks_uri.jwk : base->jwks_uri.jwk); 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->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_DEFAULT_SSL_VALIDATE_SERVER ? add->ssl_validate_server : base->ssl_validate_server; dst->validate_issuer = add->validate_issuer != OIDC_DEFAULT_VALIDATE_ISSUER ? add->validate_issuer : base->validate_issuer; dst->client_name = _oidc_strcmp(add->client_name, OIDC_DEFAULT_CLIENT_NAME) != 0 ? 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 = _oidc_strcmp(add->scope, OIDC_DEFAULT_SCOPE) != 0 ? add->scope : base->scope; dst->response_type = _oidc_strcmp(add->response_type, OIDC_DEFAULT_RESPONSE_TYPE) != 0 ? 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_DEFAULT_IDTOKEN_IAT_SLACK ? add->idtoken_iat_slack : base->idtoken_iat_slack; dst->session_max_duration = add->session_max_duration != OIDC_DEFAULT_SESSION_MAX_DURATION ? 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 != &oidc_pkce_s256 ? add->pkce : base->pkce; dst->client_jwks_uri = add->client_jwks_uri != NULL ? add->client_jwks_uri : base->client_jwks_uri; dst->client_keys = 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_USER_INFO_TOKEN_METHOD_HEADER ? add->userinfo_token_method : base->userinfo_token_method; dst->auth_request_method = add->auth_request_method != OIDC_DEFAULT_AUTH_REQUEST_METHOD ? add->auth_request_method : base->auth_request_method; dst->userinfo_refresh_interval = add->userinfo_refresh_interval != OIDC_DEFAULT_USERINFO_REFRESH_INTERVAL ? add->userinfo_refresh_interval : base->userinfo_refresh_interval; dst->request_object = add->request_object != NULL ? add->request_object : base->request_object; dst->issuer_specific_redirect_uri = add->issuer_specific_redirect_uri != OIDC_DEFAULT_PROVIDER_ISSUER_SPECIFIC_REDIRECT_URI ? add->issuer_specific_redirect_uri : base->issuer_specific_redirect_uri; } 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_merge_provider_config(pool, dst, dst, src); return dst; } static apr_status_t oidc_destroy_server_config(void *data) { oidc_cfg *cfg = (oidc_cfg *)data; oidc_cfg_provider_destroy(&cfg->provider); oidc_jwk_list_destroy(cfg->oauth.verify_public_keys); oidc_jwk_list_destroy_hash(cfg->oauth.verify_shared_keys); 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_create_server_config(apr_pool_t *pool, server_rec *svr) { oidc_cfg *c = apr_pcalloc(pool, sizeof(oidc_cfg)); apr_pool_cleanup_register(pool, c, oidc_destroy_server_config, oidc_destroy_server_config); 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; oidc_cfg_provider_init(&c->provider); c->oauth.ssl_validate_server = OIDC_DEFAULT_SSL_VALIDATE_SERVER; c->oauth.metadata_url = NULL; c->oauth.client_id = NULL; c->oauth.client_secret = NULL; c->oauth.introspection_endpoint_tls_client_cert = NULL; c->oauth.introspection_endpoint_tls_client_key = NULL; c->oauth.introspection_endpoint_url = NULL; c->oauth.introspection_endpoint_method = OIDC_DEFAULT_OAUTH_ENDPOINT_METHOD; c->oauth.introspection_endpoint_params = NULL; c->oauth.introspection_endpoint_auth = NULL; c->oauth.introspection_client_auth_bearer_token = NULL; c->oauth.introspection_token_param_name = OIDC_DEFAULT_OAUTH_TOKEN_PARAM_NAME; c->oauth.introspection_token_expiry_claim_name = OIDC_DEFAULT_OAUTH_EXPIRY_CLAIM_NAME; c->oauth.introspection_token_expiry_claim_format = OIDC_DEFAULT_OAUTH_EXPIRY_CLAIM_FORMAT; c->oauth.introspection_token_expiry_claim_required = OIDC_DEFAULT_OAUTH_EXPIRY_CLAIM_REQUIRED; c->oauth.remote_user_claim.claim_name = OIDC_DEFAULT_OAUTH_CLAIM_REMOTE_USER; c->oauth.remote_user_claim.reg_exp = NULL; c->oauth.remote_user_claim.replace = NULL; c->oauth.verify_jwks_uri = NULL; c->oauth.verify_public_keys = NULL; c->oauth.verify_shared_keys = NULL; c->cache = &oidc_cache_shm; c->cache_cfg = NULL; c->cache_encrypt = OIDC_CONFIG_POS_INT_UNSET; c->cache_file_dir = NULL; c->cache_file_clean_interval = OIDC_DEFAULT_CACHE_FILE_CLEAN_INTERVAL; #ifdef USE_MEMCACHE c->cache_memcache_servers = NULL; c->cache_memcache_min = 0; c->cache_memcache_smax = 0; c->cache_memcache_hmax = 0; c->cache_memcache_ttl = 0; #endif c->cache_shm_size_max = OIDC_DEFAULT_CACHE_SHM_SIZE; c->cache_shm_entry_size_max = OIDC_DEFAULT_CACHE_SHM_ENTRY_SIZE_MAX; #ifdef USE_LIBHIREDIS c->cache_redis_server = NULL; c->cache_redis_username = NULL; c->cache_redis_password = NULL; c->cache_redis_database = -1; c->cache_redis_connect_timeout = -1; c->cache_redis_timeout = -1; #endif c->metadata_dir = NULL; c->session_type = OIDC_DEFAULT_SESSION_TYPE; c->session_cache_fallback_to_cookie = OIDC_CONFIG_POS_INT_UNSET; c->persistent_session_cookie = 0; c->store_id_token = OIDC_DEFAULT_STORE_ID_TOKEN; c->session_cookie_chunk_size = OIDC_DEFAULT_SESSION_CLIENT_COOKIE_CHUNK_SIZE; c->http_timeout_long.request_timeout = OIDC_DEFAULT_HTTP_REQUEST_TIMEOUT_LONG; 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_DEFAULT_HTTP_REQUEST_TIMEOUT_SHORT; 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_long.retry_interval = OIDC_DEFAULT_HTTP_RETRY_INTERVAL_SHORT; c->state_timeout = OIDC_DEFAULT_STATE_TIMEOUT; 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_DEFAULT_SESSION_INACTIVITY_TIMEOUT; c->cookie_domain = NULL; c->claim_delimiter = OIDC_DEFAULT_CLAIM_DELIMITER; c->claim_prefix = NULL; c->remote_user_claim.claim_name = OIDC_DEFAULT_CLAIM_REMOTE_USER; c->remote_user_claim.reg_exp = NULL; c->remote_user_claim.replace = NULL; c->cookie_http_only = OIDC_DEFAULT_COOKIE_HTTPONLY; c->cookie_same_site = OIDC_DEFAULT_COOKIE_SAME_SITE; 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->error_template = NULL; c->post_preserve_template = NULL; c->post_restore_template = NULL; c->provider.userinfo_refresh_interval = OIDC_DEFAULT_USERINFO_REFRESH_INTERVAL; c->provider.request_object = NULL; c->provider_metadata_refresh_interval = OIDC_DEFAULT_PROVIDER_METADATA_REFRESH_INTERVAL; c->info_hook_data = NULL; c->metrics_hook_data = NULL; c->metrics_path = NULL; c->trace_parent = OIDC_TRACE_PARENT_OFF; c->black_listed_claims = NULL; c->white_listed_claims = NULL; c->filter_claims_expr = NULL; c->provider.issuer_specific_redirect_uri = OIDC_DEFAULT_PROVIDER_ISSUER_SPECIFIC_REDIRECT_URI; c->state_input_headers = OIDC_DEFAULT_STATE_INPUT_HEADERS; c->redirect_urls_allowed = NULL; c->ca_bundle_path = NULL; c->logout_x_frame_options = NULL; c->x_forwarded_headers = OIDC_DEFAULT_X_FORWARDED_HEADERS; c->action_on_userinfo_error = OIDC_ON_ERROR_CONTINUE; c->refresh_mutex = oidc_cache_mutex_create(pool, TRUE); return c; } /* * merge a new server config with a base one */ void *oidc_merge_server_config(apr_pool_t *pool, void *BASE, void *ADD) { oidc_cfg *base = (oidc_cfg *)BASE; oidc_cfg *add = (oidc_cfg *)ADD; oidc_cfg *c = apr_pcalloc(pool, sizeof(oidc_cfg)); apr_pool_cleanup_register(pool, c, oidc_destroy_server_config, oidc_destroy_server_config); c->merged = TRUE; 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); oidc_merge_provider_config(pool, &c->provider, &base->provider, &add->provider); c->oauth.ssl_validate_server = add->oauth.ssl_validate_server != OIDC_DEFAULT_SSL_VALIDATE_SERVER ? add->oauth.ssl_validate_server : base->oauth.ssl_validate_server; c->oauth.metadata_url = add->oauth.metadata_url != NULL ? add->oauth.metadata_url : base->oauth.metadata_url; c->oauth.client_id = add->oauth.client_id != NULL ? add->oauth.client_id : base->oauth.client_id; c->oauth.client_secret = add->oauth.client_secret != NULL ? add->oauth.client_secret : base->oauth.client_secret; c->oauth.introspection_endpoint_tls_client_key = add->oauth.introspection_endpoint_tls_client_key != NULL ? add->oauth.introspection_endpoint_tls_client_key : base->oauth.introspection_endpoint_tls_client_key; c->oauth.introspection_endpoint_tls_client_cert = add->oauth.introspection_endpoint_tls_client_cert != NULL ? add->oauth.introspection_endpoint_tls_client_cert : base->oauth.introspection_endpoint_tls_client_cert; c->oauth.introspection_endpoint_url = add->oauth.introspection_endpoint_url != NULL ? add->oauth.introspection_endpoint_url : base->oauth.introspection_endpoint_url; c->oauth.introspection_endpoint_method = _oidc_strcmp(add->oauth.introspection_endpoint_method, OIDC_DEFAULT_OAUTH_ENDPOINT_METHOD) != 0 ? add->oauth.introspection_endpoint_method : base->oauth.introspection_endpoint_method; c->oauth.introspection_endpoint_params = add->oauth.introspection_endpoint_params != NULL ? add->oauth.introspection_endpoint_params : base->oauth.introspection_endpoint_params; c->oauth.introspection_endpoint_auth = add->oauth.introspection_endpoint_auth != NULL ? add->oauth.introspection_endpoint_auth : base->oauth.introspection_endpoint_auth; c->oauth.introspection_client_auth_bearer_token = add->oauth.introspection_client_auth_bearer_token != NULL ? add->oauth.introspection_client_auth_bearer_token : base->oauth.introspection_client_auth_bearer_token; c->oauth.introspection_token_param_name = _oidc_strcmp(add->oauth.introspection_token_param_name, OIDC_DEFAULT_OAUTH_TOKEN_PARAM_NAME) != 0 ? add->oauth.introspection_token_param_name : base->oauth.introspection_token_param_name; c->oauth.introspection_token_expiry_claim_name = _oidc_strcmp(add->oauth.introspection_token_expiry_claim_name, OIDC_DEFAULT_OAUTH_EXPIRY_CLAIM_NAME) != 0 ? add->oauth.introspection_token_expiry_claim_name : base->oauth.introspection_token_expiry_claim_name; c->oauth.introspection_token_expiry_claim_format = _oidc_strcmp(add->oauth.introspection_token_expiry_claim_format, OIDC_DEFAULT_OAUTH_EXPIRY_CLAIM_FORMAT) != 0 ? add->oauth.introspection_token_expiry_claim_format : base->oauth.introspection_token_expiry_claim_format; c->oauth.introspection_token_expiry_claim_required = add->oauth.introspection_token_expiry_claim_required != OIDC_DEFAULT_OAUTH_EXPIRY_CLAIM_REQUIRED ? add->oauth.introspection_token_expiry_claim_required : base->oauth.introspection_token_expiry_claim_required; c->oauth.remote_user_claim.claim_name = _oidc_strcmp(add->oauth.remote_user_claim.claim_name, OIDC_DEFAULT_OAUTH_CLAIM_REMOTE_USER) != 0 ? add->oauth.remote_user_claim.claim_name : base->oauth.remote_user_claim.claim_name; c->oauth.remote_user_claim.reg_exp = add->oauth.remote_user_claim.reg_exp != NULL ? add->oauth.remote_user_claim.reg_exp : base->oauth.remote_user_claim.reg_exp; c->oauth.remote_user_claim.replace = add->oauth.remote_user_claim.replace != NULL ? add->oauth.remote_user_claim.replace : base->oauth.remote_user_claim.replace; c->oauth.verify_jwks_uri = add->oauth.verify_jwks_uri != NULL ? add->oauth.verify_jwks_uri : base->oauth.verify_jwks_uri; c->oauth.verify_public_keys = oidc_jwk_list_copy(pool, add->oauth.verify_public_keys != NULL ? add->oauth.verify_public_keys : base->oauth.verify_public_keys); c->oauth.verify_shared_keys = add->oauth.verify_shared_keys != NULL ? add->oauth.verify_shared_keys : base->oauth.verify_shared_keys; c->http_timeout_long.request_timeout = add->http_timeout_long.request_timeout != OIDC_DEFAULT_HTTP_REQUEST_TIMEOUT_LONG ? add->http_timeout_long.request_timeout : base->http_timeout_long.request_timeout; c->http_timeout_long.connect_timeout = add->http_timeout_long.connect_timeout != OIDC_DEFAULT_HTTP_CONNECT_TIMEOUT_LONG ? add->http_timeout_long.connect_timeout : base->http_timeout_long.connect_timeout; c->http_timeout_long.retries = add->http_timeout_long.retries != OIDC_DEFAULT_HTTP_RETRIES_LONG ? add->http_timeout_long.retries : base->http_timeout_long.retries; c->http_timeout_long.retry_interval = add->http_timeout_long.retry_interval != OIDC_DEFAULT_HTTP_RETRY_INTERVAL_LONG ? add->http_timeout_long.retry_interval : base->http_timeout_long.retry_interval; c->http_timeout_short.request_timeout = add->http_timeout_short.request_timeout != OIDC_DEFAULT_HTTP_REQUEST_TIMEOUT_SHORT ? add->http_timeout_short.request_timeout : base->http_timeout_short.request_timeout; c->http_timeout_short.connect_timeout = add->http_timeout_short.connect_timeout != OIDC_DEFAULT_HTTP_CONNECT_TIMEOUT_SHORT ? add->http_timeout_short.connect_timeout : base->http_timeout_short.connect_timeout; c->http_timeout_short.retries = add->http_timeout_short.retries != OIDC_DEFAULT_HTTP_RETRIES_SHORT ? add->http_timeout_short.retries : base->http_timeout_short.retries; c->http_timeout_short.retry_interval = add->http_timeout_short.retry_interval != OIDC_DEFAULT_HTTP_RETRY_INTERVAL_SHORT ? add->http_timeout_short.retry_interval : base->http_timeout_short.retry_interval; c->state_timeout = add->state_timeout != OIDC_DEFAULT_STATE_TIMEOUT ? 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_DEFAULT_SESSION_INACTIVITY_TIMEOUT ? add->session_inactivity_timeout : base->session_inactivity_timeout; if (add->cache != &oidc_cache_shm) { c->cache = add->cache; } else { c->cache = base->cache; } c->cache_encrypt = add->cache_encrypt != OIDC_CONFIG_POS_INT_UNSET ? add->cache_encrypt : base->cache_encrypt; c->cache_cfg = NULL; 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_DEFAULT_CACHE_FILE_CLEAN_INTERVAL ? add->cache_file_clean_interval : base->cache_file_clean_interval; #ifdef USE_MEMCACHE 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 ? add->cache_memcache_min : base->cache_memcache_min; c->cache_memcache_smax = add->cache_memcache_smax ? add->cache_memcache_smax : base->cache_memcache_smax; c->cache_memcache_hmax = add->cache_memcache_hmax ? add->cache_memcache_hmax : base->cache_memcache_hmax; c->cache_memcache_ttl = add->cache_memcache_ttl ? add->cache_memcache_ttl : base->cache_memcache_ttl; #endif 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; #ifdef USE_LIBHIREDIS 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 != -1 ? add->cache_redis_database : base->cache_redis_database; c->cache_redis_connect_timeout = add->cache_redis_connect_timeout != -1 ? add->cache_redis_connect_timeout : base->cache_redis_connect_timeout; c->cache_redis_timeout = add->cache_redis_timeout != -1 ? add->cache_redis_timeout : base->cache_redis_timeout; #endif c->metadata_dir = add->metadata_dir != NULL ? add->metadata_dir : base->metadata_dir; c->session_type = add->session_type != OIDC_DEFAULT_SESSION_TYPE ? 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 != 0 ? add->persistent_session_cookie : base->persistent_session_cookie; c->store_id_token = add->store_id_token != OIDC_DEFAULT_STORE_ID_TOKEN ? add->store_id_token : base->store_id_token; c->session_cookie_chunk_size = add->session_cookie_chunk_size != OIDC_DEFAULT_SESSION_CLIENT_COOKIE_CHUNK_SIZE ? 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 = _oidc_strcmp(add->claim_delimiter, OIDC_DEFAULT_CLAIM_DELIMITER) != 0 ? add->claim_delimiter : base->claim_delimiter; c->claim_prefix = add->claim_prefix != NULL ? add->claim_prefix : base->claim_prefix; c->remote_user_claim.claim_name = _oidc_strcmp(add->remote_user_claim.claim_name, OIDC_DEFAULT_CLAIM_REMOTE_USER) != 0 ? add->remote_user_claim.claim_name : base->remote_user_claim.claim_name; c->remote_user_claim.reg_exp = add->remote_user_claim.reg_exp != NULL ? add->remote_user_claim.reg_exp : base->remote_user_claim.reg_exp; c->remote_user_claim.replace = add->remote_user_claim.replace != NULL ? add->remote_user_claim.replace : base->remote_user_claim.replace; c->cookie_http_only = add->cookie_http_only != OIDC_DEFAULT_COOKIE_HTTPONLY ? add->cookie_http_only : base->cookie_http_only; c->cookie_same_site = add->cookie_same_site != OIDC_DEFAULT_COOKIE_SAME_SITE ? add->cookie_same_site : base->cookie_same_site; c->outgoing_proxy.host_port = add->outgoing_proxy.host_port != NULL ? add->outgoing_proxy.host_port : base->outgoing_proxy.host_port; c->outgoing_proxy.username_password = add->outgoing_proxy.username_password != NULL ? add->outgoing_proxy.username_password : base->outgoing_proxy.username_password; c->outgoing_proxy.auth_type = add->outgoing_proxy.auth_type != OIDC_CONFIG_POS_INT_UNSET ? add->outgoing_proxy.auth_type : base->outgoing_proxy.auth_type; c->crypto_passphrase.secret1 = add->crypto_passphrase.secret1 != NULL ? add->crypto_passphrase.secret1 : base->crypto_passphrase.secret1; c->crypto_passphrase.secret2 = add->crypto_passphrase.secret2 != NULL ? add->crypto_passphrase.secret1 : base->crypto_passphrase.secret2; c->error_template = add->error_template != NULL ? add->error_template : base->error_template; 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_DEFAULT_PROVIDER_METADATA_REFRESH_INTERVAL ? 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_TRACE_PARENT_OFF ? add->trace_parent : base->trace_parent; 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_DEFAULT_STATE_INPUT_HEADERS ? 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_DEFAULT_X_FORWARDED_HEADERS ? add->x_forwarded_headers : base->x_forwarded_headers; c->action_on_userinfo_error = add->action_on_userinfo_error != OIDC_ON_ERROR_CONTINUE ? add->action_on_userinfo_error : base->action_on_userinfo_error; c->refresh_mutex = c->refresh_mutex != NULL ? add->refresh_mutex : base->refresh_mutex; return c; } int oidc_cfg_cache_encrypt(request_rec *r) { oidc_cfg *cfg = ap_get_module_config(r->server->module_config, &auth_openidc_module); if (cfg->cache_encrypt == OIDC_CONFIG_POS_INT_UNSET) return cfg->cache->encrypt_by_default; return cfg->cache_encrypt; } int oidc_cfg_session_cache_fallback_to_cookie(request_rec *r) { oidc_cfg *cfg = ap_get_module_config(r->server->module_config, &auth_openidc_module); if (cfg->session_cache_fallback_to_cookie == OIDC_CONFIG_POS_INT_UNSET) return 0; return cfg->session_cache_fallback_to_cookie; } static const char *oidc_set_html_error_template(cmd_parms *cmd, void *struct_ptr, const char *arg) { oidc_cfg *cfg = (oidc_cfg *)ap_get_module_config(cmd->server->module_config, &auth_openidc_module); oidc_swarn( cmd->server, OIDCHTMLErrorTemplate " is deprecated; please use the standard Apache features to deal with the " OIDC_ERROR_ENVVAR " and " OIDC_ERROR_DESC_ENVVAR " environment variables set by this module, see: https://httpd.apache.org/docs/2.4/custom-error.html"); return ap_set_string_slot(cmd, cfg, arg); } /* * create a new directory config record with defaults */ void *oidc_create_dir_config(apr_pool_t *pool, char *path) { oidc_dir_cfg *c = apr_pcalloc(pool, sizeof(oidc_dir_cfg)); c->discover_url = OIDC_CONFIG_STRING_UNSET; c->cookie = OIDC_CONFIG_STRING_UNSET; c->cookie_path = OIDC_CONFIG_STRING_UNSET; c->authn_header = OIDC_CONFIG_STRING_UNSET; 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_headers = OIDC_CONFIG_POS_INT_UNSET; c->pass_info_in_env_vars = OIDC_CONFIG_POS_INT_UNSET; c->pass_info_as = 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 = OIDC_CONFIG_STRING_UNSET; c->pass_userinfo_as = NULL; c->pass_idtoken_as = OIDC_CONFIG_POS_INT_UNSET; return (c); } char *oidc_cfg_dir_discover_url(request_rec *r) { oidc_dir_cfg *dir_cfg = ap_get_module_config(r->per_dir_config, &auth_openidc_module); if ((dir_cfg->discover_url != NULL) && (_oidc_strcmp(dir_cfg->discover_url, OIDC_CONFIG_STRING_UNSET) == 0)) return NULL; return dir_cfg->discover_url; } char *oidc_cfg_dir_cookie(request_rec *r) { oidc_dir_cfg *dir_cfg = ap_get_module_config(r->per_dir_config, &auth_openidc_module); if ((dir_cfg->cookie == NULL) || ((dir_cfg->cookie != NULL) && (_oidc_strcmp(dir_cfg->cookie, OIDC_CONFIG_STRING_UNSET) == 0))) return OIDC_DEFAULT_COOKIE; return dir_cfg->cookie; } char *oidc_cfg_dir_cookie_path(request_rec *r) { oidc_dir_cfg *dir_cfg = ap_get_module_config(r->per_dir_config, &auth_openidc_module); if ((dir_cfg->cookie_path == NULL) || ((dir_cfg->cookie_path != NULL) && (_oidc_strcmp(dir_cfg->cookie_path, OIDC_CONFIG_STRING_UNSET) == 0))) return OIDC_DEFAULT_COOKIE_PATH; return dir_cfg->cookie_path; } char *oidc_cfg_dir_authn_header(request_rec *r) { oidc_dir_cfg *dir_cfg = ap_get_module_config(r->per_dir_config, &auth_openidc_module); if ((dir_cfg->authn_header == NULL) || ((dir_cfg->authn_header != NULL) && (_oidc_strcmp(dir_cfg->authn_header, OIDC_CONFIG_STRING_UNSET) == 0))) return OIDC_DEFAULT_AUTHN_HEADER; return dir_cfg->authn_header; } apr_byte_t oidc_cfg_dir_pass_info_in_headers(request_rec *r) { oidc_dir_cfg *dir_cfg = ap_get_module_config(r->per_dir_config, &auth_openidc_module); if (dir_cfg->pass_info_in_headers == OIDC_CONFIG_POS_INT_UNSET) return OIDC_DEFAULT_PASS_APP_INFO_IN_HEADERS; return dir_cfg->pass_info_in_headers; } apr_byte_t oidc_cfg_dir_pass_info_in_envvars(request_rec *r) { oidc_dir_cfg *dir_cfg = ap_get_module_config(r->per_dir_config, &auth_openidc_module); if (dir_cfg->pass_info_in_env_vars == OIDC_CONFIG_POS_INT_UNSET) return OIDC_DEFAULT_PASS_APP_INFO_IN_ENVVARS; return dir_cfg->pass_info_in_env_vars; } int oidc_cfg_dir_pass_info_encoding(request_rec *r) { oidc_dir_cfg *dir_cfg = ap_get_module_config(r->per_dir_config, &auth_openidc_module); if (dir_cfg->pass_info_as == OIDC_CONFIG_POS_INT_UNSET) return OIDC_DEFAULT_PASS_APP_INFO_HDR_AS; return dir_cfg->pass_info_as; } apr_byte_t oidc_cfg_dir_pass_access_token(request_rec *r) { oidc_dir_cfg *dir_cfg = ap_get_module_config(r->per_dir_config, &auth_openidc_module); if (dir_cfg->pass_access_token == OIDC_CONFIG_POS_INT_UNSET) return OIDC_DEFAULT_PASS_ACCESS_TOKEN; return dir_cfg->pass_access_token; } apr_byte_t oidc_cfg_dir_pass_refresh_token(request_rec *r) { oidc_dir_cfg *dir_cfg = ap_get_module_config(r->per_dir_config, &auth_openidc_module); if (dir_cfg->pass_refresh_token == OIDC_CONFIG_POS_INT_UNSET) return OIDC_DEFAULT_PASS_REFRESH_TOKEN; return dir_cfg->pass_refresh_token; } apr_byte_t oidc_cfg_dir_accept_token_in(request_rec *r) { oidc_dir_cfg *dir_cfg = ap_get_module_config(r->per_dir_config, &auth_openidc_module); if (dir_cfg->oauth_accept_token_in == OIDC_CONFIG_POS_INT_UNSET) return OIDC_OAUTH_ACCEPT_TOKEN_IN_DEFAULT; return dir_cfg->oauth_accept_token_in; } char *oidc_cfg_dir_accept_token_in_option(request_rec *r, const char *key) { oidc_dir_cfg *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); } int oidc_cfg_token_introspection_interval(request_rec *r) { oidc_dir_cfg *dir_cfg = ap_get_module_config(r->per_dir_config, &auth_openidc_module); if (dir_cfg->oauth_token_introspect_interval <= -2) return OIDC_DEFAULT_TOKEN_INTROSPECTION_INTERVAL; return dir_cfg->oauth_token_introspect_interval; } int oidc_cfg_dir_preserve_post(request_rec *r) { oidc_dir_cfg *dir_cfg = ap_get_module_config(r->per_dir_config, &auth_openidc_module); if (dir_cfg->preserve_post == OIDC_CONFIG_POS_INT_UNSET) return OIDC_DEFAULT_PRESERVE_POST; return dir_cfg->preserve_post; } apr_array_header_t *oidc_dir_cfg_pass_cookies(request_rec *r) { oidc_dir_cfg *dir_cfg = ap_get_module_config(r->per_dir_config, &auth_openidc_module); return dir_cfg->pass_cookies; } apr_array_header_t *oidc_dir_cfg_strip_cookies(request_rec *r) { oidc_dir_cfg *dir_cfg = ap_get_module_config(r->per_dir_config, &auth_openidc_module); return dir_cfg->strip_cookies; } int oidc_dir_cfg_unauth_action(request_rec *r) { oidc_dir_cfg *dir_cfg = ap_get_module_config(r->per_dir_config, &auth_openidc_module); const char *rv = NULL; if (dir_cfg->unauth_action == OIDC_CONFIG_POS_INT_UNSET) return OIDC_DEFAULT_UNAUTH_ACTION; if (dir_cfg->unauth_expression == NULL) return dir_cfg->unauth_action; rv = oidc_util_apr_expr_exec(r, dir_cfg->unauth_expression, FALSE); return (rv != NULL) ? dir_cfg->unauth_action : OIDC_DEFAULT_UNAUTH_ACTION; } apr_byte_t oidc_dir_cfg_unauth_expr_is_set(request_rec *r) { oidc_dir_cfg *dir_cfg = ap_get_module_config(r->per_dir_config, &auth_openidc_module); return (dir_cfg->unauth_expression != NULL) ? TRUE : FALSE; } int oidc_dir_cfg_unautz_action(request_rec *r) { oidc_dir_cfg *dir_cfg = ap_get_module_config(r->per_dir_config, &auth_openidc_module); if (dir_cfg->unautz_action == OIDC_CONFIG_POS_INT_UNSET) return OIDC_DEFAULT_UNAUTZ_ACTION; return dir_cfg->unautz_action; } char *oidc_dir_cfg_unauthz_arg(request_rec *r) { oidc_dir_cfg *dir_cfg = ap_get_module_config(r->per_dir_config, &auth_openidc_module); return dir_cfg->unauthz_arg; } const char *oidc_dir_cfg_path_auth_request_params(request_rec *r) { oidc_dir_cfg *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); } static apr_array_header_t *pass_userinfo_as_default = NULL; apr_array_header_t *oidc_dir_cfg_pass_user_info_as(request_rec *r) { oidc_dir_cfg *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_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; } int oidc_dir_cfg_pass_id_token_as(request_rec *r) { oidc_dir_cfg *dir_cfg = ap_get_module_config(r->per_dir_config, &auth_openidc_module); if (dir_cfg->pass_idtoken_as == OIDC_CONFIG_POS_INT_UNSET) return OIDC_DEFAULT_PASS_IDTOKEN_AS; return dir_cfg->pass_idtoken_as; } const char *oidc_dir_cfg_userinfo_claims_expr(request_rec *r) { oidc_dir_cfg *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); } const char *oidc_dir_cfg_path_scope(request_rec *r) { oidc_dir_cfg *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); } /* * merge a new directory config with a base one */ void *oidc_merge_dir_config(apr_pool_t *pool, void *BASE, void *ADD) { oidc_dir_cfg *c = apr_pcalloc(pool, sizeof(oidc_dir_cfg)); oidc_dir_cfg *base = BASE; oidc_dir_cfg *add = ADD; c->discover_url = (_oidc_strcmp(add->discover_url, OIDC_CONFIG_STRING_UNSET) != 0) ? add->discover_url : base->discover_url; c->cookie = (_oidc_strcmp(add->cookie, OIDC_CONFIG_STRING_UNSET) != 0) ? add->cookie : base->cookie; c->cookie_path = (_oidc_strcmp(add->cookie_path, OIDC_CONFIG_STRING_UNSET) != 0) ? add->cookie_path : base->cookie_path; c->authn_header = (_oidc_strcmp(add->authn_header, OIDC_CONFIG_STRING_UNSET) != 0) ? 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_headers = add->pass_info_in_headers != OIDC_CONFIG_POS_INT_UNSET ? add->pass_info_in_headers : base->pass_info_in_headers; c->pass_info_in_env_vars = add->pass_info_in_env_vars != OIDC_CONFIG_POS_INT_UNSET ? add->pass_info_in_env_vars : base->pass_info_in_env_vars; c->pass_info_as = add->pass_info_as != OIDC_CONFIG_POS_INT_UNSET ? add->pass_info_as : base->pass_info_as; 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 = (_oidc_strcmp(add->state_cookie_prefix, OIDC_CONFIG_STRING_UNSET) != 0) ? add->state_cookie_prefix : base->state_cookie_prefix; return (c); } /* * 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 *c) { apr_uri_t r_uri; apr_byte_t redirect_uri_is_relative; if ((c->metadata_dir == NULL) && (c->provider.issuer == NULL) && (c->provider.metadata_url == NULL)) { oidc_serror(s, "one of '" OIDCProviderIssuer "', '" OIDCProviderMetadataURL "' or '" OIDCMetadataDir "' must be set"); return HTTP_INTERNAL_SERVER_ERROR; } if (c->redirect_uri == NULL) return oidc_check_config_error(s, OIDCRedirectURI); redirect_uri_is_relative = (c->redirect_uri[0] == OIDC_CHAR_FORWARD_SLASH); if (c->crypto_passphrase.secret1 == NULL) return oidc_check_config_error(s, OIDCCryptoPassphrase); if (c->metadata_dir == NULL) { if (c->provider.metadata_url == NULL) { if (c->provider.issuer == NULL) return oidc_check_config_error(s, OIDCProviderIssuer); if (c->provider.authorization_endpoint_url == NULL) return oidc_check_config_error(s, OIDCProviderAuthorizationEndpoint); } else { apr_uri_parse(s->process->pconf, c->provider.metadata_url, &r_uri); if ((r_uri.scheme == NULL) || (_oidc_strcmp(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 (c->provider.client_id == NULL) return oidc_check_config_error(s, OIDCClientID); } else { if (c->provider.metadata_url != NULL) { oidc_serror(s, "only one of '" OIDCProviderMetadataURL "' or '" OIDCMetadataDir "' should be set"); return HTTP_INTERNAL_SERVER_ERROR; } } apr_uri_parse(s->process->pconf, c->redirect_uri, &r_uri); if (!redirect_uri_is_relative) { if (_oidc_strcmp(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 (c->cookie_domain != 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, c->cookie_domain)) { 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!", c->cookie_domain, r_uri.hostname, c->redirect_uri); 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 *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 (c->oauth.metadata_url != NULL) { apr_uri_parse(s->process->pconf, c->oauth.metadata_url, &r_uri); if ((r_uri.scheme == NULL) || (_oidc_strcmp(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 (c->oauth.introspection_endpoint_url == NULL) { if ((c->oauth.verify_jwks_uri == NULL) && (c->oauth.verify_public_keys == NULL) && (c->oauth.verify_shared_keys == NULL)) { oidc_serror(s, "one of '" OIDCOAuthServerMetadataURL "', '" OIDCOAuthIntrospectionEndpoint "', '" OIDCOAuthVerifyJwksUri "', '" OIDCOAuthVerifySharedKeys "' or '" OIDCOAuthVerifyCertFiles "' must be set"); return HTTP_INTERNAL_SERVER_ERROR; } } else if ((c->oauth.verify_jwks_uri != NULL) || (c->oauth.verify_public_keys != NULL) || (c->oauth.verify_shared_keys != 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 ((c->cache_encrypt == 1) && (c->crypto_passphrase.secret1 == 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 *cfg = ap_get_module_config(s->module_config, &auth_openidc_module); oidc_sdebug(s, "enter"); if ((cfg->metadata_dir != NULL) || (cfg->provider.issuer != NULL) || (cfg->provider.metadata_url != NULL)) { if (oidc_check_config_openid_openidc(s, cfg) != OK) return HTTP_INTERNAL_SERVER_ERROR; } if ((cfg->oauth.metadata_url != NULL) || (cfg->oauth.client_id != NULL) || (cfg->oauth.client_secret != NULL) || (cfg->oauth.introspection_endpoint_url != NULL) || (cfg->oauth.verify_jwks_uri != NULL) || (cfg->oauth.verify_public_keys != NULL) || (cfg->oauth.verify_shared_keys != 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 *cfg = ap_get_module_config(s->module_config, &auth_openidc_module); if (cfg->merged) { 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 *cfg = ap_get_module_config(s->module_config, &auth_openidc_module); if (cfg->merged) { 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 */ static apr_status_t oidc_cleanup_child(void *data) { server_rec *sp = (server_rec *)data; while (sp != NULL) { oidc_cfg *cfg = (oidc_cfg *)ap_get_module_config(sp->module_config, &auth_openidc_module); if (cfg->cache->destroy != NULL) { if (cfg->cache->destroy(sp) != APR_SUCCESS) { oidc_serror(sp, "cache destroy function failed"); } } if (cfg->refresh_mutex != NULL) { if (oidc_cache_mutex_destroy(sp, cfg->refresh_mutex) != TRUE) { oidc_serror(sp, "oidc_cache_mutex_destroy on refresh mutex failed"); } } if (cfg->metrics_hook_data != NULL) { if (oidc_metrics_cache_cleanup(sp) != APR_SUCCESS) { oidc_serror(sp, "oidc_metrics_cache_cleanup failed"); } } sp = sp->next; } return APR_SUCCESS; } 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(); curl_global_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; } 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", #ifdef USE_MEMCACHE "yes" #else "no" #endif , #ifdef USE_LIBHIREDIS "yes" #else "no" #endif , #ifdef USE_LIBJQ "yes" #else "no" #endif ); curl_global_init(CURL_GLOBAL_ALL); #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 *cfg = (oidc_cfg *)ap_get_module_config(sp->module_config, &auth_openidc_module); if (cfg->cache->post_config != NULL) { if (cfg->cache->post_config(sp) != OK) return HTTP_INTERNAL_SERVER_ERROR; } if (cfg->refresh_mutex != NULL) { if (oidc_cache_mutex_post_config(sp, cfg->refresh_mutex, "refresh") != TRUE) return HTTP_INTERNAL_SERVER_ERROR; } if (cfg->metrics_hook_data != NULL) { if (oidc_metrics_cache_post_config(s) != TRUE) 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 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_checker_claim, &oidc_parse_config, }; #ifdef USE_LIBJQ static const authz_provider oidc_authz_claims_expr_provider = { &oidc_authz_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 *cfg = (oidc_cfg *)ap_get_module_config(sp->module_config, &auth_openidc_module); if (cfg->cache->child_init != NULL) { if (cfg->cache->child_init(p, sp) != APR_SUCCESS) { oidc_serror(sp, "cfg->cache->child_init failed"); } } if (cfg->refresh_mutex != NULL) { if (oidc_cache_mutex_child_init(p, sp, cfg->refresh_mutex) != APR_SUCCESS) { oidc_serror(sp, "oidc_cache_mutex_child_init on refresh mutex failed"); } } if (cfg->metrics_hook_data != NULL) { if (oidc_metrics_cache_child_init(p, s) != APR_SUCCESS) { oidc_serror(sp, "oidc_metrics_cache_child_init failed"); } } sp = sp->next; } apr_pool_cleanup_register(p, s, oidc_cleanup_child, apr_pool_cleanup_null); } static const char oidcFilterName[] = "oidc_filter_in_filter"; 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; 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_util_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_util_hdr_in_content_length_get(f->r) != NULL) oidc_util_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; } /* * initialize before the post config handler runs */ void oidc_pre_config_init() { #if OPENSSL_VERSION_NUMBER >= 0x10100000L OPENSSL_init_crypto(0, NULL); #else ERR_load_crypto_strings(); OpenSSL_add_all_algorithms(); OpenSSL_add_all_digests(); #endif } /* * register our authentication and authorization functions */ 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_auth_checker, NULL, authzSucc, APR_HOOK_MIDDLE); #endif } // clang-format off /* * set of configuration primitives */ const command_rec oidc_config_cmds[] = { AP_INIT_TAKE1(OIDCProviderMetadataURL, oidc_set_url_slot, (void*)APR_OFFSETOF(oidc_cfg, provider.metadata_url), RSRC_CONF, "OpenID Connect OP configuration metadata URL."), AP_INIT_TAKE1(OIDCProviderIssuer, oidc_set_string_slot, (void*)APR_OFFSETOF(oidc_cfg, provider.issuer), RSRC_CONF, "OpenID Connect OP issuer identifier."), AP_INIT_TAKE1(OIDCProviderAuthorizationEndpoint, oidc_set_https_slot, (void *)APR_OFFSETOF(oidc_cfg, provider.authorization_endpoint_url), RSRC_CONF, "Define the OpenID OP Authorization Endpoint URL (e.g.: https://localhost:9031/as/authorization.oauth2)"), AP_INIT_TAKE1(OIDCProviderTokenEndpoint, oidc_set_https_slot, (void *)APR_OFFSETOF(oidc_cfg, provider.token_endpoint_url), RSRC_CONF, "Define the OpenID OP Token Endpoint URL (e.g.: https://localhost:9031/as/token.oauth2)"), AP_INIT_TAKE1(OIDCProviderTokenEndpointAuth, oidc_set_endpoint_auth_slot, (void *)APR_OFFSETOF(oidc_cfg, provider.token_endpoint_auth), RSRC_CONF, "Specify an authentication method for the OpenID OP Token Endpoint (e.g.: client_secret_basic)"), AP_INIT_TAKE1(OIDCProviderTokenEndpointParams, oidc_set_string_slot, (void *)APR_OFFSETOF(oidc_cfg, provider.token_endpoint_params), RSRC_CONF, "Define extra parameters that will be posted to the OpenID OP Token Endpoint (e.g.: param1=value1¶m2=value2, all urlencoded)."), AP_INIT_TAKE1(OIDCProviderRegistrationEndpointJson, oidc_set_string_slot, (void *)APR_OFFSETOF(oidc_cfg, provider.registration_endpoint_json), RSRC_CONF, "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\"] })."), AP_INIT_TAKE1(OIDCProviderUserInfoEndpoint, oidc_set_https_slot, (void *)APR_OFFSETOF(oidc_cfg, provider.userinfo_endpoint_url), RSRC_CONF, "Define the OpenID OP UserInfo Endpoint URL (e.g.: https://localhost:9031/idp/userinfo.openid)"), AP_INIT_RAW_ARGS(OIDCProviderRevocationEndpoint, oidc_set_token_revocation_endpoint, (void *)APR_OFFSETOF(oidc_cfg, provider.revocation_endpoint_url), RSRC_CONF, "Define the RFC 7009 Token Revocation Endpoint URL (e.g.: https://localhost:9031/as/revoke_token.oauth2)"), AP_INIT_TAKE1(OIDCProviderCheckSessionIFrame, oidc_set_url_slot, (void *)APR_OFFSETOF(oidc_cfg, provider.check_session_iframe), RSRC_CONF, "Define the OpenID OP Check Session iFrame URL."), AP_INIT_TAKE1(OIDCProviderEndSessionEndpoint, oidc_set_url_slot, (void *)APR_OFFSETOF(oidc_cfg, provider.end_session_endpoint), RSRC_CONF, "Define the OpenID OP End Session Endpoint URL."), AP_INIT_FLAG(OIDCProviderBackChannelLogoutSupported, oidc_set_flag_slot, (void *)APR_OFFSETOF(oidc_cfg, provider.backchannel_logout_supported), RSRC_CONF, "Define whether the OP supports OpenID Connect Back Channel Logout."), AP_INIT_TAKE1(OIDCProviderJwksUri, oidc_set_https_slot, (void *)APR_OFFSETOF(oidc_cfg, provider.jwks_uri.uri), RSRC_CONF, "Define the OpenID OP JWKS URL (e.g.: https://localhost:9031/pf/JWKS)"), AP_INIT_TAKE2(OIDCProviderSignedJwksUri, oidc_set_signed_jwks_uri, (void *)APR_OFFSETOF(oidc_cfg, provider.jwks_uri.signed_uri), RSRC_CONF, "Define the OpenID Connect OP Signed JWKS URI and a JWK that can be used to verify the data on that URL."), AP_INIT_ITERATE(OIDCProviderVerifyCertFiles, oidc_set_public_key_files, (void*)APR_OFFSETOF(oidc_cfg, provider.verify_public_keys), RSRC_CONF, "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."), AP_INIT_TAKE1(OIDCResponseType, oidc_set_response_type, (void *)APR_OFFSETOF(oidc_cfg, provider.response_type), RSRC_CONF, "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)"), AP_INIT_TAKE1(OIDCResponseMode, oidc_set_response_mode, (void *)APR_OFFSETOF(oidc_cfg, provider.response_mode), RSRC_CONF, "The response mode used; must be one of \"fragment\", \"query\" or \"form_post\" (serves as default value for discovered OPs too)"), AP_INIT_ITERATE(OIDCPublicKeyFiles, oidc_set_public_key_files, (void *)APR_OFFSETOF(oidc_cfg, public_keys), RSRC_CONF, "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."), AP_INIT_ITERATE(OIDCPrivateKeyFiles, oidc_set_private_key_files_enc, NULL, RSRC_CONF, "The fully 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."), AP_INIT_TAKE1(OIDCClientJwksUri, oidc_set_https_slot, (void *)APR_OFFSETOF(oidc_cfg, provider.client_jwks_uri), RSRC_CONF, "Define the Client JWKS URL (e.g.: https://localhost/protected/?jwks=rsa)"), AP_INIT_TAKE1(OIDCIDTokenSignedResponseAlg, oidc_set_signed_response_alg, (void *)APR_OFFSETOF(oidc_cfg, provider.id_token_signed_response_alg), RSRC_CONF, "The algorithm that the OP must use to sign the ID token."), AP_INIT_TAKE1(OIDCIDTokenEncryptedResponseAlg, oidc_set_encrypted_response_alg, (void *)APR_OFFSETOF(oidc_cfg, provider.id_token_encrypted_response_alg), RSRC_CONF, "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]"), AP_INIT_TAKE1(OIDCIDTokenEncryptedResponseEnc, oidc_set_encrypted_response_enc, (void *)APR_OFFSETOF(oidc_cfg, provider.id_token_encrypted_response_enc), RSRC_CONF, "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]"), AP_INIT_TAKE1(OIDCUserInfoSignedResponseAlg, oidc_set_signed_response_alg, (void *)APR_OFFSETOF(oidc_cfg, provider.userinfo_signed_response_alg), RSRC_CONF, "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]"), AP_INIT_TAKE1(OIDCUserInfoEncryptedResponseAlg, oidc_set_encrypted_response_alg, (void *)APR_OFFSETOF(oidc_cfg, provider.userinfo_encrypted_response_alg), RSRC_CONF, "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]"), AP_INIT_TAKE1(OIDCUserInfoEncryptedResponseEnc, oidc_set_encrypted_response_enc, (void *)APR_OFFSETOF(oidc_cfg, provider.userinfo_encrypted_response_enc), RSRC_CONF, "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]"), AP_INIT_TAKE1(OIDCUserInfoTokenMethod, oidc_set_userinfo_token_method, (void *)APR_OFFSETOF(oidc_cfg, provider.userinfo_token_method), RSRC_CONF, "The method that is used to present the access token to the userinfo endpoint; must be one of [authz_header|post_param]"), AP_INIT_TAKE1(OIDCSSLValidateServer, oidc_set_ssl_validate_slot, (void*)APR_OFFSETOF(oidc_cfg, provider.ssl_validate_server), RSRC_CONF, "Require validation of the OpenID Connect OP SSL server certificate for successful authentication (On or Off)"), AP_INIT_TAKE1(OIDCValidateIssuer, oidc_set_validate_issuer_slot, (void*)APR_OFFSETOF(oidc_cfg, provider.validate_issuer), RSRC_CONF, "Require validation of token issuer for successful authentication (On or Off)"), AP_INIT_TAKE1(OIDCClientName, oidc_set_string_slot, (void *) APR_OFFSETOF(oidc_cfg, provider.client_name), RSRC_CONF, "Define the (client_name) name that the client uses for dynamic registration to the OP."), AP_INIT_TAKE1(OIDCClientContact, oidc_set_string_slot, (void *) APR_OFFSETOF(oidc_cfg, provider.client_contact), RSRC_CONF, "Define the contact that the client registers in dynamic registration with the OP."), AP_INIT_TAKE1(OIDCScope, oidc_set_string_slot, (void *) APR_OFFSETOF(oidc_cfg, provider.scope), RSRC_CONF, "Define the OpenID Connect scope that is requested from the OP."), AP_INIT_TAKE1(OIDCPathScope, oidc_set_path_scope, (void*)APR_OFFSETOF(oidc_dir_cfg, path_scope_expr), RSRC_CONF|ACCESS_CONF|OR_AUTHCFG, "Define the OpenID Connect scope that is requested from all providers for a specific path/context."), AP_INIT_TAKE1(OIDCJWKSRefreshInterval, oidc_set_jwks_refresh_interval, (void*)APR_OFFSETOF(oidc_cfg, provider.jwks_uri.refresh_interval), RSRC_CONF, "Duration in seconds after which retrieved JWS should be refreshed."), AP_INIT_TAKE1(OIDCIDTokenIatSlack, oidc_set_idtoken_iat_slack, (void*)APR_OFFSETOF(oidc_cfg, provider.idtoken_iat_slack), RSRC_CONF, "Acceptable offset (both before and after) for checking the \"iat\" (= issued at) timestamp in the id_token."), AP_INIT_TAKE1(OIDCSessionMaxDuration, oidc_set_session_max_duration, (void*)APR_OFFSETOF(oidc_cfg, provider.session_max_duration), RSRC_CONF, "Maximum duration of a session in seconds."), AP_INIT_TAKE1(OIDCAuthRequestParams, oidc_set_string_slot, (void*)APR_OFFSETOF(oidc_cfg, provider.auth_request_params), RSRC_CONF, "Extra parameters that need to be sent in the Authorization Request (must be query-encoded like \"display=popup&prompt=consent\"."), AP_INIT_TAKE1(OIDCLogoutRequestParams, oidc_set_string_slot, (void*)APR_OFFSETOF(oidc_cfg, provider.logout_request_params), RSRC_CONF, "Extra parameters that need to be sent in the Logout Request (must be query-encoded like \"client_id=myclient&prompt=none\"."), AP_INIT_TAKE1(OIDCPathAuthRequestParams, oidc_set_path_auth_request_params, (void*)APR_OFFSETOF(oidc_dir_cfg, path_auth_request_expr), RSRC_CONF|ACCESS_CONF|OR_AUTHCFG, "Extra parameters that need to be sent in the Authorization Request (must be query-encoded like \"display=popup&prompt=consent\"."), AP_INIT_TAKE1(OIDCPKCEMethod, oidc_set_pkce_method, (void *)APR_OFFSETOF(oidc_cfg, provider.pkce), RSRC_CONF, "The RFC 7636 PCKE mode used; must be one of \"plain\" or \"S256\""), AP_INIT_TAKE1(OIDCClientID, oidc_set_string_slot, (void*)APR_OFFSETOF(oidc_cfg, provider.client_id), RSRC_CONF, "Client identifier used in calls to OpenID Connect OP."), AP_INIT_TAKE1(OIDCClientSecret, oidc_set_passphrase_slot, (void*)APR_OFFSETOF(oidc_cfg, provider.client_secret), RSRC_CONF, "Client secret used in calls to OpenID Connect OP."), AP_INIT_TAKE1(OIDCClientTokenEndpointCert, oidc_set_string_slot, (void*)APR_OFFSETOF(oidc_cfg, provider.token_endpoint_tls_client_cert), RSRC_CONF, "TLS client certificate used for calls to OpenID Connect OP token endpoint."), AP_INIT_TAKE1(OIDCClientTokenEndpointKey, oidc_set_string_slot, (void*)APR_OFFSETOF(oidc_cfg, provider.token_endpoint_tls_client_key), RSRC_CONF, "TLS client certificate private key used for calls to OpenID Connect OP token endpoint."), AP_INIT_TAKE1(OIDCClientTokenEndpointKeyPassword, oidc_set_passphrase_slot, (void*)APR_OFFSETOF(oidc_cfg, provider.token_endpoint_tls_client_key_pwd), RSRC_CONF, "TLS client certificate private key password used for calls to OpenID Connect OP token endpoint."), AP_INIT_TAKE1(OIDCRedirectURI, oidc_set_relative_or_absolute_url_slot, (void *)APR_OFFSETOF(oidc_cfg, redirect_uri), RSRC_CONF, "Define the Redirect URI (e.g.: https://localhost:9031/protected/example/)"), AP_INIT_TAKE1(OIDCDefaultURL, oidc_set_relative_or_absolute_url_slot, (void *)APR_OFFSETOF(oidc_cfg, default_sso_url), RSRC_CONF, "Defines the default URL where the user is directed to in case of 3rd-party initiated SSO."), AP_INIT_TAKE1(OIDCDefaultLoggedOutURL, oidc_set_relative_or_absolute_url_slot, (void *)APR_OFFSETOF(oidc_cfg, default_slo_url), RSRC_CONF, "Defines the default URL where the user is directed to after logout."), AP_INIT_TAKE1(OIDCCookieDomain, oidc_set_cookie_domain, NULL, RSRC_CONF, "Specify domain element for OIDC session cookie."), AP_INIT_FLAG(OIDCCookieHTTPOnly, oidc_set_flag_slot, (void *) APR_OFFSETOF(oidc_cfg, cookie_http_only), RSRC_CONF, "Defines whether or not the cookie httponly flag is set on cookies."), AP_INIT_FLAG(OIDCCookieSameSite, oidc_set_flag_slot, (void *) APR_OFFSETOF(oidc_cfg, cookie_same_site), RSRC_CONF, "Defines whether or not the cookie Same-Site flag is set on cookies."), AP_INIT_TAKE123(OIDCOutgoingProxy, oidc_set_outgoing_proxy_slot, (void*)APR_OFFSETOF(oidc_cfg, outgoing_proxy), RSRC_CONF, "Specify an outgoing proxy for your network ([:]."), AP_INIT_TAKE12(OIDCCryptoPassphrase, oidc_set_crypto_passphrase_slot, (void*)APR_OFFSETOF(oidc_cfg, crypto_passphrase), RSRC_CONF, "Passphrase used for AES crypto on cookies and state."), AP_INIT_TAKE1(OIDCClaimDelimiter, oidc_set_string_slot, (void*)APR_OFFSETOF(oidc_cfg, claim_delimiter), RSRC_CONF, "The delimiter to use when setting multi-valued claims in the HTTP headers."), AP_INIT_RAW_ARGS(OIDCClaimPrefix, oidc_cfg_set_claim_prefix, (void*)APR_OFFSETOF(oidc_cfg, claim_prefix), RSRC_CONF, "The prefix to use when setting claims in the HTTP headers."), AP_INIT_TAKE123(OIDCRemoteUserClaim, oidc_set_remote_user_claim, (void*)APR_OFFSETOF(oidc_cfg, remote_user_claim), RSRC_CONF, "The claim that is used when setting the REMOTE_USER variable for OpenID Connect protected paths."), AP_INIT_TAKE1(OIDCOAuthClientID, oidc_set_string_slot, (void*)APR_OFFSETOF(oidc_cfg, oauth.client_id), RSRC_CONF, "Client identifier used in calls to OAuth 2.0 Authorization server validation calls."), AP_INIT_TAKE1(OIDCOAuthClientSecret, oidc_set_string_slot, (void*)APR_OFFSETOF(oidc_cfg, oauth.client_secret), RSRC_CONF, "Client secret used in calls to OAuth 2.0 Authorization server validation calls."), AP_INIT_TAKE1(OIDCOAuthIntrospectionEndpoint, oidc_set_https_slot, (void *)APR_OFFSETOF(oidc_cfg, oauth.introspection_endpoint_url), RSRC_CONF, "Define the OAuth AS Introspection Endpoint URL (e.g.: https://localhost:9031/as/token.oauth2)"), AP_INIT_TAKE1(OIDCOAuthIntrospectionEndpointMethod, oidc_set_introspection_method, (void *)APR_OFFSETOF(oidc_cfg, oauth.introspection_endpoint_method), RSRC_CONF, "Define the HTTP method to use for the introspection call: one of \"GET\" or \"POST\" (default)"), AP_INIT_TAKE1(OIDCOAuthIntrospectionEndpointParams, oidc_set_string_slot, (void*)APR_OFFSETOF(oidc_cfg, oauth.introspection_endpoint_params), RSRC_CONF, "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\"."), AP_INIT_TAKE1(OIDCOAuthIntrospectionEndpointAuth, oidc_set_endpoint_auth_slot, (void *)APR_OFFSETOF(oidc_cfg, oauth.introspection_endpoint_auth), RSRC_CONF, "Specify an authentication method for the OAuth AS Introspection Endpoint (e.g.: client_secret_basic)"), AP_INIT_RAW_ARGS(OIDCOAuthIntrospectionClientAuthBearerToken, oidc_set_client_auth_bearer_token, NULL, RSRC_CONF, "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)"), AP_INIT_TAKE1(OIDCOAuthIntrospectionEndpointCert, oidc_set_string_slot, (void*)APR_OFFSETOF(oidc_cfg, oauth.introspection_endpoint_tls_client_cert), RSRC_CONF, "TLS client certificate used for calls to the OAuth 2.0 Authorization server introspection endpoint."), AP_INIT_TAKE1(OIDCOAuthIntrospectionEndpointKey, oidc_set_string_slot, (void*)APR_OFFSETOF(oidc_cfg, oauth.introspection_endpoint_tls_client_key), RSRC_CONF, "TLS client certificate private key used for calls to the OAuth 2.0 Authorization server introspection endpoint."), AP_INIT_TAKE1(OIDCOAuthIntrospectionTokenParamName, oidc_set_string_slot, (void*)APR_OFFSETOF(oidc_cfg, oauth.introspection_token_param_name), RSRC_CONF, "Name of the parameter whose value carries the access token value in an validation request to the token introspection endpoint."), AP_INIT_TAKE123(OIDCOAuthTokenExpiryClaim, oidc_set_token_expiry_claim, NULL, RSRC_CONF, "Name of the claim that carries the token expiry value in the introspection result, optionally followed by absolute|relative, optionally followed by optional|mandatory"), AP_INIT_TAKE1(OIDCOAuthSSLValidateServer, oidc_set_ssl_validate_slot, (void*)APR_OFFSETOF(oidc_cfg, oauth.ssl_validate_server), RSRC_CONF, "Require validation of the OAuth 2.0 AS Validation Endpoint SSL server certificate for successful authentication (On or Off)"), AP_INIT_TAKE123(OIDCOAuthRemoteUserClaim, oidc_set_remote_user_claim, (void*)APR_OFFSETOF(oidc_cfg, oauth.remote_user_claim), RSRC_CONF, "The claim that is used when setting the REMOTE_USER variable for OAuth 2.0 protected paths."), AP_INIT_ITERATE(OIDCOAuthVerifyCertFiles, oidc_set_public_key_files, (void*)APR_OFFSETOF(oidc_cfg, oauth.verify_public_keys), RSRC_CONF, "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."), AP_INIT_ITERATE(OIDCOAuthVerifySharedKeys, oidc_set_shared_keys, (void*)APR_OFFSETOF(oidc_cfg, oauth.verify_shared_keys), RSRC_CONF, "Shared secret(s) that is/are used to verify signed JWT access tokens locally."), AP_INIT_TAKE1(OIDCOAuthVerifyJwksUri, oidc_set_https_slot, (void *)APR_OFFSETOF(oidc_cfg, oauth.verify_jwks_uri), RSRC_CONF, "The JWKs URL on which the Authorization publishes the keys used to sign its JWT access tokens."), AP_INIT_TAKE123(OIDCHTTPTimeoutLong, oidc_set_http_timeout_slot, (void*)APR_OFFSETOF(oidc_cfg, http_timeout_long), RSRC_CONF, "Timeout for long duration HTTP calls (default)."), AP_INIT_TAKE123(OIDCHTTPTimeoutShort, oidc_set_http_timeout_slot, (void*)APR_OFFSETOF(oidc_cfg, http_timeout_short), RSRC_CONF, "Timeout for short duration HTTP calls (registry/discovery)."), AP_INIT_TAKE1(OIDCStateTimeout, oidc_set_int_slot, (void*)APR_OFFSETOF(oidc_cfg, state_timeout), RSRC_CONF, "Time to live in seconds for state parameter (cq. interval in which the authorization request and the corresponding response need to be completed)."), AP_INIT_TAKE12(OIDCStateMaxNumberOfCookies, oidc_set_max_number_of_state_cookies, (void*)APR_OFFSETOF(oidc_cfg, max_number_of_state_cookies), RSRC_CONF, "Maximun number of parallel state cookies i.e. outstanding authorization requests and whether to delete the oldest cookie(s)."), AP_INIT_TAKE1(OIDCSessionInactivityTimeout, oidc_set_session_inactivity_timeout, (void*)APR_OFFSETOF(oidc_cfg, session_inactivity_timeout), RSRC_CONF, "Inactivity interval after which the session is invalidated when no interaction has occurred."), AP_INIT_TAKE1(OIDCMetadataDir, oidc_set_dir_slot, (void*)APR_OFFSETOF(oidc_cfg, metadata_dir), RSRC_CONF, "Directory that contains provider and client metadata files."), AP_INIT_TAKE1(OIDCSessionType, oidc_set_session_type, (void*)APR_OFFSETOF(oidc_cfg, session_type), RSRC_CONF, "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\"."), AP_INIT_FLAG(OIDCSessionCacheFallbackToCookie, oidc_set_flag_slot, (void*)APR_OFFSETOF(oidc_cfg, session_cache_fallback_to_cookie), RSRC_CONF, "Fallback to client-side cookie session storage when server side cache fails."), AP_INIT_TAKE1(OIDCSessionCookieChunkSize, oidc_set_int_slot, (void*)APR_OFFSETOF(oidc_cfg, session_cookie_chunk_size), RSRC_CONF, "Chunk size for client-cookie session storage type in bytes. Defaults to 4k. Set 0 to suppress chunking."), AP_INIT_TAKE1(OIDCCacheType, oidc_set_cache_type, (void*)APR_OFFSETOF(oidc_cfg, cache), RSRC_CONF, "Cache type; must be one of \"file\", \"memcache\" or \"shm\"."), AP_INIT_FLAG(OIDCCacheEncrypt, oidc_set_flag_slot, (void*)APR_OFFSETOF(oidc_cfg, cache_encrypt), RSRC_CONF, "Encrypt the data in the cache backend (On or Off)"), AP_INIT_TAKE1(OIDCCacheDir, oidc_set_dir_slot, (void*)APR_OFFSETOF(oidc_cfg, cache_file_dir), RSRC_CONF, "Directory used for file-based caching."), AP_INIT_TAKE1(OIDCCacheFileCleanInterval, oidc_set_int_slot, (void*)APR_OFFSETOF(oidc_cfg, cache_file_clean_interval), RSRC_CONF, "Cache file clean interval in seconds."), #ifdef USE_MEMCACHE AP_INIT_TAKE1(OIDCMemCacheServers, oidc_set_string_slot, (void*)APR_OFFSETOF(oidc_cfg, cache_memcache_servers), RSRC_CONF, "Memcache servers used for caching (space separated list of [:] tuples)"), AP_INIT_TAKE1(OIDCMemCacheConnectionsMin, oidc_set_uint32_slot, (void*)APR_OFFSETOF(oidc_cfg, cache_memcache_min), RSRC_CONF, "Minimum number of connections to each Memcache server per process"), AP_INIT_TAKE1(OIDCMemCacheConnectionsSMax, oidc_set_uint32_slot, (void*)APR_OFFSETOF(oidc_cfg, cache_memcache_smax), RSRC_CONF, "Soft maximum number of connections to each Memcache server per process"), AP_INIT_TAKE1(OIDCMemCacheConnectionsHMax, oidc_set_uint32_slot, (void*)APR_OFFSETOF(oidc_cfg, cache_memcache_hmax), RSRC_CONF, "Hard maximum number of connections to each Memcache server per process"), AP_INIT_TAKE1(OIDCMemCacheConnectionsTTL, oidc_set_timeout_slot, (void*)APR_OFFSETOF(oidc_cfg, cache_memcache_ttl), RSRC_CONF, "Maximum time in seconds a connection to a Memcache server can be idle before being closed"), #endif AP_INIT_TAKE1(OIDCCacheShmMax, oidc_set_int_slot, (void*)APR_OFFSETOF(oidc_cfg, cache_shm_size_max), RSRC_CONF, "Maximum number of cache entries to use for \"shm\" caching."), AP_INIT_TAKE1(OIDCCacheShmEntrySizeMax, oidc_set_cache_shm_entry_size_max, (void*)APR_OFFSETOF(oidc_cfg, cache_shm_entry_size_max), RSRC_CONF, "Maximum size of a single cache entry used for \"shm\" caching."), #ifdef USE_LIBHIREDIS AP_INIT_TAKE1(OIDCRedisCacheServer, oidc_set_string_slot, (void*)APR_OFFSETOF(oidc_cfg, cache_redis_server), RSRC_CONF, "Redis server used for caching ([:])"), AP_INIT_TAKE1(OIDCRedisCacheUsername, oidc_set_string_slot, (void*)APR_OFFSETOF(oidc_cfg, cache_redis_username), RSRC_CONF, "Username for authentication to the Redis servers."), AP_INIT_TAKE1(OIDCRedisCachePassword, oidc_set_string_slot, (void*)APR_OFFSETOF(oidc_cfg, cache_redis_password), RSRC_CONF, "Password for authentication to the Redis servers."), AP_INIT_TAKE1(OIDCRedisCacheDatabase, oidc_set_int_slot, (void*)APR_OFFSETOF(oidc_cfg, cache_redis_database), RSRC_CONF, "Database for the Redis servers."), AP_INIT_TAKE1(OIDCRedisCacheConnectTimeout, oidc_set_int_slot, (void*)APR_OFFSETOF(oidc_cfg, cache_redis_connect_timeout), RSRC_CONF, "Timeout for connecting to the Redis servers."), AP_INIT_TAKE1(OIDCRedisCacheTimeout, oidc_set_int_slot, (void*)APR_OFFSETOF(oidc_cfg, cache_redis_timeout), RSRC_CONF, "Timeout waiting for a response of the Redis servers."), #endif AP_INIT_TAKE1(OIDCHTMLErrorTemplate, oidc_set_html_error_template, (void*)APR_OFFSETOF(oidc_cfg, error_template), RSRC_CONF, "Name of a HTML error template: needs to contain two \"%s\" characters, one for the error message, one for the description."), AP_INIT_TAKE2(OIDCPreservePostTemplates, oidc_set_post_preserve_templates, NULL, RSRC_CONF, "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" ), AP_INIT_TAKE1(OIDCDiscoverURL, oidc_set_relative_or_absolute_url_slot_dir_cfg, (void *)APR_OFFSETOF(oidc_dir_cfg, discover_url), RSRC_CONF|ACCESS_CONF|OR_AUTHCFG, "Defines an external IDP Discovery page"), AP_INIT_ITERATE(OIDCPassCookies, oidc_set_cookie_names, (void *) APR_OFFSETOF(oidc_dir_cfg, pass_cookies), RSRC_CONF|ACCESS_CONF|OR_AUTHCFG, "Specify cookies that need to be passed from the browser on to the backend to the OP/AS."), AP_INIT_ITERATE(OIDCStripCookies, oidc_set_cookie_names, (void *) APR_OFFSETOF(oidc_dir_cfg, strip_cookies), RSRC_CONF|ACCESS_CONF|OR_AUTHCFG, "Specify cookies that should be stripped from the incoming request before passing it on to the backend."), AP_INIT_TAKE1(OIDCAuthNHeader, ap_set_string_slot, (void *) APR_OFFSETOF(oidc_dir_cfg, authn_header), RSRC_CONF|ACCESS_CONF|OR_AUTHCFG, "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."), AP_INIT_TAKE1(OIDCCookiePath, ap_set_string_slot, (void *) APR_OFFSETOF(oidc_dir_cfg, cookie_path), RSRC_CONF|ACCESS_CONF|OR_AUTHCFG, "Define the cookie path for the session cookie."), AP_INIT_TAKE1(OIDCCookie, ap_set_string_slot, (void *) APR_OFFSETOF(oidc_dir_cfg, cookie), RSRC_CONF|ACCESS_CONF|OR_AUTHCFG, "Define the cookie name for the session cookie."), AP_INIT_TAKE12(OIDCUnAuthAction, oidc_set_unauth_action, (void *) APR_OFFSETOF(oidc_dir_cfg, unauth_action), RSRC_CONF|ACCESS_CONF|OR_AUTHCFG, "Sets the action taken when an unauthenticated request occurs: must be one of \"auth\" (default), \"pass\" , \"401\", \"407\", or \"410\"."), AP_INIT_TAKE12(OIDCUnAutzAction, oidc_set_unautz_action, (void *) APR_OFFSETOF(oidc_dir_cfg, unautz_action), RSRC_CONF|ACCESS_CONF|OR_AUTHCFG, "Sets the action taken when an unauthorized request occurs: must be one of \"401\" (default), \"403\" or \"auth\"."), AP_INIT_TAKE12(OIDCPassClaimsAs, oidc_set_pass_claims_as, NULL, RSRC_CONF|ACCESS_CONF|OR_AUTHCFG, "Specify how claims are passed to the application(s); must be one of \"none\", \"headers\", \"environment\" or \"both\" (default)."), AP_INIT_ITERATE(OIDCOAuthAcceptTokenAs, oidc_set_accept_oauth_token_in, NULL, RSRC_CONF|ACCESS_CONF|OR_AUTHCFG, "The method in which an OAuth token can be presented; must be one or more of: header|post|query|cookie"), AP_INIT_TAKE12(OIDCUserInfoRefreshInterval, oidc_set_userinfo_refresh_interval, (void*)APR_OFFSETOF(oidc_cfg, provider.userinfo_refresh_interval), RSRC_CONF, "Duration in seconds after which retrieved claims from the userinfo endpoint should be refreshed."), AP_INIT_TAKE1(OIDCOAuthTokenIntrospectionInterval, ap_set_int_slot, (void *) APR_OFFSETOF(oidc_dir_cfg, oauth_token_introspect_interval), RSRC_CONF|ACCESS_CONF|OR_AUTHCFG, "Sets the token introspection refresh interval."), AP_INIT_TAKE1(OIDCPreservePost, oidc_set_preserve_post, (void *) APR_OFFSETOF(oidc_dir_cfg, preserve_post), RSRC_CONF|ACCESS_CONF|OR_AUTHCFG, "Indicates whether POST parameters will be preserved across authentication requests."), AP_INIT_FLAG(OIDCPassAccessToken, ap_set_flag_slot, (void*)APR_OFFSETOF(oidc_dir_cfg, pass_access_token), RSRC_CONF|ACCESS_CONF|OR_AUTHCFG, "Pass the access token in a header and/or environment variable (On or Off)"), AP_INIT_FLAG(OIDCPassRefreshToken, ap_set_flag_slot, (void*)APR_OFFSETOF(oidc_dir_cfg, pass_refresh_token), RSRC_CONF|ACCESS_CONF|OR_AUTHCFG, "Pass the refresh token in a header and/or environment variable (On or Off)"), AP_INIT_TAKE1(OIDCRequestObject, oidc_set_string_slot, (void *)APR_OFFSETOF(oidc_cfg, provider.request_object), RSRC_CONF|ACCESS_CONF|OR_AUTHCFG, "The default request object settings"), AP_INIT_TAKE1(OIDCProviderMetadataRefreshInterval, oidc_set_int_slot, (void*)APR_OFFSETOF(oidc_cfg, provider_metadata_refresh_interval), RSRC_CONF, "Provider metadata refresh interval in seconds."), AP_INIT_TAKE1(OIDCProviderAuthRequestMethod, oidc_set_auth_request_method, (void*)APR_OFFSETOF(oidc_cfg, provider.auth_request_method), RSRC_CONF, "HTTP method used to send the authentication request to the provider (GET or POST)."), AP_INIT_ITERATE(OIDCInfoHook, oidc_set_info_hook_data, (void *)APR_OFFSETOF(oidc_cfg, info_hook_data), RSRC_CONF, "The data that will be returned from the info hook."), AP_INIT_ITERATE(OIDCMetricsData, oidc_set_metrics_hook_data, (void *)APR_OFFSETOF(oidc_cfg, metrics_hook_data), RSRC_CONF, "The data that will be returned from the metrics hook."), AP_INIT_TAKE1(OIDCMetricsPublish, oidc_set_string_slot, (void *)APR_OFFSETOF(oidc_cfg, metrics_path), RSRC_CONF, "Define the URL where the metrics will be published (e.g.: /metrics)"), AP_INIT_TAKE1(OIDCTraceParent, oidc_set_trace_parent, (void *)APR_OFFSETOF(oidc_cfg, trace_parent), RSRC_CONF, "Define to propagagte or generate a traceparent header"), AP_INIT_ITERATE(OIDCBlackListedClaims, oidc_set_filtered_claims, (void *) APR_OFFSETOF(oidc_cfg, black_listed_claims), RSRC_CONF|ACCESS_CONF|OR_AUTHCFG, "Specify claims that should be removed from the userinfo and/or id_token before storing them in the session."), AP_INIT_ITERATE(OIDCWhiteListedClaims, oidc_set_filtered_claims, (void *) APR_OFFSETOF(oidc_cfg, white_listed_claims), RSRC_CONF|ACCESS_CONF|OR_AUTHCFG, "Specify claims from the userinfo and/or id_token that should be stored in the session (all other claims will be discarded)."), AP_INIT_TAKE1(OIDCOAuthServerMetadataURL, oidc_set_url_slot, (void*)APR_OFFSETOF(oidc_cfg, oauth.metadata_url), RSRC_CONF, "Authorization Server metadata URL."), AP_INIT_TAKE12(OIDCRefreshAccessTokenBeforeExpiry, oidc_set_refresh_access_token_before_expiry, (void *)APR_OFFSETOF(oidc_dir_cfg, refresh_access_token_before_expiry), RSRC_CONF|ACCESS_CONF|OR_AUTHCFG, "Ensure the access token is valid for at least seconds by refreshing it if required; must be: [logout_on_error|authenticate_on_error]; the logout_on_error performs a logout on refresh error."), AP_INIT_TAKE1(OIDCStateInputHeaders, oidc_set_state_input_headers_as, NULL, RSRC_CONF, "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)."), AP_INIT_ITERATE(OIDCRedirectURLsAllowed, oidc_set_redirect_urls_allowed, (void *) APR_OFFSETOF(oidc_cfg, redirect_urls_allowed), RSRC_CONF|ACCESS_CONF|OR_AUTHCFG, "Specify one or more regular expressions that define URLs allowed for post logout and other redirects."), AP_INIT_TAKE1(OIDCStateCookiePrefix, ap_set_string_slot, (void *) APR_OFFSETOF(oidc_dir_cfg, state_cookie_prefix), RSRC_CONF|ACCESS_CONF|OR_AUTHCFG, "Define the cookie prefix for the state cookie."), AP_INIT_TAKE1(OIDCCABundlePath, oidc_set_path_slot, (void *) APR_OFFSETOF(oidc_cfg, ca_bundle_path), RSRC_CONF, "Sets the path to the CA bundle to be used by cURL."), AP_INIT_TAKE1(OIDCLogoutXFrameOptions, ap_set_string_slot, (void *) APR_OFFSETOF(oidc_cfg, logout_x_frame_options), RSRC_CONF, "Sets the value of the X-Frame-Options header on front channel logout."), AP_INIT_ITERATE(OIDCXForwardedHeaders, oidc_set_x_forwarded_headers, (void *) APR_OFFSETOF(oidc_cfg, x_forwarded_headers), RSRC_CONF, "Sets the value of the interpreted X-Forwarded-* headers."), AP_INIT_TAKE123(OIDCPassIDTokenAs, oidc_set_pass_idtoken_as, NULL, RSRC_CONF|ACCESS_CONF|OR_AUTHCFG, "The format in which the id_token is passed in (a) header(s); must be one or more of: claims|payload|serialized"), AP_INIT_ITERATE(OIDCPassUserInfoAs, oidc_set_pass_userinfo_as, NULL, RSRC_CONF|ACCESS_CONF|OR_AUTHCFG, "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 AP_INIT_TAKE1(OIDCFilterClaimsExpr, oidc_set_filtered_claims_expr, (void *) APR_OFFSETOF(oidc_cfg, filter_claims_expr), RSRC_CONF, "Sets the JQ expression to be executed on the claims from id_token/userinfo endpoint before storing them in the session"), AP_INIT_TAKE1(OIDCUserInfoClaimsExpr, oidc_set_userinfo_claims_expr, (void *) APR_OFFSETOF(oidc_dir_cfg, userinfo_claims_expr), RSRC_CONF|ACCESS_CONF|OR_AUTHCFG, "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.15.1/src/const.h000066400000000000000000000064171455620533500173510ustar00rootroot00000000000000/* * 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-2024 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 #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); } #define _oidc_str_to_int(s) (s ? (int)strtol(s, NULL, 10) : 0) #endif /* MOD_AUTH_OPENIDC_CONST_H_ */ mod_auth_openidc-2.4.15.1/src/jose.c000066400000000000000000001517461455620533500171640ustar00rootroot00000000000000/* * 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-2024 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 #ifdef WIN32 #define snprintf _snprintf #endif /* to 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; } /* definition follows */ static char *internal_cjose_jwk_to_json(apr_pool_t *pool, const oidc_jwk_t *oidc_jwk, oidc_jose_error_t *oidc_err); /* * assemble an error report */ 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); } /* * 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_jwt_serialize(apr_pool_t *pool, oidc_jwt_t *jwt, oidc_jose_error_t *err) { cjose_err cjose_err; const char *cser = NULL; if (_oidc_strcmp(jwt->header.alg, CJOSE_HDR_ALG_NONE) != 0) { if (cjose_jws_export(jwt->cjose_jws, &cser, &cjose_err) == FALSE) { oidc_jose_error(err, "cjose_jws_export failed: %s", oidc_cjose_e2s(pool, cjose_err)); return NULL; } } else { char *s_payload = json_dumps(jwt->payload.value.json, JSON_PRESERVE_ORDER | JSON_COMPACT); char *out = NULL; size_t out_len; if (cjose_base64url_encode((const uint8_t *)s_payload, _oidc_strlen(s_payload), &out, &out_len, &cjose_err) == FALSE) return NULL; cser = apr_pstrmemdup(pool, out, out_len); cjose_get_dealloc()(out); free(s_payload); cser = apr_psprintf(pool, "%s.%s.", OIDC_JOSE_HDR_ALG_NONE, cser); } return apr_pstrdup(pool, cser); } /* * 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_RSA1_5) == 0) || (_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, const char *s_json, oidc_jose_error_t *err) { oidc_jwk_t *result = NULL; cjose_jwk_t *cjose_jwk = NULL; cjose_err cjose_err; json_error_t json_error; oidc_jose_error_t x5c_err; char *use = NULL; json_t *json = json_loads(s_json, 0, &json_error); if (json == NULL) { oidc_jose_error(err, "could not parse JWK: %s (%s)", json_error.text, s_json); 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); end: if (json) json_decref(json); return result; } oidc_jwk_t *oidc_jwk_copy(apr_pool_t *pool, const oidc_jwk_t *src) { char *s_json = NULL; oidc_jose_error_t err; if (oidc_jwk_to_json(pool, src, &s_json, &err) == FALSE) return NULL; return oidc_jwk_parse(pool, s_json, &err); } /* * 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); } } 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; } 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) { char *s_json = json_dumps(json, 0); *jwk = oidc_jwk_parse(pool, s_json, err); free(s_json); return (*jwk != NULL); } /* * 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) { char *s = internal_cjose_jwk_to_json(pool, jwk, err); if (s == NULL) return FALSE; *s_json = apr_pstrdup(pool, s); free(s); return TRUE; } /* * 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_RSA1_5; 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 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; } 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 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) { 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; int deflate_status = Z_OK; deflateInit(&zlib, Z_BEST_COMPRESSION); deflate_status = deflate(&zlib, Z_FINISH); if (deflate_status != Z_STREAM_END) { oidc_jose_error(err, "deflate failed: %d", deflate_status); return FALSE; } if (deflateEnd(&zlib) != Z_OK) { oidc_jose_error(err, "deflateEnd failed"); return FALSE; } *output_len = (int)zlib.total_out; return TRUE; } #define OIDC_CJOSE_UNCOMPRESS_CHUNK 8192 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) { 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; size_t uncompLength = OIDC_CJOSE_UNCOMPRESS_CHUNK; char *uncomp = (char *)apr_pcalloc(pool, uncompLength + 1); inflateInit(&zlib); int done = 1; while (done) { int inflate_status; if (zlib.total_out >= OIDC_CJOSE_UNCOMPRESS_CHUNK) { char *uncomp2 = (char *)apr_pcalloc(pool, uncompLength + OIDC_CJOSE_UNCOMPRESS_CHUNK); _oidc_memcpy(uncomp2, uncomp, uncompLength); uncompLength += OIDC_CJOSE_UNCOMPRESS_CHUNK; uncomp = uncomp2; } zlib.next_out = (Bytef *)(uncomp + zlib.total_out); zlib.avail_out = (uInt)uncompLength - zlib.total_out; inflate_status = inflate(&zlib, Z_SYNC_FLUSH); if (inflate_status == Z_STREAM_END) { done = 0; } else if (inflate_status != Z_OK) { oidc_jose_error(err, "inflate failed: %d", inflate_status); inflateEnd(&zlib); return FALSE; } } if (inflateEnd(&zlib) != Z_OK) { oidc_jose_error(err, "inflateEnd failed"); return FALSE; } *output_len = (int)zlib.total_out; *output = uncomp; return TRUE; } #endif 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 } 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); char *str = json_dumps(jwt->header.value.json, JSON_PRESERVE_ORDER | JSON_COMPACT); jwt->header.value.str = apr_pstrdup(pool, str); free(str); 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 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 = json_dumps(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) { free(plaintext); return FALSE; } } else { s_payload = plaintext; payload_len = _oidc_strlen(plaintext); jwt->payload.value.str = apr_pstrdup(pool, s_payload); } jwt->cjose_jws = cjose_jws_sign(jwk->cjose_jwk, hdr, (const uint8_t *)s_payload, payload_len, &cjose_err); free(plaintext); 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 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 (strstr(version, OIDC_JOSE_CJOSE_VERSION_DEPRECATED) == version); } /* * verify the signature on 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 */ 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; } 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; } 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); memcpy(*fp, key_spec.n, key_spec.nlen); 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) 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); memcpy(*fp, &b, sizeof(b)); memcpy(*fp + sizeof(b), ec_keyspec.x, ec_keyspec.xlen); 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); } /* * produce the string jwk representation from an oidc_jwk_t structure */ static char *internal_cjose_jwk_to_json(apr_pool_t *pool, const oidc_jwk_t *oidc_jwk, oidc_jose_error_t *oidc_err) { char *result = NULL, *cjose_jwk_json; cjose_err err; json_t *json = NULL, *temp = NULL; json_error_t json_error; int i = 0; void *iter = NULL; if (!oidc_jwk) { oidc_jose_error(oidc_err, "internal_cjose_jwk_to_json failed: NULL oidc_jwk"); return NULL; } // get current cjose_jwk_json = cjose_jwk_to_json(oidc_jwk->cjose_jwk, TRUE, &err); if (cjose_jwk_json == NULL) { oidc_jose_error(oidc_err, "cjose_jwk_to_json failed: %s", oidc_cjose_e2s(pool, err)); goto to_json_cleanup; } temp = json_loads(cjose_jwk_json, 0, &json_error); if (!temp) { oidc_jose_error(oidc_err, "json_loads failed"); goto to_json_cleanup; } json = json_object(); if (oidc_jwk->use) json_object_set_new(json, OIDC_JOSE_JWK_USE_STR, json_string(oidc_jwk->use)); iter = json_object_iter(temp); while (iter) { json_object_set(json, json_object_iter_key(iter), json_object_iter_value(iter)); iter = json_object_iter_next(temp, iter); } json_decref(temp); temp = NULL; // set x5c if ((oidc_jwk->x5c != NULL) && (oidc_jwk->x5c->nelts > 0)) { temp = json_array(); if (temp == NULL) { oidc_jose_error(oidc_err, "json_array failed"); goto to_json_cleanup; } for (i = 0; i < oidc_jwk->x5c->nelts; i++) { if (json_array_append_new(temp, json_string(APR_ARRAY_IDX(oidc_jwk->x5c, i, const char *))) == -1) { oidc_jose_error(oidc_err, "json_array_append failed"); goto to_json_cleanup; } } json_object_set_new(json, OIDC_JOSE_JWK_X5C_STR, temp); } // set x5t#256 if (oidc_jwk->x5t_S256 != NULL) json_object_set_new(json, OIDC_JOSE_JWK_X5T256_STR, json_string(oidc_jwk->x5t_S256)); // set x5t if (oidc_jwk->x5t != NULL) json_object_set_new(json, OIDC_JOSE_JWK_X5T_STR, json_string(oidc_jwk->x5t)); // generate the string ... result = json_dumps(json, JSON_ENCODE_ANY | JSON_COMPACT | JSON_PRESERVE_ORDER); if (!result) { oidc_jose_error(oidc_err, "json_dumps failed"); goto to_json_cleanup; } to_json_cleanup: if (cjose_jwk_json) free(cjose_jwk_json); if (json) json_decref(json); return result; } mod_auth_openidc-2.4.15.1/src/jose.h000066400000000000000000000257441455620533500171670ustar00rootroot00000000000000/* * 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-2024 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 /* 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 */ void _oidc_jose_error_set(oidc_jose_error_t *, const char *, const int, const char *, const char *msg, ...); #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 to a JWK struct */ oidc_jwk_t *oidc_jwk_parse(apr_pool_t *pool, const char *s_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 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); /* 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_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.15.1/src/metadata.c000066400000000000000000001540241455620533500177740ustar00rootroot00000000000000/* * 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-2024 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 "mod_auth_openidc.h" #include "metrics.h" extern module AP_MODULE_DECLARE_DATA auth_openidc_module; #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_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 "keys" #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_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_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_ISSUER_SPECIFIC_REDIRECT_URI "issuer_specific_redirect_uri" /* * 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 = strstr(issuer, "https://"); if (p == issuer) { p = apr_pstrdup(r->pool, issuer + _oidc_strlen("https://")); } else { p = 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_util_escape_string(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_util_unescape_string(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 *cfg, const char *issuer, const char *type) { return apr_psprintf(r->pool, "%s/%s.%s", cfg->metadata_dir, 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 *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 *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 *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_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_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 to see if JSON provider metadata is valid */ apr_byte_t oidc_metadata_provider_is_valid(request_rec *r, oidc_cfg *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_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_valid_string_in_array(r->pool, j_provider, OIDC_METADATA_RESPONSE_TYPES_SUPPORTED, oidc_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_valid_string_in_array(r->pool, j_provider, OIDC_METADATA_RESPONSE_MODES_SUPPORTED, oidc_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_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_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_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_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_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_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_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_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_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_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 *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(provider->client_name)); json_object_set_new(data, OIDC_METADATA_REDIRECT_URIS, json_pack("[s]", oidc_get_redirect_uri_iss(r, cfg, provider))); 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 (provider->token_endpoint_auth != NULL) { json_object_set_new(data, OIDC_METADATA_TOKEN_ENDPOINT_AUTH_METHOD, json_string(provider->token_endpoint_auth)); } if (provider->client_contact != NULL) { json_object_set_new(data, OIDC_METADATA_CONTACTS, json_pack("[s]", provider->client_contact)); } if (provider->client_jwks_uri) { json_object_set_new(data, OIDC_METADATA_JWKS_URI, json_string(provider->client_jwks_uri)); } else if (cfg->public_keys != NULL) { json_object_set_new(data, OIDC_METADATA_JWKS_URI, json_string(apr_psprintf(r->pool, "%s?%s=rsa", oidc_get_redirect_uri(r, cfg), OIDC_REDIRECT_URI_REQUEST_JWKS))); } if (provider->id_token_signed_response_alg != NULL) { json_object_set_new(data, OIDC_METADATA_ID_TOKEN_SIGNED_RESPONSE_ALG, json_string(provider->id_token_signed_response_alg)); } if (provider->id_token_encrypted_response_alg != NULL) { json_object_set_new(data, OIDC_METADATA_ID_TOKEN_ENCRYPTED_RESPONSE_ALG, json_string(provider->id_token_encrypted_response_alg)); } if (provider->id_token_encrypted_response_enc != NULL) { json_object_set_new(data, OIDC_METADATA_ID_TOKEN_ENCRYPTED_RESPONSE_ENC, json_string(provider->id_token_encrypted_response_enc)); } if (provider->userinfo_signed_response_alg != NULL) { json_object_set_new(data, OIDC_METADATA_USERINFO_SIGNED_RESPONSE_ALG, json_string(provider->userinfo_signed_response_alg)); } if (provider->userinfo_encrypted_response_alg != NULL) { json_object_set_new(data, OIDC_METADATA_USERINFO_ENCRYPTED_RESPONSE_ALG, json_string(provider->userinfo_encrypted_response_alg)); } if (provider->userinfo_encrypted_response_enc != NULL) { json_object_set_new(data, OIDC_METADATA_USERINFO_ENCRYPTED_RESPONSE_ENC, json_string(provider->userinfo_encrypted_response_enc)); } if (provider->request_object != NULL) { json_t *request_object_config = NULL; if (oidc_util_decode_json_object(r, provider->request_object, &request_object_config) == TRUE) { json_t *crypto = json_object_get(request_object_config, "crypto"); char *alg = "none"; oidc_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_get_redirect_uri(r, cfg))); json_object_set_new( data, OIDC_METADATA_FRONTCHANNEL_LOGOUT_URI, json_string(apr_psprintf(r->pool, "%s?%s=%s", oidc_get_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_get_redirect_uri(r, cfg), OIDC_REDIRECT_URI_REQUEST_LOGOUT, OIDC_BACKCHANNEL_STYLE_LOGOUT_PARAM_VALUE))); if (cfg->default_slo_url != NULL) { json_object_set_new(data, OIDC_METADATA_POST_LOGOUT_REDIRECT_URIS, json_pack("[s]", oidc_get_absolute_url(r, cfg, cfg->default_slo_url))); } /* add any custom JSON in to the registration request */ if (provider->registration_endpoint_json != NULL) { json_t *json = NULL; if (oidc_util_decode_json_object(r, provider->registration_endpoint_json, &json) == FALSE) return FALSE; oidc_util_json_merge(r, json, data); json_decref(json); } /* dynamically register the client with the specified parameters */ if (oidc_util_http_post_json(r, provider->registration_endpoint_url, data, NULL, provider->registration_token, provider->ssl_validate_server, response, &cfg->http_timeout_short, &cfg->outgoing_proxy, oidc_dir_cfg_pass_cookies(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 *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_util_http_get(r, url, NULL, NULL, NULL, ssl_validate_server, &response, &cfg->http_timeout_long, &cfg->outgoing_proxy, oidc_dir_cfg_pass_cookies(r), NULL, NULL, NULL) == FALSE) return FALSE; if ((jwks_uri->signed_uri != NULL) && (jwks_uri->jwk != NULL)) { oidc_jwt_t *jwt = NULL; oidc_jose_error_t err; apr_hash_t *keys = apr_hash_make(r->pool); apr_hash_set(keys, jwks_uri->jwk->kid ? jwks_uri->jwk->kid : "", APR_HASH_KEY_STRING, jwks_uri->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)); if (jwt != NULL) oidc_jwt_destroy(jwt); return FALSE; } // TODO: add issuer? if (oidc_proto_validate_jwt(r, jwt, NULL, TRUE, 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(jwks_uri->refresh_interval)); return TRUE; } /* * return JWKs for the specified issuer */ apr_byte_t oidc_metadata_jwks_get(request_rec *r, oidc_cfg *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 *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_util_http_get(r, url, NULL, NULL, NULL, cfg->provider.ssl_validate_server, response, &cfg->http_timeout_short, &cfg->outgoing_proxy, oidc_dir_cfg_pass_cookies(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 *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 (cfg->provider_metadata_refresh_interval > 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(cfg->provider_metadata_refresh_interval)); 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", ((strstr(issuer, "http://") == issuer) || (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 ((cfg->provider_metadata_refresh_interval > 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 *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 (provider->registration_endpoint_url == 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 *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, cfg->metadata_dir, r->pool)) != APR_SUCCESS) { oidc_error(r, "error opening metadata directory '%s' (%s)", cfg->metadata_dir, 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 *) = provider->issuer; } } /* 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_json_object_get_bool(json, key, &int_value, default_value) == FALSE) { oidc_json_object_get_string(r->pool, json, key, &s_value, NULL); if (s_value != NULL) { const char *rv = oidc_parse_boolean(r->pool, s_value, &int_value); if (rv != NULL) { oidc_warn(r, "%s: %s", key, rv); int_value = default_value; } } else { oidc_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) { 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); } } /* * 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 *cfg, json_t *j_provider, oidc_provider_t *provider) { if (provider->issuer == NULL) { /* get the "issuer" from the provider metadata */ oidc_json_object_get_string(r->pool, j_provider, OIDC_METADATA_ISSUER, &provider->issuer, NULL); } if (provider->authorization_endpoint_url == NULL) { /* get a handle to the authorization endpoint */ oidc_metadata_parse_url(r, OIDC_METADATA_SUFFIX_PROVIDER, provider->issuer, j_provider, OIDC_METADATA_AUTHORIZATION_ENDPOINT, &provider->authorization_endpoint_url, NULL); } if (provider->token_endpoint_url == NULL) { /* get a handle to the token endpoint */ oidc_metadata_parse_url(r, OIDC_METADATA_SUFFIX_PROVIDER, provider->issuer, j_provider, OIDC_METADATA_TOKEN_ENDPOINT, &provider->token_endpoint_url, NULL); } if (provider->userinfo_endpoint_url == NULL) { /* get a handle to the user_info endpoint */ oidc_metadata_parse_url(r, OIDC_METADATA_SUFFIX_PROVIDER, provider->issuer, j_provider, OIDC_METADATA_USERINFO_ENDPOINT, &provider->userinfo_endpoint_url, NULL); } if (provider->revocation_endpoint_url == NULL) { /* get a handle to the token revocation endpoint */ oidc_metadata_parse_url(r, OIDC_METADATA_SUFFIX_PROVIDER, provider->issuer, j_provider, OIDC_METADATA_REVOCATION_ENDPOINT, &provider->revocation_endpoint_url, NULL); } if (provider->jwks_uri.uri == NULL) { /* get a handle to the jwks_uri endpoint */ oidc_metadata_parse_url(r, OIDC_METADATA_SUFFIX_PROVIDER, provider->issuer, j_provider, OIDC_METADATA_JWKS_URI, &provider->jwks_uri.uri, NULL); } if (provider->jwks_uri.signed_uri == NULL) { /* get a handle to the signed jwks_uri endpoint */ oidc_metadata_parse_url(r, OIDC_METADATA_SUFFIX_PROVIDER, provider->issuer, j_provider, OIDC_METADATA_SIGNED_JWKS_URI, &provider->jwks_uri.signed_uri, NULL); } if (provider->registration_endpoint_url == NULL) { /* get a handle to the client registration endpoint */ oidc_metadata_parse_url(r, OIDC_METADATA_SUFFIX_PROVIDER, provider->issuer, j_provider, OIDC_METADATA_REGISTRATION_ENDPOINT, &provider->registration_endpoint_url, NULL); } if (provider->check_session_iframe == NULL) { /* get a handle to the check session iframe */ oidc_metadata_parse_url(r, OIDC_METADATA_SUFFIX_PROVIDER, provider->issuer, j_provider, OIDC_METADATA_CHECK_SESSION_IFRAME, &provider->check_session_iframe, NULL); } if (provider->end_session_endpoint == NULL) { /* get a handle to the end session endpoint */ oidc_metadata_parse_url(r, OIDC_METADATA_SUFFIX_PROVIDER, provider->issuer, j_provider, OIDC_METADATA_END_SESSION_ENDPOINT, &provider->end_session_endpoint, NULL); } // TODO: default 0 should have a defined default that may be 1... if (provider->backchannel_logout_supported == OIDC_CONFIG_POS_INT_UNSET) { oidc_metadata_parse_boolean(r, j_provider, OIDC_METADATA_BACKCHANNEL_LOGOUT_SUPPORTED, &provider->backchannel_logout_supported, 0); } if (provider->token_endpoint_auth == NULL) { if (oidc_valid_string_in_array(r->pool, j_provider, OIDC_METADATA_TOKEN_ENDPOINT_AUTH_METHODS_SUPPORTED, oidc_cfg_get_valid_endpoint_auth_function(cfg), &provider->token_endpoint_auth, 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 "\"", provider->issuer); return FALSE; } } return TRUE; } apr_byte_t oidc_oauth_metadata_provider_parse(request_rec *r, oidc_cfg *c, json_t *j_provider) { char *issuer = NULL; /* get the "issuer" from the provider metadata */ oidc_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_metadata_parse_url(r, OIDC_METADATA_SUFFIX_PROVIDER, issuer, j_provider, OIDC_METADATA_INTROSPECTION_ENDPOINT, &c->oauth.introspection_endpoint_url, NULL); /* get a handle to the jwks_uri endpoint */ oidc_metadata_parse_url(r, OIDC_METADATA_SUFFIX_PROVIDER, issuer, j_provider, OIDC_METADATA_JWKS_URI, &c->oauth.verify_jwks_uri, NULL); if (oidc_valid_string_in_array(r->pool, j_provider, OIDC_METADATA_INTROSPECTON_ENDPOINT_AUTH_METHODS_SUPPORTED, oidc_cfg_get_valid_endpoint_auth_function(c), &c->oauth.introspection_endpoint_auth, 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; } 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_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; } /* * get an integer value from a JSON object and see if it is a valid value according to the specified validation function */ void oidc_metadata_get_valid_int(request_rec *r, const json_t *json, const char *key, oidc_valid_int_function_t valid_int_function, int *int_value, int default_int_value) { int v = 0; oidc_json_object_get_int(json, key, &v, default_int_value); const char *rv = valid_int_function(r->pool, v); if (rv != NULL) { oidc_warn(r, "integer value %d for key \"%s\" is invalid: %s; using default: %d", v, key, rv, default_int_value); v = default_int_value; } *int_value = v; } 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_JWK_KEYS); 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_JWK_KEYS); 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 *cfg, json_t *j_conf, oidc_provider_t *provider) { oidc_metadata_parse_url(r, OIDC_METADATA_SUFFIX_CONF, provider->issuer, j_conf, OIDC_METADATA_CLIENT_JWKS_URI, &provider->client_jwks_uri, cfg->provider.client_jwks_uri); oidc_metadata_get_jwks(r, j_conf, &provider->client_keys); oidc_jose_error_t err; json_t *jwk = json_object_get(j_conf, "signed_jwks_uri_key"); if (jwk != NULL) { if (oidc_jwk_parse_json(r->pool, jwk, &provider->jwks_uri.jwk, &err) == FALSE) { oidc_warn(r, "oidc_jwk_parse_json failed for \"signed_jwks_uri_key\": %s", oidc_jose_e2s(r->pool, err)); } } else if (cfg->provider.jwks_uri.jwk != NULL) { provider->jwks_uri.jwk = cfg->provider.jwks_uri.jwk; } /* get the (optional) signing & encryption settings for the id_token */ oidc_metadata_get_valid_string(r, j_conf, OIDC_METADATA_ID_TOKEN_SIGNED_RESPONSE_ALG, oidc_valid_signed_response_alg, &provider->id_token_signed_response_alg, cfg->provider.id_token_signed_response_alg); oidc_metadata_get_valid_string(r, j_conf, OIDC_METADATA_ID_TOKEN_ENCRYPTED_RESPONSE_ALG, oidc_valid_encrypted_response_alg, &provider->id_token_encrypted_response_alg, cfg->provider.id_token_encrypted_response_alg); oidc_metadata_get_valid_string(r, j_conf, OIDC_METADATA_ID_TOKEN_ENCRYPTED_RESPONSE_ENC, oidc_valid_encrypted_response_enc, &provider->id_token_encrypted_response_enc, cfg->provider.id_token_encrypted_response_enc); /* get the (optional) signing & encryption settings for the userinfo response */ oidc_metadata_get_valid_string(r, j_conf, OIDC_METADATA_USERINFO_SIGNED_RESPONSE_ALG, oidc_valid_signed_response_alg, &provider->userinfo_signed_response_alg, cfg->provider.userinfo_signed_response_alg); oidc_metadata_get_valid_string(r, j_conf, OIDC_METADATA_USERINFO_ENCRYPTED_RESPONSE_ALG, oidc_valid_encrypted_response_alg, &provider->userinfo_encrypted_response_alg, cfg->provider.userinfo_encrypted_response_alg); oidc_metadata_get_valid_string(r, j_conf, OIDC_METADATA_USERINFO_ENCRYPTED_RESPONSE_ENC, oidc_valid_encrypted_response_enc, &provider->userinfo_encrypted_response_enc, cfg->provider.userinfo_encrypted_response_enc); /* 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, &provider->ssl_validate_server, cfg->provider.ssl_validate_server); oidc_metadata_parse_boolean(r, j_conf, OIDC_METADATA_VALIDATE_ISSUER, &provider->validate_issuer, cfg->provider.validate_issuer); /* 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_json_object_get_string(r->pool, j_conf, OIDC_METADATA_SCOPE, &provider->scope, cfg->provider.scope); /* see if we've got a custom JWKs refresh interval */ oidc_metadata_get_valid_int(r, j_conf, OIDC_METADATA_JWKS_REFRESH_INTERVAL, oidc_valid_jwks_refresh_interval, &provider->jwks_uri.refresh_interval, cfg->provider.jwks_uri.refresh_interval); /* see if we've got a custom IAT slack interval */ oidc_metadata_get_valid_int(r, j_conf, OIDC_METADATA_IDTOKEN_IAT_SLACK, oidc_valid_idtoken_iat_slack, &provider->idtoken_iat_slack, cfg->provider.idtoken_iat_slack); /* see if we've got a custom max session duration */ oidc_metadata_get_valid_int(r, j_conf, OIDC_METADATA_SESSION_MAX_DURATION, oidc_valid_session_max_duration, &provider->session_max_duration, cfg->provider.session_max_duration); /* see if we've got custom authentication request parameter values */ oidc_json_object_get_string(r->pool, j_conf, OIDC_METADATA_AUTH_REQUEST_PARAMS, &provider->auth_request_params, cfg->provider.auth_request_params); /* see if we've got custom logout request parameter values */ oidc_json_object_get_string(r->pool, j_conf, OIDC_METADATA_LOGOUT_REQUEST_PARAMS, &provider->logout_request_params, cfg->provider.logout_request_params); /* see if we've got custom token endpoint parameter values */ oidc_json_object_get_string(r->pool, j_conf, OIDC_METADATA_TOKEN_ENDPOINT_PARAMS, &provider->token_endpoint_params, cfg->provider.token_endpoint_params); /* get the response mode to use */ oidc_metadata_get_valid_string(r, j_conf, OIDC_METADATA_RESPONSE_MODE, oidc_valid_response_mode, &provider->response_mode, cfg->provider.response_mode); /* get the PKCE method to use */ char *pkce_method = NULL; oidc_metadata_get_valid_string(r, j_conf, OIDC_METADATA_PKCE_METHOD, oidc_valid_pkce_method, &pkce_method, cfg->provider.pkce ? cfg->provider.pkce->method : NULL); if (pkce_method != NULL) oidc_parse_pkce_type(r->pool, pkce_method, &provider->pkce); /* get the client name */ oidc_json_object_get_string(r->pool, j_conf, OIDC_METADATA_CLIENT_NAME, &provider->client_name, cfg->provider.client_name); /* get the client contact */ oidc_json_object_get_string(r->pool, j_conf, OIDC_METADATA_CLIENT_CONTACT, &provider->client_contact, cfg->provider.client_contact); /* get the token endpoint authentication method */ oidc_metadata_get_valid_string(r, j_conf, OIDC_METADATA_TOKEN_ENDPOINT_AUTH, oidc_cfg_get_valid_endpoint_auth_function(cfg), &provider->token_endpoint_auth, provider->token_endpoint_auth); /* get the dynamic client registration token */ oidc_json_object_get_string(r->pool, j_conf, OIDC_METADATA_REGISTRATION_TOKEN, &provider->registration_token, cfg->provider.registration_token); /* see if we've got custom registration request parameter values */ oidc_json_object_get_string(r->pool, j_conf, OIDC_METADATA_REGISTRATION_ENDPOINT_JSON, &provider->registration_endpoint_json, cfg->provider.registration_endpoint_json); /* get the flow to use; let the .client file set it otherwise (pass NULL as default value) */ oidc_metadata_get_valid_string(r, j_conf, OIDC_METADATA_RESPONSE_TYPE, oidc_valid_response_type, &provider->response_type, NULL); /* see if we've got a custom user info refresh interval */ oidc_metadata_get_valid_int(r, j_conf, OIDC_METADATA_USERINFO_REFRESH_INTERVAL, oidc_valid_userinfo_refresh_interval, &provider->userinfo_refresh_interval, cfg->provider.userinfo_refresh_interval); /* TLS client cert auth settings */ oidc_json_object_get_string(r->pool, j_conf, OIDC_METADATA_TOKEN_ENDPOINT_TLS_CLIENT_CERT, &provider->token_endpoint_tls_client_cert, cfg->provider.token_endpoint_tls_client_cert); oidc_json_object_get_string(r->pool, j_conf, OIDC_METADATA_TOKEN_ENDPOINT_TLS_CLIENT_KEY, &provider->token_endpoint_tls_client_key, cfg->provider.token_endpoint_tls_client_key); oidc_json_object_get_string(r->pool, j_conf, OIDC_METADATA_TOKEN_ENDPOINT_TLS_CLIENT_KEY_PWD, &provider->token_endpoint_tls_client_key_pwd, cfg->provider.token_endpoint_tls_client_key_pwd); oidc_json_object_get_string(r->pool, j_conf, OIDC_METADATA_REQUEST_OBJECT, &provider->request_object, cfg->provider.request_object); /* see if we've got a custom userinfo endpoint token presentation method */ char *method = NULL; oidc_metadata_get_valid_string(r, j_conf, OIDC_METADATA_USERINFO_TOKEN_METHOD, oidc_valid_userinfo_token_method, &method, NULL); if (method != NULL) oidc_parse_userinfo_token_method(r->pool, method, &provider->userinfo_token_method); else provider->userinfo_token_method = OIDC_USER_INFO_TOKEN_METHOD_HEADER; /* see if we've got a custom HTTP method for passing the auth request */ oidc_metadata_get_valid_string(r, j_conf, OIDC_METADATA_AUTH_REQUEST_METHOD, oidc_valid_auth_request_method, &method, NULL); if (method != NULL) oidc_parse_auth_request_method(r->pool, method, &provider->auth_request_method); else provider->auth_request_method = cfg->provider.auth_request_method; /* get the issuer specific redirect URI option */ oidc_metadata_parse_boolean(r, j_conf, OIDC_METADATA_ISSUER_SPECIFIC_REDIRECT_URI, &provider->issuer_specific_redirect_uri, cfg->provider.issuer_specific_redirect_uri); 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 *cfg, json_t *j_client, oidc_provider_t *provider) { /* get a handle to the client_id we need to use for this provider */ oidc_json_object_get_string(r->pool, j_client, OIDC_METADATA_CLIENT_ID, &provider->client_id, NULL); /* get a handle to the client_secret we need to use for this provider */ oidc_json_object_get_string(r->pool, j_client, OIDC_METADATA_CLIENT_SECRET, &provider->client_secret, NULL); /* see if the token endpoint auth method defined in the client metadata overrides the provider one */ char *token_endpoint_auth = NULL; oidc_json_object_get_string(r->pool, j_client, OIDC_METADATA_TOKEN_ENDPOINT_AUTH_METHOD, &token_endpoint_auth, NULL); if (token_endpoint_auth != NULL) { if (oidc_cfg_get_valid_endpoint_auth_function(cfg)(r->pool, token_endpoint_auth) == NULL) { provider->token_endpoint_auth = apr_pstrdup(r->pool, token_endpoint_auth); } else { oidc_warn(r, "unsupported client auth method \"%s\" in client metadata for entry " "\"" OIDC_METADATA_TOKEN_ENDPOINT_AUTH_METHOD "\"", token_endpoint_auth); } } /* determine the response type if not set by .conf */ if (provider->response_type == NULL) { provider->response_type = cfg->provider.response_type; /* "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, provider->response_type) == 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)) { provider->response_type = apr_pstrdup(r->pool, json_string_value(j_response_type)); } } } } oidc_metadata_get_valid_string(r, j_client, OIDC_METADATA_ID_TOKEN_SIGNED_RESPONSE_ALG, oidc_valid_signed_response_alg, &provider->id_token_signed_response_alg, provider->id_token_signed_response_alg); 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 *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.15.1/src/metrics.c000066400000000000000000001431001455620533500176530ustar00rootroot00000000000000/* * 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-2024 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 "mod_auth_openidc.h" #include "metrics.h" #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_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_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, "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.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]; sprintf(s, "%" 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_no_overflow(server_rec *s, json_int_t cur, json_int_t add) { if ((OIDC_METRICS_INT_MAX - add) < cur) { oidc_swarn(s, "cannot update 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 0; } return 1; } // 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; typedef struct oidc_metrics_add_classname_ctx_t { apr_pool_t *pool; char **valid_names; } oidc_metrics_add_classname_ctx_t; 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; } 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++) { 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); return (apr_table_get(names, name) != 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) { int v; const char *env = getenv(name); return (((env) && (sscanf(env, "%d", &v) == 1)) ? v : dval); } #define OIDC_METRICS_CACHE_JSON_MAX_ENV_VAR "OIDC_METRICS_CACHE_JSON_MAX" /* * get the size of the to-be-allocated shared memory segment */ static inline int oidc_metrics_shm_size(server_rec *s) { return oidc_metrics_get_env_int(OIDC_METRICS_CACHE_JSON_MAX_ENV_VAR, OIDC_METRICS_CACHE_JSON_MAX_DEFAULT); } /* * 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 %d", OIDC_METRICS_CACHE_JSON_MAX_ENV_VAR, oidc_metrics_shm_size(s)); else _oidc_memcpy(p, value, n); } else { *p = 0; } } 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); } 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_specs = NULL, *j_entry = NULL, *j_spec = 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(); void *iter1 = json_object_iter(json); while (iter1) { j_server = json_object_iter_value(iter1); j_entries = json_object_get(j_server, OIDC_METRICS_COUNTERS); void *iter2 = json_object_iter(j_entries); while (iter2) { j_entry = json_object_iter_value(iter2); j_specs = json_object_get(j_entry, OIDC_METRICS_SPECS); void *iter3 = json_object_iter(j_specs); while (iter3) { j_spec = json_object_iter_value(iter3); json_integer_set(j_spec, 0); iter3 = json_object_iter_next(j_specs, iter3); } iter2 = json_object_iter_next(j_entries, iter2); } j_entries = json_object_get(j_server, OIDC_METRICS_TIMINGS); iter2 = json_object_iter(j_entries); while (iter2) { j_entry = json_object_iter_value(iter2); 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)); iter2 = json_object_iter_next(j_entries, iter2); } iter1 = json_object_iter_next(json, iter1); } /* serialize the metrics data, preserve order is required for Prometheus */ char *str = json_dumps(json, JSON_COMPACT | JSON_PRESERVE_ORDER); s_json = apr_pstrdup(s->process->pool, str); free(str); /* 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_no_overflow(s, n, v) == 0) return; 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_SPEC_DEFAULT "_" static inline const char *_metrics_spec2key(const char *spec) { return (spec && _oidc_strcmp(spec, "") != 0) ? spec : OIDC_METRICS_SPEC_DEFAULT; } /* * create a new spec entry in the collected data for the counter */ static void oidc_metrics_counter_specs_set_new(server_rec *s, json_t *j_specs, const char *spec, const oidc_metrics_counter_t *counter) { json_t *j_spec = json_integer(counter->count); json_object_set_new(j_specs, spec, j_spec); } /* * 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 *spec = NULL; json_t *j_value = json_object(); json_t *j_specs = json_object(); for (hi = apr_hash_first(s->process->pool, htable); hi; hi = apr_hash_next(hi)) { apr_hash_this(hi, (const void **)&spec, NULL, (void **)&counter); oidc_metrics_counter_specs_set_new(s, j_specs, spec, counter); } json_object_set_new(j_value, OIDC_METRICS_SPECS, j_specs); return j_value; } /* * update a counter entry in the collected JSON data */ static void oidc_metrics_counter_update(server_rec *s, json_t *j_value, apr_hash_t *htable) { json_int_t v = 0; json_t *j_specs = json_object_get(j_value, OIDC_METRICS_SPECS); apr_hash_index_t *hi = NULL; oidc_metrics_counter_t *counter = NULL; char *spec = NULL; json_t *j_member = NULL; for (hi = apr_hash_first(s->process->pool, htable); hi; hi = apr_hash_next(hi)) { apr_hash_this(hi, (const void **)&spec, NULL, (void **)&counter); j_member = json_object_get(j_specs, spec); if (j_member == NULL) { oidc_metrics_counter_specs_set_new(s, j_specs, spec, counter); } else { v = json_integer_value(j_member); if (_is_no_overflow(s, v, counter->count)) json_integer_set(j_member, 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; } static inline char *oidc_metrics_type2key(apr_pool_t *pool, unsigned int type) { return apr_psprintf(pool, "%u", 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_value = NULL, *j_counters = NULL, *j_timings = NULL; apr_hash_index_t *hi1 = NULL, *hi2 = NULL; const char *name = NULL, *key = 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); /* get or create the corresponding metric entry in the global metrics */ j_value = json_object_get(j_counters, key); if (j_value != NULL) oidc_metrics_counter_update(s, j_value, counter_hash); else json_object_set_new(j_counters, key, 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_value = json_object_get(j_timings, key); if (j_value != NULL) oidc_metrics_timings_update(s, j_value, 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 */ char *str = json_dumps(json, JSON_COMPACT | JSON_PRESERVE_ORDER); s_json = apr_pstrdup(s->process->pool, str); free(str); /* 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" 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)); } unsigned int oidc_metric_random_int(unsigned int mod) { unsigned int v; oidc_util_random_bytes((unsigned char *)&v, sizeof(v)); return v % mod; } /* * 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))); /* see if we are asked to exit */ while (_oidc_metrics_thread_exit == FALSE) { apr_sleep(oidc_metrics_interval(s)); // 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 */ apr_hash_clear(_oidc_metrics.counters); 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); } 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_cache_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_cache_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_cache_cleanup(server_rec *s) { /* make sure it gets executed exactly once! */ if (_oidc_metrics_cache == NULL) return APR_SUCCESS; /* signal the collector thread to exit and wait (at max 5 seconds) for it to flush its data and exit */ _oidc_metrics_thread_exit = TRUE; apr_status_t rv = APR_SUCCESS; apr_thread_join(&rv, _oidc_metrics_thread); if (rv != APR_SUCCESS) return rv; /* 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; /* 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; return rv; } /* * 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_type2key(r->server->process->pool, type); 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 specs */ static inline oidc_metrics_counter_t *oidc_metrics_counter_spec_get(request_rec *r, const char *spec, apr_hash_t *table) { /* get the entry to the specified metric */ oidc_metrics_counter_t *result = apr_hash_get(table, spec, 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, spec), APR_HASH_KEY_STRING, result); } return result; } /* * retrieve or create a local counter for the specified type */ static inline apr_hash_t *oidc_metrics_counter_get(request_rec *r, unsigned int type) { apr_hash_t *result = NULL; const char *key = oidc_metrics_type2key(r->server->process->pool, type); 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 specs 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 *spec) { 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_spec_get(r, _metrics_spec2key(spec), oidc_metrics_counter_get(r, type)); /* 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_no_overflow(r->server, counter->count, 1)) 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 { // increase after checking possible overflow if (_is_no_overflow(r->server, timing->sum, elapsed)) { 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 */ 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); } 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); } 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, *j_specs = NULL; json_t *o_json = NULL, *o_server = NULL, *o_counters = NULL, *o_counter = NULL, *o_timings = NULL, *o_timing = NULL, *o_spec = NULL; const char *s_server = NULL; unsigned int type = 0; char *str = 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(); void *iter1 = json_object_iter(json); while (iter1) { s_server = json_object_iter_key(iter1); j_server = json_object_iter_value(iter1); 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); void *iter2 = json_object_iter(j_counters); while (iter2) { type = oidc_metrics_key2type(json_object_iter_key(iter2)); j_counter = json_object_iter_value(iter2); o_counter = json_deep_copy(j_counter); j_specs = json_object_get(o_counter, OIDC_METRICS_SPECS); if ((j_specs) && (json_object_size(j_specs) == 1)) { o_spec = json_object_get(j_specs, OIDC_METRICS_SPEC_DEFAULT); if (o_spec) { json_object_set_new(o_counter, OIDC_METRICS_COUNT, json_copy(o_spec)); json_object_del(o_counter, OIDC_METRICS_SPECS); } } 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); iter2 = json_object_iter_next(j_counters, iter2); } 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); iter2 = json_object_iter(j_timings); while (iter2) { type = oidc_metrics_key2type(json_object_iter_key(iter2)); j_timing = json_object_iter_value(iter2); o_timing = json_deep_copy(j_timing); 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); iter2 = json_object_iter_next(j_timings, iter2); } iter1 = json_object_iter_next(json, iter1); } str = json_dumps(o_json, JSON_COMPACT | JSON_PRESERVE_ORDER); s_json = apr_pstrdup(r->pool, str); free(str); 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_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_CONTENT_TYPE_JSON, OK); } #define OIDC_METRICS_SERVER_PARAM "server_name" #define OIDC_METRICS_COUNTER_PARAM "counter" #define OIDC_METRICS_SPEC_PARAM "spec" /* * return status updates */ static int oidc_metrics_handle_status(request_rec *r, char *s_json) { char *msg = "OK\n"; char *metric = NULL, *s_server = NULL, *spec = NULL; json_t *json = NULL, *j_server = NULL, *j_counters = NULL, *j_counter = NULL, *j_spec = NULL, *j_specs = NULL; json_int_t type = 0; const char *s_key = NULL, *s_name = NULL; void *iter = NULL; oidc_util_get_request_parameter(r, OIDC_METRICS_SERVER_PARAM, &s_server); oidc_util_get_request_parameter(r, OIDC_METRICS_COUNTER_PARAM, &metric); oidc_util_get_request_parameter(r, OIDC_METRICS_SPEC_PARAM, &spec); if (s_server == NULL) s_server = "localhost"; if ((metric) && (s_server)) { json = oidc_metrics_json_parse_r(r, s_json); if (json == NULL) goto end; j_server = json_object_get(json, s_server); 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, metric) == 0) { j_specs = json_object_get(j_counter, OIDC_METRICS_SPECS); j_spec = json_object_get(j_specs, _metrics_spec2key(spec)); if (j_spec) msg = apr_psprintf(r->pool, "OK: %s\n", _json_int2str(r->pool, json_integer_value(j_spec))); 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 (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_SPEC "value" typedef struct oidc_metric_prometheus_callback_ctx_t { char *s_result; apr_pool_t *pool; } oidc_metric_prometheus_callback_ctx_t; int oidc_metrics_prometheus_counters(void *rec, const char *key, const char *value) { const char *s_server = NULL, *s_spec = NULL; json_t *j_counter = NULL, *j_specs = NULL, *j_spec = NULL; oidc_metric_prometheus_callback_ctx_t *ctx = (oidc_metric_prometheus_callback_ctx_t *)rec; json_t *o_counter = (json_t *)value; 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); void *iter1 = json_object_iter(o_counter); while (iter1) { s_server = json_object_iter_key(iter1); j_counter = json_object_iter_value(iter1); j_specs = json_object_get(j_counter, OIDC_METRICS_SPECS); void *iter2 = json_object_iter(j_specs); while (iter2) { s_spec = json_object_iter_key(iter2); j_spec = json_object_iter_value(iter2); s_text = apr_psprintf(ctx->pool, "%s%s{%s=\"%s\"", s_text, s_label, OIDC_METRICS_PROMETHEUS_SERVER, s_server); if (_oidc_strcmp(OIDC_METRICS_SPEC_DEFAULT, s_spec) != 0) s_text = apr_psprintf(ctx->pool, "%s,%s=\"%s\"", s_text, OIDC_METRICS_PROMETHEUS_SPEC, s_spec); s_text = apr_psprintf(ctx->pool, "%s} %s\n", s_text, _json_int2str(ctx->pool, json_integer_value(j_spec))); iter2 = json_object_iter_next(j_specs, iter2); } iter1 = json_object_iter_next(o_counter, iter1); } ctx->s_result = apr_pstrcat(ctx->pool, ctx->s_result, s_text, "\n", NULL); json_decref(o_counter); return 1; } int oidc_metrics_prometheus_timings(void *rec, const char *key, const char *value) { const char *s_server = NULL, *s_key = NULL, *s_bucket = NULL; json_t *j_timing = NULL, *j_member = NULL; oidc_metric_prometheus_callback_ctx_t *ctx = (oidc_metric_prometheus_callback_ctx_t *)rec; json_t *o_timer = (json_t *)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_table_t *table, 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_table_get(table, type); if (dst) { json_object_set(dst, server, src); } else { dst = json_object(); json_object_set(dst, server, src); apr_table_setn(table, type, (const char *)dst); } iter = json_object_iter_next(list, iter); } } 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_table_t *t_counters = apr_table_make(r->pool, 1); apr_table_t *t_timings = apr_table_make(r->pool, 1); 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); } apr_table_do(oidc_metrics_prometheus_counters, &ctx, t_counters, NULL); apr_table_do(oidc_metrics_prometheus_timings, &ctx, t_timings, NULL); 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 *); typedef struct oidc_metrics_handler_t { const char *format; oidc_metrics_handler_function_t callback; int reset; } oidc_metrics_content_handler_t; const oidc_metrics_content_handler_t _oidc_metrics_handlers[] = { // first is default {"prometheus", oidc_metrics_handle_prometheus, 1}, {"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_get_request_parameter(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_get_request_parameter(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.15.1/src/metrics.h000066400000000000000000000203001455620533500176540ustar00rootroot00000000000000/* * 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-2024 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_ apr_byte_t oidc_metrics_is_valid_classname(apr_pool_t *pool, const char *name, char **valid_names); apr_byte_t oidc_metrics_cache_post_config(server_rec *s); apr_status_t oidc_metrics_cache_child_init(apr_pool_t *p, server_rec *s); apr_status_t oidc_metrics_cache_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 (cfg->metrics_hook_data != NULL) { \ _oidc_metrics_tstart = apr_time_now(); \ } #define OIDC_METRICS_TIMING_ADD(r, cfg, type) \ if (cfg->metrics_hook_data != NULL) { \ if (apr_hash_get(cfg->metrics_hook_data, _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 (cfg->metrics_hook_data != 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 \ const char *v = NULL; \ if (cfg->metrics_hook_data != NULL) { \ v = oidc_request_state_get(r, OIDC_METRICS_REQUEST_STATE_TIMER_KEY); \ if (v) { \ sscanf(v, "%" APR_TIME_T_FMT, &_oidc_metrics_tstart); \ OIDC_METRICS_TIMING_ADD(r, cfg, type); \ } \ } // 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_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_ERROR_PROVIDER, OM_REDIRECT_URI_ERROR_INVALID, OM_CONTENT_REQUEST_DECLINED, OM_CONTENT_REQUEST_INFO, 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 *spec); #define OIDC_METRICS_COUNTER_INC_SPEC(r, cfg, type, spec) \ if (cfg->metrics_hook_data != NULL) \ if (apr_hash_get(cfg->metrics_hook_data, _oidc_metrics_counters_info[type].class_name, \ APR_HASH_KEY_STRING) != NULL) \ oidc_metrics_counter_inc(r, type, spec); #define OIDC_METRICS_COUNTER_INC(r, cfg, type) OIDC_METRICS_COUNTER_INC_SPEC(r, cfg, type, NULL); #endif /* MOD_AUTH_OPENIDC_METRICS_H_ */ mod_auth_openidc-2.4.15.1/src/mod_auth_openidc.c000066400000000000000000005152311455620533500215160ustar00rootroot00000000000000/* * 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-2024 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 "metrics.h" static int oidc_handle_logout_request(request_rec *r, oidc_cfg *c, oidc_session_t *session, const char *url); // TODO: // - sort out oidc_cfg vs. oidc_dir_cfg stuff // - rigid input checking on discovery responses // - check self-issued support // - README.quickstart // - refresh metadata once-per too? (for non-signing key changes) extern module AP_MODULE_DECLARE_DATA auth_openidc_module; /* * 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_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_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 *cfg = ap_get_module_config(r->server->module_config, &auth_openidc_module); const char *prefix = oidc_cfg_claim_prefix(r); apr_hash_t *hdrs = apr_hash_make(r->pool); if (_oidc_strcmp(prefix, "") == 0) { if ((cfg->white_listed_claims != NULL) && (apr_hash_count(cfg->white_listed_claims) > 0)) hdrs = apr_hash_overlay(r->pool, cfg->white_listed_claims, 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(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 ((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; apr_array_header_t *strip = oidc_dir_cfg_strip_cookies(r); char *cookies = apr_pstrdup(r->pool, oidc_util_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_util_hdr_in_cookie_set(r, result); } } #define OIDC_SHA1_LEN 20 /* * calculates a hash value based on request fingerprint plus a provided nonce string. */ static char *oidc_get_browser_state_hash(request_rec *r, oidc_cfg *c, const char *nonce) { oidc_debug(r, "enter"); /* helper to hold to header values */ const char *value = NULL; /* the hash context */ apr_sha1_ctx_t sha1; /* Initialize the hash context */ apr_sha1_init(&sha1); if (c->state_input_headers & OIDC_STATE_INPUT_HEADERS_X_FORWARDED_FOR) { /* get the X-FORWARDED-FOR header value */ value = oidc_util_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 (c->state_input_headers & OIDC_STATE_INPUT_HEADERS_USER_AGENT) { /* get the USER-AGENT header value */ value = oidc_util_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 */ unsigned char hash[OIDC_SHA1_LEN]; apr_sha1_final(hash, &sha1); /* base64url-encode the resulting hash and return it */ char *result = NULL; oidc_base64url_encode(r, &result, (const char *)hash, OIDC_SHA1_LEN, TRUE); return result; } /* * return the name for the state cookie */ static char *oidc_get_state_cookie_name(request_rec *r, const char *state) { return apr_psprintf(r->pool, "%s%s", oidc_cfg_dir_state_cookie_prefix(r), state); } /* * check if s_json is valid provider metadata */ static apr_byte_t oidc_provider_validate_metadata_str(request_rec *r, oidc_cfg *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", c->provider.metadata_url); json_decref(*j_provider); return FALSE; } return TRUE; } /* * return the static provider configuration, i.e. from a metadata URL or configuration primitives */ static apr_byte_t oidc_provider_static_config(request_rec *r, oidc_cfg *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 ((c->metadata_dir != NULL) || (c->provider.metadata_url == NULL)) { *provider = &c->provider; return TRUE; } oidc_cache_get_provider(r, c->provider.metadata_url, &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, c->provider.metadata_url, &j_provider, &s_json) == FALSE) { oidc_error(r, "could not retrieve metadata from url: %s", c->provider.metadata_url); 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, c->provider.metadata_url, s_json, apr_time_now() + apr_time_from_sec(c->provider_metadata_refresh_interval <= 0 ? OIDC_CACHE_PROVIDER_METADATA_EXPIRY_DEFAULT : c->provider_metadata_refresh_interval)); } *provider = oidc_cfg_provider_copy(r->pool, &c->provider); if (oidc_metadata_provider_parse(r, c, j_provider, *provider) == FALSE) { oidc_error(r, "could not parse metadata from url: %s", c->provider.metadata_url); json_decref(j_provider); return FALSE; } json_decref(j_provider); return TRUE; } /* * return the oidc_provider_t struct for the specified issuer */ static oidc_provider_t *oidc_get_provider_for_issuer(request_rec *r, oidc_cfg *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 (c->metadata_dir != 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; } /* * find out whether the request is a response from an IDP discovery page */ static apr_byte_t oidc_is_discovery_response(request_rec *r, oidc_cfg *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); } /* * return the HTTP method being called: only for POST data persistence purposes */ static const char *oidc_original_request_method(request_rec *r, oidc_cfg *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_get_redirect_uri(r, cfg))) && (oidc_is_discovery_response(r, cfg))) { oidc_util_get_request_parameter(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(r) == 0) return OIDC_METHOD_GET; const char *content_type = oidc_util_hdr_in_content_type_get(r); if ((r->method_number == M_POST) && (_oidc_strcmp(content_type, OIDC_CONTENT_TYPE_FORM_ENCODED) == 0)) method = OIDC_METHOD_FORM_POST; } oidc_debug(r, "return: %s", method); return method; } static char *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_post_preserve_javascript(request_rec *r, const char *location, char **javascript, char **javascript_method) { if (oidc_cfg_dir_preserve_post(r) == 0) return FALSE; oidc_debug(r, "enter"); oidc_cfg *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_util_escape_string(r, elts[i].key), oidc_util_escape_string(r, elts[i].val), i < arr->nelts - 1 ? "," : ""); } json = apr_psprintf(r->pool, "{ %s }", json); if (cfg->post_preserve_template != NULL) if (oidc_util_html_send_in_template(r, cfg->post_preserve_template, &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_request_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); } typedef struct oidc_state_cookies_t { char *name; apr_time_t timestamp; struct oidc_state_cookies_t *next; } oidc_state_cookies_t; static int oidc_delete_oldest_state_cookies(request_rec *r, oidc_cfg *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; while (number_of_valid_state_cookies >= max_number_of_state_cookies) { oldest = first; prev_oldest = NULL; prev = first; cur = first ? first->next : NULL; while (cur) { if ((cur->timestamp < oldest->timestamp)) { oldest = cur; prev_oldest = prev; } prev = cur; cur = cur->next; } 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_util_set_cookie(r, oldest->name, "", 0, OIDC_COOKIE_EXT_SAME_SITE_NONE(c, r)); if (prev_oldest) prev_oldest->next = oldest->next; else first = first ? first->next : NULL; 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 */ static int oidc_clean_expired_state_cookies(request_rec *r, oidc_cfg *c, const char *currentCookieName, int delete_oldest) { int number_of_valid_state_cookies = 0; oidc_state_cookies_t *first = NULL, *last = NULL; char *cookie, *tokenizerCtx = NULL; char *cookies = apr_pstrdup(r->pool, oidc_util_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 (strstr(cookie, oidc_cfg_dir_state_cookie_prefix(r)) == cookie) { char *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(c->state_timeout)) { oidc_warn( r, "state (%s) has expired (original_url=%s)", cookieName, oidc_proto_state_get_original_url(proto_state)); oidc_util_set_cookie( r, cookieName, "", 0, OIDC_COOKIE_EXT_SAME_SITE_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_util_set_cookie(r, cookieName, "", 0, OIDC_COOKIE_EXT_SAME_SITE_NONE(c, r)); } } } } cookie = apr_strtok(NULL, OIDC_STR_SEMI_COLON, &tokenizerCtx); } } if (delete_oldest > 0) number_of_valid_state_cookies = oidc_delete_oldest_state_cookies(r, c, number_of_valid_state_cookies, c->max_number_of_state_cookies, first); return number_of_valid_state_cookies; } /* * restore the state that was maintained between authorization request and response in an encrypted cookie */ static apr_byte_t oidc_restore_proto_state(request_rec *r, oidc_cfg *c, const char *state, oidc_proto_state_t **proto_state) { oidc_debug(r, "enter"); const char *cookieName = oidc_get_state_cookie_name(r, state); /* clean expired state cookies to avoid pollution */ oidc_clean_expired_state_cookies(r, c, cookieName, FALSE); /* get the state cookie value first */ char *cookieValue = oidc_util_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_util_set_cookie(r, cookieName, "", 0, OIDC_COOKIE_EXT_SAME_SITE_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_get_browser_state_hash(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(c->state_timeout)) { oidc_error(r, "state has expired"); if ((c->default_sso_url == NULL) || (apr_table_get(r->subprocess_env, "OIDC_NO_DEFAULT_URL_ON_STATE_TIMEOUT") != NULL)) { oidc_util_html_send_error( r, c->error_template, "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; } /* * 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_authorization_request_set_cookie(request_rec *r, oidc_cfg *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_clean_expired_state_cookies(r, c, NULL, oidc_cfg_delete_oldest_state_cookies(c)); int max_number_of_cookies = oidc_cfg_max_number_of_state_cookies(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); /* * TODO: the html_send code below caters for the case that there's a user behind a * browser generating this request, rather than a piece of XHR code; how would an * XHR client handle this? */ /* * it appears that sending content with a 503 turns the HTTP status code * into a 200 so we'll avoid that for now: the user will see Apache specific * readable text anyway * return oidc_util_html_send_error(r, c->error_template, "Too Many Outstanding Requests", apr_psprintf(r->pool, "No authentication request could be generated since there are too many outstanding authentication requests already; you may have to wait up to %d seconds to be able to create a new request", c->state_timeout), HTTP_SERVICE_UNAVAILABLE); */ return HTTP_SERVICE_UNAVAILABLE; } /* assemble the cookie name for the state cookie */ const char *cookieName = oidc_get_state_cookie_name(r, state); /* set it as a cookie */ oidc_util_set_cookie(r, cookieName, cookieValue, -1, OIDC_COOKIE_SAMESITE_LAX(c, r)); return OK; } /* * 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 */ static apr_byte_t oidc_set_app_claims(request_rec *r, const oidc_cfg *const cfg, const char *s_claims) { json_t *j_claims = NULL; /* 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(r), cfg->claim_delimiter, oidc_cfg_dir_pass_info_in_headers(r), oidc_cfg_dir_pass_info_in_envvars(r), oidc_cfg_dir_pass_info_encoding(r)); /* release resources */ json_decref(j_claims); } return TRUE; } static int oidc_authenticate_user(request_rec *r, oidc_cfg *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); /* * log message about max session duration */ static 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_util_hdr_in_x_requested_with_get(r) != NULL) && (_oidc_strnatcasecmp(oidc_util_hdr_in_x_requested_with_get(r), OIDC_HTTP_HDR_VAL_XML_HTTP_REQUEST) == 0)) return FALSE; if ((oidc_util_hdr_in_sec_fetch_mode_get(r) != NULL) && (_oidc_strnatcasecmp(oidc_util_hdr_in_sec_fetch_mode_get(r), OIDC_HTTP_HDR_VAL_NAVIGATE) != 0)) return FALSE; if ((oidc_util_hdr_in_sec_fetch_dest_get(r) != NULL) && (_oidc_strnatcasecmp(oidc_util_hdr_in_sec_fetch_dest_get(r), OIDC_HTTP_HDR_VAL_DOCUMENT) != 0)) return FALSE; if ((oidc_util_hdr_in_accept_contains(r, OIDC_CONTENT_TYPE_TEXT_HTML) == FALSE) && (oidc_util_hdr_in_accept_contains(r, OIDC_CONTENT_TYPE_APP_XHTML_XML) == FALSE) && (oidc_util_hdr_in_accept_contains(r, OIDC_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 *c) { /* see if we've configured OIDCUnAuthAction for this path */ switch (oidc_dir_cfg_unauth_action(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_dir_cfg_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_authenticate_user(r, c, NULL, oidc_get_current_url(r, c->x_forwarded_headers), NULL, NULL, NULL, oidc_dir_cfg_path_auth_request_params(r), oidc_dir_cfg_path_scope(r)); } /* * check if maximum session duration was exceeded */ static apr_byte_t oidc_check_max_session_duration(request_rec *r, oidc_cfg *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 */ static apr_byte_t oidc_check_cookie_domain(request_rec *r, oidc_cfg *cfg, oidc_session_t *session) { const char *c_cookie_domain = cfg->cookie_domain ? cfg->cookie_domain : oidc_get_current_url_host(r, cfg->x_forwarded_headers); const char *s_cookie_domain = oidc_session_get_cookie_domain(r, session); if ((s_cookie_domain == NULL) || (_oidc_strcmp(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 *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; } /* * store claims resolved from the userinfo endpoint in the session */ static void oidc_store_userinfo_claims(request_rec *r, oidc_cfg *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 (c->session_type != 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 (provider->userinfo_refresh_interval > 0) oidc_session_reset_userinfo_last_refresh(r, session); } #define OIDC_REFRESH_ERROR_NONE 1 #define OIDC_REFRESH_ERROR_GENERAL 2 #define OIDC_REFRESH_ERROR_PARALLEL_REFRESH 3 #define OIDC_PARALLEL_REFRESH_NOT_ALLOWED_ENVVAR "OIDC_PARALLEL_REFRESH_NOT_ALLOWED" /* * execute refresh token grant to refresh the existing access token */ static apr_byte_t oidc_refresh_token_grant(request_rec *r, oidc_cfg *c, oidc_session_t *session, oidc_provider_t *provider, char **new_access_token, char **new_id_token, int *error_code) { 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; char *value = NULL; const char *refresh_token = NULL; oidc_debug(r, "enter"); oidc_cache_mutex_lock(r->pool, r->server, c->refresh_mutex); /* 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"); *error_code = OIDC_REFRESH_ERROR_GENERAL; goto end; } // check if an existing refresh is going on or if it was just exchanged for a new one in another server oidc_cache_get_refresh_token(r, refresh_token, &value); if (value != NULL) { oidc_debug(r, "refresh token routine called again within %d seconds for the same refresh token: %s", c->http_timeout_long.request_timeout, refresh_token); *error_code = OIDC_REFRESH_ERROR_PARALLEL_REFRESH; if (apr_table_get(r->subprocess_env, OIDC_PARALLEL_REFRESH_NOT_ALLOWED_ENVVAR) != NULL) { oidc_warn(r, "aborting refresh token grant for a refresh token that was already used before"); goto end; } } // "lock" the refresh token best effort; this does not work failsafe in a clustered setup... oidc_cache_set_refresh_token(r, refresh_token, refresh_token, apr_time_now() + apr_time_from_sec(c->http_timeout_long.request_timeout)); oidc_debug(r, "refreshing refresh_token: %s", refresh_token); // don't unlock after this since other processes may be waiting for the lock to refresh the same refresh token OIDC_METRICS_TIMING_START(r, c); /* refresh the tokens by calling the token endpoint */ if (oidc_proto_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); if (*error_code != OIDC_REFRESH_ERROR_PARALLEL_REFRESH) *error_code = OIDC_REFRESH_ERROR_GENERAL; goto end; } OIDC_METRICS_TIMING_ADD(r, c, OM_PROVIDER_REFRESH); /* 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_expires(r, session, expires_in); /* reset the access token refresh timestamp */ oidc_session_reset_access_token_last_refresh(r, session); /* see if we need to return it as a parameter */ if (new_access_token != NULL) *new_access_token = s_access_token; /* 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 (c->store_id_token == TRUE) 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 (provider->session_max_duration == 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, "refreshed refresh_token: %s into %s", refresh_token, s_refresh_token); *error_code = OIDC_REFRESH_ERROR_NONE; rc = TRUE; end: oidc_cache_mutex_unlock(r->pool, r->server, c->refresh_mutex); return rc; } /* * retrieve claims from the userinfo endpoint and return the stringified response */ static const char *oidc_retrieve_claims_from_userinfo_endpoint(request_rec *r, oidc_cfg *c, oidc_provider_t *provider, const char *access_token, oidc_session_t *session, char *id_token_sub, char **userinfo_jwt, int *error_code) { char *result = NULL; char *refreshed_access_token = NULL; json_t *id_token_claims = NULL; oidc_debug(r, "enter"); /* see if a userinfo endpoint is set, otherwise there's nothing to do for us */ if (provider->userinfo_endpoint_url == 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_resolve_userinfo(r, c, provider, id_token_sub, access_token, &result, userinfo_jwt) == 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; } /* 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, NULL, error_code) == 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_resolve_userinfo(r, c, provider, id_token_sub, refreshed_access_token, &result, userinfo_jwt) == 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 */ static apr_byte_t oidc_refresh_claims_from_userinfo_endpoint(request_rec *r, oidc_cfg *cfg, oidc_session_t *session, apr_byte_t *needs_save, int *error_code) { apr_byte_t rc = TRUE; oidc_provider_t *provider = NULL; const char *claims = NULL; const char *access_token = NULL; char *userinfo_jwt = NULL; /* see if we can do anything here, i.e. a refresh interval is configured */ apr_time_t interval = oidc_session_get_userinfo_refresh_interval(r, session); oidc_debug(r, "interval=%" APR_TIME_T_FMT, apr_time_sec(interval)); if (interval > 0) { /* get the current provider info */ if (oidc_get_provider_from_session(r, cfg, session, &provider) == FALSE) { *needs_save = TRUE; return FALSE; } if (provider->userinfo_endpoint_url != 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", apr_time_sec(last_refresh + interval - apr_time_now())); /* see if we need to refresh again */ if (last_refresh + interval < apr_time_now()) { /* get the current access token */ access_token = oidc_session_get_access_token(r, session); /* retrieve the current claims */ claims = oidc_retrieve_claims_from_userinfo_endpoint( r, cfg, provider, access_token, session, NULL, &userinfo_jwt, error_code); /* store claims resolved from userinfo endpoint */ oidc_store_userinfo_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; } /* * 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 */ static apr_byte_t oidc_session_pass_tokens(request_rec *r, oidc_cfg *cfg, oidc_session_t *session, apr_byte_t *needs_save) { apr_byte_t pass_headers = oidc_cfg_dir_pass_info_in_headers(r); apr_byte_t pass_envvars = oidc_cfg_dir_pass_info_in_envvars(r); int pass_hdr_as = oidc_cfg_dir_pass_info_encoding(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(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_headers, pass_envvars, pass_hdr_as); } /* 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(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_headers, pass_envvars, pass_hdr_as); } /* set the expiry timestamp in the app headers/variables */ const char *access_token_expires = oidc_session_get_access_token_expires(r, session); if ((oidc_cfg_dir_pass_access_token(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_headers, pass_envvars, pass_hdr_as); } /* * 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(cfg->session_inactivity_timeout); 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; } /* log message about session expiry */ oidc_log_session_expires(r, "session inactivity timeout", session->expiry); return TRUE; } static apr_byte_t oidc_refresh_access_token_before_expiry(request_rec *r, oidc_cfg *cfg, oidc_session_t *session, int ttl_minimum, apr_byte_t *needs_save, int *error_code) { const char *s_access_token_expires = NULL; 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; s_access_token_expires = oidc_session_get_access_token_expires(r, session); if (s_access_token_expires == NULL) { 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; } 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; } if (sscanf(s_access_token_expires, "%" APR_TIME_T_FMT, &t_expires) != 1) { oidc_error(r, "could not parse s_access_token_expires %s", s_access_token_expires); return FALSE; } t_expires = apr_time_from_sec(t_expires - 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, error_code) == FALSE) { oidc_warn(r, "access_token could not be refreshed"); *needs_save = FALSE; return FALSE; } *needs_save = TRUE; return TRUE; } #define OIDC_USERINFO_SIGNED_JWT_EXPIRE_DEFAULT 0 #define OIDC_USERINFO_SIGNED_JWT_CACHE_TTL_ENVVAR "OIDC_USERINFO_SIGNED_JWT_CACHE_TTL" 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 (s_ttl ? _oidc_str_to_int(s_ttl) : OIDC_USERINFO_SIGNED_JWT_EXPIRE_DEFAULT); } #define OIDC_JQ_FILTER_EXPIRE_DEFAULT 600 #define OIDC_JQ_FILTER_CACHE_TTL_ENVVAR "OIDC_JQ_FILTER_CACHE_TTL" 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 (s_ttl ? _oidc_str_to_int(s_ttl) : OIDC_JQ_FILTER_EXPIRE_DEFAULT); } static apr_byte_t oidc_userinfo_create_signed_jwt(request_rec *r, oidc_cfg *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; const char *access_token_expires = NULL; 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); jwk = oidc_util_key_list_first(cfg->private_keys, -1, OIDC_JOSE_JWK_SIG_STR); // TODO: detect at config time 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, 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; } json_object_set_new(jwt->payload.value.json, OIDC_CLAIM_AUD, json_string(oidc_get_current_url(r, cfg->x_forwarded_headers))); json_object_set_new(jwt->payload.value.json, OIDC_CLAIM_ISS, json_string(cfg->provider.issuer)); 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_object(r, jwt->payload.value.json, JSON_PRESERVE_ORDER | JSON_COMPACT); if (oidc_jose_hash_and_base64url_encode(r->pool, OIDC_JOSE_ALG_SHA256, s_claims, 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 != 0) 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_proto_generate_random_string(r, &jti, 16); 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 ? _oidc_str_to_int(access_token_expires) : apr_time_sec(apr_time_now()) + OIDC_USERINFO_SIGNED_JWT_EXPIRE_DEFAULT)); } 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_jwt_serialize(r->pool, jwt, &err); if (*cser == NULL) { oidc_error(r, "oidc_jwt_serialize failed: %s", oidc_jose_e2s(r->pool, err)); goto end; } if (ttl != 0) { if (apr_table_get(r->subprocess_env, OIDC_USERINFO_SIGNED_JWT_CACHE_TTL_ENVVAR) == NULL) { oidc_json_object_get_int(jwt->payload.value.json, OIDC_CLAIM_EXP, &exp, 0); if (exp != 0) expiry = apr_time_from_sec(exp); } if (expiry == 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); } rv = TRUE; end: if (json) json_decref(json); if (jwt) oidc_jwt_destroy(jwt); return rv; } static void oidc_pass_userinfo_as(request_rec *r, oidc_cfg *cfg, oidc_session_t *session, const char *s_claims, apr_byte_t pass_headers, apr_byte_t pass_envvars, int pass_hdr_as) { 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_dir_cfg_pass_user_info_as(r); #ifdef USE_LIBJQ s_claims = oidc_util_jq_filter(r, s_claims, oidc_dir_cfg_userinfo_claims_expr(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_headers, pass_envvars, pass_hdr_as); break; case OIDC_PASS_USERINFO_AS_JWT: if (cfg->session_type != 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_headers, pass_envvars, pass_hdr_as); } 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_headers, pass_envvars, pass_hdr_as); } break; default: break; } } } /* * 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 *cfg, oidc_session_t *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; int error_code = OIDC_REFRESH_ERROR_NONE; 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 */ char *authn_header = oidc_cfg_dir_authn_header(r); apr_byte_t pass_headers = oidc_cfg_dir_pass_info_in_headers(r); apr_byte_t pass_envvars = oidc_cfg_dir_pass_info_in_envvars(r); int pass_hdr_as = oidc_cfg_dir_pass_info_encoding(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 needed, refresh the access token */ rv = oidc_refresh_access_token_before_expiry( r, cfg, session, oidc_cfg_dir_refresh_access_token_before_expiry(r), needs_save, &error_code); if (rv == FALSE) { *needs_save = FALSE; oidc_debug(r, "dir_action_on_error_refresh: %d", oidc_cfg_dir_action_on_error_refresh(r)); OIDC_METRICS_COUNTER_INC(r, cfg, OM_SESSION_ERROR_REFRESH_ACCESS_TOKEN); if (error_code != OIDC_REFRESH_ERROR_PARALLEL_REFRESH) { if (oidc_cfg_dir_action_on_error_refresh(r) == OIDC_ON_ERROR_LOGOUT) { return oidc_handle_logout_request(r, cfg, session, oidc_get_absolute_url(r, cfg, cfg->default_slo_url)); } if (oidc_cfg_dir_action_on_error_refresh(r) == OIDC_ON_ERROR_AUTHENTICATE) { oidc_session_kill(r, session); return oidc_handle_unauthenticated_user(r, cfg); } } return HTTP_INTERNAL_SERVER_ERROR; } /* if needed, refresh claims from the user info endpoint */ rv = oidc_refresh_claims_from_userinfo_endpoint(r, cfg, session, needs_save, &error_code); if (rv == FALSE) { *needs_save = FALSE; oidc_debug(r, "action_on_userinfo_error: %d", cfg->action_on_userinfo_error); OIDC_METRICS_COUNTER_INC(r, cfg, OM_SESSION_ERROR_REFRESH_USERINFO); if (error_code != OIDC_REFRESH_ERROR_PARALLEL_REFRESH) { if (cfg->action_on_userinfo_error == OIDC_ON_ERROR_LOGOUT) { return oidc_handle_logout_request(r, cfg, session, oidc_get_absolute_url(r, cfg, cfg->default_slo_url)); } if (cfg->action_on_userinfo_error == OIDC_ON_ERROR_AUTHENTICATE) { oidc_session_kill(r, session); return oidc_handle_unauthenticated_user(r, cfg); } } return HTTP_INTERNAL_SERVER_ERROR; } /* set the user authentication HTTP header if set and required */ if ((r->user != NULL) && (authn_header != NULL)) oidc_util_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_dir_cfg_pass_id_token_as(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_dir_cfg_pass_id_token_as(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_headers, pass_envvars, pass_hdr_as); } if ((oidc_dir_cfg_pass_id_token_as(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_headers, pass_envvars, pass_hdr_as); } 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, needs_save) == FALSE) return HTTP_INTERNAL_SERVER_ERROR; oidc_pass_userinfo_as(r, cfg, session, s_claims, pass_headers, pass_envvars, pass_hdr_as); /* return "user authenticated" status */ return OK; } /* * 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_authorization_response_match_state(request_rec *r, oidc_cfg *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_restore_proto_state(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; } /* * redirect the browser to the session logout endpoint */ static int oidc_session_redirect_parent_window_to_logout(request_rec *r, oidc_cfg *c) { oidc_debug(r, "enter"); char *java_script = apr_psprintf(r->pool, " \n", oidc_util_javascript_escape(r->pool, oidc_get_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_authorization_response_error(request_rec *r, oidc_cfg *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_session_redirect_parent_window_to_logout(r, c); } return oidc_util_html_send_error(r, c->error_template, apr_psprintf(r->pool, "OpenID Connect Provider error: %s", error), error_description, c->error_template ? OK : HTTP_BAD_REQUEST); } /* * 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; } /* * set the unique user identifier that will be propagated in the Apache r->user and REMOTE_USER variables */ static apr_byte_t oidc_set_request_user(request_rec *r, oidc_cfg *c, oidc_provider_t *provider, oidc_jwt_t *jwt, const char *s_claims) { char *issuer = provider->issuer; char *claim_name = apr_pstrdup(r->pool, c->remote_user_claim.claim_name); 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 = (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, c->remote_user_claim.reg_exp, c->remote_user_claim.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, c->remote_user_claim.reg_exp, c->remote_user_claim.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", c->remote_user_claim.claim_name, 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, c->remote_user_claim.claim_name, c->remote_user_claim.reg_exp ? apr_psprintf(r->pool, " and expression: \"%s\" and replace string: \"%s\"", c->remote_user_claim.reg_exp, c->remote_user_claim.replace) : ""); return TRUE; } static char *oidc_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 */ static apr_byte_t oidc_save_in_session(request_rec *r, oidc_cfg *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 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(c->session_inactivity_timeout); /* 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 (c->store_id_token == TRUE) { /* 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, provider->issuer); /* 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) && (provider->check_session_iframe != 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, provider->check_session_iframe, provider->client_id); } else if (provider->check_session_iframe == 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", provider->check_session_iframe); } /* store the, possibly, provider specific userinfo_refresh_interval for performance reasons */ oidc_session_set_userinfo_refresh_interval(r, session, provider->userinfo_refresh_interval); /* store claims resolved from userinfo endpoint */ oidc_store_userinfo_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 associated expires_in value */ oidc_session_set_access_token_expires(r, session, expires_in); /* reset the access token refresh timestamp */ oidc_session_reset_access_token_last_refresh(r, session); } /* 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 = (provider->session_max_duration == 0) ? apr_time_from_sec(id_token_jwt->payload.exp) : (apr_time_now() + apr_time_from_sec(provider->session_max_duration)); oidc_session_set_session_expires(r, session, session_expires); oidc_debug(r, "provider->session_max_duration = %d, session_expires=%" APR_TIME_T_FMT, provider->session_max_duration, 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, c->cookie_domain ? c->cookie_domain : oidc_get_current_url_host(r, c->x_forwarded_headers)); char *sid = NULL; oidc_debug(r, "provider->backchannel_logout_supported=%d", provider->backchannel_logout_supported); /* * 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_make_sid_iss_unique(r, sid, provider->issuer); /* store the session */ return oidc_session_save(r, session, TRUE); } /* * parse the expiry for the access token */ static int oidc_parse_expires_in(request_rec *r, const char *expires_in) { int number = _oidc_str_to_int(expires_in); if (number <= 0) oidc_warn(r, "could not parse \"expires_in\" value (%s) into a positive integer", expires_in); return number; } /* * handle the different flows (hybrid, implicit, Authorization Code) */ static apr_byte_t oidc_handle_flows(request_rec *r, oidc_cfg *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_authorization_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_authorization_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_handle_authorization_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_handle_authorization_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_handle_authorization_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_handle_authorization_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; } /* handle the browser back on an authorization response */ static apr_byte_t oidc_handle_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_util_hdr_out_location_set(r, o_url); return TRUE; } } return FALSE; } static char *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_handle_authorization_response(request_rec *r, oidc_cfg *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_handle_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_authorization_response_match_state(r, c, apr_table_get(params, OIDC_PROTO_STATE), &provider, &proto_state) == FALSE) { if (c->default_sso_url != NULL) { oidc_warn(r, "invalid authorization response state; a default SSO URL is set, sending the user " "there: %s", c->default_sso_url); oidc_util_hdr_out_location_set(r, oidc_get_absolute_url(r, c, c->default_sso_url)); 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 (c->error_template) { // retain backwards compatibility int rc = HTTP_BAD_REQUEST; if ((r->user) && (strncmp(r->user, "", 1) == 0)) { r->header_only = 1; r->user = NULL; rc = OK; } OIDC_METRICS_COUNTER_INC(r, c, OM_AUTHN_RESPONSE_ERROR_STATE_MISMATCH); return rc; } // 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, c->error_template, "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_authorization_response_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_handle_flows(r, c, proto_state, provider, params, response_mode, &jwt) == FALSE) { OIDC_METRICS_COUNTER_INC(r, c, OM_AUTHN_RESPONSE_ERROR_PROTOCOL); return oidc_authorization_response_error(r, c, proto_state, "Error in handling response type.", NULL); } if (jwt == NULL) { oidc_error(r, "no id_token was provided"); return oidc_authorization_response_error(r, c, proto_state, "No id_token was provided.", NULL); } int expires_in = oidc_parse_expires_in(r, apr_table_get(params, OIDC_PROTO_EXPIRES_IN)); char *userinfo_jwt = NULL; int error_code = OIDC_REFRESH_ERROR_NONE; /* * 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_retrieve_claims_from_userinfo_endpoint(r, c, provider, apr_table_get(params, OIDC_PROTO_ACCESS_TOKEN), NULL, jwt->payload.sub, &userinfo_jwt, &error_code); /* 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_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_authorization_response_error(r, c, proto_state, "User changed!", NULL); } } /* store resolved information in the session */ if (oidc_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), 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_authorization_response_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 (c->post_restore_template != NULL) if (oidc_util_html_send_in_template( r, c->post_restore_template, &post_restore_template_contents, original_url, OIDC_POST_PRESERVE_ESCAPE_JAVASCRIPT, "", OIDC_POST_PRESERVE_ESCAPE_NONE, OK) == OK) return TRUE; return oidc_request_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_util_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 */ static int oidc_handle_post_authorization_response(request_rec *r, oidc_cfg *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, c->error_template, "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 = (char *)apr_table_get(params, OIDC_PROTO_RESPONSE_MODE); /* do the actual implicit work */ return oidc_handle_authorization_response(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 */ static int oidc_handle_redirect_authorization_response(request_rec *r, oidc_cfg *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_handle_authorization_response(r, c, session, params, OIDC_PROTO_RESPONSE_MODE_QUERY); } /* * present the user with an OP selection screen */ static int oidc_discovery(request_rec *r, oidc_cfg *cfg) { oidc_debug(r, "enter"); /* obtain the URL we're currently accessing, to be stored in the state/session */ char *current_url = oidc_get_current_url(r, cfg->x_forwarded_headers); 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_dir_cfg_path_scope(r); const char *path_auth_request_params = oidc_dir_cfg_path_auth_request_params(r); char *discover_url = oidc_cfg_dir_discover_url(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_util_escape_string(r, current_url), OIDC_DISC_RM_PARAM, method, OIDC_DISC_CB_PARAM, oidc_util_escape_string(r, oidc_get_redirect_uri(r, cfg)), OIDC_CSRF_NAME, oidc_util_escape_string(r, csrf)); if (path_scopes != NULL) url = apr_psprintf(r->pool, "%s&%s=%s", url, OIDC_DISC_SC_PARAM, oidc_util_escape_string(r, path_scopes)); if (path_auth_request_params != NULL) url = apr_psprintf(r->pool, "%s&%s=%s", url, OIDC_DISC_AR_PARAM, oidc_util_escape_string(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_util_set_cookie(r, OIDC_CSRF_NAME, csrf, -1, OIDC_COOKIE_SAMESITE_STRICT(cfg, r)); /* see if we need to preserve POST parameters through Javascript/HTML5 storage */ if (oidc_post_preserve_javascript(r, url, NULL, NULL) == TRUE) return OK; /* do the actual redirect to an external discovery page */ oidc_util_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, cfg->error_template, "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_get_redirect_uri(r, cfg), OIDC_DISC_OP_PARAM, oidc_util_escape_string(r, issuer), OIDC_DISC_RT_PARAM, oidc_util_escape_string(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_util_escape_string(r, path_scopes)); if (path_auth_request_params != NULL) href = apr_psprintf(r->pool, "%s&%s=%s", href, OIDC_DISC_AR_PARAM, oidc_util_escape_string(r, path_auth_request_params)); char *display = (strstr(issuer, "https://") == NULL) ? apr_pstrdup(r->pool, issuer) : apr_pstrdup(r->pool, issuer + _oidc_strlen("https://")); /* strip port number */ // char *p = 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_get_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_util_set_cookie(r, OIDC_CSRF_NAME, csrf, -1, OIDC_COOKIE_SAMESITE_STRICT(cfg, r)); char *javascript = NULL, *javascript_method = NULL; char *html_head = ""; if (oidc_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); } /* * authenticate the user to the selected OP, if the OP is not selected yet perform discovery first */ static int oidc_authenticate_user(request_rec *r, oidc_cfg *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 (c->metadata_dir != 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, provider->response_type, OIDC_PROTO_CODE) == TRUE) && (provider->pkce != NULL)) { /* generate the code verifier value that correlates authorization requests and code exchange requests */ if (provider->pkce->state(r, &pkce_state) == FALSE) return HTTP_INTERNAL_SERVER_ERROR; /* generate the PKCE code challenge */ if (provider->pkce->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, provider->issuer); oidc_proto_state_set_response_type(proto_state, provider->response_type); oidc_proto_state_set_nonce(proto_state, nonce); oidc_proto_state_set_timestamp_now(proto_state); if (provider->response_mode) oidc_proto_state_set_response_mode(proto_state, provider->response_mode); 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_get_browser_state_hash(r, c, nonce); /* * create state that restores the context when the authorization response comes in * and cryptographically bind it to the browser */ rc = oidc_authorization_request_set_cookie(r, c, state, proto_state); if (rc != OK) { oidc_proto_state_destroy(proto_state); return rc; } /* * printout errors if Cookie settings are not going to work * TODO: separate this code out into its own function */ 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_get_redirect_uri(r, c), &r_uri); if ((_oidc_strcmp(o_uri.scheme, r_uri.scheme) != 0) && (_oidc_strcmp(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); oidc_proto_state_destroy(proto_state); return HTTP_INTERNAL_SERVER_ERROR; } if (c->cookie_domain == NULL) { if (_oidc_strcmp(o_uri.hostname, r_uri.hostname) != 0) { char *p = strstr(o_uri.hostname, r_uri.hostname); if ((p == NULL) || (_oidc_strcmp(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_proto_state_destroy(proto_state); OIDC_METRICS_COUNTER_INC(r, c, OM_AUTHN_REQUEST_ERROR_URL); return HTTP_INTERNAL_SERVER_ERROR; } } } else { if (!oidc_util_cookie_domain_valid(r_uri.hostname, c->cookie_domain)) { 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!!", c->cookie_domain, o_uri.hostname, original_url); oidc_proto_state_destroy(proto_state); OIDC_METRICS_COUNTER_INC(r, c, OM_AUTHN_REQUEST_ERROR_URL); return HTTP_INTERNAL_SERVER_ERROR; } } /* send off to the OpenID Connect Provider */ // TODO: maybe show intermediate/progress screen "redirecting to" rc = oidc_proto_authorization_request(r, provider, login_hint, oidc_get_redirect_uri_iss(r, c, provider), state, proto_state, id_token_hint, code_challenge, auth_request_params, path_scope); OIDC_METRICS_TIMING_ADD(r, c, OM_AUTHN_REQUEST); return rc; } /* * check if the target_link_uri matches to configuration settings to prevent an open redirect */ static int oidc_target_link_uri_matches_configuration(request_rec *r, oidc_cfg *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_get_redirect_uri(r, cfg), &r_uri); if (cfg->cookie_domain == 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_strcmp(o_uri.hostname, r_uri.hostname) != 0) { char *p = strstr(o_uri.hostname, r_uri.hostname); if ((p == NULL) || (_oidc_strcmp(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 */ char *p = strstr(o_uri.hostname, cfg->cookie_domain); if ((p == NULL) || (_oidc_strcmp(cfg->cookie_domain, 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.", cfg->cookie_domain, o_uri.hostname, target_link_uri); return FALSE; } } /* see if the cookie_path setting matches the target_link_uri path */ char *cookie_path = oidc_cfg_dir_cookie_path(r); if (cookie_path != NULL) { char *p = (o_uri.path != NULL) ? 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; } #define OIDC_MAX_URL_LENGTH 8192 * 2 apr_byte_t oidc_validate_redirect_url(request_rec *r, oidc_cfg *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 (c->redirect_urls_allowed != NULL) { for (hi = apr_hash_first(NULL, c->redirect_urls_allowed); 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_get_current_url_host(r, c->x_forwarded_headers); if (strchr(uri.hostname, ':')) { /* v6 literal */ url_ipv6_aware = apr_pstrcat(r->pool, "[", uri.hostname, "]", NULL); } else { url_ipv6_aware = uri.hostname; } if ((strstr(c_host, url_ipv6_aware) == NULL) || (strstr(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) && (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) && (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) && (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 (((strstr(url, "\n") != NULL) || 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 ((strstr(url, "/%09") != NULL) || (oidc_util_strcasestr(url, "/%2f") != NULL) || (strstr(url, "/\t") != NULL) || (strstr(url, "/%68") != NULL) || (oidc_util_strcasestr(url, "/http:") != NULL) || (oidc_util_strcasestr(url, "/https:") != NULL) || (oidc_util_strcasestr(url, "/javascript:") != NULL) || (strstr(url, "/〱") != NULL) || (strstr(url, "/〵") != NULL) || (strstr(url, "/ゝ") != NULL) || (strstr(url, "/ー") != NULL) || (strstr(url, "/ー") != NULL) || (strstr(url, "/<") != NULL) || (oidc_util_strcasestr(url, "%01javascript:") != NULL) || (strstr(url, "/%5c") != NULL) || (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; } /* * handle a response from an IDP discovery page and/or handle 3rd-party initiated SSO */ static int oidc_handle_discovery_response(request_rec *r, oidc_cfg *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_get_request_parameter(r, OIDC_DISC_OP_PARAM, &issuer); oidc_util_get_request_parameter(r, OIDC_DISC_USER_PARAM, &user); oidc_util_get_request_parameter(r, OIDC_DISC_RT_PARAM, &target_link_uri); oidc_util_get_request_parameter(r, OIDC_DISC_LH_PARAM, &login_hint); oidc_util_get_request_parameter(r, OIDC_DISC_SC_PARAM, &path_scopes); oidc_util_get_request_parameter(r, OIDC_DISC_AR_PARAM, &auth_request_params); oidc_util_get_request_parameter(r, OIDC_CSRF_NAME, &csrf_query); csrf_cookie = oidc_util_get_cookie(r, OIDC_CSRF_NAME); /* do CSRF protection if not 3rd party initiated SSO */ if (csrf_cookie) { /* clean CSRF cookie */ oidc_util_set_cookie(r, OIDC_CSRF_NAME, "", 0, OIDC_COOKIE_EXT_SAME_SITE_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 (c->default_sso_url == NULL) { return oidc_util_html_send_error(r, c->error_template, "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_get_absolute_url(r, c, c->default_sso_url)); } /* do open redirect prevention, step 1 */ if (oidc_target_link_uri_matches_configuration(r, c, target_link_uri) == FALSE) { return oidc_util_html_send_error(r, c->error_template, "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, c->error_template, error_str, error_description, HTTP_UNAUTHORIZED); } /* see if this is a static setup */ if (c->metadata_dir == NULL) { if ((oidc_provider_static_config(r, c, &provider) == TRUE) && (issuer != NULL)) { if (_oidc_strcmp(provider->issuer, issuer) != 0) { return oidc_util_html_send_error( r, c->error_template, "Invalid Request", apr_psprintf( r->pool, "The \"iss\" value must match the configured providers' one (%s != %s).", issuer, c->provider.issuer), HTTP_INTERNAL_SERVER_ERROR); } } return oidc_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 (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_url_based_discovery(r, c, user, &issuer) == FALSE) { /* something did not work out, show a user facing error */ return oidc_util_html_send_error(r, c->error_template, "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 (strstr(issuer, OIDC_STR_AT) != NULL) { if (login_hint == NULL) { login_hint = apr_pstrdup(r->pool, issuer); // char *p = strstr(issuer, OIDC_STR_AT); //*p = '\0'; } /* got an account name as input, perform OP discovery with that */ if (oidc_proto_account_based_discovery(r, c, issuer, &issuer) == FALSE) { /* something did not work out, show a user facing error */ return oidc_util_html_send_error(r, c->error_template, "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, &provider->jwks_uri, provider->ssl_validate_server, &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_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, c->error_template, "Invalid Request", "Could not find valid provider metadata for the selected OpenID Connect " "provider; contact the administrator", HTTP_NOT_FOUND); } static apr_uint32_t oidc_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_is_front_channel_logout(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_is_back_channel_logout(const char *logout_param_value) { return ((logout_param_value != NULL) && (_oidc_strcmp(logout_param_value, OIDC_BACKCHANNEL_STYLE_LOGOUT_PARAM_VALUE) == 0)); } #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_revoke_tokens(request_rec *r, oidc_cfg *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", provider->revocation_endpoint_url ? provider->revocation_endpoint_url : "(null)"); if ((provider->revocation_endpoint_url == NULL) || (_oidc_strcmp(provider->revocation_endpoint_url, "") == 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, provider->token_endpoint_auth, provider->client_id, provider->client_secret, provider->client_keys, provider->token_endpoint_url, params, NULL, &basic_auth, &bearer_auth) == FALSE) goto out; // TODO: use oauth.ssl_validate_server ... 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_util_http_post_form(r, provider->revocation_endpoint_url, params, basic_auth, bearer_auth, c->provider.ssl_validate_server, &response, &c->http_timeout_long, &c->outgoing_proxy, oidc_dir_cfg_pass_cookies(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_util_http_post_form(r, provider->revocation_endpoint_url, params, basic_auth, bearer_auth, c->provider.ssl_validate_server, &response, &c->http_timeout_long, &c->outgoing_proxy, oidc_dir_cfg_pass_cookies(r), NULL, NULL, NULL) == FALSE) { oidc_warn(r, "revoking access token failed"); } } out: oidc_debug(r, "leave"); } static apr_byte_t oidc_cleanup_by_sid(request_rec *r, char *sid, oidc_cfg *cfg, oidc_provider_t *provider) { char *uuid = NULL; oidc_session_t session; oidc_debug(r, "enter (sid=%s,iss=%s)", sid, provider->issuer); // 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_make_sid_iss_unique(r, sid, provider->issuer); 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 (cfg->session_type != OIDC_SESSION_TYPE_CLIENT_COOKIE) { if (oidc_session_load_cache_by_uuid(r, cfg, uuid, &session) != FALSE) if (oidc_session_extract(r, &session) != FALSE) oidc_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; } /* * handle a local logout */ static int oidc_handle_logout_request(request_rec *r, oidc_cfg *c, oidc_session_t *session, const char *url) { 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; oidc_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_is_front_channel_logout(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_get_request_parameter(r, OIDC_REDIRECT_URI_REQUEST_SID, &sid) != FALSE) { if (oidc_util_get_request_parameter(r, OIDC_REDIRECT_URI_REQUEST_ISS, &iss) != FALSE) { provider = oidc_get_provider_for_issuer(r, c, iss, FALSE); } else { /* * 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_cleanup_by_sid(r, sid, c, provider); } else { oidc_info(r, "No provider for front channel logout found"); } } } /* set recommended cache control headers */ oidc_util_hdr_err_out_add(r, OIDC_HTTP_HDR_CACHE_CONTROL, "no-cache, no-store"); oidc_util_hdr_err_out_add(r, OIDC_HTTP_HDR_PRAGMA, "no-cache"); oidc_util_hdr_err_out_add(r, OIDC_HTTP_HDR_P3P, "CAO PSA OUR"); oidc_util_hdr_err_out_add(r, OIDC_HTTP_HDR_EXPIRES, "0"); oidc_util_hdr_err_out_add(r, OIDC_HTTP_HDR_X_FRAME_OPTIONS, c->logout_x_frame_options ? c->logout_x_frame_options : "DENY"); /* see if this is PF-PA style logout in which case we return a transparent pixel */ const char *accept = oidc_util_hdr_in_accept_get(r); if ((_oidc_strcmp(url, OIDC_IMG_STYLE_LOGOUT_PARAM_VALUE) == 0) || ((accept) && strstr(accept, OIDC_CONTENT_TYPE_IMAGE_PNG))) { return oidc_util_http_send(r, (const char *)&oidc_transparent_pixel, sizeof(oidc_transparent_pixel), OIDC_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_util_hdr_err_out_add(r, OIDC_HTTP_HDR_CACHE_CONTROL, "no-cache, no-store"); oidc_util_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_util_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_handle_logout_backchannel(request_rec *r, oidc_cfg *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, cfg->private_keys, 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 ((provider->id_token_signed_response_alg != NULL) && (_oidc_strcmp(provider->id_token_signed_response_alg, jwt->header.alg) != 0)) { oidc_error(r, "logout token is signed using wrong algorithm: %s != %s", jwt->header.alg, provider->id_token_signed_response_alg); goto out; } // TODO: destroy the JWK used for decryption jwk = NULL; if (oidc_util_create_symmetric_key(r, provider->client_secret, 0, NULL, TRUE, &jwk) == FALSE) return FALSE; if (oidc_proto_jwt_verify(r, cfg, jwt, &provider->jwks_uri, provider->ssl_validate_server, oidc_util_merge_symmetric_key(r->pool, provider->verify_public_keys, jwk), provider->id_token_signed_response_alg) == FALSE) { oidc_error(r, "id_token signature could not be validated, aborting"); goto out; } if (oidc_proto_validate_jwt(r, jwt, provider->validate_issuer ? provider->issuer : NULL, FALSE, FALSE, provider->idtoken_iat_slack) == FALSE) goto out; /* verify the "aud" and "azp" values */ if (oidc_proto_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_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_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(provider->idtoken_iat_slack * 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_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_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; } oidc_cleanup_by_sid(r, sid, cfg, provider); rc = OK; out: if (jwk != NULL) { oidc_jwk_destroy(jwk); jwk = NULL; } if (jwt != NULL) { oidc_jwt_destroy(jwt); jwt = NULL; } oidc_util_hdr_err_out_add(r, OIDC_HTTP_HDR_CACHE_CONTROL, "no-cache, no-store"); oidc_util_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_handle_logout(request_rec *r, oidc_cfg *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; int error_code = OIDC_REFRESH_ERROR_NONE; oidc_util_get_request_parameter(r, OIDC_REDIRECT_URI_REQUEST_LOGOUT, &url); oidc_debug(r, "enter (url=%s)", url); if (oidc_is_front_channel_logout(url)) { return oidc_handle_logout_request(r, c, session, url); } else if (oidc_is_back_channel_logout(url)) { return oidc_handle_logout_backchannel(r, c); } if ((url == NULL) || (_oidc_strcmp(url, "") == 0)) { url = apr_pstrdup(r->pool, oidc_get_absolute_url(r, c, c->default_slo_url)); } 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, c->error_template, error_str, error_description, HTTP_BAD_REQUEST); } } oidc_get_provider_from_session(r, c, session, &provider); if ((provider != NULL) && (provider->end_session_endpoint != NULL)) { if (apr_table_get(r->subprocess_env, OIDC_REFRESH_TOKENS_BEFORE_LOGOUT_ENVVAR) != NULL) { oidc_refresh_token_grant(r, c, session, provider, NULL, &id_token_hint, &error_code); } else { id_token_hint = (char *)oidc_session_get_idtoken(r, session); } s_logout_request = apr_pstrdup(r->pool, provider->end_session_endpoint); 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_util_escape_string(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_util_escape_string(r, url)); } if (provider->logout_request_params != 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, provider->logout_request_params); } // 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_handle_logout_request(r, c, session, url); } /* * handle request for JWKs */ int oidc_handle_jwks(request_rec *r, oidc_cfg *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; c->public_keys && i < c->public_keys->nelts; i++) { jwk = APR_ARRAY_IDX(c->public_keys, 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_CONTENT_TYPE_JSON, OK); } static int oidc_handle_session_management_iframe_op(request_rec *r, oidc_cfg *c, oidc_session_t *session, const char *check_session_iframe) { oidc_debug(r, "enter"); oidc_util_hdr_out_location_set(r, check_session_iframe); return HTTP_MOVED_TEMPORARILY; } static int oidc_handle_session_management_iframe_rp(request_rec *r, oidc_cfg *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 = 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_get_request_parameter(r, "poll", &s_poll_interval); int poll_interval = s_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_get_request_parameter(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_get_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 */ static int oidc_handle_session_management(request_rec *r, oidc_cfg *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_get_request_parameter(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_handle_logout_request(r, c, session, oidc_get_absolute_url(r, c, c->default_slo_url)); } 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 (provider->check_session_iframe != NULL) { return oidc_handle_session_management_iframe_op(r, c, session, provider->check_session_iframe); } return HTTP_NOT_FOUND; } /* see if this is a request for the RP iframe */ if (_oidc_strcmp("iframe_rp", cmd) == 0) { if ((provider->client_id != NULL) && (provider->check_session_iframe != NULL)) { return oidc_handle_session_management_iframe_rp(r, c, session, provider->client_id, provider->check_session_iframe); } oidc_debug(r, "iframe_rp command issued but no client (%s) and/or no check_session_iframe (%s) set", provider->client_id, provider->check_session_iframe); 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_authenticate_user( r, c, provider, apr_psprintf(r->pool, "%s?session=iframe_rp", oidc_get_redirect_uri_iss(r, c, provider)), NULL, id_token_hint, "none", oidc_dir_cfg_path_auth_request_params(r), oidc_dir_cfg_path_scope(r)); } /* handle failure in fallthrough */ oidc_error(r, "unknown command: %s", cmd); return HTTP_INTERNAL_SERVER_ERROR; } /* * handle refresh token request */ static int oidc_handle_refresh_token_request(request_rec *r, oidc_cfg *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; int refresh_error_code = OIDC_REFRESH_ERROR_NONE; /* get the command passed to the session management handler */ oidc_util_get_request_parameter(r, OIDC_REDIRECT_URI_REQUEST_REFRESH, &return_to); oidc_util_get_request_parameter(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, &refresh_error_code) == 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, &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 ? return_to : "", OIDC_CHAR_QUERY) ? OIDC_STR_AMP : OIDC_STR_QUERY, oidc_util_escape_string(r, error_code)); /* add the redirect location header */ oidc_util_hdr_out_location_set(r, return_to); return HTTP_MOVED_TEMPORARILY; } /* * handle request object by reference request */ static int oidc_handle_request_uri(request_rec *r, oidc_cfg *c) { char *request_ref = NULL; oidc_util_get_request_parameter(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_CONTENT_TYPE_JWT, OK); } /* * handle a request to invalidate a cached access token introspection result */ int oidc_handle_remove_at_cache(request_rec *r, oidc_cfg *c) { char *access_token = NULL; oidc_util_get_request_parameter(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; } int oidc_handle_revoke_session(request_rec *r, oidc_cfg *c) { apr_byte_t rc = FALSE; char *session_id = NULL; oidc_util_get_request_parameter(r, OIDC_REDIRECT_URI_REQUEST_REVOKE_SESSION, &session_id); if (session_id == NULL) return HTTP_BAD_REQUEST; if (c->session_type == 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; } #define OIDC_INFO_PARAM_ACCESS_TOKEN_REFRESH_INTERVAL "access_token_refresh_interval" #define OIDC_INFO_PARAM_EXTEND_SESSION "extend_session" /* * handle request for session info */ static int oidc_handle_info_request(request_rec *r, oidc_cfg *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; int error_code = OIDC_REFRESH_ERROR_NONE; oidc_util_get_request_parameter(r, OIDC_REDIRECT_URI_REQUEST_INFO, &s_format); oidc_util_get_request_parameter(r, OIDC_INFO_PARAM_ACCESS_TOKEN_REFRESH_INTERVAL, &s_interval); oidc_util_get_request_parameter(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 (c->info_hook_data == 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)) { apr_time_t t_interval; if (sscanf(s_interval, "%" APR_TIME_T_FMT, &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, &error_code) == 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(c->info_hook_data, 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_refresh_claims_from_userinfo_endpoint(r, c, session, &needs_save, &error_code) == FALSE) { rc = HTTP_INTERNAL_SERVER_ERROR; goto end; } } /* include the access token in the session info */ if (apr_hash_get(c->info_hook_data, 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)); } /* include the access token expiry timestamp in the session info */ if (apr_hash_get(c->info_hook_data, OIDC_HOOK_INFO_ACCES_TOKEN_EXP, APR_HASH_KEY_STRING)) { const char *access_token_expires = oidc_session_get_access_token_expires(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(c->info_hook_data, 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(c->info_hook_data, 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(c->info_hook_data, 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(c->info_hook_data, 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(c->info_hook_data, 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(c->info_hook_data, 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(c->info_hook_data, 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(c->info_hook_data, 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 (b_extend_session) if (oidc_session_pass_tokens(r, c, 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_object(r, json, JSON_PRESERVE_ORDER); /* return the stringified JSON result */ rc = oidc_util_http_send(r, r_value, _oidc_strlen(r_value), OIDC_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_object(r, 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; } /* * handle all requests to the redirect_uri */ int oidc_handle_redirect_uri_request(request_rec *r, oidc_cfg *c, oidc_session_t *session) { apr_byte_t needs_save = FALSE; int rc = OK; OIDC_METRICS_TIMING_START(r, c); if (oidc_proto_is_redirect_authorization_response(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_handle_redirect_authorization_response(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_handle_logout(r, c, session); return rc; } else if (oidc_proto_is_post_authorization_response(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_handle_post_authorization_response(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_handle_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_handle_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_handle_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_handle_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_handle_remove_at_cache(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_handle_revoke_session(r, c); return rc; } 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); // need to establish user/claims for authorization purposes rc = oidc_handle_existing_session(r, c, session, &needs_save); // retain this session across the authentication hand 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_proto_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_handle_redirect_authorization_response(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, c->error_template, "Invalid Request", apr_psprintf(r->pool, "The OpenID Connect callback URL received an invalid request"), HTTP_INTERNAL_SERVER_ERROR); } #define OIDC_AUTH_TYPE_OPENID_CONNECT "openid-connect" #define OIDC_AUTH_TYPE_OPENID_OAUTH20 "oauth20" #define OIDC_AUTH_TYPE_OPENID_BOTH "auth-openidc" /* * main routine: handle OpenID Connect authentication */ static int oidc_check_userid_openidc(request_rec *r, oidc_cfg *c) { OIDC_METRICS_TIMING_START(r, c); if (oidc_get_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_get_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, &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 *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 *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 *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; } /* * 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 #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_handle_unauthorized_user24(request_rec *r) { char *html_head = NULL; oidc_debug(r, "enter"); oidc_cfg *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_dir_cfg_unautz_action(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, c->error_template, "Authorization Error", oidc_dir_cfg_unauthz_arg(r), HTTP_UNAUTHORIZED); if (c->error_template) r->header_only = 1; return AUTHZ_DENIED; case OIDC_UNAUTZ_RETURN302: OIDC_METRICS_COUNTER_INC(r, c, OM_AUTHZ_ACTION_302); html_head = apr_psprintf(r->pool, "", oidc_dir_cfg_unauthz_arg(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 */ 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_authenticate_user(r, c, NULL, oidc_get_current_url(r, c->x_forwarded_headers), NULL, NULL, NULL, oidc_dir_cfg_path_auth_request_params(r), oidc_dir_cfg_path_scope(r)); const char *location = oidc_util_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); 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_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_dir_cfg_unauth_action(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_worker24(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_handle_unauthorized_user24(r); return rc; } authz_status oidc_authz_checker_claim(request_rec *r, const char *require_args, const void *parsed_require_args) { return oidc_authz_checker(r, require_args, parsed_require_args, oidc_authz_match_claim); } #ifdef USE_LIBJQ authz_status oidc_authz_checker_claims_expr(request_rec *r, const char *require_args, const void *parsed_require_args) { return oidc_authz_checker(r, require_args, parsed_require_args, oidc_authz_match_claims_expr); } #endif #else /* * find out which action we need to take when encountering an unauthorized request */ static int oidc_handle_unauthorized_user22(request_rec *r) { oidc_cfg *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_oauth_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_dir_cfg_unautz_action(r)) { case OIDC_UNAUTZ_RETURN403: OIDC_METRICS_COUNTER_INC(r, c, OM_AUTHZ_ACTION_403); if (oidc_dir_cfg_unauthz_arg(r)) oidc_util_html_send(r, "Authorization Error", NULL, NULL, oidc_dir_cfg_unauthz_arg(r), HTTP_FORBIDDEN); return HTTP_FORBIDDEN; case OIDC_UNAUTZ_RETURN401: OIDC_METRICS_COUNTER_INC(r, c, OM_AUTHZ_ACTION_401); if (oidc_dir_cfg_unauthz_arg(r)) oidc_util_html_send(r, "Authorization Error", NULL, NULL, oidc_dir_cfg_unauthz_arg(r), HTTP_UNAUTHORIZED); return HTTP_UNAUTHORIZED; case OIDC_UNAUTZ_RETURN302: OIDC_METRICS_COUNTER_INC(r, c, OM_AUTHZ_ACTION_302); oidc_util_hdr_out_location_set(r, oidc_dir_cfg_unauthz_arg(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_authenticate_user(r, c, NULL, oidc_get_current_url(r, c->x_forwarded_headers), NULL, NULL, NULL, oidc_dir_cfg_path_auth_request_params(r), oidc_dir_cfg_path_scope(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_auth_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_dir_cfg_unauth_action(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_worker22(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_handle_unauthorized_user22(r); return rc; } #endif 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; } /* * handle content generating requests */ int oidc_content_handler(request_rec *r) { oidc_cfg *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) && (c->metrics_path != NULL)) if (_oidc_strcmp(r->parsed_uri.path, c->metrics_path) == 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_get_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_handle_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_JWKS)) { OIDC_METRICS_COUNTER_INC(r, c, OM_CONTENT_REQUEST_JWKS); /* handle JWKs request */ rc = oidc_handle_jwks(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(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; } extern const command_rec oidc_config_cmds[]; // clang-format off module AP_MODULE_DECLARE_DATA auth_openidc_module = { STANDARD20_MODULE_STUFF, oidc_create_dir_config, oidc_merge_dir_config, oidc_create_server_config, oidc_merge_server_config, oidc_config_cmds, oidc_register_hooks }; // clang-format on mod_auth_openidc-2.4.15.1/src/mod_auth_openidc.h000066400000000000000000001523511455620533500215230ustar00rootroot00000000000000/* * 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-2024 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 "const.h" // clang-format off #include #include #include #include #include #include #include // clang-format on #include #include #include #include #include "cache/cache.h" #include "jose.h" #include "parse.h" #ifdef APLOG_USE_MODULE APLOG_USE_MODULE(auth_openidc); #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_log(r, level, fmt, ...) fprintf(stderr, "# %s: %s\n", __FUNCTION__, apr_psprintf(r->pool, fmt, // ##__VA_ARGS__)) #define oidc_slog(s, level, fmt, ...) fprintf(stderr, "## %s: %s\n", __FUNCTION__, // apr_psprintf(s->process->pool, 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 /* 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 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 the original method in the discovery response */ #define OIDC_DISC_RM_PARAM "method" /* 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" /* 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 /* nonce bytes length */ #define OIDC_PROTO_NONCE_LENGTH 32 /* code verifier length */ #define OIDC_PROTO_CODE_VERIFIER_LENGTH 32 /* pass id_token as individual claims in headers (default) */ #define OIDC_PASS_IDTOKEN_AS_CLAIMS 1 /* pass id_token payload as JSON object in header */ #define OIDC_PASS_IDTOKEN_AS_PAYLOAD 2 /* pass id_token in compact serialized format in header */ #define OIDC_PASS_IDTOKEN_AS_SERIALIZED 4 /* pass userinfo as individual claims in headers (default) */ #define OIDC_PASS_USERINFO_AS_CLAIMS 1 /* pass userinfo payload as JSON object in header */ #define OIDC_PASS_USERINFO_AS_JSON_OBJECT 2 /* pass userinfo as a JWT in header (when returned as a JWT) */ #define OIDC_PASS_USERINFO_AS_JWT 3 /* pass as re-signed JWT including id_token claims */ #define OIDC_PASS_USERINFO_AS_SIGNED_JWT 4 #define OIDC_PASS_APP_INFO_AS_NONE 0 #define OIDC_PASS_APP_INFO_AS_BASE64URL 1 #define OIDC_PASS_APP_INFO_AS_LATIN1 2 /* actions to be taken on access token / userinfo refresh error */ #define OIDC_ON_ERROR_CONTINUE 0 #define OIDC_ON_ERROR_LOGOUT 1 #define OIDC_ON_ERROR_AUTHENTICATE 2 #define OIDC_OAUTH_ACCEPT_TOKEN_IN_DEFAULT 0 /* accept bearer token in header (default) */ #define OIDC_OAUTH_ACCEPT_TOKEN_IN_HEADER 1 /* accept bearer token as a post parameter */ #define OIDC_OAUTH_ACCEPT_TOKEN_IN_POST 2 /* accept bearer token as a query parameter */ #define OIDC_OAUTH_ACCEPT_TOKEN_IN_QUERY 4 /* accept bearer token as a cookie parameter (PingAccess) */ #define OIDC_OAUTH_ACCEPT_TOKEN_IN_COOKIE 8 /* accept bearer token as basic auth password (non-oauth clients) */ #define OIDC_OAUTH_ACCEPT_TOKEN_IN_BASIC 16 /* the hash key of the cookie name value in the list of options */ #define OIDC_OAUTH_ACCEPT_TOKEN_IN_OPTION_COOKIE_NAME "cookie-name" /* introspection method options */ #define OIDC_INTROSPECTION_METHOD_GET "GET" #define OIDC_INTROSPECTION_METHOD_POST "POST" /* HTTP methods to send authentication requests */ #define OIDC_AUTH_REQUEST_METHOD_GET 0 #define OIDC_AUTH_REQUEST_METHOD_POST 1 /* 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 /* input filter hook name */ #define OIDC_UTIL_HTTP_SENDSTRING "OIDC_UTIL_HTTP_SENDSTRING" /* 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 /* 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" /* define the name of the cookie/parameter for CSRF protection */ #define OIDC_CSRF_NAME "x_csrf" /* http methods */ #define OIDC_METHOD_GET "get" #define OIDC_METHOD_FORM_POST "form_post" /* the maximum size of data that we accept in a single POST value: 1MB */ #define OIDC_MAX_POST_DATA_LEN 1024 * 1024 #define OIDC_UNAUTH_AUTHENTICATE 1 #define OIDC_UNAUTH_PASS 2 #define OIDC_UNAUTH_RETURN401 3 #define OIDC_UNAUTH_RETURN410 4 #define OIDC_UNAUTH_RETURN407 5 #define OIDC_UNAUTZ_RETURN403 1 #define OIDC_UNAUTZ_RETURN401 2 #define OIDC_UNAUTZ_AUTHENTICATE 3 #define OIDC_UNAUTZ_RETURN302 4 #define OIDC_USER_INFO_TOKEN_METHOD_HEADER 0 #define OIDC_USER_INFO_TOKEN_METHOD_POST 1 #define OIDC_COOKIE_EXT_SAME_SITE_LAX "SameSite=Lax" #define OIDC_COOKIE_EXT_SAME_SITE_STRICT "SameSite=Strict" #define OIDC_COOKIE_EXT_SAME_SITE_NONE(c, r) oidc_util_request_is_secure(r, c) ? "SameSite=None" : NULL #define OIDC_COOKIE_SAMESITE_STRICT(c, r) \ c->cookie_same_site ? OIDC_COOKIE_EXT_SAME_SITE_STRICT : OIDC_COOKIE_EXT_SAME_SITE_NONE(c, r) #define OIDC_COOKIE_SAMESITE_LAX(c, r) \ c->cookie_same_site ? OIDC_COOKIE_EXT_SAME_SITE_LAX : OIDC_COOKIE_EXT_SAME_SITE_NONE(c, r) #define OIDC_ERROR_ENVVAR "OIDC_ERROR" #define OIDC_ERROR_DESC_ENVVAR "OIDC_ERROR_DESC" /* https://tools.ietf.org/html/draft-ietf-tokbind-ttrp-01 */ #define OIDC_TB_CFG_PROVIDED_ENV_VAR "Sec-Provided-Token-Binding-ID" /* https://www.ietf.org/id/draft-ietf-oauth-mtls-12 */ #define OIDC_TB_CFG_FINGERPRINT_ENV_VAR "TB_SSL_CLIENT_CERT_FINGERPRINT" #define OIDC_STATE_INPUT_HEADERS_USER_AGENT 1 #define OIDC_STATE_INPUT_HEADERS_X_FORWARDED_FOR 2 #define OIDC_HDR_X_FORWARDED_HOST 1 #define OIDC_HDR_X_FORWARDED_PORT 2 #define OIDC_HDR_X_FORWARDED_PROTO 4 #define OIDC_HDR_FORWARDED 8 #define OIDC_TRACE_PARENT_OFF 0 #define OIDC_TRACE_PARENT_PROPAGATE 1 #define OIDC_TRACE_PARENT_GENERATE 2 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; extern oidc_proto_pkce_t oidc_pkce_plain; extern oidc_proto_pkce_t oidc_pkce_s256; typedef struct oidc_jwks_uri_t { char *uri; int refresh_interval; char *signed_uri; oidc_jwk_t *jwk; } oidc_jwks_uri_t; typedef 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 *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; 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; char *userinfo_signed_response_alg; char *userinfo_encrypted_response_alg; char *userinfo_encrypted_response_enc; int userinfo_token_method; char *request_object; int auth_request_method; int issuer_specific_redirect_uri; } oidc_provider_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 struct oidc_apr_expr_t { #if HAVE_APACHE_24 ap_expr_info_t *expr; #endif char *str; } oidc_apr_expr_t; typedef struct oidc_oauth_t { int ssl_validate_server; char *client_id; char *client_secret; char *metadata_url; char *introspection_endpoint_tls_client_key; char *introspection_endpoint_tls_client_key_pwd; char *introspection_endpoint_tls_client_cert; char *introspection_endpoint_url; char *introspection_endpoint_method; char *introspection_endpoint_params; char *introspection_endpoint_auth; char *introspection_client_auth_bearer_token; char *introspection_token_param_name; char *introspection_token_expiry_claim_name; char *introspection_token_expiry_claim_format; int introspection_token_expiry_claim_required; oidc_remote_user_claim_t remote_user_claim; apr_hash_t *verify_shared_keys; char *verify_jwks_uri; apr_array_header_t *verify_public_keys; } oidc_oauth_t; typedef struct oidc_outgoing_proxy_t { const char *host_port; const char *username_password; unsigned long auth_type; } oidc_outgoing_proxy_t; typedef struct oidc_http_timeout_t { int request_timeout; int connect_timeout; int retries; apr_time_t retry_interval; } oidc_http_timeout_t; typedef struct oidc_crypto_passphrase_t { char *secret1; char *secret2; } oidc_crypto_passphrase_t; typedef struct oidc_cfg { /* indicates whether this is a derived config, merged from a base one */ unsigned int merged; /* HTML to display error messages+description */ char *error_template; /* Javascript template to preserve POST data */ char *post_preserve_template; /* Javascript template to restore POST data */ char *post_restore_template; /* the redirect URI as configured with the OpenID Connect OP's that we talk to */ char *redirect_uri; /* (optional) default URL for 3rd-party initiated SSO */ char *default_sso_url; /* (optional) default URL to go to after logout */ char *default_slo_url; /* 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; /* 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; /* directory that holds the provider & client metadata files */ char *metadata_dir; /* type of session management/storage */ int session_type; /* session cookie or persistent cookie */ int persistent_session_cookie; /* store the id_token in the session */ apr_byte_t store_id_token; /* session cookie chunk size */ int session_cookie_chunk_size; /* pointer to cache functions */ oidc_cache_t *cache; void *cache_cfg; /* cache_type = file: directory that holds the cache files (if not set, we'll try and use an OS defined one like * "/tmp" */ char *cache_file_dir; /* cache_type = file: clean interval */ int cache_file_clean_interval; #ifdef USE_MEMCACHE /* cache_type= memcache: list of memcache host/port servers to use */ char *cache_memcache_servers; /* cache_type= memcache: minimum number of connections to each memcache server per process*/ apr_uint32_t cache_memcache_min; /* cache_type= memcache: soft maximum number of connections to each memcache server per process */ apr_uint32_t cache_memcache_smax; /* cache_type= memcache: hard maximum number of connections to each memcache server per process */ apr_uint32_t cache_memcache_hmax; /* cache_type= memcache: maximum time in microseconds a connection to a memcache server can be idle before being * closed */ apr_uint32_t cache_memcache_ttl; #endif /* cache_type = shm: size of the shared memory segment (cq. max number of cached entries) */ int cache_shm_size_max; /* cache_type = shm: maximum size in bytes of a cache entry */ int cache_shm_entry_size_max; #ifdef USE_LIBHIREDIS /* cache_type= redis: Redis host/port server to use */ char *cache_redis_server; char *cache_redis_username; char *cache_redis_password; int cache_redis_database; int cache_redis_connect_timeout; int cache_redis_timeout; #endif int cache_encrypt; oidc_http_timeout_t http_timeout_long; oidc_http_timeout_t http_timeout_short; int state_timeout; int max_number_of_state_cookies; int delete_oldest_state_cookies; int session_inactivity_timeout; int session_cache_fallback_to_cookie; char *cookie_domain; char *claim_delimiter; char *claim_prefix; oidc_remote_user_claim_t remote_user_claim; int cookie_http_only; int cookie_same_site; oidc_outgoing_proxy_t outgoing_proxy; oidc_crypto_passphrase_t crypto_passphrase; int provider_metadata_refresh_interval; apr_hash_t *info_hook_data; apr_hash_t *metrics_hook_data; char *metrics_path; int trace_parent; apr_hash_t *black_listed_claims; apr_hash_t *white_listed_claims; oidc_apr_expr_t *filter_claims_expr; apr_byte_t state_input_headers; apr_hash_t *redirect_urls_allowed; char *ca_bundle_path; char *logout_x_frame_options; apr_byte_t x_forwarded_headers; int action_on_userinfo_error; oidc_cache_mutex_t *refresh_mutex; } oidc_cfg; void oidc_pre_config_init(); int oidc_fixups(request_rec *r); int oidc_check_user_id(request_rec *r); #if HAVE_APACHE_24 authz_status oidc_authz_checker_claim(request_rec *r, const char *require_args, const void *parsed_require_args); #ifdef USE_LIBJQ authz_status oidc_authz_checker_claims_expr(request_rec *r, const char *require_args, const void *parsed_require_args); #endif #else int oidc_auth_checker(request_rec *r); #endif 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); int oidc_handle_jwks(request_rec *r, oidc_cfg *c); int oidc_handle_remove_at_cache(request_rec *r, oidc_cfg *c); apr_byte_t oidc_post_preserve_javascript(request_rec *r, const char *location, char **javascript, char **javascript_method); void oidc_scrub_headers(request_rec *r); void oidc_strip_cookies(request_rec *r); int oidc_content_handler(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); #define OIDC_REDIRECT_URI_REQUEST_INFO "info" #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" // oidc_oauth int oidc_oauth_check_userid(request_rec *r, oidc_cfg *c, const char *access_token); apr_byte_t oidc_oauth_get_bearer_token(request_rec *r, const char **access_token); // oidc_proto.c #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_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_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_JWK_KEYS "keys" #define OIDC_HOOK_INFO_FORMAT_JSON "json" #define OIDC_HOOK_INFO_FORMAT_HTML "html" #define OIDC_HOOK_INFO_TIMESTAMP "iat" #define OIDC_HOOK_INFO_ACCES_TOKEN "access_token" #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_CONTENT_TYPE_JSON "application/json" #define OIDC_CONTENT_TYPE_JWT "application/jwt" #define OIDC_CONTENT_TYPE_FORM_ENCODED "application/x-www-form-urlencoded" #define OIDC_CONTENT_TYPE_IMAGE_PNG "image/png" #define OIDC_CONTENT_TYPE_TEXT_HTML "text/html" #define OIDC_CONTENT_TYPE_APP_XHTML_XML "application/xhtml+xml" #define OIDC_CONTENT_TYPE_ANY "*/*" #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 "#" #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_APP_INFO_REFRESH_TOKEN "refresh_token" #define OIDC_APP_INFO_ACCESS_TOKEN "access_token" #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" typedef json_t oidc_proto_state_t; 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 *c, const char *cookieValue); char *oidc_proto_state_to_cookie(request_rec *r, oidc_cfg *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); const char *oidc_proto_state_get_pkce_state(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_pkce_state(oidc_proto_state_t *proto_state, const char *pkce_state); void oidc_proto_state_set_timestamp_now(oidc_proto_state_t *proto_state); apr_byte_t oidc_proto_token_endpoint_auth(request_rec *r, oidc_cfg *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); char *oidc_proto_peek_jwt_header(request_rec *r, const char *jwt, char **alg, char **enc, char **kid); int oidc_proto_authorization_request(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); apr_byte_t oidc_proto_is_post_authorization_response(request_rec *r, oidc_cfg *cfg); apr_byte_t oidc_proto_is_redirect_authorization_response(request_rec *r, oidc_cfg *cfg); apr_byte_t oidc_proto_refresh_request(request_rec *r, oidc_cfg *cfg, oidc_provider_t *provider, const char *rtoken, char **id_token, char **access_token, char **token_type, int *expires_in, char **refresh_token); apr_byte_t oidc_proto_resolve_userinfo(request_rec *r, oidc_cfg *cfg, oidc_provider_t *provider, const char *id_token_sub, const char *access_token, char **response, char **userinfo_jwt); apr_byte_t oidc_proto_account_based_discovery(request_rec *r, oidc_cfg *cfg, const char *acct, char **issuer); apr_byte_t oidc_proto_url_based_discovery(request_rec *r, oidc_cfg *cfg, const char *url, char **issuer); apr_byte_t oidc_proto_parse_idtoken(request_rec *r, oidc_cfg *cfg, oidc_provider_t *provider, const char *id_token, const char *nonce, oidc_jwt_t **jwt, apr_byte_t is_code_flow); int oidc_proto_javascript_implicit(request_rec *r, oidc_cfg *c); 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); apr_byte_t oidc_proto_validate_authorization_response(request_rec *r, const char *response_type, const char *requested_response_mode, char **code, char **id_token, char **access_token, char **token_type, const char *used_response_mode); apr_byte_t oidc_proto_jwt_verify(request_rec *r, oidc_cfg *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_validate_jwt(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); apr_byte_t oidc_proto_generate_nonce(request_rec *r, char **nonce, int len); apr_byte_t oidc_proto_validate_aud_and_azp(request_rec *r, oidc_cfg *cfg, oidc_provider_t *provider, oidc_jwt_payload_t *id_token_payload); apr_byte_t oidc_proto_authorization_response_code_idtoken_token(request_rec *r, oidc_cfg *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_authorization_response_code_idtoken(request_rec *r, oidc_cfg *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_handle_authorization_response_code_token(request_rec *r, oidc_cfg *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_handle_authorization_response_code(request_rec *r, oidc_cfg *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_handle_authorization_response_idtoken_token(request_rec *r, oidc_cfg *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_handle_authorization_response_idtoken(request_rec *r, oidc_cfg *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_generate_random_string(request_rec *r, char **output, int len); // non-static for test.c apr_byte_t oidc_proto_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_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_validate_nonce(request_rec *r, oidc_cfg *cfg, oidc_provider_t *provider, const char *nonce, oidc_jwt_t *jwt); apr_byte_t oidc_validate_redirect_url(request_rec *r, oidc_cfg *c, const char *redirect_to_url, apr_byte_t restrict_to_host, char **err_str, char **err_desc); // oidc_authz.c 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); #ifdef USE_LIBJQ apr_byte_t oidc_authz_match_claims_expr(request_rec *r, const char *const attr_spec, json_t *claims); #endif #if HAVE_APACHE_24 authz_status oidc_authz_worker24(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_worker22(request_rec *r, json_t *claims, const require_line *const reqs, int nelts); #endif int oidc_oauth_return_www_authenticate(request_rec *r, const char *error, const char *error_description); // oidc_config.c #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 OIDCMemCacheServers "OIDCMemCacheServers" #define OIDCMemCacheConnectionsMin "OIDCMemCacheConnectionsMin" #define OIDCMemCacheConnectionsSMax "OIDCMemCacheConnectionsSMax" #define OIDCMemCacheConnectionsHMax "OIDCMemCacheConnectionsHMax" #define OIDCMemCacheConnectionsTTL "OIDCMemCacheConnectionsTTL" #define OIDCCacheShmMax "OIDCCacheShmMax" #define OIDCCacheShmEntrySizeMax "OIDCCacheShmEntrySizeMax" #define OIDCRedisCacheServer "OIDCRedisCacheServer" #define OIDCCookiePath "OIDCCookiePath" #define OIDCInfoHook "OIDCInfoHook" #define OIDCMetricsData "OIDCMetricsData" #define OIDCMetricsPublish "OIDCMetricsPublish" #define OIDCWhiteListedClaims "OIDCWhiteListedClaims" #define OIDCCryptoPassphrase "OIDCCryptoPassphrase" void *oidc_create_server_config(apr_pool_t *pool, server_rec *svr); void *oidc_merge_server_config(apr_pool_t *pool, void *BASE, void *ADD); void *oidc_create_dir_config(apr_pool_t *pool, char *path); void *oidc_merge_dir_config(apr_pool_t *pool, void *BASE, void *ADD); void oidc_register_hooks(apr_pool_t *pool); char *oidc_cfg_dir_discover_url(request_rec *r); char *oidc_cfg_dir_cookie(request_rec *r); char *oidc_cfg_dir_cookie_path(request_rec *r); char *oidc_cfg_dir_authn_header(request_rec *r); apr_byte_t oidc_cfg_dir_pass_info_in_headers(request_rec *r); apr_byte_t oidc_cfg_dir_pass_info_in_envvars(request_rec *r); int oidc_cfg_dir_pass_info_encoding(request_rec *r); apr_byte_t oidc_cfg_dir_pass_refresh_token(request_rec *r); apr_byte_t oidc_cfg_dir_pass_access_token(request_rec *r); apr_byte_t oidc_cfg_dir_accept_token_in(request_rec *r); char *oidc_cfg_dir_accept_token_in_option(request_rec *r, const char *key); int oidc_cfg_token_introspection_interval(request_rec *r); int oidc_cfg_dir_preserve_post(request_rec *r); apr_array_header_t *oidc_dir_cfg_pass_cookies(request_rec *r); apr_array_header_t *oidc_dir_cfg_strip_cookies(request_rec *r); int oidc_dir_cfg_unauth_action(request_rec *r); apr_byte_t oidc_dir_cfg_unauth_expr_is_set(request_rec *r); int oidc_dir_cfg_unautz_action(request_rec *r); char *oidc_dir_cfg_unauthz_arg(request_rec *r); const char *oidc_dir_cfg_path_auth_request_params(request_rec *r); apr_array_header_t *oidc_dir_cfg_pass_user_info_as(request_rec *r); int oidc_dir_cfg_pass_id_token_as(request_rec *r); const char *oidc_dir_cfg_userinfo_claims_expr(request_rec *r); const char *oidc_dir_cfg_path_scope(request_rec *r); oidc_valid_function_t oidc_cfg_get_valid_endpoint_auth_function(oidc_cfg *cfg); int oidc_cfg_cache_encrypt(request_rec *r); int oidc_cfg_session_cache_fallback_to_cookie(request_rec *r); const char *oidc_parse_pkce_type(apr_pool_t *pool, const char *arg, oidc_proto_pkce_t **type); const char *oidc_cfg_claim_prefix(request_rec *r); int oidc_cfg_max_number_of_state_cookies(oidc_cfg *cfg); int oidc_cfg_dir_refresh_access_token_before_expiry(request_rec *r); int oidc_cfg_dir_action_on_error_refresh(request_rec *r); char *oidc_cfg_dir_state_cookie_prefix(request_rec *r); int oidc_cfg_delete_oldest_state_cookies(oidc_cfg *cfg); oidc_provider_t *oidc_cfg_provider_create(apr_pool_t *pool); oidc_provider_t *oidc_cfg_provider_copy(apr_pool_t *pool, const oidc_provider_t *src); void oidc_config_check_x_forwarded(request_rec *r, const apr_byte_t x_forwarded_headers); int oidc_jq_filter_cache_ttl(request_rec *r); // oidc_util.c 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_proto_generate_random_hex_string(request_rec *r, char **hex_str, int byte_len); int oidc_strnenvcmp(const char *a, const char *b, int len); int oidc_base64url_encode(request_rec *r, char **dst, const char *src, int src_len, int remove_padding); int oidc_base64url_decode(apr_pool_t *pool, char **dst, const char *src); const char *oidc_get_current_url_host(request_rec *r, const apr_byte_t x_forwarded_headers); char *oidc_get_current_url(request_rec *r, const apr_byte_t x_forwarded_headers); const char *oidc_get_absolute_url(request_rec *r, oidc_cfg *cfg, const char *url); const char *oidc_get_redirect_uri(request_rec *r, oidc_cfg *c); const char *oidc_get_redirect_uri_iss(request_rec *r, oidc_cfg *c, oidc_provider_t *provider); char *oidc_url_encode(const request_rec *r, const char *str, const char *charsToEncode); char *oidc_normalize_header_name(const request_rec *r, const char *str); apr_byte_t oidc_util_request_is_secure(request_rec *r, const oidc_cfg *c); void oidc_util_set_cookie(request_rec *r, const char *cookieName, const char *cookieValue, apr_time_t expires, const char *ext); char *oidc_util_get_cookie(request_rec *r, const char *cookieName); char *oidc_util_openssl_version(apr_pool_t *pool); apr_byte_t oidc_util_http_get(request_rec *r, const char *url, const apr_table_t *params, const char *basic_auth, const char *bearer_token, int ssl_validate_server, char **response, oidc_http_timeout_t *http_timeout, const oidc_outgoing_proxy_t *outgoing_proxy, apr_array_header_t *pass_cookies, const char *ssl_cert, const char *ssl_key, const char *ssl_key_pwd); apr_byte_t oidc_util_http_post_form(request_rec *r, const char *url, const apr_table_t *params, const char *basic_auth, const char *bearer_token, int ssl_validate_server, char **response, oidc_http_timeout_t *http_timeout, const oidc_outgoing_proxy_t *outgoing_proxy, apr_array_header_t *pass_cookies, const char *ssl_cert, const char *ssl_key, const char *ssl_key_pwd); apr_byte_t oidc_util_http_post_json(request_rec *r, const char *url, json_t *data, const char *basic_auth, const char *bearer_token, int ssl_validate_server, char **response, oidc_http_timeout_t *http_timeout, const oidc_outgoing_proxy_t *outgoing_proxy, 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_matches_url(request_rec *r, const char *url); apr_byte_t oidc_util_request_has_parameter(request_rec *r, const char *param); apr_byte_t oidc_util_get_request_parameter(request_rec *r, char *name, char **value); char *oidc_util_encode_json_object(request_rec *r, json_t *json, size_t flags); apr_byte_t oidc_util_decode_json_object(request_rec *r, const char *str, json_t **json); apr_byte_t oidc_util_decode_json_and_check_error(request_rec *r, const char *str, json_t **json); int oidc_util_http_send(request_rec *r, const char *data, size_t data_len, const char *content_type, int success_rvalue); 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 *oidc_util_escape_string(const request_rec *r, const char *str); char *oidc_util_unescape_string(const request_rec *r, const char *str); 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); 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 *html_template, 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, apr_byte_t as_header, apr_byte_t as_env_var, int pass_as); void oidc_util_set_app_infos(request_rec *r, json_t *j_attrs, const char *claim_prefix, const char *claim_delimiter, apr_byte_t as_header, apr_byte_t as_env_var, int pass_as); 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_json_object_get_string(apr_pool_t *pool, json_t *json, const char *name, char **value, const char *default_value); apr_byte_t oidc_json_object_get_int(const json_t *json, const char *name, int *value, const int default_value); apr_byte_t oidc_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); int oidc_util_cookie_domain_valid(const char *hostname, char *cookie_domain); 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_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); char *oidc_util_get_chunked_cookie(request_rec *r, const char *cookieName, int cookie_chunk_size); void oidc_util_set_chunked_cookie(request_rec *r, const char *cookieName, const char *cookieValue, apr_time_t expires, int chunkSize, const char *ext); 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); apr_hash_t *oidc_util_merge_symmetric_key(apr_pool_t *pool, const apr_array_header_t *keys, oidc_jwk_t *jwk); char *oidc_util_http_query_encoded_url(request_rec *r, const char *url, const apr_table_t *params); char *oidc_util_get_full_path(apr_pool_t *pool, const char *abs_or_rel_filename); apr_byte_t oidc_enabled(request_rec *r); char *oidc_util_http_form_encoded_data(request_rec *r, const apr_table_t *params); 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); 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); void oidc_util_set_trace_parent(request_rec *r, oidc_cfg *c, const char *span); /* 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_VAL_XML_HTTP_REQUEST "XMLHttpRequest" #define OIDC_HTTP_HDR_VAL_NAVIGATE "navigate" #define OIDC_HTTP_HDR_VAL_DOCUMENT "document" void oidc_util_hdr_in_set(const request_rec *r, const char *name, const char *value); const char *oidc_util_hdr_in_cookie_get(const request_rec *r); void oidc_util_hdr_in_cookie_set(const request_rec *r, const char *value); const char *oidc_util_hdr_in_user_agent_get(const request_rec *r); const char *oidc_util_hdr_in_x_forwarded_for_get(const request_rec *r); const char *oidc_util_hdr_in_content_type_get(const request_rec *r); const char *oidc_util_hdr_in_content_length_get(const request_rec *r); const char *oidc_util_hdr_in_x_requested_with_get(const request_rec *r); const char *oidc_util_hdr_in_sec_fetch_mode_get(const request_rec *r); const char *oidc_util_hdr_in_sec_fetch_dest_get(const request_rec *r); const char *oidc_util_hdr_in_accept_get(const request_rec *r); const char *oidc_util_hdr_in_authorization_get(const request_rec *r); const char *oidc_util_hdr_in_x_forwarded_proto_get(const request_rec *r); const char *oidc_util_hdr_in_x_forwarded_port_get(const request_rec *r); const char *oidc_util_hdr_in_x_forwarded_host_get(const request_rec *r); const char *oidc_util_hdr_in_forwarded_get(const request_rec *r); const char *oidc_util_hdr_in_host_get(const request_rec *r); const char *oidc_util_hdr_in_traceparent_get(const request_rec *r); void oidc_util_hdr_out_location_set(const request_rec *r, const char *value); const char *oidc_util_hdr_out_location_get(const request_rec *r); void oidc_util_hdr_err_out_add(const request_rec *r, const char *name, const char *value); apr_byte_t oidc_util_hdr_in_accept_contains(const request_rec *r, const char *needle); apr_byte_t 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); // oidc_metadata.c apr_byte_t oidc_metadata_provider_get(request_rec *r, oidc_cfg *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 *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 *cfg, json_t *j_provider, oidc_provider_t *provider); apr_byte_t oidc_metadata_provider_is_valid(request_rec *r, oidc_cfg *cfg, json_t *j_provider, const char *issuer); apr_byte_t oidc_metadata_list(request_rec *r, oidc_cfg *cfg, apr_array_header_t **arr); apr_byte_t oidc_metadata_get(request_rec *r, oidc_cfg *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 *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 *c, json_t *j_provider); // oidc_session.c 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; 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 *c, const char *uuid, oidc_session_t *z); void oidc_session_id_new(request_rec *r, oidc_session_t *z); int oidc_handle_logout(request_rec *r, oidc_cfg *c, oidc_session_t *session); 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_expires(request_rec *r, oidc_session_t *z, const int expires_in); const char *oidc_session_get_access_token_expires(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); apr_time_t 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_reset_access_token_last_refresh(request_rec *r, oidc_session_t *z); 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); apr_byte_t oidc_is_auth_capable_request(request_rec *r); char *oidc_parse_base64(apr_pool_t *pool, const char *input, char **output, int *output_len); #endif /* MOD_AUTH_OPENIDC_H_ */ mod_auth_openidc-2.4.15.1/src/oauth.c000066400000000000000000000656041455620533500173410ustar00rootroot00000000000000/* * 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-2024 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 "mod_auth_openidc.h" apr_byte_t oidc_oauth_metadata_provider_retrieve(request_rec *r, oidc_cfg *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_util_http_get(r, url, NULL, NULL, NULL, cfg->oauth.ssl_validate_server, response, &cfg->http_timeout_short, &cfg->outgoing_proxy, oidc_dir_cfg_pass_cookies(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; } static apr_byte_t oidc_oauth_provider_config(request_rec *r, oidc_cfg *c) { json_t *j_provider = NULL; char *s_json = NULL; /* see if we should configure a static provider based on external (cached) metadata */ if (c->oauth.metadata_url == NULL) return TRUE; oidc_cache_get_oauth_provider(r, c->oauth.metadata_url, &s_json); if (s_json == NULL) { if (oidc_oauth_metadata_provider_retrieve(r, c, NULL, c->oauth.metadata_url, &j_provider, &s_json) == FALSE) { oidc_error(r, "could not retrieve metadata from url: %s", c->oauth.metadata_url); return FALSE; } oidc_cache_set_oauth_provider(r, c->oauth.metadata_url, s_json, apr_time_now() + (c->provider_metadata_refresh_interval <= 0 ? apr_time_from_sec(OIDC_CACHE_PROVIDER_METADATA_EXPIRY_DEFAULT) : c->provider_metadata_refresh_interval)); } 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", c->oauth.metadata_url); 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 *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, c->oauth.introspection_endpoint_params); /* add the access_token itself */ apr_table_addn(params, c->oauth.introspection_token_param_name, token); const char *bearer_access_token_auth = ((c->oauth.introspection_client_auth_bearer_token != NULL) && _oidc_strcmp(c->oauth.introspection_client_auth_bearer_token, "") == 0) ? token : c->oauth.introspection_client_auth_bearer_token; /* add the token endpoint authentication credentials */ if (oidc_proto_token_endpoint_auth(r, c, c->oauth.introspection_endpoint_auth, c->oauth.client_id, c->oauth.client_secret, NULL, c->oauth.introspection_endpoint_url, 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_strcmp(c->oauth.introspection_endpoint_method, OIDC_INTROSPECTION_METHOD_GET) == 0 ? oidc_util_http_get( r, c->oauth.introspection_endpoint_url, params, basic_auth, bearer_auth, c->oauth.ssl_validate_server, response, &c->http_timeout_long, &c->outgoing_proxy, oidc_dir_cfg_pass_cookies(r), oidc_util_get_full_path(r->pool, c->oauth.introspection_endpoint_tls_client_cert), oidc_util_get_full_path(r->pool, c->oauth.introspection_endpoint_tls_client_key), oidc_util_get_full_path(r->pool, c->oauth.introspection_endpoint_tls_client_key_pwd)) : oidc_util_http_post_form( r, c->oauth.introspection_endpoint_url, params, basic_auth, bearer_auth, c->oauth.ssl_validate_server, response, &c->http_timeout_long, &c->outgoing_proxy, oidc_dir_cfg_pass_cookies(r), oidc_util_get_full_path(r->pool, c->oauth.introspection_endpoint_tls_client_cert), oidc_util_get_full_path(r->pool, c->oauth.introspection_endpoint_tls_client_key), oidc_util_get_full_path(r->pool, c->oauth.introspection_endpoint_tls_client_key_pwd)); } /* * 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 */ apr_byte_t accept_token_in = oidc_cfg_dir_accept_token_in(r); const char *cookie_name = oidc_cfg_dir_accept_token_in_option(r, OIDC_OAUTH_ACCEPT_TOKEN_IN_OPTION_COOKIE_NAME); oidc_debug(r, "accept_token_in=%d", accept_token_in); *access_token = NULL; const apr_byte_t accept_header = (accept_token_in & OIDC_OAUTH_ACCEPT_TOKEN_IN_HEADER) || (accept_token_in == OIDC_OAUTH_ACCEPT_TOKEN_IN_DEFAULT); if ((accept_header) || (accept_token_in & OIDC_OAUTH_ACCEPT_TOKEN_IN_BASIC)) { /* get the authorization header */ const char *auth_line = oidc_util_hdr_in_authorization_get(r); if (auth_line) { oidc_debug(r, "authorization header found"); apr_byte_t known_scheme = 0; /* look for the Bearer keyword */ if ((_oidc_strnatcasecmp(ap_getword(r->pool, &auth_line, OIDC_CHAR_SPACE), OIDC_PROTO_BEARER) == 0) && accept_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 (accept_token_in & OIDC_OAUTH_ACCEPT_TOKEN_IN_BASIC) { char *decoded_line; int decoded_len; if (oidc_parse_base64(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", r->uri); } } } 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_util_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_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 *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" static apr_byte_t oidc_oauth_cache_access_token(request_rec *r, oidc_cfg *c, apr_time_t cache_until, const char *access_token, json_t *json) { /* no cache mode */ int token_introspection_interval = oidc_cfg_token_introspection_interval(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_object(r, 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; } static apr_byte_t oidc_oauth_get_cached_access_token(request_rec *r, oidc_cfg *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_token_introspection_interval(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 *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, c->oauth.introspection_token_expiry_claim_name, _oidc_strcmp(c->oauth.introspection_token_expiry_claim_format, OIDC_CLAIM_FORMAT_ABSOLUTE) == 0, c->oauth.introspection_token_expiry_claim_required, &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_object(r, *token, 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 *c, const char *access_token, json_t **token, char **response) { oidc_debug(r, "enter: JWT access_token header=%s", oidc_proto_peek_jwt_header(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, c->provider.client_secret, 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, c->private_keys, 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_validate_jwt(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", c->oauth.verify_public_keys ? c->oauth.verify_public_keys->nelts : 0, c->oauth.verify_shared_keys ? apr_hash_count(c->oauth.verify_shared_keys) : 0, c->oauth.verify_jwks_uri); // TODO: we're re-using the OIDC provider JWKs refresh interval here... oidc_jwks_uri_t jwks_uri = {c->oauth.verify_jwks_uri, c->provider.jwks_uri.refresh_interval, NULL, NULL}; if (oidc_proto_jwt_verify( r, c, jwt, &jwks_uri, c->oauth.ssl_validate_server, oidc_util_merge_key_sets(r->pool, c->oauth.verify_shared_keys, c->oauth.verify_public_keys), 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 WWW-Authenticate response header according to https://tools.ietf.org/html/rfc6750#section-3 */ int oidc_oauth_return_www_authenticate(request_rec *r, const char *error, const char *error_description) { apr_byte_t accept_token_in = oidc_cfg_dir_accept_token_in(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_util_hdr_err_out_add(r, OIDC_HTTP_HDR_WWW_AUTHENTICATE, hdr); return HTTP_UNAUTHORIZED; } /* * 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 *c, json_t *token) { char *remote_user = NULL; if (oidc_get_remote_user(r, c->oauth.remote_user_claim.claim_name, c->oauth.remote_user_claim.reg_exp, c->oauth.remote_user_claim.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", c->oauth.remote_user_claim.claim_name); return FALSE; } r->user = apr_pstrdup(r->pool, remote_user); oidc_debug(r, "set user to \"%s\" based on claim: \"%s\"%s", r->user, c->oauth.remote_user_claim.claim_name, c->oauth.remote_user_claim.reg_exp ? apr_psprintf(r->pool, " and expression: \"%s\" and replace string: \"%s\"", c->oauth.remote_user_claim.reg_exp, c->oauth.remote_user_claim.replace) : ""); return TRUE; } /* * main routine: handle OAuth 2.0 authentication/authorization */ int oidc_oauth_check_userid(request_rec *r, oidc_cfg *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_get_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)) { return oidc_handle_jwks(r, c); /* 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_handle_remove_at_cache(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_oauth_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 (c->oauth.introspection_endpoint_url != NULL) { /* we'll validate the token remotely */ if (oidc_oauth_resolve_access_token(r, c, access_token, &token, &s_token) == FALSE) return oidc_oauth_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_oauth_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_oauth_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_oauth_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 */ char *authn_header = oidc_cfg_dir_authn_header(r); apr_byte_t pass_headers = oidc_cfg_dir_pass_info_in_headers(r); apr_byte_t pass_envvars = oidc_cfg_dir_pass_info_in_envvars(r); int pass_hdr_as = oidc_cfg_dir_pass_info_encoding(r); if ((r->user != NULL) && (authn_header != NULL)) oidc_util_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(r), c->claim_delimiter, pass_headers, pass_envvars, pass_hdr_as); /* 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_headers, pass_envvars, pass_hdr_as); } /* free JSON resources */ json_decref(token); /* strip any cookies that we need to */ oidc_strip_cookies(r); return OK; } mod_auth_openidc-2.4.15.1/src/parse.c000066400000000000000000001260441455620533500173270ustar00rootroot00000000000000/* * 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-2024 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. * * Validation and parsing of configuration values. * * @Author: Hans Zandbelt - hans.zandbelt@openidc.com */ #include #include "mod_auth_openidc.h" /* * parse a URL according to one of two schemes (NULL for any) */ static const char *oidc_valid_url_scheme(apr_pool_t *pool, const char *arg, const char *scheme1, const char *scheme2) { apr_uri_t uri; 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_strcmp(uri.scheme, scheme1) != 0)) { if ((scheme2 != NULL) && (_oidc_strcmp(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; } /* * parse a URL according to a scheme */ const char *oidc_valid_url(apr_pool_t *pool, const char *arg, const char *scheme) { return oidc_valid_url_scheme(pool, arg, scheme, NULL); } /* * parse a URL that should conform to any HTTP scheme (http/https) */ const char *oidc_valid_http_url(apr_pool_t *pool, const char *arg) { return oidc_valid_url_scheme(pool, arg, "https", "http"); } #define STR_ERROR_MAX 128 /* * check if arg is a valid directory on the file system */ const char *oidc_valid_dir(apr_pool_t *pool, const char *arg) { char s_err[STR_ERROR_MAX]; apr_dir_t *dir = NULL; apr_status_t rc = APR_SUCCESS; /* ensure the directory exists */ if ((rc = apr_dir_open(&dir, arg, pool)) != APR_SUCCESS) { return apr_psprintf(pool, "cannot access directory '%s' (%s)", arg, apr_strerror(rc, s_err, STR_ERROR_MAX)); } /* and cleanup... */ if ((rc = apr_dir_close(dir)) != APR_SUCCESS) { return apr_psprintf(pool, "cannot close directory '%s' (%s)", arg, apr_strerror(rc, s_err, STR_ERROR_MAX)); } return NULL; } /* * check if arg is a valid cookie domain value */ const char *oidc_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; } /* * parse an integer value from a string */ const char *oidc_parse_int(apr_pool_t *pool, const char *arg, int *int_value) { if (*arg == '\0') return apr_psprintf(pool, "invalid integer value: %s", arg); int v = _oidc_str_to_int(arg); *int_value = v; return NULL; } /* * check if the provided integer value is between a specified minimum and maximum */ static const char *oidc_valid_int_min_max(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 an integer and check validity */ static const char *oidc_parse_int_valid(apr_pool_t *pool, const char *arg, int *int_value, oidc_valid_int_function_t valid_int_function) { int v = 0; const char *rv = NULL; rv = oidc_parse_int(pool, arg, &v); if (rv != NULL) return rv; rv = valid_int_function(pool, v); if (rv != NULL) return rv; *int_value = v; return NULL; } /* * parse an integer value from a string that must be between a specified minimum and maximum */ static const char *oidc_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_parse_int(pool, arg, &v); if (rv != NULL) return rv; rv = oidc_valid_int_min_max(pool, v, min_value, max_value); if (rv != NULL) return rv; *int_value = v; return NULL; } #define OIDC_LIST_OPTIONS_START "[" #define OIDC_LIST_OPTIONS_END "]" #define OIDC_LIST_OPTIONS_SEPARATOR "|" #define OIDC_LIST_OPTIONS_QUOTE "'" /* * flatten the list of string options, separated by the specified separator char */ static char *oidc_flatten_list_options(apr_pool_t *pool, 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 */ static const char *oidc_valid_string_option(apr_pool_t *pool, const char *arg, 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_flatten_list_options(pool, options)); } return NULL; } #define OIDC_CACHE_TYPE_SHM "shm" #define OIDC_CACHE_TYPE_MEMCACHE "memcache" #define OIDC_CACHE_TYPE_REDIS "redis" #define OIDC_CACHE_TYPE_FILE "file" /* * parse the cache backend type */ const char *oidc_parse_cache_type(apr_pool_t *pool, const char *arg, oidc_cache_t **type) { static char *options[] = {OIDC_CACHE_TYPE_SHM, #ifdef USE_MEMCACHE OIDC_CACHE_TYPE_MEMCACHE, #endif #ifdef USE_LIBHIREDIS OIDC_CACHE_TYPE_REDIS, #endif OIDC_CACHE_TYPE_FILE, NULL}; const char *rv = oidc_valid_string_option(pool, arg, options); if (rv != NULL) return rv; if (_oidc_strcmp(arg, OIDC_CACHE_TYPE_SHM) == 0) { *type = &oidc_cache_shm; #ifdef USE_MEMCACHE } else if (_oidc_strcmp(arg, OIDC_CACHE_TYPE_MEMCACHE) == 0) { *type = &oidc_cache_memcache; #endif } else if (_oidc_strcmp(arg, OIDC_CACHE_TYPE_FILE) == 0) { *type = &oidc_cache_file; #ifdef USE_LIBHIREDIS } else if (_oidc_strcmp(arg, OIDC_CACHE_TYPE_REDIS) == 0) { *type = &oidc_cache_redis; #endif } return NULL; } #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 ":" /* * parse the session mechanism type and the cookie persistency property */ const char *oidc_parse_session_type(apr_pool_t *pool, const char *arg, int *type, int *persistent, apr_byte_t *store_id_token) { static 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_valid_string_option(pool, arg, options); if (rv != NULL) return rv; char *s = apr_pstrdup(pool, arg); char *p = strstr(s, OIDC_SESSION_TYPE_SEPARATOR); if (p) { *p = '\0'; p++; } if (_oidc_strcmp(s, OIDC_SESSION_TYPE_SERVER_CACHE_STR) == 0) { *type = OIDC_SESSION_TYPE_SERVER_CACHE; } else if (_oidc_strcmp(s, OIDC_SESSION_TYPE_CLIENT_COOKIE_STR) == 0) { *type = OIDC_SESSION_TYPE_CLIENT_COOKIE; *store_id_token = FALSE; } if (p) { if (_oidc_strcmp(p, OIDC_SESSION_TYPE_PERSISTENT) == 0) { *persistent = 1; } else if (_oidc_strcmp(p, OIDC_SESSION_TYPE_STORE_ID_TOKEN) == 0) { // only for client-cookie *store_id_token = TRUE; } 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 *persistent = 1; *store_id_token = TRUE; } } return NULL; } /* 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 /* * parse the slot size of a SHM cache entry */ const char *oidc_parse_cache_shm_entry_size_max(apr_pool_t *pool, const char *arg, int *int_value) { const char *rv = oidc_parse_int_min_max(pool, arg, int_value, OIDC_MINIMUM_CACHE_SHM_ENTRY_SIZE_MAX, OIDC_MAXIMUM_CACHE_SHM_ENTRY_SIZE_MAX); if ((rv == NULL) && (((*int_value) % 8) != 0)) rv = "the slot size must be a multiple of 8"; return rv; } /* * parse a boolean value from a provided string */ const char *oidc_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); } #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" /* * check if the provided endpoint authentication method is supported */ static const char *oidc_valid_endpoint_auth_method_impl(apr_pool_t *pool, const char *arg, apr_byte_t has_private_key) { static 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_valid_string_option(pool, arg, options); } const char *oidc_valid_endpoint_auth_method(apr_pool_t *pool, const char *arg) { return oidc_valid_endpoint_auth_method_impl(pool, arg, TRUE); } const char *oidc_valid_endpoint_auth_method_no_private_key(apr_pool_t *pool, const char *arg) { return oidc_valid_endpoint_auth_method_impl(pool, arg, FALSE); } /* * check if the provided OAuth/OIDC response type is supported */ const char *oidc_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 PKCE method is supported */ const char *oidc_valid_pkce_method(apr_pool_t *pool, const char *arg) { static char *options[] = {OIDC_PKCE_METHOD_PLAIN, OIDC_PKCE_METHOD_S256, OIDC_PKCE_METHOD_NONE, NULL}; return oidc_valid_string_option(pool, arg, options); } #define OIDC_RESPONSE_TYPE_FRAGMENT "fragment" #define OIDC_RESPONSE_TYPE_QUERY "query" #define OIDC_RESPONSE_TYPE_FORM_POST "form_post" /* * check if the provided OAuth 2.0 response mode is supported */ const char *oidc_valid_response_mode(apr_pool_t *pool, const char *arg) { static char *options[] = {OIDC_RESPONSE_TYPE_FRAGMENT, OIDC_RESPONSE_TYPE_QUERY, OIDC_RESPONSE_TYPE_FORM_POST, NULL}; return oidc_valid_string_option(pool, arg, options); } /* * check if the provided JWT signature algorithm is supported */ const char *oidc_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_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_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; } #define OIDC_SESSION_INACTIVITY_TIMEOUT_MIN 10 #define OIDC_SESSION_INACTIVITY_TIMEOUT_MAX 3600 * 24 * 365 /* * parse a session inactivity timeout value from the provided string */ const char *oidc_parse_session_inactivity_timeout(apr_pool_t *pool, const char *arg, int *int_value) { return oidc_parse_int_min_max(pool, arg, int_value, OIDC_SESSION_INACTIVITY_TIMEOUT_MIN, OIDC_SESSION_INACTIVITY_TIMEOUT_MAX); } #define OIDC_SESSION_MAX_DURATION_MIN 15 #define OIDC_SESSION_MAX_DURATION_MAX 3600 * 24 * 365 /* * check the boundaries for session max lifetime */ const char *oidc_valid_session_max_duration(apr_pool_t *pool, int v) { if (v == 0) { return NULL; } if (v < OIDC_SESSION_MAX_DURATION_MIN) { return apr_psprintf(pool, "value must not be less than %d seconds", OIDC_SESSION_MAX_DURATION_MIN); } if (v > OIDC_SESSION_MAX_DURATION_MAX) { return apr_psprintf(pool, "value must not be greater than %d seconds", OIDC_SESSION_MAX_DURATION_MAX); } return NULL; } #define OIDC_MAX_NUMBER_OF_STATE_COOKIES_MIN 0 #define OIDC_MAX_NUMBER_OF_STATE_COOKIES_MAX 255 /* * check the maximum number of parallel state cookies */ const char *oidc_valid_max_number_of_state_cookies(apr_pool_t *pool, int v) { if (v == 0) { return NULL; } if (v < OIDC_MAX_NUMBER_OF_STATE_COOKIES_MIN) { return apr_psprintf(pool, "maximum must not be less than %d", OIDC_MAX_NUMBER_OF_STATE_COOKIES_MIN); } if (v > OIDC_MAX_NUMBER_OF_STATE_COOKIES_MAX) { return apr_psprintf(pool, "maximum must not be greater than %d", OIDC_MAX_NUMBER_OF_STATE_COOKIES_MAX); } return NULL; } /* * parse a session max duration value from the provided string */ const char *oidc_parse_session_max_duration(apr_pool_t *pool, const char *arg, int *int_value) { return oidc_parse_int_valid(pool, arg, int_value, oidc_valid_session_max_duration); } /* * parse a base64 encoded binary value from the provided string */ char *oidc_parse_base64(apr_pool_t *pool, const char *input, char **output, int *output_len) { int len = apr_base64_decode_len(input); *output = apr_palloc(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; } /* * parse a base64url encoded binary value from the provided string */ static char *oidc_parse_base64url(apr_pool_t *pool, const char *input, char **output, int *output_len) { *output_len = oidc_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_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_parse_key_value(apr_pool_t *pool, const char *enc, const char *input, char **key, int *key_len) { static char *options[] = {OIDC_KEY_ENCODING_BASE64, OIDC_KEY_ENCODING_BASE64_URL, OIDC_KEY_ENCODING_HEX, OIDC_KEY_ENCODING_PLAIN, NULL}; const char *rv = oidc_valid_string_option(pool, enc, options); if (rv != NULL) return rv; if (_oidc_strcmp(enc, OIDC_KEY_ENCODING_BASE64) == 0) return oidc_parse_base64(pool, input, key, key_len); if (_oidc_strcmp(enc, OIDC_KEY_ENCODING_BASE64_URL) == 0) return oidc_parse_base64url(pool, input, key, key_len); if (_oidc_strcmp(enc, OIDC_KEY_ENCODING_HEX) == 0) return oidc_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; } #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_parse_use_enc_kid_key_tuple(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 (strstr(tuple, OIDC_KEY_SIG_PREFIX) == tuple) { *use = OIDC_JOSE_JWK_SIG_STR; tuple += strlen(OIDC_KEY_SIG_PREFIX); } else if (strstr(tuple, OIDC_KEY_ENC_PREFIX) == tuple) { *use = OIDC_JOSE_JWK_ENC_STR; tuple += strlen(OIDC_KEY_ENC_PREFIX); } } s = apr_pstrdup(pool, tuple); p = strstr(s, OIDC_KEY_TUPLE_SEPARATOR); if (p && triplet) q = 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_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_PASS_ID_TOKEN_AS_CLAIMS_STR "claims" #define OIDC_PASS_IDTOKEN_AS_PAYLOAD_STR "payload" #define OIDC_PASS_IDTOKEN_AS_SERIALIZED_STR "serialized" /* * convert a "pass id token as" value to an integer */ static int oidc_parse_pass_idtoken_as_str2int(const char *v) { if (_oidc_strcmp(v, OIDC_PASS_ID_TOKEN_AS_CLAIMS_STR) == 0) return OIDC_PASS_IDTOKEN_AS_CLAIMS; if (_oidc_strcmp(v, OIDC_PASS_IDTOKEN_AS_PAYLOAD_STR) == 0) return OIDC_PASS_IDTOKEN_AS_PAYLOAD; if (_oidc_strcmp(v, OIDC_PASS_IDTOKEN_AS_SERIALIZED_STR) == 0) return OIDC_PASS_IDTOKEN_AS_SERIALIZED; return -1; } /* * parse a "pass id token as" value from the provided strings */ const char *oidc_parse_pass_idtoken_as(apr_pool_t *pool, const char *v1, const char *v2, const char *v3, int *int_value) { static char *options[] = {OIDC_PASS_ID_TOKEN_AS_CLAIMS_STR, OIDC_PASS_IDTOKEN_AS_PAYLOAD_STR, OIDC_PASS_IDTOKEN_AS_SERIALIZED_STR, NULL}; const char *rv = NULL; rv = oidc_valid_string_option(pool, v1, options); if (rv != NULL) return rv; *int_value = oidc_parse_pass_idtoken_as_str2int(v1); if (v2 == NULL) return NULL; rv = oidc_valid_string_option(pool, v2, options); if (rv != NULL) return rv; *int_value |= oidc_parse_pass_idtoken_as_str2int(v2); if (v3 == NULL) return NULL; rv = oidc_valid_string_option(pool, v3, options); if (rv != NULL) return rv; *int_value |= oidc_parse_pass_idtoken_as_str2int(v3); return NULL; } #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" /* * convert a "pass userinfo as" value to an integer */ static int oidc_parse_pass_userinfo_as_str2int(const char *v) { if (_oidc_strcmp(v, OIDC_PASS_USERINFO_AS_CLAIMS_STR) == 0) return OIDC_PASS_USERINFO_AS_CLAIMS; if (_oidc_strcmp(v, OIDC_PASS_USERINFO_AS_JSON_OBJECT_STR) == 0) return OIDC_PASS_USERINFO_AS_JSON_OBJECT; if (_oidc_strcmp(v, OIDC_PASS_USERINFO_AS_JWT_STR) == 0) return OIDC_PASS_USERINFO_AS_JWT; if (_oidc_strcmp(v, OIDC_PASS_USERINFO_AS_SIGNED_JWT_STR) == 0) return OIDC_PASS_USERINFO_AS_SIGNED_JWT; return -1; } /* * parse a "pass id token as" value from the provided strings */ const char *oidc_parse_pass_userinfo_as(apr_pool_t *pool, const char *v, oidc_pass_user_info_as_t **result) { static char *options[] = {OIDC_PASS_USERINFO_AS_CLAIMS_STR, OIDC_PASS_USERINFO_AS_JSON_OBJECT_STR, OIDC_PASS_USERINFO_AS_JWT_STR, OIDC_PASS_USERINFO_AS_SIGNED_JWT_STR, NULL}; const char *rv = NULL; char *name = strstr(v, ":"); if (name) { *name = '\0'; name++; } rv = oidc_valid_string_option(pool, v, options); if (rv != NULL) return rv; *result = apr_pcalloc(pool, sizeof(oidc_pass_user_info_as_t)); (*result)->type = oidc_parse_pass_userinfo_as_str2int(v); if (name) (*result)->name = apr_pstrdup(pool, name); return NULL; } #define OIDC_LOGOUT_ON_ERROR_REFRESH_STR "logout_on_error" #define OIDC_LOGOUT_ON_ERROR_AUTHENTICATE_STR "authenticate_on_error" /* * convert an "on access token refresh error" to an integer */ static int oidc_parse_action_on_error_refresh_as_str2int(const char *v) { if (_oidc_strcmp(v, OIDC_LOGOUT_ON_ERROR_REFRESH_STR) == 0) return OIDC_ON_ERROR_LOGOUT; if (_oidc_strcmp(v, OIDC_LOGOUT_ON_ERROR_AUTHENTICATE_STR) == 0) return OIDC_ON_ERROR_AUTHENTICATE; return OIDC_CONFIG_POS_INT_UNSET; } /* * parse an "on access token refresh error" value from the provided strings */ const char *oidc_parse_action_on_error_refresh_as(apr_pool_t *pool, const char *v1, int *int_value) { static char *options[] = {OIDC_LOGOUT_ON_ERROR_REFRESH_STR, OIDC_LOGOUT_ON_ERROR_AUTHENTICATE_STR, NULL}; const char *rv = NULL; rv = oidc_valid_string_option(pool, v1, options); if (rv != NULL) return rv; *int_value = oidc_parse_action_on_error_refresh_as_str2int(v1); 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" /* * convert an "accept OAuth 2.0 token in" byte value to a string representation */ const char *oidc_accept_oauth_token_in2str(apr_pool_t *pool, apr_byte_t v) { static char *options[] = {NULL, NULL, NULL, NULL, NULL}; int i = 0; if (v & OIDC_OAUTH_ACCEPT_TOKEN_IN_HEADER) { options[i] = OIDC_OAUTH_ACCEPT_TOKEN_IN_HEADER_STR; i++; } if (v & OIDC_OAUTH_ACCEPT_TOKEN_IN_POST) { options[i] = OIDC_OAUTH_ACCEPT_TOKEN_IN_POST_STR; i++; } if (v & OIDC_OAUTH_ACCEPT_TOKEN_IN_QUERY) { options[i] = OIDC_OAUTH_ACCEPT_TOKEN_IN_QUERY_STR; i++; } if (v & OIDC_OAUTH_ACCEPT_TOKEN_IN_COOKIE) { options[i] = OIDC_OAUTH_ACCEPT_TOKEN_IN_COOKIE_STR; i++; } if (v & OIDC_OAUTH_ACCEPT_TOKEN_IN_BASIC) { options[i] = OIDC_OAUTH_ACCEPT_TOKEN_IN_BASIC_STR; i++; } return oidc_flatten_list_options(pool, options); } /* * convert an "accept OAuth 2.0 token in" value to an integer */ static apr_byte_t oidc_parse_oauth_accept_token_in_str2byte(const char *v) { if (_oidc_strcmp(v, OIDC_OAUTH_ACCEPT_TOKEN_IN_HEADER_STR) == 0) return OIDC_OAUTH_ACCEPT_TOKEN_IN_HEADER; if (_oidc_strcmp(v, OIDC_OAUTH_ACCEPT_TOKEN_IN_POST_STR) == 0) return OIDC_OAUTH_ACCEPT_TOKEN_IN_POST; if (_oidc_strcmp(v, OIDC_OAUTH_ACCEPT_TOKEN_IN_QUERY_STR) == 0) return OIDC_OAUTH_ACCEPT_TOKEN_IN_QUERY; if (strstr(v, OIDC_OAUTH_ACCEPT_TOKEN_IN_COOKIE_STR) == v) return OIDC_OAUTH_ACCEPT_TOKEN_IN_COOKIE; if (strstr(v, OIDC_OAUTH_ACCEPT_TOKEN_IN_BASIC_STR) == v) return OIDC_OAUTH_ACCEPT_TOKEN_IN_BASIC; return OIDC_OAUTH_ACCEPT_TOKEN_IN_DEFAULT; } #define OIDC_OAUTH_ACCEPT_TOKEN_IN_COOKIE_NAME_DEFAULT "PA.global" #define OIDC_OAUTH_ACCEPT_TOKEN_IN_COOKIE_SEPARATOR ":" /* * parse an "accept OAuth 2.0 token in" value from the provided string */ const char *oidc_parse_accept_oauth_token_in(apr_pool_t *pool, const char *arg, int *b_value, apr_hash_t *list_options) { static char *options[] = {OIDC_OAUTH_ACCEPT_TOKEN_IN_HEADER_STR, OIDC_OAUTH_ACCEPT_TOKEN_IN_POST_STR, OIDC_OAUTH_ACCEPT_TOKEN_IN_QUERY_STR, OIDC_OAUTH_ACCEPT_TOKEN_IN_COOKIE_STR, OIDC_OAUTH_ACCEPT_TOKEN_IN_BASIC_STR, NULL}; const char *rv = NULL; const char *s = apr_pstrdup(pool, arg); char *p = 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_valid_string_option(pool, s, options); if (rv != NULL) return rv; int v = oidc_parse_oauth_accept_token_in_str2byte(s); if (*b_value == OIDC_CONFIG_POS_INT_UNSET) *b_value = v; else *b_value |= v; if (v == OIDC_OAUTH_ACCEPT_TOKEN_IN_COOKIE) { apr_hash_set(list_options, OIDC_OAUTH_ACCEPT_TOKEN_IN_OPTION_COOKIE_NAME, APR_HASH_KEY_STRING, p); } return NULL; } /* * check if the specified string is a valid claim formatting configuration value */ const char *oidc_valid_claim_format(apr_pool_t *pool, const char *arg) { static char *options[] = {OIDC_CLAIM_FORMAT_RELATIVE, OIDC_CLAIM_FORMAT_ABSOLUTE, NULL}; return oidc_valid_string_option(pool, arg, options); } /* * parse a "claim required" value from the provided string */ const char *oidc_parse_claim_required(apr_pool_t *pool, const char *arg, int *is_required) { static char *options[] = {OIDC_CLAIM_REQUIRED_MANDATORY, OIDC_CLAIM_REQUIRED_OPTIONAL, NULL}; const char *rv = oidc_valid_string_option(pool, arg, options); if (rv != NULL) return rv; *is_required = (_oidc_strcmp(arg, OIDC_CLAIM_REQUIRED_MANDATORY) == 0); return NULL; } /* * check if the provided string is a valid HTTP method for the OAuth token introspection endpoint */ const char *oidc_valid_introspection_method(apr_pool_t *pool, const char *arg) { static char *options[] = {OIDC_INTROSPECTION_METHOD_GET, OIDC_INTROSPECTION_METHOD_POST, NULL}; return oidc_valid_string_option(pool, arg, options); } #define OIDC_PASS_CLAIMS_AS_BOTH "both" #define OIDC_PASS_CLAIMS_AS_HEADERS "headers" #define OIDC_PASS_CLAIMS_AS_ENV "environment" #define OIDC_PASS_CLAIMS_AS_NONE "none" /* * parse a "set claims as" value from the provided string */ const char *oidc_parse_set_claims_as(apr_pool_t *pool, const char *arg, int *in_headers, int *in_env_vars) { static char *options[] = {OIDC_PASS_CLAIMS_AS_BOTH, OIDC_PASS_CLAIMS_AS_HEADERS, OIDC_PASS_CLAIMS_AS_ENV, OIDC_PASS_CLAIMS_AS_NONE, NULL}; const char *rv = oidc_valid_string_option(pool, arg, options); if (rv != NULL) return rv; if (_oidc_strcmp(arg, OIDC_PASS_CLAIMS_AS_BOTH) == 0) { *in_headers = 1; *in_env_vars = 1; } else if (_oidc_strcmp(arg, OIDC_PASS_CLAIMS_AS_HEADERS) == 0) { *in_headers = 1; *in_env_vars = 0; } else if (_oidc_strcmp(arg, OIDC_PASS_CLAIMS_AS_ENV) == 0) { *in_headers = 0; *in_env_vars = 1; } else if (_oidc_strcmp(arg, OIDC_PASS_CLAIMS_AS_NONE) == 0) { *in_headers = 0; *in_env_vars = 0; } return NULL; } #define OIDC_PASS_CLAIMS_ENCODING_NONE_STR "none" #define OIDC_PASS_CLAIMS_ENCODING_LATIN1_STR "latin1" #define OIDC_PASS_CLAIMS_ENCODING_BASE64URL_STR "base64url" const char *oidc_parse_pass_claims_as_encoding(apr_pool_t *pool, const char *arg, int *pass_as) { static char *options[] = {OIDC_PASS_CLAIMS_ENCODING_NONE_STR, OIDC_PASS_CLAIMS_ENCODING_LATIN1_STR, OIDC_PASS_CLAIMS_ENCODING_BASE64URL_STR, NULL}; const char *rv = oidc_valid_string_option(pool, arg, options); if (rv != NULL) return rv; if (_oidc_strcmp(arg, OIDC_PASS_CLAIMS_ENCODING_NONE_STR) == 0) { *pass_as = OIDC_PASS_APP_INFO_AS_NONE; } else if (_oidc_strcmp(arg, OIDC_PASS_CLAIMS_ENCODING_LATIN1_STR) == 0) { *pass_as = OIDC_PASS_APP_INFO_AS_LATIN1; } else if (_oidc_strcmp(arg, OIDC_PASS_CLAIMS_ENCODING_BASE64URL_STR) == 0) { *pass_as = OIDC_PASS_APP_INFO_AS_BASE64URL; } return NULL; } #define OIDC_UNAUTH_ACTION_AUTH_STR "auth" #define OIDC_UNAUTH_ACTION_PASS_STR "pass" #define OIDC_UNAUTH_ACTION_401_STR "401" #define OIDC_UNAUTH_ACTION_407_STR "407" #define OIDC_UNAUTH_ACTION_410_STR "410" /* * parse an "unauthenticated action" value from the provided string */ const char *oidc_parse_unauth_action(apr_pool_t *pool, const char *arg, int *action) { static char *options[] = {OIDC_UNAUTH_ACTION_AUTH_STR, OIDC_UNAUTH_ACTION_PASS_STR, OIDC_UNAUTH_ACTION_401_STR, OIDC_UNAUTH_ACTION_407_STR, OIDC_UNAUTH_ACTION_410_STR, NULL}; const char *rv = oidc_valid_string_option(pool, arg, options); if (rv != NULL) return rv; if (_oidc_strcmp(arg, OIDC_UNAUTH_ACTION_AUTH_STR) == 0) *action = OIDC_UNAUTH_AUTHENTICATE; else if (_oidc_strcmp(arg, OIDC_UNAUTH_ACTION_PASS_STR) == 0) *action = OIDC_UNAUTH_PASS; else if (_oidc_strcmp(arg, OIDC_UNAUTH_ACTION_401_STR) == 0) *action = OIDC_UNAUTH_RETURN401; else if (_oidc_strcmp(arg, OIDC_UNAUTH_ACTION_407_STR) == 0) *action = OIDC_UNAUTH_RETURN407; else if (_oidc_strcmp(arg, OIDC_UNAUTH_ACTION_410_STR) == 0) *action = OIDC_UNAUTH_RETURN410; return NULL; } #define OIDC_UNAUTZ_ACTION_AUTH_STR "auth" #define OIDC_UNAUTZ_ACTION_401_STR "401" #define OIDC_UNAUTZ_ACTION_403_STR "403" #define OIDC_UNAUTZ_ACTION_302_STR "302" /* * parse an "unauthorized action" value from the provided string */ const char *oidc_parse_unautz_action(apr_pool_t *pool, const char *arg, int *action) { static char *options[] = {OIDC_UNAUTZ_ACTION_AUTH_STR, OIDC_UNAUTZ_ACTION_401_STR, OIDC_UNAUTZ_ACTION_403_STR, OIDC_UNAUTZ_ACTION_302_STR, NULL}; const char *rv = oidc_valid_string_option(pool, arg, options); if (rv != NULL) return rv; if (_oidc_strcmp(arg, OIDC_UNAUTZ_ACTION_AUTH_STR) == 0) *action = OIDC_UNAUTZ_AUTHENTICATE; else if (_oidc_strcmp(arg, OIDC_UNAUTZ_ACTION_401_STR) == 0) *action = OIDC_UNAUTZ_RETURN401; else if (_oidc_strcmp(arg, OIDC_UNAUTZ_ACTION_403_STR) == 0) *action = OIDC_UNAUTZ_RETURN403; else if (_oidc_strcmp(arg, OIDC_UNAUTZ_ACTION_302_STR) == 0) *action = OIDC_UNAUTZ_RETURN302; return NULL; } /* * check if there's a valid entry in a string of arrays, with a preference */ const char *oidc_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; } #define OIDC_JWKS_REFRESH_INTERVAL_MIN 300 #define OIDC_JWKS_REFRESH_INTERVAL_MAX 3600 * 24 * 365 /* * check the boundaries for JWKs refresh interval */ const char *oidc_valid_jwks_refresh_interval(apr_pool_t *pool, int v) { return oidc_valid_int_min_max(pool, v, OIDC_JWKS_REFRESH_INTERVAL_MIN, OIDC_JWKS_REFRESH_INTERVAL_MAX); } /* * parse a JWKs refresh interval from the provided string */ const char *oidc_parse_jwks_refresh_interval(apr_pool_t *pool, const char *arg, int *int_value) { return oidc_parse_int_valid(pool, arg, int_value, oidc_valid_jwks_refresh_interval); } #define OIDC_IDTOKEN_IAT_SLACK_MIN 0 #define OIDC_IDTOKEN_IAT_SLACK_MAX 3600 /* * check the boundaries for ID token "issued-at" (iat) timestamp slack */ const char *oidc_valid_idtoken_iat_slack(apr_pool_t *pool, int v) { return oidc_valid_int_min_max(pool, v, OIDC_IDTOKEN_IAT_SLACK_MIN, OIDC_IDTOKEN_IAT_SLACK_MAX); } /* * parse an ID token "iat" slack interval */ const char *oidc_parse_idtoken_iat_slack(apr_pool_t *pool, const char *arg, int *int_value) { return oidc_parse_int_valid(pool, arg, int_value, oidc_valid_idtoken_iat_slack); } #define OIDC_USERINFO_REFRESH_INTERVAL_MIN 0 #define OIDC_USERINFO_REFRESH_INTERVAL_MAX 3600 * 24 * 365 /* * check the boundaries for the userinfo refresh interval */ const char *oidc_valid_userinfo_refresh_interval(apr_pool_t *pool, int v) { return oidc_valid_int_min_max(pool, v, OIDC_USERINFO_REFRESH_INTERVAL_MIN, OIDC_USERINFO_REFRESH_INTERVAL_MAX); } /* * parse a userinfo refresh interval from the provided string */ const char *oidc_parse_userinfo_refresh_interval(apr_pool_t *pool, const char *arg, int *int_value) { return oidc_parse_int_valid(pool, arg, int_value, oidc_valid_userinfo_refresh_interval); } #define OIDC_USER_INFO_TOKEN_METHOD_HEADER_STR "authz_header" #define OIDC_USER_INFO_TOKEN_METHOD_POST_STR "post_param" /* * check if the provided string is a valid userinfo token presentation method */ const char *oidc_valid_userinfo_token_method(apr_pool_t *pool, const char *arg) { static char *options[] = {OIDC_USER_INFO_TOKEN_METHOD_HEADER_STR, OIDC_USER_INFO_TOKEN_METHOD_POST_STR, NULL}; return oidc_valid_string_option(pool, arg, options); } /* * parse a userinfo token method string value to an integer */ const char *oidc_parse_userinfo_token_method(apr_pool_t *pool, const char *arg, int *int_value) { const char *rv = oidc_valid_userinfo_token_method(pool, arg); if (rv != NULL) return rv; if (_oidc_strcmp(arg, OIDC_USER_INFO_TOKEN_METHOD_HEADER_STR) == 0) *int_value = OIDC_USER_INFO_TOKEN_METHOD_HEADER; if (_oidc_strcmp(arg, OIDC_USER_INFO_TOKEN_METHOD_POST_STR) == 0) *int_value = OIDC_USER_INFO_TOKEN_METHOD_POST; return NULL; } /* * parse an "info hook data" value from the provided string */ const char *oidc_parse_info_hook_data(apr_pool_t *pool, const char *arg, apr_hash_t **hook_data) { static 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_valid_string_option(pool, arg, options); if (rv != NULL) return rv; if (*hook_data == NULL) *hook_data = apr_hash_make(pool); apr_hash_set(*hook_data, arg, APR_HASH_KEY_STRING, arg); return NULL; } #define OIDC_TRACE_PARENT_PROPAGATE_STR "propagate" #define OIDC_TRACE_PARENT_GENERATE_STR "generate" const char *valid_trace_parent_value(apr_pool_t *pool, const char *arg) { static char *options[] = {OIDC_TRACE_PARENT_PROPAGATE_STR, OIDC_TRACE_PARENT_GENERATE_STR, NULL}; return oidc_valid_string_option(pool, arg, options); } const char *oidc_parse_trace_parent(apr_pool_t *pool, const char *arg, int *trace_parent) { const char *rv = valid_trace_parent_value(pool, arg); if (rv != NULL) return rv; if (_oidc_strcmp(arg, OIDC_TRACE_PARENT_PROPAGATE_STR) == 0) *trace_parent = OIDC_TRACE_PARENT_PROPAGATE; else if (_oidc_strcmp(arg, OIDC_TRACE_PARENT_GENERATE_STR) == 0) *trace_parent = OIDC_TRACE_PARENT_GENERATE; return NULL; } #define OIDC_AUTH_REQUEST_METHOD_GET_STR "GET" #define OIDC_AUTH_REQEUST_METHOD_POST_STR "POST" /* * parse method for sending the authentication request */ const char *oidc_valid_auth_request_method(apr_pool_t *pool, const char *arg) { static char *options[] = {OIDC_AUTH_REQUEST_METHOD_GET_STR, OIDC_AUTH_REQEUST_METHOD_POST_STR, NULL}; return oidc_valid_string_option(pool, arg, options); } /* * parse method for sending the authentication request */ const char *oidc_parse_auth_request_method(apr_pool_t *pool, const char *arg, int *method) { const char *rv = oidc_valid_auth_request_method(pool, arg); if (rv != NULL) return rv; if (_oidc_strcmp(arg, OIDC_AUTH_REQUEST_METHOD_GET_STR) == 0) *method = OIDC_AUTH_REQUEST_METHOD_GET; else if (_oidc_strcmp(arg, OIDC_AUTH_REQEUST_METHOD_POST_STR) == 0) *method = OIDC_AUTH_REQUEST_METHOD_POST; return NULL; } /* * parse the maximum number of parallel state cookies */ const char *oidc_parse_max_number_of_state_cookies(apr_pool_t *pool, const char *arg1, const char *arg2, int *int_value, int *bool_value) { const char *rv = NULL; rv = oidc_parse_int_valid(pool, arg1, int_value, oidc_valid_max_number_of_state_cookies); if ((rv == NULL) && (arg2 != NULL)) rv = oidc_parse_boolean(pool, arg2, bool_value); return rv; } #define OIDC_REFRESH_ACCESS_TOKEN_BEFORE_EXPIRY_MIN 0 #define OIDC_REFRESH_ACCESS_TOKEN_BEFORE_EXPIRY_MAX 3600 * 24 * 365 /* * check the boundaries for the refresh access token expiry TTL */ const char *oidc_valid_refresh_access_token_before_expiry(apr_pool_t *pool, int v) { return oidc_valid_int_min_max(pool, v, OIDC_REFRESH_ACCESS_TOKEN_BEFORE_EXPIRY_MIN, OIDC_REFRESH_ACCESS_TOKEN_BEFORE_EXPIRY_MAX); } /* * parse an access token expiry TTL from the provided string */ const char *oidc_parse_refresh_access_token_before_expiry(apr_pool_t *pool, const char *arg, int *int_value) { return oidc_parse_int_valid(pool, arg, int_value, oidc_valid_refresh_access_token_before_expiry); } #define OIDC_STATE_INPUT_HEADERS_AS_BOTH "both" #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_NONE "none" /* * parse a "set state input headers as" value from the provided string */ const char *oidc_parse_set_state_input_headers_as(apr_pool_t *pool, const char *arg, apr_byte_t *state_input_headers) { static char *options[] = {OIDC_STATE_INPUT_HEADERS_AS_BOTH, OIDC_STATE_INPUT_HEADERS_AS_USER_AGENT, OIDC_STATE_INPUT_HEADERS_AS_X_FORWARDED_FOR, OIDC_STATE_INPUT_HEADERS_AS_NONE, NULL}; const char *rv = oidc_valid_string_option(pool, arg, options); if (rv != NULL) return rv; if (_oidc_strcmp(arg, OIDC_STATE_INPUT_HEADERS_AS_BOTH) == 0) { *state_input_headers = OIDC_STATE_INPUT_HEADERS_USER_AGENT | OIDC_STATE_INPUT_HEADERS_X_FORWARDED_FOR; } else if (_oidc_strcmp(arg, OIDC_STATE_INPUT_HEADERS_AS_USER_AGENT) == 0) { *state_input_headers = OIDC_STATE_INPUT_HEADERS_USER_AGENT; } else if (_oidc_strcmp(arg, OIDC_STATE_INPUT_HEADERS_AS_X_FORWARDED_FOR) == 0) { *state_input_headers = OIDC_STATE_INPUT_HEADERS_X_FORWARDED_FOR; } else if (_oidc_strcmp(arg, OIDC_STATE_INPUT_HEADERS_AS_NONE) == 0) { *state_input_headers = 0; } return NULL; } const char *oidc_parse_x_forwarded_headers(apr_pool_t *pool, const char *arg, apr_byte_t *x_forwarded_headers) { static char *options[] = {OIDC_HTTP_HDR_X_FORWARDED_HOST, OIDC_HTTP_HDR_X_FORWARDED_PORT, OIDC_HTTP_HDR_X_FORWARDED_PROTO, OIDC_HTTP_HDR_FORWARDED, NULL}; const char *rv = oidc_valid_string_option(pool, arg, options); if (rv != NULL) return rv; if (_oidc_strcmp(arg, OIDC_HTTP_HDR_X_FORWARDED_HOST) == 0) { *x_forwarded_headers |= OIDC_HDR_X_FORWARDED_HOST; } else if (_oidc_strcmp(arg, OIDC_HTTP_HDR_X_FORWARDED_PORT) == 0) { *x_forwarded_headers |= OIDC_HDR_X_FORWARDED_PORT; } else if (_oidc_strcmp(arg, OIDC_HTTP_HDR_X_FORWARDED_PROTO) == 0) { *x_forwarded_headers |= OIDC_HDR_X_FORWARDED_PROTO; } else if (_oidc_strcmp(arg, OIDC_HTTP_HDR_FORWARDED) == 0) { *x_forwarded_headers |= OIDC_HDR_FORWARDED; } return NULL; } #define OIDC_PROXY_AUTH_BASIC "basic" #define OIDC_PROXY_AUTH_DIGEST "digest" #define OIDC_PROXY_AUTH_NTLM "ntlm" #define OIDC_PROXY_AUTH_ANY "any" #ifdef CURLAUTH_NEGOTIATE #define OIDC_PROXY_AUTH_NEGOTIATE "negotiate" #endif const char *oidc_parse_outgoing_proxy_auth_type(apr_pool_t *pool, const char *arg, unsigned long *auth_type) { static char *options[] = {OIDC_PROXY_AUTH_BASIC, OIDC_PROXY_AUTH_DIGEST, OIDC_PROXY_AUTH_NTLM, OIDC_PROXY_AUTH_ANY, #ifdef CURLAUTH_NEGOTIATE OIDC_PROXY_AUTH_NEGOTIATE, #endif NULL}; const char *rv = oidc_valid_string_option(pool, arg, options); if (rv != NULL) return rv; if (_oidc_strcmp(arg, OIDC_PROXY_AUTH_BASIC) == 0) { *auth_type = CURLAUTH_BASIC; } else if (_oidc_strcmp(arg, OIDC_PROXY_AUTH_DIGEST) == 0) { *auth_type = CURLAUTH_DIGEST; } else if (_oidc_strcmp(arg, OIDC_PROXY_AUTH_NTLM) == 0) { *auth_type = CURLAUTH_NTLM; } else if (_oidc_strcmp(arg, OIDC_PROXY_AUTH_ANY) == 0) { *auth_type = CURLAUTH_ANY; #ifdef CURLAUTH_NEGOTIATE } else if (_oidc_strcmp(arg, OIDC_PROXY_AUTH_NEGOTIATE) == 0) { *auth_type = CURLAUTH_NEGOTIATE; #endif } return NULL; } mod_auth_openidc-2.4.15.1/src/parse.h000066400000000000000000000175401455620533500173340ustar00rootroot00000000000000/* * 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-2024 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. * * Validation and parsing of configuration values. * * @Author: Hans Zandbelt - hans.zandbelt@openidc.com */ #ifndef MOD_AUTH_OPENIDC_PARSE_H_ #define MOD_AUTH_OPENIDC_PARSE_H_ #include "cache/cache.h" #include "const.h" #include #include #define OIDC_CONFIG_STRING_UNSET "_UNSET_" #define OIDC_CONFIG_STRING_EMPTY "" #define OIDC_CONFIG_POS_INT_UNSET -1 #define OIDC_CLAIM_FORMAT_RELATIVE "relative" #define OIDC_CLAIM_FORMAT_ABSOLUTE "absolute" #define OIDC_CLAIM_REQUIRED_MANDATORY "mandatory" #define OIDC_CLAIM_REQUIRED_OPTIONAL "optional" #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_PASS_USERINFO_AS_CLAIMS_STR "claims" const char *oidc_valid_url(apr_pool_t *pool, const char *arg, const char *scheme); const char *oidc_valid_http_url(apr_pool_t *pool, const char *arg); const char *oidc_valid_dir(apr_pool_t *pool, const char *arg); const char *oidc_valid_cookie_domain(apr_pool_t *pool, const char *arg); const char *oidc_valid_endpoint_auth_method(apr_pool_t *pool, const char *arg); const char *oidc_valid_endpoint_auth_method_no_private_key(apr_pool_t *pool, const char *arg); const char *oidc_valid_response_type(apr_pool_t *pool, const char *arg); const char *oidc_valid_pkce_method(apr_pool_t *pool, const char *arg); const char *oidc_valid_response_mode(apr_pool_t *pool, const char *arg); const char *oidc_valid_signed_response_alg(apr_pool_t *pool, const char *arg); const char *oidc_valid_encrypted_response_alg(apr_pool_t *pool, const char *arg); const char *oidc_valid_encrypted_response_enc(apr_pool_t *pool, const char *arg); const char *oidc_valid_claim_format(apr_pool_t *pool, const char *arg); const char *oidc_valid_introspection_method(apr_pool_t *pool, const char *arg); const char *oidc_valid_session_max_duration(apr_pool_t *pool, int v); const char *oidc_valid_jwks_refresh_interval(apr_pool_t *pool, int v); const char *oidc_valid_idtoken_iat_slack(apr_pool_t *pool, int v); const char *oidc_valid_userinfo_refresh_interval(apr_pool_t *pool, int v); const char *oidc_valid_userinfo_token_method(apr_pool_t *pool, const char *arg); const char *oidc_valid_auth_request_method(apr_pool_t *pool, const char *arg); const char *oidc_valid_max_number_of_state_cookies(apr_pool_t *pool, int v); const char *oidc_parse_int(apr_pool_t *pool, const char *arg, int *int_value); const char *oidc_parse_boolean(apr_pool_t *pool, const char *arg, int *bool_value); typedef struct oidc_pass_user_info_as_t { int type; char *name; } oidc_pass_user_info_as_t; const char *oidc_parse_cache_type(apr_pool_t *pool, const char *arg, oidc_cache_t **type); const char *oidc_parse_session_type(apr_pool_t *pool, const char *arg, int *type, int *persistent, apr_byte_t *store_id_token); const char *oidc_parse_cache_shm_entry_size_max(apr_pool_t *pool, const char *arg, int *int_value); const char *oidc_parse_session_inactivity_timeout(apr_pool_t *pool, const char *arg, int *int_value); const char *oidc_parse_session_max_duration(apr_pool_t *pool, const char *arg, int *int_value); const char *oidc_parse_use_enc_kid_key_tuple(apr_pool_t *pool, const char *tuple, char **kid, char **key, int *key_len, char **use, apr_byte_t triplet); const char *oidc_parse_pass_idtoken_as(apr_pool_t *pool, const char *v1, const char *v2, const char *v3, int *int_value); const char *oidc_parse_pass_userinfo_as(apr_pool_t *pool, const char *v, oidc_pass_user_info_as_t **result); const char *oidc_parse_action_on_error_refresh_as(apr_pool_t *pool, const char *v1, int *int_value); const char *oidc_parse_accept_oauth_token_in(apr_pool_t *pool, const char *arg, int *b_value, apr_hash_t *list_options); const char *oidc_accept_oauth_token_in2str(apr_pool_t *pool, apr_byte_t v); const char *oidc_parse_claim_required(apr_pool_t *pool, const char *arg, int *is_required); const char *oidc_parse_set_claims_as(apr_pool_t *pool, const char *arg, int *in_headers, int *in_env_vars); const char *oidc_parse_pass_claims_as_encoding(apr_pool_t *pool, const char *arg, int *pass_as); const char *oidc_parse_unauth_action(apr_pool_t *pool, const char *arg, int *action); const char *oidc_parse_unautz_action(apr_pool_t *pool, const char *arg, int *action); const char *oidc_parse_jwks_refresh_interval(apr_pool_t *pool, const char *arg, int *int_value); const char *oidc_parse_idtoken_iat_slack(apr_pool_t *pool, const char *arg, int *int_value); const char *oidc_parse_userinfo_refresh_interval(apr_pool_t *pool, const char *arg, int *int_value); const char *oidc_parse_userinfo_token_method(apr_pool_t *pool, const char *arg, int *int_value); const char *oidc_parse_info_hook_data(apr_pool_t *pool, const char *arg, apr_hash_t **hook_data); const char *oidc_parse_auth_request_method(apr_pool_t *pool, const char *arg, int *method); const char *oidc_parse_max_number_of_state_cookies(apr_pool_t *pool, const char *arg1, const char *arg2, int *int_value, int *bool_value); const char *oidc_parse_refresh_access_token_before_expiry(apr_pool_t *pool, const char *arg, int *int_value); const char *oidc_parse_set_state_input_headers_as(apr_pool_t *pool, const char *arg, apr_byte_t *state_input_headers); const char *oidc_parse_x_forwarded_headers(apr_pool_t *pool, const char *arg, apr_byte_t *x_forwarded_headers); const char *oidc_parse_outgoing_proxy_auth_type(apr_pool_t *pool, const char *arg, unsigned long *auth_type); const char *oidc_parse_trace_parent(apr_pool_t *pool, const char *arg, int *trace_parent); typedef const char *(*oidc_valid_int_function_t)(apr_pool_t *, int); typedef const char *(*oidc_valid_function_t)(apr_pool_t *, const char *); const char *oidc_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); #endif /* MOD_AUTH_OPENIDC_PARSE_H_ */ mod_auth_openidc-2.4.15.1/src/pcre_subst.c000066400000000000000000000236111455620533500203620ustar00rootroot00000000000000/************************************************* * 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.15.1/src/pcre_subst.h000066400000000000000000000042311455620533500203640ustar00rootroot00000000000000/************************************************* * 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.15.1/src/proto.c000066400000000000000000003125731455620533500173640ustar00rootroot00000000000000/* * 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-2024 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 "mod_auth_openidc.h" #include "metrics.h" #include #include #ifdef USE_URANDOM #include #include #include #define DEV_RANDOM "/dev/urandom" #endif extern module AP_MODULE_DECLARE_DATA auth_openidc_module; /* * generate a random string value value of a specified length */ apr_byte_t oidc_proto_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_base64url_encode(r, output, (const char *)bytes, len, TRUE) <= 0) { oidc_error(r, "oidc_base64url_encode returned an error"); return FALSE; } return TRUE; } #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 wether 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_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_proto_copy_req_ctx_t { request_rec *r; json_t *request_object_config; oidc_jwt_t *request_object; apr_table_t *params2; } oidc_proto_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_proto_copy_from_request(void *rec, const char *name, const char *value) { oidc_proto_copy_req_ctx_t *ctx = (oidc_proto_copy_req_ctx_t *)rec; oidc_debug(ctx->r, "processing name: %s, value: %s", name, value); if (oidc_proto_param_needs_action(ctx->request_object_config, name, OIDC_REQUEST_OJBECT_COPY_FROM_REQUEST) || oidc_proto_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_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_proto_delete_from_request(void *rec, const char *name, const char *value) { oidc_proto_copy_req_ctx_t *ctx = (oidc_proto_copy_req_ctx_t *)rec; oidc_debug(ctx->r, "deleting from query parameters: name: %s, value: %s", name, value); if (oidc_proto_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 */ apr_byte_t oidc_proto_get_encryption_jwk_by_type(request_rec *r, oidc_cfg *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, &provider->jwks_uri, provider->ssl_validate_server, &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_JWK_KEYS); if ((keys == NULL) || !(json_is_array(keys))) { oidc_error(r, "\"%s\" array element is not a JSON array", OIDC_JWK_KEYS); 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_proto_create_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 *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(provider->client_id)); json_object_set_new(request_object->payload.value.json, OIDC_CLAIM_AUD, json_string(provider->issuer)); 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_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_proto_copy_req_ctx_t data = {r, request_object_config, request_object, delete_from_query_params}; apr_table_do(oidc_proto_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_proto_delete_from_request, &data, delete_from_query_params, NULL); /* debug logging */ oidc_debug(r, "request object: %s", oidc_util_encode_json_object(r, request_object->payload.value.json, 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_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 ((provider->client_keys != NULL) || (cfg->private_keys != NULL)) { sjwk = provider->client_keys ? oidc_util_key_list_first(provider->client_keys, kty, OIDC_JOSE_JWK_SIG_STR) : oidc_util_key_list_first(cfg->private_keys, 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, provider->client_secret, 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_json_object_get_string(r->pool, crypto, "crypt_alg", &jwe->header.alg, NULL); oidc_json_object_get_string(r->pool, crypto, "crypt_enc", &jwe->header.enc, NULL); char *cser = oidc_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_proto_get_encryption_jwk_by_type(r, cfg, provider, oidc_jwt_alg2kty(jwe), &ejwk); break; case CJOSE_JWK_KTY_OCT: oidc_util_create_symmetric_key(r, provider->client_secret, 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_peek_jwt_header(r, serialized_request_object, NULL, NULL, NULL)); oidc_debug(r, "serialized request object = \"%s\"", serialized_request_object); return serialized_request_object; } /* * generate a request object and pass it by reference in the authorization request */ static char *oidc_proto_create_request_uri(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_proto_create_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_proto_generate_random_string(r, &request_ref, 16) == 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_util_escape_string(r, request_ref)); } } return request_uri; } /* * Generic function to generate request/request_object parameter with value */ static void oidc_proto_add_request_param(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, provider->request_object, &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_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_create_request_uri(r, provider, request_object_config, redirect_uri, params, ttl); apr_table_set(params, OIDC_PROTO_REQUEST_URI, value); } else { /* parameter is "request" */ value = oidc_proto_create_request_object(r, provider, request_object_config, params, ttl); apr_table_set(params, OIDC_PROTO_REQUEST_OBJECT, value); } } /* 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_add_form_post_param(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_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_add_form_post_param, &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); } void add_auth_request_params(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_get_request_parameter(r, key, &val); apr_table_add(params, key, val); } } } /* * send an OpenID Connect authorization request to the specified provider */ int oidc_proto_authorization_request(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", provider->issuer, 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 = provider->scope; 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, provider->scope); } apr_table_setn(params, OIDC_PROTO_SCOPE, scope); } if (provider->client_id == 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, provider->client_id); /* 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) && (provider->pkce != NULL)) { apr_table_setn(params, OIDC_PROTO_CODE_CHALLENGE, code_challenge); apr_table_setn(params, OIDC_PROTO_CODE_CHALLENGE_METHOD, provider->pkce->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 */ add_auth_request_params(r, params, provider->auth_request_params); /* add any dynamically configured custom authorization request parameters */ add_auth_request_params(r, params, auth_request_params); /* add request parameter (request or request_uri) if set */ if (provider->request_object != NULL) oidc_proto_add_request_param(r, provider, redirect_uri, params); /* send the full authentication request via POST or GET */ if (provider->auth_request_method == OIDC_AUTH_REQUEST_METHOD_POST) { /* construct a HTML POST auto-submit page with the authorization request parameters */ rv = oidc_proto_html_post(r, provider->authorization_endpoint_url, params); } else if (provider->auth_request_method == OIDC_AUTH_REQUEST_METHOD_GET) { /* construct the full authorization request URL */ authorization_request = oidc_util_http_query_encoded_url(r, provider->authorization_endpoint_url, 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_post_preserve_javascript(r, authorization_request, NULL, NULL) == FALSE) { /* add the redirect location header */ oidc_util_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, "provider->auth_request_method set to wrong value: %d", provider->auth_request_method); return HTTP_INTERNAL_SERVER_ERROR; } /* cleanup */ oidc_proto_state_destroy(proto_state); /* no cache */ oidc_util_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; } /* * indicate whether the incoming HTTP POST request is an OpenID Connect Authorization Response */ apr_byte_t oidc_proto_is_post_authorization_response(request_rec *r, oidc_cfg *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_is_redirect_authorization_response(request_rec *r, oidc_cfg *cfg) { /* prereq: this is a call to the configured redirect_uri; see if it is a GET with state and id_token or code * parameters */ return ((r->method_number == M_GET) && oidc_util_request_has_parameter(r, OIDC_PROTO_STATE) && (oidc_util_request_has_parameter(r, OIDC_PROTO_ID_TOKEN) || oidc_util_request_has_parameter(r, OIDC_PROTO_CODE))); } /* * 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_proto_generate_random_string(r, nonce, len); } /* * PCKE "plain" proto state */ static apr_byte_t oidc_proto_pkce_state_plain(request_rec *r, char **state) { return oidc_proto_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_proto_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}; #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" 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; } 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)); } oidc_proto_state_t *oidc_proto_state_new() { return json_object(); } void oidc_proto_state_destroy(oidc_proto_state_t *proto_state) { json_decref(proto_state); } apr_byte_t oidc_proto_check_crypto_passphrase(request_rec *r, oidc_cfg *c, const char *action) { if (c->crypto_passphrase.secret1 == 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; } oidc_proto_state_t *oidc_proto_state_from_cookie(request_rec *r, oidc_cfg *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, &c->crypto_passphrase, cookieValue, &s_payload); oidc_util_decode_json_object(r, s_payload, &result); return result; } char *oidc_proto_state_to_cookie(request_rec *r, oidc_cfg *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, &c->crypto_passphrase, oidc_util_encode_json_object(r, proto_state, JSON_COMPACT), &cookieValue); return cookieValue; } char *oidc_proto_state_to_string(request_rec *r, oidc_proto_state_t *proto_state) { return oidc_util_encode_json_object(r, proto_state, JSON_COMPACT); } 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); } 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); } 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; } 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); } 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); } 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); } 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); } 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); } 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); } 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); } 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); } 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); } 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); } 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); } 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); } 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); } 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); } 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); } 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); } 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()))); } /* * 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_validate_nonce(request_rec *r, oidc_cfg *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(provider->idtoken_iat_slack * 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; } /* * validate the "aud" and "azp" claims in the id_token payload */ apr_byte_t oidc_proto_validate_aud_and_azp(request_rec *r, oidc_cfg *cfg, oidc_provider_t *provider, oidc_jwt_payload_t *id_token_payload) { char *azp = NULL; 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, provider->client_id) != 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, provider->client_id); 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) { /* check if it is a single-value */ if (json_is_string(aud)) { /* a single-valued audience must be equal to our client_id */ if (_oidc_strcmp(json_string_value(aud), provider->client_id) != 0) { oidc_error(r, "the configured client_id (%s) did not match the \"%s\" claim value (%s) in " "the id_token", provider->client_id, OIDC_CLAIM_AUD, json_string_value(aud)); return FALSE; } /* check if this is a multi-valued audience */ } else if (json_is_array(aud)) { if ((json_array_size(aud) > 1) && (azp == NULL)) { oidc_debug(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, provider->client_id) == FALSE) { oidc_error(r, "our configured client_id (%s) could not be found in the array of values " "for \"%s\" claim", provider->client_id, 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; } /* * 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_validate_jwt(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; } /* * 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 *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_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_validate_jwt(r, jwt, provider->validate_issuer ? provider->issuer : NULL, TRUE, TRUE, provider->idtoken_iat_slack) == 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_validate_aud_and_azp(r, cfg, provider, &jwt->payload) == FALSE) return FALSE; return TRUE; } /* * get the key from the JWKs that corresponds with the key specified in the header */ static apr_byte_t oidc_proto_get_key_from_jwks(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_JWK_KEYS); if ((keys == NULL) || !(json_is_array(keys))) { oidc_error(r, "\"%s\" array element is not a JSON array", OIDC_JWK_KEYS); 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_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_get_keys_from_jwks_uri(request_rec *r, oidc_cfg *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_get_key_from_jwks(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_get_keys_from_jwks_uri(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; } /* * 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 *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_get_keys_from_jwks_uri(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_get_keys_from_jwks_uri(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_peek_jwt_header(request_rec *r, const char *compact_encoded_jwt, char **alg, char **enc, char **kid) { char *input = NULL, *result = NULL; char *p = 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_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; } /* * check whether the provided string is a valid id_token and return its parsed contents */ apr_byte_t oidc_proto_parse_idtoken(request_rec *r, oidc_cfg *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_peek_jwt_header(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, provider->client_secret, oidc_alg2keysize(alg), OIDC_JOSE_ALG_SHA256, TRUE, &jwk) == FALSE) return FALSE; decryption_keys = oidc_util_merge_symmetric_key(r->pool, cfg->private_keys, jwk); if (provider->client_keys) decryption_keys = oidc_util_merge_key_sets(r->pool, decryption_keys, provider->client_keys); 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, provider->client_secret, 0, NULL, TRUE, &jwk) == FALSE) { oidc_jwt_destroy(*jwt); *jwt = NULL; return FALSE; } if (oidc_proto_jwt_verify(r, cfg, *jwt, &provider->jwks_uri, provider->ssl_validate_server, oidc_util_merge_symmetric_key(r->pool, provider->verify_public_keys, jwk), provider->id_token_signed_response_alg) == 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; } /* * 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 */ if ((token_type != NULL) && (_oidc_strnatcasecmp(token_type, OIDC_PROTO_BEARER) != 0) && (provider->userinfo_endpoint_url != NULL)) { oidc_error(r, "token_type is \"%s\" and UserInfo endpoint (%s) for issuer \"%s\" is set: can only deal " "with \"%s\" authentication against a UserInfo endpoint!", token_type, provider->userinfo_endpoint_url, provider->issuer, OIDC_PROTO_BEARER); return FALSE; } return TRUE; } /* * 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_util_escape_string(r, client_id), oidc_util_escape_string(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; } #define OIDC_PROTO_ASSERTION_JTI_LEN 16 /* * 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_proto_generate_random_string(r, &jti, OIDC_PROTO_ASSERTION_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) { oidc_jose_error_t err; if (oidc_jwt_sign(r->pool, jwt, jwk, FALSE, &err) == FALSE) { oidc_error(r, "signing JWT failed: %s", oidc_jose_e2s(r->pool, err)); return FALSE; } char *cser = oidc_jwt_serialize(r->pool, jwt, &err); if (cser == NULL) { oidc_error(r, "oidc_jwt_serialize failed: %s", oidc_jose_e2s(r->pool, err)); 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 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; } static apr_byte_t oidc_proto_endpoint_access_token_bearer(request_rec *r, oidc_cfg *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 static apr_byte_t oidc_proto_endpoint_auth_private_key_jwt(request_rec *r, oidc_cfg *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 ((cfg->private_keys != NULL) && (cfg->private_keys->nelts > 0)) { jwk = oidc_util_key_list_first(cfg->private_keys, CJOSE_JWK_KTY_RSA, OIDC_JOSE_JWK_SIG_STR); jwk_pub = oidc_util_key_list_first(cfg->public_keys, CJOSE_JWK_KTY_RSA, OIDC_JOSE_JWK_SIG_STR); if (jwk_pub && jwk_pub->x5t) // populate x5t; at least required for 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; } apr_byte_t oidc_proto_token_endpoint_auth(request_rec *r, oidc_cfg *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; } /* * send a code/refresh request to the token endpoint and return the parsed contents */ static apr_byte_t oidc_proto_token_endpoint_request(request_rec *r, oidc_cfg *cfg, oidc_provider_t *provider, apr_table_t *params, char **id_token, char **access_token, char **token_type, int *expires_in, char **refresh_token) { char *response = NULL; char *basic_auth = NULL; char *bearer_auth = NULL; /* add the token endpoint authentication credentials */ if (oidc_proto_token_endpoint_auth(r, cfg, provider->token_endpoint_auth, provider->client_id, provider->client_secret, provider->client_keys, provider->token_endpoint_url, params, NULL, &basic_auth, &bearer_auth) == FALSE) return FALSE; /* add any configured extra static parameters to the token endpoint */ oidc_util_table_add_query_encoded_params(r->pool, params, provider->token_endpoint_params); /* send the refresh request to the token endpoint */ if (oidc_util_http_post_form(r, provider->token_endpoint_url, params, basic_auth, bearer_auth, provider->ssl_validate_server, &response, &cfg->http_timeout_long, &cfg->outgoing_proxy, oidc_dir_cfg_pass_cookies(r), oidc_util_get_full_path(r->pool, provider->token_endpoint_tls_client_cert), oidc_util_get_full_path(r->pool, provider->token_endpoint_tls_client_key), provider->token_endpoint_tls_client_key_pwd) == FALSE) { oidc_warn(r, "error when calling the token endpoint (%s)", provider->token_endpoint_url); return FALSE; } /* check for errors, the response itself will have been logged already */ json_t *result = NULL; if (oidc_util_decode_json_and_check_error(r, response, &result) == FALSE) return FALSE; /* get the id_token from the parsed response */ oidc_json_object_get_string(r->pool, result, OIDC_PROTO_ID_TOKEN, id_token, NULL); /* get the access_token from the parsed response */ oidc_json_object_get_string(r->pool, result, OIDC_PROTO_ACCESS_TOKEN, access_token, NULL); /* get the token type from the parsed response */ oidc_json_object_get_string(r->pool, result, OIDC_PROTO_TOKEN_TYPE, token_type, NULL); /* 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 did not validate, dropping it"); *access_token = NULL; } } /* get the expires_in value */ oidc_json_object_get_int(result, OIDC_PROTO_EXPIRES_IN, expires_in, -1); /* get the refresh_token from the parsed response */ oidc_json_object_get_string(r->pool, result, OIDC_PROTO_REFRESH_TOKEN, refresh_token, NULL); json_decref(result); 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 *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_get_redirect_uri_iss(r, cfg, provider)); 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 ((cfg->metadata_dir != 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); } /* * refreshes the access_token/id_token /refresh_token received from the OP using the refresh_token */ apr_byte_t oidc_proto_refresh_request(request_rec *r, oidc_cfg *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, provider->scope); return oidc_proto_token_endpoint_request(r, cfg, provider, params, id_token, access_token, token_type, expires_in, refresh_token); } static apr_byte_t oidc_user_info_response_validate(request_rec *r, oidc_cfg *cfg, oidc_provider_t *provider, char **response, json_t **claims, char **userinfo_jwt) { oidc_debug(r, "enter: userinfo_signed_response_alg=%s, userinfo_encrypted_response_alg=%s, " "userinfo_encrypted_response_enc=%s", provider->userinfo_signed_response_alg, provider->userinfo_encrypted_response_alg, provider->userinfo_encrypted_response_enc); char *alg = NULL; if ((provider->userinfo_signed_response_alg != NULL) || (provider->userinfo_encrypted_response_alg != NULL) || (provider->userinfo_encrypted_response_enc != NULL)) { oidc_debug(r, "JWT header=%s", oidc_proto_peek_jwt_header(r, *response, &alg, NULL, NULL)); } oidc_jose_error_t err; oidc_jwk_t *jwk = NULL; oidc_jwt_t *jwt = NULL; char *payload = NULL; if (oidc_util_create_symmetric_key(r, provider->client_secret, oidc_alg2keysize(alg), OIDC_JOSE_ALG_SHA256, TRUE, &jwk) == FALSE) return FALSE; if (provider->userinfo_encrypted_response_alg != NULL) { if (oidc_jwe_decrypt(r->pool, *response, oidc_util_merge_symmetric_key(r->pool, cfg->private_keys, jwk), &payload, NULL, &err, TRUE) == FALSE) { oidc_error(r, "oidc_jwe_decrypt failed: %s", oidc_jose_e2s(r->pool, err)); oidc_jwk_destroy(jwk); return FALSE; } else { oidc_debug(r, "successfully decrypted JWE returned from userinfo endpoint: %s", payload); *response = payload; } } if (provider->userinfo_signed_response_alg != NULL) { if (oidc_jwt_parse(r->pool, *response, &jwt, oidc_util_merge_symmetric_key(r->pool, cfg->private_keys, jwk), FALSE, &err) == FALSE) { oidc_error(r, "oidc_jwt_parse failed: %s", oidc_jose_e2s(r->pool, err)); oidc_jwt_destroy(jwt); oidc_jwk_destroy(jwk); return FALSE; } oidc_debug(r, "successfully parsed JWT with header=%s, and payload=%s", jwt->header.value.str, jwt->payload.value.str); oidc_jwk_destroy(jwk); jwk = NULL; if (oidc_util_create_symmetric_key(r, provider->client_secret, 0, NULL, TRUE, &jwk) == FALSE) return FALSE; if (oidc_proto_jwt_verify(r, cfg, jwt, &provider->jwks_uri, provider->ssl_validate_server, oidc_util_merge_symmetric_key(r->pool, NULL, jwk), provider->userinfo_signed_response_alg) == FALSE) { oidc_error(r, "JWT signature could not be validated, aborting"); oidc_jwt_destroy(jwt); oidc_jwk_destroy(jwk); return FALSE; } oidc_jwk_destroy(jwk); 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); oidc_jwt_destroy(jwt); return TRUE; } oidc_jwk_destroy(jwk); return oidc_util_decode_json_and_check_error(r, *response, claims); } #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" static apr_byte_t oidc_proto_resolve_composite_claims(request_rec *r, oidc_cfg *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_util_http_get(r, endpoint, NULL, NULL, access_token, cfg->provider.ssl_validate_server, &s_json, &cfg->http_timeout_long, &cfg->outgoing_proxy, oidc_dir_cfg_pass_cookies(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, cfg->private_keys, 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; } /* * get claims from the OP UserInfo endpoint using the provided access_token */ apr_byte_t oidc_proto_resolve_userinfo(request_rec *r, oidc_cfg *cfg, oidc_provider_t *provider, const char *id_token_sub, const char *access_token, char **response, char **userinfo_jwt) { oidc_debug(r, "enter, endpoint=%s, access_token=%s", provider->userinfo_endpoint_url, access_token); OIDC_METRICS_TIMING_START(r, cfg); /* get the JSON response */ if (provider->userinfo_token_method == OIDC_USER_INFO_TOKEN_METHOD_HEADER) { if (oidc_util_http_get(r, provider->userinfo_endpoint_url, NULL, NULL, access_token, provider->ssl_validate_server, response, &cfg->http_timeout_long, &cfg->outgoing_proxy, oidc_dir_cfg_pass_cookies(r), NULL, NULL, NULL) == FALSE) { OIDC_METRICS_COUNTER_INC(r, cfg, OM_PROVIDER_USERINFO_ERROR); return FALSE; } } else if (provider->userinfo_token_method == 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_util_http_post_form(r, provider->userinfo_endpoint_url, params, NULL, NULL, provider->ssl_validate_server, response, &cfg->http_timeout_long, &cfg->outgoing_proxy, oidc_dir_cfg_pass_cookies(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", provider->userinfo_token_method); return FALSE; } OIDC_METRICS_TIMING_ADD(r, cfg, OM_PROVIDER_USERINFO); json_t *claims = NULL; if (oidc_user_info_response_validate(r, cfg, provider, response, &claims, userinfo_jwt) == FALSE) return FALSE; if (oidc_proto_resolve_composite_claims(r, cfg, claims) == TRUE) *response = oidc_util_encode_json_object(r, claims, JSON_PRESERVE_ORDER | JSON_COMPACT); char *user_info_sub = NULL; oidc_jose_get_string(r->pool, claims, 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); json_decref(claims); return FALSE; } 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); json_decref(claims); return FALSE; } } json_decref(claims); return TRUE; } /* * 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 *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_util_http_get(r, url, params, NULL, NULL, cfg->provider.ssl_validate_server, &response, &cfg->http_timeout_short, &cfg->outgoing_proxy, oidc_dir_cfg_pass_cookies(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_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_account_based_discovery(request_rec *r, oidc_cfg *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_url_based_discovery(request_rec *r, oidc_cfg *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); } int oidc_proto_javascript_implicit(request_rec *r, oidc_cfg *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); } /* * 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_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 c_hash value in the id_token against the code */ apr_byte_t oidc_proto_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 the at_hash value in the id_token against the access_token */ apr_byte_t oidc_proto_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; } /* * 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; } /* * 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, 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; } } 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, 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, 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 *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_parse_idtoken(r, c, provider, id_token, nonce, jwt, is_code_flow) == FALSE) return FALSE; if ((must_validate_code == TRUE) && (oidc_proto_validate_code(r, provider, *jwt, response_type, code) == FALSE)) { oidc_jwt_destroy(*jwt); *jwt = NULL; return FALSE; } return TRUE; } /* * 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 *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 (provider->pkce != NULL) provider->pkce->verifier(r, oidc_proto_state_get_pkce_state(proto_state), &code_verifier); const char *state = oidc_proto_state_get_state(proto_state); OIDC_METRICS_TIMING_START(r, c); 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; } OIDC_METRICS_TIMING_ADD(r, c, OM_PROVIDER_TOKEN); 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_authorization_response_code_idtoken(request_rec *r, oidc_cfg *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, provider->issuer, provider->client_id) == 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_handle_authorization_response_code_token(request_rec *r, oidc_cfg *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, provider->issuer, provider->client_id) == 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_handle_authorization_response_code(request_rec *r, oidc_cfg *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, provider->issuer, provider->client_id) == 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_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 *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, provider->issuer, provider->client_id) == 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_authorization_response_code_idtoken_token(request_rec *r, oidc_cfg *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_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_handle_authorization_response_idtoken_token(request_rec *r, oidc_cfg *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_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_handle_authorization_response_idtoken(request_rec *r, oidc_cfg *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.15.1/src/session.c000066400000000000000000000574021455620533500177010ustar00rootroot00000000000000/* * 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-2024 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 "mod_auth_openidc.h" extern module AP_MODULE_DECLARE_DATA auth_openidc_module; /* 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" static apr_byte_t oidc_session_encode(request_rec *r, oidc_cfg *c, oidc_session_t *z, char **s_value, apr_byte_t encrypt) { if (encrypt == FALSE) { *s_value = oidc_util_encode_json_object(r, z->state, JSON_COMPACT); return (*s_value != NULL); } else if (c->crypto_passphrase.secret1 == NULL) { oidc_error(r, "cannot encrypt session state because " OIDCCryptoPassphrase " is not set"); return FALSE; } if (oidc_util_jwt_create(r, &c->crypto_passphrase, oidc_util_encode_json_object(r, z->state, JSON_COMPACT), s_value) == FALSE) return FALSE; return TRUE; } static apr_byte_t oidc_session_decode(request_rec *r, oidc_cfg *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 (c->crypto_passphrase.secret1 == NULL) { oidc_error(r, "cannot decrypt session state because " OIDCCryptoPassphrase " is not set"); return FALSE; } if (oidc_util_jwt_verify(r, &c->crypto_passphrase, 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_proto_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; } } apr_byte_t oidc_session_load_cache_by_uuid(request_rec *r, oidc_cfg *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 *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_util_get_cookie(r, oidc_cfg_dir_cookie(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_util_set_cookie(r, oidc_cfg_dir_cookie(r), "", 0, OIDC_COOKIE_EXT_SAME_SITE_NONE(c, r)); } } return rc; } /* * 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 *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_util_set_cookie( r, oidc_cfg_dir_cookie(r), z->uuid, c->persistent_session_cookie ? z->expiry : -1, c->cookie_same_site ? (first_time ? OIDC_COOKIE_EXT_SAME_SITE_LAX : OIDC_COOKIE_EXT_SAME_SITE_STRICT) : OIDC_COOKIE_EXT_SAME_SITE_NONE(c, r)); } else { if (z->sid != NULL) oidc_cache_set_sid(r, z->sid, NULL, 0); /* clear the cookie */ oidc_util_set_cookie(r, oidc_cfg_dir_cookie(r), "", 0, OIDC_COOKIE_EXT_SAME_SITE_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 *c, oidc_session_t *z) { char *cookieValue = oidc_util_get_chunked_cookie(r, oidc_cfg_dir_cookie(r), c->session_cookie_chunk_size); 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 *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_util_set_chunked_cookie( r, oidc_cfg_dir_cookie(r), cookieValue, c->persistent_session_cookie ? z->expiry : -1, c->session_cookie_chunk_size, (z->state == NULL) ? OIDC_COOKIE_EXT_SAME_SITE_NONE(c, r) : c->cookie_same_site ? (first_time ? OIDC_COOKIE_EXT_SAME_SITE_LAX : OIDC_COOKIE_EXT_SAME_SITE_STRICT) : OIDC_COOKIE_EXT_SAME_SITE_NONE(c, r)); return TRUE; } apr_byte_t oidc_session_extract(request_rec *r, oidc_session_t *z) { apr_byte_t rc = FALSE; if (z->state == NULL) goto out; json_t *j_expires = json_object_get(z->state, OIDC_SESSION_EXPIRY_KEY); if (j_expires) z->expiry = apr_time_from_sec(json_integer_value(j_expires)); /* check whether it has expired */ 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 *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 (c->session_type == 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 ((c->session_type == OIDC_SESSION_TYPE_CLIENT_COOKIE) || ((rc == FALSE) && oidc_cfg_session_cache_fallback_to_cookie(r))) /* 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; } /* * 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 *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); json_object_set_new(z->state, OIDC_SESSION_EXPIRY_KEY, json_integer(apr_time_sec(z->expiry))); oidc_session_set(r, z, OIDC_SESSION_SESSION_ID, z->uuid); } if (c->session_type == 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 ((c->session_type == OIDC_SESSION_TYPE_CLIENT_COOKIE) || ((rc == FALSE) && oidc_cfg_session_cache_fallback_to_cookie(r))) /* 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_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 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" /* * helper functions */ typedef const char *(*oidc_session_get_str_function)(request_rec *r, oidc_session_t *z); 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(r, z, key, apr_psprintf(r->pool, "%" APR_TIME_T_FMT, apr_time_sec(timestamp))); } 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; } static apr_time_t oidc_session_get_key2timestamp(request_rec *r, oidc_session_t *z, const char *key) { apr_time_t t_expires = 0; const char *s_expires = oidc_session_get_key2string(r, z, key); if (s_expires != NULL) sscanf(s_expires, "%" APR_TIME_T_FMT, &t_expires); return apr_time_from_sec(t_expires); } #define OIDC_SESSION_WARN_CLAIM_SIZE 1024 * 8 #define OIDC_SESSION_WARN_CLAIM_SIZE_VAR "OIDC_SESSION_WARN_CLAIM_SIZE" void oidc_session_set_filtered_claims(request_rec *r, oidc_session_t *z, const char *session_key, const char *claims) { oidc_cfg *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_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 ((c->black_listed_claims != NULL) && (apr_hash_get(c->black_listed_claims, name, APR_HASH_KEY_STRING) != NULL)) { oidc_debug(r, "removing blacklisted claim [%s]: '%s'", session_key, name); is_allowed = FALSE; } if ((is_allowed == TRUE) && (c->white_listed_claims != NULL) && (apr_hash_get(c->white_listed_claims, 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_object(r, value, 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); } iter = json_object_iter_next(src, iter); } const char *filtered_claims = oidc_util_encode_json_object(r, dst, JSON_COMPACT); filtered_claims = oidc_util_jq_filter(r, filtered_claims, oidc_util_apr_expr_exec(r, c->filter_claims_expr, 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 expires */ void oidc_session_set_access_token_expires(request_rec *r, oidc_session_t *z, const int expires_in) { if (expires_in != -1) { oidc_session_set(r, z, OIDC_SESSION_KEY_ACCESSTOKEN_EXPIRES, apr_psprintf(r->pool, "%" APR_TIME_T_FMT, apr_time_sec(apr_time_now()) + expires_in)); } } const char *oidc_session_get_access_token_expires(request_rec *r, oidc_session_t *z) { return oidc_session_get_key2string(r, z, OIDC_SESSION_KEY_ACCESSTOKEN_EXPIRES); } /* * 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_timestamp(r, z, OIDC_SESSION_KEY_USERINFO_REFRESH_INTERVAL, apr_time_from_sec(interval)); } apr_time_t oidc_session_get_userinfo_refresh_interval(request_rec *r, oidc_session_t *z) { return oidc_session_get_key2timestamp(r, z, OIDC_SESSION_KEY_USERINFO_REFRESH_INTERVAL); } 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_reset_access_token_last_refresh(request_rec *r, oidc_session_t *z) { oidc_session_set_timestamp(r, z, OIDC_SESSION_KEY_ACCESS_TOKEN_LAST_REFRESH, apr_time_now()); } 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); } mod_auth_openidc-2.4.15.1/src/util.c000066400000000000000000002756471455620533500172100ustar00rootroot00000000000000/* * 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-2024 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 "mod_auth_openidc.h" #include "metrics.h" #include "pcre_subst.h" #include #ifndef WIN32 #include #endif #ifdef USE_LIBJQ #include "jq.h" #endif /* hrm, should we get rid of this by adding parameters to the (3) functions? */ extern module AP_MODULE_DECLARE_DATA auth_openidc_module; 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; } 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; } apr_byte_t oidc_proto_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; } /* * base64url encode a string */ int oidc_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; } /* * base64url decode a string */ int oidc_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 = apr_base64_decode_len(dec); *dst = apr_palloc(pool, dlen); return apr_base64_decode(*dst, dec); } static const char *oidc_util_get__oidc_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 = 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; } 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" 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" 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); } 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_get__oidc_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; } 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; oidc_jwt_t *jwt = 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_get__oidc_jwt_hdr_dir_a256gcm(r, NULL), compact_encoded_jwt, NULL); oidc_proto_peek_jwt_header(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); if (jwt != NULL) oidc_jwt_destroy(jwt); return rv; } /* * convert a character to an ENVIRONMENT-variable-safe variant */ int oidc_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_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_char_to_env(*a) - oidc_char_to_env(*b); if (d) return d; a++; b++; i++; } } /* * escape a string */ char *oidc_util_escape_string(const request_rec *r, const char *str) { CURL *curl = NULL; if (str == NULL) return ""; curl = curl_easy_init(); if (curl == NULL) { oidc_error(r, "curl_easy_init() error"); return ""; } char *result = curl_easy_escape(curl, str, 0); if (result == NULL) { oidc_error(r, "curl_easy_escape() error"); return ""; } char *rv = apr_pstrdup(r->pool, result); curl_free(result); curl_easy_cleanup(curl); return rv; } /* * escape a string */ char *oidc_util_unescape_string(const request_rec *r, const char *str) { CURL *curl = NULL; if (str == NULL) return ""; curl = curl_easy_init(); if (curl == NULL) { oidc_error(r, "curl_easy_init() error"); return ""; } int counter = 0; char *replaced = (char *)str; while (str[counter] != '\0') { if (str[counter] == '+') { replaced[counter] = ' '; } counter++; } char *result = curl_easy_unescape(curl, replaced, 0, 0); if (result == NULL) { oidc_error(r, "curl_easy_unescape() error"); return ""; } char *rv = apr_pstrdup(r->pool, result); curl_free(result); curl_easy_cleanup(curl); // oidc_debug(r, "input=\"%s\", output=\"%s\"", str, rv); return rv; } /* * 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; size_t 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; } const char *oidc_util_strcasestr(const char *s1, const char *s2) { const char *s = s1; const char *p = s2; 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); } static const char *oidc_util_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_util_hdr_in_forwarded_get(r); value = oidc_util_strcasestr(value, item); if (value) { value += _oidc_strlen(item); ptr = strstr(value, ";"); if (ptr) *ptr = '\0'; ptr = strstr(value, " "); if (ptr) *ptr = '\0'; } return apr_pstrdup(r->pool, value); } /* * get the URL scheme that is currently being accessed */ static const char *oidc_get_current_url_scheme(const request_rec *r, const apr_byte_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_util_hdr_forwarded_get(r, "proto"); else if (x_forwarded_headers & OIDC_HDR_X_FORWARDED_PROTO) scheme_str = oidc_util_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_strcmp(scheme_str, "http") != 0) && (_oidc_strcmp(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 part that is currently being accessed */ static const char *oidc_get_port_from_host(const char *host_hdr) { char *p = NULL; char *i = NULL; if (host_hdr) { if (host_hdr[0] == '[') { i = strchr(host_hdr, ']'); p = strchr(i, OIDC_CHAR_COLON); } else { p = strchr(host_hdr, OIDC_CHAR_COLON); } } if (p) return p; else return NULL; } /* * 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, const apr_byte_t 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_util_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_util_hdr_forwarded_get(r, "host"); else if (x_forwarded_headers & OIDC_HDR_X_FORWARDED_HOST) host_hdr = oidc_util_hdr_in_x_forwarded_host_get(r); if (host_hdr) { port_str = oidc_get_port_from_host(host_hdr); if (port_str) port_str++; return port_str; } /* * see if we can get the port from the "Host" header; if not * we'll determine the port locally */ host_hdr = oidc_util_hdr_in_host_get(r); if (host_hdr) { port_str = oidc_get_port_from_host(host_hdr); if (port_str) { port_str++; return port_str; } } /* * 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_util_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_util_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_strcmp(scheme_str, "https") == 0) && port == 443) return NULL; else if ((_oidc_strcmp(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_get_current_url_host(request_rec *r, const apr_byte_t x_forwarded_headers) { const char *host_str = NULL; char *p = NULL; char *i = NULL; if (x_forwarded_headers & OIDC_HDR_FORWARDED) host_str = oidc_util_hdr_forwarded_get(r, "host"); else if (x_forwarded_headers & OIDC_HDR_X_FORWARDED_HOST) host_str = oidc_util_hdr_in_x_forwarded_host_get(r); if (host_str == NULL) host_str = oidc_util_hdr_in_host_get(r); if (host_str) { host_str = apr_pstrdup(r->pool, host_str); if (host_str[0] == '[') { i = strchr(host_str, ']'); p = strchr(i, 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, const apr_byte_t x_forwarded_headers) { const char *scheme_str = NULL; const char *host_str = NULL; const char *port_str = NULL; oidc_config_check_x_forwarded(r, x_forwarded_headers); scheme_str = oidc_get_current_url_scheme(r, x_forwarded_headers); host_str = oidc_get_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_get_current_url(request_rec *r, const apr_byte_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_get_absolute_url(request_rec *r, oidc_cfg *cfg, const char *url) { if ((url != NULL) && (url[0] == OIDC_CHAR_FORWARD_SLASH)) { url = apr_pstrcat(r->pool, oidc_get_current_url_base(r, cfg->x_forwarded_headers), url, NULL); oidc_debug(r, "determined absolute url: %s", url); } return url; } /* * determine absolute Redirect URI */ const char *oidc_get_redirect_uri(request_rec *r, oidc_cfg *cfg) { return oidc_get_absolute_url(r, cfg, cfg->redirect_uri); } /* * determine absolute redirect uri that is issuer specific */ const char *oidc_get_redirect_uri_iss(request_rec *r, oidc_cfg *cfg, oidc_provider_t *provider) { const char *redirect_uri = oidc_get_redirect_uri(r, cfg); if (redirect_uri == NULL) { oidc_error(r, "redirect URI is NULL"); return NULL; } if (provider->issuer_specific_redirect_uri != 0) { redirect_uri = apr_psprintf(r->pool, "%s%s%s=%s", redirect_uri, strchr(redirect_uri, OIDC_CHAR_QUERY) != NULL ? OIDC_STR_AMP : OIDC_STR_QUERY, OIDC_PROTO_ISS, oidc_util_escape_string(r, provider->issuer)); oidc_debug(r, "determined issuer specific redirect uri: %s", redirect_uri); } return redirect_uri; } /* buffer to hold HTTP call responses */ typedef struct oidc_curl_buffer { request_rec *r; char *memory; size_t size; } oidc_curl_buffer; /* maximum acceptable size of HTTP responses: 10 Mb */ #define OIDC_CURL_MAX_RESPONSE_SIZE 1024 * 1024 * 10 /* * callback for CURL to write bytes that come back from an HTTP call */ size_t oidc_curl_write(void *contents, size_t size, size_t nmemb, void *userp) { size_t realsize = size * nmemb; oidc_curl_buffer *mem = (oidc_curl_buffer *)userp; /* check if we don't run over the maximum buffer/memory size for HTTP responses */ if (mem->size + realsize > OIDC_CURL_MAX_RESPONSE_SIZE) { 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_MAX_RESPONSE_SIZE); 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; } /* 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_util_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_util_escape_string(ctx->r, key), oidc_util_escape_string(ctx->r, value)); return 1; } /* * construct a URL with query parameters */ char *oidc_util_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_util_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_util_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_util_http_add_form_url_encoded_param, &encode_data, params, NULL); data = encode_data.encoded_params; } oidc_debug(r, "data=%s", data); return data; } /* * set libcurl SSL options */ #define OIDC_CURLOPT_SSL_OPTIONS "CURLOPT_SSL_OPTIONS" #define OIDC_UTIL_SET_CURL_OPTION(r, curl, env_var_value, option, key, val) \ if (strstr(env_var_value, option) != NULL) { \ oidc_debug(r, "curl_easy_setopt (%d) %s (%d)", key, option, val); \ curl_easy_setopt(curl, key, val); \ } static void oidc_util_set_curl_ssl_options(request_rec *r, CURL *curl) { const char *env_var_value = NULL; if (r->subprocess_env != NULL) env_var_value = apr_table_get(r->subprocess_env, OIDC_CURLOPT_SSL_OPTIONS); if (env_var_value == NULL) return; oidc_debug(r, "SSL options environment variable %s=%s found", OIDC_CURLOPT_SSL_OPTIONS, env_var_value); #if LIBCURL_VERSION_NUM >= 0x071900 OIDC_UTIL_SET_CURL_OPTION(r, curl, env_var_value, "CURLSSLOPT_ALLOW_BEAST", CURLOPT_SSL_OPTIONS, CURLSSLOPT_ALLOW_BEAST) #endif #if LIBCURL_VERSION_NUM >= 0x072c00 OIDC_UTIL_SET_CURL_OPTION(r, curl, env_var_value, "CURLSSLOPT_NO_REVOKE", CURLOPT_SSL_OPTIONS, CURLSSLOPT_NO_REVOKE) #endif #if LIBCURL_VERSION_NUM >= 0x074400 OIDC_UTIL_SET_CURL_OPTION(r, curl, env_var_value, "CURLSSLOPT_NO_PARTIALCHAIN", CURLOPT_SSL_OPTIONS, CURLSSLOPT_NO_PARTIALCHAIN) #endif #if LIBCURL_VERSION_NUM >= 0x074600 OIDC_UTIL_SET_CURL_OPTION(r, curl, env_var_value, "CURLSSLOPT_REVOKE_BEST_EFFORT", CURLOPT_SSL_OPTIONS, CURLSSLOPT_REVOKE_BEST_EFFORT) #endif #if LIBCURL_VERSION_NUM >= 0x074700 OIDC_UTIL_SET_CURL_OPTION(r, curl, env_var_value, "CURLSSLOPT_NATIVE_CA", CURLOPT_SSL_OPTIONS, CURLSSLOPT_NATIVE_CA) #endif #if LIBCURL_VERSION_NUM >= 0x072200 OIDC_UTIL_SET_CURL_OPTION(r, curl, env_var_value, "CURL_SSLVERSION_TLSv1_0", CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1_0) OIDC_UTIL_SET_CURL_OPTION(r, curl, env_var_value, "CURL_SSLVERSION_TLSv1_1", CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1_1) OIDC_UTIL_SET_CURL_OPTION(r, curl, env_var_value, "CURL_SSLVERSION_TLSv1_2", CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1_2) #endif #if LIBCURL_VERSION_NUM >= 0x073400 OIDC_UTIL_SET_CURL_OPTION(r, curl, env_var_value, "CURL_SSLVERSION_TLSv1_3", CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1_3) #endif #if LIBCURL_VERSION_NUM >= 0x073600 OIDC_UTIL_SET_CURL_OPTION(r, curl, env_var_value, "CURL_SSLVERSION_MAX_TLSv1_0", CURLOPT_SSLVERSION, CURL_SSLVERSION_MAX_TLSv1_0) OIDC_UTIL_SET_CURL_OPTION(r, curl, env_var_value, "CURL_SSLVERSION_MAX_TLSv1_1", CURLOPT_SSLVERSION, CURL_SSLVERSION_MAX_TLSv1_1) OIDC_UTIL_SET_CURL_OPTION(r, curl, env_var_value, "CURL_SSLVERSION_MAX_TLSv1_2", CURLOPT_SSLVERSION, CURL_SSLVERSION_MAX_TLSv1_2) OIDC_UTIL_SET_CURL_OPTION(r, curl, env_var_value, "CURL_SSLVERSION_MAX_TLSv1_3", CURLOPT_SSLVERSION, CURL_SSLVERSION_MAX_TLSv1_3) #endif } 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; } #define OIDC_USER_AGENT_ENV_VAR "OIDC_USER_AGENT" static const char *oidc_util_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; } /* * execute a HTTP (GET or POST) request */ static apr_byte_t oidc_util_http_call(request_rec *r, const char *url, const char *data, const char *content_type, const char *basic_auth, const char *bearer_token, int ssl_validate_server, char **response, oidc_http_timeout_t *http_timeout, const oidc_outgoing_proxy_t *outgoing_proxy, apr_array_header_t *pass_cookies, const char *ssl_cert, const char *ssl_key, const char *ssl_key_pwd) { char curlError[CURL_ERROR_SIZE]; oidc_curl_buffer curlBuffer; CURL *curl = NULL; struct curl_slist *h_list = NULL; int i = 0; CURLcode res = CURLE_OK; long response_code = 0; apr_byte_t rv = FALSE; oidc_cfg *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, bearer_token=%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", bearer_token, ssl_validate_server, http_timeout->request_timeout, http_timeout->connect_timeout, http_timeout->retries, (int)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 */ curlError[0] = 0; /* some of these are not really required */ curl_easy_setopt(curl, CURLOPT_HEADER, 0L); curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 1L); curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1L); curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, curlError); curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L); curl_easy_setopt(curl, CURLOPT_MAXREDIRS, 5L); /* set the timeouts */ curl_easy_setopt(curl, CURLOPT_TIMEOUT, http_timeout->request_timeout); curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, http_timeout->connect_timeout); /* setup the buffer where the response will be written to */ curlBuffer.r = r; curlBuffer.memory = NULL; curlBuffer.size = 0; curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, oidc_curl_write); curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *)&curlBuffer); #ifndef LIBCURL_NO_CURLPROTO #if LIBCURL_VERSION_NUM >= 0x075500 curl_easy_setopt(curl, CURLOPT_REDIR_PROTOCOLS_STR, "http,https"); curl_easy_setopt(curl, CURLOPT_PROTOCOLS_STR, "http,https"); #else curl_easy_setopt(curl, CURLOPT_REDIR_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS); curl_easy_setopt(curl, CURLOPT_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS); #endif #endif /* set the options for validating the SSL server certificate that the remote site presents */ curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, (ssl_validate_server != FALSE ? 1L : 0L)); curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, (ssl_validate_server != FALSE ? 2L : 0L)); oidc_util_set_curl_ssl_options(r, curl); if (c->ca_bundle_path != NULL) curl_easy_setopt(curl, CURLOPT_CAINFO, c->ca_bundle_path); #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) curl_easy_setopt(curl, 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 *useragent = oidc_util_user_agent(r); if ((useragent != NULL) && (_oidc_strcmp(useragent, "") != 0)) { oidc_debug(r, "set HTTP request header User-Agent to: %s", useragent); curl_easy_setopt(curl, CURLOPT_USERAGENT, useragent); } /* set optional outgoing proxy for the local network */ if (outgoing_proxy->host_port) { curl_easy_setopt(curl, CURLOPT_PROXY, outgoing_proxy->host_port); if (outgoing_proxy->username_password) curl_easy_setopt(curl, CURLOPT_PROXYUSERPWD, outgoing_proxy->username_password); if (outgoing_proxy->auth_type != OIDC_CONFIG_POS_INT_UNSET) curl_easy_setopt(curl, CURLOPT_PROXYAUTH, outgoing_proxy->auth_type); } /* see if we need to add token in the Bearer Authorization header */ if (bearer_token != NULL) { h_list = curl_slist_append(h_list, apr_psprintf(r->pool, "Authorization: Bearer %s", bearer_token)); } /* see if we need to perform HTTP basic authentication to the remote site */ if (basic_auth != NULL) { curl_easy_setopt(curl, CURLOPT_HTTPAUTH, CURLAUTH_BASIC); curl_easy_setopt(curl, CURLOPT_USERPWD, basic_auth); } if (ssl_cert != NULL) curl_easy_setopt(curl, CURLOPT_SSLCERT, ssl_cert); if (ssl_key != NULL) curl_easy_setopt(curl, CURLOPT_SSLKEY, ssl_key); if (ssl_key_pwd != NULL) curl_easy_setopt(curl, CURLOPT_KEYPASSWD, ssl_key_pwd); if (data != NULL) { /* set POST data */ curl_easy_setopt(curl, CURLOPT_POSTFIELDS, data); /* set HTTP method to POST */ curl_easy_setopt(curl, 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_util_hdr_in_traceparent_get(r); if (traceparent && c->trace_parent != 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)); } /* see if we need to add any custom headers */ if (h_list != NULL) curl_easy_setopt(curl, 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_util_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); curl_easy_setopt(curl, CURLOPT_COOKIE, cookie_string); } } /* set the target URL */ curl_easy_setopt(curl, 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, curlError[0] ? curlError : ""); OIDC_METRICS_COUNTER_INC_SPEC(r, c, OM_PROVIDER_CONNECT_ERROR, curlError[0] ? curlError : "timeout") break; } oidc_error(r, "curl_easy_perform(%d/%d) failed for %s with: [%s]", i + 1, http_timeout->retries + 1, url, curlError[0] ? curlError : ""); OIDC_METRICS_COUNTER_INC_SPEC(r, c, OM_PROVIDER_CONNECT_ERROR, curlError[0] ? curlError : "undefined") /* in case of a connectivity/network glitch we'll back off before retrying */ if (i < http_timeout->retries) apr_sleep(http_timeout->retry_interval); } if (rv == FALSE) goto end; curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &response_code); oidc_debug(r, "HTTP response code=%ld", response_code); OIDC_METRICS_COUNTER_INC_SPEC(r, c, OM_PROVIDER_HTTP_RESPONSE_CODE, apr_psprintf(r->pool, "%ld", response_code)); *response = apr_pstrmemdup(r->pool, curlBuffer.memory, curlBuffer.size); /* 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_util_http_get(request_rec *r, const char *url, const apr_table_t *params, const char *basic_auth, const char *bearer_token, int ssl_validate_server, char **response, oidc_http_timeout_t *http_timeout, const oidc_outgoing_proxy_t *outgoing_proxy, apr_array_header_t *pass_cookies, const char *ssl_cert, const char *ssl_key, const char *ssl_key_pwd) { char *query_url = oidc_util_http_query_encoded_url(r, url, params); return oidc_util_http_call(r, query_url, NULL, NULL, basic_auth, bearer_token, ssl_validate_server, response, 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_util_http_post_form(request_rec *r, const char *url, const apr_table_t *params, const char *basic_auth, const char *bearer_token, int ssl_validate_server, char **response, oidc_http_timeout_t *http_timeout, const oidc_outgoing_proxy_t *outgoing_proxy, apr_array_header_t *pass_cookies, const char *ssl_cert, const char *ssl_key, const char *ssl_key_pwd) { char *data = oidc_util_http_form_encoded_data(r, params); return oidc_util_http_call(r, url, data, OIDC_CONTENT_TYPE_FORM_ENCODED, basic_auth, bearer_token, ssl_validate_server, response, 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_util_http_post_json(request_rec *r, const char *url, json_t *json, const char *basic_auth, const char *bearer_token, int ssl_validate_server, char **response, oidc_http_timeout_t *http_timeout, const oidc_outgoing_proxy_t *outgoing_proxy, 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_object(r, json, JSON_COMPACT) : NULL; return oidc_util_http_call(r, url, data, OIDC_CONTENT_TYPE_JSON, basic_auth, bearer_token, ssl_validate_server, response, 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_util_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 char *oidc_util_get_cookie_path(request_rec *r) { char *rv = NULL; char *requestPath = oidc_util_get_path(r); char *cookie_path = oidc_cfg_dir_cookie_path(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_COOKIE_FLAG_DOMAIN "Domain" #define OIDC_COOKIE_FLAG_PATH "Path" #define OIDC_COOKIE_FLAG_EXPIRES "Expires" #define OIDC_COOKIE_FLAG_SECURE "Secure" #define OIDC_COOKIE_FLAG_HTTP_ONLY "HttpOnly" #define OIDC_COOKIE_MAX_SIZE 4093 #define OIDC_SET_COOKIE_APPEND_ENV_VAR "OIDC_SET_COOKIE_APPEND" const char *oidc_util_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; } apr_byte_t oidc_util_request_is_secure(request_rec *r, const oidc_cfg *c) { return (_oidc_strnatcasecmp("https", oidc_get_current_url_scheme(r, c->x_forwarded_headers)) == 0); } /* * set a cookie in the HTTP response headers */ void oidc_util_set_cookie(request_rec *r, const char *cookieName, const char *cookieValue, apr_time_t expires, const char *ext) { oidc_cfg *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_COOKIE_FLAG_PATH, oidc_util_get_cookie_path(r)); if (expiresString != NULL) headerString = apr_psprintf(r->pool, "%s; %s=%s", headerString, OIDC_COOKIE_FLAG_EXPIRES, expiresString); if (c->cookie_domain != NULL) headerString = apr_psprintf(r->pool, "%s; %s=%s", headerString, OIDC_COOKIE_FLAG_DOMAIN, c->cookie_domain); if (oidc_util_request_is_secure(r, c)) headerString = apr_psprintf(r->pool, "%s; %s", headerString, OIDC_COOKIE_FLAG_SECURE); if (c->cookie_http_only != FALSE) headerString = apr_psprintf(r->pool, "%s; %s", headerString, OIDC_COOKIE_FLAG_HTTP_ONLY); appendString = oidc_util_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_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_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_util_hdr_err_out_add(r, OIDC_HTTP_HDR_SET_COOKIE, headerString); } /* * get a cookie from the HTTP request */ char *oidc_util_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_util_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_COOKIE_CHUNKS_SEPARATOR "_" #define OIDC_COOKIE_CHUNKS_POSTFIX "chunks" /* * get the name of the cookie that contains the number of chunks */ static char *oidc_util_get_chunk_count_name(request_rec *r, const char *cookieName) { return apr_psprintf(r->pool, "%s%s%s", cookieName, OIDC_COOKIE_CHUNKS_SEPARATOR, OIDC_COOKIE_CHUNKS_POSTFIX); } /* * get the number of cookie chunks set by the browser */ static int oidc_util_get_chunked_count(request_rec *r, const char *cookieName) { int chunkCount = 0; char *chunkCountValue = oidc_util_get_cookie(r, oidc_util_get_chunk_count_name(r, cookieName)); if (chunkCountValue != NULL) { chunkCount = _oidc_str_to_int(chunkCountValue); if (*chunkCountValue == '\0') chunkCount = 0; } return chunkCount; } /* * get the name of a chunk */ static char *oidc_util_get_chunk_cookie_name(request_rec *r, const char *cookieName, int i) { return apr_psprintf(r->pool, "%s%s%d", cookieName, OIDC_COOKIE_CHUNKS_SEPARATOR, i); } /* * get a cookie value that is split over a number of chunked cookies */ char *oidc_util_get_chunked_cookie(request_rec *r, const char *cookieName, int chunkSize) { char *cookieValue = NULL; char *chunkValue = NULL; int i = 0; if (chunkSize == 0) { cookieValue = oidc_util_get_cookie(r, cookieName); } else { int chunkCount = oidc_util_get_chunked_count(r, cookieName); if (chunkCount > 0) { cookieValue = ""; for (i = 0; i < chunkCount; i++) { chunkValue = oidc_util_get_cookie(r, oidc_util_get_chunk_cookie_name(r, cookieName, i)); if (chunkValue != NULL) cookieValue = apr_psprintf(r->pool, "%s%s", cookieValue, chunkValue); } } else { cookieValue = oidc_util_get_cookie(r, cookieName); } } return cookieValue; } /* * unset all chunked cookies, including the counter cookie, if they exist */ static void oidc_util_clear_chunked_cookie(request_rec *r, const char *cookieName, apr_time_t expires, const char *ext) { int i = 0; int chunkCount = oidc_util_get_chunked_count(r, cookieName); if (chunkCount > 0) { for (i = 0; i < chunkCount; i++) oidc_util_set_cookie(r, oidc_util_get_chunk_cookie_name(r, cookieName, i), "", expires, ext); oidc_util_set_cookie(r, oidc_util_get_chunk_count_name(r, cookieName), "", expires, ext); } } /* * set a cookie value that is split over a number of chunked cookies */ void oidc_util_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_util_set_cookie(r, cookieName, cookieValue, expires, ext); oidc_util_clear_chunked_cookie(r, cookieName, expires, ext); return; } /* see if we need to clear a possibly chunked cookie */ if (cookieLength == 0) { oidc_util_set_cookie(r, cookieName, "", expires, ext); oidc_util_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_util_set_cookie(r, oidc_util_get_chunk_cookie_name(r, cookieName, i), chunkValue, expires, ext); } oidc_util_set_cookie(r, oidc_util_get_chunk_count_name(r, cookieName), apr_psprintf(r->pool, "%d", chunkCountValue), expires, ext); oidc_util_set_cookie(r, cookieName, "", expires, ext); } /* * 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_normalize_header_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; } /* * 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 ((strstr(r->args, option1) == r->args) || (strstr(r->args, option2) != NULL)) ? TRUE : FALSE; } /* * get a query parameter */ apr_byte_t oidc_util_get_request_parameter(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_util_unescape_string(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_object(r, value, JSON_ENCODE_ANY)); return TRUE; } return FALSE; } /* * check a JSON object for "error" results and printout */ static 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(request_rec *r, const char *str, json_t **json) { 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) { /* 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 */ oidc_error(r, "parsed JSON did not contain a JSON object"); json_decref(*json); *json = NULL; return FALSE; } return TRUE; } /* * encode a JSON object */ char *oidc_util_encode_json_object(request_rec *r, json_t *json, size_t flags) { char *s = json_dumps(json, flags); char *s_value = apr_pstrdup(r->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_CONTENT_TYPE_TEXT_HTML, status_code); } static char *html_error_template_contents = NULL; /* * get the full path to a file based on an (already) absolute filename or a filename * that is relative to the Apache root directory */ char *oidc_util_get_full_path(apr_pool_t *pool, const char *abs_or_rel_filename) { return abs_or_rel_filename ? ap_server_root_relative(pool, abs_or_rel_filename) : NULL; } /* * 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 */ apr_byte_t 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 *fullname = NULL; char *html = NULL; int rc = status_code; if (*static_template_content == NULL) { fullname = oidc_util_get_full_path(r->pool, filename); // NB: templates go into the server process pool if (oidc_util_file_read(r, fullname, r->server->process->pool, static_template_content) == FALSE) { oidc_error(r, "could not read template: %s", fullname); *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_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 *html_template, const char *error, const char *description, int status_code) { char *html = ""; int rc = status_code; if (html_template != NULL) { if (_oidc_strcmp(html_template, "deprecated") != 0) { rc = oidc_util_html_send_in_template(r, html_template, &html_error_template_contents, error, OIDC_POST_PRESERVE_ESCAPE_HTML, description, OIDC_POST_PRESERVE_ESCAPE_HTML, status_code); } else { if (error != NULL) { html = apr_psprintf(r->pool, "%s

Error:

%s

", html, oidc_util_html_escape(r->pool, error)); } if (description != NULL) { html = apr_psprintf(r->pool, "%s

Description:

%s

", html, oidc_util_html_escape(r->pool, description)); } rc = oidc_util_html_send(r, "Error", NULL, NULL, html, 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 rc; } /* * 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))) { key = ap_getword(r->pool, &val, OIDC_CHAR_EQUAL); key = oidc_util_unescape_string(r, key); val = oidc_util_unescape_string(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_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_util_hdr_in_content_type_get(r); if ((r->method_number != M_POST) || (content_type == NULL) || (strstr(content_type, OIDC_CONTENT_TYPE_FORM_ENCODED) != content_type)) { oidc_debug(r, "required content-type %s not found", OIDC_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_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; } static char *oidc_util_utf8_to_latin1(request_rec *r, const char *src) { char *dst = ""; unsigned int cp = 0; unsigned char ch; int i = 0; if (src == NULL) return NULL; dst = apr_pcalloc(r->pool, 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, apr_byte_t as_header, apr_byte_t as_env_var, int pass_as) { /* 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_normalize_header_name(r, s_key)); char *d_value = NULL; if (s_value != NULL) { if (pass_as == OIDC_PASS_APP_INFO_AS_BASE64URL) { oidc_base64url_encode(r, &d_value, s_value, _oidc_strlen(s_value), TRUE); } else if (pass_as == OIDC_PASS_APP_INFO_AS_LATIN1) { d_value = oidc_util_utf8_to_latin1(r, s_value); } } if (as_header) { oidc_util_hdr_in_set(r, s_name, (d_value != NULL) ? d_value : s_value); } if (as_env_var) { /* 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, apr_byte_t as_header, apr_byte_t as_env_var, int pass_as) { 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); // char *s_value= json_dumps(j_value, JSON_ENCODE_ANY); // oidc_util_set_app_info(r, s_key, s_value, claim_prefix); // free(s_value); /* 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, as_header, as_env_var, pass_as); } 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, as_header, as_env_var, pass_as); } 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, as_header, as_env_var, pass_as); } 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, as_header, as_env_var, pass_as); } 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_object(r, j_value, 0), claim_prefix, as_header, as_env_var, pass_as); /* 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_warn(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, as_header, as_env_var, pass_as); } else { /* no string and no array, so unclear how to handle this */ oidc_warn(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 && (val = ap_getword_white(pool, &data))) { 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) { /* 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)) { const char *k; const char *v; apr_hash_this(hi, (const void **)&k, NULL, (void **)&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_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) int from a JSON object */ apr_byte_t oidc_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_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_object(r, src, JSON_COMPACT), oidc_util_encode_json_object(r, dst, 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_object(r, dst, 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) { if (params != NULL) { char *key = NULL; const char *val = NULL; const char *p = params; while (*p && (val = ap_getword(pool, &p, OIDC_CHAR_AMP))) { key = ap_getword(pool, &val, OIDC_CHAR_EQUAL); ap_unescape_url((char *)key); ap_unescape_url((char *)val); apr_table_add(table, key, val); } } } /* * 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; 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_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 two key sets */ 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; } 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; } int oidc_util_cookie_domain_valid(const char *hostname, char *cookie_domain) { char *p = NULL; 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 = strstr(hostname, check_cookie); if ((p == NULL) || (_oidc_strcmp(check_cookie, p) != 0)) { return FALSE; } return TRUE; } static const char *oidc_util_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; } static const char *oidc_util_hdr_in_get_left_most_only(const request_rec *r, const char *name, const char *separator) { char *last = NULL; const char *value = oidc_util_hdr_in_get(r, name); if (value) return apr_strtok(apr_pstrdup(r->pool, value), separator, &last); return NULL; } static apr_byte_t oidc_util_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_util_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; } static void oidc_util_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); } } static void oidc_util_hdr_out_set(const request_rec *r, const char *name, const char *value) { oidc_util_hdr_table_set(r, r->headers_out, name, value); } static const char *oidc_util_hdr_out_get(const request_rec *r, const char *name) { return apr_table_get(r->headers_out, name); } void oidc_util_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); } void oidc_util_hdr_in_set(const request_rec *r, const char *name, const char *value) { oidc_util_hdr_table_set(r, r->headers_in, name, value); } const char *oidc_util_hdr_in_cookie_get(const request_rec *r) { return oidc_util_hdr_in_get(r, OIDC_HTTP_HDR_COOKIE); } void oidc_util_hdr_in_cookie_set(const request_rec *r, const char *value) { oidc_util_hdr_in_set(r, OIDC_HTTP_HDR_COOKIE, value); } const char *oidc_util_hdr_in_user_agent_get(const request_rec *r) { return oidc_util_hdr_in_get(r, OIDC_HTTP_HDR_USER_AGENT); } const char *oidc_util_hdr_in_x_forwarded_for_get(const request_rec *r) { return oidc_util_hdr_in_get_left_most_only(r, OIDC_HTTP_HDR_X_FORWARDED_FOR, OIDC_STR_COMMA OIDC_STR_SPACE); } const char *oidc_util_hdr_in_content_type_get(const request_rec *r) { return oidc_util_hdr_in_get(r, OIDC_HTTP_HDR_CONTENT_TYPE); } const char *oidc_util_hdr_in_content_length_get(const request_rec *r) { return oidc_util_hdr_in_get(r, OIDC_HTTP_HDR_CONTENT_LENGTH); } const char *oidc_util_hdr_in_x_requested_with_get(const request_rec *r) { return oidc_util_hdr_in_get(r, OIDC_HTTP_HDR_X_REQUESTED_WITH); } const char *oidc_util_hdr_in_sec_fetch_mode_get(const request_rec *r) { return oidc_util_hdr_in_get(r, OIDC_HTTP_HDR_SEC_FETCH_MODE); } const char *oidc_util_hdr_in_sec_fetch_dest_get(const request_rec *r) { return oidc_util_hdr_in_get(r, OIDC_HTTP_HDR_SEC_FETCH_DEST); } const char *oidc_util_hdr_in_accept_get(const request_rec *r) { return oidc_util_hdr_in_get(r, OIDC_HTTP_HDR_ACCEPT); } apr_byte_t oidc_util_hdr_in_accept_contains(const request_rec *r, const char *needle) { return oidc_util_hdr_in_contains(r, OIDC_HTTP_HDR_ACCEPT, OIDC_STR_COMMA, OIDC_CHAR_SEMI_COLON, needle); } const char *oidc_util_hdr_in_authorization_get(const request_rec *r) { return oidc_util_hdr_in_get(r, OIDC_HTTP_HDR_AUTHORIZATION); } const char *oidc_util_hdr_in_x_forwarded_proto_get(const request_rec *r) { return oidc_util_hdr_in_get_left_most_only(r, OIDC_HTTP_HDR_X_FORWARDED_PROTO, OIDC_STR_COMMA OIDC_STR_SPACE); } const char *oidc_util_hdr_in_x_forwarded_port_get(const request_rec *r) { return oidc_util_hdr_in_get_left_most_only(r, OIDC_HTTP_HDR_X_FORWARDED_PORT, OIDC_STR_COMMA OIDC_STR_SPACE); } const char *oidc_util_hdr_in_x_forwarded_host_get(const request_rec *r) { return oidc_util_hdr_in_get_left_most_only(r, OIDC_HTTP_HDR_X_FORWARDED_HOST, OIDC_STR_COMMA OIDC_STR_SPACE); } const char *oidc_util_hdr_in_forwarded_get(const request_rec *r) { return oidc_util_hdr_in_get_left_most_only(r, OIDC_HTTP_HDR_FORWARDED, OIDC_STR_COMMA); } const char *oidc_util_hdr_in_host_get(const request_rec *r) { return oidc_util_hdr_in_get(r, OIDC_HTTP_HDR_HOST); } const char *oidc_util_hdr_in_traceparent_get(const request_rec *r) { return oidc_util_hdr_in_get(r, OIDC_HTTP_HDR_TRACE_PARENT); } void oidc_util_hdr_out_location_set(const request_rec *r, const char *value) { oidc_util_hdr_out_set(r, OIDC_HTTP_HDR_LOCATION, value); } const char *oidc_util_hdr_out_location_get(const request_rec *r) { return oidc_util_hdr_out_get(r, OIDC_HTTP_HDR_LOCATION); } 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 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; } #endif 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; } 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; } 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; } #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 *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 (c->trace_parent != 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 (c->metrics_hook_data != NULL) trace_flags = trace_flags | 0x01; oidc_util_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)); } mod_auth_openidc-2.4.15.1/test/000077500000000000000000000000001455620533500162325ustar00rootroot00000000000000mod_auth_openidc-2.4.15.1/test/.gitignore000066400000000000000000000001211455620533500202140ustar00rootroot00000000000000/test /test-cmd /*.lo /*.o /*.slo /.libs/ /.deps/ /test.log /test.trs /.dirstamp mod_auth_openidc-2.4.15.1/test/certificate.pem000066400000000000000000000017111455620533500212170ustar00rootroot00000000000000-----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.15.1/test/eccert.pem000066400000000000000000000016051455620533500202040ustar00rootroot00000000000000openssl 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.15.1/test/ecpriv.key000066400000000000000000000006001455620533500202300ustar00rootroot00000000000000-----BEGIN PRIVATE KEY----- MIHuAgEAMBAGByqGSM49AgEGBSuBBAAjBIHWMIHTAgEBBEIAoXDJYBnYWJNMSh9d c4LojYYE9B4TIKexXOjTvmEI5x5tiAunNKPzFZ+ROrGxSrwUXlzGUpLePKbeqCy5 XQRvTB2hgYkDgYYABAEehIfVYXSxAPq5uVkdP09C4ysrCbgZEqF2kfBo6YR76Dk6 5Y3AAkbFZUxEvRTJG8WzRLOCXcT4DyZfRvnvVhCU3gGppoH5WWibkncnCv3HFF5E l4zUHfDbKibr/SxaZOWLNTgIoq1purpLVxvACzUsUn33LqwavM6mcmdeNXVeqksy kA== -----END PRIVATE KEY----- mod_auth_openidc-2.4.15.1/test/open-redirect-payload-list.txt000066400000000000000000000667431455620533500241530ustar00rootroot00000000000000/%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.15.1/test/post_preserve.template000066400000000000000000000007511455620533500226720ustar00rootroot00000000000000 Preserving...

Preserving...

mod_auth_openidc-2.4.15.1/test/post_restore.template000066400000000000000000000021461455620533500225220ustar00rootroot00000000000000 Restoring...

Restoring...

mod_auth_openidc-2.4.15.1/test/private.pem000066400000000000000000000032131455620533500204060ustar00rootroot00000000000000-----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.15.1/test/public.pem000066400000000000000000000007031455620533500202130ustar00rootroot00000000000000-----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.15.1/test/stub.c000066400000000000000000000170051455620533500173560ustar00rootroot00000000000000#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) { } 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_get_exec_line(apr_pool_t *p, const char *cmd, const char *const *argv) { return NULL; } 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) { } AP_DECLARE(void) ap_hook_insert_filter(void (*insert_filter)(request_rec *r), const char *const *aszPre, const char *const *aszSucc, int nOrder) { } 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) { } 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) { } AP_DECLARE(void) ap_hook_handler(int (*handler)(request_rec *r), const char *const *aszPre, const char *const *aszSucc, int nOrder) { } 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) { err = NULL; return expr->filename; } 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) { } 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) { } 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.15.1/test/test-cmd.c000066400000000000000000000357711455620533500201330ustar00rootroot00000000000000/* * 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-2024 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 #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) { 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 ((*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", 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; } oidc_jose_error_t oidc_err; oidc_jwk_t *jwk = oidc_jwk_parse(pool, s_jwk, &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); 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); oidc_jose_error_t oidc_err; oidc_jwk_t *jwk = oidc_jwk_parse(pool, s_jwk, &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); 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; } extern module AP_MODULE_DECLARE_DATA auth_openidc_module; typedef struct oidc_dir_cfg oidc_dir_cfg; 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 *cfg = oidc_create_server_config(request->pool, request->server); cfg->provider.issuer = "https://idp.example.com"; cfg->provider.authorization_endpoint_url = "https://idp.example.com/authorize"; cfg->provider.scope = "openid"; cfg->provider.client_id = "client_id"; cfg->redirect_uri = "https://www.example.com/protected/"; oidc_dir_cfg *d_cfg = oidc_create_dir_config(request->pool, NULL); request->server->module_config = apr_pcalloc(request->pool, sizeof(ap_conf_vector_t *) * kEls); request->per_dir_config = apr_pcalloc(request->pool, sizeof(ap_conf_vector_t *) * 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 = &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->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, 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]); 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; sscanf(s, "%" APR_TIME_T_FMT, &t2); 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]); 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.15.1/test/test.c000066400000000000000000002677101455620533500173720ustar00rootroot00000000000000/* * 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-2024 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 "mod_auth_openidc.h" #include #include extern module AP_MODULE_DECLARE_DATA auth_openidc_module; 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) { \ sprintf(TST_ERR_MSG, 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) { \ sprintf(TST_ERR_MSG, 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) { \ sprintf(TST_ERR_MSG, 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) { \ sprintf(TST_ERR_MSG, 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) { \ sprintf(TST_ERR_MSG, 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) { \ sprintf(TST_ERR_MSG, TST_FORMAT("%ld"), __FUNCTION__, message, result, expected); \ return TST_ERR_MSG; \ } #define TST_ASSERT_BYTE(message, result, expected) \ if (result != expected) { \ sprintf(TST_ERR_MSG, 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) { oidc_jwk_t *k = oidc_jwk_parse(pool, s, err); TST_ASSERT_ERR("oidc_jwk_parse", k != NULL, pool, (*err)); *jwk = k; return 0; } static char *test_private_key_parse(apr_pool_t *pool) { oidc_jose_error_t err; 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") : "."; sprintf((char *)rsaPrivateKeyFile, "%s/%s", dir, "/test/private.pem"); sprintf((char *)ecPrivateKeyFile, "%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; 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") : "."; sprintf((char *)publicKeyFile, "%s/%s", dir, "/test/public.pem"); sprintf((char *)certificateFile, "%s/%s", dir, "/test/certificate.pem"); sprintf((char *)ecCertificateFile, "%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_jwt_serialize(pool, jwt, &err); TST_ASSERT_ERR("oidc_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_jwt_serialize(pool, jwt, &err); TST_ASSERT_ERR("oidc_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); return 0; } static char *test_plaintext_decrypt(apr_pool_t *pool) { // from http://tools.ietf.org/html/draft-ietf-jose-json-web-encryption-30 // A.2. Example JWE using RSAES-PKCS1-V1_5 and AES_128_CBC_HMAC_SHA_256 char *s = apr_pstrdup( pool, "eyJhbGciOiJSU0ExXzUiLCJlbmMiOiJBMTI4Q0JDLUhTMjU2In0" ".UGhIOguC7IuEvf_NPVaXsGMoLOmwvc1GyqlIKOK1nN94nHPoltGRhWhw7Zx0-kFm1NJn8LE9XShH59_" "i8J0PH5ZZyNfGy2xGdULU7sHNF6Gp2vPLgNZ__deLKxGHZ7PcHALUzoOegEI-8E66jX2E4zyJKx-YxzZIItRzC5hlRirb6Y5Cl_" "p-ko3YvkkysZIFNPccxRU7qve1WYPxqbb2Yw8kZqa2rMWI5ng8OtvzlV7elprCbuPhcCdZ6XDP0_F8rkXds2vE4X-" "ncOIM8hAYHHi29NX0mcKiRaD0-D-ljQTP-cFPgwCp6X-nZZd9OHBv-B3oWh2TbqmScqXMR4gp_A" ".AxY8DCtDaGlsbGljb3RoZQ" ".KDlTtXchhZTGufMYmOYGS4HffxPSUrfmqCHXaI9wOGY" ".9hH0vgRfYgPnAHOd8stkvw"); char *k = "{\"kty\":\"RSA\"," "\"n\":" "\"sXchDaQebHnPiGvyDOAT4saGEUetSyo9MKLOoWFsueri23bOdgWp4Dy1WlUzewbgBHod5pcM9H95GQRV3JDXboIRROSBigeC5y" "jU1hGzHHyXss8UDprecbAYxknTcQkhslANGRUZmdTOQ5qTRsLAt6BTYuyvVRdhS8exSZEy_c4gs_7svlJJQ4H9_NxsiIoLwAEk7-" "Q3UXERGYw_75IDrGA84-lA_-Ct4eTlXHBIY2EaV7t7LjJaynVJCpkv4LKjTTAumiGUIuQhrNhZLuF_RJLqHpM2kgWFLU7-" "VTdL1VbC2tejvcI2BlMkEpk1BzBZI0KQB0GaDWFLN-aEAw3vRw\"," "\"e\":\"AQAB\"," "\"d\":\"VFCWOqXr8nvZNyaaJLXdnNPXZKRaWCjkU5Q2egQQpTBMwhprMzWzpR8Sxq1OPThh_" "J6MUD8Z35wky9b8eEO0pwNS8xlh1lOFRRBoNqDIKVOku0aZb-rynq8cxjDTLZQ6Fz7jSjR1Klop-YKaUHc9GsEofQqYruPhzSA-" "QgajZGPbE_0ZaVDJHfyd7UUBUKunFMScbflYAAOYJqVIVwaYR5zWEEceUjNnTNo_CVSj-" "VvXLO5VZfCUAVLgW4dpf1SrtZjSt34YLsRarSb127reG_DUwg9Ch-" "KyvjT1SkHgUWRVGcyly7uvVGRSDwsXypdrNinPA4jlhoNdizK2zF2CWQ\"" "}"; 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, "Live long and prosper.", content_len); cjose_get_dealloc()(decrypted); oidc_jwk_destroy(jwk); cjose_jwe_release(jwe); return 0; } static char *test_plaintext_decrypt2(apr_pool_t *pool) { // http://tools.ietf.org/html/draft-ietf-jose-cookbook-08#section-5.1.5 // 5.1. Key Encryption using RSA v1.5 and AES-HMAC-SHA2 char *s = apr_pstrdup(pool, "eyJhbGciOiJSU0ExXzUiLCJraWQiOiJmcm9kby5iYWdnaW5zQGhvYmJpdG9uLm" "V4YW1wbGUiLCJlbmMiOiJBMTI4Q0JDLUhTMjU2In0" "." "laLxI0j-nLH-_BgLOXMozKxmy9gffy2gTdvqzfTihJBuuzxg0V7yk1WClnQePF" "vG2K-pvSlWc9BRIazDrn50RcRai__3TDON395H3c62tIouJJ4XaRvYHFjZTZ2G" "Xfz8YAImcc91Tfk0WXC2F5Xbb71ClQ1DDH151tlpH77f2ff7xiSxh9oSewYrcG" "TSLUeeCt36r1Kt3OSj7EyBQXoZlN7IxbyhMAfgIe7Mv1rOTOI5I8NQqeXXW8Vl" "zNmoxaGMny3YnGir5Wf6Qt2nBq4qDaPdnaAuuGUGEecelIO1wx1BpyIfgvfjOh" "MBs9M8XL223Fg47xlGsMXdfuY-4jaqVw" "." "bbd5sTkYwhAIqfHsx8DayA" "." "0fys_TY_na7f8dwSfXLiYdHaA2DxUjD67ieF7fcVbIR62JhJvGZ4_FNVSiGc_r" "aa0HnLQ6s1P2sv3Xzl1p1l_o5wR_RsSzrS8Z-wnI3Jvo0mkpEEnlDmZvDu_k8O" "WzJv7eZVEqiWKdyVzFhPpiyQU28GLOpRc2VbVbK4dQKPdNTjPPEmRqcaGeTWZV" "yeSUvf5k59yJZxRuSvWFf6KrNtmRdZ8R4mDOjHSrM_s8uwIFcqt4r5GX8TKaI0" "zT5CbL5Qlw3sRc7u_hg0yKVOiRytEAEs3vZkcfLkP6nbXdC_PkMdNS-ohP78T2" "O6_7uInMGhFeX4ctHG7VelHGiT93JfWDEQi5_V9UN1rhXNrYu-0fVMkZAKX3VW" "i7lzA6BP430m" "." "kvKuFBXHe5mQr4lqgobAUg"); char *k = "{" "\"kty\": \"RSA\"," "\"kid\": \"frodo.baggins@hobbiton.example\"," "\"use\": \"enc\"," "\"n\": \"maxhbsmBtdQ3CNrKvprUE6n9lYcregDMLYNeTAWcLj8NnPU9XIYegT" "HVHQjxKDSHP2l-F5jS7sppG1wgdAqZyhnWvXhYNvcM7RfgKxqNx_xAHx" "6f3yy7s-M9PSNCwPC2lh6UAkR4I00EhV9lrypM9Pi4lBUop9t5fS9W5U" "NwaAllhrd-osQGPjIeI1deHTwx-ZTHu3C60Pu_LJIl6hKn9wbwaUmA4c" "R5Bd2pgbaY7ASgsjCUbtYJaNIHSoHXprUdJZKUMAzV0WOKPfA6OPI4oy" "pBadjvMZ4ZAj3BnXaSYsEZhaueTXvZB4eZOAjIyh2e_VOIKVMsnDrJYA" "VotGlvMQ\"," "\"e\": \"AQAB\"," "\"d\": \"Kn9tgoHfiTVi8uPu5b9TnwyHwG5dK6RE0uFdlpCGnJN7ZEi963R7wy" "bQ1PLAHmpIbNTztfrheoAniRV1NCIqXaW_qS461xiDTp4ntEPnqcKsyO" "5jMAji7-CL8vhpYYowNFvIesgMoVaPRYMYT9TW63hNM0aWs7USZ_hLg6" "Oe1mY0vHTI3FucjSM86Nff4oIENt43r2fspgEPGRrdE6fpLc9Oaq-qeP" "1GFULimrRdndm-P8q8kvN3KHlNAtEgrQAgTTgz80S-3VD0FgWfgnb1PN" "miuPUxO8OpI9KDIfu_acc6fg14nsNaJqXe6RESvhGPH2afjHqSy_Fd2v" "pzj85bQQ\"," "\"p\": \"2DwQmZ43FoTnQ8IkUj3BmKRf5Eh2mizZA5xEJ2MinUE3sdTYKSLtaE" "oekX9vbBZuWxHdVhM6UnKCJ_2iNk8Z0ayLYHL0_G21aXf9-unynEpUsH" "7HHTklLpYAzOOx1ZgVljoxAdWNn3hiEFrjZLZGS7lOH-a3QQlDDQoJOJ" "2VFmU\"," "\"q\": \"te8LY4-W7IyaqH1ExujjMqkTAlTeRbv0VLQnfLY2xINnrWdwiQ93_V" "F099aP1ESeLja2nw-6iKIe-qT7mtCPozKfVtUYfz5HrJ_XY2kfexJINb" "9lhZHMv5p1skZpeIS-GPHCC6gRlKo1q-idn_qxyusfWv7WAxlSVfQfk8" "d6Et0\"," "\"dp\": \"UfYKcL_or492vVc0PzwLSplbg4L3-Z5wL48mwiswbpzOyIgd2xHTH" "QmjJpFAIZ8q-zf9RmgJXkDrFs9rkdxPtAsL1WYdeCT5c125Fkdg317JV" "RDo1inX7x2Kdh8ERCreW8_4zXItuTl_KiXZNU5lvMQjWbIw2eTx1lpsf" "lo0rYU\"," "\"dq\": \"iEgcO-QfpepdH8FWd7mUFyrXdnOkXJBCogChY6YKuIHGc_p8Le9Mb" "pFKESzEaLlN1Ehf3B6oGBl5Iz_ayUlZj2IoQZ82znoUrpa9fVYNot87A" "CfzIG7q9Mv7RiPAderZi03tkVXAdaBau_9vs5rS-7HMtxkVrxSUvJY14" "TkXlHE\"," "\"qi\": \"kC-lzZOqoFaZCr5l0tOVtREKoVqaAYhQiqIRGL-MzS4sCmRkxm5vZ" "lXYx6RtE1n_AagjqajlkjieGlxTTThHD8Iga6foGBMaAr5uR1hGQpSc7" "Gl7CF1DZkBJMTQN6EshYzZfxW08mIO8M6Rzuh0beL6fG9mkDcIyPrBXx" "2bQ_mM\"" "}"; 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, "frodo.baggins@hobbiton.example", 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, "You can trust us to stick with you through thick and " "thin\u2013to the bitter end. And you can trust us to " "keep any secret of yours\u2013closer than you keep it " "yourself. But you cannot trust us to let you face trouble " "alone, and go off without a word. We are your friends, Frodo.", content_len); cjose_get_dealloc()(decrypted); oidc_jwk_destroy(jwk); cjose_jwe_release(jwe); 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; } static char *test_jwt_decrypt(apr_pool_t *pool) { // https://tools.ietf.org/html/draft-ietf-oauth-json-web-token-32#appendix-A.1 // A.2. Example Nested JWT char *s = apr_pstrdup(pool, "eyJhbGciOiJSU0ExXzUiLCJlbmMiOiJBMTI4Q0JDLUhTMjU2IiwiY3R5IjoiSldU" "In0." "g_hEwksO1Ax8Qn7HoN-BVeBoa8FXe0kpyk_XdcSmxvcM5_P296JXXtoHISr_DD_M" "qewaQSH4dZOQHoUgKLeFly-9RI11TG-_Ge1bZFazBPwKC5lJ6OLANLMd0QSL4fYE" "b9ERe-epKYE3xb2jfY1AltHqBO-PM6j23Guj2yDKnFv6WO72tteVzm_2n17SBFvh" "DuR9a2nHTE67pe0XGBUS_TK7ecA-iVq5COeVdJR4U4VZGGlxRGPLRHvolVLEHx6D" "YyLpw30Ay9R6d68YCLi9FYTq3hIXPK_-dmPlOUlKvPr1GgJzRoeC9G5qCvdcHWsq" "JGTO_z3Wfo5zsqwkxruxwA." "UmVkbW9uZCBXQSA5ODA1Mg." "VwHERHPvCNcHHpTjkoigx3_ExK0Qc71RMEParpatm0X_qpg-w8kozSjfNIPPXiTB" "BLXR65CIPkFqz4l1Ae9w_uowKiwyi9acgVztAi-pSL8GQSXnaamh9kX1mdh3M_TT" "-FZGQFQsFhu0Z72gJKGdfGE-OE7hS1zuBD5oEUfk0Dmb0VzWEzpxxiSSBbBAzP10" "l56pPfAtrjEYw-7ygeMkwBl6Z_mLS6w6xUgKlvW6ULmkV-uLC4FUiyKECK4e3WZY" "Kw1bpgIqGYsw2v_grHjszJZ-_I5uM-9RA8ycX9KqPRp9gc6pXmoU_-27ATs9XCvr" "ZXUtK2902AUzqpeEUJYjWWxSNsS-r1TJ1I-FMJ4XyAiGrfmo9hQPcNBYxPz3GQb2" "8Y5CLSQfNgKSGt0A4isp1hBUXBHAndgtcslt7ZoQJaKe_nNJgNliWtWpJ_ebuOpE" "l8jdhehdccnRMIwAmU1n7SPkmhIl1HlSOpvcvDfhUN5wuqU955vOBvfkBOh5A11U" "zBuo2WlgZ6hYi9-e3w29bR0C2-pp3jbqxEDw3iWaf2dc5b-LnR0FEYXvI_tYk5rd" "_J9N0mg0tQ6RbpxNEMNoA9QWk5lgdPvbh9BaO195abQ." "AVO9iT5AV4CzvDJCdhSFlQ"); char *ek = "{\"kty\":\"RSA\"," "\"n\":" "\"sXchDaQebHnPiGvyDOAT4saGEUetSyo9MKLOoWFsueri23bOdgWp4Dy1WlUzewbgBHod5pcM9H95GQRV3JDXboIRROSBigeC5" "yjU1hGzHHyXss8UDprecbAYxknTcQkhslANGRUZmdTOQ5qTRsLAt6BTYuyvVRdhS8exSZEy_c4gs_7svlJJQ4H9_" "NxsiIoLwAEk7-Q3UXERGYw_75IDrGA84-lA_-Ct4eTlXHBIY2EaV7t7LjJaynVJCpkv4LKjTTAumiGUIuQhrNhZLuF_" "RJLqHpM2kgWFLU7-VTdL1VbC2tejvcI2BlMkEpk1BzBZI0KQB0GaDWFLN-aEAw3vRw\"," "\"e\":\"AQAB\"," "\"d\":\"VFCWOqXr8nvZNyaaJLXdnNPXZKRaWCjkU5Q2egQQpTBMwhprMzWzpR8Sxq1OPThh_" "J6MUD8Z35wky9b8eEO0pwNS8xlh1lOFRRBoNqDIKVOku0aZb-rynq8cxjDTLZQ6Fz7jSjR1Klop-YKaUHc9GsEofQqYruPhzSA-" "QgajZGPbE_0ZaVDJHfyd7UUBUKunFMScbflYAAOYJqVIVwaYR5zWEEceUjNnTNo_CVSj-" "VvXLO5VZfCUAVLgW4dpf1SrtZjSt34YLsRarSb127reG_DUwg9Ch-" "KyvjT1SkHgUWRVGcyly7uvVGRSDwsXypdrNinPA4jlhoNdizK2zF2CWQ\"" "}"; char *sk = "{" "\"kty\":\"RSA\"," "\"n\":\"ofgWCuLjybRlzo0tZWJjNiuSfb4p4fAkd_wWJcyQoTbji9k0l8W26mPddx" "HmfHQp-Vaw-4qPCJrcS2mJPMEzP1Pt0Bm4d4QlL-yRT-SFd2lZS-pCgNMs" "D1W_YpRPEwOWvG6b32690r2jZ47soMZo9wGzjb_7OMg0LOL-bSf63kpaSH" "SXndS5z5rexMdbBYUsLA9e-KXBdQOS-UTo7WTBEMa2R2CapHg665xsmtdV" "MTBQY4uDZlxvb3qCo5ZwKh9kG4LT6_I5IhlJH7aGhyxXFvUK-DWNmoudF8" "NAco9_h9iaGNj8q2ethFkMLs91kzk2PAcDTW9gb54h4FRWyuXpoQ\"," "\"e\":\"AQAB\"," "\"d\":\"Eq5xpGnNCivDflJsRQBXHx1hdR1k6Ulwe2JZD50LpXyWPEAeP88vLNO97I" "jlA7_GQ5sLKMgvfTeXZx9SE-7YwVol2NXOoAJe46sui395IW_GO-pWJ1O0" "BkTGoVEn2bKVRUCgu-GjBVaYLU6f3l9kJfFNS3E0QbVdxzubSu3Mkqzjkn" "439X0M_V51gfpRLI9JYanrC4D4qAdGcopV_0ZHHzQlBjudU2QvXt4ehNYT" "CBr6XCLQUShb1juUO1ZdiYoFaFQT5Tw8bGUl_x_jTj3ccPDVZFD9pIuhLh" "BOneufuBiB4cS98l2SR_RQyGWSeWjnczT0QU91p1DhOVRuOopznQ\"," "\"p\":\"4BzEEOtIpmVdVEZNCqS7baC4crd0pqnRH_5IB3jw3bcxGn6QLvnEtfdUdi" "YrqBdss1l58BQ3KhooKeQTa9AB0Hw_Py5PJdTJNPY8cQn7ouZ2KKDcmnPG" "BY5t7yLc1QlQ5xHdwW1VhvKn-nXqhJTBgIPgtldC-KDV5z-y2XDwGUc\"," "\"q\":\"uQPEfgmVtjL0Uyyx88GZFF1fOunH3-7cepKmtH4pxhtCoHqpWmT8YAmZxa" "ewHgHAjLYsp1ZSe7zFYHj7C6ul7TjeLQeZD_YwD66t62wDmpe_HlB-TnBA" "-njbglfIsRLtXlnDzQkv5dTltRJ11BKBBypeeF6689rjcJIDEz9RWdc\"," "\"dp\":\"BwKfV3Akq5_MFZDFZCnW-wzl-CCo83WoZvnLQwCTeDv8uzluRSnm71I3Q" "CLdhrqE2e9YkxvuxdBfpT_PI7Yz-FOKnu1R6HsJeDCjn12Sk3vmAktV2zb" "34MCdy7cpdTh_YVr7tss2u6vneTwrA86rZtu5Mbr1C1XsmvkxHQAdYo0\"," "\"dq\":\"h_96-mK1R_7glhsum81dZxjTnYynPbZpHziZjeeHcXYsXaaMwkOlODsWa" "7I9xXDoRwbKgB719rrmI2oKr6N3Do9U0ajaHF-NKJnwgjMd2w9cjz3_-ky" "NlxAr2v4IKhGNpmM5iIgOS1VZnOZ68m6_pbLBSp3nssTdlqvd0tIiTHU\"," "\"qi\":\"IYd7DHOhrWvxkwPQsRM2tOgrjbcrfvtQJipd-DlcxyVuuM9sQLdgjVk2o" "y26F0EmpScGLq2MowX7fhd_QJQ3ydy5cY7YIBi87w93IKLEdfnbJtoOPLU" "W0ITrJReOgo1cq9SbsxYawBgfp_gh6A5603k2-ZQwVK0JKSHuLFkuQ3U\"" "}"; oidc_jose_error_t err; apr_hash_t *keys = apr_hash_make(pool); oidc_jwk_t *jwk_e = NULL; oidc_jwk_t *jwk_s = NULL; oidc_jwt_t *jwt = NULL; TST_ASSERT_ERR("oidc_jwk_parse (encryption key)", _jwk_parse(pool, ek, &jwk_e, &err) == 0, pool, err); apr_hash_set(keys, "dummy", APR_HASH_KEY_STRING, jwk_e); TST_ASSERT_ERR("oidc_jwt_parse", oidc_jwt_parse(pool, s, &jwt, keys, FALSE, &err), pool, err); oidc_jwk_destroy(jwk_e); TST_ASSERT_ERR("oidc_jwk_parse (signing key)", _jwk_parse(pool, sk, &jwk_s, &err) == 0, pool, err); apr_hash_set(keys, "dummy", APR_HASH_KEY_STRING, jwk_s); TST_ASSERT_ERR("oidc_jwt_verify", oidc_jwt_verify(pool, jwt, keys, &err), pool, err); oidc_jwk_destroy(jwk_s); TST_ASSERT_STR("header.alg", jwt->header.alg, CJOSE_HDR_ALG_RS256); 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; } #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_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_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_memset(&provider, 0, sizeof(provider)); provider.issuer = "https://idp.example.com"; provider.authorization_endpoint_url = "https://idp.example.com/authorize"; provider.scope = "openid"; provider.client_id = "client_id"; provider.client_secret = NULL; provider.response_type = "code"; provider.auth_request_params = "jan=piet&foo=#"; provider.request_object = NULL; provider.auth_request_method = OIDC_AUTH_REQUEST_METHOD_GET; 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, provider.issuer); oidc_proto_state_set_response_type(proto_state, provider.response_type); oidc_proto_state_set_timestamp_now(proto_state); TST_ASSERT("oidc_proto_authorization_request (1)", oidc_proto_authorization_request(r, &provider, NULL, redirect_uri, state, proto_state, NULL, NULL, NULL, NULL) == HTTP_MOVED_TEMPORARILY); TST_ASSERT_STR("oidc_proto_authorization_request (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 *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, c->provider.issuer); c->provider.end_session_endpoint = "https://idp.example.com/endsession"; c->provider.logout_request_params = "client_id=myclient&foo=bar"; r->args = "logout=https%3A%2F%2Fwww.example.com%2Floggedout"; TST_ASSERT("oidc_handle_logout (1)", oidc_handle_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"); return 0; } static char *test_proto_validate_nonce(request_rec *r) { oidc_cfg *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_validate_nonce (1)", oidc_proto_validate_nonce(r, c, &c->provider, nonce, jwt)); TST_ASSERT("oidc_proto_validate_nonce (2)", oidc_proto_validate_nonce(r, c, &c->provider, 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_base64url_encode(r, &s_jwt_header_encoded, s_jwt_header, _oidc_strlen(s_jwt_header), 1); char *s_jwt_payload_encoded = NULL; oidc_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_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_validate_jwt(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_get_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_get_current_url(r, 0); TST_ASSERT_STR("test_current_url (2a)", url, "https://www.example.com/test?foo=bar¶m1=value1"); url = oidc_get_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_get_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_get_current_url(r, 0); TST_ASSERT_STR("test_current_url (4a)", url, "https://www.example.com/test?foo=bar¶m1=value1"); url = oidc_get_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_get_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_get_current_url(r, 0); TST_ASSERT_STR("test_current_url (5a)", url, "https://www.example.com/test?foo=bar¶m1=value1"); url = oidc_get_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_get_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_get_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_get_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_get_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_get_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_get_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_get_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_get_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_get_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_get_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_get_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_get_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"); 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_util_hdr_in_accept_contains(r, "text/html") != 0); TST_ASSERT("Accept: application/json (ie 9/10/11)", oidc_util_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_util_hdr_in_accept_contains(r, "text/html") != 0); TST_ASSERT("Accept: application/json (firefox)", oidc_util_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_util_hdr_in_accept_contains(r, "text/html") != 0); TST_ASSERT("Accept: application/json (chrome/safari)", oidc_util_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_util_hdr_in_accept_contains(r, "text/html") != 0); TST_ASSERT("Accept: application/json (safari 5)", oidc_util_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_util_hdr_in_accept_contains(r, "text/html") == 0); TST_ASSERT("Accept: */* (ie 8)", oidc_util_hdr_in_accept_contains(r, "*/*") != 0); TST_ASSERT("Accept: application/json (ie 8)", oidc_util_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_util_hdr_in_accept_contains(r, "text/html") != 0); TST_ASSERT("Accept: application/json (edge)", oidc_util_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_util_hdr_in_accept_contains(r, "text/html") != 0); TST_ASSERT("Accept: application/json (opera)", oidc_util_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_util_hdr_in_accept_contains(r, "text/html") == 0); TST_ASSERT("Accept: application/json (opera)", oidc_util_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"; claims = "{" "\"sub\": \"stef\"," "\"nested\": {" "\"level1\": {" "\"level2\": \"hans\"" "}," "\"nestedarray\": [" "\"b\"," "\"c\"," "true" "]," "\"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\"" "]" "}"; 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_worker24(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_worker24(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_worker24(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_worker24(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_worker24(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_worker24(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_worker24(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_worker24(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_worker24(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_worker24(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_worker24(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_worker24(r, json, require_args, parsed_require_args, oidc_authz_match_claim); TST_ASSERT("auth status (12: nested expression)", rc == AUTHZ_GRANTED); require_args = "Require claim nested.level1.level2~zan."; parsed_require_args->filename = require_args; rc = oidc_authz_worker24(r, json, require_args, parsed_require_args, oidc_authz_match_claim); TST_ASSERT("auth status (13: nested expression)", rc == AUTHZ_DENIED); require_args = "Require claim nested.nestedarray~."; parsed_require_args->filename = require_args; rc = oidc_authz_worker24(r, json, require_args, parsed_require_args, oidc_authz_match_claim); TST_ASSERT("auth status (14: nested array expression)", rc == AUTHZ_GRANTED); require_args = "Require claim nested.nestedarray~.b"; parsed_require_args->filename = require_args; rc = oidc_authz_worker24(r, json, require_args, parsed_require_args, oidc_authz_match_claim); TST_ASSERT("auth status (15: nested array expression)", rc == AUTHZ_DENIED); require_args = "Require claim email~...$"; parsed_require_args->filename = require_args; rc = oidc_authz_worker24(r, json, require_args, parsed_require_args, oidc_authz_match_claim); TST_ASSERT("auth status (16: expression)", rc == AUTHZ_DENIED); require_args = "Require claim sub~...$"; parsed_require_args->filename = require_args; rc = oidc_authz_worker24(r, json, require_args, parsed_require_args, oidc_authz_match_claim); TST_ASSERT("auth status (17: expression)", rc == AUTHZ_GRANTED); require_args = "Require claim https://company.com/productAccess:snake2"; parsed_require_args->filename = require_args; rc = oidc_authz_worker24(r, json, require_args, parsed_require_args, oidc_authz_match_claim); TST_ASSERT("auth status (18: key in namespaced array)", 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 *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_", ",", TRUE, FALSE, 0); 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_", ",", TRUE, FALSE, OIDC_PASS_APP_INFO_AS_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_", ",", TRUE, FALSE, OIDC_PASS_APP_INFO_AS_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 *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_plaintext_decrypt, pool); TST_RUN(test_plaintext_decrypt2, pool); TST_RUN(test_plaintext_decrypt_symmetric, pool); TST_RUN(test_jwt_decrypt, 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); return 0; } typedef struct oidc_dir_cfg oidc_dir_cfg; 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 *cfg = oidc_create_server_config(request->pool, request->server); cfg->provider.issuer = "https://idp.example.com"; cfg->provider.authorization_endpoint_url = "https://idp.example.com/authorize"; cfg->provider.scope = "openid"; cfg->provider.client_id = "client_id"; cfg->redirect_uri = "https://www.example.com/protected/"; oidc_dir_cfg *d_cfg = oidc_create_dir_config(request->pool, NULL); request->server->module_config = apr_pcalloc(request->pool, sizeof(ap_conf_vector_t *) * kEls); request->per_dir_config = apr_pcalloc(request->pool, sizeof(ap_conf_vector_t *) * 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 = &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->post_config(request->server) != OK) { printf("cfg->cache->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; }