From cd66010dea459630da56e7b3f39d2c038099162d Mon Sep 17 00:00:00 2001 From: Stephane Janel Date: Sat, 14 Jan 2023 18:28:37 +0100 Subject: [PATCH] Addition of string_view support --- docs/traits.md | 3 +- include/jwt-cpp/base.h | 99 ++-- include/jwt-cpp/jwt.h | 448 +++++++++++------- include/jwt-cpp/string_types.h | 24 + include/jwt-cpp/traits/boost-json/traits.h | 3 +- .../traits/danielaparker-jsoncons/traits.h | 3 +- .../jwt-cpp/traits/kazuho-picojson/traits.h | 3 +- include/jwt-cpp/traits/nlohmann-json/traits.h | 3 +- tests/BaseTest.cpp | 223 ++++----- tests/ClaimTest.cpp | 34 ++ tests/TokenTest.cpp | 1 + 11 files changed, 498 insertions(+), 346 deletions(-) create mode 100644 include/jwt-cpp/string_types.h diff --git a/docs/traits.md b/docs/traits.md index 71eddb6af..a5681fba7 100644 --- a/docs/traits.md +++ b/docs/traits.md @@ -54,7 +54,8 @@ struct my_favorite_json_library_traits { static boolean_type as_boolean(const value_type &val); // serialization and parsing - static bool parse(value_type &val, string_type str); + template // could be the json string_type, or std::string_view for instance + static bool parse(value_type &val, const string_t& str); static string_type serialize(const value_type &val); // with no extra whitespace, padding or indentation }; ``` diff --git a/include/jwt-cpp/base.h b/include/jwt-cpp/base.h index cef493d19..de29329e7 100644 --- a/include/jwt-cpp/base.h +++ b/include/jwt-cpp/base.h @@ -5,7 +5,8 @@ #include #include #include -#include + +#include "string_types.h" #ifdef __has_cpp_attribute #if __has_cpp_attribute(fallthrough) @@ -37,10 +38,7 @@ namespace jwt { 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'}}; return data; } - static const std::string& fill() { - static std::string fill{"="}; - return fill; - } + static std::array fill() { return std::array{"="}; } }; /** * \brief valid list of character when working with [Base64URL](https://tools.ietf.org/html/rfc4648#section-5) @@ -60,10 +58,7 @@ namespace jwt { 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-', '_'}}; return data; } - static const std::string& fill() { - static std::string fill{"%3d"}; - return fill; - } + static std::array fill() { return std::array{"%3d"}; } }; namespace helper { /** @@ -81,15 +76,15 @@ namespace jwt { 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-', '_'}}; return data; } - static const std::initializer_list& fill() { - static std::initializer_list fill{"%3D", "%3d"}; - return fill; - } + static std::array fill() { return std::array{"%3D", "%3d"}; } }; } // 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 (symbol >= 'A' && symbol <= 'Z') { return static_cast(symbol - 'A'); } + if (symbol >= 'a' && symbol <= 'z') { return static_cast(26 + symbol - 'a'); } + if (symbol >= '0' && symbol <= '9') { return static_cast(52 + symbol - '0'); } + auto itr = std::find(std::next(alphabet.cbegin(), 62U), alphabet.cend(), symbol); if (itr == alphabet.cend()) { throw std::runtime_error("Invalid input: not within alphabet"); } return std::distance(alphabet.cbegin(), itr); @@ -116,30 +111,31 @@ namespace jwt { } }; - inline padding count_padding(const std::string& base, const std::vector& fills) { - for (const auto& fill : fills) { - if (base.size() < fill.size()) continue; + template + padding count_padding(string_view base, StrInputIt fillStart, StrInputIt fillEnd) { + for (StrInputIt fillIt = fillStart; fillIt != fillEnd; ++fillIt) { + int fillLen = static_cast(fillIt->size()); + int deltaLen = static_cast(base.size()) - fillLen; // Does the end of the input exactly match the fill pattern? - if (base.substr(base.size() - fill.size()) == fill) { - return padding{1, fill.length()} + - count_padding(base.substr(0, base.size() - fill.size()), fills); + if (deltaLen >= 0 && base.substr(deltaLen) == *fillIt) { + return padding{1, static_cast(fillLen)} + + count_padding(base.substr(0, deltaLen), fillStart, fillEnd); } } return {}; } - inline std::string encode(const std::string& bin, const std::array& alphabet, - const std::string& fill) { + inline std::string encode(string_view bin, const std::array& alphabet, string_view 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++]); + for (size_t i = 0; i < fast_size; i += 3) { + uint32_t octet_a = static_cast(bin[i]); + uint32_t octet_b = static_cast(bin[i + 1]); + uint32_t octet_c = static_cast(bin[i + 2]); uint32_t triple = (octet_a << 0x10) + (octet_b << 0x08) + octet_c; @@ -178,9 +174,10 @@ namespace jwt { 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); + template + inline std::string decode(string_view base, const std::array& alphabet, StrInputIt fillStart, + StrInputIt fillEnd) { + const auto pad = count_padding(base, fillStart, fillEnd); if (pad.count > 2) throw std::runtime_error("Invalid input: too much fill"); const size_t size = base.size() - pad.length; @@ -224,44 +221,42 @@ namespace jwt { 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; + inline std::string pad(string_view base, string_view fill) { + std::string res(base); + switch (res.size() % 4) { + case 1: res += fill; JWT_FALLTHROUGH; + case 2: res += fill; JWT_FALLTHROUGH; + case 3: res += fill; JWT_FALLTHROUGH; default: break; } - - return base + padding; + return res; } - inline std::string trim(const std::string& base, const std::string& fill) { + inline std::string trim(string_view base, string_view fill) { auto pos = base.find(fill); - return base.substr(0, pos); + return static_cast(base.substr(0, pos)); } } // namespace details template - std::string encode(const std::string& bin) { - return details::encode(bin, T::data(), T::fill()); + std::string encode(string_view bin) { + static const auto fills = T::fill(); + return details::encode(bin, T::data(), fills.front()); } template - std::string decode(const std::string& base) { - return details::decode(base, T::data(), T::fill()); + std::string decode(string_view base) { + static const auto fills = T::fill(); + return details::decode(base, T::data(), fills.begin(), fills.end()); } template - std::string pad(const std::string& base) { - return details::pad(base, T::fill()); + std::string pad(string_view base) { + static const auto fills = T::fill(); + return details::pad(base, fills.front()); } template - std::string trim(const std::string& base) { - return details::trim(base, T::fill()); + std::string trim(string_view base) { + static const auto fills = T::fill(); + return details::trim(base, fills.front()); } } // namespace base } // namespace jwt diff --git a/include/jwt-cpp/jwt.h b/include/jwt-cpp/jwt.h index ec63b64c7..5c02cd32e 100644 --- a/include/jwt-cpp/jwt.h +++ b/include/jwt-cpp/jwt.h @@ -12,6 +12,8 @@ #include "base.h" #endif +#include "string_types.h" + #include #include #include @@ -468,7 +470,7 @@ namespace jwt { return std::unique_ptr(BIO_new(BIO_s_mem()), BIO_free_all); } - inline std::unique_ptr make_mem_buf_bio(const std::string& data) { + inline std::unique_ptr make_mem_buf_bio(string_view 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 @@ -494,38 +496,39 @@ namespace jwt { * \param pw Password used to decrypt certificate (leave empty if not encrypted) * \param ec error_code for error_detection (gets cleared if no error ocurred) */ - inline std::string extract_pubkey_from_cert(const std::string& certstr, const std::string& pw, - std::error_code& ec) { + inline std::string extract_pubkey_from_cert(string_view certstr, const std::string& pw, std::error_code& ec) { ec.clear(); auto certbio = make_mem_buf_bio(certstr); auto keybio = make_mem_buf_bio(); + std::string res; if (!certbio || !keybio) { ec = error::rsa_error::create_mem_bio_failed; - return {}; + return res; } 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 {}; + return res; } std::unique_ptr key(X509_get_pubkey(cert.get()), EVP_PKEY_free); if (!key) { ec = error::rsa_error::get_key_failed; - return {}; + return res; } if (PEM_write_bio_PUBKEY(keybio.get(), key.get()) == 0) { ec = error::rsa_error::write_key_failed; - return {}; + return res; } 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 res; } - return {ptr, static_cast(len)}; + res.assign(ptr, static_cast(len)); + return res; } /** @@ -535,7 +538,7 @@ namespace jwt { * \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 = "") { + inline std::string extract_pubkey_from_cert(string_view certstr, const std::string& pw = "") { std::error_code ec; auto res = extract_pubkey_from_cert(certstr, pw, ec); error::throw_if_error(ec); @@ -557,33 +560,32 @@ namespace jwt { * \param ec error_code for error_detection (gets cleared if no error occures) */ template - std::string convert_base64_der_to_pem(const std::string& cert_base64_der_str, Decode decode, - std::error_code& ec) { + std::string convert_base64_der_to_pem(string_view cert_base64_der_str, Decode decode, std::error_code& ec) { ec.clear(); const auto decodedStr = decode(cert_base64_der_str); - auto c_str = reinterpret_cast(decodedStr.c_str()); - + auto c_str = reinterpret_cast(decodedStr.data()); + std::string res; std::unique_ptr cert( d2i_X509(NULL, &c_str, static_cast(decodedStr.size())), X509_free); auto certbio = make_mem_buf_bio(); if (!cert || !certbio) { ec = error::rsa_error::create_mem_bio_failed; - return {}; + return res; } if (!PEM_write_bio_X509(certbio.get(), cert.get())) { ec = error::rsa_error::write_cert_failed; - return {}; + return res; } 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 res; } - - return {ptr, static_cast(len)}; + res.assign(ptr, static_cast(len)); + return res; } /** @@ -601,7 +603,7 @@ namespace jwt { * \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::string convert_base64_der_to_pem(string_view 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); @@ -617,8 +619,8 @@ namespace jwt { * \param cert_base64_der_str String containing the certificate encoded as base64 DER * \param ec error_code for error_detection (gets cleared if no error occures) */ - inline std::string convert_base64_der_to_pem(const std::string& cert_base64_der_str, std::error_code& ec) { - auto decode = [](const std::string& token) { + inline std::string convert_base64_der_to_pem(string_view cert_base64_der_str, std::error_code& ec) { + auto decode = [](string_view token) { return base::decode(base::pad(token)); }; return convert_base64_der_to_pem(cert_base64_der_str, std::move(decode), ec); @@ -633,7 +635,7 @@ namespace jwt { * \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) { + inline std::string convert_base64_der_to_pem(string_view 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); @@ -649,7 +651,7 @@ namespace jwt { * \param password Password used to decrypt certificate (leave empty if not encrypted) * \param ec error_code for error_detection (gets cleared if no error occures) */ - inline evp_pkey_handle load_public_key_from_string(const std::string& key, const std::string& password, + inline evp_pkey_handle load_public_key_from_string(string_view key, const std::string& password, std::error_code& ec) { ec.clear(); auto pubkey_bio = make_mem_buf_bio(); @@ -675,7 +677,7 @@ namespace jwt { evp_pkey_handle pkey(PEM_read_bio_PUBKEY( pubkey_bio.get(), nullptr, nullptr, - (void*)password.data())); // NOLINT(google-readability-casting) requires `const_cast` + (void*)password.c_str())); // NOLINT(google-readability-casting) requires `const_cast` if (!pkey) ec = error::rsa_error::load_key_bio_read; return pkey; } @@ -689,7 +691,7 @@ namespace jwt { * \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 = "") { + inline evp_pkey_handle load_public_key_from_string(string_view key, const std::string& password = "") { std::error_code ec; auto res = load_public_key_from_string(key, password, ec); error::throw_if_error(ec); @@ -703,7 +705,7 @@ namespace jwt { * \param password Password used to decrypt key (leave empty if not encrypted) * \param ec error_code for error_detection (gets cleared if no error occures) */ - inline evp_pkey_handle load_private_key_from_string(const std::string& key, const std::string& password, + inline evp_pkey_handle load_private_key_from_string(string_view key, const std::string& password, std::error_code& ec) { auto privkey_bio = make_mem_buf_bio(); if (!privkey_bio) { @@ -728,7 +730,7 @@ namespace jwt { * \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 = "") { + inline evp_pkey_handle load_private_key_from_string(string_view key, const std::string& password = "") { std::error_code ec; auto res = load_private_key_from_string(key, password, ec); error::throw_if_error(ec); @@ -744,7 +746,7 @@ namespace jwt { * \param password Password used to decrypt certificate (leave empty if not encrypted) * \param ec error_code for error_detection (gets cleared if no error occures) */ - inline evp_pkey_handle load_public_ec_key_from_string(const std::string& key, const std::string& password, + inline evp_pkey_handle load_public_ec_key_from_string(string_view key, const std::string& password, std::error_code& ec) { ec.clear(); auto pubkey_bio = make_mem_buf_bio(); @@ -770,7 +772,7 @@ namespace jwt { evp_pkey_handle pkey(PEM_read_bio_PUBKEY( pubkey_bio.get(), nullptr, nullptr, - (void*)password.data())); // NOLINT(google-readability-casting) requires `const_cast` + (void*)password.c_str())); // NOLINT(google-readability-casting) requires `const_cast` if (!pkey) ec = error::ecdsa_error::load_key_bio_read; return pkey; } @@ -784,8 +786,7 @@ namespace jwt { * \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 = "") { + inline evp_pkey_handle load_public_ec_key_from_string(string_view 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); @@ -799,7 +800,7 @@ namespace jwt { * \param password Password used to decrypt key (leave empty if not encrypted) * \param ec error_code for error_detection (gets cleared if no error occures) */ - inline evp_pkey_handle load_private_ec_key_from_string(const std::string& key, const std::string& password, + inline evp_pkey_handle load_private_ec_key_from_string(string_view key, const std::string& password, std::error_code& ec) { auto privkey_bio = make_mem_buf_bio(); if (!privkey_bio) { @@ -824,8 +825,7 @@ namespace jwt { * \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 = "") { + inline evp_pkey_handle load_private_ec_key_from_string(string_view 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); @@ -855,7 +855,7 @@ namespace jwt { * \param raw String to convert * \return BIGNUM representation */ - inline std::unique_ptr raw2bn(const std::string& raw) { + inline std::unique_ptr raw2bn(string_view raw) { return std::unique_ptr( BN_bin2bn(reinterpret_cast(raw.data()), static_cast(raw.size()), nullptr), BN_free); @@ -881,7 +881,7 @@ namespace jwt { /** * \brief Return an empty string */ - std::string sign(const std::string& /*unused*/, std::error_code& ec) const { + std::string sign(string_view /*unused*/, std::error_code& ec) const { ec.clear(); return {}; } @@ -892,12 +892,12 @@ namespace jwt { * \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 { + void verify(string_view /*unused*/, string_view 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"; } + string_constant name() const { return "none"; } }; /** * \brief Base class for HMAC family of algorithms @@ -909,15 +909,14 @@ namespace jwt { * \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)) {} + hmacsha(string_view key, const EVP_MD* (*md)(), string_view name) : secret(key), md(md), alg_name(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 { + std::string sign(string_view data, std::error_code& ec) const { ec.clear(); std::string res(static_cast(EVP_MAX_MD_SIZE), '\0'); auto len = static_cast(res.size()); @@ -926,7 +925,8 @@ namespace jwt { (unsigned char*)res.data(), // NOLINT(google-readability-casting) requires `const_cast` &len) == nullptr) { ec = error::signature_generation_error::hmac_failed; - return {}; + res.clear(); + return res; } res.resize(len); return res; @@ -937,7 +937,7 @@ namespace jwt { * \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 { + void verify(string_view data, string_view signature, std::error_code& ec) const { ec.clear(); auto res = sign(data, ec); if (ec) return; @@ -955,15 +955,15 @@ namespace jwt { * Returns the algorithm name provided to the constructor * \return algorithm's name */ - std::string name() const { return alg_name; } + string_view name() const { return alg_name; } private: - /// HMAC secrect - const std::string secret; + /// HMAC secret + std::string secret; /// HMAC hash generator const EVP_MD* (*md)(); /// algorithm's name - const std::string alg_name; + std::string alg_name; }; /** * \brief Base class for RSA family of algorithms @@ -978,9 +978,9 @@ namespace jwt { * \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)) { + rsa(string_view public_key, string_view private_key, const std::string& public_key_password, + const std::string& private_key_password, const EVP_MD* (*md)(), string_view name) + : md(md), alg_name(name) { if (!private_key.empty()) { pkey = helper::load_private_key_from_string(private_key, private_key_password); } else if (!public_key.empty()) { @@ -994,28 +994,28 @@ namespace jwt { * \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 { + std::string sign(string_view data, std::error_code& ec) const { ec.clear(); auto ctx = helper::make_evp_md_ctx(); + std::string res; if (!ctx) { ec = error::signature_generation_error::create_context_failed; - return {}; + return res; } if (!EVP_SignInit(ctx.get(), md())) { ec = error::signature_generation_error::signinit_failed; - return {}; + return res; } - - 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 {}; + return res; } + res.assign(EVP_PKEY_size(pkey.get()), '\0'); + unsigned int len = 0; if (EVP_SignFinal(ctx.get(), (unsigned char*)res.data(), &len, pkey.get()) == 0) { ec = error::signature_generation_error::signfinal_failed; - return {}; + res.clear(); + return res; } res.resize(len); @@ -1027,7 +1027,7 @@ namespace jwt { * \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 { + void verify(string_view data, string_view signature, std::error_code& ec) const { ec.clear(); auto ctx = helper::make_evp_md_ctx(); if (!ctx) { @@ -1053,7 +1053,7 @@ namespace jwt { * Returns the algorithm name provided to the constructor * \return algorithm's name */ - std::string name() const { return alg_name; } + string_view name() const { return alg_name; } private: /// OpenSSL structure containing converted keys @@ -1061,7 +1061,7 @@ namespace jwt { /// Hash generator const EVP_MD* (*md)(); /// algorithm's name - const std::string alg_name; + std::string alg_name; }; /** * \brief Base class for ECDSA family of algorithms @@ -1078,9 +1078,9 @@ namespace jwt { * \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) { + ecdsa(string_view public_key, string_view private_key, const std::string& public_key_password, + const std::string& private_key_password, const EVP_MD* (*md)(), string_view name, size_t siglen) + : md(md), alg_name(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()); @@ -1103,31 +1103,33 @@ namespace jwt { * \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 { + std::string sign(string_view data, std::error_code& ec) const { ec.clear(); auto ctx = helper::make_evp_md_ctx(); + std::string res; if (!ctx) { ec = error::signature_generation_error::create_context_failed; - return {}; + return res; } if (!EVP_DigestSignInit(ctx.get(), nullptr, md(), nullptr, pkey.get())) { ec = error::signature_generation_error::signinit_failed; - return {}; + return res; } if (!EVP_DigestUpdate(ctx.get(), data.data(), data.size())) { ec = error::signature_generation_error::digestupdate_failed; - return {}; + return res; } size_t len = 0; if (!EVP_DigestSignFinal(ctx.get(), nullptr, &len)) { ec = error::signature_generation_error::signfinal_failed; - return {}; + return res; } - std::string res(len, '\0'); + res.assign(len, '\0'); if (!EVP_DigestSignFinal(ctx.get(), (unsigned char*)res.data(), &len)) { ec = error::signature_generation_error::signfinal_failed; - return {}; + res.clear(); + return res; } res.resize(len); @@ -1140,7 +1142,7 @@ namespace jwt { * \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 { + void verify(string_view data, string_view signature, std::error_code& ec) const { ec.clear(); std::string der_signature = p1363_to_der_signature(signature, ec); if (ec) { return; } @@ -1179,7 +1181,7 @@ namespace jwt { * Returns the algorithm name provided to the constructor * \return algorithm's name */ - std::string name() const { return alg_name; } + string_view name() const { return alg_name; } private: static void check_public_key(EVP_PKEY* pkey) { @@ -1212,7 +1214,7 @@ namespace jwt { #endif } - std::string der_to_p1363_signature(const std::string& der_signature, std::error_code& ec) const { + std::string der_to_p1363_signature(string_view 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())), @@ -1240,12 +1242,13 @@ namespace jwt { return rr + rs; } - std::string p1363_to_der_signature(const std::string& signature, std::error_code& ec) const { + std::string p1363_to_der_signature(string_view 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; + std::string der_signature; #ifdef JWT_OPENSSL_1_0_0 ECDSA_SIG sig; sig.r = r.get(); @@ -1255,7 +1258,7 @@ namespace jwt { std::unique_ptr sig(ECDSA_SIG_new(), ECDSA_SIG_free); if (!sig) { ec = error::signature_verification_error::create_context_failed; - return {}; + return der_signature; } ECDSA_SIG_set0(sig.get(), r.release(), s.release()); psig = sig.get(); @@ -1264,14 +1267,15 @@ namespace jwt { int length = i2d_ECDSA_SIG(psig, nullptr); if (length < 0) { ec = error::signature_verification_error::signature_encoding_failed; - return {}; + return der_signature; } - std::string der_signature(length, '\0'); + der_signature.assign(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.clear(); + return der_signature; } der_signature.resize(length); return der_signature; @@ -1282,9 +1286,9 @@ namespace jwt { /// Hash generator function const EVP_MD* (*md)(); /// algorithm's name - const std::string alg_name; + std::string alg_name; /// Length of the resulting signature - const size_t signature_length; + size_t signature_length; }; #if !defined(JWT_OPENSSL_1_0_0) && !defined(JWT_OPENSSL_1_1_0) @@ -1307,9 +1311,9 @@ namespace jwt { * 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)) { + eddsa(string_view public_key, string_view private_key, const std::string& public_key_password, + const std::string& private_key_password, string_view name) + : alg_name(name) { if (!private_key.empty()) { pkey = helper::load_private_key_from_string(private_key, private_key_password); } else if (!public_key.empty()) { @@ -1323,20 +1327,21 @@ namespace jwt { * \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 { + std::string sign(string_view data, std::error_code& ec) const { ec.clear(); auto ctx = helper::make_evp_md_ctx(); + std::string res; if (!ctx) { ec = error::signature_generation_error::create_context_failed; - return {}; + return res; } if (!EVP_DigestSignInit(ctx.get(), nullptr, nullptr, nullptr, pkey.get())) { ec = error::signature_generation_error::signinit_failed; - return {}; + return res; } size_t len = EVP_PKEY_size(pkey.get()); - std::string res(len, '\0'); + res.assign(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 @@ -1347,17 +1352,20 @@ namespace jwt { 1) { std::cout << ERR_error_string(ERR_get_error(), NULL) << std::endl; ec = error::signature_generation_error::signupdate_failed; - return {}; + res.clear(); + return res; } if (EVP_DigestSignFinal(ctx.get(), reinterpret_cast(&res[0]), &len) != 1) { ec = error::signature_generation_error::signfinal_failed; - return {}; + res.clear(); + return res; } #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 {}; + res.clear(); + return res; } #endif @@ -1371,7 +1379,7 @@ namespace jwt { * \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 { + void verify(string_view data, string_view signature, std::error_code& ec) const { ec.clear(); auto ctx = helper::make_evp_md_ctx(); if (!ctx) { @@ -1410,13 +1418,13 @@ namespace jwt { * Returns the algorithm name provided to the constructor * \return algorithm's name */ - std::string name() const { return alg_name; } + string_view name() const { return alg_name; } private: /// OpenSSL struct containing keys helper::evp_pkey_handle pkey; /// algorithm's name - const std::string alg_name; + std::string alg_name; }; #endif /** @@ -1432,9 +1440,9 @@ namespace jwt { * \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)) { + pss(string_view public_key, string_view private_key, const std::string& public_key_password, + const std::string& private_key_password, const EVP_MD* (*md)(), string_view name) + : md(md), alg_name(name) { if (!private_key.empty()) { pkey = helper::load_private_key_from_string(private_key, private_key_password); } else if (!public_key.empty()) { @@ -1449,43 +1457,45 @@ namespace jwt { * \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 { + std::string sign(string_view data, std::error_code& ec) const { ec.clear(); auto md_ctx = helper::make_evp_md_ctx(); + std::string res; if (!md_ctx) { ec = error::signature_generation_error::create_context_failed; - return {}; + return res; } 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 {}; + return res; } if (EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_PKCS1_PSS_PADDING) <= 0) { ec = error::signature_generation_error::rsa_padding_failed; - return {}; + return res; } // 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 {}; + return res; } #endif if (EVP_DigestUpdate(md_ctx.get(), data.data(), data.size()) != 1) { ec = error::signature_generation_error::digestupdate_failed; - return {}; + return res; } size_t size = EVP_PKEY_size(pkey.get()); - std::string res(size, 0x00); + res.assign(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 {}; + res.clear(); + return res; } return res; @@ -1497,7 +1507,7 @@ namespace jwt { * \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 { + void verify(string_view data, string_view signature, std::error_code& ec) const { ec.clear(); auto md_ctx = helper::make_evp_md_ctx(); @@ -1536,7 +1546,7 @@ namespace jwt { * Returns the algorithm name provided to the constructor * \return algorithm's name */ - std::string name() const { return alg_name; } + string_view name() const { return alg_name; } private: /// OpenSSL structure containing keys @@ -1544,7 +1554,7 @@ namespace jwt { /// Hash generator function const EVP_MD* (*md)(); /// algorithm's name - const std::string alg_name; + std::string alg_name; }; /** @@ -1555,7 +1565,7 @@ namespace jwt { * Construct new instance of algorithm * \param key HMAC signing key */ - explicit hs256(std::string key) : hmacsha(std::move(key), EVP_sha256, "HS256") {} + explicit hs256(string_view key) : hmacsha(key, EVP_sha256, "HS256") {} }; /** * HS384 algorithm @@ -1565,7 +1575,7 @@ namespace jwt { * Construct new instance of algorithm * \param key HMAC signing key */ - explicit hs384(std::string key) : hmacsha(std::move(key), EVP_sha384, "HS384") {} + explicit hs384(string_view key) : hmacsha(key, EVP_sha384, "HS384") {} }; /** * HS512 algorithm @@ -1575,7 +1585,7 @@ namespace jwt { * Construct new instance of algorithm * \param key HMAC signing key */ - explicit hs512(std::string key) : hmacsha(std::move(key), EVP_sha512, "HS512") {} + explicit hs512(string_view key) : hmacsha(key, EVP_sha512, "HS512") {} }; /** * RS256 algorithm @@ -1588,7 +1598,7 @@ namespace jwt { * \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 = "", + explicit rs256(string_view public_key, string_view 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") {} }; @@ -1603,7 +1613,7 @@ namespace jwt { * \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 = "", + explicit rs384(string_view public_key, string_view 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") {} }; @@ -1618,7 +1628,7 @@ namespace jwt { * \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 = "", + explicit rs512(string_view public_key, string_view 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") {} }; @@ -1635,7 +1645,7 @@ namespace jwt { * \param private_key_password Password * to decrypt private key pem. */ - explicit es256(const std::string& public_key, const std::string& private_key = "", + explicit es256(string_view public_key, string_view 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) {} }; @@ -1652,7 +1662,7 @@ namespace jwt { * \param private_key_password Password * to decrypt private key pem. */ - explicit es384(const std::string& public_key, const std::string& private_key = "", + explicit es384(string_view public_key, string_view 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) {} }; @@ -1669,7 +1679,7 @@ namespace jwt { * \param private_key_password Password * to decrypt private key pem. */ - explicit es512(const std::string& public_key, const std::string& private_key = "", + explicit es512(string_view public_key, string_view 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) {} }; @@ -1685,7 +1695,7 @@ namespace jwt { * \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 = "", + explicit es256k(string_view public_key, string_view 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) {} }; @@ -1708,7 +1718,7 @@ namespace jwt { * \param private_key_password Password * to decrypt private key pem. */ - explicit ed25519(const std::string& public_key, const std::string& private_key = "", + explicit ed25519(string_view public_key, string_view 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") {} }; @@ -1730,7 +1740,7 @@ namespace jwt { * \param private_key_password Password * to decrypt private key pem. */ - explicit ed448(const std::string& public_key, const std::string& private_key = "", + explicit ed448(string_view public_key, string_view 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") {} }; @@ -1747,7 +1757,7 @@ namespace jwt { * \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 = "", + explicit ps256(string_view public_key, string_view 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") {} }; @@ -1762,7 +1772,7 @@ namespace jwt { * \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 = "", + explicit ps384(string_view public_key, string_view 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") {} }; @@ -1777,7 +1787,7 @@ namespace jwt { * \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 = "", + explicit ps512(string_view public_key, string_view 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") {} }; @@ -1941,10 +1951,9 @@ namespace jwt { 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 + // TODO(prince-chrismc): I am not convinced this is meaningful anymore static_assert( - value, - "object_type must implementate the subscription operator '[]' taking string_type as an arguement"); + value, "object_type must implement the subscription operator '[]' taking string_type as an argument"); }; template @@ -2077,12 +2086,22 @@ namespace jwt { std::is_constructible::value, "string_type must be a std::string, convertible to a std::string, or construct a std::string."); + template + struct is_string_view_compatible { +#ifdef JWT_HAS_STRING_VIEW + static constexpr bool value = std::is_constructible::value && + std::is_convertible::value; +#else + static constexpr bool value = false; +#endif + }; + static_assert( details::is_valid_json_types::value, - "must staisfy json container requirements"); + "must satisfy json container requirements"); static_assert(details::is_valid_traits::value, "traits must satisfy requirements"); typename json_traits::value_type val; @@ -2097,7 +2116,11 @@ namespace jwt { basic_claim& operator=(basic_claim&&) = default; ~basic_claim() = default; - JWT_CLAIM_EXPLICIT basic_claim(typename json_traits::string_type s) : val(std::move(s)) {} + template::value, + bool>::type = true> + JWT_CLAIM_EXPLICIT basic_claim(string_type&& s) : val(std::forward(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)) {} @@ -2218,12 +2241,9 @@ namespace jwt { 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)) {} + map_of_claims(const typename json_traits::object_type& json) : claims(json) {} + map_of_claims(typename json_traits::object_type&& json) : claims(std::move(json)) {} iterator begin() { return claims.begin(); } iterator end() { return claims.end(); } @@ -2240,18 +2260,20 @@ namespace jwt { * \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) { + template + static typename json_traits::object_type parse_claims(const 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 { + template + bool has_claim(const string_type& name) const noexcept { return claims.count(name) != 0; } @@ -2262,7 +2284,8 @@ namespace jwt { * \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 { + template + basic_claim_t get_claim(const string_type& name) const { if (!has_claim(name)) throw error::claim_not_present_exception(); return basic_claim_t{claims.at(name)}; } @@ -2606,16 +2629,37 @@ namespace jwt { typename json_traits::object_type header_claims; typename json_traits::object_type payload_claims; + private: + template::value, + bool>::type = true> + static typename json_traits::value_type json_value_from_str(string_type&& s) { + return typename json_traits::value_type(std::forward(s)); + } + + template::value, + bool>::type = true> + static typename json_traits::value_type json_value_from_str(string_type&& s) { + return typename json_traits::value_type(typename json_traits::string_type(std::forward(s))); + } + 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); + template::value && + std::is_constructible::value, + bool>::type = true> + builder& set_header_claim(string_type&& id, value_type&& c) { + header_claims[typename json_traits::string_type(std::forward(id))] = + typename json_traits::value_type(std::forward(c)); return *this; } @@ -2625,30 +2669,44 @@ namespace jwt { * \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(); + template::value, + bool>::type = true> + builder& set_header_claim(string_type&& id, const basic_claim& c) { + header_claims[typename json_traits::string_type(std::forward(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); + template::value && + std::is_constructible::value, + bool>::type = true> + builder& set_payload_claim(string_type&& id, value_type&& c) { + payload_claims[typename json_traits::string_type(std::forward(id))] = + typename json_traits::value_type(std::forward(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(); + template::value, + bool>::type = true> + builder& set_payload_claim(string_type&& id, const basic_claim& c) { + payload_claims[typename json_traits::string_type(std::forward(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. @@ -2656,24 +2714,27 @@ namespace jwt { * \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)); + template + builder& set_algorithm(string_type&& str) { + return set_header_claim("alg", std::forward(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)); + template + builder& set_type(string_type&& str) { + return set_header_claim("typ", std::forward(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)); + template + builder& set_content_type(string_type&& str) { + return set_header_claim("cty", std::forward(str)); } /** * \brief Set key id claim @@ -2681,40 +2742,50 @@ namespace jwt { * \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)); + template + builder& set_key_id(string_type&& str) { + return set_header_claim("kid", std::forward(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)); + template + builder& set_issuer(string_type&& str) { + return set_payload_claim("iss", std::forward(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)); + template + builder& set_subject(string_type&& str) { + return set_payload_claim("sub", std::forward(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)); + builder& set_audience(const typename json_traits::array_type& arr) { return set_payload_claim("aud", arr); } + /** + * Set audience claim + * \param a Audience set + * \return *this to allow for method chaining + */ + builder& set_audience(typename json_traits::array_type&& arr) { + return set_payload_claim("aud", std::move(arr)); } /** * 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)); + template + builder& set_audience(string_type&& aud) { + return set_payload_claim("aud", std::forward(aud)); } /** * Set expires at claim @@ -2739,8 +2810,9 @@ namespace jwt { * \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)); + template + builder& set_id(string_type&& str) { + return set_payload_claim("jti", std::forward(str)); } /** @@ -2793,18 +2865,30 @@ namespace jwt { */ 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()); + typename json_traits::object_type obj_header; + const typename json_traits::object_type* pheader_claims; + if (header_claims.count("alg") == 0) { + // make a copy such that a builder can be re-used + obj_header = header_claims; + obj_header["alg"] = json_value_from_str(algo.name()); + pheader_claims = std::addressof(obj_header); + } else { + // no copy is needed because alg is already filled + pheader_claims = std::addressof(header_claims); + } - const auto header = encode(json_traits::serialize(typename json_traits::value_type(obj_header))); + const auto header = encode(json_traits::serialize(typename json_traits::value_type(*pheader_claims))); 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 {}; + typename json_traits::string_type res; + if (ec) return res; - return token + "." + encode(signature); + res.append(token); + res.push_back('.'); + res.append(encode(signature)); + return res; } #ifndef JWT_DISABLE_BASE64 /** @@ -2988,17 +3072,18 @@ namespace jwt { } } - static std::string to_lower_unicode(const std::string& str, const std::locale& loc) { + static std::string to_lower_unicode(string_view str, const std::locale& loc) { #if __cplusplus > 201103L + // Note: std::wstring_convert is deprecated from C++17 std::wstring_convert, wchar_t> conv; - auto wide = conv.from_bytes(str); + auto wide = conv.from_bytes(str.data(), str.data() + str.size()); auto& f = std::use_facet>(loc); f.tolower(&wide[0], &wide[0] + wide.size()); return conv.to_bytes(wide); #else - std::string result; - std::transform(str.begin(), str.end(), std::back_inserter(result), - [&loc](unsigned char c) { return std::tolower(c, loc); }); + std::string result(str.size(), '\0'); + std::transform(str.begin(), str.end(), result.begin(), + [&loc](unsigned char c) { return static_cast(std::tolower(c, loc)); }); return result; #endif } @@ -3028,15 +3113,13 @@ namespace jwt { private: struct algo_base { virtual ~algo_base() = default; - virtual void verify(const std::string& data, const std::string& sig, std::error_code& ec) = 0; + virtual void verify(string_view data, string_view 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); - } + void verify(string_view data, string_view sig, std::error_code& ec) override { alg.verify(data, sig, ec); } }; /// Required claims std::unordered_map claims; @@ -3207,7 +3290,7 @@ namespace jwt { */ template verifier& allow_algorithm(Algorithm alg) { - algs[alg.name()] = std::make_shared>(alg); + algs[static_cast(alg.name())] = std::make_shared>(alg); return *this; } @@ -3470,7 +3553,8 @@ namespace jwt { 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) { + template + JWT_CLAIM_EXPLICIT jwks(const string_type& str) { typename json_traits::value_type parsed_val; if (!json_traits::parse(parsed_val, str)) throw error::invalid_json_exception(); diff --git a/include/jwt-cpp/string_types.h b/include/jwt-cpp/string_types.h new file mode 100644 index 000000000..23a8a1895 --- /dev/null +++ b/include/jwt-cpp/string_types.h @@ -0,0 +1,24 @@ +#ifndef STRING_TYPES_H +#define STRING_TYPES_H + +namespace jwt { +#if __cplusplus >= 201703L + +#include + +#define JWT_HAS_STRING_VIEW + + using string_constant = std::string_view; + using string_view = std::string_view; + +#else + +#include + + using string_constant = std::string; + using string_view = const std::string&; + +#endif +} // namespace jwt + +#endif \ No newline at end of file diff --git a/include/jwt-cpp/traits/boost-json/traits.h b/include/jwt-cpp/traits/boost-json/traits.h index 3b27d5f2f..4d7419aba 100644 --- a/include/jwt-cpp/traits/boost-json/traits.h +++ b/include/jwt-cpp/traits/boost-json/traits.h @@ -67,7 +67,8 @@ namespace jwt { return val.get_double(); } - static bool parse(value_type& val, string_type str) { + template + static bool parse(value_type& val, const string_t& str) { val = json::parse(str); return true; } diff --git a/include/jwt-cpp/traits/danielaparker-jsoncons/traits.h b/include/jwt-cpp/traits/danielaparker-jsoncons/traits.h index be56740ca..795ead400 100644 --- a/include/jwt-cpp/traits/danielaparker-jsoncons/traits.h +++ b/include/jwt-cpp/traits/danielaparker-jsoncons/traits.h @@ -108,7 +108,8 @@ namespace jwt { return val.as_bool(); } - static bool parse(json& val, const std::string& str) { + template + static bool parse(json& val, const string_t& str) { val = json::parse(str); return true; } diff --git a/include/jwt-cpp/traits/kazuho-picojson/traits.h b/include/jwt-cpp/traits/kazuho-picojson/traits.h index 2f96bfc01..311c61c19 100644 --- a/include/jwt-cpp/traits/kazuho-picojson/traits.h +++ b/include/jwt-cpp/traits/kazuho-picojson/traits.h @@ -64,7 +64,8 @@ namespace jwt { return val.get(); } - static bool parse(picojson::value& val, const std::string& str) { + template + static bool parse(picojson::value& val, const string_t& str) { return picojson::parse(val, str).empty(); } diff --git a/include/jwt-cpp/traits/nlohmann-json/traits.h b/include/jwt-cpp/traits/nlohmann-json/traits.h index 7cf486902..9babb4e7d 100644 --- a/include/jwt-cpp/traits/nlohmann-json/traits.h +++ b/include/jwt-cpp/traits/nlohmann-json/traits.h @@ -64,7 +64,8 @@ namespace jwt { return val.get(); } - static bool parse(json& val, std::string str) { + template + static bool parse(json& val, const string_t& str) { val = json::parse(str.begin(), str.end()); return true; } diff --git a/tests/BaseTest.cpp b/tests/BaseTest.cpp index 210798afc..8c1faf37c 100644 --- a/tests/BaseTest.cpp +++ b/tests/BaseTest.cpp @@ -1,110 +1,119 @@ #include "jwt-cpp/base.h" #include -TEST(BaseTest, Base64Index) { - ASSERT_EQ(0, jwt::alphabet::index(jwt::alphabet::base64::data(), 'A')); - ASSERT_EQ(32, jwt::alphabet::index(jwt::alphabet::base64::data(), 'g')); - ASSERT_EQ(62, jwt::alphabet::index(jwt::alphabet::base64::data(), '+')); -} - -TEST(BaseTest, Base64URLIndex) { - ASSERT_EQ(0, jwt::alphabet::index(jwt::alphabet::base64url::data(), 'A')); - ASSERT_EQ(32, jwt::alphabet::index(jwt::alphabet::base64url::data(), 'g')); - ASSERT_EQ(62, jwt::alphabet::index(jwt::alphabet::base64url::data(), '-')); -} - -TEST(BaseTest, BaseDetailsCountPadding) { - using jwt::base::details::padding; - ASSERT_EQ(padding{}, jwt::base::details::count_padding("ABC", {"~"})); - ASSERT_EQ((padding{3, 3}), jwt::base::details::count_padding("ABC~~~", {"~"})); - ASSERT_EQ((padding{5, 5}), jwt::base::details::count_padding("ABC~~~~~", {"~"})); - - ASSERT_EQ(padding{}, jwt::base::details::count_padding("ABC", {"~", "!"})); - ASSERT_EQ((padding{1, 1}), jwt::base::details::count_padding("ABC!", {"~", "!"})); - ASSERT_EQ((padding{1, 1}), jwt::base::details::count_padding("ABC~", {"~", "!"})); - ASSERT_EQ((padding{3, 3}), jwt::base::details::count_padding("ABC~~!", {"~", "!"})); - ASSERT_EQ((padding{3, 3}), jwt::base::details::count_padding("ABC!~~", {"~", "!"})); - ASSERT_EQ((padding{5, 5}), jwt::base::details::count_padding("ABC~~!~~", {"~", "!"})); - - ASSERT_EQ((padding{2, 6}), jwt::base::details::count_padding("MTIzNA%3d%3d", {"%3d", "%3D"})); - ASSERT_EQ((padding{2, 6}), jwt::base::details::count_padding("MTIzNA%3d%3D", {"%3d", "%3D"})); - ASSERT_EQ((padding{2, 6}), jwt::base::details::count_padding("MTIzNA%3D%3d", {"%3d", "%3D"})); - ASSERT_EQ((padding{2, 6}), jwt::base::details::count_padding("MTIzNA%3D%3D", {"%3d", "%3D"})); - - // Some fake scenarios - - ASSERT_EQ(padding{}, jwt::base::details::count_padding("", {"~"})); - ASSERT_EQ(padding{}, jwt::base::details::count_padding("ABC", {"~", "~~!"})); - ASSERT_EQ(padding{}, jwt::base::details::count_padding("ABC!", {"~", "~~!"})); - ASSERT_EQ((padding{1, 1}), jwt::base::details::count_padding("ABC~", {"~", "~~!"})); - ASSERT_EQ((padding{1, 3}), jwt::base::details::count_padding("ABC~~!", {"~", "~~!"})); - ASSERT_EQ((padding{2, 2}), jwt::base::details::count_padding("ABC!~~", {"~", "~~!"})); - ASSERT_EQ((padding{3, 5}), jwt::base::details::count_padding("ABC~~!~~", {"~", "~~!"})); - ASSERT_EQ(padding{}, jwt::base::details::count_padding("ABC~~!~~", {})); -} - -TEST(BaseTest, Base64Decode) { - ASSERT_EQ("1", jwt::base::decode("MQ==")); - ASSERT_EQ("12", jwt::base::decode("MTI=")); - ASSERT_EQ("123", jwt::base::decode("MTIz")); - ASSERT_EQ("1234", jwt::base::decode("MTIzNA==")); -} - -TEST(BaseTest, Base64DecodeURL) { - ASSERT_EQ("1", jwt::base::decode("MQ%3d%3d")); - ASSERT_EQ("12", jwt::base::decode("MTI%3d")); - ASSERT_EQ("123", jwt::base::decode("MTIz")); - ASSERT_EQ("1234", jwt::base::decode("MTIzNA%3d%3d")); -} - -TEST(BaseTest, Base64DecodeURLCaseInsensitive) { - ASSERT_EQ("1", jwt::base::decode("MQ%3d%3d")); - ASSERT_EQ("1", jwt::base::decode("MQ%3D%3d")); - ASSERT_EQ("1", jwt::base::decode("MQ%3d%3D")); - ASSERT_EQ("12", jwt::base::decode("MTI%3d")); - ASSERT_EQ("123", jwt::base::decode("MTIz")); - ASSERT_EQ("1234", jwt::base::decode("MTIzNA%3d%3d")); - ASSERT_EQ("1234", jwt::base::decode("MTIzNA%3D%3D")); -} - -TEST(BaseTest, Base64Encode) { - ASSERT_EQ("MQ==", jwt::base::encode("1")); - ASSERT_EQ("MTI=", jwt::base::encode("12")); - ASSERT_EQ("MTIz", jwt::base::encode("123")); - ASSERT_EQ("MTIzNA==", jwt::base::encode("1234")); -} - -TEST(BaseTest, Base64EncodeURL) { - ASSERT_EQ("MQ%3d%3d", jwt::base::encode("1")); - ASSERT_EQ("MTI%3d", jwt::base::encode("12")); - ASSERT_EQ("MTIz", jwt::base::encode("123")); - ASSERT_EQ("MTIzNA%3d%3d", jwt::base::encode("1234")); -} - -TEST(BaseTest, Base64Pad) { - ASSERT_EQ("MQ==", jwt::base::pad("MQ")); - ASSERT_EQ("MTI=", jwt::base::pad("MTI")); - ASSERT_EQ("MTIz", jwt::base::pad("MTIz")); - ASSERT_EQ("MTIzNA==", jwt::base::pad("MTIzNA")); -} - -TEST(BaseTest, Base64PadURL) { - ASSERT_EQ("MQ%3d%3d", jwt::base::pad("MQ")); - ASSERT_EQ("MTI%3d", jwt::base::pad("MTI")); - ASSERT_EQ("MTIz", jwt::base::pad("MTIz")); - ASSERT_EQ("MTIzNA%3d%3d", jwt::base::pad("MTIzNA")); -} - -TEST(BaseTest, Base64Trim) { - ASSERT_EQ("MQ", jwt::base::trim("MQ==")); - ASSERT_EQ("MTI", jwt::base::trim("MTI=")); - ASSERT_EQ("MTIz", jwt::base::trim("MTIz")); - ASSERT_EQ("MTIzNA", jwt::base::trim("MTIzNA==")); -} - -TEST(BaseTest, Base64TrimURL) { - ASSERT_EQ("MQ", jwt::base::trim("MQ%3d%3d")); - ASSERT_EQ("MTI", jwt::base::trim("MTI%3d")); - ASSERT_EQ("MTIz", jwt::base::trim("MTIz")); - ASSERT_EQ("MTIzNA", jwt::base::trim("MTIzNA%3d%3d")); -} +namespace jwt { + + namespace { + base::details::padding count_padding(string_view base, std::initializer_list fills) { + return base::details::count_padding(base, fills.begin(), fills.end()); + } + } // namespace + + TEST(BaseTest, Base64Index) { + ASSERT_EQ(0, alphabet::index(alphabet::base64::data(), 'A')); + ASSERT_EQ(32, alphabet::index(alphabet::base64::data(), 'g')); + ASSERT_EQ(62, alphabet::index(alphabet::base64::data(), '+')); + } + + TEST(BaseTest, Base64URLIndex) { + ASSERT_EQ(0, alphabet::index(alphabet::base64url::data(), 'A')); + ASSERT_EQ(32, alphabet::index(alphabet::base64url::data(), 'g')); + ASSERT_EQ(62, alphabet::index(alphabet::base64url::data(), '-')); + } + + TEST(BaseTest, BaseDetailsCountPadding) { + using base::details::padding; + ASSERT_EQ(padding{}, count_padding("ABC", {"~"})); + ASSERT_EQ((padding{3, 3}), count_padding("ABC~~~", {"~"})); + ASSERT_EQ((padding{5, 5}), count_padding("ABC~~~~~", {"~"})); + + ASSERT_EQ(padding{}, count_padding("ABC", {"~", "!"})); + ASSERT_EQ((padding{1, 1}), count_padding("ABC!", {"~", "!"})); + ASSERT_EQ((padding{1, 1}), count_padding("ABC~", {"~", "!"})); + ASSERT_EQ((padding{3, 3}), count_padding("ABC~~!", {"~", "!"})); + ASSERT_EQ((padding{3, 3}), count_padding("ABC!~~", {"~", "!"})); + ASSERT_EQ((padding{5, 5}), count_padding("ABC~~!~~", {"~", "!"})); + + ASSERT_EQ((padding{2, 6}), count_padding("MTIzNA%3d%3d", {"%3d", "%3D"})); + ASSERT_EQ((padding{2, 6}), count_padding("MTIzNA%3d%3D", {"%3d", "%3D"})); + ASSERT_EQ((padding{2, 6}), count_padding("MTIzNA%3D%3d", {"%3d", "%3D"})); + ASSERT_EQ((padding{2, 6}), count_padding("MTIzNA%3D%3D", {"%3d", "%3D"})); + + // Some fake scenarios + + ASSERT_EQ(padding{}, count_padding("", {"~"})); + ASSERT_EQ(padding{}, count_padding("ABC", {"~", "~~!"})); + ASSERT_EQ(padding{}, count_padding("ABC!", {"~", "~~!"})); + ASSERT_EQ((padding{1, 1}), count_padding("ABC~", {"~", "~~!"})); + ASSERT_EQ((padding{1, 3}), count_padding("ABC~~!", {"~", "~~!"})); + ASSERT_EQ((padding{2, 2}), count_padding("ABC!~~", {"~", "~~!"})); + ASSERT_EQ((padding{3, 5}), count_padding("ABC~~!~~", {"~", "~~!"})); + ASSERT_EQ(padding{}, count_padding("ABC~~!~~", {})); + } + + TEST(BaseTest, Base64Decode) { + ASSERT_EQ("1", base::decode("MQ==")); + ASSERT_EQ("12", base::decode("MTI=")); + ASSERT_EQ("123", base::decode("MTIz")); + ASSERT_EQ("1234", base::decode("MTIzNA==")); + } + + TEST(BaseTest, Base64DecodeURL) { + ASSERT_EQ("1", base::decode("MQ%3d%3d")); + ASSERT_EQ("12", base::decode("MTI%3d")); + ASSERT_EQ("123", base::decode("MTIz")); + ASSERT_EQ("1234", base::decode("MTIzNA%3d%3d")); + } + + TEST(BaseTest, Base64DecodeURLCaseInsensitive) { + ASSERT_EQ("1", base::decode("MQ%3d%3d")); + ASSERT_EQ("1", base::decode("MQ%3D%3d")); + ASSERT_EQ("1", base::decode("MQ%3d%3D")); + ASSERT_EQ("12", base::decode("MTI%3d")); + ASSERT_EQ("123", base::decode("MTIz")); + ASSERT_EQ("1234", base::decode("MTIzNA%3d%3d")); + ASSERT_EQ("1234", base::decode("MTIzNA%3D%3D")); + } + + TEST(BaseTest, Base64Encode) { + ASSERT_EQ("MQ==", base::encode("1")); + ASSERT_EQ("MTI=", base::encode("12")); + ASSERT_EQ("MTIz", base::encode("123")); + ASSERT_EQ("MTIzNA==", base::encode("1234")); + } + + TEST(BaseTest, Base64EncodeURL) { + ASSERT_EQ("MQ%3d%3d", base::encode("1")); + ASSERT_EQ("MTI%3d", base::encode("12")); + ASSERT_EQ("MTIz", base::encode("123")); + ASSERT_EQ("MTIzNA%3d%3d", base::encode("1234")); + } + + TEST(BaseTest, Base64Pad) { + ASSERT_EQ("MQ==", base::pad("MQ")); + ASSERT_EQ("MTI=", base::pad("MTI")); + ASSERT_EQ("MTIz", base::pad("MTIz")); + ASSERT_EQ("MTIzNA==", base::pad("MTIzNA")); + } + + TEST(BaseTest, Base64PadURL) { + ASSERT_EQ("MQ%3d%3d", base::pad("MQ")); + ASSERT_EQ("MTI%3d", base::pad("MTI")); + ASSERT_EQ("MTIz", base::pad("MTIz")); + ASSERT_EQ("MTIzNA%3d%3d", base::pad("MTIzNA")); + } + + TEST(BaseTest, Base64Trim) { + ASSERT_EQ("MQ", base::trim("MQ==")); + ASSERT_EQ("MTI", base::trim("MTI=")); + ASSERT_EQ("MTIz", base::trim("MTIz")); + ASSERT_EQ("MTIzNA", base::trim("MTIzNA==")); + } + + TEST(BaseTest, Base64TrimURL) { + ASSERT_EQ("MQ", base::trim("MQ%3d%3d")); + ASSERT_EQ("MTI", base::trim("MTI%3d")); + ASSERT_EQ("MTIz", base::trim("MTIz")); + ASSERT_EQ("MTIzNA", base::trim("MTIzNA%3d%3d")); + } +} // namespace jwt \ No newline at end of file diff --git a/tests/ClaimTest.cpp b/tests/ClaimTest.cpp index e7cccc3b5..d940ef786 100644 --- a/tests/ClaimTest.cpp +++ b/tests/ClaimTest.cpp @@ -139,3 +139,37 @@ TEST(ClaimTest, MapOfClaim) { ASSERT_EQ(claims.get_claim("bool").as_boolean(), true); ASSERT_THROW(claims.get_claim("__missing__"), jwt::error::claim_not_present_exception); } + +#ifdef JWT_HAS_STRING_VIEW +TEST(ClaimTest, StringViewSupport) { + // Force all parameters to be string_views + std::string_view jwtType = "JWT"; + std::string_view claimKey = "access_key"; + std::string_view claimPayload = "accessKeyPayload"; + std::string_view alg = "hs512"; + std::string_view contentType = "mycontent"; + std::string_view keyId = "mykeyid"; + std::string_view issuer = "myissuer"; + std::string_view subject = "mysubject"; + std::string_view audience = "myaudience"; + std::string_view id = "myid"; + + auto jsonWebToken = jwt::create() + .set_algorithm(alg) + .set_type(jwtType) + .set_content_type(contentType) + .set_key_id(keyId) + .set_issuer(issuer) + .set_subject(subject) + .set_audience(audience) + .set_id(id) + .set_payload_claim(claimKey, jwt::claim(claimPayload)); + + std::string_view privateKey = "myprivatekey"; + + auto token = jsonWebToken.sign(jwt::algorithm::hs256{privateKey}); + ASSERT_EQ(token, "eyJhbGciOiJoczUxMiIsImN0eSI6Im15Y29udGVudCIsImtpZCI6Im15a2V5aWQiLCJ0eXAiOiJKV1QifQ." + "eyJhY2Nlc3Nfa2V5IjoiYWNjZXNzS2V5UGF5bG9hZCIsImF1ZCI6Im15YXVkaWVuY2UiLCJpc3MiOiJteWlzc3VlciIsImp0a" + "SI6Im15aWQiLCJzdWIiOiJteXN1YmplY3QifQ.9fd56G-dAxHh89Dl7wZftwsoSfFO_msUFYvA5q77fes"); +} +#endif \ No newline at end of file diff --git a/tests/TokenTest.cpp b/tests/TokenTest.cpp index a64e0d11c..07deb1799 100644 --- a/tests/TokenTest.cpp +++ b/tests/TokenTest.cpp @@ -119,6 +119,7 @@ TEST(TokenTest, CreateTokenES256) { } TEST(TokenTest, CreateTokenES256NoPrivate) { + ASSERT_THROW( []() { auto token = jwt::create().set_issuer("auth0").set_type("JWS").sign(