/* * * 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 #include #include #include #include #include #include #include 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"); VerifyOrReturnError(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(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