feat(client): Check if the "CreatedAt" timestamp of the SecurityToken matches the current system clock - consistently use monotonic clock internally

This commit is contained in:
Julius Pfrommer 2025-02-03 21:01:35 +01:00 committed by Julius Pfrommer
parent aaf6ebdf99
commit 6a0ed7d623
2 changed files with 42 additions and 7 deletions

View File

@ -531,13 +531,6 @@ processOPNResponse(UA_Client *client, const UA_ByteString *message) {
return;
}
/* Response.securityToken.revisedLifetime is UInt32 we need to cast it to
* DateTime=Int64 we take 75% of lifetime to start renewing as described in
* standard */
client->nextChannelRenewal = UA_DateTime_nowMonotonic()
+ (UA_DateTime) (response.securityToken.revisedLifetime
* (UA_Double) UA_DATETIME_MSEC * 0.75);
/* Move the nonce out of the response */
UA_ByteString_clear(&client->channel.remoteNonce);
client->channel.remoteNonce = response.serverNonce;
@ -550,6 +543,29 @@ processOPNResponse(UA_Client *client, const UA_ByteString *message) {
client->channel.securityToken = response.securityToken;
client->channel.renewState = UA_SECURECHANNELRENEWSTATE_NEWTOKEN_CLIENT;
/* Log a warning if the SecurityToken is not "fresh". Use the normal system
* clock to do the comparison. */
UA_EventLoop *el = client->config.eventLoop;
UA_DateTime wallClockNow = el->dateTime_now(el);
if(wallClockNow - client->channel.securityToken.createdAt >= UA_DATETIME_SEC * 10 ||
wallClockNow - client->channel.securityToken.createdAt <= -UA_DATETIME_SEC * 10)
UA_LOG_WARNING_CHANNEL(client->config.logging, &client->channel, "The \"CreatedAt\" "
"timestamp of the received ChannelSecurityToken does not match "
"with the local system clock");
/* The internal "monotonic" clock is used by the SecureChannel to validate
* that the SecurityToken is still valid. The monotonic clock is independent
* from the system clock getting changed or synchronized to a master clock
* during runtime. */
client->channel.securityToken.createdAt = el->dateTime_nowMonotonic(el);
/* Response.securityToken.revisedLifetime is UInt32, we need to cast it to
* DateTime=Int64. After 75% of the lifetime the renewal takes place as
* described in standard */
client->nextChannelRenewal = client->channel.securityToken.createdAt +
(UA_DateTime) (response.securityToken.revisedLifetime *
(UA_Double) UA_DATETIME_MSEC * 0.75);
/* Compute the new local keys. The remote keys are updated when a message
* with the new SecurityToken is received. */
retval = UA_SecureChannel_generateLocalKeys(&client->channel);

View File

@ -255,6 +255,24 @@ START_TEST(SecureChannel_serverCert) {
}
END_TEST
/* The monotonic clock is 24h in the future */
static UA_DateTime
dateTime_nowMonotonicWithOffset(UA_EventLoop *el) {
return UA_DateTime_now() + (UA_DATETIME_SEC * 24 * 3600);
}
/* Simulate a deviation between the "wallclock" and the monotonic clock */
START_TEST(SecureChannel_differentMonotonicClock) {
UA_Client *client = UA_Client_new();
UA_ClientConfig_setDefault(UA_Client_getConfig(client));
UA_StatusCode retval = UA_Client_connect(client, "opc.tcp://localhost:4840");
ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
UA_Client_delete(client);
}
END_TEST
int main(void) {
TCase *tc_sc = tcase_create("Client SecureChannel");
tcase_add_checked_fixture(tc_sc, setup, teardown);
@ -265,6 +283,7 @@ int main(void) {
tcase_add_test(tc_sc, SecureChannel_reconnect);
tcase_add_test(tc_sc, SecureChannel_cableunplugged);
tcase_add_test(tc_sc, SecureChannel_serverCert);
tcase_add_test(tc_sc, SecureChannel_differentMonotonicClock);
Suite *s = suite_create("Client");
suite_add_tcase(s, tc_sc);