Added checks and tests for iat/jti/sub

This commit is contained in:
Arun M 2017-12-29 01:10:41 +05:30
parent babdd4f0a6
commit 7ddf7ec3e9
9 changed files with 284 additions and 3 deletions

View file

@ -79,9 +79,16 @@ enum class VerificationErrc
InvalidAlgorithm = 1,
TokenExpired,
InvalidIssuer,
InvalidSubject,
InvalidIAT,
//Checks for the existence of JTI
//if validate_jti is passed in decode
InvalidJTI,
InvalidAudience,
ImmatureSignature,
InvalidSignature,
// Invalid value type used for known claims
TypeConversionError,
};
/**

View file

@ -194,6 +194,54 @@ public:
}
};
/**
* Derived from VerificationError.
* Thrown when the subject claim does not match
* with the one provided as part of decode argument.
*/
class InvalidSubjectError final: public VerificationError
{
public:
/**
*/
InvalidSubjectError(std::string msg)
: VerificationError(std::move(msg))
{
}
};
/**
* Derived from VerificationError.
* Thrown when verify_iat parameter is passed to
* decode and IAT is not present.
*/
class InvalidIATError final: public VerificationError
{
public:
/**
*/
InvalidIATError(std::string msg)
: VerificationError(std::move(msg))
{
}
};
/**
* Derived from VerificationError.
* Thrown when validate_jti is asked for
* in decode and jti claim is not present.
*/
class InvalidJTIError final: public VerificationError
{
public:
/**
*/
InvalidJTIError(std::string msg)
: VerificationError(std::move(msg))
{
}
};
/**
* Derived from VerificationError.
* Thrown when the token is decoded at a time before
@ -225,6 +273,22 @@ public:
}
};
/**
* Derived from VerificationError.
* Thrown when there type expectation mismatch
* while verifying the values of registered claim names.
*/
class TypeConversionError final: public VerificationError
{
public:
/**
*/
TypeConversionError(std::string msg)
: VerificationError(std::move(msg))
{
}
};
} // END namespace jwt
#endif

View file

@ -110,12 +110,20 @@ struct VerificationErrorCategory: std::error_category
return "token expired";
case VerificationErrc::InvalidIssuer:
return "invalid issuer";
case VerificationErrc::InvalidSubject:
return "invalid subject";
case VerificationErrc::InvalidAudience:
return "invalid audience";
case VerificationErrc::InvalidIAT:
return "invalid iat";
case VerificationErrc::InvalidJTI:
return "invalid jti";
case VerificationErrc::ImmatureSignature:
return "immature signature";
case VerificationErrc::InvalidSignature:
return "invalid signature";
case VerificationErrc::TypeConversionError:
return "type conversion error";
};
assert (0 && "Code not reached");

View file

