Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add client assertion restrictions, and code tidy up #423

Merged
merged 3 commits into from
Dec 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Development/cmake/NmosCppTest.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions Development/nmos/authorization_behaviour.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
};

Expand Down
4 changes: 2 additions & 2 deletions Development/nmos/client_utils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -296,10 +296,10 @@ namespace nmos
{
slog::log<slog::severities::too_much_info>(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<std::chrono::microseconds>(response_start - start_time).count() / 1000.0;

// see https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Server-Timing
Expand Down
19 changes: 17 additions & 2 deletions Development/nmos/jwt_generator_impl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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")
Expand All @@ -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);
}
};
Expand Down
69 changes: 69 additions & 0 deletions Development/nmos/test/jwt_generator_test.cpp
Original file line number Diff line number Diff line change
@@ -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));
}
Loading