blob: bec5c4f04e448db8c743121539a5b01e8e552fd8 [file] [log] [blame]
#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