From mboxrd@z Thu Jan 1 00:00:00 1970 Authentication-Results: mx.groups.io; dkim=missing; spf=pass (domain: redhat.com, ip: 209.132.183.28, mailfrom: lersek@redhat.com) Received: from mx1.redhat.com (mx1.redhat.com [209.132.183.28]) by groups.io with SMTP; Fri, 26 Apr 2019 17:54:15 -0700 Received: from smtp.corp.redhat.com (int-mx05.intmail.prod.int.phx2.redhat.com [10.5.11.15]) (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) (No client certificate requested) by mx1.redhat.com (Postfix) with ESMTPS id 862C91441B1; Sat, 27 Apr 2019 00:54:15 +0000 (UTC) Received: from lacos-laptop-7.usersys.redhat.com (ovpn-121-104.rdu2.redhat.com [10.10.121.104]) by smtp.corp.redhat.com (Postfix) with ESMTP id A48655D71A; Sat, 27 Apr 2019 00:54:13 +0000 (UTC) From: "Laszlo Ersek" To: edk2-devel-groups-io Cc: Anthony Perard , Ard Biesheuvel , Jordan Justen , Julien Grall Subject: [PATCH 15/16] OvmfPkg/EnrollDefaultKeys: enroll PK/KEK1 from the Type 11 SMBIOS table Date: Sat, 27 Apr 2019 02:53:27 +0200 Message-Id: <20190427005328.27005-16-lersek@redhat.com> In-Reply-To: <20190427005328.27005-1-lersek@redhat.com> References: <20190427005328.27005-1-lersek@redhat.com> MIME-Version: 1.0 X-Scanned-By: MIMEDefang 2.79 on 10.5.11.15 X-Greylist: Sender IP whitelisted, not delayed by milter-greylist-4.5.16 (mx1.redhat.com [10.5.110.38]); Sat, 27 Apr 2019 00:54:15 +0000 (UTC) Content-Transfer-Encoding: quoted-printable Disconnect the certificate that is enrolled as both Platform Key and firs= t Key Exchange Key from Red Hat: expect the hypervisor to specify it, as part of SMBIOS. Example usage with QEMU: * Generate self-signed X509 certificate: openssl req \ -x509 \ -newkey rsa:2048 \ -outform PEM \ -keyout PkKek1.private.key \ -out PkKek1.pem (where "PEM" simply means "DER + base64 + header + footer"). * Strip the header, footer, and newline characters; prepend the application prefix: sed \ -e 's/^-----BEGIN CERTIFICATE-----$/4e32566d-8e9e-4f52-81d3-5bb9715f9= 727:/' \ -e '/^-----END CERTIFICATE-----$/d' \ PkKek1.pem \ | tr -d '\n' \ > PkKek1.oemstr * Pass the certificate to EnrollDefaultKeys with the following QEMU option: -smbios type=3D11,value=3D"$(< PkKek1.oemstr)" (Note: for the above option to work correctly, a QEMU version is needed that includes commit 950c4e6c94b1 ("opts: don't silently truncate long option values", 2018-05-09). The first upstream release with that commi= t was v3.0.0. Once is fixed, QEMU will learn to read the file directly; passing the blob on the command will b= e necessary no more.) Cc: Anthony Perard Cc: Ard Biesheuvel Cc: Jordan Justen Cc: Julien Grall Bugzilla: https://bugzilla.tianocore.org/show_bug.cgi?id=3D1747 Signed-off-by: Laszlo Ersek --- OvmfPkg/EnrollDefaultKeys/EnrollDefaultKeys.inf | 7 + OvmfPkg/EnrollDefaultKeys/EnrollDefaultKeys.c | 223 ++++++++++++++++++= -- 2 files changed, 217 insertions(+), 13 deletions(-) diff --git a/OvmfPkg/EnrollDefaultKeys/EnrollDefaultKeys.inf b/OvmfPkg/En= rollDefaultKeys/EnrollDefaultKeys.inf index 28db52586a9b..184f7972d52d 100644 --- a/OvmfPkg/EnrollDefaultKeys/EnrollDefaultKeys.inf +++ b/OvmfPkg/EnrollDefaultKeys/EnrollDefaultKeys.inf @@ -30,16 +30,23 @@ [Guids] gEfiCertPkcs7Guid gEfiCertSha256Guid gEfiCertX509Guid gEfiCustomModeEnableGuid gEfiGlobalVariableGuid gEfiImageSecurityDatabaseGuid gEfiSecureBootEnableDisableGuid gMicrosoftVendorGuid + gOvmfPkKek1AppPrefixGuid + +[Protocols] + gEfiSmbiosProtocolGuid ## CONSUMES =20 [LibraryClasses] + BaseLib BaseMemoryLib DebugLib MemoryAllocationLib + PrintLib ShellCEntryLib + UefiBootServicesTableLib UefiLib UefiRuntimeServicesTableLib diff --git a/OvmfPkg/EnrollDefaultKeys/EnrollDefaultKeys.c b/OvmfPkg/Enro= llDefaultKeys/EnrollDefaultKeys.c index 9c4a0f06fb4d..b7b2e424c59e 100644 --- a/OvmfPkg/EnrollDefaultKeys/EnrollDefaultKeys.c +++ b/OvmfPkg/EnrollDefaultKeys/EnrollDefaultKeys.c @@ -4,26 +4,201 @@ Copyright (C) 2014-2019, Red Hat, Inc. =20 SPDX-License-Identifier: BSD-2-Clause-Patent **/ #include // gEfiCustomModeEnable= Guid #include // EFI_SETUP_MODE_NAME #include // EFI_IMAGE_SECURITY_D= ATABASE #include // gMicrosoftVendorGuid +#include // gOvmfPkKek1AppPrefix= Guid +#include // SMBIOS_HANDLE_PI_RES= ERVED +#include // GUID_STRING_LENGTH #include // CopyGuid() #include // ASSERT() #include // FreePool() +#include // AsciiSPrint() #include // ShellAppMain() +#include // gBS #include // AsciiPrint() #include // gRT +#include // EFI_SMBIOS_PROTOCOL =20 #include "EnrollDefaultKeys.h" =20 =20 +/** + Fetch the X509 certificate (to be used as Platform Key and first Key E= xchange + Key) from SMBIOS. + + @param[out] PkKek1 The X509 certificate in DER encoding from th= e + hypervisor, to be enrolled as PK and first K= EK + 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 bee= n + found. + + @retval EFI_PROTOCOL_ERROR In the OEM String matching + OVMF_PK_KEK1_APP_PREFIX_GUID, the certif= icate + is empty, or it has invalid base64 encod= ing. + + @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 =3D NULL; + + // + // Format the application prefix, for OEM String matching. + // + AsciiSPrint (OvmfPkKek1AppPrefix, sizeof OvmfPkKek1AppPrefix, "%g:", + &gOvmfPkKek1AppPrefixGuid); + + // + // Scan all "OEM Strings" tables. + // + Status =3D gBS->LocateProtocol (&gEfiSmbiosProtocolGuid, NULL, + (VOID **)&Smbios); + if (EFI_ERROR (Status)) { + AsciiPrint ("error: failed to locate EFI_SMBIOS_PROTOCOL: %r\n", Sta= tus); + return Status; + } + + Handle =3D SMBIOS_HANDLE_PI_RESERVED; + Type =3D SMBIOS_TYPE_OEM_STRINGS; + for (Status =3D Smbios->GetNext (Smbios, &Handle, &Type, &Header, NULL= ); + !EFI_ERROR (Status); + Status =3D 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 =3D (SMBIOS_TABLE_TYPE11 *)Header; + + // + // Scan all strings in the unformatted area of the current "OEM Stri= ngs" + // table. + // + OemString =3D (CONST CHAR8 *)(OemStringsTable + 1); + for (Idx =3D 0; Idx < OemStringsTable->StringCount; ++Idx) { + CHAR8 CandidatePrefix[sizeof OvmfPkKek1AppPrefix]; + + // + // NUL-terminate the candidate prefix for case-insensitive compari= son. + // + AsciiStrnCpyS (CandidatePrefix, sizeof CandidatePrefix, OemString, + GUID_STRING_LENGTH + 1); + if (AsciiStriCmp (OvmfPkKek1AppPrefix, CandidatePrefix) =3D=3D 0) = { + // + // The current string matches the prefix. + // + Base64Cert =3D OemString + GUID_STRING_LENGTH + 1; + break; + } + OemString +=3D 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 !=3D NULL); + Base64CertLen =3D AsciiStrLen (Base64Cert); + + // + // Verify the base64 encoding, and determine the decoded size. + // + DecodedCertSize =3D 0; + Status =3D Base64Decode (Base64Cert, Base64CertLen, NULL, &DecodedCert= Size); + switch (Status) { + case EFI_BUFFER_TOO_SMALL: + if (DecodedCertSize > 0) { + break; + } + // + // Fall through: the above Base64Decode() call is ill-specified in B= aseLib + // if Source decodes to zero bytes (for example if it consists of ig= nored + // whitespace only). + // + 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 =3D AllocatePool (DecodedCertSize); + if (DecodedCert =3D=3D NULL) { + AsciiPrint ("error: failed to allocate memory\n"); + return EFI_OUT_OF_RESOURCES; + } + + // + // Decoding will succeed at this point. + // + Status =3D Base64Decode (Base64Cert, Base64CertLen, DecodedCert, + &DecodedCertSize); + ASSERT_EFI_ERROR (Status); + + *PkKek1 =3D DecodedCert; + *SizeOfPkKek1 =3D DecodedCertSize; + return EFI_SUCCESS; +} + + /** Enroll a set of certificates in a global variable, overwriting it. =20 The variable will be rewritten with NV+BS+RT+AT attributes. =20 @param[in] VariableName The name of the variable to overwrite. =20 @param[in] VendorGuid The namespace (ie. vendor GUID) of the variab= le to @@ -353,116 +528,133 @@ PrintSettings ( **/ INTN EFIAPI ShellAppMain ( IN UINTN Argc, IN CHAR16 **Argv ) { + INTN RetVal; EFI_STATUS Status; SETTINGS Settings; + UINT8 *PkKek1; + UINTN SizeOfPkKek1; + + // + // Prepare for failure. + // + RetVal =3D 1; =20 // // If we're not in Setup Mode, we can't do anything. // Status =3D GetSettings (&Settings); if (EFI_ERROR (Status)) { - return 1; + return RetVal; } PrintSettings (&Settings); =20 if (Settings.SetupMode !=3D 1) { AsciiPrint ("error: already in User Mode\n"); - return 1; + return RetVal; + } + + // + // Fetch the X509 certificate (to be used as Platform Key and first Ke= y + // Exchange Key) from SMBIOS. + // + Status =3D GetPkKek1 (&PkKek1, &SizeOfPkKek1); + if (EFI_ERROR (Status)) { + return RetVal; } =20 // // Enter Custom Mode so we can enroll PK, KEK, db, and dbx without sig= nature // checks on those variable writes. // if (Settings.CustomMode !=3D CUSTOM_SECURE_BOOT_MODE) { Settings.CustomMode =3D CUSTOM_SECURE_BOOT_MODE; Status =3D gRT->SetVariable (EFI_CUSTOM_MODE_NAME, &gEfiCustomModeEn= ableGuid, (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_MOD= E_NAME, &gEfiCustomModeEnableGuid, Status); - return 1; + goto FreePkKek1; } } =20 // // Enroll db. // Status =3D EnrollListOfCerts ( EFI_IMAGE_SECURITY_DATABASE, &gEfiImageSecurityDatabaseGuid, &gEfiCertX509Guid, mMicrosoftPca, mSizeOfMicrosoftPca, &gMicrosoftVendor= Guid, mMicrosoftUefiCa, mSizeOfMicrosoftUefiCa, &gMicrosoftVendor= Guid, NULL); if (EFI_ERROR (Status)) { - return 1; + goto FreePkKek1; } =20 // // Enroll dbx. // Status =3D EnrollListOfCerts ( EFI_IMAGE_SECURITY_DATABASE1, &gEfiImageSecurityDatabaseGuid, &gEfiCertSha256Guid, mSha256OfDevNull, mSizeOfSha256OfDevNull, &gEfiCallerIdGuid= , NULL); if (EFI_ERROR (Status)) { - return 1; + goto FreePkKek1; } =20 // // Enroll KEK. // Status =3D EnrollListOfCerts ( EFI_KEY_EXCHANGE_KEY_NAME, &gEfiGlobalVariableGuid, &gEfiCertX509Guid, - mRedHatPkKek1, mSizeOfRedHatPkKek1, &gEfiCallerIdGuid, + PkKek1, SizeOfPkKek1, &gEfiCallerIdGuid, mMicrosoftKek, mSizeOfMicrosoftKek, &gMicrosoftVendorGuid, NULL); if (EFI_ERROR (Status)) { - return 1; + goto FreePkKek1; } =20 // // Enroll PK, leaving Setup Mode (entering User Mode) at once. // Status =3D EnrollListOfCerts ( EFI_PLATFORM_KEY_NAME, &gEfiGlobalVariableGuid, &gEfiCertX509Guid, - mRedHatPkKek1, mSizeOfRedHatPkKek1, &gEfiGlobalVariableGuid= , + PkKek1, SizeOfPkKek1, &gEfiGlobalVariableGuid, NULL); if (EFI_ERROR (Status)) { - return 1; + goto FreePkKek1; } =20 // // Leave Custom Mode, so that updates to PK, KEK, db, and dbx require = valid // signatures. // Settings.CustomMode =3D STANDARD_SECURE_BOOT_MODE; Status =3D gRT->SetVariable (EFI_CUSTOM_MODE_NAME, &gEfiCustomModeEnab= leGuid, EFI_VARIABLE_NON_VOLATILE | EFI_VARIABLE_BOOTSERVICE_A= CCESS, sizeof Settings.CustomMode, &Settings.CustomMode); if (EFI_ERROR (Status)) { AsciiPrint ("error: SetVariable(\"%s\", %g): %r\n", EFI_CUSTOM_MODE_= NAME, &gEfiCustomModeEnableGuid, Status); - return 1; + goto FreePkKek1; } =20 // // Final sanity check: // // [SetupMode] // (read-only, standardized by UEFI) // / \_ @@ -488,22 +680,27 @@ ShellAppMain ( // / \_ // 0, default 1 // / \_ // PK, KEK, db, dbx PK, KEK, db, d= bx // updates are verified updates are not ve= rified // Status =3D GetSettings (&Settings); if (EFI_ERROR (Status)) { - return 1; + goto FreePkKek1; } PrintSettings (&Settings); =20 if (Settings.SetupMode !=3D 0 || Settings.SecureBoot !=3D 1 || Settings.SecureBootEnable !=3D 1 || Settings.CustomMode !=3D 0 || Settings.VendorKeys !=3D 0) { AsciiPrint ("error: unexpected\n"); - return 1; + goto FreePkKek1; } =20 AsciiPrint ("info: success\n"); - return 0; + RetVal =3D 0; + +FreePkKek1: + FreePool (PkKek1); + + return RetVal; } --=20 2.19.1.3.g30247aa5d201