2 # This tool encodes and decodes GUIDed FFS sections or FMP capsule for a GUID type of
3 # EFI_CERT_TYPE_RSA2048_SHA256_GUID defined in the UEFI 2.4 Specification as
4 # {0xa7717414, 0xc616, 0x4977, {0x94, 0x20, 0x84, 0x47, 0x12, 0xa7, 0x35, 0xbf}}
5 # This tool has been tested with OpenSSL 1.0.1e 11 Feb 2013
7 # Copyright (c) 2013 - 2018, Intel Corporation. All rights reserved.<BR>
8 # SPDX-License-Identifier: BSD-2-Clause-Patent
14 from __future__
import print_function
23 from Common
.BuildVersion
import gBUILD_VERSION
26 # Globals for help information
28 __prog__
= 'Rsa2048Sha256Sign'
29 __version__
= '%s Version %s' % (__prog__
, '0.9 ' + gBUILD_VERSION
)
30 __copyright__
= 'Copyright (c) 2013 - 2018, Intel Corporation. All rights reserved.'
31 __usage__
= '%s -e|-d [options] <input_file>' % (__prog__
)
34 # GUID for SHA 256 Hash Algorithm from UEFI Specification
36 EFI_HASH_ALGORITHM_SHA256_GUID
= uuid
.UUID('{51aa59de-fdf2-4ea3-bc63-875fb7842ee9}')
39 # Structure definition to unpack EFI_CERT_BLOCK_RSA_2048_SHA256 from UEFI 2.4 Specification
41 # typedef struct _EFI_CERT_BLOCK_RSA_2048_SHA256 {
43 # UINT8 PublicKey[256];
44 # UINT8 Signature[256];
45 # } EFI_CERT_BLOCK_RSA_2048_SHA256;
47 EFI_CERT_BLOCK_RSA_2048_SHA256
= collections
.namedtuple('EFI_CERT_BLOCK_RSA_2048_SHA256', ['HashType', 'PublicKey', 'Signature'])
48 EFI_CERT_BLOCK_RSA_2048_SHA256_STRUCT
= struct
.Struct('16s256s256s')
51 # Filename of test signing private key that is stored in same directory as this tool
53 TEST_SIGNING_PRIVATE_KEY_FILENAME
= 'TestSigningPrivateKey.pem'
55 if __name__
== '__main__':
57 # Create command line argument parser object
59 parser
= argparse
.ArgumentParser(prog
=__prog__
, usage
=__usage__
, description
=__copyright__
, conflict_handler
='resolve')
60 group
= parser
.add_mutually_exclusive_group(required
=True)
61 group
.add_argument("-e", action
="store_true", dest
='Encode', help='encode file')
62 group
.add_argument("-d", action
="store_true", dest
='Decode', help='decode file')
63 group
.add_argument("--version", action
='version', version
=__version__
)
64 parser
.add_argument("-o", "--output", dest
='OutputFile', type=str, metavar
='filename', help="specify the output filename", required
=True)
65 parser
.add_argument("--monotonic-count", dest
='MonotonicCountStr', type=str, help="specify the MonotonicCount in FMP capsule.")
66 parser
.add_argument("--private-key", dest
='PrivateKeyFile', type=argparse
.FileType('rb'), help="specify the private key filename. If not specified, a test signing key is used.")
67 parser
.add_argument("-v", "--verbose", dest
='Verbose', action
="store_true", help="increase output messages")
68 parser
.add_argument("-q", "--quiet", dest
='Quiet', action
="store_true", help="reduce output messages")
69 parser
.add_argument("--debug", dest
='Debug', type=int, metavar
='[0-9]', choices
=range(0, 10), default
=0, help="set debug level")
70 parser
.add_argument(metavar
="input_file", dest
='InputFile', type=argparse
.FileType('rb'), help="specify the input filename")
73 # Parse command line arguments
75 args
= parser
.parse_args()
78 # Generate file path to Open SSL command
80 OpenSslCommand
= 'openssl'
82 OpenSslPath
= os
.environ
['OPENSSL_PATH']
83 OpenSslCommand
= os
.path
.join(OpenSslPath
, OpenSslCommand
)
84 if ' ' in OpenSslCommand
:
85 OpenSslCommand
= '"' + OpenSslCommand
+ '"'
90 # Verify that Open SSL command is available
93 Process
= subprocess
.Popen('%s version' % (OpenSslCommand
), stdout
=subprocess
.PIPE
, stderr
=subprocess
.PIPE
, shell
=True)
95 print('ERROR: Open SSL command not available. Please verify PATH or set OPENSSL_PATH')
98 Version
= Process
.communicate()
99 if Process
.returncode
!= 0:
100 print('ERROR: Open SSL command not available. Please verify PATH or set OPENSSL_PATH')
101 sys
.exit(Process
.returncode
)
102 print(Version
[0].decode('utf-8'))
105 # Read input file into a buffer and save input filename
107 args
.InputFileName
= args
.InputFile
.name
108 args
.InputFileBuffer
= args
.InputFile
.read()
109 args
.InputFile
.close()
112 # Save output filename and check if path exists
114 OutputDir
= os
.path
.dirname(args
.OutputFile
)
115 if not os
.path
.exists(OutputDir
):
116 print('ERROR: The output path does not exist: %s' % OutputDir
)
118 args
.OutputFileName
= args
.OutputFile
121 # Save private key filename and close private key file
124 args
.PrivateKeyFileName
= args
.PrivateKeyFile
.name
125 args
.PrivateKeyFile
.close()
129 # Get path to currently executing script or executable
131 if hasattr(sys
, 'frozen'):
132 RsaToolPath
= sys
.executable
134 RsaToolPath
= sys
.argv
[0]
135 if RsaToolPath
.startswith('"'):
136 RsaToolPath
= RsaToolPath
[1:]
137 if RsaToolPath
.endswith('"'):
138 RsaToolPath
= RsaToolPath
[:-1]
139 args
.PrivateKeyFileName
= os
.path
.join(os
.path
.dirname(os
.path
.realpath(RsaToolPath
)), TEST_SIGNING_PRIVATE_KEY_FILENAME
)
140 args
.PrivateKeyFile
= open(args
.PrivateKeyFileName
, 'rb')
141 args
.PrivateKeyFile
.close()
143 print('ERROR: test signing private key file %s missing' % (args
.PrivateKeyFileName
))
147 # Extract public key from private key into STDOUT
149 Process
= subprocess
.Popen('%s rsa -in "%s" -modulus -noout' % (OpenSslCommand
, args
.PrivateKeyFileName
), stdout
=subprocess
.PIPE
, stderr
=subprocess
.PIPE
, shell
=True)
150 PublicKeyHexString
= Process
.communicate()[0].split(b
'=')[1].strip()
151 PublicKeyHexString
= PublicKeyHexString
.decode('utf-8')
153 while len(PublicKeyHexString
) > 0:
154 PublicKey
= PublicKey
+ PublicKeyHexString
[0:2]
155 PublicKeyHexString
=PublicKeyHexString
[2:]
156 if Process
.returncode
!= 0:
157 sys
.exit(Process
.returncode
)
159 if args
.MonotonicCountStr
:
161 if args
.MonotonicCountStr
.upper().startswith('0X'):
162 args
.MonotonicCountValue
= int(args
.MonotonicCountStr
, 16)
164 args
.MonotonicCountValue
= int(args
.MonotonicCountStr
)
169 FullInputFileBuffer
= args
.InputFileBuffer
170 if args
.MonotonicCountStr
:
171 format
= "%dsQ" % len(args
.InputFileBuffer
)
172 FullInputFileBuffer
= struct
.pack(format
, args
.InputFileBuffer
, args
.MonotonicCountValue
)
174 # Sign the input file using the specified private key and capture signature from STDOUT
176 Process
= subprocess
.Popen('%s dgst -sha256 -sign "%s"' % (OpenSslCommand
, args
.PrivateKeyFileName
), stdin
=subprocess
.PIPE
, stdout
=subprocess
.PIPE
, stderr
=subprocess
.PIPE
, shell
=True)
177 Signature
= Process
.communicate(input=FullInputFileBuffer
)[0]
178 if Process
.returncode
!= 0:
179 sys
.exit(Process
.returncode
)
182 # Write output file that contains hash GUID, Public Key, Signature, and Input data
184 args
.OutputFile
= open(args
.OutputFileName
, 'wb')
185 args
.OutputFile
.write(EFI_HASH_ALGORITHM_SHA256_GUID
.bytes_le
)
186 args
.OutputFile
.write(bytearray
.fromhex(str(PublicKey
)))
187 args
.OutputFile
.write(Signature
)
188 args
.OutputFile
.write(args
.InputFileBuffer
)
189 args
.OutputFile
.close()
193 # Parse Hash Type, Public Key, and Signature from the section header
195 Header
= EFI_CERT_BLOCK_RSA_2048_SHA256
._make(EFI_CERT_BLOCK_RSA_2048_SHA256_STRUCT
.unpack_from(args
.InputFileBuffer
))
196 args
.InputFileBuffer
= args
.InputFileBuffer
[EFI_CERT_BLOCK_RSA_2048_SHA256_STRUCT
.size
:]
199 # Verify that the Hash Type matches the expected SHA256 type
201 if uuid
.UUID(bytes_le
= Header
.HashType
) != EFI_HASH_ALGORITHM_SHA256_GUID
:
202 print('ERROR: unsupport hash GUID')
206 # Verify the public key
208 if Header
.PublicKey
!= bytearray
.fromhex(PublicKey
):
209 print('ERROR: Public key in input file does not match public key from private key file')
212 FullInputFileBuffer
= args
.InputFileBuffer
213 if args
.MonotonicCountStr
:
214 format
= "%dsQ" % len(args
.InputFileBuffer
)
215 FullInputFileBuffer
= struct
.pack(format
, args
.InputFileBuffer
, args
.MonotonicCountValue
)
218 # Write Signature to output file
220 open(args
.OutputFileName
, 'wb').write(Header
.Signature
)
225 Process
= subprocess
.Popen('%s dgst -sha256 -prverify "%s" -signature %s' % (OpenSslCommand
, args
.PrivateKeyFileName
, args
.OutputFileName
), stdin
=subprocess
.PIPE
, stdout
=subprocess
.PIPE
, stderr
=subprocess
.PIPE
, shell
=True)
226 Process
.communicate(input=FullInputFileBuffer
)
227 if Process
.returncode
!= 0:
228 print('ERROR: Verification failed')
229 os
.remove (args
.OutputFileName
)
230 sys
.exit(Process
.returncode
)
233 # Save output file contents from input file
235 open(args
.OutputFileName
, 'wb').write(args
.InputFileBuffer
)