diff --git a/Development/cmake/NmosCppTest.cmake b/Development/cmake/NmosCppTest.cmake index 8dc26b18..31fa6c21 100644 --- a/Development/cmake/NmosCppTest.cmake +++ b/Development/cmake/NmosCppTest.cmake @@ -47,6 +47,7 @@ set(NMOS_CPP_TEST_NMOS_TEST_SOURCES nmos/test/did_sdid_test.cpp nmos/test/event_type_test.cpp nmos/test/json_validator_test.cpp + nmos/test/jwt_generator_test.cpp nmos/test/jwt_validation_test.cpp nmos/test/paging_utils_test.cpp nmos/test/query_api_test.cpp diff --git a/Development/nmos/authorization_behaviour.cpp b/Development/nmos/authorization_behaviour.cpp index f5bdeead..d5e77cdd 100644 --- a/Development/nmos/authorization_behaviour.cpp +++ b/Development/nmos/authorization_behaviour.cpp @@ -162,8 +162,8 @@ namespace nmos { return false; } - auto now = std::chrono::system_clock::now(); - auto exp = std::chrono::system_clock::from_time_t(expires_at); + const auto now = std::chrono::system_clock::now(); + const auto exp = std::chrono::system_clock::from_time_t(expires_at); return (now > exp); }; diff --git a/Development/nmos/client_utils.cpp b/Development/nmos/client_utils.cpp index 681c520d..bce7d958 100644 --- a/Development/nmos/client_utils.cpp +++ b/Development/nmos/client_utils.cpp @@ -296,10 +296,10 @@ namespace nmos { slog::log(gate, SLOG_FLF) << "Sending request"; // see https://developer.mozilla.org/en-US/docs/Web/API/Resource_Timing_API#Resource_loading_timestamps - const auto start_time = std::chrono::system_clock::now(); + const auto start_time = std::chrono::steady_clock::now(); return client.request(request, token).then([start_time, &gate](web::http::http_response res) { - const auto response_start = std::chrono::system_clock::now(); + const auto response_start = std::chrono::steady_clock::now(); const auto request_dur = std::chrono::duration_cast(response_start - start_time).count() / 1000.0; // see https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Server-Timing diff --git a/Development/nmos/jwt_generator_impl.cpp b/Development/nmos/jwt_generator_impl.cpp index b03b3e48..300c82f8 100644 --- a/Development/nmos/jwt_generator_impl.cpp +++ b/Development/nmos/jwt_generator_impl.cpp @@ -17,6 +17,8 @@ namespace nmos static utility::string_t create_client_assertion(const utility::string_t& issuer, const utility::string_t& subject, const web::uri& audience, const std::chrono::seconds& token_lifetime, const utility::string_t& public_key, const utility::string_t& private_key, const utility::string_t& keyid) { using namespace jwt::traits; + + const auto now = std::chrono::system_clock::now(); // use server private key to create client_assertion (JWT) // where client_assertion MUST including iss, sub, aud, exp, and may including jti @@ -26,8 +28,8 @@ namespace nmos .set_issuer(utility::us2s(issuer)) .set_subject(utility::us2s(subject)) .set_audience(utility::us2s(audience.to_string())) - .set_issued_at(std::chrono::system_clock::now()) - .set_expires_at(std::chrono::system_clock::now() + token_lifetime) + .set_issued_at(now) + .set_expires_at(now + token_lifetime) .set_id(utility::us2s(nmos::make_id())) .set_key_id(utility::us2s(keyid)) .set_type("JWT") @@ -36,6 +38,19 @@ namespace nmos static utility::string_t create_client_assertion(const utility::string_t& issuer, const utility::string_t& subject, const web::uri& audience, const std::chrono::seconds& token_lifetime, const utility::string_t& private_key, const utility::string_t& keyid) { + // see https://tools.ietf.org/html/rfc7523#section-3 + if (issuer.empty()) + { + throw jwk_exception("empty issuer"); + } + if (subject.empty()) + { + throw jwk_exception("empty subject"); + } + if (audience.is_empty()) + { + throw jwk_exception("empty audience"); + } return create_client_assertion(issuer, subject, audience, token_lifetime, rsa_public_key(private_key), private_key, keyid); } }; diff --git a/Development/nmos/test/jwt_generator_test.cpp b/Development/nmos/test/jwt_generator_test.cpp new file mode 100644 index 00000000..e2b1050d --- /dev/null +++ b/Development/nmos/test/jwt_generator_test.cpp @@ -0,0 +1,69 @@ +// The first "test" is of course whether the header compiles standalone +#include "nmos/jwt_generator.h" + +#include "bst/test/test.h" +#include "cpprest/basic_utils.h" +#include "nmos/jwk_utils.h" // for nmos::experimental::jwk_exception + +namespace +{ + // this is the private key (rsa.api.testsuite.nmos.tv.key.pem) from the nmos-testing + // https://https://github.com/AMWA-TV/nmos-testing/blob/master/test_data/BCP00301/ca/intermediate/private/rsa.api.testsuite.nmos.tv.key.pem + const auto test_private_key = utility::s2us(R"(-----BEGIN RSA PRIVATE KEY----- +MIIEogIBAAKCAQEAyXHgphlqcINx+ZKkBefDo5X5rHUuTpom9OcRKpWQHt7oYUr1 +UhoKJ+8SxbsSvtlrvvGa6kiSk/m6i7haU9dGKSDJzndYJSi+Qbc2jfSPfoHtHvsy +PIworhKniDA7YNE+olr23KGYSqdWidp3nzQLdaHuvOqjjjb3Jm2hvdt4Rfyk8r90 +5FY1kdZ/rINtvUDNHZnno9xPw9Hk+xc/cfOJyLUxBndy5wSp7Dhl8Wg1tLuK0rIG +JuFBFrZWykGySGP8s3KzeSeugojYa4JWoXFix6+hlTOfyyu5VXtDkTIZotXcAOBl +EEFLNtSko0yzWuSDo1HF0IwwCvgmwFnewdgFGQIDAQABAoIBAGnZ2ebNsh1/JHO0 +91VXDHk4BGL3jCanX9MOW/nZb0qZbNg68B99KVsEiAO4okgArVo/UFzNV6BD+B8U +9vnZQ7e2z/QayAl2mEqlwBflq0UZdoTyD9q692FI0hmA5qKgMN5VGCSlEQYhWhrD +3lmcmmzscyt3zAudnE7oCrZdzZxQD1r2dgjuLFiSaueUHPS/7FQavMx5sGGowcmN +ex+nEHEBeRn5Ws7NWKtX6UoFa8btJIapsqkLHgtXgEsrbFDfLXbEgdFgXg36e8Ak +lCuzUsehaM56eesVNyT/4FVN9ilJu0I1OlJs7sNXOIR4v//PqWoEnEGV/Fwb0YPT +YWPr81ECgYEA7jIIQD9TKYdBdR2A5b8AmRAuVpYsC36Cw6n/3Mq/Le1Bf0nj1cVf +JBkmLOYO/41h6Z+kITJfUZMniy6a/D7LXMSd/jwJM7WKLBfM/AIQHsSpIo/bkW/Q +zVa2inDLYnICWAuR2KNWR46CWy6wnjlWdag76YYJxlk91vpy6RqUCtUCgYEA2ICb +fBp5DNayESM7zDjh8PvgMn49tj6BjuO+oJ7vycQzDlgp/NV2kwtRpNQUcT4wAvTJ +W/GHOlUTxIDxhZqJu3Ix5ue/R0YprhlZofSzwXVoqh+NvZGi4UYiJkTr7zXhzoU1 +PV5vWb1YMqpiYJxA3BRj3K0YdVmPkdcoLsXgKzUCgYAuBh7QAyxXbtn3/h5kxfYg +nR7G/jc+dVBg7B0TFV3BSwGHzcgnCv7qI63bqQwm1rOfh4gYHfqK8YsHepbZvGxg +3WDFueXxRteO0355BxEEUO15TyCWxmsq8eFNeKPjvrGzP3EL0eue4etQIQJhYCTT +kREaexqyZ5XqTvQbFFacjQKBgEhC1KKda12/ovtZWTIWokL+rpvryskzH6cDmLKf +mcUsOSZGgu0iiksV8hAjwRby/K9f6H1JpirwDoL9zp8bL3Fi8gjxvMQbRPoY9/O4 +au7dMyvlEDf/je/Gqss/IchboZx+lYCALoYzTmbKu78nJ/bMz2/uTkWMuQCiYYUL +AoEpAoGAYIG2aCsuLV+1bPHC0vYvDC0V+NMA8e9HrplHdrQ4IBxyZnmHqvrGZ+04 +huUpDxAX+hxYap0k0RPJ3HaFmIHz7DpStX/aIjcfucEnoev7OLj4/3j6s2tHfeWL +JP+1+v/YSIKc7WXvz95YsmoJZ02Ikv8zBan9HIzczmkqDe0C1RQ= +-----END RSA PRIVATE KEY----- +)"); + +} + +BST_TEST_CASE(testClientAssertion) +{ + using namespace nmos::experimental; + + const utility::string_t issuer{ U("api.testsuite.nmos.tv") }; + const utility::string_t subject{ U("api.testsuite.nmos.tv") }; + const web::uri audience{ U("https://mocks.testsuite.nmos.tv:5010/testtoken") }; + const std::chrono::seconds token_lifetime{ 100 }; + const utility::string_t private_key{ test_private_key }; + const utility::string_t keyid{ U("key_1") }; + + // bad cases + const utility::string_t bad_issuer{}; + BST_REQUIRE_THROW(jwt_generator::create_client_assertion(bad_issuer, subject, audience, token_lifetime, private_key, keyid), nmos::experimental::jwk_exception); + + const utility::string_t bad_subject{}; + BST_REQUIRE_THROW(jwt_generator::create_client_assertion(issuer, bad_subject, audience, token_lifetime, private_key, keyid), nmos::experimental::jwk_exception); + + const utility::string_t bad_audience{}; + BST_REQUIRE_THROW(jwt_generator::create_client_assertion(issuer, subject, bad_audience, token_lifetime, private_key, keyid), nmos::experimental::jwk_exception); + + const utility::string_t bad_private_key{}; + BST_REQUIRE_THROW(jwt_generator::create_client_assertion(issuer, subject, audience, token_lifetime, bad_private_key, keyid), nmos::experimental::jwk_exception); + + // good case + BST_REQUIRE_NO_THROW(jwt_generator::create_client_assertion(issuer, subject, audience, token_lifetime, private_key, keyid)); +}