diff --git a/include/jwt/error_codes.hpp b/include/jwt/error_codes.hpp index 66a1fe9..4472843 100644 --- a/include/jwt/error_codes.hpp +++ b/include/jwt/error_codes.hpp @@ -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, }; /** diff --git a/include/jwt/exceptions.hpp b/include/jwt/exceptions.hpp index 105637e..8d53483 100644 --- a/include/jwt/exceptions.hpp +++ b/include/jwt/exceptions.hpp @@ -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 diff --git a/include/jwt/impl/error_codes.ipp b/include/jwt/impl/error_codes.ipp index 690ba7e..8aebeba 100644 --- a/include/jwt/impl/error_codes.ipp +++ b/include/jwt/impl/error_codes.ipp @@ -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"); diff --git a/include/jwt/impl/jwt.ipp b/include/jwt/impl/jwt.ipp index b6161fa..41257bb 100644 --- a/include/jwt/impl/jwt.ipp +++ b/include/jwt/impl/jwt.ipp @@ -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(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(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(args)...); } +template +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(args)...); +} + +template +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(args)...); +} + +template +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(args)...); +} + template 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"); }; diff --git a/include/jwt/jwt.hpp b/include/jwt/jwt.hpp index 0933905..adb2741 100644 --- a/include/jwt/jwt.hpp +++ b/include/jwt/jwt.hpp @@ -41,6 +41,7 @@ SOFTWARE. // For convenience using json_t = nlohmann::json; using system_time_t = std::chrono::time_point; +namespace json_ns = nlohmann; namespace jwt { @@ -1024,6 +1025,15 @@ public: //TODO: Not good template static void set_decode_params(DecodeParams& dparams, params::detail::audience_param a, Rest&&... args); + template + static void set_decode_params(DecodeParams& dparams, params::detail::subject_param a, Rest&&... args); + + template + static void set_decode_params(DecodeParams& dparams, params::detail::validate_iat_param v, Rest&&... args); + + template + static void set_decode_params(DecodeParams& dparams, params::detail::validate_jti_param v, Rest&&... args); + template 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 jwt_object decode(const jwt::string_view enc_str, diff --git a/include/jwt/parameters.hpp b/include/jwt/parameters.hpp index 2f5977c..3e4c4d3 100644 --- a/include/jwt/parameters.hpp +++ b/include/jwt/parameters.hpp @@ -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 diff --git a/tests/test_jwt_decode_verifiy.cc b/tests/test_jwt_decode_verifiy.cc index 660ee9e..0c2c516 100644 --- a/tests/test_jwt_decode_verifiy.cc +++ b/tests/test_jwt_decode_verifiy.cc @@ -167,6 +167,20 @@ TEST (DecodeVerify, InvalidAudienceTest) EXPECT_EQ (ec.value(), static_cast(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(jwt::VerificationErrc::TypeConversionError)); +} + int main(int argc, char* argv[]) { ::testing::InitGoogleTest(&argc, argv); return RUN_ALL_TESTS(); diff --git a/tests/test_jwt_decode_verifiy_with_exception.cc b/tests/test_jwt_decode_verifiy_with_exception.cc index db93b40..ec0ce9a 100644 --- a/tests/test_jwt_decode_verifiy_with_exception.cc +++ b/tests/test_jwt_decode_verifiy_with_exception.cc @@ -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(); diff --git a/tests/test_jwt_encode b/tests/test_jwt_encode index 8e4276a..25084e8 100755 Binary files a/tests/test_jwt_encode and b/tests/test_jwt_encode differ