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>
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 
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 
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,
137                              const std::shared_ptr<session::Session>& session)
138 {
139     std::vector<uint8_t> packet(sizeof(SessionHeader_t));
140 
141     // Insert Session Header into the Packet
142     auto header = reinterpret_cast<SessionHeader_t*>(packet.data());
143     header->base.rmcp.version = parser::RMCP_VERSION;
144     header->base.rmcp.reserved = 0x00;
145     header->base.rmcp.rmcpSeqNum = parser::RMCP_SEQ;
146     header->base.rmcp.classOfMsg = static_cast<uint8_t>(ClassOfMsg::IPMI);
147     header->base.format.formatType =
148         static_cast<uint8_t>(parser::SessionHeader::IPMI15);
149     header->sessSeqNum = 0;
150     header->sessId = endian::to_ipmi(outMessage->rcSessionID);
151 
152     header->payloadLength = static_cast<uint8_t>(outMessage->payload.size());
153 
154     // Insert the Payload into the Packet
155     packet.insert(packet.end(), outMessage->payload.begin(),
156                   outMessage->payload.end());
157 
158     // Insert the Session Trailer
159     packet.resize(packet.size() + sizeof(SessionTrailer_t));
160     auto trailer =
161         reinterpret_cast<SessionTrailer_t*>(packet.data() + packet.size());
162     trailer->legacyPad = 0x00;
163 
164     return packet;
165 }
166 
167 } // namespace ipmi15parser
168 
169 namespace ipmi20parser
170 {
171 
172 std::shared_ptr<Message> unflatten(std::vector<uint8_t>& inPacket)
173 {
174     // Check if the packet has atleast the Session Header
175     if (inPacket.size() < sizeof(SessionHeader_t))
176     {
177         throw std::runtime_error("IPMI2.0 Session Header Missing");
178     }
179 
180     auto header = reinterpret_cast<SessionHeader_t*>(inPacket.data());
181 
182     uint32_t sessionID = endian::from_ipmi(header->sessId);
183 
184     auto session = session::Manager::get().getSession(sessionID);
185     if (!session)
186     {
187         throw std::runtime_error("RMCP+ message from unknown session");
188     }
189 
190     auto message = std::make_shared<Message>();
191 
192     message->payloadType = static_cast<PayloadType>(header->payloadType & 0x3F);
193     message->bmcSessionID = sessionID;
194     message->sessionSeqNum = endian::from_ipmi(header->sessSeqNum);
195     message->isPacketEncrypted =
196         ((header->payloadType & PAYLOAD_ENCRYPT_MASK) ? true : false);
197     message->isPacketAuthenticated =
198         ((header->payloadType & PAYLOAD_AUTH_MASK) ? true : false);
199     message->rmcpMsgClass =
200         static_cast<ClassOfMsg>(header->base.rmcp.classOfMsg);
201 
202     // Confirm the number of data bytes received correlates to
203     // the packet length in the header
204     size_t payloadLen = endian::from_ipmi(header->payloadLength);
205     if ((payloadLen == 0) || (inPacket.size() < (sizeof(*header) + payloadLen)))
206     {
207         throw std::runtime_error("Invalid data length");
208     }
209 
210     bool integrityMismatch =
211         session->isIntegrityAlgoEnabled() && !message->isPacketAuthenticated;
212     bool encryptMismatch =
213         session->isCryptAlgoEnabled() && !message->isPacketEncrypted;
214 
215     if (sessionID != session::sessionZero &&
216         (integrityMismatch || encryptMismatch))
217     {
218         throw std::runtime_error("unencrypted or unauthenticated message");
219     }
220 
221     if (message->isPacketAuthenticated)
222     {
223         if (!(internal::verifyPacketIntegrity(inPacket, message, payloadLen,
224                                               session)))
225         {
226             throw std::runtime_error("Packet Integrity check failed");
227         }
228     }
229 
230     // Decrypt the payload if the payload is encrypted
231     if (message->isPacketEncrypted)
232     {
233         // Assign the decrypted payload to the IPMI Message
234         message->payload =
235             internal::decryptPayload(inPacket, message, payloadLen, session);
236     }
237     else
238     {
239         message->payload.assign(inPacket.begin() + sizeof(SessionHeader_t),
240                                 inPacket.begin() + sizeof(SessionHeader_t) +
241                                     payloadLen);
242     }
243 
244     return message;
245 }
246 
247 std::vector<uint8_t> flatten(const std::shared_ptr<Message>& outMessage,
248                              const std::shared_ptr<session::Session>& session)
249 {
250     std::vector<uint8_t> packet(sizeof(SessionHeader_t));
251 
252     SessionHeader_t* header = reinterpret_cast<SessionHeader_t*>(packet.data());
253     header->base.rmcp.version = parser::RMCP_VERSION;
254     header->base.rmcp.reserved = 0x00;
255     header->base.rmcp.rmcpSeqNum = parser::RMCP_SEQ;
256     header->base.rmcp.classOfMsg = static_cast<uint8_t>(ClassOfMsg::IPMI);
257     header->base.format.formatType =
258         static_cast<uint8_t>(parser::SessionHeader::IPMI20);
259     header->payloadType = static_cast<uint8_t>(outMessage->payloadType);
260     header->sessId = endian::to_ipmi(outMessage->rcSessionID);
261 
262     // Add session sequence number
263     internal::addSequenceNumber(packet, session);
264 
265     size_t payloadLen = 0;
266 
267     // Encrypt the payload if needed
268     if (outMessage->isPacketEncrypted)
269     {
270         header->payloadType |= PAYLOAD_ENCRYPT_MASK;
271         auto cipherPayload = internal::encryptPayload(outMessage, session);
272         payloadLen = cipherPayload.size();
273         header->payloadLength = endian::to_ipmi<uint16_t>(cipherPayload.size());
274 
275         // Insert the encrypted payload into the outgoing IPMI packet
276         packet.insert(packet.end(), cipherPayload.begin(), cipherPayload.end());
277     }
278     else
279     {
280         header->payloadLength =
281             endian::to_ipmi<uint16_t>(outMessage->payload.size());
282         payloadLen = outMessage->payload.size();
283 
284         // Insert the Payload into the Packet
285         packet.insert(packet.end(), outMessage->payload.begin(),
286                       outMessage->payload.end());
287     }
288 
289     if (outMessage->isPacketAuthenticated)
290     {
291         header = reinterpret_cast<SessionHeader_t*>(packet.data());
292         header->payloadType |= PAYLOAD_AUTH_MASK;
293         internal::addIntegrityData(packet, outMessage, payloadLen, session);
294     }
295 
296     return packet;
297 }
298 
299 namespace internal
300 {
301 
302 void addSequenceNumber(std::vector<uint8_t>& packet,
303                        const std::shared_ptr<session::Session>& session)
304 {
305     SessionHeader_t* header = reinterpret_cast<SessionHeader_t*>(packet.data());
306 
307     if (header->sessId == session::sessionZero)
308     {
309         header->sessSeqNum = 0x00;
310     }
311     else
312     {
313         auto seqNum = session->sequenceNums.increment();
314         header->sessSeqNum = endian::to_ipmi(seqNum);
315     }
316 }
317 
318 bool verifyPacketIntegrity(const std::vector<uint8_t>& packet,
319                            const std::shared_ptr<Message>& message,
320                            size_t payloadLen,
321                            const std::shared_ptr<session::Session>& session)
322 {
323     /*
324      * Padding bytes are added to cause the number of bytes in the data range
325      * covered by the AuthCode(Integrity Data) field to be a multiple of 4 bytes
326      * .If present each integrity Pad byte is set to FFh. The following logic
327      * calculates the number of padding bytes added in the IPMI packet.
328      */
329     auto paddingLen = 4 - ((payloadLen + 2) & 3);
330 
331     auto sessTrailerPos = sizeof(SessionHeader_t) + payloadLen + paddingLen;
332 
333     // verify packet size includes trailer struct starts at sessTrailerPos
334     if (packet.size() < (sessTrailerPos + sizeof(SessionTrailer_t)))
335     {
336         return false;
337     }
338 
339     auto trailer = reinterpret_cast<const SessionTrailer_t*>(packet.data() +
340                                                              sessTrailerPos);
341 
342     // Check trailer->padLength against paddingLen, both should match up,
343     // return false if the lengths don't match
344     if (trailer->padLength != paddingLen)
345     {
346         return false;
347     }
348 
349     auto integrityAlgo = session->getIntegrityAlgo();
350 
351     // Check if Integrity data length is as expected, check integrity data
352     // length is same as the length expected for the Integrity Algorithm that
353     // was negotiated during the session open process.
354     if ((packet.size() - sessTrailerPos - sizeof(SessionTrailer_t)) !=
355         integrityAlgo->authCodeLength)
356     {
357         return false;
358     }
359 
360     auto integrityIter = packet.cbegin();
361     std::advance(integrityIter, sessTrailerPos + sizeof(SessionTrailer_t));
362 
363     // The integrity data is calculated from the AuthType/Format field up to and
364     // including the field that immediately precedes the AuthCode field itself.
365     size_t length = packet.size() - integrityAlgo->authCodeLength -
366                     message::parser::RMCP_SESSION_HEADER_SIZE;
367 
368     return integrityAlgo->verifyIntegrityData(packet, length, integrityIter);
369 }
370 
371 void addIntegrityData(std::vector<uint8_t>& packet,
372                       const std::shared_ptr<Message>& message,
373                       size_t payloadLen,
374                       const std::shared_ptr<session::Session>& session)
375 {
376     // The following logic calculates the number of padding bytes to be added to
377     // IPMI packet. If needed each integrity Pad byte is set to FFh.
378     auto paddingLen = 4 - ((payloadLen + 2) & 3);
379     packet.insert(packet.end(), paddingLen, 0xFF);
380 
381     packet.resize(packet.size() + sizeof(SessionTrailer_t));
382 
383     auto trailer = reinterpret_cast<SessionTrailer_t*>(
384         packet.data() + packet.size() - sizeof(SessionTrailer_t));
385 
386     trailer->padLength = paddingLen;
387     trailer->nextHeader = parser::RMCP_MESSAGE_CLASS_IPMI;
388 
389     auto integrityData =
390         session->getIntegrityAlgo()->generateIntegrityData(packet);
391 
392     packet.insert(packet.end(), integrityData.begin(), integrityData.end());
393 }
394 
395 std::vector<uint8_t>
396     decryptPayload(const std::vector<uint8_t>& packet,
397                    const std::shared_ptr<Message>& message, size_t payloadLen,
398                    const std::shared_ptr<session::Session>& session)
399 {
400     return session->getCryptAlgo()->decryptPayload(
401         packet, sizeof(SessionHeader_t), payloadLen);
402 }
403 
404 std::vector<uint8_t>
405     encryptPayload(const std::shared_ptr<Message>& message,
406                    const std::shared_ptr<session::Session>& session)
407 {
408     return session->getCryptAlgo()->encryptPayload(message->payload);
409 }
410 
411 } // namespace internal
412 
413 } // namespace ipmi20parser
414 
415 #ifdef RMCP_PING
416 namespace asfparser
417 {
418 std::shared_ptr<Message> unflatten(std::vector<uint8_t>& inPacket)
419 {
420     auto message = std::make_shared<Message>();
421 
422     auto header = reinterpret_cast<AsfMessagePing_t*>(inPacket.data());
423 
424     message->payloadType = PayloadType::IPMI;
425     message->rmcpMsgClass = ClassOfMsg::ASF;
426     message->asfMsgTag = header->msgTag;
427 
428     return message;
429 }
430 
431 std::vector<uint8_t> flatten(uint8_t asfMsgTag)
432 {
433     std::vector<uint8_t> packet(sizeof(AsfMessagePong_t));
434 
435     // Insert RMCP header into the Packet
436     auto header = reinterpret_cast<AsfMessagePong_t*>(packet.data());
437     header->ping.rmcp.version = parser::RMCP_VERSION;
438     header->ping.rmcp.reserved = 0x00;
439     header->ping.rmcp.rmcpSeqNum = parser::RMCP_SEQ;
440     header->ping.rmcp.classOfMsg = static_cast<uint8_t>(ClassOfMsg::ASF);
441 
442     // No OEM-specific capabilities exist, therefore the second
443     // IANA Enterprise Number contains the same IANA(4542)
444     header->ping.iana = header->iana = endian::to_ipmi(parser::ASF_IANA);
445     header->ping.msgType = static_cast<uint8_t>(RmcpMsgType::PONG);
446     header->ping.msgTag = asfMsgTag;
447     header->ping.reserved = 0x00;
448     header->ping.dataLen =
449         parser::RMCP_ASF_PONG_DATA_LEN; // as per spec 13.2.4,
450 
451     header->iana = parser::ASF_IANA;
452     header->oemDefined = 0x00;
453     header->suppEntities = parser::ASF_SUPP_ENT;
454     header->suppInteract = parser::ASF_SUPP_INT;
455     header->reserved1 = 0x00;
456     header->reserved2 = 0x00;
457 
458     return packet;
459 }
460 
461 } // namespace asfparser
462 #endif // RMCP_PING
463 
464 } // namespace message
465