blob: fc6b4970ce08299b3f62849a764408cab7cdaffd [file] [log] [blame]
/*
*
* Copyright (c) 2021 Project CHIP Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <protocols/secure_channel/CASEServer.h>
#include <lib/core/CHIPError.h>
#include <lib/support/CHIPFaultInjection.h>
#include <lib/support/CodeUtils.h>
#include <lib/support/SafeInt.h>
#include <lib/support/logging/CHIPLogging.h>
#include <tracing/macros.h>
#include <transport/SessionManager.h>
using namespace ::chip::Inet;
using namespace ::chip::Transport;
using namespace ::chip::Credentials;
namespace chip {
CHIP_ERROR CASEServer::ListenForSessionEstablishment(Messaging::ExchangeManager * exchangeManager, SessionManager * sessionManager,
FabricTable * fabrics, SessionResumptionStorage * sessionResumptionStorage,
Credentials::CertificateValidityPolicy * certificateValidityPolicy,
Credentials::GroupDataProvider * responderGroupDataProvider)
{
VerifyOrReturnError(exchangeManager != nullptr, CHIP_ERROR_INVALID_ARGUMENT);
VerifyOrReturnError(sessionManager != nullptr, CHIP_ERROR_INVALID_ARGUMENT);
VerifyOrReturnError(responderGroupDataProvider != nullptr, CHIP_ERROR_INVALID_ARGUMENT);
mSessionManager = sessionManager;
mSessionResumptionStorage = sessionResumptionStorage;
mCertificateValidityPolicy = certificateValidityPolicy;
mFabrics = fabrics;
mExchangeManager = exchangeManager;
mGroupDataProvider = responderGroupDataProvider;
// Set up the group state provider that persists across all handshakes.
GetSession().SetGroupDataProvider(mGroupDataProvider);
ChipLogProgress(Inet, "CASE Server enabling CASE session setups");
mExchangeManager->RegisterUnsolicitedMessageHandlerForType(Protocols::SecureChannel::MsgType::CASE_Sigma1, this);
PrepareForSessionEstablishment();
return CHIP_NO_ERROR;
}
CHIP_ERROR CASEServer::InitCASEHandshake(Messaging::ExchangeContext * ec)
{
MATTER_TRACE_SCOPE("InitCASEHandshake", "CASEServer");
ReturnErrorCodeIf(ec == nullptr, CHIP_ERROR_INVALID_ARGUMENT);
// Hand over the exchange context to the CASE session.
ec->SetDelegate(&GetSession());
return CHIP_NO_ERROR;
}
CHIP_ERROR CASEServer::OnUnsolicitedMessageReceived(const PayloadHeader & payloadHeader, ExchangeDelegate *& newDelegate)
{
// TODO: assign newDelegate to CASESession, let CASESession handle future messages.
newDelegate = this;
return CHIP_NO_ERROR;
}
CHIP_ERROR CASEServer::OnMessageReceived(Messaging::ExchangeContext * ec, const PayloadHeader & payloadHeader,
System::PacketBufferHandle && payload)
{
MATTER_TRACE_SCOPE("OnMessageReceived", "CASEServer");
bool busy = GetSession().GetState() != CASESession::State::kInitialized;
CHIP_FAULT_INJECT(FaultInjection::kFault_CASEServerBusy, busy = true);
if (busy)
{
// We are in the middle of CASE handshake
// Invoke watchdog to fix any stuck handshakes
bool watchdogFired = GetSession().InvokeBackgroundWorkWatchdog();
if (!watchdogFired)
{
// Handshake wasn't stuck, send the busy status report and let the existing handshake continue.
// A successful CASE handshake can take several seconds and some may time out (30 seconds or more).
System::Clock::Milliseconds16 delay = System::Clock::kZero;
if (GetSession().GetState() == CASESession::State::kSentSigma2)
{
// The delay should be however long we think it will take for
// that to time out.
auto sigma2Timeout = CASESession::ComputeSigma2ResponseTimeout(GetSession().GetRemoteMRPConfig());
if (sigma2Timeout < System::Clock::Milliseconds16::max())
{
delay = std::chrono::duration_cast<System::Clock::Milliseconds16>(sigma2Timeout);
}
else
{
// Avoid overflow issues, just wait for as long as we can to
// get close to our expected Sigma2 timeout.
delay = System::Clock::Milliseconds16::max();
}
}
else
{
// For now, setting minimum wait time to 5000 milliseconds if we
// have no other information.
delay = System::Clock::Milliseconds16(5000);
}
CHIP_ERROR err = SendBusyStatusReport(ec, delay);
if (err != CHIP_NO_ERROR)
{
ChipLogError(Inet, "Failed to send the busy status report, err:%" CHIP_ERROR_FORMAT, err.Format());
}
return err;
}
}
if (!ec->GetSessionHandle()->IsUnauthenticatedSession())
{
ChipLogError(Inet, "CASE Server received Sigma1 message %s EC %p", "over encrypted session. Ignoring.", ec);
return CHIP_ERROR_INCORRECT_STATE;
}
ChipLogProgress(Inet, "CASE Server received Sigma1 message %s EC %p", ". Starting handshake.", ec);
CHIP_ERROR err = InitCASEHandshake(ec);
SuccessOrExit(err);
// TODO - Enable multiple concurrent CASE session establishment
// https://github.com/project-chip/connectedhomeip/issues/8342
err = GetSession().OnMessageReceived(ec, payloadHeader, std::move(payload));
SuccessOrExit(err);
exit:
// CASESession::OnMessageReceived guarantees that it will call
// OnSessionEstablishmentError if it returns error, so nothing else to do here.
return err;
}
void CASEServer::PrepareForSessionEstablishment(const ScopedNodeId & previouslyEstablishedPeer)
{
GetSession().Clear();
//
// This releases our reference to a previously pinned session. If that was a successfully established session and is now
// active, this will have no effect (the session will remain in the session table).
//
// If we previously held a session still in the pairing state, it means PairingSession was owning that session. Since it
// gave up its reference by the time we got here, releasing the pinned session here will actually result in it being
// de-allocated since no one else is holding onto this session. This will mean that when we get to allocating a session below,
// we'll at least have one free session available in the session table, and won't need to evict an arbitrary session.
//
mPinnedSecureSession.ClearValue();
//
// Indicate to the underlying CASE session to prepare for session establishment requests coming its way. This will
// involve allocating a SecureSession that will be held until it's needed for the next CASE session handshake.
//
// Logically speaking, we're attempting to evict a session using details of the just-established session (to ensure
// we're evicting sessions from the right fabric if needed) and then transferring the just established session into that
// slot (and thereby free'ing up the slot for the next session attempt). However, this transfer isn't necessary - just
// evicting a session will ensure it is available for the next attempt.
//
// This call can fail if we have run out memory to allocate SecureSessions. Continuing without taking any action
// however will render this node deaf to future handshake requests, so it's better to die here to raise attention to the problem
// / facilitate recovery.
//
// TODO(#17568): Once session eviction is actually in place, this call should NEVER fail and if so, is a logic bug.
// Dying here on failure is even more appropriate then.
//
VerifyOrDie(GetSession().PrepareForSessionEstablishment(*mSessionManager, mFabrics, mSessionResumptionStorage,
mCertificateValidityPolicy, this, previouslyEstablishedPeer,
GetLocalMRPConfig()) == CHIP_NO_ERROR);
//
// PairingSession::mSecureSessionHolder is a weak-reference. If MarkForEviction is called on this session, the session is
// going to get de-allocated from underneath us. This session that has just been allocated should *never* get evicted, and
// remain available till the next hand-shake is received.
//
// TODO: Converting SessionHolder to a true weak-ref and making PairingSession hold a strong-ref (#18397) would avoid this
// headache...
//
// Let's create a SessionHandle strong-reference to it to keep it resident.
//
mPinnedSecureSession = GetSession().CopySecureSession();
//
// If we've gotten this far, it means we have successfully allocated a SecureSession to back our next attempt. If we haven't,
// there is a bug somewhere and we should raise attention to it by dying.
//
VerifyOrDie(mPinnedSecureSession.HasValue());
}
void CASEServer::OnSessionEstablishmentError(CHIP_ERROR err)
{
MATTER_TRACE_SCOPE("OnSessionEstablishmentError", "CASEServer");
ChipLogError(Inet, "CASE Session establishment failed: %" CHIP_ERROR_FORMAT, err.Format());
MATTER_TRACE_SCOPE("CASEFail", "CASESession");
PrepareForSessionEstablishment();
}
void CASEServer::OnSessionEstablished(const SessionHandle & session)
{
MATTER_TRACE_SCOPE("OnSessionEstablished", "CASEServer");
ChipLogProgress(Inet, "CASE Session established to peer: " ChipLogFormatScopedNodeId,
ChipLogValueScopedNodeId(session->GetPeer()));
PrepareForSessionEstablishment(session->GetPeer());
}
CHIP_ERROR CASEServer::SendBusyStatusReport(Messaging::ExchangeContext * ec, System::Clock::Milliseconds16 minimumWaitTime)
{
MATTER_TRACE_SCOPE("SendBusyStatusReport", "CASEServer");
ChipLogProgress(Inet, "Already in the middle of CASE handshake, sending busy status report");
System::PacketBufferHandle handle = Protocols::SecureChannel::StatusReport::MakeBusyStatusReportMessage(minimumWaitTime);
VerifyOrReturnError(!handle.IsNull(), CHIP_ERROR_NO_MEMORY);
ChipLogProgress(Inet, "Sending status report, exchange " ChipLogFormatExchange, ChipLogValueExchange(ec));
return ec->SendMessage(Protocols::SecureChannel::MsgType::StatusReport, std::move(handle));
}
} // namespace chip