| #include "message_parsers.hpp" |
| |
| #include "endian.hpp" |
| #include "main.hpp" |
| #include "message.hpp" |
| #include "sessions_manager.hpp" |
| |
| #include <memory> |
| |
| namespace message |
| { |
| |
| namespace parser |
| { |
| |
| std::tuple<std::shared_ptr<Message>, SessionHeader> |
| unflatten(std::vector<uint8_t>& 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<RmcpHeader_t*>(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<uint8_t>(ClassOfMsg::ASF) && |
| rmcpHeaderPtr->classOfMsg > static_cast<uint8_t>(ClassOfMsg::OEM))) |
| { |
| throw std::runtime_error("RMCP Header is invalid"); |
| } |
| |
| if (rmcpHeaderPtr->classOfMsg == static_cast<uint8_t>(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<BasicHeader_t*>(inPacket.data()); |
| |
| // Read the Session Header and invoke the parser corresponding to the |
| // header type |
| switch (static_cast<SessionHeader>(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<uint8_t> flatten(const std::shared_ptr<Message>& outMessage, |
| SessionHeader authType, |
| const std::shared_ptr<session::Session>& 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<Message> unflatten(std::vector<uint8_t>& inPacket) |
| { |
| if (inPacket.size() < sizeof(SessionHeader_t)) |
| { |
| throw std::runtime_error("IPMI1.5 Session Header Missing"); |
| } |
| |
| auto header = reinterpret_cast<SessionHeader_t*>(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>(); |
| |
| 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<ClassOfMsg>(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<uint8_t> |
| flatten(const std::shared_ptr<Message>& outMessage, |
| const std::shared_ptr<session::Session>& /* session */) |
| { |
| std::vector<uint8_t> packet(sizeof(SessionHeader_t)); |
| |
| // Insert Session Header into the Packet |
| auto header = reinterpret_cast<SessionHeader_t*>(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<uint8_t>(ClassOfMsg::IPMI); |
| header->base.format.formatType = |
| static_cast<uint8_t>(parser::SessionHeader::IPMI15); |
| header->sessSeqNum = 0; |
| header->sessId = endian::to_ipmi(outMessage->rcSessionID); |
| |
| header->payloadLength = static_cast<uint8_t>(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<SessionTrailer_t*>(packet.data() + packet.size()); |
| trailer->legacyPad = 0x00; |
| |
| return packet; |
| } |
| |
| } // namespace ipmi15parser |
| |
| namespace ipmi20parser |
| { |
| |
| std::shared_ptr<Message> unflatten(std::vector<uint8_t>& 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<SessionHeader_t*>(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>(); |
| |
| message->payloadType = static_cast<PayloadType>(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<ClassOfMsg>(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<uint8_t> flatten(const std::shared_ptr<Message>& outMessage, |
| const std::shared_ptr<session::Session>& session) |
| { |
| std::vector<uint8_t> packet(sizeof(SessionHeader_t)); |
| |
| SessionHeader_t* header = reinterpret_cast<SessionHeader_t*>(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<uint8_t>(ClassOfMsg::IPMI); |
| header->base.format.formatType = |
| static_cast<uint8_t>(parser::SessionHeader::IPMI20); |
| header->payloadType = static_cast<uint8_t>(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<uint16_t>(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<uint16_t>(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<SessionHeader_t*>(packet.data()); |
| header->payloadType |= PAYLOAD_AUTH_MASK; |
| internal::addIntegrityData(packet, outMessage, payloadLen, session); |
| } |
| |
| return packet; |
| } |
| |
| namespace internal |
| { |
| |
| void addSequenceNumber(std::vector<uint8_t>& packet, |
| const std::shared_ptr<session::Session>& session) |
| { |
| SessionHeader_t* header = reinterpret_cast<SessionHeader_t*>(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<uint8_t>& packet, |
| const std::shared_ptr<Message>& /* message */, |
| size_t payloadLen, |
| const std::shared_ptr<session::Session>& 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<const SessionTrailer_t*>(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<uint8_t>& packet, |
| const std::shared_ptr<Message>& /* message */, |
| size_t payloadLen, |
| const std::shared_ptr<session::Session>& 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<SessionTrailer_t*>( |
| 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<uint8_t> |
| decryptPayload(const std::vector<uint8_t>& packet, |
| const std::shared_ptr<Message>& /* message */, |
| size_t payloadLen, |
| const std::shared_ptr<session::Session>& session) |
| { |
| return session->getCryptAlgo()->decryptPayload( |
| packet, sizeof(SessionHeader_t), payloadLen); |
| } |
| |
| std::vector<uint8_t> |
| encryptPayload(const std::shared_ptr<Message>& message, |
| const std::shared_ptr<session::Session>& session) |
| { |
| return session->getCryptAlgo()->encryptPayload(message->payload); |
| } |
| |
| } // namespace internal |
| |
| } // namespace ipmi20parser |
| |
| #ifdef RMCP_PING |
| namespace asfparser |
| { |
| std::shared_ptr<Message> unflatten(std::vector<uint8_t>& inPacket) |
| { |
| auto message = std::make_shared<Message>(); |
| |
| auto header = reinterpret_cast<AsfMessagePing_t*>(inPacket.data()); |
| |
| message->payloadType = PayloadType::IPMI; |
| message->rmcpMsgClass = ClassOfMsg::ASF; |
| message->asfMsgTag = header->msgTag; |
| |
| return message; |
| } |
| |
| std::vector<uint8_t> flatten(uint8_t asfMsgTag) |
| { |
| std::vector<uint8_t> packet(sizeof(AsfMessagePong_t)); |
| |
| // Insert RMCP header into the Packet |
| auto header = reinterpret_cast<AsfMessagePong_t*>(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<uint8_t>(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<uint8_t>(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 |