From 688d890c307b4b36fb4bdfa2e5e827e546f88dca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9F=D1=80=D0=B5=D0=BF=D0=BE=D0=B4=D0=BE=D0=B1=D0=BD?= =?UTF-8?q?=D1=8B=D0=B9=20=D0=90=D0=BB=D0=B5=D0=BD?= Date: Sun, 19 Feb 2023 20:04:14 +0300 Subject: [PATCH] Updated jwt-cpp library. --- CMakeLists.txt | 5 + src/lib/jwt-cpp/base.h | 387 +- src/lib/jwt-cpp/jwt.h | 3665 +++++++++++++---- src/lib/jwt-cpp/traits/boost-json/defaults.h | 88 + src/lib/jwt-cpp/traits/boost-json/traits.h | 80 + .../traits/danielaparker-jsoncons/defaults.h | 88 + .../traits/danielaparker-jsoncons/traits.h | 123 + src/lib/jwt-cpp/traits/defaults.h.mustache | 90 + .../jwt-cpp/traits/kazuho-picojson/defaults.h | 84 + .../jwt-cpp/traits/kazuho-picojson/traits.h | 76 + .../jwt-cpp/traits/nlohmann-json/defaults.h | 88 + src/lib/jwt-cpp/traits/nlohmann-json/traits.h | 77 + src/lib/picojson/picojson.h | 1706 ++++---- 13 files changed, 4732 insertions(+), 1825 deletions(-) create mode 100644 src/lib/jwt-cpp/traits/boost-json/defaults.h create mode 100644 src/lib/jwt-cpp/traits/boost-json/traits.h create mode 100644 src/lib/jwt-cpp/traits/danielaparker-jsoncons/defaults.h create mode 100644 src/lib/jwt-cpp/traits/danielaparker-jsoncons/traits.h create mode 100644 src/lib/jwt-cpp/traits/defaults.h.mustache create mode 100644 src/lib/jwt-cpp/traits/kazuho-picojson/defaults.h create mode 100644 src/lib/jwt-cpp/traits/kazuho-picojson/traits.h create mode 100644 src/lib/jwt-cpp/traits/nlohmann-json/defaults.h create mode 100644 src/lib/jwt-cpp/traits/nlohmann-json/traits.h diff --git a/CMakeLists.txt b/CMakeLists.txt index da703d2..4d22574 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -113,6 +113,11 @@ file(GLOB lib_files ${lib_files} ${PROJECT_LIB_DIR}/picojson/*.h) include_directories(${PROJECT_LIB_DIR}/jwt-cpp) file(GLOB lib_files ${lib_files} ${PROJECT_LIB_DIR}/jwt-cpp/*.h) +set(JWT_JSON_TRAITS_OPTIONS boost-json danielaparker-jsoncons kazuho-picojson nlohmann-json) +foreach(traits ${JWT_JSON_TRAITS_OPTIONS}) + list(APPEND lib_files ${PROJECT_LIB_DIR}/jwt-cpp/traits/${traits}/defaults.h ${PROJECT_LIB_DIR}/jwt-cpp/traits/${traits}/traits.h) +endforeach() + # Find boost #------------------------------------------------------------------------------ find_package(Boost 1.62.0 REQUIRED COMPONENTS) diff --git a/src/lib/jwt-cpp/base.h b/src/lib/jwt-cpp/base.h index 375e0eb..cef493d 100644 --- a/src/lib/jwt-cpp/base.h +++ b/src/lib/jwt-cpp/base.h @@ -1,6 +1,11 @@ -#pragma once -#include +#ifndef JWT_CPP_BASE_H +#define JWT_CPP_BASE_H + +#include #include +#include +#include +#include #ifdef __has_cpp_attribute #if __has_cpp_attribute(fallthrough) @@ -13,198 +18,252 @@ #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 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; - }; + return data; + } static const std::string& fill() { - static std::string fill = "="; + static 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 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; - }; + return data; + } static const std::string& fill() { - static std::string fill = "%3d"; + static 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::initializer_list& fill() { + static std::initializer_list fill{"%3D", "%3d"}; + return fill; + } + }; + } // namespace helper - 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()); - } + 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"); } - 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; + return std::distance(alphabet.cbegin(), itr); + } + } // namespace alphabet - // clear incomplete bytes - size_t fast_size = size - size % 3; - for (size_t i = 0; i < fast_size;) { - uint32_t octet_a = (unsigned char)bin[i++]; - uint32_t octet_b = (unsigned char)bin[i++]; - uint32_t octet_c = (unsigned char)bin[i++]; + /** + * \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; - 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 ? (unsigned char)bin[fast_size++] : 0; - uint32_t octet_b = fast_size < size ? (unsigned char)bin[fast_size++] : 0; - uint32_t octet_c = fast_size < size ? (unsigned char)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; - } - - 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 break; - } - - if ((size + fill_cnt) % 4 != 0) - throw std::runtime_error("Invalid input"); - - size_t out_size = size / 4 * 3; - std::string res; - res.reserve(out_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); - } - 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++); - - uint32_t triple = (sextet_a << 3 * 6) - + (sextet_b << 2 * 6) - + (sextet_c << 1 * 6) - + (sextet_d << 0 * 6); - - res += (triple >> 2 * 8) & 0xFF; - res += (triple >> 1 * 8) & 0xFF; - res += (triple >> 0 * 8) & 0xFF; - } - - if (fill_cnt == 0) - return res; - - uint32_t triple = (get_sextet(fast_size) << 3 * 6) - + (get_sextet(fast_size + 1) << 2 * 6); - - switch (fill_cnt) { - case 1: - triple |= (get_sextet(fast_size + 2) << 1 * 6); - res += (triple >> 2 * 8) & 0xFF; - res += (triple >> 1 * 8) & 0xFF; - break; - case 2: - res += (triple >> 2 * 8) & 0xFF; - break; - default: - break; - } - - return res; - } - - static std::string pad(const std::string& base, const std::string& fill) { - std::string padding; - switch (base.size() % 4) { + switch (mod) { case 1: - padding += fill; - JWT_FALLTHROUGH; - case 2: - padding += fill; - JWT_FALLTHROUGH; - case 3: - padding += fill; - JWT_FALLTHROUGH; - default: + 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; } - return base + padding; - } + 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"); - static std::string trim(const std::string& base, const std::string& fill) { - auto pos = base.find(fill); - return base.substr(0, pos); + 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/lib/jwt-cpp/jwt.h b/src/lib/jwt-cpp/jwt.h index eb88fa3..256a098 100644 --- a/src/lib/jwt-cpp/jwt.h +++ b/src/lib/jwt-cpp/jwt.h @@ -1,171 +1,886 @@ -#pragma once +#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.h" +#endif + +#ifndef JWT_DISABLE_BASE64 #include "base.h" -#include -#include -#include -#include -#include +#endif + +#include +#include +#include #include #include #include -#include -#include +#include +#include -//If openssl version less than 1.1 -#if OPENSSL_VERSION_NUMBER < 0x10100000L -#define OPENSSL10 +#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; - struct signature_exception : public std::runtime_error { - signature_exception() - : std::runtime_error("Signature failed.") - {} - explicit signature_exception(const std::string& msg) - : std::runtime_error(msg) - {} - explicit signature_exception(const char* msg) - : std::runtime_error(msg) - {} - }; + /** + * \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; + }; + struct token_expired_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; + } - struct signature_verification_exception : public signature_exception { - signature_verification_exception() - : signature_exception("Signature verification failed.") - {} - explicit signature_verification_exception(const std::string& msg) - : signature_exception(msg) - {} - explicit signature_verification_exception(const char* msg) - : signature_exception(msg) - {} - }; + 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; + } - struct signature_generation_exception : public signature_exception { - signature_generation_exception() - : signature_exception("Signature generation failed.") - {} - explicit signature_generation_exception(const std::string& msg) - : signature_exception(msg) - {} - explicit signature_generation_exception(const char* msg) - : signature_exception(msg) - {} - }; + inline std::error_code make_error_code(ecdsa_error e) { return {static_cast(e), ecdsa_error_category()}; } - struct rsa_exception : public signature_exception { - explicit rsa_exception(const std::string& msg) - : signature_exception(msg) - {} - explicit rsa_exception(const char* msg) - : signature_exception(msg) - {} - }; + /** + * \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; + } - struct ecdsa_exception : public signature_exception { - explicit ecdsa_exception(const std::string& msg) - : signature_exception(msg) - {} - explicit ecdsa_exception(const char* msg) - : signature_exception(msg) - {} - }; + inline std::error_code make_error_code(signature_verification_error e) { + return {static_cast(e), signature_verification_error_category()}; + } - struct token_verification_exception : public std::runtime_error { - token_verification_exception() - : std::runtime_error("Verification failed.") - {} - explicit token_verification_exception(const std::string& msg) - : std::runtime_error("Verification failed: " + msg) - {} - }; + /** + * \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; + } - struct token_expired_exception : public token_verification_exception { - token_expired_exception() - : token_verification_exception("Token expired.") - {} - explicit token_expired_exception(const std::string& msg) - : token_verification_exception("Token expired (" + msg + ").") - {} - }; + 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, + audience_missmatch, + issuer_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::audience_missmatch: + return "token doesn't contain the required audience"; + case token_verification_error::issuer_missmatch: + return "token doesn't contain the required issuer"; + 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()}; + } + + enum class token_expired_error { + ok = 0, + token_expired + }; + /** + * \brief Error category for token expired error + */ + inline std::error_category& token_expired_error_category() { + class token_expired_error_cat : public std::error_category { + public: + const char* name() const noexcept override { return "token_expired_error"; }; + std::string message(int ev) const override { + switch (static_cast(ev)) { + case token_expired_error::ok: return "no error"; + case token_expired_error::token_expired: return "token expired"; + default: return "unknown token expired error"; + } + } + }; + static token_expired_error_cat cat = {}; + return cat; + } + + inline std::error_code make_error_code(token_expired_error e) { + return {static_cast(e), token_expired_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); + if (ec.category() == token_expired_error_category()) throw token_expired_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 {}; + 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 { - inline - std::string extract_pubkey_from_cert(const std::string& certstr, const std::string& pw = "") { -#if OPENSSL_VERSION_NUMBER <= 0x10100003L - std::unique_ptr certbio(BIO_new_mem_buf(const_cast(certstr.data()), 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); + /** + * \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); } - std::unique_ptr cert(PEM_read_bio_X509(certbio.get(), nullptr, nullptr, const_cast(pw.c_str())), X509_free); - if (!cert) throw rsa_exception("Error loading cert into memory"); + 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 ocurred) + */ + 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) throw rsa_exception("Error getting public key from certificate"); - if(!PEM_write_bio_PUBKEY(keybio.get(), key.get())) throw rsa_exception("Error writing public key data in PEM format"); + 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) throw rsa_exception("Failed to convert pubkey to pem"); - std::string res(ptr, len); + 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; } - inline - std::shared_ptr load_public_key_from_string(const std::string& key, const std::string& password = "") { - std::unique_ptr pubkey_bio(BIO_new(BIO_s_mem()), BIO_free_all); - if(key.substr(0, 27) == "-----BEGIN CERTIFICATE-----") { - auto epkey = helper::extract_pubkey_from_cert(key, password); + /** + * \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 occures) + */ + 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 decodedStr = decode(cert_base64_der_str); + auto c_str = reinterpret_cast(decodedStr.c_str()); + + std::unique_ptr cert( + d2i_X509(NULL, &c_str, static_cast(decodedStr.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 + * \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; + } +#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 occures) + */ + 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 occures) + */ + 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) - throw rsa_exception("failed to load public key: bio_write failed"); + 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) - throw rsa_exception("failed to load public key: bio_write failed"); + if (BIO_write(pubkey_bio.get(), key.data(), len) != len) { + ec = error::rsa_error::load_key_bio_write; + return {}; + } } - - std::shared_ptr pkey(PEM_read_bio_PUBKEY(pubkey_bio.get(), nullptr, nullptr, (void*)password.c_str()), EVP_PKEY_free); - if (!pkey) - throw rsa_exception("failed to load public key: PEM_read_bio_PUBKEY failed:" + std::string(ERR_error_string(ERR_get_error(), NULL))); + + 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; } - inline - std::shared_ptr load_private_key_from_string(const std::string& key, const std::string& password = "") { - std::unique_ptr privkey_bio(BIO_new(BIO_s_mem()), BIO_free_all); + /** + * \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 occures) + */ + 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) - throw rsa_exception("failed to load private key: bio_write failed"); - std::shared_ptr pkey(PEM_read_bio_PrivateKey(privkey_bio.get(), nullptr, nullptr, const_cast(password.c_str())), EVP_PKEY_free); - if (!pkey) - throw rsa_exception("failed to load private key: PEM_read_bio_PrivateKey failed"); + 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 occures) + */ + 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 occures) + */ + 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 bn2raw(BIGNUM* bn) +#ifdef JWT_OPENSSL_1_0_0 + std::string + bn2raw(BIGNUM* bn) #else - static std::string bn2raw(const BIGNUM* bn) + std::string + bn2raw(const BIGNUM* bn) #endif { - std::string res; - res.resize(BN_num_bytes(bn)); - BN_bn2bin(bn, (unsigned char*)res.data()); + std::string res(BN_num_bytes(bn), '\0'); + BN_bn2bin(bn, (unsigned char*)res.data()); // NOLINT(google-readability-casting) requires `const_cast` return res; } /** @@ -173,79 +888,79 @@ namespace jwt { * \param raw String to convert * \return BIGNUM representation */ - inline - static std::unique_ptr raw2bn(const std::string& raw) { - return std::unique_ptr(BN_bin2bn((const unsigned char*)raw.data(), static_cast(raw.size()), nullptr), BN_free); + 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 { /** - * "base" algorithm. - * + * \brief "none" algorithm. + * * Returns and empty signature and checks if the given signature is empty. */ - struct algorithm { - public: - - algorithm(): md(nullptr), alg_name("none") { - - } - - algorithm(const EVP_MD*(*md)(), std::string name): md(md), alg_name(std::move(name)) { - - } - /// Return an empty string - virtual std::string sign(const std::string& data) const { - return ""; - } - /// Check if the given signature is empty. JWT's with "none" algorithm should not contain a signature. - virtual void verify(const std::string& data, const std::string& signature) const { - if (!signature.empty()) - throw signature_verification_exception(); + struct none { + /** + * \brief Return an empty string + */ + std::string sign(const std::string& /*unused*/, std::error_code& ec) const { + ec.clear(); + return {}; } - /** - * Returns the algorithm name provided to the constructor - * \return Algorithmname - */ - const std::string& name() const { - return alg_name; - } - - protected: - /// Hash generator function - const EVP_MD*(*md)(); - - private: - /// Algorithmname - const std::string alg_name; + /** + * \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"; } }; /** - * Base class for HMAC family of algorithms + * \brief Base class for HMAC family of algorithms */ - struct hmacsha: public algorithm { - public: + 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)(), const std::string& name) - : algorithm(md, name), secret(std::move(key)) - {} + 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 - * \throws signature_generation_exception */ - std::string sign(const std::string& data) const override { - std::string res; - res.resize(static_cast(EVP_MAX_MD_SIZE)); + 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()), (const unsigned char*)data.data(), static_cast(data.size()), (unsigned char*)res.data(), &len) == nullptr) - throw signature_generation_exception(); + 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; } @@ -253,77 +968,88 @@ namespace jwt { * Check if signature is valid * \param data The data to check signature against * \param signature Signature provided by the jwt - * \throws signature_verification_exception If the provided signature does not match + * \param ec Filled with details about failure. */ - void verify(const std::string& data, const std::string& signature) const override { - try { - auto res = sign(data); - 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) - throw signature_verification_exception(); - } - catch (const signature_generation_exception&) { - throw signature_verification_exception(); + 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; }; /** - * Base class for RSA family of algorithms + * \brief Base class for RSA family of algorithms */ - struct rsa: public algorithm { - public: + 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 privat_key_password Password to decrypt private 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)(), const std::string& name) - : algorithm(md, name) - { + 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()) { + } else if (!public_key.empty()) { pkey = helper::load_public_key_from_string(public_key, public_key_password); } else - throw rsa_exception("at least one of public or private key need to be present"); + 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 - * \throws signature_generation_exception */ - std::string sign(const std::string& data) const override { -#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 - if (!ctx) - throw signature_generation_exception("failed to create signature: could not create context"); - if (!EVP_SignInit(ctx.get(), md())) - throw signature_generation_exception("failed to create signature: SignInit failed"); + 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; - res.resize(EVP_PKEY_size(pkey.get())); + std::string res(EVP_PKEY_size(pkey.get()), '\0'); unsigned int len = 0; - if (!EVP_SignUpdate(ctx.get(), data.data(), data.size())) - throw signature_generation_exception(); - if (!EVP_SignFinal(ctx.get(), (unsigned char*)res.data(), &len, pkey.get())) - throw signature_generation_exception(); + 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; @@ -332,264 +1058,526 @@ namespace jwt { * Check if signature is valid * \param data The data to check signature against * \param signature Signature provided by the jwt - * \throws signature_verification_exception If the provided signature does not match + * \param ec Filled with details on failure */ - void verify(const std::string& data, const std::string& signature) const override { -#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 - if (!ctx) - throw signature_verification_exception("failed to verify signature: could not create context"); - if (!EVP_VerifyInit(ctx.get(), md())) - throw signature_verification_exception("failed to verify signature: VerifyInit failed"); - if (!EVP_VerifyUpdate(ctx.get(), data.data(), data.size())) - throw signature_verification_exception("failed to verify signature: VerifyUpdate failed"); - auto res = EVP_VerifyFinal(ctx.get(), (const unsigned char*)signature.data(), static_cast(signature.size()), pkey.get()); - if (res != 1) - throw signature_verification_exception("evp verify final failed: " + std::to_string(res) + " " + ERR_error_string(ERR_get_error(), NULL)); + 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 - std::shared_ptr pkey; + helper::evp_pkey_handle pkey; + /// Hash generator + const EVP_MD* (*md)(); + /// algorithm's name + const std::string alg_name; }; /** - * Base class for ECDSA family of algorithms + * \brief Base class for ECDSA family of algorithms */ - struct ecdsa: public algorithm { - public: + 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 privat_key_password Password to decrypt private key pem. + * \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)(), const std::string& name, size_t siglen) - : algorithm(md, name), signature_length(siglen) - { - if (!public_key.empty()) { - std::unique_ptr pubkey_bio(BIO_new(BIO_s_mem()), BIO_free_all); - 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("failed to load public key: bio_write failed"); - } else { - const int len = static_cast(public_key.size()); - if (BIO_write(pubkey_bio.get(), public_key.data(), len) != len) - throw ecdsa_exception("failed to load public key: bio_write failed"); - } - - pkey.reset(PEM_read_bio_EC_PUBKEY(pubkey_bio.get(), nullptr, nullptr, (void*)public_key_password.c_str()), EC_KEY_free); - if (!pkey) - throw ecdsa_exception("failed to load public key: PEM_read_bio_EC_PUBKEY failed:" + std::string(ERR_error_string(ERR_get_error(), NULL))); - 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("invalid key size"); - } - + 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()) { - std::unique_ptr privkey_bio(BIO_new(BIO_s_mem()), BIO_free_all); - const int len = static_cast(private_key.size()); - if (BIO_write(privkey_bio.get(), private_key.data(), len) != len) - throw ecdsa_exception("failed to load private key: bio_write failed"); - 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("failed to load private key: PEM_read_bio_ECPrivateKey failed"); - 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("invalid key size"); + 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 ecdsa_exception("at least one of public or private key need to be present"); + if (!pkey) throw error::ecdsa_exception(error::ecdsa_error::invalid_key); - if(EC_KEY_check_key(pkey.get()) == 0) - throw ecdsa_exception("failed to load key: key is invalid"); + 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 - * \throws signature_generation_exception */ - std::string sign(const std::string& data) const override { - const std::string hash = generate_hash(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 {}; + } - std::unique_ptr - sig(ECDSA_do_sign((const unsigned char*)hash.data(), static_cast(hash.size()), pkey.get()), ECDSA_SIG_free); - if(!sig) - throw signature_generation_exception(); -#ifdef OPENSSL10 + 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; + 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) + if (rr.size() > signature_length / 2 || rs.size() > signature_length / 2) throw std::logic_error("bignum size exceeded expected length"); - while(rr.size() != signature_length/2) rr = '\0' + rr; - while(rs.size() != signature_length/2) rs = '\0' + rs; + 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 - * \throws signature_verification_exception If the provided signature does not match + * \param ec Filled with details on error */ - void verify(const std::string& data, const std::string& signature) const override { - const std::string hash = generate_hash(data); - 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(); - - if(ECDSA_do_verify((const unsigned char*)hash.data(), hash.size(), &sig, pkey.get()) != 1) - throw signature_verification_exception("Invalid signature"); + 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 - std::unique_ptr sig(ECDSA_SIG_new(), ECDSA_SIG_free); - - ECDSA_SIG_set0(sig.get(), r.release(), s.release()); - - if(ECDSA_do_verify((const unsigned char*)hash.data(), static_cast(hash.size()), sig.get(), pkey.get()) != 1) - throw signature_verification_exception("Invalid signature"); + 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 } - private: /** - * Hash the provided data using the hash function specified in constructor - * \param data Data to hash - * \return Hash of data + * Returns the algorithm name provided to the constructor + * \return algorithm's name */ - std::string generate_hash(const std::string& data) 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(EVP_DigestInit(ctx.get(), md()) == 0) - throw signature_generation_exception("EVP_DigestInit failed"); - if(EVP_DigestUpdate(ctx.get(), data.data(), data.size()) == 0) - throw signature_generation_exception("EVP_DigestUpdate failed"); - unsigned int len = 0; - std::string res; - res.resize(EVP_MD_CTX_size(ctx.get())); - if(EVP_DigestFinal(ctx.get(), (unsigned char*)res.data(), &len) == 0) - throw signature_generation_exception("EVP_DigestFinal failed"); - res.resize(len); - return res; - } + std::string name() const { return alg_name; } + private: /// OpenSSL struct containing keys - std::shared_ptr pkey; - /// Length of the resulting signature - const size_t signature_length; + helper::evp_pkey_handle pkey; + /// algorithm's name + const std::string alg_name; }; - +#endif /** - * Base class for PSS-RSA family of algorithms + * \brief Base class for PSS-RSA family of algorithms */ - struct pss: public algorithm { - public: + 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 privat_key_password Password to decrypt private 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)(), const std::string& name) - : algorithm(md, name) - { + 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()) { + } else if (!public_key.empty()) { pkey = helper::load_public_key_from_string(public_key, public_key_password); } else - throw rsa_exception("at least one of public or private key need to be present"); + 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 - * \throws signature_generation_exception */ - std::string sign(const std::string& data) const override { - auto hash = this->generate_hash(data); - - std::unique_ptr key(EVP_PKEY_get1_RSA(pkey.get()), RSA_free); - 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(), (const unsigned char*)hash.data(), md(), md(), -1)) - throw signature_generation_exception("failed to create signature: RSA_padding_add_PKCS1_PSS_mgf1 failed"); + 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 (RSA_private_encrypt(size, (const unsigned char*)padded.data(), (unsigned char*)res.data(), key.get(), RSA_NO_PADDING) < 0) - throw signature_generation_exception("failed to create signature: 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; } + /** * Check if signature is valid * \param data The data to check signature against * \param signature Signature provided by the jwt - * \throws signature_verification_exception If the provided signature does not match + * \param ec Filled with error details */ - void verify(const std::string& data, const std::string& signature) const override { - auto hash = this->generate_hash(data); + void verify(const std::string& data, const std::string& signature, std::error_code& ec) const { + ec.clear(); - std::unique_ptr key(EVP_PKEY_get1_RSA(pkey.get()), RSA_free); - const int size = RSA_size(key.get()); - - std::string sig(size, 0x00); - if(!RSA_public_decrypt(static_cast(signature.size()), (const unsigned char*)signature.data(), (unsigned char*)sig.data(), key.get(), RSA_NO_PADDING)) - throw signature_verification_exception("Invalid signature"); - - if(!RSA_verify_PKCS1_PSS_mgf1(key.get(), (const unsigned char*)hash.data(), md(), md(), (const unsigned char*)sig.data(), -1)) - throw signature_verification_exception("Invalid signature"); - } - 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) 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); + 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_DigestInit(ctx.get(), md()) == 0) - throw signature_generation_exception("EVP_DigestInit failed"); - if(EVP_DigestUpdate(ctx.get(), data.data(), data.size()) == 0) - throw signature_generation_exception("EVP_DigestUpdate failed"); - unsigned int len = 0; - std::string res; - res.resize(EVP_MD_CTX_size(ctx.get())); - if(EVP_DigestFinal(ctx.get(), (unsigned char*)res.data(), &len) == 0) - throw signature_generation_exception("EVP_DigestFinal failed"); - res.resize(len); - return res; + 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 - std::shared_ptr pkey; + helper::evp_pkey_handle pkey; + /// Hash generator function + const EVP_MD* (*md)(); + /// algorithm's name + const std::string alg_name; }; /** @@ -600,9 +1588,7 @@ namespace jwt { * Construct new instance of algorithm * \param key HMAC signing key */ - explicit hs256(std::string key) - : hmacsha(std::move(key), EVP_sha256, "HS256") - {} + explicit hs256(std::string key) : hmacsha(std::move(key), EVP_sha256, "HS256") {} }; /** * HS384 algorithm @@ -612,9 +1598,7 @@ namespace jwt { * Construct new instance of algorithm * \param key HMAC signing key */ - explicit hs384(std::string key) - : hmacsha(std::move(key), EVP_sha384, "HS384") - {} + explicit hs384(std::string key) : hmacsha(std::move(key), EVP_sha384, "HS384") {} }; /** * HS512 algorithm @@ -624,9 +1608,7 @@ namespace jwt { * Construct new instance of algorithm * \param key HMAC signing key */ - explicit hs512(std::string key) - : hmacsha(std::move(key), EVP_sha512, "HS512") - {} + explicit hs512(std::string key) : hmacsha(std::move(key), EVP_sha512, "HS512") {} }; /** * RS256 algorithm @@ -637,11 +1619,11 @@ namespace jwt { * \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 privat_key_password Password to decrypt private 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") - {} + 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 @@ -652,11 +1634,11 @@ namespace jwt { * \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 privat_key_password Password to decrypt private 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") - {} + 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 @@ -667,11 +1649,11 @@ namespace jwt { * \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 privat_key_password Password to decrypt private 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") - {} + 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 @@ -680,13 +1662,15 @@ namespace jwt { /** * 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 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 privat_key_password Password to decrypt private 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) - {} + 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 @@ -695,13 +1679,15 @@ namespace jwt { /** * 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 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 privat_key_password Password to decrypt private 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) - {} + 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 @@ -710,14 +1696,78 @@ namespace jwt { /** * 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 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 privat_key_password Password to decrypt private 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) - {} + 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 @@ -728,11 +1778,11 @@ namespace jwt { * \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 privat_key_password Password to decrypt private 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") - {} + 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 @@ -743,11 +1793,11 @@ namespace jwt { * \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 privat_key_password Password to decrypt private 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") - {} + 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 @@ -758,171 +1808,512 @@ namespace jwt { * \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 privat_key_password Password to decrypt private 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") - {} + 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 /** - * Convenience wrapper for JSON value + * \brief JSON Abstractions for working with any library */ - class claim { - picojson::value val; - public: - enum class type { - null, - boolean, - number, - string, - array, - object, - int64 + 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; }; - claim() - : val() - {} - JWT_CLAIM_EXPLICIT claim(std::string s) - : val(std::move(s)) - {} - JWT_CLAIM_EXPLICIT claim(const date& s) - : val(int64_t(std::chrono::system_clock::to_time_t(s))) - {} - JWT_CLAIM_EXPLICIT claim(const std::set& s) - : val(picojson::array(s.cbegin(), s.cend())) - {} - JWT_CLAIM_EXPLICIT claim(const picojson::value& val) - : val(val) - {} + template + using void_t = typename make_void::type; +#endif - template - claim(Iterator start, Iterator end) - : val(picojson::array()) - { - auto& arr = val.get(); - for(; start != end; start++) { - arr.push_back(picojson::value(*start)); - } - } +#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 arguement"); + }; + + 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::value && + is_valid_json_object::value && + is_valid_json_array::value && + is_valid_json_string::value && + is_valid_json_number::value && + is_valid_json_integer::value && + is_valid_json_boolean::value; + }; + } // namespace details + + /** + * \brief a class to store a generic JSON value as claim + * + * \tparam json_traits : JSON implementation traits + * + * \see [RFC 7519: JSON Web Token (JWT)](https://tools.ietf.org/html/rfc7519) + */ + template + class basic_claim { /** - * Get wrapped json object - * \return Wrapped json object + * The reason behind this is to provide an expressive abstraction without + * over complexifying the API. For more information take the time to read + * https://github.com/nlohmann/json/issues/774. It maybe be expanded to + * support custom string types. */ - picojson::value to_json() const { - return val; - } + static_assert(std::is_same::value || + std::is_convertible::value || + std::is_constructible::value, + "string_type must be a std::string, convertible to a std::string, or construct a std::string."); + + static_assert( + details::is_valid_json_types::value, + "must staisfy json container requirements"); + static_assert(details::is_valid_traits::value, "traits must satisfy requirements"); + + typename json_traits::value_type val; + + public: + using set_t = std::set; + + basic_claim() = default; + basic_claim(const basic_claim&) = default; + basic_claim(basic_claim&&) = default; + basic_claim& operator=(const basic_claim&) = default; + basic_claim& operator=(basic_claim&&) = default; + ~basic_claim() = default; + + JWT_CLAIM_EXPLICIT basic_claim(typename json_traits::string_type s) : val(std::move(s)) {} + JWT_CLAIM_EXPLICIT basic_claim(const date& d) + : val(typename json_traits::integer_type(std::chrono::system_clock::to_time_t(d))) {} + JWT_CLAIM_EXPLICIT basic_claim(typename json_traits::array_type a) : val(std::move(a)) {} + JWT_CLAIM_EXPLICIT basic_claim(typename json_traits::value_type v) : val(std::move(v)) {} + JWT_CLAIM_EXPLICIT basic_claim(const set_t& s) : val(typename json_traits::array_type(s.begin(), s.end())) {} + template + basic_claim(Iterator begin, Iterator end) : val(typename json_traits::array_type(begin, end)) {} /** - * Parse input stream into wrapped json object + * Get wrapped JSON value + * \return Wrapped JSON value + */ + typename json_traits::value_type to_json() const { return val; } + + /** + * Parse input stream into underlying JSON value * \return input stream */ - inline std::istream& operator>>(std::istream& is) - { - return is >> val; - } + std::istream& operator>>(std::istream& is) { return is >> val; } /** - * Get type of contained object + * Serialize claim to output stream from wrapped JSON value + * \return ouput stream + */ + std::ostream& operator<<(std::ostream& os) { return os << val; } + + /** + * Get type of contained JSON value * \return Type - * \throws std::logic_error An internal error occured + * \throw std::logic_error An internal error occured */ - type get_type() const { - if (val.is()) return type::null; - else if (val.is()) return type::boolean; - else if (val.is()) return type::int64; - else if (val.is()) return type::number; - else if (val.is()) return type::string; - else if (val.is()) return type::array; - else if (val.is()) return type::object; - else throw std::logic_error("internal error"); - } + json::type get_type() const { return json_traits::get_type(val); } /** - * Get the contained object as a string + * Get the contained JSON value as a string * \return content as string - * \throws std::bad_cast Content was not a string + * \throw std::bad_cast Content was not a string */ - const std::string& as_string() const { - if (!val.is()) - throw std::bad_cast(); - return val.get(); - } + typename json_traits::string_type as_string() const { return json_traits::as_string(val); } + /** - * Get the contained object 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 - * \throws std::bad_cast Content was not a date + * \throw std::bad_cast Content was not a date */ date as_date() const { - return std::chrono::system_clock::from_time_t(as_int()); + 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 object as an array + * Get the contained JSON value as an array * \return content as array - * \throws std::bad_cast Content was not an array + * \throw std::bad_cast Content was not an array */ - const picojson::array& as_array() const { - if (!val.is()) - throw std::bad_cast(); - return val.get(); - } + typename json_traits::array_type as_array() const { return json_traits::as_array(val); } + /** - * Get the contained object as a set of strings + * Get the contained JSON value as a set of strings * \return content as set of strings - * \throws std::bad_cast Content was not a set + * \throw std::bad_cast Content was not an array of string */ - std::set as_set() const { - std::set res; - for(auto& e : as_array()) { - if(!e.is()) - throw std::bad_cast(); - res.insert(e.get()); + set_t as_set() const { + set_t res; + for (const auto& e : json_traits::as_array(val)) { + res.insert(json_traits::as_string(e)); } return res; } + /** - * Get the contained object as an integer + * Get the contained JSON value as an integer * \return content as int - * \throws std::bad_cast Content was not an int + * \throw std::bad_cast Content was not an int */ - int64_t as_int() const { - if (!val.is()) - throw std::bad_cast(); - return val.get(); - } + typename json_traits::integer_type as_integer() const { return json_traits::as_integer(val); } + /** - * Get the contained object as a bool + * Get the contained JSON value as a bool * \return content as bool - * \throws std::bad_cast Content was not a bool + * \throw std::bad_cast Content was not a bool */ - bool as_bool() const { - if (!val.is()) - throw std::bad_cast(); - return val.get(); - } + typename json_traits::boolean_type as_boolean() const { return json_traits::as_boolean(val); } + /** - * Get the contained object as a number + * Get the contained JSON value as a number * \return content as double - * \throws std::bad_cast Content was not a number + * \throw std::bad_cast Content was not a number */ - double as_number() const { - if (!val.is()) - throw std::bad_cast(); - return val.get(); - } + typename json_traits::number_type as_number() const { return json_traits::as_number(val); } }; + namespace error { + /** + * Attempt to parse JSON was unsuccessful + */ + struct invalid_json_exception : public std::runtime_error { + invalid_json_exception() : runtime_error("invalid json") {} + }; + /** + * Attempt to access claim was unsuccessful + */ + struct claim_not_present_exception : public std::out_of_range { + claim_not_present_exception() : out_of_range("claim not found") {} + }; + } // namespace error + + namespace details { + template + struct map_of_claims { + typename json_traits::object_type claims; + using basic_claim_t = basic_claim; + using iterator = typename json_traits::object_type::iterator; + using const_iterator = typename json_traits::object_type::const_iterator; + + map_of_claims() = default; + map_of_claims(const map_of_claims&) = default; + map_of_claims(map_of_claims&&) = default; + map_of_claims& operator=(const map_of_claims&) = default; + map_of_claims& operator=(map_of_claims&&) = default; + + map_of_claims(typename json_traits::object_type json) : claims(std::move(json)) {} + + iterator begin() { return claims.begin(); } + iterator end() { return claims.end(); } + const_iterator cbegin() const { return claims.begin(); } + const_iterator cend() const { return claims.end(); } + const_iterator begin() const { return claims.begin(); } + const_iterator end() const { return claims.end(); } + + /** + * \brief Parse a JSON string into a map of claims + * + * The implication is that a "map of claims" is identic to a JSON object + * + * \param str JSON data to be parse as an object + * \return content as JSON object + */ + static typename json_traits::object_type parse_claims(const typename json_traits::string_type& str) { + typename json_traits::value_type val; + if (!json_traits::parse(val, str)) throw error::invalid_json_exception(); + + return json_traits::as_object(val); + }; + + /** + * Check if a claim is present in the map + * \return true if claim was present, false otherwise + */ + bool has_claim(const typename json_traits::string_type& name) const noexcept { + return claims.count(name) != 0; + } + + /** + * Get a claim by name + * + * \param name the name of the desired claim + * \return Requested claim + * \throw jwt::error::claim_not_present_exception if the claim was not present + */ + basic_claim_t get_claim(const typename json_traits::string_type& name) const { + if (!has_claim(name)) throw error::claim_not_present_exception(); + return basic_claim_t{claims.at(name)}; + } + }; + } // namespace details + /** * Base class that represents a token payload. * Contains Convenience accessors for common claims. */ + template class payload { protected: - std::unordered_map payload_claims; + details::map_of_claims payload_claims; + public: + using basic_claim_t = basic_claim; + /** * Check if issuer is present ("iss") * \return true if present, false otherwise @@ -961,95 +2352,80 @@ namespace jwt { /** * Get issuer claim * \return issuer as string - * \throws std::runtime_error If claim was not present - * \throws std::bad_cast Claim was present but not a string (Should not happen in a valid token) + * \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) */ - const std::string& get_issuer() const { return get_payload_claim("iss").as_string(); } + typename json_traits::string_type get_issuer() const { return get_payload_claim("iss").as_string(); } /** * Get subject claim * \return subject as string - * \throws std::runtime_error If claim was not present - * \throws std::bad_cast Claim was present but not a string (Should not happen in a valid token) + * \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) */ - const std::string& get_subject() const { return get_payload_claim("sub").as_string(); } + typename json_traits::string_type get_subject() const { return get_payload_claim("sub").as_string(); } /** * Get audience claim - * \return audience as strings - * \throws std::runtime_error If claim was not present - * \throws std::bad_cast Claim was present but not a set (Should not happen in a valid token) + * \return audience 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) */ - std::string get_audience() const { return get_payload_claim("aud").as_string(); } - /** - * Get audiences claim - * \return audience as a set of strings - * \throws std::runtime_error If claim was not present - * \throws std::bad_cast Claim was present but not a set (Should not happen in a valid token) - */ - std::set get_audiences() const { - auto aud = get_payload_claim("aud"); - if (aud.get_type() == jwt::claim::type::string) - return { aud.as_string() }; - else - return aud.as_set(); - } + typename json_traits::string_type get_audience() const { return get_payload_claim("aud").as_string(); } /** * Get expires claim * \return expires as a date in utc - * \throws std::runtime_error If claim was not present - * \throws std::bad_cast Claim was present but not a date (Should not happen in a valid token) + * \throw std::runtime_error If claim was not present + * \throw std::bad_cast Claim was present but not a date (Should not happen in a valid token) */ date get_expires_at() const { return get_payload_claim("exp").as_date(); } /** * Get not valid before claim * \return nbf date in utc - * \throws std::runtime_error If claim was not present - * \throws std::bad_cast Claim was present but not a date (Should not happen in a valid token) + * \throw std::runtime_error If claim was not present + * \throw std::bad_cast Claim was present but not a date (Should not happen in a valid token) */ date get_not_before() const { return get_payload_claim("nbf").as_date(); } /** * Get issued at claim * \return issued at as date in utc - * \throws std::runtime_error If claim was not present - * \throws std::bad_cast Claim was present but not a date (Should not happen in a valid token) + * \throw std::runtime_error If claim was not present + * \throw std::bad_cast Claim was present but not a date (Should not happen in a valid token) */ date get_issued_at() const { return get_payload_claim("iat").as_date(); } /** * Get id claim * \return id as string - * \throws std::runtime_error If claim was not present - * \throws std::bad_cast Claim was present but not a string (Should not happen in a valid token) + * \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) */ - const std::string& get_id() const { return get_payload_claim("jti").as_string(); } + typename json_traits::string_type get_id() const { return get_payload_claim("jti").as_string(); } /** * Check if a payload claim is present * \return true if claim was present, false otherwise */ - bool has_payload_claim(const std::string& name) const noexcept { return payload_claims.count(name) != 0; } + bool has_payload_claim(const typename json_traits::string_type& name) const noexcept { + return payload_claims.has_claim(name); + } /** * Get payload claim * \return Requested claim - * \throws std::runtime_error If claim was not present + * \throw std::runtime_error If claim was not present */ - const claim& get_payload_claim(const std::string& name) const { - if (!has_payload_claim(name)) - throw std::runtime_error("claim not found"); - return payload_claims.at(name); + basic_claim_t get_payload_claim(const typename json_traits::string_type& name) const { + return payload_claims.get_claim(name); } - /** - * Get all payload claims - * \return map of claims - */ - std::unordered_map get_payload_claims() const { return payload_claims; } }; /** * Base class that represents a token header. * Contains Convenience accessors for common claims. */ + template class header { protected: - std::unordered_map header_claims; + details::map_of_claims header_claims; + public: + using basic_claim_t = basic_claim; /** * Check if algortihm is present ("alg") * \return true if present, false otherwise @@ -1073,386 +2449,784 @@ namespace jwt { /** * Get algorithm claim * \return algorithm as string - * \throws std::runtime_error If claim was not present - * \throws std::bad_cast Claim was present but not a string (Should not happen in a valid token) + * \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) */ - const std::string& get_algorithm() const { return get_header_claim("alg").as_string(); } + typename json_traits::string_type get_algorithm() const { return get_header_claim("alg").as_string(); } /** * Get type claim * \return type as a string - * \throws std::runtime_error If claim was not present - * \throws std::bad_cast Claim was present but not a string (Should not happen in a valid token) + * \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) */ - const std::string& get_type() const { return get_header_claim("typ").as_string(); } + typename json_traits::string_type get_type() const { return get_header_claim("typ").as_string(); } /** * Get content type claim * \return content type as string - * \throws std::runtime_error If claim was not present - * \throws std::bad_cast Claim was present but not a string (Should not happen in a valid token) + * \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) */ - const std::string& get_content_type() const { return get_header_claim("cty").as_string(); } + typename json_traits::string_type get_content_type() const { return get_header_claim("cty").as_string(); } /** * Get key id claim * \return key id as string - * \throws std::runtime_error If claim was not present - * \throws std::bad_cast Claim was present but not a string (Should not happen in a valid token) + * \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) */ - const std::string& get_key_id() const { return get_header_claim("kid").as_string(); } + typename json_traits::string_type get_key_id() const { return get_header_claim("kid").as_string(); } /** * Check if a header claim is present * \return true if claim was present, false otherwise */ - bool has_header_claim(const std::string& name) const noexcept { return header_claims.count(name) != 0; } + bool has_header_claim(const typename json_traits::string_type& name) const noexcept { + return header_claims.has_claim(name); + } /** * Get header claim * \return Requested claim - * \throws std::runtime_error If claim was not present + * \throw std::runtime_error If claim was not present */ - const claim& get_header_claim(const std::string& name) const { - if (!has_header_claim(name)) - throw std::runtime_error("claim not found"); - return header_claims.at(name); + basic_claim_t get_header_claim(const typename json_traits::string_type& name) const { + return header_claims.get_claim(name); } - /** - * Get all header claims - * \return map of claims - */ - std::unordered_map get_header_claims() const { return header_claims; } }; /** * Class containing all information about a decoded token */ - class decoded_jwt : public header, public payload { + template + class decoded_jwt : public header, public payload { protected: /// Unmodifed token, as passed to constructor - const std::string token; + typename json_traits::string_type token; /// Header part decoded from base64 - std::string header; + typename json_traits::string_type header; /// Unmodified header part in base64 - std::string header_base64; + typename json_traits::string_type header_base64; /// Payload part decoded from base64 - std::string payload; + typename json_traits::string_type payload; /// Unmodified payload part in base64 - std::string payload_base64; + typename json_traits::string_type payload_base64; /// Signature part decoded from base64 - std::string signature; + typename json_traits::string_type signature; /// Unmodified signature part in base64 - std::string signature_base64; + typename json_traits::string_type signature_base64; + public: + using basic_claim_t = basic_claim; +#ifndef JWT_DISABLE_BASE64 /** - * Constructor - * Parses a given token + * \brief Parses a given token + * + * \note Decodes using the jwt::base64url which supports an std::string + * * \param token The token to parse - * \throws std::invalid_argument Token is not in correct format - * \throws std::runtime_error Base64 decoding failed or invalid json + * \throw std::invalid_argument Token is not in correct format + * \throw std::runtime_error Base64 decoding failed or invalid json */ - explicit decoded_jwt(const std::string& token) - : token(token) - { + JWT_CLAIM_EXPLICIT decoded_jwt(const typename json_traits::string_type& token) + : decoded_jwt(token, [](const typename json_traits::string_type& str) { + return base::decode(base::pad(str)); + }) {} +#endif + /** + * \brief Parses a given token + * + * \tparam Decode is 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 The token to parse + * \param decode The function to decode the token + * \throw std::invalid_argument Token is not in correct format + * \throw std::runtime_error Base64 decoding failed or invalid json + */ + template + decoded_jwt(const typename json_traits::string_type& token, Decode decode) : token(token) { auto hdr_end = token.find('.'); - if (hdr_end == std::string::npos) - throw std::invalid_argument("invalid token supplied"); + if (hdr_end == json_traits::string_type::npos) throw std::invalid_argument("invalid token supplied"); auto payload_end = token.find('.', hdr_end + 1); - if (payload_end == std::string::npos) - throw std::invalid_argument("invalid token supplied"); - header = header_base64 = token.substr(0, hdr_end); - payload = payload_base64 = token.substr(hdr_end + 1, payload_end - hdr_end - 1); - signature = signature_base64 = token.substr(payload_end + 1); + if (payload_end == json_traits::string_type::npos) throw std::invalid_argument("invalid token supplied"); + header_base64 = token.substr(0, hdr_end); + payload_base64 = token.substr(hdr_end + 1, payload_end - hdr_end - 1); + signature_base64 = token.substr(payload_end + 1); - // Fix padding: JWT requires padding to get removed - header = base::pad(header); - payload = base::pad(payload); - signature = base::pad(signature); + header = decode(header_base64); + payload = decode(payload_base64); + signature = decode(signature_base64); - header = base::decode(header); - payload = base::decode(payload); - signature = base::decode(signature); - - auto parse_claims = [](const std::string& str) { - std::unordered_map res; - picojson::value val; - if (!picojson::parse(val, str).empty()) - throw std::runtime_error("Invalid json"); - - for (auto& e : val.get()) { res.insert({ e.first, claim(e.second) }); } - - return res; - }; - - header_claims = parse_claims(header); - payload_claims = parse_claims(payload); + this->header_claims = details::map_of_claims::parse_claims(header); + this->payload_claims = details::map_of_claims::parse_claims(payload); } /** * Get token string, as passed to constructor * \return token as passed to constructor */ - const std::string& get_token() const noexcept { return token; } + const typename json_traits::string_type& get_token() const noexcept { return token; } /** * Get header part as json string * \return header part after base64 decoding */ - const std::string& get_header() const noexcept { return header; } + const typename json_traits::string_type& get_header() const noexcept { return header; } /** * Get payload part as json string * \return payload part after base64 decoding */ - const std::string& get_payload() const noexcept { return payload; } + const typename json_traits::string_type& get_payload() const noexcept { return payload; } /** * Get signature part as json string * \return signature part after base64 decoding */ - const std::string& get_signature() const noexcept { return signature; } + const typename json_traits::string_type& get_signature() const noexcept { return signature; } /** * Get header part as base64 string * \return header part before base64 decoding */ - const std::string& get_header_base64() const noexcept { return header_base64; } + const typename json_traits::string_type& get_header_base64() const noexcept { return header_base64; } /** * Get payload part as base64 string * \return payload part before base64 decoding */ - const std::string& get_payload_base64() const noexcept { return payload_base64; } + const typename json_traits::string_type& get_payload_base64() const noexcept { return payload_base64; } /** * Get signature part as base64 string * \return signature part before base64 decoding */ - const std::string& get_signature_base64() const noexcept { return signature_base64; } - + const typename json_traits::string_type& get_signature_base64() const noexcept { return signature_base64; } + /** + * Get all payload as JSON object + * \return map of claims + */ + typename json_traits::object_type get_payload_json() const { return this->payload_claims.claims; } + /** + * Get all header as JSON object + * \return map of claims + */ + typename json_traits::object_type get_header_json() const { return this->header_claims.claims; } + /** + * Get a payload claim by name + * + * \param name the name of the desired claim + * \return Requested claim + * \throw jwt::error::claim_not_present_exception if the claim was not present + */ + basic_claim_t get_payload_claim(const typename json_traits::string_type& name) const { + return this->payload_claims.get_claim(name); + } + /** + * Get a header claim by name + * + * \param name the name of the desired claim + * \return Requested claim + * \throw jwt::error::claim_not_present_exception if the claim was not present + */ + basic_claim_t get_header_claim(const typename json_traits::string_type& name) const { + return this->header_claims.get_claim(name); + } }; /** * Builder class to build and sign a new token * Use jwt::create() to get an instance of this class. */ + template class builder { - std::unordered_map header_claims; - std::unordered_map payload_claims; + typename json_traits::object_type header_claims; + typename json_traits::object_type payload_claims; - builder() = default; - friend builder create(); public: + builder() = default; /** * Set a header claim. * \param id Name of the claim * \param c Claim to add * \return *this to allow for method chaining */ - builder& set_header_claim(const std::string& id, claim c) { header_claims[id] = std::move(c); return *this; } + builder& set_header_claim(const typename json_traits::string_type& id, typename json_traits::value_type c) { + header_claims[id] = std::move(c); + return *this; + } + + /** + * Set a header claim. + * \param id Name of the claim + * \param c Claim to add + * \return *this to allow for method chaining + */ + builder& set_header_claim(const typename json_traits::string_type& id, basic_claim c) { + header_claims[id] = c.to_json(); + return *this; + } /** * Set a payload claim. * \param id Name of the claim * \param c Claim to add * \return *this to allow for method chaining */ - builder& set_payload_claim(const std::string& id, claim c) { payload_claims[id] = std::move(c); return *this; } + builder& set_payload_claim(const typename json_traits::string_type& id, typename json_traits::value_type c) { + payload_claims[id] = std::move(c); + return *this; + } /** - * Set algorithm claim + * Set a payload claim. + * \param id Name of the claim + * \param c Claim to add + * \return *this to allow for method chaining + */ + builder& set_payload_claim(const typename json_traits::string_type& id, basic_claim c) { + payload_claims[id] = c.to_json(); + return *this; + } + /** + * \brief Set algorithm claim * You normally don't need to do this, as the algorithm is automatically set if you don't change it. + * * \param str Name of algorithm * \return *this to allow for method chaining */ - builder& set_algorithm(const std::string& str) { return set_header_claim("alg", claim(str)); } + builder& set_algorithm(typename json_traits::string_type str) { + return set_header_claim("alg", typename json_traits::value_type(str)); + } /** * Set type claim * \param str Type to set * \return *this to allow for method chaining */ - builder& set_type(const std::string& str) { return set_header_claim("typ", claim(str)); } + builder& set_type(typename json_traits::string_type str) { + return set_header_claim("typ", typename json_traits::value_type(str)); + } /** * Set content type claim * \param str Type to set * \return *this to allow for method chaining */ - builder& set_content_type(const std::string& str) { return set_header_claim("cty", claim(str)); } + builder& set_content_type(typename json_traits::string_type str) { + 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 */ - builder& set_key_id(const std::string& str) { return set_header_claim("kid", claim(str)); } + builder& set_key_id(typename json_traits::string_type str) { + return set_header_claim("kid", typename json_traits::value_type(str)); + } /** * Set issuer claim * \param str Issuer to set * \return *this to allow for method chaining */ - builder& set_issuer(const std::string& str) { return set_payload_claim("iss", claim(str)); } + builder& set_issuer(typename json_traits::string_type str) { + return set_payload_claim("iss", typename json_traits::value_type(str)); + } /** * Set subject claim * \param str Subject to set * \return *this to allow for method chaining */ - builder& set_subject(const std::string& str) { return set_payload_claim("sub", claim(str)); } + builder& set_subject(typename json_traits::string_type str) { + return set_payload_claim("sub", typename json_traits::value_type(str)); + } /** * Set audience claim - * \param l Audience set + * \param a Audience set * \return *this to allow for method chaining */ - builder& set_audience(const std::set& l) { return set_payload_claim("aud", claim(l)); } + builder& set_audience(typename json_traits::array_type a) { + return set_payload_claim("aud", typename json_traits::value_type(a)); + } /** * Set audience claim * \param aud Single audience * \return *this to allow for method chaining */ - builder& set_audience(const std::string& aud) { return set_payload_claim("aud", claim(aud)); } + builder& set_audience(typename json_traits::string_type aud) { + return set_payload_claim("aud", typename json_traits::value_type(aud)); + } /** * Set expires at claim * \param d Expires time * \return *this to allow for method chaining */ - builder& set_expires_at(const date& d) { return set_payload_claim("exp", claim(d)); } + builder& set_expires_at(const date& d) { return set_payload_claim("exp", basic_claim(d)); } /** * Set not before claim * \param d First valid time * \return *this to allow for method chaining */ - builder& set_not_before(const date& d) { return set_payload_claim("nbf", claim(d)); } + builder& set_not_before(const date& d) { return set_payload_claim("nbf", basic_claim(d)); } /** * Set issued at claim * \param d Issued at time, should be current time * \return *this to allow for method chaining */ - builder& set_issued_at(const date& d) { return set_payload_claim("iat", claim(d)); } + builder& set_issued_at(const date& d) { return set_payload_claim("iat", basic_claim(d)); } /** * Set id claim * \param str ID to set * \return *this to allow for method chaining */ - builder& set_id(const std::string& str) { return set_payload_claim("jti", claim(str)); } + builder& set_id(const typename json_traits::string_type& str) { + return set_payload_claim("jti", typename json_traits::value_type(str)); + } /** * Sign token and return result + * \tparam Algo Callable method which takes a string_type and return the signed input as a string_type + * \tparam Encode Callable method which takes a string_type and base64url safe encodes it, + * MUST return the result with no padding; trim the result. + * \param algo Instance of an algorithm to sign the token with + * \param encode Callable to transform the serialized json to base64 with no padding + * \return Final token as a string + * + * \note If the 'alg' header in not set in the token it will be set to `algo.name()` + */ + template + typename json_traits::string_type sign(const Algo& algo, Encode encode) const { + std::error_code ec; + auto res = sign(algo, encode, ec); + error::throw_if_error(ec); + return res; + } +#ifndef JWT_DISABLE_BASE64 + /** + * Sign token and return result + * + * using the `jwt::base` functions provided + * * \param algo Instance of an algorithm to sign the token with * \return Final token as a string */ - template - std::string sign(const T& algo) const { - picojson::object obj_header; - obj_header["alg"] = picojson::value(algo.name()); - for (auto& e : header_claims) { - obj_header[e.first] = e.second.to_json(); - } - picojson::object obj_payload; - for (auto& e : payload_claims) { - obj_payload.insert({ e.first, e.second.to_json() }); - } - - auto encode = [](const std::string& data) { - return base::trim(base::encode(data)); - }; - - std::string header = encode(picojson::value(obj_header).serialize()); - std::string payload = encode(picojson::value(obj_payload).serialize()); - - std::string token = header + "." + payload; - - return token + "." + encode(algo.sign(token)); + template + typename json_traits::string_type sign(const Algo& algo) const { + std::error_code ec; + auto res = sign(algo, ec); + error::throw_if_error(ec); + return res; } +#endif + + /** + * Sign token and return result + * \tparam Algo Callable method which takes a string_type and return the signed input as a string_type + * \tparam Encode Callable method which takes a string_type and base64url safe encodes it, + * MUST return the result with no padding; trim the result. + * \param algo Instance of an algorithm to sign the token with + * \param encode Callable to transform the serialized json to base64 with no padding + * \param ec error_code filled with details on error + * \return Final token as a string + * + * \note If the 'alg' header in not set in the token it will be set to `algo.name()` + */ + template + typename json_traits::string_type sign(const Algo& algo, Encode encode, std::error_code& ec) const { + // make a copy such that a builder can be re-used + typename json_traits::object_type obj_header = header_claims; + if (header_claims.count("alg") == 0) obj_header["alg"] = typename json_traits::value_type(algo.name()); + + const auto header = encode(json_traits::serialize(typename json_traits::value_type(obj_header))); + const auto payload = encode(json_traits::serialize(typename json_traits::value_type(payload_claims))); + const auto token = header + "." + payload; + + auto signature = algo.sign(token, ec); + if (ec) return {}; + + return token + "." + encode(signature); + } +#ifndef JWT_DISABLE_BASE64 + /** + * Sign token and return result + * + * using the `jwt::base` functions provided + * + * \param algo Instance of an algorithm to sign the token with + * \param ec error_code filled with details on error + * \return Final token as a string + */ + template + typename json_traits::string_type sign(const Algo& algo, std::error_code& ec) const { + return sign( + algo, + [](const typename json_traits::string_type& data) { + return base::trim(base::encode(data)); + }, + ec); + } +#endif }; + namespace verify_ops { + /** + * This is the base container which holds the token that need to be verified + */ + template + struct verify_context { + verify_context(date ctime, const decoded_jwt& j, size_t l) + : current_time(ctime), jwt(j), default_leeway(l) {} + // Current time, retrieved from the verifiers clock and cached for performance and consistency + date current_time; + // The jwt passed to the verifier + const decoded_jwt& jwt; + // The configured default leeway for this verification + size_t default_leeway{0}; + + // The claim key to apply this comparision 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); + } + }; + + /** + * This is the default operation and does case sensitive matching + */ + template + struct equals_claim { + const basic_claim expected; + void operator()(const verify_context& ctx, std::error_code& ec) const { + auto jc = ctx.get_claim(in_header, expected.get_type(), ec); + if (ec) return; + const bool matches = [&]() { + switch (expected.get_type()) { + case json::type::boolean: return expected.as_boolean() == jc.as_boolean(); + case json::type::integer: return expected.as_integer() == jc.as_integer(); + case json::type::number: return expected.as_number() == jc.as_number(); + case json::type::string: return expected.as_string() == jc.as_string(); + case json::type::array: + case json::type::object: + return json_traits::serialize(expected.to_json()) == json_traits::serialize(jc.to_json()); + default: throw std::logic_error("internal error, should be unreachable"); + } + }(); + if (!matches) { + ec = error::token_verification_error::claim_value_missmatch; + return; + } + } + }; + + /** + * Checks that the current time is before the time specified in the given + * claim. This is identical to how the "exp" check works. + */ + template + struct date_before_claim { + const size_t leeway; + void operator()(const verify_context& ctx, std::error_code& ec) const { + auto jc = ctx.get_claim(in_header, json::type::integer, ec); + if (ec) return; + auto c = jc.as_date(); + if (ctx.current_time > c + std::chrono::seconds(leeway)) { + ec = error::token_expired_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_expired_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) { +#if __cplusplus > 201103L + std::wstring_convert, wchar_t> conv; + auto wide = conv.from_bytes(str); + auto& f = std::use_facet>(loc); + f.tolower(&wide[0], &wide[0] + wide.size()); + return conv.to_bytes(wide); +#else + std::string result; + std::transform(str.begin(), str.end(), std::back_inserter(result), + [&loc](unsigned char c) { return std::tolower(c, loc); }); + return result; +#endif + } + }; + } // namespace verify_ops + /** - * Verifier class used to check if a decoded token contains all claims required by your application and has a valid signature. + * Verifier class used to check if a decoded token contains all claims required by your application and has a valid + * signature. */ - template + 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) = 0; + 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) override { - alg.verify(data, sig); + 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; + 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) {} + 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_expired_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_expired_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_expired_error::token_expired; + } + }; + } /** * Set default leeway to use. * \param leeway Default leeway to use if not specified otherwise * \return *this to allow chaining */ - verifier& leeway(size_t leeway) { default_leeway = leeway; return *this; } + verifier& leeway(size_t leeway) { + default_leeway = leeway; + return *this; + } /** * Set leeway for expires at. * If not specified the default leeway will be used. * \param leeway Set leeway to use for expires at. * \return *this to allow chaining */ - verifier& expires_at_leeway(size_t leeway) { return with_claim("exp", claim(std::chrono::system_clock::from_time_t(leeway))); } + verifier& expires_at_leeway(size_t leeway) { + claims["exp"] = verify_ops::date_before_claim{leeway}; + return *this; + } /** * Set leeway for not before. * If not specified the default leeway will be used. * \param leeway Set leeway to use for not before. * \return *this to allow chaining */ - verifier& not_before_leeway(size_t leeway) { return with_claim("nbf", claim(std::chrono::system_clock::from_time_t(leeway))); } + verifier& not_before_leeway(size_t leeway) { + claims["nbf"] = verify_ops::date_after_claim{leeway}; + return *this; + } /** * Set leeway for issued at. * If not specified the default leeway will be used. * \param leeway Set leeway to use for issued at. * \return *this to allow chaining */ - verifier& issued_at_leeway(size_t leeway) { return with_claim("iat", claim(std::chrono::system_clock::from_time_t(leeway))); } + verifier& issued_at_leeway(size_t leeway) { + claims["iat"] = verify_ops::date_after_claim{leeway}; + return *this; + } + + /** + * Set an type to check for. + * + * According to [RFC 7519 Section 5.1](https://datatracker.ietf.org/doc/html/rfc7519#section-5.1), + * This parameter is ignored by JWT implementations; any processing of this parameter is performed by the JWT application. + * Check is casesensitive. + * + * \param type Type Header Parameter to check for. + * \param locale Localization functionality to use when comapring + * \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. * \param iss Issuer to check for. * \return *this to allow chaining */ - verifier& with_issuer(const std::string& iss) { return with_claim("iss", claim(iss)); } + 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. * \param sub Subject to check for. * \return *this to allow chaining */ - verifier& with_subject(const std::string& sub) { return with_claim("sub", claim(sub)); } + verifier& with_subject(const typename json_traits::string_type& sub) { + return with_claim("sub", basic_claim_t(sub)); + } /** * Set an audience to check for. * If any of the specified audiences is not present in the token the check fails. * \param aud Audience to check for. * \return *this to allow chaining */ - verifier& with_audience(const std::set& aud) { return with_claim("aud", claim(aud)); } + verifier& with_audience(const typename basic_claim_t::set_t& aud) { + claims["aud"] = verify_ops::is_subset_claim{aud}; + return *this; + } /** * Set an audience to check for. * If the specified audiences is not present in the token the check fails. * \param aud Audience to check for. * \return *this to allow chaining */ - verifier& with_audience(const std::string& aud) { return with_claim("aud", claim(aud)); } + verifier& with_audience(const typename json_traits::string_type& aud) { + typename basic_claim_t::set_t s; + s.insert(aud); + return with_audience(s); + } /** * Set an id to check for. * Check is casesensitive. * \param id ID to check for. * \return *this to allow chaining */ - verifier& with_id(const std::string& id) { return with_claim("jti", claim(id)); } + verifier& with_id(const typename json_traits::string_type& id) { return with_claim("jti", basic_claim_t(id)); } + /** - * Specify a claim to check for. + * 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 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 std::string& name, claim c) { claims[name] = c; return *this; } + verifier& with_claim(const typename json_traits::string_type& name, basic_claim_t c) { + return with_claim(name, verify_ops::equals_claim{c}); + } /** * Add an algorithm available for checking. @@ -1468,145 +3242,394 @@ namespace jwt { /** * Verify the given token. * \param jwt Token to check - * \throws token_verification_exception Verification failed + * \throw token_verification_exception Verification failed */ - void verify(const decoded_jwt& jwt) const { - const std::string data = jwt.get_header_base64() + "." + jwt.get_payload_base64(); - const auto& sig = jwt.get_signature(); - const auto& algo = jwt.get_algorithm(); - if (algs.count(algo) == 0) - throw token_verification_exception("Wrong algorithm"); - algs.at(algo)->verify(data, sig); - - auto assert_claim_eq = [](const decoded_jwt& jwt, const std::string& key, const claim& c) { - if (!jwt.has_payload_claim(key)) - throw token_verification_exception("Missing " + key + " claim"); - const auto& jc = jwt.get_payload_claim(key); - if (jc.get_type() != c.get_type()) - throw token_verification_exception("Claim " + key + " type mismatch"); - if (c.get_type() == claim::type::int64) { - if (c.as_date() != jc.as_date()) - throw token_verification_exception("Claim " + key + " does not match expected"); - } else if (c.get_type() == claim::type::array) { - const auto& s1 = c.as_set(); - const auto& s2 = jc.as_set(); - if (s1.size() != s2.size()) - throw token_verification_exception("Claim " + key + " does not match expected"); - auto it1 = s1.cbegin(); - auto it2 = s2.cbegin(); - while (it1 != s1.cend() && it2 != s2.cend()) { - if (*it1++ != *it2++) - throw token_verification_exception("Claim " + key + " does not match expected"); - } - } else if (c.get_type() == claim::type::object) { - if( c.to_json().serialize() != jc.to_json().serialize()) - throw token_verification_exception("Claim " + key + " does not match expected"); - } else if (c.get_type() == claim::type::string) { - if (c.as_string() != jc.as_string()) - throw token_verification_exception("Claim " + key + " does not match expected"); - } else - throw token_verification_exception("Internal error"); - }; - - const auto time = clock.now(); - - if (jwt.has_expires_at()) { - const auto leeway = claims.count("exp") == 1 ? std::chrono::system_clock::to_time_t(claims.at("exp").as_date()) : default_leeway; - const auto exp = jwt.get_expires_at(); - if (time > exp + std::chrono::seconds(leeway)) - throw token_expired_exception(); - } - if (jwt.has_issued_at()) { - const auto leeway = claims.count("iat") == 1 ? std::chrono::system_clock::to_time_t(claims.at("iat").as_date()) : default_leeway; - const auto iat = jwt.get_issued_at(); - if (time < iat - std::chrono::seconds(leeway)) - throw token_expired_exception(); - } - if (jwt.has_not_before()) { - const auto leeway = claims.count("nbf") == 1 ? std::chrono::system_clock::to_time_t(claims.at("nbf").as_date()) : default_leeway; - const auto nbf = jwt.get_not_before(); - if (time < nbf - std::chrono::seconds(leeway)) - throw token_expired_exception(); + void verify(const decoded_jwt& jwt) const { + std::error_code ec; + verify(jwt, ec); + error::throw_if_error(ec); + } + /** + * Verify the given token. + * \param jwt Token to check + * \param ec error_code filled with details on error + */ + void verify(const decoded_jwt& jwt, std::error_code& ec) const { + ec.clear(); + const typename json_traits::string_type data = jwt.get_header_base64() + "." + jwt.get_payload_base64(); + const typename json_traits::string_type sig = jwt.get_signature(); + const std::string algo = jwt.get_algorithm(); + if (algs.count(algo) == 0) { + ec = error::token_verification_error::wrong_algorithm; + return; } + algs.at(algo)->verify(data, sig, ec); + if (ec) return; + + verify_ops::verify_context ctx{clock.now(), jwt, default_leeway}; for (auto& c : claims) { - 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()) - throw token_verification_exception("Token doesn't contain the required audience"); - if (c.second.get_type() == jwt::claim::type::array) { - const auto& aud = jwt.get_audiences(); - const auto& expected = c.second.as_set(); - for (auto& e : expected) - if (aud.count(e) == 0) - throw token_verification_exception("Token doesn't contain the required audience"); - } else { - const auto& aud = jwt.get_audience(); - if (aud != c.second.as_string()) - throw token_verification_exception("Token doesn't contain the required audience"); - } - } else { - assert_claim_eq(jwt, c.first, c.second); - } + 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 algortihm 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 * \return verifier instance */ - template - verifier verify(Clock c) { - return verifier(c); + template + verifier verify(Clock c) { + return verifier(c); } /** * Default clock class using std::chrono::system_clock as a backend. */ struct default_clock { - std::chrono::system_clock::time_point now() const { - return std::chrono::system_clock::now(); - } + date now() const { return date::clock::now(); } }; /** - * Create a verifier using the default clock + * Create a verifier using the given clock + * \param c Clock instance to use * \return verifier instance */ - inline - verifier verify() { - return verify({}); + template + verifier verify(default_clock c = {}) { + return verifier(c); } /** * Return a builder instance to create a new token */ - inline - builder create() { - return builder(); + template + builder create() { + return builder(); + } + + /** + * Decode a token + * \param token Token to decode + * \param decode function that will pad and base64url decode the token + * \return Decoded token + * \throw std::invalid_argument Token is not in correct format + * \throw std::runtime_error Base64 decoding failed or invalid json + */ + template + decoded_jwt decode(const typename json_traits::string_type& token, Decode decode) { + return decoded_jwt(token, decode); } /** * Decode a token * \param token Token to decode * \return Decoded token - * \throws std::invalid_argument Token is not in correct format - * \throws std::runtime_error Base64 decoding failed or invalid json + * \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); + template + decoded_jwt decode(const typename json_traits::string_type& token) { + return decoded_jwt(token); } -} -inline std::istream& operator>>(std::istream& is, jwt::claim& c) -{ + template + jwk parse_jwk(const typename json_traits::string_type& token) { + return jwk(token); + } + + template + jwks parse_jwks(const typename json_traits::string_type& token) { + return jwks(token); + } +} // namespace jwt + +template +std::istream& operator>>(std::istream& is, jwt::basic_claim& c) { return c.operator>>(is); } -inline std::ostream& operator<<(std::ostream& os, const jwt::claim& c) -{ +template +std::ostream& operator<<(std::ostream& os, const jwt::basic_claim& c) { return os << c.to_json(); } + +#ifndef JWT_DISABLE_PICOJSON +#include "traits/kazuho-picojson/defaults.h" +#endif + +#endif diff --git a/src/lib/jwt-cpp/traits/boost-json/defaults.h b/src/lib/jwt-cpp/traits/boost-json/defaults.h new file mode 100644 index 0000000..affeffe --- /dev/null +++ b/src/lib/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/src/lib/jwt-cpp/traits/boost-json/traits.h b/src/lib/jwt-cpp/traits/boost-json/traits.h new file mode 100644 index 0000000..3b27d5f --- /dev/null +++ b/src/lib/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/src/lib/jwt-cpp/traits/danielaparker-jsoncons/defaults.h b/src/lib/jwt-cpp/traits/danielaparker-jsoncons/defaults.h new file mode 100644 index 0000000..47e12f5 --- /dev/null +++ b/src/lib/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/src/lib/jwt-cpp/traits/danielaparker-jsoncons/traits.h b/src/lib/jwt-cpp/traits/danielaparker-jsoncons/traits.h new file mode 100644 index 0000000..be56740 --- /dev/null +++ b/src/lib/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/src/lib/jwt-cpp/traits/defaults.h.mustache b/src/lib/jwt-cpp/traits/defaults.h.mustache new file mode 100644 index 0000000..ab2a847 --- /dev/null +++ b/src/lib/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/src/lib/jwt-cpp/traits/kazuho-picojson/defaults.h b/src/lib/jwt-cpp/traits/kazuho-picojson/defaults.h new file mode 100644 index 0000000..0c82133 --- /dev/null +++ b/src/lib/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/src/lib/jwt-cpp/traits/kazuho-picojson/traits.h b/src/lib/jwt-cpp/traits/kazuho-picojson/traits.h new file mode 100644 index 0000000..d80d1f8 --- /dev/null +++ b/src/lib/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.h" + +#ifndef JWT_DISABLE_PICOJSON +#define JWT_DISABLE_PICOJSON +#endif +#include "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/src/lib/jwt-cpp/traits/nlohmann-json/defaults.h b/src/lib/jwt-cpp/traits/nlohmann-json/defaults.h new file mode 100644 index 0000000..c324075 --- /dev/null +++ b/src/lib/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/src/lib/jwt-cpp/traits/nlohmann-json/traits.h b/src/lib/jwt-cpp/traits/nlohmann-json/traits.h new file mode 100644 index 0000000..7cf4869 --- /dev/null +++ b/src/lib/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/src/lib/picojson/picojson.h b/src/lib/picojson/picojson.h index ef4b903..95c9c6a 100644 --- a/src/lib/picojson/picojson.h +++ b/src/lib/picojson/picojson.h @@ -110,263 +110,264 @@ extern "C" { #pragma warning(disable : 4244) // conversion from int to char #pragma warning(disable : 4127) // conditional expression is constant #pragma warning(disable : 4702) // unreachable code +#pragma warning(disable : 4706) // assignment within conditional expression #else #define SNPRINTF snprintf #endif namespace picojson { -enum { - null_type, - boolean_type, - number_type, - string_type, - array_type, - object_type + enum { + null_type, + boolean_type, + number_type, + string_type, + array_type, + object_type #ifdef PICOJSON_USE_INT64 - , - int64_type + , + int64_type #endif -}; + }; -enum { INDENT_WIDTH = 2 }; + enum { INDENT_WIDTH = 2, DEFAULT_MAX_DEPTHS = 100 }; -struct null {}; + struct null {}; -class value { -public: - typedef std::vector array; - typedef std::map object; - union _storage { - bool boolean_; - double number_; + class value { + public: + typedef std::vector array; + typedef std::map object; + union _storage { + bool boolean_; + double number_; #ifdef PICOJSON_USE_INT64 - int64_t int64_; + int64_t int64_; #endif - std::string *string_; - array *array_; - object *object_; - }; + std::string *string_; + array *array_; + object *object_; + }; -protected: - int type_; - _storage u_; + protected: + int type_; + _storage u_; -public: - value(); - value(int type, bool); - explicit value(bool b); + public: + value(); + value(int type, bool); + explicit value(bool b); #ifdef PICOJSON_USE_INT64 - explicit value(int64_t i); + explicit value(int64_t i); #endif - explicit value(double n); - explicit value(const std::string &s); - explicit value(const array &a); - explicit value(const object &o); + explicit value(double n); + explicit value(const std::string &s); + explicit value(const array &a); + explicit value(const object &o); #if PICOJSON_USE_RVALUE_REFERENCE - explicit value(std::string &&s); - explicit value(array &&a); - explicit value(object &&o); + explicit value(std::string &&s); + explicit value(array &&a); + explicit value(object &&o); #endif - explicit value(const char *s); - value(const char *s, size_t len); - ~value(); - value(const value &x); - value &operator=(const value &x); + explicit value(const char *s); + value(const char *s, size_t len); + ~value(); + value(const value &x); + value &operator=(const value &x); #if PICOJSON_USE_RVALUE_REFERENCE - value(value &&x) PICOJSON_NOEXCEPT; - value &operator=(value &&x) PICOJSON_NOEXCEPT; + value(value &&x) PICOJSON_NOEXCEPT; + value &operator=(value &&x) PICOJSON_NOEXCEPT; #endif - void swap(value &x) PICOJSON_NOEXCEPT; - template bool is() const; - template const T &get() const; - template T &get(); - template void set(const T &); + void swap(value &x) PICOJSON_NOEXCEPT; + template bool is() const; + template const T &get() const; + template T &get(); + template void set(const T &); #if PICOJSON_USE_RVALUE_REFERENCE - template void set(T &&); + template void set(T &&); #endif - bool evaluate_as_boolean() const; - const value &get(const size_t idx) const; - const value &get(const std::string &key) const; - value &get(const size_t idx); - value &get(const std::string &key); + bool evaluate_as_boolean() const; + const value &get(const size_t idx) const; + const value &get(const std::string &key) const; + value &get(const size_t idx); + value &get(const std::string &key); - bool contains(const size_t idx) const; - bool contains(const std::string &key) const; - std::string to_str() const; - template void serialize(Iter os, bool prettify = false) const; - std::string serialize(bool prettify = false) const; + bool contains(const size_t idx) const; + bool contains(const std::string &key) const; + std::string to_str() const; + template void serialize(Iter os, bool prettify = false) const; + std::string serialize(bool prettify = false) const; -private: - template value(const T *); // intentionally defined to block implicit conversion of pointer to bool - template static void _indent(Iter os, int indent); - template void _serialize(Iter os, int indent) const; - std::string _serialize(int indent) const; - void clear(); -}; + private: + template value(const T *); // intentionally defined to block implicit conversion of pointer to bool + template static void _indent(Iter os, int indent); + template void _serialize(Iter os, int indent) const; + std::string _serialize(int indent) const; + void clear(); + }; -typedef value::array array; -typedef value::object object; + typedef value::array array; + typedef value::object object; -inline value::value() : type_(null_type), u_() { -} + inline value::value() : type_(null_type), u_() { + } -inline value::value(int type, bool) : type_(type), u_() { - switch (type) { + inline value::value(int type, bool) : type_(type), u_() { + switch (type) { #define INIT(p, v) \ case p##type: \ u_.p = v; \ break - INIT(boolean_, false); - INIT(number_, 0.0); + INIT(boolean_, false); + INIT(number_, 0.0); #ifdef PICOJSON_USE_INT64 - INIT(int64_, 0); + INIT(int64_, 0); #endif - INIT(string_, new std::string()); - INIT(array_, new array()); - INIT(object_, new object()); + INIT(string_, new std::string()); + INIT(array_, new array()); + INIT(object_, new object()); #undef INIT - default: - break; - } -} + default: + break; + } + } -inline value::value(bool b) : type_(boolean_type), u_() { - u_.boolean_ = b; -} + inline value::value(bool b) : type_(boolean_type), u_() { + u_.boolean_ = b; + } #ifdef PICOJSON_USE_INT64 -inline value::value(int64_t i) : type_(int64_type), u_() { - u_.int64_ = i; -} + inline value::value(int64_t i) : type_(int64_type), u_() { + u_.int64_ = i; + } #endif -inline value::value(double n) : type_(number_type), u_() { - if ( + inline value::value(double n) : type_(number_type), u_() { + if ( #ifdef _MSC_VER - !_finite(n) +!_finite(n) #elif __cplusplus >= 201103L - std::isnan(n) || std::isinf(n) +std::isnan(n) || std::isinf(n) #else - isnan(n) || isinf(n) + isnan(n) || isinf(n) #endif - ) { - throw std::overflow_error(""); - } - u_.number_ = n; -} + ) { + throw std::overflow_error(""); + } + u_.number_ = n; + } -inline value::value(const std::string &s) : type_(string_type), u_() { - u_.string_ = new std::string(s); -} + inline value::value(const std::string &s) : type_(string_type), u_() { + u_.string_ = new std::string(s); + } -inline value::value(const array &a) : type_(array_type), u_() { - u_.array_ = new array(a); -} + inline value::value(const array &a) : type_(array_type), u_() { + u_.array_ = new array(a); + } -inline value::value(const object &o) : type_(object_type), u_() { - u_.object_ = new object(o); -} + inline value::value(const object &o) : type_(object_type), u_() { + u_.object_ = new object(o); + } #if PICOJSON_USE_RVALUE_REFERENCE -inline value::value(std::string &&s) : type_(string_type), u_() { - u_.string_ = new std::string(std::move(s)); -} + inline value::value(std::string &&s) : type_(string_type), u_() { + u_.string_ = new std::string(std::move(s)); + } -inline value::value(array &&a) : type_(array_type), u_() { - u_.array_ = new array(std::move(a)); -} + inline value::value(array &&a) : type_(array_type), u_() { + u_.array_ = new array(std::move(a)); + } -inline value::value(object &&o) : type_(object_type), u_() { - u_.object_ = new object(std::move(o)); -} + inline value::value(object &&o) : type_(object_type), u_() { + u_.object_ = new object(std::move(o)); + } #endif -inline value::value(const char *s) : type_(string_type), u_() { - u_.string_ = new std::string(s); -} + inline value::value(const char *s) : type_(string_type), u_() { + u_.string_ = new std::string(s); + } -inline value::value(const char *s, size_t len) : type_(string_type), u_() { - u_.string_ = new std::string(s, len); -} + inline value::value(const char *s, size_t len) : type_(string_type), u_() { + u_.string_ = new std::string(s, len); + } -inline void value::clear() { - switch (type_) { + inline void value::clear() { + switch (type_) { #define DEINIT(p) \ case p##type: \ delete u_.p; \ break - DEINIT(string_); - DEINIT(array_); - DEINIT(object_); + DEINIT(string_); + DEINIT(array_); + DEINIT(object_); #undef DEINIT - default: - break; - } -} + default: + break; + } + } -inline value::~value() { - clear(); -} + inline value::~value() { + clear(); + } -inline value::value(const value &x) : type_(x.type_), u_() { - switch (type_) { + inline value::value(const value &x) : type_(x.type_), u_() { + switch (type_) { #define INIT(p, v) \ case p##type: \ u_.p = v; \ break - INIT(string_, new std::string(*x.u_.string_)); - INIT(array_, new array(*x.u_.array_)); - INIT(object_, new object(*x.u_.object_)); + INIT(string_, new std::string(*x.u_.string_)); + INIT(array_, new array(*x.u_.array_)); + INIT(object_, new object(*x.u_.object_)); #undef INIT - default: - u_ = x.u_; - break; - } -} + default: + u_ = x.u_; + break; + } + } -inline value &value::operator=(const value &x) { - if (this != &x) { - value t(x); - swap(t); - } - return *this; -} + inline value &value::operator=(const value &x) { + if (this != &x) { + value t(x); + swap(t); + } + return *this; + } #if PICOJSON_USE_RVALUE_REFERENCE -inline value::value(value &&x) PICOJSON_NOEXCEPT : type_(null_type), u_() { - swap(x); -} -inline value &value::operator=(value &&x) PICOJSON_NOEXCEPT { - swap(x); - return *this; -} + inline value::value(value &&x) PICOJSON_NOEXCEPT : type_(null_type), u_() { + swap(x); + } + inline value &value::operator=(value &&x) PICOJSON_NOEXCEPT { + swap(x); + return *this; + } #endif -inline void value::swap(value &x) PICOJSON_NOEXCEPT { - std::swap(type_, x.type_); - std::swap(u_, x.u_); -} + inline void value::swap(value &x) PICOJSON_NOEXCEPT { + std::swap(type_, x.type_); + std::swap(u_, x.u_); + } #define IS(ctype, jtype) \ template <> inline bool value::is() const { \ return type_ == jtype##_type; \ } -IS(null, null) -IS(bool, boolean) + IS(null, null) + IS(bool, boolean) #ifdef PICOJSON_USE_INT64 -IS(int64_t, int64) + IS(int64_t, int64) #endif -IS(std::string, string) -IS(array, array) -IS(object, object) + IS(std::string, string) + IS(array, array) + IS(object, object) #undef IS -template <> inline bool value::is() const { - return type_ == number_type -#ifdef PICOJSON_USE_INT64 - || type_ == int64_type + template <> inline bool value::is() const { + return type_ == number_type + #ifdef PICOJSON_USE_INT64 + || type_ == int64_type #endif - ; -} + ; + } #define GET(ctype, var) \ template <> inline const ctype &value::get() const { \ @@ -377,17 +378,17 @@ template <> inline bool value::is() const { PICOJSON_ASSERT("type mismatch! call is() before get()" && is()); \ return var; \ } -GET(bool, u_.boolean_) -GET(std::string, *u_.string_) -GET(array, *u_.array_) -GET(object, *u_.object_) + GET(bool, u_.boolean_) + GET(std::string, *u_.string_) + GET(array, *u_.array_) + GET(object, *u_.object_) #ifdef PICOJSON_USE_INT64 -GET(double, - (type_ == int64_type && (const_cast(this)->type_ = number_type, const_cast(this)->u_.number_ = u_.int64_), - u_.number_)) -GET(int64_t, u_.int64_) + GET(double, + (type_ == int64_type && (const_cast(this)->type_ = number_type, (const_cast(this)->u_.number_ = u_.int64_)), + u_.number_)) + GET(int64_t, u_.int64_) #else -GET(double, u_.number_) + GET(double, u_.number_) #endif #undef GET @@ -397,13 +398,13 @@ GET(double, u_.number_) type_ = jtype##_type; \ setter \ } -SET(bool, boolean, u_.boolean_ = _val;) -SET(std::string, string, u_.string_ = new std::string(_val);) -SET(array, array, u_.array_ = new array(_val);) -SET(object, object, u_.object_ = new object(_val);) -SET(double, number, u_.number_ = _val;) + SET(bool, boolean, u_.boolean_ = _val;) + SET(std::string, string, u_.string_ = new std::string(_val);) + SET(array, array, u_.array_ = new array(_val);) + SET(object, object, u_.object_ = new object(_val);) + SET(double, number, u_.number_ = _val;) #ifdef PICOJSON_USE_INT64 -SET(int64_t, int64, u_.int64_ = _val;) + SET(int64_t, int64, u_.int64_ = _val;) #endif #undef SET @@ -414,468 +415,468 @@ SET(int64_t, int64, u_.int64_ = _val;) type_ = jtype##_type; \ setter \ } -MOVESET(std::string, string, u_.string_ = new std::string(std::move(_val));) -MOVESET(array, array, u_.array_ = new array(std::move(_val));) -MOVESET(object, object, u_.object_ = new object(std::move(_val));) + MOVESET(std::string, string, u_.string_ = new std::string(std::move(_val));) + MOVESET(array, array, u_.array_ = new array(std::move(_val));) + MOVESET(object, object, u_.object_ = new object(std::move(_val));) #undef MOVESET #endif -inline bool value::evaluate_as_boolean() const { - switch (type_) { - case null_type: - return false; - case boolean_type: - return u_.boolean_; - case number_type: - return u_.number_ != 0; + inline bool value::evaluate_as_boolean() const { + switch (type_) { + case null_type: + return false; + case boolean_type: + return u_.boolean_; + case number_type: + return u_.number_ != 0; #ifdef PICOJSON_USE_INT64 - case int64_type: - return u_.int64_ != 0; + case int64_type: + return u_.int64_ != 0; #endif - case string_type: - return !u_.string_->empty(); - default: - return true; - } -} - -inline const value &value::get(const size_t idx) const { - static value s_null; - PICOJSON_ASSERT(is()); - return idx < u_.array_->size() ? (*u_.array_)[idx] : s_null; -} - -inline value &value::get(const size_t idx) { - static value s_null; - PICOJSON_ASSERT(is()); - return idx < u_.array_->size() ? (*u_.array_)[idx] : s_null; -} - -inline const value &value::get(const std::string &key) const { - static value s_null; - PICOJSON_ASSERT(is()); - object::const_iterator i = u_.object_->find(key); - return i != u_.object_->end() ? i->second : s_null; -} - -inline value &value::get(const std::string &key) { - static value s_null; - PICOJSON_ASSERT(is()); - object::iterator i = u_.object_->find(key); - return i != u_.object_->end() ? i->second : s_null; -} - -inline bool value::contains(const size_t idx) const { - PICOJSON_ASSERT(is()); - return idx < u_.array_->size(); -} - -inline bool value::contains(const std::string &key) const { - PICOJSON_ASSERT(is()); - object::const_iterator i = u_.object_->find(key); - return i != u_.object_->end(); -} - -inline std::string value::to_str() const { - switch (type_) { - case null_type: - return "null"; - case boolean_type: - return u_.boolean_ ? "true" : "false"; -#ifdef PICOJSON_USE_INT64 - case int64_type: { - char buf[sizeof("-9223372036854775808")]; - SNPRINTF(buf, sizeof(buf), "%" PRId64, u_.int64_); - return buf; - } -#endif - case number_type: { - char buf[256]; - double tmp; - SNPRINTF(buf, sizeof(buf), fabs(u_.number_) < (1ULL << 53) && modf(u_.number_, &tmp) == 0 ? "%.f" : "%.17g", u_.number_); -#if PICOJSON_USE_LOCALE - char *decimal_point = localeconv()->decimal_point; - if (strcmp(decimal_point, ".") != 0) { - size_t decimal_point_len = strlen(decimal_point); - for (char *p = buf; *p != '\0'; ++p) { - if (strncmp(p, decimal_point, decimal_point_len) == 0) { - return std::string(buf, p) + "." + (p + decimal_point_len); + case string_type: + return !u_.string_->empty(); + default: + return true; } - } } + + inline const value &value::get(const size_t idx) const { + static value s_null; + PICOJSON_ASSERT(is()); + return idx < u_.array_->size() ? (*u_.array_)[idx] : s_null; + } + + inline value &value::get(const size_t idx) { + static value s_null; + PICOJSON_ASSERT(is()); + return idx < u_.array_->size() ? (*u_.array_)[idx] : s_null; + } + + inline const value &value::get(const std::string &key) const { + static value s_null; + PICOJSON_ASSERT(is()); + object::const_iterator i = u_.object_->find(key); + return i != u_.object_->end() ? i->second : s_null; + } + + inline value &value::get(const std::string &key) { + static value s_null; + PICOJSON_ASSERT(is()); + object::iterator i = u_.object_->find(key); + return i != u_.object_->end() ? i->second : s_null; + } + + inline bool value::contains(const size_t idx) const { + PICOJSON_ASSERT(is()); + return idx < u_.array_->size(); + } + + inline bool value::contains(const std::string &key) const { + PICOJSON_ASSERT(is()); + object::const_iterator i = u_.object_->find(key); + return i != u_.object_->end(); + } + + inline std::string value::to_str() const { + switch (type_) { + case null_type: + return "null"; + case boolean_type: + return u_.boolean_ ? "true" : "false"; +#ifdef PICOJSON_USE_INT64 + case int64_type: { + char buf[sizeof("-9223372036854775808")]; + SNPRINTF(buf, sizeof(buf), "%" PRId64, u_.int64_); + return buf; + } #endif - return buf; - } - case string_type: - return *u_.string_; - case array_type: - return "array"; - case object_type: - return "object"; - default: - PICOJSON_ASSERT(0); + case number_type: { + char buf[256]; + double tmp; + SNPRINTF(buf, sizeof(buf), fabs(u_.number_) < (1ULL << 53) && modf(u_.number_, &tmp) == 0 ? "%.f" : "%.17g", u_.number_); +#if PICOJSON_USE_LOCALE + char *decimal_point = localeconv()->decimal_point; + if (strcmp(decimal_point, ".") != 0) { + size_t decimal_point_len = strlen(decimal_point); + for (char *p = buf; *p != '\0'; ++p) { + if (strncmp(p, decimal_point, decimal_point_len) == 0) { + return std::string(buf, p) + "." + (p + decimal_point_len); + } + } + } +#endif + return buf; + } + case string_type: + return *u_.string_; + case array_type: + return "array"; + case object_type: + return "object"; + default: + PICOJSON_ASSERT(0); #ifdef _MSC_VER - __assume(0); + __assume(0); #endif - } - return std::string(); -} + } + return std::string(); + } -template void copy(const std::string &s, Iter oi) { - std::copy(s.begin(), s.end(), oi); -} + template void copy(const std::string &s, Iter oi) { + std::copy(s.begin(), s.end(), oi); + } -template struct serialize_str_char { - Iter oi; - void operator()(char c) { - switch (c) { + template struct serialize_str_char { + Iter oi; + void operator()(char c) { + switch (c) { #define MAP(val, sym) \ case val: \ copy(sym, oi); \ break - MAP('"', "\\\""); - MAP('\\', "\\\\"); - MAP('/', "\\/"); - MAP('\b', "\\b"); - MAP('\f', "\\f"); - MAP('\n', "\\n"); - MAP('\r', "\\r"); - MAP('\t', "\\t"); + MAP('"', "\\\""); + MAP('\\', "\\\\"); + MAP('/', "\\/"); + MAP('\b', "\\b"); + MAP('\f', "\\f"); + MAP('\n', "\\n"); + MAP('\r', "\\r"); + MAP('\t', "\\t"); #undef MAP - default: - if (static_cast(c) < 0x20 || c == 0x7f) { - char buf[7]; - SNPRINTF(buf, sizeof(buf), "\\u%04x", c & 0xff); - copy(buf, buf + 6, oi); - } else { - *oi++ = c; - } - break; - } - } -}; + default: + if (static_cast(c) < 0x20 || c == 0x7f) { + char buf[7]; + SNPRINTF(buf, sizeof(buf), "\\u%04x", c & 0xff); + copy(buf, buf + 6, oi); + } else { + *oi++ = c; + } + break; + } + } + }; -template void serialize_str(const std::string &s, Iter oi) { - *oi++ = '"'; - serialize_str_char process_char = {oi}; - std::for_each(s.begin(), s.end(), process_char); - *oi++ = '"'; -} + template void serialize_str(const std::string &s, Iter oi) { + *oi++ = '"'; + serialize_str_char process_char = {oi}; + std::for_each(s.begin(), s.end(), process_char); + *oi++ = '"'; + } -template void value::serialize(Iter oi, bool prettify) const { - return _serialize(oi, prettify ? 0 : -1); -} + template void value::serialize(Iter oi, bool prettify) const { + return _serialize(oi, prettify ? 0 : -1); + } -inline std::string value::serialize(bool prettify) const { - return _serialize(prettify ? 0 : -1); -} + inline std::string value::serialize(bool prettify) const { + return _serialize(prettify ? 0 : -1); + } -template void value::_indent(Iter oi, int indent) { - *oi++ = '\n'; - for (int i = 0; i < indent * INDENT_WIDTH; ++i) { - *oi++ = ' '; - } -} + template void value::_indent(Iter oi, int indent) { + *oi++ = '\n'; + for (int i = 0; i < indent * INDENT_WIDTH; ++i) { + *oi++ = ' '; + } + } -template void value::_serialize(Iter oi, int indent) const { - switch (type_) { - case string_type: - serialize_str(*u_.string_, oi); - break; - case array_type: { - *oi++ = '['; - if (indent != -1) { - ++indent; + template void value::_serialize(Iter oi, int indent) const { + switch (type_) { + case string_type: + serialize_str(*u_.string_, oi); + break; + case array_type: { + *oi++ = '['; + if (indent != -1) { + ++indent; + } + for (array::const_iterator i = u_.array_->begin(); i != u_.array_->end(); ++i) { + if (i != u_.array_->begin()) { + *oi++ = ','; + } + if (indent != -1) { + _indent(oi, indent); + } + i->_serialize(oi, indent); + } + if (indent != -1) { + --indent; + if (!u_.array_->empty()) { + _indent(oi, indent); + } + } + *oi++ = ']'; + break; + } + case object_type: { + *oi++ = '{'; + if (indent != -1) { + ++indent; + } + for (object::const_iterator i = u_.object_->begin(); i != u_.object_->end(); ++i) { + if (i != u_.object_->begin()) { + *oi++ = ','; + } + if (indent != -1) { + _indent(oi, indent); + } + serialize_str(i->first, oi); + *oi++ = ':'; + if (indent != -1) { + *oi++ = ' '; + } + i->second._serialize(oi, indent); + } + if (indent != -1) { + --indent; + if (!u_.object_->empty()) { + _indent(oi, indent); + } + } + *oi++ = '}'; + break; + } + default: + copy(to_str(), oi); + break; + } + if (indent == 0) { + *oi++ = '\n'; + } } - for (array::const_iterator i = u_.array_->begin(); i != u_.array_->end(); ++i) { - if (i != u_.array_->begin()) { - *oi++ = ','; - } - if (indent != -1) { - _indent(oi, indent); - } - i->_serialize(oi, indent); - } - if (indent != -1) { - --indent; - if (!u_.array_->empty()) { - _indent(oi, indent); - } - } - *oi++ = ']'; - break; - } - case object_type: { - *oi++ = '{'; - if (indent != -1) { - ++indent; - } - for (object::const_iterator i = u_.object_->begin(); i != u_.object_->end(); ++i) { - if (i != u_.object_->begin()) { - *oi++ = ','; - } - if (indent != -1) { - _indent(oi, indent); - } - serialize_str(i->first, oi); - *oi++ = ':'; - if (indent != -1) { - *oi++ = ' '; - } - i->second._serialize(oi, indent); - } - if (indent != -1) { - --indent; - if (!u_.object_->empty()) { - _indent(oi, indent); - } - } - *oi++ = '}'; - break; - } - default: - copy(to_str(), oi); - break; - } - if (indent == 0) { - *oi++ = '\n'; - } -} -inline std::string value::_serialize(int indent) const { - std::string s; - _serialize(std::back_inserter(s), indent); - return s; -} + inline std::string value::_serialize(int indent) const { + std::string s; + _serialize(std::back_inserter(s), indent); + return s; + } -template class input { -protected: - Iter cur_, end_; - bool consumed_; - int line_; + template class input { + protected: + Iter cur_, end_; + bool consumed_; + int line_; -public: - input(const Iter &first, const Iter &last) : cur_(first), end_(last), consumed_(false), line_(1) { - } - int getc() { - if (consumed_) { - if (*cur_ == '\n') { - ++line_; - } - ++cur_; - } - if (cur_ == end_) { - consumed_ = false; - return -1; - } - consumed_ = true; - return *cur_ & 0xff; - } - void ungetc() { - consumed_ = false; - } - Iter cur() const { - if (consumed_) { - input *self = const_cast *>(this); - self->consumed_ = false; - ++self->cur_; - } - return cur_; - } - int line() const { - return line_; - } - void skip_ws() { - while (1) { - int ch = getc(); - if (!(ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r')) { - ungetc(); - break; - } - } - } - bool expect(const int expected) { - skip_ws(); - if (getc() != expected) { - ungetc(); - return false; - } - return true; - } - bool match(const std::string &pattern) { - for (std::string::const_iterator pi(pattern.begin()); pi != pattern.end(); ++pi) { - if (getc() != *pi) { - ungetc(); - return false; - } - } - return true; - } -}; + public: + input(const Iter &first, const Iter &last) : cur_(first), end_(last), consumed_(false), line_(1) { + } + int getc() { + if (consumed_) { + if (*cur_ == '\n') { + ++line_; + } + ++cur_; + } + if (cur_ == end_) { + consumed_ = false; + return -1; + } + consumed_ = true; + return *cur_ & 0xff; + } + void ungetc() { + consumed_ = false; + } + Iter cur() const { + if (consumed_) { + input *self = const_cast *>(this); + self->consumed_ = false; + ++self->cur_; + } + return cur_; + } + int line() const { + return line_; + } + void skip_ws() { + while (1) { + int ch = getc(); + if (!(ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r')) { + ungetc(); + break; + } + } + } + bool expect(const int expected) { + skip_ws(); + if (getc() != expected) { + ungetc(); + return false; + } + return true; + } + bool match(const std::string &pattern) { + for (std::string::const_iterator pi(pattern.begin()); pi != pattern.end(); ++pi) { + if (getc() != *pi) { + ungetc(); + return false; + } + } + return true; + } + }; -template inline int _parse_quadhex(input &in) { - int uni_ch = 0, hex; - for (int i = 0; i < 4; i++) { - if ((hex = in.getc()) == -1) { - return -1; + template inline int _parse_quadhex(input &in) { + int uni_ch = 0, hex; + for (int i = 0; i < 4; i++) { + if ((hex = in.getc()) == -1) { + return -1; + } + if ('0' <= hex && hex <= '9') { + hex -= '0'; + } else if ('A' <= hex && hex <= 'F') { + hex -= 'A' - 0xa; + } else if ('a' <= hex && hex <= 'f') { + hex -= 'a' - 0xa; + } else { + in.ungetc(); + return -1; + } + uni_ch = uni_ch * 16 + hex; + } + return uni_ch; } - if ('0' <= hex && hex <= '9') { - hex -= '0'; - } else if ('A' <= hex && hex <= 'F') { - hex -= 'A' - 0xa; - } else if ('a' <= hex && hex <= 'f') { - hex -= 'a' - 0xa; - } else { - in.ungetc(); - return -1; - } - uni_ch = uni_ch * 16 + hex; - } - return uni_ch; -} -template inline bool _parse_codepoint(String &out, input &in) { - int uni_ch; - if ((uni_ch = _parse_quadhex(in)) == -1) { - return false; - } - if (0xd800 <= uni_ch && uni_ch <= 0xdfff) { - if (0xdc00 <= uni_ch) { - // a second 16-bit of a surrogate pair appeared - return false; + template inline bool _parse_codepoint(String &out, input &in) { + int uni_ch; + if ((uni_ch = _parse_quadhex(in)) == -1) { + return false; + } + if (0xd800 <= uni_ch && uni_ch <= 0xdfff) { + if (0xdc00 <= uni_ch) { + // a second 16-bit of a surrogate pair appeared + return false; + } + // first 16-bit of surrogate pair, get the next one + if (in.getc() != '\\' || in.getc() != 'u') { + in.ungetc(); + return false; + } + int second = _parse_quadhex(in); + if (!(0xdc00 <= second && second <= 0xdfff)) { + return false; + } + uni_ch = ((uni_ch - 0xd800) << 10) | ((second - 0xdc00) & 0x3ff); + uni_ch += 0x10000; + } + if (uni_ch < 0x80) { + out.push_back(static_cast(uni_ch)); + } else { + if (uni_ch < 0x800) { + out.push_back(static_cast(0xc0 | (uni_ch >> 6))); + } else { + if (uni_ch < 0x10000) { + out.push_back(static_cast(0xe0 | (uni_ch >> 12))); + } else { + out.push_back(static_cast(0xf0 | (uni_ch >> 18))); + out.push_back(static_cast(0x80 | ((uni_ch >> 12) & 0x3f))); + } + out.push_back(static_cast(0x80 | ((uni_ch >> 6) & 0x3f))); + } + out.push_back(static_cast(0x80 | (uni_ch & 0x3f))); + } + return true; } - // first 16-bit of surrogate pair, get the next one - if (in.getc() != '\\' || in.getc() != 'u') { - in.ungetc(); - return false; - } - int second = _parse_quadhex(in); - if (!(0xdc00 <= second && second <= 0xdfff)) { - return false; - } - uni_ch = ((uni_ch - 0xd800) << 10) | ((second - 0xdc00) & 0x3ff); - uni_ch += 0x10000; - } - if (uni_ch < 0x80) { - out.push_back(static_cast(uni_ch)); - } else { - if (uni_ch < 0x800) { - out.push_back(static_cast(0xc0 | (uni_ch >> 6))); - } else { - if (uni_ch < 0x10000) { - out.push_back(static_cast(0xe0 | (uni_ch >> 12))); - } else { - out.push_back(static_cast(0xf0 | (uni_ch >> 18))); - out.push_back(static_cast(0x80 | ((uni_ch >> 12) & 0x3f))); - } - out.push_back(static_cast(0x80 | ((uni_ch >> 6) & 0x3f))); - } - out.push_back(static_cast(0x80 | (uni_ch & 0x3f))); - } - return true; -} -template inline bool _parse_string(String &out, input &in) { - while (1) { - int ch = in.getc(); - if (ch < ' ') { - in.ungetc(); - return false; - } else if (ch == '"') { - return true; - } else if (ch == '\\') { - if ((ch = in.getc()) == -1) { - return false; - } - switch (ch) { + template inline bool _parse_string(String &out, input &in) { + while (1) { + int ch = in.getc(); + if (ch < ' ') { + in.ungetc(); + return false; + } else if (ch == '"') { + return true; + } else if (ch == '\\') { + if ((ch = in.getc()) == -1) { + return false; + } + switch (ch) { #define MAP(sym, val) \ case sym: \ out.push_back(val); \ break - MAP('"', '\"'); - MAP('\\', '\\'); - MAP('/', '/'); - MAP('b', '\b'); - MAP('f', '\f'); - MAP('n', '\n'); - MAP('r', '\r'); - MAP('t', '\t'); + MAP('"', '\"'); + MAP('\\', '\\'); + MAP('/', '/'); + MAP('b', '\b'); + MAP('f', '\f'); + MAP('n', '\n'); + MAP('r', '\r'); + MAP('t', '\t'); #undef MAP - case 'u': - if (!_parse_codepoint(out, in)) { - return false; + case 'u': + if (!_parse_codepoint(out, in)) { + return false; + } + break; + default: + return false; + } + } else { + out.push_back(static_cast(ch)); + } } - break; - default: return false; - } - } else { - out.push_back(static_cast(ch)); } - } - return false; -} -template inline bool _parse_array(Context &ctx, input &in) { - if (!ctx.parse_array_start()) { - return false; - } - size_t idx = 0; - if (in.expect(']')) { - return ctx.parse_array_stop(idx); - } - do { - if (!ctx.parse_array_item(in, idx)) { - return false; + template inline bool _parse_array(Context &ctx, input &in) { + if (!ctx.parse_array_start()) { + return false; + } + size_t idx = 0; + if (in.expect(']')) { + return ctx.parse_array_stop(idx); + } + do { + if (!ctx.parse_array_item(in, idx)) { + return false; + } + idx++; + } while (in.expect(',')); + return in.expect(']') && ctx.parse_array_stop(idx); } - idx++; - } while (in.expect(',')); - return in.expect(']') && ctx.parse_array_stop(idx); -} -template inline bool _parse_object(Context &ctx, input &in) { - if (!ctx.parse_object_start()) { - return false; - } - if (in.expect('}')) { - return true; - } - do { - std::string key; - if (!in.expect('"') || !_parse_string(key, in) || !in.expect(':')) { - return false; + template inline bool _parse_object(Context &ctx, input &in) { + if (!ctx.parse_object_start()) { + return false; + } + if (in.expect('}')) { + return ctx.parse_object_stop(); + } + do { + std::string key; + if (!in.expect('"') || !_parse_string(key, in) || !in.expect(':')) { + return false; + } + if (!ctx.parse_object_item(in, key)) { + return false; + } + } while (in.expect(',')); + return in.expect('}') && ctx.parse_object_stop(); } - if (!ctx.parse_object_item(in, key)) { - return false; - } - } while (in.expect(',')); - return in.expect('}'); -} -template inline std::string _parse_number(input &in) { - std::string num_str; - while (1) { - int ch = in.getc(); - if (('0' <= ch && ch <= '9') || ch == '+' || ch == '-' || ch == 'e' || ch == 'E') { - num_str.push_back(static_cast(ch)); - } else if (ch == '.') { + template inline std::string _parse_number(input &in) { + std::string num_str; + while (1) { + int ch = in.getc(); + if (('0' <= ch && ch <= '9') || ch == '+' || ch == '-' || ch == 'e' || ch == 'E') { + num_str.push_back(static_cast(ch)); + } else if (ch == '.') { #if PICOJSON_USE_LOCALE - num_str += localeconv()->decimal_point; + num_str += localeconv()->decimal_point; #else - num_str.push_back('.'); + num_str.push_back('.'); #endif - } else { - in.ungetc(); - break; + } else { + in.ungetc(); + break; + } + } + return num_str; } - } - return num_str; -} -template inline bool _parse(Context &ctx, input &in) { - in.skip_ws(); - int ch = in.getc(); - switch (ch) { + template inline bool _parse(Context &ctx, input &in) { + in.skip_ws(); + int ch = in.getc(); + switch (ch) { #define IS(ch, text, op) \ case ch: \ if (in.match(text) && op) { \ @@ -883,266 +884,291 @@ template inline bool _parse(Context &ctx, inpu } else { \ return false; \ } - IS('n', "ull", ctx.set_null()); - IS('f', "alse", ctx.set_bool(false)); - IS('t', "rue", ctx.set_bool(true)); + IS('n', "ull", ctx.set_null()); + IS('f', "alse", ctx.set_bool(false)); + IS('t', "rue", ctx.set_bool(true)); #undef IS - case '"': - return ctx.parse_string(in); - case '[': - return _parse_array(ctx, in); - case '{': - return _parse_object(ctx, in); - default: - if (('0' <= ch && ch <= '9') || ch == '-') { - double f; - char *endp; - in.ungetc(); - std::string num_str(_parse_number(in)); - if (num_str.empty()) { - return false; - } + case '"': + return ctx.parse_string(in); + case '[': + return _parse_array(ctx, in); + case '{': + return _parse_object(ctx, in); + default: + if (('0' <= ch && ch <= '9') || ch == '-') { + double f; + char *endp; + in.ungetc(); + std::string num_str(_parse_number(in)); + if (num_str.empty()) { + return false; + } #ifdef PICOJSON_USE_INT64 - { - errno = 0; - intmax_t ival = strtoimax(num_str.c_str(), &endp, 10); - if (errno == 0 && std::numeric_limits::min() <= ival && ival <= std::numeric_limits::max() && - endp == num_str.c_str() + num_str.size()) { - ctx.set_int64(ival); - return true; + { + errno = 0; + intmax_t ival = strtoimax(num_str.c_str(), &endp, 10); + if (errno == 0 && std::numeric_limits::min() <= ival && ival <= std::numeric_limits::max() && + endp == num_str.c_str() + num_str.size()) { + ctx.set_int64(ival); + return true; + } + } +#endif + f = strtod(num_str.c_str(), &endp); + if (endp == num_str.c_str() + num_str.size()) { + ctx.set_number(f); + return true; + } + return false; + } + break; } - } -#endif - f = strtod(num_str.c_str(), &endp); - if (endp == num_str.c_str() + num_str.size()) { - ctx.set_number(f); - return true; - } - return false; + in.ungetc(); + return false; } - break; - } - in.ungetc(); - return false; -} -class deny_parse_context { -public: - bool set_null() { - return false; - } - bool set_bool(bool) { - return false; - } + class deny_parse_context { + public: + bool set_null() { + return false; + } + bool set_bool(bool) { + return false; + } #ifdef PICOJSON_USE_INT64 - bool set_int64(int64_t) { - return false; - } + bool set_int64(int64_t) { + return false; + } #endif - bool set_number(double) { - return false; - } - template bool parse_string(input &) { - return false; - } - bool parse_array_start() { - return false; - } - template bool parse_array_item(input &, size_t) { - return false; - } - bool parse_array_stop(size_t) { - return false; - } - bool parse_object_start() { - return false; - } - template bool parse_object_item(input &, const std::string &) { - return false; - } -}; + bool set_number(double) { + return false; + } + template bool parse_string(input &) { + return false; + } + bool parse_array_start() { + return false; + } + template bool parse_array_item(input &, size_t) { + return false; + } + bool parse_array_stop(size_t) { + return false; + } + bool parse_object_start() { + return false; + } + template bool parse_object_item(input &, const std::string &) { + return false; + } + }; -class default_parse_context { -protected: - value *out_; + class default_parse_context { + protected: + value *out_; + size_t depths_; -public: - default_parse_context(value *out) : out_(out) { - } - bool set_null() { - *out_ = value(); - return true; - } - bool set_bool(bool b) { - *out_ = value(b); - return true; - } + public: + default_parse_context(value *out, size_t depths = DEFAULT_MAX_DEPTHS) : out_(out), depths_(depths) { + } + bool set_null() { + *out_ = value(); + return true; + } + bool set_bool(bool b) { + *out_ = value(b); + return true; + } #ifdef PICOJSON_USE_INT64 - bool set_int64(int64_t i) { - *out_ = value(i); - return true; - } + bool set_int64(int64_t i) { + *out_ = value(i); + return true; + } #endif - bool set_number(double f) { - *out_ = value(f); - return true; - } - template bool parse_string(input &in) { - *out_ = value(string_type, false); - return _parse_string(out_->get(), in); - } - bool parse_array_start() { - *out_ = value(array_type, false); - return true; - } - template bool parse_array_item(input &in, size_t) { - array &a = out_->get(); - a.push_back(value()); - default_parse_context ctx(&a.back()); - return _parse(ctx, in); - } - bool parse_array_stop(size_t) { - return true; - } - bool parse_object_start() { - *out_ = value(object_type, false); - return true; - } - template bool parse_object_item(input &in, const std::string &key) { - object &o = out_->get(); - default_parse_context ctx(&o[key]); - return _parse(ctx, in); - } + bool set_number(double f) { + *out_ = value(f); + return true; + } + template bool parse_string(input &in) { + *out_ = value(string_type, false); + return _parse_string(out_->get(), in); + } + bool parse_array_start() { + if (depths_ == 0) + return false; + --depths_; + *out_ = value(array_type, false); + return true; + } + template bool parse_array_item(input &in, size_t) { + array &a = out_->get(); + a.push_back(value()); + default_parse_context ctx(&a.back(), depths_); + return _parse(ctx, in); + } + bool parse_array_stop(size_t) { + ++depths_; + return true; + } + bool parse_object_start() { + if (depths_ == 0) + return false; + *out_ = value(object_type, false); + return true; + } + template bool parse_object_item(input &in, const std::string &key) { + object &o = out_->get(); + default_parse_context ctx(&o[key], depths_); + return _parse(ctx, in); + } + bool parse_object_stop() { + ++depths_; + return true; + } -private: - default_parse_context(const default_parse_context &); - default_parse_context &operator=(const default_parse_context &); -}; + private: + default_parse_context(const default_parse_context &); + default_parse_context &operator=(const default_parse_context &); + }; -class null_parse_context { -public: - struct dummy_str { - void push_back(int) { - } - }; + class null_parse_context { + protected: + size_t depths_; -public: - null_parse_context() { - } - bool set_null() { - return true; - } - bool set_bool(bool) { - return true; - } + public: + struct dummy_str { + void push_back(int) { + } + }; + + public: + null_parse_context(size_t depths = DEFAULT_MAX_DEPTHS) : depths_(depths) { + } + bool set_null() { + return true; + } + bool set_bool(bool) { + return true; + } #ifdef PICOJSON_USE_INT64 - bool set_int64(int64_t) { - return true; - } + bool set_int64(int64_t) { + return true; + } #endif - bool set_number(double) { - return true; - } - template bool parse_string(input &in) { - dummy_str s; - return _parse_string(s, in); - } - bool parse_array_start() { - return true; - } - template bool parse_array_item(input &in, size_t) { - return _parse(*this, in); - } - bool parse_array_stop(size_t) { - return true; - } - bool parse_object_start() { - return true; - } - template bool parse_object_item(input &in, const std::string &) { - return _parse(*this, in); - } + bool set_number(double) { + return true; + } + template bool parse_string(input &in) { + dummy_str s; + return _parse_string(s, in); + } + bool parse_array_start() { + if (depths_ == 0) + return false; + --depths_; + return true; + } + template bool parse_array_item(input &in, size_t) { + return _parse(*this, in); + } + bool parse_array_stop(size_t) { + ++depths_; + return true; + } + bool parse_object_start() { + if (depths_ == 0) + return false; + --depths_; + return true; + } + template bool parse_object_item(input &in, const std::string &) { + ++depths_; + return _parse(*this, in); + } + bool parse_object_stop() { + return true; + } -private: - null_parse_context(const null_parse_context &); - null_parse_context &operator=(const null_parse_context &); -}; + private: + null_parse_context(const null_parse_context &); + null_parse_context &operator=(const null_parse_context &); + }; // obsolete, use the version below -template inline std::string parse(value &out, Iter &pos, const Iter &last) { - std::string err; - pos = parse(out, pos, last, &err); - return err; -} - -template inline Iter _parse(Context &ctx, const Iter &first, const Iter &last, std::string *err) { - input in(first, last); - if (!_parse(ctx, in) && err != NULL) { - char buf[64]; - SNPRINTF(buf, sizeof(buf), "syntax error at line %d near: ", in.line()); - *err = buf; - while (1) { - int ch = in.getc(); - if (ch == -1 || ch == '\n') { - break; - } else if (ch >= ' ') { - err->push_back(static_cast(ch)); - } + template inline std::string parse(value &out, Iter &pos, const Iter &last) { + std::string err; + pos = parse(out, pos, last, &err); + return err; } - } - return in.cur(); -} -template inline Iter parse(value &out, const Iter &first, const Iter &last, std::string *err) { - default_parse_context ctx(&out); - return _parse(ctx, first, last, err); -} + template inline Iter _parse(Context &ctx, const Iter &first, const Iter &last, std::string *err) { + input in(first, last); + if (!_parse(ctx, in) && err != NULL) { + char buf[64]; + SNPRINTF(buf, sizeof(buf), "syntax error at line %d near: ", in.line()); + *err = buf; + while (1) { + int ch = in.getc(); + if (ch == -1 || ch == '\n') { + break; + } else if (ch >= ' ') { + err->push_back(static_cast(ch)); + } + } + } + return in.cur(); + } -inline std::string parse(value &out, const std::string &s) { - std::string err; - parse(out, s.begin(), s.end(), &err); - return err; -} + template inline Iter parse(value &out, const Iter &first, const Iter &last, std::string *err) { + default_parse_context ctx(&out); + return _parse(ctx, first, last, err); + } -inline std::string parse(value &out, std::istream &is) { - std::string err; - parse(out, std::istreambuf_iterator(is.rdbuf()), std::istreambuf_iterator(), &err); - return err; -} + inline std::string parse(value &out, const std::string &s) { + std::string err; + parse(out, s.begin(), s.end(), &err); + return err; + } -template struct last_error_t { static std::string s; }; -template std::string last_error_t::s; + inline std::string parse(value &out, std::istream &is) { + std::string err; + parse(out, std::istreambuf_iterator(is.rdbuf()), std::istreambuf_iterator(), &err); + return err; + } -inline void set_last_error(const std::string &s) { - last_error_t::s = s; -} + template struct last_error_t { static std::string s; }; + template std::string last_error_t::s; -inline const std::string &get_last_error() { - return last_error_t::s; -} + inline void set_last_error(const std::string &s) { + last_error_t::s = s; + } -inline bool operator==(const value &x, const value &y) { - if (x.is()) - return y.is(); + inline const std::string &get_last_error() { + return last_error_t::s; + } + + inline bool operator==(const value &x, const value &y) { + if (x.is()) + return y.is(); #define PICOJSON_CMP(type) \ if (x.is()) \ return y.is() && x.get() == y.get() - PICOJSON_CMP(bool); - PICOJSON_CMP(double); - PICOJSON_CMP(std::string); - PICOJSON_CMP(array); - PICOJSON_CMP(object); + PICOJSON_CMP(bool); + PICOJSON_CMP(double); + PICOJSON_CMP(std::string); + PICOJSON_CMP(array); + PICOJSON_CMP(object); #undef PICOJSON_CMP - PICOJSON_ASSERT(0); + PICOJSON_ASSERT(0); #ifdef _MSC_VER - __assume(0); + __assume(0); #endif - return false; -} + return false; + } -inline bool operator!=(const value &x, const value &y) { - return !(x == y); -} + inline bool operator!=(const value &x, const value &y) { + return !(x == y); + } } #if !PICOJSON_USE_RVALUE_REFERENCE @@ -1154,18 +1180,18 @@ template <> inline void swap(picojson::value &x, picojson::value &y) { #endif inline std::istream &operator>>(std::istream &is, picojson::value &x) { - picojson::set_last_error(std::string()); - const std::string err(picojson::parse(x, is)); - if (!err.empty()) { - picojson::set_last_error(err); - is.setstate(std::ios::failbit); - } - return is; + picojson::set_last_error(std::string()); + const std::string err(picojson::parse(x, is)); + if (!err.empty()) { + picojson::set_last_error(err); + is.setstate(std::ios::failbit); + } + return is; } inline std::ostream &operator<<(std::ostream &os, const picojson::value &x) { - x.serialize(std::ostream_iterator(os)); - return os; + x.serialize(std::ostream_iterator(os)); + return os; } #ifdef _MSC_VER #pragma warning(pop)