static SntpStatus_t processServerResponse()

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;
}