#include "message_parsers.hpp" #include "endian.hpp" #include "main.hpp" #include "message.hpp" #include "sessions_manager.hpp" #include namespace message { namespace parser { std::tuple, SessionHeader> unflatten(std::vector& inPacket) { // Check if the packet has atleast the size of the RMCP Header if (inPacket.size() < sizeof(RmcpHeader_t)) { throw std::runtime_error("RMCP Header missing"); } auto rmcpHeaderPtr = reinterpret_cast(inPacket.data()); // Verify if the fields in the RMCP header conforms to the specification if ((rmcpHeaderPtr->version != RMCP_VERSION) || (rmcpHeaderPtr->rmcpSeqNum != RMCP_SEQ) || (rmcpHeaderPtr->classOfMsg < static_cast(ClassOfMsg::ASF) && rmcpHeaderPtr->classOfMsg > static_cast(ClassOfMsg::OEM))) { throw std::runtime_error("RMCP Header is invalid"); } if (rmcpHeaderPtr->classOfMsg == static_cast(ClassOfMsg::ASF)) { #ifndef RMCP_PING throw std::runtime_error("RMCP Ping is not supported"); #else return std::make_tuple(asfparser::unflatten(inPacket), SessionHeader::IPMI15); #endif // RMCP_PING } auto sessionHeaderPtr = reinterpret_cast(inPacket.data()); // Read the Session Header and invoke the parser corresponding to the // header type switch (static_cast(sessionHeaderPtr->format.formatType)) { case SessionHeader::IPMI15: { return std::make_tuple(ipmi15parser::unflatten(inPacket), SessionHeader::IPMI15); } case SessionHeader::IPMI20: { return std::make_tuple(ipmi20parser::unflatten(inPacket), SessionHeader::IPMI20); } default: { throw std::runtime_error("Invalid Session Header"); } } } std::vector flatten(const std::shared_ptr& outMessage, SessionHeader authType, const std::shared_ptr& session) { // Call the flatten routine based on the header type switch (authType) { case SessionHeader::IPMI15: { return ipmi15parser::flatten(outMessage, session); } case SessionHeader::IPMI20: { return ipmi20parser::flatten(outMessage, session); } default: { return {}; } } } } // namespace parser namespace ipmi15parser { std::shared_ptr unflatten(std::vector& inPacket) { if (inPacket.size() < sizeof(SessionHeader_t)) { throw std::runtime_error("IPMI1.5 Session Header Missing"); } auto header = reinterpret_cast(inPacket.data()); uint32_t sessionID = endian::from_ipmi(header->sessId); if (sessionID != session::sessionZero) { throw std::runtime_error("IPMI1.5 session packets are unsupported"); } auto message = std::make_shared(); message->payloadType = PayloadType::IPMI; message->bmcSessionID = session::sessionZero; message->sessionSeqNum = endian::from_ipmi(header->sessSeqNum); message->isPacketEncrypted = false; message->isPacketAuthenticated = false; message->rmcpMsgClass = static_cast(header->base.rmcp.classOfMsg); // Confirm the number of data bytes received correlates to // the packet length in the header size_t payloadLen = header->payloadLength; if ((payloadLen == 0) || (inPacket.size() < (sizeof(*header) + payloadLen))) { throw std::runtime_error("Invalid data length"); } (message->payload) .assign(inPacket.data() + sizeof(SessionHeader_t), inPacket.data() + sizeof(SessionHeader_t) + payloadLen); return message; } std::vector flatten(const std::shared_ptr& outMessage, const std::shared_ptr& /* session */) { std::vector packet(sizeof(SessionHeader_t)); // Insert Session Header into the Packet auto header = reinterpret_cast(packet.data()); header->base.rmcp.version = parser::RMCP_VERSION; header->base.rmcp.reserved = 0x00; header->base.rmcp.rmcpSeqNum = parser::RMCP_SEQ; header->base.rmcp.classOfMsg = static_cast(ClassOfMsg::IPMI); header->base.format.formatType = static_cast(parser::SessionHeader::IPMI15); header->sessSeqNum = 0; header->sessId = endian::to_ipmi(outMessage->rcSessionID); header->payloadLength = static_cast(outMessage->payload.size()); // Insert the Payload into the Packet packet.insert(packet.end(), outMessage->payload.begin(), outMessage->payload.end()); // Insert the Session Trailer packet.resize(packet.size() + sizeof(SessionTrailer_t)); auto trailer = reinterpret_cast(packet.data() + packet.size()); trailer->legacyPad = 0x00; return packet; } } // namespace ipmi15parser namespace ipmi20parser { std::shared_ptr unflatten(std::vector& inPacket) { // Check if the packet has atleast the Session Header if (inPacket.size() < sizeof(SessionHeader_t)) { throw std::runtime_error("IPMI2.0 Session Header Missing"); } auto header = reinterpret_cast(inPacket.data()); uint32_t sessionID = endian::from_ipmi(header->sessId); auto session = session::Manager::get().getSession(sessionID); if (!session) { throw std::runtime_error("RMCP+ message from unknown session"); } auto message = std::make_shared(); message->payloadType = static_cast(header->payloadType & 0x3F); message->bmcSessionID = sessionID; message->sessionSeqNum = endian::from_ipmi(header->sessSeqNum); message->isPacketEncrypted = ((header->payloadType & PAYLOAD_ENCRYPT_MASK) ? true : false); message->isPacketAuthenticated = ((header->payloadType & PAYLOAD_AUTH_MASK) ? true : false); message->rmcpMsgClass = static_cast(header->base.rmcp.classOfMsg); // Confirm the number of data bytes received correlates to // the packet length in the header size_t payloadLen = endian::from_ipmi(header->payloadLength); if ((payloadLen == 0) || (inPacket.size() < (sizeof(*header) + payloadLen))) { throw std::runtime_error("Invalid data length"); } bool integrityMismatch = session->isIntegrityAlgoEnabled() && !message->isPacketAuthenticated; bool encryptMismatch = session->isCryptAlgoEnabled() && !message->isPacketEncrypted; if (sessionID != session::sessionZero && (integrityMismatch || encryptMismatch)) { throw std::runtime_error("unencrypted or unauthenticated message"); } if (message->isPacketAuthenticated) { if (!(internal::verifyPacketIntegrity(inPacket, message, payloadLen, session))) { throw std::runtime_error("Packet Integrity check failed"); } } // Decrypt the payload if the payload is encrypted if (message->isPacketEncrypted) { // Assign the decrypted payload to the IPMI Message message->payload = internal::decryptPayload(inPacket, message, payloadLen, session); } else { message->payload.assign( inPacket.begin() + sizeof(SessionHeader_t), inPacket.begin() + sizeof(SessionHeader_t) + payloadLen); } return message; } std::vector flatten(const std::shared_ptr& outMessage, const std::shared_ptr& session) { std::vector packet(sizeof(SessionHeader_t)); SessionHeader_t* header = reinterpret_cast(packet.data()); header->base.rmcp.version = parser::RMCP_VERSION; header->base.rmcp.reserved = 0x00; header->base.rmcp.rmcpSeqNum = parser::RMCP_SEQ; header->base.rmcp.classOfMsg = static_cast(ClassOfMsg::IPMI); header->base.format.formatType = static_cast(parser::SessionHeader::IPMI20); header->payloadType = static_cast(outMessage->payloadType); header->sessId = endian::to_ipmi(outMessage->rcSessionID); // Add session sequence number internal::addSequenceNumber(packet, session); size_t payloadLen = 0; // Encrypt the payload if needed if (outMessage->isPacketEncrypted) { header->payloadType |= PAYLOAD_ENCRYPT_MASK; auto cipherPayload = internal::encryptPayload(outMessage, session); payloadLen = cipherPayload.size(); header->payloadLength = endian::to_ipmi(cipherPayload.size()); // Insert the encrypted payload into the outgoing IPMI packet packet.insert(packet.end(), cipherPayload.begin(), cipherPayload.end()); } else { header->payloadLength = endian::to_ipmi(outMessage->payload.size()); payloadLen = outMessage->payload.size(); // Insert the Payload into the Packet packet.insert(packet.end(), outMessage->payload.begin(), outMessage->payload.end()); } if (outMessage->isPacketAuthenticated) { header = reinterpret_cast(packet.data()); header->payloadType |= PAYLOAD_AUTH_MASK; internal::addIntegrityData(packet, outMessage, payloadLen, session); } return packet; } namespace internal { void addSequenceNumber(std::vector& packet, const std::shared_ptr& session) { SessionHeader_t* header = reinterpret_cast(packet.data()); if (header->sessId == session::sessionZero) { header->sessSeqNum = 0x00; } else { auto seqNum = session->sequenceNums.increment(); header->sessSeqNum = endian::to_ipmi(seqNum); } } bool verifyPacketIntegrity(const std::vector& packet, const std::shared_ptr& /* message */, size_t payloadLen, const std::shared_ptr& session) { /* * Padding bytes are added to cause the number of bytes in the data range * covered by the AuthCode(Integrity Data) field to be a multiple of 4 bytes * .If present each integrity Pad byte is set to FFh. The following logic * calculates the number of padding bytes added in the IPMI packet. */ auto paddingLen = 4 - ((payloadLen + 2) & 3); auto sessTrailerPos = sizeof(SessionHeader_t) + payloadLen + paddingLen; // verify packet size includes trailer struct starts at sessTrailerPos if (packet.size() < (sessTrailerPos + sizeof(SessionTrailer_t))) { return false; } auto trailer = reinterpret_cast( packet.data() + sessTrailerPos); // Check trailer->padLength against paddingLen, both should match up, // return false if the lengths don't match if (trailer->padLength != paddingLen) { return false; } auto integrityAlgo = session->getIntegrityAlgo(); // Check if Integrity data length is as expected, check integrity data // length is same as the length expected for the Integrity Algorithm that // was negotiated during the session open process. if ((packet.size() - sessTrailerPos - sizeof(SessionTrailer_t)) != integrityAlgo->authCodeLength) { return false; } auto integrityIter = packet.cbegin(); std::advance(integrityIter, sessTrailerPos + sizeof(SessionTrailer_t)); // The integrity data is calculated from the AuthType/Format field up to and // including the field that immediately precedes the AuthCode field itself. size_t length = packet.size() - integrityAlgo->authCodeLength - message::parser::RMCP_SESSION_HEADER_SIZE; return integrityAlgo->verifyIntegrityData(packet, length, integrityIter, packet.cend()); } void addIntegrityData(std::vector& packet, const std::shared_ptr& /* message */, size_t payloadLen, const std::shared_ptr& session) { // The following logic calculates the number of padding bytes to be added to // IPMI packet. If needed each integrity Pad byte is set to FFh. auto paddingLen = 4 - ((payloadLen + 2) & 3); packet.insert(packet.end(), paddingLen, 0xFF); packet.resize(packet.size() + sizeof(SessionTrailer_t)); auto trailer = reinterpret_cast( packet.data() + packet.size() - sizeof(SessionTrailer_t)); trailer->padLength = paddingLen; trailer->nextHeader = parser::RMCP_MESSAGE_CLASS_IPMI; auto integrityData = session->getIntegrityAlgo()->generateIntegrityData(packet); packet.insert(packet.end(), integrityData.begin(), integrityData.end()); } std::vector decryptPayload( const std::vector& packet, const std::shared_ptr& /* message */, size_t payloadLen, const std::shared_ptr& session) { return session->getCryptAlgo()->decryptPayload( packet, sizeof(SessionHeader_t), payloadLen); } std::vector encryptPayload(const std::shared_ptr& message, const std::shared_ptr& session) { return session->getCryptAlgo()->encryptPayload(message->payload); } } // namespace internal } // namespace ipmi20parser #ifdef RMCP_PING namespace asfparser { std::shared_ptr unflatten(std::vector& inPacket) { auto message = std::make_shared(); auto header = reinterpret_cast(inPacket.data()); message->payloadType = PayloadType::IPMI; message->rmcpMsgClass = ClassOfMsg::ASF; message->asfMsgTag = header->msgTag; return message; } std::vector flatten(uint8_t asfMsgTag) { std::vector packet(sizeof(AsfMessagePong_t)); // Insert RMCP header into the Packet auto header = reinterpret_cast(packet.data()); header->ping.rmcp.version = parser::RMCP_VERSION; header->ping.rmcp.reserved = 0x00; header->ping.rmcp.rmcpSeqNum = parser::RMCP_SEQ; header->ping.rmcp.classOfMsg = static_cast(ClassOfMsg::ASF); // No OEM-specific capabilities exist, therefore the second // IANA Enterprise Number contains the same IANA(4542) header->ping.iana = header->iana = endian::to_ipmi(parser::ASF_IANA); header->ping.msgType = static_cast(RmcpMsgType::PONG); header->ping.msgTag = asfMsgTag; header->ping.reserved = 0x00; header->ping.dataLen = parser::RMCP_ASF_PONG_DATA_LEN; // as per spec 13.2.4, header->iana = parser::ASF_IANA; header->oemDefined = 0x00; header->suppEntities = parser::ASF_SUPP_ENT; header->suppInteract = parser::ASF_SUPP_INT; header->reserved1 = 0x00; header->reserved2 = 0x00; return packet; } } // namespace asfparser #endif // RMCP_PING } // namespace message