From f4c2c61e4d8343f8bbc5e1ce8551bc7fb0ec5f9d Mon Sep 17 00:00:00 2001 From: "Justin R. Wilson" Date: Mon, 26 Feb 2024 11:12:32 -0600 Subject: [PATCH] HSDS writer and reader requests are unauthenticated Problem ------- The API's exposed by the HSDS do not authenticate callers. Solution -------- Require a JWT and ensure that the caller is same as the DPM application of the writer/reader. Technically, the API verifies the JWT using the certificate from the DPM. It then checks that the principal is the same as for the credentials used to download DDS security documents from the DPM. Note ---- TLS is not supported (yet). Users should still run the writer and reader with a reverse proxy that can provide TLS termination. --- .github/workflows/build.yml | 4 +- .../CommunitySecurityPlugin.cpp | 7 +- Dockerfile | 35 +- HSDS/Common/Application.cpp | 75 +- HSDS/Common/Application.h | 6 + HSDS/Common/HSDSCommon.mpc | 1 + HSDS/Common/HsdsResource.h | 51 +- HSDS/Common/README.md | 1 + HSDS/Common/ResourceBase.h | 32 + HSDS/Common/jwt-cpp/base.h | 270 ++ HSDS/Common/jwt-cpp/jwt.h | 3655 +++++++++++++++++ .../jwt-cpp/traits/boost-json/defaults.h | 88 + .../Common/jwt-cpp/traits/boost-json/traits.h | 80 + .../traits/danielaparker-jsoncons/defaults.h | 88 + .../traits/danielaparker-jsoncons/traits.h | 123 + .../Common/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 + .../jwt-cpp/traits/nlohmann-json/traits.h | 77 + HSDS/Common/picojson/picojson.h | 1200 ++++++ 21 files changed, 6097 insertions(+), 34 deletions(-) create mode 100644 HSDS/Common/README.md create mode 100644 HSDS/Common/jwt-cpp/base.h create mode 100644 HSDS/Common/jwt-cpp/jwt.h create mode 100644 HSDS/Common/jwt-cpp/traits/boost-json/defaults.h create mode 100644 HSDS/Common/jwt-cpp/traits/boost-json/traits.h create mode 100644 HSDS/Common/jwt-cpp/traits/danielaparker-jsoncons/defaults.h create mode 100644 HSDS/Common/jwt-cpp/traits/danielaparker-jsoncons/traits.h create mode 100644 HSDS/Common/jwt-cpp/traits/defaults.h.mustache create mode 100644 HSDS/Common/jwt-cpp/traits/kazuho-picojson/defaults.h create mode 100644 HSDS/Common/jwt-cpp/traits/kazuho-picojson/traits.h create mode 100644 HSDS/Common/jwt-cpp/traits/nlohmann-json/defaults.h create mode 100644 HSDS/Common/jwt-cpp/traits/nlohmann-json/traits.h create mode 100644 HSDS/Common/picojson/picojson.h diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 86a6701..fbed5f7 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -40,7 +40,7 @@ jobs: shell: bash run: | cd OpenDDS - ./configure --ace-github-latest --security + ./configure --ace-github-latest --std=c++17 --security - name: get OpenDDS commit shell: bash run: | @@ -137,7 +137,7 @@ jobs: with: repository: etr/libhttpserver path: libhttpserver - ref: 0.18.2 + ref: 0.19.0 - name: install libhttpserver shell: bash run: | diff --git a/CommunitySecurityPlugin/CommunitySecurityPlugin.cpp b/CommunitySecurityPlugin/CommunitySecurityPlugin.cpp index 0622b62..e9c7025 100644 --- a/CommunitySecurityPlugin/CommunitySecurityPlugin.cpp +++ b/CommunitySecurityPlugin/CommunitySecurityPlugin.cpp @@ -85,7 +85,12 @@ class AccessControl : public OpenDDS::Security::AccessControlBuiltInImpl { return OpenDDS::Security::CommonUtilities::set_security_error(ex, -1, 0, "CommunityAccessControl::common_check: Subject name does not contain a SN"); } - if (pos->second != dpmgid.in()) { + const std::map::const_iterator pos2 = pos->find("SN"); + if (pos2 == pos->end()) { + return OpenDDS::Security::CommonUtilities::set_security_error(ex, -1, 0, "CommunityAccessControl::common_check: Subject name does not contain a DPMGID"); + } + + if (pos2->second != dpmgid.in()) { return OpenDDS::Security::CommonUtilities::set_security_error(ex, -1, 0, "CommunityAccessControl::common_check: dpmgid from sample does not match SN from subject name"); } diff --git a/Dockerfile b/Dockerfile index fdf9eb2..e0dda1d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,8 +1,39 @@ # Copyright 2023 CommunityUtils Authors -ARG BASIS=ghcr.io/opendds/opendds:master +ARG BASIS=ubuntu:focal FROM $BASIS +ENV DEBIAN_FRONTEND=noninteractive +RUN apt-get update && apt-get install -y \ + cmake \ + curl \ + g++ \ + make \ + libxerces-c-dev \ + libssl-dev \ + perl-base \ + perl-modules \ + git + +ADD . /opt/OpenDDS + +ARG ACE_CONFIG_OPTION="--doc-group" +RUN cd /opt/OpenDDS && \ + ./configure --prefix=/usr/local --std=c++17 --security ${ACE_CONFIG_OPTION} && \ + ./tools/scripts/show_build_config.pl && \ + make && \ + make install && \ + ldconfig && \ + . /opt/OpenDDS/setenv.sh && \ + cp -a ${MPC_ROOT} /usr/local/share/MPC + +ENV ACE_ROOT=/usr/local/share/ace \ + TAO_ROOT=/usr/local/share/tao \ + DDS_ROOT=/usr/local/share/dds \ + MPC_ROOT=/usr/local/share/MPC + +WORKDIR /opt/workspace + ARG DEBIAN_FRONTEND=noninteractive RUN apt-get update -y && apt-get install -y wget automake autoconf libtool libmicrohttpd-dev @@ -10,7 +41,7 @@ RUN wget https://github.com/curl/curl/releases/download/curl-7_87_0/curl-7.87.0. RUN git clone --depth 1 --branch 3.1 --single-branch https://github.com/JosephP91/curlcpp.git && cd curlcpp && mkdir build && cd build && cmake .. -DBUILD_SHARED_LIBS=SHARED && make && make install -RUN git clone --depth 1 --branch 0.18.2 --single-branch https://github.com/etr/libhttpserver.git && cd libhttpserver && ./bootstrap && mkdir build && cd build && ../configure && make && make install +RUN git clone --depth 1 --branch 0.19.0 --single-branch https://github.com/etr/libhttpserver.git && cd libhttpserver && ./bootstrap && mkdir build && cd build && ../configure && make && make install ADD . /opt/CommunityUtils ENV COMMUNITY_UTILS_ROOT=/opt/CommunityUtils diff --git a/HSDS/Common/Application.cpp b/HSDS/Common/Application.cpp index d993d09..12a62a9 100644 --- a/HSDS/Common/Application.cpp +++ b/HSDS/Common/Application.cpp @@ -331,25 +331,62 @@ Application::download_security_documents() return ret; } - std::string key_pair; - ret = download_security_document(easy, dpm_url_ + "/api/applications/key_pair?nonce=" + nonce, key_pair); - if (ret != DDS::RETCODE_OK) { - return ret; + { + std::string key_pair; + ret = download_security_document(easy, dpm_url_ + "/api/applications/key_pair?nonce=" + nonce, key_pair); + if (ret != DDS::RETCODE_OK) { + return ret; + } + + rapidjson::Document document; + document.Parse(key_pair.c_str()); + + if (document.IsObject() && + document.HasMember("public") && + document["public"].IsString() && + document.HasMember("private") && + document["private"].IsString()) { + public_key_ = document["public"].GetString(); + private_key_ = document["private"].GetString(); + } else { + ACE_ERROR((LM_ERROR, "ERROR: Application::download_security_documents: Could not extract public and private key\n")); + return DDS::RETCODE_ERROR; + } } - rapidjson::Document document; - document.Parse(key_pair.c_str()); - - if (document.IsObject() && - document.HasMember("public") && - document["public"].IsString() && - document.HasMember("private") && - document["private"].IsString()) { - public_key_ = document["public"].GetString(); - private_key_ = document["private"].GetString(); - } else { - ACE_ERROR((LM_ERROR, "ERROR: Application::download_security_documents: Could not extract public and private key\n")); - return DDS::RETCODE_ERROR; + { + verifier_.with_subject(dpm_application_id_); + + std::string public_keys; + ret = download_security_document(easy, dpm_url_ + "/auth/public_keys", public_keys); + if (ret != DDS::RETCODE_OK) { + return ret; + } + + bool have_key = false; + rapidjson::Document document; + document.Parse(public_keys.c_str()); + if (document.IsArray()) { + for (rapidjson::SizeType idx = 0; idx != document.Size(); ++idx) { + if (document[idx].IsObject() && + document[idx].HasMember("public") && + document[idx]["public"].IsString()) { + verifier_.allow_algorithm(jwt::algorithm::rs256(document[idx]["public"].GetString())); + have_key = true; + } else { + ACE_ERROR((LM_ERROR, "ERROR: Application::download_security_documents: DPM public key is not a string\n")); + return DDS::RETCODE_ERROR; + } + } + } else { + ACE_ERROR((LM_ERROR, "ERROR: Application::download_security_documents: Could not extract DPM public keys\n")); + return DDS::RETCODE_ERROR; + } + + if (!have_key) { + ACE_ERROR((LM_ERROR, "ERROR: Application::download_security_documents: No DPM public keys\n")); + return DDS::RETCODE_ERROR; + } } return DDS::RETCODE_OK; @@ -424,8 +461,8 @@ Application::setup_discovery() OpenDDS::DCPS::RcHandle discovery = OpenDDS::DCPS::make_rch("RtpsDiscovery"); discovery->config()->use_rtps_relay(true); discovery->config()->rtps_relay_only(rtps_relay_only_); - discovery->config()->spdp_rtps_relay_address(ACE_INET_Addr(spdp_rtps_relay_address_.c_str())); - discovery->config()->sedp_rtps_relay_address(ACE_INET_Addr(sedp_rtps_relay_address_.c_str())); + discovery->config()->spdp_rtps_relay_address(OpenDDS::DCPS::NetworkAddress(spdp_rtps_relay_address_.c_str())); + discovery->config()->sedp_rtps_relay_address(OpenDDS::DCPS::NetworkAddress(sedp_rtps_relay_address_.c_str())); discovery->config()->sedp_max_message_size(1400); discovery->config()->secure_participant_user_data(true); discovery->config()->sedp_responsive_mode(true); diff --git a/HSDS/Common/Application.h b/HSDS/Common/Application.h index 9901da7..9cc3147 100644 --- a/HSDS/Common/Application.h +++ b/HSDS/Common/Application.h @@ -11,6 +11,8 @@ #include #include +#include "jwt-cpp/jwt.h" + template struct Unit { Unit(const std::string& key, @@ -41,6 +43,7 @@ class HSDS_Common_Export Application { : domain_id_(1) , http_port_(8080) , create_writers_(true) + , verifier_(jwt::verify().with_issuer("dds-permissions-manager")) , rtps_relay_only_(true) , enable_http_log_access_(true) , enable_observer_(true) @@ -440,6 +443,8 @@ class HSDS_Common_Export Application { ACE_Thread_Mutex& get_mutex() { return mutex_; } + + const jwt::verifier& verifier() const { return verifier_; } private: std::string dpm_url_; std::string dpm_gid_; @@ -463,6 +468,7 @@ class HSDS_Common_Export Application { std::string public_key_; std::string private_key_; std::string permissions_; + jwt::verifier verifier_; bool rtps_relay_only_; std::string spdp_rtps_relay_address_; diff --git a/HSDS/Common/HSDSCommon.mpc b/HSDS/Common/HSDSCommon.mpc index 94617f1..502b7fd 100644 --- a/HSDS/Common/HSDSCommon.mpc +++ b/HSDS/Common/HSDSCommon.mpc @@ -5,6 +5,7 @@ project: opendds_cxx11, dcps_rtps_udp, CommunitySecurityPlugin, install { lit_libs += curlcpp idlflags += -Wb,export_include=HSDS_Common_export.h -Wb,export_macro=HSDS_Common_Export -SS dcps_ts_flags += -Gxtypes-complete -Gequality -Wb,export_include=HSDS_Common_export.h -Wb,export_macro=HSDS_Common_Export + includes += . TypeSupport_Files { HSDS3.idl diff --git a/HSDS/Common/HsdsResource.h b/HSDS/Common/HsdsResource.h index 5fe0a98..b89167e 100644 --- a/HSDS/Common/HsdsResource.h +++ b/HSDS/Common/HsdsResource.h @@ -24,7 +24,11 @@ class HsdsElementResource : public UnitResourceBase { ws.register_resource(application.unit().endpoint + "/{dpmgid}/{id}", this); } - const std::shared_ptr render_PUT(const httpserver::http_request& request) { + std::shared_ptr render_PUT(const httpserver::http_request& request) { + if (!ResourceBase::is_authorized(request)) { + return this->respond(ErrorResponse::make_unauthorized()); + } + // Extract the dpmgid and id from the URL. const std::string dpmgid = request.get_arg("dpmgid"); const std::string id = request.get_arg("id"); @@ -40,7 +44,8 @@ class HsdsElementResource : public UnitResourceBase { } // Parse the input. - rapidjson::StringStream ss(request.get_content().c_str()); + const std::string s(request.get_content()); + rapidjson::StringStream ss(s.c_str()); T element; if (!OpenDDS::DCPS::from_json(element, ss)) { ACE_ERROR((LM_NOTICE, "NOTICE: HsdsElementResource::render_PUT: failed to parse input json!!\n")); @@ -73,7 +78,11 @@ class HsdsElementResource : public UnitResourceBase { return this->respond_with_json(OpenDDS::DCPS::to_json(element)); } - const std::shared_ptr render_GET(const httpserver::http_request& request) { + std::shared_ptr render_GET(const httpserver::http_request& request) { + if (!ResourceBase::is_authorized(request)) { + return this->respond(ErrorResponse::make_unauthorized()); + } + // Extract the dpmgid and id from the URL. const std::string dpmgid = request.get_arg("dpmgid"); const std::string id = request.get_arg("id"); @@ -100,7 +109,11 @@ class HsdsElementResource : public UnitResourceBase { return this->respond_with_json(OpenDDS::DCPS::to_json(*pos)); } - const std::shared_ptr render_DELETE(const httpserver::http_request& request) { + std::shared_ptr render_DELETE(const httpserver::http_request& request) { + if (!ResourceBase::is_authorized(request)) { + return this->respond(ErrorResponse::make_unauthorized()); + } + // Extract the dpmgid and id from the URL. const std::string dpmgid = request.get_arg("dpmgid"); const std::string id = request.get_arg("id"); @@ -150,12 +163,17 @@ class HsdsCollectionResource : public UnitResourceBase { ws.register_resource(application.unit().endpoint, this); } - const std::shared_ptr render_PUT(const httpserver::http_request& request) { + std::shared_ptr render_PUT(const httpserver::http_request& request) { + if (!ResourceBase::is_authorized(request)) { + return this->respond(ErrorResponse::make_unauthorized()); + } + typedef typename UnitResourceBase::ContainerType ContainerType; ContainerType new_container; // Parse the input. - rapidjson::StringStream ss(request.get_content().c_str()); + const std::string s(request.get_content()); + rapidjson::StringStream ss(s.c_str()); OpenDDS::DCPS::JsonValueReader jvr(ss); if (!jvr.begin_sequence()) { ACE_ERROR((LM_NOTICE, "NOTICE: HsdsCollectionResource::render_PUT: begin_sequence failed!\n")); @@ -271,11 +289,16 @@ class HsdsCollectionResource : public UnitResourceBase { return this->respond_with_no_content(); } - const std::shared_ptr render_POST(const httpserver::http_request& request) { + std::shared_ptr render_POST(const httpserver::http_request& request) { + if (!ResourceBase::is_authorized(request)) { + return this->respond(ErrorResponse::make_unauthorized()); + } + std::vector list; // Parse the input. - rapidjson::StringStream ss(request.get_content().c_str()); + const std::string s(request.get_content()); + rapidjson::StringStream ss(s.c_str()); OpenDDS::DCPS::JsonValueReader jvr(ss); if (!jvr.begin_sequence()) { ACE_ERROR((LM_NOTICE, "NOTICE: HsdsCollectionResource::render_POST: begin_sequence failed!\n")); @@ -321,7 +344,11 @@ class HsdsCollectionResource : public UnitResourceBase { return this->respond_with_no_content(); } - const std::shared_ptr render_GET(const httpserver::http_request& request) { + std::shared_ptr render_GET(const httpserver::http_request& request) { + if (!ResourceBase::is_authorized(request)) { + return this->respond(ErrorResponse::make_unauthorized()); + } + size_t offset = 0; size_t count = this->unit_.container.size(); @@ -362,7 +389,11 @@ class HsdsCollectionResource : public UnitResourceBase { return response; } - const std::shared_ptr render_DELETE(const httpserver::http_request&) { + std::shared_ptr render_DELETE(const httpserver::http_request& request) { + if (!ResourceBase::is_authorized(request)) { + return this->respond(ErrorResponse::make_unauthorized()); + } + ACE_GUARD_RETURN(ACE_Thread_Mutex, g, this->application_.get_mutex(), this->respond(ErrorResponse::make_internal_server_error())); // Unregister. diff --git a/HSDS/Common/README.md b/HSDS/Common/README.md new file mode 100644 index 0000000..6749c95 --- /dev/null +++ b/HSDS/Common/README.md @@ -0,0 +1 @@ +jwt-cpp and picojson are from https://github.com/Thalhammer/jwt-cpp diff --git a/HSDS/Common/ResourceBase.h b/HSDS/Common/ResourceBase.h index c819f7b..dead3ee 100644 --- a/HSDS/Common/ResourceBase.h +++ b/HSDS/Common/ResourceBase.h @@ -22,6 +22,11 @@ class ErrorResponse { , message_("Bad Request") {} + static ErrorResponse make_unauthorized() + { + return ErrorResponse(httpserver::http::http_utils::http_unauthorized, "Unauthorized"); + } + static ErrorResponse make_bad_request(const std::string& message) { return ErrorResponse(httpserver::http::http_utils::http_bad_request, message); @@ -61,6 +66,33 @@ class ResourceBase : public httpserver::http_resource { virtual void add_headers(std::shared_ptr) const {} + bool is_authorized(const httpserver::http_request& request) const + { + const auto auth = request.get_header("Authorization"); + + const auto scheme_term = auth.find(' ', 0); + if (scheme_term == std::string::npos) { + ACE_ERROR((LM_NOTICE, "NOTICE: ResourceBase::is_authorized: can't find JWT\n")); + return false; + } + if (auth.substr(0, scheme_term) != "Bearer") { + ACE_ERROR((LM_NOTICE, "NOTICE: ResourceBase::is_authorized: can't find JWT\n")); + return false; + } + + const std::string token(auth.substr(scheme_term + 1)); + const auto decoded = jwt::decode(token); + std::error_code ec; + application_.verifier().verify(decoded, ec); + + if (ec) { + ACE_ERROR((LM_NOTICE, "NOTICE: ResourceBase::is_authorized: JWT could not be verified!\n")); + return false; + } + + return true; + } + // TODO: Could we have done this in IDL? bool check_input(ErrorResponse& err, const HSDS3::Accessibility& value) const diff --git a/HSDS/Common/jwt-cpp/base.h b/HSDS/Common/jwt-cpp/base.h new file mode 100644 index 0000000..fd3ee28 --- /dev/null +++ b/HSDS/Common/jwt-cpp/base.h @@ -0,0 +1,270 @@ +#ifndef JWT_CPP_BASE_H +#define JWT_CPP_BASE_H + +#include +#include +#include +#include +#include +#include + +#ifdef __has_cpp_attribute +#if __has_cpp_attribute(fallthrough) +#define JWT_FALLTHROUGH [[fallthrough]] +#endif +#endif + +#ifndef JWT_FALLTHROUGH +#define JWT_FALLTHROUGH +#endif + +namespace jwt { + /** + * \brief character maps when encoding and decoding + */ + namespace alphabet { + /** + * \brief valid list of character when working with [Base64](https://datatracker.ietf.org/doc/html/rfc4648#section-4) + * + * As directed in [X.509 Parameter](https://datatracker.ietf.org/doc/html/rfc7517#section-4.7) certificate chains are + * base64-encoded as per [Section 4 of RFC4648](https://datatracker.ietf.org/doc/html/rfc4648#section-4) + */ + struct base64 { + static const std::array& data() { + static constexpr std::array data{ + {'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', + 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', + 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', + 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'}}; + return data; + } + static const std::string& fill() { + static const std::string fill{"="}; + return fill; + } + }; + /** + * \brief valid list of character when working with [Base64URL](https://tools.ietf.org/html/rfc4648#section-5) + * + * As directed by [RFC 7519 Terminology](https://datatracker.ietf.org/doc/html/rfc7519#section-2) set the definition of Base64URL + * encoding as that in [RFC 7515](https://datatracker.ietf.org/doc/html/rfc7515#section-2) that states: + * + * > Base64 encoding using the URL- and filename-safe character set defined in + * > [Section 5 of RFC 4648 RFC4648](https://tools.ietf.org/html/rfc4648#section-5), with all trailing '=' characters omitted + */ + struct base64url { + static const std::array& data() { + static constexpr std::array data{ + {'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', + 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', + 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', + 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-', '_'}}; + return data; + } + static const std::string& fill() { + static const std::string fill{"%3d"}; + return fill; + } + }; + namespace helper { + /** + * @brief A General purpose base64url alphabet respecting the + * [URI Case Normalization](https://datatracker.ietf.org/doc/html/rfc3986#section-6.2.2.1) + * + * This is useful in situations outside of JWT encoding/decoding and is provided as a helper + */ + struct base64url_percent_encoding { + static const std::array& data() { + static constexpr std::array data{ + {'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', + 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', + 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', + 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-', '_'}}; + return data; + } + static const std::vector& fill() { + static const std::vector fill{"%3D", "%3d"}; + return fill; + } + }; + } // namespace helper + + inline uint32_t index(const std::array& alphabet, char symbol) { + auto itr = std::find_if(alphabet.cbegin(), alphabet.cend(), [symbol](char c) { return c == symbol; }); + if (itr == alphabet.cend()) { throw std::runtime_error("Invalid input: not within alphabet"); } + + return std::distance(alphabet.cbegin(), itr); + } + } // namespace alphabet + + /** + * \brief A collection of fellable functions for working with base64 and base64url + */ + namespace base { + + namespace details { + struct padding { + size_t count = 0; + size_t length = 0; + + padding() = default; + padding(size_t count, size_t length) : count(count), length(length) {} + + padding operator+(const padding& p) { return padding(count + p.count, length + p.length); } + + friend bool operator==(const padding& lhs, const padding& rhs) { + return lhs.count == rhs.count && lhs.length == rhs.length; + } + }; + + inline padding count_padding(const std::string& base, const std::vector& fills) { + for (const auto& fill : fills) { + if (base.size() < fill.size()) continue; + // Does the end of the input exactly match the fill pattern? + if (base.substr(base.size() - fill.size()) == fill) { + return padding{1, fill.length()} + + count_padding(base.substr(0, base.size() - fill.size()), fills); + } + } + + return {}; + } + + inline std::string encode(const std::string& bin, const std::array& alphabet, + const std::string& fill) { + size_t size = bin.size(); + std::string res; + + // clear incomplete bytes + size_t fast_size = size - size % 3; + for (size_t i = 0; i < fast_size;) { + uint32_t octet_a = static_cast(bin[i++]); + uint32_t octet_b = static_cast(bin[i++]); + uint32_t octet_c = static_cast(bin[i++]); + + uint32_t triple = (octet_a << 0x10) + (octet_b << 0x08) + octet_c; + + res += alphabet[(triple >> 3 * 6) & 0x3F]; + res += alphabet[(triple >> 2 * 6) & 0x3F]; + res += alphabet[(triple >> 1 * 6) & 0x3F]; + res += alphabet[(triple >> 0 * 6) & 0x3F]; + } + + if (fast_size == size) return res; + + size_t mod = size % 3; + + uint32_t octet_a = fast_size < size ? static_cast(bin[fast_size++]) : 0; + uint32_t octet_b = fast_size < size ? static_cast(bin[fast_size++]) : 0; + uint32_t octet_c = fast_size < size ? static_cast(bin[fast_size++]) : 0; + + uint32_t triple = (octet_a << 0x10) + (octet_b << 0x08) + octet_c; + + switch (mod) { + case 1: + res += alphabet[(triple >> 3 * 6) & 0x3F]; + res += alphabet[(triple >> 2 * 6) & 0x3F]; + res += fill; + res += fill; + break; + case 2: + res += alphabet[(triple >> 3 * 6) & 0x3F]; + res += alphabet[(triple >> 2 * 6) & 0x3F]; + res += alphabet[(triple >> 1 * 6) & 0x3F]; + res += fill; + break; + default: break; + } + + return res; + } + + inline std::string decode(const std::string& base, const std::array& alphabet, + const std::vector& fill) { + const auto pad = count_padding(base, fill); + if (pad.count > 2) throw std::runtime_error("Invalid input: too much fill"); + + const size_t size = base.size() - pad.length; + if ((size + pad.count) % 4 != 0) throw std::runtime_error("Invalid input: incorrect total size"); + + size_t out_size = size / 4 * 3; + std::string res; + res.reserve(out_size); + + auto get_sextet = [&](size_t offset) { return alphabet::index(alphabet, base[offset]); }; + + size_t fast_size = size - size % 4; + for (size_t i = 0; i < fast_size;) { + uint32_t sextet_a = get_sextet(i++); + uint32_t sextet_b = get_sextet(i++); + uint32_t sextet_c = get_sextet(i++); + uint32_t sextet_d = get_sextet(i++); + + uint32_t triple = + (sextet_a << 3 * 6) + (sextet_b << 2 * 6) + (sextet_c << 1 * 6) + (sextet_d << 0 * 6); + + res += static_cast((triple >> 2 * 8) & 0xFFU); + res += static_cast((triple >> 1 * 8) & 0xFFU); + res += static_cast((triple >> 0 * 8) & 0xFFU); + } + + if (pad.count == 0) return res; + + uint32_t triple = (get_sextet(fast_size) << 3 * 6) + (get_sextet(fast_size + 1) << 2 * 6); + + switch (pad.count) { + case 1: + triple |= (get_sextet(fast_size + 2) << 1 * 6); + res += static_cast((triple >> 2 * 8) & 0xFFU); + res += static_cast((triple >> 1 * 8) & 0xFFU); + break; + case 2: res += static_cast((triple >> 2 * 8) & 0xFFU); break; + default: break; + } + + return res; + } + + inline std::string decode(const std::string& base, const std::array& alphabet, + const std::string& fill) { + return decode(base, alphabet, std::vector{fill}); + } + + inline std::string pad(const std::string& base, const std::string& fill) { + std::string padding; + switch (base.size() % 4) { + case 1: padding += fill; JWT_FALLTHROUGH; + case 2: padding += fill; JWT_FALLTHROUGH; + case 3: padding += fill; JWT_FALLTHROUGH; + default: break; + } + + return base + padding; + } + + inline std::string trim(const std::string& base, const std::string& fill) { + auto pos = base.find(fill); + return base.substr(0, pos); + } + } // namespace details + + template + std::string encode(const std::string& bin) { + return details::encode(bin, T::data(), T::fill()); + } + template + std::string decode(const std::string& base) { + return details::decode(base, T::data(), T::fill()); + } + template + std::string pad(const std::string& base) { + return details::pad(base, T::fill()); + } + template + std::string trim(const std::string& base) { + return details::trim(base, T::fill()); + } + } // namespace base +} // namespace jwt + +#endif diff --git a/HSDS/Common/jwt-cpp/jwt.h b/HSDS/Common/jwt-cpp/jwt.h new file mode 100644 index 0000000..b2b998a --- /dev/null +++ b/HSDS/Common/jwt-cpp/jwt.h @@ -0,0 +1,3655 @@ +#ifndef JWT_CPP_JWT_H +#define JWT_CPP_JWT_H + +#ifndef JWT_DISABLE_PICOJSON +#ifndef PICOJSON_USE_INT64 +#define PICOJSON_USE_INT64 +#endif +#include "picojson/picojson.h" +#endif + +#ifndef JWT_DISABLE_BASE64 +#include "base.h" +#endif + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if __cplusplus > 201103L +#include +#endif + +#if __cplusplus >= 201402L +#ifdef __has_include +#if __has_include() +#include +#endif +#endif +#endif + +#if OPENSSL_VERSION_NUMBER >= 0x30000000L // 3.0.0 +#define JWT_OPENSSL_3_0 +#elif OPENSSL_VERSION_NUMBER >= 0x10101000L // 1.1.1 +#define JWT_OPENSSL_1_1_1 +#elif OPENSSL_VERSION_NUMBER >= 0x10100000L // 1.1.0 +#define JWT_OPENSSL_1_1_0 +#elif OPENSSL_VERSION_NUMBER >= 0x10000000L // 1.0.0 +#define JWT_OPENSSL_1_0_0 +#endif + +#if defined(LIBRESSL_VERSION_NUMBER) +#if LIBRESSL_VERSION_NUMBER >= 0x3050300fL +#define JWT_OPENSSL_1_1_0 +#else +#define JWT_OPENSSL_1_0_0 +#endif +#endif + +#if defined(LIBWOLFSSL_VERSION_HEX) +#define JWT_OPENSSL_1_1_1 +#endif + +#ifndef JWT_CLAIM_EXPLICIT +#define JWT_CLAIM_EXPLICIT explicit +#endif + +/** + * \brief JSON Web Token + * + * A namespace to contain everything related to handling JSON Web Tokens, JWT for short, + * as a part of [RFC7519](https://tools.ietf.org/html/rfc7519), or alternatively for + * JWS (JSON Web Signature) from [RFC7515](https://tools.ietf.org/html/rfc7515) + */ +namespace jwt { + /** + * Default system time point in UTC + */ + using date = std::chrono::system_clock::time_point; + + /** + * \brief Everything related to error codes issued by the library + */ + namespace error { + struct signature_verification_exception : public std::system_error { + using system_error::system_error; + }; + struct signature_generation_exception : public std::system_error { + using system_error::system_error; + }; + struct rsa_exception : public std::system_error { + using system_error::system_error; + }; + struct ecdsa_exception : public std::system_error { + using system_error::system_error; + }; + struct token_verification_exception : public std::system_error { + using system_error::system_error; + }; + /** + * \brief Errors related to processing of RSA signatures + */ + enum class rsa_error { + ok = 0, + cert_load_failed = 10, + get_key_failed, + write_key_failed, + write_cert_failed, + convert_to_pem_failed, + load_key_bio_write, + load_key_bio_read, + create_mem_bio_failed, + no_key_provided + }; + /** + * \brief Error category for RSA errors + */ + inline std::error_category& rsa_error_category() { + class rsa_error_cat : public std::error_category { + public: + const char* name() const noexcept override { return "rsa_error"; }; + std::string message(int ev) const override { + switch (static_cast(ev)) { + case rsa_error::ok: return "no error"; + case rsa_error::cert_load_failed: return "error loading cert into memory"; + case rsa_error::get_key_failed: return "error getting key from certificate"; + case rsa_error::write_key_failed: return "error writing key data in PEM format"; + case rsa_error::write_cert_failed: return "error writing cert data in PEM format"; + case rsa_error::convert_to_pem_failed: return "failed to convert key to pem"; + case rsa_error::load_key_bio_write: return "failed to load key: bio write failed"; + case rsa_error::load_key_bio_read: return "failed to load key: bio read failed"; + case rsa_error::create_mem_bio_failed: return "failed to create memory bio"; + case rsa_error::no_key_provided: return "at least one of public or private key need to be present"; + default: return "unknown RSA error"; + } + } + }; + static rsa_error_cat cat; + return cat; + } + + inline std::error_code make_error_code(rsa_error e) { return {static_cast(e), rsa_error_category()}; } + /** + * \brief Errors related to processing of RSA signatures + */ + enum class ecdsa_error { + ok = 0, + load_key_bio_write = 10, + load_key_bio_read, + create_mem_bio_failed, + no_key_provided, + invalid_key_size, + invalid_key, + create_context_failed + }; + /** + * \brief Error category for ECDSA errors + */ + inline std::error_category& ecdsa_error_category() { + class ecdsa_error_cat : public std::error_category { + public: + const char* name() const noexcept override { return "ecdsa_error"; }; + std::string message(int ev) const override { + switch (static_cast(ev)) { + case ecdsa_error::ok: return "no error"; + case ecdsa_error::load_key_bio_write: return "failed to load key: bio write failed"; + case ecdsa_error::load_key_bio_read: return "failed to load key: bio read failed"; + case ecdsa_error::create_mem_bio_failed: return "failed to create memory bio"; + case ecdsa_error::no_key_provided: + return "at least one of public or private key need to be present"; + case ecdsa_error::invalid_key_size: return "invalid key size"; + case ecdsa_error::invalid_key: return "invalid key"; + case ecdsa_error::create_context_failed: return "failed to create context"; + default: return "unknown ECDSA error"; + } + } + }; + static ecdsa_error_cat cat; + return cat; + } + + inline std::error_code make_error_code(ecdsa_error e) { return {static_cast(e), ecdsa_error_category()}; } + + /** + * \brief Errors related to verification of signatures + */ + enum class signature_verification_error { + ok = 0, + invalid_signature = 10, + create_context_failed, + verifyinit_failed, + verifyupdate_failed, + verifyfinal_failed, + get_key_failed, + set_rsa_pss_saltlen_failed, + signature_encoding_failed + }; + /** + * \brief Error category for verification errors + */ + inline std::error_category& signature_verification_error_category() { + class verification_error_cat : public std::error_category { + public: + const char* name() const noexcept override { return "signature_verification_error"; }; + std::string message(int ev) const override { + switch (static_cast(ev)) { + case signature_verification_error::ok: return "no error"; + case signature_verification_error::invalid_signature: return "invalid signature"; + case signature_verification_error::create_context_failed: + return "failed to verify signature: could not create context"; + case signature_verification_error::verifyinit_failed: + return "failed to verify signature: VerifyInit failed"; + case signature_verification_error::verifyupdate_failed: + return "failed to verify signature: VerifyUpdate failed"; + case signature_verification_error::verifyfinal_failed: + return "failed to verify signature: VerifyFinal failed"; + case signature_verification_error::get_key_failed: + return "failed to verify signature: Could not get key"; + case signature_verification_error::set_rsa_pss_saltlen_failed: + return "failed to verify signature: EVP_PKEY_CTX_set_rsa_pss_saltlen failed"; + case signature_verification_error::signature_encoding_failed: + return "failed to verify signature: i2d_ECDSA_SIG failed"; + default: return "unknown signature verification error"; + } + } + }; + static verification_error_cat cat; + return cat; + } + + inline std::error_code make_error_code(signature_verification_error e) { + return {static_cast(e), signature_verification_error_category()}; + } + + /** + * \brief Errors related to signature generation errors + */ + enum class signature_generation_error { + ok = 0, + hmac_failed = 10, + create_context_failed, + signinit_failed, + signupdate_failed, + signfinal_failed, + ecdsa_do_sign_failed, + digestinit_failed, + digestupdate_failed, + digestfinal_failed, + rsa_padding_failed, + rsa_private_encrypt_failed, + get_key_failed, + set_rsa_pss_saltlen_failed, + signature_decoding_failed + }; + /** + * \brief Error category for signature generation errors + */ + inline std::error_category& signature_generation_error_category() { + class signature_generation_error_cat : public std::error_category { + public: + const char* name() const noexcept override { return "signature_generation_error"; }; + std::string message(int ev) const override { + switch (static_cast(ev)) { + case signature_generation_error::ok: return "no error"; + case signature_generation_error::hmac_failed: return "hmac failed"; + case signature_generation_error::create_context_failed: + return "failed to create signature: could not create context"; + case signature_generation_error::signinit_failed: + return "failed to create signature: SignInit failed"; + case signature_generation_error::signupdate_failed: + return "failed to create signature: SignUpdate failed"; + case signature_generation_error::signfinal_failed: + return "failed to create signature: SignFinal failed"; + case signature_generation_error::ecdsa_do_sign_failed: return "failed to generate ecdsa signature"; + case signature_generation_error::digestinit_failed: + return "failed to create signature: DigestInit failed"; + case signature_generation_error::digestupdate_failed: + return "failed to create signature: DigestUpdate failed"; + case signature_generation_error::digestfinal_failed: + return "failed to create signature: DigestFinal failed"; + case signature_generation_error::rsa_padding_failed: + return "failed to create signature: EVP_PKEY_CTX_set_rsa_padding failed"; + case signature_generation_error::rsa_private_encrypt_failed: + return "failed to create signature: RSA_private_encrypt failed"; + case signature_generation_error::get_key_failed: + return "failed to generate signature: Could not get key"; + case signature_generation_error::set_rsa_pss_saltlen_failed: + return "failed to create signature: EVP_PKEY_CTX_set_rsa_pss_saltlen failed"; + case signature_generation_error::signature_decoding_failed: + return "failed to create signature: d2i_ECDSA_SIG failed"; + default: return "unknown signature generation error"; + } + } + }; + static signature_generation_error_cat cat = {}; + return cat; + } + + inline std::error_code make_error_code(signature_generation_error e) { + return {static_cast(e), signature_generation_error_category()}; + } + + /** + * \brief Errors related to token verification errors + */ + enum class token_verification_error { + ok = 0, + wrong_algorithm = 10, + missing_claim, + claim_type_missmatch, + claim_value_missmatch, + token_expired, + audience_missmatch + }; + /** + * \brief Error category for token verification errors + */ + inline std::error_category& token_verification_error_category() { + class token_verification_error_cat : public std::error_category { + public: + const char* name() const noexcept override { return "token_verification_error"; }; + std::string message(int ev) const override { + switch (static_cast(ev)) { + case token_verification_error::ok: return "no error"; + case token_verification_error::wrong_algorithm: return "wrong algorithm"; + case token_verification_error::missing_claim: return "decoded JWT is missing required claim(s)"; + case token_verification_error::claim_type_missmatch: + return "claim type does not match expected type"; + case token_verification_error::claim_value_missmatch: + return "claim value does not match expected value"; + case token_verification_error::token_expired: return "token expired"; + case token_verification_error::audience_missmatch: + return "token doesn't contain the required audience"; + default: return "unknown token verification error"; + } + } + }; + static token_verification_error_cat cat = {}; + return cat; + } + + inline std::error_code make_error_code(token_verification_error e) { + return {static_cast(e), token_verification_error_category()}; + } + + inline void throw_if_error(std::error_code ec) { + if (ec) { + if (ec.category() == rsa_error_category()) throw rsa_exception(ec); + if (ec.category() == ecdsa_error_category()) throw ecdsa_exception(ec); + if (ec.category() == signature_verification_error_category()) + throw signature_verification_exception(ec); + if (ec.category() == signature_generation_error_category()) throw signature_generation_exception(ec); + if (ec.category() == token_verification_error_category()) throw token_verification_exception(ec); + } + } + } // namespace error +} // namespace jwt + +namespace std { + template<> + struct is_error_code_enum : true_type {}; + template<> + struct is_error_code_enum : true_type {}; + template<> + struct is_error_code_enum : true_type {}; + template<> + struct is_error_code_enum : true_type {}; + template<> + struct is_error_code_enum : true_type {}; +} // namespace std + +namespace jwt { + /** + * \brief A collection for working with certificates + * + * These _helpers_ are usefully when working with certificates OpenSSL APIs. + * For example, when dealing with JWKS (JSON Web Key Set)[https://tools.ietf.org/html/rfc7517] + * you maybe need to extract the modulus and exponent of an RSA Public Key. + */ + namespace helper { + /** + * \brief Handle class for EVP_PKEY structures + * + * Starting from OpenSSL 1.1.0, EVP_PKEY has internal reference counting. This handle class allows + * jwt-cpp to leverage that and thus safe an allocation for the control block in std::shared_ptr. + * The handle uses shared_ptr as a fallback on older versions. The behaviour should be identical between both. + */ + class evp_pkey_handle { + public: + constexpr evp_pkey_handle() noexcept = default; +#ifdef JWT_OPENSSL_1_0_0 + /** + * \brief Construct a new handle. The handle takes ownership of the key. + * \param key The key to store + */ + explicit evp_pkey_handle(EVP_PKEY* key) { m_key = std::shared_ptr(key, EVP_PKEY_free); } + + EVP_PKEY* get() const noexcept { return m_key.get(); } + bool operator!() const noexcept { return m_key == nullptr; } + explicit operator bool() const noexcept { return m_key != nullptr; } + + private: + std::shared_ptr m_key{nullptr}; +#else + /** + * \brief Construct a new handle. The handle takes ownership of the key. + * \param key The key to store + */ + explicit constexpr evp_pkey_handle(EVP_PKEY* key) noexcept : m_key{key} {} + evp_pkey_handle(const evp_pkey_handle& other) : m_key{other.m_key} { + if (m_key != nullptr && EVP_PKEY_up_ref(m_key) != 1) throw std::runtime_error("EVP_PKEY_up_ref failed"); + } +// C++11 requires the body of a constexpr constructor to be empty +#if __cplusplus >= 201402L + constexpr +#endif + evp_pkey_handle(evp_pkey_handle&& other) noexcept + : m_key{other.m_key} { + other.m_key = nullptr; + } + evp_pkey_handle& operator=(const evp_pkey_handle& other) { + if (&other == this) return *this; + decrement_ref_count(m_key); + m_key = other.m_key; + increment_ref_count(m_key); + return *this; + } + evp_pkey_handle& operator=(evp_pkey_handle&& other) noexcept { + if (&other == this) return *this; + decrement_ref_count(m_key); + m_key = other.m_key; + other.m_key = nullptr; + return *this; + } + evp_pkey_handle& operator=(EVP_PKEY* key) { + decrement_ref_count(m_key); + m_key = key; + increment_ref_count(m_key); + return *this; + } + ~evp_pkey_handle() noexcept { decrement_ref_count(m_key); } + + EVP_PKEY* get() const noexcept { return m_key; } + bool operator!() const noexcept { return m_key == nullptr; } + explicit operator bool() const noexcept { return m_key != nullptr; } + + private: + EVP_PKEY* m_key{nullptr}; + + static void increment_ref_count(EVP_PKEY* key) { + if (key != nullptr && EVP_PKEY_up_ref(key) != 1) throw std::runtime_error("EVP_PKEY_up_ref failed"); + } + static void decrement_ref_count(EVP_PKEY* key) noexcept { + if (key != nullptr) EVP_PKEY_free(key); + } +#endif + }; + + inline std::unique_ptr make_mem_buf_bio() { + return std::unique_ptr(BIO_new(BIO_s_mem()), BIO_free_all); + } + + inline std::unique_ptr make_mem_buf_bio(const std::string& data) { + return std::unique_ptr( +#if OPENSSL_VERSION_NUMBER <= 0x10100003L + BIO_new_mem_buf(const_cast(data.data()), static_cast(data.size())), BIO_free_all +#else + BIO_new_mem_buf(data.data(), static_cast(data.size())), BIO_free_all +#endif + ); + } + + inline std::unique_ptr make_evp_md_ctx() { + return +#ifdef JWT_OPENSSL_1_0_0 + std::unique_ptr(EVP_MD_CTX_create(), &EVP_MD_CTX_destroy); +#else + std::unique_ptr(EVP_MD_CTX_new(), &EVP_MD_CTX_free); +#endif + } + + /** + * \brief Extract the public key of a pem certificate + * + * \param certstr String containing the certificate encoded as pem + * \param pw Password used to decrypt certificate (leave empty if not encrypted) + * \param ec error_code for error_detection (gets cleared if no error occurred) + */ + inline std::string extract_pubkey_from_cert(const std::string& certstr, const std::string& pw, + std::error_code& ec) { + ec.clear(); + auto certbio = make_mem_buf_bio(certstr); + auto keybio = make_mem_buf_bio(); + if (!certbio || !keybio) { + ec = error::rsa_error::create_mem_bio_failed; + return {}; + } + + std::unique_ptr cert( + PEM_read_bio_X509(certbio.get(), nullptr, nullptr, const_cast(pw.c_str())), X509_free); + if (!cert) { + ec = error::rsa_error::cert_load_failed; + return {}; + } + std::unique_ptr key(X509_get_pubkey(cert.get()), EVP_PKEY_free); + if (!key) { + ec = error::rsa_error::get_key_failed; + return {}; + } + if (PEM_write_bio_PUBKEY(keybio.get(), key.get()) == 0) { + ec = error::rsa_error::write_key_failed; + return {}; + } + char* ptr = nullptr; + auto len = BIO_get_mem_data(keybio.get(), &ptr); + if (len <= 0 || ptr == nullptr) { + ec = error::rsa_error::convert_to_pem_failed; + return {}; + } + return {ptr, static_cast(len)}; + } + + /** + * \brief Extract the public key of a pem certificate + * + * \param certstr String containing the certificate encoded as pem + * \param pw Password used to decrypt certificate (leave empty if not encrypted) + * \throw rsa_exception if an error occurred + */ + inline std::string extract_pubkey_from_cert(const std::string& certstr, const std::string& pw = "") { + std::error_code ec; + auto res = extract_pubkey_from_cert(certstr, pw, ec); + error::throw_if_error(ec); + return res; + } + + /** + * \brief Convert the certificate provided as DER to PEM. + * + * \param cert_der_str String containing the certificate encoded as base64 DER + * \param ec error_code for error_detection (gets cleared if no error occurs) + */ + inline std::string convert_der_to_pem(const std::string& cert_der_str, std::error_code& ec) { + ec.clear(); + + auto c_str = reinterpret_cast(cert_der_str.c_str()); + + std::unique_ptr cert( + d2i_X509(NULL, &c_str, static_cast(cert_der_str.size())), X509_free); + auto certbio = make_mem_buf_bio(); + if (!cert || !certbio) { + ec = error::rsa_error::create_mem_bio_failed; + return {}; + } + + if (!PEM_write_bio_X509(certbio.get(), cert.get())) { + ec = error::rsa_error::write_cert_failed; + return {}; + } + + char* ptr = nullptr; + const auto len = BIO_get_mem_data(certbio.get(), &ptr); + if (len <= 0 || ptr == nullptr) { + ec = error::rsa_error::convert_to_pem_failed; + return {}; + } + + return {ptr, static_cast(len)}; + } + + /** + * \brief Convert the certificate provided as base64 DER to PEM. + * + * This is useful when using with JWKs as x5c claim is encoded as base64 DER. More info + * (here)[https://tools.ietf.org/html/rfc7517#section-4.7] + * + * \tparam Decode is callabled, taking a string_type and returns a string_type. + * It should ensure the padding of the input and then base64 decode and return + * the results. + * + * \param cert_base64_der_str String containing the certificate encoded as base64 DER + * \param decode The function to decode the cert + * \param ec error_code for error_detection (gets cleared if no error occurs) + */ + template + std::string convert_base64_der_to_pem(const std::string& cert_base64_der_str, Decode decode, + std::error_code& ec) { + ec.clear(); + const auto decoded_str = decode(cert_base64_der_str); + return convert_der_to_pem(decoded_str, ec); + } + + /** + * \brief Convert the certificate provided as base64 DER to PEM. + * + * This is useful when using with JWKs as x5c claim is encoded as base64 DER. More info + * (here)[https://tools.ietf.org/html/rfc7517#section-4.7] + * + * \tparam Decode is callabled, taking a string_type and returns a string_type. + * It should ensure the padding of the input and then base64 decode and return + * the results. + * + * \param cert_base64_der_str String containing the certificate encoded as base64 DER + * \param decode The function to decode the cert + * \throw rsa_exception if an error occurred + */ + template + std::string convert_base64_der_to_pem(const std::string& cert_base64_der_str, Decode decode) { + std::error_code ec; + auto res = convert_base64_der_to_pem(cert_base64_der_str, std::move(decode), ec); + error::throw_if_error(ec); + return res; + } + + /** + * \brief Convert the certificate provided as DER to PEM. + * + * \param cert_der_str String containing the DER certificate + * \param decode The function to decode the cert + * \throw rsa_exception if an error occurred + */ + inline std::string convert_der_to_pem(const std::string& cert_der_str) { + std::error_code ec; + auto res = convert_der_to_pem(cert_der_str, ec); + error::throw_if_error(ec); + return res; + } + +#ifndef JWT_DISABLE_BASE64 + /** + * \brief Convert the certificate provided as base64 DER to PEM. + * + * This is useful when using with JWKs as x5c claim is encoded as base64 DER. More info + * (here)[https://tools.ietf.org/html/rfc7517#section-4.7] + * + * \param cert_base64_der_str String containing the certificate encoded as base64 DER + * \param ec error_code for error_detection (gets cleared if no error occurs) + */ + inline std::string convert_base64_der_to_pem(const std::string& cert_base64_der_str, std::error_code& ec) { + auto decode = [](const std::string& token) { + return base::decode(base::pad(token)); + }; + return convert_base64_der_to_pem(cert_base64_der_str, std::move(decode), ec); + } + + /** + * \brief Convert the certificate provided as base64 DER to PEM. + * + * This is useful when using with JWKs as x5c claim is encoded as base64 DER. More info + * (here)[https://tools.ietf.org/html/rfc7517#section-4.7] + * + * \param cert_base64_der_str String containing the certificate encoded as base64 DER + * \throw rsa_exception if an error occurred + */ + inline std::string convert_base64_der_to_pem(const std::string& cert_base64_der_str) { + std::error_code ec; + auto res = convert_base64_der_to_pem(cert_base64_der_str, ec); + error::throw_if_error(ec); + return res; + } +#endif + /** + * \brief Load a public key from a string. + * + * The string should contain a pem encoded certificate or public key + * + * \param key String containing the certificate encoded as pem + * \param password Password used to decrypt certificate (leave empty if not encrypted) + * \param ec error_code for error_detection (gets cleared if no error occurs) + */ + inline evp_pkey_handle load_public_key_from_string(const std::string& key, const std::string& password, + std::error_code& ec) { + ec.clear(); + auto pubkey_bio = make_mem_buf_bio(); + if (!pubkey_bio) { + ec = error::rsa_error::create_mem_bio_failed; + return {}; + } + if (key.substr(0, 27) == "-----BEGIN CERTIFICATE-----") { + auto epkey = helper::extract_pubkey_from_cert(key, password, ec); + if (ec) return {}; + const int len = static_cast(epkey.size()); + if (BIO_write(pubkey_bio.get(), epkey.data(), len) != len) { + ec = error::rsa_error::load_key_bio_write; + return {}; + } + } else { + const int len = static_cast(key.size()); + if (BIO_write(pubkey_bio.get(), key.data(), len) != len) { + ec = error::rsa_error::load_key_bio_write; + return {}; + } + } + + evp_pkey_handle pkey(PEM_read_bio_PUBKEY( + pubkey_bio.get(), nullptr, nullptr, + (void*)password.data())); // NOLINT(google-readability-casting) requires `const_cast` + if (!pkey) ec = error::rsa_error::load_key_bio_read; + return pkey; + } + + /** + * \brief Load a public key from a string. + * + * The string should contain a pem encoded certificate or public key + * + * \param key String containing the certificate or key encoded as pem + * \param password Password used to decrypt certificate or key (leave empty if not encrypted) + * \throw rsa_exception if an error occurred + */ + inline evp_pkey_handle load_public_key_from_string(const std::string& key, const std::string& password = "") { + std::error_code ec; + auto res = load_public_key_from_string(key, password, ec); + error::throw_if_error(ec); + return res; + } + + /** + * \brief Load a private key from a string. + * + * \param key String containing a private key as pem + * \param password Password used to decrypt key (leave empty if not encrypted) + * \param ec error_code for error_detection (gets cleared if no error occurs) + */ + inline evp_pkey_handle load_private_key_from_string(const std::string& key, const std::string& password, + std::error_code& ec) { + auto privkey_bio = make_mem_buf_bio(); + if (!privkey_bio) { + ec = error::rsa_error::create_mem_bio_failed; + return {}; + } + const int len = static_cast(key.size()); + if (BIO_write(privkey_bio.get(), key.data(), len) != len) { + ec = error::rsa_error::load_key_bio_write; + return {}; + } + evp_pkey_handle pkey( + PEM_read_bio_PrivateKey(privkey_bio.get(), nullptr, nullptr, const_cast(password.c_str()))); + if (!pkey) ec = error::rsa_error::load_key_bio_read; + return pkey; + } + + /** + * \brief Load a private key from a string. + * + * \param key String containing a private key as pem + * \param password Password used to decrypt key (leave empty if not encrypted) + * \throw rsa_exception if an error occurred + */ + inline evp_pkey_handle load_private_key_from_string(const std::string& key, const std::string& password = "") { + std::error_code ec; + auto res = load_private_key_from_string(key, password, ec); + error::throw_if_error(ec); + return res; + } + + /** + * \brief Load a public key from a string. + * + * The string should contain a pem encoded certificate or public key + * + * \param key String containing the certificate encoded as pem + * \param password Password used to decrypt certificate (leave empty if not encrypted) + * \param ec error_code for error_detection (gets cleared if no error occurs) + */ + inline evp_pkey_handle load_public_ec_key_from_string(const std::string& key, const std::string& password, + std::error_code& ec) { + ec.clear(); + auto pubkey_bio = make_mem_buf_bio(); + if (!pubkey_bio) { + ec = error::ecdsa_error::create_mem_bio_failed; + return {}; + } + if (key.substr(0, 27) == "-----BEGIN CERTIFICATE-----") { + auto epkey = helper::extract_pubkey_from_cert(key, password, ec); + if (ec) return {}; + const int len = static_cast(epkey.size()); + if (BIO_write(pubkey_bio.get(), epkey.data(), len) != len) { + ec = error::ecdsa_error::load_key_bio_write; + return {}; + } + } else { + const int len = static_cast(key.size()); + if (BIO_write(pubkey_bio.get(), key.data(), len) != len) { + ec = error::ecdsa_error::load_key_bio_write; + return {}; + } + } + + evp_pkey_handle pkey(PEM_read_bio_PUBKEY( + pubkey_bio.get(), nullptr, nullptr, + (void*)password.data())); // NOLINT(google-readability-casting) requires `const_cast` + if (!pkey) ec = error::ecdsa_error::load_key_bio_read; + return pkey; + } + + /** + * \brief Load a public key from a string. + * + * The string should contain a pem encoded certificate or public key + * + * \param key String containing the certificate or key encoded as pem + * \param password Password used to decrypt certificate or key (leave empty if not encrypted) + * \throw ecdsa_exception if an error occurred + */ + inline evp_pkey_handle load_public_ec_key_from_string(const std::string& key, + const std::string& password = "") { + std::error_code ec; + auto res = load_public_ec_key_from_string(key, password, ec); + error::throw_if_error(ec); + return res; + } + + /** + * \brief Load a private key from a string. + * + * \param key String containing a private key as pem + * \param password Password used to decrypt key (leave empty if not encrypted) + * \param ec error_code for error_detection (gets cleared if no error occurs) + */ + inline evp_pkey_handle load_private_ec_key_from_string(const std::string& key, const std::string& password, + std::error_code& ec) { + auto privkey_bio = make_mem_buf_bio(); + if (!privkey_bio) { + ec = error::ecdsa_error::create_mem_bio_failed; + return {}; + } + const int len = static_cast(key.size()); + if (BIO_write(privkey_bio.get(), key.data(), len) != len) { + ec = error::ecdsa_error::load_key_bio_write; + return {}; + } + evp_pkey_handle pkey( + PEM_read_bio_PrivateKey(privkey_bio.get(), nullptr, nullptr, const_cast(password.c_str()))); + if (!pkey) ec = error::ecdsa_error::load_key_bio_read; + return pkey; + } + + /** + * \brief Load a private key from a string. + * + * \param key String containing a private key as pem + * \param password Password used to decrypt key (leave empty if not encrypted) + * \throw ecdsa_exception if an error occurred + */ + inline evp_pkey_handle load_private_ec_key_from_string(const std::string& key, + const std::string& password = "") { + std::error_code ec; + auto res = load_private_ec_key_from_string(key, password, ec); + error::throw_if_error(ec); + return res; + } + + /** + * Convert a OpenSSL BIGNUM to a std::string + * \param bn BIGNUM to convert + * \return bignum as string + */ + inline +#ifdef JWT_OPENSSL_1_0_0 + std::string + bn2raw(BIGNUM* bn) +#else + std::string + bn2raw(const BIGNUM* bn) +#endif + { + std::string res(BN_num_bytes(bn), '\0'); + BN_bn2bin(bn, (unsigned char*)res.data()); // NOLINT(google-readability-casting) requires `const_cast` + return res; + } + /** + * Convert an std::string to a OpenSSL BIGNUM + * \param raw String to convert + * \return BIGNUM representation + */ + inline std::unique_ptr raw2bn(const std::string& raw) { + return std::unique_ptr( + BN_bin2bn(reinterpret_cast(raw.data()), static_cast(raw.size()), nullptr), + BN_free); + } + } // namespace helper + + /** + * \brief Various cryptographic algorithms when working with JWT + * + * JWT (JSON Web Tokens) signatures are typically used as the payload for a JWS (JSON Web Signature) or + * JWE (JSON Web Encryption). Both of these use various cryptographic as specified by + * [RFC7518](https://tools.ietf.org/html/rfc7518) and are exposed through the a [JOSE + * Header](https://tools.ietf.org/html/rfc7515#section-4) which points to one of the JWA (JSON Web + * Algorithms)(https://tools.ietf.org/html/rfc7518#section-3.1) + */ + namespace algorithm { + /** + * \brief "none" algorithm. + * + * Returns and empty signature and checks if the given signature is empty. + */ + struct none { + /** + * \brief Return an empty string + */ + std::string sign(const std::string& /*unused*/, std::error_code& ec) const { + ec.clear(); + return {}; + } + /** + * \brief Check if the given signature is empty. + * + * JWT's with "none" algorithm should not contain a signature. + * \param signature Signature data to verify + * \param ec error_code filled with details about the error + */ + void verify(const std::string& /*unused*/, const std::string& signature, std::error_code& ec) const { + ec.clear(); + if (!signature.empty()) { ec = error::signature_verification_error::invalid_signature; } + } + /// Get algorithm name + std::string name() const { return "none"; } + }; + /** + * \brief Base class for HMAC family of algorithms + */ + struct hmacsha { + /** + * Construct new hmac algorithm + * \param key Key to use for HMAC + * \param md Pointer to hash function + * \param name Name of the algorithm + */ + hmacsha(std::string key, const EVP_MD* (*md)(), std::string name) + : secret(std::move(key)), md(md), alg_name(std::move(name)) {} + /** + * Sign jwt data + * \param data The data to sign + * \param ec error_code filled with details on error + * \return HMAC signature for the given data + */ + std::string sign(const std::string& data, std::error_code& ec) const { + ec.clear(); + std::string res(static_cast(EVP_MAX_MD_SIZE), '\0'); + auto len = static_cast(res.size()); + if (HMAC(md(), secret.data(), static_cast(secret.size()), + reinterpret_cast(data.data()), static_cast(data.size()), + (unsigned char*)res.data(), // NOLINT(google-readability-casting) requires `const_cast` + &len) == nullptr) { + ec = error::signature_generation_error::hmac_failed; + return {}; + } + res.resize(len); + return res; + } + /** + * Check if signature is valid + * \param data The data to check signature against + * \param signature Signature provided by the jwt + * \param ec Filled with details about failure. + */ + void verify(const std::string& data, const std::string& signature, std::error_code& ec) const { + ec.clear(); + auto res = sign(data, ec); + if (ec) return; + + bool matched = true; + for (size_t i = 0; i < std::min(res.size(), signature.size()); i++) + if (res[i] != signature[i]) matched = false; + if (res.size() != signature.size()) matched = false; + if (!matched) { + ec = error::signature_verification_error::invalid_signature; + return; + } + } + /** + * Returns the algorithm name provided to the constructor + * \return algorithm's name + */ + std::string name() const { return alg_name; } + + private: + /// HMAC secrect + const std::string secret; + /// HMAC hash generator + const EVP_MD* (*md)(); + /// algorithm's name + const std::string alg_name; + }; + /** + * \brief Base class for RSA family of algorithms + */ + struct rsa { + /** + * Construct new rsa algorithm + * \param public_key RSA public key in PEM format + * \param private_key RSA private key or empty string if not available. If empty, signing will always fail. + * \param public_key_password Password to decrypt public key pem. + * \param private_key_password Password to decrypt private key pem. + * \param md Pointer to hash function + * \param name Name of the algorithm + */ + rsa(const std::string& public_key, const std::string& private_key, const std::string& public_key_password, + const std::string& private_key_password, const EVP_MD* (*md)(), std::string name) + : md(md), alg_name(std::move(name)) { + if (!private_key.empty()) { + pkey = helper::load_private_key_from_string(private_key, private_key_password); + } else if (!public_key.empty()) { + pkey = helper::load_public_key_from_string(public_key, public_key_password); + } else + throw error::rsa_exception(error::rsa_error::no_key_provided); + } + /** + * Sign jwt data + * \param data The data to sign + * \param ec error_code filled with details on error + * \return RSA signature for the given data + */ + std::string sign(const std::string& data, std::error_code& ec) const { + ec.clear(); + auto ctx = helper::make_evp_md_ctx(); + if (!ctx) { + ec = error::signature_generation_error::create_context_failed; + return {}; + } + if (!EVP_SignInit(ctx.get(), md())) { + ec = error::signature_generation_error::signinit_failed; + return {}; + } + + std::string res(EVP_PKEY_size(pkey.get()), '\0'); + unsigned int len = 0; + + if (!EVP_SignUpdate(ctx.get(), data.data(), data.size())) { + ec = error::signature_generation_error::signupdate_failed; + return {}; + } + if (EVP_SignFinal(ctx.get(), (unsigned char*)res.data(), &len, pkey.get()) == 0) { + ec = error::signature_generation_error::signfinal_failed; + return {}; + } + + res.resize(len); + return res; + } + /** + * Check if signature is valid + * \param data The data to check signature against + * \param signature Signature provided by the jwt + * \param ec Filled with details on failure + */ + void verify(const std::string& data, const std::string& signature, std::error_code& ec) const { + ec.clear(); + auto ctx = helper::make_evp_md_ctx(); + if (!ctx) { + ec = error::signature_verification_error::create_context_failed; + return; + } + if (!EVP_VerifyInit(ctx.get(), md())) { + ec = error::signature_verification_error::verifyinit_failed; + return; + } + if (!EVP_VerifyUpdate(ctx.get(), data.data(), data.size())) { + ec = error::signature_verification_error::verifyupdate_failed; + return; + } + auto res = EVP_VerifyFinal(ctx.get(), reinterpret_cast(signature.data()), + static_cast(signature.size()), pkey.get()); + if (res != 1) { + ec = error::signature_verification_error::verifyfinal_failed; + return; + } + } + /** + * Returns the algorithm name provided to the constructor + * \return algorithm's name + */ + std::string name() const { return alg_name; } + + private: + /// OpenSSL structure containing converted keys + helper::evp_pkey_handle pkey; + /// Hash generator + const EVP_MD* (*md)(); + /// algorithm's name + const std::string alg_name; + }; + /** + * \brief Base class for ECDSA family of algorithms + */ + struct ecdsa { + /** + * Construct new ecdsa algorithm + * + * \param public_key ECDSA public key in PEM format + * \param private_key ECDSA private key or empty string if not available. If empty, signing will always fail + * \param public_key_password Password to decrypt public key pem + * \param private_key_password Password to decrypt private key pem + * \param md Pointer to hash function + * \param name Name of the algorithm + * \param siglen The bit length of the signature + */ + ecdsa(const std::string& public_key, const std::string& private_key, const std::string& public_key_password, + const std::string& private_key_password, const EVP_MD* (*md)(), std::string name, size_t siglen) + : md(md), alg_name(std::move(name)), signature_length(siglen) { + if (!private_key.empty()) { + pkey = helper::load_private_ec_key_from_string(private_key, private_key_password); + check_private_key(pkey.get()); + } else if (!public_key.empty()) { + pkey = helper::load_public_ec_key_from_string(public_key, public_key_password); + check_public_key(pkey.get()); + } else { + throw error::ecdsa_exception(error::ecdsa_error::no_key_provided); + } + if (!pkey) throw error::ecdsa_exception(error::ecdsa_error::invalid_key); + + size_t keysize = EVP_PKEY_bits(pkey.get()); + if (keysize != signature_length * 4 && (signature_length != 132 || keysize != 521)) + throw error::ecdsa_exception(error::ecdsa_error::invalid_key_size); + } + + /** + * Sign jwt data + * \param data The data to sign + * \param ec error_code filled with details on error + * \return ECDSA signature for the given data + */ + std::string sign(const std::string& data, std::error_code& ec) const { + ec.clear(); + auto ctx = helper::make_evp_md_ctx(); + if (!ctx) { + ec = error::signature_generation_error::create_context_failed; + return {}; + } + if (!EVP_DigestSignInit(ctx.get(), nullptr, md(), nullptr, pkey.get())) { + ec = error::signature_generation_error::signinit_failed; + return {}; + } + if (!EVP_DigestUpdate(ctx.get(), data.data(), data.size())) { + ec = error::signature_generation_error::digestupdate_failed; + return {}; + } + + size_t len = 0; + if (!EVP_DigestSignFinal(ctx.get(), nullptr, &len)) { + ec = error::signature_generation_error::signfinal_failed; + return {}; + } + std::string res(len, '\0'); + if (!EVP_DigestSignFinal(ctx.get(), (unsigned char*)res.data(), &len)) { + ec = error::signature_generation_error::signfinal_failed; + return {}; + } + + res.resize(len); + return der_to_p1363_signature(res, ec); + } + + /** + * Check if signature is valid + * \param data The data to check signature against + * \param signature Signature provided by the jwt + * \param ec Filled with details on error + */ + void verify(const std::string& data, const std::string& signature, std::error_code& ec) const { + ec.clear(); + std::string der_signature = p1363_to_der_signature(signature, ec); + if (ec) { return; } + + auto ctx = helper::make_evp_md_ctx(); + if (!ctx) { + ec = error::signature_verification_error::create_context_failed; + return; + } + if (!EVP_DigestVerifyInit(ctx.get(), nullptr, md(), nullptr, pkey.get())) { + ec = error::signature_verification_error::verifyinit_failed; + return; + } + if (!EVP_DigestUpdate(ctx.get(), data.data(), data.size())) { + ec = error::signature_verification_error::verifyupdate_failed; + return; + } + +#if OPENSSL_VERSION_NUMBER < 0x10002000L + unsigned char* der_sig_data = reinterpret_cast(const_cast(der_signature.data())); +#else + const unsigned char* der_sig_data = reinterpret_cast(der_signature.data()); +#endif + auto res = + EVP_DigestVerifyFinal(ctx.get(), der_sig_data, static_cast(der_signature.length())); + if (res == 0) { + ec = error::signature_verification_error::invalid_signature; + return; + } + if (res == -1) { + ec = error::signature_verification_error::verifyfinal_failed; + return; + } + } + /** + * Returns the algorithm name provided to the constructor + * \return algorithm's name + */ + std::string name() const { return alg_name; } + + private: + static void check_public_key(EVP_PKEY* pkey) { +#ifdef JWT_OPENSSL_3_0 + std::unique_ptr ctx( + EVP_PKEY_CTX_new_from_pkey(nullptr, pkey, nullptr), EVP_PKEY_CTX_free); + if (!ctx) { throw error::ecdsa_exception(error::ecdsa_error::create_context_failed); } + if (EVP_PKEY_public_check(ctx.get()) != 1) { + throw error::ecdsa_exception(error::ecdsa_error::invalid_key); + } +#else + std::unique_ptr eckey(EVP_PKEY_get1_EC_KEY(pkey), EC_KEY_free); + if (!eckey) { throw error::ecdsa_exception(error::ecdsa_error::invalid_key); } + if (EC_KEY_check_key(eckey.get()) == 0) throw error::ecdsa_exception(error::ecdsa_error::invalid_key); +#endif + } + + static void check_private_key(EVP_PKEY* pkey) { +#ifdef JWT_OPENSSL_3_0 + std::unique_ptr ctx( + EVP_PKEY_CTX_new_from_pkey(nullptr, pkey, nullptr), EVP_PKEY_CTX_free); + if (!ctx) { throw error::ecdsa_exception(error::ecdsa_error::create_context_failed); } + if (EVP_PKEY_private_check(ctx.get()) != 1) { + throw error::ecdsa_exception(error::ecdsa_error::invalid_key); + } +#else + std::unique_ptr eckey(EVP_PKEY_get1_EC_KEY(pkey), EC_KEY_free); + if (!eckey) { throw error::ecdsa_exception(error::ecdsa_error::invalid_key); } + if (EC_KEY_check_key(eckey.get()) == 0) throw error::ecdsa_exception(error::ecdsa_error::invalid_key); +#endif + } + + std::string der_to_p1363_signature(const std::string& der_signature, std::error_code& ec) const { + const unsigned char* possl_signature = reinterpret_cast(der_signature.data()); + std::unique_ptr sig( + d2i_ECDSA_SIG(nullptr, &possl_signature, static_cast(der_signature.length())), + ECDSA_SIG_free); + if (!sig) { + ec = error::signature_generation_error::signature_decoding_failed; + return {}; + } + +#ifdef JWT_OPENSSL_1_0_0 + + auto rr = helper::bn2raw(sig->r); + auto rs = helper::bn2raw(sig->s); +#else + const BIGNUM* r; + const BIGNUM* s; + ECDSA_SIG_get0(sig.get(), &r, &s); + auto rr = helper::bn2raw(r); + auto rs = helper::bn2raw(s); +#endif + if (rr.size() > signature_length / 2 || rs.size() > signature_length / 2) + throw std::logic_error("bignum size exceeded expected length"); + rr.insert(0, signature_length / 2 - rr.size(), '\0'); + rs.insert(0, signature_length / 2 - rs.size(), '\0'); + return rr + rs; + } + + std::string p1363_to_der_signature(const std::string& signature, std::error_code& ec) const { + ec.clear(); + auto r = helper::raw2bn(signature.substr(0, signature.size() / 2)); + auto s = helper::raw2bn(signature.substr(signature.size() / 2)); + + ECDSA_SIG* psig; +#ifdef JWT_OPENSSL_1_0_0 + ECDSA_SIG sig; + sig.r = r.get(); + sig.s = s.get(); + psig = &sig; +#else + std::unique_ptr sig(ECDSA_SIG_new(), ECDSA_SIG_free); + if (!sig) { + ec = error::signature_verification_error::create_context_failed; + return {}; + } + ECDSA_SIG_set0(sig.get(), r.release(), s.release()); + psig = sig.get(); +#endif + + int length = i2d_ECDSA_SIG(psig, nullptr); + if (length < 0) { + ec = error::signature_verification_error::signature_encoding_failed; + return {}; + } + std::string der_signature(length, '\0'); + unsigned char* psbuffer = (unsigned char*)der_signature.data(); + length = i2d_ECDSA_SIG(psig, &psbuffer); + if (length < 0) { + ec = error::signature_verification_error::signature_encoding_failed; + return {}; + } + der_signature.resize(length); + return der_signature; + } + + /// OpenSSL struct containing keys + helper::evp_pkey_handle pkey; + /// Hash generator function + const EVP_MD* (*md)(); + /// algorithm's name + const std::string alg_name; + /// Length of the resulting signature + const size_t signature_length; + }; + +#if !defined(JWT_OPENSSL_1_0_0) && !defined(JWT_OPENSSL_1_1_0) + /** + * \brief Base class for EdDSA family of algorithms + * + * https://tools.ietf.org/html/rfc8032 + * + * The EdDSA algorithms were introduced in [OpenSSL v1.1.1](https://www.openssl.org/news/openssl-1.1.1-notes.html), + * so these algorithms are only available when building against this version or higher. + */ + struct eddsa { + /** + * Construct new eddsa algorithm + * \param public_key EdDSA public key in PEM format + * \param private_key EdDSA private key or empty string if not available. If empty, signing will always + * fail. + * \param public_key_password Password to decrypt public key pem. + * \param private_key_password Password + * to decrypt private key pem. + * \param name Name of the algorithm + */ + eddsa(const std::string& public_key, const std::string& private_key, const std::string& public_key_password, + const std::string& private_key_password, std::string name) + : alg_name(std::move(name)) { + if (!private_key.empty()) { + pkey = helper::load_private_key_from_string(private_key, private_key_password); + } else if (!public_key.empty()) { + pkey = helper::load_public_key_from_string(public_key, public_key_password); + } else + throw error::ecdsa_exception(error::ecdsa_error::load_key_bio_read); + } + /** + * Sign jwt data + * \param data The data to sign + * \param ec error_code filled with details on error + * \return EdDSA signature for the given data + */ + std::string sign(const std::string& data, std::error_code& ec) const { + ec.clear(); + auto ctx = helper::make_evp_md_ctx(); + if (!ctx) { + ec = error::signature_generation_error::create_context_failed; + return {}; + } + if (!EVP_DigestSignInit(ctx.get(), nullptr, nullptr, nullptr, pkey.get())) { + ec = error::signature_generation_error::signinit_failed; + return {}; + } + + size_t len = EVP_PKEY_size(pkey.get()); + std::string res(len, '\0'); + +// LibreSSL is the special kid in the block, as it does not support EVP_DigestSign. +// OpenSSL on the otherhand does not support using EVP_DigestSignUpdate for eddsa, which is why we end up with this +// mess. +#if defined(LIBRESSL_VERSION_NUMBER) || defined(LIBWOLFSSL_VERSION_HEX) + ERR_clear_error(); + if (EVP_DigestSignUpdate(ctx.get(), reinterpret_cast(data.data()), data.size()) != + 1) { + std::cout << ERR_error_string(ERR_get_error(), NULL) << std::endl; + ec = error::signature_generation_error::signupdate_failed; + return {}; + } + if (EVP_DigestSignFinal(ctx.get(), reinterpret_cast(&res[0]), &len) != 1) { + ec = error::signature_generation_error::signfinal_failed; + return {}; + } +#else + if (EVP_DigestSign(ctx.get(), reinterpret_cast(&res[0]), &len, + reinterpret_cast(data.data()), data.size()) != 1) { + ec = error::signature_generation_error::signfinal_failed; + return {}; + } +#endif + + res.resize(len); + return res; + } + + /** + * Check if signature is valid + * \param data The data to check signature against + * \param signature Signature provided by the jwt + * \param ec Filled with details on error + */ + void verify(const std::string& data, const std::string& signature, std::error_code& ec) const { + ec.clear(); + auto ctx = helper::make_evp_md_ctx(); + if (!ctx) { + ec = error::signature_verification_error::create_context_failed; + return; + } + if (!EVP_DigestVerifyInit(ctx.get(), nullptr, nullptr, nullptr, pkey.get())) { + ec = error::signature_verification_error::verifyinit_failed; + return; + } +// LibreSSL is the special kid in the block, as it does not support EVP_DigestVerify. +// OpenSSL on the otherhand does not support using EVP_DigestVerifyUpdate for eddsa, which is why we end up with this +// mess. +#if defined(LIBRESSL_VERSION_NUMBER) || defined(LIBWOLFSSL_VERSION_HEX) + if (EVP_DigestVerifyUpdate(ctx.get(), reinterpret_cast(data.data()), + data.size()) != 1) { + ec = error::signature_verification_error::verifyupdate_failed; + return; + } + if (EVP_DigestVerifyFinal(ctx.get(), reinterpret_cast(signature.data()), + signature.size()) != 1) { + ec = error::signature_verification_error::verifyfinal_failed; + return; + } +#else + auto res = EVP_DigestVerify(ctx.get(), reinterpret_cast(signature.data()), + signature.size(), reinterpret_cast(data.data()), + data.size()); + if (res != 1) { + ec = error::signature_verification_error::verifyfinal_failed; + return; + } +#endif + } + /** + * Returns the algorithm name provided to the constructor + * \return algorithm's name + */ + std::string name() const { return alg_name; } + + private: + /// OpenSSL struct containing keys + helper::evp_pkey_handle pkey; + /// algorithm's name + const std::string alg_name; + }; +#endif + /** + * \brief Base class for PSS-RSA family of algorithms + */ + struct pss { + /** + * Construct new pss algorithm + * \param public_key RSA public key in PEM format + * \param private_key RSA private key or empty string if not available. If empty, signing will always fail. + * \param public_key_password Password to decrypt public key pem. + * \param private_key_password Password to decrypt private key pem. + * \param md Pointer to hash function + * \param name Name of the algorithm + */ + pss(const std::string& public_key, const std::string& private_key, const std::string& public_key_password, + const std::string& private_key_password, const EVP_MD* (*md)(), std::string name) + : md(md), alg_name(std::move(name)) { + if (!private_key.empty()) { + pkey = helper::load_private_key_from_string(private_key, private_key_password); + } else if (!public_key.empty()) { + pkey = helper::load_public_key_from_string(public_key, public_key_password); + } else + throw error::rsa_exception(error::rsa_error::no_key_provided); + } + + /** + * Sign jwt data + * \param data The data to sign + * \param ec error_code filled with details on error + * \return ECDSA signature for the given data + */ + std::string sign(const std::string& data, std::error_code& ec) const { + ec.clear(); + auto md_ctx = helper::make_evp_md_ctx(); + if (!md_ctx) { + ec = error::signature_generation_error::create_context_failed; + return {}; + } + EVP_PKEY_CTX* ctx = nullptr; + if (EVP_DigestSignInit(md_ctx.get(), &ctx, md(), nullptr, pkey.get()) != 1) { + ec = error::signature_generation_error::signinit_failed; + return {}; + } + if (EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_PKCS1_PSS_PADDING) <= 0) { + ec = error::signature_generation_error::rsa_padding_failed; + return {}; + } +// wolfSSL does not require EVP_PKEY_CTX_set_rsa_pss_saltlen. The default behavior +// sets the salt length to the hash length. Unlike OpenSSL which exposes this functionality. +#ifndef LIBWOLFSSL_VERSION_HEX + if (EVP_PKEY_CTX_set_rsa_pss_saltlen(ctx, -1) <= 0) { + ec = error::signature_generation_error::set_rsa_pss_saltlen_failed; + return {}; + } +#endif + if (EVP_DigestUpdate(md_ctx.get(), data.data(), data.size()) != 1) { + ec = error::signature_generation_error::digestupdate_failed; + return {}; + } + + size_t size = EVP_PKEY_size(pkey.get()); + std::string res(size, 0x00); + if (EVP_DigestSignFinal( + md_ctx.get(), + (unsigned char*)res.data(), // NOLINT(google-readability-casting) requires `const_cast` + &size) <= 0) { + ec = error::signature_generation_error::signfinal_failed; + return {}; + } + + return res; + } + + /** + * Check if signature is valid + * \param data The data to check signature against + * \param signature Signature provided by the jwt + * \param ec Filled with error details + */ + void verify(const std::string& data, const std::string& signature, std::error_code& ec) const { + ec.clear(); + + auto md_ctx = helper::make_evp_md_ctx(); + if (!md_ctx) { + ec = error::signature_verification_error::create_context_failed; + return; + } + EVP_PKEY_CTX* ctx = nullptr; + if (EVP_DigestVerifyInit(md_ctx.get(), &ctx, md(), nullptr, pkey.get()) != 1) { + ec = error::signature_verification_error::verifyinit_failed; + return; + } + if (EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_PKCS1_PSS_PADDING) <= 0) { + ec = error::signature_generation_error::rsa_padding_failed; + return; + } +// wolfSSL does not require EVP_PKEY_CTX_set_rsa_pss_saltlen. The default behavior +// sets the salt length to the hash length. Unlike OpenSSL which exposes this functionality. +#ifndef LIBWOLFSSL_VERSION_HEX + if (EVP_PKEY_CTX_set_rsa_pss_saltlen(ctx, -1) <= 0) { + ec = error::signature_verification_error::set_rsa_pss_saltlen_failed; + return; + } +#endif + if (EVP_DigestUpdate(md_ctx.get(), data.data(), data.size()) != 1) { + ec = error::signature_verification_error::verifyupdate_failed; + return; + } + + if (EVP_DigestVerifyFinal(md_ctx.get(), (unsigned char*)signature.data(), signature.size()) <= 0) { + ec = error::signature_verification_error::verifyfinal_failed; + return; + } + } + /** + * Returns the algorithm name provided to the constructor + * \return algorithm's name + */ + std::string name() const { return alg_name; } + + private: + /// OpenSSL structure containing keys + helper::evp_pkey_handle pkey; + /// Hash generator function + const EVP_MD* (*md)(); + /// algorithm's name + const std::string alg_name; + }; + + /** + * HS256 algorithm + */ + struct hs256 : public hmacsha { + /** + * Construct new instance of algorithm + * \param key HMAC signing key + */ + explicit hs256(std::string key) : hmacsha(std::move(key), EVP_sha256, "HS256") {} + }; + /** + * HS384 algorithm + */ + struct hs384 : public hmacsha { + /** + * Construct new instance of algorithm + * \param key HMAC signing key + */ + explicit hs384(std::string key) : hmacsha(std::move(key), EVP_sha384, "HS384") {} + }; + /** + * HS512 algorithm + */ + struct hs512 : public hmacsha { + /** + * Construct new instance of algorithm + * \param key HMAC signing key + */ + explicit hs512(std::string key) : hmacsha(std::move(key), EVP_sha512, "HS512") {} + }; + /** + * RS256 algorithm + */ + struct rs256 : public rsa { + /** + * Construct new instance of algorithm + * \param public_key RSA public key in PEM format + * \param private_key RSA private key or empty string if not available. If empty, signing will always fail. + * \param public_key_password Password to decrypt public key pem. + * \param private_key_password Password to decrypt private key pem. + */ + explicit rs256(const std::string& public_key, const std::string& private_key = "", + const std::string& public_key_password = "", const std::string& private_key_password = "") + : rsa(public_key, private_key, public_key_password, private_key_password, EVP_sha256, "RS256") {} + }; + /** + * RS384 algorithm + */ + struct rs384 : public rsa { + /** + * Construct new instance of algorithm + * \param public_key RSA public key in PEM format + * \param private_key RSA private key or empty string if not available. If empty, signing will always fail. + * \param public_key_password Password to decrypt public key pem. + * \param private_key_password Password to decrypt private key pem. + */ + explicit rs384(const std::string& public_key, const std::string& private_key = "", + const std::string& public_key_password = "", const std::string& private_key_password = "") + : rsa(public_key, private_key, public_key_password, private_key_password, EVP_sha384, "RS384") {} + }; + /** + * RS512 algorithm + */ + struct rs512 : public rsa { + /** + * Construct new instance of algorithm + * \param public_key RSA public key in PEM format + * \param private_key RSA private key or empty string if not available. If empty, signing will always fail. + * \param public_key_password Password to decrypt public key pem. + * \param private_key_password Password to decrypt private key pem. + */ + explicit rs512(const std::string& public_key, const std::string& private_key = "", + const std::string& public_key_password = "", const std::string& private_key_password = "") + : rsa(public_key, private_key, public_key_password, private_key_password, EVP_sha512, "RS512") {} + }; + /** + * ES256 algorithm + */ + struct es256 : public ecdsa { + /** + * Construct new instance of algorithm + * \param public_key ECDSA public key in PEM format + * \param private_key ECDSA private key or empty string if not available. If empty, signing will always + * fail. + * \param public_key_password Password to decrypt public key pem. + * \param private_key_password Password + * to decrypt private key pem. + */ + explicit es256(const std::string& public_key, const std::string& private_key = "", + const std::string& public_key_password = "", const std::string& private_key_password = "") + : ecdsa(public_key, private_key, public_key_password, private_key_password, EVP_sha256, "ES256", 64) {} + }; + /** + * ES384 algorithm + */ + struct es384 : public ecdsa { + /** + * Construct new instance of algorithm + * \param public_key ECDSA public key in PEM format + * \param private_key ECDSA private key or empty string if not available. If empty, signing will always + * fail. + * \param public_key_password Password to decrypt public key pem. + * \param private_key_password Password + * to decrypt private key pem. + */ + explicit es384(const std::string& public_key, const std::string& private_key = "", + const std::string& public_key_password = "", const std::string& private_key_password = "") + : ecdsa(public_key, private_key, public_key_password, private_key_password, EVP_sha384, "ES384", 96) {} + }; + /** + * ES512 algorithm + */ + struct es512 : public ecdsa { + /** + * Construct new instance of algorithm + * \param public_key ECDSA public key in PEM format + * \param private_key ECDSA private key or empty string if not available. If empty, signing will always + * fail. + * \param public_key_password Password to decrypt public key pem. + * \param private_key_password Password + * to decrypt private key pem. + */ + explicit es512(const std::string& public_key, const std::string& private_key = "", + const std::string& public_key_password = "", const std::string& private_key_password = "") + : ecdsa(public_key, private_key, public_key_password, private_key_password, EVP_sha512, "ES512", 132) {} + }; + /** + * ES256K algorithm + */ + struct es256k : public ecdsa { + /** + * Construct new instance of algorithm + * \param public_key ECDSA public key in PEM format + * \param private_key ECDSA private key or empty string if not available. If empty, signing will always + * fail. + * \param public_key_password Password to decrypt public key pem. + * \param private_key_password Password to decrypt private key pem. + */ + explicit es256k(const std::string& public_key, const std::string& private_key = "", + const std::string& public_key_password = "", const std::string& private_key_password = "") + : ecdsa(public_key, private_key, public_key_password, private_key_password, EVP_sha256, "ES256K", 64) {} + }; + +#if !defined(JWT_OPENSSL_1_0_0) && !defined(JWT_OPENSSL_1_1_0) + /** + * Ed25519 algorithm + * + * https://en.wikipedia.org/wiki/EdDSA#Ed25519 + * + * Requires at least OpenSSL 1.1.1. + */ + struct ed25519 : public eddsa { + /** + * Construct new instance of algorithm + * \param public_key Ed25519 public key in PEM format + * \param private_key Ed25519 private key or empty string if not available. If empty, signing will always + * fail. + * \param public_key_password Password to decrypt public key pem. + * \param private_key_password Password + * to decrypt private key pem. + */ + explicit ed25519(const std::string& public_key, const std::string& private_key = "", + const std::string& public_key_password = "", const std::string& private_key_password = "") + : eddsa(public_key, private_key, public_key_password, private_key_password, "EdDSA") {} + }; + + /** + * Ed448 algorithm + * + * https://en.wikipedia.org/wiki/EdDSA#Ed448 + * + * Requires at least OpenSSL 1.1.1. + */ + struct ed448 : public eddsa { + /** + * Construct new instance of algorithm + * \param public_key Ed448 public key in PEM format + * \param private_key Ed448 private key or empty string if not available. If empty, signing will always + * fail. + * \param public_key_password Password to decrypt public key pem. + * \param private_key_password Password + * to decrypt private key pem. + */ + explicit ed448(const std::string& public_key, const std::string& private_key = "", + const std::string& public_key_password = "", const std::string& private_key_password = "") + : eddsa(public_key, private_key, public_key_password, private_key_password, "EdDSA") {} + }; +#endif + + /** + * PS256 algorithm + */ + struct ps256 : public pss { + /** + * Construct new instance of algorithm + * \param public_key RSA public key in PEM format + * \param private_key RSA private key or empty string if not available. If empty, signing will always fail. + * \param public_key_password Password to decrypt public key pem. + * \param private_key_password Password to decrypt private key pem. + */ + explicit ps256(const std::string& public_key, const std::string& private_key = "", + const std::string& public_key_password = "", const std::string& private_key_password = "") + : pss(public_key, private_key, public_key_password, private_key_password, EVP_sha256, "PS256") {} + }; + /** + * PS384 algorithm + */ + struct ps384 : public pss { + /** + * Construct new instance of algorithm + * \param public_key RSA public key in PEM format + * \param private_key RSA private key or empty string if not available. If empty, signing will always fail. + * \param public_key_password Password to decrypt public key pem. + * \param private_key_password Password to decrypt private key pem. + */ + explicit ps384(const std::string& public_key, const std::string& private_key = "", + const std::string& public_key_password = "", const std::string& private_key_password = "") + : pss(public_key, private_key, public_key_password, private_key_password, EVP_sha384, "PS384") {} + }; + /** + * PS512 algorithm + */ + struct ps512 : public pss { + /** + * Construct new instance of algorithm + * \param public_key RSA public key in PEM format + * \param private_key RSA private key or empty string if not available. If empty, signing will always fail. + * \param public_key_password Password to decrypt public key pem. + * \param private_key_password Password to decrypt private key pem. + */ + explicit ps512(const std::string& public_key, const std::string& private_key = "", + const std::string& public_key_password = "", const std::string& private_key_password = "") + : pss(public_key, private_key, public_key_password, private_key_password, EVP_sha512, "PS512") {} + }; + } // namespace algorithm + + /** + * \brief JSON Abstractions for working with any library + */ + namespace json { + /** + * \brief Generic JSON types used in JWTs + * + * This enum is to abstract the third party underlying types + */ + enum class type { boolean, integer, number, string, array, object }; + } // namespace json + + namespace details { +#ifdef __cpp_lib_void_t + template + using void_t = std::void_t; +#else + // https://en.cppreference.com/w/cpp/types/void_t + template + struct make_void { + using type = void; + }; + + template + using void_t = typename make_void::type; +#endif + +#ifdef __cpp_lib_experimental_detect + template class _Op, typename... _Args> + using is_detected = std::experimental::is_detected<_Op, _Args...>; +#else + struct nonesuch { + nonesuch() = delete; + ~nonesuch() = delete; + nonesuch(nonesuch const&) = delete; + nonesuch(nonesuch const&&) = delete; + void operator=(nonesuch const&) = delete; + void operator=(nonesuch&&) = delete; + }; + + // https://en.cppreference.com/w/cpp/experimental/is_detected + template class Op, class... Args> + struct detector { + using value = std::false_type; + using type = Default; + }; + + template class Op, class... Args> + struct detector>, Op, Args...> { + using value = std::true_type; + using type = Op; + }; + + template class Op, class... Args> + using is_detected = typename detector::value; +#endif + + template + using is_signature = typename std::is_same; + + template class Op, typename Signature> + struct is_function_signature_detected { + using type = Op; + static constexpr auto value = is_detected::value && std::is_function::value && + is_signature::value; + }; + + template + struct supports_get_type { + template + using get_type_t = decltype(T::get_type); + + static constexpr auto value = + is_function_signature_detected::value; + + // Internal assertions for better feedback + static_assert(value, "traits implementation must provide `jwt::json::type get_type(const value_type&)`"); + }; + +#define JWT_CPP_JSON_TYPE_TYPE(TYPE) json_##TYPE_type +#define JWT_CPP_AS_TYPE_T(TYPE) as_##TYPE_t +#define JWT_CPP_SUPPORTS_AS(TYPE) \ + template \ + struct supports_as_##TYPE { \ + template \ + using JWT_CPP_AS_TYPE_T(TYPE) = decltype(T::as_##TYPE); \ + \ + static constexpr auto value = \ + is_function_signature_detected::value; \ + \ + static_assert(value, "traits implementation must provide `" #TYPE "_type as_" #TYPE "(const value_type&)`"); \ + } + + JWT_CPP_SUPPORTS_AS(object); + JWT_CPP_SUPPORTS_AS(array); + JWT_CPP_SUPPORTS_AS(string); + JWT_CPP_SUPPORTS_AS(number); + JWT_CPP_SUPPORTS_AS(integer); + JWT_CPP_SUPPORTS_AS(boolean); + +#undef JWT_CPP_JSON_TYPE_TYPE +#undef JWT_CPP_AS_TYPE_T +#undef JWT_CPP_SUPPORTS_AS + + template + struct is_valid_traits { + static constexpr auto value = + supports_get_type::value && + supports_as_object::value && + supports_as_array::value && + supports_as_string::value && + supports_as_number::value && + supports_as_integer::value && + supports_as_boolean::value; + }; + + template + struct is_valid_json_value { + static constexpr auto value = + std::is_default_constructible::value && + std::is_constructible::value && // a more generic is_copy_constructible + std::is_move_constructible::value && std::is_assignable::value && + std::is_copy_assignable::value && std::is_move_assignable::value; + // TODO(prince-chrismc): Stream operators + }; + + // https://stackoverflow.com/a/53967057/8480874 + template + struct is_iterable : std::false_type {}; + + template + struct is_iterable())), decltype(std::end(std::declval())), +#if __cplusplus > 201402L + decltype(std::cbegin(std::declval())), decltype(std::cend(std::declval())) +#else + decltype(std::begin(std::declval())), + decltype(std::end(std::declval())) +#endif + >> : std::true_type { + }; + +#if __cplusplus > 201703L + template + inline constexpr bool is_iterable_v = is_iterable::value; +#endif + + template + using is_count_signature = typename std::is_integral().count( + std::declval()))>; + + template + struct is_subcription_operator_signature : std::false_type {}; + + template + struct is_subcription_operator_signature< + object_type, string_type, + void_t().operator[](std::declval()))>> : std::true_type { + // TODO(prince-chrismc): I am not convienced this is meaningful anymore + static_assert( + value, + "object_type must implementate the subscription operator '[]' taking string_type as an argument"); + }; + + template + using is_at_const_signature = + typename std::is_same().at(std::declval())), + const value_type&>; + + template + struct is_valid_json_object { + template + using mapped_type_t = typename T::mapped_type; + template + using key_type_t = typename T::key_type; + template + using iterator_t = typename T::iterator; + template + using const_iterator_t = typename T::const_iterator; + + static constexpr auto value = + std::is_constructible::value && + is_detected::value && + std::is_same::value && + is_detected::value && + (std::is_same::value || + std::is_constructible::value) && + is_detected::value && is_detected::value && + is_iterable::value && is_count_signature::value && + is_subcription_operator_signature::value && + is_at_const_signature::value; + }; + + template + struct is_valid_json_array { + template + using value_type_t = typename T::value_type; + + static constexpr auto value = std::is_constructible::value && + is_iterable::value && + is_detected::value && + std::is_same::value; + }; + + template + using is_substr_start_end_index_signature = + typename std::is_same().substr(std::declval(), + std::declval())), + string_type>; + + template + using is_substr_start_index_signature = + typename std::is_same().substr(std::declval())), + string_type>; + + template + using is_std_operate_plus_signature = + typename std::is_same(), std::declval())), + string_type>; + + template + struct is_valid_json_string { + static constexpr auto substr = is_substr_start_end_index_signature::value && + is_substr_start_index_signature::value; + static_assert(substr, "string_type must have a substr method taking only a start index and an overload " + "taking a start and end index, both must return a string_type"); + + static constexpr auto operator_plus = is_std_operate_plus_signature::value; + static_assert(operator_plus, + "string_type must have a '+' operator implemented which returns the concatenated string"); + + static constexpr auto value = + std::is_constructible::value && substr && operator_plus; + }; + + template + struct is_valid_json_number { + static constexpr auto value = + std::is_floating_point::value && std::is_constructible::value; + }; + + template + struct is_valid_json_integer { + static constexpr auto value = std::is_signed::value && + !std::is_floating_point::value && + std::is_constructible::value; + }; + template + struct is_valid_json_boolean { + static constexpr auto value = std::is_convertible::value && + std::is_constructible::value; + }; + + template + struct is_valid_json_types { + // Internal assertions for better feedback + static_assert(is_valid_json_value::value, + "value_type must meet basic requirements, default constructor, copyable, moveable"); + static_assert(is_valid_json_object::value, + "object_type must be a string_type to value_type container"); + static_assert(is_valid_json_array::value, + "array_type must be a container of value_type"); + + static constexpr auto value = is_valid_json_value::value && + is_valid_json_object::value && + is_valid_json_array::value && + is_valid_json_string::value && + is_valid_json_number::value && + is_valid_json_integer::value && + is_valid_json_boolean::value; + }; + } // namespace details + + /** + * \brief a class to store a generic JSON value as claim + * + * \tparam json_traits : JSON implementation traits + * + * \see [RFC 7519: JSON Web Token (JWT)](https://tools.ietf.org/html/rfc7519) + */ + template + class basic_claim { + /** + * The reason behind this is to provide an expressive abstraction without + * over 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. + */ + 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)) {} + + /** + * Get wrapped JSON value + * \return Wrapped JSON value + */ + typename json_traits::value_type to_json() const { return val; } + + /** + * Parse input stream into underlying JSON value + * \return input stream + */ + std::istream& operator>>(std::istream& is) { return is >> val; } + + /** + * Serialize claim to output stream from wrapped JSON value + * \return output stream + */ + std::ostream& operator<<(std::ostream& os) { return os << val; } + + /** + * Get type of contained JSON value + * \return Type + * \throw std::logic_error An internal error occurred + */ + json::type get_type() const { return json_traits::get_type(val); } + + /** + * Get the contained JSON value as a string + * \return content as string + * \throw std::bad_cast Content was not a string + */ + typename json_traits::string_type as_string() const { return json_traits::as_string(val); } + + /** + * \brief Get the contained JSON value as a date + * + * If the value is a decimal, it is rounded up to the closest integer + * + * \return content as date + * \throw std::bad_cast Content was not a date + */ + date as_date() const { + using std::chrono::system_clock; + if (get_type() == json::type::number) return system_clock::from_time_t(std::round(as_number())); + return system_clock::from_time_t(as_integer()); + } + + /** + * Get the contained JSON value as an array + * \return content as array + * \throw std::bad_cast Content was not an array + */ + typename json_traits::array_type as_array() const { return json_traits::as_array(val); } + + /** + * Get the contained JSON value as a set of strings + * \return content as set of strings + * \throw std::bad_cast Content was not an array of string + */ + set_t as_set() const { + set_t res; + for (const auto& e : json_traits::as_array(val)) { + res.insert(json_traits::as_string(e)); + } + return res; + } + + /** + * Get the contained JSON value as an integer + * \return content as int + * \throw std::bad_cast Content was not an int + */ + typename json_traits::integer_type as_integer() const { return json_traits::as_integer(val); } + + /** + * Get the contained JSON value as a bool + * \return content as bool + * \throw std::bad_cast Content was not a bool + */ + typename json_traits::boolean_type as_boolean() const { return json_traits::as_boolean(val); } + + /** + * Get the contained JSON value as a number + * \return content as double + * \throw std::bad_cast Content was not a number + */ + typename json_traits::number_type as_number() const { return json_traits::as_number(val); } + }; + + namespace error { + /** + * Attempt to parse JSON was unsuccessful + */ + struct invalid_json_exception : public std::runtime_error { + invalid_json_exception() : runtime_error("invalid json") {} + }; + /** + * Attempt to access claim was unsuccessful + */ + struct claim_not_present_exception : public std::out_of_range { + claim_not_present_exception() : out_of_range("claim not found") {} + }; + } // namespace error + + namespace details { + template + struct map_of_claims { + typename json_traits::object_type claims; + using basic_claim_t = basic_claim; + using iterator = typename json_traits::object_type::iterator; + using const_iterator = typename json_traits::object_type::const_iterator; + + map_of_claims() = default; + map_of_claims(const map_of_claims&) = default; + map_of_claims(map_of_claims&&) = default; + map_of_claims& operator=(const map_of_claims&) = default; + map_of_claims& operator=(map_of_claims&&) = default; + + map_of_claims(typename json_traits::object_type json) : claims(std::move(json)) {} + + iterator begin() { return claims.begin(); } + iterator end() { return claims.end(); } + const_iterator cbegin() const { return claims.begin(); } + const_iterator cend() const { return claims.end(); } + const_iterator begin() const { return claims.begin(); } + const_iterator end() const { return claims.end(); } + + /** + * \brief Parse a JSON string into a map of claims + * + * The implication is that a "map of claims" is identic to a JSON object + * + * \param str JSON data to be parse as an object + * \return content as JSON object + */ + static typename json_traits::object_type parse_claims(const typename json_traits::string_type& str) { + typename json_traits::value_type val; + if (!json_traits::parse(val, str)) throw error::invalid_json_exception(); + + return json_traits::as_object(val); + }; + + /** + * Check if a claim is present in the map + * \return true if claim was present, false otherwise + */ + bool has_claim(const typename json_traits::string_type& name) const noexcept { + return claims.count(name) != 0; + } + + /** + * Get a claim by name + * + * \param name the name of the desired claim + * \return Requested claim + * \throw jwt::error::claim_not_present_exception if the claim was not present + */ + basic_claim_t get_claim(const typename json_traits::string_type& name) const { + if (!has_claim(name)) throw error::claim_not_present_exception(); + return basic_claim_t{claims.at(name)}; + } + }; + } // namespace details + + /** + * Base class that represents a token payload. + * Contains Convenience accessors for common claims. + */ + template + class payload { + protected: + details::map_of_claims payload_claims; + + public: + using basic_claim_t = basic_claim; + + /** + * Check if issuer is present ("iss") + * \return true if present, false otherwise + */ + bool has_issuer() const noexcept { return has_payload_claim("iss"); } + /** + * Check if subject is present ("sub") + * \return true if present, false otherwise + */ + bool has_subject() const noexcept { return has_payload_claim("sub"); } + /** + * Check if audience is present ("aud") + * \return true if present, false otherwise + */ + bool has_audience() const noexcept { return has_payload_claim("aud"); } + /** + * Check if expires is present ("exp") + * \return true if present, false otherwise + */ + bool has_expires_at() const noexcept { return has_payload_claim("exp"); } + /** + * Check if not before is present ("nbf") + * \return true if present, false otherwise + */ + bool has_not_before() const noexcept { return has_payload_claim("nbf"); } + /** + * Check if issued at is present ("iat") + * \return true if present, false otherwise + */ + bool has_issued_at() const noexcept { return has_payload_claim("iat"); } + /** + * Check if token id is present ("jti") + * \return true if present, false otherwise + */ + bool has_id() const noexcept { return has_payload_claim("jti"); } + /** + * Get issuer claim + * \return issuer as string + * \throw std::runtime_error If claim was not present + * \throw std::bad_cast Claim was present but not a string (Should not happen in a valid token) + */ + typename json_traits::string_type get_issuer() const { return get_payload_claim("iss").as_string(); } + /** + * Get subject claim + * \return subject as string + * \throw std::runtime_error If claim was not present + * \throw std::bad_cast Claim was present but not a string (Should not happen in a valid token) + */ + typename json_traits::string_type get_subject() const { return get_payload_claim("sub").as_string(); } + /** + * Get audience claim + * \return audience as a set of strings + * \throw std::runtime_error If claim was not present + * \throw std::bad_cast Claim was present but not a set (Should not happen in a valid token) + */ + typename basic_claim_t::set_t get_audience() const { + auto aud = get_payload_claim("aud"); + if (aud.get_type() == json::type::string) return {aud.as_string()}; + + return aud.as_set(); + } + /** + * Get expires claim + * \return expires as a date in utc + * \throw std::runtime_error If claim was not present + * \throw std::bad_cast Claim was present but not a date (Should not happen in a valid token) + */ + date get_expires_at() const { return get_payload_claim("exp").as_date(); } + /** + * Get not valid before claim + * \return nbf date in utc + * \throw std::runtime_error If claim was not present + * \throw std::bad_cast Claim was present but not a date (Should not happen in a valid token) + */ + date get_not_before() const { return get_payload_claim("nbf").as_date(); } + /** + * Get issued at claim + * \return issued at as date in utc + * \throw std::runtime_error If claim was not present + * \throw std::bad_cast Claim was present but not a date (Should not happen in a valid token) + */ + date get_issued_at() const { return get_payload_claim("iat").as_date(); } + /** + * Get id claim + * \return id as string + * \throw std::runtime_error If claim was not present + * \throw std::bad_cast Claim was present but not a string (Should not happen in a valid token) + */ + typename json_traits::string_type get_id() const { return get_payload_claim("jti").as_string(); } + /** + * Check if a payload claim is present + * \return true if claim was present, false otherwise + */ + bool has_payload_claim(const typename json_traits::string_type& name) const noexcept { + return payload_claims.has_claim(name); + } + /** + * Get payload claim + * \return Requested claim + * \throw std::runtime_error If claim was not present + */ + basic_claim_t get_payload_claim(const typename json_traits::string_type& name) const { + return payload_claims.get_claim(name); + } + }; + + /** + * Base class that represents a token header. + * Contains Convenience accessors for common claims. + */ + template + class header { + protected: + details::map_of_claims header_claims; + + public: + using basic_claim_t = basic_claim; + /** + * Check if algorithm is present ("alg") + * \return true if present, false otherwise + */ + bool has_algorithm() const noexcept { return has_header_claim("alg"); } + /** + * Check if type is present ("typ") + * \return true if present, false otherwise + */ + bool has_type() const noexcept { return has_header_claim("typ"); } + /** + * Check if content type is present ("cty") + * \return true if present, false otherwise + */ + bool has_content_type() const noexcept { return has_header_claim("cty"); } + /** + * Check if key id is present ("kid") + * \return true if present, false otherwise + */ + bool has_key_id() const noexcept { return has_header_claim("kid"); } + /** + * Get algorithm claim + * \return algorithm as string + * \throw std::runtime_error If claim was not present + * \throw std::bad_cast Claim was present but not a string (Should not happen in a valid token) + */ + typename json_traits::string_type get_algorithm() const { return get_header_claim("alg").as_string(); } + /** + * Get type claim + * \return type as a string + * \throw std::runtime_error If claim was not present + * \throw std::bad_cast Claim was present but not a string (Should not happen in a valid token) + */ + typename json_traits::string_type get_type() const { return get_header_claim("typ").as_string(); } + /** + * Get content type claim + * \return content type as string + * \throw std::runtime_error If claim was not present + * \throw std::bad_cast Claim was present but not a string (Should not happen in a valid token) + */ + typename json_traits::string_type get_content_type() const { return get_header_claim("cty").as_string(); } + /** + * Get key id claim + * \return key id as string + * \throw std::runtime_error If claim was not present + * \throw std::bad_cast Claim was present but not a string (Should not happen in a valid token) + */ + typename json_traits::string_type get_key_id() const { return get_header_claim("kid").as_string(); } + /** + * Check if a header claim is present + * \return true if claim was present, false otherwise + */ + bool has_header_claim(const typename json_traits::string_type& name) const noexcept { + return header_claims.has_claim(name); + } + /** + * Get header claim + * \return Requested claim + * \throw std::runtime_error If claim was not present + */ + basic_claim_t get_header_claim(const typename json_traits::string_type& name) const { + return header_claims.get_claim(name); + } + }; + + /** + * Class containing all information about a decoded token + */ + template + class decoded_jwt : public header, public payload { + protected: + /// Unmodified token, as passed to constructor + typename json_traits::string_type token; + /// Header part decoded from base64 + typename json_traits::string_type header; + /// Unmodified header part in base64 + typename json_traits::string_type header_base64; + /// Payload part decoded from base64 + typename json_traits::string_type payload; + /// Unmodified payload part in base64 + typename json_traits::string_type payload_base64; + /// Signature part decoded from base64 + typename json_traits::string_type signature; + /// Unmodified signature part in base64 + typename json_traits::string_type signature_base64; + + public: + using basic_claim_t = basic_claim; +#ifndef JWT_DISABLE_BASE64 + /** + * \brief Parses a given token + * + * \note Decodes using the jwt::base64url which supports an std::string + * + * \param token The token to parse + * \throw std::invalid_argument Token is not in correct format + * \throw std::runtime_error Base64 decoding failed or invalid json + */ + JWT_CLAIM_EXPLICIT decoded_jwt(const typename json_traits::string_type& token) + : decoded_jwt(token, [](const typename json_traits::string_type& str) { + return base::decode(base::pad(str)); + }) {} +#endif + /** + * \brief Parses a given token + * + * \tparam Decode is 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 == json_traits::string_type::npos) throw std::invalid_argument("invalid token supplied"); + auto payload_end = token.find('.', hdr_end + 1); + if (payload_end == json_traits::string_type::npos) throw std::invalid_argument("invalid token supplied"); + header_base64 = token.substr(0, hdr_end); + payload_base64 = token.substr(hdr_end + 1, payload_end - hdr_end - 1); + signature_base64 = token.substr(payload_end + 1); + + header = decode(header_base64); + payload = decode(payload_base64); + signature = decode(signature_base64); + + this->header_claims = details::map_of_claims::parse_claims(header); + this->payload_claims = details::map_of_claims::parse_claims(payload); + } + + /** + * Get token string, as passed to constructor + * \return token as passed to constructor + */ + const typename json_traits::string_type& get_token() const noexcept { return token; } + /** + * Get header part as json string + * \return header part after base64 decoding + */ + const typename json_traits::string_type& get_header() const noexcept { return header; } + /** + * Get payload part as json string + * \return payload part after base64 decoding + */ + const typename json_traits::string_type& get_payload() const noexcept { return payload; } + /** + * Get signature part as json string + * \return signature part after base64 decoding + */ + const typename json_traits::string_type& get_signature() const noexcept { return signature; } + /** + * Get header part as base64 string + * \return header part before base64 decoding + */ + const typename json_traits::string_type& get_header_base64() const noexcept { return header_base64; } + /** + * Get payload part as base64 string + * \return payload part before base64 decoding + */ + const typename json_traits::string_type& get_payload_base64() const noexcept { return payload_base64; } + /** + * Get signature part as base64 string + * \return signature part before base64 decoding + */ + const typename json_traits::string_type& get_signature_base64() const noexcept { return signature_base64; } + /** + * Get all payload as JSON object + * \return map of claims + */ + typename json_traits::object_type get_payload_json() const { return this->payload_claims.claims; } + /** + * Get all header as JSON object + * \return map of claims + */ + typename json_traits::object_type get_header_json() const { return this->header_claims.claims; } + /** + * Get a payload claim by name + * + * \param name the name of the desired claim + * \return Requested claim + * \throw jwt::error::claim_not_present_exception if the claim was not present + */ + basic_claim_t get_payload_claim(const typename json_traits::string_type& name) const { + return this->payload_claims.get_claim(name); + } + /** + * Get a header claim by name + * + * \param name the name of the desired claim + * \return Requested claim + * \throw jwt::error::claim_not_present_exception if the claim was not present + */ + basic_claim_t get_header_claim(const typename json_traits::string_type& name) const { + return this->header_claims.get_claim(name); + } + }; + + /** + * Builder class to build and sign a new token + * Use jwt::create() to get an instance of this class. + */ + template + class builder { + typename json_traits::object_type header_claims; + typename json_traits::object_type payload_claims; + + 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 typename json_traits::string_type& id, typename json_traits::value_type c) { + header_claims[id] = std::move(c); + return *this; + } + + /** + * Set a header claim. + * \param id Name of the claim + * \param c Claim to add + * \return *this to allow for method chaining + */ + builder& set_header_claim(const typename json_traits::string_type& id, basic_claim c) { + header_claims[id] = c.to_json(); + return *this; + } + /** + * Set a payload claim. + * \param id Name of the claim + * \param c Claim to add + * \return *this to allow for method chaining + */ + builder& set_payload_claim(const typename json_traits::string_type& id, typename json_traits::value_type c) { + payload_claims[id] = std::move(c); + return *this; + } + /** + * Set a payload claim. + * \param id Name of the claim + * \param c Claim to add + * \return *this to allow for method chaining + */ + builder& set_payload_claim(const typename json_traits::string_type& id, basic_claim c) { + payload_claims[id] = c.to_json(); + return *this; + } + /** + * \brief Set algorithm claim + * You normally don't need to do this, as the algorithm is automatically set if you don't change it. + * + * \param str Name of algorithm + * \return *this to allow for method chaining + */ + builder& set_algorithm(typename json_traits::string_type str) { + return set_header_claim("alg", typename json_traits::value_type(str)); + } + /** + * Set type claim + * \param str Type to set + * \return *this to allow for method chaining + */ + builder& set_type(typename json_traits::string_type str) { + return set_header_claim("typ", typename json_traits::value_type(str)); + } + /** + * Set content type claim + * \param str Type to set + * \return *this to allow for method chaining + */ + builder& set_content_type(typename json_traits::string_type str) { + return set_header_claim("cty", typename json_traits::value_type(str)); + } + /** + * \brief Set key id claim + * + * \param str Key id to set + * \return *this to allow for method chaining + */ + builder& set_key_id(typename json_traits::string_type str) { + return set_header_claim("kid", typename json_traits::value_type(str)); + } + /** + * Set issuer claim + * \param str Issuer to set + * \return *this to allow for method chaining + */ + builder& set_issuer(typename json_traits::string_type str) { + return set_payload_claim("iss", typename json_traits::value_type(str)); + } + /** + * Set subject claim + * \param str Subject to set + * \return *this to allow for method chaining + */ + builder& set_subject(typename json_traits::string_type str) { + return set_payload_claim("sub", typename json_traits::value_type(str)); + } + /** + * Set audience claim + * \param a Audience set + * \return *this to allow for method chaining + */ + builder& set_audience(typename json_traits::array_type a) { + return set_payload_claim("aud", typename json_traits::value_type(a)); + } + /** + * Set audience claim + * \param aud Single audience + * \return *this to allow for method chaining + */ + builder& set_audience(typename json_traits::string_type aud) { + return set_payload_claim("aud", typename json_traits::value_type(aud)); + } + /** + * Set expires at claim + * \param d Expires time + * \return *this to allow for method chaining + */ + builder& set_expires_at(const date& d) { return set_payload_claim("exp", basic_claim(d)); } + /** + * Set not before claim + * \param d First valid time + * \return *this to allow for method chaining + */ + builder& set_not_before(const date& d) { return set_payload_claim("nbf", basic_claim(d)); } + /** + * Set issued at claim + * \param d Issued at time, should be current time + * \return *this to allow for method chaining + */ + builder& set_issued_at(const date& d) { return set_payload_claim("iat", basic_claim(d)); } + /** + * Set id claim + * \param str ID to set + * \return *this to allow for method chaining + */ + builder& set_id(const typename json_traits::string_type& str) { + return set_payload_claim("jti", typename json_traits::value_type(str)); + } + + /** + * Sign token and return result + * \tparam Algo Callable method which takes a string_type and return the signed input as a string_type + * \tparam Encode Callable method which takes a string_type and base64url safe encodes it, + * MUST return the result with no padding; trim the result. + * \param algo Instance of an algorithm to sign the token with + * \param encode Callable to transform the serialized json to base64 with no padding + * \return Final token as a string + * + * \note If the 'alg' header in not set in the token it will be set to `algo.name()` + */ + template + typename json_traits::string_type sign(const Algo& algo, Encode encode) const { + std::error_code ec; + auto res = sign(algo, encode, ec); + error::throw_if_error(ec); + return res; + } +#ifndef JWT_DISABLE_BASE64 + /** + * Sign token and return result + * + * using the `jwt::base` functions provided + * + * \param algo Instance of an algorithm to sign the token with + * \return Final token as a string + */ + template + typename json_traits::string_type sign(const Algo& algo) const { + std::error_code ec; + auto res = sign(algo, ec); + error::throw_if_error(ec); + return res; + } +#endif + + /** + * Sign token and return result + * \tparam Algo Callable method which takes a string_type and return the signed input as a string_type + * \tparam Encode Callable method which takes a string_type and base64url safe encodes it, + * MUST return the result with no padding; trim the result. + * \param algo Instance of an algorithm to sign the token with + * \param encode Callable to transform the serialized json to base64 with no padding + * \param ec error_code filled with details on error + * \return Final token as a string + * + * \note If the 'alg' header in not set in the token it will be set to `algo.name()` + */ + template + typename json_traits::string_type sign(const Algo& algo, Encode encode, std::error_code& ec) const { + // make a copy such that a builder can be re-used + typename json_traits::object_type obj_header = header_claims; + if (header_claims.count("alg") == 0) obj_header["alg"] = typename json_traits::value_type(algo.name()); + + const auto header = encode(json_traits::serialize(typename json_traits::value_type(obj_header))); + const auto payload = encode(json_traits::serialize(typename json_traits::value_type(payload_claims))); + const auto token = header + "." + payload; + + auto signature = algo.sign(token, ec); + if (ec) return {}; + + return token + "." + encode(signature); + } +#ifndef JWT_DISABLE_BASE64 + /** + * Sign token and return result + * + * using the `jwt::base` functions provided + * + * \param algo Instance of an algorithm to sign the token with + * \param ec error_code filled with details on error + * \return Final token as a string + */ + template + typename json_traits::string_type sign(const Algo& algo, std::error_code& ec) const { + return sign( + algo, + [](const typename json_traits::string_type& data) { + return base::trim(base::encode(data)); + }, + ec); + } +#endif + }; + + namespace verify_ops { + /** + * This is the base container which holds the token that need to be verified + */ + template + struct verify_context { + verify_context(date ctime, const decoded_jwt& j, size_t l) + : current_time(ctime), jwt(j), default_leeway(l) {} + // Current time, retrieved from the verifiers clock and cached for performance and consistency + date current_time; + // The jwt passed to the verifier + const decoded_jwt& jwt; + // The configured default leeway for this verification + size_t default_leeway{0}; + + // The claim key to apply this comparison on + typename json_traits::string_type claim_key{}; + + // 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_verification_error::token_expired; + } + } + }; + + /** + * Checks that the current time is after the time specified in the given + * claim. This is identical to how the "nbf" and "iat" check works. + */ + template + struct date_after_claim { + const size_t leeway; + void operator()(const verify_context& ctx, std::error_code& ec) const { + auto jc = ctx.get_claim(in_header, json::type::integer, ec); + if (ec) return; + auto c = jc.as_date(); + if (ctx.current_time < c - std::chrono::seconds(leeway)) { + ec = error::token_verification_error::token_expired; + } + } + }; + + /** + * Checks if the given set is a subset of the set inside the token. + * If the token value is a string it is traited as a set of a single element. + * The comparison is case sensitive. + */ + template + struct is_subset_claim { + const typename basic_claim::set_t expected; + void operator()(const verify_context& ctx, std::error_code& ec) const { + auto c = ctx.get_claim(in_header, ec); + if (ec) return; + if (c.get_type() == json::type::string) { + if (expected.size() != 1 || *expected.begin() != c.as_string()) { + ec = error::token_verification_error::audience_missmatch; + return; + } + } else if (c.get_type() == json::type::array) { + auto jc = c.as_set(); + for (auto& e : expected) { + if (jc.find(e) == jc.end()) { + ec = error::token_verification_error::audience_missmatch; + return; + } + } + } else { + ec = error::token_verification_error::claim_type_missmatch; + return; + } + } + }; + + /** + * Checks if the claim is a string and does an case insensitive comparison. + */ + template + struct insensitive_string_claim { + const typename json_traits::string_type expected; + std::locale locale; + insensitive_string_claim(const typename json_traits::string_type& e, std::locale loc) + : expected(to_lower_unicode(e, loc)), locale(loc) {} + + void operator()(const verify_context& ctx, std::error_code& ec) const { + const auto c = ctx.get_claim(in_header, json::type::string, ec); + if (ec) return; + if (to_lower_unicode(c.as_string(), locale) != expected) { + ec = error::token_verification_error::claim_value_missmatch; + } + } + + static std::string to_lower_unicode(const std::string& str, const std::locale& loc) { + std::mbstate_t state = std::mbstate_t(); + const char* in_next = str.data(); + const char* in_end = str.data() + str.size(); + std::wstring wide; + wide.reserve(str.size()); + + while (in_next != in_end) { + wchar_t wc; + std::size_t result = std::mbrtowc(&wc, in_next, in_end - in_next, &state); + if (result == static_cast(-1)) { + throw std::runtime_error("encoding error: " + std::string(std::strerror(errno))); + } else if (result == static_cast(-2)) { + throw std::runtime_error("conversion error: next bytes constitute an incomplete, but so far " + "valid, multibyte character."); + } + in_next += result; + wide.push_back(wc); + } + + auto& f = std::use_facet>(loc); + f.tolower(&wide[0], &wide[0] + wide.size()); + + std::string out; + out.reserve(wide.size()); + for (wchar_t wc : wide) { + char mb[MB_LEN_MAX]; + std::size_t n = std::wcrtomb(mb, wc, &state); + if (n != static_cast(-1)) out.append(mb, n); + } + + return out; + } + }; + } // namespace verify_ops + + /** + * Verifier class used to check if a decoded token contains all claims required by your application and has a valid + * signature. + */ + template + class verifier { + public: + using basic_claim_t = basic_claim; + /** + * Verification function + * + * This gets passed the current verifier, a reference to the decoded jwt, a reference to the key of this claim, + * as well as a reference to an error_code. + * The function checks if the actual value matches certain rules (e.g. equality to value x) and sets the error_code if + * it does not. Once a non zero error_code is encountered the verification stops and this error_code becomes the result + * returned from verify + */ + using verify_check_fn_t = + std::function&, std::error_code& ec)>; + + private: + struct algo_base { + virtual ~algo_base() = default; + virtual void verify(const std::string& data, const std::string& sig, std::error_code& ec) = 0; + }; + template + struct algo : public algo_base { + T alg; + explicit algo(T a) : alg(a) {} + void verify(const std::string& data, const std::string& sig, std::error_code& ec) override { + alg.verify(data, sig, ec); + } + }; + /// Required claims + std::unordered_map claims; + /// Leeway time for exp, nbf and iat + size_t default_leeway = 0; + /// Instance of clock type + Clock clock; + /// Supported algorithms + std::unordered_map> algs; + + public: + /** + * Constructor for building a new verifier instance + * \param c Clock instance + */ + explicit verifier(Clock c) : clock(c) { + claims["exp"] = [](const verify_ops::verify_context& ctx, std::error_code& ec) { + if (!ctx.jwt.has_expires_at()) return; + auto exp = ctx.jwt.get_expires_at(); + if (ctx.current_time > exp + std::chrono::seconds(ctx.default_leeway)) { + ec = error::token_verification_error::token_expired; + } + }; + claims["iat"] = [](const verify_ops::verify_context& ctx, std::error_code& ec) { + if (!ctx.jwt.has_issued_at()) return; + auto iat = ctx.jwt.get_issued_at(); + if (ctx.current_time < iat - std::chrono::seconds(ctx.default_leeway)) { + ec = error::token_verification_error::token_expired; + } + }; + claims["nbf"] = [](const verify_ops::verify_context& ctx, std::error_code& ec) { + if (!ctx.jwt.has_not_before()) return; + auto nbf = ctx.jwt.get_not_before(); + if (ctx.current_time < nbf - std::chrono::seconds(ctx.default_leeway)) { + ec = error::token_verification_error::token_expired; + } + }; + } + + /** + * Set default leeway to use. + * \param leeway Default leeway to use if not specified otherwise + * \return *this to allow chaining + */ + verifier& leeway(size_t leeway) { + default_leeway = leeway; + return *this; + } + /** + * Set leeway for expires at. + * If not specified the default leeway will be used. + * \param leeway Set leeway to use for expires at. + * \return *this to allow chaining + */ + verifier& expires_at_leeway(size_t leeway) { + claims["exp"] = verify_ops::date_before_claim{leeway}; + return *this; + } + /** + * Set leeway for not before. + * If not specified the default leeway will be used. + * \param leeway Set leeway to use for not before. + * \return *this to allow chaining + */ + verifier& not_before_leeway(size_t leeway) { + claims["nbf"] = verify_ops::date_after_claim{leeway}; + return *this; + } + /** + * Set leeway for issued at. + * If not specified the default leeway will be used. + * \param leeway Set leeway to use for issued at. + * \return *this to allow chaining + */ + verifier& issued_at_leeway(size_t leeway) { + claims["iat"] = verify_ops::date_after_claim{leeway}; + return *this; + } + + /** + * Set an type to check for. + * + * According to [RFC 7519 Section 5.1](https://datatracker.ietf.org/doc/html/rfc7519#section-5.1), + * This parameter is ignored by JWT implementations; any processing of this parameter is performed by the JWT application. + * Check is casesensitive. + * + * \param type Type Header Parameter to check for. + * \param locale Localization functionality to use when comparing + * \return *this to allow chaining + */ + verifier& with_type(const typename json_traits::string_type& type, std::locale locale = std::locale{}) { + return with_claim("typ", verify_ops::insensitive_string_claim{type, std::move(locale)}); + } + + /** + * Set an issuer to check for. + * Check is casesensitive. + * \param iss Issuer to check for. + * \return *this to allow chaining + */ + verifier& with_issuer(const typename json_traits::string_type& iss) { + return with_claim("iss", basic_claim_t(iss)); + } + + /** + * Set a subject to check for. + * Check is casesensitive. + * \param sub Subject to check for. + * \return *this to allow chaining + */ + verifier& with_subject(const typename json_traits::string_type& sub) { + return with_claim("sub", basic_claim_t(sub)); + } + /** + * Set an audience to check for. + * If any of the specified audiences is not present in the token the check fails. + * \param aud Audience to check for. + * \return *this to allow chaining + */ + verifier& with_audience(const typename basic_claim_t::set_t& aud) { + claims["aud"] = verify_ops::is_subset_claim{aud}; + return *this; + } + /** + * Set an audience to check for. + * If the specified audiences is not present in the token the check fails. + * \param aud Audience to check for. + * \return *this to allow chaining + */ + verifier& with_audience(const typename json_traits::string_type& aud) { + typename basic_claim_t::set_t s; + s.insert(aud); + return with_audience(s); + } + /** + * Set an id to check for. + * Check is casesensitive. + * \param id ID to check for. + * \return *this to allow chaining + */ + verifier& with_id(const typename json_traits::string_type& id) { return with_claim("jti", basic_claim_t(id)); } + + /** + * Specify a claim to check for using the specified operation. + * \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 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. + * \param alg Algorithm to allow + * \return *this to allow chaining + */ + template + verifier& allow_algorithm(Algorithm alg) { + algs[alg.name()] = std::make_shared>(alg); + return *this; + } + + /** + * Verify the given token. + * \param jwt Token to check + * \throw token_verification_exception Verification failed + */ + void verify(const decoded_jwt& jwt) const { + std::error_code ec; + verify(jwt, ec); + error::throw_if_error(ec); + } + /** + * Verify the given token. + * \param jwt Token to check + * \param ec error_code filled with details on error + */ + void verify(const decoded_jwt& jwt, std::error_code& ec) const { + ec.clear(); + const typename json_traits::string_type data = jwt.get_header_base64() + "." + jwt.get_payload_base64(); + const typename json_traits::string_type sig = jwt.get_signature(); + const std::string algo = jwt.get_algorithm(); + if (algs.count(algo) == 0) { + ec = error::token_verification_error::wrong_algorithm; + return; + } + algs.at(algo)->verify(data, sig, ec); + if (ec) return; + + verify_ops::verify_context ctx{clock.now(), jwt, default_leeway}; + for (auto& c : claims) { + ctx.claim_key = c.first; + c.second(ctx, ec); + if (ec) return; + } + } + }; + + /** + * \brief JSON Web Key + * + * https://tools.ietf.org/html/rfc7517 + * + * A JSON object that represents a cryptographic key. The members of + * the object represent properties of the key, including its value. + */ + template + class jwk { + using basic_claim_t = basic_claim; + const details::map_of_claims jwk_claims; + + public: + JWT_CLAIM_EXPLICIT jwk(const typename json_traits::string_type& str) + : jwk_claims(details::map_of_claims::parse_claims(str)) {} + + JWT_CLAIM_EXPLICIT jwk(const typename json_traits::value_type& json) + : jwk_claims(json_traits::as_object(json)) {} + + /** + * Get key type claim + * + * This returns the general type (e.g. RSA or EC), not a specific algorithm value. + * \return key type as string + * \throw std::runtime_error If claim was not present + * \throw std::bad_cast Claim was present but not a string (Should not happen in a valid token) + */ + typename json_traits::string_type get_key_type() const { return get_jwk_claim("kty").as_string(); } + + /** + * Get public key usage claim + * \return usage parameter as string + * \throw std::runtime_error If claim was not present + * \throw std::bad_cast Claim was present but not a string (Should not happen in a valid token) + */ + typename json_traits::string_type get_use() const { return get_jwk_claim("use").as_string(); } + + /** + * Get key operation types claim + * \return key operation types as a set of strings + * \throw std::runtime_error If claim was not present + * \throw std::bad_cast Claim was present but not a string (Should not happen in a valid token) + */ + typename basic_claim_t::set_t get_key_operations() const { return get_jwk_claim("key_ops").as_set(); } + + /** + * Get algorithm claim + * \return algorithm as string + * \throw std::runtime_error If claim was not present + * \throw std::bad_cast Claim was present but not a string (Should not happen in a valid token) + */ + typename json_traits::string_type get_algorithm() const { return get_jwk_claim("alg").as_string(); } + + /** + * Get key id claim + * \return key id as string + * \throw std::runtime_error If claim was not present + * \throw std::bad_cast Claim was present but not a string (Should not happen in a valid token) + */ + typename json_traits::string_type get_key_id() const { return get_jwk_claim("kid").as_string(); } + + /** + * \brief Get curve claim + * + * https://www.rfc-editor.org/rfc/rfc7518.html#section-6.2.1.1 + * https://www.iana.org/assignments/jose/jose.xhtml#table-web-key-elliptic-curve + * + * \return curve as string + * \throw std::runtime_error If claim was not present + * \throw std::bad_cast Claim was present but not a string (Should not happen in a valid token) + */ + typename json_traits::string_type get_curve() const { return get_jwk_claim("crv").as_string(); } + + /** + * Get x5c claim + * \return x5c as an array + * \throw std::runtime_error If claim was not present + * \throw std::bad_cast Claim was present but not a array (Should not happen in a valid token) + */ + typename json_traits::array_type get_x5c() const { return get_jwk_claim("x5c").as_array(); }; + + /** + * Get X509 URL claim + * \return x5u as string + * \throw std::runtime_error If claim was not present + * \throw std::bad_cast Claim was present but not a string (Should not happen in a valid token) + */ + typename json_traits::string_type get_x5u() const { return get_jwk_claim("x5u").as_string(); }; + + /** + * Get X509 thumbprint claim + * \return x5t as string + * \throw std::runtime_error If claim was not present + * \throw std::bad_cast Claim was present but not a string (Should not happen in a valid token) + */ + typename json_traits::string_type get_x5t() const { return get_jwk_claim("x5t").as_string(); }; + + /** + * Get X509 SHA256 thumbprint claim + * \return x5t#S256 as string + * \throw std::runtime_error If claim was not present + * \throw std::bad_cast Claim was present but not a string (Should not happen in a valid token) + */ + typename json_traits::string_type get_x5t_sha256() const { return get_jwk_claim("x5t#S256").as_string(); }; + + /** + * Get x5c claim as a string + * \return x5c as an string + * \throw std::runtime_error If claim was not present + * \throw std::bad_cast Claim was present but not a string (Should not happen in a valid token) + */ + typename json_traits::string_type get_x5c_key_value() const { + auto x5c_array = get_jwk_claim("x5c").as_array(); + if (x5c_array.size() == 0) throw error::claim_not_present_exception(); + + return json_traits::as_string(x5c_array.front()); + }; + + /** + * Check if a key type is present ("kty") + * \return true if present, false otherwise + */ + bool has_key_type() const noexcept { return has_jwk_claim("kty"); } + + /** + * Check if a public key usage indication is present ("use") + * \return true if present, false otherwise + */ + bool has_use() const noexcept { return has_jwk_claim("use"); } + + /** + * Check if a key operations parameter is present ("key_ops") + * \return true if present, false otherwise + */ + bool has_key_operations() const noexcept { return has_jwk_claim("key_ops"); } + + /** + * Check if algorithm is present ("alg") + * \return true if present, false otherwise + */ + bool has_algorithm() const noexcept { return has_jwk_claim("alg"); } + + /** + * Check if curve is present ("crv") + * \return true if present, false otherwise + */ + bool has_curve() const noexcept { return has_jwk_claim("crv"); } + + /** + * Check if key id is present ("kid") + * \return true if present, false otherwise + */ + bool has_key_id() const noexcept { return has_jwk_claim("kid"); } + + /** + * Check if X509 URL is present ("x5u") + * \return true if present, false otherwise + */ + bool has_x5u() const noexcept { return has_jwk_claim("x5u"); } + + /** + * Check if X509 Chain is present ("x5c") + * \return true if present, false otherwise + */ + bool has_x5c() const noexcept { return has_jwk_claim("x5c"); } + + /** + * Check if a X509 thumbprint is present ("x5t") + * \return true if present, false otherwise + */ + bool has_x5t() const noexcept { return has_jwk_claim("x5t"); } + + /** + * Check if a X509 SHA256 thumbprint is present ("x5t#S256") + * \return true if present, false otherwise + */ + bool has_x5t_sha256() const noexcept { return has_jwk_claim("x5t#S256"); } + + /** + * Check if a 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); + } + + /** + * Default clock class using std::chrono::system_clock as a backend. + */ + struct default_clock { + date now() const { return date::clock::now(); } + }; + + /** + * Create a verifier using the given clock + * \param c Clock instance to use + * \return verifier instance + */ + template + verifier verify(default_clock c = {}) { + return verifier(c); + } + + /** + * Return a builder instance to create a new token + */ + 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 + * \throw std::invalid_argument Token is not in correct format + * \throw std::runtime_error Base64 decoding failed or invalid json + */ + template + decoded_jwt decode(const typename json_traits::string_type& token) { + return decoded_jwt(token); + } + + 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); +} + +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/HSDS/Common/jwt-cpp/traits/boost-json/defaults.h b/HSDS/Common/jwt-cpp/traits/boost-json/defaults.h new file mode 100644 index 0000000..affeffe --- /dev/null +++ b/HSDS/Common/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/HSDS/Common/jwt-cpp/traits/boost-json/traits.h b/HSDS/Common/jwt-cpp/traits/boost-json/traits.h new file mode 100644 index 0000000..3b27d5f --- /dev/null +++ b/HSDS/Common/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/HSDS/Common/jwt-cpp/traits/danielaparker-jsoncons/defaults.h b/HSDS/Common/jwt-cpp/traits/danielaparker-jsoncons/defaults.h new file mode 100644 index 0000000..47e12f5 --- /dev/null +++ b/HSDS/Common/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/HSDS/Common/jwt-cpp/traits/danielaparker-jsoncons/traits.h b/HSDS/Common/jwt-cpp/traits/danielaparker-jsoncons/traits.h new file mode 100644 index 0000000..be56740 --- /dev/null +++ b/HSDS/Common/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/HSDS/Common/jwt-cpp/traits/defaults.h.mustache b/HSDS/Common/jwt-cpp/traits/defaults.h.mustache new file mode 100644 index 0000000..ab2a847 --- /dev/null +++ b/HSDS/Common/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/HSDS/Common/jwt-cpp/traits/kazuho-picojson/defaults.h b/HSDS/Common/jwt-cpp/traits/kazuho-picojson/defaults.h new file mode 100644 index 0000000..0c82133 --- /dev/null +++ b/HSDS/Common/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/HSDS/Common/jwt-cpp/traits/kazuho-picojson/traits.h b/HSDS/Common/jwt-cpp/traits/kazuho-picojson/traits.h new file mode 100644 index 0000000..2f96bfc --- /dev/null +++ b/HSDS/Common/jwt-cpp/traits/kazuho-picojson/traits.h @@ -0,0 +1,76 @@ +#ifndef JWT_CPP_PICOJSON_TRAITS_H +#define JWT_CPP_PICOJSON_TRAITS_H + +#ifndef PICOJSON_USE_INT64 +#define PICOJSON_USE_INT64 +#endif +#include "picojson/picojson.h" + +#ifndef JWT_DISABLE_PICOJSON +#define JWT_DISABLE_PICOJSON +#endif +#include "jwt-cpp/jwt.h" + +namespace jwt { + namespace traits { + struct kazuho_picojson { + using value_type = picojson::value; + using object_type = picojson::object; + using array_type = picojson::array; + using string_type = std::string; + using number_type = double; + using integer_type = int64_t; + using boolean_type = bool; + + static json::type get_type(const picojson::value& val) { + using json::type; + if (val.is()) return type::boolean; + if (val.is()) return type::integer; + if (val.is()) return type::number; + if (val.is()) return type::string; + if (val.is()) return type::array; + if (val.is()) return type::object; + + throw std::logic_error("invalid type"); + } + + static picojson::object as_object(const picojson::value& val) { + if (!val.is()) throw std::bad_cast(); + return val.get(); + } + + static std::string as_string(const picojson::value& val) { + if (!val.is()) throw std::bad_cast(); + return val.get(); + } + + static picojson::array as_array(const picojson::value& val) { + if (!val.is()) throw std::bad_cast(); + return val.get(); + } + + static int64_t as_integer(const picojson::value& val) { + if (!val.is()) throw std::bad_cast(); + return val.get(); + } + + static bool as_boolean(const picojson::value& val) { + if (!val.is()) throw std::bad_cast(); + return val.get(); + } + + static double as_number(const picojson::value& val) { + if (!val.is()) throw std::bad_cast(); + return val.get(); + } + + static bool parse(picojson::value& val, const std::string& str) { + return picojson::parse(val, str).empty(); + } + + static std::string serialize(const picojson::value& val) { return val.serialize(); } + }; + } // namespace traits +} // namespace jwt + +#endif diff --git a/HSDS/Common/jwt-cpp/traits/nlohmann-json/defaults.h b/HSDS/Common/jwt-cpp/traits/nlohmann-json/defaults.h new file mode 100644 index 0000000..c324075 --- /dev/null +++ b/HSDS/Common/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/HSDS/Common/jwt-cpp/traits/nlohmann-json/traits.h b/HSDS/Common/jwt-cpp/traits/nlohmann-json/traits.h new file mode 100644 index 0000000..7cf4869 --- /dev/null +++ b/HSDS/Common/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/HSDS/Common/picojson/picojson.h b/HSDS/Common/picojson/picojson.h new file mode 100644 index 0000000..76742fe --- /dev/null +++ b/HSDS/Common/picojson/picojson.h @@ -0,0 +1,1200 @@ +/* + * Copyright 2009-2010 Cybozu Labs, Inc. + * Copyright 2011-2014 Kazuho Oku + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +#ifndef picojson_h +#define picojson_h + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// for isnan/isinf +#if __cplusplus >= 201103L +#include +#else +extern "C" { +#ifdef _MSC_VER +#include +#elif defined(__INTEL_COMPILER) +#include +#else +#include +#endif +} +#endif + +#ifndef PICOJSON_USE_RVALUE_REFERENCE +#if (defined(__cpp_rvalue_references) && __cpp_rvalue_references >= 200610) || (defined(_MSC_VER) && _MSC_VER >= 1600) +#define PICOJSON_USE_RVALUE_REFERENCE 1 +#else +#define PICOJSON_USE_RVALUE_REFERENCE 0 +#endif +#endif // PICOJSON_USE_RVALUE_REFERENCE + +#ifndef PICOJSON_NOEXCEPT +#if PICOJSON_USE_RVALUE_REFERENCE +#define PICOJSON_NOEXCEPT noexcept +#else +#define PICOJSON_NOEXCEPT throw() +#endif +#endif + +// experimental support for int64_t (see README.mkdn for detail) +#ifdef PICOJSON_USE_INT64 +#define __STDC_FORMAT_MACROS +#include +#if __cplusplus >= 201103L +#include +#else +extern "C" { +#include +} +#endif +#endif + +// to disable the use of localeconv(3), set PICOJSON_USE_LOCALE to 0 +#ifndef PICOJSON_USE_LOCALE +#define PICOJSON_USE_LOCALE 1 +#endif +#if PICOJSON_USE_LOCALE +extern "C" { +#include +} +#endif + +#ifndef PICOJSON_ASSERT +#define PICOJSON_ASSERT(e) \ + do { \ + if (!(e)) \ + throw std::runtime_error(#e); \ + } while (0) +#endif + +#ifdef _MSC_VER +#define SNPRINTF _snprintf_s +#pragma warning(push) +#pragma warning(disable : 4244) // conversion from int to char +#pragma warning(disable : 4127) // conditional expression is constant +#pragma warning(disable : 4702) // unreachable code +#pragma warning(disable : 4706) // assignment within conditional expression +#else +#define SNPRINTF snprintf +#endif + +namespace picojson { + +enum { + null_type, + boolean_type, + number_type, + string_type, + array_type, + object_type +#ifdef PICOJSON_USE_INT64 + , + int64_type +#endif +}; + +enum { INDENT_WIDTH = 2, DEFAULT_MAX_DEPTHS = 100 }; + +struct null {}; + +class value { +public: + typedef std::vector array; + typedef std::map object; + union _storage { + bool boolean_; + double number_; +#ifdef PICOJSON_USE_INT64 + int64_t int64_; +#endif + std::string *string_; + array *array_; + object *object_; + }; + +protected: + int type_; + _storage u_; + +public: + value(); + value(int type, bool); + explicit value(bool b); +#ifdef PICOJSON_USE_INT64 + explicit value(int64_t i); +#endif + explicit value(double n); + explicit value(const std::string &s); + explicit value(const array &a); + explicit value(const object &o); +#if PICOJSON_USE_RVALUE_REFERENCE + explicit value(std::string &&s); + explicit value(array &&a); + explicit value(object &&o); +#endif + explicit value(const char *s); + value(const char *s, size_t len); + ~value(); + value(const value &x); + value &operator=(const value &x); +#if PICOJSON_USE_RVALUE_REFERENCE + value(value &&x) PICOJSON_NOEXCEPT; + value &operator=(value &&x) PICOJSON_NOEXCEPT; +#endif + void swap(value &x) PICOJSON_NOEXCEPT; + template bool is() const; + template const T &get() const; + template T &get(); + template void set(const T &); +#if PICOJSON_USE_RVALUE_REFERENCE + template void set(T &&); +#endif + bool evaluate_as_boolean() const; + const value &get(const size_t idx) const; + const value &get(const std::string &key) const; + value &get(const size_t idx); + value &get(const std::string &key); + + bool contains(const size_t idx) const; + bool contains(const std::string &key) const; + std::string to_str() const; + template void serialize(Iter os, bool prettify = false) const; + std::string serialize(bool prettify = false) const; + +private: + template value(const T *); // intentionally defined to block implicit conversion of pointer to bool + template static void _indent(Iter os, int indent); + template void _serialize(Iter os, int indent) const; + std::string _serialize(int indent) const; + void clear(); +}; + +typedef value::array array; +typedef value::object object; + +inline value::value() : type_(null_type), u_() { +} + +inline value::value(int type, bool) : type_(type), u_() { + switch (type) { +#define INIT(p, v) \ + case p##type: \ + u_.p = v; \ + break + INIT(boolean_, false); + INIT(number_, 0.0); +#ifdef PICOJSON_USE_INT64 + INIT(int64_, 0); +#endif + INIT(string_, new std::string()); + INIT(array_, new array()); + INIT(object_, new object()); +#undef INIT + default: + break; + } +} + +inline value::value(bool b) : type_(boolean_type), u_() { + u_.boolean_ = b; +} + +#ifdef PICOJSON_USE_INT64 +inline value::value(int64_t i) : type_(int64_type), u_() { + u_.int64_ = i; +} +#endif + +inline value::value(double n) : type_(number_type), u_() { + if ( +#ifdef _MSC_VER + !_finite(n) +#elif __cplusplus >= 201103L + std::isnan(n) || std::isinf(n) +#else + isnan(n) || isinf(n) +#endif + ) { + throw std::overflow_error(""); + } + u_.number_ = n; +} + +inline value::value(const std::string &s) : type_(string_type), u_() { + u_.string_ = new std::string(s); +} + +inline value::value(const array &a) : type_(array_type), u_() { + u_.array_ = new array(a); +} + +inline value::value(const object &o) : type_(object_type), u_() { + u_.object_ = new object(o); +} + +#if PICOJSON_USE_RVALUE_REFERENCE +inline value::value(std::string &&s) : type_(string_type), u_() { + u_.string_ = new std::string(std::move(s)); +} + +inline value::value(array &&a) : type_(array_type), u_() { + u_.array_ = new array(std::move(a)); +} + +inline value::value(object &&o) : type_(object_type), u_() { + u_.object_ = new object(std::move(o)); +} +#endif + +inline value::value(const char *s) : type_(string_type), u_() { + u_.string_ = new std::string(s); +} + +inline value::value(const char *s, size_t len) : type_(string_type), u_() { + u_.string_ = new std::string(s, len); +} + +inline void value::clear() { + switch (type_) { +#define DEINIT(p) \ + case p##type: \ + delete u_.p; \ + break + DEINIT(string_); + DEINIT(array_); + DEINIT(object_); +#undef DEINIT + default: + break; + } +} + +inline value::~value() { + clear(); +} + +inline value::value(const value &x) : type_(x.type_), u_() { + switch (type_) { +#define INIT(p, v) \ + case p##type: \ + u_.p = v; \ + break + INIT(string_, new std::string(*x.u_.string_)); + INIT(array_, new array(*x.u_.array_)); + INIT(object_, new object(*x.u_.object_)); +#undef INIT + default: + u_ = x.u_; + break; + } +} + +inline value &value::operator=(const value &x) { + if (this != &x) { + value t(x); + swap(t); + } + return *this; +} + +#if PICOJSON_USE_RVALUE_REFERENCE +inline value::value(value &&x) PICOJSON_NOEXCEPT : type_(null_type), u_() { + swap(x); +} +inline value &value::operator=(value &&x) PICOJSON_NOEXCEPT { + swap(x); + return *this; +} +#endif +inline void value::swap(value &x) PICOJSON_NOEXCEPT { + std::swap(type_, x.type_); + std::swap(u_, x.u_); +} + +#define IS(ctype, jtype) \ + template <> inline bool value::is() const { \ + return type_ == jtype##_type; \ + } +IS(null, null) +IS(bool, boolean) +#ifdef PICOJSON_USE_INT64 +IS(int64_t, int64) +#endif +IS(std::string, string) +IS(array, array) +IS(object, object) +#undef IS +template <> inline bool value::is() const { + return type_ == number_type +#ifdef PICOJSON_USE_INT64 + || type_ == int64_type +#endif + ; +} + +#define GET(ctype, var) \ + template <> inline const ctype &value::get() const { \ + PICOJSON_ASSERT("type mismatch! call is() before get()" && is()); \ + return var; \ + } \ + template <> inline ctype &value::get() { \ + PICOJSON_ASSERT("type mismatch! call is() before get()" && is()); \ + return var; \ + } +GET(bool, u_.boolean_) +GET(std::string, *u_.string_) +GET(array, *u_.array_) +GET(object, *u_.object_) +#ifdef PICOJSON_USE_INT64 +GET(double, + (type_ == int64_type && (const_cast(this)->type_ = number_type, (const_cast(this)->u_.number_ = u_.int64_)), + u_.number_)) +GET(int64_t, u_.int64_) +#else +GET(double, u_.number_) +#endif +#undef GET + +#define SET(ctype, jtype, setter) \ + template <> inline void value::set(const ctype &_val) { \ + clear(); \ + type_ = jtype##_type; \ + setter \ + } +SET(bool, boolean, u_.boolean_ = _val;) +SET(std::string, string, u_.string_ = new std::string(_val);) +SET(array, array, u_.array_ = new array(_val);) +SET(object, object, u_.object_ = new object(_val);) +SET(double, number, u_.number_ = _val;) +#ifdef PICOJSON_USE_INT64 +SET(int64_t, int64, u_.int64_ = _val;) +#endif +#undef SET + +#if PICOJSON_USE_RVALUE_REFERENCE +#define MOVESET(ctype, jtype, setter) \ + template <> inline void value::set(ctype && _val) { \ + clear(); \ + type_ = jtype##_type; \ + setter \ + } +MOVESET(std::string, string, u_.string_ = new std::string(std::move(_val));) +MOVESET(array, array, u_.array_ = new array(std::move(_val));) +MOVESET(object, object, u_.object_ = new object(std::move(_val));) +#undef MOVESET +#endif + +inline bool value::evaluate_as_boolean() const { + switch (type_) { + case null_type: + return false; + case boolean_type: + return u_.boolean_; + case number_type: + return u_.number_ != 0; +#ifdef PICOJSON_USE_INT64 + case int64_type: + return u_.int64_ != 0; +#endif + case string_type: + return !u_.string_->empty(); + default: + return true; + } +} + +inline const value &value::get(const size_t idx) const { + static value s_null; + PICOJSON_ASSERT(is()); + return idx < u_.array_->size() ? (*u_.array_)[idx] : s_null; +} + +inline value &value::get(const size_t idx) { + static value s_null; + PICOJSON_ASSERT(is()); + return idx < u_.array_->size() ? (*u_.array_)[idx] : s_null; +} + +inline const value &value::get(const std::string &key) const { + static value s_null; + PICOJSON_ASSERT(is()); + object::const_iterator i = u_.object_->find(key); + return i != u_.object_->end() ? i->second : s_null; +} + +inline value &value::get(const std::string &key) { + static value s_null; + PICOJSON_ASSERT(is()); + object::iterator i = u_.object_->find(key); + return i != u_.object_->end() ? i->second : s_null; +} + +inline bool value::contains(const size_t idx) const { + PICOJSON_ASSERT(is()); + return idx < u_.array_->size(); +} + +inline bool value::contains(const std::string &key) const { + PICOJSON_ASSERT(is()); + object::const_iterator i = u_.object_->find(key); + return i != u_.object_->end(); +} + +inline std::string value::to_str() const { + switch (type_) { + case null_type: + return "null"; + case boolean_type: + return u_.boolean_ ? "true" : "false"; +#ifdef PICOJSON_USE_INT64 + case int64_type: { + char buf[sizeof("-9223372036854775808")]; + SNPRINTF(buf, sizeof(buf), "%" PRId64, u_.int64_); + return buf; + } +#endif + case number_type: { + char buf[256]; + double tmp; + SNPRINTF(buf, sizeof(buf), fabs(u_.number_) < (1ULL << 53) && modf(u_.number_, &tmp) == 0 ? "%.f" : "%.17g", u_.number_); +#if PICOJSON_USE_LOCALE + char *decimal_point = localeconv()->decimal_point; + if (strcmp(decimal_point, ".") != 0) { + size_t decimal_point_len = strlen(decimal_point); + for (char *p = buf; *p != '\0'; ++p) { + if (strncmp(p, decimal_point, decimal_point_len) == 0) { + return std::string(buf, p) + "." + (p + decimal_point_len); + } + } + } +#endif + return buf; + } + case string_type: + return *u_.string_; + case array_type: + return "array"; + case object_type: + return "object"; + default: + PICOJSON_ASSERT(0); +#ifdef _MSC_VER + __assume(0); +#endif + } + return std::string(); +} + +template void copy(const std::string &s, Iter oi) { + std::copy(s.begin(), s.end(), oi); +} + +template struct serialize_str_char { + Iter oi; + void operator()(char c) { + switch (c) { +#define MAP(val, sym) \ + case val: \ + copy(sym, oi); \ + break + MAP('"', "\\\""); + MAP('\\', "\\\\"); + MAP('/', "\\/"); + MAP('\b', "\\b"); + MAP('\f', "\\f"); + MAP('\n', "\\n"); + MAP('\r', "\\r"); + MAP('\t', "\\t"); +#undef MAP + default: + if (static_cast(c) < 0x20 || c == 0x7f) { + char buf[7]; + SNPRINTF(buf, sizeof(buf), "\\u%04x", c & 0xff); + copy(buf, buf + 6, oi); + } else { + *oi++ = c; + } + break; + } + } +}; + +template void serialize_str(const std::string &s, Iter oi) { + *oi++ = '"'; + serialize_str_char process_char = {oi}; + std::for_each(s.begin(), s.end(), process_char); + *oi++ = '"'; +} + +template void value::serialize(Iter oi, bool prettify) const { + return _serialize(oi, prettify ? 0 : -1); +} + +inline std::string value::serialize(bool prettify) const { + return _serialize(prettify ? 0 : -1); +} + +template void value::_indent(Iter oi, int indent) { + *oi++ = '\n'; + for (int i = 0; i < indent * INDENT_WIDTH; ++i) { + *oi++ = ' '; + } +} + +template void value::_serialize(Iter oi, int indent) const { + switch (type_) { + case string_type: + serialize_str(*u_.string_, oi); + break; + case array_type: { + *oi++ = '['; + if (indent != -1) { + ++indent; + } + for (array::const_iterator i = u_.array_->begin(); i != u_.array_->end(); ++i) { + if (i != u_.array_->begin()) { + *oi++ = ','; + } + if (indent != -1) { + _indent(oi, indent); + } + i->_serialize(oi, indent); + } + if (indent != -1) { + --indent; + if (!u_.array_->empty()) { + _indent(oi, indent); + } + } + *oi++ = ']'; + break; + } + case object_type: { + *oi++ = '{'; + if (indent != -1) { + ++indent; + } + for (object::const_iterator i = u_.object_->begin(); i != u_.object_->end(); ++i) { + if (i != u_.object_->begin()) { + *oi++ = ','; + } + if (indent != -1) { + _indent(oi, indent); + } + serialize_str(i->first, oi); + *oi++ = ':'; + if (indent != -1) { + *oi++ = ' '; + } + i->second._serialize(oi, indent); + } + if (indent != -1) { + --indent; + if (!u_.object_->empty()) { + _indent(oi, indent); + } + } + *oi++ = '}'; + break; + } + default: + copy(to_str(), oi); + break; + } + if (indent == 0) { + *oi++ = '\n'; + } +} + +inline std::string value::_serialize(int indent) const { + std::string s; + _serialize(std::back_inserter(s), indent); + return s; +} + +template class input { +protected: + Iter cur_, end_; + bool consumed_; + int line_; + +public: + input(const Iter &first, const Iter &last) : cur_(first), end_(last), consumed_(false), line_(1) { + } + int getc() { + if (consumed_) { + if (*cur_ == '\n') { + ++line_; + } + ++cur_; + } + if (cur_ == end_) { + consumed_ = false; + return -1; + } + consumed_ = true; + return *cur_ & 0xff; + } + void ungetc() { + consumed_ = false; + } + Iter cur() const { + if (consumed_) { + input *self = const_cast *>(this); + self->consumed_ = false; + ++self->cur_; + } + return cur_; + } + int line() const { + return line_; + } + void skip_ws() { + while (1) { + int ch = getc(); + if (!(ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r')) { + ungetc(); + break; + } + } + } + bool expect(const int expected) { + skip_ws(); + if (getc() != expected) { + ungetc(); + return false; + } + return true; + } + bool match(const std::string &pattern) { + for (std::string::const_iterator pi(pattern.begin()); pi != pattern.end(); ++pi) { + if (getc() != *pi) { + ungetc(); + return false; + } + } + return true; + } +}; + +template inline int _parse_quadhex(input &in) { + int uni_ch = 0, hex; + for (int i = 0; i < 4; i++) { + if ((hex = in.getc()) == -1) { + return -1; + } + if ('0' <= hex && hex <= '9') { + hex -= '0'; + } else if ('A' <= hex && hex <= 'F') { + hex -= 'A' - 0xa; + } else if ('a' <= hex && hex <= 'f') { + hex -= 'a' - 0xa; + } else { + in.ungetc(); + return -1; + } + uni_ch = uni_ch * 16 + hex; + } + return uni_ch; +} + +template inline bool _parse_codepoint(String &out, input &in) { + int uni_ch; + if ((uni_ch = _parse_quadhex(in)) == -1) { + return false; + } + if (0xd800 <= uni_ch && uni_ch <= 0xdfff) { + if (0xdc00 <= uni_ch) { + // a second 16-bit of a surrogate pair appeared + return false; + } + // first 16-bit of surrogate pair, get the next one + if (in.getc() != '\\' || in.getc() != 'u') { + in.ungetc(); + return false; + } + int second = _parse_quadhex(in); + if (!(0xdc00 <= second && second <= 0xdfff)) { + return false; + } + uni_ch = ((uni_ch - 0xd800) << 10) | ((second - 0xdc00) & 0x3ff); + uni_ch += 0x10000; + } + if (uni_ch < 0x80) { + out.push_back(static_cast(uni_ch)); + } else { + if (uni_ch < 0x800) { + out.push_back(static_cast(0xc0 | (uni_ch >> 6))); + } else { + if (uni_ch < 0x10000) { + out.push_back(static_cast(0xe0 | (uni_ch >> 12))); + } else { + out.push_back(static_cast(0xf0 | (uni_ch >> 18))); + out.push_back(static_cast(0x80 | ((uni_ch >> 12) & 0x3f))); + } + out.push_back(static_cast(0x80 | ((uni_ch >> 6) & 0x3f))); + } + out.push_back(static_cast(0x80 | (uni_ch & 0x3f))); + } + return true; +} + +template inline bool _parse_string(String &out, input &in) { + while (1) { + int ch = in.getc(); + if (ch < ' ') { + in.ungetc(); + return false; + } else if (ch == '"') { + return true; + } else if (ch == '\\') { + if ((ch = in.getc()) == -1) { + return false; + } + switch (ch) { +#define MAP(sym, val) \ + case sym: \ + out.push_back(val); \ + break + MAP('"', '\"'); + MAP('\\', '\\'); + MAP('/', '/'); + MAP('b', '\b'); + MAP('f', '\f'); + MAP('n', '\n'); + MAP('r', '\r'); + MAP('t', '\t'); +#undef MAP + case 'u': + if (!_parse_codepoint(out, in)) { + return false; + } + break; + default: + return false; + } + } else { + out.push_back(static_cast(ch)); + } + } + return false; +} + +template inline bool _parse_array(Context &ctx, input &in) { + if (!ctx.parse_array_start()) { + return false; + } + size_t idx = 0; + if (in.expect(']')) { + return ctx.parse_array_stop(idx); + } + do { + if (!ctx.parse_array_item(in, idx)) { + return false; + } + idx++; + } while (in.expect(',')); + return in.expect(']') && ctx.parse_array_stop(idx); +} + +template inline bool _parse_object(Context &ctx, input &in) { + if (!ctx.parse_object_start()) { + return false; + } + if (in.expect('}')) { + return ctx.parse_object_stop(); + } + do { + std::string key; + if (!in.expect('"') || !_parse_string(key, in) || !in.expect(':')) { + return false; + } + if (!ctx.parse_object_item(in, key)) { + return false; + } + } while (in.expect(',')); + return in.expect('}') && ctx.parse_object_stop(); +} + +template inline std::string _parse_number(input &in) { + std::string num_str; + while (1) { + int ch = in.getc(); + if (('0' <= ch && ch <= '9') || ch == '+' || ch == '-' || ch == 'e' || ch == 'E') { + num_str.push_back(static_cast(ch)); + } else if (ch == '.') { +#if PICOJSON_USE_LOCALE + num_str += localeconv()->decimal_point; +#else + num_str.push_back('.'); +#endif + } else { + in.ungetc(); + break; + } + } + return num_str; +} + +template inline bool _parse(Context &ctx, input &in) { + in.skip_ws(); + int ch = in.getc(); + switch (ch) { +#define IS(ch, text, op) \ + case ch: \ + if (in.match(text) && op) { \ + return true; \ + } else { \ + return false; \ + } + IS('n', "ull", ctx.set_null()); + IS('f', "alse", ctx.set_bool(false)); + IS('t', "rue", ctx.set_bool(true)); +#undef IS + case '"': + return ctx.parse_string(in); + case '[': + return _parse_array(ctx, in); + case '{': + return _parse_object(ctx, in); + default: + if (('0' <= ch && ch <= '9') || ch == '-') { + double f; + char *endp; + in.ungetc(); + std::string num_str(_parse_number(in)); + if (num_str.empty()) { + return false; + } +#ifdef PICOJSON_USE_INT64 + { + errno = 0; + intmax_t ival = strtoimax(num_str.c_str(), &endp, 10); + if (errno == 0 && std::numeric_limits::min() <= ival && ival <= std::numeric_limits::max() && + endp == num_str.c_str() + num_str.size()) { + ctx.set_int64(ival); + return true; + } + } +#endif + f = strtod(num_str.c_str(), &endp); + if (endp == num_str.c_str() + num_str.size()) { + ctx.set_number(f); + return true; + } + return false; + } + break; + } + in.ungetc(); + return false; +} + +class deny_parse_context { +public: + bool set_null() { + return false; + } + bool set_bool(bool) { + return false; + } +#ifdef PICOJSON_USE_INT64 + bool set_int64(int64_t) { + return false; + } +#endif + bool set_number(double) { + return false; + } + template bool parse_string(input &) { + return false; + } + bool parse_array_start() { + return false; + } + template bool parse_array_item(input &, size_t) { + return false; + } + bool parse_array_stop(size_t) { + return false; + } + bool parse_object_start() { + return false; + } + template bool parse_object_item(input &, const std::string &) { + return false; + } +}; + +class default_parse_context { +protected: + value *out_; + size_t depths_; + +public: + default_parse_context(value *out, size_t depths = DEFAULT_MAX_DEPTHS) : out_(out), depths_(depths) { + } + bool set_null() { + *out_ = value(); + return true; + } + bool set_bool(bool b) { + *out_ = value(b); + return true; + } +#ifdef PICOJSON_USE_INT64 + bool set_int64(int64_t i) { + *out_ = value(i); + return true; + } +#endif + bool set_number(double f) { + *out_ = value(f); + return true; + } + template bool parse_string(input &in) { + *out_ = value(string_type, false); + return _parse_string(out_->get(), in); + } + bool parse_array_start() { + if (depths_ == 0) + return false; + --depths_; + *out_ = value(array_type, false); + return true; + } + template bool parse_array_item(input &in, size_t) { + array &a = out_->get(); + a.push_back(value()); + default_parse_context ctx(&a.back(), depths_); + return _parse(ctx, in); + } + bool parse_array_stop(size_t) { + ++depths_; + return true; + } + bool parse_object_start() { + if (depths_ == 0) + return false; + *out_ = value(object_type, false); + return true; + } + template bool parse_object_item(input &in, const std::string &key) { + object &o = out_->get(); + default_parse_context ctx(&o[key], depths_); + return _parse(ctx, in); + } + bool parse_object_stop() { + ++depths_; + return true; + } + +private: + default_parse_context(const default_parse_context &); + default_parse_context &operator=(const default_parse_context &); +}; + +class null_parse_context { +protected: + size_t depths_; + +public: + struct dummy_str { + void push_back(int) { + } + }; + +public: + null_parse_context(size_t depths = DEFAULT_MAX_DEPTHS) : depths_(depths) { + } + bool set_null() { + return true; + } + bool set_bool(bool) { + return true; + } +#ifdef PICOJSON_USE_INT64 + bool set_int64(int64_t) { + return true; + } +#endif + bool set_number(double) { + return true; + } + template bool parse_string(input &in) { + dummy_str s; + return _parse_string(s, in); + } + bool parse_array_start() { + if (depths_ == 0) + return false; + --depths_; + return true; + } + template bool parse_array_item(input &in, size_t) { + return _parse(*this, in); + } + bool parse_array_stop(size_t) { + ++depths_; + return true; + } + bool parse_object_start() { + if (depths_ == 0) + return false; + --depths_; + return true; + } + template bool parse_object_item(input &in, const std::string &) { + ++depths_; + return _parse(*this, in); + } + bool parse_object_stop() { + return true; + } + +private: + null_parse_context(const null_parse_context &); + null_parse_context &operator=(const null_parse_context &); +}; + +// obsolete, use the version below +template inline std::string parse(value &out, Iter &pos, const Iter &last) { + std::string err; + pos = parse(out, pos, last, &err); + return err; +} + +template inline Iter _parse(Context &ctx, const Iter &first, const Iter &last, std::string *err) { + input in(first, last); + if (!_parse(ctx, in) && err != NULL) { + char buf[64]; + SNPRINTF(buf, sizeof(buf), "syntax error at line %d near: ", in.line()); + *err = buf; + while (1) { + int ch = in.getc(); + if (ch == -1 || ch == '\n') { + break; + } else if (ch >= ' ') { + err->push_back(static_cast(ch)); + } + } + } + return in.cur(); +} + +template inline Iter parse(value &out, const Iter &first, const Iter &last, std::string *err) { + default_parse_context ctx(&out); + return _parse(ctx, first, last, err); +} + +inline std::string parse(value &out, const std::string &s) { + std::string err; + parse(out, s.begin(), s.end(), &err); + return err; +} + +inline std::string parse(value &out, std::istream &is) { + std::string err; + parse(out, std::istreambuf_iterator(is.rdbuf()), std::istreambuf_iterator(), &err); + return err; +} + +template struct last_error_t { static std::string s; }; +template std::string last_error_t::s; + +inline void set_last_error(const std::string &s) { + last_error_t::s = s; +} + +inline const std::string &get_last_error() { + return last_error_t::s; +} + +inline bool operator==(const value &x, const value &y) { + if (x.is()) + return y.is(); +#define PICOJSON_CMP(type) \ + if (x.is()) \ + return y.is() && x.get() == y.get() + PICOJSON_CMP(bool); + PICOJSON_CMP(double); + PICOJSON_CMP(std::string); + PICOJSON_CMP(array); + PICOJSON_CMP(object); +#undef PICOJSON_CMP + PICOJSON_ASSERT(0); +#ifdef _MSC_VER + __assume(0); +#endif + return false; +} + +inline bool operator!=(const value &x, const value &y) { + return !(x == y); +} +} + +#if !PICOJSON_USE_RVALUE_REFERENCE +namespace std { +template <> inline void swap(picojson::value &x, picojson::value &y) { + x.swap(y); +} +} +#endif + +inline std::istream &operator>>(std::istream &is, picojson::value &x) { + picojson::set_last_error(std::string()); + const std::string err(picojson::parse(x, is)); + if (!err.empty()) { + picojson::set_last_error(err); + is.setstate(std::ios::failbit); + } + return is; +} + +inline std::ostream &operator<<(std::ostream &os, const picojson::value &x) { + x.serialize(std::ostream_iterator(os)); + return os; +} +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +#endif