1 #include "message_parsers.hpp"
2
3 #include "endian.hpp"
4 #include "main.hpp"
5 #include "message.hpp"
6 #include "sessions_manager.hpp"
7
8 #include <memory>
9
10 namespace message
11 {
12
13 namespace parser
14 {
15
16 std::tuple<std::shared_ptr<Message>, SessionHeader>
unflatten(std::vector<uint8_t> & inPacket)17 unflatten(std::vector<uint8_t>& inPacket)
18 {
19 // Check if the packet has atleast the size of the RMCP Header
20 if (inPacket.size() < sizeof(RmcpHeader_t))
21 {
22 throw std::runtime_error("RMCP Header missing");
23 }
24
25 auto rmcpHeaderPtr = reinterpret_cast<RmcpHeader_t*>(inPacket.data());
26
27 // Verify if the fields in the RMCP header conforms to the specification
28 if ((rmcpHeaderPtr->version != RMCP_VERSION) ||
29 (rmcpHeaderPtr->rmcpSeqNum != RMCP_SEQ) ||
30 (rmcpHeaderPtr->classOfMsg < static_cast<uint8_t>(ClassOfMsg::ASF) &&
31 rmcpHeaderPtr->classOfMsg > static_cast<uint8_t>(ClassOfMsg::OEM)))
32 {
33 throw std::runtime_error("RMCP Header is invalid");
34 }
35
36 if (rmcpHeaderPtr->classOfMsg == static_cast<uint8_t>(ClassOfMsg::ASF))
37 {
38 #ifndef RMCP_PING
39 throw std::runtime_error("RMCP Ping is not supported");
40 #else
41 return std::make_tuple(asfparser::unflatten(inPacket),
42 SessionHeader::IPMI15);
43 #endif // RMCP_PING
44 }
45
46 auto sessionHeaderPtr = reinterpret_cast<BasicHeader_t*>(inPacket.data());
47
48 // Read the Session Header and invoke the parser corresponding to the
49 // header type
50 switch (static_cast<SessionHeader>(sessionHeaderPtr->format.formatType))
51 {
52 case SessionHeader::IPMI15:
53 {
54 return std::make_tuple(ipmi15parser::unflatten(inPacket),
55 SessionHeader::IPMI15);
56 }
57 case SessionHeader::IPMI20:
58 {
59 return std::make_tuple(ipmi20parser::unflatten(inPacket),
60 SessionHeader::IPMI20);
61 }
62 default:
63 {
64 throw std::runtime_error("Invalid Session Header");
65 }
66 }
67 }
68
flatten(const std::shared_ptr<Message> & outMessage,SessionHeader authType,const std::shared_ptr<session::Session> & session)69 std::vector<uint8_t> flatten(const std::shared_ptr<Message>& outMessage,
70 SessionHeader authType,
71 const std::shared_ptr<session::Session>& session)
72 {
73 // Call the flatten routine based on the header type
74 switch (authType)
75 {
76 case SessionHeader::IPMI15:
77 {
78 return ipmi15parser::flatten(outMessage, session);
79 }
80 case SessionHeader::IPMI20:
81 {
82 return ipmi20parser::flatten(outMessage, session);
83 }
84 default:
85 {
86 return {};
87 }
88 }
89 }
90
91 } // namespace parser
92
93 namespace ipmi15parser
94 {
95
unflatten(std::vector<uint8_t> & inPacket)96 std::shared_ptr<Message> unflatten(std::vector<uint8_t>& inPacket)
97 {
98 if (inPacket.size() < sizeof(SessionHeader_t))
99 {
100 throw std::runtime_error("IPMI1.5 Session Header Missing");
101 }
102
103 auto header = reinterpret_cast<SessionHeader_t*>(inPacket.data());
104
105 uint32_t sessionID = endian::from_ipmi(header->sessId);
106 if (sessionID != session::sessionZero)
107 {
108 throw std::runtime_error("IPMI1.5 session packets are unsupported");
109 }
110
111 auto message = std::make_shared<Message>();
112
113 message->payloadType = PayloadType::IPMI;
114 message->bmcSessionID = session::sessionZero;
115 message->sessionSeqNum = endian::from_ipmi(header->sessSeqNum);
116 message->isPacketEncrypted = false;
117 message->isPacketAuthenticated = false;
118 message->rmcpMsgClass =
119 static_cast<ClassOfMsg>(header->base.rmcp.classOfMsg);
120
121 // Confirm the number of data bytes received correlates to
122 // the packet length in the header
123 size_t payloadLen = header->payloadLength;
124 if ((payloadLen == 0) || (inPacket.size() < (sizeof(*header) + payloadLen)))
125 {
126 throw std::runtime_error("Invalid data length");
127 }
128
129 (message->payload)
130 .assign(inPacket.data() + sizeof(SessionHeader_t),
131 inPacket.data() + sizeof(SessionHeader_t) + payloadLen);
132
133 return message;
134 }
135
136 std::vector<uint8_t>
flatten(const std::shared_ptr<Message> & outMessage,const std::shared_ptr<session::Session> &)137 flatten(const std::shared_ptr<Message>& outMessage,
138 const std::shared_ptr<session::Session>& /* session */)
139 {
140 std::vector<uint8_t> packet(sizeof(SessionHeader_t));
141
142 // Insert Session Header into the Packet
143 auto header = reinterpret_cast<SessionHeader_t*>(packet.data());
144 header->base.rmcp.version = parser::RMCP_VERSION;
145 header->base.rmcp.reserved = 0x00;
146 header->base.rmcp.rmcpSeqNum = parser::RMCP_SEQ;
147 header->base.rmcp.classOfMsg = static_cast<uint8_t>(ClassOfMsg::IPMI);
148 header->base.format.formatType =
149 static_cast<uint8_t>(parser::SessionHeader::IPMI15);
150 header->sessSeqNum = 0;
151 header->sessId = endian::to_ipmi(outMessage->rcSessionID);
152
153 header->payloadLength = static_cast<uint8_t>(outMessage->payload.size());
154
155 // Insert the Payload into the Packet
156 packet.insert(packet.end(), outMessage->payload.begin(),
157 outMessage->payload.end());
158
159 // Insert the Session Trailer
160 packet.resize(packet.size() + sizeof(SessionTrailer_t));
161 auto trailer =
162 reinterpret_cast<SessionTrailer_t*>(packet.data() + packet.size());
163 trailer->legacyPad = 0x00;
164
165 return packet;
166 }
167
168 } // namespace ipmi15parser
169
170 namespace ipmi20parser
171 {
172
unflatten(std::vector<uint8_t> & inPacket)173 std::shared_ptr<Message> unflatten(std::vector<uint8_t>& inPacket)
174 {
175 // Check if the packet has atleast the Session Header
176 if (inPacket.size() < sizeof(SessionHeader_t))
177 {
178 throw std::runtime_error("IPMI2.0 Session Header Missing");
179 }
180
181 auto header = reinterpret_cast<SessionHeader_t*>(inPacket.data());
182
183 uint32_t sessionID = endian::from_ipmi(header->sessId);
184
185 auto session = session::Manager::get().getSession(sessionID);
186 if (!session)
187 {
188 throw std::runtime_error("RMCP+ message from unknown session");
189 }
190
191 auto message = std::make_shared<Message>();
192
193 message->payloadType = static_cast<PayloadType>(header->payloadType & 0x3F);
194 message->bmcSessionID = sessionID;
195 message->sessionSeqNum = endian::from_ipmi(header->sessSeqNum);
196 message->isPacketEncrypted =
197 ((header->payloadType & PAYLOAD_ENCRYPT_MASK) ? true : false);
198 message->isPacketAuthenticated =
199 ((header->payloadType & PAYLOAD_AUTH_MASK) ? true : false);
200 message->rmcpMsgClass =
201 static_cast<ClassOfMsg>(header->base.rmcp.classOfMsg);
202
203 // Confirm the number of data bytes received correlates to
204 // the packet length in the header
205 size_t payloadLen = endian::from_ipmi(header->payloadLength);
206 if ((payloadLen == 0) || (inPacket.size() < (sizeof(*header) + payloadLen)))
207 {
208 throw std::runtime_error("Invalid data length");
209 }
210
211 bool integrityMismatch =
212 session->isIntegrityAlgoEnabled() && !message->isPacketAuthenticated;
213 bool encryptMismatch = session->isCryptAlgoEnabled() &&
214 !message->isPacketEncrypted;
215
216 if (sessionID != session::sessionZero &&
217 (integrityMismatch || encryptMismatch))
218 {
219 throw std::runtime_error("unencrypted or unauthenticated message");
220 }
221
222 if (message->isPacketAuthenticated)
223 {
224 if (!(internal::verifyPacketIntegrity(inPacket, message, payloadLen,
225 session)))
226 {
227 throw std::runtime_error("Packet Integrity check failed");
228 }
229 }
230
231 // Decrypt the payload if the payload is encrypted
232 if (message->isPacketEncrypted)
233 {
234 // Assign the decrypted payload to the IPMI Message
235 message->payload =
236 internal::decryptPayload(inPacket, message, payloadLen, session);
237 }
238 else
239 {
240 message->payload.assign(
241 inPacket.begin() + sizeof(SessionHeader_t),
242 inPacket.begin() + sizeof(SessionHeader_t) + payloadLen);
243 }
244
245 return message;
246 }
247
flatten(const std::shared_ptr<Message> & outMessage,const std::shared_ptr<session::Session> & session)248 std::vector<uint8_t> flatten(const std::shared_ptr<Message>& outMessage,
249 const std::shared_ptr<session::Session>& session)
250 {
251 std::vector<uint8_t> packet(sizeof(SessionHeader_t));
252
253 SessionHeader_t* header = reinterpret_cast<SessionHeader_t*>(packet.data());
254 header->base.rmcp.version = parser::RMCP_VERSION;
255 header->base.rmcp.reserved = 0x00;
256 header->base.rmcp.rmcpSeqNum = parser::RMCP_SEQ;
257 header->base.rmcp.classOfMsg = static_cast<uint8_t>(ClassOfMsg::IPMI);
258 header->base.format.formatType =
259 static_cast<uint8_t>(parser::SessionHeader::IPMI20);
260 header->payloadType = static_cast<uint8_t>(outMessage->payloadType);
261 header->sessId = endian::to_ipmi(outMessage->rcSessionID);
262
263 // Add session sequence number
264 internal::addSequenceNumber(packet, session);
265
266 size_t payloadLen = 0;
267
268 // Encrypt the payload if needed
269 if (outMessage->isPacketEncrypted)
270 {
271 header->payloadType |= PAYLOAD_ENCRYPT_MASK;
272 auto cipherPayload = internal::encryptPayload(outMessage, session);
273 payloadLen = cipherPayload.size();
274 header->payloadLength = endian::to_ipmi<uint16_t>(cipherPayload.size());
275
276 // Insert the encrypted payload into the outgoing IPMI packet
277 packet.insert(packet.end(), cipherPayload.begin(), cipherPayload.end());
278 }
279 else
280 {
281 header->payloadLength =
282 endian::to_ipmi<uint16_t>(outMessage->payload.size());
283 payloadLen = outMessage->payload.size();
284
285 // Insert the Payload into the Packet
286 packet.insert(packet.end(), outMessage->payload.begin(),
287 outMessage->payload.end());
288 }
289
290 if (outMessage->isPacketAuthenticated)
291 {
292 header = reinterpret_cast<SessionHeader_t*>(packet.data());
293 header->payloadType |= PAYLOAD_AUTH_MASK;
294 internal::addIntegrityData(packet, outMessage, payloadLen, session);
295 }
296
297 return packet;
298 }
299
300 namespace internal
301 {
302
addSequenceNumber(std::vector<uint8_t> & packet,const std::shared_ptr<session::Session> & session)303 void addSequenceNumber(std::vector<uint8_t>& packet,
304 const std::shared_ptr<session::Session>& session)
305 {
306 SessionHeader_t* header = reinterpret_cast<SessionHeader_t*>(packet.data());
307
308 if (header->sessId == session::sessionZero)
309 {
310 header->sessSeqNum = 0x00;
311 }
312 else
313 {
314 auto seqNum = session->sequenceNums.increment();
315 header->sessSeqNum = endian::to_ipmi(seqNum);
316 }
317 }
318
verifyPacketIntegrity(const std::vector<uint8_t> & packet,const std::shared_ptr<Message> &,size_t payloadLen,const std::shared_ptr<session::Session> & session)319 bool verifyPacketIntegrity(const std::vector<uint8_t>& packet,
320 const std::shared_ptr<Message>& /* message */,
321 size_t payloadLen,
322 const std::shared_ptr<session::Session>& session)
323 {
324 /*
325 * Padding bytes are added to cause the number of bytes in the data range
326 * covered by the AuthCode(Integrity Data) field to be a multiple of 4 bytes
327 * .If present each integrity Pad byte is set to FFh. The following logic
328 * calculates the number of padding bytes added in the IPMI packet.
329 */
330 auto paddingLen = 4 - ((payloadLen + 2) & 3);
331
332 auto sessTrailerPos = sizeof(SessionHeader_t) + payloadLen + paddingLen;
333
334 // verify packet size includes trailer struct starts at sessTrailerPos
335 if (packet.size() < (sessTrailerPos + sizeof(SessionTrailer_t)))
336 {
337 return false;
338 }
339
340 auto trailer = reinterpret_cast<const SessionTrailer_t*>(
341 packet.data() + sessTrailerPos);
342
343 // Check trailer->padLength against paddingLen, both should match up,
344 // return false if the lengths don't match
345 if (trailer->padLength != paddingLen)
346 {
347 return false;
348 }
349
350 auto integrityAlgo = session->getIntegrityAlgo();
351
352 // Check if Integrity data length is as expected, check integrity data
353 // length is same as the length expected for the Integrity Algorithm that
354 // was negotiated during the session open process.
355 if ((packet.size() - sessTrailerPos - sizeof(SessionTrailer_t)) !=
356 integrityAlgo->authCodeLength)
357 {
358 return false;
359 }
360
361 auto integrityIter = packet.cbegin();
362 std::advance(integrityIter, sessTrailerPos + sizeof(SessionTrailer_t));
363
364 // The integrity data is calculated from the AuthType/Format field up to and
365 // including the field that immediately precedes the AuthCode field itself.
366 size_t length = packet.size() - integrityAlgo->authCodeLength -
367 message::parser::RMCP_SESSION_HEADER_SIZE;
368
369 return integrityAlgo->verifyIntegrityData(packet, length, integrityIter,
370 packet.cend());
371 }
372
addIntegrityData(std::vector<uint8_t> & packet,const std::shared_ptr<Message> &,size_t payloadLen,const std::shared_ptr<session::Session> & session)373 void addIntegrityData(std::vector<uint8_t>& packet,
374 const std::shared_ptr<Message>& /* message */,
375 size_t payloadLen,
376 const std::shared_ptr<session::Session>& session)
377 {
378 // The following logic calculates the number of padding bytes to be added to
379 // IPMI packet. If needed each integrity Pad byte is set to FFh.
380 auto paddingLen = 4 - ((payloadLen + 2) & 3);
381 packet.insert(packet.end(), paddingLen, 0xFF);
382
383 packet.resize(packet.size() + sizeof(SessionTrailer_t));
384
385 auto trailer = reinterpret_cast<SessionTrailer_t*>(
386 packet.data() + packet.size() - sizeof(SessionTrailer_t));
387
388 trailer->padLength = paddingLen;
389 trailer->nextHeader = parser::RMCP_MESSAGE_CLASS_IPMI;
390
391 auto integrityData =
392 session->getIntegrityAlgo()->generateIntegrityData(packet);
393
394 packet.insert(packet.end(), integrityData.begin(), integrityData.end());
395 }
396
decryptPayload(const std::vector<uint8_t> & packet,const std::shared_ptr<Message> &,size_t payloadLen,const std::shared_ptr<session::Session> & session)397 std::vector<uint8_t> decryptPayload(
398 const std::vector<uint8_t>& packet,
399 const std::shared_ptr<Message>& /* message */, size_t payloadLen,
400 const std::shared_ptr<session::Session>& session)
401 {
402 return session->getCryptAlgo()->decryptPayload(
403 packet, sizeof(SessionHeader_t), payloadLen);
404 }
405
406 std::vector<uint8_t>
encryptPayload(const std::shared_ptr<Message> & message,const std::shared_ptr<session::Session> & session)407 encryptPayload(const std::shared_ptr<Message>& message,
408 const std::shared_ptr<session::Session>& session)
409 {
410 return session->getCryptAlgo()->encryptPayload(message->payload);
411 }
412
413 } // namespace internal
414
415 } // namespace ipmi20parser
416
417 #ifdef RMCP_PING
418 namespace asfparser
419 {
unflatten(std::vector<uint8_t> & inPacket)420 std::shared_ptr<Message> unflatten(std::vector<uint8_t>& inPacket)
421 {
422 auto message = std::make_shared<Message>();
423
424 auto header = reinterpret_cast<AsfMessagePing_t*>(inPacket.data());
425
426 message->payloadType = PayloadType::IPMI;
427 message->rmcpMsgClass = ClassOfMsg::ASF;
428 message->asfMsgTag = header->msgTag;
429
430 return message;
431 }
432
flatten(uint8_t asfMsgTag)433 std::vector<uint8_t> flatten(uint8_t asfMsgTag)
434 {
435 std::vector<uint8_t> packet(sizeof(AsfMessagePong_t));
436
437 // Insert RMCP header into the Packet
438 auto header = reinterpret_cast<AsfMessagePong_t*>(packet.data());
439 header->ping.rmcp.version = parser::RMCP_VERSION;
440 header->ping.rmcp.reserved = 0x00;
441 header->ping.rmcp.rmcpSeqNum = parser::RMCP_SEQ;
442 header->ping.rmcp.classOfMsg = static_cast<uint8_t>(ClassOfMsg::ASF);
443
444 // No OEM-specific capabilities exist, therefore the second
445 // IANA Enterprise Number contains the same IANA(4542)
446 header->ping.iana = header->iana = endian::to_ipmi(parser::ASF_IANA);
447 header->ping.msgType = static_cast<uint8_t>(RmcpMsgType::PONG);
448 header->ping.msgTag = asfMsgTag;
449 header->ping.reserved = 0x00;
450 header->ping.dataLen =
451 parser::RMCP_ASF_PONG_DATA_LEN; // as per spec 13.2.4,
452
453 header->iana = parser::ASF_IANA;
454 header->oemDefined = 0x00;
455 header->suppEntities = parser::ASF_SUPP_ENT;
456 header->suppInteract = parser::ASF_SUPP_INT;
457 header->reserved1 = 0x00;
458 header->reserved2 = 0x00;
459
460 return packet;
461 }
462
463 } // namespace asfparser
464 #endif // RMCP_PING
465
466 } // namespace message
467