Skip to content

Commit

Permalink
Correcting Incorrect Usage of EfiSignatureList (#561)
Browse files Browse the repository at this point in the history
An authenticated variable may contain only a single EfiSignatureList. However, they may contain multiple EfiSignatureLists otherwise known as EfiSignatureDatabase.

This change corrects that misconception
  • Loading branch information
Flickdm committed May 31, 2024
1 parent 4bb3e2a commit ba08f64
Show file tree
Hide file tree
Showing 2 changed files with 87 additions and 70 deletions.
46 changes: 29 additions & 17 deletions edk2toollib/uefi/authenticated_variables_structure_support.py
Original file line number Diff line number Diff line change
Expand Up @@ -908,18 +908,30 @@ class EfiSignatureDatabase(CommonUefiStructure):
Useful for parsing and building the contents of the Secure Boot variables
"""

def __init__(self, filestream: BinaryIO = None, EslList: List[EfiSignatureList] = None) -> 'EfiSignatureDatabase':
def __init__(self, filestream: BinaryIO = None, esl_list: List[EfiSignatureList] = None) -> 'EfiSignatureDatabase':
"""Inits an Efi Signature Database object.
Args:
filestream (:obj:`BinaryIO`, optional): Inits the object with this stream
EslList (:obj:`list[EfiSignatureList]`, optional): Inits the object with this list
esl_list (:obj:`list[EfiSignatureList]`, optional): Inits the object with this list
"""
if filestream:
self.EslList = []
self.esl_list = []
self.PopulateFromFileStream(filestream)
else:
self.EslList = [] if EslList is None else EslList
self.esl_list = [] if esl_list is None else esl_list

@property
def EslList(self) -> List[EfiSignatureList]:
"""Returns the EFI Signature List."""
warn("EslList is deprecated. Use esl_list instead.", DeprecationWarning, 2)
return self.esl_list

@EslList.setter
def EslList(self, value: List[EfiSignatureList]) -> None:
"""Sets the EFI Signature List."""
warn("EslList is deprecated. Use esl_list instead.", DeprecationWarning, 2)
self.esl_list = value

def decode(self, fs: BinaryIO, decodesize: int = -1) -> None:
"""Decodes a filestream and generates the structure.
Expand All @@ -934,14 +946,14 @@ def decode(self, fs: BinaryIO, decodesize: int = -1) -> None:
"""
if (fs is None):
raise Exception("Invalid File Stream")
self.EslList = []
self.esl_list = []
begin = fs.tell()
fs.seek(0, io.SEEK_END)
end = fs.tell() # end is offset after last byte
fs.seek(begin)
while (fs.tell() != end):
Esl = EfiSignatureList(fs)
self.EslList.append(Esl)
self.esl_list.append(Esl)

def PopulateFromFileStream(self, fs: BinaryIO) -> None:
"""Decodes a filestream and generates the structure.
Expand All @@ -957,7 +969,7 @@ def PopulateFromFileStream(self, fs: BinaryIO) -> None:

def print(self, compact: bool = False, outfs: IO=sys.stdout) -> None:
"""Prints to the console."""
for Esl in self.EslList:
for Esl in self.esl_list:
Esl.print(compact=compact, outfs=outfs)

def Print(self, compact: bool = False, outfs: IO=sys.stdout) -> None:
Expand All @@ -982,7 +994,7 @@ def write(self, fs: BinaryIO) -> None:
"""
if (fs is None):
raise Exception("Invalid File Output Stream")
for Esl in self.EslList:
for Esl in self.esl_list:
Esl.write(fs)

def GetBytes(self) -> bytes:
Expand Down Expand Up @@ -1014,7 +1026,7 @@ def get_canonical_and_dupes(self) -> tuple['EfiSignatureDatabase', 'EfiSignature
sha256esl = None
x509eslList = None

for Esl in self.EslList:
for Esl in self.esl_list:
if (Esl.signature_data_list is None): # discard empty EfiSignatureLists
continue
if (Esl.signature_type == EfiSignatureDataFactory.EFI_CERT_SHA256_GUID):
Expand All @@ -1041,18 +1053,18 @@ def get_canonical_and_dupes(self) -> tuple['EfiSignatureDatabase', 'EfiSignature

if (sha256esl is not None):
dupes = sha256esl.SortBySignatureDataValue(deduplicate=True)
canonicalDb.EslList.append(sha256esl)
duplicatesDb.EslList.append(dupes)
canonicalDb.esl_list.append(sha256esl)
duplicatesDb.esl_list.append(dupes)

if (x509eslList is not None):
x509eslList.sort(key=attrgetter('signature_data_list'))
for esl in x509eslList:
if not canonicalDb.EslList:
canonicalDb.EslList.append(esl)
elif esl == canonicalDb.EslList[-1]:
duplicatesDb.EslList.append(esl)
if not canonicalDb.esl_list:
canonicalDb.esl_list.append(esl)
elif esl == canonicalDb.esl_list[-1]:
duplicatesDb.esl_list.append(esl)
else:
canonicalDb.EslList.append(esl)
canonicalDb.esl_list.append(esl)

return (canonicalDb, duplicatesDb)

Expand Down Expand Up @@ -1396,7 +1408,7 @@ def set_payload(self, fs: BinaryIO) -> None:
# Variables with the GUID EFI_IMAGE_SECURITY_DATABASE_GUID are formatted
# as EFI_SIGNATURE_LIST
try:
self.sig_list_payload = EfiSignatureList(fs)
self.sig_list_payload = EfiSignatureDatabase(fs)
except Exception:
# Do nothing - we attempted to parse it as a sig list and it failed
logging.debug("SigList Payload not detected.")
Expand Down
111 changes: 58 additions & 53 deletions tests.unit/test_authenticated_variables_structure_support.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,42 +8,49 @@

# spell-checker: ignore deduplicated, deduplication, Dedpulication

import datetime
import io
import unittest
import uuid
import io
import datetime

from cryptography.hazmat.primitives.serialization import pkcs12

from edk2toollib.uefi.authenticated_variables_structure_support import \
EfiSignatureDatabase, EfiSignatureList, EfiSignatureDataEfiCertSha256,\
EfiSignatureDataEfiCertX509, EfiSignatureDataFactory, EfiTime, \
EfiVariableAuthentication2, EfiVariableAuthentication2Builder, \
EfiVariableAttributes

from testdata.certificate_blobs import \
DEBIAN_CERT, UBUNTU_CERT, HASHSTR, DBXFILE, \
TEST_CERTIFICATE_PFX, TEST_CERTIFICATE_PASS
from edk2toollib.uefi.authenticated_variables_structure_support import (
EfiSignatureDatabase,
EfiSignatureDataEfiCertSha256,
EfiSignatureDataEfiCertX509,
EfiSignatureDataFactory,
EfiSignatureList,
EfiTime,
EfiVariableAttributes,
EfiVariableAuthentication2,
EfiVariableAuthentication2Builder,
)
from testdata.certificate_blobs import (
DBXFILE,
DEBIAN_CERT,
HASHSTR,
TEST_CERTIFICATE_PASS,
TEST_CERTIFICATE_PFX,
UBUNTU_CERT,
)

VERBOSE = False


def sha256_esl_builder(initList: []):
"""
Creates an EfiSignatureList of type EFI_CERT_SHA256 from a list of (hash, sigowner) tuples
"""Creates an EfiSignatureList of type EFI_CERT_SHA256 from a list of (hash, sigowner) tuples
initList is [ (hashString1, ownerGuidString1), ...]
"""

Esl = EfiSignatureList(typeguid=EfiSignatureDataFactory.EFI_CERT_SHA256_GUID)
esl = EfiSignatureList(typeguid=EfiSignatureDataFactory.EFI_CERT_SHA256_GUID)
SignatureSize = EfiSignatureDataEfiCertSha256.STATIC_STRUCT_SIZE if initList else 0
Esl.AddSignatureHeader(SigHeader=None, SigSize=SignatureSize)
esl.AddSignatureHeader(SigHeader=None, SigSize=SignatureSize)
for entry in initList:
(hashStr, ownerGuidStr) = entry
hashBytes = bytes.fromhex(hashStr)
Esl.AddSignatureData(EfiSignatureDataEfiCertSha256(digest=hashBytes,
esl.AddSignatureData(EfiSignatureDataEfiCertSha256(digest=hashBytes,
sigowner=uuid.UUID(ownerGuidStr)))
return Esl
return esl


def x509_esl_builder(initList: []):
Expand All @@ -60,25 +67,25 @@ def x509_esl_builder(initList: []):
if (listSize > 1):
raise Exception("initList length > 1, unsupported by type")

Esl = EfiSignatureList(typeguid=EfiSignatureDataFactory.EFI_CERT_X509_GUID)
esl = EfiSignatureList(typeguid=EfiSignatureDataFactory.EFI_CERT_X509_GUID)

if (listSize == 0):
Esl.AddSignatureHeader(SigHeader=None, SigSize=0)
esl.AddSignatureHeader(SigHeader=None, SigSize=0)
else:
(certHexStr, ownerGuidStr) = initList[0]
certBytes = bytes.fromhex(certHexStr)
Esl.AddSignatureHeader(SigHeader=None, SigSize=EfiSignatureDataEfiCertX509.STATIC_STRUCT_SIZE + len(certBytes))
Esl.AddSignatureData(EfiSignatureDataEfiCertX509(cert=certBytes,
esl.AddSignatureHeader(SigHeader=None, SigSize=EfiSignatureDataEfiCertX509.STATIC_STRUCT_SIZE + len(certBytes))
esl.AddSignatureData(EfiSignatureDataEfiCertX509(cert=certBytes,
sigowner=uuid.UUID(ownerGuidStr)))
return Esl
return esl


def basicEfiSignatureDatabase():
owner = uuid.uuid4().hex
certInput = [(DEBIAN_CERT, owner)]
cert = x509_esl_builder(certInput)

hashEslInput = [
hash_esl_input = [
(HASHSTR[8], owner),
(HASHSTR[0], owner),
(HASHSTR[0], owner),
Expand All @@ -91,22 +98,21 @@ def basicEfiSignatureDatabase():
(HASHSTR[2], owner)
]

hashes = sha256_esl_builder(hashEslInput)
hashes = sha256_esl_builder(hash_esl_input)

EslList = [
esl_list = [
cert, hashes
]

return EfiSignatureDatabase(EslList=EslList)
return EfiSignatureDatabase(esl_list=esl_list)


def BootHoleData():
""" Returns a tuple of the (rawData, expectedDupes, expectedCanonical) for the BootHole dbx generated by EDK2 """

"""Returns a tuple of the (rawData, expectedDupes, expectedCanonical) for the BootHole dbx generated by EDK2"""
owner = '77fa9abd-0359-4d32-bd60-28f4e78f784b'

# First prepare EfiSignatureDatabase that is unsorted, unmerged, and contains duplicates
originalHashes = [
original_hashes = [
('80B4D96931BF0D02FD91A61E19D14F1DA452E66DB2408CA8604D411F92659F0A', owner),
('F52F83A3FA9CFBD6920F722824DBE4034534D25B8507246B3B957DAC6E1BCE7A', owner),
('C5D9D8A186E2C82D09AFAA2A6F7F2E73870D3E64F72C4E08EF67796A840F0FBD', owner),
Expand Down Expand Up @@ -185,15 +191,15 @@ def BootHoleData():
('64575BD912789A2E14AD56F6341F52AF6BF80CF94400785975E9F04E2D64D745', owner),
('45C7C8AE750ACFBB48FC37527D6412DD644DAED8913CCD8A24C94D856967DF8E', owner)
]
originalEsl = sha256_esl_builder(originalHashes)
original_esl = sha256_esl_builder(original_hashes)

cert1 = [(UBUNTU_CERT, owner)]
cert1_esl = x509_esl_builder(cert1)

cert2 = [(DEBIAN_CERT, owner)]
cert2_esl = x509_esl_builder(cert2)

newHashes = [
new_hashes = [
('81D8FB4C9E2E7A8225656B4B8273B7CBA4B03EF2E9EB20E0A0291624ECA1BA86', owner),
('B92AF298DC08049B78C77492D6551B710CD72AADA3D77BE54609E43278EF6E4D', owner),
('E19DAE83C02E6F281358D4EBD11D7723B4F5EA0E357907D5443DECC5F93C1E9D', owner),
Expand Down Expand Up @@ -356,10 +362,10 @@ def BootHoleData():
('408B8B3DF5ABB043521A493525023175AB1261B1DE21064D6BF247CE142153B9', owner),
('540801DD345DC1C33EF431B35BF4C0E68BD319B577B9ABE1A9CFF1CBC39F548F', owner)
]
newHash_esl = sha256_esl_builder(newHashes)
newHash_esl = sha256_esl_builder(new_hashes)

# EDK2 appended cert1, cert2, and the partially de-duped new hashes to the original dbx
esd = EfiSignatureDatabase(EslList=[originalEsl, cert1_esl, cert2_esl, newHash_esl])
esd = EfiSignatureDatabase(esl_list=[original_esl, cert1_esl, cert2_esl, newHash_esl])

# Now prepare the duplicate data, these were the 4 dupes in the new list that were not de-duped by EDK2
expected_dupes = [
Expand All @@ -369,10 +375,10 @@ def BootHoleData():
('E051B788ECBAEDA53046C70E6AF6058F95222C046157B8C4C1B9C2CFC65F46E5', owner)
]
expected_dupes_esl = sha256_esl_builder(expected_dupes)
expected_dupes_esd = EfiSignatureDatabase(EslList=[expected_dupes_esl])
expected_dupes_esd = EfiSignatureDatabase(esl_list=[expected_dupes_esl])

# Now prepare the canonicalized data
canonicalHashes = [
canonical_hashes = [
('0257FF710F2A16E489B37493C07604A7CDA96129D8A8FD68D2B6AF633904315D', owner),
('02E6216ACAEF6401401FA555ECBED940B1A5F2569AED92956137AE58482EF1B7', owner),
('03F64A29948A88BEFFDB035E0B09A7370CCF0CD9CE6BCF8E640C2107318FAB87', owner),
Expand Down Expand Up @@ -608,15 +614,14 @@ def BootHoleData():
('FE63A84F782CC9D3FCF2CCF9FC11FBD03760878758D26285ED12669BDC6E6D01', owner),
('FECFB232D12E994B6D485D2C7167728AA5525984AD5CA61E7516221F079A1436', owner)
]
canonical_hashes_esl = sha256_esl_builder(canonicalHashes)
expected_canonical_esd = EfiSignatureDatabase(EslList=[canonical_hashes_esl, cert2_esl, cert1_esl])
canonical_hashes_esl = sha256_esl_builder(canonical_hashes)
expected_canonical_esd = EfiSignatureDatabase(esl_list=[canonical_hashes_esl, cert2_esl, cert1_esl])

return (esd, expected_dupes_esd, expected_canonical_esd)


def MixedCertsData():
""" Returns a tuple of (rawData, expectedDupes, expectedCanonical) for a mix of unsorted certs with dupes """

"""Returns a tuple of (rawData, expectedDupes, expectedCanonical) for a mix of unsorted certs with dupes"""
owner = '77fa9abd-0359-4d32-bd60-28f4e78f784b'

esls = [
Expand All @@ -631,20 +636,20 @@ def MixedCertsData():
esls[0],
esls[1]
]
esd = EfiSignatureDatabase(EslList=input)
esd = EfiSignatureDatabase(esl_list=input)

dupes = [
esls[0],
esls[1],
esls[1]
]
expected_dupes_esd = EfiSignatureDatabase(EslList=dupes)
expected_dupes_esd = EfiSignatureDatabase(esl_list=dupes)

canonical = [
esls[0],
esls[1],
]
expected_canonical_esd = EfiSignatureDatabase(EslList=canonical)
expected_canonical_esd = EfiSignatureDatabase(esl_list=canonical)

return (esd, expected_dupes_esd, expected_canonical_esd)

Expand Down Expand Up @@ -680,7 +685,7 @@ def test_deserializeEqualsSerialize(self):
def test_EfiSignatureList_Sort_and_Deduplication_x509(self):
owner = uuid.uuid4().hex

subTestList = [
sub_test_list = [
(
"SubTest_Empty",
# input
Expand All @@ -701,7 +706,7 @@ def test_EfiSignatureList_Sort_and_Deduplication_x509(self):
)
]

for subTest in subTestList:
for subTest in sub_test_list:
(testName, input, expected_sort, expected_dupes) = subTest
with self.subTest(msg=testName):
testEsl = x509_esl_builder(input)
Expand All @@ -718,7 +723,7 @@ def test_EfiSignatureList_Sort_and_Deduplication_sha256(self):
owner1 = uuid.uuid4().hex
owner2 = uuid.uuid4().hex

subTestList = [
sub_test_list = [
(
"SubTest_Empty",
# input
Expand Down Expand Up @@ -874,7 +879,7 @@ def test_EfiSignatureList_Sort_and_Deduplication_sha256(self):
),
]

for subTest in subTestList:
for subTest in sub_test_list:
(testName, input, expected_sort, expected_dupes) = subTest
with self.subTest(msg=testName):
testEsl = sha256_esl_builder(input)
Expand All @@ -887,13 +892,13 @@ def test_EfiSignatureList_Sort_and_Deduplication_sha256(self):
self.assertEqual(output_dupes_esl.encode(), expected_dupes_esl.encode())

def test_EfiSignatureDatabase_Sort_and_Deduplication(self):
subTestList = [
sub_test_list = [
("MixedCerts", MixedCertsData()),
("BootHole", BootHoleData()),
]

for subTest in subTestList:
(testName, (Esd, ExpectedDupesEsd, ExpectedCanonicalEsd)) = subTest
for sub_test in sub_test_list:
(testName, (Esd, expected_dupes_esd, expected_canonical_esd)) = sub_test
with self.subTest(msg=testName):

(output_canonical_esd, output_dupes_esd) = Esd.get_canonical_and_dupes()
Expand All @@ -908,8 +913,8 @@ def test_EfiSignatureDatabase_Sort_and_Deduplication(self):
print("Canonical: ")
output_canonical_esd.Print(compact=True)

self.assertEqual(output_dupes_esd.encode(), ExpectedDupesEsd.encode())
self.assertEqual(output_canonical_esd.encode(), ExpectedCanonicalEsd.encode())
self.assertEqual(output_dupes_esd.encode(), expected_dupes_esd.encode())
self.assertEqual(output_canonical_esd.encode(), expected_canonical_esd.encode())


class EfiVariableAuthenticatedTests(unittest.TestCase):
Expand Down

0 comments on commit ba08f64

Please sign in to comment.