From: Kinney, Michael D Date: Wed, 2 May 2018 03:54:46 +0000 (-0700) Subject: BaseTools/Capsule: Add Capsule Generation Tools X-Git-Tag: edk2-stable201903~1302 X-Git-Url: https://git.proxmox.com/?p=mirror_edk2.git;a=commitdiff_plain;h=8b63877aca1e5a3576b7275439c494a6d40ed1b5;ds=sidebyside BaseTools/Capsule: Add Capsule Generation Tools https://bugzilla.tianocore.org/show_bug.cgi?id=945 Based on content from the following branch https://github.com/Microsoft/MS_UEFI/tree/share/beta/CapsuleTools * Convert C tools to Python * Add common python modules to: BaseTools/Source/Python/Common/Uefi/Capsule BaseTools/Source/Python/Common/Edk2/Capsule * Add GenerateCapsule.py to BaseTools/Source/Python/Capsule * Add Windows and Posix wrappers for GenerateCapsule.py usage: GenerateCapsule [-h] [-o OUTPUTFILE] (-e | -d | --dump-info) [--capflag {PersistAcrossReset,PopulateSystemTable,InitiateReset}] [--capoemflag CAPSULEOEMFLAG] [--guid GUID] [--hardware-instance HARDWAREINSTANCE] [--monotonic-count MONOTONICCOUNT] [--fw-version FWVERSION] [--lsv LOWESTSUPPORTEDVERSION] [--pfx-file SIGNTOOLPFXFILE] [--signer-private-cert OPENSSLSIGNERPRIVATECERTFILE] [--other-public-cert OPENSSLOTHERPUBLICCERTFILE] [--trusted-public-cert OPENSSLTRUSTEDPUBLICCERTFILE] [--signing-tool-path SIGNINGTOOLPATH] [--version] [-v] [-q] [--debug [0-9]] InputFile Generate a capsule. Copyright (c) 2018, Intel Corporation. All rights reserved. positional arguments: InputFile Input binary payload filename. optional arguments: -h, --help show this help message and exit -o OUTPUTFILE, --output OUTPUTFILE Output filename. -e, --encode Encode file -d, --decode Decode file --dump-info Display FMP Payload Header information --capflag {PersistAcrossReset,PopulateSystemTable,InitiateReset} Capsule flag can be PersistAcrossReset, or PopulateSystemTable or InitiateReset or not set --capoemflag CAPSULEOEMFLAG Capsule OEM Flag is an integer between 0x0000 and 0xffff. --guid GUID The FMP/ESRT GUID in registry format. Required for encode operations. --hardware-instance HARDWAREINSTANCE The 64-bit hardware instance. The default is 0x0000000000000000 --monotonic-count MONOTONICCOUNT 64-bit monotonic count value in header. Default is 0x0000000000000000. --fw-version FWVERSION The 32-bit version of the binary payload (e.g. 0x11223344 or 5678). --lsv LOWESTSUPPORTEDVERSION The 32-bit lowest supported version of the binary payload (e.g. 0x11223344 or 5678). --pfx-file SIGNTOOLPFXFILE signtool PFX certificate filename. --signer-private-cert OPENSSLSIGNERPRIVATECERTFILE OpenSSL signer private certificate filename. --other-public-cert OPENSSLOTHERPUBLICCERTFILE OpenSSL other public certificate filename. --trusted-public-cert OPENSSLTRUSTEDPUBLICCERTFILE OpenSSL trusted public certificate filename. --signing-tool-path SIGNINGTOOLPATH Path to signtool or OpenSSL tool. Optional if path to tools are already in PATH. --version show program's version number and exit -v, --verbose Turn on verbose output with informational messages printed, including capsule headers and warning messages. -q, --quiet Disable all messages except fatal errors. --debug [0-9] Set debug level Cc: Sean Brogan Cc: Jiewen Yao Cc: Yonghong Zhu Cc: Liming Gao Contributed-under: TianoCore Contribution Agreement 1.1 Signed-off-by: Michael D Kinney Reviewed-by: Yonghong Zhu --- diff --git a/BaseTools/BinWrappers/PosixLike/GenerateCapsule b/BaseTools/BinWrappers/PosixLike/GenerateCapsule new file mode 100644 index 0000000000..59a6c8ba43 --- /dev/null +++ b/BaseTools/BinWrappers/PosixLike/GenerateCapsule @@ -0,0 +1,14 @@ +#!/usr/bin/env bash +#python `dirname $0`/RunToolFromSource.py `basename $0` $* + +# If a python2 command is available, use it in preference to python +if command -v python2 >/dev/null 2>&1; then + python_exe=python2 +fi + +full_cmd=${BASH_SOURCE:-$0} # see http://mywiki.wooledge.org/BashFAQ/028 for a discussion of why $0 is not a good choice here +dir=$(dirname "$full_cmd") +cmd=${full_cmd##*/} + +export PYTHONPATH="$dir/../../Source/Python${PYTHONPATH:+:"$PYTHONPATH"}" +exec "${python_exe:-python}" "$dir/../../Source/Python/Capsule/$cmd.py" "$@" diff --git a/BaseTools/BinWrappers/WindowsLike/GenerateCapsule.bat b/BaseTools/BinWrappers/WindowsLike/GenerateCapsule.bat new file mode 100644 index 0000000000..ca442d181b --- /dev/null +++ b/BaseTools/BinWrappers/WindowsLike/GenerateCapsule.bat @@ -0,0 +1 @@ +@%PYTHON_HOME%\python.exe %BASE_TOOLS_PATH%\Source\Python\Capsule\GenerateCapsule.py %* diff --git a/BaseTools/Source/Python/Capsule/GenerateCapsule.py b/BaseTools/Source/Python/Capsule/GenerateCapsule.py new file mode 100644 index 0000000000..4018dc0420 --- /dev/null +++ b/BaseTools/Source/Python/Capsule/GenerateCapsule.py @@ -0,0 +1,522 @@ +## @file +# Generate a capsule. +# +# Copyright (c) 2018, Intel Corporation. All rights reserved.
+# This program and the accompanying materials +# are licensed and made available under the terms and conditions of the BSD License +# which accompanies this distribution. The full text of the license may be found at +# http://opensource.org/licenses/bsd-license.php +# +# THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS, +# WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED. +# + +''' +GenerateCapsule +''' + +import sys +import argparse +import uuid +import struct +import subprocess +import os +import tempfile +import shutil +import platform +from Common.Uefi.Capsule.UefiCapsuleHeader import UefiCapsuleHeaderClass +from Common.Uefi.Capsule.FmpCapsuleHeader import FmpCapsuleHeaderClass +from Common.Uefi.Capsule.FmpAuthHeader import FmpAuthHeaderClass +from Common.Edk2.Capsule.FmpPayloadHeader import FmpPayloadHeaderClass + +# +# Globals for help information +# +__prog__ = 'GenerateCapsule' +__version__ = '0.9' +__copyright__ = 'Copyright (c) 2018, Intel Corporation. All rights reserved.' +__description__ = 'Generate a capsule.\n' + +def SignPayloadSignTool (Payload, ToolPath, PfxFile): + # + # 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: + File = open (TempFileName, mode='wb') + File.write (Payload) + File.close () + 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 sha256 /p7ce DetachedSignedData /p7co 1.2.840.113549.1.7.2 ' + Command = Command + '/p7 {TempDir} '.format (TempDir = TempDirectoryName) + Command = Command + '/f {PfxFile} '.format (PfxFile = PfxFile) + Command = Command + TempFileName + + # + # 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: + File = open (TempFileName + '.p7', mode='rb') + Signature = File.read () + File.close () + except: + shutil.rmtree (TempDirectoryName) + raise ValueError ('GenerateCapsule: error: can not read signature file.') + + shutil.rmtree (TempDirectoryName) + return Signature + +def VerifyPayloadSignTool (Payload, CertData, ToolPath, PfxFile): + print ('signtool verify is not supported.') + raise ValueError ('GenerateCapsule: error: signtool verify is not supported.') + +def SignPayloadOpenSsl (Payload, ToolPath, SignerPrivateCertFile, OtherPublicCertFile, TrustedPublicCertFile): + # + # 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 sha256 ' + Command = Command + '-signer "{Private}" -certfile "{Public}"'.format (Private = SignerPrivateCertFile, Public = OtherPublicCertFile) + + # + # 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): + # + # 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: + File = open (TempFileName, mode='wb') + File.write (Payload) + File.close () + 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) + + # + # 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 + + # + # 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'), + 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 ("--capflag", dest = 'CapsuleFlag', action='append', default = [], + choices=['PersistAcrossReset', 'PopulateSystemTable', 'InitiateReset'], + help = "Capsule flag can be PersistAcrossReset, or PopulateSystemTable 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 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).") + parser.add_argument ("--lsv", dest = 'LowestSupportedVersion', type = ValidateUnsignedInteger, + help = "The 32-bit lowest supported version of the binary payload (e.g. 0x11223344 or 5678).") + + parser.add_argument ("--pfx-file", dest='SignToolPfxFile', type=argparse.FileType('rb'), + help="signtool PFX certificate filename.") + + 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.") + + # + # 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") + + # + # Parse command line arguments + # + args = parser.parse_args() + + # + # Perform additional argument verification + # + if args.Encode: + if args.Guid is None: + parser.error ('the following option is required: --guid') + if 'PersistAcrossReset' not in args.CapsuleFlag: + if 'PopulateSystemTable' in args.CapsuleFlag: + parser.error ('--capflag PopulateSystemTable also requires --capflag PersistAcrossReset') + if 'InitiateReset' in args.CapsuleFlag: + parser.error ('--capflag InitiateReset also requires --capflag PersistAcrossReset') + + UseSignTool = args.SignToolPfxFile is not None + UseOpenSsl = (args.OpenSslSignerPrivateCertFile is not None and + args.OpenSslOtherPublicCertFile is not None and + args.OpenSslTrustedPublicCertFile is not None) + AnyOpenSsl = (args.OpenSslSignerPrivateCertFile is not None or + args.OpenSslOtherPublicCertFile is not None or + args.OpenSslTrustedPublicCertFile is not None) + if args.Encode or args.Decode: + if args.OutputFile is None: + parser.error ('the following option is required for all encode and decode operations: --output') + + if UseSignTool and AnyOpenSsl: + parser.error ('Providing both signtool and OpenSSL options is not supported') + if not UseSignTool and not UseOpenSsl and AnyOpenSsl: + parser.error ('all the following options are required for OpenSSL: --signer-private-cert, --other-public-cert, --trusted-public-cert') + if UseSignTool and platform.system() != 'Windows': + parser.error ('Use of signtool is not supported on this operating system.') + if args.Encode and (UseSignTool or UseOpenSsl): + if args.FwVersion is None or args.LowestSupportedVersion is None: + parser.error ('the following options are required: --fw-version, --lsv') + + if UseSignTool: + args.SignToolPfxFile.close() + args.SignToolPfxFile = args.SignToolPfxFile.name + if UseOpenSsl: + args.OpenSslSignerPrivateCertFile.close() + args.OpenSslOtherPublicCertFile.close() + args.OpenSslTrustedPublicCertFile.close() + args.OpenSslSignerPrivateCertFile = args.OpenSslSignerPrivateCertFile.name + args.OpenSslOtherPublicCertFile = args.OpenSslOtherPublicCertFile.name + args.OpenSslTrustedPublicCertFile = args.OpenSslTrustedPublicCertFile.name + + # + # Read binary input file + # + 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 () + + if args.Encode: + Result = Buffer + if UseSignTool or UseOpenSsl: + try: + FmpPayloadHeader.FwVersion = args.FwVersion + FmpPayloadHeader.LowestSupportedVersion = args.LowestSupportedVersion + FmpPayloadHeader.Payload = Result + Result = FmpPayloadHeader.Encode () + if args.Verbose: + FmpPayloadHeader.DumpInfo () + except: + print ('GenerateCapsule: error: can not encode FMP Payload Header') + sys.exit (1) + + # + # Sign image with 64-bit MonotonicCount appended to end of image + # + try: + if UseSignTool: + CertData = SignPayloadSignTool ( + Result + struct.pack (' +# This program and the accompanying materials +# are licensed and made available under the terms and conditions of the BSD License +# which accompanies this distribution. The full text of the license may be found at +# http://opensource.org/licenses/bsd-license.php +# +# THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS, +# WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED. +# + +''' +FmpPayloadHeader +''' + +import struct + +def _SIGNATURE_32 (A, B, C, D): + return struct.unpack ('=I',bytearray (A + B + C + D, 'ascii'))[0] + +def _SIGNATURE_32_TO_STRING (Signature): + return struct.pack (" +# This program and the accompanying materials +# are licensed and made available under the terms and conditions of the BSD License +# which accompanies this distribution. The full text of the license may be found at +# http://opensource.org/licenses/bsd-license.php +# +# THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS, +# WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED. +# diff --git a/BaseTools/Source/Python/Common/Edk2/__init__.py b/BaseTools/Source/Python/Common/Edk2/__init__.py new file mode 100644 index 0000000000..97d925cbf8 --- /dev/null +++ b/BaseTools/Source/Python/Common/Edk2/__init__.py @@ -0,0 +1,15 @@ +## @file +# Python 'Common.Edk2' package initialization file. +# +# This file is required to make Python interpreter treat the directory +# as containing package. +# +# Copyright (c) 2018, Intel Corporation. All rights reserved.
+# This program and the accompanying materials +# are licensed and made available under the terms and conditions of the BSD License +# which accompanies this distribution. The full text of the license may be found at +# http://opensource.org/licenses/bsd-license.php +# +# THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS, +# WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED. +# diff --git a/BaseTools/Source/Python/Common/Uefi/Capsule/FmpAuthHeader.py b/BaseTools/Source/Python/Common/Uefi/Capsule/FmpAuthHeader.py new file mode 100644 index 0000000000..aec52bf772 --- /dev/null +++ b/BaseTools/Source/Python/Common/Uefi/Capsule/FmpAuthHeader.py @@ -0,0 +1,184 @@ +## @file +# Module that encodes and decodes a EFI_FIRMWARE_IMAGE_AUTHENTICATION with +# certificate data and payload data. +# +# Copyright (c) 2018, Intel Corporation. All rights reserved.
+# This program and the accompanying materials +# are licensed and made available under the terms and conditions of the BSD License +# which accompanies this distribution. The full text of the license may be found at +# http://opensource.org/licenses/bsd-license.php +# +# THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS, +# WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED. +# + +''' +FmpAuthHeader +''' + +import struct +import uuid + +class FmpAuthHeaderClass (object): + # /// + # /// Image Attribute -Authentication Required + # /// + # typedef struct { + # /// + # /// It is included in the signature of AuthInfo. It is used to ensure freshness/no replay. + # /// It is incremented during each firmware image operation. + # /// + # UINT64 MonotonicCount; + # /// + # /// Provides the authorization for the firmware image operations. It is a signature across + # /// the image data and the Monotonic Count value. Caller uses the private key that is + # /// associated with a public key that has been provisioned via the key exchange. + # /// Because this is defined as a signature, WIN_CERTIFICATE_UEFI_GUID.CertType must + # /// be EFI_CERT_TYPE_PKCS7_GUID. + # /// + # WIN_CERTIFICATE_UEFI_GUID AuthInfo; + # } EFI_FIRMWARE_IMAGE_AUTHENTICATION; + # + # /// + # /// Certificate which encapsulates a GUID-specific digital signature + # /// + # typedef struct { + # /// + # /// This is the standard WIN_CERTIFICATE header, where + # /// wCertificateType is set to WIN_CERT_TYPE_EFI_GUID. + # /// + # WIN_CERTIFICATE Hdr; + # /// + # /// This is the unique id which determines the + # /// format of the CertData. . + # /// + # EFI_GUID CertType; + # /// + # /// The following is the certificate data. The format of + # /// the data is determined by the CertType. + # /// If CertType is EFI_CERT_TYPE_RSA2048_SHA256_GUID, + # /// the CertData will be EFI_CERT_BLOCK_RSA_2048_SHA256 structure. + # /// + # UINT8 CertData[1]; + # } WIN_CERTIFICATE_UEFI_GUID; + # + # /// + # /// The WIN_CERTIFICATE structure is part of the PE/COFF specification. + # /// + # typedef struct { + # /// + # /// The length of the entire certificate, + # /// including the length of the header, in bytes. + # /// + # UINT32 dwLength; + # /// + # /// The revision level of the WIN_CERTIFICATE + # /// structure. The current revision level is 0x0200. + # /// + # UINT16 wRevision; + # /// + # /// The certificate type. See WIN_CERT_TYPE_xxx for the UEFI + # /// certificate types. The UEFI specification reserves the range of + # /// certificate type values from 0x0EF0 to 0x0EFF. + # /// + # UINT16 wCertificateType; + # /// + # /// The following is the actual certificate. The format of + # /// the certificate depends on wCertificateType. + # /// + # /// UINT8 bCertificate[ANYSIZE_ARRAY]; + # /// + # } WIN_CERTIFICATE; + # + # #define WIN_CERT_TYPE_EFI_GUID 0x0EF1 + # + # /// + # /// This identifies a signature containing a DER-encoded PKCS #7 version 1.5 [RFC2315] + # /// SignedData value. + # /// + # #define EFI_CERT_TYPE_PKCS7_GUID \ + # { \ + # 0x4aafd29d, 0x68df, 0x49ee, {0x8a, 0xa9, 0x34, 0x7d, 0x37, 0x56, 0x65, 0xa7} \ + # } + + _StructFormat = ' +# This program and the accompanying materials +# are licensed and made available under the terms and conditions of the BSD License +# which accompanies this distribution. The full text of the license may be found at +# http://opensource.org/licenses/bsd-license.php +# +# THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS, +# WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED. +# + +''' +FmpCapsuleHeader +''' + +import struct +import uuid + +class FmpCapsuleImageHeaderClass (object): + # typedef struct { + # UINT32 Version; + # + # /// + # /// Used to identify device firmware targeted by this update. This guid is matched by + # /// system firmware against ImageTypeId field within a EFI_FIRMWARE_IMAGE_DESCRIPTOR + # /// + # EFI_GUID UpdateImageTypeId; + # + # /// + # /// Passed as ImageIndex in call to EFI_FIRMWARE_MANAGEMENT_PROTOCOL.SetImage () + # /// + # UINT8 UpdateImageIndex; + # UINT8 reserved_bytes[3]; + # + # /// + # /// Size of the binary update image which immediately follows this structure + # /// + # UINT32 UpdateImageSize; + # + # /// + # /// Size of the VendorCode bytes which optionally immediately follow binary update image in the capsule + # /// + # UINT32 UpdateVendorCodeSize; + # + # /// + # /// The HardwareInstance to target with this update. If value is zero it means match all + # /// HardwareInstances. This field allows update software to target only a single device in + # /// cases where there are more than one device with the same ImageTypeId GUID. + # /// This header is outside the signed data of the Authentication Info structure and + # /// therefore can be modified without changing the Auth data. + # /// + # UINT64 UpdateHardwareInstance; + # } EFI_FIRMWARE_MANAGEMENT_CAPSULE_IMAGE_HEADER; + # + # #define EFI_FIRMWARE_MANAGEMENT_CAPSULE_IMAGE_HEADER_INIT_VERSION 0x00000002 + + _StructFormat = ' len (self._EmbeddedDriverList): + raise ValueError + return self._EmbeddedDriverList[Index] + + def AddPayload (self, UpdateImageTypeId, Payload = b'', VendorCodeBytes = b'', HardwareInstance = 0): + self._PayloadList.append ((UpdateImageTypeId, Payload, VendorCodeBytes, HardwareInstance)) + + def GetFmpCapsuleImageHeader (self, Index): + if Index >= len (self._FmpCapsuleImageHeaderList): + raise ValueError + return self._FmpCapsuleImageHeaderList[Index] + + def Encode (self): + self.EmbeddedDriverCount = len (self._EmbeddedDriverList) + self.PayloadItemCount = len (self._PayloadList) + + FmpCapsuleHeader = struct.pack ( + self._StructFormat, + self.Version, + self.EmbeddedDriverCount, + self.PayloadItemCount + ) + + FmpCapsuleData = b'' + Offset = self._StructSize + (self.EmbeddedDriverCount + self.PayloadItemCount) * self._ItemOffsetSize + for EmbeddedDriver in self._EmbeddedDriverList: + FmpCapsuleData = FmpCapsuleData + EmbeddedDriver + self._ItemOffsetList.append (Offset) + Offset = Offset + len (EmbeddedDriver) + Index = 1 + for (UpdateImageTypeId, Payload, VendorCodeBytes, HardwareInstance) in self._PayloadList: + FmpCapsuleImageHeader = FmpCapsuleImageHeaderClass () + FmpCapsuleImageHeader.UpdateImageTypeId = UpdateImageTypeId + FmpCapsuleImageHeader.UpdateImageIndex = Index + FmpCapsuleImageHeader.Payload = Payload + FmpCapsuleImageHeader.VendorCodeBytes = VendorCodeBytes + FmpCapsuleImageHeader.UpdateHardwareInstance = HardwareInstance + FmpCapsuleImage = FmpCapsuleImageHeader.Encode () + FmpCapsuleData = FmpCapsuleData + FmpCapsuleImage + + self._ItemOffsetList.append (Offset) + self._FmpCapsuleImageHeaderList.append (FmpCapsuleImageHeader) + + Offset = Offset + len (FmpCapsuleImage) + Index = Index + 1 + + for Offset in self._ItemOffsetList: + FmpCapsuleHeader = FmpCapsuleHeader + struct.pack (self._ItemOffsetFormat, Offset) + + self._Valid = True + return FmpCapsuleHeader + FmpCapsuleData + + def Decode (self, Buffer): + if len (Buffer) < self._StructSize: + raise ValueError + (Version, EmbeddedDriverCount, PayloadItemCount) = \ + struct.unpack ( + self._StructFormat, + Buffer[0:self._StructSize] + ) + if Version < self.EFI_FIRMWARE_MANAGEMENT_CAPSULE_HEADER_INIT_VERSION: + raise ValueError + + self.Version = Version + self.EmbeddedDriverCount = EmbeddedDriverCount + self.PayloadItemCount = PayloadItemCount + self._ItemOffsetList = [] + self._EmbeddedDriverList = [] + self._PayloadList = [] + self._FmpCapsuleImageHeaderList = [] + + # + # Parse the ItemOffsetList values + # + Offset = self._StructSize + for Index in range (0, EmbeddedDriverCount + PayloadItemCount): + ItemOffset = struct.unpack (self._ItemOffsetFormat, Buffer[Offset:Offset + self._ItemOffsetSize])[0] + if ItemOffset >= len (Buffer): + raise ValueError + self._ItemOffsetList.append (ItemOffset) + Offset = Offset + self._ItemOffsetSize + Result = Buffer[Offset:] + + # + # Parse the EmbeddedDrivers + # + for Index in range (0, EmbeddedDriverCount): + Offset = self._ItemOffsetList[Index] + if Index < (len (self._ItemOffsetList) - 1): + Length = self._ItemOffsetList[Index + 1] - Offset + else: + Length = len (Buffer) - Offset + self.AddEmbeddedDriver (Buffer[Offset:Offset + Length]) + + # + # Parse the Payloads that are FMP Capsule Images + # + for Index in range (EmbeddedDriverCount, EmbeddedDriverCount + PayloadItemCount): + Offset = self._ItemOffsetList[Index] + if Index < (len (self._ItemOffsetList) - 1): + Length = self._ItemOffsetList[Index + 1] - Offset + else: + Length = len (Buffer) - Offset + FmpCapsuleImageHeader = FmpCapsuleImageHeaderClass () + FmpCapsuleImageHeader.Decode (Buffer[Offset:Offset + Length]) + self.AddPayload ( + FmpCapsuleImageHeader.UpdateImageTypeId, + FmpCapsuleImageHeader.Payload, + FmpCapsuleImageHeader.VendorCodeBytes + ) + self._FmpCapsuleImageHeaderList.append (FmpCapsuleImageHeader) + + self._Valid = True + return Result + + def DumpInfo (self): + if not self._Valid: + raise ValueError + print ('EFI_FIRMWARE_MANAGEMENT_CAPSULE_HEADER.Version = {Version:08X}'.format (Version = self.Version)) + print ('EFI_FIRMWARE_MANAGEMENT_CAPSULE_HEADER.EmbeddedDriverCount = {EmbeddedDriverCount:08X}'.format (EmbeddedDriverCount = self.EmbeddedDriverCount)) + print ('EFI_FIRMWARE_MANAGEMENT_CAPSULE_HEADER.PayloadItemCount = {PayloadItemCount:08X}'.format (PayloadItemCount = self.PayloadItemCount)) + print ('EFI_FIRMWARE_MANAGEMENT_CAPSULE_HEADER.ItemOffsetList = ') + for Offset in self._ItemOffsetList: + print (' {Offset:016X}'.format (Offset = Offset)) + for FmpCapsuleImageHeader in self._FmpCapsuleImageHeaderList: + FmpCapsuleImageHeader.DumpInfo () diff --git a/BaseTools/Source/Python/Common/Uefi/Capsule/UefiCapsuleHeader.py b/BaseTools/Source/Python/Common/Uefi/Capsule/UefiCapsuleHeader.py new file mode 100644 index 0000000000..cfe1cb6c46 --- /dev/null +++ b/BaseTools/Source/Python/Common/Uefi/Capsule/UefiCapsuleHeader.py @@ -0,0 +1,136 @@ +## @file +# Module that encodes and decodes a EFI_CAPSULE_HEADER with a payload +# +# Copyright (c) 2018, Intel Corporation. All rights reserved.
+# This program and the accompanying materials +# are licensed and made available under the terms and conditions of the BSD License +# which accompanies this distribution. The full text of the license may be found at +# http://opensource.org/licenses/bsd-license.php +# +# THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS, +# WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED. +# + +''' +UefiCapsuleHeader +''' + +import struct +import uuid + +class UefiCapsuleHeaderClass (object): + # typedef struct { + # /// + # /// A GUID that defines the contents of a capsule. + # /// + # EFI_GUID CapsuleGuid; + # /// + # /// The size of the capsule header. This may be larger than the size of + # /// the EFI_CAPSULE_HEADER since CapsuleGuid may imply + # /// extended header entries + # /// + # UINT32 HeaderSize; + # /// + # /// Bit-mapped list describing the capsule attributes. The Flag values + # /// of 0x0000 - 0xFFFF are defined by CapsuleGuid. Flag values + # /// of 0x10000 - 0xFFFFFFFF are defined by this specification + # /// + # UINT32 Flags; + # /// + # /// Size in bytes of the capsule. + # /// + # UINT32 CapsuleImageSize; + # } EFI_CAPSULE_HEADER; + # + # #define CAPSULE_FLAGS_PERSIST_ACROSS_RESET 0x00010000 + # #define CAPSULE_FLAGS_POPULATE_SYSTEM_TABLE 0x00020000 + # #define CAPSULE_FLAGS_INITIATE_RESET 0x00040000 + # + _StructFormat = '<16sIIII' + _StructSize = struct.calcsize (_StructFormat) + + EFI_FIRMWARE_MANAGEMENT_CAPSULE_ID_GUID = uuid.UUID ('6DCBD5ED-E82D-4C44-BDA1-7194199AD92A') + + _CAPSULE_FLAGS_PERSIST_ACROSS_RESET = 0x00010000 + _CAPSULE_FLAGS_POPULATE_SYSTEM_TABLE = 0x00020000 + _CAPSULE_FLAGS_INITIATE_RESET = 0x00040000 + + def __init__ (self): + self._Valid = False + self.CapsuleGuid = self.EFI_FIRMWARE_MANAGEMENT_CAPSULE_ID_GUID + self.HeaderSize = self._StructSize + self.OemFlags = 0x0000 + self.PersistAcrossReset = False + self.PopulateSystemTable = False + self.InitiateReset = False + self.CapsuleImageSize = self.HeaderSize + self.Payload = b'' + + def Encode (self): + Flags = self.OemFlags + if self.PersistAcrossReset: + Flags = Flags | self._CAPSULE_FLAGS_PERSIST_ACROSS_RESET + if self.PopulateSystemTable: + Flags = Flags | self._CAPSULE_FLAGS_POPULATE_SYSTEM_TABLE + if self.InitiateReset: + Flags = Flags | self._CAPSULE_FLAGS_INITIATE_RESET + + self.CapsuleImageSize = self.HeaderSize + len (self.Payload) + + UefiCapsuleHeader = struct.pack ( + self._StructFormat, + self.CapsuleGuid.bytes_le, + self.HeaderSize, + Flags, + self.CapsuleImageSize, + 0 + ) + self._Valid = True + return UefiCapsuleHeader + self.Payload + + def Decode (self, Buffer): + if len (Buffer) < self._StructSize: + raise ValueError + (CapsuleGuid, HeaderSize, Flags, CapsuleImageSize, Reserved) = \ + struct.unpack ( + self._StructFormat, + Buffer[0:self._StructSize] + ) + if HeaderSize < self._StructSize: + raise ValueError + if CapsuleImageSize != len (Buffer): + raise ValueError + self.CapsuleGuid = uuid.UUID (bytes_le = CapsuleGuid) + self.HeaderSize = HeaderSize + self.OemFlags = Flags & 0xffff + self.PersistAcrossReset = (Flags & self._CAPSULE_FLAGS_PERSIST_ACROSS_RESET) != 0 + self.PopulateSystemTable = (Flags & self._CAPSULE_FLAGS_POPULATE_SYSTEM_TABLE) != 0 + self.InitiateReset = (Flags & self._CAPSULE_FLAGS_INITIATE_RESET) != 0 + self.CapsuleImageSize = CapsuleImageSize + self.Payload = Buffer[self.HeaderSize:] + + self._Valid = True + return self.Payload + + def DumpInfo (self): + if not self._Valid: + raise ValueError + Flags = self.OemFlags + if self.PersistAcrossReset: + Flags = Flags | self._CAPSULE_FLAGS_PERSIST_ACROSS_RESET + if self.PopulateSystemTable: + Flags = Flags | self._CAPSULE_FLAGS_POPULATE_SYSTEM_TABLE + if self.InitiateReset: + Flags = Flags | self._CAPSULE_FLAGS_INITIATE_RESET + print ('EFI_CAPSULE_HEADER.CapsuleGuid = {Guid}'.format (Guid = str(self.CapsuleGuid).upper())) + print ('EFI_CAPSULE_HEADER.HeaderSize = {Size:08X}'.format (Size = self.HeaderSize)) + print ('EFI_CAPSULE_HEADER.Flags = {Flags:08X}'.format (Flags = Flags)) + print (' OEM Flags = {Flags:04X}'.format (Flags = self.OemFlags)) + if self.PersistAcrossReset: + print (' CAPSULE_FLAGS_PERSIST_ACROSS_RESET') + if self.PopulateSystemTable: + print (' CAPSULE_FLAGS_POPULATE_SYSTEM_TABLE') + if self.InitiateReset: + print (' CAPSULE_FLAGS_INITIATE_RESET') + print ('EFI_CAPSULE_HEADER.CapsuleImageSize = {Size:08X}'.format (Size = self.CapsuleImageSize)) + print ('sizeof (Payload) = {Size:08X}'.format (Size = len (self.Payload))) diff --git a/BaseTools/Source/Python/Common/Uefi/Capsule/__init__.py b/BaseTools/Source/Python/Common/Uefi/Capsule/__init__.py new file mode 100644 index 0000000000..d9db4aa919 --- /dev/null +++ b/BaseTools/Source/Python/Common/Uefi/Capsule/__init__.py @@ -0,0 +1,15 @@ +## @file +# Python 'Common.Uefi.Capsule' package initialization file. +# +# This file is required to make Python interpreter treat the directory +# as containing package. +# +# Copyright (c) 2018, Intel Corporation. All rights reserved.
+# This program and the accompanying materials +# are licensed and made available under the terms and conditions of the BSD License +# which accompanies this distribution. The full text of the license may be found at +# http://opensource.org/licenses/bsd-license.php +# +# THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS, +# WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED. +# diff --git a/BaseTools/Source/Python/Common/Uefi/__init__.py b/BaseTools/Source/Python/Common/Uefi/__init__.py new file mode 100644 index 0000000000..d80219dcb3 --- /dev/null +++ b/BaseTools/Source/Python/Common/Uefi/__init__.py @@ -0,0 +1,15 @@ +## @file +# Python 'Common.Uefi' package initialization file. +# +# This file is required to make Python interpreter treat the directory +# as containing package. +# +# Copyright (c) 2018, Intel Corporation. All rights reserved.
+# This program and the accompanying materials +# are licensed and made available under the terms and conditions of the BSD License +# which accompanies this distribution. The full text of the license may be found at +# http://opensource.org/licenses/bsd-license.php +# +# THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS, +# WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED. +#