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