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(std::shared_ptr<Message> outMessage,
70                              SessionHeader authType,
71                              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(std::shared_ptr<Message> outMessage,
137                              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         {
225             throw std::runtime_error("Packet Integrity check failed");
226         }
227     }
228 
229     // Decrypt the payload if the payload is encrypted
230     if (message->isPacketEncrypted)
231     {
232         // Assign the decrypted payload to the IPMI Message
233         message->payload =
234             internal::decryptPayload(inPacket, message, payloadLen);
235     }
236     else
237     {
238         message->payload.assign(inPacket.begin() + sizeof(SessionHeader_t),
239                                 inPacket.begin() + sizeof(SessionHeader_t) +
240                                     payloadLen);
241     }
242 
243     return message;
244 }
245 
246 std::vector<uint8_t> flatten(std::shared_ptr<Message> outMessage,
247                              std::shared_ptr<session::Session> session)
248 {
249     std::vector<uint8_t> packet(sizeof(SessionHeader_t));
250 
251     SessionHeader_t* header = reinterpret_cast<SessionHeader_t*>(packet.data());
252     header->base.rmcp.version = parser::RMCP_VERSION;
253     header->base.rmcp.reserved = 0x00;
254     header->base.rmcp.rmcpSeqNum = parser::RMCP_SEQ;
255     header->base.rmcp.classOfMsg = static_cast<uint8_t>(ClassOfMsg::IPMI);
256     header->base.format.formatType =
257         static_cast<uint8_t>(parser::SessionHeader::IPMI20);
258     header->payloadType = static_cast<uint8_t>(outMessage->payloadType);
259     header->sessId = endian::to_ipmi(outMessage->rcSessionID);
260 
261     // Add session sequence number
262     internal::addSequenceNumber(packet, session);
263 
264     size_t payloadLen = 0;
265 
266     // Encrypt the payload if needed
267     if (outMessage->isPacketEncrypted)
268     {
269         header->payloadType |= PAYLOAD_ENCRYPT_MASK;
270         auto cipherPayload = internal::encryptPayload(outMessage);
271         payloadLen = cipherPayload.size();
272         header->payloadLength = endian::to_ipmi<uint16_t>(cipherPayload.size());
273 
274         // Insert the encrypted payload into the outgoing IPMI packet
275         packet.insert(packet.end(), cipherPayload.begin(), cipherPayload.end());
276     }
277     else
278     {
279         header->payloadLength =
280             endian::to_ipmi<uint16_t>(outMessage->payload.size());
281         payloadLen = outMessage->payload.size();
282 
283         // Insert the Payload into the Packet
284         packet.insert(packet.end(), outMessage->payload.begin(),
285                       outMessage->payload.end());
286     }
287 
288     if (outMessage->isPacketAuthenticated)
289     {
290         header = reinterpret_cast<SessionHeader_t*>(packet.data());
291         header->payloadType |= PAYLOAD_AUTH_MASK;
292         internal::addIntegrityData(packet, outMessage, payloadLen);
293     }
294 
295     return packet;
296 }
297 
298 namespace internal
299 {
300 
301 void addSequenceNumber(std::vector<uint8_t>& packet,
302                        std::shared_ptr<session::Session> session)
303 {
304     SessionHeader_t* header = reinterpret_cast<SessionHeader_t*>(packet.data());
305 
306     if (header->sessId == session::sessionZero)
307     {
308         header->sessSeqNum = 0x00;
309     }
310     else
311     {
312         auto seqNum = session->sequenceNums.increment();
313         header->sessSeqNum = endian::to_ipmi(seqNum);
314     }
315 }
316 
317 bool verifyPacketIntegrity(const std::vector<uint8_t>& packet,
318                            const std::shared_ptr<Message> message,
319                            size_t payloadLen)
320 {
321     /*
322      * Padding bytes are added to cause the number of bytes in the data range
323      * covered by the AuthCode(Integrity Data) field to be a multiple of 4 bytes
324      * .If present each integrity Pad byte is set to FFh. The following logic
325      * calculates the number of padding bytes added in the IPMI packet.
326      */
327     auto paddingLen = 4 - ((payloadLen + 2) & 3);
328 
329     auto sessTrailerPos = sizeof(SessionHeader_t) + payloadLen + paddingLen;
330 
331     // verify packet size includes trailer struct starts at sessTrailerPos
332     if (packet.size() < (sessTrailerPos + sizeof(SessionTrailer_t)))
333     {
334         return false;
335     }
336 
337     auto trailer = reinterpret_cast<const SessionTrailer_t*>(packet.data() +
338                                                              sessTrailerPos);
339 
340     // Check trailer->padLength against paddingLen, both should match up,
341     // return false if the lengths don't match
342     if (trailer->padLength != paddingLen)
343     {
344         return false;
345     }
346 
347     auto session = session::Manager::get().getSession(message->bmcSessionID);
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, size_t payloadLen)
373 {
374     // The following logic calculates the number of padding bytes to be added to
375     // IPMI packet. If needed each integrity Pad byte is set to FFh.
376     auto paddingLen = 4 - ((payloadLen + 2) & 3);
377     packet.insert(packet.end(), paddingLen, 0xFF);
378 
379     packet.resize(packet.size() + sizeof(SessionTrailer_t));
380 
381     auto trailer = reinterpret_cast<SessionTrailer_t*>(
382         packet.data() + packet.size() - sizeof(SessionTrailer_t));
383 
384     trailer->padLength = paddingLen;
385     trailer->nextHeader = parser::RMCP_MESSAGE_CLASS_IPMI;
386 
387     auto session = session::Manager::get().getSession(message->bmcSessionID);
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> decryptPayload(const std::vector<uint8_t>& packet,
396                                     const std::shared_ptr<Message> message,
397                                     size_t payloadLen)
398 {
399     auto session = session::Manager::get().getSession(message->bmcSessionID);
400 
401     return session->getCryptAlgo()->decryptPayload(
402         packet, sizeof(SessionHeader_t), payloadLen);
403 }
404 
405 std::vector<uint8_t> encryptPayload(std::shared_ptr<Message> message)
406 {
407     auto session = session::Manager::get().getSession(message->bmcSessionID);
408 
409     return session->getCryptAlgo()->encryptPayload(message->payload);
410 }
411 
412 } // namespace internal
413 
414 } // namespace ipmi20parser
415 
416 #ifdef RMCP_PING
417 namespace asfparser
418 {
419 std::shared_ptr<Message> unflatten(std::vector<uint8_t>& inPacket)
420 {
421     auto message = std::make_shared<Message>();
422 
423     auto header = reinterpret_cast<AsfMessagePing_t*>(inPacket.data());
424 
425     message->payloadType = PayloadType::IPMI;
426     message->rmcpMsgClass = ClassOfMsg::ASF;
427     message->asfMsgTag = header->msgTag;
428 
429     return message;
430 }
431 
432 std::vector<uint8_t> flatten(uint8_t asfMsgTag)
433 {
434     std::vector<uint8_t> packet(sizeof(AsfMessagePong_t));
435 
436     // Insert RMCP header into the Packet
437     auto header = reinterpret_cast<AsfMessagePong_t*>(packet.data());
438     header->ping.rmcp.version = parser::RMCP_VERSION;
439     header->ping.rmcp.reserved = 0x00;
440     header->ping.rmcp.rmcpSeqNum = parser::RMCP_SEQ;
441     header->ping.rmcp.classOfMsg = static_cast<uint8_t>(ClassOfMsg::ASF);
442 
443     // No OEM-specific capabilities exist, therefore the second
444     // IANA Enterprise Number contains the same IANA(4542)
445     header->ping.iana = header->iana = endian::to_ipmi(parser::ASF_IANA);
446     header->ping.msgType = static_cast<uint8_t>(RmcpMsgType::PONG);
447     header->ping.msgTag = asfMsgTag;
448     header->ping.reserved = 0x00;
449     header->ping.dataLen =
450         parser::RMCP_ASF_PONG_DATA_LEN; // as per spec 13.2.4,
451 
452     header->iana = parser::ASF_IANA;
453     header->oemDefined = 0x00;
454     header->suppEntities = parser::ASF_SUPP_ENT;
455     header->suppInteract = parser::ASF_SUPP_INT;
456     header->reserved1 = 0x00;
457     header->reserved2 = 0x00;
458 
459     return packet;
460 }
461 
462 } // namespace asfparser
463 #endif // RMCP_PING
464 
465 } // namespace message
466