mirror of
https://github.com/acidanthera/audk.git
synced 2025-04-08 17:05:09 +02:00
REF: https://bugzilla.tianocore.org/show_bug.cgi?id=4821 - The capsule payload digest got hardcoded inside the GenerateCapsule script as "sha256". - It would be hard for the caller to change the supported hash algorithm which supported on OpenSSL or Windows signtool program and platform. - Capsule payload digest signed data is followed by the PKCS#7 standard, in EDK-II CryptoPkg "Pkcs7Verify ()" is supported to validate with several hash algorithms naturally. (md5, sha1, sha256, sha384, and sha512) - Deliver below changes within this patch, (1) Introduce an optional argument "--hash-algorithm" to assign the caller expected one and leave the default value "sha256" to support the backward compatibility. (2) Add the double quotes to put the string of certificate's subject name inside it. (3) Set "Open" argument of "SignToolSubjectName" into "False". (4) Set "Convert" argument of "SignToolSubjectName: into "str". (5) Correct the actual name of the "--subject-name" flag. (6) Add back correct number of arguments for PayloadDescriptor class object initializing. Note: - Platform needs to support the correspond hash algorithm to validate the digital signature or the failure would be observed. - Set the md5 and sha1 algorithm as EOL based on the CryptoPkg supported table and reject the capsule creation. Signed-off-by: Jason1 Lin <jason1.lin@intel.com>
1170 lines
63 KiB
Python
1170 lines
63 KiB
Python
## @file
|
|
# Generate a capsule.
|
|
#
|
|
# This tool generates a UEFI Capsule around an FMP Capsule. The capsule payload
|
|
# be signed using signtool or OpenSSL and if it is signed the signed content
|
|
# includes an FMP Payload Header.
|
|
#
|
|
# This tool is intended to be used to generate UEFI Capsules to update the
|
|
# system firmware or device firmware for integrated devices. In order to
|
|
# keep the tool as simple as possible, it has the following limitations:
|
|
# * Do not support vendor code bytes in a capsule.
|
|
#
|
|
# Copyright (c) 2018 - 2024, Intel Corporation. All rights reserved.<BR>
|
|
# SPDX-License-Identifier: BSD-2-Clause-Patent
|
|
#
|
|
|
|
'''
|
|
GenerateCapsule
|
|
'''
|
|
|
|
import sys
|
|
import argparse
|
|
import uuid
|
|
import struct
|
|
import subprocess
|
|
import os
|
|
import tempfile
|
|
import shutil
|
|
import platform
|
|
import json
|
|
from Common.Uefi.Capsule.UefiCapsuleHeader import UefiCapsuleHeaderClass
|
|
from Common.Uefi.Capsule.FmpCapsuleHeader import FmpCapsuleHeaderClass
|
|
from Common.Uefi.Capsule.FmpAuthHeader import FmpAuthHeaderClass
|
|
from Common.Uefi.Capsule.CapsuleDependency import CapsuleDependencyClass
|
|
from Common.Edk2.Capsule.FmpPayloadHeader import FmpPayloadHeaderClass
|
|
|
|
#
|
|
# Globals for help information
|
|
#
|
|
__prog__ = 'GenerateCapsule'
|
|
__version__ = '0.11'
|
|
__copyright__ = 'Copyright (c) 2024, Intel Corporation. All rights reserved.'
|
|
__description__ = 'Generate a capsule.\n'
|
|
|
|
#
|
|
# Globals definitions
|
|
#
|
|
HASH_ALG_MD5 = 'md5'
|
|
HASH_ALG_SHA1 = 'sha1'
|
|
HASH_ALG_SHA256 = 'sha256'
|
|
HASH_ALG_SHA384 = 'sha384'
|
|
HASH_ALG_SHA512 = 'sha512'
|
|
DEFAULT_HASH_ALGORITHM = HASH_ALG_SHA256
|
|
|
|
TOOL_SIGN_TOOL = 0x0
|
|
TOOL_OPENSSL = 0x1
|
|
|
|
SIGN_TOOL_HASH_ALG_EOL_LIST = [
|
|
HASH_ALG_MD5,
|
|
HASH_ALG_SHA1,
|
|
]
|
|
|
|
SIGN_TOOL_HASH_ALG_SUPPORT_LIST = [
|
|
HASH_ALG_SHA256,
|
|
HASH_ALG_SHA384,
|
|
HASH_ALG_SHA512,
|
|
]
|
|
|
|
OPENSSL_HASH_ALG_EOL_LIST = [
|
|
HASH_ALG_MD5,
|
|
HASH_ALG_SHA1,
|
|
]
|
|
|
|
OPENSSL_HASH_ALG_SUPPORT_LIST = [
|
|
HASH_ALG_SHA256,
|
|
HASH_ALG_SHA384,
|
|
HASH_ALG_SHA512,
|
|
]
|
|
|
|
def CheckHashAlgorithmSupported (ToolType, HashAlgorithm):
|
|
if ToolType == TOOL_SIGN_TOOL:
|
|
EolList = SIGN_TOOL_HASH_ALG_EOL_LIST
|
|
SupportList = SIGN_TOOL_HASH_ALG_SUPPORT_LIST
|
|
elif ToolType == TOOL_OPENSSL:
|
|
EolList = OPENSSL_HASH_ALG_EOL_LIST
|
|
SupportList = OPENSSL_HASH_ALG_SUPPORT_LIST
|
|
else:
|
|
raise ValueError ('GenerateCapsule: error: unsupported type of tool.')
|
|
|
|
if HashAlgorithm.lower () in EolList:
|
|
raise ValueError ('GenerateCapsule: error: hash algorithm [{HashAlgorithm}] had been EOL.'.format (HashAlgorithm = HashAlgorithm))
|
|
elif HashAlgorithm.lower () not in SupportList:
|
|
raise ValueError ('GenerateCapsule: error: hash algorithm [{HashAlgorithm}] is not supported.'.format (HashAlgorithm = HashAlgorithm))
|
|
|
|
return
|
|
|
|
def SignPayloadSignTool (Payload, ToolPath, PfxFile, SubjectName, HashAlgorithm = DEFAULT_HASH_ALGORITHM, Verbose = False):
|
|
#
|
|
# Check the hash algorithm is supported
|
|
#
|
|
CheckHashAlgorithmSupported (TOOL_SIGN_TOOL, HashAlgorithm)
|
|
|
|
#
|
|
# Create a temporary directory
|
|
#
|
|
TempDirectoryName = tempfile.mkdtemp()
|
|
|
|
#
|
|
# Generate temp file name for the payload contents
|
|
#
|
|
TempFileName = os.path.join (TempDirectoryName, 'Payload.bin')
|
|
|
|
#
|
|
# Create temporary payload file for signing
|
|
#
|
|
try:
|
|
with open (TempFileName, 'wb') as File:
|
|
File.write (Payload)
|
|
except:
|
|
shutil.rmtree (TempDirectoryName)
|
|
raise ValueError ('GenerateCapsule: error: can not write temporary payload file.')
|
|
|
|
#
|
|
# Build signtool command
|
|
#
|
|
if ToolPath is None:
|
|
ToolPath = ''
|
|
Command = ''
|
|
Command = Command + '"{Path}" '.format (Path = os.path.join (ToolPath, 'signtool.exe'))
|
|
Command = Command + 'sign /fd {HashAlgorithm} /p7ce DetachedSignedData /p7co 1.2.840.113549.1.7.2 '.format (HashAlgorithm = HashAlgorithm)
|
|
Command = Command + '/p7 {TempDir} '.format (TempDir = TempDirectoryName)
|
|
if PfxFile is not None:
|
|
Command = Command + '/f {PfxFile} '.format (PfxFile = PfxFile)
|
|
if SubjectName is not None:
|
|
Command = Command + '/n "{SubjectName}" '.format (SubjectName = SubjectName)
|
|
Command = Command + TempFileName
|
|
if Verbose:
|
|
print (Command)
|
|
|
|
#
|
|
# Sign the input file using the specified private key
|
|
#
|
|
try:
|
|
Process = subprocess.Popen (Command, stdin = subprocess.PIPE, stdout = subprocess.PIPE, stderr = subprocess.PIPE, shell = True)
|
|
Result = Process.communicate('')
|
|
except:
|
|
shutil.rmtree (TempDirectoryName)
|
|
raise ValueError ('GenerateCapsule: error: can not run signtool.')
|
|
|
|
if Process.returncode != 0:
|
|
shutil.rmtree (TempDirectoryName)
|
|
print (Result[1].decode())
|
|
raise ValueError ('GenerateCapsule: error: signtool failed.')
|
|
|
|
#
|
|
# Read the signature from the generated output file
|
|
#
|
|
try:
|
|
with open (TempFileName + '.p7', 'rb') as File:
|
|
Signature = File.read ()
|
|
except:
|
|
shutil.rmtree (TempDirectoryName)
|
|
raise ValueError ('GenerateCapsule: error: can not read signature file.')
|
|
|
|
shutil.rmtree (TempDirectoryName)
|
|
return Signature
|
|
|
|
def VerifyPayloadSignTool (Payload, CertData, ToolPath, PfxFile, SubjectName, HashAlgorithm = DEFAULT_HASH_ALGORITHM, Verbose = False):
|
|
print ('signtool verify is not supported.')
|
|
raise ValueError ('GenerateCapsule: error: signtool verify is not supported.')
|
|
|
|
def SignPayloadOpenSsl (Payload, ToolPath, SignerPrivateCertFile, OtherPublicCertFile, TrustedPublicCertFile, HashAlgorithm = DEFAULT_HASH_ALGORITHM, Verbose = False):
|
|
#
|
|
# Check the hash algorithm is supported
|
|
#
|
|
CheckHashAlgorithmSupported (TOOL_OPENSSL, HashAlgorithm)
|
|
|
|
#
|
|
# Build openssl command
|
|
#
|
|
if ToolPath is None:
|
|
ToolPath = ''
|
|
Command = ''
|
|
Command = Command + '"{Path}" '.format (Path = os.path.join (ToolPath, 'openssl'))
|
|
Command = Command + 'smime -sign -binary -outform DER -md {HashAlgorithm} '.format (HashAlgorithm = HashAlgorithm)
|
|
Command = Command + '-signer "{Private}" -certfile "{Public}"'.format (Private = SignerPrivateCertFile, Public = OtherPublicCertFile)
|
|
if Verbose:
|
|
print (Command)
|
|
|
|
#
|
|
# Sign the input file using the specified private key and capture signature from STDOUT
|
|
#
|
|
try:
|
|
Process = subprocess.Popen (Command, stdin = subprocess.PIPE, stdout = subprocess.PIPE, stderr = subprocess.PIPE, shell = True)
|
|
Result = Process.communicate(input = Payload)
|
|
Signature = Result[0]
|
|
except:
|
|
raise ValueError ('GenerateCapsule: error: can not run openssl.')
|
|
|
|
if Process.returncode != 0:
|
|
print (Result[1].decode())
|
|
raise ValueError ('GenerateCapsule: error: openssl failed.')
|
|
|
|
return Signature
|
|
|
|
def VerifyPayloadOpenSsl (Payload, CertData, ToolPath, SignerPrivateCertFile, OtherPublicCertFile, TrustedPublicCertFile, HashAlgorithm = DEFAULT_HASH_ALGORITHM, Verbose = False):
|
|
#
|
|
# Create a temporary directory
|
|
#
|
|
TempDirectoryName = tempfile.mkdtemp()
|
|
|
|
#
|
|
# Generate temp file name for the payload contents
|
|
#
|
|
TempFileName = os.path.join (TempDirectoryName, 'Payload.bin')
|
|
|
|
#
|
|
# Create temporary payload file for verification
|
|
#
|
|
try:
|
|
with open (TempFileName, 'wb') as File:
|
|
File.write (Payload)
|
|
except:
|
|
shutil.rmtree (TempDirectoryName)
|
|
raise ValueError ('GenerateCapsule: error: can not write temporary payload file.')
|
|
|
|
#
|
|
# Build openssl command
|
|
#
|
|
if ToolPath is None:
|
|
ToolPath = ''
|
|
Command = ''
|
|
Command = Command + '"{Path}" '.format (Path = os.path.join (ToolPath, 'openssl'))
|
|
Command = Command + 'smime -verify -inform DER '
|
|
Command = Command + '-content {Content} -CAfile "{Public}"'.format (Content = TempFileName, Public = TrustedPublicCertFile)
|
|
if Verbose:
|
|
print (Command)
|
|
|
|
#
|
|
# Verify signature
|
|
#
|
|
try:
|
|
Process = subprocess.Popen (Command, stdin = subprocess.PIPE, stdout = subprocess.PIPE, stderr = subprocess.PIPE, shell = True)
|
|
Result = Process.communicate(input = CertData)
|
|
except:
|
|
shutil.rmtree (TempDirectoryName)
|
|
raise ValueError ('GenerateCapsule: error: can not run openssl.')
|
|
|
|
if Process.returncode != 0:
|
|
shutil.rmtree (TempDirectoryName)
|
|
print (Result[1].decode())
|
|
raise ValueError ('GenerateCapsule: error: openssl failed.')
|
|
|
|
shutil.rmtree (TempDirectoryName)
|
|
return Payload
|
|
|
|
if __name__ == '__main__':
|
|
def convert_arg_line_to_args(arg_line):
|
|
for arg in arg_line.split():
|
|
if not arg.strip():
|
|
continue
|
|
yield arg
|
|
|
|
def ValidateUnsignedInteger (Argument):
|
|
try:
|
|
Value = int (Argument, 0)
|
|
except:
|
|
Message = '{Argument} is not a valid integer value.'.format (Argument = Argument)
|
|
raise argparse.ArgumentTypeError (Message)
|
|
if Value < 0:
|
|
Message = '{Argument} is a negative value.'.format (Argument = Argument)
|
|
raise argparse.ArgumentTypeError (Message)
|
|
return Value
|
|
|
|
def ValidateRegistryFormatGuid (Argument):
|
|
try:
|
|
Value = uuid.UUID (Argument)
|
|
except:
|
|
Message = '{Argument} is not a valid registry format GUID value.'.format (Argument = Argument)
|
|
raise argparse.ArgumentTypeError (Message)
|
|
return Value
|
|
|
|
def ConvertJsonValue (Config, FieldName, Convert, Required = True, Default = None, Open = False):
|
|
if FieldName not in Config:
|
|
if Required:
|
|
print ('GenerateCapsule: error: Payload descriptor invalid syntax. Could not find {Key} in payload descriptor.'.format(Key = FieldName))
|
|
sys.exit (1)
|
|
return Default
|
|
try:
|
|
Value = Convert (Config[FieldName])
|
|
except:
|
|
print ('GenerateCapsule: error: {Key} in payload descriptor has invalid syntax.'.format (Key = FieldName))
|
|
sys.exit (1)
|
|
if Open:
|
|
try:
|
|
Value = open (Value, "rb")
|
|
except:
|
|
print ('GenerateCapsule: error: can not open file {File}'.format (File = FieldName))
|
|
sys.exit (1)
|
|
return Value
|
|
|
|
def DecodeJsonFileParse (Json):
|
|
if 'Payloads' not in Json:
|
|
print ('GenerateCapsule: error "Payloads" section not found in JSON file {File}'.format (File = args.JsonFile.name))
|
|
sys.exit (1)
|
|
for Config in Json['Payloads']:
|
|
#
|
|
# Parse fields from JSON
|
|
#
|
|
PayloadFile = ConvertJsonValue (Config, 'Payload', os.path.expandvars, Required = False)
|
|
Guid = ConvertJsonValue (Config, 'Guid', ValidateRegistryFormatGuid, Required = False)
|
|
FwVersion = ConvertJsonValue (Config, 'FwVersion', ValidateUnsignedInteger, Required = False)
|
|
LowestSupportedVersion = ConvertJsonValue (Config, 'LowestSupportedVersion', ValidateUnsignedInteger, Required = False)
|
|
HardwareInstance = ConvertJsonValue (Config, 'HardwareInstance', ValidateUnsignedInteger, Required = False, Default = 0)
|
|
MonotonicCount = ConvertJsonValue (Config, 'MonotonicCount', ValidateUnsignedInteger, Required = False, Default = 0)
|
|
HashAlgorithm = ConvertJsonValue (Config, 'HashAlgorithm', str, Required = False, Default = DEFAULT_HASH_ALGORITHM)
|
|
SignToolPfxFile = ConvertJsonValue (Config, 'SignToolPfxFile', os.path.expandvars, Required = False, Default = None, Open = True)
|
|
SignToolSubjectName = ConvertJsonValue (Config, 'SignToolSubjectName', str, Required = False, Default = None, Open = False)
|
|
OpenSslSignerPrivateCertFile = ConvertJsonValue (Config, 'OpenSslSignerPrivateCertFile', os.path.expandvars, Required = False, Default = None, Open = True)
|
|
OpenSslOtherPublicCertFile = ConvertJsonValue (Config, 'OpenSslOtherPublicCertFile', os.path.expandvars, Required = False, Default = None, Open = True)
|
|
OpenSslTrustedPublicCertFile = ConvertJsonValue (Config, 'OpenSslTrustedPublicCertFile', os.path.expandvars, Required = False, Default = None, Open = True)
|
|
SigningToolPath = ConvertJsonValue (Config, 'SigningToolPath', os.path.expandvars, Required = False, Default = None)
|
|
UpdateImageIndex = ConvertJsonValue (Config, 'UpdateImageIndex', ValidateUnsignedInteger, Required = False, Default = 1)
|
|
|
|
PayloadDescriptorList.append (PayloadDescriptor (
|
|
PayloadFile,
|
|
Guid,
|
|
FwVersion,
|
|
LowestSupportedVersion,
|
|
MonotonicCount,
|
|
HardwareInstance,
|
|
UpdateImageIndex,
|
|
HashAlgorithm,
|
|
SignToolPfxFile,
|
|
SignToolSubjectName,
|
|
OpenSslSignerPrivateCertFile,
|
|
OpenSslOtherPublicCertFile,
|
|
OpenSslTrustedPublicCertFile,
|
|
SigningToolPath
|
|
))
|
|
|
|
def EncodeJsonFileParse (Json):
|
|
if 'EmbeddedDrivers' not in Json:
|
|
print ('GenerateCapsule: warning "EmbeddedDrivers" section not found in JSON file {File}'.format (File = args.JsonFile.name))
|
|
else:
|
|
for Config in Json['EmbeddedDrivers']:
|
|
EmbeddedDriverFile = ConvertJsonValue(Config, 'Driver', os.path.expandvars, Open = True)
|
|
#
|
|
#Read EmbeddedDriver file
|
|
#
|
|
try:
|
|
if args.Verbose:
|
|
print ('Read EmbeddedDriver file {File}'.format (File = EmbeddedDriverFile.name))
|
|
Driver = EmbeddedDriverFile.read()
|
|
except:
|
|
print ('GenerateCapsule: error: can not read EmbeddedDriver file {File}'.format (File = EmbeddedDriverFile.name))
|
|
sys.exit (1)
|
|
EmbeddedDriverDescriptorList.append (Driver)
|
|
|
|
if 'Payloads' not in Json:
|
|
print ('GenerateCapsule: error: "Payloads" section not found in JSON file {File}'.format (File = args.JsonFile.name))
|
|
sys.exit (1)
|
|
for Config in Json['Payloads']:
|
|
#
|
|
# Parse fields from JSON
|
|
#
|
|
PayloadFile = ConvertJsonValue (Config, 'Payload', os.path.expandvars, Open = True)
|
|
Guid = ConvertJsonValue (Config, 'Guid', ValidateRegistryFormatGuid)
|
|
FwVersion = ConvertJsonValue (Config, 'FwVersion', ValidateUnsignedInteger)
|
|
LowestSupportedVersion = ConvertJsonValue (Config, 'LowestSupportedVersion', ValidateUnsignedInteger)
|
|
HardwareInstance = ConvertJsonValue (Config, 'HardwareInstance', ValidateUnsignedInteger, Required = False, Default = 0)
|
|
UpdateImageIndex = ConvertJsonValue (Config, 'UpdateImageIndex', ValidateUnsignedInteger, Required = False, Default = 1)
|
|
MonotonicCount = ConvertJsonValue (Config, 'MonotonicCount', ValidateUnsignedInteger, Required = False, Default = 0)
|
|
HashAlgorithm = ConvertJsonValue (Config, 'HashAlgorithm', str, Required = False, Default = DEFAULT_HASH_ALGORITHM)
|
|
SignToolPfxFile = ConvertJsonValue (Config, 'SignToolPfxFile', os.path.expandvars, Required = False, Default = None, Open = True)
|
|
SignToolSubjectName = ConvertJsonValue (Config, 'SignToolSubjectName', str, Required = False, Default = None, Open = False)
|
|
OpenSslSignerPrivateCertFile = ConvertJsonValue (Config, 'OpenSslSignerPrivateCertFile', os.path.expandvars, Required = False, Default = None, Open = True)
|
|
OpenSslOtherPublicCertFile = ConvertJsonValue (Config, 'OpenSslOtherPublicCertFile', os.path.expandvars, Required = False, Default = None, Open = True)
|
|
OpenSslTrustedPublicCertFile = ConvertJsonValue (Config, 'OpenSslTrustedPublicCertFile', os.path.expandvars, Required = False, Default = None, Open = True)
|
|
SigningToolPath = ConvertJsonValue (Config, 'SigningToolPath', os.path.expandvars, Required = False, Default = None)
|
|
DepexExp = ConvertJsonValue (Config, 'Dependencies', str, Required = False, Default = None)
|
|
|
|
#
|
|
# Read binary input file
|
|
#
|
|
try:
|
|
if args.Verbose:
|
|
print ('Read binary input file {File}'.format (File = PayloadFile.name))
|
|
Payload = PayloadFile.read()
|
|
PayloadFile.close ()
|
|
except:
|
|
print ('GenerateCapsule: error: can not read binary input file {File}'.format (File = PayloadFile.name))
|
|
sys.exit (1)
|
|
PayloadDescriptorList.append (PayloadDescriptor (
|
|
Payload,
|
|
Guid,
|
|
FwVersion,
|
|
LowestSupportedVersion,
|
|
MonotonicCount,
|
|
HardwareInstance,
|
|
UpdateImageIndex,
|
|
HashAlgorithm,
|
|
SignToolPfxFile,
|
|
SignToolSubjectName,
|
|
OpenSslSignerPrivateCertFile,
|
|
OpenSslOtherPublicCertFile,
|
|
OpenSslTrustedPublicCertFile,
|
|
SigningToolPath,
|
|
DepexExp
|
|
))
|
|
|
|
def GenerateOutputJson (PayloadJsonDescriptorList):
|
|
PayloadJson = {
|
|
"Payloads" : [
|
|
{
|
|
"Guid": str(PayloadDescriptor.Guid).upper(),
|
|
"FwVersion": str(PayloadDescriptor.FwVersion),
|
|
"LowestSupportedVersion": str(PayloadDescriptor.LowestSupportedVersion),
|
|
"MonotonicCount": str(PayloadDescriptor.MonotonicCount),
|
|
"Payload": PayloadDescriptor.Payload,
|
|
"HardwareInstance": str(PayloadDescriptor.HardwareInstance),
|
|
"UpdateImageIndex": str(PayloadDescriptor.UpdateImageIndex),
|
|
"HashAlgorithm": str(PayloadDescriptor.HashAlgorithm),
|
|
"SignToolPfxFile": str(PayloadDescriptor.SignToolPfxFile),
|
|
"SignToolSubjectName": str(PayloadDescriptor.SignToolSubjectName),
|
|
"OpenSslSignerPrivateCertFile": str(PayloadDescriptor.OpenSslSignerPrivateCertFile),
|
|
"OpenSslOtherPublicCertFile": str(PayloadDescriptor.OpenSslOtherPublicCertFile),
|
|
"OpenSslTrustedPublicCertFile": str(PayloadDescriptor.OpenSslTrustedPublicCertFile),
|
|
"SigningToolPath": str(PayloadDescriptor.SigningToolPath),
|
|
"Dependencies" : str(PayloadDescriptor.DepexExp)
|
|
}for PayloadDescriptor in PayloadJsonDescriptorList
|
|
]
|
|
}
|
|
OutputJsonFile = args.OutputFile.name + '.json'
|
|
if 'Payloads' in PayloadJson:
|
|
PayloadSection = PayloadJson ['Payloads']
|
|
Index = 0
|
|
for PayloadField in PayloadSection:
|
|
if PayloadJsonDescriptorList[Index].SignToolPfxFile is None:
|
|
del PayloadField ['SignToolPfxFile']
|
|
if PayloadJsonDescriptorList[Index].SignToolSubjectName is None:
|
|
del PayloadField ['SignToolSubjectName']
|
|
if PayloadJsonDescriptorList[Index].OpenSslSignerPrivateCertFile is None:
|
|
del PayloadField ['OpenSslSignerPrivateCertFile']
|
|
if PayloadJsonDescriptorList[Index].OpenSslOtherPublicCertFile is None:
|
|
del PayloadField ['OpenSslOtherPublicCertFile']
|
|
if PayloadJsonDescriptorList[Index].OpenSslTrustedPublicCertFile is None:
|
|
del PayloadField ['OpenSslTrustedPublicCertFile']
|
|
if PayloadJsonDescriptorList[Index].SigningToolPath is None:
|
|
del PayloadField ['SigningToolPath']
|
|
Index = Index + 1
|
|
Result = json.dumps (PayloadJson, indent=4, sort_keys=True, separators=(',', ': '))
|
|
with open (OutputJsonFile, 'w') as OutputFile:
|
|
OutputFile.write (Result)
|
|
|
|
def CheckArgumentConflict (args):
|
|
if args.Encode:
|
|
if args.InputFile:
|
|
print ('GenerateCapsule: error: Argument InputFile conflicts with Argument -j')
|
|
sys.exit (1)
|
|
if args.EmbeddedDriver:
|
|
print ('GenerateCapsule: error: Argument --embedded-driver conflicts with Argument -j')
|
|
sys.exit (1)
|
|
if args.Guid:
|
|
print ('GenerateCapsule: error: Argument --guid conflicts with Argument -j')
|
|
sys.exit (1)
|
|
if args.FwVersion:
|
|
print ('GenerateCapsule: error: Argument --fw-version conflicts with Argument -j')
|
|
sys.exit (1)
|
|
if args.LowestSupportedVersion:
|
|
print ('GenerateCapsule: error: Argument --lsv conflicts with Argument -j')
|
|
sys.exit (1)
|
|
if args.MonotonicCount:
|
|
print ('GenerateCapsule: error: Argument --monotonic-count conflicts with Argument -j')
|
|
sys.exit (1)
|
|
if args.HardwareInstance:
|
|
print ('GenerateCapsule: error: Argument --hardware-instance conflicts with Argument -j')
|
|
sys.exit (1)
|
|
if args.HashAlgorithm:
|
|
print ('GenerateCapsule: error: Argument --hash-algorithm conflicts with Argument -j')
|
|
sys.exit (1)
|
|
if args.SignToolPfxFile:
|
|
print ('GenerateCapsule: error: Argument --pfx-file conflicts with Argument -j')
|
|
sys.exit (1)
|
|
if args.SignToolSubjectName:
|
|
print ('GenerateCapsule: error: Argument --subject-name conflicts with Argument -j')
|
|
sys.exit (1)
|
|
if args.OpenSslSignerPrivateCertFile:
|
|
print ('GenerateCapsule: error: Argument --signer-private-cert conflicts with Argument -j')
|
|
sys.exit (1)
|
|
if args.OpenSslOtherPublicCertFile:
|
|
print ('GenerateCapsule: error: Argument --other-public-cert conflicts with Argument -j')
|
|
sys.exit (1)
|
|
if args.OpenSslTrustedPublicCertFile:
|
|
print ('GenerateCapsule: error: Argument --trusted-public-cert conflicts with Argument -j')
|
|
sys.exit (1)
|
|
if args.SigningToolPath:
|
|
print ('GenerateCapsule: error: Argument --signing-tool-path conflicts with Argument -j')
|
|
sys.exit (1)
|
|
|
|
class PayloadDescriptor (object):
|
|
def __init__(self,
|
|
Payload,
|
|
Guid,
|
|
FwVersion,
|
|
LowestSupportedVersion,
|
|
MonotonicCount = 0,
|
|
HardwareInstance = 0,
|
|
UpdateImageIndex = 1,
|
|
HashAlgorithm = None,
|
|
SignToolPfxFile = None,
|
|
SignToolSubjectName = None,
|
|
OpenSslSignerPrivateCertFile = None,
|
|
OpenSslOtherPublicCertFile = None,
|
|
OpenSslTrustedPublicCertFile = None,
|
|
SigningToolPath = None,
|
|
DepexExp = None
|
|
):
|
|
self.Payload = Payload
|
|
self.Guid = Guid
|
|
self.FwVersion = FwVersion
|
|
self.LowestSupportedVersion = LowestSupportedVersion
|
|
self.MonotonicCount = MonotonicCount
|
|
self.HardwareInstance = HardwareInstance
|
|
self.UpdateImageIndex = UpdateImageIndex
|
|
self.HashAlgorithm = HashAlgorithm
|
|
self.SignToolPfxFile = SignToolPfxFile
|
|
self.SignToolSubjectName = SignToolSubjectName
|
|
self.OpenSslSignerPrivateCertFile = OpenSslSignerPrivateCertFile
|
|
self.OpenSslOtherPublicCertFile = OpenSslOtherPublicCertFile
|
|
self.OpenSslTrustedPublicCertFile = OpenSslTrustedPublicCertFile
|
|
self.SigningToolPath = SigningToolPath
|
|
self.DepexExp = DepexExp
|
|
|
|
self.UseSignTool = (self.SignToolPfxFile is not None or
|
|
self.SignToolSubjectName is not None)
|
|
self.UseOpenSsl = (self.OpenSslSignerPrivateCertFile is not None and
|
|
self.OpenSslOtherPublicCertFile is not None and
|
|
self.OpenSslTrustedPublicCertFile is not None)
|
|
self.AnyOpenSsl = (self.OpenSslSignerPrivateCertFile is not None or
|
|
self.OpenSslOtherPublicCertFile is not None or
|
|
self.OpenSslTrustedPublicCertFile is not None)
|
|
self.UseDependency = self.DepexExp is not None
|
|
|
|
def Validate(self, args):
|
|
if self.UseSignTool and self.AnyOpenSsl:
|
|
raise argparse.ArgumentTypeError ('Providing both signtool and OpenSSL options is not supported')
|
|
if not self.UseSignTool and not self.UseOpenSsl and self.AnyOpenSsl:
|
|
if args.JsonFile:
|
|
raise argparse.ArgumentTypeError ('the following JSON fields are required for OpenSSL: OpenSslSignerPrivateCertFile, OpenSslOtherPublicCertFile, OpenSslTrustedPublicCertFile')
|
|
else:
|
|
raise argparse.ArgumentTypeError ('the following options are required for OpenSSL: --signer-private-cert, --other-public-cert, --trusted-public-cert')
|
|
if self.UseSignTool and platform.system() != 'Windows':
|
|
raise argparse.ArgumentTypeError ('Use of signtool is not supported on this operating system.')
|
|
if args.Encode:
|
|
if self.FwVersion is None or self.LowestSupportedVersion is None:
|
|
if args.JsonFile:
|
|
raise argparse.ArgumentTypeError ('the following JSON fields are required: FwVersion, LowestSupportedVersion')
|
|
else:
|
|
raise argparse.ArgumentTypeError ('the following options are required: --fw-version, --lsv')
|
|
if self.FwVersion > 0xFFFFFFFF:
|
|
if args.JsonFile:
|
|
raise argparse.ArgumentTypeError ('JSON field FwVersion must be an integer in range 0x0..0xffffffff')
|
|
else:
|
|
raise argparse.ArgumentTypeError ('--fw-version must be an integer in range 0x0..0xffffffff')
|
|
if self.LowestSupportedVersion > 0xFFFFFFFF:
|
|
if args.JsonFile:
|
|
raise argparse.ArgumentTypeError ('JSON field LowestSupportedVersion must be an integer in range 0x0..0xffffffff')
|
|
else:
|
|
raise argparse.ArgumentTypeError ('--lsv must be an integer in range 0x0..0xffffffff')
|
|
|
|
if args.Encode:
|
|
if self.Guid is None:
|
|
if args.JsonFile:
|
|
raise argparse.ArgumentTypeError ('the following JSON field is required: Guid')
|
|
else:
|
|
raise argparse.ArgumentTypeError ('the following option is required: --guid')
|
|
if self.HardwareInstance > 0xFFFFFFFFFFFFFFFF:
|
|
if args.JsonFile:
|
|
raise argparse.ArgumentTypeError ('JSON field HardwareInstance must be an integer in range 0x0..0xffffffffffffffff')
|
|
else:
|
|
raise argparse.ArgumentTypeError ('--hardware-instance must be an integer in range 0x0..0xffffffffffffffff')
|
|
if self.MonotonicCount > 0xFFFFFFFFFFFFFFFF:
|
|
if args.JsonFile:
|
|
raise argparse.ArgumentTypeError ('JSON field MonotonicCount must be an integer in range 0x0..0xffffffffffffffff')
|
|
else:
|
|
raise argparse.ArgumentTypeError ('--monotonic-count must be an integer in range 0x0..0xffffffffffffffff')
|
|
if self.UpdateImageIndex < 0x1 or self.UpdateImageIndex > 0xFF:
|
|
if args.JsonFile:
|
|
raise argparse.ArgumentTypeError ('JSON field UpdateImageIndex must be an integer in range 0x1..0xff')
|
|
else:
|
|
raise argparse.ArgumentTypeError ('--update-image-index must be an integer in range 0x1..0xff')
|
|
|
|
if args.Decode:
|
|
if args.OutputFile is None:
|
|
raise argparse.ArgumentTypeError ('--decode requires --output')
|
|
|
|
if self.HashAlgorithm is None:
|
|
self.HashAlgorithm = DEFAULT_HASH_ALGORITHM
|
|
|
|
if self.UseSignTool:
|
|
if self.SignToolPfxFile is not None:
|
|
self.SignToolPfxFile.close()
|
|
self.SignToolPfxFile = self.SignToolPfxFile.name
|
|
if self.UseOpenSsl:
|
|
self.OpenSslSignerPrivateCertFile.close()
|
|
self.OpenSslOtherPublicCertFile.close()
|
|
self.OpenSslTrustedPublicCertFile.close()
|
|
self.OpenSslSignerPrivateCertFile = self.OpenSslSignerPrivateCertFile.name
|
|
self.OpenSslOtherPublicCertFile = self.OpenSslOtherPublicCertFile.name
|
|
self.OpenSslTrustedPublicCertFile = self.OpenSslTrustedPublicCertFile.name
|
|
|
|
#
|
|
# Perform additional argument verification
|
|
#
|
|
if args.Encode:
|
|
if 'PersistAcrossReset' not in args.CapsuleFlag:
|
|
if 'InitiateReset' in args.CapsuleFlag:
|
|
raise argparse.ArgumentTypeError ('--capflag InitiateReset also requires --capflag PersistAcrossReset')
|
|
if args.CapsuleOemFlag > 0xFFFF:
|
|
raise argparse.ArgumentTypeError ('--capoemflag must be an integer between 0x0000 and 0xffff')
|
|
|
|
return True
|
|
|
|
|
|
def Encode (PayloadDescriptorList, EmbeddedDriverDescriptorList, Buffer):
|
|
if args.JsonFile:
|
|
CheckArgumentConflict(args)
|
|
try:
|
|
Json = json.loads (args.JsonFile.read ())
|
|
except:
|
|
print ('GenerateCapsule: error: {JSONFile} loads failure. '.format (JSONFile = args.JsonFile))
|
|
sys.exit (1)
|
|
EncodeJsonFileParse(Json)
|
|
else:
|
|
for Driver in args.EmbeddedDriver:
|
|
EmbeddedDriverDescriptorList.append (Driver.read())
|
|
PayloadDescriptorList.append (PayloadDescriptor (
|
|
Buffer,
|
|
args.Guid,
|
|
args.FwVersion,
|
|
args.LowestSupportedVersion,
|
|
args.MonotonicCount,
|
|
args.HardwareInstance,
|
|
args.UpdateImageIndex,
|
|
args.HashAlgorithm,
|
|
args.SignToolPfxFile,
|
|
args.SignToolSubjectName,
|
|
args.OpenSslSignerPrivateCertFile,
|
|
args.OpenSslOtherPublicCertFile,
|
|
args.OpenSslTrustedPublicCertFile,
|
|
args.SigningToolPath,
|
|
None
|
|
))
|
|
for SinglePayloadDescriptor in PayloadDescriptorList:
|
|
try:
|
|
SinglePayloadDescriptor.Validate (args)
|
|
except Exception as Msg:
|
|
print ('GenerateCapsule: error: ' + str(Msg))
|
|
sys.exit (1)
|
|
for SinglePayloadDescriptor in PayloadDescriptorList:
|
|
ImageCapsuleSupport = 0x0000000000000000
|
|
Result = SinglePayloadDescriptor.Payload
|
|
try:
|
|
FmpPayloadHeader.FwVersion = SinglePayloadDescriptor.FwVersion
|
|
FmpPayloadHeader.LowestSupportedVersion = SinglePayloadDescriptor.LowestSupportedVersion
|
|
FmpPayloadHeader.Payload = SinglePayloadDescriptor.Payload
|
|
Result = FmpPayloadHeader.Encode ()
|
|
if args.Verbose:
|
|
FmpPayloadHeader.DumpInfo ()
|
|
except:
|
|
print ('GenerateCapsule: error: can not encode FMP Payload Header')
|
|
sys.exit (1)
|
|
if SinglePayloadDescriptor.UseDependency:
|
|
CapsuleDependency.Payload = Result
|
|
CapsuleDependency.DepexExp = SinglePayloadDescriptor.DepexExp
|
|
ImageCapsuleSupport |= FmpCapsuleHeader.CAPSULE_SUPPORT_DEPENDENCY
|
|
Result = CapsuleDependency.Encode ()
|
|
if args.Verbose:
|
|
CapsuleDependency.DumpInfo ()
|
|
if SinglePayloadDescriptor.UseOpenSsl or SinglePayloadDescriptor.UseSignTool:
|
|
#
|
|
# Sign image with 64-bit MonotonicCount appended to end of image
|
|
#
|
|
try:
|
|
if SinglePayloadDescriptor.UseSignTool:
|
|
CertData = SignPayloadSignTool (
|
|
Result + struct.pack ('<Q', SinglePayloadDescriptor.MonotonicCount),
|
|
SinglePayloadDescriptor.SigningToolPath,
|
|
SinglePayloadDescriptor.SignToolPfxFile,
|
|
SinglePayloadDescriptor.SignToolSubjectName,
|
|
HashAlgorithm = SinglePayloadDescriptor.HashAlgorithm,
|
|
Verbose = args.Verbose
|
|
)
|
|
else:
|
|
CertData = SignPayloadOpenSsl (
|
|
Result + struct.pack ('<Q', SinglePayloadDescriptor.MonotonicCount),
|
|
SinglePayloadDescriptor.SigningToolPath,
|
|
SinglePayloadDescriptor.OpenSslSignerPrivateCertFile,
|
|
SinglePayloadDescriptor.OpenSslOtherPublicCertFile,
|
|
SinglePayloadDescriptor.OpenSslTrustedPublicCertFile,
|
|
HashAlgorithm = SinglePayloadDescriptor.HashAlgorithm,
|
|
Verbose = args.Verbose
|
|
)
|
|
except Exception as Msg:
|
|
print ('GenerateCapsule: error: can not sign payload \n' + str(Msg))
|
|
sys.exit (1)
|
|
|
|
try:
|
|
FmpAuthHeader.MonotonicCount = SinglePayloadDescriptor.MonotonicCount
|
|
FmpAuthHeader.CertData = CertData
|
|
FmpAuthHeader.Payload = Result
|
|
ImageCapsuleSupport |= FmpCapsuleHeader.CAPSULE_SUPPORT_AUTHENTICATION
|
|
Result = FmpAuthHeader.Encode ()
|
|
if args.Verbose:
|
|
FmpAuthHeader.DumpInfo ()
|
|
except:
|
|
print ('GenerateCapsule: error: can not encode FMP Auth Header')
|
|
sys.exit (1)
|
|
FmpCapsuleHeader.AddPayload (SinglePayloadDescriptor.Guid, Result, HardwareInstance = SinglePayloadDescriptor.HardwareInstance, UpdateImageIndex = SinglePayloadDescriptor.UpdateImageIndex, CapsuleSupport = ImageCapsuleSupport)
|
|
try:
|
|
for EmbeddedDriver in EmbeddedDriverDescriptorList:
|
|
FmpCapsuleHeader.AddEmbeddedDriver(EmbeddedDriver)
|
|
|
|
Result = FmpCapsuleHeader.Encode ()
|
|
if args.Verbose:
|
|
FmpCapsuleHeader.DumpInfo ()
|
|
except:
|
|
print ('GenerateCapsule: error: can not encode FMP Capsule Header')
|
|
sys.exit (1)
|
|
|
|
try:
|
|
UefiCapsuleHeader.OemFlags = args.CapsuleOemFlag
|
|
UefiCapsuleHeader.PersistAcrossReset = 'PersistAcrossReset' in args.CapsuleFlag
|
|
UefiCapsuleHeader.PopulateSystemTable = False
|
|
UefiCapsuleHeader.InitiateReset = 'InitiateReset' in args.CapsuleFlag
|
|
UefiCapsuleHeader.Payload = Result
|
|
Result = UefiCapsuleHeader.Encode ()
|
|
if args.Verbose:
|
|
UefiCapsuleHeader.DumpInfo ()
|
|
except:
|
|
print ('GenerateCapsule: error: can not encode UEFI Capsule Header')
|
|
sys.exit (1)
|
|
try:
|
|
if args.Verbose:
|
|
print ('Write binary output file {File}'.format (File = args.OutputFile.name))
|
|
args.OutputFile.write (Result)
|
|
args.OutputFile.close ()
|
|
except:
|
|
print ('GenerateCapsule: error: can not write binary output file {File}'.format (File = args.OutputFile.name))
|
|
sys.exit (1)
|
|
|
|
def Decode (PayloadDescriptorList, PayloadJsonDescriptorList, Buffer):
|
|
if args.JsonFile:
|
|
CheckArgumentConflict(args)
|
|
#
|
|
# Parse payload descriptors from JSON
|
|
#
|
|
try:
|
|
Json = json.loads (args.JsonFile.read())
|
|
except:
|
|
print ('GenerateCapsule: error: {JSONFile} loads failure. '.format (JSONFile = args.JsonFile))
|
|
sys.exit (1)
|
|
DecodeJsonFileParse (Json)
|
|
else:
|
|
PayloadDescriptorList.append (PayloadDescriptor (
|
|
Buffer,
|
|
args.Guid,
|
|
args.FwVersion,
|
|
args.LowestSupportedVersion,
|
|
args.MonotonicCount,
|
|
args.HardwareInstance,
|
|
args.UpdateImageIndex,
|
|
args.HashAlgorithm,
|
|
args.SignToolPfxFile,
|
|
args.SignToolSubjectName,
|
|
args.OpenSslSignerPrivateCertFile,
|
|
args.OpenSslOtherPublicCertFile,
|
|
args.OpenSslTrustedPublicCertFile,
|
|
args.SigningToolPath,
|
|
None
|
|
))
|
|
#
|
|
# Perform additional verification on payload descriptors
|
|
#
|
|
for SinglePayloadDescriptor in PayloadDescriptorList:
|
|
try:
|
|
SinglePayloadDescriptor.Validate (args)
|
|
except Exception as Msg:
|
|
print ('GenerateCapsule: error: ' + str(Msg))
|
|
sys.exit (1)
|
|
try:
|
|
Result = UefiCapsuleHeader.Decode (Buffer)
|
|
if len (Result) > 0:
|
|
Result = FmpCapsuleHeader.Decode (Result)
|
|
if args.JsonFile:
|
|
if FmpCapsuleHeader.PayloadItemCount != len (PayloadDescriptorList):
|
|
CapsulePayloadNum = FmpCapsuleHeader.PayloadItemCount
|
|
JsonPayloadNum = len (PayloadDescriptorList)
|
|
print ('GenerateCapsule: Decode error: {JsonPayloadNumber} payloads in JSON file {File} and {CapsulePayloadNumber} payloads in Capsule {CapsuleName}'.format (JsonPayloadNumber = JsonPayloadNum, File = args.JsonFile.name, CapsulePayloadNumber = CapsulePayloadNum, CapsuleName = args.InputFile.name))
|
|
sys.exit (1)
|
|
for Index in range (0, FmpCapsuleHeader.PayloadItemCount):
|
|
if Index < len (PayloadDescriptorList):
|
|
GUID = FmpCapsuleHeader.GetFmpCapsuleImageHeader (Index).UpdateImageTypeId
|
|
HardwareInstance = FmpCapsuleHeader.GetFmpCapsuleImageHeader (Index).UpdateHardwareInstance
|
|
UpdateImageIndex = FmpCapsuleHeader.GetFmpCapsuleImageHeader (Index).UpdateImageIndex
|
|
if PayloadDescriptorList[Index].Guid != GUID or PayloadDescriptorList[Index].HardwareInstance != HardwareInstance:
|
|
print ('GenerateCapsule: Decode error: Guid or HardwareInstance pair in input JSON file {File} does not match the payload {PayloadIndex} in Capsule {InputCapsule}'.format (File = args.JsonFile.name, PayloadIndex = Index + 1, InputCapsule = args.InputFile.name))
|
|
sys.exit (1)
|
|
PayloadDescriptorList[Index].Payload = FmpCapsuleHeader.GetFmpCapsuleImageHeader (Index).Payload
|
|
DecodeJsonOutput = args.OutputFile.name + '.Payload.{Index:d}.bin'.format (Index = Index + 1)
|
|
PayloadJsonDescriptorList.append (PayloadDescriptor (
|
|
DecodeJsonOutput,
|
|
GUID,
|
|
None,
|
|
None,
|
|
None,
|
|
HardwareInstance,
|
|
UpdateImageIndex,
|
|
PayloadDescriptorList[Index].HashAlgorithm,
|
|
PayloadDescriptorList[Index].SignToolPfxFile,
|
|
PayloadDescriptorList[Index].SignToolSubjectName,
|
|
PayloadDescriptorList[Index].OpenSslSignerPrivateCertFile,
|
|
PayloadDescriptorList[Index].OpenSslOtherPublicCertFile,
|
|
PayloadDescriptorList[Index].OpenSslTrustedPublicCertFile,
|
|
PayloadDescriptorList[Index].SigningToolPath,
|
|
None
|
|
))
|
|
else:
|
|
PayloadDescriptorList[0].Payload = FmpCapsuleHeader.GetFmpCapsuleImageHeader (0).Payload
|
|
for Index in range (0, FmpCapsuleHeader.PayloadItemCount):
|
|
if Index > 0:
|
|
PayloadDecodeFile = FmpCapsuleHeader.GetFmpCapsuleImageHeader (Index).Payload
|
|
PayloadDescriptorList.append (PayloadDescriptor (
|
|
PayloadDecodeFile,
|
|
None,
|
|
None,
|
|
None,
|
|
None,
|
|
None,
|
|
None,
|
|
None,
|
|
None,
|
|
None,
|
|
None,
|
|
None,
|
|
None,
|
|
None,
|
|
None
|
|
))
|
|
GUID = FmpCapsuleHeader.GetFmpCapsuleImageHeader (Index).UpdateImageTypeId
|
|
HardwareInstance = FmpCapsuleHeader.GetFmpCapsuleImageHeader (Index).UpdateHardwareInstance
|
|
UpdateImageIndex = FmpCapsuleHeader.GetFmpCapsuleImageHeader (Index).UpdateImageIndex
|
|
DecodeJsonOutput = args.OutputFile.name + '.Payload.{Index:d}.bin'.format (Index = Index + 1)
|
|
PayloadJsonDescriptorList.append (PayloadDescriptor (
|
|
DecodeJsonOutput,
|
|
GUID,
|
|
None,
|
|
None,
|
|
None,
|
|
HardwareInstance,
|
|
UpdateImageIndex,
|
|
PayloadDescriptorList[Index].HashAlgorithm,
|
|
PayloadDescriptorList[Index].SignToolPfxFile,
|
|
PayloadDescriptorList[Index].SignToolSubjectName,
|
|
PayloadDescriptorList[Index].OpenSslSignerPrivateCertFile,
|
|
PayloadDescriptorList[Index].OpenSslOtherPublicCertFile,
|
|
PayloadDescriptorList[Index].OpenSslTrustedPublicCertFile,
|
|
PayloadDescriptorList[Index].SigningToolPath,
|
|
None
|
|
))
|
|
JsonIndex = 0
|
|
for SinglePayloadDescriptor in PayloadDescriptorList:
|
|
if args.Verbose:
|
|
print ('========')
|
|
UefiCapsuleHeader.DumpInfo ()
|
|
print ('--------')
|
|
FmpCapsuleHeader.DumpInfo ()
|
|
if FmpAuthHeader.IsSigned(SinglePayloadDescriptor.Payload):
|
|
if not SinglePayloadDescriptor.UseOpenSsl and not SinglePayloadDescriptor.UseSignTool:
|
|
print ('GenerateCapsule: decode warning: can not verify singed payload without cert or pfx file. Index = {Index}'.format (Index = JsonIndex + 1))
|
|
SinglePayloadDescriptor.Payload = FmpAuthHeader.Decode (SinglePayloadDescriptor.Payload)
|
|
PayloadJsonDescriptorList[JsonIndex].MonotonicCount = FmpAuthHeader.MonotonicCount
|
|
if args.Verbose:
|
|
print ('--------')
|
|
FmpAuthHeader.DumpInfo ()
|
|
|
|
#
|
|
# Verify Image with 64-bit MonotonicCount appended to end of image
|
|
#
|
|
try:
|
|
if SinglePayloadDescriptor.UseSignTool:
|
|
CertData = VerifyPayloadSignTool (
|
|
FmpAuthHeader.Payload + struct.pack ('<Q', FmpAuthHeader.MonotonicCount),
|
|
FmpAuthHeader.CertData,
|
|
SinglePayloadDescriptor.SigningToolPath,
|
|
SinglePayloadDescriptor.SignToolPfxFile,
|
|
SinglePayloadDescriptor.SignToolSubjectName,
|
|
HashAlgorithm = SinglePayloadDescriptor.HashAlgorithm,
|
|
Verbose = args.Verbose
|
|
)
|
|
else:
|
|
CertData = VerifyPayloadOpenSsl (
|
|
FmpAuthHeader.Payload + struct.pack ('<Q', FmpAuthHeader.MonotonicCount),
|
|
FmpAuthHeader.CertData,
|
|
SinglePayloadDescriptor.SigningToolPath,
|
|
SinglePayloadDescriptor.OpenSslSignerPrivateCertFile,
|
|
SinglePayloadDescriptor.OpenSslOtherPublicCertFile,
|
|
SinglePayloadDescriptor.OpenSslTrustedPublicCertFile,
|
|
HashAlgorithm = SinglePayloadDescriptor.HashAlgorithm,
|
|
Verbose = args.Verbose
|
|
)
|
|
except Exception as Msg:
|
|
print ('GenerateCapsule: warning: payload verification failed Index = {Index} \n'.format (Index = JsonIndex + 1) + str(Msg))
|
|
else:
|
|
if args.Verbose:
|
|
print ('--------')
|
|
print ('No EFI_FIRMWARE_IMAGE_AUTHENTICATION')
|
|
|
|
(PayloadSignature,) = struct.unpack ('<I', SinglePayloadDescriptor.Payload[0:4])
|
|
if PayloadSignature != FmpPayloadHeader.Signature:
|
|
SinglePayloadDescriptor.UseDependency = True
|
|
try:
|
|
SinglePayloadDescriptor.Payload = CapsuleDependency.Decode (SinglePayloadDescriptor.Payload)
|
|
PayloadJsonDescriptorList[JsonIndex].DepexExp = CapsuleDependency.DepexExp
|
|
if args.Verbose:
|
|
print ('--------')
|
|
CapsuleDependency.DumpInfo ()
|
|
except Exception as Msg:
|
|
print ('GenerateCapsule: error: invalid dependency expression')
|
|
else:
|
|
if args.Verbose:
|
|
print ('--------')
|
|
print ('No EFI_FIRMWARE_IMAGE_DEP')
|
|
|
|
try:
|
|
SinglePayloadDescriptor.Payload = FmpPayloadHeader.Decode (SinglePayloadDescriptor.Payload)
|
|
PayloadJsonDescriptorList[JsonIndex].FwVersion = FmpPayloadHeader.FwVersion
|
|
PayloadJsonDescriptorList[JsonIndex].LowestSupportedVersion = FmpPayloadHeader.LowestSupportedVersion
|
|
JsonIndex = JsonIndex + 1
|
|
if args.Verbose:
|
|
print ('--------')
|
|
FmpPayloadHeader.DumpInfo ()
|
|
print ('========')
|
|
except:
|
|
if args.Verbose:
|
|
print ('--------')
|
|
print ('No FMP_PAYLOAD_HEADER')
|
|
print ('========')
|
|
sys.exit (1)
|
|
#
|
|
# Write embedded driver file(s)
|
|
#
|
|
for Index in range (0, FmpCapsuleHeader.EmbeddedDriverCount):
|
|
EmbeddedDriverBuffer = FmpCapsuleHeader.GetEmbeddedDriver (Index)
|
|
EmbeddedDriverPath = args.OutputFile.name + '.EmbeddedDriver.{Index:d}.efi'.format (Index = Index + 1)
|
|
try:
|
|
if args.Verbose:
|
|
print ('Write embedded driver file {File}'.format (File = EmbeddedDriverPath))
|
|
with open (EmbeddedDriverPath, 'wb') as EmbeddedDriverFile:
|
|
EmbeddedDriverFile.write (EmbeddedDriverBuffer)
|
|
except:
|
|
print ('GenerateCapsule: error: can not write embedded driver file {File}'.format (File = EmbeddedDriverPath))
|
|
sys.exit (1)
|
|
|
|
except Exception as Msg:
|
|
print ('GenerateCapsule: error: can not decode capsule: ' + str(Msg))
|
|
sys.exit (1)
|
|
GenerateOutputJson(PayloadJsonDescriptorList)
|
|
PayloadIndex = 0
|
|
for SinglePayloadDescriptor in PayloadDescriptorList:
|
|
if args.OutputFile is None:
|
|
print ('GenerateCapsule: Decode error: OutputFile is needed for decode output')
|
|
sys.exit (1)
|
|
try:
|
|
if args.Verbose:
|
|
print ('Write binary output file {File}'.format (File = args.OutputFile.name))
|
|
PayloadDecodePath = args.OutputFile.name + '.Payload.{Index:d}.bin'.format (Index = PayloadIndex + 1)
|
|
with open (PayloadDecodePath, 'wb') as PayloadDecodeFile:
|
|
PayloadDecodeFile.write (SinglePayloadDescriptor.Payload)
|
|
PayloadIndex = PayloadIndex + 1
|
|
except:
|
|
print ('GenerateCapsule: error: can not write binary output file {File}'.format (File = SinglePayloadDescriptor.OutputFile.name))
|
|
sys.exit (1)
|
|
|
|
def DumpInfo (Buffer, args):
|
|
if args.OutputFile is not None:
|
|
raise argparse.ArgumentTypeError ('the following option is not supported for dumpinfo operations: --output')
|
|
try:
|
|
Result = UefiCapsuleHeader.Decode (Buffer)
|
|
print ('========')
|
|
UefiCapsuleHeader.DumpInfo ()
|
|
if len (Result) > 0:
|
|
FmpCapsuleHeader.Decode (Result)
|
|
print ('--------')
|
|
FmpCapsuleHeader.DumpInfo ()
|
|
for Index in range (0, FmpCapsuleHeader.PayloadItemCount):
|
|
Result = FmpCapsuleHeader.GetFmpCapsuleImageHeader (Index).Payload
|
|
try:
|
|
Result = FmpAuthHeader.Decode (Result)
|
|
print ('--------')
|
|
FmpAuthHeader.DumpInfo ()
|
|
except:
|
|
print ('--------')
|
|
print ('No EFI_FIRMWARE_IMAGE_AUTHENTICATION')
|
|
|
|
(PayloadSignature,) = struct.unpack ('<I', Result[0:4])
|
|
if PayloadSignature != FmpPayloadHeader.Signature:
|
|
try:
|
|
Result = CapsuleDependency.Decode (Result)
|
|
print ('--------')
|
|
CapsuleDependency.DumpInfo ()
|
|
except:
|
|
print ('GenerateCapsule: error: invalid dependency expression')
|
|
else:
|
|
print ('--------')
|
|
print ('No EFI_FIRMWARE_IMAGE_DEP')
|
|
try:
|
|
Result = FmpPayloadHeader.Decode (Result)
|
|
print ('--------')
|
|
FmpPayloadHeader.DumpInfo ()
|
|
except:
|
|
print ('--------')
|
|
print ('No FMP_PAYLOAD_HEADER')
|
|
print ('========')
|
|
except:
|
|
print ('GenerateCapsule: error: can not decode capsule')
|
|
sys.exit (1)
|
|
#
|
|
# Create command line argument parser object
|
|
#
|
|
parser = argparse.ArgumentParser (
|
|
prog = __prog__,
|
|
description = __description__ + __copyright__,
|
|
conflict_handler = 'resolve',
|
|
fromfile_prefix_chars = '@'
|
|
)
|
|
parser.convert_arg_line_to_args = convert_arg_line_to_args
|
|
|
|
#
|
|
# Add input and output file arguments
|
|
#
|
|
parser.add_argument("InputFile", type = argparse.FileType('rb'), nargs='?',
|
|
help = "Input binary payload filename.")
|
|
parser.add_argument("-o", "--output", dest = 'OutputFile', type = argparse.FileType('wb'),
|
|
help = "Output filename.")
|
|
#
|
|
# Add group for -e and -d flags that are mutually exclusive and required
|
|
#
|
|
group = parser.add_mutually_exclusive_group (required = True)
|
|
group.add_argument ("-e", "--encode", dest = 'Encode', action = "store_true",
|
|
help = "Encode file")
|
|
group.add_argument ("-d", "--decode", dest = 'Decode', action = "store_true",
|
|
help = "Decode file")
|
|
group.add_argument ("--dump-info", dest = 'DumpInfo', action = "store_true",
|
|
help = "Display FMP Payload Header information")
|
|
#
|
|
# Add optional arguments for this command
|
|
#
|
|
parser.add_argument ("-j", "--json-file", dest = 'JsonFile', type=argparse.FileType('r'),
|
|
help = "JSON configuration file for multiple payloads and embedded drivers.")
|
|
parser.add_argument ("--capflag", dest = 'CapsuleFlag', action='append', default = [],
|
|
choices=['PersistAcrossReset', 'InitiateReset'],
|
|
help = "Capsule flag can be PersistAcrossReset or InitiateReset or not set")
|
|
parser.add_argument ("--capoemflag", dest = 'CapsuleOemFlag', type = ValidateUnsignedInteger, default = 0x0000,
|
|
help = "Capsule OEM Flag is an integer between 0x0000 and 0xffff.")
|
|
|
|
parser.add_argument ("--guid", dest = 'Guid', type = ValidateRegistryFormatGuid,
|
|
help = "The FMP/ESRT GUID in registry format. Required for single payload encode operations.")
|
|
parser.add_argument ("--hardware-instance", dest = 'HardwareInstance', type = ValidateUnsignedInteger, default = 0x0000000000000000,
|
|
help = "The 64-bit hardware instance. The default is 0x0000000000000000")
|
|
|
|
|
|
parser.add_argument ("--monotonic-count", dest = 'MonotonicCount', type = ValidateUnsignedInteger, default = 0x0000000000000000,
|
|
help = "64-bit monotonic count value in header. Default is 0x0000000000000000.")
|
|
|
|
parser.add_argument ("--fw-version", dest = 'FwVersion', type = ValidateUnsignedInteger,
|
|
help = "The 32-bit version of the binary payload (e.g. 0x11223344 or 5678). Required for encode operations.")
|
|
parser.add_argument ("--lsv", dest = 'LowestSupportedVersion', type = ValidateUnsignedInteger,
|
|
help = "The 32-bit lowest supported version of the binary payload (e.g. 0x11223344 or 5678). Required for encode operations.")
|
|
|
|
parser.add_argument ("--hash-algorithm", dest = 'HashAlgorithm', type = str,
|
|
help = "Hash algorithm for the payload digest.")
|
|
|
|
parser.add_argument ("--pfx-file", dest='SignToolPfxFile', type=argparse.FileType('rb'),
|
|
help="signtool PFX certificate filename.")
|
|
parser.add_argument ("--subject-name", dest='SignToolSubjectName',
|
|
help="signtool certificate subject name.")
|
|
|
|
parser.add_argument ("--signer-private-cert", dest='OpenSslSignerPrivateCertFile', type=argparse.FileType('rb'),
|
|
help="OpenSSL signer private certificate filename.")
|
|
parser.add_argument ("--other-public-cert", dest='OpenSslOtherPublicCertFile', type=argparse.FileType('rb'),
|
|
help="OpenSSL other public certificate filename.")
|
|
parser.add_argument ("--trusted-public-cert", dest='OpenSslTrustedPublicCertFile', type=argparse.FileType('rb'),
|
|
help="OpenSSL trusted public certificate filename.")
|
|
|
|
parser.add_argument ("--signing-tool-path", dest = 'SigningToolPath',
|
|
help = "Path to signtool or OpenSSL tool. Optional if path to tools are already in PATH.")
|
|
|
|
parser.add_argument ("--embedded-driver", dest = 'EmbeddedDriver', type = argparse.FileType('rb'), action='append', default = [],
|
|
help = "Path to embedded UEFI driver to add to capsule.")
|
|
|
|
#
|
|
# Add optional arguments common to all operations
|
|
#
|
|
parser.add_argument ('--version', action='version', version='%(prog)s ' + __version__)
|
|
parser.add_argument ("-v", "--verbose", dest = 'Verbose', action = "store_true",
|
|
help = "Turn on verbose output with informational messages printed, including capsule headers and warning messages.")
|
|
parser.add_argument ("-q", "--quiet", dest = 'Quiet', action = "store_true",
|
|
help = "Disable all messages except fatal errors.")
|
|
parser.add_argument ("--debug", dest = 'Debug', type = int, metavar = '[0-9]', choices = range (0, 10), default = 0,
|
|
help = "Set debug level")
|
|
parser.add_argument ("--update-image-index", dest = 'UpdateImageIndex', type = ValidateUnsignedInteger, default = 0x01, help = "unique number identifying the firmware image within the device ")
|
|
|
|
#
|
|
# Parse command line arguments
|
|
#
|
|
args = parser.parse_args()
|
|
|
|
#
|
|
# Read binary input file
|
|
#
|
|
Buffer = ''
|
|
if args.InputFile:
|
|
if os.path.getsize (args.InputFile.name) == 0:
|
|
print ('GenerateCapsule: error: InputFile {File} is empty'.format (File = args.InputFile.name))
|
|
sys.exit (1)
|
|
try:
|
|
if args.Verbose:
|
|
print ('Read binary input file {File}'.format (File = args.InputFile.name))
|
|
Buffer = args.InputFile.read ()
|
|
args.InputFile.close ()
|
|
except:
|
|
print ('GenerateCapsule: error: can not read binary input file {File}'.format (File = args.InputFile.name))
|
|
sys.exit (1)
|
|
|
|
#
|
|
# Create objects
|
|
#
|
|
UefiCapsuleHeader = UefiCapsuleHeaderClass ()
|
|
FmpCapsuleHeader = FmpCapsuleHeaderClass ()
|
|
FmpAuthHeader = FmpAuthHeaderClass ()
|
|
FmpPayloadHeader = FmpPayloadHeaderClass ()
|
|
CapsuleDependency = CapsuleDependencyClass ()
|
|
|
|
EmbeddedDriverDescriptorList = []
|
|
PayloadDescriptorList = []
|
|
PayloadJsonDescriptorList = []
|
|
|
|
#
|
|
#Encode Operation
|
|
#
|
|
if args.Encode:
|
|
Encode (PayloadDescriptorList, EmbeddedDriverDescriptorList, Buffer)
|
|
|
|
#
|
|
#Decode Operation
|
|
#
|
|
if args.Decode:
|
|
Decode (PayloadDescriptorList, PayloadJsonDescriptorList, Buffer)
|
|
|
|
#
|
|
#Dump Info Operation
|
|
#
|
|
if args.DumpInfo:
|
|
DumpInfo (Buffer, args)
|
|
|
|
if args.Verbose:
|
|
print('Success')
|