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