diff --git a/include/jwt/algorithm.hpp b/include/jwt/algorithm.hpp index 83e8535..c7e59fb 100644 --- a/include/jwt/algorithm.hpp +++ b/include/jwt/algorithm.hpp @@ -1,6 +1,13 @@ #ifndef CPP_JWT_ALGORITHM_HPP #define CPP_JWT_ALGORITHM_HPP +/*! + * Most of the signing and verification code has been taken + * and modified for C++ specific use from the C implementation + * JWT library, libjwt. + * https://github.com/benmcollins/libjwt/tree/master/libjwt + */ + #include #include @@ -12,7 +19,9 @@ #include #include +#include "jwt/exceptions.hpp" #include "jwt/string_view.hpp" +#include "jwt/error_codes.hpp" namespace jwt { @@ -20,23 +29,25 @@ namespace jwt { using sign_result_t = std::pair; /// The result type of verification function using verify_result_t = std::pair; -/// The function pointer type of the signing function +/// The function pointer type for the signing function using sign_func_t = sign_result_t (*) (const string_view key, const string_view data); -/// +/// The function pointer type for the verifying function using verify_func_t = verify_result_t (*) (const string_view key, const string_view head, const string_view jwt_sign); namespace algo { -//TODO: All these can be done using code generaion. -// NO. NEVER. I hate Macros. -// You can use templates too. -// No. I would rather prefer explicit. -// Ok. You win. +//Me: TODO: All these can be done using code generaion. +//Me: NO. NEVER. I hate Macros. +//Me: You can use templates too. +//Me: No. I would rather prefer explicit. +//Me: Ok. You win. +//Me: Same to you. -/*! +/** + * HS256 algorithm. */ struct HS256 { @@ -46,7 +57,8 @@ struct HS256 } }; -/*! +/** + * HS384 algorithm. */ struct HS384 { @@ -56,7 +68,8 @@ struct HS384 } }; -/*! +/** + * HS512 algorithm. */ struct HS512 { @@ -66,7 +79,8 @@ struct HS512 } }; -/*! +/** + * NONE algorithm. */ struct NONE { @@ -76,7 +90,8 @@ struct NONE } }; -/*! +/** + * RS256 algorithm. */ struct RS256 { @@ -88,7 +103,8 @@ struct RS256 } }; -/*! +/** + * RS384 algorithm. */ struct RS384 { @@ -100,7 +116,8 @@ struct RS384 } }; -/*! +/** + * RS512 algorithm. */ struct RS512 { @@ -112,7 +129,8 @@ struct RS512 } }; -/*! +/** + * ES256 algorithm. */ struct ES256 { @@ -124,7 +142,8 @@ struct ES256 } }; -/*! +/** + * ES384 algorithm. */ struct ES384 { @@ -136,7 +155,8 @@ struct ES384 } }; -/*! +/** + * ES512 algorithm. */ struct ES512 { @@ -151,8 +171,8 @@ struct ES512 } //END Namespace algo -/*! - * JWT signing algorithm. +/** + * JWT signing algorithm types. */ enum class algorithm { @@ -170,7 +190,9 @@ enum class algorithm }; -/*! +/** + * Convert the algorithm enum class type to + * its stringified form. */ string_view alg_to_str(enum algorithm alg) noexcept { @@ -192,7 +214,9 @@ string_view alg_to_str(enum algorithm alg) noexcept assert (0 && "Code not reached"); } -/*! +/** + * Convert stringified algorithm to enum class. + * The string comparison is case insesitive. */ enum algorithm str_to_alg(const string_view alg) noexcept { @@ -213,7 +237,14 @@ enum algorithm str_to_alg(const string_view alg) noexcept } -/*! +/** + * OpenSSL HMAC based signature and verfication. + * + * The template type `Hasher` takes the type representing + * the HMAC algorithm type from the `jwt::algo` namespace. + * + * The struct is specialized for NONE algorithm. See the + * details of that class as well. */ template struct HMACSign @@ -221,7 +252,18 @@ struct HMACSign /// The type of Hashing algorithm using hasher_type = Hasher; - /*! + /** + * Signs the input using the HMAC algorithm using the + * provided key. + * + * Arguments: + * @key : The secret/key to use for the signing. + * Cannot be empty string. + * @data : The data to be signed. + * + * Exceptions: + * Any allocation failure will result in jwt::MemoryAllocationException + * being thrown. */ static sign_result_t sign(const string_view key, const string_view data) { @@ -240,39 +282,55 @@ struct HMACSign &len); if (!res) { - //TODO: Set the appropriate error code + ec = AlgorithmErrc::SigningErr; } - sign.resize(len); - return {std::move(sign), ec}; + sign.resize(len); + return { std::move(sign), ec }; } - /*! + /** */ static verify_result_t verify(const string_view key, const string_view head, const string_view sign); }; -/*! +/** + * Specialization of `HMACSign` class + * for NONE algorithm. + * + * This specialization is selected for even + * PEM based algorithms. + * + * The signing and verification APIs are + * basically no-op except that they would + * set the relevant error code. + * + * NOTE: error_code would be set in the case + * of usage of NONE algorithm. + * Users of this API are expected to check for + * the case explicitly. */ template <> struct HMACSign { using hasher_type = algo::NONE; - /*! + /** + * Basically a no-op. Sets the error code to NoneAlgorithmUsed. */ static sign_result_t sign(const string_view key, const string_view data) { - std::string sign; + (void)key; + (void)data; std::error_code ec{}; + ec = AlgorithmErrc::NoneAlgorithmUsed; - //TODO: Set the appropriate error code for none - return {sign, ec}; + return { std::string{}, ec }; } - /*! + /** */ static verify_result_t verify(const string_view key, const string_view head, const string_view sign) @@ -281,13 +339,21 @@ struct HMACSign std::error_code ec{}; //TODO: Set the appropriate error code for none - return {compare_res, ec}; + return { compare_res, ec }; } }; -/*! + +/** + * OpenSSL PEM based signature and verfication. + * + * The template type `Hasher` takes the type representing + * the PEM algorithm type from the `jwt::algo` namespace. + * + * For NONE algorithm, HMACSign<> specialization is used. + * See that for more details. */ template struct PEMSign @@ -296,7 +362,17 @@ public: /// The type of Hashing algorithm using hasher_type = Hasher; - /*! + /** + * Signs the input data using PEM encryption algorithm. + * + * Arguments: + * @key : The key/secret to be used for signing. + * Cannot be an empty string. + * @data: The data to be signed. + * + * Exceptions: + * Any allocation failure would be thrown out as + * jwt::MemoryAllocationException. */ static sign_result_t sign(const string_view key, const string_view data) { @@ -307,27 +383,20 @@ public: }; std::unique_ptr - pkey{load_key(key), evpkey_deletor}; + pkey{load_key(key, ec), evpkey_deletor}; - if (!pkey) { - //TODO: set valid error code - return {std::string{}, ec}; - } + if (ec) return { std::string{}, ec }; //TODO: Use stack string here ? std::string sign = evp_digest(pkey.get(), data, ec); - if (ec) { - //TODO: handle error_code - return {std::move(sign), ec}; - } - if (Hasher::type != EVP_PKEY_EC) { - return {std::move(sign), ec}; - } else { + if (ec) return { std::string{}, ec }; + + if (Hasher::type == EVP_PKEY_EC) { sign = public_key_ser(pkey.get(), sign, ec); } - return {std::move(sign), ec}; + return { std::move(sign), ec }; } /*! @@ -345,7 +414,7 @@ public: private: /*! */ - static EVP_PKEY* load_key(const string_view key); + static EVP_PKEY* load_key(const string_view key, std::error_code& ec); /*! */ diff --git a/include/jwt/error_codes.hpp b/include/jwt/error_codes.hpp index e69de29..b7357e4 100644 --- a/include/jwt/error_codes.hpp +++ b/include/jwt/error_codes.hpp @@ -0,0 +1,44 @@ +#ifndef CPP_JWT_ERROR_CODES_HPP +#define CPP_JWT_ERROR_CODES_HPP + +#include + +namespace jwt { +/** + * All the algorithm errors + */ +enum class AlgorithmErrc +{ + SigningErr = 1, + VerificationErr, + NoneAlgorithmUsed, // Not an actual error! +}; + +/** + * Algorithm error conditions + */ +enum class AlgorithmFailureSource +{ +}; + + +/** + */ +std::error_code make_error_code(AlgorithmErrc err); + +} // END namespace jwt + + +/** + * Make the custom enum classes as error code + * adaptable. + */ +namespace std +{ + template <> + struct is_error_code_enum : true_type {}; +} + +#include "jwt/impl/error_codes.ipp" + +#endif diff --git a/include/jwt/exceptions.hpp b/include/jwt/exceptions.hpp index e69de29..14863e2 100644 --- a/include/jwt/exceptions.hpp +++ b/include/jwt/exceptions.hpp @@ -0,0 +1,35 @@ +#ifndef CPP_JWT_EXCEPTIONS_HPP +#define CPP_JWT_EXCEPTIONS_HPP + +#include + +namespace jwt { + +/** + */ +class MemoryAllocationException final: public std::bad_alloc +{ +public: + /** + * Construct MemoryAllocationException from a + * string literal. + */ + template + MemoryAllocationException(const char(&msg)[N]) + : msg_(&msg[0]) + { + } + + virtual const char* what() const noexcept override + { + return msg_; + } + +private: + const char* msg_ = nullptr; +}; + + +} // END namespace jwt + +#endif diff --git a/include/jwt/impl/algorithm.ipp b/include/jwt/impl/algorithm.ipp index 0eb4480..16b5a7e 100644 --- a/include/jwt/impl/algorithm.ipp +++ b/include/jwt/impl/algorithm.ipp @@ -12,6 +12,7 @@ verify_result_t HMACSign::verify( const string_view jwt_sign) { std::error_code ec{}; + //TODO: remove these static deletors. static auto bio_deletor = [](BIO* ptr) { if (ptr) BIO_free_all(ptr); }; @@ -77,21 +78,27 @@ verify_result_t HMACSign::verify( } template -EVP_PKEY* PEMSign::load_key(const string_view key) +EVP_PKEY* PEMSign::load_key( + const string_view key, + std::error_code& ec) { - auto bio_deletor = [](BIO* ptr) { + static auto bio_deletor = [](BIO* ptr) { if (ptr) BIO_free(ptr); }; + ec.clear(); + std::unique_ptr bio_ptr{BIO_new_mem_buf((void*)key.data(), key.length()), bio_deletor}; if (!bio_ptr) { - return nullptr; + throw MemoryAllocationException("BIO_new_mem_buf failed"); } EVP_PKEY* pkey = PEM_read_bio_PrivateKey(bio_ptr.get(), nullptr, nullptr, nullptr); + if (!pkey) { + ec = AlgorithmErrc::SigningErr; return nullptr; } @@ -104,36 +111,37 @@ std::string PEMSign::evp_digest( const string_view data, std::error_code& ec) { - auto md_deletor = [](EVP_MD_CTX* ptr) { + static auto md_deletor = [](EVP_MD_CTX* ptr) { if (ptr) EVP_MD_CTX_destroy(ptr); }; + ec.clear(); + std::unique_ptr mdctx_ptr{EVP_MD_CTX_create(), md_deletor}; if (!mdctx_ptr) { - //TODO: set appropriate error_code - return std::string{}; + throw MemoryAllocationException("EVP_MD_CTX_create failed"); } //Initialiaze the digest algorithm if (EVP_DigestSignInit( mdctx_ptr.get(), nullptr, Hasher{}(), nullptr, pkey) != 1) { - //TODO: set appropriate error_code - return std::string{}; + ec = AlgorithmErrc::SigningErr; + return {}; } //Update the digest with the input data if (EVP_DigestSignUpdate(mdctx_ptr.get(), data.data(), data.length()) != 1) { - //TODO: set appropriate error_code + ec = AlgorithmErrc::SigningErr; return std::string{}; } unsigned long len = 0; if (EVP_DigestSignFinal(mdctx_ptr.get(), nullptr, &len) != 1) { - //TODO: set appropriate error_code - return std::string{}; + ec = AlgorithmErrc::SigningErr; + return {}; } std::string sign; @@ -141,8 +149,8 @@ std::string PEMSign::evp_digest( //Get the signature if (EVP_DigestSignFinal(mdctx_ptr.get(), (unsigned char*)&sign[0], &len) != 1) { - //TODO: set appropriate error_code - return std::string{}; + ec = AlgorithmErrc::SigningErr; + return {}; } return sign; @@ -157,6 +165,7 @@ std::string PEMSign::public_key_ser( // Get the EC_KEY representing a public key and // (optionaly) an associated private key std::string new_sign; + ec.clear(); static auto eckey_deletor = [](EC_KEY* ptr) { if (ptr) EC_KEY_free(ptr); @@ -170,8 +179,8 @@ std::string PEMSign::public_key_ser( ec_key{EVP_PKEY_get1_EC_KEY(pkey), eckey_deletor}; if (!ec_key) { - //TODO set a valid error code - return std::string{}; + ec = AlgorithmErrc::SigningErr; + return {}; } uint32_t degree = EC_GROUP_get_degree(EC_KEY_get0_group(ec_key.get())); @@ -183,8 +192,8 @@ std::string PEMSign::public_key_ser( ecsig_deletor}; if (!ec_sig) { - //TODO set a valid error code - return std::string{}; + ec = AlgorithmErrc::SigningErr; + return {}; } const BIGNUM* ec_sig_r = nullptr; @@ -192,7 +201,7 @@ std::string PEMSign::public_key_ser( #if 1 //Taken from https://github.com/nginnever/zogminer/issues/39 - auto ECDSA_SIG_get0 = [](const ECDSA_SIG *sig, const BIGNUM **pr, const BIGNUM **ps) + static auto ECDSA_SIG_get0 = [](const ECDSA_SIG *sig, const BIGNUM **pr, const BIGNUM **ps) { if (pr != nullptr) *pr = sig->r; if (ps != nullptr) *ps = sig->s; @@ -207,8 +216,8 @@ std::string PEMSign::public_key_ser( auto bn_len = (degree + 7) / 8; if ((r_len > bn_len) || (s_len > bn_len)) { - //TODO set a valid error code - return std::string{}; + ec = AlgorithmErrc::SigningErr; + return {}; } auto buf_len = 2 * bn_len; diff --git a/include/jwt/impl/error_codes.ipp b/include/jwt/impl/error_codes.ipp new file mode 100644 index 0000000..7b64276 --- /dev/null +++ b/include/jwt/impl/error_codes.ipp @@ -0,0 +1,48 @@ +#ifndef CPP_JWT_ERROR_CODES_IPP +#define CPP_JWT_ERROR_CODES_IPP + +namespace jwt { +// Anonymous namespace +namespace { + +/** + */ +struct AlgorithmErrCategory: std::error_category +{ + const char* name() const noexcept override + { + return "algorithms"; + } + + std::string message(int ev) const override + { + switch (static_cast(ev)) + { + case AlgorithmErrc::SigningErr: + return "signing failed"; + case AlgorithmErrc::VerificationErr: + return "verification failed"; + case AlgorithmErrc::NoneAlgorithmUsed: + return "none algorithm used"; + }; + + assert (0 && "Code not reached"); + } +}; + +// Create global object for the error categories +const AlgorithmErrCategory theAlgorithmErrCategory {}; + +} + + +// Create the AlgorithmErrc error code +std::error_code make_error_code(AlgorithmErrc err) +{ + return { static_cast(err), theAlgorithmErrCategory }; +} + + +} // END namespace jwt + +#endif diff --git a/include/jwt/test/test_jwt_object b/include/jwt/test/test_jwt_object index 432dd74..c625eff 100755 Binary files a/include/jwt/test/test_jwt_object and b/include/jwt/test/test_jwt_object differ