4 # This tool generates a UEFI Capsule around an FMP Capsule. The capsule payload
5 # be signed using signtool or OpenSSL and if it is signed the signed content
6 # includes an FMP Payload Header.
8 # This tool is intended to be used to generate UEFI Capsules to update the
9 # system firmware or device firmware for integrated devices. In order to
10 # keep the tool as simple as possible, it has the following limitations:
11 # * Do not support multiple payloads in a capsule.
12 # * Do not support optional drivers in a capsule.
13 # * Do not support vendor code bytes in a capsule.
15 # Copyright (c) 2018, Intel Corporation. All rights reserved.<BR>
16 # SPDX-License-Identifier: BSD-2-Clause-Patent
32 from Common
.Uefi
.Capsule
.UefiCapsuleHeader
import UefiCapsuleHeaderClass
33 from Common
.Uefi
.Capsule
.FmpCapsuleHeader
import FmpCapsuleHeaderClass
34 from Common
.Uefi
.Capsule
.FmpAuthHeader
import FmpAuthHeaderClass
35 from Common
.Edk2
.Capsule
.FmpPayloadHeader
import FmpPayloadHeaderClass
38 # Globals for help information
40 __prog__
= 'GenerateCapsule'
42 __copyright__
= 'Copyright (c) 2018, Intel Corporation. All rights reserved.'
43 __description__
= 'Generate a capsule.\n'
45 def SignPayloadSignTool (Payload
, ToolPath
, PfxFile
):
47 # Create a temporary directory
49 TempDirectoryName
= tempfile
.mkdtemp()
52 # Generate temp file name for the payload contents
54 TempFileName
= os
.path
.join (TempDirectoryName
, 'Payload.bin')
57 # Create temporary payload file for signing
60 File
= open (TempFileName
, mode
='wb')
64 shutil
.rmtree (TempDirectoryName
)
65 raise ValueError ('GenerateCapsule: error: can not write temporary payload file.')
68 # Build signtool command
73 Command
= Command
+ '"{Path}" '.format (Path
= os
.path
.join (ToolPath
, 'signtool.exe'))
74 Command
= Command
+ 'sign /fd sha256 /p7ce DetachedSignedData /p7co 1.2.840.113549.1.7.2 '
75 Command
= Command
+ '/p7 {TempDir} '.format (TempDir
= TempDirectoryName
)
76 Command
= Command
+ '/f {PfxFile} '.format (PfxFile
= PfxFile
)
77 Command
= Command
+ TempFileName
80 # Sign the input file using the specified private key
83 Process
= subprocess
.Popen (Command
, stdin
= subprocess
.PIPE
, stdout
= subprocess
.PIPE
, stderr
= subprocess
.PIPE
, shell
= True)
84 Result
= Process
.communicate('')
86 shutil
.rmtree (TempDirectoryName
)
87 raise ValueError ('GenerateCapsule: error: can not run signtool.')
89 if Process
.returncode
!= 0:
90 shutil
.rmtree (TempDirectoryName
)
91 print (Result
[1].decode(encoding
='utf-8', errors
='ignore'))
92 raise ValueError ('GenerateCapsule: error: signtool failed.')
95 # Read the signature from the generated output file
98 File
= open (TempFileName
+ '.p7', mode
='rb')
99 Signature
= File
.read ()
102 shutil
.rmtree (TempDirectoryName
)
103 raise ValueError ('GenerateCapsule: error: can not read signature file.')
105 shutil
.rmtree (TempDirectoryName
)
108 def VerifyPayloadSignTool (Payload
, CertData
, ToolPath
, PfxFile
):
109 print ('signtool verify is not supported.')
110 raise ValueError ('GenerateCapsule: error: signtool verify is not supported.')
112 def SignPayloadOpenSsl (Payload
, ToolPath
, SignerPrivateCertFile
, OtherPublicCertFile
, TrustedPublicCertFile
):
114 # Build openssl command
119 Command
= Command
+ '"{Path}" '.format (Path
= os
.path
.join (ToolPath
, 'openssl'))
120 Command
= Command
+ 'smime -sign -binary -outform DER -md sha256 '
121 Command
= Command
+ '-signer "{Private}" -certfile "{Public}"'.format (Private
= SignerPrivateCertFile
, Public
= OtherPublicCertFile
)
124 # Sign the input file using the specified private key and capture signature from STDOUT
127 Process
= subprocess
.Popen (Command
, stdin
= subprocess
.PIPE
, stdout
= subprocess
.PIPE
, stderr
= subprocess
.PIPE
, shell
= True)
128 Result
= Process
.communicate(input = Payload
)
129 Signature
= Result
[0].decode(encoding
='utf-8', errors
='ignore')
131 raise ValueError ('GenerateCapsule: error: can not run openssl.')
133 if Process
.returncode
!= 0:
134 print (Result
[1].decode(encoding
='utf-8', errors
='ignore'))
135 raise ValueError ('GenerateCapsule: error: openssl failed.')
139 def VerifyPayloadOpenSsl (Payload
, CertData
, ToolPath
, SignerPrivateCertFile
, OtherPublicCertFile
, TrustedPublicCertFile
):
141 # Create a temporary directory
143 TempDirectoryName
= tempfile
.mkdtemp()
146 # Generate temp file name for the payload contents
148 TempFileName
= os
.path
.join (TempDirectoryName
, 'Payload.bin')
151 # Create temporary payload file for verification
154 File
= open (TempFileName
, mode
='wb')
158 shutil
.rmtree (TempDirectoryName
)
159 raise ValueError ('GenerateCapsule: error: can not write temporary payload file.')
162 # Build openssl command
167 Command
= Command
+ '"{Path}" '.format (Path
= os
.path
.join (ToolPath
, 'openssl'))
168 Command
= Command
+ 'smime -verify -inform DER '
169 Command
= Command
+ '-content {Content} -CAfile "{Public}"'.format (Content
= TempFileName
, Public
= TrustedPublicCertFile
)
175 Process
= subprocess
.Popen (Command
, stdin
= subprocess
.PIPE
, stdout
= subprocess
.PIPE
, stderr
= subprocess
.PIPE
, shell
= True)
176 Result
= Process
.communicate(input = CertData
)
178 shutil
.rmtree (TempDirectoryName
)
179 raise ValueError ('GenerateCapsule: error: can not run openssl.')
181 if Process
.returncode
!= 0:
182 shutil
.rmtree (TempDirectoryName
)
183 print (Result
[1].decode(encoding
='utf-8', errors
='ignore'))
184 raise ValueError ('GenerateCapsule: error: openssl failed.')
186 shutil
.rmtree (TempDirectoryName
)
189 if __name__
== '__main__':
190 def convert_arg_line_to_args(arg_line
):
191 for arg
in arg_line
.split():
196 def ValidateUnsignedInteger (Argument
):
198 Value
= int (Argument
, 0)
200 Message
= '{Argument} is not a valid integer value.'.format (Argument
= Argument
)
201 raise argparse
.ArgumentTypeError (Message
)
203 Message
= '{Argument} is a negative value.'.format (Argument
= Argument
)
204 raise argparse
.ArgumentTypeError (Message
)
207 def ValidateRegistryFormatGuid (Argument
):
209 Value
= uuid
.UUID (Argument
)
211 Message
= '{Argument} is not a valid registry format GUID value.'.format (Argument
= Argument
)
212 raise argparse
.ArgumentTypeError (Message
)
216 # Create command line argument parser object
218 parser
= argparse
.ArgumentParser (
220 description
= __description__
+ __copyright__
,
221 conflict_handler
= 'resolve',
222 fromfile_prefix_chars
= '@'
224 parser
.convert_arg_line_to_args
= convert_arg_line_to_args
227 # Add input and output file arguments
229 parser
.add_argument("InputFile", type = argparse
.FileType('rb'),
230 help = "Input binary payload filename.")
231 parser
.add_argument("-o", "--output", dest
= 'OutputFile', type = argparse
.FileType('wb'),
232 help = "Output filename.")
234 # Add group for -e and -d flags that are mutually exclusive and required
236 group
= parser
.add_mutually_exclusive_group (required
= True)
237 group
.add_argument ("-e", "--encode", dest
= 'Encode', action
= "store_true",
238 help = "Encode file")
239 group
.add_argument ("-d", "--decode", dest
= 'Decode', action
= "store_true",
240 help = "Decode file")
241 group
.add_argument ("--dump-info", dest
= 'DumpInfo', action
= "store_true",
242 help = "Display FMP Payload Header information")
244 # Add optional arguments for this command
246 parser
.add_argument ("--capflag", dest
= 'CapsuleFlag', action
='append', default
= [],
247 choices
=['PersistAcrossReset', 'InitiateReset'],
248 help = "Capsule flag can be PersistAcrossReset or InitiateReset or not set")
249 parser
.add_argument ("--capoemflag", dest
= 'CapsuleOemFlag', type = ValidateUnsignedInteger
, default
= 0x0000,
250 help = "Capsule OEM Flag is an integer between 0x0000 and 0xffff.")
252 parser
.add_argument ("--guid", dest
= 'Guid', type = ValidateRegistryFormatGuid
,
253 help = "The FMP/ESRT GUID in registry format. Required for encode operations.")
254 parser
.add_argument ("--hardware-instance", dest
= 'HardwareInstance', type = ValidateUnsignedInteger
, default
= 0x0000000000000000,
255 help = "The 64-bit hardware instance. The default is 0x0000000000000000")
258 parser
.add_argument ("--monotonic-count", dest
= 'MonotonicCount', type = ValidateUnsignedInteger
, default
= 0x0000000000000000,
259 help = "64-bit monotonic count value in header. Default is 0x0000000000000000.")
261 parser
.add_argument ("--fw-version", dest
= 'FwVersion', type = ValidateUnsignedInteger
,
262 help = "The 32-bit version of the binary payload (e.g. 0x11223344 or 5678). Required for encode operations that sign a payload.")
263 parser
.add_argument ("--lsv", dest
= 'LowestSupportedVersion', type = ValidateUnsignedInteger
,
264 help = "The 32-bit lowest supported version of the binary payload (e.g. 0x11223344 or 5678). Required for encode operations that sign a payload.")
266 parser
.add_argument ("--pfx-file", dest
='SignToolPfxFile', type=argparse
.FileType('rb'),
267 help="signtool PFX certificate filename.")
269 parser
.add_argument ("--signer-private-cert", dest
='OpenSslSignerPrivateCertFile', type=argparse
.FileType('rb'),
270 help="OpenSSL signer private certificate filename.")
271 parser
.add_argument ("--other-public-cert", dest
='OpenSslOtherPublicCertFile', type=argparse
.FileType('rb'),
272 help="OpenSSL other public certificate filename.")
273 parser
.add_argument ("--trusted-public-cert", dest
='OpenSslTrustedPublicCertFile', type=argparse
.FileType('rb'),
274 help="OpenSSL trusted public certificate filename.")
276 parser
.add_argument ("--signing-tool-path", dest
= 'SigningToolPath',
277 help = "Path to signtool or OpenSSL tool. Optional if path to tools are already in PATH.")
280 # Add optional arguments common to all operations
282 parser
.add_argument ('--version', action
='version', version
='%(prog)s ' + __version__
)
283 parser
.add_argument ("-v", "--verbose", dest
= 'Verbose', action
= "store_true",
284 help = "Turn on verbose output with informational messages printed, including capsule headers and warning messages.")
285 parser
.add_argument ("-q", "--quiet", dest
= 'Quiet', action
= "store_true",
286 help = "Disable all messages except fatal errors.")
287 parser
.add_argument ("--debug", dest
= 'Debug', type = int, metavar
= '[0-9]', choices
= range (0, 10), default
= 0,
288 help = "Set debug level")
291 # Parse command line arguments
293 args
= parser
.parse_args()
296 # Perform additional argument verification
299 if args
.Guid
is None:
300 parser
.error ('the following option is required: --guid')
301 if 'PersistAcrossReset' not in args
.CapsuleFlag
:
302 if 'InitiateReset' in args
.CapsuleFlag
:
303 parser
.error ('--capflag InitiateReset also requires --capflag PersistAcrossReset')
304 if args
.CapsuleOemFlag
> 0xFFFF:
305 parser
.error ('--capoemflag must be an integer between 0x0000 and 0xffff')
306 if args
.HardwareInstance
> 0xFFFFFFFFFFFFFFFF:
307 parser
.error ('--hardware-instance must be an integer in range 0x0..0xffffffffffffffff')
308 if args
.MonotonicCount
> 0xFFFFFFFFFFFFFFFF:
309 parser
.error ('--monotonic-count must be an integer in range 0x0..0xffffffffffffffff')
311 UseSignTool
= args
.SignToolPfxFile
is not None
312 UseOpenSsl
= (args
.OpenSslSignerPrivateCertFile
is not None and
313 args
.OpenSslOtherPublicCertFile
is not None and
314 args
.OpenSslTrustedPublicCertFile
is not None)
315 AnyOpenSsl
= (args
.OpenSslSignerPrivateCertFile
is not None or
316 args
.OpenSslOtherPublicCertFile
is not None or
317 args
.OpenSslTrustedPublicCertFile
is not None)
318 if args
.Encode
or args
.Decode
:
319 if args
.OutputFile
is None:
320 parser
.error ('the following option is required for all encode and decode operations: --output')
322 if UseSignTool
and AnyOpenSsl
:
323 parser
.error ('Providing both signtool and OpenSSL options is not supported')
324 if not UseSignTool
and not UseOpenSsl
and AnyOpenSsl
:
325 parser
.error ('all the following options are required for OpenSSL: --signer-private-cert, --other-public-cert, --trusted-public-cert')
326 if UseSignTool
and platform
.system() != 'Windows':
327 parser
.error ('Use of signtool is not supported on this operating system.')
328 if args
.Encode
and (UseSignTool
or UseOpenSsl
):
329 if args
.FwVersion
is None or args
.LowestSupportedVersion
is None:
330 parser
.error ('the following options are required: --fw-version, --lsv')
331 if args
.FwVersion
> 0xFFFFFFFF:
332 parser
.error ('--fw-version must be an integer in range 0x0..0xffffffff')
333 if args
.LowestSupportedVersion
> 0xFFFFFFFF:
334 parser
.error ('--lsv must be an integer in range 0x0..0xffffffff')
337 args
.SignToolPfxFile
.close()
338 args
.SignToolPfxFile
= args
.SignToolPfxFile
.name
340 args
.OpenSslSignerPrivateCertFile
.close()
341 args
.OpenSslOtherPublicCertFile
.close()
342 args
.OpenSslTrustedPublicCertFile
.close()
343 args
.OpenSslSignerPrivateCertFile
= args
.OpenSslSignerPrivateCertFile
.name
344 args
.OpenSslOtherPublicCertFile
= args
.OpenSslOtherPublicCertFile
.name
345 args
.OpenSslTrustedPublicCertFile
= args
.OpenSslTrustedPublicCertFile
.name
348 if args
.OutputFile
is not None:
349 parser
.error ('the following option is not supported for dumpinfo operations: --output')
352 # Read binary input file
356 print ('Read binary input file {File}'.format (File
= args
.InputFile
.name
))
357 Buffer
= args
.InputFile
.read ()
358 args
.InputFile
.close ()
360 print ('GenerateCapsule: error: can not read binary input file {File}'.format (File
= args
.InputFile
.name
))
366 UefiCapsuleHeader
= UefiCapsuleHeaderClass ()
367 FmpCapsuleHeader
= FmpCapsuleHeaderClass ()
368 FmpAuthHeader
= FmpAuthHeaderClass ()
369 FmpPayloadHeader
= FmpPayloadHeaderClass ()
373 if UseSignTool
or UseOpenSsl
:
375 FmpPayloadHeader
.FwVersion
= args
.FwVersion
376 FmpPayloadHeader
.LowestSupportedVersion
= args
.LowestSupportedVersion
377 FmpPayloadHeader
.Payload
= Result
378 Result
= FmpPayloadHeader
.Encode ()
380 FmpPayloadHeader
.DumpInfo ()
382 print ('GenerateCapsule: error: can not encode FMP Payload Header')
386 # Sign image with 64-bit MonotonicCount appended to end of image
390 CertData
= SignPayloadSignTool (
391 Result
+ struct
.pack ('<Q', args
.MonotonicCount
),
392 args
.SigningToolPath
,
396 CertData
= SignPayloadOpenSsl (
397 Result
+ struct
.pack ('<Q', args
.MonotonicCount
),
398 args
.SigningToolPath
,
399 args
.OpenSslSignerPrivateCertFile
,
400 args
.OpenSslOtherPublicCertFile
,
401 args
.OpenSslTrustedPublicCertFile
404 print ('GenerateCapsule: error: can not sign payload')
408 FmpAuthHeader
.MonotonicCount
= args
.MonotonicCount
409 FmpAuthHeader
.CertData
= CertData
410 FmpAuthHeader
.Payload
= Result
411 Result
= FmpAuthHeader
.Encode ()
413 FmpAuthHeader
.DumpInfo ()
415 print ('GenerateCapsule: error: can not encode FMP Auth Header')
419 FmpCapsuleHeader
.AddPayload (args
.Guid
, Result
, HardwareInstance
= args
.HardwareInstance
)
420 Result
= FmpCapsuleHeader
.Encode ()
422 FmpCapsuleHeader
.DumpInfo ()
424 print ('GenerateCapsule: error: can not encode FMP Capsule Header')
428 UefiCapsuleHeader
.OemFlags
= args
.CapsuleOemFlag
429 UefiCapsuleHeader
.PersistAcrossReset
= 'PersistAcrossReset' in args
.CapsuleFlag
430 UefiCapsuleHeader
.PopulateSystemTable
= False
431 UefiCapsuleHeader
.InitiateReset
= 'InitiateReset' in args
.CapsuleFlag
432 UefiCapsuleHeader
.Payload
= Result
433 Result
= UefiCapsuleHeader
.Encode ()
435 UefiCapsuleHeader
.DumpInfo ()
437 print ('GenerateCapsule: error: can not encode UEFI Capsule Header')
442 Result
= UefiCapsuleHeader
.Decode (Buffer
)
443 FmpCapsuleHeader
.Decode (Result
)
444 Result
= FmpCapsuleHeader
.GetFmpCapsuleImageHeader (0).Payload
447 UefiCapsuleHeader
.DumpInfo ()
449 FmpCapsuleHeader
.DumpInfo ()
450 if UseSignTool
or UseOpenSsl
:
451 Result
= FmpAuthHeader
.Decode (Result
)
454 FmpAuthHeader
.DumpInfo ()
457 # Verify Image with 64-bit MonotonicCount appended to end of image
461 CertData
= VerifyPayloadSignTool (
462 FmpAuthHeader
.Payload
+ struct
.pack ('<Q', FmpAuthHeader
.MonotonicCount
),
463 FmpAuthHeader
.CertData
,
464 args
.SigningToolPath
,
468 CertData
= VerifyPayloadOpenSsl (
469 FmpAuthHeader
.Payload
+ struct
.pack ('<Q', FmpAuthHeader
.MonotonicCount
),
470 FmpAuthHeader
.CertData
,
471 args
.SigningToolPath
,
472 args
.OpenSslSignerPrivateCertFile
,
473 args
.OpenSslOtherPublicCertFile
,
474 args
.OpenSslTrustedPublicCertFile
477 print ('GenerateCapsule: warning: can not verify payload.')
480 Result
= FmpPayloadHeader
.Decode (Result
)
483 FmpPayloadHeader
.DumpInfo ()
488 print ('No FMP_PAYLOAD_HEADER')
494 print ('No EFI_FIRMWARE_IMAGE_AUTHENTICATION')
496 print ('No FMP_PAYLOAD_HEADER')
499 print ('GenerateCapsule: error: can not decode capsule')
504 Result
= UefiCapsuleHeader
.Decode (Buffer
)
505 FmpCapsuleHeader
.Decode (Result
)
506 Result
= FmpCapsuleHeader
.GetFmpCapsuleImageHeader (0).Payload
508 UefiCapsuleHeader
.DumpInfo ()
510 FmpCapsuleHeader
.DumpInfo ()
512 Result
= FmpAuthHeader
.Decode (Result
)
514 FmpAuthHeader
.DumpInfo ()
516 Result
= FmpPayloadHeader
.Decode (Result
)
518 FmpPayloadHeader
.DumpInfo ()
521 print ('No FMP_PAYLOAD_HEADER')
524 print ('No EFI_FIRMWARE_IMAGE_AUTHENTICATION')
526 print ('No FMP_PAYLOAD_HEADER')
529 print ('GenerateCapsule: error: can not decode capsule')
532 print('GenerateCapsule: error: invalid options')
536 # Write binary output file
538 if args
.OutputFile
is not None:
541 print ('Write binary output file {File}'.format (File
= args
.OutputFile
.name
))
542 args
.OutputFile
.write (Result
)
543 args
.OutputFile
.close ()
545 print ('GenerateCapsule: error: can not write binary output file {File}'.format (File
= args
.OutputFile
.name
))