/** @file | |
Implementation of getting bootstrap credential via IPMI. | |
Copyright (c) 2022-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. | |
SPDX-License-Identifier: BSD-2-Clause-Patent | |
@par Specification Reference: | |
- Redfish Host Interface Specification | |
(https://www.dmtf.org/sites/default/files/standards/documents/DSP0270_1.3.0.pdf) | |
**/ | |
#include "RedfishPlatformCredentialIpmiLib.h" | |
// | |
// Global flag of controlling credential service | |
// | |
BOOLEAN mRedfishServiceStopped = FALSE; | |
/** | |
Notify the Redfish service provide to stop provide configuration service to this platform. | |
This function should be called when the platform is about to leave the safe environment. | |
It will notify the Redfish service provider to abort all login session, and prohibit | |
further login with original auth info. GetAuthInfo() will return EFI_UNSUPPORTED once this | |
function is returned. | |
@param[in] This Pointer to EDKII_REDFISH_CREDENTIAL_PROTOCOL instance. | |
@param[in] ServiceStopType Reason of stopping Redfish service. | |
@retval EFI_SUCCESS Service has been stoped successfully. | |
@retval EFI_INVALID_PARAMETER This is NULL. | |
@retval Others Some error happened. | |
**/ | |
EFI_STATUS | |
EFIAPI | |
LibStopRedfishService ( | |
IN EDKII_REDFISH_CREDENTIAL_PROTOCOL *This, | |
IN EDKII_REDFISH_CREDENTIAL_STOP_SERVICE_TYPE ServiceStopType | |
) | |
{ | |
EFI_STATUS Status; | |
if ((ServiceStopType <= ServiceStopTypeNone) || (ServiceStopType >= ServiceStopTypeMax)) { | |
return EFI_INVALID_PARAMETER; | |
} | |
// | |
// Only stop credential service after leaving BIOS | |
// | |
if (ServiceStopType != ServiceStopTypeExitBootService) { | |
return EFI_UNSUPPORTED; | |
} | |
// | |
// Raise flag first | |
// | |
mRedfishServiceStopped = TRUE; | |
// | |
// Delete cached variable | |
// | |
Status = SetBootstrapAccountCredentialsToVariable (NULL, NULL, TRUE); | |
if (EFI_ERROR (Status)) { | |
DEBUG ((DEBUG_ERROR, "%a: fail to remove bootstrap credential: %r\n", __func__, Status)); | |
} | |
DEBUG ((DEBUG_MANAGEABILITY, "%a: bootstrap credential service stopped\n", __func__)); | |
return EFI_SUCCESS; | |
} | |
/** | |
Notification of Exit Boot Service. | |
@param[in] This Pointer to EDKII_REDFISH_CREDENTIAL_PROTOCOL. | |
**/ | |
VOID | |
EFIAPI | |
LibCredentialExitBootServicesNotify ( | |
IN EDKII_REDFISH_CREDENTIAL_PROTOCOL *This | |
) | |
{ | |
// | |
// Stop the credential support when system is about to enter OS. | |
// | |
LibStopRedfishService (This, ServiceStopTypeExitBootService); | |
} | |
/** | |
Notification of End of DXe. | |
@param[in] This Pointer to EDKII_REDFISH_CREDENTIAL_PROTOCOL. | |
**/ | |
VOID | |
EFIAPI | |
LibCredentialEndOfDxeNotify ( | |
IN EDKII_REDFISH_CREDENTIAL_PROTOCOL *This | |
) | |
{ | |
// | |
// Do nothing now. | |
// We can stop credential support when system reach end-of-dxe for security reason. | |
// | |
} | |
/** | |
Function to retrieve temporary user credentials for the UEFI redfish client. This function can | |
also disable bootstrap credential service in BMC. | |
@param[in] DisableBootstrapControl TRUE - Tell the BMC to disable the bootstrap credential | |
service to ensure no one else gains credentials | |
FALSE Allow the bootstrap credential service to continue | |
@param[in,out] BootstrapUsername A pointer to a Ascii encoded string for the credential username | |
When DisableBootstrapControl is TRUE, this pointer can be NULL | |
@param[in] BootstrapUsernameSize The size of BootstrapUsername including NULL terminator in bytes. | |
Per specification, the size is USERNAME_MAX_SIZE. | |
@param[in,out] BootstrapPassword A pointer to a Ascii encoded string for the credential password | |
When DisableBootstrapControl is TRUE, this pointer can be NULL | |
@param[in] BootstrapPasswordSize The size of BootstrapPassword including NULL terminator in bytes. | |
Per specification, the size is PASSWORD_MAX_SIZE. | |
@retval EFI_SUCCESS Credentials were successfully fetched and returned. When DisableBootstrapControl | |
is set to TRUE, the bootstrap credential service is disabled successfully. | |
@retval EFI_INVALID_PARAMETER BootstrapUsername or BootstrapPassword is NULL when DisableBootstrapControl | |
is set to FALSE. BootstrapUsernameSize or BootstrapPasswordSize is incorrect when | |
DisableBootstrapControl is set to FALSE. | |
@retval EFI_DEVICE_ERROR An IPMI failure occurred | |
**/ | |
EFI_STATUS | |
GetBootstrapAccountCredentials ( | |
IN BOOLEAN DisableBootstrapControl, | |
IN OUT CHAR8 *BootstrapUsername, OPTIONAL | |
IN UINTN BootstrapUsernameSize, | |
IN OUT CHAR8 *BootstrapPassword, OPTIONAL | |
IN UINTN BootstrapPasswordSize | |
) | |
{ | |
EFI_STATUS Status; | |
IPMI_BOOTSTRAP_CREDENTIALS_COMMAND_DATA CommandData; | |
IPMI_BOOTSTRAP_CREDENTIALS_RESULT_RESPONSE ResponseData; | |
UINT32 ResponseSize; | |
// | |
// NULL buffer check | |
// | |
if (!DisableBootstrapControl && ((BootstrapUsername == NULL) || (BootstrapPassword == NULL))) { | |
return EFI_INVALID_PARAMETER; | |
} | |
if ((BootstrapUsernameSize != USERNAME_MAX_SIZE) || (BootstrapPasswordSize != PASSWORD_MAX_SIZE)) { | |
return EFI_INVALID_PARAMETER; | |
} | |
DEBUG ((DEBUG_VERBOSE, "%a: Disable bootstrap control: 0x%x\n", __func__, DisableBootstrapControl)); | |
// | |
// IPMI callout to NetFn 2C, command 02 | |
// Request data: | |
// Byte 1: REDFISH_IPMI_GROUP_EXTENSION | |
// Byte 2: DisableBootstrapControl | |
// | |
CommandData.GroupExtensionId = REDFISH_IPMI_GROUP_EXTENSION; | |
CommandData.DisableBootstrapControl = (DisableBootstrapControl ? REDFISH_IPMI_BOOTSTRAP_CREDENTIAL_DISABLE : REDFISH_IPMI_BOOTSTRAP_CREDENTIAL_ENABLE); | |
ResponseSize = sizeof (ResponseData); | |
// | |
// Response data: | |
// Byte 1 : Completion code | |
// Byte 2 : REDFISH_IPMI_GROUP_EXTENSION | |
// Byte 3-18 : Username | |
// Byte 19-34: Password | |
// | |
Status = IpmiSubmitCommand ( | |
IPMI_NETFN_GROUP_EXT, | |
REDFISH_IPMI_GET_BOOTSTRAP_CREDENTIALS_CMD, | |
(UINT8 *)&CommandData, | |
sizeof (CommandData), | |
(UINT8 *)&ResponseData, | |
&ResponseSize | |
); | |
if (EFI_ERROR (Status)) { | |
DEBUG ((DEBUG_ERROR, "%a: IPMI transaction failure. Returning\n", __func__)); | |
return Status; | |
} else { | |
if (ResponseData.CompletionCode != IPMI_COMP_CODE_NORMAL) { | |
if (ResponseData.CompletionCode == REDFISH_IPMI_COMP_CODE_BOOTSTRAP_CREDENTIAL_DISABLED) { | |
DEBUG ((DEBUG_ERROR, "%a: bootstrap credential support was disabled\n", __func__)); | |
return EFI_ACCESS_DENIED; | |
} | |
DEBUG ((DEBUG_ERROR, "%a: Completion code = 0x%x. Returning\n", __func__, ResponseData.CompletionCode)); | |
return EFI_PROTOCOL_ERROR; | |
} else if (ResponseData.GroupExtensionId != REDFISH_IPMI_GROUP_EXTENSION) { | |
DEBUG ((DEBUG_ERROR, "%a: Group Extension Response = 0x%x. Returning\n", __func__, ResponseData.GroupExtensionId)); | |
return EFI_DEVICE_ERROR; | |
} else { | |
if (BootstrapUsername != NULL) { | |
CopyMem (BootstrapUsername, ResponseData.Username, USERNAME_MAX_LENGTH); | |
// | |
// Manually append null-terminator in case 16 characters username returned. | |
// | |
BootstrapUsername[USERNAME_MAX_LENGTH] = '\0'; | |
} | |
if (BootstrapPassword != NULL) { | |
CopyMem (BootstrapPassword, ResponseData.Password, PASSWORD_MAX_LENGTH); | |
// | |
// Manually append null-terminator in case 16 characters password returned. | |
// | |
BootstrapPassword[PASSWORD_MAX_LENGTH] = '\0'; | |
} | |
} | |
} | |
DEBUG ((DEBUG_MANAGEABILITY, "%a: get bootstrap credential via IPMI: %r\n", __func__, Status)); | |
return Status; | |
} | |
/** | |
Function to retrieve temporary user credentials from cached boot time variable. | |
@param[in,out] BootstrapUsername A pointer to a Ascii encoded string for the credential username. | |
@param[in] BootstrapUsernameSize The size of BootstrapUsername including NULL terminator in bytes. | |
Per specification, the size is USERNAME_MAX_SIZE. | |
@param[in,out] BootstrapPassword A pointer to a Ascii encoded string for the credential password. | |
@param[in] BootstrapPasswordSize The size of BootstrapPassword including NULL terminator in bytes. | |
Per specification, the size is PASSWORD_MAX_SIZE. | |
@retval EFI_SUCCESS Credentials were successfully fetched and returned. | |
@retval EFI_INVALID_PARAMETER BootstrapUsername or BootstrapPassword is NULL. | |
BootstrapUsernameSize or BootstrapPasswordSize is incorrect. | |
@retval EFI_NOT_FOUND No variable found for account and credentials. | |
**/ | |
EFI_STATUS | |
GetBootstrapAccountCredentialsFromVariable ( | |
IN OUT CHAR8 *BootstrapUsername, | |
IN UINTN BootstrapUsernameSize, | |
IN OUT CHAR8 *BootstrapPassword, | |
IN UINTN BootstrapPasswordSize | |
) | |
{ | |
EFI_STATUS Status; | |
BOOTSTRAP_CREDENTIALS_VARIABLE *CredentialVariable; | |
VOID *Data; | |
UINTN DataSize; | |
if ((BootstrapUsername == NULL) || (BootstrapPassword == NULL)) { | |
return EFI_INVALID_PARAMETER; | |
} | |
if ((BootstrapUsernameSize != USERNAME_MAX_SIZE) || (BootstrapPasswordSize != PASSWORD_MAX_SIZE)) { | |
return EFI_INVALID_PARAMETER; | |
} | |
DataSize = 0; | |
Status = GetVariable2 ( | |
CREDENTIAL_VARIABLE_NAME, | |
&gEfiRedfishVariableGuid, | |
(VOID *)&Data, | |
&DataSize | |
); | |
if (EFI_ERROR (Status)) { | |
return EFI_NOT_FOUND; | |
} | |
if (DataSize != sizeof (BOOTSTRAP_CREDENTIALS_VARIABLE)) { | |
DEBUG ((DEBUG_ERROR, "%a: data corruption. returned size: %d != structure size: %d\n", __func__, DataSize, sizeof (BOOTSTRAP_CREDENTIALS_VARIABLE))); | |
FreePool (Data); | |
return EFI_NOT_FOUND; | |
} | |
CredentialVariable = (BOOTSTRAP_CREDENTIALS_VARIABLE *)Data; | |
AsciiStrCpyS (BootstrapUsername, USERNAME_MAX_SIZE, CredentialVariable->Username); | |
AsciiStrCpyS (BootstrapPassword, PASSWORD_MAX_SIZE, CredentialVariable->Password); | |
ZeroMem (CredentialVariable->Username, USERNAME_MAX_SIZE); | |
ZeroMem (CredentialVariable->Password, PASSWORD_MAX_SIZE); | |
FreePool (Data); | |
DEBUG ((DEBUG_MANAGEABILITY, "%a: get bootstrap credential from variable\n", __func__)); | |
return EFI_SUCCESS; | |
} | |
/** | |
Function to save temporary user credentials into boot time variable. When DeleteVariable is True, | |
this function delete boot time variable. | |
@param[in] BootstrapUsername A pointer to a Ascii encoded string for the credential username. | |
@param[in] BootstrapPassword A pointer to a Ascii encoded string for the credential password. | |
@param[in] DeleteVariable True to remove boot time variable. False otherwise. | |
@retval EFI_SUCCESS Credentials were successfully saved. | |
@retval EFI_INVALID_PARAMETER BootstrapUsername or BootstrapPassword is NULL | |
@retval Others Error occurs | |
**/ | |
EFI_STATUS | |
SetBootstrapAccountCredentialsToVariable ( | |
IN CHAR8 *BootstrapUsername, OPTIONAL | |
IN CHAR8 *BootstrapPassword, OPTIONAL | |
IN BOOLEAN DeleteVariable | |
) | |
{ | |
EFI_STATUS Status; | |
BOOTSTRAP_CREDENTIALS_VARIABLE CredentialVariable; | |
VOID *Data; | |
if (!DeleteVariable && ((BootstrapUsername == NULL) || (BootstrapUsername[0] == '\0'))) { | |
return EFI_INVALID_PARAMETER; | |
} | |
if (!DeleteVariable && ((BootstrapPassword == NULL) || (BootstrapPassword[0] == '\0'))) { | |
return EFI_INVALID_PARAMETER; | |
} | |
// | |
// Delete variable | |
// | |
Status = GetVariable2 ( | |
CREDENTIAL_VARIABLE_NAME, | |
&gEfiRedfishVariableGuid, | |
(VOID *)&Data, | |
NULL | |
); | |
if (!EFI_ERROR (Status)) { | |
FreePool (Data); | |
gRT->SetVariable ( | |
CREDENTIAL_VARIABLE_NAME, | |
&gEfiRedfishVariableGuid, | |
EFI_VARIABLE_BOOTSERVICE_ACCESS, | |
0, | |
NULL | |
); | |
} | |
// | |
// This is request to delete credentials. We are done. | |
// | |
if (DeleteVariable) { | |
return EFI_SUCCESS; | |
} | |
ZeroMem (CredentialVariable.Username, USERNAME_MAX_SIZE); | |
ZeroMem (CredentialVariable.Password, PASSWORD_MAX_SIZE); | |
AsciiStrCpyS (CredentialVariable.Username, USERNAME_MAX_SIZE, BootstrapUsername); | |
AsciiStrCpyS (CredentialVariable.Password, PASSWORD_MAX_SIZE, BootstrapPassword); | |
Status = gRT->SetVariable ( | |
CREDENTIAL_VARIABLE_NAME, | |
&gEfiRedfishVariableGuid, | |
EFI_VARIABLE_BOOTSERVICE_ACCESS, | |
sizeof (BOOTSTRAP_CREDENTIALS_VARIABLE), | |
(VOID *)&CredentialVariable | |
); | |
ZeroMem (CredentialVariable.Username, USERNAME_MAX_SIZE); | |
ZeroMem (CredentialVariable.Password, PASSWORD_MAX_SIZE); | |
return Status; | |
} | |
/** | |
Retrieve platform's Redfish authentication information. | |
This functions returns the Redfish authentication method together with the user Id and | |
password. | |
- For AuthMethodNone, the UserId and Password could be used for HTTP header authentication | |
as defined by RFC7235. | |
- For AuthMethodRedfishSession, the UserId and Password could be used for Redfish | |
session login as defined by Redfish API specification (DSP0266). | |
Callers are responsible for and freeing the returned string storage. | |
@param[in] This Pointer to EDKII_REDFISH_CREDENTIAL_PROTOCOL instance. | |
@param[out] AuthMethod Type of Redfish authentication method. | |
@param[out] UserId The pointer to store the returned UserId string. | |
@param[out] Password The pointer to store the returned Password string. | |
@retval EFI_SUCCESS Get the authentication information successfully. | |
@retval EFI_ACCESS_DENIED SecureBoot is disabled after EndOfDxe. | |
@retval EFI_INVALID_PARAMETER This or AuthMethod or UserId or Password is NULL. | |
@retval EFI_OUT_OF_RESOURCES There are not enough memory resources. | |
@retval EFI_UNSUPPORTED Unsupported authentication method is found. | |
**/ | |
EFI_STATUS | |
EFIAPI | |
LibCredentialGetAuthInfo ( | |
IN EDKII_REDFISH_CREDENTIAL_PROTOCOL *This, | |
OUT EDKII_REDFISH_AUTH_METHOD *AuthMethod, | |
OUT CHAR8 **UserId, | |
OUT CHAR8 **Password | |
) | |
{ | |
EFI_STATUS Status; | |
BOOLEAN DisableCredentialService; | |
if ((AuthMethod == NULL) || (UserId == NULL) || (Password == NULL)) { | |
return EFI_INVALID_PARAMETER; | |
} | |
*UserId = NULL; | |
*Password = NULL; | |
DisableCredentialService = PcdGetBool (PcdRedfishDisableBootstrapCredentialService); | |
if (mRedfishServiceStopped) { | |
DEBUG ((DEBUG_ERROR, "%a: credential service is stopped due to security reason\n", __func__)); | |
return EFI_ACCESS_DENIED; | |
} | |
*AuthMethod = AuthMethodHttpBasic; | |
*UserId = AllocateZeroPool (sizeof (CHAR8) * USERNAME_MAX_SIZE); | |
if (*UserId == NULL) { | |
return EFI_OUT_OF_RESOURCES; | |
} | |
*Password = AllocateZeroPool (sizeof (CHAR8) * PASSWORD_MAX_SIZE); | |
if (*Password == NULL) { | |
return EFI_OUT_OF_RESOURCES; | |
} | |
// | |
// Get bootstrap credential from variable first | |
// | |
Status = GetBootstrapAccountCredentialsFromVariable (*UserId, USERNAME_MAX_SIZE, *Password, PASSWORD_MAX_SIZE); | |
if (!EFI_ERROR (Status)) { | |
return EFI_SUCCESS; | |
} | |
// | |
// Make a IPMI query | |
// | |
Status = GetBootstrapAccountCredentials (DisableCredentialService, *UserId, USERNAME_MAX_SIZE, *Password, PASSWORD_MAX_SIZE); | |
if (EFI_ERROR (Status)) { | |
DEBUG ((DEBUG_ERROR, "%a: fail to get bootstrap credential: %r\n", __func__, Status)); | |
return Status; | |
} | |
if (DisableCredentialService) { | |
DEBUG ((DEBUG_MANAGEABILITY, "%a: credential bootstrapping control disabled\n", __func__)); | |
} | |
Status = SetBootstrapAccountCredentialsToVariable (*UserId, *Password, FALSE); | |
if (EFI_ERROR (Status)) { | |
DEBUG ((DEBUG_ERROR, "%a: fail to cache bootstrap credential: %r\n", __func__, Status)); | |
} | |
return EFI_SUCCESS; | |
} |