pax_global_header00006660000000000000000000000064147530526040014520gustar00rootroot0000000000000052 comment=e9f7e92ff015af4bac75f888c9296e8d5fd27be6 liboauth2-2.1.0/000077500000000000000000000000001475305260400134115ustar00rootroot00000000000000liboauth2-2.1.0/.autotools000066400000000000000000000060531475305260400154470ustar00rootroot00000000000000 liboauth2-2.1.0/.clang-format000066400000000000000000000002641475305260400157660ustar00rootroot00000000000000BasedOnStyle: LLVM IndentWidth: 8 UseTab: Always BreakBeforeBraces: Linux AllowShortIfStatementsOnASingleLine: false IndentCaseLabels: false AllowShortFunctionsOnASingleLine: None liboauth2-2.1.0/.cproject000066400000000000000000000476131475305260400152360ustar00rootroot00000000000000 make all true true false make clean true true false make clang-format true true false make check true true false liboauth2-2.1.0/.dockerignore000066400000000000000000000012561475305260400160710ustar00rootroot00000000000000Makefile.in aclocal.m4 ar-lib compile config.guess config.sub configure depcomp install-sh ltmain.sh missing Makefile config.log config.status libtool test-driver build/ liboauth2-*-coverage/ liboauth2-*-coverage.info liboauth2.pc .libs/**/* .libs .settings/**/* .settings src/.deps/**/* src/.deps src/.libs/**/* src/.libs src/.dirstamp src/cache/.deps/**/* src/cache/.deps src/cache/.libs/**/* src/cache/.libs src/cache/.dirstamp src/cfg/.deps/**/* src/cfg/.deps src/cfg/.libs/**/* src/cfg/.libs src/cfg/.dirstamp src/server/.deps/**/* src/server/.deps src/server/.libs/**/* src/server/.libs src/server/.dirstamp *.log *.trs check_liboauth2 *.la liboauth2_apache.pc liboauth2_nginx.pc liboauth2-2.1.0/.github/000077500000000000000000000000001475305260400147515ustar00rootroot00000000000000liboauth2-2.1.0/.github/workflows/000077500000000000000000000000001475305260400170065ustar00rootroot00000000000000liboauth2-2.1.0/.github/workflows/archs.yml000066400000000000000000000051101475305260400206260ustar00rootroot00000000000000name: Archs on: push: branches: [ master ] pull_request: branches: [ master ] jobs: build: runs-on: ubuntu-22.04 name: ${{ matrix.arch }} strategy: fail-fast: false matrix: include: # - arch: armv6 # distro: bookworm # # alpine_latest # - arch: armv7 # distro: ubuntu_latest - arch: aarch64 distro: ubuntu_latest - arch: riscv64 distro: ubuntu_latest - arch: s390x distro: ubuntu_latest - arch: ppc64le distro: ubuntu_latest steps: - uses: actions/checkout@v4 - uses: uraimo/run-on-arch-action@v2 name: Build id: build with: arch: ${{ matrix.arch }} distro: ${{ matrix.distro }} install: | apt-get update -y apt-get install -y apache2-dev libcjose-dev libssl-dev check pkg-config wget apt-get install -y libjansson-dev libcurl4-openssl-dev libhiredis-dev libmemcached-dev redis-server memcached libpcre2-dev libpcre2-8-0 libjq-dev apt-get install -y build-essential libpcre3 libpcre3-dev zlib1g zlib1g-dev libxml2 libxml2-dev uuid-dev cd /tmp apt-get install -y nginx nginx -V 2>&1 | grep -m1 version | cut -d: -f2 | cut -d/ -f2 | cut -d" " -f1 > /tmp/nginx-version nginx -V 2>&1 | grep configure | cut -d: -f2- > /tmp/nginx-args apt-get remove -y nginx && apt-get autoremove -y sed -i s/--add-dynamic-module=[^[:space:]]*//g /tmp/nginx-args sed -i s/--with-http_xslt_module=[^[:space:]]*//g /tmp/nginx-args sed -i s/--with-http_geoip_module=[^[:space:]]*//g /tmp/nginx-args sed -i s/--with-stream_geoip_module=[^[:space:]]*//g /tmp/nginx-args sed -i s/--with-http_image_filter_module=[^[:space:]]*//g /tmp/nginx-args wget --progress=bar:force:noscroll --no-check-certificate https://nginx.org/download/nginx-$(cat /tmp/nginx-version).tar.gz tar zxf nginx-$(cat /tmp/nginx-version).tar.gz cd nginx-$(cat /tmp/nginx-version) echo "Configuring NGINX-$(cat /tmp/nginx-version): ./configure $(cat /tmp/nginx-args)" cat /tmp/nginx-args | xargs ./configure cd .. ln -s nginx-$(cat /tmp/nginx-version) nginx run: | ./autogen.sh ./configure --with-nginx=/tmp/nginx --with-jq=/usr sed -i "s/-l/#-l/g" /etc/memcached.conf service memcached start && service redis-server start && make check || (cat test-suite.log && exit -1) liboauth2-2.1.0/.github/workflows/build.yml000066400000000000000000000031101475305260400206230ustar00rootroot00000000000000name: Build on: [push, pull_request] jobs: build: runs-on: ubuntu-latest name: NGINX v${{ matrix.nginx_version }} strategy: matrix: nginx_version: [1.18.0, 1.24.0, 1.26.2, 1.27.3] services: redis: image: redis # options: >- # --health-cmd "redis-cli ping" # --health-interval 10s # --health-timeout 5s # --health-retries 5 ports: - 6379:6379 memcached: image: memcached ports: - 11211:11211 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 libmemcached-dev libpcre2-dev libpcre2-8-0 libjq-dev sudo apt-get install build-essential libpcre3 libpcre3-dev zlib1g zlib1g-dev libxml2 libxml2-dev uuid-dev cd /tmp wget https://nginx.org/download/nginx-${{ matrix.nginx_version }}.tar.gz tar zxvf nginx-${{ matrix.nginx_version }}.tar.gz ln -s nginx-${{ matrix.nginx_version }} nginx cd /tmp/nginx && ./configure --with-debug - name: Configure run: | ./autogen.sh ./configure --with-nginx=/tmp/nginx --with-jq=/usr - name: Make run: make - name: Test run: make check || (cat test-suite.log && exit -1) - name: Distcheck run: make distcheck DISTCHECK_CONFIGURE_FLAGS="--with-nginx=/tmp/nginx" DESTDIR="/tmp/liboauth2" liboauth2-2.1.0/.github/workflows/codeql-analysis.yml000066400000000000000000000016601475305260400226240ustar00rootroot00000000000000name: "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' ] steps: - name: Checkout 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 libmemcached-dev libpcre2-dev libpcre2-8-0 - name: CodeQL uses: github/codeql-action/init@v3 with: languages: ${{ matrix.language }} - name: Configure run: | ./autogen.sh ./configure - name: Make run: make - name: Perform CodeQL Analysis uses: github/codeql-action/analyze@v3liboauth2-2.1.0/.gitignore000066400000000000000000000006621475305260400154050ustar00rootroot00000000000000/Makefile.in /aclocal.m4 /ar-lib /compile /config.guess /config.sub /configure /depcomp /install-sh /ltmain.sh /missing /Makefile /config.log /config.status /libtool /test-driver /build/ /liboauth2-*-coverage/ /liboauth2-*-coverage.info /liboauth2.pc /.libs/ /*.log /*.trs /check_liboauth2 /*.la /liboauth2_apache.pc /liboauth2_nginx.pc /ChangeLog.org /.settings/ /autom4te.cache/ /config.guess~ /config.sub~ /configure~ /install-sh~ liboauth2-2.1.0/.project000066400000000000000000000017601475305260400150640ustar00rootroot00000000000000 liboauth2 org.eclipse.cdt.autotools.core.genmakebuilderV2 org.eclipse.cdt.managedbuilder.core.genmakebuilder clean,full,incremental, org.eclipse.cdt.managedbuilder.core.ScannerConfigBuilder full,incremental, org.eclipse.cdt.core.cnature org.eclipse.cdt.managedbuilder.core.managedBuildNature org.eclipse.cdt.managedbuilder.core.ScannerConfigNature org.eclipse.cdt.autotools.core.autotoolsNatureV2 org.eclipse.cdt.core.ccnature liboauth2-2.1.0/AUTHORS000066400000000000000000000012621475305260400144620ustar00rootroot00000000000000The primary author of liboauth2 is: Hans Zandbelt Thanks to the following people for contributing to liboauth2 by reporting bugs, providing fixes, suggesting useful features or other: Nicolas Mora Phil Brown Alexander Bokovoy Niebardzo Mikael Broadfoot Robert Title may-day Pavel Anpin smanolache pladen Drew liboauth2-2.1.0/ChangeLog000066400000000000000000000205561475305260400151730ustar00rootroot0000000000000002/12/2025 - add updated AWS ALB JWKs retrieval supporting new "signer"/"region" logic and key rotation closes: https://github.com/OpenIDC/mod_oauth2/issues/73 - release 2.1.0 01/02/2024 - update copyright year to 2025 09/13/2024 - add support for introspection.token_param_name; closes #57 09/11/2024 - add (optional) JQ support with caching in oauth2_jq_filter - add "json_payload_claim" claim option to oauth2_cfg_target_pass_t - make oauth2_jwt_create public in jose.h and add a json_payload parameter - nginx: fix memory leak in _oauth2_nginx_ssl_cert_set - bump to 2.1.0dev 08/22/2024 - change LICENSE to Apache 2.0 - release 2.0.0 08/02/2024 - correct error log upon mismatch in "iss" claim: id_token->JWT 06/24/2024 - allow to use local file through file:// protocol for metadata or jwks; see #51; thanks @pladen - bump to 1.6.4dev 06/24/2024 - release 1.6.3 06/20/2024 - nginx: add nginx_oauth2_set_require to be used with OAuth2Require etc. see OpenIDC/ngx_oauth2_module#7; thanks @smanolache and @pladen 06/19/2024 - add NGINX macros/functions for setting claim variables in the request context see OpenIDC/ngx_oauth2_module#7; thanks @smanolache and @pladen - allow NGINX primitives in an if block within a location block in the http block - bump to 1.6.3dev 06/05/2024 - release 1.6.2 05/31/2024 - refactor NGINX port extraction so it works with NGINX >= 1.27.0; closes #49; thanks @anpin - add PCRE2_CFLAGS to cache/server object linking 03/11/2024 - release 1.6.1 03/08/2024 - add support for RFC 8705 OAuth 2.0 Mutual-TLS Certificate-Bound Access Tokens to the NGINX binding 03/04/2024 - add support for Redis 6 ACL username based authentication; see: OpenIDC/mod_oauth2#63 - bump to 1.6.1dev 12/06/2023 - add support for the OAuth 2.0 Client Credentials grant type - use libcurl version macro that works on older platforms - release 1.6.0 11/08/2023 - update DPoP support to RFC 9449 - release 1.5.2 08/31/2023 - printout more cjose error details when errors occur verifying JWT access tokens 06/29/2023 - fix timing issue in check_openidc.c; closes #47 - bump to 1.5.2dev 04/19/2023 - add issuer validation for JWT access tokens when configured through OAuth2Verify metadata; closes #44; thanks @chris-crunchr - release 1.5.1 04/14/2023 - add support for resolving provider metadata from a Discovery endpoint URL; see https://github.com/OpenIDC/ngx_openidc_module/issues/18 - bump to 1.5.1dev 03/22/2023 - add error logs about missing or invalid "active" boolean claim in introspection response 03/08/2023 - move repo to OpenIDC github organization 03/07/2023 - release 1.5.0 03/03/2023 - add support for regular expressions in Require statements; see https://github.com/zmartzone/mod_oauth2/discussions/39 - depend on libpcre2 - fix memory leak in _oauth2_jose_options_jwk_set_rsa_key when using OpenSSL 3.x - bump to 1.5.0dev 03/01/2023 - add support for introspect.params; see https://github.com/zmartzone/mod_oauth2/discussions/44 - release 1.4.5.5 01/22/2023 - hack for el7/x86 where openssl 1.0.2 and openssl 1.1.1 are installed for respectively Apache and NGINX 1.20.1 - bump to 1.4.5.5rc0 01/21/2023 - revert header_add/header_set change - release 1.4.5.4 01/20/2023 - don't add WWW-Authenticate header(s) but (over)write a single one; see zmartzone/mod_oauth2#42 - release 1.4.5.3 12/14/2022 - fix NGINX https schema detection - bump to 1.4.5.3dev 12/06/2022 - change Apache module init info log - release 1.4.5.2 11/30/2022 - initialize check_oauth2 properly; call OPENSSL_init_crypto for OpenSSL >= 1.1.0 11/23/2022 - add JANSSON_LIBS to apache/nginx LIBADD; closes #40; thanks @pskopnik - bump to 1.4.5.2dev 08/22/2022 - fix concurrency issue when using OAuth2Verify metadata; see #37; thanks @rtitle - fix memory leak in cURL writeback function - release 1.4.5.1 07/28/2022 - fix memory leak when using OAuth2Verify metadata 07/27/2022 - use main request for Apache request contexts - set refresh to true when getting jwsk_uri results from cache - print warning when cjose_jws_verify fails - avoid using cjose_jwk_retain because it is not thread safe - release 1.4.5 06/24/2022 - add cjose, curl and ssl to liboauth2.pc.in - add add curl and cjose flags to liboauth2_cache_la_CFLAGS 04/16/2022 - fix file cache so we do not try to remove a file that was cleaned just before; see #33 - fix tests for client_secret_jwt and private_key_jwt so encoded JWT comparison works for cjose >= 0.6.2 - release 1.4.4.2 03/06/2022 - add support for OpenSSL 3.0; closes #31 - bump to 1.5.0dev 03/03/2022 - fix race condition and potential crash in curl usage in oauth2_url_decode see zmartzone/mod_oauth2#27; thanks @rtitle - release 1.4.4.1 12/23/2021 - allow deprecated declarations to build with OpenSSL 3.0; see #31 - release 1.4.4 12/22/2021 - hash the cache encryption key to a string instead of bytes - Makefile.am improvements: - move OpenSSL libs go generic libraries so cache files compile with the right flags - use ${srcdir} to conform to distcheck - add Github Actions CI; remove Travis 10/12/2021 - make outgoing_proxy an endpoint property - accommodate for NULL key in oauth2_cache_get and oauth2_cache_set - release 1.4.3.2 10/11/2021 - add outgoing_proxy option to verify context - correct remote_user debug printout - release 1.4.3.1 06/21/2021 - printout remote username claim when not found, for debugging purposes 06/10/2021 - use encrypted JWTs for storing encrypted cache contents and avoid using static AAD/IV closes #26; thanks @niebardzo - avoid memory leaks on JWT validation errors - release 1.4.3 06/07/2021 - correct iat slack validation defaults, see https://github.com/zmartzone/mod_oauth2/discussions/20 thanks @DrakezulsMinimalism - release 1.4.2.1 05/28/2021 - add Travis and LGTM 05/25/2021 - set memory alignment of shm cache structs to 64 bytes; see #21 and #24 - release 1.4.2 04/19/2021 - apache: use include directory from APXS; thanks @abbra - pass missing argument to oauth2_error in _oauth2_dpop_jti_validate; thanks @abbra 02/02/2021 - avoid creating files for anonymous shared memory segments; see #18 - release 1.4.1 01/30/2021 - fix Apache cleanup routines; see zmartzone/liboauth2#18 and zmartzone/mod_oauth2#7 01/26/2021 - add support for RFC 8705 OAuth 2.0 Mutual-TLS Certificate-Bound Access Tokens https://tools.ietf.org/html/rfc8705; thanks @vdzhuvinov 12/23/2020 - use per-process semaphore locking to prevent multi-process issue; see #18 - release 1.4.0.1 12/21/2020 - release 1.4.0 12/03/2020 - add oauth2_cfg_openidc_set_options for configurable state cookie handling 12/02/2020 - cleanup OIDC expired/superfluous state cookies; closes zmartzone/ngx_openidc_module#6 11/13/2020 - add support for PKCE 11/12/2020 - separate OpenID client configs and named providers - fix parsing in oauth2_cfg_set_flag_slot - add configurable state and session cookie paths 11/11/2020 - fix session cache handler cloning - support configurable cookie path for session cookie 11/09/2020 - refactored caching; use named caches consistently 11/08/2020 - use endpoint more consistently - harmonize naming of endpoint, endpoint auth and ropc 11/07/2020 - don't use automake config.h; closes #10; thanks @babelouest 10/07/2020 - add support for DPOP bound access tokens - bump to 1.4.0-dev 02/27/2020 - lock access to cache globals - log corrections and improvements 02/26/2020 - resolve some TODOs; valgrind - bump to 1.3.0 02/25/2020 - change to named sessions 02/21/2020 - add serialized id_token to session - externalize oauth2_jose_jwt_verify and allow verification context to be NULL - bump to 1.2.5 02/13/2020 - add userinfo endpoint request and claims - bump to 1.2.4 - change to named cache configurations 02/10/2020 - implement session expiry checks - bump to 1.2.3 02/05/2020 - add missing ROPC config functions - bump to 1.2.2 02/04/2020 - add generic endpoint config struct and ROPC client capability - bump to 1.2.1 and bump copyright year 01/31/2020 - sane session cfg defaults 09/12/2019 - change http request header function naming - more openidc handling - bump to 1.2.0 09/02/2019 - fix type (auth->client_secret_jwt.aud = NULL); closes #3; thanks @pengjiaoyang 08/19/2019 - add first outline of openidc and sessions 07/03/2019 - return status code from HTTP callouts - bump to version 1.1.1 07/01/2019 - encapsulate oauth2_log_sink_t - bump to version 1.1.0 05/20/2019 - add Apache Require claim authorization functions - bump to version 1.0.1 03/22/2019 - initial import of version 1.0.0 liboauth2-2.1.0/LICENSE000066400000000000000000000236761475305260400144340ustar00rootroot00000000000000 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 liboauth2-2.1.0/Makefile.am000066400000000000000000000146721475305260400154570ustar00rootroot00000000000000ACLOCAL_AMFLAGS=-I m4 EXTRA_DIST = autogen.sh ChangeLog README.md LICENSE # # generic # AM_CPPFLAGS = -Wall -Werror -Wno-error=deprecated-declarations -I${srcdir}/include -I${srcdir}/src @JANSSON_CFLAGS@ @OPENSSL_CFLAGS@ AM_CPPFLAGS += $(CODE_COVERAGE_CPPFLAGS) $(CODE_COVERAGE_CFLAGS) AM_LDFLAGS = --coverage LDADD = @JANSSON_LIBS@ @OPENSSL_LIBS@ LDADD += $(CODE_COVERAGE_LIBS) # # headers # includesubdir = $(includedir)/oauth2 includesub_HEADERS = \ include/oauth2/cache.h \ include/oauth2/http.h \ include/oauth2/ipc.h \ include/oauth2/jose.h \ include/oauth2/log.h \ include/oauth2/mem.h \ include/oauth2/proto.h \ include/oauth2/oauth2.h \ include/oauth2/openidc.h \ include/oauth2/session.h \ include/oauth2/cfg.h \ include/oauth2/util.h \ include/oauth2/version.h if HAVE_LIBJQ includesub_HEADERS += \ include/oauth2/jq.h endif # # liboauth # lib_LTLIBRARIES = liboauth2.la liboauth2_la_pkgconfigdir = $(libdir)/pkgconfig liboauth2_la_pkgconfig_DATA = liboauth2.pc liboauth2_la_CFLAGS = @CURL_CFLAGS@ @CJOSE_CFLAGS@ @PCRE2_CFLAGS@ liboauth2_la_LIBADD = @CURL_LIBS@ @CJOSE_LIBS@ @PCRE2_LIBS@ liboauth2_la_SOURCES = \ src/version.c \ src/mem.c \ src/log.c \ src/util_int.h \ src/util.c \ src/cfg_int.h \ src/cfg/cfg.c \ src/cfg/auth.c \ src/cfg/source.c \ src/cfg/cache_cfg.c \ src/cfg/verify.c \ src/cfg/target.c \ src/cfg/proto_cfg.c \ src/cfg/openidc_cfg.c \ src/cfg/session_cfg.c \ src/ipc.c \ src/cache_int.h \ src/cache/shm.c \ src/cache/file.c \ src/jose_int.h \ src/jose.c \ src/http.c \ src/proto.c \ src/oauth2_int.h \ src/oauth2.c \ src/dpop.c \ src/session.c \ src/openidc_int.h \ src/openidc/provider.c \ src/openidc/resolver.c \ src/openidc/client.c \ src/openidc/state.c \ src/openidc/openidc.c if HAVE_LIBJQ AM_CPPFLAGS += -DHAVE_LIBJQ liboauth2_la_CFLAGS += @JQ_CFLAGS@ liboauth2_la_LIBADD += @JQ_LIBS@ liboauth2_la_SOURCES += src/jq.c endif # # cache # noinst_LTLIBRARIES = liboauth2_cache.la liboauth2_la_LIBADD += liboauth2_cache.la liboauth2_cache_la_SOURCES = src/cache.c liboauth2_cache_la_CPPFLAGS = $(AM_CPPFLAGS) liboauth2_cache_la_CFLAGS = @CURL_CFLAGS@ @CJOSE_CFLAGS@ @PCRE2_CFLAGS@ if HAVE_LIBMEMCACHE liboauth2_cache_la_CPPFLAGS += -DHAVE_LIBMEMCACHE liboauth2_la_LIBADD += liboauth2_memcache.la noinst_LTLIBRARIES += liboauth2_memcache.la liboauth2_memcache_la_SOURCES = src/cache/memcache.c liboauth2_memcache_la_CFLAGS = @MEMCACHE_CFLAGS@ liboauth2_memcache_la_LIBADD = @MEMCACHE_LIBS@ endif if HAVE_LIBHIREDIS liboauth2_cache_la_CPPFLAGS += -DHAVE_LIBHIREDIS liboauth2_la_LIBADD += liboauth2_redis.la noinst_LTLIBRARIES += liboauth2_redis.la liboauth2_redis_la_SOURCES = src/cache/redis.c liboauth2_redis_la_CFLAGS = @HIREDIS_CFLAGS@ liboauth2_redis_la_LIBADD = @HIREDIS_LIBS@ endif # # liboauth2_apache # if HAVE_APACHE lib_LTLIBRARIES += liboauth2_apache.la liboauth2_apache_la_pkgconfigdir = $(libdir)/pkgconfig liboauth2_apache_la_pkgconfig_DATA = liboauth2_apache.pc liboauth2_apache_la_CPPFLAGS = $(AM_CPPFLAGS) -DHAVE_APACHE liboauth2_apache_la_CFLAGS = @APACHE_CFLAGS@ @PCRE2_CFLAGS@ liboauth2_apache_la_LIBADD = liboauth2.la @APR_LIBS@ @JANSSON_LIBS@ includesub_HEADERS += \ include/oauth2/apache.h liboauth2_apache_la_SOURCES = \ src/server/apache.c endif # # liboauth2_nginx # if HAVE_NGINX lib_LTLIBRARIES += liboauth2_nginx.la liboauth2_nginx_la_pkgconfigdir = $(libdir)/pkgconfig liboauth2_nginx_la_pkgconfig_DATA = liboauth2_nginx.pc liboauth2_nginx_la_CPPFLAGS = $(AM_CPPFLAGS) -DHAVE_NGINX liboauth2_nginx_la_CFLAGS = @NGINX_CFLAGS@ @PCRE2_CFLAGS@ liboauth2_nginx_la_LIBADD = liboauth2.la @NGINX_LIBS@ @JANSSON_LIBS@ includesub_HEADERS += \ include/oauth2/nginx.h liboauth2_nginx_la_SOURCES = \ src/server/nginx.c endif # # check # if HAVE_CHECK TESTS = check_liboauth2 check_PROGRAMS = $(TESTS) check_liboauth2_CPPFLAGS = $(liboauth2_cache_la_CPPFLAGS) check_liboauth2_CFLAGS = @OPENSSL_CFLAGS@ @CURL_CFLAGS@ @CJOSE_CFLAGS@ @PCRE2_CFLAGS@ @JQ_CFLAGS@ @CHECK_CFLAGS@ check_liboauth2_LDADD = liboauth2.la if HAVE_APACHE check_liboauth2_CPPFLAGS += $(liboauth2_apache_la_CPPFLAGS) check_liboauth2_CFLAGS += $(liboauth2_apache_la_CFLAGS) check_liboauth2_LDADD += liboauth2_apache.la ${liboauth2_apache_la_LIBADD} endif if HAVE_NGINX check_liboauth2_CPPFLAGS += $(liboauth2_nginx_la_CPPFLAGS) check_liboauth2_CFLAGS += $(liboauth2_nginx_la_CFLAGS) check_liboauth2_LDADD += liboauth2_nginx.la endif check_liboauth2_LDADD += @OPENSSL_LIBS@ @CURL_LIBS@ @CJOSE_LIBS@ @PCRE2_LIBS@ @CHECK_LIBS@ check_liboauth2_SOURCES = \ test/check_liboauth2.h \ test/check_liboauth2.c \ test/check_version.c \ test/check_mem.c \ test/check_log.c \ test/check_cfg.c \ test/check_util.c \ test/check_ipc.c \ test/check_cache.c \ test/check_jose.c \ test/check_http.c \ test/check_proto.c \ test/check_oauth2.c \ test/check_openidc.c \ test/server_stubs.c \ test/provider.json \ test/client.json if HAVE_LIBJQ check_liboauth2_SOURCES += \ test/check_jq.c endif if HAVE_APACHE check_liboauth2_SOURCES += \ test/check_apache.c endif if HAVE_NGINX check_liboauth2_SOURCES += \ test/check_nginx.c endif endif #@CODE_COVERAGE_RULES@ clang-format: clang-format -style=file -i `find . -name *.[ch]` test: check TAG=liboauth2/test docker: clean docker build --progress plain -f test/Dockerfile . -t $(TAG) docker-check: docker docker run -it --rm $(TAG):latest /bin/bash -c "./start.sh && make check" docker-coverage: clean docker build --build-arg CONFIGURE_ARGS="--enable-code-coverage" -f test/Dockerfile . -t $(TAG) docker run -it --rm $(TAG):latest /bin/bash -c "./start.sh && make check-code-coverage" docker-valgrind: docker docker run -it --rm -e CK_FORK=no $(TAG):latest /bin/bash -c "./start.sh && /usr/bin/valgrind --leak-check=full --show-leak-kinds=definite --read-inline-info=yes --keep-debuginfo=yes .libs/check_liboauth2" docker-valgrind-%: docker docker run -it --rm -e CK_FORK=no -e CK_RUN_SUITE=${subst docker-valgrind-,,$@} $(TAG):latest /bin/bash -c "./start.sh && /usr/bin/valgrind --leak-check=full --show-leak-kinds=definite --read-inline-info=yes --keep-debuginfo=yes .libs/check_liboauth2" docker-gdb: docker docker run --cap-add=SYS_PTRACE --security-opt seccomp=unconfined -it --rm -e CK_FORK=no $(TAG):latest /bin/bash -c "./start.sh && /usr/bin/gdb .libs/check_liboauth2" docker-gdb-%: docker docker run --cap-add=SYS_PTRACE --security-opt seccomp=unconfined -it --rm -e CK_FORK=no -e CK_RUN_SUITE=${subst docker-gdb-,,$@} $(TAG):latest /bin/bash -c "./start.sh && /usr/bin/gdb .libs/check_liboauth2" liboauth2-2.1.0/README.md000066400000000000000000000103011475305260400146630ustar00rootroot00000000000000[![Build Status](https://github.com/OpenIDC/liboauth2/actions/workflows/build.yml/badge.svg)](https://github.com/OpenIDC/liboauth2/actions/workflows/build.yml) [![Architectures Status](https://github.com/OpenIDC/liboauth2/actions/workflows/archs.yml/badge.svg)](https://github.com/OpenIDC/liboauth2/actions/workflows/archs.yml) [![CodeQL Analysis](https://github.com/OpenIDC/liboauth2/actions/workflows/codeql-analysis.yml/badge.svg)](https://github.com/OpenIDC/liboauth2/actions/workflows/codeql-analysis.yml) # liboauth2 Generic library to build C-based OAuth 2.x and OpenID Connect servers and clients e.g. web-server plugins. ## Overview - extends [cjose](https://github.com/OpenIDC/cjose) into OAuth 2.x and OpenID Connect specific claims, secrets, and hashes - adds OAuth 2.x and OpenID Connect protocols by abstracting HTTP requests and responses from web server implementation specifics - reusable code across other OAuth 2.x and REST related protocols e.g. token exchange with endpoint authentication, source token retrieval, target pass settings etc. - generic code with plugins for Apache, NGINX, and possibly more (e.g. Envoy, HA Proxy, IIS) - configurable cache backend/size/options per cache element type - cookie-based session management (i.e. enforce inactivity timeout, expiry) ## Features - [OpenID Connect 1.0](https://openid.net/specs/openid-connect-core-1_0.html) - OAuth 2.0 Resource Owner Password Credentials ([RFC 6749](https://tools.ietf.org/html/rfc6749#section-4.3)) - OAuth 2.0 Token Introspection ([RFC 7662](https://tools.ietf.org/html/rfc7662)) - JWT bearer token validation using JWK, JWKS URI, shared symmetric key, X.509 cert, and RSA public key ([RFC 6750](https://tools.ietf.org/html/rfc6750)) - OAuth 2.0 Authorization Server Metadata ([RFC 8414](https://tools.ietf.org/html/rfc8414)) - Proof Key for Code Exchange (PKCE) by OAuth Public Clients ([RFC 7636](https://tools.ietf.org/html/rfc7636)) - OAuth 2.0 Mutual-TLS (MTLS) Certificate-Bound Access Tokens ([RFC 8705](https://tools.ietf.org/html/rfc8705)) - OAuth 2.0 Demonstrating Proof of Possession (DPoP) ([RFC9449](https://tools.ietf.org/html/rfc9449)) - Amazon ALB [EC key URL based `x-amzn-oidc-data` JWT verification](https://docs.aws.amazon.com/elasticloadbalancing/latest/application/listener-authenticate-users.html) - endpoint authentication methods: `client_secret_basic`, `client_secret_post`, [`client_secret_jwt`, `private_key_jwt`](https://tools.ietf.org/html/rfc7523), [TLS client certificate](https://tools.ietf.org/id/draft-ietf-oauth-mtls), and HTTP basic authentication - configurable cache backends: shared memory, file-based, memcache, and Redis - retrieving a token from a header, a query parameter, a post parameter, or a cookie - setting a token as a header, a query parameter, a post parameter, or a cookie - Apache and NGINX bindings ## Dependencies liboauth2 depends on the following libraries: - [`openssl`](https://www.openssl.org/) for SSL and crypto support - [`libcurl`](https://curl.haxx.se/libcurl/) for HTTP client support - [`jansson`](http://www.digip.org/jansson/) for JSON parsing - [`cjose`](https://github.com/OpenIDC/cjose) for JSON Object Signing and Encryption (JOSE) support - (optional) [`libmemcached`](https://libmemcached.org) for memcache cache backend support - (optional) [`libhiredis`](https://github.com/redis/hiredis) for Redis cache backend support - (optional) [`Apache 2.x`](https://httpd.apache.org/) for Apache 2.x bindings support - (optional) [`NGINX`](https://nginx.org) for NGINX bindings support - (optional, build time only) [`check`](https://libcheck.github.io/check/) for unit test support ## Support ### Community Support See [Frequently Asked Questions](https://github.com/OpenIDC/liboauth2/wiki) on the Wiki. Ask questions in the [Discussions](https://github.com/OpenIDC/liboauth2/discussions) tracker. ### Commercial Support For commercial support contracts, professional services, training, and use-case specific support, contact [OpenIDC](https://www.openidc.com) at: [sales@openidc.com](mailto:sales@openidc.com) Disclaimer ---------- *This software is open sourced by OpenIDC. For commercial support you can contact [OpenIDC](https://www.openidc.com) as described above in the [Support](#support) section.* liboauth2-2.1.0/autogen.sh000077500000000000000000000000761475305260400154150ustar00rootroot00000000000000#!/bin/sh autoreconf --force --install rm -rf autom4te.cache/ liboauth2-2.1.0/configure.ac000066400000000000000000000110231475305260400156740ustar00rootroot00000000000000AC_INIT([liboauth2],[2.1.0],[hans.zandbelt@openidc.com]) AM_INIT_AUTOMAKE([foreign no-define subdir-objects]) AC_CONFIG_MACRO_DIR([m4]) AM_PROG_AR LT_INIT([dlopen]) AC_PROG_CC AX_CODE_COVERAGE PKG_CHECK_MODULES(OPENSSL, openssl) AC_SUBST(OPENSSL_CFLAGS) AC_SUBST(OPENSSL_LIBS) PKG_CHECK_MODULES(CURL, libcurl) AC_SUBST(CURL_CFLAGS) AC_SUBST(CURL_LIBS) PKG_CHECK_MODULES(JANSSON, jansson) AC_SUBST(JANSSON_CFLAGS) AC_SUBST(JANSSON_LIBS) PKG_CHECK_MODULES(CJOSE, cjose) AC_SUBST(CJOSE_CFLAGS) AC_SUBST(CJOSE_LIBS) PKG_CHECK_MODULES(PCRE2, libpcre2-8) AC_SUBST(PCRE2_CFLAGS) AC_SUBST(PCRE2_LIBS) AC_ARG_WITH([memcache], AS_HELP_STRING([--with-memcache], [build with Memcache cache support [default=autodetect]]),) if test "x$with_memcache" != "xno"; then PKG_CHECK_MODULES([MEMCACHE], [libmemcached >= 1.0], [have_memcache="yes"], [have_memcache="no"]) fi AM_CONDITIONAL(HAVE_LIBMEMCACHE, [test x"$have_memcache" = "xyes"]) AC_SUBST(MEMCACHE_CFLAGS) AC_SUBST(MEMCACHE_LIBS) if test x"$have_memcache" = "xyes"; then MEMCACHE_PC=', libmemcached >= 1.0' fi AC_SUBST(MEMCACHE_PC) AC_ARG_WITH([redis], AS_HELP_STRING([--with-redis], [build with Redis cache support [default=autodetect]]),) if test "x$with_redis" != "xno"; then PKG_CHECK_MODULES([HIREDIS], [hiredis], [have_redis="yes"], [have_redis="no"]) fi AM_CONDITIONAL(HAVE_LIBHIREDIS, [test x"$have_redis" = "xyes"]) AC_SUBST(HIREDIS_CFLAGS) AC_SUBST(HIREDIS_LIBS) if test x"$have_redis" = "xyes"; then HIREDIS_PC=', hiredis' fi AC_SUBST(HIREDIS_PC) have_jq=no AC_ARG_WITH([jq], AS_HELP_STRING([--with-jq=PATH], [location of your libjq installation])]) if test -n "$with_jq" ; then if test "x$with_jq" != "xno"; then if test "x$with_jq" = "xyes"; then PKG_CHECK_MODULES([JQ], [libjq >= 1.6], [have_jq="yes"], [have_jq="no"]) else 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_jq=yes], [have_jq=no]) LDFLAGS="$JQ_LIBS $LDFLAGS" AC_CHECK_LIB([jq], [jq_init], [have_jq=yes], [have_jq=no]) if test "x$have_jq" = "xno" ; then AC_MSG_WARN("cannot find library for -ljq.") JQ_CFLAGS= JQ_LIBS= fi fi fi fi AM_CONDITIONAL(HAVE_LIBJQ, [test x"$have_jq" = "xyes"]) AC_SUBST(JQ_CFLAGS) AC_SUBST(JQ_LIBS) if test x"$have_jq" = "xyes"; then # note the leading comma and space(s) JQ_LIBS_PC=', libjq >= 1.6' JQ_CFLAGS_PC=' -DOAUTH2_WITH_JQ' fi AC_SUBST(JQ_LIBS_PC) AC_SUBST(JQ_CFLAGS_PC) AC_ARG_WITH([apache], AS_HELP_STRING([--with-apache], [build with Apache support [default=autodetect]]),) 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, [apxs2 apxs])]) if test "x$with_apache" != "xno"; then PKG_CHECK_MODULES([APR], [apr-1, apr-util-1], [have_apache="yes"], [have_apache="no"]) AS_IF([test "x${APXS}" != "x" -a -x "${APXS}"], [AC_MSG_NOTICE([apxs found at $APXS])], [AC_MSG_FAILURE(["apxs not found. Use --with-apxs"])]) APACHE_CFLAGS="`${APXS} -q CFLAGS` `${APXS} -q EXTRA_CPPFLAGS` -I`${APXS} -q INCLUDEDIR` ${APR_CFLAGS}" fi AM_CONDITIONAL(HAVE_APACHE, [test x"$have_apache" = "xyes"]) AC_SUBST(APR_LIBS) AC_SUBST(APACHE_CFLAGS) AC_ARG_WITH([nginx], AS_HELP_STRING([--with-nginx=DIR], [build with NGINX support [default=no]]), [have_nginx="yes"], [have_nginx="no"]) if test x"$have_nginx" = "xyes" ; then if test ! -d "$withval"; then AC_MSG_ERROR([Could not find NGINX. Please specify the path to the NGINX sources using the --with-nginx=/full/path/to/nginx- option.]) else NGINX_CFLAGS="-I $withval/src/core -I $withval/src/event -I $withval/src/event/modules -I $withval/src/os/unix -I $withval/objs -I $withval/src/http -I $withval/src/http/v2 -I $withval/src/http/modules" NGINX_LIBS= CPPFLAGS_SAVE="$CPPFLAGS" CPPFLAGS="$NGINX_CFLAGS $CPPFLAGS" AC_CHECK_HEADER([nginx.h], [], [have_nginx="no" NGINX_CFLAGS= NGINX_LIBS=]) CPPFLAGS="$CPPFLAGS_SAVE" fi fi AM_CONDITIONAL(HAVE_NGINX, [test x"$have_nginx" = "xyes"]) AC_SUBST(NGINX_CFLAGS) AC_SUBST(NGINX_LIBS) PKG_CHECK_MODULES([CHECK], [check >= 0.9.4], [have_check="yes"], [ AC_MSG_WARN([Check not found; cannot run unit tests!]); [have_check="no"] ]) AM_CONDITIONAL(HAVE_CHECK, [test x"$have_check" = "xyes"]) AC_SUBST(CHECK_CFLAGS) AC_SUBST(CHECK_LIBS) AC_CONFIG_FILES([ include/oauth2/version.h liboauth2.pc liboauth2_apache.pc liboauth2_nginx.pc Makefile ]) AC_OUTPUT liboauth2-2.1.0/include/000077500000000000000000000000001475305260400150345ustar00rootroot00000000000000liboauth2-2.1.0/include/oauth2/000077500000000000000000000000001475305260400162365ustar00rootroot00000000000000liboauth2-2.1.0/include/oauth2/.gitignore000066400000000000000000000000721475305260400202250ustar00rootroot00000000000000/version.h /stamp-h1 /config.h.in~ /config.h.in /config.h liboauth2-2.1.0/include/oauth2/apache.h000066400000000000000000000375101475305260400176360ustar00rootroot00000000000000#ifndef _OAUTH2_APACHE_H_ #define _OAUTH2_APACHE_H_ /*************************************************************************** * * Copyright (C) 2018-2025 - ZmartZone Holding BV * * 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. * * @Author: Hans Zandbelt - hans.zandbelt@openidc.com * **************************************************************************/ #include #include #include #include #include #include #include // avoid errors about ap_auto_config overriding these, so undefine first #undef PACKAGE_BUGREPORT #undef PACKAGE_NAME #undef PACKAGE_STRING #undef PACKAGE_TARNAME #undef PACKAGE_VERSION #include #include #include #include extern oauth2_cfg_server_callback_funcs_t oauth2_apache_server_callback_funcs; /* * logging */ extern oauth2_uint_t log_level_log2apache[]; extern oauth2_uint_t log_level_apache2oauth2[]; #ifndef APLOG_USE_MODULE #define APLOG_USE_MODULE(foo) \ extern module AP_MODULE_DECLARE_DATA foo##_module; \ AP_MAYBE_UNUSED(static int *const aplog_module_index) = \ &(foo##_module.module_index) #endif #define OAUTH2_APACHE_LOG(foo) \ \ APLOG_USE_MODULE(foo); \ \ static void foo##_log_server( \ oauth2_log_sink_t *sink, const char *filename, unsigned long line, \ const char *function, oauth2_log_level_t level, const char *msg) \ { \ ap_log_error( \ filename, line, \ aplog_module_index ? *aplog_module_index \ : APLOG_NO_MODULE, \ log_level_log2apache[level], 0, \ (const server_rec *)oauth2_log_sink_ctx_get(sink), \ "%s: %s", function, msg); \ } \ \ static void foo##_log_request( \ oauth2_log_sink_t *sink, const char *filename, unsigned long line, \ const char *function, oauth2_log_level_t level, const char *msg) \ { \ ap_log_rerror( \ filename, line, \ aplog_module_index ? *aplog_module_index \ : APLOG_NO_MODULE, \ log_level_log2apache[level], 0, \ (const request_rec *)oauth2_log_sink_ctx_get(sink), \ "%s: %s", function, msg); \ } /* * parent/child cleanup */ apr_status_t oauth2_apache_child_cleanup(void *data, module *m, const char *package_name_version); #define OAUTH2_APACHE_CHILD_CLEANUP(foo) \ static apr_status_t foo##_child_cleanup(void *data) \ { \ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, \ (const server_rec *)data, "%s: %s", __FUNCTION__, \ "enter"); \ return oauth2_apache_child_cleanup( \ data, &foo##_module, OAUTH2_PACKAGE_NAME_VERSION); \ } apr_status_t oauth2_apache_parent_cleanup(void *data, module *m, const char *package_name_version); #define OAUTH2_APACHE_PARENT_CLEANUP(foo) \ static apr_status_t foo##_parent_cleanup(void *data) \ { \ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, \ (const server_rec *)data, "%s: %s", __FUNCTION__, \ "enter"); \ return oauth2_apache_parent_cleanup( \ data, &foo##_module, OAUTH2_PACKAGE_NAME_VERSION); \ } /* * post config */ int oauth2_apache_post_config(apr_pool_t *pool, apr_pool_t *p1, apr_pool_t *p2, server_rec *s, module *m, const char *package_name_version, apr_status_t (*parent_cleanup)(void *), apr_status_t (*child_cleanup)(void *)); #define OAUTH2_APACHE_POST_CONFIG(foo) foo##_post_config #define OAUTH2_APACHE_POST_CONFIG_IMPL(foo) \ static apr_status_t OAUTH2_APACHE_POST_CONFIG(foo)( \ apr_pool_t * pool, apr_pool_t * p1, apr_pool_t * p2, \ server_rec * s) \ { \ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, \ (const server_rec *)s, "%s: %s", __FUNCTION__, \ "enter"); \ return oauth2_apache_post_config( \ pool, p1, p2, s, &foo##_module, \ OAUTH2_PACKAGE_NAME_VERSION, foo##_parent_cleanup, \ foo##_child_cleanup); \ } /* * directory config */ #define OAUTH2_APACHE_CMD_ARGS1(module, type, primitive, func, member) \ static const char *apache_##module##_set_##primitive( \ cmd_parms *cmd, void *m, const char *v1) \ { \ oauth2_apache_cfg_srv_t *srv_cfg = ap_get_module_config( \ cmd->server->module_config, &module##_module); \ type *cfg = (type *)m; \ (void)cfg; \ return func(srv_cfg->log, member, v1); \ } #define OAUTH2_APACHE_CMD_ARGS2(module, type, primitive, func, member) \ static const char *apache_##module##_set_##primitive( \ cmd_parms *cmd, void *m, const char *v1, const char *v2) \ { \ oauth2_apache_cfg_srv_t *srv_cfg = ap_get_module_config( \ cmd->server->module_config, &module##_module); \ type *cfg = (type *)m; \ (void)cfg; \ return func(srv_cfg->log, member, v1, v2); \ } #define OAUTH2_APACHE_CMD_ARGS3(module, type, primitive, func, member) \ static const char *apache_##module##_set_##primitive( \ cmd_parms *cmd, void *m, const char *v1, const char *v2, \ const char *v3) \ { \ oauth2_apache_cfg_srv_t *srv_cfg = ap_get_module_config( \ cmd->server->module_config, &module##_module); \ type *cfg = (type *)m; \ (void)cfg; \ return func(srv_cfg->log, member, v1, v2, v3); \ } #define OAUTH2_APACHE_CMD_ARGSV4(module, type, primitive, func, member) \ static const char *apache_##module##_set_##primitive( \ cmd_parms *cmd, void *m, int argc, char *const argv[]) \ { \ oauth2_apache_cfg_srv_t *srv_cfg = ap_get_module_config( \ cmd->server->module_config, &module##_module); \ type *cfg = (type *)m; \ (void)cfg; \ return func(srv_cfg->log, member, argc > 0 ? argv[0] : NULL, \ argc > 1 ? argv[1] : NULL, \ argc > 2 ? argv[2] : NULL, \ argc > 3 ? argv[3] : NULL); \ } #define OAUTH2_APACHE_CMD_ARGS(module, nargs, cmd, member, desc) \ AP_INIT_TAKE##nargs(cmd, apache_##module##_set_##member, NULL, \ RSRC_CONF | ACCESS_CONF | OR_AUTHCFG, desc) #define OAUTH2_APACHE_DIR_CTX(type, method) oauth2_##type##_dir_##method #define OAUTH2_APACHE_DIR_CTX_FUNCS(type) \ apr_status_t OAUTH2_APACHE_DIR_CTX(type, cleanup)(void *data) \ { \ oauth2_##type##_t *cfg = (oauth2_##type##_t *)data; \ oauth2_##type##_free(NULL, cfg); \ return APR_SUCCESS; \ } \ \ void *OAUTH2_APACHE_DIR_CTX(type, create)(apr_pool_t * pool, \ char *path) \ { \ oauth2_##type##_t *cfg = oauth2_##type##_create(NULL, path); \ apr_pool_cleanup_register( \ pool, cfg, OAUTH2_APACHE_DIR_CTX(type, cleanup), \ OAUTH2_APACHE_DIR_CTX(type, cleanup)); \ return cfg; \ } \ \ static void *OAUTH2_APACHE_DIR_CTX(type, merge)(apr_pool_t * pool, \ void *b, void *a) \ { \ oauth2_##type##_t *cfg = \ OAUTH2_APACHE_DIR_CTX(type, create)(pool, NULL); \ oauth2_##type##_t *base = b; \ oauth2_##type##_t *add = a; \ oauth2_##type##_merge(NULL, cfg, base, add); \ return cfg; \ } /* * server config */ typedef struct oauth2_apache_cfg_srv_t { oauth2_log_sink_t *sink; oauth2_log_t *log; bool is_child; } oauth2_apache_cfg_srv_t; void *oauth2_apache_cfg_srv_create(apr_pool_t *pool, server_rec *s, oauth2_log_function_t server_log_cb); void *oauth2_apache_cfg_srv_merge(apr_pool_t *pool, void *b, void *a); /* * handlers */ #define OAUTH2_APACHE_HANDLERS(foo) \ OAUTH2_APACHE_CHILD_CLEANUP(foo) \ OAUTH2_APACHE_PARENT_CLEANUP(foo) \ OAUTH2_APACHE_POST_CONFIG_IMPL(foo) /* * module config */ #define OAUTH2_APACHE_COMMANDS(foo) foo##_commands #define OAUTH2_APACHE_REGISTER_HOOKS(foo) foo##_register_hooks #define OAUTH2_APACHE_MODULE_DECLARE_EX(foo, dir_create, dir_merge) \ \ void *oauth2_apache_##foo##_cfg_srv_create(apr_pool_t *pool, \ server_rec *s) \ { \ return oauth2_apache_cfg_srv_create(pool, s, \ foo##_log_server); \ } \ \ module AP_MODULE_DECLARE_DATA foo##_module = { \ STANDARD20_MODULE_STUFF, \ dir_create, \ dir_merge, \ oauth2_apache_##foo##_cfg_srv_create, \ oauth2_apache_cfg_srv_merge, \ OAUTH2_APACHE_COMMANDS(foo), \ OAUTH2_APACHE_REGISTER_HOOKS(foo)}; #define OAUTH2_APACHE_MODULE_DECLARE(foo, type) \ \ OAUTH2_APACHE_DIR_CTX_FUNCS(type) \ \ OAUTH2_APACHE_MODULE_DECLARE_EX(foo, \ OAUTH2_APACHE_DIR_CTX(type, create), \ OAUTH2_APACHE_DIR_CTX(type, merge)) /* * request context */ #define OAUTH2_APACHE_REQUEST_CTX(r, foo) \ oauth2_apache_request_context( \ r, foo##_log_request, \ "oauth2_" OAUTH2_TOSTRING(foo) "_module_user_data_key"); typedef struct oauth2_apache_request_ctx_t { oauth2_log_t *log; oauth2_http_request_t *request; request_rec *r; } oauth2_apache_request_ctx_t; oauth2_apache_request_ctx_t * oauth2_apache_request_context(request_rec *r, oauth2_log_function_t request_log_cb, const char *user_data_key); /* * misc */ bool oauth2_apache_http_request_set(oauth2_log_t *log, oauth2_http_request_t *request, request_rec *r); int oauth2_apache_return_www_authenticate(oauth2_cfg_source_token_t *cfg, oauth2_apache_request_ctx_t *ctx, int status_code, const char *error, const char *error_description); bool oauth2_apache_request_header_set(oauth2_log_t *log, void *rec, const char *name, const char *value); void oauth2_apache_hdr_out_add(oauth2_log_t *log, const request_rec *r, const char *name, const char *value); void oauth2_apache_scrub_headers(oauth2_apache_request_ctx_t *ctx, oauth2_cfg_target_pass_t *target_pass); bool oauth2_apache_set_request_user(oauth2_cfg_target_pass_t *target_pass, oauth2_apache_request_ctx_t *ctx, json_t *json_token); void oauth2_apache_target_pass(oauth2_apache_request_ctx_t *ctx, oauth2_cfg_target_pass_t *target_pass, const char *target_token, json_t *json_token); bool oauth2_apache_http_response_set(oauth2_log_t *log, oauth2_http_response_t *response, request_rec *r); void oauth2_apache_request_state_set_json(oauth2_apache_request_ctx_t *ctx, const char *key, json_t *claims); void oauth2_apache_request_state_get_json(oauth2_apache_request_ctx_t *ctx, const char *key, json_t **claims); typedef bool (*oauth2_apache_authz_match_claim_fn_type)( oauth2_apache_request_ctx_t *, const char *const, const json_t *const); bool oauth2_apache_authz_match_claim(oauth2_apache_request_ctx_t *ctx, const char *const attr_spec, const json_t *const claims); authz_status oauth2_apache_authorize(oauth2_apache_request_ctx_t *ctx, const json_t *const claims, const char *require_args, oauth2_apache_authz_match_claim_fn_type match_claim_fn); #endif /* _OAUTH2_APACHE_H_ */ liboauth2-2.1.0/include/oauth2/cache.h000066400000000000000000000044001475305260400174500ustar00rootroot00000000000000#ifndef _OAUTH2_CACHE_H_ #define _OAUTH2_CACHE_H_ /*************************************************************************** * * Copyright (C) 2018-2025 - ZmartZone Holding BV * * 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. * * @Author: Hans Zandbelt - hans.zandbelt@openidc.com * **************************************************************************/ #include "oauth2/log.h" #include "oauth2/util.h" typedef struct oauth2_cache_t oauth2_cache_t; typedef bool (*oauth2_cache_init_function)(oauth2_log_t *log, oauth2_cache_t *, const oauth2_nv_list_t *options); typedef bool (*oauth2_cache_post_config_function)(oauth2_log_t *log, oauth2_cache_t *); typedef bool (*oauth2_cache_child_init_function)(oauth2_log_t *log, oauth2_cache_t *); typedef bool (*oauth2_cache_get_function)(oauth2_log_t *log, oauth2_cache_t *, const char *key, char **value); typedef bool (*oauth2_cache_set_function)(oauth2_log_t *log, oauth2_cache_t *, const char *key, const char *value, oauth2_time_t expiry); typedef bool (*oauth2_cache_free_function)(oauth2_log_t *log, oauth2_cache_t *); typedef struct oauth2_cache_type_t { const char *name; bool encrypt_by_default; oauth2_cache_init_function init; oauth2_cache_post_config_function post_config; oauth2_cache_child_init_function child_init; oauth2_cache_get_function get; oauth2_cache_set_function set; oauth2_cache_free_function free; } oauth2_cache_type_t; oauth2_cache_t *oauth2_cache_obtain(oauth2_log_t *log, const char *name); bool oauth2_cache_get(oauth2_log_t *log, oauth2_cache_t *ctx, const char *key, char **value); bool oauth2_cache_set(oauth2_log_t *log, oauth2_cache_t *ctx, const char *key, const char *value, oauth2_time_t ttl_s); #endif /* _OAUTH2_CACHE_H_ */ liboauth2-2.1.0/include/oauth2/cfg.h000066400000000000000000000221071475305260400171500ustar00rootroot00000000000000#ifndef _OAUTH2_CFG_H_ #define _OAUTH2_CFG_H_ /*************************************************************************** * * Copyright (C) 2018-2025 - ZmartZone Holding BV * * 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. * * @Author: Hans Zandbelt - hans.zandbelt@openidc.com * **************************************************************************/ #include #include #include #include "oauth2/util.h" #define OAUTH2_CFG_FLAG_UNSET (oauth2_flag_t) - 1 #define OAUTH2_CFG_UINT_UNSET (oauth2_uint_t) - 1 #define OAUTH2_CFG_TIME_UNSET (oauth2_time_t) - 1 /* * generic */ const char *oauth2_cfg_set_flag_slot(void *cfg, size_t offset, const char *value); const char *oauth2_cfg_set_uint_slot(void *cfg, size_t offset, const char *value); const char *oauth2_cfg_set_time_slot(void *cfg, size_t offset, const char *value); const char *oauth2_cfg_set_str_slot(void *cfg, size_t offset, const char *value); #define OAUTH2_CFG_TYPE_DECLARE(module, object) \ OAUTH2_TYPE_DECLARE(module, object) \ void oauth2_##module##_##object##_merge( \ oauth2_log_t *, oauth2_##module##_##object##_t *, \ oauth2_##module##_##object##_t *, \ oauth2_##module##_##object##_t *); const char *oauth2_crypto_passphrase_set(oauth2_log_t *log, void *dummy, const char *passphrase); const char *oauth2_crypto_passphrase_get(oauth2_log_t *log); /* * cache */ char *oauth2_cfg_set_cache(oauth2_log_t *log, void *dummy, const char *type, const char *options); /* * webserver callbacks */ typedef bool(oauth2_cfg_env_get_cb)(oauth2_log_t *log, void *ctx, const char *key, char **value); typedef bool(oauth2_cfg_env_set_cb)(oauth2_log_t *log, void *ctx, const char *key, const char *value); typedef bool(oauth2_cfg_form_post_read_cb)(oauth2_log_t *log, void *ctx, oauth2_nv_list_t **params); typedef struct oauth2_cfg_server_callback_funcs_t { oauth2_cfg_env_get_cb *get; oauth2_cfg_env_set_cb *set; oauth2_cfg_form_post_read_cb *form_post; } oauth2_cfg_server_callback_funcs_t; /* * endpoint auth */ /* * * * client_secret_basic client_id=&client_secret= * client_secret_post client_id=&client_secret= * client_secret_jwt client_id=&client_secret=&aud= * private_key_jwt jwk=&aud= * client_cert cert=&key= * basic username=&password= */ typedef enum oauth2_cfg_endpoint_auth_type_t { OAUTH2_ENDPOINT_AUTH_NONE, OAUTH2_ENDPOINT_AUTH_CLIENT_SECRET_BASIC, OAUTH2_ENDPOINT_AUTH_CLIENT_SECRET_POST, OAUTH2_ENDPOINT_AUTH_CLIENT_SECRET_JWT, OAUTH2_ENDPOINT_AUTH_PRIVATE_KEY_JWT, OAUTH2_ENDPOINT_AUTH_CLIENT_CERT, OAUTH2_ENDPOINT_AUTH_BASIC } oauth2_cfg_endpoint_auth_type_t; OAUTH2_CFG_TYPE_DECLARE(cfg, endpoint_auth) char *oauth2_cfg_set_endpoint_auth(oauth2_log_t *log, oauth2_cfg_endpoint_auth_t *auth, const char *type, const oauth2_nv_list_t *params, const char *prefix); oauth2_cfg_endpoint_auth_type_t oauth2_cfg_endpoint_auth_type(const oauth2_cfg_endpoint_auth_t *auth); /* * endpoint */ OAUTH2_CFG_TYPE_DECLARE(cfg, endpoint) char *oauth2_cfg_set_endpoint(oauth2_log_t *log, oauth2_cfg_endpoint_t *cfg, const char *url, const oauth2_nv_list_t *params, const char *prefix); const char *oauth2_cfg_endpoint_get_url(const oauth2_cfg_endpoint_t *cfg); void oauth2_cfg_endpoint_set_url(oauth2_cfg_endpoint_t *cfg, const char *url); const oauth2_cfg_endpoint_auth_t * oauth2_cfg_endpoint_get_auth(const oauth2_cfg_endpoint_t *cfg); oauth2_flag_t oauth2_cfg_endpoint_get_ssl_verify(const oauth2_cfg_endpoint_t *cfg); oauth2_uint_t oauth2_cfg_endpoint_get_http_timeout(const oauth2_cfg_endpoint_t *cfg); const char * oauth2_cfg_endpoint_get_outgoing_proxy(const oauth2_cfg_endpoint_t *cfg); /* * token verify */ typedef enum oauth2_cfg_token_verify_type_t { OAUTH2_TOKEN_VERIFY_BEARER, OAUTH2_TOKEN_VERIFY_DPOP, OAUTH2_TOKEN_VERIFY_MTLS } oauth2_cfg_token_verify_type_t; /* * [] * * plain|b64|hex [kid=] | * pem [kid=] | * pubkey [kid=] | * jwk | * jwks_uri * [type=&refresh=&ssl_verify=] pubkey_uri * [type=&refresh=&ssl_verify=] * * introspect * */ OAUTH2_CFG_TYPE_DECLARE(cfg, token_verify) char *oauth2_cfg_token_verify_add_options(oauth2_log_t *log, oauth2_cfg_token_verify_t **verify, const char *type, const char *value, const char *options); /* * token in request */ typedef struct oauth2_cfg_token_in_envvar_t { char *name; } oauth2_cfg_token_in_envvar_t; typedef struct oauth2_cfg_token_in_header_t { char *name; char *type; } oauth2_cfg_token_in_header_t; typedef struct oauth2_token_in_query_t { char *param_name; } oauth2_cfg_token_in_query_t; typedef struct oauth2_cfg_token_in_post_t { char *param_name; } oauth2_cfg_token_in_post_t; typedef struct oauth2_cfg_token_in_cookie_t { char *name; } oauth2_cfg_token_in_cookie_t; typedef struct oauth2_cfg_token_in_basic_t { // TODO: what will be the username? uint8_t dummy; } oauth2_cfg_token_in_basic_t; typedef struct oauth2_cfg_token_in_t { char value; oauth2_cfg_token_in_envvar_t envvar; oauth2_cfg_token_in_header_t header; oauth2_cfg_token_in_query_t query; oauth2_cfg_token_in_post_t post; oauth2_cfg_token_in_cookie_t cookie; oauth2_cfg_token_in_basic_t basic; } oauth2_cfg_token_in_t; typedef enum oauth2_cfg_token_in_type_t { // undefined = 0 OAUTH2_CFG_TOKEN_IN_ENVVAR = 1, OAUTH2_CFG_TOKEN_IN_HEADER = 2, OAUTH2_CFG_TOKEN_IN_QUERY = 4, OAUTH2_CFG_TOKEN_IN_POST = 8, OAUTH2_CFG_TOKEN_IN_COOKIE = 16, OAUTH2_CFG_TOKEN_IN_BASIC = 32 } oauth2_cfg_token_in_type_t; #define OAUTH2_CFG_TOKEN_IN_ENVVAR_STR "environment" #define OAUTH2_CFG_TOKEN_IN_HEADER_STR "header" #define OAUTH2_CFG_TOKEN_IN_QUERY_STR "query" #define OAUTH2_CFG_TOKEN_IN_POST_STR "post" #define OAUTH2_CFG_TOKEN_IN_COOKIE_STR "cookie" #define OAUTH2_CFG_TOKEN_IN_BASIC_STR "basic" char *oauth2_cfg_token_in_set(oauth2_log_t *log, oauth2_cfg_token_in_t *cfg, const char *method, const oauth2_nv_list_t *params, oauth2_uint_t allowed); /* * source token */ OAUTH2_CFG_TYPE_DECLARE(cfg, source_token) char *oauth2_cfg_source_token_set_accept_in(oauth2_log_t *log, oauth2_cfg_source_token_t *cfg, const char *method, const char *options); char oauth2_cfg_source_token_get_accept_in(oauth2_cfg_source_token_t *cfg); oauth2_flag_t oauth2_cfg_source_token_get_strip(oauth2_cfg_source_token_t *cfg); /* * target pass */ OAUTH2_CFG_TYPE_DECLARE(cfg, target_pass) char *oauth2_cfg_set_target_pass_options(oauth2_log_t *log, oauth2_cfg_target_pass_t *cfg, const char *options); oauth2_flag_t oauth2_cfg_target_pass_get_as_headers(oauth2_cfg_target_pass_t *cfg); oauth2_flag_t oauth2_cfg_target_pass_get_as_envvars(oauth2_cfg_target_pass_t *cfg); const char *oauth2_cfg_target_pass_get_prefix(oauth2_cfg_target_pass_t *cfg); const char * oauth2_cfg_target_pass_get_authn_header(oauth2_cfg_target_pass_t *cfg); const char * oauth2_cfg_target_get_remote_user_claim(oauth2_cfg_target_pass_t *cfg); const char * oauth2_cfg_target_get_json_payload_claim(oauth2_cfg_target_pass_t *cfg); /* * resource owner password credentials */ OAUTH2_CFG_TYPE_DECLARE(cfg, ropc) char *oauth2_cfg_set_ropc(oauth2_log_t *log, oauth2_cfg_ropc_t *cfg, const char *url, const char *options); // TODO: just ropc_exec, no member get functions? const oauth2_cfg_endpoint_t * oauth2_cfg_ropc_get_token_endpoint(oauth2_cfg_ropc_t *cfg); const char *oauth2_cfg_ropc_get_client_id(oauth2_cfg_ropc_t *cfg); const char *oauth2_cfg_ropc_get_username(oauth2_cfg_ropc_t *cfg); const char *oauth2_cfg_ropc_get_password(oauth2_cfg_ropc_t *cfg); const oauth2_nv_list_t * oauth2_cfg_ropc_get_request_parameters(oauth2_cfg_ropc_t *cfg); /* * client credentials */ OAUTH2_CFG_TYPE_DECLARE(cfg, cc) char *oauth2_cfg_set_cc(oauth2_log_t *log, oauth2_cfg_cc_t *cfg, const char *url, const char *options); const oauth2_cfg_endpoint_t * oauth2_cfg_cc_get_token_endpoint(oauth2_cfg_cc_t *cfg); const char *oauth2_cfg_cc_get_client_id(oauth2_cfg_cc_t *cfg); const oauth2_nv_list_t * oauth2_cfg_cc_get_request_parameters(oauth2_cfg_cc_t *cfg); #endif /* _OAUTH2_CFG_H_ */ liboauth2-2.1.0/include/oauth2/http.h000066400000000000000000000232521475305260400173720ustar00rootroot00000000000000#ifndef _OAUTH2_HTTP_H_ #define _OAUTH2_HTTP_H_ /*************************************************************************** * * Copyright (C) 2018-2025 - ZmartZone Holding BV * * 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. * * @Author: Hans Zandbelt - hans.zandbelt@openidc.com * **************************************************************************/ #include "oauth2/cfg.h" #include "oauth2/util.h" #include /* * header names */ // TODO: can these be http.c internal with the set and get functions available? #define OAUTH2_HTTP_HDR_X_FORWARDED_PROTO "X-Forwarded-Proto" #define OAUTH2_HTTP_HDR_X_FORWARDED_PORT "X-Forwarded-Port" #define OAUTH2_HTTP_HDR_X_FORWARDED_HOST "X-Forwarded-Host" #define OAUTH2_HTTP_HDR_HOST "Host" #define OAUTH2_HTTP_HDR_COOKIE "Cookie" #define OAUTH2_HTTP_HDR_CONTENT_TYPE "Content-Type" #define OAUTH2_HTTP_HDR_CONTENT_LENGTH "Content-Length" #define OAUTH2_HTTP_HDR_AUTHORIZATION "Authorization" #define OAUTH2_HTTP_HDR_X_REQUESTED_WITH "X-Requested-With" #define OAUTH2_HTTP_HDR_ACCEPT "Accept" #define OAUTH2_HTTP_HDR_LOCATION "Location" #define OAUTH2_HTTP_HDR_SET_COOKIE "Set-Cookie" #define OAUTH2_HTTP_HDR_BEARER "Bearer" #define OAUTH2_HTTP_HDR_BASIC "Basic" #define OAUTH2_HTTP_HDR_REALM "realm" #define OAUTH2_HTTP_HDR_WWW_AUTHENTICATE "WWW-Authenticate" #define OAUTH2_HTTP_HDR_XML_HTTP_REQUEST "XMLHttpRequest" #define OAUTH2_TLS_CERT_VAR_NAME "SSL_CLIENT_CERT" /* * content type */ #define OAUTH2_CONTENT_TYPE_FORM_ENCODED "application/x-www-form-urlencoded" #define OAUTH2_CONTENT_TYPE_JSON "application/json" #define OAUTH2_CONTENT_TYPE_TEXT_HTML "text/html" #define OAUTH2_CONTENT_TYPE_APP_XHTML_XML "application/xhtml+xml" #define OAUTH2_CONTENT_TYPE_ANY "*/*" /* * protocol */ #define OAUTH2_HTTP_SCHEME_HTTP "http" #define OAUTH2_HTTP_SCHEME_HTTPS "https" typedef enum { OAUTH2_HTTP_METHOD_UNKNOWN, OAUTH2_HTTP_METHOD_GET, OAUTH2_HTTP_METHOD_PUT, OAUTH2_HTTP_METHOD_POST, OAUTH2_HTTP_METHOD_DELETE, OAUTH2_HTTP_METHOD_CONNECT, OAUTH2_HTTP_METHOD_OPTIONS } oauth2_http_method_t; typedef oauth2_uint_t oauth2_http_status_code_t; /* * TODO: make sure the caller calls: * 1. oauth2_http_request_scheme_set to set the "native" URL scheme on * which the request was received i.e. without taking into account headers * 2. oauth2_http_request_hostname_set to set the configured server * hostname * 3. oauth2_http_request_port_set to set the "hative" port on which the * request was received * 4. oauth2_http_request_path_set for the path that is accessed * 5. oauth2_http_request_method_set for the HTTP method used * 6. oauth2_http_request_query_set for the query string * 7. oauth2_http_request_header_set for each incoming header */ OAUTH2_TYPE_DECLARE(http, request) OAUTH2_TYPE_DECLARE_MEMBER_SET_GET(http, request, scheme, char *) OAUTH2_TYPE_DECLARE_MEMBER_SET_GET(http, request, hostname, char *) OAUTH2_TYPE_DECLARE_MEMBER_SET_GET(http, request, path, char *) OAUTH2_TYPE_DECLARE_MEMBER_SET_GET(http, request, method, oauth2_http_method_t) OAUTH2_TYPE_DECLARE_MEMBER_SET_GET(http, request, query, char *) const char *oauth2_http_request_method_get_str(oauth2_log_t *, oauth2_http_request_t *); bool oauth2_http_request_context_set(oauth2_log_t *log, oauth2_http_request_t *request, const char *name, const char *value); const char *oauth2_http_request_context_get( oauth2_log_t *log, const oauth2_http_request_t *request, const char *name); OAUTH2_TYPE_DECLARE(http, response) OAUTH2_TYPE_DECLARE_MEMBER_SET_GET(http, response, headers, oauth2_nv_list_t *) OAUTH2_TYPE_DECLARE_MEMBER_SET_GET(http, response, status_code, oauth2_http_status_code_t) bool oauth2_http_response_header_set(oauth2_log_t *log, oauth2_http_response_t *response, const char *name, const char *value); const char * oauth2_http_response_header_get(oauth2_log_t *log, const oauth2_http_response_t *response, const char *name); const char *oauth2_http_response_header_set_cookie_prefix_get( oauth2_log_t *log, oauth2_http_response_t *response, const char *prefix); bool oauth2_http_response_cookie_set(oauth2_log_t *log, oauth2_http_response_t *response, const char *name, const char *value, const char *path, const bool is_secure, oauth2_time_t max_age); void oauth2_http_response_headers_loop(oauth2_log_t *log, const oauth2_http_response_t *response, oauth2_nv_list_loop_cb_t *callback, void *rec); // typedef bool (*oauth2_http_read_post_callback_t)(oauth2_log_t *log, // oauth2_http_request_t *request, char **data); bool oauth2_http_request_port_set(oauth2_log_t *log, oauth2_http_request_t *r, unsigned long port); char *oauth2_http_request_port_get(oauth2_log_t *log, const oauth2_http_request_t *r); /* * currently accessed url functions */ char *oauth2_http_request_url_base_get(oauth2_log_t *log, const oauth2_http_request_t *r); char *oauth2_http_request_url_path_get(oauth2_log_t *log, const oauth2_http_request_t *request); char *oauth2_http_request_url_get(oauth2_log_t *log, const oauth2_http_request_t *r); /* * request header functions */ OAUTH2_MEMBER_LIST_DECLARE_SET_UNSET_ADD_GET(http, request, header) void oauth2_http_request_headers_loop(oauth2_log_t *log, oauth2_http_request_t *request, oauth2_nv_list_loop_cb_t *callback, void *rec); const char * oauth2_http_request_header_content_type_get(oauth2_log_t *log, const oauth2_http_request_t *r); const char * oauth2_http_request_header_cookie_get(oauth2_log_t *log, const oauth2_http_request_t *r); const char * oauth2_http_request_header_content_length_get(oauth2_log_t *log, const oauth2_http_request_t *r); bool oauth2_http_request_header_content_length_set(oauth2_log_t *log, oauth2_http_request_t *r, size_t len); const char * oauth2_http_request_header_x_requested_with_get(oauth2_log_t *log, const oauth2_http_request_t *r); const char * oauth2_http_request_header_accept_get(oauth2_log_t *log, const oauth2_http_request_t *request); bool oauth2_http_request_is_xml_http_request( oauth2_log_t *log, const oauth2_http_request_t *request); bool oauth2_http_request_is_secure(oauth2_log_t *log, const oauth2_http_request_t *request); /* * request args functions */ char *oauth2_http_url_query_encode(oauth2_log_t *log, const char *url, const oauth2_nv_list_t *args); char *oauth2_http_url_form_encode(oauth2_log_t *log, const oauth2_nv_list_t *args); bool oauth2_http_request_query_param_add(oauth2_log_t *log, oauth2_http_request_t *request, const char *name, const char *value); const char *oauth2_http_request_query_param_get(oauth2_log_t *log, oauth2_http_request_t *request, const char *name); bool oauth2_http_request_query_param_unset(oauth2_log_t *log, oauth2_http_request_t *request, const char *name); /* * http call context object */ OAUTH2_TYPE_DECLARE(http, call_ctx) OAUTH2_TYPE_DECLARE_MEMBER_SET(http, call_ctx, bearer_token, char *) OAUTH2_TYPE_DECLARE_MEMBER_SET(http, call_ctx, content_type, char *) OAUTH2_TYPE_DECLARE_MEMBER_SET(http, call_ctx, outgoing_proxy, char *) OAUTH2_TYPE_DECLARE_MEMBER_SET(http, call_ctx, ca_info, char *) OAUTH2_TYPE_DECLARE_MEMBER_SET(http, call_ctx, ssl_cert, char *) OAUTH2_TYPE_DECLARE_MEMBER_SET(http, call_ctx, ssl_key, char *) OAUTH2_TYPE_DECLARE_MEMBER_SET(http, call_ctx, timeout, int) OAUTH2_TYPE_DECLARE_MEMBER_SET(http, call_ctx, ssl_verify, bool) OAUTH2_MEMBER_LIST_DECLARE_SET_UNSET_ADD_GET(http, call_ctx, cookie) OAUTH2_MEMBER_LIST_DECLARE_SET_UNSET_ADD_GET(http, call_ctx, hdr) bool oauth2_http_call_ctx_basic_auth_set(oauth2_log_t *log, oauth2_http_call_ctx_t *ctx, const char *username, const char *password, bool url_encode); /* * http call functions */ bool oauth2_http_call(oauth2_log_t *log, const char *url, const char *data, oauth2_http_call_ctx_t *ctx, char **response, oauth2_http_status_code_t *status_code); bool oauth2_http_get(oauth2_log_t *log, const char *url, const oauth2_nv_list_t *params, oauth2_http_call_ctx_t *ctx, char **response, oauth2_http_status_code_t *status_code); bool oauth2_http_post_form(oauth2_log_t *log, const char *url, const oauth2_nv_list_t *params, oauth2_http_call_ctx_t *ctx, char **response, oauth2_http_status_code_t *status_code); bool oauth2_http_post_json(oauth2_log_t *log, const char *url, const json_t *json, oauth2_http_call_ctx_t *ctx, char **response, oauth2_http_status_code_t *status_code); /* * http cookie functions */ char *oauth2_http_request_cookie_get(oauth2_log_t *log, oauth2_http_request_t *r, const char *name, bool strip); bool oauth2_http_request_cookie_set(oauth2_log_t *log, oauth2_http_request_t *r, const char *name, const char *value); /* * http auth */ bool oauth2_http_auth_client_cert(oauth2_log_t *log, const char *ssl_cert, const char *ssl_key, oauth2_http_call_ctx_t *ctx); bool oauth2_http_auth_basic(oauth2_log_t *log, const char *username, const char *passwd, oauth2_http_call_ctx_t *ctx); #endif /* _OAUTH2_HTTP_H_ */ liboauth2-2.1.0/include/oauth2/ipc.h000066400000000000000000000037241475305260400171700ustar00rootroot00000000000000#ifndef _OAUTH2_IPC_H_ #define _OAUTH2_IPC_H_ /*************************************************************************** * * Copyright (C) 2018-2025 - ZmartZone Holding BV * * 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. * * @Author: Hans Zandbelt - hans.zandbelt@openidc.com * **************************************************************************/ #include #include #include "oauth2/util.h" // TODO: macro for post_config and child_init functions OAUTH2_TYPE_DECLARE(ipc, mutex) bool oauth2_ipc_mutex_post_config(oauth2_log_t *log, oauth2_ipc_mutex_t *m); bool oauth2_ipc_mutex_lock(oauth2_log_t *log, oauth2_ipc_mutex_t *m); bool oauth2_ipc_mutex_unlock(oauth2_log_t *log, oauth2_ipc_mutex_t *m); OAUTH2_TYPE_DECLARE(ipc, sema) bool oauth2_ipc_sema_post_config(oauth2_log_t *log, oauth2_ipc_sema_t *sema); bool oauth2_ipc_sema_post(oauth2_log_t *log, oauth2_ipc_sema_t *sema); bool oauth2_ipc_sema_wait(oauth2_log_t *log, oauth2_ipc_sema_t *sema); bool oauth2_ipc_sema_trywait(oauth2_log_t *log, oauth2_ipc_sema_t *sema); typedef struct oauth2_ipc_shm_t oauth2_ipc_shm_t; oauth2_ipc_shm_t *oauth2_ipc_shm_init(oauth2_log_t *log, size_t size); void oauth2_ipc_shm_free(oauth2_log_t *, oauth2_ipc_shm_t *); bool oauth2_ipc_shm_post_config(oauth2_log_t *log, oauth2_ipc_shm_t *shm); bool oauth2_ipc_shm_child_init(oauth2_log_t *log, oauth2_ipc_shm_t *shm); void *oauth2_ipc_shm_get(oauth2_log_t *log, oauth2_ipc_shm_t *s); #endif /* _OAUTH2_IPC_H_ */ liboauth2-2.1.0/include/oauth2/jose.h000066400000000000000000000060561475305260400173560ustar00rootroot00000000000000#ifndef _OAUTH2_JOSE_H_ #define _OAUTH2_JOSE_H_ /*************************************************************************** * * Copyright (C) 2018-2025 - ZmartZone Holding BV * * 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. * * @Author: Hans Zandbelt - hans.zandbelt@openidc.com * **************************************************************************/ #include #include #include "oauth2/cfg.h" #include "oauth2/log.h" #include "oauth2/util.h" #define OAUTH2_JOSE_OPENSSL_ALG_SHA1 "sha1" #define OAUTH2_JOSE_OPENSSL_ALG_SHA256 "sha256" #define OAUTH2_JOSE_HDR_TYP "typ" #define OAUTH2_JOSE_HDR_TYP_JWT "JWT" #define OAUTH2_JOSE_JWT_ISS "iss" #define OAUTH2_JOSE_JWT_IAT "iat" #define OAUTH2_JOSE_JWT_EXP "exp" #define OAUTH2_JOSE_JWT_SUB "sub" #define OAUTH2_JOSE_JWT_AUD "aud" typedef struct oauth2_jose_jwk_t oauth2_jose_jwk_t; void oauth2_jose_jwk_release(oauth2_jose_jwk_t *jwk); typedef struct oauth2_jose_jwk_list_t oauth2_jose_jwk_list_t; void oauth2_jose_jwk_list_free(oauth2_log_t *log, oauth2_jose_jwk_list_t *keys); bool oauth2_jose_hash_bytes(oauth2_log_t *log, const char *digest, const unsigned char *src, unsigned int src_len, unsigned char **dst, unsigned int *dst_len); bool oauth2_jose_hash2s(oauth2_log_t *log, const char *digest, const char *src, char **dst); bool oauth2_jose_jwk_create_symmetric(oauth2_log_t *log, const char *client_secret, const char *hash_algo, oauth2_jose_jwk_t **jwk); bool oauth2_jose_encrypt(oauth2_log_t *log, const char *secret, const char *s_sig_payload, char **cser); bool oauth2_jose_jwt_encrypt(oauth2_log_t *log, const char *secret, json_t *payload, char **cser); bool oauth2_jose_decrypt(oauth2_log_t *log, const char *secret, const char *cser, char **result); bool oauth2_jose_jwt_decrypt(oauth2_log_t *log, const char *secret, const char *cser, json_t **result); typedef struct oauth2_jose_jwt_verify_ctx_t oauth2_jose_jwt_verify_ctx_t; bool oauth2_jose_jwt_verify(oauth2_log_t *log, oauth2_jose_jwt_verify_ctx_t *jwt_verify_ctx, const char *token, json_t **json_payload, char **s_payload); bool oauth2_jose_jwk_thumbprint(oauth2_log_t *log, const cjose_jwk_t *jwk, unsigned char **hash_bytes, unsigned int *hash_bytes_len); char *oauth2_jwt_create(oauth2_log_t *log, cjose_jwk_t *jwk, const char *alg, const char *iss, const char *sub, const char *client_id, const char *aud, oauth2_uint_t exp, bool include_iat, bool include_jti, const json_t *json_payload); #endif /* _OAUTH2_JOSE_H_ */ liboauth2-2.1.0/include/oauth2/jq.h000066400000000000000000000022631475305260400170240ustar00rootroot00000000000000#ifndef _OAUTH2_JQ_H #define _OAUTH2_JQ_H /*************************************************************************** * * Copyright (C) 2018-2025 - ZmartZone Holding BV * * 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. * * @Author: Hans Zandbelt - hans.zandbelt@openidc.com * **************************************************************************/ #include "oauth2/cache.h" #include "oauth2/log.h" typedef struct jq_state jq_state; bool oauth2_jq_filter_compile(oauth2_log_t *log, const char *filter, jq_state **r_jq); bool oauth2_jq_filter(oauth2_log_t *log, oauth2_cache_t *cache, const char *input, const char *filter, char **result); #endif /* _OAUTH2_JQ_H */ liboauth2-2.1.0/include/oauth2/log.h000066400000000000000000000104421475305260400171710ustar00rootroot00000000000000#ifndef _OAUTH2_LOG_H_ #define _OAUTH2_LOG_H_ /*************************************************************************** * * Copyright (C) 2018-2025 - ZmartZone Holding BV * * 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. * * @Author: Hans Zandbelt - hans.zandbelt@openidc.com * **************************************************************************/ // don't change this without checking consequences in log.c... typedef enum oauth2_log_level_t { OAUTH2_LOG_ERROR, OAUTH2_LOG_WARN, OAUTH2_LOG_NOTICE, OAUTH2_LOG_INFO, OAUTH2_LOG_DEBUG, OAUTH2_LOG_TRACE1, OAUTH2_LOG_TRACE2 } oauth2_log_level_t; // TODO: instead of sinks, just define oauth2_err_t and use that to return // errors...? // we must use #define to make the __ macro's work... // we must use the target defined log levels... // so #define all of oauth2_error, oauth2_warn, oauth2_debug, oauth2_trace1? // must also keep oauth2_error etc. to printout logs in the test phase (although // could do that with oauth2_err_t) // probably good to print debug traces inside protocol functions! // #define _oauth2_log // ap_log_rerror(APLOG_MARK, level, 0, r,"%s: %s", __FUNCTION__, // apr_psprintf(r->pool, fmt, ##__VA_ARGS__)) // and: // #define_oauth2_log // ap_log_error(APLOG_MARK, level, 0, s, "%s: %s", __FUNCTION__, // apr_psprintf(s->process->pool, fmt, ##__VA_ARGS__)) // can also #define _oauth2_log to a no-op for speed or other reasons #ifndef _oauth2_log #define _oauth2_log(log, level, fmt, ...) \ oauth2_log(log, __FILE__, __LINE__, __FUNCTION__, level, fmt, \ ##__VA_ARGS__) #endif #define oauth2_error(log, fmt, ...) \ _oauth2_log(log, OAUTH2_LOG_ERROR, fmt, ##__VA_ARGS__) #define oauth2_warn(log, fmt, ...) \ _oauth2_log(log, OAUTH2_LOG_WARN, fmt, ##__VA_ARGS__) #define oauth2_notice(log, fmt, ...) \ _oauth2_log(log, OAUTH2_LOG_NOTICE, fmt, ##__VA_ARGS__) #define oauth2_info(log, fmt, ...) \ _oauth2_log(log, OAUTH2_LOG_INFO, fmt, ##__VA_ARGS__) #define oauth2_debug(log, fmt, ...) \ _oauth2_log(log, OAUTH2_LOG_DEBUG, fmt, ##__VA_ARGS__) #define oauth2_trace1(log, fmt, ...) \ _oauth2_log(log, OAUTH2_LOG_TRACE1, fmt, ##__VA_ARGS__) #define oauth2_trace2(log, fmt, ...) \ _oauth2_log(log, OAUTH2_LOG_TRACE2, fmt, ##__VA_ARGS__) /* * log context definitions */ typedef struct oauth2_log_t oauth2_log_t; /* * log sink types */ typedef struct oauth2_log_sink_t oauth2_log_sink_t; typedef void (*oauth2_log_function_t)(oauth2_log_sink_t *sink, const char *filename, unsigned long line, const char *function, oauth2_log_level_t level, const char *msg); /* * API */ extern oauth2_log_sink_t oauth2_log_sink_stderr; extern oauth2_log_sink_t oauth2_log_sink_stdout; void oauth2_log(oauth2_log_t *log, const char *filename, unsigned long line, const char *function, oauth2_log_level_t level, const char *fmt, ...); oauth2_log_sink_t *oauth2_log_sink_create(oauth2_log_level_t level, oauth2_log_function_t callback, void *ctx); void *oauth2_log_sink_ctx_get(oauth2_log_sink_t *sink); oauth2_log_function_t oauth2_log_sink_callback_get(oauth2_log_sink_t *sink); void oauth2_log_sink_add(oauth2_log_t *log, oauth2_log_sink_t *add); void oauth2_log_sink_level_set(oauth2_log_sink_t *sink, oauth2_log_level_t level); /* * internals */ oauth2_log_t *oauth2_log_init(oauth2_log_level_t level, oauth2_log_sink_t *sink); void oauth2_log_free(oauth2_log_t *); #endif /* _OAUTH2_LOG_H_ */ liboauth2-2.1.0/include/oauth2/mem.h000066400000000000000000000036301475305260400171670ustar00rootroot00000000000000#ifndef _OAUTH2_MEM_H_ #define _OAUTH2_MEM_H_ /*************************************************************************** * * Copyright (C) 2018-2025 - ZmartZone Holding BV * * 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. * * @Author: Hans Zandbelt - hans.zandbelt@openidc.com * **************************************************************************/ #include typedef void *(*oauth2_mem_alloc_fn_t)(size_t); typedef void *(*oauth2_mem_realloc_fn_t)(void *, size_t); typedef void (*oauth2_mem_dealloc_fn_t)(void *); oauth2_mem_alloc_fn_t oauth2_mem_get_alloc(); oauth2_mem_realloc_fn_t oauth2_mem_get_realloc(); oauth2_mem_dealloc_fn_t oauth2_mem_get_dealloc(); void oauth2_mem_set_alloc_funcs(oauth2_mem_alloc_fn_t alloc, oauth2_mem_realloc_fn_t realloc, oauth2_mem_dealloc_fn_t dealloc); typedef void *(*oauth2_mem_alloc3_fn_t)(size_t, const char *, int); typedef void *(*oauth2_mem_realloc3_fn_t)(void *, size_t, const char *, int); typedef void (*oauth2_mem_dealloc3_fn_t)(void *, const char *, int); oauth2_mem_alloc3_fn_t oauth2_mem_get_alloc3(); oauth2_mem_realloc3_fn_t oauth2_mem_get_realloc3(); oauth2_mem_dealloc3_fn_t oauth2_mem_get_dealloc3(); void oauth2_mem_set_alloc_ex_funcs(oauth2_mem_alloc3_fn_t alloc3, oauth2_mem_realloc3_fn_t realloc3, oauth2_mem_dealloc3_fn_t dealloc3); void *oauth2_mem_alloc(size_t); void oauth2_mem_free(void *); #endif /* _OAUTH2_MEM_H_ */ liboauth2-2.1.0/include/oauth2/nginx.h000066400000000000000000000240461475305260400175400ustar00rootroot00000000000000#ifndef _OAUTH2_NGINX_H_ #define _OAUTH2_NGINX_H_ /*************************************************************************** * * Copyright (C) 2018-2025 - ZmartZone Holding BV * * 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. * * @Author: Hans Zandbelt - hans.zandbelt@openidc.com * **************************************************************************/ #include #include #include #include #include #include #include #include #include // module #define OAUTH2_NGINX_MODULE(module) \ extern ngx_module_t ngx_##module##_module; \ \ ngx_module_t *ngx_modules[] = {&ngx_##module##_module, NULL}; \ \ char *ngx_module_names[] = {OAUTH2_TOSTRING(ngx_##module##_module), \ NULL}; \ \ char *ngx_module_order[] = {NULL}; // functions #define OAUTH2_NGINX_CFG_FUNC_START(module, type, primitive) \ static char *ngx_##module##_set_##primitive( \ ngx_conf_t *cf, ngx_command_t *cmd, void *conf) \ { \ const char *rv = NULL; \ type *cfg = (type *)conf; \ ngx_str_t *value = cf->args->elts; // fprintf(stderr, " ## %s: %p (log=%p)\n", __FUNCTION__, cfg, cf->log); #define OAUTH2_NGINX_CFG_FUNC_END(cf, rv) \ if (rv) \ ngx_log_error(NGX_LOG_ERR, cf->log, 0, rv); \ return rv ? NGX_CONF_ERROR : NGX_CONF_OK; \ } #define OAUTH2_NGINX_CFG_FUNC_RET1(module, type, primitive, func, member) \ OAUTH2_NGINX_CFG_FUNC_START(module, type, primitive) \ (void)value; \ rv = func(cf, &cfg->member); \ OAUTH2_NGINX_CFG_FUNC_END(cf, rv) #define OAUTH2_NGINX_CFG_FUNC_ARGS1(module, type, primitive, func, member) \ OAUTH2_NGINX_CFG_FUNC_START(module, type, primitive) \ char *v1 = cf->args->nelts > 1 \ ? oauth2_strndup((const char *)value[1].data, \ (size_t)value[1].len) \ : NULL; \ rv = func(cfg->log, member, v1); \ oauth2_mem_free(v1); \ OAUTH2_NGINX_CFG_FUNC_END(cf, rv) #define OAUTH2_NGINX_CFG_FUNC_ARGS2(module, type, primitive, func, member) \ OAUTH2_NGINX_CFG_FUNC_START(module, type, primitive) \ char *v1 = cf->args->nelts > 1 \ ? oauth2_strndup((const char *)value[1].data, \ (size_t)value[1].len) \ : NULL; \ char *v2 = cf->args->nelts > 2 \ ? oauth2_strndup((const char *)value[2].data, \ (size_t)value[2].len) \ : NULL; \ rv = func(cfg->log, member, v1, v2); \ oauth2_mem_free(v2); \ oauth2_mem_free(v1); \ OAUTH2_NGINX_CFG_FUNC_END(cf, rv) #define OAUTH2_NGINX_CFG_FUNC_ARGS3(module, type, primitive, func, member) \ OAUTH2_NGINX_CFG_FUNC_START(module, type, primitive) \ char *v1 = cf->args->nelts > 1 \ ? oauth2_strndup((const char *)value[1].data, \ (size_t)value[1].len) \ : NULL; \ char *v2 = cf->args->nelts > 2 \ ? oauth2_strndup((const char *)value[2].data, \ (size_t)value[2].len) \ : NULL; \ char *v3 = cf->args->nelts > 3 \ ? oauth2_strndup((const char *)value[3].data, \ (size_t)value[3].len) \ : NULL; \ rv = func(cfg->log, member, v1, v2, v3); \ oauth2_mem_free(v3); \ oauth2_mem_free(v2); \ oauth2_mem_free(v1); \ OAUTH2_NGINX_CFG_FUNC_END(cf, rv) #define OAUTH2_NGINX_CFG_FUNC_ARGS4(module, type, primitive, func, member) \ OAUTH2_NGINX_CFG_FUNC_START(module, type, primitive) \ char *v1 = cf->args->nelts > 1 \ ? oauth2_strndup((const char *)value[1].data, \ (size_t)value[1].len) \ : NULL; \ char *v2 = cf->args->nelts > 2 \ ? oauth2_strndup((const char *)value[2].data, \ (size_t)value[2].len) \ : NULL; \ char *v3 = cf->args->nelts > 3 \ ? oauth2_strndup((const char *)value[3].data, \ (size_t)value[3].len) \ : NULL; \ char *v4 = cf->args->nelts > 4 \ ? oauth2_strndup((const char *)value[3].data, \ (size_t)value[4].len) \ : NULL; \ rv = func(cfg->log, member, v1, v2, v3, v4); \ oauth2_mem_free(v4); \ oauth2_mem_free(v3); \ oauth2_mem_free(v2); \ oauth2_mem_free(v1); \ OAUTH2_NGINX_CFG_FUNC_END(cf, rv) // commands // clang-format off #define OAUTH2_NGINX_CMD(take, module, directive, primitive) \ { \ ngx_string(directive), \ NGX_HTTP_LOC_CONF | NGX_HTTP_LIF_CONF | \ NGX_CONF_TAKE##take, \ ngx_##module##_set_##primitive, NGX_HTTP_LOC_CONF_OFFSET, \ 0, NULL \ } // clang-format on // logging void oauth2_nginx_log(oauth2_log_sink_t *sink, const char *filename, unsigned long line, const char *function, oauth2_log_level_t level, const char *msg); // requests typedef struct oauth2_nginx_request_context_t { oauth2_log_t *log; ngx_http_request_t *r; oauth2_http_request_t *request; } oauth2_nginx_request_context_t; oauth2_nginx_request_context_t * oauth2_nginx_request_context_init(ngx_http_request_t *r); void oauth2_nginx_request_context_free(void *rec); ngx_int_t oauth2_nginx_http_response_set(oauth2_log_t *log, oauth2_http_response_t *response, ngx_http_request_t *r); // char *oauth2_nginx_str2chr(ngx_pool_t *p, const ngx_str_t *str); #define OAUTH2_NGINX_CMD_SET_IMPL(module, primitive) \ static ngx_int_t ngx_##module##_##primitive##_variable( \ ngx_http_request_t *r, ngx_http_variable_value_t *v, \ uintptr_t data) \ { \ return oauth2_nginx_##primitive##_variable( \ ngx_##module##_module, r, v, data); \ } \ \ static char *ngx_##module##_set_##primitive( \ ngx_conf_t *cf, ngx_command_t *cmd, void *conf) \ { \ return oauth2_nginx_set_##primitive( \ ngx_##module##_module, \ ngx_##module##_##primitive##_variable, cf, cmd, conf); \ } ngx_int_t oauth2_nginx_claim_variable(ngx_module_t module, ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data); char *oauth2_nginx_set_claim(ngx_module_t module, ngx_http_get_variable_pt handler, ngx_conf_t *cf, ngx_command_t *cmd, void *conf); ngx_int_t oauth2_nginx_set_target_variables(ngx_module_t module, oauth2_nginx_request_context_t *ctx, json_t *json_token); char *nginx_oauth2_set_require(ngx_conf_t *cf, ngx_array_t **requirements); ngx_int_t nginx_oauth2_check_requirements(oauth2_nginx_request_context_t *ctx, ngx_array_t *requirements); #endif /* _OAUTH2_NGINX_H_ */ liboauth2-2.1.0/include/oauth2/oauth2.h000066400000000000000000000053111475305260400176110ustar00rootroot00000000000000#ifndef _OAUTH2_H_ #define _OAUTH2_H_ /*************************************************************************** * * Copyright (C) 2018-2025 - ZmartZone Holding BV * * 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. * * @Author: Hans Zandbelt - hans.zandbelt@openidc.com * **************************************************************************/ #include #include "oauth2/cfg.h" #include "oauth2/http.h" #include "oauth2/log.h" #include "oauth2/util.h" #define OAUTH2_GRANT_TYPE "grant_type" #define OAUTH2_ACCESS_TOKEN "access_token" #define OAUTH2_CODE "code" #define OAUTH2_SCOPE "scope" #define OAUTH2_NONCE "nonce" #define OAUTH2_STATE "state" #define OAUTH2_RESPONSE_TYPE "response_type" #define OAUTH2_REDIRECT_URI "redirect_uri" #define OAUTH2_GRANT_TYPE "grant_type" #define OAUTH2_GRANT_TYPE_AUTHORIZATION_CODE "authorization_code" #define OAUTH2_CODE_CHALLENGE "code_challenge" #define OAUTH2_CODE_CHALLENGE_METHOD "code_challenge_method" #define OAUTH2_CODE_VERIFIER "code_verifier" #define OAUTH2_RESPONSE_TYPE_CODE "code" #define OAUTH2_CLIENT_ID "client_id" #define OAUTH2_CLIENT_SECRET "client_secret" #define OAUTH2_ERROR "error" #define OAUTH2_ERROR_DESCRIPTION "error_description" #define OAUTH2_ERROR_INVALID_TOKEN "invalid_token" #define OAUTH2_ERROR_INVALID_REQUEST "invalid_request" #define OAUTH2_ERROR_INSUFFICIENT_SCOPE "insufficient_scope" #define OAUTH2_ERROR_INSUFFICIENT_USER_AUTHENTICATION \ "insufficient_user_authentication" #define OAUTH2_CLAIM_ISS "iss" #define OAUTH2_CLAIM_SUB "sub" #define OAUTH2_CLAIM_JTI "jti" #define OAUTH2_CLAIM_EXP "exp" #define OAUTH2_CLAIM_AUD "aud" #define OAUTH2_CLAIM_IAT "iat" typedef enum { OAUTH2_UNAUTH_ACTION_UNDEFINED, OAUTH2_UNAUTH_ACTION_AUTHENTICATE, OAUTH2_UNAUTH_ACTION_PASS, OAUTH2_UNAUTH_ACTION_HTTP_401, OAUTH2_UNAUTH_ACTION_HTTP_410 } oauth2_unauth_action_t; bool oauth2_http_ctx_auth_add(oauth2_log_t *log, oauth2_http_call_ctx_t *ctx, const oauth2_cfg_endpoint_auth_t *auth, oauth2_nv_list_t *params); bool oauth2_token_verify(oauth2_log_t *log, oauth2_http_request_t *request, oauth2_cfg_token_verify_t *verify, const char *token, json_t **json_payload); #endif /* _OAUTH2_H_ */ liboauth2-2.1.0/include/oauth2/openidc.h000066400000000000000000000122761475305260400200400ustar00rootroot00000000000000#ifndef _OAUTH2_OPENIDC_H_ #define _OAUTH2_OPENIDC_H_ /*************************************************************************** * * Copyright (C) 2018-2025 - ZmartZone Holding BV * * 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. * * @Author: Hans Zandbelt - hans.zandbelt@openidc.com * **************************************************************************/ #include "oauth2/http.h" #include "oauth2/oauth2.h" #include "oauth2/util.h" #define OAUTH2_OPENIDC_ID_TOKEN "id_token" #define OAUTH2_OPENIDC_ACCESS_TOKEN "access_token" #define OAUTH2_CLAIM_ISS "iss" OAUTH2_CFG_TYPE_DECLARE(cfg, session) OAUTH2_CFG_TYPE_DECLARE(cfg, openidc_provider_resolver) /* * location-based OpenID Connect configuration */ OAUTH2_CFG_TYPE_DECLARE(cfg, openidc) OAUTH2_TYPE_DECLARE_MEMBER_SET_GET(cfg, openidc, handler_path, char *) OAUTH2_TYPE_DECLARE_MEMBER_SET(cfg, openidc, redirect_uri, char *) OAUTH2_TYPE_DECLARE_MEMBER_SET_GET(cfg, openidc, unauth_action, oauth2_unauth_action_t) OAUTH2_TYPE_DECLARE_MEMBER_SET_GET(cfg, openidc, session, oauth2_cfg_session_t *) OAUTH2_TYPE_DECLARE_MEMBER_SET_GET(cfg, openidc, state_cookie_name_prefix, char *) OAUTH2_TYPE_DECLARE_MEMBER_SET_GET(cfg, openidc, state_cookie_timeout, oauth2_time_t) OAUTH2_TYPE_DECLARE_MEMBER_SET_GET(cfg, openidc, state_cookie_max, oauth2_uint_t) OAUTH2_TYPE_DECLARE_MEMBER_GET(cfg, openidc, state_cookie_delete_oldest, oauth2_flag_t) char *oauth2_cfg_openidc_redirect_uri_get(oauth2_log_t *, const oauth2_cfg_openidc_t *, const oauth2_http_request_t *); /* * protocol state */ OAUTH2_TYPE_DECLARE(openidc, proto_state) oauth2_openidc_proto_state_t * oauth2_openidc_proto_state_init(oauth2_log_t *log); oauth2_openidc_proto_state_t * oauth2_openidc_proto_state_clone(oauth2_log_t *log, const oauth2_openidc_proto_state_t *src); void oauth2_openidc_proto_state_free(oauth2_log_t *log, oauth2_openidc_proto_state_t *p); bool oauth2_openidc_proto_state_set(oauth2_log_t *log, oauth2_openidc_proto_state_t *p, const char *name, const char *value); bool oauth2_openidc_proto_state_set_int(oauth2_log_t *log, oauth2_openidc_proto_state_t *p, const char *name, const json_int_t value); json_t * oauth2_openidc_proto_state_json_get(const oauth2_openidc_proto_state_t *p); /* * OpenID Connect provider configuration */ OAUTH2_TYPE_DECLARE(openidc, provider) OAUTH2_TYPE_DECLARE_MEMBER_SET_GET(openidc, provider, issuer, char *) OAUTH2_TYPE_DECLARE_MEMBER_SET_GET(openidc, provider, authorization_endpoint, char *) OAUTH2_TYPE_DECLARE_MEMBER_SET_GET(openidc, provider, token_endpoint, char *) OAUTH2_TYPE_DECLARE_MEMBER_SET_GET(openidc, provider, userinfo_endpoint, char *) OAUTH2_TYPE_DECLARE_MEMBER_SET_GET(openidc, provider, jwks_uri, char *) bool oauth2_cfg_openidc_provider_resolver_set( oauth2_log_t *log, oauth2_cfg_openidc_t *cfg, oauth2_cfg_openidc_provider_resolver_t *resolver); oauth2_cfg_openidc_provider_resolver_t * oauth2_cfg_openidc_provider_resolver_get(oauth2_log_t *log, const oauth2_cfg_openidc_t *cfg); char *oauth2_cfg_openidc_provider_resolver_set_options( oauth2_log_t *log, oauth2_cfg_openidc_t *cfg, const char *type, const char *value, const char *options); /* * OpenID Connect client configuration */ OAUTH2_TYPE_DECLARE(openidc, client) OAUTH2_TYPE_DECLARE_MEMBER_SET_GET(openidc, client, scope, char *) OAUTH2_TYPE_DECLARE_MEMBER_SET_GET(openidc, client, client_id, char *) OAUTH2_TYPE_DECLARE_MEMBER_SET_GET(openidc, client, client_secret, char *) OAUTH2_TYPE_DECLARE_MEMBER_SET_GET(openidc, client, token_endpoint_auth, oauth2_cfg_endpoint_auth_t *) OAUTH2_TYPE_DECLARE_MEMBER_SET_GET(openidc, client, ssl_verify, oauth2_flag_t) OAUTH2_TYPE_DECLARE_MEMBER_SET_GET(openidc, client, http_timeout, oauth2_uint_t) char *oauth2_openidc_client_set_options(oauth2_log_t *log, oauth2_cfg_openidc_t *cfg, const char *type, const char *value, const char *options); OAUTH2_TYPE_DECLARE_MEMBER_GET(cfg, openidc, client, oauth2_openidc_client_t *) /* * OpenID Connect configuration */ char *oauth2_cfg_openidc_set_options(oauth2_log_t *log, oauth2_cfg_openidc_t *cfg, const char *options); char *oauth2_cfg_openidc_redirect_uri_get_iss( oauth2_log_t *, const oauth2_cfg_openidc_t *, const oauth2_http_request_t *, const oauth2_openidc_provider_t *); bool oauth2_openidc_is_request_to_redirect_uri(oauth2_log_t *log, const oauth2_cfg_openidc_t *cfg, oauth2_http_request_t *request); bool oauth2_openidc_handle(oauth2_log_t *log, const oauth2_cfg_openidc_t *c, oauth2_http_request_t *r, oauth2_http_response_t **response, json_t **claims); #endif /* _OAUTH2_OPENIDC_H_ */ liboauth2-2.1.0/include/oauth2/proto.h000066400000000000000000000026411475305260400175550ustar00rootroot00000000000000#ifndef _OAUTH2_PROTO_H_ #define _OAUTH2_PROTO_H_ /*************************************************************************** * * Copyright (C) 2018-2025 - ZmartZone Holding BV * * 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. * * @Author: Hans Zandbelt - hans.zandbelt@openidc.com * **************************************************************************/ #include "oauth2/cfg.h" #include "oauth2/http.h" char *oauth2_get_source_token(oauth2_log_t *log, oauth2_cfg_source_token_t *cfg, oauth2_http_request_t *request, oauth2_cfg_server_callback_funcs_t *srv_cb, void *srv_cb_ctx); bool oauth2_ropc_exec(oauth2_log_t *log, oauth2_cfg_ropc_t *cfg, const char *username, const char *password, char **rtoken, oauth2_uint_t *status_code); bool oauth2_cc_exec(oauth2_log_t *log, oauth2_cfg_cc_t *cfg, char **rtoken, oauth2_uint_t *status_code); #endif /* _OAUTH2_PROTO_H_ */ liboauth2-2.1.0/include/oauth2/session.h000066400000000000000000000067321475305260400201020ustar00rootroot00000000000000#ifndef _OAUTH2_SESSION_H_ #define _OAUTH2_SESSION_H_ /*************************************************************************** * * Copyright (C) 2018-2025 - ZmartZone Holding BV * * 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. * * @Author: Hans Zandbelt - hans.zandbelt@openidc.com * **************************************************************************/ #include "oauth2/cache.h" #include "oauth2/openidc.h" #include "oauth2/util.h" OAUTH2_CFG_TYPE_DECLARE(cfg, session) void oauth2_cfg_session_release(oauth2_log_t *log, oauth2_cfg_session_t *session); OAUTH2_TYPE_DECLARE_MEMBER_GET(cfg, session, cookie_name, char *) OAUTH2_TYPE_DECLARE_MEMBER_GET(cfg, session, cookie_path, char *) OAUTH2_TYPE_DECLARE_MEMBER_GET(cfg, session, inactivity_timeout_s, oauth2_time_t) OAUTH2_TYPE_DECLARE_MEMBER_GET(cfg, session, max_duration_s, oauth2_time_t) OAUTH2_TYPE_DECLARE_MEMBER_GET(cfg, session, cache, oauth2_cache_t *) typedef bool(oauth2_session_load_callback_t)(oauth2_log_t *log, const oauth2_cfg_session_t *cfg, oauth2_http_request_t *request, json_t **json); typedef bool(oauth2_session_save_callback_t)( oauth2_log_t *log, const oauth2_cfg_session_t *cfg, const oauth2_http_request_t *request, oauth2_http_response_t *response, json_t *json); OAUTH2_TYPE_DECLARE_MEMBER_GET(cfg, session, load_callback, oauth2_session_load_callback_t *) OAUTH2_TYPE_DECLARE_MEMBER_GET(cfg, session, save_callback, oauth2_session_save_callback_t *) char *oauth2_cfg_session_set_options(oauth2_log_t *log, oauth2_cfg_session_t *cfg, const char *type, const char *options); OAUTH2_TYPE_DECLARE(session, rec); OAUTH2_TYPE_DECLARE_MEMBER_SET_GET(session, rec, user, char *) OAUTH2_TYPE_DECLARE_MEMBER_SET_GET(session, rec, id_token, char *) OAUTH2_TYPE_DECLARE_MEMBER_GET(session, rec, id_token_claims, json_t *) OAUTH2_TYPE_DECLARE_MEMBER_GET(session, rec, userinfo_claims, json_t *) OAUTH2_TYPE_DECLARE_MEMBER_SET_GET(session, rec, start, oauth2_time_t) OAUTH2_TYPE_DECLARE_MEMBER_SET_GET(session, rec, expiry, oauth2_time_t) bool oauth2_session_rec_id_token_claims_set(oauth2_log_t *log, oauth2_session_rec_t *session, json_t *id_token); bool oauth2_session_rec_userinfo_claims_set(oauth2_log_t *log, oauth2_session_rec_t *session, json_t *userinfo_claims); bool oauth2_session_load(oauth2_log_t *log, const oauth2_cfg_session_t *c, oauth2_http_request_t *r, oauth2_session_rec_t **session); bool oauth2_session_save(oauth2_log_t *log, const oauth2_cfg_session_t *cfg, const oauth2_http_request_t *request, oauth2_http_response_t *response, oauth2_session_rec_t *session); void oauth2_session_rec_free(oauth2_log_t *log, oauth2_session_rec_t *s); bool oauth2_session_handle(oauth2_log_t *log, const oauth2_cfg_session_t *cfg, const oauth2_http_request_t *request, oauth2_http_response_t *response, oauth2_session_rec_t *session); #endif /* _OAUTH2_SESSION_H_ */ liboauth2-2.1.0/include/oauth2/util.h000066400000000000000000000201071475305260400173640ustar00rootroot00000000000000#ifndef _OAUTH2_UTIL_H #define _OAUTH2_UTIL_H /*************************************************************************** * * Copyright (C) 2018-2025 - ZmartZone Holding BV * * 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. * * @Author: Hans Zandbelt - hans.zandbelt@openidc.com * **************************************************************************/ #include #include #include #include "oauth2/log.h" #include #define OAUTH2_STRINGIFY(x) #x #define OAUTH2_TOSTRING(x) OAUTH2_STRINGIFY(x) typedef char oauth2_flag_t; typedef unsigned int oauth2_uint_t; typedef uint64_t oauth2_time_t; #define OAUTH2_UINT_FORMAT "%u" #define OAUTH2_TIME_T_FORMAT "%lu" #define OAUTH2_MSEC_PER_SEC 1000 #define OAUTH2_USEC_PER_MSEC 1000 #define OAUTH2_TYPE_DECLARE(module, object) \ typedef struct oauth2_##module##_##object##_t \ oauth2_##module##_##object##_t; \ oauth2_##module##_##object##_t *oauth2_##module##_##object##_init( \ oauth2_log_t *); \ oauth2_##module##_##object##_t *oauth2_##module##_##object##_clone( \ oauth2_log_t *, const oauth2_##module##_##object##_t *); \ void oauth2_##module##_##object##_free( \ oauth2_log_t *, oauth2_##module##_##object##_t *); #define OAUTH2_TYPE_DECLARE_MEMBER_SET(module, object, member, type) \ bool oauth2_##module##_##object##_##member##_set( \ oauth2_log_t *, oauth2_##module##_##object##_t *, const type); #define OAUTH2_TYPE_DECLARE_MEMBER_GET(module, object, member, type) \ type oauth2_##module##_##object##_##member##_get( \ oauth2_log_t *, const oauth2_##module##_##object##_t *); #define OAUTH2_TYPE_DECLARE_MEMBER_SET_GET(module, object, member, type) \ OAUTH2_TYPE_DECLARE_MEMBER_SET(module, object, member, type) \ OAUTH2_TYPE_DECLARE_MEMBER_GET(module, object, member, type) #define OAUTH2_MEMBER_LIST_DECLARE_SET(module, object, member) \ bool oauth2_##module##_##object##_##member##_set( \ oauth2_log_t *, oauth2_##module##_##object##_t *, const char *, \ const char *); #define OAUTH2_MEMBER_LIST_DECLARE_UNSET(module, object, member) \ bool oauth2_##module##_##object##_##member##_unset( \ oauth2_log_t *, oauth2_##module##_##object##_t *, const char *); #define OAUTH2_MEMBER_LIST_DECLARE_ADD(module, object, member) \ bool oauth2_##module##_##object##_##member##_add( \ oauth2_log_t *, oauth2_##module##_##object##_t *, const char *, \ const char *); #define OAUTH2_MEMBER_LIST_DECLARE_GET(module, object, member) \ const char *oauth2_##module##_##object##_##member##_get( \ oauth2_log_t *, const oauth2_##module##_##object##_t *, \ const char *); #define OAUTH2_MEMBER_LIST_DECLARE_SET_UNSET_ADD_GET(module, object, member) \ OAUTH2_MEMBER_LIST_DECLARE_SET(module, object, member) \ OAUTH2_MEMBER_LIST_DECLARE_UNSET(module, object, member) \ OAUTH2_MEMBER_LIST_DECLARE_ADD(module, object, member) \ OAUTH2_MEMBER_LIST_DECLARE_GET(module, object, member) #define OAUTH2_LIST_DECLARE_SET(module, type) \ bool oauth2_##module##_##type##_set(oauth2_log_t *, \ oauth2_##module##_##type##_t *, \ const char *, const char *); #define OAUTH2_LIST_DECLARE_UNSET(module, type) \ bool oauth2_##module##_##type##_unset( \ oauth2_log_t *, oauth2_##module##_##type##_t *, const char *); #define OAUTH2_LIST_DECLARE_ADD(module, type) \ bool oauth2_##module##_##type##_add(oauth2_log_t *, \ oauth2_##module##_##type##_t *, \ const char *, const char *); #define OAUTH2_LIST_DECLARE_GET(module, type) \ const char *oauth2_##module##_##type##_get( \ oauth2_log_t *, const oauth2_##module##_##type##_t *, \ const char *); #define OAUTH2_LIST_DECLARE_SET_UNSET_ADD_GET(module, type) \ OAUTH2_LIST_DECLARE_SET(module, type) \ OAUTH2_LIST_DECLARE_UNSET(module, type) \ OAUTH2_LIST_DECLARE_ADD(module, type) \ OAUTH2_LIST_DECLARE_GET(module, type) OAUTH2_TYPE_DECLARE(nv, list) OAUTH2_TYPE_DECLARE_MEMBER_SET_GET(nv, list, case_sensitive, bool) OAUTH2_LIST_DECLARE_SET_UNSET_ADD_GET(nv, list) typedef bool(oauth2_nv_list_loop_cb_t)(oauth2_log_t *log, void *rec, const char *key, const char *value); void oauth2_nv_list_loop(oauth2_log_t *log, const oauth2_nv_list_t *list, oauth2_nv_list_loop_cb_t *callback, void *rec); char *oauth2_nv_list2s(oauth2_log_t *log, const oauth2_nv_list_t *list); void oauth2_nv_list_merge_into(oauth2_log_t *log, const oauth2_nv_list_t *source, oauth2_nv_list_t *target); oauth2_log_t *oauth2_init(oauth2_log_level_t level, oauth2_log_sink_t *sink); void oauth2_shutdown(oauth2_log_t *); int oauth2_snprintf(char *dst, size_t len, const char *fmt, ...); char *oauth2_strdup(const char *src); char *oauth2_strndup(const char *src, size_t len); char *oauth2_stradd(char *src, const char *add1, const char *add2, const char *add3); char *oauth2_getword(const char **line, char stop); size_t oauth2_base64url_encode(oauth2_log_t *log, const uint8_t *src, const size_t src_len, char **dst); bool oauth2_base64url_decode(oauth2_log_t *log, const char *src, uint8_t **dst, size_t *dst_len); size_t oauth2_base64_encode(oauth2_log_t *log, const uint8_t *src, const size_t src_len, char **dst); bool oauth2_base64_decode(oauth2_log_t *log, const char *src, uint8_t **dst, size_t *dst_len); char *oauth2_url_encode(oauth2_log_t *log, const char *str); char *oauth2_url_decode(oauth2_log_t *log, const char *str); char *oauth2_html_escape(oauth2_log_t *log, const char *src); bool oauth2_parse_form_encoded_params(oauth2_log_t *log, const char *data, oauth2_nv_list_t **params); bool oauth2_json_decode_check_error(oauth2_log_t *log, const char *str, json_t **json); bool oauth2_json_decode_object(oauth2_log_t *log, const char *payload, json_t **json); bool oauth2_json_object_get(oauth2_log_t *log, const json_t *json, const char *name, json_t **value); bool oauth2_json_string_get(oauth2_log_t *log, const json_t *json, const char *name, char **value, const char *default_value); bool oauth2_json_number_get(oauth2_log_t *log, const json_t *json, const char *name, json_int_t *number, const json_int_t default_value); char *oauth2_rand_str(oauth2_log_t *log, size_t len); oauth2_time_t oauth2_time_now_sec(); oauth2_time_t oauth2_parse_time_sec(oauth2_log_t *log, const char *seconds, oauth2_time_t default_value); bool oauth2_parse_bool(oauth2_log_t *log, const char *value, bool default_value); oauth2_uint_t oauth2_parse_uint(oauth2_log_t *log, const char *int_value, oauth2_uint_t default_value); int oauth2_strnenvcmp(const char *a, const char *b, int len); char *oauth2_json_encode(oauth2_log_t *log, json_t *json, size_t flags); char *oauth2_normalize_header_name(const char *str); char *oauth_read_file(oauth2_log_t *log, const char *filename); #endif /* _OAUTH2_UTIL_H */ liboauth2-2.1.0/include/oauth2/version.h.in000066400000000000000000000022161475305260400205020ustar00rootroot00000000000000#ifndef _OAUTH2_VERSION_H_ #define _OAUTH2_VERSION_H_ /* include/oauth2/version.h Generated from version.h.in by autoheader. */ /*************************************************************************** * * Copyright (C) 2018-2025 - ZmartZone Holding BV * * 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. * * @Author: Hans Zandbelt - hans.zandbelt@openidc.com * **************************************************************************/ #define OAUTH2_PACKAGE_NAME "@PACKAGE_NAME@" #define OAUTH2_PACKAGE_VERSION "@PACKAGE_VERSION@" const char *oauth2_version(); const char *oauth2_package_string(); #endif // _OAUTH2_VERSION_H_ liboauth2-2.1.0/liboauth2.pc.in000066400000000000000000000007401475305260400162340ustar00rootroot00000000000000# # liboauth2 - OAuth 2.0 / OpenID Connect implementation for C # prefix=@prefix@ exec_prefix=@exec_prefix@ libdir=@libdir@ includedir=@includedir@ Name: liboauth2 URL: https://github.com/OpenIDC/liboauth2 Description: OAuth 2.0 / OpenID Connect implementation for C Version: @VERSION@ Requires: cjose >= 0.5.1, jansson >= 2.3, libcurl, libpcre2-8, libssl, libcrypto >= 1.0.1@MEMCACHE_PC@@HIREDIS_PC@@JQ_LIBS_PC@ Cflags: -I${includedir}@JQ_CFLAGS_PC@ Libs: -L${libdir} -loauth2 liboauth2-2.1.0/liboauth2_apache.pc.in000066400000000000000000000005701475305260400175360ustar00rootroot00000000000000# # liboauth2_apache - Apache C bindings for liboauth2 # prefix=@prefix@ exec_prefix=@exec_prefix@ libdir=@libdir@ includedir=@includedir@ Name: liboauth2_apache URL: https://github.com/OpenIDC/liboauth2 Description: Apache C bindings for liboauth2 Version: @VERSION@ Requires: liboauth2 >= 2.0.0, apr-1, apr-util-1 Cflags: -I${includedir} Libs: -L${libdir} -loauth2_apache liboauth2-2.1.0/liboauth2_nginx.pc.in000066400000000000000000000005401475305260400174350ustar00rootroot00000000000000# # liboauth2_nginx - NGINX C bindings for liboauth2 # prefix=@prefix@ exec_prefix=@exec_prefix@ libdir=@libdir@ includedir=@includedir@ Name: liboauth2_nginx URL: https://github.com/OpenIDC/liboauth2 Description: NGINX C bindings for liboauth2 Version: @VERSION@ Requires: liboauth2 >= 2.0.0 Cflags: -I${includedir} Libs: -L${libdir} -loauth2_nginx liboauth2-2.1.0/m4/000077500000000000000000000000001475305260400137315ustar00rootroot00000000000000liboauth2-2.1.0/m4/.gitignore000066400000000000000000000001041475305260400157140ustar00rootroot00000000000000/libtool.m4 /ltoptions.m4 /ltsugar.m4 /ltversion.m4 /lt~obsolete.m4 liboauth2-2.1.0/m4/ax_code_coverage.m4000066400000000000000000000270751475305260400174630ustar00rootroot00000000000000# =========================================================================== # https://www.gnu.org/software/autoconf-archive/ax_code_coverage.html # =========================================================================== # # SYNOPSIS # # AX_CODE_COVERAGE() # # DESCRIPTION # # Defines CODE_COVERAGE_CPPFLAGS, CODE_COVERAGE_CFLAGS, # CODE_COVERAGE_CXXFLAGS and CODE_COVERAGE_LIBS which should be included # in the CPPFLAGS, CFLAGS CXXFLAGS and LIBS/LIBADD variables of every # build target (program or library) which should be built with code # coverage support. Also defines CODE_COVERAGE_RULES which should be # substituted in your Makefile; and $enable_code_coverage which can be # used in subsequent configure output. CODE_COVERAGE_ENABLED is defined # and substituted, and corresponds to the value of the # --enable-code-coverage option, which defaults to being disabled. # # Test also for gcov program and create GCOV variable that could be # substituted. # # Note that all optimization flags in CFLAGS must be disabled when code # coverage is enabled. # # Usage example: # # configure.ac: # # AX_CODE_COVERAGE # # Makefile.am: # # @CODE_COVERAGE_RULES@ # my_program_LIBS = ... $(CODE_COVERAGE_LIBS) ... # my_program_CPPFLAGS = ... $(CODE_COVERAGE_CPPFLAGS) ... # my_program_CFLAGS = ... $(CODE_COVERAGE_CFLAGS) ... # my_program_CXXFLAGS = ... $(CODE_COVERAGE_CXXFLAGS) ... # # This results in a "check-code-coverage" rule being added to any # Makefile.am which includes "@CODE_COVERAGE_RULES@" (assuming the module # has been configured with --enable-code-coverage). Running `make # check-code-coverage` in that directory will run the module's test suite # (`make check`) and build a code coverage report detailing the code which # was touched, then print the URI for the report. # # In earlier versions of this macro, CODE_COVERAGE_LDFLAGS was defined # instead of CODE_COVERAGE_LIBS. They are both still defined, but use of # CODE_COVERAGE_LIBS is preferred for clarity; CODE_COVERAGE_LDFLAGS is # deprecated. They have the same value. # # This code was derived from Makefile.decl in GLib, originally licenced # under LGPLv2.1+. # # LICENSE # # Copyright (c) 2012, 2016 Philip Withnall # Copyright (c) 2012 Xan Lopez # Copyright (c) 2012 Christian Persch # Copyright (c) 2012 Paolo Borelli # Copyright (c) 2012 Dan Winship # Copyright (c) 2015 Bastien ROUCARIES # # This library is free software; you can redistribute it and/or modify it # under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation; either version 2.1 of the License, or (at # your option) any later version. # # This library 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. See the GNU Lesser # General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with this program. If not, see . #serial 25 AC_DEFUN([AX_CODE_COVERAGE],[ dnl Check for --enable-code-coverage AC_REQUIRE([AC_PROG_SED]) # allow to override gcov location AC_ARG_WITH([gcov], [AS_HELP_STRING([--with-gcov[=GCOV]], [use given GCOV for coverage (GCOV=gcov).])], [_AX_CODE_COVERAGE_GCOV_PROG_WITH=$with_gcov], [_AX_CODE_COVERAGE_GCOV_PROG_WITH=gcov]) AC_MSG_CHECKING([whether to build with code coverage support]) AC_ARG_ENABLE([code-coverage], AS_HELP_STRING([--enable-code-coverage], [Whether to enable code coverage support]),, enable_code_coverage=no) AM_CONDITIONAL([CODE_COVERAGE_ENABLED], [test x$enable_code_coverage = xyes]) AC_SUBST([CODE_COVERAGE_ENABLED], [$enable_code_coverage]) AC_MSG_RESULT($enable_code_coverage) AS_IF([ test "$enable_code_coverage" = "yes" ], [ # check for gcov AC_CHECK_TOOL([GCOV], [$_AX_CODE_COVERAGE_GCOV_PROG_WITH], [:]) AS_IF([test "X$GCOV" = "X:"], [AC_MSG_ERROR([gcov is needed to do coverage])]) AC_SUBST([GCOV]) dnl Check if gcc is being used AS_IF([ test "$GCC" = "no" ], [ AC_MSG_ERROR([not compiling with gcc, which is required for gcov code coverage]) ]) AC_CHECK_PROG([LCOV], [lcov], [lcov]) AC_CHECK_PROG([GENHTML], [genhtml], [genhtml]) AS_IF([ test -z "$LCOV" ], [ AC_MSG_ERROR([To enable code coverage reporting you must have lcov installed]) ]) AS_IF([ test -z "$GENHTML" ], [ AC_MSG_ERROR([Could not find genhtml from the lcov package]) ]) dnl Build the code coverage flags dnl Define CODE_COVERAGE_LDFLAGS for backwards compatibility CODE_COVERAGE_CPPFLAGS="-DNDEBUG" CODE_COVERAGE_CFLAGS="-O0 -g -fprofile-arcs -ftest-coverage" CODE_COVERAGE_CXXFLAGS="-O0 -g -fprofile-arcs -ftest-coverage" #CODE_COVERAGE_LIBS="-lgcov" CODE_COVERAGE_LDFLAGS="$CODE_COVERAGE_LIBS" AC_SUBST([CODE_COVERAGE_CPPFLAGS]) AC_SUBST([CODE_COVERAGE_CFLAGS]) AC_SUBST([CODE_COVERAGE_CXXFLAGS]) AC_SUBST([CODE_COVERAGE_LIBS]) AC_SUBST([CODE_COVERAGE_LDFLAGS]) [CODE_COVERAGE_RULES_CHECK=' -$(A''M_V_at)$(MAKE) $(AM_MAKEFLAGS) -k check $(A''M_V_at)$(MAKE) $(AM_MAKEFLAGS) code-coverage-capture '] [CODE_COVERAGE_RULES_CAPTURE=' $(code_coverage_v_lcov_cap)$(LCOV) $(code_coverage_quiet) $(addprefix --directory ,$(CODE_COVERAGE_DIRECTORY)) --capture --output-file "$(CODE_COVERAGE_OUTPUT_FILE).tmp" --test-name "$(call code_coverage_sanitize,$(PACKAGE_NAME)-$(PACKAGE_VERSION))" --no-checksum --compat-libtool $(CODE_COVERAGE_LCOV_SHOPTS) $(CODE_COVERAGE_LCOV_OPTIONS) $(code_coverage_v_lcov_ign)$(LCOV) $(code_coverage_quiet) $(addprefix --directory ,$(CODE_COVERAGE_DIRECTORY)) --remove "$(CODE_COVERAGE_OUTPUT_FILE).tmp" "/tmp/*" $(CODE_COVERAGE_IGNORE_PATTERN) --output-file "$(CODE_COVERAGE_OUTPUT_FILE)" $(CODE_COVERAGE_LCOV_SHOPTS) $(CODE_COVERAGE_LCOV_RMOPTS) -@rm -f $(CODE_COVERAGE_OUTPUT_FILE).tmp $(code_coverage_v_genhtml)LANG=C $(GENHTML) $(code_coverage_quiet) $(addprefix --prefix ,$(CODE_COVERAGE_DIRECTORY)) --output-directory "$(CODE_COVERAGE_OUTPUT_DIRECTORY)" --title "$(PACKAGE_NAME)-$(PACKAGE_VERSION) Code Coverage" --legend --show-details "$(CODE_COVERAGE_OUTPUT_FILE)" $(CODE_COVERAGE_GENHTML_OPTIONS) @echo "file://$(abs_builddir)/$(CODE_COVERAGE_OUTPUT_DIRECTORY)/index.html" '] [CODE_COVERAGE_RULES_CLEAN=' clean: code-coverage-clean distclean: code-coverage-clean code-coverage-clean: -$(LCOV) --directory $(top_builddir) -z -rm -rf $(CODE_COVERAGE_OUTPUT_FILE) $(CODE_COVERAGE_OUTPUT_FILE).tmp $(CODE_COVERAGE_OUTPUT_DIRECTORY) -find . \( -name "*.gcda" -o -name "*.gcno" -o -name "*.gcov" \) -delete '] ], [ [CODE_COVERAGE_RULES_CHECK=' @echo "Need to reconfigure with --enable-code-coverage" '] CODE_COVERAGE_RULES_CAPTURE="$CODE_COVERAGE_RULES_CHECK" CODE_COVERAGE_RULES_CLEAN='' ]) [CODE_COVERAGE_RULES=' # Code coverage # # Optional: # - CODE_COVERAGE_DIRECTORY: Top-level directory for code coverage reporting. # Multiple directories may be specified, separated by whitespace. # (Default: $(top_builddir)) # - CODE_COVERAGE_OUTPUT_FILE: Filename and path for the .info file generated # by lcov for code coverage. (Default: # $(PACKAGE_NAME)-$(PACKAGE_VERSION)-coverage.info) # - CODE_COVERAGE_OUTPUT_DIRECTORY: Directory for generated code coverage # reports to be created. (Default: # $(PACKAGE_NAME)-$(PACKAGE_VERSION)-coverage) # - CODE_COVERAGE_BRANCH_COVERAGE: Set to 1 to enforce branch coverage, # set to 0 to disable it and leave empty to stay with the default. # (Default: empty) # - CODE_COVERAGE_LCOV_SHOPTS_DEFAULT: Extra options shared between both lcov # instances. (Default: based on $CODE_COVERAGE_BRANCH_COVERAGE) # - CODE_COVERAGE_LCOV_SHOPTS: Extra options to shared between both lcov # instances. (Default: $CODE_COVERAGE_LCOV_SHOPTS_DEFAULT) # - CODE_COVERAGE_LCOV_OPTIONS_GCOVPATH: --gcov-tool pathtogcov # - CODE_COVERAGE_LCOV_OPTIONS_DEFAULT: Extra options to pass to the # collecting lcov instance. (Default: $CODE_COVERAGE_LCOV_OPTIONS_GCOVPATH) # - CODE_COVERAGE_LCOV_OPTIONS: Extra options to pass to the collecting lcov # instance. (Default: $CODE_COVERAGE_LCOV_OPTIONS_DEFAULT) # - CODE_COVERAGE_LCOV_RMOPTS_DEFAULT: Extra options to pass to the filtering # lcov instance. (Default: empty) # - CODE_COVERAGE_LCOV_RMOPTS: Extra options to pass to the filtering lcov # instance. (Default: $CODE_COVERAGE_LCOV_RMOPTS_DEFAULT) # - CODE_COVERAGE_GENHTML_OPTIONS_DEFAULT: Extra options to pass to the # genhtml instance. (Default: based on $CODE_COVERAGE_BRANCH_COVERAGE) # - CODE_COVERAGE_GENHTML_OPTIONS: Extra options to pass to the genhtml # instance. (Default: $CODE_COVERAGE_GENHTML_OPTIONS_DEFAULT) # - CODE_COVERAGE_IGNORE_PATTERN: Extra glob pattern of files to ignore # # The generated report will be titled using the $(PACKAGE_NAME) and # $(PACKAGE_VERSION). In order to add the current git hash to the title, # use the git-version-gen script, available online. # Optional variables CODE_COVERAGE_DIRECTORY ?= $(top_builddir) CODE_COVERAGE_OUTPUT_FILE ?= $(PACKAGE_NAME)-$(PACKAGE_VERSION)-coverage.info CODE_COVERAGE_OUTPUT_DIRECTORY ?= $(PACKAGE_NAME)-$(PACKAGE_VERSION)-coverage CODE_COVERAGE_BRANCH_COVERAGE ?= CODE_COVERAGE_LCOV_SHOPTS_DEFAULT ?= $(if $(CODE_COVERAGE_BRANCH_COVERAGE),\ --rc lcov_branch_coverage=$(CODE_COVERAGE_BRANCH_COVERAGE)) CODE_COVERAGE_LCOV_SHOPTS ?= $(CODE_COVERAGE_LCOV_SHOPTS_DEFAULT) CODE_COVERAGE_LCOV_OPTIONS_GCOVPATH ?= --gcov-tool "$(GCOV)" CODE_COVERAGE_LCOV_OPTIONS_DEFAULT ?= $(CODE_COVERAGE_LCOV_OPTIONS_GCOVPATH) CODE_COVERAGE_LCOV_OPTIONS ?= $(CODE_COVERAGE_LCOV_OPTIONS_DEFAULT) CODE_COVERAGE_LCOV_RMOPTS_DEFAULT ?= CODE_COVERAGE_LCOV_RMOPTS ?= $(CODE_COVERAGE_LCOV_RMOPTS_DEFAULT) CODE_COVERAGE_GENHTML_OPTIONS_DEFAULT ?=\ $(if $(CODE_COVERAGE_BRANCH_COVERAGE),\ --rc genhtml_branch_coverage=$(CODE_COVERAGE_BRANCH_COVERAGE)) CODE_COVERAGE_GENHTML_OPTIONS ?= $(CODE_COVERAGE_GENHTML_OPTIONS_DEFAULT) CODE_COVERAGE_IGNORE_PATTERN ?= GITIGNOREFILES ?= GITIGNOREFILES += $(CODE_COVERAGE_OUTPUT_FILE) $(CODE_COVERAGE_OUTPUT_DIRECTORY) code_coverage_v_lcov_cap = $(code_coverage_v_lcov_cap_$(V)) code_coverage_v_lcov_cap_ = $(code_coverage_v_lcov_cap_$(AM_DEFAULT_VERBOSITY)) code_coverage_v_lcov_cap_0 = @echo " LCOV --capture"\ $(CODE_COVERAGE_OUTPUT_FILE); code_coverage_v_lcov_ign = $(code_coverage_v_lcov_ign_$(V)) code_coverage_v_lcov_ign_ = $(code_coverage_v_lcov_ign_$(AM_DEFAULT_VERBOSITY)) code_coverage_v_lcov_ign_0 = @echo " LCOV --remove /tmp/*"\ $(CODE_COVERAGE_IGNORE_PATTERN); code_coverage_v_genhtml = $(code_coverage_v_genhtml_$(V)) code_coverage_v_genhtml_ = $(code_coverage_v_genhtml_$(AM_DEFAULT_VERBOSITY)) code_coverage_v_genhtml_0 = @echo " GEN " $(CODE_COVERAGE_OUTPUT_DIRECTORY); code_coverage_quiet = $(code_coverage_quiet_$(V)) code_coverage_quiet_ = $(code_coverage_quiet_$(AM_DEFAULT_VERBOSITY)) code_coverage_quiet_0 = --quiet # sanitizes the test-name: replaces with underscores: dashes and dots code_coverage_sanitize = $(subst -,_,$(subst .,_,$(1))) # Use recursive makes in order to ignore errors during check check-code-coverage:'"$CODE_COVERAGE_RULES_CHECK"' # Capture code coverage data code-coverage-capture: code-coverage-capture-hook'"$CODE_COVERAGE_RULES_CAPTURE"' # Hook rule executed before code-coverage-capture, overridable by the user code-coverage-capture-hook: '"$CODE_COVERAGE_RULES_CLEAN"' A''M_DISTCHECK_CONFIGURE_FLAGS ?= A''M_DISTCHECK_CONFIGURE_FLAGS += --disable-code-coverage .PHONY: check-code-coverage code-coverage-capture code-coverage-capture-hook code-coverage-clean '] AC_SUBST([CODE_COVERAGE_RULES]) m4_ifdef([_AM_SUBST_NOTMAKE], [_AM_SUBST_NOTMAKE([CODE_COVERAGE_RULES])]) ]) liboauth2-2.1.0/src/000077500000000000000000000000001475305260400142005ustar00rootroot00000000000000liboauth2-2.1.0/src/.gitignore000066400000000000000000000000741475305260400161710ustar00rootroot00000000000000/*.lo /*.o /*.la /*.gcno /*.gcno /.deps/ /.libs/ /.dirstamp liboauth2-2.1.0/src/cache.c000066400000000000000000000226671475305260400154240ustar00rootroot00000000000000/*************************************************************************** * * Copyright (C) 2018-2025 - ZmartZone Holding BV * * 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. * * @Author: Hans Zandbelt - hans.zandbelt@openidc.com * **************************************************************************/ #include #include #include #include #include "oauth2/cache.h" #include "oauth2/ipc.h" #include "oauth2/jose.h" #include "oauth2/mem.h" #include "oauth2/util.h" #include "cache_int.h" #include "cfg_int.h" #include "util_int.h" _OAUTH2_CFG_GLOBAL_LIST(cache_type, oauth2_cache_type_t) _OAUTH2_CFG_GLOBAL_LIST(cache, oauth2_cache_t) extern oauth2_cache_type_t oauth2_cache_shm; extern oauth2_cache_type_t oauth2_cache_file; #ifdef HAVE_LIBMEMCACHE extern oauth2_cache_type_t oauth2_cache_memcache; #endif #ifdef HAVE_LIBHIREDIS extern oauth2_cache_type_t oauth2_cache_redis; #endif #define _OAUTH2_CACHE_OPENSSL_ERR ERR_error_string(ERR_get_error(), NULL) static bool _oauth2_cache_global_initialized = false; static void _oauth2_cache_global_init(oauth2_log_t *log) { if (_oauth2_cache_global_initialized == true) goto end; _M_cache_type_list_register(log, oauth2_cache_shm.name, &oauth2_cache_shm, NULL); _M_cache_type_list_register(log, oauth2_cache_file.name, &oauth2_cache_file, NULL); #ifdef HAVE_LIBMEMCACHE _M_cache_type_list_register(log, oauth2_cache_memcache.name, &oauth2_cache_memcache, NULL); #endif #ifdef HAVE_LIBHIREDIS _M_cache_type_list_register(log, oauth2_cache_redis.name, &oauth2_cache_redis, NULL); #endif _oauth2_cache_global_initialized = true; end: return; } static void _oauth2_cache_free(oauth2_log_t *log, oauth2_cache_t *cache) { oauth2_debug(log, "enter"); if ((cache == NULL) || (cache->type == NULL)) goto end; if (cache->key_hash_algo) oauth2_mem_free(cache->key_hash_algo); if (cache->enc_key) oauth2_mem_free(cache->enc_key); if (cache->passphrase_hash_algo) oauth2_mem_free(cache->passphrase_hash_algo); if (cache->type->free) cache->type->free(log, cache); oauth2_mem_free(cache); end: oauth2_debug(log, "leave"); return; } oauth2_cache_t *_oauth2_cache_init(oauth2_log_t *log, const char *type, const oauth2_nv_list_t *params) { oauth2_cache_t *cache = NULL; oauth2_cache_type_t *cache_type = NULL; _oauth2_cache_global_init(log); if (type == NULL) type = "shm"; cache_type = _M_cache_type_list_get(log, type); if (cache_type == NULL) { oauth2_error(log, "cache type %s is not registered", type); goto end; } if (cache_type->init == NULL) goto end; cache = oauth2_mem_alloc(sizeof(oauth2_cache_t)); if (cache == NULL) goto end; if (cache_type->init(log, cache, params) == false) goto end; cache->key_hash_algo = oauth2_strdup(oauth2_nv_list_get(log, params, "key_hash_algo")); cache->passphrase_hash_algo = oauth2_strdup( oauth2_nv_list_get(log, params, "passphrase_hash_algo")); cache->encrypt = oauth2_parse_bool(log, oauth2_nv_list_get(log, params, "encrypt"), cache->type->encrypt_by_default); if (cache->encrypt == false) { cache->enc_key = NULL; goto end; } end: if (cache) _M_cache_list_register(log, oauth2_nv_list_get(log, params, "name"), cache, _oauth2_cache_free); return cache; } oauth2_cache_t *oauth2_cache_obtain(oauth2_log_t *log, const char *name) { oauth2_cache_t *c = NULL; oauth2_debug(log, "enter: %s", name); if (_M_cache_list_empty(log)) { c = _oauth2_cache_init(log, NULL, NULL); if (c == NULL) goto end; if (_oauth2_cache_post_config(log, c) == false) { c = NULL; goto end; } } c = _M_cache_list_get(log, name); end: oauth2_debug(log, "leave: %p", c); return c; } void _oauth2_cache_global_cleanup(oauth2_log_t *log) { oauth2_debug(log, "enter"); _M_cache_list_release(log); _M_cache_type_list_release(log); _oauth2_cache_global_initialized = false; oauth2_debug(log, "leave"); } bool _oauth2_cache_post_config(oauth2_log_t *log, oauth2_cache_t *cache) { bool rc = false; oauth2_debug(log, "enter"); if ((cache == NULL) || (cache->type == NULL)) goto end; if (cache->type->post_config == NULL) { rc = true; goto end; } rc = cache->type->post_config(log, cache); end: oauth2_debug(log, "return: %d", rc); return rc; } bool oauth2_cache_child_init(oauth2_log_t *log, oauth2_cache_t *cache) { bool rc = false; if ((cache == NULL) || (cache->type == NULL)) goto end; if (cache->type->child_init == NULL) { rc = true; goto end; } rc = cache->type->child_init(log, cache); end: return rc; } static bool _oauth2_cache_hash_key(oauth2_log_t *log, const char *key, const char *algo, char **hash) { bool rc = false; oauth2_debug(log, "enter: key=%s, algo=%s", key, algo); if ((algo) && (strcmp(algo, "none") == 0)) { *hash = oauth2_strdup(key); rc = true; goto end; } if (algo == NULL) algo = OAUTH2_JOSE_OPENSSL_ALG_SHA256; rc = oauth2_jose_hash2s(log, algo, key, hash); end: oauth2_debug(log, "leave: hashed key: %s", *hash); return rc; } static const char *_oauth_cache_get_enc_key(oauth2_log_t *log, oauth2_cache_t *cache) { const char *passphrase = NULL, *passphrase_hash_algo = NULL; if (cache->enc_key != NULL) goto end; passphrase = oauth2_crypto_passphrase_get(log); if (passphrase == NULL) goto end; passphrase_hash_algo = cache->passphrase_hash_algo ? passphrase_hash_algo : OAUTH2_JOSE_OPENSSL_ALG_SHA256; if (strcmp(passphrase_hash_algo, "none") == 0) { cache->enc_key = oauth2_strdup(passphrase); } else { if (oauth2_jose_hash2s(log, passphrase_hash_algo, passphrase, &cache->enc_key) == false) { oauth2_error( log, "could not hash cache encryption passphrase"); goto end; } } end: return cache->enc_key; } static int oauth2_cache_decrypt(oauth2_log_t *log, oauth2_cache_t *cache, const char *value, char **plaintext) { int len = -1; oauth2_debug(log, "enter"); if (oauth2_jose_decrypt( log, (const char *)_oauth_cache_get_enc_key(log, cache), value, plaintext) == false) goto end; len = strlen(*plaintext); end: oauth2_debug(log, "leave: len=%d", len); return len; } bool oauth2_cache_get(oauth2_log_t *log, oauth2_cache_t *cache, const char *key, char **value) { bool rc = false; char *hashed_key = NULL; char *encrypted_value = NULL; oauth2_debug(log, "enter: key=%s, type=%s, decrypt=%d", key ? key : "", cache && cache->type ? cache->type->name : "", cache ? cache->encrypt : -1); if ((cache == NULL) || (cache->type == NULL) || (cache->type->get == NULL) || (key == NULL) || (value == NULL)) goto end; if (_oauth2_cache_hash_key(log, key, cache->key_hash_algo, &hashed_key) == false) goto end; if (cache->type->get(log, cache, hashed_key, value) == false) goto end; if ((cache->encrypt) && (*value)) { if (oauth2_cache_decrypt(log, cache, *value, &encrypted_value) < 0) { oauth2_mem_free(*value); *value = NULL; goto end; } oauth2_mem_free(*value); *value = encrypted_value; } rc = true; end: if (hashed_key) oauth2_mem_free(hashed_key); oauth2_debug(log, "leave: cache %s for key: %s return: %lu bytes", rc ? (*value ? "hit" : "miss") : "error", key ? key : "", *value ? (unsigned long)strlen(*value) : 0); return rc; } static int oauth2_cache_encrypt(oauth2_log_t *log, oauth2_cache_t *cache, const char *plaintext, char **result) { int len = -1; oauth2_debug(log, "enter: %s", plaintext); if (oauth2_jose_encrypt( log, (const char *)_oauth_cache_get_enc_key(log, cache), plaintext, result) == false) goto end; len = strlen(*result); end: oauth2_debug(log, "leave: len=%d", (int)len); return len; } bool oauth2_cache_set(oauth2_log_t *log, oauth2_cache_t *cache, const char *key, const char *value, oauth2_time_t ttl_s) { bool rc = false; char *hashed_key = NULL; char *encrypted = NULL; oauth2_debug(log, "enter: key=%s, len=%lu, ttl(s)=" OAUTH2_TIME_T_FORMAT ", type=%s, encrypt=%d", key ? key : "", value ? (unsigned long)strlen(value) : 0, ttl_s, (cache && cache->type) ? cache->type->name : "", cache ? cache->encrypt : -1); if ((cache == NULL) || (cache->type == NULL) || (cache->type->set == NULL) || (key == NULL)) goto end; if (_oauth2_cache_hash_key(log, key, cache->key_hash_algo, &hashed_key) == false) goto end; if ((cache->encrypt) && (value)) if (oauth2_cache_encrypt(log, cache, value, &encrypted) < 0) goto end; if (cache->type->set(log, cache, hashed_key, encrypted ? encrypted : value, ttl_s) == false) { goto end; } rc = true; end: if (hashed_key) oauth2_mem_free(hashed_key); if (encrypted) oauth2_mem_free(encrypted); if (rc) oauth2_debug(log, "leave: successfully stored: %s", key ? key : ""); else oauth2_error(log, "leave: could NOT store: %s", key ? key : ""); return rc; } liboauth2-2.1.0/src/cache/000077500000000000000000000000001475305260400152435ustar00rootroot00000000000000liboauth2-2.1.0/src/cache/.gitignore000066400000000000000000000000561475305260400172340ustar00rootroot00000000000000/.dirstamp /.deps/ /.libs/ /*.lo /*.o /*.gcno liboauth2-2.1.0/src/cache/file.c000066400000000000000000000250041475305260400163270ustar00rootroot00000000000000/*************************************************************************** * * Copyright (C) 2018-2025 - ZmartZone Holding BV * * 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. * * @Author: Hans Zandbelt - hans.zandbelt@openidc.com * **************************************************************************/ #include #include #include #include #ifndef _WIN32 #include #define _unlink unlink #endif #include #include #include #include #include "cache_int.h" typedef struct oauth2_cache_impl_file_t { oauth2_ipc_mutex_t *mutex; char *dir; oauth2_time_t clean_interval; } oauth2_cache_impl_file_t; typedef struct { oauth2_uint_t len; oauth2_time_t expire; } oauth2_cache_file_info_t; #define OAUTH2_CACHE_FILE_PREFIX "oauth2-cache-" oauth2_cache_type_t oauth2_cache_file; static bool oauth2_cache_file_init(oauth2_log_t *log, oauth2_cache_t *cache, const oauth2_nv_list_t *options) { bool rc = false; const char *v = NULL; oauth2_cache_impl_file_t *impl = NULL; oauth2_debug(log, "enter"); impl = oauth2_mem_alloc(sizeof(oauth2_cache_impl_file_t)); if (impl == NULL) goto end; cache->impl = impl; cache->type = &oauth2_cache_file; impl->mutex = oauth2_ipc_mutex_init(log); if (impl->mutex == NULL) goto end; v = oauth2_nv_list_get(log, options, "dir"); if (v == NULL) { #ifdef WIN32 v = "C:\\TEMP"; #else v = "/tmp"; #endif } impl->dir = oauth2_strdup(v); v = oauth2_nv_list_get(log, options, "clean_interval"); impl->clean_interval = oauth2_parse_time_sec(log, v, 60); rc = true; end: oauth2_debug(log, "leave: %d", rc); return rc; } static bool oauth2_cache_file_free(oauth2_log_t *log, oauth2_cache_t *cache) { bool rc = false; oauth2_cache_impl_file_t *impl = (oauth2_cache_impl_file_t *)cache->impl; oauth2_debug(log, "enter"); if (impl == NULL) goto end; if (impl->mutex != NULL) { oauth2_ipc_mutex_free(log, impl->mutex); impl->mutex = NULL; } if (impl->dir) { oauth2_mem_free(impl->dir); impl->dir = NULL; } oauth2_mem_free(impl); cache->impl = NULL; rc = true; end: oauth2_debug(log, "leave: %d", rc); return rc; } static bool oauth2_cache_file_post_config(oauth2_log_t *log, oauth2_cache_t *cache) { bool rc = false; oauth2_cache_impl_file_t *impl = (oauth2_cache_impl_file_t *)cache->impl; oauth2_debug(log, "enter"); if (impl == NULL) goto end; rc = oauth2_ipc_mutex_post_config(log, impl->mutex); if (rc == false) goto end; // TODO: check directory accessibility ? rc = true; end: oauth2_debug(log, "leave: %d", rc); return rc; } static bool oauth2_cache_file_child_init(oauth2_log_t *log, oauth2_cache_t *cache) { bool rc = false; oauth2_cache_impl_file_t *impl = (oauth2_cache_impl_file_t *)cache->impl; oauth2_debug(log, "enter"); if (impl == NULL) goto end; // TOOD: nothing? then put function pointer in type struct to NULL rc = true; end: oauth2_debug(log, "leave: %d", rc); return rc; } static char *_oauth2_cache_file_path(oauth2_log_t *log, oauth2_cache_impl_file_t *impl, const char *key) { char *path = NULL; // TODO: WIN32 \ ? path = oauth2_strdup(impl->dir); path = oauth2_stradd(path, "/", OAUTH2_CACHE_FILE_PREFIX, key); return path; } static bool _oauth2_cache_file_read(oauth2_log_t *log, FILE *f, void *buf, const size_t len) { bool rc = false; int n = 0; n = fread(buf, 1, len, f); if (n <= 0) { oauth2_error(log, "fread failed: %s", strerror(errno)); goto end; } if (n != len) { oauth2_error(log, "fread returned %zu bytes but requested %zu bytes", n, len); goto end; } rc = true; end: return rc; } static bool _oauth2_cache_file_remove(oauth2_log_t *log, const char *path) { bool rc = true; if (_unlink(path) != 0) { oauth2_error(log, "could not delete cache file \"%s\" (%s)", path, strerror(errno)); rc = false; } return rc; } static bool oauth2_cache_file_get(oauth2_log_t *log, oauth2_cache_t *cache, const char *key, char **value) { bool rc = false; char *path = NULL; FILE *f = NULL; oauth2_cache_file_info_t info; oauth2_cache_impl_file_t *impl = (oauth2_cache_impl_file_t *)cache->impl; oauth2_debug(log, "enter"); if (impl == NULL) goto end; // TODO: // if (oauth2_cache_shm_check_key(log, impl, key) == false) // goto end; // // and/or url-encode to make a valid filename? *value = NULL; path = _oauth2_cache_file_path(log, cache->impl, key); if (oauth2_ipc_mutex_lock(log, impl->mutex) == false) goto end; f = fopen(path, "rb"); if (f == NULL) { if (errno == ENOENT) { oauth2_debug(log, "cache miss for key \"%s\"", key); rc = true; } else { oauth2_error(log, "fopen failed: %s", strerror(errno)); } goto unlock; } if (fseek(f, 0, SEEK_SET) != 0) { oauth2_error(log, "fseek failed: %s", strerror(errno)); goto unlock; } if (_oauth2_cache_file_read(log, f, &info, sizeof(oauth2_cache_file_info_t)) == false) goto unlock; if (oauth2_time_now_sec() >= info.expire) { fclose(f); f = NULL; oauth2_debug(log, "cache entry \"%s\" expired, removing file \"%s\"", key, path); rc = _oauth2_cache_file_remove(log, path); goto unlock; } *value = oauth2_mem_alloc(info.len); if (*value == NULL) goto unlock; rc = _oauth2_cache_file_read(log, f, (void *)*value, info.len); unlock: oauth2_ipc_mutex_unlock(log, impl->mutex); end: if (f) fclose(f); if (path) oauth2_mem_free(path); oauth2_debug(log, "leave: %d", rc); return rc; } static bool _oauth2_cache_file_write(oauth2_log_t *log, FILE *f, void *buf, const size_t len) { bool rc = false; int n = 0; n = fwrite(buf, 1, len, f); if (n <= 0) { oauth2_error(log, "fwrite failed: %s", strerror(errno)); goto end; } if (n != len) { oauth2_error( log, "fwrite returned %zu bytes but requested %zu bytes", n, len); goto end; } rc = true; end: return rc; } #ifdef __APPLE__ #ifndef st_mtime #define st_mtime st_mtimespec.tv_sec #endif #endif static void _oauth2_cache_files_clean(oauth2_log_t *log, oauth2_cache_impl_file_t *impl) { bool rc = false; char *path = NULL, *fpath = NULL, *filename = NULL; struct stat fi; FILE *f = NULL; DIR *d = NULL; struct dirent *dep = NULL; oauth2_cache_file_info_t info; // TODO: pretty unique, right? path = oauth2_stradd(NULL, impl->dir, "/", "__oauth2-cache-cleaned__"); if (stat(path, &fi) == 0) { if (oauth2_time_now_sec() < (fi.st_mtime + impl->clean_interval)) { oauth2_debug(log, "last cleanup call was less " "than " OAUTH2_TIME_T_FORMAT " seconds ago (next one as early as " "in " OAUTH2_TIME_T_FORMAT " secs)", impl->clean_interval, fi.st_mtime + impl->clean_interval - oauth2_time_now_sec()); goto end; } oauth2_debug(log, "start cleaning cycle"); } // create and/or set file modification time f = fopen(path, "wb"); if (f == NULL) { oauth2_error(log, "fopen failed: %s", strerror(errno)); goto end; } _oauth2_cache_file_write(log, f, (void *)"", 1); fclose(f); f = NULL; d = opendir(impl->dir); if (d == NULL) { oauth2_error(log, "opendir failed: %s", strerror(errno)); goto end; } while ((dep = readdir(d))) { filename = oauth2_stradd(NULL, impl->dir, "/", dep->d_name); if (filename == NULL) goto cont; if (stat(filename, &fi) != 0) { oauth2_error(log, "stat failed on %s: %s\n", filename, strerror(errno)); goto cont; } if ((fi.st_mode & S_IFMT) == S_IFDIR) goto cont; if (strstr(dep->d_name, OAUTH2_CACHE_FILE_PREFIX) != dep->d_name) goto cont; fpath = oauth2_stradd(NULL, impl->dir, "/", dep->d_name); if (fpath == NULL) goto cont; f = fopen(fpath, "rb"); if (f == NULL) { oauth2_error(log, "fopen failed: %s", strerror(errno)); goto cont; } rc = _oauth2_cache_file_read(log, f, &info, sizeof(oauth2_cache_file_info_t)); if ((rc == false) || (oauth2_time_now_sec() < info.expire)) { oauth2_debug( log, "cache entry (%s) expired, removing file \"%s\")", dep->d_name, fpath); if (_oauth2_cache_file_remove(log, fpath) == false) { oauth2_error(log, "could not delete cache file: %s", fpath); } } cont: if (f) { fclose(f); f = NULL; } if (filename) { oauth2_mem_free(filename); filename = NULL; } if (fpath) { oauth2_mem_free(fpath); fpath = NULL; } continue; } end: if (d) closedir(d); if (path) oauth2_mem_free(path); return; } static bool oauth2_cache_file_set(oauth2_log_t *log, oauth2_cache_t *cache, const char *key, const char *value, oauth2_time_t ttl_s) { bool rc = false; char *path = NULL; FILE *f = NULL; oauth2_cache_file_info_t info; oauth2_cache_impl_file_t *impl = (oauth2_cache_impl_file_t *)cache->impl; oauth2_debug(log, "enter"); if (impl == NULL) goto end; /* * TODO: if (oauth2_cache_shm_check_key(log, impl, key) == false) goto end; if (oauth2_cache_shm_check_value(log, impl, value) == false) goto end; */ path = _oauth2_cache_file_path(log, impl, key); if (oauth2_ipc_mutex_lock(log, impl->mutex) == false) goto end; _oauth2_cache_files_clean(log, impl); if (value == NULL) { rc = (access(path, F_OK) == 0) ? _oauth2_cache_file_remove(log, path) : true; goto unlock; } f = fopen(path, "wb"); if (f == NULL) { oauth2_error(log, "fopen failed: %s", strerror(errno)); goto unlock; } if (fseek(f, 0, SEEK_SET) != 0) { oauth2_error(log, "fseek failed: %s", strerror(errno)); goto unlock; } info.expire = oauth2_time_now_sec() + ttl_s; info.len = strlen(value) + 1; if (_oauth2_cache_file_write(log, f, &info, sizeof(oauth2_cache_file_info_t)) == false) goto unlock; rc = _oauth2_cache_file_write(log, f, (void *)value, info.len); unlock: oauth2_ipc_mutex_unlock(log, impl->mutex); end: if (f) fclose(f); if (path) oauth2_mem_free(path); oauth2_debug(log, "leave: %d", rc); return rc; } OAUTH2_CACHE_TYPE_DECLARE(file, true) liboauth2-2.1.0/src/cache/memcache.c000066400000000000000000000106711475305260400171560ustar00rootroot00000000000000/*************************************************************************** * * Copyright (C) 2018-2025 - ZmartZone Holding BV * * 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. * * @Author: Hans Zandbelt - hans.zandbelt@openidc.com * **************************************************************************/ #include #include #include #include #include "cache_int.h" #include typedef struct oauth2_cache_impl_memcache_t { memcached_st *memc; } oauth2_cache_impl_memcache_t; oauth2_cache_type_t oauth2_cache_memcache; static bool oauth2_cache_memcache_init(oauth2_log_t *log, oauth2_cache_t *cache, const oauth2_nv_list_t *options) { bool rc = false; oauth2_cache_impl_memcache_t *impl = NULL; const char *config_string = NULL; oauth2_debug(log, "enter"); impl = oauth2_mem_alloc(sizeof(oauth2_cache_impl_memcache_t)); if (impl == NULL) goto end; cache->impl = impl; cache->type = &oauth2_cache_memcache; config_string = oauth2_nv_list_get(log, options, "config_string"); if (config_string == NULL) config_string = "--SERVER=localhost"; impl->memc = memcached(config_string, strlen(config_string)); if (impl->memc == NULL) { oauth2_error(log, "call to memcached() failed"); goto end; } rc = true; end: oauth2_debug(log, "leave: %d", rc); return rc; } static bool oauth2_cache_memcache_free(oauth2_log_t *log, oauth2_cache_t *cache) { bool rc = false; oauth2_cache_impl_memcache_t *impl = (oauth2_cache_impl_memcache_t *)cache->impl; oauth2_debug(log, "enter"); if (impl == NULL) goto end; if (impl->memc) { memcached_free(impl->memc); impl->memc = NULL; } oauth2_mem_free(impl); cache->impl = NULL; rc = true; end: oauth2_debug(log, "leave: %d", rc); return rc; } static bool oauth2_cache_memcache_post_config(oauth2_log_t *log, oauth2_cache_t *cache) { bool rc = false; oauth2_cache_impl_memcache_t *impl = (oauth2_cache_impl_memcache_t *)cache->impl; oauth2_debug(log, "enter"); if (impl == NULL) goto end; //... rc = true; end: oauth2_debug(log, "leave: %d", rc); return rc; } static bool oauth2_cache_memcache_child_init(oauth2_log_t *log, oauth2_cache_t *cache) { bool rc = false; oauth2_cache_impl_memcache_t *impl = (oauth2_cache_impl_memcache_t *)cache->impl; oauth2_debug(log, "enter"); if (impl == NULL) goto end; //... rc = true; end: oauth2_debug(log, "leave: %d", rc); return rc; } static bool oauth2_cache_memcache_get(oauth2_log_t *log, oauth2_cache_t *cache, const char *key, char **value) { bool rc = false; memcached_return mrc; size_t len; uint32_t flags; oauth2_cache_impl_memcache_t *impl = (oauth2_cache_impl_memcache_t *)cache->impl; oauth2_debug(log, "enter"); if ((impl == NULL) || (impl->memc == NULL)) goto end; *value = NULL; *value = memcached_get(impl->memc, key, strlen(key), &len, &flags, &mrc); if ((mrc != MEMCACHED_SUCCESS) && (mrc != MEMCACHED_NOTFOUND)) { oauth2_error(log, "memcached_get failed: %s\n", memcached_strerror(impl->memc, mrc)); goto end; } rc = true; end: oauth2_debug(log, "leave: %d", rc); return rc; } static bool oauth2_cache_memcache_set(oauth2_log_t *log, oauth2_cache_t *cache, const char *key, const char *value, oauth2_time_t ttl_s) { bool rc = false; memcached_return mrc; uint32_t flags = 0; oauth2_cache_impl_memcache_t *impl = (oauth2_cache_impl_memcache_t *)cache->impl; oauth2_debug(log, "enter"); if ((impl == NULL) || (impl->memc == NULL)) goto end; mrc = memcached_set(impl->memc, key, strlen(key), value, value ? strlen(value) : 0, (time_t)ttl_s, flags); if (mrc != MEMCACHED_SUCCESS) { oauth2_error(log, "memcached_set failed: %s\n", memcached_strerror(impl->memc, mrc)); goto end; } rc = true; end: oauth2_debug(log, "leave: %d", rc); return rc; } OAUTH2_CACHE_TYPE_DECLARE(memcache, true) liboauth2-2.1.0/src/cache/redis.c000066400000000000000000000201121475305260400165110ustar00rootroot00000000000000/*************************************************************************** * * Copyright (C) 2018-2025 - ZmartZone Holding BV * * 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. * * @Author: Hans Zandbelt - hans.zandbelt@openidc.com * **************************************************************************/ #include #include #include #include #include #include "cache_int.h" #include "hiredis/hiredis.h" typedef struct oauth2_cache_impl_redis_t { oauth2_ipc_mutex_t *mutex; char *host_str; oauth2_uint_t port; char *username; char *passwd; redisContext *ctx; } oauth2_cache_impl_redis_t; oauth2_cache_type_t oauth2_cache_redis; static bool oauth2_cache_redis_init(oauth2_log_t *log, oauth2_cache_t *cache, const oauth2_nv_list_t *options) { bool rc = false; oauth2_cache_impl_redis_t *impl = NULL; const char *v = NULL; oauth2_debug(log, "enter"); impl = oauth2_mem_alloc(sizeof(oauth2_cache_impl_redis_t)); if (impl == NULL) goto end; cache->impl = impl; cache->type = &oauth2_cache_redis; impl->mutex = oauth2_ipc_mutex_init(log); if (impl->mutex == NULL) goto end; // TODO: #define and/or parse host:port tuple in one step v = oauth2_nv_list_get(log, options, "host"); if (v == NULL) v = "localhost"; impl->host_str = oauth2_strdup(v); v = oauth2_nv_list_get(log, options, "port"); impl->port = oauth2_parse_uint(log, v, 6379); v = oauth2_nv_list_get(log, options, "username"); impl->username = v ? oauth2_strdup(v) : NULL; v = oauth2_nv_list_get(log, options, "password"); impl->passwd = v ? oauth2_strdup(v) : NULL; impl->ctx = NULL; rc = true; end: oauth2_debug(log, "leave: %d", rc); return rc; } static bool oauth2_cache_redis_free(oauth2_log_t *log, oauth2_cache_t *cache) { bool rc = false; oauth2_cache_impl_redis_t *impl = (oauth2_cache_impl_redis_t *)cache->impl; oauth2_debug(log, "enter"); if (impl == NULL) goto end; if (impl->mutex) { oauth2_ipc_mutex_lock(log, impl->mutex); if (impl->ctx) { redisFree(impl->ctx); impl->ctx = NULL; } oauth2_ipc_mutex_unlock(log, impl->mutex); oauth2_ipc_mutex_free(log, impl->mutex); impl->mutex = NULL; } if (impl->host_str) oauth2_mem_free(impl->host_str); if (impl->username) oauth2_mem_free(impl->username); if (impl->passwd) oauth2_mem_free(impl->passwd); oauth2_mem_free(impl); cache->impl = NULL; rc = true; end: oauth2_debug(log, "leave: %d", rc); return rc; } static bool oauth2_cache_redis_post_config(oauth2_log_t *log, oauth2_cache_t *cache) { bool rc = false; oauth2_cache_impl_redis_t *impl = (oauth2_cache_impl_redis_t *)cache->impl; oauth2_debug(log, "enter"); if (impl == NULL) goto end; rc = oauth2_ipc_mutex_post_config(log, impl->mutex); if (rc == false) goto end; // TODO: connect to the server here? rc = true; end: oauth2_debug(log, "leave: %d", rc); return rc; } static bool oauth2_cache_redis_child_init(oauth2_log_t *log, oauth2_cache_t *cache) { bool rc = false; oauth2_cache_impl_redis_t *impl = (oauth2_cache_impl_redis_t *)cache->impl; oauth2_debug(log, "enter"); if (impl == NULL) goto end; // TODO: nothing? rc = true; end: oauth2_debug(log, "leave: %d", rc); return rc; } static bool _oauth2_cache_redis_connect(oauth2_log_t *log, oauth2_cache_impl_redis_t *impl) { bool rc = false; if (impl->ctx) { rc = true; goto end; } impl->ctx = redisConnect(impl->host_str, impl->port); if ((impl->ctx == NULL) || (impl->ctx->err != 0)) { oauth2_error(log, "failed to connect to Redis server (%s:%d): '%s'", impl->host_str, impl->port, impl->ctx != NULL ? impl->ctx->errstr : ""); redisFree(impl->ctx); impl->ctx = NULL; goto end; } oauth2_debug( log, "successfully connected to Redis server (%s:" OAUTH2_UINT_FORMAT ")", impl->host_str, impl->port); rc = true; end: return rc; } #define OIDC_REDIS_MAX_TRIES 2 static redisReply *_oauth2_cache_redis_command(oauth2_log_t *log, oauth2_cache_impl_redis_t *impl, const char *command) { redisReply *reply = NULL; int i = 0; oauth2_debug(log, "enter: %s", command); for (i = 0; i < OIDC_REDIS_MAX_TRIES; i++) { if (_oauth2_cache_redis_connect(log, impl) == false) break; if (impl->passwd != NULL) { if (impl->username != NULL) reply = redisCommand(impl->ctx, "AUTH %s %s", impl->username, impl->passwd); else reply = redisCommand(impl->ctx, "AUTH %s", impl->passwd); if ((reply == NULL) || (reply->type == REDIS_REPLY_ERROR)) oauth2_error( log, "Redis AUTH command (attempt=%d to " "%s:" OAUTH2_UINT_FORMAT ") failed: '%s' [%s]", i, impl->host_str, impl->port, impl->ctx->errstr, reply ? reply->str : ""); if (reply) { freeReplyObject(reply); reply = NULL; } } reply = redisCommand(impl->ctx, command); if ((reply != NULL) && (reply->type != REDIS_REPLY_ERROR)) break; oauth2_error( log, "Redis command (attempt=%d to %s:" OAUTH2_UINT_FORMAT ") failed, disconnecting: '%s' [%s]", i, impl->host_str, impl->port, impl->ctx->errstr, reply ? reply->str : ""); if (reply) { freeReplyObject(reply); reply = NULL; } redisFree(impl->ctx); impl->ctx = NULL; } oauth2_debug(log, "leave: %p", reply); return reply; } static bool oauth2_cache_redis_get(oauth2_log_t *log, oauth2_cache_t *cache, const char *key, char **value) { bool rc = false; redisReply *reply = NULL; char *cmd = NULL; oauth2_cache_impl_redis_t *impl = (oauth2_cache_impl_redis_t *)cache->impl; oauth2_debug(log, "enter"); if (impl == NULL) goto end; *value = NULL; if (oauth2_ipc_mutex_lock(log, impl->mutex) == false) goto end; cmd = oauth2_stradd(NULL, "GET", " ", key); reply = _oauth2_cache_redis_command(log, impl, cmd); if (reply == NULL) goto unlock; if (reply->type == REDIS_REPLY_NIL) { rc = true; goto unlock; } // TODO: should we not store the \0 and/or allow binary data? if (reply->len != strlen(reply->str)) { oauth2_error( log, "redisCommand reply->len != strlen(reply->str): '%s'", reply->str); goto unlock; } *value = oauth2_strndup(reply->str, reply->len); rc = true; unlock: oauth2_ipc_mutex_unlock(log, impl->mutex); end: if (cmd) oauth2_mem_free(cmd); if (reply) freeReplyObject(reply); oauth2_debug(log, "leave: %d", rc); return rc; } #define OAUTH2_UINT_MAX_STR 64 static bool oauth2_cache_redis_set(oauth2_log_t *log, oauth2_cache_t *cache, const char *key, const char *value, oauth2_time_t ttl_s) { bool rc = false; redisReply *reply = NULL; char *cmd = NULL; char s_timeout[OAUTH2_UINT_MAX_STR]; oauth2_cache_impl_redis_t *impl = (oauth2_cache_impl_redis_t *)cache->impl; oauth2_debug(log, "enter"); if (impl == NULL) goto end; if (oauth2_ipc_mutex_lock(log, impl->mutex) == false) goto end; if (value) { oauth2_snprintf(s_timeout, OAUTH2_UINT_MAX_STR, "" OAUTH2_TIME_T_FORMAT "", ttl_s); cmd = oauth2_strdup("SETEX "); cmd = oauth2_stradd(cmd, key, " ", s_timeout); cmd = oauth2_stradd(cmd, " ", value, NULL); } else { cmd = oauth2_stradd(NULL, "DEL", " ", key); } reply = _oauth2_cache_redis_command(log, impl, cmd); if (reply == NULL) goto unlock; rc = (reply->type != REDIS_REPLY_ERROR); unlock: oauth2_ipc_mutex_unlock(log, impl->mutex); end: if (cmd) oauth2_mem_free(cmd); if (reply) freeReplyObject(reply); oauth2_debug(log, "leave: %d", rc); return rc; } OAUTH2_CACHE_TYPE_DECLARE(redis, true) liboauth2-2.1.0/src/cache/shm.c000066400000000000000000000254541475305260400162100ustar00rootroot00000000000000/*************************************************************************** * * Copyright (C) 2018-2025 - ZmartZone Holding BV * * 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. * * @Author: Hans Zandbelt - hans.zandbelt@openidc.com * **************************************************************************/ #include #include #include #include #include #include "cache_int.h" typedef struct oauth2_cache_impl_shm_t { oauth2_ipc_shm_t *shm; oauth2_ipc_mutex_t *mutex; oauth2_uint_t max_key_size; oauth2_uint_t max_val_size; oauth2_uint_t max_entries; } oauth2_cache_impl_shm_t; typedef __attribute__((aligned(64))) struct oauth2_cache_shm_entry_t { oauth2_time_t access_s; oauth2_time_t expires_s; uint8_t key_and_value[]; } oauth2_cache_shm_entry_t; #define OAUTH2_CACHE_SHM_KEY_OFFSET(ptr) (uint8_t *)&ptr->key_and_value[0] #define OAUTH2_CACHE_SHM_VALUE_OFFSET(ptr, impl) \ (uint8_t *)&ptr->key_and_value[impl->max_key_size] #define OAUTH2_CACHE_SHM_SLOT_SIZE(impl) \ (sizeof(oauth2_cache_shm_entry_t) + impl->max_key_size + \ impl->max_val_size) #define OAUTH2_CACHE_SHM_ADD_OFFSET(ptr, impl) \ ptr = (oauth2_cache_shm_entry_t *)((uint8_t *)ptr + \ OAUTH2_CACHE_SHM_SLOT_SIZE(impl)) #define OAUTH2_CACHE_SHM_MAX_KEY_SIZE "max_key_size" #define OAUTH2_CACHE_SHM_MAX_VALUE_SIZE "max_val_size" #define OAUTH2_CACHE_SHM_MAX_ENTRIES "max_entries" #define OAUTH2_CACHE_SHM_MAX_KEY_SIZE_DEFAULT 64 #define OAUTH2_CACHE_SHM_MAX_VALUE_SIZE_DEFAULT 8192 #define OAUTH2_CACHE_SHM_MAX_ENTRIES_DEFAULT 1000 oauth2_cache_type_t oauth2_cache_shm; static bool oauth2_cache_shm_init(oauth2_log_t *log, oauth2_cache_t *cache, const oauth2_nv_list_t *options) { bool rc = false; oauth2_cache_impl_shm_t *impl = NULL; oauth2_uint_t n = 0; oauth2_debug(log, "enter"); impl = oauth2_mem_alloc(sizeof(oauth2_cache_impl_shm_t)); if (impl == NULL) goto end; cache->impl = impl; cache->type = &oauth2_cache_shm; impl->mutex = oauth2_ipc_mutex_init(log); if (impl->mutex == NULL) goto end; n = oauth2_parse_uint( log, oauth2_nv_list_get(log, options, OAUTH2_CACHE_SHM_MAX_KEY_SIZE), OAUTH2_CACHE_SHM_MAX_KEY_SIZE_DEFAULT); impl->max_key_size = (n / 64 + 1) * 64; impl->max_val_size = oauth2_parse_uint( log, oauth2_nv_list_get(log, options, OAUTH2_CACHE_SHM_MAX_VALUE_SIZE), OAUTH2_CACHE_SHM_MAX_VALUE_SIZE_DEFAULT); impl->max_entries = oauth2_parse_uint( log, oauth2_nv_list_get(log, options, OAUTH2_CACHE_SHM_MAX_ENTRIES), OAUTH2_CACHE_SHM_MAX_ENTRIES_DEFAULT); oauth2_debug(log, "creating shm cache: %s=" OAUTH2_UINT_FORMAT " %s=" OAUTH2_UINT_FORMAT " %s=" OAUTH2_UINT_FORMAT "", OAUTH2_CACHE_SHM_MAX_KEY_SIZE, impl->max_key_size, OAUTH2_CACHE_SHM_MAX_VALUE_SIZE, impl->max_val_size, OAUTH2_CACHE_SHM_MAX_ENTRIES, impl->max_entries); impl->shm = oauth2_ipc_shm_init(log, OAUTH2_CACHE_SHM_SLOT_SIZE(impl) * impl->max_entries); if (impl->shm == NULL) goto end; rc = true; end: oauth2_debug(log, "leave: %d", rc); return rc; } static bool oauth2_cache_shm_free(oauth2_log_t *log, oauth2_cache_t *cache) { bool rc = false; oauth2_cache_impl_shm_t *impl = (oauth2_cache_impl_shm_t *)cache->impl; oauth2_debug(log, "enter"); if (impl == NULL) goto end; if (impl->mutex != NULL) { oauth2_ipc_mutex_lock(log, impl->mutex); oauth2_ipc_shm_free(log, impl->shm); oauth2_ipc_mutex_unlock(log, impl->mutex); oauth2_ipc_mutex_free(log, impl->mutex); impl->mutex = NULL; } oauth2_mem_free(impl); cache->impl = NULL; rc = true; end: oauth2_debug(log, "leave: %d", rc); return rc; } static bool oauth2_cache_shm_post_config(oauth2_log_t *log, oauth2_cache_t *cache) { bool rc = false; int i = 0; oauth2_cache_shm_entry_t *ptr = NULL; oauth2_cache_impl_shm_t *impl = (oauth2_cache_impl_shm_t *)cache->impl; oauth2_debug(log, "enter"); if (impl == NULL) goto end; rc = oauth2_ipc_mutex_post_config(log, impl->mutex); if (rc == false) goto end; rc = oauth2_ipc_shm_post_config(log, impl->shm); if (rc == false) goto end; ptr = oauth2_ipc_shm_get(log, impl->shm); if (ptr == NULL) { oauth2_error(log, "oauth2_ipc_shm_get failed"); goto end; } for (i = 0; i < impl->max_entries; i++, OAUTH2_CACHE_SHM_ADD_OFFSET(ptr, impl)) { ptr->access_s = 0; ptr->expires_s = 0; *OAUTH2_CACHE_SHM_KEY_OFFSET(ptr) = '\0'; *OAUTH2_CACHE_SHM_VALUE_OFFSET(ptr, impl) = '\0'; } oauth2_debug(log, "initialized shared memory with a cache size (# " "entries) of: " OAUTH2_UINT_FORMAT ", and a max (single) slot size of: " OAUTH2_UINT_FORMAT, impl->max_entries, OAUTH2_CACHE_SHM_SLOT_SIZE(impl)); rc = true; end: oauth2_debug(log, "leave: %d", rc); return rc; } static bool oauth2_cache_shm_child_init(oauth2_log_t *log, oauth2_cache_t *cache) { bool rc = false; oauth2_cache_impl_shm_t *impl = (oauth2_cache_impl_shm_t *)cache->impl; oauth2_debug(log, "enter"); if (impl == NULL) goto end; rc = oauth2_ipc_shm_child_init(log, impl->shm); if (rc == false) goto end; rc = true; end: oauth2_debug(log, "leave: %d", rc); return rc; } static bool oauth2_cache_shm_check_key(oauth2_log_t *log, oauth2_cache_impl_shm_t *impl, const char *key) { bool rc = true; if (strlen(key) > impl->max_key_size) { oauth2_error(log, "could not construct cache key since key size is " "too large (%lu > " OAUTH2_UINT_FORMAT ") : %s", (unsigned long)strlen(key), impl->max_key_size, key); rc = false; } return rc; } static bool oauth2_cache_shm_get(oauth2_log_t *log, oauth2_cache_t *cache, const char *key, char **value) { bool rc = false; int i = 0; oauth2_cache_shm_entry_t *ptr = NULL; const char *entry_key = NULL; oauth2_time_t now_s = 0; oauth2_cache_impl_shm_t *impl = (oauth2_cache_impl_shm_t *)cache->impl; oauth2_debug(log, "enter"); if (impl == NULL) goto end; if (oauth2_cache_shm_check_key(log, impl, key) == false) goto end; *value = NULL; if (oauth2_ipc_mutex_lock(log, impl->mutex) == false) goto end; ptr = oauth2_ipc_shm_get(log, impl->shm); if (ptr == NULL) goto unlock; now_s = oauth2_time_now_sec(); for (i = 0; i < impl->max_entries; i++, OAUTH2_CACHE_SHM_ADD_OFFSET(ptr, impl)) { entry_key = (const char *)OAUTH2_CACHE_SHM_KEY_OFFSET(ptr); oauth2_trace2(log, "loop: %s", entry_key); if ((entry_key[0] != '\0') && (strncmp(entry_key, key, impl->max_key_size) == 0)) { oauth2_debug(log, "found: %s (expires=" OAUTH2_TIME_T_FORMAT ", now=" OAUTH2_TIME_T_FORMAT ")", entry_key, ptr->expires_s, now_s); if (ptr->expires_s > now_s) { oauth2_debug(log, "not expired: %s", entry_key); ptr->access_s = now_s; *value = oauth2_strdup( (const char *)OAUTH2_CACHE_SHM_VALUE_OFFSET( ptr, impl)); } else { oauth2_debug(log, "expired, clean: %s", entry_key); *OAUTH2_CACHE_SHM_KEY_OFFSET(ptr) = '\0'; ptr->access_s = 0; } break; } } rc = true; unlock: oauth2_ipc_mutex_unlock(log, impl->mutex); end: oauth2_debug(log, "leave: %d", rc); return rc; } static bool oauth2_cache_shm_check_value(oauth2_log_t *log, oauth2_cache_impl_shm_t *impl, const char *value) { bool rc = true; if ((value != NULL) && (strlen(value) > impl->max_val_size)) { oauth2_error(log, "could not store value since value size is too " "large (%lu > " OAUTH2_UINT_FORMAT ")", (unsigned long)strlen(value), (unsigned long)impl->max_val_size); rc = false; } return rc; } static bool oauth2_cache_shm_set(oauth2_log_t *log, oauth2_cache_t *cache, const char *key, const char *value, oauth2_time_t ttl_s) { bool rc = false; oauth2_cache_shm_entry_t *match, *free, *lru; oauth2_cache_shm_entry_t *ptr; int i = 0; oauth2_time_t now_s, age_s = 0; oauth2_cache_impl_shm_t *impl = (oauth2_cache_impl_shm_t *)cache->impl; oauth2_debug(log, "enter"); if (impl == NULL) goto end; if (oauth2_cache_shm_check_key(log, impl, key) == false) goto end; if (oauth2_cache_shm_check_value(log, impl, value) == false) goto end; if (oauth2_ipc_mutex_lock(log, impl->mutex) == false) goto end; ptr = oauth2_ipc_shm_get(log, impl->shm); if (ptr == NULL) goto unlock; now_s = oauth2_time_now_sec(); match = NULL; free = NULL; lru = ptr; for (i = 0; i < impl->max_entries; i++, OAUTH2_CACHE_SHM_ADD_OFFSET(ptr, impl)) { if (*OAUTH2_CACHE_SHM_KEY_OFFSET(ptr) == '\0') { if (free == NULL) free = ptr; continue; } if (strncmp((const char *)OAUTH2_CACHE_SHM_KEY_OFFSET(ptr), key, impl->max_key_size) == 0) { match = ptr; break; } if (ptr->expires_s <= now_s) { if (free == NULL) free = ptr; continue; } if (ptr->access_s < lru->access_s) { lru = ptr; } } if (match == NULL && free == NULL) { age_s = (now_s - lru->access_s); // TODO: make this 1 hour warning window configurable? if (age_s < 3600) { oauth2_warn( log, "dropping LRU entry with age=" OAUTH2_TIME_T_FORMAT " secs, which is less than one hour; consider " "increasing the cache size through the setting for " "the maximum number of cache entries that can be " "held, which is " OAUTH2_UINT_FORMAT " now", age_s, impl->max_entries); } } ptr = match ? match : (free ? free : lru); if (value != NULL) { /* memcpy((char *)OAUTH2_CACHE_SHM_KEY_OFFSET(ptr), key, strlen(key)); ((char *)OAUTH2_CACHE_SHM_KEY_OFFSET(ptr))[strlen(key)] = '\0'; memcpy((char *)OAUTH2_CACHE_SHM_VALUE_OFFSET(ptr, impl), value, strlen(value)); ((char *)OAUTH2_CACHE_SHM_VALUE_OFFSET(ptr, impl))[strlen(value)] = '\0'; */ oauth2_snprintf((char *)OAUTH2_CACHE_SHM_KEY_OFFSET(ptr), impl->max_key_size, "%s", key); oauth2_snprintf( (char *)OAUTH2_CACHE_SHM_VALUE_OFFSET(ptr, impl), impl->max_val_size, "%s", value); ptr->access_s = now_s; ptr->expires_s = now_s + ttl_s; } else { *OAUTH2_CACHE_SHM_KEY_OFFSET(ptr) = '\0'; ptr->access_s = 0; } rc = true; unlock: oauth2_ipc_mutex_unlock(log, impl->mutex); end: oauth2_debug(log, "leave: %d", rc); return rc; } OAUTH2_CACHE_TYPE_DECLARE(shm, false) liboauth2-2.1.0/src/cache_int.h000066400000000000000000000035041475305260400162700ustar00rootroot00000000000000#ifndef _OAUTH2_CACHE_INT_H_ #define _OAUTH2_CACHE_INT_H_ /*************************************************************************** * * Copyright (C) 2018-2025 - ZmartZone Holding BV * * 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. * * @Author: Hans Zandbelt - hans.zandbelt@openidc.com * **************************************************************************/ #include "oauth2/cache.h" #include "oauth2/log.h" typedef struct oauth2_cache_t { void *impl; oauth2_cache_type_t *type; char *key_hash_algo; bool encrypt; char *enc_key; char *passphrase_hash_algo; } oauth2_cache_t; oauth2_cache_t *_oauth2_cache_init(oauth2_log_t *log, const char *type, const oauth2_nv_list_t *params); bool _oauth2_cache_post_config(oauth2_log_t *log, oauth2_cache_t *cache); bool _oauth2_cache_child_init(oauth2_log_t *log, oauth2_cache_t *cache); void _oauth2_cache_global_cleanup(oauth2_log_t *log); // clang-format off #define OAUTH2_CACHE_TYPE_DECLARE(type, encrypt) \ oauth2_cache_type_t oauth2_cache_##type = { \ #type, \ encrypt, \ oauth2_cache_##type##_init, \ oauth2_cache_##type##_post_config, \ oauth2_cache_##type##_child_init, \ oauth2_cache_##type##_get, \ oauth2_cache_##type##_set, \ oauth2_cache_##type##_free \ }; // clang-format on #endif /* _OAUTH2_CACHE_INT_H_ */ liboauth2-2.1.0/src/cfg/000077500000000000000000000000001475305260400147375ustar00rootroot00000000000000liboauth2-2.1.0/src/cfg/.gitignore000066400000000000000000000000561475305260400167300ustar00rootroot00000000000000/.dirstamp /.deps/ /.libs/ /*.lo /*.o /*.gcno liboauth2-2.1.0/src/cfg/auth.c000066400000000000000000000330161475305260400160470ustar00rootroot00000000000000/*************************************************************************** * * Copyright (C) 2018-2025 - ZmartZone Holding BV * * 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. * * @Author: Hans Zandbelt - hans.zandbelt@openidc.com * **************************************************************************/ #include "oauth2/cfg.h" #include "oauth2/mem.h" #include "cfg_int.h" #include #define OAUTH2_ENDPOINT_AUTH_NONE_STR "none" #define OAUTH2_ENDPOINT_AUTH_CLIENT_SECRET_BASIC_STR "client_secret_basic" #define OAUTH2_ENDPOINT_AUTH_CLIENT_SECRET_POST_STR "client_secret_post" #define OAUTH2_ENDPOINT_AUTH_CLIENT_SECRET_JWT_STR "client_secret_jwt" #define OAUTH2_ENDPOINT_AUTH_PRIVATE_KEY_JWT_STR "private_key_jwt" #define OAUTH2_ENDPOINT_AUTH_CLIENT_CERT_STR "client_cert" #define OAUTH2_ENDPOINT_AUTH_BASIC_STR "basic" oauth2_cfg_endpoint_auth_t *oauth2_cfg_endpoint_auth_init(oauth2_log_t *log) { oauth2_cfg_endpoint_auth_t *auth = (oauth2_cfg_endpoint_auth_t *)oauth2_mem_alloc( sizeof(oauth2_cfg_endpoint_auth_t)); auth->type = OAUTH2_ENDPOINT_AUTH_NONE; return auth; } void oauth2_cfg_endpoint_auth_free(oauth2_log_t *log, oauth2_cfg_endpoint_auth_t *auth) { if (auth == NULL) goto end; switch (auth->type) { case OAUTH2_ENDPOINT_AUTH_NONE: break; case OAUTH2_ENDPOINT_AUTH_CLIENT_SECRET_BASIC: if (auth->client_secret_basic.client_id) oauth2_mem_free(auth->client_secret_basic.client_id); if (auth->client_secret_basic.client_secret) oauth2_mem_free( auth->client_secret_basic.client_secret); break; case OAUTH2_ENDPOINT_AUTH_CLIENT_SECRET_POST: if (auth->client_secret_post.client_id) oauth2_mem_free(auth->client_secret_post.client_id); if (auth->client_secret_post.client_secret) oauth2_mem_free(auth->client_secret_post.client_secret); break; case OAUTH2_ENDPOINT_AUTH_CLIENT_SECRET_JWT: if (auth->client_secret_jwt.client_id) oauth2_mem_free(auth->client_secret_jwt.client_id); if (auth->client_secret_jwt.jwk) cjose_jwk_release(auth->client_secret_jwt.jwk); if (auth->client_secret_jwt.aud) oauth2_mem_free(auth->client_secret_jwt.aud); break; case OAUTH2_ENDPOINT_AUTH_PRIVATE_KEY_JWT: if (auth->private_key_jwt.client_id) oauth2_mem_free(auth->private_key_jwt.client_id); if (auth->private_key_jwt.jwk) cjose_jwk_release(auth->private_key_jwt.jwk); if (auth->private_key_jwt.aud) oauth2_mem_free(auth->private_key_jwt.aud); break; case OAUTH2_ENDPOINT_AUTH_CLIENT_CERT: if (auth->client_cert.certfile) oauth2_mem_free(auth->client_cert.certfile); if (auth->client_cert.keyfile) oauth2_mem_free(auth->client_cert.keyfile); break; case OAUTH2_ENDPOINT_AUTH_BASIC: if (auth->basic.username) oauth2_mem_free(auth->basic.username); if (auth->basic.password) oauth2_mem_free(auth->basic.password); break; } oauth2_mem_free(auth); end: return; } oauth2_cfg_endpoint_auth_t * oauth2_cfg_endpoint_auth_clone(oauth2_log_t *log, const oauth2_cfg_endpoint_auth_t *src) { oauth2_cfg_endpoint_auth_t *dst = NULL; cjose_err err; if (src == NULL) goto end; dst = oauth2_cfg_endpoint_auth_init(log); dst->type = src->type; switch (dst->type) { case OAUTH2_ENDPOINT_AUTH_NONE: dst->none = src->none; break; case OAUTH2_ENDPOINT_AUTH_CLIENT_SECRET_BASIC: dst->client_secret_basic.client_id = oauth2_strdup(src->client_secret_basic.client_id); dst->client_secret_basic.client_secret = oauth2_strdup(src->client_secret_basic.client_secret); break; case OAUTH2_ENDPOINT_AUTH_CLIENT_SECRET_POST: dst->client_secret_post.client_id = oauth2_strdup(src->client_secret_post.client_id); dst->client_secret_post.client_secret = oauth2_strdup(src->client_secret_post.client_secret); break; case OAUTH2_ENDPOINT_AUTH_CLIENT_SECRET_JWT: dst->client_secret_jwt.aud = oauth2_strdup(src->client_secret_jwt.aud); dst->client_secret_jwt.client_id = oauth2_strdup(src->client_secret_jwt.client_id); dst->client_secret_jwt.jwk = cjose_jwk_retain(src->client_secret_jwt.jwk, &err); break; case OAUTH2_ENDPOINT_AUTH_PRIVATE_KEY_JWT: dst->private_key_jwt.aud = oauth2_strdup(src->private_key_jwt.aud); dst->private_key_jwt.client_id = oauth2_strdup(src->private_key_jwt.client_id); dst->private_key_jwt.jwk = cjose_jwk_retain(src->private_key_jwt.jwk, &err); break; case OAUTH2_ENDPOINT_AUTH_CLIENT_CERT: dst->client_cert.certfile = oauth2_strdup(src->client_cert.certfile); dst->client_cert.keyfile = oauth2_strdup(src->client_cert.keyfile); break; case OAUTH2_ENDPOINT_AUTH_BASIC: dst->basic.username = oauth2_strdup(src->basic.username); dst->basic.password = oauth2_strdup(src->basic.password); break; } end: return dst; } static char * oauth2_cfg_endpoint_auth_none_options_set(oauth2_log_t *log, oauth2_cfg_endpoint_auth_t *auth, const oauth2_nv_list_t *params) { auth->type = OAUTH2_ENDPOINT_AUTH_NONE; return NULL; } static char *oauth2_cfg_endpoint_auth_client_secret_basic_options_set( oauth2_log_t *log, oauth2_cfg_endpoint_auth_t *auth, const oauth2_nv_list_t *params) { char *rv = NULL; auth->type = OAUTH2_ENDPOINT_AUTH_CLIENT_SECRET_BASIC; auth->client_secret_basic.client_id = oauth2_strdup(oauth2_nv_list_get(log, params, "client_id")); if (auth->client_secret_basic.client_id == NULL) { rv = oauth2_stradd(NULL, "client_id", " must be set for ", OAUTH2_ENDPOINT_AUTH_CLIENT_SECRET_BASIC_STR); goto end; } auth->client_secret_basic.client_secret = oauth2_strdup(oauth2_nv_list_get(log, params, "client_secret")); if (auth->client_secret_basic.client_secret == NULL) { rv = oauth2_stradd(NULL, "client_secret", " must be set for ", OAUTH2_ENDPOINT_AUTH_CLIENT_SECRET_BASIC_STR); goto end; } end: return rv; } static char *oauth2_cfg_endpoint_auth_client_secret_post_options_set( oauth2_log_t *log, oauth2_cfg_endpoint_auth_t *auth, const oauth2_nv_list_t *params) { char *rv = NULL; auth->type = OAUTH2_ENDPOINT_AUTH_CLIENT_SECRET_POST; auth->client_secret_post.client_id = oauth2_strdup(oauth2_nv_list_get(log, params, "client_id")); if (auth->client_secret_post.client_id == NULL) { rv = oauth2_stradd(NULL, "client_id", " must be set for ", OAUTH2_ENDPOINT_AUTH_CLIENT_SECRET_POST_STR); goto end; } auth->client_secret_post.client_secret = oauth2_strdup(oauth2_nv_list_get(log, params, "client_secret")); if (auth->client_secret_post.client_secret == NULL) { rv = oauth2_stradd(NULL, "client_secret", " must be set for ", OAUTH2_ENDPOINT_AUTH_CLIENT_SECRET_POST_STR); goto end; } end: return rv; } static char *oauth2_cfg_endpoint_auth_client_secret_jwt_options_set( oauth2_log_t *log, oauth2_cfg_endpoint_auth_t *auth, const oauth2_nv_list_t *params) { char *rv = NULL; const char *client_secret = NULL; cjose_err err; auth->type = OAUTH2_ENDPOINT_AUTH_CLIENT_SECRET_JWT; auth->client_secret_jwt.client_id = oauth2_strdup(oauth2_nv_list_get(log, params, "client_id")); if (auth->client_secret_jwt.client_id == NULL) { rv = oauth2_stradd(NULL, "client_id", " must be set for ", OAUTH2_ENDPOINT_AUTH_CLIENT_SECRET_JWT_STR); goto end; } client_secret = oauth2_nv_list_get(log, params, "client_secret"); if (client_secret == NULL) { rv = oauth2_stradd(NULL, "client_secret", " must be set for ", OAUTH2_ENDPOINT_AUTH_CLIENT_SECRET_JWT_STR); goto end; } err.code = CJOSE_ERR_NONE; auth->client_secret_jwt.jwk = cjose_jwk_create_oct_spec( (const unsigned char *)client_secret, strlen(client_secret), &err); if (auth->client_secret_jwt.jwk == NULL) { rv = oauth2_stradd(NULL, "cjose_jwk_create_oct_spec failed: ", err.message, NULL); goto end; } // auth->client_secret_jwt.client_secret = // oauth2_strdup(oauth2_nv_list_get(log, params, "client_secret")); // if //(auth->client_secret_jwt.client_secret == NULL) { rv = // oauth2_stradd(NULL, "client_secret", " must be set for ", // OAUTH2_ENDPOINT_AUTH_CLIENT_SECRET_JWT_STR); goto // end; // } auth->client_secret_jwt.aud = oauth2_strdup(oauth2_nv_list_get(log, params, "aud")); if (auth->client_secret_jwt.aud == NULL) { rv = oauth2_stradd(NULL, "aud", " must be set for ", OAUTH2_ENDPOINT_AUTH_CLIENT_SECRET_JWT_STR); goto end; } end: return rv; } static char *oauth2_cfg_endpoint_auth_private_key_jwt_options_set( oauth2_log_t *log, oauth2_cfg_endpoint_auth_t *auth, const oauth2_nv_list_t *params) { char *rv = NULL; const char *jwk = NULL; cjose_err err; auth->type = OAUTH2_ENDPOINT_AUTH_PRIVATE_KEY_JWT; auth->private_key_jwt.client_id = oauth2_strdup(oauth2_nv_list_get(log, params, "client_id")); if (auth->private_key_jwt.client_id == NULL) { rv = oauth2_stradd(NULL, "client_id", " must be set for ", OAUTH2_ENDPOINT_AUTH_PRIVATE_KEY_JWT_STR); goto end; } err.code = CJOSE_ERR_NONE; jwk = oauth2_nv_list_get(log, params, "jwk"); if (jwk == NULL) { rv = oauth2_stradd(NULL, "jwk", " must be set for ", OAUTH2_ENDPOINT_AUTH_PRIVATE_KEY_JWT_STR); goto end; } auth->private_key_jwt.jwk = cjose_jwk_import(jwk, strlen(jwk), &err); if (auth->private_key_jwt.jwk == NULL) { rv = oauth2_stradd(NULL, "parsing JWK failed: ", "cjose_jwk_import error: ", err.message); goto end; } auth->private_key_jwt.aud = oauth2_strdup(oauth2_nv_list_get(log, params, "aud")); if (auth->private_key_jwt.aud == NULL) { rv = oauth2_stradd(NULL, "aud", " must be set for ", OAUTH2_ENDPOINT_AUTH_PRIVATE_KEY_JWT_STR); goto end; } end: return rv; } static char *oauth2_cfg_endpoint_auth_client_cert_options_set( oauth2_log_t *log, oauth2_cfg_endpoint_auth_t *auth, const oauth2_nv_list_t *params) { char *rv = NULL; auth->type = OAUTH2_ENDPOINT_AUTH_CLIENT_CERT; auth->client_cert.certfile = oauth2_strdup(oauth2_nv_list_get(log, params, "cert")); if (auth->client_cert.certfile == NULL) { rv = oauth2_stradd(NULL, "cert", " must be set for ", OAUTH2_ENDPOINT_AUTH_CLIENT_CERT_STR); goto end; } auth->client_cert.keyfile = oauth2_strdup(oauth2_nv_list_get(log, params, "key")); if (auth->client_cert.keyfile == NULL) { rv = oauth2_stradd(NULL, "key", " must be set for ", OAUTH2_ENDPOINT_AUTH_CLIENT_CERT_STR); goto end; } end: return rv; } static char * oauth2_cfg_endpoint_auth_basic_options_set(oauth2_log_t *log, oauth2_cfg_endpoint_auth_t *auth, const oauth2_nv_list_t *params) { char *rv = NULL; auth->type = OAUTH2_ENDPOINT_AUTH_BASIC; auth->basic.username = oauth2_strdup(oauth2_nv_list_get(log, params, "username")); auth->basic.password = oauth2_strdup(oauth2_nv_list_get(log, params, "password")); return rv; } typedef char *( oauth2_cfg_endpoint_auth_set_options_cb_t)(oauth2_log_t *log, oauth2_cfg_endpoint_auth_t *auth, const oauth2_nv_list_t *params); typedef struct oauth2_cfg_endpoint_auth_set_options_ctx_t { const char *type; oauth2_cfg_endpoint_auth_set_options_cb_t *options_callback; } oauth2_cfg_endpoint_auth_set_options_ctx_t; // clang-format off static oauth2_cfg_endpoint_auth_set_options_ctx_t _oauth2_cfg_endpoint_auth_options_set[] = { { OAUTH2_ENDPOINT_AUTH_NONE_STR, oauth2_cfg_endpoint_auth_none_options_set }, { OAUTH2_ENDPOINT_AUTH_CLIENT_SECRET_BASIC_STR, oauth2_cfg_endpoint_auth_client_secret_basic_options_set }, { OAUTH2_ENDPOINT_AUTH_CLIENT_SECRET_POST_STR, oauth2_cfg_endpoint_auth_client_secret_post_options_set }, { OAUTH2_ENDPOINT_AUTH_CLIENT_SECRET_JWT_STR, oauth2_cfg_endpoint_auth_client_secret_jwt_options_set }, { OAUTH2_ENDPOINT_AUTH_PRIVATE_KEY_JWT_STR, oauth2_cfg_endpoint_auth_private_key_jwt_options_set }, { OAUTH2_ENDPOINT_AUTH_CLIENT_CERT_STR, oauth2_cfg_endpoint_auth_client_cert_options_set }, { OAUTH2_ENDPOINT_AUTH_BASIC_STR, oauth2_cfg_endpoint_auth_basic_options_set }, { NULL, NULL } }; // clang-format on char *oauth2_cfg_set_endpoint_auth(oauth2_log_t *log, oauth2_cfg_endpoint_auth_t *auth, const char *type, const oauth2_nv_list_t *params, const char *prefix) { char *rv = NULL; int i = 0; if (auth == NULL) { rv = oauth2_strdup("internal error: auth must be set"); goto end; } if (type == NULL) goto end; i = 0; while (_oauth2_cfg_endpoint_auth_options_set[i].type != NULL) { if (strcmp(_oauth2_cfg_endpoint_auth_options_set[i].type, type) == 0) { rv = _oauth2_cfg_endpoint_auth_options_set[i] .options_callback(log, auth, params); goto end; } i++; } rv = oauth2_strdup("Invalid value, must be one of: "); i = 0; while (_oauth2_cfg_endpoint_auth_options_set[i].type != NULL) { rv = oauth2_stradd( rv, _oauth2_cfg_endpoint_auth_options_set[i + 1].type == NULL ? " or " : i > 0 ? ", " : "", _oauth2_cfg_endpoint_auth_options_set[i].type, NULL); i++; } rv = oauth2_stradd(rv, ".", NULL, NULL); end: oauth2_debug(log, "leave: %s", rv); return rv; } oauth2_cfg_endpoint_auth_type_t oauth2_cfg_endpoint_auth_type(const oauth2_cfg_endpoint_auth_t *auth) { return auth ? auth->type : OAUTH2_ENDPOINT_AUTH_NONE; } liboauth2-2.1.0/src/cfg/cache_cfg.c000066400000000000000000000031121475305260400167620ustar00rootroot00000000000000/*************************************************************************** * * Copyright (C) 2018-2025 - ZmartZone Holding BV * * 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. * * @Author: Hans Zandbelt - hans.zandbelt@openidc.com * **************************************************************************/ #include "oauth2/mem.h" #include "cache_int.h" #include "cfg_int.h" char *oauth2_cfg_set_cache(oauth2_log_t *log, void *dummy, const char *type, const char *options) { char *rv = NULL; oauth2_nv_list_t *params = NULL; oauth2_cache_t *cache = NULL; if (oauth2_parse_form_encoded_params(log, options, ¶ms) == false) { rv = "parsing cache parameters failed"; goto end; } cache = _oauth2_cache_init(log, type, params); if (cache == NULL) { rv = oauth2_strdup( "internal error: oauth2_cache_init returned null"); goto end; } if (_oauth2_cache_post_config(log, cache) == false) { rv = oauth2_strdup( "internal error: oauth2_cache_post_config returned false"); goto end; } end: if (params) oauth2_nv_list_free(log, params); return rv; } liboauth2-2.1.0/src/cfg/cfg.c000066400000000000000000000145561475305260400156550ustar00rootroot00000000000000/*************************************************************************** * * Copyright (C) 2018-2025 - ZmartZone Holding BV * * 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. * * @Author: Hans Zandbelt - hans.zandbelt@openidc.com * **************************************************************************/ #include "oauth2/cfg.h" #include "oauth2/mem.h" #include "cfg_int.h" #include "util_int.h" #include #include #define OAUTH2_CFG_FLAG_ON "on" #define OAUTH2_CFG_FLAG_OFF "off" static char *_crypto_passphrase = NULL; const char *oauth2_crypto_passphrase_set(oauth2_log_t *log, void *dummy, const char *passphrase) { if (_crypto_passphrase != NULL) oauth2_mem_free(_crypto_passphrase); _crypto_passphrase = oauth2_strdup(passphrase); return NULL; } #define OAUTH2_CFG_DEFAULT_CRYPTO_PASSPHRASE_LEN 12 const char *oauth2_crypto_passphrase_get(oauth2_log_t *log) { char *p = NULL; if (_crypto_passphrase == NULL) { oauth2_warn(log, "no crypto passphrase configured, generating one: " "configure it statically to survive restarts"); p = oauth2_rand_str(log, OAUTH2_CFG_DEFAULT_CRYPTO_PASSPHRASE_LEN); oauth2_crypto_passphrase_set(log, NULL, p); oauth2_mem_free(p); } return _crypto_passphrase; } const char *oauth2_cfg_set_flag_slot(void *cfg, size_t offset, const char *value) { const char *rv = NULL; oauth2_flag_t *fp = NULL; if (cfg == NULL) { rv = "internal error: struct is NULL"; goto end; } if (value == NULL) goto end; fp = (oauth2_flag_t *)((char *)cfg + offset); if ((strcasecmp(value, OAUTH2_CFG_FLAG_ON) == 0) || (strcasecmp(value, "true") == 0) || (strcasecmp(value, "1") == 0)) *fp = (oauth2_flag_t) true; else if ((strcasecmp(value, OAUTH2_CFG_FLAG_OFF) == 0) || (strcasecmp(value, "false") == 0) || (strcasecmp(value, "0") == 0)) { *fp = (oauth2_flag_t) false; } else rv = "value must be \"true\", \"false\", \"1\", \"0\", " "\"" OAUTH2_CFG_FLAG_ON "\" or \"" OAUTH2_CFG_FLAG_OFF "\""; end: return rv; } static const char *_oauth2_cfg_parse_long_uint(const char *value, long int *rvalue) { const char *rv = NULL; char *endptr = NULL; long int v = 0; if ((value == NULL) || (rvalue == NULL)) { rv = "internal error: value or rvalue is NULL"; goto end; } errno = 0; v = strtol(value, &endptr, 10); if (endptr == value) rv = "strtol: no digits found"; else if ((errno == ERANGE) && (v == LONG_MIN)) rv = "strtol: underflow occurred"; else if ((errno == ERANGE) && (v == LONG_MAX)) rv = "strtol: overflow occurred"; else if (errno == EINVAL) rv = "strtol: invalid, base contains unsupported value"; else if ((errno != 0) && (v == 0)) rv = "strtol: invalid, unspecified error occurred"; else if ((errno == 0) && (*endptr != '\0')) rv = "strtol: valid, but additional characters remain"; else if (v < 0) { rv = "strtol: negative value found"; } else if ((errno == 0) && (*endptr == '\0')) { *rvalue = v; } end: return rv; } const char *oauth2_cfg_set_uint_slot(void *cfg, size_t offset, const char *value) { const char *rv = NULL; oauth2_uint_t *fp = NULL; long int v = 0; if (cfg == NULL) { rv = "internal error: struct is NULL"; goto end; } rv = _oauth2_cfg_parse_long_uint(value, &v); if (rv != NULL) goto end; fp = (oauth2_uint_t *)((char *)cfg + offset); *fp = (oauth2_uint_t)v; end: return rv; } const char *oauth2_cfg_set_time_slot(void *cfg, size_t offset, const char *value) { const char *rv = NULL; oauth2_time_t *fp = NULL; long int v = 0; if (cfg == NULL) { rv = "internal error: struct is NULL"; goto end; } rv = _oauth2_cfg_parse_long_uint(value, &v); if (rv != NULL) goto end; fp = (oauth2_time_t *)((char *)cfg + offset); *fp = (oauth2_time_t)v; end: return rv; } const char *oauth2_cfg_set_str_slot(void *cfg, size_t offset, const char *value) { const char *rv = NULL; char **fp = NULL; if ((cfg == NULL) || (value == NULL)) { rv = "internal error: struct or value is NULL"; goto end; } fp = (char **)((char *)cfg + offset); *fp = oauth2_strdup(value); if (*fp == NULL) rv = "oauth2_strdup() in oauth2_cfg_set_str_slot failed"; end: return rv; } oauth2_cfg_ctx_t *oauth2_cfg_ctx_init(oauth2_log_t *log) { oauth2_cfg_ctx_t *ctx = (oauth2_cfg_ctx_t *)oauth2_mem_alloc(sizeof(oauth2_cfg_ctx_t)); ctx->ptr = NULL; ctx->callbacks = NULL; return ctx; } void oauth2_cfg_ctx_free(oauth2_log_t *log, oauth2_cfg_ctx_t *ctx) { if (ctx == NULL) goto end; if (ctx->ptr) ctx->callbacks->free(log, ctx->ptr); oauth2_mem_free(ctx); end: return; } oauth2_cfg_ctx_t *oauth2_cfg_ctx_clone(oauth2_log_t *log, oauth2_cfg_ctx_t *src) { oauth2_cfg_ctx_t *dst = NULL; if (src == NULL) goto end; dst = oauth2_cfg_ctx_init(NULL); dst->callbacks = src->callbacks; if (dst->callbacks) dst->ptr = dst->callbacks->clone(log, src->ptr); end: return dst; } char *oauth2_cfg_set_options(oauth2_log_t *log, void *cfg, const char *type, const char *value, const char *options, const oauth2_cfg_set_options_ctx_t *set) { char *rv = NULL; int i = 0; oauth2_nv_list_t *params = NULL; if (cfg == NULL) goto end; oauth2_debug(log, "enter: type=%s, value=%s, options=%s", type, value, options); if (oauth2_parse_form_encoded_params(log, options, ¶ms) == false) goto end; i = 0; while (set[i].type != NULL) { if (strcmp(set[i].type, type) == 0) { rv = set[i].set_options_callback(log, value, params, cfg); goto end; } i++; } rv = oauth2_strdup("Invalid value, must be one of: "); i = 0; while (set[i].type != NULL) { rv = oauth2_stradd(rv, set[i + 1].type == NULL ? " or " : i > 0 ? ", " : "", set[i].type, NULL); i++; } rv = oauth2_stradd(rv, ".", NULL, NULL); end: if (params) oauth2_nv_list_free(log, params); oauth2_debug(log, "leave: %s", rv ? rv : "(null)"); return rv; } liboauth2-2.1.0/src/cfg/openidc_cfg.c000066400000000000000000000302271475305260400173470ustar00rootroot00000000000000/*************************************************************************** * * Copyright (C) 2018-2025 - ZmartZone Holding BV * * 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. * * @Author: Hans Zandbelt - hans.zandbelt@openidc.com * **************************************************************************/ #include "oauth2/cfg.h" #include "oauth2/mem.h" #include "cfg_int.h" #include "util_int.h" oauth2_cfg_openidc_t *oauth2_cfg_openidc_init(oauth2_log_t *log) { oauth2_cfg_openidc_t *c = NULL; c = oauth2_mem_alloc(sizeof(oauth2_cfg_openidc_t)); if (c == NULL) goto end; c->handler_path = NULL; c->redirect_uri = NULL; c->provider_resolver = NULL; c->unauth_action = OAUTH2_UNAUTH_ACTION_UNDEFINED; c->session = NULL; c->client = NULL; c->state_cookie_name_prefix = NULL; c->state_cookie_timeout = OAUTH2_CFG_TIME_UNSET; c->state_cookie_max = OAUTH2_CFG_UINT_UNSET; c->state_cookie_delete_oldest = OAUTH2_CFG_FLAG_UNSET; end: return c; } oauth2_cfg_openidc_t *oauth2_cfg_openidc_clone(oauth2_log_t *log, const oauth2_cfg_openidc_t *src) { oauth2_cfg_openidc_t *dst = NULL; if (src == NULL) goto end; dst = oauth2_cfg_openidc_init(log); if (dst == NULL) goto end; dst->handler_path = oauth2_strdup(src->handler_path); dst->redirect_uri = oauth2_strdup(src->redirect_uri); dst->provider_resolver = oauth2_cfg_openidc_provider_resolver_clone( log, src->provider_resolver); dst->unauth_action = src->unauth_action; dst->session = src->session; dst->client = oauth2_openidc_client_clone(log, src->client); dst->state_cookie_name_prefix = oauth2_strdup(src->state_cookie_name_prefix); dst->state_cookie_timeout = src->state_cookie_timeout; dst->state_cookie_max = src->state_cookie_max; dst->state_cookie_delete_oldest = src->state_cookie_delete_oldest; end: return dst; } #define _OAUTH_CFG_MERGE_STRING(cfg, base, add, x) \ cfg->x = oauth2_strdup(add->x ? add->x : base->x); #define _OAUTH_CFG_MERGE_VALUE(cfg, base, add, x, undefined) \ cfg->x = add->x != undefined ? add->x : base->x; void oauth2_cfg_openidc_merge(oauth2_log_t *log, oauth2_cfg_openidc_t *cfg, oauth2_cfg_openidc_t *base, oauth2_cfg_openidc_t *add) { if ((cfg == NULL) || (base == NULL) || (add == NULL)) goto end; _OAUTH_CFG_MERGE_STRING(cfg, base, add, handler_path); _OAUTH_CFG_MERGE_STRING(cfg, base, add, redirect_uri); cfg->provider_resolver = add->provider_resolver ? oauth2_cfg_openidc_provider_resolver_clone( log, add->provider_resolver) : oauth2_cfg_openidc_provider_resolver_clone( log, base->provider_resolver); _OAUTH_CFG_MERGE_VALUE(cfg, base, add, unauth_action, OAUTH2_UNAUTH_ACTION_UNDEFINED) cfg->session = add->session ? add->session : base->session; cfg->client = add->client ? oauth2_openidc_client_clone(log, add->client) : oauth2_openidc_client_clone(log, base->client); _OAUTH_CFG_MERGE_STRING(cfg, base, add, state_cookie_name_prefix); _OAUTH_CFG_MERGE_VALUE(cfg, base, add, state_cookie_timeout, OAUTH2_CFG_TIME_UNSET); _OAUTH_CFG_MERGE_VALUE(cfg, base, add, state_cookie_max, OAUTH2_CFG_UINT_UNSET); _OAUTH_CFG_MERGE_VALUE(cfg, base, add, state_cookie_delete_oldest, OAUTH2_CFG_FLAG_UNSET); end: return; } void oauth2_cfg_openidc_free(oauth2_log_t *log, oauth2_cfg_openidc_t *c) { if (c == NULL) goto end; if (c->handler_path) oauth2_mem_free(c->handler_path); if (c->redirect_uri) oauth2_mem_free(c->redirect_uri); if (c->provider_resolver) oauth2_cfg_openidc_provider_resolver_free(log, c->provider_resolver); if (c->client) oauth2_openidc_client_free(log, c->client); if (c->state_cookie_name_prefix) oauth2_mem_free(c->state_cookie_name_prefix); oauth2_mem_free(c); end: return; } _OAUTH2_TYPE_IMPLEMENT_MEMBER_SET(cfg, openidc, handler_path, char *, str) _OAUTH2_TYPE_IMPLEMENT_MEMBER_SET(cfg, openidc, redirect_uri, char *, str) _OAUTH2_TYPE_IMPLEMENT_MEMBER_SET_GET(cfg, openidc, session, oauth2_cfg_session_t *, ptr) _OAUTH2_TYPE_IMPLEMENT_MEMBER_SET(cfg, openidc, state_cookie_name_prefix, char *, str) #define OAUTH2_OPENIDC_STATE_COOKIE_NAME_PREFIX_DEFAULT "openidc_state_" char * oauth2_cfg_openidc_state_cookie_name_prefix_get(oauth2_log_t *log, const oauth2_cfg_openidc_t *cfg) { return cfg->state_cookie_name_prefix ? cfg->state_cookie_name_prefix : OAUTH2_OPENIDC_STATE_COOKIE_NAME_PREFIX_DEFAULT; } _OAUTH2_TYPE_IMPLEMENT_MEMBER_SET(cfg, openidc, state_cookie_timeout, oauth2_time_t, time) #define OAUTH2_OPENIDC_STATE_COOKIE_TIMEOUT_DEFAULT 300 oauth2_time_t oauth2_cfg_openidc_state_cookie_timeout_get(oauth2_log_t *log, const oauth2_cfg_openidc_t *cfg) { return cfg->state_cookie_timeout != OAUTH2_CFG_TIME_UNSET ? cfg->state_cookie_timeout : OAUTH2_OPENIDC_STATE_COOKIE_TIMEOUT_DEFAULT; } _OAUTH2_TYPE_IMPLEMENT_MEMBER_SET(cfg, openidc, state_cookie_max, oauth2_uint_t, uint) #define OAUTH2_OPENIDC_STATE_COOKIE_MAX_DEFAULT 6 oauth2_uint_t oauth2_cfg_openidc_state_cookie_max_get(oauth2_log_t *log, const oauth2_cfg_openidc_t *cfg) { return cfg->state_cookie_max != OAUTH2_CFG_UINT_UNSET ? cfg->state_cookie_max : OAUTH2_OPENIDC_STATE_COOKIE_MAX_DEFAULT; } #define OAUTH2_OPENIDC_STATE_COOKIE_DELETE_OLDEST_DEFAULT false oauth2_flag_t oauth2_cfg_openidc_state_cookie_delete_oldest_get( oauth2_log_t *log, const oauth2_cfg_openidc_t *cfg) { return (cfg->state_cookie_delete_oldest != OAUTH2_CFG_FLAG_UNSET) ? cfg->state_cookie_delete_oldest : OAUTH2_OPENIDC_STATE_COOKIE_DELETE_OLDEST_DEFAULT; } bool oauth2_cfg_openidc_provider_resolver_set( oauth2_log_t *log, oauth2_cfg_openidc_t *cfg, oauth2_cfg_openidc_provider_resolver_t *resolver) { cfg->provider_resolver = resolver; return true; } oauth2_cfg_openidc_provider_resolver_t * oauth2_cfg_openidc_provider_resolver_get(oauth2_log_t *log, const oauth2_cfg_openidc_t *cfg) { return cfg ? cfg->provider_resolver : NULL; } #define OAUTH2_OPENIDC_CFG_HANDLER_PATH_DEFAULT "/openid-connect" char *oauth2_cfg_openidc_handler_path_get(oauth2_log_t *log, const oauth2_cfg_openidc_t *c) { return c->handler_path ? c->handler_path : OAUTH2_OPENIDC_CFG_HANDLER_PATH_DEFAULT; } char *oauth2_cfg_openidc_redirect_uri_get(oauth2_log_t *log, const oauth2_cfg_openidc_t *c, const oauth2_http_request_t *r) { char *redirect_uri = NULL, *path = NULL; if (c == NULL) goto end; if (c->redirect_uri) { if (c->redirect_uri[0] != _OAUTH2_CHAR_FSLASH) { // absolute redirect uri redirect_uri = oauth2_strdup(c->redirect_uri); goto end; } path = oauth2_strdup(c->redirect_uri); } else { path = oauth2_stradd( NULL, oauth2_cfg_openidc_handler_path_get(log, c), "/redirect_uri", NULL); } redirect_uri = oauth2_http_request_url_base_get(log, r); if (redirect_uri == NULL) goto end; redirect_uri = oauth2_stradd(redirect_uri, path, NULL, NULL); oauth2_debug(log, "derived absolute redirect uri: %s", redirect_uri); end: if (path) oauth2_mem_free(path); return redirect_uri; } char *oauth2_cfg_openidc_redirect_uri_get_iss( oauth2_log_t *log, const oauth2_cfg_openidc_t *c, const oauth2_http_request_t *r, const oauth2_openidc_provider_t *provider) { char *redirect_uri = NULL, *issuer = NULL, *sep = NULL, *value = NULL; redirect_uri = oauth2_cfg_openidc_redirect_uri_get(log, c, r); if (redirect_uri == NULL) goto end; // if (provider->issuer_specific_redirect_uri != 0) { issuer = oauth2_openidc_provider_issuer_get(log, provider); if (issuer) value = oauth2_url_encode(log, issuer); if (value == NULL) goto end; sep = strchr(redirect_uri, _OAUTH2_CHAR_QUERY) != NULL ? _OAUTH2_STR_AMP : _OAUTH2_STR_QMARK; redirect_uri = _oauth2_stradd4(redirect_uri, sep, "iss", _OAUTH2_STR_EQUAL, value); end: if (value) oauth2_mem_free(value); return redirect_uri; } _OAUTH2_TYPE_IMPLEMENT_MEMBER_SET_GET(cfg, openidc, unauth_action, oauth2_unauth_action_t, uint) _OAUTH2_TYPE_IMPLEMENT_MEMBER_SET_GET(cfg, openidc, client, oauth2_openidc_client_t *, ptr) /* * provider resolver */ oauth2_cfg_openidc_provider_resolver_t * oauth2_cfg_openidc_provider_resolver_init(oauth2_log_t *log) { oauth2_cfg_openidc_provider_resolver_t *c = NULL; c = oauth2_mem_alloc(sizeof(oauth2_cfg_openidc_provider_resolver_t)); if (c == NULL) goto end; c->cache = NULL; c->callback = NULL; c->ctx = oauth2_cfg_ctx_init(log); end: return c; } oauth2_cfg_openidc_provider_resolver_t * oauth2_cfg_openidc_provider_resolver_clone( oauth2_log_t *log, const oauth2_cfg_openidc_provider_resolver_t *src) { oauth2_cfg_openidc_provider_resolver_t *dst = NULL; if (src == NULL) goto end; dst = oauth2_cfg_openidc_provider_resolver_init(log); if (dst == NULL) goto end; dst->cache = src->cache; dst->callback = src->callback; // TODO: sort out wrt. _init... if (dst->ctx) oauth2_cfg_ctx_free(log, dst->ctx); dst->ctx = oauth2_cfg_ctx_clone(log, src->ctx); end: return dst; } void oauth2_cfg_openidc_provider_resolver_free( oauth2_log_t *log, oauth2_cfg_openidc_provider_resolver_t *c) { if (c == NULL) goto end; if (c->ctx) oauth2_cfg_ctx_free(log, c->ctx); oauth2_mem_free(c); end: return; } char *oauth2_cfg_openidc_set_options(oauth2_log_t *log, oauth2_cfg_openidc_t *cfg, const char *options) { char *rv = NULL; oauth2_nv_list_t *params = NULL; const char *value = NULL; if (cfg == NULL) { rv = oauth2_strdup("struct is null"); goto end; } if (oauth2_parse_form_encoded_params(log, options, ¶ms) == false) goto end; value = oauth2_nv_list_get(log, params, "handler_path"); if (value) { rv = oauth2_strdup(oauth2_cfg_set_str_slot( cfg, offsetof(oauth2_cfg_openidc_t, handler_path), value)); if (rv) goto end; } value = oauth2_nv_list_get(log, params, "redirect_uri"); if (value) { rv = oauth2_strdup(oauth2_cfg_set_str_slot( cfg, offsetof(oauth2_cfg_openidc_t, redirect_uri), value)); if (rv) goto end; } value = oauth2_nv_list_get(log, params, "state.cookie.name.prefix"); if (value) { rv = oauth2_strdup(oauth2_cfg_set_str_slot( cfg, offsetof(oauth2_cfg_openidc_t, state_cookie_name_prefix), value)); if (rv) goto end; } value = oauth2_nv_list_get(log, params, "state.cookie.timeout"); if (value) { rv = oauth2_strdup(oauth2_cfg_set_time_slot( cfg, offsetof(oauth2_cfg_openidc_t, state_cookie_timeout), value)); if (rv) goto end; } value = oauth2_nv_list_get(log, params, "state.cookie.max"); if (value) { rv = oauth2_strdup(oauth2_cfg_set_uint_slot( cfg, offsetof(oauth2_cfg_openidc_t, state_cookie_max), value)); if (rv) goto end; } value = oauth2_nv_list_get(log, params, "state.cookie.delete.oldest"); if (value) { rv = oauth2_strdup(oauth2_cfg_set_flag_slot( cfg, offsetof(oauth2_cfg_openidc_t, state_cookie_delete_oldest), value)); if (rv) goto end; } value = oauth2_nv_list_get(log, params, "unauth_action"); if (value) { if (strncasecmp(value, "auth", 4) == 0) oauth2_cfg_openidc_unauth_action_set( log, cfg, OAUTH2_UNAUTH_ACTION_AUTHENTICATE); else if (strncasecmp(value, "pass", 4) == 0) oauth2_cfg_openidc_unauth_action_set( log, cfg, OAUTH2_UNAUTH_ACTION_PASS); else if (strncasecmp(value, "401", 3) == 0) oauth2_cfg_openidc_unauth_action_set( log, cfg, OAUTH2_UNAUTH_ACTION_HTTP_401); else if (strncasecmp(value, "410", 3) == 0) oauth2_cfg_openidc_unauth_action_set( log, cfg, OAUTH2_UNAUTH_ACTION_HTTP_410); else { rv = oauth2_strdup("unknown value for unauth_action"); goto end; } } end: if (params) oauth2_nv_list_free(log, params); return rv; } liboauth2-2.1.0/src/cfg/proto_cfg.c000066400000000000000000000316641475305260400170770ustar00rootroot00000000000000/*************************************************************************** * * Copyright (C) 2018-2025 - ZmartZone Holding BV * * 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. * * @Author: Hans Zandbelt - hans.zandbelt@openidc.com * **************************************************************************/ #include "oauth2/cfg.h" #include "oauth2/mem.h" #include "cfg_int.h" /* * endpoint */ oauth2_cfg_endpoint_t *oauth2_cfg_endpoint_init(oauth2_log_t *log) { oauth2_cfg_endpoint_t *endpoint = NULL; endpoint = (oauth2_cfg_endpoint_t *)oauth2_mem_alloc( sizeof(oauth2_cfg_endpoint_t)); if (endpoint == NULL) goto end; endpoint->url = NULL; endpoint->auth = NULL; endpoint->ssl_verify = OAUTH2_CFG_FLAG_UNSET; endpoint->http_timeout = OAUTH2_CFG_UINT_UNSET; endpoint->outgoing_proxy = NULL; end: return endpoint; } void oauth2_cfg_endpoint_free(oauth2_log_t *log, oauth2_cfg_endpoint_t *endpoint) { if (endpoint == NULL) goto end; if (endpoint->url) oauth2_mem_free(endpoint->url); if (endpoint->auth) oauth2_cfg_endpoint_auth_free(log, endpoint->auth); if (endpoint->outgoing_proxy) oauth2_mem_free(endpoint->outgoing_proxy); oauth2_mem_free(endpoint); end: return; } oauth2_cfg_endpoint_t * oauth2_cfg_endpoint_clone(oauth2_log_t *log, const oauth2_cfg_endpoint_t *src) { oauth2_cfg_endpoint_t *dst = NULL; if (src == NULL) goto end; dst = oauth2_cfg_endpoint_init(log); dst->url = oauth2_strdup(src->url); dst->auth = oauth2_cfg_endpoint_auth_clone(log, src->auth); dst->ssl_verify = src->ssl_verify; dst->http_timeout = src->http_timeout; dst->outgoing_proxy = oauth2_strdup(src->outgoing_proxy); end: return dst; } #define OAUTH2_CFG_ENDPOINT_SSL_VERIFY_DEFAULT 1 #define OAUTH2_CFG_ENDPOINT_HTTP_TIMEOUT_DEFAULT 20 char *oauth2_cfg_set_endpoint(oauth2_log_t *log, oauth2_cfg_endpoint_t *cfg, const char *url, const oauth2_nv_list_t *params, const char *prefix) { char *rv = NULL; const char *value = NULL; char *key = NULL; if (cfg == NULL) { rv = oauth2_strdup("struct is null"); goto end; } if (url == NULL) { key = oauth2_stradd(NULL, prefix ? prefix : NULL, prefix ? "." : NULL, "url"); value = oauth2_nv_list_get(log, params, key); if (value) { rv = oauth2_strdup(oauth2_cfg_set_str_slot( cfg, offsetof(oauth2_cfg_endpoint_t, url), value)); if (rv) goto end; } oauth2_mem_free(key); } else { cfg->url = oauth2_strdup(url); } key = oauth2_stradd(NULL, prefix ? prefix : NULL, prefix ? "." : NULL, "auth"); value = oauth2_nv_list_get(log, params, key); cfg->auth = oauth2_cfg_endpoint_auth_init(log); rv = oauth2_cfg_set_endpoint_auth(log, cfg->auth, value, params, key); if (rv != NULL) goto end; oauth2_mem_free(key); // TODO: if ssl_verify == true and url is not a https URL then fail key = oauth2_stradd(NULL, prefix ? prefix : NULL, prefix ? "." : NULL, "ssl_verify"); value = oauth2_nv_list_get(log, params, key); cfg->ssl_verify = oauth2_parse_bool(log, value, true); oauth2_mem_free(key); key = oauth2_stradd(NULL, prefix ? prefix : NULL, prefix ? "." : NULL, "http_timeout"); value = oauth2_nv_list_get(log, params, key); if (value) { rv = oauth2_strdup(oauth2_cfg_set_uint_slot( cfg, offsetof(oauth2_cfg_endpoint_t, http_timeout), value)); if (rv) goto end; } oauth2_mem_free(key); key = oauth2_stradd(NULL, prefix ? prefix : NULL, prefix ? "." : NULL, "outgoing_proxy"); value = oauth2_nv_list_get(log, params, key); if (value) { rv = oauth2_strdup(oauth2_cfg_set_str_slot( cfg, offsetof(oauth2_cfg_endpoint_t, outgoing_proxy), value)); if (rv) goto end; } oauth2_mem_free(key); key = NULL; end: if (key) oauth2_mem_free(key); oauth2_debug(log, "leave: %s", rv); return rv; } void oauth2_cfg_endpoint_set_url(oauth2_cfg_endpoint_t *cfg, const char *url) { if (cfg->url) oauth2_mem_free(cfg->url); cfg->url = oauth2_strdup(url); } const char *oauth2_cfg_endpoint_get_url(const oauth2_cfg_endpoint_t *cfg) { return cfg ? cfg->url : NULL; } const oauth2_cfg_endpoint_auth_t * oauth2_cfg_endpoint_get_auth(const oauth2_cfg_endpoint_t *cfg) { return cfg ? cfg->auth : NULL; } oauth2_flag_t oauth2_cfg_endpoint_get_ssl_verify(const oauth2_cfg_endpoint_t *cfg) { if ((cfg == NULL) || (cfg->ssl_verify == OAUTH2_CFG_FLAG_UNSET)) return OAUTH2_CFG_ENDPOINT_SSL_VERIFY_DEFAULT; return cfg->ssl_verify; } oauth2_uint_t oauth2_cfg_endpoint_get_http_timeout(const oauth2_cfg_endpoint_t *cfg) { if ((cfg == NULL) || (cfg->http_timeout == OAUTH2_CFG_UINT_UNSET)) return OAUTH2_CFG_ENDPOINT_HTTP_TIMEOUT_DEFAULT; return cfg->http_timeout; } const char * oauth2_cfg_endpoint_get_outgoing_proxy(const oauth2_cfg_endpoint_t *cfg) { return cfg ? cfg->outgoing_proxy : NULL; } /* * Resource Owner Password Credentials */ #define OAUTH2_CFG_ROPC_CLIENT_ID_DEFAULT NULL #define OAUTH2_CFG_ROPC_USERNAME_DEFAULT NULL #define OAUTH2_CFG_ROPC_PASSWORD_DEFAULT NULL typedef struct oauth2_cfg_ropc_t { oauth2_cfg_endpoint_t *token_endpoint; char *client_id; char *username; char *password; oauth2_nv_list_t *request_parameters; } oauth2_cfg_ropc_t; oauth2_cfg_ropc_t *oauth2_cfg_ropc_init(oauth2_log_t *log) { oauth2_cfg_ropc_t *ropc = NULL; ropc = (oauth2_cfg_ropc_t *)oauth2_mem_alloc(sizeof(oauth2_cfg_ropc_t)); if (ropc == NULL) goto end; ropc->token_endpoint = NULL; ropc->client_id = NULL; ropc->username = NULL; ropc->password = NULL; ropc->request_parameters = NULL; end: return ropc; } void oauth2_cfg_ropc_free(oauth2_log_t *log, oauth2_cfg_ropc_t *ropc) { if (ropc == NULL) goto end; if (ropc->token_endpoint) oauth2_cfg_endpoint_free(log, ropc->token_endpoint); if (ropc->client_id) oauth2_mem_free(ropc->client_id); if (ropc->username) oauth2_mem_free(ropc->username); if (ropc->password) oauth2_mem_free(ropc->password); if (ropc->request_parameters) oauth2_nv_list_free(log, ropc->request_parameters); oauth2_mem_free(ropc); end: return; } void oauth2_cfg_ropc_merge(oauth2_log_t *log, oauth2_cfg_ropc_t *dst, oauth2_cfg_ropc_t *base, oauth2_cfg_ropc_t *add) { oauth2_cfg_ropc_t *src = (add && add->token_endpoint != 0) ? add : base ? base : NULL; if ((src == NULL) || (dst == NULL)) goto end; dst->token_endpoint = oauth2_cfg_endpoint_clone(log, src->token_endpoint); dst->client_id = oauth2_strdup(src->client_id); dst->username = oauth2_strdup(src->username); dst->password = oauth2_strdup(src->password); dst->request_parameters = oauth2_nv_list_clone(log, src->request_parameters); end: return; } oauth2_cfg_ropc_t *oauth2_cfg_ropc_clone(oauth2_log_t *log, const oauth2_cfg_ropc_t *src) { oauth2_cfg_ropc_t *dst = NULL; if (src == NULL) goto end; dst = oauth2_cfg_ropc_init(log); dst->token_endpoint = oauth2_cfg_endpoint_clone(log, src->token_endpoint); dst->client_id = oauth2_strdup(src->client_id); dst->username = oauth2_strdup(src->username); dst->password = oauth2_strdup(src->password); dst->request_parameters = oauth2_nv_list_clone(log, src->request_parameters); end: return dst; } char *oauth2_cfg_set_ropc(oauth2_log_t *log, oauth2_cfg_ropc_t *cfg, const char *url, const char *options) { char *rv = NULL; oauth2_nv_list_t *params = NULL; const char *value = NULL; if (cfg == NULL) { rv = oauth2_strdup("struct is null"); goto end; } if (oauth2_parse_form_encoded_params(log, options, ¶ms) == false) goto end; cfg->token_endpoint = oauth2_cfg_endpoint_init(log); rv = oauth2_cfg_set_endpoint(log, cfg->token_endpoint, url, params, NULL); if (rv) goto end; value = oauth2_nv_list_get(log, params, "client_id"); if (value) { rv = oauth2_strdup(oauth2_cfg_set_str_slot( cfg, offsetof(oauth2_cfg_ropc_t, client_id), value)); if (rv) goto end; } value = oauth2_nv_list_get(log, params, "username"); if (value) { rv = oauth2_strdup(oauth2_cfg_set_str_slot( cfg, offsetof(oauth2_cfg_ropc_t, username), value)); if (rv) goto end; } value = oauth2_nv_list_get(log, params, "password"); if (value) { rv = oauth2_strdup(oauth2_cfg_set_str_slot( cfg, offsetof(oauth2_cfg_ropc_t, password), value)); if (rv) goto end; } value = oauth2_nv_list_get(log, params, "params"); if (value) { if (oauth2_parse_form_encoded_params( log, value, &cfg->request_parameters) == false) { rv = oauth2_strdup("could not parse request parameters"); goto end; } } end: if (params) oauth2_nv_list_free(log, params); oauth2_debug(log, "leave: %s", rv); return rv; } const oauth2_cfg_endpoint_t * oauth2_cfg_ropc_get_token_endpoint(oauth2_cfg_ropc_t *cfg) { return cfg ? cfg->token_endpoint : NULL; } const char *oauth2_cfg_ropc_get_client_id(oauth2_cfg_ropc_t *cfg) { if ((cfg == NULL) || (cfg->client_id == NULL)) return OAUTH2_CFG_ROPC_CLIENT_ID_DEFAULT; return cfg->client_id; } const oauth2_nv_list_t * oauth2_cfg_ropc_get_request_parameters(oauth2_cfg_ropc_t *cfg) { return cfg->request_parameters; } const char *oauth2_cfg_ropc_get_username(oauth2_cfg_ropc_t *cfg) { if ((cfg == NULL) || (cfg->username == NULL)) return OAUTH2_CFG_ROPC_USERNAME_DEFAULT; return cfg->username; } const char *oauth2_cfg_ropc_get_password(oauth2_cfg_ropc_t *cfg) { if ((cfg == NULL) || (cfg->password == NULL)) return OAUTH2_CFG_ROPC_PASSWORD_DEFAULT; return cfg->password; } /* * Client Credentials */ typedef struct oauth2_cfg_cc_t { oauth2_cfg_endpoint_t *token_endpoint; char *client_id; oauth2_nv_list_t *request_parameters; } oauth2_cfg_cc_t; oauth2_cfg_cc_t *oauth2_cfg_cc_init(oauth2_log_t *log) { oauth2_cfg_cc_t *cc = NULL; cc = (oauth2_cfg_cc_t *)oauth2_mem_alloc(sizeof(oauth2_cfg_cc_t)); if (cc == NULL) goto end; cc->token_endpoint = NULL; cc->client_id = NULL; cc->request_parameters = NULL; end: return cc; } void oauth2_cfg_cc_free(oauth2_log_t *log, oauth2_cfg_cc_t *cc) { if (cc == NULL) goto end; if (cc->token_endpoint) oauth2_cfg_endpoint_free(log, cc->token_endpoint); if (cc->client_id) oauth2_mem_free(cc->client_id); if (cc->request_parameters) oauth2_nv_list_free(log, cc->request_parameters); oauth2_mem_free(cc); end: return; } void oauth2_cfg_cc_merge(oauth2_log_t *log, oauth2_cfg_cc_t *dst, oauth2_cfg_cc_t *base, oauth2_cfg_cc_t *add) { oauth2_cfg_cc_t *src = (add && add->token_endpoint != 0) ? add : base ? base : NULL; if ((src == NULL) || (dst == NULL)) goto end; dst->token_endpoint = oauth2_cfg_endpoint_clone(log, src->token_endpoint); dst->client_id = oauth2_strdup(src->client_id); dst->request_parameters = oauth2_nv_list_clone(log, src->request_parameters); end: return; } oauth2_cfg_cc_t *oauth2_cfg_cc_clone(oauth2_log_t *log, const oauth2_cfg_cc_t *src) { oauth2_cfg_cc_t *dst = NULL; if (src == NULL) goto end; dst = oauth2_cfg_cc_init(log); dst->token_endpoint = oauth2_cfg_endpoint_clone(log, src->token_endpoint); dst->client_id = oauth2_strdup(src->client_id); dst->request_parameters = oauth2_nv_list_clone(log, src->request_parameters); end: return dst; } char *oauth2_cfg_set_cc(oauth2_log_t *log, oauth2_cfg_cc_t *cfg, const char *url, const char *options) { char *rv = NULL; oauth2_nv_list_t *params = NULL; const char *value = NULL; if (cfg == NULL) { rv = oauth2_strdup("struct is null"); goto end; } if (oauth2_parse_form_encoded_params(log, options, ¶ms) == false) goto end; cfg->token_endpoint = oauth2_cfg_endpoint_init(log); rv = oauth2_cfg_set_endpoint(log, cfg->token_endpoint, url, params, NULL); if (rv) goto end; value = oauth2_nv_list_get(log, params, "client_id"); if (value) { rv = oauth2_strdup(oauth2_cfg_set_str_slot( cfg, offsetof(oauth2_cfg_cc_t, client_id), value)); if (rv) goto end; } value = oauth2_nv_list_get(log, params, "params"); if (value) { if (oauth2_parse_form_encoded_params( log, value, &cfg->request_parameters) == false) { rv = oauth2_strdup("could not parse request parameters"); goto end; } } end: if (params) oauth2_nv_list_free(log, params); oauth2_debug(log, "leave: %s", rv); return rv; } const oauth2_cfg_endpoint_t * oauth2_cfg_cc_get_token_endpoint(oauth2_cfg_cc_t *cfg) { return cfg ? cfg->token_endpoint : NULL; } const char *oauth2_cfg_cc_get_client_id(oauth2_cfg_cc_t *cfg) { if ((cfg == NULL) || (cfg->client_id == NULL)) return NULL; return cfg->client_id; } const oauth2_nv_list_t * oauth2_cfg_cc_get_request_parameters(oauth2_cfg_cc_t *cfg) { return cfg->request_parameters; } liboauth2-2.1.0/src/cfg/session_cfg.c000066400000000000000000000165611475305260400174160ustar00rootroot00000000000000/*************************************************************************** * * Copyright (C) 2018-2025 - ZmartZone Holding BV * * 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. * * @Author: Hans Zandbelt - hans.zandbelt@openidc.com * **************************************************************************/ #include "oauth2/ipc.h" #include "oauth2/mem.h" #include "cache_int.h" #include "cfg_int.h" #include "util_int.h" _OAUTH2_CFG_GLOBAL_LIST(session, oauth2_cfg_session_t) #define OAUTH2_SESSION_TYPE_COOKIE_STR "cookie" #define OAUTH2_SESSION_TYPE_CACHE_STR "cache" oauth2_cfg_session_t *oauth2_cfg_session_init(oauth2_log_t *log) { oauth2_cfg_session_t *session = NULL; session = (oauth2_cfg_session_t *)oauth2_mem_alloc( sizeof(oauth2_cfg_session_t)); session->type = OAUTH2_CFG_UINT_UNSET; session->cookie_name = NULL; session->cookie_path = NULL; session->inactivity_timeout_s = OAUTH2_CFG_TIME_UNSET; session->max_duration_s = OAUTH2_CFG_TIME_UNSET; session->cache = NULL; session->load_callback = NULL; session->save_callback = NULL; return session; } /* oauth2_cfg_session_t *oauth2_cfg_session_clone(oauth2_log_t *log, oauth2_cfg_session_t *src) { oauth2_cfg_session_t *dst = NULL; if (src == NULL) goto end; dst = oauth2_cfg_session_init(log); dst->type = src->type; dst->cookie_name = oauth2_strdup(src->cookie_name); dst->cookie_path = oauth2_strdup(src->cookie_path); dst->inactivity_timeout_s = src->inactivity_timeout_s; dst->max_duration_s = src->max_duration_s; dst->cache = src->cache; dst->load_callback = src->load_callback; dst->save_callback = src->save_callback; end: return dst; } */ // void oauth2_cfg_session_merge( // oauth2_log_t *log, oauth2_cfg_session_t *cfg, // oauth2_cfg_session_t *add, // oauth2_cfg_session_t *base) { //} void oauth2_cfg_session_free(oauth2_log_t *log, oauth2_cfg_session_t *session) { if (session->cookie_name) oauth2_mem_free(session->cookie_name); if (session->cookie_path) oauth2_mem_free(session->cookie_path); oauth2_mem_free(session); } oauth2_cfg_session_t *_oauth2_cfg_session_obtain(oauth2_log_t *log, const char *name) { oauth2_cfg_session_t *cfg = NULL; oauth2_debug(log, "enter: %s", name); if (_M_session_list_empty(log)) { cfg = oauth2_cfg_session_init(log); if (cfg == NULL) goto end; if (oauth2_cfg_session_set_options( log, cfg, OAUTH2_SESSION_TYPE_CACHE_STR, NULL) != NULL) { cfg = NULL; goto end; } } cfg = _M_session_list_get(log, name); end: oauth2_debug(log, "leave: %p", cfg); return cfg; } void _oauth2_session_global_cleanup(oauth2_log_t *log) { oauth2_debug(log, "enter"); _M_session_list_release(log); oauth2_debug(log, "leave"); } #define OAUTH2_INACTIVITY_TIMEOUT_S_DEFAULT 60 * 5 oauth2_time_t oauth2_cfg_session_inactivity_timeout_s_get(oauth2_log_t *log, const oauth2_cfg_session_t *cfg) { if ((cfg == NULL) || (cfg->inactivity_timeout_s == OAUTH2_CFG_TIME_UNSET)) return OAUTH2_INACTIVITY_TIMEOUT_S_DEFAULT; return cfg->inactivity_timeout_s; } #define OAUTH2_SESSION_MAX_DURATION_S_DEFAULT 60 * 60 * 8 oauth2_time_t oauth2_cfg_session_max_duration_s_get(oauth2_log_t *log, const oauth2_cfg_session_t *cfg) { if ((cfg == NULL) || (cfg->max_duration_s == OAUTH2_CFG_TIME_UNSET)) return OAUTH2_SESSION_MAX_DURATION_S_DEFAULT; return cfg->max_duration_s; } #define OAUTH2_SESSION_COOKIE_NAME_DEFAULT "openidc_session" char *oauth2_cfg_session_cookie_name_get(oauth2_log_t *log, const oauth2_cfg_session_t *cfg) { if ((cfg == NULL) || (cfg->cookie_name == NULL)) return OAUTH2_SESSION_COOKIE_NAME_DEFAULT; return cfg->cookie_name; } #define OAUTH2_SESSION_COOKIE_PATH_DEFAULT "/" char *oauth2_cfg_session_cookie_path_get(oauth2_log_t *log, const oauth2_cfg_session_t *cfg) { if ((cfg == NULL) || (cfg->cookie_path == NULL)) return OAUTH2_SESSION_COOKIE_PATH_DEFAULT; return cfg->cookie_path; } oauth2_session_load_callback_t * oauth2_cfg_session_load_callback_get(oauth2_log_t *log, const oauth2_cfg_session_t *cfg) { if ((cfg == NULL) || (cfg->load_callback == NULL)) return oauth2_session_load_cookie; return cfg->load_callback; } oauth2_session_save_callback_t * oauth2_cfg_session_save_callback_get(oauth2_log_t *log, const oauth2_cfg_session_t *cfg) { if ((cfg == NULL) || (cfg->save_callback == NULL)) return oauth2_session_save_cookie; return cfg->save_callback; } oauth2_cache_t *oauth2_cfg_session_cache_get(oauth2_log_t *log, const oauth2_cfg_session_t *cfg) { return cfg->cache; } _OAUTH_CFG_CTX_CALLBACK(oauth2_cfg_session_set_options_cookie) { oauth2_cfg_session_t *cfg = (oauth2_cfg_session_t *)ctx; char *rv = NULL; oauth2_debug(log, "enter"); cfg->type = OAUTH2_SESSION_TYPE_COOKIE; cfg->load_callback = oauth2_session_load_cookie; cfg->save_callback = oauth2_session_save_cookie; oauth2_debug(log, "leave: %s", rv); return rv; } #define OAUTH2_CFG_SESSION_CACHE_DEFAULT OAUTH2_SESSION_EXPIRY_S_DEFAULT _OAUTH_CFG_CTX_CALLBACK(oauth2_cfg_session_set_options_cache) { oauth2_cfg_session_t *cfg = (oauth2_cfg_session_t *)ctx; char *rv = NULL; oauth2_debug(log, "enter"); cfg->type = OAUTH2_SESSION_TYPE_CACHE; cfg->load_callback = oauth2_session_load_cache; cfg->save_callback = oauth2_session_save_cache; cfg->cache = oauth2_cache_obtain(log, oauth2_nv_list_get(log, params, "cache")); oauth2_debug(log, "leave: %s", rv); return rv; } // clang-format off static oauth2_cfg_set_options_ctx_t _oauth2_cfg_session_options_set[] = { { OAUTH2_SESSION_TYPE_COOKIE_STR, oauth2_cfg_session_set_options_cookie }, { OAUTH2_SESSION_TYPE_CACHE_STR, oauth2_cfg_session_set_options_cache }, { NULL, NULL } }; // clang-format on char *oauth2_cfg_session_set_options(oauth2_log_t *log, oauth2_cfg_session_t *cfg, const char *type, const char *options) { char *rv = NULL; oauth2_nv_list_t *params = NULL; const char *value = NULL; if (cfg == NULL) // global cfg = oauth2_cfg_session_init(log); rv = oauth2_cfg_set_options(log, cfg, type, NULL, options, _oauth2_cfg_session_options_set); if (rv != NULL) goto end; if (oauth2_parse_form_encoded_params(log, options, ¶ms) == false) goto end; value = oauth2_nv_list_get(log, params, "cookie.name"); if (value) cfg->cookie_name = oauth2_strdup(value); value = oauth2_nv_list_get(log, params, "cookie.path"); if (value) cfg->cookie_path = oauth2_strdup(value); value = oauth2_nv_list_get(log, params, "max_duration"); if (value) cfg->max_duration_s = oauth2_parse_time_sec(log, value, OAUTH2_CFG_TIME_UNSET); value = oauth2_nv_list_get(log, params, "inactivity_timeout"); if (value) cfg->inactivity_timeout_s = oauth2_parse_time_sec(log, value, OAUTH2_CFG_TIME_UNSET); _M_session_list_register(log, oauth2_nv_list_get(log, params, "name"), cfg, oauth2_cfg_session_free); end: if (params) oauth2_nv_list_free(log, params); return rv; } liboauth2-2.1.0/src/cfg/source.c000066400000000000000000000230531475305260400164060ustar00rootroot00000000000000/*************************************************************************** * * Copyright (C) 2018-2025 - ZmartZone Holding BV * * 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. * * @Author: Hans Zandbelt - hans.zandbelt@openidc.com * **************************************************************************/ #include "oauth2/cfg.h" #include "oauth2/mem.h" #include "cfg_int.h" #define OAUTH2_CFG_SOURCE_TOKEN_STRIP_DEFAULT (oauth2_flag_t) true #define OAUTH2_CFG_SOURCE_TOKEN_ACCEPT_IN_DEFAULT \ (OAUTH2_CFG_TOKEN_IN_ENVVAR | OAUTH2_CFG_TOKEN_IN_HEADER) static char * oauth2_cfg_accept_in_envvar_options_set(oauth2_log_t *log, oauth2_cfg_token_in_t *accept_in, const oauth2_nv_list_t *params) { char *rv = NULL; accept_in->envvar.name = oauth2_strdup(oauth2_nv_list_get(log, params, "name")); return rv; } static char * oauth2_cfg_accept_in_header_options_set(oauth2_log_t *log, oauth2_cfg_token_in_t *accept_in, const oauth2_nv_list_t *params) { char *rv = NULL; accept_in->header.name = oauth2_strdup(oauth2_nv_list_get(log, params, "name")); accept_in->header.type = oauth2_strdup(oauth2_nv_list_get(log, params, "type")); return rv; } static char * oauth2_cfg_accept_in_query_options_set(oauth2_log_t *log, oauth2_cfg_token_in_t *accept_in, const oauth2_nv_list_t *params) { char *rv = NULL; accept_in->query.param_name = oauth2_strdup(oauth2_nv_list_get(log, params, "name")); return rv; } static char * oauth2_cfg_accept_in_post_options_set(oauth2_log_t *log, oauth2_cfg_token_in_t *accept_in, const oauth2_nv_list_t *params) { char *rv = NULL; accept_in->post.param_name = oauth2_strdup(oauth2_nv_list_get(log, params, "name")); return rv; } static char * oauth2_cfg_accept_in_cookie_options_set(oauth2_log_t *log, oauth2_cfg_token_in_t *accept_in, const oauth2_nv_list_t *params) { char *rv = NULL; accept_in->cookie.name = oauth2_strdup(oauth2_nv_list_get(log, params, "name")); return rv; } typedef char *( oauth2_cfg_accept_token_in_set_options_cb_t)(oauth2_log_t *log, oauth2_cfg_token_in_t *accept_in, const oauth2_nv_list_t *params); typedef struct oauth2_cfg_accept_token_in_set_options_ctx_t { const char *method; oauth2_cfg_token_in_type_t type; oauth2_cfg_accept_token_in_set_options_cb_t *options_callback; } oauth2_cfg_accept_token_in_set_options_ctx_t; // clang-format off static oauth2_cfg_accept_token_in_set_options_ctx_t _oauth2_cfg_accept_in_options_set[] = { { OAUTH2_CFG_TOKEN_IN_ENVVAR_STR, OAUTH2_CFG_TOKEN_IN_ENVVAR, oauth2_cfg_accept_in_envvar_options_set }, { OAUTH2_CFG_TOKEN_IN_HEADER_STR, OAUTH2_CFG_TOKEN_IN_HEADER, oauth2_cfg_accept_in_header_options_set }, { OAUTH2_CFG_TOKEN_IN_QUERY_STR, OAUTH2_CFG_TOKEN_IN_QUERY, oauth2_cfg_accept_in_query_options_set }, { OAUTH2_CFG_TOKEN_IN_POST_STR, OAUTH2_CFG_TOKEN_IN_POST, oauth2_cfg_accept_in_post_options_set }, { OAUTH2_CFG_TOKEN_IN_COOKIE_STR, OAUTH2_CFG_TOKEN_IN_COOKIE, oauth2_cfg_accept_in_cookie_options_set }, { OAUTH2_CFG_TOKEN_IN_BASIC_STR, OAUTH2_CFG_TOKEN_IN_BASIC, NULL }, { NULL, 0, NULL } }; // clang-format on char *oauth2_cfg_token_in_set(oauth2_log_t *log, oauth2_cfg_token_in_t *cfg, const char *method, const oauth2_nv_list_t *params, oauth2_uint_t allowed) { char *rv = NULL; int i = 0; if (method == NULL) { rv = oauth2_strdup("Invalid value, method must be set"); goto end; } i = 0; while (_oauth2_cfg_accept_in_options_set[i].method != NULL) { if ((strcmp(_oauth2_cfg_accept_in_options_set[i].method, method) == 0) && (allowed & _oauth2_cfg_accept_in_options_set[i].type)) { cfg->value |= _oauth2_cfg_accept_in_options_set[i].type; if (_oauth2_cfg_accept_in_options_set[i] .options_callback) rv = _oauth2_cfg_accept_in_options_set[i] .options_callback(log, cfg, params); goto end; } i++; } rv = oauth2_strdup("Invalid value, must be one of: "); i = 0; while (_oauth2_cfg_accept_in_options_set[i].method != NULL) { rv = oauth2_stradd( rv, _oauth2_cfg_accept_in_options_set[i + 1].method == NULL ? " or " : i > 0 ? ", " : "", _oauth2_cfg_accept_in_options_set[i].method, NULL); i++; } rv = oauth2_stradd(rv, ".", NULL, NULL); end: oauth2_debug(log, "leave: %s", rv); return rv; } #define OAUTH2_CFG_SET_TAKE1_IMPL(ctype, name, member, type) \ const char *oauth2_cfg_set_##name##_##member( \ oauth2_log_t *log, ctype *cfg, const char *value) \ { \ return oauth2_cfg_set_##type##_slot( \ cfg, offsetof(ctype, member), value); \ } OAUTH2_CFG_SET_TAKE1_IMPL(oauth2_cfg_source_token_t, source_token, strip, flag) oauth2_flag_t oauth2_cfg_source_token_get_strip(oauth2_cfg_source_token_t *cfg) { if (cfg->strip == OAUTH2_CFG_FLAG_UNSET) return OAUTH2_CFG_SOURCE_TOKEN_STRIP_DEFAULT; return cfg->strip; } char *oauth2_cfg_source_token_set_accept_in(oauth2_log_t *log, oauth2_cfg_source_token_t *cfg, const char *method, const char *options) { char *rv = NULL; oauth2_nv_list_t *params = NULL; static oauth2_uint_t allowed = OAUTH2_CFG_TOKEN_IN_ENVVAR | OAUTH2_CFG_TOKEN_IN_HEADER | OAUTH2_CFG_TOKEN_IN_QUERY | OAUTH2_CFG_TOKEN_IN_POST | OAUTH2_CFG_TOKEN_IN_COOKIE | OAUTH2_CFG_TOKEN_IN_BASIC; const char *strip = NULL; if (cfg == NULL) { rv = oauth2_strdup("struct is null"); goto end; } if (oauth2_parse_form_encoded_params(log, options, ¶ms) == false) goto end; rv = oauth2_cfg_token_in_set(log, &cfg->accept_in, method, params, allowed); if (rv != NULL) goto end; strip = oauth2_nv_list_get(log, params, "strip"); if (strip == NULL) goto end; rv = oauth2_strdup(oauth2_cfg_set_source_token_strip(log, cfg, strip)); end: if (params) oauth2_nv_list_free(log, params); oauth2_debug(log, "leave: %s", rv); return rv; } char oauth2_cfg_source_token_get_accept_in(oauth2_cfg_source_token_t *cfg) { if (cfg->accept_in.value == 0) return OAUTH2_CFG_SOURCE_TOKEN_ACCEPT_IN_DEFAULT; return cfg->accept_in.value; } oauth2_cfg_source_token_t *oauth2_cfg_source_token_init(oauth2_log_t *log) { oauth2_cfg_source_token_t *cfg = (oauth2_cfg_source_token_t *)oauth2_mem_alloc( sizeof(oauth2_cfg_source_token_t)); cfg->accept_in.value = 0; cfg->accept_in.query.param_name = NULL; cfg->accept_in.post.param_name = NULL; cfg->accept_in.cookie.name = NULL; cfg->accept_in.envvar.name = NULL; cfg->accept_in.header.name = NULL; cfg->accept_in.header.type = NULL; cfg->strip = OAUTH2_CFG_FLAG_UNSET; // cfg->encryption_keys = NULL; return cfg; } void oauth2_cfg_source_token_free(oauth2_log_t *log, oauth2_cfg_source_token_t *cfg) { if (cfg == NULL) goto end; if (cfg->accept_in.query.param_name) oauth2_mem_free(cfg->accept_in.query.param_name); if (cfg->accept_in.post.param_name) oauth2_mem_free(cfg->accept_in.post.param_name); if (cfg->accept_in.cookie.name) oauth2_mem_free(cfg->accept_in.cookie.name); if (cfg->accept_in.envvar.name) oauth2_mem_free(cfg->accept_in.envvar.name); if (cfg->accept_in.header.name) oauth2_mem_free(cfg->accept_in.header.name); if (cfg->accept_in.header.type) oauth2_mem_free(cfg->accept_in.header.type); oauth2_mem_free(cfg); end: return; } oauth2_cfg_source_token_t * oauth2_cfg_source_token_clone(oauth2_log_t *log, const oauth2_cfg_source_token_t *src) { oauth2_cfg_source_token_t *dst = NULL; if (src == NULL) goto end; dst = oauth2_cfg_source_token_init(log); dst->accept_in.value = src->accept_in.value; dst->accept_in.query.param_name = oauth2_strdup(src->accept_in.query.param_name); dst->accept_in.post.param_name = oauth2_strdup(src->accept_in.post.param_name); dst->accept_in.cookie.name = oauth2_strdup(src->accept_in.cookie.name); dst->accept_in.envvar.name = oauth2_strdup(src->accept_in.envvar.name); dst->accept_in.header.name = oauth2_strdup(src->accept_in.header.name); dst->accept_in.header.type = oauth2_strdup(src->accept_in.header.type); dst->strip = src->strip; end: return dst; } void oauth2_cfg_source_token_merge(oauth2_log_t *log, oauth2_cfg_source_token_t *dst, oauth2_cfg_source_token_t *base, oauth2_cfg_source_token_t *add) { oauth2_cfg_source_token_t *src = (add && add->accept_in.value != 0) ? add : base ? base : NULL; if (src == NULL) goto end; dst->accept_in.value = src->accept_in.value; dst->accept_in.query.param_name = oauth2_strdup(src->accept_in.query.param_name); dst->accept_in.post.param_name = oauth2_strdup(src->accept_in.post.param_name); dst->accept_in.cookie.name = oauth2_strdup(src->accept_in.cookie.name); dst->accept_in.envvar.name = oauth2_strdup(src->accept_in.envvar.name); dst->accept_in.header.name = oauth2_strdup(src->accept_in.header.name); dst->accept_in.header.type = oauth2_strdup(src->accept_in.header.type); dst->strip = src->strip; end: return; } liboauth2-2.1.0/src/cfg/target.c000066400000000000000000000143151475305260400163750ustar00rootroot00000000000000/*************************************************************************** * * Copyright (C) 2018-2025 - ZmartZone Holding BV * * 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. * * @Author: Hans Zandbelt - hans.zandbelt@openidc.com * **************************************************************************/ #include "oauth2/cfg.h" #include "oauth2/mem.h" #define OAUTH2_CFG_PASS_TARGET_AS_ENVVARS_DEFAULT true #define OAUTH2_CFG_PASS_TARGET_AS_HEADERS_DEFAULT true #define OAUTH2_CFG_PASS_TARGET_PREFIX_DEFAULT "OAUTH2_CLAIM_" #define OAUTH2_CFG_PASS_TARGET_AUTHN_HEADER_DEFAULT NULL #define OAUTH2_CFG_PASS_TARGET_REMOTE_USER_CLAIM_DEFAULT "sub" #define OAUTH2_CFG_PASS_TARGET_JSON_PAYLOAD_CLAIM_DEFAULT NULL typedef struct oauth2_cfg_target_pass_t { oauth2_flag_t as_envvars; oauth2_flag_t as_headers; char *authn_header; char *prefix; char *remote_user_claim; char *json_payload_claim; } oauth2_cfg_target_pass_t; oauth2_cfg_target_pass_t *oauth2_cfg_target_pass_init(oauth2_log_t *log) { oauth2_cfg_target_pass_t *pass = NULL; pass = (oauth2_cfg_target_pass_t *)oauth2_mem_alloc( sizeof(oauth2_cfg_target_pass_t)); if (pass == NULL) goto end; pass->as_envvars = OAUTH2_CFG_FLAG_UNSET; pass->as_headers = OAUTH2_CFG_FLAG_UNSET; pass->authn_header = NULL; pass->prefix = NULL; pass->remote_user_claim = NULL; pass->json_payload_claim = NULL; end: return pass; } void oauth2_cfg_target_pass_free(oauth2_log_t *log, oauth2_cfg_target_pass_t *pass) { if (pass == NULL) goto end; if (pass->authn_header) oauth2_mem_free(pass->authn_header); if (pass->prefix) oauth2_mem_free(pass->prefix); if (pass->remote_user_claim) oauth2_mem_free(pass->remote_user_claim); if (pass->json_payload_claim) oauth2_mem_free(pass->json_payload_claim); oauth2_mem_free(pass); end: return; } void oauth2_cfg_target_pass_merge(oauth2_log_t *log, oauth2_cfg_target_pass_t *cfg, oauth2_cfg_target_pass_t *base, oauth2_cfg_target_pass_t *add) { if ((cfg == NULL) || (base == NULL) || (add == NULL)) goto end; cfg->as_envvars = add->as_envvars != OAUTH2_CFG_FLAG_UNSET ? add->as_envvars : base->as_envvars; cfg->as_headers = add->as_headers != OAUTH2_CFG_FLAG_UNSET ? add->as_headers : base->as_headers; cfg->authn_header = oauth2_strdup( add->authn_header != NULL ? add->authn_header : base->authn_header); cfg->prefix = oauth2_strdup(add->prefix != NULL ? add->prefix : base->prefix); cfg->remote_user_claim = oauth2_strdup(add->remote_user_claim != NULL ? add->remote_user_claim : base->remote_user_claim); cfg->json_payload_claim = oauth2_strdup(add->json_payload_claim != NULL ? add->json_payload_claim : base->json_payload_claim); end: return; } char *oauth2_cfg_set_target_pass_options(oauth2_log_t *log, oauth2_cfg_target_pass_t *cfg, const char *options) { char *rv = NULL; oauth2_nv_list_t *params = NULL; const char *value = NULL; if (cfg == NULL) { rv = oauth2_strdup("struct is null"); goto end; } if (oauth2_parse_form_encoded_params(log, options, ¶ms) == false) goto end; value = oauth2_nv_list_get(log, params, "envvars"); if (value) { rv = oauth2_strdup(oauth2_cfg_set_flag_slot( cfg, offsetof(oauth2_cfg_target_pass_t, as_envvars), value)); if (rv) goto end; } value = oauth2_nv_list_get(log, params, "headers"); if (value) { rv = oauth2_strdup(oauth2_cfg_set_flag_slot( cfg, offsetof(oauth2_cfg_target_pass_t, as_headers), value)); if (rv) goto end; } value = oauth2_nv_list_get(log, params, "authn_header"); if (value) { rv = oauth2_strdup(oauth2_cfg_set_str_slot( cfg, offsetof(oauth2_cfg_target_pass_t, authn_header), value)); if (rv) goto end; } value = oauth2_nv_list_get(log, params, "prefix"); if (value) { rv = oauth2_strdup(oauth2_cfg_set_str_slot( cfg, offsetof(oauth2_cfg_target_pass_t, prefix), value)); if (rv) goto end; } value = oauth2_nv_list_get(log, params, "remote_user_claim"); if (value) { rv = oauth2_strdup(oauth2_cfg_set_str_slot( cfg, offsetof(oauth2_cfg_target_pass_t, remote_user_claim), value)); if (rv) goto end; } value = oauth2_nv_list_get(log, params, "json_payload_claim"); if (value) { rv = oauth2_strdup(oauth2_cfg_set_str_slot( cfg, offsetof(oauth2_cfg_target_pass_t, json_payload_claim), value)); if (rv) goto end; } end: if (params) oauth2_nv_list_free(log, params); oauth2_debug(log, "leave: %s", rv); return rv; } oauth2_flag_t oauth2_cfg_target_pass_get_as_envvars(oauth2_cfg_target_pass_t *cfg) { if (cfg->as_headers == OAUTH2_CFG_FLAG_UNSET) return OAUTH2_CFG_PASS_TARGET_AS_ENVVARS_DEFAULT; return cfg->as_envvars; } oauth2_flag_t oauth2_cfg_target_pass_get_as_headers(oauth2_cfg_target_pass_t *cfg) { if (cfg->as_headers == OAUTH2_CFG_FLAG_UNSET) return OAUTH2_CFG_PASS_TARGET_AS_HEADERS_DEFAULT; return cfg->as_headers; } const char *oauth2_cfg_target_pass_get_prefix(oauth2_cfg_target_pass_t *cfg) { if (cfg->prefix == NULL) return OAUTH2_CFG_PASS_TARGET_PREFIX_DEFAULT; return cfg->prefix; } const char * oauth2_cfg_target_pass_get_authn_header(oauth2_cfg_target_pass_t *cfg) { if (cfg->authn_header == NULL) return OAUTH2_CFG_PASS_TARGET_AUTHN_HEADER_DEFAULT; return cfg->authn_header; } const char * oauth2_cfg_target_get_remote_user_claim(oauth2_cfg_target_pass_t *cfg) { if (cfg->remote_user_claim == NULL) return OAUTH2_CFG_PASS_TARGET_REMOTE_USER_CLAIM_DEFAULT; return cfg->remote_user_claim; } const char * oauth2_cfg_target_get_json_payload_claim(oauth2_cfg_target_pass_t *cfg) { if (cfg->json_payload_claim == NULL) return OAUTH2_CFG_PASS_TARGET_JSON_PAYLOAD_CLAIM_DEFAULT; return cfg->json_payload_claim; } liboauth2-2.1.0/src/cfg/verify.c000066400000000000000000000216601475305260400164140ustar00rootroot00000000000000/*************************************************************************** * * Copyright (C) 2018-2025 - ZmartZone Holding BV * * 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. * * @Author: Hans Zandbelt - hans.zandbelt@openidc.com * **************************************************************************/ #include "oauth2/cfg.h" #include "oauth2/jose.h" #include "oauth2/mem.h" #include "cache_int.h" #include "cfg_int.h" #include "jose_int.h" #include "oauth2_int.h" #define OAUTH2_JOSE_VERIFY_JWK_PLAIN_STR "plain" #define OAUTH2_JOSE_VERIFY_JWK_BASE64_STR "base64" #define OAUTH2_JOSE_VERIFY_JWK_BASE64URL_STR "base64url" #define OAUTH2_JOSE_VERIFY_JWK_HEX_STR "hex" #define OAUTH2_JOSE_VERIFY_JWK_PEM_STR "pem" #define OAUTH2_JOSE_VERIFY_JWK_PUBKEY_STR "pubkey" #define OAUTH2_JOSE_VERIFY_JWK_JWK_STR "jwk" #define OAUTH2_JOSE_VERIFY_JWK_JWKS_URI_STR "jwks_uri" #define OAUTH2_JOSE_VERIFY_JWK_ECKEY_URI_STR "eckey_uri" #define OAUTH2_JOSE_VERIFY_JWK_AWS_ALB_STR "aws_alb" #define OAUTH2_CFG_VERIFY_INTROSPECT_URL_STR "introspect" #define OAUTH2_CFG_VERIFY_METADATA_URL_STR "metadata" // clang-format off static oauth2_cfg_set_options_ctx_t _oauth2_cfg_verify_options_set[] = { { OAUTH2_JOSE_VERIFY_JWK_PLAIN_STR, oauth2_jose_verify_options_jwk_set_plain }, { OAUTH2_JOSE_VERIFY_JWK_BASE64_STR, oauth2_jose_verify_options_jwk_set_base64 }, { OAUTH2_JOSE_VERIFY_JWK_BASE64URL_STR, oauth2_jose_verify_options_jwk_set_base64url }, { OAUTH2_JOSE_VERIFY_JWK_HEX_STR, oauth2_jose_verify_options_jwk_set_hex }, { OAUTH2_JOSE_VERIFY_JWK_PEM_STR, oauth2_jose_verify_options_jwk_set_pem }, { OAUTH2_JOSE_VERIFY_JWK_PUBKEY_STR, oauth2_jose_verify_options_jwk_set_pubkey }, { OAUTH2_JOSE_VERIFY_JWK_JWK_STR, oauth2_jose_verify_options_jwk_set_jwk }, { OAUTH2_JOSE_VERIFY_JWK_JWKS_URI_STR, oauth2_jose_verify_options_jwk_set_jwks_uri }, { OAUTH2_JOSE_VERIFY_JWK_ECKEY_URI_STR, oauth2_jose_verify_options_jwk_set_eckey_uri }, { OAUTH2_JOSE_VERIFY_JWK_AWS_ALB_STR, oauth2_jose_verify_options_jwk_set_aws_alb }, { OAUTH2_CFG_VERIFY_INTROSPECT_URL_STR, oauth2_verify_options_set_introspect_url }, { OAUTH2_CFG_VERIFY_METADATA_URL_STR, oauth2_verify_options_set_metadata_url }, { NULL, NULL } }; // clang-format on oauth2_cfg_token_verify_t *oauth2_cfg_token_verify_init(oauth2_log_t *log) { oauth2_cfg_token_verify_t *verify = (oauth2_cfg_token_verify_t *)oauth2_mem_alloc( sizeof(oauth2_cfg_token_verify_t)); verify->ctx = NULL; verify->callback = NULL; verify->cache = NULL; verify->type = OAUTH2_CFG_UINT_UNSET; verify->dpop.cache = NULL; verify->dpop.expiry_s = OAUTH2_CFG_UINT_UNSET; verify->dpop.iat_validate = OAUTH2_CFG_UINT_UNSET; verify->dpop.iat_slack_after = OAUTH2_CFG_UINT_UNSET; verify->dpop.iat_slack_before = OAUTH2_CFG_UINT_UNSET; verify->expiry_s = OAUTH2_CFG_UINT_UNSET; verify->mtls.env_var_name = NULL; verify->mtls.policy = OAUTH2_CFG_UINT_UNSET; verify->next = NULL; return verify; } void oauth2_cfg_token_verify_free(oauth2_log_t *log, oauth2_cfg_token_verify_t *verify) { oauth2_cfg_token_verify_t *ptr = verify; while (ptr) { verify = verify->next; if (ptr->mtls.env_var_name != NULL) oauth2_mem_free(ptr->mtls.env_var_name); if (ptr->ctx) oauth2_cfg_ctx_free(log, ptr->ctx); oauth2_mem_free(ptr); ptr = verify; } } oauth2_cfg_token_verify_t * oauth2_cfg_token_verify_clone(oauth2_log_t *log, const oauth2_cfg_token_verify_t *src) { oauth2_cfg_token_verify_t *dst = NULL; if (src == NULL) goto end; dst = oauth2_cfg_token_verify_init(NULL); dst->cache = src->cache; dst->expiry_s = src->expiry_s; dst->callback = src->callback; dst->type = src->type; dst->dpop.cache = src->dpop.cache; dst->dpop.expiry_s = src->dpop.expiry_s; dst->dpop.iat_slack_after = src->dpop.iat_slack_after; dst->dpop.iat_slack_before = src->dpop.iat_slack_before; dst->dpop.iat_validate = src->dpop.iat_validate; dst->mtls.env_var_name = oauth2_strdup(src->mtls.env_var_name); dst->mtls.policy = src->mtls.policy; dst->ctx = oauth2_cfg_ctx_clone(log, src->ctx); dst->next = oauth2_cfg_token_verify_clone(NULL, src->next); end: return dst; } static oauth2_cfg_token_verify_t * _oauth2_cfg_token_verify_add(oauth2_log_t *log, oauth2_cfg_token_verify_t **verify) { oauth2_cfg_token_verify_t *v = NULL, *last = NULL; if (verify == NULL) goto end; v = oauth2_cfg_token_verify_init(log); if (v == NULL) goto end; v->cache = NULL; v->callback = NULL; v->ctx = oauth2_cfg_ctx_init(log); if (v->ctx == NULL) goto end; if (*verify == NULL) { *verify = v; goto end; } for (last = *verify; last->next; last = last->next) ; last->next = v; end: return v; } static char * _oauth2_cfg_token_verify_type_set(oauth2_log_t *log, oauth2_cfg_token_verify_t *verify, oauth2_nv_list_t *params) { char *rv = NULL; const char *v = NULL; v = oauth2_nv_list_get(log, params, "type"); if (v == NULL) goto end; if (strcasecmp(v, OAUTH2_TOKEN_VERIFY_BEARER_STR) == 0) { verify->type = OAUTH2_TOKEN_VERIFY_BEARER; goto end; } if (strcasecmp(v, OAUTH2_TOKEN_VERIFY_DPOP_STR) == 0) { verify->type = OAUTH2_TOKEN_VERIFY_DPOP; goto end; } if (strcasecmp(v, OAUTH2_TOKEN_VERIFY_MTLS_STR) == 0) { verify->type = OAUTH2_TOKEN_VERIFY_MTLS; goto end; } rv = oauth2_strdup("Invalid value, must be one of: \""); rv = oauth2_stradd(rv, OAUTH2_TOKEN_VERIFY_BEARER_STR, "\", \"", NULL); rv = oauth2_stradd(rv, OAUTH2_TOKEN_VERIFY_DPOP_STR, "\" or \"", NULL); rv = oauth2_stradd(rv, OAUTH2_TOKEN_VERIFY_MTLS_STR, "\".", NULL); end: return rv; } #define OAUTH2_CFG_VERIFY_DPOP_CACHE_DEFAULT 10 #define OAUTH2_VERIFY_DPOP_SLACK_DEFAULT (oauth2_uint_t)5 static char * _oauth2_cfg_token_verify_options_dpop_set(oauth2_log_t *log, oauth2_cfg_token_verify_t *verify, oauth2_nv_list_t *params) { char *rv = NULL; verify->dpop.cache = oauth2_cache_obtain( log, oauth2_nv_list_get(log, params, "dpop.cache")); verify->dpop.expiry_s = oauth2_parse_uint( log, oauth2_nv_list_get(log, params, "dpop.expiry"), OAUTH2_CFG_VERIFY_DPOP_CACHE_DEFAULT); verify->dpop.iat_validate = oauth2_parse_validate_claim_option( log, oauth2_nv_list_get(log, params, "dpop.iat.verify"), OAUTH2_JOSE_JWT_VALIDATE_CLAIM_REQUIRED); verify->dpop.iat_slack_before = oauth2_parse_uint( log, oauth2_nv_list_get(log, params, "dpop.iat.slack.before"), OAUTH2_VERIFY_DPOP_SLACK_DEFAULT); verify->dpop.iat_slack_after = oauth2_parse_uint( log, oauth2_nv_list_get(log, params, "dpop.iat.slack.after"), OAUTH2_VERIFY_DPOP_SLACK_DEFAULT); return rv; } static char * _oauth2_cfg_token_verify_options_mtls_set(oauth2_log_t *log, oauth2_cfg_token_verify_t *verify, oauth2_nv_list_t *params) { char *rv = NULL; const char *policy = NULL; verify->mtls.env_var_name = oauth2_strdup(oauth2_nv_list_get(log, params, "mtls.env_var_name")); policy = oauth2_nv_list_get(log, params, "mtls.policy"); if (policy != NULL) { if (strcmp(policy, "optional") == 0) verify->mtls.policy = OAUTH2_MTLS_VERIFY_POLICY_OPTIONAL; else if (strcmp(policy, "required") == 0) verify->mtls.policy = OAUTH2_MTLS_VERIFY_POLICY_REQUIRED; } return rv; } #define OAUTH2_CFG_VERIFY_RESULT_CACHE_DEFAULT 300 char *oauth2_cfg_token_verify_add_options(oauth2_log_t *log, oauth2_cfg_token_verify_t **verify, const char *type, const char *value, const char *options) { char *rv = NULL; oauth2_cfg_token_verify_t *v = NULL; oauth2_nv_list_t *params = NULL; oauth2_debug(log, "enter: type=%s, value=%s, options=%s", type, value, options); if (oauth2_parse_form_encoded_params(log, options, ¶ms) == false) goto end; v = _oauth2_cfg_token_verify_add(log, verify); v->cache = oauth2_cache_obtain( log, oauth2_nv_list_get(log, params, "verify.cache")); v->expiry_s = oauth2_parse_uint(log, oauth2_nv_list_get(log, params, "expiry"), OAUTH2_CFG_VERIFY_RESULT_CACHE_DEFAULT); rv = _oauth2_cfg_token_verify_type_set(log, v, params); if (rv != NULL) goto end; if (v->type == OAUTH2_TOKEN_VERIFY_DPOP) { rv = _oauth2_cfg_token_verify_options_dpop_set(log, v, params); if (rv != NULL) goto end; } else if (v->type == OAUTH2_TOKEN_VERIFY_MTLS) { rv = _oauth2_cfg_token_verify_options_mtls_set(log, v, params); if (rv != NULL) goto end; } rv = oauth2_cfg_set_options(log, v, type, value, options, _oauth2_cfg_verify_options_set); end: if (params) oauth2_nv_list_free(log, params); oauth2_debug(log, "leave: %s", rv ? rv : "(null)"); return rv; } liboauth2-2.1.0/src/cfg_int.h000066400000000000000000000477211475305260400157750ustar00rootroot00000000000000#ifndef _OAUTH2_CFG_INT_H_ #define _OAUTH2_CFG_INT_H_ /*************************************************************************** * * Copyright (C) 2018-2025 - ZmartZone Holding BV * * 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. * * @Author: Hans Zandbelt - hans.zandbelt@openidc.com * **************************************************************************/ #include "oauth2/cache.h" #include "oauth2/cfg.h" #include "oauth2/oauth2.h" #include "oauth2/openidc.h" #include "oauth2/session.h" #include typedef enum oauth2_jose_jwt_validate_claim_t { OAUTH2_JOSE_JWT_VALIDATE_CLAIM_OPTIONAL, OAUTH2_JOSE_JWT_VALIDATE_CLAIM_REQUIRED, OAUTH2_JOSE_JWT_VALIDATE_CLAIM_SKIP } oauth2_jose_jwt_validate_claim_t; oauth2_jose_jwt_validate_claim_t oauth2_parse_validate_claim_option( oauth2_log_t *log, const char *value, oauth2_jose_jwt_validate_claim_t default_value); /* * endpoint */ typedef struct oauth2_cfg_endpoint_t { char *url; oauth2_cfg_endpoint_auth_t *auth; oauth2_flag_t ssl_verify; oauth2_uint_t http_timeout; char *outgoing_proxy; } oauth2_cfg_endpoint_t; /* * auth */ typedef struct oauth2_cfg_endpoint_auth_none_t { uint8_t dummy; } oauth2_cfg_endpoint_auth_none_t; typedef struct oauth2_cfg_endpoint_auth_client_secret_basic_t { char *client_id; char *client_secret; } oauth2_cfg_endpoint_auth_client_secret_basic_t; typedef struct oauth2_cfg_endpoint_auth_client_secret_post_t { char *client_id; char *client_secret; } oauth2_cfg_endpoint_auth_client_secret_post_t; typedef struct oauth2_cfg_endpoint_auth_client_secret_jwt_t { char *client_id; cjose_jwk_t *jwk; char *aud; } oauth2_cfg_endpoint_auth_client_secret_jwt_t; typedef struct oauth2_cfg_endpoint_auth_private_key_jwt_t { char *client_id; cjose_jwk_t *jwk; char *aud; } oauth2_cfg_endpoint_auth_private_key_jwt_t; typedef struct oauth2_cfg_endpoint_auth_client_cert_t { char *certfile; char *keyfile; } oauth2_cfg_endpoint_auth_client_cert_t; typedef struct oauth2_cfg_endpoint_auth_basic_t { char *username; char *password; } oauth2_cfg_endpoint_auth_basic_t; typedef struct oauth2_cfg_endpoint_auth_t { oauth2_cfg_endpoint_auth_type_t type; union { oauth2_cfg_endpoint_auth_none_t none; oauth2_cfg_endpoint_auth_client_secret_basic_t client_secret_basic; oauth2_cfg_endpoint_auth_client_secret_post_t client_secret_post; oauth2_cfg_endpoint_auth_client_secret_jwt_t client_secret_jwt; oauth2_cfg_endpoint_auth_private_key_jwt_t private_key_jwt; oauth2_cfg_endpoint_auth_client_cert_t client_cert; oauth2_cfg_endpoint_auth_basic_t basic; }; } oauth2_cfg_endpoint_auth_t; /* * source token */ typedef struct oauth2_cfg_source_token_t { oauth2_cfg_token_in_t accept_in; oauth2_flag_t strip; } oauth2_cfg_source_token_t; /* * cache */ // typedef struct oauth2_cfg_cache_t { // oauth2_cache_t *cache; // oauth2_time_t expiry_s; //} oauth2_cfg_cache_t; // // oauth2_cfg_cache_t *oauth2_cfg_cache_init(oauth2_log_t *log); // oauth2_cfg_cache_t *oauth2_cfg_cache_clone(oauth2_log_t *log, // oauth2_cfg_cache_t *src); // void oauth2_cfg_cache_free(oauth2_log_t *log, oauth2_cfg_cache_t *cache); char *oauth2_cfg_cache_set_options(oauth2_log_t *log, const char *type, const oauth2_nv_list_t *params); /* * verify */ typedef void *(oauth2_cfg_ctx_init_cb)(oauth2_log_t *log); typedef void *(oauth2_cfg_ctx_clone_cb)(oauth2_log_t *log, void *src); typedef void(oauth2_cfg_ctx_free_cb)(oauth2_log_t *log, void *); typedef struct oauth2_cfg_ctx_funcs_t { oauth2_cfg_ctx_init_cb *init; oauth2_cfg_ctx_clone_cb *clone; oauth2_cfg_ctx_free_cb *free; } oauth2_cfg_ctx_funcs_t; typedef bool(oauth2_cfg_token_verify_cb_t)(oauth2_log_t *, oauth2_cfg_token_verify_t *, const char *, json_t **, char **s_payload); typedef struct oauth2_cfg_ctx_t { void *ptr; oauth2_cfg_ctx_funcs_t *callbacks; } oauth2_cfg_ctx_t; oauth2_cfg_ctx_t *oauth2_cfg_ctx_init(oauth2_log_t *log); oauth2_cfg_ctx_t *oauth2_cfg_ctx_clone(oauth2_log_t *log, oauth2_cfg_ctx_t *src); void oauth2_cfg_ctx_free(oauth2_log_t *log, oauth2_cfg_ctx_t *ctx); #define OAUTH2_TOKEN_VERIFY_BEARER_STR "bearer" #define OAUTH2_TOKEN_VERIFY_DPOP_STR "dpop" #define OAUTH2_TOKEN_VERIFY_MTLS_STR "mtls" typedef struct oauth2_cfg_dpop_verify_t { oauth2_cache_t *cache; oauth2_time_t expiry_s; oauth2_jose_jwt_validate_claim_t iat_validate; oauth2_uint_t iat_slack_before; oauth2_uint_t iat_slack_after; } oauth2_cfg_dpop_verify_t; typedef enum oauth2_cfg_mtls_verify_policy_t { OAUTH2_MTLS_VERIFY_POLICY_OPTIONAL, OAUTH2_MTLS_VERIFY_POLICY_REQUIRED, } oauth2_cfg_mtls_verify_policy_t; typedef struct oauth2_cfg_mtls_verify_t { char *env_var_name; oauth2_cfg_mtls_verify_policy_t policy; } oauth2_cfg_mtls_verify_t; typedef struct oauth2_cfg_token_verify_t { oauth2_cfg_token_verify_cb_t *callback; oauth2_cfg_ctx_t *ctx; oauth2_cache_t *cache; oauth2_time_t expiry_s; oauth2_cfg_token_verify_type_t type; oauth2_cfg_dpop_verify_t dpop; oauth2_cfg_mtls_verify_t mtls; struct oauth2_cfg_token_verify_t *next; } oauth2_cfg_token_verify_t; typedef char *(oauth2_cfg_set_options_cb_t)(oauth2_log_t *log, const char *value, const oauth2_nv_list_t *params, void *cfg); #define _OAUTH_CFG_CTX_CALLBACK(name) \ char *name(oauth2_log_t *log, const char *value, \ const oauth2_nv_list_t *params, void *ctx) typedef struct oauth2_cfg_set_options_ctx_t { const char *type; oauth2_cfg_set_options_cb_t *set_options_callback; } oauth2_cfg_set_options_ctx_t; char *oauth2_cfg_set_options(oauth2_log_t *log, void *cfg, const char *type, const char *value, const char *options, const oauth2_cfg_set_options_ctx_t *set); /* * session */ bool oauth2_session_load_cookie(oauth2_log_t *log, const oauth2_cfg_session_t *cfg, oauth2_http_request_t *request, json_t **json); bool oauth2_session_save_cookie(oauth2_log_t *log, const oauth2_cfg_session_t *cfg, const oauth2_http_request_t *request, oauth2_http_response_t *response, json_t *json); bool oauth2_session_load_cache(oauth2_log_t *log, const oauth2_cfg_session_t *cfg, oauth2_http_request_t *request, json_t **json); bool oauth2_session_save_cache(oauth2_log_t *log, const oauth2_cfg_session_t *cfg, const oauth2_http_request_t *request, oauth2_http_response_t *response, json_t *json); typedef enum oauth2_cfg_session_type_t { OAUTH2_SESSION_TYPE_COOKIE, OAUTH2_SESSION_TYPE_CACHE } oauth2_cfg_session_type_t; typedef struct oauth2_cfg_session_t { oauth2_cfg_session_type_t type; char *cookie_name; char *cookie_path; oauth2_time_t inactivity_timeout_s; oauth2_time_t max_duration_s; oauth2_cache_t *cache; oauth2_session_load_callback_t *load_callback; oauth2_session_save_callback_t *save_callback; } oauth2_cfg_session_t; void _oauth2_session_global_cleanup(oauth2_log_t *log); /* * openidc */ typedef bool(oauth2_openidc_provider_resolver_func_t)( oauth2_log_t *log, const oauth2_cfg_openidc_t *cfg, const oauth2_http_request_t *, char **); typedef struct oauth2_cfg_openidc_provider_resolver_t { oauth2_openidc_provider_resolver_func_t *callback; oauth2_cfg_ctx_t *ctx; oauth2_cache_t *cache; } oauth2_cfg_openidc_provider_resolver_t; // TODO: set add log typedef struct oauth2_cfg_openidc_t { char *handler_path; char *redirect_uri; oauth2_cfg_openidc_provider_resolver_t *provider_resolver; oauth2_openidc_client_t *client; oauth2_unauth_action_t unauth_action; oauth2_cfg_session_t *session; char *state_cookie_name_prefix; oauth2_time_t state_cookie_timeout; oauth2_uint_t state_cookie_max; oauth2_flag_t state_cookie_delete_oldest; } oauth2_cfg_openidc_t; /* * generic */ #define _OAUTH2_CFG_CTX_TYPE_START(type) typedef struct type##_t { #define _OAUTH2_CFG_CTX_TYPE_END(type) \ } \ type##_t; #define _OAUTH2_CFG_CTX_INIT_START(type) \ void *type##_init(oauth2_log_t *log) \ { \ type##_t *ctx = (type##_t *)oauth2_mem_alloc(sizeof(type##_t)); #define _OAUTH2_CFG_CTX_INIT_END \ return ctx; \ } #define _OAUTH2_CFG_CTX_CLONE_START(type) \ void *type##_clone(oauth2_log_t *log, void *s) \ { \ type##_t *src = s; \ type##_t *dst = NULL; \ if (src == NULL) \ goto end; \ dst = type##_init(log); #define _OAUTH2_CFG_CTX_CLONE_END \ end: \ return dst; \ } #define _OAUTH2_CFG_CTX_FREE_START(type) \ void type##_free(oauth2_log_t *log, void *c) \ { \ type##_t *ctx = (type##_t *)c; #define _OAUTH2_CFG_CTX_FREE_END \ if (ctx) \ oauth2_mem_free(ctx); \ } #define _OAUTH2_CFG_CTX_FUNCS(type) \ static oauth2_cfg_ctx_funcs_t type##_funcs = { \ type##_init, type##_clone, type##_free}; #define _OAUTH2_CFG_CTX_TYPE_SINGLE_STRING(type, member) \ _OAUTH2_CFG_CTX_TYPE_START(type) \ char *member; \ _OAUTH2_CFG_CTX_TYPE_END(type) \ \ _OAUTH2_CFG_CTX_INIT_START(type) \ ctx->member = NULL; \ _OAUTH2_CFG_CTX_INIT_END \ \ _OAUTH2_CFG_CTX_CLONE_START(type) \ dst->member = oauth2_strdup(src->member); \ _OAUTH2_CFG_CTX_CLONE_END \ \ _OAUTH2_CFG_CTX_FREE_START(type) \ if (ctx->member) \ oauth2_mem_free(ctx->member); \ _OAUTH2_CFG_CTX_FREE_END \ \ _OAUTH2_CFG_CTX_FUNCS(type) oauth2_cfg_session_t *_oauth2_cfg_session_obtain(oauth2_log_t *log, const char *name); #define _OAUTH2_CFG_GLOBAL_LIST(name, type) \ typedef void (*_oauth2_##name##_free_fn)(oauth2_log_t * log, \ type * mtype); \ \ typedef struct oauth2_##name##_list_t { \ char *mname; \ type *mtype; \ _oauth2_##name##_free_fn free_fn; \ struct oauth2_##name##_list_t *next; \ } oauth2_##name##_list_t; \ \ static oauth2_##name##_list_t *_oauth2_##name##_list = NULL; \ static oauth2_ipc_mutex_t *_oauth2_##name##_list_mutex = NULL; \ \ static bool _M_##name##_list_lock(oauth2_log_t *log) \ { \ bool rc = false; \ if (_oauth2_##name##_list_mutex == NULL) { \ _oauth2_##name##_list_mutex = \ oauth2_ipc_mutex_init(log); \ oauth2_ipc_mutex_post_config( \ log, _oauth2_##name##_list_mutex); \ } \ rc = oauth2_ipc_mutex_lock(log, _oauth2_##name##_list_mutex); \ return rc; \ } \ \ static bool _M_##name##_list_unlock(oauth2_log_t *log) \ { \ bool rc = false; \ rc = \ oauth2_ipc_mutex_unlock(log, _oauth2_##name##_list_mutex); \ return rc; \ } \ \ static void _M_##name##_list_register( \ oauth2_log_t *log, const char *name, type *mtype, \ _oauth2_##name##_free_fn mfree_fn) \ { \ oauth2_##name##_list_t *ptr = NULL, *prev = NULL; \ \ /* oauth2_debug(log, "registering: %s", name); */ \ \ ptr = oauth2_mem_alloc(sizeof(oauth2_##name##_list_t)); \ ptr->mname = oauth2_strdup(name); \ ptr->mtype = mtype; \ ptr->next = NULL; \ ptr->free_fn = mfree_fn; \ \ _M_##name##_list_lock(log); \ \ if (_oauth2_##name##_list) { \ prev = _oauth2_##name##_list; \ while (prev->next) \ prev = prev->next; \ prev->next = ptr; \ } else { \ _oauth2_##name##_list = ptr; \ } \ \ _M_##name##_list_unlock(log); \ } \ \ bool _M_##name##_list_empty(oauth2_log_t *log) \ { \ return (_oauth2_##name##_list == NULL); \ } \ \ static type *_M_##name##_list_get(oauth2_log_t *log, \ const char *mname) \ { \ oauth2_##name##_list_t *ptr = NULL, *match = NULL; \ \ _M_##name##_list_lock(log); \ \ ptr = _oauth2_##name##_list; \ while (ptr) { \ \ /* oauth2_debug(log, "comparing: \ * \"%s\" with \%s\"", ptr->mname, mname); */ \ \ if ((mname) && (ptr->mname)) { \ if (strcmp(ptr->mname, mname) == 0) { \ match = ptr; \ break; \ } \ } else if ((mname == NULL) || \ (strcmp("default", mname) == 0)) { \ match = ptr; \ } \ ptr = ptr->next; \ } \ \ _M_##name##_list_unlock(log); \ \ oauth2_debug(log, "returning: %p, %p, %s", match, \ match ? match->mtype : NULL, \ match ? match->mname : NULL); \ \ return match ? match->mtype : NULL; \ } \ \ static void _M_##name##_list_release(oauth2_log_t *log) \ { \ oauth2_##name##_list_t *ptr = NULL; \ \ _M_##name##_list_lock(log); \ \ while ((ptr = _oauth2_##name##_list)) { \ _oauth2_##name##_list = _oauth2_##name##_list->next; \ if (ptr->free_fn) \ ptr->free_fn(log, ptr->mtype); \ oauth2_mem_free(ptr->mname); \ oauth2_mem_free(ptr); \ } \ \ _M_##name##_list_unlock(log); \ \ if (_oauth2_##name##_list_mutex != NULL) { \ oauth2_ipc_mutex_free(log, \ _oauth2_##name##_list_mutex); \ _oauth2_##name##_list_mutex = NULL; \ } \ } #endif /* _OAUTH2_CFG_INT_H_ */ liboauth2-2.1.0/src/dpop.c000066400000000000000000000366611475305260400153220ustar00rootroot00000000000000/*************************************************************************** * * Copyright (C) 2018-2025 - ZmartZone Holding BV * * 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. * * @Author: Hans Zandbelt - hans.zandbelt@openidc.com * **************************************************************************/ #include #include #include #include "cfg_int.h" #include "jose_int.h" #define OAUTH2_HTTP_HDR_DPOP "DPoP" #define OAUTH_DPOP_HDR_JWK "jwk" #define OAUTH_DPOP_CLAIM_HTM "htm" #define OAUTH_DPOP_CLAIM_HTU "htu" #define OAUTH_DPOP_CLAIM_ATH "ath" #define OAUTH_DPOP_CLAIM_NONCE "nonce" #define OAUTH_DPOP_HDR_TYP_VALUE "dpop+jwt" static bool _oauth2_dpop_jwt_validate(oauth2_log_t *log, const char *s_dpop, cjose_jws_t **jws, cjose_header_t **hdr, json_t **dpop_payload) { bool rc = false; cjose_err err; uint8_t *plaintext = NULL; size_t plaintext_len = 0; char *s_payload = NULL; *jws = cjose_jws_import(s_dpop, strlen(s_dpop), &err); if (*jws == NULL) { oauth2_error(log, "cjose_jws_import failed: %s", err.message); goto end; } *hdr = cjose_jws_get_protected(*jws); if (*hdr == NULL) goto end; if (cjose_jws_get_plaintext(*jws, &plaintext, &plaintext_len, &err) == false) { oauth2_error(log, "cjose_jws_get_plaintext failed: %s", err.message); goto end; } s_payload = oauth2_strndup((const char *)plaintext, plaintext_len); oauth2_debug(log, "DPOP payload: %s", s_payload); if (oauth2_json_decode_object(log, s_payload, dpop_payload) == false) { oauth2_error(log, "decoding JWT payload failed"); goto end; } rc = true; end: if (s_payload) oauth2_mem_free(s_payload); return rc; } static bool _oauth2_dpop_claims_validate(oauth2_log_t *log, cjose_header_t *hdr, json_t *dpop_payload, cjose_jwk_t **jwk, const char **hdr_typ, const char **hdr_alg, char **clm_htm, char **clm_htu, char **clm_jti, char **clm_ath, char **clm_nonce) { bool rc = false; cjose_err err; char *hdr_jwk = NULL; json_int_t clm_iat = 0; *hdr_typ = cjose_header_get(hdr, OAUTH2_JOSE_HDR_TYP, &err); if (*hdr_typ == NULL) { oauth2_error(log, "required claim \"%s\" not found in DPOP header", OAUTH2_JOSE_HDR_TYP); goto end; } *hdr_alg = cjose_header_get(hdr, CJOSE_HDR_ALG, &err); if (*hdr_alg == NULL) { oauth2_error(log, "required claim \"%s\" not found in DPOP header", CJOSE_HDR_ALG); goto end; } hdr_jwk = cjose_header_get_raw(hdr, OAUTH_DPOP_HDR_JWK, &err); if (hdr_jwk == NULL) { oauth2_error(log, "required claim \"%s\" not found in DPOP header", OAUTH_DPOP_HDR_JWK); goto end; } *jwk = cjose_jwk_import(hdr_jwk, strlen(hdr_jwk), &err); if (*jwk == NULL) { oauth2_error(log, "cjose_jwk_import failed: %s", err.message); goto end; } if ((oauth2_json_string_get(log, dpop_payload, OAUTH_DPOP_CLAIM_HTU, clm_htu, NULL) == false) || (*clm_htu == NULL)) { oauth2_error(log, "required claim \"%s\" not found in DPOP payload", OAUTH_DPOP_CLAIM_HTU); goto end; } if ((oauth2_json_string_get(log, dpop_payload, OAUTH_DPOP_CLAIM_HTM, clm_htm, NULL) == false) || (*clm_htm == NULL)) { oauth2_error(log, "required claim \"%s\" not found in DPOP payload", OAUTH_DPOP_CLAIM_HTM); goto end; } if ((oauth2_json_string_get(log, dpop_payload, OAUTH2_CLAIM_JTI, clm_jti, NULL) == false) || (*clm_jti == NULL)) { oauth2_error(log, "required claim \"%s\" not found in DPOP payload", OAUTH2_CLAIM_JTI); goto end; } if ((oauth2_json_number_get(log, dpop_payload, OAUTH2_CLAIM_IAT, &clm_iat, 0) == false) || (clm_iat == 0)) { oauth2_error(log, "required claim \"%s\" not found in DPOP payload", OAUTH2_CLAIM_IAT); goto end; } if ((oauth2_json_string_get(log, dpop_payload, OAUTH_DPOP_CLAIM_ATH, clm_ath, NULL) == false) || (*clm_ath == NULL)) { oauth2_error(log, "required claim \"%s\" not found in DPOP payload", OAUTH_DPOP_CLAIM_ATH); goto end; } if ((oauth2_json_string_get(log, dpop_payload, OAUTH_DPOP_CLAIM_NONCE, clm_nonce, NULL) == false) || (*clm_nonce == NULL)) { oauth2_debug( log, "(optional) claim \"%s\" not found in DPOP payload", OAUTH_DPOP_CLAIM_NONCE); } rc = true; end: if (hdr_jwk) cjose_get_dealloc()(hdr_jwk); return rc; } static bool _oauth2_dpop_hdr_typ_validate(oauth2_log_t *log, const char *hdr_typ) { bool rc = false; if (strcasecmp(hdr_typ, OAUTH_DPOP_HDR_TYP_VALUE) != 0) { oauth2_error( log, "the %s header value (%s) does not match DPOP value (%s)", OAUTH2_JOSE_HDR_TYP, hdr_typ, OAUTH_DPOP_HDR_TYP_VALUE); goto end; } rc = true; end: return rc; } static bool _oauth2_dpop_hdr_alg_validate(oauth2_log_t *log, const char *hdr_alg) { bool rc = false; if (strcasecmp(hdr_alg, CJOSE_HDR_ALG_NONE) == 0) { oauth2_error(log, "the %s header value cannot be \"%s\"", CJOSE_HDR_ALG, CJOSE_HDR_ALG_NONE); goto end; } if ((strstr(hdr_alg, "RS") != hdr_alg) && (strstr(hdr_alg, "PS") != hdr_alg) && (strstr(hdr_alg, "ES") != hdr_alg)) { oauth2_error(log, "the %s header value must be asymmetric and " "starting with \"RS\", \"PS\" or \"ES\".", CJOSE_HDR_ALG); goto end; } rc = true; end: return rc; } static bool _oauth2_dpop_sig_verify(oauth2_log_t *log, cjose_jws_t *jws, const cjose_jwk_t *jwk) { bool rc = false; cjose_err err; if (cjose_jws_verify(jws, jwk, &err) == false) { oauth2_error(log, "DPOP signature verification failed: %s", err.message); goto end; } rc = true; end: return rc; } static bool _oauth2_dpop_htm_validate(oauth2_log_t *log, oauth2_http_request_t *request, const char *clm_htm) { bool rc = false; const char *method = NULL; method = oauth2_http_request_method_get_str(log, request); if (strcasecmp(method, clm_htm) != 0) { oauth2_error(log, "requested HTTP method (%s) does not match DPOP " "\"%s\" value (%s)", method, OAUTH_DPOP_CLAIM_HTM, clm_htm); goto end; } rc = true; end: return rc; } static bool _oauth2_dpop_htu_validate(oauth2_log_t *log, oauth2_http_request_t *request, const char *clm_htu) { bool rc = false; char *url = NULL; url = oauth2_http_request_url_path_get(log, request); if (url == NULL) goto end; if (strcasecmp(url, clm_htu) != 0) { oauth2_error( log, "requested URL (%s) does not match DPOP \"%s\" value (%s)", url, OAUTH_DPOP_CLAIM_HTU, clm_htu); goto end; } rc = true; end: if (url) oauth2_mem_free(url); return rc; } static bool _oauth2_dpop_iat_validate(oauth2_log_t *log, oauth2_cfg_dpop_verify_t *verify, json_t *dpop_payload) { bool rc = false; if (oauth2_jose_jwt_validate_iat( log, dpop_payload, verify->iat_validate, verify->iat_slack_before, verify->iat_slack_after) == false) goto end; rc = true; end: return rc; } static bool _oauth2_dpop_jti_validate(oauth2_log_t *log, oauth2_cfg_dpop_verify_t *verify, const char *clm_jti, const char *s_dpop) { bool rc = false; char *s_value = NULL; oauth2_cache_get(log, verify->cache, clm_jti, &s_value); if (s_value != NULL) { oauth2_error(log, "a token with the same JTI \"%s\" exists in " "the cache: possible replay attack", clm_jti); goto end; } oauth2_cache_set(log, verify->cache, clm_jti, s_dpop, verify->expiry_s); rc = true; end: if (s_value) oauth2_mem_free(s_value); return rc; } static bool(_oauth2_dpp_hdr_count)(oauth2_log_t *log, void *rec, const char *key, const char *value) { int *n = (int *)rec; if (strcasecmp(key, OAUTH2_HTTP_HDR_DPOP) == 0) (*n)++; return true; } static bool _oauth2_dpop_header_count(oauth2_log_t *log, oauth2_http_request_t *request) { bool rc = false; int n = 0; oauth2_http_request_headers_loop(log, request, _oauth2_dpp_hdr_count, &n); if (n > 1) { oauth2_error(log, "more than one %s header found", OAUTH2_HTTP_HDR_DPOP); goto end; } if (n == 0) { oauth2_error(log, "no %s header found", OAUTH2_HTTP_HDR_DPOP); goto end; } rc = true; end: return rc; } static bool _oauth2_dpop_ath_validate(oauth2_log_t *log, oauth2_cfg_dpop_verify_t *verify, const char *clm_ath, const char *access_token) { bool rc = false; unsigned char *calc = NULL; unsigned int calc_len = 0; uint8_t *dec = NULL; size_t dec_len = 0; if ((clm_ath == NULL) || (access_token == NULL)) goto end; if (oauth2_jose_hash_bytes( log, "sha256", (const unsigned char *)access_token, strlen(access_token), &calc, &calc_len) == false) goto end; if (oauth2_base64url_decode(log, clm_ath, &dec, &dec_len) == false) goto end; if ((calc_len != dec_len) || (memcmp(dec, calc, dec_len) != 0)) { oauth2_error(log, "provided \"ath\" hash value (%s) does not match " "the calculated value (dec_len=%d, calc_len=%d)", clm_ath, dec_len, calc_len); goto end; } oauth2_debug(log, "successfully validated the provided \"ath\" hash value " "(%s) against the calculated value", clm_ath); rc = true; end: if (dec) oauth2_mem_free(dec); if (calc) oauth2_mem_free(calc); return rc; } static bool _oauth2_dpop_parse_and_validate(oauth2_log_t *log, oauth2_cfg_dpop_verify_t *verify, oauth2_http_request_t *request, const char *access_token, cjose_jwk_t **jwk) { bool rc = false; const char *hdr_typ = NULL, *hdr_alg = NULL; char *clm_htm = NULL, *clm_htu = NULL, *clm_jti = NULL, *clm_ath = NULL, *clm_nonce = NULL; const char *s_dpop = NULL; cjose_jws_t *jws = NULL; cjose_header_t *hdr = NULL; char *s_peek = NULL; json_t *dpop_payload = NULL; if ((request == NULL) || (verify == NULL) || (jwk == NULL)) goto end; /* * 1. that there is not more than one DPoP header in the request, */ if (_oauth2_dpop_header_count(log, request) == false) goto end; s_dpop = oauth2_http_request_header_get(log, request, OAUTH2_HTTP_HDR_DPOP); if (s_dpop == NULL) goto end; s_peek = oauth2_jose_jwt_header_peek(log, s_dpop, NULL); if (s_peek) oauth2_debug(log, "DPOP header: %s", s_peek); /* * 2. the string value of the header field is a well-formed JWT, */ if (_oauth2_dpop_jwt_validate(log, s_dpop, &jws, &hdr, &dpop_payload) == false) goto end; /* * 3. all required claims per Section 4.2 are contained in the JWT, */ if (_oauth2_dpop_claims_validate(log, hdr, dpop_payload, jwk, &hdr_typ, &hdr_alg, &clm_htm, &clm_htu, &clm_jti, &clm_ath, &clm_nonce) == false) goto end; /* * 4. the typ field in the header has the value dpop+jwt, */ if (_oauth2_dpop_hdr_typ_validate(log, hdr_typ) == false) goto end; /* * 5. the algorithm in the header of the JWT indicates an asymmetric * digital signature algorithm, is not none, is supported by the * application, and is deemed secure, */ if (_oauth2_dpop_hdr_alg_validate(log, hdr_alg) == false) goto end; /* * 6. the JWT signature verifies with the public key contained in the * jwk header of the JWT, */ if (_oauth2_dpop_sig_verify(log, jws, *jwk) == false) goto end; /* * 7. the jwk header of the JWT does not contain a private key, */ // TODO: /* * 8. the htm claim matches the HTTP method value of the HTTP request * in which the JWT was received, */ if (_oauth2_dpop_htm_validate(log, request, clm_htm) == false) goto end; /* * 9. the htu claim matches the HTTPS URI value for the HTTP request * in which the JWT was received, ignoring any query and fragment * parts, */ if (_oauth2_dpop_htu_validate(log, request, clm_htu) == false) goto end; /* * 10. if the server provided a nonce value to the client, the nonce * claim matches the server-provided nonce value, */ // TODO: /* * 11. the iat claim value is within an acceptable timeframe and, * within a reasonable consideration of accuracy and resource * utilization, a proof JWT with the same jti value has not * previously been received at the same resource during that time * period (see Section 11.1), */ if (_oauth2_dpop_iat_validate(log, verify, dpop_payload) == false) goto end; if (_oauth2_dpop_jti_validate(log, verify, clm_jti, s_dpop) == false) goto end; /* * 12. if presented to a protected resource in conjunction with an * access token, * * 1. ensure that the value of the ath claim equals the hash of * that access token, */ if (_oauth2_dpop_ath_validate(log, verify, clm_ath, access_token) == false) goto end; /* * 2. confirm that the public key to which the access token is * bound matches the public key from the DPoP proof. * */ // done in the calling function oauth2_dpop_token_verify with the "jkt" // claim rc = true; end: if (s_peek) oauth2_mem_free(s_peek); if (clm_htu) oauth2_mem_free(clm_htu); if (clm_htm) oauth2_mem_free(clm_htm); if (clm_jti) oauth2_mem_free(clm_jti); if (clm_ath) oauth2_mem_free(clm_ath); if (clm_nonce) oauth2_mem_free(clm_nonce); if (dpop_payload) json_decref(dpop_payload); if (jws) cjose_jws_release(jws); return rc; } #define OAUTH_DPOP_CLAIM_CNF "cnf" #define OAUTH_DPOP_CLAIM_CNF_JKT "jkt" bool oauth2_dpop_token_verify(oauth2_log_t *log, oauth2_cfg_dpop_verify_t *verify, oauth2_http_request_t *request, const char *access_token, json_t *json_payload) { bool rc = false; cjose_jwk_t *jwk = NULL; json_t *cnf = NULL; char *calc_thumb = NULL; const char *prov_thumb = NULL; unsigned char *hash_bytes = NULL; unsigned int hash_bytes_len = 0; uint8_t *dst = NULL; size_t dst_len = 0; if ((request == NULL) || (json_payload == NULL)) goto end; if (_oauth2_dpop_parse_and_validate(log, verify, request, access_token, &jwk) == false) goto end; if (oauth2_jose_jwk_thumbprint(log, jwk, &hash_bytes, &hash_bytes_len) == false) { oauth2_error(log, "oauth2_jose_jwk_thumbprint failed"); goto end; } cnf = json_object_get(json_payload, OAUTH_DPOP_CLAIM_CNF); if (cnf == NULL) { oauth2_error(log, "no \"%s\" claim found", OAUTH_DPOP_CLAIM_CNF); goto end; } prov_thumb = json_string_value(json_object_get(cnf, OAUTH_DPOP_CLAIM_CNF_JKT)); if (oauth2_base64url_decode(log, prov_thumb, &dst, &dst_len) == false) { oauth2_error(log, "oauth2_base64url_decode failed"); goto end; } if ((hash_bytes_len != dst_len) || (memcmp(hash_bytes, dst, hash_bytes_len)) != 0) { oauth2_error(log, "public key thumbprint in DPOP \"%s\" does not " "match \"%s\" claim \%s\" for the access token", calc_thumb, OAUTH_DPOP_CLAIM_CNF_JKT, prov_thumb); goto end; } rc = true; end: if (dst) oauth2_mem_free(dst); if (hash_bytes) oauth2_mem_free(hash_bytes); if (calc_thumb) oauth2_mem_free(calc_thumb); if (jwk) cjose_jwk_release(jwk); return rc; } liboauth2-2.1.0/src/http.c000066400000000000000000001233111475305260400153240ustar00rootroot00000000000000/*************************************************************************** * * Copyright (C) 2018-2025 - ZmartZone Holding BV * * 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. * * @Author: Hans Zandbelt - hans.zandbelt@openidc.com * **************************************************************************/ #include #include #include #include "oauth2/http.h" #include "oauth2/mem.h" #include "oauth2/util.h" #include "oauth2/version.h" #include "util_int.h" /* * request */ typedef struct oauth2_http_request_t { // oauth2_http_server_t *server; oauth2_nv_list_t *header; char *scheme; unsigned long port; char *hostname; oauth2_uint_t method; char *path; char *query; oauth2_nv_list_t *_parsed_query; oauth2_nv_list_t *_parsed_cookies; oauth2_nv_list_t *_context; } oauth2_http_request_t; // TODO: provide scheme as part of init? oauth2_http_request_t *oauth2_http_request_init(oauth2_log_t *log) { oauth2_http_request_t *request = NULL; request = oauth2_mem_alloc(sizeof(oauth2_http_request_t)); if (request == NULL) goto end; request->header = oauth2_nv_list_init(log); oauth2_nv_list_case_sensitive_set(log, request->header, false); request->scheme = NULL; request->hostname = NULL; request->port = 0; request->method = 0; request->path = NULL; request->query = NULL; request->_parsed_query = NULL; request->_parsed_cookies = NULL; request->_context = oauth2_nv_list_init(log); ; end: return request; } void oauth2_http_request_free(oauth2_log_t *log, oauth2_http_request_t *request) { if (request == NULL) goto end; oauth2_nv_list_free(log, request->_context); oauth2_nv_list_free(log, request->_parsed_query); oauth2_nv_list_free(log, request->_parsed_cookies); oauth2_nv_list_free(log, request->header); if (request->scheme) oauth2_mem_free(request->scheme); if (request->hostname) oauth2_mem_free(request->hostname); if (request->path) oauth2_mem_free(request->path); if (request->query) oauth2_mem_free(request->query); oauth2_mem_free(request); end: return; } _OAUTH2_MEMBER_LIST_IMPLEMENT_UNSET_GET(http, request, header) void oauth2_http_request_headers_loop(oauth2_log_t *log, oauth2_http_request_t *request, oauth2_nv_list_loop_cb_t *callback, void *rec) { oauth2_nv_list_loop(log, request->header, callback, rec); } /* * headers */ typedef bool(_oauth2_nv_list_set_add_sanitize_cb_t)(oauth2_log_t *, oauth2_nv_list_t *, const char *, const char *); static bool _oauth2_http_request_header_set_add_sanitized( oauth2_log_t *log, oauth2_http_request_t *request, const char *name, const char *value, _oauth2_nv_list_set_add_sanitize_cb_t add_set_cb) { bool rc = false; char *s_value = NULL, *p = NULL; if ((request == NULL) && (name == NULL)) goto end; if (value) { s_value = oauth2_strdup(value); if (s_value == NULL) goto end; /* * 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 */ while ((p = strchr(s_value, '\n'))) *p = ' '; } oauth2_debug(log, "%s: %s", name, s_value ? s_value : "(null)"); rc = add_set_cb(log, request->header, name, s_value); end: if (s_value) oauth2_mem_free(s_value); return rc; } bool oauth2_http_request_context_set(oauth2_log_t *log, oauth2_http_request_t *request, const char *name, const char *value) { bool rc = false; if (request == NULL) goto end; if (strcmp(name, OAUTH2_TLS_CERT_VAR_NAME) == 0) oauth2_debug( log, "set SSL client certificate in request context: %s", value); rc = oauth2_nv_list_set(log, request->_context, name, value); end: return rc; } const char *oauth2_http_request_context_get( oauth2_log_t *log, const oauth2_http_request_t *request, const char *name) { const char *rv = NULL; if (request == NULL) goto end; rv = oauth2_nv_list_get(log, request->_context, name); end: return rv; } bool oauth2_http_request_header_set(oauth2_log_t *log, oauth2_http_request_t *request, const char *name, const char *value) { return _oauth2_http_request_header_set_add_sanitized( log, request, name, value, oauth2_nv_list_set); } bool oauth2_http_request_header_add(oauth2_log_t *log, oauth2_http_request_t *request, const char *name, const char *value) { return _oauth2_http_request_header_set_add_sanitized( log, request, name, value, oauth2_nv_list_add); } static char *oauth2_http_request_header_get_left_most_only( oauth2_log_t *log, const oauth2_http_request_t *request, const char *name) { char *rv = NULL, *v = NULL; const char *value = NULL; const char *separators = ", \t"; value = oauth2_http_request_header_get(log, request, name); if (value == NULL) goto end; v = oauth2_strdup(value); if (v) rv = strtok(v, separators); end: return rv; } static char * oauth2_http_request_header_x_forwarded_proto_get(oauth2_log_t *log, const oauth2_http_request_t *r) { return oauth2_http_request_header_get_left_most_only( log, r, OAUTH2_HTTP_HDR_X_FORWARDED_PROTO); } static char *oauth2_http_request_header_x_forwarded_port_get( oauth2_log_t *log, const oauth2_http_request_t *request) { return oauth2_http_request_header_get_left_most_only( log, request, OAUTH2_HTTP_HDR_X_FORWARDED_PORT); } static char *oauth2_http_request_header_x_forwarded_host_get( oauth2_log_t *log, const oauth2_http_request_t *request) { return oauth2_http_request_header_get_left_most_only( log, request, OAUTH2_HTTP_HDR_X_FORWARDED_HOST); } static char * oauth2_http_request_header_host_get(oauth2_log_t *log, const oauth2_http_request_t *request) { return oauth2_strdup( oauth2_http_request_header_get(log, request, OAUTH2_HTTP_HDR_HOST)); } const char *oauth2_http_request_header_content_type_get( oauth2_log_t *log, const oauth2_http_request_t *request) { return oauth2_http_request_header_get(log, request, OAUTH2_HTTP_HDR_CONTENT_TYPE); } const char *oauth2_http_request_header_content_length_get( oauth2_log_t *log, const oauth2_http_request_t *request) { return oauth2_http_request_header_get(log, request, OAUTH2_HTTP_HDR_CONTENT_LENGTH); } const char *oauth2_http_request_header_x_requested_with_get( oauth2_log_t *log, const oauth2_http_request_t *request) { return oauth2_http_request_header_get(log, request, OAUTH2_HTTP_HDR_X_REQUESTED_WITH); } const char * oauth2_http_request_header_accept_get(oauth2_log_t *log, const oauth2_http_request_t *request) { return oauth2_http_request_header_get(log, request, OAUTH2_HTTP_HDR_ACCEPT); } #define OAUTH2_HTTP_HDR_CONTENT_LENGTH_MAX 256 bool oauth2_http_request_header_content_length_set( oauth2_log_t *log, oauth2_http_request_t *request, size_t len) { char str[OAUTH2_HTTP_HDR_CONTENT_LENGTH_MAX]; oauth2_snprintf(str, OAUTH2_HTTP_HDR_CONTENT_LENGTH_MAX, "%lu", len); return oauth2_http_request_header_set( log, request, OAUTH2_HTTP_HDR_CONTENT_LENGTH, str); } const char * oauth2_http_request_header_cookie_get(oauth2_log_t *log, const oauth2_http_request_t *request) { return oauth2_http_request_header_get(log, request, OAUTH2_HTTP_HDR_COOKIE); } static bool _oauth2_http_request_header_cookie_set( oauth2_log_t *log, oauth2_http_request_t *request, const char *value) { return oauth2_http_request_header_set(log, request, OAUTH2_HTTP_HDR_COOKIE, value); } _OAUTH2_TYPE_IMPLEMENT_MEMBER_SET(http, request, scheme, char *, str) _OAUTH2_TYPE_IMPLEMENT_MEMBER_SET(http, request, hostname, char *, str) _OAUTH2_TYPE_IMPLEMENT_MEMBER_SET_GET(http, request, path, char *, str) _OAUTH2_TYPE_IMPLEMENT_MEMBER_SET_GET(http, request, method, oauth2_http_method_t, uint) _OAUTH2_TYPE_IMPLEMENT_MEMBER_SET_GET(http, request, query, char *, str) const char *oauth2_http_request_method_get_str(oauth2_log_t *log, oauth2_http_request_t *request) { const char *rv = NULL; switch (oauth2_http_request_method_get(log, request)) { case OAUTH2_HTTP_METHOD_GET: rv = "GET"; break; case OAUTH2_HTTP_METHOD_PUT: rv = "PUT"; break; case OAUTH2_HTTP_METHOD_POST: rv = "POST"; break; case OAUTH2_HTTP_METHOD_DELETE: rv = "DELETE"; break; case OAUTH2_HTTP_METHOD_CONNECT: rv = "CONNECT"; break; case OAUTH2_HTTP_METHOD_OPTIONS: rv = "OPTIONS"; break; case OAUTH2_HTTP_METHOD_UNKNOWN: break; default: break; } return rv; } /* * current request URI */ char *oauth2_http_request_scheme_get(oauth2_log_t *log, const oauth2_http_request_t *request) { char *scheme_str = NULL; if (request == NULL) goto end; scheme_str = oauth2_http_request_header_x_forwarded_proto_get(log, request); if (scheme_str == NULL) scheme_str = oauth2_strdup(request->scheme); if ((scheme_str == NULL) || ((strcmp(scheme_str, OAUTH2_HTTP_SCHEME_HTTP) != 0) && (strcmp(scheme_str, OAUTH2_HTTP_SCHEME_HTTPS) != 0))) { oauth2_warn(log, "detected HTTP scheme \"%s\" is not \"%s\" nor " "\"%s\"; perhaps your reverse proxy passes a " "wrongly configured \"%s\" header: falling back " "to default \"%s\"", scheme_str, OAUTH2_HTTP_SCHEME_HTTP, OAUTH2_HTTP_SCHEME_HTTPS, OAUTH2_HTTP_HDR_X_FORWARDED_PROTO, OAUTH2_HTTP_SCHEME_HTTPS); scheme_str = oauth2_strdup(OAUTH2_HTTP_SCHEME_HTTPS); } end: return scheme_str; } bool oauth2_http_request_port_set(oauth2_log_t *log, oauth2_http_request_t *request, unsigned long port) { request->port = port; return (request->port > 0); } #define OAUTH2_PORT_STR_MAX 16 char *oauth2_http_request_port_get(oauth2_log_t *log, const oauth2_http_request_t *request) { char *proto_str = NULL, *port_str = NULL, *scheme_str = NULL; port_str = oauth2_http_request_header_x_forwarded_port_get(log, request); if (port_str) goto end; char *host_hdr = oauth2_http_request_header_x_forwarded_host_get(log, request); if (host_hdr) { port_str = strchr(host_hdr, _OAUTH2_CHAR_COLON); if (port_str) { port_str++; port_str = oauth2_strdup(port_str); } oauth2_mem_free(host_hdr); goto end; } host_hdr = oauth2_http_request_header_host_get(log, request); if (host_hdr) { port_str = strchr(host_hdr, _OAUTH2_CHAR_COLON); if (port_str) { port_str++; port_str = oauth2_strdup(port_str); } oauth2_mem_free(host_hdr); if (port_str) goto end; } proto_str = oauth2_http_request_header_x_forwarded_proto_get(log, request); if (proto_str) goto end; unsigned long port = request->port; scheme_str = oauth2_http_request_scheme_get(log, request); if (scheme_str) { if ((strcasecmp(scheme_str, OAUTH2_HTTP_SCHEME_HTTPS) == 0) && port == 443) goto end; else if ((strcasecmp(scheme_str, OAUTH2_HTTP_SCHEME_HTTP) == 0) && port == 80) goto end; } if (port > 0) { port_str = oauth2_mem_alloc(OAUTH2_PORT_STR_MAX); oauth2_snprintf(port_str, OAUTH2_PORT_STR_MAX, "%lu", port); } end: if (proto_str) oauth2_mem_free(proto_str); if (scheme_str) oauth2_mem_free(scheme_str); return port_str; } char *oauth2_http_request_hostname_get(oauth2_log_t *log, const oauth2_http_request_t *request) { char *host_str = NULL; if (request == NULL) goto end; host_str = oauth2_http_request_header_x_forwarded_host_get(log, request); if (host_str == NULL) host_str = oauth2_http_request_header_host_get(log, request); if (host_str) { char *p = strchr(host_str, _OAUTH2_CHAR_COLON); if (p != NULL) *p = '\0'; goto end; } if (request->hostname) host_str = oauth2_strdup(request->hostname); end: return host_str; } char *oauth2_http_request_url_base_get(oauth2_log_t *log, const oauth2_http_request_t *request) { // TODO: store static in request so this is evaluated only once for each // request // or do we want to allow dynamically inserted header evaluation? char *url = NULL, *host_str = NULL, *port_str = NULL; if (request == NULL) goto end; url = oauth2_http_request_scheme_get(log, request); if (url == NULL) goto end; host_str = oauth2_http_request_hostname_get(log, request); if (host_str == NULL) { oauth2_mem_free(url); url = NULL; goto end; } port_str = oauth2_http_request_port_get(log, request); url = _oauth2_stradd4(url, "://", host_str, port_str ? ":" : NULL, port_str); end: if (host_str) oauth2_mem_free(host_str); if (port_str) oauth2_mem_free(port_str); return url; } char *oauth2_http_request_url_path_get(oauth2_log_t *log, const oauth2_http_request_t *request) { char *url = NULL, *base_str = NULL, *path_str = NULL; base_str = oauth2_http_request_url_base_get(log, request); if (base_str == NULL) goto end; // TODO: in Apache r->path (or r->uri) can be absolute // for forwarding proxy setups; are we dealing with that? path_str = request->path ? request->path : ""; url = oauth2_stradd(NULL, base_str, path_str, NULL); end: oauth2_debug(log, "%s", url); if (base_str) oauth2_mem_free(base_str); return url; } char *oauth2_http_request_url_get(oauth2_log_t *log, const oauth2_http_request_t *request) { char *url = NULL, *url_path_str = NULL, *query_str = NULL, *sep = NULL; url_path_str = oauth2_http_request_url_path_get(log, request); if (url_path_str == NULL) goto end; // TODO: query_args_add http until function sep = (request->query && *request->query != '\0') ? _OAUTH2_STR_QMARK : ""; query_str = request->query ? request->query : ""; url = oauth2_stradd(NULL, url_path_str, sep, query_str); end: oauth2_debug(log, "%s", url); if (url_path_str) oauth2_mem_free(url_path_str); return url; } /* * oauth2_http_call_ctx_t */ typedef struct oauth2_http_call_ctx_t { char *basic_auth_username; char *basic_auth_password; char *bearer_token; int timeout; bool ssl_verify; char *outgoing_proxy; oauth2_nv_list_t *cookie; oauth2_nv_list_t *hdr; char *ca_info; char *ssl_cert; char *ssl_key; char *to_str; } oauth2_http_call_ctx_t; #define OAUTH2_HTTP_CALL_TIMEOUT_DEFAULT 15 #define OAUTH2_HTTP_CALL_SSL_VERIFY_DEFAULT true oauth2_http_call_ctx_t *oauth2_http_call_ctx_init(oauth2_log_t *log) { oauth2_http_call_ctx_t *ctx = NULL; ctx = oauth2_mem_alloc(sizeof(oauth2_http_call_ctx_t)); if (ctx == NULL) goto end; oauth2_http_call_ctx_timeout_set(log, ctx, OAUTH2_HTTP_CALL_TIMEOUT_DEFAULT); oauth2_http_call_ctx_ssl_verify_set( log, ctx, OAUTH2_HTTP_CALL_SSL_VERIFY_DEFAULT); oauth2_http_call_ctx_outgoing_proxy_set(log, ctx, NULL); oauth2_http_call_ctx_ca_info_set(log, ctx, NULL); oauth2_http_call_ctx_ssl_cert_set(log, ctx, NULL); oauth2_http_call_ctx_ssl_key_set(log, ctx, NULL); ctx->cookie = oauth2_nv_list_init(log); ctx->hdr = oauth2_nv_list_init(log); oauth2_nv_list_case_sensitive_set(log, ctx->hdr, false); ctx->to_str = NULL; end: return ctx; } void oauth2_http_call_ctx_free(oauth2_log_t *log, oauth2_http_call_ctx_t *ctx) { if (ctx == NULL) goto end; if (ctx->basic_auth_username) oauth2_mem_free(ctx->basic_auth_username); if (ctx->basic_auth_password) oauth2_mem_free(ctx->basic_auth_password); if (ctx->bearer_token) oauth2_mem_free(ctx->bearer_token); if (ctx->outgoing_proxy) oauth2_mem_free(ctx->outgoing_proxy); if (ctx->ca_info) oauth2_mem_free(ctx->ca_info); if (ctx->ssl_cert) oauth2_mem_free(ctx->ssl_cert); if (ctx->ssl_key) oauth2_mem_free(ctx->ssl_key); if (ctx->cookie) oauth2_nv_list_free(log, ctx->cookie); if (ctx->hdr) oauth2_nv_list_free(log, ctx->hdr); if (ctx->to_str) oauth2_mem_free(ctx->to_str); oauth2_mem_free(ctx); end: return; } _OAUTH2_TYPE_IMPLEMENT_MEMBER_SET(http, call_ctx, timeout, int, integer) _OAUTH2_TYPE_IMPLEMENT_MEMBER_SET(http, call_ctx, ssl_verify, bool, bln) _OAUTH2_TYPE_IMPLEMENT_MEMBER_SET(http, call_ctx, outgoing_proxy, char *, str) _OAUTH2_TYPE_IMPLEMENT_MEMBER_SET(http, call_ctx, ca_info, char *, str) _OAUTH2_TYPE_IMPLEMENT_MEMBER_SET(http, call_ctx, ssl_cert, char *, str) _OAUTH2_TYPE_IMPLEMENT_MEMBER_SET(http, call_ctx, ssl_key, char *, str) _OAUTH2_MEMBER_LIST_IMPLEMENT_SET_ADD_UNSET_GET(http, call_ctx, cookie); _OAUTH2_MEMBER_LIST_IMPLEMENT_SET_ADD_UNSET_GET(http, call_ctx, hdr); bool oauth2_http_call_ctx_content_type_set(oauth2_log_t *log, oauth2_http_call_ctx_t *ctx, const char *content_type) { return oauth2_http_call_ctx_hdr_set( log, ctx, OAUTH2_HTTP_HDR_CONTENT_TYPE, content_type); } bool oauth2_http_call_ctx_bearer_token_set(oauth2_log_t *log, oauth2_http_call_ctx_t *ctx, const char *token) { bool rc = false; char *str = NULL; if ((ctx == NULL) || (token == NULL)) goto end; str = oauth2_stradd(str, OAUTH2_HTTP_HDR_BEARER, " ", token); rc = oauth2_http_call_ctx_hdr_set(log, ctx, OAUTH2_HTTP_HDR_AUTHORIZATION, str); end: if (str) oauth2_mem_free(str); return rc; } bool oauth2_http_call_ctx_basic_auth_set(oauth2_log_t *log, oauth2_http_call_ctx_t *ctx, const char *username, const char *password, bool url_encode) { if (url_encode) { ctx->basic_auth_username = oauth2_url_encode(log, username); ctx->basic_auth_password = oauth2_url_encode(log, password); } else { ctx->basic_auth_username = oauth2_strdup(username); ctx->basic_auth_password = oauth2_strdup(password); } return true; } static char *_oauth2_http_call_ctx2s(oauth2_log_t *log, oauth2_http_call_ctx_t *ctx) { char *ptr = NULL; if (ctx == NULL) return NULL; if (ctx->to_str) oauth2_mem_free(ctx->to_str); ctx->to_str = oauth2_strdup("["); ctx->to_str = oauth2_stradd(ctx->to_str, " ssl_verify", _OAUTH2_STR_EQUAL, ctx->ssl_verify ? "true" : "false"); if (ctx->basic_auth_username) ctx->to_str = oauth2_stradd(ctx->to_str, " basic_auth_username", _OAUTH2_STR_EQUAL, ctx->basic_auth_username); if (ctx->basic_auth_password) ctx->to_str = oauth2_stradd(ctx->to_str, " basic_auth_password", _OAUTH2_STR_EQUAL, ctx->basic_auth_password); if (ctx->outgoing_proxy) ctx->to_str = oauth2_stradd(ctx->to_str, " outgoing_proxy", _OAUTH2_STR_EQUAL, ctx->outgoing_proxy); if (ctx->ca_info) ctx->to_str = oauth2_stradd(ctx->to_str, " ca_info", _OAUTH2_STR_EQUAL, ctx->ca_info); if (ctx->ssl_cert) ctx->to_str = oauth2_stradd(ctx->to_str, " ssl_cert", _OAUTH2_STR_EQUAL, ctx->ssl_cert); if (ctx->ssl_key) ctx->to_str = oauth2_stradd(ctx->to_str, " ssl_key", _OAUTH2_STR_EQUAL, ctx->ssl_key); ptr = oauth2_nv_list2s(log, ctx->hdr); if (ptr) { ctx->to_str = oauth2_stradd(ctx->to_str, " hdr", _OAUTH2_STR_EQUAL, ptr); oauth2_mem_free(ptr); } ptr = oauth2_nv_list2s(log, ctx->cookie); if (ptr) { ctx->to_str = oauth2_stradd(ctx->to_str, " cookie", _OAUTH2_STR_EQUAL, ptr); oauth2_mem_free(ptr); } ctx->to_str = oauth2_stradd(ctx->to_str, " ]", NULL, NULL); return ctx->to_str; } /* * encoding */ typedef struct _oauth2_http_encode_str_t { const char *sep; char **str; } _oauth2_http_encode_str_t; static bool _oauth2_http_url_encode_list(oauth2_log_t *log, void *rec, const char *key, const char *value) { bool rc = false; char *enc_key = NULL, *enc_val = NULL; _oauth2_http_encode_str_t *state = (_oauth2_http_encode_str_t *)rec; if ((state->str == NULL) || (key == NULL)) goto end; oauth2_debug(log, "processing: %s=%s", key, value); enc_key = oauth2_url_encode(log, key); enc_val = oauth2_url_encode(log, value); *state->str = _oauth2_stradd4(*state->str, *state->str ? state->sep : "", enc_key, _OAUTH2_STR_EQUAL, enc_val); rc = true; end: if (enc_key) oauth2_mem_free(enc_key); if (enc_val) oauth2_mem_free(enc_val); return rc; } static bool _oauth2_http_url_query_encode_param(oauth2_log_t *log, void *rec, const char *key, const char *value) { _oauth2_http_encode_str_t encode_str = {_OAUTH2_STR_AMP, (char **)rec}; return _oauth2_http_url_encode_list(log, &encode_str, key, value); } static bool _oauth2_http_url_encode_cookie(oauth2_log_t *log, void *rec, const char *key, const char *value) { _oauth2_http_encode_str_t encode_str = {_OAUTH2_STR_SEMICOL " ", (char **)rec}; return _oauth2_http_url_encode_list(log, &encode_str, key, value); } static char *_oauth2_http_cookies_encode(oauth2_log_t *log, oauth2_nv_list_t *cookies) { char *str = NULL; oauth2_nv_list_loop(log, cookies, _oauth2_http_url_encode_cookie, &str); return str; } char *oauth2_http_url_query_encode(oauth2_log_t *log, const char *url, const oauth2_nv_list_t *params) { char *result = NULL; const char *sep = NULL; char *encode_str = NULL; oauth2_nv_list_loop(log, params, _oauth2_http_url_query_encode_param, &encode_str); if (url && encode_str) sep = strrchr(url, _OAUTH2_CHAR_QUERY) != NULL ? _OAUTH2_STR_AMP : _OAUTH2_STR_QMARK; result = oauth2_stradd(result, url, sep, encode_str); oauth2_debug(log, "result=%s", result); if (encode_str) oauth2_mem_free(encode_str); return result; } char *oauth2_http_url_form_encode(oauth2_log_t *log, const oauth2_nv_list_t *args) { char *encode_str = NULL; oauth2_nv_list_loop(log, args, _oauth2_http_url_query_encode_param, &encode_str); oauth2_debug(log, "data=%s", encode_str); return encode_str; } /* * curl */ typedef struct oauth2_http_curl_buf_t { oauth2_log_t *log; char *memory; size_t size; } oauth2_http_curl_buf_t; #define _OAUTH2_HTTP_CURL_BUF_MAX 1024 * 1024 static size_t oauth2_http_curl_buf_write(void *contents, size_t size, size_t nmemb, void *userp) { size_t realsize = size * nmemb, rc = 0; oauth2_http_curl_buf_t *mem = (oauth2_http_curl_buf_t *)userp; if (mem->size + realsize > _OAUTH2_HTTP_CURL_BUF_MAX) { oauth2_error(mem->log, "HTTP response larger than maximum allowed " "size: current size=%ld, additional " "size=%ld, max=%d", mem->size, realsize, _OAUTH2_HTTP_CURL_BUF_MAX); goto end; } char *newptr = oauth2_mem_alloc(mem->size + realsize + 1); if (newptr == NULL) { oauth2_error( mem->log, "memory allocation for new buffer of %ld bytes failed", mem->size + realsize + 1); goto end; } memcpy(newptr, mem->memory, mem->size); memcpy(&(newptr[mem->size]), contents, realsize); mem->size += realsize; oauth2_mem_free(mem->memory); mem->memory = newptr; mem->memory[mem->size] = 0; rc = realsize; end: return rc; } static bool _oauth2_http_curl_header_add(oauth2_log_t *log, void *rec, const char *key, const char *value) { bool rc = false; char *str = NULL; struct curl_slist **h_list = (struct curl_slist **)rec; if ((h_list == NULL) || (key == NULL)) goto end; str = _oauth2_stradd4(NULL, key, _OAUTH2_STR_COLON, " ", value); if (str == NULL) goto end; *h_list = curl_slist_append(*h_list, str); rc = true; end: if (str) oauth2_mem_free(str); return rc; } bool oauth2_http_call(oauth2_log_t *log, const char *url, const char *data, oauth2_http_call_ctx_t *ctx, char **response, oauth2_http_status_code_t *status_code) { bool rc = false; char *str = NULL; long response_code = 0; char err[CURL_ERROR_SIZE]; CURL *curl = NULL; CURLcode errornum = CURLE_OK; struct curl_slist *h_list = NULL; oauth2_http_curl_buf_t buf; buf.log = log; buf.memory = NULL; buf.size = 0; oauth2_debug(log, "enter: url=%s, data=%s, ctx=%s", url, data ? data : "(null)", _oauth2_http_call_ctx2s(log, ctx)); if ((url == NULL) || (response == NULL)) goto end; // TODO: this is somewhat shared (at least the initialization of // globals) with url-encode/url-decode?? curl = curl_easy_init(); if (curl == NULL) { oauth2_error(log, "curl_easy_init() error"); goto end; } err[0] = 0; 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, err); curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L); curl_easy_setopt(curl, CURLOPT_MAXREDIRS, 5L); if (ctx) curl_easy_setopt(curl, CURLOPT_TIMEOUT, ctx->timeout); curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, oauth2_http_curl_buf_write); curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *)&buf); #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,file"); #else curl_easy_setopt(curl, CURLOPT_REDIR_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS); curl_easy_setopt(curl, CURLOPT_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS | CURLPROTO_FILE); #endif #endif if (ctx) { curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, (ctx->ssl_verify != false ? 1L : 0L)); curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, (ctx->ssl_verify != false ? 2L : 0L)); } if (ctx && (ctx->ca_info)) { curl_easy_setopt(curl, CURLOPT_CAINFO, ctx->ca_info); } else { #ifdef WIN32 DWORD buflen; char *ptr = NULL; char *retval = oauth2_mem_alloc(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 oauth2_warn(log, "no curl-ca-bundle.crt file found in path"); oauth2_mem_free(retval); #endif } curl_easy_setopt(curl, CURLOPT_USERAGENT, oauth2_package_string()); if (ctx && ctx->outgoing_proxy) curl_easy_setopt(curl, CURLOPT_PROXY, ctx->outgoing_proxy); if (ctx && (ctx->basic_auth_username || ctx->basic_auth_password)) { curl_easy_setopt(curl, CURLOPT_HTTPAUTH, CURLAUTH_BASIC); if (ctx->basic_auth_username) curl_easy_setopt(curl, CURLOPT_USERNAME, ctx->basic_auth_username); if (ctx->basic_auth_password) curl_easy_setopt(curl, CURLOPT_PASSWORD, ctx->basic_auth_password); } if (ctx) { if (ctx->ssl_cert != NULL) curl_easy_setopt(curl, CURLOPT_SSLCERT, ctx->ssl_cert); if (ctx->ssl_key != NULL) curl_easy_setopt(curl, CURLOPT_SSLKEY, ctx->ssl_key); } if (data != NULL) { curl_easy_setopt(curl, CURLOPT_POSTFIELDS, data); curl_easy_setopt(curl, CURLOPT_POST, 1); } if (ctx) oauth2_nv_list_loop(log, ctx->hdr, _oauth2_http_curl_header_add, &h_list); if (h_list != NULL) curl_easy_setopt(curl, CURLOPT_HTTPHEADER, h_list); if (ctx) str = _oauth2_http_cookies_encode(log, ctx->cookie); if (str) { oauth2_debug(log, "passing browser cookies on backend call: %s", str); curl_easy_setopt(curl, CURLOPT_COOKIE, str); oauth2_mem_free(str); str = NULL; } curl_easy_setopt(curl, CURLOPT_URL, url); errornum = curl_easy_perform(curl); if (errornum != CURLE_OK) { oauth2_error(log, "curl_easy_perform() failed on: %s (%s: %s)", url, curl_easy_strerror(errornum), err[0] ? err : ""); if (errornum == CURLE_OPERATION_TIMEDOUT) // 408 Request Timeout // 504 Gateway Timeout *status_code = 504; goto end; } curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &response_code); oauth2_debug(log, "HTTP response code=%ld", response_code); if (status_code) *status_code = (oauth2_uint_t)response_code; *response = oauth2_mem_alloc(buf.size + 1); strncpy(*response, buf.memory, buf.size); (*response)[buf.size] = '\0'; rc = true; end: if (buf.memory) oauth2_mem_free(buf.memory); if (h_list != NULL) curl_slist_free_all(h_list); curl_easy_cleanup(curl); oauth2_debug(log, "leave [%d]: %s", rc, (response && *response) ? *response : "(null)"); return rc; } bool oauth2_http_get(oauth2_log_t *log, const char *url, const oauth2_nv_list_t *params, oauth2_http_call_ctx_t *ctx, char **response, oauth2_http_status_code_t *status_code) { bool rc = false; char *query_url = NULL; oauth2_debug(log, "enter: %s", url); query_url = oauth2_http_url_query_encode(log, url, params); rc = oauth2_http_call(log, query_url, NULL, ctx, response, status_code); if (query_url) oauth2_mem_free(query_url); oauth2_debug(log, "leave: %d", rc); return rc; } bool oauth2_http_post_form(oauth2_log_t *log, const char *url, const oauth2_nv_list_t *params, oauth2_http_call_ctx_t *ctx, char **response, oauth2_http_status_code_t *status_code) { bool rc = false; char *data = NULL; data = oauth2_http_url_form_encode(log, params); oauth2_http_call_ctx_content_type_set(log, ctx, OAUTH2_CONTENT_TYPE_FORM_ENCODED); rc = oauth2_http_call(log, url, data, ctx, response, status_code); if (data) oauth2_mem_free(data); return rc; } bool oauth2_http_post_json(oauth2_log_t *log, const char *url, const json_t *json, oauth2_http_call_ctx_t *ctx, char **response, oauth2_http_status_code_t *status_code) { bool rc = false; char *json_str = NULL; if (json) json_str = json_dumps(json, JSON_PRESERVE_ORDER | JSON_COMPACT); oauth2_http_call_ctx_content_type_set(log, ctx, OAUTH2_CONTENT_TYPE_JSON); rc = oauth2_http_call(log, url, json_str, ctx, response, status_code); if (json_str) oauth2_mem_free(json_str); return rc; } /* * query */ static bool _oauth2_http_request_query_parse(oauth2_log_t *log, oauth2_http_request_t *request) { bool rc = false; if (request == NULL) goto end; if (request->_parsed_query != NULL) { rc = true; goto end; } request->_parsed_query = oauth2_nv_list_init(log); if (request->_parsed_query == NULL) goto end; rc = _oauth2_nv_list_parse(log, request->query, request->_parsed_query, _OAUTH2_CHAR_AMP, _OAUTH2_CHAR_EQUAL, true, false); end: return rc; } bool oauth2_http_request_query_param_add(oauth2_log_t *log, oauth2_http_request_t *request, const char *name, const char *value) { bool rc = false; char *query_str = NULL; oauth2_debug(log, "enter: %s=%s", name, value); if ((request == NULL) || (name == NULL)) goto end; if (_oauth2_http_request_query_parse(log, request) == false) goto end; if (oauth2_nv_list_add(log, request->_parsed_query, name, value) == false) goto end; query_str = oauth2_http_url_query_encode(log, NULL, request->_parsed_query); rc = oauth2_http_request_query_set(log, request, query_str); end: if (query_str) oauth2_mem_free(query_str); oauth2_debug(log, "leave (%d)", rc); return rc; } const char *oauth2_http_request_query_param_get(oauth2_log_t *log, oauth2_http_request_t *request, const char *name) { const char *value = NULL; oauth2_debug(log, "enter: %s", name); if ((request == NULL) || (name == NULL)) goto end; if (_oauth2_http_request_query_parse(log, request) == false) goto end; value = oauth2_nv_list_get(log, request->_parsed_query, name); end: oauth2_debug(log, "leave: %s=%s", name, value ? value : "(null)"); return value; } bool oauth2_http_request_query_param_unset(oauth2_log_t *log, oauth2_http_request_t *request, const char *name) { bool rc = false; char *query_str = NULL; oauth2_debug(log, "enter: %s", name); if ((request == NULL) || (name == NULL)) goto end; if (_oauth2_http_request_query_parse(log, request) == false) { oauth2_error(log, "_oauth2_http_request_query_parse failed"); goto end; } if (oauth2_nv_list_unset(log, request->_parsed_query, name) == false) { oauth2_error(log, "oauth2_nv_list_unset failed"); goto end; } query_str = oauth2_http_url_query_encode(log, NULL, request->_parsed_query); rc = oauth2_http_request_query_set(log, request, query_str); end: if (query_str) oauth2_mem_free(query_str); oauth2_debug(log, "leave: rc=%d", rc); return rc; } /* * cookies */ static bool _oauth2_http_request_get_parsed_cookies(oauth2_log_t *log, oauth2_http_request_t *request) { bool rc = false; const char *cookies = NULL; if (request == NULL) goto end; if (request->_parsed_cookies != NULL) { rc = true; goto end; } request->_parsed_cookies = oauth2_nv_list_init(log); if (request->_parsed_cookies == NULL) goto end; cookies = oauth2_http_request_header_cookie_get(log, request); if (cookies == NULL) { rc = true; goto end; } rc = _oauth2_nv_list_parse(log, cookies, request->_parsed_cookies, _OAUTH2_CHAR_SEMICOL, _OAUTH2_CHAR_EQUAL, true, true); end: return rc; } static bool _oauth2_http_request_set_parsed_cookies_in_header( oauth2_log_t *log, oauth2_http_request_t *request) { char *cookies = NULL; oauth2_debug(log, "enter"); cookies = _oauth2_http_cookies_encode(log, request->_parsed_cookies); if (cookies == NULL) goto end; _oauth2_http_request_header_cookie_set(log, request, cookies); end: oauth2_debug(log, "leave: %s", cookies); if (cookies) oauth2_mem_free(cookies); return true; } char *oauth2_http_request_cookie_get(oauth2_log_t *log, oauth2_http_request_t *request, const char *name, bool strip) { char *rv = NULL; const char *value = NULL; oauth2_debug(log, "enter: %s", name); if ((request == NULL) || (name == NULL)) goto end; if (_oauth2_http_request_get_parsed_cookies(log, request) == false) goto end; value = oauth2_nv_list_get(log, request->_parsed_cookies, name); if (value == NULL) goto end; rv = oauth2_strdup(value); if (strip == false) goto end; oauth2_nv_list_unset(log, request->_parsed_cookies, name); _oauth2_http_request_set_parsed_cookies_in_header(log, request); end: oauth2_debug(log, "leave: %s=%s", name, rv ? rv : "(null)"); return rv; } bool oauth2_http_request_cookie_set(oauth2_log_t *log, oauth2_http_request_t *request, const char *name, const char *value) { bool rc = false; oauth2_debug(log, "enter: %s=%s", name, value); if ((request == NULL) || (name == NULL)) goto end; if (_oauth2_http_request_get_parsed_cookies(log, request) == false) goto end; rc = oauth2_nv_list_set(log, request->_parsed_cookies, name, value); if (rc == false) goto end; rc = _oauth2_http_request_set_parsed_cookies_in_header(log, request); end: oauth2_debug(log, "leave (%d)", rc); return rc; } /* * authentication */ bool oauth2_http_auth_client_cert(oauth2_log_t *log, const char *ssl_cert, const char *ssl_key, oauth2_http_call_ctx_t *ctx) { bool rc = false; if ((ssl_cert == NULL) || (ssl_key == NULL)) goto end; rc = oauth2_http_call_ctx_ssl_cert_set(log, ctx, ssl_cert); if (rc == false) goto end; rc = oauth2_http_call_ctx_ssl_key_set(log, ctx, ssl_key); end: return rc; } bool oauth2_http_auth_basic(oauth2_log_t *log, const char *username, const char *passwd, oauth2_http_call_ctx_t *ctx) { return oauth2_http_call_ctx_basic_auth_set(log, ctx, username, passwd, false); } static bool oauth2_http_request_header_contains( oauth2_log_t *log, const oauth2_http_request_t *request, const char *name, char sepchar, const char *needle) { bool rc = false; char *save_input = NULL, *val = NULL; const char *value = NULL, *p = NULL; if (name == NULL) goto end; value = oauth2_http_request_header_get(log, request, name); if (value == NULL) goto end; save_input = oauth2_strdup(value); p = save_input; while (p && *p) { val = oauth2_getword(&p, sepchar); if (val == NULL) break; rc = (strncasecmp(val, needle, strlen(needle)) == 0); oauth2_mem_free(val); if (rc == true) break; } end: if (save_input) oauth2_mem_free(save_input); return rc; } bool oauth2_http_request_is_xml_http_request( oauth2_log_t *log, const oauth2_http_request_t *request) { bool rc = false; oauth2_debug(log, "enter"); if ((oauth2_http_request_header_x_requested_with_get(log, request) != NULL) && (strcasecmp( oauth2_http_request_header_x_requested_with_get(log, request), OAUTH2_HTTP_HDR_XML_HTTP_REQUEST) == 0)) { rc = true; goto end; } if ((oauth2_http_request_header_contains( log, request, OAUTH2_HTTP_HDR_ACCEPT, _OAUTH2_CHAR_COMMA, OAUTH2_CONTENT_TYPE_TEXT_HTML) == false) && (oauth2_http_request_header_contains( log, request, OAUTH2_HTTP_HDR_ACCEPT, _OAUTH2_CHAR_COMMA, OAUTH2_CONTENT_TYPE_APP_XHTML_XML) == false) && (oauth2_http_request_header_contains( log, request, OAUTH2_HTTP_HDR_ACCEPT, _OAUTH2_CHAR_COMMA, OAUTH2_CONTENT_TYPE_ANY) == false)) { rc = true; goto end; } end: oauth2_debug(log, "return: %d", rc); return rc; } bool oauth2_http_request_is_secure(oauth2_log_t *log, const oauth2_http_request_t *request) { bool rc = false; char *scheme = oauth2_http_request_scheme_get(log, request); rc = (strcasecmp(scheme, "https") == 0); oauth2_mem_free(scheme); return rc; } typedef struct oauth2_http_response_t { oauth2_nv_list_t *headers; oauth2_http_status_code_t status_code; } oauth2_http_response_t; oauth2_http_response_t *oauth2_http_response_init(oauth2_log_t *log) { oauth2_http_response_t *response = NULL; response = oauth2_mem_alloc(sizeof(oauth2_http_response_t)); if (response == NULL) goto end; response->headers = oauth2_nv_list_init(log); response->status_code = 0; end: return response; } oauth2_http_response_t * oauth2_http_response_clone(oauth2_log_t *log, const oauth2_http_response_t *src) { oauth2_http_response_t *dst = oauth2_http_response_init(log); dst->headers = oauth2_nv_list_clone(log, src->headers); dst->status_code = src->status_code; return dst; } void oauth2_http_response_free(oauth2_log_t *log, oauth2_http_response_t *response) { if (response == NULL) goto end; if (response->headers) oauth2_nv_list_free(log, response->headers); oauth2_mem_free(response); end: return; } bool oauth2_http_response_headers_set(oauth2_log_t *log, oauth2_http_response_t *response, const oauth2_nv_list_t *hdrs) { return false; } oauth2_nv_list_t * oauth2_http_response_headers_get(oauth2_log_t *log, const oauth2_http_response_t *response) { return response->headers; } /* bool oauth2_http_response_status_code_set(oauth2_log_t *, oauth2_http_response_t *, const oauth2_http_status_code_t) { return false; } oauth2_http_status_code_t oauth2_http_response_status_code_get(oauth2_log_t *, const oauth2_http_response_t *) { return 0; } */ _OAUTH2_TYPE_IMPLEMENT_MEMBER_SET_GET(http, response, status_code, oauth2_http_status_code_t, uint) bool oauth2_http_response_header_set(oauth2_log_t *log, oauth2_http_response_t *response, const char *name, const char *value) { return oauth2_nv_list_set(log, response->headers, name, value); } static bool oauth2_http_response_header_add(oauth2_log_t *log, oauth2_http_response_t *response, const char *name, const char *value) { return oauth2_nv_list_add(log, response->headers, name, value); } const char *oauth2_http_response_header_get( oauth2_log_t *log, const oauth2_http_response_t *response, const char *name) { return oauth2_nv_list_get(log, response->headers, name); } typedef struct _oauth2_http_response_header_set_cookie_prefix_match_t { const char *prefix; const char *result; } _oauth2_http_response_header_set_cookie_prefix_match_t; static bool _oauth2_http_response_header_set_cookie_prefix_match( oauth2_log_t *log, void *rec, const char *name, const char *value) { bool rc = true; _oauth2_http_response_header_set_cookie_prefix_match_t *ctx = (_oauth2_http_response_header_set_cookie_prefix_match_t *)rec; if (strcasecmp(OAUTH2_HTTP_HDR_SET_COOKIE, name) == 0) { oauth2_debug(log, "matching: value=%s prefix=%s", value, ctx->prefix); if (strstr(value, ctx->prefix) == value) { ctx->result = value; rc = false; } } return rc; } const char *oauth2_http_response_header_set_cookie_prefix_get( oauth2_log_t *log, oauth2_http_response_t *response, const char *prefix) { _oauth2_http_response_header_set_cookie_prefix_match_t ctx; ctx.prefix = prefix; ctx.result = NULL; oauth2_http_response_headers_loop( log, response, _oauth2_http_response_header_set_cookie_prefix_match, (void *)&ctx); oauth2_debug(log, "on search for %s, return: %s", prefix, ctx.result); return ctx.result; } #define OAUTH2_HTTP_COOKIE_MAX_AGE_LENGTH_MAX 64 bool oauth2_http_response_cookie_set(oauth2_log_t *log, oauth2_http_response_t *response, const char *name, const char *value, const char *path, const bool is_secure, oauth2_time_t max_age) { bool rc = false; char *str = NULL; oauth2_nv_list_t *cookies = NULL; char maxagestr[OAUTH2_HTTP_COOKIE_MAX_AGE_LENGTH_MAX]; if (value) { cookies = oauth2_nv_list_init(log); oauth2_nv_list_set(log, cookies, name, value); str = _oauth2_http_cookies_encode(log, cookies); if (str == NULL) goto end; } else { str = oauth2_stradd( NULL, name, "=;", " Expires=Thu, 01 Jan 1970 00:00:00 GMT; Max-Age=0"); } if (path) str = oauth2_stradd(str, "; path", "=", path); if ((value != NULL) && (max_age != OAUTH2_CFG_TIME_UNSET)) { oauth2_snprintf(maxagestr, OAUTH2_HTTP_COOKIE_MAX_AGE_LENGTH_MAX, OAUTH2_TIME_T_FORMAT, max_age); str = oauth2_stradd(str, "; Max-Age", "=", maxagestr); } if (is_secure) str = oauth2_stradd(str, "; HttpOnly", "; Secure", "; SameSite=None"); rc = oauth2_http_response_header_add(log, response, OAUTH2_HTTP_HDR_SET_COOKIE, str); end: if (cookies) oauth2_nv_list_free(log, cookies); if (str) oauth2_mem_free(str); return rc; } void oauth2_http_response_headers_loop(oauth2_log_t *log, const oauth2_http_response_t *response, oauth2_nv_list_loop_cb_t *callback, void *rec) { oauth2_nv_list_loop(log, response->headers, callback, rec); } liboauth2-2.1.0/src/ipc.c000066400000000000000000000152731475305260400151270ustar00rootroot00000000000000/*************************************************************************** * * Copyright (C) 2018-2025 - ZmartZone Holding BV * * 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. * * @Author: Hans Zandbelt - hans.zandbelt@openidc.com * **************************************************************************/ #include #ifndef _WIN32 #include #include #else #include "mmap-windows.c" #ifdef _MSC_VER #define _unlink unlink #endif #endif #include #include #include #include #include "oauth2/ipc.h" #include "oauth2/mem.h" #include "oauth2/util.h" #include "oauth2/version.h" #include "util_int.h" // from the sema.h docs #define _OAUTH2_IPC_NAME_MAX 63 static char *_oauth2_ipc_get_name(oauth2_log_t *log, const char *type, void *ptr) { char *rv = NULL; rv = oauth2_mem_alloc(_OAUTH2_IPC_NAME_MAX); oauth2_snprintf(rv, _OAUTH2_IPC_NAME_MAX, "/zzo-%s-%ld.%p", type, (long int)getpid(), ptr ? ptr : 0); return rv; } /* * semaphore */ typedef struct oauth2_ipc_sema_t { char *name; sem_t *sema; } oauth2_ipc_sema_t; oauth2_ipc_sema_t *oauth2_ipc_sema_init(oauth2_log_t *log) { oauth2_ipc_sema_t *s = oauth2_mem_alloc(sizeof(oauth2_ipc_sema_t)); if (s) { s->sema = NULL; } return s; } void oauth2_ipc_sema_free(oauth2_log_t *log, oauth2_ipc_sema_t *s) { if (s == NULL) goto end; if (s->sema != NULL) { if (sem_close(s->sema) != 0) oauth2_error(log, "sem_close() failed: %s ", strerror(errno)); s->sema = NULL; } if (s->name) oauth2_mem_free(s->name); oauth2_mem_free(s); end: return; } bool oauth2_ipc_sema_post_config(oauth2_log_t *log, oauth2_ipc_sema_t *sema) { bool rc = false; if (sema == NULL) goto end; if (sema->name) { oauth2_mem_free(sema->name); sema->name = NULL; } sema->name = _oauth2_ipc_get_name(log, "sema", sema); if (sema->name == NULL) goto end; sema->sema = sem_open(sema->name, O_CREAT, 0644, 0); if (sema->sema == SEM_FAILED) { oauth2_error( log, "sem_open() failed to create named semaphore %s: %s (%d)", sema->name, strerror(errno), errno); sema->sema = NULL; goto end; } if (sem_unlink(sema->name) != 0) oauth2_error(log, "sem_unlink() failed: %s ", strerror(errno)); rc = true; end: return rc; } bool oauth2_ipc_sema_post(oauth2_log_t *log, oauth2_ipc_sema_t *sema) { bool rc = false; int rv = 0; if ((sema == NULL) || (sema->sema == NULL)) goto end; rv = sem_post(sema->sema); if (rv != 0) { oauth2_error(log, "sem_post() failed: %s (%d)", strerror(errno), errno); goto end; } rc = true; end: return rc; } bool oauth2_ipc_sema_wait(oauth2_log_t *log, oauth2_ipc_sema_t *sema) { bool rc = true; int rv = 0; if ((sema == NULL) || (sema->sema == NULL)) goto end; rv = sem_wait(sema->sema); if (rv != 0) { oauth2_error(log, "sem_wait() failed: %s (%d)", strerror(errno), errno); goto end; } rc = true; end: return rc; } bool oauth2_ipc_sema_trywait(oauth2_log_t *log, oauth2_ipc_sema_t *sema) { bool rc = true; int rv = 0; if ((sema == NULL) || (sema->sema == NULL)) goto end; rv = sem_trywait(sema->sema); if (rv != 0) { if (errno == EAGAIN) rc = false; else oauth2_error(log, "sem_trywait() failed: %s (%d)", strerror(errno), errno); } end: return rc; } /* * mutex */ typedef struct oauth2_ipc_mutex_t { oauth2_ipc_sema_t *mutex; } oauth2_ipc_mutex_t; oauth2_ipc_mutex_t *oauth2_ipc_mutex_init(oauth2_log_t *log) { oauth2_ipc_mutex_t *m = oauth2_mem_alloc(sizeof(oauth2_ipc_mutex_t)); if (m) { m->mutex = oauth2_ipc_sema_init(log); } return m; } void oauth2_ipc_mutex_free(oauth2_log_t *log, oauth2_ipc_mutex_t *m) { if ((m == NULL) && (m->mutex == NULL)) goto end; oauth2_ipc_sema_free(log, m->mutex); m->mutex = NULL; oauth2_mem_free(m); end: return; } bool oauth2_ipc_mutex_post_config(oauth2_log_t *log, oauth2_ipc_mutex_t *m) { bool rc = false; if ((m == NULL) || (m->mutex == NULL)) goto end; rc = oauth2_ipc_sema_post_config(log, m->mutex); if (rc == false) goto end; rc = oauth2_ipc_sema_post(log, m->mutex); end: return rc; } bool oauth2_ipc_mutex_lock(oauth2_log_t *log, oauth2_ipc_mutex_t *m) { bool rc = false; if ((m == NULL) || (m->mutex == NULL)) goto end; rc = oauth2_ipc_sema_wait(log, m->mutex); end: return rc; } bool oauth2_ipc_mutex_unlock(oauth2_log_t *log, oauth2_ipc_mutex_t *m) { bool rc = false; if ((m == NULL) || (m->mutex == NULL)) goto end; rc = oauth2_ipc_sema_post(log, m->mutex); end: return rc; } /* * shared memory */ typedef struct oauth2_ipc_shm_t { oauth2_ipc_mutex_t *mutex; oauth2_ipc_sema_t *num; size_t size; void *ptr; } oauth2_ipc_shm_t; oauth2_ipc_shm_t *oauth2_ipc_shm_init(oauth2_log_t *log, size_t size) { oauth2_ipc_shm_t *shm = oauth2_mem_alloc(sizeof(oauth2_ipc_shm_t)); shm->mutex = oauth2_ipc_mutex_init(log); shm->num = oauth2_ipc_sema_init(log); shm->ptr = NULL; shm->size = size; return shm; } void oauth2_ipc_shm_free(oauth2_log_t *log, oauth2_ipc_shm_t *shm) { if (shm == NULL) goto end; if (shm->mutex) oauth2_ipc_mutex_free(log, shm->mutex); shm->mutex = NULL; if (shm->ptr) { if (munmap(shm->ptr, shm->size) < 0) oauth2_error(log, "munmap() failed: %s", strerror(errno)); shm->ptr = NULL; } if (shm->num) { oauth2_ipc_sema_free(log, shm->num); shm->num = NULL; } oauth2_mem_free(shm); end: return; } bool oauth2_ipc_shm_post_config(oauth2_log_t *log, oauth2_ipc_shm_t *shm) { bool rc = false; if (shm == NULL) goto end; rc = oauth2_ipc_sema_post_config(log, shm->num); if (rc == false) goto end; rc = oauth2_ipc_mutex_post_config(log, shm->mutex); if (rc == false) goto end; oauth2_debug(log, "creating anonymous shm"); shm->ptr = mmap(0, shm->size, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0); if (shm->ptr == MAP_FAILED) { oauth2_error(log, "mmap() failed: %s", strerror(errno)); goto end; } rc = oauth2_ipc_sema_post(log, shm->num); end: return rc; } bool oauth2_ipc_shm_child_init(oauth2_log_t *log, oauth2_ipc_shm_t *shm) { return shm ? oauth2_ipc_sema_post(log, shm->num) : false; } void *oauth2_ipc_shm_get(oauth2_log_t *log, oauth2_ipc_shm_t *shm) { return shm ? shm->ptr : NULL; } liboauth2-2.1.0/src/jose.c000066400000000000000000001634241475305260400153160ustar00rootroot00000000000000/*************************************************************************** * * Copyright (C) 2018-2025 - ZmartZone Holding BV * * 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. * * @Author: Hans Zandbelt - hans.zandbelt@openidc.com * **************************************************************************/ #include "oauth2/jose.h" #include "oauth2/http.h" #include "oauth2/mem.h" #include "oauth2/util.h" #include "cjose/cjose.h" #include "cache_int.h" #include "jose_int.h" #include "util_int.h" #include #include #include #include #include #if OPENSSL_VERSION_NUMBER >= 0x30000000L #include #include #endif #define _OAUTH2_JOSE_OPENSSL_ERR_LOG(log, function) \ oauth2_error(log, "%s failed: %s", function, \ ERR_error_string(ERR_get_error(), NULL)) #define _OAUTH2_JOSE_JANSSON_ERR_LOG(log, msg, json_err) \ oauth2_error(log, "%s failed: %s", msg, json_err.text) #if (OPENSSL_VERSION_NUMBER < 0x10100000) || defined(LIBRESSL_VERSION_NUMBER) EVP_MD_CTX *EVP_MD_CTX_new() { return oauth2_mem_alloc(sizeof(EVP_MD_CTX)); } void EVP_MD_CTX_free(EVP_MD_CTX *ctx) { if (ctx) oauth2_mem_free(ctx); } #endif static oauth2_jose_jwk_t *oauth2_jose_jwk_new() { oauth2_jose_jwk_t *jwk = oauth2_mem_alloc(sizeof(oauth2_jose_jwk_t)); jwk->jwk = NULL; jwk->kid = NULL; return jwk; } static oauth2_jose_jwk_t *oauth2_jose_jwk_oct_new(oauth2_log_t *log, unsigned char *key, unsigned int key_len) { oauth2_jose_jwk_t *rv = NULL; cjose_err err; cjose_jwk_t *c_jwk = NULL; c_jwk = cjose_jwk_create_oct_spec(key, key_len, &err); if (c_jwk == NULL) { _OAUTH2_UTIL_JOSE_ERR_LOG(log, "cjose_jwk_create_oct_spec", err); goto end; } oauth2_trace1(log, "jwk oct spec created", key_len); rv = oauth2_jose_jwk_new(); if (rv == NULL) goto end; rv->jwk = c_jwk; end: return rv; } void oauth2_jose_jwk_release(oauth2_jose_jwk_t *jwk) { if (jwk->jwk) { cjose_jwk_release(jwk->jwk); jwk->jwk = NULL; } if (jwk->kid) { oauth2_mem_free(jwk->kid); jwk->kid = NULL; } oauth2_mem_free(jwk); } bool oauth2_jose_hash_bytes(oauth2_log_t *log, const char *digest, const unsigned char *src, unsigned int src_len, unsigned char **dst, unsigned int *dst_len) { const EVP_MD *evp_digest = NULL; EVP_MD_CTX *ctx = NULL; unsigned char md_value[EVP_MAX_MD_SIZE]; bool rc = false; oauth2_debug(log, "enter"); if ((dst == NULL) || (dst_len == NULL)) goto end; if ((src == NULL) || (src_len == 0)) { oauth2_warn(log, "cannot hash empty string"); goto end; } ctx = EVP_MD_CTX_new(); if (ctx == NULL) { _OAUTH2_JOSE_OPENSSL_ERR_LOG(log, "EVP_MD_CTX_new"); goto end; } EVP_MD_CTX_init(ctx); if ((evp_digest = EVP_get_digestbyname(digest)) == NULL) { // hack away for el7/x86 where Apache is compiled against // OpenSSL 1.0.2 but NGINX 1.20.1 against OpenSSL 1.1.0 if (strcmp(digest, "sha256") == 0) { oauth2_debug(log, "try to directly set EVP_sha256"); evp_digest = EVP_sha256(); } if (evp_digest == NULL) { oauth2_error(log, "no OpenSSL digest algorithm found for " "algorithm \"%s\"", digest); goto end; } } if (!EVP_DigestInit_ex(ctx, evp_digest, NULL)) goto end; if (!EVP_DigestUpdate(ctx, src, src_len)) goto end; if (!EVP_DigestFinal(ctx, md_value, dst_len)) goto end; *dst = oauth2_mem_alloc((size_t)*dst_len); if (*dst == NULL) { *dst_len = 0; goto end; } memcpy(*dst, md_value, *dst_len); rc = true; end: if (ctx) EVP_MD_CTX_free(ctx); oauth2_debug(log, "leave: %d", rc); return rc; } bool oauth2_jose_jwk_create_symmetric(oauth2_log_t *log, const char *secret, const char *hash_algo, oauth2_jose_jwk_t **jwk) { unsigned char *key = NULL; unsigned int key_len = 0; bool rv = false, rc = false; oauth2_debug(log, "enter"); if (jwk == NULL) goto end; oauth2_trace1(log, "secret: %s", secret); if (hash_algo != NULL) { /* * hash the client_secret first, this is OpenID Connect specific */ rc = oauth2_jose_hash_bytes( log, hash_algo, (const unsigned char *)secret, secret ? strlen(secret) : 0, &key, &key_len); if (rc == false) { oauth2_error(log, "oauth2_jose_hash_bytes failed"); goto end; } } else if (secret != NULL) { key_len = strlen(secret); key = (unsigned char *)oauth2_strdup(secret); } oauth2_trace1(log, "key and key_len (%d) set", key_len); *jwk = oauth2_jose_jwk_oct_new(log, key, key_len); rv = (*jwk != NULL); end: if (key) oauth2_mem_free(key); oauth2_debug(log, "leave"); return rv; } bool oauth2_jose_encrypt(oauth2_log_t *log, const char *secret, const char *s_sig_payload, char **cser) { bool rv = false, rc = false; cjose_err err; oauth2_jose_jwk_t *jwk = NULL; cjose_jws_t *jwt = NULL; cjose_jwe_t *jwe = NULL; cjose_header_t *sig_hdr = NULL, *enc_hdr = NULL; char *s_enc_payload = NULL; oauth2_debug(log, "enter"); if (cser == NULL) goto end; if (oauth2_jose_jwk_create_symmetric( log, secret, OAUTH2_JOSE_OPENSSL_ALG_SHA256, &jwk) == false) { oauth2_error(log, "oauth2_jose_jwk_create_symmetric failed"); goto end; } oauth2_trace1(log, "hashed symmetric key created: %s", OAUTH2_JOSE_OPENSSL_ALG_SHA256); sig_hdr = cjose_header_new(&err); if (sig_hdr == NULL) { _OAUTH2_UTIL_JOSE_ERR_LOG(log, "cjose_header_new for signature", err); goto end; } oauth2_trace1(log, "inner header created"); rc = cjose_header_set(sig_hdr, CJOSE_HDR_ALG, CJOSE_HDR_ALG_HS256, &err); if (rc == false) { _OAUTH2_UTIL_JOSE_ERR_LOG( log, "cjose_header_set for signature alg", err); goto end; } oauth2_trace1(log, "inner header \"%s\" set: %s", CJOSE_HDR_ALG, CJOSE_HDR_ALG_HS256); jwt = cjose_jws_sign(jwk->jwk, sig_hdr, (const uint8_t *)s_sig_payload, s_sig_payload ? strlen(s_sig_payload) : 0, &err); if (jwt == NULL) { _OAUTH2_UTIL_JOSE_ERR_LOG(log, "cjose_jws_sign", err); goto end; } oauth2_trace1(log, "inner jwt signed"); rc = cjose_jws_export(jwt, (const char **)&s_enc_payload, &err); if (rc == false) { _OAUTH2_UTIL_JOSE_ERR_LOG(log, "cjose_jws_export", err); goto end; } oauth2_trace1(log, "inner jwt exported: %s", s_enc_payload); enc_hdr = cjose_header_new(&err); if (enc_hdr == NULL) { _OAUTH2_UTIL_JOSE_ERR_LOG( log, "cjose_header_new for encryption", err); goto end; } oauth2_trace1(log, "outer header created"); rc = cjose_header_set(enc_hdr, CJOSE_HDR_ALG, CJOSE_HDR_ALG_DIR, &err); if (rc == false) { _OAUTH2_UTIL_JOSE_ERR_LOG( log, "cjose_header_set for encryption alg", err); goto end; } oauth2_trace1(log, "outer header \"%s\" set: %s", CJOSE_HDR_ALG, CJOSE_HDR_ALG_DIR); rc = cjose_header_set(enc_hdr, CJOSE_HDR_ENC, CJOSE_HDR_ENC_A256GCM, &err); if (rc == false) { _OAUTH2_UTIL_JOSE_ERR_LOG( log, "cjose_header_set for encryption enc", err); goto end; } oauth2_trace1(log, "outer header \"%s\" set: %s", CJOSE_HDR_ENC, CJOSE_HDR_ENC_A256GCM); jwe = cjose_jwe_encrypt(jwk->jwk, enc_hdr, (const uint8_t *)s_enc_payload, strlen(s_enc_payload), &err); if (jwt == NULL) { _OAUTH2_UTIL_JOSE_ERR_LOG(log, "cjose_jwe_encrypt", err); goto end; } oauth2_trace1(log, "jwe created"); *cser = cjose_jwe_export(jwe, &err); if (*cser == NULL) { _OAUTH2_UTIL_JOSE_ERR_LOG(log, "cjose_jwe_export", err); goto end; } oauth2_trace1(log, "jwe exported: %s", *cser); rv = true; end: if (jwe) cjose_jwe_release(jwe); if (jwk) oauth2_jose_jwk_release(jwk); if (jwt) cjose_jws_release(jwt); if (sig_hdr) cjose_header_release(sig_hdr); if (enc_hdr) cjose_header_release(enc_hdr); oauth2_debug(log, "leave"); return rv; } bool oauth2_jose_jwt_encrypt(oauth2_log_t *log, const char *secret, json_t *payload, char **cser) { bool rc = false; char *s_sig_payload = NULL; oauth2_debug(log, "enter"); if (cser == NULL) goto end; s_sig_payload = payload ? json_dumps(payload, JSON_PRESERVE_ORDER | JSON_COMPACT) : NULL; oauth2_trace1(log, "JSON payload serialized: %s", s_sig_payload); rc = oauth2_jose_encrypt(log, secret, s_sig_payload, cser); end: if (s_sig_payload) oauth2_mem_free(s_sig_payload); oauth2_debug(log, "leave"); return rc; } bool oauth2_jose_decrypt(oauth2_log_t *log, const char *secret, const char *cser, char **result) { // oauth2_debug(log, "enter: JWT header=%s", // oidc_proto_peek_jwt_header(log, cser, NULL)); bool rv = false, rc = false; cjose_err err; oauth2_jose_jwk_t *jwk = NULL; cjose_jws_t *jwt = NULL; cjose_jwe_t *jwe = NULL; uint8_t *s_decrypted = NULL, *s_payload = NULL; size_t dec_len, payload_len; oauth2_debug(log, "enter"); if (result == NULL) goto end; if (oauth2_jose_jwk_create_symmetric( log, secret, OAUTH2_JOSE_OPENSSL_ALG_SHA256, &jwk) == false) { oauth2_error(log, "oauth2_jose_jwk_create_symmetric failed"); goto end; } oauth2_trace1(log, "symmetric key created"); jwe = cjose_jwe_import(cser, cser ? strlen(cser) : 0, &err); if (jwe == NULL) { _OAUTH2_UTIL_JOSE_ERR_LOG(log, "cjose_jwe_import", err); goto end; } oauth2_trace1(log, "jwe imported"); s_decrypted = cjose_jwe_decrypt(jwe, jwk->jwk, &dec_len, &err); if (s_decrypted == NULL) { _OAUTH2_UTIL_JOSE_ERR_LOG(log, "cjose_jwe_decrypt", err); goto end; } oauth2_trace1(log, "jwe decrypted"); jwt = cjose_jws_import((const char *)s_decrypted, dec_len, &err); if (jwt == NULL) { _OAUTH2_UTIL_JOSE_ERR_LOG(log, "cjose_jws_import", err); goto end; } oauth2_trace1(log, "innner jws imported"); rc = cjose_jws_verify(jwt, jwk->jwk, &err); if (rc == false) { _OAUTH2_UTIL_JOSE_ERR_LOG(log, "cjose_jws_verify", err); goto end; } oauth2_trace1(log, "inner jws verified"); rc = cjose_jws_get_plaintext(jwt, &s_payload, &payload_len, &err); if (rc == false) { _OAUTH2_UTIL_JOSE_ERR_LOG(log, "cjose_jws_get_plaintext", err); goto end; } oauth2_trace1(log, "plaintext retrieved"); *result = oauth2_mem_alloc(payload_len + 1); strncpy(*result, (const char *)s_payload, payload_len); (*result)[payload_len] = '\0'; oauth2_trace1(log, "plaintext copied"); rv = true; end: if (s_decrypted) oauth2_mem_free(s_decrypted); if (jwe) cjose_jwe_release(jwe); if (jwk) oauth2_jose_jwk_release(jwk); if (jwt) cjose_jws_release(jwt); oauth2_debug(log, "leave"); return rv; } bool oauth2_jose_jwt_decrypt(oauth2_log_t *log, const char *secret, const char *cser, json_t **result) { bool rc = false; char *payload = NULL; json_error_t json_error; oauth2_debug(log, "enter"); if ((secret == NULL) || (cser == NULL) || (result == NULL)) goto end; if (oauth2_jose_decrypt(log, secret, cser, &payload) == false) goto end; *result = json_loads(payload, 0, &json_error); if (*result == NULL) { _OAUTH2_JOSE_JANSSON_ERR_LOG(log, "json_loads", json_error); goto end; } oauth2_trace1(log, "payload parsed to JSON"); rc = true; end: if (payload) oauth2_mem_free(payload); oauth2_debug(log, "leave"); return rc; } #define OAUTH2_JTI_LENGTH 16 char *oauth2_jwt_create(oauth2_log_t *log, cjose_jwk_t *jwk, const char *alg, const char *iss, const char *sub, const char *client_id, const char *aud, oauth2_uint_t exp, bool include_iat, bool include_jti, const json_t *json_payload) { char *rv = NULL; char *payload = NULL; json_t *assertion = NULL; cjose_header_t *hdr = NULL; cjose_jws_t *jws = NULL; const char *jwt = NULL; cjose_err err; char *jti = NULL; oauth2_debug(log, "enter"); if (jwk == NULL) goto end; if (json_payload) assertion = json_deep_copy(json_payload); else assertion = json_object(); if (include_jti) { jti = oauth2_rand_str(log, OAUTH2_JTI_LENGTH); json_object_set_new(assertion, OAUTH2_CLAIM_JTI, json_string(jti)); } if (iss) json_object_set_new(assertion, OAUTH2_CLAIM_ISS, json_string(iss)); if (sub) json_object_set_new(assertion, OAUTH2_CLAIM_SUB, json_string(sub)); if (aud) json_object_set_new(assertion, OAUTH2_CLAIM_AUD, json_string(aud)); if (exp > 0) json_object_set_new(assertion, OAUTH2_CLAIM_EXP, json_integer(oauth2_time_now_sec() + exp)); if (include_iat) json_object_set_new(assertion, OAUTH2_CLAIM_IAT, json_integer(oauth2_time_now_sec())); payload = json_dumps(assertion, JSON_PRESERVE_ORDER | JSON_COMPACT); hdr = cjose_header_new(&err); if (hdr == NULL) { oauth2_error(log, "cjose_header_new failed: %s", err.message); goto end; } if (cjose_header_set(hdr, CJOSE_HDR_ALG, alg, &err) == false) { oauth2_error(log, "cjose_header_set %s:%s failed: %s", CJOSE_HDR_ALG, alg, err.message); goto end; } if (cjose_header_set(hdr, OAUTH2_JOSE_HDR_TYP, OAUTH2_JOSE_HDR_TYP_JWT, &err) == false) { oauth2_error(log, "cjose_header_set %s:%s failed: %s", OAUTH2_JOSE_HDR_TYP, OAUTH2_JOSE_HDR_TYP_JWT, err.message); goto end; } jws = cjose_jws_sign(jwk, hdr, (const uint8_t *)payload, strlen(payload), &err); if (jws == NULL) { oauth2_error(log, "cjose_jws_sign failed: %s", err.message); goto end; } if (cjose_jws_export(jws, &jwt, &err) == false) { oauth2_error(log, "cjose_jws_export failed: %s", err.message); goto end; } rv = oauth2_strndup(jwt, strlen(jwt)); end: if (jti) oauth2_mem_free(jti); if (assertion) json_decref(assertion); if (payload) free(payload); if (hdr) cjose_header_release(hdr); if (jws) cjose_jws_release(jws); oauth2_debug(log, "leave"); return rv; } bool oauth2_jose_hash2s(oauth2_log_t *log, const char *digest, const char *src, char **dst) { bool rc = false; unsigned char *hash_bytes = NULL; unsigned int hash_bytes_len = 0; if (oauth2_jose_hash_bytes(log, digest, (const unsigned char *)src, strlen(src), &hash_bytes, &hash_bytes_len) == false) goto end; *dst = _oauth2_bytes2str(log, hash_bytes, hash_bytes_len); rc = true; end: if (hash_bytes) oauth2_mem_free(hash_bytes); return rc; } _OAUTH2_CFG_CTX_INIT_START(oauth2_uri_ctx) ctx->endpoint = NULL; ctx->cache = NULL; ctx->expiry_s = OAUTH2_CFG_UINT_UNSET; _OAUTH2_CFG_CTX_INIT_END _OAUTH2_CFG_CTX_CLONE_START(oauth2_uri_ctx) dst->endpoint = oauth2_cfg_endpoint_clone(log, src->endpoint); dst->cache = src->cache; dst->expiry_s = src->expiry_s; _OAUTH2_CFG_CTX_CLONE_END _OAUTH2_CFG_CTX_FREE_START(oauth2_uri_ctx) if (ctx->endpoint) oauth2_cfg_endpoint_free(log, ctx->endpoint); _OAUTH2_CFG_CTX_FREE_END static oauth2_jose_jwk_list_t *oauth2_jose_jwk_list_init(oauth2_log_t *log) { oauth2_jose_jwk_list_t *list = (oauth2_jose_jwk_list_t *)oauth2_mem_alloc( sizeof(oauth2_jose_jwk_list_t)); list->jwk = oauth2_jose_jwk_new(); list->next = NULL; return list; } static oauth2_jose_jwk_list_t * oauth2_jose_jwk_list_clone(oauth2_log_t *log, oauth2_jose_jwk_list_t *src) { oauth2_jose_jwk_list_t *dst = NULL, *ptr = NULL, *last = NULL, *elem = NULL; char *str = NULL; cjose_err err; ptr = src; while (ptr) { elem = oauth2_jose_jwk_list_init(log); elem->jwk->kid = oauth2_strdup(ptr->jwk->kid); err.code = CJOSE_ERR_NONE; // cjose_jwk_retain is not thread safe str = cjose_jwk_to_json(ptr->jwk->jwk, true, &err); if (str) { elem->jwk->jwk = cjose_jwk_import(str, strlen(str), &err); cjose_get_dealloc()(str); } if ((elem->jwk->jwk == NULL) && (err.code != CJOSE_ERR_NONE)) { oauth2_error(log, "cjose_jwk_to_json/import failed: %s", err.message); oauth2_jose_jwk_list_free(log, elem); continue; } if (dst == NULL) { dst = elem; last = dst; } else { last->next = elem; last = last->next; } ptr = ptr->next; } return dst; } void oauth2_jose_jwk_list_free(oauth2_log_t *log, oauth2_jose_jwk_list_t *keys) { oauth2_jose_jwk_list_t *ptr = NULL; ptr = keys; while (ptr) { keys = keys->next; oauth2_jose_jwk_release(ptr->jwk); oauth2_mem_free(ptr); ptr = keys; } } static oauth2_jose_jwk_list_t * oauth2_jose_jwks_list_resolve(oauth2_log_t *, oauth2_jose_jwks_provider_t *, bool *, cjose_header_t *); static oauth2_jose_jwk_list_t * oauth2_jose_jwks_uri_resolve(oauth2_log_t *, oauth2_jose_jwks_provider_t *, bool *, cjose_header_t *); static oauth2_jose_jwk_list_t *oauth2_jose_jwks_eckey_url_resolve( oauth2_log_t *, oauth2_jose_jwks_provider_t *, bool *, cjose_header_t *); static oauth2_jose_jwk_list_t * oauth2_jose_jwks_aws_alb_resolve(oauth2_log_t *, oauth2_jose_jwks_provider_t *, bool *, cjose_header_t *); static oauth2_jose_jwks_provider_t * _oauth2_jose_jwks_provider_init(oauth2_log_t *log, oauth2_jose_jwks_provider_type_t type) { oauth2_jose_jwks_provider_t *provider = (oauth2_jose_jwks_provider_t *)oauth2_mem_alloc( sizeof(oauth2_jose_jwks_provider_t)); provider->type = type; switch (type) { case OAUTH2_JOSE_JWKS_PROVIDER_LIST: provider->resolve = oauth2_jose_jwks_list_resolve; provider->jwks = NULL; break; case OAUTH2_JOSE_JWKS_PROVIDER_JWKS_URI: provider->jwks_uri = oauth2_uri_ctx_init(log); provider->resolve = oauth2_jose_jwks_uri_resolve; break; case OAUTH2_JOSE_JWKS_PROVIDER_ECKEY_URI: provider->jwks_uri = oauth2_uri_ctx_init(log); provider->resolve = oauth2_jose_jwks_eckey_url_resolve; break; case OAUTH2_JOSE_JWKS_PROVIDER_AWS_ALB: provider->jwks_uri = oauth2_uri_ctx_init(log); provider->resolve = oauth2_jose_jwks_aws_alb_resolve; provider->alb_arn = NULL; provider->alb_base_url = NULL; break; } return provider; } static oauth2_jose_jwks_provider_t * _oauth2_jose_jwks_provider_clone(oauth2_log_t *log, oauth2_jose_jwks_provider_t *src) { oauth2_jose_jwks_provider_t *dst = NULL; if (src == NULL) goto end; dst = (oauth2_jose_jwks_provider_t *)oauth2_mem_alloc( sizeof(oauth2_jose_jwks_provider_t)); dst->type = src->type; dst->resolve = src->resolve; switch (src->type) { case OAUTH2_JOSE_JWKS_PROVIDER_LIST: dst->jwks = oauth2_jose_jwk_list_clone(log, src->jwks); break; case OAUTH2_JOSE_JWKS_PROVIDER_JWKS_URI: case OAUTH2_JOSE_JWKS_PROVIDER_ECKEY_URI: dst->jwks_uri = oauth2_uri_ctx_clone(log, src->jwks_uri); break; case OAUTH2_JOSE_JWKS_PROVIDER_AWS_ALB: dst->jwks_uri = oauth2_uri_ctx_clone(log, src->jwks_uri); dst->alb_arn = oauth2_strdup(src->alb_arn); dst->alb_base_url = oauth2_strdup(src->alb_base_url); break; } end: return dst; } static void _oauth2_jose_jwks_provider_free(oauth2_log_t *log, oauth2_jose_jwks_provider_t *provider) { if (provider == NULL) goto end; switch (provider->type) { case OAUTH2_JOSE_JWKS_PROVIDER_LIST: if (provider->jwks) oauth2_jose_jwk_list_free(log, provider->jwks); break; case OAUTH2_JOSE_JWKS_PROVIDER_JWKS_URI: case OAUTH2_JOSE_JWKS_PROVIDER_ECKEY_URI: if (provider->jwks_uri) oauth2_uri_ctx_free(log, provider->jwks_uri); break; case OAUTH2_JOSE_JWKS_PROVIDER_AWS_ALB: if (provider->jwks_uri) oauth2_uri_ctx_free(log, provider->jwks_uri); if (provider->alb_arn) oauth2_mem_free(provider->alb_arn); if (provider->alb_base_url) oauth2_mem_free(provider->alb_base_url); break; } oauth2_mem_free(provider); end: return; } oauth2_jose_jwt_validate_claim_t oauth2_parse_validate_claim_option( oauth2_log_t *log, const char *value, oauth2_jose_jwt_validate_claim_t default_value) { oauth2_jose_jwt_validate_claim_t result = default_value; if (value == NULL) goto end; if (strcasecmp(value, "optional") == 0) { result = OAUTH2_JOSE_JWT_VALIDATE_CLAIM_OPTIONAL; goto end; } if (strcasecmp(value, "skip") == 0) { result = OAUTH2_JOSE_JWT_VALIDATE_CLAIM_SKIP; goto end; } if (strcasecmp(value, "required") == 0) { result = OAUTH2_JOSE_JWT_VALIDATE_CLAIM_REQUIRED; goto end; } end: return result; } static const char * _oauth2_validate_claim_option2s(oauth2_jose_jwt_validate_claim_t value) { const char *result = ""; if (value == OAUTH2_JOSE_JWT_VALIDATE_CLAIM_OPTIONAL) { result = "optional"; goto end; } if (value == OAUTH2_JOSE_JWT_VALIDATE_CLAIM_SKIP) { result = "skip"; goto end; } if (value == OAUTH2_JOSE_JWT_VALIDATE_CLAIM_REQUIRED) { result = "required"; goto end; } end: return result; } #define OAUTH2_JOSE_JWT_IAT_SLACK_DEFAULT (oauth2_uint_t)10 #define OAUTH2_JOSE_JWT_IAT_SLACK_BEFORE "verify.iat.slack_before" #define OAUTH2_JOSE_JWT_IAT_SLACK_AFTER "verify.iat.slack_after" #define OAUTH2_JOSE_JWT_ISS_VALIDATE "verify.iss" #define OAUTH2_JOSE_JWT_EXP_VALIDATE "verify.exp" #define OAUTH2_JOSE_JWT_IAT_VALIDATE "verify.iat" _OAUTH2_CFG_CTX_INIT_START(oauth2_jose_jwt_verify_ctx) ctx->exp_validate = OAUTH2_CFG_UINT_UNSET; ctx->iat_validate = OAUTH2_CFG_UINT_UNSET; ctx->iss_validate = OAUTH2_CFG_UINT_UNSET; ctx->issuer = NULL; ctx->iat_slack_after = OAUTH2_CFG_UINT_UNSET; ctx->iat_slack_before = OAUTH2_CFG_UINT_UNSET; ctx->jwks_provider = NULL; _OAUTH2_CFG_CTX_INIT_END _OAUTH2_CFG_CTX_CLONE_START(oauth2_jose_jwt_verify_ctx) dst->exp_validate = src->exp_validate; dst->iat_validate = src->iat_validate; dst->iss_validate = src->iss_validate; dst->issuer = oauth2_strdup(src->issuer); dst->iat_slack_after = src->iat_slack_after; dst->iat_slack_before = src->iat_slack_before; dst->jwks_provider = _oauth2_jose_jwks_provider_clone(log, src->jwks_provider); _OAUTH2_CFG_CTX_CLONE_END _OAUTH2_CFG_CTX_FREE_START(oauth2_jose_jwt_verify_ctx) if (ctx->issuer) oauth2_mem_free(ctx->issuer); if (ctx->jwks_provider) _oauth2_jose_jwks_provider_free(log, ctx->jwks_provider); _OAUTH2_CFG_CTX_FREE_END _OAUTH2_CFG_CTX_FUNCS(oauth2_jose_jwt_verify_ctx) bool oauth2_jose_jwt_verify_set_options( oauth2_log_t *log, oauth2_jose_jwt_verify_ctx_t *jwt_verify, oauth2_jose_jwks_provider_type_t type, const oauth2_nv_list_t *params) { jwt_verify->jwks_provider = _oauth2_jose_jwks_provider_init(log, type); jwt_verify->iss_validate = oauth2_parse_validate_claim_option( log, oauth2_nv_list_get(log, params, OAUTH2_JOSE_JWT_ISS_VALIDATE), OAUTH2_JOSE_JWT_VALIDATE_CLAIM_OPTIONAL); jwt_verify->exp_validate = oauth2_parse_validate_claim_option( log, oauth2_nv_list_get(log, params, OAUTH2_JOSE_JWT_EXP_VALIDATE), OAUTH2_JOSE_JWT_VALIDATE_CLAIM_OPTIONAL); jwt_verify->iat_validate = oauth2_parse_validate_claim_option( log, oauth2_nv_list_get(log, params, OAUTH2_JOSE_JWT_IAT_VALIDATE), OAUTH2_JOSE_JWT_VALIDATE_CLAIM_OPTIONAL); // TODO: this is probably different (default -1) for id_token's // would we need to pass all flags explicitly in init? jwt_verify->iat_slack_before = oauth2_parse_uint( log, oauth2_nv_list_get(log, params, OAUTH2_JOSE_JWT_IAT_SLACK_BEFORE), OAUTH2_CFG_UINT_UNSET); jwt_verify->iat_slack_after = oauth2_parse_uint( log, oauth2_nv_list_get(log, params, OAUTH2_JOSE_JWT_IAT_SLACK_AFTER), OAUTH2_JOSE_JWT_IAT_SLACK_DEFAULT); // TODO: calculate rc based on previous calls return true; } typedef struct oauth2_jose_jwt_verify_jwk_ctx_t { cjose_jws_t *jws; const char *kid; bool verified; } oauth2_jose_jwt_verify_jwk_ctx_t; static bool _oauth2_jose_jwt_verify_jwk(oauth2_log_t *log, void *rec, const char *kid, const oauth2_jose_jwk_t *jwk) { bool rc = true; cjose_err err; oauth2_jose_jwt_verify_jwk_ctx_t *ctx = (oauth2_jose_jwt_verify_jwk_ctx_t *)rec; oauth2_debug(log, "enter: jws kid=%s, jwk kid=%s", ctx->kid, kid); if ((ctx == NULL) || (jwk == NULL)) goto end; // NB: kid can be "" if ((ctx->kid != NULL) && (kid != NULL) && (strcmp(kid, "") != 0) && (strcmp(ctx->kid, kid) != 0)) goto end; err.code = CJOSE_ERR_NONE; if (cjose_jws_verify(ctx->jws, jwk->jwk, &err) == true) { oauth2_debug(log, "cjose_jws_verify returned true"); ctx->verified = true; // break the loop rc = false; } else if (err.code != CJOSE_ERR_NONE) { oauth2_warn(log, "cjose_jws_verify failed: [%s:%lu %s %s]", err.file ? err.file : "", err.line, err.function ? err.function : "", err.message ? err.message : ""); } end: oauth2_debug(log, "leave: rc=%d", rc == false); return rc; } char *oauth2_jose_jwt_header_peek(oauth2_log_t *log, const char *compact_encoded_jwt, const char **alg) { char *input = NULL, *result = NULL; json_t *json = NULL; char *p = NULL; size_t result_len; char *rv = NULL; if (compact_encoded_jwt == NULL) goto end; p = strstr(compact_encoded_jwt, "."); if (p == NULL) goto end; input = oauth2_strndup(compact_encoded_jwt, strlen(compact_encoded_jwt) - strlen(p)); oauth2_debug(log, "decoding: %s (%d-%d=%d)", input, strlen(compact_encoded_jwt), strlen(p), strlen(compact_encoded_jwt) - strlen(p)); if (oauth2_base64url_decode(log, input, (uint8_t **)&result, &result_len) == false) goto end; rv = oauth2_strndup(result, result_len); oauth2_debug(log, "decoded: %s", rv); if (oauth2_json_decode_object(log, rv, &json) == false) { oauth2_mem_free(rv); rv = NULL; goto end; } if ((json == NULL) || (alg == NULL)) goto end; *alg = json_string_value(json_object_get(json, CJOSE_HDR_ALG)); end: if (input) oauth2_mem_free(input); if (result) oauth2_mem_free(result); if (json) json_decref(json); return rv; } typedef bool(oauth2_jose_verification_keys_loop_cb_t)( oauth2_log_t *log, void *rec, const char *kid, const oauth2_jose_jwk_t *jwk); static void _oauth2_jose_verification_keys_loop( oauth2_log_t *log, const oauth2_jose_jwk_list_t *list, oauth2_jose_verification_keys_loop_cb_t *callback, void *rec) { const oauth2_jose_jwk_list_t *ptr = NULL; if ((list == NULL) || (callback == NULL)) goto end; for (ptr = list; ptr; ptr = ptr->next) { if (callback(log, rec, ptr->jwk->kid, ptr->jwk) == false) break; } end: return; } static bool _oauth2_jose_jwt_validate_iss(oauth2_log_t *log, const json_t *json_payload, const char *iss, oauth2_jose_jwt_validate_claim_t validate) { bool rc = false; char *value = NULL; oauth2_debug(log, "enter: iss=%s, validate=%s", iss, _oauth2_validate_claim_option2s(validate)); if (validate == OAUTH2_JOSE_JWT_VALIDATE_CLAIM_SKIP) { rc = true; goto end; } if (iss == NULL) { rc = (validate != OAUTH2_JOSE_JWT_VALIDATE_CLAIM_REQUIRED); goto end; } if (oauth2_json_string_get(log, json_payload, OAUTH2_JOSE_JWT_ISS, &value, NULL) == false) { rc = (validate != OAUTH2_JOSE_JWT_VALIDATE_CLAIM_REQUIRED); goto end; } if (value == NULL) { oauth2_error(log, "JWT did not contain an \"%s\" string (requested " "value: %s)", OAUTH2_JOSE_JWT_ISS, iss); rc = (validate != OAUTH2_JOSE_JWT_VALIDATE_CLAIM_REQUIRED); goto end; } if (strcmp(iss, value) != 0) { oauth2_error(log, "requested issuer (%s) does not match received " "\"%s\" value in JWT (%s)", iss, OAUTH2_JOSE_JWT_ISS, value); goto end; } rc = true; end: oauth2_debug(log, "leave: %d", rc); return rc; } static bool _oauth2_jose_jwt_validate_exp(oauth2_log_t *log, const json_t *json_payload, oauth2_jose_jwt_validate_claim_t validate) { bool rc = false; json_int_t exp = -1; oauth2_time_t now; oauth2_debug(log, "enter: validate=%s", _oauth2_validate_claim_option2s(validate)); if (validate == OAUTH2_JOSE_JWT_VALIDATE_CLAIM_SKIP) { rc = true; goto end; } if (oauth2_json_number_get(log, json_payload, OAUTH2_JOSE_JWT_EXP, &exp, -1) == false) { rc = (validate != OAUTH2_JOSE_JWT_VALIDATE_CLAIM_REQUIRED); goto end; } if (exp == -1) { oauth2_warn(log, "JWT did not contain a \"%s\" number", OAUTH2_JOSE_JWT_EXP); rc = (validate != OAUTH2_JOSE_JWT_VALIDATE_CLAIM_REQUIRED); goto end; } now = oauth2_time_now_sec(); oauth2_debug(log, "\"%s\"=%" JSON_INTEGER_FORMAT ", %ld seconds from now", OAUTH2_JOSE_JWT_EXP, exp, (long)(exp - now)); if (now > exp) { oauth2_error(log, "\"%s\" validation failure (%ld): JWT expired %ld " "seconds ago", OAUTH2_JOSE_JWT_EXP, (long)exp, (long)(now - exp)); goto end; } rc = true; end: oauth2_debug(log, "leave: %d", rc); return rc; } bool oauth2_jose_jwt_validate_iat(oauth2_log_t *log, const json_t *json_payload, oauth2_jose_jwt_validate_claim_t validate, oauth2_uint_t slack_before, oauth2_uint_t slack_after) { bool rc = false; json_int_t iat = -1; oauth2_time_t now; oauth2_debug(log, "enter: validate=%s, slack_before=" OAUTH2_UINT_FORMAT ", slack_after=" OAUTH2_UINT_FORMAT, _oauth2_validate_claim_option2s(validate), slack_before, slack_after); if (validate == OAUTH2_JOSE_JWT_VALIDATE_CLAIM_SKIP) { rc = true; goto end; } if (oauth2_json_number_get(log, json_payload, OAUTH2_JOSE_JWT_IAT, &iat, -1) == false) { rc = (validate != OAUTH2_JOSE_JWT_VALIDATE_CLAIM_REQUIRED); goto end; } if (iat == -1) { oauth2_warn(log, "JWT did not contain a \"%s\" number", OAUTH2_JOSE_JWT_IAT); rc = (validate != OAUTH2_JOSE_JWT_VALIDATE_CLAIM_REQUIRED); goto end; } now = oauth2_time_now_sec(); if ((slack_before != OAUTH2_CFG_UINT_UNSET) && ((now - slack_before) > iat)) { oauth2_error(log, "\"%s\" validation failure (%ld): JWT was issued " "more than %d seconds ago", OAUTH2_JOSE_JWT_IAT, (long)iat, slack_before); goto end; } if ((slack_after != OAUTH2_CFG_UINT_UNSET) && ((now + slack_after) < iat)) { oauth2_error(log, "\"%s\" validation failure (%ld): JWT was issued " "more than %d seconds in the future", OAUTH2_JOSE_JWT_IAT, (long)iat, slack_after); goto end; } rc = true; end: oauth2_debug(log, "leave: %d", rc); return rc; } static bool _oauth2_jose_jwt_payload_validate(oauth2_log_t *log, oauth2_jose_jwt_verify_ctx_t *jwt_verify_ctx, const json_t *json_payload) { bool rc = false; oauth2_debug(log, "enter"); if (_oauth2_jose_jwt_validate_iss( log, json_payload, jwt_verify_ctx->issuer, jwt_verify_ctx->iss_validate) == false) goto end; if (_oauth2_jose_jwt_validate_exp( log, json_payload, jwt_verify_ctx->exp_validate) == false) goto end; if (oauth2_jose_jwt_validate_iat( log, json_payload, jwt_verify_ctx->iat_validate, jwt_verify_ctx->iat_slack_before, jwt_verify_ctx->iat_slack_after) == false) goto end; // TODO: token_binding_policy // if (oauth2_jose_jwt_validate_cnf(r, jwt->payload.value.json, // token_binding_policy) == false) // goto end; rc = true; end: oauth2_debug(log, "leave: %d", rc); return rc; } bool oauth2_jose_jwt_verify(oauth2_log_t *log, oauth2_jose_jwt_verify_ctx_t *jwt_verify_ctx, const char *token, json_t **json_payload, char **s_payload) { bool rc = false; char *peek = NULL; cjose_jws_t *jws = NULL; cjose_header_t *hdr = NULL; cjose_err err; oauth2_jose_jwk_list_t *keys = NULL; oauth2_jose_jwt_verify_jwk_ctx_t ctx; uint8_t *plaintext = NULL; size_t plaintext_len = 0; bool refresh = false; peek = oauth2_jose_jwt_header_peek(log, token, NULL); oauth2_debug(log, "enter: JWT token header=%s", peek); if (token == NULL) goto end; /* * TODO: resolve the shared secret(s) and the private key(s) for * decryption */ // TODO: this is not optimized anymore across different JWK verify // configs jws = cjose_jws_import(token, strlen(token), &err); if (jws == NULL) { _OAUTH2_UTIL_JOSE_ERR_LOG(log, "cjose_jws_import", err); goto end; } hdr = cjose_jws_get_protected(jws); if (hdr == NULL) goto end; if (jwt_verify_ctx) { keys = jwt_verify_ctx->jwks_provider->resolve( log, jwt_verify_ctx->jwks_provider, &refresh, hdr); ctx.jws = jws; ctx.kid = cjose_header_get(hdr, "kid", &err); ctx.verified = false; _oauth2_jose_verification_keys_loop( log, keys, _oauth2_jose_jwt_verify_jwk, &ctx); if (ctx.verified == false) { if (refresh == false) goto end; if (keys) oauth2_jose_jwk_list_free(log, keys); keys = jwt_verify_ctx->jwks_provider->resolve( log, jwt_verify_ctx->jwks_provider, &refresh, hdr); _oauth2_jose_verification_keys_loop( log, keys, _oauth2_jose_jwt_verify_jwk, &ctx); if (ctx.verified == false) goto end; } } if (cjose_jws_get_plaintext(jws, &plaintext, &plaintext_len, &err) == false) { _OAUTH2_UTIL_JOSE_ERR_LOG(log, "cjose_jws_get_plaintext", err); goto end; } if ((s_payload == NULL) || (json_payload == NULL)) goto end; *s_payload = oauth2_strndup((const char *)plaintext, plaintext_len); oauth2_debug(log, "got plaintext (len=%lu): %s", plaintext_len, *s_payload); if (oauth2_json_decode_object(log, *s_payload, json_payload) == false) { oauth2_mem_free(*s_payload); *s_payload = NULL; goto end; } if (jwt_verify_ctx) { if (_oauth2_jose_jwt_payload_validate(log, jwt_verify_ctx, *json_payload) == false) { json_decref(*json_payload); *json_payload = NULL; oauth2_mem_free(*s_payload); *s_payload = NULL; goto end; } } rc = true; end: if (peek) oauth2_mem_free(peek); if (jws) cjose_jws_release(jws); if (keys) oauth2_jose_jwk_list_free(log, keys); oauth2_debug(log, "leave: %d", rc); return rc; } static bool _oauth2_jose_jwt_verify_callback(oauth2_log_t *log, oauth2_cfg_token_verify_t *verify, const char *token, json_t **json_payload, char **s_payload) { bool rc = false; oauth2_jose_jwt_verify_ctx_t *ctx = NULL; oauth2_debug(log, "enter"); if ((verify == NULL) || (verify->ctx == NULL) || (verify->ctx->ptr == NULL)) goto end; ctx = (oauth2_jose_jwt_verify_ctx_t *)verify->ctx->ptr; rc = oauth2_jose_jwt_verify(log, ctx, token, json_payload, s_payload); if (rc == false) goto end; end: return rc; } static char * _oauth2_jose_verify_options_jwk_add_jwk(oauth2_log_t *log, cjose_jwk_t *jwk, const oauth2_nv_list_t *params, oauth2_cfg_token_verify_t *verify) { char *rv = NULL; oauth2_jose_jwt_verify_ctx_t *ptr = NULL; const char *kid = NULL; cjose_err err; verify->callback = _oauth2_jose_jwt_verify_callback; verify->ctx->callbacks = &oauth2_jose_jwt_verify_ctx_funcs; verify->ctx->ptr = verify->ctx->callbacks->init(log); ptr = (oauth2_jose_jwt_verify_ctx_t *)verify->ctx->ptr; if (oauth2_jose_jwt_verify_set_options( log, ptr, OAUTH2_JOSE_JWKS_PROVIDER_LIST, params) == false) { rv = oauth2_strdup("oauth2_jose_jwt_verify_set_options failed"); goto end; } // set or possibly override kid in JWK kid = oauth2_nv_list_get(log, params, "kid"); if (kid) { if (cjose_jwk_set_kid(jwk, kid, strlen(kid), &err) == false) { rv = oauth2_stradd(NULL, "cjose_jwk_set_kid failed", ": ", err.message); goto end; } } else { err.code = CJOSE_ERR_NONE; kid = cjose_jwk_get_kid(jwk, &err); if ((kid == NULL) && (err.code != CJOSE_ERR_NONE)) { rv = oauth2_stradd(NULL, "cjose_jwk_get_kid failed", ": ", err.message); goto end; } } // list of one ptr->jwks_provider->jwks = oauth2_jose_jwk_list_init(log); ptr->jwks_provider->jwks->jwk->jwk = jwk; ptr->jwks_provider->jwks->jwk->kid = kid ? oauth2_strdup(kid) : NULL; ptr->jwks_provider->jwks->next = NULL; end: return rv; } static char *_oauth2_jose_verify_options_jwk_set_symmetric_key( oauth2_log_t *log, const uint8_t *key, size_t key_len, const oauth2_nv_list_t *params, oauth2_cfg_token_verify_t *verify) { char *rv = NULL; cjose_err err; cjose_jwk_t *jwk = NULL; jwk = cjose_jwk_create_oct_spec(key, key_len, &err); if (jwk == NULL) { rv = oauth2_stradd(NULL, "cjose_jwk_create_oct_spec failed", ": ", err.message); goto end; } rv = _oauth2_jose_verify_options_jwk_add_jwk(log, jwk, params, verify); end: return rv; } _OAUTH_CFG_CTX_CALLBACK(oauth2_jose_verify_options_jwk_set_plain) { oauth2_cfg_token_verify_t *verify = (oauth2_cfg_token_verify_t *)ctx; char *rv = NULL; const uint8_t *key = NULL; size_t key_len = 0; if (value == NULL) { rv = oauth2_strdup("no plain symmetric key value provided"); goto end; } key = (const uint8_t *)value; key_len = strlen(value); rv = _oauth2_jose_verify_options_jwk_set_symmetric_key( log, key, key_len, params, verify); end: return rv; } _OAUTH_CFG_CTX_CALLBACK(oauth2_jose_verify_options_jwk_set_base64) { oauth2_cfg_token_verify_t *verify = (oauth2_cfg_token_verify_t *)ctx; char *rv = NULL; uint8_t *key = NULL; size_t key_len = 0; if (value == NULL) { rv = oauth2_strdup( "no base64 encoded symmetric key value provided"); goto end; } if (oauth2_base64_decode(log, value, &key, &key_len) == false) { rv = oauth2_strdup("oauth2_base64_decode failed"); goto end; } rv = _oauth2_jose_verify_options_jwk_set_symmetric_key( log, key, key_len, params, verify); end: if (key) oauth2_mem_free(key); return rv; } _OAUTH_CFG_CTX_CALLBACK(oauth2_jose_verify_options_jwk_set_base64url) { oauth2_cfg_token_verify_t *verify = (oauth2_cfg_token_verify_t *)ctx; char *rv = NULL; uint8_t *key = NULL; size_t key_len = 0; if (value == NULL) { rv = oauth2_strdup( "no base64url encoded symmetric key value provided"); goto end; } if (oauth2_base64url_decode(log, value, &key, &key_len) == false) { rv = oauth2_strdup("oauth2_base64url_decode failed"); goto end; } rv = _oauth2_jose_verify_options_jwk_set_symmetric_key( log, key, key_len, params, verify); end: if (key) oauth2_mem_free(key); return rv; } _OAUTH_CFG_CTX_CALLBACK(oauth2_jose_verify_options_jwk_set_hex) { oauth2_cfg_token_verify_t *verify = (oauth2_cfg_token_verify_t *)ctx; char *rv = NULL; unsigned char *key = NULL; size_t key_len = 0; const char *ptr = NULL; size_t n = 0; if (value == NULL) { rv = oauth2_strdup("no hex symmetric key value provided"); goto end; } key_len = strlen(value) / 2; ptr = value; key = oauth2_mem_alloc(key_len); for (n = 0; n < key_len / sizeof(unsigned char); n++) { if (sscanf(ptr, "%2hhx", &key[n]) != 1) { rv = oauth2_strdup("sscanf failed"); goto end; } ptr += 2; } rv = _oauth2_jose_verify_options_jwk_set_symmetric_key( log, (const uint8_t *)key, key_len, params, verify); end: if (key) oauth2_mem_free(key); return rv; } static BIO *_oauth2_jose_str2bio(oauth2_log_t *log, const char *value) { BIO *input = NULL; if ((input = BIO_new(BIO_s_mem())) == NULL) { oauth2_error(log, "BIO allocation failed: %s", ERR_error_string(ERR_get_error(), NULL)); goto end; } if (BIO_puts(input, value) <= 0) { _OAUTH2_JOSE_OPENSSL_ERR_LOG(log, "BIO_puts"); goto end; } end: return input; } static char * _oauth2_jose_options_jwk_set_rsa_key(oauth2_log_t *log, EVP_PKEY *pkey, const oauth2_nv_list_t *params, oauth2_cfg_token_verify_t *verify) { char *rv = NULL; cjose_jwk_rsa_keyspec key_spec; cjose_err err; cjose_jwk_t *jwk = NULL; BIGNUM *rsa_n = NULL, *rsa_e = NULL; 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); #else RSA *rsa = (RSA *)EVP_PKEY_get1_RSA(pkey); if (rsa == NULL) { rv = oauth2_stradd(NULL, "EVP_PKEY_get1_RSA failed", ": ", ERR_error_string(ERR_get_error(), NULL)); goto end; } #if OPENSSL_VERSION_NUMBER >= 0x10100005L && !defined(LIBRESSL_VERSION_NUMBER) RSA_get0_key(rsa, (const BIGNUM **)&rsa_n, (const BIGNUM **)&rsa_e, NULL); #else rsa_n = rsa->n; rsa_e = rsa->e; #endif RSA_free(rsa); #endif key_spec.nlen = BN_num_bytes(rsa_n); key_spec.n = oauth2_mem_alloc(key_spec.nlen); BN_bn2bin(rsa_n, key_spec.n); key_spec.elen = BN_num_bytes(rsa_e); key_spec.e = oauth2_mem_alloc(key_spec.elen); BN_bn2bin(rsa_e, key_spec.e); jwk = cjose_jwk_create_RSA_spec(&key_spec, &err); if (jwk == NULL) { rv = oauth2_stradd(NULL, "cjose_jwk_create_RSA_spec failed", ": ", err.message); goto end; } rv = _oauth2_jose_verify_options_jwk_add_jwk(log, jwk, params, verify); end: #if OPENSSL_VERSION_NUMBER >= 0x30000000L if (rsa_n) BN_clear_free(rsa_n); if (rsa_e) BN_clear_free(rsa_e); #endif if (key_spec.n) oauth2_mem_free(key_spec.n); if (key_spec.e) oauth2_mem_free(key_spec.e); return rv; } _OAUTH_CFG_CTX_CALLBACK(oauth2_jose_verify_options_jwk_set_pem) { oauth2_cfg_token_verify_t *verify = (oauth2_cfg_token_verify_t *)ctx; char *rv = NULL; BIO *input = NULL; X509 *x509 = NULL; EVP_PKEY *pkey = NULL; input = _oauth2_jose_str2bio(log, value); if (input == NULL) { rv = oauth2_stradd(NULL, "_oauth2_jose_str2bio failed", ": ", ERR_error_string(ERR_get_error(), NULL)); goto end; } x509 = PEM_read_bio_X509_AUX(input, NULL, NULL, NULL); if (x509 == NULL) { rv = oauth2_stradd(NULL, "PEM_read_bio_X509_AUX failed", ": ", ERR_error_string(ERR_get_error(), NULL)); goto end; } pkey = X509_get_pubkey(x509); if (pkey == NULL) { rv = oauth2_stradd(NULL, "X509_get_pubkey failed", ": ", ERR_error_string(ERR_get_error(), NULL)); goto end; } rv = _oauth2_jose_options_jwk_set_rsa_key(log, pkey, params, verify); end: if (x509) X509_free(x509); if (pkey) EVP_PKEY_free(pkey); if (input) BIO_free(input); return rv; } _OAUTH_CFG_CTX_CALLBACK(oauth2_jose_verify_options_jwk_set_pubkey) { oauth2_cfg_token_verify_t *verify = (oauth2_cfg_token_verify_t *)ctx; char *rv = NULL; BIO *input = NULL; EVP_PKEY *pkey = NULL; input = _oauth2_jose_str2bio(log, value); if (input == NULL) { rv = oauth2_stradd(NULL, "_oauth2_jose_str2bio failed", ": ", ERR_error_string(ERR_get_error(), NULL)); goto end; } pkey = PEM_read_bio_PUBKEY(input, &pkey, NULL, NULL); if (pkey == NULL) { rv = oauth2_stradd(NULL, "PEM_read_bio_PUBKEY failed", ": ", ERR_error_string(ERR_get_error(), NULL)); goto end; } rv = _oauth2_jose_options_jwk_set_rsa_key(log, pkey, params, verify); end: if (pkey) EVP_PKEY_free(pkey); if (input) BIO_free(input); return rv; } _OAUTH_CFG_CTX_CALLBACK(oauth2_jose_verify_options_jwk_set_jwk) { oauth2_cfg_token_verify_t *verify = (oauth2_cfg_token_verify_t *)ctx; char *rv = NULL; cjose_jwk_t *jwk = NULL; cjose_err err; jwk = cjose_jwk_import(value, strlen(value), &err); if (jwk == NULL) { rv = oauth2_stradd(NULL, "cjose_jwk_import failed", ": ", err.message); goto end; } rv = _oauth2_jose_verify_options_jwk_add_jwk(log, jwk, params, verify); end: return rv; } #define OAUTH2_JOSE_URI_REFRESH_DEFAULT 60 * 60 * 24 char *oauth2_jose_options_uri_ctx(oauth2_log_t *log, const char *value, const oauth2_nv_list_t *params, oauth2_uri_ctx_t *ctx, const char *prefix) { char *rv = NULL; char *key = NULL; ctx->endpoint = oauth2_cfg_endpoint_init(log); rv = oauth2_cfg_set_endpoint(log, ctx->endpoint, value, params, prefix); key = oauth2_stradd(NULL, prefix, ".", "cache"); ctx->cache = oauth2_cache_obtain(log, oauth2_nv_list_get(log, params, key)); oauth2_mem_free(key); if (ctx->cache == NULL) rv = oauth2_strdup("cache could not be found"); key = oauth2_stradd(NULL, prefix, ".", "expiry"); ctx->expiry_s = oauth2_parse_uint(log, oauth2_nv_list_get(log, params, key), OAUTH2_JOSE_URI_REFRESH_DEFAULT); oauth2_mem_free(key); return rv; } static char *_oauth2_jose_verify_options_jwk_set_url( oauth2_log_t *log, const char *value, const oauth2_nv_list_t *params, oauth2_cfg_token_verify_t *verify, oauth2_jose_jwks_provider_type_t type, const char *prefix) { char *rv = NULL; oauth2_jose_jwt_verify_ctx_t *ptr = NULL; oauth2_debug(log, "enter"); verify->callback = _oauth2_jose_jwt_verify_callback; verify->ctx->callbacks = &oauth2_jose_jwt_verify_ctx_funcs; verify->ctx->ptr = verify->ctx->callbacks->init(log); ptr = (oauth2_jose_jwt_verify_ctx_t *)verify->ctx->ptr; if (oauth2_jose_jwt_verify_set_options(log, ptr, type, params) == false) { rv = oauth2_strdup("oauth2_jose_jwt_verify_set_options failed"); goto end; } rv = oauth2_jose_options_uri_ctx(log, value, params, ptr->jwks_provider->jwks_uri, prefix); end: oauth2_debug(log, "leave: %s", rv); return rv; } _OAUTH_CFG_CTX_CALLBACK(oauth2_jose_verify_options_jwk_set_jwks_uri) { oauth2_cfg_token_verify_t *verify = (oauth2_cfg_token_verify_t *)ctx; return _oauth2_jose_verify_options_jwk_set_url( log, value, params, verify, OAUTH2_JOSE_JWKS_PROVIDER_JWKS_URI, "jwks_uri"); } _OAUTH_CFG_CTX_CALLBACK(oauth2_jose_verify_options_jwk_set_eckey_uri) { oauth2_cfg_token_verify_t *verify = (oauth2_cfg_token_verify_t *)ctx; return _oauth2_jose_verify_options_jwk_set_url( log, value, params, verify, OAUTH2_JOSE_JWKS_PROVIDER_ECKEY_URI, "eckey_uri"); } _OAUTH_CFG_CTX_CALLBACK(oauth2_jose_verify_options_jwk_set_aws_alb) { char *rv = NULL; oauth2_cfg_token_verify_t *verify = (oauth2_cfg_token_verify_t *)ctx; const char *alb_base_url = NULL; oauth2_debug(log, "enter"); rv = _oauth2_jose_verify_options_jwk_set_url( log, value, params, verify, OAUTH2_JOSE_JWKS_PROVIDER_AWS_ALB, "aws_alb"); if (rv != NULL) goto end; oauth2_jose_jwt_verify_ctx_t *ptr = verify->ctx->ptr; // this is going to be set dynamically if (ptr->jwks_provider->jwks_uri->endpoint->url) { oauth2_mem_free(ptr->jwks_provider->jwks_uri->endpoint->url); ptr->jwks_provider->jwks_uri->endpoint->url = NULL; } ptr->jwks_provider->alb_arn = oauth2_strdup(value); alb_base_url = oauth2_nv_list_get(log, params, "alb_base_url"); if (alb_base_url) { ptr->jwks_provider->alb_base_url = oauth2_strdup(alb_base_url); } end: oauth2_debug(log, "leave: %s", rv); return rv; } static oauth2_jose_jwk_list_t * oauth2_jose_jwks_list_resolve(oauth2_log_t *log, oauth2_jose_jwks_provider_t *provider, bool *refresh, cjose_header_t *hdr) { *refresh = false; return oauth2_jose_jwk_list_clone(log, provider->jwks); } typedef oauth2_jose_jwk_list_t *( oauth2_jose_jwks_url_resolve_response_cb_t)(oauth2_log_t *log, char *response); // cater for the (Amazon ALB) use case that only a single EC(!) key is served // from the URL static oauth2_jose_jwk_list_t * _oauth2_jose_jwks_eckey_url_resolve_response_callback(oauth2_log_t *log, char *response) { oauth2_jose_jwk_list_t *keys = NULL; BIGNUM *x = NULL, *y = NULL; cjose_jwk_ec_keyspec spec; cjose_jwk_t *jwk = NULL; cjose_err err; #if OPENSSL_VERSION_NUMBER >= 0x30000000L OSSL_DECODER_CTX *dctx = NULL; EVP_PKEY *pkey = NULL; const unsigned char *data = (unsigned char *)response; size_t datalen = strlen(response) + 1; dctx = OSSL_DECODER_CTX_new_for_pkey( &pkey, "PEM", NULL, "EC", OSSL_KEYMGMT_SELECT_PUBLIC_KEY | OSSL_KEYMGMT_SELECT_DOMAIN_PARAMETERS, NULL, NULL); if (dctx == NULL) { _OAUTH2_JOSE_OPENSSL_ERR_LOG(log, "OSSL_DECODER_CTX_new_for_pkey"); goto end; } if (OSSL_DECODER_from_data(dctx, &data, &datalen) <= 0) { _OAUTH2_JOSE_OPENSSL_ERR_LOG(log, "OSSL_DECODER_from_data"); goto end; } char *curve_name[64]; size_t len = 0; if (EVP_PKEY_get_utf8_string_param(pkey, OSSL_PKEY_PARAM_GROUP_NAME, (char *)curve_name, sizeof(curve_name), &len) <= 0) { _OAUTH2_JOSE_OPENSSL_ERR_LOG(log, "EVP_PKEY_get_utf8_string_param"); goto end; } spec.crv = OBJ_txt2nid((char *)curve_name); if (spec.crv == NID_undef) { _OAUTH2_JOSE_OPENSSL_ERR_LOG(log, "OBJ_txt2nid"); goto end; } if (EVP_PKEY_get_bn_param(pkey, OSSL_PKEY_PARAM_EC_PUB_X, &x) <= 0) { _OAUTH2_JOSE_OPENSSL_ERR_LOG(log, "EVP_PKEY_get_bn_param"); goto end; } if (EVP_PKEY_get_bn_param(pkey, OSSL_PKEY_PARAM_EC_PUB_Y, &y) <= 0) { _OAUTH2_JOSE_OPENSSL_ERR_LOG(log, "EVP_PKEY_get_bn_param"); goto end; } #else BIO *input = NULL; EC_KEY *eckey = NULL; const EC_GROUP *ecgroup = NULL; const EC_POINT *ecpoint = NULL; input = _oauth2_jose_str2bio(log, response); if (input == NULL) goto end; eckey = PEM_read_bio_EC_PUBKEY(input, NULL, 0, 0); if (eckey == NULL) { _OAUTH2_JOSE_OPENSSL_ERR_LOG(log, "PEM_read_bio_EC_PUBKEY"); goto end; } ecgroup = EC_KEY_get0_group(eckey); if (ecgroup == NULL) { _OAUTH2_JOSE_OPENSSL_ERR_LOG(log, "EC_KEY_get0_group"); goto end; } spec.crv = EC_GROUP_get_curve_name(ecgroup); if (spec.crv == 0) { _OAUTH2_JOSE_OPENSSL_ERR_LOG(log, "EC_GROUP_get_curve_name"); goto end; } ecpoint = EC_KEY_get0_public_key(eckey); if (ecpoint == 0) { _OAUTH2_JOSE_OPENSSL_ERR_LOG(log, "EC_KEY_get0_public_key"); goto end; } x = BN_new(); y = BN_new(); if ((x == NULL) || (y == NULL)) { _OAUTH2_JOSE_OPENSSL_ERR_LOG(log, "BN_new"); goto end; } if (EC_POINT_get_affine_coordinates_GFp(ecgroup, ecpoint, x, y, NULL) != 1) { _OAUTH2_JOSE_OPENSSL_ERR_LOG( log, "EC_POINT_get_affine_coordinates_GFp"); goto end; } #endif spec.xlen = BN_num_bytes(x); spec.x = oauth2_mem_alloc(spec.xlen); BN_bn2bin(x, spec.x); spec.ylen = BN_num_bytes(y); spec.y = oauth2_mem_alloc(spec.ylen); BN_bn2bin(y, spec.y); spec.dlen = 0; spec.d = NULL; err.code = CJOSE_ERR_NONE; jwk = cjose_jwk_create_EC_spec(&spec, &err); if ((jwk == NULL) || (err.code != CJOSE_ERR_NONE)) { oauth2_error(log, "cjose_jwk_create_EC_spec failed: %s", err.message); goto end; } keys = oauth2_jose_jwk_list_init(log); keys->jwk->jwk = jwk; keys->jwk->kid = NULL; end: if (spec.x) oauth2_mem_free(spec.x); if (spec.y) oauth2_mem_free(spec.y); if (x) BN_free(x); if (y) BN_free(y); #if OPENSSL_VERSION_NUMBER >= 0x30000000L if (dctx) OSSL_DECODER_CTX_free(dctx); #else if (eckey) EC_KEY_free(eckey); if (input) BIO_free(input); #endif return keys; } static oauth2_jose_jwk_list_t * _oauth2_jose_jwks_uri_resolve_response_callback(oauth2_log_t *log, char *response) { json_t *json_result = NULL, *json_keys = NULL, *json_key = NULL; oauth2_jose_jwk_list_t *result = NULL, *elem = NULL, *last = NULL; int i = 0; cjose_err err; if (oauth2_json_decode_object(log, response, &json_result) == false) goto end; // TODO: #define json_keys = json_object_get(json_result, "keys"); if ((json_keys == NULL) || !(json_is_array(json_keys))) { oauth2_error(log, "\"keys\" array element is not a JSON array"); goto end; } for (i = 0; i < json_array_size(json_keys); i++) { json_key = json_array_get(json_keys, i); // TODO: #define const char *use = json_string_value(json_object_get(json_key, "use")); if ((use != NULL) && (strcmp(use, "sig") != 0)) { oauth2_debug(log, "skipping key because of " "non-matching \"%s\": \"%s\"", "use", use); continue; } // TODO: search/skip based on key type (?) elem = oauth2_jose_jwk_list_init(log); err.code = CJOSE_ERR_NONE; elem->jwk->jwk = cjose_jwk_import_json(json_key, &err); if ((elem->jwk->jwk == NULL) || (err.code != CJOSE_ERR_NONE)) { oauth2_warn(log, "cjose_jwk_import_json failed", ": ", err.message); oauth2_jose_jwk_list_free(log, elem); continue; } elem->jwk->kid = oauth2_strdup(cjose_jwk_get_kid(elem->jwk->jwk, &err)); if (err.code != CJOSE_ERR_NONE) { oauth2_warn(log, "cjose_jwk_get_kid failed", ": ", err.message); oauth2_jose_jwk_list_free(log, elem); continue; } if (result == NULL) { result = elem; last = result; } else { last->next = elem; last = last->next; } } end: if (json_result) json_decref(json_result); return result; } char *oauth2_jose_resolve_from_uri(oauth2_log_t *log, oauth2_uri_ctx_t *uri_ctx, bool *refresh) { bool rc = false; oauth2_http_call_ctx_t *ctx = NULL; char *response = NULL; oauth2_uint_t status_code = 0; oauth2_debug(log, "enter"); if (uri_ctx == NULL) goto end; if (*refresh == false) { oauth2_cache_get(log, uri_ctx->cache, oauth2_cfg_endpoint_get_url(uri_ctx->endpoint), &response); *refresh = true; } if (response == NULL) { *refresh = false; ctx = oauth2_http_call_ctx_init(log); oauth2_http_call_ctx_ssl_verify_set( log, ctx, oauth2_cfg_endpoint_get_ssl_verify(uri_ctx->endpoint)); oauth2_http_call_ctx_outgoing_proxy_set( log, ctx, oauth2_cfg_endpoint_get_outgoing_proxy(uri_ctx->endpoint)); rc = oauth2_http_get( log, oauth2_cfg_endpoint_get_url(uri_ctx->endpoint), NULL, ctx, &response, &status_code); if (rc == false) goto end; if ((status_code < 200) || (status_code >= 300)) { rc = false; goto end; } oauth2_cache_set(log, uri_ctx->cache, oauth2_cfg_endpoint_get_url(uri_ctx->endpoint), response, uri_ctx->expiry_s); } end: if (ctx) oauth2_http_call_ctx_free(log, ctx); oauth2_debug(log, "leave: %s", response); return response; } static oauth2_jose_jwk_list_t *_oauth2_jose_jwks_resolve_from_uri( oauth2_log_t *log, oauth2_jose_jwks_provider_t *provider, bool *refresh, oauth2_jose_jwks_url_resolve_response_cb_t *resolve_response_cb) { oauth2_jose_jwk_list_t *dst = NULL; char *response = NULL; response = oauth2_jose_resolve_from_uri(log, provider->jwks_uri, refresh); if (response == NULL) goto end; dst = resolve_response_cb(log, response); end: if (response) oauth2_mem_free(response); return dst; } static oauth2_jose_jwk_list_t * oauth2_jose_jwks_uri_resolve(oauth2_log_t *log, oauth2_jose_jwks_provider_t *provider, bool *refresh, cjose_header_t *hdr) { return _oauth2_jose_jwks_resolve_from_uri( log, provider, refresh, _oauth2_jose_jwks_uri_resolve_response_callback); } static oauth2_jose_jwk_list_t * oauth2_jose_jwks_eckey_url_resolve(oauth2_log_t *log, oauth2_jose_jwks_provider_t *provider, bool *refresh, cjose_header_t *hdr) { return _oauth2_jose_jwks_resolve_from_uri( log, provider, refresh, _oauth2_jose_jwks_eckey_url_resolve_response_callback); } static const char *_oauth2_jose_jwks_aws_alb_region(const char *arn) { if (!arn) return NULL; char *arn_copy = oauth2_strdup(arn); if (!arn_copy) return NULL; char *token = strtok(arn_copy, ":"); int count = 0; const char *region = NULL; while (token) { if (count == 3) { region = oauth2_strdup(token); break; } token = strtok(NULL, ":"); count++; } oauth2_mem_free(arn_copy); return region; } static oauth2_jose_jwk_list_t * oauth2_jose_jwks_aws_alb_resolve(oauth2_log_t *log, oauth2_jose_jwks_provider_t *provider, bool *refresh, cjose_header_t *hdr) { cjose_err err; char *url = NULL; const char *region = NULL; const char *signer = cjose_header_get(hdr, "signer", &err); const char *kid = cjose_header_get(hdr, "kid", &err); if (!signer || !kid) { oauth2_error(log, "missing 'signer' or 'kid' in JWT header: " "signer=%s, kid=%s", signer, kid); return NULL; } if (strcmp(signer, provider->alb_arn) != 0) { oauth2_error( log, "signer does not match configured ARN: signer=%s, arn=%s", signer, provider->alb_arn); return NULL; } if (provider->alb_base_url == NULL) { region = _oauth2_jose_jwks_aws_alb_region(provider->alb_arn); if (!region) { oauth2_error( log, "failed to extract region from ARN: arn=%s", provider->alb_arn); return NULL; } url = _oauth2_stradd4(NULL, "https://public-keys.auth.elb.", region, ".amazonaws.com/", kid); } else { url = oauth2_stradd(NULL, provider->alb_base_url, kid, NULL); } oauth2_debug(log, "constructed ALB JWKs URL: %s", url); provider->jwks_uri->endpoint->url = url; oauth2_jose_jwk_list_t *result = _oauth2_jose_jwks_resolve_from_uri( log, provider, refresh, _oauth2_jose_jwks_eckey_url_resolve_response_callback); provider->jwks_uri->endpoint->url = NULL; oauth2_mem_free(url); return result; } /* oauth2_jose_jwk_list_t * oauth2_jose_jwks_resolve(oauth2_log_t *log, oauth2_cfg_token_verify_t *verify, bool *refresh) { // oauth2_jose_jwk_list_t *list = NULL, *last = NULL; oauth2_jose_jwt_verify_ctx_t *ptr = NULL; oauth2_jose_jwk_list_t *jwks = NULL; oauth2_debug(log, "enter"); if ((verify == NULL) || (verify->ctx == NULL)) goto end; ptr = (oauth2_jose_jwt_verify_ctx_t *)verify->ctx->ptr; jwks = ptr->jwks_provider->resolve(log, ptr->jwks_provider, refresh); end: oauth2_debug(log, "leave: %p", jwks); return jwks; } */ bool oauth2_jose_jwk_thumbprint(oauth2_log_t *log, const cjose_jwk_t *jwk, unsigned char **hash_bytes, unsigned int *hash_bytes_len) { bool rc = false; char *s_payload = NULL, *s_compact = NULL; json_t *json_payload = NULL, *json_comp = NULL; cjose_err err; s_payload = cjose_jwk_to_json(jwk, false, &err); if (s_payload == NULL) { oauth2_error(log, "cjose_jwk_to_json failed: %s", err.message); goto end; } if (oauth2_json_decode_object(log, s_payload, &json_payload) == false) { oauth2_error(log, "decoding JWK JSON failed"); goto end; } json_comp = json_object(); switch (cjose_jwk_get_kty(jwk, &err)) { case CJOSE_JWK_KTY_EC: json_object_set(json_comp, "crv", json_object_get(json_payload, "crv")); json_object_set(json_comp, "kty", json_object_get(json_payload, "kty")); json_object_set(json_comp, "x", json_object_get(json_payload, "x")); json_object_set(json_comp, "y", json_object_get(json_payload, "y")); break; case CJOSE_JWK_KTY_RSA: json_object_set(json_comp, "e", json_object_get(json_payload, "e")); json_object_set(json_comp, "kty", json_object_get(json_payload, "kty")); json_object_set(json_comp, "n", json_object_get(json_payload, "n")); break; case CJOSE_JWK_KTY_OCT: json_object_set(json_comp, "k", json_object_get(json_payload, "k")); json_object_set(json_comp, "kty", json_object_get(json_payload, "kty")); break; default: oauth2_error(log, "unknown kty"); goto end; } s_compact = oauth2_json_encode(log, json_comp, JSON_PRESERVE_ORDER | JSON_COMPACT); if (oauth2_jose_hash_bytes(log, OAUTH2_JOSE_OPENSSL_ALG_SHA256, (const unsigned char *)s_compact, strlen(s_compact), hash_bytes, hash_bytes_len) == false) { oauth2_error(log, "oauth2_jose_hash_bytes failed"); goto end; } rc = true; end: if (s_compact) oauth2_mem_free(s_compact); if (s_payload) cjose_get_dealloc()(s_payload); if (json_payload) json_decref(json_payload); if (json_comp) json_decref(json_comp); return rc; } liboauth2-2.1.0/src/jose_int.h000066400000000000000000000105601475305260400161650ustar00rootroot00000000000000#ifndef _OAUTH2_JOSE_INT_H_ #define _OAUTH2_JOSE_INT_H_ /*************************************************************************** * * Copyright (C) 2018-2025 - ZmartZone Holding BV * * 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. * * @Author: Hans Zandbelt - hans.zandbelt@openidc.com * **************************************************************************/ #include "oauth2/cfg.h" #include "oauth2/log.h" #include "oauth2/util.h" #include "cfg_int.h" typedef struct oauth2_jose_jwk_t { cjose_jwk_t *jwk; char *kid; } oauth2_jose_jwk_t; typedef struct oauth2_jose_jwk_list_t { oauth2_jose_jwk_t *jwk; struct oauth2_jose_jwk_list_t *next; } oauth2_jose_jwk_list_t; typedef struct oauth2_uri_ctx_t { oauth2_cfg_endpoint_t *endpoint; oauth2_cache_t *cache; oauth2_time_t expiry_s; } oauth2_uri_ctx_t; typedef enum oauth2_jose_jwks_provider_type_t { OAUTH2_JOSE_JWKS_PROVIDER_LIST, OAUTH2_JOSE_JWKS_PROVIDER_JWKS_URI, OAUTH2_JOSE_JWKS_PROVIDER_ECKEY_URI, OAUTH2_JOSE_JWKS_PROVIDER_AWS_ALB } oauth2_jose_jwks_provider_type_t; typedef struct oauth2_jose_jwks_provider_t oauth2_jose_jwks_provider_t; typedef oauth2_jose_jwk_list_t *( oauth2_jose_jwks_resolve_cb_t)(oauth2_log_t *, oauth2_jose_jwks_provider_t *, bool *, cjose_header_t *hdr); typedef struct oauth2_jose_jwks_provider_t { oauth2_jose_jwks_provider_type_t type; oauth2_jose_jwks_resolve_cb_t *resolve; // NB: avoid union because of compiler/memory issues // OAUTH2_JOSE_JWKS_PROVIDER_JWKS_URI and // OAUTH2_JOSE_JWKS_PROVIDER_ECKEY_URI oauth2_uri_ctx_t *jwks_uri; // OAUTH2_JOSE_JWKS_PROVIDER_LIST oauth2_jose_jwk_list_t *jwks; // OAUTH2_JOSE_JWKS_PROVIDER_AWS_ALB char *alb_arn; char *alb_base_url; } oauth2_jose_jwks_provider_t; _OAUTH2_CFG_CTX_TYPE_START(oauth2_jose_jwt_verify_ctx) oauth2_jose_jwks_provider_t *jwks_provider; char *issuer; oauth2_jose_jwt_validate_claim_t iss_validate; oauth2_jose_jwt_validate_claim_t exp_validate; oauth2_jose_jwt_validate_claim_t iat_validate; oauth2_uint_t iat_slack_before; oauth2_uint_t iat_slack_after; _OAUTH2_CFG_CTX_TYPE_END(oauth2_jose_jwt_verify_ctx) void *oauth2_uri_ctx_init(oauth2_log_t *log); void *oauth2_uri_ctx_clone(oauth2_log_t *log, void *c); void oauth2_uri_ctx_free(oauth2_log_t *log, void *c); _OAUTH_CFG_CTX_CALLBACK(oauth2_jose_verify_options_jwk_set_plain); _OAUTH_CFG_CTX_CALLBACK(oauth2_jose_verify_options_jwk_set_base64); _OAUTH_CFG_CTX_CALLBACK(oauth2_jose_verify_options_jwk_set_base64url); _OAUTH_CFG_CTX_CALLBACK(oauth2_jose_verify_options_jwk_set_hex); _OAUTH_CFG_CTX_CALLBACK(oauth2_jose_verify_options_jwk_set_pem); _OAUTH_CFG_CTX_CALLBACK(oauth2_jose_verify_options_jwk_set_pubkey); _OAUTH_CFG_CTX_CALLBACK(oauth2_jose_verify_options_jwk_set_jwk); _OAUTH_CFG_CTX_CALLBACK(oauth2_jose_verify_options_jwk_set_jwks_uri); _OAUTH_CFG_CTX_CALLBACK(oauth2_jose_verify_options_jwk_set_eckey_uri); _OAUTH_CFG_CTX_CALLBACK(oauth2_jose_verify_options_jwk_set_aws_alb); char *oauth2_jose_resolve_from_uri(oauth2_log_t *log, oauth2_uri_ctx_t *uri_ctx, bool *refresh); char *oauth2_jose_options_uri_ctx(oauth2_log_t *log, const char *value, const oauth2_nv_list_t *params, oauth2_uri_ctx_t *ctx, const char *prefix); void *oauth2_jose_jwt_verify_ctx_init(oauth2_log_t *log); void *oauth2_jose_jwt_verify_ctx_clone(oauth2_log_t *log, void *s); void oauth2_jose_jwt_verify_ctx_free(oauth2_log_t *log, void *c); bool oauth2_jose_jwt_verify_set_options( oauth2_log_t *log, oauth2_jose_jwt_verify_ctx_t *jwt_verify, oauth2_jose_jwks_provider_type_t type, const oauth2_nv_list_t *params); char *oauth2_jose_jwt_header_peek(oauth2_log_t *log, const char *compact_encoded_jwt, const char **alg); bool oauth2_jose_jwt_validate_iat(oauth2_log_t *log, const json_t *json_payload, oauth2_jose_jwt_validate_claim_t validate, oauth2_uint_t slack_before, oauth2_uint_t slack_after); #endif /* _OAUTH2_JOSE_INT_H_ */ liboauth2-2.1.0/src/jq.c000066400000000000000000000100241475305260400147530ustar00rootroot00000000000000/*************************************************************************** * * Copyright (C) 2018-2025 - ZmartZone Holding BV * * 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. * * @Author: Hans Zandbelt - hans.zandbelt@openidc.com * **************************************************************************/ #include "oauth2/jq.h" #include "oauth2/jose.h" #include "oauth2/mem.h" #include "oauth2/util.h" #include "jq.h" static char *oauth2_jq_exec(oauth2_log_t *log, jq_state *jq, struct jv_parser *parser) { 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 = oauth2_strdup(jv_string_value(str)); oauth2_debug(log, "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); oauth2_error(log, "invalid: %s", jv_string_value(msg)); jv_free(msg); } else { jv_free(value); } return rv; } #define OAUTH2_JQ_FILTER_EXPIRE_DEFAULT 600 #define OAUTH2_JQ_FILTER_CACHE_TTL_ENVVAR "OAUTH2_JQ_FILTER_CACHE_TTL" static int oauth2_jq_filter_cache_ttl(oauth2_log_t *log) { // const char *s_ttl = apr_table_get(r->subprocess_env, // OAUTH2_JQ_FILTER_CACHE_TTL_ENVVAR); return _oauth2_str_to_int(s_ttl, // OAUTH2_JQ_FILTER_EXPIRE_DEFAULT); return OAUTH2_JQ_FILTER_EXPIRE_DEFAULT; } bool oauth2_jq_filter_compile(oauth2_log_t *log, const char *filter, jq_state **r_jq) { bool rc = false; jq_state *jq = jq_init(); if (jq == NULL) { oauth2_error(log, "jq_init failed"); goto end; } if (jq_compile(jq, filter) == 0) { oauth2_error(log, "jq_compile failed"); goto end; } if (r_jq != NULL) *r_jq = jq; rc = true; end: if ((rc == false) || (r_jq == NULL)) { if (jq != NULL) jq_teardown(&jq); } return rc; } bool oauth2_jq_filter(oauth2_log_t *log, oauth2_cache_t *cache, const char *input, const char *filter, char **result) { bool rc = false; jq_state *jq = NULL; struct jv_parser *parser = NULL; int ttl = 0; char *key = NULL, *skey = NULL; char *value = NULL; if (filter == NULL) { oauth2_debug(log, "filter is NULL, abort"); goto end; } if (input == NULL) { oauth2_debug(log, "input is NULL, set to empty object"); input = "{}"; } oauth2_debug(log, "processing input: %s", input); oauth2_debug(log, "processing filter: %s", filter); ttl = oauth2_jq_filter_cache_ttl(log); if ((cache != NULL) && (ttl > 0)) { skey = oauth2_stradd(NULL, (char *)input, filter, NULL); if (oauth2_jose_hash2s(log, OAUTH2_JOSE_OPENSSL_ALG_SHA256, skey, &key) == false) { oauth2_error(log, "oauth2_jose_hash2s returned an error"); goto end; } oauth2_cache_get(log, cache, key, &value); if (value != NULL) { oauth2_debug(log, "return cached result: %s", value); *result = value; rc = true; goto end; } } if (oauth2_jq_filter_compile(log, filter, &jq) == false) goto end; parser = jv_parser_new(0); if (parser == NULL) { oauth2_error(log, "jv_parser_new returned NULL"); goto end; } jv_parser_set_buf(parser, input, strlen(input), 0); *result = oauth2_jq_exec(log, jq, parser); if ((cache != NULL) && (*result != NULL) && (ttl != 0)) { oauth2_debug(log, "caching result: %s", *result); oauth2_cache_set(log, cache, key, *result, ttl); } rc = true; end: if (key) oauth2_mem_free(key); if (skey) oauth2_mem_free(skey); if (parser) jv_parser_free(parser); if (jq) jq_teardown(&jq); return rc; } liboauth2-2.1.0/src/log.c000066400000000000000000000145211475305260400151300ustar00rootroot00000000000000/*************************************************************************** * * Copyright (C) 2018-2025 - ZmartZone Holding BV * * 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. * * @Author: Hans Zandbelt - hans.zandbelt@openidc.com * **************************************************************************/ // need this at the top for vasprintf #include "oauth2/log.h" #include "oauth2/mem.h" #include "util_int.h" #include #include typedef struct oauth2_log_sink_t { oauth2_log_level_t level; oauth2_log_function_t callback; void *ctx; } oauth2_log_sink_t; // note this is fastest, but must maintain the order of the enum... static const char *_oauth2_log_level2str[] = {"ERR", "WRN", "NOT", "INF", "DBG", "TR1", "TR2"}; typedef struct oauth2_log_sink_list_elem_t { oauth2_log_sink_t *sink; struct oauth2_log_sink_list_elem_t *next; } oauth2_log_sink_list_elem_t; typedef struct oauth2_log_sink_list_t { oauth2_log_sink_list_elem_t *first; oauth2_log_sink_list_elem_t *last; } oauth2_log_sink_list_t; typedef struct oauth2_log_t { oauth2_log_sink_list_t sinks; } oauth2_log_t; oauth2_log_sink_t *oauth2_log_sink_create(oauth2_log_level_t level, oauth2_log_function_t callback, void *ctx) { oauth2_log_sink_t *sink = oauth2_mem_alloc(sizeof(oauth2_log_sink_t)); sink->callback = callback; sink->level = level; sink->ctx = ctx; return sink; } void *oauth2_log_sink_ctx_get(oauth2_log_sink_t *sink) { return sink->ctx; } oauth2_log_function_t oauth2_log_sink_callback_get(oauth2_log_sink_t *sink) { return sink->callback; } void oauth2_log_sink_add(oauth2_log_t *log, oauth2_log_sink_t *add) { oauth2_log_sink_list_elem_t *ptr = (oauth2_log_sink_list_elem_t *)oauth2_mem_alloc( sizeof(oauth2_log_sink_list_elem_t)); ; ptr->sink = add; ptr->next = NULL; if (log->sinks.first == NULL) { log->sinks.first = ptr; log->sinks.last = ptr; } else { log->sinks.last->next = ptr; } } void oauth2_log_sink_level_set(oauth2_log_sink_t *sink, oauth2_log_level_t level) { sink->level = level; } static void oauth2_log_std(FILE *std, oauth2_log_sink_t *sink, const char *filename, unsigned long line, const char *function, oauth2_log_level_t level, const char *msg) { // TODO: make a print-to-string function for this generic prefix? fprintf(std, "[%s:%lu:%s:%s] %s\n", filename, line, function, _oauth2_log_level2str[level], msg); } static void oauth2_log_std_err(oauth2_log_sink_t *sink, const char *filename, unsigned long line, const char *function, oauth2_log_level_t level, const char *msg) { oauth2_log_std(stderr, sink, filename, line, function, level, msg); } static void oauth2_log_std_out(oauth2_log_sink_t *sink, const char *filename, unsigned long line, const char *function, oauth2_log_level_t level, const char *msg) { oauth2_log_std(stdout, sink, filename, line, function, level, msg); } oauth2_log_sink_t oauth2_log_sink_stderr = {OAUTH2_LOG_INFO, oauth2_log_std_err, NULL}; oauth2_log_sink_t oauth2_log_sink_stdout = {OAUTH2_LOG_INFO, oauth2_log_std_out, NULL}; // API #ifdef _MSC_VER int vasprintf(char **strp, const char *fmt, va_list ap) { // _vscprintf tells you how big the buffer needs to be int len = _vscprintf(fmt, ap); if (len == -1) { return -1; } size_t size = (size_t)len + 1; char *str = malloc(size); if (!str) { return -1; } // _vsprintf_s is the "secure" version of vsprintf int r = vsprintf_s(str, len + 1, fmt, ap); if (r == -1) { free(str); return -1; } *strp = str; return r; } #endif void oauth2_log(oauth2_log_t *log, const char *filename, unsigned long line, const char *function, oauth2_log_level_t level, const char *fmt, ...) { va_list ap; oauth2_log_sink_list_elem_t *ptr; char *msg = NULL; int rc = 0; if ((log == NULL) || (log->sinks.first == NULL) || (fmt == NULL)) goto end; va_start(ap, fmt); rc = vasprintf(&msg, fmt, ap); // TODO: can't get this to work...? // rc = oauth2_sprintf(&msg, fmt, ap); (void)rc; va_end(ap); if (msg) { for (ptr = log->sinks.first; ptr != NULL; ptr = ptr->next) { if (level > ptr->sink->level) continue; ptr->sink->callback(ptr->sink, filename, line, function, level, msg); } // TODO: can't get this to work...? // oauth2_mem_free(msg); free(msg); } end: return; } oauth2_log_t *oauth2_log_init(oauth2_log_level_t level, oauth2_log_sink_t *sink) { oauth2_log_t *log = (oauth2_log_t *)oauth2_mem_alloc(sizeof(oauth2_log_t)); if (log == NULL) goto end; log->sinks.first = NULL; log->sinks.last = NULL; oauth2_log_sink_add(log, (sink != NULL) ? sink : &oauth2_log_sink_stderr); log->sinks.first->sink->level = level; end: return log; } void oauth2_log_free(oauth2_log_t *log) { oauth2_log_sink_list_elem_t *ptr = NULL; if (log == NULL) goto end; while ((ptr = log->sinks.first)) { log->sinks.first = log->sinks.first->next; if ((ptr->sink != &oauth2_log_sink_stderr) && (ptr->sink != &oauth2_log_sink_stdout)) oauth2_mem_free(ptr->sink); oauth2_mem_free(ptr); } log->sinks.last = NULL; oauth2_mem_free(log); end: return; } /* static int oauth2_log_level2aplog[] = { APLOG_ERR, APLOG_WARNING, APLOG_NOTICE, APLOG_INFO, APLOG_DEBUG, APLOG_TRACE1 }; void oauth2_log_backend_ap_log_rerror(void *log_log, const char *filename, unsigned long line, const char *function, oauth2_log_level_t level, const char *fmt, ...) { request_rec *r = (request_rec *)log_log; ap_log_rerror(filename, line, APLOG_MODULE_INDEX, oauth2_log_level2aplog[level], 0, r,"%s: %s", function, apr_psprintf(r->pool, fmt, ##__VA_ARGS__)) } */ liboauth2-2.1.0/src/mem.c000066400000000000000000000046411475305260400151270ustar00rootroot00000000000000/*************************************************************************** * * Copyright (C) 2018-2025 - ZmartZone Holding BV * * 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. * * @Author: Hans Zandbelt - hans.zandbelt@openidc.com * **************************************************************************/ #include "oauth2/mem.h" #include "cjose/util.h" #include "oauth2/util.h" #include "util_int.h" #include #include #include "curl/curl.h" static void *oauth2_mem_calloc_callback(size_t nmemb, size_t size) { return oauth2_mem_alloc(nmemb * size); } void oauth2_mem_set_alloc_funcs(oauth2_mem_alloc_fn_t alloc, oauth2_mem_realloc_fn_t realloc, oauth2_mem_dealloc_fn_t dealloc) { cjose_set_alloc_funcs(alloc, realloc, dealloc); curl_global_init_mem(CURL_GLOBAL_ALL, alloc, dealloc, realloc, oauth2_strdup, oauth2_mem_calloc_callback); } void oauth2_mem_set_alloc_ex_funcs(oauth2_mem_alloc3_fn_t alloc3, oauth2_mem_realloc3_fn_t realloc3, oauth2_mem_dealloc3_fn_t dealloc3) { cjose_set_alloc_ex_funcs(alloc3, realloc3, dealloc3); curl_global_init_mem(CURL_GLOBAL_ALL, cjose_get_alloc(), cjose_get_dealloc(), cjose_get_realloc(), oauth2_strdup, oauth2_mem_calloc_callback); } oauth2_mem_alloc_fn_t oauth2_mem_get_alloc() { return cjose_get_alloc(); } oauth2_mem_alloc3_fn_t oauth2_mem_get_alloc3() { return cjose_get_alloc3(); } oauth2_mem_realloc_fn_t oauth2_mem_get_realloc() { return cjose_get_realloc(); } oauth2_mem_realloc3_fn_t oauth2_mem_get_realloc3() { return cjose_get_realloc3(); } oauth2_mem_dealloc_fn_t oauth2_mem_get_dealloc() { return cjose_get_dealloc(); } oauth2_mem_dealloc3_fn_t oauth2_mem_get_dealloc3() { return cjose_get_dealloc3(); } void *oauth2_mem_alloc(size_t size) { void *ptr = oauth2_mem_get_alloc()(size); if (ptr) memset(ptr, 0, size); return ptr; } void oauth2_mem_free(void *ptr) { oauth2_mem_get_dealloc()(ptr); } liboauth2-2.1.0/src/oauth2.c000066400000000000000000000601041475305260400155470ustar00rootroot00000000000000/*************************************************************************** * * Copyright (C) 2018-2025 - ZmartZone Holding BV * * 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. * * @Author: Hans Zandbelt - hans.zandbelt@openidc.com * **************************************************************************/ #include "oauth2/oauth2.h" #include "cfg_int.h" #include "oauth2/cfg.h" #include "oauth2/http.h" #include "oauth2/jose.h" #include "oauth2/mem.h" #include "oauth2/util.h" #include "jose_int.h" #include "oauth2_int.h" #include "util_int.h" #include #include #include /* * auth */ #define OAUTH2_CLIENT_ASSERTION "client_assertion" #define OAUTH2_CLIENT_ASSERTION_TYPE "client_assertion_type" #define OAUTH2_CLIENT_ASSERTION_TYPE_JWT_BEARER \ "urn:ietf:params:oauth:client-assertion-type:jwt-bearer" static bool _oauth2_add_signed_jwt(oauth2_log_t *log, cjose_jwk_t *jwk, const char *alg, const char *client_id, const char *aud, oauth2_nv_list_t *params) { bool rc = false; char *jwt = NULL; oauth2_debug(log, "enter"); jwt = oauth2_jwt_create(log, jwk, alg, client_id, client_id, client_id, aud, 60, true, true, NULL); if (jwt == NULL) goto end; oauth2_nv_list_set(log, params, OAUTH2_CLIENT_ASSERTION_TYPE, OAUTH2_CLIENT_ASSERTION_TYPE_JWT_BEARER); oauth2_nv_list_set(log, params, OAUTH2_CLIENT_ASSERTION, jwt); rc = true; end: if (jwt) oauth2_mem_free(jwt); oauth2_debug(log, "leave"); return rc; } static bool oauth2_auth_client_secret_jwt(oauth2_log_t *log, oauth2_http_call_ctx_t *ctx, const oauth2_cfg_endpoint_auth_t *auth, oauth2_nv_list_t *params) { bool rc = false; oauth2_debug(log, "enter"); if ((auth->client_secret_jwt.client_id == NULL) || (auth->client_secret_jwt.jwk == NULL) || (auth->client_secret_jwt.aud == NULL)) goto end; rc = _oauth2_add_signed_jwt(log, auth->client_secret_jwt.jwk, CJOSE_HDR_ALG_HS256, auth->client_secret_jwt.client_id, auth->client_secret_jwt.aud, params); end: oauth2_debug(log, "leave"); return rc; } static bool oauth2_auth_private_key_jwt(oauth2_log_t *log, oauth2_http_call_ctx_t *ctx, const oauth2_cfg_endpoint_auth_t *auth, oauth2_nv_list_t *params) { bool rc = false; cjose_err err; // cjose_jwk_t *jwk = NULL; oauth2_debug(log, "enter"); if ((auth->private_key_jwt.client_id == NULL) || (auth->private_key_jwt.jwk == NULL) || (auth->private_key_jwt.aud == NULL)) goto end; // jwk = cjose_jwk_import(cser_jwk, strlen(cser_jwk), &err); // if (jwk == NULL) { // oauth2_error(log, "cjose_jwk_import failed: %s", // err.message); goto end; // } if (cjose_jwk_get_kty(auth->private_key_jwt.jwk, &err) != CJOSE_JWK_KTY_RSA) { oauth2_error(log, "jwk is not an RSA key: %s", err.message); goto end; } rc = _oauth2_add_signed_jwt( log, auth->private_key_jwt.jwk, CJOSE_HDR_ALG_RS256, auth->private_key_jwt.client_id, auth->private_key_jwt.aud, params); end: oauth2_debug(log, "leave"); return rc; } static bool oauth2_auth_client_secret_basic(oauth2_log_t *log, oauth2_http_call_ctx_t *ctx, const oauth2_cfg_endpoint_auth_t *auth, oauth2_nv_list_t *params) { bool rc = false; if ((auth->client_secret_basic.client_id == NULL) || (auth->client_secret_basic.client_secret == NULL)) goto end; rc = oauth2_http_call_ctx_basic_auth_set( log, ctx, auth->client_secret_basic.client_id, auth->client_secret_basic.client_secret, true); end: return rc; } static bool oauth2_auth_client_secret_post(oauth2_log_t *log, oauth2_http_call_ctx_t *ctx, const oauth2_cfg_endpoint_auth_t *auth, oauth2_nv_list_t *params) { bool rc = false; if ((auth->client_secret_post.client_id == NULL) || (auth->client_secret_post.client_secret == NULL)) goto end; rc = oauth2_nv_list_add(log, params, OAUTH2_CLIENT_ID, auth->client_secret_post.client_id); if (rc == false) goto end; rc = oauth2_nv_list_add(log, params, OAUTH2_CLIENT_SECRET, auth->client_secret_post.client_secret); end: return rc; } static bool oauth2_auth_client_cert(oauth2_log_t *log, oauth2_http_call_ctx_t *ctx, const oauth2_cfg_endpoint_auth_t *auth, oauth2_nv_list_t *params) { bool rc = false; if ((auth->client_cert.certfile == NULL) || (auth->client_cert.keyfile == NULL)) goto end; rc = oauth2_http_auth_client_cert(log, auth->client_cert.certfile, auth->client_cert.keyfile, ctx); end: return rc; } bool oauth2_auth_basic(oauth2_log_t *log, oauth2_http_call_ctx_t *ctx, const oauth2_cfg_endpoint_auth_t *auth, oauth2_nv_list_t *params) { bool rc = false; rc = oauth2_http_auth_basic(log, auth->basic.username, auth->basic.password, ctx); return rc; } typedef bool(oauth2_http_ctx_add_auth_cb_t)( oauth2_log_t *log, oauth2_http_call_ctx_t *ctx, const oauth2_cfg_endpoint_auth_t *auth, oauth2_nv_list_t *params); typedef struct oauth2_http_ctx_auth_cb_ctx_t { oauth2_cfg_endpoint_auth_type_t type; oauth2_http_ctx_add_auth_cb_t *add_callback; } oauth2_http_ctx_auth_cb_ctx_t; // clang-format off static oauth2_http_ctx_auth_cb_ctx_t oauth2_http_ctx_auth_cb[] = { { OAUTH2_ENDPOINT_AUTH_CLIENT_SECRET_BASIC, oauth2_auth_client_secret_basic }, { OAUTH2_ENDPOINT_AUTH_CLIENT_SECRET_POST, oauth2_auth_client_secret_post }, { OAUTH2_ENDPOINT_AUTH_CLIENT_SECRET_JWT, oauth2_auth_client_secret_jwt }, { OAUTH2_ENDPOINT_AUTH_PRIVATE_KEY_JWT, oauth2_auth_private_key_jwt }, { OAUTH2_ENDPOINT_AUTH_CLIENT_CERT, oauth2_auth_client_cert }, { OAUTH2_ENDPOINT_AUTH_BASIC, oauth2_auth_basic }, // must be last { OAUTH2_ENDPOINT_AUTH_NONE, NULL }, }; // clang-format on bool oauth2_http_ctx_auth_add(oauth2_log_t *log, oauth2_http_call_ctx_t *ctx, const oauth2_cfg_endpoint_auth_t *auth, oauth2_nv_list_t *params) { bool rc = false; int i = 0; if ((ctx == NULL) || (auth == NULL)) goto end; if (auth->type == OAUTH2_ENDPOINT_AUTH_NONE) { rc = true; goto end; } i = 0; while (oauth2_http_ctx_auth_cb[i].type != OAUTH2_ENDPOINT_AUTH_NONE) { if (auth->type == oauth2_http_ctx_auth_cb[i].type) { rc = oauth2_http_ctx_auth_cb[i].add_callback( log, ctx, auth, params); goto end; } i++; } end: return rc; } /* * introspect */ _OAUTH2_CFG_CTX_TYPE_START(oauth2_introspect_ctx) oauth2_cfg_endpoint_t *endpoint; char *token_param_name; oauth2_nv_list_t *params; _OAUTH2_CFG_CTX_TYPE_END(oauth2_introspect_ctx) _OAUTH2_CFG_CTX_INIT_START(oauth2_introspect_ctx) ctx->endpoint = NULL; ctx->token_param_name = NULL; ctx->params = NULL; _OAUTH2_CFG_CTX_INIT_END _OAUTH2_CFG_CTX_CLONE_START(oauth2_introspect_ctx) dst->endpoint = oauth2_cfg_endpoint_clone(log, src->endpoint); dst->token_param_name = oauth2_strdup(src->token_param_name); dst->params = oauth2_nv_list_clone(log, src->params); _OAUTH2_CFG_CTX_CLONE_END _OAUTH2_CFG_CTX_FREE_START(oauth2_introspect_ctx) if (ctx->endpoint) oauth2_cfg_endpoint_free(log, ctx->endpoint); if (ctx->token_param_name) oauth2_mem_free(ctx->token_param_name); if (ctx->params) oauth2_nv_list_free(log, ctx->params); _OAUTH2_CFG_CTX_FREE_END _OAUTH2_CFG_CTX_FUNCS(oauth2_introspect_ctx) #define OAUTH2_INTROSPECT_TOKEN "token" #define OAUTH2_INTROSPECT_TOKEN_TYPE_HINT "token_type_hint" #define OAUTH2_INTROSPECT_TOKEN_TYPE_HINT_ACCESS_TOKEN "access_token" #define OAUTH2_INTROSPECT_CLAIM_ACTIVE "active" static bool _oauth2_introspect_verify(oauth2_log_t *log, oauth2_introspect_ctx_t *ctx, const char *token, json_t **json_payload, char **s_payload) { bool rc = false; oauth2_nv_list_t *params = NULL; oauth2_http_call_ctx_t *http_ctx = NULL; json_t *active = NULL; oauth2_uint_t status_code = 0; oauth2_debug(log, "enter"); http_ctx = oauth2_http_call_ctx_init(log); if (http_ctx == NULL) goto end; if (oauth2_http_call_ctx_ssl_verify_set( log, http_ctx, oauth2_cfg_endpoint_get_ssl_verify(ctx->endpoint)) == false) goto end; oauth2_http_call_ctx_outgoing_proxy_set( log, http_ctx, oauth2_cfg_endpoint_get_outgoing_proxy(ctx->endpoint)); params = oauth2_nv_list_init(log); if (params == NULL) goto end; oauth2_nv_list_add(log, params, ctx->token_param_name ? ctx->token_param_name : OAUTH2_INTROSPECT_TOKEN, token); oauth2_nv_list_add(log, params, OAUTH2_INTROSPECT_TOKEN_TYPE_HINT, OAUTH2_INTROSPECT_TOKEN_TYPE_HINT_ACCESS_TOKEN); oauth2_nv_list_merge_into(log, ctx->params, params); if (oauth2_http_ctx_auth_add( log, http_ctx, oauth2_cfg_endpoint_get_auth(ctx->endpoint), params) == false) goto end; if (oauth2_http_post_form( log, oauth2_cfg_endpoint_get_url(ctx->endpoint), params, http_ctx, s_payload, &status_code) == false) goto end; if ((status_code < 200) || (status_code >= 300)) { rc = false; goto end; } if (oauth2_json_decode_check_error(log, *s_payload, json_payload) == false) goto end; active = json_object_get(*json_payload, OAUTH2_INTROSPECT_CLAIM_ACTIVE); if (active == NULL) { oauth2_error(log, "no claim \"%s\" found in introspection response", OAUTH2_INTROSPECT_CLAIM_ACTIVE); goto end; } if (json_is_boolean(active) == false) { oauth2_error(log, "claim \"%s\" in introspection response " "is not a boolean", OAUTH2_INTROSPECT_CLAIM_ACTIVE); goto end; } if (json_is_true(active) == false) { oauth2_error( log, "\"%s\" boolean object with value \"false\" found in " "response JSON object", OAUTH2_INTROSPECT_CLAIM_ACTIVE); goto end; } rc = true; // TODO: verify if returned content is JWT? how to call into existing // jwks_uri etc. code? end: if (rc == false) { if ((json_payload) && (*json_payload)) { json_decref(*json_payload); *json_payload = NULL; } if ((s_payload) && (*s_payload)) { oauth2_mem_free(*s_payload); *s_payload = NULL; } } if (params) oauth2_nv_list_free(log, params); if (http_ctx) oauth2_http_call_ctx_free(log, http_ctx); oauth2_debug(log, "leave: %d", rc); return rc; } static bool _oauth2_introspect_verify_callback( oauth2_log_t *log, oauth2_cfg_token_verify_t *verify, const char *token, json_t **json_payload, char **s_payload) { bool rc = false; oauth2_introspect_ctx_t *ctx = NULL; ctx = (oauth2_introspect_ctx_t *)verify->ctx->ptr; if ((verify == NULL) || (verify->ctx == NULL) || (verify->ctx->ptr == NULL)) goto end; rc = _oauth2_introspect_verify(log, ctx, token, json_payload, s_payload); end: return rc; } static char *_oauth2_verify_options_set_introspect_url_ctx( oauth2_log_t *log, const char *url, const oauth2_nv_list_t *params, oauth2_introspect_ctx_t *ctx) { char *rv = NULL; oauth2_debug(log, "enter"); ctx->endpoint = oauth2_cfg_endpoint_init(log); rv = oauth2_cfg_set_endpoint(log, ctx->endpoint, url, params, "introspect"); ctx->token_param_name = oauth2_strdup( oauth2_nv_list_get(log, params, "introspect.token_param_name")); if (oauth2_parse_form_encoded_params( log, oauth2_nv_list_get(log, params, "introspect.params"), &ctx->params) == false) rv = oauth2_strdup("oauth2_parse_form_encoded_params failed"); oauth2_debug(log, "leave: %s", rv); return rv; } _OAUTH_CFG_CTX_CALLBACK(oauth2_verify_options_set_introspect_url) { oauth2_cfg_token_verify_t *verify = (oauth2_cfg_token_verify_t *)ctx; char *rv = NULL; oauth2_debug(log, "enter"); verify->callback = _oauth2_introspect_verify_callback; verify->ctx->callbacks = &oauth2_introspect_ctx_funcs; verify->ctx->ptr = verify->ctx->callbacks->init(log); rv = _oauth2_verify_options_set_introspect_url_ctx( log, value, params, (oauth2_introspect_ctx_t *)verify->ctx->ptr); oauth2_debug(log, "leave: %s", rv); return rv; } _OAUTH2_CFG_CTX_TYPE_START(oauth2_metadata_ctx) oauth2_introspect_ctx_t *introspect; oauth2_jose_jwt_verify_ctx_t *jwks_uri_verify; oauth2_uri_ctx_t *metadata_uri; _OAUTH2_CFG_CTX_TYPE_END(oauth2_metadata_ctx) _OAUTH2_CFG_CTX_INIT_START(oauth2_metadata_ctx) ctx->introspect = oauth2_introspect_ctx_init(log); ctx->jwks_uri_verify = (oauth2_jose_jwt_verify_ctx_t *)oauth2_jose_jwt_verify_ctx_init(log); ctx->metadata_uri = oauth2_uri_ctx_init(log); _OAUTH2_CFG_CTX_INIT_END _OAUTH2_CFG_CTX_CLONE_START(oauth2_metadata_ctx) if (dst->introspect) oauth2_introspect_ctx_free(log, dst->introspect); dst->introspect = oauth2_introspect_ctx_clone(log, src->introspect); if (dst->jwks_uri_verify) oauth2_jose_jwt_verify_ctx_free(log, dst->jwks_uri_verify); dst->jwks_uri_verify = oauth2_jose_jwt_verify_ctx_clone(log, src->jwks_uri_verify); if (dst->metadata_uri) oauth2_uri_ctx_free(log, dst->metadata_uri); dst->metadata_uri = oauth2_uri_ctx_clone(log, src->metadata_uri); _OAUTH2_CFG_CTX_CLONE_END _OAUTH2_CFG_CTX_FREE_START(oauth2_metadata_ctx) if (ctx->introspect) oauth2_introspect_ctx_free(log, ctx->introspect); if (ctx->jwks_uri_verify) oauth2_jose_jwt_verify_ctx_free(log, ctx->jwks_uri_verify); if (ctx->metadata_uri) oauth2_uri_ctx_free(log, ctx->metadata_uri); _OAUTH2_CFG_CTX_FREE_END _OAUTH2_CFG_CTX_FUNCS(oauth2_metadata_ctx) static bool _oauth2_metadata_verify_callback(oauth2_log_t *log, oauth2_cfg_token_verify_t *verify, const char *token, json_t **json_payload, char **s_payload) { bool rc = false; oauth2_metadata_ctx_t *ptr = NULL; bool refresh = false; char *response = NULL; json_t *json_metadata = NULL, *json_issuer = NULL, *json_jwks_uri = NULL, *json_introspection_endpoint; oauth2_jose_jwt_verify_ctx_t *jwks_uri_verify = NULL; oauth2_introspect_ctx_t *introspect_ctx = NULL; const char *jwks_uri = NULL, *introspection_uri = NULL; char *peek = NULL; if ((verify == NULL) || (verify->ctx == NULL) || (verify->ctx->ptr == NULL)) goto end; ptr = (oauth2_metadata_ctx_t *)verify->ctx->ptr; response = oauth2_jose_resolve_from_uri(log, ptr->metadata_uri, &refresh); if (response == NULL) goto end; if (oauth2_json_decode_object(log, response, &json_metadata) == false) goto end; peek = oauth2_jose_jwt_header_peek(log, token, NULL); if (peek) { oauth2_debug(log, "JWT token: header=%s", peek); goto jwks_uri; } else { oauth2_debug(log, "no JWT token: introspect it"); goto introspect; } jwks_uri: json_jwks_uri = json_object_get(json_metadata, "jwks_uri"); if (json_jwks_uri) { if (json_is_string(json_jwks_uri)) { jwks_uri = json_string_value(json_jwks_uri); } else { oauth2_warn(log, "\"jwks_uri\" value is not a string"); } } if (jwks_uri) { // NB: need a copy because we're going to modify a static/shared // config setting jwks_uri_verify = oauth2_jose_jwt_verify_ctx_clone(log, ptr->jwks_uri_verify); json_issuer = json_object_get(json_metadata, "issuer"); if (json_issuer) { if (json_is_string(json_issuer)) { jwks_uri_verify->issuer = oauth2_strdup( json_string_value(json_issuer)); } else { oauth2_error( log, "\"issuer\" value is not a string"); goto end; } } else { oauth2_error( log, "required \"issuer\" value not found in metadata"); goto end; } oauth2_cfg_endpoint_set_url( jwks_uri_verify->jwks_provider->jwks_uri->endpoint, jwks_uri); rc = oauth2_jose_jwt_verify(log, jwks_uri_verify, token, json_payload, s_payload); if (rc == true) goto end; } introspect: json_introspection_endpoint = json_object_get(json_metadata, "introspection_endpoint"); if (json_introspection_endpoint) { if (json_is_string(json_introspection_endpoint)) { introspection_uri = json_string_value(json_introspection_endpoint); } else { oauth2_warn( log, "\"introspection_endpoint\" value is not a string"); } } if (introspection_uri) { // NB: need a copy because we're going to modify a static/shared // config setting introspect_ctx = oauth2_introspect_ctx_clone(log, ptr->introspect); oauth2_cfg_endpoint_set_url(introspect_ctx->endpoint, introspection_uri); rc = _oauth2_introspect_verify(log, introspect_ctx, token, json_payload, s_payload); if (rc == true) goto end; } end: if (peek) oauth2_mem_free(peek); if (json_metadata) json_decref(json_metadata); if (response) oauth2_mem_free(response); if (jwks_uri_verify) oauth2_jose_jwt_verify_ctx_free(log, jwks_uri_verify); if (introspect_ctx) oauth2_introspect_ctx_free(log, introspect_ctx); return rc; } _OAUTH_CFG_CTX_CALLBACK(oauth2_verify_options_set_metadata_url) { oauth2_cfg_token_verify_t *verify = (oauth2_cfg_token_verify_t *)ctx; char *rv = NULL; oauth2_metadata_ctx_t *ptr = NULL; oauth2_debug(log, "enter"); verify->callback = _oauth2_metadata_verify_callback; verify->ctx->callbacks = &oauth2_metadata_ctx_funcs; verify->ctx->ptr = verify->ctx->callbacks->init(log); ptr = (oauth2_metadata_ctx_t *)verify->ctx->ptr; rv = _oauth2_verify_options_set_introspect_url_ctx(log, value, params, ptr->introspect); if (rv != NULL) goto end; // TODO: should we not combine these next 2 calls in a single function? if (oauth2_jose_jwt_verify_set_options( log, ptr->jwks_uri_verify, OAUTH2_JOSE_JWKS_PROVIDER_JWKS_URI, params) == false) { rv = oauth2_strdup("oauth2_jose_jwt_verify_set_options failed"); goto end; } rv = oauth2_jose_options_uri_ctx( log, value, params, ptr->jwks_uri_verify->jwks_provider->jwks_uri, "jwks_uri"); if (rv != NULL) { rv = oauth2_strdup( "oauth2_jose_options_uri_ctx failed for jwks_uri"); goto end; } rv = oauth2_jose_options_uri_ctx(log, value, params, ptr->metadata_uri, "metadata"); if (rv != NULL) { rv = oauth2_strdup( "oauth2_jose_options_uri_ctx failed for metadata"); goto end; } end: oauth2_debug(log, "leave: %s", rv); return rv; } static const char * oauth2_mtls_verify_policy2str(const oauth2_cfg_mtls_verify_policy_t policy) { if (policy == OAUTH2_MTLS_VERIFY_POLICY_OPTIONAL) return "optional"; if (policy == OAUTH2_MTLS_VERIFY_POLICY_REQUIRED) return "required"; return "unset"; } static char * oauth2_mtls_client_cert_fingerprint(oauth2_log_t *log, oauth2_cfg_mtls_verify_t *mtls_verify, const oauth2_http_request_t *request) { BIO *input = NULL; X509 *x509 = NULL; unsigned char md[EVP_MAX_MD_SIZE]; unsigned int md_len; char *fingerprint = NULL; const char *cert_pem = NULL; if (mtls_verify == NULL) goto end; cert_pem = oauth2_http_request_context_get(log, request, OAUTH2_TLS_CERT_VAR_NAME); oauth2_debug(log, "request context variable: %s=%s", OAUTH2_TLS_CERT_VAR_NAME, cert_pem); if (cert_pem == NULL) goto end; input = BIO_new(BIO_s_mem()); if (input == NULL) { oauth2_error(log, "memory allocation BIO_new/BIO_s_mem"); goto end; } if (BIO_puts(input, cert_pem) <= 0) { oauth2_error(log, "memory allocation BIO_new/BIO_s_mem"); goto end; } x509 = PEM_read_bio_X509_AUX(input, NULL, NULL, NULL); if (x509 == NULL) { oauth2_error(log, "could not decode x509 cert from presumably " "PEM encoded env var value"); goto end; } if (!X509_digest(x509, EVP_sha256(), md, &md_len)) { oauth2_error(log, "X509_digest failed"); goto end; } oauth2_base64url_encode(log, md, md_len, &fingerprint); end: if (input) BIO_free(input); if (x509) X509_free(x509); return fingerprint; } oauth2_cfg_mtls_verify_policy_t oauth2_mtls_verify_policy_get(const oauth2_cfg_mtls_verify_t *mtls_verify) { if ((mtls_verify == NULL) || (mtls_verify->policy == OAUTH2_CFG_UINT_UNSET)) return OAUTH2_MTLS_VERIFY_POLICY_REQUIRED; return mtls_verify->policy; } static bool oauth2_mtls_validate_cnf_x5t_s256( oauth2_log_t *log, oauth2_cfg_mtls_verify_t *mtls_verify, const oauth2_http_request_t *request, const char *x5t_256_str) { bool rc = false; char *fingerprint = NULL; fingerprint = oauth2_mtls_client_cert_fingerprint(log, mtls_verify, request); if (fingerprint == NULL) { oauth2_debug(log, "no certificate (fingerprint) provided"); goto end; } if (strcmp(fingerprint, x5t_256_str) != 0) { oauth2_warn(log, "fingerprint of provided cert (%s) does not match " "cnf[\"x5t#S256\"] (%s)", fingerprint, x5t_256_str); goto end; } oauth2_debug( log, "fingerprint of provided cert (%s) matches cnf[\"x5t#S256\"]", fingerprint); rc = true; end: if (fingerprint) oauth2_mem_free(fingerprint); return rc; } #define OAUTH2_CLAIM_CNF "cnf" #define OAUTH2_CLAIM_CNF_X5T_S256 "x5t#S256" static bool oauth2_mtls_token_verify(oauth2_log_t *log, oauth2_cfg_mtls_verify_t *mtls_verify, const oauth2_http_request_t *request, json_t *jwt) { bool rc = false; char *cnf_x5t_s256_str = NULL; oauth2_debug(log, "enter: policy=%s", oauth2_mtls_verify_policy2str(mtls_verify->policy)); json_t *cnf = json_object_get(jwt, OAUTH2_CLAIM_CNF); // cnf = json_object(); // json_object_set(cnf, "x5t#S256", // json_string("3rM_CLStQF3e-N2_9dZcR-BMw45XmRR0jbj6MSHU2bM")); if (cnf == NULL) { oauth2_debug(log, "no \"%s\" claim found in the token", OAUTH2_CLAIM_CNF); goto err; } oauth2_json_string_get(log, cnf, OAUTH2_CLAIM_CNF_X5T_S256, &cnf_x5t_s256_str, NULL); if (cnf_x5t_s256_str == NULL) { oauth2_debug(log, " \"%s\" claim found in the token but no \"%s\" " "key found inside", OAUTH2_CLAIM_CNF, OAUTH2_CLAIM_CNF_X5T_S256); goto err; } rc = oauth2_mtls_validate_cnf_x5t_s256(log, mtls_verify, request, cnf_x5t_s256_str); goto end; err: if ((cnf_x5t_s256_str == NULL) && (oauth2_mtls_verify_policy_get(mtls_verify) != OAUTH2_MTLS_VERIFY_POLICY_REQUIRED)) rc = true; end: if (cnf_x5t_s256_str != NULL) oauth2_mem_free(cnf_x5t_s256_str); return rc; } bool oauth2_token_verify(oauth2_log_t *log, oauth2_http_request_t *request, oauth2_cfg_token_verify_t *verify, const char *token, json_t **json_payload) { bool rc = false; oauth2_cfg_token_verify_t *ptr = NULL; char *s_payload = NULL; oauth2_debug(log, "enter"); if ((verify == NULL) || (token == NULL)) goto end; ptr = verify; while (ptr && ptr->callback) { oauth2_cache_get(log, ptr->cache, token, &s_payload); if ((s_payload) && (oauth2_json_decode_object(log, s_payload, json_payload))) { rc = true; break; } if (ptr->callback(log, ptr, token, json_payload, &s_payload)) { oauth2_cache_set(log, ptr->cache, token, s_payload, ptr->expiry_s); rc = true; break; } ptr = ptr->next; } if (rc == true) { if (ptr->type == OAUTH2_TOKEN_VERIFY_DPOP) { rc = oauth2_dpop_token_verify( log, &verify->dpop, request, token, *json_payload); } else if (ptr->type == OAUTH2_TOKEN_VERIFY_MTLS) { rc = oauth2_mtls_token_verify(log, &verify->mtls, request, *json_payload); } } end: if (s_payload) oauth2_mem_free(s_payload); oauth2_debug(log, "leave: %d", rc); return rc; } /* void oauth2_scrub_headers(request_rec *r) { oidc_cfg *cfg = ap_get_module_config(r->server->module_config, &auth_openidc_module); if (cfg->scrub_request_headers != 0) { const char *prefix = oidc_cfg_claim_prefix(r); apr_hash_t *hdrs = apr_hash_make(r->pool); if (apr_strnatcmp(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!"); } 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); oidc_scrub_request_headers(r, OIDC_DEFAULT_HEADER_PREFIX, hdrs); if ((strstr(prefix, OIDC_DEFAULT_HEADER_PREFIX) != prefix)) { oidc_scrub_request_headers(r, prefix, NULL); } } } */ liboauth2-2.1.0/src/oauth2_int.h000066400000000000000000000027361475305260400164350ustar00rootroot00000000000000#ifndef _OAUTH2_INT_H_ #define _OAUTH2_INT_H_ /*************************************************************************** * * Copyright (C) 2018-2025 - ZmartZone Holding BV * * 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. * * @Author: Hans Zandbelt - hans.zandbelt@openidc.com * **************************************************************************/ #include "oauth2/cfg.h" #include "oauth2/http.h" #include "oauth2/log.h" #include "oauth2/util.h" #include "cfg_int.h" _OAUTH_CFG_CTX_CALLBACK(oauth2_verify_options_set_introspect_url); _OAUTH_CFG_CTX_CALLBACK(oauth2_verify_options_set_metadata_url); bool oauth2_auth_basic(oauth2_log_t *log, oauth2_http_call_ctx_t *ctx, const oauth2_cfg_endpoint_auth_t *auth, oauth2_nv_list_t *params); bool oauth2_dpop_token_verify(oauth2_log_t *log, oauth2_cfg_dpop_verify_t *verify, oauth2_http_request_t *request, const char *access_token, json_t *json_payload); #endif /* _OAUTH2_INT_H_ */ liboauth2-2.1.0/src/openidc/000077500000000000000000000000001475305260400156215ustar00rootroot00000000000000liboauth2-2.1.0/src/openidc/.gitignore000066400000000000000000000000561475305260400176120ustar00rootroot00000000000000/.dirstamp /.deps/ /.libs/ /*.lo /*.o /*.gcno liboauth2-2.1.0/src/openidc/client.c000066400000000000000000000225611475305260400172510ustar00rootroot00000000000000/*************************************************************************** * * Copyright (C) 2018-2025 - ZmartZone Holding BV * * 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. * * @Author: Hans Zandbelt - hans.zandbelt@openidc.com * **************************************************************************/ #include #include "cfg_int.h" #include "openidc_int.h" #include "util_int.h" oauth2_openidc_client_t *oauth2_openidc_client_init(oauth2_log_t *log) { oauth2_openidc_client_t *c = NULL; c = oauth2_mem_alloc(sizeof(oauth2_openidc_client_t)); if (c == NULL) goto end; c->client_id = NULL; c->client_secret = NULL; c->scope = NULL; c->token_endpoint_auth = NULL; c->ssl_verify = OAUTH2_CFG_FLAG_UNSET; c->http_timeout = OAUTH2_CFG_UINT_UNSET; end: return c; } void oauth2_openidc_client_free(oauth2_log_t *log, oauth2_openidc_client_t *c) { if (c == NULL) goto end; if (c->client_id) oauth2_mem_free(c->client_id); if (c->client_secret) oauth2_mem_free(c->client_secret); if (c->scope) oauth2_mem_free(c->scope); if (c->token_endpoint_auth) oauth2_cfg_endpoint_auth_free(log, c->token_endpoint_auth); oauth2_mem_free(c); end: return; } oauth2_openidc_client_t * oauth2_openidc_client_clone(oauth2_log_t *log, const oauth2_openidc_client_t *src) { oauth2_openidc_client_t *dst = NULL; if (src == NULL) goto end; dst = oauth2_openidc_client_init(log); if (dst == NULL) goto end; dst->client_id = oauth2_strdup(src->client_id); dst->client_secret = oauth2_strdup(src->client_secret); dst->scope = oauth2_strdup(src->scope); dst->token_endpoint_auth = oauth2_cfg_endpoint_auth_clone(log, src->token_endpoint_auth); dst->ssl_verify = src->ssl_verify; dst->http_timeout = src->http_timeout; end: return dst; } static char *_oauth2_openidc_client_metadata_parse( oauth2_log_t *log, oauth2_cfg_openidc_t *cfg, const char *s_json, const oauth2_nv_list_t *options_params) { char *rv = NULL; json_t *json = NULL; oauth2_cfg_endpoint_auth_t *auth = NULL; char *value = NULL; oauth2_nv_list_t *params = NULL; oauth2_debug(log, "enter"); if ((cfg == NULL) || (cfg->client == NULL) || (s_json == NULL)) { rv = oauth2_strdup( "internal error: struct, client or json is NULL"); goto end; } if (oauth2_json_decode_object(log, s_json, &json) == false) { rv = oauth2_strdup("could not parse json object"); goto end; } params = options_params ? oauth2_nv_list_clone(log, options_params) : oauth2_nv_list_init(log); if ((oauth2_json_string_get(log, json, "client_id", &value, NULL) == false) || (value == NULL)) { rv = oauth2_strdup("could not parse client_id"); goto end; } if (value) { // TODO: better merging? oauth2_nv_list_add(log, params, "client_id", value); oauth2_openidc_client_client_id_set(log, cfg->client, value); oauth2_mem_free(value); value = NULL; } if (oauth2_json_string_get(log, json, "client_secret", &value, NULL) == false) { rv = oauth2_strdup("could not parse client_secret"); goto end; } if (value) { // TODO: better merging? oauth2_nv_list_add(log, params, "client_secret", value); oauth2_openidc_client_client_secret_set(log, cfg->client, value); oauth2_mem_free(value); value = NULL; } if (oauth2_json_string_get(log, json, "scope", &value, NULL) == false) { rv = oauth2_strdup("could not parse scope"); goto end; } if (value) { oauth2_openidc_client_scope_set(log, cfg->client, value); oauth2_mem_free(value); value = NULL; } auth = oauth2_cfg_endpoint_auth_init(log); value = NULL; if (oauth2_json_string_get(log, json, "token_endpoint_auth_method", &value, NULL) == false) { rv = oauth2_strdup("could not parse token_endpoint_auth_method"); oauth2_cfg_endpoint_auth_free(log, auth); goto end; } if (value == NULL) { oauth2_cfg_endpoint_auth_free(log, auth); goto end; } rv = oauth2_cfg_set_endpoint_auth(log, auth, value, params, NULL); if (rv != NULL) { oauth2_cfg_endpoint_auth_free(log, auth); goto end; } oauth2_cfg_endpoint_auth_free( log, oauth2_openidc_client_token_endpoint_auth_get(log, cfg->client)); oauth2_openidc_client_token_endpoint_auth_set(log, cfg->client, auth); end: if ((rv != NULL) && (cfg->client)) { oauth2_openidc_client_free(log, cfg->client); cfg->client = NULL; } if (value) oauth2_mem_free(value); if (params) oauth2_nv_list_free(log, params); if (json) json_decref(json); oauth2_debug(log, "leave: %s", rv); return rv; } static char * _oauth2_openidc_client_set_options_file(oauth2_log_t *log, const char *filename, const oauth2_nv_list_t *params, void *c) { oauth2_cfg_openidc_t *cfg = (oauth2_cfg_openidc_t *)c; char *s_json = NULL; char *rv = NULL; s_json = oauth_read_file(log, filename); if (s_json == NULL) goto end; rv = _oauth2_openidc_client_metadata_parse(log, cfg, s_json, params); end: if (s_json) oauth2_mem_free(s_json); return rv; } static char * _oauth2_openidc_client_set_options_json(oauth2_log_t *log, const char *value, const oauth2_nv_list_t *params, void *c) { oauth2_cfg_openidc_t *cfg = (oauth2_cfg_openidc_t *)c; return _oauth2_openidc_client_metadata_parse(log, cfg, value, params); } static char * _oauth2_openidc_client_set_options_string(oauth2_log_t *log, const char *value, const oauth2_nv_list_t *params, void *c) { oauth2_cfg_openidc_t *cfg = (oauth2_cfg_openidc_t *)c; char *rv = NULL; oauth2_nv_list_t *client_params = NULL; oauth2_cfg_endpoint_auth_t *auth = NULL; oauth2_debug(log, "enter"); if (oauth2_parse_form_encoded_params(log, value, &client_params) == false) { rv = oauth2_strdup("could not parse parameters"); goto end; } oauth2_openidc_client_client_id_set( log, cfg->client, oauth2_nv_list_get(log, client_params, "client_id")); oauth2_openidc_client_client_secret_set( log, cfg->client, oauth2_nv_list_get(log, client_params, "client_secret")); oauth2_openidc_client_scope_set( log, cfg->client, oauth2_nv_list_get(log, client_params, "scope")); auth = oauth2_cfg_endpoint_auth_init(log); // TODO: merge client_params and params? rv = oauth2_cfg_set_endpoint_auth( log, auth, oauth2_nv_list_get(log, client_params, "token_endpoint_auth_method"), client_params, NULL); if (rv != NULL) { oauth2_cfg_endpoint_auth_free(log, auth); goto end; } oauth2_cfg_endpoint_auth_free( log, oauth2_openidc_client_token_endpoint_auth_get(log, cfg->client)); oauth2_openidc_client_token_endpoint_auth_set(log, cfg->client, auth); end: if (client_params) oauth2_nv_list_free(log, client_params); oauth2_debug(log, "leave: %s", rv); return rv; } _OAUTH2_TYPE_IMPLEMENT_MEMBER_SET_GET(openidc, client, scope, char *, str) _OAUTH2_TYPE_IMPLEMENT_MEMBER_SET_GET(openidc, client, client_id, char *, str) _OAUTH2_TYPE_IMPLEMENT_MEMBER_SET_GET(openidc, client, client_secret, char *, str) _OAUTH2_TYPE_IMPLEMENT_MEMBER_SET_GET(openidc, client, token_endpoint_auth, oauth2_cfg_endpoint_auth_t *, ptr) _OAUTH2_TYPE_IMPLEMENT_MEMBER_SET_GET(openidc, client, ssl_verify, oauth2_flag_t, bln) _OAUTH2_TYPE_IMPLEMENT_MEMBER_SET_GET(openidc, client, http_timeout, oauth2_uint_t, uint) #define OAUTH2_OPENIDC_RESOLVER_STRING_STR "string" #define OAUTH2_OPENIDC_RESOLVER_JSON_STR "json" #define OAUTH2_OPENIDC_RESOLVER_FILE_STR "file" // clang-format off static oauth2_cfg_set_options_ctx_t _oauth2_cfg_client_resolver_options_set[] = { { OAUTH2_OPENIDC_RESOLVER_STRING_STR, _oauth2_openidc_client_set_options_string }, { OAUTH2_OPENIDC_RESOLVER_JSON_STR, _oauth2_openidc_client_set_options_json }, { OAUTH2_OPENIDC_RESOLVER_FILE_STR, _oauth2_openidc_client_set_options_file }, { NULL, NULL } }; // clang-format on char *oauth2_openidc_client_set_options(oauth2_log_t *log, oauth2_cfg_openidc_t *cfg, const char *type, const char *value, const char *options) { char *rv = NULL; oauth2_nv_list_t *params = NULL; oauth2_debug(log, "type=%s value=%s options=%s", type, value, options); if (cfg->client == NULL) { cfg->client = oauth2_openidc_client_init(log); if (cfg->client == NULL) { rv = oauth2_strdup("could not create client"); goto end; } } if (oauth2_parse_form_encoded_params(log, options, ¶ms) == false) { rv = oauth2_strdup("could not parse parameters"); goto end; } cfg->session = _oauth2_cfg_session_obtain( log, oauth2_nv_list_get(log, params, "session")); if (cfg->session == NULL) { rv = oauth2_strdup("could not obtain session"); goto end; } rv = oauth2_strdup(oauth2_cfg_set_flag_slot( cfg->client, offsetof(oauth2_openidc_client_t, ssl_verify), oauth2_nv_list_get(log, params, "ssl_verify"))); if (rv != NULL) goto end; rv = oauth2_cfg_set_options(log, cfg, type, value, options, _oauth2_cfg_client_resolver_options_set); end: if (params) oauth2_nv_list_free(log, params); oauth2_debug(log, "leave: %s", rv); return rv; } liboauth2-2.1.0/src/openidc/openidc.c000066400000000000000000000424611475305260400174150ustar00rootroot00000000000000/*************************************************************************** * * Copyright (C) 2018-2025 - ZmartZone Holding BV * * 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. * * @Author: Hans Zandbelt - hans.zandbelt@openidc.com * **************************************************************************/ #include "oauth2/openidc.h" #include "oauth2/jose.h" #include "oauth2/mem.h" #include "oauth2/oauth2.h" #include "oauth2/session.h" #include "cfg_int.h" #include "openidc_int.h" #define OAUTH2_STATE_LENGTH 16 #define OAUTH2_NONCE_LENGTH 16 #define OAUTH2_PKCE_LENGTH 48 static bool _oauth2_openidc_authenticate(oauth2_log_t *log, const oauth2_cfg_openidc_t *cfg, const oauth2_http_request_t *request, oauth2_http_response_t **response) { bool rc = false; oauth2_openidc_provider_t *provider = NULL; char *nonce = NULL, *state = NULL, *redirect_uri = NULL, *location = NULL, *pkce = NULL, *code_challenge = NULL; oauth2_nv_list_t *params = oauth2_nv_list_init(log); char *client_id = NULL, *scope = NULL; unsigned char *dst = NULL; unsigned int dst_len = 0; oauth2_debug(log, "enter"); if ((cfg == NULL) || (request == NULL) || (response == NULL)) goto end; if (_oauth2_openidc_provider_resolve(log, cfg, request, NULL, &provider) == false) goto end; oauth2_nv_list_add(log, params, OAUTH2_RESPONSE_TYPE, OAUTH2_RESPONSE_TYPE_CODE); client_id = oauth2_openidc_client_client_id_get(log, cfg->client); if (client_id) oauth2_nv_list_add(log, params, OAUTH2_CLIENT_ID, client_id); // redirect_uri = oauth2_openidc_cfg_redirect_uri_get_iss(log, cfg, // request, provider); redirect_uri = oauth2_cfg_openidc_redirect_uri_get(log, cfg, request); if (redirect_uri) oauth2_nv_list_add(log, params, OAUTH2_REDIRECT_URI, redirect_uri); scope = oauth2_openidc_client_scope_get(log, cfg->client); if (scope) oauth2_nv_list_add(log, params, OAUTH2_SCOPE, scope); nonce = oauth2_rand_str(log, OAUTH2_NONCE_LENGTH); oauth2_nv_list_add(log, params, OAUTH2_NONCE, nonce); state = oauth2_rand_str(log, OAUTH2_STATE_LENGTH); oauth2_nv_list_add(log, params, OAUTH2_STATE, state); pkce = oauth2_rand_str(log, OAUTH2_PKCE_LENGTH); oauth2_jose_hash_bytes(log, "sha256", (const unsigned char *)pkce, strlen(pkce), &dst, &dst_len); oauth2_base64url_encode(log, dst, dst_len, &code_challenge); oauth2_nv_list_add(log, params, OAUTH2_CODE_CHALLENGE, code_challenge); oauth2_nv_list_add(log, params, OAUTH2_CODE_CHALLENGE_METHOD, "S256"); // TODO: handle POST binding as well if (*response == NULL) goto end; if (_oauth2_openidc_state_cookie_set(log, cfg, provider, request, *response, state, pkce) == false) goto end; location = oauth2_http_url_query_encode( log, provider->authorization_endpoint, params); if (oauth2_http_response_header_set( log, *response, OAUTH2_HTTP_HDR_LOCATION, location) == false) goto end; rc = oauth2_http_response_status_code_set(log, *response, 302); end: if (provider) oauth2_openidc_provider_free(log, provider); if (redirect_uri) oauth2_mem_free(redirect_uri); if (nonce) oauth2_mem_free(nonce); if (state) oauth2_mem_free(state); if (location) oauth2_mem_free(location); if (params) oauth2_nv_list_free(log, params); if (code_challenge) oauth2_mem_free(code_challenge); if (pkce) oauth2_mem_free(pkce); if (dst) oauth2_mem_free(dst); oauth2_debug(log, "return: %d", rc); return rc; } static bool _oauth2_openidc_unauthenticated_request( oauth2_log_t *log, const oauth2_cfg_openidc_t *cfg, const oauth2_http_request_t *request, oauth2_session_rec_t *session, oauth2_http_response_t **response) { bool rc = false; oauth2_debug(log, "enter"); if (response == NULL) goto end; *response = oauth2_http_response_init(log); switch (oauth2_cfg_openidc_unauth_action_get(log, cfg)) { case OAUTH2_UNAUTH_ACTION_PASS: // r->user = ""; // oidc_scrub_headers(r); rc = true; goto end; break; case OAUTH2_UNAUTH_ACTION_HTTP_401: oauth2_http_response_status_code_set(log, *response, 401); rc = true; goto end; break; case OAUTH2_UNAUTH_ACTION_HTTP_410: oauth2_http_response_status_code_set(log, *response, 410); rc = true; goto end; break; case OAUTH2_UNAUTH_ACTION_AUTHENTICATE: case OAUTH2_UNAUTH_ACTION_UNDEFINED: default: if (oauth2_http_request_is_xml_http_request(log, request)) { oauth2_http_response_status_code_set(log, *response, 410); rc = true; goto end; } break; } rc = _oauth2_openidc_authenticate(log, cfg, request, response); end: oauth2_debug(log, "return: %d", rc); return rc; } static bool _oauth2_openidc_existing_session(oauth2_log_t *log, const oauth2_cfg_openidc_t *c, const oauth2_http_request_t *r, oauth2_session_rec_t *session, oauth2_http_response_t **response, json_t **claims) { bool rc = false; json_t *id_token_claims = NULL, *userinfo_claims = NULL; const char *key = NULL; json_t *value = NULL; oauth2_debug(log, "enter"); *response = oauth2_http_response_init(log); if (oauth2_session_handle(log, c->session, r, *response, session) == false) goto end; id_token_claims = oauth2_session_rec_id_token_claims_get(log, session); userinfo_claims = oauth2_session_rec_userinfo_claims_get(log, session); *claims = json_object(); if (id_token_claims) { json_object_foreach(id_token_claims, key, value) json_object_set_new(*claims, key, json_deep_copy(value)); } if (userinfo_claims) { json_object_foreach(userinfo_claims, key, value) json_object_set_new(*claims, key, json_deep_copy(value)); } rc = true; end: oauth2_debug(log, "return: %d (%p, %p)", rc, *response, *claims); return rc; } static bool _oauth2_openidc_id_token_verify(oauth2_log_t *log, oauth2_openidc_provider_t *provider, const char *s_id_token, json_t **id_token, bool ssl_verify) { bool rc = false; char *rv = NULL; char *options = NULL; oauth2_cfg_token_verify_t *verify = NULL; if (ssl_verify == false) options = oauth2_stradd(NULL, "jwks_uri.ssl_verify", "=", "false"); rv = oauth2_cfg_token_verify_add_options(log, &verify, "jwks_uri", provider->jwks_uri, options); if (rv != NULL) { oauth2_error( log, "oauth2_cfg_token_verify_add_options failed: %s", rv); goto end; } if (oauth2_token_verify(log, NULL, verify, s_id_token, id_token) == false) { oauth2_error(log, "id_token verification failed"); goto end; } rc = true; end: if (rv) oauth2_mem_free(rv); if (options) oauth2_mem_free(options); if (verify) oauth2_cfg_token_verify_free(log, verify); return rc; } static bool _oauth2_openidc_token_endpoint_call(oauth2_log_t *log, oauth2_openidc_client_t *client, oauth2_openidc_provider_t *provider, oauth2_nv_list_t *params, json_t **json) { bool rc = false; oauth2_http_call_ctx_t *http_ctx = NULL; char *s_response = NULL; oauth2_uint_t status_code = 0; oauth2_debug(log, "enter"); http_ctx = oauth2_http_call_ctx_init(log); if (http_ctx == NULL) goto end; if (oauth2_http_call_ctx_ssl_verify_set( log, http_ctx, oauth2_openidc_client_ssl_verify_get(log, client)) == false) goto end; if (oauth2_http_call_ctx_timeout_set( log, http_ctx, oauth2_openidc_client_http_timeout_get(log, client)) == false) goto end; // TODO: add configurable extra POST params if (oauth2_http_ctx_auth_add( log, http_ctx, oauth2_openidc_client_token_endpoint_auth_get(log, client), params) == false) goto end; if (oauth2_http_post_form( log, oauth2_openidc_provider_token_endpoint_get(log, provider), params, http_ctx, &s_response, &status_code) == false) goto end; if ((status_code < 200) || (status_code >= 300)) { rc = false; goto end; } if (oauth2_json_decode_check_error(log, s_response, json) == false) goto end; rc = true; end: if (s_response) oauth2_mem_free(s_response); if (http_ctx) oauth2_http_call_ctx_free(log, http_ctx); oauth2_debug(log, "leave: %d", rc); return rc; } static bool _oauth2_openidc_token_request(oauth2_log_t *log, const oauth2_cfg_openidc_t *cfg, oauth2_http_request_t *request, oauth2_openidc_provider_t *provider, const char *code, const char *pkce, char **s_id_token, char **s_access_token) { bool rc = false; oauth2_nv_list_t *params = NULL; char *redirect_uri = NULL; json_t *json = NULL; oauth2_debug(log, "enter"); redirect_uri = oauth2_cfg_openidc_redirect_uri_get(log, cfg, request); if (redirect_uri == NULL) goto end; params = oauth2_nv_list_init(log); if (params == NULL) goto end; oauth2_nv_list_add(log, params, OAUTH2_GRANT_TYPE, OAUTH2_GRANT_TYPE_AUTHORIZATION_CODE); oauth2_nv_list_add(log, params, OAUTH2_CODE, code); oauth2_nv_list_add(log, params, OAUTH2_REDIRECT_URI, redirect_uri); oauth2_nv_list_add(log, params, OAUTH2_CODE_VERIFIER, pkce); if (_oauth2_openidc_token_endpoint_call(log, cfg->client, provider, params, &json) == false) goto end; if (oauth2_json_string_get(log, json, OAUTH2_OPENIDC_ID_TOKEN, s_id_token, NULL) == false) { oauth2_error(log, "no %s found in token response", OAUTH2_OPENIDC_ID_TOKEN); goto end; } if (oauth2_json_string_get(log, json, OAUTH2_OPENIDC_ACCESS_TOKEN, s_access_token, NULL) == false) { oauth2_error(log, "no %s found in token response", OAUTH2_OPENIDC_ACCESS_TOKEN); goto end; } rc = true; end: if (redirect_uri) oauth2_mem_free(redirect_uri); if (params) oauth2_nv_list_free(log, params); if (json) json_decref(json); oauth2_debug(log, "leave: %d", rc); return rc; } static bool _oauth2_openidc_userinfo_request( oauth2_log_t *log, const oauth2_cfg_openidc_t *cfg, oauth2_http_request_t *request, oauth2_openidc_provider_t *provider, const char *s_access_token, json_t **userinfo_claims) { bool rc = false; oauth2_http_call_ctx_t *http_ctx = NULL; char *s_response = NULL; oauth2_uint_t status_code = 0; if (oauth2_openidc_provider_userinfo_endpoint_get(log, provider) == NULL) { rc = true; goto end; } http_ctx = oauth2_http_call_ctx_init(log); if (http_ctx == NULL) goto end; if (oauth2_http_call_ctx_ssl_verify_set( log, http_ctx, oauth2_openidc_client_ssl_verify_get(log, cfg->client)) == false) goto end; if (oauth2_http_call_ctx_timeout_set( log, http_ctx, oauth2_openidc_client_http_timeout_get(log, cfg->client)) == false) goto end; if (oauth2_http_call_ctx_bearer_token_set(log, http_ctx, s_access_token) == false) goto end; if (oauth2_http_get( log, oauth2_openidc_provider_userinfo_endpoint_get(log, provider), NULL, http_ctx, &s_response, &status_code) == false) goto end; if ((status_code < 200) || (status_code >= 300)) { rc = false; goto end; } if (oauth2_json_decode_check_error(log, s_response, userinfo_claims) == false) goto end; rc = true; end: if (s_response) oauth2_mem_free(s_response); if (http_ctx) oauth2_http_call_ctx_free(log, http_ctx); return rc; } static bool _oauth2_openidc_redirect_uri_handler( oauth2_log_t *log, const oauth2_cfg_openidc_t *cfg, oauth2_http_request_t *request, oauth2_session_rec_t *session, oauth2_http_response_t **response) { bool rc = false; oauth2_openidc_provider_t *provider = NULL; const char *code = NULL, *state = NULL; char *location = NULL, *s_id_token = NULL, *s_access_token = NULL, *pkce = NULL; json_t *id_token = NULL, *userinfo_claims = NULL; oauth2_openidc_proto_state_t *proto_state = NULL; oauth2_debug(log, "enter"); // at this point we know there's a request to the redirect uri // errors set in the HTTP response code = oauth2_http_request_query_param_get(log, request, OAUTH2_CODE); if (code == NULL) { oauth2_error(log, "invalid request to the redirect_uri: %s " "parameter could not be found [%s]", OAUTH2_CODE, oauth2_http_request_query_get(log, request)); goto end; } state = oauth2_http_request_query_param_get(log, request, OAUTH2_STATE); if (state == NULL) { oauth2_error(log, "invalid request to the redirect_uri: %s " "parameter could not be found [%s]", OAUTH2_STATE, oauth2_http_request_query_get(log, request)); goto end; } *response = oauth2_http_response_init(log); if (_oauth2_openidc_state_cookie_get(log, cfg, request, *response, state, &proto_state) == false) goto end; if (_oauth2_openidc_state_validate(log, cfg, request, proto_state, &provider) == false) goto end; if (oauth2_openidc_proto_state_pkce_get(log, proto_state, &pkce) == false) goto end; if (_oauth2_openidc_token_request(log, cfg, request, provider, code, pkce, &s_id_token, &s_access_token) == false) goto end; if (_oauth2_openidc_id_token_verify( log, provider, s_id_token, &id_token, oauth2_openidc_client_ssl_verify_get(log, cfg->client)) == false) goto end; if (_oauth2_openidc_userinfo_request(log, cfg, request, provider, s_access_token, &userinfo_claims) == false) goto end; // TODO: evaluate and set configurable r->user claim oauth2_session_rec_user_set( log, session, json_string_value(json_object_get(id_token, "sub"))); oauth2_session_rec_id_token_claims_set(log, session, id_token); oauth2_session_rec_userinfo_claims_set(log, session, userinfo_claims); oauth2_session_save(log, cfg->session, request, *response, session); // redirect to where we wanted to go originally if (oauth2_openidc_proto_state_target_link_uri_get(log, proto_state, &location) == false) goto end; if (oauth2_http_response_header_set( log, *response, OAUTH2_HTTP_HDR_LOCATION, location) == false) goto end; if (oauth2_http_response_status_code_set(log, *response, 302) == false) goto end; rc = true; end: if (pkce) oauth2_mem_free(pkce); if (s_id_token) oauth2_mem_free(s_id_token); if (s_access_token) oauth2_mem_free(s_access_token); if (location) oauth2_mem_free(location); if (proto_state) oauth2_openidc_proto_state_free(log, proto_state); if (provider) oauth2_openidc_provider_free(log, provider); if (id_token) json_decref(id_token); if (userinfo_claims) json_decref(userinfo_claims); oauth2_debug(log, "leave: %d", rc); return rc; } bool oauth2_openidc_is_request_to_redirect_uri(oauth2_log_t *log, const oauth2_cfg_openidc_t *cfg, oauth2_http_request_t *request) { bool rc = false; char *redirect_uri = NULL, *request_url; request_url = oauth2_http_request_url_path_get(log, request); if (request_url == NULL) goto end; // redirect_uri = oauth2_openidc_cfg_redirect_uri_get_iss(log, cfg, // request, provider); redirect_uri = oauth2_cfg_openidc_redirect_uri_get(log, cfg, request); if (redirect_uri == NULL) goto end; oauth2_debug(log, "comparing: \"%s\"=\"%s\"", request_url, redirect_uri); if (strcmp(redirect_uri, request_url) != 0) goto end; rc = true; end: if (request_url) oauth2_mem_free(request_url); if (redirect_uri) oauth2_mem_free(redirect_uri); return rc; } static bool _oauth2_openidc_internal_requests(oauth2_log_t *log, const oauth2_cfg_openidc_t *cfg, oauth2_http_request_t *request, oauth2_session_rec_t *session, oauth2_http_response_t **response, bool *processed) { bool rc = true; oauth2_debug(log, "enter"); if (oauth2_openidc_is_request_to_redirect_uri(log, cfg, request) == true) { rc = _oauth2_openidc_redirect_uri_handler(log, cfg, request, session, response); *processed = true; goto end; } // TODO: // - session info // - key materials // - 3rd-party init SSO rc = true; end: oauth2_debug(log, "leave: %d", rc); return rc; } bool oauth2_openidc_handle(oauth2_log_t *log, const oauth2_cfg_openidc_t *cfg, oauth2_http_request_t *request, oauth2_http_response_t **response, json_t **claims) { bool rc = false, processed = false; oauth2_session_rec_t *session = NULL; oauth2_debug(log, "incoming request: %s%s%s", oauth2_http_request_path_get(log, request), oauth2_http_request_query_get(log, request) ? "?" : "", oauth2_http_request_query_get(log, request) ? oauth2_http_request_query_get(log, request) : ""); if (oauth2_session_load(log, cfg->session, request, &session) == false) goto end; rc = _oauth2_openidc_internal_requests(log, cfg, request, session, response, &processed); if ((processed == true) || (rc == false)) goto end; if (oauth2_session_rec_user_get(log, session) != NULL) { oauth2_debug(log, "user found in session: %s", oauth2_session_rec_user_get(log, session)); rc = _oauth2_openidc_existing_session( log, cfg, request, session, response, claims); goto end; } oauth2_debug(log, "no user found in session"); rc = _oauth2_openidc_unauthenticated_request(log, cfg, request, session, response); end: oauth2_session_rec_free(log, session); oauth2_debug(log, "return: %d", rc); return rc; } liboauth2-2.1.0/src/openidc/provider.c000066400000000000000000000041771475305260400176300ustar00rootroot00000000000000/*************************************************************************** * * Copyright (C) 2018-2025 - ZmartZone Holding BV * * 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. * * @Author: Hans Zandbelt - hans.zandbelt@openidc.com * **************************************************************************/ #include #include "openidc_int.h" #include "util_int.h" oauth2_openidc_provider_t *oauth2_openidc_provider_init(oauth2_log_t *log) { oauth2_openidc_provider_t *p = NULL; p = oauth2_mem_alloc(sizeof(oauth2_openidc_provider_t)); if (p == NULL) goto end; p->issuer = NULL; p->authorization_endpoint = NULL; p->token_endpoint = NULL; p->userinfo_endpoint = NULL; p->jwks_uri = NULL; end: return p; } void oauth2_openidc_provider_free(oauth2_log_t *log, oauth2_openidc_provider_t *p) { if (p == NULL) goto end; if (p->issuer) oauth2_mem_free(p->issuer); if (p->authorization_endpoint) oauth2_mem_free(p->authorization_endpoint); if (p->token_endpoint) oauth2_mem_free(p->token_endpoint); if (p->jwks_uri) oauth2_mem_free(p->jwks_uri); if (p->userinfo_endpoint) oauth2_mem_free(p->userinfo_endpoint); oauth2_mem_free(p); end: return; } _OAUTH2_TYPE_IMPLEMENT_MEMBER_SET_GET(openidc, provider, issuer, char *, str) _OAUTH2_TYPE_IMPLEMENT_MEMBER_SET_GET(openidc, provider, authorization_endpoint, char *, str) _OAUTH2_TYPE_IMPLEMENT_MEMBER_SET_GET(openidc, provider, token_endpoint, char *, str) _OAUTH2_TYPE_IMPLEMENT_MEMBER_SET_GET(openidc, provider, userinfo_endpoint, char *, str) _OAUTH2_TYPE_IMPLEMENT_MEMBER_SET_GET(openidc, provider, jwks_uri, char *, str) liboauth2-2.1.0/src/openidc/resolver.c000066400000000000000000000305761475305260400176410ustar00rootroot00000000000000/*************************************************************************** * * Copyright (C) 2018-2025 - ZmartZone Holding BV * * 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. * * @Author: Hans Zandbelt - hans.zandbelt@openidc.com * **************************************************************************/ #include #include "cache_int.h" #include "cfg_int.h" #include "openidc_int.h" static bool _oauth2_openidc_provider_metadata_parse(oauth2_log_t *log, const char *s_json, oauth2_openidc_provider_t **provider) { bool rc = false; json_t *json = NULL; oauth2_openidc_provider_t *p = NULL; oauth2_debug(log, "enter"); if (oauth2_json_decode_object(log, s_json, &json) == false) { oauth2_error(log, "could not parse json object"); goto end; } *provider = oauth2_openidc_provider_init(log); p = *provider; if (p == NULL) goto end; if ((oauth2_json_string_get(log, json, "issuer", &p->issuer, NULL) == false) || (p->issuer == NULL)) { oauth2_error(log, "could not parse issuer"); goto end; } if (oauth2_json_string_get(log, json, "authorization_endpoint", &p->authorization_endpoint, NULL) == false) { oauth2_error(log, "could not parse authorization_endpoint"); goto end; } if (oauth2_json_string_get(log, json, "token_endpoint", &p->token_endpoint, NULL) == false) { oauth2_error(log, "could not parse token_endpoint"); goto end; } if (oauth2_json_string_get(log, json, "userinfo_endpoint", &p->userinfo_endpoint, NULL) == false) { oauth2_error(log, "could not parse userinfo_endpoint"); goto end; } if (oauth2_json_string_get(log, json, "jwks_uri", &p->jwks_uri, NULL) == false) { oauth2_error(log, "could not parse jwks_uri"); goto end; } rc = true; end: if ((rc == false) && (*provider)) { oauth2_openidc_provider_free(log, *provider); *provider = NULL; } if (json) json_decref(json); oauth2_debug(log, "leave: %d", rc); return rc; } #define OAUTH_OPENIDC_PROVIDER_CACHE_EXPIRY_DEFAULT 60 * 60 * 24 bool _oauth2_openidc_provider_resolve(oauth2_log_t *log, const oauth2_cfg_openidc_t *cfg, const oauth2_http_request_t *request, const char *issuer, oauth2_openidc_provider_t **provider) { bool rc = false; char *s_json = NULL; if ((cfg->provider_resolver == NULL) || (cfg->provider_resolver->callback == NULL)) { oauth2_error( log, "configuration error: provider_resolver is not configured"); goto end; } if ((issuer) && (cfg->provider_resolver->cache)) { oauth2_cache_get(log, cfg->provider_resolver->cache, issuer, &s_json); } if (s_json == NULL) { if (cfg->provider_resolver->callback(log, cfg, request, &s_json) == false) { oauth2_error(log, "resolver callback returned false"); goto end; } if (s_json == NULL) { oauth2_error( log, "no provider was returned by the provider " "resolver; probably a configuration error"); goto end; } } if (_oauth2_openidc_provider_metadata_parse(log, s_json, provider) == false) goto end; // TODO: cache expiry configuration option if (cfg->provider_resolver->cache) { oauth2_cache_set( log, cfg->provider_resolver->cache, oauth2_openidc_provider_issuer_get(log, *provider), s_json, OAUTH_OPENIDC_PROVIDER_CACHE_EXPIRY_DEFAULT); } rc = true; end: if (s_json) oauth2_mem_free(s_json); return rc; } _OAUTH2_CFG_CTX_TYPE_SINGLE_STRING(oauth2_openidc_provider_resolver_file_ctx, filename) #define OAUTH2_OPENIDC_PROVIDER_RESOLVE_FILENAME_DEFAULT "conf/provider.json" static bool _oauth2_openidc_provider_resolve_file( oauth2_log_t *log, const oauth2_cfg_openidc_t *cfg, const oauth2_http_request_t *request, char **s_json) { bool rc = false; oauth2_openidc_provider_resolver_file_ctx_t *ctx = NULL; char *filename = NULL; oauth2_debug(log, "enter"); ctx = (oauth2_openidc_provider_resolver_file_ctx_t *) cfg->provider_resolver->ctx->ptr; filename = ctx->filename ? ctx->filename : OAUTH2_OPENIDC_PROVIDER_RESOLVE_FILENAME_DEFAULT; *s_json = oauth_read_file(log, filename); if (*s_json == NULL) goto end; rc = true; end: oauth2_debug(log, "leave: %d", rc); return rc; } // TODO: must explicitly (re-)populate cache on startup! #define OAUTH2_CFG_OPENIDC_PROVIDER_CACHE_DEFAULT 60 * 60 * 24 static char *_oauth2_cfg_openidc_provider_resolver_file_set_options( oauth2_log_t *log, const char *value, const oauth2_nv_list_t *params, void *c) { oauth2_cfg_openidc_t *cfg = (oauth2_cfg_openidc_t *)c; // TODO: macroize? cfg->provider_resolver = oauth2_cfg_openidc_provider_resolver_init(log); cfg->provider_resolver->callback = _oauth2_openidc_provider_resolve_file; cfg->provider_resolver->ctx->callbacks = &oauth2_openidc_provider_resolver_file_ctx_funcs; cfg->provider_resolver->ctx->ptr = cfg->provider_resolver->ctx->callbacks->init(log); // TODO: factor out? oauth2_openidc_provider_resolver_file_ctx_t *ctx = (oauth2_openidc_provider_resolver_file_ctx_t *) cfg->provider_resolver->ctx->ptr; ctx->filename = oauth2_strdup(value); cfg->provider_resolver->cache = oauth2_cache_obtain(log, oauth2_nv_list_get(log, params, "cache")); return NULL; } _OAUTH2_CFG_CTX_TYPE_START(oauth2_openidc_provider_resolver_url_ctx) oauth2_cfg_endpoint_t *endpoint; _OAUTH2_CFG_CTX_TYPE_END(oauth2_openidc_provider_resolver_url_ctx) _OAUTH2_CFG_CTX_INIT_START(oauth2_openidc_provider_resolver_url_ctx) ctx->endpoint = NULL; _OAUTH2_CFG_CTX_INIT_END _OAUTH2_CFG_CTX_CLONE_START(oauth2_openidc_provider_resolver_url_ctx) dst->endpoint = oauth2_cfg_endpoint_clone(log, src->endpoint); _OAUTH2_CFG_CTX_CLONE_END _OAUTH2_CFG_CTX_FREE_START(oauth2_openidc_provider_resolver_url_ctx) if (ctx->endpoint) oauth2_cfg_endpoint_free(log, ctx->endpoint); _OAUTH2_CFG_CTX_FREE_END _OAUTH2_CFG_CTX_FUNCS(oauth2_openidc_provider_resolver_url_ctx) static bool _oauth2_openidc_provider_resolve_url_exec( oauth2_log_t *log, oauth2_openidc_provider_resolver_url_ctx_t *ctx, char **s_json) { bool rc = false; oauth2_http_call_ctx_t *http_ctx = NULL; char *s_response = NULL; oauth2_uint_t status_code = 0; oauth2_debug(log, "enter"); http_ctx = oauth2_http_call_ctx_init(log); if (http_ctx == NULL) goto end; if (oauth2_http_call_ctx_ssl_verify_set( log, http_ctx, oauth2_cfg_endpoint_get_ssl_verify(ctx->endpoint)) == false) goto end; if (oauth2_http_call_ctx_timeout_set( log, http_ctx, oauth2_cfg_endpoint_get_http_timeout(ctx->endpoint)) == false) goto end; oauth2_http_call_ctx_outgoing_proxy_set( log, http_ctx, oauth2_cfg_endpoint_get_outgoing_proxy(ctx->endpoint)); if (oauth2_http_get(log, oauth2_cfg_endpoint_get_url(ctx->endpoint), NULL, http_ctx, s_json, &status_code) == false) goto end; if ((status_code < 200) || (status_code >= 300)) { goto end; } rc = true; end: if (s_response) oauth2_mem_free(s_response); if (http_ctx) oauth2_http_call_ctx_free(log, http_ctx); oauth2_debug(log, "leave: %d", rc); return rc; } static bool _oauth2_openidc_provider_resolve_url( oauth2_log_t *log, const oauth2_cfg_openidc_t *cfg, const oauth2_http_request_t *request, char **s_json) { bool rc = false; oauth2_openidc_provider_resolver_url_ctx_t *ctx = NULL; oauth2_debug(log, "enter"); ctx = (oauth2_openidc_provider_resolver_url_ctx_t *) cfg->provider_resolver->ctx->ptr; if (ctx->endpoint == NULL) goto end; if (_oauth2_openidc_provider_resolve_url_exec(log, ctx, s_json) == false) goto end; rc = true; end: oauth2_debug(log, "leave: %d", rc); return rc; } static char *_oauth2_cfg_openidc_provider_resolver_url_set_options( oauth2_log_t *log, const char *value, const oauth2_nv_list_t *params, void *c) { oauth2_cfg_openidc_t *cfg = (oauth2_cfg_openidc_t *)c; char *rv = NULL; // TODO: macroize? cfg->provider_resolver = oauth2_cfg_openidc_provider_resolver_init(log); cfg->provider_resolver->callback = _oauth2_openidc_provider_resolve_url; cfg->provider_resolver->ctx->callbacks = &oauth2_openidc_provider_resolver_url_ctx_funcs; cfg->provider_resolver->ctx->ptr = cfg->provider_resolver->ctx->callbacks->init(log); // TODO: factor out? oauth2_openidc_provider_resolver_url_ctx_t *ctx = (oauth2_openidc_provider_resolver_url_ctx_t *) cfg->provider_resolver->ctx->ptr; ctx->endpoint = oauth2_cfg_endpoint_init(log); rv = oauth2_cfg_set_endpoint(log, ctx->endpoint, value, params, NULL); if (rv) goto end; cfg->provider_resolver->cache = oauth2_cache_obtain(log, oauth2_nv_list_get(log, params, "cache")); end: return rv; } // DIR static char *_oauth2_cfg_openidc_provider_resolver_dir_set_options( oauth2_log_t *log, const char *value, const oauth2_nv_list_t *params, void *c) { // oauth2_cfg_cache_set_options( // log, resolver->cache, "resolver", // params, OAUTH2_CFG_OPENIDC_PROVIDER_CACHE_DEFAULT); return NULL; } // STRING _OAUTH2_CFG_CTX_TYPE_SINGLE_STRING(oauth2_openidc_provider_resolver_str_ctx, metadata) static bool _oauth2_openidc_provider_resolve_string( oauth2_log_t *log, const oauth2_cfg_openidc_t *cfg, const oauth2_http_request_t *request, char **s_json) { bool rc = false; oauth2_openidc_provider_resolver_str_ctx_t *ctx = NULL; oauth2_debug(log, "enter"); ctx = (oauth2_openidc_provider_resolver_str_ctx_t *) cfg->provider_resolver->ctx->ptr; if (ctx->metadata == NULL) { oauth2_error(log, "metadata not configured"); goto end; } *s_json = oauth2_strdup(ctx->metadata); rc = true; end: oauth2_debug(log, "leave: %d", rc); return rc; } static char *_oauth2_cfg_openidc_provider_resolver_string_set_options( oauth2_log_t *log, const char *value, const oauth2_nv_list_t *params, void *c) { oauth2_cfg_openidc_t *cfg = (oauth2_cfg_openidc_t *)c; oauth2_openidc_provider_resolver_str_ctx_t *ctx = NULL; cfg->provider_resolver = oauth2_cfg_openidc_provider_resolver_init(log); cfg->provider_resolver->callback = _oauth2_openidc_provider_resolve_string; cfg->provider_resolver->ctx->callbacks = &oauth2_openidc_provider_resolver_str_ctx_funcs; cfg->provider_resolver->ctx->ptr = cfg->provider_resolver->ctx->callbacks->init(log); ctx = (oauth2_openidc_provider_resolver_str_ctx_t *) cfg->provider_resolver->ctx->ptr; ctx->metadata = oauth2_strdup(value); return NULL; } #define OAUTH2_OPENIDC_PROVIDER_RESOLVER_STR_STR "string" #define OAUTH2_OPENIDC_PROVIDER_RESOLVER_FILE_STR "file" #define OAUTH2_OPENIDC_PROVIDER_RESOLVER_DIR_STR "dir" #define OAUTH2_OPENIDC_PROVIDER_RESOLVER_URL_STR "url" // clang-format off static oauth2_cfg_set_options_ctx_t _oauth2_cfg_resolver_options_set[] = { { OAUTH2_OPENIDC_PROVIDER_RESOLVER_STR_STR, _oauth2_cfg_openidc_provider_resolver_string_set_options }, { OAUTH2_OPENIDC_PROVIDER_RESOLVER_FILE_STR, _oauth2_cfg_openidc_provider_resolver_file_set_options }, { OAUTH2_OPENIDC_PROVIDER_RESOLVER_DIR_STR, _oauth2_cfg_openidc_provider_resolver_dir_set_options }, { OAUTH2_OPENIDC_PROVIDER_RESOLVER_URL_STR, _oauth2_cfg_openidc_provider_resolver_url_set_options }, { NULL, NULL } }; // clang-format on char *oauth2_cfg_openidc_provider_resolver_set_options( oauth2_log_t *log, oauth2_cfg_openidc_t *cfg, const char *type, const char *value, const char *options) { char *rv = NULL; oauth2_nv_list_t *params = NULL; oauth2_debug(log, "type=%s value=%s options=%s", type, value, options); if (cfg->provider_resolver) { oauth2_cfg_openidc_provider_resolver_free( log, cfg->provider_resolver); cfg->provider_resolver = NULL; } oauth2_parse_form_encoded_params(log, options, ¶ms); cfg->session = _oauth2_cfg_session_obtain( log, oauth2_nv_list_get(log, params, "session")); if (cfg->session == NULL) { rv = oauth2_strdup("could not configure session"); goto end; } rv = oauth2_cfg_set_options(log, cfg, type, value, options, _oauth2_cfg_resolver_options_set); end: if (params) oauth2_nv_list_free(log, params); oauth2_debug(log, "leave: %s", rv); return rv; } liboauth2-2.1.0/src/openidc/state.c000066400000000000000000000343561475305260400171200ustar00rootroot00000000000000/*************************************************************************** * * Copyright (C) 2018-2025 - ZmartZone Holding BV * * 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. * * @Author: Hans Zandbelt - hans.zandbelt@openidc.com * **************************************************************************/ #include #include #include "cfg_int.h" #include "openidc_int.h" typedef struct oauth2_openidc_proto_state_t { json_t *state; } oauth2_openidc_proto_state_t; oauth2_openidc_proto_state_t *oauth2_openidc_proto_state_init(oauth2_log_t *log) { oauth2_openidc_proto_state_t *p = oauth2_mem_alloc(sizeof(oauth2_openidc_proto_state_t)); p->state = json_object(); return p; } oauth2_openidc_proto_state_t * oauth2_openidc_proto_state_clone(oauth2_log_t *log, const oauth2_openidc_proto_state_t *src) { oauth2_openidc_proto_state_t *dst = oauth2_openidc_proto_state_init(log); if (dst->state) json_decref(dst->state); dst->state = json_deep_copy(src->state); return dst; } void oauth2_openidc_proto_state_free(oauth2_log_t *log, oauth2_openidc_proto_state_t *p) { if (p->state) json_decref(p->state); oauth2_mem_free(p); } bool oauth2_openidc_proto_state_set(oauth2_log_t *log, oauth2_openidc_proto_state_t *p, const char *name, const char *value) { json_object_set_new(p->state, name, json_string(value)); return true; } bool oauth2_openidc_proto_state_set_int(oauth2_log_t *log, oauth2_openidc_proto_state_t *p, const char *name, const json_int_t value) { json_object_set_new(p->state, name, json_integer(value)); return true; } bool oauth2_openidc_proto_state_json_set(oauth2_log_t *log, oauth2_openidc_proto_state_t *p, json_t *json) { if (p->state) json_decref(p->state); p->state = json; return true; } json_t * oauth2_openidc_proto_state_json_get(const oauth2_openidc_proto_state_t *p) { return p->state; } bool oauth2_openidc_proto_state_target_link_uri_get( oauth2_log_t *log, oauth2_openidc_proto_state_t *p, char **value) { return oauth2_json_string_get( log, oauth2_openidc_proto_state_json_get(p), _OAUTH2_OPENIDC_PROTO_STATE_KEY_TARGET_LINK_URI, value, NULL); } bool oauth2_openidc_proto_state_pkce_get(oauth2_log_t *log, oauth2_openidc_proto_state_t *p, char **value) { return oauth2_json_string_get( log, oauth2_openidc_proto_state_json_get(p), _OAUTH2_OPENIDC_PROTO_STATE_KEY_PKCE, value, NULL); } static oauth2_openidc_proto_state_t *_oauth2_openidc_proto_state_create( oauth2_log_t *log, oauth2_openidc_provider_t *provider, const char *target_link_uri, const char *pkce, const oauth2_http_request_t *request) { oauth2_openidc_proto_state_t *p = oauth2_openidc_proto_state_init(log); oauth2_openidc_proto_state_set( log, p, _OAUTH2_OPENIDC_PROTO_STATE_KEY_ISSUER, oauth2_openidc_provider_issuer_get(log, provider)); oauth2_openidc_proto_state_set( log, p, _OAUTH2_OPENIDC_PROTO_STATE_KEY_TARGET_LINK_URI, target_link_uri); oauth2_openidc_proto_state_set( log, p, _OAUTH2_OPENIDC_PROTO_STATE_KEY_PKCE, pkce); oauth2_openidc_proto_state_set_int( log, p, _OAUTH2_OPENIDC_PROTO_STATE_KEY_REQUEST_METHOD, oauth2_http_request_method_get(log, request)); oauth2_openidc_proto_state_set_int( log, p, _OAUTH2_OPENIDC_PROTO_STATE_KEY_TIMESTAMP, oauth2_time_now_sec()); // TODO: response mode _OAUTH2_OPENIDC_PROTO_STATE_KEY_RESPONSE_MODE // TODO: response type _OAUTH2_OPENIDC_PROTO_STATE_KEY_RESPONSE_TYPE return p; } typedef struct oidc_state_cookies_t { char *name; oauth2_time_t timestamp; char *target_uri; struct oidc_state_cookies_t *next; } oidc_state_cookies_t; static bool _oauth2_openidc_cookie_clear(oauth2_log_t *log, oauth2_http_response_t *response, const char *name, const char *path, const bool is_secure) { return oauth2_http_response_cookie_set( log, response, name, NULL, path, is_secure, OAUTH2_CFG_TIME_UNSET); } static int _oauth2_openidc_delete_oldest_state_cookies( oauth2_log_t *log, oauth2_http_response_t *response, const char *path, int number_of_valid_state_cookies, int max_number_of_state_cookies, oidc_state_cookies_t **first, const bool is_secure) { 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)->next; while (cur) { if ((cur->timestamp < oldest->timestamp)) { oldest = cur; prev_oldest = prev; } prev = cur; cur = cur->next; } oauth2_warn( log, "deleting oldest state cookie: %s ; time until " "expiry " OAUTH2_TIME_T_FORMAT " seconds [target_uri=%s]", oldest->name, oldest->timestamp - oauth2_time_now_sec(), oldest->target_uri); _oauth2_openidc_cookie_clear(log, response, oldest->name, path, is_secure); if (prev_oldest) prev_oldest->next = oldest->next; else *first = (*first)->next; number_of_valid_state_cookies--; oauth2_mem_free(oldest->name); oauth2_mem_free(oldest->target_uri); oauth2_mem_free(oldest); } return number_of_valid_state_cookies; } static bool _oauth2_openidc_state_expired( oauth2_log_t *log, const oauth2_cfg_openidc_t *cfg, const oauth2_openidc_proto_state_t *proto_state, oauth2_time_t *tsr) { bool rc = true; oauth2_time_t now, exp; oauth2_time_t ts; now = oauth2_time_now_sec(); ts = json_integer_value( json_object_get(oauth2_openidc_proto_state_json_get(proto_state), _OAUTH2_OPENIDC_PROTO_STATE_KEY_TIMESTAMP)); exp = oauth2_cfg_openidc_state_cookie_timeout_get(log, cfg); if (now > ts + exp) { oauth2_error(log, "state expired: now: %d, then: %d, ttl: %d", now, ts, exp); goto end; } rc = false; end: if (tsr) *tsr = ts; return rc; } static bool _oauth2_openidc_get_state_from_cookie( oauth2_log_t *log, const char *value, oauth2_openidc_proto_state_t **proto_state) { bool rc = false; json_t *json = NULL; if (oauth2_jose_jwt_decrypt(log, oauth2_crypto_passphrase_get(log), value, &json) == false) goto end; *proto_state = oauth2_openidc_proto_state_init(log); oauth2_openidc_proto_state_json_set(log, *proto_state, json); rc = true; end: return rc; } static oidc_state_cookies_t * _oauth2_openidc_cookie_valid(oauth2_log_t *log, const oauth2_cfg_openidc_t *cfg, const oauth2_http_request_t *request, oauth2_http_response_t *response, char *cookie, const char *path) { oidc_state_cookies_t *entry = NULL; oauth2_openidc_proto_state_t *proto_state = NULL; char *cookieStart = NULL, *cookieName = NULL, *cookieValue = NULL; oauth2_time_t ts; char *target_uri = NULL; cookieStart = cookie; while (cookie != NULL && *cookie != '=') cookie++; if (*cookie != '=') goto end; *cookie = '\0'; cookie++; cookieName = oauth2_url_decode(log, cookieStart); cookieValue = oauth2_url_decode(log, cookie); if ((_oauth2_openidc_get_state_from_cookie(log, cookieValue, &proto_state) == false) || (proto_state == NULL)) { oauth2_warn( log, "state cookie could not be retrieved/decoded, deleting: %s", cookieName); _oauth2_openidc_cookie_clear( log, response, cookieName, path, oauth2_http_request_is_secure(log, request)); goto end; } oauth2_openidc_proto_state_target_link_uri_get(log, proto_state, &target_uri); if (_oauth2_openidc_state_expired(log, cfg, proto_state, &ts)) { oauth2_warn(log, "state (%s) has expired [target_uri=%s]", cookieName, target_uri); _oauth2_openidc_cookie_clear( log, response, cookieName, path, oauth2_http_request_is_secure(log, request)); goto end; } entry = oauth2_mem_alloc(sizeof(oidc_state_cookies_t)); entry->name = oauth2_strdup(cookieName); entry->timestamp = ts; entry->target_uri = oauth2_strdup(target_uri); entry->next = NULL; end: if (cookieName) oauth2_mem_free(cookieName); if (cookieValue) oauth2_mem_free(cookieValue); if (target_uri) oauth2_mem_free(target_uri); if (proto_state) oauth2_openidc_proto_state_free(log, proto_state); return entry; } static bool _oauth2_openidc_clean_expired_state_cookies( oauth2_log_t *log, const oauth2_cfg_openidc_t *cfg, const oauth2_http_request_t *request, oauth2_http_response_t *response) { bool rc = false; char *cookies = NULL, *save_ptr = NULL; oidc_state_cookies_t *first = NULL, *last = NULL, *entry = NULL; const char delim[2] = ";"; int number_of_valid_state_cookies = 0; char *cookieStr = NULL; // TODO: session reference...? const char *path = oauth2_cfg_session_cookie_path_get( log, oauth2_cfg_openidc_session_get(log, cfg)); cookies = oauth2_strdup(oauth2_http_request_header_cookie_get(log, request)); if (cookies == NULL) { rc = true; goto end; } cookieStr = strtok_r(cookies, delim, &save_ptr); while (cookieStr != NULL) { while (*cookieStr == ' ') cookieStr++; if (strstr(cookieStr, oauth2_cfg_openidc_state_cookie_name_prefix_get( log, cfg)) != cookieStr) goto cont; entry = _oauth2_openidc_cookie_valid(log, cfg, request, response, cookieStr, path); if (entry == NULL) goto cont; if (first == NULL) { first = entry; last = first; } else { last->next = entry; last = last->next; } number_of_valid_state_cookies++; cont: cookieStr = strtok_r(NULL, delim, &save_ptr); } if ((number_of_valid_state_cookies >= oauth2_cfg_openidc_state_cookie_max_get(log, cfg)) && (oauth2_cfg_openidc_state_cookie_delete_oldest_get(log, cfg) == false)) { oauth2_debug(log, "max number of state cookies has been reached"); goto end; } _oauth2_openidc_delete_oldest_state_cookies( log, response, path, number_of_valid_state_cookies, oauth2_cfg_openidc_state_cookie_max_get(log, cfg), &first, oauth2_http_request_is_secure(log, request)); rc = true; end: while (first) { entry = first; first = first->next; oauth2_mem_free(entry->name); oauth2_mem_free(entry->target_uri); oauth2_mem_free(entry); } if (cookies) oauth2_mem_free(cookies); return rc; } /* * state cookie handling */ bool _oauth2_openidc_state_cookie_set(oauth2_log_t *log, const oauth2_cfg_openidc_t *cfg, oauth2_openidc_provider_t *provider, const oauth2_http_request_t *request, oauth2_http_response_t *response, const char *state, const char *pkce) { bool rc = false; char *name = NULL, *value = NULL, *target_link_uri = NULL; oauth2_openidc_proto_state_t *proto_state = NULL; const char *path = NULL; name = oauth2_stradd( name, oauth2_cfg_openidc_state_cookie_name_prefix_get(log, cfg), state, NULL); if (name == NULL) goto end; if (_oauth2_openidc_clean_expired_state_cookies(log, cfg, request, response) == false) goto end; target_link_uri = oauth2_http_request_url_get(log, request); // TODO: add different state policy that keeps track in the // shared cache of outstanding parallel requests from the same // client (ip/user-agent) against a configurable maximum and // uses only a single shared cookie across those requests // (accepting consecutive responses, or take the last one) proto_state = _oauth2_openidc_proto_state_create( log, provider, target_link_uri, pkce, request); if (oauth2_jose_jwt_encrypt( log, oauth2_crypto_passphrase_get(log), oauth2_openidc_proto_state_json_get(proto_state), &value) == false) goto end; path = oauth2_cfg_session_cookie_path_get( log, oauth2_cfg_openidc_session_get(log, cfg)); rc = oauth2_http_response_cookie_set( log, response, name, value, path, oauth2_http_request_is_secure(log, request), oauth2_cfg_openidc_state_cookie_timeout_get(log, cfg)); end: if (proto_state) oauth2_openidc_proto_state_free(log, proto_state); if (name) oauth2_mem_free(name); if (value) oauth2_mem_free(value); if (target_link_uri) oauth2_mem_free(target_link_uri); return rc; } bool _oauth2_openidc_state_cookie_get( oauth2_log_t *log, const oauth2_cfg_openidc_t *cfg, oauth2_http_request_t *request, oauth2_http_response_t *response, const char *state, oauth2_openidc_proto_state_t **proto_state) { bool rc = false; char *name = NULL, *value = NULL; const char *path = NULL; name = oauth2_stradd( name, oauth2_cfg_openidc_state_cookie_name_prefix_get(log, cfg), state, NULL); if (name == NULL) goto end; value = oauth2_http_request_cookie_get(log, request, name, true); if (value == NULL) { oauth2_warn(log, "no state cookie found"); goto end; } path = oauth2_cfg_session_cookie_path_get( log, oauth2_cfg_openidc_session_get(log, cfg)); rc = _oauth2_openidc_cookie_clear( log, response, name, path, oauth2_http_request_is_secure(log, request)); if (rc == false) goto end; rc = _oauth2_openidc_get_state_from_cookie(log, value, proto_state); end: if (name) oauth2_mem_free(name); if (value) oauth2_mem_free(value); return rc; } bool _oauth2_openidc_state_validate(oauth2_log_t *log, const oauth2_cfg_openidc_t *cfg, oauth2_http_request_t *request, oauth2_openidc_proto_state_t *proto_state, oauth2_openidc_provider_t **provider) { bool rc = false; const char *iss = NULL; iss = json_string_value( json_object_get(oauth2_openidc_proto_state_json_get(proto_state), _OAUTH2_OPENIDC_PROTO_STATE_KEY_ISSUER)); if (iss == NULL) { oauth2_error(log, "no issuer (key=%s) found in state", _OAUTH2_OPENIDC_PROTO_STATE_KEY_ISSUER); goto end; } if (_oauth2_openidc_provider_resolve(log, cfg, request, iss, provider) == false) { oauth2_error(log, "_oauth2_openidc_provider_resolve returned false"); goto end; } if (_oauth2_openidc_state_expired(log, cfg, proto_state, NULL)) goto end; rc = true; end: return rc; } liboauth2-2.1.0/src/openidc_int.h000066400000000000000000000057151475305260400166540ustar00rootroot00000000000000#ifndef _OAUTH2_OPENIDC_INT_H_ #define _OAUTH2_OPENIDC_INT_H_ /*************************************************************************** * * Copyright (C) 2018-2025 - ZmartZone Holding BV * * 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. * * @Author: Hans Zandbelt - hans.zandbelt@openidc.com * **************************************************************************/ #include "oauth2/openidc.h" typedef struct oauth2_openidc_provider_t { char *issuer; char *authorization_endpoint; char *token_endpoint; char *jwks_uri; char *userinfo_endpoint; } oauth2_openidc_provider_t; typedef struct oauth2_openidc_client_t { char *client_id; char *client_secret; char *scope; oauth2_cfg_endpoint_auth_t *token_endpoint_auth; oauth2_flag_t ssl_verify; oauth2_uint_t http_timeout; oauth2_openidc_provider_t *provider; } oauth2_openidc_client_t; #define _OAUTH2_OPENIDC_PROTO_STATE_KEY_ISSUER "i" #define _OAUTH2_OPENIDC_PROTO_STATE_KEY_TARGET_LINK_URI "l" #define _OAUTH2_OPENIDC_PROTO_STATE_KEY_REQUEST_METHOD "m" #define _OAUTH2_OPENIDC_PROTO_STATE_KEY_RESPONSE_MODE "r" #define _OAUTH2_OPENIDC_PROTO_STATE_KEY_RESPONSE_TYPE "y" #define _OAUTH2_OPENIDC_PROTO_STATE_KEY_TIMESTAMP "t" #define _OAUTH2_OPENIDC_PROTO_STATE_KEY_PKCE "p" bool _oauth2_openidc_state_cookie_get( oauth2_log_t *log, const oauth2_cfg_openidc_t *cfg, oauth2_http_request_t *request, oauth2_http_response_t *response, const char *state, oauth2_openidc_proto_state_t **proto_state); bool _oauth2_openidc_state_cookie_set(oauth2_log_t *log, const oauth2_cfg_openidc_t *cfg, oauth2_openidc_provider_t *provider, const oauth2_http_request_t *request, oauth2_http_response_t *response, const char *state, const char *pkce); bool _oauth2_openidc_state_validate(oauth2_log_t *log, const oauth2_cfg_openidc_t *cfg, oauth2_http_request_t *request, oauth2_openidc_proto_state_t *proto_state, oauth2_openidc_provider_t **provider); bool oauth2_openidc_proto_state_target_link_uri_get( oauth2_log_t *log, oauth2_openidc_proto_state_t *p, char **value); bool oauth2_openidc_proto_state_pkce_get(oauth2_log_t *log, oauth2_openidc_proto_state_t *p, char **value); bool _oauth2_openidc_provider_resolve(oauth2_log_t *log, const oauth2_cfg_openidc_t *cfg, const oauth2_http_request_t *request, const char *issuer, oauth2_openidc_provider_t **provider); #endif /* _OAUTH2_OPENIDC_INT_H_ */ liboauth2-2.1.0/src/proto.c000066400000000000000000000353211475305260400155130ustar00rootroot00000000000000/*************************************************************************** * * Copyright (C) 2018-2025 - ZmartZone Holding BV * * 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. * * @Author: Hans Zandbelt - hans.zandbelt@openidc.com * **************************************************************************/ #include "oauth2/proto.h" #include "oauth2/mem.h" #include "cfg_int.h" #include "util_int.h" #include #define OAUTH2_CFG_SOURCE_TOKEN_HEADER_NAME_DEFAULT \ OAUTH2_HTTP_HDR_AUTHORIZATION #define OAUTH2_CFG_SOURCE_TOKEN_HEADER_TYPE_DEFAULT OAUTH2_HTTP_HDR_BEARER static char * _oauth2_get_source_token_from_header(oauth2_log_t *log, oauth2_cfg_source_token_t *cfg, oauth2_http_request_t *request) { char *source_token = NULL; char *scheme = NULL; const char *auth_line = NULL; char *name = NULL; char *type = NULL; oauth2_debug(log, "enter"); name = cfg->accept_in.header.name ? cfg->accept_in.header.name : OAUTH2_CFG_SOURCE_TOKEN_HEADER_NAME_DEFAULT; type = cfg->accept_in.header.type ? cfg->accept_in.header.type : OAUTH2_CFG_SOURCE_TOKEN_HEADER_TYPE_DEFAULT; auth_line = oauth2_http_request_header_get(log, request, name); if (auth_line == NULL) goto end; oauth2_debug(log, "%s header found", name); if ((type != NULL) && (strcmp(type, "") != 0)) { scheme = oauth2_getword(&auth_line, ' '); if (strcasecmp(scheme, type) != 0) { oauth2_warn( log, "client used unsupported authentication scheme: %s", scheme); goto end; } } while (isspace(*auth_line)) auth_line++; source_token = oauth2_strdup(auth_line); if (source_token != NULL) if (oauth2_cfg_source_token_get_strip(cfg) != 0) oauth2_http_request_header_unset(log, request, name); end: if (scheme) oauth2_mem_free(scheme); oauth2_debug(log, "leave: %s", source_token); return source_token; } #define OAUTH2_CFG_SOURCE_TOKEN_QUERY_PARAMNAME_DEFAULT "access_token" static char *_oauth2_get_source_token_from_query(oauth2_log_t *log, oauth2_cfg_source_token_t *cfg, oauth2_http_request_t *request) { char *source_token = NULL; char *name = NULL; oauth2_debug(log, "enter"); name = cfg->accept_in.query.param_name ? cfg->accept_in.query.param_name : OAUTH2_CFG_SOURCE_TOKEN_QUERY_PARAMNAME_DEFAULT; source_token = oauth2_strdup( oauth2_http_request_query_param_get(log, request, name)); if (source_token == NULL) { oauth2_debug( log, "no source token found in query parameter: %s", name); } else if (oauth2_cfg_source_token_get_strip(cfg) != 0) { oauth2_debug(log, "stripping query param %s from outgoing request", name); oauth2_http_request_query_param_unset(log, request, name); } return source_token; } #define OAUTH2_CFG_SOURCE_TOKEN_POST_PARAMNAME_DEFAULT "access_token" static char *_oauth2_get_source_token_from_post( oauth2_log_t *log, oauth2_cfg_source_token_t *cfg, oauth2_http_request_t *request, oauth2_cfg_server_callback_funcs_t *srv_cb, void *srv_cb_ctx) { char *source_token = NULL; oauth2_nv_list_t *params = NULL; const char *content_type = NULL; char *name = NULL; oauth2_debug(log, "enter"); name = cfg->accept_in.post.param_name ? cfg->accept_in.post.param_name : OAUTH2_CFG_SOURCE_TOKEN_POST_PARAMNAME_DEFAULT; content_type = oauth2_http_request_header_content_type_get(log, request); if ((oauth2_http_request_method_get(log, request) != OAUTH2_HTTP_METHOD_POST) || (strcasecmp(content_type, OAUTH2_CONTENT_TYPE_FORM_ENCODED) != 0)) { oauth2_debug(log, "no form-encoded HTTP POST"); goto end; } if (srv_cb->form_post(log, srv_cb_ctx, ¶ms) == false) { oauth2_error(log, "HTTP POST read callback failed"); goto end; } source_token = oauth2_strdup(oauth2_nv_list_get(log, params, name)); if (source_token == NULL) { oauth2_debug(log, "no source token found in POST parameter: %s", name); } else if (oauth2_cfg_source_token_get_strip(cfg) != 0) { // TBD: would work if we can remove stuff across // brigades/buckets in the // input filter... // sts_userdata_set_post_param(r, post_param_name, // NULL); oauth2_warn(log, "stripping post param %s from outgoing request " "is not supported!", name); } end: if (params) oauth2_nv_list_free(log, params); oauth2_debug(log, "leave: %s", source_token); return source_token; } #define OAUTH2_CFG_SOURCE_TOKEN_COOKIE_NAME_DEFAULT "access_token" static char * _oauth2_get_source_token_from_cookie(oauth2_log_t *log, oauth2_cfg_source_token_t *cfg, oauth2_http_request_t *request) { char *source_token = NULL; char *name = NULL; oauth2_debug(log, "enter"); name = cfg->accept_in.cookie.name ? cfg->accept_in.cookie.name : OAUTH2_CFG_SOURCE_TOKEN_COOKIE_NAME_DEFAULT; source_token = oauth2_http_request_cookie_get( log, request, name, oauth2_cfg_source_token_get_strip(cfg)); if (source_token == NULL) oauth2_debug(log, "no source token found in cookie: %s", name); return source_token; } #define OAUTH2_CFG_SOURCE_TOKEN_ENVVAR_NAME_DEFAULT "access_token" static char *_oauth2_get_source_token_from_envvar( oauth2_log_t *log, oauth2_cfg_source_token_t *cfg, oauth2_cfg_server_callback_funcs_t *srv_cb, void *srv_cb_ctx) { char *source_token = NULL; char *name = NULL; oauth2_debug(log, "enter"); name = cfg->accept_in.envvar.name ? cfg->accept_in.envvar.name : OAUTH2_CFG_SOURCE_TOKEN_ENVVAR_NAME_DEFAULT; if (srv_cb->get(log, srv_cb_ctx, name, &source_token) == false) { oauth2_error(log, "environment variable get callback failed"); goto end; } if (source_token == NULL) { oauth2_debug(log, "no source token found in %s environment variable", name); goto end; }; if (oauth2_cfg_source_token_get_strip(cfg)) srv_cb->set(log, srv_cb_ctx, name, NULL); end: return source_token; } static char *_oauth2_get_source_token_from_basic(oauth2_log_t *log, oauth2_cfg_source_token_t *cfg, oauth2_http_request_t *request) { char *source_token = NULL; char *decoded_line = NULL; size_t decoded_len = 0; const char *auth_line = NULL; char *ptr = NULL; char *scheme = NULL; oauth2_debug(log, "enter"); auth_line = oauth2_http_request_header_get( log, request, OAUTH2_HTTP_HDR_AUTHORIZATION); if (auth_line == NULL) goto end; oauth2_debug(log, "%s header found", OAUTH2_HTTP_HDR_AUTHORIZATION); scheme = oauth2_getword(&auth_line, ' '); if ((scheme == NULL) || (strcasecmp(scheme, "Basic") != 0)) { oauth2_warn(log, "client used unsupported " "authentication scheme: %s", scheme); goto end; } while (isspace(*auth_line)) auth_line++; if (oauth2_base64_decode(log, auth_line, (uint8_t **)&decoded_line, &decoded_len) == false) goto end; decoded_line[decoded_len] = '\0'; ptr = decoded_line; if (strchr(ptr, ':') != NULL) { oauth2_mem_free(oauth2_getword((const char **)&ptr, ':')); source_token = oauth2_strdup(ptr); } if ((source_token != NULL) && (oauth2_cfg_source_token_get_strip(cfg) != 0)) oauth2_http_request_header_unset(log, request, OAUTH2_HTTP_HDR_AUTHORIZATION); end: if (scheme) oauth2_mem_free(scheme); if (decoded_line) oauth2_mem_free(decoded_line); return source_token; } char *oauth2_get_source_token(oauth2_log_t *log, oauth2_cfg_source_token_t *cfg, oauth2_http_request_t *request, oauth2_cfg_server_callback_funcs_t *srv_cb, void *srv_cb_ctx) { char *source_token = NULL; char accept_source_token_in = oauth2_cfg_source_token_get_accept_in(cfg); if ((source_token == NULL) && (accept_source_token_in & OAUTH2_CFG_TOKEN_IN_ENVVAR)) source_token = _oauth2_get_source_token_from_envvar( log, cfg, srv_cb, srv_cb_ctx); if ((source_token == NULL) && (accept_source_token_in & OAUTH2_CFG_TOKEN_IN_HEADER)) source_token = _oauth2_get_source_token_from_header(log, cfg, request); if ((source_token == NULL) && (accept_source_token_in & OAUTH2_CFG_TOKEN_IN_QUERY)) { source_token = _oauth2_get_source_token_from_query(log, cfg, request); } if ((source_token == NULL) && (accept_source_token_in & OAUTH2_CFG_TOKEN_IN_POST)) { source_token = _oauth2_get_source_token_from_post( log, cfg, request, srv_cb, srv_cb_ctx); } if ((source_token == NULL) && (accept_source_token_in & OAUTH2_CFG_TOKEN_IN_COOKIE)) source_token = _oauth2_get_source_token_from_cookie(log, cfg, request); if ((source_token == NULL) && (accept_source_token_in & OAUTH2_CFG_TOKEN_IN_BASIC)) source_token = _oauth2_get_source_token_from_basic(log, cfg, request); if (source_token == NULL) { oauth2_debug(log, "no source token found in any of the configured " "methods: %x", accept_source_token_in); } return source_token; } bool oauth2_proto_request(oauth2_log_t *log, const oauth2_cfg_endpoint_t *token_endpoint, oauth2_http_call_ctx_t *ctx, const oauth2_nv_list_t *params, char **rtoken, oauth2_uint_t *status_code) { bool rc = false; char *response = NULL; json_t *result = NULL; char *tkn = NULL; oauth2_http_call_ctx_ssl_verify_set( log, ctx, oauth2_cfg_endpoint_get_ssl_verify(token_endpoint)); oauth2_http_call_ctx_timeout_set( log, ctx, oauth2_cfg_endpoint_get_http_timeout(token_endpoint)); // oauth2_http_call_ctx_outgoing_proxy_set(log, ctx, outgoing_proxy); if (oauth2_http_post_form(log, oauth2_cfg_endpoint_get_url(token_endpoint), params, ctx, &response, status_code) == false) goto end; if ((*status_code < 200) || (*status_code >= 300)) goto end; if (oauth2_json_decode_check_error(log, response, &result) == false) goto end; if (oauth2_json_string_get(log, result, OAUTH2_ACCESS_TOKEN, &tkn, NULL) == false) goto end; if (tkn == NULL) { oauth2_error(log, "no access token found in result"); goto end; } *rtoken = oauth2_strdup(tkn); rc = true; /* char **token_type = NULL; sts_util_json_object_get_string(r->pool, result, "token_type", token_type, NULL); 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; } } sts_util_json_object_get_int(r->pool, result, OIDC_PROTO_EXPIRES_IN, expires_in, -1); sts_util_json_object_get_string(r->pool, result, OIDC_PROTO_REFRESH_TOKEN, refresh_token, NULL); */ end: if (response) oauth2_mem_free(response); if (tkn) oauth2_mem_free(tkn); if (result) json_decref(result); return rc; } #define OAUTH2_PROTO_ROPC_GRANT_TYPE_VALUE "password" #define OAUTH2_PROTO_ROPC_USERNAME "username" #define OAUTH2_PROTO_ROPC_PASSWORD "password" bool oauth2_ropc_exec(oauth2_log_t *log, oauth2_cfg_ropc_t *cfg, const char *username, const char *password, char **rtoken, oauth2_uint_t *status_code) { bool rc = false; oauth2_nv_list_t *params = NULL; oauth2_http_call_ctx_t *ctx = NULL; const char *client_id = oauth2_cfg_ropc_get_client_id(cfg); const oauth2_cfg_endpoint_t *token_endpoint = oauth2_cfg_ropc_get_token_endpoint(cfg); oauth2_debug(log, "enter"); if (cfg == NULL) { oauth2_error(log, "token endpoint cfg is not set"); goto end; } if (token_endpoint == NULL) { oauth2_warn(log, "token endpoint is not set"); goto end; } params = oauth2_nv_list_init(log); oauth2_nv_list_add(log, params, OAUTH2_GRANT_TYPE, OAUTH2_PROTO_ROPC_GRANT_TYPE_VALUE); if ((oauth2_cfg_endpoint_auth_type(oauth2_cfg_endpoint_get_auth( token_endpoint)) == OAUTH2_ENDPOINT_AUTH_NONE) && (client_id != NULL)) oauth2_nv_list_add(log, params, OAUTH2_CLIENT_ID, client_id); if (username != NULL) oauth2_nv_list_add(log, params, OAUTH2_PROTO_ROPC_USERNAME, username); oauth2_nv_list_add(log, params, OAUTH2_PROTO_ROPC_PASSWORD, password); oauth2_nv_list_merge_into( log, oauth2_cfg_ropc_get_request_parameters(cfg), params); ctx = oauth2_http_call_ctx_init(log); if (ctx == NULL) goto end; oauth2_http_call_ctx_ssl_verify_set( log, ctx, oauth2_cfg_endpoint_get_ssl_verify(token_endpoint)); oauth2_http_call_ctx_outgoing_proxy_set( log, ctx, oauth2_cfg_endpoint_get_outgoing_proxy(token_endpoint)); if (oauth2_http_ctx_auth_add( log, ctx, oauth2_cfg_endpoint_get_auth(token_endpoint), params) == false) goto end; rc = oauth2_proto_request(log, oauth2_cfg_ropc_get_token_endpoint(cfg), ctx, params, rtoken, status_code); end: if (params) oauth2_nv_list_free(log, params); if (ctx) oauth2_http_call_ctx_free(log, ctx); oauth2_debug(log, "leave: %d", rc); return rc; } #define OAUTH2_PROTO_CC_GRANT_TYPE_VALUE "client_credentials" bool oauth2_cc_exec(oauth2_log_t *log, oauth2_cfg_cc_t *cfg, char **rtoken, oauth2_uint_t *status_code) { bool rc = false; oauth2_nv_list_t *params = NULL; oauth2_http_call_ctx_t *ctx = NULL; const char *client_id = oauth2_cfg_cc_get_client_id(cfg); const oauth2_cfg_endpoint_t *token_endpoint = oauth2_cfg_cc_get_token_endpoint(cfg); oauth2_debug(log, "enter"); if (cfg == NULL) { oauth2_error(log, "token endpoint cfg is not set"); goto end; } if (token_endpoint == NULL) { oauth2_warn(log, "token endpoint is not set"); goto end; } params = oauth2_nv_list_init(log); oauth2_nv_list_add(log, params, OAUTH2_GRANT_TYPE, OAUTH2_PROTO_CC_GRANT_TYPE_VALUE); if ((oauth2_cfg_endpoint_auth_type(oauth2_cfg_endpoint_get_auth( token_endpoint)) == OAUTH2_ENDPOINT_AUTH_NONE) && (client_id != NULL)) oauth2_nv_list_add(log, params, OAUTH2_CLIENT_ID, client_id); oauth2_nv_list_merge_into( log, oauth2_cfg_cc_get_request_parameters(cfg), params); ctx = oauth2_http_call_ctx_init(log); if (ctx == NULL) goto end; oauth2_http_call_ctx_ssl_verify_set( log, ctx, oauth2_cfg_endpoint_get_ssl_verify(token_endpoint)); oauth2_http_call_ctx_outgoing_proxy_set( log, ctx, oauth2_cfg_endpoint_get_outgoing_proxy(token_endpoint)); if (oauth2_http_ctx_auth_add( log, ctx, oauth2_cfg_endpoint_get_auth(token_endpoint), params) == false) goto end; rc = oauth2_proto_request(log, oauth2_cfg_cc_get_token_endpoint(cfg), ctx, params, rtoken, status_code); end: if (params) oauth2_nv_list_free(log, params); if (ctx) oauth2_http_call_ctx_free(log, ctx); oauth2_debug(log, "leave: %d", rc); return rc; } liboauth2-2.1.0/src/server/000077500000000000000000000000001475305260400155065ustar00rootroot00000000000000liboauth2-2.1.0/src/server/.gitignore000066400000000000000000000000561475305260400174770ustar00rootroot00000000000000/.dirstamp /.deps/ /.libs/ /*.lo /*.o /*.gcno liboauth2-2.1.0/src/server/apache.c000066400000000000000000000723621475305260400171050ustar00rootroot00000000000000/*************************************************************************** * * Copyright (C) 2018-2025 - ZmartZone Holding BV * * 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. * * @Author: Hans Zandbelt - hans.zandbelt@openidc.com * **************************************************************************/ #include #include #include #include #include "util_int.h" #include #include #include #include #include #include // clang-format off oauth2_uint_t log_level_apache2oauth2[] = { OAUTH2_LOG_ERROR, OAUTH2_LOG_ERROR, OAUTH2_LOG_ERROR, OAUTH2_LOG_ERROR, OAUTH2_LOG_WARN, OAUTH2_LOG_NOTICE, OAUTH2_LOG_INFO, OAUTH2_LOG_DEBUG, OAUTH2_LOG_TRACE1, OAUTH2_LOG_TRACE2, OAUTH2_LOG_TRACE1, OAUTH2_LOG_TRACE1, OAUTH2_LOG_TRACE1, OAUTH2_LOG_TRACE1, OAUTH2_LOG_TRACE1, OAUTH2_LOG_TRACE1, OAUTH2_LOG_TRACE1 }; oauth2_uint_t log_level_log2apache[] = { APLOG_ERR, APLOG_WARNING, APLOG_NOTICE, APLOG_INFO, APLOG_DEBUG, APLOG_TRACE1, APLOG_TRACE1 }; oauth2_http_method_t request_method_apache2oauth2[] = { OAUTH2_HTTP_METHOD_GET, OAUTH2_HTTP_METHOD_PUT, OAUTH2_HTTP_METHOD_POST, OAUTH2_HTTP_METHOD_DELETE, OAUTH2_HTTP_METHOD_CONNECT, OAUTH2_HTTP_METHOD_OPTIONS, OAUTH2_HTTP_METHOD_UNKNOWN, OAUTH2_HTTP_METHOD_UNKNOWN, OAUTH2_HTTP_METHOD_UNKNOWN, OAUTH2_HTTP_METHOD_UNKNOWN, OAUTH2_HTTP_METHOD_UNKNOWN, OAUTH2_HTTP_METHOD_UNKNOWN, OAUTH2_HTTP_METHOD_UNKNOWN, OAUTH2_HTTP_METHOD_UNKNOWN, OAUTH2_HTTP_METHOD_UNKNOWN, OAUTH2_HTTP_METHOD_UNKNOWN, OAUTH2_HTTP_METHOD_UNKNOWN, OAUTH2_HTTP_METHOD_UNKNOWN, OAUTH2_HTTP_METHOD_UNKNOWN, OAUTH2_HTTP_METHOD_UNKNOWN, OAUTH2_HTTP_METHOD_UNKNOWN, OAUTH2_HTTP_METHOD_UNKNOWN, OAUTH2_HTTP_METHOD_UNKNOWN, OAUTH2_HTTP_METHOD_UNKNOWN, OAUTH2_HTTP_METHOD_UNKNOWN, OAUTH2_HTTP_METHOD_UNKNOWN, OAUTH2_HTTP_METHOD_UNKNOWN }; // clang-format on static apr_status_t oauth2_apache_cfg_srv_free(void *data) { oauth2_apache_cfg_srv_t *cfg = (oauth2_apache_cfg_srv_t *)data; // ap_log_error(APLOG_MARK, APLOG_WARNING, 0, // (const server_rec //*)oauth2_log_sink_ctx_get(cfg->sink), // "%s: %s: %pp", __FUNCTION__, "free", cfg); if (cfg) { if (cfg->log) oauth2_log_free(cfg->log); oauth2_mem_free(cfg); } return APR_SUCCESS; } void *oauth2_apache_cfg_srv_create(apr_pool_t *pool, server_rec *s, oauth2_log_function_t server_log_cb) { oauth2_apache_cfg_srv_t *cfg = (oauth2_apache_cfg_srv_t *)oauth2_mem_alloc( sizeof(oauth2_apache_cfg_srv_t)); // NB: this is not actually set to the/a configured level here... oauth2_uint_t level = (s && (s->log.level != -1)) ? log_level_apache2oauth2[s->log.level] : OAUTH2_LOG_TRACE1; cfg->sink = oauth2_log_sink_create(level, server_log_cb, s); cfg->log = oauth2_log_init(level, cfg->sink); // ap_log_error(APLOG_MARK, APLOG_WARNING, 0, // (const server_rec //*)oauth2_log_sink_ctx_get(cfg->sink), // "%s: %s: %pp", __FUNCTION__, "create", cfg); // note: cleanup as part of oauth2_apache_child_cleanup does not work // with multiple modules loaded apr_pool_cleanup_register(pool, cfg, oauth2_apache_cfg_srv_free, oauth2_apache_cfg_srv_free); return cfg; } void *oauth2_apache_cfg_srv_merge(apr_pool_t *pool, void *b, void *a) { oauth2_apache_cfg_srv_t *add = (oauth2_apache_cfg_srv_t *)a; oauth2_apache_cfg_srv_t *cfg = oauth2_apache_cfg_srv_create( pool, (server_rec *)oauth2_log_sink_ctx_get(add->sink), oauth2_log_sink_ctx_get(add->sink)); // ap_log_error(APLOG_MARK, APLOG_WARNING, 0, // (const server_rec //*)oauth2_log_sink_ctx_get(cfg->sink), // "%s: %s: %pp", __FUNCTION__, "merge", cfg); return cfg; } /* * parent/child cleanup */ apr_status_t oauth2_apache_child_cleanup(void *data, module *m, const char *package_name_version) { oauth2_shutdown(NULL); return APR_SUCCESS; } apr_status_t oauth2_apache_parent_cleanup(void *data, module *m, const char *package_name_version) { server_rec *s = (server_rec *)data; oauth2_apache_cfg_srv_t *cfg = (oauth2_apache_cfg_srv_t *)ap_get_module_config(s->module_config, m); oauth2_info(cfg->log, "%s-%s - shutdown", package_name_version, oauth2_package_string()); oauth2_apache_child_cleanup(s, m, package_name_version); return APR_SUCCESS; } APR_DECLARE_OPTIONAL_FN(char *, ssl_var_lookup, (apr_pool_t *, server_rec *, conn_rec *, request_rec *, char *)); static APR_OPTIONAL_FN_TYPE(ssl_var_lookup) *_oauth2_ssl_var_lookup = NULL; static const char *oauth2_apache_ssl_var_lookup(apr_pool_t *p, server_rec *s, conn_rec *c, request_rec *r, const char *var) { return (_oauth2_ssl_var_lookup != NULL) ? (const char *)_oauth2_ssl_var_lookup(p, s, c, r, (char *)var) : NULL; } /* * post config */ int oauth2_apache_post_config(apr_pool_t *pool, apr_pool_t *p1, apr_pool_t *p2, server_rec *s, module *m, const char *package_name_version, apr_status_t (*parent_cleanup)(void *), apr_status_t (*child_cleanup)(void *)) { void *data = NULL; oauth2_log_t *p = NULL; oauth2_apache_cfg_srv_t *cfg = NULL; server_rec *sp = NULL; apr_pool_userdata_get(&data, package_name_version, s->process->pool); if (data == NULL) { apr_pool_userdata_set((const void *)1, package_name_version, apr_pool_cleanup_null, s->process->pool); goto end; } p = oauth2_init(OAUTH2_LOG_INFO, NULL); oauth2_log_free(p); sp = s; for (sp = s; sp; sp = sp->next) { cfg = (oauth2_apache_cfg_srv_t *)ap_get_module_config( sp->module_config, m); // only now the level has been set according to the config! oauth2_log_sink_level_set( cfg->sink, (sp && (sp->log.level != -1)) ? log_level_apache2oauth2[sp->log.level] : OAUTH2_LOG_TRACE1); } apr_pool_cleanup_register(pool, s, parent_cleanup, child_cleanup); _oauth2_ssl_var_lookup = APR_RETRIEVE_OPTIONAL_FN(ssl_var_lookup); cfg = (oauth2_apache_cfg_srv_t *)ap_get_module_config(s->module_config, m); ap_log_error(APLOG_MARK, APLOG_INFO, 0, s, "%s-%s - init", package_name_version, oauth2_package_string()); end: return OK; } static int oauth2_apache_http_request_hdr_add(void *rec, const char *key, const char *value) { oauth2_apache_request_ctx_t *ctx = (oauth2_apache_request_ctx_t *)rec; return (oauth2_http_request_header_add(ctx->log, ctx->request, key, value) == true); } static const char *oauth2_apache_get_server_name(request_rec *r) { const char *rv = NULL; #ifdef APACHE2_0 rv = (char *)ap_get_server_name(r); #else rv = (char *)ap_get_server_name_for_url(r); #endif return rv; } static const char *oauth2_apache_http_scheme(request_rec *r) { const char *rv = NULL; #ifdef APACHE2_0 rv = ap_http_method(r); #else rv = ap_http_scheme(r); #endif return rv; } static oauth2_apache_request_ctx_t * oauth2_apache_request_context_init(request_rec *r, oauth2_log_function_t request_log_cb) { oauth2_apache_request_ctx_t *ctx = NULL; oauth2_log_sink_t *log_sink_apache = NULL; // TODO: memory allocation failure checks...? ctx = oauth2_mem_alloc(sizeof(oauth2_apache_request_ctx_t)); ctx->r = r; // TODO: more elegant log-for-request handling oauth2_log_level_t level = (r && r->log) ? log_level_apache2oauth2[r->log->level] : OAUTH2_LOG_TRACE1; log_sink_apache = oauth2_log_sink_create(level, request_log_cb, r); ctx->log = oauth2_log_init(level, log_sink_apache); ctx->request = oauth2_http_request_init(ctx->log); oauth2_http_request_scheme_set(ctx->log, ctx->request, oauth2_apache_http_scheme(r)); oauth2_http_request_hostname_set(ctx->log, ctx->request, oauth2_apache_get_server_name(r)); oauth2_http_request_port_set(ctx->log, ctx->request, r->connection->local_addr->port); oauth2_http_request_path_set(ctx->log, ctx->request, r->uri); oauth2_http_request_method_set( ctx->log, ctx->request, request_method_apache2oauth2[r->method_number]); oauth2_http_request_query_set(ctx->log, ctx->request, r->args); apr_table_do(oauth2_apache_http_request_hdr_add, ctx, r->headers_in, NULL); /* * a workaround since mod_ssl's CGI envvar setting happens * only in the fixup handler phase */ oauth2_http_request_context_set( ctx->log, ctx->request, OAUTH2_TLS_CERT_VAR_NAME, oauth2_apache_ssl_var_lookup(r->pool, r->server, r->connection, r, "SSL_CLIENT_CERT")); oauth2_debug(ctx->log, "created request context: %p", ctx); return ctx; } static apr_status_t oauth2_apache_request_context_free(void *rec) { oauth2_apache_request_ctx_t *ctx = (oauth2_apache_request_ctx_t *)rec; if (ctx) { oauth2_debug(ctx->log, "dispose request context: %p", ctx); oauth2_http_request_free(ctx->log, ctx->request); oauth2_log_free(ctx->log); oauth2_mem_free(ctx); } return APR_SUCCESS; } oauth2_apache_request_ctx_t * oauth2_apache_request_context(request_rec *rr, oauth2_log_function_t request_log_cb, const char *user_data_key) { request_rec *r = (rr->main != NULL) ? rr->main : rr; oauth2_apache_request_ctx_t *ctx = NULL; apr_pool_userdata_get((void **)&ctx, user_data_key, r->pool); if (ctx == NULL) { ctx = oauth2_apache_request_context_init(r, request_log_cb); apr_pool_userdata_set((const void *)ctx, user_data_key, oauth2_apache_request_context_free, r->pool); } return ctx; } int oauth2_apache_return_www_authenticate(oauth2_cfg_source_token_t *cfg, oauth2_apache_request_ctx_t *ctx, int status_code, const char *error, const char *error_description) { oauth2_uint_t accept_token_in = OAUTH2_CFG_UINT_UNSET; char *hdr = NULL; oauth2_debug(ctx->log, "enter"); accept_token_in = oauth2_cfg_source_token_get_accept_in(cfg); if (accept_token_in == OAUTH2_CFG_TOKEN_IN_BASIC) { hdr = apr_psprintf(ctx->r->pool, "%s", OAUTH2_HTTP_HDR_BASIC); } else { hdr = apr_psprintf(ctx->r->pool, "%s", OAUTH2_HTTP_HDR_BEARER); } if (ap_auth_name(ctx->r) != NULL) hdr = apr_psprintf(ctx->r->pool, "%s %s=\"%s\"", hdr, OAUTH2_HTTP_HDR_REALM, ap_auth_name(ctx->r)); if (error != NULL) hdr = apr_psprintf(ctx->r->pool, "%s%s %s=\"%s\"", hdr, (ap_auth_name(ctx->r) ? "," : ""), OAUTH2_ERROR, error); if (error_description != NULL) hdr = apr_psprintf(ctx->r->pool, "%s, %s=\"%s\"", hdr, OAUTH2_ERROR_DESCRIPTION, error_description); oauth2_apache_hdr_out_add(ctx->log, ctx->r, OAUTH2_HTTP_HDR_WWW_AUTHENTICATE, hdr); oauth2_debug(ctx->log, "leave"); return status_code; } bool oauth2_apache_request_header_set(oauth2_log_t *log, void *rec, const char *name, const char *value) { request_rec *r = (request_rec *)rec; oauth2_debug(log, "setting request header: %s=%s", name, value); apr_table_set(r->headers_in, name, value); return true; } bool oauth2_apache_http_request_set(oauth2_log_t *log, oauth2_http_request_t *request, request_rec *r) { bool rc = false; if (request == NULL) goto end; oauth2_http_request_headers_loop(log, request, oauth2_apache_request_header_set, r); r->args = apr_pstrdup(r->pool, oauth2_http_request_query_get(log, request)); rc = true; end: return rc; } bool oauth2_apache_response_header_add(oauth2_log_t *log, void *rec, const char *name, const char *value) { request_rec *r = (request_rec *)rec; oauth2_apache_hdr_out_add(log, r, name, value); return true; } bool oauth2_apache_http_response_set(oauth2_log_t *log, oauth2_http_response_t *response, request_rec *r) { bool rc = false; if ((response == NULL) || (r == NULL)) goto end; oauth2_http_response_headers_loop(log, response, oauth2_apache_response_header_add, r); r->status = oauth2_http_response_status_code_get(log, response); rc = true; end: return rc; } void oauth2_apache_hdr_out_add(oauth2_log_t *log, const request_rec *r, const char *name, const char *value) { oauth2_debug(log, "%s: %s", name, value); apr_table_add(r->err_headers_out, name, value); } void oauth2_apache_scrub_headers(oauth2_apache_request_ctx_t *ctx, oauth2_cfg_target_pass_t *target_pass) { apr_hash_t *scrub_hdrs = NULL; const char *prefix = NULL; int prefix_len = 0; const char *authn_hdr = NULL; const apr_array_header_t *h = NULL; const apr_table_entry_t *e = NULL; const char *k = NULL; apr_table_t *clean_headers = NULL; bool prefix_matches = false; bool header_matches = false; int i = 0; const char *hdr = NULL; if (oauth2_cfg_target_pass_get_as_headers(target_pass) == false) goto end; prefix = oauth2_cfg_target_pass_get_prefix(target_pass); scrub_hdrs = apr_hash_make(ctx->r->pool); if (strcmp(prefix, "") == 0) { /* if ((cfg->white_listed_claims != NULL) && (apr_hash_count(cfg->white_listed_claims) > 0)) scrub_hdrs = apr_hash_overlay(r->pool, cfg->white_listed_claims, scrub_hdrs); else oidc_warn(r, "both " OIDCClaimPrefix " and " OIDCWhiteListedClaims " are empty: this renders an insecure setup!"); */ } authn_hdr = oauth2_cfg_target_pass_get_authn_header(target_pass); if (authn_hdr != NULL) apr_hash_set(scrub_hdrs, authn_hdr, APR_HASH_KEY_STRING, authn_hdr); prefix_len = prefix ? strlen(prefix) : 0; h = apr_table_elts(ctx->r->headers_in); clean_headers = apr_table_make(ctx->r->pool, h->nelts); e = (const apr_table_entry_t *)h->elts; for (i = 0; i < h->nelts; i++) { k = e[i].key; hdr = (k != NULL) && (scrub_hdrs != NULL) ? apr_hash_get(scrub_hdrs, k, APR_HASH_KEY_STRING) : NULL; header_matches = (hdr != NULL) && (oauth2_strnenvcmp(k, hdr, -1) == 0); prefix_matches = (k != NULL) && prefix_len && (oauth2_strnenvcmp(k, prefix, prefix_len) == 0); if (prefix_matches || header_matches) { oauth2_warn( ctx->log, "cleaned suspicious request header (%s: %.32s)", k, e[i].val); continue; } apr_table_addn(clean_headers, k, e[i].val); } ctx->r->headers_in = clean_headers; end: return; } static const char *oauth2_apache_get_envvar(oauth2_log_t *log, request_rec *r, const char *name) { oauth2_debug(log, "get environment variable: %s", name); return apr_table_get(r->subprocess_env, name); } static void oauth2_apache_set_envvar(oauth2_log_t *log, request_rec *r, const char *name, const char *value) { oauth2_debug(log, "set environment variable: %s=%s", name, value); if (value) apr_table_set(r->subprocess_env, name, value); else apr_table_unset(r->subprocess_env, name); /* #define OAUTH2_APACHE2_USERDATA_ENV_KEY "oauth2_apache_userdata_env_key" * // TODO: pull and set in fixup handler apr_table_t *env = NULL; apr_pool_userdata_get((void **) &env, OAUTH2_APACHE2_USERDATA_ENV_KEY, r->pool); if (env == NULL) env = apr_table_make(r->pool, 5); apr_table_set(env, name, value); apr_pool_userdata_set(env, OAUTH2_APACHE2_USERDATA_ENV_KEY, NULL, r->pool); */ } #define OAUTH2_MAX_POST_DATA_LEN 1024 * 1024 static bool oauth2_apache_post_read(oauth2_log_t *log, request_rec *r, char **rbuf) { bool rc = false; 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) goto end; len = ap_should_client_block(r) ? r->remaining : 0; if (len > OAUTH2_MAX_POST_DATA_LEN) { oauth2_error( log, "POST parameter value is too large: %lu bytes (max=%d)", (unsigned long)len, OAUTH2_MAX_POST_DATA_LEN); goto end; } *rbuf = oauth2_mem_alloc(len + 1); if (*rbuf == NULL) { oauth2_error( log, "could not allocate memory for %lu bytes of POST data.", (unsigned long)len); goto end; } (*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) { oauth2_error(log, "failed to read POST data from client"); goto end; } bytes_read += read_length; bytes_left -= read_length; } rc = true; end: if ((rc == false) && (*rbuf)) { oauth2_mem_free(*rbuf); *rbuf = NULL; } return rc; } static void oauth2_apache_set_target_info(oauth2_apache_request_ctx_t *ctx, oauth2_cfg_target_pass_t *target_pass, const char *key, const char *value) { char *norm = NULL, *name = NULL; norm = oauth2_normalize_header_name(key); if (norm == NULL) goto end; name = oauth2_stradd( NULL, oauth2_cfg_target_pass_get_prefix(target_pass), norm, NULL); if (name == NULL) goto end; if (oauth2_cfg_target_pass_get_as_headers(target_pass)) oauth2_apache_request_header_set(ctx->log, ctx->r, name, value); if (oauth2_cfg_target_pass_get_as_envvars(target_pass)) oauth2_apache_set_envvar(ctx->log, ctx->r, name, value); end: if (norm) oauth2_mem_free(norm); if (name) oauth2_mem_free(name); } static void oauth2_apache_set_target_infos(oauth2_apache_request_ctx_t *ctx, oauth2_cfg_target_pass_t *target_pass, json_t *json_token) { void *iter = NULL; const char *key = NULL; json_t *value = NULL; char *v = NULL; iter = json_object_iter(json_token); while (iter) { key = json_object_iter_key(iter); value = json_object_iter_value(iter); if (json_is_string(value)) { v = oauth2_strdup(json_string_value(value)); } else { v = oauth2_json_encode(ctx->log, value, JSON_ENCODE_ANY); } oauth2_apache_set_target_info(ctx, target_pass, key, v); if (v) oauth2_mem_free(v); iter = json_object_iter_next(json_token, iter); } } void oauth2_apache_target_pass(oauth2_apache_request_ctx_t *ctx, oauth2_cfg_target_pass_t *target_pass, const char *target_token, json_t *json_token) { const char *authn_hdr = NULL; const char *json_payload_claim = NULL; char *s_compact = NULL; authn_hdr = oauth2_cfg_target_pass_get_authn_header(target_pass); json_payload_claim = oauth2_cfg_target_get_json_payload_claim(target_pass); if ((ctx->r->user != NULL) && (authn_hdr != NULL)) oauth2_apache_request_header_set(ctx->log, ctx->r, authn_hdr, ctx->r->user); oauth2_apache_set_target_infos(ctx, target_pass, json_token); if (target_token != NULL) { // TODO: hmmm, "access_token" or "target_token" or "token" or // configurable...? oauth2_apache_set_target_info(ctx, target_pass, "access_token", target_token); } if (json_payload_claim != NULL) { s_compact = oauth2_json_encode( ctx->log, json_token, JSON_PRESERVE_ORDER | JSON_COMPACT); oauth2_apache_set_target_info(ctx, target_pass, json_payload_claim, s_compact); oauth2_mem_free(s_compact); } // TODO: strip cookies according to config setting // oauth2_http:strip_cookies(r); } bool oauth2_apache_set_request_user(oauth2_cfg_target_pass_t *target_pass, oauth2_apache_request_ctx_t *ctx, json_t *json_token) { bool rc = false; const char *claim = NULL; json_t *remote_user = NULL; if ((target_pass == NULL) || (json_token == NULL)) goto end; claim = oauth2_cfg_target_get_remote_user_claim(target_pass); if (claim == NULL) { oauth2_error(ctx->log, "remote user claim was not set"); goto end; } remote_user = json_object_get(json_token, claim); if ((remote_user == NULL) || (!json_is_string(remote_user))) { oauth2_error(ctx->log, "remote user claim \"%s\" could not be found", claim); goto end; } ctx->r->user = apr_pstrdup(ctx->r->pool, json_string_value(remote_user)); oauth2_debug(ctx->log, "set user to \"%s\" based on claim: %s=%s", ctx->r->user, claim, json_string_value(remote_user)); // TODO: more flexibility and or regular expressions? rc = true; end: return rc; } static bool _oauth2_apache_env_get_cb(oauth2_log_t *log, void *ctx, const char *name, char **value) { *value = oauth2_strdup( oauth2_apache_get_envvar(log, (request_rec *)ctx, name)); return true; } static bool _oauth2_apache_env_set_cb(oauth2_log_t *log, void *ctx, const char *name, const char *value) { oauth2_apache_set_envvar(log, (request_rec *)ctx, name, value); return true; } static bool _oauth2_apache_read_form_post(oauth2_log_t *log, void *ctx, oauth2_nv_list_t **params) { bool rc = false; char *data = NULL; if (oauth2_apache_post_read(log, (request_rec *)ctx, &data) == false) goto end; if (oauth2_parse_form_encoded_params(log, data, params) == false) goto end; rc = true; end: if (data) oauth2_mem_free(data); return rc; } #define OAUTH2_APACHE_USERDATA_KEY "oauth2_apache_userdata" static apr_table_t * oauth2_apache_request_state(oauth2_apache_request_ctx_t *ctx) { request_rec *r = (ctx->r->main != NULL) ? ctx->r->main : ctx->r; apr_table_t *state = NULL; apr_pool_userdata_get((void **)&state, OAUTH2_APACHE_USERDATA_KEY, r->pool); if (state == NULL) { state = apr_table_make(r->pool, 5); apr_pool_userdata_set(state, OAUTH2_APACHE_USERDATA_KEY, NULL, r->pool); } return state; } static void oauth2_apache_request_state_set(oauth2_apache_request_ctx_t *ctx, const char *key, const char *value) { apr_table_t *state = oauth2_apache_request_state(ctx); apr_table_setn(state, key, apr_pstrdup(ctx->r->pool, value)); } static const char * oauth2_apache_request_state_get(oauth2_apache_request_ctx_t *ctx, const char *key) { apr_table_t *state = oauth2_apache_request_state(ctx); return apr_table_get(state, key); } void oauth2_apache_request_state_set_json(oauth2_apache_request_ctx_t *ctx, const char *key, json_t *claims) { char *s = oauth2_json_encode(ctx->log, claims, 0); oauth2_apache_request_state_set(ctx, key, s); oauth2_mem_free(s); } void oauth2_apache_request_state_get_json(oauth2_apache_request_ctx_t *ctx, const char *key, json_t **claims) { const char *s_claims = oauth2_apache_request_state_get(ctx, key); if (s_claims != NULL) oauth2_json_decode_object(ctx->log, s_claims, claims); } static bool oauth2_apache_authz_match_value(oauth2_apache_request_ctx_t *ctx, const char *spec_c, json_t *val, const char *key) { int i = 0; oauth2_debug(ctx->log, "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 (apr_strnatcmp(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) == atoi(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 (apr_strnatcmp(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++) { json_t *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 (apr_strnatcmp(json_string_value(elem), spec_c) == 0) return true; } else if (json_is_boolean(elem)) { if (apr_strnatcmp(json_is_true(elem) ? "true" : "false", spec_c) == 0) return true; } else if (json_is_integer(elem)) { if (json_integer_value(elem) == atoi(spec_c)) return true; } else { oauth2_warn(ctx->log, "unhandled in-array JSON object " "type [%d] for key \"%s\"", elem->type, (const char *)key); } } } else { oauth2_warn(ctx->log, "unhandled JSON object type [%d] for key \"%s\"", val->type, (const char *)key); } return false; } static bool oauth2_apache_authz_match_expr(oauth2_apache_request_ctx_t *ctx, const char *spec_c, json_t *val) { bool rc = false; pcre2_code *preg = NULL; char *error_str = NULL; int i = 0; /* setup the regex; spec_c points to the NULL-terminated value pattern */ preg = oauth2_pcre2_compile(spec_c); if (preg == NULL) { oauth2_error(ctx->log, "pattern [%s] is not a valid regular expression", spec_c); 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 (oauth2_pcre2_exec(preg, json_string_value(val), (int)strlen(json_string_value(val)), &error_str) > 0) { oauth2_debug(ctx->log, "value \"%s\" matched regex \"%s\"", json_string_value(val), spec_c); rc = true; goto end; } else if (error_str) { oauth2_debug(ctx->log, "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 (oauth2_pcre2_exec( preg, json_string_value(elem), (int)strlen(json_string_value(elem)), &error_str) > 0) { oauth2_debug(ctx->log, "array value \"%s\" " "matched regex \"%s\"", json_string_value(elem), spec_c); rc = true; goto end; } else if (error_str) { oauth2_debug(ctx->log, "pcre error (array): %s", error_str); } if (error_str) { oauth2_mem_free(error_str); error_str = NULL; } } } } end: if (error_str) oauth2_mem_free(error_str); if (preg) pcre2_code_free(preg); return rc; } bool oauth2_apache_authz_match_claim(oauth2_apache_request_ctx_t *ctx, const char *const attr_spec, const json_t *const claims) { const char *key; json_t *val; if (claims == NULL) return false; /* loop over all of the user claims */ void *iter = json_object_iter((json_t *)claims); while (iter) { key = json_object_iter_key(iter); val = json_object_iter_value(iter); oauth2_debug(ctx->log, "evaluating key \"%s\"", (const char *)key); const char *attr_c = (const char *)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) == ':') { /* skip the colon */ spec_c++; if (oauth2_apache_authz_match_value(ctx, spec_c, val, key) == true) return true; /* a tilde denotes a string PCRE match */ } else if (!(*attr_c) && (*spec_c) == '~') { /* skip the tilde */ spec_c++; if (oauth2_apache_authz_match_expr(ctx, spec_c, val) == true) return true; /* dot means child nodes must be evaluated */ } else if (!(*attr_c) && (*spec_c) == '.') { /* skip the dot */ spec_c++; if (json_is_object(val)) { oauth2_debug( ctx->log, "attribute chunk matched, evaluating " "children of key: \"%s\".", key); return oauth2_apache_authz_match_claim( ctx, spec_c, json_object_get(claims, key)); } else if (json_is_array(val)) { oauth2_debug( ctx->log, "attribute chunk matched, evaluating array " "values of key: \"%s\".", key); return oauth2_apache_authz_match_value( ctx, spec_c, json_object_get(claims, key), key); } else { oauth2_debug( ctx->log, "\"%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((json_t *)claims, iter); } return false; } authz_status oauth2_apache_authorize(oauth2_apache_request_ctx_t *ctx, const json_t *const claims, const char *require_args, oauth2_apache_authz_match_claim_fn_type match_claim_fn) { int count_oauth_claims = 0; const char *t, *w; if (ctx->r->user == NULL) return AUTHZ_DENIED_NO_USER; /* if no claims, impossible to satisfy */ if (!claims) return AUTHZ_DENIED; t = require_args; while ((w = ap_getword_conf(ctx->r->pool, &t)) && w[0]) { count_oauth_claims++; oauth2_debug(ctx->log, "evaluating claim/expr specification: %s", w); if (match_claim_fn(ctx, w, claims) == TRUE) { oauth2_debug(ctx->log, "require claim/expr '%s' matched", w); return AUTHZ_GRANTED; } } if (count_oauth_claims == 0) { oauth2_warn(ctx->log, "'require claim/expr' missing specification(s) in " "configuration, denying"); } return AUTHZ_DENIED; } // clang-format off oauth2_cfg_server_callback_funcs_t oauth2_apache_server_callback_funcs = { _oauth2_apache_env_get_cb, _oauth2_apache_env_set_cb, _oauth2_apache_read_form_post }; // clang-format on liboauth2-2.1.0/src/server/nginx.c000066400000000000000000000415651475305260400170100ustar00rootroot00000000000000/*************************************************************************** * * Copyright (C) 2018-2025 - ZmartZone Holding BV * * 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. * * @Author: Hans Zandbelt - hans.zandbelt@openidc.com * **************************************************************************/ #include "oauth2/nginx.h" #include #include #include #include // yep, this is tightly aligned with the (sequence of...) the log levels in // lmo/log.h, but is faaast // clang-format off /* static int log_level_nginx2oauth[] = { OAUTH2_LOG_ERROR, OAUTH2_LOG_ERROR, OAUTH2_LOG_ERROR, OAUTH2_LOG_ERROR, OAUTH2_LOG_ERROR, OAUTH2_LOG_WARN, OAUTH2_LOG_NOTICE, OAUTH2_LOG_INFO, OAUTH2_LOG_DEBUG }; */ // TODO: TRACE2 to STDERR?? static int log_level_log2nginx[] = { NGX_LOG_ERR, NGX_LOG_WARN, NGX_LOG_NOTICE, NGX_LOG_INFO, NGX_LOG_DEBUG, NGX_LOG_DEBUG, NGX_LOG_STDERR }; // clang-format on void oauth2_nginx_log(oauth2_log_sink_t *sink, const char *filename, unsigned long line, const char *function, oauth2_log_level_t level, const char *msg) { // TODO: ngx_err_t? ngx_log_error_core(log_level_log2nginx[level], (ngx_log_t *)oauth2_log_sink_ctx_get(sink), 0, "# %s: %s", function, msg); } #define _OAUTH2_NGINX_STRING_COPY(ctx, r_member, set_func) \ char *v = (ctx->r->r_member.len > 0) \ ? oauth2_strndup((const char *)ctx->r->r_member.data, \ ctx->r->r_member.len) \ : NULL; \ oauth2_http_request_##set_func##_set(ctx->log, ctx->request, v); \ oauth2_mem_free(v); #define _OAUTH2_NGINX_START_END_COPY(ctx, r_member_start, r_member_end, \ set_func) \ int len = ctx->r->r_member_end - ctx->r->r_member_start; \ char *v = \ (len > 0) \ ? oauth2_strndup((const char *)ctx->r->r_member_start, len) \ : NULL; \ oauth2_http_request_##set_func##_set(ctx->log, ctx->request, v); \ oauth2_mem_free(v); static void _oauth2_nginx_schema_copy(oauth2_nginx_request_context_t *ctx) { oauth2_http_request_scheme_set( ctx->log, ctx->request, ctx->r->http_connection->ssl == 1 ? "https" : "http"); } static void _oauth2_nginx_host_copy(oauth2_nginx_request_context_t *ctx) { _OAUTH2_NGINX_START_END_COPY(ctx, host_start, host_end, hostname); } static void _oauth2_nginx_port_copy(oauth2_nginx_request_context_t *ctx) { in_port_t port = 0; struct sockaddr_in *sin; #if (NGX_HAVE_INET6) struct sockaddr_in6 *sin6; #endif switch (ctx->r->connection->local_sockaddr->sa_family) { #if (NGX_HAVE_INET6) case AF_INET6: sin6 = (struct sockaddr_in6 *)ctx->r->connection->local_sockaddr; port = ntohs(sin6->sin6_port); break; #endif #if (NGX_HAVE_UNIX_DOMAIN) case AF_UNIX: port = 0; break; #endif default: /* AF_INET */ sin = (struct sockaddr_in *)ctx->r->connection->local_sockaddr; port = ntohs(sin->sin_port); } oauth2_http_request_port_set(ctx->log, ctx->request, (unsigned long)port); } static void _oauth2_nginx_path_copy(oauth2_nginx_request_context_t *ctx) { _OAUTH2_NGINX_STRING_COPY(ctx, uri, path); } static void _oauth2_nginx_method_copy(oauth2_nginx_request_context_t *ctx) { oauth2_http_method_t m = OAUTH2_HTTP_METHOD_UNKNOWN; char *v = (ctx->r->method_name.len > 0) ? oauth2_strndup((const char *)ctx->r->method_name.data, ctx->r->method_name.len) : NULL; if (v == NULL) goto end; if (strcmp(v, "GET") == 0) m = OAUTH2_HTTP_METHOD_GET; else if (strcmp(v, "POST") == 0) m = OAUTH2_HTTP_METHOD_POST; else if (strcmp(v, "PUT") == 0) m = OAUTH2_HTTP_METHOD_PUT; else if (strcmp(v, "DELETE") == 0) m = OAUTH2_HTTP_METHOD_DELETE; else if (strcmp(v, "CONNECT") == 0) m = OAUTH2_HTTP_METHOD_CONNECT; else if (strcmp(v, "OPTIONS") == 0) m = OAUTH2_HTTP_METHOD_OPTIONS; oauth2_http_request_method_set(ctx->log, ctx->request, m); end: if (v) oauth2_mem_free(v); } static void _oauth2_nginx_query_copy(oauth2_nginx_request_context_t *ctx) { _OAUTH2_NGINX_STRING_COPY(ctx, args, query); } static void _oauth2_nginx_headers_copy(oauth2_nginx_request_context_t *ctx) { char *name = NULL, *value = NULL; ngx_list_part_t *part; ngx_table_elt_t *h; ngx_uint_t i; part = &ctx->r->headers_in.headers.part; h = part->elts; for (i = 0; /* void */; i++) { if (i >= part->nelts) { if (part->next == NULL) { break; } part = part->next; h = part->elts; i = 0; } name = oauth2_strndup((const char *)h[i].key.data, h[i].key.len); value = oauth2_strndup((const char *)h[i].value.data, h[i].value.len); // TODO: avoid duplicate copy oauth2_http_request_header_add(ctx->log, ctx->request, name, value); oauth2_mem_free(name); oauth2_mem_free(value); } } void _oauth2_nginx_request_copy(oauth2_nginx_request_context_t *ctx) { if ((ctx == NULL) || (ctx->r == NULL)) goto end; _oauth2_nginx_schema_copy(ctx); _oauth2_nginx_host_copy(ctx); _oauth2_nginx_port_copy(ctx); _oauth2_nginx_path_copy(ctx); _oauth2_nginx_query_copy(ctx); _oauth2_nginx_method_copy(ctx); _oauth2_nginx_headers_copy(ctx); end: return; } static void _oauth2_nginx_ssl_cert_set(oauth2_nginx_request_context_t *ctx) { ngx_str_t name; ngx_uint_t key; ngx_http_variable_value_t *vv = NULL; char *s_key = "ssl_client_cert"; name.len = strlen(s_key); name.data = ngx_palloc(ctx->r->pool, name.len); memcpy(name.data, s_key, name.len); key = ngx_hash_strlow(name.data, name.data, name.len); vv = ngx_http_get_variable(ctx->r, &name, key); if ((vv == NULL) || (vv->not_found)) { ngx_pfree(ctx->r->pool, name.data); return; } char *s = oauth2_strndup((char *)vv->data, vv->len); oauth2_http_request_context_set(ctx->log, ctx->request, OAUTH2_TLS_CERT_VAR_NAME, s); ngx_pfree(ctx->r->pool, name.data); oauth2_mem_free(s); } oauth2_nginx_request_context_t * oauth2_nginx_request_context_init(ngx_http_request_t *r) { // ngx_http_core_srv_conf_t *cscf; oauth2_nginx_request_context_t *ctx = NULL; oauth2_log_sink_t *log_sink_nginx = NULL; // if (r == NULL) // goto end; // cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module); // TODO: memory allocation failure checks...? ctx = oauth2_mem_alloc(sizeof(oauth2_nginx_request_context_t)); // TODO: get the log level from NGINX... oauth2_log_level_t level = OAUTH2_LOG_TRACE1; log_sink_nginx = oauth2_log_sink_create(level, oauth2_nginx_log, r->connection->log); ctx->log = oauth2_log_init(level, log_sink_nginx); ctx->request = oauth2_http_request_init(ctx->log); ctx->r = r; _oauth2_nginx_request_copy(ctx); _oauth2_nginx_ssl_cert_set(ctx); oauth2_debug(ctx->log, "created NGINX request context: %p", ctx); // end: return ctx; } void oauth2_nginx_request_context_free(void *rec) { oauth2_nginx_request_context_t *ctx = (oauth2_nginx_request_context_t *)rec; if (ctx) { oauth2_debug(ctx->log, "dispose NGINX request context: %p", ctx); if (ctx->request) oauth2_http_request_free(ctx->log, ctx->request); oauth2_log_free(ctx->log); oauth2_mem_free(ctx); } } static bool oauth2_nginx_response_header_set(oauth2_log_t *log, void *rec, const char *name, const char *value) { bool rc = false; ngx_table_elt_t *h = NULL; ngx_http_request_t *r = (ngx_http_request_t *)rec; h = ngx_list_push(&r->headers_out.headers); if (h == NULL) goto end; h->hash = 1; h->key.len = strlen(name); h->key.data = ngx_palloc(r->pool, h->key.len); memcpy(h->key.data, name, h->key.len); h->value.len = strlen(value); h->value.data = ngx_palloc(r->pool, h->value.len); memcpy(h->value.data, value, h->value.len); rc = true; end: return rc; } ngx_int_t oauth2_nginx_http_response_set(oauth2_log_t *log, oauth2_http_response_t *response, ngx_http_request_t *r) { ngx_int_t nrc = NGX_ERROR; if ((response == NULL) || (r == NULL)) goto end; oauth2_http_response_headers_loop(log, response, oauth2_nginx_response_header_set, r); r->headers_out.status = oauth2_http_response_status_code_get(log, response); if (r->headers_out.status == 200) nrc = NGX_OK; else if (r->headers_out.status == 302) nrc = NGX_HTTP_MOVED_TEMPORARILY; else if (r->headers_out.status == 401) nrc = NGX_HTTP_UNAUTHORIZED; else nrc = r->headers_out.status; // nrc = ngx_http_send_header(r); end: return nrc; } typedef struct oauth2_nginx_claim_hash_t { ngx_hash_keys_arrays_t keys; ngx_hash_t h; } oauth2_nginx_claim_hash_t; static inline ngx_str_t oauth2_nginx_chr2str(ngx_pool_t *p, const char *k) { ngx_str_t in = {strlen(k), (u_char *)k}; ngx_str_t out = {in.len, ngx_pstrdup(p, &in)}; return out; } char *oauth2_nginx_str2chr(ngx_pool_t *p, const ngx_str_t *str) { char *s = ngx_pnalloc(p, str->len + 1); if (s != NULL) { memcpy(s, str->data, str->len); s[str->len] = '\0'; } return s; } static inline char *oauth2_nginx_chr2chr(ngx_pool_t *p, const char *str) { ngx_str_t s = {strlen(str), (u_char *)str}; return oauth2_nginx_str2chr(p, &s); } ngx_int_t oauth2_nginx_claim_variable(ngx_module_t module, ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data) { oauth2_nginx_claim_hash_t *claims = NULL; const char *value = NULL; ngx_str_t key = {strlen((const char *)data), (u_char *)data}; claims = (oauth2_nginx_claim_hash_t *)ngx_http_get_module_ctx(r, module); if (claims == NULL) { v->not_found = 1; return NGX_OK; } value = (const char *)ngx_hash_find( &claims->h, ngx_hash_key(key.data, key.len), key.data, key.len); if (value != NULL) { ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "oauth2_nginx_claim_variable: %V=%s", &key, value); v->data = (u_char *)value; v->len = strlen(value); v->no_cacheable = 1; v->not_found = 0; } else { ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "oauth2_nginx_claim_variable: %V=(null)", &key); v->not_found = 1; } return NGX_OK; } static const size_t OAUTH2_NGINX_MAX_BUF = 128; char *oauth2_nginx_set_claim(ngx_module_t module, ngx_http_get_variable_pt handler, ngx_conf_t *cf, ngx_command_t *cmd, void *conf) { ngx_http_variable_t *v; char buf[OAUTH2_NGINX_MAX_BUF]; int n = 0; char *s = NULL; ngx_str_t *value = cf->args->elts; if (value[2].len <= 1 || value[2].data[0] != '$') { n = snprintf(buf, sizeof(buf), "Invalid variable name %.*s", (int)value[2].len, value[2].data); ngx_str_t msg = {n, (u_char *)&buf[0]}; s = oauth2_nginx_str2chr(cf->pool, &msg); return s ? s : NGX_CONF_ERROR; } value[2].len--; value[2].data++; v = ngx_http_add_variable(cf, &value[2], NGX_HTTP_VAR_CHANGEABLE); if (!v) { ngx_str_t msg = ngx_string("ngx_http_add_variable failed"); s = oauth2_nginx_str2chr(cf->pool, &msg); return s ? s : NGX_CONF_ERROR; } v->get_handler = handler; char *claim = oauth2_nginx_str2chr(cf->pool, &value[1]); if (!claim) { ngx_str_t msg = ngx_string("Out of memory"); s = oauth2_nginx_str2chr(cf->pool, &msg); return s ? s : NGX_CONF_ERROR; } v->data = (uintptr_t)claim; return NGX_CONF_OK; } static ngx_int_t ngx_set_target_variable(oauth2_nginx_claim_hash_t *claims, oauth2_nginx_request_context_t *ctx, const char *k, const char *v) { ngx_str_t key = oauth2_nginx_chr2str(claims->keys.pool, k); if (key.data == NULL) return NGX_ERROR; const char *value = oauth2_nginx_chr2chr(claims->keys.pool, v); if (value == NULL) return NGX_ERROR; return ngx_hash_add_key(&claims->keys, &key, (char *)value, NGX_HASH_READONLY_KEY); } static ngx_int_t ngx_oauth2_init_keys(ngx_pool_t *pool, oauth2_nginx_claim_hash_t *claims) { claims->keys.pool = pool; claims->keys.temp_pool = pool; return ngx_hash_keys_array_init(&claims->keys, NGX_HASH_SMALL); } static ngx_int_t ngx_oauth2_init_hash(ngx_pool_t *pool, oauth2_nginx_claim_hash_t *claims) { ngx_hash_init_t init; init.hash = &claims->h; init.key = ngx_hash_key; init.max_size = 64; init.bucket_size = ngx_align(64, ngx_cacheline_size); init.name = "claims"; init.pool = pool; init.temp_pool = pool; return ngx_hash_init(&init, claims->keys.keys.elts, claims->keys.keys.nelts); } ngx_int_t oauth2_nginx_set_target_variables(ngx_module_t module, oauth2_nginx_request_context_t *ctx, json_t *json_token) { void *iter = NULL; const char *key = NULL, *val = NULL; json_t *value = NULL; oauth2_nginx_claim_hash_t *claims = NULL; int rc = NGX_OK; claims = (oauth2_nginx_claim_hash_t *)ngx_http_get_module_ctx(ctx->r, module); if (claims == NULL) { claims = ngx_palloc(ctx->r->pool, sizeof(*claims)); if (claims == NULL) { oauth2_error(ctx->log, "error allocating claims hash"); return NGX_ERROR; } rc = ngx_oauth2_init_keys(ctx->r->pool, claims); if (rc != NGX_OK) { oauth2_error(ctx->log, "error %d initializing hash keys", rc); return rc; } ngx_http_set_ctx(ctx->r, claims, module); } iter = json_object_iter(json_token); while (iter) { key = json_object_iter_key(iter); value = json_object_iter_value(iter); if (json_is_string(value)) { rc = ngx_set_target_variable(claims, ctx, key, json_string_value(value)); } else { val = oauth2_json_encode(ctx->log, value, JSON_ENCODE_ANY); rc = ngx_set_target_variable(claims, ctx, key, val); oauth2_mem_free((char *)val); } if (rc != NGX_OK) { oauth2_error( ctx->log, "error %d setting value of key %s in claims hash", rc, key); return rc; } iter = json_object_iter_next(json_token, iter); } rc = ngx_oauth2_init_hash(ctx->r->pool, claims); if (rc != NGX_OK) { oauth2_error(ctx->log, "error %d initializing claims hash", rc); return rc; } return NGX_OK; } char *nginx_oauth2_set_require(ngx_conf_t *cf, ngx_array_t **requirements) { ngx_http_complex_value_t *val = NULL; ngx_http_compile_complex_value_t ccv; ngx_str_t *var = NULL; int rc = NGX_OK; char *s = NULL; char buf[OAUTH2_NGINX_MAX_BUF]; if (cf->args == NULL) return NGX_CONF_ERROR; if (*requirements == NULL) { *requirements = ngx_array_create(cf->pool, cf->args->nelts, sizeof(ngx_http_complex_value_t)); if (*requirements == NULL) { ngx_str_t msg = ngx_string("Out of memory"); s = oauth2_nginx_str2chr(cf->pool, &msg); return s ? s : NGX_CONF_ERROR; } } for (unsigned int i = 1; i < cf->args->nelts; ++i) { var = (ngx_str_t *)cf->args->elts + i; /* no allocation here because we've already dimensioned the * array upon its creation */ val = (ngx_http_complex_value_t *)ngx_array_push(*requirements); ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t)); ccv.cf = cf; ccv.value = var; ccv.complex_value = val; rc = ngx_http_compile_complex_value(&ccv); if (rc != NGX_OK) { int n = snprintf(buf, sizeof(buf), "Error %d compiling " "expression %.*s", rc, (int)var->len, var->data); ngx_str_t msg = {n, (u_char *)&buf[0]}; s = oauth2_nginx_str2chr(cf->pool, &msg); return s ? s : NGX_CONF_ERROR; } } return NGX_CONF_OK; } static ngx_int_t nginx_oauth2_check_requirement(oauth2_nginx_request_context_t *ctx, ngx_http_complex_value_t *cv) { ngx_str_t v; ngx_int_t rc = ngx_http_complex_value(ctx->r, cv, &v); if (rc != NGX_OK) { ngx_log_error(NGX_LOG_ERR, ctx->r->connection->log, 0, "error %d evaluating expression %*.s", rc, (int)cv->value.len, cv->value.data); return NGX_ERROR; } ngx_log_debug3(NGX_LOG_DEBUG_HTTP, ctx->r->connection->log, 0, "nginx_oauth2_check_requirement: expression \"%*.s\" " "evaluated to: %s", (int)cv->value.len, cv->value.data, (1 == v.len && '1' == *v.data) ? "NGX_OK" : "NGX_HTTP_UNAUTHORIZED"); return 1 == v.len && '1' == *v.data ? NGX_OK : NGX_HTTP_UNAUTHORIZED; } ngx_int_t nginx_oauth2_check_requirements(oauth2_nginx_request_context_t *ctx, ngx_array_t *requirements) { int rc = NGX_OK; ngx_uint_t i = 0; if (requirements == NULL) return NGX_OK; for (i = 0; i < requirements->nelts; ++i) { ngx_http_complex_value_t *cv = (ngx_http_complex_value_t *)requirements->elts + i; rc = nginx_oauth2_check_requirement(ctx, cv); if (rc != NGX_OK) break; } return rc; } liboauth2-2.1.0/src/session.c000066400000000000000000000313521475305260400160330ustar00rootroot00000000000000/*************************************************************************** * * Copyright (C) 2018-2025 - ZmartZone Holding BV * * 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. * * @Author: Hans Zandbelt - hans.zandbelt@openidc.com * **************************************************************************/ #include "oauth2/session.h" #include "oauth2/jose.h" #include "oauth2/mem.h" #include "oauth2/util.h" #include "cfg_int.h" #include "util_int.h" typedef struct oauth2_session_rec_t { char *id; oauth2_time_t start; oauth2_time_t expiry; char *user; char *id_token; json_t *id_token_claims; json_t *userinfo_claims; } oauth2_session_rec_t; oauth2_session_rec_t *oauth2_session_rec_init(oauth2_log_t *log) { oauth2_session_rec_t *s = (oauth2_session_rec_t *)oauth2_mem_alloc( sizeof(oauth2_session_rec_t)); s->id = NULL; s->user = NULL; s->id_token = NULL; s->id_token_claims = NULL; s->userinfo_claims = NULL; s->expiry = 0; s->start = oauth2_time_now_sec(); return s; } void oauth2_session_rec_free(oauth2_log_t *log, oauth2_session_rec_t *s) { if (s->user) oauth2_mem_free(s->user); if (s->id_token) oauth2_mem_free(s->id_token); if (s->id_token_claims) json_decref(s->id_token_claims); if (s->userinfo_claims) json_decref(s->userinfo_claims); if (s->id) oauth2_mem_free(s->id); if (s) oauth2_mem_free(s); } _OAUTH2_TYPE_IMPLEMENT_MEMBER_SET_GET(session, rec, id, char *, str) _OAUTH2_TYPE_IMPLEMENT_MEMBER_SET_GET(session, rec, user, char *, str) _OAUTH2_TYPE_IMPLEMENT_MEMBER_SET_GET(session, rec, id_token, char *, str) _OAUTH2_TYPE_IMPLEMENT_MEMBER_SET_GET(session, rec, start, oauth2_time_t, time) _OAUTH2_TYPE_IMPLEMENT_MEMBER_SET_GET(session, rec, expiry, oauth2_time_t, time) _OAUTH2_TYPE_IMPLEMENT_MEMBER_GET(session, rec, id_token_claims, json_t *) _OAUTH2_TYPE_IMPLEMENT_MEMBER_GET(session, rec, userinfo_claims, json_t *) static bool _oauth2_session_rec_json_object_set(oauth2_log_t *log, oauth2_session_rec_t *session, const char *name, json_t *json, json_t **session_ptr) { bool rc = false; char *s_json = NULL; if (json == NULL) { rc = true; goto end; } s_json = oauth2_json_encode(log, json, 0); oauth2_debug(log, "%s=%s", name, s_json); *session_ptr = json_incref(json); rc = true; end: if (s_json) oauth2_mem_free(s_json); return rc; } bool oauth2_session_rec_id_token_claims_set(oauth2_log_t *log, oauth2_session_rec_t *session, json_t *id_token) { return _oauth2_session_rec_json_object_set( log, session, "id_token", id_token, &session->id_token_claims); } bool oauth2_session_rec_userinfo_claims_set(oauth2_log_t *log, oauth2_session_rec_t *session, json_t *userinfo_claims) { return _oauth2_session_rec_json_object_set(log, session, "userinfo", userinfo_claims, &session->userinfo_claims); } #define OAUTH_SESSION_KEY_ID "id" #define OAUTH_SESSION_KEY_USER "u" #define OAUTH_SESSION_KEY_ID_TOKEN "i" #define OAUTH_SESSION_KEY_ID_TOKEN_CLAIMS "ic" #define OAUTH_SESSION_KEY_USERINFO_CLAIMS "uc" #define OAUTH_SESSION_KEY_START "s" #define OAUTH_SESSION_KEY_EXPIRY "e" bool oauth2_session_load_cookie(oauth2_log_t *log, const oauth2_cfg_session_t *cfg, oauth2_http_request_t *request, json_t **json) { bool rc = false; const char *name = NULL; char *value = NULL; name = oauth2_cfg_session_cookie_name_get(log, cfg); value = oauth2_http_request_cookie_get(log, request, name, true); if (value == NULL) { oauth2_debug(log, "no session cookie found"); rc = true; goto end; } rc = oauth2_jose_jwt_decrypt(log, oauth2_crypto_passphrase_get(log), value, json); end: if (value) oauth2_mem_free(value); return rc; } bool oauth2_session_save_cookie(oauth2_log_t *log, const oauth2_cfg_session_t *cfg, const oauth2_http_request_t *request, oauth2_http_response_t *response, json_t *json) { bool rc = false; const char *name = NULL, *path = NULL; ; char *value = NULL; if (oauth2_jose_jwt_encrypt(log, oauth2_crypto_passphrase_get(log), json, &value) == false) goto end; name = oauth2_cfg_session_cookie_name_get(log, cfg); path = oauth2_cfg_session_cookie_path_get(log, cfg); rc = oauth2_http_response_cookie_set( log, response, name, value, path, oauth2_http_request_is_secure(log, request), OAUTH2_CFG_TIME_UNSET); end: if (value) oauth2_mem_free(value); return rc; } bool oauth2_session_load_cache(oauth2_log_t *log, const oauth2_cfg_session_t *cfg, oauth2_http_request_t *request, json_t **json) { bool rc = false; const char *name = NULL; char *key = NULL, *value = NULL; name = oauth2_cfg_session_cookie_name_get(log, cfg); key = oauth2_http_request_cookie_get(log, request, name, true); if (key == NULL) { oauth2_debug(log, "no session cookie found"); rc = true; goto end; } if (oauth2_cache_get(log, cfg->cache, key, &value) == false) goto end; if (value == NULL) { oauth2_debug(log, "no session found in cache"); rc = true; goto end; } if (oauth2_json_decode_object(log, value, json) == false) goto end; oauth2_debug(log, "restored session from cache: %s", value); rc = true; end: if (key) oauth2_mem_free(key); if (value) oauth2_mem_free(value); return rc; } bool oauth2_session_save_cache(oauth2_log_t *log, const oauth2_cfg_session_t *cfg, const oauth2_http_request_t *request, oauth2_http_response_t *response, json_t *json) { bool rc = false; const char *name = NULL, *path = NULL; char *key = NULL, *value = NULL; value = oauth2_json_encode(log, json, 0); if (value == NULL) goto end; if (oauth2_json_string_get(log, json, OAUTH_SESSION_KEY_ID, &key, NULL) == false) { oauth2_error(log, "no session identifier found in session"); goto end; } if (oauth2_cache_set(log, cfg->cache, key, value, oauth2_cfg_session_inactivity_timeout_s_get( log, cfg)) == false) { oauth2_error(log, "could not store session in cache"); goto end; } name = oauth2_cfg_session_cookie_name_get(log, cfg); path = oauth2_cfg_session_cookie_path_get(log, cfg); rc = oauth2_http_response_cookie_set( log, response, name, key, path, oauth2_http_request_is_secure(log, request), OAUTH2_CFG_TIME_UNSET); end: if (key) oauth2_mem_free(key); if (value) oauth2_mem_free(value); return rc; } #define OAUTH2_SESSION_ID_LENGTH 16 static char *oauth2_session_id_generate(oauth2_log_t *log) { return oauth2_rand_str(log, OAUTH2_SESSION_ID_LENGTH); } bool oauth2_session_load(oauth2_log_t *log, const oauth2_cfg_session_t *cfg, oauth2_http_request_t *request, oauth2_session_rec_t **session) { bool rc = false; json_t *json = NULL, *json_ptr = NULL; oauth2_session_load_callback_t *session_load_callback = NULL; json_int_t expiry = 0, start = 0; oauth2_time_t now = 0; oauth2_debug(log, "enter"); if (session == NULL) goto end; *session = oauth2_session_rec_init(log); if (*session == NULL) goto end; session_load_callback = oauth2_cfg_session_load_callback_get(log, cfg); if (session_load_callback == NULL) goto end; rc = session_load_callback(log, cfg, request, &json); if ((rc == false) || (json == NULL)) { if ((rc) && ((*session)->id == NULL)) (*session)->id = oauth2_session_id_generate(log); goto end; } now = oauth2_time_now_sec(); if (oauth2_json_number_get(log, json, OAUTH_SESSION_KEY_START, &start, 0) == false) goto end; if (now >= start + oauth2_cfg_session_max_duration_s_get(log, cfg)) { oauth2_warn(log, "session has exceeded maximum duration; " "start=" OAUTH2_TIME_T_FORMAT " expiry=" OAUTH2_TIME_T_FORMAT " now=" OAUTH2_TIME_T_FORMAT "", start, oauth2_cfg_session_max_duration_s_get(log, cfg), now); rc = false; goto end; } (*session)->start = start; if (oauth2_json_number_get(log, json, OAUTH_SESSION_KEY_EXPIRY, &expiry, 0) == false) goto end; if (now >= expiry) { oauth2_warn(log, "session has expired"); // TODO: refactor and/or remove from cache? oauth2_session_rec_free(log, *session); *session = oauth2_session_rec_init(log); (*session)->id = oauth2_session_id_generate(log); rc = true; goto end; } (*session)->expiry = expiry; if (oauth2_json_string_get(log, json, OAUTH_SESSION_KEY_ID, &(*session)->id, NULL) == false) goto end; if (oauth2_json_string_get(log, json, OAUTH_SESSION_KEY_USER, &(*session)->user, NULL) == false) goto end; if (oauth2_json_string_get(log, json, OAUTH_SESSION_KEY_ID_TOKEN, &(*session)->id_token, NULL) == false) goto end; if (oauth2_json_object_get(log, json, OAUTH_SESSION_KEY_ID_TOKEN_CLAIMS, &json_ptr) == false) goto end; oauth2_session_rec_id_token_claims_set(log, *session, json_ptr); if (json_ptr) json_decref(json_ptr); if (oauth2_json_object_get(log, json, OAUTH_SESSION_KEY_USERINFO_CLAIMS, &json_ptr) == false) goto end; oauth2_session_rec_userinfo_claims_set(log, *session, json_ptr); if (json_ptr) json_decref(json_ptr); end: if (json) json_decref(json); oauth2_debug(log, "return: %d", rc); return rc; } bool oauth2_session_handle(oauth2_log_t *log, const oauth2_cfg_session_t *cfg, const oauth2_http_request_t *request, oauth2_http_response_t *response, oauth2_session_rec_t *session) { bool rc = false; bool needs_save = false; /* * 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 */ oauth2_time_t interval = oauth2_cfg_session_inactivity_timeout_s_get(log, cfg); oauth2_time_t now = oauth2_time_now_sec(); oauth2_time_t slack = interval / 10; if (slack > 60) slack = 60; if (session->expiry - now < interval - slack) { // session->expiry = now + interval; needs_save = true; } oauth2_debug(log, "session inactivity timeout: " OAUTH2_TIME_T_FORMAT ", interval: " OAUTH2_TIME_T_FORMAT "", session->expiry - now, interval); if (needs_save) rc = oauth2_session_save(log, cfg, request, response, session); else rc = true; return rc; } bool oauth2_session_save(oauth2_log_t *log, const oauth2_cfg_session_t *cfg, const oauth2_http_request_t *request, oauth2_http_response_t *response, oauth2_session_rec_t *session) { bool rc = false; json_t *json = NULL; oauth2_session_save_callback_t *session_save_callback = NULL; if (session == NULL) goto end; json = json_object(); if (json == NULL) goto end; if (session->start > 0) json_object_set_new(json, OAUTH_SESSION_KEY_START, json_integer(session->start)); if (session->expiry == 0) { oauth2_debug( log, "setting expiry according to " "cfg->inactivity_timeout_s=" OAUTH2_TIME_T_FORMAT "", oauth2_cfg_session_inactivity_timeout_s_get(log, cfg)); session->expiry = oauth2_time_now_sec() + oauth2_cfg_session_inactivity_timeout_s_get(log, cfg); } if (session->expiry > 0) json_object_set_new(json, OAUTH_SESSION_KEY_EXPIRY, json_integer(session->expiry)); if (session->id) json_object_set_new(json, OAUTH_SESSION_KEY_ID, json_string(session->id)); if (session->user) json_object_set_new(json, OAUTH_SESSION_KEY_USER, json_string(session->user)); if (session->id_token) json_object_set_new(json, OAUTH_SESSION_KEY_ID_TOKEN, json_string(session->id_token)); if (session->id_token_claims) json_object_set(json, OAUTH_SESSION_KEY_ID_TOKEN_CLAIMS, session->id_token_claims); if (session->userinfo_claims) json_object_set(json, OAUTH_SESSION_KEY_USERINFO_CLAIMS, session->userinfo_claims); session_save_callback = oauth2_cfg_session_save_callback_get(log, cfg); if (session_save_callback == NULL) goto end; rc = session_save_callback(log, cfg, request, response, json); end: if (json) json_decref(json); return rc; } liboauth2-2.1.0/src/util.c000066400000000000000000000735751475305260400153420ustar00rootroot00000000000000/*************************************************************************** * * Copyright (C) 2018-2025 - ZmartZone Holding BV * * 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. * * @Author: Hans Zandbelt - hans.zandbelt@openidc.com * **************************************************************************/ // need this at the top for vasprintf #include "util_int.h" #include #include #include "oauth2/ipc.h" #include "oauth2/log.h" #include "oauth2/mem.h" #include "oauth2/util.h" #include #include #include #include #include #include #include "cache_int.h" #include "cfg_int.h" static CURL *_s_curl = NULL; static oauth2_ipc_mutex_t *_curl_mutex = NULL; oauth2_log_t *oauth2_init(oauth2_log_level_t level, oauth2_log_sink_t *sink) { oauth2_log_t *log = NULL; #if OPENSSL_VERSION_NUMBER >= 0x10100000L OPENSSL_init_crypto(0, NULL); #else ERR_load_crypto_strings(); OpenSSL_add_all_algorithms(); OpenSSL_add_all_digests(); #endif // TODO: align flags/call with memory initialization in mem.c // possibly providing alloc funcs as part of init? curl_global_init(CURL_GLOBAL_ALL); log = oauth2_log_init(level, sink); _curl_mutex = oauth2_ipc_mutex_init(log); oauth2_ipc_mutex_post_config(log, _curl_mutex); return log; } void oauth2_shutdown(oauth2_log_t *log) { _oauth2_session_global_cleanup(log); _oauth2_cache_global_cleanup(log); if (_s_curl) { curl_easy_cleanup(_s_curl); _s_curl = NULL; } if (_curl_mutex != NULL) { oauth2_ipc_mutex_free(log, _curl_mutex); _curl_mutex = NULL; } curl_global_cleanup(); EVP_cleanup(); ERR_free_strings(); CRYPTO_cleanup_all_ex_data(); // #if OPENSSL_API_COMPAT < 0x10100000L // #if OPENSSL_VERSION_NUMBER < 0x10000000L // SSL_COMP_free_compression_methods(); // #endif #if (OPENSSL_VERSION_NUMBER < 0x10100000) || defined(LIBRESSL_VERSION_NUMBER) // #if OPENSSL_API_COMPAT < 0x10100000L ERR_remove_thread_state(NULL); #endif oauth2_log_free(log); } typedef bool(oauth2_cjose_base64_encode_callback_t)(const uint8_t *input, const size_t inlen, char **output, size_t *outlen, cjose_err *err); static size_t _oauth2_cjose_base64_encode( oauth2_log_t *log, oauth2_cjose_base64_encode_callback_t encode, const uint8_t *src, const size_t src_len, char **dst) { bool rc = false; size_t dst_len; cjose_err err; memset(&err, 0, sizeof(err)); oauth2_debug(log, "enter: len=%d", (int)src_len); if (dst) *dst = NULL; dst_len = 0; if (src == NULL) { oauth2_warn(log, "not encoding null input to empty string"); goto end; } rc = encode(src, src_len, dst, &dst_len, &err); if (!rc) { _OAUTH2_UTIL_JOSE_ERR_LOG(log, "encode", err); goto end; } end: oauth2_debug(log, "leave: len=%d", (int)dst_len); return dst_len; } size_t oauth2_base64url_encode(oauth2_log_t *log, const uint8_t *src, const size_t src_len, char **dst) { return _oauth2_cjose_base64_encode(log, cjose_base64url_encode, src, src_len, dst); } size_t oauth2_base64_encode(oauth2_log_t *log, const uint8_t *src, const size_t src_len, char **dst) { return _oauth2_cjose_base64_encode(log, cjose_base64_encode, src, src_len, dst); } typedef bool(oauth2_cjose_base64_decode_callback_t)(const char *input, const size_t inlen, uint8_t **output, size_t *outlen, cjose_err *err); static bool _oauth2_cjose_base64_decode(oauth2_log_t *log, oauth2_cjose_base64_decode_callback_t decode, const char *src, uint8_t **dst, size_t *dst_len) { bool rc = false; cjose_err err; size_t src_len = 0; memset(&err, 0, sizeof(err)); src_len = src ? strlen(src) : 0; oauth2_debug(log, "enter: len=%d", (int)src_len); if (dst == NULL) goto end; *dst = NULL; if (dst_len == NULL) goto end; *dst_len = 0; if (src == NULL) { oauth2_warn(log, "not decoding null input"); goto end; } if (decode(src, src_len, dst, dst_len, &err) == false) { _OAUTH2_UTIL_JOSE_ERR_LOG(log, "decode", err); goto end; } rc = true; end: oauth2_debug(log, "leave: len=%d", (dst_len) ? (int)*dst_len : -1); return rc; } bool oauth2_base64url_decode(oauth2_log_t *log, const char *src, uint8_t **dst, size_t *dst_len) { return _oauth2_cjose_base64_decode(log, cjose_base64url_decode, src, dst, dst_len); } bool oauth2_base64_decode(oauth2_log_t *log, const char *src, uint8_t **dst, size_t *dst_len) { return _oauth2_cjose_base64_decode(log, cjose_base64_decode, src, dst, dst_len); } static int oauth2_char_to_env(int c) { return isalnum(c) ? toupper(c) : '_'; } int oauth2_strnenvcmp(const char *a, const char *b, int len) { int rv = 0; int d, i = 0; while (1) { // if len < 0 then we don't stop based on length if (len >= 0 && i >= len) goto end; // if we're at the end of both strings, they're equal if (!*a && !*b) goto end; // if the second string is shorter, pick it: if (*a && !*b) { rv = 1; goto end; } // if the first string is shorter, pick it: if (!*a && *b) { rv = -1; goto end; } // normalize the characters as for conversion to an environment // variable. d = oauth2_char_to_env(*a) - oauth2_char_to_env(*b); if (d) { rv = d; goto end; } a++; b++; i++; } end: return rv; } static CURL *oauth2_curl_init(oauth2_log_t *log) { oauth2_ipc_mutex_lock(log, _curl_mutex); if (_s_curl == NULL) { _s_curl = curl_easy_init(); if (_s_curl == NULL) { oauth2_error(log, "curl_easy_init() error"); } } return _s_curl; } static void oauth2_curl_free(oauth2_log_t *log, CURL *curl) { oauth2_ipc_mutex_unlock(log, _curl_mutex); } char *oauth2_url_encode(oauth2_log_t *log, const char *src) { char *dst = NULL, *rc = NULL; CURL *curl = NULL; oauth2_debug(log, "enter: %s", src); if (src == NULL) { oauth2_warn(log, "not encoding empty string"); goto end; } curl = oauth2_curl_init(log); if (curl == NULL) goto end; rc = curl_easy_escape(curl, src, strlen(src)); if (rc == NULL) { oauth2_error(log, "curl_easy_escape() error"); goto end; } dst = oauth2_strdup(rc); end: if (rc) curl_free(rc); if (curl) oauth2_curl_free(log, curl); oauth2_debug(log, "leave: %s", dst); return dst; } char *oauth2_url_decode(oauth2_log_t *log, const char *src) { char *dst = NULL, *rc = NULL; char *replaced = NULL; CURL *curl = NULL; int i = 0; oauth2_debug(log, "enter: %s", src); if (src == NULL) { oauth2_warn(log, "not decoding empty string"); goto end; } curl = oauth2_curl_init(log); if (curl == NULL) goto end; replaced = oauth2_strdup(src); if (replaced == NULL) goto end; // https://github.com/unshiftio/querystringify/issues/7#issuecomment-287627341 for (i = 0; replaced[i] != '\0'; i++) if (replaced[i] == '+') // NOTE: technically it would be more correct to make // this a %20... replaced[i] = ' '; rc = curl_easy_unescape(curl, replaced, strlen(replaced), NULL); if (rc == NULL) { oauth2_error(log, "curl_easy_unescape() error"); goto end; } dst = oauth2_strdup(rc); end: if (rc) curl_free(rc); if (replaced) oauth2_mem_free(replaced); if (curl) oauth2_curl_free(log, curl); oauth2_debug(log, "leave: %s", dst); return dst; } // TODO: this has performance/memory issues for large chunks of HTML char *oauth2_html_escape(oauth2_log_t *log, const char *src) { char *dst = NULL, *rc = NULL; const char escape_chars[6] = {'&', '\'', '\"', '>', '<', '\0'}; const char *const replace_chars[] = {"&", "'", """, ">", "<"}; unsigned int i, j = 0, k, n = 0, escape_chars_len = strlen(escape_chars); size_t m = 0, src_len = src ? strlen(src) : 0; oauth2_debug(log, "enter: %s", src); if (src == NULL) goto end; rc = oauth2_mem_alloc(src_len * 6 + 1); for (i = 0; i < src_len; i++) { for (n = 0; n < escape_chars_len; n++) { if (src[i] == escape_chars[n]) { m = strlen(replace_chars[n]); for (k = 0; k < m; k++) rc[j + k] = replace_chars[n][k]; j += m; break; } } if (n == escape_chars_len) { rc[j] = src[i]; j++; } } rc[j] = '\0'; dst = oauth2_strdup(rc); end: if (rc) oauth2_mem_free(rc); oauth2_debug(log, "leave: %s", dst); return dst; } static char *_oauth2_trim(char *src) { char *rv = NULL; char *start = NULL, *end = NULL; char *buf = NULL; if (src == NULL) goto end; buf = oauth2_strdup(src); start = buf; while (isspace(*start)) ++start; end = &start[strlen(start)]; while (--end >= start && isspace(*end)) *end = '\0'; rv = oauth2_strdup(start); end: if (buf) oauth2_mem_free(buf); return rv; } char *oauth2_getword(const char **line, char stop) { const char *pos = *line; int len; char *res; while ((*pos != stop) && *pos) { ++pos; } len = pos - *line; res = oauth2_strndup(*line, len); if (stop) { while (*pos == stop) { ++pos; } } *line = pos; return res; } bool _oauth2_nv_list_parse(oauth2_log_t *log, const char *input, oauth2_nv_list_t *tuples, char sep_tuple, char sep_nv, bool trim, bool url_decode) { bool rc = false; const char *p = NULL; char *save_input = NULL, *save_val = NULL; char *key = NULL, *val = NULL; char *dec_key = NULL, *dec_val = NULL; char *trm_key = NULL, *trm_val = NULL; if ((input == NULL) || (tuples == NULL)) goto end; save_input = oauth2_strdup(input); p = save_input; while (p && *p && (val = oauth2_getword(&p, sep_tuple))) { save_val = val; key = oauth2_getword((const char **)&val, sep_nv); if (key == NULL) continue; trm_key = trim ? _oauth2_trim(key) : oauth2_strdup(key); trm_val = trim ? _oauth2_trim(val) : oauth2_strdup(val); if (url_decode) { dec_key = oauth2_url_decode(log, trm_key); dec_val = oauth2_url_decode(log, trm_val); oauth2_nv_list_add(log, tuples, dec_key, dec_val); oauth2_mem_free(dec_key); oauth2_mem_free(dec_val); } else { oauth2_nv_list_add(log, tuples, trm_key, trm_val); } oauth2_mem_free(trm_key); if (trm_val) oauth2_mem_free(trm_val); oauth2_mem_free(key); if (save_val) oauth2_mem_free(save_val); } rc = true; end: if (save_input) oauth2_mem_free(save_input); return rc; } bool oauth2_parse_form_encoded_params(oauth2_log_t *log, const char *data, oauth2_nv_list_t **params) { bool rc = false; if (params == NULL) goto end; if (data == NULL) { rc = true; goto end; } *params = oauth2_nv_list_init(log); if (*params == NULL) goto end; rc = _oauth2_nv_list_parse(log, data, *params, _OAUTH2_CHAR_AMP, _OAUTH2_CHAR_EQUAL, false, true); end: return rc; } /* * internal */ char *oauth2_strndup(const char *src, size_t len) { char *dst = NULL; if (src == NULL) goto end; dst = oauth2_mem_alloc(len + 1); if (dst == NULL) goto end; memcpy(dst, src, len); dst[len] = '\0'; end: return dst; } char *oauth2_strdup(const char *src) { return oauth2_strndup(src, src ? strlen(src) : 0); } char *_oauth2_stradd4(char *src, const char *add1, const char *add2, const char *add3, const char *add4) { char *ptr = NULL; size_t len = 0; if (src == NULL) src = oauth2_strdup(""); if (src == NULL) goto end; if (add1 == NULL) add1 = ""; if (add2 == NULL) add2 = ""; if (add3 == NULL) add3 = ""; if (add4 == NULL) add4 = ""; len = strlen(src) + strlen(add1) + strlen(add2) + strlen(add3) + strlen(add4) + 1; ptr = oauth2_mem_alloc(len); if (ptr == NULL) goto end; oauth2_snprintf(ptr, len, "%s%s%s%s%s", src, add1, add2, add3, add4); end: if (src) oauth2_mem_free(src); return ptr; } char *oauth2_stradd(char *src, const char *add1, const char *add2, const char *add3) { return _oauth2_stradd4(src, add1, add2, add3, NULL); } static bool _oauth2_nv2s(oauth2_log_t *log, void *rec, const char *key, const char *value) { bool rc = false; char **str = (char **)rec; if (str == NULL) goto end; *str = _oauth2_stradd4(*str, " ", key, _OAUTH2_STR_EQUAL, value); rc = true; end: return rc; } char *oauth2_nv_list2s(oauth2_log_t *log, const oauth2_nv_list_t *list) { char *str = oauth2_strdup("["); oauth2_nv_list_loop(log, list, _oauth2_nv2s, &str); str = oauth2_stradd(str, " ]", NULL, NULL); return str; } bool _oauth2_struct_slot_str_set(void *struct_ptr, size_t offset, const char *value) { bool rc = false; char *base = NULL, **ptr = NULL; if ((struct_ptr == NULL) || (value == NULL)) goto end; base = (char *)struct_ptr; ptr = (char **)(base + offset); if (*ptr) oauth2_mem_free(*ptr); *ptr = oauth2_strdup(value); rc = (*ptr != NULL); end: return rc; } #define _OAUTH2_STRUCT_MEMBER_SET(name, type) \ bool _oauth2_struct_slot_##name##_set(void *struct_ptr, size_t offset, \ const type value) \ { \ bool rc = false; \ char *base = NULL; \ type *ptr = NULL; \ \ if (struct_ptr == NULL) \ goto end; \ \ base = (char *)struct_ptr; \ ptr = (type *)(base + offset); \ \ *ptr = value; \ \ rc = true; \ \ end: \ \ return rc; \ } _OAUTH2_STRUCT_MEMBER_SET(bln, bool) _OAUTH2_STRUCT_MEMBER_SET(integer, int) _OAUTH2_STRUCT_MEMBER_SET(uint, oauth2_uint_t) _OAUTH2_STRUCT_MEMBER_SET(time, oauth2_time_t) bool _oauth2_struct_slot_ptr_set(void *struct_ptr, size_t offset, const void *value) { bool rc = false; char *base = NULL; void **ptr = NULL; if (struct_ptr == NULL) goto end; base = (char *)struct_ptr; ptr = (void **)(base + offset); // TODO: should probably make a typed definition for nv_list to free // here if already set... *ptr = (void *)value; rc = true; end: return rc; } int oauth2_snprintf(char *dst, size_t len, const char *fmt, ...) { va_list ap; int rc = 0; if ((dst == NULL) || (fmt == NULL)) goto end; va_start(ap, fmt); rc = vsnprintf(dst, len, fmt, ap); va_end(ap); end: return rc; } /* * name value pairs/list stuff */ typedef struct _oauth2_nv_t { char *name; char *value; struct _oauth2_nv_t *next; } _oauth2_nv_t; static _oauth2_nv_t *_oauth2_nv_new(oauth2_log_t *log, const char *name, const char *value) { _oauth2_nv_t *ptr = NULL; ptr = oauth2_mem_alloc(sizeof(_oauth2_nv_t)); if (ptr != NULL) { ptr->name = name ? oauth2_strdup(name) : NULL; ptr->value = value ? oauth2_strdup(value) : NULL; ptr->next = NULL; } return ptr; } static void _oauth2_nv_free(oauth2_log_t *log, _oauth2_nv_t *ptr) { if (ptr->name) oauth2_mem_free(ptr->name); if (ptr->value) oauth2_mem_free(ptr->value); oauth2_mem_free(ptr); } typedef struct oauth2_nv_list_t { _oauth2_nv_t *first; bool case_sensitive; } oauth2_nv_list_t; oauth2_nv_list_t *oauth2_nv_list_init(oauth2_log_t *log) { oauth2_nv_list_t *ptr = NULL; ptr = oauth2_mem_alloc(sizeof(oauth2_nv_list_t)); if (ptr != NULL) { ptr->case_sensitive = true; } return ptr; } void oauth2_nv_list_free(oauth2_log_t *log, oauth2_nv_list_t *list) { _oauth2_nv_t *ptr = NULL; if (list == NULL) goto end; while ((ptr = list->first)) { list->first = list->first->next; _oauth2_nv_free(log, ptr); } oauth2_mem_free(list); end: return; } _OAUTH2_TYPE_IMPLEMENT_MEMBER_SET_GET(nv, list, case_sensitive, bool, bln) static bool _oauth2_nv_list_find(oauth2_log_t *log, const oauth2_nv_list_t *list, const char *name, _oauth2_nv_t **ptr, _oauth2_nv_t **prev) { bool rc = false; if ((list == NULL) || (name == NULL)) goto end; for (*ptr = list->first; *ptr; *ptr = (*ptr)->next) { if (list->case_sensitive) { if (strcmp((*ptr)->name, name) == 0) { rc = true; break; } } else { if (strcasecmp((*ptr)->name, name) == 0) { rc = true; break; } } *prev = *ptr; } end: return rc; } bool oauth2_nv_list_set(oauth2_log_t *log, oauth2_nv_list_t *list, const char *name, const char *value) { bool rc = false; _oauth2_nv_t *ptr = NULL, *prev = NULL; if ((list == NULL) || (name == NULL)) goto end; _oauth2_nv_list_find(log, list, name, &ptr, &prev); if (ptr == NULL) { rc = oauth2_nv_list_add(log, list, name, value); goto end; } if (ptr->value) oauth2_mem_free(ptr->value); ptr->value = value ? oauth2_strdup(value) : NULL; rc = true; end: return rc; } bool oauth2_nv_list_unset(oauth2_log_t *log, oauth2_nv_list_t *list, const char *name) { bool rc = false; _oauth2_nv_t *ptr = NULL, *prev = NULL; if ((list == NULL) || (name == NULL)) goto end; _oauth2_nv_list_find(log, list, name, &ptr, &prev); if (ptr) { if (prev) prev->next = ptr->next; else list->first = ptr->next; _oauth2_nv_free(log, ptr); } rc = true; end: return rc; } bool oauth2_nv_list_add(oauth2_log_t *log, oauth2_nv_list_t *list, const char *name, const char *value) { bool rc = false; _oauth2_nv_t *ptr = NULL, *prev = NULL; if ((list == NULL) || (name == NULL)) goto end; ptr = _oauth2_nv_new(log, name, value); if (ptr == NULL) goto end; if (list->first == NULL) { list->first = ptr; } else { for (prev = list->first; prev->next; prev = prev->next) ; prev->next = ptr; } rc = true; end: return rc; } const char *oauth2_nv_list_get(oauth2_log_t *log, const oauth2_nv_list_t *list, const char *name) { const char *value = NULL; _oauth2_nv_t *ptr = NULL, *prev = NULL; if ((list == NULL) || (name == NULL)) goto end; _oauth2_nv_list_find(log, list, name, &ptr, &prev); if (ptr) value = ptr->value; end: if (name != NULL) oauth2_debug(log, "%s=%s", name, value ? value : "(null)"); return value; } void oauth2_nv_list_loop(oauth2_log_t *log, const oauth2_nv_list_t *list, oauth2_nv_list_loop_cb_t *callback, void *rec) { _oauth2_nv_t *ptr = NULL; if ((list == NULL) || (callback == NULL)) goto end; for (ptr = list->first; ptr; ptr = ptr->next) { if (callback(log, rec, ptr->name, ptr->value) == false) break; } end: return; } static bool _oauth2_nv_list_copy(oauth2_log_t *log, void *rec, const char *key, const char *value) { oauth2_nv_list_t *dst = (oauth2_nv_list_t *)rec; return oauth2_nv_list_add(log, dst, key, value); } oauth2_nv_list_t *oauth2_nv_list_clone(oauth2_log_t *log, const oauth2_nv_list_t *src) { oauth2_nv_list_t *dst = NULL; if (src == NULL) goto end; dst = oauth2_nv_list_init(log); if (dst == NULL) goto end; if (oauth2_nv_list_case_sensitive_set( log, dst, oauth2_nv_list_case_sensitive_get(log, src)) == false) goto end; oauth2_nv_list_loop(log, src, _oauth2_nv_list_copy, dst); end: return dst; } void oauth2_nv_list_merge_into(oauth2_log_t *log, const oauth2_nv_list_t *source, oauth2_nv_list_t *target) { if (source) { oauth2_nv_list_loop(log, source, _oauth2_nv_list_copy, target); } } /* * JSON */ bool oauth2_json_decode_object(oauth2_log_t *log, const char *payload, json_t **json) { bool rc = false; json_error_t err; *json = json_loads(payload, 0, &err); if (*json == NULL) { oauth2_error(log, "json_loads failed: %s", err.text); goto end; } if (*json == NULL) { oauth2_error(log, "JSON parsing returned an error: %s (%s)", err.text, payload); goto end; } if (!json_is_object(*json)) { oauth2_error(log, "parsed JSON did not contain a JSON object"); json_decref(*json); *json = NULL; goto end; } rc = true; end: return rc; } bool oauth2_json_object_get(oauth2_log_t *log, const json_t *json, const char *name, json_t **value) { bool rc = false; json_t *v = NULL; if ((json == NULL) || (name == NULL) || (value == NULL)) goto end; v = json_object_get(json, name); if (v == NULL) goto end; if (json_is_null(v)) { rc = true; goto end; } if (!json_is_object(v)) { oauth2_warn(log, "found a non-object object with key: \"%s\"", name); goto end; } json_incref(v); *value = v; rc = true; end: return rc; } bool oauth2_json_string_get(oauth2_log_t *log, const json_t *json, const char *name, char **value, const char *default_value) { bool rc = false; json_t *v = NULL; if ((json == NULL) || (name == NULL) || (value == NULL)) { if (default_value) *value = oauth2_strdup(default_value); goto end; } v = json_object_get(json, name); if (v == NULL) { if (default_value) *value = oauth2_strdup(default_value); rc = true; goto end; } if (json_is_null(v)) { rc = true; goto end; } if (!json_is_string(v)) { oauth2_warn(log, "found a non-string object with key: \"%s\"", name); goto end; } *value = oauth2_strdup(json_string_value(v)); rc = true; end: return rc; } bool oauth2_json_number_get(oauth2_log_t *log, const json_t *json, const char *name, json_int_t *number, const json_int_t default_value) { bool rc = false; json_t *v = NULL; if ((json == NULL) || (name == NULL) || (number == NULL)) { *number = default_value; goto end; } v = json_object_get(json, name); if (v == NULL) { *number = default_value; rc = true; goto end; } if (json_is_null(v)) { *number = default_value; rc = true; goto end; } if (!json_is_number(v)) { oauth2_warn(log, "found a non-number object with key: \"%s\"", name); goto end; } *number = json_integer_value(v); rc = true; end: return rc; } char *oauth2_json_encode(oauth2_log_t *log, json_t *json, size_t flags) { char *s = json_dumps(json, flags); char *s_value = oauth2_strdup(s); free(s); return s_value; } static bool oauth2_json_string_print(oauth2_log_t *log, json_t *result, const char *key, const char *msg) { bool rc = false; json_t *value = NULL; char *str = NULL; value = json_object_get(result, key); if ((value != NULL) && (!json_is_null(value))) { str = oauth2_json_encode(log, value, JSON_ENCODE_ANY); oauth2_error(log, "%s: response contained an \"%s\" entry with " "value: \"%s\"", msg, key, oauth2_json_encode(log, value, JSON_ENCODE_ANY)); oauth2_mem_free(str); rc = true; } return rc; } static bool oauth2_json_check_error(oauth2_log_t *log, json_t *json) { bool rc = false; if (oauth2_json_string_print(log, json, "error", "oidc_util_check_json_error") == true) { oauth2_json_string_print(log, json, "error_description", "oidc_util_check_json_error"); rc = true; } return rc; } bool oauth2_json_decode_check_error(oauth2_log_t *log, const char *str, json_t **json) { bool rc = false; if (oauth2_json_decode_object(log, str, json) == false) goto end; if (oauth2_json_check_error(log, *json) == true) { json_decref(*json); *json = NULL; goto end; } rc = true; end: return rc; } /* * other */ #define OAUTH2_RANDOM_BUFSIZE 4096 bool _oauth2_rand_bytes(oauth2_log_t *log, uint8_t *buf, size_t len) { bool rc = true; int chunk = 0; uint8_t *ptr = buf; while (len > 0) { chunk = len < OAUTH2_RANDOM_BUFSIZE ? len : OAUTH2_RANDOM_BUFSIZE; if (RAND_bytes(ptr, chunk) <= 0) { oauth2_error(log, "could not generate random bytes %d", chunk); rc = false; break; } len -= chunk; ptr += chunk; } return rc; } char *_oauth2_bytes2str(oauth2_log_t *log, uint8_t *buf, size_t len) { char *rv = NULL, *ptr = NULL; int i = 0, n = 0; rv = oauth2_mem_alloc(len * 2 + 1); if (rv == NULL) goto end; ptr = rv; for (i = 0; i < len; i++) { n = oauth2_snprintf(ptr, 3, "%02x", buf[i]); if (n != 2) { oauth2_error(log, "could not oauth2_snprintf byte %d", i); oauth2_mem_free(rv); rv = NULL; goto end; } ptr += 2; } rv[len * 2] = '\0'; end: return rv; } char *oauth2_rand_str(oauth2_log_t *log, size_t len) { char *rv = NULL; uint8_t *buf = NULL; size_t half_len = 0; if (len == 0) goto end; half_len = len / 2 + 1; buf = oauth2_mem_alloc(half_len); if (buf == NULL) goto end; if (_oauth2_rand_bytes(log, buf, half_len) == false) goto end; rv = _oauth2_bytes2str(log, buf, half_len); // need this if len is uneven rv[len] = '\0'; end: if (buf) oauth2_mem_free(buf); // oauth2_error(log, " ## returning: %s (%lu)", rv, len); return rv; } /* char *oauth2_rand_str(oauth2_log_t *log, size_t len) { char *rv = NULL; bool rc = 0; uint8_t *buf = NULL; if (len == 0) goto end; buf = oauth2_mem_alloc(len); if (buf == NULL) goto end; rc = _oauth2_rand_bytes(log, buf, len); if (rc == false) goto end; oauth2_base64url_encode(log, (const uint8_t *)buf, len, &rv); if (rv) rv[len] = '\0'; end: if (buf) oauth2_mem_free(buf); // oauth2_error(log, " ## returning: %s (%lu)", rv, len); return rv; } */ #ifdef _MSC_VER #define WIN32_LEAN_AND_MEAN #include #include // portable: uint64_t MSVC: __int64 // MSVC defines this in winsock2.h!? // typedef struct timeval { // long tv_sec; // long tv_usec; //} timeval; int gettimeofday(struct timeval *tp, struct timezone *tzp) { // Note: some broken versions only have 8 trailing zero's, the correct // epoch has 9 trailing zero's This magic number is the number of 100 // nanosecond intervals since January 1, 1601 (UTC) until 00:00:00 // January 1, 1970 static const uint64_t EPOCH = ((uint64_t)116444736000000000ULL); SYSTEMTIME system_time; FILETIME file_time; uint64_t time; GetSystemTime(&system_time); SystemTimeToFileTime(&system_time, &file_time); time = ((uint64_t)file_time.dwLowDateTime); time += ((uint64_t)file_time.dwHighDateTime) << 32; tp->tv_sec = (long)((time - EPOCH) / 10000000L); tp->tv_usec = (long)(system_time.wMilliseconds * 1000); return 0; } #endif static oauth2_time_t _oauth2_time_now_ms() { struct timeval tv; gettimeofday(&tv, NULL); return (tv.tv_sec * (uint64_t)OAUTH2_MSEC_PER_SEC) + (tv.tv_usec / OAUTH2_USEC_PER_MSEC); } oauth2_time_t oauth2_time_now_sec() { return _oauth2_time_now_ms() / OAUTH2_MSEC_PER_SEC; } oauth2_time_t oauth2_parse_time_sec(oauth2_log_t *log, const char *seconds, oauth2_time_t default_value) { oauth2_time_t result = default_value; if (seconds) result = (oauth2_time_t)strtol(seconds, NULL, 10); return result; } bool oauth2_parse_bool(oauth2_log_t *log, const char *value, bool default_value) { return value ? (strcasecmp(value, "true") == 0) : default_value; } oauth2_uint_t oauth2_parse_uint(oauth2_log_t *log, const char *int_value, oauth2_uint_t default_value) { oauth2_uint_t result = default_value; if (int_value) result = (oauth2_uint_t)strtol(int_value, NULL, 10); return result; } /* * 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 *oauth2_normalize_header_name(const char *str) { /* token = 1* * CTL = * separators = "(" | ")" | "<" | ">" | "@" * | "," | ";" | ":" | "\" | <"> * | "/" | "[" | "]" | "?" | "=" * | "{" | "}" | SP | HT */ const char *separators = "()<>@,;:\\\"/[]?={} \t"; char *ns = oauth2_strdup(str); size_t i; for (i = 0; i < strlen(ns); i++) { if (ns[i] < 32 || ns[i] == 127) ns[i] = '-'; else if (strchr(separators, ns[i]) != NULL) ns[i] = '-'; } return ns; } char *oauth_read_file(oauth2_log_t *log, const char *filename) { char *rv = NULL; FILE *fp = NULL; long fsize = 0; size_t n = 0; fp = fopen(filename, "rb"); if (fp == NULL) { oauth2_error(log, "could not read file: %s", filename); goto end; } fseek(fp, 0, SEEK_END); fsize = ftell(fp); fseek(fp, 0, SEEK_SET); rv = oauth2_mem_alloc(fsize + 1); n = fread(rv, 1, fsize, fp); if (n != fsize) { oauth2_error(log, "read only %ld bytes from file of %ld length", n, fsize); oauth2_mem_free(rv); rv = NULL; goto end; } rv[fsize] = 0; end: if (fp) fclose(fp); return rv; } pcre2_code *oauth2_pcre2_compile(const char *regexp) { int errorcode; PCRE2_SIZE erroroffset; pcre2_code *preg = pcre2_compile((PCRE2_SPTR)regexp, (PCRE2_SIZE)strlen(regexp), 0, &errorcode, &erroroffset, NULL); return preg; } int oauth2_pcre2_exec(pcre2_code *preg, const char *input, int len, char **error_str) { int rc = 0; pcre2_match_data *match_data = NULL; match_data = pcre2_match_data_create_from_pattern(preg, NULL); if (match_data == NULL) { *error_str = oauth2_strdup( "pcre2_match_data_create_from_pattern failed"); goto end; } rc = pcre2_match(preg, (PCRE2_SPTR)input, (PCRE2_SIZE)len, 0, 0, match_data, NULL); if (rc < 0) { switch (rc) { case PCRE2_ERROR_NOMATCH: *error_str = oauth2_strdup("string did not match the pattern"); break; default: *error_str = oauth2_strdup("unknown error"); break; } } end: if (match_data) pcre2_match_data_free(match_data); return rc; } liboauth2-2.1.0/src/util_int.h000066400000000000000000000156771475305260400162200ustar00rootroot00000000000000#ifndef _OAUTH2_UTIL_INT_H_ #define _OAUTH2_UTIL_INT_H_ /*************************************************************************** * * Copyright (C) 2018-2025 - ZmartZone Holding BV * * 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. * * @Author: Hans Zandbelt - hans.zandbelt@openidc.com * **************************************************************************/ #ifdef _MSC_VER // not #if defined(_WIN32) || defined(_WIN64) because we have strncasecmp in // mingw #define strncasecmp _strnicmp #define strcasecmp _stricmp #define close _close #endif // need this for vasprintf with stdio.h #ifndef _GNU_SOURCE #define _GNU_SOURCE #endif #include #include #include #define PCRE2_CODE_UNIT_WIDTH 8 #include #include "oauth2/log.h" #include "oauth2/util.h" /* * internal defines */ #define _OAUTH2_CHAR_COLON ':' #define _OAUTH2_CHAR_QUERY '?' #define _OAUTH2_CHAR_FSLASH '/' #define _OAUTH2_CHAR_EQUAL '=' #define _OAUTH2_CHAR_SPACE ' ' #define _OAUTH2_CHAR_SEMICOL ';' #define _OAUTH2_CHAR_AMP '&' #define _OAUTH2_CHAR_DOT '.' #define _OAUTH2_CHAR_COMMA ',' #define _OAUTH2_STR_COMMA "," #define _OAUTH2_STR_COLON ":" #define _OAUTH2_STR_QMARK "?" #define _OAUTH2_STR_AMP "&" #define _OAUTH2_STR_SEMICOL ";" #define _OAUTH2_STR_EQUAL "=" #define _OAUTH2_STR_DOT "." /* * internal generic (log) macros */ #define _OAUTH2_UTIL_JOSE_ERR_LOG(log, msg, err) \ oauth2_error(log, "%s failed: [%s:%lu %s %s]", msg ? msg : "", \ err.file ? err.file : "", err.line, \ err.function ? err.function : "", \ err.message ? err.message : "") /* * struct type set/get macros */ #define _OAUTH2_TYPE_IMPLEMENT_MEMBER_SET(module, object, member, type, name) \ bool oauth2_##module##_##object##_##member##_set( \ oauth2_log_t *log, oauth2_##module##_##object##_t *p, \ const type v) \ { \ return _oauth2_struct_slot_##name##_set( \ p, offsetof(oauth2_##module##_##object##_t, member), v); \ } #define _OAUTH2_TYPE_IMPLEMENT_MEMBER_GET(module, object, member, type) \ type oauth2_##module##_##object##_##member##_get( \ oauth2_log_t *log, const oauth2_##module##_##object##_t *p) \ { \ if (p == NULL) \ abort(); \ return p->member; \ } #define _OAUTH2_TYPE_IMPLEMENT_MEMBER_SET_GET(module, object, member, type, \ name) \ _OAUTH2_TYPE_IMPLEMENT_MEMBER_SET(module, object, member, type, name) \ _OAUTH2_TYPE_IMPLEMENT_MEMBER_GET(module, object, member, type) /* * internal utility functions */ bool _oauth2_struct_slot_str_set(void *struct_ptr, size_t offset, const char *value); bool _oauth2_struct_slot_integer_set(void *struct_ptr, size_t offset, const int value); bool _oauth2_struct_slot_bln_set(void *struct_ptr, size_t offset, const bool value); bool _oauth2_struct_slot_ptr_set(void *struct_ptr, size_t offset, const void *value); bool _oauth2_struct_slot_uint_set(void *struct_ptr, size_t offset, oauth2_uint_t value); bool _oauth2_struct_slot_time_set(void *struct_ptr, size_t offset, oauth2_time_t value); char *_oauth2_stradd4(char *s1, const char *s2, const char *s3, const char *s4, const char *s5); bool _oauth2_nv_list_parse(oauth2_log_t *log, const char *input, oauth2_nv_list_t *tuples, char sep_tuple, char sep_nv, bool trim, bool url_decode); char *_oauth2_bytes2str(oauth2_log_t *log, uint8_t *buf, size_t len); /* * struct list member management macros */ #define _OAUTH2_MEMBER_LIST_IMPLEMENT_SET(module, type, list) \ bool oauth2_##module##_##type##_##list##_set( \ oauth2_log_t *log, oauth2_##module##_##type##_t *r, \ const char *name, const char *value) \ { \ return r ? oauth2_nv_list_set(log, r->list, name, value) \ : false; \ } #define _OAUTH2_MEMBER_LIST_IMPLEMENT_UNSET(module, type, list) \ bool oauth2_##module##_##type##_##list##_unset( \ oauth2_log_t *log, oauth2_##module##_##type##_t *r, \ const char *name) \ { \ return r ? oauth2_nv_list_unset(log, r->list, name) : false; \ } #define _OAUTH2_MEMBER_LIST_IMPLEMENT_ADD(module, type, list) \ bool oauth2_##module##_##type##_##list##_add( \ oauth2_log_t *log, oauth2_##module##_##type##_t *r, \ const char *name, const char *value) \ { \ return r ? oauth2_nv_list_add(log, r->list, name, value) \ : false; \ } #define _OAUTH2_MEMBER_LIST_IMPLEMENT_GET(module, type, list) \ const char *oauth2_##module##_##type##_##list##_get( \ oauth2_log_t *log, const oauth2_##module##_##type##_t *r, \ const char *name) \ { \ return r ? oauth2_nv_list_get(log, r->list, name) : NULL; \ } #define _OAUTH2_MEMBER_LIST_IMPLEMENT_UNSET_GET(module, type, list) \ _OAUTH2_MEMBER_LIST_IMPLEMENT_UNSET(module, type, list) \ _OAUTH2_MEMBER_LIST_IMPLEMENT_GET(module, type, list) #define _OAUTH2_MEMBER_LIST_IMPLEMENT_SET_ADD_UNSET_GET(module, type, list) \ _OAUTH2_MEMBER_LIST_IMPLEMENT_SET(module, type, list) \ _OAUTH2_MEMBER_LIST_IMPLEMENT_UNSET_GET(module, type, list) \ _OAUTH2_MEMBER_LIST_IMPLEMENT_ADD(module, type, list) pcre2_code *oauth2_pcre2_compile(const char *regexp); int oauth2_pcre2_exec(pcre2_code *preg, const char *input, int len, char **error_str); #endif /* _OAUTH2_UTIL_INT_H_ */ liboauth2-2.1.0/src/version.c000066400000000000000000000017671475305260400160440ustar00rootroot00000000000000/*************************************************************************** * * Copyright (C) 2018-2025 - ZmartZone Holding BV * * 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. * * @Author: Hans Zandbelt - hans.zandbelt@openidc.com * **************************************************************************/ #include const char *oauth2_version() { return OAUTH2_PACKAGE_VERSION; } const char *oauth2_package_string() { return OAUTH2_PACKAGE_NAME "-" OAUTH2_PACKAGE_VERSION; } liboauth2-2.1.0/test/000077500000000000000000000000001475305260400143705ustar00rootroot00000000000000liboauth2-2.1.0/test/.gitignore000066400000000000000000000001031475305260400163520ustar00rootroot00000000000000/*.o /*.log /*.trs /*.gcda /*.gcno /.deps/ /.dirstamp /Dockerfile* liboauth2-2.1.0/test/Dockerfile000066400000000000000000000030611475305260400163620ustar00rootroot00000000000000FROM ubuntu:jammy RUN apt-get update && apt-get install -y pkg-config make gcc gdb lcov valgrind vim curl wget RUN apt-get update && apt-get install -y autoconf automake libtool RUN apt-get update && apt-get install -y libssl-dev libjansson-dev libcurl4-openssl-dev check RUN apt-get update && apt-get install -y apache2-dev RUN apt-get update && apt-get install -y libpcre3-dev zlib1g-dev ENV NGINX_VERSION 1.18.0 WORKDIR /root RUN wget https://nginx.org/download/nginx-${NGINX_VERSION}.tar.gz RUN tar zxvf nginx-${NGINX_VERSION}.tar.gz RUN ln -s nginx-${NGINX_VERSION} nginx RUN cd /root/nginx && ./configure --with-debug RUN apt-get update && apt-get install -y libpcre2-dev libpcre2-8-0 RUN apt-get update && apt-get install -y libcjose-dev libcjose0 RUN apt-get update && apt-get install -y libmemcached-dev memcached RUN apt-get update && apt-get install -y libhiredis-dev redis-server RUN apt-get update && apt-get install -y libjq-dev ENV SRCDIR /root/liboauth2 RUN mkdir ${SRCDIR} WORKDIR ${SRCDIR} ENV LD_LIBRARY_PATH ${SRCDIR}/.libs ENV CK_FORK "no" RUN sed -i "s/bind .*/bind 127.0.0.1/g" /etc/redis/redis.conf RUN echo "requirepass foobared" >> /etc/redis/redis.conf RUN echo "#!/bin/sh" >> ./start.sh RUN echo "service memcached start" >> ./start.sh RUN echo "service redis-server start" >> ./start.sh RUN chmod a+x ./start.sh COPY . ${SRCDIR} ARG CONFIGURE_ARGS RUN ./autogen.sh RUN ./configure \ CFLAGS="-g -O0 -I/usr/include/apache2" \ LDFLAGS="-lrt" \ --with-nginx=/root/nginx \ --with-jq=/usr \ ${CONFIGURE_ARGS} RUN make all RUN make check_liboauth2 liboauth2-2.1.0/test/check_apache.c000066400000000000000000000203431475305260400171140ustar00rootroot00000000000000/*************************************************************************** * * Copyright (C) 2018-2025 - ZmartZone Holding BV * * 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. * * @Author: Hans Zandbelt - hans.zandbelt@openidc.com * **************************************************************************/ #include "check_liboauth2.h" #include "oauth2/apache.h" #include "oauth2/mem.h" #include static apr_pool_t *pool = NULL; static request_rec *request = NULL; static oauth2_log_t *_log = 0; static request_rec *setup_request(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; " "oauth2_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->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->provider.token_binding_policy = OIDC_TOKEN_BINDING_POLICY_OPTIONAL; 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 = "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; } static void check_apache_log_request(oauth2_log_sink_t *sink, const char *filename, unsigned long line, const char *function, oauth2_log_level_t level, const char *msg) { oauth2_log(_log, filename, line, function, level, "%s", msg); } static void setup(void) { _log = oauth2_init(OAUTH2_LOG_TRACE1, 0); apr_initialize(); apr_pool_create(&pool, NULL); request = setup_request(pool); } static void teardown(void) { apr_pool_destroy(pool); apr_terminate(); oauth2_shutdown(_log); } START_TEST(test_apache_request_state) { json_error_t err; const char *s_claims = "{ \"sub\": \"joe\" }"; json_t *in_claims = NULL, *out_claims = NULL; const char *key = "C"; char *value = NULL; oauth2_apache_request_ctx_t *ctx = NULL; ctx = oauth2_apache_request_context(request, check_apache_log_request, "check_apache"); in_claims = json_loads(s_claims, 0, &err); oauth2_apache_request_state_set_json(ctx, key, in_claims); oauth2_apache_request_state_get_json(ctx, key, &out_claims); ck_assert_ptr_ne(out_claims, NULL); oauth2_json_string_get(_log, out_claims, "sub", &value, NULL); ck_assert_ptr_ne(value, NULL); ck_assert_str_eq(value, "joe"); oauth2_mem_free(value); json_decref(in_claims); json_decref(out_claims); } END_TEST START_TEST(test_apache_authz_match_claim) { json_error_t err; oauth2_apache_request_ctx_t *ctx = NULL; const char *s_claims = "{ \"sub\": \"joe\" }"; json_t *claims = NULL; bool rc = false; ctx = oauth2_apache_request_context(request, check_apache_log_request, "check_apache"); claims = json_loads(s_claims, 0, &err); rc = oauth2_apache_authz_match_claim(ctx, "sub:joe", claims); ck_assert_int_eq(rc, true); rc = oauth2_apache_authz_match_claim(ctx, "sub:hans", claims); ck_assert_int_eq(rc, false); json_decref(claims); } END_TEST START_TEST(test_apache_authz_match_claim_expr) { json_error_t err; oauth2_apache_request_ctx_t *ctx = NULL; const char *s_claims = "{ \"scope\": \"one, two, three, four, five\", " "\"scopes\": [ \"one\", \"two\", \"three\" ] }"; json_t *claims = NULL; bool rc = false; ctx = oauth2_apache_request_context(request, check_apache_log_request, "check_apache"); claims = json_loads(s_claims, 0, &err); rc = oauth2_apache_authz_match_claim(ctx, "scope~(^|\\s)(one)($|\\s|,)", claims); ck_assert_int_eq(rc, true); rc = oauth2_apache_authz_match_claim( ctx, "scope~(^|\\s)(four)($|\\s|,)", claims); ck_assert_int_eq(rc, true); rc = oauth2_apache_authz_match_claim( ctx, "scope~(^|\\s)(five)($|\\s|,)", claims); ck_assert_int_eq(rc, true); rc = oauth2_apache_authz_match_claim(ctx, "scope~(^|\\s)(six)($|\\s|,)", claims); ck_assert_int_eq(rc, false); rc = oauth2_apache_authz_match_claim(ctx, "scopes~^three$", claims); ck_assert_int_eq(rc, true); rc = oauth2_apache_authz_match_claim(ctx, "scopes~^four", claims); ck_assert_int_eq(rc, false); json_decref(claims); } END_TEST START_TEST(test_apache_authorize) { json_error_t err; oauth2_apache_request_ctx_t *ctx = NULL; const char *s_claims = "{ \"sub\": \"joe\" }"; json_t *claims = NULL; authz_status rc = AUTHZ_DENIED; ctx = oauth2_apache_request_context(request, check_apache_log_request, "check_apache"); claims = json_loads(s_claims, 0, &err); rc = oauth2_apache_authorize(ctx, claims, "sub:hans", oauth2_apache_authz_match_claim); ck_assert_int_eq(rc, AUTHZ_DENIED_NO_USER); request->user = "joe"; rc = oauth2_apache_authorize(ctx, claims, "sub:hans", oauth2_apache_authz_match_claim); ck_assert_int_eq(rc, AUTHZ_DENIED); rc = oauth2_apache_authorize(ctx, claims, "sub:joe", oauth2_apache_authz_match_claim); ck_assert_int_eq(rc, AUTHZ_GRANTED); json_decref(claims); } END_TEST START_TEST(test_apache_http_response_set) { bool rc = false; oauth2_http_response_t *response = NULL; response = oauth2_http_response_init(_log); rc = oauth2_apache_http_response_set(_log, response, request); ck_assert_int_eq(rc, true); oauth2_http_response_free(_log, response); } END_TEST Suite *oauth2_check_apache_suite() { Suite *s = suite_create("apache"); TCase *c = tcase_create("core"); tcase_add_checked_fixture(c, setup, teardown); tcase_add_test(c, test_apache_request_state); tcase_add_test(c, test_apache_authz_match_claim); tcase_add_test(c, test_apache_authz_match_claim_expr); tcase_add_test(c, test_apache_authorize); tcase_add_test(c, test_apache_http_response_set); suite_add_tcase(s, c); return s; } liboauth2-2.1.0/test/check_cache.c000066400000000000000000000131751475305260400167430ustar00rootroot00000000000000/*************************************************************************** * * Copyright (C) 2018-2025 - ZmartZone Holding BV * * 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. * * @Author: Hans Zandbelt - hans.zandbelt@openidc.com * **************************************************************************/ #include "check_liboauth2.h" #include "oauth2/cache.h" #include "oauth2/cfg.h" #include "oauth2/mem.h" #include #include #include #include static oauth2_log_t *_log = 0; static void setup(void) { _log = oauth2_init(OAUTH2_LOG_TRACE1, 0); } static void teardown(void) { oauth2_shutdown(_log); } // oauth2_cfg_set_cache START_TEST(test_cache_bogus) { char *rv = NULL; rv = oauth2_cfg_set_cache(_log, NULL, "bogus", NULL); ck_assert_ptr_ne(rv, NULL); oauth2_mem_free(rv); } END_TEST static void _test_basic_cache(oauth2_cache_t *c) { bool rc = false; char *value = NULL; rc = oauth2_cache_set(_log, c, "piet", "klaas", 2); ck_assert_int_eq(rc, true); value = NULL; rc = oauth2_cache_get(_log, c, "piet", &value); ck_assert_int_eq(rc, true); ck_assert_ptr_ne(value, NULL); ck_assert_str_eq(value, "klaas"); oauth2_mem_free(value); sleep(3); value = NULL; rc = oauth2_cache_get(_log, c, "piet", &value); ck_assert_int_eq(rc, true); ck_assert_ptr_eq(value, NULL); rc = oauth2_cache_set(_log, c, "piet", "klaas", 1); ck_assert_int_eq(rc, true); value = NULL; rc = oauth2_cache_get(_log, c, "piet", &value); ck_assert_int_eq(rc, true); ck_assert_ptr_ne(value, NULL); ck_assert_str_eq(value, "klaas"); oauth2_mem_free(value); rc = oauth2_cache_set(_log, c, "piet", NULL, 0); ck_assert_int_eq(rc, true); value = NULL; rc = oauth2_cache_get(_log, c, "piet", &value); ck_assert_int_eq(rc, true); ck_assert_ptr_eq(value, NULL); value = NULL; rc = oauth2_cache_get(_log, c, "piet", &value); ck_assert_int_eq(rc, true); ck_assert_ptr_eq(value, NULL); } START_TEST(test_cache_shm) { bool rc = false; char *value = NULL; oauth2_cache_t *c = NULL; char *rv = NULL; rv = oauth2_cfg_set_cache(_log, NULL, "shm", "max_val_size=16&max_entries=2"); ck_assert_ptr_eq(rv, NULL); c = oauth2_cache_obtain(_log, NULL); ck_assert_ptr_ne(c, NULL); _test_basic_cache(c); // override the max nr of entries rc = oauth2_cache_set(_log, c, "hans", "zandbelt", 1); ck_assert_int_eq(rc, true); rc = oauth2_cache_set(_log, c, "nog", "een", 1); ck_assert_int_eq(rc, true); rc = oauth2_cache_set(_log, c, "hallo", "dan", 1); ck_assert_int_eq(rc, true); value = NULL; rc = oauth2_cache_get(_log, c, "piet", &value); ck_assert_int_eq(rc, true); ck_assert_ptr_eq(value, NULL); rc = oauth2_cache_set(_log, c, "value_too_long", "12345678901234567890", 1); ck_assert_int_eq(rc, false); rv = oauth2_cfg_set_cache( _log, NULL, "shm", "name=short_key_size&key_hash_algo=none&max_key_size=8"); ck_assert_ptr_eq(rv, NULL); c = oauth2_cache_obtain(_log, "short_key_size"); ck_assert_ptr_ne(c, NULL); rc = oauth2_cache_set(_log, c, "hans", "zandbelt", 1); ck_assert_int_eq(rc, true); rc = oauth2_cache_set(_log, c, "key_too_long_" "123456789012345678901234567890123456789012345678" "9012345678901234567890", "12345678901234567890", 1); ck_assert_int_eq(rc, false); } END_TEST START_TEST(test_cache_file) { bool rc = false; oauth2_cache_t *c = NULL; char *rv = NULL; char *value = NULL; rv = oauth2_cfg_set_cache( _log, NULL, "file", "name=file&key_hash_algo=none&max_key_size=8&clean_interval=1"); ck_assert_ptr_eq(rv, NULL); c = oauth2_cache_obtain(_log, "file"); ck_assert_ptr_ne(c, NULL); _test_basic_cache(c); rc = oauth2_cache_set(_log, c, "hans", "zandbelt", 1); ck_assert_int_eq(rc, true); // also wait for the cache clean cycle (interval=1) to run sleep(1); rc = oauth2_cache_set(_log, c, "hans2", "zandbelt2", 1); ck_assert_int_eq(rc, true); value = NULL; rc = oauth2_cache_get(_log, c, "hans", &value); ck_assert_int_eq(rc, true); ck_assert_ptr_eq(value, NULL); // TODO: test file /tmp/mod-auth-openidc-hans exists? } END_TEST #ifdef HAVE_LIBMEMCACHE START_TEST(test_cache_memcache) { oauth2_cache_t *c = NULL; char *rv = NULL; rv = oauth2_cfg_set_cache(_log, NULL, "memcache", "name=memcache"); ck_assert_ptr_eq(rv, NULL); c = oauth2_cache_obtain(_log, "memcache"); ck_assert_ptr_ne(c, NULL); _test_basic_cache(c); } END_TEST #endif #ifdef HAVE_LIBHIREDIS START_TEST(test_cache_redis) { oauth2_cache_t *c = NULL; char *rv = NULL; //&password=foobared rv = oauth2_cfg_set_cache(_log, NULL, "redis", "name=redis"); ck_assert_ptr_eq(rv, NULL); c = oauth2_cache_obtain(_log, "redis"); ck_assert_ptr_ne(c, NULL); _test_basic_cache(c); } END_TEST #endif Suite *oauth2_check_cache_suite() { Suite *s = suite_create("cache"); TCase *c = tcase_create("core"); tcase_add_checked_fixture(c, setup, teardown); tcase_add_test(c, test_cache_bogus); tcase_add_test(c, test_cache_shm); tcase_add_test(c, test_cache_file); #ifdef HAVE_LIBMEMCACHE tcase_add_test(c, test_cache_memcache); #endif #ifdef HAVE_LIBHIREDIS tcase_add_test(c, test_cache_redis); #endif tcase_set_timeout(c, 8); suite_add_tcase(s, c); return s; } liboauth2-2.1.0/test/check_cfg.c000066400000000000000000000144271475305260400164400ustar00rootroot00000000000000#include #include "check_liboauth2.h" #include "oauth2/cfg.h" #include #include "cfg_int.h" static oauth2_log_t *_log = 0; static void setup(void) { _log = oauth2_init(OAUTH2_LOG_TRACE1, 0); } static void teardown(void) { oauth2_shutdown(_log); } typedef struct test_cfg_slot_struct { oauth2_flag_t flag; oauth2_uint_t uint; } test_cfg_slot_struct; START_TEST(test_flag_slot) { const char *rv = NULL; test_cfg_slot_struct st = {OAUTH2_CFG_FLAG_UNSET, OAUTH2_CFG_UINT_UNSET}; rv = oauth2_cfg_set_flag_slot( NULL, offsetof(test_cfg_slot_struct, flag), NULL); ck_assert_ptr_ne(rv, NULL); ck_assert_uint_eq(st.flag, OAUTH2_CFG_FLAG_UNSET); rv = oauth2_cfg_set_flag_slot(&st, offsetof(test_cfg_slot_struct, flag), NULL); ck_assert_ptr_eq(rv, NULL); ck_assert_uint_eq(st.flag, OAUTH2_CFG_FLAG_UNSET); rv = oauth2_cfg_set_flag_slot(&st, offsetof(test_cfg_slot_struct, flag), ""); ck_assert_ptr_ne(rv, NULL); ck_assert_uint_eq(st.flag, OAUTH2_CFG_FLAG_UNSET); rv = oauth2_cfg_set_flag_slot(&st, offsetof(test_cfg_slot_struct, flag), "true"); ck_assert_ptr_eq(rv, NULL); ck_assert_uint_eq(st.flag, true); rv = oauth2_cfg_set_flag_slot(&st, offsetof(test_cfg_slot_struct, flag), "false"); ck_assert_ptr_eq(rv, NULL); ck_assert_uint_eq(st.flag, false); rv = oauth2_cfg_set_flag_slot(&st, offsetof(test_cfg_slot_struct, flag), "True"); ck_assert_ptr_eq(rv, NULL); ck_assert_uint_eq(st.flag, true); rv = oauth2_cfg_set_flag_slot(&st, offsetof(test_cfg_slot_struct, flag), "False"); ck_assert_ptr_eq(rv, NULL); ck_assert_uint_eq(st.flag, false); rv = oauth2_cfg_set_flag_slot(&st, offsetof(test_cfg_slot_struct, flag), "TRUE"); ck_assert_ptr_eq(rv, NULL); ck_assert_uint_eq(st.flag, true); rv = oauth2_cfg_set_flag_slot(&st, offsetof(test_cfg_slot_struct, flag), "FALSE"); ck_assert_ptr_eq(rv, NULL); ck_assert_uint_eq(st.flag, false); rv = oauth2_cfg_set_flag_slot(&st, offsetof(test_cfg_slot_struct, flag), "0"); ck_assert_ptr_eq(rv, NULL); ck_assert_uint_eq(st.flag, false); rv = oauth2_cfg_set_flag_slot(&st, offsetof(test_cfg_slot_struct, flag), "1"); ck_assert_ptr_eq(rv, NULL); ck_assert_uint_eq(st.flag, true); st.flag = OAUTH2_CFG_FLAG_UNSET; rv = oauth2_cfg_set_flag_slot(&st, offsetof(test_cfg_slot_struct, flag), "2"); ck_assert_ptr_ne(rv, NULL); ck_assert_uint_eq(st.flag, OAUTH2_CFG_FLAG_UNSET); rv = oauth2_cfg_set_flag_slot(&st, offsetof(test_cfg_slot_struct, flag), "On"); ck_assert_ptr_eq(rv, NULL); ck_assert_uint_eq(st.flag, true); rv = oauth2_cfg_set_flag_slot(&st, offsetof(test_cfg_slot_struct, flag), "Off"); ck_assert_ptr_eq(rv, NULL); ck_assert_uint_eq(st.flag, false); rv = oauth2_cfg_set_flag_slot(&st, offsetof(test_cfg_slot_struct, flag), "ON"); ck_assert_ptr_eq(rv, NULL); ck_assert_uint_eq(st.flag, true); rv = oauth2_cfg_set_flag_slot(&st, offsetof(test_cfg_slot_struct, flag), "OFF"); ck_assert_ptr_eq(rv, NULL); ck_assert_uint_eq(st.flag, false); } END_TEST START_TEST(test_uint_slot) { const char *rv = NULL; test_cfg_slot_struct st = {OAUTH2_CFG_FLAG_UNSET, OAUTH2_CFG_UINT_UNSET}; rv = oauth2_cfg_set_uint_slot( NULL, offsetof(test_cfg_slot_struct, uint), NULL); ck_assert_ptr_ne(rv, NULL); ck_assert_uint_eq(st.uint, OAUTH2_CFG_UINT_UNSET); rv = oauth2_cfg_set_uint_slot(&st, offsetof(test_cfg_slot_struct, uint), NULL); ck_assert_ptr_ne(rv, NULL); ck_assert_uint_eq(st.uint, OAUTH2_CFG_UINT_UNSET); rv = oauth2_cfg_set_uint_slot(&st, offsetof(test_cfg_slot_struct, uint), ""); ck_assert_ptr_ne(rv, NULL); ck_assert_uint_eq(st.uint, OAUTH2_CFG_UINT_UNSET); rv = oauth2_cfg_set_uint_slot(&st, offsetof(test_cfg_slot_struct, uint), "1two"); ck_assert_ptr_ne(rv, NULL); ck_assert_uint_eq(st.uint, OAUTH2_CFG_UINT_UNSET); rv = oauth2_cfg_set_uint_slot(&st, offsetof(test_cfg_slot_struct, uint), "-1"); ck_assert_ptr_ne(rv, NULL); ck_assert_uint_eq(st.uint, OAUTH2_CFG_UINT_UNSET); rv = oauth2_cfg_set_uint_slot(&st, offsetof(test_cfg_slot_struct, uint), "1"); ck_assert_ptr_eq(rv, NULL); ck_assert_uint_eq(st.uint, 1); } END_TEST START_TEST(test_target_pass) { oauth2_cfg_target_pass_t *cfg = NULL, *cfg2 = NULL, *cfg3 = NULL; char *rv = NULL; cfg = oauth2_cfg_target_pass_init(_log); ck_assert_ptr_ne(cfg, NULL); rv = oauth2_cfg_set_target_pass_options(_log, NULL, NULL); ck_assert_ptr_ne(rv, NULL); oauth2_mem_free(rv); ck_assert_uint_eq(oauth2_cfg_target_pass_get_as_envvars(cfg), true); ck_assert_uint_eq(oauth2_cfg_target_pass_get_as_headers(cfg), true); ck_assert_ptr_eq(oauth2_cfg_target_pass_get_authn_header(cfg), NULL); ck_assert_str_eq(oauth2_cfg_target_pass_get_prefix(cfg), "OAUTH2_CLAIM_"); ck_assert_str_eq(oauth2_cfg_target_get_remote_user_claim(cfg), "sub"); ck_assert_ptr_eq(oauth2_cfg_target_get_json_payload_claim(cfg), NULL); rv = oauth2_cfg_set_target_pass_options( _log, cfg, "envvars=false&headers=false&authn_header=auth&prefix=oidc&remote_" "user_claim=preferred_username&json_payload_claim=my_json"); ck_assert_ptr_eq(rv, NULL); ck_assert_uint_eq(oauth2_cfg_target_pass_get_as_envvars(cfg), false); ck_assert_uint_eq(oauth2_cfg_target_pass_get_as_headers(cfg), false); ck_assert_str_eq(oauth2_cfg_target_pass_get_authn_header(cfg), "auth"); ck_assert_str_eq(oauth2_cfg_target_pass_get_prefix(cfg), "oidc"); ck_assert_str_eq(oauth2_cfg_target_get_remote_user_claim(cfg), "preferred_username"); ck_assert_str_eq(oauth2_cfg_target_get_json_payload_claim(cfg), "my_json"); oauth2_cfg_target_pass_merge(_log, NULL, NULL, NULL); cfg2 = oauth2_cfg_target_pass_init(_log); ck_assert_ptr_ne(cfg2, NULL); cfg3 = oauth2_cfg_target_pass_init(_log); ck_assert_ptr_ne(cfg3, NULL); oauth2_cfg_target_pass_merge(_log, cfg2, cfg, cfg3); oauth2_cfg_target_pass_free(_log, cfg3); oauth2_cfg_target_pass_free(_log, cfg2); oauth2_cfg_target_pass_free(_log, cfg); } END_TEST Suite *oauth2_check_cfg_suite() { Suite *s = suite_create("cfg"); TCase *c = tcase_create("core"); tcase_add_checked_fixture(c, setup, teardown); tcase_add_test(c, test_flag_slot); tcase_add_test(c, test_uint_slot); tcase_add_test(c, test_target_pass); suite_add_tcase(s, c); return s; } liboauth2-2.1.0/test/check_http.c000066400000000000000000000600361475305260400166550ustar00rootroot00000000000000/*************************************************************************** * * Copyright (C) 2018-2025 - ZmartZone Holding BV * * 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. * * @Author: Hans Zandbelt - hans.zandbelt@openidc.com * **************************************************************************/ #include "check_liboauth2.h" #include "oauth2/http.h" #include "oauth2/mem.h" #include "oauth2/util.h" #include static oauth2_log_t *_log = NULL; OAUTH2_CHECK_HTTP_PATHS void oauth2_check_http_cleanup() { oauth2_check_http_base_free(); } static void setup(void) { _log = oauth2_init(OAUTH2_LOG_TRACE1, 0); // for coverage oauth2_http_request_free(_log, NULL); } static void teardown(void) { oauth2_shutdown(_log); } static void *faulty_alloc(size_t amt) { return NULL; } START_TEST(test_request_scheme) { char *scheme = NULL; oauth2_http_request_t *r = NULL; bool rc; // set null scheme scheme = NULL; r = oauth2_http_request_init(_log); rc = oauth2_http_request_scheme_set(_log, r, NULL); ck_assert_int_eq(rc, false); oauth2_http_request_free(_log, r); // set no scheme and get default scheme = NULL; r = oauth2_http_request_init(_log); scheme = oauth2_http_request_scheme_get(_log, r); ck_assert_str_eq(scheme, "https"); oauth2_mem_free(scheme); oauth2_http_request_free(_log, r); // set scheme via native setting scheme = NULL; r = oauth2_http_request_init(_log); oauth2_http_request_scheme_set(_log, r, "http"); scheme = oauth2_http_request_scheme_get(_log, r); ck_assert_str_eq(scheme, "http"); oauth2_mem_free(scheme); oauth2_http_request_free(_log, r); // set scheme via X-Forwarded-Proto scheme = NULL; r = oauth2_http_request_init(_log); oauth2_http_request_scheme_set(_log, r, "http"); oauth2_http_request_header_set(_log, r, "X-Forwarded-Proto", "https"); scheme = oauth2_http_request_scheme_get(_log, r); ck_assert_str_eq(scheme, "https"); oauth2_mem_free(scheme); oauth2_http_request_free(_log, r); // lowercase header scheme = NULL; r = oauth2_http_request_init(_log); oauth2_http_request_scheme_set(_log, r, "http"); oauth2_http_request_header_set(_log, r, "x-forwarded-proto", "https"); scheme = oauth2_http_request_scheme_get(_log, r); ck_assert_str_eq(scheme, "https"); oauth2_mem_free(scheme); oauth2_http_request_free(_log, r); // set scheme via X-Forwarded-Proto with multiple entries scheme = NULL; r = oauth2_http_request_init(_log); oauth2_http_request_scheme_set(_log, r, "http"); oauth2_http_request_header_set(_log, r, "X-Forwarded-Proto", "https, http"); scheme = oauth2_http_request_scheme_get(_log, r); ck_assert_str_eq(scheme, "https"); oauth2_mem_free(scheme); oauth2_http_request_free(_log, r); // set no scheme and add some headers r = oauth2_http_request_init(_log); oauth2_http_request_header_set(_log, r, "One", "1"); oauth2_http_request_header_set(_log, r, "Two", "2"); scheme = oauth2_http_request_scheme_get(_log, r); ck_assert_str_eq(scheme, "https"); oauth2_mem_free(scheme); oauth2_http_request_free(_log, r); // get scheme from null request scheme = NULL; scheme = oauth2_http_request_scheme_get(_log, NULL); ck_assert_ptr_eq(scheme, NULL); } END_TEST START_TEST(test_request_hostname) { char *hostname = NULL; oauth2_http_request_t *r = NULL; bool rc; // get default hostname null hostname = NULL; hostname = oauth2_http_request_hostname_get(_log, NULL); ck_assert_ptr_eq(hostname, NULL); // set null hostname hostname = NULL; r = oauth2_http_request_init(_log); rc = oauth2_http_request_hostname_set(_log, r, NULL); ck_assert_int_eq(rc, false); oauth2_http_request_free(_log, r); // set and get hostname hostname = NULL; r = oauth2_http_request_init(_log); oauth2_http_request_hostname_set(_log, r, "internal"); hostname = oauth2_http_request_hostname_get(_log, r); ck_assert_str_eq(hostname, "internal"); oauth2_mem_free(hostname); oauth2_http_request_free(_log, r); // set native hostname but override via X-Forwarded-Host hostname = NULL; r = oauth2_http_request_init(_log); oauth2_http_request_hostname_set(_log, r, "internal"); oauth2_http_request_header_set(_log, r, "X-Forwarded-Host", "external"); hostname = oauth2_http_request_hostname_get(_log, r); ck_assert_str_eq(hostname, "external"); oauth2_mem_free(hostname); oauth2_http_request_free(_log, r); // set native hostname but override via X-Forwarded-Host that includes // port hostname = NULL; r = oauth2_http_request_init(_log); oauth2_http_request_hostname_set(_log, r, "internal"); oauth2_http_request_header_set(_log, r, "X-Forwarded-Host", "external:8080"); hostname = oauth2_http_request_hostname_get(_log, r); ck_assert_str_eq(hostname, "external"); oauth2_mem_free(hostname); oauth2_http_request_free(_log, r); } END_TEST START_TEST(test_request_port) { char *port = NULL; oauth2_http_request_t *r = NULL; bool rc; // get default port null port = NULL; r = oauth2_http_request_init(_log); port = oauth2_http_request_port_get(_log, r); ck_assert_ptr_eq(port, NULL); oauth2_http_request_free(_log, r); // set native port port = NULL; r = oauth2_http_request_init(_log); rc = oauth2_http_request_port_set(_log, r, 443); ck_assert_int_eq(rc, true); port = oauth2_http_request_port_get(_log, r); ck_assert_ptr_eq(port, NULL); oauth2_http_request_free(_log, r); // get port via X-Forwareded-Port port = NULL; r = oauth2_http_request_init(_log); oauth2_http_request_header_set(_log, r, "X-Forwarded-Port", "8080"); port = oauth2_http_request_port_get(_log, r); ck_assert_str_eq(port, "8080"); oauth2_mem_free(port); oauth2_http_request_free(_log, r); // get port via X-Forwarded-Host overriding Host port = NULL; r = oauth2_http_request_init(_log); oauth2_http_request_header_set(_log, r, "Host", "internal:8282"); oauth2_http_request_header_set(_log, r, "X-Forwarded-Host", "external:8181"); port = oauth2_http_request_port_get(_log, r); ck_assert_str_eq(port, "8181"); oauth2_mem_free(port); oauth2_http_request_free(_log, r); // get port via Host that includes port port = NULL; r = oauth2_http_request_init(_log); oauth2_http_request_header_set(_log, r, "Host", "internal:8282"); port = oauth2_http_request_port_get(_log, r); ck_assert_str_eq(port, "8282"); oauth2_mem_free(port); oauth2_http_request_free(_log, r); // get port as null default for scheme, skipping Host header that // doesn't contain a port port = NULL; r = oauth2_http_request_init(_log); oauth2_http_request_scheme_set(_log, r, "http"); oauth2_http_request_header_set(_log, r, "Host", "internal"); port = oauth2_http_request_port_get(_log, r); ck_assert_ptr_eq(port, NULL); oauth2_http_request_free(_log, r); // get default port null, skipping Host and X-Forwarded-Host that don't // contain a port port = NULL; r = oauth2_http_request_init(_log); oauth2_http_request_header_set(_log, r, "Host", "internal"); oauth2_http_request_header_set(_log, r, "X-Forwarded-Host", "external"); port = oauth2_http_request_port_get(_log, r); ck_assert_ptr_eq(port, NULL); oauth2_http_request_free(_log, r); // get default port null for https scheme, skipping Host that doesn't // contain a port port = NULL; r = oauth2_http_request_init(_log); oauth2_http_request_scheme_set(_log, r, "https"); oauth2_http_request_header_set(_log, r, "Host", "internal"); port = oauth2_http_request_port_get(_log, r); ck_assert_ptr_eq(port, NULL); oauth2_http_request_free(_log, r); // get native port set to default scheme port port = NULL; r = oauth2_http_request_init(_log); rc = oauth2_http_request_port_set(_log, r, 443); ck_assert_int_eq(rc, true); oauth2_http_request_scheme_set(_log, r, "https"); oauth2_http_request_header_set(_log, r, "Host", "internal"); port = oauth2_http_request_port_get(_log, r); ck_assert_ptr_eq(port, NULL); oauth2_http_request_free(_log, r); // get native port, skipping Host that doesn't contain a port port = NULL; r = oauth2_http_request_init(_log); rc = oauth2_http_request_port_set(_log, r, 8080); oauth2_http_request_header_set(_log, r, "Host", "internal"); port = oauth2_http_request_port_get(_log, r); ck_assert_str_eq(port, "8080"); oauth2_mem_free(port); oauth2_http_request_free(_log, r); // get default port for scheme determined via X-Forwarded-Proto, // overriding native port port = NULL; r = oauth2_http_request_init(_log); rc = oauth2_http_request_port_set(_log, r, 8080); oauth2_http_request_header_set(_log, r, "Host", "internal"); oauth2_http_request_header_set(_log, r, "X-Forwarded-Proto", "https"); port = oauth2_http_request_port_get(_log, r); ck_assert_ptr_eq(port, NULL); oauth2_http_request_free(_log, r); // get default port null for default scheme https port = NULL; r = oauth2_http_request_init(_log); rc = oauth2_http_request_port_set(_log, r, 443); oauth2_http_request_header_set(_log, r, "Host", "internal"); port = oauth2_http_request_port_get(_log, r); ck_assert_ptr_eq(port, NULL); oauth2_http_request_free(_log, r); // get default port null for scheme http port = NULL; r = oauth2_http_request_init(_log); oauth2_http_request_scheme_set(_log, r, "http"); rc = oauth2_http_request_port_set(_log, r, 80); oauth2_http_request_header_set(_log, r, "Host", "internal"); port = oauth2_http_request_port_get(_log, r); ck_assert_ptr_eq(port, NULL); oauth2_http_request_free(_log, r); // get native port 80 overriding provided scheme port = NULL; r = oauth2_http_request_init(_log); rc = oauth2_http_request_port_set(_log, r, 80); oauth2_http_request_scheme_set(_log, r, "https"); oauth2_http_request_header_set(_log, r, "Host", "internal"); port = oauth2_http_request_port_get(_log, r); ck_assert_str_eq(port, "80"); oauth2_mem_free(port); oauth2_http_request_free(_log, r); // get native port 8080 overriding provided scheme port = NULL; r = oauth2_http_request_init(_log); rc = oauth2_http_request_port_set(_log, r, 8080); oauth2_http_request_scheme_set(_log, r, "https"); oauth2_http_request_header_set(_log, r, "Host", "internal"); port = oauth2_http_request_port_get(_log, r); ck_assert_str_eq(port, "8080"); oauth2_mem_free(port); oauth2_http_request_free(_log, r); } END_TEST START_TEST(test_request_header) { const char *value = NULL; oauth2_http_request_t *r = NULL; oauth2_mem_alloc_fn_t alloc_save; bool rc; // set a bunch of headers and retrieve one of them r = oauth2_http_request_init(_log); rc = oauth2_http_request_header_set(_log, r, "One", "1"); ck_assert_int_eq(rc, true); rc = oauth2_http_request_header_set(_log, r, "Two", "2"); ck_assert_int_eq(rc, true); rc = oauth2_http_request_header_set(_log, r, "Three", "3"); ck_assert_int_eq(rc, true); value = oauth2_http_request_header_get(_log, r, "Two"); ck_assert_str_eq(value, "2"); oauth2_http_request_free(_log, r); // retrieve null header from request r = oauth2_http_request_init(_log); value = oauth2_http_request_header_get(_log, r, NULL); ck_assert_ptr_eq(value, NULL); oauth2_http_request_free(_log, r); // retrieve from null request r = NULL; value = oauth2_http_request_header_get(_log, r, "Two"); ck_assert_ptr_eq(value, NULL); // retrieve null header from null request r = NULL; value = oauth2_http_request_header_get(_log, r, NULL); ck_assert_ptr_eq(value, NULL); // set header using failing memory allocation r = NULL; r = oauth2_http_request_init(_log); alloc_save = oauth2_mem_get_alloc(); oauth2_mem_set_alloc_funcs(faulty_alloc, oauth2_mem_get_realloc(), oauth2_mem_get_dealloc()); rc = oauth2_http_request_header_set(_log, r, "One", "1"); ck_assert_int_eq(rc, false); oauth2_http_request_free(_log, r); // create request using failing memory allocation r = oauth2_http_request_init(_log); ck_assert_ptr_eq(r, NULL); // reset the memory allocator function to their defaults oauth2_mem_set_alloc_funcs(alloc_save, oauth2_mem_get_realloc(), oauth2_mem_get_dealloc()); } END_TEST START_TEST(test_url_base) { char *base = NULL; oauth2_http_request_t *r = NULL; // non-initialized base = NULL; r = oauth2_http_request_init(_log); base = oauth2_http_request_url_base_get(_log, r); ck_assert_ptr_eq(base, NULL); oauth2_http_request_free(_log, r); // only hostname initialized, defaults to https base = NULL; r = oauth2_http_request_init(_log); oauth2_http_request_hostname_set(_log, r, "internal"); base = oauth2_http_request_url_base_get(_log, r); ck_assert_str_eq(base, "https://internal"); oauth2_mem_free(base); oauth2_http_request_free(_log, r); // hostname and port initialized, scheme defaults to https base = NULL; r = oauth2_http_request_init(_log); oauth2_http_request_hostname_set(_log, r, "internal"); oauth2_http_request_port_set(_log, r, 8080); base = oauth2_http_request_url_base_get(_log, r); ck_assert_str_eq(base, "https://internal:8080"); oauth2_mem_free(base); oauth2_http_request_free(_log, r); // X-Forwarded-Host with port provided base = NULL; r = oauth2_http_request_init(_log); oauth2_http_request_hostname_set(_log, r, "internal"); oauth2_http_request_port_set(_log, r, 8080); oauth2_http_request_header_set(_log, r, "X-Forwarded-Host", "external:9000"); base = oauth2_http_request_url_base_get(_log, r); ck_assert_str_eq(base, "https://external:9000"); oauth2_mem_free(base); oauth2_http_request_free(_log, r); // X-Forwarded-Proto and X-Forwarded-Host provided base = NULL; r = oauth2_http_request_init(_log); oauth2_http_request_hostname_set(_log, r, "internal"); oauth2_http_request_port_set(_log, r, 8080); oauth2_http_request_header_set(_log, r, "X-Forwarded-Proto", "http"); oauth2_http_request_header_set(_log, r, "X-Forwarded-Host", "external:9000"); base = oauth2_http_request_url_base_get(_log, r); ck_assert_str_eq(base, "http://external:9000"); oauth2_mem_free(base); oauth2_http_request_free(_log, r); // X-Forwarded-Proto, X-Forwarded-Host and X-Forwarded-Port provided base = NULL; r = oauth2_http_request_init(_log); oauth2_http_request_hostname_set(_log, r, "internal"); oauth2_http_request_port_set(_log, r, 8080); oauth2_http_request_header_set(_log, r, "X-Forwarded-Proto", "http"); oauth2_http_request_header_set(_log, r, "X-Forwarded-Host", "external:9000"); oauth2_http_request_header_set(_log, r, "X-Forwarded-Port", "8000"); base = oauth2_http_request_url_base_get(_log, r); ck_assert_str_eq(base, "http://external:8000"); oauth2_mem_free(base); oauth2_http_request_free(_log, r); } END_TEST START_TEST(test_url_get) { char *url = NULL; oauth2_http_request_t *r = NULL; // mostly test backwards compatibility url = NULL; r = oauth2_http_request_init(_log); oauth2_http_request_header_set(_log, r, "Host", "www.example.com"); url = oauth2_http_request_url_get(_log, r); ck_assert_str_eq(url, "https://www.example.com"); oauth2_mem_free(url); oauth2_http_request_header_set(_log, r, "X-Forwarded-Host", "www.outer.com"); url = oauth2_http_request_url_get(_log, r); ck_assert_str_eq(url, "https://www.outer.com"); oauth2_mem_free(url); oauth2_http_request_header_set(_log, r, "X-Forwarded-Host", "www.outer.com:654"); url = oauth2_http_request_url_get(_log, r); ck_assert_str_eq(url, "https://www.outer.com:654"); oauth2_mem_free(url); oauth2_http_request_header_set(_log, r, "X-Forwarded-Port", "321"); url = oauth2_http_request_url_get(_log, r); ck_assert_str_eq(url, "https://www.outer.com:321"); oauth2_mem_free(url); oauth2_http_request_header_set(_log, r, "X-Forwarded-Proto", "http"); url = oauth2_http_request_url_get(_log, r); ck_assert_str_eq(url, "http://www.outer.com:321"); oauth2_mem_free(url); oauth2_http_request_header_set(_log, r, "X-Forwarded-Proto", "https, http"); url = oauth2_http_request_url_get(_log, r); ck_assert_str_eq(url, "https://www.outer.com:321"); oauth2_mem_free(url); // add a space after the comma... oauth2_http_request_header_set(_log, r, "X-Forwarded-Proto", "https , http"); url = oauth2_http_request_url_get(_log, r); ck_assert_str_eq(url, "https://www.outer.com:321"); oauth2_mem_free(url); oauth2_http_request_header_unset(_log, r, "X-Forwarded-Host"); oauth2_http_request_header_unset(_log, r, "X-Forwarded-Port"); url = oauth2_http_request_url_get(_log, r); ck_assert_str_eq(url, "https://www.example.com"); oauth2_mem_free(url); // test deleting first header oauth2_http_request_header_set(_log, r, "Host", NULL); url = oauth2_http_request_url_get(_log, r); ck_assert_ptr_eq(url, NULL); oauth2_http_request_free(_log, r); } END_TEST START_TEST(test_query_encode) { oauth2_nv_list_t *args = NULL; char *url = NULL, *enc = NULL; args = oauth2_nv_list_init(_log); enc = oauth2_http_url_query_encode(_log, NULL, args); ck_assert_str_eq(enc, ""); oauth2_mem_free(enc); url = "https://www.example.com"; enc = oauth2_http_url_query_encode(_log, url, args); ck_assert_str_eq(enc, "https://www.example.com"); oauth2_mem_free(enc); url = "https://www.example.com"; oauth2_nv_list_add(_log, args, "two", "TWO TWO"); enc = oauth2_http_url_query_encode(_log, url, args); ck_assert_str_eq(enc, "https://www.example.com?two=TWO%20TWO"); oauth2_mem_free(enc); url = "https://www.example.com?one=ONE"; enc = oauth2_http_url_query_encode(_log, url, args); ck_assert_str_eq(enc, "https://www.example.com?one=ONE&two=TWO%20TWO"); oauth2_mem_free(enc); url = "https://www.example.com"; oauth2_nv_list_add(_log, args, "none", NULL); enc = oauth2_http_url_query_encode(_log, url, args); ck_assert_str_eq(enc, "https://www.example.com?two=TWO%20TWO&none="); oauth2_mem_free(enc); oauth2_nv_list_free(_log, args); } END_TEST START_TEST(test_form_encode) { oauth2_nv_list_t *args = NULL; char *enc = NULL; args = oauth2_nv_list_init(_log); enc = oauth2_http_url_form_encode(_log, args); ck_assert_ptr_eq(enc, NULL); oauth2_nv_list_add(_log, args, "two", "TWO TWO"); enc = oauth2_http_url_form_encode(_log, args); ck_assert_str_eq(enc, "two=TWO%20TWO"); oauth2_mem_free(enc); oauth2_nv_list_add(_log, args, "three", "THREE&THREE"); enc = oauth2_http_url_form_encode(_log, args); ck_assert_str_eq(enc, "two=TWO%20TWO&three=THREE%26THREE"); oauth2_mem_free(enc); oauth2_nv_list_free(_log, args); } END_TEST static char *get_json = "{ \"my\": \"json\" }"; static char *get_json_path = "/my_json"; static char *oauth2_check_http_serve_get(const char *request) { if (strncmp(request, get_json_path, strlen(get_json_path)) == 0) { return oauth2_strdup(get_json); } return oauth2_strdup("problem"); } static char *post_json = "{ \"form\": \"post\" }"; static char *post_form_json_path = "/post_json"; static char *oauth2_check_http_serve_post(const char *request) { if (strncmp(request, post_form_json_path, strlen(post_form_json_path)) == 0) { return oauth2_strdup(post_json); } return oauth2_strdup("problem"); } START_TEST(test_http_get) { bool rc; char *response = NULL, *url = NULL; oauth2_nv_list_t *params = oauth2_nv_list_init(_log); oauth2_http_call_ctx_t *ctx = oauth2_http_call_ctx_init(_log); url = oauth2_stradd(NULL, oauth2_check_http_base_url(), get_json_path, NULL); rc = oauth2_http_get(_log, url, NULL, NULL, NULL, NULL); ck_assert_int_eq(rc, false); rc = oauth2_http_get(_log, url, NULL, NULL, &response, NULL); ck_assert_int_eq(rc, true); ck_assert_str_eq(response, get_json); oauth2_mem_free(response); oauth2_http_call_ctx_basic_auth_set(_log, ctx, "hans:ja", "my secret", true); oauth2_http_call_ctx_cookie_add(_log, ctx, "mycookie", "mycvalue"); oauth2_http_call_ctx_cookie_add(_log, ctx, "othercookie", "my2ndvalue"); oauth2_http_call_ctx_hdr_add(_log, ctx, "SM_SESSION", "something"); oauth2_nv_list_add(_log, params, "jan", "piet"); rc = oauth2_http_get(_log, url, params, ctx, &response, NULL); ck_assert_int_eq(rc, true); ck_assert_str_eq(response, get_json); oauth2_mem_free(response); oauth2_nv_list_free(_log, params); oauth2_http_call_ctx_free(_log, ctx); oauth2_mem_free(url); } END_TEST START_TEST(test_http_post_form) { bool rc; char *response = NULL, *url = NULL; oauth2_nv_list_t *params = oauth2_nv_list_init(_log); url = oauth2_stradd(NULL, oauth2_check_http_base_url(), post_form_json_path, NULL); oauth2_nv_list_add(_log, params, "jan", "piet"); rc = oauth2_http_post_form(_log, url, params, NULL, &response, NULL); ck_assert_int_eq(rc, true); ck_assert_str_eq(response, post_json); oauth2_mem_free(response); oauth2_nv_list_free(_log, params); oauth2_mem_free(url); } END_TEST START_TEST(test_cookies) { bool rc = false; oauth2_http_request_t *r = NULL; char *rv = NULL; const char *rvv = NULL; oauth2_nv_list_t *params = NULL; params = NULL; rc = oauth2_parse_form_encoded_params( _log, "jan=piet&klaas=vaak&hans=zandbelt", ¶ms); ck_assert_int_eq(rc, true); ck_assert_ptr_ne(params, NULL); oauth2_nv_list_free(_log, params); r = oauth2_http_request_init(_log); oauth2_http_request_header_set(_log, r, "Host", "www.example.com"); oauth2_http_request_header_set(_log, r, "Cookie", "jan=piet; klaas=vaak; hans=zandbelt"); rv = oauth2_http_request_cookie_get(_log, r, "klaas", false); ck_assert_ptr_ne(rv, NULL); ck_assert_str_eq(rv, "vaak"); oauth2_mem_free(rv); rv = oauth2_http_request_cookie_get(_log, r, "klaas", true); ck_assert_ptr_ne(rv, NULL); ck_assert_str_eq(rv, "vaak"); oauth2_mem_free(rv); rv = oauth2_http_request_cookie_get(_log, r, "klaas", true); ck_assert_ptr_eq(rv, NULL); rvv = oauth2_http_request_header_get(_log, r, "Cookie"); ck_assert_ptr_ne(rvv, NULL); ck_assert_str_eq(rvv, "jan=piet; hans=zandbelt"); oauth2_http_request_free(_log, r); } END_TEST START_TEST(test_auth) { bool rc = false; oauth2_http_call_ctx_t *ctx = oauth2_http_call_ctx_init(_log); rc = oauth2_http_auth_client_cert(_log, "cert.pem", "key.pem", ctx); ck_assert_int_eq(rc, true); rc = oauth2_http_auth_basic(_log, "myusername", "mypassword", ctx); ck_assert_int_eq(rc, true); oauth2_http_call_ctx_free(_log, ctx); } END_TEST START_TEST(test_xml_http_request) { bool rc = false; oauth2_http_request_t *r = NULL; r = oauth2_http_request_init(_log); rc = oauth2_http_request_header_set(_log, r, "Accept", "text/html"); ck_assert_int_eq(rc, true); rc = oauth2_http_request_is_xml_http_request(_log, r); ck_assert_int_eq(rc, false); rc = oauth2_http_request_header_set(_log, r, "Accept", "application/json"); ck_assert_int_eq(rc, true); rc = oauth2_http_request_is_xml_http_request(_log, r); ck_assert_int_eq(rc, true); rc = oauth2_http_request_header_set(_log, r, "Accept", "*/*"); ck_assert_int_eq(rc, true); rc = oauth2_http_request_is_xml_http_request(_log, r); ck_assert_int_eq(rc, false); rc = oauth2_http_request_header_set(_log, r, "X-Requested-With", "XMLHttpRequest"); ck_assert_int_eq(rc, true); rc = oauth2_http_request_header_set( _log, r, "Accept", "text/html, application/xhtml+xml, application/xml;q=0.9, " "image/webp, */*;q=0.8"); ck_assert_int_eq(rc, true); rc = oauth2_http_request_is_xml_http_request(_log, r); ck_assert_int_eq(rc, true); oauth2_http_request_free(_log, r); } END_TEST Suite *oauth2_check_http_suite() { Suite *s = suite_create("http"); TCase *c = tcase_create("core"); liboauth2_check_register_http_callbacks(oauth2_check_http_base_path(), oauth2_check_http_serve_get, oauth2_check_http_serve_post); tcase_add_checked_fixture(c, setup, teardown); tcase_add_test(c, test_request_scheme); tcase_add_test(c, test_request_hostname); tcase_add_test(c, test_request_port); tcase_add_test(c, test_url_base); tcase_add_test(c, test_url_get); tcase_add_test(c, test_request_header); tcase_add_test(c, test_query_encode); tcase_add_test(c, test_form_encode); tcase_add_test(c, test_http_get); tcase_add_test(c, test_http_post_form); tcase_add_test(c, test_cookies); tcase_add_test(c, test_auth); tcase_add_test(c, test_xml_http_request); suite_add_tcase(s, c); return s; } liboauth2-2.1.0/test/check_ipc.c000066400000000000000000000055031475305260400164470ustar00rootroot00000000000000/*************************************************************************** * * Copyright (C) 2018-2025 - ZmartZone Holding BV * * 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. * * @Author: Hans Zandbelt - hans.zandbelt@openidc.com * **************************************************************************/ #include "check_liboauth2.h" #include "oauth2/ipc.h" #include "oauth2/mem.h" #include #include static oauth2_log_t *_log = 0; static void setup(void) { _log = oauth2_init(OAUTH2_LOG_TRACE1, 0); } static void teardown(void) { oauth2_shutdown(_log); } START_TEST(test_sema) { bool rc = false; oauth2_ipc_sema_t *s = NULL; s = oauth2_ipc_sema_init(_log); ck_assert_ptr_ne(s, NULL); rc = oauth2_ipc_sema_post_config(_log, s); ck_assert_int_eq(rc, true); rc = oauth2_ipc_sema_post(_log, s); ck_assert_int_eq(rc, true); rc = oauth2_ipc_sema_post(_log, s); ck_assert_int_eq(rc, true); rc = oauth2_ipc_sema_wait(_log, s); ck_assert_int_eq(rc, true); rc = oauth2_ipc_sema_wait(_log, s); ck_assert_int_eq(rc, true); // TODO: check for timeout // rc = oauth2_ipc_sema_wait(_log, s); // ck_assert_int_eq(rc, true); oauth2_ipc_sema_free(_log, s); s = NULL; } END_TEST START_TEST(test_mutex) { bool rc = false; oauth2_ipc_mutex_t *m = NULL; m = oauth2_ipc_mutex_init(_log); ck_assert_ptr_ne(m, NULL); rc = oauth2_ipc_mutex_post_config(_log, m); ck_assert_int_eq(rc, true); rc = oauth2_ipc_mutex_lock(_log, m); ck_assert_int_eq(rc, true); // TODO: check timeout // rc = oauth2_ipc_mutex_lock(_log, m); // ck_assert_int_eq(rc, true); rc = oauth2_ipc_mutex_unlock(_log, m); ck_assert_int_eq(rc, true); oauth2_ipc_mutex_free(_log, m); m = NULL; } END_TEST START_TEST(test_shm) { bool rc = false; oauth2_ipc_shm_t *shm = NULL; void *ptr = NULL; shm = oauth2_ipc_shm_init(_log, 256); ck_assert_ptr_ne(shm, NULL); rc = oauth2_ipc_shm_post_config(_log, shm); ck_assert_int_eq(rc, true); ptr = oauth2_ipc_shm_get(_log, shm); ck_assert_ptr_ne(ptr, NULL); oauth2_ipc_shm_free(_log, shm); shm = NULL; } END_TEST Suite *oauth2_check_ipc_suite() { Suite *s = suite_create("ipc"); TCase *c = tcase_create("core"); tcase_add_checked_fixture(c, setup, teardown); tcase_add_test(c, test_sema); tcase_add_test(c, test_mutex); tcase_add_test(c, test_shm); suite_add_tcase(s, c); return s; } liboauth2-2.1.0/test/check_jose.c000066400000000000000000000355761475305260400166510ustar00rootroot00000000000000/*************************************************************************** * * Copyright (C) 2018-2025 - ZmartZone Holding BV * * 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. * * @Author: Hans Zandbelt - hans.zandbelt@openidc.com * **************************************************************************/ #include "oauth2/jose.h" #include "oauth2/mem.h" #include "oauth2/util.h" #include #include #include #include "check_liboauth2.h" #include "jose_int.h" static oauth2_log_t *_log = 0; static const char *secret1 = NULL; static const char *secret2 = NULL; static json_t *payload1 = NULL; const char *s_payload1 = NULL; static json_t *payload2 = NULL; const char *s_payload2 = NULL; static const char *serialized_hdr = NULL; static const char *encrypted1 = NULL; static const char *encrypted1_corrupt_tag = NULL; static const char *encrypted1_signed2 = NULL; static const char *encrypted1_signed2_corrupt_sig = NULL; static const char *encrypted1_signed2_corrupt_hdr = NULL; static const char *encrypted1_signed2_corrupt_payload = NULL; OAUTH2_CHECK_HTTP_PATHS void oauth2_check_jose_cleanup() { oauth2_check_http_base_free(); } static void setup(void) { _log = oauth2_init(OAUTH2_LOG_TRACE1, 0); secret1 = "12345"; secret2 = "abcde"; s_payload1 = "{\"iss\":\"https://example.org\"}"; json_error_t err; payload1 = json_loads(s_payload1, 0, &err); s_payload2 = "{\"aud\":\"https://another.org\"}"; payload2 = json_loads(s_payload2, 0, &err); serialized_hdr = "eyJhbGciOiAiZGlyIiwgImVuYyI6ICJBMjU2R0NNIn0.."; encrypted1 = "eyJhbGciOiAiZGlyIiwgImVuYyI6ICJBMjU2R0NNIn0..-" "jvbGVBwu8GOvVwF.3lPSed2UdIu-" "obtRNcMaCP7WUYETLwkXD2BZbx0sjOUiRNbHXQmYm7c0B4Mp2f2m-C-" "hAzBdlPGDjeNP1PiFZiWFtDRGuskW4qGrUoFCSWZx5vAyfOFjuRN2ydst7" "geoD32_8zY-pYyVzQ.HWN2Hq8sLnFWT_XKU20Mpw"; // encrypted_wrong_payload = encrypted1_corrupt_tag = "eyJhbGciOiAiZGlyIiwgImVuYyI6ICJBMjU2R0NNIn0..-" "jvbGVBwu8GOvVwF.3lPSed2UdIu-" "obtRNcMaCP7WUYETLwkXD2BZbx0sjOUiRNbHXQmYm7c0B4Mp2f2m-C-" "hAzBdlPGDjeNP1PiFZiWFtDRGuskW4qGrUoFCSWZx5vAyfOFjuRN2ydst7" "geoD32_8zY-pYyVzQ.HWN2Hq8sLnFWT_XKU20MpW"; encrypted1_signed2 = "eyJhbGciOiAiZGlyIiwgImVuYyI6ICJBMjU2R0NNIn0.." "4lW9e06wrl3DMuNs.-" "wmcLlYUPuGGWKPIimR3j66Y15yarBpaF75g07Q23epRmYO7NL" "Gvwt7tiGYJGxqh_6f9SHJDK7wMYR4GsP6W4AZWZOurCxY_" "PdwZWnrPit11s7zi77fFEqz3b3g2scYbZd9PfN-KJ4Ol0g." "oH5VdKxGZanSP0H0-XuGtg"; encrypted1_signed2_corrupt_sig = "eyJhbGciOiAiZGlyIiwgImVuYyI6ICJBMjU2R0NNIn0..f-t6lZhrSTUwQUhe." "1fIV8PJSgpoOgiR-0yVlkzzcgfpEghVnevJuYbO06DA9x-" "X47wzpoIPIX941fXmSFIItQDiF1t9lVxfIhnJ46JYuYlOCwkn_" "6vUIQKpCbGbUNwvrPo8aF8g75T8FMKmeqjjawmby2nwawQ." "Sm6gRVXw7RhI7NI2hfIglg"; encrypted1_signed2_corrupt_hdr = "eyJhbGciOiAiZGlyIiwgImVuYyI6ICJBMjU2R0NNIn0..0yJnmp9r_YW8am-4." "Qe6GoANyIV3ET_bb4Npr-QguqGzzTNrm_" "iWvx5iYchUr8HL1Qv8jWQq8FOeuNHhbdNW0huKhH_rjfNA_" "bNHN6nUGmHmeSkhWMKPsf2JnrDZIdcCN7uXVxhKeMXe5TGZ6sY5AdF7lX8Ufcg." "auXeS-tKPnxFj-iUYULfOQ"; encrypted1_signed2_corrupt_payload = "eyJhbGciOiAiZGlyIiwgImVuYyI6ICJBMjU2R0NNIn0..eeCTIS3jEdKO45ao.PM_" "PHuxN-zPZ9gNVLgpuXZl5dXCTCTQedYCj_" "QBoOrKhg93A1QOrj1NcHt0NSspDkJ9PpVnJG6T1nzKPIZsdWHHjse33xqs4HNbDW2y" "bBMuZFQ_S9eEDfEVusEcls0cg0pcrtemeJ8fb.NDWKfDRNgbl5anMH1NCbIw"; } static void teardown(void) { json_decref(payload1); json_decref(payload2); oauth2_shutdown(_log); } static void *faulty_alloc(size_t amt) { return NULL; } START_TEST(test_hash_bytes) { bool rc; const char *src = "abc"; unsigned char src_hash[32] = { 0xba, 0x78, 0x16, 0xbf, 0x8f, 0x01, 0xcf, 0xea, 0x41, 0x41, 0x40, 0xde, 0x5d, 0xae, 0x22, 0x23, 0xb0, 0x03, 0x61, 0xa3, 0x96, 0x17, 0x7a, 0x9c, 0xb4, 0x10, 0xff, 0x61, 0xf2, 0x00, 0x15, 0xad}; unsigned int dst_len; unsigned char *dst; oauth2_mem_alloc_fn_t alloc_save; dst = NULL; dst_len = 0; rc = oauth2_jose_hash_bytes(_log, OAUTH2_JOSE_OPENSSL_ALG_SHA256, (const unsigned char *)src, strlen(src), &dst, &dst_len); ck_assert_int_eq(rc, true); ck_assert_int_eq(sizeof(src_hash), dst_len); oauth2_mem_free(dst); dst = NULL; dst_len = 0; alloc_save = oauth2_mem_get_alloc(); oauth2_mem_set_alloc_funcs(faulty_alloc, oauth2_mem_get_realloc(), oauth2_mem_get_dealloc()); rc = oauth2_jose_hash_bytes(_log, OAUTH2_JOSE_OPENSSL_ALG_SHA256, (const unsigned char *)src, strlen(src), &dst, &dst_len); ck_assert_int_eq(rc, false); ck_assert_ptr_eq(dst, NULL); ck_assert_int_eq(dst_len, 0); dst = (unsigned char *)oauth2_strdup("test-faulty-alloc"); ck_assert_ptr_eq(dst, NULL); oauth2_mem_set_alloc_funcs(alloc_save, oauth2_mem_get_realloc(), oauth2_mem_get_dealloc()); dst = NULL; dst_len = 0; rc = oauth2_jose_hash_bytes(_log, "non-existing-digest", (const unsigned char *)src, strlen(src), &dst, &dst_len); ck_assert_int_eq(rc, false); ck_assert_ptr_eq(dst, NULL); ck_assert_int_eq(dst_len, 0); rc = oauth2_jose_hash_bytes(_log, OAUTH2_JOSE_OPENSSL_ALG_SHA256, NULL, 0, &dst, &dst_len); ck_assert_int_eq(rc, false); ck_assert_ptr_eq(dst, NULL); ck_assert_int_eq(dst_len, 0); rc = oauth2_jose_hash_bytes(_log, OAUTH2_JOSE_OPENSSL_ALG_SHA256, NULL, 0, NULL, NULL); ck_assert_int_eq(rc, false); } END_TEST START_TEST(test_jwk_create_symmetric) { bool rc; const char *client_secret = "abc"; oauth2_jose_jwk_t *jwk = NULL; rc = oauth2_jose_jwk_create_symmetric(_log, client_secret, NULL, &jwk); ck_assert_int_eq(rc, true); oauth2_jose_jwk_release(jwk); rc = oauth2_jose_jwk_create_symmetric( _log, client_secret, OAUTH2_JOSE_OPENSSL_ALG_SHA256, &jwk); ck_assert_int_eq(rc, true); oauth2_jose_jwk_release(jwk); jwk = NULL; rc = oauth2_jose_jwk_create_symmetric( _log, NULL, OAUTH2_JOSE_OPENSSL_ALG_SHA256, &jwk); ck_assert_int_eq(rc, false); ck_assert_ptr_eq(jwk, NULL); rc = oauth2_jose_jwk_create_symmetric(_log, NULL, NULL, &jwk); ck_assert_int_eq(rc, false); ck_assert_ptr_eq(jwk, NULL); rc = oauth2_jose_jwk_create_symmetric(_log, client_secret, "bogus-algorithm", &jwk); ck_assert_int_eq(rc, false); ck_assert_ptr_eq(jwk, NULL); } END_TEST START_TEST(test_jwt_encrypt) { bool rc; char *cser = NULL; rc = oauth2_jose_jwt_encrypt(_log, secret1, payload1, &cser); ck_assert_int_eq(rc, true); // TODO: this fails intermittently in docker-valgrind (only on first // runs...) !? ck_assert(strncmp(cser, serialized_hdr, strlen(serialized_hdr)) == 0); oauth2_mem_free(cser); cser = NULL; rc = oauth2_jose_jwt_encrypt(_log, secret2, payload2, &cser); ck_assert_int_eq(rc, true); ck_assert(strncmp(cser, serialized_hdr, strlen(serialized_hdr)) == 0); oauth2_mem_free(cser); cser = NULL; rc = oauth2_jose_jwt_encrypt(_log, NULL, payload1, &cser); ck_assert_int_eq(rc, false); ck_assert_ptr_eq(cser, NULL); rc = oauth2_jose_jwt_encrypt(_log, secret1, NULL, &cser); ck_assert_int_eq(rc, false); ck_assert_ptr_eq(cser, NULL); rc = oauth2_jose_jwt_encrypt(_log, secret1, payload1, NULL); ck_assert_int_eq(rc, false); } END_TEST START_TEST(test_jwt_decrypt) { bool rc; char *cser = NULL; json_t *result = NULL; rc = oauth2_jose_jwt_decrypt(_log, secret1, encrypted1, &result); ck_assert_int_eq(rc, true); ck_assert_ptr_ne(result, NULL); cser = json_dumps(payload1, JSON_PRESERVE_ORDER | JSON_COMPACT); ck_assert_str_eq(cser, s_payload1); oauth2_mem_free(cser); json_decref(result); result = NULL; rc = oauth2_jose_jwt_decrypt(_log, secret1, encrypted1_corrupt_tag, &result); ck_assert_int_eq(rc, false); ck_assert_ptr_eq(result, NULL); rc = oauth2_jose_jwt_decrypt(_log, secret1, encrypted1_signed2, &result); ck_assert_int_eq(rc, false); ck_assert_ptr_eq(result, NULL); rc = oauth2_jose_jwt_decrypt(_log, secret1, encrypted1_signed2_corrupt_sig, &result); ck_assert_int_eq(rc, false); ck_assert_ptr_eq(result, NULL); rc = oauth2_jose_jwt_decrypt(_log, secret1, encrypted1_signed2_corrupt_hdr, &result); ck_assert_int_eq(rc, false); ck_assert_ptr_eq(result, NULL); rc = oauth2_jose_jwt_decrypt( _log, secret1, encrypted1_signed2_corrupt_payload, &result); ck_assert_int_eq(rc, false); ck_assert_ptr_eq(result, NULL); rc = oauth2_jose_jwt_decrypt(_log, secret2, encrypted1, &result); ck_assert_int_eq(rc, false); ck_assert_ptr_eq(result, NULL); rc = oauth2_jose_jwt_decrypt(_log, NULL, encrypted1, &result); ck_assert_int_eq(rc, false); ck_assert_ptr_eq(result, NULL); rc = oauth2_jose_jwt_decrypt(_log, secret1, NULL, &result); ck_assert_int_eq(rc, false); ck_assert_ptr_eq(result, NULL); rc = oauth2_jose_jwt_decrypt(_log, secret1, encrypted1, NULL); ck_assert_int_eq(rc, false); } END_TEST static char *get_jwks_uri_json = "{\"keys\":[{\"kty\":\"RSA\",\"kid\":\"k1\",\"use\":\"sig\",\"n\":" "\"hKvkosOyK33gznaRCNgakMLE2GHS5_7K34oqZRsAWC-7aC420eJNL2z_" "8Z7ouWXpJNZ2YHQcqxPe4UZGtiDiFYLdDbQPrCDiTpuRYybe1UmZJ3Kk5fBx9yXKU0zbdSKYPE" "eq1w5Fi7rt46YkZ6qwv3Yixo7eTxbglezJOx_YcS5sfXxcwBU1nYbGU_" "MgrBXAfy1Hea5tcUSPot-BTMcuj_doHLT_sEm4AZwaZiLhMiqfI-" "J6Gv5Hg6aBTXpYv50DEdcoZzkabMHxjHICS9w2FGWAzMt_" "AvW4ISlbAxlBroXhTEXC6GIJwoDTskuPlCO4CVa3axh0s1D49JFJoBYasw\",\"e\":" "\"AQAB\",\"x5c\":[" "\"MIIDSjCCAjKgAwIBAgIGAVvvqweOMA0GCSqGSIb3DQEBCwUAMGYxCzAJBgNVBAYTAlVTMQsw" "CQYDVQQIEwJDTzEPMA0GA1UEBxMGRGVudmVyMQ0wCwYDVQQKEwRQaW5nMQwwCgYDVQQLEwNEZX" "YxHDAaBgNVBAMTE0NvbmZpZyBTaWduaW5nIENlcnQwHhcNMTcwNTEwMDAwMzM0WhcNMzIwNTA2" "MDAwMzM0WjBmMQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ08xDzANBgNVBAcTBkRlbnZlcjENMA" "sGA1UEChMEUGluZzEMMAoGA1UECxMDRGV2MRwwGgYDVQQDExNDb25maWcgU2lnbmluZyBDZXJ0" "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAhKvkosOyK33gznaRCNgakMLE2GHS5/" "7K34oqZRsAWC+7aC420eJNL2z/" "8Z7ouWXpJNZ2YHQcqxPe4UZGtiDiFYLdDbQPrCDiTpuRYybe1UmZJ3Kk5fBx9yXKU0zbdSKYPE" "eq1w5Fi7rt46YkZ6qwv3Yixo7eTxbglezJOx/YcS5sfXxcwBU1nYbGU/" "MgrBXAfy1Hea5tcUSPot+BTMcuj/doHLT/" "sEm4AZwaZiLhMiqfI+J6Gv5Hg6aBTXpYv50DEdcoZzkabMHxjHICS9w2FGWAzMt/" "AvW4ISlbAxlBroXhTEXC6GIJwoDTskuPlCO4CVa3axh0s1D49JFJoBYaswIDAQABMA0GCSqGSI" "b3DQEBCwUAA4IBAQBCYXguSAbrwHw9g+UXuWzgj6b3jN+" "OAAQUuvpnY0KrNBentCgC3ualfgieB2c0cyLXBFTNDzMCVb2eB+f66/" "ZRQC8W6DTc5aCE3nTH8tSzbMLwwlMnQelkQMF4LZ9NZmrubVT2IYZ+" "hzwHhvVOHSQ6kqjQHXWcZ30VEbe6EV47LC1M78v+UX3CP+" "lOcovbyHl9J4VqQLKlxajr0QAqHnETkr84fI54RE2kSkWVuWp36VNY39Sl0/" "yEmnouFbV0UBMZck7gMNseCtwSYdkwls/LDFEp9D4rF1gHRlSBRskNc/" "NaasTSX4JpNf+xakm7yePtuWyAY/" "fQ7ETSPMJdVEaL\"],\"x5t\":\"31YdH_bv2Hlg89wmwBphxJZaK64\"}]}"; static char *jwks_uri_path = "/jwks_uri"; static char *oauth2_check_jose_serve_get(const char *request) { if (strncmp(request, jwks_uri_path, strlen(jwks_uri_path)) == 0) { return oauth2_strdup(get_jwks_uri_json); } return oauth2_strdup("problem"); } START_TEST(test_jwks_resolve_uri) { oauth2_cfg_token_verify_t *verify = NULL; oauth2_jose_jwk_list_t *list = NULL; const char *rv = NULL; bool refresh = false; char *url = NULL; oauth2_jose_jwt_verify_ctx_t *ptr = NULL; url = oauth2_stradd(NULL, oauth2_check_http_base_url(), jwks_uri_path, NULL); rv = oauth2_cfg_token_verify_add_options(_log, &verify, "jwks_uri", url, "ssl_verify=false"); ck_assert_ptr_eq(rv, NULL); ptr = (oauth2_jose_jwt_verify_ctx_t *)verify->ctx->ptr; list = ptr->jwks_provider->resolve(_log, ptr->jwks_provider, &refresh, NULL); ck_assert_ptr_ne(list, NULL); oauth2_jose_jwk_list_free(_log, list); oauth2_mem_free(url); oauth2_cfg_token_verify_free(_log, verify); } END_TEST START_TEST(test_jwk_resolve_plain) { oauth2_cfg_token_verify_t *verify = NULL; oauth2_jose_jwk_list_t *list = NULL; const char *rv = NULL; bool refresh = false; oauth2_jose_jwt_verify_ctx_t *ptr = NULL; rv = oauth2_cfg_token_verify_add_options(_log, &verify, "plain", "mysecret", "kid=mykid"); ck_assert_ptr_eq(rv, NULL); ptr = (oauth2_jose_jwt_verify_ctx_t *)verify->ctx->ptr; list = ptr->jwks_provider->resolve(_log, ptr->jwks_provider, &refresh, NULL); ck_assert_ptr_ne(list, NULL); oauth2_jose_jwk_list_free(_log, list); oauth2_cfg_token_verify_free(_log, verify); } END_TEST START_TEST(test_jwt_verify) { bool rc = false; json_t *json_payload = NULL; char *s_payload = NULL; char *jwt = NULL; oauth2_jose_jwk_t *jwk = NULL; char *rv = NULL; oauth2_cfg_token_verify_t *verify = NULL; rc = oauth2_jose_jwt_verify(_log, NULL, jwt, &json_payload, &s_payload); ck_assert_int_eq(rc, false); ck_assert_ptr_eq(json_payload, NULL); ck_assert_ptr_eq(s_payload, NULL); rc = oauth2_jose_jwk_create_symmetric(_log, "my_good_secret", NULL, &jwk); ck_assert_int_eq(rc, true); jwt = oauth2_jwt_create(_log, jwk->jwk, CJOSE_HDR_ALG_HS256, "my_iss", "my_sub", "my_client_id", "my_aud", 60, true, true, NULL); ck_assert_ptr_ne(jwt, NULL); oauth2_jose_jwk_release(jwk); rv = oauth2_cfg_token_verify_add_options( _log, &verify, "plain", "my_wrong_secret", "kid=my_wrong_kid1"); ck_assert_ptr_eq(rv, NULL); rc = oauth2_token_verify(_log, NULL, verify, jwt, &json_payload); ck_assert_int_eq(rc, false); oauth2_cfg_token_verify_free(_log, verify); verify = NULL; rv = oauth2_cfg_token_verify_add_options( _log, &verify, "plain", "my_good_secret", "kid=my_good_kid&expiry=1&verify.iat=required"); ck_assert_ptr_eq(rv, NULL); rc = oauth2_token_verify(_log, NULL, verify, jwt, &json_payload); ck_assert_int_eq(rc, true); ck_assert_ptr_ne(json_payload, NULL); json_decref(json_payload); oauth2_cfg_token_verify_free(_log, verify); verify = NULL; sleep(3); rv = oauth2_cfg_token_verify_add_options( _log, &verify, "plain", "my_good_secret", "kid=my_good_kid&expiry=1&verify.iat=required&verify.iat.slack_" "before=2"); ck_assert_ptr_eq(rv, NULL); rc = oauth2_token_verify(_log, NULL, verify, jwt, &json_payload); ck_assert_int_eq(rc, false); oauth2_cfg_token_verify_free(_log, verify); verify = NULL; oauth2_mem_free(jwt); } END_TEST Suite *oauth2_check_jose_suite() { Suite *s = suite_create("jose"); TCase *c = tcase_create("core"); liboauth2_check_register_http_callbacks( oauth2_check_http_base_path(), oauth2_check_jose_serve_get, NULL); tcase_add_checked_fixture(c, setup, teardown); tcase_add_test(c, test_hash_bytes); tcase_add_test(c, test_jwk_create_symmetric); tcase_add_test(c, test_jwt_encrypt); tcase_add_test(c, test_jwt_decrypt); tcase_add_test(c, test_jwks_resolve_uri); tcase_add_test(c, test_jwk_resolve_plain); tcase_add_test(c, test_jwt_verify); tcase_set_timeout(c, 8); suite_add_tcase(s, c); return s; } liboauth2-2.1.0/test/check_jq.c000066400000000000000000000044571475305260400163150ustar00rootroot00000000000000/*************************************************************************** * * Copyright (C) 2018-2025 - ZmartZone Holding BV * * 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. * * @Author: Hans Zandbelt - hans.zandbelt@openidc.com * **************************************************************************/ #include "check_liboauth2.h" #include "oauth2/cfg.h" #include "oauth2/jq.h" #include "oauth2/mem.h" #include static oauth2_log_t *_log = 0; static void setup(void) { _log = oauth2_init(OAUTH2_LOG_TRACE1, 0); } static void teardown(void) { oauth2_shutdown(_log); } START_TEST(test_jq_compile) { bool rc = false; rc = oauth2_jq_filter_compile(_log, ".add + 1", NULL); ck_assert_int_eq(rc, true); rc = oauth2_jq_filter_compile(_log, "bla", NULL); ck_assert_int_eq(rc, false); } START_TEST(test_jq_filter) { bool rc = false; oauth2_cache_t *c = NULL; char *rv = NULL; char *result = NULL; rv = oauth2_cfg_set_cache(_log, NULL, "shm", NULL); ck_assert_ptr_eq(rv, NULL); c = oauth2_cache_obtain(_log, NULL); ck_assert_ptr_ne(c, NULL); rc = oauth2_jq_filter(_log, c, "{\"add\":1}", ".add + 1", &result); ck_assert_int_eq(rc, true); ck_assert_str_eq(result, "2"); oauth2_mem_free(result); rc = oauth2_jq_filter(_log, c, "{\"add\":2}", ".add + 1", &result); ck_assert_int_eq(rc, true); ck_assert_str_eq(result, "3"); oauth2_mem_free(result); // should use cache rc = oauth2_jq_filter(_log, c, "{\"add\":2}", ".add + 1", &result); ck_assert_int_eq(rc, true); ck_assert_str_eq(result, "3"); oauth2_mem_free(result); } END_TEST Suite *oauth2_check_jq_suite() { Suite *s = suite_create("jq"); TCase *c = tcase_create("core"); tcase_add_checked_fixture(c, setup, teardown); tcase_add_test(c, test_jq_compile); tcase_add_test(c, test_jq_filter); suite_add_tcase(s, c); return s; } liboauth2-2.1.0/test/check_liboauth2.c000066400000000000000000000227111475305260400175650ustar00rootroot00000000000000/*************************************************************************** * * Copyright (C) 2018-2025 - ZmartZone Holding BV * * 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. * * @Author: Hans Zandbelt - hans.zandbelt@openidc.com * **************************************************************************/ #include "check_liboauth2.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "../include/oauth2/log.h" #include "../include/oauth2/mem.h" #include "util_int.h" // #include static int http_server_port = 8888; static int http_server_signal_delivered = 0; #define HTTP_SERVER_BUFSIZE 8096 #define HTTP_SERVER_SIGNUM SIGCONT static void http_server_signal_handler(int signum) { http_server_signal_delivered = signum; } static void http_server_error(oauth2_log_t *log, int type, int socket_fd) { ssize_t rc; static char buf[HTTP_SERVER_BUFSIZE + 1]; char *status = "500 Internal Server Error"; if (type == 400) status = "400 Bad Request"; else if (type == 403) status = "403 Forbidden"; else if (type == 404) status = "404 Not Found"; else if (type == 405) status = "405 Method Not Allowed"; else if (type == 406) status = "406 Not Acceptable"; sprintf(buf, "HTTP/1.1 %s\nContent-Length: 0\nConnection: close\n\n", status); rc = write(socket_fd, buf, strlen(buf)); (void)rc; } struct { char *ext; char *filetype; } extensions[] = {{"html", "text/html"}, {"json", "application/json"}, {0, 0}}; typedef struct http_serve_routing_t { char *path; http_serve_callback_get_t *callback_get; http_serve_callback_get_t *callback_post; struct http_serve_routing_t *next; } http_serve_routing_t; static http_serve_routing_t *http_serve_routing_table = NULL; void liboauth2_check_register_http_callbacks( const char *path, http_serve_callback_get_t *cb_get, http_serve_callback_post_t *cb_post) { http_serve_routing_t *ptr = NULL; if (http_serve_routing_table == NULL) { http_serve_routing_table = oauth2_mem_alloc(sizeof(http_serve_routing_t)); ptr = http_serve_routing_table; } else { for (ptr = http_serve_routing_table; ptr->next; ptr = ptr->next) ; ptr->next = oauth2_mem_alloc(sizeof(http_serve_routing_t)); ptr = ptr->next; } ptr->path = oauth2_strdup(path); ptr->callback_get = cb_get; ptr->callback_post = cb_post; ptr->next = NULL; } static void liboauth2_check_cleanup_http_callbacks() { http_serve_routing_t *ptr = NULL; while ((ptr = http_serve_routing_table)) { http_serve_routing_table = http_serve_routing_table->next; oauth2_mem_free(ptr->path); oauth2_mem_free(ptr); } } static void http_server_process(oauth2_log_t *log, int fd, int hit) { int j, file_fd, buflen; long i, ret, len; char *fstr; static char buffer[HTTP_SERVER_BUFSIZE + 1]; static char outbuf[HTTP_SERVER_BUFSIZE + 1]; ssize_t rc; char *response = NULL; http_serve_routing_t *ptr = NULL; ret = read(fd, buffer, HTTP_SERVER_BUFSIZE); if (ret == 0 || ret == -1) { http_server_error(log, 400, fd); return; } if (ret > 0 && ret < HTTP_SERVER_BUFSIZE) buffer[ret] = 0; else buffer[0] = 0; for (i = 0; i < ret; i++) if (buffer[i] == '\r' || buffer[i] == '\n') buffer[i] = '*'; oauth2_debug(log, "request: %s %d", buffer, hit); /* if (strncmp(buffer, "GET ", 4) && strncmp(buffer, "get ", 4)) { http_server_error(log, 405, fd); return; } */ for (i = 4; i < HTTP_SERVER_BUFSIZE; i++) { if (buffer[i] == ' ') { buffer[i] = 0; break; } } for (j = 0; j < i - 1; j++) if (buffer[j] == '.' && buffer[j + 1] == '.') { http_server_error(log, 403, fd); return; } if (!strncmp(&buffer[0], "GET /\0", 6) || !strncmp(&buffer[0], "get /\0", 6)) (void)strcpy(buffer, "GET /index.html"); buflen = strlen(buffer); fstr = (char *)0; for (i = 0; extensions[i].ext != 0; i++) { len = strlen(extensions[i].ext); if (!strncmp(&buffer[buflen - len], extensions[i].ext, len)) { fstr = extensions[i].filetype; break; } } if (fstr == 0) { // http_server_error(log, 406, fd); // return; fstr = "application/json"; } for (ptr = http_serve_routing_table; ptr; ptr = ptr->next) { len = strlen(ptr->path); if (strncmp(&buffer[0], "GET", 3) == 0) { if (strncmp(&buffer[4], ptr->path, len) == 0) { if (ptr->callback_get) response = ptr->callback_get(&buffer[4 + len]); } } if (strncmp(&buffer[0], "POST", 4) == 0) { if (strncmp(&buffer[5], ptr->path, len) == 0) { if (ptr->callback_post) response = ptr->callback_post( &buffer[5 + len]); } } if (response) { sprintf(outbuf, "HTTP/1.1 200\nContent-Length: " "%zu\nConnection: close\n\n", strlen(response)); rc = write(fd, outbuf, strlen(outbuf)); rc = write(fd, response, strlen(response)); (void)rc; oauth2_mem_free(response); return; } } if ((file_fd = open(&buffer[5], O_RDONLY)) == -1) { http_server_error(log, 404, fd); return; } oauth2_debug(log, "SEND: %s, %d", &buffer[5], hit); len = (long)lseek(file_fd, (off_t)0, SEEK_END); (void)lseek(file_fd, (off_t)0, SEEK_SET); (void)sprintf(buffer, "HTTP/1.1 200 OK\nServer: " "libmodauth2/1.0\nContent-Length: " "%ld\nConnection: close\nContent-Type: %s\n\n", len, fstr); oauth2_debug(log, "Header: %s, %d", buffer, hit); rc = write(fd, buffer, strlen(buffer)); (void)rc; while ((ret = read(file_fd, buffer, HTTP_SERVER_BUFSIZE)) > 0) { rc = write(fd, buffer, ret); (void)rc; } } // static sem_t *sema = NULL; pid_t http_server_spawn() { int listenfd, socketfd, hit; static struct sockaddr_in serv_addr; static struct sockaddr_in cli_addr; fd_set read_fd_set; socklen_t length; pid_t pid = fork(); if (pid != 0) { return pid; } struct sigaction action; action.sa_flags = 0; action.sa_handler = http_server_signal_handler; sigemptyset(&action.sa_mask); sigaction(HTTP_SERVER_SIGNUM, &action, NULL); oauth2_log_t *log = oauth2_log_init(OAUTH2_LOG_TRACE1, NULL); if ((listenfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) oauth2_error(log, "socket failed"); serv_addr.sin_family = AF_INET; serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); serv_addr.sin_port = htons(http_server_port); if (bind(listenfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) oauth2_error(log, "bind failed"); if (listen(listenfd, 64) < 0) oauth2_error(log, "listen failed"); FD_ZERO(&read_fd_set); FD_SET(listenfd, &read_fd_set); // sem_post(sema); for (hit = 1;; hit++) { if (select(FD_SETSIZE, &read_fd_set, NULL, NULL, NULL) < 0) { if (http_server_signal_delivered == HTTP_SERVER_SIGNUM) break; oauth2_error(log, "select failed: %s", strerror(errno)); } if (FD_ISSET(listenfd, &read_fd_set)) { length = sizeof(cli_addr); if ((socketfd = accept(listenfd, (struct sockaddr *)&cli_addr, &length)) <= 0) { oauth2_error(log, "accept failed: %s, %d", strerror(errno), socketfd); continue; } if (fork() == 0) { http_server_process(log, socketfd, hit); close(socketfd); oauth2_log_free(log); exit(0); } } } close(listenfd); oauth2_log_free(log); exit(0); } int main(void) { int n_failed; SRunner *sr = srunner_create(suite_create("liboauth2")); // srunner_set_fork_status(sr, CK_NOFORK); srunner_add_suite(sr, oauth2_check_version_suite()); srunner_add_suite(sr, oauth2_check_mem_suite()); srunner_add_suite(sr, oauth2_check_log_suite()); srunner_add_suite(sr, oauth2_check_cfg_suite()); srunner_add_suite(sr, oauth2_check_util_suite()); srunner_add_suite(sr, oauth2_check_ipc_suite()); srunner_add_suite(sr, oauth2_check_cache_suite()); srunner_add_suite(sr, oauth2_check_jose_suite()); srunner_add_suite(sr, oauth2_check_http_suite()); srunner_add_suite(sr, oauth2_check_proto_suite()); srunner_add_suite(sr, oauth2_check_oauth2_suite()); srunner_add_suite(sr, oauth2_check_openidc_suite()); #ifdef HAVE_LIBJQ srunner_add_suite(sr, oauth2_check_jq_suite()); #endif #ifdef HAVE_APACHE srunner_add_suite(sr, oauth2_check_apache_suite()); #endif #ifdef HAVE_NGINX srunner_add_suite(sr, oauth2_check_nginx_suite()); #endif // sema = sem_open ("sema", O_CREAT | O_EXCL, 0644, 0); pid_t pid = http_server_spawn(); // sleep(1); // sem_wait(sema); // srunner_run_all(sr, CK_ENV); srunner_run_all(sr, CK_VERBOSE); n_failed = srunner_ntests_failed(sr); srunner_free(sr); kill(pid, HTTP_SERVER_SIGNUM); waitpid(pid, NULL, 0); // http_serve_routing_t *ptr = NULL; // while (http_serve_routing_table) { // ptr = http_serve_routing_table; // http_serve_routing_table = // http_serve_routing_table->next; oauth2_mem_free(ptr); // } // sem_unlink("sema"); oauth2_check_jose_cleanup(); oauth2_check_http_cleanup(); oauth2_check_proto_cleanup(); oauth2_check_oauth2_cleanup(); oauth2_check_openidc_cleanup(); liboauth2_check_cleanup_http_callbacks(); return (n_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE; } liboauth2-2.1.0/test/check_liboauth2.h000066400000000000000000000160421475305260400175720ustar00rootroot00000000000000#ifndef _OAUTH2_CHECK_LIBOAUTH2_H_ #define _OAUTH2_CHECK_LIBOAUTH2_H_ /*************************************************************************** * * Copyright (C) 2018-2025 - ZmartZone Holding BV * * 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. * * @Author: Hans Zandbelt - hans.zandbelt@openidc.com * **************************************************************************/ // #pragma GCC diagnostic ignored "-Wgnu-zero-variadic-macro-arguments" // #pragma GCC diagnostic ignored //"-Wincompatible-pointer-types-discards-qualifiers" // #pragma GCC diagnostic ignored "-Wpointer-sign" #include Suite *oauth2_check_version_suite(); Suite *oauth2_check_mem_suite(); Suite *oauth2_check_log_suite(); Suite *oauth2_check_cfg_suite(); Suite *oauth2_check_util_suite(); Suite *oauth2_check_ipc_suite(); Suite *oauth2_check_cache_suite(); Suite *oauth2_check_jose_suite(); void oauth2_check_jose_cleanup(); Suite *oauth2_check_http_suite(); void oauth2_check_http_cleanup(); Suite *oauth2_check_proto_suite(); void oauth2_check_proto_cleanup(); Suite *oauth2_check_oauth2_suite(); void oauth2_check_oauth2_cleanup(); Suite *oauth2_check_openidc_suite(); void oauth2_check_openidc_cleanup(); #ifdef HAVE_LIBJQ Suite *oauth2_check_jq_suite(); #endif #ifdef HAVE_APACHE Suite *oauth2_check_apache_suite(); #endif #ifdef HAVE_NGINX Suite *oauth2_check_nginx_suite(); #endif typedef char *(http_serve_callback_get_t)(const char *request); typedef char *(http_serve_callback_post_t)(const char *request); void liboauth2_check_register_http_callbacks( const char *path, http_serve_callback_get_t *get_cb, http_serve_callback_post_t *post_cb); #define _ck_assert_bin(X, OP, Y, LEN) \ do { \ const uint8_t *_chk_x = (X); \ const uint8_t *_chk_y = (Y); \ const size_t _chk_len = (LEN); \ ck_assert_msg(0 OP memcmp(_chk_x, _chk_y, _chk_len), \ "Assertion '" #X #OP #Y "' failed: " #LEN \ "==%z, " #X "==0x%zx, " #Y "==0x%zx", \ _chk_len, _chk_x, _chk_y); \ } while (0); #define ck_assert_bin_eq(X, Y, LEN) _ck_assert_bin(X, ==, Y, LEN) #ifndef _ck_assert_ptr #define _ck_assert_ptr(X, OP, Y) \ do { \ const void *_ck_x = (X); \ const void *_ck_y = (Y); \ ck_assert_msg(_ck_x OP _ck_y, \ "Assertion '%s' failed: %s == %#x, %s == %#x", \ #X " " #OP " " #Y, #X, _ck_x, #Y, _ck_y); \ } while (0) #define ck_assert_ptr_eq(X, Y) _ck_assert_ptr(X, ==, Y) #define ck_assert_ptr_ne(X, Y) _ck_assert_ptr(X, !=, Y) #endif #ifndef _ck_assert_uint #define _ck_assert_uint(X, OP, Y) \ do { \ uintmax_t _ck_x = (X); \ uintmax_t _ck_y = (Y); \ ck_assert_msg(_ck_x OP _ck_y, \ "Assertion '%s' failed: %s == %ju, %s == %ju", \ #X " " #OP " " #Y, #X, _ck_x, #Y, _ck_y); \ } while (0) #define ck_assert_uint_eq(X, Y) _ck_assert_uint(X, ==, Y) #define ck_assert_uint_ne(X, Y) _ck_assert_uint(X, !=, Y) #endif #define OAUTH2_CHECK_HTTP_PATHS \ static char *_http_base_path = NULL; \ \ static char *oauth2_check_http_base_path() \ { \ char *p = NULL, *path = NULL; \ if (_http_base_path == NULL) { \ path = oauth2_strdup(__FILE__); \ p = strrchr(path, '.'); \ if (p) \ *p = '\0'; \ p = path; \ while (*p == '.') { \ p++; \ if (*p == '/') \ p++; \ } \ if (*p == '/') \ p++; \ _http_base_path = oauth2_stradd(NULL, "/", p, NULL); \ oauth2_mem_free(path); \ } \ return _http_base_path; \ } \ \ static char *_http_base_url = NULL; \ \ static char *oauth2_check_http_base_url() \ { \ if (_http_base_url == NULL) \ _http_base_url = oauth2_stradd( \ NULL, "http://127.0.0.1:8888", \ oauth2_check_http_base_path(), NULL); \ return _http_base_url; \ } \ \ static void oauth2_check_http_base_free() \ { \ if (_http_base_url != NULL) { \ oauth2_mem_free(_http_base_url); \ _http_base_url = NULL; \ } \ if (_http_base_path != NULL) { \ oauth2_mem_free(_http_base_path); \ _http_base_path = NULL; \ } \ } #endif /* _OAUTH2_CHECK_LIBOAUTH2_H_ */ liboauth2-2.1.0/test/check_log.c000066400000000000000000000047351475305260400164630ustar00rootroot00000000000000/*************************************************************************** * * Copyright (C) 2018-2025 - ZmartZone Holding BV * * 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. * * @Author: Hans Zandbelt - hans.zandbelt@openidc.com * **************************************************************************/ #include "check_liboauth2.h" #include "oauth2/log.h" #include #include static oauth2_log_t *_log = 0; static void setup(void) { // for coverage oauth2_log_free(NULL); _log = oauth2_log_init(OAUTH2_LOG_TRACE1, 0); } static void teardown(void) { oauth2_log_free(_log); } START_TEST(test_log) { // mostly to complete coverage // TODO: could return bytes written from oauth2_log statements oauth2_debug(NULL, NULL); // TOOD: could return bool from oauth2_log_sink_add oauth2_log_sink_add(_log, &oauth2_log_sink_stderr); oauth2_info(_log, NULL); oauth2_info(_log, ""); oauth2_log_sink_level_set(&oauth2_log_sink_stderr, OAUTH2_LOG_ERROR); } END_TEST static int check_log_test_sink_callback_dummy = 0; static void check_log_test_sink_callback(oauth2_log_sink_t *sink, const char *filename, unsigned long line, const char *function, oauth2_log_level_t level, const char *msg) { check_log_test_sink_callback_dummy = 1; } START_TEST(test_sink) { char *dummy = "dummy"; oauth2_log_sink_t *sink = oauth2_log_sink_create( OAUTH2_LOG_TRACE1, check_log_test_sink_callback, dummy); oauth2_log_sink_add(_log, sink); ck_assert_ptr_eq(oauth2_log_sink_callback_get(sink), check_log_test_sink_callback); ck_assert_ptr_eq(oauth2_log_sink_ctx_get(sink), dummy); check_log_test_sink_callback_dummy = 0; oauth2_info(_log, ""); ck_assert_int_eq(check_log_test_sink_callback_dummy, 1); } END_TEST Suite *oauth2_check_log_suite() { Suite *s = suite_create("log"); TCase *c = tcase_create("core"); tcase_add_checked_fixture(c, setup, teardown); tcase_add_test(c, test_log); tcase_add_test(c, test_sink); suite_add_tcase(s, c); return s; } liboauth2-2.1.0/test/check_mem.c000066400000000000000000000114671475305260400164600ustar00rootroot00000000000000/*************************************************************************** * * Copyright (C) 2018-2025 - ZmartZone Holding BV * * 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. * * @Author: Hans Zandbelt - hans.zandbelt@openidc.com * **************************************************************************/ #include "check_liboauth2.h" #include "oauth2/mem.h" #include #include #include static oauth2_mem_alloc_fn_t _save_alloc = NULL; static oauth2_mem_realloc_fn_t _save_realloc = NULL; static oauth2_mem_dealloc_fn_t _save_dealloc = NULL; static void *test_alloc(size_t amt) { return malloc(amt); } static void *test_realloc(void *ptr, size_t amt) { return realloc(ptr, amt); } static void test_dealloc(void *ptr) { free(ptr); } static void test_mem_functions_set() { _save_alloc = oauth2_mem_get_alloc(); _save_realloc = oauth2_mem_get_realloc(); _save_dealloc = oauth2_mem_get_dealloc(); oauth2_mem_set_alloc_funcs(test_alloc, test_realloc, test_dealloc); } static void test_mem_functions_reset() { oauth2_mem_set_alloc_funcs(_save_alloc, _save_realloc, _save_dealloc); _save_alloc = NULL; _save_realloc = NULL; _save_dealloc = NULL; } static void *test_alloc3(size_t amt, const char *file, int line) { return malloc(amt); } static void *test_realloc3(void *ptr, size_t amt, const char *file, int line) { return realloc(ptr, amt); } static void test_dealloc3(void *ptr, const char *file, int line) { free(ptr); } static oauth2_mem_alloc3_fn_t _save_alloc3 = NULL; static oauth2_mem_realloc3_fn_t _save_realloc3 = NULL; static oauth2_mem_dealloc3_fn_t _save_dealloc3 = NULL; static void test_mem_functions_set3() { _save_alloc3 = oauth2_mem_get_alloc3(); _save_realloc3 = oauth2_mem_get_realloc3(); _save_dealloc3 = oauth2_mem_get_dealloc3(); oauth2_mem_set_alloc_ex_funcs(test_alloc3, test_realloc3, test_dealloc3); } /* * TODO: why does this result in a timeout? * probably we can call this only once anyhow, but would it affect the other check_util tests? * perhaps separate it out in a different suite then? static void test_mem_functions_reset3() { cjose_set_alloc_ex_funcs(_save_alloc3, _save_realloc3, _save_dealloc3); oauth2_mem_set_alloc_ex_funcs(_save_alloc3, _save_realloc3, _save_dealloc3); _save_alloc3 = NULL; _save_realloc3 = NULL; _save_dealloc3 = NULL; } */ static void setup(void) { // provide coverage for oauth2_mem_calloc_callback // NB: the setup for cURL can only be initialized once and stays this // way test_mem_functions_set(); CURL *curl1 = NULL, *curl2 = NULL; curl1 = curl_easy_init(); curl2 = curl_easy_duphandle(curl1); curl_easy_cleanup(curl2); curl_easy_cleanup(curl1); } static void teardown(void) { } START_TEST(test_mem) { void *ptr = NULL; ck_assert(NULL != oauth2_mem_get_alloc()); ck_assert(NULL != oauth2_mem_get_realloc()); ck_assert(NULL != oauth2_mem_get_dealloc()); ck_assert(NULL != oauth2_mem_get_alloc3()); ck_assert(NULL != oauth2_mem_get_realloc3()); ck_assert(NULL != oauth2_mem_get_dealloc3()); test_mem_functions_set(); ck_assert(NULL != oauth2_mem_get_alloc()); ck_assert(NULL != oauth2_mem_get_realloc()); ck_assert(NULL != oauth2_mem_get_dealloc()); ck_assert(NULL != oauth2_mem_get_alloc3()); ck_assert(NULL != oauth2_mem_get_realloc3()); ck_assert(NULL != oauth2_mem_get_dealloc3()); ptr = oauth2_mem_alloc(8); ptr = oauth2_mem_get_realloc()(ptr, 8); oauth2_mem_free(ptr); test_mem_functions_reset(); ck_assert(NULL != oauth2_mem_get_alloc()); ck_assert(NULL != oauth2_mem_get_realloc()); ck_assert(NULL != oauth2_mem_get_dealloc()); ck_assert(NULL != oauth2_mem_get_alloc3()); ck_assert(NULL != oauth2_mem_get_realloc3()); ck_assert(NULL != oauth2_mem_get_dealloc3()); test_mem_functions_set3(); ck_assert(NULL != oauth2_mem_get_alloc()); ck_assert(NULL != oauth2_mem_get_realloc()); ck_assert(NULL != oauth2_mem_get_dealloc()); ck_assert(NULL != oauth2_mem_get_alloc3()); ck_assert(NULL != oauth2_mem_get_realloc3()); ck_assert(NULL != oauth2_mem_get_dealloc3()); ptr = oauth2_mem_alloc(8); ptr = oauth2_mem_get_realloc()(ptr, 8); oauth2_mem_free(ptr); } END_TEST Suite *oauth2_check_mem_suite() { Suite *s = suite_create("mem"); TCase *c = tcase_create("core"); tcase_add_checked_fixture(c, setup, teardown); tcase_add_test(c, test_mem); suite_add_tcase(s, c); return s; } liboauth2-2.1.0/test/check_nginx.c000066400000000000000000000105721475305260400170210ustar00rootroot00000000000000/*************************************************************************** * * Copyright (C) 2018-2025 - ZmartZone Holding BV * * 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. * * @Author: Hans Zandbelt - hans.zandbelt@openidc.com * **************************************************************************/ #include "check_liboauth2.h" #include "oauth2/mem.h" #include "oauth2/nginx.h" #include static oauth2_log_t *_log = 0; static ngx_http_request_t *_request = NULL; static char *_url = "https://example.org:8080/some?jan=piet"; static ngx_str_t _uri = ngx_string("/some"); static ngx_str_t _method_name = ngx_string("POST"); static ngx_str_t _args = ngx_string("param=value"); static void setup(void) { ngx_table_elt_t *h = NULL; _log = oauth2_init(OAUTH2_LOG_TRACE1, 0); _request = oauth2_mem_alloc(sizeof(ngx_http_request_t)); _request->schema_start = (u_char *)_url; _request->schema_end = (u_char *)_url + strlen("https"); _request->host_start = (u_char *)_url + strlen("https://"); _request->host_end = (u_char *)_url + strlen("https://example.org"); _request->uri = _uri; _request->method_name = _method_name; _request->args = _args; _request->pool = ngx_create_pool(1024, NULL); _request->connection = oauth2_mem_alloc(sizeof(ngx_connection_t)); _request->connection->log = NULL; _request->connection->local_sockaddr = oauth2_mem_alloc(sizeof(struct sockaddr_in)); _request->connection->local_sockaddr->sa_family = AF_INET; ((struct sockaddr_in *)_request->connection->local_sockaddr)->sin_port = htons(8080); _request->http_connection = oauth2_mem_alloc(sizeof(ngx_http_connection_t)); _request->http_connection->ssl = 1; ngx_list_init(&_request->headers_out.headers, _request->pool, 20, sizeof(ngx_table_elt_t)); ngx_list_init(&_request->headers_in.headers, _request->pool, 20, sizeof(ngx_table_elt_t)); h = ngx_list_push(&_request->headers_in.headers); h->hash = ngx_hash(ngx_hash(ngx_hash('H', 'o'), 's'), 't'); h->key.data = (u_char *)oauth2_strdup("Host"); h->key.len = sizeof("Host") - 1; h->lowcase_key = (u_char *)"host"; h->value.data = (u_char *)oauth2_strdup("example.org"); h->value.len = sizeof("example.org") - 1; } static void list_free(ngx_list_t *list) { ngx_list_part_t *part; ngx_table_elt_t *h; ngx_uint_t i; part = &list->part; h = part->elts; for (i = 0; /* void */; i++) { if (i >= part->nelts) { if (part->next == NULL) { break; } part = part->next; h = part->elts; i = 0; } oauth2_mem_free(h[i].value.data); oauth2_mem_free(h[i].key.data); } oauth2_mem_free(list->part.elts); } static void teardown(void) { list_free(&_request->headers_out.headers); list_free(&_request->headers_in.headers); oauth2_mem_free(_request->http_connection); oauth2_mem_free(_request->connection->local_sockaddr); oauth2_mem_free(_request->connection); ngx_destroy_pool(_request->pool); oauth2_mem_free(_request); oauth2_shutdown(_log); } START_TEST(test_request_context) { oauth2_nginx_request_context_t *ctx = NULL; ctx = oauth2_nginx_request_context_init(_request); ck_assert_ptr_ne(ctx, NULL); // TODO: check request values oauth2_nginx_request_context_free(ctx); } END_TEST START_TEST(test_nginx_http_response_set) { ngx_int_t nrc = NGX_ERROR; oauth2_http_response_t *response = NULL; response = oauth2_http_response_init(_log); oauth2_http_response_header_set(_log, response, "Content-Length", "512"); nrc = oauth2_nginx_http_response_set(_log, response, _request); ck_assert_int_eq(nrc, NGX_OK); // TODO: check status code and response headers oauth2_http_response_free(_log, response); } END_TEST /* */ Suite *oauth2_check_nginx_suite() { Suite *s = suite_create("nginx"); TCase *c = tcase_create("core"); tcase_add_checked_fixture(c, setup, teardown); tcase_add_test(c, test_request_context); tcase_add_test(c, test_nginx_http_response_set); suite_add_tcase(s, c); return s; } liboauth2-2.1.0/test/check_oauth2.c000066400000000000000000001245211475305260400171000ustar00rootroot00000000000000/*************************************************************************** * * Copyright (C) 2018-2025 - ZmartZone Holding BV * * 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. * * @Author: Hans Zandbelt - hans.zandbelt@openidc.com * **************************************************************************/ #include "check_liboauth2.h" #include "oauth2/mem.h" #include "oauth2/oauth2.h" #include "oauth2_int.h" #include #include static oauth2_log_t *_log = 0; OAUTH2_CHECK_HTTP_PATHS void oauth2_check_oauth2_cleanup() { oauth2_check_http_base_free(); } static void setup(void) { _log = oauth2_init(OAUTH2_LOG_TRACE1, 0); } static void teardown(void) { oauth2_shutdown(_log); } START_TEST(test_oauth2_verify_clone) { oauth2_cfg_token_verify_t *src = NULL, *dst = NULL; char *rv = NULL; rv = oauth2_cfg_token_verify_add_options(_log, &src, "plain", "mysecret", NULL); ck_assert_ptr_eq(rv, NULL); dst = oauth2_cfg_token_verify_clone(_log, src); oauth2_cfg_token_verify_free(_log, dst); oauth2_cfg_token_verify_free(_log, src); } END_TEST static void test_oauth_auth_clone(oauth2_cfg_endpoint_auth_t *src) { bool rc = false; oauth2_cfg_endpoint_auth_t *dst = NULL; oauth2_http_call_ctx_t *ctx = NULL; oauth2_nv_list_t *params = NULL; params = oauth2_nv_list_init(_log); ctx = oauth2_http_call_ctx_init(_log); dst = oauth2_cfg_endpoint_auth_clone(_log, src); ck_assert_ptr_ne(dst, NULL); rc = oauth2_http_ctx_auth_add(_log, ctx, dst, params); ck_assert_int_eq(rc, true); oauth2_nv_list_free(_log, params); oauth2_cfg_endpoint_auth_free(_log, dst); oauth2_http_call_ctx_free(_log, ctx); } START_TEST(test_oauth2_auth_client_secret_basic) { bool rc = false; oauth2_http_call_ctx_t *ctx = NULL; oauth2_cfg_endpoint_auth_t *auth = NULL; oauth2_nv_list_t *params = NULL; char *rv = NULL; // const char *str = NULL; // TODO: make the actual call ctx = oauth2_http_call_ctx_init(_log); params = oauth2_nv_list_init(_log); auth = oauth2_cfg_endpoint_auth_init(_log); rv = oauth2_cfg_set_endpoint_auth(_log, auth, "client_secret_basic", params, NULL); ck_assert_ptr_ne(rv, NULL); oauth2_mem_free(rv); oauth2_cfg_endpoint_auth_free(_log, auth); auth = oauth2_cfg_endpoint_auth_init(_log); oauth2_nv_list_add(_log, params, "client_id", "myclient"); rv = oauth2_cfg_set_endpoint_auth(_log, auth, "client_secret_basic", params, NULL); ck_assert_ptr_ne(rv, NULL); oauth2_mem_free(rv); oauth2_cfg_endpoint_auth_free(_log, auth); auth = oauth2_cfg_endpoint_auth_init(_log); oauth2_nv_list_add(_log, params, "client_secret", "mysecret"); rv = oauth2_cfg_set_endpoint_auth(_log, auth, "client_secret_basic", params, NULL); ck_assert_ptr_eq(rv, NULL); ck_assert_int_eq(OAUTH2_ENDPOINT_AUTH_CLIENT_SECRET_BASIC, oauth2_cfg_endpoint_auth_type(auth)); rc = oauth2_http_ctx_auth_add(_log, ctx, auth, NULL); ck_assert_int_eq(rc, true); test_oauth_auth_clone(auth); // str = oauth2_http_call_ctx_hdr_get(_log, ctx, "Authorization"); // ck_assert_ptr_ne(str, NULL); // ck_assert_str_eq(str, "bXljbGllbnQ6bXlzZWNyZXQ="); oauth2_cfg_endpoint_auth_free(_log, auth); oauth2_nv_list_free(_log, params); oauth2_http_call_ctx_free(_log, ctx); } END_TEST START_TEST(test_oauth2_auth_client_secret_post) { bool rc = false; oauth2_http_call_ctx_t *ctx = NULL; oauth2_cfg_endpoint_auth_t *auth = NULL; oauth2_nv_list_t *params = NULL; oauth2_nv_list_t *post = NULL; char *rv = NULL; const char *str = NULL; ctx = oauth2_http_call_ctx_init(_log); params = oauth2_nv_list_init(_log); auth = oauth2_cfg_endpoint_auth_init(_log); rv = oauth2_cfg_set_endpoint_auth(_log, auth, "client_secret_post", params, NULL); ck_assert_ptr_ne(rv, NULL); oauth2_mem_free(rv); oauth2_cfg_endpoint_auth_free(_log, auth); auth = oauth2_cfg_endpoint_auth_init(_log); oauth2_nv_list_add(_log, params, "client_id", "myclient"); rv = oauth2_cfg_set_endpoint_auth(_log, auth, "client_secret_post", params, NULL); ck_assert_ptr_ne(rv, NULL); oauth2_mem_free(rv); oauth2_cfg_endpoint_auth_free(_log, auth); auth = oauth2_cfg_endpoint_auth_init(_log); oauth2_nv_list_add(_log, params, "client_secret", "mysecret"); rv = oauth2_cfg_set_endpoint_auth(_log, auth, "client_secret_post", params, NULL); ck_assert_ptr_eq(rv, NULL); post = oauth2_nv_list_init(_log); rc = oauth2_http_ctx_auth_add(_log, ctx, auth, post); ck_assert_int_eq(rc, true); ck_assert_int_eq(OAUTH2_ENDPOINT_AUTH_CLIENT_SECRET_POST, oauth2_cfg_endpoint_auth_type(auth)); str = oauth2_nv_list_get(_log, post, "client_id"); ck_assert_ptr_ne(str, NULL); ck_assert_str_eq(str, "myclient"); str = oauth2_nv_list_get(_log, post, "client_secret"); ck_assert_ptr_ne(str, NULL); ck_assert_str_eq(str, "mysecret"); oauth2_nv_list_free(_log, post); test_oauth_auth_clone(auth); oauth2_cfg_endpoint_auth_free(_log, auth); oauth2_nv_list_free(_log, params); oauth2_http_call_ctx_free(_log, ctx); } END_TEST START_TEST(test_oauth2_auth_client_secret_jwt) { bool rc = false; oauth2_http_call_ctx_t *ctx = NULL; oauth2_cfg_endpoint_auth_t *auth = NULL; oauth2_nv_list_t *params = NULL; oauth2_nv_list_t *post = NULL; char *rv = NULL; const char *str = NULL; ctx = oauth2_http_call_ctx_init(_log); params = oauth2_nv_list_init(_log); auth = oauth2_cfg_endpoint_auth_init(_log); rv = oauth2_cfg_set_endpoint_auth(_log, auth, "client_secret_jwt", params, NULL); ck_assert_ptr_ne(rv, NULL); oauth2_mem_free(rv); oauth2_cfg_endpoint_auth_free(_log, auth); auth = oauth2_cfg_endpoint_auth_init(_log); oauth2_nv_list_add(_log, params, "client_id", "myclient"); rv = oauth2_cfg_set_endpoint_auth(_log, auth, "client_secret_jwt", params, NULL); ck_assert_ptr_ne(rv, NULL); oauth2_mem_free(rv); oauth2_cfg_endpoint_auth_free(_log, auth); auth = oauth2_cfg_endpoint_auth_init(_log); oauth2_nv_list_add(_log, params, "client_secret", "mysecret"); rv = oauth2_cfg_set_endpoint_auth(_log, auth, "client_secret_jwt", params, NULL); ck_assert_ptr_ne(rv, NULL); oauth2_mem_free(rv); oauth2_cfg_endpoint_auth_free(_log, auth); auth = oauth2_cfg_endpoint_auth_init(_log); oauth2_nv_list_add(_log, params, "aud", "myaud"); rv = oauth2_cfg_set_endpoint_auth(_log, auth, "client_secret_jwt", params, NULL); ck_assert_ptr_eq(rv, NULL); ck_assert_int_eq(OAUTH2_ENDPOINT_AUTH_CLIENT_SECRET_JWT, oauth2_cfg_endpoint_auth_type(auth)); post = oauth2_nv_list_init(_log); rc = oauth2_http_ctx_auth_add(_log, ctx, auth, post); ck_assert_int_eq(rc, true); str = oauth2_nv_list_get(_log, post, "client_assertion_type"); ck_assert_ptr_ne(str, NULL); ck_assert_str_eq( str, "urn:ietf:params:oauth:client-assertion-type:jwt-bearer"); str = oauth2_nv_list_get(_log, post, "client_assertion"); ck_assert_ptr_ne(str, NULL); ck_assert(strncmp(str, "eyJhbGciO", strlen("eyJhbGciO")) == 0); oauth2_nv_list_free(_log, post); test_oauth_auth_clone(auth); oauth2_cfg_endpoint_auth_free(_log, auth); oauth2_nv_list_free(_log, params); oauth2_http_call_ctx_free(_log, ctx); } END_TEST START_TEST(test_oauth2_auth_private_key_jwt) { bool rc = false; oauth2_http_call_ctx_t *ctx = NULL; oauth2_cfg_endpoint_auth_t *auth = NULL; oauth2_nv_list_t *params = NULL; oauth2_nv_list_t *post = NULL; char *rv = NULL; const char *str = NULL; const char *s_jwk = NULL; s_jwk = "{" "\"kty\" : \"RSA\"," "\"n\": " "\"ym7jipmB37CgdonwGFVRuZmRfCl3lVh91fmm5CXHcNlUFZNR3D6Q9r63PpGRnfSs" "X3dOweh8BXd2AJ3mxvcE4z9xH--tA5EaOGI7IVF0Ip_" "i3flGg85xOADlb8rX3ez1NqkqMVJeeJypKhCCDNfvu_" "MXSdPLglU969YQF5xKAK8VFRfI6EfxxrZ_3Dvt2CKDV4LTPPJe9KI2_" "LuLQFBJ3MzlCTVxY6gyaljrWaDq7q5Lt3GB1KYS0Yd8COEQwsclOLm0Tddhg4cle-" "DfaTMi7xsTZsPKyac5x17Y4N4isHhZULuWHX7o1bs809xcj-_-YCRq6C61je_" "mzFhuF4pczw\"," "\"e\": \"AQAB\"," "\"d\": " "\"qvxW_" "e8DoCnUn8uLHUKTsS1hkXqFI4SHZYFl0jeG6m7ncwHolxvR3ljg9tyGHuFX55sizu7" "MMuHgrkyxbUWgv0ILD2qmvOiHOTDfuRjP-58JRW0UfqiVQTSgl3jCNRW9WdoxZU-" "ptD6_NGSVNLwAJsUB2r4mm4PctaMuHINKjp_TnuD-5vfi9Tj88hbqvX_" "0j8T62ZaLRdERb1KGDM_" "8bnqQpnLZ0MZQnpLQ8cKIcjj7p0II6pzvqgdO1RqfYx7qG0cbcIRh26rnB9X4rp5Br" "bvDzKe6NOqacZUcNUmbPzI01-hiT0HgJvV592CBOxt2T31ltQ4wCEdzhQeT3n9_" "wQ\"" "}"; ctx = oauth2_http_call_ctx_init(_log); params = oauth2_nv_list_init(_log); auth = oauth2_cfg_endpoint_auth_init(_log); rv = oauth2_cfg_set_endpoint_auth(_log, auth, "private_key_jwt", params, NULL); ck_assert_ptr_ne(rv, NULL); oauth2_mem_free(rv); oauth2_cfg_endpoint_auth_free(_log, auth); auth = oauth2_cfg_endpoint_auth_init(_log); oauth2_nv_list_add(_log, params, "client_id", "myclient"); rv = oauth2_cfg_set_endpoint_auth(_log, auth, "private_key_jwt", params, NULL); ck_assert_ptr_ne(rv, NULL); oauth2_mem_free(rv); oauth2_cfg_endpoint_auth_free(_log, auth); auth = oauth2_cfg_endpoint_auth_init(_log); oauth2_nv_list_add(_log, params, "jwk", s_jwk); rv = oauth2_cfg_set_endpoint_auth(_log, auth, "private_key_jwt", params, NULL); ck_assert_ptr_ne(rv, NULL); oauth2_mem_free(rv); oauth2_cfg_endpoint_auth_free(_log, auth); auth = oauth2_cfg_endpoint_auth_init(_log); ck_assert_ptr_ne(auth, NULL); oauth2_nv_list_add(_log, params, "aud", "myaud"); rv = oauth2_cfg_set_endpoint_auth(_log, auth, "private_key_jwt", params, NULL); ck_assert_ptr_eq(rv, NULL); ck_assert_int_eq(OAUTH2_ENDPOINT_AUTH_PRIVATE_KEY_JWT, oauth2_cfg_endpoint_auth_type(auth)); post = oauth2_nv_list_init(_log); rc = oauth2_http_ctx_auth_add(_log, ctx, auth, post); ck_assert_int_eq(rc, true); str = oauth2_nv_list_get(_log, post, "client_assertion_type"); ck_assert_ptr_ne(str, NULL); ck_assert_str_eq( str, "urn:ietf:params:oauth:client-assertion-type:jwt-bearer"); str = oauth2_nv_list_get(_log, post, "client_assertion"); ck_assert_ptr_ne(str, NULL); ck_assert(strncmp(str, "eyJhbGciOi", strlen("eyJhbGciOi")) == 0); oauth2_nv_list_free(_log, post); test_oauth_auth_clone(auth); oauth2_cfg_endpoint_auth_free(_log, auth); oauth2_nv_list_free(_log, params); oauth2_http_call_ctx_free(_log, ctx); } END_TEST START_TEST(test_oauth2_auth_client_cert) { bool rc = false; oauth2_http_call_ctx_t *ctx = NULL; oauth2_cfg_endpoint_auth_t *auth = NULL; oauth2_nv_list_t *params = NULL; char *rv = NULL; // TODO: make the actual call ctx = oauth2_http_call_ctx_init(_log); params = oauth2_nv_list_init(_log); auth = oauth2_cfg_endpoint_auth_init(_log); rv = oauth2_cfg_set_endpoint_auth(_log, auth, "client_cert", params, NULL); ck_assert_ptr_ne(rv, NULL); oauth2_mem_free(rv); oauth2_cfg_endpoint_auth_free(_log, auth); auth = oauth2_cfg_endpoint_auth_init(_log); oauth2_nv_list_add(_log, params, "cert", "mycert.pem"); rv = oauth2_cfg_set_endpoint_auth(_log, auth, "client_cert", params, NULL); ck_assert_ptr_ne(rv, NULL); oauth2_mem_free(rv); oauth2_cfg_endpoint_auth_free(_log, auth); auth = oauth2_cfg_endpoint_auth_init(_log); oauth2_nv_list_add(_log, params, "key", "mykey.pem"); rv = oauth2_cfg_set_endpoint_auth(_log, auth, "client_cert", params, NULL); ck_assert_ptr_eq(rv, NULL); ck_assert_int_eq(OAUTH2_ENDPOINT_AUTH_CLIENT_CERT, oauth2_cfg_endpoint_auth_type(auth)); rc = oauth2_http_ctx_auth_add(_log, ctx, auth, NULL); ck_assert_int_eq(rc, true); test_oauth_auth_clone(auth); oauth2_cfg_endpoint_auth_free(_log, auth); oauth2_nv_list_free(_log, params); oauth2_http_call_ctx_free(_log, ctx); } END_TEST START_TEST(test_oauth2_auth_http_basic) { bool rc = false; oauth2_http_call_ctx_t *ctx = NULL; oauth2_cfg_endpoint_auth_t *auth = NULL; oauth2_nv_list_t *params = NULL; char *rv = NULL; // TODO: make the actual call ctx = oauth2_http_call_ctx_init(_log); params = oauth2_nv_list_init(_log); auth = oauth2_cfg_endpoint_auth_init(_log); oauth2_nv_list_add(_log, params, "username", "myuser"); oauth2_nv_list_add(_log, params, "password", "mysecret"); rv = oauth2_cfg_set_endpoint_auth(_log, auth, "basic", params, NULL); ck_assert_ptr_eq(rv, NULL); ck_assert_int_eq(OAUTH2_ENDPOINT_AUTH_BASIC, oauth2_cfg_endpoint_auth_type(auth)); rc = oauth2_http_ctx_auth_add(_log, ctx, auth, NULL); ck_assert_int_eq(rc, true); test_oauth_auth_clone(auth); oauth2_cfg_endpoint_auth_free(_log, auth); oauth2_nv_list_free(_log, params); oauth2_http_call_ctx_free(_log, ctx); } END_TEST START_TEST(test_oauth2_auth_none) { bool rc = false; oauth2_http_call_ctx_t *ctx = NULL; oauth2_cfg_endpoint_auth_t *auth = NULL; oauth2_nv_list_t *params = NULL; char *rv = NULL; rv = oauth2_cfg_set_endpoint_auth(_log, NULL, "none", NULL, NULL); ck_assert_ptr_ne(rv, NULL); oauth2_mem_free(rv); ctx = oauth2_http_call_ctx_init(_log); auth = oauth2_cfg_endpoint_auth_init(_log); ck_assert_ptr_ne(auth, NULL); rv = oauth2_cfg_set_endpoint_auth(_log, auth, "bogus", NULL, NULL); ck_assert_ptr_ne(rv, NULL); oauth2_mem_free(rv); rv = oauth2_cfg_set_endpoint_auth(_log, auth, "none", NULL, NULL); ck_assert_ptr_eq(rv, NULL); ck_assert_int_eq(OAUTH2_ENDPOINT_AUTH_NONE, oauth2_cfg_endpoint_auth_type(auth)); rc = oauth2_http_ctx_auth_add(_log, ctx, auth, NULL); ck_assert_int_eq(rc, true); test_oauth_auth_clone(auth); oauth2_cfg_endpoint_auth_free(_log, auth); oauth2_nv_list_free(_log, params); oauth2_http_call_ctx_free(_log, ctx); } END_TEST static char *get_jwks_uri_json = "{\"keys\":[{\"kty\":\"RSA\",\"kid\":\"k1\",\"use\":\"sig\",\"n\":" "\"hKvkosOyK33gznaRCNgakMLE2GHS5_7K34oqZRsAWC-7aC420eJNL2z_" "8Z7ouWXpJNZ2YHQcqxPe4UZGtiDiFYLdDbQPrCDiTpuRYybe1UmZJ3Kk5fBx9yXKU0zbdSKYPE" "eq1w5Fi7rt46YkZ6qwv3Yixo7eTxbglezJOx_YcS5sfXxcwBU1nYbGU_" "MgrBXAfy1Hea5tcUSPot-BTMcuj_doHLT_sEm4AZwaZiLhMiqfI-" "J6Gv5Hg6aBTXpYv50DEdcoZzkabMHxjHICS9w2FGWAzMt_" "AvW4ISlbAxlBroXhTEXC6GIJwoDTskuPlCO4CVa3axh0s1D49JFJoBYasw\",\"e\":" "\"AQAB\",\"x5c\":[" "\"MIIDSjCCAjKgAwIBAgIGAVvvqweOMA0GCSqGSIb3DQEBCwUAMGYxCzAJBgNVBAYTAlVTMQsw" "CQYDVQQIEwJDTzEPMA0GA1UEBxMGRGVudmVyMQ0wCwYDVQQKEwRQaW5nMQwwCgYDVQQLEwNEZX" "YxHDAaBgNVBAMTE0NvbmZpZyBTaWduaW5nIENlcnQwHhcNMTcwNTEwMDAwMzM0WhcNMzIwNTA2" "MDAwMzM0WjBmMQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ08xDzANBgNVBAcTBkRlbnZlcjENMA" "sGA1UEChMEUGluZzEMMAoGA1UECxMDRGV2MRwwGgYDVQQDExNDb25maWcgU2lnbmluZyBDZXJ0" "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAhKvkosOyK33gznaRCNgakMLE2GHS5/" "7K34oqZRsAWC+7aC420eJNL2z/" "8Z7ouWXpJNZ2YHQcqxPe4UZGtiDiFYLdDbQPrCDiTpuRYybe1UmZJ3Kk5fBx9yXKU0zbdSKYPE" "eq1w5Fi7rt46YkZ6qwv3Yixo7eTxbglezJOx/YcS5sfXxcwBU1nYbGU/" "MgrBXAfy1Hea5tcUSPot+BTMcuj/doHLT/" "sEm4AZwaZiLhMiqfI+J6Gv5Hg6aBTXpYv50DEdcoZzkabMHxjHICS9w2FGWAzMt/" "AvW4ISlbAxlBroXhTEXC6GIJwoDTskuPlCO4CVa3axh0s1D49JFJoBYaswIDAQABMA0GCSqGSI" "b3DQEBCwUAA4IBAQBCYXguSAbrwHw9g+UXuWzgj6b3jN+" "OAAQUuvpnY0KrNBentCgC3ualfgieB2c0cyLXBFTNDzMCVb2eB+f66/" "ZRQC8W6DTc5aCE3nTH8tSzbMLwwlMnQelkQMF4LZ9NZmrubVT2IYZ+" "hzwHhvVOHSQ6kqjQHXWcZ30VEbe6EV47LC1M78v+UX3CP+" "lOcovbyHl9J4VqQLKlxajr0QAqHnETkr84fI54RE2kSkWVuWp36VNY39Sl0/" "yEmnouFbV0UBMZck7gMNseCtwSYdkwls/LDFEp9D4rF1gHRlSBRskNc/" "NaasTSX4JpNf+xakm7yePtuWyAY/" "fQ7ETSPMJdVEaL\"],\"x5t\":\"31YdH_bv2Hlg89wmwBphxJZaK64\"}]}"; static char *get_jwks_uri_path = "/jwks_uri"; static char *get_eckey_pem = "-----BEGIN PUBLIC " "KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEXJ+33eo/" "U6z4PGV0++" "Qdj1Ev2363\n47i7PxTx8Tr87RYHXIIXLRmH1aIz0OVLt4eM9iXDlDGB6ldBFsM8P61nqQ==" "\n-----END PUBLIC KEY-----"; static char *get_eckey_url_path = "/ec_key"; static char *introspection_result_json = "{ \"active\": true }"; static char *post_introspection_path = "/introspection"; const char *valid_access_token = "my_valid_token"; static char *metadata_path = "/.well-known/oauth2-configuration"; static char metadata[512]; static char *get_metadata_json() { static char *format = "{" "\"issuer\": \"https://example.com\"," "\"jwks_uri\": \"%s%s\"," "\"introspection_endpoint\": \"%s%s\"" "}"; oauth2_snprintf(metadata, sizeof(metadata), format, oauth2_check_http_base_url(), get_jwks_uri_path, oauth2_check_http_base_url(), post_introspection_path); return metadata; } static char *oauth2_check_oauth2_serve_get(const char *request) { char *rv = NULL; if (strncmp(request, get_jwks_uri_path, strlen(get_jwks_uri_path)) == 0) { rv = oauth2_strdup(get_jwks_uri_json); goto end; } if (strncmp(request, get_eckey_url_path, strlen(get_eckey_url_path)) == 0) { rv = oauth2_strdup(get_eckey_pem); goto end; } if (strncmp(request, metadata_path, strlen(metadata_path)) == 0) { rv = oauth2_strdup(get_metadata_json()); goto end; } rv = oauth2_strdup("problem"); end: return rv; } static char *oauth2_check_oauth2_serve_post(const char *request) { oauth2_nv_list_t *params = NULL; char *data = NULL; const char *token = NULL; const char *sep = "****"; char *rv = NULL; if (strncmp(request, post_introspection_path, strlen(post_introspection_path)) == 0) { request += strlen(post_introspection_path) + 5; data = strstr(request, sep); if (data == NULL) goto error; data += strlen(sep); if (oauth2_parse_form_encoded_params(_log, data, ¶ms) == false) goto error; token = oauth2_nv_list_get(_log, params, "key2"); if ((token == NULL) || (strcmp(token, "two") != 0)) goto error; token = oauth2_nv_list_get(_log, params, "token"); if (token == NULL) goto error; if ((token) && (strcmp(token, valid_access_token) == 0)) rv = oauth2_strdup(introspection_result_json); else rv = oauth2_strdup("{ \"active\": false }"); oauth2_nv_list_free(_log, params); goto end; } error: rv = oauth2_strdup("{ \"error\": \"problem\" }"); end: return rv; } START_TEST(test_oauth2_verify_jwk) { bool rc = false; oauth2_cfg_token_verify_t *verify = NULL; char *jwt = "eyJhbGciOiJSUzI1NiIsImtpZCI6ImsxIn0." "eyJzY29wZSI6W10sImNsaWVudF9pZF9uYW1lIjoicm9fY2xpZW50IiwiYWdpZCI6Im" "4zak1UazdXSDVVSU9FTWNEZEZPSVR5eFZ2VW1XRHVyIiwiT3JnTmFtZSI6IlBpbmcg" "SWRlbnRpdHkgQ29ycG9yYXRpb24iLCJjbmYiOnsieDV0I1MyNTYiOiJsNnU5S1VDZ0" "I2UHpHdklpTS0tWEYwTHF3N1ZYejdvQWtoUkhhbEZqOGkwIn0sIlVzZXJuYW1lIjoi" "am9lIiwiZXhwIjoxNTQyMTI5NzgzfQ.MUghlaVxy5ij3HODBl6spAA-h6W7D-" "PoKyhDfR5DnODQqwb5zaqba2pWyJ0d6-4AQfQ6dIe0jfwQeUrPTu2DZLtk3H-" "noCSjtXrFV_RFNfz9kqdEXwkVZAX8H_ySrYFcAx3Ac9C8bZzjRUM6c4emql-" "I6T1fVGqO_" "bVUsWbPmPtNanq3UyqTrlDwQ6weO0ZbLH9tcDpZD4ojNCJjkHa3lvjwYzPNwlAI6a_" "DGng-7rgrobhOiaAgBAwLhq9fvTtM2MWNmWXmUCymq3nGqG_d_t5i_" "x7Zf28T3ejzEX-ETefpTENX7BJ57-vQbAeECRTIo_LhzKTaDkiZWpf6JgraQg"; char *jwk = "{\"kty\":\"RSA\",\"kid\":\"k1\",\"use\":\"sig\",\"n\":" "\"hKvkosOyK33gznaRCNgakMLE2GHS5_7K34oqZRsAWC-7aC420eJNL2z_" "8Z7ouWXpJNZ2YHQcqxPe4UZGtiDiFYLdDbQPrCDiTpuRYybe1UmZJ3Kk5f" "Bx9yXKU0zbdSKYPE" "eq1w5Fi7rt46YkZ6qwv3Yixo7eTxbglezJOx_YcS5sfXxcwBU1nYbGU_" "MgrBXAfy1Hea5tcUSPot-BTMcuj_doHLT_sEm4AZwaZiLhMiqfI-" "J6Gv5Hg6aBTXpYv50DEdcoZzkabMHxjHICS9w2FGWAzMt_" "AvW4ISlbAxlBroXhTEXC6GIJwoDTskuPlCO4CVa3axh0s1D49JFJoBYasw" "\",\"e\":" "\"AQAB\"}"; json_t *json_payload = NULL; const char *rv = NULL; rv = oauth2_cfg_token_verify_add_options(_log, &verify, "jwk", jwk, "verify.exp=skip"); ck_assert_ptr_eq(rv, NULL); rc = oauth2_token_verify(_log, NULL, verify, jwt, &json_payload); ck_assert_int_eq(rc, true); oauth2_cfg_token_verify_free(_log, verify); json_decref(json_payload); } END_TEST START_TEST(test_oauth2_verify_jwk_dpop) { bool rc = false; oauth2_cfg_token_verify_t *verify = NULL; char *jwt = "eyJhbGciOiJSUzI1NiIsImtpZCI6ImsxIiwicGkuYXRtIjoiMSJ9." "eyJzY29wZSI6WyJvcGVuaWQiXSwiYXV0aG9yaXphdGlvbl9kZXRhaWxzIjpbXSwiY2" "xpZW50X2lkX25hbWUiOiJyb19jbGllbnQiLCJVc2VybmFtZSI6ImpvZSIsIk9yZ05h" "bWUiOiJQaW5nIElkZW50aXR5IENvcnBvcmF0aW9uIiwiY25mIjp7ImprdCI6InZBcG" "RWSVBLenNwMXpzSzVjQ25JTmRLOEdKU0Z3VlNSeV9IZ1VFNDBYUGsifSwiZXhwIjox" "Njk5NDUyNDYzfQ.DihED2HcrAkW9ItLYIAqeweOEyI_" "qqcCfMVmHNEnIpvz7GeaYFQ6E3edj1AWU09NsUa3W3mV5Ze6-" "zgeSwXQf9yEs2TVO88Ye6zpv3F1SPlO8Zhue4qGZJpdKtyz_sPCGRd2v_" "1N6ji9m1IyNzJwiMD32molHWpcIRytke2hG9AZvxzcmJ1lwd0ReVyV8payUmxtVcwN" "yKzTmX-XNV7kNP6DZsnJTOYFgJ98punDpdorpIMFwjOTcFk8zMFdHO9rdR_" "jUI4NGXlfmLXtTrS-FdSd3bRDQFuJA5qGdNie-5vS-kfeIUCaAQZFXR6MsD-Dz_" "xKPhuDQecbDiIj5s726Q"; char *jwk = "{\"kty\":\"RSA\",\"kid\":\"k1\",\"use\":\"sig\",\"n\":" "\"hKvkosOyK33gznaRCNgakMLE2GHS5_7K34oqZRsAWC-7aC420eJNL2z_" "8Z7ouWXpJNZ2YHQcqxPe4UZGtiDiFYLdDbQPrCDiTpuRYybe1UmZJ3Kk5fBx9yXKU0" "zbdSKYPEeq1w5Fi7rt46YkZ6qwv3Yixo7eTxbglezJOx_YcS5sfXxcwBU1nYbGU_" "MgrBXAfy1Hea5tcUSPot-BTMcuj_doHLT_sEm4AZwaZiLhMiqfI-" "J6Gv5Hg6aBTXpYv50DEdcoZzkabMHxjHICS9w2FGWAzMt_" "AvW4ISlbAxlBroXhTEXC6GIJwoDTskuPlCO4CVa3axh0s1D49JFJoBYasw\"," "\"e\":\"AQAB\"}"; char *dpop = "eyJ0eXAiOiJkcG9wK2p3dCIsImFsZyI6IlJTMjU2IiwiandrIjp7Imt0eSI6IlJTQS" "IsImUiOiJBUUFCIiwibiI6InJiOXZ5ekFJaVFqQUVFdGFTZnJnU2NSVHotVnNEZ2hp" "b1Z2ajNJNnlZbHJ2TFdaNHFWdEtzUDNQU3Z4dTNVejdWTWRwVFEyODc5WlRGVWh0LV" "9Cc3M1NHNtOUdJTTZQVGRZY3VDN3dOMHR2N2JHMDNsVGdOUFdvcmZrMzRhSVk1NHh1" "Tmo5SHNBVnJKRG1NWklWTnBOUGZabXcwcDVheFBVQ19OTEw4YVhUeDJnWFZFV3V4dG" "NXSjFzbzFHVE5pbXZPMXM1eklTaDZvTXlDVFRhQ1N2el9DYWVwektTeXZfc00yWk9Y" "VkhUVzZ2SEM4Q0tMY2VwT1NFLWx0UXV3TUF6MWxEU0szQ0hURFRTMVNzdEpVMklKam" "hobnFwVVgzVHNxQ0E5em9PQlk4aXVRd3hCTDBicFl4T0dVZ21oNXU0Sm90bFlzZkU0" "T3FMdG1ueGJuSzdydyJ9fQ." "eyJqdGkiOiIrSmJDS3hrazFUNXQ4bys1IiwiaHRtIjoiR0VUIiwiaHR1IjoiaHR0cH" "M6Ly9sb2NhbGhvc3Quem1hcnR6b25lLmV1L2FwaS8iLCJpYXQiOjE2OTk0NDUyNjMs" "ImF0aCI6Ikd6TkR1S1poVHd5dHppN09rd3VXMjhwQ0xTb0paZ2xmN1pNRG94SGQ2dk" "kifQ.K-" "xn9siYFnhi3y5gejKwwIEzD2uKmmtfqV0XDbHh7JZ2RNQJfBNpyiEhSUT5dXc5AY8h" "RzUyWmi4cmE0yW97FKphZdbeumFBGuLiTyMQNVTUWdjMOS7-uWV27bZXj-KaI3C9c_" "mNHjsuW_Ax5LSK35u8Iw_A25EXrJhezzAP74chiKJN1pw3eq_2EZlUF-" "ihz7Y045sW56EBf-4SCJUfOhGnrb7rHg3KXMiOcSdEnzFiaTSYOozlMZxFvY-" "VnaqksBZ17-mGMSi7K_" "9QdBac7ick7OQ7VecYittd5nmnvrRaGytJdJYOSfB5HDPtoaXFNGj24yaJan3IOr2H" "bg2t8A"; json_t *json_payload = NULL; oauth2_http_request_t *request = NULL; const char *rv = NULL; /* oauth2_nv_list_t *params = NULL; oauth2_cache_t *cache = NULL; rc = oauth2_parse_form_encoded_params( _log, "name=dpop-cache&max_entries=5", ¶ms); ck_assert_int_eq(rc, true); cache = oauth2_cache_init(_log, "shm", params); rc = oauth2_cache_post_config(_log, cache); ck_assert_int_eq(rc, true); */ rv = oauth2_cfg_token_verify_add_options( _log, &verify, "jwk", jwk, "verify.exp=skip&type=dpop&dpop.iat.verify=skip"); // &dpop.cache=dpop-cache ck_assert_ptr_eq(rv, NULL); request = oauth2_http_request_init(_log); oauth2_http_request_scheme_set(_log, request, "https"); oauth2_http_request_hostname_set(_log, request, "localhost.zmartzone.eu"); // oauth2_http_request_port_set(); oauth2_http_request_path_set(_log, request, "/api/"); oauth2_http_request_method_set(_log, request, OAUTH2_HTTP_METHOD_GET); oauth2_http_request_header_set(_log, request, "DPoP", dpop); rc = oauth2_token_verify(_log, request, verify, jwt, &json_payload); ck_assert_int_eq(rc, true); // now it should be in the cache json_decref(json_payload); rc = oauth2_token_verify(_log, request, verify, jwt, &json_payload); ck_assert_int_eq(rc, false); oauth2_http_request_free(_log, request); oauth2_cfg_token_verify_free(_log, verify); json_decref(json_payload); /* oauth2_nv_list_free(_log, params); */ } END_TEST START_TEST(test_oauth2_verify_jwks_uri) { bool rc = false; oauth2_cfg_token_verify_t *verify = NULL; char *jwt = "eyJhbGciOiJSUzI1NiIsImtpZCI6ImsxIn0." "eyJzY29wZSI6W10sImNsaWVudF9pZF9uYW1lIjoicm9fY2xpZW50IiwiYWdpZCI6Im" "4zak1UazdXSDVVSU9FTWNEZEZPSVR5eFZ2VW1XRHVyIiwiT3JnTmFtZSI6IlBpbmcg" "SWRlbnRpdHkgQ29ycG9yYXRpb24iLCJjbmYiOnsieDV0I1MyNTYiOiJsNnU5S1VDZ0" "I2UHpHdklpTS0tWEYwTHF3N1ZYejdvQWtoUkhhbEZqOGkwIn0sIlVzZXJuYW1lIjoi" "am9lIiwiZXhwIjoxNTQyMTI5NzgzfQ.MUghlaVxy5ij3HODBl6spAA-h6W7D-" "PoKyhDfR5DnODQqwb5zaqba2pWyJ0d6-4AQfQ6dIe0jfwQeUrPTu2DZLtk3H-" "noCSjtXrFV_RFNfz9kqdEXwkVZAX8H_ySrYFcAx3Ac9C8bZzjRUM6c4emql-" "I6T1fVGqO_" "bVUsWbPmPtNanq3UyqTrlDwQ6weO0ZbLH9tcDpZD4ojNCJjkHa3lvjwYzPNwlAI6a_" "DGng-7rgrobhOiaAgBAwLhq9fvTtM2MWNmWXmUCymq3nGqG_d_t5i_" "x7Zf28T3ejzEX-ETefpTENX7BJ57-vQbAeECRTIo_LhzKTaDkiZWpf6JgraQg"; json_t *json_payload = NULL; const char *rv = NULL; char *url = NULL; url = oauth2_stradd(NULL, oauth2_check_http_base_url(), get_jwks_uri_path, NULL); rv = oauth2_cfg_token_verify_add_options(_log, &verify, "jwks_uri", url, "verify.exp=skip"); ck_assert_ptr_eq(rv, NULL); rc = oauth2_token_verify(_log, NULL, verify, jwt, &json_payload); ck_assert_int_eq(rc, true); oauth2_cfg_token_verify_free(_log, verify); oauth2_mem_free(url); json_decref(json_payload); } END_TEST START_TEST(test_oauth2_verify_eckey_uri) { bool rc = false; oauth2_cfg_token_verify_t *verify = NULL; char *jwt = "eyJ0eXAiOiJKV1QiLCJraWQiOiIwOWQ0ZmExNy0yMjNlLTQwZmEtYjI4MC04OTRlOD" "QzZDcwMWYiLCJhbGciOiJFUzI1NiIsImlzcyI6Imh0dHBzOi8vYWNjb3VudHMuZ29v" "Z2xlLmNvbSIsImNsaWVudCI6IjY4NjMwMzIzMzEzMS1wZjA4b3J2YzVyY3BmaXQwdm" "xxNW82dWg0N3UyZW5mZy5hcHBzLmdvb2dsZXVzZXJjb250ZW50LmNvbSIsInNpZ25l" "ciI6ImFybjphd3M6ZWxhc3RpY2xvYWRiYWxhbmNpbmc6ZXUtY2VudHJhbC0xOjAwNj" "E3NTk0MDQ5NDpsb2FkYmFsYW5jZXIvYXBwL2JhbGFuY2VyMS8xODE3NThhZTJiMGMz" "ZWRlIiwiZXhwIjoxNTQyMDQ1Mzk5fQ==." "ewogICJzdWIiOiAiMTA5NzE2NDkyNjgxNjg2MTcyOTY5IiwKICAibmFtZSI6ICJIYW" "5zIFphbmRiZWx0IiwKICAiZ2l2ZW5fbmFtZSI6ICJIYW5zIiwKICAiZmFtaWx5X25h" "bWUiOiAiWmFuZGJlbHQiLAogICJwcm9maWxlIjogImh0dHBzOi8vcGx1cy5nb29nbG" "UuY29tLzEwOTcxNjQ5MjY4MTY4NjE3Mjk2OSIsCiAgInBpY3R1cmUiOiAiaHR0cHM6" "Ly9saDMuZ29vZ2xldXNlcmNvbnRlbnQuY29tLy1pOUc3U1V2S1FETS9BQUFBQUFBQU" "FBSS9BQUFBQUFBQUFBQS9zeEFzTk5FVlJWZy9waG90by5qcGciCn0=." "AlH8PGya9avWoGVkWOFWbMNiLdpSDQZqP-" "OuGfIXHw1CZWjxfJInXYiRsKRZlvlXJA5fguaeNKZ1Q_RyDjNqRg=="; json_t *json_payload = NULL; const char *rv = NULL; char *url = NULL; url = oauth2_stradd(NULL, oauth2_check_http_base_url(), get_eckey_url_path, NULL); rv = oauth2_cfg_token_verify_add_options(_log, &verify, "eckey_uri", url, NULL); ck_assert_ptr_eq(rv, NULL); rc = oauth2_token_verify(_log, NULL, verify, jwt, &json_payload); ck_assert_int_eq(rc, true); oauth2_cfg_token_verify_free(_log, verify); oauth2_mem_free(url); json_decref(json_payload); } END_TEST START_TEST(test_oauth2_verify_aws_alb) { bool rc = false; oauth2_cfg_token_verify_t *verify = NULL; char *jwt = "eyJ0eXAiOiJKV1QiLCJraWQiOiIwOWQ0ZmExNy0yMjNlLTQwZmEtYjI4MC04OTRlOD" "QzZDcwMWYiLCJhbGciOiJFUzI1NiIsImlzcyI6Imh0dHBzOi8vYWNjb3VudHMuZ29v" "Z2xlLmNvbSIsImNsaWVudCI6IjY4NjMwMzIzMzEzMS1wZjA4b3J2YzVyY3BmaXQwdm" "xxNW82dWg0N3UyZW5mZy5hcHBzLmdvb2dsZXVzZXJjb250ZW50LmNvbSIsInNpZ25l" "ciI6ImFybjphd3M6ZWxhc3RpY2xvYWRiYWxhbmNpbmc6ZXUtY2VudHJhbC0xOjAwNj" "E3NTk0MDQ5NDpsb2FkYmFsYW5jZXIvYXBwL2JhbGFuY2VyMS8xODE3NThhZTJiMGMz" "ZWRlIiwiZXhwIjoxNTQyMDQ1Mzk5fQ==." "ewogICJzdWIiOiAiMTA5NzE2NDkyNjgxNjg2MTcyOTY5IiwKICAibmFtZSI6ICJIYW" "5zIFphbmRiZWx0IiwKICAiZ2l2ZW5fbmFtZSI6ICJIYW5zIiwKICAiZmFtaWx5X25h" "bWUiOiAiWmFuZGJlbHQiLAogICJwcm9maWxlIjogImh0dHBzOi8vcGx1cy5nb29nbG" "UuY29tLzEwOTcxNjQ5MjY4MTY4NjE3Mjk2OSIsCiAgInBpY3R1cmUiOiAiaHR0cHM6" "Ly9saDMuZ29vZ2xldXNlcmNvbnRlbnQuY29tLy1pOUc3U1V2S1FETS9BQUFBQUFBQU" "FBSS9BQUFBQUFBQUFBQS9zeEFzTk5FVlJWZy9waG90by5qcGciCn0=." "AlH8PGya9avWoGVkWOFWbMNiLdpSDQZqP-" "OuGfIXHw1CZWjxfJInXYiRsKRZlvlXJA5fguaeNKZ1Q_RyDjNqRg=="; json_t *json_payload = NULL; const char *rv = NULL; char *url = NULL, *options = NULL; url = oauth2_stradd(NULL, oauth2_check_http_base_url(), get_eckey_url_path, NULL); options = oauth2_stradd(NULL, "alb_base_url", "=", url); rv = oauth2_cfg_token_verify_add_options( _log, &verify, "aws_alb", "arn:aws:elasticloadbalancing:eu-central-1:006175940494:" "loadbalancer/app/balancer1/181758ae2b0c3ede", options); ck_assert_ptr_eq(rv, NULL); rc = oauth2_token_verify(_log, NULL, verify, jwt, &json_payload); ck_assert_int_eq(rc, true); oauth2_cfg_token_verify_free(_log, verify); oauth2_mem_free(options); oauth2_mem_free(url); json_decref(json_payload); } END_TEST START_TEST(test_oauth2_verify_token_introspection) { bool rc = false; oauth2_cfg_token_verify_t *verify = NULL; json_t *json_payload = NULL; const char *rv = NULL; char *url = NULL; url = oauth2_stradd(NULL, oauth2_check_http_base_url(), post_introspection_path, NULL); rv = oauth2_cfg_token_verify_add_options( _log, &verify, "introspect", url, "introspect.ssl_verify=false&introspect.params=key1%3Done%26key2%" "3Dtwo"); ck_assert_ptr_eq(rv, NULL); rc = oauth2_token_verify(_log, NULL, verify, "bogus", &json_payload); ck_assert_int_eq(rc, false); json_decref(json_payload); rc = oauth2_token_verify(_log, NULL, verify, valid_access_token, &json_payload); ck_assert_int_eq(rc, true); json_decref(json_payload); // get it from the cache rc = oauth2_token_verify(_log, NULL, verify, valid_access_token, &json_payload); ck_assert_int_eq(rc, true); json_decref(json_payload); oauth2_cfg_token_verify_free(_log, verify); oauth2_mem_free(url); } END_TEST START_TEST(test_oauth2_verify_token_plain) { bool rc = false; oauth2_cfg_token_verify_t *verify = NULL; char *jwt = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9." "eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIn0." "sQOVoEtkQlgy8UwlPOi5YWSdGAkRn80JqT53RdktIms"; json_t *json_payload = NULL; const char *rv = NULL; rv = oauth2_cfg_token_verify_add_options(_log, &verify, "plain", "mysecret", "kid=mykid"); ck_assert_ptr_eq(rv, NULL); rc = oauth2_token_verify(_log, NULL, verify, jwt, &json_payload); ck_assert_int_eq(rc, true); oauth2_cfg_token_verify_free(_log, verify); json_decref(json_payload); } END_TEST START_TEST(test_oauth2_verify_token_base64) { bool rc = false; oauth2_cfg_token_verify_t *verify = NULL; char *jwt = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9." "eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIn0." "kEm7kPCWXNn-p4cRSDAuO-htYx8hpq_7imIhMlig5So"; json_t *json_payload = NULL; const char *rv = NULL; rv = oauth2_cfg_token_verify_add_options(_log, &verify, "base64", "YW5vdGhlcnNlY3JldA==", NULL); ck_assert_ptr_eq(rv, NULL); rc = oauth2_token_verify(_log, NULL, verify, jwt, &json_payload); ck_assert_int_eq(rc, true); oauth2_cfg_token_verify_free(_log, verify); json_decref(json_payload); } END_TEST START_TEST(test_oauth2_verify_token_base64url) { // https://tools.ietf.org/html/rfc7515#appendix-A with iat/exp // validation set to false bool rc = false; oauth2_cfg_token_verify_t *verify = NULL; char *jwt = "eyJ0eXAiOiJKV1QiLA0KICJhbGciOiJIUzI1NiJ9." "eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly" "9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ.dBjftJeZ4CVP-" "mB92K27uhbUJU1p1r_wW1gFWFOEjXk"; json_t *json_payload = NULL; const char *rv = NULL; rv = oauth2_cfg_token_verify_add_options( _log, &verify, "base64url", "AyM1SysPpbyDfgZld3umj1qzKObwVMkoqQ-EstJQLr_T-" "1qS0gZH75aKtMN3Yj0iPS4hcgUuTwjAzZr1Z9CAow", "verify.exp=skip"); ck_assert_ptr_eq(rv, NULL); rc = oauth2_token_verify(_log, NULL, verify, jwt, &json_payload); ck_assert_int_eq(rc, true); oauth2_cfg_token_verify_free(_log, verify); json_decref(json_payload); } END_TEST START_TEST(test_oauth2_verify_token_hex) { bool rc = false; oauth2_cfg_token_verify_t *verify = NULL; char *jwt = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9." "eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIn0." "ZdF3p7DBVz50evb9_eaY6euUtYikb6NTF7QHO6OTbGg"; json_t *json_payload = NULL; const char *rv = NULL; rv = oauth2_cfg_token_verify_add_options( _log, &verify, "hex", "6d797468697264736563726574", NULL); ck_assert_ptr_eq(rv, NULL); rc = oauth2_token_verify(_log, NULL, verify, jwt, &json_payload); ck_assert_int_eq(rc, true); oauth2_cfg_token_verify_free(_log, verify); json_decref(json_payload); } END_TEST START_TEST(test_oauth2_verify_token_pem) { bool rc = false; oauth2_cfg_token_verify_t *verify = NULL; char *jwt = "eyJhbGciOiJSUzI1NiJ9." "eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFt" "cGxlLmNvbS9pc19yb290Ijp0cnVlfQ." "cC4hiUPoj9Eetdgtv3hF80EGrhuB__dzERat0XF9g2VtQgr9PJbu3XOiZj5RZmh7" "AAuHIm4Bh-0Qc_lF5YKt_O8W2Fp5jujGbds9uJdbF9CUAr7t1dnZcAcQjbKBYNX4" "BAynRFdiuB--f_nZLgrnbyTyWzO75vRK5h6xBArLIARNPvkSjtQBMHlb1L07Qe7K" "0GarZRmB_eSN9383LcOLn6_dO--xi12jzDwusC-eOkHWEsqtFZESc6BfI7noOPqv" "hJ1phCnvWh6IeYI2w9QOYEUipUTI8np6LbgGY9Fs98rqVt5AXLIhWkWywlVmtVrB" "p0igcN_IoypGlUPQGe77Rw"; json_t *json_payload = NULL; const char *rv = NULL; char *pem = "-----BEGIN CERTIFICATE-----\n" "MIICwzCCAaugAwIBAgIBADANBgkqhkiG9w0BAQQFADAlMQswCQYDVQQGEwJOTDEW\n" "MBQGA1UEAwwNWm1hcnRab25lIElBTTAeFw0xOTAyMDcxOTI4MTFaFw0yMDAyMDcx\n" "OTI4MTFaMCUxCzAJBgNVBAYTAk5MMRYwFAYDVQQDDA1abWFydFpvbmUgSUFNMIIB\n" "IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAofgWCuLjybRlzo0tZWJjNiuS\n" "fb4p4fAkd/wWJcyQoTbji9k0l8W26mPddxHmfHQp+Vaw+4qPCJrcS2mJPMEzP1Pt\n" "0Bm4d4QlL+yRT+SFd2lZS+pCgNMsD1W/YpRPEwOWvG6b32690r2jZ47soMZo9wGz\n" "jb/7OMg0LOL+bSf63kpaSHSXndS5z5rexMdbBYUsLA9e+KXBdQOS+UTo7WTBEMa2\n" "R2CapHg665xsmtdVMTBQY4uDZlxvb3qCo5ZwKh9kG4LT6/I5IhlJH7aGhyxXFvUK\n" "+DWNmoudF8NAco9/h9iaGNj8q2ethFkMLs91kzk2PAcDTW9gb54h4FRWyuXpoQID\n" "AQABMA0GCSqGSIb3DQEBBAUAA4IBAQB8USZJ2O2um7QXYKJmI1YpeV1UCoqwl8zs\n" "Ow6oMxppGRd8ZiOI4N+fYvRkZmdLDlvg/Xww0Z6sNT0HDlS0otbUhiYBg9fQb44v\n" "Rx3lLXeziHoprzP/SApf5lFUJmzvfbsyKKRFsmkpGWbtkWKDHxk1BA/4symkoifC\n" "OE8+GbbdaDXthPDEsrLNnTpH5mLrWZ4+Ulp7FQiB3okXnL/wasiMufRZdEhUPLdP\n" "KS/Ch2wudSukzgin9K0GsvdM64I70tLyHRPtkIAorm5RwgGJvO5lHD/2W1hjIun0\n" "aItLpLaBsJJKaMxUVbt6pGopRRQnCHscUxKZZEJDm6Qjiuw66iUW\n" "-----END CERTIFICATE-----\n"; rv = oauth2_cfg_token_verify_add_options(_log, &verify, "pem", pem, "verify.exp=skip"); ck_assert_ptr_eq(rv, NULL); rc = oauth2_token_verify(_log, NULL, verify, jwt, &json_payload); ck_assert_int_eq(rc, true); oauth2_cfg_token_verify_free(_log, verify); json_decref(json_payload); } END_TEST START_TEST(test_oauth2_verify_token_pubkey) { bool rc = false; oauth2_cfg_token_verify_t *verify = NULL; char *jwt = "eyJhbGciOiJSUzI1NiJ9." "eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFt" "cGxlLmNvbS9pc19yb290Ijp0cnVlfQ." "cC4hiUPoj9Eetdgtv3hF80EGrhuB__dzERat0XF9g2VtQgr9PJbu3XOiZj5RZmh7" "AAuHIm4Bh-0Qc_lF5YKt_O8W2Fp5jujGbds9uJdbF9CUAr7t1dnZcAcQjbKBYNX4" "BAynRFdiuB--f_nZLgrnbyTyWzO75vRK5h6xBArLIARNPvkSjtQBMHlb1L07Qe7K" "0GarZRmB_eSN9383LcOLn6_dO--xi12jzDwusC-eOkHWEsqtFZESc6BfI7noOPqv" "hJ1phCnvWh6IeYI2w9QOYEUipUTI8np6LbgGY9Fs98rqVt5AXLIhWkWywlVmtVrB" "p0igcN_IoypGlUPQGe77Rw"; json_t *json_payload = NULL; const char *rv = NULL; char *pubkey = "-----BEGIN PUBLIC KEY-----\n" "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAofgWCuLjybRlzo0tZWJj\n" "NiuSfb4p4fAkd/wWJcyQoTbji9k0l8W26mPddxHmfHQp+Vaw+4qPCJrcS2mJPMEz\n" "P1Pt0Bm4d4QlL+yRT+SFd2lZS+pCgNMsD1W/YpRPEwOWvG6b32690r2jZ47soMZo\n" "9wGzjb/7OMg0LOL+bSf63kpaSHSXndS5z5rexMdbBYUsLA9e+KXBdQOS+UTo7WTB\n" "EMa2R2CapHg665xsmtdVMTBQY4uDZlxvb3qCo5ZwKh9kG4LT6/I5IhlJH7aGhyxX\n" "FvUK+DWNmoudF8NAco9/h9iaGNj8q2ethFkMLs91kzk2PAcDTW9gb54h4FRWyuXp\n" "oQIDAQAB\n" "-----END PUBLIC KEY-----"; rv = oauth2_cfg_token_verify_add_options(_log, &verify, "pubkey", pubkey, "verify.exp=skip"); ck_assert_ptr_eq(rv, NULL); rc = oauth2_token_verify(_log, NULL, verify, jwt, &json_payload); ck_assert_int_eq(rc, true); oauth2_cfg_token_verify_free(_log, verify); json_decref(json_payload); } END_TEST START_TEST(test_oauth2_verify_token_metadata) { bool rc = false; oauth2_cfg_token_verify_t *verify = NULL; json_t *json_payload = NULL; const char *rv = NULL; char *url = NULL; url = oauth2_stradd(NULL, oauth2_check_http_base_url(), metadata_path, NULL); rv = oauth2_cfg_token_verify_add_options( _log, &verify, "metadata", url, "verify.exp=skip&verify.iss=required&introspect.params=key2%3Dtwo"); ck_assert_ptr_eq(rv, NULL); // reference token rc = oauth2_token_verify(_log, NULL, verify, "bogus", &json_payload); ck_assert_int_eq(rc, false); json_decref(json_payload); rc = oauth2_token_verify(_log, NULL, verify, valid_access_token, &json_payload); ck_assert_int_eq(rc, true); json_decref(json_payload); // get it from the cache rc = oauth2_token_verify(_log, NULL, verify, valid_access_token, &json_payload); ck_assert_int_eq(rc, true); json_decref(json_payload); // jwt token char *jwt = "eyJhbGciOiJSUzI1NiIsImtpZCI6ImsxIn0." "eyJzY29wZSI6W10sImNsaWVudF9pZF9uYW1lIjoicm9fY2xpZW50IiwiYWdpZCI6Im" "4zak1UazdXSDVVSU9FTWNEZEZPSVR5eFZ2VW1XRHVyIiwiT3JnTmFtZSI6IlBpbmcg" "SWRlbnRpdHkgQ29ycG9yYXRpb24iLCJjbmYiOnsieDV0I1MyNTYiOiJsNnU5S1VDZ0" "I2UHpHdklpTS0tWEYwTHF3N1ZYejdvQWtoUkhhbEZqOGkwIn0sIlVzZXJuYW1lIjoi" "am9lIiwiZXhwIjoxNTQyMTI5NzgzfQ.MUghlaVxy5ij3HODBl6spAA-h6W7D-" "PoKyhDfR5DnODQqwb5zaqba2pWyJ0d6-4AQfQ6dIe0jfwQeUrPTu2DZLtk3H-" "noCSjtXrFV_RFNfz9kqdEXwkVZAX8H_ySrYFcAx3Ac9C8bZzjRUM6c4emql-" "I6T1fVGqO_" "bVUsWbPmPtNanq3UyqTrlDwQ6weO0ZbLH9tcDpZD4ojNCJjkHa3lvjwYzPNwlAI6a_" "DGng-7rgrobhOiaAgBAwLhq9fvTtM2MWNmWXmUCymq3nGqG_d_t5i_" "x7Zf28T3ejzEX-ETefpTENX7BJ57-vQbAeECRTIo_LhzKTaDkiZWpf6JgraQg"; rc = oauth2_token_verify(_log, NULL, verify, jwt, &json_payload); ck_assert_int_eq(rc, false); json_decref(json_payload); oauth2_cfg_token_verify_free(_log, verify); verify = NULL; rv = oauth2_cfg_token_verify_add_options( _log, &verify, "metadata", url, "verify.exp=skip&verify.iss=optional&introspect.params=key2%3Dtwo"); ck_assert_ptr_eq(rv, NULL); rc = oauth2_token_verify(_log, NULL, verify, jwt, &json_payload); ck_assert_int_eq(rc, true); json_decref(json_payload); // get it from the cache rc = oauth2_token_verify(_log, NULL, verify, jwt, &json_payload); ck_assert_int_eq(rc, true); json_decref(json_payload); oauth2_cfg_token_verify_free(_log, verify); oauth2_mem_free(url); } END_TEST START_TEST(test_oauth2_verify_jwk_mtls) { bool rc = false; oauth2_cfg_token_verify_t *verify = NULL; char *jwt = "eyJhbGciOiAiUlMyNTYifQ." "ewogICJpc3MiOiAiaHR0cHM6Ly9zZXJ2ZXIuZXhhbXBsZS5jb20iLAogICJzdWIiOi" "AidHkud2ViYkBleGFtcGxlLmNvbSIsCiAgImV4cCI6IDE0OTM3MjY0MDAsCiAgIm5i" "ZiI6IDE0OTM3MjI4MDAsCiAgImNuZiI6eyJ4NXQjUzI1NiI6IkE0RHRMMkptVU1oQX" "N2Smo1dEt5bjY0U3F6bXVYYk1ySmEwbjc2MXk1djAifQp9.K5g_" "fMQkl6g9qo2uGDYDLrlQaTk_aEmP3601IYeVWrezftobh5IClflB-" "9YYvIU1O4Bswpl5bvBdkN7G7UXCwrzI6fFSVMvYqTxe0WlmLAnVH_RPqZ6Gxw_" "SpgemmXg8EPfDwa-14H5O-4S9a0JIE7Q1PBLsvqMhmO2WhpQFCvxnXJ1ho0m_" "dDH95y3ukJqvTe1mWwdd7K79GRnf4oFIi_" "Xm7iHOYYwEXwA1XNQgLcxbaGUKaA8N4yVLj2Q43JtxN7AUDQyfEhfzx5JhcQQ4t8gU" "GhovGvHh7LAYwpY8Ea6r3y2LSazhTm8cFFix1L6T5vFNH3MUFn2ouR8UDGA87Q"; char *jwk = "{\"kty\":\"RSA\",\"kid\":\"k1\",\"use\":\"sig\",\"n\":" "\"ym7jipmB37CgdonwGFVRuZmRfCl3lVh91fmm5CXHcNlUFZNR3D6Q9r63" "PpGRnfSsX3dOweh8BXd2AJ3mxvcE4z9xH--tA5EaOGI7IVF0Ip_" "i3flGg85xOADlb8rX3ez1NqkqMVJeeJypKhCCDNfvu_" "MXSdPLglU969YQF5xKAK8VFRfI6EfxxrZ_3Dvt2CKDV4LTPPJe9KI2_" "LuLQFBJ3MzlCTVxY6gyaljrWaDq7q5Lt3GB1KYS0Yd8COEQwsclOLm0Tdd" "hg4cle-DfaTMi7xsTZsPKyac5x17Y4N4isHhZULuWHX7o1bs809xcj-_-" "YCRq6C61je_mzFhuF4pczw\",\"e\":\"AQAB\"}"; json_t *json_payload = NULL; oauth2_http_request_t *request = NULL; const char *rv = NULL; rv = oauth2_cfg_token_verify_add_options( _log, &verify, "jwk", jwk, "verify.exp=skip&type=mtls&mtls.policy=required"); ck_assert_ptr_eq(rv, NULL); request = oauth2_http_request_init(_log); oauth2_http_request_scheme_set(_log, request, "https"); oauth2_http_request_hostname_set(_log, request, "resource.example.org"); // oauth2_http_request_port_set(); oauth2_http_request_path_set(_log, request, "/protectedresource"); oauth2_http_request_method_set(_log, request, OAUTH2_HTTP_METHOD_GET); oauth2_http_request_context_set( _log, request, OAUTH2_TLS_CERT_VAR_NAME, "-----BEGIN " "CERTIFICATE-----" "\nMIIBBjCBrAIBAjAKBggqhkjOPQQDAjAPMQ0wCwYDVQQDDARtdGxzMB4X" "DTE4MTAxODEyMzcwOVoXDTIyMDUwMjEyMzcwOVowDzENMAsGA1UEAwwEbX" "RsczBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABNcnyxwqV6hY8QnhxxzF" "Q03C7HKW9OylMbnQZjjJ/Au08/" "coZwxS7LfA4vOLS9WuneIXhbGGWvsDSb0tH6IxLm8wCgYIKoZIzj0EAwID" "SQAwRgIhAP0RC1E+vwJD/D1AGHGzuri+hlV/" "PpQEKTWUVeORWz83AiEA5x2eXZOVbUlJSGQgjwD5vaUaKlLR50Q2DmFfQj" "1L+SY=\n-----END CERTIFICATE-----"); rc = oauth2_token_verify(_log, request, verify, jwt, &json_payload); ck_assert_int_eq(rc, true); oauth2_http_request_free(_log, request); oauth2_cfg_token_verify_free(_log, verify); json_decref(json_payload); /* oauth2_nv_list_free(_log, params); */ } END_TEST Suite *oauth2_check_oauth2_suite() { Suite *s = suite_create("oauth2"); TCase *c = tcase_create("core"); liboauth2_check_register_http_callbacks(oauth2_check_http_base_path(), oauth2_check_oauth2_serve_get, oauth2_check_oauth2_serve_post); tcase_add_checked_fixture(c, setup, teardown); tcase_add_test(c, test_oauth2_auth_client_secret_basic); tcase_add_test(c, test_oauth2_auth_client_secret_post); tcase_add_test(c, test_oauth2_auth_client_secret_jwt); tcase_add_test(c, test_oauth2_auth_private_key_jwt); tcase_add_test(c, test_oauth2_auth_client_cert); tcase_add_test(c, test_oauth2_auth_http_basic); tcase_add_test(c, test_oauth2_auth_none); tcase_add_test(c, test_oauth2_verify_clone); tcase_add_test(c, test_oauth2_verify_jwks_uri); tcase_add_test(c, test_oauth2_verify_jwk); tcase_add_test(c, test_oauth2_verify_jwk_dpop); tcase_add_test(c, test_oauth2_verify_eckey_uri); tcase_add_test(c, test_oauth2_verify_aws_alb); tcase_add_test(c, test_oauth2_verify_token_introspection); tcase_add_test(c, test_oauth2_verify_token_plain); tcase_add_test(c, test_oauth2_verify_token_base64); tcase_add_test(c, test_oauth2_verify_token_base64url); tcase_add_test(c, test_oauth2_verify_token_hex); tcase_add_test(c, test_oauth2_verify_token_pem); tcase_add_test(c, test_oauth2_verify_token_pubkey); tcase_add_test(c, test_oauth2_verify_token_metadata); tcase_add_test(c, test_oauth2_verify_jwk_mtls); suite_add_tcase(s, c); return s; } liboauth2-2.1.0/test/check_openidc.c000066400000000000000000001076261475305260400173260ustar00rootroot00000000000000/*************************************************************************** * * Copyright (C) 2018-2025 - ZmartZone Holding BV * * 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. * * @Author: Hans Zandbelt - hans.zandbelt@openidc.com * **************************************************************************/ #include "check_liboauth2.h" #include "oauth2/jose.h" #include "oauth2/mem.h" #include "oauth2/openidc.h" #include "oauth2/util.h" #include "cfg_int.h" #include "openidc_int.h" #include #include #include #include static oauth2_log_t *_log = 0; static char *_openidc_metadata = NULL; OAUTH2_CHECK_HTTP_PATHS static void setup(void) { _log = oauth2_init(OAUTH2_LOG_TRACE1, 0); } void oauth2_check_openidc_cleanup() { oauth2_check_http_base_free(); } static void teardown(void) { oauth2_shutdown(_log); if (_openidc_metadata != NULL) { oauth2_mem_free(_openidc_metadata); _openidc_metadata = NULL; } } static char *jwk_rsa_str = "{" "\"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\"" "}"; static cjose_jwk_t *jwk_rsa = NULL; static char *jwks_uri_path = "/jwks_uri"; static char *token_endpoint_path = "/token"; static char *userinfo_endpoint_path = "/userinfo"; static char *discovery_endpoint_path = "/.well-known/openid-configuration"; static cjose_jwk_t *oauth2_jwk_rsa_get() { cjose_err err; if (jwk_rsa == NULL) { jwk_rsa = cjose_jwk_import(jwk_rsa_str, strlen(jwk_rsa_str), &err); if (jwk_rsa == NULL) { fprintf( stderr, "## cjose_jwk_import failed: %s (%s:%s:%ld) \n%s\n", err.message, err.file, err.function, err.line, jwk_rsa_str); } } return jwk_rsa; } static char *oauth2_check_openidc_serve_get(const char *request) { char *rv = NULL, *s = NULL; cjose_err err; if (strncmp(request, jwks_uri_path, strlen(jwks_uri_path)) == 0) { // TODO: static s = cjose_jwk_to_json(oauth2_jwk_rsa_get(), false, &err); rv = oauth2_stradd(NULL, "{ \"keys\": [ ", s, " ] }"); cjose_get_dealloc()(s); } else if (strncmp(request, userinfo_endpoint_path, strlen(userinfo_endpoint_path)) == 0) { rv = oauth2_strdup("{ \"sub\": \"myclient\", " "\"myuserinfoclaim\": \"somevalue\" }"); } else if (strncmp(request, discovery_endpoint_path, strlen(discovery_endpoint_path)) == 0) { rv = oauth2_strdup("{ \"issuer\": \"https://op.example.org\" }"); } else { rv = oauth2_strdup("problem"); } return rv; } static bool _oauth2_check_openidc_idtoken_create(oauth2_log_t *log, cjose_jwk_t *jwk, const char *alg, const char *client_id, const char *aud, char **id_token) { bool rc = false; char *payload = NULL; json_t *json = NULL; cjose_header_t *hdr = NULL; cjose_jws_t *jws = NULL; const char *jwt = NULL; cjose_err err; oauth2_debug(_log, "## enter"); json = json_object(); json_object_set_new(json, OAUTH2_JOSE_JWT_ISS, json_string(client_id)); json_object_set_new(json, OAUTH2_JOSE_JWT_SUB, json_string(client_id)); json_object_set_new(json, OAUTH2_JOSE_JWT_AUD, json_string(aud)); json_object_set_new(json, OAUTH2_JOSE_JWT_EXP, json_integer(oauth2_time_now_sec() + 60)); json_object_set_new(json, OAUTH2_JOSE_JWT_IAT, json_integer(oauth2_time_now_sec())); payload = json_dumps(json, JSON_PRESERVE_ORDER | JSON_COMPACT); hdr = cjose_header_new(&err); if (hdr == NULL) { oauth2_error(_log, "cjose_header_new failed: %s", err.message); goto end; } if (cjose_header_set(hdr, CJOSE_HDR_ALG, alg, &err) == false) { oauth2_error(_log, "cjose_header_set %s:%s failed: %s", CJOSE_HDR_ALG, alg, err.message); goto end; } if (cjose_header_set(hdr, OAUTH2_JOSE_HDR_TYP, OAUTH2_JOSE_HDR_TYP_JWT, &err) == false) { oauth2_error(_log, "cjose_header_set %s:%s failed: %s", OAUTH2_JOSE_HDR_TYP, OAUTH2_JOSE_HDR_TYP_JWT, err.message); goto end; } jws = cjose_jws_sign(jwk, hdr, (const uint8_t *)payload, strlen(payload), &err); if (jws == NULL) { oauth2_error(_log, "cjose_jws_sign failed: %s", err.message); goto end; } if (cjose_jws_export(jws, &jwt, &err) == false) { oauth2_error(_log, "cjose_jws_export failed: %s", err.message); goto end; } *id_token = oauth2_strdup(jwt); rc = true; end: if (json) json_decref(json); if (payload) free(payload); if (hdr) cjose_header_release(hdr); if (jws) cjose_jws_release(jws); oauth2_debug(_log, "## return: %d", rc); return rc; } static char *oauth2_check_openidc_serve_post(const char *request) { oauth2_nv_list_t *params = NULL; char *data = NULL; const char *code = NULL; const char *sep = "****"; char *rv = NULL; char *id_token = NULL; if (strncmp(request, token_endpoint_path, strlen(token_endpoint_path)) == 0) { request += strlen(token_endpoint_path) + 5; data = strstr(request, sep); if (data == NULL) goto error; data += strlen(sep); if (oauth2_parse_form_encoded_params(_log, data, ¶ms) == false) goto error; code = oauth2_nv_list_get(_log, params, "code"); if (code == NULL) goto error; if (_oauth2_check_openidc_idtoken_create( _log, oauth2_jwk_rsa_get(), "RS256", "myclient", "myclient", &id_token) == false) goto error; rv = oauth2_stradd(NULL, "{ \"id_token\": \"", id_token, "\", "); rv = oauth2_stradd(rv, "\"access_token\": \"", "xxxx", "\" }"); oauth2_mem_free(id_token); oauth2_nv_list_free(_log, params); goto end; } error: rv = oauth2_strdup("problem"); end: return rv; } START_TEST(test_openidc_cfg) { bool rc = false; oauth2_cfg_openidc_t *c = NULL, *c2 = NULL, *c3 = NULL, *c4 = NULL, *c5 = NULL; oauth2_http_request_t *r = NULL; oauth2_openidc_provider_t *p = NULL; oauth2_cfg_openidc_provider_resolver_t *pr = NULL; char *value = NULL, *rv = NULL; c = oauth2_cfg_openidc_init(_log); r = oauth2_http_request_init(_log); rc = oauth2_cfg_openidc_redirect_uri_set( _log, c, "https://example.org/redirect_uri"); ck_assert_int_eq(rc, true); value = oauth2_cfg_openidc_redirect_uri_get(_log, c, r); ck_assert_str_eq(value, "https://example.org/redirect_uri"); oauth2_mem_free(value); rc = oauth2_cfg_openidc_redirect_uri_set( _log, c, "https://example.com/redirect_uri"); ck_assert_int_eq(rc, true); value = oauth2_cfg_openidc_redirect_uri_get(_log, c, r); ck_assert_str_eq(value, "https://example.com/redirect_uri"); oauth2_mem_free(value); rc = oauth2_cfg_openidc_redirect_uri_set(_log, c, "/redirect_uri"); ck_assert_int_eq(rc, true); value = oauth2_cfg_openidc_redirect_uri_get(_log, c, r); ck_assert_ptr_eq(value, NULL); rc = oauth2_http_request_header_set(_log, r, "Host", "example.com"); ck_assert_int_eq(rc, true); value = oauth2_cfg_openidc_redirect_uri_get(_log, c, r); ck_assert_str_eq(value, "https://example.com/redirect_uri"); oauth2_mem_free(value); p = oauth2_openidc_provider_init(_log); ck_assert_ptr_ne(p, NULL); value = oauth2_cfg_openidc_redirect_uri_get_iss(_log, c, r, p); // ck_assert_ptr_eq(value, NULL); ck_assert_str_eq(value, "https://example.com/redirect_uri"); oauth2_mem_free(value); rc = oauth2_openidc_provider_issuer_set(_log, p, "jan"); ck_assert_int_eq(rc, true); value = oauth2_cfg_openidc_redirect_uri_get_iss(_log, c, r, p); ck_assert_str_eq(value, "https://example.com/redirect_uri?iss=jan"); oauth2_mem_free(value); c2 = oauth2_cfg_openidc_clone(_log, c); ck_assert_ptr_ne(c2, NULL); value = oauth2_cfg_openidc_redirect_uri_get_iss(_log, c2, r, p); ck_assert_str_eq(value, "https://example.com/redirect_uri?iss=jan"); oauth2_mem_free(value); rv = oauth2_cfg_openidc_provider_resolver_set_options( _log, c, "string", "issuer=https://localhost", NULL); ck_assert_ptr_eq(rv, NULL); rv = oauth2_openidc_client_set_options(_log, c, "string", "client_id=mycc", NULL); ck_assert_ptr_eq(rv, NULL); c3 = oauth2_cfg_openidc_clone(_log, c); c4 = oauth2_cfg_openidc_init(_log); pr = oauth2_cfg_openidc_provider_resolver_init(_log); ck_assert_ptr_ne(pr, NULL); rc = oauth2_cfg_openidc_provider_resolver_set(_log, c4, pr); ck_assert_uint_eq(rc, true); rc = oauth2_cfg_openidc_handler_path_set(_log, c4, "/mypath"); ck_assert_uint_eq(rc, true); rc = oauth2_cfg_openidc_state_cookie_name_prefix_set(_log, c4, "mycookie-"); ck_assert_uint_eq(rc, true); c5 = oauth2_cfg_openidc_init(_log); oauth2_cfg_openidc_merge(_log, c5, c4, c); value = oauth2_cfg_openidc_redirect_uri_get_iss(_log, c5, r, p); ck_assert_str_eq(value, "https://example.com/redirect_uri?iss=jan"); oauth2_mem_free(value); ck_assert_ptr_ne(oauth2_cfg_openidc_provider_resolver_get(_log, c5), NULL); ck_assert_str_eq(oauth2_cfg_openidc_handler_path_get(_log, c5), "/mypath"); ck_assert_str_eq( oauth2_cfg_openidc_state_cookie_name_prefix_get(_log, c5), "mycookie-"); oauth2_cfg_openidc_free(_log, c5); c5 = oauth2_cfg_openidc_init(_log); oauth2_cfg_openidc_merge(_log, c5, c, c4); ck_assert_str_eq( oauth2_cfg_openidc_state_cookie_name_prefix_get(_log, c5), "mycookie-"); ck_assert_str_eq(oauth2_openidc_client_client_id_get( _log, oauth2_cfg_openidc_client_get(_log, c5)), "mycc"); oauth2_cfg_openidc_free(_log, c5); oauth2_cfg_openidc_free(_log, c4); oauth2_cfg_openidc_free(_log, c3); oauth2_cfg_openidc_free(_log, c2); oauth2_openidc_provider_free(_log, p); oauth2_http_request_free(_log, r); oauth2_cfg_openidc_free(_log, c); } END_TEST static char *test_openidc_metadata_get() { if (_openidc_metadata) goto end; char *token_endpoint = oauth2_stradd(NULL, oauth2_check_http_base_url(), token_endpoint_path, NULL); char *userinfo_endpoint = oauth2_stradd( NULL, oauth2_check_http_base_url(), userinfo_endpoint_path, NULL); char *jwks_uri = oauth2_stradd(NULL, oauth2_check_http_base_url(), jwks_uri_path, NULL); _openidc_metadata = oauth2_strdup("{ " "\"issuer\": \"https://op.example.org\"," "\"authorization_endpoint\": " "\"https://op.example.org/authorize\","); _openidc_metadata = oauth2_stradd( _openidc_metadata, "\"token_endpoint\": \"", token_endpoint, "\","); _openidc_metadata = oauth2_stradd(_openidc_metadata, "\"userinfo_endpoint\": \"", userinfo_endpoint, "\","); _openidc_metadata = oauth2_stradd(_openidc_metadata, "\"jwks_uri\": \"", jwks_uri, "\" }"); oauth2_mem_free(token_endpoint); oauth2_mem_free(jwks_uri); oauth2_mem_free(userinfo_endpoint); end: return _openidc_metadata; } START_TEST(test_openidc_proto_state) { bool rc = false; json_t *json = NULL; oauth2_cfg_openidc_t *c = NULL; oauth2_http_request_t *r = oauth2_http_request_init(_log); oauth2_http_response_t *response = NULL; oauth2_openidc_provider_t *provider = NULL; char *value = NULL; const char *cookie = NULL; c = oauth2_cfg_openidc_init(_log); oauth2_cfg_openidc_provider_resolver_set_options( _log, c, "string", test_openidc_metadata_get(), NULL); oauth2_openidc_proto_state_t *p1 = oauth2_openidc_proto_state_init(_log); ck_assert_ptr_ne(p1, NULL); rc = oauth2_openidc_proto_state_set(_log, p1, "one", "string"); ck_assert_int_eq(rc, true); rc = oauth2_openidc_proto_state_set_int(_log, p1, "two", 2); ck_assert_int_eq(rc, true); json = oauth2_openidc_proto_state_json_get(p1); ck_assert_ptr_ne(json, NULL); ck_assert_str_eq(json_string_value(json_object_get(json, "one")), "string"); ck_assert_int_eq(json_integer_value(json_object_get(json, "two")), 2); rc = oauth2_openidc_proto_state_set( _log, p1, _OAUTH2_OPENIDC_PROTO_STATE_KEY_ISSUER, "https://op.example.org"); ck_assert_int_eq(rc, true); rc = oauth2_openidc_proto_state_set_int( _log, p1, _OAUTH2_OPENIDC_PROTO_STATE_KEY_TIMESTAMP, oauth2_time_now_sec()); ck_assert_int_eq(rc, true); rc = oauth2_openidc_proto_state_set( _log, p1, _OAUTH2_OPENIDC_PROTO_STATE_KEY_TARGET_LINK_URI, "https://example.org/secure"); rc = _oauth2_openidc_state_validate(_log, c, r, p1, &provider); ck_assert_int_eq(rc, true); rc = oauth2_openidc_proto_state_target_link_uri_get(_log, p1, &value); ck_assert_int_eq(rc, true); ck_assert_str_eq(value, "https://example.org/secure"); oauth2_mem_free(value); value = NULL; oauth2_openidc_proto_state_t *p2 = oauth2_openidc_proto_state_clone(_log, p1); ck_assert_ptr_ne(c, NULL); json = oauth2_openidc_proto_state_json_get(p2); ck_assert_ptr_ne(json, NULL); ck_assert_str_eq(json_string_value(json_object_get(json, "one")), "string"); ck_assert_int_eq(json_integer_value(json_object_get(json, "two")), 2); oauth2_openidc_proto_state_free(_log, p2); oauth2_openidc_proto_state_free(_log, p1); oauth2_http_request_scheme_set(_log, r, "https"); oauth2_http_request_hostname_set(_log, r, "example.org"); oauth2_http_request_path_set(_log, r, "/secure"); response = oauth2_http_response_init(_log); rc = _oauth2_openidc_state_cookie_set(_log, c, provider, r, response, "1234", "4321"); ck_assert_int_eq(rc, true); cookie = oauth2_http_response_header_get(_log, response, "Set-Cookie"); ck_assert_ptr_ne(strstr(cookie, "openidc_state_1234="), NULL); oauth2_openidc_proto_state_t *p3 = NULL; oauth2_http_request_header_set(_log, r, "Cookie", cookie); rc = _oauth2_openidc_state_cookie_get(_log, c, r, response, "1234", &p3); ck_assert_int_eq(rc, true); ck_assert_ptr_ne(p3, NULL); rc = oauth2_openidc_proto_state_target_link_uri_get(_log, p3, &value); ck_assert_int_eq(rc, true); ck_assert_ptr_ne(value, NULL); ck_assert_str_eq(value, "https://example.org/secure"); oauth2_mem_free(value); value = NULL; rc = oauth2_openidc_proto_state_pkce_get(_log, p3, &value); ck_assert_int_eq(rc, true); ck_assert_ptr_ne(value, NULL); ck_assert_str_eq(value, "4321"); oauth2_mem_free(value); value = NULL; oauth2_openidc_proto_state_free(_log, p3); oauth2_openidc_provider_free(_log, provider); oauth2_http_response_free(_log, response); oauth2_http_request_free(_log, r); oauth2_cfg_openidc_free(_log, c); } END_TEST static void _test_openidc_resolve_to_false(oauth2_cfg_openidc_t *c, oauth2_http_request_t *r, const char *metadata) { bool rc = false; char *rv = NULL; oauth2_openidc_provider_t *provider = NULL; rv = oauth2_cfg_openidc_provider_resolver_set_options(_log, c, "string", metadata, NULL); ck_assert_ptr_eq(rv, NULL); rc = _oauth2_openidc_provider_resolve(_log, c, r, NULL, &provider); ck_assert_int_eq(rc, false); ck_assert_ptr_eq(NULL, provider); } START_TEST(test_openidc_resolver_url) { bool rc = false; char *rv = NULL; oauth2_cfg_openidc_t *c = NULL; oauth2_http_request_t *r = NULL; oauth2_openidc_provider_t *provider = NULL; char *discovery_uri = oauth2_stradd(NULL, oauth2_check_http_base_url(), discovery_endpoint_path, NULL); c = oauth2_cfg_openidc_init(_log); r = oauth2_http_request_init(_log); rv = oauth2_cfg_openidc_provider_resolver_set_options( _log, c, "url", discovery_uri, NULL); ck_assert_ptr_eq(rv, NULL); rc = _oauth2_openidc_provider_resolve(_log, c, r, NULL, &provider); ck_assert_int_eq(rc, true); ck_assert_ptr_ne(NULL, provider); ck_assert_str_eq("https://op.example.org", oauth2_openidc_provider_issuer_get(_log, provider)); oauth2_openidc_provider_free(_log, provider); provider = NULL; oauth2_openidc_provider_free(_log, provider); provider = NULL; oauth2_mem_free(discovery_uri); oauth2_http_request_free(_log, r); oauth2_cfg_openidc_free(_log, c); } END_TEST START_TEST(test_openidc_resolver) { bool rc = false; char *rv = NULL; oauth2_cfg_openidc_t *c = NULL; oauth2_http_request_t *r = NULL; oauth2_openidc_provider_t *provider = NULL; char filename[512]; c = oauth2_cfg_openidc_init(_log); r = oauth2_http_request_init(_log); rv = oauth2_cfg_openidc_provider_resolver_set_options( _log, c, "string", test_openidc_metadata_get(), NULL); ck_assert_ptr_eq(rv, NULL); rc = _oauth2_openidc_provider_resolve(_log, c, r, NULL, &provider); ck_assert_int_eq(rc, true); ck_assert_ptr_ne(NULL, provider); ck_assert_str_eq("https://op.example.org", oauth2_openidc_provider_issuer_get(_log, provider)); oauth2_openidc_provider_free(_log, provider); provider = NULL; sprintf((char *)filename, "%s/%s", getenv("srcdir") ? getenv("srcdir") : ".", "test/provider.json"); rv = oauth2_cfg_openidc_provider_resolver_set_options(_log, c, "file", filename, NULL); ck_assert_ptr_eq(rv, NULL); rc = _oauth2_openidc_provider_resolve(_log, c, r, NULL, &provider); ck_assert_int_eq(rc, true); ck_assert_ptr_ne(NULL, provider); ck_assert_str_eq("https://pingfed:9031", oauth2_openidc_provider_issuer_get(_log, provider)); ck_assert_ptr_ne( NULL, oauth2_openidc_provider_authorization_endpoint_get(_log, provider)); ck_assert_ptr_ne( NULL, oauth2_openidc_provider_token_endpoint_get(_log, provider)); /* ck_assert_ptr_ne(NULL, oauth2_openidc_provider_token_endpoint_auth_get( _log, provider)); ck_assert_int_eq( false, oauth2_openidc_provider_ssl_verify_get(_log, provider)); */ ck_assert_ptr_ne(NULL, oauth2_openidc_provider_jwks_uri_get(_log, provider)); /* ck_assert_ptr_ne(NULL, oauth2_openidc_provider_scope_get(_log, provider)); ck_assert_ptr_ne(NULL, oauth2_openidc_provider_client_id_get(_log, provider)); ck_assert_ptr_ne( NULL, oauth2_openidc_provider_client_secret_get(_log, provider)); ck_assert_int_eq( true, oauth2_openidc_provider_ssl_verify_set(_log, provider, true)); */ ck_assert_int_eq(true, oauth2_openidc_provider_authorization_endpoint_set( _log, provider, "https://other.org/authorize")); ck_assert_int_eq(true, oauth2_openidc_provider_token_endpoint_set( _log, provider, "https://other.org/token")); ck_assert_int_eq(true, oauth2_openidc_provider_jwks_uri_set( _log, provider, "https://other.org/jwks_uri")); /* ck_assert_int_eq(true, oauth2_openidc_provider_scope_set( _log, provider, "openid profile other")); ck_assert_int_eq(true, oauth2_openidc_provider_client_id_set( _log, provider, "someclientid")); ck_assert_int_eq(true, oauth2_openidc_provider_client_secret_set( _log, provider, "someclientsecret")); */ oauth2_openidc_provider_free(_log, provider); provider = NULL; _test_openidc_resolve_to_false(c, r, NULL); _test_openidc_resolve_to_false(c, r, ""); _test_openidc_resolve_to_false(c, r, "{"); _test_openidc_resolve_to_false(c, r, "{}"); _test_openidc_resolve_to_false(c, r, "{ \"issuer\": 0 }"); _test_openidc_resolve_to_false(c, r, "{ \"authorization_endpoint\": 1, " "\"issuer\": \"https://example.org\" }"); _test_openidc_resolve_to_false( c, r, "{ \"token_endpoint\": 1, \"authorization_endpoint\": " "\"https://example.org/authorize\", \"issuer\": " "\"https://example.org\" }"); _test_openidc_resolve_to_false( c, r, "{ \"jwks_uri\": 0, \"token_endpoint\": " "\"https://example.org/authorize\", \"authorization_endpoint\": " "\"https://example.org/authorize\", \"issuer\": " "\"https://example.org\" }"); rv = oauth2_cfg_openidc_provider_resolver_set_options(_log, c, "dir", NULL, NULL); ck_assert_ptr_eq(rv, NULL); rc = _oauth2_openidc_provider_resolve(_log, c, r, NULL, &provider); ck_assert_int_eq(rc, false); oauth2_http_request_free(_log, r); oauth2_cfg_openidc_free(_log, c); } END_TEST START_TEST(test_openidc_client) { char *rv = NULL; oauth2_cfg_openidc_t *cfg = NULL; oauth2_openidc_client_t *client = NULL; char filename[512]; cfg = oauth2_cfg_openidc_init(_log); ck_assert_ptr_ne(cfg, NULL); sprintf((char *)filename, "%s/%s", getenv("srcdir") ? getenv("srcdir") : ".", "test/client.json"); rv = oauth2_openidc_client_set_options(_log, cfg, "file", filename, "ssl_verify=false"); ck_assert_ptr_eq(rv, NULL); client = oauth2_cfg_openidc_client_get(_log, cfg); ck_assert_ptr_ne(client, NULL); ck_assert_str_eq(oauth2_openidc_client_client_id_get(_log, client), "a_client"); ck_assert_uint_eq(oauth2_openidc_client_ssl_verify_get(_log, client), false); rv = oauth2_openidc_client_set_options( _log, cfg, "json", "{ \"client_id\": \"b_client\" }", "ssl_verify=true"); ck_assert_str_eq(oauth2_openidc_client_client_id_get(_log, client), "b_client"); ck_assert_ptr_eq(rv, NULL); ck_assert_uint_eq(oauth2_openidc_client_ssl_verify_get(_log, client), true); rv = oauth2_openidc_client_set_options( _log, cfg, "string", "client_id=bla&client_secret=bla&token_endpoint_auth_method=bogus", NULL); ck_assert_ptr_ne(rv, NULL); oauth2_mem_free(rv); rv = oauth2_openidc_client_set_options(_log, cfg, "json", NULL, NULL); ck_assert_ptr_ne(rv, NULL); oauth2_mem_free(rv); rv = oauth2_openidc_client_set_options(_log, cfg, "json", "{", NULL); ck_assert_ptr_ne(rv, NULL); oauth2_mem_free(rv); rv = oauth2_openidc_client_set_options(_log, cfg, "json", "{ \"client_id\": 0 }", NULL); ck_assert_ptr_ne(rv, NULL); oauth2_mem_free(rv); rv = oauth2_openidc_client_set_options( _log, cfg, "json", "{ \"client_id\": \"valid\", \"client_secret\": 0 }", NULL); ck_assert_ptr_ne(rv, NULL); oauth2_mem_free(rv); rv = oauth2_openidc_client_set_options( _log, cfg, "json", "{ \"client_id\": \"valid\", \"client_secret\": \"valid\", " "\"scope\": 0 }", NULL); ck_assert_ptr_ne(rv, NULL); oauth2_mem_free(rv); rv = oauth2_openidc_client_set_options( _log, cfg, "json", "{ \"client_id\": \"valid\", \"client_secret\": \"valid\", " "\"scope\": \"valid\", \"token_endpoint_auth_method\": 0 }", NULL); ck_assert_ptr_ne(rv, NULL); oauth2_mem_free(rv); rv = oauth2_openidc_client_set_options( _log, cfg, "json", "{ \"client_id\": \"valid\", \"client_secret\": \"valid\", " "\"scope\": \"valid\", \"token_endpoint_auth_method\": \"bogus\" }", NULL); ck_assert_ptr_ne(rv, NULL); oauth2_mem_free(rv); rv = oauth2_openidc_client_set_options( _log, cfg, "json", "{ \"client_id\": \"valid\", \"client_secret\": \"valid\", " "\"scope\": \"valid\", \"token_endpoint_auth_method\": " "\"client_secret_post\" }", NULL); ck_assert_ptr_eq(rv, NULL); oauth2_cfg_openidc_free(_log, cfg); } END_TEST typedef struct _openidc_set_cookie_find_ctx_t { const char *name; const char *match; const char *found; } _openidc_set_cookie_find_ctx_t; static bool _test_openidc_response_set_cookie_match(oauth2_log_t *log, void *rec, const char *name, const char *value) { _openidc_set_cookie_find_ctx_t *ctx = (_openidc_set_cookie_find_ctx_t *)rec; if (strncasecmp("Set-Cookie", name, strlen("Set-Cookie")) == 0) { if (strstr(value, ctx->name) == value) { if (ctx->match != NULL) { if (strstr(value, ctx->match) != NULL) { ctx->found = value; } } else { ctx->found = value; } } } return (ctx->found == NULL); } static const char * _test_openidc_response_set_cookie_find(const oauth2_http_response_t *response, const char *name, const char *match) { _openidc_set_cookie_find_ctx_t ctx; ctx.name = name; ctx.match = match; ctx.found = NULL; oauth2_http_response_headers_loop( _log, response, _test_openidc_response_set_cookie_match, &ctx); return ctx.found; } static bool _test_openidc_response_header_find_cookie_expire( const oauth2_http_response_t *response, const char *name) { return (_test_openidc_response_set_cookie_find( response, name, "Expires=Thu, 01 Jan 1970 00:00:00 GMT; Max-Age=0") != NULL); } static void _openidc_verify_authentication_request_state( const oauth2_http_response_t *response, char **r_state, char **state_cookie_name, char **state_cookie) { bool rc = false; const char *location = NULL; char *state = NULL; ck_assert_ptr_ne(NULL, response); ck_assert_uint_eq(oauth2_http_response_status_code_get(_log, response), 302); location = oauth2_http_response_header_get(_log, response, "Location"); ck_assert_ptr_ne(NULL, strstr(location, "response_type=code")); ck_assert_ptr_ne(NULL, strstr(location, "https://op.example.org/authorize")); state = strstr(location, "state="); ck_assert_ptr_ne(NULL, state); state += strlen("state="); *r_state = oauth2_strdup(state); char *p = strstr(*r_state, "&"); if (p) *p = '\0'; *state_cookie_name = oauth2_stradd(NULL, "openidc_state_", *r_state, NULL); rc = _test_openidc_response_header_find_cookie_expire( response, *state_cookie_name); ck_assert_int_eq(rc, false); *state_cookie = oauth2_strdup(_test_openidc_response_set_cookie_find( response, *state_cookie_name, NULL)); ck_assert_ptr_ne(NULL, *state_cookie); } static void _test_openidc_handle(oauth2_cfg_openidc_t *c) { bool rc = false; oauth2_http_request_t *r = NULL; oauth2_http_response_t *response = NULL; const char *location = NULL; char *state = NULL, *state_cookie_name = NULL, *state_cookie = NULL; char *query_str = NULL, *session_cookie = NULL; json_t *claims = NULL; r = oauth2_http_request_init(_log); rc = oauth2_http_request_path_set(_log, r, "/secure"); ck_assert_int_eq(rc, true); rc = oauth2_http_request_header_set(_log, r, "Host", "app.example.org"); ck_assert_int_eq(rc, true); rc = oauth2_http_request_header_set(_log, r, "Accept", "text/html"); ck_assert_int_eq(rc, true); rc = oauth2_openidc_handle(_log, c, r, &response, &claims); ck_assert_int_eq(rc, true); _openidc_verify_authentication_request_state( response, &state, &state_cookie_name, &state_cookie); json_decref(claims); oauth2_http_response_free(_log, response); response = NULL; oauth2_http_request_free(_log, r); r = oauth2_http_request_init(_log); rc = oauth2_http_request_path_set(_log, r, "/openid-connect/redirect_uri"); ck_assert_int_eq(rc, true); rc = oauth2_http_request_header_set(_log, r, "Host", "app.example.org"); ck_assert_int_eq(rc, true); rc = oauth2_http_request_header_set(_log, r, "Accept", "text/html"); ck_assert_int_eq(rc, true); rc = oauth2_http_request_header_set(_log, r, "Cookie", state_cookie); ck_assert_int_eq(rc, true); query_str = oauth2_stradd(NULL, "code=4321&state", "=", state); rc = oauth2_http_request_query_set(_log, r, query_str); ck_assert_int_eq(rc, true); rc = oauth2_openidc_handle(_log, c, r, &response, &claims); ck_assert_int_eq(rc, true); ck_assert_ptr_ne(NULL, response); ck_assert_uint_eq(oauth2_http_response_status_code_get(_log, response), 302); oauth2_mem_free(state_cookie); state_cookie = oauth2_strdup(oauth2_http_response_header_set_cookie_prefix_get( _log, response, state_cookie_name)); ck_assert_ptr_ne(NULL, state_cookie); rc = _test_openidc_response_header_find_cookie_expire( response, state_cookie_name); ck_assert_int_eq(rc, true); location = oauth2_http_response_header_get(_log, response, "Location"); ck_assert_ptr_ne(NULL, response); ck_assert_int_eq(strcmp(location, "https://app.example.org/secure"), 0); session_cookie = oauth2_strdup(oauth2_http_response_header_set_cookie_prefix_get( _log, response, "openidc_session")); ck_assert_ptr_ne(NULL, session_cookie); json_decref(claims); oauth2_http_response_free(_log, response); oauth2_http_request_free(_log, r); r = oauth2_http_request_init(_log); rc = oauth2_http_request_path_set(_log, r, "/secure"); ck_assert_int_eq(rc, true); rc = oauth2_http_request_header_set(_log, r, "Host", "app.example.org"); ck_assert_int_eq(rc, true); rc = oauth2_http_request_header_set(_log, r, "Accept", "text/html"); ck_assert_int_eq(rc, true); rc = oauth2_http_request_header_set(_log, r, "Cookie", session_cookie); ck_assert_int_eq(rc, true); rc = oauth2_openidc_handle(_log, c, r, &response, &claims); ck_assert_int_eq(rc, true); ck_assert_ptr_ne(NULL, response); // TODO: ck_assert_uint_eq(oauth2_http_response_status_code_get(_log, response), 0); // ck_assert_ptr_ne(NULL, // oauth2_http_response_header_set_cookie_prefix_get(_log, response, // "openidc_session")); ck_assert_str_eq( json_string_value(json_object_get(claims, "myuserinfoclaim")), "somevalue"); json_decref(claims); oauth2_http_request_free(_log, r); oauth2_http_response_free(_log, response); sleep(3); r = oauth2_http_request_init(_log); rc = oauth2_http_request_path_set(_log, r, "/secure"); ck_assert_int_eq(rc, true); rc = oauth2_http_request_header_set(_log, r, "Host", "app.example.org"); ck_assert_int_eq(rc, true); rc = oauth2_http_request_header_set(_log, r, "Accept", "text/html"); ck_assert_int_eq(rc, true); rc = oauth2_http_request_header_set(_log, r, "Cookie", session_cookie); ck_assert_int_eq(rc, true); response = NULL; rc = oauth2_openidc_handle(_log, c, r, &response, &claims); ck_assert_int_eq(rc, true); ck_assert_ptr_ne(NULL, response); ck_assert_uint_eq(oauth2_http_response_status_code_get(_log, response), 302); oauth2_http_request_free(_log, r); oauth2_http_response_free(_log, response); oauth2_mem_free(state); oauth2_mem_free(query_str); oauth2_mem_free(state_cookie_name); oauth2_mem_free(state_cookie); oauth2_mem_free(session_cookie); } START_TEST(test_openidc_handle_cookie) { oauth2_cfg_openidc_t *c = NULL; oauth2_cfg_session_t *session_cfg = NULL; c = oauth2_cfg_openidc_init(_log); session_cfg = oauth2_cfg_session_init(_log); oauth2_cfg_session_set_options( _log, session_cfg, "cookie", "name=short_cookie&inactivity_timeout=2"); oauth2_cfg_openidc_provider_resolver_set_options( _log, c, "string", test_openidc_metadata_get(), "session=short_cookie"); oauth2_openidc_client_set_options( _log, c, "string", "token_endpoint_auth_method=client_secret_post&client_id=myclient&" "client_secret=" "mysecret&scope=openid%20profile", "ssl_verify=false"); _test_openidc_handle(c); oauth2_cfg_openidc_free(_log, c); } END_TEST START_TEST(test_openidc_handle_cache) { oauth2_cache_t *cache = NULL; oauth2_cfg_session_t *session_cfg = NULL; oauth2_cfg_openidc_t *c = NULL; char *rv = NULL; c = oauth2_cfg_openidc_init(_log); rv = oauth2_cfg_set_cache(_log, NULL, "shm", "name=memory&max_entries=5"); ck_assert_ptr_eq(rv, NULL); cache = oauth2_cache_obtain(_log, NULL); ck_assert_ptr_ne(cache, NULL); session_cfg = oauth2_cfg_session_init(_log); oauth2_cfg_session_set_options( _log, session_cfg, "cache", "name=short_memory&cache=memory&inactivity_timeout=2"); oauth2_cfg_openidc_provider_resolver_set_options( _log, c, "string", test_openidc_metadata_get(), "session=short_memory"); oauth2_openidc_client_set_options( _log, c, "string", "token_endpoint_auth_method=client_secret_post&client_id=myclient&" "client_secret=" "mysecret&scope=openid%20profile", "ssl_verify=false"); _test_openidc_handle(c); oauth2_cfg_openidc_free(_log, c); } END_TEST START_TEST(test_openidc_state_cookie) { bool rc = false; oauth2_cfg_openidc_t *c = NULL; oauth2_http_request_t *request = NULL; oauth2_http_response_t *response = NULL; char *state = NULL; char *state_cookie_name1 = NULL, *state_cookie_name2 = NULL, *state_cookie_name3 = NULL; char *state_cookie1 = NULL, *state_cookie2 = NULL, *state_cookie3 = NULL; c = oauth2_cfg_openidc_init(_log); oauth2_cfg_openidc_set_options( _log, c, "state.cookie.max=1&state.cookie.delete.oldest=true"); oauth2_cfg_openidc_provider_resolver_set_options( _log, c, "string", test_openidc_metadata_get(), NULL); oauth2_openidc_client_set_options( _log, c, "string", "client_id=myclient&client_secret=mysecret", NULL); request = oauth2_http_request_init(_log); oauth2_http_request_path_set(_log, request, "/secure"); oauth2_http_request_header_set(_log, request, "Host", "app.example.org"); oauth2_http_request_header_set(_log, request, "Accept", "text/html"); rc = oauth2_openidc_handle(_log, c, request, &response, NULL); ck_assert_int_eq(rc, true); _openidc_verify_authentication_request_state( response, &state, &state_cookie_name1, &state_cookie1); oauth2_mem_free(state); oauth2_http_response_free(_log, response); oauth2_http_request_header_add(_log, request, "Cookie", state_cookie1); rc = oauth2_openidc_handle(_log, c, request, &response, NULL); ck_assert_int_eq(rc, true); _openidc_verify_authentication_request_state( response, &state, &state_cookie_name2, &state_cookie2); oauth2_mem_free(state); oauth2_http_response_free(_log, response); oauth2_http_request_header_add(_log, request, "Cookie", state_cookie2); rc = oauth2_openidc_handle(_log, c, request, &response, NULL); ck_assert_int_eq(rc, true); _openidc_verify_authentication_request_state( response, &state, &state_cookie_name3, &state_cookie3); oauth2_mem_free(state); rc = _test_openidc_response_header_find_cookie_expire( response, state_cookie_name3); ck_assert_int_eq(rc, false); rc = _test_openidc_response_header_find_cookie_expire( response, state_cookie_name2); ck_assert_int_eq(rc, false); rc = _test_openidc_response_header_find_cookie_expire( response, state_cookie_name1); ck_assert_int_eq(rc, true); oauth2_mem_free(state_cookie_name1); oauth2_mem_free(state_cookie_name2); oauth2_mem_free(state_cookie_name3); oauth2_mem_free(state_cookie1); oauth2_mem_free(state_cookie2); oauth2_mem_free(state_cookie3); oauth2_http_request_free(_log, request); oauth2_http_response_free(_log, response); oauth2_cfg_openidc_free(_log, c); } END_TEST Suite *oauth2_check_openidc_suite() { Suite *s = suite_create("openidc"); TCase *c = tcase_create("core"); liboauth2_check_register_http_callbacks( oauth2_check_http_base_path(), oauth2_check_openidc_serve_get, oauth2_check_openidc_serve_post); tcase_add_checked_fixture(c, setup, teardown); tcase_add_test(c, test_openidc_cfg); tcase_add_test(c, test_openidc_proto_state); tcase_add_test(c, test_openidc_resolver); tcase_add_test(c, test_openidc_client); tcase_add_test(c, test_openidc_handle_cookie); tcase_add_test(c, test_openidc_handle_cache); tcase_add_test(c, test_openidc_state_cookie); tcase_add_test(c, test_openidc_resolver_url); tcase_set_timeout(c, 8); suite_add_tcase(s, c); return s; } liboauth2-2.1.0/test/check_proto.c000066400000000000000000000320761475305260400170440ustar00rootroot00000000000000/*************************************************************************** * * Copyright (C) 2018-2025 - ZmartZone Holding BV * * 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. * * @Author: Hans Zandbelt - hans.zandbelt@openidc.com * **************************************************************************/ #include "check_liboauth2.h" #include "oauth2/mem.h" #include "oauth2/proto.h" #include #include static oauth2_log_t *_log = 0; OAUTH2_CHECK_HTTP_PATHS void oauth2_check_proto_cleanup() { oauth2_check_http_base_free(); } static void setup(void) { _log = oauth2_log_init(OAUTH2_LOG_TRACE1, 0); } static void teardown(void) { oauth2_log_free(_log); } static const char *my_token_name = "access_token"; static const char *my_token_name2 = "access_token2"; static const char *my_env_var_token = "my_env_var_token"; static const char *my_env_var_token2 = "my_env_var_token2"; static const char *my_post_token = "my_post_token"; static const char *my_post_token2 = "my_post_token2"; static bool _oauth2_check_proto_env_get_cb(oauth2_log_t *log, void *ctx, const char *name, char **value) { if (strcmp(name, my_token_name) == 0) *value = oauth2_strdup(my_env_var_token); if (strcmp(name, my_token_name2) == 0) *value = oauth2_strdup(my_env_var_token2); return true; } static bool _oauth2_check_proto_env_set_cb(oauth2_log_t *log, void *ctx, const char *name, const char *value) { return true; } static bool _oauth2_check_proto_read_form_post(oauth2_log_t *log, void *ctx, oauth2_nv_list_t **params) { *params = oauth2_nv_list_init(_log); oauth2_nv_list_add(_log, *params, my_token_name, my_post_token); oauth2_nv_list_add(_log, *params, my_token_name2, my_post_token2); return true; } static oauth2_cfg_server_callback_funcs_t _oauth2_check_proto_callbacks = { _oauth2_check_proto_env_get_cb, _oauth2_check_proto_env_set_cb, _oauth2_check_proto_read_form_post}; START_TEST(test_proto_get_source_token_environment) { char *token = NULL; char *rv = NULL; oauth2_cfg_source_token_t *cfg = NULL, *cfg2 = NULL; oauth2_http_request_t *request = NULL; request = oauth2_http_request_init(_log); cfg = oauth2_cfg_source_token_init(_log); ck_assert_ptr_ne(cfg, NULL); token = oauth2_get_source_token(_log, cfg, request, &_oauth2_check_proto_callbacks, NULL); ck_assert_ptr_ne(token, NULL); ck_assert_str_eq(token, my_env_var_token); oauth2_mem_free(token); cfg2 = oauth2_cfg_source_token_clone(_log, cfg); oauth2_cfg_source_token_free(_log, cfg); rv = oauth2_cfg_source_token_set_accept_in(_log, NULL, NULL, NULL); ck_assert_ptr_ne(rv, NULL); oauth2_mem_free(rv); rv = oauth2_cfg_token_in_set(_log, NULL, NULL, NULL, OAUTH2_CFG_TOKEN_IN_ENVVAR); ck_assert_ptr_ne(rv, NULL); oauth2_mem_free(rv); rv = oauth2_cfg_source_token_set_accept_in(_log, cfg2, "bogus", NULL); ck_assert_ptr_ne(rv, NULL); oauth2_mem_free(rv); rv = oauth2_cfg_source_token_set_accept_in( _log, cfg2, "environment", "name=access_token2&strip=false"); ck_assert_ptr_eq(rv, NULL); ck_assert_uint_eq(oauth2_cfg_source_token_get_strip(cfg2), false); token = oauth2_get_source_token(_log, cfg2, request, &_oauth2_check_proto_callbacks, NULL); ck_assert_ptr_ne(token, NULL); ck_assert_str_eq(token, my_env_var_token2); oauth2_mem_free(token); oauth2_cfg_source_token_free(_log, cfg2); oauth2_http_request_free(_log, request); } END_TEST START_TEST(test_proto_get_source_token_header) { char *rv = NULL; char *token = NULL; oauth2_cfg_source_token_t *cfg = NULL, *cfg2 = NULL; oauth2_http_request_t *request = NULL; request = oauth2_http_request_init(_log); cfg = oauth2_cfg_source_token_init(_log); ck_assert_ptr_ne(cfg, NULL); rv = oauth2_cfg_source_token_set_accept_in(_log, cfg, "header", NULL); ck_assert_ptr_eq(rv, NULL); oauth2_http_request_header_set(_log, request, "Authorization", "bearer my_header_token"); token = oauth2_get_source_token(_log, cfg, request, &_oauth2_check_proto_callbacks, NULL); ck_assert_ptr_ne(token, NULL); ck_assert_str_eq(token, "my_header_token"); oauth2_mem_free(token); cfg2 = oauth2_cfg_source_token_init(_log); ck_assert_ptr_ne(cfg2, NULL); oauth2_cfg_source_token_merge(_log, cfg2, NULL, cfg); oauth2_cfg_source_token_free(_log, cfg); rv = oauth2_cfg_source_token_set_accept_in(_log, cfg2, "header", "type=other&name=MyHeader"); ck_assert_ptr_eq(rv, NULL); oauth2_http_request_header_set(_log, request, "MyHeader", "other my_other_token"); token = oauth2_get_source_token(_log, cfg2, request, &_oauth2_check_proto_callbacks, NULL); ck_assert_ptr_ne(token, NULL); ck_assert_str_eq(token, "my_other_token"); oauth2_mem_free(token); oauth2_cfg_source_token_free(_log, cfg2); oauth2_http_request_free(_log, request); } END_TEST START_TEST(test_proto_get_source_token_query) { char *rv = NULL; char *token = NULL; oauth2_cfg_source_token_t *cfg = NULL, *cfg2 = NULL; oauth2_http_request_t *request = NULL; request = oauth2_http_request_init(_log); cfg = oauth2_cfg_source_token_init(_log); ck_assert_ptr_ne(cfg, NULL); rv = oauth2_cfg_source_token_set_accept_in(_log, cfg, "query", NULL); ck_assert_ptr_eq(rv, NULL); oauth2_http_request_query_set(_log, request, "access_token=my_query_token"); token = oauth2_get_source_token(_log, cfg, request, &_oauth2_check_proto_callbacks, NULL); ck_assert_ptr_ne(token, NULL); ck_assert_str_eq(token, "my_query_token"); oauth2_mem_free(token); oauth2_http_request_free(_log, request); cfg2 = oauth2_cfg_source_token_clone(_log, cfg); oauth2_cfg_source_token_free(_log, cfg); request = oauth2_http_request_init(_log); rv = oauth2_cfg_source_token_set_accept_in(_log, cfg2, "query", "name=access_token2"); ck_assert_ptr_eq(rv, NULL); oauth2_http_request_query_set(_log, request, "access_token2=my_query_token2"); token = oauth2_get_source_token(_log, cfg2, request, &_oauth2_check_proto_callbacks, NULL); ck_assert_ptr_ne(token, NULL); ck_assert_str_eq(token, "my_query_token2"); oauth2_mem_free(token); oauth2_cfg_source_token_free(_log, cfg2); oauth2_http_request_free(_log, request); } END_TEST START_TEST(test_proto_get_source_token_cookie) { char *rv = NULL; char *token = NULL; oauth2_cfg_source_token_t *cfg = NULL; oauth2_http_request_t *request = NULL; request = oauth2_http_request_init(_log); cfg = oauth2_cfg_source_token_init(_log); ck_assert_ptr_ne(cfg, NULL); rv = oauth2_cfg_source_token_set_accept_in(_log, cfg, "cookie", NULL); ck_assert_ptr_eq(rv, NULL); oauth2_http_request_cookie_set(_log, request, "access_token", "my_cookie_token"); token = oauth2_get_source_token(_log, cfg, request, &_oauth2_check_proto_callbacks, NULL); ck_assert_ptr_ne(token, NULL); ck_assert_str_eq(token, "my_cookie_token"); oauth2_mem_free(token); oauth2_cfg_source_token_free(_log, cfg); cfg = oauth2_cfg_source_token_init(_log); ck_assert_ptr_ne(cfg, NULL); rv = oauth2_cfg_source_token_set_accept_in(_log, cfg, "cookie", "name=access_token_cookie"); ck_assert_ptr_eq(rv, NULL); oauth2_http_request_cookie_set(_log, request, "access_token_cookie", "my_cookie_token2"); token = oauth2_get_source_token(_log, cfg, request, &_oauth2_check_proto_callbacks, NULL); ck_assert_ptr_ne(token, NULL); ck_assert_str_eq(token, "my_cookie_token2"); oauth2_mem_free(token); oauth2_cfg_source_token_free(_log, cfg); oauth2_http_request_free(_log, request); } END_TEST START_TEST(test_proto_get_source_token_post) { const char *rv = NULL; char *token = NULL; oauth2_cfg_source_token_t *cfg = NULL; oauth2_http_request_t *request = NULL; request = oauth2_http_request_init(_log); oauth2_http_request_method_set(_log, request, OAUTH2_HTTP_METHOD_POST); oauth2_http_request_header_set(_log, request, "Content-Type", "application/x-www-form-urlencoded"); cfg = oauth2_cfg_source_token_init(_log); ck_assert_ptr_ne(cfg, NULL); rv = oauth2_cfg_source_token_set_accept_in(_log, cfg, "post", NULL); ck_assert_ptr_eq(rv, NULL); token = oauth2_get_source_token(_log, cfg, request, &_oauth2_check_proto_callbacks, NULL); ck_assert_ptr_ne(token, NULL); ck_assert_str_eq(token, my_post_token); oauth2_mem_free(token); oauth2_cfg_source_token_free(_log, cfg); cfg = oauth2_cfg_source_token_init(_log); ck_assert_ptr_ne(cfg, NULL); rv = oauth2_cfg_source_token_set_accept_in(_log, cfg, "post", "name=access_token2"); ck_assert_ptr_eq(rv, NULL); token = oauth2_get_source_token(_log, cfg, request, &_oauth2_check_proto_callbacks, NULL); ck_assert_ptr_ne(token, NULL); ck_assert_str_eq(token, my_post_token2); oauth2_mem_free(token); oauth2_cfg_source_token_free(_log, cfg); oauth2_http_request_free(_log, request); } END_TEST START_TEST(test_proto_get_source_token_basic) { char *rv = NULL; char *token = NULL; oauth2_cfg_source_token_t *cfg = NULL; oauth2_http_request_t *request = NULL; request = oauth2_http_request_init(_log); cfg = oauth2_cfg_source_token_init(_log); ck_assert_ptr_ne(cfg, NULL); rv = oauth2_cfg_source_token_set_accept_in(_log, cfg, "basic", NULL); ck_assert_ptr_eq(rv, NULL); oauth2_http_request_header_set(_log, request, "Authorization", "Basic ZHVtbXk6bXlfYmFzaWNfdG9rZW4="); token = oauth2_get_source_token(_log, cfg, request, &_oauth2_check_proto_callbacks, NULL); ck_assert_ptr_ne(token, NULL); ck_assert_str_eq(token, "my_basic_token"); oauth2_mem_free(token); oauth2_cfg_source_token_free(_log, cfg); oauth2_http_request_free(_log, request); } END_TEST static char *token_endpoint_path = "/token"; static char *ropc_result_json = "{ \"access_token\": \"my_ropc_token\" }"; static char *cc_result_json = "{ \"access_token\": \"my_cc_token\" }"; static char *oauth2_check_proto_serve_post(const char *request) { oauth2_nv_list_t *params = NULL; char *data = NULL; const char *grant_type = NULL; const char *sep = "****"; char *rv = NULL; if (strncmp(request, token_endpoint_path, strlen(token_endpoint_path)) == 0) { request += strlen(token_endpoint_path) + 5; data = strstr(request, sep); if (data == NULL) goto error; data += strlen(sep); if (oauth2_parse_form_encoded_params(_log, data, ¶ms) == false) goto error; grant_type = oauth2_nv_list_get(_log, params, "grant_type"); if (grant_type == NULL) goto error; if ((grant_type) && (strcmp(grant_type, "password") == 0)) { // TODO: check username password rv = oauth2_strdup(ropc_result_json); } else if ((grant_type) && (strcmp(grant_type, "client_credentials") == 0)) { rv = oauth2_strdup(cc_result_json); } else { rv = oauth2_strdup( "{ \"error\": \"unsupported grant_type\" }"); } oauth2_nv_list_free(_log, params); goto end; } error: rv = oauth2_strdup("problem"); end: return rv; } START_TEST(test_proto_ropc) { bool rc = false; oauth2_cfg_ropc_t *cfg = NULL; char *token = NULL; oauth2_uint_t status_code = 0; char *rv = NULL; char *url = NULL; url = oauth2_stradd(NULL, NULL, oauth2_check_http_base_url(), token_endpoint_path); cfg = oauth2_cfg_ropc_init(_log); rv = oauth2_cfg_set_ropc(_log, cfg, url, NULL); ck_assert_ptr_eq(rv, NULL); rc = oauth2_ropc_exec(_log, cfg, "joe", "2Federate", &token, &status_code); ck_assert_int_eq(rc, true); ck_assert_str_eq(token, "my_ropc_token"); oauth2_mem_free(token); oauth2_cfg_ropc_free(_log, cfg); oauth2_mem_free(url); } END_TEST START_TEST(test_proto_cc) { bool rc = false; oauth2_cfg_cc_t *cfg = NULL; char *token = NULL; oauth2_uint_t status_code = 0; char *rv = NULL; char *url = NULL; url = oauth2_stradd(NULL, NULL, oauth2_check_http_base_url(), token_endpoint_path); cfg = oauth2_cfg_cc_init(_log); rv = oauth2_cfg_set_cc(_log, cfg, url, NULL); ck_assert_ptr_eq(rv, NULL); rc = oauth2_cc_exec(_log, cfg, &token, &status_code); ck_assert_int_eq(rc, true); ck_assert_str_eq(token, "my_cc_token"); oauth2_mem_free(token); oauth2_cfg_cc_free(_log, cfg); oauth2_mem_free(url); } END_TEST Suite *oauth2_check_proto_suite() { Suite *s = suite_create("proto"); TCase *c = tcase_create("core"); liboauth2_check_register_http_callbacks( oauth2_check_http_base_path(), NULL, oauth2_check_proto_serve_post); tcase_add_checked_fixture(c, setup, teardown); tcase_add_test(c, test_proto_get_source_token_environment); tcase_add_test(c, test_proto_get_source_token_header); tcase_add_test(c, test_proto_get_source_token_query); tcase_add_test(c, test_proto_get_source_token_cookie); tcase_add_test(c, test_proto_get_source_token_post); tcase_add_test(c, test_proto_get_source_token_basic); tcase_add_test(c, test_proto_ropc); tcase_add_test(c, test_proto_cc); suite_add_tcase(s, c); return s; } liboauth2-2.1.0/test/check_util.c000066400000000000000000000234111475305260400166470ustar00rootroot00000000000000/*************************************************************************** * * Copyright (C) 2018-2025 - ZmartZone Holding BV * * 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. * * @Author: Hans Zandbelt - hans.zandbelt@openidc.com * **************************************************************************/ #include "check_liboauth2.h" #include "oauth2/mem.h" #include "oauth2/util.h" #include #include #include static oauth2_mem_alloc_fn_t _save_alloc = NULL; static oauth2_mem_realloc_fn_t _save_realloc = NULL; static oauth2_mem_dealloc_fn_t _save_dealloc = NULL; static void *test_alloc(size_t amt) { return malloc(amt); } static void *test_realloc(void *ptr, size_t amt) { return realloc(ptr, amt); } static void test_dealloc(void *ptr) { free(ptr); } static void test_mem_functions_set() { _save_alloc = oauth2_mem_get_alloc(); _save_realloc = oauth2_mem_get_realloc(); _save_dealloc = oauth2_mem_get_dealloc(); oauth2_mem_set_alloc_funcs(test_alloc, test_realloc, test_dealloc); } static void test_mem_functions_reset() { oauth2_mem_set_alloc_funcs(_save_alloc, _save_realloc, _save_dealloc); _save_alloc = NULL; _save_realloc = NULL; _save_dealloc = NULL; } static void *test_alloc3(size_t amt, const char *file, int line) { return malloc(amt); } static void *test_realloc3(void *ptr, size_t amt, const char *file, int line) { return realloc(ptr, amt); } static void test_dealloc3(void *ptr, const char *file, int line) { free(ptr); } static oauth2_mem_alloc3_fn_t _save_alloc3 = NULL; static oauth2_mem_realloc3_fn_t _save_realloc3 = NULL; static oauth2_mem_dealloc3_fn_t _save_dealloc3 = NULL; static void test_mem_functions_set3() { _save_alloc3 = oauth2_mem_get_alloc3(); _save_realloc3 = oauth2_mem_get_realloc3(); _save_dealloc3 = oauth2_mem_get_dealloc3(); oauth2_mem_set_alloc_ex_funcs(test_alloc3, test_realloc3, test_dealloc3); } /* * TODO: why does this result in a timeout? * probably we can call this only once anyhow, but would it affect the other check_util tests? * perhaps separate it out in a different suite then? static void test_mem_functions_reset3() { cjose_set_alloc_ex_funcs(_save_alloc3, _save_realloc3, _save_dealloc3); oauth2_mem_set_alloc_ex_funcs(_save_alloc3, _save_realloc3, _save_dealloc3); _save_alloc3 = NULL; _save_realloc3 = NULL; _save_dealloc3 = NULL; } */ static oauth2_log_t *_log = 0; static void setup(void) { // provide coverage for oauth2_mem_calloc_callback // NB: the setup for cURL can only be initialized once and stays this // way test_mem_functions_set(); CURL *curl1 = NULL, *curl2 = NULL; curl1 = curl_easy_init(); curl2 = curl_easy_duphandle(curl1); curl_easy_cleanup(curl2); curl_easy_cleanup(curl1); // for coverage oauth2_log_free(NULL); _log = oauth2_init(OAUTH2_LOG_TRACE1, 0); } static void teardown(void) { oauth2_shutdown(_log); } START_TEST(test_log) { // mostly to complete coverage // TODO: could return bytes written from oauth2_log statements oauth2_debug(NULL, NULL); // TOOD: could return bool from oauth2_log_sink_add oauth2_log_sink_add(_log, &oauth2_log_sink_stderr); oauth2_info(_log, NULL); oauth2_info(_log, ""); oauth2_log_sink_level_set(&oauth2_log_sink_stderr, OAUTH2_LOG_ERROR); } END_TEST START_TEST(test_mem) { void *ptr = NULL; ck_assert(NULL != oauth2_mem_get_alloc()); ck_assert(NULL != oauth2_mem_get_realloc()); ck_assert(NULL != oauth2_mem_get_dealloc()); ck_assert(NULL != oauth2_mem_get_alloc3()); ck_assert(NULL != oauth2_mem_get_realloc3()); ck_assert(NULL != oauth2_mem_get_dealloc3()); test_mem_functions_set(); ck_assert(NULL != oauth2_mem_get_alloc()); ck_assert(NULL != oauth2_mem_get_realloc()); ck_assert(NULL != oauth2_mem_get_dealloc()); ck_assert(NULL != oauth2_mem_get_alloc3()); ck_assert(NULL != oauth2_mem_get_realloc3()); ck_assert(NULL != oauth2_mem_get_dealloc3()); ptr = oauth2_mem_alloc(8); ptr = oauth2_mem_get_realloc()(ptr, 8); oauth2_mem_free(ptr); test_mem_functions_reset(); ck_assert(NULL != oauth2_mem_get_alloc()); ck_assert(NULL != oauth2_mem_get_realloc()); ck_assert(NULL != oauth2_mem_get_dealloc()); ck_assert(NULL != oauth2_mem_get_alloc3()); ck_assert(NULL != oauth2_mem_get_realloc3()); ck_assert(NULL != oauth2_mem_get_dealloc3()); test_mem_functions_set3(); ck_assert(NULL != oauth2_mem_get_alloc()); ck_assert(NULL != oauth2_mem_get_realloc()); ck_assert(NULL != oauth2_mem_get_dealloc()); ck_assert(NULL != oauth2_mem_get_alloc3()); ck_assert(NULL != oauth2_mem_get_realloc3()); ck_assert(NULL != oauth2_mem_get_dealloc3()); ptr = oauth2_mem_alloc(8); ptr = oauth2_mem_get_realloc()(ptr, 8); oauth2_mem_free(ptr); // test_mem_functions_reset3(); } END_TEST START_TEST(test_strdup) { char *src = NULL, *dst = NULL; src = "bla"; dst = NULL; dst = oauth2_strdup(src); ck_assert_ptr_ne(dst, NULL); ck_assert_str_eq(src, dst); oauth2_mem_free(dst); src = NULL; dst = NULL; dst = oauth2_strdup(src); ck_assert_ptr_eq(dst, NULL); oauth2_mem_free(dst); } END_TEST START_TEST(test_base64url_encode) { size_t dst_len; char *dst; const char *plain = "Node.js is awesome."; const char *encoded = "Tm9kZS5qcyBpcyBhd2Vzb21lLg"; dst_len = oauth2_base64url_encode(_log, (const uint8_t *)plain, strlen(plain), &dst); ck_assert_int_eq(dst_len, strlen(encoded)); ck_assert_str_eq(dst, encoded); oauth2_mem_free(dst); dst = NULL; dst_len = oauth2_base64url_encode(_log, NULL, 0, &dst); ck_assert_ptr_eq(dst, NULL); ck_assert_int_eq(dst_len, 0); dst_len = oauth2_base64url_encode(_log, NULL, 0, NULL); ck_assert_int_eq(dst_len, 0); dst_len = oauth2_base64url_encode(_log, (const uint8_t *)"", 0, NULL); ck_assert_int_eq(dst_len, 0); } END_TEST START_TEST(test_base64url_decode) { uint8_t *dst; size_t dst_len; bool rc; const char *encoded = "Tm9kZS5qcyBpcyBhd2Vzb21lLg"; const char *plain = "Node.js is awesome."; rc = oauth2_base64url_decode(_log, encoded, &dst, &dst_len); ck_assert_int_eq(rc, true); ck_assert_int_eq(dst_len, strlen(plain)); ck_assert(strncmp((const char *)dst, plain, dst_len) == 0); oauth2_mem_free(dst); dst = NULL; dst_len = 0; rc = oauth2_base64url_decode(_log, NULL, &dst, &dst_len); ck_assert_int_eq(rc, false); ck_assert_ptr_eq(dst, NULL); ck_assert_int_eq(dst_len, 0); rc = oauth2_base64url_decode(_log, NULL, NULL, 0); ck_assert_int_eq(rc, false); ck_assert_ptr_eq(dst, NULL); ck_assert_int_eq(dst_len, 0); rc = oauth2_base64url_decode(_log, "", NULL, 0); ck_assert_int_eq(rc, false); ck_assert_ptr_eq(dst, NULL); ck_assert_int_eq(dst_len, 0); } END_TEST START_TEST(test_url_encode) { char *src = NULL, *dst = NULL, *enc = NULL; src = "bla bla"; enc = "bla%20bla"; dst = oauth2_url_encode(_log, src); ck_assert_str_eq(dst, enc); oauth2_mem_free(dst); src = "Hello Günter"; enc = "Hello%20G%C3%BCnter"; dst = oauth2_url_encode(_log, src); ck_assert_str_eq(dst, enc); oauth2_mem_free(dst); dst = NULL; src = NULL; dst = oauth2_url_encode(_log, src); ck_assert_ptr_eq(dst, NULL); } END_TEST START_TEST(test_url_decode) { char *dst = NULL, *src = NULL, *dec = NULL; src = "bla%20bla"; dec = "bla bla"; dst = oauth2_url_decode(_log, src); ck_assert_str_eq(dst, dec); oauth2_mem_free(dst); dst = NULL; src = "http://www.example.com/path/foo+bar/path?query+name=query+value"; dec = "http://www.example.com/path/foo bar/path?query name=query value"; dst = oauth2_url_decode(_log, src); ck_assert_str_eq(dst, dec); oauth2_mem_free(dst); src = "Hello%20G%C3%BCnter"; dec = "Hello Günter"; dst = oauth2_url_decode(_log, src); ck_assert_str_eq(dst, dec); oauth2_mem_free(dst); dst = NULL; src = NULL; dst = oauth2_url_decode(_log, src); ck_assert_ptr_eq(dst, NULL); } END_TEST START_TEST(test_html_encode) { char *src = NULL, *dst = NULL, *enc = NULL; src = "bla&bla"; enc = "bla&bla"; dst = oauth2_html_escape(_log, src); ck_assert_str_eq(dst, enc); oauth2_mem_free(dst); dst = NULL; // https://www.w3schools.com/php/func_string_htmlentities.asp src = "Go to w3schools.com"; enc = "<a href="https://www.w3schools.com">Go to " "w3schools.com</a>"; dst = oauth2_html_escape(_log, src); ck_assert_str_eq(dst, enc); oauth2_mem_free(dst); dst = NULL; src = NULL; dst = oauth2_html_escape(_log, src); ck_assert_ptr_eq(dst, NULL); } END_TEST START_TEST(test_random) { char *rv = NULL; rv = oauth2_rand_str(_log, 8); ck_assert_ptr_ne(rv, NULL); ck_assert_str_ne(rv, ""); ck_assert_int_eq(strlen(rv), 8); oauth2_mem_free(rv); rv = oauth2_rand_str(_log, 16); ck_assert_ptr_ne(rv, NULL); ck_assert_str_ne(rv, ""); ck_assert_int_eq(strlen(rv), 16); oauth2_mem_free(rv); rv = oauth2_rand_str(_log, 7); ck_assert_ptr_ne(rv, NULL); ck_assert_str_ne(rv, ""); ck_assert_int_eq(strlen(rv), 7); oauth2_mem_free(rv); } END_TEST Suite *oauth2_check_util_suite() { Suite *s = suite_create("util"); TCase *c = tcase_create("core"); tcase_add_checked_fixture(c, setup, teardown); tcase_add_test(c, test_log); tcase_add_test(c, test_mem); tcase_add_test(c, test_strdup); tcase_add_test(c, test_base64url_encode); tcase_add_test(c, test_base64url_decode); tcase_add_test(c, test_url_encode); tcase_add_test(c, test_url_decode); tcase_add_test(c, test_html_encode); tcase_add_test(c, test_random); suite_add_tcase(s, c); return s; } liboauth2-2.1.0/test/check_version.c000066400000000000000000000032141475305260400173560ustar00rootroot00000000000000/*************************************************************************** * * Copyright (C) 2018-2025 - ZmartZone Holding BV * * 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. * * @Author: Hans Zandbelt - hans.zandbelt@openidc.com * **************************************************************************/ #include "check_liboauth2.h" #include "oauth2/mem.h" #include "oauth2/util.h" #include "oauth2/version.h" #include START_TEST(test_version_defines) { ck_assert_str_eq(OAUTH2_PACKAGE_VERSION, PACKAGE_VERSION); ck_assert_str_eq(OAUTH2_PACKAGE_NAME, PACKAGE_NAME); } END_TEST START_TEST(test_version_function) { const char *version = oauth2_version(); ck_assert_str_eq(version, PACKAGE_VERSION); } END_TEST START_TEST(test_package_string) { const char *pkg_str = oauth2_package_string(); ck_assert_str_eq(pkg_str, PACKAGE_NAME "-" PACKAGE_VERSION); } END_TEST Suite *oauth2_check_version_suite() { Suite *s = suite_create("version"); TCase *c = tcase_create("core"); tcase_add_test(c, test_version_defines); tcase_add_test(c, test_version_function); tcase_add_test(c, test_package_string); suite_add_tcase(s, c); return s; } liboauth2-2.1.0/test/client.json000066400000000000000000000001001475305260400165300ustar00rootroot00000000000000{ "client_id": "a_client", "client_secret": "a_secret" } liboauth2-2.1.0/test/provider.json000066400000000000000000000006641475305260400171230ustar00rootroot00000000000000{ "issuer": "https://pingfed:9031", "authorization_endpoint": "https://pingfed:9031/as/authorization.oauth2", "token_endpoint": "https://pingfed:9031/as/token.oauth2", "revocation_endpoint": "https://pingfed:9031/as/revoke_token.oauth2", "userinfo_endpoint": "https://pingfed:9031/idp/userinfo.openid", "introspection_endpoint": "https://pingfed:9031/as/introspect.oauth2", "jwks_uri": "https://pingfed:9031/pf/JWKS" } liboauth2-2.1.0/test/server_stubs.c000066400000000000000000000115261475305260400172670ustar00rootroot00000000000000#ifdef HAVE_APACHE #include #include 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_conf_nc(apr_pool_t *p, char **line) { return ap_getword_conf(p, (const char **)line); } AP_DECLARE(int) ap_should_client_block(request_rec *r) { return 0; } AP_DECLARE(long) ap_get_client_block(request_rec *r, char *buffer, apr_size_t bufsiz) { return 0; } AP_DECLARE(const char *) ap_get_server_name(request_rec *r) { return "www.example.com"; } AP_DECLARE(const char *) ap_get_server_name_for_url(request_rec *r) { return "www.example.com"; } AP_DECLARE(int) ap_setup_client_block(request_rec *r, int read_policy) { return 0; } AP_DECLARE(const char *) ap_auth_type(request_rec *r) { return "oauth2"; } AP_DECLARE(const char *) ap_auth_name(request_rec *r) { return "oauth2"; } const char *ap_run_http_scheme(const request_rec *r) { return "https"; } 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, ...) { } #endif #ifdef HAVE_NGINX #undef LF #undef CR #undef CRLF #include #include void ngx_log_error_core(ngx_uint_t level, ngx_log_t *log, ngx_err_t err, const char *fmt, ...) { } ngx_pool_t *ngx_create_pool(size_t size, ngx_log_t *log) { ngx_pool_t *pool = (ngx_pool_t *)oauth2_mem_alloc(size); return pool; } void ngx_destroy_pool(ngx_pool_t *pool) { oauth2_mem_free(pool); } void *ngx_palloc(ngx_pool_t *pool, size_t size) { void *p = (void *)oauth2_mem_alloc(size); return p; } void *ngx_pnalloc(ngx_pool_t *pool, size_t size) { void *p = (void *)oauth2_mem_alloc(size); return p; } ngx_int_t ngx_pfree(ngx_pool_t *pool, void *p) { oauth2_mem_free(p); return NGX_OK; } void *ngx_list_push(ngx_list_t *l) { void *elt; ngx_list_part_t *last; last = l->last; if (last->nelts == l->nalloc) { /* the last part is full, allocate a new list part */ last = ngx_palloc(l->pool, sizeof(ngx_list_part_t)); if (last == NULL) { return NULL; } last->elts = ngx_palloc(l->pool, l->nalloc * l->size); if (last->elts == NULL) { return NULL; } last->nelts = 0; last->next = NULL; l->last->next = last; l->last = last; } elt = (char *)last->elts + l->size * last->nelts; last->nelts++; return elt; } ngx_http_variable_value_t * ngx_http_get_variable(ngx_http_request_t *r, ngx_str_t *name, ngx_uint_t key) { return NULL; } ngx_uint_t ngx_hash_strlow(u_char *dst, u_char *src, size_t n) { return 0; } ngx_uint_t ngx_cacheline_size = 64; ngx_uint_t ngx_hash_key(u_char *data, size_t len) { return (ngx_uint_t)(*data); } u_char *ngx_pstrdup(ngx_pool_t *pool, ngx_str_t *src) { u_char *dst = ngx_pnalloc(pool, src->len); memcpy(dst, src->data, src->len); return dst; } ngx_int_t ngx_hash_init(ngx_hash_init_t *hinit, ngx_hash_key_t *names, ngx_uint_t nelts) { return 0; } ngx_int_t ngx_hash_keys_array_init(ngx_hash_keys_arrays_t *ha, ngx_uint_t type) { return 0; } ngx_int_t ngx_hash_add_key(ngx_hash_keys_arrays_t *ha, ngx_str_t *key, void *value, ngx_uint_t flags) { return 0; } void *ngx_hash_find(ngx_hash_t *hash, ngx_uint_t key, u_char *name, size_t len) { return NULL; } ngx_http_variable_t *ngx_http_add_variable(ngx_conf_t *cf, ngx_str_t *name, ngx_uint_t flags) { return NULL; } ngx_array_t *ngx_array_create(ngx_pool_t *p, ngx_uint_t n, size_t size) { return NULL; } void *ngx_array_push(ngx_array_t *a) { return NULL; } ngx_int_t ngx_http_compile_complex_value(ngx_http_compile_complex_value_t *ccv) { return 0; } ngx_int_t ngx_http_complex_value(ngx_http_request_t *r, ngx_http_complex_value_t *val, ngx_str_t *value) { return 0; } #endif