/** @file Enroll default PK, KEK, db, dbx. Copyright (C) 2014-2019, Red Hat, Inc. SPDX-License-Identifier: BSD-2-Clause-Patent **/ #include // gEfiCustomModeEnableGuid #include // EFI_SETUP_MODE_NAME #include // EFI_IMAGE_SECURITY_DATABASE #include // gMicrosoftVendorGuid #include // gOvmfPkKek1AppPrefixGuid #include // SMBIOS_HANDLE_PI_RESERVED #include // GUID_STRING_LENGTH #include // CopyGuid() #include // ASSERT() #include // FreePool() #include // AsciiSPrint() #include // ShellAppMain() #include // gBS #include // AsciiPrint() #include // gRT #include // 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", __FUNCTION__, 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; }