]> git.proxmox.com Git - mirror_edk2.git/blob - BaseTools/Source/Python/Capsule/GenerateCapsule.py
BaseTools/Capsule: Fix CertType GUID byte order
[mirror_edk2.git] / BaseTools / Source / Python / Capsule / GenerateCapsule.py
1 ## @file
2 # Generate a capsule.
3 #
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
9 #
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.
12 #
13
14 '''
15 GenerateCapsule
16 '''
17
18 import sys
19 import argparse
20 import uuid
21 import struct
22 import subprocess
23 import os
24 import tempfile
25 import shutil
26 import platform
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
31
32 #
33 # Globals for help information
34 #
35 __prog__ = 'GenerateCapsule'
36 __version__ = '0.9'
37 __copyright__ = 'Copyright (c) 2018, Intel Corporation. All rights reserved.'
38 __description__ = 'Generate a capsule.\n'
39
40 def SignPayloadSignTool (Payload, ToolPath, PfxFile):
41 #
42 # Create a temporary directory
43 #
44 TempDirectoryName = tempfile.mkdtemp()
45
46 #
47 # Generate temp file name for the payload contents
48 #
49 TempFileName = os.path.join (TempDirectoryName, 'Payload.bin')
50
51 #
52 # Create temporary payload file for signing
53 #
54 try:
55 File = open (TempFileName, mode='wb')
56 File.write (Payload)
57 File.close ()
58 except:
59 shutil.rmtree (TempDirectoryName)
60 raise ValueError ('GenerateCapsule: error: can not write temporary payload file.')
61
62 #
63 # Build signtool command
64 #
65 if ToolPath is None:
66 ToolPath = ''
67 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
73
74 #
75 # Sign the input file using the specified private key
76 #
77 try:
78 Process = subprocess.Popen (Command, stdin = subprocess.PIPE, stdout = subprocess.PIPE, stderr = subprocess.PIPE, shell = True)
79 Result = Process.communicate('')
80 except:
81 shutil.rmtree (TempDirectoryName)
82 raise ValueError ('GenerateCapsule: error: can not run signtool.')
83
84 if Process.returncode != 0:
85 shutil.rmtree (TempDirectoryName)
86 print (Result[1].decode())
87 raise ValueError ('GenerateCapsule: error: signtool failed.')
88
89 #
90 # Read the signature from the generated output file
91 #
92 try:
93 File = open (TempFileName + '.p7', mode='rb')
94 Signature = File.read ()
95 File.close ()
96 except:
97 shutil.rmtree (TempDirectoryName)
98 raise ValueError ('GenerateCapsule: error: can not read signature file.')
99
100 shutil.rmtree (TempDirectoryName)
101 return Signature
102
103 def VerifyPayloadSignTool (Payload, CertData, ToolPath, PfxFile):
104 print ('signtool verify is not supported.')
105 raise ValueError ('GenerateCapsule: error: signtool verify is not supported.')
106
107 def SignPayloadOpenSsl (Payload, ToolPath, SignerPrivateCertFile, OtherPublicCertFile, TrustedPublicCertFile):
108 #
109 # Build openssl command
110 #
111 if ToolPath is None:
112 ToolPath = ''
113 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)
117
118 #
119 # Sign the input file using the specified private key and capture signature from STDOUT
120 #
121 try:
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]
125 except:
126 raise ValueError ('GenerateCapsule: error: can not run openssl.')
127
128 if Process.returncode != 0:
129 print (Result[1].decode())
130 raise ValueError ('GenerateCapsule: error: openssl failed.')
131
132 return Signature
133
134 def VerifyPayloadOpenSsl (Payload, CertData, ToolPath, SignerPrivateCertFile, OtherPublicCertFile, TrustedPublicCertFile):
135 #
136 # Create a temporary directory
137 #
138 TempDirectoryName = tempfile.mkdtemp()
139
140 #
141 # Generate temp file name for the payload contents
142 #
143 TempFileName = os.path.join (TempDirectoryName, 'Payload.bin')
144
145 #
146 # Create temporary payload file for verification
147 #
148 try:
149 File = open (TempFileName, mode='wb')
150 File.write (Payload)
151 File.close ()
152 except:
153 shutil.rmtree (TempDirectoryName)
154 raise ValueError ('GenerateCapsule: error: can not write temporary payload file.')
155
156 #
157 # Build openssl command
158 #
159 if ToolPath is None:
160 ToolPath = ''
161 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)
165
166 #
167 # Verify signature
168 #
169 try:
170 Process = subprocess.Popen (Command, stdin = subprocess.PIPE, stdout = subprocess.PIPE, stderr = subprocess.PIPE, shell = True)
171 Result = Process.communicate(input = CertData)
172 except:
173 shutil.rmtree (TempDirectoryName)
174 raise ValueError ('GenerateCapsule: error: can not run openssl.')
175
176 if Process.returncode != 0:
177 shutil.rmtree (TempDirectoryName)
178 print (Result[1].decode())
179 raise ValueError ('GenerateCapsule: error: openssl failed.')
180
181 shutil.rmtree (TempDirectoryName)
182 return Payload
183
184 if __name__ == '__main__':
185 def convert_arg_line_to_args(arg_line):
186 for arg in arg_line.split():
187 if not arg.strip():
188 continue
189 yield arg
190
191 def ValidateUnsignedInteger (Argument):
192 try:
193 Value = int (Argument, 0)
194 except:
195 Message = '{Argument} is not a valid integer value.'.format (Argument = Argument)
196 raise argparse.ArgumentTypeError (Message)
197 if Value < 0:
198 Message = '{Argument} is a negative value.'.format (Argument = Argument)
199 raise argparse.ArgumentTypeError (Message)
200 return Value
201
202 def ValidateRegistryFormatGuid (Argument):
203 try:
204 Value = uuid.UUID (Argument)
205 except:
206 Message = '{Argument} is not a valid registry format GUID value.'.format (Argument = Argument)
207 raise argparse.ArgumentTypeError (Message)
208 return Value
209
210 #
211 # Create command line argument parser object
212 #
213 parser = argparse.ArgumentParser (
214 prog = __prog__,
215 description = __description__ + __copyright__,
216 conflict_handler = 'resolve',
217 fromfile_prefix_chars = '@'
218 )
219 parser.convert_arg_line_to_args = convert_arg_line_to_args
220
221 #
222 # Add input and output file arguments
223 #
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.")
228 #
229 # Add group for -e and -d flags that are mutually exclusive and required
230 #
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")
238 #
239 # Add optional arguments for this command
240 #
241 parser.add_argument ("--capflag", dest = 'CapsuleFlag', action='append', default = [],
242 choices=['PersistAcrossReset', 'InitiateReset'],
243 help = "Capsule flag can be PersistAcrossReset 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.")
246
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")
251
252
253 parser.add_argument ("--monotonic-count", dest = 'MonotonicCount', type = ValidateUnsignedInteger, default = 0x0000000000000000,
254 help = "64-bit monotonic count value in header. Default is 0x0000000000000000.")
255
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).")
260
261 parser.add_argument ("--pfx-file", dest='SignToolPfxFile', type=argparse.FileType('rb'),
262 help="signtool PFX certificate filename.")
263
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.")
270
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.")
273
274 #
275 # Add optional arguments common to all operations
276 #
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")
284
285 #
286 # Parse command line arguments
287 #
288 args = parser.parse_args()
289
290 #
291 # Perform additional argument verification
292 #
293 if args.Encode:
294 if args.Guid is None:
295 parser.error ('the following option is required: --guid')
296 if 'PersistAcrossReset' not in args.CapsuleFlag:
297 if 'InitiateReset' in args.CapsuleFlag:
298 parser.error ('--capflag InitiateReset also requires --capflag PersistAcrossReset')
299 if args.CapsuleOemFlag > 0xFFFF:
300 parser.error ('--capoemflag must be an integer between 0x0000 and 0xffff')
301 if args.HardwareInstance > 0xFFFFFFFFFFFFFFFF:
302 parser.error ('--hardware-instance must be an integer in range 0x0..0xffffffffffffffff')
303 if args.MonotonicCount > 0xFFFFFFFFFFFFFFFF:
304 parser.error ('--monotonic-count must be an integer in range 0x0..0xffffffffffffffff')
305
306 UseSignTool = args.SignToolPfxFile is not None
307 UseOpenSsl = (args.OpenSslSignerPrivateCertFile is not None and
308 args.OpenSslOtherPublicCertFile is not None and
309 args.OpenSslTrustedPublicCertFile is not None)
310 AnyOpenSsl = (args.OpenSslSignerPrivateCertFile is not None or
311 args.OpenSslOtherPublicCertFile is not None or
312 args.OpenSslTrustedPublicCertFile is not None)
313 if args.Encode or args.Decode:
314 if args.OutputFile is None:
315 parser.error ('the following option is required for all encode and decode operations: --output')
316
317 if UseSignTool and AnyOpenSsl:
318 parser.error ('Providing both signtool and OpenSSL options is not supported')
319 if not UseSignTool and not UseOpenSsl and AnyOpenSsl:
320 parser.error ('all the following options are required for OpenSSL: --signer-private-cert, --other-public-cert, --trusted-public-cert')
321 if UseSignTool and platform.system() != 'Windows':
322 parser.error ('Use of signtool is not supported on this operating system.')
323 if args.Encode and (UseSignTool or UseOpenSsl):
324 if args.FwVersion is None or args.LowestSupportedVersion is None:
325 parser.error ('the following options are required: --fw-version, --lsv')
326 if args.FwVersion > 0xFFFFFFFF:
327 parser.error ('--fw-version must be an integer in range 0x0..0xffffffff')
328 if args.LowestSupportedVersion > 0xFFFFFFFF:
329 parser.error ('--lsv must be an integer in range 0x0..0xffffffff')
330
331 if UseSignTool:
332 args.SignToolPfxFile.close()
333 args.SignToolPfxFile = args.SignToolPfxFile.name
334 if UseOpenSsl:
335 args.OpenSslSignerPrivateCertFile.close()
336 args.OpenSslOtherPublicCertFile.close()
337 args.OpenSslTrustedPublicCertFile.close()
338 args.OpenSslSignerPrivateCertFile = args.OpenSslSignerPrivateCertFile.name
339 args.OpenSslOtherPublicCertFile = args.OpenSslOtherPublicCertFile.name
340 args.OpenSslTrustedPublicCertFile = args.OpenSslTrustedPublicCertFile.name
341
342 #
343 # Read binary input file
344 #
345 try:
346 if args.Verbose:
347 print ('Read binary input file {File}'.format (File = args.InputFile.name))
348 Buffer = args.InputFile.read ()
349 args.InputFile.close ()
350 except:
351 print ('GenerateCapsule: error: can not read binary input file {File}'.format (File = args.InputFile.name))
352 sys.exit (1)
353
354 #
355 # Create objects
356 #
357 UefiCapsuleHeader = UefiCapsuleHeaderClass ()
358 FmpCapsuleHeader = FmpCapsuleHeaderClass ()
359 FmpAuthHeader = FmpAuthHeaderClass ()
360 FmpPayloadHeader = FmpPayloadHeaderClass ()
361
362 if args.Encode:
363 Result = Buffer
364 if UseSignTool or UseOpenSsl:
365 try:
366 FmpPayloadHeader.FwVersion = args.FwVersion
367 FmpPayloadHeader.LowestSupportedVersion = args.LowestSupportedVersion
368 FmpPayloadHeader.Payload = Result
369 Result = FmpPayloadHeader.Encode ()
370 if args.Verbose:
371 FmpPayloadHeader.DumpInfo ()
372 except:
373 print ('GenerateCapsule: error: can not encode FMP Payload Header')
374 sys.exit (1)
375
376 #
377 # Sign image with 64-bit MonotonicCount appended to end of image
378 #
379 try:
380 if UseSignTool:
381 CertData = SignPayloadSignTool (
382 Result + struct.pack ('<Q', args.MonotonicCount),
383 args.SigningToolPath,
384 args.SignToolPfxFile
385 )
386 else:
387 CertData = SignPayloadOpenSsl (
388 Result + struct.pack ('<Q', args.MonotonicCount),
389 args.SigningToolPath,
390 args.OpenSslSignerPrivateCertFile,
391 args.OpenSslOtherPublicCertFile,
392 args.OpenSslTrustedPublicCertFile
393 )
394 except:
395 print ('GenerateCapsule: error: can not sign payload')
396 raise
397 sys.exit (1)
398
399 try:
400 FmpAuthHeader.MonotonicCount = args.MonotonicCount
401 FmpAuthHeader.CertData = CertData
402 FmpAuthHeader.Payload = Result
403 Result = FmpAuthHeader.Encode ()
404 if args.Verbose:
405 FmpAuthHeader.DumpInfo ()
406 except:
407 print ('GenerateCapsule: error: can not encode FMP Auth Header')
408 sys.exit (1)
409
410 try:
411 FmpCapsuleHeader.AddPayload (args.Guid, Result, HardwareInstance = args.HardwareInstance)
412 Result = FmpCapsuleHeader.Encode ()
413 if args.Verbose:
414 FmpCapsuleHeader.DumpInfo ()
415 except:
416 print ('GenerateCapsule: error: can not encode FMP Capsule Header')
417 sys.exit (1)
418
419 try:
420 UefiCapsuleHeader.OemFlags = args.CapsuleOemFlag
421 UefiCapsuleHeader.PersistAcrossReset = 'PersistAcrossReset' in args.CapsuleFlag
422 UefiCapsuleHeader.PopulateSystemTable = False
423 UefiCapsuleHeader.InitiateReset = 'InitiateReset' in args.CapsuleFlag
424 UefiCapsuleHeader.Payload = Result
425 Result = UefiCapsuleHeader.Encode ()
426 if args.Verbose:
427 UefiCapsuleHeader.DumpInfo ()
428 except:
429 print ('GenerateCapsule: error: can not encode UEFI Capsule Header')
430 sys.exit (1)
431
432 elif args.Decode:
433 try:
434 Result = UefiCapsuleHeader.Decode (Buffer)
435 FmpCapsuleHeader.Decode (Result)
436 Result = FmpCapsuleHeader.GetFmpCapsuleImageHeader (0).Payload
437 if args.Verbose:
438 print ('========')
439 UefiCapsuleHeader.DumpInfo ()
440 print ('--------')
441 FmpCapsuleHeader.DumpInfo ()
442 if UseSignTool or UseOpenSsl:
443 Result = FmpAuthHeader.Decode (Result)
444
445 #
446 # Verify Image with 64-bit MonotonicCount appended to end of image
447 #
448 try:
449 if UseSignTool:
450 CertData = VerifyPayloadSignTool (
451 FmpAuthHeader.Payload + struct.pack ('<Q', FmpAuthHeader.MonotonicCount),
452 FmpAuthHeader.CertData,
453 args.SigningToolPath,
454 args.SignToolPfxFile
455 )
456 else:
457 CertData = VerifyPayloadOpenSsl (
458 FmpAuthHeader.Payload + struct.pack ('<Q', FmpAuthHeader.MonotonicCount),
459 FmpAuthHeader.CertData,
460 args.SigningToolPath,
461 args.OpenSslSignerPrivateCertFile,
462 args.OpenSslOtherPublicCertFile,
463 args.OpenSslTrustedPublicCertFile
464 )
465 except ValueError:
466 print ('GenerateCapsule: warning: can not verify payload.')
467
468 Result = FmpPayloadHeader.Decode (Result)
469 if args.Verbose:
470 print ('--------')
471 FmpAuthHeader.DumpInfo ()
472 print ('--------')
473 FmpPayloadHeader.DumpInfo ()
474 else:
475 if args.Verbose:
476 print ('--------')
477 print ('No EFI_FIRMWARE_IMAGE_AUTHENTICATION')
478 print ('--------')
479 print ('No FMP_PAYLOAD_HEADER')
480 if args.Verbose:
481 print ('========')
482 except:
483 print ('GenerateCapsule: error: can not decode capsule')
484 raise
485 sys.exit (1)
486
487 elif args.DumpInfo:
488 try:
489 Result = UefiCapsuleHeader.Decode (Buffer)
490 FmpCapsuleHeader.Decode (Result)
491 Result = FmpCapsuleHeader.GetFmpCapsuleImageHeader (0).Payload
492 print ('========')
493 UefiCapsuleHeader.DumpInfo ()
494 print ('--------')
495 FmpCapsuleHeader.DumpInfo ()
496 try:
497 Result = FmpAuthHeader.Decode (Result)
498 Result = FmpPayloadHeader.Decode (Result)
499 print ('--------')
500 FmpAuthHeader.DumpInfo ()
501 print ('--------')
502 FmpPayloadHeader.DumpInfo ()
503 except:
504 print ('--------')
505 print ('No EFI_FIRMWARE_IMAGE_AUTHENTICATION')
506 print ('--------')
507 print ('No FMP_PAYLOAD_HEADER')
508 print ('========')
509 except:
510 print ('GenerateCapsule: error: can not decode capsule')
511 sys.exit (1)
512 else:
513 print('GenerateCapsule: error: invalid options')
514 sys.exit (1)
515
516 #
517 # Write binary output file
518 #
519 if args.OutputFile is not None:
520 try:
521 if args.Verbose:
522 print ('Write binary output file {File}'.format (File = args.OutputFile.name))
523 args.OutputFile.write (Result)
524 args.OutputFile.close ()
525 except:
526 print ('GenerateCapsule: error: can not write binary output file {File}'.format (File = args.OutputFile.name))
527 sys.exit (1)
528
529 if args.Verbose:
530 print('Success')