From dd9d43d6c0fc9a720a3a1b314981c9b9297bd89d Mon Sep 17 00:00:00 2001 From: Pete Batard Date: Tue, 3 Sep 2024 18:29:50 +0100 Subject: [PATCH] Fix support for OVMF and other more restrictive platforms * It looks like some platforms do not accept an unsigned ESL/AuthVar for the PK but insist on the UEFI "Trust me, bro!" equivalent of a self signed AuthVar... * We therefore switch to using authenticated variables everywhere and add self signing for the PK one. We also add support for loading '.auth' for people who want to provide their own PK on such platforms. * Also fix the wrong use of gEfiCertX509Sha###Guid since these apply for SHA-### hashes only. Unconditionally use gEfiCertX509Guid instead. --- MosbyPkg.inf | 4 +- build.sh | 2 +- src/mosby.c | 99 +++++++++++--------- src/mosby.h | 8 +- src/pki.c | 242 +++++++++++++++++++++++++++++++++++++----------- src/pki.h | 24 ++++- src/variables.c | 50 ---------- src/variables.h | 7 -- 8 files changed, 271 insertions(+), 165 deletions(-) diff --git a/MosbyPkg.inf b/MosbyPkg.inf index b74b398..72ee5f2 100644 --- a/MosbyPkg.inf +++ b/MosbyPkg.inf @@ -54,9 +54,7 @@ [Guids] gEfiCertPkcs7Guid - gEfiCertX509Sha256Guid - gEfiCertX509Sha384Guid - gEfiCertX509Sha512Guid + gEfiCertX509Guid gEfiGlobalVariableGuid gEfiImageSecurityDatabaseGuid gEfiFileInfoGuid diff --git a/build.sh b/build.sh index 6ac2dd1..13306ef 100755 --- a/build.sh +++ b/build.sh @@ -19,7 +19,7 @@ else fi # DISABLED = SecureBoot=0, SetupMode=0 # SETUP = SecureBoot=0, SetupMode=1 -SB_MODE=DISABLED +SB_MODE=SETUP cp OVMF_VARS_4M.secboot.$SB_MODE.fd OVMF_VARS_4M.secboot.fd export QEMU_CMD="qemu-system-x86_64 $CPU_OPT -m 1024 -M q35 -L . \ -drive if=pflash,format=raw,unit=0,file=OVMF_CODE_4M.secboot.fd,readonly=on \ diff --git a/src/mosby.c b/src/mosby.c index 2314c60..de6c688 100644 --- a/src/mosby.c +++ b/src/mosby.c @@ -36,39 +36,39 @@ STATIC EFI_GUID gEfiShimLockGuid = /* Attributes for the "key" types we support */ STATIC struct { - CHAR8 *Name; - CHAR16 *VarName; - EFI_GUID *VarGuid; + CHAR8 *DisplayName; + CHAR16 *VariableName; + EFI_GUID *VariableGuid; } KeyInfo[MAX_TYPES] = { [PK] = { - .Name = "PK", - .VarName = EFI_PLATFORM_KEY_NAME, - .VarGuid = &gEfiGlobalVariableGuid, + .DisplayName = "PK", + .VariableName = EFI_PLATFORM_KEY_NAME, + .VariableGuid = &gEfiGlobalVariableGuid, }, [KEK] = { - .Name = "KEK", - .VarName = EFI_KEY_EXCHANGE_KEY_NAME, - .VarGuid = &gEfiGlobalVariableGuid, + .DisplayName = "KEK", + .VariableName = EFI_KEY_EXCHANGE_KEY_NAME, + .VariableGuid = &gEfiGlobalVariableGuid, }, [DB] = { - .Name = "DB", - .VarName = EFI_IMAGE_SECURITY_DATABASE, - .VarGuid = &gEfiImageSecurityDatabaseGuid, + .DisplayName = "DB", + .VariableName = EFI_IMAGE_SECURITY_DATABASE, + .VariableGuid = &gEfiImageSecurityDatabaseGuid, }, [DBX] = { - .Name = "DBX", - .VarName = EFI_IMAGE_SECURITY_DATABASE1, - .VarGuid = &gEfiImageSecurityDatabaseGuid, + .DisplayName = "DBX", + .VariableName = EFI_IMAGE_SECURITY_DATABASE1, + .VariableGuid = &gEfiImageSecurityDatabaseGuid, }, [DBT] = { - .Name = "DBT", - .VarName = EFI_IMAGE_SECURITY_DATABASE2, - .VarGuid = &gEfiImageSecurityDatabaseGuid, + .DisplayName = "DBT", + .VariableName = EFI_IMAGE_SECURITY_DATABASE2, + .VariableGuid = &gEfiImageSecurityDatabaseGuid, }, [MOK] = { - .Name = "MOK", - .VarName = L"MokList", - .VarGuid = &gEfiShimLockGuid, + .DisplayName = "MOK", + .VariableName = L"MokList", + .VariableGuid = &gEfiShimLockGuid, } }; @@ -181,13 +181,13 @@ EFI_STATUS ParseList( }; } else { for (Type = 0; Type < MAX_TYPES; Type++) { - if (i + AsciiStrLen(KeyInfo[Type].Name) >= Installable->ListDataSize) + if (i + AsciiStrLen(KeyInfo[Type].DisplayName) >= Installable->ListDataSize) continue; - if (AsciiStrnCmp(KeyInfo[Type].Name, &Installable->ListData[i], AsciiStrLen(KeyInfo[Type].Name)) != 0) + if (AsciiStrnCmp(KeyInfo[Type].DisplayName, &Installable->ListData[i], AsciiStrLen(KeyInfo[Type].DisplayName)) != 0) continue; - if (!IsWhiteSpace(Installable->ListData[i + AsciiStrLen(KeyInfo[Type].Name)])) + if (!IsWhiteSpace(Installable->ListData[i + AsciiStrLen(KeyInfo[Type].DisplayName)])) continue; - i += AsciiStrLen(KeyInfo[Type].Name); + i += AsciiStrLen(KeyInfo[Type].DisplayName); while (IsWhiteSpace(Installable->ListData[i]) && i < Installable->ListDataSize) i++; if (Installable->List[Type].NumEntries < MOSBY_MAX_ENTRIES) { @@ -223,6 +223,9 @@ EFI_STATUS EFIAPI efi_main( IN EFI_SYSTEM_TABLE* SystemTable ) { + // TODO: MOK may need different options + CONST UINT32 Attributes = EFI_VARIABLE_NON_VOLATILE | EFI_VARIABLE_RUNTIME_ACCESS | + EFI_VARIABLE_BOOTSERVICE_ACCESS | EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS; BOOLEAN TestMode = FALSE; EFI_STATUS Status, SetStatus; INTN Argc, Type, Entry, Sel; @@ -355,42 +358,44 @@ EFI_STATUS EFIAPI efi_main( EFI_HANDLE Handle = NULL; SafeFree(Installable.List[Type].Path[Entry]); UnicodeSPrint(Title, ARRAY_SIZE(Title), L"Please select %a %s", - KeyInfo[Type].Name, (Type == DBX) ? L"binary" : L"certificate"); + KeyInfo[Type].DisplayName, (Type == DBX) ? L"binary" : L"certificate"); Status = SimpleFileSelector(&Handle, (CONST CHAR16 *[]){ L"", Title, NULL - }, L"\\", L".cer|.crt|.esl|.bin", &Installable.List[Type].Path[Entry]); + }, L"\\", L".cer|.crt|.esl|.bin|.auth", &Installable.List[Type].Path[Entry]); RecallPrintRestore(); if (EFI_ERROR(Status)) continue; if (!SimpleFileExistsByPath(gBaseImageHandle, Installable.List[Type].Path[Entry])) { - RecallPrint(L"No valid file selected for %a[%d] - Ignoring\n", KeyInfo[Type].Name, Entry); + RecallPrint(L"No valid file selected for %a[%d] - Ignoring\n", KeyInfo[Type].DisplayName, Entry); continue; } } - Installable.List[Type].Esl[Entry] = LoadToEsl(Installable.List[Type].Path[Entry]); - - if (Installable.List[Type].Esl[Entry] == NULL) { - RecallPrint(L"Failed to load %a[%d] - Aborting\n", KeyInfo[Type].Name, Entry); + Status = LoadToAuthVar(Installable.List[Type].Path[Entry], &Installable.List[Type].Variable[Entry]); + if (EFI_ERROR(Status)) { + RecallPrint(L"Failed to load %a[%d] - Aborting\n", KeyInfo[Type].DisplayName, Entry); goto exit; } } } /* 6. Generate a keyless PK cert if none was specified */ - if (Installable.List[PK].Esl[0] == NULL) { + if (Installable.List[PK].Variable[0].Data == NULL) { RecallPrint(L"Generating PK certificate...\n"); SafeFree(Installable.List[PK].Path[0]); Installable.List[PK].Path[0] = StrDup(L"AutoGenerated"); - Cert = GenerateCredentials("Mosby Generated PK", NULL); - Installable.List[PK].Esl[0] = CertToEsl(Cert); - if (Installable.List[PK].Esl[0] == NULL) { + Cert = GenerateCredentials("Mosby Generated PK", &Key); + Status = CertToAuthVar(Cert, &Installable.List[PK].Variable[0]); + if (EFI_ERROR(Status)) { SafeFree(Cert); goto exit; } + Status = SignToAuthVar(KeyInfo[PK].VariableName, KeyInfo[PK].VariableGuid, + Attributes, &Installable.List[PK].Variable[0], Cert, Key); + FreeCredentials(Cert, Key); } /* 7. Generate DB credentials if requested */ @@ -400,9 +405,9 @@ EFI_STATUS EFIAPI efi_main( RecallPrint(L"Generating Secure Boot signing credentials...\n"); SafeFree(Installable.List[DB].Path[Entry]); Installable.List[DB].Path[Entry] = StrDup(L"AutoGenerated"); - Cert = GenerateCredentials("Secure Boot signing", &Key);; - Installable.List[DB].Esl[Entry] = CertToEsl(Cert); - if (Installable.List[DB].Esl[Entry] == NULL) { + Cert = GenerateCredentials("Secure Boot signing", &Key); + Status = CertToAuthVar(Cert, &Installable.List[DB].Variable[Entry]); + if (EFI_ERROR(Status)) { SafeFree(Cert); goto exit; } @@ -410,6 +415,7 @@ EFI_STATUS EFIAPI efi_main( if (EFI_ERROR(Status)) goto exit; RecallPrint(L"Saved Secure Boot signing credentials as '%s'\n", MOSBY_CRED_NAME); + FreeCredentials(Cert, Key); } /* 8. Install the cert and DBX variables, making sure that we finish with the PK. */ @@ -418,12 +424,15 @@ EFI_STATUS EFIAPI efi_main( Status = EFI_NOT_FOUND; for (Type = MAX_TYPES - 1; Type >= 0; Type--) { for (Entry = 0; Entry < Installable.List[Type].NumEntries; Entry++) { - if (Installable.List[Type].Esl[Entry] != NULL) { - RecallPrint(L"Installing %a[%d] (from %s)\n", KeyInfo[Type].Name, Entry, Installable.List[Type].Path[Entry]); - SetStatus = SetSecureBootVariable(KeyInfo[Type].VarName, KeyInfo[Type].VarGuid, - Installable.List[Type].Esl[Entry], (Entry != 0)); - if (EFI_ERROR(SetStatus)) + if (Installable.List[Type].Variable[Entry].Data != NULL) { + RecallPrint(L"Installing %a[%d] (from %s)\n", KeyInfo[Type].DisplayName, Entry, Installable.List[Type].Path[Entry]); + SetStatus = gRT->SetVariable(KeyInfo[Type].VariableName, KeyInfo[Type].VariableGuid, + Attributes | ((Entry != 0) ? EFI_VARIABLE_APPEND_WRITE : 0), + Installable.List[Type].Variable[Entry].Size, Installable.List[Type].Variable[Entry].Data); + if (EFI_ERROR(SetStatus)) { + Print(L"Failed to set Secure Boot variable: %r\n", SetStatus); Status = SetStatus; + } } } } @@ -432,7 +441,7 @@ EFI_STATUS EFIAPI efi_main( for (Type = 0; Type < MAX_TYPES; Type++) for (Entry = 0; Entry < MOSBY_MAX_ENTRIES; Entry++) { FreePool(Installable.List[Type].Path[Entry]); - FreePool(Installable.List[Type].Esl[Entry]); + FreePool(Installable.List[Type].Variable[Entry].Data); } FreePool(Installable.ListData); FreePool(Argv); diff --git a/src/mosby.h b/src/mosby.h index 3af4853..1f24e29 100644 --- a/src/mosby.h +++ b/src/mosby.h @@ -25,6 +25,7 @@ #include #include #include +#include #include #include #include @@ -56,11 +57,16 @@ enum { MAX_TYPES }; +typedef struct { + UINTN Size; + EFI_VARIABLE_AUTHENTICATION_2 *Data; +} AUTHENTICATED_VARIABLE; + /* Structure containing the list of "keys" for a specific type */ typedef struct { UINTN NumEntries; CHAR16 *Path[MOSBY_MAX_ENTRIES]; - EFI_SIGNATURE_LIST *Esl[MOSBY_MAX_ENTRIES]; + AUTHENTICATED_VARIABLE Variable[MOSBY_MAX_ENTRIES]; } INSTALLABLE_LIST; /* Structure containing the collection of all the lists */ diff --git a/src/pki.c b/src/pki.c index d84c95a..f812aaf 100644 --- a/src/pki.c +++ b/src/pki.c @@ -48,6 +48,8 @@ ERR_print_errors_cb(OpenSSLErrorCallback, _ErrMsg); goto exit; \ } while(0) +STATIC EFI_TIME mTime = { 0 }; + /* For OpenSSL error reporting */ STATIC int OpenSSLErrorCallback( CONST CHAR8 *AsciiString, @@ -66,6 +68,17 @@ EFI_STATUS InitializePki( CONST CHAR8 DefaultSeed[] = "Mosby crypto default seed"; EFI_LOADED_IMAGE_PROTOCOL *LoadedImage; CHAR16 *Seed = NULL; + EFI_STATUS Status; + + Status = gRT->GetTime(&mTime, NULL); + if (EFI_ERROR(Status)) + ReportErrorAndExit(L"Failed to get current time: %r\n", Status); + + // SetVariable() *will* fail with "Security Violation" unless you + // explicitly zero these before calling CreateTimeBasedPayload() + mTime.Nanosecond = 0; + mTime.TimeZone = 0; + mTime.Daylight = 0; // Try to use the loaded image's DevicePath (of the DeviceHandle) as our seed since // it is both unique enough and *not* time-based (therefore harder to guess). @@ -81,7 +94,10 @@ EFI_STATUS InitializePki( RAND_seed(DefaultSeed, sizeof(DefaultSeed)); } FreePool(Seed); - return (RAND_status() != 1 && !TestMode) ? EFI_UNSUPPORTED : EFI_SUCCESS; + Status = (RAND_status() != 1 && !TestMode) ? EFI_UNSUPPORTED : EFI_SUCCESS; + +exit: + return Status; } /* Helper function to add X509 extensions */ @@ -268,13 +284,21 @@ EFI_STATUS SaveCredentials( #if 0 // Save certificate as DER encoded .cer + Size = (INTN)i2d_X509(Cert, NULL); + if (Size <= 0) + ReportOpenSSLErrorAndExit(EFI_PROTOCOL_ERROR); + Buffer = AllocateZeroPool(Size); + if (Buffer == NULL) { + Status = EFI_OUT_OF_RESOURCES; + ReportErrorAndExit(L"Failed to allocate DER buffer\n"); + } Ptr = Buffer; // i2d_###() modifies the pointer - Size = (INTN)i2d_X509((X509*)Cert, &Ptr); + Size = (INTN)i2d_X509(Cert, &Ptr); if (Size < 0) ReportOpenSSLErrorAndExit(EFI_PROTOCOL_ERROR); UnicodeSPrint(Path, ARRAY_SIZE(Path), L"%s.cer", BaseName); Status = SimpleFileWriteAllByPath(gBaseImageHandle, Path, (UINTN)Size, Buffer); - OPENSSL_free(Buffer); + SafeFree(Buffer); if (EFI_ERROR(Status)) goto exit; #endif @@ -300,50 +324,41 @@ EFI_STATUS SaveCredentials( return Status; } -EFI_SIGNATURE_LIST* CertToEsl( - CONST IN VOID *Cert +VOID FreeCredentials( + IN CONST VOID *Cert, + IN CONST VOID *Key ) { + X509_free((X509*)Cert); + EVP_PKEY_free((EVP_PKEY*)Key); +} + +EFI_STATUS CertToAuthVar( + CONST IN VOID *Cert, + OUT AUTHENTICATED_VARIABLE *Variable +) +{ + EFI_STATUS Status = EFI_INVALID_PARAMETER; EFI_SIGNATURE_LIST *Esl = NULL; EFI_SIGNATURE_DATA *Data = NULL; INTN Size; UINT8 *Ptr; - EFI_GUID OwnerGuid, *TypeGuid = NULL; + EFI_GUID OwnerGuid; UINT8 Sha1[SHA_DIGEST_LENGTH] = { 0 }; - if (Cert == NULL) - return NULL; + if (Cert == NULL || Variable == NULL) + return EFI_INVALID_PARAMETER; Size = (INTN)i2d_X509((X509*)Cert, NULL); if (Size <= 0) - return NULL; + goto exit; Esl = AllocateZeroPool(sizeof(EFI_SIGNATURE_LIST) + sizeof (EFI_SIGNATURE_DATA) - 1 + Size); - if (Esl == NULL) + if (Esl == NULL) { + Status = EFI_OUT_OF_RESOURCES; ReportErrorAndExit(L"Failed to allocate ESL\n"); - - switch (X509_get_signature_nid((X509*)Cert)) { - case NID_sha256: - case NID_sha256WithRSAEncryption: - TypeGuid = &gEfiCertX509Sha256Guid; - break; - case NID_sha384: - case NID_sha384WithRSAEncryption: - TypeGuid = &gEfiCertX509Sha384Guid; - break; - case NID_sha512: - case NID_sha512WithRSAEncryption: - TypeGuid = &gEfiCertX509Sha512Guid; - break; - default: - break; - } - - if (TypeGuid == NULL) { - FreePool(Esl); - ReportErrorAndExit(L"Unsupported signature algorithm\n"); } - CopyGuid(&Esl->SignatureType, TypeGuid); + CopyGuid(&Esl->SignatureType, &gEfiCertX509Guid); Esl->SignatureListSize = sizeof(EFI_SIGNATURE_LIST) + sizeof (EFI_SIGNATURE_DATA) - 1 + Size; Esl->SignatureSize = sizeof(EFI_SIGNATURE_DATA) - 1 + Size; @@ -361,22 +376,39 @@ EFI_SIGNATURE_LIST* CertToEsl( CopyMem(&OwnerGuid.Data4, &Sha1[8], 8); CopyGuid(&Data->SignatureOwner, &OwnerGuid); + Variable->Size = Esl->SignatureListSize; + Variable->Data = (EFI_VARIABLE_AUTHENTICATION_2*)Esl; + // NB: CreateTimeBasedPayload() frees the input buffer before replacing it + Status = CreateTimeBasedPayload(&Variable->Size, (UINT8**)&Variable->Data, &mTime); + if (EFI_ERROR(Status)) { + FreePool(Esl); + ReportErrorAndExit(L"Failed to create time-based data payload: %r\n", Status); + } + exit: - return Esl; + if (EFI_ERROR(Status)) { + Variable->Size = 0; + Variable->Data = NULL; + } + return Status; } -EFI_SIGNATURE_LIST* LoadToEsl( - IN CONST CHAR16 *Path +EFI_STATUS LoadToAuthVar( + IN CONST CHAR16 *Path, + OUT AUTHENTICATED_VARIABLE* Variable ) { EFI_STATUS Status; UINTN Size, HeaderSize; UINT8 *Buffer = NULL; CONST UINT8 *Ptr; - EFI_SIGNATURE_LIST* Esl = NULL; - EFI_VARIABLE_AUTHENTICATION_2* SignedEsl = NULL; + EFI_SIGNATURE_LIST *Esl = NULL; + EFI_VARIABLE_AUTHENTICATION_2 *SignedEsl = NULL; BIO *bio = NULL; + if (Path == NULL || Variable == NULL) + return EFI_INVALID_PARAMETER; + if (!SimpleFileExistsByPath(gBaseImageHandle, Path)) ReportErrorAndExit(L"File '%s' does not exist\n", Path); @@ -384,10 +416,11 @@ EFI_SIGNATURE_LIST* LoadToEsl( if (EFI_ERROR(Status)) goto exit; + Status = EFI_INVALID_PARAMETER; if (Size < sizeof(EFI_SIGNATURE_LIST)) ReportErrorAndExit(L"'%s' is too small to be a valid certificate or signature list\n", Path); - // Check for signed ESL (PKCS#7 only, as it's what DBX updates use) + // Check for signed ESL (PKCS#7 only) SignedEsl = (EFI_VARIABLE_AUTHENTICATION_2*)Buffer; if (Size > sizeof(EFI_VARIABLE_AUTHENTICATION_2) && SignedEsl->AuthInfo.Hdr.dwLength < Size && @@ -402,44 +435,145 @@ EFI_SIGNATURE_LIST* LoadToEsl( HeaderSize += 4; // For the 4 extra bytes above if (HeaderSize + sizeof(EFI_SIGNATURE_LIST) > Size) ReportErrorAndExit(L"Invalid signed ESL '%s'\n", Path); - Esl = AllocateZeroPool(Size - HeaderSize); - if (Esl == NULL) - ReportErrorAndExit(L"Failed to allocate unsigned ESL for '%s'\n", Path); - CopyMem(Esl, &Buffer[HeaderSize], Size - HeaderSize); - SafeFree(Buffer); - if (Esl->SignatureListSize != Size - HeaderSize) { - SafeFree(Esl); + Esl = (EFI_SIGNATURE_LIST*)&Buffer[HeaderSize]; + if (Esl->SignatureListSize != Size - HeaderSize) ReportErrorAndExit(L"Invalid signed ESL '%s'\n", Path); - } + Buffer = NULL; // Don't free our data + Variable->Size = Size; + Variable->Data = SignedEsl; + Status = EFI_SUCCESS; goto exit; } // Check for a DER encoded X509 certificate Ptr = Buffer; // d2i_###() modifies the pointer - Esl = CertToEsl(d2i_X509(NULL, &Ptr, Size)); - if (Esl != NULL) + Status = CertToAuthVar(d2i_X509(NULL, &Ptr, Size), Variable); + if (Status == EFI_SUCCESS) goto exit; // Check for a PEM encoded X509 certificate bio = BIO_new_mem_buf(Buffer, Size); if (bio == NULL) ReportErrorAndExit(L"Failed to allocate X509 buffer\n"); - Esl = CertToEsl(PEM_read_bio_X509(bio, NULL, NULL, NULL)); - if (Esl != NULL) + Status = CertToAuthVar(PEM_read_bio_X509(bio, NULL, NULL, NULL), Variable); + if (Status == EFI_SUCCESS) goto exit; // Check for an unsigned ESL Esl = (EFI_SIGNATURE_LIST*)Buffer; if (Esl->SignatureListSize == Size) { - Buffer = NULL; // Don't free the ESL - goto exit; + Variable->Size = Esl->SignatureListSize; + Variable->Data = (EFI_VARIABLE_AUTHENTICATION_2*)Esl; + // NB: CreateTimeBasedPayload() frees the input buffer before replacing it + Status = CreateTimeBasedPayload(&Variable->Size, (UINT8**)&Variable->Data, &mTime); + if (EFI_ERROR(Status)) + ReportErrorAndExit(L"Failed to create time-based data payload: %r\n", Status); + Buffer = NULL; // CreateTimeBasedPayload freed Esl = Buffer already } - Esl = NULL; ReportErrorAndExit(L"Failed to process '%s'\n", Path); exit: BIO_free(bio); FreePool(Buffer); - return Esl; + if (EFI_ERROR(Status)) { + Variable->Size = 0; + Variable->Data = NULL; + } + return Status; +} + +EFI_STATUS SignToAuthVar( + IN CONST CHAR16 *VariableName, + IN CONST EFI_GUID *VendorGuid, + IN CONST UINT32 Attributes, + IN OUT AUTHENTICATED_VARIABLE *Variable, + IN CONST VOID *Cert, + IN CONST VOID *Key +) +{ + CONST INTN PAYLOAD = 4; + CONST int flags = PKCS7_BINARY | PKCS7_DETACHED | PKCS7_NOATTR; + CONST struct { + UINT8 *Ptr; + UINTN Size; + } SignableElement[5] = { + { (UINT8*)VariableName, StrLen(VariableName) * sizeof(CHAR16) }, + { (UINT8*)VendorGuid, sizeof(EFI_GUID) }, + { (UINT8*)&Attributes, sizeof(Attributes) }, + { (UINT8*)&Variable->Data->TimeStamp, sizeof(EFI_TIME) }, + { &(((UINT8*)Variable->Data)[OFFSET_OF_AUTHINFO2_CERT_DATA]), Variable->Size - OFFSET_OF_AUTHINFO2_CERT_DATA } + }; + EFI_STATUS Status = EFI_INVALID_PARAMETER; + UINT8 *SignData = NULL; + EFI_VARIABLE_AUTHENTICATION_2 *SignedVariable = NULL; + UINT8 *Payload, *Ptr; + UINTN i, SignDataSize, SignatureSize, Offset; + BIO *bio; + PKCS7 *p7; + + if (Variable->Size < OFFSET_OF_AUTHINFO2_CERT_DATA) + ReportErrorAndExit(L"Variable to sign (%d) is too small (%d)\n", Variable->Size, OFFSET_OF_AUTHINFO2_CERT_DATA); + + // We can only sign for PKCS#7 + if (!CompareGuid(&Variable->Data->AuthInfo.CertType, &gEfiCertPkcs7Guid)) + ReportErrorAndExit(L"Variable to sign is not PKCS#7\n"); + + // Make sure we are dealing with a variable that does NOT already contain a signature + if (Variable->Data->AuthInfo.Hdr.dwLength != OFFSET_OF(WIN_CERTIFICATE_UEFI_GUID, CertData)) + ReportErrorAndExit(L"Variable is already signed\n"); + + // Construct the data buffer to sign + SignDataSize = 0; + for (i = 0; i < ARRAY_SIZE(SignableElement); i++) + SignDataSize += SignableElement[i].Size; + SignData = AllocateZeroPool(SignDataSize); + if (SignData == NULL) { + Status = EFI_OUT_OF_RESOURCES; + ReportErrorAndExit(L"Failed to allocate buffer to sign\n"); + } + Offset = 0; + for (i = 0; i < ARRAY_SIZE(SignableElement); i++) { + CopyMem(&SignData[Offset], SignableElement[i].Ptr, SignableElement[i].Size); + Offset += SignableElement[i].Size; + } + + // Sign the constructed data buffer + bio = BIO_new_mem_buf(SignData, SignDataSize); + if (bio == NULL) + ReportOpenSSLErrorAndExit(EFI_PROTOCOL_ERROR); + + p7 = PKCS7_sign(NULL, NULL, NULL, bio, flags | PKCS7_PARTIAL); + if (p7 == NULL) + ReportOpenSSLErrorAndExit(EFI_PROTOCOL_ERROR); + if (PKCS7_sign_add_signer(p7, (X509*)Cert, (EVP_PKEY*)Key, EVP_get_digestbyname("SHA256"), flags) == NULL) + ReportOpenSSLErrorAndExit(EFI_PROTOCOL_ERROR); + if (!PKCS7_final(p7, bio, flags)) + ReportOpenSSLErrorAndExit(EFI_PROTOCOL_ERROR); + SignatureSize = i2d_PKCS7(p7, NULL); + + // Create the signed variable + SignedVariable = AllocateZeroPool(Variable->Size + SignatureSize); + if (SignedVariable == NULL) { + Status = EFI_OUT_OF_RESOURCES; + ReportErrorAndExit(L"Failed to allocate buffer for signed variable\n"); + } + CopyMem(SignedVariable, Variable->Data, OFFSET_OF_AUTHINFO2_CERT_DATA); + SignedVariable->AuthInfo.Hdr.dwLength = OFFSET_OF(WIN_CERTIFICATE_UEFI_GUID, CertData) + SignatureSize; + Ptr = SignedVariable->AuthInfo.CertData; + SignatureSize = i2d_PKCS7(p7, &Ptr); + Payload = (UINT8*)SignedVariable; + Payload = &Payload[OFFSET_OF_AUTHINFO2_CERT_DATA + SignatureSize]; + CopyMem(Payload, SignableElement[PAYLOAD].Ptr, SignableElement[PAYLOAD].Size); + + // Update the variable passed as parameter with the signed one + FreePool(Variable->Data); + Variable->Data = SignedVariable; + Variable->Size = Variable->Size + SignatureSize; + + Status = EFI_SUCCESS; + +exit: + FreePool(SignData); + return Status; } diff --git a/src/pki.h b/src/pki.h index 85e0b59..c24945c 100644 --- a/src/pki.h +++ b/src/pki.h @@ -36,10 +36,26 @@ EFI_STATUS SaveCredentials( IN CONST CHAR16 *BaseName ); -EFI_SIGNATURE_LIST* CertToEsl( - CONST IN VOID *Cert +VOID FreeCredentials( + IN CONST VOID *Cert, + IN CONST VOID *Key +); + +EFI_STATUS CertToAuthVar( + CONST IN VOID *Cert, + OUT AUTHENTICATED_VARIABLE *Variable ); -EFI_SIGNATURE_LIST* LoadToEsl( - IN CONST CHAR16 *Path +EFI_STATUS LoadToAuthVar( + IN CONST CHAR16 *Path, + OUT AUTHENTICATED_VARIABLE* Variable +); + +EFI_STATUS SignToAuthVar( + IN CONST CHAR16 *VariableName, + IN CONST EFI_GUID *VendorGuid, + IN CONST UINT32 Attributes, + IN OUT AUTHENTICATED_VARIABLE *Variable, + IN CONST VOID *Cert, + IN CONST VOID *Key ); diff --git a/src/variables.c b/src/variables.c index c833cf7..304538b 100644 --- a/src/variables.c +++ b/src/variables.c @@ -198,53 +198,3 @@ EFI_STATUS CheckSetupMode( exit: return Status; } - -// We would just use EDK2's EnrollFromInput() if we could, but it doesn't support appending... -EFI_STATUS SetSecureBootVariable( - IN CONST CHAR16 *VariableName, - IN CONST EFI_GUID *VendorGuid, - IN CONST EFI_SIGNATURE_LIST *Esl, - IN CONST BOOLEAN Append -) -{ - // TODO: MOK may need different options - CONST UINT32 Attributes = EFI_VARIABLE_NON_VOLATILE | EFI_VARIABLE_RUNTIME_ACCESS | - EFI_VARIABLE_BOOTSERVICE_ACCESS | EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS; - EFI_STATUS Status; - EFI_TIME Time = { 0 }; - UINT8 *Data = NULL; - UINTN Size; - - // Work on a copy of the ESL - Size = Esl->SignatureListSize; - Data = AllocateZeroPool(Size); - if (Data == NULL) { - Status = EFI_OUT_OF_RESOURCES; - ReportErrorAndExit(L"Failed to allocate time-based data payload: %r\n", Status); - } - CopyMem(Data, Esl, Size); - - Status = gRT->GetTime(&Time, NULL); - if (EFI_ERROR(Status)) - ReportErrorAndExit(L"Failed to get current time: %r\n", Status); - - // SetVariable() *will* fail with "Security Violation" unless you - // explicitly zero these before calling CreateTimeBasedPayload() - Time.Nanosecond = 0; - Time.TimeZone = 0; - Time.Daylight = 0; - - // NB: CreateTimeBasedPayload() frees the input buffer before replacing it - Status = CreateTimeBasedPayload(&Size, &Data, &Time); - if (EFI_ERROR(Status)) - ReportErrorAndExit(L"Failed to create time-based data payload: %r\n", Status); - - Status = gRT->SetVariable((CHAR16*)VariableName, (EFI_GUID*)VendorGuid, - Attributes | (Append ? EFI_VARIABLE_APPEND_WRITE : 0), Size, Data); - if (EFI_ERROR(Status)) - ReportErrorAndExit(L"Failed to set Secure Boot variable: %r\n", Status); - -exit: - FreePool(Data); - return Status; -} diff --git a/src/variables.h b/src/variables.h index 8795cd3..1dcead6 100644 --- a/src/variables.h +++ b/src/variables.h @@ -19,10 +19,3 @@ EFI_STATUS CheckSetupMode( IN CONST BOOLEAN TestMode ); - -EFI_STATUS SetSecureBootVariable( - IN CONST CHAR16 *VariableName, - IN CONST EFI_GUID *VendorGuid, - IN CONST EFI_SIGNATURE_LIST *Esl, - IN CONST BOOLEAN Append -);