| #include "rakp12.hpp" |
| |
| #include "comm_module.hpp" |
| #include "endian.hpp" |
| #include "guid.hpp" |
| #include "sessions_manager.hpp" |
| |
| #include <openssl/rand.h> |
| |
| #include <ipmid/types.hpp> |
| #include <phosphor-logging/log.hpp> |
| |
| #include <algorithm> |
| #include <cstring> |
| #include <iomanip> |
| |
| using namespace phosphor::logging; |
| |
| namespace command |
| { |
| |
| bool isChannelAccessModeEnabled(const uint8_t accessMode) |
| { |
| return accessMode != |
| static_cast<uint8_t>(ipmi::EChannelAccessMode::disabled); |
| } |
| |
| void logInvalidLoginRedfishEvent(const std::string& journalMsg, |
| const std::optional<std::string>& messageArgs) |
| { |
| static constexpr std::string_view openBMCMessageRegistryVersion = "0.1."; |
| std::string messageID = "OpenBMC." + |
| std::string(openBMCMessageRegistryVersion) + |
| "InvalidLoginAttempted"; |
| phosphor::logging::log<phosphor::logging::level::ERR>( |
| journalMsg.c_str(), |
| phosphor::logging::entry("REDFISH_MESSAGE_ID=%s", messageID.c_str()), |
| phosphor::logging::entry("REDFISH_MESSAGE_ARGS=%s", |
| messageArgs.value().c_str())); |
| } |
| std::vector<uint8_t> RAKP12(const std::vector<uint8_t>& inPayload, |
| std::shared_ptr<message::Handler>& handler) |
| { |
| auto request = reinterpret_cast<const RAKP1request*>(inPayload.data()); |
| // verify inPayload minimum size |
| if (inPayload.size() < (sizeof(*request) - userNameMaxLen)) |
| { |
| std::vector<uint8_t> errorPayload{IPMI_CC_REQ_DATA_LEN_INVALID}; |
| return errorPayload; |
| } |
| |
| std::vector<uint8_t> outPayload(sizeof(RAKP2response)); |
| auto response = reinterpret_cast<RAKP2response*>(outPayload.data()); |
| |
| // Session ID zero is reserved for Session Setup |
| if (endian::from_ipmi(request->managedSystemSessionID) == |
| session::sessionZero) |
| { |
| log<level::INFO>("RAKP12: BMC invalid Session ID"); |
| response->rmcpStatusCode = |
| static_cast<uint8_t>(RAKP_ReturnCode::INVALID_SESSION_ID); |
| return outPayload; |
| } |
| |
| std::shared_ptr<session::Session> session; |
| try |
| { |
| session = session::Manager::get().getSession( |
| endian::from_ipmi(request->managedSystemSessionID)); |
| } |
| catch (const std::exception& e) |
| { |
| log<level::ERR>("RAKP12 : session not found", |
| entry("EXCEPTION=%s", e.what())); |
| response->rmcpStatusCode = |
| static_cast<uint8_t>(RAKP_ReturnCode::INVALID_SESSION_ID); |
| return outPayload; |
| } |
| |
| auto rakp1Size = |
| sizeof(RAKP1request) - (userNameMaxLen - request->user_name_len); |
| |
| std::string message = "Invalid login attempted via RCMPP interface "; |
| // Validate user name length in the message |
| if (request->user_name_len > userNameMaxLen || |
| inPayload.size() != rakp1Size) |
| { |
| response->rmcpStatusCode = |
| static_cast<uint8_t>(RAKP_ReturnCode::INVALID_NAME_LENGTH); |
| logInvalidLoginRedfishEvent(message); |
| return outPayload; |
| } |
| |
| session->userName.assign(request->user_name, request->user_name_len); |
| |
| // Update transaction time |
| session->updateLastTransactionTime(); |
| |
| auto rcSessionID = endian::to_ipmi(session->getRCSessionID()); |
| auto bmcSessionID = endian::to_ipmi(session->getBMCSessionID()); |
| auto authAlgo = session->getAuthAlgo(); |
| |
| /* |
| * Generate Key Authentication Code - RAKP 2 |
| * |
| * 1) Remote Console Session ID - 4 bytes |
| * 2) Managed System Session ID - 4 bytes |
| * 3) Remote Console Random Number - 16 bytes |
| * 4) Managed System Random Number - 16 bytes |
| * 5) Managed System GUID - 16 bytes |
| * 6) Requested Privilege Level - 1 byte |
| * 7) User Name Length Byte - 1 byte (0 for 'null' username) |
| * 8) User Name - variable (absent for 'null' username) |
| */ |
| |
| std::vector<uint8_t> input; |
| input.resize(sizeof(rcSessionID) + sizeof(bmcSessionID) + |
| cipher::rakp_auth::REMOTE_CONSOLE_RANDOM_NUMBER_LEN + |
| cipher::rakp_auth::BMC_RANDOM_NUMBER_LEN + BMC_GUID_LEN + |
| sizeof(request->req_max_privilege_level) + |
| sizeof(request->user_name_len) + session->userName.size()); |
| |
| auto iter = input.begin(); |
| |
| // Remote Console Session ID |
| std::copy_n(reinterpret_cast<uint8_t*>(&rcSessionID), sizeof(rcSessionID), |
| iter); |
| std::advance(iter, sizeof(rcSessionID)); |
| |
| // Managed System Session ID |
| std::copy_n(reinterpret_cast<uint8_t*>(&bmcSessionID), sizeof(bmcSessionID), |
| iter); |
| std::advance(iter, sizeof(bmcSessionID)); |
| |
| // Copy the Remote Console Random Number from the RAKP1 request to the |
| // Authentication Algorithm |
| std::copy_n( |
| reinterpret_cast<const uint8_t*>(request->remote_console_random_number), |
| cipher::rakp_auth::REMOTE_CONSOLE_RANDOM_NUMBER_LEN, |
| authAlgo->rcRandomNum.begin()); |
| |
| std::copy(authAlgo->rcRandomNum.begin(), authAlgo->rcRandomNum.end(), iter); |
| std::advance(iter, cipher::rakp_auth::REMOTE_CONSOLE_RANDOM_NUMBER_LEN); |
| |
| // Generate the Managed System Random Number |
| if (!RAND_bytes(input.data() + sizeof(rcSessionID) + sizeof(bmcSessionID) + |
| cipher::rakp_auth::REMOTE_CONSOLE_RANDOM_NUMBER_LEN, |
| cipher::rakp_auth::BMC_RANDOM_NUMBER_LEN)) |
| { |
| response->rmcpStatusCode = |
| static_cast<uint8_t>(RAKP_ReturnCode::INSUFFICIENT_RESOURCE); |
| return outPayload; |
| } |
| // As stated in Set Session Privilege Level command in IPMI Spec, when |
| // creating a session through Activate command / RAKP 1 message, it must |
| // be established with USER privilege as well as all other sessions are |
| // initially set to USER privilege, regardless of the requested maximum |
| // privilege. |
| if (!(static_cast<session::Privilege>(request->req_max_privilege_level & |
| session::reqMaxPrivMask) > |
| session::Privilege::CALLBACK)) |
| { |
| response->rmcpStatusCode = |
| static_cast<uint8_t>(RAKP_ReturnCode::UNAUTH_ROLE_PRIV); |
| return outPayload; |
| } |
| session->currentPrivilege(static_cast<uint8_t>(session::Privilege::USER)); |
| |
| session->reqMaxPrivLevel = |
| static_cast<session::Privilege>(request->req_max_privilege_level); |
| if (request->user_name_len == 0) |
| { |
| // Bail out, if user name is not specified. |
| // Yes, NULL user name is not supported for security reasons. |
| response->rmcpStatusCode = |
| static_cast<uint8_t>(RAKP_ReturnCode::UNAUTH_NAME); |
| logInvalidLoginRedfishEvent(message); |
| return outPayload; |
| } |
| |
| // Perform user name based lookup |
| std::string userName(request->user_name, request->user_name_len); |
| ipmi::SecureString passwd; |
| uint8_t userId = ipmi::ipmiUserGetUserId(userName); |
| if (userId == ipmi::invalidUserId) |
| { |
| response->rmcpStatusCode = |
| static_cast<uint8_t>(RAKP_ReturnCode::UNAUTH_NAME); |
| logInvalidLoginRedfishEvent(message); |
| return outPayload; |
| } |
| // check user is enabled before proceeding. |
| bool userEnabled = false; |
| ipmi::ipmiUserCheckEnabled(userId, userEnabled); |
| if (!userEnabled) |
| { |
| response->rmcpStatusCode = |
| static_cast<uint8_t>(RAKP_ReturnCode::INACTIVE_ROLE); |
| logInvalidLoginRedfishEvent(message); |
| return outPayload; |
| } |
| // Get the user password for RAKP message authenticate |
| passwd = ipmi::ipmiUserGetPassword(userName); |
| if (passwd.empty()) |
| { |
| response->rmcpStatusCode = |
| static_cast<uint8_t>(RAKP_ReturnCode::UNAUTH_NAME); |
| logInvalidLoginRedfishEvent(message); |
| return outPayload; |
| } |
| // Check whether user is already locked for failed attempts |
| if (!ipmi::ipmiUserPamAuthenticate(userName, passwd)) |
| { |
| log<level::ERR>("Authentication failed - user already locked out", |
| entry("USER-ID=%d", static_cast<uint8_t>(userId))); |
| |
| response->rmcpStatusCode = |
| static_cast<uint8_t>(RAKP_ReturnCode::UNAUTH_NAME); |
| logInvalidLoginRedfishEvent(message); |
| return outPayload; |
| } |
| |
| uint8_t chNum = static_cast<uint8_t>(getInterfaceIndex()); |
| // Get channel based access information |
| if ((ipmi::ipmiUserGetPrivilegeAccess( |
| userId, chNum, session->sessionUserPrivAccess) != IPMI_CC_OK) || |
| (ipmi::getChannelAccessData(chNum, session->sessionChannelAccess) != |
| IPMI_CC_OK)) |
| { |
| response->rmcpStatusCode = |
| static_cast<uint8_t>(RAKP_ReturnCode::INACTIVE_ROLE); |
| logInvalidLoginRedfishEvent(message); |
| return outPayload; |
| } |
| if (!isChannelAccessModeEnabled(session->sessionChannelAccess.accessMode)) |
| { |
| phosphor::logging::log<phosphor::logging::level::ERR>( |
| "Channel access mode disabled."); |
| response->rmcpStatusCode = |
| static_cast<uint8_t>(RAKP_ReturnCode::INACTIVE_ROLE); |
| logInvalidLoginRedfishEvent(message); |
| return outPayload; |
| } |
| if (session->sessionUserPrivAccess.privilege > |
| static_cast<uint8_t>(session::Privilege::OEM)) |
| { |
| response->rmcpStatusCode = |
| static_cast<uint8_t>(RAKP_ReturnCode::INACTIVE_ROLE); |
| logInvalidLoginRedfishEvent(message); |
| return outPayload; |
| } |
| session->channelNum(chNum); |
| session->userID(userId); |
| // minimum privilege of Channel / User / session::privilege::USER |
| // has to be used as session current privilege level |
| uint8_t minPriv = 0; |
| if (session->sessionChannelAccess.privLimit < |
| session->sessionUserPrivAccess.privilege) |
| { |
| minPriv = session->sessionChannelAccess.privLimit; |
| } |
| else |
| { |
| minPriv = session->sessionUserPrivAccess.privilege; |
| } |
| if (session->currentPrivilege() > minPriv) |
| { |
| session->currentPrivilege(static_cast<uint8_t>(minPriv)); |
| } |
| // For username / privilege lookup, fail with UNAUTH_NAME, if requested |
| // max privilege does not match user privilege |
| if (((request->req_max_privilege_level & userNameOnlyLookupMask) == |
| userNamePrivLookup) && |
| ((request->req_max_privilege_level & session::reqMaxPrivMask) != |
| session->sessionUserPrivAccess.privilege)) |
| { |
| log<level::INFO>( |
| "Username/Privilege lookup failed for requested privilege"); |
| response->rmcpStatusCode = |
| static_cast<uint8_t>(RAKP_ReturnCode::UNAUTH_NAME); |
| |
| logInvalidLoginRedfishEvent(message); |
| return outPayload; |
| } |
| |
| std::fill(authAlgo->userKey.data(), |
| authAlgo->userKey.data() + authAlgo->userKey.size(), 0); |
| std::copy_n(passwd.c_str(), passwd.size(), authAlgo->userKey.data()); |
| |
| // Copy the Managed System Random Number to the Authentication Algorithm |
| std::copy_n(iter, cipher::rakp_auth::BMC_RANDOM_NUMBER_LEN, |
| authAlgo->bmcRandomNum.begin()); |
| std::advance(iter, cipher::rakp_auth::BMC_RANDOM_NUMBER_LEN); |
| |
| // Managed System GUID |
| std::copy_n(cache::guid.data(), cache::guid.size(), iter); |
| std::advance(iter, BMC_GUID_LEN); |
| |
| // Requested Privilege Level |
| std::copy_n(&(request->req_max_privilege_level), |
| sizeof(request->req_max_privilege_level), iter); |
| std::advance(iter, sizeof(request->req_max_privilege_level)); |
| |
| // User Name Length Byte |
| std::copy_n(&(request->user_name_len), sizeof(request->user_name_len), |
| iter); |
| std::advance(iter, sizeof(request->user_name_len)); |
| |
| std::copy_n(session->userName.data(), session->userName.size(), iter); |
| |
| // Generate Key Exchange Authentication Code - RAKP2 |
| auto output = authAlgo->generateHMAC(input); |
| |
| response->messageTag = request->messageTag; |
| response->rmcpStatusCode = static_cast<uint8_t>(RAKP_ReturnCode::NO_ERROR); |
| response->reserved = 0; |
| response->remoteConsoleSessionID = rcSessionID; |
| |
| // Copy Managed System Random Number to the Response |
| std::copy(authAlgo->bmcRandomNum.begin(), authAlgo->bmcRandomNum.end(), |
| response->managed_system_random_number); |
| |
| // Copy System GUID to the Response |
| std::copy_n(cache::guid.data(), cache::guid.size(), |
| response->managed_system_guid); |
| |
| // Insert the HMAC output into the payload |
| outPayload.insert(outPayload.end(), output.begin(), output.end()); |
| return outPayload; |
| } |
| |
| } // namespace command |