diff --git a/.github/workflows/ci-buster.yml b/.github/workflows/ci-bookworm.yml similarity index 94% rename from .github/workflows/ci-buster.yml rename to .github/workflows/ci-bookworm.yml index d7af2cfb88..91b5ccebe4 100644 --- a/.github/workflows/ci-buster.yml +++ b/.github/workflows/ci-bookworm.yml @@ -1,4 +1,4 @@ -name: CI Debian Buster +name: CI Debian Bookworm on: push: @@ -30,14 +30,14 @@ jobs: - crypto_backend: gnutls jwt_backend: jwt_cpp runs-on: ubuntu-latest - container: debian:buster + container: debian:bookworm steps: - name: Update packages run: apt-get -qq update && apt-get -qq upgrade - name: Install git run: apt-get -qq install git - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: submodules: recursive - name: Install dependencies diff --git a/.github/workflows/ci-bullseye.yml b/.github/workflows/ci-bullseye.yml index 5cf07a39f3..d60c96a647 100644 --- a/.github/workflows/ci-bullseye.yml +++ b/.github/workflows/ci-bullseye.yml @@ -37,7 +37,7 @@ jobs: run: apt-get -qq update && apt-get -qq upgrade - name: Install git run: apt-get -qq install git - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: submodules: recursive - name: Install dependencies diff --git a/.github/workflows/ci-centos-7.yml b/.github/workflows/ci-centos-7.yml deleted file mode 100644 index 39973d05a1..0000000000 --- a/.github/workflows/ci-centos-7.yml +++ /dev/null @@ -1,37 +0,0 @@ -name: CI CentOS 7 - -on: - push: - branches: - - '*' - pull_request: - branches: [ master ] - -permissions: - contents: read - packages: read - -jobs: - build: - strategy: - matrix: - crypto_backend: [ gnutls, openssl ] - jwt_backend: [ libjwt, jwt_cpp ] - exclude: - - crypto_backend: gnutls - jwt_backend: jwt_cpp - - crypto_backend: gnutls - jwt_backend: libjwt - runs-on: ubuntu-latest - container: ghcr.io/dougnazar/centos7-gcc8-zm:latest - - steps: - - uses: actions/checkout@v3 - with: - submodules: recursive - - name: Prepare - run: mkdir build - - name: Configure - run: source /usr/bin/scl_source enable devtoolset-8 && cd build && cmake3 --version && cmake3 .. -DBUILD_MAN=0 -DENABLE_WERROR=1 -DZM_CRYPTO_BACKEND=${{ matrix.crypto_backend }} -DZM_JWT_BACKEND=${{ matrix.jwt_backend }} - - name: Build - run: source /usr/bin/scl_source enable devtoolset-8 && cd build && make -j3 | grep --line-buffered -Ev '^(cp lib\/|Installing.+\.pm)' && (exit ${PIPESTATUS[0]}) diff --git a/.github/workflows/ci-centos-8.yml b/.github/workflows/ci-centos-8.yml index 09c04a72c8..5d04f16694 100644 --- a/.github/workflows/ci-centos-8.yml +++ b/.github/workflows/ci-centos-8.yml @@ -29,7 +29,7 @@ jobs: run: yum -y install "dnf-command(config-manager)" https://mirrors.rpmfusion.org/free/el/rpmfusion-free-release-8.noarch.rpm https://dl.fedoraproject.org/pub/epel/epel-release-latest-8.noarch.rpm && yum config-manager --set-enabled powertools - name: Install git run: yum -y install git - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: submodules: recursive - name: Install dependencies diff --git a/.github/workflows/ci-eslint.yml b/.github/workflows/ci-eslint.yml index 6a7b640f41..f64a351b08 100644 --- a/.github/workflows/ci-eslint.yml +++ b/.github/workflows/ci-eslint.yml @@ -15,7 +15,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: submodules: recursive - name: Install ESLint diff --git a/.github/workflows/ci-bionic.yml b/.github/workflows/ci-focal.yml similarity index 94% rename from .github/workflows/ci-bionic.yml rename to .github/workflows/ci-focal.yml index 13c22749d1..51363bd538 100644 --- a/.github/workflows/ci-bionic.yml +++ b/.github/workflows/ci-focal.yml @@ -1,4 +1,4 @@ -name: CI Ubuntu Bionic (18.04) +name: CI Ubuntu Focal (20.04) on: push: @@ -24,7 +24,7 @@ jobs: crypto_package: libssl-dev jwt_package: libjwt-dev runs-on: ubuntu-latest - container: ubuntu:bionic + container: ubuntu:focal steps: - name: Update packages @@ -34,7 +34,7 @@ jobs: add-apt-repository ppa:git-core/ppa apt-get -qq update apt-get -qq install git - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: submodules: recursive - name: Install dependencies diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index e0d3475a6b..0149f48c37 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -37,7 +37,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: # We must fetch at least the immediate parents so that if this is # a pull request then we can checkout the head. diff --git a/.github/workflows/create-packages.yml b/.github/workflows/create-packages.yml index fa228e25e6..5470451b49 100644 --- a/.github/workflows/create-packages.yml +++ b/.github/workflows/create-packages.yml @@ -27,7 +27,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: fetch-depth: '0' submodules: recursive diff --git a/.github/workflows/depsreview.yaml b/.github/workflows/depsreview.yaml index da99d0c548..b9945082d5 100644 --- a/.github/workflows/depsreview.yaml +++ b/.github/workflows/depsreview.yaml @@ -9,6 +9,6 @@ jobs: runs-on: ubuntu-latest steps: - name: 'Checkout Repository' - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: 'Dependency Review' uses: actions/dependency-review-action@v3 diff --git a/.github/workflows/release-packages.yml b/.github/workflows/release-packages.yml index d682f2e3bf..a74d3bbdd6 100644 --- a/.github/workflows/release-packages.yml +++ b/.github/workflows/release-packages.yml @@ -19,7 +19,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: fetch-depth: '0' submodules: recursive diff --git a/CMakeLists.txt b/CMakeLists.txt index df2effa0a6..167efc7771 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -299,16 +299,6 @@ check_type_size("ucontext_t" HAVE_UCONTEXT_T) # *** LIBRARY CHECKS *** -if(UNIX) - include(CheckLibraryExists) - CHECK_LIBRARY_EXISTS(rt clock_gettime "time.h" HAVE_CLOCK_GETTIME) - if(NOT HAVE_CLOCK_GETTIME) - message(FATAL_ERROR "clock_gettime not found") - else() - list(APPEND ZM_BIN_LIBS "-lrt") - endif() -endif() - # zlib find_package(ZLIB) if(ZLIB_FOUND) diff --git a/dep/jwt-cpp/CMakeLists.txt b/dep/jwt-cpp/CMakeLists.txt index 81ddc84a11..fc2e612739 100644 --- a/dep/jwt-cpp/CMakeLists.txt +++ b/dep/jwt-cpp/CMakeLists.txt @@ -1,6 +1,157 @@ +cmake_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) + +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_ENABLE_COVERAGE "Enable code coverage testing" OFF) +option(JWT_ENABLE_FUZZING "Enable fuzz testing" OFF) + +option(JWT_EXTERNAL_PICOJSON "Use find_package() to locate picojson, provided to integrate with package managers" OFF) +option(JWT_DISABLE_BASE64 "Do not include the base64 implementation from this library" OFF) +option(JWT_DISABLE_PICOJSON "Do not provide the picojson template specialiaze" 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) + +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(JWT_EXTERNAL_PICOJSON) + find_package(picojson 1.3.0 REQUIRED) +endif() + +find_package(nlohmann_json CONFIG) + +if(NOT nlohmann_json_FOUND) + message(STATUS "jwt-cpp: using FetchContent for nlohmann json") + 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() + +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) +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}) + target_compile_definitions(jwt-cpp INTERFACE OPENSSL_EXTRA OPENSSL_ALL) +endif() + +if(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} PATH_VARS JWT_EXTERNAL_PICOJSON JWT_SSL_LIBRARY) +write_basic_package_version_file(${CMAKE_CURRENT_BINARY_DIR}/jwt-cpp-config-version.cmake VERSION 0.6.0 + 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) + add_subdirectory(tests) +endif() -target_include_directories(jwt-cpp - INTERFACE - ${CMAKE_CURRENT_SOURCE_DIR}/include) +if(JWT_ENABLE_FUZZING) + add_subdirectory(tests/fuzz) +endif() diff --git a/dep/jwt-cpp/Doxyfile b/dep/jwt-cpp/Doxyfile index 0e912fd797..41343d7365 100644 --- a/dep/jwt-cpp/Doxyfile +++ b/dep/jwt-cpp/Doxyfile @@ -32,19 +32,19 @@ DOXYFILE_ENCODING = UTF-8 # title of most generated pages and in a few other places. # The default value is: My Project. -PROJECT_NAME = "JWT-C++" +PROJECT_NAME = JWT-CPP # The PROJECT_NUMBER tag can be used to enter a project or revision number. This # could be handy for archiving the generated documentation or if some version # control system is used. -PROJECT_NUMBER = 0.5.0 +PROJECT_NUMBER = v0.6.0 # Using the PROJECT_BRIEF tag one can provide an optional one line description # for a project that appears at the top of each page and should give viewer a # quick idea about the purpose of the project. Keep the description short. -PROJECT_BRIEF = +PROJECT_BRIEF = "A header only library for creating and validating JSON Web Tokens (JWT) in C++" # With the PROJECT_LOGO tag one can specify a logo or an icon that is included # in the documentation. The maximum height of the logo should not exceed 55 @@ -58,7 +58,7 @@ PROJECT_LOGO = # entered, it will be relative to the location where doxygen was started. If # left blank the current directory will be used. -OUTPUT_DIRECTORY = docs +OUTPUT_DIRECTORY = doxy # If the CREATE_SUBDIRS tag is set to YES then doxygen will create 4096 sub- # directories (in 2 levels) under the output directory of each output format and @@ -790,7 +790,7 @@ WARN_LOGFILE = # spaces. See also FILE_PATTERNS and EXTENSION_MAPPING # Note: If this tag is empty the current directory is searched. -INPUT = include README.md +INPUT = include README.md docs # This tag can be used to specify the character encoding of the source files # that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses @@ -873,7 +873,7 @@ RECURSIVE = YES # Note that relative paths are relative to the directory from which doxygen is # run. -EXCLUDE = include/jwt-cpp/picojson.h +EXCLUDE = include/nlohmann/json.hpp, include/picojson/picojson.h # The EXCLUDE_SYMLINKS tag can be used to select whether or not files or # directories that are symbolic links (a Unix file system feature) are excluded @@ -889,7 +889,7 @@ EXCLUDE_SYMLINKS = NO # Note that the wildcards are matched against the file with absolute path, so to # exclude all test directories for example use the pattern */test/* -EXCLUDE_PATTERNS = *nlohmann*, *picojson* +EXCLUDE_PATTERNS = # The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names # (namespaces, classes, functions, etc.) that should be excluded from the @@ -900,7 +900,7 @@ EXCLUDE_PATTERNS = *nlohmann*, *picojson* # Note that the wildcards are matched against the file with absolute path, so to # exclude all test directories use the pattern */test/* -EXCLUDE_SYMBOLS = jwt::details +EXCLUDE_SYMBOLS = jwt::details, std # The EXAMPLE_PATH tag can be used to specify one or more files or directories # that contain example code fragments that are included (see the \include @@ -920,7 +920,7 @@ EXAMPLE_PATTERNS = * # irrespective of the value of the RECURSIVE tag. # The default value is: NO. -EXAMPLE_RECURSIVE = NO +EXAMPLE_RECURSIVE = YES # The IMAGE_PATH tag can be used to specify one or more files or directories # that contain images that are to be included in the documentation (see the @@ -1192,7 +1192,7 @@ HTML_STYLESHEET = # list). For an example see the documentation. # This tag requires that the tag GENERATE_HTML is set to YES. -HTML_EXTRA_STYLESHEET = +HTML_EXTRA_STYLESHEET = doxygen-awesome.css doxygen-awesome-sidebar-only.css .github/overrides.css # The HTML_EXTRA_FILES tag can be used to specify one or more extra images or # other source files which should be copied to the HTML output directory. Note @@ -1478,7 +1478,7 @@ DISABLE_INDEX = NO # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. -GENERATE_TREEVIEW = NO +GENERATE_TREEVIEW = YES # The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values that # doxygen will group on one line in the generated HTML documentation. @@ -2265,7 +2265,7 @@ CLASS_GRAPH = YES # The default value is: YES. # This tag requires that the tag HAVE_DOT is set to YES. -COLLABORATION_GRAPH = YES +COLLABORATION_GRAPH = NO # If the GROUP_GRAPHS tag is set to YES then doxygen will generate a graph for # groups, showing the direct groups dependencies. @@ -2350,7 +2350,7 @@ CALLER_GRAPH = NO # The default value is: YES. # This tag requires that the tag HAVE_DOT is set to YES. -GRAPHICAL_HIERARCHY = YES +GRAPHICAL_HIERARCHY = NO # If the DIRECTORY_GRAPH tag is set to YES then doxygen will show the # dependencies a directory has on other directories in a graphical way. The @@ -2376,7 +2376,7 @@ DIRECTORY_GRAPH = YES # The default value is: png. # This tag requires that the tag HAVE_DOT is set to YES. -DOT_IMAGE_FORMAT = png +DOT_IMAGE_FORMAT = svg # If DOT_IMAGE_FORMAT is set to svg, then this option can be set to YES to # enable generation of interactive SVG images that allow zooming and panning. @@ -2467,7 +2467,7 @@ MAX_DOT_GRAPH_DEPTH = 0 # The default value is: NO. # This tag requires that the tag HAVE_DOT is set to YES. -DOT_TRANSPARENT = NO +DOT_TRANSPARENT = YES # Set the DOT_MULTI_TARGETS tag to YES to allow dot to generate multiple output # files in one run (i.e. multiple -o and -T options on the command line). This diff --git a/dep/jwt-cpp/README.md b/dep/jwt-cpp/README.md index 5e39032629..9aea7c85e1 100644 --- a/dep/jwt-cpp/README.md +++ b/dep/jwt-cpp/README.md @@ -1,14 +1,18 @@ -# ![logo](https://raw.githubusercontent.com/Thalhammer/jwt-cpp/master/.github/logo.svg) +logo [![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/app/Thalhammer/jwt-cpp?utm_source=github.com&utm_medium=referral&utm_content=Thalhammer/jwt-cpp&utm_campaign=Badge_Grade_Settings) +[![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/) -[![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) + [![Stars Badge](https://img.shields.io/github/stars/Thalhammer/jwt-cpp)](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 @@ -23,27 +27,36 @@ jwt-cpp supports all the algorithms defined by the specifications. The modular d 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 | | +| HMSC | RSA | ECDSA | PSS | EdDSA | +|-------|-------|--------|-------|---------| +| HS256 | RS256 | ES256 | PS256 | Ed25519 | +| HS384 | RS384 | ES384 | PS384 | Ed448 | +| HS512 | RS512 | ES512 | PS512 | | +| | | ES256K | | | ## SSL Compatibility -In the name of flexibility and extensibility, jwt-cpp supports both [OpenSSL](https://github.com/openssl/openssl) and [LibreSSL](https://github.com/libressl-portable/portable). These are the version which are, or have been, tested: +In the name of flexibility and extensibility, jwt-cpp supports [OpenSSL](https://github.com/openssl/openssl), [LibreSSL](https://github.com/libressl-portable/portable), and [wolfSSL](https://github.com/wolfSSL/wolfssl). Read [this page](docs/ssl.md) for more details. These are the version which are currently being tested: -| OpenSSL | LibreSSL | -| -------------- | --------------- | -| [1.0.2][1.0.2] | ![3.1.5][3.1] | -| 1.1.0 | ![3.2.3][3.2] | -| [1.1.1][1.1.1] | ![3.3.1][3.3] | +| OpenSSL | LibreSSL | wolfSSL | +|-------------------|----------------|----------------| +| ![1.0.2u][o1.0.2] | ![3.3.6][l3.3] | ![5.1.1][w5.1] | +| ![1.1.0i][o1.1.0] | ![3.4.3][l3.4] | ![5.2.0][w5.2] | +| ![1.1.1q][o1.1.1] | ![3.5.3][l3.5] | ![5.3.0][w5.3] | +| ![3.0.5][o3.0] | | | -[1.0.2]: https://travis-ci.com/github/Thalhammer/jwt-cpp -[1.1.1]: https://github.com/Thalhammer/jwt-cpp/actions?query=workflow%3A%22Coverage+CI%22 -[3.1]: https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/Thalhammer/jwt-cpp/badges/libressl/3.1.5/shields.json -[3.2]: https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/Thalhammer/jwt-cpp/badges/libressl/3.2.3/shields.json -[3.3]: https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/Thalhammer/jwt-cpp/badges/libressl/3.3.1/shields.json +> ℹ️ Note: A complete list of versions tested in the past can be found [here](https://github.com/Thalhammer/jwt-cpp/tree/badges). + +[o1.0.2]: https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/Thalhammer/jwt-cpp/badges/openssl/1.0.2u/shields.json +[o1.1.0]: https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/Thalhammer/jwt-cpp/badges/openssl/1.1.0i/shields.json +[o1.1.1]: https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/Thalhammer/jwt-cpp/badges/openssl/1.1.1q/shields.json +[o3.0]: https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/Thalhammer/jwt-cpp/badges/openssl/3.0.5/shields.json +[l3.3]: https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/Thalhammer/jwt-cpp/badges/libressl/3.3.6/shields.json +[l3.4]: https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/Thalhammer/jwt-cpp/badges/libressl/3.4.3/shields.json +[l3.5]: https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/Thalhammer/jwt-cpp/badges/libressl/3.5.3/shields.json +[w5.1]: https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/Thalhammer/jwt-cpp/badges/wolfssl/5.1.1/shields.json +[w5.2]: https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/Thalhammer/jwt-cpp/badges/wolfssl/5.2.0/shields.json +[w5.3]: https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/Thalhammer/jwt-cpp/badges/wolfssl/5.3.0/shields.json ## Overview @@ -53,11 +66,23 @@ There is no hard dependency on a JSON library. Instead, there's a generic `jwt:: jwt::basic_claim claim(json::object({{"json", true},{"example", 0}})); ``` -This allows for complete freedom when picking which libraries you want to use. For more information, [see below](#providing-your-own-json-traits-your-traits). +This allows for complete freedom when picking which libraries you want to use. For more information, [read this page](docs/traits.md)). + +For your convenience there are serval traits implementation which provide some popular JSON libraries. They are: + +[![picojson][picojson]](https://github.com/kazuho/picojson) +[![nlohmann][nlohmann]](https://github.com/nlohmann/json) +[![jsoncons][jsoncons]](https://github.com/danielaparker/jsoncons) +[![boostjson][boostjson]](https://github.com/boostorg/json) -In order to maintain compatibility, [picojson](https://github.com/kazuho/picojson) is still used to provide a specialized `jwt::claim` along with all helpers. Defining `JWT_DISABLE_PICOJSON` will remove this optional dependency. +[picojson]: https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/Thalhammer/jwt-cpp/badges/traits/kazuho-picojson/shields.json +[nlohmann]: https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/Thalhammer/jwt-cpp/badges/traits/nlohmann-json/shields.json +[jsoncons]: https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/Thalhammer/jwt-cpp/badges/traits/danielaparker-jsoncons/shields.json +[boostjson]: https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/Thalhammer/jwt-cpp/badges/traits/boost-json/shields.json -As for the base64 requirements of JWTs, this libary provides `base.h` with all the required implentation; However base64 implementations are very common, with varying degrees of performance. When providing your own base64 implementation, you can define `JWT_DISABLE_BASE64` to remove the jwt-cpp implementation. +In order to maintain compatibility, [picojson](https://github.com/kazuho/picojson) is still used to provide a specialized `jwt::claim` along with all helpers. Defining `JWT_DISABLE_PICOJSON` will remove this optional dependency. It's possible to directly include the traits defaults for the other JSON libraries. See the [traits examples](https://github.com/Thalhammer/jwt-cpp/tree/master/example/traits) for details. + +As for the base64 requirements of JWTs, this library provides `base.h` with all the required implementation; However base64 implementations are very common, with varying degrees of performance. When providing your own base64 implementation, you can define `JWT_DISABLE_BASE64` to remove the jwt-cpp implementation. ### Getting Started @@ -71,7 +96,7 @@ int main() { std::string token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXUyJ9.eyJpc3MiOiJhdXRoMCJ9.AbIJTDMFc7yUa5MhvcP03nJPyCPzZtQcGEp-zWfOkEE"; auto decoded = jwt::decode(token); - for(auto& e : decoded.get_payload_claims()) + for(auto& e : decoded.get_payload_json()) std::cout << e.first << " = " << e.second << std::endl; } ``` @@ -98,74 +123,11 @@ auto token = jwt::create() .sign(jwt::algorithm::hs256{"secret"}); ``` -Here is a simple example of creating a token that will expire in one hour: - -```cpp -auto token = jwt::create() - .set_issuer("auth0") - .set_issued_at(std::chrono::system_clock::now()) - .set_expires_at(std::chrono::system_clock::now() + std::chrono::seconds{3600}) - .sign(jwt::algorithm::hs256{"secret"}); -``` - > To see more examples working with RSA public and private keys, visit our [examples](https://github.com/Thalhammer/jwt-cpp/tree/master/example)! ### Providing your own JSON Traits -There are several key items that need to be provided to a `jwt::basic_claim` in order for it to be interoptable with you JSON library of choice. - -* type specifications -* conversion from generic "value type" to a specific type -* serialization and parsing - -If ever you are not sure, the traits are heavily checked against static asserts to make sure you provide everything that's required. - -> :warning: Not all JSON libraries are a like, you may need to extent certain types such that it can be used by jwt-cpp. See this [example](https://github.com/Thalhammer/jwt-cpp/blob/ac3de9e69bc698a464dacb256a1b50512843f092/tests/jsoncons/JsonconsTest.cpp). - -```cpp -struct my_favorite_json_library_traits { - // Type Specifications - using value_type = json; // The generic "value type" implementation, most libraries have one - using object_type = json::object_t; // The "map type" string to value - using array_type = json::array_t; // The "list type" array of values - using string_type = std::string; // The "list of chars", must be a narrow char - using number_type = double; // The "percision type" - using integer_type = int64_t; // The "integral type" - using boolean_type = bool; // The "boolean type" - - // 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.type() == json::value_t::object) - return type::object; - if (val.type() == json::value_t::array) - return type::array; - if (val.type() == json::value_t::string) - return type::string; - if (val.type() == json::value_t::number_float) - return type::number; - if (val.type() == json::value_t::number_integer) - return type::integer; - if (val.type() == json::value_t::boolean) - return type::boolean; - - throw std::logic_error("invalid type"); - } - - // Conversion from generic value to specific type - static object_type as_object(const value_type &val); - static array_type as_array(const value_type &val); - static string_type as_string(const value_type &val); - static number_type as_number(const value_type &val); - static integer_type as_int(const value_type &val); - static boolean_type as_bool(const value_type &val); - - // serilization and parsing - static bool parse(value_type &val, string_type str); - static string_type serialize(const value_type &val); // with no extra whitespace, padding or indentation -}; -``` +To learn how to writes a trait's implementation, checkout the [these instructions](docs/traits.md) ## Contributing @@ -187,22 +149,7 @@ In order to build the test cases you also need ## Troubleshooting -### Expired tokens - -If you are generating tokens that seem to immediately expire, you are likely not using UTC. Specifically, -if you use `get_time` to get the current time, it likely uses localtime, while this library uses UTC, -which may be why your token is immediately expiring. Please see example above on the right way to use current time. - -### Missing \_HMAC and \_EVP_sha256 symbols on Mac - -There seems to exists a problem with the included openssl library of MacOS. Make sure you link to one provided by brew. -See [here](https://github.com/Thalhammer/jwt-cpp/issues/6) for more details. - -### Building on windows fails with syntax errors - -The header ``, which is often included in windowsprojects, defines macros for MIN and MAX which screw up std::numeric_limits. -See [here](https://github.com/Thalhammer/jwt-cpp/issues/5) for more details. To fix this do one of the following things: +See the [FAQs](docs/faqs.md) for tips. -* define NOMINMAX, which suppresses this behaviour -* include this library before you include windows.h -* place `#undef max` and `#undef min` before you include this library +## Conference Coverage +[![CppCon](https://img.youtube.com/vi/Oq4NW5idmiI/0.jpg)](https://www.youtube.com/watch?v=Oq4NW5idmiI) diff --git a/dep/jwt-cpp/cmake/HunterGate.cmake b/dep/jwt-cpp/cmake/HunterGate.cmake new file mode 100644 index 0000000000..64ccde563b --- /dev/null +++ b/dep/jwt-cpp/cmake/HunterGate.cmake @@ -0,0 +1,537 @@ +# 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() diff --git a/dep/jwt-cpp/cmake/code-coverage.cmake b/dep/jwt-cpp/cmake/code-coverage.cmake new file mode 100644 index 0000000000..5e630bc8c2 --- /dev/null +++ b/dep/jwt-cpp/cmake/code-coverage.cmake @@ -0,0 +1,12 @@ +set(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() diff --git a/dep/jwt-cpp/cmake/jwt-cpp-config.cmake.in b/dep/jwt-cpp/cmake/jwt-cpp-config.cmake.in new file mode 100644 index 0000000000..029f3586cd --- /dev/null +++ b/dep/jwt-cpp/cmake/jwt-cpp-config.cmake.in @@ -0,0 +1,19 @@ +@PACKAGE_INIT@ + +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(JWT_EXTERNAL_PICOJSON) + find_dependency(picojson REQUIRED) +endif() + +include("${CMAKE_CURRENT_LIST_DIR}/jwt-cpp-targets.cmake") diff --git a/dep/jwt-cpp/cmake/private-find-boost-json.cmake b/dep/jwt-cpp/cmake/private-find-boost-json.cmake new file mode 100644 index 0000000000..70c7b6ccd2 --- /dev/null +++ b/dep/jwt-cpp/cmake/private-find-boost-json.cmake @@ -0,0 +1,16 @@ +if(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() diff --git a/dep/jwt-cpp/cmake/private-find-kazuho-picojson.cmake b/dep/jwt-cpp/cmake/private-find-kazuho-picojson.cmake new file mode 100644 index 0000000000..ccbed30659 --- /dev/null +++ b/dep/jwt-cpp/cmake/private-find-kazuho-picojson.cmake @@ -0,0 +1,10 @@ +if(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() diff --git a/dep/jwt-cpp/docs/faqs.md b/dep/jwt-cpp/docs/faqs.md new file mode 100644 index 0000000000..48b107fd75 --- /dev/null +++ b/dep/jwt-cpp/docs/faqs.md @@ -0,0 +1,75 @@ +# Frequently Asked Questions + +## Handling Tokens + +### The generated JWT token can be decoded, is this correct and secure? + +This is the expected behaviour. While the integrity of tokens is ensured by the generated/verified hash, +the contents of the token are only **encoded and not encrypted**. This means you can be sure the token +has not been modified by an unauthorized party, but you should not store confidential information in it. +Anyone with access to the token can read all the claims you put into it. They can however not modify +them unless they have the (private or symmetric) key used to generate the token. If you need to put +confidential information into it, current industry recommends generating a random id and store the data on your +server, using the id to look it up whenever you need. + +### How can new keys be generated for my application? + +The algorithms provided are all based on OpenSSL, mixing other +cryptographic tools might not work. + +Here are a few links for your convenience: + +- [RSA](https://stackoverflow.com/a/44474607) +- [ED25519](https://stackoverflow.com/a/73118582) +- [ES256](https://github.com/Thalhammer/jwt-cpp/blob/68309438cf30679d6581d6cfbfeea0c028d9ed04/example/es256k.cpp#L5) + +### Can this library encrypt/decrypt claims? + +No it does not, see [#115](https://github.com/Thalhammer/jwt-cpp/issues/115) for more details. +More importantly you probably dont want to be using JWTs for anything sensitive. Read [this](https://stackoverflow.com/a/43497242/8480874) +for more. + +### Why are my tokens immediately expired? + +If you are generating tokens that seem to immediately expire, you are likely mixing local time where it is not required. The JWT specification +requires using UTC which this library does. + +Here is a simple example of creating a token that will expire in one hour: + +```cpp +auto token = jwt::create() + .set_issued_at(std::chrono::system_clock::now()) + .set_expires_at(std::chrono::system_clock::now() + std::chrono::seconds{3600}) + .sign(jwt::algorithm::hs256{"secret"}); +``` + +### Can you add claims to a signed token? + +The signature includes both the header and payload, according to the RFCs... changing the payload would cause a discrepancy. +That should result in the token being rejected. For more details checkout [#194](https://github.com/Thalhammer/jwt-cpp/issues/194). + +### Why does `jwt::basic_claim` have no `as_object()` method? + +This was brought up in [#212](https://github.com/Thalhammer/jwt-cpp/issues/212#issuecomment-1054344192) and +[#101](https://github.com/Thalhammer/jwt-cpp/issues/101) as it's an excellent question. + +It simply was not required to handle the required keys in JWTs for signing or verification. All the the mandatory keys are numeric, +string or array types which required type definitions and access. + +The alternative is to use the `to_json()` method and use the libraries own APIs to pick the data type you need. + +## Build Issues + +### Missing \_HMAC and \_EVP_sha256 symbols on Mac + +There seems to exists a problem with the included openssl library of MacOS. Make sure you link to one provided by brew. +See [here](https://github.com/Thalhammer/jwt-cpp/issues/6) for more details. + +### Building on windows fails with syntax errors + +The header ``, which is often included in windowsprojects, defines macros for MIN and MAX which screw up std::numeric_limits. +See [here](https://github.com/Thalhammer/jwt-cpp/issues/5) for more details. To fix this do one of the following things: + +* define NOMINMAX, which suppresses this behaviour +* include this library before you include windows.h +* place `#undef max` and `#undef min` before you include this library diff --git a/dep/jwt-cpp/docs/ssl.md b/dep/jwt-cpp/docs/ssl.md new file mode 100644 index 0000000000..4f001ac53e --- /dev/null +++ b/dep/jwt-cpp/docs/ssl.md @@ -0,0 +1,19 @@ +# Cryptography Libraries + +The underlying cryptography libraries describe [here](../README.md#ssl-compatibility) can be selected when +configuring CMake by explicitly setting `JWT_SSL_LIBRARY` to one of three values. The default is to use OpenSSL. + +- OpenSSL +- LibreSSL +- wolfSSL + +Here's an example: + +```sh +cmake . -DJWT_SSL_LIBRARY:STRING=wolfSSL +``` + +## Notes + +JWT-CPP relies on the OpenSSL API, as a result both LibreSSL and wolfSSL need to include their respective compatibility layers. +Most system already have OpenSSL so it's important to make sure when compiling your application it only includes one. Otherwise you may have missing symbols when linking. diff --git a/dep/jwt-cpp/docs/traits.md b/dep/jwt-cpp/docs/traits.md new file mode 100644 index 0000000000..71eddb6af9 --- /dev/null +++ b/dep/jwt-cpp/docs/traits.md @@ -0,0 +1,60 @@ +# JSON Traits + +Traits define the compatibility mapping for JWT-CPP required functionality to the JSON implementation of choice. + +## Providing your own JSON Traits + +There are several key items that need to be provided to a `jwt::basic_claim` in order for it to be interoperable with you JSON library of choice. + +* type specifications +* conversion from generic "value type" to a specific type +* serialization and parsing + +If ever you are not sure, the traits are heavily checked against static asserts to make sure you provide everything that's required. + +> :warning: Not all JSON libraries are a like, you may need to extend certain types such that it can be used. See this [provided implementation](https://github.com/Thalhammer/jwt-cpp/blob/e6b92cca0b7088027269c481fa244e5c39df88ff/include/jwt-cpp/traits/danielaparker-jsoncons/traits.h#L18). + +```cpp +struct my_favorite_json_library_traits { + // Type Specifications + using value_type = json; // The generic "value type" implementation, most libraries have one + using object_type = json::object_t; // The "map type" string to value + using array_type = json::array_t; // The "list type" array of values + using string_type = std::string; // The "list of chars", must be a narrow char + using number_type = double; // The "precision type" + using integer_type = int64_t; // The "integral type" + using boolean_type = bool; // The "boolean type" + + // Translation between the implementation notion of type, to the jwt::json::type equivalent + static jwt::json::type get_type(const value_type &val) { + using jwt::json::type; + + if (val.type() == json::value_t::object) + return type::object; + if (val.type() == json::value_t::array) + return type::array; + if (val.type() == json::value_t::string) + return type::string; + if (val.type() == json::value_t::number_float) + return type::number; + if (val.type() == json::value_t::number_integer) + return type::integer; + if (val.type() == json::value_t::boolean) + return type::boolean; + + throw std::logic_error("invalid type"); + } + + // Conversion from generic value to specific type + static object_type as_object(const value_type &val); + static array_type as_array(const value_type &val); + static string_type as_string(const value_type &val); + static number_type as_number(const value_type &val); + static integer_type as_integer(const value_type &val); + static boolean_type as_boolean(const value_type &val); + + // serialization and parsing + static bool parse(value_type &val, string_type str); + static string_type serialize(const value_type &val); // with no extra whitespace, padding or indentation +}; +``` diff --git a/dep/jwt-cpp/example/CMakeLists.txt b/dep/jwt-cpp/example/CMakeLists.txt new file mode 100644 index 0000000000..a153b57b16 --- /dev/null +++ b/dep/jwt-cpp/example/CMakeLists.txt @@ -0,0 +1,33 @@ +cmake_minimum_required(VERSION 3.8) +project(jwt-cpp-examples) + +if(NOT TARGET jwt-cpp) + find_package(jwt-cpp CONFIG REQUIRED) +endif() + +add_subdirectory(traits) + +if(JWT_DISABLE_PICOJSON) + message(FATAL_ERROR "examples require picojson to be available!") +endif() + +add_executable(print-claims print-claims.cpp) +target_link_libraries(print-claims jwt-cpp::jwt-cpp) + +add_executable(private-claims private-claims.cpp) +target_link_libraries(private-claims jwt-cpp::jwt-cpp) + +add_executable(rsa-create rsa-create.cpp) +target_link_libraries(rsa-create jwt-cpp::jwt-cpp) + +add_executable(rsa-verify rsa-verify.cpp) +target_link_libraries(rsa-verify jwt-cpp::jwt-cpp) + +add_executable(jwks-verify jwks-verify.cpp) +target_link_libraries(jwks-verify jwt-cpp::jwt-cpp) + +add_executable(es256k es256k.cpp) +target_link_libraries(es256k jwt-cpp::jwt-cpp) + +add_executable(partial-claim-verifier partial-claim-verifier.cpp) +target_link_libraries(partial-claim-verifier jwt-cpp::jwt-cpp nlohmann_json::nlohmann_json) diff --git a/dep/jwt-cpp/example/conan/CMakeLists.txt b/dep/jwt-cpp/example/conan/CMakeLists.txt new file mode 100644 index 0000000000..7bd897c164 --- /dev/null +++ b/dep/jwt-cpp/example/conan/CMakeLists.txt @@ -0,0 +1,8 @@ +cmake_minimum_required(VERSION 3.8) +project(conan-example) + +include(${CMAKE_BINARY_DIR}/conanbuildinfo.cmake) +conan_basic_setup() + +add_executable(${PROJECT_NAME} main.cpp) +target_link_libraries(${PROJECT_NAME} ${CONAN_LIBS}) diff --git a/dep/jwt-cpp/example/conan/README.md b/dep/jwt-cpp/example/conan/README.md new file mode 100644 index 0000000000..8032ffdefe --- /dev/null +++ b/dep/jwt-cpp/example/conan/README.md @@ -0,0 +1,24 @@ +# Conan example + +In the current directory on Linux environment + +```sh +mkdir build && cd build +conan install .. && cmake .. +cmake --build . +``` + +run executable + +```sh +$ ./bin/conan-example +sub = "jrocket@example.com" +iss = "Online JWT Builder" +exp = 1641559177 +aud = "www.example.com" +Surname = "Rocket" +Role = ["Manager","Project Administrator"] +iat = 1610023177 +GivenName = "Johnny" +Email = "jrocket@example.com" +``` diff --git a/dep/jwt-cpp/example/conan/conanfile.txt b/dep/jwt-cpp/example/conan/conanfile.txt new file mode 100644 index 0000000000..b2a27f124e --- /dev/null +++ b/dep/jwt-cpp/example/conan/conanfile.txt @@ -0,0 +1,6 @@ +[requires] +jwt-cpp/0.6.0 +picojson/cci.20210117 + +[generators] +cmake diff --git a/dep/jwt-cpp/example/conan/main.cpp b/dep/jwt-cpp/example/conan/main.cpp new file mode 100644 index 0000000000..9095ccb997 --- /dev/null +++ b/dep/jwt-cpp/example/conan/main.cpp @@ -0,0 +1,17 @@ +#include "jwt-cpp/jwt.h" +#include +#include + +int main() { + + std::string token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9." + "eyJpc3MiOiJPbmxpbmUgSldUIEJ1aWxkZXIiLCJpYXQiOjE2MTAwMjMxNzcsImV4cCI6MTY0MTU1OTE3NywiYXVkIjoid3" + "d3LmV4YW1wbGUuY29tIiwic3ViIjoianJvY2tldEBleGFtcGxlLmNvbSIsIkdpdmVuTmFtZSI6IkpvaG5ueSIsIlN1cm5h" + "bWUiOiJSb2NrZXQiLCJFbWFpbCI6Impyb2NrZXRAZXhhbXBsZS5jb20iLCJSb2xlIjpbIk1hbmFnZXIiLCJQcm9qZWN0IE" + "FkbWluaXN0cmF0b3IiXX0.5EOfHnBmpdPvRHAuVDttgJQvbFuGEF7fC4uBSXAGg6c"; + + auto decoded = jwt::decode(token); + + for (auto& e : decoded.get_payload_json()) + std::cout << e.first << " = " << e.second << std::endl; +} diff --git a/dep/jwt-cpp/example/es256k.cpp b/dep/jwt-cpp/example/es256k.cpp new file mode 100644 index 0000000000..d2a59745f6 --- /dev/null +++ b/dep/jwt-cpp/example/es256k.cpp @@ -0,0 +1,40 @@ +#include +#include + +int main() { + // openssl ecparam -name secp256k1 -genkey -noout -out ec-secp256k1-priv-key.pem + std::string es256k_priv_key = R"(-----BEGIN EC PRIVATE KEY----- +MHQCAQEEIArnQWnspKtjiVuZuzuZ/l1Uqqq8gb2unLJ/6U/Saf4ioAcGBSuBBAAK +oUQDQgAEfy03KCKUpIPMIJBtIG4xOwGm0Np/yHKaK9EDZi0mZ7VUeeNKq476CU5X +940yusahgneePQrDMF2nWFEtBCOiXQ== +-----END EC PRIVATE KEY-----)"; + // openssl ec -in ec-secp256k1-priv-key.pem -pubout > ec-secp256k1-pub-key.pem + std::string es256k_pub_key = R"(-----BEGIN PUBLIC KEY----- +MFYwEAYHKoZIzj0CAQYFK4EEAAoDQgAEfy03KCKUpIPMIJBtIG4xOwGm0Np/yHKa +K9EDZi0mZ7VUeeNKq476CU5X940yusahgneePQrDMF2nWFEtBCOiXQ== +-----END PUBLIC KEY-----)"; + + auto token = jwt::create() + .set_issuer("auth0") + .set_type("JWT") + .set_id("es256k-create-example") + .set_issued_at(std::chrono::system_clock::now()) + .set_expires_at(std::chrono::system_clock::now() + std::chrono::seconds{36000}) + .set_payload_claim("sample", jwt::claim(std::string{"test"})) + .sign(jwt::algorithm::es256k(es256k_pub_key, es256k_priv_key, "", "")); + + std::cout << "token:\n" << token << std::endl; + + auto verify = jwt::verify() + .allow_algorithm(jwt::algorithm::es256k(es256k_pub_key, es256k_priv_key, "", "")) + .with_issuer("auth0"); + + auto decoded = jwt::decode(token); + + verify.verify(decoded); + + for (auto& e : decoded.get_header_json()) + std::cout << e.first << " = " << e.second << std::endl; + for (auto& e : decoded.get_payload_json()) + std::cout << e.first << " = " << e.second << std::endl; +} diff --git a/dep/jwt-cpp/example/jwks-verify.cpp b/dep/jwt-cpp/example/jwks-verify.cpp new file mode 100644 index 0000000000..2464a5fe60 --- /dev/null +++ b/dep/jwt-cpp/example/jwks-verify.cpp @@ -0,0 +1,57 @@ +#include +#include + +int main() { + std::string raw_jwks = + R"({"keys": [{ + "kid":"internal-gateway-jwt.api.sc.net", + "alg": "RS256", + "kty": "RSA", + "use": "sig", + "x5c": [ + "MIIC+DCCAeCgAwIBAgIJBIGjYW6hFpn2MA0GCSqGSIb3DQEBBQUAMCMxITAfBgNVBAMTGGN1c3RvbWVyLWRlbW9zLmF1dGgwLmNvbTAeFw0xNjExMjIyMjIyMDVaFw0zMDA4MDEyMjIyMDVaMCMxITAfBgNVBAMTGGN1c3RvbWVyLWRlbW9zLmF1dGgwLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMnjZc5bm/eGIHq09N9HKHahM7Y31P0ul+A2wwP4lSpIwFrWHzxw88/7Dwk9QMc+orGXX95R6av4GF+Es/nG3uK45ooMVMa/hYCh0Mtx3gnSuoTavQEkLzCvSwTqVwzZ+5noukWVqJuMKNwjL77GNcPLY7Xy2/skMCT5bR8UoWaufooQvYq6SyPcRAU4BtdquZRiBT4U5f+4pwNTxSvey7ki50yc1tG49Per/0zA4O6Tlpv8x7Red6m1bCNHt7+Z5nSl3RX/QYyAEUX1a28VcYmR41Osy+o2OUCXYdUAphDaHo4/8rbKTJhlu8jEcc1KoMXAKjgaVZtG/v5ltx6AXY0CAwEAAaMvMC0wDAYDVR0TBAUwAwEB/zAdBgNVHQ4EFgQUQxFG602h1cG+pnyvJoy9pGJJoCswDQYJKoZIhvcNAQEFBQADggEBAGvtCbzGNBUJPLICth3mLsX0Z4z8T8iu4tyoiuAshP/Ry/ZBnFnXmhD8vwgMZ2lTgUWwlrvlgN+fAtYKnwFO2G3BOCFw96Nm8So9sjTda9CCZ3dhoH57F/hVMBB0K6xhklAc0b5ZxUpCIN92v/w+xZoz1XQBHe8ZbRHaP1HpRM4M7DJk2G5cgUCyu3UBvYS41sHvzrxQ3z7vIePRA4WF4bEkfX12gvny0RsPkrbVMXX1Rj9t6V7QXrbPYBAO+43JvDGYawxYVvLhz+BJ45x50GFQmHszfY3BR9TPK8xmMmQwtIvLu1PMttNCs7niCYkSiUv2sc2mlq1i3IashGkkgmo=" + ], + "n": "yeNlzlub94YgerT030codqEztjfU_S6X4DbDA_iVKkjAWtYfPHDzz_sPCT1Axz6isZdf3lHpq_gYX4Sz-cbe4rjmigxUxr-FgKHQy3HeCdK6hNq9ASQvMK9LBOpXDNn7mei6RZWom4wo3CMvvsY1w8tjtfLb-yQwJPltHxShZq5-ihC9irpLI9xEBTgG12q5lGIFPhTl_7inA1PFK97LuSLnTJzW0bj096v_TMDg7pOWm_zHtF53qbVsI0e3v5nmdKXdFf9BjIARRfVrbxVxiZHjU6zL6jY5QJdh1QCmENoejj_ytspMmGW7yMRxzUqgxcAqOBpVm0b-_mW3HoBdjQ", + "e": "AQAB", + "x5t": "NjVBRjY5MDlCMUIwNzU4RTA2QzZFMDQ4QzQ2MDAyQjVDNjk1RTM2Qg" + }, +{ + "kid":"internal-123456", + "use":"sig", + "x5c":["MIIG1TCCBL2gAwIBAgIIFvMVGp6t\/cMwDQYJKoZIhvcNAQELBQAwZjELMAkGA1UEBhMCR0IxIDAeBgNVBAoMF1N0YW5kYXJkIENoYXJ0ZXJlZCBCYW5rMTUwMwYDVQQDDCxTdGFuZGFyZCBDaGFydGVyZWQgQmFuayBTaWduaW5nIENBIEcxIC0gU0hBMjAeFw0xODEwMTAxMTI2MzVaFw0yMjEwMTAxMTI2MzVaMIG9MQswCQYDVQQGEwJTRzESMBAGA1UECAwJU2luZ2Fwb3JlMRIwEAYDVQQHDAlTaW5nYXBvcmUxIDAeBgNVBAoMF1N0YW5kYXJkIENoYXJ0ZXJlZCBCYW5rMRwwGgYDVQQLDBNGb3VuZGF0aW9uIFNlcnZpY2VzMSgwJgYDVQQDDB9pbnRlcm5hbC1nYXRld2F5LWp3dC5hcGkuc2MubmV0MRwwGgYJKoZIhvcNAQkBFg1BUElQU1NAc2MuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArVWBoIi3IJ4nOWXu7\/SDxczqMou1B+c4c2FdQrOXrK31HxAaz4WEtma9BLXFdFHJ5mCCPIvdUcVxxnCynqhMOkZ\/a7acQbUD9cDzI8isMB9JL7VooDw0CctxHxffjqQQVIEhC2Q7zsM1pQayR7cl+pbBlvHIoRxq2n1B0fFvfoiosjf4kDiCpgHdM+v5Hw9aVYmUbroHxmQWqhB0iRTJQPPLZqqQVC50A1Q\/96gkwoODyotc46Uy9wYEpdGrtDG\/thWay3fmMsjpWR0U25xFIrxTrfCGBblYpD7juukWWml2E9rtE2rHgUxbymxXjEw7xrMwcGrhOGyqwoBqJy1JVwIDAQABo4ICLTCCAikwZAYIKwYBBQUHAQEEWDBWMFQGCCsGAQUFBzABhkhodHRwOi8vY29yZW9jc3AuZ2xvYmFsLnN0YW5kYXJkY2hhcnRlcmVkLmNvbS9lamJjYS9wdWJsaWN3ZWIvc3RhdHVzL29jc3AwHQYDVR0OBBYEFIinW4BNDeVEFcuLf8YjZjtySoW9MAwGA1UdEwEB\/wQCMAAwHwYDVR0jBBgwFoAUfNZMoZi33nKrcmVU3TFVQnuEi\/4wggFCBgNVHR8EggE5MIIBNTCCATGggcKggb+GgbxodHRwOi8vY29yZWNybC5nbG9iYWwuc3RhbmRhcmRjaGFydGVyZWQuY29tL2VqYmNhL3B1YmxpY3dlYi93ZWJkaXN0L2NlcnRkaXN0P2NtZD1jcmwmaXNzdWVyPUNOPVN0YW5kYXJkJTIwQ2hhcnRlcmVkJTIwQmFuayUyMFNpZ25pbmclMjBDQSUyMEcxJTIwLSUyMFNIQTIsTz1TdGFuZGFyZCUyMENoYXJ0ZXJlZCUyMEJhbmssQz1HQqJqpGgwZjE1MDMGA1UEAwwsU3RhbmRhcmQgQ2hhcnRlcmVkIEJhbmsgU2lnbmluZyBDQSBHMSAtIFNIQTIxIDAeBgNVBAoMF1N0YW5kYXJkIENoYXJ0ZXJlZCBCYW5rMQswCQYDVQQGEwJHQjAOBgNVHQ8BAf8EBAMCBsAwHQYDVR0lBBYwFAYIKwYBBQUHAwIGCCsGAQUFBwMEMA0GCSqGSIb3DQEBCwUAA4ICAQBtsoRlDHuOTDChcWdfdVUtRgP0U0ijDSeJi8vULN1rgYnqqJc4PdJno50aiu9MGlxY02O7HW7ZVD6QEG\/pqHmZ0sbWpb\/fumMgZSjP65IcGuS53zgcNtLYnyXyEv+v5T\/CK3bk4Li6tUW3ScJPUwVWwP1E0\/u6aBSb5k\/h4lTwS1o88ybS5pJOg6XutXByp991QQrrs7tp7fKNynjNZbFuG3J1e09X+zTfJOpjaDUofQTkt8IyMRI6Cs4wI1eZA+dAIL8B0n8ze1mRl1FOJqgdZrAQjoqZkCTnc0Il5VY\/dUXxGVg6D9e5pfck3FWT107K9\/5EZoxytpqYXFCjMXi5hx4YjK17OUgm82mZhvqkNdzF8Yq2vFuB3LPfyelESq99xFLykvinrVm1NtZKeDTT1Jq\/VvZt6stO\/tovq1RfJJcznpYcwOzxlnhGR6E+hxuBx7aDJzGf0JaoRxQILH1B2XV9WDI3HPYQsP7XtriX+QUJ\/aly28QkV48RmaGYCsly43YZu1MKudSsw+dhnbZzRsg\/aes3dzGW2x137bQPtux7k2LCSpsTXgedhOys28YoGlsoe8kUv0myAU4Stt+I3mrwO3BKUn+tJggvlDiiiyT1tg2HiklyU\/2FxQkZRMeB0eRrXTpg3l9x2mpF+dDFxOMKszxwD2kgoEZgo6o58A=="], + "n":"nr9UsxnPVd21iuiGcIJ_Qli2XVlAZe5VbELA1hO2-L4k5gI4fjHZ3ysUcautLpbOYogOQgsnlpsLrCmvNDvBDVzVp2nMbpguJlt12vHSP1fRJJpipGQ8qU-VaXsC4OjOQf3H9ojAU5Vfnl5gZ7kVCd8g4M29l-IRyNpxE-Ccxc2Y7molsCHT6GHLMMBVsd11JIOXMICJf4hz2YYkQ1t7C8SaB2RFRPuGO5Mn6mfAnwdmRera4TBz6_pIPPCgCbN8KOdJItWkr9F7Tjv_0nhh-ZVlQvbQ9PXHyKTj00g3IYUlbZIWHm0Ley__fzNZk2dyAAVjNA2QSzTZJc33MQx1pQ", + "e":"AQAB", + "x5t":"-qC0akuyiHTV5aFsKVWM9da7lzq6DLrj09I", + "alg":"RS256", + "kty":"RSA" + } +]})"; + + std::string token = + "eyJraWQiOiJpbnRlcm5hbC1nYXRld2F5LWp3dC5hcGkuc2MubmV0IiwiYWxnIjoiUlMyNTYiLCJ0eXAiOiJKV1QifQ." + "eyJuYmYiOjE1Mzk3NjcwMTUsImlhdCI6MTUzOTc2Njk5MiwiaXNzIjoia29uZyIsImh0dHA6XC9cL3dzbzIub3JnXC9nYXRld2F5XC9zdWJzY3" + "JpYmVyIjoidXZ0dXNlcjJAY2FyYm9uLnN1cGVyIiwib3JpZ2luYWxfaXNzIjoiaHR0cDpcL1wvd3NvMi5vcmdcL2dhdGV3YXkiLCJzdWIiOiJ1" + "dnR1c2VyMkBjYXJib24uc3VwZXIiLCJodHRwOlwvXC93c28yLm9yZ1wvZ2F0ZXdheVwvZW5kdXNlciI6InV2dHVzZXIyQGNhcmJvbi5zdXBlci" + "IsImp0aSI6IjI0NmJkZTlhLWQ4OGQtNGRlZC1hODhmLTRhMTNhOWJmODQ4ZiIsImh0dHA6XC9cL3dzbzIub3JnXC9nYXRld2F5XC9hcHBsaWNh" + "dGlvbm5hbWUiOiJ1dnR1c2VyMl9hcHBfMSIsImV4cCI6MTUzOTc2NzkxNX0.foxbo6C30yr_wkF-5EkgtYUMG-4SXNfRsmewdT6MbE-" + "RXVkIPkVk8kDP41yRXmnk4OxburCqawiGlzzEhfHoFf0qv0qZEmwEXSdcyRw-czZTs6ACjWYe8kejOCVmpvUrq01NgOhTwgVg6pv93BlcmNY--" + "zytjx_9hlVm5SS1lZ0I21n45BIWu5JvBD51TZXEURb_XhL7RcF9I8mfzrRpB2fSHW38gj-nogsdOPA_y3S-hJKylmmaqmaQgTF-jP-" + "gYr6eqKyGPVwc6fLZ5zqAup59SefdPEY23-WWmHzj968jlsDSEiCp_YiYTnF3tHVLFWDsrprYKwNb0_p95tBmPA"; + + auto decoded_jwt = jwt::decode(token); + auto jwks = jwt::parse_jwks(raw_jwks); + auto jwk = jwks.get_jwk(decoded_jwt.get_key_id()); + + auto issuer = decoded_jwt.get_issuer(); + auto x5c = jwk.get_x5c_key_value(); + + if (!x5c.empty() && !issuer.empty()) { + auto verifier = + jwt::verify() + .allow_algorithm(jwt::algorithm::rs256(jwt::helper::convert_base64_der_to_pem(x5c), "", "", "")) + .with_issuer(issuer) + .leeway(60UL); // value in seconds, add some to compensate timeout + + verifier.verify(decoded_jwt); + } +} diff --git a/dep/jwt-cpp/example/partial-claim-verifier.cpp b/dep/jwt-cpp/example/partial-claim-verifier.cpp new file mode 100644 index 0000000000..6093d3be9b --- /dev/null +++ b/dep/jwt-cpp/example/partial-claim-verifier.cpp @@ -0,0 +1,91 @@ +#include "jwt-cpp/traits/nlohmann-json/defaults.h" + +#include + +int main() { + std::string rsa_priv_key = R"(-----BEGIN PRIVATE KEY----- +MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQC4ZtdaIrd1BPIJ +tfnF0TjIK5inQAXZ3XlCrUlJdP+XHwIRxdv1FsN12XyMYO/6ymLmo9ryoQeIrsXB +XYqlET3zfAY+diwCb0HEsVvhisthwMU4gZQu6TYW2s9LnXZB5rVtcBK69hcSlA2k +ZudMZWxZcj0L7KMfO2rIvaHw/qaVOE9j0T257Z8Kp2CLF9MUgX0ObhIsdumFRLaL +DvDUmBPr2zuh/34j2XmWwn1yjN/WvGtdfhXW79Ki1S40HcWnygHgLV8sESFKUxxQ +mKvPUTwDOIwLFL5WtE8Mz7N++kgmDcmWMCHc8kcOIu73Ta/3D4imW7VbKgHZo9+K +3ESFE3RjAgMBAAECggEBAJTEIyjMqUT24G2FKiS1TiHvShBkTlQdoR5xvpZMlYbN +tVWxUmrAGqCQ/TIjYnfpnzCDMLhdwT48Ab6mQJw69MfiXwc1PvwX1e9hRscGul36 +ryGPKIVQEBsQG/zc4/L2tZe8ut+qeaK7XuYrPp8bk/X1e9qK5m7j+JpKosNSLgJj +NIbYsBkG2Mlq671irKYj2hVZeaBQmWmZxK4fw0Istz2WfN5nUKUeJhTwpR+JLUg4 +ELYYoB7EO0Cej9UBG30hbgu4RyXA+VbptJ+H042K5QJROUbtnLWuuWosZ5ATldwO +u03dIXL0SH0ao5NcWBzxU4F2sBXZRGP2x/jiSLHcqoECgYEA4qD7mXQpu1b8XO8U +6abpKloJCatSAHzjgdR2eRDRx5PMvloipfwqA77pnbjTUFajqWQgOXsDTCjcdQui +wf5XAaWu+TeAVTytLQbSiTsBhrnoqVrr3RoyDQmdnwHT8aCMouOgcC5thP9vQ8Us +rVdjvRRbnJpg3BeSNimH+u9AHgsCgYEA0EzcbOltCWPHRAY7B3Ge/AKBjBQr86Kv +TdpTlxePBDVIlH+BM6oct2gaSZZoHbqPjbq5v7yf0fKVcXE4bSVgqfDJ/sZQu9Lp +PTeV7wkk0OsAMKk7QukEpPno5q6tOTNnFecpUhVLLlqbfqkB2baYYwLJR3IRzboJ +FQbLY93E8gkCgYB+zlC5VlQbbNqcLXJoImqItgQkkuW5PCgYdwcrSov2ve5r/Acz +FNt1aRdSlx4176R3nXyibQA1Vw+ztiUFowiP9WLoM3PtPZwwe4bGHmwGNHPIfwVG +m+exf9XgKKespYbLhc45tuC08DATnXoYK7O1EnUINSFJRS8cezSI5eHcbQKBgQDC +PgqHXZ2aVftqCc1eAaxaIRQhRmY+CgUjumaczRFGwVFveP9I6Gdi+Kca3DE3F9Pq +PKgejo0SwP5vDT+rOGHN14bmGJUMsX9i4MTmZUZ5s8s3lXh3ysfT+GAhTd6nKrIE +kM3Nh6HWFhROptfc6BNusRh1kX/cspDplK5x8EpJ0QKBgQDWFg6S2je0KtbV5PYe +RultUEe2C0jYMDQx+JYxbPmtcopvZQrFEur3WKVuLy5UAy7EBvwMnZwIG7OOohJb +vkSpADK6VPn9lbqq7O8cTedEHttm6otmLt8ZyEl3hZMaL3hbuRj6ysjmoFKx6CrX +rK0/Ikt5ybqUzKCMJZg2VKGTxg== +-----END PRIVATE KEY-----)"; + + auto role_claim = nlohmann::json{{"my-service", {{"roles", {"foo", "bar", "baz"}}}}}; + + auto token = jwt::create() + .set_issuer("auth0") + .set_type("JWT") + .set_id("rsa-create-example") + .set_issued_at(std::chrono::system_clock::now()) + .set_expires_at(std::chrono::system_clock::now() + std::chrono::seconds{36000}) + .set_payload_claim("resource-access", role_claim) + .sign(jwt::algorithm::rs256("", rsa_priv_key, "", "")); + + std::cout << "token: " << token << std::endl; + + std::string rsa_pub_key = R"(-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuGbXWiK3dQTyCbX5xdE4 +yCuYp0AF2d15Qq1JSXT/lx8CEcXb9RbDddl8jGDv+spi5qPa8qEHiK7FwV2KpRE9 +83wGPnYsAm9BxLFb4YrLYcDFOIGULuk2FtrPS512Qea1bXASuvYXEpQNpGbnTGVs +WXI9C+yjHztqyL2h8P6mlThPY9E9ue2fCqdgixfTFIF9Dm4SLHbphUS2iw7w1JgT +69s7of9+I9l5lsJ9cozf1rxrXX4V1u/SotUuNB3Fp8oB4C1fLBEhSlMcUJirz1E8 +AziMCxS+VrRPDM+zfvpIJg3JljAh3PJHDiLu902v9w+Iplu1WyoB2aPfitxEhRN0 +YwIDAQAB +-----END PUBLIC KEY-----)"; + + auto decoded = jwt::decode(token); + + for (const auto& e : decoded.get_payload_json()) + std::cout << e.first << " = " << e.second << std::endl; + + std::cout << std::endl; + + auto role_verifier = [](const jwt::verify_context& ctx, std::error_code& ec) { + using error = jwt::error::token_verification_error; + + auto c = ctx.get_claim(false, ec); + if (ec) return; + if (c.get_type() == jwt::json::type::object) { + auto obj = c.to_json(); + try { + auto roles = obj["my-service"]["roles"].get(); + if (roles.end() == std::find(roles.begin(), roles.end(), "foo")) ec = error::claim_value_missmatch; + } catch (const std::exception& ex) { ec = error::claim_value_missmatch; } + } else + ec = error::claim_type_missmatch; + }; + + auto verifier = jwt::verify() + .allow_algorithm(jwt::algorithm::rs256(rsa_pub_key, "", "", "")) + .with_issuer("auth0") + .with_claim("resource-access", role_verifier); + + try { + verifier.verify(decoded); + std::cout << "Success!" << std::endl; + } catch (const std::exception& ex) { std::cout << "Error: " << ex.what() << std::endl; } + + return 0; +} diff --git a/dep/jwt-cpp/example/print-claims.cpp b/dep/jwt-cpp/example/print-claims.cpp new file mode 100644 index 0000000000..8dd05abba2 --- /dev/null +++ b/dep/jwt-cpp/example/print-claims.cpp @@ -0,0 +1,11 @@ +#include +#include + +int main() { + std::string token = + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXUyJ9.eyJpc3MiOiJhdXRoMCJ9.AbIJTDMFc7yUa5MhvcP03nJPyCPzZtQcGEp-zWfOkEE"; + auto decoded = jwt::decode(token); + + for (auto& e : decoded.get_payload_json()) + std::cout << e.first << " = " << e.second << std::endl; +} diff --git a/dep/jwt-cpp/example/private-claims.cpp b/dep/jwt-cpp/example/private-claims.cpp new file mode 100644 index 0000000000..97c84ae6c6 --- /dev/null +++ b/dep/jwt-cpp/example/private-claims.cpp @@ -0,0 +1,46 @@ +#include + +#include +#include + +using sec = std::chrono::seconds; +using min = std::chrono::minutes; + +int main() { + jwt::claim from_raw_json; + std::istringstream iss{R"##({"api":{"array":[1,2,3],"null":null}})##"}; + iss >> from_raw_json; + + jwt::claim::set_t list{"once", "twice"}; + std::vector big_numbers{727663072ULL, 770979831ULL, 427239169ULL, 525936436ULL}; + + const auto time = jwt::date::clock::now(); + const auto token = jwt::create() + .set_type("JWT") + .set_issuer("auth.mydomain.io") + .set_audience("mydomain.io") + .set_issued_at(time) + .set_not_before(time + sec{15}) + .set_expires_at(time + sec{15} + min{2}) + .set_payload_claim("boolean", picojson::value(true)) + .set_payload_claim("integer", picojson::value(int64_t{12345})) + .set_payload_claim("precision", picojson::value(12.345)) + .set_payload_claim("strings", jwt::claim(list)) + .set_payload_claim("array", jwt::claim(big_numbers.begin(), big_numbers.end())) + .set_payload_claim("object", from_raw_json) + .sign(jwt::algorithm::none{}); + + const auto decoded = jwt::decode(token); + + const auto api_array = decoded.get_payload_claim("object").to_json().get("api").get("array"); + std::cout << "api array = " << api_array << std::endl; + + jwt::verify() + .allow_algorithm(jwt::algorithm::none{}) + .with_issuer("auth.mydomain.io") + .with_audience("mydomain.io") + .with_claim("object", from_raw_json) + .verify(decoded); + + return 0; +} diff --git a/dep/jwt-cpp/example/rsa-create.cpp b/dep/jwt-cpp/example/rsa-create.cpp new file mode 100644 index 0000000000..16ab8f88b7 --- /dev/null +++ b/dep/jwt-cpp/example/rsa-create.cpp @@ -0,0 +1,44 @@ +#include +#include + +int main() { + std::string rsa_priv_key = R"(-----BEGIN PRIVATE KEY----- +MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQC4ZtdaIrd1BPIJ +tfnF0TjIK5inQAXZ3XlCrUlJdP+XHwIRxdv1FsN12XyMYO/6ymLmo9ryoQeIrsXB +XYqlET3zfAY+diwCb0HEsVvhisthwMU4gZQu6TYW2s9LnXZB5rVtcBK69hcSlA2k +ZudMZWxZcj0L7KMfO2rIvaHw/qaVOE9j0T257Z8Kp2CLF9MUgX0ObhIsdumFRLaL +DvDUmBPr2zuh/34j2XmWwn1yjN/WvGtdfhXW79Ki1S40HcWnygHgLV8sESFKUxxQ +mKvPUTwDOIwLFL5WtE8Mz7N++kgmDcmWMCHc8kcOIu73Ta/3D4imW7VbKgHZo9+K +3ESFE3RjAgMBAAECggEBAJTEIyjMqUT24G2FKiS1TiHvShBkTlQdoR5xvpZMlYbN +tVWxUmrAGqCQ/TIjYnfpnzCDMLhdwT48Ab6mQJw69MfiXwc1PvwX1e9hRscGul36 +ryGPKIVQEBsQG/zc4/L2tZe8ut+qeaK7XuYrPp8bk/X1e9qK5m7j+JpKosNSLgJj +NIbYsBkG2Mlq671irKYj2hVZeaBQmWmZxK4fw0Istz2WfN5nUKUeJhTwpR+JLUg4 +ELYYoB7EO0Cej9UBG30hbgu4RyXA+VbptJ+H042K5QJROUbtnLWuuWosZ5ATldwO +u03dIXL0SH0ao5NcWBzxU4F2sBXZRGP2x/jiSLHcqoECgYEA4qD7mXQpu1b8XO8U +6abpKloJCatSAHzjgdR2eRDRx5PMvloipfwqA77pnbjTUFajqWQgOXsDTCjcdQui +wf5XAaWu+TeAVTytLQbSiTsBhrnoqVrr3RoyDQmdnwHT8aCMouOgcC5thP9vQ8Us +rVdjvRRbnJpg3BeSNimH+u9AHgsCgYEA0EzcbOltCWPHRAY7B3Ge/AKBjBQr86Kv +TdpTlxePBDVIlH+BM6oct2gaSZZoHbqPjbq5v7yf0fKVcXE4bSVgqfDJ/sZQu9Lp +PTeV7wkk0OsAMKk7QukEpPno5q6tOTNnFecpUhVLLlqbfqkB2baYYwLJR3IRzboJ +FQbLY93E8gkCgYB+zlC5VlQbbNqcLXJoImqItgQkkuW5PCgYdwcrSov2ve5r/Acz +FNt1aRdSlx4176R3nXyibQA1Vw+ztiUFowiP9WLoM3PtPZwwe4bGHmwGNHPIfwVG +m+exf9XgKKespYbLhc45tuC08DATnXoYK7O1EnUINSFJRS8cezSI5eHcbQKBgQDC +PgqHXZ2aVftqCc1eAaxaIRQhRmY+CgUjumaczRFGwVFveP9I6Gdi+Kca3DE3F9Pq +PKgejo0SwP5vDT+rOGHN14bmGJUMsX9i4MTmZUZ5s8s3lXh3ysfT+GAhTd6nKrIE +kM3Nh6HWFhROptfc6BNusRh1kX/cspDplK5x8EpJ0QKBgQDWFg6S2je0KtbV5PYe +RultUEe2C0jYMDQx+JYxbPmtcopvZQrFEur3WKVuLy5UAy7EBvwMnZwIG7OOohJb +vkSpADK6VPn9lbqq7O8cTedEHttm6otmLt8ZyEl3hZMaL3hbuRj6ysjmoFKx6CrX +rK0/Ikt5ybqUzKCMJZg2VKGTxg== +-----END PRIVATE KEY-----)"; + + auto token = jwt::create() + .set_issuer("auth0") + .set_type("JWT") + .set_id("rsa-create-example") + .set_issued_at(std::chrono::system_clock::now()) + .set_expires_at(std::chrono::system_clock::now() + std::chrono::seconds{36000}) + .set_payload_claim("sample", jwt::claim(std::string{"test"})) + .sign(jwt::algorithm::rs256("", rsa_priv_key, "", "")); + + std::cout << "token:\n" << token << std::endl; +} diff --git a/dep/jwt-cpp/example/rsa-verify.cpp b/dep/jwt-cpp/example/rsa-verify.cpp new file mode 100644 index 0000000000..e2af4095d6 --- /dev/null +++ b/dep/jwt-cpp/example/rsa-verify.cpp @@ -0,0 +1,34 @@ +#include +#include + +int main() { + std::string rsa_pub_key = R"(-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuGbXWiK3dQTyCbX5xdE4 +yCuYp0AF2d15Qq1JSXT/lx8CEcXb9RbDddl8jGDv+spi5qPa8qEHiK7FwV2KpRE9 +83wGPnYsAm9BxLFb4YrLYcDFOIGULuk2FtrPS512Qea1bXASuvYXEpQNpGbnTGVs +WXI9C+yjHztqyL2h8P6mlThPY9E9ue2fCqdgixfTFIF9Dm4SLHbphUS2iw7w1JgT +69s7of9+I9l5lsJ9cozf1rxrXX4V1u/SotUuNB3Fp8oB4C1fLBEhSlMcUJirz1E8 +AziMCxS+VrRPDM+zfvpIJg3JljAh3PJHDiLu902v9w+Iplu1WyoB2aPfitxEhRN0 +YwIDAQAB +-----END PUBLIC KEY-----)"; + + std::string token = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXUyJ9.eyJpc3MiOiJhdXRoMCJ9." + "VA2i1ui1cnoD6I3wnji1WAVCf29EekysvevGrT2GXqK1dDMc8" + "HAZCTQxa1Q8NppnpYV-hlqxh-X3Bb0JOePTGzjynpNZoJh2aHZD-" + "GKpZt7OO1Zp8AFWPZ3p8Cahq8536fD8RiBES9jRsvChZvOqA7gMcFc4" + "YD0iZhNIcI7a654u5yPYyTlf5kjR97prCf_OXWRn-bYY74zna4p_bP9oWCL4BkaoRcMxi-" + "IR7kmVcCnvbYqyIrKloXP2qPO442RBGqU7Ov9" + "sGQxiVqtRHKXZR9RbfvjrErY1KGiCp9M5i2bsUHadZEY44FE2jiOmx-" + "uc2z5c05CCXqVSpfCjWbh9gQ"; + + auto verify = jwt::verify().allow_algorithm(jwt::algorithm::rs256(rsa_pub_key, "", "", "")).with_issuer("auth0"); + + auto decoded = jwt::decode(token); + + verify.verify(decoded); + + for (auto& e : decoded.get_header_json()) + std::cout << e.first << " = " << e.second << std::endl; + for (auto& e : decoded.get_payload_json()) + std::cout << e.first << " = " << e.second << std::endl; +} diff --git a/dep/jwt-cpp/example/traits/CMakeLists.txt b/dep/jwt-cpp/example/traits/CMakeLists.txt new file mode 100644 index 0000000000..42f88d54fa --- /dev/null +++ b/dep/jwt-cpp/example/traits/CMakeLists.txt @@ -0,0 +1,30 @@ +cmake_minimum_required(VERSION 3.8) +project(jwt-cpp-traits) + +if(NOT TARGET jwt-cpp) + find_package(jwt-cpp CONFIG REQUIRED) +endif() + +find_package(jsoncons CONFIG) +if(TARGET jsoncons) + add_executable(danielaparker-jsoncons danielaparker-jsoncons.cpp) + target_link_libraries(danielaparker-jsoncons jwt-cpp::jwt-cpp jsoncons) +endif() + +include("../../cmake/private-find-boost-json.cmake") +if(TARGET boost_json) + add_executable(boost-json boost-json.cpp) + target_link_libraries(boost-json jwt-cpp::jwt-cpp boost_json) +endif() + +find_package(nlohmann_json CONFIG) +if(TARGET nlohmann_json::nlohmann_json) + add_executable(nlohmann-json nlohmann-json.cpp) + target_link_libraries(nlohmann-json nlohmann_json::nlohmann_json jwt-cpp::jwt-cpp) +endif() + +include("../../cmake/private-find-kazuho-picojson.cmake") +if(TARGET kazuho_picojson) + add_executable(kazuho-picojson kazuho-picojson.cpp) + target_link_libraries(kazuho-picojson jwt-cpp::jwt-cpp kazuho_picojson) +endif() diff --git a/dep/jwt-cpp/example/traits/README.md b/dep/jwt-cpp/example/traits/README.md new file mode 100644 index 0000000000..c31464b54f --- /dev/null +++ b/dep/jwt-cpp/example/traits/README.md @@ -0,0 +1,6 @@ +# Traits Examples + +These example require upstream CMake installation to work. There are exceptions: + +- For Boost.JSON headers must be located by custom CMake. +- For PicoJSON headers must be located by custom CMake. diff --git a/dep/jwt-cpp/example/traits/boost-json.cpp b/dep/jwt-cpp/example/traits/boost-json.cpp new file mode 100644 index 0000000000..59c585eead --- /dev/null +++ b/dep/jwt-cpp/example/traits/boost-json.cpp @@ -0,0 +1,52 @@ +#include "jwt-cpp/traits/boost-json/traits.h" + +// #include // You may require this if you are not building it elsewhere +#include +#include + +int main() { + using sec = std::chrono::seconds; + using min = std::chrono::minutes; + using traits = jwt::traits::boost_json; + using claim = jwt::basic_claim; + + traits::value_type raw_value; + traits::parse(raw_value, R"##({"api":{"array":[1,2,3],"null":null}})##"); + claim from_raw_json(raw_value); + + claim::set_t list{"once", "twice"}; + std::vector big_numbers{727663072ULL, 770979831ULL, 427239169ULL, 525936436ULL}; + + const auto time = jwt::date::clock::now(); + const auto token = jwt::create() + .set_type("JWT") + .set_issuer("auth.mydomain.io") + .set_audience("mydomain.io") + .set_issued_at(time) + .set_not_before(time) + .set_expires_at(time + min{2} + sec{15}) + .set_payload_claim("boolean", true) + .set_payload_claim("integer", 12345) + .set_payload_claim("precision", 12.3456789) + .set_payload_claim("strings", claim(list)) + .set_payload_claim("array", claim{big_numbers.begin(), big_numbers.end()}) + .set_payload_claim("object", from_raw_json) + .sign(jwt::algorithm::none{}); + const auto decoded = jwt::decode(token); + + for (auto& e : decoded.get_header_json()) + std::cout << e.key() << " = " << e.value() << std::endl; + + const auto array = + traits::as_array(decoded.get_payload_claim("object").to_json().as_object()["api"].as_object()["array"]); + std::cout << "payload /object/api/array = " << array << std::endl; + + jwt::verify() + .allow_algorithm(jwt::algorithm::none{}) + .with_issuer("auth.mydomain.io") + .with_audience("mydomain.io") + .with_claim("object", from_raw_json) + .verify(decoded); + + return 0; +} diff --git a/dep/jwt-cpp/example/traits/danielaparker-jsoncons.cpp b/dep/jwt-cpp/example/traits/danielaparker-jsoncons.cpp new file mode 100644 index 0000000000..5eac313ce5 --- /dev/null +++ b/dep/jwt-cpp/example/traits/danielaparker-jsoncons.cpp @@ -0,0 +1,47 @@ +#include "jwt-cpp/traits/danielaparker-jsoncons/traits.h" + +#include +#include + +int main() { + using sec = std::chrono::seconds; + using min = std::chrono::minutes; + using traits = jwt::traits::danielaparker_jsoncons; + using claim = jwt::basic_claim; + + claim from_raw_json; + std::istringstream iss{R"##({"api":{"array":[1,2,3],"null":null}})##"}; + iss >> from_raw_json; + + claim::set_t list{"once", "twice"}; + std::vector big_numbers{727663072ULL, 770979831ULL, 427239169ULL, 525936436ULL}; + + const auto time = jwt::date::clock::now(); + const auto token = jwt::create() + .set_type("JWT") + .set_issuer("auth.mydomain.io") + .set_audience("mydomain.io") + .set_issued_at(time) + .set_not_before(time) + .set_expires_at(time + min{2} + sec{15}) + .set_payload_claim("boolean", true) + .set_payload_claim("integer", 12345) + .set_payload_claim("precision", 12.3456789) + .set_payload_claim("strings", list) + .set_payload_claim("array", {big_numbers.begin(), big_numbers.end()}) + .set_payload_claim("object", from_raw_json) + .sign(jwt::algorithm::none{}); + const auto decoded = jwt::decode(token); + + const auto array = traits::as_array(decoded.get_payload_claim("object").to_json()["api"]["array"]); + std::cout << "payload /object/api/array = " << array << std::endl; + + jwt::verify() + .allow_algorithm(jwt::algorithm::none{}) + .with_issuer("auth.mydomain.io") + .with_audience("mydomain.io") + .with_claim("object", from_raw_json) + .verify(decoded); + + return 0; +} diff --git a/dep/jwt-cpp/example/traits/kazuho-picojson.cpp b/dep/jwt-cpp/example/traits/kazuho-picojson.cpp new file mode 100644 index 0000000000..16724007b2 --- /dev/null +++ b/dep/jwt-cpp/example/traits/kazuho-picojson.cpp @@ -0,0 +1,47 @@ +#include "jwt-cpp/traits/kazuho-picojson/traits.h" + +#include +#include + +int main() { + using sec = std::chrono::seconds; + using min = std::chrono::minutes; + using traits = jwt::traits::kazuho_picojson; + using claim = jwt::basic_claim; + + claim from_raw_json; + std::istringstream iss{R"##({"api":{"array":[1,2,3],"null":null}})##"}; + iss >> from_raw_json; + + claim::set_t list{"once", "twice"}; + std::vector big_numbers{727663072ULL, 770979831ULL, 427239169ULL, 525936436ULL}; + + const auto time = jwt::date::clock::now(); + const auto token = jwt::create() + .set_type("JWT") + .set_issuer("auth.mydomain.io") + .set_audience("mydomain.io") + .set_issued_at(time) + .set_not_before(time) + .set_expires_at(time + min{2} + sec{15}) + .set_payload_claim("boolean", picojson::value(true)) + .set_payload_claim("integer", picojson::value(int64_t{12345})) + .set_payload_claim("precision", picojson::value(12.345)) + .set_payload_claim("strings", claim(list)) + .set_payload_claim("array", claim(big_numbers.begin(), big_numbers.end())) + .set_payload_claim("object", from_raw_json) + .sign(jwt::algorithm::none{}); + const auto decoded = jwt::decode(token); + + const auto api_array = decoded.get_payload_claim("object").to_json().get("api").get("array"); + std::cout << "api array = " << api_array << std::endl; + + jwt::verify() + .allow_algorithm(jwt::algorithm::none{}) + .with_issuer("auth.mydomain.io") + .with_audience("mydomain.io") + .with_claim("object", from_raw_json) + .verify(decoded); + + return 0; +} diff --git a/dep/jwt-cpp/example/traits/nlohmann-json.cpp b/dep/jwt-cpp/example/traits/nlohmann-json.cpp new file mode 100644 index 0000000000..00fa2acd07 --- /dev/null +++ b/dep/jwt-cpp/example/traits/nlohmann-json.cpp @@ -0,0 +1,47 @@ +#include "jwt-cpp/traits/nlohmann-json/traits.h" + +#include +#include + +int main() { + using sec = std::chrono::seconds; + using min = std::chrono::minutes; + using traits = jwt::traits::nlohmann_json; + using claim = jwt::basic_claim; + + claim from_raw_json; + std::istringstream iss{R"##({"api":{"array":[1,2,3],"null":null}})##"}; + iss >> from_raw_json; + + claim::set_t list{"once", "twice"}; + std::vector big_numbers{727663072ULL, 770979831ULL, 427239169ULL, 525936436ULL}; + + const auto time = jwt::date::clock::now(); + const auto token = jwt::create() + .set_type("JWT") + .set_issuer("auth.mydomain.io") + .set_audience("mydomain.io") + .set_issued_at(time) + .set_not_before(time) + .set_expires_at(time + min{2} + sec{15}) + .set_payload_claim("boolean", true) + .set_payload_claim("integer", 12345) + .set_payload_claim("precision", 12.3456789) + .set_payload_claim("strings", list) + .set_payload_claim("array", {big_numbers.begin(), big_numbers.end()}) + .set_payload_claim("object", from_raw_json) + .sign(jwt::algorithm::none{}); + const auto decoded = jwt::decode(token); + + const auto array = traits::as_array(decoded.get_payload_claim("object").to_json()["api"]["array"]); + std::cout << "payload /object/api/array = " << array << std::endl; + + jwt::verify() + .allow_algorithm(jwt::algorithm::none{}) + .with_issuer("auth.mydomain.io") + .with_audience("mydomain.io") + .with_claim("object", from_raw_json) + .verify(decoded); + + return 0; +} diff --git a/dep/jwt-cpp/include/jwt-cpp/base.h b/dep/jwt-cpp/include/jwt-cpp/base.h index c447113c9d..8e6fc311ac 100644 --- a/dep/jwt-cpp/include/jwt-cpp/base.h +++ b/dep/jwt-cpp/include/jwt-cpp/base.h @@ -1,9 +1,12 @@ #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) @@ -21,7 +24,10 @@ namespace jwt { */ namespace alphabet { /** - * \brief valid list of characted when working with [Base64](https://tools.ietf.org/html/rfc3548) + * \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() { @@ -38,7 +44,13 @@ namespace jwt { } }; /** - * \brief valid list of characted when working with [Base64URL](https://tools.ietf.org/html/rfc4648) + * \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() { @@ -54,155 +66,205 @@ namespace jwt { 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::initializer_list& fill() { + static std::initializer_list fill{"%3D", "%3d"}; + return fill; + } + }; + } // namespace helper + + inline uint32_t index(const std::array& alphabet, char symbol) { + auto itr = std::find_if(alphabet.cbegin(), alphabet.cend(), [symbol](char c) { return c == symbol; }); + if (itr == alphabet.cend()) { throw std::runtime_error("Invalid input: not within alphabet"); } + + return std::distance(alphabet.cbegin(), itr); + } } // namespace alphabet /** - * \brief Alphabet generic methods for working with encoding/decoding the base64 family + * \brief A collection of fellable functions for working with base64 and base64url */ - class base { - public: - template - static std::string encode(const std::string& bin) { - return encode(bin, T::data(), T::fill()); - } - template - static std::string decode(const std::string& base) { - return decode(base, T::data(), T::fill()); - } - template - static std::string pad(const std::string& base) { - return pad(base, T::fill()); - } - template - static std::string trim(const std::string& base) { - return trim(base, T::fill()); - } + namespace base { - private: - static std::string encode(const std::string& bin, const std::array& alphabet, - const std::string& fill) { - size_t size = bin.size(); - std::string res; + namespace details { + struct padding { + size_t count = 0; + size_t length = 0; - // 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++]); + padding() = default; + padding(size_t count, size_t length) : count(count), length(length) {} - uint32_t triple = (octet_a << 0x10) + (octet_b << 0x08) + octet_c; + padding operator+(const padding& p) { return padding(count + p.count, length + p.length); } - res += alphabet[(triple >> 3 * 6) & 0x3F]; - res += alphabet[(triple >> 2 * 6) & 0x3F]; - res += alphabet[(triple >> 1 * 6) & 0x3F]; - res += alphabet[(triple >> 0 * 6) & 0x3F]; - } + 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); + } + } - 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 {}; } - return res; - } + 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; - static std::string decode(const std::string& base, const std::array& alphabet, - const std::string& fill) { - size_t size = base.size(); - - size_t fill_cnt = 0; - while (size > fill.size()) { - if (base.substr(size - fill.size(), fill.size()) == fill) { - fill_cnt++; - size -= fill.size(); - if (fill_cnt > 2) throw std::runtime_error("Invalid input"); - } else + 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; } - if ((size + fill_cnt) % 4 != 0) throw std::runtime_error("Invalid input"); + inline std::string decode(const std::string& base, const std::array& alphabet, + const std::vector& fill) { + const auto pad = count_padding(base, fill); + if (pad.count > 2) throw std::runtime_error("Invalid input: too much fill"); - size_t out_size = size / 4 * 3; - std::string res; - res.reserve(out_size); + const size_t size = base.size() - pad.length; + if ((size + pad.count) % 4 != 0) throw std::runtime_error("Invalid input: incorrect total size"); - auto get_sextet = [&](size_t offset) { - for (size_t i = 0; i < alphabet.size(); i++) { - if (alphabet[i] == base[offset]) return static_cast(i); + size_t out_size = size / 4 * 3; + std::string res; + res.reserve(out_size); + + auto get_sextet = [&](size_t offset) { return alphabet::index(alphabet, 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); } - throw std::runtime_error("Invalid input"); - }; - 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++); + if (pad.count == 0) return res; + + uint32_t triple = (get_sextet(fast_size) << 3 * 6) + (get_sextet(fast_size + 1) << 2 * 6); - uint32_t triple = (sextet_a << 3 * 6) + (sextet_b << 2 * 6) + (sextet_c << 1 * 6) + (sextet_d << 0 * 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; + } - res += static_cast((triple >> 2 * 8) & 0xFFU); - res += static_cast((triple >> 1 * 8) & 0xFFU); - res += static_cast((triple >> 0 * 8) & 0xFFU); + return res; } - if (fill_cnt == 0) return res; + inline std::string decode(const std::string& base, const std::array& alphabet, + const std::string& fill) { + return decode(base, alphabet, std::vector{fill}); + } - uint32_t triple = (get_sextet(fast_size) << 3 * 6) + (get_sextet(fast_size + 1) << 2 * 6); + 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; + } - switch (fill_cnt) { - 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 base + padding; } - return res; - } - - static 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; + inline std::string trim(const std::string& base, const std::string& fill) { + auto pos = base.find(fill); + return base.substr(0, pos); } + } // namespace details - return base + padding; + template + std::string encode(const std::string& bin) { + return details::encode(bin, T::data(), T::fill()); } - - static std::string trim(const std::string& base, const std::string& fill) { - auto pos = base.find(fill); - return base.substr(0, pos); + template + std::string decode(const std::string& base) { + return details::decode(base, T::data(), T::fill()); + } + template + std::string pad(const std::string& base) { + return details::pad(base, T::fill()); + } + template + std::string trim(const std::string& base) { + return details::trim(base, T::fill()); } - }; + } // namespace base } // namespace jwt #endif diff --git a/dep/jwt-cpp/include/jwt-cpp/jwt.h b/dep/jwt-cpp/include/jwt-cpp/jwt.h index bd3e6e9867..b2b998a2e2 100644 --- a/dep/jwt-cpp/include/jwt-cpp/jwt.h +++ b/dep/jwt-cpp/include/jwt-cpp/jwt.h @@ -13,19 +13,33 @@ #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 > 201103L +#include +#endif #if __cplusplus >= 201402L #ifdef __has_include @@ -35,19 +49,26 @@ #endif #endif -// If openssl version less than 1.1 -#if OPENSSL_VERSION_NUMBER < 0x10100000L -#define OPENSSL10 +#if OPENSSL_VERSION_NUMBER >= 0x30000000L // 3.0.0 +#define JWT_OPENSSL_3_0 +#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 openssl version less than 1.1.1 -#if OPENSSL_VERSION_NUMBER < 0x10101000L -#define OPENSSL110 +#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(LIBRESSL_VERSION_NUMBER) -#define OPENSSL10 -#define OPENSSL110 +#if defined(LIBWOLFSSL_VERSION_HEX) +#define JWT_OPENSSL_1_1_1 #endif #ifndef JWT_CLAIM_EXPLICIT @@ -62,6 +83,9 @@ * 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; /** @@ -136,7 +160,8 @@ namespace jwt { create_mem_bio_failed, no_key_provided, invalid_key_size, - invalid_key + invalid_key, + create_context_failed }; /** * \brief Error category for ECDSA errors @@ -155,6 +180,7 @@ namespace jwt { 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"; default: return "unknown ECDSA error"; } } @@ -175,7 +201,9 @@ namespace jwt { verifyinit_failed, verifyupdate_failed, verifyfinal_failed, - get_key_failed + get_key_failed, + set_rsa_pss_saltlen_failed, + signature_encoding_failed }; /** * \brief Error category for verification errors @@ -198,6 +226,10 @@ namespace jwt { 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"; } } @@ -226,7 +258,9 @@ namespace jwt { digestfinal_failed, rsa_padding_failed, rsa_private_encrypt_failed, - get_key_failed + get_key_failed, + set_rsa_pss_saltlen_failed, + signature_decoding_failed }; /** * \brief Error category for signature generation errors @@ -255,11 +289,15 @@ namespace jwt { 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: RSA_padding_add_PKCS1_PSS_mgf1 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"; } } @@ -326,15 +364,8 @@ namespace jwt { } } } // namespace error - - // FIXME: Remove - // Keep backward compat at least for a couple of revisions - using error::ecdsa_exception; - using error::rsa_exception; - using error::signature_generation_exception; - using error::signature_verification_exception; - using error::token_verification_exception; } // namespace jwt + namespace std { template<> struct is_error_code_enum : true_type {}; @@ -347,6 +378,7 @@ namespace std { template<> struct is_error_code_enum : true_type {}; } // namespace std + namespace jwt { /** * \brief A collection for working with certificates @@ -356,24 +388,119 @@ namespace jwt { * 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: + 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 + ); + } + + 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 * * \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 occures) + * \param ec error_code for error_detection (gets cleared if no error occurred) */ inline std::string extract_pubkey_from_cert(const std::string& certstr, const std::string& pw, std::error_code& ec) { ec.clear(); -#if OPENSSL_VERSION_NUMBER <= 0x10100003L - std::unique_ptr certbio( - BIO_new_mem_buf(const_cast(certstr.data()), static_cast(certstr.size())), BIO_free_all); -#else - std::unique_ptr certbio( - BIO_new_mem_buf(certstr.data(), static_cast(certstr.size())), BIO_free_all); -#endif - std::unique_ptr keybio(BIO_new(BIO_s_mem()), BIO_free_all); + auto certbio = make_mem_buf_bio(certstr); + auto keybio = make_mem_buf_bio(); if (!certbio || !keybio) { ec = error::rsa_error::create_mem_bio_failed; return {}; @@ -418,28 +545,19 @@ namespace jwt { } /** - * \brief Convert the certificate provided as base64 DER to PEM. + * \brief Convert the certificate provided as 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 callabled, 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 occures) + * \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) */ - template - std::string convert_base64_der_to_pem(const std::string& cert_base64_der_str, Decode decode, - std::error_code& ec) { + inline std::string convert_der_to_pem(const std::string& cert_der_str, std::error_code& ec) { ec.clear(); - const auto decodedStr = decode(cert_base64_der_str); - auto c_str = reinterpret_cast(decodedStr.c_str()); - std::unique_ptr cert(d2i_X509(NULL, &c_str, decodedStr.size()), X509_free); - std::unique_ptr certbio(BIO_new(BIO_s_mem()), BIO_free_all); + 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 {}; @@ -460,6 +578,28 @@ namespace jwt { return {ptr, static_cast(len)}; } + /** + * \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 callabled, 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. * @@ -481,6 +621,21 @@ namespace jwt { 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 + * \param decode The function to decode the cert + * \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. @@ -489,7 +644,7 @@ namespace jwt { * (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 occures) + * \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) { @@ -519,42 +674,38 @@ namespace jwt { * * The string should contain a pem encoded certificate or public key * - * \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 occures) + * \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 std::shared_ptr load_public_key_from_string(const std::string& key, - const std::string& password, std::error_code& ec) { + inline evp_pkey_handle load_public_key_from_string(const std::string& key, const std::string& password, + std::error_code& ec) { ec.clear(); - std::unique_ptr pubkey_bio(BIO_new(BIO_s_mem()), BIO_free_all); + auto pubkey_bio = make_mem_buf_bio(); if (!pubkey_bio) { ec = error::rsa_error::create_mem_bio_failed; - return nullptr; + return {}; } if (key.substr(0, 27) == "-----BEGIN CERTIFICATE-----") { auto epkey = helper::extract_pubkey_from_cert(key, password, ec); - if (ec) return nullptr; + if (ec) return {}; const int len = static_cast(epkey.size()); if (BIO_write(pubkey_bio.get(), epkey.data(), len) != len) { ec = error::rsa_error::load_key_bio_write; - return nullptr; + return {}; } } else { const int len = static_cast(key.size()); if (BIO_write(pubkey_bio.get(), key.data(), len) != len) { ec = error::rsa_error::load_key_bio_write; - return nullptr; + return {}; } } - std::shared_ptr pkey( - PEM_read_bio_PUBKEY(pubkey_bio.get(), nullptr, nullptr, - (void*)password.data()), // NOLINT(google-readability-casting) requires `const_cast` - EVP_PKEY_free); - if (!pkey) { - ec = error::rsa_error::load_key_bio_read; - return nullptr; - } + 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::rsa_error::load_key_bio_read; return pkey; } @@ -563,12 +714,11 @@ namespace jwt { * * The string should contain a pem encoded certificate or public key * - * \param certstr String containing the certificate or key encoded as pem - * \param pw Password used to decrypt certificate or key (leave empty if not encrypted) + * \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 rsa_exception if an error occurred */ - inline std::shared_ptr load_public_key_from_string(const std::string& key, - const std::string& password = "") { + 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); @@ -579,28 +729,24 @@ namespace jwt { * \brief Load a private key from a string. * * \param key String containing a private key as pem - * \param pw Password used to decrypt key (leave empty if not encrypted) - * \param ec error_code for error_detection (gets cleared if no error occures) + * \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 std::shared_ptr - load_private_key_from_string(const std::string& key, const std::string& password, std::error_code& ec) { - std::unique_ptr privkey_bio(BIO_new(BIO_s_mem()), BIO_free_all); + inline evp_pkey_handle load_private_key_from_string(const std::string& key, const std::string& password, + std::error_code& ec) { + auto privkey_bio = make_mem_buf_bio(); if (!privkey_bio) { ec = error::rsa_error::create_mem_bio_failed; - return nullptr; + return {}; } const int len = static_cast(key.size()); if (BIO_write(privkey_bio.get(), key.data(), len) != len) { ec = error::rsa_error::load_key_bio_write; - return nullptr; - } - std::shared_ptr pkey( - PEM_read_bio_PrivateKey(privkey_bio.get(), nullptr, nullptr, const_cast(password.c_str())), - EVP_PKEY_free); - if (!pkey) { - ec = error::rsa_error::load_key_bio_read; - return nullptr; + return {}; } + evp_pkey_handle pkey( + PEM_read_bio_PrivateKey(privkey_bio.get(), nullptr, nullptr, const_cast(password.c_str()))); + if (!pkey) ec = error::rsa_error::load_key_bio_read; return pkey; } @@ -608,28 +754,124 @@ namespace jwt { * \brief Load a private key from a string. * * \param key String containing a private key as pem - * \param pw Password used to decrypt key (leave empty if not encrypted) + * \param password Password used to decrypt key (leave empty if not encrypted) * \throw rsa_exception if an error occurred */ - inline std::shared_ptr load_private_key_from_string(const std::string& key, - const std::string& password = "") { + 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 + * + * \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) { + ec.clear(); + auto pubkey_bio = make_mem_buf_bio(); + if (!pubkey_bio) { + ec = error::ecdsa_error::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::ecdsa_error::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::ecdsa_error::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::ecdsa_error::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 + * + * \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_ec_key_from_string(key, password, ec); + error::throw_if_error(ec); + return res; + } + + /** + * \brief Load a private key from a string. + * + * \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) { + auto privkey_bio = make_mem_buf_bio(); + if (!privkey_bio) { + ec = error::ecdsa_error::create_mem_bio_failed; + return {}; + } + const int len = static_cast(key.size()); + if (BIO_write(privkey_bio.get(), key.data(), len) != len) { + ec = error::ecdsa_error::load_key_bio_write; + return {}; + } + evp_pkey_handle pkey( + PEM_read_bio_PrivateKey(privkey_bio.get(), nullptr, nullptr, const_cast(password.c_str()))); + if (!pkey) ec = error::ecdsa_error::load_key_bio_read; + return pkey; + } + + /** + * \brief Load a private key from a string. + * + * \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_ec_key_from_string(key, password, ec); + error::throw_if_error(ec); + return res; + } + /** * Convert a OpenSSL BIGNUM to a std::string * \param bn BIGNUM to convert * \return bignum as string */ inline -#ifdef OPENSSL10 - static std::string +#ifdef JWT_OPENSSL_1_0_0 + std::string bn2raw(BIGNUM* bn) #else - static std::string + std::string bn2raw(const BIGNUM* bn) #endif { @@ -642,7 +884,7 @@ namespace jwt { * \param raw String to convert * \return BIGNUM representation */ - inline static std::unique_ptr raw2bn(const std::string& raw) { + inline std::unique_ptr raw2bn(const std::string& raw) { return std::unique_ptr( BN_bin2bn(reinterpret_cast(raw.data()), static_cast(raw.size()), nullptr), BN_free); @@ -773,7 +1015,7 @@ namespace jwt { } else if (!public_key.empty()) { pkey = helper::load_public_key_from_string(public_key, public_key_password); } else - throw rsa_exception(error::rsa_error::no_key_provided); + throw error::rsa_exception(error::rsa_error::no_key_provided); } /** * Sign jwt data @@ -783,11 +1025,7 @@ namespace jwt { */ std::string sign(const std::string& data, std::error_code& ec) const { ec.clear(); -#ifdef OPENSSL10 - std::unique_ptr ctx(EVP_MD_CTX_create(), EVP_MD_CTX_destroy); -#else - std::unique_ptr ctx(EVP_MD_CTX_create(), EVP_MD_CTX_free); -#endif + auto ctx = helper::make_evp_md_ctx(); if (!ctx) { ec = error::signature_generation_error::create_context_failed; return {}; @@ -820,11 +1058,7 @@ namespace jwt { */ void verify(const std::string& data, const std::string& signature, std::error_code& ec) const { ec.clear(); -#ifdef OPENSSL10 - std::unique_ptr ctx(EVP_MD_CTX_create(), EVP_MD_CTX_destroy); -#else - std::unique_ptr ctx(EVP_MD_CTX_create(), EVP_MD_CTX_free); -#endif + auto ctx = helper::make_evp_md_ctx(); if (!ctx) { ec = error::signature_verification_error::create_context_failed; return; @@ -852,7 +1086,7 @@ namespace jwt { private: /// OpenSSL structure containing converted keys - std::shared_ptr pkey; + helper::evp_pkey_handle pkey; /// Hash generator const EVP_MD* (*md)(); /// algorithm's name @@ -864,57 +1098,34 @@ namespace jwt { 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 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 (!public_key.empty()) { - std::unique_ptr pubkey_bio(BIO_new(BIO_s_mem()), BIO_free_all); - if (!pubkey_bio) throw ecdsa_exception(error::ecdsa_error::create_mem_bio_failed); - if (public_key.substr(0, 27) == "-----BEGIN CERTIFICATE-----") { - auto epkey = helper::extract_pubkey_from_cert(public_key, public_key_password); - const int len = static_cast(epkey.size()); - if (BIO_write(pubkey_bio.get(), epkey.data(), len) != len) - throw ecdsa_exception(error::ecdsa_error::load_key_bio_write); - } else { - const int len = static_cast(public_key.size()); - if (BIO_write(pubkey_bio.get(), public_key.data(), len) != len) - throw ecdsa_exception(error::ecdsa_error::load_key_bio_write); - } - - pkey.reset(PEM_read_bio_EC_PUBKEY( - pubkey_bio.get(), nullptr, nullptr, - (void*)public_key_password - .c_str()), // NOLINT(google-readability-casting) requires `const_cast` - EC_KEY_free); - if (!pkey) throw ecdsa_exception(error::ecdsa_error::load_key_bio_read); - size_t keysize = EC_GROUP_get_degree(EC_KEY_get0_group(pkey.get())); - if (keysize != signature_length * 4 && (signature_length != 132 || keysize != 521)) - throw ecdsa_exception(error::ecdsa_error::invalid_key_size); + 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); - if (!private_key.empty()) { - std::unique_ptr privkey_bio(BIO_new(BIO_s_mem()), BIO_free_all); - if (!privkey_bio) throw ecdsa_exception(error::ecdsa_error::create_mem_bio_failed); - const int len = static_cast(private_key.size()); - if (BIO_write(privkey_bio.get(), private_key.data(), len) != len) - throw ecdsa_exception(error::ecdsa_error::load_key_bio_write); - pkey.reset(PEM_read_bio_ECPrivateKey(privkey_bio.get(), nullptr, nullptr, - const_cast(private_key_password.c_str())), - EC_KEY_free); - if (!pkey) throw ecdsa_exception(error::ecdsa_error::load_key_bio_read); - size_t keysize = EC_GROUP_get_degree(EC_KEY_get0_group(pkey.get())); - if (keysize != signature_length * 4 && (signature_length != 132 || keysize != 521)) - throw ecdsa_exception(error::ecdsa_error::invalid_key_size); - } - if (!pkey) throw ecdsa_exception(error::ecdsa_error::no_key_provided); - - if (EC_KEY_check_key(pkey.get()) == 0) throw 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 @@ -923,33 +1134,33 @@ namespace jwt { */ std::string sign(const std::string& data, std::error_code& ec) const { ec.clear(); - const std::string hash = generate_hash(data, ec); - if (ec) return {}; + 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 {}; + } - std::unique_ptr sig( - ECDSA_do_sign(reinterpret_cast(hash.data()), static_cast(hash.size()), - pkey.get()), - ECDSA_SIG_free); - if (!sig) { - ec = error::signature_generation_error::ecdsa_do_sign_failed; + 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 {}; } -#ifdef OPENSSL10 - 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; + res.resize(len); + return der_to_p1363_signature(res, ec); } /** @@ -960,36 +1171,38 @@ namespace jwt { */ void verify(const std::string& data, const std::string& signature, std::error_code& ec) const { ec.clear(); - const std::string hash = generate_hash(data, ec); - if (ec) return; - auto r = helper::raw2bn(signature.substr(0, signature.size() / 2)); - auto s = helper::raw2bn(signature.substr(signature.size() / 2)); - -#ifdef OPENSSL10 - ECDSA_SIG sig; - sig.r = r.get(); - sig.s = s.get(); + std::string der_signature = p1363_to_der_signature(signature, ec); + if (ec) { return; } - if (ECDSA_do_verify((const unsigned char*)hash.data(), static_cast(hash.size()), &sig, - pkey.get()) != 1) { - ec = error::signature_verification_error::invalid_signature; + auto ctx = helper::make_evp_md_ctx(); + if (!ctx) { + ec = error::signature_verification_error::create_context_failed; return; } -#else - std::unique_ptr sig(ECDSA_SIG_new(), ECDSA_SIG_free); - if (!sig) { - ec = error::signature_verification_error::create_context_failed; + 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; } - ECDSA_SIG_set0(sig.get(), r.release(), s.release()); - - if (ECDSA_do_verify(reinterpret_cast(hash.data()), static_cast(hash.size()), - sig.get(), pkey.get()) != 1) { +#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; } -#endif + if (res == -1) { + ec = error::signature_verification_error::verifyfinal_failed; + return; + } } /** * Returns the algorithm name provided to the constructor @@ -998,45 +1211,103 @@ namespace jwt { std::string name() const { return alg_name; } private: - /** - * Hash the provided data using the hash function specified in constructor - * \param data Data to hash - * \return Hash of data - */ - std::string generate_hash(const std::string& data, std::error_code& ec) const { -#ifdef OPENSSL10 - std::unique_ptr ctx(EVP_MD_CTX_create(), - &EVP_MD_CTX_destroy); + 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 ctx(EVP_MD_CTX_new(), EVP_MD_CTX_free); + 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 - if (!ctx) { - ec = error::signature_generation_error::create_context_failed; - return {}; - } - if (EVP_DigestInit(ctx.get(), md()) == 0) { - ec = error::signature_generation_error::digestinit_failed; - return {}; + } + + 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); } - if (EVP_DigestUpdate(ctx.get(), data.data(), data.size()) == 0) { - ec = error::signature_generation_error::digestupdate_failed; +#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 {}; } - unsigned int len = 0; - std::string res(EVP_MD_CTX_size(ctx.get()), '\0'); - if (EVP_DigestFinal( - ctx.get(), - (unsigned char*)res.data(), // NOLINT(google-readability-casting) requires `const_cast` - &len) == 0) { - ec = error::signature_generation_error::digestfinal_failed; + +#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)); + auto s = helper::raw2bn(signature.substr(signature.size() / 2)); + + 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 {}; } - res.resize(len); - return res; + 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 - std::shared_ptr pkey; + helper::evp_pkey_handle pkey; /// Hash generator function const EVP_MD* (*md)(); /// algorithm's name @@ -1045,7 +1316,7 @@ namespace jwt { const size_t signature_length; }; -#ifndef OPENSSL110 +#if !defined(JWT_OPENSSL_1_0_0) && !defined(JWT_OPENSSL_1_1_0) /** * \brief Base class for EdDSA family of algorithms * @@ -1073,7 +1344,7 @@ namespace jwt { } else if (!public_key.empty()) { pkey = helper::load_public_key_from_string(public_key, public_key_password); } else - throw ecdsa_exception(error::ecdsa_error::load_key_bio_read); + throw error::ecdsa_exception(error::ecdsa_error::load_key_bio_read); } /** * Sign jwt data @@ -1083,7 +1354,7 @@ namespace jwt { */ std::string sign(const std::string& data, std::error_code& ec) const { ec.clear(); - std::unique_ptr ctx(EVP_MD_CTX_create(), EVP_MD_CTX_free); + auto ctx = helper::make_evp_md_ctx(); if (!ctx) { ec = error::signature_generation_error::create_context_failed; return {}; @@ -1099,7 +1370,7 @@ namespace jwt { // 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. -#ifdef LIBRESSL_VERSION_NUMBER +#if defined(LIBRESSL_VERSION_NUMBER) || defined(LIBWOLFSSL_VERSION_HEX) ERR_clear_error(); if (EVP_DigestSignUpdate(ctx.get(), reinterpret_cast(data.data()), data.size()) != 1) { @@ -1131,7 +1402,7 @@ namespace jwt { */ void verify(const std::string& data, const std::string& signature, std::error_code& ec) const { ec.clear(); - std::unique_ptr ctx(EVP_MD_CTX_create(), EVP_MD_CTX_free); + auto ctx = helper::make_evp_md_ctx(); if (!ctx) { ec = error::signature_verification_error::create_context_failed; return; @@ -1143,7 +1414,7 @@ namespace jwt { // 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. -#ifdef LIBRESSL_VERSION_NUMBER +#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; @@ -1172,7 +1443,7 @@ namespace jwt { private: /// OpenSSL struct containing keys - std::shared_ptr pkey; + helper::evp_pkey_handle pkey; /// algorithm's name const std::string alg_name; }; @@ -1198,7 +1469,7 @@ namespace jwt { } else if (!public_key.empty()) { pkey = helper::load_public_key_from_string(public_key, public_key_password); } else - throw rsa_exception(error::rsa_error::no_key_provided); + throw error::rsa_exception(error::rsa_error::no_key_provided); } /** @@ -1209,31 +1480,43 @@ namespace jwt { */ std::string sign(const std::string& data, std::error_code& ec) const { ec.clear(); - auto hash = this->generate_hash(data, ec); - if (ec) return {}; - - std::unique_ptr key(EVP_PKEY_get1_RSA(pkey.get()), RSA_free); - if (!key) { - ec = error::signature_generation_error::get_key_failed; + auto md_ctx = helper::make_evp_md_ctx(); + if (!md_ctx) { + ec = error::signature_generation_error::create_context_failed; return {}; } - const int size = RSA_size(key.get()); - - std::string padded(size, 0x00); - if (RSA_padding_add_PKCS1_PSS_mgf1( - key.get(), (unsigned char*)padded.data(), reinterpret_cast(hash.data()), - md(), md(), -1) == 0) { // NOLINT(google-readability-casting) requires `const_cast` + 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 (RSA_private_encrypt(size, reinterpret_cast(padded.data()), - (unsigned char*)res.data(), key.get(), RSA_NO_PADDING) < - 0) { // NOLINT(google-readability-casting) requires `const_cast` - ec = error::signature_generation_error::rsa_private_encrypt_failed; + 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; } @@ -1245,28 +1528,36 @@ namespace jwt { */ void verify(const std::string& data, const std::string& signature, std::error_code& ec) const { ec.clear(); - auto hash = this->generate_hash(data, ec); - if (ec) return; - std::unique_ptr key(EVP_PKEY_get1_RSA(pkey.get()), RSA_free); - if (!key) { - ec = error::signature_verification_error::get_key_failed; + auto md_ctx = helper::make_evp_md_ctx(); + if (!md_ctx) { + ec = error::signature_verification_error::create_context_failed; return; } - const int size = RSA_size(key.get()); - - std::string sig(size, 0x00); - if (RSA_public_decrypt( - static_cast(signature.size()), reinterpret_cast(signature.data()), - (unsigned char*)sig.data(), // NOLINT(google-readability-casting) requires `const_cast` - key.get(), RSA_NO_PADDING) == 0) { - ec = error::signature_verification_error::invalid_signature; + 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 (RSA_verify_PKCS1_PSS_mgf1(key.get(), reinterpret_cast(hash.data()), md(), - md(), reinterpret_cast(sig.data()), -1) == 0) { - ec = error::signature_verification_error::invalid_signature; + if (EVP_DigestVerifyFinal(md_ctx.get(), (unsigned char*)signature.data(), signature.size()) <= 0) { + ec = error::signature_verification_error::verifyfinal_failed; return; } } @@ -1277,43 +1568,8 @@ namespace jwt { std::string name() const { return alg_name; } private: - /** - * Hash the provided data using the hash function specified in constructor - * \param data Data to hash - * \return Hash of data - */ - std::string generate_hash(const std::string& data, std::error_code& ec) const { -#ifdef OPENSSL10 - std::unique_ptr ctx(EVP_MD_CTX_create(), - &EVP_MD_CTX_destroy); -#else - std::unique_ptr ctx(EVP_MD_CTX_new(), EVP_MD_CTX_free); -#endif - if (!ctx) { - ec = error::signature_generation_error::create_context_failed; - return {}; - } - if (EVP_DigestInit(ctx.get(), md()) == 0) { - ec = error::signature_generation_error::digestinit_failed; - return {}; - } - if (EVP_DigestUpdate(ctx.get(), data.data(), data.size()) == 0) { - ec = error::signature_generation_error::digestupdate_failed; - return {}; - } - unsigned int len = 0; - std::string res(EVP_MD_CTX_size(ctx.get()), '\0'); - if (EVP_DigestFinal(ctx.get(), (unsigned char*)res.data(), &len) == - 0) { // NOLINT(google-readability-casting) requires `const_cast` - ec = error::signature_generation_error::digestfinal_failed; - return {}; - } - res.resize(len); - return res; - } - /// OpenSSL structure containing keys - std::shared_ptr pkey; + helper::evp_pkey_handle pkey; /// Hash generator function const EVP_MD* (*md)(); /// algorithm's name @@ -1446,8 +1702,24 @@ namespace jwt { 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) {} + }; -#ifndef OPENSSL110 +#if !defined(JWT_OPENSSL_1_0_0) && !defined(JWT_OPENSSL_1_1_0) /** * Ed25519 algorithm * @@ -1570,9 +1842,6 @@ namespace jwt { #ifdef __cpp_lib_experimental_detect template class _Op, typename... _Args> using is_detected = std::experimental::is_detected<_Op, _Args...>; - - template class _Op, typename... _Args> - using is_detected_t = std::experimental::detected_t<_Op, _Args...>; #else struct nonesuch { nonesuch() = delete; @@ -1598,139 +1867,58 @@ namespace jwt { template class Op, class... Args> using is_detected = typename detector::value; - - template class Op, class... Args> - using is_detected_t = typename detector::type; #endif - template - using get_type_function = decltype(traits_type::get_type); + template + using is_signature = typename std::is_same; - template - using is_get_type_signature = - typename std::is_same, json::type(const value_type&)>; + 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 { - static constexpr auto value = is_detected::value && - std::is_function>::value && - is_get_type_signature::value; - }; - - template - using as_object_function = decltype(traits_type::as_object); - - template - using is_as_object_signature = - typename std::is_same, object_type(const value_type&)>; - - template - struct supports_as_object { - static constexpr auto value = std::is_constructible::value && - is_detected::value && - std::is_function>::value && - is_as_object_signature::value; - }; - - template - using as_array_function = decltype(traits_type::as_array); - - template - using is_as_array_signature = - typename std::is_same, array_type(const value_type&)>; - - template - struct supports_as_array { - static constexpr auto value = std::is_constructible::value && - is_detected::value && - std::is_function>::value && - is_as_array_signature::value; - }; - - template - using as_string_function = decltype(traits_type::as_string); + template + using get_type_t = decltype(T::get_type); - template - using is_as_string_signature = - typename std::is_same, string_type(const value_type&)>; - - template - struct supports_as_string { - static constexpr auto value = std::is_constructible::value && - is_detected::value && - std::is_function>::value && - is_as_string_signature::value; - }; - - template - using as_number_function = decltype(traits_type::as_number); - - template - using is_as_number_signature = - typename std::is_same, number_type(const value_type&)>; - - template - struct supports_as_number { - static constexpr auto value = std::is_floating_point::value && - std::is_constructible::value && - is_detected::value && - std::is_function>::value && - is_as_number_signature::value; - }; - - template - using as_integer_function = decltype(traits_type::as_int); - - template - using is_as_integer_signature = - typename std::is_same, integer_type(const value_type&)>; + static constexpr auto value = + is_function_signature_detected::value; - template - struct supports_as_integer { - static constexpr auto value = std::is_signed::value && - !std::is_floating_point::value && - std::is_constructible::value && - is_detected::value && - std::is_function>::value && - is_as_integer_signature::value; + // Internal assertions for better feedback + static_assert(value, "traits implementation must provide `jwt::json::type get_type(const value_type&)`"); }; - template - using as_boolean_function = decltype(traits_type::as_bool); +#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&)`"); \ + } - template - using is_as_boolean_signature = - typename std::is_same, boolean_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); - template - struct supports_as_boolean { - static constexpr auto value = std::is_convertible::value && - std::is_constructible::value && - is_detected::value && - std::is_function>::value && - is_as_boolean_signature::value; - }; +#undef JWT_CPP_JSON_TYPE_TYPE +#undef JWT_CPP_AS_TYPE_T +#undef JWT_CPP_SUPPORTS_AS template struct is_valid_traits { - // Internal assertions for better feedback - static_assert(supports_get_type::value, - "traits must provide `jwt::json::type get_type(const value_type&)`"); - static_assert(supports_as_object::value, - "traits must provide `object_type as_object(const value_type&)`"); - static_assert(supports_as_array::value, - "traits must provide `array_type as_array(const value_type&)`"); - static_assert(supports_as_string::value, - "traits must provide `string_type as_string(const value_type&)`"); - static_assert(supports_as_number::value, - "traits must provide `number_type as_number(const value_type&)`"); - static_assert( - supports_as_integer::value, - "traits must provide `integer_type as_int(const value_type&)`"); - static_assert( - supports_as_boolean::value, - "traits must provide `boolean_type as_bool(const value_type&)`"); - static constexpr auto value = supports_get_type::value && supports_as_object::value && @@ -1748,62 +1936,45 @@ namespace jwt { 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(cmcarthur): Stream operators + // TODO(prince-chrismc): Stream operators }; - template - using has_mapped_type = typename traits_type::mapped_type; - - template - using has_key_type = typename traits_type::key_type; - - template - using has_value_type = typename traits_type::value_type; - - template - using has_iterator = typename object_type::iterator; - - template - using has_const_iterator = typename object_type::const_iterator; - - template - using is_begin_signature = - typename std::is_same().begin()), has_iterator>; + // https://stackoverflow.com/a/53967057/8480874 + template + struct is_iterable : std::false_type {}; - template - using is_begin_const_signature = - typename std::is_same().begin()), has_const_iterator>; - - template - struct supports_begin { - static constexpr auto value = - is_detected::value && is_detected::value && - is_begin_signature::value && is_begin_const_signature::value; + 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 { }; - template - using is_end_signature = - typename std::is_same().end()), has_iterator>; +#if __cplusplus > 201703L + template + inline constexpr bool is_iterable_v = is_iterable::value; +#endif - template - using is_end_const_signature = - typename std::is_same().end()), has_const_iterator>; + template + using is_count_signature = typename std::is_integral().count( + std::declval()))>; - template - struct supports_end { - static constexpr auto value = - is_detected::value && is_detected::value && - is_end_signature::value && is_end_const_signature::value; - }; + template + struct is_subcription_operator_signature : std::false_type {}; template - using is_count_signature = typename std::is_integral().count(std::declval()))>; - - template - using is_subcription_operator_signature = - typename std::is_same()[std::declval()]), - value_type&>; + 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 = @@ -1812,47 +1983,112 @@ namespace jwt { 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 = - is_detected::value && + std::is_constructible::value && + is_detected::value && std::is_same::value && - is_detected::value && - std::is_same::value && - supports_begin::value && supports_end::value && - is_count_signature::value && - is_subcription_operator_signature::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; - - static constexpr auto supports_claims_transform = - value && is_detected::value && - std::is_same>::value; }; template struct is_valid_json_array { - static constexpr auto value = std::is_same::value; + template + using value_type_t = typename T::value_type; + + static constexpr auto value = std::is_constructible::value && + is_iterable::value && + is_detected::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 + 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"); + "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_object::value && - is_valid_json_value::value && - is_valid_json_array::value; + 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 * - * The default template parameters use [picojson](https://github.com/kazuho/picojson) - * * \tparam json_traits : JSON implementation traits * * \see [RFC 7519: JSON Web Token (JWT)](https://tools.ietf.org/html/rfc7519) @@ -1865,12 +2101,16 @@ namespace jwt { * https://github.com/nlohmann/json/issues/774. It maybe be expanded to * support custom string types. */ - static_assert(std::is_same::value, - "string_type must be a std::string."); + 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, + details::is_valid_json_types::value, "must staisfy json container requirements"); static_assert(details::is_valid_traits::value, "traits must satisfy requirements"); @@ -1909,14 +2149,14 @@ namespace jwt { /** * Serialize claim to output stream from wrapped JSON value - * \return ouput stream + * \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 occured + * \throw std::logic_error An internal error occurred */ json::type get_type() const { return json_traits::get_type(val); } @@ -1928,11 +2168,18 @@ namespace jwt { typename json_traits::string_type as_string() const { return json_traits::as_string(val); } /** - * Get the contained JSON value as a date + * \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 { return std::chrono::system_clock::from_time_t(as_int()); } + 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 @@ -1959,14 +2206,14 @@ namespace jwt { * \return content as int * \throw std::bad_cast Content was not an int */ - typename json_traits::integer_type as_int() const { return json_traits::as_int(val); } + 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_bool() const { return json_traits::as_bool(val); } + typename json_traits::boolean_type as_boolean() const { return json_traits::as_boolean(val); } /** * Get the contained JSON value as a number @@ -1977,9 +2224,15 @@ namespace jwt { }; 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") {} }; @@ -1987,10 +2240,8 @@ namespace jwt { namespace details { template - class map_of_claims { + struct map_of_claims { typename json_traits::object_type claims; - - public: using basic_claim_t = basic_claim; using iterator = typename json_traits::object_type::iterator; using const_iterator = typename json_traits::object_type::const_iterator; @@ -2044,21 +2295,6 @@ namespace jwt { if (!has_claim(name)) throw error::claim_not_present_exception(); return basic_claim_t{claims.at(name)}; } - - std::unordered_map get_claims() const { - static_assert( - details::is_valid_json_object::supports_claims_transform, - "currently there is a limitation on the internal implemantation of the `object_type` to have an " - "`std::pair` like `value_type`"); - - std::unordered_map res; - std::transform(claims.begin(), claims.end(), std::inserter(res, res.end()), - [](const typename json_traits::object_type::value_type& val) { - return std::make_pair(val.first, basic_claim_t{val.second}); - }); - return res; - } }; } // namespace details @@ -2192,7 +2428,7 @@ namespace jwt { public: using basic_claim_t = basic_claim; /** - * Check if algortihm is present ("alg") + * Check if algorithm is present ("alg") * \return true if present, false otherwise */ bool has_algorithm() const noexcept { return has_header_claim("alg"); } @@ -2262,8 +2498,8 @@ namespace jwt { template class decoded_jwt : public header, public payload { protected: - /// Unmodifed token, as passed to constructor - const typename json_traits::string_type token; + /// 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 @@ -2290,8 +2526,8 @@ namespace jwt { * \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& token) { - return base::decode(base::pad(token)); + : decoded_jwt(token, [](const typename json_traits::string_type& str) { + return base::decode(base::pad(str)); }) {} #endif /** @@ -2359,18 +2595,34 @@ namespace jwt { */ const typename json_traits::string_type& get_signature_base64() const noexcept { return signature_base64; } /** - * Get all payload claims + * Get all payload as JSON object * \return map of claims */ - std::unordered_map get_payload_claims() const { - return this->payload_claims.get_claims(); - } + typename json_traits::object_type get_payload_json() const { return this->payload_claims.claims; } /** - * Get all header claims + * Get all header as JSON object * \return map of claims */ - std::unordered_map get_header_claims() const { - return this->header_claims.get_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); } }; @@ -2427,8 +2679,9 @@ namespace jwt { return *this; } /** - * Set algorithm claim + * \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 */ @@ -2452,7 +2705,8 @@ namespace jwt { return set_header_claim("cty", typename json_traits::value_type(str)); } /** - * Set key id claim + * \brief Set key id claim + * * \param str Key id to set * \return *this to allow for method chaining */ @@ -2603,46 +2857,277 @@ namespace jwt { #endif }; - /** - * Verifier class used to check if a decoded token contains all claims required by your application and has a valid - * signature. - */ - template - class verifier { - 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); + 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{}; + + // Helper method to get a claim from the jwt in this context + 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); + } + } + 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; + } + basic_claim get_claim(std::error_code& ec) const { return get_claim(false, ec); } + basic_claim get_claim(json::type t, std::error_code& ec) const { + return get_claim(false, t, ec); } }; - using basic_claim_t = basic_claim; - /// 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 + * This is the default operation and does case sensitive matching */ - explicit verifier(Clock c) : clock(c) {} - - /** - * Set default leeway to use. - * \param leeway Default leeway to use if not specified otherwise - * \return *this to allow chaining + 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 traited as a set of 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; + /** + * Verification function + * + * 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; @@ -2655,7 +3140,8 @@ namespace jwt { * \return *this to allow chaining */ verifier& expires_at_leeway(size_t leeway) { - return with_claim("exp", basic_claim_t(std::chrono::system_clock::from_time_t(leeway))); + claims["exp"] = verify_ops::date_before_claim{leeway}; + return *this; } /** * Set leeway for not before. @@ -2664,7 +3150,8 @@ namespace jwt { * \return *this to allow chaining */ verifier& not_before_leeway(size_t leeway) { - return with_claim("nbf", basic_claim_t(std::chrono::system_clock::from_time_t(leeway))); + claims["nbf"] = verify_ops::date_after_claim{leeway}; + return *this; } /** * Set leeway for issued at. @@ -2673,8 +3160,25 @@ namespace jwt { * \return *this to allow chaining */ verifier& issued_at_leeway(size_t leeway) { - return with_claim("iat", basic_claim_t(std::chrono::system_clock::from_time_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 casesensitive. + * + * \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 casesensitive. @@ -2684,6 +3188,7 @@ namespace jwt { 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 casesensitive. @@ -2700,7 +3205,8 @@ namespace jwt { * \return *this to allow chaining */ verifier& with_audience(const typename basic_claim_t::set_t& aud) { - return with_claim("aud", basic_claim_t(aud)); + claims["aud"] = verify_ops::is_subset_claim{aud}; + return *this; } /** * Set an audience to check for. @@ -2709,7 +3215,9 @@ namespace jwt { * \return *this to allow chaining */ verifier& with_audience(const typename json_traits::string_type& aud) { - return with_claim("aud", basic_claim_t(aud)); + typename basic_claim_t::set_t s; + s.insert(aud); + return with_audience(s); } /** * Set an id to check for. @@ -2718,15 +3226,26 @@ namespace jwt { * \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. + * \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. + * Specify a claim to check for equality (both type & value). * \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) { - claims[name] = c; - return *this; + return with_claim(name, verify_ops::equals_claim{c}); } /** @@ -2767,111 +3286,287 @@ namespace jwt { algs.at(algo)->verify(data, sig, ec); if (ec) return; - auto assert_claim_eq = [](const decoded_jwt& jwt, const typename json_traits::string_type& key, - const basic_claim_t& c, std::error_code& ec) { - if (!jwt.has_payload_claim(key)) { - ec = error::token_verification_error::missing_claim; - return; - } - auto jc = jwt.get_payload_claim(key); - if (jc.get_type() != c.get_type()) { - ec = error::token_verification_error::claim_type_missmatch; - return; - } - if (c.get_type() == json::type::integer) { - if (c.as_date() != jc.as_date()) { - ec = error::token_verification_error::claim_value_missmatch; - return; - } - } else if (c.get_type() == json::type::array) { - auto s1 = c.as_set(); - auto s2 = jc.as_set(); - if (s1.size() != s2.size()) { - ec = error::token_verification_error::claim_value_missmatch; - return; - } - auto it1 = s1.cbegin(); - auto it2 = s2.cbegin(); - while (it1 != s1.cend() && it2 != s2.cend()) { - if (*it1++ != *it2++) { - ec = error::token_verification_error::claim_value_missmatch; - return; - } - } - } else if (c.get_type() == json::type::object) { - if (json_traits::serialize(c.to_json()) != json_traits::serialize(jc.to_json())) { - ec = error::token_verification_error::claim_value_missmatch; - return; - } - } else if (c.get_type() == json::type::string) { - if (c.as_string() != jc.as_string()) { - ec = error::token_verification_error::claim_value_missmatch; - return; - } - } else - throw std::logic_error("internal error, should be unreachable"); - }; - - auto time = clock.now(); - - if (jwt.has_expires_at()) { - auto leeway = claims.count("exp") == 1 - ? std::chrono::system_clock::to_time_t(claims.at("exp").as_date()) - : default_leeway; - auto exp = jwt.get_expires_at(); - if (time > exp + std::chrono::seconds(leeway)) { - ec = error::token_verification_error::token_expired; - return; - } - } - if (jwt.has_issued_at()) { - auto leeway = claims.count("iat") == 1 - ? std::chrono::system_clock::to_time_t(claims.at("iat").as_date()) - : default_leeway; - auto iat = jwt.get_issued_at(); - if (time < iat - std::chrono::seconds(leeway)) { - ec = error::token_verification_error::token_expired; - return; - } - } - if (jwt.has_not_before()) { - auto leeway = claims.count("nbf") == 1 - ? std::chrono::system_clock::to_time_t(claims.at("nbf").as_date()) - : default_leeway; - auto nbf = jwt.get_not_before(); - if (time < nbf - std::chrono::seconds(leeway)) { - ec = error::token_verification_error::token_expired; - return; - } - } + verify_ops::verify_context ctx{clock.now(), jwt, default_leeway}; for (auto& c : claims) { - if (c.first == "exp" || c.first == "iat" || c.first == "nbf") { - // Nothing to do here, already checked - } else if (c.first == "aud") { - if (!jwt.has_audience()) { - ec = error::token_verification_error::audience_missmatch; - return; - } - auto aud = jwt.get_audience(); - typename basic_claim_t::set_t expected = {}; - if (c.second.get_type() == json::type::string) - expected = {c.second.as_string()}; - else - expected = c.second.as_set(); - for (auto& e : expected) { - if (aud.count(e) == 0) { - ec = error::token_verification_error::audience_missmatch; - return; - } - } - } else { - assert_claim_eq(jwt, c.first, c.second, ec); - if (ec) return; - } + 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 jwks 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 jwks claim + * \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); + } + + 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: + using jwk_t = jwk; + using jwt_vector_t = std::vector; + using iterator = typename jwt_vector_t::iterator; + using const_iterator = typename jwt_vector_t::const_iterator; + + 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 jwk_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 + */ + jwk_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: + jwt_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 jwk_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 @@ -2889,6 +3584,16 @@ namespace jwt { date now() const { return date::clock::now(); } }; + /** + * Create a verifier using the given 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 */ @@ -2922,108 +3627,15 @@ namespace jwt { return decoded_jwt(token); } -#ifndef JWT_DISABLE_PICOJSON - struct picojson_traits { - 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_int(const picojson::value& val) { - if (!val.is()) throw std::bad_cast(); - return val.get(); - } - - static bool as_bool(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(); } - }; - - /** - * Default JSON claim - * - * This type is the default 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{}); + template + jwk parse_jwk(const typename json_traits::string_type& token) { + return jwk(token); } - /** - * Return a picojson builder instance to create a new token - */ - inline builder create() { return builder(); } -#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 callabled, 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); + + template + jwks parse_jwks(const typename json_traits::string_type& token) { + return jwks(token); } -#endif } // namespace jwt template @@ -3036,4 +3648,8 @@ std::ostream& operator<<(std::ostream& os, const jwt::basic_claim& return os << c.to_json(); } +#ifndef JWT_DISABLE_PICOJSON +#include "traits/kazuho-picojson/defaults.h" +#endif + #endif diff --git a/dep/jwt-cpp/include/jwt-cpp/traits/boost-json/defaults.h b/dep/jwt-cpp/include/jwt-cpp/traits/boost-json/defaults.h new file mode 100644 index 0000000000..affeffe849 --- /dev/null +++ b/dep/jwt-cpp/include/jwt-cpp/traits/boost-json/defaults.h @@ -0,0 +1,88 @@ +#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{}); + } + + /** + * Return a builder instance to create a new token + */ + inline builder create() { return builder(); } + +#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 callabled, 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 diff --git a/dep/jwt-cpp/include/jwt-cpp/traits/boost-json/traits.h b/dep/jwt-cpp/include/jwt-cpp/traits/boost-json/traits.h new file mode 100644 index 0000000000..3b27d5f2f7 --- /dev/null +++ b/dep/jwt-cpp/include/jwt-cpp/traits/boost-json/traits.h @@ -0,0 +1,80 @@ +#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 { + namespace traits { + namespace json = 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 diff --git a/dep/jwt-cpp/include/jwt-cpp/traits/danielaparker-jsoncons/defaults.h b/dep/jwt-cpp/include/jwt-cpp/traits/danielaparker-jsoncons/defaults.h new file mode 100644 index 0000000000..47e12f5f0b --- /dev/null +++ b/dep/jwt-cpp/include/jwt-cpp/traits/danielaparker-jsoncons/defaults.h @@ -0,0 +1,88 @@ +#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{}); + } + + /** + * Return a builder instance to create a new token + */ + inline builder create() { return builder(); } + +#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 callabled, 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 diff --git a/dep/jwt-cpp/include/jwt-cpp/traits/danielaparker-jsoncons/traits.h b/dep/jwt-cpp/include/jwt-cpp/traits/danielaparker-jsoncons/traits.h new file mode 100644 index 0000000000..be56740ca0 --- /dev/null +++ b/dep/jwt-cpp/include/jwt-cpp/traits/danielaparker-jsoncons/traits.h @@ -0,0 +1,123 @@ +#define JWT_DISABLE_PICOJSON +#define JSONCONS_NO_DEPRECATED + +#include "jwt-cpp/jwt.h" + +#include "jsoncons/json.hpp" + +#include + +namespace jwt { + namespace traits { + 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; + } + }; + using array_type = json::array; + 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 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 diff --git a/dep/jwt-cpp/include/jwt-cpp/traits/defaults.h.mustache b/dep/jwt-cpp/include/jwt-cpp/traits/defaults.h.mustache new file mode 100644 index 0000000000..ab2a84721e --- /dev/null +++ b/dep/jwt-cpp/include/jwt-cpp/traits/defaults.h.mustache @@ -0,0 +1,90 @@ +#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{}); + } + + /** + * Return a builder instance to create a new token + */ + inline builder create() { return builder(); } + +#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 callabled, 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 diff --git a/dep/jwt-cpp/include/jwt-cpp/traits/kazuho-picojson/defaults.h b/dep/jwt-cpp/include/jwt-cpp/traits/kazuho-picojson/defaults.h new file mode 100644 index 0000000000..0c82133a67 --- /dev/null +++ b/dep/jwt-cpp/include/jwt-cpp/traits/kazuho-picojson/defaults.h @@ -0,0 +1,84 @@ +#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{}); + } + + /** + * Return a builder instance to create a new token + */ + inline builder create() { return builder(); } + +#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 callabled, 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 diff --git a/dep/jwt-cpp/include/jwt-cpp/traits/kazuho-picojson/traits.h b/dep/jwt-cpp/include/jwt-cpp/traits/kazuho-picojson/traits.h new file mode 100644 index 0000000000..2f96bfc014 --- /dev/null +++ b/dep/jwt-cpp/include/jwt-cpp/traits/kazuho-picojson/traits.h @@ -0,0 +1,76 @@ +#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 { + namespace traits { + 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 diff --git a/dep/jwt-cpp/include/jwt-cpp/traits/nlohmann-json/defaults.h b/dep/jwt-cpp/include/jwt-cpp/traits/nlohmann-json/defaults.h new file mode 100644 index 0000000000..c324075f87 --- /dev/null +++ b/dep/jwt-cpp/include/jwt-cpp/traits/nlohmann-json/defaults.h @@ -0,0 +1,88 @@ +#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{}); + } + + /** + * Return a builder instance to create a new token + */ + inline builder create() { return builder(); } + +#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 callabled, 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 diff --git a/dep/jwt-cpp/include/jwt-cpp/traits/nlohmann-json/traits.h b/dep/jwt-cpp/include/jwt-cpp/traits/nlohmann-json/traits.h new file mode 100644 index 0000000000..7cf486902e --- /dev/null +++ b/dep/jwt-cpp/include/jwt-cpp/traits/nlohmann-json/traits.h @@ -0,0 +1,77 @@ +#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 { + namespace traits { + 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 diff --git a/dep/jwt-cpp/nuget/jwt-cpp.nuspec b/dep/jwt-cpp/nuget/jwt-cpp.nuspec new file mode 100644 index 0000000000..26957cb65b --- /dev/null +++ b/dep/jwt-cpp/nuget/jwt-cpp.nuspec @@ -0,0 +1,29 @@ + + + + jwt-cpp + 0.6.0-nuget.3 + Thalhammer; prince-chrismc + Thalhammer; prince-chrismc + https://github.com/Thalhammer/jwt-cpp + JWT++ is a header only library for creating and validating JSON Web Tokens in C++11. This library supports all the algorithms defined by the JWT specifications, and the modular design allows to easily add additional algorithms without any problems. In the name of flexibility and extensibility, jwt-cpp supports OpenSSL, LibreSSL, and wolfSSL. And there is no hard dependency on a JSON library. + + Supporting OpenSSL 3.0.0, WolfSSL, Hunter CMake, Boost.JSON, JWKs, ES256K. + MIT + Copyright (c) 2018 Dominik Thalhammer + JWT++: JSON Web Tokens in C++11 + JWT++; a header only library for creating and validating JSON Web Tokens in C++11. + JWT, json, web, token, C++, header-only + + + + + + + + + + + + + diff --git a/dep/jwt-cpp/nuget/jwt-cpp.targets b/dep/jwt-cpp/nuget/jwt-cpp.targets new file mode 100644 index 0000000000..a40f2b306f --- /dev/null +++ b/dep/jwt-cpp/nuget/jwt-cpp.targets @@ -0,0 +1,8 @@ + + + + + $(MSBuildThisFileDirectory)..\..\lib\native\include\;%(AdditionalIncludeDirectories) + + + \ No newline at end of file diff --git a/dep/jwt-cpp/tests/BaseTest.cpp b/dep/jwt-cpp/tests/BaseTest.cpp new file mode 100644 index 0000000000..210798afc8 --- /dev/null +++ b/dep/jwt-cpp/tests/BaseTest.cpp @@ -0,0 +1,110 @@ +#include "jwt-cpp/base.h" +#include + +TEST(BaseTest, Base64Index) { + ASSERT_EQ(0, jwt::alphabet::index(jwt::alphabet::base64::data(), 'A')); + ASSERT_EQ(32, jwt::alphabet::index(jwt::alphabet::base64::data(), 'g')); + ASSERT_EQ(62, jwt::alphabet::index(jwt::alphabet::base64::data(), '+')); +} + +TEST(BaseTest, Base64URLIndex) { + ASSERT_EQ(0, jwt::alphabet::index(jwt::alphabet::base64url::data(), 'A')); + ASSERT_EQ(32, jwt::alphabet::index(jwt::alphabet::base64url::data(), 'g')); + ASSERT_EQ(62, jwt::alphabet::index(jwt::alphabet::base64url::data(), '-')); +} + +TEST(BaseTest, BaseDetailsCountPadding) { + using jwt::base::details::padding; + ASSERT_EQ(padding{}, jwt::base::details::count_padding("ABC", {"~"})); + ASSERT_EQ((padding{3, 3}), jwt::base::details::count_padding("ABC~~~", {"~"})); + ASSERT_EQ((padding{5, 5}), jwt::base::details::count_padding("ABC~~~~~", {"~"})); + + ASSERT_EQ(padding{}, jwt::base::details::count_padding("ABC", {"~", "!"})); + ASSERT_EQ((padding{1, 1}), jwt::base::details::count_padding("ABC!", {"~", "!"})); + ASSERT_EQ((padding{1, 1}), jwt::base::details::count_padding("ABC~", {"~", "!"})); + ASSERT_EQ((padding{3, 3}), jwt::base::details::count_padding("ABC~~!", {"~", "!"})); + ASSERT_EQ((padding{3, 3}), jwt::base::details::count_padding("ABC!~~", {"~", "!"})); + ASSERT_EQ((padding{5, 5}), jwt::base::details::count_padding("ABC~~!~~", {"~", "!"})); + + ASSERT_EQ((padding{2, 6}), jwt::base::details::count_padding("MTIzNA%3d%3d", {"%3d", "%3D"})); + ASSERT_EQ((padding{2, 6}), jwt::base::details::count_padding("MTIzNA%3d%3D", {"%3d", "%3D"})); + ASSERT_EQ((padding{2, 6}), jwt::base::details::count_padding("MTIzNA%3D%3d", {"%3d", "%3D"})); + ASSERT_EQ((padding{2, 6}), jwt::base::details::count_padding("MTIzNA%3D%3D", {"%3d", "%3D"})); + + // Some fake scenarios + + ASSERT_EQ(padding{}, jwt::base::details::count_padding("", {"~"})); + ASSERT_EQ(padding{}, jwt::base::details::count_padding("ABC", {"~", "~~!"})); + ASSERT_EQ(padding{}, jwt::base::details::count_padding("ABC!", {"~", "~~!"})); + ASSERT_EQ((padding{1, 1}), jwt::base::details::count_padding("ABC~", {"~", "~~!"})); + ASSERT_EQ((padding{1, 3}), jwt::base::details::count_padding("ABC~~!", {"~", "~~!"})); + ASSERT_EQ((padding{2, 2}), jwt::base::details::count_padding("ABC!~~", {"~", "~~!"})); + ASSERT_EQ((padding{3, 5}), jwt::base::details::count_padding("ABC~~!~~", {"~", "~~!"})); + ASSERT_EQ(padding{}, jwt::base::details::count_padding("ABC~~!~~", {})); +} + +TEST(BaseTest, Base64Decode) { + ASSERT_EQ("1", jwt::base::decode("MQ==")); + ASSERT_EQ("12", jwt::base::decode("MTI=")); + ASSERT_EQ("123", jwt::base::decode("MTIz")); + ASSERT_EQ("1234", jwt::base::decode("MTIzNA==")); +} + +TEST(BaseTest, Base64DecodeURL) { + ASSERT_EQ("1", jwt::base::decode("MQ%3d%3d")); + ASSERT_EQ("12", jwt::base::decode("MTI%3d")); + ASSERT_EQ("123", jwt::base::decode("MTIz")); + ASSERT_EQ("1234", jwt::base::decode("MTIzNA%3d%3d")); +} + +TEST(BaseTest, Base64DecodeURLCaseInsensitive) { + ASSERT_EQ("1", jwt::base::decode("MQ%3d%3d")); + ASSERT_EQ("1", jwt::base::decode("MQ%3D%3d")); + ASSERT_EQ("1", jwt::base::decode("MQ%3d%3D")); + ASSERT_EQ("12", jwt::base::decode("MTI%3d")); + ASSERT_EQ("123", jwt::base::decode("MTIz")); + ASSERT_EQ("1234", jwt::base::decode("MTIzNA%3d%3d")); + ASSERT_EQ("1234", jwt::base::decode("MTIzNA%3D%3D")); +} + +TEST(BaseTest, Base64Encode) { + ASSERT_EQ("MQ==", jwt::base::encode("1")); + ASSERT_EQ("MTI=", jwt::base::encode("12")); + ASSERT_EQ("MTIz", jwt::base::encode("123")); + ASSERT_EQ("MTIzNA==", jwt::base::encode("1234")); +} + +TEST(BaseTest, Base64EncodeURL) { + ASSERT_EQ("MQ%3d%3d", jwt::base::encode("1")); + ASSERT_EQ("MTI%3d", jwt::base::encode("12")); + ASSERT_EQ("MTIz", jwt::base::encode("123")); + ASSERT_EQ("MTIzNA%3d%3d", jwt::base::encode("1234")); +} + +TEST(BaseTest, Base64Pad) { + ASSERT_EQ("MQ==", jwt::base::pad("MQ")); + ASSERT_EQ("MTI=", jwt::base::pad("MTI")); + ASSERT_EQ("MTIz", jwt::base::pad("MTIz")); + ASSERT_EQ("MTIzNA==", jwt::base::pad("MTIzNA")); +} + +TEST(BaseTest, Base64PadURL) { + ASSERT_EQ("MQ%3d%3d", jwt::base::pad("MQ")); + ASSERT_EQ("MTI%3d", jwt::base::pad("MTI")); + ASSERT_EQ("MTIz", jwt::base::pad("MTIz")); + ASSERT_EQ("MTIzNA%3d%3d", jwt::base::pad("MTIzNA")); +} + +TEST(BaseTest, Base64Trim) { + ASSERT_EQ("MQ", jwt::base::trim("MQ==")); + ASSERT_EQ("MTI", jwt::base::trim("MTI=")); + ASSERT_EQ("MTIz", jwt::base::trim("MTIz")); + ASSERT_EQ("MTIzNA", jwt::base::trim("MTIzNA==")); +} + +TEST(BaseTest, Base64TrimURL) { + ASSERT_EQ("MQ", jwt::base::trim("MQ%3d%3d")); + ASSERT_EQ("MTI", jwt::base::trim("MTI%3d")); + ASSERT_EQ("MTIz", jwt::base::trim("MTIz")); + ASSERT_EQ("MTIzNA", jwt::base::trim("MTIzNA%3d%3d")); +} diff --git a/dep/jwt-cpp/tests/CMakeLists.txt b/dep/jwt-cpp/tests/CMakeLists.txt new file mode 100644 index 0000000000..cb7eaddc80 --- /dev/null +++ b/dep/jwt-cpp/tests/CMakeLists.txt @@ -0,0 +1,66 @@ +if(JWT_DISABLE_BASE64) + message(FATAL_ERROR "Tests requires the base64 support to be enabled!") +endif() + +if(JWT_DISABLE_PICOJSON) + message(FATAL_ERROR "Tests requires the picojson support to be enabled!") +endif() + +enable_testing() +include(GoogleTest) +if(HUNTER_ENABLED) + hunter_add_package(GTest) +endif() +find_package(GTest REQUIRED) + +set(TEST_SOURCES + ${CMAKE_CURRENT_SOURCE_DIR}/BaseTest.cpp ${CMAKE_CURRENT_SOURCE_DIR}/ClaimTest.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/Keys.cpp ${CMAKE_CURRENT_SOURCE_DIR}/HelperTest.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/TestMain.cpp ${CMAKE_CURRENT_SOURCE_DIR}/TokenFormatTest.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/TokenTest.cpp ${CMAKE_CURRENT_SOURCE_DIR}/JwksTest.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/OpenSSLErrorTest.cpp ${CMAKE_CURRENT_SOURCE_DIR}/traits/NlohmannTest.cpp) + +find_package(jsoncons CONFIG) +if(TARGET jsoncons) + list(APPEND TEST_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/traits/JsonconsTest.cpp) +endif() + +include("private-find-boost-json") +if(TARGET boost_json) + list(APPEND TEST_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/traits/BoostJsonTest.cpp) +endif() + +add_executable(jwt-cpp-test ${TEST_SOURCES}) + +# NOTE: Don't use space inside a generator expression here, because the function prematurely breaks the expression into +# multiple lines. https://cmake.org/pipermail/cmake/2018-April/067422.html +set(JWT_TESTER_GCC_FLAGS -Wall -Wextra -Wpedantic) +set(JWT_TESTER_CLANG_FLAGS -Weverything -Wno-c++98-compat -Wno-global-constructors -Wno-weak-vtables) +target_compile_options( + jwt-cpp-test PRIVATE $<$:/W4> $<$:${JWT_TESTER_GCC_FLAGS}> + $<$:${JWT_TESTER_CLANG_FLAGS}>) +if(HUNTER_ENABLED) + target_link_libraries(jwt-cpp-test PRIVATE GTest::gtest GTest::gtest_main) + # Define a compile define to bypass openssl error tests + target_compile_definitions(jwt-cpp-test PRIVATE HUNTER_ENABLED=1) +else() + target_link_libraries(jwt-cpp-test PRIVATE GTest::GTest GTest::Main) + if(TARGET jsoncons) + target_link_libraries(jwt-cpp-test PRIVATE jsoncons) + endif() + if(TARGET boost_json) + target_link_libraries(jwt-cpp-test PRIVATE boost_json) + endif() +endif() +target_link_libraries(jwt-cpp-test PRIVATE jwt-cpp nlohmann_json::nlohmann_json + $<$>:${CMAKE_DL_LIBS}>) + +gtest_add_tests(TARGET jwt-cpp-test) + +if(JWT_ENABLE_COVERAGE) + include("code-coverage") + setup_coverage(jwt-cpp-test) + set(COVERAGE_EXCLUDES "/usr/**" "/home/*/.conan/**" "*test*" "*build*" "**/nlohmann/json.hpp" + "**/picojson/picojson.h" "*boost*" "*jsoncons*") + setup_target_for_coverage_lcov(NAME coverage EXECUTABLE ${CMAKE_CURRENT_BINARY_DIR}/jwt-cpp-test) +endif() diff --git a/dep/jwt-cpp/tests/ClaimTest.cpp b/dep/jwt-cpp/tests/ClaimTest.cpp new file mode 100644 index 0000000000..e7cccc3b59 --- /dev/null +++ b/dep/jwt-cpp/tests/ClaimTest.cpp @@ -0,0 +1,141 @@ +#include "jwt-cpp/jwt.h" +#include + +TEST(ClaimTest, AudienceAsString) { + std::string token = + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJ0ZXN0In0.WZnM3SIiSRHsbO3O7Z2bmIzTJ4EC32HRBKfLznHhrh4"; + auto decoded = jwt::decode(token); + + ASSERT_TRUE(decoded.has_algorithm()); + ASSERT_TRUE(decoded.has_type()); + ASSERT_FALSE(decoded.has_content_type()); + ASSERT_FALSE(decoded.has_key_id()); + ASSERT_FALSE(decoded.has_issuer()); + ASSERT_FALSE(decoded.has_subject()); + ASSERT_TRUE(decoded.has_audience()); + ASSERT_FALSE(decoded.has_expires_at()); + ASSERT_FALSE(decoded.has_not_before()); + ASSERT_FALSE(decoded.has_issued_at()); + ASSERT_FALSE(decoded.has_id()); + + ASSERT_EQ("HS256", decoded.get_algorithm()); + ASSERT_EQ("JWT", decoded.get_type()); + auto aud = decoded.get_audience(); + ASSERT_EQ(1, aud.size()); + ASSERT_EQ("test", *aud.begin()); +} + +TEST(ClaimTest, SetAudienceAsString) { + auto token = jwt::create().set_type("JWT").set_audience("test").sign(jwt::algorithm::hs256("test")); + ASSERT_EQ("eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJ0ZXN0In0.ny5Fa0vzAg7tNL95KWg_ecBNd3XP3tdAzq0SFA6diY4", + token); +} + +TEST(ClaimTest, AudienceAsSet) { + std::string token = "eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJhdWQiOlsidGVzdCIsInRlc3QyIl19."; + auto decoded = jwt::decode(token); + + ASSERT_TRUE(decoded.has_algorithm()); + ASSERT_TRUE(decoded.has_type()); + ASSERT_FALSE(decoded.has_content_type()); + ASSERT_FALSE(decoded.has_key_id()); + ASSERT_FALSE(decoded.has_issuer()); + ASSERT_FALSE(decoded.has_subject()); + ASSERT_TRUE(decoded.has_audience()); + ASSERT_FALSE(decoded.has_expires_at()); + ASSERT_FALSE(decoded.has_not_before()); + ASSERT_FALSE(decoded.has_issued_at()); + ASSERT_FALSE(decoded.has_id()); + + ASSERT_EQ("none", decoded.get_algorithm()); + ASSERT_EQ("JWT", decoded.get_type()); + auto aud = decoded.get_audience(); + ASSERT_EQ(2, aud.size()); + ASSERT_TRUE(aud.count("test") > 0); + ASSERT_TRUE(aud.count("test2") > 0); +} + +TEST(ClaimTest, SetAudienceAsSet) { + auto token = jwt::create() + .set_type("JWT") + .set_audience({{picojson::value("test"), picojson::value("test2")}}) + .sign(jwt::algorithm::none{}); + ASSERT_EQ("eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJhdWQiOlsidGVzdCIsInRlc3QyIl19.", token); +} + +TEST(ClaimTest, SetArray) { + std::vector vect = {100, 20, 10}; + auto token = + jwt::create().set_payload_claim("test", jwt::claim(vect.begin(), vect.end())).sign(jwt::algorithm::none{}); + ASSERT_EQ(token, "eyJhbGciOiJub25lIn0.eyJ0ZXN0IjpbMTAwLDIwLDEwXX0."); +} + +TEST(ClaimTest, SetObject) { + std::istringstream iss{"{\"api-x\": [1]}"}; + jwt::claim object; + iss >> object; + ASSERT_EQ(object.get_type(), jwt::json::type::object); + + auto token = jwt::create().set_payload_claim("namespace", object).sign(jwt::algorithm::hs256("test")); + ASSERT_EQ(token, + "eyJhbGciOiJIUzI1NiJ9.eyJuYW1lc3BhY2UiOnsiYXBpLXgiOlsxXX19.F8I6I2RcSF98bKa0IpIz09fRZtHr1CWnWKx2za-tFQA"); +} + +TEST(ClaimTest, SetAlgorithm) { + auto token = jwt::create().set_algorithm("test").sign(jwt::algorithm::none{}); + + auto decoded_token = jwt::decode(token); + ASSERT_EQ(decoded_token.get_algorithm(), "test"); +} + +TEST(ClaimTest, AsInt) { + jwt::claim c(picojson::value(static_cast(10))); + ASSERT_EQ(c.as_integer(), 10); +} + +TEST(ClaimTest, AsDate) { + jwt::claim c(picojson::value(static_cast(10))); + ASSERT_EQ(c.as_date(), std::chrono::system_clock::from_time_t(10)); +} + +TEST(ClaimTest, PicoJSONTraitsAccessorsThrow) { + jwt::traits::kazuho_picojson::value_type val; + ASSERT_THROW(jwt::traits::kazuho_picojson::as_array(val), std::bad_cast); + ASSERT_THROW(jwt::traits::kazuho_picojson::as_boolean(val), std::bad_cast); + ASSERT_THROW(jwt::traits::kazuho_picojson::as_integer(val), std::bad_cast); + ASSERT_THROW(jwt::traits::kazuho_picojson::as_number(val), std::bad_cast); + ASSERT_THROW(jwt::traits::kazuho_picojson::as_object(val), std::bad_cast); + ASSERT_THROW(jwt::traits::kazuho_picojson::as_string(val), std::bad_cast); + ASSERT_THROW(jwt::traits::kazuho_picojson::get_type(val), std::logic_error); +} + +TEST(ClaimTest, PicoJSONTraitsAsBool) { + jwt::traits::kazuho_picojson::value_type val(true); + ASSERT_EQ(jwt::traits::kazuho_picojson::as_boolean(val), true); + ASSERT_EQ(jwt::traits::kazuho_picojson::get_type(val), jwt::json::type::boolean); +} + +TEST(ClaimTest, PicoJSONTraitsAsDouble) { + jwt::traits::kazuho_picojson::value_type val(10.0); + ASSERT_EQ(jwt::traits::kazuho_picojson::as_number(val), (int)10); + ASSERT_EQ(jwt::traits::kazuho_picojson::get_type(val), jwt::json::type::number); +} + +TEST(ClaimTest, MapOfClaim) { + using map = jwt::details::map_of_claims; + ASSERT_THROW(map::parse_claims(R"##(__ not json __)##"), jwt::error::invalid_json_exception); + const map claims{ + map::parse_claims(R"##({ "array": [1], "string" : "hello world", "number": 9.9, "bool": true})##")}; + + ASSERT_TRUE(claims.has_claim("array")); + ASSERT_TRUE(claims.has_claim("string")); + ASSERT_TRUE(claims.has_claim("number")); + ASSERT_TRUE(claims.has_claim("bool")); + ASSERT_FALSE(claims.has_claim("__missing__")); + + ASSERT_EQ(map::basic_claim_t{claims.get_claim("array").as_array().at(0)}.as_integer(), (int)1); + ASSERT_EQ(claims.get_claim("string").as_string(), "hello world"); + ASSERT_EQ(claims.get_claim("number").as_number(), 9.9); + ASSERT_EQ(claims.get_claim("bool").as_boolean(), true); + ASSERT_THROW(claims.get_claim("__missing__"), jwt::error::claim_not_present_exception); +} diff --git a/dep/jwt-cpp/tests/HelperTest.cpp b/dep/jwt-cpp/tests/HelperTest.cpp new file mode 100644 index 0000000000..558ca90bbb --- /dev/null +++ b/dep/jwt-cpp/tests/HelperTest.cpp @@ -0,0 +1,205 @@ +#include "jwt-cpp/jwt.h" +#include + +namespace { + extern std::string google_cert; + extern std::string google_cert_base64_der; + extern std::string google_public_key; +} // namespace + +TEST(HelperTest, Cert2Pubkey) { + auto key = jwt::helper::extract_pubkey_from_cert(google_cert); + ASSERT_EQ(google_public_key, key); +} + +TEST(HelperTest, Base64DER2PemCert) { + auto cert_pem = jwt::helper::convert_base64_der_to_pem(google_cert_base64_der); + ASSERT_EQ(google_cert, cert_pem); +} + +TEST(HelperTest, DER2PemCert) { + auto decoded = + jwt::base::decode(jwt::base::pad(google_cert_base64_der)); + auto cert_pem = jwt::helper::convert_der_to_pem(decoded); + ASSERT_EQ(google_cert, cert_pem); +} + +TEST(HelperTest, ErrorCodeMessages) { + ASSERT_EQ(std::error_code(jwt::error::rsa_error::ok).message(), "no error"); + ASSERT_EQ(std::error_code(static_cast(-1)).message(), "unknown RSA error"); + ASSERT_EQ(std::error_code(jwt::error::rsa_error::ok).category().name(), std::string("rsa_error")); + + ASSERT_EQ(std::error_code(jwt::error::ecdsa_error::ok).message(), "no error"); + ASSERT_EQ(std::error_code(static_cast(-1)).message(), "unknown ECDSA error"); + ASSERT_EQ(std::error_code(jwt::error::ecdsa_error::ok).category().name(), std::string("ecdsa_error")); + + ASSERT_EQ(std::error_code(jwt::error::signature_verification_error::ok).message(), "no error"); + ASSERT_EQ(std::error_code(static_cast(-1)).message(), + "unknown signature verification error"); + ASSERT_EQ(std::error_code(jwt::error::signature_verification_error::ok).category().name(), + std::string("signature_verification_error")); + + ASSERT_EQ(std::error_code(jwt::error::signature_generation_error::ok).message(), "no error"); + ASSERT_EQ(std::error_code(static_cast(-1)).message(), + "unknown signature generation error"); + ASSERT_EQ(std::error_code(jwt::error::signature_generation_error::ok).category().name(), + std::string("signature_generation_error")); + + ASSERT_EQ(std::error_code(jwt::error::token_verification_error::ok).message(), "no error"); + ASSERT_EQ(std::error_code(static_cast(-1)).message(), + "unknown token verification error"); + ASSERT_EQ(std::error_code(jwt::error::token_verification_error::ok).category().name(), + std::string("token_verification_error")); + + int i = 10; + for (i = 10; i < 19; i++) { + ASSERT_NE(std::error_code(static_cast(i)).message(), + std::error_code(static_cast(-1)).message()); + } + ASSERT_EQ(std::error_code(static_cast(i)).message(), + std::error_code(static_cast(-1)).message()); + + for (i = 10; i < 17; i++) { + ASSERT_NE(std::error_code(static_cast(i)).message(), + std::error_code(static_cast(-1)).message()); + } + ASSERT_EQ(std::error_code(static_cast(i)).message(), + std::error_code(static_cast(-1)).message()); + + for (i = 10; i < 18; i++) { + ASSERT_NE(std::error_code(static_cast(i)).message(), + std::error_code(static_cast(-1)).message()); + } + ASSERT_EQ(std::error_code(static_cast(i)).message(), + std::error_code(static_cast(-1)).message()); + + for (i = 10; i < 24; i++) { + ASSERT_NE(std::error_code(static_cast(i)).message(), + std::error_code(static_cast(-1)).message()); + } + ASSERT_EQ(std::error_code(static_cast(i)).message(), + std::error_code(static_cast(-1)).message()); + + for (i = 10; i < 16; i++) { + ASSERT_NE(std::error_code(static_cast(i)).message(), + std::error_code(static_cast(-1)).message()); + } + ASSERT_EQ(std::error_code(static_cast(i)).message(), + std::error_code(static_cast(-1)).message()); +} + +namespace { + std::string google_cert = +// This is to handle the different subject alternate name ordering +// see https://github.com/wolfSSL/wolfssl/issues/4397 +#ifdef LIBWOLFSSL_VERSION_HEX + R"(-----BEGIN CERTIFICATE----- +MIIFfTCCBOagAwIBAgIKYFOB9QABAACIvTANBgkqhkiG9w0BAQUFADBGMQswCQYD +VQQGEwJVUzETMBEGA1UEChMKR29vZ2xlIEluYzEiMCAGA1UEAxMZR29vZ2xlIElu +dGVybmV0IEF1dGhvcml0eTAeFw0xMzA1MjIxNTQ5MDRaFw0xMzEwMzEyMzU5NTla +MGYxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1N +b3VudGFpbiBWaWV3MRMwEQYDVQQKEwpHb29nbGUgSW5jMRUwEwYDVQQDDAwqLmdv +b2dsZS5jb20wWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARmSpIUbCqhUBq1UwnR +Ai7/TNSk6W8JmasR+I0r/NLDYv5yApbAz8HXXN8hDdurMRP6Jy1Q0UIKmyls8HPH +exoCo4IDlzCCA5MwggLDBgNVHREEggK6MIICtoIMKi5nb29nbGUuY29tgg0qLmFu +ZHJvaWQuY29tghYqLmFwcGVuZ2luZS5nb29nbGUuY29tghIqLmNsb3VkLmdvb2ds +ZS5jb22CFiouZ29vZ2xlLWFuYWx5dGljcy5jb22CCyouZ29vZ2xlLmNhggsqLmdv +b2dsZS5jbIIOKi5nb29nbGUuY28uaW6CDiouZ29vZ2xlLmNvLmpwgg4qLmdvb2ds +ZS5jby51a4IPKi5nb29nbGUuY29tLmFygg8qLmdvb2dsZS5jb20uYXWCDyouZ29v +Z2xlLmNvbS5icoIPKi5nb29nbGUuY29tLmNvgg8qLmdvb2dsZS5jb20ubXiCDyou +Z29vZ2xlLmNvbS50coIPKi5nb29nbGUuY29tLnZuggsqLmdvb2dsZS5kZYILKi5n +b29nbGUuZXOCCyouZ29vZ2xlLmZyggsqLmdvb2dsZS5odYILKi5nb29nbGUuaXSC +CyouZ29vZ2xlLm5sggsqLmdvb2dsZS5wbIILKi5nb29nbGUucHSCDyouZ29vZ2xl +YXBpcy5jboIUKi5nb29nbGVjb21tZXJjZS5jb22CDSouZ3N0YXRpYy5jb22CDCou +dXJjaGluLmNvbYIQKi51cmwuZ29vZ2xlLmNvbYIWKi55b3V0dWJlLW5vY29va2ll +LmNvbYINKi55b3V0dWJlLmNvbYIWKi55b3V0dWJlZWR1Y2F0aW9uLmNvbYILKi55 +dGltZy5jb22CC2FuZHJvaWQuY29tggRnLmNvggZnb28uZ2yCFGdvb2dsZS1hbmFs +eXRpY3MuY29tggpnb29nbGUuY29tghJnb29nbGVjb21tZXJjZS5jb22CCnVyY2hp +bi5jb22CCHlvdXR1LmJlggt5b3V0dWJlLmNvbYIUeW91dHViZWVkdWNhdGlvbi5j +b20wHQYDVR0OBBYEFFN409DVTUYFOWYp0Rxq5cqBhJ6GMB8GA1UdIwQYMBaAFL/A +MOv1QxE+Z7qekfv8atrjaxIkMFsGA1UdHwRUMFIwUKBOoEyGSmh0dHA6Ly93d3cu +Z3N0YXRpYy5jb20vR29vZ2xlSW50ZXJuZXRBdXRob3JpdHkvR29vZ2xlSW50ZXJu +ZXRBdXRob3JpdHkuY3JsMA4GA1UdDwEB/wQEAwIHgDAdBgNVHSUEFjAUBggrBgEF +BQcDAQYIKwYBBQUHAwIwDQYJKoZIhvcNAQEFBQADgYEAAyfQrePfKEL5f67KHp4A +FrYSrvGJjn+ZQHzzoivX2/GW5Iw0oPqf+Jjy8eO2ufQGG5bz++YnK50WQhwQNRgT +iK9AyWx8gvtI4bDo569UuabTZqJEdaPlPbQOz5I6m55hQbc0Fwc//kjOFRghAlsb +OVBpRpkExpB4LxmyUw0tYjY= +-----END CERTIFICATE----- +)"; +#else + R"(-----BEGIN CERTIFICATE----- +MIIF8DCCBVmgAwIBAgIKYFOB9QABAACIvTANBgkqhkiG9w0BAQUFADBGMQswCQYD +VQQGEwJVUzETMBEGA1UEChMKR29vZ2xlIEluYzEiMCAGA1UEAxMZR29vZ2xlIElu +dGVybmV0IEF1dGhvcml0eTAeFw0xMzA1MjIxNTQ5MDRaFw0xMzEwMzEyMzU5NTla +MGYxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1N +b3VudGFpbiBWaWV3MRMwEQYDVQQKEwpHb29nbGUgSW5jMRUwEwYDVQQDFAwqLmdv +b2dsZS5jb20wWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARmSpIUbCqhUBq1UwnR +Ai7/TNSk6W8JmasR+I0r/NLDYv5yApbAz8HXXN8hDdurMRP6Jy1Q0UIKmyls8HPH +exoCo4IECjCCBAYwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMAsGA1Ud +DwQEAwIHgDAdBgNVHQ4EFgQUU3jT0NVNRgU5ZinRHGrlyoGEnoYwHwYDVR0jBBgw +FoAUv8Aw6/VDET5nup6R+/xq2uNrEiQwWwYDVR0fBFQwUjBQoE6gTIZKaHR0cDov +L3d3dy5nc3RhdGljLmNvbS9Hb29nbGVJbnRlcm5ldEF1dGhvcml0eS9Hb29nbGVJ +bnRlcm5ldEF1dGhvcml0eS5jcmwwZgYIKwYBBQUHAQEEWjBYMFYGCCsGAQUFBzAC +hkpodHRwOi8vd3d3LmdzdGF0aWMuY29tL0dvb2dsZUludGVybmV0QXV0aG9yaXR5 +L0dvb2dsZUludGVybmV0QXV0aG9yaXR5LmNydDAMBgNVHRMBAf8EAjAAMIICwwYD +VR0RBIICujCCAraCDCouZ29vZ2xlLmNvbYINKi5hbmRyb2lkLmNvbYIWKi5hcHBl +bmdpbmUuZ29vZ2xlLmNvbYISKi5jbG91ZC5nb29nbGUuY29tghYqLmdvb2dsZS1h +bmFseXRpY3MuY29tggsqLmdvb2dsZS5jYYILKi5nb29nbGUuY2yCDiouZ29vZ2xl +LmNvLmlugg4qLmdvb2dsZS5jby5qcIIOKi5nb29nbGUuY28udWuCDyouZ29vZ2xl +LmNvbS5hcoIPKi5nb29nbGUuY29tLmF1gg8qLmdvb2dsZS5jb20uYnKCDyouZ29v +Z2xlLmNvbS5jb4IPKi5nb29nbGUuY29tLm14gg8qLmdvb2dsZS5jb20udHKCDyou +Z29vZ2xlLmNvbS52boILKi5nb29nbGUuZGWCCyouZ29vZ2xlLmVzggsqLmdvb2ds +ZS5mcoILKi5nb29nbGUuaHWCCyouZ29vZ2xlLml0ggsqLmdvb2dsZS5ubIILKi5n +b29nbGUucGyCCyouZ29vZ2xlLnB0gg8qLmdvb2dsZWFwaXMuY26CFCouZ29vZ2xl +Y29tbWVyY2UuY29tgg0qLmdzdGF0aWMuY29tggwqLnVyY2hpbi5jb22CECoudXJs +Lmdvb2dsZS5jb22CFioueW91dHViZS1ub2Nvb2tpZS5jb22CDSoueW91dHViZS5j +b22CFioueW91dHViZWVkdWNhdGlvbi5jb22CCyoueXRpbWcuY29tggthbmRyb2lk +LmNvbYIEZy5jb4IGZ29vLmdsghRnb29nbGUtYW5hbHl0aWNzLmNvbYIKZ29vZ2xl +LmNvbYISZ29vZ2xlY29tbWVyY2UuY29tggp1cmNoaW4uY29tggh5b3V0dS5iZYIL +eW91dHViZS5jb22CFHlvdXR1YmVlZHVjYXRpb24uY29tMA0GCSqGSIb3DQEBBQUA +A4GBAAMn0K3j3yhC+X+uyh6eABa2Eq7xiY5/mUB886Ir19vxluSMNKD6n/iY8vHj +trn0BhuW8/vmJyudFkIcEDUYE4ivQMlsfIL7SOGw6OevVLmm02aiRHWj5T20Ds+S +OpueYUG3NBcHP/5IzhUYIQJbGzlQaUaZBMaQeC8ZslMNLWI2 +-----END CERTIFICATE----- +)"; +#endif + + std::string google_cert_base64_der = "MIIF8DCCBVmgAwIBAgIKYFOB9QABAACIvTANBgkqhkiG9w0BAQUFADBGMQswCQYD" + "VQQGEwJVUzETMBEGA1UEChMKR29vZ2xlIEluYzEiMCAGA1UEAxMZR29vZ2xlIElu" + "dGVybmV0IEF1dGhvcml0eTAeFw0xMzA1MjIxNTQ5MDRaFw0xMzEwMzEyMzU5NTla" + "MGYxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1N" + "b3VudGFpbiBWaWV3MRMwEQYDVQQKEwpHb29nbGUgSW5jMRUwEwYDVQQDFAwqLmdv" + "b2dsZS5jb20wWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARmSpIUbCqhUBq1UwnR" + "Ai7/TNSk6W8JmasR+I0r/NLDYv5yApbAz8HXXN8hDdurMRP6Jy1Q0UIKmyls8HPH" + "exoCo4IECjCCBAYwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMAsGA1Ud" + "DwQEAwIHgDAdBgNVHQ4EFgQUU3jT0NVNRgU5ZinRHGrlyoGEnoYwHwYDVR0jBBgw" + "FoAUv8Aw6/VDET5nup6R+/xq2uNrEiQwWwYDVR0fBFQwUjBQoE6gTIZKaHR0cDov" + "L3d3dy5nc3RhdGljLmNvbS9Hb29nbGVJbnRlcm5ldEF1dGhvcml0eS9Hb29nbGVJ" + "bnRlcm5ldEF1dGhvcml0eS5jcmwwZgYIKwYBBQUHAQEEWjBYMFYGCCsGAQUFBzAC" + "hkpodHRwOi8vd3d3LmdzdGF0aWMuY29tL0dvb2dsZUludGVybmV0QXV0aG9yaXR5" + "L0dvb2dsZUludGVybmV0QXV0aG9yaXR5LmNydDAMBgNVHRMBAf8EAjAAMIICwwYD" + "VR0RBIICujCCAraCDCouZ29vZ2xlLmNvbYINKi5hbmRyb2lkLmNvbYIWKi5hcHBl" + "bmdpbmUuZ29vZ2xlLmNvbYISKi5jbG91ZC5nb29nbGUuY29tghYqLmdvb2dsZS1h" + "bmFseXRpY3MuY29tggsqLmdvb2dsZS5jYYILKi5nb29nbGUuY2yCDiouZ29vZ2xl" + "LmNvLmlugg4qLmdvb2dsZS5jby5qcIIOKi5nb29nbGUuY28udWuCDyouZ29vZ2xl" + "LmNvbS5hcoIPKi5nb29nbGUuY29tLmF1gg8qLmdvb2dsZS5jb20uYnKCDyouZ29v" + "Z2xlLmNvbS5jb4IPKi5nb29nbGUuY29tLm14gg8qLmdvb2dsZS5jb20udHKCDyou" + "Z29vZ2xlLmNvbS52boILKi5nb29nbGUuZGWCCyouZ29vZ2xlLmVzggsqLmdvb2ds" + "ZS5mcoILKi5nb29nbGUuaHWCCyouZ29vZ2xlLml0ggsqLmdvb2dsZS5ubIILKi5n" + "b29nbGUucGyCCyouZ29vZ2xlLnB0gg8qLmdvb2dsZWFwaXMuY26CFCouZ29vZ2xl" + "Y29tbWVyY2UuY29tgg0qLmdzdGF0aWMuY29tggwqLnVyY2hpbi5jb22CECoudXJs" + "Lmdvb2dsZS5jb22CFioueW91dHViZS1ub2Nvb2tpZS5jb22CDSoueW91dHViZS5j" + "b22CFioueW91dHViZWVkdWNhdGlvbi5jb22CCyoueXRpbWcuY29tggthbmRyb2lk" + "LmNvbYIEZy5jb4IGZ29vLmdsghRnb29nbGUtYW5hbHl0aWNzLmNvbYIKZ29vZ2xl" + "LmNvbYISZ29vZ2xlY29tbWVyY2UuY29tggp1cmNoaW4uY29tggh5b3V0dS5iZYIL" + "eW91dHViZS5jb22CFHlvdXR1YmVlZHVjYXRpb24uY29tMA0GCSqGSIb3DQEBBQUA" + "A4GBAAMn0K3j3yhC+X+uyh6eABa2Eq7xiY5/mUB886Ir19vxluSMNKD6n/iY8vHj" + "trn0BhuW8/vmJyudFkIcEDUYE4ivQMlsfIL7SOGw6OevVLmm02aiRHWj5T20Ds+S" + "OpueYUG3NBcHP/5IzhUYIQJbGzlQaUaZBMaQeC8ZslMNLWI2"; + + std::string google_public_key = R"(-----BEGIN PUBLIC KEY----- +MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEZkqSFGwqoVAatVMJ0QIu/0zUpOlv +CZmrEfiNK/zSw2L+cgKWwM/B11zfIQ3bqzET+ictUNFCCpspbPBzx3saAg== +-----END PUBLIC KEY----- +)"; +} // namespace diff --git a/dep/jwt-cpp/tests/JwksTest.cpp b/dep/jwt-cpp/tests/JwksTest.cpp new file mode 100644 index 0000000000..92cb15a2da --- /dev/null +++ b/dep/jwt-cpp/tests/JwksTest.cpp @@ -0,0 +1,118 @@ +#include "jwt-cpp/jwt.h" +#include + +TEST(JwksTest, OneKeyParse) { + std::string public_key = R"({ + "alg": "RS256", + "kty": "RSA", + "use": "sig", + "x5c": [ + "MIIC+DCCAeCgAwIBAgIJBIGjYW6hFpn2MA0GCSqGSIb3DQEBBQUAMCMxITAfBgNVBAMTGGN1c3RvbWVyLWRlbW9zLmF1dGgwLmNvbTAeFw0xNjExMjIyMjIyMDVaFw0zMDA4MDEyMjIyMDVaMCMxITAfBgNVBAMTGGN1c3RvbWVyLWRlbW9zLmF1dGgwLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMnjZc5bm/eGIHq09N9HKHahM7Y31P0ul+A2wwP4lSpIwFrWHzxw88/7Dwk9QMc+orGXX95R6av4GF+Es/nG3uK45ooMVMa/hYCh0Mtx3gnSuoTavQEkLzCvSwTqVwzZ+5noukWVqJuMKNwjL77GNcPLY7Xy2/skMCT5bR8UoWaufooQvYq6SyPcRAU4BtdquZRiBT4U5f+4pwNTxSvey7ki50yc1tG49Per/0zA4O6Tlpv8x7Red6m1bCNHt7+Z5nSl3RX/QYyAEUX1a28VcYmR41Osy+o2OUCXYdUAphDaHo4/8rbKTJhlu8jEcc1KoMXAKjgaVZtG/v5ltx6AXY0CAwEAAaMvMC0wDAYDVR0TBAUwAwEB/zAdBgNVHQ4EFgQUQxFG602h1cG+pnyvJoy9pGJJoCswDQYJKoZIhvcNAQEFBQADggEBAGvtCbzGNBUJPLICth3mLsX0Z4z8T8iu4tyoiuAshP/Ry/ZBnFnXmhD8vwgMZ2lTgUWwlrvlgN+fAtYKnwFO2G3BOCFw96Nm8So9sjTda9CCZ3dhoH57F/hVMBB0K6xhklAc0b5ZxUpCIN92v/w+xZoz1XQBHe8ZbRHaP1HpRM4M7DJk2G5cgUCyu3UBvYS41sHvzrxQ3z7vIePRA4WF4bEkfX12gvny0RsPkrbVMXX1Rj9t6V7QXrbPYBAO+43JvDGYawxYVvLhz+BJ45x50GFQmHszfY3BR9TPK8xmMmQwtIvLu1PMttNCs7niCYkSiUv2sc2mlq1i3IashGkkgmo=" + ], + "n": "yeNlzlub94YgerT030codqEztjfU_S6X4DbDA_iVKkjAWtYfPHDzz_sPCT1Axz6isZdf3lHpq_gYX4Sz-cbe4rjmigxUxr-FgKHQy3HeCdK6hNq9ASQvMK9LBOpXDNn7mei6RZWom4wo3CMvvsY1w8tjtfLb-yQwJPltHxShZq5-ihC9irpLI9xEBTgG12q5lGIFPhTl_7inA1PFK97LuSLnTJzW0bj096v_TMDg7pOWm_zHtF53qbVsI0e3v5nmdKXdFf9BjIARRfVrbxVxiZHjU6zL6jY5QJdh1QCmENoejj_ytspMmGW7yMRxzUqgxcAqOBpVm0b-_mW3HoBdjQ", + "e": "AQAB", + "kid": "123456789", + "x5t": "NjVBRjY5MDlCMUIwNzU4RTA2QzZFMDQ4QzQ2MDAyQjVDNjk1RTM2Qg" + })"; + ASSERT_THROW(jwt::parse_jwk("__not_json__"), jwt::error::invalid_json_exception); + ASSERT_THROW(jwt::parse_jwk(R"##(["not","an","object"])##"), std::bad_cast); + + auto jwk = jwt::parse_jwk(public_key); + + ASSERT_TRUE(jwk.has_algorithm()); + ASSERT_TRUE(jwk.has_key_id()); + ASSERT_TRUE(jwk.has_x5c()); + ASSERT_FALSE(jwk.has_jwk_claim("foo")); + + ASSERT_EQ("RS256", jwk.get_algorithm()); + ASSERT_EQ("123456789", jwk.get_key_id()); +} + +TEST(JwksTest, MultiKeysParse) { + std::string public_key = R"({ + "keys": [{ + "kid": "internal-gateway-jwt", + "use": "sig", + "x5c": ["MIIG1TCCBL2gAwIBAgIIFvMVGp6t\/cMwDQYJKoZIhvcNAQELBQAwZjELMAkGA1UEBhMCR0IxIDAeBgNVBAoMF1N0YW5kYXJkIENoYXJ0ZXJlZCBCYW5rMTUwMwYDVQQDDCxTdGFuZGFyZCBDaGFydGVyZWQgQmFuayBTaWduaW5nIENBIEcxIC0gU0hBMjAeFw0xODEwMTAxMTI2MzVaFw0yMjEwMTAxMTI2MzVaMIG9MQswCQYDVQQGEwJTRzESMBAGA1UECAwJU2luZ2Fwb3JlMRIwEAYDVQQHDAlTaW5nYXBvcmUxIDAeBgNVBAoMF1N0YW5kYXJkIENoYXJ0ZXJlZCBCYW5rMRwwGgYDVQQLDBNGb3VuZGF0aW9uIFNlcnZpY2VzMSgwJgYDVQQDDB9pbnRlcm5hbC1nYXRld2F5LWp3dC5hcGkuc2MubmV0MRwwGgYJKoZIhvcNAQkBFg1BUElQU1NAc2MuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArVWBoIi3IJ4nOWXu7\/SDxczqMou1B+c4c2FdQrOXrK31HxAaz4WEtma9BLXFdFHJ5mCCPIvdUcVxxnCynqhMOkZ\/a7acQbUD9cDzI8isMB9JL7VooDw0CctxHxffjqQQVIEhC2Q7zsM1pQayR7cl+pbBlvHIoRxq2n1B0fFvfoiosjf4kDiCpgHdM+v5Hw9aVYmUbroHxmQWqhB0iRTJQPPLZqqQVC50A1Q\/96gkwoODyotc46Uy9wYEpdGrtDG\/thWay3fmMsjpWR0U25xFIrxTrfCGBblYpD7juukWWml2E9rtE2rHgUxbymxXjEw7xrMwcGrhOGyqwoBqJy1JVwIDAQABo4ICLTCCAikwZAYIKwYBBQUHAQEEWDBWMFQGCCsGAQUFBzABhkhodHRwOi8vY29yZW9jc3AuZ2xvYmFsLnN0YW5kYXJkY2hhcnRlcmVkLmNvbS9lamJjYS9wdWJsaWN3ZWIvc3RhdHVzL29jc3AwHQYDVR0OBBYEFIinW4BNDeVEFcuLf8YjZjtySoW9MAwGA1UdEwEB\/wQCMAAwHwYDVR0jBBgwFoAUfNZMoZi33nKrcmVU3TFVQnuEi\/4wggFCBgNVHR8EggE5MIIBNTCCATGggcKggb+GgbxodHRwOi8vY29yZWNybC5nbG9iYWwuc3RhbmRhcmRjaGFydGVyZWQuY29tL2VqYmNhL3B1YmxpY3dlYi93ZWJkaXN0L2NlcnRkaXN0P2NtZD1jcmwmaXNzdWVyPUNOPVN0YW5kYXJkJTIwQ2hhcnRlcmVkJTIwQmFuayUyMFNpZ25pbmclMjBDQSUyMEcxJTIwLSUyMFNIQTIsTz1TdGFuZGFyZCUyMENoYXJ0ZXJlZCUyMEJhbmssQz1HQqJqpGgwZjE1MDMGA1UEAwwsU3RhbmRhcmQgQ2hhcnRlcmVkIEJhbmsgU2lnbmluZyBDQSBHMSAtIFNIQTIxIDAeBgNVBAoMF1N0YW5kYXJkIENoYXJ0ZXJlZCBCYW5rMQswCQYDVQQGEwJHQjAOBgNVHQ8BAf8EBAMCBsAwHQYDVR0lBBYwFAYIKwYBBQUHAwIGCCsGAQUFBwMEMA0GCSqGSIb3DQEBCwUAA4ICAQBtsoRlDHuOTDChcWdfdVUtRgP0U0ijDSeJi8vULN1rgYnqqJc4PdJno50aiu9MGlxY02O7HW7ZVD6QEG\/pqHmZ0sbWpb\/fumMgZSjP65IcGuS53zgcNtLYnyXyEv+v5T\/CK3bk4Li6tUW3ScJPUwVWwP1E0\/u6aBSb5k\/h4lTwS1o88ybS5pJOg6XutXByp991QQrrs7tp7fKNynjNZbFuG3J1e09X+zTfJOpjaDUofQTkt8IyMRI6Cs4wI1eZA+dAIL8B0n8ze1mRl1FOJqgdZrAQjoqZkCTnc0Il5VY\/dUXxGVg6D9e5pfck3FWT107K9\/5EZoxytpqYXFCjMXi5hx4YjK17OUgm82mZhvqkNdzF8Yq2vFuB3LPfyelESq99xFLykvinrVm1NtZKeDTT1Jq\/VvZt6stO\/tovq1RfJJcznpYcwOzxlnhGR6E+hxuBx7aDJzGf0JaoRxQILH1B2XV9WDI3HPYQsP7XtriX+QUJ\/aly28QkV48RmaGYCsly43YZu1MKudSsw+dhnbZzRsg\/aes3dzGW2x137bQPtux7k2LCSpsTXgedhOys28YoGlsoe8kUv0myAU4Stt+I3mrwO3BKUn+tJggvlDiiiyT1tg2HiklyU\/2FxQkZRMeB0eRrXTpg3l9x2mpF+dDFxOMKszxwD2kgoEZgo6o58A=="], + "n": "nr9UsxnPVd21iuiGcIJ_Qli2XVlAZe5VbELA1hO2-L4k5gI4fjHZ3ysUcautLpbOYogOQgsnlpsLrCmvNDvBDVzVp2nMbpguJlt12vHSP1fRJJpipGQ8qU-VaXsC4OjOQf3H9ojAU5Vfnl5gZ7kVCd8g4M29l-IRyNpxE-Ccxc2Y7molsCHT6GHLMMBVsd11JIOXMICJf4hz2YYkQ1t7C8SaB2RFRPuGO5Mn6mfAnwdmRera4TBz6_pIPPCgCbN8KOdJItWkr9F7Tjv_0nhh-ZVlQvbQ9PXHyKTj00g3IYUlbZIWHm0Ley__fzNZk2dyAAVjNA2QSzTZJc33MQx1pQ", + "e": "AQAB", + "x5t": "-qC0akuyiHT7GcV5a8O5nrFsKVWM9da7lzq6DLrj09I", + "alg": "RS256", + "kty": "RSA" + }, + { + "kid": "internal-0", + "use": "sig", + "x5c": ["MIIG1TCCBL2gAwIBAgIIFvMVGp6t\/cMwDQYJKoZIhvcNAQELBQAwZjELMAkGA1UEBhMCR0IxIDAeBgNVBAoMF1N0YW5kYXJkIENoYXJ0ZXJlZCBCYW5rMTUwMwYDVQQDDCxTdGFuZGFyZCBDaGFydGVyZWQgQmFuayBTaWduaW5nIENBIEcxIC0gU0hBMjAeFw0xODEwMTAxMTI2MzVaFw0yMjEwMTAxMTI2MzVaMIG9MQswCQYDVQQGEwJTRzESMBAGA1UECAwJU2luZ2Fwb3JlMRIwEAYDVQQHDAlTaW5nYXBvcmUxIDAeBgNVBAoMF1N0YW5kYXJkIENoYXJ0ZXJlZCBCYW5rMRwwGgYDVQQLDBNGb3VuZGF0aW9uIFNlcnZpY2VzMSgwJgYDVQQDDB9pbnRlcm5hbC1nYXRld2F5LWp3dC5hcGkuc2MubmV0MRwwGgYJKoZIhvcNAQkBFg1BUElQU1NAc2MuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArVWBoIi3IJ4nOWXu7\/SDxczqMou1B+c4c2FdQrOXrK31HxAaz4WEtma9BLXFdFHJ5mCCPIvdUcVxxnCynqhMOkZ\/a7acQbUD9cDzI8isMB9JL7VooDw0CctxHxffjqQQVIEhC2Q7zsM1pQayR7cl+pbBlvHIoRxq2n1B0fFvfoiosjf4kDiCpgHdM+v5Hw9aVYmUbroHxmQWqhB0iRTJQPPLZqqQVC50A1Q\/96gkwoODyotc46Uy9wYEpdGrtDG\/thWay3fmMsjpWR0U25xFIrxTrfCGBblYpD7juukWWml2E9rtE2rHgUxbymxXjEw7xrMwcGrhOGyqwoBqJy1JVwIDAQABo4ICLTCCAikwZAYIKwYBBQUHAQEEWDBWMFQGCCsGAQUFBzABhkhodHRwOi8vY29yZW9jc3AuZ2xvYmFsLnN0YW5kYXJkY2hhcnRlcmVkLmNvbS9lamJjYS9wdWJsaWN3ZWIvc3RhdHVzL29jc3AwHQYDVR0OBBYEFIinW4BNDeVEFcuLf8YjZjtySoW9MAwGA1UdEwEB\/wQCMAAwHwYDVR0jBBgwFoAUfNZMoZi33nKrcmVU3TFVQnuEi\/4wggFCBgNVHR8EggE5MIIBNTCCATGggcKggb+GgbxodHRwOi8vY29yZWNybC5nbG9iYWwuc3RhbmRhcmRjaGFydGVyZWQuY29tL2VqYmNhL3B1YmxpY3dlYi93ZWJkaXN0L2NlcnRkaXN0P2NtZD1jcmwmaXNzdWVyPUNOPVN0YW5kYXJkJTIwQ2hhcnRlcmVkJTIwQmFuayUyMFNpZ25pbmclMjBDQSUyMEcxJTIwLSUyMFNIQTIsTz1TdGFuZGFyZCUyMENoYXJ0ZXJlZCUyMEJhbmssQz1HQqJqpGgwZjE1MDMGA1UEAwwsU3RhbmRhcmQgQ2hhcnRlcmVkIEJhbmsgU2lnbmluZyBDQSBHMSAtIFNIQTIxIDAeBgNVBAoMF1N0YW5kYXJkIENoYXJ0ZXJlZCBCYW5rMQswCQYDVQQGEwJHQjAOBgNVHQ8BAf8EBAMCBsAwHQYDVR0lBBYwFAYIKwYBBQUHAwIGCCsGAQUFBwMEMA0GCSqGSIb3DQEBCwUAA4ICAQBtsoRlDHuOTDChcWdfdVUtRgP0U0ijDSeJi8vULN1rgYnqqJc4PdJno50aiu9MGlxY02O7HW7ZVD6QEG\/pqHmZ0sbWpb\/fumMgZSjP65IcGuS53zgcNtLYnyXyEv+v5T\/CK3bk4Li6tUW3ScJPUwVWwP1E0\/u6aBSb5k\/h4lTwS1o88ybS5pJOg6XutXByp991QQrrs7tp7fKNynjNZbFuG3J1e09X+zTfJOpjaDUofQTkt8IyMRI6Cs4wI1eZA+dAIL8B0n8ze1mRl1FOJqgdZrAQjoqZkCTnc0Il5VY\/dUXxGVg6D9e5pfck3FWT107K9\/5EZoxytpqYXFCjMXi5hx4YjK17OUgm82mZhvqkNdzF8Yq2vFuB3LPfyelESq99xFLykvinrVm1NtZKeDTT1Jq\/VvZt6stO\/tovq1RfJJcznpYcwOzxlnhGR6E+hxuBx7aDJzGf0JaoRxQILH1B2XV9WDI3HPYQsP7XtriX+QUJ\/aly28QkV48RmaGYCsly43YZu1MKudSsw+dhnbZzRsg\/aes3dzGW2x137bQPtux7k2LCSpsTXgedhOys28YoGlsoe8kUv0myAU4Stt+I3mrwO3BKUn+tJggvlDiiiyT1tg2HiklyU\/2FxQkZRMeB0eRrXTpg3l9x2mpF+dDFxOMKszxwD2kgoEZgo6o58A=="], + "n": "nr9UsxnPVd21iuiGcIJ_Qli2XVlAZe5VbELA1hO2-L4k5gI4fjHZ3ysUcautLpbOYogOQgsnlpsLrCmvNDvBDVzVp2nMbpguJlt12vHSP1fRJJpipGQ8qU-VaXsC4OjOQf3H9ojAU5Vfnl5gZ7kVCd8g4M29l-IRyNpxE-Ccxc2Y7molsCHT6GHLMMBVsd11JIOXMICJf4hz2YYkQ1t7C8SaB2RFRPuGO5Mn6mfAnwdmRera4TBz6_pIPPCgCbN8KOdJItWkr9F7Tjv_0nhh-ZVlQvbQ9PXHyKTj00g3IYUlbZIWHm0Ley__fzNZk2dyAAVjNA2QSzTZJc33MQx1pQ", + "e": "AQAB", + "x5t": "-qC0akuyiHT7GcV5a8O5nrFsKVWM9da7lzq6DLrj09I", + "alg": "RS256", + "kty": "RSA" + } + ] +})"; + auto jwks = jwt::parse_jwks(public_key); + auto jwk = jwks.get_jwk("internal-gateway-jwt"); + + ASSERT_TRUE(jwk.has_algorithm()); + ASSERT_TRUE(jwk.has_key_id()); + ASSERT_TRUE(jwk.has_x5c()); + ASSERT_FALSE(jwk.has_jwk_claim("foo")); + + ASSERT_EQ("RS256", jwk.get_algorithm()); + ASSERT_EQ("internal-gateway-jwt", jwk.get_key_id()); + + ASSERT_THROW(jwks.get_jwk("123456"), jwt::error::claim_not_present_exception); +} + +TEST(JwksTest, Missingx5c) { + std::string public_key = R"({ + "keys": [{ + "kid": "internal-gateway-jwt", + "use": "sig", + "n": "nr9UsxnPVd21iuiGcIJ_Qli2XVlAZe5VbELA1hO2-L4k5gI4fjHZ3ysUcautLpbOYogOQgsnlpsLrCmvNDvBDVzVp2nMbpguJlt12vHSP1fRJJpipGQ8qU-VaXsC4OjOQf3H9ojAU5Vfnl5gZ7kVCd8g4M29l-IRyNpxE-Ccxc2Y7molsCHT6GHLMMBVsd11JIOXMICJf4hz2YYkQ1t7C8SaB2RFRPuGO5Mn6mfAnwdmRera4TBz6_pIPPCgCbN8KOdJItWkr9F7Tjv_0nhh-ZVlQvbQ9PXHyKTj00g3IYUlbZIWHm0Ley__fzNZk2dyAAVjNA2QSzTZJc33MQx1pQ", + "e": "AQAB", + "x5t": "-qC0akuyiHT7GcV5a8O5nrFsKVWM9da7lzq6DLrj09I", + "alg": "RS256", + "kty": "RSA" + }, + { + "kid": "internal-0", + "use": "sig", + "x5c": [], + "n": "nr9UsxnPVd21iuiGcIJ_Qli2XVlAZe5VbELA1hO2-L4k5gI4fjHZ3ysUcautLpbOYogOQgsnlpsLrCmvNDvBDVzVp2nMbpguJlt12vHSP1fRJJpipGQ8qU-VaXsC4OjOQf3H9ojAU5Vfnl5gZ7kVCd8g4M29l-IRyNpxE-Ccxc2Y7molsCHT6GHLMMBVsd11JIOXMICJf4hz2YYkQ1t7C8SaB2RFRPuGO5Mn6mfAnwdmRera4TBz6_pIPPCgCbN8KOdJItWkr9F7Tjv_0nhh-ZVlQvbQ9PXHyKTj00g3IYUlbZIWHm0Ley__fzNZk2dyAAVjNA2QSzTZJc33MQx1pQ", + "e": "AQAB", + "x5t": "-qC0akuyiHT7GcV5a8O5nrFsKVWM9da7lzq6DLrj09I", + "alg": "RS256", + "kty": "RSA" + }, + { + "kid": "internal-1", + "x5c": ["1","2","3"], + "n": "nr9UsxnPVd21iuiGcIJ_Qli2XVlAZe5VbELA1hO2-L4k5gI4fjHZ3ysUcautLpbOYogOQgsnlpsLrCmvNDvBDVzVp2nMbpguJlt12vHSP1fRJJpipGQ8qU-VaXsC4OjOQf3H9ojAU5Vfnl5gZ7kVCd8g4M29l-IRyNpxE-Ccxc2Y7molsCHT6GHLMMBVsd11JIOXMICJf4hz2YYkQ1t7C8SaB2RFRPuGO5Mn6mfAnwdmRera4TBz6_pIPPCgCbN8KOdJItWkr9F7Tjv_0nhh-ZVlQvbQ9PXHyKTj00g3IYUlbZIWHm0Ley__fzNZk2dyAAVjNA2QSzTZJc33MQx1pQ", + "e": "AQAB", + "x5t": "-qC0akuyiHT7GcV5a8O5nrFsKVWM9da7lzq6DLrj09I", + "alg": "RS256", + "kty": "RSA" + } + ] +})"; + + auto jwks = jwt::parse_jwks(public_key); + ASSERT_TRUE(jwks.has_jwk("internal-gateway-jwt")); + ASSERT_FALSE(jwks.has_jwk("random-jwt")); + auto jwk = jwks.get_jwk("internal-gateway-jwt"); + + ASSERT_TRUE(jwk.has_algorithm()); + ASSERT_THROW(jwk.get_x5c(), jwt::error::claim_not_present_exception); + + auto jwk2 = jwks.get_jwk("internal-0"); + + ASSERT_EQ(jwk2.get_x5c().size(), 0); + ASSERT_THROW(jwk2.get_x5c_key_value(), jwt::error::claim_not_present_exception); + + auto jwk3 = jwks.get_jwk("internal-1"); + ASSERT_EQ(jwk3.get_x5c().size(), 3); + ASSERT_EQ(jwk3.get_x5c_key_value(), "1"); +} diff --git a/dep/jwt-cpp/tests/Keys.cpp b/dep/jwt-cpp/tests/Keys.cpp new file mode 100644 index 0000000000..60abdd2518 --- /dev/null +++ b/dep/jwt-cpp/tests/Keys.cpp @@ -0,0 +1,220 @@ +#include + +namespace test_keys { + std::string rsa_priv_key = R"(-----BEGIN PRIVATE KEY----- +MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQC4ZtdaIrd1BPIJ +tfnF0TjIK5inQAXZ3XlCrUlJdP+XHwIRxdv1FsN12XyMYO/6ymLmo9ryoQeIrsXB +XYqlET3zfAY+diwCb0HEsVvhisthwMU4gZQu6TYW2s9LnXZB5rVtcBK69hcSlA2k +ZudMZWxZcj0L7KMfO2rIvaHw/qaVOE9j0T257Z8Kp2CLF9MUgX0ObhIsdumFRLaL +DvDUmBPr2zuh/34j2XmWwn1yjN/WvGtdfhXW79Ki1S40HcWnygHgLV8sESFKUxxQ +mKvPUTwDOIwLFL5WtE8Mz7N++kgmDcmWMCHc8kcOIu73Ta/3D4imW7VbKgHZo9+K +3ESFE3RjAgMBAAECggEBAJTEIyjMqUT24G2FKiS1TiHvShBkTlQdoR5xvpZMlYbN +tVWxUmrAGqCQ/TIjYnfpnzCDMLhdwT48Ab6mQJw69MfiXwc1PvwX1e9hRscGul36 +ryGPKIVQEBsQG/zc4/L2tZe8ut+qeaK7XuYrPp8bk/X1e9qK5m7j+JpKosNSLgJj +NIbYsBkG2Mlq671irKYj2hVZeaBQmWmZxK4fw0Istz2WfN5nUKUeJhTwpR+JLUg4 +ELYYoB7EO0Cej9UBG30hbgu4RyXA+VbptJ+H042K5QJROUbtnLWuuWosZ5ATldwO +u03dIXL0SH0ao5NcWBzxU4F2sBXZRGP2x/jiSLHcqoECgYEA4qD7mXQpu1b8XO8U +6abpKloJCatSAHzjgdR2eRDRx5PMvloipfwqA77pnbjTUFajqWQgOXsDTCjcdQui +wf5XAaWu+TeAVTytLQbSiTsBhrnoqVrr3RoyDQmdnwHT8aCMouOgcC5thP9vQ8Us +rVdjvRRbnJpg3BeSNimH+u9AHgsCgYEA0EzcbOltCWPHRAY7B3Ge/AKBjBQr86Kv +TdpTlxePBDVIlH+BM6oct2gaSZZoHbqPjbq5v7yf0fKVcXE4bSVgqfDJ/sZQu9Lp +PTeV7wkk0OsAMKk7QukEpPno5q6tOTNnFecpUhVLLlqbfqkB2baYYwLJR3IRzboJ +FQbLY93E8gkCgYB+zlC5VlQbbNqcLXJoImqItgQkkuW5PCgYdwcrSov2ve5r/Acz +FNt1aRdSlx4176R3nXyibQA1Vw+ztiUFowiP9WLoM3PtPZwwe4bGHmwGNHPIfwVG +m+exf9XgKKespYbLhc45tuC08DATnXoYK7O1EnUINSFJRS8cezSI5eHcbQKBgQDC +PgqHXZ2aVftqCc1eAaxaIRQhRmY+CgUjumaczRFGwVFveP9I6Gdi+Kca3DE3F9Pq +PKgejo0SwP5vDT+rOGHN14bmGJUMsX9i4MTmZUZ5s8s3lXh3ysfT+GAhTd6nKrIE +kM3Nh6HWFhROptfc6BNusRh1kX/cspDplK5x8EpJ0QKBgQDWFg6S2je0KtbV5PYe +RultUEe2C0jYMDQx+JYxbPmtcopvZQrFEur3WKVuLy5UAy7EBvwMnZwIG7OOohJb +vkSpADK6VPn9lbqq7O8cTedEHttm6otmLt8ZyEl3hZMaL3hbuRj6ysjmoFKx6CrX +rK0/Ikt5ybqUzKCMJZg2VKGTxg== +-----END PRIVATE KEY-----)"; + std::string rsa_pub_key = R"(-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuGbXWiK3dQTyCbX5xdE4 +yCuYp0AF2d15Qq1JSXT/lx8CEcXb9RbDddl8jGDv+spi5qPa8qEHiK7FwV2KpRE9 +83wGPnYsAm9BxLFb4YrLYcDFOIGULuk2FtrPS512Qea1bXASuvYXEpQNpGbnTGVs +WXI9C+yjHztqyL2h8P6mlThPY9E9ue2fCqdgixfTFIF9Dm4SLHbphUS2iw7w1JgT +69s7of9+I9l5lsJ9cozf1rxrXX4V1u/SotUuNB3Fp8oB4C1fLBEhSlMcUJirz1E8 +AziMCxS+VrRPDM+zfvpIJg3JljAh3PJHDiLu902v9w+Iplu1WyoB2aPfitxEhRN0 +YwIDAQAB +-----END PUBLIC KEY-----)"; + std::string rsa_pub_key_invalid = R"(-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxzYuc22QSst/dS7geYYK +5l5kLxU0tayNdixkEQ17ix+CUcUbKIsnyftZxaCYT46rQtXgCaYRdJcbB3hmyrOa +vkhTpX79xJZnQmfuamMbZBqitvscxW9zRR9tBUL6vdi/0rpoUwPMEh8+Bw7CgYR0 +FK0DhWYBNDfe9HKcyZEv3max8Cdq18htxjEsdYO0iwzhtKRXomBWTdhD5ykd/fAC +VTr4+KEY+IeLvubHVmLUhbE5NgWXxrRpGasDqzKhCTmsa2Ysf712rl57SlH0Wz/M +r3F7aM9YpErzeYLrl0GhQr9BVJxOvXcVd4kmY+XkiCcrkyS1cnghnllh+LCwQu1s +YwIDAQAB +-----END PUBLIC KEY-----)"; + std::string rsa512_priv_key = R"(-----BEGIN RSA PRIVATE KEY----- +MIICWwIBAAKBgQDdlatRjRjogo3WojgGHFHYLugdUWAY9iR3fy4arWNA1KoS8kVw +33cJibXr8bvwUAUparCwlvdbH6dvEOfou0/gCFQsHUfQrSDv+MuSUMAe8jzKE4qW ++jK+xQU9a03GUnKHkkle+Q0pX/g6jXZ7r1/xAK5Do2kQ+X5xK9cipRgEKwIDAQAB +AoGAD+onAtVye4ic7VR7V50DF9bOnwRwNXrARcDhq9LWNRrRGElESYYTQ6EbatXS +3MCyjjX2eMhu/aF5YhXBwkppwxg+EOmXeh+MzL7Zh284OuPbkglAaGhV9bb6/5Cp +uGb1esyPbYW+Ty2PC0GSZfIXkXs76jXAu9TOBvD0ybc2YlkCQQDywg2R/7t3Q2OE +2+yo382CLJdrlSLVROWKwb4tb2PjhY4XAwV8d1vy0RenxTB+K5Mu57uVSTHtrMK0 +GAtFr833AkEA6avx20OHo61Yela/4k5kQDtjEf1N0LfI+BcWZtxsS3jDM3i1Hp0K +Su5rsCPb8acJo5RO26gGVrfAsDcIXKC+bQJAZZ2XIpsitLyPpuiMOvBbzPavd4gY +6Z8KWrfYzJoI/Q9FuBo6rKwl4BFoToD7WIUS+hpkagwWiz+6zLoX1dbOZwJACmH5 +fSSjAkLRi54PKJ8TFUeOP15h9sQzydI8zJU+upvDEKZsZc/UhT/SySDOxQ4G/523 +Y0sz/OZtSWcol/UMgQJALesy++GdvoIDLfJX5GBQpuFgFenRiRDabxrE9MNUZ2aP +FaFp+DyAe+b4nDwuJaW2LURbr8AEZga7oQj0uYxcYw== +-----END RSA PRIVATE KEY-----)"; + std::string rsa512_pub_key = R"(-----BEGIN PUBLIC KEY----- +MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDdlatRjRjogo3WojgGHFHYLugd +UWAY9iR3fy4arWNA1KoS8kVw33cJibXr8bvwUAUparCwlvdbH6dvEOfou0/gCFQs +HUfQrSDv+MuSUMAe8jzKE4qW+jK+xQU9a03GUnKHkkle+Q0pX/g6jXZ7r1/xAK5D +o2kQ+X5xK9cipRgEKwIDAQAB +-----END PUBLIC KEY-----)"; + std::string rsa512_pub_key_invalid = R"(-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxzYuc22QSst/dS7geYYK +5l5kLxU0tayNdixkEQ17ix+CUcUbKIsnyftZxaCYT46rQtXgCaYRdJcbB3hmyrOa +vkhTpX79xJZnQmfuamMbZBqitvscxW9zRR9tBUL6vdi/0rpoUwPMEh8+Bw7CgYR0 +FK0DhWYBNDfe9HKcyZEv3max8Cdq18htxjEsdYO0iwzhtKRXomBWTdhD5ykd/fAC +VTr4+KEY+IeLvubHVmLUhbE5NgWXxrRpGasDqzKhCTmsa2Ysf712rl57SlH0Wz/M +r3F7aM9YpErzeYLrl0GhQr9BVJxOvXcVd4kmY+XkiCcrkyS1cnghnllh+LCwQu1s +YwIDAQAB +-----END PUBLIC KEY-----)"; + std::string ecdsa521_priv_key = R"(-----BEGIN EC PRIVATE KEY----- +MIHcAgEBBEIAuZxTZjLIZM5hxgZX+JRrqt5FKpAEg/meZ7m9aSE3XbRITqtfz1Uy +h2Srn7o8+4j/jQpwHTTHZThy10u5jMjaR+mgBwYFK4EEACOhgYkDgYYABAFFah0k +6m4ddp/tUN/ObrKKwSCp4QUZdiAMaC9eY1HyNBPuuEsH5qCfeY5lmeJwSUpzCosn +rgW8M2hQ4Kr5V9OXrgHLA5WVtH6//sSkUY2/xYuqc7/Ln8gI5ddtr1qG64Xtgs05 +/CNajSjFZeLm76llakvYiBTTH/ii8hIfrwukW9IP7Q== +-----END EC PRIVATE KEY-----)"; + std::string ecdsa521_pub_key = R"(-----BEGIN PUBLIC KEY----- +MIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQBRWodJOpuHXaf7VDfzm6yisEgqeEF +GXYgDGgvXmNR8jQT7rhLB+agn3mOZZnicElKcwqLJ64FvDNoUOCq+VfTl64BywOV +lbR+v/7EpFGNv8WLqnO/y5/ICOXXba9ahuuF7YLNOfwjWo0oxWXi5u+pZWpL2IgU +0x/4ovISH68LpFvSD+0= +-----END PUBLIC KEY-----)"; + std::string ecdsa521_pub_key_invalid = R"(-----BEGIN PUBLIC KEY----- +MIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQB3l84szH3VOII+Qp4TGgRqCps1q7N +/41ucX90sVKT+6Z2FgYqGsfDcViWBdcjRQLL69LGApcvGGSa1MiYXmLWBYAAKriR +7S63iNLzarnq1WnCH+GowDMl127CPoSW4LmMqV5rJLJQNwfiy+N0wph/Adnqqb+6 +w1N7aVXutqCx02T/jrQ= +-----END PUBLIC KEY-----)"; + std::string ecdsa384_priv_key = R"(-----BEGIN EC PRIVATE KEY----- +MIGkAgEBBDCrPXJDgQDtNRpM0qNUW/zN1vrCvOVH1CsItVZ+1NeGB+w/2whnIXJQ +K7U5C1ETPHagBwYFK4EEACKhZANiAAR0JjvVJXc3u1I/7vt5mxzPtAIi1VIqxCwN +wgISZVySTYZQzyicW2GfhMlFCow28LzqTwH/eCymAvnTAmpK/P1hXhNcnxDBZNOU +WMbMLFcQrg2wwpIb/k/IXobNwjNPRBo= +-----END EC PRIVATE KEY-----)"; + std::string ecdsa384_pub_key = R"(-----BEGIN PUBLIC KEY----- +MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEdCY71SV3N7tSP+77eZscz7QCItVSKsQs +DcICEmVckk2GUM8onFthn4TJRQqMNvC86k8B/3gspgL50wJqSvz9YV4TXJ8QwWTT +lFjGzCxXEK4NsMKSG/5PyF6GzcIzT0Qa +-----END PUBLIC KEY-----)"; + std::string ecdsa384_pub_key_invalid = R"(-----BEGIN PUBLIC KEY----- +MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE7TAJMuqdY9JYXrv2p06bXhLmRddkCQZ6 +4BJeTNGz59QqbHk5+6avGj2lXK+c9rr0vejbts5A50WF1E4b7ItLxEIONfSbF74Q +sQ4dg7kzXiz+XX/aEVqH1HlIa9YAJPaH +-----END PUBLIC KEY-----)"; + std::string ecdsa256_priv_key = R"(-----BEGIN PRIVATE KEY----- +MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgPGJGAm4X1fvBuC1z +SpO/4Izx6PXfNMaiKaS5RUkFqEGhRANCAARCBvmeksd3QGTrVs2eMrrfa7CYF+sX +sjyGg+Bo5mPKGH4Gs8M7oIvoP9pb/I85tdebtKlmiCZHAZE5w4DfJSV6 +-----END PRIVATE KEY-----)"; + std::string ecdsa256_pub_key = R"(-----BEGIN PUBLIC KEY----- +MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEQgb5npLHd0Bk61bNnjK632uwmBfr +F7I8hoPgaOZjyhh+BrPDO6CL6D/aW/yPObXXm7SpZogmRwGROcOA3yUleg== +-----END PUBLIC KEY-----)"; + std::string ecdsa256_pub_key_invalid = R"(-----BEGIN PUBLIC KEY----- +MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEoBUyo8CQAFPeYPvv78ylh5MwFZjT +CLQeb042TjiMJxG+9DLFmRSMlBQ9T/RsLLc+PmpB1+7yPAR+oR5gZn3kJQ== +-----END PUBLIC KEY-----)"; + std::string ecdsa256_certificate = R"(-----BEGIN CERTIFICATE----- +MIIB3zCCAYWgAwIBAgIUXns6sbspahSWU4TRQssO3NLmDr0wCgYIKoZIzj0EAwIw +RTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoMGElu +dGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAeFw0yMDA3MjAyMTA4NTlaFw0yMTA3MjAy +MTA4NTlaMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYD +VQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwWTATBgcqhkjOPQIBBggqhkjO +PQMBBwNCAASa+WuX0TojwP3IFXEKO/9vHM+h1YBqTFY1wVGitOZewLxgXgCA8Mlu +yubQaTjyu/2XMAUmcbPwddiUidU4P5OJo1MwUTAdBgNVHQ4EFgQUz9J272oD2ple +bwxJWJ5IFTxFfTowHwYDVR0jBBgwFoAUz9J272oD2plebwxJWJ5IFTxFfTowDwYD +VR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAgNIADBFAiEAv2rcDBD4p/UeJIEFOK9j +o4XKKbXs6oqMpkEDUinf6DACIGPcShH4m5haelZleORWM068C3BSVToRMSAivE79 +8ZhX +-----END CERTIFICATE-----)"; + std::string ed25519_priv_key = R"(-----BEGIN PRIVATE KEY----- +MC4CAQAwBQYDK2VwBCIEIP7pYCi8Xipwjttw6ehj0PNrsg3K1Vc5RBqAG5JePqsm +-----END PRIVATE KEY-----)"; + std::string ed25519_pub_key = R"(-----BEGIN PUBLIC KEY----- +MCowBQYDK2VwAyEAJHsGwkQLdwqaqp2Ar39YtLCVHiWgUPcGwQfes2cUOEc= +-----END PUBLIC KEY-----)"; + std::string ed25519_pub_key_invalid = R"(-----BEGIN PUBLIC KEY----- +MCowBQYDK2VwAyEAJHsGwkQLdwqaqp2Ar39YtLCVHIwGupcGwQfes2cUOEc= +-----END PUBLIC KEY-----)"; + std::string ed25519_certificate = R"(-----BEGIN CERTIFICATE----- +MIIBjzCCAUECFCQlWQxMEMe4c3OOimH4/y+o/HpfMAUGAytlcDBqMQswCQYDVQQG +EwJDQTEPMA0GA1UECAwGUXVlYmVjMREwDwYDVQQHDAhNb250cmVhbDEQMA4GA1UE +CgwHand0LWNwcDEPMA0GA1UECwwGZ2l0aHViMRQwEgYDVQQDDAtleGFtcGxlLmNv +bTAeFw0yMDA3MzAyMTIwMDBaFw0yMjA2MzAyMTIwMDBaMGoxCzAJBgNVBAYTAkNB +MQ8wDQYDVQQIDAZRdWViZWMxETAPBgNVBAcMCE1vbnRyZWFsMRAwDgYDVQQKDAdq +d3QtY3BwMQ8wDQYDVQQLDAZnaXRodWIxFDASBgNVBAMMC2V4YW1wbGUuY29tMCow +BQYDK2VwAyEAUdLe1SUWxc/95f39pfmuwe1SLHpFXf5gcRQlMH2sjgwwBQYDK2Vw +A0EAezYcLIUnyy86uUnAZdAMPn7wTruNKtG36GrTF3PF4dtdoGF1OV5DLnNK0Hbs +3GyYtaZs6AEHwDXl/INXu2zoCQ== +-----END CERTIFICATE-----)"; + std::string ed448_priv_key = R"(-----BEGIN PRIVATE KEY----- +MEcCAQAwBQYDK2VxBDsEOZNyV4kIWehIWSsPCnDEZbBF+g2WoUgUwox8eQJTq8Hz +y4okU+JZAV8RqQ270fJL/Safvvc1SbbF1A== +-----END PRIVATE KEY-----)"; + std::string ed448_pub_key = R"(-----BEGIN PUBLIC KEY----- +MEMwBQYDK2VxAzoAQomzAH8FXc0rhznaLR9DYwe5+DNhP0UwpZzS5y6iJ/iirrzI +9/Fjbc/UvFxwFKFw8IJ9QUwFsE8A +-----END PUBLIC KEY-----)"; + std::string ed448_pub_key_invalid = R"(-----BEGIN PUBLIC KEY----- +MEMwBQYDK2VxAzoAQomzAH8FXc0rhznaLR9DYwe5+DNhP0UwpZzS5y6iJ/iirrzI +9/Fjbc/UvFxwFKFW7ij8QUwFsE8A +-----END PUBLIC KEY-----)"; + std::string sample_cert = R"(-----BEGIN CERTIFICATE----- +MIIDHDCCAgSgAwIBAgIIGlbUz5cvweUwDQYJKoZIhvcNAQEFBQAwMTEvMC0GA1UE +AxMmc2VjdXJldG9rZW4uc3lzdGVtLmdzZXJ2aWNlYWNjb3VudC5jb20wHhcNMTkw +NDEwMjEyMDUxWhcNMTkwNDI3MDkzNTUxWjAxMS8wLQYDVQQDEyZzZWN1cmV0b2tl +bi5zeXN0ZW0uZ3NlcnZpY2VhY2NvdW50LmNvbTCCASIwDQYJKoZIhvcNAQEBBQAD +ggEPADCCAQoCggEBALRPFSsUi/bGcMVkD+XjJ6z/71u+7Wn1C1bRnM9sU3q7+Ere +DV6an+z+YsjblskBX73h1GyYvmtkyuL7Uq0N+y+RTOmd2fwDw48gM5FEq6DNpVVW +ZRIzzoMSLZCB+tg1eQZdGKtmctdd5Jjhwihf9Aa759fcj60GDG39G6A/w4Jok+J6 +7sRabxxontJ4Kpo6zmwUKbWF8naJeCRTO0VAYLkJqEWO4VJTIHJeu2WpxM0qzvY9 +IY5Yd7Njegu64FoHU55dSfee2KwDa0/bajrknJfxWBN4hk/rqgGjxQmzAYMCB7/p +/9Snfg4NmfX5cJJ01SNzY6Q/mJRjB3iX2PBz+GsCAwEAAaM4MDYwDAYDVR0TAQH/ +BAIwADAOBgNVHQ8BAf8EBAMCB4AwFgYDVR0lAQH/BAwwCgYIKwYBBQUHAwIwDQYJ +KoZIhvcNAQEFBQADggEBAKCSq0D+NIAsGULZrhXouurxInxDyq03xLNcxvKDQchc +XfGA1r3eltmlyKQb5TmAsuKwS/LAQ5z8SlRTOmDGVEtDwnw3S83C4ufXbP0eMB6H +eKf2XCA00T3odUfXmQZme8hG6z7GKVOdn/0oY+vaX38brlCpRXDTm1WldyddUpMz +ftcs6dibdnbQtbX6o9E+KuvGHoNW5xcSjX8lwXTpopfvufPOLPcnFXi4UoYZ8NZ2 +2mRG78LkOA+SkOMutbt6w7TBDvADmFzuzvAULy4gsfcamOYcQ7uiHnnD+PoNiNbw +flE/m/0zymX8I/Xu3+KKLhUnUROGC6zO3OnLHXCnEns= +-----END CERTIFICATE----- +)"; + std::string sample_cert_base64_der = "MIIDHDCCAgSgAwIBAgIIGlbUz5cvweUwDQYJKoZIhvcNAQEFBQAwMTEvMC0GA1UE" + "AxMmc2VjdXJldG9rZW4uc3lzdGVtLmdzZXJ2aWNlYWNjb3VudC5jb20wHhcNMTkw" + "NDEwMjEyMDUxWhcNMTkwNDI3MDkzNTUxWjAxMS8wLQYDVQQDEyZzZWN1cmV0b2tl" + "bi5zeXN0ZW0uZ3NlcnZpY2VhY2NvdW50LmNvbTCCASIwDQYJKoZIhvcNAQEBBQAD" + "ggEPADCCAQoCggEBALRPFSsUi/bGcMVkD+XjJ6z/71u+7Wn1C1bRnM9sU3q7+Ere" + "DV6an+z+YsjblskBX73h1GyYvmtkyuL7Uq0N+y+RTOmd2fwDw48gM5FEq6DNpVVW" + "ZRIzzoMSLZCB+tg1eQZdGKtmctdd5Jjhwihf9Aa759fcj60GDG39G6A/w4Jok+J6" + "7sRabxxontJ4Kpo6zmwUKbWF8naJeCRTO0VAYLkJqEWO4VJTIHJeu2WpxM0qzvY9" + "IY5Yd7Njegu64FoHU55dSfee2KwDa0/bajrknJfxWBN4hk/rqgGjxQmzAYMCB7/p" + "/9Snfg4NmfX5cJJ01SNzY6Q/mJRjB3iX2PBz+GsCAwEAAaM4MDYwDAYDVR0TAQH/" + "BAIwADAOBgNVHQ8BAf8EBAMCB4AwFgYDVR0lAQH/BAwwCgYIKwYBBQUHAwIwDQYJ" + "KoZIhvcNAQEFBQADggEBAKCSq0D+NIAsGULZrhXouurxInxDyq03xLNcxvKDQchc" + "XfGA1r3eltmlyKQb5TmAsuKwS/LAQ5z8SlRTOmDGVEtDwnw3S83C4ufXbP0eMB6H" + "eKf2XCA00T3odUfXmQZme8hG6z7GKVOdn/0oY+vaX38brlCpRXDTm1WldyddUpMz" + "ftcs6dibdnbQtbX6o9E+KuvGHoNW5xcSjX8lwXTpopfvufPOLPcnFXi4UoYZ8NZ2" + "2mRG78LkOA+SkOMutbt6w7TBDvADmFzuzvAULy4gsfcamOYcQ7uiHnnD+PoNiNbw" + "flE/m/0zymX8I/Xu3+KKLhUnUROGC6zO3OnLHXCnEns="; + std::string sample_cert_pubkey = R"(-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtE8VKxSL9sZwxWQP5eMn +rP/vW77tafULVtGcz2xTerv4St4NXpqf7P5iyNuWyQFfveHUbJi+a2TK4vtSrQ37 +L5FM6Z3Z/APDjyAzkUSroM2lVVZlEjPOgxItkIH62DV5Bl0Yq2Zy113kmOHCKF/0 +Brvn19yPrQYMbf0boD/DgmiT4nruxFpvHGie0ngqmjrObBQptYXydol4JFM7RUBg +uQmoRY7hUlMgcl67ZanEzSrO9j0hjlh3s2N6C7rgWgdTnl1J957YrANrT9tqOuSc +l/FYE3iGT+uqAaPFCbMBgwIHv+n/1Kd+Dg2Z9flwknTVI3NjpD+YlGMHeJfY8HP4 +awIDAQAB +-----END PUBLIC KEY----- +)"; +} // namespace test_keys diff --git a/dep/jwt-cpp/tests/OpenSSLErrorTest.cpp b/dep/jwt-cpp/tests/OpenSSLErrorTest.cpp new file mode 100644 index 0000000000..45ada667b4 --- /dev/null +++ b/dep/jwt-cpp/tests/OpenSSLErrorTest.cpp @@ -0,0 +1,931 @@ +#ifdef __linux__ // None of this stuff is going to work outside of linux! + +#ifndef HUNTER_ENABLED // Static linking (which hunter always? does) breaks the tests (duplicate definition), so skip them + +#include "jwt-cpp/jwt.h" +#include + +#include +// TODO: Figure out why the tests fail on older openssl versions +#ifndef JWT_OPENSSL_1_0_0 // It fails on < 1.1 but no idea why. +// LibreSSL has different return codes but was already outside of the effective scope + +/** + * ============ Beginning of black magic =============== + * We mock out a couple of openssl functions. + * We can do this because the current executable take precedence while linking. + * Once it is running and we want normal behaviour, we query the original method using dlsym. + */ +static uint64_t fail_BIO_new = 0; +static uint64_t fail_PEM_read_bio_X509 = 0; +static uint64_t fail_X509_get_pubkey = 0; +static uint64_t fail_PEM_write_bio_PUBKEY = 0; +static uint64_t fail_PEM_write_bio_cert = 0; +static uint64_t fail_BIO_ctrl = 0; +static uint64_t fail_BIO_write = 0; +static uint64_t fail_PEM_read_bio_PUBKEY = 0; +static uint64_t fail_PEM_read_bio_PrivateKey = 0; +static uint64_t fail_HMAC = 0; +static uint64_t fail_EVP_MD_CTX_new = 0; +static uint64_t fail_EVP_DigestInit = 0; +static uint64_t fail_EVP_DigestUpdate = 0; +static uint64_t fail_EVP_DigestFinal = 0; +static uint64_t fail_EVP_SignFinal = 0; +static uint64_t fail_EVP_VerifyFinal = 0; +#ifdef JWT_OPENSSL_3_0 +static uint64_t fail_EVP_PKEY_public_check = 0; +static uint64_t fail_EVP_PKEY_private_check = 0; +static uint64_t fail_EVP_PKEY_CTX_new_from_pkey = 0; +#else +static uint64_t fail_EC_KEY_check_key = 0; +static uint64_t fail_EVP_PKEY_get1_EC_KEY = 0; +#endif +static uint64_t fail_ECDSA_SIG_new = 0; +static uint64_t fail_EVP_PKEY_get1_RSA = 0; +static uint64_t fail_EVP_DigestSignInit = 0; +static uint64_t fail_EVP_DigestSign = 0; +static uint64_t fail_EVP_DigestVerifyInit = 0; +static uint64_t fail_EVP_DigestVerify = 0; +static uint64_t fail_EVP_DigestSignFinal = 0; +static uint64_t fail_EVP_DigestVerifyFinal = 0; +static uint64_t fail_d2i_ECDSA_SIG = 0; +static uint64_t fail_i2d_ECDSA_SIG = 0; + +BIO* BIO_new(const BIO_METHOD* type) { + static BIO* (*origMethod)(const BIO_METHOD*) = nullptr; + if (origMethod == nullptr) origMethod = (decltype(origMethod))dlsym(RTLD_NEXT, "BIO_new"); + bool fail = fail_BIO_new & 1; + fail_BIO_new = fail_BIO_new >> 1; + if (fail) + return nullptr; + else + return origMethod(type); +} + +X509* PEM_read_bio_X509(BIO* bp, X509** x, pem_password_cb* cb, void* u) { + static X509* (*origMethod)(BIO * bp, X509 * *x, pem_password_cb * cb, void* u) = nullptr; + if (origMethod == nullptr) origMethod = (decltype(origMethod))dlsym(RTLD_NEXT, "PEM_read_bio_X509"); + bool fail = fail_PEM_read_bio_X509 & 1; + fail_PEM_read_bio_X509 = fail_PEM_read_bio_X509 >> 1; + if (fail) + return nullptr; + else + return origMethod(bp, x, cb, u); +} + +EVP_PKEY* X509_get_pubkey(X509* x) { + static EVP_PKEY* (*origMethod)(X509*) = nullptr; + if (origMethod == nullptr) origMethod = (decltype(origMethod))dlsym(RTLD_NEXT, "X509_get_pubkey"); + bool fail = fail_X509_get_pubkey & 1; + fail_X509_get_pubkey = fail_X509_get_pubkey >> 1; + if (fail) + return nullptr; + else + return origMethod(x); +} + +#ifdef JWT_OPENSSL_3_0 +#define OPENSSL_CONST const +#else +#define OPENSSL_CONST +#endif + +int PEM_write_bio_PUBKEY(BIO* bp, OPENSSL_CONST EVP_PKEY* x) { + static int (*origMethod)(BIO * bp, OPENSSL_CONST EVP_PKEY * x) = nullptr; + if (origMethod == nullptr) origMethod = (decltype(origMethod))dlsym(RTLD_NEXT, "PEM_write_bio_PUBKEY"); + bool fail = fail_PEM_write_bio_PUBKEY & 1; + fail_PEM_write_bio_PUBKEY = fail_PEM_write_bio_PUBKEY >> 1; + if (fail) + return 0; + else + return origMethod(bp, x); +} + +int PEM_write_bio_X509(BIO* bp, OPENSSL_CONST X509* x) { + static int (*origMethod)(BIO * bp, OPENSSL_CONST X509 * x) = nullptr; + if (origMethod == nullptr) origMethod = (decltype(origMethod))dlsym(RTLD_NEXT, "PEM_write_bio_X509"); + bool fail = fail_PEM_write_bio_cert & 1; + fail_PEM_write_bio_cert = fail_PEM_write_bio_cert >> 1; + if (fail) + return 0; + else + return origMethod(bp, x); +} + +long BIO_ctrl(BIO* bp, int cmd, long larg, void* parg) { + static long (*origMethod)(BIO * bp, int cmd, long larg, void* parg) = nullptr; + if (origMethod == nullptr) origMethod = (decltype(origMethod))dlsym(RTLD_NEXT, "BIO_ctrl"); + bool fail = fail_BIO_ctrl & 1; + fail_BIO_ctrl = fail_BIO_ctrl >> 1; + if (fail) + return 0; + else + return origMethod(bp, cmd, larg, parg); +} + +int BIO_write(BIO* b, const void* data, int dlen) { + static int (*origMethod)(BIO * b, const void* data, int dlen) = nullptr; + if (origMethod == nullptr) origMethod = (decltype(origMethod))dlsym(RTLD_NEXT, "BIO_write"); + bool fail = fail_BIO_write & 1; + fail_BIO_write = fail_BIO_write >> 1; + if (fail) + return 0; + else + return origMethod(b, data, dlen); +} + +EVP_PKEY* PEM_read_bio_PUBKEY(BIO* bp, EVP_PKEY** x, pem_password_cb* cb, void* u) { + static EVP_PKEY* (*origMethod)(BIO * bp, EVP_PKEY * *x, pem_password_cb * cb, void* u) = nullptr; + if (origMethod == nullptr) origMethod = (decltype(origMethod))dlsym(RTLD_NEXT, "PEM_read_bio_PUBKEY"); + bool fail = fail_PEM_read_bio_PUBKEY & 1; + fail_PEM_read_bio_PUBKEY = fail_PEM_read_bio_PUBKEY >> 1; + if (fail) + return nullptr; + else + return origMethod(bp, x, cb, u); +} + +EVP_PKEY* PEM_read_bio_PrivateKey(BIO* bp, EVP_PKEY** x, pem_password_cb* cb, void* u) { + static EVP_PKEY* (*origMethod)(BIO * bp, EVP_PKEY * *x, pem_password_cb * cb, void* u) = nullptr; + if (origMethod == nullptr) origMethod = (decltype(origMethod))dlsym(RTLD_NEXT, "PEM_read_bio_PrivateKey"); + bool fail = fail_PEM_read_bio_PrivateKey & 1; + fail_PEM_read_bio_PrivateKey = fail_PEM_read_bio_PrivateKey >> 1; + if (fail) + return nullptr; + else + return origMethod(bp, x, cb, u); +} + +unsigned char* HMAC(const EVP_MD* evp_md, const void* key, int key_len, const unsigned char* d, size_t n, + unsigned char* md, unsigned int* md_len) { + static unsigned char* (*origMethod)(const EVP_MD* evp_md, const void* key, int key_len, const unsigned char* d, + size_t n, unsigned char* md, unsigned int* md_len) = nullptr; + if (origMethod == nullptr) origMethod = (decltype(origMethod))dlsym(RTLD_NEXT, "HMAC"); + bool fail = fail_HMAC & 1; + fail_HMAC = fail_HMAC >> 1; + if (fail) + return nullptr; + else + return origMethod(evp_md, key, key_len, d, n, md, md_len); +} + +EVP_MD_CTX* EVP_MD_CTX_new(void) { + static EVP_MD_CTX* (*origMethod)(void) = nullptr; + if (origMethod == nullptr) origMethod = (decltype(origMethod))dlsym(RTLD_NEXT, "EVP_MD_CTX_new"); + bool fail = fail_EVP_MD_CTX_new & 1; + fail_EVP_MD_CTX_new = fail_EVP_MD_CTX_new >> 1; + if (fail) + return nullptr; + else + return origMethod(); +} + +int EVP_DigestSignFinal(EVP_MD_CTX* ctx, unsigned char* sigret, size_t* siglen) { + static int (*origMethod)(EVP_MD_CTX * ctx, unsigned char* sigret, size_t* siglen) = nullptr; + if (origMethod == nullptr) origMethod = (decltype(origMethod))dlsym(RTLD_NEXT, "EVP_DigestSignFinal"); + bool fail = fail_EVP_DigestSignFinal & 1; + fail_EVP_DigestSignFinal = fail_EVP_DigestSignFinal >> 1; + if (fail) + return 0; + else + return origMethod(ctx, sigret, siglen); +} + +int EVP_DigestInit(EVP_MD_CTX* ctx, const EVP_MD* type) { + static int (*origMethod)(EVP_MD_CTX * ctx, const EVP_MD* type) = nullptr; + if (origMethod == nullptr) origMethod = (decltype(origMethod))dlsym(RTLD_NEXT, "EVP_DigestInit"); + bool fail = fail_EVP_DigestInit & 1; + fail_EVP_DigestInit = fail_EVP_DigestInit >> 1; + if (fail) + return 0; + else + return origMethod(ctx, type); +} + +int EVP_DigestUpdate(EVP_MD_CTX* ctx, const void* d, size_t cnt) { + static int (*origMethod)(EVP_MD_CTX * ctx, const void* d, size_t cnt) = nullptr; + if (origMethod == nullptr) origMethod = (decltype(origMethod))dlsym(RTLD_NEXT, "EVP_DigestUpdate"); + bool fail = fail_EVP_DigestUpdate & 1; + fail_EVP_DigestUpdate = fail_EVP_DigestUpdate >> 1; + if (fail) + return 0; + else + return origMethod(ctx, d, cnt); +} + +int EVP_DigestFinal(EVP_MD_CTX* ctx, unsigned char* md, unsigned int* s) { + static int (*origMethod)(EVP_MD_CTX * ctx, unsigned char* md, unsigned int* s) = nullptr; + if (origMethod == nullptr) origMethod = (decltype(origMethod))dlsym(RTLD_NEXT, "EVP_DigestFinal"); + bool fail = fail_EVP_DigestFinal & 1; + fail_EVP_DigestFinal = fail_EVP_DigestFinal >> 1; + if (fail) + return 0; + else + return origMethod(ctx, md, s); +} + +int EVP_SignFinal(EVP_MD_CTX* ctx, unsigned char* md, unsigned int* s, EVP_PKEY* pkey) { + static int (*origMethod)(EVP_MD_CTX * ctx, unsigned char* md, unsigned int* s, EVP_PKEY* pkey) = nullptr; + if (origMethod == nullptr) origMethod = (decltype(origMethod))dlsym(RTLD_NEXT, "EVP_SignFinal"); + bool fail = fail_EVP_SignFinal & 1; + fail_EVP_SignFinal = fail_EVP_SignFinal >> 1; + if (fail) + return 0; + else + return origMethod(ctx, md, s, pkey); +} + +int EVP_VerifyFinal(EVP_MD_CTX* ctx, const unsigned char* sigbuf, unsigned int siglen, EVP_PKEY* pkey) { + static int (*origMethod)(EVP_MD_CTX * ctx, const unsigned char* sigbuf, unsigned int siglen, EVP_PKEY* pkey) = + nullptr; + if (origMethod == nullptr) origMethod = (decltype(origMethod))dlsym(RTLD_NEXT, "EVP_VerifyFinal"); + bool fail = fail_EVP_VerifyFinal & 1; + fail_EVP_VerifyFinal = fail_EVP_VerifyFinal >> 1; + if (fail) + return 0; + else + return origMethod(ctx, sigbuf, siglen, pkey); +} + +#ifdef JWT_OPENSSL_3_0 +int EVP_PKEY_public_check(EVP_PKEY_CTX* ctx) { + static int (*origMethod)(EVP_PKEY_CTX * ctx) = nullptr; + if (origMethod == nullptr) origMethod = (decltype(origMethod))dlsym(RTLD_NEXT, "EVP_PKEY_public_check"); + bool fail = fail_EVP_PKEY_public_check & 1; + fail_EVP_PKEY_public_check = fail_EVP_PKEY_public_check >> 1; + if (fail) + return 0; + else + return origMethod(ctx); +} + +int EVP_PKEY_private_check(EVP_PKEY_CTX* ctx) { + static int (*origMethod)(EVP_PKEY_CTX * ctx) = nullptr; + if (origMethod == nullptr) origMethod = (decltype(origMethod))dlsym(RTLD_NEXT, "EVP_PKEY_private_check"); + bool fail = fail_EVP_PKEY_private_check & 1; + fail_EVP_PKEY_private_check = fail_EVP_PKEY_private_check >> 1; + if (fail) + return 0; + else + return origMethod(ctx); +} + +EVP_PKEY_CTX* EVP_PKEY_CTX_new_from_pkey(OSSL_LIB_CTX* libctx, EVP_PKEY* pkey, const char* propquery) { + static EVP_PKEY_CTX* (*origMethod)(OSSL_LIB_CTX * libctx, EVP_PKEY * pkey, const char* propquery) = nullptr; + if (origMethod == nullptr) origMethod = (decltype(origMethod))dlsym(RTLD_NEXT, "EVP_PKEY_CTX_new_from_pkey"); + bool fail = fail_EVP_PKEY_CTX_new_from_pkey & 1; + fail_EVP_PKEY_CTX_new_from_pkey = fail_EVP_PKEY_CTX_new_from_pkey >> 1; + if (fail) + return nullptr; + else + return origMethod(libctx, pkey, propquery); +} + +#else +int EC_KEY_check_key(const EC_KEY* key) { + static int (*origMethod)(const EC_KEY* key) = nullptr; + if (origMethod == nullptr) origMethod = (decltype(origMethod))dlsym(RTLD_NEXT, "EC_KEY_check_key"); + bool fail = fail_EC_KEY_check_key & 1; + fail_EC_KEY_check_key = fail_EC_KEY_check_key >> 1; + if (fail) + return 0; + else + return origMethod(key); +} + +EC_KEY* EVP_PKEY_get1_EC_KEY(EVP_PKEY* pkey) { + static EC_KEY* (*origMethod)(EVP_PKEY * pkey) = nullptr; + if (origMethod == nullptr) origMethod = (decltype(origMethod))dlsym(RTLD_NEXT, "EVP_PKEY_get1_EC_KEY"); + bool fail = fail_EVP_PKEY_get1_EC_KEY & 1; + fail_EVP_PKEY_get1_EC_KEY = fail_EVP_PKEY_get1_EC_KEY >> 1; + if (fail) + return nullptr; + else + return origMethod(pkey); +} +#endif + +ECDSA_SIG* ECDSA_SIG_new(void) { + static ECDSA_SIG* (*origMethod)() = nullptr; + if (origMethod == nullptr) origMethod = (decltype(origMethod))dlsym(RTLD_NEXT, "ECDSA_SIG_new"); + bool fail = fail_ECDSA_SIG_new & 1; + fail_ECDSA_SIG_new = fail_ECDSA_SIG_new >> 1; + if (fail) + return nullptr; + else + return origMethod(); +} + +struct rsa_st* EVP_PKEY_get1_RSA(EVP_PKEY* pkey) { + static struct rsa_st* (*origMethod)(EVP_PKEY * pkey) = nullptr; + if (origMethod == nullptr) origMethod = (decltype(origMethod))dlsym(RTLD_NEXT, "EVP_PKEY_get1_RSA"); + bool fail = fail_EVP_PKEY_get1_RSA & 1; + fail_EVP_PKEY_get1_RSA = fail_EVP_PKEY_get1_RSA >> 1; + if (fail) + return nullptr; + else + return origMethod(pkey); +} + +int EVP_DigestSignInit(EVP_MD_CTX* ctx, EVP_PKEY_CTX** pctx, const EVP_MD* type, ENGINE* e, EVP_PKEY* pkey) { + static int (*origMethod)(EVP_MD_CTX * ctx, EVP_PKEY_CTX * *pctx, const EVP_MD* type, ENGINE* e, EVP_PKEY* pkey) = + nullptr; + if (origMethod == nullptr) origMethod = (decltype(origMethod))dlsym(RTLD_NEXT, "EVP_DigestSignInit"); + bool fail = fail_EVP_DigestSignInit & 1; + fail_EVP_DigestSignInit = fail_EVP_DigestSignInit >> 1; + if (fail) + return 0; + else + return origMethod(ctx, pctx, type, e, pkey); +} + +int EVP_DigestSign(EVP_MD_CTX* ctx, unsigned char* sigret, size_t* siglen, const unsigned char* tbs, size_t tbslen) { + static int (*origMethod)(EVP_MD_CTX * ctx, unsigned char* sigret, size_t* siglen, const unsigned char* tbs, + size_t tbslen) = nullptr; + if (origMethod == nullptr) origMethod = (decltype(origMethod))dlsym(RTLD_NEXT, "EVP_DigestSign"); + bool fail = fail_EVP_DigestSign & 1; + fail_EVP_DigestSign = fail_EVP_DigestSign >> 1; + if (fail) + return 0; + else + return origMethod(ctx, sigret, siglen, tbs, tbslen); +} + +int EVP_DigestVerifyInit(EVP_MD_CTX* ctx, EVP_PKEY_CTX** pctx, const EVP_MD* type, ENGINE* e, EVP_PKEY* pkey) { + static int (*origMethod)(EVP_MD_CTX * ctx, EVP_PKEY_CTX * *pctx, const EVP_MD* type, ENGINE* e, EVP_PKEY* pkey) = + nullptr; + if (origMethod == nullptr) origMethod = (decltype(origMethod))dlsym(RTLD_NEXT, "EVP_DigestVerifyInit"); + bool fail = fail_EVP_DigestVerifyInit & 1; + fail_EVP_DigestVerifyInit = fail_EVP_DigestVerifyInit >> 1; + if (fail) + return 0; + else + return origMethod(ctx, pctx, type, e, pkey); +} + +int EVP_DigestVerify(EVP_MD_CTX* ctx, unsigned char* sigret, size_t* siglen, const unsigned char* tbs, size_t tbslen) { + static int (*origMethod)(EVP_MD_CTX * ctx, unsigned char* sigret, size_t* siglen, const unsigned char* tbs, + size_t tbslen) = nullptr; + if (origMethod == nullptr) origMethod = (decltype(origMethod))dlsym(RTLD_NEXT, "EVP_DigestVerify"); + bool fail = fail_EVP_DigestVerify & 1; + fail_EVP_DigestVerify = fail_EVP_DigestVerify >> 1; + if (fail) + return 0; + else + return origMethod(ctx, sigret, siglen, tbs, tbslen); +} + +int EVP_DigestVerifyFinal(EVP_MD_CTX* ctx, const unsigned char* sigret, size_t siglen) { + static int (*origMethod)(EVP_MD_CTX * ctx, const unsigned char* sigret, size_t siglen) = nullptr; + if (origMethod == nullptr) origMethod = (decltype(origMethod))dlsym(RTLD_NEXT, "EVP_DigestVerifyFinal"); + bool fail = fail_EVP_DigestVerifyFinal & 1; + fail_EVP_DigestVerifyFinal = fail_EVP_DigestVerifyFinal >> 1; + if (fail) + return 0; + else + return origMethod(ctx, sigret, siglen); +} + +int i2d_ECDSA_SIG(const ECDSA_SIG* sig, unsigned char** ppout) { + static int (*origMethod)(const ECDSA_SIG* sig, unsigned char** ppout) = nullptr; + if (origMethod == nullptr) origMethod = (decltype(origMethod))dlsym(RTLD_NEXT, "i2d_ECDSA_SIG"); + bool fail = fail_i2d_ECDSA_SIG & 1; + fail_i2d_ECDSA_SIG = fail_i2d_ECDSA_SIG >> 1; + if (fail) + return -1; + else + return origMethod(sig, ppout); +} + +ECDSA_SIG* d2i_ECDSA_SIG(ECDSA_SIG** psig, const unsigned char** ppin, long len) { + static ECDSA_SIG* (*origMethod)(ECDSA_SIG * *psig, const unsigned char** ppin, long len) = nullptr; + if (origMethod == nullptr) origMethod = (decltype(origMethod))dlsym(RTLD_NEXT, "d2i_ECDSA_SIG"); + bool fail = fail_d2i_ECDSA_SIG & 1; + fail_d2i_ECDSA_SIG = fail_d2i_ECDSA_SIG >> 1; + if (fail) + return nullptr; + else + return origMethod(psig, ppin, len); +} + +/** + * =========== End of black magic ============ + */ + +inline namespace test_keys { + extern std::string rsa_priv_key; + extern std::string rsa_pub_key; + extern std::string rsa_pub_key_invalid; + extern std::string rsa512_priv_key; + extern std::string rsa512_pub_key; + extern std::string rsa512_pub_key_invalid; + extern std::string ecdsa256_certificate; + extern std::string ecdsa256_priv_key; + extern std::string ecdsa256_pub_key; + extern std::string ecdsa256_pub_key_invalid; + extern std::string ecdsa384_priv_key; + extern std::string ecdsa384_pub_key; + extern std::string ecdsa384_pub_key_invalid; + extern std::string ecdsa521_priv_key; + extern std::string ecdsa521_pub_key; + extern std::string ecdsa521_pub_key_invalid; + extern std::string sample_cert; + extern std::string sample_cert_base64_der; + extern std::string sample_cert_pubkey; + extern std::string ed25519_priv_key; + extern std::string ed25519_pub_key; + extern std::string ed25519_pub_key_invalid; + extern std::string ed25519_certificate; + extern std::string ed448_priv_key; + extern std::string ed448_pub_key; + extern std::string ed448_pub_key_invalid; +} // namespace test_keys + +TEST(OpenSSLErrorTest, ExtractPubkeyFromCertReference) { + std::error_code ec; + auto res = jwt::helper::extract_pubkey_from_cert(sample_cert, "", ec); + ASSERT_EQ(res, sample_cert_pubkey); + ASSERT_FALSE(!(!ec)); + ASSERT_EQ(ec.value(), 0); +} + +TEST(OpenSSLErrorTest, ConvertCertBase64DerToPemReference) { + std::error_code ec; + auto res = jwt::helper::convert_base64_der_to_pem(sample_cert_base64_der, ec); + ASSERT_EQ(res, sample_cert); + ASSERT_FALSE(!(!ec)); + ASSERT_EQ(ec.value(), 0); +} + +struct multitest_entry { + uint64_t* fail_mask_ptr; + uint64_t fail_bitmask; + std::error_code expected_ec; +}; + +template +void run_multitest(const std::vector& mapping, Func fn) { + for (auto& e : mapping) { + std::error_code ec; + *e.fail_mask_ptr = e.fail_bitmask; + try { + fn(ec); + } catch (...) { + *e.fail_mask_ptr = 0; + throw; + } + *e.fail_mask_ptr = 0; + ASSERT_EQ(ec, e.expected_ec); + } +} + +TEST(OpenSSLErrorTest, ExtractPubkeyFromCert) { + std::vector mapping{{&fail_BIO_new, 1, jwt::error::rsa_error::create_mem_bio_failed}, + {&fail_PEM_read_bio_X509, 1, jwt::error::rsa_error::cert_load_failed}, + {&fail_X509_get_pubkey, 1, jwt::error::rsa_error::get_key_failed}, + {&fail_PEM_write_bio_PUBKEY, 1, jwt::error::rsa_error::write_key_failed}, + {&fail_BIO_ctrl, 1, jwt::error::rsa_error::convert_to_pem_failed}}; + + run_multitest(mapping, [](std::error_code& ec) { + try { + jwt::helper::extract_pubkey_from_cert(sample_cert, ""); + FAIL(); // Should never reach this + } catch (const jwt::error::rsa_exception& e) { ec = e.code(); } + }); +} + +TEST(OpenSSLErrorTest, ExtractPubkeyFromCertErrorCode) { + std::vector mapping{{&fail_BIO_new, 1, jwt::error::rsa_error::create_mem_bio_failed}, + {&fail_PEM_read_bio_X509, 1, jwt::error::rsa_error::cert_load_failed}, + {&fail_X509_get_pubkey, 1, jwt::error::rsa_error::get_key_failed}, + {&fail_PEM_write_bio_PUBKEY, 1, jwt::error::rsa_error::write_key_failed}, + {&fail_BIO_ctrl, 1, jwt::error::rsa_error::convert_to_pem_failed}}; + + run_multitest(mapping, [](std::error_code& ec) { + auto res = jwt::helper::extract_pubkey_from_cert(sample_cert, "", ec); + ASSERT_EQ(res, ""); + }); +} + +TEST(OpenSSLErrorTest, ConvertCertBase64DerToPem) { + std::vector mapping{{&fail_BIO_new, 1, jwt::error::rsa_error::create_mem_bio_failed}, + {&fail_PEM_write_bio_cert, 1, jwt::error::rsa_error::write_cert_failed}, + {&fail_BIO_ctrl, 1, jwt::error::rsa_error::convert_to_pem_failed}}; + + run_multitest(mapping, [](std::error_code& ec) { + try { + jwt::helper::convert_base64_der_to_pem(sample_cert_base64_der); + FAIL(); // Should never reach this + } catch (const jwt::error::rsa_exception& e) { ec = e.code(); } + }); +} + +TEST(OpenSSLErrorTest, ConvertCertBase64DerToPemErrorCode) { + std::vector mapping{{&fail_BIO_new, 1, jwt::error::rsa_error::create_mem_bio_failed}, + {&fail_PEM_write_bio_cert, 1, jwt::error::rsa_error::write_cert_failed}, + {&fail_BIO_ctrl, 1, jwt::error::rsa_error::convert_to_pem_failed}}; + + run_multitest(mapping, [](std::error_code& ec) { + auto res = jwt::helper::convert_base64_der_to_pem(sample_cert_base64_der, ec); + ASSERT_EQ(res, ""); + }); +} + +TEST(OpenSSLErrorTest, LoadPublicKeyFromStringReference) { + auto res = jwt::helper::load_public_key_from_string(rsa_pub_key, ""); + ASSERT_TRUE(res); +} + +TEST(OpenSSLErrorTest, LoadPublicKeyFromString) { + std::vector mapping{{&fail_BIO_new, 1, jwt::error::rsa_error::create_mem_bio_failed}, + {&fail_BIO_write, 1, jwt::error::rsa_error::load_key_bio_write}, + {&fail_PEM_read_bio_PUBKEY, 1, jwt::error::rsa_error::load_key_bio_read}}; + + run_multitest(mapping, [](std::error_code& ec) { + try { + jwt::helper::load_public_key_from_string(rsa_pub_key, ""); + FAIL(); // Should never reach this + } catch (const jwt::error::rsa_exception& e) { ec = e.code(); } + }); +} + +TEST(OpenSSLErrorTest, LoadPublicKeyFromStringErrorCode) { + std::vector mapping{{&fail_BIO_new, 1, jwt::error::rsa_error::create_mem_bio_failed}, + {&fail_BIO_write, 1, jwt::error::rsa_error::load_key_bio_write}, + {&fail_PEM_read_bio_PUBKEY, 1, jwt::error::rsa_error::load_key_bio_read}}; + + run_multitest(mapping, [](std::error_code& ec) { + auto res = jwt::helper::load_public_key_from_string(rsa_pub_key, "", ec); + ASSERT_FALSE(res); + }); +} + +TEST(OpenSSLErrorTest, LoadPublicKeyCertFromStringReference) { + auto res = jwt::helper::load_public_key_from_string(sample_cert, ""); + ASSERT_TRUE(res); +} + +TEST(OpenSSLErrorTest, LoadPublicKeyCertFromString) { + std::vector mapping { + {&fail_BIO_new, 1, jwt::error::rsa_error::create_mem_bio_failed}, +#if !defined(LIBRESSL_VERSION_NUMBER) || LIBRESSL_VERSION_NUMBER < 0x3050300fL + {&fail_BIO_write, 1, jwt::error::rsa_error::load_key_bio_write}, +#else + {&fail_BIO_write, 1, jwt::error::rsa_error::write_key_failed}, +#endif + { + &fail_PEM_read_bio_PUBKEY, 1, jwt::error::rsa_error::load_key_bio_read + } + }; + + run_multitest(mapping, [](std::error_code& ec) { + try { + jwt::helper::load_public_key_from_string(sample_cert, ""); + FAIL(); // Should never reach this + } catch (const jwt::error::rsa_exception& e) { ec = e.code(); } + }); +} + +TEST(OpenSSLErrorTest, LoadPublicKeyCertFromStringErrorCode) { + std::vector mapping { + {&fail_BIO_new, 1, jwt::error::rsa_error::create_mem_bio_failed}, +#if !defined(LIBRESSL_VERSION_NUMBER) || LIBRESSL_VERSION_NUMBER < 0x3050300fL + {&fail_BIO_write, 1, jwt::error::rsa_error::load_key_bio_write}, +#else + {&fail_BIO_write, 1, jwt::error::rsa_error::write_key_failed}, +#endif + { + &fail_PEM_read_bio_PUBKEY, 1, jwt::error::rsa_error::load_key_bio_read + } + }; + + run_multitest(mapping, [](std::error_code& ec) { + auto res = jwt::helper::load_public_key_from_string(sample_cert, "", ec); + ASSERT_FALSE(res); + }); +} + +TEST(OpenSSLErrorTest, LoadPrivateKeyFromStringReference) { + auto res = jwt::helper::load_private_key_from_string(rsa_priv_key, ""); + ASSERT_TRUE(res); +} + +TEST(OpenSSLErrorTest, LoadPrivateKeyFromString) { + std::vector mapping{{&fail_BIO_new, 1, jwt::error::rsa_error::create_mem_bio_failed}, + {&fail_BIO_write, 1, jwt::error::rsa_error::load_key_bio_write}, + {&fail_PEM_read_bio_PrivateKey, 1, jwt::error::rsa_error::load_key_bio_read}}; + + run_multitest(mapping, [](std::error_code& ec) { + try { + jwt::helper::load_private_key_from_string(rsa_priv_key, ""); + FAIL(); // Should never reach this + } catch (const jwt::error::rsa_exception& e) { ec = e.code(); } + }); +} + +TEST(OpenSSLErrorTest, LoadPrivateKeyFromStringErrorCode) { + std::vector mapping{{&fail_BIO_new, 1, jwt::error::rsa_error::create_mem_bio_failed}, + {&fail_BIO_write, 1, jwt::error::rsa_error::load_key_bio_write}, + {&fail_PEM_read_bio_PrivateKey, 1, jwt::error::rsa_error::load_key_bio_read}}; + + run_multitest(mapping, [](std::error_code& ec) { + auto res = jwt::helper::load_private_key_from_string(rsa_priv_key, "", ec); + ASSERT_FALSE(res); + }); +} + +TEST(OpenSSLErrorTest, HMACSign) { + std::string token = + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXUyJ9.eyJpc3MiOiJhdXRoMCJ9.AbIJTDMFc7yUa5MhvcP03nJPyCPzZtQcGEp-zWfOkEE"; + + auto verify = jwt::verify().allow_algorithm(jwt::algorithm::hs256{"secret"}).with_issuer("auth0"); + + auto decoded_token = jwt::decode(token); + std::vector mapping{{&fail_HMAC, 1, jwt::error::signature_generation_error::hmac_failed}}; + + run_multitest(mapping, [&](std::error_code& ec) { verify.verify(decoded_token, ec); }); +} + +TEST(OpenSSLErrorTest, RS256Reference) { + jwt::algorithm::rs256 alg{rsa_pub_key, rsa_priv_key}; + std::error_code ec; + auto res = alg.sign("testdata", ec); + ASSERT_EQ(jwt::base::encode(res), + "oCJUeLmIKKVVE/UWhEL/Malx0l9TCoXWNAS2z9o8ZYNaS4POIeadZWeUbLdICx3SCJCnGRwL8JkAmYx1wexT2QGuVXXAtZvRu8ceyuQy" + "AhzGkI9HdADu5YAJsUaLknDUV5hmundXQY8lhwQnKFXW0rl0H8DoFiPQErFmcKI6PA9NVGK/LSiqHqesNeg0wqCTxMmeT6pqI7FH9fDO" + "CaBpwUJ4t5aKoytQ75t13OfUM7tfLlVkFZtI3RndhivxLA5d4Elt/Gv3RhDu6Eiom5NZ/pwRvP26Sox+FWapz3DGCil70H1iGSYu8ENa" + "afUBCGGhT4sk7kl7zS6XiEpMobLq3A=="); + ASSERT_FALSE(!(!ec)); + + alg.verify("testdata", res, ec); + ASSERT_FALSE(!(!ec)); +} + +TEST(OpenSSLErrorTest, RS256SignErrorCode) { + jwt::algorithm::rs256 alg{rsa_pub_key, rsa_priv_key}; + std::vector mapping{ + {&fail_EVP_MD_CTX_new, 1, jwt::error::signature_generation_error::create_context_failed}, + {&fail_EVP_DigestInit, 1, jwt::error::signature_generation_error::signinit_failed}, + {&fail_EVP_DigestUpdate, 1, jwt::error::signature_generation_error::signupdate_failed}, + {&fail_EVP_SignFinal, 1, jwt::error::signature_generation_error::signfinal_failed}}; + + run_multitest(mapping, [&alg](std::error_code& ec) { + auto res = alg.sign("testdata", ec); + ASSERT_EQ(res, ""); + }); +} + +TEST(OpenSSLErrorTest, RS256VerifyErrorCode) { + jwt::algorithm::rs256 alg{rsa_pub_key, rsa_priv_key}; + auto signature = jwt::base::decode( + "oCJUeLmIKKVVE/UWhEL/Malx0l9TCoXWNAS2z9o8ZYNaS4POIeadZWeUbLdICx3SCJCnGRwL8JkAmYx1wexT2QGuVXXAtZvRu8ceyuQy" + "AhzGkI9HdADu5YAJsUaLknDUV5hmundXQY8lhwQnKFXW0rl0H8DoFiPQErFmcKI6PA9NVGK/LSiqHqesNeg0wqCTxMmeT6pqI7FH9fDO" + "CaBpwUJ4t5aKoytQ75t13OfUM7tfLlVkFZtI3RndhivxLA5d4Elt/Gv3RhDu6Eiom5NZ/pwRvP26Sox+FWapz3DGCil70H1iGSYu8ENa" + "afUBCGGhT4sk7kl7zS6XiEpMobLq3A=="); + std::vector mapping{ + {&fail_EVP_MD_CTX_new, 1, jwt::error::signature_verification_error::create_context_failed}, + {&fail_EVP_DigestInit, 1, jwt::error::signature_verification_error::verifyinit_failed}, + {&fail_EVP_DigestUpdate, 1, jwt::error::signature_verification_error::verifyupdate_failed}, + {&fail_EVP_VerifyFinal, 1, jwt::error::signature_verification_error::verifyfinal_failed}}; + + run_multitest(mapping, [&alg, &signature](std::error_code& ec) { alg.verify("testdata", signature, ec); }); +} + +TEST(OpenSSLErrorTest, LoadECDSAPrivateKeyFromString) { + std::vector mapping{ + {&fail_BIO_new, 1, jwt::error::ecdsa_error::create_mem_bio_failed}, + {&fail_BIO_write, 1, jwt::error::ecdsa_error::load_key_bio_write}, + {&fail_PEM_read_bio_PrivateKey, 1, jwt::error::ecdsa_error::load_key_bio_read}, +#ifdef JWT_OPENSSL_3_0 + {&fail_EVP_PKEY_private_check, 1, jwt::error::ecdsa_error::invalid_key}, + {&fail_EVP_PKEY_CTX_new_from_pkey, 1, jwt::error::ecdsa_error::create_context_failed}, +#else + {&fail_EC_KEY_check_key, 1, jwt::error::ecdsa_error::invalid_key}, + {&fail_EVP_PKEY_get1_EC_KEY, 1, jwt::error::ecdsa_error::invalid_key}, +#endif + }; + + run_multitest(mapping, [](std::error_code& ec) { + try { + jwt::algorithm::es256 alg{"", ecdsa256_priv_key}; + FAIL(); // Should never reach this + } catch (const std::system_error& e) { ec = e.code(); } + }); +} + +TEST(OpenSSLErrorTest, LoadECDSAPublicKeyFromString) { + std::vector mapping{ + {&fail_BIO_new, 1, jwt::error::ecdsa_error::create_mem_bio_failed}, + {&fail_BIO_write, 1, jwt::error::ecdsa_error::load_key_bio_write}, + {&fail_PEM_read_bio_PUBKEY, 1, jwt::error::ecdsa_error::load_key_bio_read}, +#ifdef JWT_OPENSSL_3_0 + {&fail_EVP_PKEY_public_check, 1, jwt::error::ecdsa_error::invalid_key}, + {&fail_EVP_PKEY_CTX_new_from_pkey, 1, jwt::error::ecdsa_error::create_context_failed}, +#else + {&fail_EC_KEY_check_key, 1, jwt::error::ecdsa_error::invalid_key}, + {&fail_EVP_PKEY_get1_EC_KEY, 1, jwt::error::ecdsa_error::invalid_key}, +#endif + }; + + run_multitest(mapping, [](std::error_code& ec) { + try { + jwt::algorithm::es256 alg{ecdsa256_pub_key, ""}; + FAIL(); // Should never reach this + } catch (const std::system_error& e) { ec = e.code(); } + }); +} + +TEST(OpenSSLErrorTest, ECDSACertificate) { + std::vector mapping { + {&fail_BIO_new, 1, jwt::error::ecdsa_error::create_mem_bio_failed}, +#if !defined(LIBRESSL_VERSION_NUMBER) || LIBRESSL_VERSION_NUMBER < 0x3050300fL + {&fail_BIO_write, 1, jwt::error::ecdsa_error::load_key_bio_write}, +#else + {&fail_BIO_write, 1, jwt::error::rsa_error::write_key_failed}, +#endif + {&fail_PEM_read_bio_PUBKEY, 1, jwt::error::ecdsa_error::load_key_bio_read}, + // extract_pubkey_from_cert + {&fail_BIO_new, 2, jwt::error::rsa_error::create_mem_bio_failed}, + {&fail_PEM_read_bio_X509, 1, jwt::error::rsa_error::cert_load_failed}, + {&fail_X509_get_pubkey, 1, jwt::error::rsa_error::get_key_failed}, + {&fail_PEM_write_bio_PUBKEY, 1, jwt::error::rsa_error::write_key_failed}, { + &fail_BIO_ctrl, 1, jwt::error::rsa_error::convert_to_pem_failed + } + }; + + run_multitest(mapping, [](std::error_code& ec) { + try { + jwt::algorithm::es256 alg{ecdsa256_certificate}; + FAIL(); // Should never reach this + } catch (const std::system_error& e) { ec = e.code(); } + }); +} + +TEST(OpenSSLErrorTest, ES256Reference) { + jwt::algorithm::es256 alg{ecdsa256_pub_key, ecdsa256_priv_key}; + std::error_code ec; + auto res = alg.sign("testdata", ec); + ASSERT_FALSE(!(!ec)); + + alg.verify("testdata", res, ec); + ASSERT_FALSE(!(!ec)); +} + +TEST(OpenSSLErrorTest, ES256SignErrorCode) { + jwt::algorithm::es256 alg{ecdsa256_pub_key, ecdsa256_priv_key}; + std::vector mapping{ + {&fail_EVP_MD_CTX_new, 1, jwt::error::signature_generation_error::create_context_failed}, + {&fail_EVP_DigestSignInit, 1, jwt::error::signature_generation_error::signinit_failed}, + {&fail_EVP_DigestUpdate, 1, jwt::error::signature_generation_error::digestupdate_failed}, + {&fail_EVP_DigestSignFinal, 1, jwt::error::signature_generation_error::signfinal_failed}, + {&fail_EVP_DigestSignFinal, 2, jwt::error::signature_generation_error::signfinal_failed}, + {&fail_d2i_ECDSA_SIG, 1, jwt::error::signature_generation_error::signature_decoding_failed}, + }; + + run_multitest(mapping, [&alg](std::error_code& ec) { + auto res = alg.sign("testdata", ec); + ASSERT_EQ(res, ""); + }); +} + +TEST(OpenSSLErrorTest, ES256VerifyErrorCode) { + jwt::algorithm::es256 alg{ecdsa256_pub_key, ecdsa256_priv_key}; + auto signature = jwt::base::decode( + "aC/NqyHfPw5FDA0yRAnrbkrAlXjsr0obRkCg/HgP+77QYJrAg6YKkKoJwMXjUX8fQrxXKTN7em5L9dtmOep37Q=="); + std::vector mapping{ + {&fail_EVP_MD_CTX_new, 1, jwt::error::signature_verification_error::create_context_failed}, + {&fail_EVP_DigestVerifyInit, 1, jwt::error::signature_verification_error::verifyinit_failed}, + {&fail_EVP_DigestUpdate, 1, jwt::error::signature_verification_error::verifyupdate_failed}, + {&fail_EVP_DigestVerifyFinal, 1, jwt::error::signature_verification_error::invalid_signature}, + {&fail_ECDSA_SIG_new, 1, jwt::error::signature_verification_error::create_context_failed}, + {&fail_i2d_ECDSA_SIG, 1, jwt::error::signature_verification_error::signature_encoding_failed}, + {&fail_i2d_ECDSA_SIG, 2, jwt::error::signature_verification_error::signature_encoding_failed}, + }; + + run_multitest(mapping, [&alg, &signature](std::error_code& ec) { alg.verify("testdata", signature, ec); }); +} + +TEST(OpenSSLErrorTest, PS256Reference) { + jwt::algorithm::ps256 alg{rsa_pub_key, rsa_priv_key}; + std::error_code ec; + auto res = alg.sign("testdata", ec); + ASSERT_FALSE(!(!ec)); + + alg.verify("testdata", res, ec); + ASSERT_FALSE(!(!ec)); +} + +TEST(OpenSSLErrorTest, PS256SignErrorCode) { + jwt::algorithm::ps256 alg{rsa_pub_key, rsa_priv_key}; + std::vector mapping{ + {&fail_EVP_MD_CTX_new, 1, jwt::error::signature_generation_error::create_context_failed}, + {&fail_EVP_DigestSignInit, 1, jwt::error::signature_generation_error::signinit_failed}, + {&fail_EVP_DigestUpdate, 1, jwt::error::signature_generation_error::digestupdate_failed}, + {&fail_EVP_DigestSignFinal, 1, jwt::error::signature_generation_error::signfinal_failed}, + //TODO: EVP_PKEY_CTX_set_rsa_padding, EVP_PKEY_CTX_set_rsa_pss_saltlen + }; + + run_multitest(mapping, [&alg](std::error_code& ec) { + auto res = alg.sign("testdata", ec); + ASSERT_EQ(res, ""); + }); +} + +TEST(OpenSSLErrorTest, PS256VerifyErrorCode) { + jwt::algorithm::ps256 alg{rsa_pub_key, rsa_priv_key}; + std::string signature = + "LMiWCiW0a/" + "mbU6LK8EZaDQ6TGisqfD+LF46zUbzjhFt02J9yVuf3ZDNTdRgLKKP8nCJUx0SN+5CS2YD268Ioxau5bWs49RVCxtID5DcRpJlSo+Vk+" + "dCmwxhQWHX8HNh3o7kBK5H8fLeTeupuSov+0hH3+" + "GRrYJqZvCdbcadi6amNKCfeIl6a5mp2VCM55NsPoRxsmSzc1G7AHWb1ckOCsm3KY5BL6B074bHgoqO3yaLlKWLAcy4OYyRpJ/wnZQ9PPrhwdq/" + "B59uW3x1QUCKYKgZeqZOoqIP1YgLwvEpPtXYutQCFr4eBKgV7vdtE0wgHR43ka16fi5L4SyaZv53NCg=="; + signature = jwt::base::decode(signature); + std::vector mapping{ + {&fail_EVP_MD_CTX_new, 1, jwt::error::signature_verification_error::create_context_failed}, + {&fail_EVP_DigestVerifyInit, 1, jwt::error::signature_verification_error::verifyinit_failed}, + {&fail_EVP_DigestUpdate, 1, jwt::error::signature_verification_error::verifyupdate_failed}, + {&fail_EVP_DigestVerifyFinal, 1, jwt::error::signature_verification_error::verifyfinal_failed}, + }; + + run_multitest(mapping, [&alg, &signature](std::error_code& ec) { alg.verify("testdata", signature, ec); }); +} + +#if !defined(JWT_OPENSSL_1_0_0) && !defined(JWT_OPENSSL_1_1_0) +TEST(OpenSSLErrorTest, EdDSAKey) { + std::vector mapping{ + // load_private_key_from_string + {&fail_BIO_new, 1, jwt::error::rsa_error::create_mem_bio_failed}, + {&fail_BIO_write, 1, jwt::error::rsa_error::load_key_bio_write}, + {&fail_PEM_read_bio_PrivateKey, 1, jwt::error::rsa_error::load_key_bio_read}, + // load_public_key_from_string + {&fail_BIO_new, 1, jwt::error::rsa_error::create_mem_bio_failed}, + {&fail_BIO_write, 1, jwt::error::rsa_error::load_key_bio_write}, + // { &fail_PEM_read_bio_PUBKEY, 1, jwt::error::rsa_error::load_key_bio_read } + }; + + run_multitest(mapping, [](std::error_code& ec) { + try { + jwt::algorithm::ed25519 alg{ed25519_pub_key, ed25519_priv_key}; + FAIL(); // Should never reach this + } catch (const std::system_error& e) { ec = e.code(); } + }); +} + +TEST(OpenSSLErrorTest, EdDSACertificate) { + std::vector mapping{// load_public_key_from_string + {&fail_BIO_new, 1, jwt::error::rsa_error::create_mem_bio_failed}, + {&fail_BIO_write, 1, jwt::error::rsa_error::load_key_bio_write}, + {&fail_PEM_read_bio_PUBKEY, 1, jwt::error::rsa_error::load_key_bio_read}, + // extract_pubkey_from_cert + {&fail_BIO_new, 2, jwt::error::rsa_error::create_mem_bio_failed}, + {&fail_PEM_read_bio_X509, 1, jwt::error::rsa_error::cert_load_failed}, + {&fail_X509_get_pubkey, 1, jwt::error::rsa_error::get_key_failed}, + {&fail_PEM_write_bio_PUBKEY, 1, jwt::error::rsa_error::write_key_failed}, + {&fail_BIO_ctrl, 1, jwt::error::rsa_error::convert_to_pem_failed}}; + + run_multitest(mapping, [](std::error_code& ec) { + try { + jwt::algorithm::ed25519 alg{ed25519_certificate}; + FAIL(); // Should never reach this + } catch (const std::system_error& e) { ec = e.code(); } + }); +} + +TEST(OpenSSLErrorTest, Ed25519Reference) { + jwt::algorithm::ed25519 alg{ed25519_pub_key, ed25519_priv_key}; + std::error_code ec; + auto res = alg.sign("testdata", ec); + ASSERT_FALSE(!(!ec)); + + alg.verify("testdata", res, ec); + ASSERT_FALSE(!(!ec)); +} + +TEST(OpenSSLErrorTest, Ed25519SignErrorCode) { + jwt::algorithm::ed25519 alg{ed25519_pub_key, ed25519_priv_key}; + std::vector mapping{ + {&fail_EVP_MD_CTX_new, 1, jwt::error::signature_generation_error::create_context_failed}, + {&fail_EVP_DigestSignInit, 1, jwt::error::signature_generation_error::signinit_failed}, + {&fail_EVP_DigestSign, 1, jwt::error::signature_generation_error::signfinal_failed}}; + + run_multitest(mapping, [&alg](std::error_code& ec) { + auto res = alg.sign("testdata", ec); + ASSERT_EQ(res, ""); + }); +} + +TEST(OpenSSLErrorTest, Ed25519VerifyErrorCode) { + jwt::algorithm::ed25519 alg{ed25519_pub_key, ed25519_priv_key}; + auto signature = jwt::base::decode( + "aC/NqyHfPw5FDA0yRAnrbkrAlXjsr0obRkCg/HgP+77QYJrAg6YKkKoJwMXjUX8fQrxXKTN7em5L9dtmOep37Q=="); + std::vector mapping{ + {&fail_EVP_MD_CTX_new, 1, jwt::error::signature_verification_error::create_context_failed}, + {&fail_EVP_DigestVerifyInit, 1, jwt::error::signature_verification_error::verifyinit_failed}, + {&fail_EVP_DigestVerify, 1, jwt::error::signature_verification_error::verifyfinal_failed}}; + + run_multitest(mapping, [&alg, &signature](std::error_code& ec) { alg.verify("testdata", signature, ec); }); +} +#endif +#endif +#endif +#endif diff --git a/dep/jwt-cpp/tests/TestMain.cpp b/dep/jwt-cpp/tests/TestMain.cpp new file mode 100644 index 0000000000..8f1c9c1b5f --- /dev/null +++ b/dep/jwt-cpp/tests/TestMain.cpp @@ -0,0 +1,6 @@ +#include + +int main(int argc, char* argv[]) { + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/dep/jwt-cpp/tests/TokenFormatTest.cpp b/dep/jwt-cpp/tests/TokenFormatTest.cpp new file mode 100644 index 0000000000..688c6e5091 --- /dev/null +++ b/dep/jwt-cpp/tests/TokenFormatTest.cpp @@ -0,0 +1,16 @@ +#include "jwt-cpp/jwt.h" +#include + +TEST(TokenFormatTest, MissingDot) { + ASSERT_THROW(jwt::decode("eyJhbGciOiJub25lIiwidHlwIjoiSldTIn0.eyJpc3MiOiJhdXRoMCJ9"), std::invalid_argument); + ASSERT_THROW(jwt::decode("eyJhbGciOiJub25lIiwidHlwIjoiSldTIn0eyJpc3MiOiJhdXRoMCJ9."), std::invalid_argument); + ASSERT_THROW(jwt::decode("eyJhbGciOiJub25lIiwidHlwIjoiSldTIn0eyJpc3MiOiJhdXRoMCJ9"), std::invalid_argument); +} + +TEST(TokenFormatTest, InvalidChar) { + ASSERT_THROW(jwt::decode("eyJhbGciOiJub25lIiwidHlwIjoiSldTIn0().eyJpc3MiOiJhdXRoMCJ9."), std::runtime_error); +} + +TEST(TokenFormatTest, InvalidJSON) { + ASSERT_THROW(jwt::decode("YXsiYWxnIjoibm9uZSIsInR5cCI6IkpXUyJ9YQ.eyJpc3MiOiJhdXRoMCJ9."), std::runtime_error); +} diff --git a/dep/jwt-cpp/tests/TokenTest.cpp b/dep/jwt-cpp/tests/TokenTest.cpp new file mode 100644 index 0000000000..a64e0d11c1 --- /dev/null +++ b/dep/jwt-cpp/tests/TokenTest.cpp @@ -0,0 +1,801 @@ +#include "jwt-cpp/jwt.h" +#include + +inline namespace test_keys { + extern std::string rsa_priv_key; + extern std::string rsa_pub_key; + extern std::string rsa_pub_key_invalid; + extern std::string rsa512_priv_key; + extern std::string rsa512_pub_key; + extern std::string rsa512_pub_key_invalid; + extern std::string ecdsa256_certificate; + extern std::string ecdsa256_priv_key; + extern std::string ecdsa256_pub_key; + extern std::string ecdsa256_pub_key_invalid; + extern std::string ecdsa384_priv_key; + extern std::string ecdsa384_pub_key; + extern std::string ecdsa384_pub_key_invalid; + extern std::string ecdsa521_priv_key; + extern std::string ecdsa521_pub_key; + extern std::string ecdsa521_pub_key_invalid; + extern std::string ed25519_priv_key; + extern std::string ed25519_pub_key; + extern std::string ed25519_pub_key_invalid; + extern std::string ed448_priv_key; + extern std::string ed448_pub_key; + extern std::string ed448_pub_key_invalid; +} // namespace test_keys + +TEST(TokenTest, DecodeToken) { + std::string token = + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXUyJ9.eyJpc3MiOiJhdXRoMCJ9.AbIJTDMFc7yUa5MhvcP03nJPyCPzZtQcGEp-zWfOkEE"; + auto decoded = jwt::decode(token); + + ASSERT_TRUE(decoded.has_algorithm()); + ASSERT_TRUE(decoded.has_type()); + ASSERT_FALSE(decoded.has_content_type()); + ASSERT_FALSE(decoded.has_key_id()); + ASSERT_TRUE(decoded.has_issuer()); + ASSERT_FALSE(decoded.has_subject()); + ASSERT_FALSE(decoded.has_audience()); + ASSERT_FALSE(decoded.has_expires_at()); + ASSERT_FALSE(decoded.has_not_before()); + ASSERT_FALSE(decoded.has_issued_at()); + ASSERT_FALSE(decoded.has_id()); + + ASSERT_EQ("HS256", decoded.get_algorithm()); + ASSERT_EQ("JWS", decoded.get_type()); + ASSERT_EQ("auth0", decoded.get_issuer()); +} + +TEST(TokenTest, CreateToken) { + auto token = jwt::create().set_issuer("auth0").set_type("JWS").sign(jwt::algorithm::none{}); + ASSERT_EQ("eyJhbGciOiJub25lIiwidHlwIjoiSldTIn0.eyJpc3MiOiJhdXRoMCJ9.", token); +} + +TEST(TokenTest, CreateTokenHS256) { + auto token = jwt::create().set_issuer("auth0").set_type("JWS").sign(jwt::algorithm::hs256{"secret"}); + ASSERT_EQ("eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXUyJ9.eyJpc3MiOiJhdXRoMCJ9.AbIJTDMFc7yUa5MhvcP03nJPyCPzZtQcGEp-zWfOkEE", + token); +} + +TEST(TokenTest, CreateTokenRS256) { + auto token = jwt::create().set_issuer("auth0").set_type("JWS").sign( + jwt::algorithm::rs256(rsa_pub_key, rsa_priv_key, "", "")); + + ASSERT_EQ( + "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXUyJ9.eyJpc3MiOiJhdXRoMCJ9.VA2i1ui1cnoD6I3wnji1WAVCf29EekysvevGrT2GXqK1dDMc8" + "HAZCTQxa1Q8NppnpYV-hlqxh-X3Bb0JOePTGzjynpNZoJh2aHZD-GKpZt7OO1Zp8AFWPZ3p8Cahq8536fD8RiBES9jRsvChZvOqA7gMcFc4" + "YD0iZhNIcI7a654u5yPYyTlf5kjR97prCf_OXWRn-bYY74zna4p_bP9oWCL4BkaoRcMxi-IR7kmVcCnvbYqyIrKloXP2qPO442RBGqU7Ov9" + "sGQxiVqtRHKXZR9RbfvjrErY1KGiCp9M5i2bsUHadZEY44FE2jiOmx-uc2z5c05CCXqVSpfCjWbh9gQ", + token); +} + +TEST(TokenTest, CreateTokenRS512) { + auto token = jwt::create().set_issuer("auth0").set_type("JWS").sign( + jwt::algorithm::rs512(rsa512_pub_key, rsa512_priv_key, "", "")); + + ASSERT_EQ("eyJhbGciOiJSUzUxMiIsInR5cCI6IkpXUyJ9.eyJpc3MiOiJhdXRoMCJ9.GZhnjtsvBl2_KDSxg4JW6xnmNjr2mWhYSZSSQyLKvI0" + "TK86sJKchkt_HDy2IC5l5BGRhq_Xv9pHdA1umidQZG3a7gWvHsujqybCBgBraMTd1wJrCl4QxFg2RYHhHbRqb9BnPJgFD_vryd4GB" + "hfGgejPBCBlGrQtqFGFdHHOjNHY", + token); +} + +TEST(TokenTest, CreateTokenPS256) { + auto token = jwt::create().set_issuer("auth0").set_type("JWS").sign( + jwt::algorithm::ps256(rsa_pub_key, rsa_priv_key, "", "")); + + // TODO: Find a better way to check if generated signature is valid + // Can't do simple check for equal since pss adds random salt. +} + +TEST(TokenTest, CreateTokenPS384) { + auto token = jwt::create().set_issuer("auth0").set_type("JWS").sign( + jwt::algorithm::ps384(rsa_pub_key, rsa_priv_key, "", "")); + + // TODO: Find a better way to check if generated signature is valid + // Can't do simple check for equal since pss adds random salt. +} + +TEST(TokenTest, CreateTokenPS512) { + auto token = jwt::create().set_issuer("auth0").set_type("JWS").sign( + jwt::algorithm::ps512(rsa_pub_key, rsa_priv_key, "", "")); + + // TODO: Find a better way to check if generated signature is valid + // Can't do simple check for equal since pss adds random salt. +} + +TEST(TokenTest, CreateTokenES256) { + + auto token = + jwt::create().set_issuer("auth0").set_type("JWS").sign(jwt::algorithm::es256("", ecdsa256_priv_key, "", "")); + + auto decoded = jwt::decode(token); + + ASSERT_THROW( + jwt::verify().allow_algorithm(jwt::algorithm::es256(ecdsa256_pub_key_invalid, "", "", "")).verify(decoded), + jwt::error::signature_verification_exception); + ASSERT_NO_THROW(jwt::verify().allow_algorithm(jwt::algorithm::es256(ecdsa256_pub_key, "", "", "")).verify(decoded)); +} + +TEST(TokenTest, CreateTokenES256NoPrivate) { + ASSERT_THROW( + []() { + auto token = jwt::create().set_issuer("auth0").set_type("JWS").sign( + jwt::algorithm::es256(ecdsa256_pub_key, "", "", "")); + }(), + jwt::error::signature_generation_exception); +} + +TEST(TokenTest, CreateTokenES384) { + + auto token = + jwt::create().set_issuer("auth0").set_type("JWS").sign(jwt::algorithm::es384("", ecdsa384_priv_key, "", "")); + + auto decoded = jwt::decode(token); + + ASSERT_THROW( + jwt::verify().allow_algorithm(jwt::algorithm::es384(ecdsa384_pub_key_invalid, "", "", "")).verify(decoded), + jwt::error::signature_verification_exception); + ASSERT_NO_THROW(jwt::verify().allow_algorithm(jwt::algorithm::es384(ecdsa384_pub_key, "", "", "")).verify(decoded)); +} + +TEST(TokenTest, CreateTokenES384NoPrivate) { + + ASSERT_THROW( + []() { + auto token = jwt::create().set_issuer("auth0").set_type("JWS").sign( + jwt::algorithm::es384(ecdsa384_pub_key, "", "", "")); + }(), + jwt::error::signature_generation_exception); +} + +TEST(TokenTest, CreateTokenES512) { + + auto token = + jwt::create().set_issuer("auth0").set_type("JWS").sign(jwt::algorithm::es512("", ecdsa521_priv_key, "", "")); + + auto decoded = jwt::decode(token); + + ASSERT_THROW( + jwt::verify().allow_algorithm(jwt::algorithm::es512(ecdsa521_pub_key_invalid, "", "", "")).verify(decoded), + jwt::error::signature_verification_exception); + ASSERT_NO_THROW(jwt::verify().allow_algorithm(jwt::algorithm::es512(ecdsa521_pub_key, "", "", "")).verify(decoded)); +} + +TEST(TokenTest, CreateTokenES512NoPrivate) { + + ASSERT_THROW( + []() { + auto token = jwt::create().set_issuer("auth0").set_type("JWS").sign( + jwt::algorithm::es512(ecdsa521_pub_key, "", "", "")); + }(), + jwt::error::signature_generation_exception); +} + +#if !defined(JWT_OPENSSL_1_0_0) && !defined(JWT_OPENSSL_1_1_0) +TEST(TokenTest, CreateTokenEd25519) { + + auto token = + jwt::create().set_issuer("auth0").set_type("JWS").sign(jwt::algorithm::ed25519("", ed25519_priv_key, "", "")); + + auto decoded = jwt::decode(token); + + ASSERT_THROW( + jwt::verify().allow_algorithm(jwt::algorithm::ed25519(ed25519_pub_key_invalid, "", "", "")).verify(decoded), + jwt::error::signature_verification_exception); + ASSERT_NO_THROW( + jwt::verify().allow_algorithm(jwt::algorithm::ed25519(ed25519_pub_key, "", "", "")).verify(decoded)); +} + +TEST(TokenTest, CreateTokenEd448) { + + auto token = + jwt::create().set_issuer("auth0").set_type("JWS").sign(jwt::algorithm::ed448("", ed448_priv_key, "", "")); + + auto decoded = jwt::decode(token); + + ASSERT_THROW( + jwt::verify().allow_algorithm(jwt::algorithm::ed448(ed448_pub_key_invalid, "", "", "")).verify(decoded), + jwt::error::signature_verification_exception); + ASSERT_NO_THROW(jwt::verify().allow_algorithm(jwt::algorithm::ed448(ed448_pub_key, "", "", "")).verify(decoded)); +} +#endif + +TEST(TokenTest, VerifyTokenWrongAlgorithm) { + std::string token = + "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXUyJ9.eyJpc3MiOiJhdXRoMCJ9.VA2i1ui1cnoD6I3wnji1WAVCf29EekysvevGrT2GXqK1dDMc8" + "HAZCTQxa1Q8NppnpYV-hlqxh-X3Bb0JOePTGzjynpNZoJh2aHZD-GKpZt7OO1Zp8AFWPZ3p8Cahq8536fD8RiBES9jRsvChZvOqA7gMcFc4" + "YD0iZhNIcI7a654u5yPYyTlf5kjR97prCf_OXWRn-bYY74zna4p_bP9oWCL4BkaoRcMxi-IR7kmVcCnvbYqyIrKloXP2qPO442RBGqU7Ov9" + "sGQxiVqtRHKXZR9RbfvjrErY1KGiCp9M5i2bsUHadZEY44FE2jiOmx-uc2z5c05CCXqVSpfCjWbh9gQ"; + + auto verify = jwt::verify().allow_algorithm(jwt::algorithm::none{}).with_issuer("auth0"); + + auto decoded_token = jwt::decode(token); + + ASSERT_THROW(verify.verify(decoded_token), jwt::error::token_verification_exception); +} + +TEST(TokenTest, VerifyTokenNoneFail) { + // None algorithm should not have a signature + std::string token = "eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJpYXQiOjE1OTUyNjc1MTZ9.cmFuZG9tc2ln"; + + auto verify = jwt::verify().allow_algorithm(jwt::algorithm::none{}); + + auto decoded_token = jwt::decode(token); + + ASSERT_THROW(verify.verify(decoded_token), jwt::error::signature_verification_exception); +} + +TEST(TokenTest, VerifyTokenRS256FailNoKey) { + ASSERT_THROW( + []() { + auto verify = jwt::verify().allow_algorithm(jwt::algorithm::rs256("", "", "", "")).with_issuer("auth0"); + }(), + jwt::error::rsa_exception); +} + +TEST(TokenTest, VerifyTokenRS256) { + std::string token = + "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXUyJ9.eyJpc3MiOiJhdXRoMCJ9.VA2i1ui1cnoD6I3wnji1WAVCf29EekysvevGrT2GXqK1dDMc8" + "HAZCTQxa1Q8NppnpYV-hlqxh-X3Bb0JOePTGzjynpNZoJh2aHZD-GKpZt7OO1Zp8AFWPZ3p8Cahq8536fD8RiBES9jRsvChZvOqA7gMcFc4" + "YD0iZhNIcI7a654u5yPYyTlf5kjR97prCf_OXWRn-bYY74zna4p_bP9oWCL4BkaoRcMxi-IR7kmVcCnvbYqyIrKloXP2qPO442RBGqU7Ov9" + "sGQxiVqtRHKXZR9RbfvjrErY1KGiCp9M5i2bsUHadZEY44FE2jiOmx-uc2z5c05CCXqVSpfCjWbh9gQ"; + + auto verify = + jwt::verify().allow_algorithm(jwt::algorithm::rs256(rsa_pub_key, rsa_priv_key, "", "")).with_issuer("auth0"); + + auto decoded_token = jwt::decode(token); + + verify.verify(decoded_token); +} + +TEST(TokenTest, VerifyTokenRS256PublicOnly) { + std::string token = + "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXUyJ9.eyJpc3MiOiJhdXRoMCJ9.VA2i1ui1cnoD6I3wnji1WAVCf29EekysvevGrT2GXqK1dDMc8" + "HAZCTQxa1Q8NppnpYV-hlqxh-X3Bb0JOePTGzjynpNZoJh2aHZD-GKpZt7OO1Zp8AFWPZ3p8Cahq8536fD8RiBES9jRsvChZvOqA7gMcFc4" + "YD0iZhNIcI7a654u5yPYyTlf5kjR97prCf_OXWRn-bYY74zna4p_bP9oWCL4BkaoRcMxi-IR7kmVcCnvbYqyIrKloXP2qPO442RBGqU7Ov9" + "sGQxiVqtRHKXZR9RbfvjrErY1KGiCp9M5i2bsUHadZEY44FE2jiOmx-uc2z5c05CCXqVSpfCjWbh9gQ"; + + auto verify = jwt::verify().allow_algorithm(jwt::algorithm::rs256(rsa_pub_key, "", "", "")).with_issuer("auth0"); + + auto decoded_token = jwt::decode(token); + + verify.verify(decoded_token); +} + +TEST(TokenTest, VerifyTokenRS256PrivateOnly) { + std::string token = + "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXUyJ9.eyJpc3MiOiJhdXRoMCJ9.VA2i1ui1cnoD6I3wnji1WAVCf29EekysvevGrT2GXqK1dDMc8" + "HAZCTQxa1Q8NppnpYV-hlqxh-X3Bb0JOePTGzjynpNZoJh2aHZD-GKpZt7OO1Zp8AFWPZ3p8Cahq8536fD8RiBES9jRsvChZvOqA7gMcFc4" + "YD0iZhNIcI7a654u5yPYyTlf5kjR97prCf_OXWRn-bYY74zna4p_bP9oWCL4BkaoRcMxi-IR7kmVcCnvbYqyIrKloXP2qPO442RBGqU7Ov9" + "sGQxiVqtRHKXZR9RbfvjrErY1KGiCp9M5i2bsUHadZEY44FE2jiOmx-uc2z5c05CCXqVSpfCjWbh9gQ"; + + auto verify = jwt::verify().allow_algorithm(jwt::algorithm::rs256("", rsa_priv_key, "", "")).with_issuer("auth0"); + + auto decoded_token = jwt::decode(token); + + verify.verify(decoded_token); +} + +TEST(TokenTest, VerifyTokenRS256Fail) { + std::string token = + "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXUyJ9.eyJpc3MiOiJhdXRoMCJ9.VA2i1ui1cnoD6I3wnji1WAVCf29EekysvevGrT2GXqK1dDMc8" + "HAZCTQxa1Q8NppnpYV-hlqxh-X3Bb0JOePTGzjynpNZoJh2aHZD-GKpZt7OO1Zp8AFWPZ3p8Cahq8536fD8RiBES9jRsvChZvOqA7gMcFc4" + "YD0iZhNIcI7a654u5yPYyTlf5kjR97prCf_OXWRn-bYY74zna4p_bP9oWCL4BkaoRcMxi-IR7kmVcCnvbYqyIrKloXP2qPO442RBGqU7Ov9" + "sGQxiVqtRHKXZR9RbfvjrErY1KGiCp9M5i2bsUHadZEY44FE2jiOmx-uc2z5c05CCXqVSpfCjWbh9gQ"; + + auto verify = + jwt::verify().allow_algorithm(jwt::algorithm::rs256(rsa_pub_key_invalid, "", "", "")).with_issuer("auth0"); + + auto decoded_token = jwt::decode(token); + + ASSERT_THROW(verify.verify(decoded_token), jwt::error::signature_verification_exception); +} + +TEST(TokenTest, VerifyTokenRS512) { + std::string token = + "eyJhbGciOiJSUzUxMiIsInR5cCI6IkpXUyJ9.eyJpc3MiOiJhdXRoMCJ9.GZhnjtsvBl2_KDSxg4JW6xnmNjr2mWhYSZ" + "SSQyLKvI0TK86sJKchkt_HDy2IC5l5BGRhq_Xv9pHdA1umidQZG3a7gWvHsujqybCBgBraMTd1wJrCl4QxFg2RYHhHbRqb9BnPJgFD_vryd4" + "GBhfGgejPBCBlGrQtqFGFdHHOjNHY"; + + auto verify = jwt::verify() + .allow_algorithm(jwt::algorithm::rs512(rsa512_pub_key, rsa512_priv_key, "", "")) + .with_issuer("auth0"); + + auto decoded_token = jwt::decode(token); + + verify.verify(decoded_token); +} + +TEST(TokenTest, VerifyTokenRS512PublicOnly) { + std::string token = + "eyJhbGciOiJSUzUxMiIsInR5cCI6IkpXUyJ9.eyJpc3MiOiJhdXRoMCJ9.GZhnjtsvBl2_KDSxg4JW6xnmNjr2mWhYSZ" + "SSQyLKvI0TK86sJKchkt_HDy2IC5l5BGRhq_Xv9pHdA1umidQZG3a7gWvHsujqybCBgBraMTd1wJrCl4QxFg2RYHhHbRqb9BnPJgFD_vryd4" + "GBhfGgejPBCBlGrQtqFGFdHHOjNHY"; + + auto verify = jwt::verify().allow_algorithm(jwt::algorithm::rs512(rsa512_pub_key, "", "", "")).with_issuer("auth0"); + + auto decoded_token = jwt::decode(token); + + verify.verify(decoded_token); +} + +TEST(TokenTest, VerifyTokenRS512PrivateOnly) { + std::string token = + "eyJhbGciOiJSUzUxMiIsInR5cCI6IkpXUyJ9.eyJpc3MiOiJhdXRoMCJ9.GZhnjtsvBl2_KDSxg4JW6xnmNjr2mWhYSZ" + "SSQyLKvI0TK86sJKchkt_HDy2IC5l5BGRhq_Xv9pHdA1umidQZG3a7gWvHsujqybCBgBraMTd1wJrCl4QxFg2RYHhHbRqb9BnPJgFD_vryd4" + "GBhfGgejPBCBlGrQtqFGFdHHOjNHY"; + + auto verify = + jwt::verify().allow_algorithm(jwt::algorithm::rs512("", rsa512_priv_key, "", "")).with_issuer("auth0"); + + auto decoded_token = jwt::decode(token); + + verify.verify(decoded_token); +} + +TEST(TokenTest, VerifyTokenRS512Fail) { + std::string token = + "eyJhbGciOiJSUzUxMiIsInR5cCI6IkpXUyJ9.eyJpc3MiOiJhdXRoMCJ9.GZhnjtsvBl2_KDSxg4JW6xnmNjr2mWhYSZ" + "SSQyLKvI0TK86sJKchkt_HDy2IC5l5BGRhq_Xv9pHdA1umidQZG3a7gWvHsujqybCBgBraMTd1wJrCl4QxFg2RYHhHbRqb9BnPJgFD_vryd4" + "GBhfGgejPBCBlGrQtqFGFdHHOjNHY"; + + auto verify = + jwt::verify().allow_algorithm(jwt::algorithm::rs512(rsa_pub_key_invalid, "", "", "")).with_issuer("auth0"); + + auto decoded_token = jwt::decode(token); + + ASSERT_THROW(verify.verify(decoded_token), jwt::error::signature_verification_exception); +} + +TEST(TokenTest, VerifyTokenHS256) { + std::string token = + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXUyJ9.eyJpc3MiOiJhdXRoMCJ9.AbIJTDMFc7yUa5MhvcP03nJPyCPzZtQcGEp-zWfOkEE"; + + auto verify = jwt::verify().allow_algorithm(jwt::algorithm::hs256{"secret"}).with_issuer("auth0"); + + auto decoded_token = jwt::decode(token); + verify.verify(decoded_token); +} + +TEST(TokenTest, VerifyTokenHS256Fail) { + std::string token = + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXUyJ9.eyJpc3MiOiJhdXRoMCJ9.AbIJTDMFc7yUa5MhvcP03nJPyCPzZtQcGEp-zWfOkEE"; + + auto verify = jwt::verify().allow_algorithm(jwt::algorithm::hs256{"wrongsecret"}).with_issuer("auth0"); + + auto decoded_token = jwt::decode(token); + ASSERT_THROW(verify.verify(decoded_token), jwt::error::signature_verification_exception); +} + +TEST(TokenTest, VerifyTokenHS256FailSignatureLength) { + std::string token = + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXUyJ9.eyJpc3MiOiJhdXRoMCJ9.AbIJTDMFc7yUa5MhvcP03nJPyCPzZtQcGEp-zWfOkA"; + + auto verify = jwt::verify().allow_algorithm(jwt::algorithm::hs256{"secret"}).with_issuer("auth0"); + + auto decoded_token = jwt::decode(token); + ASSERT_THROW(verify.verify(decoded_token), jwt::error::signature_verification_exception); +} + +TEST(TokenTest, VerifyFail) { + { + auto token = jwt::create() + .set_issuer("auth0") + .set_type("JWS") + .set_audience("random") + .set_payload_claim("typetest", picojson::value(10.0)) + .sign(jwt::algorithm::none{}); + + auto decoded_token = jwt::decode(token); + + { + auto verify = jwt::verify().allow_algorithm(jwt::algorithm::none{}).with_issuer("auth"); + ASSERT_THROW(verify.verify(decoded_token), jwt::error::token_verification_exception); + } + { + auto verify = jwt::verify().allow_algorithm(jwt::algorithm::none{}).with_type("JWT"); + ASSERT_THROW(verify.verify(decoded_token), jwt::error::token_verification_exception); + } + { + auto verify = jwt::verify() + .allow_algorithm(jwt::algorithm::none{}) + .with_issuer("auth0") + .with_audience(std::set{"test"}); + ASSERT_THROW(verify.verify(decoded_token), jwt::error::token_verification_exception); + } + { + auto verify = + jwt::verify().allow_algorithm(jwt::algorithm::none{}).with_issuer("auth0").with_audience("test"); + ASSERT_THROW(verify.verify(decoded_token), jwt::error::token_verification_exception); + } + { + auto verify = + jwt::verify().allow_algorithm(jwt::algorithm::none{}).with_issuer("auth0").with_subject("test"); + ASSERT_THROW(verify.verify(decoded_token), jwt::error::token_verification_exception); + } + { + auto verify = jwt::verify() + .allow_algorithm(jwt::algorithm::none{}) + .with_issuer("auth0") + .with_claim("myclaim", jwt::claim(std::string("test"))); + ASSERT_THROW(verify.verify(decoded_token), jwt::error::token_verification_exception); + } + { + auto verify = jwt::verify() + .allow_algorithm(jwt::algorithm::none{}) + .with_issuer("auth0") + .with_claim("typetest", jwt::claim(picojson::value(true))); + ASSERT_THROW(verify.verify(decoded_token), jwt::error::token_verification_exception); + } + { + jwt::claim object; + std::istringstream iss{R"({ "test": null })"}; + iss >> object; + ASSERT_EQ(object.get_type(), jwt::json::type::object); + + auto verify = jwt::verify() + .allow_algorithm(jwt::algorithm::none{}) + .with_issuer("auth0") + .with_claim("myclaim", object); + ASSERT_THROW(verify.verify(decoded_token), jwt::error::token_verification_exception); + } + } + { + auto token = jwt::create().set_issuer("auth0").set_type("JWS").sign(jwt::algorithm::none{}); + + auto decoded_token = jwt::decode(token); + + { + auto verify = + jwt::verify().allow_algorithm(jwt::algorithm::none{}).with_issuer("auth0").with_audience("test"); + ASSERT_THROW(verify.verify(decoded_token), jwt::error::token_verification_exception); + } + } +} + +TEST(TokenTest, VerifyTokenES256FailNoKey) { + ASSERT_THROW( + []() { + auto verify = jwt::verify().allow_algorithm(jwt::algorithm::es256("", "", "", "")).with_issuer("auth0"); + }(), + jwt::error::ecdsa_exception); +} + +TEST(TokenTest, VerifyTokenES256) { + const std::string token = "eyJhbGciOiJFUzI1NiJ9.eyJpc3MiOiJhdXRoMCJ9.4iVk3-Y0v4RT4_9IaQlp-8dZ_" + "4fsTzIylgrPTDLrEvTHBTyVS3tgPbr2_IZfLETtiKRqCg0aQ5sh9eIsTTwB1g"; + + auto verify = jwt::verify().allow_algorithm(jwt::algorithm::es256(ecdsa256_pub_key, "", "", "")); + auto decoded_token = jwt::decode(token); + + verify.verify(decoded_token); +} + +TEST(TokenTest, VerifyTokenES256Fail) { + const std::string token = "eyJhbGciOiJFUzI1NiJ9.eyJpc3MiOiJhdXRoMCJ9.4iVk3-Y0v4RT4_9IaQlp-8dZ_" + "4fsTzIylgrPTDLrEvTHBTyVS3tgPbr2_IZfLETtiKRqCg0aQ5sh9eIsTTwB1g"; + + auto verify = jwt::verify().allow_algorithm(jwt::algorithm::es256(ecdsa256_pub_key_invalid, "", "", "")); + auto decoded_token = jwt::decode(token); + + ASSERT_THROW(verify.verify(decoded_token), jwt::error::signature_verification_exception); +} + +TEST(TokenTest, VerifyTokenES384) { + const std::string token = + "eyJhbGciOiJFUzM4NCIsInR5cCI6IkpXUyJ9.eyJpc3MiOiJhdXRoMCJ9.nTUwWanmj_K1VZM5it1ES-1FbnmRDL-lH3V_Fem-" + "AhMur9Q61yZfKIydrpdavkm_SMxEsUGPVoqkpoEsjFjrtzMDs5s9yaFYD_ydiy1dsn9VbcI55voA3XwEcWFiPHri"; + + auto verify = jwt::verify().allow_algorithm(jwt::algorithm::es384(ecdsa384_pub_key, "", "", "")); + auto decoded_token = jwt::decode(token); + + verify.verify(decoded_token); +} + +TEST(TokenTest, VerifyTokenES384Fail) { + const std::string token = + "eyJhbGciOiJFUzM4NCIsInR5cCI6IkpXUyJ9.eyJpc3MiOiJhdXRoMCJ9.nTUwWanmj_K1VZM5it1ES-1FbnmRDL-lH3V_Fem-" + "AhMur9Q61yZfKIydrpdavkm_SMxEsUGPVoqkpoEsjFjrtzMDs5s9yaFYD_ydiy1dsn9VbcI55voA3XwEcWFiPHri"; + + auto verify = jwt::verify().allow_algorithm(jwt::algorithm::es384(ecdsa384_pub_key_invalid, "", "", "")); + auto decoded_token = jwt::decode(token); + + ASSERT_THROW(verify.verify(decoded_token), jwt::error::signature_verification_exception); +} + +TEST(TokenTest, VerifyTokenES521) { + const std::string token = + "eyJhbGciOiJFUzUxMiIsInR5cCI6IkpXUyJ9.eyJpc3MiOiJhdXRoMCJ9.ASF5hh9_Jyujzm3GRBttoth-3I6lCcwqun9Tt7Ekz9_23BN6-" + "BFgwKidECWCNc4VINEqFEFdApC2y3YRdkpKX2etAWI7yYudAlxJ7Z17m6GwAoLOGaeNonsaKOe1UnC5W86eoXrCoPRgzsFTpKIb8NiolcYWjIY" + "-r8gQd7BZ7whaj9Ft"; + + auto verify = jwt::verify().allow_algorithm(jwt::algorithm::es512(ecdsa521_pub_key, "", "", "")); + auto decoded_token = jwt::decode(token); + + verify.verify(decoded_token); +} + +TEST(TokenTest, VerifyTokenES521Fail) { + const std::string token = + "eyJhbGciOiJFUzUxMiIsInR5cCI6IkpXUyJ9.eyJpc3MiOiJhdXRoMCJ9.ASF5hh9_Jyujzm3GRBttoth-3I6lCcwqun9Tt7Ekz9_23BN6-" + "BFgwKidECWCNc4VINEqFEFdApC2y3YRdkpKX2etAWI7yYudAlxJ7Z17m6GwAoLOGaeNonsaKOe1UnC5W86eoXrCoPRgzsFTpKIb8NiolcYWjIY" + "-r8gQd7BZ7whaj9Ft"; + + auto verify = jwt::verify().allow_algorithm(jwt::algorithm::es512(ecdsa521_pub_key_invalid, "", "", "")); + auto decoded_token = jwt::decode(token); + + ASSERT_THROW(verify.verify(decoded_token), jwt::error::signature_verification_exception); +} + +TEST(TokenTest, VerifyTokenPS256) { + std::string token = + "eyJhbGciOiJQUzI1NiIsInR5cCI6IkpXUyJ9.eyJpc3MiOiJhdXRoMCJ9.CJ4XjVWdbV6vXGZkD4GdJbtYc80SN9cmPOqRhZBRzOyDRqTFE" + "4MsbdKyQuhAWcvuMOjn-24qOTjVMR_P_uTC1uG6WPLcucxZyLnbb56zbKnEklW2SX0mQnCGewr-93a_vDaFT6Cp45MsF_OwFPRCMaS5CJg-" + "N5KY67UrVSr3s9nkuK9ZTQkyODHfyEUh9F_FhRCATGrb5G7_qHqBYvTvaPUXqzhhpCjN855Tocg7A24Hl0yMwM-XdasucW5xNdKjG_YCkis" + "HX7ax--JiF5GNYCO61eLFteO4THUg-3Z0r4OlGqlppyWo5X5tjcxOZCvBh7WDWfkxA48KFZPRv0nlKA"; + + auto verify = + jwt::verify().allow_algorithm(jwt::algorithm::ps256(rsa_pub_key, rsa_priv_key, "", "")).with_issuer("auth0"); + + auto decoded_token = jwt::decode(token); + + verify.verify(decoded_token); +} + +TEST(TokenTest, VerifyTokenPS256PublicOnly) { + std::string token = + "eyJhbGciOiJQUzI1NiIsInR5cCI6IkpXUyJ9.eyJpc3MiOiJhdXRoMCJ9.CJ4XjVWdbV6vXGZkD4GdJbtYc80SN9cmPOqRhZBRzOyDRqTFE" + "4MsbdKyQuhAWcvuMOjn-24qOTjVMR_P_uTC1uG6WPLcucxZyLnbb56zbKnEklW2SX0mQnCGewr-93a_vDaFT6Cp45MsF_OwFPRCMaS5CJg-" + "N5KY67UrVSr3s9nkuK9ZTQkyODHfyEUh9F_FhRCATGrb5G7_qHqBYvTvaPUXqzhhpCjN855Tocg7A24Hl0yMwM-XdasucW5xNdKjG_YCkis" + "HX7ax--JiF5GNYCO61eLFteO4THUg-3Z0r4OlGqlppyWo5X5tjcxOZCvBh7WDWfkxA48KFZPRv0nlKA"; + + auto verify = jwt::verify().allow_algorithm(jwt::algorithm::ps256(rsa_pub_key, "", "", "")).with_issuer("auth0"); + + auto decoded_token = jwt::decode(token); + + verify.verify(decoded_token); +} + +TEST(TokenTest, VerifyTokenPS256Fail) { + std::string token = + "eyJhbGciOiJQUzI1NiIsInR5cCI6IkpXUyJ9.eyJpc3MiOiJhdXRoMCJ9.CJ4XjVWdbV6vXGZkD4GdJbtYc80SN9cmPOqRhZBRzOyDRqTFE" + "4MsbdKyQuhAWcvuMOjn-24qOTjVMR_P_uTC1uG6WPLcucxZyLnbb56zbKnEklW2SX0mQnCGewr-93a_vDaFT6Cp45MsF_OwFPRCMaS5CJg-" + "N5KY67UrVSr3s9nkuK9ZTQkyODHfyEUh9F_FhRCATGrb5G7_qHqBYvTvaPUXqzhhpCjN855Tocg7A24Hl0yMwM-XdasucW5xNdKjG_YCkis" + "HX7ax--JiF5GNYCO61eLFteO4THUg-3Z0r4OlGqlppyWo5X5tjcxOZCvBh7WDWfkxA48KFZPRv0nlKA"; + + auto verify = + jwt::verify().allow_algorithm(jwt::algorithm::ps256(rsa_pub_key_invalid, "", "", "")).with_issuer("auth0"); + + auto decoded_token = jwt::decode(token); + + ASSERT_THROW(verify.verify(decoded_token), jwt::error::signature_verification_exception); +} + +TEST(TokenTest, VerifyTokenPS256FailNoKey) { + ASSERT_THROW( + []() { + auto verify = jwt::verify().allow_algorithm(jwt::algorithm::ps256("", "", "", "")).with_issuer("auth0"); + }(), + jwt::error::rsa_exception); +} + +#if !defined(JWT_OPENSSL_1_0_0) && !defined(JWT_OPENSSL_1_1_0) +TEST(TokenTest, VerifyTokenEd25519) { + const std::string token = + "eyJhbGciOiJFZERTQSIsInR5cCI6IkpXUyJ9.eyJpc3MiOiJhdXRoMCJ9.OujgVcO8xQx5xLcAYWENCRU1SCGH5HcX4MX4o6wU3M4" + "DOnKiNmc0O2AnvQlzr-9cgI4QGQzeC6gz_fgLoesADg"; + + auto verify = jwt::verify().allow_algorithm(jwt::algorithm::ed25519(ed25519_pub_key, "", "", "")); + auto decoded_token = jwt::decode(token); + + verify.verify(decoded_token); +} + +TEST(TokenTest, VerifyTokenEd25519Fail) { + const std::string token = + "eyJhbGciOiJFZERTQSIsInR5cCI6IkpXUyJ9.eyJpc3MiOiJhdXRoMCJ9.OujgVcO8xQx5xLcAYWENCRU1SCGH5HcX4MX4o6wU3M4" + "DOnKiNmc0O2AnvQlzr-9cgI4QGQzeC6gz_fgLoesADg"; + + auto verify = jwt::verify().allow_algorithm(jwt::algorithm::ed25519(ed25519_pub_key_invalid, "", "", "")); + auto decoded_token = jwt::decode(token); + + ASSERT_THROW(verify.verify(decoded_token), jwt::error::signature_verification_exception); +} + +TEST(TokenTest, VerifyTokenEd448) { + const std::string token = + "eyJhbGciOiJFZERTQSIsInR5cCI6IkpXUyJ9.eyJpc3MiOiJhdXRoMCJ9.Aldes9jrXZXxfNjuovqmIZ3r2WF4yVXVr2Q8B8SkAmv" + "Bsw_3MHs8HtgKeXbqKFYWpHOCtmZJcH-AWMvoY6FCNdQqbESGTkv58O6tFbXDD_nLejWNAOuvcO2LPMySmkVNQUopmQf_HO62Mug1ngepUDE" + "A"; + + auto verify = jwt::verify().allow_algorithm(jwt::algorithm::ed448(ed448_pub_key, "", "", "")); + auto decoded_token = jwt::decode(token); + + verify.verify(decoded_token); +} + +TEST(TokenTest, VerifyTokenEd448Fail) { + const std::string token = + "eyJhbGciOiJFZERTQSIsInR5cCI6IkpXUyJ9.eyJpc3MiOiJhdXRoMCJ9.Aldes9jrXZXxfNjuovqmIZ3r2WF4yVXVr2Q8B8SkAmv" + "Bsw_3MHs8HtgKeXbqKFYWpHOCtmZJcH-AWMvoY6FCNdQqbESGTkv58O6tFbXDD_nLejWNAOuvcO2LPMySmkVNQUopmQf_HO62Mug1ngepUDE" + "A"; + + auto verify = jwt::verify().allow_algorithm(jwt::algorithm::ed448(ed448_pub_key_invalid, "", "", "")); + auto decoded_token = jwt::decode(token); + + ASSERT_THROW(verify.verify(decoded_token), jwt::error::signature_verification_exception); +} +#endif + +struct test_clock { + jwt::date n; + jwt::date now() const { return n; } +}; + +TEST(TokenTest, VerifyTokenExpireFail) { + auto token = jwt::create().set_expires_at(std::chrono::system_clock::from_time_t(100)).sign(jwt::algorithm::none{}); + auto decoded_token = jwt::decode(token); + + auto verify = jwt::verify({std::chrono::system_clock::from_time_t(110)}) + .allow_algorithm(jwt::algorithm::none{}); + ASSERT_THROW(verify.verify(decoded_token), jwt::error::token_verification_exception); + std::error_code ec; + ASSERT_NO_THROW(verify.verify(decoded_token, ec)); + ASSERT_TRUE(!(!ec)); + ASSERT_EQ(ec.category(), jwt::error::token_verification_error_category()); + ASSERT_EQ(ec.value(), static_cast(jwt::error::token_verification_error::token_expired)); +} + +TEST(TokenTest, VerifyTokenExpire) { + auto token = jwt::create().set_expires_at(std::chrono::system_clock::from_time_t(100)).sign(jwt::algorithm::none{}); + auto decoded_token = jwt::decode(token); + + auto verify = jwt::verify({std::chrono::system_clock::from_time_t(90)}) + .allow_algorithm(jwt::algorithm::none{}); + ASSERT_NO_THROW(verify.verify(decoded_token)); + std::error_code ec; + ASSERT_NO_THROW(verify.verify(decoded_token, ec)); + ASSERT_FALSE(!(!ec)); + ASSERT_EQ(ec.value(), 0); +} + +TEST(TokenTest, VerifyTokenNBFFail) { + auto token = jwt::create().set_not_before(std::chrono::system_clock::from_time_t(100)).sign(jwt::algorithm::none{}); + auto decoded_token = jwt::decode(token); + + auto verify = jwt::verify({std::chrono::system_clock::from_time_t(90)}) + .allow_algorithm(jwt::algorithm::none{}); + ASSERT_THROW(verify.verify(decoded_token), jwt::error::token_verification_exception); + std::error_code ec; + ASSERT_NO_THROW(verify.verify(decoded_token, ec)); + ASSERT_TRUE(!(!ec)); + ASSERT_EQ(ec.category(), jwt::error::token_verification_error_category()); + ASSERT_EQ(ec.value(), static_cast(jwt::error::token_verification_error::token_expired)); +} + +TEST(TokenTest, VerifyTokenNBF) { + auto token = jwt::create().set_not_before(std::chrono::system_clock::from_time_t(100)).sign(jwt::algorithm::none{}); + auto decoded_token = jwt::decode(token); + + auto verify = jwt::verify({std::chrono::system_clock::from_time_t(110)}) + .allow_algorithm(jwt::algorithm::none{}); + ASSERT_NO_THROW(verify.verify(decoded_token)); + std::error_code ec; + ASSERT_NO_THROW(verify.verify(decoded_token, ec)); + ASSERT_FALSE(!(!ec)); + ASSERT_EQ(ec.value(), 0); +} + +TEST(TokenTest, VerifyTokenIATFail) { + auto token = jwt::create().set_issued_at(std::chrono::system_clock::from_time_t(100)).sign(jwt::algorithm::none{}); + auto decoded_token = jwt::decode(token); + + auto verify = jwt::verify({std::chrono::system_clock::from_time_t(90)}) + .allow_algorithm(jwt::algorithm::none{}); + ASSERT_THROW(verify.verify(decoded_token), jwt::error::token_verification_exception); + std::error_code ec; + ASSERT_NO_THROW(verify.verify(decoded_token, ec)); + ASSERT_TRUE(!(!ec)); + ASSERT_EQ(ec.category(), jwt::error::token_verification_error_category()); + ASSERT_EQ(ec.value(), static_cast(jwt::error::token_verification_error::token_expired)); +} + +TEST(TokenTest, VerifyTokenIAT) { + auto token = jwt::create().set_issued_at(std::chrono::system_clock::from_time_t(100)).sign(jwt::algorithm::none{}); + auto decoded_token = jwt::decode(token); + + auto verify = jwt::verify({std::chrono::system_clock::from_time_t(110)}) + .allow_algorithm(jwt::algorithm::none{}); + ASSERT_NO_THROW(verify.verify(decoded_token)); + std::error_code ec; + ASSERT_NO_THROW(verify.verify(decoded_token, ec)); + ASSERT_FALSE(!(!ec)); + ASSERT_EQ(ec.value(), 0); +} + +TEST(TokenTest, VerifyTokenType) { + auto token = jwt::create().set_type("JWS").sign(jwt::algorithm::none{}); + auto decoded_token = jwt::decode(token); + + auto verify = jwt::verify().with_type("jws").allow_algorithm(jwt::algorithm::none{}); + ASSERT_NO_THROW(verify.verify(decoded_token)); + std::error_code ec; + ASSERT_NO_THROW(verify.verify(decoded_token, ec)); + ASSERT_FALSE(!(!ec)); + ASSERT_EQ(ec.value(), 0); +} + +TEST(TokenTest, GetClaimThrows) { + auto token = "eyJhbGciOiJub25lIiwidHlwIjoiSldTIn0.eyJpc3MiOiJhdXRoMCJ9."; + auto decoded_token = jwt::decode(token); + + ASSERT_THROW(decoded_token.get_header_claim("test"), jwt::error::claim_not_present_exception); + ASSERT_THROW(decoded_token.get_payload_claim("test"), jwt::error::claim_not_present_exception); +} + +TEST(TokenTest, ThrowInvalidKeyLength) { + // We should throw if passed the wrong size + ASSERT_THROW(jwt::algorithm::es256(ecdsa384_pub_key, ""), jwt::error::ecdsa_exception); + ASSERT_THROW(jwt::algorithm::es256("", ecdsa384_priv_key), jwt::error::ecdsa_exception); + ASSERT_THROW(jwt::algorithm::es256(ecdsa384_pub_key, ecdsa384_priv_key), jwt::error::ecdsa_exception); + ASSERT_THROW(jwt::algorithm::es256(ecdsa521_pub_key, ""), jwt::error::ecdsa_exception); + ASSERT_THROW(jwt::algorithm::es256("", ecdsa521_priv_key), jwt::error::ecdsa_exception); + ASSERT_THROW(jwt::algorithm::es256(ecdsa521_pub_key, ecdsa521_priv_key), jwt::error::ecdsa_exception); + + // But also if only one cert has the wrong size + ASSERT_THROW(jwt::algorithm::es256(ecdsa256_pub_key, ecdsa384_priv_key), jwt::error::ecdsa_exception); + ASSERT_THROW(jwt::algorithm::es256(ecdsa256_pub_key, ecdsa521_priv_key), jwt::error::ecdsa_exception); + + ASSERT_THROW(jwt::algorithm::es384(ecdsa256_pub_key, ""), jwt::error::ecdsa_exception); + ASSERT_THROW(jwt::algorithm::es384("", ecdsa256_priv_key), jwt::error::ecdsa_exception); + ASSERT_THROW(jwt::algorithm::es384(ecdsa256_pub_key, ecdsa256_priv_key), jwt::error::ecdsa_exception); + ASSERT_THROW(jwt::algorithm::es384(ecdsa521_pub_key, ""), jwt::error::ecdsa_exception); + ASSERT_THROW(jwt::algorithm::es384("", ecdsa521_priv_key), jwt::error::ecdsa_exception); + ASSERT_THROW(jwt::algorithm::es384(ecdsa521_pub_key, ecdsa521_priv_key), jwt::error::ecdsa_exception); + + ASSERT_THROW(jwt::algorithm::es384(ecdsa384_pub_key, ecdsa256_priv_key), jwt::error::ecdsa_exception); + ASSERT_THROW(jwt::algorithm::es384(ecdsa384_pub_key, ecdsa521_priv_key), jwt::error::ecdsa_exception); + + ASSERT_THROW(jwt::algorithm::es512(ecdsa256_pub_key, ""), jwt::error::ecdsa_exception); + ASSERT_THROW(jwt::algorithm::es512("", ecdsa256_priv_key), jwt::error::ecdsa_exception); + ASSERT_THROW(jwt::algorithm::es512(ecdsa256_pub_key, ecdsa256_priv_key), jwt::error::ecdsa_exception); + ASSERT_THROW(jwt::algorithm::es512(ecdsa384_pub_key, ""), jwt::error::ecdsa_exception); + ASSERT_THROW(jwt::algorithm::es512("", ecdsa384_priv_key), jwt::error::ecdsa_exception); + ASSERT_THROW(jwt::algorithm::es512(ecdsa384_pub_key, ecdsa384_priv_key), jwt::error::ecdsa_exception); + + ASSERT_THROW(jwt::algorithm::es512(ecdsa521_pub_key, ecdsa256_priv_key), jwt::error::ecdsa_exception); + ASSERT_THROW(jwt::algorithm::es512(ecdsa521_pub_key, ecdsa384_priv_key), jwt::error::ecdsa_exception); + + // Make sure we do not throw if the correct params are passed + ASSERT_NO_THROW(jwt::algorithm::es256(ecdsa256_pub_key, ecdsa256_priv_key)); + ASSERT_NO_THROW(jwt::algorithm::es384(ecdsa384_pub_key, ecdsa384_priv_key)); + ASSERT_NO_THROW(jwt::algorithm::es512(ecdsa521_pub_key, ecdsa521_priv_key)); +} + +TEST(TokenTest, MoveDecodedToken) { + std::string token0 = "eyJhbGciOiJub25lIiwidHlwIjoiSldTIn0.eyJpc3MiOiJhdXRoMCJ9."; + std::string token1 = + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXUyJ9.eyJpc3MiOiJhdXRoMCJ9.AbIJTDMFc7yUa5MhvcP03nJPyCPzZtQcGEp-zWfOkEE"; + auto decoded_token0 = jwt::decode(token0); + auto decoded_token1 = jwt::decode(token1); + decoded_token0 = std::move(decoded_token1); + ASSERT_EQ(token1, decoded_token0.get_token()); + + ASSERT_TRUE(decoded_token0.has_algorithm()); + ASSERT_TRUE(decoded_token0.has_type()); + ASSERT_FALSE(decoded_token0.has_content_type()); + ASSERT_FALSE(decoded_token0.has_key_id()); + ASSERT_TRUE(decoded_token0.has_issuer()); + ASSERT_FALSE(decoded_token0.has_subject()); + ASSERT_FALSE(decoded_token0.has_audience()); + ASSERT_FALSE(decoded_token0.has_expires_at()); + ASSERT_FALSE(decoded_token0.has_not_before()); + ASSERT_FALSE(decoded_token0.has_issued_at()); + ASSERT_FALSE(decoded_token0.has_id()); + + ASSERT_EQ("HS256", decoded_token0.get_algorithm()); + ASSERT_EQ("JWS", decoded_token0.get_type()); + ASSERT_EQ("auth0", decoded_token0.get_issuer()); +} diff --git a/dep/jwt-cpp/tests/cmake/CMakeLists.txt b/dep/jwt-cpp/tests/cmake/CMakeLists.txt new file mode 100644 index 0000000000..56acedc39a --- /dev/null +++ b/dep/jwt-cpp/tests/cmake/CMakeLists.txt @@ -0,0 +1,9 @@ +cmake_minimum_required(VERSION 3.8) +project(jwt-cpp-installation-tests) + +set(TEST CACHE STRING "The test source file to be used") + +find_package(jwt-cpp 0.6.0 EXACT REQUIRED CONFIG) + +add_executable(test-project ${TEST}.cpp) +target_link_libraries(test-project jwt-cpp::jwt-cpp) diff --git a/dep/jwt-cpp/tests/cmake/base64-is-disabled.cpp b/dep/jwt-cpp/tests/cmake/base64-is-disabled.cpp new file mode 100644 index 0000000000..3f3ac5620f --- /dev/null +++ b/dep/jwt-cpp/tests/cmake/base64-is-disabled.cpp @@ -0,0 +1,10 @@ +#ifndef JWT_DISABLE_BASE64 +#error "This test expects 'JWT_DISABLE_BASE64' to be defined!" +#endif + +#include "jwt-cpp/jwt.h" + +int main() { + jwt::date date; + return 0; +} diff --git a/dep/jwt-cpp/tests/cmake/defaults-enabled.cpp b/dep/jwt-cpp/tests/cmake/defaults-enabled.cpp new file mode 100644 index 0000000000..9699c30ded --- /dev/null +++ b/dep/jwt-cpp/tests/cmake/defaults-enabled.cpp @@ -0,0 +1,6 @@ +#include "jwt-cpp/jwt.h" + +int main() { + jwt::claim claim; + return 0; +} diff --git a/dep/jwt-cpp/tests/cmake/libressl-is-used.cpp b/dep/jwt-cpp/tests/cmake/libressl-is-used.cpp new file mode 100644 index 0000000000..7305a3f7e5 --- /dev/null +++ b/dep/jwt-cpp/tests/cmake/libressl-is-used.cpp @@ -0,0 +1,13 @@ +#if !__has_include() +#error "missing LibreSSL's TLS header!" +#endif + +#include + +#include "jwt-cpp/jwt.h" + +int main() { + tls_init(); + jwt::date date; + return 0; +} diff --git a/dep/jwt-cpp/tests/cmake/picojson-is-disabled.cpp b/dep/jwt-cpp/tests/cmake/picojson-is-disabled.cpp new file mode 100644 index 0000000000..a6baf37c4d --- /dev/null +++ b/dep/jwt-cpp/tests/cmake/picojson-is-disabled.cpp @@ -0,0 +1,10 @@ +#ifndef JWT_DISABLE_PICOJSON +#error "This test expects 'JWT_DISABLE_PICOJSON' to be defined!" +#endif + +#include "jwt-cpp/jwt.h" + +int main() { + jwt::date date; + return 0; +} diff --git a/dep/jwt-cpp/tests/cmake/wolfssl-is-used.cpp b/dep/jwt-cpp/tests/cmake/wolfssl-is-used.cpp new file mode 100644 index 0000000000..ca05316a9e --- /dev/null +++ b/dep/jwt-cpp/tests/cmake/wolfssl-is-used.cpp @@ -0,0 +1,21 @@ +#if !__has_include() +#error "missing wolfSSL's SSL header!" +#endif + +#ifndef OPENSSL_EXTRA +#error "missing wolfSSL's OPENSSL_EXTRA macro!" +#endif + +#ifndef OPENSSL_ALL +#error "missing wolfSSL's OPENSSL_ALL macro!" +#endif + +#include "jwt-cpp/jwt.h" + +#include + +int main() { + wolfSSL_library_init(); + jwt::date date; + return 0; +} diff --git a/dep/jwt-cpp/tests/fuzz/BaseDecodeFuzz.cpp b/dep/jwt-cpp/tests/fuzz/BaseDecodeFuzz.cpp new file mode 100644 index 0000000000..5dc4f003b4 --- /dev/null +++ b/dep/jwt-cpp/tests/fuzz/BaseDecodeFuzz.cpp @@ -0,0 +1,13 @@ +#include + +extern "C" { + +int LLVMFuzzerTestOneInput(const uint8_t* Data, size_t Size) { + try { + const auto bin = jwt::base::decode(std::string{(char*)Data, Size}); + } catch (const std::runtime_error&) { + // parse errors are ok, because input may be random bytes + } + return 0; // Non-zero return values are reserved for future use. +} +} diff --git a/dep/jwt-cpp/tests/fuzz/BaseEncodeFuzz.cpp b/dep/jwt-cpp/tests/fuzz/BaseEncodeFuzz.cpp new file mode 100644 index 0000000000..9ac80252d4 --- /dev/null +++ b/dep/jwt-cpp/tests/fuzz/BaseEncodeFuzz.cpp @@ -0,0 +1,9 @@ +#include + +extern "C" { + +int LLVMFuzzerTestOneInput(const uint8_t* Data, size_t Size) { + jwt::base::encode(std::string{(char*)Data, Size}); + return 0; // Non-zero return values are reserved for future use. +} +} diff --git a/dep/jwt-cpp/tests/fuzz/CMakeLists.txt b/dep/jwt-cpp/tests/fuzz/CMakeLists.txt new file mode 100644 index 0000000000..1860731fe2 --- /dev/null +++ b/dep/jwt-cpp/tests/fuzz/CMakeLists.txt @@ -0,0 +1,20 @@ +if(NOT ${CMAKE_CXX_COMPILER_ID} STREQUAL Clang) + message(FATAL_ERROR "Fuzzing is only available on Clang") +endif() + +function(ADD_FUZZING_EXECUTABLE TARGET) + add_executable(jwt-cpp-fuzz-${TARGET} "${TARGET}.cpp") + target_compile_options( + jwt-cpp-fuzz-${TARGET} + PRIVATE -g -O1 -fsanitize=fuzzer,address,signed-integer-overflow,undefined + -fno-omit-frame-pointer) + target_link_options( + jwt-cpp-fuzz-${TARGET} PRIVATE + -fsanitize=fuzzer,address,signed-integer-overflow,undefined + -fno-omit-frame-pointer) + target_link_libraries(jwt-cpp-fuzz-${TARGET} PRIVATE jwt-cpp::jwt-cpp) +endfunction() + +add_fuzzing_executable(BaseEncodeFuzz) +add_fuzzing_executable(BaseDecodeFuzz) +add_fuzzing_executable(TokenDecodeFuzz) diff --git a/dep/jwt-cpp/tests/fuzz/TokenDecodeFuzz.cpp b/dep/jwt-cpp/tests/fuzz/TokenDecodeFuzz.cpp new file mode 100644 index 0000000000..6a23c2217d --- /dev/null +++ b/dep/jwt-cpp/tests/fuzz/TokenDecodeFuzz.cpp @@ -0,0 +1,27 @@ +#include + +extern "C" { + +int LLVMFuzzerTestOneInput(const uint8_t* Data, size_t Size) { + try { + // step 1: parse input + const auto jwt1 = jwt::decode(std::string{(char*)Data, Size}); + + try { + // step 2: round trip + std::string s1 = jwt1.get_token(); + const auto jwt2 = jwt::decode(s1); + + // tokens must match + if (s1 != jwt2.get_token()) abort(); + } catch (...) { + // parsing raw data twice must not fail + abort(); + } + } catch (...) { + // parse errors are ok, because input may be random bytes + } + + return 0; // Non-zero return values are reserved for future use. +} +} diff --git a/dep/jwt-cpp/tests/fuzz/decode-corpus/086a3aa337038cac8a75a05131444f222e48aee8 b/dep/jwt-cpp/tests/fuzz/decode-corpus/086a3aa337038cac8a75a05131444f222e48aee8 new file mode 100644 index 0000000000..510325478e --- /dev/null +++ b/dep/jwt-cpp/tests/fuzz/decode-corpus/086a3aa337038cac8a75a05131444f222e48aee8 @@ -0,0 +1 @@ +FMF= \ No newline at end of file diff --git a/dep/jwt-cpp/tests/fuzz/decode-corpus/8ebaef2304e91465585c8d7fcf4d9f939e08d6b4 b/dep/jwt-cpp/tests/fuzz/decode-corpus/8ebaef2304e91465585c8d7fcf4d9f939e08d6b4 new file mode 100644 index 0000000000..4ce4f4553e --- /dev/null +++ b/dep/jwt-cpp/tests/fuzz/decode-corpus/8ebaef2304e91465585c8d7fcf4d9f939e08d6b4 @@ -0,0 +1 @@ +eCy \ No newline at end of file diff --git a/dep/jwt-cpp/tests/fuzz/decode-corpus/ba528234d9f6949ed9c9626c08a782f6e7c15b8b b/dep/jwt-cpp/tests/fuzz/decode-corpus/ba528234d9f6949ed9c9626c08a782f6e7c15b8b new file mode 100644 index 0000000000..d2563290e8 --- /dev/null +++ b/dep/jwt-cpp/tests/fuzz/decode-corpus/ba528234d9f6949ed9c9626c08a782f6e7c15b8b @@ -0,0 +1 @@ +FF== \ No newline at end of file diff --git a/dep/jwt-cpp/tests/fuzz/decode-corpus/de1028a3fe87471f027522c3ed9ec02b8364a006 b/dep/jwt-cpp/tests/fuzz/decode-corpus/de1028a3fe87471f027522c3ed9ec02b8364a006 new file mode 100644 index 0000000000..f66ae60aab --- /dev/null +++ b/dep/jwt-cpp/tests/fuzz/decode-corpus/de1028a3fe87471f027522c3ed9ec02b8364a006 @@ -0,0 +1 @@ +eCyIcHzyc2RQHa1EchsP11BhieWRIdm2MToLRpVLKFGNKFvfXIEinoFpLv \ No newline at end of file diff --git a/dep/jwt-cpp/tests/fuzz/decode-corpus/e8f531caaa67cecb1c7b162f3e1d4e320d79befd b/dep/jwt-cpp/tests/fuzz/decode-corpus/e8f531caaa67cecb1c7b162f3e1d4e320d79befd new file mode 100644 index 0000000000..0245b58cfe --- /dev/null +++ b/dep/jwt-cpp/tests/fuzz/decode-corpus/e8f531caaa67cecb1c7b162f3e1d4e320d79befd @@ -0,0 +1 @@ +eCyI \ No newline at end of file diff --git a/dep/jwt-cpp/tests/fuzz/token-corpus/9d891e731f75deae56884d79e9816736b7488080 b/dep/jwt-cpp/tests/fuzz/token-corpus/9d891e731f75deae56884d79e9816736b7488080 new file mode 100644 index 0000000000..a96aa0ea9d --- /dev/null +++ b/dep/jwt-cpp/tests/fuzz/token-corpus/9d891e731f75deae56884d79e9816736b7488080 @@ -0,0 +1 @@ +.. \ No newline at end of file diff --git a/dep/jwt-cpp/tests/fuzz/token-corpus/ff384e2421a333cd52f259cec14c7f790d595db9 b/dep/jwt-cpp/tests/fuzz/token-corpus/ff384e2421a333cd52f259cec14c7f790d595db9 new file mode 100644 index 0000000000..d6b84aac83 --- /dev/null +++ b/dep/jwt-cpp/tests/fuzz/token-corpus/ff384e2421a333cd52f259cec14c7f790d595db9 @@ -0,0 +1 @@ +eyJhbGci.. \ No newline at end of file diff --git a/dep/jwt-cpp/tests/fuzz/token-corpus/valid-sample b/dep/jwt-cpp/tests/fuzz/token-corpus/valid-sample new file mode 100644 index 0000000000..d16e17a9a3 --- /dev/null +++ b/dep/jwt-cpp/tests/fuzz/token-corpus/valid-sample @@ -0,0 +1 @@ +eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXUyJ9.eyJpc3MiOiJhdXRoMCJ9.AbIJTDMFc7yUa5MhvcP03nJPyCPzZtQcGEp-zWfOkEE \ No newline at end of file diff --git a/dep/jwt-cpp/tests/traits/BoostJsonTest.cpp b/dep/jwt-cpp/tests/traits/BoostJsonTest.cpp new file mode 100644 index 0000000000..058ce38590 --- /dev/null +++ b/dep/jwt-cpp/tests/traits/BoostJsonTest.cpp @@ -0,0 +1,128 @@ +#include "jwt-cpp/traits/boost-json/traits.h" + +#include + +TEST(BoostJsonTest, BasicClaims) { + const auto string = jwt::basic_claim(jwt::traits::boost_json::string_type("string")); + ASSERT_EQ(string.get_type(), jwt::json::type::string); + + const auto array = + jwt::basic_claim(std::set{"string", "string"}); + ASSERT_EQ(array.get_type(), jwt::json::type::array); + + jwt::traits::boost_json::value_type jvi = 159816816; + const auto integer = jwt::basic_claim(jvi); + ASSERT_EQ(integer.get_type(), jwt::json::type::integer); +} + +TEST(BoostJsonTest, AudienceAsString) { + jwt::traits::boost_json::string_type token = + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJ0ZXN0In0.WZnM3SIiSRHsbO3O7Z2bmIzTJ4EC32HRBKfLznHhrh4"; + auto decoded = jwt::decode(token); + + ASSERT_TRUE(decoded.has_algorithm()); + ASSERT_TRUE(decoded.has_type()); + ASSERT_FALSE(decoded.has_content_type()); + ASSERT_FALSE(decoded.has_key_id()); + ASSERT_FALSE(decoded.has_issuer()); + ASSERT_FALSE(decoded.has_subject()); + ASSERT_TRUE(decoded.has_audience()); + ASSERT_FALSE(decoded.has_expires_at()); + ASSERT_FALSE(decoded.has_not_before()); + ASSERT_FALSE(decoded.has_issued_at()); + ASSERT_FALSE(decoded.has_id()); + + ASSERT_EQ("HS256", decoded.get_algorithm()); + ASSERT_EQ("JWT", decoded.get_type()); + auto aud = decoded.get_audience(); + ASSERT_EQ(1, aud.size()); + ASSERT_EQ("test", *aud.begin()); +} + +TEST(BoostJsonTest, SetArray) { + std::vector vect = {100, 20, 10}; + auto token = jwt::create() + .set_payload_claim("test", jwt::basic_claim(vect.begin(), vect.end())) + .sign(jwt::algorithm::none{}); + ASSERT_EQ(token, "eyJhbGciOiJub25lIn0.eyJ0ZXN0IjpbMTAwLDIwLDEwXX0."); +} + +TEST(BoostJsonTest, SetObject) { + jwt::traits::boost_json::value_type value; + ASSERT_TRUE(jwt::traits::boost_json::parse(value, "{\"api-x\": [1]}")); + jwt::basic_claim object(value); + ASSERT_EQ(object.get_type(), jwt::json::type::object); + + auto token = jwt::create() + .set_payload_claim("namespace", object) + .sign(jwt::algorithm::hs256("test")); + ASSERT_EQ(token, + "eyJhbGciOiJIUzI1NiJ9.eyJuYW1lc3BhY2UiOnsiYXBpLXgiOlsxXX19.F8I6I2RcSF98bKa0IpIz09fRZtHr1CWnWKx2za-tFQA"); +} + +TEST(BoostJsonTest, VerifyTokenHS256) { + jwt::traits::boost_json::string_type token = + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXUyJ9.eyJpc3MiOiJhdXRoMCJ9.AbIJTDMFc7yUa5MhvcP03nJPyCPzZtQcGEp-zWfOkEE"; + + const auto decoded_token = jwt::decode(token); + const auto verify = + jwt::verify().allow_algorithm(jwt::algorithm::hs256{"secret"}).with_issuer("auth0"); + verify.verify(decoded_token); +} + +TEST(BoostJsonTest, VerifyTokenExpirationValid) { + const auto token = jwt::create() + .set_issuer("auth0") + .set_issued_at(std::chrono::system_clock::now()) + .set_expires_at(std::chrono::system_clock::now() + std::chrono::seconds{3600}) + .sign(jwt::algorithm::hs256{"secret"}); + + const auto decoded_token = jwt::decode(token); + const auto verify = + jwt::verify().allow_algorithm(jwt::algorithm::hs256{"secret"}).with_issuer("auth0"); + verify.verify(decoded_token); +} + +TEST(BoostJsonTest, VerifyTokenExpired) { + const auto token = jwt::create() + .set_issuer("auth0") + .set_issued_at(std::chrono::system_clock::now() - std::chrono::seconds{3601}) + .set_expires_at(std::chrono::system_clock::now() - std::chrono::seconds{1}) + .sign(jwt::algorithm::hs256{"secret"}); + + const auto decoded_token = jwt::decode(token); + const auto verify = + jwt::verify().allow_algorithm(jwt::algorithm::hs256{"secret"}).with_issuer("auth0"); + ASSERT_THROW(verify.verify(decoded_token), jwt::error::token_verification_exception); + + std::error_code ec; + ASSERT_NO_THROW(verify.verify(decoded_token, ec)); + ASSERT_TRUE(!(!ec)); + ASSERT_EQ(ec.category(), jwt::error::token_verification_error_category()); + ASSERT_EQ(ec.value(), static_cast(jwt::error::token_verification_error::token_expired)); +} + +TEST(BoostJsonTest, VerifyArray) { + jwt::traits::boost_json::string_type token = "eyJhbGciOiJub25lIn0.eyJ0ZXN0IjpbMTAwLDIwLDEwXX0."; + const auto decoded_token = jwt::decode(token); + + std::vector vect = {100, 20, 10}; + jwt::basic_claim array_claim(vect.begin(), vect.end()); + const auto verify = + jwt::verify().allow_algorithm(jwt::algorithm::none{}).with_claim("test", array_claim); + ASSERT_NO_THROW(verify.verify(decoded_token)); +} + +TEST(BoostJsonTest, VerifyObject) { + jwt::traits::boost_json::string_type token = + "eyJhbGciOiJIUzI1NiJ9.eyJuYW1lc3BhY2UiOnsiYXBpLXgiOlsxXX19.F8I6I2RcSF98bKa0IpIz09fRZtHr1CWnWKx2za-tFQA"; + const auto decoded_token = jwt::decode(token); + + jwt::traits::boost_json::value_type value; + ASSERT_TRUE(jwt::traits::boost_json::parse(value, "{\"api-x\": [1]}")); + jwt::basic_claim object_claim(value); + const auto verify = jwt::verify() + .allow_algorithm(jwt::algorithm::hs256("test")) + .with_claim("namespace", object_claim); + ASSERT_NO_THROW(verify.verify(decoded_token)); +} diff --git a/dep/jwt-cpp/tests/traits/JsonconsTest.cpp b/dep/jwt-cpp/tests/traits/JsonconsTest.cpp new file mode 100644 index 0000000000..685b1df9ef --- /dev/null +++ b/dep/jwt-cpp/tests/traits/JsonconsTest.cpp @@ -0,0 +1,133 @@ +#include "jwt-cpp/traits/danielaparker-jsoncons/traits.h" + +#include + +TEST(JsonconsTest, BasicClaims) { + const auto string = jwt::basic_claim( + jwt::traits::danielaparker_jsoncons::string_type("string")); + ASSERT_EQ(string.get_type(), jwt::json::type::string); + + const auto array = jwt::basic_claim( + std::set{"string", "string"}); + ASSERT_EQ(array.get_type(), jwt::json::type::array); + + const auto integer = jwt::basic_claim(159816816); + ASSERT_EQ(integer.get_type(), jwt::json::type::integer); +} + +TEST(JsonconsTest, AudienceAsString) { + jwt::traits::danielaparker_jsoncons::string_type token = + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJ0ZXN0In0.WZnM3SIiSRHsbO3O7Z2bmIzTJ4EC32HRBKfLznHhrh4"; + auto decoded = jwt::decode(token); + + ASSERT_TRUE(decoded.has_algorithm()); + ASSERT_TRUE(decoded.has_type()); + ASSERT_FALSE(decoded.has_content_type()); + ASSERT_FALSE(decoded.has_key_id()); + ASSERT_FALSE(decoded.has_issuer()); + ASSERT_FALSE(decoded.has_subject()); + ASSERT_TRUE(decoded.has_audience()); + ASSERT_FALSE(decoded.has_expires_at()); + ASSERT_FALSE(decoded.has_not_before()); + ASSERT_FALSE(decoded.has_issued_at()); + ASSERT_FALSE(decoded.has_id()); + + ASSERT_EQ("HS256", decoded.get_algorithm()); + ASSERT_EQ("JWT", decoded.get_type()); + auto aud = decoded.get_audience(); + ASSERT_EQ(1, aud.size()); + ASSERT_EQ("test", *aud.begin()); +} + +TEST(JsonconsTest, SetArray) { + std::vector vect = {100, 20, 10}; + auto token = + jwt::create() + .set_payload_claim("test", jwt::basic_claim(vect.begin(), vect.end())) + .sign(jwt::algorithm::none{}); + ASSERT_EQ(token, "eyJhbGciOiJub25lIn0.eyJ0ZXN0IjpbMTAwLDIwLDEwXX0."); +} + +TEST(JsonconsTest, SetObject) { + std::istringstream iss{"{\"api-x\": [1]}"}; + jwt::basic_claim object; + iss >> object; + ASSERT_EQ(object.get_type(), jwt::json::type::object); + + auto token = jwt::create() + .set_payload_claim("namespace", object) + .sign(jwt::algorithm::hs256("test")); + ASSERT_EQ(token, + "eyJhbGciOiJIUzI1NiJ9.eyJuYW1lc3BhY2UiOnsiYXBpLXgiOlsxXX19.F8I6I2RcSF98bKa0IpIz09fRZtHr1CWnWKx2za-tFQA"); +} + +TEST(JsonconsTest, VerifyTokenHS256) { + jwt::traits::danielaparker_jsoncons::string_type token = + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXUyJ9.eyJpc3MiOiJhdXRoMCJ9.AbIJTDMFc7yUa5MhvcP03nJPyCPzZtQcGEp-zWfOkEE"; + + const auto decoded_token = jwt::decode(token); + const auto verify = jwt::verify() + .allow_algorithm(jwt::algorithm::hs256{"secret"}) + .with_issuer("auth0"); + verify.verify(decoded_token); +} + +TEST(JsonconsTest, VerifyTokenExpirationValid) { + const auto token = jwt::create() + .set_issuer("auth0") + .set_issued_at(std::chrono::system_clock::now()) + .set_expires_at(std::chrono::system_clock::now() + std::chrono::seconds{3600}) + .sign(jwt::algorithm::hs256{"secret"}); + + const auto decoded_token = jwt::decode(token); + const auto verify = jwt::verify() + .allow_algorithm(jwt::algorithm::hs256{"secret"}) + .with_issuer("auth0"); + verify.verify(decoded_token); +} + +TEST(JsonconsTest, VerifyTokenExpired) { + const auto token = jwt::create() + .set_issuer("auth0") + .set_issued_at(std::chrono::system_clock::now() - std::chrono::seconds{3601}) + .set_expires_at(std::chrono::system_clock::now() - std::chrono::seconds{1}) + .sign(jwt::algorithm::hs256{"secret"}); + + const auto decoded_token = jwt::decode(token); + const auto verify = jwt::verify() + .allow_algorithm(jwt::algorithm::hs256{"secret"}) + .with_issuer("auth0"); + ASSERT_THROW(verify.verify(decoded_token), jwt::error::token_verification_exception); + + std::error_code ec; + ASSERT_NO_THROW(verify.verify(decoded_token, ec)); + ASSERT_TRUE(!(!ec)); + ASSERT_EQ(ec.category(), jwt::error::token_verification_error_category()); + ASSERT_EQ(ec.value(), static_cast(jwt::error::token_verification_error::token_expired)); +} + +TEST(JsonconsTest, VerifyArray) { + jwt::traits::danielaparker_jsoncons::string_type token = "eyJhbGciOiJub25lIn0.eyJ0ZXN0IjpbMTAwLDIwLDEwXX0."; + const auto decoded_token = jwt::decode(token); + + std::vector vect = {100, 20, 10}; + jwt::basic_claim array_claim(vect.begin(), vect.end()); + const auto verify = jwt::verify() + .allow_algorithm(jwt::algorithm::none{}) + .with_claim("test", array_claim); + ASSERT_NO_THROW(verify.verify(decoded_token)); +} + +TEST(JsonconsTest, VerifyObject) { + jwt::traits::danielaparker_jsoncons::string_type token = + "eyJhbGciOiJIUzI1NiJ9.eyJuYW1lc3BhY2UiOnsiYXBpLXgiOlsxXX19.F8I6I2RcSF98bKa0IpIz09fRZtHr1CWnWKx2za-tFQA"; + const auto decoded_token = jwt::decode(token); + + jwt::basic_claim object_claim; + std::istringstream iss{"{\"api-x\": [1]}"}; + iss >> object_claim; + const auto verify = jwt::verify() + .allow_algorithm(jwt::algorithm::hs256("test")) + .with_claim("namespace", object_claim); + ASSERT_NO_THROW(verify.verify(decoded_token)); +} diff --git a/dep/jwt-cpp/tests/traits/NlohmannTest.cpp b/dep/jwt-cpp/tests/traits/NlohmannTest.cpp new file mode 100644 index 0000000000..faa303eeeb --- /dev/null +++ b/dep/jwt-cpp/tests/traits/NlohmannTest.cpp @@ -0,0 +1,128 @@ +#include "jwt-cpp/traits/nlohmann-json/traits.h" + +#include + +TEST(NlohmannTest, BasicClaims) { + const auto string = jwt::basic_claim(jwt::traits::nlohmann_json::string_type("string")); + ASSERT_EQ(string.get_type(), jwt::json::type::string); + + const auto array = jwt::basic_claim( + std::set{"string", "string"}); + ASSERT_EQ(array.get_type(), jwt::json::type::array); + + const auto integer = jwt::basic_claim(159816816); + ASSERT_EQ(integer.get_type(), jwt::json::type::integer); +} + +TEST(NlohmannTest, AudienceAsString) { + jwt::traits::nlohmann_json::string_type token = + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJ0ZXN0In0.WZnM3SIiSRHsbO3O7Z2bmIzTJ4EC32HRBKfLznHhrh4"; + auto decoded = jwt::decode(token); + + ASSERT_TRUE(decoded.has_algorithm()); + ASSERT_TRUE(decoded.has_type()); + ASSERT_FALSE(decoded.has_content_type()); + ASSERT_FALSE(decoded.has_key_id()); + ASSERT_FALSE(decoded.has_issuer()); + ASSERT_FALSE(decoded.has_subject()); + ASSERT_TRUE(decoded.has_audience()); + ASSERT_FALSE(decoded.has_expires_at()); + ASSERT_FALSE(decoded.has_not_before()); + ASSERT_FALSE(decoded.has_issued_at()); + ASSERT_FALSE(decoded.has_id()); + + ASSERT_EQ("HS256", decoded.get_algorithm()); + ASSERT_EQ("JWT", decoded.get_type()); + auto aud = decoded.get_audience(); + ASSERT_EQ(1, aud.size()); + ASSERT_EQ("test", *aud.begin()); +} + +TEST(NlohmannTest, SetArray) { + std::vector vect = {100, 20, 10}; + auto token = jwt::create() + .set_payload_claim("test", jwt::basic_claim(vect.begin(), vect.end())) + .sign(jwt::algorithm::none{}); + ASSERT_EQ(token, "eyJhbGciOiJub25lIn0.eyJ0ZXN0IjpbMTAwLDIwLDEwXX0."); +} + +TEST(NlohmannTest, SetObject) { + std::istringstream iss{"{\"api-x\": [1]}"}; + jwt::basic_claim object; + iss >> object; + ASSERT_EQ(object.get_type(), jwt::json::type::object); + + auto token = jwt::create() + .set_payload_claim("namespace", object) + .sign(jwt::algorithm::hs256("test")); + ASSERT_EQ(token, + "eyJhbGciOiJIUzI1NiJ9.eyJuYW1lc3BhY2UiOnsiYXBpLXgiOlsxXX19.F8I6I2RcSF98bKa0IpIz09fRZtHr1CWnWKx2za-tFQA"); +} + +TEST(NlohmannTest, VerifyTokenHS256) { + jwt::traits::nlohmann_json::string_type token = + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXUyJ9.eyJpc3MiOiJhdXRoMCJ9.AbIJTDMFc7yUa5MhvcP03nJPyCPzZtQcGEp-zWfOkEE"; + + const auto decoded_token = jwt::decode(token); + const auto verify = + jwt::verify().allow_algorithm(jwt::algorithm::hs256{"secret"}).with_issuer("auth0"); + verify.verify(decoded_token); +} + +TEST(NlohmannTest, VerifyTokenExpirationValid) { + const auto token = jwt::create() + .set_issuer("auth0") + .set_issued_at(std::chrono::system_clock::now()) + .set_expires_at(std::chrono::system_clock::now() + std::chrono::seconds{3600}) + .sign(jwt::algorithm::hs256{"secret"}); + + const auto decoded_token = jwt::decode(token); + const auto verify = + jwt::verify().allow_algorithm(jwt::algorithm::hs256{"secret"}).with_issuer("auth0"); + verify.verify(decoded_token); +} + +TEST(NlohmannTest, VerifyTokenExpired) { + const auto token = jwt::create() + .set_issuer("auth0") + .set_issued_at(std::chrono::system_clock::now() - std::chrono::seconds{3601}) + .set_expires_at(std::chrono::system_clock::now() - std::chrono::seconds{1}) + .sign(jwt::algorithm::hs256{"secret"}); + + const auto decoded_token = jwt::decode(token); + const auto verify = + jwt::verify().allow_algorithm(jwt::algorithm::hs256{"secret"}).with_issuer("auth0"); + ASSERT_THROW(verify.verify(decoded_token), jwt::error::token_verification_exception); + + std::error_code ec; + ASSERT_NO_THROW(verify.verify(decoded_token, ec)); + ASSERT_TRUE(!(!ec)); + ASSERT_EQ(ec.category(), jwt::error::token_verification_error_category()); + ASSERT_EQ(ec.value(), static_cast(jwt::error::token_verification_error::token_expired)); +} + +TEST(NlohmannTest, VerifyArray) { + jwt::traits::nlohmann_json::string_type token = "eyJhbGciOiJub25lIn0.eyJ0ZXN0IjpbMTAwLDIwLDEwXX0."; + const auto decoded_token = jwt::decode(token); + + std::vector vect = {100, 20, 10}; + jwt::basic_claim array_claim(vect.begin(), vect.end()); + const auto verify = jwt::verify() + .allow_algorithm(jwt::algorithm::none{}) + .with_claim("test", array_claim); + ASSERT_NO_THROW(verify.verify(decoded_token)); +} + +TEST(NlohmannTest, VerifyObject) { + jwt::traits::nlohmann_json::string_type token = + "eyJhbGciOiJIUzI1NiJ9.eyJuYW1lc3BhY2UiOnsiYXBpLXgiOlsxXX19.F8I6I2RcSF98bKa0IpIz09fRZtHr1CWnWKx2za-tFQA"; + const auto decoded_token = jwt::decode(token); + + jwt::basic_claim object_claim; + std::istringstream iss{"{\"api-x\": [1]}"}; + iss >> object_claim; + const auto verify = jwt::verify() + .allow_algorithm(jwt::algorithm::hs256("test")) + .with_claim("namespace", object_claim); + ASSERT_NO_THROW(verify.verify(decoded_token)); +} diff --git a/dep/jwt-cpp/tests/traits/TraitsTest.cpp.mustache b/dep/jwt-cpp/tests/traits/TraitsTest.cpp.mustache new file mode 100644 index 0000000000..db7d8c417d --- /dev/null +++ b/dep/jwt-cpp/tests/traits/TraitsTest.cpp.mustache @@ -0,0 +1,133 @@ +#include "jwt-cpp/traits/{{traits_dir}}/traits.h" + +#include + +TEST({{test_suite_name}}, BasicClaims) { + const auto string = jwt::basic_claim( + jwt::traits::{{traits_name}}::string_type("string")); + ASSERT_EQ(string.get_type(), jwt::json::type::string); + + const auto array = jwt::basic_claim( + std::set{"string", "string"}); + ASSERT_EQ(array.get_type(), jwt::json::type::array); + + const auto integer = jwt::basic_claim(159816816); + ASSERT_EQ(integer.get_type(), jwt::json::type::integer); +} + +TEST({{test_suite_name}}, AudienceAsString) { + jwt::traits::{{traits_name}}::string_type token = + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJ0ZXN0In0.WZnM3SIiSRHsbO3O7Z2bmIzTJ4EC32HRBKfLznHhrh4"; + auto decoded = jwt::decode(token); + + ASSERT_TRUE(decoded.has_algorithm()); + ASSERT_TRUE(decoded.has_type()); + ASSERT_FALSE(decoded.has_content_type()); + ASSERT_FALSE(decoded.has_key_id()); + ASSERT_FALSE(decoded.has_issuer()); + ASSERT_FALSE(decoded.has_subject()); + ASSERT_TRUE(decoded.has_audience()); + ASSERT_FALSE(decoded.has_expires_at()); + ASSERT_FALSE(decoded.has_not_before()); + ASSERT_FALSE(decoded.has_issued_at()); + ASSERT_FALSE(decoded.has_id()); + + ASSERT_EQ("HS256", decoded.get_algorithm()); + ASSERT_EQ("JWT", decoded.get_type()); + auto aud = decoded.get_audience(); + ASSERT_EQ(1, aud.size()); + ASSERT_EQ("test", *aud.begin()); +} + +TEST({{test_suite_name}}, SetArray) { + std::vector vect = {100, 20, 10}; + auto token = + jwt::create() + .set_payload_claim("test", jwt::basic_claim(vect.begin(), vect.end())) + .sign(jwt::algorithm::none{}); + ASSERT_EQ(token, "eyJhbGciOiJub25lIn0.eyJ0ZXN0IjpbMTAwLDIwLDEwXX0."); +} + +TEST({{test_suite_name}}, SetObject) { + std::istringstream iss{"{\"api-x\": [1]}"}; + jwt::basic_claim object; + iss >> object; + ASSERT_EQ(object.get_type(), jwt::json::type::object); + + auto token = jwt::create() + .set_payload_claim("namespace", object) + .sign(jwt::algorithm::hs256("test")); + ASSERT_EQ(token, + "eyJhbGciOiJIUzI1NiJ9.eyJuYW1lc3BhY2UiOnsiYXBpLXgiOlsxXX19.F8I6I2RcSF98bKa0IpIz09fRZtHr1CWnWKx2za-tFQA"); +} + +TEST({{test_suite_name}}, VerifyTokenHS256) { + jwt::traits::{{traits_name}}::string_type token = + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXUyJ9.eyJpc3MiOiJhdXRoMCJ9.AbIJTDMFc7yUa5MhvcP03nJPyCPzZtQcGEp-zWfOkEE"; + + const auto decoded_token = jwt::decode(token); + const auto verify = jwt::verify() + .allow_algorithm(jwt::algorithm::hs256{"secret"}) + .with_issuer("auth0"); + verify.verify(decoded_token); +} + +TEST({{test_suite_name}}, VerifyTokenExpirationValid) { + const auto token = jwt::create() + .set_issuer("auth0") + .set_issued_at(std::chrono::system_clock::now()) + .set_expires_at(std::chrono::system_clock::now() + std::chrono::seconds{3600}) + .sign(jwt::algorithm::hs256{"secret"}); + + const auto decoded_token = jwt::decode(token); + const auto verify = jwt::verify() + .allow_algorithm(jwt::algorithm::hs256{"secret"}) + .with_issuer("auth0"); + verify.verify(decoded_token); +} + +TEST({{test_suite_name}}, VerifyTokenExpired) { + const auto token = jwt::create() + .set_issuer("auth0") + .set_issued_at(std::chrono::system_clock::now() - std::chrono::seconds{3601}) + .set_expires_at(std::chrono::system_clock::now() - std::chrono::seconds{1}) + .sign(jwt::algorithm::hs256{"secret"}); + + const auto decoded_token = jwt::decode(token); + const auto verify = jwt::verify() + .allow_algorithm(jwt::algorithm::hs256{"secret"}) + .with_issuer("auth0"); + ASSERT_THROW(verify.verify(decoded_token), jwt::error::token_verification_exception); + + std::error_code ec; + ASSERT_NO_THROW(verify.verify(decoded_token, ec)); + ASSERT_TRUE(!(!ec)); + ASSERT_EQ(ec.category(), jwt::error::token_verification_error_category()); + ASSERT_EQ(ec.value(), static_cast(jwt::error::token_verification_error::token_expired)); +} + +TEST({{test_suite_name}}, VerifyArray) { + jwt::traits::{{traits_name}}::string_type token = "eyJhbGciOiJub25lIn0.eyJ0ZXN0IjpbMTAwLDIwLDEwXX0."; + const auto decoded_token = jwt::decode(token); + + std::vector vect = {100, 20, 10}; + jwt::basic_claim array_claim(vect.begin(), vect.end()); + const auto verify = jwt::verify() + .allow_algorithm(jwt::algorithm::none{}) + .with_claim("test", array_claim); + ASSERT_NO_THROW(verify.verify(decoded_token)); +} + +TEST({{test_suite_name}}, VerifyObject) { + jwt::traits::{{traits_name}}::string_type token = + "eyJhbGciOiJIUzI1NiJ9.eyJuYW1lc3BhY2UiOnsiYXBpLXgiOlsxXX19.F8I6I2RcSF98bKa0IpIz09fRZtHr1CWnWKx2za-tFQA"; + const auto decoded_token = jwt::decode(token); + + jwt::basic_claim object_claim; + std::istringstream iss{"{\"api-x\": [1]}"}; + iss >> object_claim; + const auto verify = jwt::verify() + .allow_algorithm(jwt::algorithm::hs256("test")) + .with_claim("namespace", object_claim); + ASSERT_NO_THROW(verify.verify(decoded_token)); +} diff --git a/scripts/zmfilter.pl.in b/scripts/zmfilter.pl.in index 6c507dc3ec..db551ddde6 100644 --- a/scripts/zmfilter.pl.in +++ b/scripts/zmfilter.pl.in @@ -894,7 +894,7 @@ sub sendSummaryEmail { return 1; } return 0; -} +} # end sub sendSummaryEmail sub sendEmail { my $filter = shift; @@ -919,6 +919,8 @@ sub sendEmail { if (sendTheEmail($filter, $subject, $body, @attachments)) { $event->save({Emailed=>1}); return 1; + } else { + Warning("Failed to send the email"); } return 0; } @@ -929,7 +931,7 @@ sub sendTheEmail { eval { if ($Config{ZM_NEW_MAIL_MODULES}) { my $total_size = 0; -### Create the multipart container + # Create the multipart container my $mail = MIME::Lite->new( From => $Config{ZM_FROM_EMAIL}, To => $$filter{EmailTo}, @@ -947,7 +949,6 @@ sub sendTheEmail { ); $related->attach($alternative); - # Create a text-only version, it has to go first require HTML::FormatText; $alternative->attach( diff --git a/src/zm_crypt.cpp b/src/zm_crypt.cpp index 5c5baf0f8b..77075e9f31 100644 --- a/src/zm_crypt.cpp +++ b/src/zm_crypt.cpp @@ -110,7 +110,7 @@ std::pair verifyToken(const std::string &jwt_token_s } if (decoded.has_payload_claim("iat")) { - token_issued_at = (unsigned int) (decoded.get_payload_claim("iat").as_int()); + token_issued_at = (unsigned int) (decoded.get_payload_claim("iat").as_integer()); Debug(1, "Got IAT token=%u", token_issued_at); } else { Error("IAT not found in claim. This should not happen"); diff --git a/src/zm_db.cpp b/src/zm_db.cpp index 632e549bf6..2ebae05027 100644 --- a/src/zm_db.cpp +++ b/src/zm_db.cpp @@ -42,10 +42,6 @@ bool zmDbConnect() { return false; } - bool reconnect = 1; - if ( mysql_options(&dbconn, MYSQL_OPT_RECONNECT, &reconnect) ) - Error("Can't set database auto reconnect option: %s", mysql_error(&dbconn)); - if ( !staticConfig.DB_SSL_CA_CERT.empty() ) { mysql_ssl_set(&dbconn, staticConfig.DB_SSL_CLIENT_KEY.c_str(), diff --git a/src/zm_ffmpeg_camera.cpp b/src/zm_ffmpeg_camera.cpp index e62a31530c..8fd3d28f87 100644 --- a/src/zm_ffmpeg_camera.cpp +++ b/src/zm_ffmpeg_camera.cpp @@ -124,6 +124,7 @@ FfmpegCamera::FfmpegCamera( mOptions(p_options), hwaccel_name(p_hwaccel_name), hwaccel_device(p_hwaccel_device), + mSecondInput(nullptr), frameCount(0), use_hwaccel(true), mCanCapture(false), @@ -524,19 +525,7 @@ int FfmpegCamera::OpenFfmpeg() { Debug(1, "Thread count? %d", mVideoCodecContext->thread_count); zm_dump_codec(mVideoCodecContext); - if (mAudioStreamId == -1 and !monitor->GetSecondPath().empty()) { - Debug(1, "Trying secondary stream at %s", monitor->GetSecondPath().c_str()); - FFmpeg_Input *second_input = new FFmpeg_Input(); - if (second_input->Open(monitor->GetSecondPath().c_str()) > 0) { - mSecondFormatContext = second_input->get_format_context(); - mAudioStreamId = second_input->get_audio_stream_id(); - mAudioStream = second_input->get_audio_stream(); - } else { - Warning("Failed to open secondary input"); - } - } // end if have audio stream - - if ( mAudioStreamId >= 0 ) { + if (mAudioStreamId >= 0) { const AVCodec *mAudioCodec = nullptr; if (!(mAudioCodec = avcodec_find_decoder(mAudioStream->codecpar->codec_id))) { Debug(1, "Can't find codec for audio stream from %s", mMaskedPath.c_str()); @@ -551,7 +540,18 @@ int FfmpegCamera::OpenFfmpeg() { return -1; } // end if opened } // end if found decoder - } // end if mAudioStreamId + } else if (!monitor->GetSecondPath().empty()) { + Debug(1, "Trying secondary stream at %s", monitor->GetSecondPath().c_str()); + mSecondInput = zm::make_unique(); + if (mSecondInput->Open(monitor->GetSecondPath().c_str()) > 0) { + mSecondFormatContext = mSecondInput->get_format_context(); + mAudioStreamId = mSecondInput->get_audio_stream_id(); + mAudioStream = mSecondInput->get_audio_stream(); + mAudioCodecContext = mSecondInput->get_audio_codec_context(); + } else { + Warning("Failed to open secondary input"); + } + } // end if have audio stream if ( ((unsigned int)mVideoCodecContext->width != width) @@ -575,11 +575,6 @@ int FfmpegCamera::Close() { avcodec_free_context(&mVideoCodecContext); mVideoCodecContext = nullptr; // Freed by av_close_input_file } - if ( mAudioCodecContext ) { - avcodec_close(mAudioCodecContext); - avcodec_free_context(&mAudioCodecContext); - mAudioCodecContext = nullptr; // Freed by av_close_input_file - } #if HAVE_LIBAVUTIL_HWCONTEXT_H if ( hw_device_ctx ) { diff --git a/src/zm_ffmpeg_camera.h b/src/zm_ffmpeg_camera.h index 517bf8e058..3fba7f46c4 100644 --- a/src/zm_ffmpeg_camera.h +++ b/src/zm_ffmpeg_camera.h @@ -24,6 +24,8 @@ #include +class FFmpeg_Input; + #if HAVE_LIBAVUTIL_HWCONTEXT_H typedef struct DecodeContext { AVBufferRef *hw_device_ref; @@ -48,7 +50,9 @@ class FfmpegCamera : public Camera { std::string hwaccel_name; std::string hwaccel_device; - int frameCount; + std::unique_ptr mSecondInput; + + int frameCount; _AVPIXELFORMAT imagePixFormat; diff --git a/src/zm_ffmpeg_input.h b/src/zm_ffmpeg_input.h index d2143ee274..238c7f8501 100644 --- a/src/zm_ffmpeg_input.h +++ b/src/zm_ffmpeg_input.h @@ -38,6 +38,8 @@ class FFmpeg_Input { return ( audio_stream_id >= 0 ) ? input_format_context->streams[audio_stream_id] : nullptr; } AVFormatContext *get_format_context() { return input_format_context; }; + AVCodecContext *get_video_codec_context() { return ( video_stream_id >= 0 ) ? streams[video_stream_id].context : nullptr; }; + AVCodecContext *get_audio_codec_context() { return ( audio_stream_id >= 0 ) ? streams[audio_stream_id].context : nullptr; }; private: typedef struct { diff --git a/src/zm_packetqueue.cpp b/src/zm_packetqueue.cpp index d438c8c411..48dd32ebf3 100644 --- a/src/zm_packetqueue.cpp +++ b/src/zm_packetqueue.cpp @@ -105,7 +105,9 @@ bool PacketQueue::queuePacket(std::shared_ptr add_packet) { } rit++; } // end while - } else if (!max_keyframe_interval_ and add_packet->keyframe) { + } + + if (!max_keyframe_interval_ and add_packet->keyframe) { auto rit = pktQueue.rbegin(); int packet_count = 0; while (rit != pktQueue.rend()) { @@ -113,6 +115,7 @@ bool PacketQueue::queuePacket(std::shared_ptr add_packet) { if ((*rit)->keyframe) break; packet_count ++; } + ++rit; } Debug(1, "Have keyframe interval: %d", packet_count); max_keyframe_interval_ = packet_count; diff --git a/src/zm_signal.cpp b/src/zm_signal.cpp index 4211a7038e..ba2fedc821 100644 --- a/src/zm_signal.cpp +++ b/src/zm_signal.cpp @@ -62,8 +62,10 @@ RETSIGTYPE zm_die_handler(int signal) ucontext_t *uc = (ucontext_t *) context; cr2 = info->si_addr; #if defined(__x86_64__) - #if defined(__FreeBSD_kernel__) || defined(__FreeBSD__) + #if defined(__FreeBSD_kernel__) || defined(__FreeBSD__) ip = (void *)(uc->uc_mcontext.mc_rip); + #elif defined(__OpenBSD__) + ip = (void *)(uc->sc_rip); #else ip = (void *)(uc->uc_mcontext.gregs[REG_RIP]); #endif diff --git a/web/ajax/events.php b/web/ajax/events.php index 853640627b..0f636a8231 100644 --- a/web/ajax/events.php +++ b/web/ajax/events.php @@ -208,6 +208,7 @@ function queryRequest($filter, $search, $advsearch, $sort, $offset, $order, $lim // Error($filter->sql()); $where = $filter->sql()?' WHERE ('.$filter->sql().')' : ''; + $col_str = ' E.*, UNIX_TIMESTAMP(E.StartDateTime) @@ -245,7 +246,8 @@ function queryRequest($filter, $search, $advsearch, $sort, $offset, $order, $lim GROUP BY E.Id '.($sort?' ORDER BY '.$sort.' '.$order:''); - if ($filter->limit() and !count($filter->post_sql_conditions())) { + if ((int)$filter->limit() and !count($filter->post_sql_conditions())) { + $sql .= ' LIMIT '.$filter->limit(); } diff --git a/web/ajax/status.php b/web/ajax/status.php index 3b31974635..5a1c91991f 100644 --- a/web/ajax/status.php +++ b/web/ajax/status.php @@ -423,14 +423,14 @@ function getFrameImage() { $sql = 'SELECT * FROM Frames WHERE EventId = ? AND FrameId = ?'; if ( !($frame = dbFetchOne($sql, NULL, array($eventId, $frameId))) ) { - ZM\Error("Frame not found for event $eventId frame $frameId"); + ZM\Debug("Frame not found for event $eventId frame $frameId"); $frame = array(); $frame['EventId'] = $eventId; $frame['FrameId'] = $frameId; $frame['Type'] = 'Virtual'; } - $event = dbFetchOne('SELECT * FROM Events WHERE Id = ?', NULL, array($frame['EventId'])); - $frame['Image'] = getImageSrc($event, $frame, SCALE_BASE); + $event = new ZM\Event($frame['EventId']); + $frame['Image'] = $event->getImageSrc($frame, SCALE_BASE); return $frame; } diff --git a/web/ajax/watch.php b/web/ajax/watch.php index 8c5b0667f2..ee72f802c4 100644 --- a/web/ajax/watch.php +++ b/web/ajax/watch.php @@ -17,7 +17,7 @@ if ( empty($_REQUEST['mid']) ) { $message = 'Must specify a monitor Id'; } else { - $mid = $_REQUEST['mid']; + $mid = validCardinal($_REQUEST['mid']); } // Limit specifies the number of rows to return @@ -27,7 +27,7 @@ $limit = $_REQUEST['limit']; } -if ( $message ) { +if ($message) { ZM\Error($message); ajaxError($message); return; @@ -35,7 +35,7 @@ // Sort specifies the name of the column to sort on $sort = 'Id'; -if ( isset($_REQUEST['sort']) ) { +if (isset($_REQUEST['sort'])) { $sort = $_REQUEST['sort']; } @@ -46,6 +46,23 @@ // MAIN LOOP // +// The names of the dB columns in the events table we are interested in +$columns = array('Id', 'MonitorId', 'StorageId', 'Name', 'Cause', 'StartDateTime', 'EndDateTime', 'Length', 'Frames', 'AlarmFrames', 'TotScore', 'AvgScore', 'MaxScore', 'Archived', 'Emailed', 'Notes', 'DiskSpace'); + +if ( $sort != 'Id' ) { + if (!in_array($sort, $columns)) { + ZM\Error('Invalid sort field: ' . $sort); + $sort = 'Id'; + } else if ($sort == 'EndDateTime') { + if ($order == 'ASC') { + $sort = 'E.EndDateTime IS NULL, E.EndDateTime'; + } else { + $sort = 'E.EndDateTime IS NOT NULL, E.EndDateTime'; + } + } else { + $sort = 'E.'.$sort; + } +} $where = 'WHERE MonitorId = '.$mid; $col_str = ' @@ -72,25 +89,27 @@ ZM\Debug('Calling the following sql query: ' .$sql); $rows = dbQuery($sql, array($limit)); - $returned_rows = array(); -foreach ( $rows as $row ) { - $event = new ZM\Event($row['Id']); - - $scale = intval(5*100*ZM_WEB_LIST_THUMB_WIDTH / $event->Width()); - $imgSrc = $event->getThumbnailSrc(array(), '&'); - $streamSrc = $event->getStreamSrc(array( - 'mode'=>'jpeg', 'scale'=>$scale, 'maxfps'=>ZM_WEB_VIDEO_MAXFPS, 'replay'=>'single', 'rate'=>'400'), '&'); - - // Modify the row data as needed - $row['imgHtml'] = 'Event '.$event->Id().''; - $row['Name'] = validHtmlStr($row['Name']); - $row['StartDateTime'] = $dateTimeFormatter->format(strtotime($row['StartDateTime'])); + +if ($rows) { + foreach ( $rows as $row ) { + $event = new ZM\Event($row['Id']); + + $scale = intval(5*100*ZM_WEB_LIST_THUMB_WIDTH / $event->Width()); + $imgSrc = $event->getThumbnailSrc(array(), '&'); + $streamSrc = $event->getStreamSrc(array( + 'mode'=>'jpeg', 'scale'=>$scale, 'maxfps'=>ZM_WEB_VIDEO_MAXFPS, 'replay'=>'single', 'rate'=>'400'), '&'); + + // Modify the row data as needed + $row['imgHtml'] = 'Event '.$event->Id().''; + $row['Name'] = validHtmlStr($row['Name']); + $row['StartDateTime'] = $dateTimeFormatter->format(strtotime($row['StartDateTime'])); $row['EndDateTime'] = $row['EndDateTime'] ? $dateTimeFormatter->format(strtotime($row['EndDateTime'])) : null; - $row['Length'] = gmdate('H:i:s', intval($row['Length'])); + $row['Length'] = gmdate('H:i:s', intval($row['Length'])); - $returned_rows[] = $row; -} # end foreach row matching search + $returned_rows[] = $row; + } # end foreach row matching search +} $data['rows'] = $returned_rows; ajaxResponse($data); diff --git a/web/includes/Event.php b/web/includes/Event.php index 55792d6ddb..67a83ba224 100644 --- a/web/includes/Event.php +++ b/web/includes/Event.php @@ -416,9 +416,15 @@ function getImageSrc($frame, $scale=SCALE_BASE, $captureOnly=false, $overwrite=f $eventPath = $Event->Path(); if ( $frame and !is_array($frame) ) { - # Must be an Id Debug("Assuming that $frame is an Id"); - $frame = array('FrameId'=>$frame, 'Type'=>'', 'Delta'=>0); + $f = Frame::find_one(['Id'=>$frame]); + if ($f) { + $frame = (array)$f; + } else { + $frame = $this->find_virtual_frame($frame); + if (!$frame) + $frame = array('FrameId'=>$frame, 'Type'=>'', 'Delta'=>0); + } } if ( ( !$frame ) and file_exists($eventPath.'/snapshot.jpg') ) { @@ -703,6 +709,36 @@ function createVideo($format, $rate, $scale, $transform, $overwrite=false) { return $status ? '' : rtrim($result); } + public function find_virtual_frame($frameid) { + $frame = null; + + $previousBulkFrame = dbFetchOne( + 'SELECT * FROM Frames WHERE EventId=? AND FrameId < ? ORDER BY FrameID DESC LIMIT 1', + NULL, array($this->Id(), $fid) + ); + $nextBulkFrame = dbFetchOne( + 'SELECT * FROM Frames WHERE EventId=? AND FrameId > ? ORDER BY FrameID ASC LIMIT 1', + NULL, array($this->Id(), $fid) + ); + if ($previousBulkFrame and $nextBulkFrame) { + $frame = new Frame($previousBulkFrame); + $frame->FrameId($fid); + + $percentage = ($frame->FrameId() - $previousBulkFrame['FrameId']) / ($nextBulkFrame['FrameId'] - $previousBulkFrame['FrameId']); + $frame->Delta($previousBulkFrame['Delta'] + floor( 100* ( $nextBulkFrame['Delta'] - $previousBulkFrame['Delta'] ) * $percentage )/100); + Debug('Got virtual frame from Bulk Frames previous delta: ' . $previousBulkFrame['Delta'] . ' + nextdelta:' . $nextBulkFrame['Delta'] . ' - ' . $previousBulkFrame['Delta'] . ' * ' . $percentage ); + } else if ($previousBulkFrame) { + //If no next Frame we have to pull data from the Event itself + $frame = new Frame($previousBulkFrame); + $frame->FrameId($_REQUEST['fid']); + + $percentage = ($frame->FrameId()/$this->Frames()); + + $frame->Delta(floor($this->Length() * $percentage)); + } + return $frame; + } + } # end class ?> diff --git a/web/includes/Filter.php b/web/includes/Filter.php index e549193829..136cc4c065 100644 --- a/web/includes/Filter.php +++ b/web/includes/Filter.php @@ -12,6 +12,11 @@ class Filter extends ZM_Object { protected static $archiveTypes = null; protected static $booleanValues = null; + protected $Query; + protected $Terms; + protected $Query_json; + + protected $defaults = array( 'Id' => null, 'Name' => '', diff --git a/web/includes/FilterTerm.php b/web/includes/FilterTerm.php index 6fee3bbb0a..fae5a0070e 100644 --- a/web/includes/FilterTerm.php +++ b/web/includes/FilterTerm.php @@ -25,6 +25,7 @@ class FilterTerm { public $cookie; public $placeholder; public $collate; + public $tablename; public function __construct($filter = null, $term = null, $index=0) { $this->cnj = ''; @@ -35,7 +36,7 @@ public function __construct($filter = null, $term = null, $index=0) { if ($term) { $this->attr = isset($term['attr']) ? $term['attr'] : ''; $this->attr = preg_replace('/[^A-Za-z0-9\.]/', '', $this->attr, -1, $count); - if ($count) Error("Invalid characters removed from filter attr ${term['attr']}, possible hacking attempt."); + if ($count) Error("Invalid characters removed from filter attr {$term['attr']}, possible hacking attempt."); $this->op = isset($term['op']) ? $term['op'] : '='; $this->val = isset($term['val']) ? $term['val'] : ''; if (is_array($this->val)) $this->val = implode(',', $this->val); diff --git a/web/includes/Monitor.php b/web/includes/Monitor.php index 3bc50a5c6b..73bae535b2 100644 --- a/web/includes/Monitor.php +++ b/web/includes/Monitor.php @@ -276,6 +276,8 @@ public static function getStatuses() { 'ArchivedEventDiskSpace' => array('type'=>'integer', 'default'=>null, 'do_not_update'=>1), ); + protected $Id; + public function save($data = null) { if ($data) $this->set($data); if ($this->Manufacturer() and $this->Manufacturer()->Name() and ! $this->Manufacturer->Id()) { @@ -412,17 +414,19 @@ public function __call($fn, array $args) { } return $this->defaults[$fn]; } else if (array_key_exists($fn, $this->status_fields)) { - $sql = 'SELECT * FROM `Monitor_Status` WHERE `MonitorId`=?'; - $row = dbFetchOne($sql, NULL, array($this->{'Id'})); - if (!$row) { - Warning('Unable to load Monitor status record for Id='.$this->{'Id'}.' using '.$sql); - return null; - } else { - foreach ($row as $k => $v) { - $this->{$k} = $v; + if ($this->Id()) { + $sql = 'SELECT * FROM `Monitor_Status` WHERE `MonitorId`=?'; + $row = dbFetchOne($sql, NULL, array($this->{'Id'})); + if (!$row) { + Warning('Unable to load Monitor status record for Id='.$this->{'Id'}.' using '.$sql); + } else { + foreach ($row as $k => $v) { + $this->{$k} = $v; + } + return $this->{$fn}; } - } - return $this->{$fn}; + } # end if this->Id + return null; } else if (array_key_exists($fn, $this->summary_fields)) { $sql = 'SELECT * FROM `Event_Summaries` WHERE `MonitorId`=?'; $row = dbFetchOne($sql, NULL, array($this->{'Id'})); diff --git a/web/includes/Object.php b/web/includes/Object.php index a084d3c850..0541345bc0 100644 --- a/web/includes/Object.php +++ b/web/includes/Object.php @@ -31,7 +31,7 @@ public function __construct($IdOrRow = NULL) { return; } foreach ($row as $k => $v) { - $this->{$k} = $v; + $this->$k = $v; } global $object_cache; if (!isset($object_cache[$class])) { diff --git a/web/includes/Storage.php b/web/includes/Storage.php index 5389acdfb5..b8005a6d58 100644 --- a/web/includes/Storage.php +++ b/web/includes/Storage.php @@ -6,6 +6,18 @@ class Storage extends ZM_Object { protected static $table = 'Storage'; + + protected $Id; + protected $Path; + protected $Name; + protected $Type; + protected $Url; + protected $DiskSpace; + protected $Scheme; + protected $ServerId; + protected $DoDelete; + protected $Enabled; + protected $defaults = array( 'Id' => null, 'Path' => array('type'=>'text','filter_regexp'=>array('/[^\w\-\.\(\)\:\/ ]/','/\/$/'), 'default'=>''), diff --git a/web/includes/User.php b/web/includes/User.php index 1d5a00a108..7ed04590df 100644 --- a/web/includes/User.php +++ b/web/includes/User.php @@ -9,6 +9,29 @@ class User extends ZM_Object { protected static $table = 'Users'; + protected $Id; + protected $Username; + protected $Name; + protected $Email; + protected $Phone; + protected $Password; + protected $Language; + protected $Enabled; + protected $Stream; + protected $Events; + protected $Experiments; + protected $Control; + protected $Monitors; + protected $Groups; + protected $Devices; + protected $Snapshots; + protected $System; + protected $MaxBandwidth; + protected $TokenMinExpiry; + protected $APIEnabled; + protected $HomeView; + + protected $defaults = array( 'Id' => null, 'Username' => array('type'=>'text','filter_regexp'=>'/[^\w\.@ ]/', 'default'=>''), diff --git a/web/includes/auth.php b/web/includes/auth.php index d8df621c1e..b4ce988acf 100644 --- a/web/includes/auth.php +++ b/web/includes/auth.php @@ -302,7 +302,7 @@ function visibleMonitor($mid) { function canView($area, $mid=false) { global $user; - return ( $user && ($user->{$area} == 'View' || $user->{$area} == 'Edit') && ( !$mid || visibleMonitor($mid) ) ); + return ( $user && $user->$area() && ($user->$area() == 'View' || $user->$area() == 'Edit') && ( !$mid || visibleMonitor($mid) ) ); } function editableMonitor($mid) { @@ -337,7 +337,7 @@ function editableMonitor($mid) { function canEdit($area, $mid=false) { global $user; - return ( $user && ($user->{$area} == 'Edit') && ( !$mid || visibleMonitor($mid) )); + return ( $user && ($user->$area() == 'Edit') && ( !$mid || visibleMonitor($mid) )); } function userFromSession() { diff --git a/web/includes/functions.php b/web/includes/functions.php index 54cd7b5639..aeb5f79336 100644 --- a/web/includes/functions.php +++ b/web/includes/functions.php @@ -1872,6 +1872,9 @@ function generateConnKey() { } function detaintPath($path) { + + // Strip out :// because php:// is a way to inject code apparently + $path = str_replace('://', '', $path); // Remove any absolute paths, or relative ones that want to go up do { $path = str_replace('../', '', $path, $count); @@ -2290,37 +2293,57 @@ function i18n() { function get_networks() { $interfaces = array(); - exec('ip link', $output, $status); - if ( $status ) { - $html_output = implode('
', $output); - ZM\Error("Unable to list network interfaces, status is '$status'. Output was:

$html_output"); - } else { - foreach ( $output as $line ) { - if ( preg_match('/^\d+: ([[:alnum:]]+):/', $line, $matches ) ) { - if ( $matches[1] != 'lo' ) { - $interfaces[$matches[1]] = $matches[1]; - } else { - ZM\Debug("No match for $line"); - } - } - } - } - $routes = array(); - exec('ip route', $output, $status); - if ( $status ) { - $html_output = implode('
', $output); - ZM\Error("Unable to list network interfaces, status is '$status'. Output was:

$html_output"); - } else { - foreach ( $output as $line ) { - if ( preg_match('/^default via [.[:digit:]]+ dev ([[:alnum:]]+)/', $line, $matches) ) { - $interfaces['default'] = $matches[1]; - } else if ( preg_match('/^([.[:digit:]]+\/[[:digit:]]+) dev ([[:alnum:]]+)/', $line, $matches) ) { - $interfaces[$matches[2]] .= ' ' . $matches[1]; - ZM\Debug("Matched $line: $matches[2] .= $matches[1]"); - } else { - ZM\Debug("Didn't match $line"); - } - } # end foreach line of output + if (file_exists('/usr/sbin/ip')) { + exec('ip link', $output, $status); + if ( $status ) { + $html_output = implode('
', $output); + ZM\Error("Unable to list network interfaces, status is '$status'. Output was:

$html_output"); + } else { + foreach ( $output as $line ) { + if ( preg_match('/^\d+: ([[:alnum:]]+):/', $line, $matches ) ) { + if ( $matches[1] != 'lo' ) { + $interfaces[$matches[1]] = $matches[1]; + } else { + ZM\Debug("No match for $line"); + } + } + } + } + $routes = array(); + exec('ip route', $output, $status); + if ( $status ) { + $html_output = implode('
', $output); + ZM\Error("Unable to list network interfaces, status is '$status'. Output was:

$html_output"); + } else { + foreach ( $output as $line ) { + if ( preg_match('/^default via [.[:digit:]]+ dev ([[:alnum:]]+)/', $line, $matches) ) { + $interfaces['default'] = $matches[1]; + } else if ( preg_match('/^([.[:digit:]]+\/[[:digit:]]+) dev ([[:alnum:]]+)/', $line, $matches) ) { + $interfaces[$matches[2]] .= ' ' . $matches[1]; + ZM\Debug("Matched $line: $matches[2] .= $matches[1]"); + } else { + ZM\Debug("Didn't match $line"); + } + } # end foreach line of output + } + } else if (file_exists('/sbin/ifconfig')) { + ZM\Debug("Executing ifconfig"); + exec('ifconfig', $output, $status); + if ( $status ) { + $html_output = implode("\n", $output); + ZM\Error("Unable to list network interfaces, status is '$status'. Output was:$html_output"); + } else { + preg_match("/^([eth|enp][A-z0-9]*)\s+Link\s+encap:([A-z]*)\s+HWaddr\s+([A-z0-9:]*).*". + "inet addr:([0-9.]+).*Bcast:([0-9.]+).*Mask:([0-9.]+).*". + "MTU:([0-9.]+).*Metric:([0-9.]+).*". + "RX packets:([0-9.]+).*errors:([0-9.]+).*dropped:([0-9.]+).*overruns:([0-9.]+).*frame:([0-9.]+).*". + "TX packets:([0-9.]+).*errors:([0-9.]+).*dropped:([0-9.]+).*overruns:([0-9.]+).*carrier:([0-9.]+).*". + "RX bytes:([0-9.]+).*\((.*)\).*TX bytes:([0-9.]+).*\((.*)\)". + "/ims", implode("\n", $output), $regex); + + ZM\Debug(print_r( $regex,true)); + } + } return $interfaces; } diff --git a/web/includes/logger.php b/web/includes/logger.php index c823878958..20b4a232ad 100644 --- a/web/includes/logger.php +++ b/web/includes/logger.php @@ -35,6 +35,9 @@ class Logger { private $logFile = ''; private $logFd = NULL; + private $savedLogErrors = null; + private $savedDisplayErrors = null; + public static $codes = array( self::DEBUG => 'DBG', self::INFO => 'INF', @@ -238,7 +241,7 @@ public function level( $level ) { if ( !$this->hasTerm ) { if ( $lastLevel < self::DEBUG && $this->level >= self::DEBUG ) { $this->savedErrorReporting = error_reporting(E_ALL); - $this->savedDisplayErrors = ini_set('display_errors', true); + $this->savedDisplayErrors = ini_set('display_errors', false); } elseif ( $lastLevel >= self::DEBUG && $this->level < self::DEBUG ) { error_reporting($this->savedErrorReporting); ini_set('display_errors', $this->savedDisplayErrors); diff --git a/web/includes/monitor_probe.php b/web/includes/monitor_probe.php index beedc39872..e8ced4fe77 100644 --- a/web/includes/monitor_probe.php +++ b/web/includes/monitor_probe.php @@ -694,12 +694,12 @@ function get_arp_results() { return $results; } if (count($result)==1) { - $arp_command .= ' -n'; + $arp_command .= ' -an'; } $result = exec(escapeshellcmd($arp_command), $output, $status); if ($status) { - ZM\Error("Unable to probe network cameras, status is '$status'"); + ZM\Error("Unable to probe network cameras using $arp_command, status is '$status' output is ".implode("\n", $output)); return $results; } foreach ($output as $line) { @@ -779,21 +779,25 @@ function probeNetwork() { ZM\Warning('No content from '.ZM_PATH_DATA.'/MacVendors.json'); } $macBases = json_decode($macVendors, true); - $oui_txt = file_get_contents('/usr/share/arp-scan/ieee-oui.txt'); - if (!$oui_txt) { - ZM\Warning('No content from /usr/share/arp-scan/ieee-oui.txt'); - } else { - foreach (explode(PHP_EOL, $oui_txt) as $line) { - if (false === strpos($line , '#')) { - $record = explode("\t", $line); - if (count($record) < 2) continue; - $mac = strtolower($record[0]); - $type = preg_replace('/\W/', '', $record[1]); - if (!isset($macBases[$mac])) - $macBases[$mac] = [ 'vendor'=>$record[1], 'type'=>$type]; + if (defined('ZM_PATH_OUI') and ZM_PATH_OUI and file_exists(ZM_PATH_OUI)) { + $oui_txt = file_get_contents(ZM_PATH_OUI); + if (!$oui_txt) { + ZM\Warning('No content from /usr/share/arp-scan/ieee-oui.txt'); + } else { + foreach (explode(PHP_EOL, $oui_txt) as $line) { + if (false === strpos($line , '#')) { + $record = explode("\t", $line); + if (count($record) < 2) continue; + $mac = strtolower($record[0]); + $type = preg_replace('/\W/', '', $record[1]); + if (!isset($macBases[$mac])) + $macBases[$mac] = [ 'vendor'=>$record[1], 'type'=>$type]; + } } + #ZM\Debug("bases: " . print_r($macBases, true)); } - #ZM\Debug("bases: " . print_r($macBases, true)); + } else { + ZM\Debug("No ieee-oui.txt"); } foreach (get_arp_results() as $mac=>$ip) { diff --git a/web/skins/classic/css/base/views/timeline.css.php b/web/skins/classic/css/base/views/timeline.css.php index 586fdbe859..c92fd1c309 100644 --- a/web/skins/classic/css/base/views/timeline.css.php +++ b/web/skins/classic/css/base/views/timeline.css.php @@ -33,6 +33,8 @@ case null: case 1: echo 'padding-top: 100%;'; break; + case 1.22: // 704x576 + echo 'padding-top: 82%;'; break; case 1.33: // 4:3 echo 'padding-top: 75%;'; break; case 1.47: diff --git a/web/skins/classic/includes/functions.php b/web/skins/classic/includes/functions.php index 566ad11bb3..dc1de91b08 100644 --- a/web/skins/classic/includes/functions.php +++ b/web/skins/classic/includes/functions.php @@ -212,14 +212,14 @@ function getNormalNavBarHTML($running, $user, $bandwidth_options, $view, $skin)
+