@ -455,6 +455,23 @@ std::error_code jwt_object::verify(
}
}
//Check the subject
if (dparams.has_sub)
{
if (has_claim(registered_claims::subject))
{
jwt::string_view p_sub = payload()
.get_claim_value<std::string>(registered_claims::subject);
if (p_sub.data() != dparams.sub) {
ec = VerificationErrc::InvalidSubject;
return ec;
}
} else {
ec = VerificationErrc::InvalidSubject;
return ec;
}
}
//Check for NBF
if (has_claim(registered_claims::not_before))
{
@ -471,6 +488,27 @@ std::error_code jwt_object::verify(
}
}
//Check IAT validation
if (dparams.validate_iat) {
if (!has_claim(registered_claims::issued_at)) {
ec = VerificationErrc::InvalidIAT;
return ec;
} else {
// Will throw type conversion error
auto val = payload()
.get_claim_value<uint64_t>(registered_claims::issued_at);
(void)val;
}
}
//Check JTI validation
if (dparams.validate_jti) {
if (!has_claim("jti")) {
ec = VerificationErrc::InvalidJTI;
return ec;
}
}
return ec;
}
@ -534,6 +572,28 @@ void jwt_object::set_decode_params(DecodeParams& dparams, params::detail::audien
jwt_object::set_decode_params(dparams, std::forward<Rest>(args)...);
}
template <typename DecodeParams, typename... Rest>
void jwt_object::set_decode_params(DecodeParams& dparams, params::detail::subject_param s, Rest&&... args)
{
dparams.sub = std::move(s).get();
dparams.has_sub = true;
jwt_object::set_decode_params(dparams, std::forward<Rest>(args)...);
}
template <typename DecodeParams, typename... Rest>
void jwt_object::set_decode_params(DecodeParams& dparams, params::detail::validate_iat_param v, Rest&&... args)
{
dparams.validate_iat = v.get();
jwt_object::set_decode_params(dparams, std::forward<Rest>(args)...);
}
template <typename DecodeParams, typename... Rest>
void jwt_object::set_decode_params(DecodeParams& dparams, params::detail::validate_jti_param v, Rest&&... args)
{
dparams.validate_jti = v.get();
jwt_object::set_decode_params(dparams, std::forward<Rest>(args)...);
}
template <typename DecodeParams>
void jwt_object::set_decode_params(DecodeParams& dparams)
{
@ -577,6 +637,17 @@ jwt_object decode(const jwt::string_view enc_str,
//TODO: optional type
bool has_aud = false;
std::string aud;
//The subject
//TODO: optional type
bool has_sub = false;
std::string sub;
//Validate IAT
bool validate_iat = false;
//Validate JTI
bool validate_jti = false;
};
decode_params dparams{};
@ -624,7 +695,11 @@ jwt_object decode(const jwt::string_view enc_str,
obj.payload(std::move(payload));
if (dparams.verify) {
ec = obj.verify(dparams, algos);
try {
ec = obj.verify(dparams, algos);
} catch (const json_ns::detail::type_error& e) {
ec = VerificationErrc::TypeConversionError;
}
if (ec) return obj;
}
@ -705,6 +780,18 @@ void jwt_throw_exception(const std::error_code& ec)
{
throw InvalidAudienceError(ec.message());
}
case VerificationErrc::InvalidSubject:
{
throw InvalidSubjectError(ec.message());
}
case VerificationErrc::InvalidIAT:
{
throw InvalidIATError(ec.message());
}
case VerificationErrc::InvalidJTI:
{
throw InvalidJTIError(ec.message());
}
case VerificationErrc::ImmatureSignature:
{
throw ImmatureSignatureError(ec.message());
@ -713,6 +800,10 @@ void jwt_throw_exception(const std::error_code& ec)
{
throw InvalidSignatureError(ec.message());
}
case VerificationErrc::TypeConversionError:
{
throw TypeConversionError(ec.message());
}
default:
assert (0 && "Unknown error code");
};

View file

@ -41,6 +41,7 @@ SOFTWARE.
// For convenience
using json_t = nlohmann::json;
using system_time_t = std::chrono::time_point<std::chrono::system_clock>;
namespace json_ns = nlohmann;
namespace jwt {
@ -1024,6 +1025,15 @@ public: //TODO: Not good
template <typename DecodeParams, typename... Rest>
static void set_decode_params(DecodeParams& dparams, params::detail::audience_param a, Rest&&... args);
template <typename DecodeParams, typename... Rest>
static void set_decode_params(DecodeParams& dparams, params::detail::subject_param a, Rest&&... args);
template <typename DecodeParams, typename... Rest>
static void set_decode_params(DecodeParams& dparams, params::detail::validate_iat_param v, Rest&&... args);
template <typename DecodeParams, typename... Rest>
static void set_decode_params(DecodeParams& dparams, params::detail::validate_jti_param v, Rest&&... args);
template <typename DecodeParams>
static void set_decode_params(DecodeParams& dparams);
@ -1043,6 +1053,9 @@ private: // Data Members
* Decode the JWT signature to create the `jwt_object`.
* This version reports error back using `std::error_code`.
*
* If any of the registered claims are found in wrong format
* then sets TypeConversion error in the error_code.
*
* NOTE: Memory allocation exceptions are not caught.
*
* Optional parameters that can be passed:
@ -1062,6 +1075,14 @@ private: // Data Members
*
* 5. issuer: The issuer the client expects to be in the claim.
* NOTE: It is case-sensitive
*
* 6. sub: The subject the client expects to be in the claim.
*
* 7. validate_iat: Checks if IAT claim is present or not
* and the type is uint64_t or not. If claim is not present
* then set InvalidIAT error.
*
* 8. validate_jti: Checks if jti claim is present or not.
*/
template <typename SequenceT, typename... Args>
jwt_object decode(const jwt::string_view enc_str,

View file

@ -198,6 +198,46 @@ struct issuer_param
std::string iss_;
};
/**
*/
struct subject_param
{
subject_param(std::string sub)
: sub_(std::move(sub))
{}
const std::string& get() const& noexcept { return sub_; }
std::string get() && noexcept { return sub_; }
std::string sub_;
};
/**
*/
struct validate_iat_param
{
validate_iat_param(bool v)
: iat_(v)
{}
bool get() const noexcept { return iat_; }
bool iat_;
};
/**
*/
struct validate_jti_param
{
validate_jti_param(bool v)
: jti_(v)
{}
bool get() const noexcept { return jti_; }
bool jti_;
};
/**
*/
struct nbf_param
@ -336,7 +376,7 @@ algorithms(SequenceConcept&& sc)
/**
*/
detail::audience_param
aud(const string_view aud)
aud(const jwt::string_view aud)
{
return { aud.data() };
}
@ -344,11 +384,35 @@ aud(const string_view aud)
/**
*/
detail::issuer_param
issuer(const string_view iss)
issuer(const jwt::string_view iss)
{
return { iss.data() };
}
/**
*/
detail::subject_param
sub(const jwt::string_view subj)
{
return { subj.data() };
}
/**
*/
detail::validate_iat_param
validate_iat(bool v)
{
return { v };
}
/**
*/
detail::validate_jti_param
validate_jti(bool v)
{
return { v };
}
/**
*/
detail::nbf_param

View file

@ -167,6 +167,20 @@ TEST (DecodeVerify, InvalidAudienceTest)
EXPECT_EQ (ec.value(), static_cast<int>(jwt::VerificationErrc::InvalidAudience));
}
TEST (DecodeVerify, InvalidIATTest)
{
using namespace jwt::params;
jwt::jwt_object obj{algorithm("hs256"), secret("secret"), payload({{"sub", "test"}, {"aud", "www"}})};
obj.add_claim("iat", "what?");
auto enc_str = obj.signature();
std::error_code ec;
auto dec_obj = jwt::decode(enc_str, algorithms({"hs256"}), ec, secret("secret"), validate_iat(true));
EXPECT_EQ (ec.value(), static_cast<int>(jwt::VerificationErrc::TypeConversionError));
}
int main(int argc, char* argv[]) {
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();

View file

@ -164,6 +164,18 @@ TEST (DecodeVerifyExp, KeyNotPresentTest)
jwt::KeyNotPresentError);
}
TEST (DecodeVerifyExp, InvalidSubjectTest)
{
using namespace jwt::params;
jwt::jwt_object obj{algorithm("hs256"), secret("secret"), payload({{"sub", "test"}, {"aud", "www"}})};
auto enc_str = obj.signature();
EXPECT_THROW (jwt::decode(enc_str, algorithms({"hs256"}), secret("secret"), sub("TEST")),
jwt::InvalidSubjectError);
}
int main(int argc, char* argv[]) {
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();

Binary file not shown.