4 # Copyright (c) 2018, Intel Corporation. All rights reserved.<BR>
5 # This program and the accompanying materials
6 # are licensed and made available under the terms and conditions of the BSD License
7 # which accompanies this distribution. The full text of the license may be found at
8 # http://opensource.org/licenses/bsd-license.php
10 # THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.
27 from Common
.Uefi
.Capsule
.UefiCapsuleHeader
import UefiCapsuleHeaderClass
28 from Common
.Uefi
.Capsule
.FmpCapsuleHeader
import FmpCapsuleHeaderClass
29 from Common
.Uefi
.Capsule
.FmpAuthHeader
import FmpAuthHeaderClass
30 from Common
.Edk2
.Capsule
.FmpPayloadHeader
import FmpPayloadHeaderClass
33 # Globals for help information
35 __prog__
= 'GenerateCapsule'
37 __copyright__
= 'Copyright (c) 2018, Intel Corporation. All rights reserved.'
38 __description__
= 'Generate a capsule.\n'
40 def SignPayloadSignTool (Payload
, ToolPath
, PfxFile
):
42 # Create a temporary directory
44 TempDirectoryName
= tempfile
.mkdtemp()
47 # Generate temp file name for the payload contents
49 TempFileName
= os
.path
.join (TempDirectoryName
, 'Payload.bin')
52 # Create temporary payload file for signing
55 File
= open (TempFileName
, mode
='wb')
59 shutil
.rmtree (TempDirectoryName
)
60 raise ValueError ('GenerateCapsule: error: can not write temporary payload file.')
63 # Build signtool command
68 Command
= Command
+ '"{Path}" '.format (Path
= os
.path
.join (ToolPath
, 'signtool.exe'))
69 Command
= Command
+ 'sign /fd sha256 /p7ce DetachedSignedData /p7co 1.2.840.113549.1.7.2 '
70 Command
= Command
+ '/p7 {TempDir} '.format (TempDir
= TempDirectoryName
)
71 Command
= Command
+ '/f {PfxFile} '.format (PfxFile
= PfxFile
)
72 Command
= Command
+ TempFileName
75 # Sign the input file using the specified private key
78 Process
= subprocess
.Popen (Command
, stdin
= subprocess
.PIPE
, stdout
= subprocess
.PIPE
, stderr
= subprocess
.PIPE
, shell
= True)
79 Result
= Process
.communicate('')
81 shutil
.rmtree (TempDirectoryName
)
82 raise ValueError ('GenerateCapsule: error: can not run signtool.')
84 if Process
.returncode
!= 0:
85 shutil
.rmtree (TempDirectoryName
)
86 print (Result
[1].decode())
87 raise ValueError ('GenerateCapsule: error: signtool failed.')
90 # Read the signature from the generated output file
93 File
= open (TempFileName
+ '.p7', mode
='rb')
94 Signature
= File
.read ()
97 shutil
.rmtree (TempDirectoryName
)
98 raise ValueError ('GenerateCapsule: error: can not read signature file.')
100 shutil
.rmtree (TempDirectoryName
)
103 def VerifyPayloadSignTool (Payload
, CertData
, ToolPath
, PfxFile
):
104 print ('signtool verify is not supported.')
105 raise ValueError ('GenerateCapsule: error: signtool verify is not supported.')
107 def SignPayloadOpenSsl (Payload
, ToolPath
, SignerPrivateCertFile
, OtherPublicCertFile
, TrustedPublicCertFile
):
109 # Build openssl command
114 Command
= Command
+ '"{Path}" '.format (Path
= os
.path
.join (ToolPath
, 'openssl'))
115 Command
= Command
+ 'smime -sign -binary -outform DER -md sha256 '
116 Command
= Command
+ '-signer "{Private}" -certfile "{Public}"'.format (Private
= SignerPrivateCertFile
, Public
= OtherPublicCertFile
)
119 # Sign the input file using the specified private key and capture signature from STDOUT
122 Process
= subprocess
.Popen (Command
, stdin
= subprocess
.PIPE
, stdout
= subprocess
.PIPE
, stderr
= subprocess
.PIPE
, shell
= True)
123 Result
= Process
.communicate(input = Payload
)
124 Signature
= Result
[0]
126 raise ValueError ('GenerateCapsule: error: can not run openssl.')
128 if Process
.returncode
!= 0:
129 print (Result
[1].decode())
130 raise ValueError ('GenerateCapsule: error: openssl failed.')
134 def VerifyPayloadOpenSsl (Payload
, CertData
, ToolPath
, SignerPrivateCertFile
, OtherPublicCertFile
, TrustedPublicCertFile
):
136 # Create a temporary directory
138 TempDirectoryName
= tempfile
.mkdtemp()
141 # Generate temp file name for the payload contents
143 TempFileName
= os
.path
.join (TempDirectoryName
, 'Payload.bin')
146 # Create temporary payload file for verification
149 File
= open (TempFileName
, mode
='wb')
153 shutil
.rmtree (TempDirectoryName
)
154 raise ValueError ('GenerateCapsule: error: can not write temporary payload file.')
157 # Build openssl command
162 Command
= Command
+ '"{Path}" '.format (Path
= os
.path
.join (ToolPath
, 'openssl'))
163 Command
= Command
+ 'smime -verify -inform DER '
164 Command
= Command
+ '-content {Content} -CAfile "{Public}"'.format (Content
= TempFileName
, Public
= TrustedPublicCertFile
)
170 Process
= subprocess
.Popen (Command
, stdin
= subprocess
.PIPE
, stdout
= subprocess
.PIPE
, stderr
= subprocess
.PIPE
, shell
= True)
171 Result
= Process
.communicate(input = CertData
)
173 shutil
.rmtree (TempDirectoryName
)
174 raise ValueError ('GenerateCapsule: error: can not run openssl.')
176 if Process
.returncode
!= 0:
177 shutil
.rmtree (TempDirectoryName
)
178 print (Result
[1].decode())
179 raise ValueError ('GenerateCapsule: error: openssl failed.')
181 shutil
.rmtree (TempDirectoryName
)
184 if __name__
== '__main__':
185 def convert_arg_line_to_args(arg_line
):
186 for arg
in arg_line
.split():
191 def ValidateUnsignedInteger (Argument
):
193 Value
= int (Argument
, 0)
195 Message
= '{Argument} is not a valid integer value.'.format (Argument
= Argument
)
196 raise argparse
.ArgumentTypeError (Message
)
198 Message
= '{Argument} is a negative value.'.format (Argument
= Argument
)
199 raise argparse
.ArgumentTypeError (Message
)
202 def ValidateRegistryFormatGuid (Argument
):
204 Value
= uuid
.UUID (Argument
)
206 Message
= '{Argument} is not a valid registry format GUID value.'.format (Argument
= Argument
)
207 raise argparse
.ArgumentTypeError (Message
)
211 # Create command line argument parser object
213 parser
= argparse
.ArgumentParser (
215 description
= __description__
+ __copyright__
,
216 conflict_handler
= 'resolve',
217 fromfile_prefix_chars
= '@'
219 parser
.convert_arg_line_to_args
= convert_arg_line_to_args
222 # Add input and output file arguments
224 parser
.add_argument("InputFile", type = argparse
.FileType('rb'),
225 help = "Input binary payload filename.")
226 parser
.add_argument("-o", "--output", dest
= 'OutputFile', type = argparse
.FileType('wb'),
227 help = "Output filename.")
229 # Add group for -e and -d flags that are mutually exclusive and required
231 group
= parser
.add_mutually_exclusive_group (required
= True)
232 group
.add_argument ("-e", "--encode", dest
= 'Encode', action
= "store_true",
233 help = "Encode file")
234 group
.add_argument ("-d", "--decode", dest
= 'Decode', action
= "store_true",
235 help = "Decode file")
236 group
.add_argument ("--dump-info", dest
= 'DumpInfo', action
= "store_true",
237 help = "Display FMP Payload Header information")
239 # Add optional arguments for this command
241 parser
.add_argument ("--capflag", dest
= 'CapsuleFlag', action
='append', default
= [],
242 choices
=['PersistAcrossReset', 'PopulateSystemTable', 'InitiateReset'],
243 help = "Capsule flag can be PersistAcrossReset, or PopulateSystemTable or InitiateReset or not set")
244 parser
.add_argument ("--capoemflag", dest
= 'CapsuleOemFlag', type = ValidateUnsignedInteger
, default
= 0x0000,
245 help = "Capsule OEM Flag is an integer between 0x0000 and 0xffff.")
247 parser
.add_argument ("--guid", dest
= 'Guid', type = ValidateRegistryFormatGuid
,
248 help = "The FMP/ESRT GUID in registry format. Required for encode operations.")
249 parser
.add_argument ("--hardware-instance", dest
= 'HardwareInstance', type = ValidateUnsignedInteger
, default
= 0x0000000000000000,
250 help = "The 64-bit hardware instance. The default is 0x0000000000000000")
253 parser
.add_argument ("--monotonic-count", dest
= 'MonotonicCount', type = ValidateUnsignedInteger
, default
= 0x0000000000000000,
254 help = "64-bit monotonic count value in header. Default is 0x0000000000000000.")
256 parser
.add_argument ("--fw-version", dest
= 'FwVersion', type = ValidateUnsignedInteger
,
257 help = "The 32-bit version of the binary payload (e.g. 0x11223344 or 5678).")
258 parser
.add_argument ("--lsv", dest
= 'LowestSupportedVersion', type = ValidateUnsignedInteger
,
259 help = "The 32-bit lowest supported version of the binary payload (e.g. 0x11223344 or 5678).")
261 parser
.add_argument ("--pfx-file", dest
='SignToolPfxFile', type=argparse
.FileType('rb'),
262 help="signtool PFX certificate filename.")
264 parser
.add_argument ("--signer-private-cert", dest
='OpenSslSignerPrivateCertFile', type=argparse
.FileType('rb'),
265 help="OpenSSL signer private certificate filename.")
266 parser
.add_argument ("--other-public-cert", dest
='OpenSslOtherPublicCertFile', type=argparse
.FileType('rb'),
267 help="OpenSSL other public certificate filename.")
268 parser
.add_argument ("--trusted-public-cert", dest
='OpenSslTrustedPublicCertFile', type=argparse
.FileType('rb'),
269 help="OpenSSL trusted public certificate filename.")
271 parser
.add_argument ("--signing-tool-path", dest
= 'SigningToolPath',
272 help = "Path to signtool or OpenSSL tool. Optional if path to tools are already in PATH.")
275 # Add optional arguments common to all operations
277 parser
.add_argument ('--version', action
='version', version
='%(prog)s ' + __version__
)
278 parser
.add_argument ("-v", "--verbose", dest
= 'Verbose', action
= "store_true",
279 help = "Turn on verbose output with informational messages printed, including capsule headers and warning messages.")
280 parser
.add_argument ("-q", "--quiet", dest
= 'Quiet', action
= "store_true",
281 help = "Disable all messages except fatal errors.")
282 parser
.add_argument ("--debug", dest
= 'Debug', type = int, metavar
= '[0-9]', choices
= range (0, 10), default
= 0,
283 help = "Set debug level")
286 # Parse command line arguments
288 args
= parser
.parse_args()
291 # Perform additional argument verification
294 if args
.Guid
is None:
295 parser
.error ('the following option is required: --guid')
296 if 'PersistAcrossReset' not in args
.CapsuleFlag
:
297 if 'PopulateSystemTable' in args
.CapsuleFlag
:
298 parser
.error ('--capflag PopulateSystemTable also requires --capflag PersistAcrossReset')
299 if 'InitiateReset' in args
.CapsuleFlag
:
300 parser
.error ('--capflag InitiateReset also requires --capflag PersistAcrossReset')
302 UseSignTool
= args
.SignToolPfxFile
is not None
303 UseOpenSsl
= (args
.OpenSslSignerPrivateCertFile
is not None and
304 args
.OpenSslOtherPublicCertFile
is not None and
305 args
.OpenSslTrustedPublicCertFile
is not None)
306 AnyOpenSsl
= (args
.OpenSslSignerPrivateCertFile
is not None or
307 args
.OpenSslOtherPublicCertFile
is not None or
308 args
.OpenSslTrustedPublicCertFile
is not None)
309 if args
.Encode
or args
.Decode
:
310 if args
.OutputFile
is None:
311 parser
.error ('the following option is required for all encode and decode operations: --output')
313 if UseSignTool
and AnyOpenSsl
:
314 parser
.error ('Providing both signtool and OpenSSL options is not supported')
315 if not UseSignTool
and not UseOpenSsl
and AnyOpenSsl
:
316 parser
.error ('all the following options are required for OpenSSL: --signer-private-cert, --other-public-cert, --trusted-public-cert')
317 if UseSignTool
and platform
.system() != 'Windows':
318 parser
.error ('Use of signtool is not supported on this operating system.')
319 if args
.Encode
and (UseSignTool
or UseOpenSsl
):
320 if args
.FwVersion
is None or args
.LowestSupportedVersion
is None:
321 parser
.error ('the following options are required: --fw-version, --lsv')
324 args
.SignToolPfxFile
.close()
325 args
.SignToolPfxFile
= args
.SignToolPfxFile
.name
327 args
.OpenSslSignerPrivateCertFile
.close()
328 args
.OpenSslOtherPublicCertFile
.close()
329 args
.OpenSslTrustedPublicCertFile
.close()
330 args
.OpenSslSignerPrivateCertFile
= args
.OpenSslSignerPrivateCertFile
.name
331 args
.OpenSslOtherPublicCertFile
= args
.OpenSslOtherPublicCertFile
.name
332 args
.OpenSslTrustedPublicCertFile
= args
.OpenSslTrustedPublicCertFile
.name
335 # Read binary input file
339 print ('Read binary input file {File}'.format (File
= args
.InputFile
.name
))
340 Buffer
= args
.InputFile
.read ()
341 args
.InputFile
.close ()
343 print ('GenerateCapsule: error: can not read binary input file {File}'.format (File
= args
.InputFile
.name
))
349 UefiCapsuleHeader
= UefiCapsuleHeaderClass ()
350 FmpCapsuleHeader
= FmpCapsuleHeaderClass ()
351 FmpAuthHeader
= FmpAuthHeaderClass ()
352 FmpPayloadHeader
= FmpPayloadHeaderClass ()
356 if UseSignTool
or UseOpenSsl
:
358 FmpPayloadHeader
.FwVersion
= args
.FwVersion
359 FmpPayloadHeader
.LowestSupportedVersion
= args
.LowestSupportedVersion
360 FmpPayloadHeader
.Payload
= Result
361 Result
= FmpPayloadHeader
.Encode ()
363 FmpPayloadHeader
.DumpInfo ()
365 print ('GenerateCapsule: error: can not encode FMP Payload Header')
369 # Sign image with 64-bit MonotonicCount appended to end of image
373 CertData
= SignPayloadSignTool (
374 Result
+ struct
.pack ('<Q', args
.MonotonicCount
),
375 args
.SigningToolPath
,
379 CertData
= SignPayloadOpenSsl (
380 Result
+ struct
.pack ('<Q', args
.MonotonicCount
),
381 args
.SigningToolPath
,
382 args
.OpenSslSignerPrivateCertFile
,
383 args
.OpenSslOtherPublicCertFile
,
384 args
.OpenSslTrustedPublicCertFile
387 print ('GenerateCapsule: error: can not sign payload')
392 FmpAuthHeader
.MonotonicCount
= args
.MonotonicCount
393 FmpAuthHeader
.CertData
= CertData
394 FmpAuthHeader
.Payload
= Result
395 Result
= FmpAuthHeader
.Encode ()
397 FmpAuthHeader
.DumpInfo ()
399 print ('GenerateCapsule: error: can not encode FMP Auth Header')
403 FmpCapsuleHeader
.AddPayload (args
.Guid
, Result
, HardwareInstance
= args
.HardwareInstance
)
404 Result
= FmpCapsuleHeader
.Encode ()
406 FmpCapsuleHeader
.DumpInfo ()
408 print ('GenerateCapsule: error: can not encode FMP Capsule Header')
412 UefiCapsuleHeader
.OemFlags
= args
.CapsuleOemFlag
413 UefiCapsuleHeader
.PersistAcrossReset
= 'PersistAcrossReset' in args
.CapsuleFlag
414 UefiCapsuleHeader
.PopulateSystemTable
= 'PopulateSystemTable' in args
.CapsuleFlag
415 UefiCapsuleHeader
.InitiateReset
= 'InitiateReset' in args
.CapsuleFlag
416 UefiCapsuleHeader
.Payload
= Result
417 Result
= UefiCapsuleHeader
.Encode ()
419 UefiCapsuleHeader
.DumpInfo ()
421 print ('GenerateCapsule: error: can not encode UEFI Capsule Header')
426 Result
= UefiCapsuleHeader
.Decode (Buffer
)
427 FmpCapsuleHeader
.Decode (Result
)
428 Result
= FmpCapsuleHeader
.GetFmpCapsuleImageHeader (0).Payload
431 UefiCapsuleHeader
.DumpInfo ()
433 FmpCapsuleHeader
.DumpInfo ()
434 if UseSignTool
or UseOpenSsl
:
435 Result
= FmpAuthHeader
.Decode (Result
)
438 # Verify Image with 64-bit MonotonicCount appended to end of image
442 CertData
= VerifyPayloadSignTool (
443 FmpAuthHeader
.Payload
+ struct
.pack ('<Q', FmpAuthHeader
.MonotonicCount
),
444 FmpAuthHeader
.CertData
,
445 args
.SigningToolPath
,
449 CertData
= VerifyPayloadOpenSsl (
450 FmpAuthHeader
.Payload
+ struct
.pack ('<Q', FmpAuthHeader
.MonotonicCount
),
451 FmpAuthHeader
.CertData
,
452 args
.SigningToolPath
,
453 args
.OpenSslSignerPrivateCertFile
,
454 args
.OpenSslOtherPublicCertFile
,
455 args
.OpenSslTrustedPublicCertFile
458 print ('GenerateCapsule: warning: can not verify payload.')
460 Result
= FmpPayloadHeader
.Decode (Result
)
463 FmpAuthHeader
.DumpInfo ()
465 FmpPayloadHeader
.DumpInfo ()
469 print ('No EFI_FIRMWARE_IMAGE_AUTHENTICATION')
471 print ('No FMP_PAYLOAD_HEADER')
475 print ('GenerateCapsule: error: can not decode capsule')
481 Result
= UefiCapsuleHeader
.Decode (Buffer
)
482 FmpCapsuleHeader
.Decode (Result
)
483 Result
= FmpCapsuleHeader
.GetFmpCapsuleImageHeader (0).Payload
485 UefiCapsuleHeader
.DumpInfo ()
487 FmpCapsuleHeader
.DumpInfo ()
489 Result
= FmpAuthHeader
.Decode (Result
)
490 Result
= FmpPayloadHeader
.Decode (Result
)
492 FmpAuthHeader
.DumpInfo ()
494 FmpPayloadHeader
.DumpInfo ()
497 print ('No EFI_FIRMWARE_IMAGE_AUTHENTICATION')
499 print ('No FMP_PAYLOAD_HEADER')
502 print ('GenerateCapsule: error: can not decode capsule')
505 print('GenerateCapsule: error: invalid options')
509 # Write binary output file
511 if args
.OutputFile
is not None:
514 print ('Write binary output file {File}'.format (File
= args
.OutputFile
.name
))
515 args
.OutputFile
.write (Result
)
516 args
.OutputFile
.close ()
518 print ('GenerateCapsule: error: can not write binary output file {File}'.format (File
= args
.OutputFile
.name
))