]> git.proxmox.com Git - mirror_edk2.git/blob - BaseTools/Source/Python/Capsule/GenerateCapsule.py
BaseTools: Replace BSD License with BSD+Patent License
[mirror_edk2.git] / BaseTools / Source / Python / Capsule / GenerateCapsule.py
1 ## @file
2 # Generate a capsule.
3 #
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.
7 #
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.
14 #
15 # Copyright (c) 2018, Intel Corporation. All rights reserved.<BR>
16 # SPDX-License-Identifier: BSD-2-Clause-Patent
17 #
18
19 '''
20 GenerateCapsule
21 '''
22
23 import sys
24 import argparse
25 import uuid
26 import struct
27 import subprocess
28 import os
29 import tempfile
30 import shutil
31 import platform
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
36
37 #
38 # Globals for help information
39 #
40 __prog__ = 'GenerateCapsule'
41 __version__ = '0.9'
42 __copyright__ = 'Copyright (c) 2018, Intel Corporation. All rights reserved.'
43 __description__ = 'Generate a capsule.\n'
44
45 def SignPayloadSignTool (Payload, ToolPath, PfxFile):
46 #
47 # Create a temporary directory
48 #
49 TempDirectoryName = tempfile.mkdtemp()
50
51 #
52 # Generate temp file name for the payload contents
53 #
54 TempFileName = os.path.join (TempDirectoryName, 'Payload.bin')
55
56 #
57 # Create temporary payload file for signing
58 #
59 try:
60 File = open (TempFileName, mode='wb')
61 File.write (Payload)
62 File.close ()
63 except:
64 shutil.rmtree (TempDirectoryName)
65 raise ValueError ('GenerateCapsule: error: can not write temporary payload file.')
66
67 #
68 # Build signtool command
69 #
70 if ToolPath is None:
71 ToolPath = ''
72 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
78
79 #
80 # Sign the input file using the specified private key
81 #
82 try:
83 Process = subprocess.Popen (Command, stdin = subprocess.PIPE, stdout = subprocess.PIPE, stderr = subprocess.PIPE, shell = True)
84 Result = Process.communicate('')
85 except:
86 shutil.rmtree (TempDirectoryName)
87 raise ValueError ('GenerateCapsule: error: can not run signtool.')
88
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.')
93
94 #
95 # Read the signature from the generated output file
96 #
97 try:
98 File = open (TempFileName + '.p7', mode='rb')
99 Signature = File.read ()
100 File.close ()
101 except:
102 shutil.rmtree (TempDirectoryName)
103 raise ValueError ('GenerateCapsule: error: can not read signature file.')
104
105 shutil.rmtree (TempDirectoryName)
106 return Signature
107
108 def VerifyPayloadSignTool (Payload, CertData, ToolPath, PfxFile):
109 print ('signtool verify is not supported.')
110 raise ValueError ('GenerateCapsule: error: signtool verify is not supported.')
111
112 def SignPayloadOpenSsl (Payload, ToolPath, SignerPrivateCertFile, OtherPublicCertFile, TrustedPublicCertFile):
113 #
114 # Build openssl command
115 #
116 if ToolPath is None:
117 ToolPath = ''
118 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)
122
123 #
124 # Sign the input file using the specified private key and capture signature from STDOUT
125 #
126 try:
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')
130 except:
131 raise ValueError ('GenerateCapsule: error: can not run openssl.')
132
133 if Process.returncode != 0:
134 print (Result[1].decode(encoding='utf-8', errors='ignore'))
135 raise ValueError ('GenerateCapsule: error: openssl failed.')
136
137 return Signature
138
139 def VerifyPayloadOpenSsl (Payload, CertData, ToolPath, SignerPrivateCertFile, OtherPublicCertFile, TrustedPublicCertFile):
140 #
141 # Create a temporary directory
142 #
143 TempDirectoryName = tempfile.mkdtemp()
144
145 #
146 # Generate temp file name for the payload contents
147 #
148 TempFileName = os.path.join (TempDirectoryName, 'Payload.bin')
149
150 #
151 # Create temporary payload file for verification
152 #
153 try:
154 File = open (TempFileName, mode='wb')
155 File.write (Payload)
156 File.close ()
157 except:
158 shutil.rmtree (TempDirectoryName)
159 raise ValueError ('GenerateCapsule: error: can not write temporary payload file.')
160
161 #
162 # Build openssl command
163 #
164 if ToolPath is None:
165 ToolPath = ''
166 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)
170
171 #
172 # Verify signature
173 #
174 try:
175 Process = subprocess.Popen (Command, stdin = subprocess.PIPE, stdout = subprocess.PIPE, stderr = subprocess.PIPE, shell = True)
176 Result = Process.communicate(input = CertData)
177 except:
178 shutil.rmtree (TempDirectoryName)
179 raise ValueError ('GenerateCapsule: error: can not run openssl.')
180
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.')
185
186 shutil.rmtree (TempDirectoryName)
187 return Payload
188
189 if __name__ == '__main__':
190 def convert_arg_line_to_args(arg_line):
191 for arg in arg_line.split():
192 if not arg.strip():
193 continue
194 yield arg
195
196 def ValidateUnsignedInteger (Argument):
197 try:
198 Value = int (Argument, 0)
199 except:
200 Message = '{Argument} is not a valid integer value.'.format (Argument = Argument)
201 raise argparse.ArgumentTypeError (Message)
202 if Value < 0:
203 Message = '{Argument} is a negative value.'.format (Argument = Argument)
204 raise argparse.ArgumentTypeError (Message)
205 return Value
206
207 def ValidateRegistryFormatGuid (Argument):
208 try:
209 Value = uuid.UUID (Argument)
210 except:
211 Message = '{Argument} is not a valid registry format GUID value.'.format (Argument = Argument)
212 raise argparse.ArgumentTypeError (Message)
213 return Value
214
215 #
216 # Create command line argument parser object
217 #
218 parser = argparse.ArgumentParser (
219 prog = __prog__,
220 description = __description__ + __copyright__,
221 conflict_handler = 'resolve',
222 fromfile_prefix_chars = '@'
223 )
224 parser.convert_arg_line_to_args = convert_arg_line_to_args
225
226 #
227 # Add input and output file arguments
228 #
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.")
233 #
234 # Add group for -e and -d flags that are mutually exclusive and required
235 #
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")
243 #
244 # Add optional arguments for this command
245 #
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.")
251
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")
256
257
258 parser.add_argument ("--monotonic-count", dest = 'MonotonicCount', type = ValidateUnsignedInteger, default = 0x0000000000000000,
259 help = "64-bit monotonic count value in header. Default is 0x0000000000000000.")
260
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.")
265
266 parser.add_argument ("--pfx-file", dest='SignToolPfxFile', type=argparse.FileType('rb'),
267 help="signtool PFX certificate filename.")
268
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.")
275
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.")
278
279 #
280 # Add optional arguments common to all operations
281 #
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")
289
290 #
291 # Parse command line arguments
292 #
293 args = parser.parse_args()
294
295 #
296 # Perform additional argument verification
297 #
298 if args.Encode:
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')
310
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')
321
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')
335
336 if UseSignTool:
337 args.SignToolPfxFile.close()
338 args.SignToolPfxFile = args.SignToolPfxFile.name
339 if UseOpenSsl:
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
346
347 if args.DumpInfo:
348 if args.OutputFile is not None:
349 parser.error ('the following option is not supported for dumpinfo operations: --output')
350
351 #
352 # Read binary input file
353 #
354 try:
355 if args.Verbose:
356 print ('Read binary input file {File}'.format (File = args.InputFile.name))
357 Buffer = args.InputFile.read ()
358 args.InputFile.close ()
359 except:
360 print ('GenerateCapsule: error: can not read binary input file {File}'.format (File = args.InputFile.name))
361 sys.exit (1)
362
363 #
364 # Create objects
365 #
366 UefiCapsuleHeader = UefiCapsuleHeaderClass ()
367 FmpCapsuleHeader = FmpCapsuleHeaderClass ()
368 FmpAuthHeader = FmpAuthHeaderClass ()
369 FmpPayloadHeader = FmpPayloadHeaderClass ()
370
371 if args.Encode:
372 Result = Buffer
373 if UseSignTool or UseOpenSsl:
374 try:
375 FmpPayloadHeader.FwVersion = args.FwVersion
376 FmpPayloadHeader.LowestSupportedVersion = args.LowestSupportedVersion
377 FmpPayloadHeader.Payload = Result
378 Result = FmpPayloadHeader.Encode ()
379 if args.Verbose:
380 FmpPayloadHeader.DumpInfo ()
381 except:
382 print ('GenerateCapsule: error: can not encode FMP Payload Header')
383 sys.exit (1)
384
385 #
386 # Sign image with 64-bit MonotonicCount appended to end of image
387 #
388 try:
389 if UseSignTool:
390 CertData = SignPayloadSignTool (
391 Result + struct.pack ('<Q', args.MonotonicCount),
392 args.SigningToolPath,
393 args.SignToolPfxFile
394 )
395 else:
396 CertData = SignPayloadOpenSsl (
397 Result + struct.pack ('<Q', args.MonotonicCount),
398 args.SigningToolPath,
399 args.OpenSslSignerPrivateCertFile,
400 args.OpenSslOtherPublicCertFile,
401 args.OpenSslTrustedPublicCertFile
402 )
403 except:
404 print ('GenerateCapsule: error: can not sign payload')
405 sys.exit (1)
406
407 try:
408 FmpAuthHeader.MonotonicCount = args.MonotonicCount
409 FmpAuthHeader.CertData = CertData
410 FmpAuthHeader.Payload = Result
411 Result = FmpAuthHeader.Encode ()
412 if args.Verbose:
413 FmpAuthHeader.DumpInfo ()
414 except:
415 print ('GenerateCapsule: error: can not encode FMP Auth Header')
416 sys.exit (1)
417
418 try:
419 FmpCapsuleHeader.AddPayload (args.Guid, Result, HardwareInstance = args.HardwareInstance)
420 Result = FmpCapsuleHeader.Encode ()
421 if args.Verbose:
422 FmpCapsuleHeader.DumpInfo ()
423 except:
424 print ('GenerateCapsule: error: can not encode FMP Capsule Header')
425 sys.exit (1)
426
427 try:
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 ()
434 if args.Verbose:
435 UefiCapsuleHeader.DumpInfo ()
436 except:
437 print ('GenerateCapsule: error: can not encode UEFI Capsule Header')
438 sys.exit (1)
439
440 elif args.Decode:
441 try:
442 Result = UefiCapsuleHeader.Decode (Buffer)
443 FmpCapsuleHeader.Decode (Result)
444 Result = FmpCapsuleHeader.GetFmpCapsuleImageHeader (0).Payload
445 if args.Verbose:
446 print ('========')
447 UefiCapsuleHeader.DumpInfo ()
448 print ('--------')
449 FmpCapsuleHeader.DumpInfo ()
450 if UseSignTool or UseOpenSsl:
451 Result = FmpAuthHeader.Decode (Result)
452 if args.Verbose:
453 print ('--------')
454 FmpAuthHeader.DumpInfo ()
455
456 #
457 # Verify Image with 64-bit MonotonicCount appended to end of image
458 #
459 try:
460 if UseSignTool:
461 CertData = VerifyPayloadSignTool (
462 FmpAuthHeader.Payload + struct.pack ('<Q', FmpAuthHeader.MonotonicCount),
463 FmpAuthHeader.CertData,
464 args.SigningToolPath,
465 args.SignToolPfxFile
466 )
467 else:
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
475 )
476 except ValueError:
477 print ('GenerateCapsule: warning: can not verify payload.')
478
479 try:
480 Result = FmpPayloadHeader.Decode (Result)
481 if args.Verbose:
482 print ('--------')
483 FmpPayloadHeader.DumpInfo ()
484 print ('========')
485 except:
486 if args.Verbose:
487 print ('--------')
488 print ('No FMP_PAYLOAD_HEADER')
489 print ('========')
490 raise
491 else:
492 if args.Verbose:
493 print ('--------')
494 print ('No EFI_FIRMWARE_IMAGE_AUTHENTICATION')
495 print ('--------')
496 print ('No FMP_PAYLOAD_HEADER')
497 print ('========')
498 except:
499 print ('GenerateCapsule: error: can not decode capsule')
500 sys.exit (1)
501
502 elif args.DumpInfo:
503 try:
504 Result = UefiCapsuleHeader.Decode (Buffer)
505 FmpCapsuleHeader.Decode (Result)
506 Result = FmpCapsuleHeader.GetFmpCapsuleImageHeader (0).Payload
507 print ('========')
508 UefiCapsuleHeader.DumpInfo ()
509 print ('--------')
510 FmpCapsuleHeader.DumpInfo ()
511 try:
512 Result = FmpAuthHeader.Decode (Result)
513 print ('--------')
514 FmpAuthHeader.DumpInfo ()
515 try:
516 Result = FmpPayloadHeader.Decode (Result)
517 print ('--------')
518 FmpPayloadHeader.DumpInfo ()
519 except:
520 print ('--------')
521 print ('No FMP_PAYLOAD_HEADER')
522 except:
523 print ('--------')
524 print ('No EFI_FIRMWARE_IMAGE_AUTHENTICATION')
525 print ('--------')
526 print ('No FMP_PAYLOAD_HEADER')
527 print ('========')
528 except:
529 print ('GenerateCapsule: error: can not decode capsule')
530 sys.exit (1)
531 else:
532 print('GenerateCapsule: error: invalid options')
533 sys.exit (1)
534
535 #
536 # Write binary output file
537 #
538 if args.OutputFile is not None:
539 try:
540 if args.Verbose:
541 print ('Write binary output file {File}'.format (File = args.OutputFile.name))
542 args.OutputFile.write (Result)
543 args.OutputFile.close ()
544 except:
545 print ('GenerateCapsule: error: can not write binary output file {File}'.format (File = args.OutputFile.name))
546 sys.exit (1)
547
548 if args.Verbose:
549 print('Success')