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