Line data Source code
1 : /*
2 : *
3 : * Copyright (c) 2021 Project CHIP Authors
4 : *
5 : * Licensed under the Apache License, Version 2.0 (the "License");
6 : * you may not use this file except in compliance with the License.
7 : * You may obtain a copy of the License at
8 : *
9 : * http://www.apache.org/licenses/LICENSE-2.0
10 : *
11 : * Unless required by applicable law or agreed to in writing, software
12 : * distributed under the License is distributed on an "AS IS" BASIS,
13 : * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 : * See the License for the specific language governing permissions and
15 : * limitations under the License.
16 : */
17 :
18 : #include <protocols/secure_channel/CASEServer.h>
19 :
20 : #include <lib/core/CHIPError.h>
21 : #include <lib/support/CodeUtils.h>
22 : #include <lib/support/SafeInt.h>
23 : #include <lib/support/logging/CHIPLogging.h>
24 : #include <tracing/macros.h>
25 : #include <transport/SessionManager.h>
26 :
27 : using namespace ::chip::Inet;
28 : using namespace ::chip::Transport;
29 : using namespace ::chip::Credentials;
30 :
31 : namespace chip {
32 :
33 7 : CHIP_ERROR CASEServer::ListenForSessionEstablishment(Messaging::ExchangeManager * exchangeManager, SessionManager * sessionManager,
34 : FabricTable * fabrics, SessionResumptionStorage * sessionResumptionStorage,
35 : Credentials::CertificateValidityPolicy * certificateValidityPolicy,
36 : Credentials::GroupDataProvider * responderGroupDataProvider)
37 : {
38 7 : VerifyOrReturnError(exchangeManager != nullptr, CHIP_ERROR_INVALID_ARGUMENT);
39 7 : VerifyOrReturnError(sessionManager != nullptr, CHIP_ERROR_INVALID_ARGUMENT);
40 7 : VerifyOrReturnError(responderGroupDataProvider != nullptr, CHIP_ERROR_INVALID_ARGUMENT);
41 :
42 7 : mSessionManager = sessionManager;
43 7 : mSessionResumptionStorage = sessionResumptionStorage;
44 7 : mCertificateValidityPolicy = certificateValidityPolicy;
45 7 : mFabrics = fabrics;
46 7 : mExchangeManager = exchangeManager;
47 7 : mGroupDataProvider = responderGroupDataProvider;
48 :
49 : // Set up the group state provider that persists across all handshakes.
50 7 : GetSession().SetGroupDataProvider(mGroupDataProvider);
51 :
52 7 : ChipLogProgress(Inet, "CASE Server enabling CASE session setups");
53 7 : mExchangeManager->RegisterUnsolicitedMessageHandlerForType(Protocols::SecureChannel::MsgType::CASE_Sigma1, this);
54 :
55 7 : PrepareForSessionEstablishment();
56 :
57 7 : return CHIP_NO_ERROR;
58 : }
59 :
60 7 : CHIP_ERROR CASEServer::InitCASEHandshake(Messaging::ExchangeContext * ec)
61 : {
62 : MATTER_TRACE_SCOPE("InitCASEHandshake", "CASEServer");
63 7 : ReturnErrorCodeIf(ec == nullptr, CHIP_ERROR_INVALID_ARGUMENT);
64 :
65 : // Hand over the exchange context to the CASE session.
66 7 : ec->SetDelegate(&GetSession());
67 :
68 7 : return CHIP_NO_ERROR;
69 : }
70 :
71 8 : CHIP_ERROR CASEServer::OnUnsolicitedMessageReceived(const PayloadHeader & payloadHeader, ExchangeDelegate *& newDelegate)
72 : {
73 : // TODO: assign newDelegate to CASESession, let CASESession handle future messages.
74 8 : newDelegate = this;
75 8 : return CHIP_NO_ERROR;
76 : }
77 :
78 8 : CHIP_ERROR CASEServer::OnMessageReceived(Messaging::ExchangeContext * ec, const PayloadHeader & payloadHeader,
79 : System::PacketBufferHandle && payload)
80 : {
81 : MATTER_TRACE_SCOPE("OnMessageReceived", "CASEServer");
82 8 : if (GetSession().GetState() != CASESession::State::kInitialized)
83 : {
84 : // We are in the middle of CASE handshake
85 :
86 : // Invoke watchdog to fix any stuck handshakes
87 1 : bool watchdogFired = GetSession().InvokeBackgroundWorkWatchdog();
88 1 : if (!watchdogFired)
89 : {
90 : // Handshake wasn't stuck, send the busy status report and let the existing handshake continue.
91 :
92 : // A successful CASE handshake can take several seconds and some may time out (30 seconds or more).
93 : // TODO: Come up with better estimate: https://github.com/project-chip/connectedhomeip/issues/28288
94 : // For now, setting minimum wait time to 5000 milliseconds.
95 1 : CHIP_ERROR err = SendBusyStatusReport(ec, System::Clock::Milliseconds16(5000));
96 1 : if (err != CHIP_NO_ERROR)
97 : {
98 0 : ChipLogError(Inet, "Failed to send the busy status report, err:%" CHIP_ERROR_FORMAT, err.Format());
99 : }
100 1 : return err;
101 : }
102 : }
103 :
104 7 : if (!ec->GetSessionHandle()->IsUnauthenticatedSession())
105 : {
106 0 : ChipLogError(Inet, "CASE Server received Sigma1 message %s EC %p", "over encrypted session. Ignoring.", ec);
107 0 : return CHIP_ERROR_INCORRECT_STATE;
108 : }
109 :
110 7 : ChipLogProgress(Inet, "CASE Server received Sigma1 message %s EC %p", ". Starting handshake.", ec);
111 :
112 7 : CHIP_ERROR err = InitCASEHandshake(ec);
113 7 : SuccessOrExit(err);
114 :
115 : // TODO - Enable multiple concurrent CASE session establishment
116 : // https://github.com/project-chip/connectedhomeip/issues/8342
117 :
118 7 : err = GetSession().OnMessageReceived(ec, payloadHeader, std::move(payload));
119 7 : SuccessOrExit(err);
120 :
121 7 : exit:
122 : // CASESession::OnMessageReceived guarantees that it will call
123 : // OnSessionEstablishmentError if it returns error, so nothing else to do here.
124 7 : return err;
125 : }
126 :
127 16 : void CASEServer::PrepareForSessionEstablishment(const ScopedNodeId & previouslyEstablishedPeer)
128 : {
129 16 : GetSession().Clear();
130 :
131 : //
132 : // This releases our reference to a previously pinned session. If that was a successfully established session and is now
133 : // active, this will have no effect (the session will remain in the session table).
134 : //
135 : // If we previously held a session still in the pairing state, it means PairingSession was owning that session. Since it
136 : // gave up its reference by the time we got here, releasing the pinned session here will actually result in it being
137 : // de-allocated since no one else is holding onto this session. This will mean that when we get to allocating a session below,
138 : // we'll at least have one free session available in the session table, and won't need to evict an arbitrary session.
139 : //
140 16 : mPinnedSecureSession.ClearValue();
141 :
142 : //
143 : // Indicate to the underlying CASE session to prepare for session establishment requests coming its way. This will
144 : // involve allocating a SecureSession that will be held until it's needed for the next CASE session handshake.
145 : //
146 : // Logically speaking, we're attempting to evict a session using details of the just-established session (to ensure
147 : // we're evicting sessions from the right fabric if needed) and then transferring the just established session into that
148 : // slot (and thereby free'ing up the slot for the next session attempt). However, this transfer isn't necessary - just
149 : // evicting a session will ensure it is available for the next attempt.
150 : //
151 : // This call can fail if we have run out memory to allocate SecureSessions. Continuing without taking any action
152 : // however will render this node deaf to future handshake requests, so it's better to die here to raise attention to the problem
153 : // / facilitate recovery.
154 : //
155 : // TODO(#17568): Once session eviction is actually in place, this call should NEVER fail and if so, is a logic bug.
156 : // Dying here on failure is even more appropriate then.
157 : //
158 16 : VerifyOrDie(GetSession().PrepareForSessionEstablishment(*mSessionManager, mFabrics, mSessionResumptionStorage,
159 : mCertificateValidityPolicy, this, previouslyEstablishedPeer,
160 : GetLocalMRPConfig()) == CHIP_NO_ERROR);
161 :
162 : //
163 : // PairingSession::mSecureSessionHolder is a weak-reference. If MarkForEviction is called on this session, the session is
164 : // going to get de-allocated from underneath us. This session that has just been allocated should *never* get evicted, and
165 : // remain available till the next hand-shake is received.
166 : //
167 : // TODO: Converting SessionHolder to a true weak-ref and making PairingSession hold a strong-ref (#18397) would avoid this
168 : // headache...
169 : //
170 : // Let's create a SessionHandle strong-reference to it to keep it resident.
171 : //
172 16 : mPinnedSecureSession = GetSession().CopySecureSession();
173 :
174 : //
175 : // If we've gotten this far, it means we have successfully allocated a SecureSession to back our next attempt. If we haven't,
176 : // there is a bug somewhere and we should raise attention to it by dying.
177 : //
178 16 : VerifyOrDie(mPinnedSecureSession.HasValue());
179 16 : }
180 :
181 2 : void CASEServer::OnSessionEstablishmentError(CHIP_ERROR err)
182 : {
183 : MATTER_TRACE_SCOPE("OnSessionEstablishmentError", "CASEServer");
184 2 : ChipLogError(Inet, "CASE Session establishment failed: %" CHIP_ERROR_FORMAT, err.Format());
185 :
186 : MATTER_TRACE_SCOPE("CASEFail", "CASESession");
187 2 : PrepareForSessionEstablishment();
188 2 : }
189 :
190 7 : void CASEServer::OnSessionEstablished(const SessionHandle & session)
191 : {
192 : MATTER_TRACE_SCOPE("OnSessionEstablished", "CASEServer");
193 7 : ChipLogProgress(Inet, "CASE Session established to peer: " ChipLogFormatScopedNodeId,
194 : ChipLogValueScopedNodeId(session->GetPeer()));
195 7 : PrepareForSessionEstablishment(session->GetPeer());
196 7 : }
197 :
198 1 : CHIP_ERROR CASEServer::SendBusyStatusReport(Messaging::ExchangeContext * ec, System::Clock::Milliseconds16 minimumWaitTime)
199 : {
200 : MATTER_TRACE_SCOPE("SendBusyStatusReport", "CASEServer");
201 1 : ChipLogProgress(Inet, "Already in the middle of CASE handshake, sending busy status report");
202 :
203 1 : System::PacketBufferHandle handle = Protocols::SecureChannel::StatusReport::MakeBusyStatusReportMessage(minimumWaitTime);
204 1 : VerifyOrReturnError(!handle.IsNull(), CHIP_ERROR_NO_MEMORY);
205 :
206 1 : ChipLogProgress(Inet, "Sending status report, exchange " ChipLogFormatExchange, ChipLogValueExchange(ec));
207 1 : return ec->SendMessage(Protocols::SecureChannel::MsgType::StatusReport, std::move(handle));
208 1 : }
209 :
210 : } // namespace chip
|