Add error codes for signing error checks

This commit is contained in:
Arun M 2017-11-23 12:27:26 +05:30
parent 2290c8733a
commit 53a281640f
6 changed files with 275 additions and 70 deletions

View file

@ -1,6 +1,13 @@
#ifndef CPP_JWT_ALGORITHM_HPP #ifndef CPP_JWT_ALGORITHM_HPP
#define 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 <cassert> #include <cassert>
#include <system_error> #include <system_error>
@ -12,7 +19,9 @@
#include <openssl/ecdsa.h> #include <openssl/ecdsa.h>
#include <openssl/buffer.h> #include <openssl/buffer.h>
#include "jwt/exceptions.hpp"
#include "jwt/string_view.hpp" #include "jwt/string_view.hpp"
#include "jwt/error_codes.hpp"
namespace jwt { namespace jwt {
@ -20,23 +29,25 @@ namespace jwt {
using sign_result_t = std::pair<std::string, std::error_code>; using sign_result_t = std::pair<std::string, std::error_code>;
/// The result type of verification function /// The result type of verification function
using verify_result_t = std::pair<bool, std::error_code>; using verify_result_t = std::pair<bool, std::error_code>;
/// 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, using sign_func_t = sign_result_t (*) (const string_view key,
const string_view data); const string_view data);
/// /// The function pointer type for the verifying function
using verify_func_t = verify_result_t (*) (const string_view key, using verify_func_t = verify_result_t (*) (const string_view key,
const string_view head, const string_view head,
const string_view jwt_sign); const string_view jwt_sign);
namespace algo { namespace algo {
//TODO: All these can be done using code generaion. //Me: TODO: All these can be done using code generaion.
// NO. NEVER. I hate Macros. //Me: NO. NEVER. I hate Macros.
// You can use templates too. //Me: You can use templates too.
// No. I would rather prefer explicit. //Me: No. I would rather prefer explicit.
// Ok. You win. //Me: Ok. You win.
//Me: Same to you.
/*! /**
* HS256 algorithm.
*/ */
struct HS256 struct HS256
{ {
@ -46,7 +57,8 @@ struct HS256
} }
}; };
/*! /**
* HS384 algorithm.
*/ */
struct HS384 struct HS384
{ {
@ -56,7 +68,8 @@ struct HS384
} }
}; };
/*! /**
* HS512 algorithm.
*/ */
struct HS512 struct HS512
{ {
@ -66,7 +79,8 @@ struct HS512
} }
}; };
/*! /**
* NONE algorithm.
*/ */
struct NONE struct NONE
{ {
@ -76,7 +90,8 @@ struct NONE
} }
}; };
/*! /**
* RS256 algorithm.
*/ */
struct RS256 struct RS256
{ {
@ -88,7 +103,8 @@ struct RS256
} }
}; };
/*! /**
* RS384 algorithm.
*/ */
struct RS384 struct RS384
{ {
@ -100,7 +116,8 @@ struct RS384
} }
}; };
/*! /**
* RS512 algorithm.
*/ */
struct RS512 struct RS512
{ {
@ -112,7 +129,8 @@ struct RS512
} }
}; };
/*! /**
* ES256 algorithm.
*/ */
struct ES256 struct ES256
{ {
@ -124,7 +142,8 @@ struct ES256
} }
}; };
/*! /**
* ES384 algorithm.
*/ */
struct ES384 struct ES384
{ {
@ -136,7 +155,8 @@ struct ES384
} }
}; };
/*! /**
* ES512 algorithm.
*/ */
struct ES512 struct ES512
{ {
@ -151,8 +171,8 @@ struct ES512
} //END Namespace algo } //END Namespace algo
/*! /**
* JWT signing algorithm. * JWT signing algorithm types.
*/ */
enum class algorithm 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 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"); 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 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 <typename Hasher> template <typename Hasher>
struct HMACSign struct HMACSign
@ -221,7 +252,18 @@ struct HMACSign
/// The type of Hashing algorithm /// The type of Hashing algorithm
using hasher_type = Hasher; 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) static sign_result_t sign(const string_view key, const string_view data)
{ {
@ -240,39 +282,55 @@ struct HMACSign
&len); &len);
if (!res) { 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 static verify_result_t
verify(const string_view key, const string_view head, const string_view sign); 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 <> template <>
struct HMACSign<algo::NONE> struct HMACSign<algo::NONE>
{ {
using hasher_type = algo::NONE; 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) static sign_result_t sign(const string_view key, const string_view data)
{ {
std::string sign; (void)key;
(void)data;
std::error_code ec{}; std::error_code ec{};
ec = AlgorithmErrc::NoneAlgorithmUsed;
//TODO: Set the appropriate error code for none return { std::string{}, ec };
return {sign, ec};
} }
/*! /**
*/ */
static verify_result_t static verify_result_t
verify(const string_view key, const string_view head, const string_view sign) verify(const string_view key, const string_view head, const string_view sign)
@ -281,13 +339,21 @@ struct HMACSign<algo::NONE>
std::error_code ec{}; std::error_code ec{};
//TODO: Set the appropriate error code for none //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 <typename Hasher> template <typename Hasher>
struct PEMSign struct PEMSign
@ -296,7 +362,17 @@ public:
/// The type of Hashing algorithm /// The type of Hashing algorithm
using hasher_type = Hasher; 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) static sign_result_t sign(const string_view key, const string_view data)
{ {
@ -307,27 +383,20 @@ public:
}; };
std::unique_ptr<EVP_PKEY, decltype(evpkey_deletor)> std::unique_ptr<EVP_PKEY, decltype(evpkey_deletor)>
pkey{load_key(key), evpkey_deletor}; pkey{load_key(key, ec), evpkey_deletor};
if (!pkey) { if (ec) return { std::string{}, ec };
//TODO: set valid error code
return {std::string{}, ec};
}
//TODO: Use stack string here ? //TODO: Use stack string here ?
std::string sign = evp_digest(pkey.get(), data, ec); 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) { if (ec) return { std::string{}, ec };
return {std::move(sign), ec};
} else { if (Hasher::type == EVP_PKEY_EC) {
sign = public_key_ser(pkey.get(), sign, 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: private:
/*! /*!
*/ */
static EVP_PKEY* load_key(const string_view key); static EVP_PKEY* load_key(const string_view key, std::error_code& ec);
/*! /*!
*/ */

View file

@ -0,0 +1,44 @@
#ifndef CPP_JWT_ERROR_CODES_HPP
#define CPP_JWT_ERROR_CODES_HPP
#include <system_error>
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<jwt::AlgorithmErrc> : true_type {};
}
#include "jwt/impl/error_codes.ipp"
#endif

View file

@ -0,0 +1,35 @@
#ifndef CPP_JWT_EXCEPTIONS_HPP
#define CPP_JWT_EXCEPTIONS_HPP
#include <new>
namespace jwt {
/**
*/
class MemoryAllocationException final: public std::bad_alloc
{
public:
/**
* Construct MemoryAllocationException from a
* string literal.
*/
template <size_t N>
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

View file

@ -12,6 +12,7 @@ verify_result_t HMACSign<Hasher>::verify(
const string_view jwt_sign) const string_view jwt_sign)
{ {
std::error_code ec{}; std::error_code ec{};
//TODO: remove these static deletors.
static auto bio_deletor = [](BIO* ptr) { static auto bio_deletor = [](BIO* ptr) {
if (ptr) BIO_free_all(ptr); if (ptr) BIO_free_all(ptr);
}; };
@ -77,21 +78,27 @@ verify_result_t HMACSign<Hasher>::verify(
} }
template <typename Hasher> template <typename Hasher>
EVP_PKEY* PEMSign<Hasher>::load_key(const string_view key) EVP_PKEY* PEMSign<Hasher>::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); if (ptr) BIO_free(ptr);
}; };
ec.clear();
std::unique_ptr<BIO, decltype(bio_deletor)> std::unique_ptr<BIO, decltype(bio_deletor)>
bio_ptr{BIO_new_mem_buf((void*)key.data(), key.length()), bio_deletor}; bio_ptr{BIO_new_mem_buf((void*)key.data(), key.length()), bio_deletor};
if (!bio_ptr) { 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); EVP_PKEY* pkey = PEM_read_bio_PrivateKey(bio_ptr.get(), nullptr, nullptr, nullptr);
if (!pkey) { if (!pkey) {
ec = AlgorithmErrc::SigningErr;
return nullptr; return nullptr;
} }
@ -104,36 +111,37 @@ std::string PEMSign<Hasher>::evp_digest(
const string_view data, const string_view data,
std::error_code& ec) 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); if (ptr) EVP_MD_CTX_destroy(ptr);
}; };
ec.clear();
std::unique_ptr<EVP_MD_CTX, decltype(md_deletor)> std::unique_ptr<EVP_MD_CTX, decltype(md_deletor)>
mdctx_ptr{EVP_MD_CTX_create(), md_deletor}; mdctx_ptr{EVP_MD_CTX_create(), md_deletor};
if (!mdctx_ptr) { if (!mdctx_ptr) {
//TODO: set appropriate error_code throw MemoryAllocationException("EVP_MD_CTX_create failed");
return std::string{};
} }
//Initialiaze the digest algorithm //Initialiaze the digest algorithm
if (EVP_DigestSignInit( if (EVP_DigestSignInit(
mdctx_ptr.get(), nullptr, Hasher{}(), nullptr, pkey) != 1) { mdctx_ptr.get(), nullptr, Hasher{}(), nullptr, pkey) != 1) {
//TODO: set appropriate error_code ec = AlgorithmErrc::SigningErr;
return std::string{}; return {};
} }
//Update the digest with the input data //Update the digest with the input data
if (EVP_DigestSignUpdate(mdctx_ptr.get(), data.data(), data.length()) != 1) { if (EVP_DigestSignUpdate(mdctx_ptr.get(), data.data(), data.length()) != 1) {
//TODO: set appropriate error_code ec = AlgorithmErrc::SigningErr;
return std::string{}; return std::string{};
} }
unsigned long len = 0; unsigned long len = 0;
if (EVP_DigestSignFinal(mdctx_ptr.get(), nullptr, &len) != 1) { if (EVP_DigestSignFinal(mdctx_ptr.get(), nullptr, &len) != 1) {
//TODO: set appropriate error_code ec = AlgorithmErrc::SigningErr;
return std::string{}; return {};
} }
std::string sign; std::string sign;
@ -141,8 +149,8 @@ std::string PEMSign<Hasher>::evp_digest(
//Get the signature //Get the signature
if (EVP_DigestSignFinal(mdctx_ptr.get(), (unsigned char*)&sign[0], &len) != 1) { if (EVP_DigestSignFinal(mdctx_ptr.get(), (unsigned char*)&sign[0], &len) != 1) {
//TODO: set appropriate error_code ec = AlgorithmErrc::SigningErr;
return std::string{}; return {};
} }
return sign; return sign;
@ -157,6 +165,7 @@ std::string PEMSign<Hasher>::public_key_ser(
// Get the EC_KEY representing a public key and // Get the EC_KEY representing a public key and
// (optionaly) an associated private key // (optionaly) an associated private key
std::string new_sign; std::string new_sign;
ec.clear();
static auto eckey_deletor = [](EC_KEY* ptr) { static auto eckey_deletor = [](EC_KEY* ptr) {
if (ptr) EC_KEY_free(ptr); if (ptr) EC_KEY_free(ptr);
@ -170,8 +179,8 @@ std::string PEMSign<Hasher>::public_key_ser(
ec_key{EVP_PKEY_get1_EC_KEY(pkey), eckey_deletor}; ec_key{EVP_PKEY_get1_EC_KEY(pkey), eckey_deletor};
if (!ec_key) { if (!ec_key) {
//TODO set a valid error code ec = AlgorithmErrc::SigningErr;
return std::string{}; return {};
} }
uint32_t degree = EC_GROUP_get_degree(EC_KEY_get0_group(ec_key.get())); uint32_t degree = EC_GROUP_get_degree(EC_KEY_get0_group(ec_key.get()));
@ -183,8 +192,8 @@ std::string PEMSign<Hasher>::public_key_ser(
ecsig_deletor}; ecsig_deletor};
if (!ec_sig) { if (!ec_sig) {
//TODO set a valid error code ec = AlgorithmErrc::SigningErr;
return std::string{}; return {};
} }
const BIGNUM* ec_sig_r = nullptr; const BIGNUM* ec_sig_r = nullptr;
@ -192,7 +201,7 @@ std::string PEMSign<Hasher>::public_key_ser(
#if 1 #if 1
//Taken from https://github.com/nginnever/zogminer/issues/39 //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 (pr != nullptr) *pr = sig->r;
if (ps != nullptr) *ps = sig->s; if (ps != nullptr) *ps = sig->s;
@ -207,8 +216,8 @@ std::string PEMSign<Hasher>::public_key_ser(
auto bn_len = (degree + 7) / 8; auto bn_len = (degree + 7) / 8;
if ((r_len > bn_len) || (s_len > bn_len)) { if ((r_len > bn_len) || (s_len > bn_len)) {
//TODO set a valid error code ec = AlgorithmErrc::SigningErr;
return std::string{}; return {};
} }
auto buf_len = 2 * bn_len; auto buf_len = 2 * bn_len;

View file

@ -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<AlgorithmErrc>(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<int>(err), theAlgorithmErrCategory };
}
} // END namespace jwt
#endif

Binary file not shown.