in source/core_sntp_client.c [629:736]
static SntpStatus_t processServerResponse( SntpContext_t * pContext,
const SntpTimestamp_t * pResponseRxTime )
{
SntpStatus_t status = SntpSuccess;
const SntpServerInfo_t * pServer = &pContext->pTimeServers[ pContext->currentServerIndex ];
assert( pContext != NULL );
assert( pResponseRxTime != NULL );
if( pContext->authIntf.validateServerAuth != NULL )
{
/* Verify the server from the authentication data in the SNTP response packet. */
status = pContext->authIntf.validateServerAuth( pContext->authIntf.pAuthContext,
pServer,
pContext->pNetworkBuffer,
pContext->sntpPacketSize );
assert( ( status == SntpSuccess ) || ( status == SntpErrorAuthFailure ) ||
( status == SntpServerNotAuthenticated ) );
if( status != SntpSuccess )
{
LogError( ( "Unable to use server response: Server authentication function failed: "
"ReturnStatus=%s", Sntp_StatusToStr( status ) ) );
}
else
{
LogDebug( ( "Server response has been validated: Server=%.*s", ( int ) pServer->serverNameLen, pServer->pServerName ) );
}
}
if( status == SntpSuccess )
{
SntpResponseData_t parsedResponse;
/* De-serialize response packet to determine whether the server accepted or rejected
* the request for time. Also, calculate the system clock offset if the server responded
* with time. */
status = Sntp_DeserializeResponse( &pContext->lastRequestTime,
pResponseRxTime,
pContext->pNetworkBuffer,
pContext->sntpPacketSize,
&parsedResponse );
/* We do not expect the following errors to be returned as the context
* has been validated in the Sntp_ReceiveTimeResponse API. */
assert( status != SntpErrorBadParameter );
assert( status != SntpErrorBufferTooSmall );
if( ( status == SntpRejectedResponseChangeServer ) ||
( status == SntpRejectedResponseRetryWithBackoff ) ||
( status == SntpRejectedResponseOtherCode ) )
{
/* Server has rejected the time request. Thus, we will rotate to the next time server
* in the list. */
rotateServerForNextTimeQuery( pContext );
LogError( ( "Unable to use server response: Server has rejected request for time: RejectionCode=%.*s",
( int ) SNTP_KISS_OF_DEATH_CODE_LENGTH, ( char * ) &parsedResponse.rejectedResponseCode ) );
status = SntpRejectedResponse;
}
else if( status == SntpInvalidResponse )
{
LogError( ( "Unable to use server response: Server response failed sanity checks." ) );
}
else
{
/* Server has responded successfully with time, and we have calculated the clock offset
* of system clock relative to the server.*/
LogDebug( ( "Updating system time: ServerTime=%u %ums ClockOffset=%lums",
parsedResponse.serverTime.seconds, FRACTIONS_TO_MS( parsedResponse.serverTime.fractions ),
parsedResponse.clockOffsetMs ) );
/* Update the system clock with the calculated offset. */
pContext->setTimeFunc( pServer, &parsedResponse.serverTime,
parsedResponse.clockOffsetMs, parsedResponse.leapSecondType );
status = SntpSuccess;
}
}
/* Reset the last request time state in context to protect against replay attacks.
* Note: The last request time is not cleared when a rejection response packet is received and the client does
* has not authenticated server from the response. This is because clearing of the state causes the coreSNTP
* library to discard any subsequent server response packets (as the "originate timestamp" of those packets will
* not match the last request time value of the context), and thus, an attacker can cause Denial of Service
* attacks by spoofing server response before the actual server is able to respond.
*/
if( ( status == SntpSuccess ) ||
( ( pContext->authIntf.validateServerAuth != NULL ) && ( status == SntpRejectedResponse ) ) )
{
/* In the attack of SNTP request packet being replayed, the replayed request packet is serviced by
* SNTP/NTP server with SNTP response (as servers are stateless) and client receives the response
* containing new values of server timestamps but the stale value of "originate timestamp".
* To prevent the coreSNTP library from servicing such a server response (associated with the replayed
* SNTP request packet), the last request timestamp state is cleared in the context after receiving the
* first valid server response. Therefore, any subsequent server response(s) from replayed client request
* packets can be invalidated due to the "originate timestamp" not matching the last request time stored
* in the context.
* Note: If an attacker spoofs a server response with a zero "originate timestamp" after the coreSNTP
* library (i.e. the SNTP client) has cleared the internal state to zero, the spoofed packet will be
* discarded as the coreSNTP serializer does not accept server responses with zero value for timestamps.
*/
pContext->lastRequestTime.seconds = 0U;
pContext->lastRequestTime.fractions = 0U;
}
return status;
}