diff --git a/src/ros2_medkit_diagnostic_bridge/CHANGELOG.rst b/src/ros2_medkit_diagnostic_bridge/CHANGELOG.rst
new file mode 100644
index 00000000..44977c70
--- /dev/null
+++ b/src/ros2_medkit_diagnostic_bridge/CHANGELOG.rst
@@ -0,0 +1,19 @@
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+Changelog for package ros2_medkit_diagnostic_bridge
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+Forthcoming
+-----------
+* Initial rosdistro release
+* Bridge node converting standard ROS 2 /diagnostics to FaultManager fault reports
+* Severity mapping:
+
+ * OK -> PASSED event (fault condition cleared)
+ * WARN -> WARN severity FAILED event
+ * ERROR -> ERROR severity FAILED event
+ * STALE -> CRITICAL severity FAILED event
+
+* Auto-generated fault codes from diagnostic names (UPPER_SNAKE_CASE)
+* Custom name_to_code mappings via ROS parameters
+* Stateless design: always sends PASSED for OK status (handles restarts)
+* Contributors: Michal Faferek
diff --git a/src/ros2_medkit_fault_manager/CHANGELOG.rst b/src/ros2_medkit_fault_manager/CHANGELOG.rst
new file mode 100644
index 00000000..cd11a283
--- /dev/null
+++ b/src/ros2_medkit_fault_manager/CHANGELOG.rst
@@ -0,0 +1,50 @@
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+Changelog for package ros2_medkit_fault_manager
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+Forthcoming
+-----------
+* Initial rosdistro release
+* Central fault management node with ROS 2 services:
+
+ * ReportFault - report FAILED/PASSED events with debounce filtering
+ * GetFaults - query faults with filtering by severity, status, correlation
+ * ClearFault - clear/acknowledge faults
+
+* Debounce filtering with configurable thresholds:
+
+ * FAILED events decrement counter, PASSED events increment
+ * Configurable confirmation_threshold (default: -1, immediate)
+ * Optional healing support (healing_enabled, healing_threshold)
+ * Time-based auto-confirmation (auto_confirm_after_sec)
+ * CRITICAL severity bypasses debounce
+
+* Dual storage backends:
+
+ * SQLite persistent storage with WAL mode (default)
+ * In-memory storage for testing/lightweight deployments
+
+* Snapshot capture on fault confirmation:
+
+ * Topic data captured as JSON with configurable topic resolution
+ * Priority: fault_specific > patterns > default_topics
+ * Stored in SQLite with indexed fault_code lookup
+ * Auto-cleanup on fault clear
+
+* Rosbag capture with ring buffer:
+
+ * Configurable duration, post-fault recording, topic selection
+ * Lazy start mode (start on PREFAILED) or immediate
+ * Auto-cleanup of bag files, storage limits (max_bag_size_mb)
+ * GetRosbag service for bag file metadata
+
+* Fault correlation engine:
+
+ * Hierarchical mode: root cause to symptom relationships
+ * Auto-cluster mode: group similar faults within time window
+ * YAML-based configuration with pattern wildcards
+ * Muted faults tracking, auto-clear on root cause resolution
+
+* FaultEvent publishing on ~/events topic for SSE streaming
+* Wall clock timestamps (compatible with use_sim_time)
+* Contributors: Bartosz Burda, Michal Faferek
diff --git a/src/ros2_medkit_fault_reporter/CHANGELOG.rst b/src/ros2_medkit_fault_reporter/CHANGELOG.rst
new file mode 100644
index 00000000..3e9bf5e9
--- /dev/null
+++ b/src/ros2_medkit_fault_reporter/CHANGELOG.rst
@@ -0,0 +1,22 @@
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+Changelog for package ros2_medkit_fault_reporter
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+Forthcoming
+-----------
+* Initial rosdistro release
+* FaultReporter client library with simple API:
+
+ * report(fault_code, severity, description) - report FAILED events
+ * report_passed(fault_code) - report fault condition cleared
+ * High-severity faults (ERROR, CRITICAL) bypass local filtering
+
+* LocalFilter for per-fault-code threshold/window filtering:
+
+ * Configurable threshold (default: 3 reports) and time window (default: 10s)
+ * Prevents flooding FaultManager with duplicate reports
+ * PASSED events always forwarded (bypass filtering)
+
+* Configuration via ROS parameters (filter_threshold, filter_window_sec)
+* Thread-safe implementation with mutex-protected config access
+* Contributors: Bartosz Burda, Michal Faferek
diff --git a/src/ros2_medkit_gateway/CHANGELOG.rst b/src/ros2_medkit_gateway/CHANGELOG.rst
new file mode 100644
index 00000000..87ad4769
--- /dev/null
+++ b/src/ros2_medkit_gateway/CHANGELOG.rst
@@ -0,0 +1,38 @@
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+Changelog for package ros2_medkit_gateway
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+Forthcoming
+-----------
+* Initial rosdistro release
+* HTTP REST gateway for ros2_medkit diagnostics system
+* SOVD-compatible entity discovery with four entity types:
+
+ * Areas, Components, Apps, Functions
+ * HATEOAS links and capabilities in all responses
+ * Relationship endpoints (subareas, subcomponents, related-apps, hosts)
+
+* Three discovery modes:
+
+ * Runtime-only: automatic ROS 2 graph introspection
+ * Manifest-only: YAML manifest with validation (11 rules)
+ * Hybrid: manifest as source of truth + runtime linking
+
+* REST API endpoints:
+
+ * Fault management: GET/POST/DELETE /api/v1/faults
+ * Data access: topic sampling via GenericSubscription
+ * Operations: service calls and action goals via GenericClient
+ * Configuration: parameter get/set via ROS 2 parameter API
+ * Snapshots: GET /api/v1/faults/{code}/snapshots
+ * Rosbag: GET /api/v1/faults/{code}/snapshots/bag
+
+* Server-Sent Events (SSE) at /api/v1/faults/stream:
+
+ * Multi-client support with thread-safe event queue
+ * Keepalive, Last-Event-ID reconnection, configurable max_clients
+
+* JWT-based authentication with configurable policies
+* HTTPS/TLS support via OpenSSL and cpp-httplib
+* Native C++ ROS 2 serialization via ros2_medkit_serialization (no CLI dependencies)
+* Contributors: Bartosz Burda, Michal Faferek
diff --git a/src/ros2_medkit_gateway/CMakeLists.txt b/src/ros2_medkit_gateway/CMakeLists.txt
index b7337b27..fe5f5115 100644
--- a/src/ros2_medkit_gateway/CMakeLists.txt
+++ b/src/ros2_medkit_gateway/CMakeLists.txt
@@ -45,54 +45,18 @@ find_package(OpenSSL REQUIRED)
# This enables the httplib::SSLServer class for HTTPS support
add_compile_definitions(CPPHTTPLIB_OPENSSL_SUPPORT)
-# Fetch header-only libraries
-include(FetchContent)
-
-# Fetch tl::expected (C++11/14/17 compatible std::expected alternative)
-# Disable tests and examples to avoid clang-tidy analyzing external code
-set(EXPECTED_BUILD_TESTS OFF CACHE BOOL "" FORCE)
-set(EXPECTED_BUILD_PACKAGE OFF CACHE BOOL "" FORCE)
-fetchcontent_declare(
- tl_expected
- GIT_REPOSITORY https://github.com/TartanLlama/expected.git
- GIT_TAG v1.3.1
- SYSTEM # Treat as system headers to suppress warnings
- EXCLUDE_FROM_ALL # Don't build tests/examples
-)
-fetchcontent_getproperties(tl_expected)
-if(NOT tl_expected_POPULATED)
- fetchcontent_populate(tl_expected)
- # Only add include directory, don't add_subdirectory to avoid install conflicts
-endif()
-
-# Create interface library for tl::expected manually
+# Vendored header-only libraries (no network access on ROS build farm)
+# tl::expected v1.3.1 (CC0) - https://github.com/TartanLlama/expected
add_library(tl_expected_iface INTERFACE)
target_include_directories(tl_expected_iface SYSTEM INTERFACE
- ${tl_expected_SOURCE_DIR}/include
+ ${CMAKE_CURRENT_SOURCE_DIR}/src/vendored/tl_expected/include
)
add_library(tl::expected ALIAS tl_expected_iface)
-# Fetch jwt-cpp header-only library
-# Disable tests and examples to avoid clang-tidy analyzing external code
-set(JWT_CPP_BUILD_EXAMPLES OFF CACHE BOOL "" FORCE)
-set(JWT_CPP_BUILD_TESTS OFF CACHE BOOL "" FORCE)
-fetchcontent_declare(
- jwt-cpp
- GIT_REPOSITORY https://github.com/Thalhammer/jwt-cpp.git
- GIT_TAG v0.7.0
- SYSTEM # Treat as system headers to suppress warnings
- EXCLUDE_FROM_ALL # Don't build tests/examples
-)
-fetchcontent_getproperties(jwt-cpp)
-if(NOT jwt-cpp_POPULATED)
- fetchcontent_populate(jwt-cpp)
- # Only add include directory, don't add_subdirectory to avoid install conflicts
-endif()
-
-# Create interface library for jwt-cpp manually
+# jwt-cpp v0.7.0 (MIT) - https://github.com/Thalhammer/jwt-cpp
add_library(jwt_cpp_iface INTERFACE)
target_include_directories(jwt_cpp_iface SYSTEM INTERFACE
- ${jwt-cpp_SOURCE_DIR}/include
+ ${CMAKE_CURRENT_SOURCE_DIR}/src/vendored/jwt_cpp/include
)
add_library(jwt-cpp::jwt-cpp ALIAS jwt_cpp_iface)
@@ -217,20 +181,52 @@ if(BUILD_TESTING)
set(ament_cmake_clang_format_CONFIG_FILE "${CMAKE_CURRENT_SOURCE_DIR}/../../.clang-format")
set(ament_cmake_clang_tidy_CONFIG_FILE "${CMAKE_CURRENT_SOURCE_DIR}/../../.clang-tidy")
- # Limit clang-tidy to only report issues from our source files (not FetchContent deps)
+ # Limit clang-tidy to only report issues from our source files (not vendored deps)
set(ament_cmake_clang_tidy_HEADER_FILTER "^${CMAKE_CURRENT_SOURCE_DIR}/(include|src|test)/")
- # Exclude linters that don't work well with FetchContent dependencies:
+ # Exclude linters that don't work well with vendored dependencies:
# - uncrustify/cpplint: conflicts with clang-format
- # - copyright/lint_cmake: flags generated/fetched files in install/
+ # - copyright/clang_format: we configure manually to skip src/vendored/
# - clang_tidy: we configure it manually below with increased timeout
list(APPEND AMENT_LINT_AUTO_EXCLUDE
ament_cmake_uncrustify
ament_cmake_cpplint
ament_cmake_clang_tidy
+ ament_cmake_copyright
+ ament_cmake_clang_format
)
ament_lint_auto_find_test_dependencies()
+ # Configure copyright check to exclude vendored dependencies
+ find_package(ament_cmake_copyright REQUIRED)
+ set(VENDORED_FILES
+ "src/vendored/jwt_cpp/include/jwt-cpp/base.h"
+ "src/vendored/jwt_cpp/include/jwt-cpp/jwt.h"
+ "src/vendored/jwt_cpp/include/jwt-cpp/traits/boost-json/defaults.h"
+ "src/vendored/jwt_cpp/include/jwt-cpp/traits/boost-json/traits.h"
+ "src/vendored/jwt_cpp/include/jwt-cpp/traits/danielaparker-jsoncons/defaults.h"
+ "src/vendored/jwt_cpp/include/jwt-cpp/traits/danielaparker-jsoncons/traits.h"
+ "src/vendored/jwt_cpp/include/jwt-cpp/traits/kazuho-picojson/defaults.h"
+ "src/vendored/jwt_cpp/include/jwt-cpp/traits/kazuho-picojson/traits.h"
+ "src/vendored/jwt_cpp/include/jwt-cpp/traits/nlohmann-json/defaults.h"
+ "src/vendored/jwt_cpp/include/jwt-cpp/traits/nlohmann-json/traits.h"
+ "src/vendored/jwt_cpp/include/picojson/picojson.h"
+ "src/vendored/tl_expected/include/tl/expected.hpp"
+ )
+ ament_copyright(EXCLUDE ${VENDORED_FILES})
+
+ # Configure clang-format to only check our source files (not vendored)
+ find_package(ament_cmake_clang_format REQUIRED)
+ file(GLOB_RECURSE _format_files
+ "include/*.h" "include/*.hpp"
+ "src/*.cpp" "src/*.h" "src/*.hpp"
+ "test/*.cpp" "test/*.h" "test/*.hpp"
+ )
+ list(FILTER _format_files EXCLUDE REGEX ".*/vendored/.*")
+ ament_clang_format(${_format_files}
+ CONFIG_FILE "${ament_cmake_clang_format_CONFIG_FILE}"
+ )
+
# Configure clang-tidy manually with increased timeout (1500s instead of default 300s)
# This is needed because the project has many files and clang-tidy analysis takes time
ament_clang_tidy(
diff --git a/src/ros2_medkit_gateway/package.xml b/src/ros2_medkit_gateway/package.xml
index 5bfe149d..9512fa76 100644
--- a/src/ros2_medkit_gateway/package.xml
+++ b/src/ros2_medkit_gateway/package.xml
@@ -18,8 +18,9 @@
action_msgs
nlohmann-json-dev
libcpp-httplib-dev
-
+
+ libssl-dev
yaml_cpp_vendor
ros2_medkit_msgs
ros2_medkit_serialization
diff --git a/src/ros2_medkit_gateway/src/vendored/jwt_cpp/LICENSE b/src/ros2_medkit_gateway/src/vendored/jwt_cpp/LICENSE
new file mode 100644
index 00000000..7d583cec
--- /dev/null
+++ b/src/ros2_medkit_gateway/src/vendored/jwt_cpp/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2018 Dominik Thalhammer
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/src/ros2_medkit_gateway/src/vendored/jwt_cpp/include/jwt-cpp/base.h b/src/ros2_medkit_gateway/src/vendored/jwt_cpp/include/jwt-cpp/base.h
new file mode 100644
index 00000000..fd3ee281
--- /dev/null
+++ b/src/ros2_medkit_gateway/src/vendored/jwt_cpp/include/jwt-cpp/base.h
@@ -0,0 +1,270 @@
+#ifndef JWT_CPP_BASE_H
+#define JWT_CPP_BASE_H
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+#ifdef __has_cpp_attribute
+#if __has_cpp_attribute(fallthrough)
+#define JWT_FALLTHROUGH [[fallthrough]]
+#endif
+#endif
+
+#ifndef JWT_FALLTHROUGH
+#define JWT_FALLTHROUGH
+#endif
+
+namespace jwt {
+ /**
+ * \brief character maps when encoding and decoding
+ */
+ namespace alphabet {
+ /**
+ * \brief valid list of character when working with [Base64](https://datatracker.ietf.org/doc/html/rfc4648#section-4)
+ *
+ * As directed in [X.509 Parameter](https://datatracker.ietf.org/doc/html/rfc7517#section-4.7) certificate chains are
+ * base64-encoded as per [Section 4 of RFC4648](https://datatracker.ietf.org/doc/html/rfc4648#section-4)
+ */
+ struct base64 {
+ static const std::array& data() {
+ static constexpr std::array data{
+ {'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
+ 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',
+ 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
+ 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'}};
+ return data;
+ }
+ static const std::string& fill() {
+ static const std::string fill{"="};
+ return fill;
+ }
+ };
+ /**
+ * \brief valid list of character when working with [Base64URL](https://tools.ietf.org/html/rfc4648#section-5)
+ *
+ * As directed by [RFC 7519 Terminology](https://datatracker.ietf.org/doc/html/rfc7519#section-2) set the definition of Base64URL
+ * encoding as that in [RFC 7515](https://datatracker.ietf.org/doc/html/rfc7515#section-2) that states:
+ *
+ * > Base64 encoding using the URL- and filename-safe character set defined in
+ * > [Section 5 of RFC 4648 RFC4648](https://tools.ietf.org/html/rfc4648#section-5), with all trailing '=' characters omitted
+ */
+ struct base64url {
+ static const std::array& data() {
+ static constexpr std::array data{
+ {'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
+ 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',
+ 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
+ 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-', '_'}};
+ return data;
+ }
+ static const std::string& fill() {
+ static const std::string fill{"%3d"};
+ return fill;
+ }
+ };
+ namespace helper {
+ /**
+ * @brief A General purpose base64url alphabet respecting the
+ * [URI Case Normalization](https://datatracker.ietf.org/doc/html/rfc3986#section-6.2.2.1)
+ *
+ * This is useful in situations outside of JWT encoding/decoding and is provided as a helper
+ */
+ struct base64url_percent_encoding {
+ static const std::array& data() {
+ static constexpr std::array data{
+ {'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
+ 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',
+ 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
+ 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-', '_'}};
+ return data;
+ }
+ static const std::vector& fill() {
+ static const std::vector 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 A collection of fellable functions for working with base64 and base64url
+ */
+ namespace base {
+
+ namespace details {
+ struct padding {
+ size_t count = 0;
+ size_t length = 0;
+
+ padding() = default;
+ padding(size_t count, size_t length) : count(count), length(length) {}
+
+ padding operator+(const padding& p) { return padding(count + p.count, length + p.length); }
+
+ friend bool operator==(const padding& lhs, const padding& rhs) {
+ return lhs.count == rhs.count && lhs.length == rhs.length;
+ }
+ };
+
+ inline padding count_padding(const std::string& base, const std::vector& fills) {
+ for (const auto& fill : fills) {
+ if (base.size() < fill.size()) continue;
+ // Does the end of the input exactly match the fill pattern?
+ if (base.substr(base.size() - fill.size()) == fill) {
+ return padding{1, fill.length()} +
+ count_padding(base.substr(0, base.size() - fill.size()), fills);
+ }
+ }
+
+ return {};
+ }
+
+ inline std::string encode(const std::string& bin, const std::array& alphabet,
+ const std::string& fill) {
+ size_t size = bin.size();
+ std::string res;
+
+ // clear incomplete bytes
+ size_t fast_size = size - size % 3;
+ for (size_t i = 0; i < fast_size;) {
+ uint32_t octet_a = static_cast(bin[i++]);
+ uint32_t octet_b = static_cast(bin[i++]);
+ uint32_t octet_c = static_cast(bin[i++]);
+
+ uint32_t triple = (octet_a << 0x10) + (octet_b << 0x08) + octet_c;
+
+ res += alphabet[(triple >> 3 * 6) & 0x3F];
+ res += alphabet[(triple >> 2 * 6) & 0x3F];
+ res += alphabet[(triple >> 1 * 6) & 0x3F];
+ res += alphabet[(triple >> 0 * 6) & 0x3F];
+ }
+
+ if (fast_size == size) return res;
+
+ size_t mod = size % 3;
+
+ uint32_t octet_a = fast_size < size ? static_cast(bin[fast_size++]) : 0;
+ uint32_t octet_b = fast_size < size ? static_cast(bin[fast_size++]) : 0;
+ uint32_t octet_c = fast_size < size ? static_cast(bin[fast_size++]) : 0;
+
+ uint32_t triple = (octet_a << 0x10) + (octet_b << 0x08) + octet_c;
+
+ switch (mod) {
+ case 1:
+ res += alphabet[(triple >> 3 * 6) & 0x3F];
+ res += alphabet[(triple >> 2 * 6) & 0x3F];
+ res += fill;
+ res += fill;
+ break;
+ case 2:
+ res += alphabet[(triple >> 3 * 6) & 0x3F];
+ res += alphabet[(triple >> 2 * 6) & 0x3F];
+ res += alphabet[(triple >> 1 * 6) & 0x3F];
+ res += fill;
+ break;
+ default: break;
+ }
+
+ return res;
+ }
+
+ inline std::string decode(const std::string& base, const std::array& 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");
+
+ const size_t size = base.size() - pad.length;
+ if ((size + pad.count) % 4 != 0) throw std::runtime_error("Invalid input: incorrect total size");
+
+ size_t out_size = size / 4 * 3;
+ std::string res;
+ res.reserve(out_size);
+
+ auto get_sextet = [&](size_t offset) { return alphabet::index(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);
+ }
+
+ if (pad.count == 0) return res;
+
+ uint32_t triple = (get_sextet(fast_size) << 3 * 6) + (get_sextet(fast_size + 1) << 2 * 6);
+
+ switch (pad.count) {
+ case 1:
+ triple |= (get_sextet(fast_size + 2) << 1 * 6);
+ res += static_cast((triple >> 2 * 8) & 0xFFU);
+ res += static_cast((triple >> 1 * 8) & 0xFFU);
+ break;
+ case 2: res += static_cast((triple >> 2 * 8) & 0xFFU); break;
+ default: break;
+ }
+
+ return res;
+ }
+
+ inline std::string decode(const std::string& base, const std::array& alphabet,
+ const std::string& fill) {
+ return decode(base, alphabet, std::vector{fill});
+ }
+
+ inline std::string pad(const std::string& base, const std::string& fill) {
+ std::string padding;
+ switch (base.size() % 4) {
+ case 1: padding += fill; JWT_FALLTHROUGH;
+ case 2: padding += fill; JWT_FALLTHROUGH;
+ case 3: padding += fill; JWT_FALLTHROUGH;
+ default: break;
+ }
+
+ return base + padding;
+ }
+
+ inline std::string trim(const std::string& base, const std::string& fill) {
+ auto pos = base.find(fill);
+ return base.substr(0, pos);
+ }
+ } // namespace details
+
+ template
+ std::string encode(const std::string& bin) {
+ return details::encode(bin, T::data(), T::fill());
+ }
+ 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/src/ros2_medkit_gateway/src/vendored/jwt_cpp/include/jwt-cpp/jwt.h b/src/ros2_medkit_gateway/src/vendored/jwt_cpp/include/jwt-cpp/jwt.h
new file mode 100644
index 00000000..b2b998a2
--- /dev/null
+++ b/src/ros2_medkit_gateway/src/vendored/jwt_cpp/include/jwt-cpp/jwt.h
@@ -0,0 +1,3655 @@
+#ifndef JWT_CPP_JWT_H
+#define JWT_CPP_JWT_H
+
+#ifndef JWT_DISABLE_PICOJSON
+#ifndef PICOJSON_USE_INT64
+#define PICOJSON_USE_INT64
+#endif
+#include "picojson/picojson.h"
+#endif
+
+#ifndef JWT_DISABLE_BASE64
+#include "base.h"
+#endif
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#if __cplusplus > 201103L
+#include
+#endif
+
+#if __cplusplus >= 201402L
+#ifdef __has_include
+#if __has_include()
+#include
+#endif
+#endif
+#endif
+
+#if OPENSSL_VERSION_NUMBER >= 0x30000000L // 3.0.0
+#define JWT_OPENSSL_3_0
+#elif OPENSSL_VERSION_NUMBER >= 0x10101000L // 1.1.1
+#define JWT_OPENSSL_1_1_1
+#elif OPENSSL_VERSION_NUMBER >= 0x10100000L // 1.1.0
+#define JWT_OPENSSL_1_1_0
+#elif OPENSSL_VERSION_NUMBER >= 0x10000000L // 1.0.0
+#define JWT_OPENSSL_1_0_0
+#endif
+
+#if defined(LIBRESSL_VERSION_NUMBER)
+#if LIBRESSL_VERSION_NUMBER >= 0x3050300fL
+#define JWT_OPENSSL_1_1_0
+#else
+#define JWT_OPENSSL_1_0_0
+#endif
+#endif
+
+#if defined(LIBWOLFSSL_VERSION_HEX)
+#define JWT_OPENSSL_1_1_1
+#endif
+
+#ifndef JWT_CLAIM_EXPLICIT
+#define JWT_CLAIM_EXPLICIT explicit
+#endif
+
+/**
+ * \brief JSON Web Token
+ *
+ * A namespace to contain everything related to handling JSON Web Tokens, JWT for short,
+ * as a part of [RFC7519](https://tools.ietf.org/html/rfc7519), or alternatively for
+ * JWS (JSON Web Signature) from [RFC7515](https://tools.ietf.org/html/rfc7515)
+ */
+namespace jwt {
+ /**
+ * Default system time point in UTC
+ */
+ using date = std::chrono::system_clock::time_point;
+
+ /**
+ * \brief Everything related to error codes issued by the library
+ */
+ namespace error {
+ struct signature_verification_exception : public std::system_error {
+ using system_error::system_error;
+ };
+ struct signature_generation_exception : public std::system_error {
+ using system_error::system_error;
+ };
+ struct rsa_exception : public std::system_error {
+ using system_error::system_error;
+ };
+ struct ecdsa_exception : public std::system_error {
+ using system_error::system_error;
+ };
+ struct token_verification_exception : public std::system_error {
+ using system_error::system_error;
+ };
+ /**
+ * \brief Errors related to processing of RSA signatures
+ */
+ enum class rsa_error {
+ ok = 0,
+ cert_load_failed = 10,
+ get_key_failed,
+ write_key_failed,
+ write_cert_failed,
+ convert_to_pem_failed,
+ load_key_bio_write,
+ load_key_bio_read,
+ create_mem_bio_failed,
+ no_key_provided
+ };
+ /**
+ * \brief Error category for RSA errors
+ */
+ inline std::error_category& rsa_error_category() {
+ class rsa_error_cat : public std::error_category {
+ public:
+ const char* name() const noexcept override { return "rsa_error"; };
+ std::string message(int ev) const override {
+ switch (static_cast(ev)) {
+ case rsa_error::ok: return "no error";
+ case rsa_error::cert_load_failed: return "error loading cert into memory";
+ case rsa_error::get_key_failed: return "error getting key from certificate";
+ case rsa_error::write_key_failed: return "error writing key data in PEM format";
+ case rsa_error::write_cert_failed: return "error writing cert data in PEM format";
+ case rsa_error::convert_to_pem_failed: return "failed to convert key to pem";
+ case rsa_error::load_key_bio_write: return "failed to load key: bio write failed";
+ case rsa_error::load_key_bio_read: return "failed to load key: bio read failed";
+ case rsa_error::create_mem_bio_failed: return "failed to create memory bio";
+ case rsa_error::no_key_provided: return "at least one of public or private key need to be present";
+ default: return "unknown RSA error";
+ }
+ }
+ };
+ static rsa_error_cat cat;
+ return cat;
+ }
+
+ inline std::error_code make_error_code(rsa_error e) { return {static_cast(e), rsa_error_category()}; }
+ /**
+ * \brief Errors related to processing of RSA signatures
+ */
+ enum class ecdsa_error {
+ ok = 0,
+ load_key_bio_write = 10,
+ load_key_bio_read,
+ create_mem_bio_failed,
+ no_key_provided,
+ invalid_key_size,
+ invalid_key,
+ create_context_failed
+ };
+ /**
+ * \brief Error category for ECDSA errors
+ */
+ inline std::error_category& ecdsa_error_category() {
+ class ecdsa_error_cat : public std::error_category {
+ public:
+ const char* name() const noexcept override { return "ecdsa_error"; };
+ std::string message(int ev) const override {
+ switch (static_cast(ev)) {
+ case ecdsa_error::ok: return "no error";
+ case ecdsa_error::load_key_bio_write: return "failed to load key: bio write failed";
+ case ecdsa_error::load_key_bio_read: return "failed to load key: bio read failed";
+ case ecdsa_error::create_mem_bio_failed: return "failed to create memory bio";
+ case ecdsa_error::no_key_provided:
+ return "at least one of public or private key need to be present";
+ case ecdsa_error::invalid_key_size: return "invalid key size";
+ case ecdsa_error::invalid_key: return "invalid key";
+ case ecdsa_error::create_context_failed: return "failed to create context";
+ default: return "unknown ECDSA error";
+ }
+ }
+ };
+ static ecdsa_error_cat cat;
+ return cat;
+ }
+
+ inline std::error_code make_error_code(ecdsa_error e) { return {static_cast(e), ecdsa_error_category()}; }
+
+ /**
+ * \brief Errors related to verification of signatures
+ */
+ enum class signature_verification_error {
+ ok = 0,
+ invalid_signature = 10,
+ create_context_failed,
+ verifyinit_failed,
+ verifyupdate_failed,
+ verifyfinal_failed,
+ get_key_failed,
+ set_rsa_pss_saltlen_failed,
+ signature_encoding_failed
+ };
+ /**
+ * \brief Error category for verification errors
+ */
+ inline std::error_category& signature_verification_error_category() {
+ class verification_error_cat : public std::error_category {
+ public:
+ const char* name() const noexcept override { return "signature_verification_error"; };
+ std::string message(int ev) const override {
+ switch (static_cast(ev)) {
+ case signature_verification_error::ok: return "no error";
+ case signature_verification_error::invalid_signature: return "invalid signature";
+ case signature_verification_error::create_context_failed:
+ return "failed to verify signature: could not create context";
+ case signature_verification_error::verifyinit_failed:
+ return "failed to verify signature: VerifyInit failed";
+ case signature_verification_error::verifyupdate_failed:
+ return "failed to verify signature: VerifyUpdate failed";
+ case signature_verification_error::verifyfinal_failed:
+ return "failed to verify signature: VerifyFinal failed";
+ case signature_verification_error::get_key_failed:
+ return "failed to verify signature: Could not get key";
+ case signature_verification_error::set_rsa_pss_saltlen_failed:
+ return "failed to verify signature: EVP_PKEY_CTX_set_rsa_pss_saltlen failed";
+ case signature_verification_error::signature_encoding_failed:
+ return "failed to verify signature: i2d_ECDSA_SIG failed";
+ default: return "unknown signature verification error";
+ }
+ }
+ };
+ static verification_error_cat cat;
+ return cat;
+ }
+
+ inline std::error_code make_error_code(signature_verification_error e) {
+ return {static_cast(e), signature_verification_error_category()};
+ }
+
+ /**
+ * \brief Errors related to signature generation errors
+ */
+ enum class signature_generation_error {
+ ok = 0,
+ hmac_failed = 10,
+ create_context_failed,
+ signinit_failed,
+ signupdate_failed,
+ signfinal_failed,
+ ecdsa_do_sign_failed,
+ digestinit_failed,
+ digestupdate_failed,
+ digestfinal_failed,
+ rsa_padding_failed,
+ rsa_private_encrypt_failed,
+ get_key_failed,
+ set_rsa_pss_saltlen_failed,
+ signature_decoding_failed
+ };
+ /**
+ * \brief Error category for signature generation errors
+ */
+ inline std::error_category& signature_generation_error_category() {
+ class signature_generation_error_cat : public std::error_category {
+ public:
+ const char* name() const noexcept override { return "signature_generation_error"; };
+ std::string message(int ev) const override {
+ switch (static_cast(ev)) {
+ case signature_generation_error::ok: return "no error";
+ case signature_generation_error::hmac_failed: return "hmac failed";
+ case signature_generation_error::create_context_failed:
+ return "failed to create signature: could not create context";
+ case signature_generation_error::signinit_failed:
+ return "failed to create signature: SignInit failed";
+ case signature_generation_error::signupdate_failed:
+ return "failed to create signature: SignUpdate failed";
+ case signature_generation_error::signfinal_failed:
+ return "failed to create signature: SignFinal failed";
+ case signature_generation_error::ecdsa_do_sign_failed: return "failed to generate ecdsa signature";
+ case signature_generation_error::digestinit_failed:
+ return "failed to create signature: DigestInit failed";
+ case signature_generation_error::digestupdate_failed:
+ return "failed to create signature: DigestUpdate failed";
+ case signature_generation_error::digestfinal_failed:
+ return "failed to create signature: DigestFinal failed";
+ case signature_generation_error::rsa_padding_failed:
+ return "failed to create signature: EVP_PKEY_CTX_set_rsa_padding failed";
+ case signature_generation_error::rsa_private_encrypt_failed:
+ return "failed to create signature: RSA_private_encrypt failed";
+ case signature_generation_error::get_key_failed:
+ return "failed to generate signature: Could not get key";
+ case signature_generation_error::set_rsa_pss_saltlen_failed:
+ return "failed to create signature: EVP_PKEY_CTX_set_rsa_pss_saltlen failed";
+ case signature_generation_error::signature_decoding_failed:
+ return "failed to create signature: d2i_ECDSA_SIG failed";
+ default: return "unknown signature generation error";
+ }
+ }
+ };
+ static signature_generation_error_cat cat = {};
+ return cat;
+ }
+
+ inline std::error_code make_error_code(signature_generation_error e) {
+ return {static_cast(e), signature_generation_error_category()};
+ }
+
+ /**
+ * \brief Errors related to token verification errors
+ */
+ enum class token_verification_error {
+ ok = 0,
+ wrong_algorithm = 10,
+ missing_claim,
+ claim_type_missmatch,
+ claim_value_missmatch,
+ token_expired,
+ audience_missmatch
+ };
+ /**
+ * \brief Error category for token verification errors
+ */
+ inline std::error_category& token_verification_error_category() {
+ class token_verification_error_cat : public std::error_category {
+ public:
+ const char* name() const noexcept override { return "token_verification_error"; };
+ std::string message(int ev) const override {
+ switch (static_cast(ev)) {
+ case token_verification_error::ok: return "no error";
+ case token_verification_error::wrong_algorithm: return "wrong algorithm";
+ case token_verification_error::missing_claim: return "decoded JWT is missing required claim(s)";
+ case token_verification_error::claim_type_missmatch:
+ return "claim type does not match expected type";
+ case token_verification_error::claim_value_missmatch:
+ return "claim value does not match expected value";
+ case token_verification_error::token_expired: return "token expired";
+ case token_verification_error::audience_missmatch:
+ return "token doesn't contain the required audience";
+ default: return "unknown token verification error";
+ }
+ }
+ };
+ static token_verification_error_cat cat = {};
+ return cat;
+ }
+
+ inline std::error_code make_error_code(token_verification_error e) {
+ return {static_cast(e), token_verification_error_category()};
+ }
+
+ inline void throw_if_error(std::error_code ec) {
+ if (ec) {
+ if (ec.category() == rsa_error_category()) throw rsa_exception(ec);
+ if (ec.category() == ecdsa_error_category()) throw ecdsa_exception(ec);
+ if (ec.category() == signature_verification_error_category())
+ throw signature_verification_exception(ec);
+ if (ec.category() == signature_generation_error_category()) throw signature_generation_exception(ec);
+ if (ec.category() == token_verification_error_category()) throw token_verification_exception(ec);
+ }
+ }
+ } // namespace error
+} // namespace jwt
+
+namespace std {
+ template<>
+ struct is_error_code_enum : true_type {};
+ template<>
+ struct is_error_code_enum : true_type {};
+ template<>
+ struct is_error_code_enum : true_type {};
+ template<>
+ struct is_error_code_enum : true_type {};
+ template<>
+ struct is_error_code_enum : true_type {};
+} // namespace std
+
+namespace jwt {
+ /**
+ * \brief A collection for working with certificates
+ *
+ * These _helpers_ are usefully when working with certificates OpenSSL APIs.
+ * For example, when dealing with JWKS (JSON Web Key Set)[https://tools.ietf.org/html/rfc7517]
+ * you maybe need to extract the modulus and exponent of an RSA Public Key.
+ */
+ namespace helper {
+ /**
+ * \brief Handle class for EVP_PKEY structures
+ *
+ * Starting from OpenSSL 1.1.0, EVP_PKEY has internal reference counting. This handle class allows
+ * jwt-cpp to leverage that and thus safe an allocation for the control block in std::shared_ptr.
+ * The handle uses shared_ptr as a fallback on older versions. The behaviour should be identical between both.
+ */
+ class evp_pkey_handle {
+ public:
+ 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 occurred)
+ */
+ inline std::string extract_pubkey_from_cert(const std::string& certstr, const std::string& pw,
+ std::error_code& ec) {
+ ec.clear();
+ auto certbio = make_mem_buf_bio(certstr);
+ auto keybio = make_mem_buf_bio();
+ if (!certbio || !keybio) {
+ ec = error::rsa_error::create_mem_bio_failed;
+ return {};
+ }
+
+ std::unique_ptr cert(
+ PEM_read_bio_X509(certbio.get(), nullptr, nullptr, const_cast(pw.c_str())), X509_free);
+ if (!cert) {
+ ec = error::rsa_error::cert_load_failed;
+ return {};
+ }
+ std::unique_ptr key(X509_get_pubkey(cert.get()), EVP_PKEY_free);
+ if (!key) {
+ ec = error::rsa_error::get_key_failed;
+ return {};
+ }
+ if (PEM_write_bio_PUBKEY(keybio.get(), key.get()) == 0) {
+ ec = error::rsa_error::write_key_failed;
+ return {};
+ }
+ char* ptr = nullptr;
+ auto len = BIO_get_mem_data(keybio.get(), &ptr);
+ if (len <= 0 || ptr == nullptr) {
+ ec = error::rsa_error::convert_to_pem_failed;
+ return {};
+ }
+ return {ptr, static_cast(len)};
+ }
+
+ /**
+ * \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)
+ * \throw rsa_exception if an error occurred
+ */
+ inline std::string extract_pubkey_from_cert(const std::string& certstr, const std::string& pw = "") {
+ std::error_code ec;
+ auto res = extract_pubkey_from_cert(certstr, pw, ec);
+ error::throw_if_error(ec);
+ return res;
+ }
+
+ /**
+ * \brief Convert the certificate provided as DER to PEM.
+ *
+ * \param cert_der_str String containing the certificate encoded as base64 DER
+ * \param ec error_code for error_detection (gets cleared if no error occurs)
+ */
+ inline std::string convert_der_to_pem(const std::string& cert_der_str, std::error_code& ec) {
+ ec.clear();
+
+ auto c_str = reinterpret_cast(cert_der_str.c_str());
+
+ std::unique_ptr cert(
+ d2i_X509(NULL, &c_str, static_cast(cert_der_str.size())), X509_free);
+ auto certbio = make_mem_buf_bio();
+ if (!cert || !certbio) {
+ ec = error::rsa_error::create_mem_bio_failed;
+ return {};
+ }
+
+ if (!PEM_write_bio_X509(certbio.get(), cert.get())) {
+ ec = error::rsa_error::write_cert_failed;
+ return {};
+ }
+
+ char* ptr = nullptr;
+ const auto len = BIO_get_mem_data(certbio.get(), &ptr);
+ if (len <= 0 || ptr == nullptr) {
+ ec = error::rsa_error::convert_to_pem_failed;
+ return {};
+ }
+
+ 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.
+ *
+ * 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
+ * \throw rsa_exception if an error occurred
+ */
+ template
+ std::string convert_base64_der_to_pem(const std::string& cert_base64_der_str, Decode decode) {
+ std::error_code ec;
+ auto res = convert_base64_der_to_pem(cert_base64_der_str, std::move(decode), ec);
+ error::throw_if_error(ec);
+ return res;
+ }
+
+ /**
+ * \brief Convert the certificate provided as DER to PEM.
+ *
+ * \param cert_der_str String containing the DER certificate
+ * \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.
+ *
+ * This is useful when using with JWKs as x5c claim is encoded as base64 DER. More info
+ * (here)[https://tools.ietf.org/html/rfc7517#section-4.7]
+ *
+ * \param cert_base64_der_str String containing the certificate encoded as base64 DER
+ * \param ec error_code for error_detection (gets cleared if no error occurs)
+ */
+ inline std::string convert_base64_der_to_pem(const std::string& cert_base64_der_str, std::error_code& ec) {
+ auto decode = [](const std::string& token) {
+ return base::decode(base::pad(token));
+ };
+ return convert_base64_der_to_pem(cert_base64_der_str, std::move(decode), ec);
+ }
+
+ /**
+ * \brief Convert the certificate provided as base64 DER to PEM.
+ *
+ * This is useful when using with JWKs as x5c claim is encoded as base64 DER. More info
+ * (here)[https://tools.ietf.org/html/rfc7517#section-4.7]
+ *
+ * \param cert_base64_der_str String containing the certificate encoded as base64 DER
+ * \throw rsa_exception if an error occurred
+ */
+ inline std::string convert_base64_der_to_pem(const std::string& cert_base64_der_str) {
+ std::error_code ec;
+ auto res = convert_base64_der_to_pem(cert_base64_der_str, ec);
+ error::throw_if_error(ec);
+ return res;
+ }
+#endif
+ /**
+ * \brief Load a public key from a string.
+ *
+ * The string should contain a pem encoded certificate or public key
+ *
+ * \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_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::rsa_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::rsa_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::rsa_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::rsa_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 rsa_exception if an error occurred
+ */
+ inline evp_pkey_handle load_public_key_from_string(const std::string& key, const std::string& password = "") {
+ std::error_code ec;
+ auto res = load_public_key_from_string(key, password, ec);
+ error::throw_if_error(ec);
+ return res;
+ }
+
+ /**
+ * \brief Load a private key from a string.
+ *
+ * \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_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 {};
+ }
+ 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 {};
+ }
+ 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;
+ }
+
+ /**
+ * \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 rsa_exception if an error occurred
+ */
+ 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 JWT_OPENSSL_1_0_0
+ std::string
+ bn2raw(BIGNUM* bn)
+#else
+ std::string
+ bn2raw(const BIGNUM* bn)
+#endif
+ {
+ std::string res(BN_num_bytes(bn), '\0');
+ BN_bn2bin(bn, (unsigned char*)res.data()); // NOLINT(google-readability-casting) requires `const_cast`
+ return res;
+ }
+ /**
+ * Convert an std::string to a OpenSSL BIGNUM
+ * \param raw String to convert
+ * \return BIGNUM representation
+ */
+ 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);
+ }
+ } // namespace helper
+
+ /**
+ * \brief Various cryptographic algorithms when working with JWT
+ *
+ * JWT (JSON Web Tokens) signatures are typically used as the payload for a JWS (JSON Web Signature) or
+ * JWE (JSON Web Encryption). Both of these use various cryptographic as specified by
+ * [RFC7518](https://tools.ietf.org/html/rfc7518) and are exposed through the a [JOSE
+ * Header](https://tools.ietf.org/html/rfc7515#section-4) which points to one of the JWA (JSON Web
+ * Algorithms)(https://tools.ietf.org/html/rfc7518#section-3.1)
+ */
+ namespace algorithm {
+ /**
+ * \brief "none" algorithm.
+ *
+ * Returns and empty signature and checks if the given signature is empty.
+ */
+ struct none {
+ /**
+ * \brief Return an empty string
+ */
+ std::string sign(const std::string& /*unused*/, std::error_code& ec) const {
+ ec.clear();
+ return {};
+ }
+ /**
+ * \brief Check if the given signature is empty.
+ *
+ * JWT's with "none" algorithm should not contain a signature.
+ * \param signature Signature data to verify
+ * \param ec error_code filled with details about the error
+ */
+ void verify(const std::string& /*unused*/, const std::string& signature, std::error_code& ec) const {
+ ec.clear();
+ if (!signature.empty()) { ec = error::signature_verification_error::invalid_signature; }
+ }
+ /// Get algorithm name
+ std::string name() const { return "none"; }
+ };
+ /**
+ * \brief Base class for HMAC family of algorithms
+ */
+ struct hmacsha {
+ /**
+ * Construct new hmac algorithm
+ * \param key Key to use for HMAC
+ * \param md Pointer to hash function
+ * \param name Name of the algorithm
+ */
+ hmacsha(std::string key, const EVP_MD* (*md)(), std::string name)
+ : secret(std::move(key)), md(md), alg_name(std::move(name)) {}
+ /**
+ * Sign jwt data
+ * \param data The data to sign
+ * \param ec error_code filled with details on error
+ * \return HMAC signature for the given data
+ */
+ std::string sign(const std::string& data, std::error_code& ec) const {
+ ec.clear();
+ std::string res(static_cast(EVP_MAX_MD_SIZE), '\0');
+ auto len = static_cast(res.size());
+ if (HMAC(md(), secret.data(), static_cast(secret.size()),
+ reinterpret_cast(data.data()), static_cast(data.size()),
+ (unsigned char*)res.data(), // NOLINT(google-readability-casting) requires `const_cast`
+ &len) == nullptr) {
+ ec = error::signature_generation_error::hmac_failed;
+ return {};
+ }
+ res.resize(len);
+ return res;
+ }
+ /**
+ * Check if signature is valid
+ * \param data The data to check signature against
+ * \param signature Signature provided by the jwt
+ * \param ec Filled with details about failure.
+ */
+ void verify(const std::string& data, const std::string& signature, std::error_code& ec) const {
+ ec.clear();
+ auto res = sign(data, ec);
+ if (ec) return;
+
+ bool matched = true;
+ for (size_t i = 0; i < std::min(res.size(), signature.size()); i++)
+ if (res[i] != signature[i]) matched = false;
+ if (res.size() != signature.size()) matched = false;
+ if (!matched) {
+ ec = error::signature_verification_error::invalid_signature;
+ return;
+ }
+ }
+ /**
+ * Returns the algorithm name provided to the constructor
+ * \return algorithm's name
+ */
+ std::string name() const { return alg_name; }
+
+ private:
+ /// HMAC secrect
+ const std::string secret;
+ /// HMAC hash generator
+ const EVP_MD* (*md)();
+ /// algorithm's name
+ const std::string alg_name;
+ };
+ /**
+ * \brief Base class for RSA family of algorithms
+ */
+ struct rsa {
+ /**
+ * Construct new rsa algorithm
+ * \param public_key RSA public key in PEM format
+ * \param private_key RSA private key or empty string if not available. If empty, signing will always fail.
+ * \param public_key_password Password to decrypt public key pem.
+ * \param private_key_password Password to decrypt private key pem.
+ * \param md Pointer to hash function
+ * \param name Name of the algorithm
+ */
+ rsa(const std::string& public_key, const std::string& private_key, const std::string& public_key_password,
+ const std::string& private_key_password, const EVP_MD* (*md)(), std::string name)
+ : md(md), alg_name(std::move(name)) {
+ if (!private_key.empty()) {
+ pkey = helper::load_private_key_from_string(private_key, private_key_password);
+ } else if (!public_key.empty()) {
+ pkey = helper::load_public_key_from_string(public_key, public_key_password);
+ } else
+ throw error::rsa_exception(error::rsa_error::no_key_provided);
+ }
+ /**
+ * Sign jwt data
+ * \param data The data to sign
+ * \param ec error_code filled with details on error
+ * \return RSA signature for the given data
+ */
+ std::string sign(const std::string& data, std::error_code& ec) const {
+ ec.clear();
+ auto ctx = helper::make_evp_md_ctx();
+ if (!ctx) {
+ ec = error::signature_generation_error::create_context_failed;
+ return {};
+ }
+ if (!EVP_SignInit(ctx.get(), md())) {
+ ec = error::signature_generation_error::signinit_failed;
+ return {};
+ }
+
+ std::string res(EVP_PKEY_size(pkey.get()), '\0');
+ unsigned int len = 0;
+
+ if (!EVP_SignUpdate(ctx.get(), data.data(), data.size())) {
+ ec = error::signature_generation_error::signupdate_failed;
+ return {};
+ }
+ if (EVP_SignFinal(ctx.get(), (unsigned char*)res.data(), &len, pkey.get()) == 0) {
+ ec = error::signature_generation_error::signfinal_failed;
+ return {};
+ }
+
+ res.resize(len);
+ return res;
+ }
+ /**
+ * Check if signature is valid
+ * \param data The data to check signature against
+ * \param signature Signature provided by the jwt
+ * \param ec Filled with details on failure
+ */
+ void verify(const std::string& data, const std::string& signature, std::error_code& ec) const {
+ ec.clear();
+ auto ctx = helper::make_evp_md_ctx();
+ if (!ctx) {
+ ec = error::signature_verification_error::create_context_failed;
+ return;
+ }
+ if (!EVP_VerifyInit(ctx.get(), md())) {
+ ec = error::signature_verification_error::verifyinit_failed;
+ return;
+ }
+ if (!EVP_VerifyUpdate(ctx.get(), data.data(), data.size())) {
+ ec = error::signature_verification_error::verifyupdate_failed;
+ return;
+ }
+ auto res = EVP_VerifyFinal(ctx.get(), reinterpret_cast(signature.data()),
+ static_cast(signature.size()), pkey.get());
+ if (res != 1) {
+ ec = error::signature_verification_error::verifyfinal_failed;
+ return;
+ }
+ }
+ /**
+ * Returns the algorithm name provided to the constructor
+ * \return algorithm's name
+ */
+ std::string name() const { return alg_name; }
+
+ private:
+ /// OpenSSL structure containing converted keys
+ helper::evp_pkey_handle pkey;
+ /// Hash generator
+ const EVP_MD* (*md)();
+ /// algorithm's name
+ const std::string alg_name;
+ };
+ /**
+ * \brief Base class for ECDSA family of algorithms
+ */
+ struct ecdsa {
+ /**
+ * Construct new ecdsa algorithm
+ *
+ * \param public_key ECDSA public key in PEM format
+ * \param private_key ECDSA private key or empty string if not available. If empty, signing will always fail
+ * \param public_key_password Password to decrypt public key pem
+ * \param private_key_password Password to decrypt private key pem
+ * \param md Pointer to hash function
+ * \param name Name of the algorithm
+ * \param siglen The bit length of the signature
+ */
+ ecdsa(const std::string& public_key, const std::string& private_key, const std::string& public_key_password,
+ const std::string& private_key_password, const EVP_MD* (*md)(), std::string name, size_t siglen)
+ : md(md), alg_name(std::move(name)), signature_length(siglen) {
+ if (!private_key.empty()) {
+ pkey = helper::load_private_ec_key_from_string(private_key, private_key_password);
+ check_private_key(pkey.get());
+ } else if (!public_key.empty()) {
+ pkey = helper::load_public_ec_key_from_string(public_key, public_key_password);
+ check_public_key(pkey.get());
+ } else {
+ throw error::ecdsa_exception(error::ecdsa_error::no_key_provided);
+ }
+ if (!pkey) throw error::ecdsa_exception(error::ecdsa_error::invalid_key);
+
+ size_t keysize = EVP_PKEY_bits(pkey.get());
+ if (keysize != signature_length * 4 && (signature_length != 132 || keysize != 521))
+ throw error::ecdsa_exception(error::ecdsa_error::invalid_key_size);
+ }
+
+ /**
+ * Sign jwt data
+ * \param data The data to sign
+ * \param ec error_code filled with details on error
+ * \return ECDSA signature for the given data
+ */
+ std::string sign(const std::string& data, std::error_code& ec) const {
+ ec.clear();
+ auto ctx = helper::make_evp_md_ctx();
+ if (!ctx) {
+ ec = error::signature_generation_error::create_context_failed;
+ return {};
+ }
+ if (!EVP_DigestSignInit(ctx.get(), nullptr, md(), nullptr, pkey.get())) {
+ ec = error::signature_generation_error::signinit_failed;
+ return {};
+ }
+ if (!EVP_DigestUpdate(ctx.get(), data.data(), data.size())) {
+ ec = error::signature_generation_error::digestupdate_failed;
+ return {};
+ }
+
+ size_t len = 0;
+ if (!EVP_DigestSignFinal(ctx.get(), nullptr, &len)) {
+ ec = error::signature_generation_error::signfinal_failed;
+ return {};
+ }
+ std::string res(len, '\0');
+ if (!EVP_DigestSignFinal(ctx.get(), (unsigned char*)res.data(), &len)) {
+ ec = error::signature_generation_error::signfinal_failed;
+ return {};
+ }
+
+ res.resize(len);
+ return der_to_p1363_signature(res, ec);
+ }
+
+ /**
+ * Check if signature is valid
+ * \param data The data to check signature against
+ * \param signature Signature provided by the jwt
+ * \param ec Filled with details on error
+ */
+ void verify(const std::string& data, const std::string& signature, std::error_code& ec) const {
+ ec.clear();
+ std::string der_signature = p1363_to_der_signature(signature, ec);
+ if (ec) { return; }
+
+ auto ctx = helper::make_evp_md_ctx();
+ if (!ctx) {
+ ec = error::signature_verification_error::create_context_failed;
+ return;
+ }
+ if (!EVP_DigestVerifyInit(ctx.get(), nullptr, md(), nullptr, pkey.get())) {
+ ec = error::signature_verification_error::verifyinit_failed;
+ return;
+ }
+ if (!EVP_DigestUpdate(ctx.get(), data.data(), data.size())) {
+ ec = error::signature_verification_error::verifyupdate_failed;
+ return;
+ }
+
+#if OPENSSL_VERSION_NUMBER < 0x10002000L
+ unsigned char* der_sig_data = reinterpret_cast(const_cast(der_signature.data()));
+#else
+ const unsigned char* der_sig_data = reinterpret_cast(der_signature.data());
+#endif
+ auto res =
+ EVP_DigestVerifyFinal(ctx.get(), der_sig_data, static_cast(der_signature.length()));
+ if (res == 0) {
+ ec = error::signature_verification_error::invalid_signature;
+ return;
+ }
+ if (res == -1) {
+ ec = error::signature_verification_error::verifyfinal_failed;
+ return;
+ }
+ }
+ /**
+ * Returns the algorithm name provided to the constructor
+ * \return algorithm's name
+ */
+ std::string name() const { return alg_name; }
+
+ private:
+ static void check_public_key(EVP_PKEY* pkey) {
+#ifdef JWT_OPENSSL_3_0
+ std::unique_ptr ctx(
+ EVP_PKEY_CTX_new_from_pkey(nullptr, pkey, nullptr), EVP_PKEY_CTX_free);
+ if (!ctx) { throw error::ecdsa_exception(error::ecdsa_error::create_context_failed); }
+ if (EVP_PKEY_public_check(ctx.get()) != 1) {
+ throw error::ecdsa_exception(error::ecdsa_error::invalid_key);
+ }
+#else
+ std::unique_ptr eckey(EVP_PKEY_get1_EC_KEY(pkey), EC_KEY_free);
+ if (!eckey) { throw error::ecdsa_exception(error::ecdsa_error::invalid_key); }
+ if (EC_KEY_check_key(eckey.get()) == 0) throw error::ecdsa_exception(error::ecdsa_error::invalid_key);
+#endif
+ }
+
+ static void check_private_key(EVP_PKEY* pkey) {
+#ifdef JWT_OPENSSL_3_0
+ std::unique_ptr ctx(
+ EVP_PKEY_CTX_new_from_pkey(nullptr, pkey, nullptr), EVP_PKEY_CTX_free);
+ if (!ctx) { throw error::ecdsa_exception(error::ecdsa_error::create_context_failed); }
+ if (EVP_PKEY_private_check(ctx.get()) != 1) {
+ throw error::ecdsa_exception(error::ecdsa_error::invalid_key);
+ }
+#else
+ std::unique_ptr eckey(EVP_PKEY_get1_EC_KEY(pkey), EC_KEY_free);
+ if (!eckey) { throw error::ecdsa_exception(error::ecdsa_error::invalid_key); }
+ if (EC_KEY_check_key(eckey.get()) == 0) throw error::ecdsa_exception(error::ecdsa_error::invalid_key);
+#endif
+ }
+
+ std::string der_to_p1363_signature(const std::string& der_signature, std::error_code& ec) const {
+ const unsigned char* possl_signature = reinterpret_cast(der_signature.data());
+ std::unique_ptr sig(
+ d2i_ECDSA_SIG(nullptr, &possl_signature, static_cast(der_signature.length())),
+ ECDSA_SIG_free);
+ if (!sig) {
+ ec = error::signature_generation_error::signature_decoding_failed;
+ return {};
+ }
+
+#ifdef JWT_OPENSSL_1_0_0
+
+ auto rr = helper::bn2raw(sig->r);
+ auto rs = helper::bn2raw(sig->s);
+#else
+ const BIGNUM* r;
+ const BIGNUM* s;
+ ECDSA_SIG_get0(sig.get(), &r, &s);
+ auto rr = helper::bn2raw(r);
+ auto rs = helper::bn2raw(s);
+#endif
+ if (rr.size() > signature_length / 2 || rs.size() > signature_length / 2)
+ throw std::logic_error("bignum size exceeded expected length");
+ rr.insert(0, signature_length / 2 - rr.size(), '\0');
+ rs.insert(0, signature_length / 2 - rs.size(), '\0');
+ return rr + rs;
+ }
+
+ std::string p1363_to_der_signature(const std::string& signature, std::error_code& ec) const {
+ ec.clear();
+ auto r = helper::raw2bn(signature.substr(0, signature.size() / 2));
+ 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 {};
+ }
+ ECDSA_SIG_set0(sig.get(), r.release(), s.release());
+ psig = sig.get();
+#endif
+
+ int length = i2d_ECDSA_SIG(psig, nullptr);
+ if (length < 0) {
+ ec = error::signature_verification_error::signature_encoding_failed;
+ return {};
+ }
+ std::string der_signature(length, '\0');
+ unsigned char* psbuffer = (unsigned char*)der_signature.data();
+ length = i2d_ECDSA_SIG(psig, &psbuffer);
+ if (length < 0) {
+ ec = error::signature_verification_error::signature_encoding_failed;
+ return {};
+ }
+ der_signature.resize(length);
+ return der_signature;
+ }
+
+ /// OpenSSL struct containing keys
+ helper::evp_pkey_handle pkey;
+ /// Hash generator function
+ const EVP_MD* (*md)();
+ /// algorithm's name
+ const std::string alg_name;
+ /// Length of the resulting signature
+ const size_t signature_length;
+ };
+
+#if !defined(JWT_OPENSSL_1_0_0) && !defined(JWT_OPENSSL_1_1_0)
+ /**
+ * \brief Base class for EdDSA family of algorithms
+ *
+ * https://tools.ietf.org/html/rfc8032
+ *
+ * The EdDSA algorithms were introduced in [OpenSSL v1.1.1](https://www.openssl.org/news/openssl-1.1.1-notes.html),
+ * so these algorithms are only available when building against this version or higher.
+ */
+ struct eddsa {
+ /**
+ * Construct new eddsa algorithm
+ * \param public_key EdDSA public key in PEM format
+ * \param private_key EdDSA private key or empty string if not available. If empty, signing will always
+ * fail.
+ * \param public_key_password Password to decrypt public key pem.
+ * \param private_key_password Password
+ * to decrypt private key pem.
+ * \param name Name of the algorithm
+ */
+ eddsa(const std::string& public_key, const std::string& private_key, const std::string& public_key_password,
+ const std::string& private_key_password, std::string name)
+ : alg_name(std::move(name)) {
+ if (!private_key.empty()) {
+ pkey = helper::load_private_key_from_string(private_key, private_key_password);
+ } else if (!public_key.empty()) {
+ pkey = helper::load_public_key_from_string(public_key, public_key_password);
+ } else
+ throw error::ecdsa_exception(error::ecdsa_error::load_key_bio_read);
+ }
+ /**
+ * Sign jwt data
+ * \param data The data to sign
+ * \param ec error_code filled with details on error
+ * \return EdDSA signature for the given data
+ */
+ std::string sign(const std::string& data, std::error_code& ec) const {
+ ec.clear();
+ auto ctx = helper::make_evp_md_ctx();
+ if (!ctx) {
+ ec = error::signature_generation_error::create_context_failed;
+ return {};
+ }
+ if (!EVP_DigestSignInit(ctx.get(), nullptr, nullptr, nullptr, pkey.get())) {
+ ec = error::signature_generation_error::signinit_failed;
+ return {};
+ }
+
+ size_t len = EVP_PKEY_size(pkey.get());
+ std::string res(len, '\0');
+
+// LibreSSL is the special kid in the block, as it does not support EVP_DigestSign.
+// OpenSSL on the otherhand does not support using EVP_DigestSignUpdate for eddsa, which is why we end up with this
+// mess.
+#if defined(LIBRESSL_VERSION_NUMBER) || defined(LIBWOLFSSL_VERSION_HEX)
+ ERR_clear_error();
+ if (EVP_DigestSignUpdate(ctx.get(), reinterpret_cast(data.data()), data.size()) !=
+ 1) {
+ std::cout << ERR_error_string(ERR_get_error(), NULL) << std::endl;
+ ec = error::signature_generation_error::signupdate_failed;
+ return {};
+ }
+ if (EVP_DigestSignFinal(ctx.get(), reinterpret_cast(&res[0]), &len) != 1) {
+ ec = error::signature_generation_error::signfinal_failed;
+ return {};
+ }
+#else
+ if (EVP_DigestSign(ctx.get(), reinterpret_cast(&res[0]), &len,
+ reinterpret_cast(data.data()), data.size()) != 1) {
+ ec = error::signature_generation_error::signfinal_failed;
+ return {};
+ }
+#endif
+
+ res.resize(len);
+ return res;
+ }
+
+ /**
+ * Check if signature is valid
+ * \param data The data to check signature against
+ * \param signature Signature provided by the jwt
+ * \param ec Filled with details on error
+ */
+ void verify(const std::string& data, const std::string& signature, std::error_code& ec) const {
+ ec.clear();
+ auto ctx = helper::make_evp_md_ctx();
+ if (!ctx) {
+ ec = error::signature_verification_error::create_context_failed;
+ return;
+ }
+ if (!EVP_DigestVerifyInit(ctx.get(), nullptr, nullptr, nullptr, pkey.get())) {
+ ec = error::signature_verification_error::verifyinit_failed;
+ return;
+ }
+// LibreSSL is the special kid in the block, as it does not support EVP_DigestVerify.
+// OpenSSL on the otherhand does not support using EVP_DigestVerifyUpdate for eddsa, which is why we end up with this
+// mess.
+#if defined(LIBRESSL_VERSION_NUMBER) || defined(LIBWOLFSSL_VERSION_HEX)
+ if (EVP_DigestVerifyUpdate(ctx.get(), reinterpret_cast(data.data()),
+ data.size()) != 1) {
+ ec = error::signature_verification_error::verifyupdate_failed;
+ return;
+ }
+ if (EVP_DigestVerifyFinal(ctx.get(), reinterpret_cast(signature.data()),
+ signature.size()) != 1) {
+ ec = error::signature_verification_error::verifyfinal_failed;
+ return;
+ }
+#else
+ auto res = EVP_DigestVerify(ctx.get(), reinterpret_cast(signature.data()),
+ signature.size(), reinterpret_cast(data.data()),
+ data.size());
+ if (res != 1) {
+ ec = error::signature_verification_error::verifyfinal_failed;
+ return;
+ }
+#endif
+ }
+ /**
+ * Returns the algorithm name provided to the constructor
+ * \return algorithm's name
+ */
+ std::string name() const { return alg_name; }
+
+ private:
+ /// OpenSSL struct containing keys
+ helper::evp_pkey_handle pkey;
+ /// algorithm's name
+ const std::string alg_name;
+ };
+#endif
+ /**
+ * \brief Base class for PSS-RSA family of algorithms
+ */
+ struct pss {
+ /**
+ * Construct new pss algorithm
+ * \param public_key RSA public key in PEM format
+ * \param private_key RSA private key or empty string if not available. If empty, signing will always fail.
+ * \param public_key_password Password to decrypt public key pem.
+ * \param private_key_password Password to decrypt private key pem.
+ * \param md Pointer to hash function
+ * \param name Name of the algorithm
+ */
+ pss(const std::string& public_key, const std::string& private_key, const std::string& public_key_password,
+ const std::string& private_key_password, const EVP_MD* (*md)(), std::string name)
+ : md(md), alg_name(std::move(name)) {
+ if (!private_key.empty()) {
+ pkey = helper::load_private_key_from_string(private_key, private_key_password);
+ } else if (!public_key.empty()) {
+ pkey = helper::load_public_key_from_string(public_key, public_key_password);
+ } else
+ throw error::rsa_exception(error::rsa_error::no_key_provided);
+ }
+
+ /**
+ * Sign jwt data
+ * \param data The data to sign
+ * \param ec error_code filled with details on error
+ * \return ECDSA signature for the given data
+ */
+ std::string sign(const std::string& data, std::error_code& ec) const {
+ ec.clear();
+ auto md_ctx = helper::make_evp_md_ctx();
+ if (!md_ctx) {
+ ec = error::signature_generation_error::create_context_failed;
+ return {};
+ }
+ EVP_PKEY_CTX* ctx = nullptr;
+ if (EVP_DigestSignInit(md_ctx.get(), &ctx, md(), nullptr, pkey.get()) != 1) {
+ ec = error::signature_generation_error::signinit_failed;
+ return {};
+ }
+ if (EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_PKCS1_PSS_PADDING) <= 0) {
+ ec = error::signature_generation_error::rsa_padding_failed;
+ return {};
+ }
+// wolfSSL does not require EVP_PKEY_CTX_set_rsa_pss_saltlen. The default behavior
+// sets the salt length to the hash length. Unlike OpenSSL which exposes this functionality.
+#ifndef LIBWOLFSSL_VERSION_HEX
+ if (EVP_PKEY_CTX_set_rsa_pss_saltlen(ctx, -1) <= 0) {
+ ec = error::signature_generation_error::set_rsa_pss_saltlen_failed;
+ return {};
+ }
+#endif
+ if (EVP_DigestUpdate(md_ctx.get(), data.data(), data.size()) != 1) {
+ ec = error::signature_generation_error::digestupdate_failed;
+ return {};
+ }
+
+ size_t size = EVP_PKEY_size(pkey.get());
+ std::string res(size, 0x00);
+ if (EVP_DigestSignFinal(
+ md_ctx.get(),
+ (unsigned char*)res.data(), // NOLINT(google-readability-casting) requires `const_cast`
+ &size) <= 0) {
+ ec = error::signature_generation_error::signfinal_failed;
+ return {};
+ }
+
+ return res;
+ }
+
+ /**
+ * Check if signature is valid
+ * \param data The data to check signature against
+ * \param signature Signature provided by the jwt
+ * \param ec Filled with error details
+ */
+ void verify(const std::string& data, const std::string& signature, std::error_code& ec) const {
+ ec.clear();
+
+ auto md_ctx = helper::make_evp_md_ctx();
+ if (!md_ctx) {
+ ec = error::signature_verification_error::create_context_failed;
+ return;
+ }
+ EVP_PKEY_CTX* ctx = nullptr;
+ if (EVP_DigestVerifyInit(md_ctx.get(), &ctx, md(), nullptr, pkey.get()) != 1) {
+ ec = error::signature_verification_error::verifyinit_failed;
+ return;
+ }
+ if (EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_PKCS1_PSS_PADDING) <= 0) {
+ ec = error::signature_generation_error::rsa_padding_failed;
+ return;
+ }
+// wolfSSL does not require EVP_PKEY_CTX_set_rsa_pss_saltlen. The default behavior
+// sets the salt length to the hash length. Unlike OpenSSL which exposes this functionality.
+#ifndef LIBWOLFSSL_VERSION_HEX
+ if (EVP_PKEY_CTX_set_rsa_pss_saltlen(ctx, -1) <= 0) {
+ ec = error::signature_verification_error::set_rsa_pss_saltlen_failed;
+ return;
+ }
+#endif
+ if (EVP_DigestUpdate(md_ctx.get(), data.data(), data.size()) != 1) {
+ ec = error::signature_verification_error::verifyupdate_failed;
+ return;
+ }
+
+ if (EVP_DigestVerifyFinal(md_ctx.get(), (unsigned char*)signature.data(), signature.size()) <= 0) {
+ ec = error::signature_verification_error::verifyfinal_failed;
+ return;
+ }
+ }
+ /**
+ * Returns the algorithm name provided to the constructor
+ * \return algorithm's name
+ */
+ std::string name() const { return alg_name; }
+
+ private:
+ /// OpenSSL structure containing keys
+ helper::evp_pkey_handle pkey;
+ /// Hash generator function
+ const EVP_MD* (*md)();
+ /// algorithm's name
+ const std::string alg_name;
+ };
+
+ /**
+ * HS256 algorithm
+ */
+ struct hs256 : public hmacsha {
+ /**
+ * Construct new instance of algorithm
+ * \param key HMAC signing key
+ */
+ explicit hs256(std::string key) : hmacsha(std::move(key), EVP_sha256, "HS256") {}
+ };
+ /**
+ * HS384 algorithm
+ */
+ struct hs384 : public hmacsha {
+ /**
+ * Construct new instance of algorithm
+ * \param key HMAC signing key
+ */
+ explicit hs384(std::string key) : hmacsha(std::move(key), EVP_sha384, "HS384") {}
+ };
+ /**
+ * HS512 algorithm
+ */
+ struct hs512 : public hmacsha {
+ /**
+ * Construct new instance of algorithm
+ * \param key HMAC signing key
+ */
+ explicit hs512(std::string key) : hmacsha(std::move(key), EVP_sha512, "HS512") {}
+ };
+ /**
+ * RS256 algorithm
+ */
+ struct rs256 : public rsa {
+ /**
+ * Construct new instance of algorithm
+ * \param public_key RSA public key in PEM format
+ * \param private_key RSA private key or empty string if not available. If empty, signing will always fail.
+ * \param public_key_password Password to decrypt public key pem.
+ * \param private_key_password Password to decrypt private key pem.
+ */
+ explicit rs256(const std::string& public_key, const std::string& private_key = "",
+ const std::string& public_key_password = "", const std::string& private_key_password = "")
+ : rsa(public_key, private_key, public_key_password, private_key_password, EVP_sha256, "RS256") {}
+ };
+ /**
+ * RS384 algorithm
+ */
+ struct rs384 : public rsa {
+ /**
+ * Construct new instance of algorithm
+ * \param public_key RSA public key in PEM format
+ * \param private_key RSA private key or empty string if not available. If empty, signing will always fail.
+ * \param public_key_password Password to decrypt public key pem.
+ * \param private_key_password Password to decrypt private key pem.
+ */
+ explicit rs384(const std::string& public_key, const std::string& private_key = "",
+ const std::string& public_key_password = "", const std::string& private_key_password = "")
+ : rsa(public_key, private_key, public_key_password, private_key_password, EVP_sha384, "RS384") {}
+ };
+ /**
+ * RS512 algorithm
+ */
+ struct rs512 : public rsa {
+ /**
+ * Construct new instance of algorithm
+ * \param public_key RSA public key in PEM format
+ * \param private_key RSA private key or empty string if not available. If empty, signing will always fail.
+ * \param public_key_password Password to decrypt public key pem.
+ * \param private_key_password Password to decrypt private key pem.
+ */
+ explicit rs512(const std::string& public_key, const std::string& private_key = "",
+ const std::string& public_key_password = "", const std::string& private_key_password = "")
+ : rsa(public_key, private_key, public_key_password, private_key_password, EVP_sha512, "RS512") {}
+ };
+ /**
+ * ES256 algorithm
+ */
+ struct es256 : public ecdsa {
+ /**
+ * Construct new instance of algorithm
+ * \param public_key ECDSA public key in PEM format
+ * \param private_key ECDSA private key or empty string if not available. If empty, signing will always
+ * fail.
+ * \param public_key_password Password to decrypt public key pem.
+ * \param private_key_password Password
+ * to decrypt private key pem.
+ */
+ explicit es256(const std::string& public_key, const std::string& private_key = "",
+ const std::string& public_key_password = "", const std::string& private_key_password = "")
+ : ecdsa(public_key, private_key, public_key_password, private_key_password, EVP_sha256, "ES256", 64) {}
+ };
+ /**
+ * ES384 algorithm
+ */
+ struct es384 : public ecdsa {
+ /**
+ * Construct new instance of algorithm
+ * \param public_key ECDSA public key in PEM format
+ * \param private_key ECDSA private key or empty string if not available. If empty, signing will always
+ * fail.
+ * \param public_key_password Password to decrypt public key pem.
+ * \param private_key_password Password
+ * to decrypt private key pem.
+ */
+ explicit es384(const std::string& public_key, const std::string& private_key = "",
+ const std::string& public_key_password = "", const std::string& private_key_password = "")
+ : ecdsa(public_key, private_key, public_key_password, private_key_password, EVP_sha384, "ES384", 96) {}
+ };
+ /**
+ * ES512 algorithm
+ */
+ struct es512 : public ecdsa {
+ /**
+ * Construct new instance of algorithm
+ * \param public_key ECDSA public key in PEM format
+ * \param private_key ECDSA private key or empty string if not available. If empty, signing will always
+ * fail.
+ * \param public_key_password Password to decrypt public key pem.
+ * \param private_key_password Password
+ * to decrypt private key pem.
+ */
+ explicit es512(const std::string& public_key, const std::string& private_key = "",
+ const std::string& public_key_password = "", const std::string& private_key_password = "")
+ : ecdsa(public_key, private_key, public_key_password, private_key_password, EVP_sha512, "ES512", 132) {}
+ };
+ /**
+ * ES256K algorithm
+ */
+ struct es256k : public ecdsa {
+ /**
+ * Construct new instance of algorithm
+ * \param public_key ECDSA public key in PEM format
+ * \param private_key ECDSA private key or empty string if not available. If empty, signing will always
+ * fail.
+ * \param public_key_password Password to decrypt public key pem.
+ * \param private_key_password Password to decrypt private key pem.
+ */
+ explicit es256k(const std::string& public_key, const std::string& private_key = "",
+ const std::string& public_key_password = "", const std::string& private_key_password = "")
+ : ecdsa(public_key, private_key, public_key_password, private_key_password, EVP_sha256, "ES256K", 64) {}
+ };
+
+#if !defined(JWT_OPENSSL_1_0_0) && !defined(JWT_OPENSSL_1_1_0)
+ /**
+ * Ed25519 algorithm
+ *
+ * https://en.wikipedia.org/wiki/EdDSA#Ed25519
+ *
+ * Requires at least OpenSSL 1.1.1.
+ */
+ struct ed25519 : public eddsa {
+ /**
+ * Construct new instance of algorithm
+ * \param public_key Ed25519 public key in PEM format
+ * \param private_key Ed25519 private key or empty string if not available. If empty, signing will always
+ * fail.
+ * \param public_key_password Password to decrypt public key pem.
+ * \param private_key_password Password
+ * to decrypt private key pem.
+ */
+ explicit ed25519(const std::string& public_key, const std::string& private_key = "",
+ const std::string& public_key_password = "", const std::string& private_key_password = "")
+ : eddsa(public_key, private_key, public_key_password, private_key_password, "EdDSA") {}
+ };
+
+ /**
+ * Ed448 algorithm
+ *
+ * https://en.wikipedia.org/wiki/EdDSA#Ed448
+ *
+ * Requires at least OpenSSL 1.1.1.
+ */
+ struct ed448 : public eddsa {
+ /**
+ * Construct new instance of algorithm
+ * \param public_key Ed448 public key in PEM format
+ * \param private_key Ed448 private key or empty string if not available. If empty, signing will always
+ * fail.
+ * \param public_key_password Password to decrypt public key pem.
+ * \param private_key_password Password
+ * to decrypt private key pem.
+ */
+ explicit ed448(const std::string& public_key, const std::string& private_key = "",
+ const std::string& public_key_password = "", const std::string& private_key_password = "")
+ : eddsa(public_key, private_key, public_key_password, private_key_password, "EdDSA") {}
+ };
+#endif
+
+ /**
+ * PS256 algorithm
+ */
+ struct ps256 : public pss {
+ /**
+ * Construct new instance of algorithm
+ * \param public_key RSA public key in PEM format
+ * \param private_key RSA private key or empty string if not available. If empty, signing will always fail.
+ * \param public_key_password Password to decrypt public key pem.
+ * \param private_key_password Password to decrypt private key pem.
+ */
+ explicit ps256(const std::string& public_key, const std::string& private_key = "",
+ const std::string& public_key_password = "", const std::string& private_key_password = "")
+ : pss(public_key, private_key, public_key_password, private_key_password, EVP_sha256, "PS256") {}
+ };
+ /**
+ * PS384 algorithm
+ */
+ struct ps384 : public pss {
+ /**
+ * Construct new instance of algorithm
+ * \param public_key RSA public key in PEM format
+ * \param private_key RSA private key or empty string if not available. If empty, signing will always fail.
+ * \param public_key_password Password to decrypt public key pem.
+ * \param private_key_password Password to decrypt private key pem.
+ */
+ explicit ps384(const std::string& public_key, const std::string& private_key = "",
+ const std::string& public_key_password = "", const std::string& private_key_password = "")
+ : pss(public_key, private_key, public_key_password, private_key_password, EVP_sha384, "PS384") {}
+ };
+ /**
+ * PS512 algorithm
+ */
+ struct ps512 : public pss {
+ /**
+ * Construct new instance of algorithm
+ * \param public_key RSA public key in PEM format
+ * \param private_key RSA private key or empty string if not available. If empty, signing will always fail.
+ * \param public_key_password Password to decrypt public key pem.
+ * \param private_key_password Password to decrypt private key pem.
+ */
+ explicit ps512(const std::string& public_key, const std::string& private_key = "",
+ const std::string& public_key_password = "", const std::string& private_key_password = "")
+ : pss(public_key, private_key, public_key_password, private_key_password, EVP_sha512, "PS512") {}
+ };
+ } // namespace algorithm
+
+ /**
+ * \brief JSON Abstractions for working with any library
+ */
+ namespace json {
+ /**
+ * \brief Generic JSON types used in JWTs
+ *
+ * This enum is to abstract the third party underlying types
+ */
+ enum class type { boolean, integer, number, string, array, object };
+ } // namespace json
+
+ namespace details {
+#ifdef __cpp_lib_void_t
+ template
+ using void_t = std::void_t;
+#else
+ // https://en.cppreference.com/w/cpp/types/void_t
+ template
+ struct make_void {
+ using type = void;
+ };
+
+ template
+ using void_t = typename make_void::type;
+#endif
+
+#ifdef __cpp_lib_experimental_detect
+ template class _Op, typename... _Args>
+ using is_detected = std::experimental::is_detected<_Op, _Args...>;
+#else
+ struct nonesuch {
+ nonesuch() = delete;
+ ~nonesuch() = delete;
+ nonesuch(nonesuch const&) = delete;
+ nonesuch(nonesuch const&&) = delete;
+ void operator=(nonesuch const&) = delete;
+ void operator=(nonesuch&&) = delete;
+ };
+
+ // https://en.cppreference.com/w/cpp/experimental/is_detected
+ template class Op, class... Args>
+ struct detector {
+ using value = std::false_type;
+ using type = Default;
+ };
+
+ template class Op, class... Args>
+ struct detector>, Op, Args...> {
+ using value = std::true_type;
+ using type = Op;
+ };
+
+ template class Op, class... Args>
+ using is_detected = typename detector::value;
+#endif
+
+ template
+ using is_signature = typename std::is_same;
+
+ template class Op, typename Signature>
+ struct is_function_signature_detected {
+ using type = Op;
+ static constexpr auto value = is_detected::value && std::is_function::value &&
+ is_signature::value;
+ };
+
+ template
+ struct supports_get_type {
+ template
+ using get_type_t = decltype(T::get_type);
+
+ static constexpr auto value =
+ is_function_signature_detected::value;
+
+ // Internal assertions for better feedback
+ static_assert(value, "traits implementation must provide `jwt::json::type get_type(const value_type&)`");
+ };
+
+#define JWT_CPP_JSON_TYPE_TYPE(TYPE) json_##TYPE_type
+#define JWT_CPP_AS_TYPE_T(TYPE) as_##TYPE_t
+#define JWT_CPP_SUPPORTS_AS(TYPE) \
+ template \
+ struct supports_as_##TYPE { \
+ template \
+ using JWT_CPP_AS_TYPE_T(TYPE) = decltype(T::as_##TYPE); \
+ \
+ static constexpr auto value = \
+ is_function_signature_detected::value; \
+ \
+ static_assert(value, "traits implementation must provide `" #TYPE "_type as_" #TYPE "(const value_type&)`"); \
+ }
+
+ JWT_CPP_SUPPORTS_AS(object);
+ JWT_CPP_SUPPORTS_AS(array);
+ JWT_CPP_SUPPORTS_AS(string);
+ JWT_CPP_SUPPORTS_AS(number);
+ JWT_CPP_SUPPORTS_AS(integer);
+ JWT_CPP_SUPPORTS_AS(boolean);
+
+#undef JWT_CPP_JSON_TYPE_TYPE
+#undef JWT_CPP_AS_TYPE_T
+#undef JWT_CPP_SUPPORTS_AS
+
+ template
+ struct is_valid_traits {
+ static constexpr auto value =
+ supports_get_type::value &&
+ supports_as_object::value &&
+ supports_as_array::value &&
+ supports_as_string::value &&
+ supports_as_number::value &&
+ supports_as_integer::value &&
+ supports_as_boolean::value;
+ };
+
+ template
+ struct is_valid_json_value {
+ static constexpr auto value =
+ std::is_default_constructible::value &&
+ std::is_constructible::value && // a more generic is_copy_constructible
+ std::is_move_constructible::value && std::is_assignable::value &&
+ std::is_copy_assignable::value && std::is_move_assignable::value;
+ // TODO(prince-chrismc): Stream operators
+ };
+
+ // https://stackoverflow.com/a/53967057/8480874
+ template
+ struct is_iterable : std::false_type {};
+
+ template
+ struct is_iterable())), decltype(std::end(std::declval())),
+#if __cplusplus > 201402L
+ decltype(std::cbegin(std::declval())), decltype(std::cend(std::declval()))
+#else
+ decltype(std::begin(std::declval())),
+ decltype(std::end(std::declval()))
+#endif
+ >> : std::true_type {
+ };
+
+#if __cplusplus > 201703L
+ template
+ inline constexpr bool is_iterable_v = is_iterable::value;
+#endif
+
+ template
+ using is_count_signature = typename std::is_integral().count(
+ std::declval()))>;
+
+ template
+ struct is_subcription_operator_signature : std::false_type {};
+
+ template
+ struct is_subcription_operator_signature<
+ object_type, string_type,
+ void_t().operator[](std::declval()))>> : std::true_type {
+ // TODO(prince-chrismc): I am not convienced this is meaningful anymore
+ static_assert(
+ value,
+ "object_type must implementate the subscription operator '[]' taking string_type as an argument");
+ };
+
+ template
+ using is_at_const_signature =
+ typename std::is_same().at(std::declval())),
+ const value_type&>;
+
+ template
+ struct is_valid_json_object {
+ template
+ using mapped_type_t = typename T::mapped_type;
+ template
+ using key_type_t = typename T::key_type;
+ template
+ using iterator_t = typename T::iterator;
+ template
+ using const_iterator_t = typename T::const_iterator;
+
+ static constexpr auto value =
+ std::is_constructible::value &&
+ is_detected::value &&
+ std::is_same::value &&
+ is_detected::value &&
+ (std::is_same::value ||
+ std::is_constructible::value) &&
+ is_detected::value && is_detected::value &&
+ is_iterable::value && is_count_signature::value &&
+ is_subcription_operator_signature::value &&
+ is_at_const_signature::value;
+ };
+
+ template
+ struct is_valid_json_array {
+ template
+ using value_type_t = typename T::value_type;
+
+ 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
+ struct is_valid_json_types {
+ // Internal assertions for better feedback
+ static_assert(is_valid_json_value::value,
+ "value_type must meet basic requirements, default constructor, copyable, moveable");
+ static_assert(is_valid_json_object::value,
+ "object_type must be a string_type to value_type container");
+ static_assert(is_valid_json_array::value,
+ "array_type must be a container of value_type");
+
+ static constexpr auto value = is_valid_json_value