CloverBootloader/OvmfPkg/EnrollDefaultKeys/EnrollDefaultKeys.c

853 lines
25 KiB
C

/** @file
Enroll default PK, KEK, db, dbx.
Copyright (C) 2014-2019, Red Hat, Inc.
SPDX-License-Identifier: BSD-2-Clause-Patent
**/
#include <Guid/AuthenticatedVariableFormat.h> // gEfiCustomModeEnableGuid
#include <Guid/GlobalVariable.h> // EFI_SETUP_MODE_NAME
#include <Guid/ImageAuthentication.h> // EFI_IMAGE_SECURITY_DATABASE
#include <Guid/MicrosoftVendor.h> // gMicrosoftVendorGuid
#include <Guid/OvmfPkKek1AppPrefix.h> // gOvmfPkKek1AppPrefixGuid
#include <IndustryStandard/SmBios.h> // SMBIOS_HANDLE_PI_RESERVED
#include <Library/BaseLib.h> // GUID_STRING_LENGTH
#include <Library/BaseMemoryLib.h> // CopyGuid()
#include <Library/DebugLib.h> // ASSERT()
#include <Library/MemoryAllocationLib.h> // FreePool()
#include <Library/PrintLib.h> // AsciiSPrint()
#include <Library/ShellCEntryLib.h> // ShellAppMain()
#include <Library/UefiBootServicesTableLib.h> // gBS
#include <Library/UefiLib.h> // AsciiPrint()
#include <Library/UefiRuntimeServicesTableLib.h> // gRT
#include <Protocol/Smbios.h> // EFI_SMBIOS_PROTOCOL
#include "EnrollDefaultKeys.h"
/**
Fetch the X509 certificate (to be used as Platform Key and first Key Exchange
Key) from SMBIOS.
@param[out] PkKek1 The X509 certificate in DER encoding from the
hypervisor, to be enrolled as PK and first KEK
entry. On success, the caller is responsible for
releasing PkKek1 with FreePool().
@param[out] SizeOfPkKek1 The size of PkKek1 in bytes.
@retval EFI_SUCCESS PkKek1 and SizeOfPkKek1 have been set
successfully.
@retval EFI_NOT_FOUND An OEM String matching
OVMF_PK_KEK1_APP_PREFIX_GUID has not been
found.
@retval EFI_PROTOCOL_ERROR In the OEM String matching
OVMF_PK_KEK1_APP_PREFIX_GUID, the certificate
is empty, or it has invalid base64 encoding.
@retval EFI_OUT_OF_RESOURCES Memory allocation failed.
@return Error codes from gBS->LocateProtocol().
**/
STATIC
EFI_STATUS
GetPkKek1 (
OUT UINT8 **PkKek1,
OUT UINTN *SizeOfPkKek1
)
{
CONST CHAR8 *Base64Cert;
CHAR8 OvmfPkKek1AppPrefix[GUID_STRING_LENGTH + 1 + 1];
EFI_STATUS Status;
EFI_SMBIOS_PROTOCOL *Smbios;
EFI_SMBIOS_HANDLE Handle;
EFI_SMBIOS_TYPE Type;
EFI_SMBIOS_TABLE_HEADER *Header;
SMBIOS_TABLE_TYPE11 *OemStringsTable;
UINTN Base64CertLen;
UINTN DecodedCertSize;
UINT8 *DecodedCert;
Base64Cert = NULL;
//
// Format the application prefix, for OEM String matching.
//
AsciiSPrint (
OvmfPkKek1AppPrefix,
sizeof OvmfPkKek1AppPrefix,
"%g:",
&gOvmfPkKek1AppPrefixGuid
);
//
// Scan all "OEM Strings" tables.
//
Status = gBS->LocateProtocol (
&gEfiSmbiosProtocolGuid,
NULL,
(VOID **)&Smbios
);
if (EFI_ERROR (Status)) {
AsciiPrint ("error: failed to locate EFI_SMBIOS_PROTOCOL: %r\n", Status);
return Status;
}
Handle = SMBIOS_HANDLE_PI_RESERVED;
Type = SMBIOS_TYPE_OEM_STRINGS;
for (Status = Smbios->GetNext (Smbios, &Handle, &Type, &Header, NULL);
!EFI_ERROR (Status);
Status = Smbios->GetNext (Smbios, &Handle, &Type, &Header, NULL))
{
CONST CHAR8 *OemString;
UINTN Idx;
if (Header->Length < sizeof *OemStringsTable) {
//
// Malformed table header, skip to next.
//
continue;
}
OemStringsTable = (SMBIOS_TABLE_TYPE11 *)Header;
//
// Scan all strings in the unformatted area of the current "OEM Strings"
// table.
//
OemString = (CONST CHAR8 *)(OemStringsTable + 1);
for (Idx = 0; Idx < OemStringsTable->StringCount; ++Idx) {
CHAR8 CandidatePrefix[sizeof OvmfPkKek1AppPrefix];
//
// NUL-terminate the candidate prefix for case-insensitive comparison.
//
AsciiStrnCpyS (
CandidatePrefix,
sizeof CandidatePrefix,
OemString,
GUID_STRING_LENGTH + 1
);
if (AsciiStriCmp (OvmfPkKek1AppPrefix, CandidatePrefix) == 0) {
//
// The current string matches the prefix.
//
Base64Cert = OemString + GUID_STRING_LENGTH + 1;
break;
}
OemString += AsciiStrSize (OemString);
}
if (Idx < OemStringsTable->StringCount) {
//
// The current table has a matching string.
//
break;
}
}
if (EFI_ERROR (Status)) {
//
// No table with a matching string has been found.
//
AsciiPrint (
"error: OEM String with app prefix %g not found: %r\n",
&gOvmfPkKek1AppPrefixGuid,
Status
);
return EFI_NOT_FOUND;
}
ASSERT (Base64Cert != NULL);
Base64CertLen = AsciiStrLen (Base64Cert);
//
// Verify the base64 encoding, and determine the decoded size.
//
DecodedCertSize = 0;
Status = Base64Decode (Base64Cert, Base64CertLen, NULL, &DecodedCertSize);
switch (Status) {
case EFI_BUFFER_TOO_SMALL:
ASSERT (DecodedCertSize > 0);
break;
case EFI_SUCCESS:
AsciiPrint (
"error: empty certificate after app prefix %g\n",
&gOvmfPkKek1AppPrefixGuid
);
return EFI_PROTOCOL_ERROR;
default:
AsciiPrint (
"error: invalid base64 string after app prefix %g\n",
&gOvmfPkKek1AppPrefixGuid
);
return EFI_PROTOCOL_ERROR;
}
//
// Allocate the output buffer.
//
DecodedCert = AllocatePool (DecodedCertSize);
if (DecodedCert == NULL) {
AsciiPrint ("error: failed to allocate memory\n");
return EFI_OUT_OF_RESOURCES;
}
//
// Decoding will succeed at this point.
//
Status = Base64Decode (
Base64Cert,
Base64CertLen,
DecodedCert,
&DecodedCertSize
);
ASSERT_EFI_ERROR (Status);
*PkKek1 = DecodedCert;
*SizeOfPkKek1 = DecodedCertSize;
return EFI_SUCCESS;
}
/**
Enroll a set of certificates in a global variable, overwriting it.
The variable will be rewritten with NV+BS+RT+AT attributes.
@param[in] VariableName The name of the variable to overwrite.
@param[in] VendorGuid The namespace (ie. vendor GUID) of the variable to
overwrite.
@param[in] CertType The GUID determining the type of all the
certificates in the set that is passed in. For
example, gEfiCertX509Guid stands for DER-encoded
X.509 certificates, while gEfiCertSha256Guid stands
for SHA256 image hashes.
@param[in] ... A list of
IN CONST UINT8 *Cert,
IN UINTN CertSize,
IN CONST EFI_GUID *OwnerGuid
triplets. If the first component of a triplet is
NULL, then the other two components are not
accessed, and processing is terminated. The list of
certificates is enrolled in the variable specified,
overwriting it. The OwnerGuid component identifies
the agent installing the certificate.
@retval EFI_INVALID_PARAMETER The triplet list is empty (ie. the first Cert
value is NULL), or one of the CertSize values
is 0, or one of the CertSize values would
overflow the accumulated UINT32 data size.
@retval EFI_OUT_OF_RESOURCES Out of memory while formatting variable
payload.
@retval EFI_SUCCESS Enrollment successful; the variable has been
overwritten (or created).
@return Error codes from gRT->GetTime() and
gRT->SetVariable().
**/
STATIC
EFI_STATUS
EFIAPI
EnrollListOfCerts (
IN CHAR16 *VariableName,
IN EFI_GUID *VendorGuid,
IN EFI_GUID *CertType,
...
)
{
UINTN DataSize;
SINGLE_HEADER *SingleHeader;
REPEATING_HEADER *RepeatingHeader;
VA_LIST Marker;
CONST UINT8 *Cert;
EFI_STATUS Status;
UINT8 *Data;
UINT8 *Position;
Status = EFI_SUCCESS;
//
// compute total size first, for UINT32 range check, and allocation
//
DataSize = sizeof *SingleHeader;
VA_START (Marker, CertType);
for (Cert = VA_ARG (Marker, CONST UINT8 *);
Cert != NULL;
Cert = VA_ARG (Marker, CONST UINT8 *))
{
UINTN CertSize;
CertSize = VA_ARG (Marker, UINTN);
(VOID)VA_ARG (Marker, CONST EFI_GUID *);
if ((CertSize == 0) ||
(CertSize > MAX_UINT32 - sizeof *RepeatingHeader) ||
(DataSize > MAX_UINT32 - sizeof *RepeatingHeader - CertSize))
{
Status = EFI_INVALID_PARAMETER;
break;
}
DataSize += sizeof *RepeatingHeader + CertSize;
}
VA_END (Marker);
if (DataSize == sizeof *SingleHeader) {
Status = EFI_INVALID_PARAMETER;
}
if (EFI_ERROR (Status)) {
goto Out;
}
Data = AllocatePool (DataSize);
if (Data == NULL) {
Status = EFI_OUT_OF_RESOURCES;
goto Out;
}
Position = Data;
SingleHeader = (SINGLE_HEADER *)Position;
Status = gRT->GetTime (&SingleHeader->TimeStamp, NULL);
if (EFI_ERROR (Status)) {
goto FreeData;
}
SingleHeader->TimeStamp.Pad1 = 0;
SingleHeader->TimeStamp.Nanosecond = 0;
SingleHeader->TimeStamp.TimeZone = 0;
SingleHeader->TimeStamp.Daylight = 0;
SingleHeader->TimeStamp.Pad2 = 0;
#if 0
SingleHeader->dwLength = DataSize - sizeof SingleHeader->TimeStamp;
#else
//
// This looks like a bug in edk2. According to the UEFI specification,
// dwLength is "The length of the entire certificate, including the length of
// the header, in bytes". That shouldn't stop right after CertType -- it
// should include everything below it.
//
SingleHeader->dwLength = sizeof *SingleHeader
- sizeof SingleHeader->TimeStamp;
#endif
SingleHeader->wRevision = 0x0200;
SingleHeader->wCertificateType = WIN_CERT_TYPE_EFI_GUID;
CopyGuid (&SingleHeader->CertType, &gEfiCertPkcs7Guid);
Position += sizeof *SingleHeader;
VA_START (Marker, CertType);
for (Cert = VA_ARG (Marker, CONST UINT8 *);
Cert != NULL;
Cert = VA_ARG (Marker, CONST UINT8 *))
{
UINTN CertSize;
CONST EFI_GUID *OwnerGuid;
CertSize = VA_ARG (Marker, UINTN);
OwnerGuid = VA_ARG (Marker, CONST EFI_GUID *);
RepeatingHeader = (REPEATING_HEADER *)Position;
CopyGuid (&RepeatingHeader->SignatureType, CertType);
RepeatingHeader->SignatureListSize =
(UINT32)(sizeof *RepeatingHeader + CertSize);
RepeatingHeader->SignatureHeaderSize = 0;
RepeatingHeader->SignatureSize =
(UINT32)(sizeof RepeatingHeader->SignatureOwner + CertSize);
CopyGuid (&RepeatingHeader->SignatureOwner, OwnerGuid);
Position += sizeof *RepeatingHeader;
CopyMem (Position, Cert, CertSize);
Position += CertSize;
}
VA_END (Marker);
ASSERT (Data + DataSize == Position);
Status = gRT->SetVariable (
VariableName,
VendorGuid,
(EFI_VARIABLE_NON_VOLATILE |
EFI_VARIABLE_BOOTSERVICE_ACCESS |
EFI_VARIABLE_RUNTIME_ACCESS |
EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS),
DataSize,
Data
);
FreeData:
FreePool (Data);
Out:
if (EFI_ERROR (Status)) {
AsciiPrint (
"error: %a(\"%s\", %g): %r\n",
__func__,
VariableName,
VendorGuid,
Status
);
}
return Status;
}
/**
Read a UEFI variable into a caller-allocated buffer, enforcing an exact size.
@param[in] VariableName The name of the variable to read; passed to
gRT->GetVariable().
@param[in] VendorGuid The vendor (namespace) GUID of the variable to read;
passed to gRT->GetVariable().
@param[out] Data The caller-allocated buffer that is supposed to
receive the variable's contents. On error, the
contents of Data are indeterminate.
@param[in] DataSize The size in bytes that the caller requires the UEFI
variable to have. The caller is responsible for
providing room for DataSize bytes in Data.
@param[in] AllowMissing If FALSE, the variable is required to exist. If
TRUE, the variable is permitted to be missing.
@retval EFI_SUCCESS The UEFI variable exists, has the required size
(DataSize), and has been read into Data.
@retval EFI_SUCCESS The UEFI variable doesn't exist, and
AllowMissing is TRUE. DataSize bytes in Data
have been zeroed out.
@retval EFI_NOT_FOUND The UEFI variable doesn't exist, and
AllowMissing is FALSE.
@retval EFI_BUFFER_TOO_SMALL The UEFI variable exists, but its size is
greater than DataSize.
@retval EFI_PROTOCOL_ERROR The UEFI variable exists, but its size is
smaller than DataSize.
@return Error codes propagated from gRT->GetVariable().
**/
STATIC
EFI_STATUS
GetExact (
IN CHAR16 *VariableName,
IN EFI_GUID *VendorGuid,
OUT VOID *Data,
IN UINTN DataSize,
IN BOOLEAN AllowMissing
)
{
UINTN Size;
EFI_STATUS Status;
Size = DataSize;
Status = gRT->GetVariable (VariableName, VendorGuid, NULL, &Size, Data);
if (EFI_ERROR (Status)) {
if ((Status == EFI_NOT_FOUND) && AllowMissing) {
ZeroMem (Data, DataSize);
return EFI_SUCCESS;
}
AsciiPrint (
"error: GetVariable(\"%s\", %g): %r\n",
VariableName,
VendorGuid,
Status
);
return Status;
}
if (Size != DataSize) {
AsciiPrint (
"error: GetVariable(\"%s\", %g): expected size 0x%Lx, "
"got 0x%Lx\n",
VariableName,
VendorGuid,
(UINT64)DataSize,
(UINT64)Size
);
return EFI_PROTOCOL_ERROR;
}
return EFI_SUCCESS;
}
/**
Populate a SETTINGS structure from the underlying UEFI variables.
The following UEFI variables are standard variables:
- L"SetupMode" (EFI_SETUP_MODE_NAME)
- L"SecureBoot" (EFI_SECURE_BOOT_MODE_NAME)
- L"VendorKeys" (EFI_VENDOR_KEYS_VARIABLE_NAME)
The following UEFI variables are edk2 extensions:
- L"SecureBootEnable" (EFI_SECURE_BOOT_ENABLE_NAME)
- L"CustomMode" (EFI_CUSTOM_MODE_NAME)
The L"SecureBootEnable" UEFI variable is permitted to be missing, in which
case the corresponding field in the SETTINGS object will be zeroed out. The
rest of the covered UEFI variables are required to exist; otherwise, the
function will fail.
@param[out] Settings The SETTINGS object to fill.
@retval EFI_SUCCESS Settings has been populated.
@return Error codes propagated from the GetExact() function. The
contents of Settings are indeterminate.
**/
STATIC
EFI_STATUS
GetSettings (
OUT SETTINGS *Settings
)
{
EFI_STATUS Status;
Status = GetExact (
EFI_SETUP_MODE_NAME,
&gEfiGlobalVariableGuid,
&Settings->SetupMode,
sizeof Settings->SetupMode,
FALSE
);
if (EFI_ERROR (Status)) {
return Status;
}
Status = GetExact (
EFI_SECURE_BOOT_MODE_NAME,
&gEfiGlobalVariableGuid,
&Settings->SecureBoot,
sizeof Settings->SecureBoot,
FALSE
);
if (EFI_ERROR (Status)) {
return Status;
}
Status = GetExact (
EFI_SECURE_BOOT_ENABLE_NAME,
&gEfiSecureBootEnableDisableGuid,
&Settings->SecureBootEnable,
sizeof Settings->SecureBootEnable,
TRUE
);
if (EFI_ERROR (Status)) {
return Status;
}
Status = GetExact (
EFI_CUSTOM_MODE_NAME,
&gEfiCustomModeEnableGuid,
&Settings->CustomMode,
sizeof Settings->CustomMode,
FALSE
);
if (EFI_ERROR (Status)) {
return Status;
}
Status = GetExact (
EFI_VENDOR_KEYS_VARIABLE_NAME,
&gEfiGlobalVariableGuid,
&Settings->VendorKeys,
sizeof Settings->VendorKeys,
FALSE
);
return Status;
}
/**
Print the contents of a SETTINGS structure to the UEFI console.
@param[in] Settings The SETTINGS object to print the contents of.
**/
STATIC
VOID
PrintSettings (
IN CONST SETTINGS *Settings
)
{
AsciiPrint (
"info: SetupMode=%d SecureBoot=%d SecureBootEnable=%d "
"CustomMode=%d VendorKeys=%d\n",
Settings->SetupMode,
Settings->SecureBoot,
Settings->SecureBootEnable,
Settings->CustomMode,
Settings->VendorKeys
);
}
/**
Entry point function of this shell application.
**/
INTN
EFIAPI
ShellAppMain (
IN UINTN Argc,
IN CHAR16 **Argv
)
{
INTN RetVal;
EFI_STATUS Status;
SETTINGS Settings;
UINT8 *PkKek1;
UINTN SizeOfPkKek1;
BOOLEAN NoDefault;
if ((Argc == 2) && (StrCmp (Argv[1], L"--no-default") == 0)) {
NoDefault = TRUE;
} else {
NoDefault = FALSE;
}
//
// Prepare for failure.
//
RetVal = 1;
//
// If we're not in Setup Mode, we can't do anything.
//
Status = GetSettings (&Settings);
if (EFI_ERROR (Status)) {
return RetVal;
}
PrintSettings (&Settings);
if (Settings.SetupMode != 1) {
AsciiPrint ("error: already in User Mode\n");
return RetVal;
}
//
// Set PkKek1 and SizeOfPkKek1 to suppress incorrect compiler/analyzer
// warnings.
//
PkKek1 = NULL;
SizeOfPkKek1 = 0;
//
// Fetch the X509 certificate (to be used as Platform Key and first Key
// Exchange Key) from SMBIOS.
//
Status = GetPkKek1 (&PkKek1, &SizeOfPkKek1);
if (EFI_ERROR (Status)) {
return RetVal;
}
//
// Enter Custom Mode so we can enroll PK, KEK, db, and dbx without signature
// checks on those variable writes.
//
if (Settings.CustomMode != CUSTOM_SECURE_BOOT_MODE) {
Settings.CustomMode = CUSTOM_SECURE_BOOT_MODE;
Status = gRT->SetVariable (
EFI_CUSTOM_MODE_NAME,
&gEfiCustomModeEnableGuid,
(EFI_VARIABLE_NON_VOLATILE |
EFI_VARIABLE_BOOTSERVICE_ACCESS),
sizeof Settings.CustomMode,
&Settings.CustomMode
);
if (EFI_ERROR (Status)) {
AsciiPrint (
"error: SetVariable(\"%s\", %g): %r\n",
EFI_CUSTOM_MODE_NAME,
&gEfiCustomModeEnableGuid,
Status
);
goto FreePkKek1;
}
}
//
// Enroll db.
//
if (NoDefault) {
Status = EnrollListOfCerts (
EFI_IMAGE_SECURITY_DATABASE,
&gEfiImageSecurityDatabaseGuid,
&gEfiCertX509Guid,
PkKek1,
SizeOfPkKek1,
&gEfiCallerIdGuid,
NULL
);
} else {
Status = EnrollListOfCerts (
EFI_IMAGE_SECURITY_DATABASE,
&gEfiImageSecurityDatabaseGuid,
&gEfiCertX509Guid,
mMicrosoftPca,
mSizeOfMicrosoftPca,
&gMicrosoftVendorGuid,
mMicrosoftUefiCa,
mSizeOfMicrosoftUefiCa,
&gMicrosoftVendorGuid,
NULL
);
}
if (EFI_ERROR (Status)) {
goto FreePkKek1;
}
//
// Enroll dbx.
//
Status = EnrollListOfCerts (
EFI_IMAGE_SECURITY_DATABASE1,
&gEfiImageSecurityDatabaseGuid,
&gEfiCertSha256Guid,
mSha256OfDevNull,
mSizeOfSha256OfDevNull,
&gEfiCallerIdGuid,
NULL
);
if (EFI_ERROR (Status)) {
goto FreePkKek1;
}
//
// Enroll KEK.
//
if (NoDefault) {
Status = EnrollListOfCerts (
EFI_KEY_EXCHANGE_KEY_NAME,
&gEfiGlobalVariableGuid,
&gEfiCertX509Guid,
PkKek1,
SizeOfPkKek1,
&gEfiCallerIdGuid,
NULL
);
} else {
Status = EnrollListOfCerts (
EFI_KEY_EXCHANGE_KEY_NAME,
&gEfiGlobalVariableGuid,
&gEfiCertX509Guid,
PkKek1,
SizeOfPkKek1,
&gEfiCallerIdGuid,
mMicrosoftKek,
mSizeOfMicrosoftKek,
&gMicrosoftVendorGuid,
NULL
);
}
if (EFI_ERROR (Status)) {
goto FreePkKek1;
}
//
// Enroll PK, leaving Setup Mode (entering User Mode) at once.
//
Status = EnrollListOfCerts (
EFI_PLATFORM_KEY_NAME,
&gEfiGlobalVariableGuid,
&gEfiCertX509Guid,
PkKek1,
SizeOfPkKek1,
&gEfiGlobalVariableGuid,
NULL
);
if (EFI_ERROR (Status)) {
goto FreePkKek1;
}
//
// Leave Custom Mode, so that updates to PK, KEK, db, and dbx require valid
// signatures.
//
Settings.CustomMode = STANDARD_SECURE_BOOT_MODE;
Status = gRT->SetVariable (
EFI_CUSTOM_MODE_NAME,
&gEfiCustomModeEnableGuid,
EFI_VARIABLE_NON_VOLATILE | EFI_VARIABLE_BOOTSERVICE_ACCESS,
sizeof Settings.CustomMode,
&Settings.CustomMode
);
if (EFI_ERROR (Status)) {
AsciiPrint (
"error: SetVariable(\"%s\", %g): %r\n",
EFI_CUSTOM_MODE_NAME,
&gEfiCustomModeEnableGuid,
Status
);
goto FreePkKek1;
}
//
// Final sanity check:
//
// [SetupMode]
// (read-only, standardized by UEFI)
// / \_
// 0 1, default
// / \_
// PK enrolled no PK enrolled yet,
// (this is called "User Mode") PK enrollment possible
// |
// |
// [SecureBootEnable]
// (read-write, edk2-specific, boot service only)
// / \_
// 0 1, default
// / \_
// [SecureBoot]=0 [SecureBoot]=1
// (read-only, standardized by UEFI) (read-only, standardized by UEFI)
// images are not verified images are verified, platform is
// operating in Secure Boot mode
// |
// |
// [CustomMode]
// (read-write, edk2-specific, boot service only)
// / \_
// 0, default 1
// / \_
// PK, KEK, db, dbx PK, KEK, db, dbx
// updates are verified updates are not verified
//
Status = GetSettings (&Settings);
if (EFI_ERROR (Status)) {
goto FreePkKek1;
}
PrintSettings (&Settings);
if ((Settings.SetupMode != 0) || (Settings.SecureBoot != 1) ||
(Settings.SecureBootEnable != 1) || (Settings.CustomMode != 0) ||
(Settings.VendorKeys != 0))
{
AsciiPrint ("error: unexpected\n");
goto FreePkKek1;
}
AsciiPrint ("info: success\n");
RetVal = 0;
FreePkKek1:
FreePool (PkKek1);
return RetVal;
}