pax_global_header00006660000000000000000000000064151364727550014531gustar00rootroot0000000000000052 comment=b941106ac9b9600198202d000a971d74a4f74fde scitokens-cpp-1.3.0/000077500000000000000000000000001513647275500143145ustar00rootroot00000000000000scitokens-cpp-1.3.0/.clang-format000066400000000000000000000000411513647275500166620ustar00rootroot00000000000000BasedOnStyle: LLVM IndentWidth: 4scitokens-cpp-1.3.0/.devcontainer/000077500000000000000000000000001513647275500170535ustar00rootroot00000000000000scitokens-cpp-1.3.0/.devcontainer/Dockerfile000066400000000000000000000025131513647275500210460ustar00rootroot00000000000000# Use latest Ubuntu as base FROM ubuntu:latest # Avoid warnings by switching to noninteractive ENV DEBIAN_FRONTEND=noninteractive # Configure apt and install packages RUN apt-get update \ && apt-get -y install --no-install-recommends \ # Build essentials and tools build-essential \ cmake \ ninja-build \ pkg-config \ git \ # Required dependencies from GitHub Actions workflow libssl-dev \ sqlite3 \ libsqlite3-dev \ libcurl4 \ libcurl4-openssl-dev \ uuid-dev \ libgtest-dev \ # C++ development and debugging tools gdb \ gdbserver \ valgrind \ clang \ clang-format \ clang-tidy \ lldb \ # Additional utilities curl \ wget \ vim \ nano \ htop \ tree \ zip \ unzip \ ca-certificates \ sudo \ # Clean up && apt-get autoremove -y \ && apt-get clean -y \ && rm -rf /var/lib/apt/lists/* # Create a non-root user to use ARG USERNAME=vscode # Create user and group without forcing specific UID/GID RUN groupadd $USERNAME \ && useradd -g $USERNAME -m $USERNAME \ && echo $USERNAME ALL=\(root\) NOPASSWD:ALL > /etc/sudoers.d/$USERNAME \ && chmod 0440 /etc/sudoers.d/$USERNAME # Switch back to dialog for any ad-hoc use of apt-get ENV DEBIAN_FRONTEND=dialog # Set the default user USER $USERNAME scitokens-cpp-1.3.0/.devcontainer/README.md000066400000000000000000000042431513647275500203350ustar00rootroot00000000000000# SciTokens C++ Development Container This devcontainer configuration enables GitHub Codespaces and local development container support for the scitokens-cpp project. ## Features ### Base Environment - **OS**: Latest Ubuntu - **User**: Non-root `vscode` user with sudo access ### Build Dependencies All dependencies from the GitHub Actions workflow are included: - `libssl-dev` - OpenSSL development files - `sqlite3` and `libsqlite3-dev` - SQLite database - `cmake` - Build system - `libcurl4` and `libcurl4-openssl-dev` - HTTP client library - `uuid-dev` - UUID generation library - `libgtest-dev` - Google Test framework ### Development Tools - **Build Tools**: build-essential, cmake, ninja-build, pkg-config - **Debuggers**: gdb, gdbserver, lldb - **Analysis Tools**: valgrind, clang-tidy - **Compilers**: gcc/g++ (via build-essential), clang - **Formatters**: clang-format - **Utilities**: git, curl, wget, vim, nano, htop, tree ### VSCode Extensions - C/C++ Extension Pack - CMake Tools - CMake Language Support - GitHub Copilot (if available) ## Usage ### GitHub Codespaces 1. Navigate to the repository on GitHub 2. Click the "Code" button 3. Select "Codespaces" tab 4. Click "Create codespace on [branch]" ### Local Development with VSCode 1. Install [Docker](https://www.docker.com/products/docker-desktop) 2. Install [VSCode](https://code.visualstudio.com/) 3. Install the [Dev Containers extension](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers) 4. Open the repository in VSCode 5. Click "Reopen in Container" when prompted (or use Command Palette: "Dev Containers: Reopen in Container") ## Building the Project After the container is created, submodules are automatically initialized. To build: ```bash # Create build directory mkdir -p build cd build # Configure with CMake cmake .. -DCMAKE_BUILD_TYPE=Release -DSCITOKENS_BUILD_UNITTESTS=ON # Build cmake --build . # Run tests ctest --verbose ``` ## Debugging The container includes both GDB and LLDB debuggers. You can: - Use VSCode's integrated debugging features - Run debuggers from the terminal - Use valgrind for memory analysis Example using GDB: ```bash gdb ./scitokens-test ``` scitokens-cpp-1.3.0/.devcontainer/devcontainer.json000066400000000000000000000017671513647275500224420ustar00rootroot00000000000000{ "name": "SciTokens C++ Development", "build": { "dockerfile": "Dockerfile" }, // Configure tool-specific properties. "customizations": { "vscode": { "settings": { "terminal.integrated.defaultProfile.linux": "bash", "C_Cpp.default.configurationProvider": "ms-vscode.cmake-tools" }, "extensions": [ "ms-vscode.cpptools", "ms-vscode.cpptools-extension-pack", "ms-vscode.cmake-tools", "twxs.cmake", "github.copilot", "github.copilot-chat" ] } }, // Use 'forwardPorts' to make a list of ports inside the container available locally. // "forwardPorts": [], // Use 'postCreateCommand' to run commands after the container is created. "postCreateCommand": "git config --global --add safe.directory /workspaces/scitokens-cpp && git submodule update --init --recursive", // Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root. "remoteUser": "vscode", "features": { "ghcr.io/devcontainers/features/git:1": {} } } scitokens-cpp-1.3.0/.github/000077500000000000000000000000001513647275500156545ustar00rootroot00000000000000scitokens-cpp-1.3.0/.github/copilot-instructions.md000066400000000000000000000100611513647275500224070ustar00rootroot00000000000000# GitHub Copilot Instructions for scitokens-cpp ## Project Overview scitokens-cpp is a C++ library for creating and validating SciTokens (JWT-based authorization tokens for scientific computing). The library uses JWT-cpp for token operations and supports OIDC discovery with JWKS for public key distribution. ## Building the Project ### Prerequisites - CMake 3.10 or later - C++11 compatible compiler (gcc, clang) - OpenSSL 1.1.1 or later (3.0+ recommended) - libuuid - sqlite3 - jwt-cpp (included as vendor submodule) ### Build Commands ```bash # Create build directory mkdir -p build cd build # Configure with CMake (enable tests with -DSCITOKENS_BUILD_UNITTESTS=ON) cmake .. -DSCITOKENS_BUILD_UNITTESTS=ON # Build all targets make # Install (optional) sudo make install ``` ### CMake Build Options - Tests are **disabled by default** - use `-DSCITOKENS_BUILD_UNITTESTS=ON` to enable - Build produces: - `libSciTokens.so` - Main library - `scitokens-test` - Unit tests (Google Test) - `scitokens-integration-test` - Integration tests with real HTTPS server - `scitokens-generate-jwks` - JWKS generation utility - Command-line tools: `scitokens-verify`, `scitokens-create`, `scitokens-list-access`, `scitokens-test-access` ## Running Tests ### Unit Tests ```bash cd build ./scitokens-test ``` Expected: 29 unit tests should pass ### Integration Tests Integration tests use CTest fixtures with setup/teardown phases: ```bash cd build/test ctest --output-on-failure ``` Or run specific test phases: ```bash ctest -R integration::setup # Start HTTPS JWKS server ctest -R integration::test # Run integration tests ctest -R integration::teardown # Stop server ``` **Integration test infrastructure:** - `test/jwks_server.py` - Python HTTPS server with OIDC discovery and JWKS endpoints - `test/integration-test-setup.sh` - Generates TLS certificates and starts server - `test/integration-test-teardown.sh` - Stops server gracefully - `test/integration_test.cpp` - C++ tests using real HTTPS connections Expected: 3 integration tests should pass (total time ~1-2 seconds) ### All Tests ```bash cd build/test ctest --output-on-failure ``` Expected: 32 total tests (29 unit + 3 integration) ## Code Style - C++11 standard - Use `clang-format` for formatting (configuration in project root) - Format before committing: `clang-format -i src/*.cpp src/*.h` ## Testing Infrastructure Details ### JWKS Server (test/jwks_server.py) Python HTTPS server that provides: - `/.well-known/openid-configuration` - OIDC discovery document - `/oauth2/certs` - JWKS public key endpoint Server features: - HTTP/1.1 with keep-alive support - TLS 1.2+ with self-signed certificates - Graceful shutdown with SIGTERM - Logs to `build/tests/integration/server.log` ### Integration Test Flow 1. **Setup**: Generate EC P-256 key pair, create JWKS, generate TLS certificates, start HTTPS server 2. **Test**: Create tokens, verify with JWKS discovery, test dynamic issuer enforcement 3. **Teardown**: Stop server, print logs if tests failed ### Debugging Integration Tests If integration tests fail: 1. Check server log: `cat build/tests/integration/server.log` 2. Verify server started: `cat build/tests/integration/server_ready` 3. Test HTTPS manually: `curl -k https://localhost:/.well-known/openid-configuration` ## Key Files - `src/scitokens.cpp`, `src/scitokens.h` - Main library API - `src/generate_jwks.cpp` - JWKS generation (EC P-256 keys) - `src/scitokens_internal.cpp` - Token validation and OIDC discovery - `src/scitokens_cache.cpp` - JWKS caching - `test/integration_test.cpp` - End-to-end integration tests - `test/main.cpp` - Google Test unit tests ## Development Workflow 1. Make code changes 2. Build: `cd build && cmake .. && make` 3. Run unit tests: `./scitokens-test` 4. Run integration tests: `cd test && ctest --output-on-failure` 5. Format code: `clang-format -i ` 6. Commit with descriptive message ## CI/CD GitHub Actions runs tests on: - Ubuntu 22.04 (OpenSSL 3.0.2) - Ubuntu 24.04 (OpenSSL 3.0.13) Integration tests verify TLS compatibility across OpenSSL versions. scitokens-cpp-1.3.0/.github/workflows/000077500000000000000000000000001513647275500177115ustar00rootroot00000000000000scitokens-cpp-1.3.0/.github/workflows/build-rpm.yml000066400000000000000000000006631513647275500223340ustar00rootroot00000000000000name: RPM Build on: pull_request: branches: - master push: branches: - master jobs: build-rpm: runs-on: ubuntu-latest steps: - uses: actions/checkout@v1 with: submodules: recursive # Custom rpm building from Derek to build RPM - uses: djw8605/docker-mock-rpmbuilder@master with: spec-file: rpm/scitokens-cpp.spec mock-config: epel-7-x86_64 scitokens-cpp-1.3.0/.github/workflows/ccpp.yml000066400000000000000000000053641513647275500213710ustar00rootroot00000000000000name: C/C++ CI on: pull_request: branches: - master push: branches: - master env: # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) BUILD_TYPE: Release jobs: build: strategy: matrix: external-gtest: [ YES, NO ] os: [ ubuntu-latest, ubuntu-22.04 ] asan: [ YES, NO ] runs-on: ${{ matrix.os }} name: Build with external_gtest=${{ matrix.external-gtest }} on ${{ matrix.os }} and asan=${{ matrix.asan }} steps: - uses: actions/checkout@v1 with: submodules: recursive - name: install deps run: | sudo apt update && sudo apt-get install libssl-dev sqlite3 libsqlite3-dev cmake libcurl4 libcurl4-openssl-dev uuid-dev libgtest-dev - name: Create Build Environment # Some projects don't allow in-source building, so create a separate build directory # We'll use this as our working directory for all subsequent commands run: cmake -E make_directory ${{runner.workspace}}/build - name: Configure CMake # Use a bash shell so we can use the same syntax for environment variable # access regardless of the host operating system shell: bash working-directory: ${{runner.workspace}}/build # Note the current convention is to use the -S and -B options here to specify source # and build directories, but this is only available with CMake 3.13 and higher. # The CMake binaries on the Github Actions machines are (as of this writing) 3.12 run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DSCITOKENS_BUILD_UNITTESTS=ON -DSCITOKENS_EXTERNAL_GTEST=${{ matrix.external-gtest }} -DSCITOKENS_WITH_ASAN=${{ matrix.asan }} - name: Build working-directory: ${{runner.workspace}}/build shell: bash # Execute the build. You can specify a specific target with "--target " run: cmake --build . --config $BUILD_TYPE - name: Check OpenSSL version shell: bash run: | openssl version echo "Testing with the OpenSSL version shown above (actual version depends on the selected GitHub Actions runner image, such as ubuntu-22.04 or ubuntu-latest, which may change over time)." - name: Test working-directory: ${{runner.workspace}}/build shell: bash # Execute tests defined by the CMake configuration. # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail run: ctest -C $BUILD_TYPE --verbose - name: Test scitokens-verify tool working-directory: ${{runner.workspace}}/build shell: bash run: | ./scitokens-verify --help echo "✓ scitokens-verify tool works - OpenSSL version-specific memory management validated via unit tests" scitokens-cpp-1.3.0/.github/workflows/docs.yml000066400000000000000000000026021513647275500213640ustar00rootroot00000000000000 name: Documentation Build Test on: push: branches: - '**' tags: - '*' pull_request: release: types: [published] jobs: build-docs: runs-on: ubuntu-latest name: Build Documentation steps: - uses: actions/checkout@v4 with: submodules: recursive - name: Set up Python uses: actions/setup-python@v5 with: python-version: '3.11' - name: Install system dependencies run: | sudo apt-get update sudo apt-get install -y doxygen - name: Install Python dependencies run: | pip install -r docs/requirements.txt - name: Build documentation working-directory: docs run: | make html - name: Upload artifact for GitHub Pages if: (github.event_name == 'push' && startsWith(github.ref, 'refs/tags/')) || github.event_name == 'release' uses: actions/upload-pages-artifact@v3 with: path: docs/_build/html deploy-docs: if: (github.event_name == 'push' && startsWith(github.ref, 'refs/tags/')) || github.event_name == 'release' needs: build-docs runs-on: ubuntu-latest permissions: pages: write id-token: write environment: name: github-pages url: ${{ steps.deployment.outputs.page_url }} steps: - name: Deploy to GitHub Pages id: deployment uses: actions/deploy-pages@v4scitokens-cpp-1.3.0/.github/workflows/el8-openssl1-test.yml000066400000000000000000000037301513647275500236460ustar00rootroot00000000000000name: EL8 OpenSSL 1.x Test on: pull_request: branches: - master push: branches: - master env: # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) BUILD_TYPE: Release jobs: test-el8-openssl1: runs-on: ubuntu-latest container: image: rockylinux:8 name: Build and test on EL8 (OpenSSL 1.1.1) steps: - name: Install dependencies run: | dnf install -y epel-release dnf install -y gcc-c++ cmake3 openssl-devel libcurl-devel sqlite-devel libuuid-devel make git python3 - uses: actions/checkout@v3 with: submodules: recursive - name: Check OpenSSL version run: | openssl version echo "Expected: OpenSSL 1.1.1 for EL8" - name: Create Build Environment # Some projects don't allow in-source building, so create a separate build directory # We'll use this as our working directory for all subsequent commands run: mkdir -p build - name: Configure CMake # Use a bash shell so we can use the same syntax for environment variable # access regardless of the host operating system shell: bash # Note the current convention is to use the -S and -B options here to specify source # and build directories, but this is only available with CMake 3.13 and higher. # The CMake binaries on the Github Actions machines are (as of this writing) 3.12 run: cmake3 -S . -B build -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DSCITOKENS_BUILD_UNITTESTS=ON -DSCITOKENS_EXTERNAL_GTEST=NO - name: Build shell: bash # Execute the build. You can specify a specific target with "--target " run: cmake3 --build build --config $BUILD_TYPE - name: Test shell: bash # Execute tests defined by the CMake configuration. # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail run: cd build && ctest -C $BUILD_TYPE --verbose scitokens-cpp-1.3.0/.github/workflows/linter.yml000066400000000000000000000046131513647275500217350ustar00rootroot00000000000000name: Lint # Linter Action documentation at https://github.com/marketplace/actions/lint-action # One thing to note is that this action is currently configured automatically fix and re-push the linted code to the repo on a pull request. # Because the github token used for authenticating this commit comes from the upstream repo (ie scitokens/scitokens-cpp), those linter changes will not be pushed # to the fork that is providing the pull request. A manual git fetch will have to be run by the fork after the PR is merged to update the fork to the linted code. # The linter does not have authorization to lint any code in the repo's .github/workflows/ directory. # If the linter fails, the PR can still be completed, but none of the linter changes will be made. on: # push: # Can specify more circumstances under which to run the linter. # branches: # Not specifying input to "branches:" causes the action to run on push for all branches. push: branches: - master # Trigger the workflow on pull request, # but only for master pull_request_target: branches: - master permissions: checks: write contents: read pull-requests: write jobs: run-linters: name: Run linters runs-on: ubuntu-latest steps: - name: Check out repository (push) if: ${{ github.event_name == 'push' }} uses: actions/checkout@v3 - name: Check out repository (pull_request_target) if: ${{ github.event_name == 'pull_request_target' }} uses: actions/checkout@v3 with: ref: ${{ github.event.pull_request.head.sha }} - name: Install ClangFormat run: sudo apt-get install -y clang-format - name: Run linters uses: wearerequired/lint-action@v2 with: github_token: ${{ secrets.github_token }} # For providing the commit authorization for the auto_fix feature clang_format: true clang_format_auto_fix: true auto_fix: true commit: false continue_on_error: false git_email: github.event.commits[0].author.name # Uses the author's git email instead of the default git email associated with the action ("lint-action@samuelmeuli.com") clang_format_args: -style=file # Any additional arguments for clang_format - name: suggester / lint uses: reviewdog/action-suggester@v1 with: tool_name: lint scitokens-cpp-1.3.0/.gitignore000066400000000000000000000001261513647275500163030ustar00rootroot00000000000000build docs/_build/ _codeql_build_dir _codeql_detected_source_root *.pyc __pycache__/ scitokens-cpp-1.3.0/.gitmodules000066400000000000000000000003271513647275500164730ustar00rootroot00000000000000[submodule "vendor/jwt-cpp"] path = vendor/jwt-cpp url = https://github.com/Thalhammer/jwt-cpp.git [submodule "vendor/gtest"] path = vendor/gtest url = https://github.com/google/googletest.git branch = v1.8.x scitokens-cpp-1.3.0/.readthedocs.yml000066400000000000000000000004621513647275500174040ustar00rootroot00000000000000# ReadTheDocs Configuration File # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details version: 2 build: os: ubuntu-22.04 tools: python: "3.11" apt_packages: - doxygen sphinx: configuration: docs/conf.py python: install: - requirements: docs/requirements.txtscitokens-cpp-1.3.0/CMakeLists.txt000066400000000000000000000073061513647275500170620ustar00rootroot00000000000000 cmake_minimum_required( VERSION 3.10) project( scitokens-cpp DESCRIPTION "A C++ Library to interface to scitokens" VERSION 1.0.2 LANGUAGES CXX) option( SCITOKENS_BUILD_UNITTESTS "Build the scitokens-cpp unit tests" OFF ) option( SCITOKENS_EXTERNAL_GTEST "Use an external/pre-installed copy of GTest" OFF ) option( SCITOKENS_WITH_ASAN "Build with the address sanitizer" OFF ) set( CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake;${CMAKE_MODULE_PATH}" ) set( CMAKE_BUILD_TYPE RelWithDebInfo) # -g -O2 set( CMAKE_UNITY_BUILD False) if( SCITOKENS_WITH_ASAN ) add_compile_options(-fsanitize=address -fno-omit-frame-pointer) add_link_options(-fsanitize=address) endif() include(GNUInstallDirs) find_package( jwt-cpp REQUIRED ) find_package( CURL REQUIRED ) find_package( UUID REQUIRED ) if( APPLE ) find_package( OpenSSL REQUIRED ) find_package( Sqlite3 REQUIRED ) set(LIBCRYPTO_INCLUDE_DIRS ${OPENSSL_INCLUDE_DIR}) set(LIBCRYPTO_LIBRARIES ${OPENSSL_CRYPTO_LIBRARY}) set(CMAKE_MACOSX_RPATH ON) elseif( UNIX ) include (FindPkgConfig) pkg_check_modules(LIBCRYPTO REQUIRED libcrypto) pkg_check_modules(OPENSSL REQUIRED openssl) pkg_check_modules(SQLITE REQUIRED sqlite3) endif() add_library(SciTokens SHARED src/scitokens.cpp src/scitokens_internal.cpp src/scitokens_cache.cpp src/scitokens_monitoring.cpp) target_compile_features(SciTokens PUBLIC cxx_std_11) # Use at least C++11 for building and when linking to scitokens target_include_directories(SciTokens PUBLIC ${JWT_CPP_INCLUDES} "${PROJECT_SOURCE_DIR}/src" PRIVATE ${CURL_INCLUDE_DIRS} ${OPENSSL_INCLUDE_DIRS} ${LIBCRYPTO_INCLUDE_DIRS} ${SQLITE_INCLUDE_DIRS} ${UUID_INCLUDE_DIRS}) # Find threading library find_package(Threads REQUIRED) target_link_libraries(SciTokens PUBLIC ${OPENSSL_LIBRARIES} ${LIBCRYPTO_LIBRARIES} ${CURL_LIBRARIES} ${SQLITE_LIBRARIES} ${UUID_LIBRARIES} Threads::Threads) if (UNIX) # pkg_check_modules fails to return an absolute path on RHEL7. Set the # link directories accordingly. target_link_directories(SciTokens PUBLIC ${OPENSSL_LIBRARY_DIRS} ${LIBCRYPTO_LIBRARY_DIRS}) endif() if ( NOT APPLE AND UNIX ) set_target_properties(SciTokens PROPERTIES LINK_FLAGS "-Wl,--version-script=${PROJECT_SOURCE_DIR}/configs/export-symbols") endif() add_executable(scitokens-test src/test.cpp) target_include_directories(scitokens-test PRIVATE "${PROJECT_SOURCE_DIR}" ${JWT_CPP_INCLUDES} ${LIBCRYPTO_INCLUDE_DIRS}) target_link_libraries(scitokens-test SciTokens) add_executable(scitokens-verify src/verify.cpp) target_link_libraries(scitokens-verify SciTokens) add_executable(scitokens-test-access src/test_access.cpp) target_link_libraries(scitokens-test-access SciTokens) add_executable(scitokens-list-access src/list_access.cpp) target_link_libraries(scitokens-list-access SciTokens) add_executable(scitokens-create src/create.cpp) target_link_libraries(scitokens-create SciTokens) add_executable(scitokens-generate-jwks src/generate_jwks.cpp) target_include_directories(scitokens-generate-jwks PRIVATE ${OPENSSL_INCLUDE_DIRS} ${LIBCRYPTO_INCLUDE_DIRS}) target_link_libraries(scitokens-generate-jwks ${OPENSSL_LIBRARIES} ${LIBCRYPTO_LIBRARIES}) get_directory_property(TARGETS BUILDSYSTEM_TARGETS) install( TARGETS ${TARGETS} LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} ) install( FILES src/scitokens.h DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/scitokens ) set_target_properties( SciTokens PROPERTIES VERSION "0.0.2" SOVERSION "0" ) if( SCITOKENS_BUILD_UNITTESTS ) if( NOT SCITOKENS_EXTERNAL_GTEST ) include(ExternalProject) ExternalProject_Add(gtest PREFIX external/gtest URL ${CMAKE_CURRENT_SOURCE_DIR}/vendor/gtest INSTALL_COMMAND : ) endif() enable_testing() add_subdirectory(test) endif() scitokens-cpp-1.3.0/Doxyfile000066400000000000000000000030131513647275500160170ustar00rootroot00000000000000# Doxyfile configuration for SciTokens C++ library # Project related configuration options PROJECT_NAME = "SciTokens C++" PROJECT_NUMBER = "1.0.2" PROJECT_BRIEF = "A C++ Library to interface to scitokens" OUTPUT_DIRECTORY = docs/_build/doxygen # Build related configuration options EXTRACT_ALL = YES EXTRACT_PRIVATE = NO EXTRACT_STATIC = YES EXTRACT_LOCAL_CLASSES = NO # Configuration options related to the input files INPUT = src/scitokens.h FILE_PATTERNS = *.h *.hpp *.cpp RECURSIVE = NO EXCLUDE_PATTERNS = */vendor/* */build/* */_build/* # Configuration options related to source browsing SOURCE_BROWSER = YES INLINE_SOURCES = NO STRIP_CODE_COMMENTS = YES # Configuration options related to the alphabetical class index ALPHABETICAL_INDEX = YES # Configuration options related to the HTML output GENERATE_HTML = NO GENERATE_LATEX = NO GENERATE_XML = YES XML_OUTPUT = xml # Configuration options related to the preprocessor ENABLE_PREPROCESSING = YES MACRO_EXPANSION = YES EXPAND_ONLY_PREDEF = NO SEARCH_INCLUDES = YES INCLUDE_PATH = src/ PREDEFINED = __cplusplus # Configuration options related to external references TAGFILES = GENERATE_TAGFILE = # Configuration options related to the dot tool HAVE_DOT = NO # Configuration options related to the search engine SEARCHENGINE = NOscitokens-cpp-1.3.0/LICENSE000066400000000000000000000261351513647275500153300ustar00rootroot00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "{}" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright {yyyy} {name of copyright owner} Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. scitokens-cpp-1.3.0/README.md000066400000000000000000000064301513647275500155760ustar00rootroot00000000000000 SciTokens C++ Library ===================== This repository implements a minimal library for creating and using SciTokens from C or C++. [SciTokens](https://scitokens.org) provide a token format for distributed authorization. The tokens are self-describing, can be verified in a distributed fashion (no need to contact the issuer to determine if the token is valid). This is convenient for a federated environment where several otherwise-independent storage endpoints want to delegate trust for an issuer for managing a storage allocation. Building -------- To build the `scitokens-cpp` library, the following dependencies are needed: - [jwt-cpp] v0.5.0 or later (https://github.com/Thalhammer/jwt-cpp): A header-only C++ library for manipulating JWTs. - OpenSSL 1.0 or later. - `sqlite3` CMake is used for the build system. To build, from the source directory: ``` mkdir build cd build JWT_CPP_DIR=~/path/to/jwt-cpp cmake .. make ``` Testing ------- The easiest way to test `scitokens-cpp` is to head to the [SciTokens Demo app](https://demo.scitokens.org) and copy the generated token. Then, from the build directory: ``` echo "" | ./scitokens-verify ``` Replace the given token above with the fresh one you just generated; using the above token should give an expired token error. The token must be provided via standard input (stdin). Generating Keys for Testing ---------------------------- For testing and development purposes, you can generate EC (ES256) key pairs using the `scitokens-generate-jwks` tool: ``` ./scitokens-generate-jwks --kid my-key-id --jwks jwks.json --private private.pem --public public.pem ``` This generates: - `jwks.json`: A JWKS (JSON Web Key Set) file containing the public key - `public.pem`: The public key in PEM format - `private.pem`: The private key in PEM format You can then create and verify tokens using these keys: ``` # Create a token ./scitokens-create --cred public.pem --key private.pem --keyid my-key-id --issuer https://my-issuer.example.com --claim "sub=testuser" # Verify the token echo "" | ./scitokens-verify --cred public.pem --issuer https://my-issuer.example.com --keyid my-key-id ``` Instructions for Generating a Release ------------------------------------- SciTokens-cpp includes a submodule, jwt-cpp. Therefore, to create a release, you have to include the submodule into the release. VER=0.3.3 # for example git archive --prefix "scitokens-cpp-$VER/" -o "scitokens-cpp-$VER.tar" v$VER git submodule update --init git submodule foreach --recursive "git archive --prefix=scitokens-cpp-$VER/\$path/ --output=\$sha1.tar HEAD && tar --concatenate --file=$(pwd)/scitokens-cpp-$VER.tar \$sha1.tar && rm \$sha1.tar" gzip "scitokens-cpp-$VER.tar" Before tagging a new release, make sure that the RPM spec file has an updated version number and an associated changelog entry. Also, make sure that the ``debian/changelog`` has an entry that matches the RPM changelog entry. This package is built on the [cvmfs-config OpenSUSE Build Service](https://build.opensuse.org/project/show/home:cvmfs:contrib). In order to support that run `debian/obsupdate.sh` whenever the version or release number is changed in `rpm/scitokens-cpp.spec`, and commit the generated `debian/scitokens-cpp.dsc` before tagging the release. scitokens-cpp-1.3.0/cmake/000077500000000000000000000000001513647275500153745ustar00rootroot00000000000000scitokens-cpp-1.3.0/cmake/FindSqlite3.cmake000066400000000000000000000022441513647275500205250ustar00rootroot00000000000000# - Try to find SQLITE3 # # SQLITE3_FOUND - system has SQLITE3 # SQLITE3_INCLUDE_DIRS - the SQLITE3 include directory # SQLITE3_LIBRARIES - Link these to use SQLITE3 # include(CheckSymbolExists) FIND_LIBRARY(SQLITE3_LIBRARY NAMES sqlite) find_path(SQLITE3_INCLUDE_DIR NAMES sqlite3.h PATHS ${SQLITE3_DIR} $ENV{SQLITE3_DIR} ~/Library/Frameworks /Library/Frameworks /usr/local/include /usr/include ) find_library(SQLITE3_LIBRARY NAMES sqlite3 PATHS ${SQLITE_DIR} $ENV{SQLITE_DIR} ~/Library/Frameworks /Library/Frameworks ) INCLUDE(FindPackageHandleStandardArgs) FIND_PACKAGE_HANDLE_STANDARD_ARGS(SQLITE3 DEFAULT_MSG SQLITE3_LIBRARY SQLITE3_INCLUDE_DIR) IF(SQLITE3_FOUND) SET(SQLITE3_LIBRARIES ${SQLITE3_LIBRARY}) SET(SQLITE_LIBRARIES ${SQLITE3_LIBRARY}) SET(SQLITE3_INCLUDE_DIRS ${SQLITE3_INCLUDE_DIR}) SET(SQLITE_INCLUDE_DIRS ${SQLITE3_INCLUDE_DIR}) ELSE() SET(SQLITE_LIBRARIES) SET(SQLITE3_LIBRARIES) SET(SQLITE_INCLUDE_DIRS) SET(SQLITE3_INCLUDE_DIRS) endif() mark_as_advanced(SQLITE3_INCLUDE_DIRS SQLITE3_LIBRARIES SQLITE_LIBRARIES SQLITE_INCLUDE_DIRS) scitokens-cpp-1.3.0/cmake/FindUUID.cmake000066400000000000000000000056301513647275500177510ustar00rootroot00000000000000# - Try to find UUID # Once done this will define # # UUID_FOUND - system has UUID # UUID_INCLUDE_DIRS - the UUID include directory # UUID_LIBRARIES - Link these to use UUID # UUID_DEFINITIONS - Compiler switches required for using UUID # # Copyright (c) 2006 Andreas Schneider # # Redistribution and use is allowed according to the terms of the New # BSD license. # For details see the accompanying COPYING-CMAKE-SCRIPTS file. # if (APPLE) include(CheckSymbolExists) # On mac, it can't find uuid library # So, just check for the functions with the default include CHECK_SYMBOL_EXISTS("uuid_generate" "uuid/uuid.h" UUID_SYMBOL) endif (APPLE) if (UUID_SYMBOL) set(UUID_FOUND TRUE) elseif (UUID_LIBRARIES AND UUID_INCLUDE_DIRS) # in cache already set(UUID_FOUND TRUE) else (UUID_LIBRARIES AND UUID_INCLUDE_DIRS) find_path(UUID_INCLUDE_DIR NAMES uuid/uuid.h PATHS ${UUID_DIR}/include $ENV{UUID_DIR}/include $ENV{UUID_DIR} ${DELTA3D_EXT_DIR}/inc $ENV{DELTA_ROOT}/ext/inc $ENV{DELTA_ROOT} ~/Library/Frameworks /Library/Frameworks /usr/local/include /usr/include /usr/include/gdal /sw/include # Fink /opt/local/include # DarwinPorts /opt/csw/include # Blastwave /opt/include [HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Control\\Session\ Manager\\Environment;OSG_ROOT]/include /usr/freeware/include ) find_library(UUID_LIBRARY NAMES uuid PATHS ${UUID_DIR}/lib $ENV{UUID_DIR}/lib $ENV{UUID_DIR} ${DELTA3D_EXT_DIR}/lib $ENV{DELTA_ROOT}/ext/lib $ENV{DELTA_ROOT} $ENV{OSG_ROOT}/lib ~/Library/Frameworks /Library/Frameworks /usr/local/lib /usr/lib /sw/lib /opt/local/lib /opt/csw/lib /opt/lib /usr/freeware/lib64 ) find_library(UUID_LIBRARY_DEBUG NAMES uuidd PATHS ${UUID_DIR}/lib $ENV{UUID_DIR}/lib $ENV{UUID_DIR} ${DELTA3D_EXT_DIR}/lib $ENV{DELTA_ROOT}/ext/lib $ENV{DELTA_ROOT} $ENV{OSG_ROOT}/lib ~/Library/Frameworks /Library/Frameworks /usr/local/lib /usr/lib /sw/lib /opt/local/lib /opt/csw/lib /opt/lib /usr/freeware/lib64 ) set(UUID_INCLUDE_DIRS ${UUID_INCLUDE_DIR}) set(UUID_LIBRARIES ${UUID_LIBRARY}) if (UUID_INCLUDE_DIRS AND UUID_LIBRARIES) set(UUID_FOUND TRUE) endif (UUID_INCLUDE_DIRS AND UUID_LIBRARIES) if (UUID_FOUND) if (NOT UUID_FIND_QUIETLY) message(STATUS "Found UUID : ${UUID_LIBRARIES}") endif (NOT UUID_FIND_QUIETLY) else (UUID_FOUND) if (UUID_FIND_REQUIRED) message(FATAL_ERROR "Could not find UUID") endif (UUID_FIND_REQUIRED) endif (UUID_FOUND) # show the UUID_INCLUDE_DIRS and UUID_LIBRARIES variables only in the advanced view mark_as_advanced(UUID_INCLUDE_DIRS UUID_LIBRARIES) endif (UUID_SYMBOL) scitokens-cpp-1.3.0/cmake/Findjwt-cpp.cmake000066400000000000000000000002371513647275500205650ustar00rootroot00000000000000 FIND_PATH(JWT_CPP_INCLUDES jwt-cpp/jwt.h HINTS ${JWT_CPP_DIR} $ENV{JWT_CPP_DIR} /usr ${PROJECT_SOURCE_DIR}/vendor/jwt-cpp PATH_SUFFIXES include ) scitokens-cpp-1.3.0/configs/000077500000000000000000000000001513647275500157445ustar00rootroot00000000000000scitokens-cpp-1.3.0/configs/export-symbols000066400000000000000000000001341513647275500206740ustar00rootroot00000000000000{ global: scitoken*; validator*; enforcer*; keycache*; config*; local: *; }; scitokens-cpp-1.3.0/debian/000077500000000000000000000000001513647275500155365ustar00rootroot00000000000000scitokens-cpp-1.3.0/debian/README.obs000066400000000000000000000005001513647275500171730ustar00rootroot00000000000000This package is built by cvmfs-contrib on the OpenSUSE Build System https://build.opensuse.org/project/show/home:cvmfs:contrib OBS requires a little help from the package in order to build on Debian. Each time there's a new release, run obsupdate.sh in this directory and commit the newly updated scitokens-cpp.dsc. scitokens-cpp-1.3.0/debian/changelog000066400000000000000000000144531513647275500174170ustar00rootroot00000000000000scitokens-cpp (1.3.0-1) unstable; urgency=medium * Add scitokens-generate-jwks CLI for key generation in https://github.com/scitokens/scitokens-cpp/pull/186 * Add environment variable configuration loading on library initialization in https://github.com/scitokens/scitokens-cpp/pull/190 * Add per-issuer lock to prevent multiple concurrent queries against issuers without a known key in https://github.com/scitokens/scitokens-cpp/pull/180 * Add negative cache for failed issuer lookups (preventing frequent re-queries) in https://github.com/scitokens/scitokens-cpp/pull/178 * Add monitoring API for per-issuer validation statistics in https://github.com/scitokens/scitokens-cpp/pull/182 * Add optional background thread for JWKS refresh in https://github.com/scitokens/scitokens-cpp/pull/192 * Add keycache load, metadata, and delete APIs in https://github.com/scitokens/scitokens-cpp/pull/194 * Revert "Fix memory leak in rs256_from_coords" by @djw8605 in https://github.com/scitokens/scitokens-cpp/pull/162 * Add CTest-based integration test with JWKS server and TLS infrastructure by @Copilot in https://github.com/scitokens/scitokens-cpp/pull/184 * Add devcontainer configuration for GitHub Codespaces by @Copilot in https://github.com/scitokens/scitokens-cpp/pull/188 -- Brian Bockelman Thu, 11 Dec 2025 16:10:00 -0600 scitokens-cpp (1.2.0-1) unstable; urgency=medium * Fix segfault if the JSON parser cannot parse the JWKS * Fix float time claims issue and improve error handling * Fix security issue with malicious issuer handling in error messages * Improve JWTVerificationException message to include the invalid issuer * Update usage on verify command to make the TOKENFILE explicit * Read token for scitokens-verify from stdin * Set CURLOPT_NOSIGNAL option in SimpleCurlGet to prevent signal interruptions * Add asan value to the job name * Turn off building unit tests by default * Add cmake option SCITOKENS_WITH_ASAN which enables memory checking with the address sanitizer. Also enable this in CI so tests fail if they hit a memory leak or other memory problem * Fix memory leak in store_public_ec_key * Fix memory leaks in the unit tests * Fix memory leak in rs256_from_coords * Fix memory leak in scitokens_verify -- Derek Weitzel Fri, 05 Dec 2025 00:00:00 +0000 scitokens-cpp (1.1.3-1) stable; urgency=medium * Include cstdint import for jwt library to support newer compilers -- Derek Weitzel Mon, 24 Feb 2025 12:00:00 -0600 scitokens-cpp (1.1.2-1) stable; urgency=medium * Turn off CMAKE unity builds * Add a mutex around requesting public keys to stop overloading issuers -- Derek Weitzel Wed, 30 Oct 2024 12:00:00 -0600 scitokens-cpp (1.1.1-1) stable; urgency=medium * Improve error handling around the sqlite3 library * Fix test failures and compiler warnings -- Derek Weitzel Wed, 28 Feb 2024 12:00:00 -0600 scitokens-cpp (1.1.0-1) stable; urgency=medium * Allow the scitokens library user to setup a custom CA file * Fix typecast errors in scitoken_status_get_*() that caused async queries to fail * Fix logic error in deserialize_continue() that caused async deserialization to fail -- Tim Theisen Tue, 07 Nov 2023 15:46:00 -0600 scitokens-cpp (1.0.2-1) stable; urgency=medium * Add support for API-configurable cache home * Fix enforcer_acl_free logic * scitokens_internal: catch matching exception type after jwt-cpp update -- Derek Weitzel Thu, 15 Jun 2023 12:00:00 -0500 scitokens-cpp (1.0.1-1) stable; urgency=medium * Fix bug in generate acls which would cause a timeout -- Derek Weitzel Wed, 26 Apr 2023 12:00:00 -0500 scitokens-cpp (1.0.0-1) stable; urgency=medium * Add async API for parsing and verifying tokens * Add configuration API * Make nbf claim optional for non-scitokens tokens * Update to OpenSSL 3.0 -- Derek Weitzel Tue, 21 Mar 2023 10:18:59 -0500 scitokens-cpp (0.7.3-1) stable; urgency=medium * Retry failed key renewal every 5 minutes -- Derek Weitzel Tue, 01 Nov 2022 08:29:22 -0500 scitokens-cpp (0.7.2-1) stable; urgency=medium * Add curl timeout of 4 seconds for update, and 30 for expired keys -- Derek Weitzel Mon, 31 Oct 2022 15:35:17 -0500 scitokens-cpp (0.7.1-1) stable; urgency=medium * Add scitokens-* binaries to the package * Bug: close sqlite db handle on return -- Derek Weitzel Wed, 22 Jun 2022 11:26:18 -0500 scitokens-cpp (0.7.0-1) stable; urgency=medium * Changes from static analysis * If only one key is available, do not error on no kid * Support at+jwt profile -- Derek Weitzel Fri, 18 Feb 2022 13:16:18 -0600 scitokens-cpp (0.6.3-1) stable; urgency=medium * Add support for building Debian packages on the OpenSUSE Build System * Add patch to jwt-cpp to update its picojson dependency in order to enable it to compile on Debian 11 and Ubuntu 21.04 * Fix el7 build by requiring epel-rpm-macros -- Dave Dykstra Fri, 03 Sep 2021 12:00:00 -0500 scitokens-cpp (0.6.2-2) stable; urgency=medium * Make the build require cmake3 instead of cmake -- Dave Dykstra Thu, 26 Aug 2021 12:00:00 -0500 scitokens-cpp (0.6.2-1) stable; urgency=medium * Correct WLCG compat for condor read permissions -- Dave Dykstra Thu, 26 Aug 2021 12:00:00 -0500 scitokens-cpp (0.6.1-1) stable; urgency=medium * Fix vector resize for el8+ builds -- Derek Weitzel Thu, 20 May 2021 12:00:00 -0500 scitokens-cpp (0.6.0-2) unstable; urgency=medium * Fix empty libscitokens0 package -- Tim Theisen Fri, 26 Mar 2021 08:11:00 -0500 scitokens-cpp (0.6.0-1) unstable; urgency=medium * Fix compilation errors on c++11 * Update to jwt-cpp-0.4.0 vendor * Change scitoken profile name to match spec, scitoken:2.0 -- Derek Weitzel Tue, 09 Mar 2021 13:45:00 -0600 scitokens-cpp (0.5.1-3) unstable; urgency=low * Updated packaging for Debian -- Tim Theisen Sun, 28 Feb 2021 16:02:24 -0600 scitokens-cpp (0.5.1-1) unstable; urgency=low * Initial release. -- Tim Theisen Fri, 04 Dec 2020 10:54:24 -0600 scitokens-cpp-1.3.0/debian/compat000066400000000000000000000000031513647275500167350ustar00rootroot0000000000000012 scitokens-cpp-1.3.0/debian/control000066400000000000000000000027671513647275500171550ustar00rootroot00000000000000Source: scitokens-cpp Section: science Priority: optional Maintainer: Tim Theisen Build-Depends: cmake (>=2.6), debhelper (>=9), libcurl4-openssl-dev | libcurl4-gnutls-dev, libsqlite3-dev, libssl-dev, pkg-config, uuid-dev Standards-Version: 3.9.8 Homepage: https://github.com/scitokens/scitokens-cpp Package: libscitokens0 Section: libs Architecture: any Multi-Arch: same Pre-Depends: ${misc:Pre-Depends} Depends: ${misc:Depends}, ${shlibs:Depends} Description: C++ Implementation of the SciTokens Library SciTokens provide a token format for distributed authorization The tokens are self-describing, can be verified in a distributed fashion (no need to contact the issuer to determine if the token is valid). This is convenient for a federated environment where several otherwise-independent storage endpoints want to delegate trust for an issuer for managing a storage allocation. Package: libscitokens-dev Section: libdevel Architecture: any Multi-Arch: same Depends: libscitokens0 (= ${binary:Version}), ${misc:Depends} Description: Header files for the libscitokens public interfaces SciTokens provide a token format for distributed authorization. The tokens are self-describing, can be verified in a distributed fashion (no need to contact the issuer to determine if the token is valid). This is convenient for a federated environment where several otherwise-independent storage endpoints want to delegate trust for an issuer for managing a storage allocation. scitokens-cpp-1.3.0/debian/copyright000066400000000000000000000422151513647275500174750ustar00rootroot00000000000000Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ Upstream-Name: scitokens-cpp Upstream-Contact: discuss@scitokens.org Source: https://github.com/scitokens/scitokens-cpp Files: * Copyright: NONE License: Apache-2.0 Files: ./vendor/gtest/BUILD.bazel ./vendor/gtest/LICENSE ./vendor/gtest/ci/build-linux-bazel.sh ./vendor/gtest/ci/env-linux.sh ./vendor/gtest/ci/env-osx.sh ./vendor/gtest/ci/get-nprocessors.sh ./vendor/gtest/ci/install-linux.sh ./vendor/gtest/ci/install-osx.sh ./vendor/gtest/ci/log-config.sh ./vendor/gtest/googlemock/LICENSE ./vendor/gtest/googlemock/include/gmock/gmock-actions.h ./vendor/gtest/googlemock/include/gmock/gmock-cardinalities.h ./vendor/gtest/googlemock/include/gmock/gmock-function-mocker.h ./vendor/gtest/googlemock/include/gmock/gmock-generated-actions.h ./vendor/gtest/googlemock/include/gmock/gmock-generated-actions.h.pump ./vendor/gtest/googlemock/include/gmock/gmock-generated-function-mockers.h ./vendor/gtest/googlemock/include/gmock/gmock-generated-function-mockers.h.pump ./vendor/gtest/googlemock/include/gmock/gmock-generated-matchers.h ./vendor/gtest/googlemock/include/gmock/gmock-generated-matchers.h.pump ./vendor/gtest/googlemock/include/gmock/gmock-matchers.h ./vendor/gtest/googlemock/include/gmock/gmock-more-actions.h ./vendor/gtest/googlemock/include/gmock/gmock-more-matchers.h ./vendor/gtest/googlemock/include/gmock/gmock-nice-strict.h ./vendor/gtest/googlemock/include/gmock/gmock-spec-builders.h ./vendor/gtest/googlemock/include/gmock/gmock.h ./vendor/gtest/googlemock/include/gmock/internal/custom/gmock-matchers.h ./vendor/gtest/googlemock/include/gmock/internal/custom/gmock-port.h ./vendor/gtest/googlemock/include/gmock/internal/gmock-internal-utils.h ./vendor/gtest/googlemock/include/gmock/internal/gmock-port.h ./vendor/gtest/googlemock/scripts/fuse_gmock_files.py ./vendor/gtest/googlemock/scripts/pump.py ./vendor/gtest/googlemock/src/gmock-all.cc ./vendor/gtest/googlemock/src/gmock-cardinalities.cc ./vendor/gtest/googlemock/src/gmock-internal-utils.cc ./vendor/gtest/googlemock/src/gmock-matchers.cc ./vendor/gtest/googlemock/src/gmock-spec-builders.cc ./vendor/gtest/googlemock/src/gmock.cc ./vendor/gtest/googlemock/src/gmock_main.cc ./vendor/gtest/googlemock/test/BUILD.bazel ./vendor/gtest/googlemock/test/gmock-actions_test.cc ./vendor/gtest/googlemock/test/gmock-cardinalities_test.cc ./vendor/gtest/googlemock/test/gmock-function-mocker_test.cc ./vendor/gtest/googlemock/test/gmock-generated-actions_test.cc ./vendor/gtest/googlemock/test/gmock-generated-function-mockers_test.cc ./vendor/gtest/googlemock/test/gmock-generated-matchers_test.cc ./vendor/gtest/googlemock/test/gmock-internal-utils_test.cc ./vendor/gtest/googlemock/test/gmock-matchers_test.cc ./vendor/gtest/googlemock/test/gmock-more-actions_test.cc ./vendor/gtest/googlemock/test/gmock-nice-strict_test.cc ./vendor/gtest/googlemock/test/gmock-port_test.cc ./vendor/gtest/googlemock/test/gmock-pp-string_test.cc ./vendor/gtest/googlemock/test/gmock-spec-builders_test.cc ./vendor/gtest/googlemock/test/gmock_all_test.cc ./vendor/gtest/googlemock/test/gmock_ex_test.cc ./vendor/gtest/googlemock/test/gmock_leak_test.py ./vendor/gtest/googlemock/test/gmock_leak_test_.cc ./vendor/gtest/googlemock/test/gmock_link2_test.cc ./vendor/gtest/googlemock/test/gmock_link_test.cc ./vendor/gtest/googlemock/test/gmock_link_test.h ./vendor/gtest/googlemock/test/gmock_output_test.py ./vendor/gtest/googlemock/test/gmock_output_test_.cc ./vendor/gtest/googlemock/test/gmock_stress_test.cc ./vendor/gtest/googlemock/test/gmock_test.cc ./vendor/gtest/googlemock/test/gmock_test_utils.py ./vendor/gtest/googlemock/test/pump_test.py ./vendor/gtest/googletest/LICENSE ./vendor/gtest/googletest/include/gtest/gtest-death-test.h ./vendor/gtest/googletest/include/gtest/gtest-matchers.h ./vendor/gtest/googletest/include/gtest/gtest-message.h ./vendor/gtest/googletest/include/gtest/gtest-param-test.h ./vendor/gtest/googletest/include/gtest/gtest-printers.h ./vendor/gtest/googletest/include/gtest/gtest-spi.h ./vendor/gtest/googletest/include/gtest/gtest-test-part.h ./vendor/gtest/googletest/include/gtest/gtest-typed-test.h ./vendor/gtest/googletest/include/gtest/gtest.h ./vendor/gtest/googletest/include/gtest/gtest_pred_impl.h ./vendor/gtest/googletest/include/gtest/gtest_prod.h ./vendor/gtest/googletest/include/gtest/internal/custom/gtest-port.h ./vendor/gtest/googletest/include/gtest/internal/custom/gtest-printers.h ./vendor/gtest/googletest/include/gtest/internal/custom/gtest.h ./vendor/gtest/googletest/include/gtest/internal/gtest-death-test-internal.h ./vendor/gtest/googletest/include/gtest/internal/gtest-filepath.h ./vendor/gtest/googletest/include/gtest/internal/gtest-internal.h ./vendor/gtest/googletest/include/gtest/internal/gtest-param-util.h ./vendor/gtest/googletest/include/gtest/internal/gtest-port-arch.h ./vendor/gtest/googletest/include/gtest/internal/gtest-port.h ./vendor/gtest/googletest/include/gtest/internal/gtest-string.h ./vendor/gtest/googletest/include/gtest/internal/gtest-type-util.h ./vendor/gtest/googletest/samples/prime_tables.h ./vendor/gtest/googletest/samples/sample1.cc ./vendor/gtest/googletest/samples/sample1.h ./vendor/gtest/googletest/samples/sample10_unittest.cc ./vendor/gtest/googletest/samples/sample1_unittest.cc ./vendor/gtest/googletest/samples/sample2.cc ./vendor/gtest/googletest/samples/sample2.h ./vendor/gtest/googletest/samples/sample2_unittest.cc ./vendor/gtest/googletest/samples/sample3-inl.h ./vendor/gtest/googletest/samples/sample3_unittest.cc ./vendor/gtest/googletest/samples/sample4.cc ./vendor/gtest/googletest/samples/sample4.h ./vendor/gtest/googletest/samples/sample4_unittest.cc ./vendor/gtest/googletest/samples/sample5_unittest.cc ./vendor/gtest/googletest/samples/sample6_unittest.cc ./vendor/gtest/googletest/samples/sample7_unittest.cc ./vendor/gtest/googletest/samples/sample8_unittest.cc ./vendor/gtest/googletest/samples/sample9_unittest.cc ./vendor/gtest/googletest/scripts/common.py ./vendor/gtest/googletest/scripts/fuse_gtest_files.py ./vendor/gtest/googletest/scripts/gen_gtest_pred_impl.py ./vendor/gtest/googletest/scripts/release_docs.py ./vendor/gtest/googletest/scripts/run_with_path.py ./vendor/gtest/googletest/scripts/upload.py ./vendor/gtest/googletest/scripts/upload_gtest.py ./vendor/gtest/googletest/src/gtest-all.cc ./vendor/gtest/googletest/src/gtest-death-test.cc ./vendor/gtest/googletest/src/gtest-filepath.cc ./vendor/gtest/googletest/src/gtest-internal-inl.h ./vendor/gtest/googletest/src/gtest-matchers.cc ./vendor/gtest/googletest/src/gtest-port.cc ./vendor/gtest/googletest/src/gtest-printers.cc ./vendor/gtest/googletest/src/gtest-test-part.cc ./vendor/gtest/googletest/src/gtest-typed-test.cc ./vendor/gtest/googletest/src/gtest.cc ./vendor/gtest/googletest/src/gtest_main.cc ./vendor/gtest/googletest/test/BUILD.bazel ./vendor/gtest/googletest/test/googletest-break-on-failure-unittest.py ./vendor/gtest/googletest/test/googletest-break-on-failure-unittest_.cc ./vendor/gtest/googletest/test/googletest-catch-exceptions-test.py ./vendor/gtest/googletest/test/googletest-catch-exceptions-test_.cc ./vendor/gtest/googletest/test/googletest-color-test.py ./vendor/gtest/googletest/test/googletest-color-test_.cc ./vendor/gtest/googletest/test/googletest-death-test-test.cc ./vendor/gtest/googletest/test/googletest-death-test_ex_test.cc ./vendor/gtest/googletest/test/googletest-env-var-test.py ./vendor/gtest/googletest/test/googletest-env-var-test_.cc ./vendor/gtest/googletest/test/googletest-filepath-test.cc ./vendor/gtest/googletest/test/googletest-filter-unittest.py ./vendor/gtest/googletest/test/googletest-filter-unittest_.cc ./vendor/gtest/googletest/test/googletest-json-outfiles-test.py ./vendor/gtest/googletest/test/googletest-json-output-unittest.py ./vendor/gtest/googletest/test/googletest-list-tests-unittest.py ./vendor/gtest/googletest/test/googletest-list-tests-unittest_.cc ./vendor/gtest/googletest/test/googletest-listener-test.cc ./vendor/gtest/googletest/test/googletest-message-test.cc ./vendor/gtest/googletest/test/googletest-options-test.cc ./vendor/gtest/googletest/test/googletest-output-test.py ./vendor/gtest/googletest/test/googletest-output-test_.cc ./vendor/gtest/googletest/test/googletest-param-test-invalid-name1-test.py ./vendor/gtest/googletest/test/googletest-param-test-invalid-name1-test_.cc ./vendor/gtest/googletest/test/googletest-param-test-invalid-name2-test.py ./vendor/gtest/googletest/test/googletest-param-test-invalid-name2-test_.cc ./vendor/gtest/googletest/test/googletest-param-test-test.cc ./vendor/gtest/googletest/test/googletest-param-test-test.h ./vendor/gtest/googletest/test/googletest-param-test2-test.cc ./vendor/gtest/googletest/test/googletest-port-test.cc ./vendor/gtest/googletest/test/googletest-printers-test.cc ./vendor/gtest/googletest/test/googletest-shuffle-test.py ./vendor/gtest/googletest/test/googletest-shuffle-test_.cc ./vendor/gtest/googletest/test/googletest-test-part-test.cc ./vendor/gtest/googletest/test/googletest-test2_test.cc ./vendor/gtest/googletest/test/googletest-throw-on-failure-test.py ./vendor/gtest/googletest/test/googletest-throw-on-failure-test_.cc ./vendor/gtest/googletest/test/googletest-uninitialized-test.py ./vendor/gtest/googletest/test/googletest-uninitialized-test_.cc ./vendor/gtest/googletest/test/gtest-typed-test2_test.cc ./vendor/gtest/googletest/test/gtest-typed-test_test.cc ./vendor/gtest/googletest/test/gtest-typed-test_test.h ./vendor/gtest/googletest/test/gtest-unittest-api_test.cc ./vendor/gtest/googletest/test/gtest_all_test.cc ./vendor/gtest/googletest/test/gtest_assert_by_exception_test.cc ./vendor/gtest/googletest/test/gtest_environment_test.cc ./vendor/gtest/googletest/test/gtest_help_test.py ./vendor/gtest/googletest/test/gtest_help_test_.cc ./vendor/gtest/googletest/test/gtest_json_test_utils.py ./vendor/gtest/googletest/test/gtest_list_output_unittest.py ./vendor/gtest/googletest/test/gtest_list_output_unittest_.cc ./vendor/gtest/googletest/test/gtest_main_unittest.cc ./vendor/gtest/googletest/test/gtest_no_test_unittest.cc ./vendor/gtest/googletest/test/gtest_pred_impl_unittest.cc ./vendor/gtest/googletest/test/gtest_premature_exit_test.cc ./vendor/gtest/googletest/test/gtest_prod_test.cc ./vendor/gtest/googletest/test/gtest_repeat_test.cc ./vendor/gtest/googletest/test/gtest_skip_test.cc ./vendor/gtest/googletest/test/gtest_sole_header_test.cc ./vendor/gtest/googletest/test/gtest_stress_test.cc ./vendor/gtest/googletest/test/gtest_test_macro_stack_footprint_test.cc ./vendor/gtest/googletest/test/gtest_test_utils.py ./vendor/gtest/googletest/test/gtest_throw_on_failure_ex_test.cc ./vendor/gtest/googletest/test/gtest_unittest.cc ./vendor/gtest/googletest/test/gtest_xml_outfile1_test_.cc ./vendor/gtest/googletest/test/gtest_xml_outfile2_test_.cc ./vendor/gtest/googletest/test/gtest_xml_outfiles_test.py ./vendor/gtest/googletest/test/gtest_xml_output_unittest.py ./vendor/gtest/googletest/test/gtest_xml_output_unittest_.cc ./vendor/gtest/googletest/test/gtest_xml_test_utils.py ./vendor/gtest/googletest/test/production.cc ./vendor/gtest/googletest/test/production.h Copyright: 2005, Google Inc. 2006, Google Inc. 2007, Google Inc. 2008, Google Inc. 2009, Google Inc. 2010, Google Inc. 2013, Google Inc. 2015, Google Inc. 2017, Google Inc. 2018, Google Inc. License: BSD-3-clause Files: ./vendor/gtest/googlemock/scripts/generator/cpp/ast.py ./vendor/gtest/googlemock/scripts/generator/cpp/gmock_class_test.py ./vendor/gtest/googlemock/scripts/generator/cpp/keywords.py ./vendor/gtest/googlemock/scripts/generator/cpp/tokenize.py ./vendor/gtest/googlemock/scripts/generator/cpp/utils.py Copyright: 2007, Google Inc. 2007, Neal Norwitz 2009, Google Inc. 2009, Neal Norwitz License: Apache-2.0 Files: ./vendor/gtest/googletest/test/gtest_skip_check_output_test.py ./vendor/gtest/googletest/test/gtest_skip_environment_check_output_test.py ./vendor/gtest/googletest/test/gtest_skip_in_environment_setup_test.cc ./vendor/gtest/googletest/test/gtest_testbridge_test.py ./vendor/gtest/googletest/test/gtest_testbridge_test_.cc Copyright: 2018, Google LLC. 2019, Google LLC. License: BSD-3-clause Files: ./vendor/gtest/googlemock/scripts/generator/cpp/gmock_class.py ./vendor/gtest/googlemock/scripts/generator/gmock_gen.py Copyright: 2008, Google Inc. License: Apache-2.0 Files: ./vendor/jwt-cpp/include/jwt-cpp/picojson.h Copyright: 2009-2010, Cybozu Labs, Inc. 2011-2014, Kazuho Oku License: BSD-2-clause Files: ./vendor/gtest/library.json Copyright: NONE License: BSD-3-clause Files: ./vendor/jwt-cpp/* Copyright: 2018, Dominik Thalhammer License: Expat Files: ./cmake/FindUUID.cmake Copyright: 2006, Andreas Schneider License: BSD-3-clause License: Apache-2.0 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. . On Debian systems, the full text of the Apache License, Version 2.0 can be found in the file `/usr/share/common-licenses/Apache-2.0'. License: BSD-2-clause Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: . 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. . 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. . THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. License: BSD-3-clause Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: . 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. . 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. . 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. . THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. License: Expat Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: . The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. . THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. scitokens-cpp-1.3.0/debian/get-orig-source.sh000077500000000000000000000020701513647275500211070ustar00rootroot00000000000000# Generate a source tarball including submodules if [ -z "${1}" ] ; then echo No tag or branch given exit 1 fi ver=${1} # Remove initial v from tag name for use in filenames if [ ${ver:0:1} = 'v' ] ; then fver=${ver:1} else fver=${ver} fi if [ -r scitokens-cpp_${fver}.orig.tar.gz ] ; then echo scitokens-cpp_${fver}.orig.tar.gz already exists exit 1 fi curdir=$(pwd) tdir=$(mktemp -d) cd ${tdir} git clone https://github.com/scitokens/scitokens-cpp.git cd scitokens-cpp git checkout ${ver} if [ $? -ne 0 ] ; then echo No such tag or branch: ${ver} cd ${curdir} rm -rf ${tdir} exit 1 fi git archive --prefix scitokens-cpp_${fver}/ ${ver} -o ${tdir}/scitokens-cpp_${fver}.orig.tar git submodule update --init git submodule foreach --recursive "git archive --prefix scitokens-cpp_${fver}/\$path/ \$sha1 -o ${tdir}/\$sha1.tar ; tar -A -f ${tdir}/scitokens-cpp_${fver}.orig.tar ${tdir}/\$sha1.tar ; rm ${tdir}/\$sha1.tar" cd ${tdir} gzip scitokens-cpp_${fver}.orig.tar mv scitokens-cpp_${fver}.orig.tar.gz ${curdir} cd ${curdir} rm -rf ${tdir} scitokens-cpp-1.3.0/debian/libscitokens-dev.install000066400000000000000000000001231513647275500223670ustar00rootroot00000000000000debian/tmp/usr/include/scitokens usr/include/ debian/tmp/usr/lib/*/libSciTokens.so scitokens-cpp-1.3.0/debian/libscitokens0.install000066400000000000000000000001061513647275500216740ustar00rootroot00000000000000debian/tmp/usr/lib/*/libSciTokens.so.* debian/tmp/usr/bin/scitokens-* scitokens-cpp-1.3.0/debian/obsupdate.sh000077500000000000000000000023151513647275500200640ustar00rootroot00000000000000#!/bin/bash # update ../packaging/debian/packageName.dsc # That file is used by build.opensuse.org's Open Build Service # After the file is updated, it needs to be separately committed to git. HERE="`dirname $0`" ME="`basename $0`" cd $HERE PKG="`sed -n 's/^Source: //p' control`" SPECFILE="../rpm/$PKG.spec" VERSION="$(grep ^Version: $SPECFILE | awk '{print $2}')" RPMREL="$(grep '^%define release_prefix' $SPECFILE | awk '{print $3}')" if [ -z "$RPMREL" ]; then RPMREL="$(grep '^Release:' $SPECFILE | awk '{print $2}' | cut -d% -f1)" fi # if the version is current, increment the release number, else choose 1 DEBREL="`sed -n "s/^Version: ${VERSION}\.${RPMREL}-//p" $PKG.dsc 2>/dev/null`" if [ -z "$DEBREL" ]; then DEBREL=1 else let DEBREL+=1 fi ( echo "# created by $ME, do not edit by hand" # The following two lines are OBS "magic" to use the tarball from the rpm echo "Debtransform-Tar: ${PKG}-${VERSION}.tar.gz" #echo "Debtransform-Files-Tar: " echo "Format: 3.0" echo "Version: ${VERSION}.${RPMREL}-${DEBREL}" echo "Binary: $PKG" cat control echo "Files:" echo " ffffffffffffffffffffffffffffffff 99999 file1" echo " ffffffffffffffffffffffffffffffff 99999 file2" ) > $PKG.dsc # echo "Updated $PWD/$PKG.dsc" scitokens-cpp-1.3.0/debian/patches/000077500000000000000000000000001513647275500171655ustar00rootroot00000000000000scitokens-cpp-1.3.0/debian/patches/series000066400000000000000000000000001513647275500203700ustar00rootroot00000000000000scitokens-cpp-1.3.0/debian/rules000077500000000000000000000003061513647275500166150ustar00rootroot00000000000000#!/usr/bin/make -f DEB_HOST_MULTIARCH ?= $(shell dpkg-architecture -qDEB_HOST_MULTIARCH) %: dh $@ override_dh_auto_configure: dh_auto_configure -- -DLIB_INSTALL_DIR=lib/$(DEB_HOST_MULTIARCH) scitokens-cpp-1.3.0/debian/scitokens-cpp.dsc000066400000000000000000000033551513647275500210210ustar00rootroot00000000000000# created by obsupdate.sh, do not edit by hand Debtransform-Tar: scitokens-cpp-1.3.0.tar.gz Format: 3.0 Version: 1.3.0.1-1 Binary: scitokens-cpp Source: scitokens-cpp Section: science Priority: optional Maintainer: Tim Theisen Build-Depends: cmake (>=2.6), debhelper (>=9), libcurl4-openssl-dev | libcurl4-gnutls-dev, libsqlite3-dev, libssl-dev, pkg-config, uuid-dev Standards-Version: 3.9.8 Homepage: https://github.com/scitokens/scitokens-cpp Package: libscitokens0 Section: libs Architecture: any Multi-Arch: same Pre-Depends: ${misc:Pre-Depends} Depends: ${misc:Depends}, ${shlibs:Depends} Description: C++ Implementation of the SciTokens Library SciTokens provide a token format for distributed authorization The tokens are self-describing, can be verified in a distributed fashion (no need to contact the issuer to determine if the token is valid). This is convenient for a federated environment where several otherwise-independent storage endpoints want to delegate trust for an issuer for managing a storage allocation. Package: libscitokens-dev Section: libdevel Architecture: any Multi-Arch: same Depends: libscitokens0 (= ${binary:Version}), ${misc:Depends} Description: Header files for the libscitokens public interfaces SciTokens provide a token format for distributed authorization. The tokens are self-describing, can be verified in a distributed fashion (no need to contact the issuer to determine if the token is valid). This is convenient for a federated environment where several otherwise-independent storage endpoints want to delegate trust for an issuer for managing a storage allocation. Files: ffffffffffffffffffffffffffffffff 99999 file1 ffffffffffffffffffffffffffffffff 99999 file2 scitokens-cpp-1.3.0/debian/source/000077500000000000000000000000001513647275500170365ustar00rootroot00000000000000scitokens-cpp-1.3.0/debian/source/format000066400000000000000000000000141513647275500202440ustar00rootroot000000000000003.0 (quilt) scitokens-cpp-1.3.0/debian/source/local-options000066400000000000000000000000541513647275500215430ustar00rootroot00000000000000#abort-on-upstream-changes #unapply-patches scitokens-cpp-1.3.0/debian/watch000066400000000000000000000000121513647275500165600ustar00rootroot00000000000000version=3 scitokens-cpp-1.3.0/docs/000077500000000000000000000000001513647275500152445ustar00rootroot00000000000000scitokens-cpp-1.3.0/docs/Makefile000066400000000000000000000011571513647275500167100ustar00rootroot00000000000000# Makefile for Sphinx documentation # # You can set these variables from the command line, and also # from the environment for the first two. SPHINXOPTS ?= SPHINXBUILD ?= sphinx-build SOURCEDIR = . BUILDDIR = _build # Put it first so that "make" without argument is like "make help". help: @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) .PHONY: help Makefile # Catch-all target: route all unknown targets to Sphinx using the new # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). %: Makefile @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)scitokens-cpp-1.3.0/docs/README.md000066400000000000000000000026601513647275500165270ustar00rootroot00000000000000# SciTokens C++ Documentation This directory contains the Sphinx documentation for the SciTokens C++ library. ## Building the Documentation ### Prerequisites 1. Install Python dependencies: ```bash pip install -r requirements.txt ``` 2. Install Doxygen (for API extraction): ```bash # Ubuntu/Debian sudo apt install doxygen # CentOS/RHEL sudo yum install doxygen ``` ### Building From this directory, run: ```bash make html ``` Or using sphinx-build directly: ```bash sphinx-build -b html . _build/html ``` The generated documentation will be in `_build/html/`. ## Documentation Structure - `index.rst` - Main documentation page - `installation.rst` - Installation and building instructions - `api.rst` - API reference (auto-generated from source comments) - `examples.rst` - Usage examples - `conf.py` - Sphinx configuration - `requirements.txt` - Python dependencies ## ReadTheDocs Integration This documentation is configured for ReadTheDocs. See `.readthedocs.yml` in the project root for the configuration. The documentation will automatically build when pushed to the repository. ## Adding Examples Examples in `examples.rst` are based on the test cases in the `test/` directory. When adding new functionality, please: 1. Add appropriate docstring comments to the public API functions in `src/scitokens.h` 2. Add usage examples to `examples.rst` 3. Test that the documentation builds without warningsscitokens-cpp-1.3.0/docs/api.rst000066400000000000000000000003541513647275500165510ustar00rootroot00000000000000API Reference ============= This page provides the complete API reference for the SciTokens C++ library. The API is primarily exposed as a C interface for maximum compatibility. .. doxygenfile:: scitokens.h :project: scitokens-cppscitokens-cpp-1.3.0/docs/conf.py000066400000000000000000000032051513647275500165430ustar00rootroot00000000000000# Configuration file for the Sphinx documentation builder. # # For the full list of built-in configuration values, see the documentation: # https://www.sphinx-doc.org/en/master/usage/configuration.html # -- Project information ----------------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information project = 'SciTokens C++' copyright = '2024, SciTokens Team' author = 'SciTokens Team' release = '1.0.2' # -- General configuration --------------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration extensions = [ 'breathe', 'sphinx.ext.autodoc', 'sphinx.ext.viewcode', 'sphinx.ext.napoleon' ] templates_path = ['_templates'] exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] # -- Options for HTML output ------------------------------------------------ # https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output html_theme = 'sphinx_rtd_theme' html_static_path = ['_static'] # -- Breathe Configuration -------------------------------------------------- breathe_projects = { "scitokens-cpp": "_build/doxygen/xml" } breathe_default_project = "scitokens-cpp" # -- Doxygen integration ---------------------------------------------------- import subprocess import os def run_doxygen(app, config): """Run doxygen to generate XML for breathe""" try: subprocess.run(['doxygen', 'Doxyfile'], cwd='..', check=True) except subprocess.CalledProcessError: print("Failed to run doxygen") def setup(app): app.connect('config-inited', run_doxygen)scitokens-cpp-1.3.0/docs/examples.rst000066400000000000000000000224421513647275500176200ustar00rootroot00000000000000Examples ======== This page provides practical examples of using the SciTokens C++ library for common tasks. Simple Token Creation --------------------- This example shows how to create a basic SciToken without signing: .. code-block:: c #include #include int main() { // Create a token without a key (for testing) SciToken token = scitoken_create(NULL); if (token == NULL) { fprintf(stderr, "Failed to create token\n"); return 1; } // Clean up scitoken_destroy(token); printf("Token created successfully\n"); return 0; } Creating and Signing a Token ---------------------------- This example demonstrates how to create a key pair and sign a token: .. code-block:: c #include #include #include // Example EC private key const char ec_private[] = "-----BEGIN EC PRIVATE KEY-----\n" "MHcCAQEEIESSMxT7PLTR9A/aqd+CM0/6vv6fQWqDm0mNx8uE9EbpoAoGCCqGSM49\n" "AwEHoUQDQgAE1i+ImZ//iQhOPh0OMfZzdbmPH+3G1ouWezolCugQYWIRqNmwq3zR\n" "EnTbe4EmymTpJ1MJTPP/tCEUP3G/QqQuhA==\n" "-----END EC PRIVATE KEY-----\n"; // Example EC public key const char ec_public[] = "-----BEGIN PUBLIC KEY-----\n" "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE1i+ImZ//iQhOPh0OMfZzdbmPH+3G\n" "1ouWezolCugQYWIRqNmwq3zREnTbe4EmymTpJ1MJTPP/tCEUP3G/QqQuhA==\n" "-----END PUBLIC KEY-----\n"; int main() { char *err_msg = NULL; // Create a key for signing SciTokenKey key = scitoken_key_create("test-key", "ES256", ec_public, ec_private, &err_msg); if (key == NULL) { fprintf(stderr, "Failed to create key: %s\n", err_msg); free(err_msg); return 1; } // Create a token with the key SciToken token = scitoken_create(key); if (token == NULL) { fprintf(stderr, "Failed to create token\n"); scitoken_key_destroy(key); return 1; } // Set issuer claim int rv = scitoken_set_claim_string(token, "iss", "https://example.org", &err_msg); if (rv != 0) { fprintf(stderr, "Failed to set issuer: %s\n", err_msg); free(err_msg); scitoken_destroy(token); scitoken_key_destroy(key); return 1; } // Serialize the token char *serialized_token; rv = scitoken_serialize(token, &serialized_token, &err_msg); if (rv != 0) { fprintf(stderr, "Failed to serialize: %s\n", err_msg); free(err_msg); } else { printf("Serialized token: %s\n", serialized_token); free(serialized_token); } // Clean up scitoken_destroy(token); scitoken_key_destroy(key); return 0; } Token Validation with Enforcer ------------------------------ This example shows how to use an Enforcer to generate ACLs from a token: .. code-block:: c #include #include #include int main() { char *err_msg = NULL; // First, create and serialize a token (see previous example) // For this example, we'll assume we have a token // Create an enforcer const char *audiences[] = {"https://example.org/", NULL}; Enforcer enforcer = enforcer_create("https://example.org", audiences, &err_msg); if (enforcer == NULL) { fprintf(stderr, "Failed to create enforcer: %s\n", err_msg); free(err_msg); return 1; } // Assuming we have a valid token from previous steps SciToken token = NULL; // This would be your deserialized token // Generate ACLs from the token Acl *acls = NULL; int rv = enforcer_generate_acls(enforcer, token, &acls, &err_msg); if (rv != 0) { fprintf(stderr, "Failed to generate ACLs: %s\n", err_msg); free(err_msg); } else { // Print the ACLs for (int i = 0; acls[i].authz != NULL || acls[i].resource != NULL; i++) { printf("ACL %d: %s on %s\n", i, acls[i].authz ? acls[i].authz : "(null)", acls[i].resource ? acls[i].resource : "(null)"); } // Free the ACLs enforcer_acl_free(acls); } // Clean up enforcer_destroy(enforcer); if (token) scitoken_destroy(token); return 0; } Complete Example: Create, Sign, and Validate -------------------------------------------- This comprehensive example demonstrates the full workflow: .. code-block:: c #include #include #include int main() { char *err_msg = NULL; int rv; // Keys for this example const char ec_private[] = "-----BEGIN EC PRIVATE KEY-----\n" "MHcCAQEEIESSMxT7PLTR9A/aqd+CM0/6vv6fQWqDm0mNx8uE9EbpoAoGCCqGSM49\n" "AwEHoUQDQgAE1i+ImZ//iQhOPh0OMfZzdbmPH+3G1ouWezolCugQYWIRqNmwq3zR\n" "EnTbe4EmymTpJ1MJTPP/tCEUP3G/QqQuhA==\n" "-----END EC PRIVATE KEY-----\n"; const char ec_public[] = "-----BEGIN PUBLIC KEY-----\n" "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE1i+ImZ//iQhOPh0OMfZzdbmPH+3G\n" "1ouWezolCugQYWIRqNmwq3zREnTbe4EmymTpJ1MJTPP/tCEUP3G/QqQuhA==\n" "-----END PUBLIC KEY-----\n"; // Step 1: Create signing key SciTokenKey key = scitoken_key_create("1", "ES256", ec_public, ec_private, &err_msg); if (!key) { fprintf(stderr, "Failed to create key: %s\n", err_msg); free(err_msg); return 1; } // Step 2: Create and configure token SciToken token = scitoken_create(key); if (!token) { fprintf(stderr, "Failed to create token\n"); scitoken_key_destroy(key); return 1; } // Set token claims rv = scitoken_set_claim_string(token, "iss", "https://demo.scitokens.org/gtest", &err_msg); if (rv != 0) { fprintf(stderr, "Failed to set issuer: %s\n", err_msg); goto cleanup; } rv = scitoken_set_claim_string(token, "aud", "https://demo.scitokens.org/", &err_msg); if (rv != 0) { fprintf(stderr, "Failed to set audience: %s\n", err_msg); goto cleanup; } rv = scitoken_set_claim_string(token, "scope", "read:/data", &err_msg); if (rv != 0) { fprintf(stderr, "Failed to set scope: %s\n", err_msg); goto cleanup; } // Step 3: Serialize token char *serialized; rv = scitoken_serialize(token, &serialized, &err_msg); if (rv != 0) { fprintf(stderr, "Failed to serialize: %s\n", err_msg); goto cleanup; } printf("Created token: %s\n", serialized); // Step 4: Store public key for validation rv = scitoken_store_public_ec_key("https://demo.scitokens.org/gtest", "1", ec_public, &err_msg); if (rv != 0) { fprintf(stderr, "Failed to store public key: %s\n", err_msg); free(serialized); goto cleanup; } // Step 5: Deserialize and validate SciToken parsed_token; const char *allowed_issuers[] = {"https://demo.scitokens.org/gtest", NULL}; rv = scitoken_deserialize(serialized, &parsed_token, allowed_issuers, &err_msg); free(serialized); if (rv != 0) { fprintf(stderr, "Failed to deserialize: %s\n", err_msg); goto cleanup; } printf("Token validation successful!\n"); // Step 6: Create enforcer and generate ACLs const char *audiences[] = {"https://demo.scitokens.org/", NULL}; Enforcer enforcer = enforcer_create("https://demo.scitokens.org/gtest", audiences, &err_msg); if (enforcer) { Acl *acls; rv = enforcer_generate_acls(enforcer, parsed_token, &acls, &err_msg); if (rv == 0) { printf("Generated ACLs:\n"); for (int i = 0; acls[i].authz || acls[i].resource; i++) { printf(" %s: %s\n", acls[i].authz ? acls[i].authz : "(null)", acls[i].resource ? acls[i].resource : "(null)"); } enforcer_acl_free(acls); } enforcer_destroy(enforcer); } scitoken_destroy(parsed_token); cleanup: if (err_msg) free(err_msg); scitoken_destroy(token); scitoken_key_destroy(key); return rv; }scitokens-cpp-1.3.0/docs/index.rst000066400000000000000000000021641513647275500171100ustar00rootroot00000000000000SciTokens C++ Library Documentation ==================================== SciTokens provide a token format for distributed authorization. The tokens are self-describing, can be verified in a distributed fashion (no need to contact the issuer to determine if the token is valid). This is convenient for a federated environment where several otherwise-independent storage endpoints want to delegate trust for an issuer for managing a storage allocation. The SciTokens C++ library implements a minimal library for creating and using SciTokens from C or C++. .. toctree:: :maxdepth: 2 :caption: Contents: installation api examples Quick Start ----------- This library provides both C and C++ APIs for working with SciTokens. The primary interface is through the C API defined in ``scitokens.h``. Key Features: * Create and sign SciTokens * Validate and verify SciTokens * Generate Access Control Lists (ACLs) from tokens * Support for multiple token profiles (SciTokens 1.0/2.0, WLCG, AT+JWT) * Asynchronous token operations Indices and tables ================== * :ref:`genindex` * :ref:`modindex` * :ref:`search`scitokens-cpp-1.3.0/docs/installation.rst000066400000000000000000000036171513647275500205060ustar00rootroot00000000000000Installation ============ Dependencies ------------ To build the ``scitokens-cpp`` library, the following dependencies are needed: * `jwt-cpp `_ v0.5.0 or later: A header-only C++ library for manipulating JWTs. * OpenSSL 1.0 or later * ``sqlite3`` * ``libcurl`` * ``libuuid`` On Ubuntu/Debian systems:: sudo apt install libcurl4-openssl-dev libssl-dev libsqlite3-dev uuid-dev pkg-config On CentOS/RHEL systems:: sudo yum install libcurl-devel openssl-devel sqlite-devel libuuid-devel pkgconfig Building from Source -------------------- CMake is used for the build system. To build, from the source directory:: mkdir build cd build cmake .. make The library will be built as ``libSciTokens.so`` and the following utilities will be created: * ``scitokens-verify`` - Verify a SciToken * ``scitokens-create`` - Create a new SciToken * ``scitokens-test`` - Test utility * ``scitokens-test-access`` - Test access patterns * ``scitokens-list-access`` - List access permissions Installation ------------ To install the library and headers:: sudo make install This will install: * Library: ``libSciTokens.so`` to ``/usr/local/lib`` * Headers: ``scitokens.h`` to ``/usr/local/include/scitokens/`` * Utilities: Command-line tools to ``/usr/local/bin`` Package Installation -------------------- RPM packages are available for CentOS/RHEL systems. Check the project releases page for available packages. Testing the Installation ------------------------ The easiest way to test ``scitokens-cpp`` is to head to the `SciTokens Demo app `_ and copy the generated token. Then, from the build directory:: echo "" | ./scitokens-verify Replace the given token above with the fresh one you just generated; using an old token should give an expired token error. The token must be provided via standard input (stdin).scitokens-cpp-1.3.0/docs/requirements.txt000066400000000000000000000000571513647275500205320ustar00rootroot00000000000000sphinx>=4.0 breathe>=4.30 sphinx-rtd-theme>=1.0scitokens-cpp-1.3.0/rpm/000077500000000000000000000000001513647275500151125ustar00rootroot00000000000000scitokens-cpp-1.3.0/rpm/scitokens-cpp.spec000066400000000000000000000167401513647275500205600ustar00rootroot00000000000000Name: scitokens-cpp Version: 1.3.0 Release: 1%{?dist} Summary: C++ Implementation of the SciTokens Library License: ASL 2.0 URL: https://github.com/scitokens/scitokens-cpp # Directions to generate a proper release: # VER=0.3.3 # for example # git archive --prefix "scitokens-cpp-$VER/" -o "scitokens-cpp-$VER.tar" v$VER # git submodule update --init # git submodule foreach --recursive "git archive --prefix=scitokens-cpp-$VER/\$path/ --output=\$sha1.tar HEAD && tar --concatenate --file=$(pwd)/scitokens-cpp-$VER.tar \$sha1.tar && rm \$sha1.tar" # gzip "scitokens-cpp-$VER.tar" Source0: https://github.com/scitokens/scitokens-cpp/releases/download/v%{version}/%{name}-%{version}.tar.gz # Scitokens-cpp bundles jwt-cpp, a header only dependency # Since it doesn't create a library that can be used by others, it seems # inappropriate to include a "Provides", as jwt-cpp is not provided # by this package. BuildRequires: gcc-c++ BuildRequires: make BuildRequires: cmake3 BuildRequires: sqlite-devel BuildRequires: openssl-devel BuildRequires: libcurl-devel BuildRequires: libuuid-devel %if 0%{?el7} # needed for ldconfig_scriptlets BuildRequires: epel-rpm-macros %endif %description %{summary} %package devel Summary: Header files for the scitokens-cpp public interfaces Requires: %{name}%{?_isa} = %{version} %description devel %{summary} %prep %setup -q %build %cmake3 %cmake3_build %install %cmake3_install # Run the ldconfig %ldconfig_scriptlets %files %{_libdir}/libSciTokens.so.0* %{_bindir}/scitokens-* %license LICENSE %doc README.md %files devel %{_libdir}/libSciTokens.so %{_includedir}/scitokens/scitokens.h %dir %{_includedir}/scitokens %changelog * Thu Dec 11 2025 Brian Bockelman - 1.3.0-1 - Add scitokens-generate-jwks CLI for key generation. - Add environment variable-based configuration on library initialization. - Add per-issuer lock to prevent multiple concurrent queries against issuers without a known key - Add negative cache for failed issuer lookups (preventing frequent re-queries) - Add monitoring API for per-issuer validation statistics - Add optional background thread for JWKS refresh - Add keycache load, metadata, and delete APIs - Revert "Fix memory leak in rs256_from_coords" by @djw8605 - Add CTest-based integration test with JWKS server and TLS infrastructure * Fri Dec 05 2025 Derek Weitzel - 1.2.0-1 - Fix segfault if the JSON parser cannot parse the JWKS - Fix float time claims issue and improve error handling - Fix security issue with malicious issuer handling in error messages - Improve JWTVerificationException message to include the invalid issuer - Update usage on verify command to make the TOKENFILE explicit - Read token for scitokens-verify from stdin - Set CURLOPT_NOSIGNAL option in SimpleCurlGet to prevent signal interruptions - Adding asan value to the job name - Turn off building unit tests by default. - Add cmake option SCITOKENS_WITH_ASAN which enables memory checking with the address sanitizer. Also enable this in CI, so that tests fail if they hit a memory leak or other memory problem. - Fix memory leak in store_public_ec_key - Fix memory leaks in the unit tests - Fix memory leak in rs256_from_coords - Fix memory leak in scitokens_verify * Mon Feb 24 2025 Derek Weitzel - 1.1.3-1 - Include cstdint import for jwt library to support newer compilers * Wed Oct 30 2024 Derek Weitzel - 1.1.2-1 - Turn off CMAKE unity builds - Add a mutex around requesting public keys to stop overloading issuers * Wed Feb 28 2024 Derek Weitzel - 1.1.1-1 - Improve error handling around the sqlite3 library - Fix test failures and compiler warnings * Tue Nov 07 2023 Derek Weitzel - 1.1.0-1 - Allow the scitokens library user to setup a custom CA file - Fix typecast errors in scitoken_status_get_*() that caused async queries to fail - Fix logic error in deserialize_continue() that caused async deserialization to fail * Thu Jun 15 2023 Derek Weitzel - 1.0.2-1 - Add support for API-configurable cache home - Fix enforcer_acl_free logic - scitokens_internal: catch matching exception type after jwt-cpp update * Wed Apr 26 2023 Derek Weitzel - 1.0.1-1 - Fix bug in generate acls which would cause a timeout * Tue Mar 21 2023 Derek Weitzel - 1.0.0-1 - Add async API for parsing and verifying tokens - Add configuration API - Make nbf claim optional for non-scitokens tokens - Update to OpenSSL 3.0 * Wed Jun 22 2022 Derek Weitzel - 0.7.1-1 - Add scitokens-* binaries to the package - Bug: close sqlite db handle on return * Fri Feb 18 2022 Derek Weitzel - 0.7.0-1 - Changes from static analysis - If only one key is available, do not error on no kid - Support at+jwt profile * Fri Sep 03 2021 Dave Dykstra - 0.6.3-1 - Add support for building Debian packages on the OpenSUSE Build System - Add patch to jwt-cpp to update its picojson dependency in order to enable it to compile on Debian 11 and Ubuntu 21.04 - Fix el7 build by requiring epel-rpm-macros * Thu Aug 26 2021 Dave Dykstra - 0.6.2-2 - Make the build require cmake3 instead of cmake * Thu Jun 03 2021 Derek Weitzel - 0.6.2-1 - Correct WLCG compat for condor read permissions * Thu May 20 2021 Derek Weitzel - 0.6.1-1 - Fix vector resize for el8+ builds * Tue May 18 2021 Derek Weitzel - 0.6.0-2 - Add back paren patch * Tue Mar 09 2021 Derek Weitzel - 0.6.0-1 - Fix compilation errors on c++11 - Update to jwt-cpp-0.4.0 vendor - Change scitoken profile name to match spec, scitoken:2.0 * Wed Jun 24 2020 Derek Weitzel - 0.5.1-1 - Add storage.modify as write permission * Fri Feb 28 2020 Derek Weitzel - 0.5.0-1 - Add API for retrieving string list attributes * Fri Nov 08 2019 Derek Weitzel - 0.4.0-1 - Add support for WLCG profile * Fri Nov 08 2019 Derek Weitzel - 0.3.5-1 - Fix EC public key handling * Wed Sep 18 2019 Derek Weitzel - 0.3.4-1 - Fix bugs for support with IAM * Thu Aug 01 2019 Derek Weitzel - 0.3.3-3 - Update the packaging to bring it line with EPEL (fedora) guidelines * Tue Jul 30 2019 Derek Weitzel - 0.3.3-2 - Change the Source URL - Use make_build in the packaging * Thu Jul 25 2019 Derek Weitzel - 0.3.3-1 - Merge OSG changes - Use a newer, still supported version of devtoolset - Fix bug in verifying EC signed tokens #13 * Thu Jul 25 2019 Derek Weitzel - 0.3.2-1 - Update RPM to v0.3.2 of the packaging. - Fix downloading public key bug #12 * Thu Jun 20 2019 Brian Bockelman - 0.3.1-1 - Update RPM to v0.3.1 of the packaging. * Wed May 29 2019 Mátyás Selmeci - 0.3.0-4 - Use double layer of const for deserialize (patch from https://github.com/scitokens/scitokens-cpp/commit/ac0b2f0679488fa91c14ed781268efbcdb69ed3c) * Mon May 13 2019 Mátyás Selmeci - 0.3.0-3 - Add Force-aud-test-in-the-validator.patch from https://github.com/scitokens/scitokens-cpp/pull/8 * Fri May 03 2019 Mátyás Selmeci - 0.3.0-2 - Fix requirements * Thu May 02 2019 Mátyás Selmeci - 0.3.0-1 - Update to v0.3.0 - Add dependencies on libcurl-devel, libuuid-devel * Thu Jan 03 2019 Brian Bockelman - 0.1.0-1 - Initial version of the SciTokens C++ RPM. scitokens-cpp-1.3.0/src/000077500000000000000000000000001513647275500151035ustar00rootroot00000000000000scitokens-cpp-1.3.0/src/create.cpp000066400000000000000000000140361513647275500170560ustar00rootroot00000000000000 #include "scitokens.h" #include #include #include #include #include #include #include namespace { const char usage[] = "\n" "Syntax: %s [--cred cred_file] [--key key_file] [--keyid kid]\n" " [--claim key=val] ...\n" "\n" " Options\n" " -h | --help Display usage\n" " -c | --cred File containing signing " "credential.\n" " -k | --key File containing the signing " "private key.\n" " -K | --keyid Name of the token key.\n" " -i | --issuer Issuer for the token.\n" " -p | --profile Token profile (wlcg, scitokens1, " "scitokens2, atjwt).\n" "\n"; const struct option long_options[] = {{"help", no_argument, NULL, 'h'}, {"cred", required_argument, NULL, 'c'}, {"key", required_argument, NULL, 'k'}, {"keyid", required_argument, NULL, 'K'}, {"issuer", required_argument, NULL, 'i'}, {"claim", required_argument, NULL, 'C'}, {"profile", required_argument, NULL, 'p'}, {0, 0, 0, 0}}; const char short_options[] = "hc:k:K:i:C:p:"; std::string g_cred, g_key, g_kid, g_issuer, g_profile; std::vector g_claims; int init_arguments(int argc, char *argv[]) { int arg; while ((arg = getopt_long(argc, argv, short_options, long_options, nullptr)) != -1) { switch (arg) { case 'h': printf(usage, argv[0]); exit(0); break; case 'c': g_cred = optarg; break; case 'k': g_key = optarg; break; case 'K': g_kid = optarg; break; case 'i': g_issuer = optarg; break; case 'C': g_claims.emplace_back(optarg); break; case 'p': g_profile = optarg; break; default: fprintf(stderr, usage, argv[0]); exit(1); break; } } if (optind != argc) { fprintf(stderr, "%s: invalid option -- %s\n", argv[0], argv[optind]); fprintf(stderr, usage, argv[0]); exit(1); } if (g_cred.empty()) { fprintf(stderr, "%s: missing --cred option\n", argv[0]); fprintf(stderr, usage, argv[0]); exit(1); } if (g_key.empty()) { fprintf(stderr, "%s: missing --key option\n", argv[0]); fprintf(stderr, usage, argv[0]); exit(1); } if (g_kid.empty()) { fprintf(stderr, "%s: missing --keyid option\n", argv[0]); fprintf(stderr, usage, argv[0]); exit(1); } if (g_issuer.empty()) { fprintf(stderr, "%s: missing --issuer option\n", argv[0]); fprintf(stderr, usage, argv[0]); exit(1); } return 0; } } // namespace int main(int argc, char *argv[]) { int rv = init_arguments(argc, argv); if (rv) { return rv; } std::ifstream priv_ifs(g_key); std::string private_contents((std::istreambuf_iterator(priv_ifs)), (std::istreambuf_iterator())); std::ifstream pub_ifs(g_cred); std::string public_contents((std::istreambuf_iterator(pub_ifs)), (std::istreambuf_iterator())); char *err_msg; auto key_raw = scitoken_key_create(g_kid.c_str(), "ES256", public_contents.c_str(), private_contents.c_str(), &err_msg); std::unique_ptr key( key_raw, scitoken_key_destroy); if (key_raw == nullptr) { fprintf(stderr, "Failed to generate a key: %s\n", err_msg); free(err_msg); return 1; } std::unique_ptr token( scitoken_create(key_raw), scitoken_destroy); if (token.get() == nullptr) { fprintf(stderr, "Failed to generate a new token.\n"); return 1; } rv = scitoken_set_claim_string(token.get(), "iss", g_issuer.c_str(), &err_msg); if (rv) { fprintf(stderr, "Failed to set issuer: %s\n", err_msg); free(err_msg); return 1; } for (const auto &claim : g_claims) { auto pos = claim.find("="); if (pos == std::string::npos) { fprintf(stderr, "Claim must contain a '=' character: %s\n", claim.c_str()); return 1; } auto key = claim.substr(0, pos); auto val = claim.substr(pos + 1); rv = scitoken_set_claim_string(token.get(), key.c_str(), val.c_str(), &err_msg); if (rv) { fprintf(stderr, "Failed to set claim (%s=%s): %s\n", key.c_str(), val.c_str(), err_msg); free(err_msg); return 1; } } if (!g_profile.empty()) { SciTokenProfile profile; if (g_profile == "wlcg") { profile = SciTokenProfile::WLCG_1_0; } else if (g_profile == "scitokens1") { profile = SciTokenProfile::SCITOKENS_1_0; } else if (g_profile == "scitokens2") { profile = SciTokenProfile::SCITOKENS_2_0; } else if (g_profile == "atjwt") { profile = SciTokenProfile::AT_JWT; } else { fprintf(stderr, "Unknown token profile: %s\n", g_profile.c_str()); return 1; } scitoken_set_serialize_mode(token.get(), profile); } char *value; rv = scitoken_serialize(token.get(), &value, &err_msg); if (rv) { fprintf(stderr, "Failed to serialize the token: %s\n", err_msg); free(err_msg); return 1; } printf("%s\n", value); } scitokens-cpp-1.3.0/src/generate_jwks.cpp000066400000000000000000000274021513647275500204440ustar00rootroot00000000000000#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if OPENSSL_VERSION_NUMBER >= 0x30000000L #include #include #endif #define EC_NAME NID_X9_62_prime256v1 namespace { const char usage[] = "\n" "Generate an EC key pair and output in JWKS and PEM formats.\n" "\n" "Syntax: %s [--kid key_id] [--jwks jwks_file] [--private private_file] " "[--public public_file]\n" "\n" " Options\n" " -h | --help Display usage\n" " -k | --kid Key ID for the JWKS (default: " "generated from public key fingerprint)\n" " -j | --jwks Output file for JWKS (default: " "\"jwks.json\")\n" " -p | --private Output file for private key PEM " "(default: \"private.pem\")\n" " -P | --public Output file for public key PEM " "(default: \"public.pem\")\n" "\n"; const struct option long_options[] = {{"help", no_argument, NULL, 'h'}, {"kid", required_argument, NULL, 'k'}, {"jwks", required_argument, NULL, 'j'}, {"private", required_argument, NULL, 'p'}, {"public", required_argument, NULL, 'P'}, {0, 0, 0, 0}}; const char short_options[] = "hk:j:p:P:"; std::string g_kid = ""; // Empty by default, will be generated from fingerprint std::string g_jwks_file = "jwks.json"; std::string g_private_file = "private.pem"; std::string g_public_file = "public.pem"; int init_arguments(int argc, char *argv[]) { int arg; while ((arg = getopt_long(argc, argv, short_options, long_options, nullptr)) != -1) { switch (arg) { case 'h': printf(usage, argv[0]); exit(0); break; case 'k': g_kid = optarg; break; case 'j': g_jwks_file = optarg; break; case 'p': g_private_file = optarg; break; case 'P': g_public_file = optarg; break; default: fprintf(stderr, usage, argv[0]); exit(1); break; } } if (optind != argc) { fprintf(stderr, "%s: invalid option -- %s\n", argv[0], argv[optind]); fprintf(stderr, usage, argv[0]); exit(1); } return 0; } // Base64url encode without padding std::string base64url_encode(const unsigned char *data, size_t len) { static const char base64_chars[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"; std::string result; result.reserve(((len + 2) / 3) * 4); for (size_t i = 0; i < len; i += 3) { unsigned int val = data[i] << 16; if (i + 1 < len) val |= data[i + 1] << 8; if (i + 2 < len) val |= data[i + 2]; result.push_back(base64_chars[(val >> 18) & 0x3F]); result.push_back(base64_chars[(val >> 12) & 0x3F]); if (i + 1 < len) { result.push_back(base64_chars[(val >> 6) & 0x3F]); } if (i + 2 < len) { result.push_back(base64_chars[val & 0x3F]); } } // Remove padding return result; } // Extract coordinates from EC key for JWKS bool extract_ec_coordinates(EVP_PKEY *pkey, std::string &x_coord, std::string &y_coord) { #if OPENSSL_VERSION_NUMBER >= 0x30000000L // For OpenSSL 3.0+, use the BIGNUM parameter API which is more reliable BIGNUM *x_bn = nullptr; BIGNUM *y_bn = nullptr; if (EVP_PKEY_get_bn_param(pkey, OSSL_PKEY_PARAM_EC_PUB_X, &x_bn) != 1 || EVP_PKEY_get_bn_param(pkey, OSSL_PKEY_PARAM_EC_PUB_Y, &y_bn) != 1) { BN_free(x_bn); BN_free(y_bn); return false; } std::unique_ptr x(x_bn, BN_free); std::unique_ptr y(y_bn, BN_free); // Convert BIGNUMs to fixed-size byte arrays (32 bytes for P-256) unsigned char x_buf[32] = {0}; unsigned char y_buf[32] = {0}; int x_len = BN_num_bytes(x.get()); int y_len = BN_num_bytes(y.get()); // Pad with zeros on the left if necessary BN_bn2bin(x.get(), x_buf + (32 - x_len)); BN_bn2bin(y.get(), y_buf + (32 - y_len)); x_coord = base64url_encode(x_buf, 32); y_coord = base64url_encode(y_buf, 32); #else std::unique_ptr ec_key( EVP_PKEY_get1_EC_KEY(pkey), EC_KEY_free); if (!ec_key) { return false; } const EC_POINT *pub_key = EC_KEY_get0_public_key(ec_key.get()); const EC_GROUP *group = EC_KEY_get0_group(ec_key.get()); if (!pub_key || !group) { return false; } std::unique_ptr x(BN_new(), BN_free); std::unique_ptr y(BN_new(), BN_free); // Use EC_POINT_get_affine_coordinates for OpenSSL 1.1.1+ // or EC_POINT_get_affine_coordinates_GFp for older versions int result = 0; #if OPENSSL_VERSION_NUMBER >= 0x10101000L result = EC_POINT_get_affine_coordinates(group, pub_key, x.get(), y.get(), nullptr); #else result = EC_POINT_get_affine_coordinates_GFp(group, pub_key, x.get(), y.get(), nullptr); #endif if (result != 1) { return false; } // Convert BIGNUMs to fixed-size byte arrays (32 bytes for P-256) unsigned char x_buf[32] = {0}; unsigned char y_buf[32] = {0}; int x_len = BN_num_bytes(x.get()); int y_len = BN_num_bytes(y.get()); BN_bn2bin(x.get(), x_buf + (32 - x_len)); BN_bn2bin(y.get(), y_buf + (32 - y_len)); x_coord = base64url_encode(x_buf, 32); y_coord = base64url_encode(y_buf, 32); #endif return true; } // Generate a key ID from the public key fingerprint std::string generate_key_id(EVP_PKEY *pkey) { // Get the public key in DER format std::unique_ptr bio(BIO_new(BIO_s_mem()), BIO_free_all); if (!bio) { return ""; } if (i2d_PUBKEY_bio(bio.get(), pkey) != 1) { return ""; } // Get the DER data char *der_data = nullptr; long der_len = BIO_get_mem_data(bio.get(), &der_data); if (der_len <= 0 || !der_data) { return ""; } // Compute SHA256 hash unsigned char hash[SHA256_DIGEST_LENGTH]; SHA256(reinterpret_cast(der_data), der_len, hash); // Convert first 4 bytes to hex (8 characters) std::ostringstream oss; for (int i = 0; i < 4; i++) { oss << std::hex << std::setfill('0') << std::setw(2) << static_cast(hash[i]); } return oss.str(); } } // namespace int main(int argc, char *argv[]) { if (init_arguments(argc, argv)) { return 1; } // Generate EC key EVP_PKEY *pkey = nullptr; #if OPENSSL_VERSION_NUMBER >= 0x30000000L std::unique_ptr ctx( EVP_PKEY_CTX_new_from_name(nullptr, "EC", nullptr), EVP_PKEY_CTX_free); if (!ctx || EVP_PKEY_keygen_init(ctx.get()) <= 0) { fprintf(stderr, "Failed to initialize EC key generation context\n"); return 1; } OSSL_PARAM params[2]; const char *curve_name = "prime256v1"; params[0] = OSSL_PARAM_construct_utf8_string( OSSL_PKEY_PARAM_GROUP_NAME, const_cast(curve_name), 0); params[1] = OSSL_PARAM_construct_end(); if (EVP_PKEY_CTX_set_params(ctx.get(), params) <= 0) { fprintf(stderr, "Failed to set EC curve parameters\n"); return 1; } if (EVP_PKEY_keygen(ctx.get(), &pkey) <= 0) { fprintf(stderr, "Failed to generate EC key\n"); return 1; } std::unique_ptr pkey_ptr(pkey, EVP_PKEY_free); #else std::unique_ptr ec_key( EC_KEY_new_by_curve_name(EC_NAME), EC_KEY_free); if (!ec_key) { fprintf(stderr, "Failed to create EC key\n"); return 1; } if (EC_KEY_generate_key(ec_key.get()) != 1) { fprintf(stderr, "Failed to generate EC key\n"); return 1; } std::unique_ptr pkey_ptr(EVP_PKEY_new(), EVP_PKEY_free); if (!pkey_ptr) { fprintf(stderr, "Failed to create EVP_PKEY\n"); return 1; } if (EVP_PKEY_assign_EC_KEY(pkey_ptr.get(), ec_key.get()) != 1) { fprintf(stderr, "Failed to assign EC key to EVP_PKEY\n"); return 1; } // Successfully assigned; release ownership from ec_key ec_key.release(); #endif // Extract EC coordinates for JWKS std::string x_coord, y_coord; if (!extract_ec_coordinates(pkey_ptr.get(), x_coord, y_coord)) { fprintf(stderr, "Failed to extract EC coordinates\n"); return 1; } // Generate key ID from fingerprint if not specified if (g_kid.empty()) { g_kid = generate_key_id(pkey_ptr.get()); if (g_kid.empty()) { fprintf(stderr, "Failed to generate key ID from fingerprint\n"); return 1; } } // Write JWKS file std::ofstream jwks_out(g_jwks_file); if (!jwks_out) { fprintf(stderr, "Failed to open %s for writing\n", g_jwks_file.c_str()); return 1; } jwks_out << "{\n"; jwks_out << " \"keys\": [\n"; jwks_out << " {\n"; jwks_out << " \"alg\": \"ES256\",\n"; jwks_out << " \"kty\": \"EC\",\n"; jwks_out << " \"use\": \"sig\",\n"; jwks_out << " \"crv\": \"P-256\",\n"; jwks_out << " \"kid\": \"" << g_kid << "\",\n"; jwks_out << " \"x\": \"" << x_coord << "\",\n"; jwks_out << " \"y\": \"" << y_coord << "\"\n"; jwks_out << " }\n"; jwks_out << " ]\n"; jwks_out << "}\n"; printf("JWKS written to: %s\n", g_jwks_file.c_str()); // Write public key PEM std::unique_ptr pub_bio( BIO_new_file(g_public_file.c_str(), "w"), BIO_free_all); if (!pub_bio) { fprintf(stderr, "Failed to open %s for writing\n", g_public_file.c_str()); return 1; } if (PEM_write_bio_PUBKEY(pub_bio.get(), pkey_ptr.get()) != 1) { fprintf(stderr, "Failed to write public key\n"); return 1; } printf("Public key written to: %s\n", g_public_file.c_str()); // Write private key PEM with secure permissions (0600) // First, create the file with restrictive permissions int fd = open(g_private_file.c_str(), O_WRONLY | O_CREAT | O_TRUNC, 0600); if (fd < 0) { fprintf(stderr, "Failed to create %s with secure permissions\n", g_private_file.c_str()); return 1; } close(fd); std::unique_ptr priv_bio( BIO_new_file(g_private_file.c_str(), "w"), BIO_free_all); if (!priv_bio) { fprintf(stderr, "Failed to open %s for writing\n", g_private_file.c_str()); return 1; } if (PEM_write_bio_PrivateKey(priv_bio.get(), pkey_ptr.get(), nullptr, nullptr, 0, nullptr, nullptr) != 1) { fprintf(stderr, "Failed to write private key\n"); return 1; } printf("Private key written to: %s\n", g_private_file.c_str()); return 0; } scitokens-cpp-1.3.0/src/list_access.cpp000066400000000000000000000027241513647275500201100ustar00rootroot00000000000000#include #include "scitokens.h" int main(int argc, const char **argv) { if (argc < 4) { std::cerr << "Usage: " << argv[0] << " (TOKEN) (ISSUER) (AUDIENCE)" << std::endl; return 1; } std::string token(argv[1]); std::string issuer(argv[2]); std::string audience(argv[3]); const char *aud_list[2]; aud_list[0] = audience.c_str(); aud_list[1] = nullptr; SciToken scitoken; char *err_msg = nullptr; if (scitoken_deserialize(token.c_str(), &scitoken, nullptr, &err_msg)) { std::cout << "Failed to deserialize a token: " << err_msg << std::endl; return 1; } std::cout << "Token deserialization successful. Checking authorizations." << std::endl; Enforcer enf; if (!(enf = enforcer_create(issuer.c_str(), aud_list, &err_msg))) { std::cout << "Failed to create a new enforcer object: " << err_msg << std::endl; return 1; } Acl *acls; if (enforcer_generate_acls(enf, scitoken, &acls, &err_msg)) { std::cout << "ACL generation failed: " << err_msg << std::endl; return 1; } std::cout << "Start of ACLs:" << std::endl; for (int idx = 0; acls[idx].authz && acls[idx].resource; idx++) { std::cout << "ACL: " << acls[idx].authz << ":" << acls[idx].resource << std::endl; } std::cout << "End of ACLs:" << std::endl; enforcer_destroy(enf); return 0; } scitokens-cpp-1.3.0/src/scitokens.cpp000066400000000000000000001216061513647275500176170ustar00rootroot00000000000000#include #include #include #include #include #include #include #include #include #include "scitokens.h" #include "scitokens_internal.h" /** * GLOBALS */ // These are kept for backwards compatibility but are now handled by // construct-on-first-use in the Configuration class accessor functions // See scitokens_internal.h for the new implementation std::atomic_int configurer::Configuration::m_next_update_delta{0}; std::atomic_int configurer::Configuration::m_expiry_delta{0}; std::shared_ptr configurer::Configuration::m_cache_home; std::shared_ptr configurer::Configuration::m_tls_ca_file; namespace { // Helper function to convert string to lowercase std::string to_lowercase(const std::string &str) { std::string result = str; std::transform(result.begin(), result.end(), result.begin(), [](unsigned char c) { return std::tolower(c); }); return result; } // Load configuration from environment variables on library initialization void load_config_from_environment() { // List of known configuration keys with their types and corresponding env // var names struct ConfigMapping { const char *config_key; const char *env_var_suffix; // After SCITOKEN_CONFIG_ bool is_int; }; const std::array known_configs = { {{"keycache.update_interval_s", "KEYCACHE_UPDATE_INTERVAL_S", true}, {"keycache.expiration_interval_s", "KEYCACHE_EXPIRATION_INTERVAL_S", true}, {"keycache.cache_home", "KEYCACHE_CACHE_HOME", false}, {"tls.ca_file", "TLS_CA_FILE", false}, {"monitoring.file", "MONITORING_FILE", false}, {"monitoring.file_interval_s", "MONITORING_FILE_INTERVAL_S", true}, {"keycache.refresh_interval_ms", "KEYCACHE_REFRESH_INTERVAL_MS", true}, {"keycache.refresh_threshold_ms", "KEYCACHE_REFRESH_THRESHOLD_MS", true}}}; const char *prefix = "SCITOKEN_CONFIG_"; // Check each known configuration for (const auto &config : known_configs) { // Build the full environment variable name std::string env_var = prefix + std::string(config.env_var_suffix); // Also try case variations (uppercase, lowercase, mixed) const char *env_value = std::getenv(env_var.c_str()); if (!env_value) { // Try with lowercase std::string env_var_lower = to_lowercase(env_var); env_value = std::getenv(env_var_lower.c_str()); } if (!env_value) { continue; // Not set in environment } char *err_msg = nullptr; if (config.is_int) { try { int value = std::stoi(env_value); scitoken_config_set_int(config.config_key, value, &err_msg); } catch (const std::invalid_argument &) { // Silently ignore invalid integer format during initialization } catch (const std::out_of_range &) { // Silently ignore out-of-range values during initialization } } else { scitoken_config_set_str(config.config_key, env_value, &err_msg); } // Free error message if any (we ignore errors during initialization) if (err_msg) { free(err_msg); } } } // Use constructor attribute to run on library load __attribute__((constructor)) void init_scitokens_config() { load_config_from_environment(); } } // anonymous namespace // Monitoring file config (empty string means disabled) // Protected by mutex; atomic flag for fast-path check std::string configurer::Configuration::m_monitoring_file; std::mutex configurer::Configuration::m_monitoring_file_mutex; std::atomic configurer::Configuration::m_monitoring_file_configured{ false}; std::atomic_int configurer::Configuration::m_monitoring_file_interval{60}; void configurer::Configuration::set_monitoring_file(const std::string &path) { std::lock_guard lock(m_monitoring_file_mutex); m_monitoring_file = path; // Update the atomic flag after setting the string m_monitoring_file_configured.store(!path.empty(), std::memory_order_release); } std::string configurer::Configuration::get_monitoring_file() { std::lock_guard lock(m_monitoring_file_mutex); return m_monitoring_file; } void configurer::Configuration::set_monitoring_file_interval(int seconds) { m_monitoring_file_interval = seconds; } int configurer::Configuration::get_monitoring_file_interval() { return m_monitoring_file_interval; } // Background refresh config std::atomic_bool configurer::Configuration::m_background_refresh_enabled{false}; std::atomic_int configurer::Configuration::m_refresh_interval_ms{ 60000}; // 60 seconds std::atomic_int configurer::Configuration::m_refresh_threshold_ms{ 600000}; // 10 minutes SciTokenKey scitoken_key_create(const char *key_id, const char *alg, const char *public_contents, const char *private_contents, char **err_msg) { if (key_id == nullptr) { if (err_msg) { *err_msg = strdup("Key ID cannot be NULL."); } return nullptr; } if (alg == nullptr) { if (err_msg) { *err_msg = strdup("Algorithm cannot be NULL."); } return nullptr; } if (public_contents == nullptr) { if (err_msg) { *err_msg = strdup("Public key contents cannot be NULL."); } return nullptr; } if (private_contents == nullptr) { if (err_msg) { *err_msg = strdup("Private key contents cannot be NULL."); } return nullptr; } return new scitokens::SciTokenKey(key_id, alg, public_contents, private_contents); } void scitoken_key_destroy(SciTokenKey token) { scitokens::SciTokenKey *real_token = reinterpret_cast(token); delete real_token; } SciToken scitoken_create(SciTokenKey private_key) { scitokens::SciTokenKey *key = reinterpret_cast(private_key); return new scitokens::SciToken(*key); } void scitoken_destroy(SciToken token) { scitokens::SciToken *real_token = reinterpret_cast(token); delete real_token; } int scitoken_set_claim_string(SciToken token, const char *key, const char *value, char **err_msg) { scitokens::SciToken *real_token = reinterpret_cast(token); if (real_token == nullptr) { if (err_msg) { *err_msg = strdup("Token passed is not initialized."); } return -1; } if (key == nullptr) { if (err_msg) { *err_msg = strdup("Claim key passed is not initialized."); } return -1; } if (value == nullptr) { if (err_msg) { *err_msg = strdup("Claim value passed is not initialized."); } return -1; } try { real_token->set_claim(key, jwt::claim(std::string(value))); } catch (std::exception &exc) { if (err_msg) { *err_msg = strdup(exc.what()); } return -1; } return 0; } void scitoken_set_serialize_profile(SciToken token, SciTokenProfile profile) { scitoken_set_serialize_mode(token, profile); } void scitoken_set_serialize_mode(SciToken token, SciTokenProfile profile) { scitokens::SciToken *real_token = reinterpret_cast(token); if (real_token == nullptr) { return; } real_token->set_serialize_mode( static_cast(profile)); } void scitoken_set_deserialize_profile(SciToken token, SciTokenProfile profile) { scitokens::SciToken *real_token = reinterpret_cast(token); if (real_token == nullptr) { return; } real_token->set_deserialize_mode( static_cast(profile)); } int scitoken_get_claim_string(const SciToken token, const char *key, char **value, char **err_msg) { scitokens::SciToken *real_token = reinterpret_cast(token); std::string claim_str; try { claim_str = real_token->get_claim_string(key); } catch (std::exception &exc) { if (err_msg) { *err_msg = strdup(exc.what()); } return -1; } *value = strdup(claim_str.c_str()); return 0; } int scitoken_set_claim_string_list(const SciToken token, const char *key, const char **value, char **err_msg) { auto real_token = reinterpret_cast(token); if (real_token == nullptr) { if (err_msg) *err_msg = strdup( "NULL scitoken passed to scitoken_get_claim_string_list"); return -1; } std::vector claim_list; int idx = 0; while (value[idx++]) { } claim_list.reserve(idx); idx = 0; while (value[idx++]) { claim_list.emplace_back(value[idx - 1]); } real_token->set_claim_list(key, claim_list); return 0; } int scitoken_get_claim_string_list(const SciToken token, const char *key, char ***value, char **err_msg) { auto real_token = reinterpret_cast(token); if (real_token == nullptr) { if (err_msg) *err_msg = strdup( "NULL scitoken passed to scitoken_get_claim_string_list"); return -1; } std::vector claim_list; try { claim_list = real_token->get_claim_list(key); } catch (std::exception &exc) { if (err_msg) { *err_msg = strdup(exc.what()); } return -1; } auto claim_list_c = static_cast(malloc(sizeof(char *) * (claim_list.size() + 1))); claim_list_c[claim_list.size()] = nullptr; int idx = 0; for (const auto &entry : claim_list) { claim_list_c[idx] = strdup(entry.c_str()); if (!claim_list_c[idx]) { scitoken_free_string_list(claim_list_c); if (err_msg) { *err_msg = strdup("Failed to create a copy of string entry in list"); } return -1; } idx++; } *value = claim_list_c; return 0; } void scitoken_free_string_list(char **value) { int idx = 0; do { free(value[idx++]); } while (value[idx]); free(value); } int scitoken_get_expiration(const SciToken token, long long *expiry, char **err_msg) { if (!token) { if (err_msg) { *err_msg = strdup("Token cannot be NULL"); } return -1; } if (!expiry) { if (err_msg) { *err_msg = strdup("Expiry output parameter cannot be NULL"); } return -1; } scitokens::SciToken *real_token = reinterpret_cast(token); if (!real_token->has_claim("exp")) { *expiry = -1; return 0; } long long result; try { auto claim_value = real_token->get_claim("exp").to_json(); if (claim_value.is()) { // Integer value result = claim_value.get(); } else if (claim_value.is()) { // Float value - convert to integer (truncate) // Float value - convert to integer using std::floor(). // This ensures expiration is not extended by fractional seconds. result = static_cast(std::floor(claim_value.get())); } else { if (err_msg) { *err_msg = strdup("'exp' claim must be a number (integer or float)"); } return -1; } } catch (std::exception &exc) { if (err_msg) { *err_msg = strdup(exc.what()); } return -1; } *expiry = result; return 0; } void scitoken_set_lifetime(SciToken token, int lifetime) { if (token == nullptr) { return; } scitokens::SciToken *real_token = reinterpret_cast(token); real_token->set_lifetime(lifetime); } int scitoken_serialize(const SciToken token, char **value, char **err_msg) { if (value == nullptr) { if (err_msg) { *err_msg = strdup("Output variable not provided"); } return -1; } scitokens::SciToken *real_token = reinterpret_cast(token); try { std::string serialized = real_token->serialize(); *value = strdup(serialized.c_str()); } catch (std::exception &exc) { if (err_msg) { *err_msg = strdup(exc.what()); } return -1; } return 0; } int scitoken_deserialize(const char *value, SciToken *token, char const *const *allowed_issuers, char **err_msg) { if (value == nullptr) { if (err_msg) { *err_msg = strdup("Token may not be NULL"); } return -1; } if (token == nullptr) { if (err_msg) { *err_msg = strdup("Output token not provided"); } return -1; } scitokens::SciTokenKey key; scitokens::SciToken *real_token = new scitokens::SciToken(key); int retval = scitoken_deserialize_v2(value, reinterpret_cast(real_token), allowed_issuers, err_msg); if (retval) { delete real_token; } else { *token = real_token; } return retval; } int scitoken_deserialize_v2(const char *value, SciToken token, char const *const *allowed_issuers, char **err_msg) { scitokens::SciToken *real_token = reinterpret_cast(token); std::vector allowed_issuers_vec; if (allowed_issuers != nullptr) { for (int idx = 0; allowed_issuers[idx]; idx++) { allowed_issuers_vec.push_back(allowed_issuers[idx]); } } try { real_token->deserialize(value, allowed_issuers_vec); } catch (std::exception &exc) { if (err_msg) { *err_msg = strdup(exc.what()); } return -1; } return 0; } int scitoken_deserialize_start(const char *value, SciToken *token, char const *const *allowed_issuers, SciTokenStatus *status_out, char **err_msg) { if (value == nullptr) { if (err_msg) { *err_msg = strdup("Token may not be NULL"); } return -1; } if (token == nullptr) { if (err_msg) { *err_msg = strdup("Output token not provided"); } return -1; } scitokens::SciTokenKey key; scitokens::SciToken *real_token = new scitokens::SciToken(key); std::vector allowed_issuers_vec; if (allowed_issuers != nullptr) { for (int idx = 0; allowed_issuers[idx]; idx++) { allowed_issuers_vec.push_back(allowed_issuers[idx]); } } std::unique_ptr status; try { status = real_token->deserialize_start(value, allowed_issuers_vec); } catch (std::exception &exc) { if (err_msg) { *err_msg = strdup(exc.what()); } delete real_token; *status_out = nullptr; return -1; } // Check if we're done if (status->m_status->m_done) { *token = real_token; *status_out = nullptr; return 0; } *token = real_token; *status_out = status.release(); return 0; } int scitoken_deserialize_continue(SciToken *token, SciTokenStatus *status, char **err_msg) { if (token == nullptr) { if (err_msg) { *err_msg = strdup("Output token not provided"); } return -1; } scitokens::SciToken *real_token = reinterpret_cast(*token); std::unique_ptr real_status( reinterpret_cast(*status)); if (*status == nullptr || real_status->m_status->m_done) { *status = nullptr; return 0; } try { real_status = real_token->deserialize_continue(std::move(real_status)); } catch (std::exception &exc) { *status = nullptr; if (err_msg) { *err_msg = strdup(exc.what()); } return -1; } if (real_status->m_status->m_done) { *status = nullptr; } else { *status = real_status.release(); } return 0; } int scitoken_store_public_ec_key(const char *issuer, const char *keyid, const char *key, char **err_msg) { bool success; try { success = scitokens::Validator::store_public_ec_key(issuer, keyid, key); } catch (std::exception &exc) { if (err_msg) { *err_msg = strdup(exc.what()); } return -1; } return success ? 0 : -1; } Validator validator_create() { return new Validator(); } void validator_destroy(Validator validator) { scitokens::Validator *real_validator = reinterpret_cast(validator); delete real_validator; } void validator_set_token_profile(Validator validator, SciTokenProfile profile) { if (validator == nullptr) { return; } auto real_validator = reinterpret_cast(validator); real_validator->set_validate_profile( static_cast(profile)); } int validator_add(Validator validator, const char *claim, StringValidatorFunction validator_func, char **err_msg) { if (validator == nullptr) { if (err_msg) { *err_msg = strdup("Validator may not be a null pointer"); } return -1; } auto real_validator = reinterpret_cast(validator); if (claim == nullptr) { if (err_msg) { *err_msg = strdup("Claim name may not be a null pointer"); } return -1; } if (validator_func == nullptr) { if (err_msg) { *err_msg = strdup("Validator function may not be a null pointer"); } return -1; } real_validator->add_string_validator(claim, validator_func); return 0; } int validator_add_critical_claims(Validator validator, const char **claims, char **err_msg) { if (validator == nullptr) { if (err_msg) { *err_msg = strdup("Validator may not be a null pointer"); } return -1; } auto real_validator = reinterpret_cast(validator); if (claims == nullptr) { if (err_msg) { *err_msg = strdup("Claim list may not be a null pointer"); } return -1; } std::vector claims_vec; for (int idx = 0; claims[idx]; idx++) { claims_vec.push_back(claims[idx]); } real_validator->add_critical_claims(claims_vec); return 0; } int validator_validate(Validator validator, SciToken scitoken, char **err_msg) { if (validator == nullptr) { if (err_msg) { *err_msg = strdup("Validator may not be a null pointer"); } return -1; } auto real_validator = reinterpret_cast(validator); if (scitoken == nullptr) { if (err_msg) { *err_msg = strdup("SciToken may not be a null pointer"); } return -1; } auto real_scitoken = reinterpret_cast(scitoken); try { real_validator->verify(*real_scitoken, time(NULL) + 20); } catch (std::exception &exc) { if (err_msg) { *err_msg = strdup(exc.what()); } return -1; } return 0; } int validator_set_time(Validator validator, time_t now, char **err_msg) { if (validator == nullptr) { if (err_msg) { *err_msg = strdup("Validator may not be a null pointer"); } return -1; } auto real_validator = reinterpret_cast(validator); real_validator->set_now(std::chrono::system_clock::from_time_t(now)); return 0; } Enforcer enforcer_create(const char *issuer, const char **audience_list, char **err_msg) { if (issuer == nullptr) { if (err_msg) { *err_msg = strdup("Issuer may not be a null pointer"); } return nullptr; } std::vector aud_list; if (audience_list != nullptr) { for (int idx = 0; audience_list[idx]; idx++) { aud_list.push_back(audience_list[idx]); } } return new scitokens::Enforcer(issuer, aud_list); } void enforcer_destroy(Enforcer enf) { if (enf == nullptr) { return; } auto real_enf = reinterpret_cast(enf); delete real_enf; } void enforcer_acl_free(Acl *acls) { for (int idx = 0; acls[idx].authz != nullptr || acls[idx].resource != nullptr; idx++) { free(const_cast(acls[idx].authz)); free(const_cast(acls[idx].resource)); } free(acls); } void enforcer_set_validate_profile(Enforcer enf, SciTokenProfile profile) { if (enf == nullptr) { return; } auto real_enf = reinterpret_cast(enf); real_enf->set_validate_profile( static_cast(profile)); } namespace { Acl *convert_acls(scitokens::Enforcer::AclsList &acls_list, char **err_msg) { Acl *acl_result = static_cast(malloc((acls_list.size() + 1) * sizeof(Acl))); size_t idx = 0; for (const auto &acl : acls_list) { acl_result[idx].authz = strdup(acl.first.c_str()); acl_result[idx].resource = strdup(acl.second.c_str()); if (acl_result[idx].authz == nullptr) { enforcer_acl_free(acl_result); if (err_msg) { *err_msg = strdup("ACL was generated without an authorization set."); } return nullptr; } if (acl_result[idx].resource == nullptr) { enforcer_acl_free(acl_result); if (err_msg) { *err_msg = strdup("ACL was generated without a resource set."); } return nullptr; } idx++; } acl_result[idx].authz = nullptr; acl_result[idx].resource = nullptr; return acl_result; } } // namespace int enforcer_set_time(Enforcer enf, time_t now, char **err_msg) { if (enf == nullptr) { if (err_msg) { *err_msg = strdup("Enforcer may not be a null pointer"); } return -1; } auto real_enf = reinterpret_cast(enf); real_enf->set_now(std::chrono::system_clock::from_time_t(now)); return 0; } int enforcer_generate_acls(const Enforcer enf, const SciToken scitoken, Acl **acls, char **err_msg) { if (enf == nullptr) { if (err_msg) { *err_msg = strdup("Enforcer may not be a null pointer"); } return -1; } auto real_enf = reinterpret_cast(enf); if (scitoken == nullptr) { if (err_msg) { *err_msg = strdup("SciToken may not be a null pointer"); } return -1; } auto real_scitoken = reinterpret_cast(scitoken); scitokens::Enforcer::AclsList acls_list; try { acls_list = real_enf->generate_acls(*real_scitoken); } catch (std::exception &exc) { if (err_msg) { *err_msg = strdup(exc.what()); } return -1; } auto result_acls = convert_acls(acls_list, err_msg); if (!result_acls) { return -1; } *acls = result_acls; return 0; } int enforcer_generate_acls_start(const Enforcer enf, const SciToken scitoken, SciTokenStatus *status_out, Acl **acls, char **err_msg) { if (enf == nullptr) { if (err_msg) { *err_msg = strdup("Enforcer may not be a null pointer"); } return -1; } auto real_enf = reinterpret_cast(enf); if (scitoken == nullptr) { if (err_msg) { *err_msg = strdup("SciToken may not be a null pointer"); } return -1; } auto real_scitoken = reinterpret_cast(scitoken); scitokens::Enforcer::AclsList acls_list; std::unique_ptr status; try { status = real_enf->generate_acls_start(*real_scitoken, acls_list); } catch (std::exception &exc) { if (err_msg) { *err_msg = strdup(exc.what()); } return -1; } if (status->m_done) { auto result_acls = convert_acls(acls_list, err_msg); if (!result_acls) { return -1; } *acls = result_acls; *status_out = nullptr; return 0; } *status_out = status.release(); return 0; } int enforcer_generate_acls_continue(const Enforcer enf, SciTokenStatus *status, Acl **acls, char **err_msg) { if (enf == nullptr) { if (err_msg) { *err_msg = strdup("Enforcer may not be a null pointer"); } return -1; } auto real_enf = reinterpret_cast(enf); if (status == nullptr) { if (err_msg) { *err_msg = strdup("Status may not be a null pointer"); } return -1; } scitokens::Enforcer::AclsList acls_list; std::unique_ptr status_internal( reinterpret_cast(*status)); try { status_internal = real_enf->generate_acls_continue( std::move(status_internal), acls_list); } catch (std::exception &exc) { *status = nullptr; if (err_msg) { *err_msg = strdup(exc.what()); } return -1; } if (status_internal->m_done) { auto result_acls = convert_acls(acls_list, err_msg); if (!result_acls) { return -1; } *acls = result_acls; *status = nullptr; return 0; } *status = status_internal.release(); return 0; } int enforcer_test(const Enforcer enf, const SciToken scitoken, const Acl *acl, char **err_msg) { if (enf == nullptr) { if (err_msg) { *err_msg = strdup("Enforcer may not be a null pointer"); } return -1; } auto real_enf = reinterpret_cast(enf); if (scitoken == nullptr) { if (err_msg) { *err_msg = strdup("SciToken may not be a null pointer"); } return -1; } auto real_scitoken = reinterpret_cast(scitoken); if (acl == nullptr) { if (err_msg) { *err_msg = strdup("ACL may not be a null pointer"); } return -1; } try { return real_enf->test(*real_scitoken, acl->authz, acl->resource) == true ? 0 : -1; } catch (std::exception &exc) { if (err_msg) { *err_msg = strdup(exc.what()); } return -1; } return 0; } void scitoken_status_free(SciTokenStatus status) { std::unique_ptr status_real( reinterpret_cast(status)); } int scitoken_status_get_timeout_val(const SciTokenStatus *status, time_t expiry_time, struct timeval *timeout, char **err_msg) { if (status == nullptr) { if (err_msg) { *err_msg = strdup("Status object may not be a null pointer"); } return -1; } if (timeout == nullptr) { if (err_msg) { *err_msg = strdup("Timeout object may not be a null pointer"); } return -1; } auto real_status = reinterpret_cast(*status); struct timeval timeout_internal = real_status->m_status->get_timeout_val(expiry_time); timeout->tv_sec = timeout_internal.tv_sec; timeout->tv_usec = timeout_internal.tv_usec; return 0; } int scitoken_status_get_read_fd_set(SciTokenStatus *status, fd_set **read_fd_set, char **err_msg) { if (status == nullptr) { if (err_msg) { *err_msg = strdup("Status object may not be a null pointer"); } return -1; } if (read_fd_set == nullptr) { if (err_msg) { *err_msg = strdup("Read fd_set object may not be a null pointer"); } return -1; } auto real_status = reinterpret_cast(*status); *read_fd_set = real_status->m_status->get_read_fd_set(); return 0; } int scitoken_status_get_write_fd_set(SciTokenStatus *status, fd_set **write_fd_set, char **err_msg) { if (status == nullptr) { if (err_msg) { *err_msg = strdup("Status object may not be a null pointer"); } return -1; } if (write_fd_set == nullptr) { if (err_msg) { *err_msg = strdup("Write fd_set object may not be a null pointer"); } return -1; } auto real_status = reinterpret_cast(*status); *write_fd_set = real_status->m_status->get_write_fd_set(); return 0; } int scitoken_status_get_exc_fd_set(SciTokenStatus *status, fd_set **exc_fd_set, char **err_msg) { if (status == nullptr) { if (err_msg) { *err_msg = strdup("Status object may not be a null pointer"); } return -1; } if (exc_fd_set == nullptr) { if (err_msg) { *err_msg = strdup("Read fd_set object may not be a null pointer"); } return -1; } auto real_status = reinterpret_cast(*status); *exc_fd_set = real_status->m_status->get_exc_fd_set(); return 0; } int scitoken_status_get_max_fd(const SciTokenStatus *status, int *max_fd, char **err_msg) { if (status == nullptr) { if (err_msg) { *err_msg = strdup("Status object may not be a null pointer"); } return -1; } if (max_fd == nullptr) { if (err_msg) { *err_msg = strdup("Max FD may not be a null pointer"); } return -1; } auto real_status = reinterpret_cast(*status); *max_fd = real_status->m_status->get_max_fd(); return 0; } int keycache_refresh_jwks(const char *issuer, char **err_msg) { if (!issuer) { if (err_msg) { *err_msg = strdup("Issuer may not be a null pointer"); } return -1; } try { if (!scitokens::Validator::refresh_jwks(issuer)) { if (err_msg) { *err_msg = strdup("Failed to refresh JWKS cache for issuer."); } return -1; } } catch (std::exception &exc) { if (err_msg) { *err_msg = strdup(exc.what()); } return -1; } return 0; } int keycache_get_cached_jwks(const char *issuer, char **jwks, char **err_msg) { if (!issuer) { if (err_msg) { *err_msg = strdup("Issuer may not be a null pointer"); } return -1; } if (!jwks) { if (err_msg) { *err_msg = strdup("JWKS output pointer may not be null."); } return -1; } try { *jwks = strdup(scitokens::Validator::get_jwks(issuer).c_str()); } catch (std::exception &exc) { if (err_msg) { *err_msg = strdup(exc.what()); } return -1; } return 0; } int keycache_set_jwks(const char *issuer, const char *jwks, char **err_msg) { if (!issuer) { if (err_msg) { *err_msg = strdup("Issuer may not be a null pointer"); } return -1; } if (!jwks) { if (err_msg) { *err_msg = strdup("JWKS pointer may not be null."); } return -1; } try { if (!scitokens::Validator::store_jwks(issuer, jwks)) { if (err_msg) { *err_msg = strdup("Failed to set the JWKS cache for issuer."); } return -1; } } catch (std::exception &exc) { if (err_msg) { *err_msg = strdup(exc.what()); } return -1; } return 0; } int keycache_set_background_refresh(int enabled, char **err_msg) { try { bool enable = (enabled != 0); configurer::Configuration::set_background_refresh_enabled(enable); if (enable) { scitokens::internal::BackgroundRefreshManager::get_instance() .start(); } else { scitokens::internal::BackgroundRefreshManager::get_instance() .stop(); } } catch (std::exception &exc) { if (err_msg) { *err_msg = strdup(exc.what()); } return -1; } return 0; } int keycache_stop_background_refresh(char **err_msg) { return keycache_set_background_refresh(0, err_msg); } int keycache_load_jwks(const char *issuer, char **jwks, char **err_msg) { if (!issuer) { if (err_msg) { *err_msg = strdup("Issuer may not be a null pointer"); } return -1; } if (!jwks) { if (err_msg) { *err_msg = strdup("JWKS output pointer may not be null."); } return -1; } try { *jwks = strdup(scitokens::Validator::load_jwks(issuer).c_str()); } catch (std::exception &exc) { if (err_msg) { *err_msg = strdup(exc.what()); } return -1; } return 0; } int keycache_get_jwks_metadata(const char *issuer, char **metadata, char **err_msg) { if (!issuer) { if (err_msg) { *err_msg = strdup("Issuer may not be a null pointer"); } return -1; } if (!metadata) { if (err_msg) { *err_msg = strdup("Metadata output pointer may not be null."); } return -1; } try { *metadata = strdup(scitokens::Validator::get_jwks_metadata(issuer).c_str()); } catch (std::exception &exc) { if (err_msg) { *err_msg = strdup(exc.what()); } return -1; } return 0; } int keycache_delete_jwks(const char *issuer, char **err_msg) { if (!issuer) { if (err_msg) { *err_msg = strdup("Issuer may not be a null pointer"); } return -1; } try { if (!scitokens::Validator::delete_jwks(issuer)) { if (err_msg) { *err_msg = strdup("Failed to delete JWKS cache entry for issuer."); } return -1; } } catch (std::exception &exc) { if (err_msg) { *err_msg = strdup(exc.what()); } return -1; } return 0; } int config_set_int(const char *key, int value, char **err_msg) { return scitoken_config_set_int(key, value, err_msg); } int scitoken_config_set_int(const char *key, int value, char **err_msg) { if (!key) { if (err_msg) { *err_msg = strdup("A key must be provided."); } return -1; } std::string _key = key; if (_key == "keycache.update_interval_s") { if (value < 0) { if (err_msg) { *err_msg = strdup("Update interval must be positive."); } return -1; } configurer::Configuration::set_next_update_delta(value); return 0; } else if (_key == "keycache.expiration_interval_s") { if (value < 0) { if (err_msg) { *err_msg = strdup("Expiry interval must be positive."); } return -1; } configurer::Configuration::set_expiry_delta(value); return 0; } else if (_key == "monitoring.file_interval_s") { if (value < 0) { if (err_msg) { *err_msg = strdup("Interval cannot be negative."); } return -1; } configurer::Configuration::set_monitoring_file_interval(value); return 0; } else if (_key == "keycache.refresh_interval_ms") { if (value < 0) { if (err_msg) { *err_msg = strdup("Refresh interval must be positive."); } return -1; } configurer::Configuration::set_refresh_interval(value); return 0; } else if (_key == "keycache.refresh_threshold_ms") { if (value < 0) { if (err_msg) { *err_msg = strdup("Refresh threshold must be positive."); } return -1; } configurer::Configuration::set_refresh_threshold(value); return 0; } else { if (err_msg) { *err_msg = strdup("Key not recognized."); } return -1; } } int config_get_int(const char *key, char **err_msg) { return scitoken_config_get_int(key, err_msg); } int scitoken_config_get_int(const char *key, char **err_msg) { if (!key) { if (err_msg) { *err_msg = strdup("A key must be provided."); } return -1; } std::string _key = key; if (_key == "keycache.update_interval_s") { return configurer::Configuration::get_next_update_delta(); } else if (_key == "keycache.expiration_interval_s") { return configurer::Configuration::get_expiry_delta(); } else if (_key == "monitoring.file_interval_s") { return configurer::Configuration::get_monitoring_file_interval(); } else if (_key == "keycache.refresh_interval_ms") { return configurer::Configuration::get_refresh_interval(); } else if (_key == "keycache.refresh_threshold_ms") { return configurer::Configuration::get_refresh_threshold(); } else { if (err_msg) { *err_msg = strdup("Key not recognized."); } return -1; } } int scitoken_config_set_str(const char *key, const char *value, char **err_msg) { if (!key) { if (err_msg) { *err_msg = strdup("A key must be provided."); } return -1; } std::string _key = key; if (_key == "keycache.cache_home") { auto rp = configurer::Configuration::set_cache_home(value); if (!rp.first) { // There was an error, pass rp.second to err_msg if (err_msg) { *err_msg = strdup(rp.second.c_str()); } return -1; } } else if (_key == "tls.ca_file") { configurer::Configuration::set_tls_ca_file(value ? std::string(value) : ""); } else if (_key == "monitoring.file") { configurer::Configuration::set_monitoring_file( value ? std::string(value) : ""); } else { if (err_msg) { *err_msg = strdup("Key not recognized."); } return -1; } return 0; } int scitoken_config_get_str(const char *key, char **output, char **err_msg) { if (!key) { if (err_msg) { *err_msg = strdup("A key must be provided."); } return -1; } std::string _key = key; if (_key == "keycache.cache_home") { *output = strdup(configurer::Configuration::get_cache_home().c_str()); } else if (_key == "tls.ca_file") { *output = strdup(configurer::Configuration::get_tls_ca_file().c_str()); } else if (_key == "monitoring.file") { *output = strdup(configurer::Configuration::get_monitoring_file().c_str()); } else { if (err_msg) { *err_msg = strdup("Key not recognized."); } return -1; } return 0; } int scitoken_get_monitoring_json(char **json_out, char **err_msg) { if (!json_out) { if (err_msg) { *err_msg = strdup("JSON output pointer may not be null."); } return -1; } try { std::string json = scitokens::internal::MonitoringStats::instance().get_json(); *json_out = strdup(json.c_str()); } catch (std::exception &exc) { if (err_msg) { *err_msg = strdup(exc.what()); } return -1; } return 0; } int scitoken_reset_monitoring_stats(char **err_msg) { try { scitokens::internal::MonitoringStats::instance().reset(); } catch (std::exception &exc) { if (err_msg) { *err_msg = strdup(exc.what()); } return -1; } return 0; } scitokens-cpp-1.3.0/src/scitokens.h000066400000000000000000000531431513647275500172640ustar00rootroot00000000000000/** * Public header for the SciTokens C library. * * */ #include #include #ifdef __cplusplus #include extern "C" { #else #include #endif /** @brief Opaque handle for cryptographic keys used to sign/verify tokens */ typedef void *SciTokenKey; /** @brief Opaque handle for SciToken objects */ typedef void *SciToken; /** @brief Opaque handle for token validators */ typedef void *Validator; /** @brief Opaque handle for token enforcers that generate ACLs */ typedef void *Enforcer; /** @brief Opaque handle for asynchronous operation status */ typedef void *SciTokenStatus; /** @brief Opaque handle for configuration objects */ typedef void *Configuration; /** @brief Function pointer type for custom string validation */ typedef int (*StringValidatorFunction)(const char *value, char **err_msg); /** @brief Access Control List entry containing authorization and resource */ typedef struct Acl_s { const char *authz; /**< Authorization type (e.g., "read", "write") */ const char *resource; /**< Resource path or pattern */ } Acl; /** * Determine the mode we will use to validate tokens. * - COMPAT mode (default) indicates any supported token format * is acceptable. Where possible, the scope names are translated into * equivalent SciTokens 1.0 claim names (i.e., storage.read -> read; * storage.write -> write). If a typ header claim is present, use that to deduce * type (RFC8725 Section 3.11). * - SCITOKENS_1_0, SCITOKENS_2_0, WLCG_1_0, AT_JWT: only accept these specific * profiles. No automatic translation is performed. */ typedef enum _profile { COMPAT = 0, SCITOKENS_1_0, SCITOKENS_2_0, WLCG_1_0, AT_JWT } SciTokenProfile; /** * @brief Create a cryptographic key for signing tokens * * @param key_id Identifier for the key (used in 'kid' header) * @param algorithm Signing algorithm (e.g., "ES256", "RS256") * @param public_contents PEM-encoded public key * @param private_contents PEM-encoded private key * @param err_msg Output parameter for error messages (caller must free) * @return SciTokenKey handle on success, NULL on failure */ SciTokenKey scitoken_key_create(const char *key_id, const char *algorithm, const char *public_contents, const char *private_contents, char **err_msg); /** * @brief Destroy a key object and free associated memory * * @param private_key Key handle to destroy */ void scitoken_key_destroy(SciTokenKey private_key); /** * @brief Create a new SciToken * * @param private_key Key to use for signing, or NULL for unsigned token * @return SciToken handle on success, NULL on failure */ SciToken scitoken_create(SciTokenKey private_key); /** * @brief Destroy a token object and free associated memory * * @param token Token handle to destroy */ void scitoken_destroy(SciToken token); /** * @brief Set a string claim in the token * * @param token Token to modify * @param key Claim name (e.g., "iss", "aud", "scope") * @param value Claim value * @param err_msg Output parameter for error messages (caller must free) * @return 0 on success, non-zero on failure */ int scitoken_set_claim_string(SciToken token, const char *key, const char *value, char **err_msg); /** * @brief Get a string claim from the token * * @param token Token to query * @param key Claim name to retrieve * @param value Output parameter for claim value (caller must free) * @param err_msg Output parameter for error messages (caller must free) * @return 0 on success, non-zero on failure */ int scitoken_get_claim_string(const SciToken token, const char *key, char **value, char **err_msg); /** * Given a SciToken object, parse a specific claim's value as a list of strings. * If the JSON value is not actually a list of strings - or the claim is not set * - returns an error and sets the err_msg appropriately. * * The returned value is a list of strings that ends with a nullptr. */ int scitoken_get_claim_string_list(const SciToken token, const char *key, char ***value, char **err_msg); /** * Given a list of strings that was returned by scitoken_get_claim_string_list, * free all the associated memory. */ void scitoken_free_string_list(char **value); /** * Set the value of a claim to a list of strings. */ int scitoken_set_claim_string_list(const SciToken token, const char *key, const char **values, char **err_msg); /** * @brief Get the expiration time of the token * * @param token Token to query * @param value Output parameter for expiration time (Unix timestamp) * @param err_msg Output parameter for error messages (caller must free) * @return 0 on success, non-zero on failure */ int scitoken_get_expiration(const SciToken token, long long *value, char **err_msg); /** * @brief Set the lifetime of the token in seconds * * @param token Token to modify * @param lifetime Lifetime in seconds from creation */ void scitoken_set_lifetime(SciToken token, int lifetime); /** * @brief Serialize the token to a JWT string * * @param token Token to serialize * @param value Output parameter for JWT string (caller must free) * @param err_msg Output parameter for error messages (caller must free) * @return 0 on success, non-zero on failure */ int scitoken_serialize(const SciToken token, char **value, char **err_msg); /** * Set the profile used for serialization; if COMPAT mode is used, then * the library default is utilized (currently, scitokens 1.0). */ void scitoken_set_serialize_profile(SciToken token, SciTokenProfile profile); void scitoken_set_serialize_mode(SciToken token, SciTokenProfile profile); void scitoken_set_deserialize_profile(SciToken token, SciTokenProfile profile); /** * @brief Deserialize a JWT string into a SciToken * * @param value JWT string to parse * @param token Output parameter for created token (caller must destroy) * @param allowed_issuers NULL-terminated array of allowed issuer URLs, or NULL * for any * @param err_msg Output parameter for error messages (caller must free) * @return 0 on success, non-zero on failure */ int scitoken_deserialize(const char *value, SciToken *token, char const *const *allowed_issuers, char **err_msg); /** * @brief Start the deserialization process for a token, returning a status * object. * * @param value The serialized token. * @param token Destination for the token object. * @param allowed_issuers List of allowed issuers, or nullptr for no issuer * check. * @param status Destination for the status object. * @param err_msg Destination for error message. * @return int 0 on success, -1 on error. */ int scitoken_deserialize_start(const char *value, SciToken *token, char const *const *allowed_issuers, SciTokenStatus *status, char **err_msg); /** * @brief Continue the deserialization process for a token, updating the status * object. * * If the status object indicates that the token is complete, the token object * will be populated and the status object will be nullptr. * * @param token The token object, returned from scitoken_deserialize_start. * @param status Status object for the deserialize. * @param err_msg Destination for error message. * @return int 0 on success, -1 on error. */ int scitoken_deserialize_continue(SciToken *token, SciTokenStatus *status, char **err_msg); int scitoken_deserialize_v2(const char *value, SciToken token, char const *const *allowed_issuers, char **err_msg); /** * @brief Store a public EC key for token verification * * @param issuer Issuer URL that will use this key * @param keyid Key identifier * @param value PEM-encoded public key * @param err_msg Output parameter for error messages (caller must free) * @return 0 on success, non-zero on failure */ int scitoken_store_public_ec_key(const char *issuer, const char *keyid, const char *value, char **err_msg); /** * @brief Create a new token validator * * @return Validator handle on success, NULL on failure */ Validator validator_create(); /** * Set the profile used for validating the tokens; COMPAT (default) will accept * any known token type while others will only support that specific profile. */ void validator_set_token_profile(Validator, SciTokenProfile profile); /** * Set the time to use with the validator. Useful if you want to see if the * token would have been valid at some time in the past. */ int validator_set_time(Validator validator, time_t now, char **err_msg); int validator_add(Validator validator, const char *claim, StringValidatorFunction validator_func, char **err_msg); int validator_add_critical_claims(Validator validator, const char **claims, char **err_msg); /** * @brief Validate a SciToken using the configured validator * * @param validator Validator to use * @param scitoken Token to validate * @param err_msg Output parameter for error messages (caller must free) * @return 0 on success, non-zero on failure */ int validator_validate(Validator validator, SciToken scitoken, char **err_msg); /** * @brief Destroy a validator object and free associated memory * * @param validator Validator handle to destroy */ void validator_destroy(Validator validator); /** * @brief Create a new token enforcer * * @param issuer Required issuer URL for tokens * @param audience NULL-terminated array of acceptable audience values * @param err_msg Output parameter for error messages (caller must free) * @return Enforcer handle on success, NULL on failure */ Enforcer enforcer_create(const char *issuer, const char **audience, char **err_msg); /** * @brief Destroy an enforcer object and free associated memory * * @param enforcer Enforcer handle to destroy */ void enforcer_destroy(Enforcer enforcer); /** * Set the profile used for enforcing ACLs; when set to COMPAT (default), then * the authorizations will be converted to SciTokens 1.0-style authorizations * (so, WLCG's storage.read becomes read). */ void enforcer_set_validate_profile(Enforcer, SciTokenProfile profile); /** * Set the time to use with the enforcer. Useful if you want to see if the * token would have been valid at some time in the past. */ int enforcer_set_time(Enforcer enf, time_t now, char **err_msg); /** * @brief Generate Access Control Lists from a token * * @param enf Enforcer to use * @param scitokens Token to process * @param acls Output parameter for ACL array (caller must free with * enforcer_acl_free) * @param err_msg Output parameter for error messages (caller must free) * @return 0 on success, non-zero on failure */ int enforcer_generate_acls(const Enforcer enf, const SciToken scitokens, Acl **acls, char **err_msg); /** * The asynchronous versions of enforcer_generate_acls. */ int enforcer_generate_acls_start(const Enforcer enf, const SciToken scitokens, SciTokenStatus *status, Acl **acls, char **err_msg); int enforcer_generate_acls_continue(const Enforcer enf, SciTokenStatus *status, Acl **acls, char **err_msg); /** * @brief Free an array of ACLs returned by enforcer_generate_acls * * @param acls ACL array to free */ void enforcer_acl_free(Acl *acls); /** * @brief Test if a token grants access for a specific ACL * * @param enf Enforcer to use * @param sci Token to test * @param acl ACL to test against * @param err_msg Output parameter for error messages (caller must free) * @return 0 if access granted, non-zero if denied or error */ int enforcer_test(const Enforcer enf, const SciToken sci, const Acl *acl, char **err_msg); void scitoken_status_free(SciTokenStatus *status); /** * Get the suggested timeout val. After the timeout value has passed, the * asynchronous operation should continue. * * - `expiry_time`: the expiration time (in Unix epoch seconds) for the * operation in total. The returned timeout value will never take the operation * past the expiration time. */ int scitoken_status_get_timeout_val(const SciTokenStatus *status, time_t expiry_time, struct timeval *timeout, char **err_msg); /** * Get the set of read file descriptors. This will return a borrowed pointer * (whose lifetime matches the status object) pointing at a fd_set array of size * FD_SETSIZE. Any file descriptors owned by the status operation will be set * and the returned fd_set can be used for select() operations. * * IMPLEMENTATION NOTE: If the file descriptor monitored by libcurl are too high * to be stored in this set, libcurl should give a corresponding low timeout val * (100ms) and effectively switch to polling. See: * for more information. */ int scitoken_status_get_read_fd_set(SciTokenStatus *status, fd_set **read_fd_set, char **err_msg); /** * Get the set of write FDs; see documentation for * scitoken_status_get_read_fd_set. */ int scitoken_status_get_write_fd_set(SciTokenStatus *status, fd_set **write_fd_set, char **err_msg); /** * Get the set of exception FDs; see documentation for * scitoken_status_get_exc_fd_set. */ int scitoken_status_get_exc_fd_set(SciTokenStatus *status, fd_set **exc_fd_set, char **err_msg); /** * Get the maximum FD in the status set. * * IMPLEMENTATION NOTE: If the max FD is -1 then it implies libcurl is something * that cannot be modelled by a socket. In such a case, the libcurl docs * suggest using a 100ms timeout for select operations. See * . */ int scitoken_status_get_max_fd(const SciTokenStatus *status, int *max_fd, char **err_msg); /** * API for explicity managing the key cache. * * This manipulates the keycache for the current eUID. */ /** * Refresh the JWKS in the keycache for a given issuer; the refresh will occur * even if the JWKS is not otherwise due for updates. * - Returns 0 on success, nonzero on failure. */ int keycache_refresh_jwks(const char *issuer, char **err_msg); /** * Retrieve the JWKS from the keycache for a given issuer. * - Returns 0 if successful, nonzero on failure. * - If the existing JWKS has expired - or does not exist - this does not * trigger a new download of the JWKS from the issuer. Instead, it will return * a JWKS object with an empty set of keys. * - `jwks` is an output variable set to the contents of the JWKS in the key * cache. */ int keycache_get_cached_jwks(const char *issuer, char **jwks, char **err_msg); /** * Replace any existing key cache entry with one provided by the user. * The expiration and next update time of the user-provided JWKS will utilize * the same rules as a download from an issuer with no explicit cache lifetime * directives. * - `jwks` is value that will be set in the cache. */ int keycache_set_jwks(const char *issuer, const char *jwks, char **err_msg); /** * Enable or disable the background refresh thread for JWKS. * - When enabled, a background thread will periodically check if any known * issuers need their JWKS refreshed based on the configured refresh interval * and threshold. * - If enabled=1 and the thread is not running, it will be started. * - If enabled=0 and the thread is running, it will be stopped gracefully. * - Returns 0 on success, nonzero on failure. */ int keycache_set_background_refresh(int enabled, char **err_msg); /** * Stop the background refresh thread if it is running. * - This is a convenience function equivalent to * keycache_set_background_refresh(0, err_msg). * - Returns 0 on success, nonzero on failure. */ int keycache_stop_background_refresh(char **err_msg); /** * Load the JWKS from the keycache for a given issuer, refreshing only if * needed. * - Returns 0 if successful, nonzero on failure. * - If the existing JWKS has not expired, this will return the cached JWKS * without triggering a download. * - If the JWKS has expired or does not exist, this will attempt to refresh * it from the issuer. * - `jwks` is an output variable set to the contents of the JWKS. */ int keycache_load_jwks(const char *issuer, char **jwks, char **err_msg); /** * Get metadata for a cached JWKS entry. * - Returns 0 if successful, nonzero on failure. * - `metadata` is an output variable set to a JSON string containing: * - "expires": expiration time (Unix epoch seconds) * - "next_update": next update time (Unix epoch seconds) * - If the issuer does not exist in the cache, returns an error. */ int keycache_get_jwks_metadata(const char *issuer, char **metadata, char **err_msg); /** * Delete a JWKS entry from the keycache. * - Returns 0 if successful, nonzero on failure. * - If the issuer does not exist in the cache, this is not considered an error. */ int keycache_delete_jwks(const char *issuer, char **err_msg); /** * APIs for managing scitokens configuration parameters. */ // On its way to deprecation int config_set_int(const char *key, int value, char **err_msg); /** * Update scitokens int parameters. * Takes in key/value pairs and assigns the input value to whatever * configuration variable is indicated by the key. * Returns 0 on success, and non-zero for invalid keys or values. * * Supported keys: * - "keycache.update_interval_s": Interval between key cache updates (seconds) * - "keycache.expiration_interval_s": Key cache expiration time (seconds) * - "monitoring.file_interval_s": Interval between monitoring file writes * (seconds, default 60) * - "keycache.refresh_interval_ms": Background refresh thread check interval * (milliseconds, default 60000) * - "keycache.refresh_threshold_ms": Time before next_update when background * refresh triggers (milliseconds, default 600000) */ int scitoken_config_set_int(const char *key, int value, char **err_msg); // on its way to deprecation int config_get_int(const char *key, char **err_msg); /** * Get current scitokens int parameters. * Returns the value associated with the supplied input key on success, and -1 * on failure. This assumes there are no keys for which a negative return value * is permissible. * * Supported keys: * - "keycache.update_interval_s": Interval between key cache updates (seconds) * - "keycache.expiration_interval_s": Key cache expiration time (seconds) * - "monitoring.file_interval_s": Interval between monitoring file writes * (seconds, default 60) * - "keycache.refresh_interval_ms": Background refresh thread check interval * (milliseconds, default 60000) * - "keycache.refresh_threshold_ms": Time before next_update when background * refresh triggers (milliseconds, default 600000) */ int scitoken_config_get_int(const char *key, char **err_msg); /** * Set current scitokens str parameters. * Returns 0 on success, nonzero on failure * * Supported keys: * - "keycache.cache_home": Directory for the key cache * - "tls.ca_file": Path to TLS CA certificate file * - "monitoring.file": Path to write monitoring JSON (empty to disable, default * disabled) When enabled, monitoring stats are written periodically during * verify() calls. The write interval is controlled by * "monitoring.file_interval_s". */ int scitoken_config_set_str(const char *key, const char *value, char **err_msg); /** * Get current scitokens str parameters. * Returns 0 on success, nonzero on failure, and populates the value associated * with the input key to output. * * Supported keys: * - "keycache.cache_home": Directory for the key cache * - "tls.ca_file": Path to TLS CA certificate file * - "monitoring.file": Path to write monitoring JSON (empty if disabled) */ int scitoken_config_get_str(const char *key, char **output, char **err_msg); /** * Get monitoring statistics as a JSON string. * Returns a JSON object containing per-issuer validation statistics. * * Per-issuer statistics (under "issuers" key): * - successful_validations: count of successful token validations * - unsuccessful_validations: count of failed token validations * - expired_tokens: count of expired tokens encountered * - sync_validations_started: count of validations started via blocking API * - async_validations_started: count of validations started via async API * - sync_total_time_s: time spent in blocking verify() calls (updated every * 50ms) * - async_total_time_s: time spent in async validations (updated on completion) * - total_validation_time_s: sum of sync and async time * - successful_key_lookups: count of successful JWKS web refreshes * - failed_key_lookups: count of failed JWKS web refreshes * - failed_key_lookup_time_s: total time spent on failed key lookups * - expired_keys: count of times keys expired before refresh completed * - failed_refreshes: count of failed key refresh attempts (used cached keys) * - stale_key_uses: count of times keys were used past their next_update time * * Failed issuer lookups (under "failed_issuer_lookups" key): * - Per unknown issuer: count and total_time_s of failed lookup attempts * - Limited to 100 entries to prevent resource exhaustion from DDoS attacks * * The returned string must be freed by the caller using free(). * Returns 0 on success, nonzero on failure. */ int scitoken_get_monitoring_json(char **json_out, char **err_msg); /** * Reset all monitoring statistics. * Returns 0 on success, nonzero on failure. */ int scitoken_reset_monitoring_stats(char **err_msg); #ifdef __cplusplus } #endif scitokens-cpp-1.3.0/src/scitokens_cache.cpp000066400000000000000000000423641513647275500207450ustar00rootroot00000000000000 #include #include #include #include #include #include #include #ifndef PICOJSON_USE_INT64 #define PICOJSON_USE_INT64 #endif #include #include #include "scitokens_internal.h" namespace { // Timeout in milliseconds to wait when database is locked // This handles concurrent access from multiple threads/processes constexpr int SQLITE_BUSY_TIMEOUT_MS = 5000; // Default time before expiry when next_update should occur (4 hours) constexpr int64_t DEFAULT_NEXT_UPDATE_OFFSET_S = 4 * 3600; void initialize_cachedb(const std::string &keycache_file) { sqlite3 *db; int rc = sqlite3_open(keycache_file.c_str(), &db); if (rc != SQLITE_OK) { std::cerr << "SQLite key cache creation failed." << std::endl; sqlite3_close(db); return; } // Set busy timeout to handle concurrent access sqlite3_busy_timeout(db, SQLITE_BUSY_TIMEOUT_MS); char *err_msg = nullptr; rc = sqlite3_exec(db, "CREATE TABLE IF NOT EXISTS keycache (" "issuer text UNIQUE PRIMARY KEY NOT NULL," "keys text NOT NULL)", NULL, 0, &err_msg); if (rc) { std::cerr << "Sqlite table creation failed: " << err_msg << std::endl; sqlite3_free(err_msg); } sqlite3_close(db); } /** * Get the Cache file location * 1. User-defined through config api * 2. $XDG_CACHE_HOME * 3. .cache subdirectory of home directory as returned by the password * database */ std::string get_cache_file() { const char *xdg_cache_home = getenv("XDG_CACHE_HOME"); auto bufsize = sysconf(_SC_GETPW_R_SIZE_MAX); bufsize = (bufsize == -1) ? 16384 : bufsize; std::unique_ptr buf(new char[bufsize]); std::string home_dir; struct passwd pwd, *result = NULL; getpwuid_r(geteuid(), &pwd, buf.get(), bufsize, &result); if (result && result->pw_dir) { home_dir = result->pw_dir; home_dir += "/.cache"; } // Figure out where to plop the cache based on priority std::string cache_dir; std::string configured_cache_dir = configurer::Configuration::get_cache_home(); if (configured_cache_dir.length() > 0) { // The variable has been configured cache_dir = configured_cache_dir; } else { cache_dir = xdg_cache_home ? xdg_cache_home : home_dir.c_str(); } if (cache_dir.size() == 0) { return ""; } int r = mkdir(cache_dir.c_str(), 0700); if ((r < 0) && errno != EEXIST) { return ""; } std::string keycache_dir = cache_dir + "/scitokens"; r = mkdir(keycache_dir.c_str(), 0700); if ((r < 0) && errno != EEXIST) { return ""; } std::string keycache_file = keycache_dir + "/scitokens_cpp.sqllite"; initialize_cachedb(keycache_file); return keycache_file; } // Remove a given issuer from the database. Starts a new transaction // if `new_transaction` is true. // If a failure occurs, then this function returns nonzero and closes // the database handle. int remove_issuer_entry(sqlite3 *db, const std::string &issuer, bool new_transaction) { int rc; if (new_transaction) { if ((rc = sqlite3_exec(db, "BEGIN", 0, 0, 0)) != SQLITE_OK) { sqlite3_close(db); return -1; } } sqlite3_stmt *stmt; rc = sqlite3_prepare_v2(db, "DELETE FROM keycache WHERE issuer = ?", -1, &stmt, NULL); if (rc != SQLITE_OK) { sqlite3_close(db); return -1; } if (sqlite3_bind_text(stmt, 1, issuer.c_str(), issuer.size(), SQLITE_STATIC) != SQLITE_OK) { sqlite3_finalize(stmt); sqlite3_close(db); return -1; } rc = sqlite3_step(stmt); if (rc != SQLITE_DONE) { sqlite3_finalize(stmt); sqlite3_close(db); return -1; } sqlite3_finalize(stmt); if (new_transaction) { if ((rc = sqlite3_exec(db, "COMMIT", 0, 0, 0)) != SQLITE_OK) { sqlite3_close(db); return -1; } } return 0; } } // namespace bool scitokens::Validator::get_public_keys_from_db(const std::string issuer, int64_t now, picojson::value &keys, int64_t &next_update) { auto cache_fname = get_cache_file(); if (cache_fname.size() == 0) { return false; } sqlite3 *db; int rc = sqlite3_open(cache_fname.c_str(), &db); if (rc) { sqlite3_close(db); return false; } // Set busy timeout to handle concurrent access sqlite3_busy_timeout(db, SQLITE_BUSY_TIMEOUT_MS); sqlite3_stmt *stmt; rc = sqlite3_prepare_v2(db, "SELECT keys from keycache where issuer = ?", -1, &stmt, NULL); if (rc != SQLITE_OK) { sqlite3_close(db); return false; } if (sqlite3_bind_text(stmt, 1, issuer.c_str(), issuer.size(), SQLITE_STATIC) != SQLITE_OK) { sqlite3_finalize(stmt); sqlite3_close(db); return false; } rc = sqlite3_step(stmt); if (rc == SQLITE_ROW) { const unsigned char *data = sqlite3_column_text(stmt, 0); std::string metadata(reinterpret_cast(data)); sqlite3_finalize(stmt); picojson::value json_obj; auto err = picojson::parse(json_obj, metadata); if (!err.empty() || !json_obj.is()) { if (remove_issuer_entry(db, issuer, true) != 0) { return false; } sqlite3_close(db); return false; } auto top_obj = json_obj.get(); auto iter = top_obj.find("jwks"); if (iter == top_obj.end() || !iter->second.is()) { if (remove_issuer_entry(db, issuer, true) != 0) { return false; } sqlite3_close(db); return false; } auto keys_local = iter->second; // Check if this is a negative cache entry (empty keys array) if (keys_local.is()) { auto jwks_obj = keys_local.get(); auto keys_iter = jwks_obj.find("keys"); if (keys_iter != jwks_obj.end() && keys_iter->second.is()) { auto keys_array = keys_iter->second.get(); if (keys_array.empty()) { // Check if negative cache has expired iter = top_obj.find("expires"); if (iter != top_obj.end() && iter->second.is()) { auto expiry = iter->second.get(); if (now > expiry) { // Negative cache expired, remove and return false if (remove_issuer_entry(db, issuer, true) != 0) { return false; } sqlite3_close(db); return false; } } // Negative cache still valid - throw exception sqlite3_close(db); throw NegativeCacheHitException(issuer); } } } iter = top_obj.find("expires"); if (iter == top_obj.end() || !iter->second.is()) { if (remove_issuer_entry(db, issuer, true) != 0) { return false; } sqlite3_close(db); return false; } auto expiry = iter->second.get(); if (now > expiry) { if (remove_issuer_entry(db, issuer, true) != 0) { return false; } sqlite3_close(db); return false; } sqlite3_close(db); iter = top_obj.find("next_update"); if (iter == top_obj.end() || !iter->second.is()) { next_update = expiry - DEFAULT_NEXT_UPDATE_OFFSET_S; } else { next_update = iter->second.get(); } keys = keys_local; return true; } else if (rc == SQLITE_DONE) { sqlite3_finalize(stmt); sqlite3_close(db); return false; } else { // TODO: log error? sqlite3_finalize(stmt); sqlite3_close(db); return false; } } bool scitokens::Validator::store_public_keys(const std::string &issuer, const picojson::value &keys, int64_t next_update, int64_t expires) { picojson::object top_obj; top_obj["jwks"] = keys; top_obj["next_update"] = picojson::value(next_update); top_obj["expires"] = picojson::value(expires); picojson::value db_value(top_obj); std::string db_str = db_value.serialize(); auto cache_fname = get_cache_file(); if (cache_fname.size() == 0) { return false; } sqlite3 *db; int rc = sqlite3_open(cache_fname.c_str(), &db); if (rc) { sqlite3_close(db); return false; } // Set busy timeout to handle concurrent access sqlite3_busy_timeout(db, SQLITE_BUSY_TIMEOUT_MS); if ((rc = sqlite3_exec(db, "BEGIN", 0, 0, 0)) != SQLITE_OK) { sqlite3_close(db); return false; } if (remove_issuer_entry(db, issuer, false) != 0) { return false; } sqlite3_stmt *stmt; rc = sqlite3_prepare_v2(db, "INSERT INTO keycache VALUES (?, ?)", -1, &stmt, NULL); if (rc != SQLITE_OK) { sqlite3_close(db); return false; } if (sqlite3_bind_text(stmt, 1, issuer.c_str(), issuer.size(), SQLITE_STATIC) != SQLITE_OK) { sqlite3_finalize(stmt); sqlite3_close(db); return false; } if (sqlite3_bind_text(stmt, 2, db_str.c_str(), db_str.size(), SQLITE_STATIC) != SQLITE_OK) { sqlite3_finalize(stmt); sqlite3_close(db); return false; } rc = sqlite3_step(stmt); if (rc != SQLITE_DONE) { sqlite3_finalize(stmt); sqlite3_close(db); return false; } sqlite3_finalize(stmt); if (sqlite3_exec(db, "COMMIT", 0, 0, 0) != SQLITE_OK) { sqlite3_close(db); return false; } sqlite3_close(db); return true; } std::vector> scitokens::Validator::get_all_issuers_from_db(int64_t now) { std::vector> result; auto cache_fname = get_cache_file(); if (cache_fname.size() == 0) { return result; } sqlite3 *db; int rc = sqlite3_open(cache_fname.c_str(), &db); if (rc) { sqlite3_close(db); return result; } sqlite3_stmt *stmt; rc = sqlite3_prepare_v2(db, "SELECT issuer, keys FROM keycache", -1, &stmt, NULL); if (rc != SQLITE_OK) { sqlite3_close(db); return result; } while ((rc = sqlite3_step(stmt)) == SQLITE_ROW) { const unsigned char *issuer_data = sqlite3_column_text(stmt, 0); const unsigned char *keys_data = sqlite3_column_text(stmt, 1); if (!issuer_data || !keys_data) { continue; } std::string issuer(reinterpret_cast(issuer_data)); std::string metadata(reinterpret_cast(keys_data)); // Parse the metadata to get next_update and check expiry picojson::value json_obj; auto err = picojson::parse(json_obj, metadata); if (!err.empty() || !json_obj.is()) { continue; } auto top_obj = json_obj.get(); // Get expiry time auto expires_iter = top_obj.find("expires"); if (expires_iter == top_obj.end() || !expires_iter->second.is()) { continue; } auto expiry = expires_iter->second.get(); // Get next_update time auto next_update_iter = top_obj.find("next_update"); int64_t next_update; if (next_update_iter == top_obj.end() || !next_update_iter->second.is()) { // If next_update is not set, default to 4 hours before expiry next_update = expiry - DEFAULT_NEXT_UPDATE_OFFSET_S; } else { next_update = next_update_iter->second.get(); } // Include expired entries - they should be refreshed after a long // downtime If expired, set next_update to now so they get refreshed // immediately if (now > expiry) { next_update = now; } result.push_back({issuer, next_update}); } sqlite3_finalize(stmt); sqlite3_close(db); return result; } std::string scitokens::Validator::load_jwks(const std::string &issuer) { auto now = std::time(NULL); picojson::value jwks; int64_t next_update; try { // Try to get from cache if (get_public_keys_from_db(issuer, now, jwks, next_update)) { // Check if refresh is needed (expired based on next_update) if (now <= next_update) { // Still valid, return cached version return jwks.serialize(); } // Past next_update, need to refresh } } catch (const NegativeCacheHitException &) { // Negative cache hit - return empty keys return std::string("{\"keys\": []}"); } // Either not in cache or past next_update - refresh if (!refresh_jwks(issuer)) { throw CurlException("Failed to load JWKS for issuer: " + issuer); } // Get the newly refreshed JWKS return get_jwks(issuer); } std::string scitokens::Validator::get_jwks_metadata(const std::string &issuer) { auto now = std::time(NULL); int64_t next_update = -1; int64_t expires = -1; // Get the metadata from database without expiry check auto cache_fname = get_cache_file(); if (cache_fname.size() == 0) { throw std::runtime_error("Unable to access cache file"); } sqlite3 *db; int rc = sqlite3_open(cache_fname.c_str(), &db); if (rc) { sqlite3_close(db); throw std::runtime_error("Failed to open cache database"); } sqlite3_busy_timeout(db, SQLITE_BUSY_TIMEOUT_MS); sqlite3_stmt *stmt; rc = sqlite3_prepare_v2(db, "SELECT keys from keycache where issuer = ?", -1, &stmt, NULL); if (rc != SQLITE_OK) { sqlite3_close(db); throw std::runtime_error("Failed to prepare database query"); } if (sqlite3_bind_text(stmt, 1, issuer.c_str(), issuer.size(), SQLITE_STATIC) != SQLITE_OK) { sqlite3_finalize(stmt); sqlite3_close(db); throw std::runtime_error("Failed to bind issuer to query"); } rc = sqlite3_step(stmt); if (rc == SQLITE_ROW) { const unsigned char *data = sqlite3_column_text(stmt, 0); std::string metadata(reinterpret_cast(data)); sqlite3_finalize(stmt); sqlite3_close(db); picojson::value json_obj; auto err = picojson::parse(json_obj, metadata); if (!err.empty() || !json_obj.is()) { throw JsonException("Invalid JSON in cache entry"); } auto top_obj = json_obj.get(); // Extract expires auto iter = top_obj.find("expires"); if (iter != top_obj.end() && iter->second.is()) { expires = iter->second.get(); } // Extract next_update iter = top_obj.find("next_update"); if (iter != top_obj.end() && iter->second.is()) { next_update = iter->second.get(); } else if (expires != -1) { // Default next_update to 4 hours before expiry next_update = expires - DEFAULT_NEXT_UPDATE_OFFSET_S; } // Build metadata JSON (add future keys at top level if needed) picojson::object metadata_obj; if (expires != -1) { metadata_obj["expires"] = picojson::value(expires); } if (next_update != -1) { metadata_obj["next_update"] = picojson::value(next_update); } return picojson::value(metadata_obj).serialize(); } else { sqlite3_finalize(stmt); sqlite3_close(db); throw std::runtime_error("Issuer not found in cache"); } } bool scitokens::Validator::delete_jwks(const std::string &issuer) { auto cache_fname = get_cache_file(); if (cache_fname.size() == 0) { return false; } sqlite3 *db; int rc = sqlite3_open(cache_fname.c_str(), &db); if (rc) { sqlite3_close(db); return false; } sqlite3_busy_timeout(db, SQLITE_BUSY_TIMEOUT_MS); // Use the existing remove_issuer_entry function // Note: remove_issuer_entry closes the database on error if (remove_issuer_entry(db, issuer, true) != 0) { // Database already closed by remove_issuer_entry return false; } sqlite3_close(db); return true; } scitokens-cpp-1.3.0/src/scitokens_internal.cpp000066400000000000000000001552641513647275500215220ustar00rootroot00000000000000 #include #include #include #include #include #include #include #include #include #include #include #if OPENSSL_VERSION_NUMBER >= 0x30000000L #include #include #endif #define EC_NAME NID_X9_62_prime256v1 #include "scitokens_internal.h" using namespace scitokens; namespace { struct CurlRaii { CurlRaii() { curl_global_init(CURL_GLOBAL_DEFAULT); } ~CurlRaii() { curl_global_cleanup(); } }; CurlRaii myCurl; std::mutex key_refresh_mutex; // Per-issuer mutex map for preventing thundering herd on new issuers std::mutex issuer_mutex_map_lock; std::unordered_map> issuer_mutexes; constexpr size_t MAX_ISSUER_MUTEXES = 1000; // Get or create a mutex for a specific issuer std::shared_ptr get_issuer_mutex(const std::string &issuer) { std::lock_guard guard(issuer_mutex_map_lock); auto it = issuer_mutexes.find(issuer); if (it != issuer_mutexes.end()) { return it->second; } // Prevent resource exhaustion: limit the number of cached mutexes if (issuer_mutexes.size() >= MAX_ISSUER_MUTEXES) { // Remove mutexes that are no longer in use // Since we hold issuer_mutex_map_lock, no other thread can acquire // a reference to these mutexes, making this check safe for (auto iter = issuer_mutexes.begin(); iter != issuer_mutexes.end();) { if (iter->second.use_count() == 1) { // Only we hold a reference, safe to remove iter = issuer_mutexes.erase(iter); } else { ++iter; } } // If still at capacity after cleanup, fail rather than unbounded growth if (issuer_mutexes.size() >= MAX_ISSUER_MUTEXES) { throw std::runtime_error( "Too many concurrent issuers - resource exhaustion prevented"); } } auto mutex_ptr = std::make_shared(); issuer_mutexes[issuer] = mutex_ptr; return mutex_ptr; } } // namespace namespace scitokens { // Define the static once_flag for Validator std::once_flag Validator::m_background_refresh_once; namespace internal { // BackgroundRefreshManager implementation void BackgroundRefreshManager::start() { std::lock_guard lock(m_mutex); if (m_running.load(std::memory_order_acquire)) { return; // Already running } m_shutdown.store(false, std::memory_order_release); m_running.store(true, std::memory_order_release); m_thread = std::make_unique( &BackgroundRefreshManager::refresh_loop, this); } void BackgroundRefreshManager::stop() { std::unique_ptr thread_to_join; { std::lock_guard lock(m_mutex); if (!m_running.load(std::memory_order_acquire)) { return; // Not running } m_shutdown.store(true, std::memory_order_release); m_running.store(false, std::memory_order_release); thread_to_join = std::move(m_thread); } m_cv.notify_all(); if (thread_to_join && thread_to_join->joinable()) { thread_to_join->join(); } } void BackgroundRefreshManager::refresh_loop() { while (!m_shutdown.load(std::memory_order_acquire)) { auto interval = configurer::Configuration::get_refresh_interval(); auto threshold = configurer::Configuration::get_refresh_threshold(); // Wait for the interval or until shutdown { std::unique_lock lock(m_mutex); m_cv.wait_for(lock, std::chrono::milliseconds(interval), [this]() { return m_shutdown.load(std::memory_order_acquire); }); } if (m_shutdown.load(std::memory_order_acquire)) { break; } // Get list of issuers from the database auto now = std::time(NULL); auto issuers = scitokens::Validator::get_all_issuers_from_db(now); for (const auto &issuer_pair : issuers) { if (m_shutdown.load(std::memory_order_acquire)) { break; } const auto &issuer = issuer_pair.first; const auto &next_update = issuer_pair.second; // Calculate time until next_update in milliseconds int64_t time_until_update = (next_update - now) * 1000; // If next update is within threshold, try to refresh if (time_until_update <= threshold) { auto &stats = MonitoringStats::instance().get_issuer_stats(issuer); try { // Perform refresh (this will use the refresh_jwks method) scitokens::Validator::refresh_jwks(issuer); stats.inc_background_successful_refresh(); } catch (std::exception &) { // Track failed refresh attempts stats.inc_background_failed_refresh(); // Silently ignore errors in background refresh to avoid // disrupting the application. Background refresh is a // best-effort optimization. If it fails, the next token // verification will trigger a foreground refresh as usual. } } } // Write monitoring file from background thread if configured // This avoids writing from verify() when background thread is running MonitoringStats::instance().maybe_write_monitoring_file(); } } SimpleCurlGet::GetStatus SimpleCurlGet::perform_start(const std::string &url) { m_len = 0; m_curl_multi.reset(curl_multi_init()); if (!m_curl_multi) { throw CurlException("Failed to create a new curl async handle."); } m_curl.reset(curl_easy_init()); if (!m_curl) { throw CurlException("Failed to create a new curl handle."); } if (m_maxbytes > 0) { size_t new_size = std::min(m_maxbytes, 8 * 1024); if (m_data.size() < new_size) { m_data.resize(new_size); } } long timeout = m_timeout > 120 ? 120 : m_timeout; CURLcode rv = curl_easy_setopt(m_curl.get(), CURLOPT_URL, url.c_str()); if (rv != CURLE_OK) { throw CurlException("Failed to set CURLOPT_URL."); } rv = curl_easy_setopt(m_curl.get(), CURLOPT_WRITEFUNCTION, &write_data); if (rv != CURLE_OK) { throw CurlException("Failed to set CURLOPT_WRITEFUNCTION."); } rv = curl_easy_setopt(m_curl.get(), CURLOPT_WRITEDATA, this); if (rv != CURLE_OK) { throw CurlException("Failed to set CURLOPT_WRITEDATA."); } rv = curl_easy_setopt(m_curl.get(), CURLOPT_TIMEOUT, timeout); if (rv != CURLE_OK) { throw CurlException("Failed to set CURLOPT_TIMEOUT."); } rv = curl_easy_setopt(m_curl.get(), CURLOPT_FOLLOWLOCATION, 1L); if (rv != CURLE_OK) { throw CurlException("Failed to set CURLOPT_FOLLOWLOCATION."); } // Disable signal handling to avoid issues in multi-threaded contexts. rv = curl_easy_setopt(m_curl.get(), CURLOPT_NOSIGNAL, 1L); if (rv != CURLE_OK) { throw CurlException("Failed to set CURLOPT_NOSIGNAL."); } auto ca_file = configurer::Configuration::get_tls_ca_file(); if (!ca_file.empty()) { rv = curl_easy_setopt(m_curl.get(), CURLOPT_CAINFO, ca_file.c_str()); if (rv != CURLE_OK) { throw CurlException("Failed to set CURLOPT_CAINFO."); } } { auto mres = curl_multi_add_handle(m_curl_multi.get(), m_curl.get()); if (mres) { throw CurlException("Failed to add curl handle to async object"); } } return perform_continue(); } std::string SimpleCurlGet::get_url() const { if (!m_curl) { return ""; } char *url = nullptr; auto rv = curl_easy_getinfo(m_curl.get(), CURLINFO_EFFECTIVE_URL, &url); if (rv != CURLE_OK) { return ""; } return std::string(url); } SimpleCurlGet::GetStatus SimpleCurlGet::perform_continue() { int still_running; auto resm = curl_multi_perform(m_curl_multi.get(), &still_running); if (!resm && still_running) { resm = curl_multi_timeout(m_curl_multi.get(), &m_timeout_ms); if (resm) { throw CurlException(curl_multi_strerror(resm)); } if (m_timeout_ms < 0) { m_timeout_ms = 100; } FD_ZERO(m_read_fd_set); FD_ZERO(m_write_fd_set); FD_ZERO(m_exc_fd_set); resm = curl_multi_fdset(m_curl_multi.get(), m_read_fd_set, m_write_fd_set, m_exc_fd_set, &m_max_fd); if (resm) { throw CurlException(curl_multi_strerror(resm)); } if (m_max_fd < 0) m_timeout_ms = 100; return GetStatus(); } if (resm) { throw CurlException(curl_multi_strerror(resm)); } CURLMsg *msg; CURLcode res = static_cast(-1); do { int msgq = 0; msg = curl_multi_info_read(m_curl_multi.get(), &msgq); if (msg && (msg->msg == CURLMSG_DONE)) { CURL *easy_handle = msg->easy_handle; res = msg->data.result; curl_multi_remove_handle(m_curl_multi.get(), easy_handle); } } while (msg); if (res) { throw CurlException(curl_easy_strerror(res)); } long status_code; res = curl_easy_getinfo(m_curl.get(), CURLINFO_RESPONSE_CODE, &status_code); if (res != CURLE_OK) { throw CurlException(curl_easy_strerror(res)); } GetStatus status; status.m_done = true; status.m_status_code = status_code; return status; } int SimpleCurlGet::perform(const std::string &url, time_t expiry_time) { GetStatus status = perform_start(url); while (!status.m_done) { auto now = time(NULL); int timeout_ms = 1000 * (expiry_time - now); if (timeout_ms < 0) timeout_ms = 0; if (m_timeout_ms < timeout_ms) timeout_ms = m_timeout_ms; struct timeval timeout; timeout.tv_sec = timeout_ms / 1000; timeout.tv_usec = (timeout_ms % 1000) * 1000; // Return value of select is ignored; curl will take care of it. select(m_max_fd + 1, m_read_fd_set, m_write_fd_set, m_exc_fd_set, &timeout); status = perform_continue(); } return status.m_status_code; } void SimpleCurlGet::get_data(char *&buffer, size_t &len) { buffer = &m_data[0]; len = m_len; } size_t SimpleCurlGet::write_data(void *buffer, size_t size, size_t nmemb, void *userp) { SimpleCurlGet *myself = reinterpret_cast(userp); size_t new_data = size * nmemb; size_t new_length = myself->m_len + new_data; if (myself->m_maxbytes > 0 && (new_length > static_cast(myself->m_maxbytes))) { return 0; } if (myself->m_data.size() < new_length) { myself->m_data.resize(new_length); } memcpy(&(myself->m_data[myself->m_len]), buffer, new_data); myself->m_len = new_length; return new_data; } } // namespace internal } // namespace scitokens namespace { void parse_url(const std::string &url, std::string &schema, std::string &netloc, std::string &path) { const std::string prot_end("://"); std::string::const_iterator prot_iter = std::search(url.begin(), url.end(), prot_end.begin(), prot_end.end()); schema.reserve(distance(url.begin(), prot_iter)); std::transform(url.begin(), prot_iter, std::back_inserter(schema), std::function(tolower)); if (prot_iter == url.end()) { throw InvalidIssuerException("Issuer URL missing hostname."); } std::advance(prot_iter, prot_end.length()); std::string::const_iterator path_iter = std::find(prot_iter, url.end(), '/'); netloc.reserve(std::distance(prot_iter, path_iter)); std::transform(prot_iter, path_iter, std::back_inserter(netloc), std::function(tolower)); std::string::const_iterator query_iter = std::find(path_iter, url.end(), '?'); path.assign(path_iter, query_iter); } void get_metadata_endpoint(const std::string &issuer, std::string &openid_metadata, std::string &oauth_metadata) { std::string schema, netloc, path; parse_url(issuer, schema, netloc, path); if (schema != "https") { throw InvalidIssuerException("Issuer URL must be HTTPS"); } if (path == "/") { path = ""; } std::string new_path = "/.well-known/oauth-authorization-server" + path; oauth_metadata = "https://" + netloc + new_path; openid_metadata = issuer + "/.well-known/openid-configuration"; } /* "keys": [ { "alg": "RS256", "e": "AQAB", "kid": "key-rs256", "kty": "RSA", "n": "uGDGTLXnqh3mfopjys6sFUBvFl3F4Qt6NEYphq_u_aBhtN1X9NEyb78uB_I1KjciJNGLIQU0ECsJiFx6qV1hR9xE1dPyrS3bU92AVtnBrvzUtTU-aUZAmZQiuAC_rC0-z_TOQr6qJkkUgZtxR9n9op55ZBpRfZD5dzhkW4Dm146vfTKt0D4cIMoMNJS5xQx9nibeB4E8hryZDW_fPeD0XZDcpByNyP0jFDYkxdUtQFvyRpz4WMZ4ejUfvW3gf4LRAfGZJtMnsZ7ZW4RfoQbhiXKMfWeBEjQDiXh0r-KuZLykxhYJtpf7fTnPna753IzMgRMmW3F69iQn2LQN3LoSMw==", "use": "sig" }, { "alg": "ES256", "kid": "key-es356", "kty": "EC", "use": "sig", "x": "ncSCrGTBTXXOhNiAOTwNdPjwRz1hVY4saDNiHQK9Bh4=", "y": "sCsFXvx7FAAklwq3CzRCBcghqZOFPB2dKUayS6LY_Lo=" } ] } */ picojson::value::object find_key_id(const picojson::value json, const std::string &kid) { if (!json.is()) { throw JsonException("Top-level JSON is not an object."); } auto top_obj = json.get(); auto iter = top_obj.find("keys"); if (iter == top_obj.end() || (!iter->second.is())) { throw JsonException("Metadata resource is missing 'keys' array value"); } auto keys_array = iter->second.get(); if (kid.empty()) { if (keys_array.size() != 1) { throw JsonException("Key ID empty but multiple keys published."); } auto &key = keys_array.at(0); return key.get(); } else { for (auto &key : keys_array) { if (!key.is()) { continue; } auto key_obj = key.get(); iter = key_obj.find("kid"); if (iter == key_obj.end() || (!iter->second.is())) { continue; } std::string cur_kid = iter->second.get(); if (cur_kid == kid) { return key_obj; } } throw JsonException("Key ID is not published by the issuer."); } } struct local_base64url : public jwt::alphabet::base64url { static const std::string &fill() { static std::string fill = "="; return fill; } }; // Assuming a padding, decode std::string b64url_decode_nopadding(const std::string &input) { std::string result = input; switch (result.size() % 4) { case 1: result += "="; // fallthrough case 2: result += "="; // fallthrough case 3: result += "="; // fallthrough default: break; } return jwt::base::decode(result); } // Base64-encode without padding. std::string b64url_encode_nopadding(const std::string &input) { std::string result = jwt::base::encode(input); auto pos = result.find("="); return result.substr(0, pos); } std::string es256_from_coords(const std::string &x_str, const std::string &y_str) { auto x_decode = b64url_decode_nopadding(x_str); auto y_decode = b64url_decode_nopadding(y_str); std::unique_ptr pubkey_bio( BIO_new(BIO_s_mem()), BIO_free_all); std::unique_ptr x_bignum( BN_bin2bn(reinterpret_cast(x_decode.c_str()), x_decode.size(), nullptr), BN_free); std::unique_ptr y_bignum( BN_bin2bn(reinterpret_cast(y_decode.c_str()), y_decode.size(), nullptr), BN_free); #if OPENSSL_VERSION_NUMBER >= 0x30000000L unsigned char *buf; OSSL_PARAM *params; std::unique_ptr ec_group( EC_GROUP_new_by_curve_name(EC_NAME), EC_GROUP_free); if (!ec_group.get()) { throw UnsupportedKeyException("Unable to get OpenSSL EC group"); } std::unique_ptr Q_point( EC_POINT_new(ec_group.get()), EC_POINT_free); if (!Q_point.get()) { throw UnsupportedKeyException("Unable to allocate new EC point"); } if (!EC_POINT_set_affine_coordinates(ec_group.get(), Q_point.get(), x_bignum.get(), y_bignum.get(), NULL)) { throw UnsupportedKeyException("Invalid elliptic curve point in key"); } size_t out_len = EC_POINT_point2buf(ec_group.get(), Q_point.get(), POINT_CONVERSION_UNCOMPRESSED, &buf, NULL); if (out_len == 0) { throw UnsupportedKeyException( "Failed to convert EC point to octet base buffer"); } std::unique_ptr param_build( OSSL_PARAM_BLD_new(), OSSL_PARAM_BLD_free); if (!param_build.get() || !OSSL_PARAM_BLD_push_utf8_string(param_build.get(), "group", "prime256v1", 0) || !OSSL_PARAM_BLD_push_octet_string(param_build.get(), "pub", buf, out_len) || (params = OSSL_PARAM_BLD_to_param(param_build.get())) == NULL) { throw UnsupportedKeyException( "Failed to build EC public key parameters"); } EVP_PKEY *pkey = NULL; std::unique_ptr ec_ctx( EVP_PKEY_CTX_new_from_name(NULL, "EC", NULL), EVP_PKEY_CTX_free); if (!ec_ctx.get()) { throw UnsupportedKeyException("Failed to set EC PKEY context"); } if (EVP_PKEY_fromdata_init(ec_ctx.get()) <= 0 || EVP_PKEY_fromdata(ec_ctx.get(), &pkey, EVP_PKEY_PUBLIC_KEY, params) <= 0 || pkey == NULL) { throw UnsupportedKeyException("Failed to set the EC public key"); } if (PEM_write_bio_PUBKEY(pubkey_bio.get(), pkey) == 0) { throw UnsupportedKeyException("Failed to serialize EC public key"); } EVP_PKEY_free(pkey); OSSL_PARAM_free(params); OPENSSL_free(buf); #else std::unique_ptr ec( EC_KEY_new_by_curve_name(EC_NAME), EC_KEY_free); if (!ec.get()) { throw UnsupportedKeyException( "OpenSSL does not support the P-256 curve"); } EC_GROUP *params = (EC_GROUP *)EC_KEY_get0_group(ec.get()); if (!params) { throw UnsupportedKeyException("Unable to get OpenSSL EC group"); } std::unique_ptr Q_point( EC_POINT_new(params), EC_POINT_free); if (!Q_point.get()) { throw UnsupportedKeyException("Unable to allocate new EC point"); } if (EC_POINT_set_affine_coordinates_GFp( params, Q_point.get(), x_bignum.get(), y_bignum.get(), NULL) != 1) { throw UnsupportedKeyException("Invalid elliptic curve point in key"); } if (EC_KEY_set_public_key(ec.get(), Q_point.get()) != 1) { throw UnsupportedKeyException("Unable to set the EC public key"); } if (PEM_write_bio_EC_PUBKEY(pubkey_bio.get(), ec.get()) == 0) { throw UnsupportedKeyException("Failed to serialize EC public key"); } #endif char *mem_data; size_t mem_len = BIO_get_mem_data(pubkey_bio.get(), &mem_data); std::string result = std::string(mem_data, mem_len); return result; } std::string rs256_from_coords(const std::string &e_str, const std::string &n_str) { auto e_decode = b64url_decode_nopadding(e_str); auto n_decode = b64url_decode_nopadding(n_str); std::unique_ptr pubkey_bio( BIO_new(BIO_s_mem()), BIO_free_all); std::unique_ptr e_bignum( BN_bin2bn(reinterpret_cast(e_decode.c_str()), e_decode.size(), nullptr), BN_free); std::unique_ptr n_bignum( BN_bin2bn(reinterpret_cast(n_decode.c_str()), n_decode.size(), nullptr), BN_free); #if OPENSSL_VERSION_NUMBER >= 0x30000000L // ======================================== // OpenSSL 3.x: Use EVP_PKEY API // ======================================== OSSL_PARAM *params; std::unique_ptr rsa_ctx( EVP_PKEY_CTX_new_from_name(NULL, "RSA", NULL), EVP_PKEY_CTX_free); if (!rsa_ctx.get()) { throw UnsupportedKeyException("Failed to set RSA PKEY context"); } std::unique_ptr param_build( OSSL_PARAM_BLD_new(), OSSL_PARAM_BLD_free); if (!param_build.get() || !OSSL_PARAM_BLD_push_BN_pad(param_build.get(), "e", e_bignum.get(), BN_num_bytes(e_bignum.get())) || !OSSL_PARAM_BLD_push_BN_pad(param_build.get(), "n", n_bignum.get(), BN_num_bytes(n_bignum.get())) || (params = OSSL_PARAM_BLD_to_param(param_build.get())) == NULL) { throw UnsupportedKeyException( "Failed to build RSA public key parameters"); } EVP_PKEY *pkey = NULL; if (EVP_PKEY_fromdata_init(rsa_ctx.get()) <= 0 || EVP_PKEY_fromdata(rsa_ctx.get(), &pkey, EVP_PKEY_PUBLIC_KEY, params) <= 0 || pkey == NULL) { throw UnsupportedKeyException("Failed to set the RSA public key"); } if (PEM_write_bio_PUBKEY(pubkey_bio.get(), pkey) == 0) { throw UnsupportedKeyException("Failed to serialize RSA public key"); } EVP_PKEY_free(pkey); OSSL_PARAM_free(params); // Note: OSSL_PARAM_BLD_push_BN_pad() copied the BIGNUM data, so unique_ptr // still owns the original BIGNUMs and will free them automatically #else // ======================================== // OpenSSL 1.x: Use RSA structure API // ======================================== std::unique_ptr rsa(RSA_new(), RSA_free); #if OPENSSL_VERSION_NUMBER < 0x10100000L || defined(LIBRESSL_VERSION_NUMBER) // OpenSSL 1.0.x / LibreSSL: Direct member assignment transfers ownership rsa->e = e_bignum.get(); rsa->n = n_bignum.get(); rsa->d = nullptr; #else // OpenSSL 1.1.x: RSA_set0_key() transfers ownership RSA_set0_key(rsa.get(), n_bignum.get(), e_bignum.get(), nullptr); #endif std::unique_ptr pkey(EVP_PKEY_new(), EVP_PKEY_free); if (EVP_PKEY_set1_RSA(pkey.get(), rsa.get()) != 1) { throw UnsupportedKeyException("Failed to set the public key"); } if (PEM_write_bio_PUBKEY(pubkey_bio.get(), pkey.get()) == 0) { throw UnsupportedKeyException("Failed to serialize RSA public key"); } // Release BIGNUMs from unique_ptr - ownership was transferred to RSA // structure e_bignum.release(); n_bignum.release(); #endif char *mem_data; size_t mem_len = BIO_get_mem_data(pubkey_bio.get(), &mem_data); std::string result = std::string(mem_data, mem_len); return result; } /** * Normalize path: collapse etc. * >>> normalize_path('/a/b///c') * '/a/b/c' */ std::string normalize_absolute_path(const std::string &path) { if ((path == "//") || (path == "/") || (path == "")) { return "/"; } std::vector path_components; auto path_iter = path.begin(); while (path_iter != path.end()) { while (*path_iter == '/') { path_iter++; } auto next_path_iter = std::find(path_iter, path.end(), '/'); std::string component; component.reserve(std::distance(path_iter, next_path_iter)); component.assign(path_iter, next_path_iter); path_components.push_back(component); path_iter = next_path_iter; } std::vector path_components_filtered; path_components_filtered.reserve(path_components.size()); for (const auto &component : path_components) { if (component == "..") { path_components_filtered.pop_back(); } else if (!component.empty() && component != ".") { path_components_filtered.push_back(component); } } std::stringstream ss; for (const auto &component : path_components_filtered) { ss << "/" << component; } std::string result = ss.str(); return result.empty() ? "/" : result; } } // namespace // static std::unordered_map json_to_claim_map(const picojson::object &json) { std::unordered_map m; for (const auto &[name, value] : json) { m.emplace(name, jwt::claim(value)); } return m; // nvro } void SciToken::deserialize(const std::string &data, const std::vector allowed_issuers) { m_decoded.reset(new jwt::decoded_jwt(data)); scitokens::Validator val; val.add_allowed_issuers(allowed_issuers); val.set_validate_all_claims_scitokens_1(false); val.set_validate_profile(m_deserialize_profile); val.verify(*m_decoded); // Set all the claims m_claims = json_to_claim_map(m_decoded->get_payload_json()); // Copy over the profile m_profile = val.get_profile(); } std::unique_ptr SciToken::deserialize_start(const std::string &data, const std::vector allowed_issuers) { m_decoded.reset(new jwt::decoded_jwt(data)); std::unique_ptr status(new SciTokenAsyncStatus()); status->m_validator.reset(new scitokens::Validator()); status->m_validator->add_allowed_issuers(allowed_issuers); status->m_validator->set_validate_all_claims_scitokens_1(false); status->m_validator->set_validate_profile(m_deserialize_profile); status->m_status = status->m_validator->verify_async(*m_decoded); return deserialize_continue(std::move(status)); } std::unique_ptr SciToken::deserialize_continue(std::unique_ptr status) { // Check if the status is completed (verification is complete) if (status->m_status->m_done) { // Set all the claims m_claims = json_to_claim_map(m_decoded->get_payload_json()); // Copy over the profile m_profile = status->m_validator->get_profile(); } else { status->m_status = status->m_validator->verify_async_continue( std::move(status->m_status)); if (status->m_status->m_done) { // Set all the claims m_claims = json_to_claim_map(m_decoded->get_payload_json()); // Copy over the profile m_profile = status->m_validator->get_profile(); } } return std::move(status); } std::unique_ptr Validator::get_public_keys_from_web(const std::string &issuer, unsigned timeout) { try { std::string openid_metadata, oauth_metadata; get_metadata_endpoint(issuer, openid_metadata, oauth_metadata); std::unique_ptr status(new AsyncStatus()); status->m_oauth_metadata_url = oauth_metadata; status->m_cget.reset(new internal::SimpleCurlGet(1024 * 1024, timeout)); auto cget_status = status->m_cget->perform_start(openid_metadata); status->m_continue_fetch = true; if (!cget_status.m_done) { return status; } return get_public_keys_from_web_continue(std::move(status)); } catch (const CurlException &e) { // Rethrow CURL errors during issuer key fetch as IssuerLookupException throw IssuerLookupException(e.what()); } } std::unique_ptr Validator::get_public_keys_from_web_continue( std::unique_ptr status) { try { char *buffer; size_t len; switch (status->m_state) { case AsyncStatus::DOWNLOAD_METADATA: { auto cget_status = status->m_cget->perform_continue(); if (!cget_status.m_done) { return std::move(status); } if (cget_status.m_status_code != 200) { if (status->m_oauth_fallback) { throw IssuerLookupException( "Failed to retrieve metadata provider " "information for issuer."); } else { status->m_oauth_fallback = true; status->m_cget.reset(new internal::SimpleCurlGet()); cget_status = status->m_cget->perform_start( status->m_oauth_metadata_url); if (!cget_status.m_done) { return std::move(status); } return get_public_keys_from_web_continue(std::move(status)); } } status->m_cget->get_data(buffer, len); std::string metadata(buffer, len); picojson::value json_obj; auto err = picojson::parse(json_obj, metadata); if (!err.empty()) { throw JsonException("JSON parse failure when downloading from " "the metadata URL " + status->m_cget->get_url() + ": " + err); } if (!json_obj.is()) { throw JsonException("Metadata resource " + status->m_cget->get_url() + " contains " "improperly-formatted JSON."); } auto top_obj = json_obj.get(); auto iter = top_obj.find("jwks_uri"); if (iter == top_obj.end() || (!iter->second.is())) { throw JsonException("Metadata resource " + status->m_cget->get_url() + " is missing 'jwks_uri' string value"); } auto jwks_uri = iter->second.get(); status->m_has_metadata = true; status->m_state = AsyncStatus::DOWNLOAD_PUBLIC_KEY; status->m_cget.reset(new internal::SimpleCurlGet()); status->m_cget->perform_start(jwks_uri); // This should also fall through the next state } case AsyncStatus::DOWNLOAD_PUBLIC_KEY: { auto cget_status = status->m_cget->perform_continue(); if (!cget_status.m_done) { return std::move(status); } if (cget_status.m_status_code != 200) { throw IssuerLookupException( "Failed to retrieve the issuer's key set"); } status->m_cget->get_data(buffer, len); auto metadata = std::string(buffer, len); picojson::value json_obj; auto err = picojson::parse(json_obj, metadata); if (!err.empty()) { throw JsonException( "JSON parse failure when downloading from the " " public key URL " + status->m_cget->get_url() + ": " + err); } status->m_cget.reset(); auto now = std::time(NULL); // TODO: take expiration time from the cache-control header in the // response. int next_update_delta = configurer::Configuration::get_next_update_delta(); int expiry_delta = configurer::Configuration::get_expiry_delta(); status->m_next_update = now + next_update_delta; status->m_expires = now + expiry_delta; status->m_keys = json_obj; status->m_continue_fetch = false; status->m_done = true; status->m_state = AsyncStatus::DONE; } case AsyncStatus::DONE: status->m_done = true; } // Switch return std::move(status); } catch (const CurlException &e) { // Rethrow CURL errors during issuer key fetch as IssuerLookupException // (unless it's already an IssuerLookupException) if (dynamic_cast(&e)) { throw; } throw IssuerLookupException(e.what()); } } std::string Validator::get_jwks(const std::string &issuer) { auto now = std::time(NULL); picojson::value jwks; int64_t next_update; try { if (get_public_keys_from_db(issuer, now, jwks, next_update)) { return jwks.serialize(); } } catch (const NegativeCacheHitException &) { // Negative cache hit - return empty keys without incrementing counter // (counter is incremented elsewhere for validation failures) return std::string("{\"keys\": []}"); } return std::string("{\"keys\": []}"); } bool Validator::refresh_jwks(const std::string &issuer) { picojson::value keys; std::unique_ptr status = get_public_keys_from_web( issuer, internal::SimpleCurlGet::extended_timeout); while (!status->m_done) { status = get_public_keys_from_web_continue(std::move(status)); } return store_public_keys(issuer, status->m_keys, status->m_next_update, status->m_expires); } bool Validator::store_jwks(const std::string &issuer, const std::string &jwks_str) { picojson::value jwks; std::string err = picojson::parse(jwks, jwks_str); auto now = std::time(NULL); int next_update_delta = configurer::Configuration::get_next_update_delta(); int expiry_delta = configurer::Configuration::get_expiry_delta(); int64_t next_update = now + next_update_delta, expires = now + expiry_delta; if (!err.empty()) { throw JsonException(err); } return store_public_keys(issuer, jwks, next_update, expires); } std::unique_ptr Validator::get_public_key_pem(const std::string &issuer, const std::string &kid, std::string &public_pem, std::string &algorithm) { auto now = std::time(NULL); std::unique_ptr result(new AsyncStatus()); if (get_public_keys_from_db(issuer, now, result->m_keys, result->m_next_update)) { std::unique_lock lock(key_refresh_mutex, std::defer_lock); // If refresh is due *and* the key refresh mutex is free, try to update if (now > result->m_next_update && lock.try_lock()) { // Get a reference to this issuer's statistics auto &issuer_stats = internal::MonitoringStats::instance().get_issuer_stats(issuer); // Record that we're using a stale key (past next_update) issuer_stats.inc_stale_key_use(); try { result->m_ignore_error = true; result = get_public_keys_from_web( issuer, internal::SimpleCurlGet::default_timeout); // Hold refresh mutex in the new result result->m_refresh_lock = std::move(lock); // Mark that this is a refresh attempt for a known issuer result->m_is_refresh = true; } catch (std::runtime_error &) { result->m_do_store = false; // Record failed refresh for known issuer issuer_stats.inc_failed_refresh(); // ignore the exception: we have a valid set of keys already } } else { // Got the keys from the DB, and they are still valid. result->m_continue_fetch = false; result->m_do_store = false; result->m_done = true; } } else { // No keys in the DB, or they are expired, so get them from the web. // Record that we had expired keys if the issuer was previously known auto &issuer_stats = internal::MonitoringStats::instance().get_issuer_stats(issuer); issuer_stats.inc_expired_key(); // Use per-issuer lock to prevent thundering herd for new issuers auto issuer_mutex = get_issuer_mutex(issuer); std::unique_lock issuer_lock(*issuer_mutex); // Check again if keys are now in DB (another thread may have fetched // them while we were waiting for the lock) if (get_public_keys_from_db(issuer, now, result->m_keys, result->m_next_update)) { // Keys are now available, use them result->m_continue_fetch = false; result->m_do_store = false; result->m_done = true; // Lock released here - no need to hold it } else { // Still no keys, fetch them from the web result = get_public_keys_from_web( issuer, internal::SimpleCurlGet::default_timeout); // Transfer ownership of the lock to the async status // The lock will be held until keys are stored in // get_public_key_pem_continue result->m_issuer_mutex = issuer_mutex; result->m_issuer_lock = std::move(issuer_lock); } } result->m_issuer = issuer; result->m_kid = kid; // Always call the continue because it formats the public_pem and algorithm return get_public_key_pem_continue(std::move(result), public_pem, algorithm); } std::unique_ptr Validator::get_public_key_pem_continue(std::unique_ptr status, std::string &public_pem, std::string &algorithm) { if (status->m_continue_fetch) { // Save issuer and lock info before potentially moving status std::string issuer = status->m_issuer; auto issuer_mutex = status->m_issuer_mutex; std::unique_lock issuer_lock( std::move(status->m_issuer_lock)); try { status = get_public_keys_from_web_continue(std::move(status)); if (status->m_continue_fetch) { // Restore the lock to status before returning status->m_issuer_mutex = issuer_mutex; status->m_issuer_lock = std::move(issuer_lock); return std::move(status); } // Success - restore the lock to status for later release status->m_issuer_mutex = issuer_mutex; status->m_issuer_lock = std::move(issuer_lock); } catch (...) { // Web fetch failed - store empty keys as negative cache entry // This prevents thundering herd on repeated failed lookups if (issuer_lock.owns_lock()) { // Store empty keys with short TTL for negative caching auto now = std::time(NULL); int negative_cache_ttl = configurer::Configuration::get_next_update_delta(); picojson::value empty_keys; picojson::object keys_obj; keys_obj["keys"] = picojson::value(picojson::array()); empty_keys = picojson::value(keys_obj); store_public_keys(issuer, empty_keys, now + negative_cache_ttl, now + negative_cache_ttl); issuer_lock.unlock(); } throw; // Re-throw the original exception } } if (status->m_do_store) { // Async web fetch completed successfully - record monitoring // This counts both initial fetches and refreshes auto &issuer_stats = internal::MonitoringStats::instance().get_issuer_stats( status->m_issuer); issuer_stats.inc_successful_key_lookup(); store_public_keys(status->m_issuer, status->m_keys, status->m_next_update, status->m_expires); // Release the per-issuer lock now that keys are stored // Other threads waiting on this issuer can now proceed if (status->m_issuer_lock.owns_lock()) { status->m_issuer_lock.unlock(); } } status->m_done = true; auto key_obj = find_key_id(status->m_keys, status->m_kid); auto iter = key_obj.find("alg"); std::string alg; if (iter == key_obj.end() || (!iter->second.is())) { auto iter2 = key_obj.find("kty"); if (iter2 == key_obj.end() || !iter2->second.is()) { throw JsonException("Key is missing key type"); } else { auto kty = iter2->second.get(); if (kty == "RSA") { alg = "RS256"; } else if (kty == "EC") { auto iter3 = key_obj.find("crv"); if (iter3 == key_obj.end() || !iter3->second.is()) { throw JsonException("EC key is missing curve name"); } auto crv = iter3->second.get(); if (crv == "P-256") { alg = "ES256"; } else { throw JsonException("Unsupported EC curve in public key"); } } else { throw JsonException("Unknown public key type"); } } } else { alg = iter->second.get(); } if (alg != "RS256" and alg != "ES256") { throw UnsupportedKeyException( "Issuer is using an unsupported algorithm"); } std::string pem; if (alg == "ES256") { iter = key_obj.find("x"); if (iter == key_obj.end() || (!iter->second.is())) { throw JsonException("Elliptic curve is missing x-coordinate"); } auto x = iter->second.get(); iter = key_obj.find("y"); if (iter == key_obj.end() || (!iter->second.is())) { throw JsonException("Elliptic curve is missing y-coordinate"); } auto y = iter->second.get(); pem = es256_from_coords(x, y); } else { iter = key_obj.find("e"); if (iter == key_obj.end() || (!iter->second.is())) { throw JsonException("Public key is missing exponent"); } auto e = iter->second.get(); iter = key_obj.find("n"); if (iter == key_obj.end() || (!iter->second.is())) { throw JsonException("Public key is missing n-value"); } auto n = iter->second.get(); pem = rs256_from_coords(e, n); } public_pem = pem; algorithm = alg; return std::move(status); } bool scitokens::Validator::store_public_ec_key(const std::string &issuer, const std::string &keyid, const std::string &public_key) { std::unique_ptr pubkey_bio( BIO_new(BIO_s_mem()), BIO_free_all); if ((size_t)BIO_write(pubkey_bio.get(), public_key.data(), public_key.size()) != public_key.size()) { return false; } std::unique_ptr x_bignum(BN_new(), BN_free); std::unique_ptr y_bignum(BN_new(), BN_free); #if OPENSSL_VERSION_NUMBER >= 0x30000000L std::unique_ptr pkey( PEM_read_bio_PUBKEY(pubkey_bio.get(), nullptr, nullptr, nullptr), EVP_PKEY_free); if (!pkey.get()) { return false; } std::unique_ptr ec_group( EC_GROUP_new_by_curve_name(EC_NAME), EC_GROUP_free); if (!ec_group.get()) { throw UnsupportedKeyException("Unable to get OpenSSL EC group"); } std::unique_ptr q_point( EC_POINT_new(ec_group.get()), EC_POINT_free); if (!q_point.get()) { throw UnsupportedKeyException("Unable to get OpenSSL EC point"); } OSSL_PARAM *params; if (!EVP_PKEY_todata(pkey.get(), EVP_PKEY_PUBLIC_KEY, ¶ms)) { throw UnsupportedKeyException( "Unable to get OpenSSL public key parameters"); } const void *buf = NULL; size_t buf_len; OSSL_PARAM *p = OSSL_PARAM_locate(params, "pub"); if (!p || !OSSL_PARAM_get_octet_string_ptr(p, &buf, &buf_len) || !EC_POINT_oct2point(ec_group.get(), q_point.get(), static_cast(buf), buf_len, nullptr)) { throw UnsupportedKeyException( "Failed to to set OpenSSL EC point with public key information"); } if (!EC_POINT_get_affine_coordinates(ec_group.get(), q_point.get(), x_bignum.get(), y_bignum.get(), NULL)) { throw UnsupportedKeyException( "Unable to get OpenSSL affine coordinates"); } OSSL_PARAM_free(params); #else std::unique_ptr pkey( PEM_read_bio_EC_PUBKEY(pubkey_bio.get(), nullptr, nullptr, nullptr), EC_KEY_free); if (!pkey) { return false; } EC_GROUP *params = (EC_GROUP *)EC_KEY_get0_group(pkey.get()); if (!params) { throw UnsupportedKeyException("Unable to get OpenSSL EC group"); } const EC_POINT *point = EC_KEY_get0_public_key(pkey.get()); if (!point) { throw UnsupportedKeyException("Unable to get OpenSSL EC point"); } if (!EC_POINT_get_affine_coordinates_GFp(params, point, x_bignum.get(), y_bignum.get(), nullptr)) { throw UnsupportedKeyException( "Unable to get OpenSSL affine coordinates"); } #endif auto x_num = BN_num_bytes(x_bignum.get()); auto y_num = BN_num_bytes(y_bignum.get()); std::vector x_bin; x_bin.resize(x_num); std::vector y_bin; y_bin.resize(y_num); BN_bn2bin(x_bignum.get(), &x_bin[0]); BN_bn2bin(y_bignum.get(), &y_bin[0]); std::string x_str(reinterpret_cast(&x_bin[0]), x_num); std::string y_str(reinterpret_cast(&y_bin[0]), y_num); picojson::object key_obj; key_obj["alg"] = picojson::value("ES256"); key_obj["kid"] = picojson::value(keyid); key_obj["use"] = picojson::value("sig"); key_obj["kty"] = picojson::value("EC"); key_obj["x"] = picojson::value(b64url_encode_nopadding(x_str)); key_obj["y"] = picojson::value(b64url_encode_nopadding(y_str)); std::vector key_list; key_list.emplace_back(key_obj); picojson::object top_obj; top_obj["keys"] = picojson::value(key_list); picojson::value top_value(top_obj); auto now = std::time(NULL); int next_update_delta = configurer::Configuration::get_next_update_delta(); int expiry_delta = configurer::Configuration::get_expiry_delta(); return store_public_keys(issuer, top_value, now + next_update_delta, now + expiry_delta); } bool scitokens::Enforcer::scope_validator(const jwt::claim &claim, void *myself) { auto me = reinterpret_cast(myself); if (claim.get_type() != jwt::json::type::string) { return false; } std::string scope = claim.as_string(); std::string requested_path = normalize_absolute_path(me->m_test_path); auto scope_iter = scope.begin(); // std::cout << "Comparing scope " << scope << " against test accesses " << // me->m_test_authz << ":" << requested_path << std::endl; bool compat_modify = false, compat_create = false, compat_cancel = false; while (scope_iter != scope.end()) { while (*scope_iter == ' ') { scope_iter++; } auto next_scope_iter = std::find(scope_iter, scope.end(), ' '); std::string full_authz; full_authz.reserve(std::distance(scope_iter, next_scope_iter)); full_authz.assign(scope_iter, next_scope_iter); auto sep_iter = full_authz.find(':'); std::string authz = full_authz.substr(0, sep_iter); std::string path; if (sep_iter == std::string::npos) { path = "/"; } else { path = full_authz.substr((++sep_iter)); } path = normalize_absolute_path(path); // If we are in compatibility mode and this is a WLCG token, then // translate the authorization names to utilize the SciToken-style // names. std::string alt_authz; if (me->m_validate_profile == SciToken::Profile::COMPAT && me->m_validator.get_profile() == SciToken::Profile::WLCG_1_0) { if (authz == "storage.read") { authz = "read"; } else if (authz == "storage.create") { authz = "write"; alt_authz = "create"; } else if (authz == "storage.modify") { authz = "write"; alt_authz = "modify"; } else if (authz == "compute.read") { authz = "condor"; path = "/READ"; } else if (authz == "compute.modify") { compat_modify = true; } else if (authz == "compute.create") { compat_create = true; } else if (authz == "compute.cancel") { compat_cancel = true; } } if (me->m_test_authz.empty()) { me->m_gen_acls.emplace_back(authz, path); if (!alt_authz.empty()) me->m_gen_acls.emplace_back(alt_authz, path); } else if (((me->m_test_authz == authz) || (!alt_authz.empty() && (me->m_test_authz == alt_authz))) && (requested_path.substr(0, path.size()) == path)) { return true; } scope_iter = next_scope_iter; } // Compatibility mode: the combination on compute modify, create, and cancel // mode are equivalent to the condor:/WRITE authorization. if (compat_modify && compat_create && compat_cancel) { if (me->m_test_authz.empty()) { me->m_gen_acls.emplace_back("condor", "/WRITE"); } else if ((me->m_test_authz == "condor") && (requested_path.substr(0, 6) == "/WRITE")) { return true; } } return me->m_test_authz.empty(); } // Configuration class functions std::pair configurer::Configuration::set_cache_home(const std::string dir_path) { // If setting to "", then we should treat as though it is unsetting the // config if (dir_path.length() == 0) { // User is configuring to empty string std::lock_guard lock(get_cache_home_mutex()); get_cache_home_string() = dir_path; get_cache_home_set().store(false, std::memory_order_relaxed); return std::make_pair(true, ""); } std::vector path_components = path_split(dir_path); // cleans any extraneous /'s std::string cleaned_dir_path; for (const auto &component : path_components) { // add the / back to the path components cleaned_dir_path += "/" + component; } // Check that the cache_home exists, and if not try to create it auto rp = mkdir_and_parents_if_needed( cleaned_dir_path); // Structured bindings not introduced until cpp 17 if (!rp.first) { // std::string err_prefix{ "An issue was encountered with the provided cache home path: "}; return std::make_pair(false, err_prefix + rp.second); } // Now it exists and we can write to it, set the value and let // scitokens_cache handle the rest { std::lock_guard lock(get_cache_home_mutex()); get_cache_home_string() = cleaned_dir_path; get_cache_home_set().store(true, std::memory_order_relaxed); } return std::make_pair(true, ""); } void configurer::Configuration::set_tls_ca_file(const std::string ca_file) { std::lock_guard lock(get_tls_ca_file_mutex()); get_tls_ca_file_string() = ca_file; get_tls_ca_file_set().store(!ca_file.empty(), std::memory_order_relaxed); } std::string configurer::Configuration::get_cache_home() { // Fast path: check if the value has been set if (!get_cache_home_set().load(std::memory_order_relaxed)) { return ""; } // Slow path: acquire lock and read the value std::lock_guard lock(get_cache_home_mutex()); return get_cache_home_string(); } std::string configurer::Configuration::get_tls_ca_file() { // Fast path: check if the value has been set if (!get_tls_ca_file_set().load(std::memory_order_relaxed)) { return ""; } // Slow path: acquire lock and read the value std::lock_guard lock(get_tls_ca_file_mutex()); return get_tls_ca_file_string(); } // bool configurer::Configuration::check_dir(const std::string dir_path) { // struct stat info; // return stat(dir_path.c_str(), &info) == 0 && (info.st_mode & S_IFDIR); // } std::pair configurer::Configuration::mkdir_and_parents_if_needed( const std::string dir_path) { // SciTokens-cpp already makes assumptions about using Linux file paths, // so making that assumption here as well. // Using these perms because that's what the actual cache file uses in // scitokens_cache mode_t mode = 0700; // Maybe these permissions should be configurable? int result; std::string currentLevel; std::vector path_components = path_split(dir_path); for (const auto &component : path_components) { currentLevel += "/" + component; result = mkdir(currentLevel.c_str(), mode); if ((result < 0) && errno != EEXIST) { std::string err_prefix{"There was an error while creating/checking " "the directory: mkdir error: "}; return std::make_pair(false, err_prefix + strerror(errno)); } } return std::make_pair(true, ""); } std::vector configurer::Configuration::path_split(std::string path) { std::vector path_components; std::stringstream ss(path); std::string component; while (std::getline(ss, component, '/')) { if (!component.empty()) { path_components.push_back(component); } } if (path_components[0] == "") { path_components.erase(path_components.begin()); } return path_components; } scitokens-cpp-1.3.0/src/scitokens_internal.h000066400000000000000000001613011513647275500211540ustar00rootroot00000000000000 #include #include #include #include #include #include #include #include #include #include #include #if defined(__GNUC__) #define WARN_UNUSED_RESULT __attribute__((warn_unused_result)) #else #define WARN_UNUSED_RESULT #endif namespace { struct FixedClock { jwt::date m_now; jwt::date now() const { return m_now; } }; } // namespace namespace jwt { template class decoded_jwt; namespace traits { struct kazuho_picojson; } } // namespace jwt namespace configurer { class Configuration { public: Configuration() {} static void set_next_update_delta(int _next_update_delta) { get_next_update_delta_ref() = _next_update_delta; } static int get_next_update_delta() { return get_next_update_delta_ref(); } static void set_expiry_delta(int _expiry_delta) { get_expiry_delta_ref() = _expiry_delta; } static int get_expiry_delta() { return get_expiry_delta_ref(); } static std::pair set_cache_home(const std::string cache_home); static std::string get_cache_home(); static void set_tls_ca_file(const std::string ca_file); static std::string get_tls_ca_file(); // Monitoring file configuration static void set_monitoring_file(const std::string &path); static std::string get_monitoring_file(); static void set_monitoring_file_interval(int seconds); static int get_monitoring_file_interval(); // Fast-path check: returns true if monitoring file might be configured static bool is_monitoring_file_configured() { return m_monitoring_file_configured.load(std::memory_order_relaxed); } // Background refresh configuration static void set_background_refresh_enabled(bool enabled) { m_background_refresh_enabled = enabled; } static bool get_background_refresh_enabled() { return m_background_refresh_enabled; } static void set_refresh_interval(int interval_ms) { m_refresh_interval_ms = interval_ms; } static int get_refresh_interval() { return m_refresh_interval_ms; } static void set_refresh_threshold(int threshold_ms) { m_refresh_threshold_ms = threshold_ms; } static int get_refresh_threshold() { return m_refresh_threshold_ms; } private: // Accessor functions for construct-on-first-use idiom static std::atomic_int &get_next_update_delta_ref() { static std::atomic_int instance{600}; return instance; } static std::atomic_int &get_expiry_delta_ref() { static std::atomic_int instance{4 * 24 * 3600}; return instance; } // Thread-safe accessors for string configurations static std::mutex &get_cache_home_mutex() { static std::mutex instance; return instance; } static std::string &get_cache_home_string() { static std::string instance; return instance; } static std::atomic &get_cache_home_set() { static std::atomic instance{false}; return instance; } static std::mutex &get_tls_ca_file_mutex() { static std::mutex instance; return instance; } static std::string &get_tls_ca_file_string() { static std::string instance; return instance; } static std::atomic &get_tls_ca_file_set() { static std::atomic instance{false}; return instance; } // Keep old declarations for backwards compatibility (will forward to // accessor functions) static std::atomic_int m_next_update_delta; static std::atomic_int m_expiry_delta; static std::shared_ptr m_cache_home; static std::shared_ptr m_tls_ca_file; static std::string m_monitoring_file; static std::mutex m_monitoring_file_mutex; static std::atomic m_monitoring_file_configured; // Fast-path flag static std::atomic_int m_monitoring_file_interval; // In seconds, default 60 static std::atomic_bool m_background_refresh_enabled; static std::atomic_int m_refresh_interval_ms; // N milliseconds static std::atomic_int m_refresh_threshold_ms; // M milliseconds // static bool check_dir(const std::string dir_path); static std::pair mkdir_and_parents_if_needed(const std::string dir_path); static std::vector path_split(const std::string dir_path); }; } // namespace configurer namespace scitokens { namespace internal { // Forward declaration class MonitoringStats; /** * Manages the background thread for refreshing JWKS. * This is a singleton that starts/stops a background thread which periodically * checks if any known issuers need their JWKS refreshed. */ class BackgroundRefreshManager { public: static BackgroundRefreshManager &get_instance() { static BackgroundRefreshManager instance; return instance; } // Start the background refresh thread (can be called multiple times) void start(); // Stop the background refresh thread (can be called multiple times) void stop(); // Check if the background refresh thread is running bool is_running() const { return m_running.load(std::memory_order_acquire); } private: BackgroundRefreshManager() = default; ~BackgroundRefreshManager() { stop(); } BackgroundRefreshManager(const BackgroundRefreshManager &) = delete; BackgroundRefreshManager & operator=(const BackgroundRefreshManager &) = delete; void refresh_loop(); std::mutex m_mutex; std::condition_variable m_cv; std::unique_ptr m_thread; std::atomic_bool m_shutdown{false}; std::atomic_bool m_running{false}; }; class SimpleCurlGet { int m_maxbytes{1048576}; unsigned m_timeout; std::vector m_data; size_t m_len{0}; std::unique_ptr m_curl; std::unique_ptr m_curl_multi; fd_set m_read_fd_set[FD_SETSIZE]; fd_set m_write_fd_set[FD_SETSIZE]; fd_set m_exc_fd_set[FD_SETSIZE]; int m_max_fd{-1}; long m_timeout_ms{0}; public: static const unsigned default_timeout = 4; static const unsigned extended_timeout = 30; SimpleCurlGet(int maxbytes = 1024 * 1024, unsigned timeout = 30) : m_maxbytes(maxbytes), m_timeout(timeout), m_curl(nullptr, &curl_easy_cleanup), m_curl_multi(nullptr, &curl_multi_cleanup) {} struct GetStatus { bool m_done{false}; int m_status_code{-1}; }; GetStatus perform_start(const std::string &url); GetStatus perform_continue(); int perform(const std::string &url, time_t expiry_time); void get_data(char *&buffer, size_t &len); std::string get_url() const; long get_timeout_ms() const { return m_timeout_ms; } int get_max_fd() const { return m_max_fd; } fd_set *get_read_fd_set() { return m_read_fd_set; } fd_set *get_write_fd_set() { return m_write_fd_set; } fd_set *get_exc_fd_set() { return m_exc_fd_set; } private: static size_t write_data(void *buffer, size_t size, size_t nmemb, void *userp); }; /** * Statistics for monitoring token validation per issuer. * All counters are atomic for thread-safe access. * Time values are stored in nanoseconds internally for atomic operations. */ struct IssuerStats { // Validation result counters std::atomic successful_validations{0}; std::atomic unsuccessful_validations{0}; std::atomic expired_tokens{0}; // Validation started counters (separate from results) std::atomic sync_validations_started{ 0}; // Started via blocking verify() std::atomic async_validations_started{ 0}; // Started via verify_async() // Duration tracking (nanoseconds) // sync_total_time_ns is updated periodically during blocking verify() std::atomic sync_total_time_ns{0}; // async_total_time_ns is only updated on completion std::atomic async_total_time_ns{0}; // Key lookup statistics std::atomic successful_key_lookups{0}; std::atomic failed_key_lookups{0}; std::atomic failed_key_lookup_time_ns{0}; // In nanoseconds // Key refresh statistics std::atomic expired_keys{0}; std::atomic failed_refreshes{0}; std::atomic stale_key_uses{0}; // Background refresh statistics (tracked by background thread) std::atomic background_successful_refreshes{0}; std::atomic background_failed_refreshes{0}; // Negative cache statistics std::atomic negative_cache_hits{0}; // Increment methods for atomic counters (use relaxed ordering for stats) void inc_successful_validation() { successful_validations.fetch_add(1, std::memory_order_relaxed); } void inc_unsuccessful_validation() { unsuccessful_validations.fetch_add(1, std::memory_order_relaxed); } void inc_expired_token() { expired_tokens.fetch_add(1, std::memory_order_relaxed); } void inc_sync_validation_started() { sync_validations_started.fetch_add(1, std::memory_order_relaxed); } void inc_async_validation_started() { async_validations_started.fetch_add(1, std::memory_order_relaxed); } void inc_stale_key_use() { stale_key_uses.fetch_add(1, std::memory_order_relaxed); } void inc_failed_refresh() { failed_refreshes.fetch_add(1, std::memory_order_relaxed); } void inc_expired_key() { expired_keys.fetch_add(1, std::memory_order_relaxed); } void inc_successful_key_lookup() { successful_key_lookups.fetch_add(1, std::memory_order_relaxed); } void inc_failed_key_lookup() { failed_key_lookups.fetch_add(1, std::memory_order_relaxed); } void inc_background_successful_refresh() { background_successful_refreshes.fetch_add(1, std::memory_order_relaxed); } void inc_background_failed_refresh() { background_failed_refreshes.fetch_add(1, std::memory_order_relaxed); } void inc_negative_cache_hit() { negative_cache_hits.fetch_add(1, std::memory_order_relaxed); } // Time setters that accept std::chrono::duration (use relaxed ordering) template void add_sync_time(std::chrono::duration duration) { auto ns = std::chrono::duration_cast(duration); sync_total_time_ns.fetch_add(static_cast(ns.count()), std::memory_order_relaxed); } template void add_async_time(std::chrono::duration duration) { auto ns = std::chrono::duration_cast(duration); async_total_time_ns.fetch_add(static_cast(ns.count()), std::memory_order_relaxed); } template void add_failed_key_lookup_time(std::chrono::duration duration) { auto ns = std::chrono::duration_cast(duration); failed_key_lookup_time_ns.fetch_add(static_cast(ns.count()), std::memory_order_relaxed); } void inc_failed_key_lookup(std::chrono::nanoseconds duration) { failed_key_lookups.fetch_add(1, std::memory_order_relaxed); failed_key_lookup_time_ns.fetch_add( static_cast(duration.count()), std::memory_order_relaxed); } // Time getters that return seconds as double (use relaxed ordering) double get_sync_time_s() const { return static_cast( sync_total_time_ns.load(std::memory_order_relaxed)) / 1e9; } double get_async_time_s() const { return static_cast( async_total_time_ns.load(std::memory_order_relaxed)) / 1e9; } double get_total_time_s() const { return get_sync_time_s() + get_async_time_s(); } double get_failed_key_lookup_time_s() const { return static_cast( failed_key_lookup_time_ns.load(std::memory_order_relaxed)) / 1e9; } }; /** * Statistics for failed (unknown) issuer lookups. */ struct FailedIssuerStats { uint64_t count{0}; double total_time_s{0.0}; }; /** * Monitoring statistics singleton. * Tracks per-issuer validation statistics and protects against * resource exhaustion from invalid issuers. */ class MonitoringStats { public: static MonitoringStats &instance(); /** * Get a reference to an issuer's statistics, creating the entry if needed. * The returned reference remains valid for the lifetime of the singleton. * All IssuerStats fields are atomic, so concurrent access is safe. */ IssuerStats &get_issuer_stats(const std::string &issuer) { std::lock_guard lock(m_mutex); return m_issuer_stats[issuer]; } /** * Record a failed issuer lookup (for unknown/invalid issuers). * This uses a separate map with DDoS protection. */ void record_failed_issuer_lookup(const std::string &issuer, double duration_s); std::string get_json() const; void reset(); /** * Check if the monitoring file should be written and write it if so. * This method is thread-safe and uses relaxed atomic operations for * the fast path (checking if write is needed). Only one thread will * actually perform the write. * * Does not throw exceptions - file write errors are silently ignored. */ void maybe_write_monitoring_file() noexcept; /** * Same as maybe_write_monitoring_file(), but skips if background refresh * thread is running. This should be called from verify() routines to * avoid redundant writes when the background thread is handling them. */ void maybe_write_monitoring_file_from_verify() noexcept; private: MonitoringStats() = default; ~MonitoringStats() = default; MonitoringStats(const MonitoringStats &) = delete; MonitoringStats &operator=(const MonitoringStats &) = delete; // Limit the number of failed issuer entries to prevent DDoS static constexpr size_t MAX_FAILED_ISSUERS = 100; mutable std::mutex m_mutex; std::unordered_map m_issuer_stats; std::unordered_map m_failed_issuer_lookups; // Atomic timestamp for last monitoring file write (seconds since epoch) // Uses relaxed memory ordering for fast-path checks std::atomic m_last_file_write_time{0}; std::string sanitize_issuer_for_json(const std::string &issuer) const; void prune_failed_issuers(); void write_monitoring_file_impl() noexcept; }; } // namespace internal class UnsupportedKeyException : public std::runtime_error { public: explicit UnsupportedKeyException(const std::string &msg) : std::runtime_error(msg) {} }; class JWTVerificationException : public std::runtime_error { public: explicit JWTVerificationException(const std::string &msg) : std::runtime_error("token verification failed: " + msg) {} }; class CurlException : public std::runtime_error { public: explicit CurlException(const std::string &msg) : std::runtime_error(msg) {} }; class IssuerLookupException : public CurlException { public: explicit IssuerLookupException(const std::string &msg) : CurlException(msg) {} }; class TokenExpiredException : public JWTVerificationException { public: explicit TokenExpiredException(const std::string &msg) : JWTVerificationException(msg) {} }; class MissingIssuerException : public std::runtime_error { public: MissingIssuerException() : std::runtime_error("Issuer not specified in claims") {} }; class InvalidIssuerException : public std::runtime_error { public: InvalidIssuerException(const std::string &msg) : std::runtime_error(msg) {} }; class NegativeCacheHitException : public InvalidIssuerException { public: explicit NegativeCacheHitException(const std::string &issuer) : InvalidIssuerException("Issuer is in negative cache (recently failed " "to retrieve keys): " + issuer) {} }; class JsonException : public std::runtime_error { public: JsonException(const std::string &msg) : std::runtime_error(msg) {} }; class SciTokenKey { public: SciTokenKey() : m_kid("none"), m_name("none") {} SciTokenKey(const std::string &key_id, const std::string &algorithm, const std::string &public_contents, const std::string &private_contents) : m_kid(key_id), m_name(algorithm), m_public(public_contents), m_private(private_contents) {} std::string serialize(jwt::builder &builder) { if (m_kid != "none") { builder.set_key_id(m_kid); } return builder.sign(*this); } std::string sign(const std::string &data, std::error_code &ec) const { if (m_name == "RS256") { return jwt::algorithm::rs256(m_public, m_private).sign(data, ec); } else if (m_name == "ES256") { return jwt::algorithm::es256(m_public, m_private).sign(data, ec); } throw UnsupportedKeyException( "Provided algorithm name is not supported"); } std::string name() const { return m_name; } void verify(const std::string &data, const std::string &signature, std::error_code &ec) const { if (m_name == "RS256") { jwt::algorithm::rs256(m_public, m_private) .verify(data, signature, ec); } else if (m_name == "ES256") { jwt::algorithm::es256(m_public, m_private) .verify(data, signature, ec); } else { throw UnsupportedKeyException( "Provided algorithm is not supported."); } } private: std::string m_kid; std::string m_name; std::string m_public; std::string m_private; }; class Validator; class AsyncStatus { public: AsyncStatus() = default; AsyncStatus(const AsyncStatus &) = delete; AsyncStatus &operator=(const AsyncStatus &) = delete; enum AsyncState { DOWNLOAD_METADATA, DOWNLOAD_PUBLIC_KEY, DONE }; bool m_done{false}; bool m_continue_fetch{false}; bool m_ignore_error{false}; bool m_do_store{true}; bool m_has_metadata{false}; bool m_oauth_fallback{false}; bool m_is_refresh{false}; // True if this is a refresh of an existing key AsyncState m_state{DOWNLOAD_METADATA}; std::unique_lock m_refresh_lock; // Per-issuer lock to prevent thundering herd on new issuers // We store both the shared_ptr (to keep mutex alive) and the lock std::shared_ptr m_issuer_mutex; std::unique_lock m_issuer_lock; int64_t m_next_update{-1}; int64_t m_expires{-1}; picojson::value m_keys; std::string m_issuer; std::string m_kid; std::string m_oauth_metadata_url; std::unique_ptr m_cget; std::string m_jwt_string; std::string m_public_pem; std::string m_algorithm; std::chrono::steady_clock::time_point m_start_time; bool m_monitoring_started{false}; bool m_is_sync{ false}; // True if called from blocking verify(), false for pure async struct timeval get_timeout_val(time_t expiry_time) const { auto now = time(NULL); long timeout_ms = 100 * (expiry_time - now); if (m_cget && (m_cget->get_timeout_ms() < timeout_ms)) timeout_ms = m_cget->get_timeout_ms(); struct timeval timeout; timeout.tv_sec = timeout_ms / 1000; timeout.tv_usec = (timeout_ms % 1000) * 1000; return timeout; } int get_max_fd() const { return m_cget ? m_cget->get_max_fd() : -1; } fd_set *get_read_fd_set() { return m_cget ? m_cget->get_read_fd_set() : nullptr; } fd_set *get_write_fd_set() { return m_cget ? m_cget->get_write_fd_set() : nullptr; } fd_set *get_exc_fd_set() { return m_cget ? m_cget->get_exc_fd_set() : nullptr; } }; class SciTokenAsyncStatus { public: SciTokenAsyncStatus() = default; SciTokenAsyncStatus(const SciTokenAsyncStatus &) = delete; SciTokenAsyncStatus &operator=(const SciTokenAsyncStatus &) = delete; std::unique_ptr m_validator; std::unique_ptr m_status; }; class SciToken { friend class scitokens::Validator; public: enum class Profile { COMPAT = 0, SCITOKENS_1_0, SCITOKENS_2_0, WLCG_1_0, AT_JWT }; SciToken(SciTokenKey &signing_algorithm) : m_key(signing_algorithm) {} void set_claim(const std::string &key, const jwt::claim &value) { m_claims[key] = value; if (key == "iss") { m_issuer_set = true; } } void set_serialize_mode(Profile profile) { m_serialize_profile = profile; } void set_deserialize_mode(Profile profile) { m_deserialize_profile = profile; } const jwt::claim get_claim(const std::string &key) { return m_claims[key]; } bool has_claim(const std::string &key) const { return m_claims.find(key) != m_claims.end(); } void set_claim_list(const std::string &claim, std::vector &claim_list) { picojson::array array; array.reserve(claim_list.size()); for (const auto &entry : claim_list) { array.emplace_back(entry); } m_claims[claim] = jwt::claim(picojson::value(array)); } // Return a claim as a string // If the claim is not a string, it can throw // a std::bad_cast() exception. const std::string get_claim_string(const std::string &key) { return m_claims[key].as_string(); } const std::vector get_claim_list(const std::string &key) { picojson::array array; try { array = m_claims[key].as_array(); } catch (std::bad_cast &) { throw JsonException("Claim's value is not a JSON list"); } std::vector result; for (const auto &value : array) { result.emplace_back(value.get()); } return result; } void set_lifetime(int lifetime) { m_lifetime = lifetime; } std::string serialize() { auto builder(jwt::create()); if (!m_issuer_set) { throw MissingIssuerException(); } auto time = std::chrono::system_clock::now(); builder.set_issued_at(time); builder.set_not_before(time); builder.set_expires_at(time + std::chrono::seconds(m_lifetime)); if (m_serialize_profile == Profile::AT_JWT) { builder.set_type("at+jwt"); } uuid_t uuid; uuid_generate(uuid); char uuid_str[37]; uuid_unparse_lower(uuid, uuid_str); m_claims["jti"] = jwt::claim(std::string(uuid_str)); if (m_serialize_profile == Profile::SCITOKENS_2_0) { m_claims["ver"] = jwt::claim(std::string("scitoken:2.0")); auto iter = m_claims.find("aud"); if (iter == m_claims.end()) { m_claims["aud"] = jwt::claim(std::string("ANY")); } } else if (m_serialize_profile == Profile::WLCG_1_0) { m_claims["wlcg.ver"] = jwt::claim(std::string("1.0")); auto iter = m_claims.find("aud"); if (iter == m_claims.end()) { m_claims["aud"] = jwt::claim(std::string("https://wlcg.cern.ch/jwt/v1/any")); } } // Set all the payload claims for (auto it : m_claims) { builder.set_payload_claim(it.first, it.second); } return m_key.serialize(builder); } void deserialize(const std::string &data, std::vector allowed_issuers = {}); std::unique_ptr deserialize_start(const std::string &data, std::vector allowed_issuers = {}); std::unique_ptr deserialize_continue(std::unique_ptr status); private: bool m_issuer_set{false}; int m_lifetime{600}; Profile m_profile{Profile::SCITOKENS_1_0}; Profile m_serialize_profile{Profile::COMPAT}; Profile m_deserialize_profile{Profile::COMPAT}; std::unordered_map m_claims; std::unique_ptr> m_decoded; SciTokenKey &m_key; }; class Validator { friend class internal::BackgroundRefreshManager; typedef int (*StringValidatorFunction)(const char *value, char **err_msg); typedef bool (*ClaimValidatorFunction)(const jwt::claim &claim_value, void *data); typedef std::map> ClaimStringValidatorMap; typedef std::map>> ClaimValidatorMap; public: Validator() : m_now(std::chrono::system_clock::now()) {} void set_now(std::chrono::system_clock::time_point now) { m_now = now; } // Maximum timeout for select() in microseconds for periodic checks static constexpr long MAX_SELECT_TIMEOUT_US = 50000; // 50ms std::unique_ptr verify_async(const SciToken &scitoken) { const jwt::decoded_jwt *jwt_decoded = scitoken.m_decoded.get(); if (!jwt_decoded) { throw JWTVerificationException( "Token is not deserialized from string."); } return verify_async(*jwt_decoded); } void verify(const SciToken &scitoken, time_t expiry_time) { // Check if monitoring file should be written (fast-path, relaxed // atomic). Skip if background thread is running. internal::MonitoringStats::instance() .maybe_write_monitoring_file_from_verify(); std::string issuer = ""; auto start_time = std::chrono::steady_clock::now(); auto last_duration_update = start_time; internal::IssuerStats *issuer_stats = nullptr; try { auto result = verify_async(scitoken); // Note: m_is_sync flag no longer needed since counting is only done // in verify_async_continue // Extract issuer from the result's JWT string after decoding starts const jwt::decoded_jwt *jwt_decoded = scitoken.m_decoded.get(); if (jwt_decoded && jwt_decoded->has_payload_claim("iss")) { issuer = jwt_decoded->get_issuer(); // Record sync validation started and get stats reference issuer_stats = &internal::MonitoringStats::instance().get_issuer_stats( issuer); issuer_stats->inc_sync_validation_started(); } while (!result->m_done) { auto timeout_val = result->get_timeout_val(expiry_time); // Limit select to MAX_SELECT_TIMEOUT_US for periodic checks if (timeout_val.tv_sec > 0 || timeout_val.tv_usec > MAX_SELECT_TIMEOUT_US) { timeout_val.tv_sec = 0; timeout_val.tv_usec = MAX_SELECT_TIMEOUT_US; } int select_result = select(result->get_max_fd() + 1, result->get_read_fd_set(), result->get_write_fd_set(), result->get_exc_fd_set(), &timeout_val); // Update duration periodically on each select return if (issuer_stats) { auto now = std::chrono::steady_clock::now(); auto delta = std::chrono::duration_cast( now - last_duration_update); issuer_stats->add_sync_time(delta); last_duration_update = now; } if (time(NULL) >= expiry_time) { throw CurlException( "Timeout when loading the OIDC metadata."); } // Only continue if select returned due to I/O activity (not // timeout) if (select_result > 0) { result = verify_async_continue(std::move(result)); } // If select_result == 0 (timeout) or -1 (error/interrupt), // just loop back to update duration and check expiry } // Record successful validation (final duration update) if (issuer_stats) { auto end_time = std::chrono::steady_clock::now(); auto delta = std::chrono::duration_cast( end_time - last_duration_update); issuer_stats->add_sync_time(delta); // Note: inc_successful_validation() is called in // verify_async_continue } } catch (const std::exception &e) { // Record failure (final duration update) if (issuer_stats) { auto end_time = std::chrono::steady_clock::now(); auto delta = std::chrono::duration_cast( end_time - last_duration_update); issuer_stats->add_sync_time(delta); record_validation_error_stats(*issuer_stats, e); } else if (!issuer.empty()) { // Issuer known but stats not yet retrieved auto &stats = internal::MonitoringStats::instance().get_issuer_stats( issuer); auto duration = std::chrono::duration_cast( std::chrono::steady_clock::now() - start_time); stats.add_sync_time(duration); record_validation_error_stats(stats, e); } throw; } } void verify(const jwt::decoded_jwt &jwt) { // Check if monitoring file should be written (fast-path, relaxed // atomic). Skip if background thread is running. internal::MonitoringStats::instance() .maybe_write_monitoring_file_from_verify(); std::string issuer = ""; auto start_time = std::chrono::steady_clock::now(); internal::IssuerStats *issuer_stats = nullptr; try { // Try to extract issuer for monitoring if (jwt.has_payload_claim("iss")) { issuer = jwt.get_issuer(); // Record sync validation started and get stats reference issuer_stats = &internal::MonitoringStats::instance().get_issuer_stats( issuer); issuer_stats->inc_sync_validation_started(); } auto result = verify_async(jwt); // Note: m_is_sync flag no longer needed since counting is only done // in verify_async_continue while (!result->m_done) { result = verify_async_continue(std::move(result)); } // Record successful validation if (issuer_stats) { auto end_time = std::chrono::steady_clock::now(); auto duration = std::chrono::duration_cast( end_time - start_time); issuer_stats->add_sync_time(duration); // Note: inc_successful_validation() is called in // verify_async_continue } } catch (const std::exception &e) { // Record failure if we have an issuer if (issuer_stats) { auto end_time = std::chrono::steady_clock::now(); auto duration = std::chrono::duration_cast( end_time - start_time); issuer_stats->add_sync_time(duration); record_validation_error_stats(*issuer_stats, e); } else if (!issuer.empty()) { // Issuer known but stats not yet retrieved auto &stats = internal::MonitoringStats::instance().get_issuer_stats( issuer); auto duration = std::chrono::duration_cast( std::chrono::steady_clock::now() - start_time); stats.add_sync_time(duration); record_validation_error_stats(stats, e); } throw; } } std::unique_ptr verify_async(const jwt::decoded_jwt &jwt) { // Start background refresh thread if configured on first verification std::call_once(m_background_refresh_once, []() { if (configurer::Configuration::get_background_refresh_enabled()) { internal::BackgroundRefreshManager::get_instance().start(); } }); // If token has a typ header claim (RFC8725 Section 3.11), trust that in // COMPAT mode. if (jwt.has_type()) { std::string t_type = jwt.get_type(); if (m_validate_profile == SciToken::Profile::COMPAT) { if (t_type == "at+jwt" || t_type == "application/at+jwt") { m_profile = SciToken::Profile::AT_JWT; } } else if (m_validate_profile == SciToken::Profile::AT_JWT) { if (t_type != "at+jwt" && t_type != "application/at+jwt") { throw JWTVerificationException( "'typ' header claim must be at+jwt"); } m_profile = SciToken::Profile::AT_JWT; } } else { if (m_validate_profile == SciToken::Profile::AT_JWT) { throw JWTVerificationException( "'typ' header claim must be set for at+jwt tokens"); } } if (!jwt.has_payload_claim("iat")) { throw JWTVerificationException("'iat' claim is mandatory"); } if (m_profile == SciToken::Profile::SCITOKENS_1_0 || m_profile == SciToken::Profile::SCITOKENS_2_0) { if (!jwt.has_payload_claim("nbf")) { throw JWTVerificationException("'nbf' claim is mandatory"); } } if (!jwt.has_payload_claim("exp")) { throw JWTVerificationException("'exp' claim is mandatory"); } if (!jwt.has_payload_claim("iss")) { throw JWTVerificationException("'iss' claim is mandatory"); } if (!m_allowed_issuers.empty()) { std::string issuer = jwt.get_issuer(); bool permitted = false; for (const auto &allowed_issuer : m_allowed_issuers) { if (issuer == allowed_issuer) { permitted = true; break; } } if (!permitted) { std::string safe_issuer = format_issuer_for_error(jwt); throw JWTVerificationException( "Token issuer " + safe_issuer + " is not in list of allowed issuers."); } } for (const auto &claim : m_critical_claims) { if (!jwt.has_payload_claim(claim)) { std::stringstream ss; ss << "'" << claim << "' claim is mandatory"; throw JWTVerificationException(ss.str()); } } std::string public_pem; std::string algorithm; // Key id is optional in the RFC, set to blank if it doesn't exist std::string key_id; if (jwt.has_key_id()) { key_id = jwt.get_key_id(); } auto status = get_public_key_pem(jwt.get_issuer(), key_id, public_pem, algorithm); status->m_jwt_string = jwt.get_token(); status->m_public_pem = public_pem; status->m_algorithm = algorithm; // Start monitoring timing and record async validation started status->m_start_time = std::chrono::steady_clock::now(); status->m_monitoring_started = true; status->m_issuer = jwt.get_issuer(); auto &stats = internal::MonitoringStats::instance().get_issuer_stats( jwt.get_issuer()); stats.inc_async_validation_started(); return verify_async_continue(std::move(status)); } std::unique_ptr verify_async_continue(std::unique_ptr status) { if (!status->m_done) { std::string public_pem, algorithm; status = get_public_key_pem_continue(std::move(status), public_pem, algorithm); status->m_public_pem = public_pem; status->m_algorithm = algorithm; if (!status->m_done) { return std::move(status); } } // std::cout << "Public PEM: " << public_pem << std::endl << "Algorithm: // " << algorithm << std::endl; SciTokenKey key(status->m_kid, status->m_algorithm, status->m_public_pem, ""); auto verifier = jwt::verify({m_now}) .allow_algorithm(key); const jwt::decoded_jwt jwt( status->m_jwt_string); try { verifier.verify(jwt); } catch (const std::exception &e) { // Check if this is an expiration error from jwt-cpp std::string error_msg = e.what(); if (error_msg.find("exp") != std::string::npos || error_msg.find("expir") != std::string::npos) { throw TokenExpiredException(error_msg); } throw; } bool must_verify_everything = true; if (jwt.has_payload_claim("ver")) { const jwt::claim &claim = jwt.get_payload_claim("ver"); if (claim.get_type() != jwt::json::type::string) { throw JWTVerificationException( "'ver' claim value must be a string (if present)"); } std::string ver_string = claim.as_string(); if ((ver_string == "scitokens:2.0") || (ver_string == "scitoken:2.0")) { must_verify_everything = false; if ((m_validate_profile != SciToken::Profile::COMPAT) && (m_validate_profile != SciToken::Profile::SCITOKENS_2_0)) { throw JWTVerificationException( "Invalidate token type; not expecting a SciToken 2.0."); } m_profile = SciToken::Profile::SCITOKENS_2_0; if (!jwt.has_payload_claim("aud")) { throw JWTVerificationException( "'aud' claim required for SciTokens 2.0 profile"); } } else if (ver_string == "scitokens:1.0") { must_verify_everything = m_validate_all_claims; if ((m_validate_profile != SciToken::Profile::COMPAT) && (m_validate_profile != SciToken::Profile::SCITOKENS_1_0)) { throw JWTVerificationException( "Invalidate token type; not expecting a SciToken 1.0."); } m_profile = SciToken::Profile::SCITOKENS_1_0; } else { std::stringstream ss; ss << "Unknown profile version in token: " << ver_string; throw JWTVerificationException(ss.str()); } // Handle WLCG common JWT profile. } else if (jwt.has_payload_claim("wlcg.ver")) { if ((m_validate_profile != SciToken::Profile::COMPAT) && (m_validate_profile != SciToken::Profile::WLCG_1_0)) { throw JWTVerificationException( "Invalidate token type; not expecting a WLCG 1.0."); } m_profile = SciToken::Profile::WLCG_1_0; must_verify_everything = false; const jwt::claim &claim = jwt.get_payload_claim("wlcg.ver"); if (claim.get_type() != jwt::json::type::string) { throw JWTVerificationException( "'ver' claim value must be a string (if present)"); } std::string ver_string = claim.as_string(); if (ver_string != "1.0") { std::stringstream ss; ss << "Unknown WLCG profile version in token: " << ver_string; throw JWTVerificationException(ss.str()); } if (!jwt.has_payload_claim("aud")) { throw JWTVerificationException( "Malformed token: 'aud' claim required for WLCG profile"); } } else if (m_profile == SciToken::Profile::AT_JWT) { // detected early above from typ header claim. must_verify_everything = false; } else { if ((m_validate_profile != SciToken::Profile::COMPAT) && (m_validate_profile != SciToken::Profile::SCITOKENS_1_0)) { throw JWTVerificationException( "Invalidate token type; not expecting a SciToken 1.0."); } m_profile = SciToken::Profile::SCITOKENS_1_0; must_verify_everything = m_validate_all_claims; } auto claims = jwt.get_payload_json(); for (const auto &claim_pair : claims) { if (claim_pair.first == "iat" || claim_pair.first == "nbf" || claim_pair.first == "exp" || claim_pair.first == "ver") { continue; } auto iter = m_validators.find(claim_pair.first); auto iter_claim = m_claim_validators.find(claim_pair.first); if ((iter == m_validators.end() || iter->second.empty()) && (iter_claim == m_claim_validators.end() || iter_claim->second.empty())) { bool is_issuer = claim_pair.first == "iss"; if (is_issuer && !m_allowed_issuers.empty()) { // skip; we verified it above } else if (must_verify_everything) { std::stringstream ss; ss << "'" << claim_pair.first << "' claim verification is mandatory"; // std::cout << ss.str() << std::endl; throw JWTVerificationException(ss.str()); } } // std::cout << "Running claim " << claim_pair.first << " through // validation." << std::endl; if (iter != m_validators.end()) for (const auto &verification_func : iter->second) { const jwt::claim &claim = jwt.get_payload_claim(claim_pair.first); if (claim.get_type() != jwt::json::type::string) { std::stringstream ss; ss << "'" << claim_pair.first << "' claim value must be a string to verify."; throw JWTVerificationException(ss.str()); } std::string value = claim.as_string(); char *err_msg = nullptr; if (verification_func(value.c_str(), &err_msg)) { if (err_msg) { throw JWTVerificationException(err_msg); } else { std::stringstream ss; ss << "'" << claim_pair.first << "' claim verification failed."; throw JWTVerificationException(ss.str()); } } } if (iter_claim != m_claim_validators.end()) for (const auto &verification_pair : iter_claim->second) { const jwt::claim &claim = jwt.get_payload_claim(claim_pair.first); if (verification_pair.first( claim, verification_pair.second) == false) { std::stringstream ss; ss << "'" << claim_pair.first << "' claim verification failed."; throw JWTVerificationException(ss.str()); } } } // Record successful validation if (status->m_monitoring_started) { auto end_time = std::chrono::steady_clock::now(); auto duration = std::chrono::duration_cast( end_time - status->m_start_time); auto &stats = internal::MonitoringStats::instance().get_issuer_stats( status->m_issuer); stats.inc_successful_validation(); stats.add_async_time(duration); } // Create new result, preserving monitoring flags std::unique_ptr result(new AsyncStatus()); result->m_done = true; result->m_is_sync = status->m_is_sync; result->m_monitoring_started = status->m_monitoring_started; return result; } void add_critical_claims(const std::vector &claims) { std::copy(claims.begin(), claims.end(), std::back_inserter(m_critical_claims)); } void add_allowed_issuers(const std::vector &allowed_issuers) { std::copy(allowed_issuers.begin(), allowed_issuers.end(), std::back_inserter(m_allowed_issuers)); } void add_string_validator(const std::string &claim, StringValidatorFunction func) { auto result = m_validators.insert( {claim, std::vector()}); result.first->second.push_back(func); } void add_claim_validator(const std::string &claim, ClaimValidatorFunction func, void *data) { auto result = m_claim_validators.insert( {claim, std::vector>()}); result.first->second.push_back({func, data}); } void set_validate_all_claims_scitokens_1(bool new_val) { m_validate_all_claims = new_val; } /** * Get the profile of the last validated token. * * If there has been no validation - or the validation failed, * then the return value is unspecified. * * Will not return Profile::COMPAT. */ SciToken::Profile get_profile() const { if (m_profile == SciToken::Profile::COMPAT) { throw JWTVerificationException("Token profile has not been set."); } return m_profile; } /** * Set the profile that will be used for validation; COMPAT indicates any * supported profile is allowable. */ void set_validate_profile(SciToken::Profile profile) { m_validate_profile = profile; } /** * Store the contents of a public EC key for a given issuer. */ static bool store_public_ec_key(const std::string &issuer, const std::string &kid, const std::string &key); /** * Store the contents of a JWKS for a given issuer. */ static bool store_jwks(const std::string &issuer, const std::string &jwks); /** * Trigger a refresh of the JWKS or a given issuer. */ static bool refresh_jwks(const std::string &issuer); /** * Fetch the contents of fa JWKS for a given issuer (do not trigger a * refresh). Will return an empty JWKS if no valid JWKS is available. */ static std::string get_jwks(const std::string &issuer); /** * Get all issuers from the database along with their next_update times. * Returns a vector of pairs (issuer, next_update). * Only returns non-expired entries. */ static std::vector> get_all_issuers_from_db(int64_t now); /** * Load JWKS for a given issuer, refreshing only if needed. * Returns the JWKS string. If refresh is needed and fails, throws * exception. */ static std::string load_jwks(const std::string &issuer); /** * Get metadata for a cached JWKS entry. * Returns a JSON string with expires, next_update, and extra fields. * Throws exception if issuer not found in cache. */ static std::string get_jwks_metadata(const std::string &issuer); /** * Delete a JWKS entry from the keycache. * Returns true on success, false on failure. */ static bool delete_jwks(const std::string &issuer); private: static std::unique_ptr get_public_key_pem(const std::string &issuer, const std::string &kid, std::string &public_pem, std::string &algorithm); static std::unique_ptr get_public_key_pem_continue(std::unique_ptr status, std::string &public_pem, std::string &algorithm); static std::unique_ptr get_public_keys_from_web(const std::string &issuer, unsigned timeout); static std::unique_ptr get_public_keys_from_web_continue(std::unique_ptr status); static bool get_public_keys_from_db(const std::string issuer, int64_t now, picojson::value &keys, int64_t &next_update); static bool store_public_keys(const std::string &issuer, const picojson::value &keys, int64_t next_update, int64_t expires); /** * Safely format an issuer for error messages. * Serializes the issuer claim back to JSON format and limits the size * to prevent malicious issuers from causing problems in error output. */ static std::string format_issuer_for_error( const jwt::decoded_jwt &jwt) { try { if (!jwt.has_payload_claim("iss")) { return ""; } // Get the raw claim and serialize it back to JSON const auto &claim = jwt.get_payload_claim("iss"); std::string serialized = claim.to_json().serialize(); // Limit the size to prevent abuse const size_t max_issuer_length = 256; if (serialized.length() > max_issuer_length) { serialized = serialized.substr(0, max_issuer_length - 3) + "..."; } return serialized; } catch (...) { // If anything goes wrong, return a safe fallback return ""; } } /** * Helper method to record monitoring statistics for validation errors. * This version operates on an IssuerStats reference and does NOT update * time (caller is responsible for time tracking). */ void record_validation_error_stats(internal::IssuerStats &stats, const std::exception &e) { if (dynamic_cast(&e)) { stats.inc_expired_token(); } else if (dynamic_cast(&e)) { stats.inc_negative_cache_hit(); } stats.inc_unsuccessful_validation(); } bool m_validate_all_claims{true}; SciToken::Profile m_profile{SciToken::Profile::COMPAT}; SciToken::Profile m_validate_profile{SciToken::Profile::COMPAT}; ClaimStringValidatorMap m_validators; ClaimValidatorMap m_claim_validators; std::chrono::system_clock::time_point m_now; std::vector m_critical_claims; std::vector m_allowed_issuers; // Once flag for starting background refresh on first verification static std::once_flag m_background_refresh_once; }; class Enforcer { public: typedef std::vector> AclsList; Enforcer(std::string issuer, std::vector audience_list) : m_issuer(issuer), m_audiences(audience_list) { m_validator.add_allowed_issuers({m_issuer}); m_validator.add_claim_validator("jti", &Enforcer::str_validator, nullptr); m_validator.add_claim_validator("sub", &Enforcer::str_validator, nullptr); m_validator.add_claim_validator("opt", &Enforcer::all_validator, nullptr); m_validator.add_claim_validator("aud", &Enforcer::aud_validator, this); m_validator.add_claim_validator("scope", &Enforcer::scope_validator, this); std::vector critical_claims = {"scope"}; // If any audiences are in the given to us, then force the validator to // check it. if (!m_audiences.empty()) { critical_claims.push_back("aud"); } m_validator.add_critical_claims(critical_claims); } void set_now(std::chrono::system_clock::time_point now) { m_validator.set_now(now); } void set_validate_profile(SciToken::Profile profile) { m_validate_profile = profile; } bool test(const SciToken &scitoken, const std::string &authz, const std::string &path) { reset_state(); m_test_path = path; m_test_authz = authz; try { m_validator.verify(scitoken, time(NULL) + 20); return true; } catch (std::runtime_error &) { throw; } } AclsList generate_acls(const SciToken &scitoken) { reset_state(); m_validator.verify(scitoken, time(NULL) + 20); return m_gen_acls; } std::unique_ptr generate_acls_start(const SciToken &scitoken, AclsList &acls) { reset_state(); auto status = m_validator.verify_async(scitoken); if (status->m_done) { acls = m_gen_acls; } return status; } std::unique_ptr generate_acls_continue(std::unique_ptr status, AclsList &acls) { auto result = m_validator.verify_async_continue(std::move(status)); if (result->m_done) { acls = m_gen_acls; } return result; } private: static bool all_validator(const jwt::claim &, void *) { return true; } static bool str_validator(const jwt::claim &claim, void *) { return claim.get_type() == jwt::json::type::string; } static bool scope_validator(const jwt::claim &claim, void *myself); static bool aud_validator(const jwt::claim &claim, void *myself) { auto me = reinterpret_cast(myself); std::vector jwt_audiences; if (claim.get_type() == jwt::json::type::string) { const std::string &audience = claim.as_string(); jwt_audiences.push_back(audience); } else if (claim.get_type() == jwt::json::type::array) { const picojson::array &audiences = claim.as_array(); for (const auto &aud_value : audiences) { const std::string &audience = aud_value.get(); jwt_audiences.push_back(audience); } } for (const auto &aud_value : jwt_audiences) { if (((me->m_validator.get_profile() == SciToken::Profile::SCITOKENS_2_0) && (aud_value == "ANY")) || ((me->m_validator.get_profile() == SciToken::Profile::WLCG_1_0) && (aud_value == "https://wlcg.cern.ch/jwt/v1/any"))) { return true; } for (const auto &aud : me->m_audiences) { if (aud == aud_value) { return true; } } } return false; } void reset_state() { m_test_path = ""; m_test_authz = ""; m_gen_acls.clear(); m_validator.set_validate_profile(m_validate_profile); } SciToken::Profile m_validate_profile{SciToken::Profile::COMPAT}; std::string m_test_path; std::string m_test_authz; AclsList m_gen_acls; std::string m_issuer; std::vector m_audiences; scitokens::Validator m_validator; }; } // namespace scitokens scitokens-cpp-1.3.0/src/scitokens_monitoring.cpp000066400000000000000000000223041513647275500220570ustar00rootroot00000000000000#include "scitokens_internal.h" #include #include #include #include #ifndef PICOJSON_USE_INT64 #define PICOJSON_USE_INT64 #endif #include namespace scitokens { namespace internal { MonitoringStats &MonitoringStats::instance() { static MonitoringStats instance; return instance; } void MonitoringStats::record_failed_issuer_lookup(const std::string &issuer, double duration_s) { std::lock_guard lock(m_mutex); // Limit the number of failed issuer entries to prevent resource exhaustion if (m_failed_issuer_lookups.size() >= MAX_FAILED_ISSUERS) { prune_failed_issuers(); } // Only track if we still have room or issuer is already tracked if (m_failed_issuer_lookups.size() < MAX_FAILED_ISSUERS || m_failed_issuer_lookups.find(issuer) != m_failed_issuer_lookups.end()) { auto &stats = m_failed_issuer_lookups[issuer]; stats.count++; stats.total_time_s += duration_s; } } std::string MonitoringStats::sanitize_issuer_for_json(const std::string &issuer) const { // Limit issuer length to prevent abuse const size_t max_length = 256; std::string sanitized = issuer; if (sanitized.length() > max_length) { sanitized = sanitized.substr(0, max_length - 3) + "..."; } return sanitized; } void MonitoringStats::prune_failed_issuers() { // Remove entries with the lowest counts to make room for new ones if (m_failed_issuer_lookups.empty()) { return; } // Find the minimum count uint64_t min_count = UINT64_MAX; for (const auto &entry : m_failed_issuer_lookups) { uint64_t count = entry.second.count; if (count < min_count) { min_count = count; } } // Remove all entries with the minimum count for (auto it = m_failed_issuer_lookups.begin(); it != m_failed_issuer_lookups.end();) { if (it->second.count == min_count) { it = m_failed_issuer_lookups.erase(it); } else { ++it; } } } std::string MonitoringStats::get_json() const { std::lock_guard lock(m_mutex); picojson::object root; picojson::object issuers_obj; // Add per-issuer statistics for (const auto &entry : m_issuer_stats) { const std::string &issuer = entry.first; const IssuerStats &stats = entry.second; picojson::object issuer_obj; issuer_obj["successful_validations"] = picojson::value(static_cast( stats.successful_validations.load(std::memory_order_relaxed))); issuer_obj["unsuccessful_validations"] = picojson::value( static_cast(stats.unsuccessful_validations.load( std::memory_order_relaxed))); issuer_obj["expired_tokens"] = picojson::value(static_cast( stats.expired_tokens.load(std::memory_order_relaxed))); // Validation started counters issuer_obj["sync_validations_started"] = picojson::value( static_cast(stats.sync_validations_started.load( std::memory_order_relaxed))); issuer_obj["async_validations_started"] = picojson::value( static_cast(stats.async_validations_started.load( std::memory_order_relaxed))); // Duration tracking issuer_obj["sync_total_time_s"] = picojson::value(stats.get_sync_time_s()); issuer_obj["async_total_time_s"] = picojson::value(stats.get_async_time_s()); issuer_obj["total_validation_time_s"] = picojson::value(stats.get_total_time_s()); // Web lookup statistics issuer_obj["successful_key_lookups"] = picojson::value(static_cast( stats.successful_key_lookups.load(std::memory_order_relaxed))); issuer_obj["failed_key_lookups"] = picojson::value(static_cast( stats.failed_key_lookups.load(std::memory_order_relaxed))); issuer_obj["failed_key_lookup_time_s"] = picojson::value(stats.get_failed_key_lookup_time_s()); // Key refresh statistics issuer_obj["expired_keys"] = picojson::value(static_cast( stats.expired_keys.load(std::memory_order_relaxed))); issuer_obj["failed_refreshes"] = picojson::value(static_cast( stats.failed_refreshes.load(std::memory_order_relaxed))); issuer_obj["stale_key_uses"] = picojson::value(static_cast( stats.stale_key_uses.load(std::memory_order_relaxed))); // Background refresh statistics issuer_obj["background_successful_refreshes"] = picojson::value( static_cast(stats.background_successful_refreshes.load( std::memory_order_relaxed))); issuer_obj["background_failed_refreshes"] = picojson::value( static_cast(stats.background_failed_refreshes.load( std::memory_order_relaxed))); // Negative cache statistics issuer_obj["negative_cache_hits"] = picojson::value(static_cast( stats.negative_cache_hits.load(std::memory_order_relaxed))); std::string sanitized_issuer = sanitize_issuer_for_json(issuer); issuers_obj[sanitized_issuer] = picojson::value(issuer_obj); } root["issuers"] = picojson::value(issuers_obj); // Add failed issuer lookups with duration if (!m_failed_issuer_lookups.empty()) { picojson::object failed_obj; for (const auto &entry : m_failed_issuer_lookups) { std::string sanitized_issuer = sanitize_issuer_for_json(entry.first); picojson::object lookup_stats; lookup_stats["count"] = picojson::value(static_cast(entry.second.count)); lookup_stats["total_time_s"] = picojson::value(entry.second.total_time_s); failed_obj[sanitized_issuer] = picojson::value(lookup_stats); } root["failed_issuer_lookups"] = picojson::value(failed_obj); } return picojson::value(root).serialize(); } void MonitoringStats::reset() { std::lock_guard lock(m_mutex); m_issuer_stats.clear(); m_failed_issuer_lookups.clear(); } void MonitoringStats::maybe_write_monitoring_file() noexcept { try { // Fast path: check atomic flag first (relaxed load, no mutex) if (!configurer::Configuration::is_monitoring_file_configured()) { return; } // Get current time and interval (relaxed loads for fast path) auto now = std::chrono::steady_clock::now(); auto now_seconds = std::chrono::duration_cast( now.time_since_epoch()) .count(); int64_t last_write = m_last_file_write_time.load(std::memory_order_relaxed); int interval = configurer::Configuration::get_monitoring_file_interval(); // Check if enough time has passed since last write if (now_seconds - last_write < interval) { return; } // Try to atomically claim the write (compare-and-swap) // Only one thread will succeed in updating the timestamp if (!m_last_file_write_time.compare_exchange_strong( last_write, now_seconds, std::memory_order_acq_rel, std::memory_order_relaxed)) { // Another thread beat us to it, they will do the write return; } // We successfully claimed the write, do it write_monitoring_file_impl(); } catch (...) { // Silently ignore any errors - this is best-effort } } void MonitoringStats::maybe_write_monitoring_file_from_verify() noexcept { // If background refresh thread is running, it will handle the writes // This avoids redundant writes and potential contention if (BackgroundRefreshManager::get_instance().is_running()) { return; } maybe_write_monitoring_file(); } void MonitoringStats::write_monitoring_file_impl() noexcept { try { std::string monitoring_file = configurer::Configuration::get_monitoring_file(); if (monitoring_file.empty()) { return; } // Get the JSON content std::string json_content = get_json(); // Write to a temporary file first, then rename for atomicity std::string tmp_file = monitoring_file + ".tmp"; { std::ofstream ofs(tmp_file, std::ios::out | std::ios::trunc); if (!ofs) { return; // Cannot open file, silently fail } ofs << json_content; if (!ofs) { return; // Write failed, silently fail } } // Close file before rename // Atomic rename (on POSIX systems) if (std::rename(tmp_file.c_str(), monitoring_file.c_str()) != 0) { // Rename failed, try to clean up temp file std::remove(tmp_file.c_str()); } } catch (...) { // Silently ignore any errors - this is best-effort } } } // namespace internal } // namespace scitokens scitokens-cpp-1.3.0/src/test.cpp000066400000000000000000000070451513647275500165740ustar00rootroot00000000000000#include #include #include #include #include "scitokens.h" int main(int argc, const char **argv) { std::string token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXUyJ9.eyJpc3MiOiJhdXRoMCJ9." "AbIJTDMFc7yUa5MhvcP03nJPyCPzZtQcGEp-zWfOkEE"; if (argc == 2) { token = argv[1]; } auto decoded = jwt::decode(token); for (auto &e : decoded.get_payload_json()) std::cout << e.first << " = " << e.second << std::endl; std::ifstream priv_ifs("test.pem"); std::string private_contents((std::istreambuf_iterator(priv_ifs)), (std::istreambuf_iterator())); std::ifstream pub_ifs("test.pem.pub"); std::string public_contents((std::istreambuf_iterator(pub_ifs)), (std::istreambuf_iterator())); char *err_msg; SciTokenKey key = scitoken_key_create("key-es356", "RS256", public_contents.c_str(), private_contents.c_str(), &err_msg); if (!key) { std::cout << "Failed to generate a key: " << err_msg << std::endl; return 1; } SciToken scitoken = scitoken_create(key); if (scitoken_set_claim_string(scitoken, "iss", "https://demo.scitokens.org", &err_msg)) { std::cout << "Failed to set a claim: " << err_msg << std::endl; } // Test setting and getting a claim char *value; if (scitoken_get_claim_string(scitoken, "iss", &value, &err_msg)) { std::cout << "Failed to get a claim: " << err_msg << std::endl; } if (strcmp(value, "https://demo.scitokens.org") != 0) { std::cout << "Failed to get same claim a claim: " << err_msg << std::endl; } if (scitoken_serialize(scitoken, &value, &err_msg)) { std::cout << "Failed to generate a token: " << err_msg << std::endl; return 1; } std::cout << "SciToken: " << value << std::endl; auto decoded2 = jwt::decode(value); for (auto &e : decoded2.get_payload_json()) std::cout << e.first << " = " << e.second << std::endl; scitoken_destroy(scitoken); scitoken_key_destroy(key); // Get updated strings from https://demo.scitokens.org/ // std::string test_value = // "eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiIsImtpZCI6ImtleS1lczI1NiJ9.eyJpc3MiOiJodHRwczovL2RlbW8uc2NpdG9rZW5zLm9yZyIsImV4cCI6MTU0NjM4OTU5MiwiaWF0IjoxNTQ2Mzg4OTkyLCJuYmYiOjE1NDYzODg5OTIsImp0aSI6IjRkMzM2MTU5LWMxMDEtNGRhYy1iYzI5LWI5NDQ3ZDRkY2IxZSJ9.VfSCPj79IfdVCZHw8n0RJJupbaSU0OqMWxRVAnVUNvk1SCz0Ep3O06Boe5I0SRiZR8_0jzHw9vHZ0YOT_0kPAw"; std::string test_value = "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6ImtleS1yczI1NiJ9." "eyJpc3MiOiJodHRwczovL2RlbW8uc2NpdG9rZW5zLm9yZyIsImV4cCI6MTU0NjM5MjAwOS" "wiaWF0IjoxNTQ2MzkxNDA5LCJuYmYiOjE1NDYzOTE0MDksImp0aSI6ImFkYTk2MjdiLWEx" "MGYtNGMyYS05Nzc2LTE4ZThkN2JmN2M4NSJ9.cNMG5zI2-JHh7l_" "PUPUAxom5Vi6Q3akKmv6q57CoVKHtxZAZRc47Uoix_" "AH3Xzr42qohr2FPamRTxUMsfZjrAFDJ_4JhJ-kKjJ3cRXXF-" "gj7lbniCDGOBuPXeMsVmeED15nauZ3XKXUHTGLEsg5O6RjS7sGKM_" "e9YiYvcTvWXcdkrkxZ2dPPU-R3IxdK6PtE9OB2XOk85H670OAJT3qimKm8Dk_" "Ri6DEEty1Su_" "1Tov3ac5B19iZkbhhVPMVP0cRolR9UNLhMxQAsbgEmArQOcs046AOzqQz6osOkdYOrVVO7" "lO2owUyMol94mB_39y1M8jcf5WNq3ukMMIzMCAPwA"; if (scitoken_deserialize(test_value.c_str(), &scitoken, nullptr, &err_msg)) { std::cout << "Failed to deserialize a token: " << err_msg << std::endl; return 1; } return 0; } scitokens-cpp-1.3.0/src/test_access.cpp000066400000000000000000000027131513647275500201120ustar00rootroot00000000000000#include #include "scitokens.h" int main(int argc, const char **argv) { if (argc < 6) { std::cerr << "Usage: " << argv[0] << " (TOKEN) (ISSUER) (AUDIENCE) (AUTHZ) (PATH)" << std::endl; return 1; } std::string token(argv[1]); std::string issuer(argv[2]); std::string audience(argv[3]); std::string authz(argv[4]); std::string path(argv[5]); const char *aud_list[2]; aud_list[0] = audience.c_str(); aud_list[1] = nullptr; SciToken scitoken; char *err_msg = nullptr; if (scitoken_deserialize(token.c_str(), &scitoken, nullptr, &err_msg)) { std::cout << "Failed to deserialize a token: " << err_msg << std::endl; return 1; } std::cout << "Token deserialization successful. Checking authorizations." << std::endl; Enforcer enf; if (!(enf = enforcer_create(issuer.c_str(), aud_list, &err_msg))) { std::cout << "Failed to create a new enforcer object: " << err_msg << std::endl; return 1; } const Acl acl{authz.c_str(), path.c_str()}; if (enforcer_test(enf, scitoken, &acl, &err_msg)) { if (err_msg) { std::cout << "Access test failed: " << err_msg << std::endl; } else { std::cout << "Access test failed." << std::endl; } return 1; } std::cout << "Access test successful." << std::endl; enforcer_destroy(enf); return 0; } scitokens-cpp-1.3.0/src/verify.cpp000066400000000000000000000100231513647275500171070ustar00rootroot00000000000000#include "scitokens.h" #include #include #include #include namespace { const char usage[] = "\n" "Syntax: %s [--cred cred_file] < TOKENFILE\n" "\n" " Options\n" " -h | --help Display usage\n" " -c | --cred File containing the signing credential.\n" " -i | --issuer Issuer of the token to verify.\n" " -K | --keyid Name of the token key.\n" " -p | --profile Profile to enforce (wlcg, scitokens1, " "scitokens2, atjwt).\n" "\n" " The token to verify must be provided via standard input (stdin).\n"; const struct option long_options[] = {{"help", no_argument, NULL, 'h'}, {"cred", required_argument, NULL, 'c'}, {"issuer", required_argument, NULL, 'i'}, {"keyid", required_argument, NULL, 'K'}, {"profile", required_argument, NULL, 'p'}, {0, 0, 0, 0}}; const char short_options[] = "hc:i:K:p:"; std::string g_cred, g_issuer, g_keyid, g_profile; int init_arguments(int argc, char *const argv[]) { int arg; while ((arg = getopt_long(argc, argv, short_options, long_options, nullptr)) != -1) { switch (arg) { case 'h': printf(usage, argv[0]); exit(0); break; case 'c': g_cred = optarg; break; case 'i': g_issuer = optarg; break; case 'K': g_keyid = optarg; break; case 'p': g_profile = optarg; break; default: fprintf(stderr, usage, argv[0]); exit(1); break; } } if (optind < argc - 1) { fprintf(stderr, "%s: invalid option -- %s\n", argv[0], argv[optind]); fprintf(stderr, usage, argv[0]); exit(1); } if ((!g_cred.empty() || !g_issuer.empty() || !g_keyid.empty()) && (g_cred.empty() || g_issuer.empty() || g_keyid.empty())) { fprintf(stderr, "%s: If --cred, --keyid, or --issuer are set, then all must be " "set.\n", argv[0]); fprintf(stderr, usage, argv[0]); exit(1); } return 0; } } // namespace int main(int argc, char *const *argv) { if (init_arguments(argc, argv)) { return 1; } std::string token; // If a positional argument is present, treat it as the token (with warning) if (optind < argc) { std::cout << argv[0] << ": Warning: Providing the token on the command line is " << "insecure. Please use stdin instead." << std::endl; token = argv[optind]; } else { // Read token from stdin std::getline(std::cin, token); } if (token.empty()) { fprintf(stderr, "%s: No token provided on stdin or command line.\n", argv[0]); fprintf(stderr, usage, argv[0]); return 1; } if (!g_issuer.empty()) { char *err_msg; std::ifstream pub_ifs(g_cred); std::string public_contents((std::istreambuf_iterator(pub_ifs)), (std::istreambuf_iterator())); auto rv = scitoken_store_public_ec_key(g_issuer.c_str(), g_keyid.c_str(), public_contents.c_str(), &err_msg); if (rv) { fprintf(stderr, "%s: %s\n", argv[0], err_msg); free(err_msg); return 1; } } SciToken scitoken; char *err_msg = nullptr; if (scitoken_deserialize(token.c_str(), &scitoken, nullptr, &err_msg)) { std::cout << "Failed to deserialize a token: " << err_msg << std::endl; return 1; } scitoken_destroy(scitoken); std::cout << "Token deserialization successful." << std::endl; return 0; } scitokens-cpp-1.3.0/test/000077500000000000000000000000001513647275500152735ustar00rootroot00000000000000scitokens-cpp-1.3.0/test/CMakeLists.txt000066400000000000000000000052371513647275500200420ustar00rootroot00000000000000 add_executable(scitokens-gtest main.cpp) if( NOT SCITOKENS_EXTERNAL_GTEST ) add_dependencies(scitokens-gtest gtest) include_directories("${PROJECT_SOURCE_DIR}/vendor/gtest/googletest/include") endif() if(SCITOKENS_EXTERNAL_GTEST) set(LIBGTEST "gtest") else() set(LIBGTEST "${CMAKE_BINARY_DIR}/external/gtest/src/gtest-build/lib/libgtest.a") endif() target_link_libraries(scitokens-gtest SciTokens "${LIBGTEST}" pthread) add_test( NAME unit COMMAND ${CMAKE_CURRENT_BINARY_DIR}/scitokens-gtest ) # Environment variable configuration test executable add_executable(scitokens-env-test test_env_config.cpp) target_link_libraries(scitokens-env-test SciTokens) # Environment variable configuration test add_test( NAME env_config COMMAND ${CMAKE_CURRENT_BINARY_DIR}/scitokens-env-test ) set_tests_properties(env_config PROPERTIES ENVIRONMENT "SCITOKEN_CONFIG_KEYCACHE_UPDATE_INTERVAL_S=1234;SCITOKEN_CONFIG_KEYCACHE_EXPIRATION_INTERVAL_S=5678;SCITOKEN_CONFIG_KEYCACHE_CACHE_HOME=/tmp/test_scitoken_cache;SCITOKEN_CONFIG_TLS_CA_FILE=/tmp/test_ca.pem" ) # Monitoring unit tests add_executable(scitokens-monitoring-test monitoring_test.cpp) if( NOT SCITOKENS_EXTERNAL_GTEST ) add_dependencies(scitokens-monitoring-test gtest) endif() target_link_libraries(scitokens-monitoring-test SciTokens "${LIBGTEST}" pthread) add_test( NAME monitoring COMMAND ${CMAKE_CURRENT_BINARY_DIR}/scitokens-monitoring-test ) # Integration test executable add_executable(scitokens-integration-test integration_test.cpp) if( NOT SCITOKENS_EXTERNAL_GTEST ) add_dependencies(scitokens-integration-test gtest) endif() target_link_libraries(scitokens-integration-test SciTokens "${LIBGTEST}" pthread) # Integration test fixture - setup add_test( NAME integration::setup COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/integration-test-setup.sh integration ) set_tests_properties(integration::setup PROPERTIES FIXTURES_SETUP integration ENVIRONMENT "BINARY_DIR=${CMAKE_BINARY_DIR};SOURCE_DIR=${PROJECT_SOURCE_DIR}" ) # Integration test fixture - teardown add_test( NAME integration::teardown COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/integration-test-teardown.sh integration ) set_tests_properties(integration::teardown PROPERTIES FIXTURES_CLEANUP integration ENVIRONMENT "BINARY_DIR=${CMAKE_BINARY_DIR}" ) # Integration test add_test( NAME integration::test COMMAND ${CMAKE_CURRENT_BINARY_DIR}/scitokens-integration-test ) set_tests_properties(integration::test PROPERTIES FIXTURES_REQUIRED integration ENVIRONMENT "BINARY_DIR=${CMAKE_BINARY_DIR};CURL_CA_BUNDLE=${CMAKE_BINARY_DIR}/tests/integration/current/ca-cert.pem" ) scitokens-cpp-1.3.0/test/README_INTEGRATION.md000066400000000000000000000110341513647275500204140ustar00rootroot00000000000000# Integration Tests This directory contains integration tests for scitokens-cpp that use a full end-to-end testing environment. ## Overview The integration test framework provides: 1. **TLS Infrastructure**: Automatic generation of CA certificates and server certificates for HTTPS testing 2. **Key Management**: EC key generation and JWKS creation for token signing 3. **Test Server**: Python-based HTTPS server hosting JWKS and supporting OIDC discovery 4. **CTest Fixtures**: Automated setup and teardown using CTest fixture framework ## Architecture The integration tests use a CTest fixture pattern with three components: ### Setup (`integration-test-setup.sh`) The setup script: - Creates a temporary test run directory - Generates TLS certificates (CA and server certificate) - Generates EC P-256 signing keys - Converts public key to JWKS format - Starts a Python HTTPS server on a dynamic port (port 0) - Writes environment configuration to `build/tests/integration/setup.sh` The setup script writes the following information to `setup.sh`: - `ISSUER_URL`: The HTTPS URL of the test issuer (e.g., `https://localhost:12345`) - `SERVER_PID`: Process ID of the running server - `CA_CERT`: Path to the CA certificate for TLS verification - `SIGNING_KEY`: Path to the EC private key - `SIGNING_PUB`: Path to the EC public key - `JWKS_FILE`: Path to the JWKS file ### Test (`integration_test.cpp`) The integration test program: - Reads configuration from `build/tests/integration/setup.sh` - Configures scitokens to trust the test CA certificate - Creates and signs tokens using the test issuer - Verifies tokens using JWKS discovery from the test server - Tests the enforcer functionality with dynamically issued tokens Three test cases are included: 1. **CreateAndSignToken**: Verifies basic token creation and signing 2. **VerifyTokenWithJWKSDiscovery**: Tests token verification using JWKS discovery from the HTTPS server 3. **EnforcerWithDynamicIssuer**: Tests the enforcer API with tokens from the dynamic test issuer ### Teardown (`integration-test-teardown.sh`) The teardown script: - Reads the server PID from `setup.sh` - Gracefully stops the server (SIGTERM, then SIGKILL if needed) - Cleans up the test environment ## Running the Tests ### Build with Integration Tests ```bash mkdir build cd build cmake -DSCITOKENS_BUILD_UNITTESTS=ON .. make ``` ### Run Integration Tests Only ```bash ctest -R integration --output-on-failure ``` ### Run All Tests ```bash ctest --output-on-failure ``` ## Test Server The Python test server (`jwks_server.py`) implements: - **OIDC Discovery**: Serves `.well-known/openid-configuration` - **JWKS Endpoint**: Serves the JWKS at `/oauth2/certs` - **HTTPS Support**: Uses TLS with generated certificates - **Dynamic Port Allocation**: Binds to port 0 for automatic port selection The server is designed to be minimal and focused solely on the requirements for integration testing. ## Requirements - Python 3.6+ (for the test server) - OpenSSL command-line tools - scitokens-generate-jwks (built from this repository) ## Troubleshooting ### Server fails to start Check that Python 3 is available: ```bash python3 --version ``` ### Missing scitokens-generate-jwks If the setup script fails because `scitokens-generate-jwks` is not found, rebuild the project: ```bash make scitokens-generate-jwks ``` ### TLS certificate errors The test automatically generates self-signed certificates. The scitokens library is configured to trust the test CA certificate via the `tls.ca_file` configuration option. ### Server doesn't shut down cleanly The teardown script will wait up to 10 seconds for graceful shutdown, then send SIGKILL. If tests are interrupted, you may need to manually kill the server process: ```bash ps aux | grep jwks_server kill ``` ## Design Decisions 1. **Dynamic Port Allocation**: Using port 0 ensures tests can run in parallel and don't conflict with existing services. 2. **Self-Signed Certificates**: Generated on-the-fly to avoid committing secrets to the repository and to test the full TLS stack. 3. **HTTPS Required**: SciTokens requires HTTPS issuers, so we use HTTPS even for local testing. 4. **CTest Fixtures**: Using CTest's fixture framework ensures proper setup and teardown ordering, even when tests run in parallel. 5. **Detached Server Process**: The server runs detached from the shell to avoid blocking the setup script. 6. **Minimal Python Server**: Using Python's built-in `http.server` keeps dependencies minimal. JWKS generation is handled by the `scitokens-generate-jwks` tool built from this repository. scitokens-cpp-1.3.0/test/integration-test-setup.sh000077500000000000000000000110061513647275500222660ustar00rootroot00000000000000#!/bin/bash # # Setup script for scitokens-cpp integration tests # Creates TLS certificates, keys, JWKS, and launches test server # set -e TEST_NAME=${1:-integration} if [ -z "$BINARY_DIR" ]; then echo "\$BINARY_DIR environment variable is not set; cannot run test" exit 1 fi if [ -z "$SOURCE_DIR" ]; then echo "\$SOURCE_DIR environment variable is not set; cannot run test" exit 1 fi echo "Setting up integration test environment for $TEST_NAME" # Create test directory TEST_DIR="$BINARY_DIR/tests/$TEST_NAME" mkdir -p "$TEST_DIR" RUNDIR=$(mktemp -d -p "$TEST_DIR" test_run.XXXXXXXX) chmod 0755 "$RUNDIR" if [ ! -d "$RUNDIR" ]; then echo "Failed to create test run directory; cannot run test" exit 1 fi echo "Using $RUNDIR as the test run directory" cd "$RUNDIR" # Create link to rundir at fixed location for tests to find if [ -L "$TEST_DIR/current" ]; then rm "$TEST_DIR/current" fi ln -sf "$RUNDIR" "$TEST_DIR/current" ############################ # Generate TLS certificates ############################ echo "Generating TLS CA and host certificate..." # Generate CA key and certificate openssl genrsa -out ca-key.pem 2048 2>/dev/null openssl req -new -x509 -days 365 -key ca-key.pem -out ca-cert.pem \ -subj "/C=US/ST=Test/L=Test/O=SciTokens Test/CN=Test CA" 2>/dev/null # Generate server key and certificate openssl genrsa -out server-key.pem 2048 2>/dev/null openssl req -new -key server-key.pem -out server.csr \ -subj "/C=US/ST=Test/L=Test/O=SciTokens Test/CN=localhost" 2>/dev/null # Create server certificate signed by CA with proper extensions cat > server-cert-ext.cnf </dev/null echo "TLS certificates created" ########################## # Generate signing keys and JWKS ########################## echo "Generating EC signing keys and JWKS..." # Use scitokens-generate-jwks to create keys and JWKS if [ ! -f "$BINARY_DIR/scitokens-generate-jwks" ]; then echo "Error: scitokens-generate-jwks not found in $BINARY_DIR" echo "Please build the project first with: make scitokens-generate-jwks" exit 1 fi "$BINARY_DIR/scitokens-generate-jwks" \ --kid "test-key-1" \ --jwks jwks.json \ --private signing-key.pem \ --public signing-pub.pem if [ ! -f jwks.json ]; then echo "Failed to generate JWKS" exit 1 fi echo "Signing keys and JWKS created" ########################## # Start Python web server ########################## echo "Starting JWKS web server..." # Clean up old server ready file to avoid stale data READY_FILE="$TEST_DIR/server_ready" rm -f "$READY_FILE" # Start server in background, detached from terminal python3 "$SOURCE_DIR/test/jwks_server.py" \ --jwks "$RUNDIR/jwks.json" \ --build-dir "$BINARY_DIR" \ --test-name "$TEST_NAME" \ --cert "$RUNDIR/server-cert.pem" \ --key "$RUNDIR/server-key.pem" \ /dev/null 2>&1 & SERVER_PID=$! echo "Server PID: $SERVER_PID" # Wait for server to be ready TIMEOUT=10 ELAPSED=0 while [ ! -f "$READY_FILE" ]; do sleep 0.05 ELAPSED=$((ELAPSED + 1)) if [ $ELAPSED -ge $((TIMEOUT * 20)) ]; then echo "Timeout waiting for server to start" kill $SERVER_PID 2>/dev/null || true exit 1 fi # Check if server process is still running if ! kill -0 $SERVER_PID 2>/dev/null; then echo "Server process died unexpectedly" exit 1 fi done echo "Server ready" # Read server info from ready file . "$READY_FILE" # Verify we got the PID from the ready file if [ -z "$PID" ]; then echo "Failed to get PID from server ready file" kill $SERVER_PID 2>/dev/null || true exit 1 fi if [ -z "$ISSUER_URL" ]; then echo "Failed to get issuer URL from server" kill $PID 2>/dev/null || true exit 1 fi echo "Issuer URL: $ISSUER_URL" ########################## # Write setup.sh ########################## cat > "$TEST_DIR/setup.sh" </dev/null; then echo "Server process was already stopped" exit 0 fi # Send SIGTERM to server kill "$SERVER_PID" 2>/dev/null || true # Wait for server to stop (with timeout) TIMEOUT=5 ELAPSED=0 while kill -0 "$SERVER_PID" 2>/dev/null; do sleep 0.1 ELAPSED=$((ELAPSED + 1)) if [ $ELAPSED -ge $((TIMEOUT * 10)) ]; then echo "Timeout waiting for server to stop, sending SIGKILL" kill -9 "$SERVER_PID" 2>/dev/null || true sleep 0.1 break fi done # Verify server is stopped (best effort - don't fail if already gone) if kill -0 "$SERVER_PID" 2>/dev/null; then echo "Warning: Server may still be running" else echo "Server stopped successfully" fi # Print server log if it exists SERVER_LOG="$TEST_DIR/server.log" if [ -f "$SERVER_LOG" ]; then echo "" echo "=== Server Log ===" cat "$SERVER_LOG" echo "==================" fi scitokens-cpp-1.3.0/test/integration_test.cpp000066400000000000000000002043061513647275500213660ustar00rootroot00000000000000#include "../src/scitokens.h" #include "test_utils.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifndef PICOJSON_USE_INT64 #define PICOJSON_USE_INT64 #endif #include using scitokens_test::SecureTempDir; namespace { // Helper class to parse monitoring JSON class MonitoringStats { public: struct IssuerStats { uint64_t successful_validations{0}; uint64_t unsuccessful_validations{0}; uint64_t expired_tokens{0}; // Validation started counters uint64_t sync_validations_started{0}; uint64_t async_validations_started{0}; // Duration tracking double sync_total_time_s{0.0}; double async_total_time_s{0.0}; double total_validation_time_s{0.0}; // Key lookup statistics uint64_t successful_key_lookups{0}; uint64_t failed_key_lookups{0}; double failed_key_lookup_time_s{0.0}; // Key refresh statistics uint64_t expired_keys{0}; uint64_t failed_refreshes{0}; uint64_t stale_key_uses{0}; // Background refresh statistics uint64_t background_successful_refreshes{0}; uint64_t background_failed_refreshes{0}; }; struct FailedIssuerLookup { uint64_t count{0}; double total_time_s{0.0}; }; bool parse(const std::string &json) { picojson::value root; std::string err = picojson::parse(root, json); if (!err.empty()) { return false; } if (!root.is()) { return false; } auto &root_obj = root.get(); // Parse issuers issuers_.clear(); auto issuers_it = root_obj.find("issuers"); if (issuers_it != root_obj.end() && issuers_it->second.is()) { auto &issuers_obj = issuers_it->second.get(); for (const auto &issuer_entry : issuers_obj) { if (issuer_entry.second.is()) { IssuerStats stats; auto &stats_obj = issuer_entry.second.get(); auto it = stats_obj.find("successful_validations"); if (it != stats_obj.end() && it->second.is()) { stats.successful_validations = static_cast(it->second.get()); } it = stats_obj.find("unsuccessful_validations"); if (it != stats_obj.end() && it->second.is()) { stats.unsuccessful_validations = static_cast(it->second.get()); } it = stats_obj.find("expired_tokens"); if (it != stats_obj.end() && it->second.is()) { stats.expired_tokens = static_cast(it->second.get()); } // Validation started counters it = stats_obj.find("sync_validations_started"); if (it != stats_obj.end() && it->second.is()) { stats.sync_validations_started = static_cast(it->second.get()); } it = stats_obj.find("async_validations_started"); if (it != stats_obj.end() && it->second.is()) { stats.async_validations_started = static_cast(it->second.get()); } // Duration tracking it = stats_obj.find("sync_total_time_s"); if (it != stats_obj.end() && it->second.is()) { stats.sync_total_time_s = it->second.get(); } it = stats_obj.find("async_total_time_s"); if (it != stats_obj.end() && it->second.is()) { stats.async_total_time_s = it->second.get(); } it = stats_obj.find("total_validation_time_s"); if (it != stats_obj.end() && it->second.is()) { stats.total_validation_time_s = it->second.get(); } // Key lookup statistics it = stats_obj.find("successful_key_lookups"); if (it != stats_obj.end() && it->second.is()) { stats.successful_key_lookups = static_cast(it->second.get()); } it = stats_obj.find("failed_key_lookups"); if (it != stats_obj.end() && it->second.is()) { stats.failed_key_lookups = static_cast(it->second.get()); } it = stats_obj.find("failed_key_lookup_time_s"); if (it != stats_obj.end() && it->second.is()) { stats.failed_key_lookup_time_s = it->second.get(); } // Key refresh statistics it = stats_obj.find("expired_keys"); if (it != stats_obj.end() && it->second.is()) { stats.expired_keys = static_cast(it->second.get()); } it = stats_obj.find("failed_refreshes"); if (it != stats_obj.end() && it->second.is()) { stats.failed_refreshes = static_cast(it->second.get()); } it = stats_obj.find("stale_key_uses"); if (it != stats_obj.end() && it->second.is()) { stats.stale_key_uses = static_cast(it->second.get()); } // Background refresh statistics it = stats_obj.find("background_successful_refreshes"); if (it != stats_obj.end() && it->second.is()) { stats.background_successful_refreshes = static_cast(it->second.get()); } it = stats_obj.find("background_failed_refreshes"); if (it != stats_obj.end() && it->second.is()) { stats.background_failed_refreshes = static_cast(it->second.get()); } issuers_[issuer_entry.first] = stats; } } } // Parse failed issuer lookups (now has count and total_time_s) failed_issuer_lookups_.clear(); auto failed_it = root_obj.find("failed_issuer_lookups"); if (failed_it != root_obj.end() && failed_it->second.is()) { auto &failed_obj = failed_it->second.get(); for (const auto &entry : failed_obj) { if (entry.second.is()) { FailedIssuerLookup lookup; auto &lookup_obj = entry.second.get(); auto it = lookup_obj.find("count"); if (it != lookup_obj.end() && it->second.is()) { lookup.count = static_cast(it->second.get()); } it = lookup_obj.find("total_time_s"); if (it != lookup_obj.end() && it->second.is()) { lookup.total_time_s = it->second.get(); } failed_issuer_lookups_[entry.first] = lookup; } } } return true; } IssuerStats getIssuerStats(const std::string &issuer) const { auto it = issuers_.find(issuer); if (it != issuers_.end()) { return it->second; } return IssuerStats{}; } FailedIssuerLookup getFailedLookup(const std::string &issuer) const { auto it = failed_issuer_lookups_.find(issuer); if (it != failed_issuer_lookups_.end()) { return it->second; } return FailedIssuerLookup{}; } uint64_t getFailedLookupCount(const std::string &issuer) const { return getFailedLookup(issuer).count; } double getFailedLookupTime(const std::string &issuer) const { return getFailedLookup(issuer).total_time_s; } size_t getIssuerCount() const { return issuers_.size(); } size_t getFailedIssuerCount() const { return failed_issuer_lookups_.size(); } private: std::map issuers_; std::map failed_issuer_lookups_; }; // Helper to get current monitoring stats MonitoringStats getCurrentMonitoringStats() { char *json_out = nullptr; char *err_msg = nullptr; MonitoringStats stats; int rv = scitoken_get_monitoring_json(&json_out, &err_msg); if (rv == 0 && json_out) { stats.parse(json_out); free(json_out); } if (err_msg) free(err_msg); return stats; } // Helper to read environment variables from setup.sh class TestEnvironment { public: static TestEnvironment &getInstance() { static TestEnvironment instance; return instance; } bool load() { if (loaded_) return true; const char *binary_dir = getenv("BINARY_DIR"); if (!binary_dir) { std::cerr << "BINARY_DIR not set" << std::endl; return false; } std::string setup_file = std::string(binary_dir) + "/tests/integration/setup.sh"; std::ifstream file(setup_file); if (!file.is_open()) { std::cerr << "Could not open " << setup_file << std::endl; return false; } std::string line; while (std::getline(file, line)) { // Skip comments and empty lines if (line.empty() || line[0] == '#') continue; // Parse KEY=VALUE auto pos = line.find('='); if (pos != std::string::npos) { std::string key = line.substr(0, pos); std::string value = line.substr(pos + 1); vars_[key] = value; } } loaded_ = true; return true; } std::string get(const std::string &key) const { auto it = vars_.find(key); if (it != vars_.end()) { return it->second; } return ""; } private: TestEnvironment() : loaded_(false) {} bool loaded_; std::map vars_; }; class IntegrationTest : public ::testing::Test { protected: void SetUp() override { ASSERT_TRUE(TestEnvironment::getInstance().load()) << "Failed to load test environment"; issuer_url_ = TestEnvironment::getInstance().get("ISSUER_URL"); signing_key_file_ = TestEnvironment::getInstance().get("SIGNING_KEY"); signing_pub_file_ = TestEnvironment::getInstance().get("SIGNING_PUB"); std::string ca_cert_file = TestEnvironment::getInstance().get("CA_CERT"); ASSERT_FALSE(issuer_url_.empty()) << "ISSUER_URL not set"; ASSERT_FALSE(signing_key_file_.empty()) << "SIGNING_KEY not set"; ASSERT_FALSE(signing_pub_file_.empty()) << "SIGNING_PUB not set"; ASSERT_FALSE(ca_cert_file.empty()) << "CA_CERT not set"; // Set the TLS CA file for scitokens to use char *err_msg = nullptr; int rv = scitoken_config_set_str("tls.ca_file", ca_cert_file.c_str(), &err_msg); ASSERT_EQ(rv, 0) << "Failed to set TLS CA file: " << (err_msg ? err_msg : "unknown error"); if (err_msg) free(err_msg); // Load keys std::ifstream priv_ifs(signing_key_file_); ASSERT_TRUE(priv_ifs.is_open()) << "Failed to open " << signing_key_file_; private_key_ = std::string(std::istreambuf_iterator(priv_ifs), std::istreambuf_iterator()); std::ifstream pub_ifs(signing_pub_file_); ASSERT_TRUE(pub_ifs.is_open()) << "Failed to open " << signing_pub_file_; public_key_ = std::string(std::istreambuf_iterator(pub_ifs), std::istreambuf_iterator()); } std::string issuer_url_; std::string signing_key_file_; std::string signing_pub_file_; std::string private_key_; std::string public_key_; }; TEST_F(IntegrationTest, CreateAndSignToken) { char *err_msg = nullptr; // Create a key std::unique_ptr key( scitoken_key_create("test-key-1", "ES256", public_key_.c_str(), private_key_.c_str(), &err_msg), scitoken_key_destroy); ASSERT_TRUE(key.get() != nullptr) << "Failed to create key: " << (err_msg ? err_msg : "unknown error"); if (err_msg) free(err_msg); // Create a token std::unique_ptr token( scitoken_create(key.get()), scitoken_destroy); ASSERT_TRUE(token.get() != nullptr) << "Failed to create token"; // Set issuer auto rv = scitoken_set_claim_string(token.get(), "iss", issuer_url_.c_str(), &err_msg); ASSERT_EQ(rv, 0) << "Failed to set issuer: " << (err_msg ? err_msg : "unknown error"); if (err_msg) free(err_msg); // Set some claims rv = scitoken_set_claim_string(token.get(), "sub", "test-subject", &err_msg); ASSERT_EQ(rv, 0) << "Failed to set subject: " << (err_msg ? err_msg : "unknown error"); if (err_msg) free(err_msg); rv = scitoken_set_claim_string(token.get(), "scope", "read:/test", &err_msg); ASSERT_EQ(rv, 0) << "Failed to set scope: " << (err_msg ? err_msg : "unknown error"); if (err_msg) free(err_msg); // Set lifetime scitoken_set_lifetime(token.get(), 3600); // Serialize the token char *token_value = nullptr; rv = scitoken_serialize(token.get(), &token_value, &err_msg); ASSERT_EQ(rv, 0) << "Failed to serialize token: " << (err_msg ? err_msg : "unknown error"); if (err_msg) free(err_msg); ASSERT_TRUE(token_value != nullptr); std::unique_ptr token_value_ptr(token_value, free); EXPECT_GT(strlen(token_value), 50) << "Token seems too short"; std::cout << "Created token: " << token_value << std::endl; } TEST_F(IntegrationTest, VerifyTokenWithJWKSDiscovery) { char *err_msg = nullptr; // Create a key std::unique_ptr key( scitoken_key_create("test-key-1", "ES256", public_key_.c_str(), private_key_.c_str(), &err_msg), scitoken_key_destroy); ASSERT_TRUE(key.get() != nullptr); if (err_msg) { free(err_msg); err_msg = nullptr; } // Create and sign a token std::unique_ptr token( scitoken_create(key.get()), scitoken_destroy); ASSERT_TRUE(token.get() != nullptr); auto rv = scitoken_set_claim_string(token.get(), "iss", issuer_url_.c_str(), &err_msg); ASSERT_EQ(rv, 0); if (err_msg) { free(err_msg); err_msg = nullptr; } rv = scitoken_set_claim_string(token.get(), "sub", "test-subject", &err_msg); ASSERT_EQ(rv, 0); if (err_msg) { free(err_msg); err_msg = nullptr; } rv = scitoken_set_claim_string(token.get(), "scope", "read:/test", &err_msg); ASSERT_EQ(rv, 0); if (err_msg) { free(err_msg); err_msg = nullptr; } scitoken_set_lifetime(token.get(), 3600); char *token_value = nullptr; rv = scitoken_serialize(token.get(), &token_value, &err_msg); ASSERT_EQ(rv, 0); if (err_msg) { free(err_msg); err_msg = nullptr; } std::unique_ptr token_value_ptr(token_value, free); // Now verify the token using JWKS discovery std::unique_ptr verify_token( scitoken_create(nullptr), scitoken_destroy); ASSERT_TRUE(verify_token.get() != nullptr); // This should fetch the JWKS from the server via discovery rv = scitoken_deserialize_v2(token_value, verify_token.get(), nullptr, &err_msg); ASSERT_EQ(rv, 0) << "Failed to verify token: " << (err_msg ? err_msg : "unknown error"); if (err_msg) { free(err_msg); err_msg = nullptr; } // Verify we can read back the claims char *value = nullptr; rv = scitoken_get_claim_string(verify_token.get(), "iss", &value, &err_msg); ASSERT_EQ(rv, 0); ASSERT_TRUE(value != nullptr); std::unique_ptr value_ptr(value, free); EXPECT_EQ(std::string(value), issuer_url_); value_ptr.reset(); rv = scitoken_get_claim_string(verify_token.get(), "sub", &value, &err_msg); ASSERT_EQ(rv, 0); ASSERT_TRUE(value != nullptr); value_ptr.reset(value); EXPECT_STREQ(value, "test-subject"); value_ptr.reset(); rv = scitoken_get_claim_string(verify_token.get(), "scope", &value, &err_msg); ASSERT_EQ(rv, 0); ASSERT_TRUE(value != nullptr); value_ptr.reset(value); EXPECT_STREQ(value, "read:/test"); } TEST_F(IntegrationTest, EnforcerWithDynamicIssuer) { char *err_msg = nullptr; // Create a key and token std::unique_ptr key( scitoken_key_create("test-key-1", "ES256", public_key_.c_str(), private_key_.c_str(), &err_msg), scitoken_key_destroy); ASSERT_TRUE(key.get() != nullptr); if (err_msg) { free(err_msg); err_msg = nullptr; } std::unique_ptr token( scitoken_create(key.get()), scitoken_destroy); ASSERT_TRUE(token.get() != nullptr); auto rv = scitoken_set_claim_string(token.get(), "iss", issuer_url_.c_str(), &err_msg); ASSERT_EQ(rv, 0); if (err_msg) { free(err_msg); err_msg = nullptr; } rv = scitoken_set_claim_string(token.get(), "aud", "https://test.example.com", &err_msg); ASSERT_EQ(rv, 0); if (err_msg) { free(err_msg); err_msg = nullptr; } rv = scitoken_set_claim_string(token.get(), "scope", "read:/data", &err_msg); ASSERT_EQ(rv, 0); if (err_msg) { free(err_msg); err_msg = nullptr; } rv = scitoken_set_claim_string(token.get(), "ver", "scitoken:2.0", &err_msg); ASSERT_EQ(rv, 0); if (err_msg) { free(err_msg); err_msg = nullptr; } scitoken_set_lifetime(token.get(), 3600); char *token_value = nullptr; rv = scitoken_serialize(token.get(), &token_value, &err_msg); ASSERT_EQ(rv, 0); if (err_msg) { free(err_msg); err_msg = nullptr; } std::unique_ptr token_value_ptr(token_value, free); // Deserialize for verification std::unique_ptr verify_token( scitoken_create(nullptr), scitoken_destroy); ASSERT_TRUE(verify_token.get() != nullptr); rv = scitoken_deserialize_v2(token_value, verify_token.get(), nullptr, &err_msg); ASSERT_EQ(rv, 0); if (err_msg) { free(err_msg); err_msg = nullptr; } // Create enforcer const char *audiences[] = {"https://test.example.com", nullptr}; auto enforcer = enforcer_create(issuer_url_.c_str(), audiences, &err_msg); ASSERT_TRUE(enforcer != nullptr); if (err_msg) { free(err_msg); err_msg = nullptr; } // Test with valid ACL Acl acl; acl.authz = "read"; acl.resource = "/data/file.txt"; rv = enforcer_test(enforcer, verify_token.get(), &acl, &err_msg); EXPECT_EQ(rv, 0) << "Enforcer should allow read on /data/file.txt"; if (err_msg) { free(err_msg); err_msg = nullptr; } // Test with invalid ACL (wrong authz) acl.authz = "write"; acl.resource = "/data/file.txt"; rv = enforcer_test(enforcer, verify_token.get(), &acl, &err_msg); EXPECT_NE(rv, 0) << "Enforcer should deny write access"; if (err_msg) { free(err_msg); err_msg = nullptr; } enforcer_destroy(enforcer); } // ============================================================================= // Monitoring API Integration Tests // ============================================================================= TEST_F(IntegrationTest, MonitoringCountersIncrease) { char *err_msg = nullptr; // Reset monitoring stats scitoken_reset_monitoring_stats(&err_msg); if (err_msg) { free(err_msg); err_msg = nullptr; } // Get initial stats auto initial_stats = getCurrentMonitoringStats(); auto initial_issuer_stats = initial_stats.getIssuerStats(issuer_url_); // Create and verify a valid token std::unique_ptr key( scitoken_key_create("test-key-1", "ES256", public_key_.c_str(), private_key_.c_str(), &err_msg), scitoken_key_destroy); ASSERT_TRUE(key.get() != nullptr); if (err_msg) { free(err_msg); err_msg = nullptr; } std::unique_ptr token( scitoken_create(key.get()), scitoken_destroy); ASSERT_TRUE(token.get() != nullptr); auto rv = scitoken_set_claim_string(token.get(), "iss", issuer_url_.c_str(), &err_msg); ASSERT_EQ(rv, 0); if (err_msg) { free(err_msg); err_msg = nullptr; } rv = scitoken_set_claim_string(token.get(), "sub", "test-subject", &err_msg); ASSERT_EQ(rv, 0); if (err_msg) { free(err_msg); err_msg = nullptr; } scitoken_set_lifetime(token.get(), 3600); char *token_value = nullptr; rv = scitoken_serialize(token.get(), &token_value, &err_msg); ASSERT_EQ(rv, 0); if (err_msg) { free(err_msg); err_msg = nullptr; } std::unique_ptr token_value_ptr(token_value, free); // Verify the token (should increment successful_validations) std::unique_ptr verify_token( scitoken_create(nullptr), scitoken_destroy); ASSERT_TRUE(verify_token.get() != nullptr); rv = scitoken_deserialize_v2(token_value, verify_token.get(), nullptr, &err_msg); ASSERT_EQ(rv, 0); if (err_msg) { free(err_msg); err_msg = nullptr; } // Check that counters increased auto after_stats = getCurrentMonitoringStats(); auto after_issuer_stats = after_stats.getIssuerStats(issuer_url_); EXPECT_GT(after_issuer_stats.successful_validations, initial_issuer_stats.successful_validations) << "successful_validations should have increased"; // Duration should also have increased EXPECT_GT(after_issuer_stats.total_validation_time_s, initial_issuer_stats.total_validation_time_s) << "total_validation_time_s should have increased"; std::cout << "After successful validation:" << std::endl; std::cout << " successful_validations: " << after_issuer_stats.successful_validations << std::endl; std::cout << " total_validation_time_s: " << after_issuer_stats.total_validation_time_s << std::endl; } TEST_F(IntegrationTest, MonitoringFailedIssuerLookup404) { char *err_msg = nullptr; // Reset monitoring stats scitoken_reset_monitoring_stats(&err_msg); if (err_msg) { free(err_msg); err_msg = nullptr; } // Parse the issuer URL to construct a 404 path // The server returns 404 for paths other than // /.well-known/openid-configuration We need to use the same host but a path // that doesn't exist std::string issuer_404 = issuer_url_ + "/nonexistent-path"; // Create a token with issuer that will get 404 std::unique_ptr key( scitoken_key_create("test-key-1", "ES256", public_key_.c_str(), private_key_.c_str(), &err_msg), scitoken_key_destroy); ASSERT_TRUE(key.get() != nullptr); if (err_msg) { free(err_msg); err_msg = nullptr; } std::unique_ptr token( scitoken_create(key.get()), scitoken_destroy); ASSERT_TRUE(token.get() != nullptr); // Use issuer URL that will cause 404 on metadata lookup auto rv = scitoken_set_claim_string(token.get(), "iss", issuer_404.c_str(), &err_msg); ASSERT_EQ(rv, 0); if (err_msg) { free(err_msg); err_msg = nullptr; } scitoken_set_lifetime(token.get(), 3600); char *token_value = nullptr; rv = scitoken_serialize(token.get(), &token_value, &err_msg); ASSERT_EQ(rv, 0); if (err_msg) { free(err_msg); err_msg = nullptr; } std::unique_ptr token_value_ptr(token_value, free); // Try to verify - should fail with 404 std::unique_ptr verify_token( scitoken_create(nullptr), scitoken_destroy); ASSERT_TRUE(verify_token.get() != nullptr); rv = scitoken_deserialize_v2(token_value, verify_token.get(), nullptr, &err_msg); EXPECT_NE(rv, 0) << "Verification should fail for 404 issuer"; if (err_msg) { std::cout << "Expected error: " << err_msg << std::endl; free(err_msg); err_msg = nullptr; } // Check that failed issuer lookup was recorded auto stats = getCurrentMonitoringStats(); auto issuer_stats = stats.getIssuerStats(issuer_404); EXPECT_GT(issuer_stats.unsuccessful_validations, 0u) << "unsuccessful_validations should have increased for 404 issuer"; std::cout << "After 404 response:" << std::endl; std::cout << " unsuccessful_validations: " << issuer_stats.unsuccessful_validations << std::endl; } TEST_F(IntegrationTest, MonitoringFailedDNSLookup) { char *err_msg = nullptr; // Reset monitoring stats scitoken_reset_monitoring_stats(&err_msg); if (err_msg) { free(err_msg); err_msg = nullptr; } // Use an issuer with a hostname that won't resolve std::string dns_fail_issuer = "https://this-hostname-does-not-exist-12345.invalid"; // Create a token with issuer that will fail DNS lookup std::unique_ptr key( scitoken_key_create("test-key-1", "ES256", public_key_.c_str(), private_key_.c_str(), &err_msg), scitoken_key_destroy); ASSERT_TRUE(key.get() != nullptr); if (err_msg) { free(err_msg); err_msg = nullptr; } std::unique_ptr token( scitoken_create(key.get()), scitoken_destroy); ASSERT_TRUE(token.get() != nullptr); auto rv = scitoken_set_claim_string(token.get(), "iss", dns_fail_issuer.c_str(), &err_msg); ASSERT_EQ(rv, 0); if (err_msg) { free(err_msg); err_msg = nullptr; } scitoken_set_lifetime(token.get(), 3600); char *token_value = nullptr; rv = scitoken_serialize(token.get(), &token_value, &err_msg); ASSERT_EQ(rv, 0); if (err_msg) { free(err_msg); err_msg = nullptr; } std::unique_ptr token_value_ptr(token_value, free); // Try to verify - should fail with DNS error std::unique_ptr verify_token( scitoken_create(nullptr), scitoken_destroy); ASSERT_TRUE(verify_token.get() != nullptr); rv = scitoken_deserialize_v2(token_value, verify_token.get(), nullptr, &err_msg); EXPECT_NE(rv, 0) << "Verification should fail for DNS lookup failure"; if (err_msg) { std::cout << "Expected error (DNS): " << err_msg << std::endl; free(err_msg); err_msg = nullptr; } // Check that failed issuer lookup was recorded auto stats = getCurrentMonitoringStats(); auto issuer_stats = stats.getIssuerStats(dns_fail_issuer); EXPECT_GT(issuer_stats.unsuccessful_validations, 0u) << "unsuccessful_validations should have increased for DNS failure"; std::cout << "After DNS failure:" << std::endl; std::cout << " unsuccessful_validations: " << issuer_stats.unsuccessful_validations << std::endl; } TEST_F(IntegrationTest, MonitoringFailedTCPConnection) { char *err_msg = nullptr; // Reset monitoring stats scitoken_reset_monitoring_stats(&err_msg); if (err_msg) { free(err_msg); err_msg = nullptr; } // Use localhost with a privileged port (< 1024) that won't have a server // Port 1 is typically not used and requires root to bind std::string tcp_fail_issuer = "https://localhost:1"; // Create a token with issuer that will fail TCP connection std::unique_ptr key( scitoken_key_create("test-key-1", "ES256", public_key_.c_str(), private_key_.c_str(), &err_msg), scitoken_key_destroy); ASSERT_TRUE(key.get() != nullptr); if (err_msg) { free(err_msg); err_msg = nullptr; } std::unique_ptr token( scitoken_create(key.get()), scitoken_destroy); ASSERT_TRUE(token.get() != nullptr); auto rv = scitoken_set_claim_string(token.get(), "iss", tcp_fail_issuer.c_str(), &err_msg); ASSERT_EQ(rv, 0); if (err_msg) { free(err_msg); err_msg = nullptr; } scitoken_set_lifetime(token.get(), 3600); char *token_value = nullptr; rv = scitoken_serialize(token.get(), &token_value, &err_msg); ASSERT_EQ(rv, 0); if (err_msg) { free(err_msg); err_msg = nullptr; } std::unique_ptr token_value_ptr(token_value, free); // Try to verify - should fail with connection refused std::unique_ptr verify_token( scitoken_create(nullptr), scitoken_destroy); ASSERT_TRUE(verify_token.get() != nullptr); rv = scitoken_deserialize_v2(token_value, verify_token.get(), nullptr, &err_msg); EXPECT_NE(rv, 0) << "Verification should fail for TCP connection failure"; if (err_msg) { std::cout << "Expected error (TCP): " << err_msg << std::endl; free(err_msg); err_msg = nullptr; } // Check that failed issuer lookup was recorded auto stats = getCurrentMonitoringStats(); auto issuer_stats = stats.getIssuerStats(tcp_fail_issuer); EXPECT_GT(issuer_stats.unsuccessful_validations, 0u) << "unsuccessful_validations should have increased for TCP failure"; std::cout << "After TCP connection failure:" << std::endl; std::cout << " unsuccessful_validations: " << issuer_stats.unsuccessful_validations << std::endl; } TEST_F(IntegrationTest, MonitoringDurationTracking) { char *err_msg = nullptr; // Reset monitoring stats scitoken_reset_monitoring_stats(&err_msg); if (err_msg) { free(err_msg); err_msg = nullptr; } // Perform multiple validations and check duration increases for (int i = 0; i < 3; i++) { std::unique_ptr key( scitoken_key_create("test-key-1", "ES256", public_key_.c_str(), private_key_.c_str(), &err_msg), scitoken_key_destroy); ASSERT_TRUE(key.get() != nullptr); if (err_msg) { free(err_msg); err_msg = nullptr; } std::unique_ptr token( scitoken_create(key.get()), scitoken_destroy); ASSERT_TRUE(token.get() != nullptr); auto rv = scitoken_set_claim_string(token.get(), "iss", issuer_url_.c_str(), &err_msg); ASSERT_EQ(rv, 0); if (err_msg) { free(err_msg); err_msg = nullptr; } rv = scitoken_set_claim_string(token.get(), "sub", "test-subject", &err_msg); ASSERT_EQ(rv, 0); if (err_msg) { free(err_msg); err_msg = nullptr; } scitoken_set_lifetime(token.get(), 3600); char *token_value = nullptr; rv = scitoken_serialize(token.get(), &token_value, &err_msg); ASSERT_EQ(rv, 0); if (err_msg) { free(err_msg); err_msg = nullptr; } std::unique_ptr token_value_ptr(token_value, free); std::unique_ptr verify_token( scitoken_create(nullptr), scitoken_destroy); ASSERT_TRUE(verify_token.get() != nullptr); rv = scitoken_deserialize_v2(token_value, verify_token.get(), nullptr, &err_msg); ASSERT_EQ(rv, 0); if (err_msg) { free(err_msg); err_msg = nullptr; } } // Check final stats auto stats = getCurrentMonitoringStats(); auto issuer_stats = stats.getIssuerStats(issuer_url_); EXPECT_GE(issuer_stats.successful_validations, 3u) << "Should have at least 3 successful validations"; EXPECT_GT(issuer_stats.total_validation_time_s, 0.0) << "total_validation_time_s should be positive"; std::cout << "After multiple validations:" << std::endl; std::cout << " successful_validations: " << issuer_stats.successful_validations << std::endl; std::cout << " total_validation_time_s: " << issuer_stats.total_validation_time_s << std::endl; } // Test monitoring file output during token verification TEST_F(IntegrationTest, MonitoringFileOutput) { char *err_msg = nullptr; // Create a secure temp directory for the monitoring file SecureTempDir temp_dir("monitoring_test_"); ASSERT_TRUE(temp_dir.valid()) << "Failed to create temp directory"; // Set up a test file path and zero interval for immediate write std::string test_file = temp_dir.path() + "/monitoring.json"; scitoken_config_set_str("monitoring.file", test_file.c_str(), &err_msg); scitoken_config_set_int("monitoring.file_interval_s", 0, &err_msg); // Reset monitoring stats scitoken_reset_monitoring_stats(&err_msg); // Create and verify a token (this should trigger file write) // Use test-key-1 to match the key ID in the JWKS server SciTokenKey key = scitoken_key_create("test-key-1", "ES256", public_key_.c_str(), private_key_.c_str(), &err_msg); ASSERT_TRUE(key != nullptr); std::unique_ptr key_ptr( key, scitoken_key_destroy); SciToken token = scitoken_create(key); ASSERT_TRUE(token != nullptr); std::unique_ptr token_ptr( token, scitoken_destroy); scitoken_set_claim_string(token, "iss", issuer_url_.c_str(), &err_msg); scitoken_set_claim_string(token, "sub", "test-user", &err_msg); scitoken_set_claim_string(token, "scope", "read:/test", &err_msg); char *token_value = nullptr; int rv = scitoken_serialize(token, &token_value, &err_msg); ASSERT_EQ(rv, 0); std::unique_ptr token_value_ptr(token_value, free); // Verify the token - this should trigger monitoring file write std::unique_ptr verify_token( scitoken_create(nullptr), scitoken_destroy); ASSERT_TRUE(verify_token.get() != nullptr); rv = scitoken_deserialize_v2(token_value, verify_token.get(), nullptr, &err_msg); if (rv != 0 && err_msg) { std::cerr << "Token verification error: " << err_msg << std::endl; } ASSERT_EQ(rv, 0) << "Token verification should succeed"; if (err_msg) { free(err_msg); err_msg = nullptr; } // Check that the monitoring file was created std::ifstream file(test_file); EXPECT_TRUE(file.good()) << "Monitoring file should have been created at " << test_file; if (file.good()) { // Read and parse the file std::stringstream buffer; buffer << file.rdbuf(); std::string content = buffer.str(); EXPECT_FALSE(content.empty()) << "Monitoring file should not be empty"; // Try to parse it as JSON picojson::value root; std::string parse_err = picojson::parse(root, content); EXPECT_TRUE(parse_err.empty()) << "Monitoring file should contain valid JSON: " << parse_err; if (parse_err.empty()) { // Verify it has the expected structure EXPECT_TRUE(root.is()); auto &root_obj = root.get(); EXPECT_TRUE(root_obj.find("issuers") != root_obj.end()) << "Monitoring JSON should have 'issuers' key"; } std::cout << "Monitoring file content:" << std::endl; std::cout << content << std::endl; } // Clean up - disable monitoring file scitoken_config_set_str("monitoring.file", "", &err_msg); scitoken_config_set_int("monitoring.file_interval_s", 60, &err_msg); // temp_dir destructor will clean up the directory and file } // ============================================================================= // Background JWKS Refresh Test // ============================================================================= TEST_F(IntegrationTest, BackgroundRefreshTest) { char *err_msg = nullptr; // Reset monitoring stats to get a clean baseline scitoken_reset_monitoring_stats(&err_msg); if (err_msg) { free(err_msg); err_msg = nullptr; } // Set smaller intervals for testing (1 second refresh interval, 2 seconds // threshold) int rv = scitoken_config_set_int("keycache.refresh_interval_ms", 1000, &err_msg); ASSERT_EQ(rv, 0) << "Failed to set refresh interval: " << (err_msg ? err_msg : "unknown error"); if (err_msg) { free(err_msg); err_msg = nullptr; } rv = scitoken_config_set_int("keycache.refresh_threshold_ms", 2000, &err_msg); ASSERT_EQ(rv, 0) << "Failed to set refresh threshold: " << (err_msg ? err_msg : "unknown error"); if (err_msg) { free(err_msg); err_msg = nullptr; } // Set update interval to 1 second BEFORE first verification so the // cache entry will have next_update just 1 second in the future. // This ensures the background thread can refresh within the test window. rv = scitoken_config_set_int("keycache.update_interval_s", 1, &err_msg); ASSERT_EQ(rv, 0); if (err_msg) { free(err_msg); err_msg = nullptr; } // Create a key and token std::unique_ptr key( scitoken_key_create("test-key-1", "ES256", public_key_.c_str(), private_key_.c_str(), &err_msg), scitoken_key_destroy); ASSERT_TRUE(key.get() != nullptr); if (err_msg) { free(err_msg); err_msg = nullptr; } std::unique_ptr token( scitoken_create(key.get()), scitoken_destroy); ASSERT_TRUE(token.get() != nullptr); rv = scitoken_set_claim_string(token.get(), "iss", issuer_url_.c_str(), &err_msg); ASSERT_EQ(rv, 0); if (err_msg) { free(err_msg); err_msg = nullptr; } rv = scitoken_set_claim_string(token.get(), "sub", "test-subject", &err_msg); ASSERT_EQ(rv, 0); if (err_msg) { free(err_msg); err_msg = nullptr; } rv = scitoken_set_claim_string(token.get(), "scope", "read:/test", &err_msg); ASSERT_EQ(rv, 0); if (err_msg) { free(err_msg); err_msg = nullptr; } scitoken_set_lifetime(token.get(), 3600); char *token_value = nullptr; rv = scitoken_serialize(token.get(), &token_value, &err_msg); ASSERT_EQ(rv, 0); if (err_msg) { free(err_msg); err_msg = nullptr; } std::unique_ptr token_value_ptr(token_value, free); // First verification - this will fetch JWKS and track the issuer std::unique_ptr verify_token( scitoken_create(nullptr), scitoken_destroy); ASSERT_TRUE(verify_token.get() != nullptr); rv = scitoken_deserialize_v2(token_value, verify_token.get(), nullptr, &err_msg); ASSERT_EQ(rv, 0) << "Failed to verify token: " << (err_msg ? err_msg : "unknown error"); if (err_msg) { free(err_msg); err_msg = nullptr; } // Get the current JWKS to verify it exists char *jwks_before = nullptr; rv = keycache_get_cached_jwks(issuer_url_.c_str(), &jwks_before, &err_msg); ASSERT_EQ(rv, 0) << "Failed to get cached JWKS: " << (err_msg ? err_msg : "unknown error"); ASSERT_TRUE(jwks_before != nullptr); if (err_msg) { free(err_msg); err_msg = nullptr; } std::cout << "Initial JWKS fetched successfully" << std::endl; // Re-set the JWKS to force a fresh cache entry with the current // update_interval (1 second). This ensures next_update is just 1 second // in the future so the background thread will refresh it. rv = keycache_set_jwks(issuer_url_.c_str(), jwks_before, &err_msg); ASSERT_EQ(rv, 0) << "Failed to set JWKS: " << (err_msg ? err_msg : "unknown error"); free(jwks_before); if (err_msg) { free(err_msg); err_msg = nullptr; } std::cout << "JWKS re-set with 1-second update interval" << std::endl; // Get monitoring stats before background refresh auto before_stats = getCurrentMonitoringStats(); auto before_issuer_stats = before_stats.getIssuerStats(issuer_url_); std::cout << "Before background refresh:" << std::endl; std::cout << " background_successful_refreshes: " << before_issuer_stats.background_successful_refreshes << std::endl; std::cout << " background_failed_refreshes: " << before_issuer_stats.background_failed_refreshes << std::endl; // Enable background refresh rv = keycache_set_background_refresh(1, &err_msg); ASSERT_EQ(rv, 0) << "Failed to enable background refresh: " << (err_msg ? err_msg : "unknown error"); if (err_msg) { free(err_msg); err_msg = nullptr; } std::cout << "Background refresh enabled" << std::endl; // Wait for background refresh to trigger (threshold is 2 seconds, interval // is 1 second) We need to wait at least 3 seconds: 1s for next_update to be // within threshold + 2s for detection Note: Using sleep() is acceptable for // integration tests as we're verifying real-time behavior of the background // thread against an actual HTTPS server std::cout << "Waiting 4 seconds for background refresh..." << std::endl; sleep(4); // Stop background refresh rv = keycache_stop_background_refresh(&err_msg); ASSERT_EQ(rv, 0) << "Failed to stop background refresh: " << (err_msg ? err_msg : "unknown error"); if (err_msg) { free(err_msg); err_msg = nullptr; } std::cout << "Background refresh stopped successfully" << std::endl; // Verify we can still access the JWKS char *jwks_after = nullptr; rv = keycache_get_cached_jwks(issuer_url_.c_str(), &jwks_after, &err_msg); ASSERT_EQ(rv, 0) << "Failed to get cached JWKS after background refresh: " << (err_msg ? err_msg : "unknown error"); ASSERT_TRUE(jwks_after != nullptr); std::unique_ptr jwks_after_ptr(jwks_after, free); if (err_msg) { free(err_msg); err_msg = nullptr; } // Verify that background refresh statistics increased for our issuer auto after_stats = getCurrentMonitoringStats(); auto after_issuer_stats = after_stats.getIssuerStats(issuer_url_); std::cout << "After background refresh:" << std::endl; std::cout << " background_successful_refreshes: " << after_issuer_stats.background_successful_refreshes << std::endl; std::cout << " background_failed_refreshes: " << after_issuer_stats.background_failed_refreshes << std::endl; // The background thread should have performed at least one refresh // for our issuer (either successful or failed) uint64_t total_background_refreshes = after_issuer_stats.background_successful_refreshes + after_issuer_stats.background_failed_refreshes; uint64_t before_total = before_issuer_stats.background_successful_refreshes + before_issuer_stats.background_failed_refreshes; EXPECT_GT(total_background_refreshes, before_total) << "Background refresh thread should have performed at least one " "refresh attempt for our issuer"; std::cout << "Test completed successfully" << std::endl; } // Test that concurrent threads validating tokens from the same new issuer // all succeed even when there's no pre-existing cache entry. // Note: The per-issuer lock prevents the worst thundering herd scenarios // by serializing DB checks after initial discovery, but the current // implementation may still make multiple web requests if the fetch is async. TEST_F(IntegrationTest, ConcurrentNewIssuerLookup) { char *err_msg = nullptr; // Use a unique secure cache directory to ensure no cached keys exist // This forces the code path where keys must be fetched from the server SecureTempDir unique_cache("concurrent_test_"); ASSERT_TRUE(unique_cache.valid()) << "Failed to create temp cache directory"; int rv = scitoken_config_set_str("keycache.cache_home", unique_cache.path().c_str(), &err_msg); ASSERT_EQ(rv, 0) << "Failed to set cache_home: " << (err_msg ? err_msg : "unknown"); if (err_msg) { free(err_msg); err_msg = nullptr; } // Reset monitoring stats before the test rv = scitoken_reset_monitoring_stats(&err_msg); ASSERT_EQ(rv, 0) << "Failed to reset monitoring stats"; if (err_msg) { free(err_msg); err_msg = nullptr; } // Create a token with the test issuer std::unique_ptr key( scitoken_key_create("test-key-1", "ES256", public_key_.c_str(), private_key_.c_str(), &err_msg), scitoken_key_destroy); ASSERT_TRUE(key.get() != nullptr); if (err_msg) { free(err_msg); err_msg = nullptr; } std::unique_ptr token( scitoken_create(key.get()), scitoken_destroy); ASSERT_TRUE(token.get() != nullptr); rv = scitoken_set_claim_string(token.get(), "iss", issuer_url_.c_str(), &err_msg); ASSERT_EQ(rv, 0); if (err_msg) { free(err_msg); err_msg = nullptr; } scitoken_set_lifetime(token.get(), 300); char *token_value = nullptr; rv = scitoken_serialize(token.get(), &token_value, &err_msg); ASSERT_EQ(rv, 0); std::string token_str(token_value); free(token_value); if (err_msg) { free(err_msg); err_msg = nullptr; } // Get initial counts before the concurrent test auto stats_before = getCurrentMonitoringStats(); auto initial_successful_validations = stats_before.getIssuerStats(issuer_url_).successful_validations; auto initial_expired_keys = stats_before.getIssuerStats(issuer_url_).expired_keys; auto initial_key_lookups = stats_before.getIssuerStats(issuer_url_).successful_key_lookups; std::cout << "Using unique cache directory: " << unique_cache.path() << std::endl; std::cout << "Initial successful_validations: " << initial_successful_validations << std::endl; std::cout << "Initial expired_keys: " << initial_expired_keys << std::endl; std::cout << "Initial successful_key_lookups: " << initial_key_lookups << std::endl; // Launch multiple threads to concurrently validate the same token const int NUM_THREADS = 10; std::vector threads; std::atomic success_count{0}; std::atomic failure_count{0}; // Use a barrier to synchronize thread start std::atomic start_flag{false}; for (int i = 0; i < NUM_THREADS; i++) { threads.emplace_back([&]() { // Wait for all threads to be ready while (!start_flag.load()) { std::this_thread::yield(); } char *thread_err = nullptr; std::unique_ptr verify_token( scitoken_create(nullptr), scitoken_destroy); int result = scitoken_deserialize_v2( token_str.c_str(), verify_token.get(), nullptr, &thread_err); if (result == 0) { success_count++; } else { failure_count++; if (thread_err) { std::cerr << "Thread validation error: " << thread_err << std::endl; } } if (thread_err) free(thread_err); }); } // Signal all threads to start simultaneously start_flag.store(true); // Wait for all threads to complete for (auto &t : threads) { t.join(); } std::cout << "Threads completed - success: " << success_count.load() << ", failure: " << failure_count.load() << std::endl; // All threads should have successfully validated // This proves the per-issuer locking and caching mechanisms work correctly // even under concurrent load with an empty cache EXPECT_EQ(success_count.load(), NUM_THREADS) << "All threads should validate successfully"; // Check monitoring stats to verify the code paths were exercised auto stats_after = getCurrentMonitoringStats(); auto issuer_stats = stats_after.getIssuerStats(issuer_url_); auto new_expired_keys = issuer_stats.expired_keys - initial_expired_keys; auto new_key_lookups = issuer_stats.successful_key_lookups - initial_key_lookups; std::cout << "Final stats for issuer:" << std::endl; std::cout << " successful_validations: " << issuer_stats.successful_validations << std::endl; std::cout << " expired_keys: " << issuer_stats.expired_keys << " (new: " << new_expired_keys << ")" << std::endl; std::cout << " successful_key_lookups: " << issuer_stats.successful_key_lookups << " (new: " << new_key_lookups << ")" << std::endl; // The per-issuer lock should ensure only ONE thread fetches keys from web. // All other threads should wait for the lock, then find keys in the cache. // This is the key assertion that proves the thundering herd prevention // works. EXPECT_EQ(new_key_lookups, 1u) << "Per-issuer lock should ensure only ONE web fetch for " << NUM_THREADS << " concurrent requests"; // The expired_keys counter tracks entries into the "no cached keys" path. // With a fresh cache, all threads should hit this path because they all // check the DB before acquiring the per-issuer lock. EXPECT_EQ(new_expired_keys, static_cast(NUM_THREADS)) << "All threads should enter the expired_keys code path"; // unique_cache destructor will clean up the temporary cache directory } // Stress test: repeatedly deserialize a valid token across multiple threads // for a fixed duration and verify monitoring counters match actual counts TEST_F(IntegrationTest, StressTestValidToken) { char *err_msg = nullptr; // Use a unique secure cache directory to ensure no cached keys exist from // prior tests. This forces fresh key lookup and prevents background refresh // interference. SecureTempDir unique_cache("stress_valid_"); ASSERT_TRUE(unique_cache.valid()) << "Failed to create temp cache directory"; int rv = scitoken_config_set_str("keycache.cache_home", unique_cache.path().c_str(), &err_msg); ASSERT_EQ(rv, 0) << "Failed to set cache_home: " << (err_msg ? err_msg : "unknown"); if (err_msg) { free(err_msg); err_msg = nullptr; } // Ensure background refresh is disabled so it doesn't interfere rv = keycache_stop_background_refresh(&err_msg); ASSERT_EQ(rv, 0) << "Failed to stop background refresh"; if (err_msg) { free(err_msg); err_msg = nullptr; } // Reset update interval to default (600 seconds) - BackgroundRefreshTest // may have set it to 1 second rv = scitoken_config_set_int("keycache.update_interval_s", 600, &err_msg); ASSERT_EQ(rv, 0) << "Failed to set update_interval_s"; if (err_msg) { free(err_msg); err_msg = nullptr; } // Reset monitoring stats before the test rv = scitoken_reset_monitoring_stats(&err_msg); ASSERT_EQ(rv, 0) << "Failed to reset monitoring stats"; if (err_msg) { free(err_msg); err_msg = nullptr; } // Create a valid token std::unique_ptr key( scitoken_key_create("test-key-1", "ES256", public_key_.c_str(), private_key_.c_str(), &err_msg), scitoken_key_destroy); ASSERT_TRUE(key.get() != nullptr); if (err_msg) { free(err_msg); err_msg = nullptr; } std::unique_ptr token( scitoken_create(key.get()), scitoken_destroy); ASSERT_TRUE(token.get() != nullptr); rv = scitoken_set_claim_string(token.get(), "iss", issuer_url_.c_str(), &err_msg); ASSERT_EQ(rv, 0); if (err_msg) { free(err_msg); err_msg = nullptr; } rv = scitoken_set_claim_string(token.get(), "sub", "stress-test", &err_msg); ASSERT_EQ(rv, 0); if (err_msg) { free(err_msg); err_msg = nullptr; } scitoken_set_lifetime(token.get(), 3600); char *token_value = nullptr; rv = scitoken_serialize(token.get(), &token_value, &err_msg); ASSERT_EQ(rv, 0); std::string token_str(token_value); free(token_value); if (err_msg) { free(err_msg); err_msg = nullptr; } // Get initial stats auto stats_before = getCurrentMonitoringStats(); auto initial_successful = stats_before.getIssuerStats(issuer_url_).successful_validations; auto initial_unsuccessful = stats_before.getIssuerStats(issuer_url_).unsuccessful_validations; auto initial_key_lookups = stats_before.getIssuerStats(issuer_url_).successful_key_lookups; // Stress test parameters const int NUM_THREADS = 10; const int TEST_DURATION_MS = 5000; // 5 seconds std::atomic total_attempts{0}; std::atomic total_successes{0}; std::atomic total_failures{0}; std::atomic stop_flag{false}; std::vector threads; for (int i = 0; i < NUM_THREADS; i++) { threads.emplace_back([&]() { while (!stop_flag.load()) { total_attempts++; char *thread_err = nullptr; std::unique_ptr verify_token( scitoken_create(nullptr), scitoken_destroy); int result = scitoken_deserialize_v2(token_str.c_str(), verify_token.get(), nullptr, &thread_err); if (result == 0) { total_successes++; } else { total_failures++; if (thread_err) { std::cerr << "Unexpected error: " << thread_err << std::endl; } } if (thread_err) free(thread_err); } }); } // Run for the test duration std::this_thread::sleep_for(std::chrono::milliseconds(TEST_DURATION_MS)); stop_flag.store(true); // Wait for all threads to complete for (auto &t : threads) { t.join(); } // Get final stats auto stats_after = getCurrentMonitoringStats(); auto issuer_stats = stats_after.getIssuerStats(issuer_url_); auto new_successful = issuer_stats.successful_validations - initial_successful; auto new_unsuccessful = issuer_stats.unsuccessful_validations - initial_unsuccessful; auto new_key_lookups = issuer_stats.successful_key_lookups - initial_key_lookups; std::cout << "Stress test (valid token) results:" << std::endl; std::cout << " Test duration: " << TEST_DURATION_MS << " ms" << std::endl; std::cout << " Threads: " << NUM_THREADS << std::endl; std::cout << " Total attempts: " << total_attempts.load() << std::endl; std::cout << " Total successes: " << total_successes.load() << std::endl; std::cout << " Total failures: " << total_failures.load() << std::endl; std::cout << " Monitoring successful_validations: " << new_successful << std::endl; std::cout << " Monitoring unsuccessful_validations: " << new_unsuccessful << std::endl; std::cout << " Monitoring successful_key_lookups: " << new_key_lookups << std::endl; // Verify all attempts succeeded EXPECT_EQ(total_failures.load(), 0u) << "All deserializations of valid token should succeed"; // Verify monitoring counters match actual counts EXPECT_EQ(new_successful, total_successes.load()) << "Monitoring successful_validations should match actual success " "count"; EXPECT_EQ(new_unsuccessful, 0u) << "There should be no unsuccessful validations for valid token"; // Verify at most one key lookup (keys should be cached after first fetch) // Using a fresh cache directory ensures no interference from prior tests EXPECT_LE(new_key_lookups, 1u) << "Should have at most one key lookup (cached after first)"; // Sanity check: we should have done a meaningful number of validations EXPECT_GT(total_attempts.load(), 100u) << "Should have completed at least 100 validations in " << TEST_DURATION_MS << "ms"; // unique_cache destructor will clean up the temporary cache directory } // Stress test: repeatedly deserialize a token with an invalid issuer (404) // across multiple threads and verify monitoring counters match actual failure // counts TEST_F(IntegrationTest, StressTestInvalidIssuer) { char *err_msg = nullptr; // Use a unique secure cache directory to ensure no cached keys exist from // prior tests SecureTempDir unique_cache("stress_invalid_"); ASSERT_TRUE(unique_cache.valid()) << "Failed to create temp cache directory"; int rv = scitoken_config_set_str("keycache.cache_home", unique_cache.path().c_str(), &err_msg); ASSERT_EQ(rv, 0) << "Failed to set cache_home: " << (err_msg ? err_msg : "unknown"); if (err_msg) { free(err_msg); err_msg = nullptr; } // Reset monitoring stats before the test rv = scitoken_reset_monitoring_stats(&err_msg); ASSERT_EQ(rv, 0) << "Failed to reset monitoring stats"; if (err_msg) { free(err_msg); err_msg = nullptr; } // Create a token with an issuer path that returns 404 // The server returns 404 for paths like /nonexistent-path std::string invalid_issuer = issuer_url_ + "/nonexistent-path"; std::unique_ptr key( scitoken_key_create("test-key-1", "ES256", public_key_.c_str(), private_key_.c_str(), &err_msg), scitoken_key_destroy); ASSERT_TRUE(key.get() != nullptr); if (err_msg) { free(err_msg); err_msg = nullptr; } std::unique_ptr token( scitoken_create(key.get()), scitoken_destroy); ASSERT_TRUE(token.get() != nullptr); rv = scitoken_set_claim_string(token.get(), "iss", invalid_issuer.c_str(), &err_msg); ASSERT_EQ(rv, 0); if (err_msg) { free(err_msg); err_msg = nullptr; } rv = scitoken_set_claim_string(token.get(), "sub", "stress-test-invalid", &err_msg); ASSERT_EQ(rv, 0); if (err_msg) { free(err_msg); err_msg = nullptr; } scitoken_set_lifetime(token.get(), 3600); char *token_value = nullptr; rv = scitoken_serialize(token.get(), &token_value, &err_msg); ASSERT_EQ(rv, 0); std::string token_str(token_value); free(token_value); if (err_msg) { free(err_msg); err_msg = nullptr; } // Get initial stats for the invalid issuer auto stats_before = getCurrentMonitoringStats(); auto initial_successful = stats_before.getIssuerStats(invalid_issuer).successful_validations; auto initial_unsuccessful = stats_before.getIssuerStats(invalid_issuer).unsuccessful_validations; auto initial_key_lookups = stats_before.getIssuerStats(invalid_issuer).successful_key_lookups; // Stress test parameters const int NUM_THREADS = 10; const int TEST_DURATION_MS = 5000; // 5 seconds std::atomic total_attempts{0}; std::atomic total_successes{0}; std::atomic total_failures{0}; std::atomic stop_flag{false}; std::vector threads; for (int i = 0; i < NUM_THREADS; i++) { threads.emplace_back([&]() { while (!stop_flag.load()) { total_attempts++; char *thread_err = nullptr; std::unique_ptr verify_token( scitoken_create(nullptr), scitoken_destroy); int result = scitoken_deserialize_v2(token_str.c_str(), verify_token.get(), nullptr, &thread_err); if (result == 0) { total_successes++; } else { total_failures++; } if (thread_err) free(thread_err); } }); } // Run for the test duration std::this_thread::sleep_for(std::chrono::milliseconds(TEST_DURATION_MS)); stop_flag.store(true); // Wait for all threads to complete for (auto &t : threads) { t.join(); } // Get final stats for the invalid issuer auto stats_after = getCurrentMonitoringStats(); auto issuer_stats = stats_after.getIssuerStats(invalid_issuer); auto new_successful = issuer_stats.successful_validations - initial_successful; auto new_unsuccessful = issuer_stats.unsuccessful_validations - initial_unsuccessful; auto new_key_lookups = issuer_stats.successful_key_lookups - initial_key_lookups; std::cout << "Stress test (invalid issuer - 404) results:" << std::endl; std::cout << " Test duration: " << TEST_DURATION_MS << " ms" << std::endl; std::cout << " Threads: " << NUM_THREADS << std::endl; std::cout << " Invalid issuer: " << invalid_issuer << std::endl; std::cout << " Total attempts: " << total_attempts.load() << std::endl; std::cout << " Total successes: " << total_successes.load() << std::endl; std::cout << " Total failures: " << total_failures.load() << std::endl; std::cout << " Monitoring successful_validations: " << new_successful << std::endl; std::cout << " Monitoring unsuccessful_validations: " << new_unsuccessful << std::endl; std::cout << " Monitoring successful_key_lookups: " << new_key_lookups << std::endl; // Verify all attempts failed (issuer returns 404) EXPECT_EQ(total_successes.load(), 0u) << "All deserializations with invalid issuer should fail"; // Verify monitoring counters match actual counts EXPECT_EQ(new_successful, 0u) << "There should be no successful validations for invalid issuer"; EXPECT_EQ(new_unsuccessful, total_failures.load()) << "Monitoring unsuccessful_validations should match actual failure " "count"; // No successful key lookups expected (issuer returns 404) EXPECT_EQ(new_key_lookups, 0u) << "Should have no successful key lookups (issuer returns 404)"; // Sanity check: we should have done a meaningful number of validations EXPECT_GT(total_attempts.load(), 100u) << "Should have completed at least 100 validations in " << TEST_DURATION_MS << "ms"; // unique_cache destructor will clean up the temporary cache directory } } // namespace int main(int argc, char **argv) { ::testing::InitGoogleTest(&argc, argv); return RUN_ALL_TESTS(); } scitokens-cpp-1.3.0/test/jwks_server.py000077500000000000000000000132501513647275500202150ustar00rootroot00000000000000#!/usr/bin/env python3 """ Simple Python web server that hosts JWKS and supports OIDC discovery. Used for integration testing of scitokens-cpp. """ import argparse import json import os import signal import socket import ssl import sys from http.server import HTTPServer, BaseHTTPRequestHandler from pathlib import Path class JWKSHandler(BaseHTTPRequestHandler): """HTTP handler for JWKS and discovery endpoints.""" # Use HTTP/1.1 for proper connection handling protocol_version = 'HTTP/1.1' def log_message(self, format, *args): """Override to log to file instead of stderr.""" if hasattr(self.server, 'log_file'): with open(self.server.log_file, 'a') as f: f.write("%s - - [%s] %s\n" % ( self.address_string(), self.log_date_time_string(), format % args)) def do_GET(self): """Handle GET requests for JWKS and discovery.""" if self.path == '/.well-known/openid-configuration': self.serve_discovery() elif self.path == '/oauth2/certs' or self.path == '/jwks': self.serve_jwks() else: self.send_error(404, "Not Found") def serve_discovery(self): """Serve OIDC discovery document.""" issuer = self.server.issuer_url discovery = { "issuer": issuer, "jwks_uri": f"{issuer}/oauth2/certs", "token_endpoint": f"{issuer}/token", "authorization_endpoint": f"{issuer}/authorize", } content = json.dumps(discovery).encode() self.send_response(200) self.send_header('Content-Type', 'application/json') self.send_header('Content-Length', str(len(content))) self.end_headers() self.wfile.write(content) def serve_jwks(self): """Serve JWKS document.""" with open(self.server.jwks_file, 'r') as f: jwks_content = f.read() content = jwks_content.encode() self.send_response(200) self.send_header('Content-Type', 'application/json') self.send_header('Content-Length', str(len(content))) self.end_headers() self.wfile.write(content) def main(): parser = argparse.ArgumentParser(description='JWKS test server') parser.add_argument('--jwks', required=True, help='Path to JWKS file') parser.add_argument('--build-dir', required=True, help='Build directory') parser.add_argument('--test-name', default='integration', help='Test name') parser.add_argument('--cert', help='Path to TLS certificate file') parser.add_argument('--key', help='Path to TLS key file') args = parser.parse_args() # Determine if we're using HTTPS use_https = args.cert and args.key protocol = "https" if use_https else "http" # Create test directory test_dir = Path(args.build_dir) / 'tests' / args.test_name test_dir.mkdir(parents=True, exist_ok=True) # Create ready file to signal server is ready ready_file = test_dir / 'server_ready' log_file = test_dir / 'server.log' # Setup HTTP server - bind to port 0 to get a free port automatically server = HTTPServer(('localhost', 0), JWKSHandler) server.jwks_file = args.jwks server.log_file = str(log_file) # Setup TLS if certificates provided if use_https: context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER) context.load_cert_chain(args.cert, args.key) # Set minimum TLS version to 1.2 for security # Use ssl.TLSVersion for Python 3.7+, fall back to options for Python 3.6 (EL8) try: context.minimum_version = ssl.TLSVersion.TLSv1_2 except AttributeError: # Python 3.6 doesn't have ssl.TLSVersion, use options instead context.options |= ssl.OP_NO_SSLv2 | ssl.OP_NO_SSLv3 | ssl.OP_NO_TLSv1 | ssl.OP_NO_TLSv1_1 # Set cipher suites for OpenSSL 3.0.2 compatibility # SECLEVEL=1 allows 2048-bit RSA and SHA-1 for test certificates try: context.set_ciphers('DEFAULT:@SECLEVEL=1') except ssl.SSLError: # Fallback for older Python/OpenSSL context.set_ciphers('DEFAULT') # Disable TLS session tickets to avoid issues with session resumption context.options |= ssl.OP_NO_TICKET # Allow self-signed certificates for testing context.check_hostname = False context.verify_mode = ssl.CERT_NONE server.socket = context.wrap_socket(server.socket, server_side=True) # Get the actual port that was assigned port = server.server_address[1] issuer_url = f"{protocol}://localhost:{port}" server.issuer_url = issuer_url # Write server info to ready file with open(ready_file, 'w') as f: f.write(f"PID={os.getpid()}\n") f.write(f"ISSUER_URL={issuer_url}\n") f.write(f"PORT={port}\n") print(f"Server started on {issuer_url}", flush=True) print(f"Server PID: {os.getpid()}", flush=True) print(f"Server ready file: {ready_file}", flush=True) # Handle shutdown gracefully - set a flag that will be checked shutdown_requested = [False] def signal_handler(signum, frame): print("Shutting down server...", flush=True) shutdown_requested[0] = True # Shutdown needs to be called from a different thread or we need to exit # Using os._exit to immediately terminate os._exit(0) signal.signal(signal.SIGTERM, signal_handler) signal.signal(signal.SIGINT, signal_handler) try: server.serve_forever() except KeyboardInterrupt: pass finally: server.server_close() if __name__ == '__main__': main() scitokens-cpp-1.3.0/test/main.cpp000066400000000000000000001447721513647275500167420ustar00rootroot00000000000000#include "../src/scitokens.h" #include "test_utils.h" #include #include #include #include #include #ifndef PICOJSON_USE_INT64 #define PICOJSON_USE_INT64 #endif #include #include #include #include using scitokens_test::SecureTempDir; namespace { const char ec_private[] = "-----BEGIN EC PRIVATE KEY-----\n" "MHcCAQEEIESSMxT7PLTR9A/aqd+CM0/6vv6fQWqDm0mNx8uE9EbpoAoGCCqGSM49\n" "AwEHoUQDQgAE1i+ImZ//iQhOPh0OMfZzdbmPH+3G1ouWezolCugQYWIRqNmwq3zR\n" "EnTbe4EmymTpJ1MJTPP/tCEUP3G/QqQuhA==\n" "-----END EC PRIVATE KEY-----\n"; const char ec_public[] = "-----BEGIN PUBLIC KEY-----\n" "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE1i+ImZ//iQhOPh0OMfZzdbmPH+3G\n" "1ouWezolCugQYWIRqNmwq3zREnTbe4EmymTpJ1MJTPP/tCEUP3G/QqQuhA==\n" "-----END PUBLIC KEY-----\n"; const char ec_private_2[] = "-----BEGIN EC PRIVATE KEY-----\n" "MHcCAQEEIJH6NpWPHcM7wxL/bv89Nezug+KEUQjI9fZxhrBHNA1ioAoGCCqGSM49\n" "AwEHoUQDQgAEb8M7AxRN+DmbfYOoA6DeHCcSeA+kXWCq4E/g2ME/uBOdP8RE0tql\n" "e8fxYcaPikgMcppGq2ycTiLGgEYXgsq2JA==\n" "-----END EC PRIVATE KEY-----\n"; const char ec_public_2[] = "-----BEGIN PUBLIC KEY-----\n" "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEb8M7AxRN+DmbfYOoA6DeHCcSeA+k\n" "XWCq4E/g2ME/uBOdP8RE0tqle8fxYcaPikgMcppGq2ycTiLGgEYXgsq2JA==\n" "-----END PUBLIC KEY-----\n"; TEST(SciTokenTest, CreateToken) { SciToken token = scitoken_create(nullptr); ASSERT_TRUE(token != nullptr); scitoken_destroy(token); } TEST(SciTokenTest, SignToken) { char *err_msg = nullptr; std::unique_ptr mykey( scitoken_key_create("1", "ES256", ec_public, ec_private, &err_msg), scitoken_key_destroy); ASSERT_TRUE(mykey.get() != nullptr) << err_msg; std::unique_ptr mytoken( scitoken_create(mykey.get()), scitoken_destroy); ASSERT_TRUE(mytoken.get() != nullptr); auto rv = scitoken_set_claim_string( mytoken.get(), "iss", "https://demo.scitokens.org/gtest", &err_msg); ASSERT_TRUE(rv == 0) << err_msg; char *value; rv = scitoken_serialize(mytoken.get(), &value, &err_msg); ASSERT_TRUE(rv == 0) << err_msg; EXPECT_TRUE(value != nullptr); std::unique_ptr value_ptr(value, free); ASSERT_TRUE(strlen(value) > 50); } class SerializeTest : public ::testing::Test { protected: void SetUp() override { char *err_msg = nullptr; m_key = KeyPtr( scitoken_key_create("1", "ES256", ec_public, ec_private, &err_msg), scitoken_key_destroy); ASSERT_TRUE(m_key.get() != nullptr) << err_msg; m_token = TokenPtr(scitoken_create(m_key.get()), scitoken_destroy); ASSERT_TRUE(m_token.get() != nullptr); auto rv = scitoken_set_claim_string( m_token.get(), "iss", "https://demo.scitokens.org/gtest", &err_msg); ASSERT_TRUE(rv == 0) << err_msg; rv = scitoken_store_public_ec_key("https://demo.scitokens.org/gtest", "1", ec_public, &err_msg); ASSERT_TRUE(rv == 0) << err_msg; scitoken_set_lifetime(m_token.get(), 60); m_audiences_array.push_back("https://demo.scitokens.org/"); m_audiences_array.push_back(nullptr); const char *groups[3] = {nullptr, nullptr, nullptr}; const char group0[] = "group0"; const char group1[] = "group1"; groups[0] = group0; groups[1] = group1; rv = scitoken_set_claim_string_list(m_token.get(), "groups", groups, &err_msg); ASSERT_TRUE(rv == 0) << err_msg; m_read_token.reset(scitoken_create(nullptr)); ASSERT_TRUE(m_read_token.get() != nullptr); } using KeyPtr = std::unique_ptr; KeyPtr m_key{nullptr, scitoken_key_destroy}; using TokenPtr = std::unique_ptr; TokenPtr m_token{nullptr, scitoken_destroy}; std::vector m_audiences_array; TokenPtr m_read_token{nullptr, scitoken_destroy}; }; TEST_F(SerializeTest, VerifyTest) { char *err_msg = nullptr; char *token_value = nullptr; auto rv = scitoken_serialize(m_token.get(), &token_value, &err_msg); ASSERT_TRUE(rv == 0) << err_msg; std::unique_ptr token_value_ptr(token_value, free); rv = scitoken_deserialize_v2(token_value, m_read_token.get(), nullptr, &err_msg); ASSERT_TRUE(rv == 0) << err_msg; char *value; rv = scitoken_get_claim_string(m_read_token.get(), "iss", &value, &err_msg); ASSERT_TRUE(value != nullptr); ASSERT_TRUE(rv == 0) << err_msg; std::unique_ptr value_ptr(value, free); EXPECT_STREQ(value, "https://demo.scitokens.org/gtest"); value_ptr.reset(); rv = scitoken_get_claim_string(m_read_token.get(), "doesnotexist", &value, &err_msg); free(err_msg); EXPECT_FALSE(rv == 0); } TEST_F(SerializeTest, TestStringList) { char *err_msg = nullptr; char **value; auto rv = scitoken_get_claim_string_list(m_token.get(), "groups", &value, &err_msg); ASSERT_TRUE(rv == 0) << err_msg; ASSERT_TRUE(value != nullptr); ASSERT_TRUE(value[0] != nullptr); EXPECT_STREQ(value[0], "group0"); ASSERT_TRUE(value[1] != nullptr); EXPECT_STREQ(value[1], "group1"); EXPECT_TRUE(value[2] == nullptr); scitoken_free_string_list(value); } TEST_F(SerializeTest, VerifyWLCGTest) { char *err_msg = nullptr; char *token_value = nullptr; scitoken_set_serialize_profile(m_token.get(), SciTokenProfile::WLCG_1_0); auto rv = scitoken_serialize(m_token.get(), &token_value, &err_msg); ASSERT_TRUE(rv == 0) << err_msg; std::unique_ptr token_value_ptr(token_value, free); // Accepts any profile. rv = scitoken_deserialize_v2(token_value, m_read_token.get(), nullptr, &err_msg); ASSERT_TRUE(rv == 0) << err_msg; char *value; rv = scitoken_get_claim_string(m_read_token.get(), "wlcg.ver", &value, &err_msg); ASSERT_TRUE(rv == 0) << err_msg; ASSERT_TRUE(value != nullptr); std::unique_ptr value_ptr(value, free); EXPECT_STREQ(value, "1.0"); value_ptr.reset(); rv = scitoken_get_claim_string(m_read_token.get(), "ver", &value, &err_msg); free(err_msg); err_msg = nullptr; EXPECT_FALSE(rv == 0); // Accepts only a WLCG token scitoken_set_deserialize_profile(m_read_token.get(), SciTokenProfile::WLCG_1_0); rv = scitoken_deserialize_v2(token_value, m_read_token.get(), nullptr, &err_msg); ASSERT_TRUE(rv == 0) << err_msg; // Accepts only SciToken 1.0; should fail. scitoken_set_deserialize_profile(m_read_token.get(), SciTokenProfile::SCITOKENS_1_0); rv = scitoken_deserialize_v2(token_value, m_read_token.get(), nullptr, &err_msg); free(err_msg); ASSERT_FALSE(rv == 0); } TEST_F(SerializeTest, FailVerifyToken) { char *err_msg = nullptr; std::unique_ptr mykey( scitoken_key_create("1", "ES256", ec_public_2, ec_private_2, &err_msg), scitoken_key_destroy); ASSERT_TRUE(mykey.get() != nullptr) << err_msg; std::unique_ptr mytoken( scitoken_create(mykey.get()), scitoken_destroy); ASSERT_TRUE(mytoken.get() != nullptr); auto rv = scitoken_set_claim_string( mytoken.get(), "iss", "https://demo.scitokens.org/gtest", &err_msg); ASSERT_TRUE(rv == 0) << err_msg; char *value; rv = scitoken_serialize(mytoken.get(), &value, &err_msg); ASSERT_TRUE(rv == 0) << err_msg; EXPECT_TRUE(value != nullptr); std::unique_ptr value_ptr(value, free); EXPECT_TRUE(strlen(value) > 50); // Should fail; we signed it with the wrong public key. rv = scitoken_deserialize_v2(value, m_read_token.get(), nullptr, &err_msg); free(err_msg); EXPECT_FALSE(rv == 0); } TEST_F(SerializeTest, VerifyATJWTTest) { char *err_msg = nullptr; // Serialize as at+jwt token. char *token_value = nullptr; scitoken_set_serialize_profile(m_token.get(), SciTokenProfile::AT_JWT); auto rv = scitoken_serialize(m_token.get(), &token_value, &err_msg); ASSERT_TRUE(rv == 0) << err_msg; std::unique_ptr token_value_ptr(token_value, free); // Accepts any profile. rv = scitoken_deserialize_v2(token_value, m_read_token.get(), nullptr, &err_msg); ASSERT_TRUE(rv == 0) << err_msg; // Accepts only an at+jwt token, should work with at+jwt token scitoken_set_deserialize_profile(m_read_token.get(), SciTokenProfile::AT_JWT); rv = scitoken_deserialize_v2(token_value, m_read_token.get(), nullptr, &err_msg); ASSERT_TRUE(rv == 0) << err_msg; // Accepts only SciToken 2.0; should fail scitoken_set_deserialize_profile(m_read_token.get(), SciTokenProfile::SCITOKENS_2_0); rv = scitoken_deserialize_v2(token_value, m_read_token.get(), nullptr, &err_msg); free(err_msg); ASSERT_FALSE(rv == 0); } TEST_F(SerializeTest, FailVerifyATJWTTest) { char *err_msg = nullptr; // Serialize as "compat" token. char *token_value = nullptr; scitoken_set_serialize_profile(m_token.get(), SciTokenProfile::COMPAT); auto rv = scitoken_serialize(m_token.get(), &token_value, &err_msg); ASSERT_TRUE(rv == 0) << err_msg; std::unique_ptr token_value_ptr(token_value, free); // Accepts any profile. rv = scitoken_deserialize_v2(token_value, m_read_token.get(), nullptr, &err_msg); ASSERT_TRUE(rv == 0) << err_msg; // Accepts only an at+jwt token, should fail with COMPAT token scitoken_set_deserialize_profile(m_read_token.get(), SciTokenProfile::AT_JWT); rv = scitoken_deserialize_v2(token_value, m_read_token.get(), nullptr, &err_msg); free(err_msg); ASSERT_FALSE(rv == 0); } TEST_F(SerializeTest, EnforcerTest) { /* * Test that the enforcer works and returns an err_msg */ char *err_msg = nullptr; auto rv = scitoken_set_claim_string( m_token.get(), "aud", "https://demo.scitokens.org/", &err_msg); ASSERT_TRUE(rv == 0) << err_msg; auto enforcer = enforcer_create("https://demo.scitokens.org/gtest", &m_audiences_array[0], &err_msg); ASSERT_TRUE(enforcer != nullptr) << err_msg; Acl acl; acl.authz = "read"; acl.resource = "/stuff"; rv = scitoken_set_claim_string(m_token.get(), "scope", "read:/blah", &err_msg); ASSERT_TRUE(rv == 0) << err_msg; rv = scitoken_set_claim_string(m_token.get(), "ver", "scitoken:2.0", &err_msg); ASSERT_TRUE(rv == 0) << err_msg; char *token_value = nullptr; rv = scitoken_serialize(m_token.get(), &token_value, &err_msg); ASSERT_TRUE(rv == 0) << err_msg; std::unique_ptr token_value_ptr(token_value, free); rv = scitoken_deserialize_v2(token_value, m_read_token.get(), nullptr, &err_msg); ASSERT_TRUE(rv == 0) << err_msg; free(err_msg); rv = enforcer_test(enforcer, m_read_token.get(), &acl, &err_msg); ASSERT_STREQ( err_msg, "token verification failed: 'scope' claim verification failed."); ASSERT_TRUE(rv == -1) << err_msg; free(err_msg); enforcer_destroy(enforcer); } TEST_F(SerializeTest, EnforcerScopeTest) { char *err_msg = nullptr; auto rv = scitoken_set_claim_string( m_token.get(), "aud", "https://demo.scitokens.org/", &err_msg); ASSERT_TRUE(rv == 0) << err_msg; auto enforcer = enforcer_create("https://demo.scitokens.org/gtest", &m_audiences_array[0], &err_msg); ASSERT_TRUE(enforcer != nullptr) << err_msg; scitoken_set_serialize_profile(m_token.get(), SciTokenProfile::WLCG_1_0); rv = scitoken_set_claim_string( m_token.get(), "scope", "storage.modify:/ storage.read:/ openid offline_access", &err_msg); ASSERT_TRUE(rv == 0) << err_msg; char *token_value = nullptr; rv = scitoken_serialize(m_token.get(), &token_value, &err_msg); ASSERT_TRUE(rv == 0) << err_msg; rv = scitoken_deserialize_v2(token_value, m_read_token.get(), nullptr, &err_msg); free(token_value); ASSERT_TRUE(rv == 0) << err_msg; Acl *acls; enforcer_generate_acls(enforcer, m_read_token.get(), &acls, &err_msg); ASSERT_TRUE(acls != nullptr) << err_msg; int idx = 0; bool found_read = false; bool found_write = false; while (acls[idx].resource && acls[idx++].authz) { auto resource = acls[idx - 1].resource; auto authz = acls[idx - 1].authz; if (strcmp(authz, "read") == 0) { found_read = true; ASSERT_STREQ(resource, "/"); } else if (strcmp(authz, "write") == 0) { found_write = true; ASSERT_STREQ(resource, "/"); } } enforcer_acl_free(acls); enforcer_destroy(enforcer); ASSERT_TRUE(found_read); ASSERT_TRUE(found_write); } } // namespace TEST_F(SerializeTest, DeserializeAsyncTest) { char *err_msg = nullptr; // Serialize as "compat" token. char *token_value = nullptr; scitoken_set_serialize_profile(m_token.get(), SciTokenProfile::COMPAT); auto rv = scitoken_serialize(m_token.get(), &token_value, &err_msg); ASSERT_TRUE(rv == 0) << err_msg; std::unique_ptr token_value_ptr(token_value, free); SciToken scitoken; SciTokenStatus status; // Accepts any profile. rv = scitoken_deserialize_start(token_value, &scitoken, nullptr, &status, &err_msg); ASSERT_TRUE(rv == 0) << err_msg; // Accepts only an at+jwt token, should fail with COMPAT token while (rv == 0 && status) { rv = scitoken_deserialize_continue(&scitoken, &status, &err_msg); ASSERT_TRUE(rv == 0) << err_msg; } scitoken_destroy(scitoken); } TEST_F(SerializeTest, FailDeserializeAsyncTest) { char *err_msg = nullptr; std::unique_ptr mykey( scitoken_key_create("1", "ES256", ec_public_2, ec_private_2, &err_msg), scitoken_key_destroy); ASSERT_TRUE(mykey.get() != nullptr) << err_msg; std::unique_ptr mytoken( scitoken_create(mykey.get()), scitoken_destroy); ASSERT_TRUE(mytoken.get() != nullptr); auto rv = scitoken_set_claim_string( mytoken.get(), "iss", "https://demo.scitokens.org/gtest", &err_msg); ASSERT_TRUE(rv == 0) << err_msg; char *value; rv = scitoken_serialize(mytoken.get(), &value, &err_msg); ASSERT_TRUE(rv == 0) << err_msg; EXPECT_TRUE(value != nullptr); std::unique_ptr value_ptr(value, free); EXPECT_TRUE(strlen(value) > 50); char *token_value = nullptr; std::unique_ptr token_value_ptr(token_value, free); SciToken scitoken; SciTokenStatus status; // Accepts any profile. rv = scitoken_deserialize_start(value, &scitoken, nullptr, &status, &err_msg); EXPECT_FALSE(rv == 0) << err_msg; free(err_msg); err_msg = nullptr; // Accepts only an at+jwt token, should fail with COMPAT token while (rv == 0 && status) { rv = scitoken_deserialize_continue(&scitoken, &status, &err_msg); EXPECT_FALSE(rv == 0) << err_msg; free(err_msg); err_msg = nullptr; } } TEST_F(SerializeTest, ExplicitTime) { char *err_msg = nullptr; scitoken_set_serialize_profile(m_token.get(), SciTokenProfile::WLCG_1_0); auto rv = scitoken_set_claim_string(m_token.get(), "scope", "storage.read:/", &err_msg); ASSERT_TRUE(rv == 0) << err_msg; char *token_value = nullptr; rv = scitoken_serialize(m_token.get(), &token_value, &err_msg); ASSERT_TRUE(rv == 0) << err_msg; rv = scitoken_deserialize_v2(token_value, m_read_token.get(), nullptr, &err_msg); free(token_value); ASSERT_TRUE(rv == 0) << err_msg; auto enforcer = enforcer_create("https://demo.scitokens.org/gtest", &m_audiences_array[0], &err_msg); ASSERT_TRUE(enforcer != nullptr) << err_msg; Acl *acls; rv = enforcer_generate_acls(enforcer, m_read_token.get(), &acls, &err_msg); ASSERT_TRUE(rv == 0) << err_msg; ASSERT_TRUE(acls != nullptr); enforcer_acl_free(acls); enforcer_set_time(enforcer, time(NULL), &err_msg); rv = enforcer_generate_acls(enforcer, m_read_token.get(), &acls, &err_msg); ASSERT_TRUE(rv == 0) << err_msg; enforcer_set_time(enforcer, time(NULL) + 100, &err_msg); enforcer_acl_free(acls); rv = enforcer_generate_acls(enforcer, m_read_token.get(), &acls, &err_msg); free(err_msg); err_msg = nullptr; ASSERT_FALSE(rv == 0); enforcer_set_time(enforcer, time(NULL) - 100, &err_msg); rv = enforcer_generate_acls(enforcer, m_read_token.get(), &acls, &err_msg); ASSERT_FALSE(rv == 0); free(err_msg); enforcer_destroy(enforcer); } TEST_F(SerializeTest, GetExpirationErrorHandling) { char *err_msg = nullptr; // Test NULL token handling long long expiry; auto rv = scitoken_get_expiration(nullptr, &expiry, &err_msg); ASSERT_FALSE(rv == 0); ASSERT_TRUE(err_msg != nullptr); EXPECT_STREQ(err_msg, "Token cannot be NULL"); free(err_msg); err_msg = nullptr; // Test NULL expiry parameter handling rv = scitoken_get_expiration(m_token.get(), nullptr, &err_msg); ASSERT_FALSE(rv == 0); ASSERT_TRUE(err_msg != nullptr); EXPECT_STREQ(err_msg, "Expiry output parameter cannot be NULL"); free(err_msg); err_msg = nullptr; // Test normal operation works char *token_value = nullptr; rv = scitoken_serialize(m_token.get(), &token_value, &err_msg); ASSERT_TRUE(rv == 0) << err_msg; rv = scitoken_deserialize_v2(token_value, m_read_token.get(), nullptr, &err_msg); ASSERT_TRUE(rv == 0) << err_msg; rv = scitoken_get_expiration(m_read_token.get(), &expiry, &err_msg); ASSERT_TRUE(rv == 0) << err_msg; ASSERT_TRUE(expiry > 0); free(token_value); } class SerializeNoKidTest : public ::testing::Test { protected: void SetUp() override { char *err_msg = nullptr; m_key = KeyPtr(scitoken_key_create("none", "ES256", ec_public, ec_private, &err_msg), scitoken_key_destroy); ASSERT_TRUE(m_key.get() != nullptr) << err_msg; m_token = TokenPtr(scitoken_create(m_key.get()), scitoken_destroy); ASSERT_TRUE(m_token.get() != nullptr); auto rv = scitoken_set_claim_string( m_token.get(), "iss", "https://demo.scitokens.org/gtest", &err_msg); ASSERT_TRUE(rv == 0) << err_msg; rv = scitoken_store_public_ec_key("https://demo.scitokens.org/gtest", "1", ec_public, &err_msg); ASSERT_TRUE(rv == 0) << err_msg; scitoken_set_lifetime(m_token.get(), 60); m_audiences_array.push_back("https://demo.scitokens.org/"); m_audiences_array.push_back(nullptr); const char *groups[3] = {nullptr, nullptr, nullptr}; const char group0[] = "group0"; const char group1[] = "group1"; groups[0] = group0; groups[1] = group1; rv = scitoken_set_claim_string_list(m_token.get(), "groups", groups, &err_msg); ASSERT_TRUE(rv == 0) << err_msg; m_read_token.reset(scitoken_create(nullptr)); ASSERT_TRUE(m_read_token.get() != nullptr); } using KeyPtr = std::unique_ptr; KeyPtr m_key{nullptr, scitoken_key_destroy}; using TokenPtr = std::unique_ptr; TokenPtr m_token{nullptr, scitoken_destroy}; std::vector m_audiences_array; TokenPtr m_read_token{nullptr, scitoken_destroy}; }; TEST_F(SerializeNoKidTest, VerifyATJWTTest) { char *err_msg = nullptr; // Serialize as at+jwt token. char *token_value = nullptr; scitoken_set_serialize_profile(m_token.get(), SciTokenProfile::AT_JWT); auto rv = scitoken_serialize(m_token.get(), &token_value, &err_msg); ASSERT_TRUE(rv == 0) << err_msg; std::unique_ptr token_value_ptr(token_value, free); // Accepts any profile. rv = scitoken_deserialize_v2(token_value, m_read_token.get(), nullptr, &err_msg); ASSERT_TRUE(rv == 0) << err_msg; // Accepts only an at+jwt token, should work with at+jwt token scitoken_set_deserialize_profile(m_read_token.get(), SciTokenProfile::AT_JWT); rv = scitoken_deserialize_v2(token_value, m_read_token.get(), nullptr, &err_msg); ASSERT_TRUE(rv == 0) << err_msg; // Accepts only SciToken 2.0; should fail scitoken_set_deserialize_profile(m_read_token.get(), SciTokenProfile::SCITOKENS_2_0); rv = scitoken_deserialize_v2(token_value, m_read_token.get(), nullptr, &err_msg); free(err_msg); ASSERT_FALSE(rv == 0); } class KeycacheTest : public ::testing::Test { protected: std::string demo_scitokens_url = "https://demo.scitokens.org"; static int64_t get_next_update_from_metadata(const std::string &metadata) { picojson::value root; std::string err = picojson::parse(root, metadata); if (!err.empty() || !root.is()) { return -1; } auto &root_obj = root.get(); auto nu_it = root_obj.find("next_update"); if (nu_it != root_obj.end() && nu_it->second.is()) { return nu_it->second.get(); } return -1; } void SetUp() override { char *err_msg = nullptr; auto rv = keycache_set_jwks(demo_scitokens_url.c_str(), demo_scitokens.c_str(), &err_msg); ASSERT_TRUE(rv == 0) << err_msg; } // Reference copy of the keys at https://demo.scitokens.org/oauth2/certs; // may need to be updated periodically. std::string demo_scitokens = "{\"keys\":[{\"alg\":\"RS256\",\"e\":\"AQAB\",\"kid\":\"key-rs256\"," "\"kty\":\"RSA\",\"n\":\"uGDGTLXnqh3mfopjys6sFUBvFl3F4Qt6NEYphq_u_" "aBhtN1X9NEyb78uB_" "I1KjciJNGLIQU0ECsJiFx6qV1hR9xE1dPyrS3bU92AVtnBrvzUtTU-aUZAmZQiuAC_rC0-" "z_" "TOQr6qJkkUgZtxR9n9op55ZBpRfZD5dzhkW4Dm146vfTKt0D4cIMoMNJS5xQx9nibeB4E8" "hryZDW_" "fPeD0XZDcpByNyP0jFDYkxdUtQFvyRpz4WMZ4ejUfvW3gf4LRAfGZJtMnsZ7ZW4RfoQbhi" "XKMfWeBEjQDiXh0r-KuZLykxhYJtpf7fTnPna753IzMgRMmW3F69iQn2LQN3LoSMw==\"," "\"use\":\"sig\"},{\"alg\":\"ES256\",\"kid\":\"key-es256\",\"kty\":" "\"EC\",\"use\":\"sig\",\"x\":" "\"ncSCrGTBTXXOhNiAOTwNdPjwRz1hVY4saDNiHQK9Bh4=\",\"y\":" "\"sCsFXvx7FAAklwq3CzRCBcghqZOFPB2dKUayS6LY_Lo=\"}]}"; std::string demo_scitokens2 = "{\"keys\":[{\"alg\":\"ES256\",\"kid\":\"key-es256\",\"kty\":\"EC\"," "\"use\":\"sig\",\"x\":\"ncSCrGTBTXXOhNiAOTwNdPjwRz1hVY4saDNiHQK9Bh4=" "\",\"y\":\"sCsFXvx7FAAklwq3CzRCBcghqZOFPB2dKUayS6LY_Lo=\"}]}"; }; TEST_F(KeycacheTest, RefreshTest) { char *err_msg = nullptr; auto rv = keycache_refresh_jwks(demo_scitokens_url.c_str(), &err_msg); ASSERT_TRUE(rv == 0) << err_msg; char *output_jwks; rv = keycache_get_cached_jwks(demo_scitokens_url.c_str(), &output_jwks, &err_msg); ASSERT_TRUE(rv == 0) << err_msg; ASSERT_TRUE(output_jwks != nullptr); std::string output_jwks_str(output_jwks); free(output_jwks); EXPECT_EQ(demo_scitokens, output_jwks_str); } TEST_F(KeycacheTest, RefreshInvalid) { char *err_msg = nullptr, *jwks; auto rv = keycache_refresh_jwks("https://demo.scitokens.org/invalid", &err_msg); ASSERT_FALSE(rv == 0); free(err_msg); rv = keycache_get_cached_jwks("https://demo.scitokens.org/invalid", &jwks, &err_msg); ASSERT_TRUE(rv == 0) << err_msg; ASSERT_TRUE(jwks != nullptr); std::string jwks_str(jwks); free(jwks); EXPECT_EQ(jwks_str, "{\"keys\": []}"); } TEST_F(KeycacheTest, GetInvalid) { char *err_msg = nullptr, *jwks; auto rv = keycache_get_cached_jwks("https://demo.scitokens.org/unknown", &jwks, &err_msg); ASSERT_TRUE(rv == 0) << err_msg; ASSERT_TRUE(jwks != nullptr); std::string jwks_str(jwks); free(jwks); } TEST_F(KeycacheTest, GetTest) { char *err_msg = nullptr, *jwks; auto rv = keycache_get_cached_jwks(demo_scitokens_url.c_str(), &jwks, &err_msg); ASSERT_TRUE(rv == 0) << err_msg; ASSERT_TRUE(jwks != nullptr); std::string jwks_str(jwks); free(jwks); EXPECT_EQ(demo_scitokens, jwks_str); } TEST_F(KeycacheTest, SetGetTest) { char *err_msg = nullptr; auto rv = keycache_set_jwks(demo_scitokens_url.c_str(), demo_scitokens2.c_str(), &err_msg); ASSERT_TRUE(rv == 0) << err_msg; char *jwks; rv = keycache_get_cached_jwks(demo_scitokens_url.c_str(), &jwks, &err_msg); ASSERT_TRUE(rv == 0) << err_msg; ASSERT_TRUE(jwks != nullptr); std::string jwks_str(jwks); free(jwks); EXPECT_EQ(demo_scitokens2, jwks_str); } TEST_F(KeycacheTest, SetGetConfiguredCacheHome) { // Create a secure temporary directory for the cache SecureTempDir temp_cache("cache_home_test_"); ASSERT_TRUE(temp_cache.valid()) << "Failed to create temp directory"; char *err_msg = nullptr; std::string key = "keycache.cache_home"; auto rv = scitoken_config_set_str(key.c_str(), temp_cache.path().c_str(), &err_msg); ASSERT_TRUE(rv == 0) << err_msg; // Set the jwks at the new cache home rv = keycache_set_jwks(demo_scitokens_url.c_str(), demo_scitokens2.c_str(), &err_msg); ASSERT_TRUE(rv == 0) << err_msg; // Fetch the cached jwks from the new cache home char *jwks; rv = keycache_get_cached_jwks(demo_scitokens_url.c_str(), &jwks, &err_msg); ASSERT_TRUE(rv == 0) << err_msg; ASSERT_TRUE(jwks != nullptr); std::string jwks_str(jwks); free(jwks); EXPECT_EQ(demo_scitokens2, jwks_str); // Check that cache home is still what was set char *output; rv = scitoken_config_get_str(key.c_str(), &output, &err_msg); ASSERT_TRUE(rv == 0) << err_msg; EXPECT_STREQ(output, temp_cache.path().c_str()); free(output); // Reset cache home to whatever it was before by setting empty config rv = scitoken_config_set_str(key.c_str(), "", &err_msg); ASSERT_TRUE(rv == 0) << err_msg; // temp_cache destructor will clean up the directory } TEST_F(KeycacheTest, InvalidConfigKeyTest) { char *err_msg = nullptr; int new_update_interval = 400; std::string key = "invalid key"; auto rv = scitoken_config_set_int(key.c_str(), new_update_interval, &err_msg); free(err_msg); err_msg = nullptr; ASSERT_FALSE(rv == 0); const char *key2 = nullptr; rv = scitoken_config_set_int(key2, new_update_interval, &err_msg); free(err_msg); ASSERT_FALSE(rv == 0); } TEST_F(KeycacheTest, SetGetUpdateTest) { char *err_msg = nullptr; int new_update_interval = 400; std::string key = "keycache.update_interval_s"; auto rv = scitoken_config_set_int(key.c_str(), new_update_interval, &err_msg); ASSERT_TRUE(rv == 0) << err_msg; rv = scitoken_config_get_int(key.c_str(), &err_msg); EXPECT_EQ(rv, new_update_interval) << err_msg; } TEST_F(KeycacheTest, SetGetExpirationTest) { char *err_msg = nullptr; int new_expiration_interval = 2 * 24 * 3600; std::string key = "keycache.expiration_interval_s"; auto rv = scitoken_config_set_int(key.c_str(), new_expiration_interval, &err_msg); ASSERT_TRUE(rv == 0) << err_msg; rv = scitoken_config_get_int(key.c_str(), &err_msg); EXPECT_EQ(rv, new_expiration_interval) << err_msg; } TEST_F(KeycacheTest, SetInvalidUpdateTest) { char *err_msg = nullptr; int new_update_interval = -1; std::string key = "keycache.update_interval_s"; auto rv = scitoken_config_set_int(key.c_str(), new_update_interval, &err_msg); free(err_msg); ASSERT_FALSE(rv == 0); } TEST_F(KeycacheTest, SetInvalidExpirationTest) { char *err_msg = nullptr; int new_expiration_interval = -2 * 24 * 3600; std::string key = "keycache.expiration_interval_s"; auto rv = scitoken_config_set_int(key.c_str(), new_expiration_interval, &err_msg); free(err_msg); ASSERT_FALSE(rv == 0); } TEST_F(KeycacheTest, RefreshExpiredTest) { char *err_msg = nullptr, *jwks; int new_expiration_interval = 0; std::string key = "keycache.expiration_interval_s"; auto rv = scitoken_config_set_int(key.c_str(), new_expiration_interval, &err_msg); ASSERT_TRUE(rv == 0) << err_msg; rv = keycache_refresh_jwks(demo_scitokens_url.c_str(), &err_msg); ASSERT_TRUE(rv == 0) << err_msg; sleep(1); rv = keycache_get_cached_jwks(demo_scitokens_url.c_str(), &jwks, &err_msg); ASSERT_TRUE(rv == 0) << err_msg; ASSERT_TRUE(jwks != nullptr); std::string jwks_str(jwks); free(jwks); EXPECT_EQ(jwks_str, "{\"keys\": []}"); } TEST_F(KeycacheTest, NegativeCacheTest) { // This test verifies that failed issuer lookups are cached as negative // entries and that subsequent attempts fail quickly with the right counter char *err_msg = nullptr; // Reset monitoring stats for clean baseline scitoken_reset_monitoring_stats(&err_msg); if (err_msg) { free(err_msg); err_msg = nullptr; } // Create a token with an issuer that will fail to lookup std::unique_ptr mykey( scitoken_key_create("1", "ES256", ec_public, ec_private, &err_msg), scitoken_key_destroy); ASSERT_TRUE(mykey.get() != nullptr) << err_msg; std::unique_ptr mytoken( scitoken_create(mykey.get()), scitoken_destroy); ASSERT_TRUE(mytoken.get() != nullptr); // Use a unique issuer that doesn't exist (will fail to fetch keys) // Include timestamp to avoid interference from previous test runs std::string invalid_issuer = "https://invalid-issuer-negative-cache-" + std::to_string(std::time(nullptr)) + ".example.com"; auto rv = scitoken_set_claim_string(mytoken.get(), "iss", invalid_issuer.c_str(), &err_msg); ASSERT_TRUE(rv == 0) << err_msg; char *token_value = nullptr; rv = scitoken_serialize(mytoken.get(), &token_value, &err_msg); ASSERT_TRUE(rv == 0) << err_msg; std::unique_ptr token_value_ptr(token_value, free); // First attempt should fail to fetch keys (DNS failure or connection // refused). This is a cache MISS (creates negative cache entry). std::unique_ptr read_token( scitoken_create(nullptr), scitoken_destroy); ASSERT_TRUE(read_token.get() != nullptr); rv = scitoken_deserialize_v2(token_value, read_token.get(), nullptr, &err_msg); ASSERT_FALSE(rv == 0); // Should fail if (err_msg) { free(err_msg); err_msg = nullptr; } // Check that a negative cache entry was created (returns empty keys) char *jwks; rv = keycache_get_cached_jwks(invalid_issuer.c_str(), &jwks, &err_msg); ASSERT_TRUE(rv == 0) << err_msg; ASSERT_TRUE(jwks != nullptr); std::string jwks_str(jwks); free(jwks); // Should return empty keys array (negative cache) EXPECT_EQ(jwks_str, "{\"keys\": []}"); // Second attempt should fail quickly using negative cache rv = scitoken_deserialize_v2(token_value, read_token.get(), nullptr, &err_msg); ASSERT_FALSE(rv == 0); // Should still fail ASSERT_TRUE(err_msg != nullptr); std::string error_msg(err_msg); free(err_msg); err_msg = nullptr; // Error message should indicate it's from negative cache EXPECT_NE(error_msg.find("negative cache"), std::string::npos) << "Error message should mention negative cache: " << error_msg; // Third attempt to verify counter increments correctly rv = scitoken_deserialize_v2(token_value, read_token.get(), nullptr, &err_msg); ASSERT_FALSE(rv == 0); if (err_msg) { free(err_msg); err_msg = nullptr; } // Get monitoring stats and verify negative_cache_hits counter char *json_out = nullptr; rv = scitoken_get_monitoring_json(&json_out, &err_msg); ASSERT_TRUE(rv == 0) << err_msg; ASSERT_TRUE(json_out != nullptr); std::string json_str(json_out); free(json_out); // Parse JSON and check negative_cache_hits // Only the second and third attempts should hit the negative cache: // - First attempt: creates negative cache (cache miss, not hit) // - Second and third attempts: hit existing negative cache EXPECT_NE(json_str.find("\"negative_cache_hits\""), std::string::npos) << "JSON should contain negative_cache_hits field"; // Verify 2 negative cache hits (attempts 2 and 3 only) EXPECT_NE(json_str.find("\"negative_cache_hits\":2"), std::string::npos) << "Should have 2 negative cache hits. JSON: " << json_str; } TEST_F(KeycacheTest, LoadJwksTest) { // Test load API - should return cached JWKS without triggering refresh char *err_msg = nullptr; char *jwks = nullptr; // Capture metadata before load to ensure no refresh changes it char *metadata_before = nullptr; auto rv = keycache_get_jwks_metadata(demo_scitokens_url.c_str(), &metadata_before, &err_msg); ASSERT_EQ(rv, 0) << (err_msg ? err_msg : ""); ASSERT_TRUE(metadata_before != nullptr); int64_t next_update_before = get_next_update_from_metadata(std::string(metadata_before)); free(metadata_before); if (err_msg) { free(err_msg); err_msg = nullptr; } // Load JWKS - should return cached version from SetUp() rv = keycache_load_jwks(demo_scitokens_url.c_str(), &jwks, &err_msg); ASSERT_TRUE(rv == 0) << (err_msg ? err_msg : "unknown error"); ASSERT_TRUE(jwks != nullptr); std::string jwks_str(jwks); free(jwks); if (err_msg) free(err_msg); EXPECT_EQ(demo_scitokens, jwks_str); // Metadata should be unchanged (no refresh triggered) char *metadata_after = nullptr; rv = keycache_get_jwks_metadata(demo_scitokens_url.c_str(), &metadata_after, &err_msg); ASSERT_EQ(rv, 0) << (err_msg ? err_msg : ""); ASSERT_TRUE(metadata_after != nullptr); int64_t next_update_after = get_next_update_from_metadata(std::string(metadata_after)); free(metadata_after); if (err_msg) free(err_msg); EXPECT_EQ(next_update_before, next_update_after); } TEST_F(KeycacheTest, LoadJwksMissingTest) { // Test load API with missing issuer - should attempt refresh char *err_msg = nullptr; char *jwks = nullptr; // Try to load a non-existent issuer - will fail to refresh auto rv = keycache_load_jwks("https://demo.scitokens.org/nonexistent", &jwks, &err_msg); ASSERT_FALSE(rv == 0); // Should fail since issuer doesn't exist if (err_msg) free(err_msg); } TEST_F(KeycacheTest, LoadJwksTriggersRefreshWhenStale) { // Force next_update in the past so load_jwks triggers a refresh char *err_msg = nullptr; // Reset monitoring to capture only this test's activity scitoken_reset_monitoring_stats(&err_msg); if (err_msg) { free(err_msg); err_msg = nullptr; } // Save and override update interval so next_update is "now" int original_update_interval = scitoken_config_get_int("keycache.update_interval_s", &err_msg); if (err_msg) { free(err_msg); err_msg = nullptr; } auto rv = scitoken_config_set_int("keycache.update_interval_s", 0, &err_msg); ASSERT_EQ(rv, 0) << (err_msg ? err_msg : ""); if (err_msg) { free(err_msg); err_msg = nullptr; } // Re-set JWKS so the stored next_update uses the new interval (now) rv = keycache_set_jwks(demo_scitokens_url.c_str(), demo_scitokens.c_str(), &err_msg); ASSERT_EQ(rv, 0) << (err_msg ? err_msg : ""); if (err_msg) { free(err_msg); err_msg = nullptr; } // Capture metadata before load to confirm next_update advances char *metadata_before = nullptr; rv = keycache_get_jwks_metadata(demo_scitokens_url.c_str(), &metadata_before, &err_msg); ASSERT_EQ(rv, 0) << (err_msg ? err_msg : ""); ASSERT_TRUE(metadata_before != nullptr); int64_t next_update_before = get_next_update_from_metadata(std::string(metadata_before)); free(metadata_before); if (err_msg) { free(err_msg); err_msg = nullptr; } // Ensure current time passes the stored next_update std::this_thread::sleep_for(std::chrono::seconds(1)); // Load JWKS - should detect stale entry and trigger refresh char *jwks = nullptr; rv = keycache_load_jwks(demo_scitokens_url.c_str(), &jwks, &err_msg); ASSERT_EQ(rv, 0) << (err_msg ? err_msg : ""); ASSERT_TRUE(jwks != nullptr); std::string jwks_str(jwks); free(jwks); if (err_msg) { free(err_msg); err_msg = nullptr; } EXPECT_EQ(demo_scitokens, jwks_str); // Verify next_update moved forward (refresh occurred) char *metadata_after = nullptr; rv = keycache_get_jwks_metadata(demo_scitokens_url.c_str(), &metadata_after, &err_msg); ASSERT_EQ(rv, 0) << (err_msg ? err_msg : ""); ASSERT_TRUE(metadata_after != nullptr); int64_t next_update_after = get_next_update_from_metadata(std::string(metadata_after)); free(metadata_after); if (err_msg) { free(err_msg); err_msg = nullptr; } EXPECT_GT(next_update_after, next_update_before); // Restore original update interval rv = scitoken_config_set_int("keycache.update_interval_s", original_update_interval, &err_msg); ASSERT_EQ(rv, 0) << (err_msg ? err_msg : ""); if (err_msg) free(err_msg); } TEST_F(KeycacheTest, GetMetadataTest) { // Test metadata API - should return expires and next_update char *err_msg = nullptr; char *metadata = nullptr; // Get metadata for cached issuer auto rv = keycache_get_jwks_metadata(demo_scitokens_url.c_str(), &metadata, &err_msg); ASSERT_TRUE(rv == 0) << (err_msg ? err_msg : "unknown error"); ASSERT_TRUE(metadata != nullptr); std::string metadata_str(metadata); free(metadata); if (err_msg) free(err_msg); // Verify JSON structure - should have expires and next_update fields EXPECT_NE(metadata_str.find("\"expires\":"), std::string::npos); EXPECT_NE(metadata_str.find("\"next_update\":"), std::string::npos); } TEST_F(KeycacheTest, GetMetadataMissingTest) { // Test metadata API with missing issuer char *err_msg = nullptr; char *metadata = nullptr; // Try to get metadata for non-existent issuer auto rv = keycache_get_jwks_metadata("https://demo.scitokens.org/unknown", &metadata, &err_msg); ASSERT_FALSE(rv == 0); // Should fail if (err_msg) free(err_msg); } TEST_F(KeycacheTest, DeleteJwksTest) { // Test delete API char *err_msg = nullptr; // First verify the issuer is in cache char *jwks = nullptr; auto rv = keycache_get_cached_jwks(demo_scitokens_url.c_str(), &jwks, &err_msg); ASSERT_TRUE(rv == 0) << (err_msg ? err_msg : "unknown error"); ASSERT_TRUE(jwks != nullptr); free(jwks); if (err_msg) { free(err_msg); err_msg = nullptr; } // Delete the entry rv = keycache_delete_jwks(demo_scitokens_url.c_str(), &err_msg); ASSERT_TRUE(rv == 0) << (err_msg ? err_msg : "unknown error"); if (err_msg) { free(err_msg); err_msg = nullptr; } // Verify it's gone - get_cached_jwks should return empty keys rv = keycache_get_cached_jwks(demo_scitokens_url.c_str(), &jwks, &err_msg); ASSERT_TRUE(rv == 0) << (err_msg ? err_msg : "unknown error"); ASSERT_TRUE(jwks != nullptr); std::string jwks_str(jwks); free(jwks); if (err_msg) free(err_msg); EXPECT_EQ(jwks_str, "{\"keys\": []}"); } TEST_F(KeycacheTest, DeleteJwksNonExistentTest) { // Test delete API with non-existent issuer - should not fail char *err_msg = nullptr; auto rv = keycache_delete_jwks("https://demo.scitokens.org/never-existed", &err_msg); ASSERT_TRUE(rv == 0) << (err_msg ? err_msg : "unknown error"); // Should succeed (idempotent) if (err_msg) free(err_msg); } class IssuerSecurityTest : public ::testing::Test { protected: void SetUp() override { char *err_msg = nullptr; m_key = KeyPtr( scitoken_key_create("1", "ES256", ec_public, ec_private, &err_msg), scitoken_key_destroy); ASSERT_TRUE(m_key.get() != nullptr) << err_msg; m_token = TokenPtr(scitoken_create(m_key.get()), scitoken_destroy); ASSERT_TRUE(m_token.get() != nullptr); // Store public key for verification auto rv = scitoken_store_public_ec_key( "https://demo.scitokens.org/gtest", "1", ec_public, &err_msg); ASSERT_TRUE(rv == 0) << err_msg; scitoken_set_lifetime(m_token.get(), 60); m_read_token.reset(scitoken_create(nullptr)); ASSERT_TRUE(m_read_token.get() != nullptr); } using KeyPtr = std::unique_ptr; KeyPtr m_key{nullptr, scitoken_key_destroy}; using TokenPtr = std::unique_ptr; TokenPtr m_token{nullptr, scitoken_destroy}; TokenPtr m_read_token{nullptr, scitoken_destroy}; }; TEST_F(IssuerSecurityTest, LongIssuerTruncation) { char *err_msg = nullptr; // Create a very long issuer (1000 characters) std::string very_long_issuer(1000, 'A'); auto rv = scitoken_set_claim_string(m_token.get(), "iss", very_long_issuer.c_str(), &err_msg); ASSERT_TRUE(rv == 0) << err_msg; char *token_value = nullptr; rv = scitoken_serialize(m_token.get(), &token_value, &err_msg); ASSERT_TRUE(rv == 0) << err_msg; std::unique_ptr token_value_ptr(token_value, free); // Try to verify with a restricted issuer list to trigger error const char *allowed_issuers[] = {"https://good.issuer.com", nullptr}; rv = scitoken_deserialize_v2(token_value, m_read_token.get(), allowed_issuers, &err_msg); // Should fail ASSERT_FALSE(rv == 0); ASSERT_TRUE(err_msg != nullptr); std::string error_message(err_msg); std::unique_ptr err_msg_ptr(err_msg, free); // Error message should be reasonable length (< 400 chars) EXPECT_LT(error_message.length(), 400); // Should contain expected error text EXPECT_NE(error_message.find("is not in list of allowed issuers"), std::string::npos); // Should contain truncated issuer with ellipsis EXPECT_NE(error_message.find("..."), std::string::npos); } TEST_F(IssuerSecurityTest, SpecialCharacterIssuer) { char *err_msg = nullptr; // Create an issuer with special characters and control chars std::string special_issuer = "https://bad.com/\"\n\t\r\x01\x1f"; auto rv = scitoken_set_claim_string(m_token.get(), "iss", special_issuer.c_str(), &err_msg); ASSERT_TRUE(rv == 0) << err_msg; char *token_value = nullptr; rv = scitoken_serialize(m_token.get(), &token_value, &err_msg); ASSERT_TRUE(rv == 0) << err_msg; std::unique_ptr token_value_ptr(token_value, free); // Try to verify with a restricted issuer list to trigger error const char *allowed_issuers[] = {"https://good.issuer.com", nullptr}; rv = scitoken_deserialize_v2(token_value, m_read_token.get(), allowed_issuers, &err_msg); // Should fail ASSERT_FALSE(rv == 0); ASSERT_TRUE(err_msg != nullptr); std::string error_message(err_msg); std::unique_ptr err_msg_ptr(err_msg, free); // Error message should be reasonable length EXPECT_LT(error_message.length(), 300); // Should contain expected error text EXPECT_NE(error_message.find("is not in list of allowed issuers"), std::string::npos); // Should contain properly escaped JSON (with quotes) EXPECT_NE(error_message.find("\""), std::string::npos); } // Test suite for environment variable configuration class EnvConfigTest : public ::testing::Test { protected: void SetUp() override { // Save original config values char *err_msg = nullptr; original_update_interval = scitoken_config_get_int("keycache.update_interval_s", &err_msg); original_expiry_interval = scitoken_config_get_int("keycache.expiration_interval_s", &err_msg); char *cache_home = nullptr; scitoken_config_get_str("keycache.cache_home", &cache_home, &err_msg); if (cache_home) { original_cache_home = cache_home; free(cache_home); } char *ca_file = nullptr; scitoken_config_get_str("tls.ca_file", &ca_file, &err_msg); if (ca_file) { original_ca_file = ca_file; free(ca_file); } } void TearDown() override { // Restore original config values char *err_msg = nullptr; scitoken_config_set_int("keycache.update_interval_s", original_update_interval, &err_msg); scitoken_config_set_int("keycache.expiration_interval_s", original_expiry_interval, &err_msg); scitoken_config_set_str("keycache.cache_home", original_cache_home.c_str(), &err_msg); scitoken_config_set_str("tls.ca_file", original_ca_file.c_str(), &err_msg); } int original_update_interval = 600; int original_expiry_interval = 4 * 24 * 3600; std::string original_cache_home; std::string original_ca_file; }; TEST_F(EnvConfigTest, IntConfigFromEnv) { // Note: This test verifies that the environment variable was read at // library load time We can't test setting environment variables after // library load in the same process This test would need to be run with // environment variables set before starting the test // Test that we can manually set and get config values char *err_msg = nullptr; int test_value = 1234; auto rv = scitoken_config_set_int("keycache.update_interval_s", test_value, &err_msg); ASSERT_EQ(rv, 0) << (err_msg ? err_msg : ""); int retrieved = scitoken_config_get_int("keycache.update_interval_s", &err_msg); EXPECT_EQ(retrieved, test_value) << (err_msg ? err_msg : ""); if (err_msg) free(err_msg); } TEST_F(EnvConfigTest, StringConfigFromEnv) { // Test that we can manually set and get string config values // Use a secure temp directory instead of hardcoded /tmp path SecureTempDir temp_cache("env_config_test_"); ASSERT_TRUE(temp_cache.valid()) << "Failed to create temp directory"; char *err_msg = nullptr; auto rv = scitoken_config_set_str("keycache.cache_home", temp_cache.path().c_str(), &err_msg); ASSERT_EQ(rv, 0) << (err_msg ? err_msg : ""); char *output = nullptr; rv = scitoken_config_get_str("keycache.cache_home", &output, &err_msg); ASSERT_EQ(rv, 0) << (err_msg ? err_msg : ""); ASSERT_TRUE(output != nullptr); EXPECT_STREQ(output, temp_cache.path().c_str()); free(output); if (err_msg) free(err_msg); // temp_cache destructor will clean up the directory } // Test for thundering herd prevention with per-issuer locks TEST_F(IssuerSecurityTest, ThunderingHerdPrevention) { char *err_msg = nullptr; // Create tokens for a new issuer and pre-populate the cache std::string test_issuer = "https://thundering-herd-test.example.org/gtest"; auto rv = scitoken_set_claim_string(m_token.get(), "iss", test_issuer.c_str(), &err_msg); ASSERT_TRUE(rv == 0) << err_msg; // Store public key for this issuer in the cache rv = scitoken_store_public_ec_key(test_issuer.c_str(), "1", ec_public, &err_msg); ASSERT_TRUE(rv == 0) << err_msg; char *token_value = nullptr; rv = scitoken_serialize(m_token.get(), &token_value, &err_msg); ASSERT_TRUE(rv == 0) << err_msg; std::unique_ptr token_value_ptr(token_value, free); // Successfully deserialize - the per-issuer lock should prevent thundering // herd Since we pre-populated the cache, this should succeed without // network access rv = scitoken_deserialize_v2(token_value, m_read_token.get(), nullptr, &err_msg); ASSERT_TRUE(rv == 0) << err_msg; // Verify the issuer claim char *value; rv = scitoken_get_claim_string(m_read_token.get(), "iss", &value, &err_msg); ASSERT_TRUE(rv == 0) << err_msg; ASSERT_TRUE(value != nullptr); std::unique_ptr value_ptr(value, free); EXPECT_STREQ(value, test_issuer.c_str()); } int main(int argc, char **argv) { ::testing::InitGoogleTest(&argc, argv); return RUN_ALL_TESTS(); } scitokens-cpp-1.3.0/test/monitoring_test.cpp000066400000000000000000000354671513647275500212420ustar00rootroot00000000000000/** * Monitoring API unit tests * * Tests the monitoring API for per-issuer validation statistics including: * - Counter increments for successful/unsuccessful validations * - Duration tracking * - Failed issuer lookup tracking * - DDoS protection (max entries limit) * - Reset functionality */ #include "../src/scitokens.h" #include #include #include #ifndef PICOJSON_USE_INT64 #define PICOJSON_USE_INT64 #endif #include namespace { // Helper class to parse monitoring JSON class MonitoringStats { public: struct IssuerStats { uint64_t successful_validations{0}; uint64_t unsuccessful_validations{0}; uint64_t expired_tokens{0}; // Validation started counters uint64_t sync_validations_started{0}; uint64_t async_validations_started{0}; // Duration tracking double sync_total_time_s{0.0}; double async_total_time_s{0.0}; double total_validation_time_s{0.0}; // Key lookup statistics uint64_t successful_key_lookups{0}; uint64_t failed_key_lookups{0}; double failed_key_lookup_time_s{0.0}; // Key refresh statistics uint64_t expired_keys{0}; uint64_t failed_refreshes{0}; uint64_t stale_key_uses{0}; }; struct FailedIssuerLookup { uint64_t count{0}; double total_time_s{0.0}; }; bool parse(const std::string &json) { picojson::value root; std::string err = picojson::parse(root, json); if (!err.empty()) { return false; } if (!root.is()) { return false; } auto &root_obj = root.get(); // Parse issuers issuers_.clear(); auto issuers_it = root_obj.find("issuers"); if (issuers_it != root_obj.end() && issuers_it->second.is()) { auto &issuers_obj = issuers_it->second.get(); for (const auto &issuer_entry : issuers_obj) { if (issuer_entry.second.is()) { IssuerStats stats; auto &stats_obj = issuer_entry.second.get(); auto it = stats_obj.find("successful_validations"); if (it != stats_obj.end() && it->second.is()) { stats.successful_validations = static_cast(it->second.get()); } it = stats_obj.find("unsuccessful_validations"); if (it != stats_obj.end() && it->second.is()) { stats.unsuccessful_validations = static_cast(it->second.get()); } it = stats_obj.find("expired_tokens"); if (it != stats_obj.end() && it->second.is()) { stats.expired_tokens = static_cast(it->second.get()); } // Validation started counters it = stats_obj.find("sync_validations_started"); if (it != stats_obj.end() && it->second.is()) { stats.sync_validations_started = static_cast(it->second.get()); } it = stats_obj.find("async_validations_started"); if (it != stats_obj.end() && it->second.is()) { stats.async_validations_started = static_cast(it->second.get()); } // Duration tracking it = stats_obj.find("sync_total_time_s"); if (it != stats_obj.end() && it->second.is()) { stats.sync_total_time_s = it->second.get(); } it = stats_obj.find("async_total_time_s"); if (it != stats_obj.end() && it->second.is()) { stats.async_total_time_s = it->second.get(); } it = stats_obj.find("total_validation_time_s"); if (it != stats_obj.end() && it->second.is()) { stats.total_validation_time_s = it->second.get(); } // Key lookup statistics it = stats_obj.find("successful_key_lookups"); if (it != stats_obj.end() && it->second.is()) { stats.successful_key_lookups = static_cast(it->second.get()); } it = stats_obj.find("failed_key_lookups"); if (it != stats_obj.end() && it->second.is()) { stats.failed_key_lookups = static_cast(it->second.get()); } it = stats_obj.find("failed_key_lookup_time_s"); if (it != stats_obj.end() && it->second.is()) { stats.failed_key_lookup_time_s = it->second.get(); } // Key refresh statistics it = stats_obj.find("expired_keys"); if (it != stats_obj.end() && it->second.is()) { stats.expired_keys = static_cast(it->second.get()); } it = stats_obj.find("failed_refreshes"); if (it != stats_obj.end() && it->second.is()) { stats.failed_refreshes = static_cast(it->second.get()); } it = stats_obj.find("stale_key_uses"); if (it != stats_obj.end() && it->second.is()) { stats.stale_key_uses = static_cast(it->second.get()); } issuers_[issuer_entry.first] = stats; } } } // Parse failed issuer lookups (now has count and total_time_s) failed_issuer_lookups_.clear(); auto failed_it = root_obj.find("failed_issuer_lookups"); if (failed_it != root_obj.end() && failed_it->second.is()) { auto &failed_obj = failed_it->second.get(); for (const auto &entry : failed_obj) { if (entry.second.is()) { FailedIssuerLookup lookup; auto &lookup_obj = entry.second.get(); auto it = lookup_obj.find("count"); if (it != lookup_obj.end() && it->second.is()) { lookup.count = static_cast(it->second.get()); } it = lookup_obj.find("total_time_s"); if (it != lookup_obj.end() && it->second.is()) { lookup.total_time_s = it->second.get(); } failed_issuer_lookups_[entry.first] = lookup; } } } return true; } IssuerStats getIssuerStats(const std::string &issuer) const { auto it = issuers_.find(issuer); if (it != issuers_.end()) { return it->second; } return IssuerStats{}; } FailedIssuerLookup getFailedLookup(const std::string &issuer) const { auto it = failed_issuer_lookups_.find(issuer); if (it != failed_issuer_lookups_.end()) { return it->second; } return FailedIssuerLookup{}; } uint64_t getFailedLookupCount(const std::string &issuer) const { return getFailedLookup(issuer).count; } double getFailedLookupTime(const std::string &issuer) const { return getFailedLookup(issuer).total_time_s; } size_t getIssuerCount() const { return issuers_.size(); } size_t getFailedIssuerCount() const { return failed_issuer_lookups_.size(); } private: std::map issuers_; std::map failed_issuer_lookups_; }; // Helper to get current monitoring stats MonitoringStats getCurrentStats() { char *json_out = nullptr; char *err_msg = nullptr; MonitoringStats stats; int rv = scitoken_get_monitoring_json(&json_out, &err_msg); if (rv == 0 && json_out) { stats.parse(json_out); free(json_out); } if (err_msg) free(err_msg); return stats; } class MonitoringTest : public ::testing::Test { protected: void SetUp() override { // Reset monitoring stats before each test char *err_msg = nullptr; scitoken_reset_monitoring_stats(&err_msg); if (err_msg) free(err_msg); } }; TEST_F(MonitoringTest, GetMonitoringJson) { char *json_out = nullptr; char *err_msg = nullptr; int rv = scitoken_get_monitoring_json(&json_out, &err_msg); ASSERT_EQ(rv, 0) << "Failed to get monitoring JSON: " << (err_msg ? err_msg : "unknown"); ASSERT_NE(json_out, nullptr); // Should be valid JSON with "issuers" key MonitoringStats stats; EXPECT_TRUE(stats.parse(json_out)); EXPECT_EQ(stats.getIssuerCount(), 0); // Should be empty after reset free(json_out); if (err_msg) free(err_msg); } TEST_F(MonitoringTest, GetMonitoringJsonNullOutput) { char *err_msg = nullptr; int rv = scitoken_get_monitoring_json(nullptr, &err_msg); EXPECT_NE(rv, 0); EXPECT_NE(err_msg, nullptr); if (err_msg) free(err_msg); } TEST_F(MonitoringTest, ResetMonitoringStats) { char *err_msg = nullptr; int rv = scitoken_reset_monitoring_stats(&err_msg); EXPECT_EQ(rv, 0); auto stats = getCurrentStats(); EXPECT_EQ(stats.getIssuerCount(), 0); EXPECT_EQ(stats.getFailedIssuerCount(), 0); if (err_msg) free(err_msg); } TEST_F(MonitoringTest, DDoSProtection) { // The monitoring system should limit tracking failed issuers to // MAX_FAILED_ISSUERS (100) const int DDOS_TEST_COUNT = 150; char *err_msg = nullptr; // Try to create many tokens with different invalid issuers for (int i = 0; i < DDOS_TEST_COUNT; i++) { std::string fake_token = "invalid.token." + std::to_string(i); SciToken temp_token = nullptr; scitoken_deserialize(fake_token.c_str(), &temp_token, nullptr, &err_msg); if (err_msg) { free(err_msg); err_msg = nullptr; } } auto stats = getCurrentStats(); // The system should have limited entries to prevent resource exhaustion // We can't check exact count since malformed tokens may fail before issuer // extraction, but we should verify the system didn't crash and stats work char *json_out = nullptr; int rv = scitoken_get_monitoring_json(&json_out, &err_msg); EXPECT_EQ(rv, 0); EXPECT_NE(json_out, nullptr); if (json_out) free(json_out); if (err_msg) free(err_msg); } // Test monitoring file configuration API TEST_F(MonitoringTest, MonitoringFileConfiguration) { char *err_msg = nullptr; char *path = nullptr; int interval = 0; // Initially disabled (empty string) int rv = scitoken_config_get_str("monitoring.file", &path, &err_msg); EXPECT_EQ(rv, 0); EXPECT_NE(path, nullptr); EXPECT_STREQ(path, ""); free(path); path = nullptr; // Default interval should be 60 seconds interval = scitoken_config_get_int("monitoring.file_interval_s", &err_msg); EXPECT_EQ(interval, 60); // Set a monitoring file path rv = scitoken_config_set_str( "monitoring.file", "/tmp/scitokens_test_monitoring.json", &err_msg); EXPECT_EQ(rv, 0); rv = scitoken_config_get_str("monitoring.file", &path, &err_msg); EXPECT_EQ(rv, 0); EXPECT_NE(path, nullptr); EXPECT_STREQ(path, "/tmp/scitokens_test_monitoring.json"); free(path); path = nullptr; // Set a custom interval rv = scitoken_config_set_int("monitoring.file_interval_s", 30, &err_msg); EXPECT_EQ(rv, 0); interval = scitoken_config_get_int("monitoring.file_interval_s", &err_msg); EXPECT_EQ(interval, 30); // Disable by setting to empty string rv = scitoken_config_set_str("monitoring.file", "", &err_msg); EXPECT_EQ(rv, 0); rv = scitoken_config_get_str("monitoring.file", &path, &err_msg); EXPECT_EQ(rv, 0); EXPECT_NE(path, nullptr); EXPECT_STREQ(path, ""); free(path); path = nullptr; // Disable by setting to nullptr rv = scitoken_config_set_str("monitoring.file", nullptr, &err_msg); EXPECT_EQ(rv, 0); rv = scitoken_config_get_str("monitoring.file", &path, &err_msg); EXPECT_EQ(rv, 0); EXPECT_NE(path, nullptr); EXPECT_STREQ(path, ""); free(path); path = nullptr; // Reset interval to default for other tests scitoken_config_set_int("monitoring.file_interval_s", 60, &err_msg); } // Test monitoring file write with zero interval (immediate write) TEST_F(MonitoringTest, MonitoringFileWrite) { char *err_msg = nullptr; // Set up a test file path and zero interval for immediate write std::string test_file = "/tmp/scitokens_monitoring_test_" + std::to_string(time(nullptr)) + ".json"; scitoken_config_set_str("monitoring.file", test_file.c_str(), &err_msg); scitoken_config_set_int("monitoring.file_interval_s", 0, &err_msg); // Clean up any existing file std::remove(test_file.c_str()); // Reset stats and record something scitoken_reset_monitoring_stats(&err_msg); // The maybe_write_monitoring_file is called during verify(), but we can't // easily trigger that without a valid token/issuer. However, we can test // the configuration API works and that files aren't written when disabled. // Verify file doesn't exist yet (nothing to trigger write) FILE *f = fopen(test_file.c_str(), "r"); EXPECT_EQ(f, nullptr); // File should not exist // Disable monitoring file scitoken_config_set_str("monitoring.file", "", &err_msg); scitoken_config_set_int("monitoring.file_interval_s", 60, &err_msg); // Clean up test file if it was created std::remove(test_file.c_str()); } } // namespace int main(int argc, char **argv) { ::testing::InitGoogleTest(&argc, argv); return RUN_ALL_TESTS(); } scitokens-cpp-1.3.0/test/test_env_config.cpp000066400000000000000000000136711513647275500211630ustar00rootroot00000000000000/** * Test program to verify environment variable configuration loading. * This must be run as a separate process with environment variables set * before the library is loaded to properly test the constructor function. */ #include "scitokens.h" #include #include #include #include int main() { int failures = 0; char *err_msg = nullptr; // Test 1: Check if SCITOKEN_CONFIG_KEYCACHE_UPDATE_INTERVAL_S was loaded const char *env_update = std::getenv("SCITOKEN_CONFIG_KEYCACHE_UPDATE_INTERVAL_S"); if (env_update) { try { int expected = std::stoi(env_update); int actual = scitoken_config_get_int("keycache.update_interval_s", &err_msg); if (actual != expected) { std::cerr << "FAIL: keycache.update_interval_s expected " << expected << " but got " << actual << std::endl; if (err_msg) { std::cerr << "Error: " << err_msg << std::endl; free(err_msg); err_msg = nullptr; } failures++; } else { std::cout << "PASS: keycache.update_interval_s = " << actual << std::endl; } } catch (const std::exception &e) { std::cerr << "FAIL: Could not parse env var value: " << e.what() << std::endl; failures++; } } // Test 2: Check if SCITOKEN_CONFIG_KEYCACHE_EXPIRATION_INTERVAL_S was // loaded const char *env_expiry = std::getenv("SCITOKEN_CONFIG_KEYCACHE_EXPIRATION_INTERVAL_S"); if (env_expiry) { try { int expected = std::stoi(env_expiry); int actual = scitoken_config_get_int( "keycache.expiration_interval_s", &err_msg); if (actual != expected) { std::cerr << "FAIL: keycache.expiration_interval_s expected " << expected << " but got " << actual << std::endl; if (err_msg) { std::cerr << "Error: " << err_msg << std::endl; free(err_msg); err_msg = nullptr; } failures++; } else { std::cout << "PASS: keycache.expiration_interval_s = " << actual << std::endl; } } catch (const std::exception &e) { std::cerr << "FAIL: Could not parse env var value: " << e.what() << std::endl; failures++; } } // Test 3: Check if SCITOKEN_CONFIG_KEYCACHE_CACHE_HOME was loaded const char *env_cache = std::getenv("SCITOKEN_CONFIG_KEYCACHE_CACHE_HOME"); if (env_cache) { char *actual = nullptr; int rv = scitoken_config_get_str("keycache.cache_home", &actual, &err_msg); if (rv != 0 || !actual) { std::cerr << "FAIL: Could not retrieve keycache.cache_home" << std::endl; if (err_msg) { std::cerr << "Error: " << err_msg << std::endl; free(err_msg); err_msg = nullptr; } failures++; } else if (strcmp(actual, env_cache) != 0) { std::cerr << "FAIL: keycache.cache_home expected '" << env_cache << "' but got '" << actual << "'" << std::endl; failures++; } else { std::cout << "PASS: keycache.cache_home = " << actual << std::endl; } if (actual) free(actual); } // Test 4: Check if SCITOKEN_CONFIG_TLS_CA_FILE was loaded const char *env_ca = std::getenv("SCITOKEN_CONFIG_TLS_CA_FILE"); if (env_ca) { char *actual = nullptr; int rv = scitoken_config_get_str("tls.ca_file", &actual, &err_msg); if (rv != 0 || !actual) { std::cerr << "FAIL: Could not retrieve tls.ca_file" << std::endl; if (err_msg) { std::cerr << "Error: " << err_msg << std::endl; free(err_msg); err_msg = nullptr; } failures++; } else if (strcmp(actual, env_ca) != 0) { std::cerr << "FAIL: tls.ca_file expected '" << env_ca << "' but got '" << actual << "'" << std::endl; failures++; } else { std::cout << "PASS: tls.ca_file = " << actual << std::endl; } if (actual) free(actual); } // Test 5: Test case insensitivity (lowercase env var) const char *env_lower = std::getenv("scitoken_config_keycache_update_interval_s"); if (env_lower) { try { int expected = std::stoi(env_lower); int actual = scitoken_config_get_int("keycache.update_interval_s", &err_msg); if (actual != expected) { std::cerr << "FAIL: lowercase env var - " "keycache.update_interval_s expected " << expected << " but got " << actual << std::endl; if (err_msg) { std::cerr << "Error: " << err_msg << std::endl; free(err_msg); err_msg = nullptr; } failures++; } else { std::cout << "PASS: lowercase env var - keycache.update_interval_s = " << actual << std::endl; } } catch (const std::exception &e) { std::cerr << "FAIL: Could not parse env var value: " << e.what() << std::endl; failures++; } } if (failures == 0) { std::cout << "\nAll environment variable configuration tests passed!" << std::endl; return 0; } else { std::cerr << "\n" << failures << " test(s) failed!" << std::endl; return 1; } } scitokens-cpp-1.3.0/test/test_utils.h000066400000000000000000000100771513647275500176500ustar00rootroot00000000000000#ifndef SCITOKENS_TEST_UTILS_H #define SCITOKENS_TEST_UTILS_H #include #include #include #include #include #include #include namespace scitokens_test { /** * Helper class to create and manage secure temporary directories. * Uses mkdtemp for security and cleans up on destruction. * * Example usage: * SecureTempDir temp_dir("my_test_"); * ASSERT_TRUE(temp_dir.valid()); * std::string cache_path = temp_dir.path() + "/cache"; * // ... use the directory ... * // Directory is automatically cleaned up when temp_dir goes out of scope */ class SecureTempDir { public: /** * Create a temp directory under the specified base path. * @param prefix Prefix for the directory name (default: "scitokens_test_") * @param base_path Base path for the temp directory. If empty, uses * BINARY_DIR/tests (from CMake) or falls back to cwd/tests */ explicit SecureTempDir(const std::string &prefix = "scitokens_test_", const std::string &base_path = "") { std::string base = base_path; if (base.empty()) { // Try to use build/tests directory (set by CMake) const char *binary_dir = std::getenv("BINARY_DIR"); if (binary_dir) { base = std::string(binary_dir) + "/tests"; } else { // Fallback: use current working directory + tests char cwd[PATH_MAX]; if (getcwd(cwd, sizeof(cwd))) { base = std::string(cwd) + "/tests"; } else { base = "/tmp"; // Last resort fallback } } } // Ensure base directory exists mkdir(base.c_str(), 0700); // Create template for mkdtemp std::string tmpl = base + "/" + prefix + "XXXXXX"; std::vector tmpl_buf(tmpl.begin(), tmpl.end()); tmpl_buf.push_back('\0'); char *result = mkdtemp(tmpl_buf.data()); if (result) { path_ = result; } } ~SecureTempDir() { cleanup(); } // Delete copy constructor and assignment SecureTempDir(const SecureTempDir &) = delete; SecureTempDir &operator=(const SecureTempDir &) = delete; // Allow move SecureTempDir(SecureTempDir &&other) noexcept : path_(std::move(other.path_)) { other.path_.clear(); } SecureTempDir &operator=(SecureTempDir &&other) noexcept { if (this != &other) { cleanup(); path_ = std::move(other.path_); other.path_.clear(); } return *this; } /** Get the path to the temporary directory */ const std::string &path() const { return path_; } /** Check if the directory was created successfully */ bool valid() const { return !path_.empty(); } /** Manually trigger cleanup (also called by destructor) */ void cleanup() { if (!path_.empty()) { remove_directory_recursive(path_); path_.clear(); } } private: std::string path_; /** * Safely remove a directory recursively using fork/execv. * This prevents shell injection attacks that could occur with system(). */ static void remove_directory_recursive(const std::string &path) { pid_t pid = fork(); if (pid == 0) { // Child process: exec rm -rf with path as direct argument // Using execv prevents any shell interpretation of the path char *const args[] = {const_cast("rm"), const_cast("-rf"), const_cast(path.c_str()), nullptr}; execv("/bin/rm", args); _exit(1); // execv failed } else if (pid > 0) { // Parent: wait for child to complete int status; waitpid(pid, &status, 0); } // If fork failed, silently ignore (cleanup is best-effort) } }; } // namespace scitokens_test #endif // SCITOKENS_TEST_UTILS_H scitokens-cpp-1.3.0/vendor/000077500000000000000000000000001513647275500156115ustar00rootroot00000000000000scitokens-cpp-1.3.0/vendor/.clang-format000066400000000000000000000000501513647275500201570ustar00rootroot00000000000000DisableFormat: true SortIncludes: Never scitokens-cpp-1.3.0/vendor/gtest/000077500000000000000000000000001513647275500167375ustar00rootroot00000000000000scitokens-cpp-1.3.0/vendor/jwt-cpp/000077500000000000000000000000001513647275500171755ustar00rootroot00000000000000scitokens-cpp-1.3.0/vendor/jwt-cpp/.editorconfig000066400000000000000000000004411513647275500216510ustar00rootroot00000000000000root = true [!*.{h,cpp}] indent_style = space indent_size = 2 end_of_line = lf charset = utf-8 trim_trailing_whitespace = true insert_final_newline = true [*.{h,cpp}] indent_style = tab trim_trailing_whitespace = true insert_final_newline = true [*.md] trim_trailing_whitespace = false scitokens-cpp-1.3.0/vendor/jwt-cpp/.gitattributes000066400000000000000000000005721513647275500220740ustar00rootroot00000000000000# Auto detect text files and perform LF normalization * text=auto # Custom for Visual Studio *.cs diff=csharp # Standard to msysgit *.doc diff=astextplain *.DOC diff=astextplain *.docx diff=astextplain *.DOCX diff=astextplain *.dot diff=astextplain *.DOT diff=astextplain *.pdf diff=astextplain *.PDF diff=astextplain *.rtf diff=astextplain *.RTF diff=astextplain scitokens-cpp-1.3.0/vendor/jwt-cpp/.github/000077500000000000000000000000001513647275500205355ustar00rootroot00000000000000scitokens-cpp-1.3.0/vendor/jwt-cpp/.github/FUNDING.yml000066400000000000000000000000701513647275500223470ustar00rootroot00000000000000github: [Thalhammer,prince-chrismc] patreon: Thalhammer scitokens-cpp-1.3.0/vendor/jwt-cpp/.github/ISSUE_TEMPLATE/000077500000000000000000000000001513647275500227205ustar00rootroot00000000000000scitokens-cpp-1.3.0/vendor/jwt-cpp/.github/ISSUE_TEMPLATE/bug-report.yml000066400000000000000000000044361513647275500255400ustar00rootroot00000000000000name: Bug Report 🐛 description: File a bug report labels: ["bug"] body: - type: markdown attributes: value: | Thanks for taking the time to fill out this bug report! validations: required: false - type: textarea id: what-happened attributes: label: What happened? description: Also tell us, what did you expect to happen? Feel free to include some screenshots placeholder: Tell us what you see! value: "A bug happened!" validations: required: true - type: textarea id: reproduce attributes: label: How To Reproduce? description: Please provide a small snippet to reproduce the issue placeholder: Some C++ code or Shell code to recreate th problem value: | ```c++ #include "jwt-cpp/jwt.h" int main() { return 0; } ``` - type: dropdown id: version attributes: label: Version description: What version of our software are you running? options: - 0.7.1 - 0.7.0 - 0.6.0 - 0.5.2 - Older (please let us know if the "What happened" box) validations: required: true - type: dropdown id: operating-system attributes: label: What OS are you seeing the problem on? multiple: true options: - Windows - Linux - MacOS - Other (please let us know if the "What happened" box) validations: required: true - type: dropdown id: compiler attributes: label: What compiler are you seeing the problem on? multiple: true options: - GCC - Clang - MSVC - Other (please let us know if the "What happened" box) validations: required: true - type: textarea id: logs attributes: label: Relevant log output description: Please copy and paste any relevant log output. This will be automatically formatted into code, so no need for backticks. render: shell - type: checkboxes id: terms attributes: label: Code of Conduct description: By submitting this issue, you agree to follow our [Code of Conduct](https://example.com) options: - label: I agree to follow this project's Code of Conduct required: true scitokens-cpp-1.3.0/vendor/jwt-cpp/.github/ISSUE_TEMPLATE/feature-request.yml000066400000000000000000000007171513647275500265710ustar00rootroot00000000000000name: Feature Request 🧪 description: Have a great idea? Find something is missing? labels: ["enhancement"] body: - type: markdown attributes: value: | We'd love to hear your idea(s)! - type: input id: question attributes: label: "What would you like to see added?" validations: required: true - type: textarea id: context attributes: label: Additional Context validations: required: true scitokens-cpp-1.3.0/vendor/jwt-cpp/.github/ISSUE_TEMPLATE/support-question.yml000066400000000000000000000007161513647275500270300ustar00rootroot00000000000000name: Support Question 🤹 description: Have some questions? We can offer help. labels: ["question"] body: - type: markdown attributes: value: | Don't hesitate to ask any question you might have! - type: input id: question attributes: label: "What's your question?" validations: required: true - type: textarea id: context attributes: label: Additional Context validations: required: true scitokens-cpp-1.3.0/vendor/jwt-cpp/.github/actions/000077500000000000000000000000001513647275500221755ustar00rootroot00000000000000scitokens-cpp-1.3.0/vendor/jwt-cpp/.github/actions/badge/000077500000000000000000000000001513647275500232375ustar00rootroot00000000000000scitokens-cpp-1.3.0/vendor/jwt-cpp/.github/actions/badge/action.yml000066400000000000000000000016631513647275500252450ustar00rootroot00000000000000name: Regular badging sequence description: Publishes a badge based on the job status inputs: category: description: The subfolder where to group the badges required: true label: description: The label to you in the badge (this should be unique for each badge in a category) required: true github_token: description: The token to use to publish the changes required: false default: ${{ github.token }} runs: using: composite steps: - if: job.status == 'success' uses: ./.github/actions/badge/write with: category: ${{ inputs.category }} label: ${{ inputs.label }} - if: job.status == 'failure' uses: ./.github/actions/badge/write with: category: ${{ inputs.category }} label: ${{ inputs.label }} message: Failing color: red - uses: ./.github/actions/badge/publish with: github_token: ${{ inputs.github_token }} scitokens-cpp-1.3.0/vendor/jwt-cpp/.github/actions/badge/write/000077500000000000000000000000001513647275500243715ustar00rootroot00000000000000scitokens-cpp-1.3.0/vendor/jwt-cpp/.github/actions/badge/write/action.yml000066400000000000000000000016071513647275500263750ustar00rootroot00000000000000name: Make a Badge description: Creates a JSON file for use with Sheilds.io. The default is a "brightgreen" "Passing" badge inputs: category: description: The subfolder where to group the badges required: true label: description: The label to you in the badge (this should be unqie for each badge in a category) required: true message: description: The message you wish to have in the badge required: false default: "Passing" color: description: The color you wish the badge to be required: false default: "brightgreen" runs: using: composite steps: - run: | mkdir -p badges/${{ inputs.category }}/${{ inputs.label }} echo '{ "schemaVersion": 1, "label": "${{ inputs.label }}", "message": "${{ inputs.message }}", "color": "${{ inputs.color }}" }' > badges/${{ inputs.category }}/${{ inputs.label }}/shields.json shell: bash scitokens-cpp-1.3.0/vendor/jwt-cpp/.github/actions/install/000077500000000000000000000000001513647275500236435ustar00rootroot00000000000000scitokens-cpp-1.3.0/vendor/jwt-cpp/.github/actions/install/boost-json/000077500000000000000000000000001513647275500257405ustar00rootroot00000000000000scitokens-cpp-1.3.0/vendor/jwt-cpp/.github/actions/install/boost-json/action.yml000066400000000000000000000010231513647275500277340ustar00rootroot00000000000000name: Install Boost.JSON description: Install Boost.JSON for building test application inputs: version: description: The desired Boost.JSON version to install required: false default: "1.78.0" runs: using: composite steps: - run: | cd /tmp wget https://github.com/boostorg/json/archive/boost-${{ inputs.version }}.tar.gz tar -zxf /tmp/boost-${{ inputs.version }}.tar.gz cd json-boost-${{ inputs.version }} sudo cp -vR include/boost /usr/local/include shell: bash scitokens-cpp-1.3.0/vendor/jwt-cpp/.github/actions/install/cmake/000077500000000000000000000000001513647275500247235ustar00rootroot00000000000000scitokens-cpp-1.3.0/vendor/jwt-cpp/.github/actions/install/cmake/action.yml000066400000000000000000000023151513647275500267240ustar00rootroot00000000000000name: Install CMake description: Download, Build and Cache CMake inputs: version: description: The desired CMake version to install required: true url: description: "The corresponding URL to download the source code from" required: true runs: using: composite steps: - name: Cache CMake id: cache-cmake uses: actions/cache@v3 with: path: cmake-${{ inputs.version }} key: ${{ runner.name }}-${{ runner.os }}-${{ runner.arch }}-${{ job.container.id }}-cmake-${{ inputs.version }} - name: Build cmake if: steps.cache-cmake.outputs.cache-hit != 'true' run: | wget ${{ inputs.url }} tar -zxf cmake-${{ inputs.version }}.tar.gz cd cmake-${{ inputs.version }} ./bootstrap make -j $(nproc) shell: bash - name: Install cmake run: | cd cmake-${{ inputs.version }} # Depending if we run in on a GitHub Actions or from within a Docker image we have different permissions if [[ $EUID > 0 ]]; then # If we are not root then we need to sudo sudo make install else # Default docker image does not have users setup so we are only root and can not sudo make install fi shell: bash scitokens-cpp-1.3.0/vendor/jwt-cpp/.github/actions/install/danielaparker-jsoncons/000077500000000000000000000000001513647275500302775ustar00rootroot00000000000000scitokens-cpp-1.3.0/vendor/jwt-cpp/.github/actions/install/danielaparker-jsoncons/action.yml000066400000000000000000000010051513647275500322730ustar00rootroot00000000000000name: Install jsoncons description: Install jsoncons for building test application inputs: version: description: The desired jsoncons version to install required: false default: "0.168.7" runs: using: composite steps: - run: | cd /tmp wget https://github.com/danielaparker/jsoncons/archive/v${{ inputs.version }}.tar.gz tar -zxf /tmp/v${{ inputs.version }}.tar.gz cd jsoncons-${{ inputs.version }} cmake . sudo cmake --install . shell: bash scitokens-cpp-1.3.0/vendor/jwt-cpp/.github/actions/install/gtest/000077500000000000000000000000001513647275500247715ustar00rootroot00000000000000scitokens-cpp-1.3.0/vendor/jwt-cpp/.github/actions/install/gtest/action.yml000066400000000000000000000010151513647275500267660ustar00rootroot00000000000000name: Install GTest description: Install and setup GTest for linking and building test application runs: using: composite steps: - run: sudo apt-get install libgtest-dev lcov shell: bash - run: (cd /usr/src/gtest && sudo `which cmake` .) shell: bash - run: sudo make -j $(nproc) -C /usr/src/gtest shell: bash - run: sudo ln -s /usr/src/gtest/libgtest.a /usr/lib/libgtest.a shell: bash - run: sudo ln -s /usr/src/gtest/libgtest_main.a /usr/lib/libgtest_main.a shell: bash scitokens-cpp-1.3.0/vendor/jwt-cpp/.github/actions/install/kazuho-picojson/000077500000000000000000000000001513647275500267665ustar00rootroot00000000000000scitokens-cpp-1.3.0/vendor/jwt-cpp/.github/actions/install/kazuho-picojson/action.yml000066400000000000000000000010061513647275500307630ustar00rootroot00000000000000name: Install PicoJSON description: Install PicoJSON for building test application inputs: version: description: The desired PicoJSON version to install required: false default: "v1.3.0" runs: using: composite steps: - run: | cd /tmp wget https://github.com/kazuho/picojson/archive/${{ inputs.version }}.tar.gz tar -zxf /tmp/${{ inputs.version }}.tar.gz cd picojson-${{ inputs.version }} sudo cp -v picojson.h /usr/local/include/picojson shell: bash scitokens-cpp-1.3.0/vendor/jwt-cpp/.github/actions/install/libressl/000077500000000000000000000000001513647275500254625ustar00rootroot00000000000000scitokens-cpp-1.3.0/vendor/jwt-cpp/.github/actions/install/libressl/action.yml000066400000000000000000000012561513647275500274660ustar00rootroot00000000000000name: Install LibreSSL description: Install and setup LibreSSL for linking and building test application inputs: version: description: The desired LibreSSL version to install required: false default: "3.3.0" runs: using: composite steps: - run: | wget https://raw.githubusercontent.com/libressl-portable/portable/v${{ inputs.version }}/FindLibreSSL.cmake -P cmake/ cd /tmp wget https://ftp.openbsd.org/pub/OpenBSD/LibreSSL/libressl-${{ inputs.version }}.tar.gz tar -zvxf /tmp/libressl-${{ inputs.version }}.tar.gz cd libressl-${{ inputs.version }} ./configure sudo make -j $(nproc) install shell: bash scitokens-cpp-1.3.0/vendor/jwt-cpp/.github/actions/install/nlohmann-json/000077500000000000000000000000001513647275500264245ustar00rootroot00000000000000scitokens-cpp-1.3.0/vendor/jwt-cpp/.github/actions/install/nlohmann-json/action.yml000066400000000000000000000010061513647275500304210ustar00rootroot00000000000000name: Install nlohmann-json description: Install nlohmann-json for building test application inputs: version: description: The desired nlohmann-json version to install required: false default: "3.11.2" runs: using: composite steps: - run: | cd /tmp wget https://github.com/nlohmann/json/archive/v${{ inputs.version }}.tar.gz tar -zxf /tmp/v${{ inputs.version }}.tar.gz cd json-${{ inputs.version }} cmake . sudo cmake --install . shell: bash scitokens-cpp-1.3.0/vendor/jwt-cpp/.github/actions/install/open-source-parsers-jsoncpp/000077500000000000000000000000001513647275500312315ustar00rootroot00000000000000scitokens-cpp-1.3.0/vendor/jwt-cpp/.github/actions/install/open-source-parsers-jsoncpp/action.yml000066400000000000000000000015231513647275500332320ustar00rootroot00000000000000name: Install open-source-parsers/jsoncpp description: Install open-source-parsers/jsoncpp for building test application inputs: version: description: The desired open-source-parsers/jsoncpp version to install required: false default: "1.9.5" runs: using: composite steps: - run: | cd /tmp wget https://github.com/open-source-parsers/jsoncpp/archive/${{ inputs.version }}.tar.gz tar -zxf /tmp/${{ inputs.version }}.tar.gz cd jsoncpp-${{ inputs.version }} # https://github.com/open-source-parsers/jsoncpp/blob/69098a18b9af0c47549d9a271c054d13ca92b006/include/PreventInSourceBuilds.cmake#L8 mkdir build cd build cmake .. -DJSONCPP_WITH_TESTS=OFF -DBUILD_SHARED_LIBS=OFF -DBUILD_OBJECT_LIBS=OFF cmake --build . sudo cmake --install . shell: bash scitokens-cpp-1.3.0/vendor/jwt-cpp/.github/actions/install/openssl/000077500000000000000000000000001513647275500253265ustar00rootroot00000000000000scitokens-cpp-1.3.0/vendor/jwt-cpp/.github/actions/install/openssl/action.yml000066400000000000000000000013051513647275500273250ustar00rootroot00000000000000name: Install OpenSSL description: Install and setup OpenSSL for linking and building test application inputs: version: description: The desired OpenSSL version to install required: false default: "openssl-3.0.0" runs: using: composite steps: - run: | cd /tmp wget https://github.com/openssl/openssl/archive/refs/tags/${{ inputs.version }}.tar.gz tar -zxf /tmp/${{ inputs.version }}.tar.gz cd openssl-${{ inputs.version }} ./config --prefix=/tmp --libdir=lib make -j $(nproc) sudo make -j $(nproc) install_sw echo "OPENSSL_ROOT_DIR=/tmp" >> "$GITHUB_ENV" echo "OpenSSL_ROOT=/tmp" >> "$GITHUB_ENV" shell: bash scitokens-cpp-1.3.0/vendor/jwt-cpp/.github/actions/install/wolfssl/000077500000000000000000000000001513647275500253345ustar00rootroot00000000000000scitokens-cpp-1.3.0/vendor/jwt-cpp/.github/actions/install/wolfssl/action.yml000066400000000000000000000013601513647275500273340ustar00rootroot00000000000000name: Install wolfSSL description: Install and setup wolfSSL for linking and building test application inputs: version: description: The desired stable wolfSSL version to install required: false default: "v4.8.1-stable" runs: using: composite steps: - run: | cd /tmp wget -O wolfssl.tar.gz https://github.com/wolfSSL/wolfssl/archive/${{ inputs.version }}.tar.gz tar -zxf /tmp/wolfssl.tar.gz cd wolfssl-* autoreconf -fiv ./configure --enable-opensslall --enable-opensslextra --disable-examples --disable-crypttests --enable-harden --enable-all --enable-all-crypto make sudo make install shell: bash - run: sudo rm -rf /usr/include/openssl shell: bash scitokens-cpp-1.3.0/vendor/jwt-cpp/.github/actions/process-linting-results/000077500000000000000000000000001513647275500270145ustar00rootroot00000000000000scitokens-cpp-1.3.0/vendor/jwt-cpp/.github/actions/process-linting-results/action.yml000066400000000000000000000017221513647275500310160ustar00rootroot00000000000000name: Process Linting Results description: Add a comment to a pull request with when `git diff` present and save the changes as an artifact so they can be applied manually inputs: linter_name: description: The name of the tool to credit in the comment required: true runs: using: "composite" steps: - run: git add --update shell: bash - id: stage #continue-on-error: true uses: Thalhammer/patch-generator-action@v3 # Unfortunately the previous action reports a failure so nothing else can run # partially a limitation on composite actions since `continue-on-error` is not # yet supported - if: steps.stage.outputs.result == 'dirty' uses: actions-ecosystem/action-create-comment@v1 with: github_token: ${{ github.token }} body: | Hello, @${{ github.actor }}! `${{ inputs.linter_name }}` had some concerns :scream: - run: exit $(git status -uno -s | wc -l) shell: bash scitokens-cpp-1.3.0/vendor/jwt-cpp/.github/actions/render/000077500000000000000000000000001513647275500234545ustar00rootroot00000000000000scitokens-cpp-1.3.0/vendor/jwt-cpp/.github/actions/render/defaults/000077500000000000000000000000001513647275500252635ustar00rootroot00000000000000scitokens-cpp-1.3.0/vendor/jwt-cpp/.github/actions/render/defaults/action.yml000066400000000000000000000046361513647275500272740ustar00rootroot00000000000000name: "Render `defaults.h` Template" description: "Generate the `defaults.h` header file for a JSON library" inputs: traits_name: description: "Name of the traits structure to be used. Typically in the format `author_repository` or equivilant" required: true library_name: description: "Name of the JSON library." required: true library_url: description: "URL to the JSON library." required: true disable_default_traits: description: "Set the macro to disable the default traits" required: false default: "true" outputs: file_path: description: "Relative path which the 'defaults.h' was written to" value: ${{ steps.script.outputs.result }} runs: using: composite steps: - uses: actions/setup-node@v3 with: node-version: 14 - run: npm install mustache shell: bash - uses: actions/github-script@v6 id: script env: TRAITS_NAME: ${{ inputs.traits_name }} LIBRARY_NAME: ${{ inputs.library_name }} LIBRARY_URL: ${{ inputs.library_url }} DISABLE_DEFAULT_TRAITS: ${{ inputs.disable_default_traits }} with: result-encoding: string script: | const mustache = require('mustache') const path = require('path') const fs = require('fs') const { TRAITS_NAME, LIBRARY_NAME, LIBRARY_URL, DISABLE_DEFAULT_TRAITS } = process.env console.log(`Rendering ${TRAITS_NAME}!`) const disableDefault = DISABLE_DEFAULT_TRAITS === 'true' const template = fs.readFileSync(path.join('include', 'jwt-cpp', 'traits', 'defaults.h.mustache'), 'utf8') const content = mustache.render(template, { traits_name: TRAITS_NAME, traits_name_upper: TRAITS_NAME.toUpperCase(), library_name: LIBRARY_NAME, library_url: LIBRARY_URL, disable_default_traits: disableDefault, }) // https://dmitripavlutin.com/replace-all-string-occurrences-javascript/ function replaceAll(string, search, replace) { return string.split(search).join(replace); } const outputDir = path.join('include', 'jwt-cpp', 'traits', replaceAll(TRAITS_NAME, '_', '-')) fs.mkdirSync(outputDir, { recursive: true }) const filePath = path.join(outputDir, 'defaults.h') fs.writeFileSync(filePath, content) return filePath scitokens-cpp-1.3.0/vendor/jwt-cpp/.github/actions/render/tests/000077500000000000000000000000001513647275500246165ustar00rootroot00000000000000scitokens-cpp-1.3.0/vendor/jwt-cpp/.github/actions/render/tests/action.yml000066400000000000000000000031521513647275500266170ustar00rootroot00000000000000name: "Render `TraitsTests.cpp` Template" description: "Generate the `TraitsTests.cpp` header file for a JSON library" inputs: traits_name: description: "Name of the traits structure to be used. Typically in the format `author_repository` or equivilant" required: true test_suite_name: description: "Name of the JSON library." required: true runs: using: composite steps: - uses: actions/setup-node@v3 with: node-version: 14 - run: npm install mustache shell: bash - uses: actions/github-script@v6 env: TRAITS_NAME: ${{ inputs.traits_name }} SUITE_NAME: ${{ inputs.test_suite_name }} with: script: | const mustache = require('mustache') const path = require('path') const fs = require('fs') const { TRAITS_NAME, SUITE_NAME } = process.env console.log(`Rendering ${TRAITS_NAME}!`) // https://dmitripavlutin.com/replace-all-string-occurrences-javascript/ function replaceAll(string, search, replace) { return string.split(search).join(replace); } const template = fs.readFileSync(path.join('tests', 'traits', 'TraitsTest.cpp.mustache'), 'utf8') const content = mustache.render(template, { traits_name: TRAITS_NAME, traits_dir: replaceAll(TRAITS_NAME, '_', '-'), test_suite_name: SUITE_NAME, }) const outputDir = path.join('tests', 'traits') fs.mkdirSync(outputDir, { recursive: true }) fs.writeFileSync(path.join(outputDir, `${SUITE_NAME}.cpp`), content) scitokens-cpp-1.3.0/vendor/jwt-cpp/.github/logo.svg000066400000000000000000000065151513647275500222250ustar00rootroot00000000000000 scitokens-cpp-1.3.0/vendor/jwt-cpp/.github/security.md000066400000000000000000000015371513647275500227340ustar00rootroot00000000000000# Reporting Security Issues If you believe you have found a security vulnerability in JWT-CPP, we encourage you to let us know right away. We will investigate all legitimate reports and do our best to quickly fix the problem. Please refer to the section below for our responsible disclosure policy: ## Disclosure Policy Please contact one or more of the maintainers using the email advertised on our GitHub profiles: - [@Thalhammer](https://github.com/Thalhammer) - [@prince-chrismc](https://github.com/prince-chrismc) Please provide as many details as possible about version, platform, and workflow as possible. Having steps and reproducible code is better and is always greatly appreciated. ## Supported Version Typically, fixes will be immediately released as a new patch release. However, older affected versions may receive a new patch upon request. scitokens-cpp-1.3.0/vendor/jwt-cpp/.github/workflows/000077500000000000000000000000001513647275500225725ustar00rootroot00000000000000scitokens-cpp-1.3.0/vendor/jwt-cpp/.github/workflows/cmake.yml000066400000000000000000000155601513647275500244040ustar00rootroot00000000000000name: CMake CI on: push: branches: [master] pull_request: branches: [master] paths: - "CMakeLists.txt" - "cmake/**" - "include/jwt-cpp/**" - "tests/cmake/**" - ".github/actions/**" - ".github/workflows/cmake.yml" jobs: default-linux: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: lukka/get-cmake@latest - name: setup run: | cmake --preset release sudo cmake --build --preset release --target install - name: test working-directory: tests/cmake run: | cmake . -DTEST:STRING="defaults-enabled" -DCMAKE_FIND_DEBUG_MODE=1 cmake --build . default-linux-with-examples: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: lukka/get-cmake@latest - name: setup run: | mkdir build cd build cmake .. -DJWT_BUILD_EXAMPLES=ON sudo cmake --install . - name: test working-directory: tests/cmake run: | cmake . -DTEST:STRING="defaults-enabled" cmake --build . default-win: runs-on: windows-latest steps: - uses: actions/checkout@v4 - uses: lukka/get-cmake@latest - run: choco install openssl - name: setup run: | cmake -E make_directory build cd build cmake .. -DJWT_BUILD_EXAMPLES=OFF cmake --install . - name: test run: | cd tests/cmake cmake . -DTEST:STRING="defaults-enabled" -DCMAKE_FIND_DEBUG_MODE=1 cmake --build . min-req: runs-on: ubuntu-20.04 steps: - uses: actions/checkout@v4 - uses: ./.github/actions/install/cmake with: version: "3.14.7" url: "https://cmake.org/files/v3.14/cmake-3.14.7.tar.gz" - uses: ./.github/actions/install/gtest - name: setup run: | mkdir build cd build cmake .. -DJWT_BUILD_EXAMPLES=ON -DJWT_BUILD_TESTS=ON sudo make install - name: test run: | cd tests/cmake cmake . -DTEST:STRING="defaults-enabled" cmake --build . preset-support-but-not-enough: runs-on: ubuntu-22.04 steps: - uses: actions/checkout@v4 - uses: ./.github/actions/install/cmake with: version: "3.24.4" url: "https://cmake.org/files/v3.24/cmake-3.24.4.tar.gz" - uses: ./.github/actions/install/gtest - name: setup run: | mkdir build cd build cmake .. -DJWT_BUILD_EXAMPLES=ON -DJWT_BUILD_TESTS=ON sudo make install - name: test run: | cd tests/cmake cmake . -DTEST:STRING="defaults-enabled" cmake --build . custom-install-linux: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: lukka/get-cmake@latest - name: setup run: | mkdir build cd build cmake .. -DCMAKE_INSTALL_PREFIX:STRING="/opt/jwt-cpp" -DJWT_BUILD_EXAMPLES=OFF make install - name: test run: | cd tests/cmake cmake . -DCMAKE_PREFIX_PATH="/opt/jwt-cpp" -DTEST:STRING="defaults-enabled" -DCMAKE_FIND_DEBUG_MODE=1 cmake --build . root-hint-install-linux: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: lukka/get-cmake@latest - name: setup run: | mkdir build cd build cmake .. -DCMAKE_INSTALL_PREFIX:STRING="/opt/jwt-cpp" -DJWT_BUILD_EXAMPLES=OFF make install - name: test run: | cd tests/cmake cmake . -Djwt-cpp_ROOT="/opt/jwt-cpp" -DTEST:STRING="defaults-enabled" -DCMAKE_FIND_DEBUG_MODE=1 cmake --build . custom-install-win: runs-on: windows-latest steps: - uses: actions/checkout@v4 - uses: lukka/get-cmake@latest - run: choco install openssl - name: setup run: | cmake -E make_directory build cd build cmake .. -DCMAKE_INSTALL_PREFIX:STRING="C:/jwt-cpp" -DJWT_BUILD_EXAMPLES=OFF cmake --install . - name: test run: | cd tests/cmake cmake . -DCMAKE_PREFIX_PATH="C:/jwt-cpp" -DTEST:STRING="defaults-enabled" -DCMAKE_FIND_DEBUG_MODE=1 cmake --build . no-pico: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: lukka/get-cmake@latest - name: setup run: | mkdir build cd build cmake .. -DJWT_DISABLE_PICOJSON=ON -DJWT_BUILD_EXAMPLES=OFF sudo make install - name: test run: | cd tests/cmake cmake . -DCMAKE_PREFIX_PATH=/usr/local/cmake -DTEST:STRING="picojson-is-disabled" cmake --build . no-base64: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: lukka/get-cmake@latest - name: setup run: | mkdir build cd build cmake .. -DJWT_DISABLE_BASE64=ON -DJWT_BUILD_EXAMPLES=OFF sudo make install - name: test run: | cd tests/cmake cmake . -DCMAKE_PREFIX_PATH=/usr/local/cmake -DTEST:STRING="base64-is-disabled" cmake --build . with-libressl: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: lukka/get-cmake@latest - uses: ./.github/actions/install/libressl - name: setup run: | mkdir build cd build cmake .. -DJWT_SSL_LIBRARY:STRING="LibreSSL" -DJWT_BUILD_EXAMPLES=OFF sudo make install - name: test run: | cd tests/cmake cmake . -DCMAKE_PREFIX_PATH=/usr/local/cmake -DCMAKE_MODULE_PATH=../../cmake -DTEST:STRING="libressl-is-used" cmake --build . with-wolfssl: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: lukka/get-cmake@latest - uses: ./.github/actions/install/wolfssl with: version: ab3bbf11e9d39b52e24bf42bbc6babc16d4a669b - name: setup run: | mkdir build cd build cmake .. -DJWT_SSL_LIBRARY:STRING="wolfSSL" -DJWT_BUILD_EXAMPLES=OFF sudo make install - name: test run: | cd tests/cmake cmake . -DTEST:STRING="wolfssl-is-used" cmake --build . with-hunter: # This is actually testing the integration with the package management runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: lukka/get-cmake@latest - name: setup run: | mkdir build cd build cmake .. -DJWT_BUILD_TESTS=ON -DJWT_BUILD_EXAMPLES=ON -DJWT_ENABLE_COVERAGE=OFF -DHUNTER_ENABLED=ON make - name: test run: | cd build ./tests/jwt-cpp-test scitokens-cpp-1.3.0/vendor/jwt-cpp/.github/workflows/cross-platform.yml000066400000000000000000000013511513647275500262700ustar00rootroot00000000000000name: Cross-Platform CI on: push: branches: [master] pull_request: branches: [master] jobs: build: runs-on: ${{ matrix.os }} strategy: matrix: os: [macos-latest, windows-latest, ubuntu-latest] steps: - uses: actions/checkout@v4 - name: configure run: cmake --preset examples - name: build run: cmake --build --preset examples - name: test run: | cmake --build --preset examples --target rsa-create-run cmake --build --preset examples --target rsa-verify-run - if: github.event_name == 'push' && always() uses: ./.github/actions/badge with: category: cross-platform label: ${{ matrix.os }} scitokens-cpp-1.3.0/vendor/jwt-cpp/.github/workflows/documentation.yml000066400000000000000000000011321513647275500261630ustar00rootroot00000000000000name: Documentation CI on: push: branches: [master] jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: ssciwr/doxygen-install@v1 with: version: "1.10.0" - run: sudo apt install graphviz - run: | cmake . -DJWT_BUILD_DOCS=ON cmake --build . --target jwt-docs - if: github.event_name == 'push' name: deploy uses: peaceiris/actions-gh-pages@v3 with: github_token: ${{ secrets.GITHUB_TOKEN }} publish_dir: ./build/html force_orphan: true scitokens-cpp-1.3.0/vendor/jwt-cpp/.github/workflows/jwt.yml000066400000000000000000000055201513647275500241230ustar00rootroot00000000000000name: JWT CI on: push: branches: [master] pull_request: branches: [master] jobs: coverage: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: lukka/get-cmake@latest - uses: ./.github/actions/install/gtest - uses: ./.github/actions/install/danielaparker-jsoncons - uses: ./.github/actions/install/boost-json - uses: ./.github/actions/install/open-source-parsers-jsoncpp - name: configure run: cmake --preset coverage - name: run run: cmake --build --preset coverage - uses: coverallsapp/github-action@v2 with: github-token: ${{ secrets.GITHUB_TOKEN }} file: build/coverage.info format: lcov fuzzing: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: lukka/get-cmake@latest - name: configure run: cmake --preset ci-fuzzing - name: build run: cmake --build --preset ci-fuzzing - name: run run: | cmake --build --preset ci-fuzzing --target jwt-cpp-fuzz-BaseEncodeFuzz-run cmake --build --preset ci-fuzzing --target jwt-cpp-fuzz-BaseDecodeFuzz-run cmake --build --preset ci-fuzzing --target jwt-cpp-fuzz-TokenDecodeFuzz-run asan: runs-on: ubuntu-latest strategy: fail-fast: false matrix: openssl: - { tag: "openssl-3.0.5", name: "3.0.5" } - { tag: "OpenSSL_1_1_1q", name: "1.1.1q" } steps: - uses: actions/checkout@v4 - uses: lukka/get-cmake@latest - uses: ./.github/actions/install/gtest - uses: ./.github/actions/install/openssl with: version: ${{ matrix.openssl.tag }} - name: configure run: cmake --preset ci-asan - name: build run: cmake --build --preset ci-asan - name: run run: | cmake --build --preset ci-asan --target private-claims-run cmake --build --preset ci-asan --target rsa-create-run cmake --build --preset ci-asan --target rsa-verify-run cmake --build --preset ci-asan --target jwks-verify-run cmake --build --preset ci-asan --target jwt-cpp-test-run ubsan: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: lukka/get-cmake@latest - uses: ./.github/actions/install/gtest - name: configure run: cmake --preset ci-ubsan - name: build run: cmake --build --preset ci-ubsan -DCMAKE_CXX_STANDARD=20 - name: run run: | cmake --build --preset ci-ubsan --target private-claims-run cmake --build --preset ci-ubsan --target rsa-create-run cmake --build --preset ci-ubsan --target rsa-verify-run cmake --build --preset ci-ubsan --target jwks-verify-run cmake --build --preset ci-ubsan --target jwt-cpp-test-run scitokens-cpp-1.3.0/vendor/jwt-cpp/.github/workflows/lint.yml000066400000000000000000000104731513647275500242700ustar00rootroot00000000000000name: Lint CI on: push: branches: [master] pull_request: branches: [master] jobs: clang-format: runs-on: ubuntu-22.04 strategy: fail-fast: false matrix: files: - "include/jwt-cpp/*.h" - "include/jwt-cpp/traits/**/*.h" - "tests/*.cpp" - "tests/**/*.cpp" - "example/*.cpp" - "example/**/*.cpp" steps: - run: | sudo apt-get install clang-format-14 shopt -s globstar - uses: actions/checkout@v4 - run: clang-format-14 -i ${{ matrix.files }} - uses: ./.github/actions/process-linting-results with: linter_name: clang-format cmake-format: runs-on: ubuntu-20.04 strategy: fail-fast: false matrix: files: ["**/CMakeLists.txt", "cmake/code-coverage.cmake"] steps: - uses: actions/setup-python@v5 with: python-version: "3.x" - run: pip install cmakelang - run: shopt -s globstar - uses: actions/checkout@v4 - run: cmake-format -i ${{ matrix.files }} - uses: ./.github/actions/process-linting-results with: linter_name: cmake-format clang-tidy: runs-on: ubuntu-20.04 steps: - run: sudo apt-get install clang-tidy - uses: lukka/get-cmake@latest - uses: actions/checkout@v4 - name: configure run: cmake --preset examples -DCMAKE_CXX_CLANG_TIDY="clang-tidy;-fix" - name: run run: cmake --build --preset examples - uses: ./.github/actions/process-linting-results with: linter_name: clang-tidy render-defaults: runs-on: ubuntu-22.04 strategy: fail-fast: false matrix: traits: - { name: "boost_json", library: "Boost.JSON", url: "https://github.com/boostorg/json", disable_pico: true } - { name: "danielaparker_jsoncons", library: "jsoncons", url: "https://github.com/danielaparker/jsoncons", disable_pico: true } - { name: "kazuho_picojson", library: "picojson", url: "https://github.com/kazuho/picojson", disable_pico: false } - { name: "nlohmann_json", library: "JSON for Modern C++", url: "https://github.com/nlohmann/json", disable_pico: true } - { name: "open_source_parsers_jsoncpp", library: "jsoncpp", url: "https://github.com/open-source-parsers/jsoncpp", disable_pico: true } name: render-defaults (${{ matrix.traits.name }}) steps: - uses: actions/checkout@v4 - run: | sudo apt-get install clang-format-14 - uses: ./.github/actions/render/defaults id: render with: traits_name: ${{ matrix.traits.name }} library_name: ${{ matrix.traits.library }} library_url: ${{ matrix.traits.url }} disable_default_traits: ${{ matrix.traits.disable_pico }} - run: clang-format-14 -i ${{ steps.render.outputs.file_path }} - run: git add ${{ steps.render.outputs.file_path }} - uses: ./.github/actions/process-linting-results with: linter_name: render-defaults render-tests: runs-on: ubuntu-22.04 strategy: fail-fast: false matrix: traits: # - { name: "boost_json", suite: "BoostJsonTest" } # Currently needs work arounds for API limitations - { name: "danielaparker_jsoncons", suite: "JsonconsTest" } # - { name: "kazuho_picojson", suite: "PicoJsonTest" } # Currently the default everything tests against this! - { name: "nlohmann_json", suite: "NlohmannTest" } - { name: "open_source_parsers_jsoncpp", suite: "OspJsoncppTest" } name: render-tests (${{ matrix.traits.name }}) steps: - uses: actions/checkout@v4 - run: | sudo apt-get install clang-format-14 shopt -s globstar - uses: ./.github/actions/render/tests with: traits_name: ${{ matrix.traits.name }} test_suite_name: ${{ matrix.traits.suite }} - run: clang-format-14 -i tests/**/*.cpp - run: git add tests/traits/* - uses: ./.github/actions/process-linting-results with: linter_name: render-tests line-ending: runs-on: ubuntu-20.04 steps: - uses: actions/checkout@v4 - run: git add --renormalize . - uses: ./.github/actions/process-linting-results with: linter_name: line-ending scitokens-cpp-1.3.0/vendor/jwt-cpp/.github/workflows/release.yml000066400000000000000000000027241513647275500247420ustar00rootroot00000000000000name: Release CD on: # Allows you to run this workflow manually from the Actions tab workflow_dispatch: # https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#release release: types: [published] jobs: nuget: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Setup NuGet uses: NuGet/setup-nuget@v1 with: nuget-api-key: ${{ secrets.nuget_api_key }} - name: Create NuGet pkg working-directory: ./nuget run: nuget pack jwt-cpp.nuspec - name: Publish NuGet pkg working-directory: ./nuget run: nuget push *.nupkg -Source 'https://api.nuget.org/v3/index.json' release-asset: if: github.event_name != 'workflow_dispatch' runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - run: tar --exclude='./.git' -vczf /tmp/jwt-cpp-${{ github.event.release.tag_name }}.tar.gz . - uses: shogo82148/actions-upload-release-asset@v1 with: upload_url: ${{ github.event.release.upload_url }} asset_path: /tmp/jwt-cpp-${{ github.event.release.tag_name }}.tar.gz - run: zip -x './.git/*' -r /tmp/jwt-cpp-${{ github.event.release.tag_name }}.zip . - uses: shogo82148/actions-upload-release-asset@v1 with: upload_url: ${{ github.event.release.upload_url }} asset_path: /tmp/jwt-cpp-${{ github.event.release.tag_name }}.zip scitokens-cpp-1.3.0/vendor/jwt-cpp/.github/workflows/ssl.yml000066400000000000000000000067311513647275500241250ustar00rootroot00000000000000name: SSL Compatibility CI on: push: branches: [master] pull_request: branches: [master] jobs: openssl: runs-on: ubuntu-latest strategy: matrix: openssl: - { tag: "openssl-3.0.5", name: "3.0.5" } - { tag: "OpenSSL_1_1_1q", name: "1.1.1q" } - { tag: "OpenSSL_1_1_0i", name: "1.1.0i" } # Do not bump, there's a broken in the autoconfig script and it's not maintained - { tag: "OpenSSL_1_0_2u", name: "1.0.2u" } - { tag: "OpenSSL_1_0_1u", name: "1.0.1u" } name: OpenSSL ${{ matrix.openssl.name }} steps: - uses: actions/checkout@v4 - uses: lukka/get-cmake@latest - uses: ./.github/actions/install/gtest - uses: ./.github/actions/install/openssl with: version: ${{ matrix.openssl.tag }} - name: configure run: cmake --preset unit-tests -DOPENSSL_ROOT_DIR=/tmp - run: cmake --build --preset unit-tests - name: test run: ctest --preset unit-tests --output-on-failure - if: github.event_name == 'push' && always() uses: ./.github/actions/badge with: category: openssl label: ${{ matrix.openssl.name }} openssl-no-deprecated: runs-on: ubuntu-latest name: OpenSSL 3.0 No Deprecated steps: - uses: actions/checkout@v4 - uses: lukka/get-cmake@latest - uses: ./.github/actions/install/gtest - uses: ./.github/actions/install/openssl with: version: "openssl-3.0.5" - name: configure run: cmake --preset unit-tests -DOPENSSL_ROOT_DIR=/tmp -DCMAKE_CXX_FLAGS="-DOPENSSL_NO_DEPRECATED=1" -DCMAKE_C_FLAGS="-DOPENSSL_NO_DEPRECATED=1" - run: cmake --build --preset unit-tests - name: test run: ctest --preset unit-tests libressl: runs-on: ubuntu-latest strategy: fail-fast: false matrix: libressl: ["3.5.3", "3.4.3", "3.3.6"] name: LibreSSL ${{ matrix.libressl }} steps: - uses: actions/checkout@v4 - uses: lukka/get-cmake@latest - uses: ./.github/actions/install/gtest - uses: ./.github/actions/install/libressl with: version: ${{ matrix.libressl }} - name: configure run: cmake --preset unit-tests -DJWT_SSL_LIBRARY:STRING=LibreSSL - run: cmake --build --preset unit-tests - name: test run: ctest --preset unit-tests - if: github.event_name == 'push' && always() uses: ./.github/actions/badge with: category: libressl label: ${{ matrix.libressl }} wolfssl: runs-on: ubuntu-latest strategy: matrix: wolfssl: - { ref: "v5.1.1-stable", name: "5.1.1"} - { ref: "v5.2.0-stable", name: "5.2.0" } - { ref: "v5.3.0-stable", name: "5.3.0"} - { ref: "v5.7.0-stable", name: "5.7.0"} name: wolfSSL ${{ matrix.wolfssl.name }} steps: - uses: actions/checkout@v4 - uses: lukka/get-cmake@latest - uses: ./.github/actions/install/gtest - uses: ./.github/actions/install/wolfssl with: version: ${{ matrix.wolfssl.ref }} - name: configure run: cmake --preset unit-tests -DJWT_SSL_LIBRARY:STRING=wolfSSL - run: cmake --build --preset unit-tests - name: test run: ctest --preset unit-tests - if: github.event_name == 'push' && always() uses: ./.github/actions/badge with: category: wolfssl label: ${{ matrix.wolfssl.name }} scitokens-cpp-1.3.0/vendor/jwt-cpp/.github/workflows/targets.yml000066400000000000000000000040541513647275500247710ustar00rootroot00000000000000name: Specific Targets CI on: push: branches: [master] pull_request: branches: [master] paths: - "CMakeLists.txt" - "cmake/**" - "include/jwt-cpp/**" - "tests/cmake/**" - ".github/actions/**" - ".github/workflows/targets.yml" jobs: gcc-4-8: if: false # There's no existing image with node20 and gcc4.8 https://github.com/actions/checkout/issues/1809 name: GCC 4.8 runs-on: ubuntu-latest container: image: ubuntu:bionic-20230530 # 18.04 env: CC: /usr/bin/gcc-4.8 CXX: /usr/bin/g++-4.8 steps: - run: | apt-get update apt-get install -y g++-4.8 wget make libssl-dev - uses: actions/checkout@v3 # Can not be upgrade as v4 needs NodeJS 20 doesn't exist next to gcc-4.8 - uses: ./.github/actions/install/cmake with: version: "3.26.3" url: "https://cmake.org/files/v3.26/cmake-3.26.3.tar.gz" - name: setup run: | mkdir build cd build cmake .. cmake --build . cmake --install . - name: test working-directory: tests/cmake run: | CC=gcc-4.8 CXX=g++-4.8 cmake . -DTEST:STRING="defaults-enabled" cmake --build . gcc-12: name: GCC 12 runs-on: ubuntu-latest container: image: ubuntu:jammy-20231004 # 22.04 env: CC: /usr/bin/gcc-12 CXX: /usr/bin/g++-12 steps: - run: | apt-get update apt-get install -y g++-12 wget make libssl-dev - uses: actions/checkout@v4 - uses: ./.github/actions/install/cmake with: version: "3.26.3" url: "https://cmake.org/files/v3.26/cmake-3.26.3.tar.gz" - name: setup run: | mkdir build cd build cmake .. cmake --build . cmake --install . - name: test working-directory: tests/cmake run: | CC=gcc-12 CXX=g++-12 cmake . -DCMAKE_CXX_STANDARD=20 -DTEST:STRING="defaults-enabled" cmake --build . scitokens-cpp-1.3.0/vendor/jwt-cpp/.github/workflows/traits.yml000066400000000000000000000053761513647275500246360ustar00rootroot00000000000000name: Traits CI on: push: branches: [master] pull_request: branches: [master] jobs: traits: name: Traits (${{ matrix.target.name }}) runs-on: ubuntu-latest strategy: matrix: target: - { name: "danielaparker-jsoncons", tag: "0.168.7", version: "v0.168.7" } - { name: "boost-json", tag: "1.78.0", version: "v1.80.0" } - { name: "nlohmann-json", tag: "3.11.2", version: "v3.11.2" } - { name: "kazuho-picojson", tag: "111c9be5188f7350c2eac9ddaedd8cca3d7bf394", version: "111c9be" } - { name: "open-source-parsers-jsoncpp", tag: "1.9.5", version: "v1.9.5" } steps: - uses: actions/checkout@v4 - uses: lukka/get-cmake@latest - name: setup run: | mkdir build cd build cmake .. -DJWT_BUILD_EXAMPLES=OFF sudo cmake --install . # Install the JSON library - if: matrix.target.name == 'danielaparker-jsoncons' uses: ./.github/actions/install/danielaparker-jsoncons with: version: ${{matrix.target.tag}} - if: matrix.target.name == 'boost-json' uses: ./.github/actions/install/boost-json with: version: ${{matrix.target.tag}} - if: matrix.target.name == 'nlohmann-json' uses: ./.github/actions/install/nlohmann-json with: version: ${{matrix.target.tag}} - if: matrix.target.name == 'kazuho-picojson' run: rm -rf include/picojson - if: matrix.target.name == 'kazuho-picojson' uses: ./.github/actions/install/kazuho-picojson with: version: ${{matrix.target.tag}} - if: matrix.target.name == 'open-source-parsers-jsoncpp' uses: ./.github/actions/install/open-source-parsers-jsoncpp with: version: ${{matrix.target.tag}} - name: test working-directory: example/traits run: | cmake . -DCMAKE_FIND_DEBUG_MODE=1 cmake --build . --target ${{ matrix.target.name }} ./${{ matrix.target.name }} - name: badge success if: github.event_name == 'push' && success() uses: ./.github/actions/badge/write with: category: traits label: ${{ matrix.target.name }} message: ${{ matrix.target.version }} color: lightblue # turquoise - name: badge failure if: github.event_name == 'push' && !success() uses: ./.github/actions/badge/write with: category: traits label: ${{ matrix.target.name }} message: ${{ matrix.target.version }} color: orange - if: github.event_name == 'push' && always() uses: ./.github/actions/badge/publish with: github_token: ${{ secrets.GITHUB_TOKEN }} scitokens-cpp-1.3.0/vendor/jwt-cpp/.gitignore000066400000000000000000000117431513647275500211730ustar00rootroot00000000000000## Ignore Visual Studio temporary files, build results, and ## files generated by popular Visual Studio add-ons. # User-specific files *.suo *.user *.userosscache *.sln.docstates # User-specific files (MonoDevelop/Xamarin Studio) *.userprefs # Build results [Dd]ebug/ [Dd]ebugPublic/ [Rr]elease/ [Rr]eleases/ x64/ x86/ bld/ [Bb]in/ [Oo]bj/ [Ll]og/ # Visual Studio 2015 cache/options directory .vs/ # Uncomment if you have tasks that create the project's static files in wwwroot #wwwroot/ # MSTest test Results [Tt]est[Rr]esult*/ [Bb]uild[Ll]og.* # NUNIT *.VisualState.xml TestResult.xml # Build Results of an ATL Project [Dd]ebugPS/ [Rr]eleasePS/ dlldata.c # DNX project.lock.json artifacts/ *_i.c *_p.c *_i.h *.ilk *.meta *.obj *.pch *.pdb *.pgc *.pgd *.rsp *.sbr *.tlb *.tli *.tlh *.tmp *.tmp_proj *.log *.vspscc *.vssscc .builds *.pidb *.svclog *.scc # Chutzpah Test files _Chutzpah* # Visual C++ cache files ipch/ *.aps *.ncb *.opendb *.opensdf *.sdf *.cachefile *.VC.db *.VC.VC.opendb # Visual Studio profiler *.psess *.vsp *.vspx *.sap # TFS 2012 Local Workspace $tf/ # Guidance Automation Toolkit *.gpState # ReSharper is a .NET coding add-in _ReSharper*/ *.[Rr]e[Ss]harper *.DotSettings.user # JustCode is a .NET coding add-in .JustCode # TeamCity is a build add-in _TeamCity* # DotCover is a Code Coverage Tool *.dotCover # NCrunch _NCrunch_* .*crunch*.local.xml nCrunchTemp_* # MightyMoose *.mm.* AutoTest.Net/ # Web workbench (sass) .sass-cache/ # Installshield output folder [Ee]xpress/ # DocProject is a documentation generator add-in DocProject/buildhelp/ DocProject/Help/*.HxT DocProject/Help/*.HxC DocProject/Help/*.hhc DocProject/Help/*.hhk DocProject/Help/*.hhp DocProject/Help/Html2 DocProject/Help/html # Click-Once directory publish/ # Publish Web Output *.[Pp]ublish.xml *.azurePubxml # TODO: Comment the next line if you want to checkin your web deploy settings # but database connection strings (with potential passwords) will be unencrypted *.pubxml *.publishproj # Microsoft Azure Web App publish settings. Comment the next line if you want to # checkin your Azure Web App publish settings, but sensitive information contained # in these scripts will be unencrypted PublishScripts/ # NuGet Packages *.nupkg # The packages folder can be ignored because of Package Restore **/packages/* # except build/, which is used as an MSBuild target. !**/packages/build/ # Uncomment if necessary however generally it will be regenerated when needed #!**/packages/repositories.config # NuGet v3's project.json files produces more ignoreable files *.nuget.props *.nuget.targets # Microsoft Azure Build Output csx/ *.build.csdef # Microsoft Azure Emulator ecf/ rcf/ # Windows Store app package directories and files AppPackages/ BundleArtifacts/ Package.StoreAssociation.xml _pkginfo.txt # Visual Studio cache files # files ending in .cache can be ignored *.[Cc]ache # but keep track of directories ending in .cache !*.[Cc]ache/ # Others ClientBin/ ~$* *~ *.dbmdl *.dbproj.schemaview *.pfx *.publishsettings node_modules/ orleans.codegen.cs # Since there are multiple workflows, uncomment next line to ignore bower_components # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) #bower_components/ # RIA/Silverlight projects Generated_Code/ # Backup & report files from converting an old project file # to a newer Visual Studio version. Backup files are not needed, # because we have git ;-) _UpgradeReport_Files/ Backup*/ UpgradeLog*.XML UpgradeLog*.htm # SQL Server files *.mdf *.ldf # Business Intelligence projects *.rdl.data *.bim.layout *.bim_*.settings # Microsoft Fakes FakesAssemblies/ # GhostDoc plugin setting file *.GhostDoc.xml # Node.js Tools for Visual Studio .ntvs_analysis.dat # Visual Studio 6 build log *.plg # Visual Studio 6 workspace options file *.opt # Visual Studio LightSwitch build output **/*.HTMLClient/GeneratedArtifacts **/*.DesktopClient/GeneratedArtifacts **/*.DesktopClient/ModelManifest.xml **/*.Server/GeneratedArtifacts **/*.Server/ModelManifest.xml _Pvt_Extensions # Paket dependency manager .paket/paket.exe paket-files/ # FAKE - F# Make .fake/ # JetBrains Rider .idea/ *.sln.iml # ========================= # Operating System Files # ========================= # OSX # ========================= .DS_Store .AppleDouble .LSOverride # Thumbnails ._* # Files that might appear in the root of a volume .DocumentRevisions-V100 .fseventsd .Spotlight-V100 .TemporaryItems .Trashes .VolumeIcon.icns # Directories potentially created on remote AFP share .AppleDB .AppleDesktop Network Trash Folder Temporary Items .apdisk # Windows # ========================= # Windows image file caches Thumbs.db ehthumbs.db # Folder config file Desktop.ini # Recycle Bin used on file shares $RECYCLE.BIN/ # Windows Installer files *.cab *.msi *.msm *.msp # Windows shortcuts *.lnk # Linux files test *.o *.o.d # IDE-specific files .vscode/ .vscode/!extensions.json # Allow to provide recommended extensions # ClangD cache files .cache build/* package-lock.json CMakeUserPresets.json scitokens-cpp-1.3.0/vendor/jwt-cpp/CMakeLists.txt000066400000000000000000000163311513647275500217410ustar00rootroot00000000000000cmake_minimum_required(VERSION 3.14) cmake_policy(VERSION 3.14) if(POLICY CMP0135) # DOWNLOAD_EXTRACT_TIMESTAMP cmake_policy(SET CMP0135 OLD) endif() # HUNTER_ENABLED is always set if this package is included in a project using hunter (HunterGate sets it) In this case # we will use hunter as well to stay consistent. If not the use can supply it on configure to force using hunter. if(HUNTER_ENABLED) include("cmake/HunterGate.cmake") huntergate(URL "https://github.com/cpp-pm/hunter/archive/v0.23.314.tar.gz" SHA1 "95c47c92f68edb091b5d6d18924baabe02a6962a") message(STATUS "jwt-cpp: using hunter for dependency resolution") endif() project(jwt-cpp LANGUAGES CXX) option(JWT_BUILD_EXAMPLES "Configure CMake to build examples (or not)" ON) option(JWT_BUILD_TESTS "Configure CMake to build tests (or not)" OFF) option(JWT_BUILD_DOCS "Adds a target for building the doxygen documentation" OFF) option(JWT_ENABLE_COVERAGE "Enable code coverage testing" OFF) option(JWT_ENABLE_FUZZING "Enable fuzz testing" OFF) option(JWT_DISABLE_PICOJSON "Do not provide the picojson template specialiaze" OFF) option(JWT_DISABLE_BASE64 "Do not include the base64 implementation from this library" OFF) include(CMakeDependentOption) cmake_dependent_option(JWT_EXTERNAL_PICOJSON "Use find_package() to locate picojson, provided to integrate with package managers" OFF "NOT JWT_DISABLE_PICOJSON" OFF) cmake_dependent_option(JWT_EXTERNAL_NLOHMANN_JSON "Use find_package() to locate nlohman-json required for tests and examples" OFF "JWT_BUILD_EXAMPLES OR JWT_BUILD_TESTS" OFF) set(JWT_SSL_LIBRARY_OPTIONS OpenSSL LibreSSL wolfSSL) set(JWT_SSL_LIBRARY OpenSSL CACHE STRING "Determines which SSL library to build with") set_property(CACHE JWT_SSL_LIBRARY PROPERTY STRINGS ${JWT_SSL_LIBRARY_OPTIONS}) set(JWT_JSON_TRAITS_OPTIONS boost-json danielaparker-jsoncons kazuho-picojson nlohmann-json open-source-parsers-jsoncpp) list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/cmake") if(NOT JWT_SSL_LIBRARY IN_LIST JWT_SSL_LIBRARY_OPTIONS) message(FATAL_ERROR "JWT_SSL_LIBRARY must be one of ${JWT_SSL_LIBRARY_OPTIONS}") endif() # If Hunter is enabled, we configure it to resolve OpenSSL and warn the user if he selected an option not supported by # hunter. We fall back to the system library in this case. if(HUNTER_ENABLED) if(${JWT_SSL_LIBRARY} MATCHES "OpenSSL") hunter_add_package(OpenSSL) elseif(${JWT_SSL_LIBRARY} MATCHES "LibreSSL") message(WARNING "Hunter does not support LibreSSL yet, the system library will be used (if available)") elseif(${JWT_SSL_LIBRARY} MATCHES "wolfSSL") message(WARNING "Hunter does not support wolfSSL yet, the system library will be used (if available)") endif() if(JWT_EXTERNAL_PICOJSON) message(WARNING "Hunter does not support picojson yet, the system library will be used (if available)") endif() endif() # Lookup dependencies if(${JWT_SSL_LIBRARY} MATCHES "OpenSSL") find_package(OpenSSL 1.0.1 REQUIRED) elseif(${JWT_SSL_LIBRARY} MATCHES "LibreSSL") find_package(LibreSSL 3.0.0 REQUIRED) elseif(${JWT_SSL_LIBRARY} MATCHES "wolfSSL") find_package(PkgConfig REQUIRED) pkg_check_modules(wolfssl REQUIRED IMPORTED_TARGET wolfssl) list(TRANSFORM wolfssl_INCLUDE_DIRS APPEND "/wolfssl") # This is required to access OpenSSL compatibility API endif() if(NOT JWT_DISABLE_PICOJSON AND JWT_EXTERNAL_PICOJSON) find_package(picojson 1.3.0 REQUIRED) endif() if(JWT_BUILD_EXAMPLES OR JWT_BUILD_TESTS) if(JWT_EXTERNAL_NLOHMANN_JSON) message(STATUS "jwt-cpp: using find_package for nlohmann-json required for tests") find_package(nlohmann_json CONFIG REQUIRED) else() message(STATUS "jwt-cpp: using FetchContent for nlohmann-json required for tests") include(FetchContent) fetchcontent_declare(nlohmann_json URL https://github.com/nlohmann/json/releases/download/v3.11.2/json.tar.xz URL_MD5 127794b2c82c0c5693805feaa2a703e2) fetchcontent_makeavailable(nlohmann_json) endif() endif() set(JWT_INCLUDE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/include) set(JWT_HEADER_FILES ${JWT_INCLUDE_PATH}/jwt-cpp/jwt.h) foreach(traits ${JWT_JSON_TRAITS_OPTIONS}) list(APPEND JWT_HEADER_FILES ${JWT_INCLUDE_PATH}/jwt-cpp/traits/${traits}/defaults.h ${JWT_INCLUDE_PATH}/jwt-cpp/traits/${traits}/traits.h) endforeach() if(NOT JWT_DISABLE_BASE64) list(APPEND JWT_HEADER_FILES ${JWT_INCLUDE_PATH}/jwt-cpp/base.h) endif() add_library(jwt-cpp INTERFACE) add_library(jwt-cpp::jwt-cpp ALIAS jwt-cpp) # To match export target_compile_features(jwt-cpp INTERFACE cxx_std_11) if(JWT_DISABLE_BASE64) target_compile_definitions(jwt-cpp INTERFACE JWT_DISABLE_BASE64) endif() if(JWT_DISABLE_PICOJSON) target_compile_definitions(jwt-cpp INTERFACE JWT_DISABLE_PICOJSON) endif() include(GNUInstallDirs) include(CMakePackageConfigHelpers) target_include_directories(jwt-cpp INTERFACE $ $) if(${JWT_SSL_LIBRARY} MATCHES "OpenSSL") target_link_libraries(jwt-cpp INTERFACE OpenSSL::SSL OpenSSL::Crypto) endif() if(${JWT_SSL_LIBRARY} MATCHES "LibreSSL") target_link_libraries(jwt-cpp INTERFACE LibreSSL::TLS) endif() if(${JWT_SSL_LIBRARY} MATCHES "wolfSSL") target_link_libraries(jwt-cpp INTERFACE PkgConfig::wolfssl) # This is required to access OpenSSL compatibility API target_include_directories(jwt-cpp INTERFACE ${wolfssl_INCLUDE_DIRS}) # This flag is required to have the mandatory header included automatically # https://github.com/Thalhammer/jwt-cpp/pull/352#discussion_r1627971786 # https://github.com/wolfSSL/wolfssl/blob/3b74a6402998a8b8839e25e31ba8ac74749aa9b0/wolfssl/wolfcrypt/settings.h#L58 target_compile_definitions(jwt-cpp INTERFACE EXTERNAL_OPTS_OPENVPN) endif() if(NOT JWT_DISABLE_PICOJSON AND JWT_EXTERNAL_PICOJSON) target_link_libraries(jwt-cpp INTERFACE picojson::picojson>) endif() # Hunter needs relative paths so the files are placed correctly if(NOT JWT_CMAKE_FILES_INSTALL_DIR) set(JWT_CMAKE_FILES_INSTALL_DIR cmake) endif() configure_package_config_file( ${CMAKE_CURRENT_LIST_DIR}/cmake/jwt-cpp-config.cmake.in ${CMAKE_CURRENT_BINARY_DIR}/jwt-cpp-config.cmake INSTALL_DESTINATION ${JWT_CMAKE_FILES_INSTALL_DIR}) write_basic_package_version_file(${CMAKE_CURRENT_BINARY_DIR}/jwt-cpp-config-version.cmake VERSION 0.7.1 COMPATIBILITY ExactVersion) install(TARGETS jwt-cpp EXPORT jwt-cpp-targets PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) install(EXPORT jwt-cpp-targets NAMESPACE jwt-cpp:: FILE jwt-cpp-targets.cmake DESTINATION ${JWT_CMAKE_FILES_INSTALL_DIR}) install(DIRECTORY ${JWT_INCLUDE_PATH}/jwt-cpp DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) if(NOT JWT_EXTERNAL_PICOJSON AND NOT JWT_DISABLE_PICOJSON) install(FILES ${JWT_INCLUDE_PATH}/picojson/picojson.h DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/picojson) endif() install(FILES ${CMAKE_CURRENT_BINARY_DIR}/jwt-cpp-config.cmake ${CMAKE_CURRENT_BINARY_DIR}/jwt-cpp-config-version.cmake DESTINATION ${JWT_CMAKE_FILES_INSTALL_DIR}) if(JWT_BUILD_EXAMPLES) add_subdirectory(example) endif() if(JWT_BUILD_TESTS) enable_testing() add_subdirectory(tests) endif() if(JWT_ENABLE_FUZZING) add_subdirectory(tests/fuzz) endif() if(JWT_BUILD_DOCS) add_subdirectory(docs) endif() scitokens-cpp-1.3.0/vendor/jwt-cpp/CMakePresets.json000066400000000000000000000013461513647275500224220ustar00rootroot00000000000000{ "version": 6, "cmakeMinimumRequired": { "major": 3, "minor": 25, "patch": 0 }, "include": [ "example/CMakePresets.json", "tests/CMakePresets.json" ], "configurePresets": [ { "name": "dev", "displayName": "Development", "inherits": "debug", "environment": { "CMAKE_EXPORT_COMPILE_COMMANDS": "ON" }, "cacheVariables": { "JWT_BUILD_EXAMPLES": "ON", "JWT_BUILD_TESTS": "ON" } } ], "buildPresets": [ { "name": "dev", "configurePreset": "dev", "configuration": "Debug" } ] }scitokens-cpp-1.3.0/vendor/jwt-cpp/LICENSE000066400000000000000000000020631513647275500202030ustar00rootroot00000000000000MIT License Copyright (c) 2018 Dominik Thalhammer Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. scitokens-cpp-1.3.0/vendor/jwt-cpp/README.md000066400000000000000000000174751513647275500204720ustar00rootroot00000000000000logo [![License Badge](https://img.shields.io/github/license/Thalhammer/jwt-cpp)](https://github.com/Thalhammer/jwt-cpp/blob/master/LICENSE) [![Codacy Badge](https://api.codacy.com/project/badge/Grade/5f7055e294744901991fd0a1620b231d)](https://app.codacy.com/gh/Thalhammer/jwt-cpp/dashboard) [![Linux Badge][Linux]][Cross-Platform] [![MacOS Badge][MacOS]][Cross-Platform] [![Windows Badge][Windows]][Cross-Platform] [![Coverage Status](https://coveralls.io/repos/github/Thalhammer/jwt-cpp/badge.svg?branch=master)](https://coveralls.io/github/Thalhammer/jwt-cpp?branch=master) [![Documentation Badge](https://img.shields.io/badge/Documentation-master-blue)](https://thalhammer.github.io/jwt-cpp/) [![Stars Badge](https://img.shields.io/github/stars/Thalhammer/jwt-cpp?style=flat)](https://github.com/Thalhammer/jwt-cpp/stargazers) [![GitHub release (latest SemVer including pre-releases)](https://img.shields.io/github/v/release/Thalhammer/jwt-cpp?include_prereleases)](https://github.com/Thalhammer/jwt-cpp/releases) [![ConanCenter package](https://repology.org/badge/version-for-repo/conancenter/jwt-cpp.svg)](https://repology.org/project/jwt-cpp/versions) [![Vcpkg package](https://repology.org/badge/version-for-repo/vcpkg/jwt-cpp.svg)](https://repology.org/project/jwt-cpp/versions) [Linux]: https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/Thalhammer/jwt-cpp/badges/cross-platform/ubuntu-latest/shields.json [MacOS]: https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/Thalhammer/jwt-cpp/badges/cross-platform/macos-latest/shields.json [Windows]: https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/Thalhammer/jwt-cpp/badges/cross-platform/windows-latest/shields.json [Cross-Platform]: https://github.com/Thalhammer/jwt-cpp/actions?query=workflow%3A%22Cross-Platform+CI%22 ## Overview A header only library for creating and validating [JSON Web Tokens](https://tools.ietf.org/html/rfc7519) in C++11. For a great introduction, [read this](https://jwt.io/introduction/). The objective is to deliver a versatile and universally applicable collection of algorithms, classes, and data structures, fostering adaptability and seamless integration with other libraries that you may already be employing. ## Signature algorithms jwt-cpp comprehensively supports all algorithms specified in the standard. Its modular design facilitates the seamless [inclusion of additional algorithms](docs/signing.md#custom-signature-algorithms) without encountering any complications. Should you wish to contribute new algorithms, feel free to initiate a pull request or [open an issue](https://github.com/Thalhammer/jwt-cpp/issues/new). For completeness, here is a list of all supported algorithms: | HMSC | RSA | ECDSA | PSS | EdDSA | | ----- | ----- | ------ | ----- | ------- | | HS256 | RS256 | ES256 | PS256 | Ed25519 | | HS384 | RS384 | ES384 | PS384 | Ed448 | | HS512 | RS512 | ES512 | PS512 | | | | | ES256K | | | ## Getting Started Installation instructions can be found [here](docs/install.md). A simple example is decoding a token and printing all of its [claims](https://tools.ietf.org/html/rfc7519#section-4) let's ([try it out](https://jwt.io/#debugger-io?token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXUyJ9.eyJpc3MiOiJhdXRoMCIsInNhbXBsZSI6InRlc3QifQ.lQm3N2bVlqt2-1L-FsOjtR6uE-L4E9zJutMWKIe1v1M)): ```cpp #include #include int main() { std::string token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXUyJ9.eyJpc3MiOiJhdXRoMCIsInNhbXBsZSI6InRlc3QifQ.lQm3N2bVlqt2-1L-FsOjtR6uE-L4E9zJutMWKIe1v1M"; auto decoded = jwt::decode(token); for(auto& e : decoded.get_payload_json()) std::cout << e.first << " = " << e.second << std::endl; } ``` You can build and run [this example](example/print-claims.cpp) locally after cloning the repository. Running some commands, we can see the contents of the [JWT payload](https://datatracker.ietf.org/doc/html/rfc7519#section-3) ```sh cmake . cmake --build . --target print-claims ./print-claims # iss = "auth0" # sample = "test" ``` You'll very quickly notice JWT are not encrypted but rather cryptographically signed to provide [non-repudiation](https://csrc.nist.gov/glossary/term/non_repudiation). In order to verify a token you first build a verifier and use it to verify a decoded token. ```cpp auto verifier = jwt::verify() .with_issuer("auth0") .with_claim("sample", jwt::claim(std::string("test"))) .allow_algorithm(jwt::algorithm::hs256{"secret"}); verifier.verify(decoded_token); ``` The verifier is stateless so you can reuse it for different tokens. Creating the token above (and signing it) is equally as easy. ```cpp auto token = jwt::create() .set_type("JWS") .set_issuer("auth0") .set_payload_claim("sample", jwt::claim(std::string("test"))) .sign(jwt::algorithm::hs256{"secret"}); ``` If you are looking to issue or verify more unique tokens, checkout out the [examples](https://github.com/Thalhammer/jwt-cpp/tree/master/example) working with RSA public and private keys, elliptic curve tokens, and much more! ### Configuration Options Building on the goal of providing flexibility. #### SSL Compatibility jwt-cpp supports [OpenSSL](https://github.com/openssl/openssl), [LibreSSL](https://github.com/libressl-portable/portable), and [wolfSSL](https://github.com/wolfSSL/wolfssl). For a listed of tested versions, check [this page](docs/ssl.md) for more details. #### JSON Implementation There is no strict reliance on a specific JSON library in this context. Instead, the jwt-cpp utilizes a generic `jwt::basic_claim` that is templated based on type trait. This trait provides the semantic [JSON types](https://json-schema.org/understanding-json-schema/reference/type.html) for values, objects, arrays, strings, numbers, integers, and booleans, along with methods to seamlessly translate between them. This design offers flexibility in choosing the JSON library that best suits your needs. To leverage one of the provided JSON traits, refer to [docs/traits.md](docs/traits.md#selecting-a-json-library) for detailed guidance. ##### Providing your own JSON Traits ```cpp jwt::basic_claim claim(json::object({{"json", true},{"example", 0}})); ``` To learn how to writes a trait's implementation, checkout the [these instructions](docs/traits.md#providing-your-own-json-traits) #### Base64 Options With regard to the base64 specifications for JWTs, this library includes `base.h` encompassing all necessary variants. While the library itself offers a proficient base64 implementation, it's worth noting that base64 implementations are widely available, exhibiting diverse performance levels. If you prefer to use your own base64 implementation, you have the option to define `JWT_DISABLE_BASE64` to exclude the jwt-cpp implementation. ## Contributing If you have suggestions for improvement or if you've identified a bug, please don't hesitate to [open an issue](https://github.com/Thalhammer/jwt-cpp/issues/new) or contribute by creating a pull request. When reporting a bug, provide comprehensive details about your environment, including compiler version and other relevant information, to facilitate issue reproduction. Additionally, if you're introducing a new feature, ensure that you include corresponding test cases to validate its functionality. ### Dependencies In order to use jwt-cpp you need the following tools. * libcrypto (openssl or compatible) * libssl-dev (for the header files) * a compiler supporting at least c++11 * basic stl support In order to build the test cases you also need * gtest * pthread ## Troubleshooting See the [FAQs](docs/faqs.md) for tips. ## Conference Coverage [![CppCon](https://img.youtube.com/vi/Oq4NW5idmiI/0.jpg)](https://www.youtube.com/watch?v=Oq4NW5idmiI) scitokens-cpp-1.3.0/vendor/jwt-cpp/cmake/000077500000000000000000000000001513647275500202555ustar00rootroot00000000000000scitokens-cpp-1.3.0/vendor/jwt-cpp/cmake/CMakePresets.json000066400000000000000000000022211513647275500234730ustar00rootroot00000000000000{ "version": 6, "cmakeMinimumRequired": { "major": 3, "minor": 25, "patch": 0 }, "configurePresets": [ { "name": "default", "displayName": "Default Config", "hidden": true, "binaryDir": "${sourceDir}/build", "cacheVariables": { "JWT_BUILD_EXAMPLES": "OFF", "JWT_BUILD_TESTS": "OFF" } }, { "name": "debug", "displayName": "Debug", "inherits": "default", "cacheVariables": { "CMAKE_BUILD_TYPE": "Debug" } }, { "name": "release", "displayName": "Release", "inherits": "default", "cacheVariables": { "CMAKE_BUILD_TYPE": "Release" } } ], "buildPresets": [ { "name": "debug", "configurePreset": "debug", "configuration": "Debug" }, { "name": "release", "configurePreset": "release", "configuration": "Release" } ] }scitokens-cpp-1.3.0/vendor/jwt-cpp/cmake/HunterGate.cmake000066400000000000000000000412411513647275500233270ustar00rootroot00000000000000# Copyright (c) 2013-2019, Ruslan Baratov # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, this # list of conditions and the following disclaimer. # # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions and the following disclaimer in the documentation # and/or other materials provided with the distribution. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # This is a gate file to Hunter package manager. # Include this file using `include` command and add package you need, example: # # cmake_minimum_required(VERSION 3.2) # # include("cmake/HunterGate.cmake") # HunterGate( # URL "https://github.com/path/to/hunter/archive.tar.gz" # SHA1 "798501e983f14b28b10cda16afa4de69eee1da1d" # ) # # project(MyProject) # # hunter_add_package(Foo) # hunter_add_package(Boo COMPONENTS Bar Baz) # # Projects: # * https://github.com/hunter-packages/gate/ # * https://github.com/ruslo/hunter option(HUNTER_ENABLED "Enable Hunter package manager support" ON) if(HUNTER_ENABLED) if(CMAKE_VERSION VERSION_LESS "3.2") message( FATAL_ERROR "At least CMake version 3.2 required for Hunter dependency management." " Update CMake or set HUNTER_ENABLED to OFF." ) endif() endif() include(CMakeParseArguments) # cmake_parse_arguments option(HUNTER_STATUS_PRINT "Print working status" ON) option(HUNTER_STATUS_DEBUG "Print a lot info" OFF) option(HUNTER_TLS_VERIFY "Enable/disable TLS certificate checking on downloads" ON) set(HUNTER_ROOT "" CACHE FILEPATH "Override the HUNTER_ROOT.") set(HUNTER_ERROR_PAGE "https://hunter.readthedocs.io/en/latest/reference/errors") function(hunter_gate_status_print) if(HUNTER_STATUS_PRINT OR HUNTER_STATUS_DEBUG) foreach(print_message ${ARGV}) message(STATUS "[hunter] ${print_message}") endforeach() endif() endfunction() function(hunter_gate_status_debug) if(HUNTER_STATUS_DEBUG) foreach(print_message ${ARGV}) string(TIMESTAMP timestamp) message(STATUS "[hunter *** DEBUG *** ${timestamp}] ${print_message}") endforeach() endif() endfunction() function(hunter_gate_error_page error_page) message("------------------------------ ERROR ------------------------------") message(" ${HUNTER_ERROR_PAGE}/${error_page}.html") message("-------------------------------------------------------------------") message("") message(FATAL_ERROR "") endfunction() function(hunter_gate_internal_error) message("") foreach(print_message ${ARGV}) message("[hunter ** INTERNAL **] ${print_message}") endforeach() message("[hunter ** INTERNAL **] [Directory:${CMAKE_CURRENT_LIST_DIR}]") message("") hunter_gate_error_page("error.internal") endfunction() function(hunter_gate_fatal_error) cmake_parse_arguments(hunter "" "ERROR_PAGE" "" "${ARGV}") if("${hunter_ERROR_PAGE}" STREQUAL "") hunter_gate_internal_error("Expected ERROR_PAGE") endif() message("") foreach(x ${hunter_UNPARSED_ARGUMENTS}) message("[hunter ** FATAL ERROR **] ${x}") endforeach() message("[hunter ** FATAL ERROR **] [Directory:${CMAKE_CURRENT_LIST_DIR}]") message("") hunter_gate_error_page("${hunter_ERROR_PAGE}") endfunction() function(hunter_gate_user_error) hunter_gate_fatal_error(${ARGV} ERROR_PAGE "error.incorrect.input.data") endfunction() function(hunter_gate_self root version sha1 result) string(COMPARE EQUAL "${root}" "" is_bad) if(is_bad) hunter_gate_internal_error("root is empty") endif() string(COMPARE EQUAL "${version}" "" is_bad) if(is_bad) hunter_gate_internal_error("version is empty") endif() string(COMPARE EQUAL "${sha1}" "" is_bad) if(is_bad) hunter_gate_internal_error("sha1 is empty") endif() string(SUBSTRING "${sha1}" 0 7 archive_id) if(EXISTS "${root}/cmake/Hunter") set(hunter_self "${root}") else() set( hunter_self "${root}/_Base/Download/Hunter/${version}/${archive_id}/Unpacked" ) endif() set("${result}" "${hunter_self}" PARENT_SCOPE) endfunction() # Set HUNTER_GATE_ROOT cmake variable to suitable value. function(hunter_gate_detect_root) # Check CMake variable if(HUNTER_ROOT) set(HUNTER_GATE_ROOT "${HUNTER_ROOT}" PARENT_SCOPE) hunter_gate_status_debug("HUNTER_ROOT detected by cmake variable") return() endif() # Check environment variable if(DEFINED ENV{HUNTER_ROOT}) set(HUNTER_GATE_ROOT "$ENV{HUNTER_ROOT}" PARENT_SCOPE) hunter_gate_status_debug("HUNTER_ROOT detected by environment variable") return() endif() # Check HOME environment variable if(DEFINED ENV{HOME}) set(HUNTER_GATE_ROOT "$ENV{HOME}/.hunter" PARENT_SCOPE) hunter_gate_status_debug("HUNTER_ROOT set using HOME environment variable") return() endif() # Check SYSTEMDRIVE and USERPROFILE environment variable (windows only) if(WIN32) if(DEFINED ENV{SYSTEMDRIVE}) set(HUNTER_GATE_ROOT "$ENV{SYSTEMDRIVE}/.hunter" PARENT_SCOPE) hunter_gate_status_debug( "HUNTER_ROOT set using SYSTEMDRIVE environment variable" ) return() endif() if(DEFINED ENV{USERPROFILE}) set(HUNTER_GATE_ROOT "$ENV{USERPROFILE}/.hunter" PARENT_SCOPE) hunter_gate_status_debug( "HUNTER_ROOT set using USERPROFILE environment variable" ) return() endif() endif() hunter_gate_fatal_error( "Can't detect HUNTER_ROOT" ERROR_PAGE "error.detect.hunter.root" ) endfunction() function(hunter_gate_download dir) string( COMPARE NOTEQUAL "$ENV{HUNTER_DISABLE_AUTOINSTALL}" "" disable_autoinstall ) if(disable_autoinstall AND NOT HUNTER_RUN_INSTALL) hunter_gate_fatal_error( "Hunter not found in '${dir}'" "Set HUNTER_RUN_INSTALL=ON to auto-install it from '${HUNTER_GATE_URL}'" "Settings:" " HUNTER_ROOT: ${HUNTER_GATE_ROOT}" " HUNTER_SHA1: ${HUNTER_GATE_SHA1}" ERROR_PAGE "error.run.install" ) endif() string(COMPARE EQUAL "${dir}" "" is_bad) if(is_bad) hunter_gate_internal_error("Empty 'dir' argument") endif() string(COMPARE EQUAL "${HUNTER_GATE_SHA1}" "" is_bad) if(is_bad) hunter_gate_internal_error("HUNTER_GATE_SHA1 empty") endif() string(COMPARE EQUAL "${HUNTER_GATE_URL}" "" is_bad) if(is_bad) hunter_gate_internal_error("HUNTER_GATE_URL empty") endif() set(done_location "${dir}/DONE") set(sha1_location "${dir}/SHA1") set(build_dir "${dir}/Build") set(cmakelists "${dir}/CMakeLists.txt") hunter_gate_status_debug("Locking directory: ${dir}") file(LOCK "${dir}" DIRECTORY GUARD FUNCTION) hunter_gate_status_debug("Lock done") if(EXISTS "${done_location}") # while waiting for lock other instance can do all the job hunter_gate_status_debug("File '${done_location}' found, skip install") return() endif() file(REMOVE_RECURSE "${build_dir}") file(REMOVE_RECURSE "${cmakelists}") file(MAKE_DIRECTORY "${build_dir}") # check directory permissions # Disabling languages speeds up a little bit, reduces noise in the output # and avoids path too long windows error file( WRITE "${cmakelists}" "cmake_minimum_required(VERSION 3.2)\n" "project(HunterDownload LANGUAGES NONE)\n" "include(ExternalProject)\n" "ExternalProject_Add(\n" " Hunter\n" " URL\n" " \"${HUNTER_GATE_URL}\"\n" " URL_HASH\n" " SHA1=${HUNTER_GATE_SHA1}\n" " DOWNLOAD_DIR\n" " \"${dir}\"\n" " TLS_VERIFY\n" " ${HUNTER_TLS_VERIFY}\n" " SOURCE_DIR\n" " \"${dir}/Unpacked\"\n" " CONFIGURE_COMMAND\n" " \"\"\n" " BUILD_COMMAND\n" " \"\"\n" " INSTALL_COMMAND\n" " \"\"\n" ")\n" ) if(HUNTER_STATUS_DEBUG) set(logging_params "") else() set(logging_params OUTPUT_QUIET) endif() hunter_gate_status_debug("Run generate") # Need to add toolchain file too. # Otherwise on Visual Studio + MDD this will fail with error: # "Could not find an appropriate version of the Windows 10 SDK installed on this machine" if(EXISTS "${CMAKE_TOOLCHAIN_FILE}") get_filename_component(absolute_CMAKE_TOOLCHAIN_FILE "${CMAKE_TOOLCHAIN_FILE}" ABSOLUTE) set(toolchain_arg "-DCMAKE_TOOLCHAIN_FILE=${absolute_CMAKE_TOOLCHAIN_FILE}") else() # 'toolchain_arg' can't be empty set(toolchain_arg "-DCMAKE_TOOLCHAIN_FILE=") endif() string(COMPARE EQUAL "${CMAKE_MAKE_PROGRAM}" "" no_make) if(no_make) set(make_arg "") else() # Test case: remove Ninja from PATH but set it via CMAKE_MAKE_PROGRAM set(make_arg "-DCMAKE_MAKE_PROGRAM=${CMAKE_MAKE_PROGRAM}") endif() execute_process( COMMAND "${CMAKE_COMMAND}" "-H${dir}" "-B${build_dir}" "-G${CMAKE_GENERATOR}" "${toolchain_arg}" ${make_arg} WORKING_DIRECTORY "${dir}" RESULT_VARIABLE download_result ${logging_params} ) if(NOT download_result EQUAL 0) hunter_gate_internal_error( "Configure project failed." "To reproduce the error run: ${CMAKE_COMMAND} -H${dir} -B${build_dir} -G${CMAKE_GENERATOR} ${toolchain_arg} ${make_arg}" "In directory ${dir}" ) endif() hunter_gate_status_print( "Initializing Hunter workspace (${HUNTER_GATE_SHA1})" " ${HUNTER_GATE_URL}" " -> ${dir}" ) execute_process( COMMAND "${CMAKE_COMMAND}" --build "${build_dir}" WORKING_DIRECTORY "${dir}" RESULT_VARIABLE download_result ${logging_params} ) if(NOT download_result EQUAL 0) hunter_gate_internal_error("Build project failed") endif() file(REMOVE_RECURSE "${build_dir}") file(REMOVE_RECURSE "${cmakelists}") file(WRITE "${sha1_location}" "${HUNTER_GATE_SHA1}") file(WRITE "${done_location}" "DONE") hunter_gate_status_debug("Finished") endfunction() # Must be a macro so master file 'cmake/Hunter' can # apply all variables easily just by 'include' command # (otherwise PARENT_SCOPE magic needed) macro(HunterGate) if(HUNTER_GATE_DONE) # variable HUNTER_GATE_DONE set explicitly for external project # (see `hunter_download`) set_property(GLOBAL PROPERTY HUNTER_GATE_DONE YES) endif() # First HunterGate command will init Hunter, others will be ignored get_property(_hunter_gate_done GLOBAL PROPERTY HUNTER_GATE_DONE SET) if(NOT HUNTER_ENABLED) # Empty function to avoid error "unknown function" function(hunter_add_package) endfunction() set( _hunter_gate_disabled_mode_dir "${CMAKE_CURRENT_LIST_DIR}/cmake/Hunter/disabled-mode" ) if(EXISTS "${_hunter_gate_disabled_mode_dir}") hunter_gate_status_debug( "Adding \"disabled-mode\" modules: ${_hunter_gate_disabled_mode_dir}" ) list(APPEND CMAKE_PREFIX_PATH "${_hunter_gate_disabled_mode_dir}") endif() elseif(_hunter_gate_done) hunter_gate_status_debug("Secondary HunterGate (use old settings)") hunter_gate_self( "${HUNTER_CACHED_ROOT}" "${HUNTER_VERSION}" "${HUNTER_SHA1}" _hunter_self ) include("${_hunter_self}/cmake/Hunter") else() set(HUNTER_GATE_LOCATION "${CMAKE_CURRENT_SOURCE_DIR}") string(COMPARE NOTEQUAL "${PROJECT_NAME}" "" _have_project_name) if(_have_project_name) hunter_gate_fatal_error( "Please set HunterGate *before* 'project' command. " "Detected project: ${PROJECT_NAME}" ERROR_PAGE "error.huntergate.before.project" ) endif() cmake_parse_arguments( HUNTER_GATE "LOCAL" "URL;SHA1;GLOBAL;FILEPATH" "" ${ARGV} ) string(COMPARE EQUAL "${HUNTER_GATE_SHA1}" "" _empty_sha1) string(COMPARE EQUAL "${HUNTER_GATE_URL}" "" _empty_url) string( COMPARE NOTEQUAL "${HUNTER_GATE_UNPARSED_ARGUMENTS}" "" _have_unparsed ) string(COMPARE NOTEQUAL "${HUNTER_GATE_GLOBAL}" "" _have_global) string(COMPARE NOTEQUAL "${HUNTER_GATE_FILEPATH}" "" _have_filepath) if(_have_unparsed) hunter_gate_user_error( "HunterGate unparsed arguments: ${HUNTER_GATE_UNPARSED_ARGUMENTS}" ) endif() if(_empty_sha1) hunter_gate_user_error("SHA1 suboption of HunterGate is mandatory") endif() if(_empty_url) hunter_gate_user_error("URL suboption of HunterGate is mandatory") endif() if(_have_global) if(HUNTER_GATE_LOCAL) hunter_gate_user_error("Unexpected LOCAL (already has GLOBAL)") endif() if(_have_filepath) hunter_gate_user_error("Unexpected FILEPATH (already has GLOBAL)") endif() endif() if(HUNTER_GATE_LOCAL) if(_have_global) hunter_gate_user_error("Unexpected GLOBAL (already has LOCAL)") endif() if(_have_filepath) hunter_gate_user_error("Unexpected FILEPATH (already has LOCAL)") endif() endif() if(_have_filepath) if(_have_global) hunter_gate_user_error("Unexpected GLOBAL (already has FILEPATH)") endif() if(HUNTER_GATE_LOCAL) hunter_gate_user_error("Unexpected LOCAL (already has FILEPATH)") endif() endif() hunter_gate_detect_root() # set HUNTER_GATE_ROOT # Beautify path, fix probable problems with windows path slashes get_filename_component( HUNTER_GATE_ROOT "${HUNTER_GATE_ROOT}" ABSOLUTE ) hunter_gate_status_debug("HUNTER_ROOT: ${HUNTER_GATE_ROOT}") if(NOT HUNTER_ALLOW_SPACES_IN_PATH) string(FIND "${HUNTER_GATE_ROOT}" " " _contain_spaces) if(NOT _contain_spaces EQUAL -1) hunter_gate_fatal_error( "HUNTER_ROOT (${HUNTER_GATE_ROOT}) contains spaces." "Set HUNTER_ALLOW_SPACES_IN_PATH=ON to skip this error" "(Use at your own risk!)" ERROR_PAGE "error.spaces.in.hunter.root" ) endif() endif() string( REGEX MATCH "[0-9]+\\.[0-9]+\\.[0-9]+[-_a-z0-9]*" HUNTER_GATE_VERSION "${HUNTER_GATE_URL}" ) string(COMPARE EQUAL "${HUNTER_GATE_VERSION}" "" _is_empty) if(_is_empty) set(HUNTER_GATE_VERSION "unknown") endif() hunter_gate_self( "${HUNTER_GATE_ROOT}" "${HUNTER_GATE_VERSION}" "${HUNTER_GATE_SHA1}" _hunter_self ) set(_master_location "${_hunter_self}/cmake/Hunter") if(EXISTS "${HUNTER_GATE_ROOT}/cmake/Hunter") # Hunter downloaded manually (e.g. by 'git clone') set(_unused "xxxxxxxxxx") set(HUNTER_GATE_SHA1 "${_unused}") set(HUNTER_GATE_VERSION "${_unused}") else() get_filename_component(_archive_id_location "${_hunter_self}/.." ABSOLUTE) set(_done_location "${_archive_id_location}/DONE") set(_sha1_location "${_archive_id_location}/SHA1") # Check Hunter already downloaded by HunterGate if(NOT EXISTS "${_done_location}") hunter_gate_download("${_archive_id_location}") endif() if(NOT EXISTS "${_done_location}") hunter_gate_internal_error("hunter_gate_download failed") endif() if(NOT EXISTS "${_sha1_location}") hunter_gate_internal_error("${_sha1_location} not found") endif() file(READ "${_sha1_location}" _sha1_value) string(TOLOWER "${_sha1_value}" _sha1_value_lower) string(TOLOWER "${HUNTER_GATE_SHA1}" _HUNTER_GATE_SHA1_lower) string(COMPARE EQUAL "${_sha1_value_lower}" "${_HUNTER_GATE_SHA1_lower}" _is_equal) if(NOT _is_equal) hunter_gate_internal_error( "Short SHA1 collision:" " ${_sha1_value} (from ${_sha1_location})" " ${HUNTER_GATE_SHA1} (HunterGate)" ) endif() if(NOT EXISTS "${_master_location}") hunter_gate_user_error( "Master file not found:" " ${_master_location}" "try to update Hunter/HunterGate" ) endif() endif() include("${_master_location}") set_property(GLOBAL PROPERTY HUNTER_GATE_DONE YES) endif() endmacro() scitokens-cpp-1.3.0/vendor/jwt-cpp/cmake/code-coverage.cmake000066400000000000000000000007221513647275500237630ustar00rootroot00000000000000set(COVERAGE_CMAKE "${CMAKE_BINARY_DIR}/cmake/CodeCoverage.cmake") if(NOT EXISTS ${COVERAGE_CMAKE}) set(COVERAGE_URL "https://raw.githubusercontent.com/bilke/cmake-modules/master/CodeCoverage.cmake") file(DOWNLOAD ${COVERAGE_URL} ${COVERAGE_CMAKE}) endif() include(${COVERAGE_CMAKE}) function(setup_coverage TARGET) target_compile_options(${TARGET} PRIVATE -g -O0 -fprofile-arcs -ftest-coverage) target_link_libraries(${TARGET} PRIVATE gcov) endfunction() scitokens-cpp-1.3.0/vendor/jwt-cpp/cmake/jwt-cpp-config.cmake.in000066400000000000000000000012321513647275500245110ustar00rootroot00000000000000@PACKAGE_INIT@ set(JWT_DISABLE_PICOJSON @JWT_DISABLE_PICOJSON@) set(JWT_EXTERNAL_PICOJSON @JWT_EXTERNAL_PICOJSON@) set(JWT_SSL_LIBRARY @JWT_SSL_LIBRARY@) include(CMakeFindDependencyMacro) if(${JWT_SSL_LIBRARY} MATCHES "wolfSSL") find_dependency(PkgConfig REQUIRED) pkg_check_modules(wolfssl REQUIRED IMPORTED_TARGET wolfssl) list(TRANSFORM wolfssl_INCLUDE_DIRS APPEND "/wolfssl") # This is required to access OpenSSL compatibility API else() find_dependency(${JWT_SSL_LIBRARY} REQUIRED) endif() if(NOT JWT_DISABLE_PICOJSON AND JWT_EXTERNAL_PICOJSON) find_dependency(picojson REQUIRED) endif() include("${CMAKE_CURRENT_LIST_DIR}/jwt-cpp-targets.cmake") scitokens-cpp-1.3.0/vendor/jwt-cpp/cmake/private-find-boost-json.cmake000066400000000000000000000015011513647275500257370ustar00rootroot00000000000000if(TARGET boost_json) return() endif() unset(BOOSTJSON_INCLUDE_DIR CACHE) find_path(BOOSTJSON_INCLUDE_DIR "boost/json.hpp" "boost/json/src.hpp") if(EXISTS "${BOOSTJSON_INCLUDE_DIR}/boost/json.hpp") file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/private-boost-json.cpp.in" "#include ") configure_file("${CMAKE_CURRENT_BINARY_DIR}/private-boost-json.cpp.in" private-boost-json.cpp COPYONLY) add_library(boost_json "${BOOSTJSON_INCLUDE_DIR}/boost/json.hpp" "${BOOSTJSON_INCLUDE_DIR}/boost/json/src.hpp" "${CMAKE_CURRENT_BINARY_DIR}/private-boost-json.cpp") target_include_directories(boost_json PUBLIC ${BOOSTJSON_INCLUDE_DIR}) target_compile_definitions(boost_json PUBLIC BOOST_JSON_STANDALONE) target_compile_features(boost_json PUBLIC cxx_std_17) endif() scitokens-cpp-1.3.0/vendor/jwt-cpp/cmake/private-find-kazuho-picojson.cmake000066400000000000000000000005011513647275500267640ustar00rootroot00000000000000if(TARGET kazuho_picojson) return() endif() unset(PICOJSON_INCLUDE_DIR CACHE) find_path(PICOJSON_INCLUDE_DIR "picojson/picojson.h") if(EXISTS "${PICOJSON_INCLUDE_DIR}/picojson/picojson.h") add_library(kazuho_picojson INTERFACE) target_include_directories(kazuho_picojson INTERFACE ${PICOJSON_INCLUDE_DIR}) endif() scitokens-cpp-1.3.0/vendor/jwt-cpp/include/000077500000000000000000000000001513647275500206205ustar00rootroot00000000000000scitokens-cpp-1.3.0/vendor/jwt-cpp/include/jwt-cpp/000077500000000000000000000000001513647275500222045ustar00rootroot00000000000000scitokens-cpp-1.3.0/vendor/jwt-cpp/include/jwt-cpp/base.h000066400000000000000000000332231513647275500232720ustar00rootroot00000000000000#ifndef JWT_CPP_BASE_H #define JWT_CPP_BASE_H #include #include #include #include #include #include #ifdef __has_cpp_attribute #if __has_cpp_attribute(fallthrough) #define JWT_FALLTHROUGH [[fallthrough]] #endif #endif #ifndef JWT_FALLTHROUGH #define JWT_FALLTHROUGH #endif namespace jwt { /** * \brief character maps when encoding and decoding */ namespace alphabet { /** * \brief valid list of character when working with [Base64](https://datatracker.ietf.org/doc/html/rfc4648#section-4) * * As directed in [X.509 Parameter](https://datatracker.ietf.org/doc/html/rfc7517#section-4.7) certificate chains are * base64-encoded as per [Section 4 of RFC4648](https://datatracker.ietf.org/doc/html/rfc4648#section-4) */ struct base64 { static const std::array& data() { static constexpr std::array data{ {'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'}}; return data; } static const std::array& rdata() { static constexpr std::array rdata{{ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, }}; return rdata; } static const std::string& fill() { static const std::string fill{"="}; return fill; } }; /** * \brief valid list of character when working with [Base64URL](https://tools.ietf.org/html/rfc4648#section-5) * * As directed by [RFC 7519 Terminology](https://datatracker.ietf.org/doc/html/rfc7519#section-2) set the definition of Base64URL * encoding as that in [RFC 7515](https://datatracker.ietf.org/doc/html/rfc7515#section-2) that states: * * > Base64 encoding using the URL- and filename-safe character set defined in * > [Section 5 of RFC 4648 RFC4648](https://tools.ietf.org/html/rfc4648#section-5), with all trailing '=' characters omitted */ struct base64url { static const std::array& data() { static constexpr std::array data{ {'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-', '_'}}; return data; } static const std::array& rdata() { static constexpr std::array rdata{{ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, 63, -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, }}; return rdata; } static const std::string& fill() { static const std::string fill{"%3d"}; return fill; } }; namespace helper { /** * \brief A General purpose base64url alphabet respecting the * [URI Case Normalization](https://datatracker.ietf.org/doc/html/rfc3986#section-6.2.2.1) * * This is useful in situations outside of JWT encoding/decoding and is provided as a helper */ struct base64url_percent_encoding { static const std::array& data() { static constexpr std::array data{ {'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-', '_'}}; return data; } static const std::array& rdata() { static constexpr std::array rdata{{ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, 63, -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, }}; return rdata; } static const std::vector& fill() { static const std::vector fill{"%3D", "%3d"}; return fill; } }; } // namespace helper inline uint32_t index(const std::array& rdata, char symbol) { auto index = rdata[static_cast(symbol)]; if (index <= -1) { throw std::runtime_error("Invalid input: not within alphabet"); } return static_cast(index); } } // namespace alphabet /** * \brief A collection of fellable functions for working with base64 and base64url */ namespace base { namespace details { struct padding { size_t count = 0; size_t length = 0; padding() = default; padding(size_t count, size_t length) : count(count), length(length) {} padding operator+(const padding& p) { return padding(count + p.count, length + p.length); } friend bool operator==(const padding& lhs, const padding& rhs) { return lhs.count == rhs.count && lhs.length == rhs.length; } }; inline padding count_padding(const std::string& base, const std::vector& fills) { for (const auto& fill : fills) { if (base.size() < fill.size()) continue; // Does the end of the input exactly match the fill pattern? if (base.substr(base.size() - fill.size()) == fill) { return padding{1, fill.length()} + count_padding(base.substr(0, base.size() - fill.size()), fills); } } return {}; } inline std::string encode(const std::string& bin, const std::array& alphabet, const std::string& fill) { size_t size = bin.size(); std::string res; // clear incomplete bytes size_t fast_size = size - size % 3; for (size_t i = 0; i < fast_size;) { uint32_t octet_a = static_cast(bin[i++]); uint32_t octet_b = static_cast(bin[i++]); uint32_t octet_c = static_cast(bin[i++]); uint32_t triple = (octet_a << 0x10) + (octet_b << 0x08) + octet_c; res += alphabet[(triple >> 3 * 6) & 0x3F]; res += alphabet[(triple >> 2 * 6) & 0x3F]; res += alphabet[(triple >> 1 * 6) & 0x3F]; res += alphabet[(triple >> 0 * 6) & 0x3F]; } if (fast_size == size) return res; size_t mod = size % 3; uint32_t octet_a = fast_size < size ? static_cast(bin[fast_size++]) : 0; uint32_t octet_b = fast_size < size ? static_cast(bin[fast_size++]) : 0; uint32_t octet_c = fast_size < size ? static_cast(bin[fast_size++]) : 0; uint32_t triple = (octet_a << 0x10) + (octet_b << 0x08) + octet_c; switch (mod) { case 1: res += alphabet[(triple >> 3 * 6) & 0x3F]; res += alphabet[(triple >> 2 * 6) & 0x3F]; res += fill; res += fill; break; case 2: res += alphabet[(triple >> 3 * 6) & 0x3F]; res += alphabet[(triple >> 2 * 6) & 0x3F]; res += alphabet[(triple >> 1 * 6) & 0x3F]; res += fill; break; default: break; } return res; } inline std::string decode(const std::string& base, const std::array& rdata, const std::vector& fill) { const auto pad = count_padding(base, fill); if (pad.count > 2) throw std::runtime_error("Invalid input: too much fill"); const size_t size = base.size() - pad.length; if ((size + pad.count) % 4 != 0) throw std::runtime_error("Invalid input: incorrect total size"); size_t out_size = size / 4 * 3; std::string res; res.reserve(out_size); auto get_sextet = [&](size_t offset) { return alphabet::index(rdata, base[offset]); }; size_t fast_size = size - size % 4; for (size_t i = 0; i < fast_size;) { uint32_t sextet_a = get_sextet(i++); uint32_t sextet_b = get_sextet(i++); uint32_t sextet_c = get_sextet(i++); uint32_t sextet_d = get_sextet(i++); uint32_t triple = (sextet_a << 3 * 6) + (sextet_b << 2 * 6) + (sextet_c << 1 * 6) + (sextet_d << 0 * 6); res += static_cast((triple >> 2 * 8) & 0xFFU); res += static_cast((triple >> 1 * 8) & 0xFFU); res += static_cast((triple >> 0 * 8) & 0xFFU); } if (pad.count == 0) return res; uint32_t triple = (get_sextet(fast_size) << 3 * 6) + (get_sextet(fast_size + 1) << 2 * 6); switch (pad.count) { case 1: triple |= (get_sextet(fast_size + 2) << 1 * 6); res += static_cast((triple >> 2 * 8) & 0xFFU); res += static_cast((triple >> 1 * 8) & 0xFFU); break; case 2: res += static_cast((triple >> 2 * 8) & 0xFFU); break; default: break; } return res; } inline std::string decode(const std::string& base, const std::array& rdata, const std::string& fill) { return decode(base, rdata, std::vector{fill}); } inline std::string pad(const std::string& base, const std::string& fill) { std::string padding; switch (base.size() % 4) { case 1: padding += fill; JWT_FALLTHROUGH; case 2: padding += fill; JWT_FALLTHROUGH; case 3: padding += fill; JWT_FALLTHROUGH; default: break; } return base + padding; } inline std::string trim(const std::string& base, const std::string& fill) { auto pos = base.find(fill); return base.substr(0, pos); } } // namespace details /** * \brief Generic base64 encoding * * A Generic base64 encode function that supports any "alphabet" * such as jwt::alphabet::base64 * * \code * const auto b64 = jwt::base::encode("example_data") * \endcode */ template std::string encode(const std::string& bin) { return details::encode(bin, T::data(), T::fill()); } /** * \brief Generic base64 decoding * * A Generic base64 decoding function that supports any "alphabet" * such as jwt::alphabet::base64 * * \code * const auto b64 = jwt::base::decode("ZXhhbXBsZV9kYXRh") * \endcode */ template std::string decode(const std::string& base) { return details::decode(base, T::rdata(), T::fill()); } /** * \brief Generic base64 padding * * A Generic base64 pad function that supports any "alphabet" * such as jwt::alphabet::base64 * * \code * const auto b64 = jwt::base::pad("ZXhhbXBsZV9kYQ") * \endcode */ template std::string pad(const std::string& base) { return details::pad(base, T::fill()); } /** * \brief Generic base64 trimming * * A Generic base64 trim function that supports any "alphabet" * such as jwt::alphabet::base64 * * \code * const auto b64 = jwt::base::trim("ZXhhbXBsZV9kYQ==") * \endcode */ template std::string trim(const std::string& base) { return details::trim(base, T::fill()); } } // namespace base } // namespace jwt #endif scitokens-cpp-1.3.0/vendor/jwt-cpp/include/jwt-cpp/jwt.h000066400000000000000000004630211513647275500231670ustar00rootroot00000000000000#ifndef JWT_CPP_JWT_H #define JWT_CPP_JWT_H #ifndef JWT_DISABLE_PICOJSON #ifndef PICOJSON_USE_INT64 #define PICOJSON_USE_INT64 #endif #include "picojson/picojson.h" #endif #ifndef JWT_DISABLE_BASE64 #include "base.h" #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if __cplusplus >= 201402L #ifdef __has_include #if __has_include() #include #endif #endif #endif #if OPENSSL_VERSION_NUMBER >= 0x30000000L // 3.0.0 #define JWT_OPENSSL_3_0 #include #elif OPENSSL_VERSION_NUMBER >= 0x10101000L // 1.1.1 #define JWT_OPENSSL_1_1_1 #elif OPENSSL_VERSION_NUMBER >= 0x10100000L // 1.1.0 #define JWT_OPENSSL_1_1_0 #elif OPENSSL_VERSION_NUMBER >= 0x10000000L // 1.0.0 #define JWT_OPENSSL_1_0_0 #endif #if defined(LIBRESSL_VERSION_NUMBER) #if LIBRESSL_VERSION_NUMBER >= 0x3050300fL #define JWT_OPENSSL_1_1_0 #else #define JWT_OPENSSL_1_0_0 #endif #endif #if defined(LIBWOLFSSL_VERSION_HEX) #define JWT_OPENSSL_1_1_1 #endif #ifndef JWT_CLAIM_EXPLICIT #define JWT_CLAIM_EXPLICIT explicit #endif /** * \brief JSON Web Token. * * A namespace to contain everything related to handling JSON Web Tokens, JWT for short, * as a part of [RFC7519](https://tools.ietf.org/html/rfc7519), or alternatively for * JWS (JSON Web Signature) from [RFC7515](https://tools.ietf.org/html/rfc7515) */ namespace jwt { /** * Default system time point in UTC */ using date = std::chrono::system_clock::time_point; /** * \brief Everything related to error codes issued by the library */ namespace error { struct signature_verification_exception : public std::system_error { using system_error::system_error; }; struct signature_generation_exception : public std::system_error { using system_error::system_error; }; struct rsa_exception : public std::system_error { using system_error::system_error; }; struct ecdsa_exception : public std::system_error { using system_error::system_error; }; struct token_verification_exception : public std::system_error { using system_error::system_error; }; /** * \brief Errors related to processing of RSA signatures */ enum class rsa_error { ok = 0, cert_load_failed = 10, get_key_failed, write_key_failed, write_cert_failed, convert_to_pem_failed, load_key_bio_write, load_key_bio_read, create_mem_bio_failed, no_key_provided, set_rsa_failed, create_context_failed }; /** * \brief Error category for RSA errors */ inline std::error_category& rsa_error_category() { class rsa_error_cat : public std::error_category { public: const char* name() const noexcept override { return "rsa_error"; }; std::string message(int ev) const override { switch (static_cast(ev)) { case rsa_error::ok: return "no error"; case rsa_error::cert_load_failed: return "error loading cert into memory"; case rsa_error::get_key_failed: return "error getting key from certificate"; case rsa_error::write_key_failed: return "error writing key data in PEM format"; case rsa_error::write_cert_failed: return "error writing cert data in PEM format"; case rsa_error::convert_to_pem_failed: return "failed to convert key to pem"; case rsa_error::load_key_bio_write: return "failed to load key: bio write failed"; case rsa_error::load_key_bio_read: return "failed to load key: bio read failed"; case rsa_error::create_mem_bio_failed: return "failed to create memory bio"; case rsa_error::no_key_provided: return "at least one of public or private key need to be present"; case rsa_error::set_rsa_failed: return "set modulus and exponent to RSA failed"; case rsa_error::create_context_failed: return "failed to create context"; default: return "unknown RSA error"; } } }; static rsa_error_cat cat; return cat; } /** * \brief Converts JWT-CPP errors into generic STL error_codes */ inline std::error_code make_error_code(rsa_error e) { return {static_cast(e), rsa_error_category()}; } /** * \brief Errors related to processing of RSA signatures */ enum class ecdsa_error { ok = 0, load_key_bio_write = 10, load_key_bio_read, create_mem_bio_failed, no_key_provided, invalid_key_size, invalid_key, create_context_failed, cert_load_failed, get_key_failed, write_key_failed, write_cert_failed, convert_to_pem_failed, unknown_curve, set_ecdsa_failed }; /** * \brief Error category for ECDSA errors */ inline std::error_category& ecdsa_error_category() { class ecdsa_error_cat : public std::error_category { public: const char* name() const noexcept override { return "ecdsa_error"; }; std::string message(int ev) const override { switch (static_cast(ev)) { case ecdsa_error::ok: return "no error"; case ecdsa_error::load_key_bio_write: return "failed to load key: bio write failed"; case ecdsa_error::load_key_bio_read: return "failed to load key: bio read failed"; case ecdsa_error::create_mem_bio_failed: return "failed to create memory bio"; case ecdsa_error::no_key_provided: return "at least one of public or private key need to be present"; case ecdsa_error::invalid_key_size: return "invalid key size"; case ecdsa_error::invalid_key: return "invalid key"; case ecdsa_error::create_context_failed: return "failed to create context"; case ecdsa_error::cert_load_failed: return "error loading cert into memory"; case ecdsa_error::get_key_failed: return "error getting key from certificate"; case ecdsa_error::write_key_failed: return "error writing key data in PEM format"; case ecdsa_error::write_cert_failed: return "error writing cert data in PEM format"; case ecdsa_error::convert_to_pem_failed: return "failed to convert key to pem"; case ecdsa_error::unknown_curve: return "unknown curve"; case ecdsa_error::set_ecdsa_failed: return "set parameters to ECDSA failed"; default: return "unknown ECDSA error"; } } }; static ecdsa_error_cat cat; return cat; } /** * \brief Converts JWT-CPP errors into generic STL error_codes */ inline std::error_code make_error_code(ecdsa_error e) { return {static_cast(e), ecdsa_error_category()}; } /** * \brief Errors related to verification of signatures */ enum class signature_verification_error { ok = 0, invalid_signature = 10, create_context_failed, verifyinit_failed, verifyupdate_failed, verifyfinal_failed, get_key_failed, set_rsa_pss_saltlen_failed, signature_encoding_failed }; /** * \brief Error category for verification errors */ inline std::error_category& signature_verification_error_category() { class verification_error_cat : public std::error_category { public: const char* name() const noexcept override { return "signature_verification_error"; }; std::string message(int ev) const override { switch (static_cast(ev)) { case signature_verification_error::ok: return "no error"; case signature_verification_error::invalid_signature: return "invalid signature"; case signature_verification_error::create_context_failed: return "failed to verify signature: could not create context"; case signature_verification_error::verifyinit_failed: return "failed to verify signature: VerifyInit failed"; case signature_verification_error::verifyupdate_failed: return "failed to verify signature: VerifyUpdate failed"; case signature_verification_error::verifyfinal_failed: return "failed to verify signature: VerifyFinal failed"; case signature_verification_error::get_key_failed: return "failed to verify signature: Could not get key"; case signature_verification_error::set_rsa_pss_saltlen_failed: return "failed to verify signature: EVP_PKEY_CTX_set_rsa_pss_saltlen failed"; case signature_verification_error::signature_encoding_failed: return "failed to verify signature: i2d_ECDSA_SIG failed"; default: return "unknown signature verification error"; } } }; static verification_error_cat cat; return cat; } /** * \brief Converts JWT-CPP errors into generic STL error_codes */ inline std::error_code make_error_code(signature_verification_error e) { return {static_cast(e), signature_verification_error_category()}; } /** * \brief Errors related to signature generation errors */ enum class signature_generation_error { ok = 0, hmac_failed = 10, create_context_failed, signinit_failed, signupdate_failed, signfinal_failed, ecdsa_do_sign_failed, digestinit_failed, digestupdate_failed, digestfinal_failed, rsa_padding_failed, rsa_private_encrypt_failed, get_key_failed, set_rsa_pss_saltlen_failed, signature_decoding_failed }; /** * \brief Error category for signature generation errors */ inline std::error_category& signature_generation_error_category() { class signature_generation_error_cat : public std::error_category { public: const char* name() const noexcept override { return "signature_generation_error"; }; std::string message(int ev) const override { switch (static_cast(ev)) { case signature_generation_error::ok: return "no error"; case signature_generation_error::hmac_failed: return "hmac failed"; case signature_generation_error::create_context_failed: return "failed to create signature: could not create context"; case signature_generation_error::signinit_failed: return "failed to create signature: SignInit failed"; case signature_generation_error::signupdate_failed: return "failed to create signature: SignUpdate failed"; case signature_generation_error::signfinal_failed: return "failed to create signature: SignFinal failed"; case signature_generation_error::ecdsa_do_sign_failed: return "failed to generate ecdsa signature"; case signature_generation_error::digestinit_failed: return "failed to create signature: DigestInit failed"; case signature_generation_error::digestupdate_failed: return "failed to create signature: DigestUpdate failed"; case signature_generation_error::digestfinal_failed: return "failed to create signature: DigestFinal failed"; case signature_generation_error::rsa_padding_failed: return "failed to create signature: EVP_PKEY_CTX_set_rsa_padding failed"; case signature_generation_error::rsa_private_encrypt_failed: return "failed to create signature: RSA_private_encrypt failed"; case signature_generation_error::get_key_failed: return "failed to generate signature: Could not get key"; case signature_generation_error::set_rsa_pss_saltlen_failed: return "failed to create signature: EVP_PKEY_CTX_set_rsa_pss_saltlen failed"; case signature_generation_error::signature_decoding_failed: return "failed to create signature: d2i_ECDSA_SIG failed"; default: return "unknown signature generation error"; } } }; static signature_generation_error_cat cat = {}; return cat; } /** * \brief Converts JWT-CPP errors into generic STL error_codes */ inline std::error_code make_error_code(signature_generation_error e) { return {static_cast(e), signature_generation_error_category()}; } /** * \brief Errors related to token verification errors */ enum class token_verification_error { ok = 0, wrong_algorithm = 10, missing_claim, claim_type_missmatch, claim_value_missmatch, token_expired, audience_missmatch }; /** * \brief Error category for token verification errors */ inline std::error_category& token_verification_error_category() { class token_verification_error_cat : public std::error_category { public: const char* name() const noexcept override { return "token_verification_error"; }; std::string message(int ev) const override { switch (static_cast(ev)) { case token_verification_error::ok: return "no error"; case token_verification_error::wrong_algorithm: return "wrong algorithm"; case token_verification_error::missing_claim: return "decoded JWT is missing required claim(s)"; case token_verification_error::claim_type_missmatch: return "claim type does not match expected type"; case token_verification_error::claim_value_missmatch: return "claim value does not match expected value"; case token_verification_error::token_expired: return "token expired"; case token_verification_error::audience_missmatch: return "token doesn't contain the required audience"; default: return "unknown token verification error"; } } }; static token_verification_error_cat cat = {}; return cat; } /** * \brief Converts JWT-CPP errors into generic STL error_codes */ inline std::error_code make_error_code(token_verification_error e) { return {static_cast(e), token_verification_error_category()}; } /** * \brief Raises an exception if any JWT-CPP error codes are active */ inline void throw_if_error(std::error_code ec) { if (ec) { if (ec.category() == rsa_error_category()) throw rsa_exception(ec); if (ec.category() == ecdsa_error_category()) throw ecdsa_exception(ec); if (ec.category() == signature_verification_error_category()) throw signature_verification_exception(ec); if (ec.category() == signature_generation_error_category()) throw signature_generation_exception(ec); if (ec.category() == token_verification_error_category()) throw token_verification_exception(ec); } } } // namespace error } // namespace jwt namespace std { template<> struct is_error_code_enum : true_type {}; template<> struct is_error_code_enum : true_type {}; template<> struct is_error_code_enum : true_type {}; template<> struct is_error_code_enum : true_type {}; template<> struct is_error_code_enum : true_type {}; } // namespace std namespace jwt { /** * \brief A collection for working with certificates * * These _helpers_ are usefully when working with certificates OpenSSL APIs. * For example, when dealing with JWKS (JSON Web Key Set)[https://tools.ietf.org/html/rfc7517] * you maybe need to extract the modulus and exponent of an RSA Public Key. */ namespace helper { /** * \brief Handle class for EVP_PKEY structures * * Starting from OpenSSL 1.1.0, EVP_PKEY has internal reference counting. This handle class allows * jwt-cpp to leverage that and thus safe an allocation for the control block in std::shared_ptr. * The handle uses shared_ptr as a fallback on older versions. The behaviour should be identical between both. */ class evp_pkey_handle { public: /** * \brief Creates a null key pointer. */ constexpr evp_pkey_handle() noexcept = default; #ifdef JWT_OPENSSL_1_0_0 /** * \brief Construct a new handle. The handle takes ownership of the key. * \param key The key to store */ explicit evp_pkey_handle(EVP_PKEY* key) { m_key = std::shared_ptr(key, EVP_PKEY_free); } EVP_PKEY* get() const noexcept { return m_key.get(); } bool operator!() const noexcept { return m_key == nullptr; } explicit operator bool() const noexcept { return m_key != nullptr; } private: std::shared_ptr m_key{nullptr}; #else /** * \brief Construct a new handle. The handle takes ownership of the key. * \param key The key to store */ explicit constexpr evp_pkey_handle(EVP_PKEY* key) noexcept : m_key{key} {} evp_pkey_handle(const evp_pkey_handle& other) : m_key{other.m_key} { if (m_key != nullptr && EVP_PKEY_up_ref(m_key) != 1) throw std::runtime_error("EVP_PKEY_up_ref failed"); } // C++11 requires the body of a constexpr constructor to be empty #if __cplusplus >= 201402L constexpr #endif evp_pkey_handle(evp_pkey_handle&& other) noexcept : m_key{other.m_key} { other.m_key = nullptr; } evp_pkey_handle& operator=(const evp_pkey_handle& other) { if (&other == this) return *this; decrement_ref_count(m_key); m_key = other.m_key; increment_ref_count(m_key); return *this; } evp_pkey_handle& operator=(evp_pkey_handle&& other) noexcept { if (&other == this) return *this; decrement_ref_count(m_key); m_key = other.m_key; other.m_key = nullptr; return *this; } evp_pkey_handle& operator=(EVP_PKEY* key) { decrement_ref_count(m_key); m_key = key; increment_ref_count(m_key); return *this; } ~evp_pkey_handle() noexcept { decrement_ref_count(m_key); } EVP_PKEY* get() const noexcept { return m_key; } bool operator!() const noexcept { return m_key == nullptr; } explicit operator bool() const noexcept { return m_key != nullptr; } private: EVP_PKEY* m_key{nullptr}; static void increment_ref_count(EVP_PKEY* key) { if (key != nullptr && EVP_PKEY_up_ref(key) != 1) throw std::runtime_error("EVP_PKEY_up_ref failed"); } static void decrement_ref_count(EVP_PKEY* key) noexcept { if (key != nullptr) EVP_PKEY_free(key); } #endif }; inline std::unique_ptr make_mem_buf_bio() { return std::unique_ptr(BIO_new(BIO_s_mem()), BIO_free_all); } inline std::unique_ptr make_mem_buf_bio(const std::string& data) { return std::unique_ptr( #if OPENSSL_VERSION_NUMBER <= 0x10100003L BIO_new_mem_buf(const_cast(data.data()), static_cast(data.size())), BIO_free_all #else BIO_new_mem_buf(data.data(), static_cast(data.size())), BIO_free_all #endif ); } template std::string write_bio_to_string(std::unique_ptr& bio_out, std::error_code& ec) { char* ptr = nullptr; auto len = BIO_get_mem_data(bio_out.get(), &ptr); if (len <= 0 || ptr == nullptr) { ec = error_category::convert_to_pem_failed; return {}; } return {ptr, static_cast(len)}; } inline std::unique_ptr make_evp_md_ctx() { return #ifdef JWT_OPENSSL_1_0_0 std::unique_ptr(EVP_MD_CTX_create(), &EVP_MD_CTX_destroy); #else std::unique_ptr(EVP_MD_CTX_new(), &EVP_MD_CTX_free); #endif } /** * \brief Extract the public key of a pem certificate * * \tparam error_category jwt::error enum category to match with the keys being used * \param certstr String containing the certificate encoded as pem * \param pw Password used to decrypt certificate (leave empty if not encrypted) * \param ec error_code for error_detection (gets cleared if no error occurred) */ template std::string extract_pubkey_from_cert(const std::string& certstr, const std::string& pw, std::error_code& ec) { ec.clear(); auto certbio = make_mem_buf_bio(certstr); auto keybio = make_mem_buf_bio(); if (!certbio || !keybio) { ec = error_category::create_mem_bio_failed; return {}; } std::unique_ptr cert( PEM_read_bio_X509(certbio.get(), nullptr, nullptr, const_cast(pw.c_str())), X509_free); if (!cert) { ec = error_category::cert_load_failed; return {}; } std::unique_ptr key(X509_get_pubkey(cert.get()), EVP_PKEY_free); if (!key) { ec = error_category::get_key_failed; return {}; } if (PEM_write_bio_PUBKEY(keybio.get(), key.get()) == 0) { ec = error_category::write_key_failed; return {}; } return write_bio_to_string(keybio, ec); } /** * \brief Extract the public key of a pem certificate * * \tparam error_category jwt::error enum category to match with the keys being used * \param certstr String containing the certificate encoded as pem * \param pw Password used to decrypt certificate (leave empty if not encrypted) * \throw templated error_category's type exception if an error occurred */ template std::string extract_pubkey_from_cert(const std::string& certstr, const std::string& pw = "") { std::error_code ec; auto res = extract_pubkey_from_cert(certstr, pw, ec); error::throw_if_error(ec); return res; } /** * \brief Convert the certificate provided as DER to PEM. * * \param cert_der_str String containing the certificate encoded as base64 DER * \param ec error_code for error_detection (gets cleared if no error occurs) */ inline std::string convert_der_to_pem(const std::string& cert_der_str, std::error_code& ec) { ec.clear(); auto c_str = reinterpret_cast(cert_der_str.c_str()); std::unique_ptr cert( d2i_X509(NULL, &c_str, static_cast(cert_der_str.size())), X509_free); auto certbio = make_mem_buf_bio(); if (!cert || !certbio) { ec = error::rsa_error::create_mem_bio_failed; return {}; } if (!PEM_write_bio_X509(certbio.get(), cert.get())) { ec = error::rsa_error::write_cert_failed; return {}; } return write_bio_to_string(certbio, ec); } /** * \brief Convert the certificate provided as base64 DER to PEM. * * This is useful when using with JWKs as x5c claim is encoded as base64 DER. More info * [here](https://tools.ietf.org/html/rfc7517#section-4.7). * * \tparam Decode is callable, taking a string_type and returns a string_type. * It should ensure the padding of the input and then base64 decode and return * the results. * * \param cert_base64_der_str String containing the certificate encoded as base64 DER * \param decode The function to decode the cert * \param ec error_code for error_detection (gets cleared if no error occurs) */ template std::string convert_base64_der_to_pem(const std::string& cert_base64_der_str, Decode decode, std::error_code& ec) { ec.clear(); const auto decoded_str = decode(cert_base64_der_str); return convert_der_to_pem(decoded_str, ec); } /** * \brief Convert the certificate provided as base64 DER to PEM. * * This is useful when using with JWKs as x5c claim is encoded as base64 DER. More info * [here](https://tools.ietf.org/html/rfc7517#section-4.7) * * \tparam Decode is callable, taking a string_type and returns a string_type. * It should ensure the padding of the input and then base64 decode and return * the results. * * \param cert_base64_der_str String containing the certificate encoded as base64 DER * \param decode The function to decode the cert * \throw rsa_exception if an error occurred */ template std::string convert_base64_der_to_pem(const std::string& cert_base64_der_str, Decode decode) { std::error_code ec; auto res = convert_base64_der_to_pem(cert_base64_der_str, std::move(decode), ec); error::throw_if_error(ec); return res; } /** * \brief Convert the certificate provided as DER to PEM. * * \param cert_der_str String containing the DER certificate * \throw rsa_exception if an error occurred */ inline std::string convert_der_to_pem(const std::string& cert_der_str) { std::error_code ec; auto res = convert_der_to_pem(cert_der_str, ec); error::throw_if_error(ec); return res; } #ifndef JWT_DISABLE_BASE64 /** * \brief Convert the certificate provided as base64 DER to PEM. * * This is useful when using with JWKs as x5c claim is encoded as base64 DER. More info * [here](https://tools.ietf.org/html/rfc7517#section-4.7) * * \param cert_base64_der_str String containing the certificate encoded as base64 DER * \param ec error_code for error_detection (gets cleared if no error occurs) */ inline std::string convert_base64_der_to_pem(const std::string& cert_base64_der_str, std::error_code& ec) { auto decode = [](const std::string& token) { return base::decode(base::pad(token)); }; return convert_base64_der_to_pem(cert_base64_der_str, std::move(decode), ec); } /** * \brief Convert the certificate provided as base64 DER to PEM. * * This is useful when using with JWKs as x5c claim is encoded as base64 DER. More info * [here](https://tools.ietf.org/html/rfc7517#section-4.7) * * \param cert_base64_der_str String containing the certificate encoded as base64 DER * \throw rsa_exception if an error occurred */ inline std::string convert_base64_der_to_pem(const std::string& cert_base64_der_str) { std::error_code ec; auto res = convert_base64_der_to_pem(cert_base64_der_str, ec); error::throw_if_error(ec); return res; } #endif /** * \brief Load a public key from a string. * * The string should contain a pem encoded certificate or public key * * \tparam error_category jwt::error enum category to match with the keys being used * \param key String containing the certificate encoded as pem * \param password Password used to decrypt certificate (leave empty if not encrypted) * \param ec error_code for error_detection (gets cleared if no error occurs) */ template evp_pkey_handle load_public_key_from_string(const std::string& key, const std::string& password, std::error_code& ec) { ec.clear(); auto pubkey_bio = make_mem_buf_bio(); if (!pubkey_bio) { ec = error_category::create_mem_bio_failed; return {}; } if (key.substr(0, 27) == "-----BEGIN CERTIFICATE-----") { auto epkey = helper::extract_pubkey_from_cert(key, password, ec); if (ec) return {}; const int len = static_cast(epkey.size()); if (BIO_write(pubkey_bio.get(), epkey.data(), len) != len) { ec = error_category::load_key_bio_write; return {}; } } else { const int len = static_cast(key.size()); if (BIO_write(pubkey_bio.get(), key.data(), len) != len) { ec = error_category::load_key_bio_write; return {}; } } evp_pkey_handle pkey(PEM_read_bio_PUBKEY( pubkey_bio.get(), nullptr, nullptr, (void*)password.data())); // NOLINT(google-readability-casting) requires `const_cast` if (!pkey) ec = error_category::load_key_bio_read; return pkey; } /** * \brief Load a public key from a string. * * The string should contain a pem encoded certificate or public key * * \tparam error_category jwt::error enum category to match with the keys being used * \param key String containing the certificate encoded as pem * \param password Password used to decrypt certificate (leave empty if not encrypted) * \throw Templated error_category's type exception if an error occurred */ template inline evp_pkey_handle load_public_key_from_string(const std::string& key, const std::string& password = "") { std::error_code ec; auto res = load_public_key_from_string(key, password, ec); error::throw_if_error(ec); return res; } /** * \brief Load a private key from a string. * * \tparam error_category jwt::error enum category to match with the keys being used * \param key String containing a private key as pem * \param password Password used to decrypt key (leave empty if not encrypted) * \param ec error_code for error_detection (gets cleared if no error occurs) */ template inline evp_pkey_handle load_private_key_from_string(const std::string& key, const std::string& password, std::error_code& ec) { ec.clear(); auto private_key_bio = make_mem_buf_bio(); if (!private_key_bio) { ec = error_category::create_mem_bio_failed; return {}; } const int len = static_cast(key.size()); if (BIO_write(private_key_bio.get(), key.data(), len) != len) { ec = error_category::load_key_bio_write; return {}; } evp_pkey_handle pkey( PEM_read_bio_PrivateKey(private_key_bio.get(), nullptr, nullptr, const_cast(password.c_str()))); if (!pkey) ec = error_category::load_key_bio_read; return pkey; } /** * \brief Load a private key from a string. * * \tparam error_category jwt::error enum category to match with the keys being used * \param key String containing a private key as pem * \param password Password used to decrypt key (leave empty if not encrypted) * \throw Templated error_category's type exception if an error occurred */ template inline evp_pkey_handle load_private_key_from_string(const std::string& key, const std::string& password = "") { std::error_code ec; auto res = load_private_key_from_string(key, password, ec); error::throw_if_error(ec); return res; } /** * \brief Load a public key from a string. * * The string should contain a pem encoded certificate or public key * * \deprecated Use the templated version helper::load_private_key_from_string with error::ecdsa_error * * \param key String containing the certificate encoded as pem * \param password Password used to decrypt certificate (leave empty if not encrypted) * \param ec error_code for error_detection (gets cleared if no error occurs) */ inline evp_pkey_handle load_public_ec_key_from_string(const std::string& key, const std::string& password, std::error_code& ec) { return load_public_key_from_string(key, password, ec); } /** * Convert a OpenSSL BIGNUM to a std::string * \param bn BIGNUM to convert * \return bignum as string */ inline #ifdef JWT_OPENSSL_1_0_0 std::string bn2raw(BIGNUM* bn) #else std::string bn2raw(const BIGNUM* bn) #endif { std::string res(BN_num_bytes(bn), '\0'); BN_bn2bin(bn, (unsigned char*)res.data()); // NOLINT(google-readability-casting) requires `const_cast` return res; } /** * Convert an std::string to a OpenSSL BIGNUM * \param raw String to convert * \param ec error_code for error_detection (gets cleared if no error occurs) * \return BIGNUM representation */ inline std::unique_ptr raw2bn(const std::string& raw, std::error_code& ec) { auto bn = BN_bin2bn(reinterpret_cast(raw.data()), static_cast(raw.size()), nullptr); // https://www.openssl.org/docs/man1.1.1/man3/BN_bin2bn.html#RETURN-VALUES if (!bn) { ec = error::rsa_error::set_rsa_failed; return {nullptr, BN_free}; } return {bn, BN_free}; } /** * Convert an std::string to a OpenSSL BIGNUM * \param raw String to convert * \return BIGNUM representation */ inline std::unique_ptr raw2bn(const std::string& raw) { std::error_code ec; auto res = raw2bn(raw, ec); error::throw_if_error(ec); return res; } /** * \brief Load a public key from a string. * * The string should contain a pem encoded certificate or public key * * \deprecated Use the templated version helper::load_private_key_from_string with error::ecdsa_error * * \param key String containing the certificate or key encoded as pem * \param password Password used to decrypt certificate or key (leave empty if not encrypted) * \throw ecdsa_exception if an error occurred */ inline evp_pkey_handle load_public_ec_key_from_string(const std::string& key, const std::string& password = "") { std::error_code ec; auto res = load_public_key_from_string(key, password, ec); error::throw_if_error(ec); return res; } /** * \brief Load a private key from a string. * * \deprecated Use the templated version helper::load_private_key_from_string with error::ecdsa_error * * \param key String containing a private key as pem * \param password Password used to decrypt key (leave empty if not encrypted) * \param ec error_code for error_detection (gets cleared if no error occurs) */ inline evp_pkey_handle load_private_ec_key_from_string(const std::string& key, const std::string& password, std::error_code& ec) { return load_private_key_from_string(key, password, ec); } /** * \brief create public key from modulus and exponent. This is defined in * [RFC 7518 Section 6.3](https://www.rfc-editor.org/rfc/rfc7518#section-6.3) * Using the required "n" (Modulus) Parameter and "e" (Exponent) Parameter. * * \tparam Decode is callable, taking a string_type and returns a string_type. * It should ensure the padding of the input and then base64url decode and * return the results. * \param modulus string containing base64url encoded modulus * \param exponent string containing base64url encoded exponent * \param decode The function to decode the RSA parameters * \param ec error_code for error_detection (gets cleared if no error occur * \return public key in PEM format */ template std::string create_public_key_from_rsa_components(const std::string& modulus, const std::string& exponent, Decode decode, std::error_code& ec) { ec.clear(); auto decoded_modulus = decode(modulus); auto decoded_exponent = decode(exponent); auto n = helper::raw2bn(decoded_modulus, ec); if (ec) return {}; auto e = helper::raw2bn(decoded_exponent, ec); if (ec) return {}; #if defined(JWT_OPENSSL_3_0) // OpenSSL deprecated mutable keys and there is a new way for making them // https://mta.openssl.org/pipermail/openssl-users/2021-July/013994.html // https://www.openssl.org/docs/man3.1/man3/OSSL_PARAM_BLD_new.html#Example-2 std::unique_ptr param_bld(OSSL_PARAM_BLD_new(), OSSL_PARAM_BLD_free); if (!param_bld) { ec = error::rsa_error::create_context_failed; return {}; } if (OSSL_PARAM_BLD_push_BN(param_bld.get(), "n", n.get()) != 1 || OSSL_PARAM_BLD_push_BN(param_bld.get(), "e", e.get()) != 1) { ec = error::rsa_error::set_rsa_failed; return {}; } std::unique_ptr params(OSSL_PARAM_BLD_to_param(param_bld.get()), OSSL_PARAM_free); if (!params) { ec = error::rsa_error::set_rsa_failed; return {}; } std::unique_ptr ctx( EVP_PKEY_CTX_new_from_name(nullptr, "RSA", nullptr), EVP_PKEY_CTX_free); if (!ctx) { ec = error::rsa_error::create_context_failed; return {}; } // https://www.openssl.org/docs/man3.0/man3/EVP_PKEY_fromdata.html#EXAMPLES // Error codes based on https://www.openssl.org/docs/manmaster/man3/EVP_PKEY_fromdata_init.html#RETURN-VALUES EVP_PKEY* pkey = NULL; if (EVP_PKEY_fromdata_init(ctx.get()) <= 0 || EVP_PKEY_fromdata(ctx.get(), &pkey, EVP_PKEY_KEYPAIR, params.get()) <= 0) { // It's unclear if this can fail after allocating but free it anyways // https://www.openssl.org/docs/man3.0/man3/EVP_PKEY_fromdata.html EVP_PKEY_free(pkey); ec = error::rsa_error::cert_load_failed; return {}; } // Transfer ownership so we get ref counter and cleanup evp_pkey_handle rsa(pkey); #else std::unique_ptr rsa(RSA_new(), RSA_free); #if defined(JWT_OPENSSL_1_1_1) || defined(JWT_OPENSSL_1_1_0) // After this RSA_free will also free the n and e big numbers // See https://github.com/Thalhammer/jwt-cpp/pull/298#discussion_r1282619186 if (RSA_set0_key(rsa.get(), n.get(), e.get(), nullptr) == 1) { // This can only fail we passed in NULL for `n` or `e` // https://github.com/openssl/openssl/blob/d6e4056805f54bb1a0ef41fa3a6a35b70c94edba/crypto/rsa/rsa_lib.c#L396 // So to make sure there is no memory leak, we hold the references n.release(); e.release(); } else { ec = error::rsa_error::set_rsa_failed; return {}; } #elif defined(JWT_OPENSSL_1_0_0) rsa->e = e.release(); rsa->n = n.release(); rsa->d = nullptr; #endif #endif auto pub_key_bio = make_mem_buf_bio(); if (!pub_key_bio) { ec = error::rsa_error::create_mem_bio_failed; return {}; } auto write_pem_to_bio = #if defined(JWT_OPENSSL_3_0) // https://www.openssl.org/docs/man3.1/man3/PEM_write_bio_RSA_PUBKEY.html &PEM_write_bio_PUBKEY; #else &PEM_write_bio_RSA_PUBKEY; #endif if (write_pem_to_bio(pub_key_bio.get(), rsa.get()) != 1) { ec = error::rsa_error::load_key_bio_write; return {}; } return write_bio_to_string(pub_key_bio, ec); } /** * Create public key from modulus and exponent. This is defined in * [RFC 7518 Section 6.3](https://www.rfc-editor.org/rfc/rfc7518#section-6.3) * Using the required "n" (Modulus) Parameter and "e" (Exponent) Parameter. * * \tparam Decode is callable, taking a string_type and returns a string_type. * It should ensure the padding of the input and then base64url decode and * return the results. * \param modulus string containing base64url encoded modulus * \param exponent string containing base64url encoded exponent * \param decode The function to decode the RSA parameters * \return public key in PEM format */ template std::string create_public_key_from_rsa_components(const std::string& modulus, const std::string& exponent, Decode decode) { std::error_code ec; auto res = create_public_key_from_rsa_components(modulus, exponent, decode, ec); error::throw_if_error(ec); return res; } #ifndef JWT_DISABLE_BASE64 /** * Create public key from modulus and exponent. This is defined in * [RFC 7518 Section 6.3](https://www.rfc-editor.org/rfc/rfc7518#section-6.3) * Using the required "n" (Modulus) Parameter and "e" (Exponent) Parameter. * * \param modulus string containing base64 encoded modulus * \param exponent string containing base64 encoded exponent * \param ec error_code for error_detection (gets cleared if no error occur * \return public key in PEM format */ inline std::string create_public_key_from_rsa_components(const std::string& modulus, const std::string& exponent, std::error_code& ec) { auto decode = [](const std::string& token) { return base::decode(base::pad(token)); }; return create_public_key_from_rsa_components(modulus, exponent, std::move(decode), ec); } /** * Create public key from modulus and exponent. This is defined in * [RFC 7518 Section 6.3](https://www.rfc-editor.org/rfc/rfc7518#section-6.3) * Using the required "n" (Modulus) Parameter and "e" (Exponent) Parameter. * * \param modulus string containing base64url encoded modulus * \param exponent string containing base64url encoded exponent * \return public key in PEM format */ inline std::string create_public_key_from_rsa_components(const std::string& modulus, const std::string& exponent) { std::error_code ec; auto res = create_public_key_from_rsa_components(modulus, exponent, ec); error::throw_if_error(ec); return res; } #endif /** * \brief Load a private key from a string. * * \deprecated Use the templated version helper::load_private_key_from_string with error::ecdsa_error * * \param key String containing a private key as pem * \param password Password used to decrypt key (leave empty if not encrypted) * \throw ecdsa_exception if an error occurred */ inline evp_pkey_handle load_private_ec_key_from_string(const std::string& key, const std::string& password = "") { std::error_code ec; auto res = load_private_key_from_string(key, password, ec); error::throw_if_error(ec); return res; } #if defined(JWT_OPENSSL_3_0) /** * \brief Convert a curve name to a group name. * * \param curve string containing curve name * \param ec error_code for error_detection * \return group name */ inline std::string curve2group(const std::string curve, std::error_code& ec) { if (curve == "P-256") { return "prime256v1"; } else if (curve == "P-384") { return "secp384r1"; } else if (curve == "P-521") { return "secp521r1"; } else { ec = jwt::error::ecdsa_error::unknown_curve; return {}; } } #else /** * \brief Convert a curve name to an ID. * * \param curve string containing curve name * \param ec error_code for error_detection * \return ID */ inline int curve2nid(const std::string curve, std::error_code& ec) { if (curve == "P-256") { return NID_X9_62_prime256v1; } else if (curve == "P-384") { return NID_secp384r1; } else if (curve == "P-521") { return NID_secp521r1; } else { ec = jwt::error::ecdsa_error::unknown_curve; return {}; } } #endif /** * Create public key from curve name and coordinates. This is defined in * [RFC 7518 Section 6.2](https://www.rfc-editor.org/rfc/rfc7518#section-6.2) * Using the required "crv" (Curve), "x" (X Coordinate) and "y" (Y Coordinate) Parameters. * * \tparam Decode is callable, taking a string_type and returns a string_type. * It should ensure the padding of the input and then base64url decode and * return the results. * \param curve string containing curve name * \param x string containing base64url encoded x coordinate * \param y string containing base64url encoded y coordinate * \param decode The function to decode the RSA parameters * \param ec error_code for error_detection (gets cleared if no error occur * \return public key in PEM format */ template std::string create_public_key_from_ec_components(const std::string& curve, const std::string& x, const std::string& y, Decode decode, std::error_code& ec) { ec.clear(); auto decoded_x = decode(x); auto decoded_y = decode(y); #if defined(JWT_OPENSSL_3_0) // OpenSSL deprecated mutable keys and there is a new way for making them // https://mta.openssl.org/pipermail/openssl-users/2021-July/013994.html // https://www.openssl.org/docs/man3.1/man3/OSSL_PARAM_BLD_new.html#Example-2 std::unique_ptr param_bld(OSSL_PARAM_BLD_new(), OSSL_PARAM_BLD_free); if (!param_bld) { ec = error::ecdsa_error::create_context_failed; return {}; } std::string group = helper::curve2group(curve, ec); if (ec) return {}; // https://github.com/openssl/openssl/issues/16270#issuecomment-895734092 std::string pub = std::string("\x04").append(decoded_x).append(decoded_y); if (OSSL_PARAM_BLD_push_utf8_string(param_bld.get(), "group", group.data(), group.size()) != 1 || OSSL_PARAM_BLD_push_octet_string(param_bld.get(), "pub", pub.data(), pub.size()) != 1) { ec = error::ecdsa_error::set_ecdsa_failed; return {}; } std::unique_ptr params(OSSL_PARAM_BLD_to_param(param_bld.get()), OSSL_PARAM_free); if (!params) { ec = error::ecdsa_error::set_ecdsa_failed; return {}; } std::unique_ptr ctx( EVP_PKEY_CTX_new_from_name(nullptr, "EC", nullptr), EVP_PKEY_CTX_free); if (!ctx) { ec = error::ecdsa_error::create_context_failed; return {}; } // https://www.openssl.org/docs/man3.0/man3/EVP_PKEY_fromdata.html#EXAMPLES // Error codes based on https://www.openssl.org/docs/manmaster/man3/EVP_PKEY_fromdata_init.html#RETURN-VALUES EVP_PKEY* pkey = NULL; if (EVP_PKEY_fromdata_init(ctx.get()) <= 0 || EVP_PKEY_fromdata(ctx.get(), &pkey, EVP_PKEY_KEYPAIR, params.get()) <= 0) { // It's unclear if this can fail after allocating but free it anyways // https://www.openssl.org/docs/man3.0/man3/EVP_PKEY_fromdata.html EVP_PKEY_free(pkey); ec = error::ecdsa_error::cert_load_failed; return {}; } // Transfer ownership so we get ref counter and cleanup evp_pkey_handle ecdsa(pkey); #else int nid = helper::curve2nid(curve, ec); if (ec) return {}; auto qx = helper::raw2bn(decoded_x, ec); if (ec) return {}; auto qy = helper::raw2bn(decoded_y, ec); if (ec) return {}; std::unique_ptr ecgroup(EC_GROUP_new_by_curve_name(nid), EC_GROUP_free); if (!ecgroup) { ec = error::ecdsa_error::set_ecdsa_failed; return {}; } EC_GROUP_set_asn1_flag(ecgroup.get(), OPENSSL_EC_NAMED_CURVE); std::unique_ptr ecpoint(EC_POINT_new(ecgroup.get()), EC_POINT_free); if (!ecpoint || EC_POINT_set_affine_coordinates_GFp(ecgroup.get(), ecpoint.get(), qx.get(), qy.get(), nullptr) != 1) { ec = error::ecdsa_error::set_ecdsa_failed; return {}; } std::unique_ptr ecdsa(EC_KEY_new(), EC_KEY_free); if (!ecdsa || EC_KEY_set_group(ecdsa.get(), ecgroup.get()) != 1 || EC_KEY_set_public_key(ecdsa.get(), ecpoint.get()) != 1) { ec = error::ecdsa_error::set_ecdsa_failed; return {}; } #endif auto pub_key_bio = make_mem_buf_bio(); if (!pub_key_bio) { ec = error::ecdsa_error::create_mem_bio_failed; return {}; } auto write_pem_to_bio = #if defined(JWT_OPENSSL_3_0) // https://www.openssl.org/docs/man3.1/man3/PEM_write_bio_EC_PUBKEY.html &PEM_write_bio_PUBKEY; #else &PEM_write_bio_EC_PUBKEY; #endif if (write_pem_to_bio(pub_key_bio.get(), ecdsa.get()) != 1) { ec = error::ecdsa_error::load_key_bio_write; return {}; } return write_bio_to_string(pub_key_bio, ec); } /** * Create public key from curve name and coordinates. This is defined in * [RFC 7518 Section 6.2](https://www.rfc-editor.org/rfc/rfc7518#section-6.2) * Using the required "crv" (Curve), "x" (X Coordinate) and "y" (Y Coordinate) Parameters. * * \tparam Decode is callable, taking a string_type and returns a string_type. * It should ensure the padding of the input and then base64url decode and * return the results. * \param curve string containing curve name * \param x string containing base64url encoded x coordinate * \param y string containing base64url encoded y coordinate * \param decode The function to decode the RSA parameters * \return public key in PEM format */ template std::string create_public_key_from_ec_components(const std::string& curve, const std::string& x, const std::string& y, Decode decode) { std::error_code ec; auto res = create_public_key_from_ec_components(curve, x, y, decode, ec); error::throw_if_error(ec); return res; } #ifndef JWT_DISABLE_BASE64 /** * Create public key from curve name and coordinates. This is defined in * [RFC 7518 Section 6.2](https://www.rfc-editor.org/rfc/rfc7518#section-6.2) * Using the required "crv" (Curve), "x" (X Coordinate) and "y" (Y Coordinate) Parameters. * * \param curve string containing curve name * \param x string containing base64url encoded x coordinate * \param y string containing base64url encoded y coordinate * \param ec error_code for error_detection (gets cleared if no error occur * \return public key in PEM format */ inline std::string create_public_key_from_ec_components(const std::string& curve, const std::string& x, const std::string& y, std::error_code& ec) { auto decode = [](const std::string& token) { return base::decode(base::pad(token)); }; return create_public_key_from_ec_components(curve, x, y, std::move(decode), ec); } /** * Create public key from curve name and coordinates. This is defined in * [RFC 7518 Section 6.2](https://www.rfc-editor.org/rfc/rfc7518#section-6.2) * Using the required "crv" (Curve), "x" (X Coordinate) and "y" (Y Coordinate) Parameters. * * \param curve string containing curve name * \param x string containing base64url encoded x coordinate * \param y string containing base64url encoded y coordinate * \return public key in PEM format */ inline std::string create_public_key_from_ec_components(const std::string& curve, const std::string& x, const std::string& y) { std::error_code ec; auto res = create_public_key_from_ec_components(curve, x, y, ec); error::throw_if_error(ec); return res; } #endif } // namespace helper /** * \brief Various cryptographic algorithms when working with JWT * * JWT (JSON Web Tokens) signatures are typically used as the payload for a JWS (JSON Web Signature) or * JWE (JSON Web Encryption). Both of these use various cryptographic as specified by * [RFC7518](https://tools.ietf.org/html/rfc7518) and are exposed through the a [JOSE * Header](https://tools.ietf.org/html/rfc7515#section-4) which points to one of the JWA [JSON Web * Algorithms](https://tools.ietf.org/html/rfc7518#section-3.1) */ namespace algorithm { /** * \brief "none" algorithm. * * Returns and empty signature and checks if the given signature is empty. * See [RFC 7518 Section 3.6](https://datatracker.ietf.org/doc/html/rfc7518#section-3.6) * for more information. */ struct none { /** * \brief Return an empty string */ std::string sign(const std::string& /*unused*/, std::error_code& ec) const { ec.clear(); return {}; } /** * \brief Check if the given signature is empty. * * JWT's with "none" algorithm should not contain a signature. * \param signature Signature data to verify * \param ec error_code filled with details about the error */ void verify(const std::string& /*unused*/, const std::string& signature, std::error_code& ec) const { ec.clear(); if (!signature.empty()) { ec = error::signature_verification_error::invalid_signature; } } /// Get algorithm name std::string name() const { return "none"; } }; /** * \brief Base class for HMAC family of algorithms */ struct hmacsha { /** * Construct new hmac algorithm * * \param key Key to use for HMAC * \param md Pointer to hash function * \param name Name of the algorithm */ hmacsha(std::string key, const EVP_MD* (*md)(), std::string name) : secret(std::move(key)), md(md), alg_name(std::move(name)) {} /** * Sign jwt data * * \param data The data to sign * \param ec error_code filled with details on error * \return HMAC signature for the given data */ std::string sign(const std::string& data, std::error_code& ec) const { ec.clear(); std::string res(static_cast(EVP_MAX_MD_SIZE), '\0'); auto len = static_cast(res.size()); if (HMAC(md(), secret.data(), static_cast(secret.size()), reinterpret_cast(data.data()), static_cast(data.size()), (unsigned char*)res.data(), // NOLINT(google-readability-casting) requires `const_cast` &len) == nullptr) { ec = error::signature_generation_error::hmac_failed; return {}; } res.resize(len); return res; } /** * Check if signature is valid * * \param data The data to check signature against * \param signature Signature provided by the jwt * \param ec Filled with details about failure. */ void verify(const std::string& data, const std::string& signature, std::error_code& ec) const { ec.clear(); auto res = sign(data, ec); if (ec) return; bool matched = true; for (size_t i = 0; i < std::min(res.size(), signature.size()); i++) if (res[i] != signature[i]) matched = false; if (res.size() != signature.size()) matched = false; if (!matched) { ec = error::signature_verification_error::invalid_signature; return; } } /** * Returns the algorithm name provided to the constructor * * \return algorithm's name */ std::string name() const { return alg_name; } private: /// HMAC secret const std::string secret; /// HMAC hash generator const EVP_MD* (*md)(); /// algorithm's name const std::string alg_name; }; /** * \brief Base class for RSA family of algorithms */ struct rsa { /** * Construct new rsa algorithm * * \param public_key RSA public key in PEM format * \param private_key RSA private key or empty string if not available. If empty, signing will always fail. * \param public_key_password Password to decrypt public key pem. * \param private_key_password Password to decrypt private key pem. * \param md Pointer to hash function * \param name Name of the algorithm */ rsa(const std::string& public_key, const std::string& private_key, const std::string& public_key_password, const std::string& private_key_password, const EVP_MD* (*md)(), std::string name) : md(md), alg_name(std::move(name)) { if (!private_key.empty()) { pkey = helper::load_private_key_from_string(private_key, private_key_password); } else if (!public_key.empty()) { pkey = helper::load_public_key_from_string(public_key, public_key_password); } else throw error::rsa_exception(error::rsa_error::no_key_provided); } /** * Sign jwt data * \param data The data to sign * \param ec error_code filled with details on error * \return RSA signature for the given data */ std::string sign(const std::string& data, std::error_code& ec) const { ec.clear(); auto ctx = helper::make_evp_md_ctx(); if (!ctx) { ec = error::signature_generation_error::create_context_failed; return {}; } if (!EVP_SignInit(ctx.get(), md())) { ec = error::signature_generation_error::signinit_failed; return {}; } std::string res(EVP_PKEY_size(pkey.get()), '\0'); unsigned int len = 0; if (!EVP_SignUpdate(ctx.get(), data.data(), data.size())) { ec = error::signature_generation_error::signupdate_failed; return {}; } if (EVP_SignFinal(ctx.get(), (unsigned char*)res.data(), &len, pkey.get()) == 0) { ec = error::signature_generation_error::signfinal_failed; return {}; } res.resize(len); return res; } /** * Check if signature is valid * * \param data The data to check signature against * \param signature Signature provided by the jwt * \param ec Filled with details on failure */ void verify(const std::string& data, const std::string& signature, std::error_code& ec) const { ec.clear(); auto ctx = helper::make_evp_md_ctx(); if (!ctx) { ec = error::signature_verification_error::create_context_failed; return; } if (!EVP_VerifyInit(ctx.get(), md())) { ec = error::signature_verification_error::verifyinit_failed; return; } if (!EVP_VerifyUpdate(ctx.get(), data.data(), data.size())) { ec = error::signature_verification_error::verifyupdate_failed; return; } auto res = EVP_VerifyFinal(ctx.get(), reinterpret_cast(signature.data()), static_cast(signature.size()), pkey.get()); if (res != 1) { ec = error::signature_verification_error::verifyfinal_failed; return; } } /** * Returns the algorithm name provided to the constructor * \return algorithm's name */ std::string name() const { return alg_name; } private: /// OpenSSL structure containing converted keys helper::evp_pkey_handle pkey; /// Hash generator const EVP_MD* (*md)(); /// algorithm's name const std::string alg_name; }; /** * \brief Base class for ECDSA family of algorithms */ struct ecdsa { /** * Construct new ecdsa algorithm * * \param public_key ECDSA public key in PEM format * \param private_key ECDSA private key or empty string if not available. If empty, signing will always fail * \param public_key_password Password to decrypt public key pem * \param private_key_password Password to decrypt private key pem * \param md Pointer to hash function * \param name Name of the algorithm * \param siglen The bit length of the signature */ ecdsa(const std::string& public_key, const std::string& private_key, const std::string& public_key_password, const std::string& private_key_password, const EVP_MD* (*md)(), std::string name, size_t siglen) : md(md), alg_name(std::move(name)), signature_length(siglen) { if (!private_key.empty()) { pkey = helper::load_private_ec_key_from_string(private_key, private_key_password); check_private_key(pkey.get()); } else if (!public_key.empty()) { pkey = helper::load_public_ec_key_from_string(public_key, public_key_password); check_public_key(pkey.get()); } else { throw error::ecdsa_exception(error::ecdsa_error::no_key_provided); } if (!pkey) throw error::ecdsa_exception(error::ecdsa_error::invalid_key); size_t keysize = EVP_PKEY_bits(pkey.get()); if (keysize != signature_length * 4 && (signature_length != 132 || keysize != 521)) throw error::ecdsa_exception(error::ecdsa_error::invalid_key_size); } /** * Sign jwt data * \param data The data to sign * \param ec error_code filled with details on error * \return ECDSA signature for the given data */ std::string sign(const std::string& data, std::error_code& ec) const { ec.clear(); auto ctx = helper::make_evp_md_ctx(); if (!ctx) { ec = error::signature_generation_error::create_context_failed; return {}; } if (!EVP_DigestSignInit(ctx.get(), nullptr, md(), nullptr, pkey.get())) { ec = error::signature_generation_error::signinit_failed; return {}; } if (!EVP_DigestUpdate(ctx.get(), data.data(), data.size())) { ec = error::signature_generation_error::digestupdate_failed; return {}; } size_t len = 0; if (!EVP_DigestSignFinal(ctx.get(), nullptr, &len)) { ec = error::signature_generation_error::signfinal_failed; return {}; } std::string res(len, '\0'); if (!EVP_DigestSignFinal(ctx.get(), (unsigned char*)res.data(), &len)) { ec = error::signature_generation_error::signfinal_failed; return {}; } res.resize(len); return der_to_p1363_signature(res, ec); } /** * Check if signature is valid * \param data The data to check signature against * \param signature Signature provided by the jwt * \param ec Filled with details on error */ void verify(const std::string& data, const std::string& signature, std::error_code& ec) const { ec.clear(); std::string der_signature = p1363_to_der_signature(signature, ec); if (ec) { return; } auto ctx = helper::make_evp_md_ctx(); if (!ctx) { ec = error::signature_verification_error::create_context_failed; return; } if (!EVP_DigestVerifyInit(ctx.get(), nullptr, md(), nullptr, pkey.get())) { ec = error::signature_verification_error::verifyinit_failed; return; } if (!EVP_DigestUpdate(ctx.get(), data.data(), data.size())) { ec = error::signature_verification_error::verifyupdate_failed; return; } #if OPENSSL_VERSION_NUMBER < 0x10002000L unsigned char* der_sig_data = reinterpret_cast(const_cast(der_signature.data())); #else const unsigned char* der_sig_data = reinterpret_cast(der_signature.data()); #endif auto res = EVP_DigestVerifyFinal(ctx.get(), der_sig_data, static_cast(der_signature.length())); if (res == 0) { ec = error::signature_verification_error::invalid_signature; return; } if (res == -1) { ec = error::signature_verification_error::verifyfinal_failed; return; } } /** * Returns the algorithm name provided to the constructor * \return algorithm's name */ std::string name() const { return alg_name; } private: static void check_public_key(EVP_PKEY* pkey) { #ifdef JWT_OPENSSL_3_0 std::unique_ptr ctx( EVP_PKEY_CTX_new_from_pkey(nullptr, pkey, nullptr), EVP_PKEY_CTX_free); if (!ctx) { throw error::ecdsa_exception(error::ecdsa_error::create_context_failed); } if (EVP_PKEY_public_check(ctx.get()) != 1) { throw error::ecdsa_exception(error::ecdsa_error::invalid_key); } #else std::unique_ptr eckey(EVP_PKEY_get1_EC_KEY(pkey), EC_KEY_free); if (!eckey) { throw error::ecdsa_exception(error::ecdsa_error::invalid_key); } if (EC_KEY_check_key(eckey.get()) == 0) throw error::ecdsa_exception(error::ecdsa_error::invalid_key); #endif } static void check_private_key(EVP_PKEY* pkey) { #ifdef JWT_OPENSSL_3_0 std::unique_ptr ctx( EVP_PKEY_CTX_new_from_pkey(nullptr, pkey, nullptr), EVP_PKEY_CTX_free); if (!ctx) { throw error::ecdsa_exception(error::ecdsa_error::create_context_failed); } if (EVP_PKEY_private_check(ctx.get()) != 1) { throw error::ecdsa_exception(error::ecdsa_error::invalid_key); } #else std::unique_ptr eckey(EVP_PKEY_get1_EC_KEY(pkey), EC_KEY_free); if (!eckey) { throw error::ecdsa_exception(error::ecdsa_error::invalid_key); } if (EC_KEY_check_key(eckey.get()) == 0) throw error::ecdsa_exception(error::ecdsa_error::invalid_key); #endif } std::string der_to_p1363_signature(const std::string& der_signature, std::error_code& ec) const { const unsigned char* possl_signature = reinterpret_cast(der_signature.data()); std::unique_ptr sig( d2i_ECDSA_SIG(nullptr, &possl_signature, static_cast(der_signature.length())), ECDSA_SIG_free); if (!sig) { ec = error::signature_generation_error::signature_decoding_failed; return {}; } #ifdef JWT_OPENSSL_1_0_0 auto rr = helper::bn2raw(sig->r); auto rs = helper::bn2raw(sig->s); #else const BIGNUM* r; const BIGNUM* s; ECDSA_SIG_get0(sig.get(), &r, &s); auto rr = helper::bn2raw(r); auto rs = helper::bn2raw(s); #endif if (rr.size() > signature_length / 2 || rs.size() > signature_length / 2) throw std::logic_error("bignum size exceeded expected length"); rr.insert(0, signature_length / 2 - rr.size(), '\0'); rs.insert(0, signature_length / 2 - rs.size(), '\0'); return rr + rs; } std::string p1363_to_der_signature(const std::string& signature, std::error_code& ec) const { ec.clear(); auto r = helper::raw2bn(signature.substr(0, signature.size() / 2), ec); if (ec) return {}; auto s = helper::raw2bn(signature.substr(signature.size() / 2), ec); if (ec) return {}; ECDSA_SIG* psig; #ifdef JWT_OPENSSL_1_0_0 ECDSA_SIG sig; sig.r = r.get(); sig.s = s.get(); psig = &sig; #else std::unique_ptr sig(ECDSA_SIG_new(), ECDSA_SIG_free); if (!sig) { ec = error::signature_verification_error::create_context_failed; return {}; } ECDSA_SIG_set0(sig.get(), r.release(), s.release()); psig = sig.get(); #endif int length = i2d_ECDSA_SIG(psig, nullptr); if (length < 0) { ec = error::signature_verification_error::signature_encoding_failed; return {}; } std::string der_signature(length, '\0'); unsigned char* psbuffer = (unsigned char*)der_signature.data(); length = i2d_ECDSA_SIG(psig, &psbuffer); if (length < 0) { ec = error::signature_verification_error::signature_encoding_failed; return {}; } der_signature.resize(length); return der_signature; } /// OpenSSL struct containing keys helper::evp_pkey_handle pkey; /// Hash generator function const EVP_MD* (*md)(); /// algorithm's name const std::string alg_name; /// Length of the resulting signature const size_t signature_length; }; #if !defined(JWT_OPENSSL_1_0_0) && !defined(JWT_OPENSSL_1_1_0) /** * \brief Base class for EdDSA family of algorithms * * https://tools.ietf.org/html/rfc8032 * * The EdDSA algorithms were introduced in [OpenSSL v1.1.1](https://www.openssl.org/news/openssl-1.1.1-notes.html), * so these algorithms are only available when building against this version or higher. */ struct eddsa { /** * Construct new eddsa algorithm * \param public_key EdDSA public key in PEM format * \param private_key EdDSA private key or empty string if not available. If empty, signing will always * fail. * \param public_key_password Password to decrypt public key pem. * \param private_key_password Password * to decrypt private key pem. * \param name Name of the algorithm */ eddsa(const std::string& public_key, const std::string& private_key, const std::string& public_key_password, const std::string& private_key_password, std::string name) : alg_name(std::move(name)) { if (!private_key.empty()) { pkey = helper::load_private_key_from_string(private_key, private_key_password); } else if (!public_key.empty()) { pkey = helper::load_public_key_from_string(public_key, public_key_password); } else throw error::ecdsa_exception(error::ecdsa_error::load_key_bio_read); } /** * Sign jwt data * \param data The data to sign * \param ec error_code filled with details on error * \return EdDSA signature for the given data */ std::string sign(const std::string& data, std::error_code& ec) const { ec.clear(); auto ctx = helper::make_evp_md_ctx(); if (!ctx) { ec = error::signature_generation_error::create_context_failed; return {}; } if (!EVP_DigestSignInit(ctx.get(), nullptr, nullptr, nullptr, pkey.get())) { ec = error::signature_generation_error::signinit_failed; return {}; } size_t len = EVP_PKEY_size(pkey.get()); std::string res(len, '\0'); // LibreSSL is the special kid in the block, as it does not support EVP_DigestSign. // OpenSSL on the otherhand does not support using EVP_DigestSignUpdate for eddsa, which is why we end up with this // mess. #if defined(LIBRESSL_VERSION_NUMBER) || defined(LIBWOLFSSL_VERSION_HEX) ERR_clear_error(); if (EVP_DigestSignUpdate(ctx.get(), reinterpret_cast(data.data()), data.size()) != 1) { std::cout << ERR_error_string(ERR_get_error(), NULL) << std::endl; ec = error::signature_generation_error::signupdate_failed; return {}; } if (EVP_DigestSignFinal(ctx.get(), reinterpret_cast(&res[0]), &len) != 1) { ec = error::signature_generation_error::signfinal_failed; return {}; } #else if (EVP_DigestSign(ctx.get(), reinterpret_cast(&res[0]), &len, reinterpret_cast(data.data()), data.size()) != 1) { ec = error::signature_generation_error::signfinal_failed; return {}; } #endif res.resize(len); return res; } /** * Check if signature is valid * \param data The data to check signature against * \param signature Signature provided by the jwt * \param ec Filled with details on error */ void verify(const std::string& data, const std::string& signature, std::error_code& ec) const { ec.clear(); auto ctx = helper::make_evp_md_ctx(); if (!ctx) { ec = error::signature_verification_error::create_context_failed; return; } if (!EVP_DigestVerifyInit(ctx.get(), nullptr, nullptr, nullptr, pkey.get())) { ec = error::signature_verification_error::verifyinit_failed; return; } // LibreSSL is the special kid in the block, as it does not support EVP_DigestVerify. // OpenSSL on the otherhand does not support using EVP_DigestVerifyUpdate for eddsa, which is why we end up with this // mess. #if defined(LIBRESSL_VERSION_NUMBER) || defined(LIBWOLFSSL_VERSION_HEX) if (EVP_DigestVerifyUpdate(ctx.get(), reinterpret_cast(data.data()), data.size()) != 1) { ec = error::signature_verification_error::verifyupdate_failed; return; } if (EVP_DigestVerifyFinal(ctx.get(), reinterpret_cast(signature.data()), signature.size()) != 1) { ec = error::signature_verification_error::verifyfinal_failed; return; } #else auto res = EVP_DigestVerify(ctx.get(), reinterpret_cast(signature.data()), signature.size(), reinterpret_cast(data.data()), data.size()); if (res != 1) { ec = error::signature_verification_error::verifyfinal_failed; return; } #endif } /** * Returns the algorithm name provided to the constructor * \return algorithm's name */ std::string name() const { return alg_name; } private: /// OpenSSL struct containing keys helper::evp_pkey_handle pkey; /// algorithm's name const std::string alg_name; }; #endif /** * \brief Base class for PSS-RSA family of algorithms */ struct pss { /** * Construct new pss algorithm * \param public_key RSA public key in PEM format * \param private_key RSA private key or empty string if not available. If empty, signing will always fail. * \param public_key_password Password to decrypt public key pem. * \param private_key_password Password to decrypt private key pem. * \param md Pointer to hash function * \param name Name of the algorithm */ pss(const std::string& public_key, const std::string& private_key, const std::string& public_key_password, const std::string& private_key_password, const EVP_MD* (*md)(), std::string name) : md(md), alg_name(std::move(name)) { if (!private_key.empty()) { pkey = helper::load_private_key_from_string(private_key, private_key_password); } else if (!public_key.empty()) { pkey = helper::load_public_key_from_string(public_key, public_key_password); } else throw error::rsa_exception(error::rsa_error::no_key_provided); } /** * Sign jwt data * \param data The data to sign * \param ec error_code filled with details on error * \return ECDSA signature for the given data */ std::string sign(const std::string& data, std::error_code& ec) const { ec.clear(); auto md_ctx = helper::make_evp_md_ctx(); if (!md_ctx) { ec = error::signature_generation_error::create_context_failed; return {}; } EVP_PKEY_CTX* ctx = nullptr; if (EVP_DigestSignInit(md_ctx.get(), &ctx, md(), nullptr, pkey.get()) != 1) { ec = error::signature_generation_error::signinit_failed; return {}; } if (EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_PKCS1_PSS_PADDING) <= 0) { ec = error::signature_generation_error::rsa_padding_failed; return {}; } // wolfSSL does not require EVP_PKEY_CTX_set_rsa_pss_saltlen. The default behavior // sets the salt length to the hash length. Unlike OpenSSL which exposes this functionality. #ifndef LIBWOLFSSL_VERSION_HEX if (EVP_PKEY_CTX_set_rsa_pss_saltlen(ctx, -1) <= 0) { ec = error::signature_generation_error::set_rsa_pss_saltlen_failed; return {}; } #endif if (EVP_DigestUpdate(md_ctx.get(), data.data(), data.size()) != 1) { ec = error::signature_generation_error::digestupdate_failed; return {}; } size_t size = EVP_PKEY_size(pkey.get()); std::string res(size, 0x00); if (EVP_DigestSignFinal( md_ctx.get(), (unsigned char*)res.data(), // NOLINT(google-readability-casting) requires `const_cast` &size) <= 0) { ec = error::signature_generation_error::signfinal_failed; return {}; } return res; } /** * Check if signature is valid * \param data The data to check signature against * \param signature Signature provided by the jwt * \param ec Filled with error details */ void verify(const std::string& data, const std::string& signature, std::error_code& ec) const { ec.clear(); auto md_ctx = helper::make_evp_md_ctx(); if (!md_ctx) { ec = error::signature_verification_error::create_context_failed; return; } EVP_PKEY_CTX* ctx = nullptr; if (EVP_DigestVerifyInit(md_ctx.get(), &ctx, md(), nullptr, pkey.get()) != 1) { ec = error::signature_verification_error::verifyinit_failed; return; } if (EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_PKCS1_PSS_PADDING) <= 0) { ec = error::signature_generation_error::rsa_padding_failed; return; } // wolfSSL does not require EVP_PKEY_CTX_set_rsa_pss_saltlen. The default behavior // sets the salt length to the hash length. Unlike OpenSSL which exposes this functionality. #ifndef LIBWOLFSSL_VERSION_HEX if (EVP_PKEY_CTX_set_rsa_pss_saltlen(ctx, -1) <= 0) { ec = error::signature_verification_error::set_rsa_pss_saltlen_failed; return; } #endif if (EVP_DigestUpdate(md_ctx.get(), data.data(), data.size()) != 1) { ec = error::signature_verification_error::verifyupdate_failed; return; } if (EVP_DigestVerifyFinal(md_ctx.get(), (unsigned char*)signature.data(), signature.size()) <= 0) { ec = error::signature_verification_error::verifyfinal_failed; return; } } /** * Returns the algorithm name provided to the constructor * \return algorithm's name */ std::string name() const { return alg_name; } private: /// OpenSSL structure containing keys helper::evp_pkey_handle pkey; /// Hash generator function const EVP_MD* (*md)(); /// algorithm's name const std::string alg_name; }; /** * HS256 algorithm */ struct hs256 : public hmacsha { /** * Construct new instance of algorithm * \param key HMAC signing key */ explicit hs256(std::string key) : hmacsha(std::move(key), EVP_sha256, "HS256") {} }; /** * HS384 algorithm */ struct hs384 : public hmacsha { /** * Construct new instance of algorithm * \param key HMAC signing key */ explicit hs384(std::string key) : hmacsha(std::move(key), EVP_sha384, "HS384") {} }; /** * HS512 algorithm */ struct hs512 : public hmacsha { /** * Construct new instance of algorithm * \param key HMAC signing key */ explicit hs512(std::string key) : hmacsha(std::move(key), EVP_sha512, "HS512") {} }; /** * RS256 algorithm. * * This data structure is used to describe the RSA256 and can be used to verify JWTs */ struct rs256 : public rsa { /** * \brief Construct new instance of algorithm * * \param public_key RSA public key in PEM format * \param private_key RSA private key or empty string if not available. If empty, signing will always fail. * \param public_key_password Password to decrypt public key pem. * \param private_key_password Password to decrypt private key pem. */ explicit rs256(const std::string& public_key, const std::string& private_key = "", const std::string& public_key_password = "", const std::string& private_key_password = "") : rsa(public_key, private_key, public_key_password, private_key_password, EVP_sha256, "RS256") {} }; /** * RS384 algorithm */ struct rs384 : public rsa { /** * Construct new instance of algorithm * \param public_key RSA public key in PEM format * \param private_key RSA private key or empty string if not available. If empty, signing will always fail. * \param public_key_password Password to decrypt public key pem. * \param private_key_password Password to decrypt private key pem. */ explicit rs384(const std::string& public_key, const std::string& private_key = "", const std::string& public_key_password = "", const std::string& private_key_password = "") : rsa(public_key, private_key, public_key_password, private_key_password, EVP_sha384, "RS384") {} }; /** * RS512 algorithm */ struct rs512 : public rsa { /** * Construct new instance of algorithm * \param public_key RSA public key in PEM format * \param private_key RSA private key or empty string if not available. If empty, signing will always fail. * \param public_key_password Password to decrypt public key pem. * \param private_key_password Password to decrypt private key pem. */ explicit rs512(const std::string& public_key, const std::string& private_key = "", const std::string& public_key_password = "", const std::string& private_key_password = "") : rsa(public_key, private_key, public_key_password, private_key_password, EVP_sha512, "RS512") {} }; /** * ES256 algorithm */ struct es256 : public ecdsa { /** * Construct new instance of algorithm * \param public_key ECDSA public key in PEM format * \param private_key ECDSA private key or empty string if not available. If empty, signing will always * fail. * \param public_key_password Password to decrypt public key pem. * \param private_key_password Password * to decrypt private key pem. */ explicit es256(const std::string& public_key, const std::string& private_key = "", const std::string& public_key_password = "", const std::string& private_key_password = "") : ecdsa(public_key, private_key, public_key_password, private_key_password, EVP_sha256, "ES256", 64) {} }; /** * ES384 algorithm */ struct es384 : public ecdsa { /** * Construct new instance of algorithm * \param public_key ECDSA public key in PEM format * \param private_key ECDSA private key or empty string if not available. If empty, signing will always * fail. * \param public_key_password Password to decrypt public key pem. * \param private_key_password Password * to decrypt private key pem. */ explicit es384(const std::string& public_key, const std::string& private_key = "", const std::string& public_key_password = "", const std::string& private_key_password = "") : ecdsa(public_key, private_key, public_key_password, private_key_password, EVP_sha384, "ES384", 96) {} }; /** * ES512 algorithm */ struct es512 : public ecdsa { /** * Construct new instance of algorithm * \param public_key ECDSA public key in PEM format * \param private_key ECDSA private key or empty string if not available. If empty, signing will always * fail. * \param public_key_password Password to decrypt public key pem. * \param private_key_password Password * to decrypt private key pem. */ explicit es512(const std::string& public_key, const std::string& private_key = "", const std::string& public_key_password = "", const std::string& private_key_password = "") : ecdsa(public_key, private_key, public_key_password, private_key_password, EVP_sha512, "ES512", 132) {} }; /** * ES256K algorithm */ struct es256k : public ecdsa { /** * Construct new instance of algorithm * \param public_key ECDSA public key in PEM format * \param private_key ECDSA private key or empty string if not available. If empty, signing will always * fail. * \param public_key_password Password to decrypt public key pem. * \param private_key_password Password to decrypt private key pem. */ explicit es256k(const std::string& public_key, const std::string& private_key = "", const std::string& public_key_password = "", const std::string& private_key_password = "") : ecdsa(public_key, private_key, public_key_password, private_key_password, EVP_sha256, "ES256K", 64) {} }; #if !defined(JWT_OPENSSL_1_0_0) && !defined(JWT_OPENSSL_1_1_0) /** * Ed25519 algorithm * * https://en.wikipedia.org/wiki/EdDSA#Ed25519 * * Requires at least OpenSSL 1.1.1. */ struct ed25519 : public eddsa { /** * Construct new instance of algorithm * \param public_key Ed25519 public key in PEM format * \param private_key Ed25519 private key or empty string if not available. If empty, signing will always * fail. * \param public_key_password Password to decrypt public key pem. * \param private_key_password Password * to decrypt private key pem. */ explicit ed25519(const std::string& public_key, const std::string& private_key = "", const std::string& public_key_password = "", const std::string& private_key_password = "") : eddsa(public_key, private_key, public_key_password, private_key_password, "EdDSA") {} }; /** * Ed448 algorithm * * https://en.wikipedia.org/wiki/EdDSA#Ed448 * * Requires at least OpenSSL 1.1.1. */ struct ed448 : public eddsa { /** * Construct new instance of algorithm * \param public_key Ed448 public key in PEM format * \param private_key Ed448 private key or empty string if not available. If empty, signing will always * fail. * \param public_key_password Password to decrypt public key pem. * \param private_key_password Password * to decrypt private key pem. */ explicit ed448(const std::string& public_key, const std::string& private_key = "", const std::string& public_key_password = "", const std::string& private_key_password = "") : eddsa(public_key, private_key, public_key_password, private_key_password, "EdDSA") {} }; #endif /** * PS256 algorithm */ struct ps256 : public pss { /** * Construct new instance of algorithm * \param public_key RSA public key in PEM format * \param private_key RSA private key or empty string if not available. If empty, signing will always fail. * \param public_key_password Password to decrypt public key pem. * \param private_key_password Password to decrypt private key pem. */ explicit ps256(const std::string& public_key, const std::string& private_key = "", const std::string& public_key_password = "", const std::string& private_key_password = "") : pss(public_key, private_key, public_key_password, private_key_password, EVP_sha256, "PS256") {} }; /** * PS384 algorithm */ struct ps384 : public pss { /** * Construct new instance of algorithm * \param public_key RSA public key in PEM format * \param private_key RSA private key or empty string if not available. If empty, signing will always fail. * \param public_key_password Password to decrypt public key pem. * \param private_key_password Password to decrypt private key pem. */ explicit ps384(const std::string& public_key, const std::string& private_key = "", const std::string& public_key_password = "", const std::string& private_key_password = "") : pss(public_key, private_key, public_key_password, private_key_password, EVP_sha384, "PS384") {} }; /** * PS512 algorithm */ struct ps512 : public pss { /** * Construct new instance of algorithm * \param public_key RSA public key in PEM format * \param private_key RSA private key or empty string if not available. If empty, signing will always fail. * \param public_key_password Password to decrypt public key pem. * \param private_key_password Password to decrypt private key pem. */ explicit ps512(const std::string& public_key, const std::string& private_key = "", const std::string& public_key_password = "", const std::string& private_key_password = "") : pss(public_key, private_key, public_key_password, private_key_password, EVP_sha512, "PS512") {} }; } // namespace algorithm /** * \brief JSON Abstractions for working with any library */ namespace json { /** * \brief Categories for the various JSON types used in JWTs * * This enum is to abstract the third party underlying types and allows the library * to identify the different structures and reason about them without needing a "concept" * to capture that defintion to compare against a concrete type. */ enum class type { boolean, integer, number, string, array, object }; } // namespace json namespace details { #ifdef __cpp_lib_void_t template using void_t = std::void_t; #else // https://en.cppreference.com/w/cpp/types/void_t template struct make_void { using type = void; }; template using void_t = typename make_void::type; #endif #ifdef __cpp_lib_experimental_detect template class _Op, typename... _Args> using is_detected = std::experimental::is_detected<_Op, _Args...>; #else struct nonesuch { nonesuch() = delete; ~nonesuch() = delete; nonesuch(nonesuch const&) = delete; nonesuch(nonesuch const&&) = delete; void operator=(nonesuch const&) = delete; void operator=(nonesuch&&) = delete; }; // https://en.cppreference.com/w/cpp/experimental/is_detected template class Op, class... Args> struct detector { using value = std::false_type; using type = Default; }; template class Op, class... Args> struct detector>, Op, Args...> { using value = std::true_type; using type = Op; }; template class Op, class... Args> using is_detected = typename detector::value; #endif template using is_signature = typename std::is_same; template class Op, typename Signature> struct is_function_signature_detected { using type = Op; static constexpr auto value = is_detected::value && std::is_function::value && is_signature::value; }; template struct supports_get_type { template using get_type_t = decltype(T::get_type); static constexpr auto value = is_function_signature_detected::value; // Internal assertions for better feedback static_assert(value, "traits implementation must provide `jwt::json::type get_type(const value_type&)`"); }; #define JWT_CPP_JSON_TYPE_TYPE(TYPE) json_##TYPE_type #define JWT_CPP_AS_TYPE_T(TYPE) as_##TYPE_t #define JWT_CPP_SUPPORTS_AS(TYPE) \ template \ struct supports_as_##TYPE { \ template \ using JWT_CPP_AS_TYPE_T(TYPE) = decltype(T::as_##TYPE); \ \ static constexpr auto value = \ is_function_signature_detected::value; \ \ static_assert(value, "traits implementation must provide `" #TYPE "_type as_" #TYPE "(const value_type&)`"); \ } JWT_CPP_SUPPORTS_AS(object); JWT_CPP_SUPPORTS_AS(array); JWT_CPP_SUPPORTS_AS(string); JWT_CPP_SUPPORTS_AS(number); JWT_CPP_SUPPORTS_AS(integer); JWT_CPP_SUPPORTS_AS(boolean); #undef JWT_CPP_JSON_TYPE_TYPE #undef JWT_CPP_AS_TYPE_T #undef JWT_CPP_SUPPORTS_AS template struct is_valid_traits { static constexpr auto value = supports_get_type::value && supports_as_object::value && supports_as_array::value && supports_as_string::value && supports_as_number::value && supports_as_integer::value && supports_as_boolean::value; }; template struct is_valid_json_value { static constexpr auto value = std::is_default_constructible::value && std::is_constructible::value && // a more generic is_copy_constructible std::is_move_constructible::value && std::is_assignable::value && std::is_copy_assignable::value && std::is_move_assignable::value; // TODO(prince-chrismc): Stream operators }; // https://stackoverflow.com/a/53967057/8480874 template struct is_iterable : std::false_type {}; template struct is_iterable())), decltype(std::end(std::declval())), #if __cplusplus > 201402L decltype(std::cbegin(std::declval())), decltype(std::cend(std::declval())) #else decltype(std::begin(std::declval())), decltype(std::end(std::declval())) #endif >> : std::true_type { }; #if __cplusplus > 201703L template inline constexpr bool is_iterable_v = is_iterable::value; #endif template using is_count_signature = typename std::is_integral().count( std::declval()))>; template struct is_subcription_operator_signature : std::false_type {}; template struct is_subcription_operator_signature< object_type, string_type, void_t().operator[](std::declval()))>> : std::true_type { // TODO(prince-chrismc): I am not convienced this is meaningful anymore static_assert( value, "object_type must implementate the subscription operator '[]' taking string_type as an argument"); }; template using is_at_const_signature = typename std::is_same().at(std::declval())), const value_type&>; template struct is_valid_json_object { template using mapped_type_t = typename T::mapped_type; template using key_type_t = typename T::key_type; template using iterator_t = typename T::iterator; template using const_iterator_t = typename T::const_iterator; static constexpr auto value = std::is_constructible::value && is_detected::value && std::is_same::value && is_detected::value && (std::is_same::value || std::is_constructible::value) && is_detected::value && is_detected::value && is_iterable::value && is_count_signature::value && is_subcription_operator_signature::value && is_at_const_signature::value; }; template struct is_valid_json_array { template using value_type_t = typename T::value_type; using front_base_type = typename std::decay().front())>::type; static constexpr auto value = std::is_constructible::value && is_iterable::value && is_detected::value && std::is_same::value && std::is_same::value; }; template using is_substr_start_end_index_signature = typename std::is_same().substr(std::declval(), std::declval())), string_type>; template using is_substr_start_index_signature = typename std::is_same().substr(std::declval())), string_type>; template using is_std_operate_plus_signature = typename std::is_same(), std::declval())), string_type>; template struct is_valid_json_string { static constexpr auto substr = is_substr_start_end_index_signature::value && is_substr_start_index_signature::value; static_assert(substr, "string_type must have a substr method taking only a start index and an overload " "taking a start and end index, both must return a string_type"); static constexpr auto operator_plus = is_std_operate_plus_signature::value; static_assert(operator_plus, "string_type must have a '+' operator implemented which returns the concatenated string"); static constexpr auto value = std::is_constructible::value && substr && operator_plus; }; template struct is_valid_json_number { static constexpr auto value = std::is_floating_point::value && std::is_constructible::value; }; template struct is_valid_json_integer { static constexpr auto value = std::is_signed::value && !std::is_floating_point::value && std::is_constructible::value; }; template struct is_valid_json_boolean { static constexpr auto value = std::is_convertible::value && std::is_constructible::value; }; template struct is_valid_json_types { // Internal assertions for better feedback static_assert(is_valid_json_value::value, "value_type must meet basic requirements, default constructor, copyable, moveable"); static_assert(is_valid_json_object::value, "object_type must be a string_type to value_type container"); static_assert(is_valid_json_array::value, "array_type must be a container of value_type"); static constexpr auto value = is_valid_json_value::value && is_valid_json_object::value && is_valid_json_array::value && is_valid_json_string::value && is_valid_json_number::value && is_valid_json_integer::value && is_valid_json_boolean::value; }; } // namespace details /** * \brief a class to store a generic JSON value as claim * * \tparam json_traits : JSON implementation traits * * \see [RFC 7519: JSON Web Token (JWT)](https://tools.ietf.org/html/rfc7519) */ template class basic_claim { /** * The reason behind this is to provide an expressive abstraction without * over complicating the API. For more information take the time to read * https://github.com/nlohmann/json/issues/774. It maybe be expanded to * support custom string types. */ static_assert(std::is_same::value || std::is_convertible::value || std::is_constructible::value, "string_type must be a std::string, convertible to a std::string, or construct a std::string."); static_assert( details::is_valid_json_types::value, "must satisfy json container requirements"); static_assert(details::is_valid_traits::value, "traits must satisfy requirements"); typename json_traits::value_type val; public: /** * Order list of strings */ using set_t = std::set; basic_claim() = default; basic_claim(const basic_claim&) = default; basic_claim(basic_claim&&) = default; basic_claim& operator=(const basic_claim&) = default; basic_claim& operator=(basic_claim&&) = default; ~basic_claim() = default; JWT_CLAIM_EXPLICIT basic_claim(typename json_traits::string_type s) : val(std::move(s)) {} JWT_CLAIM_EXPLICIT basic_claim(const date& d) : val(typename json_traits::integer_type(std::chrono::system_clock::to_time_t(d))) {} JWT_CLAIM_EXPLICIT basic_claim(typename json_traits::array_type a) : val(std::move(a)) {} JWT_CLAIM_EXPLICIT basic_claim(typename json_traits::value_type v) : val(std::move(v)) {} JWT_CLAIM_EXPLICIT basic_claim(const set_t& s) : val(typename json_traits::array_type(s.begin(), s.end())) {} template basic_claim(Iterator begin, Iterator end) : val(typename json_traits::array_type(begin, end)) {} /** * Get wrapped JSON value * \return Wrapped JSON value */ typename json_traits::value_type to_json() const { return val; } /** * Parse input stream into underlying JSON value * \return input stream */ std::istream& operator>>(std::istream& is) { return is >> val; } /** * Serialize claim to output stream from wrapped JSON value * \return output stream */ std::ostream& operator<<(std::ostream& os) { return os << val; } /** * Get type of contained JSON value * \return Type * \throw std::logic_error An internal error occurred */ json::type get_type() const { return json_traits::get_type(val); } /** * Get the contained JSON value as a string * \return content as string * \throw std::bad_cast Content was not a string */ typename json_traits::string_type as_string() const { return json_traits::as_string(val); } /** * \brief Get the contained JSON value as a date * * If the value is a decimal, it is rounded up to the closest integer * * \return content as date * \throw std::bad_cast Content was not a date */ date as_date() const { using std::chrono::system_clock; if (get_type() == json::type::number) return system_clock::from_time_t(std::round(as_number())); return system_clock::from_time_t(as_integer()); } /** * Get the contained JSON value as an array * \return content as array * \throw std::bad_cast Content was not an array */ typename json_traits::array_type as_array() const { return json_traits::as_array(val); } /** * Get the contained JSON value as a set of strings * \return content as set of strings * \throw std::bad_cast Content was not an array of string */ set_t as_set() const { set_t res; for (const auto& e : json_traits::as_array(val)) { res.insert(json_traits::as_string(e)); } return res; } /** * Get the contained JSON value as an integer * \return content as int * \throw std::bad_cast Content was not an int */ typename json_traits::integer_type as_integer() const { return json_traits::as_integer(val); } /** * Get the contained JSON value as a bool * \return content as bool * \throw std::bad_cast Content was not a bool */ typename json_traits::boolean_type as_boolean() const { return json_traits::as_boolean(val); } /** * Get the contained JSON value as a number * \return content as double * \throw std::bad_cast Content was not a number */ typename json_traits::number_type as_number() const { return json_traits::as_number(val); } }; namespace error { /** * Attempt to parse JSON was unsuccessful */ struct invalid_json_exception : public std::runtime_error { invalid_json_exception() : runtime_error("invalid json") {} }; /** * Attempt to access claim was unsuccessful */ struct claim_not_present_exception : public std::out_of_range { claim_not_present_exception() : out_of_range("claim not found") {} }; } // namespace error namespace details { template struct map_of_claims { typename json_traits::object_type claims; using basic_claim_t = basic_claim; using iterator = typename json_traits::object_type::iterator; using const_iterator = typename json_traits::object_type::const_iterator; map_of_claims() = default; map_of_claims(const map_of_claims&) = default; map_of_claims(map_of_claims&&) = default; map_of_claims& operator=(const map_of_claims&) = default; map_of_claims& operator=(map_of_claims&&) = default; map_of_claims(typename json_traits::object_type json) : claims(std::move(json)) {} iterator begin() { return claims.begin(); } iterator end() { return claims.end(); } const_iterator cbegin() const { return claims.begin(); } const_iterator cend() const { return claims.end(); } const_iterator begin() const { return claims.begin(); } const_iterator end() const { return claims.end(); } /** * \brief Parse a JSON string into a map of claims * * The implication is that a "map of claims" is identic to a JSON object * * \param str JSON data to be parse as an object * \return content as JSON object */ static typename json_traits::object_type parse_claims(const typename json_traits::string_type& str) { typename json_traits::value_type val; if (!json_traits::parse(val, str)) throw error::invalid_json_exception(); return json_traits::as_object(val); }; /** * Check if a claim is present in the map * \return true if claim was present, false otherwise */ bool has_claim(const typename json_traits::string_type& name) const noexcept { return claims.count(name) != 0; } /** * Get a claim by name * * \param name the name of the desired claim * \return Requested claim * \throw jwt::error::claim_not_present_exception if the claim was not present */ basic_claim_t get_claim(const typename json_traits::string_type& name) const { if (!has_claim(name)) throw error::claim_not_present_exception(); return basic_claim_t{claims.at(name)}; } }; } // namespace details /** * Base class that represents a token payload. * Contains Convenience accessors for common claims. */ template class payload { protected: details::map_of_claims payload_claims; public: using basic_claim_t = basic_claim; /** * Check if issuer is present ("iss") * \return true if present, false otherwise */ bool has_issuer() const noexcept { return has_payload_claim("iss"); } /** * Check if subject is present ("sub") * \return true if present, false otherwise */ bool has_subject() const noexcept { return has_payload_claim("sub"); } /** * Check if audience is present ("aud") * \return true if present, false otherwise */ bool has_audience() const noexcept { return has_payload_claim("aud"); } /** * Check if expires is present ("exp") * \return true if present, false otherwise */ bool has_expires_at() const noexcept { return has_payload_claim("exp"); } /** * Check if not before is present ("nbf") * \return true if present, false otherwise */ bool has_not_before() const noexcept { return has_payload_claim("nbf"); } /** * Check if issued at is present ("iat") * \return true if present, false otherwise */ bool has_issued_at() const noexcept { return has_payload_claim("iat"); } /** * Check if token id is present ("jti") * \return true if present, false otherwise */ bool has_id() const noexcept { return has_payload_claim("jti"); } /** * Get issuer claim * \return issuer as string * \throw std::runtime_error If claim was not present * \throw std::bad_cast Claim was present but not a string (Should not happen in a valid token) */ typename json_traits::string_type get_issuer() const { return get_payload_claim("iss").as_string(); } /** * Get subject claim * \return subject as string * \throw std::runtime_error If claim was not present * \throw std::bad_cast Claim was present but not a string (Should not happen in a valid token) */ typename json_traits::string_type get_subject() const { return get_payload_claim("sub").as_string(); } /** * Get audience claim * \return audience as a set of strings * \throw std::runtime_error If claim was not present * \throw std::bad_cast Claim was present but not a set (Should not happen in a valid token) */ typename basic_claim_t::set_t get_audience() const { auto aud = get_payload_claim("aud"); if (aud.get_type() == json::type::string) return {aud.as_string()}; return aud.as_set(); } /** * Get expires claim * \return expires as a date in utc * \throw std::runtime_error If claim was not present * \throw std::bad_cast Claim was present but not a date (Should not happen in a valid token) */ date get_expires_at() const { return get_payload_claim("exp").as_date(); } /** * Get not valid before claim * \return nbf date in utc * \throw std::runtime_error If claim was not present * \throw std::bad_cast Claim was present but not a date (Should not happen in a valid token) */ date get_not_before() const { return get_payload_claim("nbf").as_date(); } /** * Get issued at claim * \return issued at as date in utc * \throw std::runtime_error If claim was not present * \throw std::bad_cast Claim was present but not a date (Should not happen in a valid token) */ date get_issued_at() const { return get_payload_claim("iat").as_date(); } /** * Get id claim * \return id as string * \throw std::runtime_error If claim was not present * \throw std::bad_cast Claim was present but not a string (Should not happen in a valid token) */ typename json_traits::string_type get_id() const { return get_payload_claim("jti").as_string(); } /** * Check if a payload claim is present * \return true if claim was present, false otherwise */ bool has_payload_claim(const typename json_traits::string_type& name) const noexcept { return payload_claims.has_claim(name); } /** * Get payload claim * \return Requested claim * \throw std::runtime_error If claim was not present */ basic_claim_t get_payload_claim(const typename json_traits::string_type& name) const { return payload_claims.get_claim(name); } }; /** * Base class that represents a token header. * Contains Convenience accessors for common claims. */ template class header { protected: details::map_of_claims header_claims; public: using basic_claim_t = basic_claim; /** * Check if algorithm is present ("alg") * \return true if present, false otherwise */ bool has_algorithm() const noexcept { return has_header_claim("alg"); } /** * Check if type is present ("typ") * \return true if present, false otherwise */ bool has_type() const noexcept { return has_header_claim("typ"); } /** * Check if content type is present ("cty") * \return true if present, false otherwise */ bool has_content_type() const noexcept { return has_header_claim("cty"); } /** * Check if key id is present ("kid") * \return true if present, false otherwise */ bool has_key_id() const noexcept { return has_header_claim("kid"); } /** * Get algorithm claim * \return algorithm as string * \throw std::runtime_error If claim was not present * \throw std::bad_cast Claim was present but not a string (Should not happen in a valid token) */ typename json_traits::string_type get_algorithm() const { return get_header_claim("alg").as_string(); } /** * Get type claim * \return type as a string * \throw std::runtime_error If claim was not present * \throw std::bad_cast Claim was present but not a string (Should not happen in a valid token) */ typename json_traits::string_type get_type() const { return get_header_claim("typ").as_string(); } /** * Get content type claim * \return content type as string * \throw std::runtime_error If claim was not present * \throw std::bad_cast Claim was present but not a string (Should not happen in a valid token) */ typename json_traits::string_type get_content_type() const { return get_header_claim("cty").as_string(); } /** * Get key id claim * \return key id as string * \throw std::runtime_error If claim was not present * \throw std::bad_cast Claim was present but not a string (Should not happen in a valid token) */ typename json_traits::string_type get_key_id() const { return get_header_claim("kid").as_string(); } /** * Check if a header claim is present * \return true if claim was present, false otherwise */ bool has_header_claim(const typename json_traits::string_type& name) const noexcept { return header_claims.has_claim(name); } /** * Get header claim * \return Requested claim * \throw std::runtime_error If claim was not present */ basic_claim_t get_header_claim(const typename json_traits::string_type& name) const { return header_claims.get_claim(name); } }; /** * Class containing all information about a decoded token */ template class decoded_jwt : public header, public payload { protected: /// Unmodified token, as passed to constructor typename json_traits::string_type token; /// Header part decoded from base64 typename json_traits::string_type header; /// Unmodified header part in base64 typename json_traits::string_type header_base64; /// Payload part decoded from base64 typename json_traits::string_type payload; /// Unmodified payload part in base64 typename json_traits::string_type payload_base64; /// Signature part decoded from base64 typename json_traits::string_type signature; /// Unmodified signature part in base64 typename json_traits::string_type signature_base64; public: using basic_claim_t = basic_claim; #ifndef JWT_DISABLE_BASE64 /** * \brief Parses a given token * * \note Decodes using the jwt::base64url which supports an std::string * * \param token The token to parse * \throw std::invalid_argument Token is not in correct format * \throw std::runtime_error Base64 decoding failed or invalid json */ JWT_CLAIM_EXPLICIT decoded_jwt(const typename json_traits::string_type& token) : decoded_jwt(token, [](const typename json_traits::string_type& str) { return base::decode(base::pad(str)); }) {} #endif /** * \brief Parses a given token * * \tparam Decode is callable, taking a string_type and returns a string_type. * It should ensure the padding of the input and then base64url decode and * return the results. * \param token The token to parse * \param decode The function to decode the token * \throw std::invalid_argument Token is not in correct format * \throw std::runtime_error Base64 decoding failed or invalid json */ template decoded_jwt(const typename json_traits::string_type& token, Decode decode) : token(token) { auto hdr_end = token.find('.'); if (hdr_end == json_traits::string_type::npos) throw std::invalid_argument("invalid token supplied"); auto payload_end = token.find('.', hdr_end + 1); if (payload_end == json_traits::string_type::npos) throw std::invalid_argument("invalid token supplied"); header_base64 = token.substr(0, hdr_end); payload_base64 = token.substr(hdr_end + 1, payload_end - hdr_end - 1); signature_base64 = token.substr(payload_end + 1); header = decode(header_base64); payload = decode(payload_base64); signature = decode(signature_base64); this->header_claims = details::map_of_claims::parse_claims(header); this->payload_claims = details::map_of_claims::parse_claims(payload); } /** * Get token string, as passed to constructor * \return token as passed to constructor */ const typename json_traits::string_type& get_token() const noexcept { return token; } /** * Get header part as json string * \return header part after base64 decoding */ const typename json_traits::string_type& get_header() const noexcept { return header; } /** * Get payload part as json string * \return payload part after base64 decoding */ const typename json_traits::string_type& get_payload() const noexcept { return payload; } /** * Get signature part as json string * \return signature part after base64 decoding */ const typename json_traits::string_type& get_signature() const noexcept { return signature; } /** * Get header part as base64 string * \return header part before base64 decoding */ const typename json_traits::string_type& get_header_base64() const noexcept { return header_base64; } /** * Get payload part as base64 string * \return payload part before base64 decoding */ const typename json_traits::string_type& get_payload_base64() const noexcept { return payload_base64; } /** * Get signature part as base64 string * \return signature part before base64 decoding */ const typename json_traits::string_type& get_signature_base64() const noexcept { return signature_base64; } /** * Get all payload as JSON object * \return map of claims */ typename json_traits::object_type get_payload_json() const { return this->payload_claims.claims; } /** * Get all header as JSON object * \return map of claims */ typename json_traits::object_type get_header_json() const { return this->header_claims.claims; } /** * Get a payload claim by name * * \param name the name of the desired claim * \return Requested claim * \throw jwt::error::claim_not_present_exception if the claim was not present */ basic_claim_t get_payload_claim(const typename json_traits::string_type& name) const { return this->payload_claims.get_claim(name); } /** * Get a header claim by name * * \param name the name of the desired claim * \return Requested claim * \throw jwt::error::claim_not_present_exception if the claim was not present */ basic_claim_t get_header_claim(const typename json_traits::string_type& name) const { return this->header_claims.get_claim(name); } }; /** * Builder class to build and sign a new token * Use jwt::create() to get an instance of this class. */ template class builder { typename json_traits::object_type header_claims; typename json_traits::object_type payload_claims; /// Instance of clock type Clock clock; public: /** * Constructor for building a new builder instance * \param c Clock instance */ JWT_CLAIM_EXPLICIT builder(Clock c) : clock(c) {} /** * Set a header claim. * \param id Name of the claim * \param c Claim to add * \return *this to allow for method chaining */ builder& set_header_claim(const typename json_traits::string_type& id, typename json_traits::value_type c) { header_claims[id] = std::move(c); return *this; } /** * Set a header claim. * \param id Name of the claim * \param c Claim to add * \return *this to allow for method chaining */ builder& set_header_claim(const typename json_traits::string_type& id, basic_claim c) { header_claims[id] = c.to_json(); return *this; } /** * Set a payload claim. * \param id Name of the claim * \param c Claim to add * \return *this to allow for method chaining */ builder& set_payload_claim(const typename json_traits::string_type& id, typename json_traits::value_type c) { payload_claims[id] = std::move(c); return *this; } /** * Set a payload claim. * \param id Name of the claim * \param c Claim to add * \return *this to allow for method chaining */ builder& set_payload_claim(const typename json_traits::string_type& id, basic_claim c) { payload_claims[id] = c.to_json(); return *this; } /** * \brief Set algorithm claim * You normally don't need to do this, as the algorithm is automatically set if you don't change it. * * \param str Name of algorithm * \return *this to allow for method chaining */ builder& set_algorithm(typename json_traits::string_type str) { return set_header_claim("alg", typename json_traits::value_type(str)); } /** * Set type claim * \param str Type to set * \return *this to allow for method chaining */ builder& set_type(typename json_traits::string_type str) { return set_header_claim("typ", typename json_traits::value_type(str)); } /** * Set content type claim * \param str Type to set * \return *this to allow for method chaining */ builder& set_content_type(typename json_traits::string_type str) { return set_header_claim("cty", typename json_traits::value_type(str)); } /** * \brief Set key id claim * * \param str Key id to set * \return *this to allow for method chaining */ builder& set_key_id(typename json_traits::string_type str) { return set_header_claim("kid", typename json_traits::value_type(str)); } /** * Set issuer claim * \param str Issuer to set * \return *this to allow for method chaining */ builder& set_issuer(typename json_traits::string_type str) { return set_payload_claim("iss", typename json_traits::value_type(str)); } /** * Set subject claim * \param str Subject to set * \return *this to allow for method chaining */ builder& set_subject(typename json_traits::string_type str) { return set_payload_claim("sub", typename json_traits::value_type(str)); } /** * Set audience claim * \param a Audience set * \return *this to allow for method chaining */ builder& set_audience(typename json_traits::array_type a) { return set_payload_claim("aud", typename json_traits::value_type(a)); } /** * Set audience claim * \param aud Single audience * \return *this to allow for method chaining */ builder& set_audience(typename json_traits::string_type aud) { return set_payload_claim("aud", typename json_traits::value_type(aud)); } /** * Set expires at claim * \param d Expires time * \return *this to allow for method chaining */ builder& set_expires_at(const date& d) { return set_payload_claim("exp", basic_claim(d)); } /** * Set expires at claim to @p d from the current moment * \param d token expiration timeout * \return *this to allow for method chaining */ template builder& set_expires_in(const std::chrono::duration& d) { return set_payload_claim("exp", basic_claim(clock.now() + d)); } /** * Set not before claim * \param d First valid time * \return *this to allow for method chaining */ builder& set_not_before(const date& d) { return set_payload_claim("nbf", basic_claim(d)); } /** * Set issued at claim * \param d Issued at time, should be current time * \return *this to allow for method chaining */ builder& set_issued_at(const date& d) { return set_payload_claim("iat", basic_claim(d)); } /** * Set issued at claim to the current moment * \return *this to allow for method chaining */ builder& set_issued_now() { return set_issued_at(clock.now()); } /** * Set id claim * \param str ID to set * \return *this to allow for method chaining */ builder& set_id(const typename json_traits::string_type& str) { return set_payload_claim("jti", typename json_traits::value_type(str)); } /** * Sign token and return result * \tparam Algo Callable method which takes a string_type and return the signed input as a string_type * \tparam Encode Callable method which takes a string_type and base64url safe encodes it, * MUST return the result with no padding; trim the result. * \param algo Instance of an algorithm to sign the token with * \param encode Callable to transform the serialized json to base64 with no padding * \return Final token as a string * * \note If the 'alg' header in not set in the token it will be set to `algo.name()` */ template typename json_traits::string_type sign(const Algo& algo, Encode encode) const { std::error_code ec; auto res = sign(algo, encode, ec); error::throw_if_error(ec); return res; } #ifndef JWT_DISABLE_BASE64 /** * Sign token and return result * * using the `jwt::base` functions provided * * \param algo Instance of an algorithm to sign the token with * \return Final token as a string */ template typename json_traits::string_type sign(const Algo& algo) const { std::error_code ec; auto res = sign(algo, ec); error::throw_if_error(ec); return res; } #endif /** * Sign token and return result * \tparam Algo Callable method which takes a string_type and return the signed input as a string_type * \tparam Encode Callable method which takes a string_type and base64url safe encodes it, * MUST return the result with no padding; trim the result. * \param algo Instance of an algorithm to sign the token with * \param encode Callable to transform the serialized json to base64 with no padding * \param ec error_code filled with details on error * \return Final token as a string * * \note If the 'alg' header in not set in the token it will be set to `algo.name()` */ template typename json_traits::string_type sign(const Algo& algo, Encode encode, std::error_code& ec) const { // make a copy such that a builder can be re-used typename json_traits::object_type obj_header = header_claims; if (header_claims.count("alg") == 0) obj_header["alg"] = typename json_traits::value_type(algo.name()); const auto header = encode(json_traits::serialize(typename json_traits::value_type(obj_header))); const auto payload = encode(json_traits::serialize(typename json_traits::value_type(payload_claims))); const auto token = header + "." + payload; auto signature = algo.sign(token, ec); if (ec) return {}; return token + "." + encode(signature); } #ifndef JWT_DISABLE_BASE64 /** * Sign token and return result * * using the `jwt::base` functions provided * * \param algo Instance of an algorithm to sign the token with * \param ec error_code filled with details on error * \return Final token as a string */ template typename json_traits::string_type sign(const Algo& algo, std::error_code& ec) const { return sign( algo, [](const typename json_traits::string_type& data) { return base::trim(base::encode(data)); }, ec); } #endif }; namespace verify_ops { /** * This is the base container which holds the token that need to be verified */ template struct verify_context { verify_context(date ctime, const decoded_jwt& j, size_t l) : current_time(ctime), jwt(j), default_leeway(l) {} /// Current time, retrieved from the verifiers clock and cached for performance and consistency date current_time; /// The jwt passed to the verifier const decoded_jwt& jwt; /// The configured default leeway for this verification size_t default_leeway{0}; /// The claim key to apply this comparison on typename json_traits::string_type claim_key{}; /** * \brief Helper method to get a claim from the jwt in this context * \param in_header check JWT header or payload sections * \param ec std::error_code which will indicate if any error occure * \return basic_claim if it was present otherwise empty */ basic_claim get_claim(bool in_header, std::error_code& ec) const { if (in_header) { if (!jwt.has_header_claim(claim_key)) { ec = error::token_verification_error::missing_claim; return {}; } return jwt.get_header_claim(claim_key); } else { if (!jwt.has_payload_claim(claim_key)) { ec = error::token_verification_error::missing_claim; return {}; } return jwt.get_payload_claim(claim_key); } } /** * Helper method to get a claim of a specific type from the jwt in this context * \param in_header check JWT header or payload sections * \param t the expected type of the claim * \param ec std::error_code which will indicate if any error occure * \return basic_claim if it was present otherwise empty */ basic_claim get_claim(bool in_header, json::type t, std::error_code& ec) const { auto c = get_claim(in_header, ec); if (ec) return {}; if (c.get_type() != t) { ec = error::token_verification_error::claim_type_missmatch; return {}; } return c; } /** * \brief Helper method to get a payload claim from the jwt * \param ec std::error_code which will indicate if any error occure * \return basic_claim if it was present otherwise empty */ basic_claim get_claim(std::error_code& ec) const { return get_claim(false, ec); } /** * \brief Helper method to get a payload claim of a specific type from the jwt * \param t the expected type of the claim * \param ec std::error_code which will indicate if any error occure * \return basic_claim if it was present otherwise empty */ basic_claim get_claim(json::type t, std::error_code& ec) const { return get_claim(false, t, ec); } }; /** * This is the default operation and does case sensitive matching */ template struct equals_claim { const basic_claim expected; void operator()(const verify_context& ctx, std::error_code& ec) const { auto jc = ctx.get_claim(in_header, expected.get_type(), ec); if (ec) return; const bool matches = [&]() { switch (expected.get_type()) { case json::type::boolean: return expected.as_boolean() == jc.as_boolean(); case json::type::integer: return expected.as_integer() == jc.as_integer(); case json::type::number: return expected.as_number() == jc.as_number(); case json::type::string: return expected.as_string() == jc.as_string(); case json::type::array: case json::type::object: return json_traits::serialize(expected.to_json()) == json_traits::serialize(jc.to_json()); default: throw std::logic_error("internal error, should be unreachable"); } }(); if (!matches) { ec = error::token_verification_error::claim_value_missmatch; return; } } }; /** * Checks that the current time is before the time specified in the given * claim. This is identical to how the "exp" check works. */ template struct date_before_claim { const size_t leeway; void operator()(const verify_context& ctx, std::error_code& ec) const { auto jc = ctx.get_claim(in_header, json::type::integer, ec); if (ec) return; auto c = jc.as_date(); if (ctx.current_time > c + std::chrono::seconds(leeway)) { ec = error::token_verification_error::token_expired; } } }; /** * Checks that the current time is after the time specified in the given * claim. This is identical to how the "nbf" and "iat" check works. */ template struct date_after_claim { const size_t leeway; void operator()(const verify_context& ctx, std::error_code& ec) const { auto jc = ctx.get_claim(in_header, json::type::integer, ec); if (ec) return; auto c = jc.as_date(); if (ctx.current_time < c - std::chrono::seconds(leeway)) { ec = error::token_verification_error::token_expired; } } }; /** * Checks if the given set is a subset of the set inside the token. * If the token value is a string it is treated as a set with a single element. * The comparison is case sensitive. */ template struct is_subset_claim { const typename basic_claim::set_t expected; void operator()(const verify_context& ctx, std::error_code& ec) const { auto c = ctx.get_claim(in_header, ec); if (ec) return; if (c.get_type() == json::type::string) { if (expected.size() != 1 || *expected.begin() != c.as_string()) { ec = error::token_verification_error::audience_missmatch; return; } } else if (c.get_type() == json::type::array) { auto jc = c.as_set(); for (auto& e : expected) { if (jc.find(e) == jc.end()) { ec = error::token_verification_error::audience_missmatch; return; } } } else { ec = error::token_verification_error::claim_type_missmatch; return; } } }; /** * Checks if the claim is a string and does an case insensitive comparison. */ template struct insensitive_string_claim { const typename json_traits::string_type expected; std::locale locale; insensitive_string_claim(const typename json_traits::string_type& e, std::locale loc) : expected(to_lower_unicode(e, loc)), locale(loc) {} void operator()(const verify_context& ctx, std::error_code& ec) const { const auto c = ctx.get_claim(in_header, json::type::string, ec); if (ec) return; if (to_lower_unicode(c.as_string(), locale) != expected) { ec = error::token_verification_error::claim_value_missmatch; } } static std::string to_lower_unicode(const std::string& str, const std::locale& loc) { std::mbstate_t state = std::mbstate_t(); const char* in_next = str.data(); const char* in_end = str.data() + str.size(); std::wstring wide; wide.reserve(str.size()); while (in_next != in_end) { wchar_t wc; std::size_t result = std::mbrtowc(&wc, in_next, in_end - in_next, &state); if (result == static_cast(-1)) { throw std::runtime_error("encoding error: " + std::string(std::strerror(errno))); } else if (result == static_cast(-2)) { throw std::runtime_error("conversion error: next bytes constitute an incomplete, but so far " "valid, multibyte character."); } in_next += result; wide.push_back(wc); } auto& f = std::use_facet>(loc); f.tolower(&wide[0], &wide[0] + wide.size()); std::string out; out.reserve(wide.size()); for (wchar_t wc : wide) { char mb[MB_LEN_MAX]; std::size_t n = std::wcrtomb(mb, wc, &state); if (n != static_cast(-1)) out.append(mb, n); } return out; } }; } // namespace verify_ops /** * Verifier class used to check if a decoded token contains all claims required by your application and has a valid * signature. */ template class verifier { public: using basic_claim_t = basic_claim; /** * \brief Verification function data structure. * * This gets passed the current verifier, a reference to the decoded jwt, a reference to the key of this claim, * as well as a reference to an error_code. * The function checks if the actual value matches certain rules (e.g. equality to value x) and sets the error_code if * it does not. Once a non zero error_code is encountered the verification stops and this error_code becomes the result * returned from verify */ using verify_check_fn_t = std::function&, std::error_code& ec)>; private: struct algo_base { virtual ~algo_base() = default; virtual void verify(const std::string& data, const std::string& sig, std::error_code& ec) = 0; }; template struct algo : public algo_base { T alg; explicit algo(T a) : alg(a) {} void verify(const std::string& data, const std::string& sig, std::error_code& ec) override { alg.verify(data, sig, ec); } }; /// Required claims std::unordered_map claims; /// Leeway time for exp, nbf and iat size_t default_leeway = 0; /// Instance of clock type Clock clock; /// Supported algorithms std::unordered_map> algs; public: /** * Constructor for building a new verifier instance * \param c Clock instance */ explicit verifier(Clock c) : clock(c) { claims["exp"] = [](const verify_ops::verify_context& ctx, std::error_code& ec) { if (!ctx.jwt.has_expires_at()) return; auto exp = ctx.jwt.get_expires_at(); if (ctx.current_time > exp + std::chrono::seconds(ctx.default_leeway)) { ec = error::token_verification_error::token_expired; } }; claims["iat"] = [](const verify_ops::verify_context& ctx, std::error_code& ec) { if (!ctx.jwt.has_issued_at()) return; auto iat = ctx.jwt.get_issued_at(); if (ctx.current_time < iat - std::chrono::seconds(ctx.default_leeway)) { ec = error::token_verification_error::token_expired; } }; claims["nbf"] = [](const verify_ops::verify_context& ctx, std::error_code& ec) { if (!ctx.jwt.has_not_before()) return; auto nbf = ctx.jwt.get_not_before(); if (ctx.current_time < nbf - std::chrono::seconds(ctx.default_leeway)) { ec = error::token_verification_error::token_expired; } }; } /** * Set default leeway to use. * \param leeway Default leeway to use if not specified otherwise * \return *this to allow chaining */ verifier& leeway(size_t leeway) { default_leeway = leeway; return *this; } /** * Set leeway for expires at. * If not specified the default leeway will be used. * \param leeway Set leeway to use for expires at. * \return *this to allow chaining */ verifier& expires_at_leeway(size_t leeway) { claims["exp"] = verify_ops::date_before_claim{leeway}; return *this; } /** * Set leeway for not before. * If not specified the default leeway will be used. * \param leeway Set leeway to use for not before. * \return *this to allow chaining */ verifier& not_before_leeway(size_t leeway) { claims["nbf"] = verify_ops::date_after_claim{leeway}; return *this; } /** * Set leeway for issued at. * If not specified the default leeway will be used. * \param leeway Set leeway to use for issued at. * \return *this to allow chaining */ verifier& issued_at_leeway(size_t leeway) { claims["iat"] = verify_ops::date_after_claim{leeway}; return *this; } /** * Set an type to check for. * * According to [RFC 7519 Section 5.1](https://datatracker.ietf.org/doc/html/rfc7519#section-5.1), * This parameter is ignored by JWT implementations; any processing of this parameter is performed by the JWT application. * Check is case sensitive. * * \param type Type Header Parameter to check for. * \param locale Localization functionality to use when comparing * \return *this to allow chaining */ verifier& with_type(const typename json_traits::string_type& type, std::locale locale = std::locale{}) { return with_claim("typ", verify_ops::insensitive_string_claim{type, std::move(locale)}); } /** * Set an issuer to check for. * Check is case sensitive. * \param iss Issuer to check for. * \return *this to allow chaining */ verifier& with_issuer(const typename json_traits::string_type& iss) { return with_claim("iss", basic_claim_t(iss)); } /** * Set a subject to check for. * Check is case sensitive. * \param sub Subject to check for. * \return *this to allow chaining */ verifier& with_subject(const typename json_traits::string_type& sub) { return with_claim("sub", basic_claim_t(sub)); } /** * Set an audience to check for. * If any of the specified audiences is not present in the token the check fails. * \param aud Audience to check for. * \return *this to allow chaining */ verifier& with_audience(const typename basic_claim_t::set_t& aud) { claims["aud"] = verify_ops::is_subset_claim{aud}; return *this; } /** * Set an audience to check for. * If the specified audiences is not present in the token the check fails. * \param aud Audience to check for. * \return *this to allow chaining */ verifier& with_audience(const typename json_traits::string_type& aud) { typename basic_claim_t::set_t s; s.insert(aud); return with_audience(s); } /** * Set an id to check for. * Check is case sensitive. * \param id ID to check for. * \return *this to allow chaining */ verifier& with_id(const typename json_traits::string_type& id) { return with_claim("jti", basic_claim_t(id)); } /** * Specify a claim to check for using the specified operation. * This is helpful for implementating application specific authentication checks * such as the one seen in partial-claim-verifier.cpp * * \snippet{trimleft} partial-claim-verifier.cpp verifier check custom claim * * \param name Name of the claim to check for * \param fn Function to use for verifying the claim * \return *this to allow chaining */ verifier& with_claim(const typename json_traits::string_type& name, verify_check_fn_t fn) { claims[name] = fn; return *this; } /** * Specify a claim to check for equality (both type & value). * See the private-claims.cpp example. * * \snippet{trimleft} private-claims.cpp verify exact claim * * \param name Name of the claim to check for * \param c Claim to check for * \return *this to allow chaining */ verifier& with_claim(const typename json_traits::string_type& name, basic_claim_t c) { return with_claim(name, verify_ops::equals_claim{c}); } /** * \brief Add an algorithm available for checking. * * This is used to handle incomming tokens for predefined algorithms * which the authorization server is provided. For example a small system * where only a single RSA key-pair is used to sign tokens * * \snippet{trimleft} example/rsa-verify.cpp allow rsa algorithm * * \tparam Algorithm any algorithm such as those provided by jwt::algorithm * \param alg Algorithm to allow * \return *this to allow chaining */ template verifier& allow_algorithm(Algorithm alg) { algs[alg.name()] = std::make_shared>(alg); return *this; } /** * Verify the given token. * \param jwt Token to check * \throw token_verification_exception Verification failed */ void verify(const decoded_jwt& jwt) const { std::error_code ec; verify(jwt, ec); error::throw_if_error(ec); } /** * Verify the given token. * \param jwt Token to check * \param ec error_code filled with details on error */ void verify(const decoded_jwt& jwt, std::error_code& ec) const { ec.clear(); const typename json_traits::string_type data = jwt.get_header_base64() + "." + jwt.get_payload_base64(); const typename json_traits::string_type sig = jwt.get_signature(); const std::string algo = jwt.get_algorithm(); if (algs.count(algo) == 0) { ec = error::token_verification_error::wrong_algorithm; return; } algs.at(algo)->verify(data, sig, ec); if (ec) return; verify_ops::verify_context ctx{clock.now(), jwt, default_leeway}; for (auto& c : claims) { ctx.claim_key = c.first; c.second(ctx, ec); if (ec) return; } } }; /** * \brief JSON Web Key * * https://tools.ietf.org/html/rfc7517 * * A JSON object that represents a cryptographic key. The members of * the object represent properties of the key, including its value. */ template class jwk { using basic_claim_t = basic_claim; const details::map_of_claims jwk_claims; public: JWT_CLAIM_EXPLICIT jwk(const typename json_traits::string_type& str) : jwk_claims(details::map_of_claims::parse_claims(str)) {} JWT_CLAIM_EXPLICIT jwk(const typename json_traits::value_type& json) : jwk_claims(json_traits::as_object(json)) {} /** * Get key type claim * * This returns the general type (e.g. RSA or EC), not a specific algorithm value. * \return key type as string * \throw std::runtime_error If claim was not present * \throw std::bad_cast Claim was present but not a string (Should not happen in a valid token) */ typename json_traits::string_type get_key_type() const { return get_jwk_claim("kty").as_string(); } /** * Get public key usage claim * \return usage parameter as string * \throw std::runtime_error If claim was not present * \throw std::bad_cast Claim was present but not a string (Should not happen in a valid token) */ typename json_traits::string_type get_use() const { return get_jwk_claim("use").as_string(); } /** * Get key operation types claim * \return key operation types as a set of strings * \throw std::runtime_error If claim was not present * \throw std::bad_cast Claim was present but not a string (Should not happen in a valid token) */ typename basic_claim_t::set_t get_key_operations() const { return get_jwk_claim("key_ops").as_set(); } /** * Get algorithm claim * \return algorithm as string * \throw std::runtime_error If claim was not present * \throw std::bad_cast Claim was present but not a string (Should not happen in a valid token) */ typename json_traits::string_type get_algorithm() const { return get_jwk_claim("alg").as_string(); } /** * Get key id claim * \return key id as string * \throw std::runtime_error If claim was not present * \throw std::bad_cast Claim was present but not a string (Should not happen in a valid token) */ typename json_traits::string_type get_key_id() const { return get_jwk_claim("kid").as_string(); } /** * \brief Get curve claim * * https://www.rfc-editor.org/rfc/rfc7518.html#section-6.2.1.1 * https://www.iana.org/assignments/jose/jose.xhtml#table-web-key-elliptic-curve * * \return curve as string * \throw std::runtime_error If claim was not present * \throw std::bad_cast Claim was present but not a string (Should not happen in a valid token) */ typename json_traits::string_type get_curve() const { return get_jwk_claim("crv").as_string(); } /** * Get x5c claim * \return x5c as an array * \throw std::runtime_error If claim was not present * \throw std::bad_cast Claim was present but not a array (Should not happen in a valid token) */ typename json_traits::array_type get_x5c() const { return get_jwk_claim("x5c").as_array(); }; /** * Get X509 URL claim * \return x5u as string * \throw std::runtime_error If claim was not present * \throw std::bad_cast Claim was present but not a string (Should not happen in a valid token) */ typename json_traits::string_type get_x5u() const { return get_jwk_claim("x5u").as_string(); }; /** * Get X509 thumbprint claim * \return x5t as string * \throw std::runtime_error If claim was not present * \throw std::bad_cast Claim was present but not a string (Should not happen in a valid token) */ typename json_traits::string_type get_x5t() const { return get_jwk_claim("x5t").as_string(); }; /** * Get X509 SHA256 thumbprint claim * \return x5t#S256 as string * \throw std::runtime_error If claim was not present * \throw std::bad_cast Claim was present but not a string (Should not happen in a valid token) */ typename json_traits::string_type get_x5t_sha256() const { return get_jwk_claim("x5t#S256").as_string(); }; /** * Get x5c claim as a string * \return x5c as an string * \throw std::runtime_error If claim was not present * \throw std::bad_cast Claim was present but not a string (Should not happen in a valid token) */ typename json_traits::string_type get_x5c_key_value() const { auto x5c_array = get_jwk_claim("x5c").as_array(); if (x5c_array.size() == 0) throw error::claim_not_present_exception(); return json_traits::as_string(x5c_array.front()); }; /** * Check if a key type is present ("kty") * \return true if present, false otherwise */ bool has_key_type() const noexcept { return has_jwk_claim("kty"); } /** * Check if a public key usage indication is present ("use") * \return true if present, false otherwise */ bool has_use() const noexcept { return has_jwk_claim("use"); } /** * Check if a key operations parameter is present ("key_ops") * \return true if present, false otherwise */ bool has_key_operations() const noexcept { return has_jwk_claim("key_ops"); } /** * Check if algorithm is present ("alg") * \return true if present, false otherwise */ bool has_algorithm() const noexcept { return has_jwk_claim("alg"); } /** * Check if curve is present ("crv") * \return true if present, false otherwise */ bool has_curve() const noexcept { return has_jwk_claim("crv"); } /** * Check if key id is present ("kid") * \return true if present, false otherwise */ bool has_key_id() const noexcept { return has_jwk_claim("kid"); } /** * Check if X509 URL is present ("x5u") * \return true if present, false otherwise */ bool has_x5u() const noexcept { return has_jwk_claim("x5u"); } /** * Check if X509 Chain is present ("x5c") * \return true if present, false otherwise */ bool has_x5c() const noexcept { return has_jwk_claim("x5c"); } /** * Check if a X509 thumbprint is present ("x5t") * \return true if present, false otherwise */ bool has_x5t() const noexcept { return has_jwk_claim("x5t"); } /** * Check if a X509 SHA256 thumbprint is present ("x5t#S256") * \return true if present, false otherwise */ bool has_x5t_sha256() const noexcept { return has_jwk_claim("x5t#S256"); } /** * Check if a jwk claim is present * \return true if claim was present, false otherwise */ bool has_jwk_claim(const typename json_traits::string_type& name) const noexcept { return jwk_claims.has_claim(name); } /** * Get jwk claim by name * \return Requested claim * \throw std::runtime_error If claim was not present */ basic_claim_t get_jwk_claim(const typename json_traits::string_type& name) const { return jwk_claims.get_claim(name); } /** * Check if the jwk has any claims * \return true is any claim is present */ bool empty() const noexcept { return jwk_claims.empty(); } /** * Get all jwk claims * \return Map of claims */ typename json_traits::object_type get_claims() const { return this->jwk_claims.claims; } }; /** * \brief JWK Set * * https://tools.ietf.org/html/rfc7517 * * A JSON object that represents a set of JWKs. The JSON object MUST * have a "keys" member, which is an array of JWKs. * * This container takes a JWKs and simplifies it to a vector of JWKs */ template class jwks { public: /// JWK instance template specialization using jwks_t = jwk; /// Type specialization for the vector of JWK using jwks_vector_t = std::vector; using iterator = typename jwks_vector_t::iterator; using const_iterator = typename jwks_vector_t::const_iterator; /** * Default constructor producing an empty object without any keys */ jwks() = default; /** * Parses a string buffer to extract the JWKS. * \param str buffer containing JSON object representing a JWKS * \throw error::invalid_json_exception or underlying JSON implation error if the JSON is * invalid with regards to the JWKS specification */ JWT_CLAIM_EXPLICIT jwks(const typename json_traits::string_type& str) { typename json_traits::value_type parsed_val; if (!json_traits::parse(parsed_val, str)) throw error::invalid_json_exception(); const details::map_of_claims jwks_json = json_traits::as_object(parsed_val); if (!jwks_json.has_claim("keys")) throw error::invalid_json_exception(); auto jwk_list = jwks_json.get_claim("keys").as_array(); std::transform(jwk_list.begin(), jwk_list.end(), std::back_inserter(jwk_claims), [](const typename json_traits::value_type& val) { return jwks_t{val}; }); } iterator begin() { return jwk_claims.begin(); } iterator end() { return jwk_claims.end(); } const_iterator cbegin() const { return jwk_claims.begin(); } const_iterator cend() const { return jwk_claims.end(); } const_iterator begin() const { return jwk_claims.begin(); } const_iterator end() const { return jwk_claims.end(); } /** * Check if a jwk with the kid is present * \return true if jwk was present, false otherwise */ bool has_jwk(const typename json_traits::string_type& key_id) const noexcept { return find_by_kid(key_id) != end(); } /** * Get jwk * \return Requested jwk by key_id * \throw std::runtime_error If jwk was not present */ jwks_t get_jwk(const typename json_traits::string_type& key_id) const { const auto maybe = find_by_kid(key_id); if (maybe == end()) throw error::claim_not_present_exception(); return *maybe; } private: jwks_vector_t jwk_claims; const_iterator find_by_kid(const typename json_traits::string_type& key_id) const noexcept { return std::find_if(cbegin(), cend(), [key_id](const jwks_t& jwk) { if (!jwk.has_key_id()) { return false; } return jwk.get_key_id() == key_id; }); } }; /** * Create a verifier using the given clock * \param c Clock instance to use * \return verifier instance */ template verifier verify(Clock c) { return verifier(c); } /** * Create a builder using the given clock * \param c Clock instance to use * \return builder instance */ template builder create(Clock c) { return builder(c); } /** * Default clock class using std::chrono::system_clock as a backend. */ struct default_clock { /** * Gets the current system time * \return time_point of the host system */ date now() const { return date::clock::now(); } }; /** * Create a verifier using the default_clock. * * * * \param c Clock instance to use * \return verifier instance */ template verifier verify(default_clock c = {}) { return verifier(c); } /** * Return a builder instance to create a new token */ template builder create(default_clock c = {}) { return builder(c); } /** * \brief Decode a token. This can be used to to help access important feild like 'x5c' * for verifying tokens. See associated example rsa-verify.cpp for more details. * * \tparam json_traits JSON implementation traits * \tparam Decode is callable, taking a string_type and returns a string_type. * It should ensure the padding of the input and then base64url decode and * return the results. * \param token Token to decode * \param decode function that will pad and base64url decode the token * \return Decoded token * \throw std::invalid_argument Token is not in correct format * \throw std::runtime_error Base64 decoding failed or invalid json */ template decoded_jwt decode(const typename json_traits::string_type& token, Decode decode) { return decoded_jwt(token, decode); } /** * Decode a token. This can be used to to help access important feild like 'x5c' * for verifying tokens. See associated example rsa-verify.cpp for more details. * * \tparam json_traits JSON implementation traits * \param token Token to decode * \return Decoded token * \throw std::invalid_argument Token is not in correct format * \throw std::runtime_error Base64 decoding failed or invalid json */ template decoded_jwt decode(const typename json_traits::string_type& token) { return decoded_jwt(token); } /** * Parse a single JSON Web Key * \tparam json_traits JSON implementation traits * \param jwk_ string buffer containing the JSON object * \return Decoded jwk */ template jwk parse_jwk(const typename json_traits::string_type& jwk_) { return jwk(jwk_); } /** * Parse a JSON Web Key Set. This can be used to to help access * important feild like 'x5c' for verifying tokens. See example * jwks-verify.cpp for more information. * * \tparam json_traits JSON implementation traits * \param jwks_ string buffer containing the JSON object * \return Parsed JSON object containing the data of the JWK SET string * \throw std::runtime_error Token is not in correct format */ template jwks parse_jwks(const typename json_traits::string_type& jwks_) { return jwks(jwks_); } } // namespace jwt template std::istream& operator>>(std::istream& is, jwt::basic_claim& c) { return c.operator>>(is); } template std::ostream& operator<<(std::ostream& os, const jwt::basic_claim& c) { return os << c.to_json(); } #ifndef JWT_DISABLE_PICOJSON #include "traits/kazuho-picojson/defaults.h" #endif #endif scitokens-cpp-1.3.0/vendor/jwt-cpp/include/jwt-cpp/traits/000077500000000000000000000000001513647275500235125ustar00rootroot00000000000000scitokens-cpp-1.3.0/vendor/jwt-cpp/include/jwt-cpp/traits/boost-json/000077500000000000000000000000001513647275500256075ustar00rootroot00000000000000scitokens-cpp-1.3.0/vendor/jwt-cpp/include/jwt-cpp/traits/boost-json/defaults.h000066400000000000000000000052701513647275500275730ustar00rootroot00000000000000#ifndef JWT_CPP_BOOST_JSON_DEFAULTS_H #define JWT_CPP_BOOST_JSON_DEFAULTS_H #ifndef JWT_DISABLE_PICOJSON #define JWT_DISABLE_PICOJSON #endif #include "traits.h" namespace jwt { /** * \brief a class to store a generic [Boost.JSON](https://github.com/boostorg/json) value as claim * * This type is the specialization of the \ref basic_claim class which * uses the standard template types. */ using claim = basic_claim; /** * Create a verifier using the default clock * \return verifier instance */ inline verifier verify() { return verify(default_clock{}); } /** * Create a builder using the default clock * \return builder instance to create a new token */ inline builder create() { return builder(default_clock{}); } #ifndef JWT_DISABLE_BASE64 /** * Decode a token * \param token Token to decode * \return Decoded token * \throw std::invalid_argument Token is not in correct format * \throw std::runtime_error Base64 decoding failed or invalid json */ inline decoded_jwt decode(const std::string& token) { return decoded_jwt(token); } #endif /** * Decode a token * \tparam Decode is callable, taking a string_type and returns a string_type. * It should ensure the padding of the input and then base64url decode and * return the results. * \param token Token to decode * \param decode The token to parse * \return Decoded token * \throw std::invalid_argument Token is not in correct format * \throw std::runtime_error Base64 decoding failed or invalid json */ template decoded_jwt decode(const std::string& token, Decode decode) { return decoded_jwt(token, decode); } /** * Parse a jwk * \param token JWK Token to parse * \return Parsed JWK * \throw std::runtime_error Token is not in correct format */ inline jwk parse_jwk(const traits::boost_json::string_type& token) { return jwk(token); } /** * Parse a jwks * \param token JWKs Token to parse * \return Parsed JWKs * \throw std::runtime_error Token is not in correct format */ inline jwks parse_jwks(const traits::boost_json::string_type& token) { return jwks(token); } /** * This type is the specialization of the \ref verify_ops::verify_context class which * uses the standard template types. */ using verify_context = verify_ops::verify_context; } // namespace jwt #endif // JWT_CPP_BOOST_JSON_DEFAULTS_H scitokens-cpp-1.3.0/vendor/jwt-cpp/include/jwt-cpp/traits/boost-json/traits.h000066400000000000000000000051471513647275500272750ustar00rootroot00000000000000#ifndef JWT_CPP_BOOSTJSON_TRAITS_H #define JWT_CPP_BOOSTJSON_TRAITS_H #define JWT_DISABLE_PICOJSON #include "jwt-cpp/jwt.h" #include // if not boost JSON standalone then error... namespace jwt { /** * \brief Namespace containing all the json_trait implementations for a jwt::basic_claim. */ namespace traits { namespace json = boost::json; /// basic_claim's JSON trait implementation for Boost.JSON struct boost_json { using value_type = json::value; using object_type = json::object; using array_type = json::array; using string_type = std::string; using number_type = double; using integer_type = std::int64_t; using boolean_type = bool; static jwt::json::type get_type(const value_type& val) { using jwt::json::type; if (val.kind() == json::kind::bool_) return type::boolean; if (val.kind() == json::kind::int64) return type::integer; if (val.kind() == json::kind::uint64) // boost internally tracks two types of integers return type::integer; if (val.kind() == json::kind::double_) return type::number; if (val.kind() == json::kind::string) return type::string; if (val.kind() == json::kind::array) return type::array; if (val.kind() == json::kind::object) return type::object; throw std::logic_error("invalid type"); } static object_type as_object(const value_type& val) { if (val.kind() != json::kind::object) throw std::bad_cast(); return val.get_object(); } static array_type as_array(const value_type& val) { if (val.kind() != json::kind::array) throw std::bad_cast(); return val.get_array(); } static string_type as_string(const value_type& val) { if (val.kind() != json::kind::string) throw std::bad_cast(); return string_type{val.get_string()}; } static integer_type as_integer(const value_type& val) { switch (val.kind()) { case json::kind::int64: return val.get_int64(); case json::kind::uint64: return static_cast(val.get_uint64()); default: throw std::bad_cast(); } } static boolean_type as_boolean(const value_type& val) { if (val.kind() != json::kind::bool_) throw std::bad_cast(); return val.get_bool(); } static number_type as_number(const value_type& val) { if (val.kind() != json::kind::double_) throw std::bad_cast(); return val.get_double(); } static bool parse(value_type& val, string_type str) { val = json::parse(str); return true; } static std::string serialize(const value_type& val) { return json::serialize(val); } }; } // namespace traits } // namespace jwt #endif // JWT_CPP_BOOSTJSON_TRAITS_H scitokens-cpp-1.3.0/vendor/jwt-cpp/include/jwt-cpp/traits/danielaparker-jsoncons/000077500000000000000000000000001513647275500301465ustar00rootroot00000000000000scitokens-cpp-1.3.0/vendor/jwt-cpp/include/jwt-cpp/traits/danielaparker-jsoncons/defaults.h000066400000000000000000000056431513647275500321360ustar00rootroot00000000000000#ifndef JWT_CPP_DANIELAPARKER_JSONCONS_DEFAULTS_H #define JWT_CPP_DANIELAPARKER_JSONCONS_DEFAULTS_H #ifndef JWT_DISABLE_PICOJSON #define JWT_DISABLE_PICOJSON #endif #include "traits.h" namespace jwt { /** * \brief a class to store a generic [jsoncons](https://github.com/danielaparker/jsoncons) value as claim * * This type is the specialization of the \ref basic_claim class which * uses the standard template types. */ using claim = basic_claim; /** * Create a verifier using the default clock * \return verifier instance */ inline verifier verify() { return verify(default_clock{}); } /** * Create a builder using the default clock * \return builder instance to create a new token */ inline builder create() { return builder(default_clock{}); } #ifndef JWT_DISABLE_BASE64 /** * Decode a token * \param token Token to decode * \return Decoded token * \throw std::invalid_argument Token is not in correct format * \throw std::runtime_error Base64 decoding failed or invalid json */ inline decoded_jwt decode(const std::string& token) { return decoded_jwt(token); } #endif /** * Decode a token * \tparam Decode is callable, taking a string_type and returns a string_type. * It should ensure the padding of the input and then base64url decode and * return the results. * \param token Token to decode * \param decode The token to parse * \return Decoded token * \throw std::invalid_argument Token is not in correct format * \throw std::runtime_error Base64 decoding failed or invalid json */ template decoded_jwt decode(const std::string& token, Decode decode) { return decoded_jwt(token, decode); } /** * Parse a jwk * \param token JWK Token to parse * \return Parsed JWK * \throw std::runtime_error Token is not in correct format */ inline jwk parse_jwk(const traits::danielaparker_jsoncons::string_type& token) { return jwk(token); } /** * Parse a jwks * \param token JWKs Token to parse * \return Parsed JWKs * \throw std::runtime_error Token is not in correct format */ inline jwks parse_jwks(const traits::danielaparker_jsoncons::string_type& token) { return jwks(token); } /** * This type is the specialization of the \ref verify_ops::verify_context class which * uses the standard template types. */ using verify_context = verify_ops::verify_context; } // namespace jwt #endif // JWT_CPP_DANIELAPARKER_JSONCONS_DEFAULTS_H scitokens-cpp-1.3.0/vendor/jwt-cpp/include/jwt-cpp/traits/danielaparker-jsoncons/traits.h000066400000000000000000000115021513647275500316240ustar00rootroot00000000000000#define JWT_DISABLE_PICOJSON #define JSONCONS_NO_DEPRECATED #include "jwt-cpp/jwt.h" #include "jsoncons/json.hpp" #include namespace jwt { /** * \brief Namespace containing all the json_trait implementations for a jwt::basic_claim. */ namespace traits { /// basic_claim's JSON trait implementation for jsoncons. struct danielaparker_jsoncons { // Needs at least https://github.com/danielaparker/jsoncons/commit/28c56b90ec7337f98a5b8942574590111a5e5831 static_assert(jsoncons::version().minor >= 167, "A higher version of jsoncons is required!"); using json = jsoncons::json; using value_type = json; struct object_type : json::object { // Add missing C++11 member types // https://github.com/danielaparker/jsoncons/commit/1b1ceeb572f9a2db6d37cff47ac78a4f14e072e2#commitcomment-45391411 using value_type = key_value_type; // Enable optional jwt-cpp methods using mapped_type = key_value_type::value_type; using size_type = size_t; // for implementing count object_type() = default; object_type(const object_type&) = default; explicit object_type(const json::object& o) : json::object(o) {} object_type(object_type&&) = default; explicit object_type(json::object&& o) : json::object(o) {} ~object_type() = default; object_type& operator=(const object_type& o) = default; object_type& operator=(object_type&& o) noexcept = default; // Add missing C++11 subscription operator mapped_type& operator[](const key_type& key) { // https://github.com/microsoft/STL/blob/2914b4301c59dc7ffc09d16ac6f7979fde2b7f2c/stl/inc/map#L325 return try_emplace(key).first->value(); } // Add missing C++11 element access const mapped_type& at(const key_type& key) const { auto target = find(key); if (target != end()) return target->value(); throw std::out_of_range("invalid key"); } // Add missing C++11 lookup method size_type count(const key_type& key) const { struct compare { bool operator()(const value_type& val, const key_type& key) const { return val.key() < key; } bool operator()(const key_type& key, const value_type& val) const { return key < val.key(); } }; // https://en.cppreference.com/w/cpp/algorithm/binary_search#Complexity if (std::binary_search(this->begin(), this->end(), key, compare{})) return 1; return 0; } }; class array_type : public json::array { public: using json::array::array; explicit array_type(const json::array& a) : json::array(a) {} explicit array_type(json::array&& a) : json::array(a) {} value_type const& front() const { return this->operator[](0U); } }; using string_type = std::string; // current limitation of traits implementation using number_type = double; using integer_type = int64_t; using boolean_type = bool; static jwt::json::type get_type(const json& val) { using jwt::json::type; if (val.type() == jsoncons::json_type::bool_value) return type::boolean; if (val.type() == jsoncons::json_type::int64_value) return type::integer; if (val.type() == jsoncons::json_type::uint64_value) return type::integer; if (val.type() == jsoncons::json_type::half_value) return type::number; if (val.type() == jsoncons::json_type::double_value) return type::number; if (val.type() == jsoncons::json_type::string_value) return type::string; if (val.type() == jsoncons::json_type::array_value) return type::array; if (val.type() == jsoncons::json_type::object_value) return type::object; throw std::logic_error("invalid type"); } static object_type as_object(const json& val) { if (val.type() != jsoncons::json_type::object_value) throw std::bad_cast(); return object_type(val.object_value()); } static array_type as_array(const json& val) { if (val.type() != jsoncons::json_type::array_value) throw std::bad_cast(); return array_type(val.array_value()); } static string_type as_string(const json& val) { if (val.type() != jsoncons::json_type::string_value) throw std::bad_cast(); return val.as_string(); } static number_type as_number(const json& val) { if (get_type(val) != jwt::json::type::number) throw std::bad_cast(); return val.as_double(); } static integer_type as_integer(const json& val) { if (get_type(val) != jwt::json::type::integer) throw std::bad_cast(); return val.as(); } static boolean_type as_boolean(const json& val) { if (val.type() != jsoncons::json_type::bool_value) throw std::bad_cast(); return val.as_bool(); } static bool parse(json& val, const std::string& str) { val = json::parse(str); return true; } static std::string serialize(const json& val) { std::ostringstream os; os << jsoncons::print(val); return os.str(); } }; } // namespace traits } // namespace jwt scitokens-cpp-1.3.0/vendor/jwt-cpp/include/jwt-cpp/traits/defaults.h.mustache000066400000000000000000000055301513647275500273050ustar00rootroot00000000000000#ifndef JWT_CPP_{{traits_name_upper}}_DEFAULTS_H #define JWT_CPP_{{traits_name_upper}}_DEFAULTS_H {{#disable_default_traits}} #ifndef JWT_DISABLE_PICOJSON #define JWT_DISABLE_PICOJSON #endif {{/disable_default_traits}} #include "traits.h" namespace jwt { /** * \brief a class to store a generic [{{library_name}}]({{{library_url}}}) value as claim * * This type is the specialization of the \ref basic_claim class which * uses the standard template types. */ using claim = basic_claim; /** * Create a verifier using the default clock * \return verifier instance */ inline verifier verify() { return verify(default_clock{}); } /** * Create a builder using the default clock * \return builder instance to create a new token */ inline builder create() { return builder(default_clock{}); } #ifndef JWT_DISABLE_BASE64 /** * Decode a token * \param token Token to decode * \return Decoded token * \throw std::invalid_argument Token is not in correct format * \throw std::runtime_error Base64 decoding failed or invalid json */ inline decoded_jwt decode(const std::string& token) { return decoded_jwt(token); } #endif /** * Decode a token * \tparam Decode is callable, taking a string_type and returns a string_type. * It should ensure the padding of the input and then base64url decode and * return the results. * \param token Token to decode * \param decode The token to parse * \return Decoded token * \throw std::invalid_argument Token is not in correct format * \throw std::runtime_error Base64 decoding failed or invalid json */ template decoded_jwt decode(const std::string& token, Decode decode) { return decoded_jwt(token, decode); } /** * Parse a jwk * \param token JWK Token to parse * \return Parsed JWK * \throw std::runtime_error Token is not in correct format */ inline jwk parse_jwk(const traits::{{traits_name}}::string_type& token) { return jwk(token); } /** * Parse a jwks * \param token JWKs Token to parse * \return Parsed JWKs * \throw std::runtime_error Token is not in correct format */ inline jwks parse_jwks(const traits::{{traits_name}}::string_type& token) { return jwks(token); } /** * This type is the specialization of the \ref verify_ops::verify_context class which * uses the standard template types. */ using verify_context = verify_ops::verify_context; } // namespace jwt #endif // JWT_CPP_{{traits_name_upper}}_DEFAULTS_H scitokens-cpp-1.3.0/vendor/jwt-cpp/include/jwt-cpp/traits/kazuho-picojson/000077500000000000000000000000001513647275500266355ustar00rootroot00000000000000scitokens-cpp-1.3.0/vendor/jwt-cpp/include/jwt-cpp/traits/kazuho-picojson/defaults.h000066400000000000000000000053251513647275500306220ustar00rootroot00000000000000#ifndef JWT_CPP_KAZUHO_PICOJSON_DEFAULTS_H #define JWT_CPP_KAZUHO_PICOJSON_DEFAULTS_H #include "traits.h" namespace jwt { /** * \brief a class to store a generic [picojson](https://github.com/kazuho/picojson) value as claim * * This type is the specialization of the \ref basic_claim class which * uses the standard template types. */ using claim = basic_claim; /** * Create a verifier using the default clock * \return verifier instance */ inline verifier verify() { return verify(default_clock{}); } /** * Create a builder using the default clock * \return builder instance to create a new token */ inline builder create() { return builder(default_clock{}); } #ifndef JWT_DISABLE_BASE64 /** * Decode a token * \param token Token to decode * \return Decoded token * \throw std::invalid_argument Token is not in correct format * \throw std::runtime_error Base64 decoding failed or invalid json */ inline decoded_jwt decode(const std::string& token) { return decoded_jwt(token); } #endif /** * Decode a token * \tparam Decode is callable, taking a string_type and returns a string_type. * It should ensure the padding of the input and then base64url decode and * return the results. * \param token Token to decode * \param decode The token to parse * \return Decoded token * \throw std::invalid_argument Token is not in correct format * \throw std::runtime_error Base64 decoding failed or invalid json */ template decoded_jwt decode(const std::string& token, Decode decode) { return decoded_jwt(token, decode); } /** * Parse a jwk * \param token JWK Token to parse * \return Parsed JWK * \throw std::runtime_error Token is not in correct format */ inline jwk parse_jwk(const traits::kazuho_picojson::string_type& token) { return jwk(token); } /** * Parse a jwks * \param token JWKs Token to parse * \return Parsed JWKs * \throw std::runtime_error Token is not in correct format */ inline jwks parse_jwks(const traits::kazuho_picojson::string_type& token) { return jwks(token); } /** * This type is the specialization of the \ref verify_ops::verify_context class which * uses the standard template types. */ using verify_context = verify_ops::verify_context; } // namespace jwt #endif // JWT_CPP_KAZUHO_PICOJSON_DEFAULTS_H scitokens-cpp-1.3.0/vendor/jwt-cpp/include/jwt-cpp/traits/kazuho-picojson/traits.h000066400000000000000000000044641513647275500303240ustar00rootroot00000000000000#ifndef JWT_CPP_PICOJSON_TRAITS_H #define JWT_CPP_PICOJSON_TRAITS_H #ifndef PICOJSON_USE_INT64 #define PICOJSON_USE_INT64 #endif #include "picojson/picojson.h" #ifndef JWT_DISABLE_PICOJSON #define JWT_DISABLE_PICOJSON #endif #include "jwt-cpp/jwt.h" namespace jwt { /** * \brief Namespace containing all the json_trait implementations for a jwt::basic_claim. */ namespace traits { /// basic_claim's JSON trait implementation for picojson struct kazuho_picojson { using value_type = picojson::value; using object_type = picojson::object; using array_type = picojson::array; using string_type = std::string; using number_type = double; using integer_type = int64_t; using boolean_type = bool; static json::type get_type(const picojson::value& val) { using json::type; if (val.is()) return type::boolean; if (val.is()) return type::integer; if (val.is()) return type::number; if (val.is()) return type::string; if (val.is()) return type::array; if (val.is()) return type::object; throw std::logic_error("invalid type"); } static picojson::object as_object(const picojson::value& val) { if (!val.is()) throw std::bad_cast(); return val.get(); } static std::string as_string(const picojson::value& val) { if (!val.is()) throw std::bad_cast(); return val.get(); } static picojson::array as_array(const picojson::value& val) { if (!val.is()) throw std::bad_cast(); return val.get(); } static int64_t as_integer(const picojson::value& val) { if (!val.is()) throw std::bad_cast(); return val.get(); } static bool as_boolean(const picojson::value& val) { if (!val.is()) throw std::bad_cast(); return val.get(); } static double as_number(const picojson::value& val) { if (!val.is()) throw std::bad_cast(); return val.get(); } static bool parse(picojson::value& val, const std::string& str) { return picojson::parse(val, str).empty(); } static std::string serialize(const picojson::value& val) { return val.serialize(); } }; } // namespace traits } // namespace jwt #endif scitokens-cpp-1.3.0/vendor/jwt-cpp/include/jwt-cpp/traits/nlohmann-json/000077500000000000000000000000001513647275500262735ustar00rootroot00000000000000scitokens-cpp-1.3.0/vendor/jwt-cpp/include/jwt-cpp/traits/nlohmann-json/defaults.h000066400000000000000000000053721513647275500302620ustar00rootroot00000000000000#ifndef JWT_CPP_NLOHMANN_JSON_DEFAULTS_H #define JWT_CPP_NLOHMANN_JSON_DEFAULTS_H #ifndef JWT_DISABLE_PICOJSON #define JWT_DISABLE_PICOJSON #endif #include "traits.h" namespace jwt { /** * \brief a class to store a generic [JSON for Modern C++](https://github.com/nlohmann/json) value as claim * * This type is the specialization of the \ref basic_claim class which * uses the standard template types. */ using claim = basic_claim; /** * Create a verifier using the default clock * \return verifier instance */ inline verifier verify() { return verify(default_clock{}); } /** * Create a builder using the default clock * \return builder instance to create a new token */ inline builder create() { return builder(default_clock{}); } #ifndef JWT_DISABLE_BASE64 /** * Decode a token * \param token Token to decode * \return Decoded token * \throw std::invalid_argument Token is not in correct format * \throw std::runtime_error Base64 decoding failed or invalid json */ inline decoded_jwt decode(const std::string& token) { return decoded_jwt(token); } #endif /** * Decode a token * \tparam Decode is callable, taking a string_type and returns a string_type. * It should ensure the padding of the input and then base64url decode and * return the results. * \param token Token to decode * \param decode The token to parse * \return Decoded token * \throw std::invalid_argument Token is not in correct format * \throw std::runtime_error Base64 decoding failed or invalid json */ template decoded_jwt decode(const std::string& token, Decode decode) { return decoded_jwt(token, decode); } /** * Parse a jwk * \param token JWK Token to parse * \return Parsed JWK * \throw std::runtime_error Token is not in correct format */ inline jwk parse_jwk(const traits::nlohmann_json::string_type& token) { return jwk(token); } /** * Parse a jwks * \param token JWKs Token to parse * \return Parsed JWKs * \throw std::runtime_error Token is not in correct format */ inline jwks parse_jwks(const traits::nlohmann_json::string_type& token) { return jwks(token); } /** * This type is the specialization of the \ref verify_ops::verify_context class which * uses the standard template types. */ using verify_context = verify_ops::verify_context; } // namespace jwt #endif // JWT_CPP_NLOHMANN_JSON_DEFAULTS_H scitokens-cpp-1.3.0/vendor/jwt-cpp/include/jwt-cpp/traits/nlohmann-json/traits.h000066400000000000000000000051451513647275500277570ustar00rootroot00000000000000#ifndef JWT_CPP_NLOHMANN_JSON_TRAITS_H #define JWT_CPP_NLOHMANN_JSON_TRAITS_H #include "jwt-cpp/jwt.h" #include "nlohmann/json.hpp" namespace jwt { /** * \brief Namespace containing all the json_trait implementations for a jwt::basic_claim. */ namespace traits { /// basic_claim's JSON trait implementation for Modern C++ JSON struct nlohmann_json { using json = nlohmann::json; using value_type = json; using object_type = json::object_t; using array_type = json::array_t; using string_type = std::string; // current limitation of traits implementation using number_type = json::number_float_t; using integer_type = json::number_integer_t; using boolean_type = json::boolean_t; static jwt::json::type get_type(const json& val) { using jwt::json::type; if (val.type() == json::value_t::boolean) return type::boolean; // nlohmann internally tracks two types of integers if (val.type() == json::value_t::number_integer) return type::integer; if (val.type() == json::value_t::number_unsigned) return type::integer; if (val.type() == json::value_t::number_float) return type::number; if (val.type() == json::value_t::string) return type::string; if (val.type() == json::value_t::array) return type::array; if (val.type() == json::value_t::object) return type::object; throw std::logic_error("invalid type"); } static json::object_t as_object(const json& val) { if (val.type() != json::value_t::object) throw std::bad_cast(); return val.get(); } static std::string as_string(const json& val) { if (val.type() != json::value_t::string) throw std::bad_cast(); return val.get(); } static json::array_t as_array(const json& val) { if (val.type() != json::value_t::array) throw std::bad_cast(); return val.get(); } static int64_t as_integer(const json& val) { switch (val.type()) { case json::value_t::number_integer: case json::value_t::number_unsigned: return val.get(); default: throw std::bad_cast(); } } static bool as_boolean(const json& val) { if (val.type() != json::value_t::boolean) throw std::bad_cast(); return val.get(); } static double as_number(const json& val) { if (val.type() != json::value_t::number_float) throw std::bad_cast(); return val.get(); } static bool parse(json& val, std::string str) { val = json::parse(str.begin(), str.end()); return true; } static std::string serialize(const json& val) { return val.dump(); } }; } // namespace traits } // namespace jwt #endif scitokens-cpp-1.3.0/vendor/jwt-cpp/include/jwt-cpp/traits/open-source-parsers-jsoncpp/000077500000000000000000000000001513647275500311005ustar00rootroot00000000000000scitokens-cpp-1.3.0/vendor/jwt-cpp/include/jwt-cpp/traits/open-source-parsers-jsoncpp/defaults.h000066400000000000000000000060101513647275500330550ustar00rootroot00000000000000#ifndef JWT_CPP_OPEN_SOURCE_PARSERS_JSONCPP_DEFAULTS_H #define JWT_CPP_OPEN_SOURCE_PARSERS_JSONCPP_DEFAULTS_H #ifndef JWT_DISABLE_PICOJSON #define JWT_DISABLE_PICOJSON #endif #include "traits.h" namespace jwt { /** * \brief a class to store a generic [jsoncpp](https://github.com/open-source-parsers/jsoncpp) value as claim * * This type is the specialization of the \ref basic_claim class which * uses the standard template types. */ using claim = basic_claim; /** * Create a verifier using the default clock * \return verifier instance */ inline verifier verify() { return verify(default_clock{}); } /** * Create a builder using the default clock * \return builder instance to create a new token */ inline builder create() { return builder(default_clock{}); } #ifndef JWT_DISABLE_BASE64 /** * Decode a token * \param token Token to decode * \return Decoded token * \throw std::invalid_argument Token is not in correct format * \throw std::runtime_error Base64 decoding failed or invalid json */ inline decoded_jwt decode(const std::string& token) { return decoded_jwt(token); } #endif /** * Decode a token * \tparam Decode is callable, taking a string_type and returns a string_type. * It should ensure the padding of the input and then base64url decode and * return the results. * \param token Token to decode * \param decode The token to parse * \return Decoded token * \throw std::invalid_argument Token is not in correct format * \throw std::runtime_error Base64 decoding failed or invalid json */ template decoded_jwt decode(const std::string& token, Decode decode) { return decoded_jwt(token, decode); } /** * Parse a jwk * \param token JWK Token to parse * \return Parsed JWK * \throw std::runtime_error Token is not in correct format */ inline jwk parse_jwk(const traits::open_source_parsers_jsoncpp::string_type& token) { return jwk(token); } /** * Parse a jwks * \param token JWKs Token to parse * \return Parsed JWKs * \throw std::runtime_error Token is not in correct format */ inline jwks parse_jwks(const traits::open_source_parsers_jsoncpp::string_type& token) { return jwks(token); } /** * This type is the specialization of the \ref verify_ops::verify_context class which * uses the standard template types. */ using verify_context = verify_ops::verify_context; } // namespace jwt #endif // JWT_CPP_OPEN_SOURCE_PARSERS_JSONCPP_DEFAULTS_H scitokens-cpp-1.3.0/vendor/jwt-cpp/include/jwt-cpp/traits/open-source-parsers-jsoncpp/traits.h000066400000000000000000000105721513647275500325640ustar00rootroot00000000000000#ifndef JWT_CPP_JSONCPP_TRAITS_H #define JWT_CPP_JSONCPP_TRAITS_H #include "jwt-cpp/jwt.h" #include "json/json.h" namespace jwt { /** * \brief Namespace containing all the json_trait implementations for a jwt::basic_claim. */ namespace traits { /// basic_claim's JSON trait implementation for jsoncpp struct open_source_parsers_jsoncpp { using value_type = Json::Value; using string_type = std::string; class array_type : public Json::Value { public: using value_type = Json::Value; array_type() = default; array_type(const array_type&) = default; explicit array_type(const Json::Value& o) : Json::Value(o) {} array_type(array_type&&) = default; explicit array_type(Json::Value&& o) : Json::Value(o) {} template array_type(Iterator begin, Iterator end) { for (Iterator it = begin; it != end; ++it) { Json::Value value; value = *it; this->append(value); } } ~array_type() = default; array_type& operator=(const array_type& o) = default; array_type& operator=(array_type&& o) noexcept = default; value_type const& front() const { return this->operator[](0U); } }; using number_type = double; using integer_type = Json::Value::Int; using boolean_type = bool; class object_type : public Json::Value { public: using key_type = std::string; using mapped_type = Json::Value; using size_type = size_t; object_type() = default; object_type(const object_type&) = default; explicit object_type(const Json::Value& o) : Json::Value(o) {} object_type(object_type&&) = default; explicit object_type(Json::Value&& o) : Json::Value(o) {} ~object_type() = default; object_type& operator=(const object_type& o) = default; object_type& operator=(object_type&& o) noexcept = default; // Add missing C++11 element access const mapped_type& at(const key_type& key) const { Json::Value const* found = find(key.data(), key.data() + key.length()); if (!found) throw std::out_of_range("invalid key"); return *found; } size_type count(const key_type& key) const { return this->isMember(key) ? 1 : 0; } }; // Translation between the implementation notion of type, to the jwt::json::type equivilant static jwt::json::type get_type(const value_type& val) { using jwt::json::type; if (val.isArray()) return type::array; else if (val.isString()) return type::string; // Order is important https://github.com/Thalhammer/jwt-cpp/pull/320#issuecomment-1865322511 else if (val.isInt()) return type::integer; else if (val.isNumeric()) return type::number; else if (val.isBool()) return type::boolean; else if (val.isObject()) return type::object; throw std::logic_error("invalid type"); } static integer_type as_integer(const value_type& val) { switch (val.type()) { case Json::intValue: return val.asInt64(); case Json::uintValue: return static_cast(val.asUInt64()); default: throw std::bad_cast(); } } static boolean_type as_boolean(const value_type& val) { if (!val.isBool()) throw std::bad_cast(); return val.asBool(); } static number_type as_number(const value_type& val) { if (!val.isNumeric()) throw std::bad_cast(); return val.asDouble(); } static string_type as_string(const value_type& val) { if (!val.isString()) throw std::bad_cast(); return val.asString(); } static object_type as_object(const value_type& val) { if (!val.isObject()) throw std::bad_cast(); return object_type(val); } static array_type as_array(const value_type& val) { if (!val.isArray()) throw std::bad_cast(); return array_type(val); } static bool parse(value_type& val, string_type str) { Json::CharReaderBuilder builder; const std::unique_ptr reader(builder.newCharReader()); return reader->parse(reinterpret_cast(str.c_str()), reinterpret_cast(str.c_str() + str.size()), &val, nullptr); } static string_type serialize(const value_type& val) { Json::StreamWriterBuilder builder; builder["commentStyle"] = "None"; builder["indentation"] = ""; std::unique_ptr writer(builder.newStreamWriter()); return Json::writeString(builder, val); } }; } // namespace traits } // namespace jwt #endif scitokens-cpp-1.3.0/vendor/jwt-cpp/include/picojson/000077500000000000000000000000001513647275500224445ustar00rootroot00000000000000scitokens-cpp-1.3.0/vendor/jwt-cpp/include/picojson/picojson.h000066400000000000000000001023531513647275500244450ustar00rootroot00000000000000/* * Copyright 2009-2010 Cybozu Labs, Inc. * Copyright 2011-2014 Kazuho Oku * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ #ifndef picojson_h #define picojson_h #include #include #include #include #include #include #include #include #include #include #include #include #include // for isnan/isinf #if __cplusplus >= 201103L #include #else extern "C" { #ifdef _MSC_VER #include #elif defined(__INTEL_COMPILER) #include #else #include #endif } #endif #ifndef PICOJSON_USE_RVALUE_REFERENCE #if (defined(__cpp_rvalue_references) && __cpp_rvalue_references >= 200610) || (defined(_MSC_VER) && _MSC_VER >= 1600) #define PICOJSON_USE_RVALUE_REFERENCE 1 #else #define PICOJSON_USE_RVALUE_REFERENCE 0 #endif #endif // PICOJSON_USE_RVALUE_REFERENCE #ifndef PICOJSON_NOEXCEPT #if PICOJSON_USE_RVALUE_REFERENCE #define PICOJSON_NOEXCEPT noexcept #else #define PICOJSON_NOEXCEPT throw() #endif #endif // experimental support for int64_t (see README.mkdn for detail) #ifdef PICOJSON_USE_INT64 #define __STDC_FORMAT_MACROS #include #if __cplusplus >= 201103L #include #else extern "C" { #include } #endif #endif // to disable the use of localeconv(3), set PICOJSON_USE_LOCALE to 0 #ifndef PICOJSON_USE_LOCALE #define PICOJSON_USE_LOCALE 1 #endif #if PICOJSON_USE_LOCALE extern "C" { #include } #endif #ifndef PICOJSON_ASSERT #define PICOJSON_ASSERT(e) \ do { \ if (!(e)) \ throw std::runtime_error(#e); \ } while (0) #endif #ifdef _MSC_VER #define SNPRINTF _snprintf_s #pragma warning(push) #pragma warning(disable : 4244) // conversion from int to char #pragma warning(disable : 4127) // conditional expression is constant #pragma warning(disable : 4702) // unreachable code #pragma warning(disable : 4706) // assignment within conditional expression #else #define SNPRINTF snprintf #endif namespace picojson { enum { null_type, boolean_type, number_type, string_type, array_type, object_type #ifdef PICOJSON_USE_INT64 , int64_type #endif }; enum { INDENT_WIDTH = 2, DEFAULT_MAX_DEPTHS = 100 }; struct null {}; class value { public: typedef std::vector array; typedef std::map object; union _storage { bool boolean_; double number_; #ifdef PICOJSON_USE_INT64 int64_t int64_; #endif std::string *string_; array *array_; object *object_; }; protected: int type_; _storage u_; public: value(); value(int type, bool); explicit value(bool b); #ifdef PICOJSON_USE_INT64 explicit value(int64_t i); #endif explicit value(double n); explicit value(const std::string &s); explicit value(const array &a); explicit value(const object &o); #if PICOJSON_USE_RVALUE_REFERENCE explicit value(std::string &&s); explicit value(array &&a); explicit value(object &&o); #endif explicit value(const char *s); value(const char *s, size_t len); ~value(); value(const value &x); value &operator=(const value &x); #if PICOJSON_USE_RVALUE_REFERENCE value(value &&x) PICOJSON_NOEXCEPT; value &operator=(value &&x) PICOJSON_NOEXCEPT; #endif void swap(value &x) PICOJSON_NOEXCEPT; template bool is() const; template const T &get() const; template T &get(); template void set(const T &); #if PICOJSON_USE_RVALUE_REFERENCE template void set(T &&); #endif bool evaluate_as_boolean() const; const value &get(const size_t idx) const; const value &get(const std::string &key) const; value &get(const size_t idx); value &get(const std::string &key); bool contains(const size_t idx) const; bool contains(const std::string &key) const; std::string to_str() const; template void serialize(Iter os, bool prettify = false) const; std::string serialize(bool prettify = false) const; private: template value(const T *); // intentionally defined to block implicit conversion of pointer to bool template static void _indent(Iter os, int indent); template void _serialize(Iter os, int indent) const; std::string _serialize(int indent) const; void clear(); }; typedef value::array array; typedef value::object object; inline value::value() : type_(null_type), u_() { } inline value::value(int type, bool) : type_(type), u_() { switch (type) { #define INIT(p, v) \ case p##type: \ u_.p = v; \ break INIT(boolean_, false); INIT(number_, 0.0); #ifdef PICOJSON_USE_INT64 INIT(int64_, 0); #endif INIT(string_, new std::string()); INIT(array_, new array()); INIT(object_, new object()); #undef INIT default: break; } } inline value::value(bool b) : type_(boolean_type), u_() { u_.boolean_ = b; } #ifdef PICOJSON_USE_INT64 inline value::value(int64_t i) : type_(int64_type), u_() { u_.int64_ = i; } #endif inline value::value(double n) : type_(number_type), u_() { if ( #ifdef _MSC_VER !_finite(n) #elif __cplusplus >= 201103L std::isnan(n) || std::isinf(n) #else isnan(n) || isinf(n) #endif ) { throw std::overflow_error(""); } u_.number_ = n; } inline value::value(const std::string &s) : type_(string_type), u_() { u_.string_ = new std::string(s); } inline value::value(const array &a) : type_(array_type), u_() { u_.array_ = new array(a); } inline value::value(const object &o) : type_(object_type), u_() { u_.object_ = new object(o); } #if PICOJSON_USE_RVALUE_REFERENCE inline value::value(std::string &&s) : type_(string_type), u_() { u_.string_ = new std::string(std::move(s)); } inline value::value(array &&a) : type_(array_type), u_() { u_.array_ = new array(std::move(a)); } inline value::value(object &&o) : type_(object_type), u_() { u_.object_ = new object(std::move(o)); } #endif inline value::value(const char *s) : type_(string_type), u_() { u_.string_ = new std::string(s); } inline value::value(const char *s, size_t len) : type_(string_type), u_() { u_.string_ = new std::string(s, len); } inline void value::clear() { switch (type_) { #define DEINIT(p) \ case p##type: \ delete u_.p; \ break DEINIT(string_); DEINIT(array_); DEINIT(object_); #undef DEINIT default: break; } } inline value::~value() { clear(); } inline value::value(const value &x) : type_(x.type_), u_() { switch (type_) { #define INIT(p, v) \ case p##type: \ u_.p = v; \ break INIT(string_, new std::string(*x.u_.string_)); INIT(array_, new array(*x.u_.array_)); INIT(object_, new object(*x.u_.object_)); #undef INIT default: u_ = x.u_; break; } } inline value &value::operator=(const value &x) { if (this != &x) { value t(x); swap(t); } return *this; } #if PICOJSON_USE_RVALUE_REFERENCE inline value::value(value &&x) PICOJSON_NOEXCEPT : type_(null_type), u_() { swap(x); } inline value &value::operator=(value &&x) PICOJSON_NOEXCEPT { swap(x); return *this; } #endif inline void value::swap(value &x) PICOJSON_NOEXCEPT { std::swap(type_, x.type_); std::swap(u_, x.u_); } #define IS(ctype, jtype) \ template <> inline bool value::is() const { \ return type_ == jtype##_type; \ } IS(null, null) IS(bool, boolean) #ifdef PICOJSON_USE_INT64 IS(int64_t, int64) #endif IS(std::string, string) IS(array, array) IS(object, object) #undef IS template <> inline bool value::is() const { return type_ == number_type #ifdef PICOJSON_USE_INT64 || type_ == int64_type #endif ; } #define GET(ctype, var) \ template <> inline const ctype &value::get() const { \ PICOJSON_ASSERT("type mismatch! call is() before get()" && is()); \ return var; \ } \ template <> inline ctype &value::get() { \ PICOJSON_ASSERT("type mismatch! call is() before get()" && is()); \ return var; \ } GET(bool, u_.boolean_) GET(std::string, *u_.string_) GET(array, *u_.array_) GET(object, *u_.object_) #ifdef PICOJSON_USE_INT64 GET(double, (type_ == int64_type && (const_cast(this)->type_ = number_type, (const_cast(this)->u_.number_ = u_.int64_)), u_.number_)) GET(int64_t, u_.int64_) #else GET(double, u_.number_) #endif #undef GET #define SET(ctype, jtype, setter) \ template <> inline void value::set(const ctype &_val) { \ clear(); \ type_ = jtype##_type; \ setter \ } SET(bool, boolean, u_.boolean_ = _val;) SET(std::string, string, u_.string_ = new std::string(_val);) SET(array, array, u_.array_ = new array(_val);) SET(object, object, u_.object_ = new object(_val);) SET(double, number, u_.number_ = _val;) #ifdef PICOJSON_USE_INT64 SET(int64_t, int64, u_.int64_ = _val;) #endif #undef SET #if PICOJSON_USE_RVALUE_REFERENCE #define MOVESET(ctype, jtype, setter) \ template <> inline void value::set(ctype && _val) { \ clear(); \ type_ = jtype##_type; \ setter \ } MOVESET(std::string, string, u_.string_ = new std::string(std::move(_val));) MOVESET(array, array, u_.array_ = new array(std::move(_val));) MOVESET(object, object, u_.object_ = new object(std::move(_val));) #undef MOVESET #endif inline bool value::evaluate_as_boolean() const { switch (type_) { case null_type: return false; case boolean_type: return u_.boolean_; case number_type: return u_.number_ != 0; #ifdef PICOJSON_USE_INT64 case int64_type: return u_.int64_ != 0; #endif case string_type: return !u_.string_->empty(); default: return true; } } inline const value &value::get(const size_t idx) const { static value s_null; PICOJSON_ASSERT(is()); return idx < u_.array_->size() ? (*u_.array_)[idx] : s_null; } inline value &value::get(const size_t idx) { static value s_null; PICOJSON_ASSERT(is()); return idx < u_.array_->size() ? (*u_.array_)[idx] : s_null; } inline const value &value::get(const std::string &key) const { static value s_null; PICOJSON_ASSERT(is()); object::const_iterator i = u_.object_->find(key); return i != u_.object_->end() ? i->second : s_null; } inline value &value::get(const std::string &key) { static value s_null; PICOJSON_ASSERT(is()); object::iterator i = u_.object_->find(key); return i != u_.object_->end() ? i->second : s_null; } inline bool value::contains(const size_t idx) const { PICOJSON_ASSERT(is()); return idx < u_.array_->size(); } inline bool value::contains(const std::string &key) const { PICOJSON_ASSERT(is()); object::const_iterator i = u_.object_->find(key); return i != u_.object_->end(); } inline std::string value::to_str() const { switch (type_) { case null_type: return "null"; case boolean_type: return u_.boolean_ ? "true" : "false"; #ifdef PICOJSON_USE_INT64 case int64_type: { char buf[sizeof("-9223372036854775808")]; SNPRINTF(buf, sizeof(buf), "%" PRId64, u_.int64_); return buf; } #endif case number_type: { char buf[256]; double tmp; SNPRINTF(buf, sizeof(buf), fabs(u_.number_) < (1ULL << 53) && modf(u_.number_, &tmp) == 0 ? "%.f" : "%.17g", u_.number_); #if PICOJSON_USE_LOCALE char *decimal_point = localeconv()->decimal_point; if (strcmp(decimal_point, ".") != 0) { size_t decimal_point_len = strlen(decimal_point); for (char *p = buf; *p != '\0'; ++p) { if (strncmp(p, decimal_point, decimal_point_len) == 0) { return std::string(buf, p) + "." + (p + decimal_point_len); } } } #endif return buf; } case string_type: return *u_.string_; case array_type: return "array"; case object_type: return "object"; default: PICOJSON_ASSERT(0); #ifdef _MSC_VER __assume(0); #endif } return std::string(); } template void copy(const std::string &s, Iter oi) { std::copy(s.begin(), s.end(), oi); } template struct serialize_str_char { Iter oi; void operator()(char c) { switch (c) { #define MAP(val, sym) \ case val: \ copy(sym, oi); \ break MAP('"', "\\\""); MAP('\\', "\\\\"); MAP('/', "\\/"); MAP('\b', "\\b"); MAP('\f', "\\f"); MAP('\n', "\\n"); MAP('\r', "\\r"); MAP('\t', "\\t"); #undef MAP default: if (static_cast(c) < 0x20 || c == 0x7f) { char buf[7]; SNPRINTF(buf, sizeof(buf), "\\u%04x", c & 0xff); copy(buf, buf + 6, oi); } else { *oi++ = c; } break; } } }; template void serialize_str(const std::string &s, Iter oi) { *oi++ = '"'; serialize_str_char process_char = {oi}; std::for_each(s.begin(), s.end(), process_char); *oi++ = '"'; } template void value::serialize(Iter oi, bool prettify) const { return _serialize(oi, prettify ? 0 : -1); } inline std::string value::serialize(bool prettify) const { return _serialize(prettify ? 0 : -1); } template void value::_indent(Iter oi, int indent) { *oi++ = '\n'; for (int i = 0; i < indent * INDENT_WIDTH; ++i) { *oi++ = ' '; } } template void value::_serialize(Iter oi, int indent) const { switch (type_) { case string_type: serialize_str(*u_.string_, oi); break; case array_type: { *oi++ = '['; if (indent != -1) { ++indent; } for (array::const_iterator i = u_.array_->begin(); i != u_.array_->end(); ++i) { if (i != u_.array_->begin()) { *oi++ = ','; } if (indent != -1) { _indent(oi, indent); } i->_serialize(oi, indent); } if (indent != -1) { --indent; if (!u_.array_->empty()) { _indent(oi, indent); } } *oi++ = ']'; break; } case object_type: { *oi++ = '{'; if (indent != -1) { ++indent; } for (object::const_iterator i = u_.object_->begin(); i != u_.object_->end(); ++i) { if (i != u_.object_->begin()) { *oi++ = ','; } if (indent != -1) { _indent(oi, indent); } serialize_str(i->first, oi); *oi++ = ':'; if (indent != -1) { *oi++ = ' '; } i->second._serialize(oi, indent); } if (indent != -1) { --indent; if (!u_.object_->empty()) { _indent(oi, indent); } } *oi++ = '}'; break; } default: copy(to_str(), oi); break; } if (indent == 0) { *oi++ = '\n'; } } inline std::string value::_serialize(int indent) const { std::string s; _serialize(std::back_inserter(s), indent); return s; } template class input { protected: Iter cur_, end_; bool consumed_; int line_; public: input(const Iter &first, const Iter &last) : cur_(first), end_(last), consumed_(false), line_(1) { } int getc() { if (consumed_) { if (*cur_ == '\n') { ++line_; } ++cur_; } if (cur_ == end_) { consumed_ = false; return -1; } consumed_ = true; return *cur_ & 0xff; } void ungetc() { consumed_ = false; } Iter cur() const { if (consumed_) { input *self = const_cast *>(this); self->consumed_ = false; ++self->cur_; } return cur_; } int line() const { return line_; } void skip_ws() { while (1) { int ch = getc(); if (!(ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r')) { ungetc(); break; } } } bool expect(const int expected) { skip_ws(); if (getc() != expected) { ungetc(); return false; } return true; } bool match(const std::string &pattern) { for (std::string::const_iterator pi(pattern.begin()); pi != pattern.end(); ++pi) { if (getc() != *pi) { ungetc(); return false; } } return true; } }; template inline int _parse_quadhex(input &in) { int uni_ch = 0, hex; for (int i = 0; i < 4; i++) { if ((hex = in.getc()) == -1) { return -1; } if ('0' <= hex && hex <= '9') { hex -= '0'; } else if ('A' <= hex && hex <= 'F') { hex -= 'A' - 0xa; } else if ('a' <= hex && hex <= 'f') { hex -= 'a' - 0xa; } else { in.ungetc(); return -1; } uni_ch = uni_ch * 16 + hex; } return uni_ch; } template inline bool _parse_codepoint(String &out, input &in) { int uni_ch; if ((uni_ch = _parse_quadhex(in)) == -1) { return false; } if (0xd800 <= uni_ch && uni_ch <= 0xdfff) { if (0xdc00 <= uni_ch) { // a second 16-bit of a surrogate pair appeared return false; } // first 16-bit of surrogate pair, get the next one if (in.getc() != '\\' || in.getc() != 'u') { in.ungetc(); return false; } int second = _parse_quadhex(in); if (!(0xdc00 <= second && second <= 0xdfff)) { return false; } uni_ch = ((uni_ch - 0xd800) << 10) | ((second - 0xdc00) & 0x3ff); uni_ch += 0x10000; } if (uni_ch < 0x80) { out.push_back(static_cast(uni_ch)); } else { if (uni_ch < 0x800) { out.push_back(static_cast(0xc0 | (uni_ch >> 6))); } else { if (uni_ch < 0x10000) { out.push_back(static_cast(0xe0 | (uni_ch >> 12))); } else { out.push_back(static_cast(0xf0 | (uni_ch >> 18))); out.push_back(static_cast(0x80 | ((uni_ch >> 12) & 0x3f))); } out.push_back(static_cast(0x80 | ((uni_ch >> 6) & 0x3f))); } out.push_back(static_cast(0x80 | (uni_ch & 0x3f))); } return true; } template inline bool _parse_string(String &out, input &in) { while (1) { int ch = in.getc(); if (ch < ' ') { in.ungetc(); return false; } else if (ch == '"') { return true; } else if (ch == '\\') { if ((ch = in.getc()) == -1) { return false; } switch (ch) { #define MAP(sym, val) \ case sym: \ out.push_back(val); \ break MAP('"', '\"'); MAP('\\', '\\'); MAP('/', '/'); MAP('b', '\b'); MAP('f', '\f'); MAP('n', '\n'); MAP('r', '\r'); MAP('t', '\t'); #undef MAP case 'u': if (!_parse_codepoint(out, in)) { return false; } break; default: return false; } } else { out.push_back(static_cast(ch)); } } return false; } template inline bool _parse_array(Context &ctx, input &in) { if (!ctx.parse_array_start()) { return false; } size_t idx = 0; if (in.expect(']')) { return ctx.parse_array_stop(idx); } do { if (!ctx.parse_array_item(in, idx)) { return false; } idx++; } while (in.expect(',')); return in.expect(']') && ctx.parse_array_stop(idx); } template inline bool _parse_object(Context &ctx, input &in) { if (!ctx.parse_object_start()) { return false; } if (in.expect('}')) { return ctx.parse_object_stop(); } do { std::string key; if (!in.expect('"') || !_parse_string(key, in) || !in.expect(':')) { return false; } if (!ctx.parse_object_item(in, key)) { return false; } } while (in.expect(',')); return in.expect('}') && ctx.parse_object_stop(); } template inline std::string _parse_number(input &in) { std::string num_str; while (1) { int ch = in.getc(); if (('0' <= ch && ch <= '9') || ch == '+' || ch == '-' || ch == 'e' || ch == 'E') { num_str.push_back(static_cast(ch)); } else if (ch == '.') { #if PICOJSON_USE_LOCALE num_str += localeconv()->decimal_point; #else num_str.push_back('.'); #endif } else { in.ungetc(); break; } } return num_str; } template inline bool _parse(Context &ctx, input &in) { in.skip_ws(); int ch = in.getc(); switch (ch) { #define IS(ch, text, op) \ case ch: \ if (in.match(text) && op) { \ return true; \ } else { \ return false; \ } IS('n', "ull", ctx.set_null()); IS('f', "alse", ctx.set_bool(false)); IS('t', "rue", ctx.set_bool(true)); #undef IS case '"': return ctx.parse_string(in); case '[': return _parse_array(ctx, in); case '{': return _parse_object(ctx, in); default: if (('0' <= ch && ch <= '9') || ch == '-') { double f; char *endp; in.ungetc(); std::string num_str(_parse_number(in)); if (num_str.empty()) { return false; } #ifdef PICOJSON_USE_INT64 { errno = 0; intmax_t ival = strtoimax(num_str.c_str(), &endp, 10); if (errno == 0 && std::numeric_limits::min() <= ival && ival <= std::numeric_limits::max() && endp == num_str.c_str() + num_str.size()) { ctx.set_int64(ival); return true; } } #endif f = strtod(num_str.c_str(), &endp); if (endp == num_str.c_str() + num_str.size()) { ctx.set_number(f); return true; } return false; } break; } in.ungetc(); return false; } class deny_parse_context { public: bool set_null() { return false; } bool set_bool(bool) { return false; } #ifdef PICOJSON_USE_INT64 bool set_int64(int64_t) { return false; } #endif bool set_number(double) { return false; } template bool parse_string(input &) { return false; } bool parse_array_start() { return false; } template bool parse_array_item(input &, size_t) { return false; } bool parse_array_stop(size_t) { return false; } bool parse_object_start() { return false; } template bool parse_object_item(input &, const std::string &) { return false; } }; class default_parse_context { protected: value *out_; size_t depths_; public: default_parse_context(value *out, size_t depths = DEFAULT_MAX_DEPTHS) : out_(out), depths_(depths) { } bool set_null() { *out_ = value(); return true; } bool set_bool(bool b) { *out_ = value(b); return true; } #ifdef PICOJSON_USE_INT64 bool set_int64(int64_t i) { *out_ = value(i); return true; } #endif bool set_number(double f) { *out_ = value(f); return true; } template bool parse_string(input &in) { *out_ = value(string_type, false); return _parse_string(out_->get(), in); } bool parse_array_start() { if (depths_ == 0) return false; --depths_; *out_ = value(array_type, false); return true; } template bool parse_array_item(input &in, size_t) { array &a = out_->get(); a.push_back(value()); default_parse_context ctx(&a.back(), depths_); return _parse(ctx, in); } bool parse_array_stop(size_t) { ++depths_; return true; } bool parse_object_start() { if (depths_ == 0) return false; *out_ = value(object_type, false); return true; } template bool parse_object_item(input &in, const std::string &key) { object &o = out_->get(); default_parse_context ctx(&o[key], depths_); return _parse(ctx, in); } bool parse_object_stop() { ++depths_; return true; } private: default_parse_context(const default_parse_context &); default_parse_context &operator=(const default_parse_context &); }; class null_parse_context { protected: size_t depths_; public: struct dummy_str { void push_back(int) { } }; public: null_parse_context(size_t depths = DEFAULT_MAX_DEPTHS) : depths_(depths) { } bool set_null() { return true; } bool set_bool(bool) { return true; } #ifdef PICOJSON_USE_INT64 bool set_int64(int64_t) { return true; } #endif bool set_number(double) { return true; } template bool parse_string(input &in) { dummy_str s; return _parse_string(s, in); } bool parse_array_start() { if (depths_ == 0) return false; --depths_; return true; } template bool parse_array_item(input &in, size_t) { return _parse(*this, in); } bool parse_array_stop(size_t) { ++depths_; return true; } bool parse_object_start() { if (depths_ == 0) return false; --depths_; return true; } template bool parse_object_item(input &in, const std::string &) { ++depths_; return _parse(*this, in); } bool parse_object_stop() { return true; } private: null_parse_context(const null_parse_context &); null_parse_context &operator=(const null_parse_context &); }; // obsolete, use the version below template inline std::string parse(value &out, Iter &pos, const Iter &last) { std::string err; pos = parse(out, pos, last, &err); return err; } template inline Iter _parse(Context &ctx, const Iter &first, const Iter &last, std::string *err) { input in(first, last); if (!_parse(ctx, in) && err != NULL) { char buf[64]; SNPRINTF(buf, sizeof(buf), "syntax error at line %d near: ", in.line()); *err = buf; while (1) { int ch = in.getc(); if (ch == -1 || ch == '\n') { break; } else if (ch >= ' ') { err->push_back(static_cast(ch)); } } } return in.cur(); } template inline Iter parse(value &out, const Iter &first, const Iter &last, std::string *err) { default_parse_context ctx(&out); return _parse(ctx, first, last, err); } inline std::string parse(value &out, const std::string &s) { std::string err; parse(out, s.begin(), s.end(), &err); return err; } inline std::string parse(value &out, std::istream &is) { std::string err; parse(out, std::istreambuf_iterator(is.rdbuf()), std::istreambuf_iterator(), &err); return err; } template struct last_error_t { static std::string s; }; template std::string last_error_t::s; inline void set_last_error(const std::string &s) { last_error_t::s = s; } inline const std::string &get_last_error() { return last_error_t::s; } inline bool operator==(const value &x, const value &y) { if (x.is()) return y.is(); #define PICOJSON_CMP(type) \ if (x.is()) \ return y.is() && x.get() == y.get() PICOJSON_CMP(bool); PICOJSON_CMP(double); PICOJSON_CMP(std::string); PICOJSON_CMP(array); PICOJSON_CMP(object); #undef PICOJSON_CMP PICOJSON_ASSERT(0); #ifdef _MSC_VER __assume(0); #endif return false; } inline bool operator!=(const value &x, const value &y) { return !(x == y); } } #if !PICOJSON_USE_RVALUE_REFERENCE namespace std { template <> inline void swap(picojson::value &x, picojson::value &y) { x.swap(y); } } #endif inline std::istream &operator>>(std::istream &is, picojson::value &x) { picojson::set_last_error(std::string()); const std::string err(picojson::parse(x, is)); if (!err.empty()) { picojson::set_last_error(err); is.setstate(std::ios::failbit); } return is; } inline std::ostream &operator<<(std::ostream &os, const picojson::value &x) { x.serialize(std::ostream_iterator(os)); return os; } #ifdef _MSC_VER #pragma warning(pop) #endif #endif