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