CloverBootloader/rEFIt_UEFI/entry_scan/securevars.cpp
2022-04-26 00:55:56 +02:00

530 lines
20 KiB
C++

/*
* refit/scan/securevars.c
*
* Copyright (c) 2006-2010 Christoph Pfisterer
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the
* distribution.
*
* * Neither the name of Christoph Pfisterer nor the names of the
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
//CONST INTN SecureVarsDef = 0; // jief : not used ??
#ifdef ENABLE_SECURE_BOOT
#include <Platform.h>
#include "../Platform/Settings.h"
#include "entry_scan.h"
#include <Guid/ImageAuthentication.h>
#include "securebootkeys.h"
#include "../../Library/OpensslLib/Include/OpenSslSupport.h"
#include "../../Library/OpensslLib/openssl-1.0.1e/include/openssl/err.h"
#include "../../Library/OpensslLib/openssl-1.0.1e/include/openssl/pem.h"
#include "../../Library/OpensslLib/openssl-1.0.1e/include/openssl/sha.h"
#include "../../Library/OpensslLib/openssl-1.0.1e/include/openssl/bio.h"
#ifndef DEBUG_ALL
#define DEBUG_SECURE_VARS 1
#else
#define DEBUG_SECURE_VARS DEBUG_ALL
#endif
#if DEBUG_SECURE_VARS == 0
#define DBG(...)
#else
#define DBG(...) DebugLog(DEBUG_SECURE_VARS, __VA_ARGS__)
#endif
#define SET_DATABASE_ATTRIBUTES (EFI_VARIABLE_NON_VOLATILE | EFI_VARIABLE_BOOTSERVICE_ACCESS | EFI_VARIABLE_RUNTIME_ACCESS | \
EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS | EFI_VARIABLE_AUTHENTICATED_WRITE_ACCESS)
#define SET_ADD_DATABASE_ATTRIBUTES (SET_DATABASE_ATTRIBUTES | EFI_VARIABLE_APPEND_WRITE)
// Clear the secure boot keys
EFI_STATUS ClearSecureBootKeys(void)
{
// Clear the platform database
return gRT->SetVariable(PLATFORM_DATABASE_NAME, PLATFORM_DATABASE_GUID, SET_DATABASE_ATTRIBUTES, sizeof(gSecureBootPlatformNullSignedKey), (void *)gSecureBootPlatformNullSignedKey);
}
// Enroll the secure boot keys
EFI_STATUS EnrollSecureBootKeys(IN void *AuthorizedDatabase,
IN UINTN AuthorizedDatabaseSize,
IN XBool WantDefaultKeys)
{
EFI_STATUS Status;
// Enroll this image's certificate
UINTN DatabaseSize = 0;
void *Database = NULL;
FreePool(AuthorizedDatabase);
if (WantDefaultKeys) {
// Get default authorized database
DBG("Retrieving default authorized database ...\n");
Database = GetSignatureDatabase(DEFAULT_AUTHORIZED_DATABASE_NAME, DEFAULT_AUTHORIZED_DATABASE_GUID, &DatabaseSize);
if ( DatabaseSize == 0 && Database != NULL ) {
FreePool(Database);
Database = NULL;
}
}
// Set the authorized database
if (Database != NULL) {
DBG("Appending default authorized database to authorized database ...\n");
//Status = AppendSignatureDatabaseToDatabase(&Database, &DatabaseSize, AuthorizedDatabase, AuthorizedDatabaseSize);
Status = AppendSignatureToDatabase(&Database, &DatabaseSize, gEfiCertX509Guid, (void *)gSecureBootDatabaseKey, sizeof(gSecureBootDatabaseKey));
if (EFI_ERROR(Status)) {
FreePool(Database);
return Status;
}
DBG("Setting authorized database ...\n");
Status = SetAuthorizedDatabase(Database, DatabaseSize);
FreePool(Database);
DatabaseSize = 0;
Database = NULL;
} else {
// Set clover signature as only unless default keys are also wanted
DBG("Setting authorized database ...\n");
if (WantDefaultKeys) {
DBG("No default authorized database found, using built-in default keys ...\n");
DatabaseSize = 0;
Database = NULL;
Status = AppendSignatureToDatabase(&Database, &DatabaseSize, gEfiCertX509Guid, (void *)gSecureBootCanonicalDatabaseKey, sizeof(gSecureBootCanonicalDatabaseKey));
if (EFI_ERROR(Status)) {
if (Database != NULL) {
FreePool(Database);
}
DBG("Failed to modify authorized database with Canonical key! %s\n", efiStrError(Status));
return Status;
}
Status = AppendSignatureToDatabase(&Database, &DatabaseSize, gEfiCertX509Guid, (void *)gSecureBootMSPCADatabaseKey, sizeof(gSecureBootMSPCADatabaseKey));
if (EFI_ERROR(Status)) {
if (Database != NULL) {
FreePool(Database);
}
DBG("Failed to modify authorized database with MS PCA key! %s\n", efiStrError(Status));
return Status;
}
Status = AppendSignatureToDatabase(&Database, &DatabaseSize, gEfiCertX509Guid, (void *)gSecureBootMSUEFICADatabaseKey, sizeof(gSecureBootMSUEFICADatabaseKey));
if (EFI_ERROR(Status)) {
if (Database != NULL) {
FreePool(Database);
}
DBG("Failed to modify authorized database with MS UEFICA key! %s\n", efiStrError(Status));
return Status;
}
}
Status = AppendSignatureToDatabase(&Database, &DatabaseSize, gEfiCertX509Guid, (void *)gSecureBootDatabaseKey, sizeof(gSecureBootDatabaseKey));
//Status = AppendSignatureToDatabase(&Database, &DatabaseSize, &gEfiCertX509Guid, AuthorizedDatabase, AuthorizedDatabaseSize);
Status = SetAuthorizedDatabase(Database, DatabaseSize);
FreePool(Database);
DatabaseSize = 0;
Database = NULL;
//Status = SetAuthorizedDatabase(AuthorizedDatabase, AuthorizedDatabaseSize);
// Append keys if needed...
}
if (EFI_ERROR(Status)) {
DBG("Failed to set the authorized database! %s\n", efiStrError(Status));
return Status;
}
// We set the unauthorized database
if (WantDefaultKeys) {
// Get the default authorized database
DBG("Retrieving the default unauthorized database ...\n");
DatabaseSize = 0;
Database = NULL;
Database = GetSignatureDatabase(DEFAULT_UNAUTHORIZED_DATABASE_NAME, DEFAULT_UNAUTHORIZED_DATABASE_GUID, &DatabaseSize);
if ((DatabaseSize == 0) && (Database != NULL)) {
FreePool(Database);
Database = NULL;
}
// Set the unauthorized database
if (Database != NULL) {
Status = SetSignatureDatabase(UNAUTHORIZED_DATABASE_NAME, UNAUTHORIZED_DATABASE_GUID, Database, DatabaseSize);
FreePool(Database);
DatabaseSize = 0;
if (EFI_ERROR(Status)) {
DBG("Failed to set the unauthorized database! %s\n", efiStrError(Status));
return Status;
}
}
}
// We need to enroll our own exchange database because we may update databases outside of setup mode
DatabaseSize = 0;
Database = NULL;
if (WantDefaultKeys) {
// Get the default exchange database
DBG("Retrieving default exchange database ...\n");
Database = GetSignatureDatabase(DEFAULT_EXCHANGE_DATABASE_NAME, DEFAULT_EXCHANGE_DATABASE_GUID, &DatabaseSize);
if ((DatabaseSize == 0) && (Database != NULL)) {
FreePool(Database);
Database = NULL;
DatabaseSize = 0;
}
if (Database == NULL) {
DBG("No default exchange database found, using built-in default keys ...\n");
DatabaseSize = 0;
Database = NULL;
Status = AppendSignatureToDatabase(&Database, &DatabaseSize, gEfiCertX509Guid, (void *)gSecureBootMSExchangeKey, sizeof(gSecureBootMSExchangeKey));
if (EFI_ERROR(Status)) {
if (Database != NULL) {
FreePool(Database);
}
DBG("Failed to modify exchange database with MS exchange key! %s\n", efiStrError(Status));
return Status;
}
}
}
// Set the exchange database
DBG("Modifying exchange database ...\n");
Status = AppendSignatureToDatabase(&Database, &DatabaseSize, gEfiCertX509Guid, (void *)gSecureBootExchangeKeyDER, sizeof(gSecureBootExchangeKeyDER));
if (EFI_ERROR(Status)) {
if (Database != NULL) {
FreePool(Database);
}
DBG("Failed to modify exchange database! %s\n", efiStrError(Status));
return Status;
}
DBG("Setting the exchange database ...\n");
Status = SetSignatureDatabase(EXCHANGE_DATABASE_NAME, EXCHANGE_DATABASE_GUID, Database, DatabaseSize);
FreePool(Database);
DatabaseSize = 0;
Database = NULL;
if (EFI_ERROR(Status)) {
DBG("Failed to set exchange database key! %s\n", efiStrError(Status));
return Status;
}
// Unsure if default platform database should be enrolled.....???
// Set the platform database - NOT ENROLLING DEFAULT PLATFORM DATABASE, ONLY CLOVER SHOULD OWN PLATFORM(?)
DBG("Setting the platform database ...\n");
Status = gRT->SetVariable(PLATFORM_DATABASE_NAME, PLATFORM_DATABASE_GUID, SET_DATABASE_ATTRIBUTES, sizeof(gSecureBootPlatformSignedKey), (void *)gSecureBootPlatformSignedKey);
return Status;
}
// Read signature database
void *GetSignatureDatabase(const wchar_t *DatabaseName,
const EFI_GUID& DatabaseGuid,
OUT UINTN *DatabaseSize)
{
UINTN Size = 0;
void *Database;
// Check parameters
if (DatabaseSize == NULL) {
return NULL;
}
*DatabaseSize = 0;
if ( DatabaseName == NULL || DatabaseGuid.isNull() ) {
return NULL;
}
// Get database size
if (gRT->GetVariable(DatabaseName, DatabaseGuid, NULL, &Size, NULL) != EFI_BUFFER_TOO_SMALL) {
return NULL;
}
if (Size == 0) {
return NULL;
}
// Allocate a buffer large enough to hold the database
Database = (__typeof__(Database))AllocateZeroPool(Size);
if (Database == NULL) {
return NULL;
}
// Read database
if (EFI_ERROR(gRT->GetVariable(DatabaseName, DatabaseGuid, NULL, &Size, Database)) || (Size == 0)) {
FreePool(Database);
return NULL;
}
// Return database
*DatabaseSize = Size;
return Database;
}
#define IsLeap(y) (((y) % 4) == 0 && (((y) % 100) != 0 || ((y) % 400) == 0))
#define SECSPERMIN (60)
#define SECSPERHOUR (60 * 60)
#define SECSPERDAY (24 * SECSPERHOUR)
STATIC UINTN CumulativeDays[2][14] = {
{
0,
0,
31,
31 + 28,
31 + 28 + 31,
31 + 28 + 31 + 30,
31 + 28 + 31 + 30 + 31,
31 + 28 + 31 + 30 + 31 + 30,
31 + 28 + 31 + 30 + 31 + 30 + 31,
31 + 28 + 31 + 30 + 31 + 30 + 31 + 31,
31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30,
31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31,
31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31 + 30,
31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31 + 30 + 31
},
{
0,
0,
31,
31 + 29,
31 + 29 + 31,
31 + 29 + 31 + 30,
31 + 29 + 31 + 30 + 31,
31 + 29 + 31 + 30 + 31 + 30,
31 + 29 + 31 + 30 + 31 + 30 + 31,
31 + 29 + 31 + 30 + 31 + 30 + 31 + 31,
31 + 29 + 31 + 30 + 31 + 30 + 31 + 31 + 30,
31 + 29 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31,
31 + 29 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31 + 30,
31 + 29 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31 + 30 + 31
}
};
STATIC EFI_STATUS GetUTCTime(OUT EFI_TIME *Timestamp)
{
EFI_STATUS Status;
UINTN Timer = 0;
UINTN Year, YearNo, MonthNo;
UINTN DayNo, DayRemainder;
if (Timestamp == NULL) {
return EFI_INVALID_PARAMETER;
}
// Get current time
Status = gRT->GetTime(Timestamp, NULL);
if (EFI_ERROR(Status)) {
return Status;
}
// Get the current offset of the year in seconds since epoch
for (Year = 1970; Year < Timestamp->Year; ++Year) {
Timer += (UINTN)((IsLeap (Year) ? 366 : 365) * SECSPERDAY);
}
// Get the current offset
Timer += (UINTN)((Timestamp->TimeZone != EFI_UNSPECIFIED_TIMEZONE) ? (Timestamp->TimeZone * 60) : 0) +
(UINTN)(CumulativeDays[IsLeap(Timestamp->Year)][Timestamp->Month] * SECSPERDAY) +
(UINTN)(((Timestamp->Day > 0) ? Timestamp->Day - 1 : 0) * SECSPERDAY) +
(UINTN)(Timestamp->Hour * SECSPERHOUR) +
(UINTN)(Timestamp->Minute * 60) +
(UINTN)Timestamp->Second;
// Convert back to time
ZeroMem(Timestamp, sizeof(EFI_TIME));
DayNo = (UINTN)(Timer / SECSPERDAY);
DayRemainder = (UINTN)(Timer % SECSPERDAY);
Timestamp->Second = (UINT8)(DayRemainder % SECSPERMIN);
Timestamp->Minute = (UINT8)((DayRemainder % SECSPERHOUR) / SECSPERMIN);
Timestamp->Hour = (UINT8)(DayRemainder / SECSPERHOUR);
for (Year = 1970, YearNo = 0; DayNo > 0; ++Year) {
UINTN TotalDays = (IsLeap(Year) ? 366 : 365);
if (DayNo >= TotalDays) {
DayNo = (DayNo - TotalDays);
YearNo++;
} else {
break;
}
}
Timestamp->Year = (UINT16)(YearNo + 1970);
for (MonthNo = 12; MonthNo > 1; --MonthNo) {
if (DayNo >= CumulativeDays[IsLeap(Year)][MonthNo]) {
DayNo = (UINT16) (DayNo - (UINT16) (CumulativeDays[IsLeap(Year)][MonthNo]));
break;
}
}
Timestamp->Month = (UINT8)MonthNo;
Timestamp->Day = (UINT8)(DayNo + 1);
Timestamp->Nanosecond = 0;
Timestamp->TimeZone = 0;
Timestamp->Daylight = 0;
Timestamp->Pad1 = 0;
Timestamp->Pad2 = 0;
return EFI_SUCCESS;
}
// Write signed variable
EFI_STATUS SetSignedVariable(IN CHAR16 *DatabaseName,
const EFI_GUID& DatabaseGuid,
IN UINT32 Attributes,
IN void *Database,
IN UINTN DatabaseSize)
{
EFI_STATUS Status;
EFI_VARIABLE_AUTHENTICATION_2 *Authentication;
UINTN Size, NameLen;
UINTN DataSize = 0;
EFI_TIME Timestamp;
void *Data = NULL;
BIO *BioData = NULL;
PKCS7 *p7;
X509 *Certificate = NULL;
EVP_PKEY *PrivateKey = NULL;
const EVP_MD *md;
// Check parameters
if ( DatabaseName == NULL || DatabaseGuid.isNull() ) {
return EFI_INVALID_PARAMETER;
}
DBG("Setting secure variable: %s %ls 0x%llX (0x%llX)\n", DatabaseGuid.toXString8().c_str(), DatabaseName, uintptr_t(Database), DatabaseSize);
NameLen = StrLen(DatabaseName);
if (NameLen == 0) {
return EFI_INVALID_PARAMETER;
}
// Get the current time
DBG("Getting timestamp ...\n");
Status = GetUTCTime(&Timestamp);
if (EFI_ERROR(Status)) {
return Status;
}
// TODO: %t specifier DBG("Timestamp: %t\n", Timestamp);
// In user mode we need to sign the database with exchange key
if (!GlobalConfig.SecureBootSetupMode) {
// Initialize the cyphers and digests
ERR_load_crypto_strings();
OpenSSL_add_all_digests();
OpenSSL_add_all_ciphers();
// Create signing certificate
BioData = BIO_new_mem_buf((void *)gSecureBootExchangeKey, sizeof(gSecureBootExchangeKey));
if (BioData == NULL) {
return EFI_OUT_OF_RESOURCES;
}
Certificate = PEM_read_bio_X509(BioData, NULL, NULL, NULL);
BIO_free(BioData);
if (Certificate == NULL) {
return EFI_OUT_OF_RESOURCES;
}
// Create signing private key
BioData = BIO_new_mem_buf((void *)gSecureBootExchangePrivateKey, sizeof(gSecureBootExchangePrivateKey));
if (BioData == NULL) {
return EFI_OUT_OF_RESOURCES;
}
PrivateKey = PEM_read_bio_PrivateKey(BioData, NULL, NULL, NULL);
BIO_free(BioData);
if (PrivateKey == NULL) {
X509_free(Certificate);
return EFI_OUT_OF_RESOURCES;
}
// Do the actual signing process
BioData = BIO_new(BIO_s_mem());
BIO_write(BioData, DatabaseName, (int)StrLen(DatabaseName));
BIO_write(BioData, &DatabaseGuid, sizeof(EFI_GUID));
BIO_write(BioData, &Attributes, sizeof(UINT32));
BIO_write(BioData, &Timestamp, sizeof(EFI_TIME));
BIO_write(BioData, Database, (int)DatabaseSize);
md = EVP_get_digestbyname("SHA256");
p7 = PKCS7_new();
PKCS7_set_type(p7, NID_pkcs7_signed);
PKCS7_content_new(p7, NID_pkcs7_data);
PKCS7_sign_add_signer(p7, Certificate, PrivateKey, md, PKCS7_BINARY | PKCS7_DETACHED | PKCS7_NOSMIMECAP);
PKCS7_set_detached(p7, 1);
PKCS7_final(p7, BioData, PKCS7_BINARY | PKCS7_DETACHED | PKCS7_NOSMIMECAP);
X509_free(Certificate);
EVP_PKEY_free(PrivateKey);
DataSize = i2d_PKCS7(p7, NULL);
Data = (__typeof__(Data))AllocateZeroPool(DataSize);
i2d_PKCS7(p7, (unsigned char **)&Data);
PKCS7_free(p7);
// Set the authentication buffer size
Size = sizeof(EFI_TIME) + sizeof(EFI_GUID) + sizeof(UINT32) + sizeof(UINT16) + sizeof(UINT16) + DataSize;
} else {
// In setup mode we don't need to sign, so just set the database
DBG("In setup mode, not signing ...\n");
Size = sizeof(EFI_TIME) + sizeof(EFI_GUID) + sizeof(UINT32) + sizeof(UINT16) + sizeof(UINT16) + DatabaseSize;
}
// Create the authentication buffer
DBG("Creating authentication ...\n");
Authentication = (EFI_VARIABLE_AUTHENTICATION_2 *)AllocateZeroPool(Size);
if (Authentication == NULL) {
if (Data != NULL) {
FreePool(Data);
}
return EFI_OUT_OF_RESOURCES;
}
// Set the certificate elements
CopyMem(&(Authentication->TimeStamp), &Timestamp, sizeof(EFI_TIME));
Authentication->AuthInfo.Hdr.dwLength = (UINT32)(sizeof(EFI_GUID) + sizeof(UINT32) + sizeof(UINT16) + sizeof(UINT16) + DataSize);
Authentication->AuthInfo.Hdr.wRevision = 0x0200;
Authentication->AuthInfo.Hdr.wCertificateType = WIN_CERT_TYPE_EFI_GUID;
CopyMem(&(Authentication->AuthInfo.CertType), &gEfiCertPkcs7Guid, sizeof(EFI_GUID));
// Copy the data into the authentication
if (Data != NULL) {
CopyMem(((UINT8 *)Authentication) + sizeof(EFI_TIME) + sizeof(EFI_GUID) + sizeof(UINT32) + sizeof(UINT16) + sizeof(UINT16), Data, DataSize);
FreePool(Data);
} else {
CopyMem(((UINT8 *)Authentication) + sizeof(EFI_TIME) + sizeof(EFI_GUID) + sizeof(UINT32) + sizeof(UINT16) + sizeof(UINT16), Database, DatabaseSize); //Payload, PayloadSize);
}
DBG("Writing secure variable 0x%llX (0x%llX) ...\n", uintptr_t(Authentication), Size);
// Write the database variable
Status = gRT->SetVariable(DatabaseName, DatabaseGuid, SET_DATABASE_ATTRIBUTES, Size, Authentication);
// Cleanup the authentication buffer
FreePool(Authentication);
return Status;
}
// Write signature database
EFI_STATUS SetSignatureDatabase(IN CHAR16 *DatabaseName,
const EFI_GUID& DatabaseGuid,
IN void *Database,
IN UINTN DatabaseSize)
{
EFI_STATUS Status;
// Check is valid to set database
if ((GlobalConfig.SecureBoot && GlobalConfig.SecureBootSetupMode) ||
(!GlobalConfig.SecureBoot && !GlobalConfig.SecureBootSetupMode)) {
return EFI_NOT_FOUND;
}
// Erase database
Status = SetSignedVariable(DatabaseName, DatabaseGuid, 0, NULL, 0);
// Check if database was not found which is cool cause we are removing it
if (Status == EFI_NOT_FOUND) {
if ((Database == NULL) || (DatabaseSize == 0)) {
return EFI_SUCCESS;
}
} else if (EFI_ERROR(Status) || (Database == NULL) || (DatabaseSize == 0)) {
// Return status if only erasing
return Status;
}
return SetSignedVariable(DatabaseName, DatabaseGuid, SET_DATABASE_ATTRIBUTES, Database, DatabaseSize);
}
#endif // ENABLE_SECURE_BOOT