]> git.proxmox.com Git - mirror_edk2.git/blob - BaseTools/Source/Python/Capsule/GenerateCapsule.py
d829000849ad0185e9e3b911b1fe09b0708f1a5e
[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 raise
412 sys.exit (1)
413
414 try:
415 FmpAuthHeader.MonotonicCount = args.MonotonicCount
416 FmpAuthHeader.CertData = CertData
417 FmpAuthHeader.Payload = Result
418 Result = FmpAuthHeader.Encode ()
419 if args.Verbose:
420 FmpAuthHeader.DumpInfo ()
421 except:
422 print ('GenerateCapsule: error: can not encode FMP Auth Header')
423 sys.exit (1)
424
425 try:
426 FmpCapsuleHeader.AddPayload (args.Guid, Result, HardwareInstance = args.HardwareInstance)
427 Result = FmpCapsuleHeader.Encode ()
428 if args.Verbose:
429 FmpCapsuleHeader.DumpInfo ()
430 except:
431 print ('GenerateCapsule: error: can not encode FMP Capsule Header')
432 sys.exit (1)
433
434 try:
435 UefiCapsuleHeader.OemFlags = args.CapsuleOemFlag
436 UefiCapsuleHeader.PersistAcrossReset = 'PersistAcrossReset' in args.CapsuleFlag
437 UefiCapsuleHeader.PopulateSystemTable = False
438 UefiCapsuleHeader.InitiateReset = 'InitiateReset' in args.CapsuleFlag
439 UefiCapsuleHeader.Payload = Result
440 Result = UefiCapsuleHeader.Encode ()
441 if args.Verbose:
442 UefiCapsuleHeader.DumpInfo ()
443 except:
444 print ('GenerateCapsule: error: can not encode UEFI Capsule Header')
445 sys.exit (1)
446
447 elif args.Decode:
448 try:
449 Result = UefiCapsuleHeader.Decode (Buffer)
450 FmpCapsuleHeader.Decode (Result)
451 Result = FmpCapsuleHeader.GetFmpCapsuleImageHeader (0).Payload
452 if args.Verbose:
453 print ('========')
454 UefiCapsuleHeader.DumpInfo ()
455 print ('--------')
456 FmpCapsuleHeader.DumpInfo ()
457 if UseSignTool or UseOpenSsl:
458 Result = FmpAuthHeader.Decode (Result)
459
460 #
461 # Verify Image with 64-bit MonotonicCount appended to end of image
462 #
463 try:
464 if UseSignTool:
465 CertData = VerifyPayloadSignTool (
466 FmpAuthHeader.Payload + struct.pack ('<Q', FmpAuthHeader.MonotonicCount),
467 FmpAuthHeader.CertData,
468 args.SigningToolPath,
469 args.SignToolPfxFile
470 )
471 else:
472 CertData = VerifyPayloadOpenSsl (
473 FmpAuthHeader.Payload + struct.pack ('<Q', FmpAuthHeader.MonotonicCount),
474 FmpAuthHeader.CertData,
475 args.SigningToolPath,
476 args.OpenSslSignerPrivateCertFile,
477 args.OpenSslOtherPublicCertFile,
478 args.OpenSslTrustedPublicCertFile
479 )
480 except ValueError:
481 print ('GenerateCapsule: warning: can not verify payload.')
482
483 Result = FmpPayloadHeader.Decode (Result)
484 if args.Verbose:
485 print ('--------')
486 FmpAuthHeader.DumpInfo ()
487 print ('--------')
488 FmpPayloadHeader.DumpInfo ()
489 else:
490 if args.Verbose:
491 print ('--------')
492 print ('No EFI_FIRMWARE_IMAGE_AUTHENTICATION')
493 print ('--------')
494 print ('No FMP_PAYLOAD_HEADER')
495 if args.Verbose:
496 print ('========')
497 except:
498 print ('GenerateCapsule: error: can not decode capsule')
499 raise
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 Result = FmpPayloadHeader.Decode (Result)
514 print ('--------')
515 FmpAuthHeader.DumpInfo ()
516 print ('--------')
517 FmpPayloadHeader.DumpInfo ()
518 except:
519 print ('--------')
520 print ('No EFI_FIRMWARE_IMAGE_AUTHENTICATION')
521 print ('--------')
522 print ('No FMP_PAYLOAD_HEADER')
523 print ('========')
524 except:
525 print ('GenerateCapsule: error: can not decode capsule')
526 sys.exit (1)
527 else:
528 print('GenerateCapsule: error: invalid options')
529 sys.exit (1)
530
531 #
532 # Write binary output file
533 #
534 if args.OutputFile is not None:
535 try:
536 if args.Verbose:
537 print ('Write binary output file {File}'.format (File = args.OutputFile.name))
538 args.OutputFile.write (Result)
539 args.OutputFile.close ()
540 except:
541 print ('GenerateCapsule: error: can not write binary output file {File}'.format (File = args.OutputFile.name))
542 sys.exit (1)
543
544 if args.Verbose:
545 print('Success')