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 # This program and the accompanying materials
17 # are licensed and made available under the terms and conditions of the BSD License
18 # which accompanies this distribution. The full text of the license may be found at
19 # http://opensource.org/licenses/bsd-license.php
21 # THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,
22 # WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.
38 from Common
.Uefi
.Capsule
.UefiCapsuleHeader
import UefiCapsuleHeaderClass
39 from Common
.Uefi
.Capsule
.FmpCapsuleHeader
import FmpCapsuleHeaderClass
40 from Common
.Uefi
.Capsule
.FmpAuthHeader
import FmpAuthHeaderClass
41 from Common
.Edk2
.Capsule
.FmpPayloadHeader
import FmpPayloadHeaderClass
44 # Globals for help information
46 __prog__
= 'GenerateCapsule'
48 __copyright__
= 'Copyright (c) 2018, Intel Corporation. All rights reserved.'
49 __description__
= 'Generate a capsule.\n'
51 def SignPayloadSignTool (Payload
, ToolPath
, PfxFile
):
53 # Create a temporary directory
55 TempDirectoryName
= tempfile
.mkdtemp()
58 # Generate temp file name for the payload contents
60 TempFileName
= os
.path
.join (TempDirectoryName
, 'Payload.bin')
63 # Create temporary payload file for signing
66 File
= open (TempFileName
, mode
='wb')
70 shutil
.rmtree (TempDirectoryName
)
71 raise ValueError ('GenerateCapsule: error: can not write temporary payload file.')
74 # Build signtool command
79 Command
= Command
+ '"{Path}" '.format (Path
= os
.path
.join (ToolPath
, 'signtool.exe'))
80 Command
= Command
+ 'sign /fd sha256 /p7ce DetachedSignedData /p7co 1.2.840.113549.1.7.2 '
81 Command
= Command
+ '/p7 {TempDir} '.format (TempDir
= TempDirectoryName
)
82 Command
= Command
+ '/f {PfxFile} '.format (PfxFile
= PfxFile
)
83 Command
= Command
+ TempFileName
86 # Sign the input file using the specified private key
89 Process
= subprocess
.Popen (Command
, stdin
= subprocess
.PIPE
, stdout
= subprocess
.PIPE
, stderr
= subprocess
.PIPE
, shell
= True)
90 Result
= Process
.communicate('')
92 shutil
.rmtree (TempDirectoryName
)
93 raise ValueError ('GenerateCapsule: error: can not run signtool.')
95 if Process
.returncode
!= 0:
96 shutil
.rmtree (TempDirectoryName
)
97 print (Result
[1].decode())
98 raise ValueError ('GenerateCapsule: error: signtool failed.')
101 # Read the signature from the generated output file
104 File
= open (TempFileName
+ '.p7', mode
='rb')
105 Signature
= File
.read ()
108 shutil
.rmtree (TempDirectoryName
)
109 raise ValueError ('GenerateCapsule: error: can not read signature file.')
111 shutil
.rmtree (TempDirectoryName
)
114 def VerifyPayloadSignTool (Payload
, CertData
, ToolPath
, PfxFile
):
115 print ('signtool verify is not supported.')
116 raise ValueError ('GenerateCapsule: error: signtool verify is not supported.')
118 def SignPayloadOpenSsl (Payload
, ToolPath
, SignerPrivateCertFile
, OtherPublicCertFile
, TrustedPublicCertFile
):
120 # Build openssl command
125 Command
= Command
+ '"{Path}" '.format (Path
= os
.path
.join (ToolPath
, 'openssl'))
126 Command
= Command
+ 'smime -sign -binary -outform DER -md sha256 '
127 Command
= Command
+ '-signer "{Private}" -certfile "{Public}"'.format (Private
= SignerPrivateCertFile
, Public
= OtherPublicCertFile
)
130 # Sign the input file using the specified private key and capture signature from STDOUT
133 Process
= subprocess
.Popen (Command
, stdin
= subprocess
.PIPE
, stdout
= subprocess
.PIPE
, stderr
= subprocess
.PIPE
, shell
= True)
134 Result
= Process
.communicate(input = Payload
)
135 Signature
= Result
[0]
137 raise ValueError ('GenerateCapsule: error: can not run openssl.')
139 if Process
.returncode
!= 0:
140 print (Result
[1].decode())
141 raise ValueError ('GenerateCapsule: error: openssl failed.')
145 def VerifyPayloadOpenSsl (Payload
, CertData
, ToolPath
, SignerPrivateCertFile
, OtherPublicCertFile
, TrustedPublicCertFile
):
147 # Create a temporary directory
149 TempDirectoryName
= tempfile
.mkdtemp()
152 # Generate temp file name for the payload contents
154 TempFileName
= os
.path
.join (TempDirectoryName
, 'Payload.bin')
157 # Create temporary payload file for verification
160 File
= open (TempFileName
, mode
='wb')
164 shutil
.rmtree (TempDirectoryName
)
165 raise ValueError ('GenerateCapsule: error: can not write temporary payload file.')
168 # Build openssl command
173 Command
= Command
+ '"{Path}" '.format (Path
= os
.path
.join (ToolPath
, 'openssl'))
174 Command
= Command
+ 'smime -verify -inform DER '
175 Command
= Command
+ '-content {Content} -CAfile "{Public}"'.format (Content
= TempFileName
, Public
= TrustedPublicCertFile
)
181 Process
= subprocess
.Popen (Command
, stdin
= subprocess
.PIPE
, stdout
= subprocess
.PIPE
, stderr
= subprocess
.PIPE
, shell
= True)
182 Result
= Process
.communicate(input = CertData
)
184 shutil
.rmtree (TempDirectoryName
)
185 raise ValueError ('GenerateCapsule: error: can not run openssl.')
187 if Process
.returncode
!= 0:
188 shutil
.rmtree (TempDirectoryName
)
189 print (Result
[1].decode())
190 raise ValueError ('GenerateCapsule: error: openssl failed.')
192 shutil
.rmtree (TempDirectoryName
)
195 if __name__
== '__main__':
196 def convert_arg_line_to_args(arg_line
):
197 for arg
in arg_line
.split():
202 def ValidateUnsignedInteger (Argument
):
204 Value
= int (Argument
, 0)
206 Message
= '{Argument} is not a valid integer value.'.format (Argument
= Argument
)
207 raise argparse
.ArgumentTypeError (Message
)
209 Message
= '{Argument} is a negative value.'.format (Argument
= Argument
)
210 raise argparse
.ArgumentTypeError (Message
)
213 def ValidateRegistryFormatGuid (Argument
):
215 Value
= uuid
.UUID (Argument
)
217 Message
= '{Argument} is not a valid registry format GUID value.'.format (Argument
= Argument
)
218 raise argparse
.ArgumentTypeError (Message
)
222 # Create command line argument parser object
224 parser
= argparse
.ArgumentParser (
226 description
= __description__
+ __copyright__
,
227 conflict_handler
= 'resolve',
228 fromfile_prefix_chars
= '@'
230 parser
.convert_arg_line_to_args
= convert_arg_line_to_args
233 # Add input and output file arguments
235 parser
.add_argument("InputFile", type = argparse
.FileType('rb'),
236 help = "Input binary payload filename.")
237 parser
.add_argument("-o", "--output", dest
= 'OutputFile', type = argparse
.FileType('wb'),
238 help = "Output filename.")
240 # Add group for -e and -d flags that are mutually exclusive and required
242 group
= parser
.add_mutually_exclusive_group (required
= True)
243 group
.add_argument ("-e", "--encode", dest
= 'Encode', action
= "store_true",
244 help = "Encode file")
245 group
.add_argument ("-d", "--decode", dest
= 'Decode', action
= "store_true",
246 help = "Decode file")
247 group
.add_argument ("--dump-info", dest
= 'DumpInfo', action
= "store_true",
248 help = "Display FMP Payload Header information")
250 # Add optional arguments for this command
252 parser
.add_argument ("--capflag", dest
= 'CapsuleFlag', action
='append', default
= [],
253 choices
=['PersistAcrossReset', 'InitiateReset'],
254 help = "Capsule flag can be PersistAcrossReset or InitiateReset or not set")
255 parser
.add_argument ("--capoemflag", dest
= 'CapsuleOemFlag', type = ValidateUnsignedInteger
, default
= 0x0000,
256 help = "Capsule OEM Flag is an integer between 0x0000 and 0xffff.")
258 parser
.add_argument ("--guid", dest
= 'Guid', type = ValidateRegistryFormatGuid
,
259 help = "The FMP/ESRT GUID in registry format. Required for encode operations.")
260 parser
.add_argument ("--hardware-instance", dest
= 'HardwareInstance', type = ValidateUnsignedInteger
, default
= 0x0000000000000000,
261 help = "The 64-bit hardware instance. The default is 0x0000000000000000")
264 parser
.add_argument ("--monotonic-count", dest
= 'MonotonicCount', type = ValidateUnsignedInteger
, default
= 0x0000000000000000,
265 help = "64-bit monotonic count value in header. Default is 0x0000000000000000.")
267 parser
.add_argument ("--fw-version", dest
= 'FwVersion', type = ValidateUnsignedInteger
,
268 help = "The 32-bit version of the binary payload (e.g. 0x11223344 or 5678). Required for encode operations that sign a payload.")
269 parser
.add_argument ("--lsv", dest
= 'LowestSupportedVersion', type = ValidateUnsignedInteger
,
270 help = "The 32-bit lowest supported version of the binary payload (e.g. 0x11223344 or 5678). Required for encode operations that sign a payload.")
272 parser
.add_argument ("--pfx-file", dest
='SignToolPfxFile', type=argparse
.FileType('rb'),
273 help="signtool PFX certificate filename.")
275 parser
.add_argument ("--signer-private-cert", dest
='OpenSslSignerPrivateCertFile', type=argparse
.FileType('rb'),
276 help="OpenSSL signer private certificate filename.")
277 parser
.add_argument ("--other-public-cert", dest
='OpenSslOtherPublicCertFile', type=argparse
.FileType('rb'),
278 help="OpenSSL other public certificate filename.")
279 parser
.add_argument ("--trusted-public-cert", dest
='OpenSslTrustedPublicCertFile', type=argparse
.FileType('rb'),
280 help="OpenSSL trusted public certificate filename.")
282 parser
.add_argument ("--signing-tool-path", dest
= 'SigningToolPath',
283 help = "Path to signtool or OpenSSL tool. Optional if path to tools are already in PATH.")
286 # Add optional arguments common to all operations
288 parser
.add_argument ('--version', action
='version', version
='%(prog)s ' + __version__
)
289 parser
.add_argument ("-v", "--verbose", dest
= 'Verbose', action
= "store_true",
290 help = "Turn on verbose output with informational messages printed, including capsule headers and warning messages.")
291 parser
.add_argument ("-q", "--quiet", dest
= 'Quiet', action
= "store_true",
292 help = "Disable all messages except fatal errors.")
293 parser
.add_argument ("--debug", dest
= 'Debug', type = int, metavar
= '[0-9]', choices
= range (0, 10), default
= 0,
294 help = "Set debug level")
297 # Parse command line arguments
299 args
= parser
.parse_args()
302 # Perform additional argument verification
305 if args
.Guid
is None:
306 parser
.error ('the following option is required: --guid')
307 if 'PersistAcrossReset' not in args
.CapsuleFlag
:
308 if 'InitiateReset' in args
.CapsuleFlag
:
309 parser
.error ('--capflag InitiateReset also requires --capflag PersistAcrossReset')
310 if args
.CapsuleOemFlag
> 0xFFFF:
311 parser
.error ('--capoemflag must be an integer between 0x0000 and 0xffff')
312 if args
.HardwareInstance
> 0xFFFFFFFFFFFFFFFF:
313 parser
.error ('--hardware-instance must be an integer in range 0x0..0xffffffffffffffff')
314 if args
.MonotonicCount
> 0xFFFFFFFFFFFFFFFF:
315 parser
.error ('--monotonic-count must be an integer in range 0x0..0xffffffffffffffff')
317 UseSignTool
= args
.SignToolPfxFile
is not None
318 UseOpenSsl
= (args
.OpenSslSignerPrivateCertFile
is not None and
319 args
.OpenSslOtherPublicCertFile
is not None and
320 args
.OpenSslTrustedPublicCertFile
is not None)
321 AnyOpenSsl
= (args
.OpenSslSignerPrivateCertFile
is not None or
322 args
.OpenSslOtherPublicCertFile
is not None or
323 args
.OpenSslTrustedPublicCertFile
is not None)
324 if args
.Encode
or args
.Decode
:
325 if args
.OutputFile
is None:
326 parser
.error ('the following option is required for all encode and decode operations: --output')
328 if UseSignTool
and AnyOpenSsl
:
329 parser
.error ('Providing both signtool and OpenSSL options is not supported')
330 if not UseSignTool
and not UseOpenSsl
and AnyOpenSsl
:
331 parser
.error ('all the following options are required for OpenSSL: --signer-private-cert, --other-public-cert, --trusted-public-cert')
332 if UseSignTool
and platform
.system() != 'Windows':
333 parser
.error ('Use of signtool is not supported on this operating system.')
334 if args
.Encode
and (UseSignTool
or UseOpenSsl
):
335 if args
.FwVersion
is None or args
.LowestSupportedVersion
is None:
336 parser
.error ('the following options are required: --fw-version, --lsv')
337 if args
.FwVersion
> 0xFFFFFFFF:
338 parser
.error ('--fw-version must be an integer in range 0x0..0xffffffff')
339 if args
.LowestSupportedVersion
> 0xFFFFFFFF:
340 parser
.error ('--lsv must be an integer in range 0x0..0xffffffff')
343 args
.SignToolPfxFile
.close()
344 args
.SignToolPfxFile
= args
.SignToolPfxFile
.name
346 args
.OpenSslSignerPrivateCertFile
.close()
347 args
.OpenSslOtherPublicCertFile
.close()
348 args
.OpenSslTrustedPublicCertFile
.close()
349 args
.OpenSslSignerPrivateCertFile
= args
.OpenSslSignerPrivateCertFile
.name
350 args
.OpenSslOtherPublicCertFile
= args
.OpenSslOtherPublicCertFile
.name
351 args
.OpenSslTrustedPublicCertFile
= args
.OpenSslTrustedPublicCertFile
.name
354 if args
.OutputFile
is not None:
355 parser
.error ('the following option is not supported for dumpinfo operations: --output')
358 # Read binary input file
362 print ('Read binary input file {File}'.format (File
= args
.InputFile
.name
))
363 Buffer
= args
.InputFile
.read ()
364 args
.InputFile
.close ()
366 print ('GenerateCapsule: error: can not read binary input file {File}'.format (File
= args
.InputFile
.name
))
372 UefiCapsuleHeader
= UefiCapsuleHeaderClass ()
373 FmpCapsuleHeader
= FmpCapsuleHeaderClass ()
374 FmpAuthHeader
= FmpAuthHeaderClass ()
375 FmpPayloadHeader
= FmpPayloadHeaderClass ()
379 if UseSignTool
or UseOpenSsl
:
381 FmpPayloadHeader
.FwVersion
= args
.FwVersion
382 FmpPayloadHeader
.LowestSupportedVersion
= args
.LowestSupportedVersion
383 FmpPayloadHeader
.Payload
= Result
384 Result
= FmpPayloadHeader
.Encode ()
386 FmpPayloadHeader
.DumpInfo ()
388 print ('GenerateCapsule: error: can not encode FMP Payload Header')
392 # Sign image with 64-bit MonotonicCount appended to end of image
396 CertData
= SignPayloadSignTool (
397 Result
+ struct
.pack ('<Q', args
.MonotonicCount
),
398 args
.SigningToolPath
,
402 CertData
= SignPayloadOpenSsl (
403 Result
+ struct
.pack ('<Q', args
.MonotonicCount
),
404 args
.SigningToolPath
,
405 args
.OpenSslSignerPrivateCertFile
,
406 args
.OpenSslOtherPublicCertFile
,
407 args
.OpenSslTrustedPublicCertFile
410 print ('GenerateCapsule: error: can not sign payload')
414 FmpAuthHeader
.MonotonicCount
= args
.MonotonicCount
415 FmpAuthHeader
.CertData
= CertData
416 FmpAuthHeader
.Payload
= Result
417 Result
= FmpAuthHeader
.Encode ()
419 FmpAuthHeader
.DumpInfo ()
421 print ('GenerateCapsule: error: can not encode FMP Auth Header')
425 FmpCapsuleHeader
.AddPayload (args
.Guid
, Result
, HardwareInstance
= args
.HardwareInstance
)
426 Result
= FmpCapsuleHeader
.Encode ()
428 FmpCapsuleHeader
.DumpInfo ()
430 print ('GenerateCapsule: error: can not encode FMP Capsule Header')
434 UefiCapsuleHeader
.OemFlags
= args
.CapsuleOemFlag
435 UefiCapsuleHeader
.PersistAcrossReset
= 'PersistAcrossReset' in args
.CapsuleFlag
436 UefiCapsuleHeader
.PopulateSystemTable
= False
437 UefiCapsuleHeader
.InitiateReset
= 'InitiateReset' in args
.CapsuleFlag
438 UefiCapsuleHeader
.Payload
= Result
439 Result
= UefiCapsuleHeader
.Encode ()
441 UefiCapsuleHeader
.DumpInfo ()
443 print ('GenerateCapsule: error: can not encode UEFI Capsule Header')
448 Result
= UefiCapsuleHeader
.Decode (Buffer
)
449 FmpCapsuleHeader
.Decode (Result
)
450 Result
= FmpCapsuleHeader
.GetFmpCapsuleImageHeader (0).Payload
453 UefiCapsuleHeader
.DumpInfo ()
455 FmpCapsuleHeader
.DumpInfo ()
456 if UseSignTool
or UseOpenSsl
:
457 Result
= FmpAuthHeader
.Decode (Result
)
460 # Verify Image with 64-bit MonotonicCount appended to end of image
464 CertData
= VerifyPayloadSignTool (
465 FmpAuthHeader
.Payload
+ struct
.pack ('<Q', FmpAuthHeader
.MonotonicCount
),
466 FmpAuthHeader
.CertData
,
467 args
.SigningToolPath
,
471 CertData
= VerifyPayloadOpenSsl (
472 FmpAuthHeader
.Payload
+ struct
.pack ('<Q', FmpAuthHeader
.MonotonicCount
),
473 FmpAuthHeader
.CertData
,
474 args
.SigningToolPath
,
475 args
.OpenSslSignerPrivateCertFile
,
476 args
.OpenSslOtherPublicCertFile
,
477 args
.OpenSslTrustedPublicCertFile
480 print ('GenerateCapsule: warning: can not verify payload.')
482 Result
= FmpPayloadHeader
.Decode (Result
)
485 FmpAuthHeader
.DumpInfo ()
487 FmpPayloadHeader
.DumpInfo ()
491 print ('No EFI_FIRMWARE_IMAGE_AUTHENTICATION')
493 print ('No FMP_PAYLOAD_HEADER')
497 print ('GenerateCapsule: error: can not decode capsule')
502 Result
= UefiCapsuleHeader
.Decode (Buffer
)
503 FmpCapsuleHeader
.Decode (Result
)
504 Result
= FmpCapsuleHeader
.GetFmpCapsuleImageHeader (0).Payload
506 UefiCapsuleHeader
.DumpInfo ()
508 FmpCapsuleHeader
.DumpInfo ()
510 Result
= FmpAuthHeader
.Decode (Result
)
511 Result
= FmpPayloadHeader
.Decode (Result
)
513 FmpAuthHeader
.DumpInfo ()
515 FmpPayloadHeader
.DumpInfo ()
518 print ('No EFI_FIRMWARE_IMAGE_AUTHENTICATION')
520 print ('No FMP_PAYLOAD_HEADER')
523 print ('GenerateCapsule: error: can not decode capsule')
526 print('GenerateCapsule: error: invalid options')
530 # Write binary output file
532 if args
.OutputFile
is not None:
535 print ('Write binary output file {File}'.format (File
= args
.OutputFile
.name
))
536 args
.OutputFile
.write (Result
)
537 args
.OutputFile
.close ()
539 print ('GenerateCapsule: error: can not write binary output file {File}'.format (File
= args
.OutputFile
.name
))