Kinney, Michael D 8b63877aca 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 <sean.brogan@microsoft.com>
Cc: Jiewen Yao <jiewen.yao@intel.com>
Cc: Yonghong Zhu <yonghong.zhu@intel.com>
Cc: Liming Gao <liming.gao@intel.com>
Contributed-under: TianoCore Contribution Agreement 1.1
Signed-off-by: Michael D Kinney <michael.d.kinney@intel.com>
Reviewed-by: Yonghong Zhu <yonghong.zhu@intel.com>
2018-08-02 14:35:20 -07:00

523 lines
22 KiB
Python

## @file
# Generate a capsule.
#
# Copyright (c) 2018, Intel Corporation. All rights reserved.<BR>
# 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 ('<Q', args.MonotonicCount),
args.SigningToolPath,
args.SignToolPfxFile
)
else:
CertData = SignPayloadOpenSsl (
Result + struct.pack ('<Q', args.MonotonicCount),
args.SigningToolPath,
args.OpenSslSignerPrivateCertFile,
args.OpenSslOtherPublicCertFile,
args.OpenSslTrustedPublicCertFile
)
except:
print ('GenerateCapsule: error: can not sign payload')
raise
sys.exit (1)
try:
FmpAuthHeader.MonotonicCount = args.MonotonicCount
FmpAuthHeader.CertData = CertData
FmpAuthHeader.Payload = Result
Result = FmpAuthHeader.Encode ()
if args.Verbose:
FmpAuthHeader.DumpInfo ()
except:
print ('GenerateCapsule: error: can not encode FMP Auth Header')
sys.exit (1)
try:
FmpCapsuleHeader.AddPayload (args.Guid, Result, HardwareInstance = args.HardwareInstance)
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 = 'PopulateSystemTable' in args.CapsuleFlag
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)
elif args.Decode:
try:
Result = UefiCapsuleHeader.Decode (Buffer)
FmpCapsuleHeader.Decode (Result)
Result = FmpCapsuleHeader.GetFmpCapsuleImageHeader (0).Payload
if args.Verbose:
print ('========')
UefiCapsuleHeader.DumpInfo ()
print ('--------')
FmpCapsuleHeader.DumpInfo ()
if UseSignTool or UseOpenSsl:
Result = FmpAuthHeader.Decode (Result)
#
# Verify Image with 64-bit MonotonicCount appended to end of image
#
try:
if UseSignTool:
CertData = VerifyPayloadSignTool (
FmpAuthHeader.Payload + struct.pack ('<Q', FmpAuthHeader.MonotonicCount),
FmpAuthHeader.CertData,
args.SigningToolPath,
args.SignToolPfxFile
)
else:
CertData = VerifyPayloadOpenSsl (
FmpAuthHeader.Payload + struct.pack ('<Q', FmpAuthHeader.MonotonicCount),
FmpAuthHeader.CertData,
args.SigningToolPath,
args.OpenSslSignerPrivateCertFile,
args.OpenSslOtherPublicCertFile,
args.OpenSslTrustedPublicCertFile
)
except ValueError:
print ('GenerateCapsule: warning: can not verify payload.')
Result = FmpPayloadHeader.Decode (Result)
if args.Verbose:
print ('--------')
FmpAuthHeader.DumpInfo ()
print ('--------')
FmpPayloadHeader.DumpInfo ()
else:
if args.Verbose:
print ('--------')
print ('No EFI_FIRMWARE_IMAGE_AUTHENTICATION')
print ('--------')
print ('No FMP_PAYLOAD_HEADER')
if args.Verbose:
print ('========')
except:
print ('GenerateCapsule: error: can not decode capsule')
raise
sys.exit (1)
elif args.DumpInfo:
try:
Result = UefiCapsuleHeader.Decode (Buffer)
FmpCapsuleHeader.Decode (Result)
Result = FmpCapsuleHeader.GetFmpCapsuleImageHeader (0).Payload
print ('========')
UefiCapsuleHeader.DumpInfo ()
print ('--------')
FmpCapsuleHeader.DumpInfo ()
try:
Result = FmpAuthHeader.Decode (Result)
Result = FmpPayloadHeader.Decode (Result)
print ('--------')
FmpAuthHeader.DumpInfo ()
print ('--------')
FmpPayloadHeader.DumpInfo ()
except:
print ('--------')
print ('No EFI_FIRMWARE_IMAGE_AUTHENTICATION')
print ('--------')
print ('No FMP_PAYLOAD_HEADER')
print ('========')
except:
print ('GenerateCapsule: error: can not decode capsule')
sys.exit (1)
else:
print('GenerateCapsule: error: invalid options')
sys.exit (1)
#
# Write binary output file
#
if args.OutputFile is not None:
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)
if args.Verbose:
print('Success')