]> git.proxmox.com Git - mirror_edk2.git/blob - IntelFsp2Pkg/Tools/ConfigEditor/SingleSign.py
IntelFsp2Pkg: Add Config Editor tool support
[mirror_edk2.git] / IntelFsp2Pkg / Tools / ConfigEditor / SingleSign.py
1 #!/usr/bin/env python
2 # @ SingleSign.py
3 # Single signing script
4 #
5 # Copyright (c) 2020 - 2021, Intel Corporation. All rights reserved.<BR>
6 # SPDX-License-Identifier: BSD-2-Clause-Patent
7 #
8 ##
9
10 import os
11 import sys
12 import re
13 import shutil
14 import subprocess
15
16 SIGNING_KEY = {
17 # Key Id | Key File Name start |
18 # =================================================================
19 # KEY_ID_MASTER is used for signing Slimboot Key Hash Manifest \
20 # container (KEYH Component)
21 "KEY_ID_MASTER_RSA2048": "MasterTestKey_Priv_RSA2048.pem",
22 "KEY_ID_MASTER_RSA3072": "MasterTestKey_Priv_RSA3072.pem",
23
24 # KEY_ID_CFGDATA is used for signing external Config data blob)
25 "KEY_ID_CFGDATA_RSA2048": "ConfigTestKey_Priv_RSA2048.pem",
26 "KEY_ID_CFGDATA_RSA3072": "ConfigTestKey_Priv_RSA3072.pem",
27
28 # KEY_ID_FIRMWAREUPDATE is used for signing capsule firmware update image)
29 "KEY_ID_FIRMWAREUPDATE_RSA2048": "FirmwareUpdateTestKey_Priv_RSA2048.pem",
30 "KEY_ID_FIRMWAREUPDATE_RSA3072": "FirmwareUpdateTestKey_Priv_RSA3072.pem",
31
32 # KEY_ID_CONTAINER is used for signing container header with mono signature
33 "KEY_ID_CONTAINER_RSA2048": "ContainerTestKey_Priv_RSA2048.pem",
34 "KEY_ID_CONTAINER_RSA3072": "ContainerTestKey_Priv_RSA3072.pem",
35
36 # CONTAINER_COMP1_KEY_ID is used for signing container components
37 "KEY_ID_CONTAINER_COMP_RSA2048": "ContainerCompTestKey_Priv_RSA2048.pem",
38 "KEY_ID_CONTAINER_COMP_RSA3072": "ContainerCompTestKey_Priv_RSA3072.pem",
39
40 # KEY_ID_OS1_PUBLIC, KEY_ID_OS2_PUBLIC is used for referencing \
41 # Boot OS public keys
42 "KEY_ID_OS1_PUBLIC_RSA2048": "OS1_TestKey_Pub_RSA2048.pem",
43 "KEY_ID_OS1_PUBLIC_RSA3072": "OS1_TestKey_Pub_RSA3072.pem",
44
45 "KEY_ID_OS2_PUBLIC_RSA2048": "OS2_TestKey_Pub_RSA2048.pem",
46 "KEY_ID_OS2_PUBLIC_RSA3072": "OS2_TestKey_Pub_RSA3072.pem",
47
48 }
49
50 MESSAGE_SBL_KEY_DIR = """!!! PRE-REQUISITE: Path to SBL_KEY_DIR has.
51 to be set with SBL KEYS DIRECTORY !!! \n!!! Generate keys.
52 using GenerateKeys.py available in BootloaderCorePkg/Tools.
53 directory !!! \n !!! Run $python.
54 BootloaderCorePkg/Tools/GenerateKeys.py -k $PATH_TO_SBL_KEY_DIR !!!\n
55 !!! Set SBL_KEY_DIR environ with path to SBL KEYS DIR !!!\n"
56 !!! Windows $set SBL_KEY_DIR=$PATH_TO_SBL_KEY_DIR !!!\n
57 !!! Linux $export SBL_KEY_DIR=$PATH_TO_SBL_KEY_DIR !!!\n"""
58
59
60 def get_openssl_path():
61 if os.name == 'nt':
62 if 'OPENSSL_PATH' not in os.environ:
63 openssl_dir = "C:\\Openssl\\bin\\"
64 if os.path.exists(openssl_dir):
65 os.environ['OPENSSL_PATH'] = openssl_dir
66 else:
67 os.environ['OPENSSL_PATH'] = "C:\\Openssl\\"
68 if 'OPENSSL_CONF' not in os.environ:
69 openssl_cfg = "C:\\Openssl\\openssl.cfg"
70 if os.path.exists(openssl_cfg):
71 os.environ['OPENSSL_CONF'] = openssl_cfg
72 openssl = os.path.join(
73 os.environ.get('OPENSSL_PATH', ''),
74 'openssl.exe')
75 else:
76 # Get openssl path for Linux cases
77 openssl = shutil.which('openssl')
78
79 return openssl
80
81
82 def run_process(arg_list, print_cmd=False, capture_out=False):
83 sys.stdout.flush()
84 if print_cmd:
85 print(' '.join(arg_list))
86
87 exc = None
88 result = 0
89 output = ''
90 try:
91 if capture_out:
92 output = subprocess.check_output(arg_list).decode()
93 else:
94 result = subprocess.call(arg_list)
95 except Exception as ex:
96 result = 1
97 exc = ex
98
99 if result:
100 if not print_cmd:
101 print('Error in running process:\n %s' % ' '.join(arg_list))
102 if exc is None:
103 sys.exit(1)
104 else:
105 raise exc
106
107 return output
108
109
110 def check_file_pem_format(priv_key):
111 # Check for file .pem format
112 key_name = os.path.basename(priv_key)
113 if os.path.splitext(key_name)[1] == ".pem":
114 return True
115 else:
116 return False
117
118
119 def get_key_id(priv_key):
120 # Extract base name if path is provided.
121 key_name = os.path.basename(priv_key)
122 # Check for KEY_ID in key naming.
123 if key_name.startswith('KEY_ID'):
124 return key_name
125 else:
126 return None
127
128
129 def get_sbl_key_dir():
130 # Check Key store setting SBL_KEY_DIR path
131 if 'SBL_KEY_DIR' not in os.environ:
132 exception_string = "ERROR: SBL_KEY_DIR is not defined." \
133 " Set SBL_KEY_DIR with SBL Keys directory!!\n"
134 raise Exception(exception_string + MESSAGE_SBL_KEY_DIR)
135
136 sbl_key_dir = os.environ.get('SBL_KEY_DIR')
137 if not os.path.exists(sbl_key_dir):
138 exception_string = "ERROR:SBL_KEY_DIR set " + sbl_key_dir \
139 + " is not valid." \
140 " Set the correct SBL_KEY_DIR path !!\n" \
141 + MESSAGE_SBL_KEY_DIR
142 raise Exception(exception_string)
143 else:
144 return sbl_key_dir
145
146
147 def get_key_from_store(in_key):
148
149 # Check in_key is path to key
150 if os.path.exists(in_key):
151 return in_key
152
153 # Get Slimboot key dir path
154 sbl_key_dir = get_sbl_key_dir()
155
156 # Extract if in_key is key_id
157 priv_key = get_key_id(in_key)
158 if priv_key is not None:
159 if (priv_key in SIGNING_KEY):
160 # Generate key file name from key id
161 priv_key_file = SIGNING_KEY[priv_key]
162 else:
163 exception_string = "KEY_ID" + priv_key + "is not found " \
164 "is not found in supported KEY IDs!!"
165 raise Exception(exception_string)
166 elif check_file_pem_format(in_key):
167 # check if file name is provided in pem format
168 priv_key_file = in_key
169 else:
170 priv_key_file = None
171 raise Exception('key provided %s is not valid!' % in_key)
172
173 # Create a file path
174 # Join Key Dir and priv_key_file
175 try:
176 priv_key = os.path.join(sbl_key_dir, priv_key_file)
177 except Exception:
178 raise Exception('priv_key is not found %s!' % priv_key)
179
180 # Check for priv_key construted based on KEY ID exists in specified path
181 if not os.path.isfile(priv_key):
182 exception_string = "!!! ERROR: Key file corresponding to" \
183 + in_key + "do not exist in Sbl key " \
184 "directory at" + sbl_key_dir + "!!! \n" \
185 + MESSAGE_SBL_KEY_DIR
186 raise Exception(exception_string)
187
188 return priv_key
189
190 #
191 # Sign an file using openssl
192 #
193 # priv_key [Input] Key Id or Path to Private key
194 # hash_type [Input] Signing hash
195 # sign_scheme[Input] Sign/padding scheme
196 # in_file [Input] Input file to be signed
197 # out_file [Input/Output] Signed data file
198 #
199
200
201 def single_sign_file(priv_key, hash_type, sign_scheme, in_file, out_file):
202
203 _hash_type_string = {
204 "SHA2_256": 'sha256',
205 "SHA2_384": 'sha384',
206 "SHA2_512": 'sha512',
207 }
208
209 _hash_digest_Size = {
210 # Hash_string : Hash_Size
211 "SHA2_256": 32,
212 "SHA2_384": 48,
213 "SHA2_512": 64,
214 "SM3_256": 32,
215 }
216
217 _sign_scheme_string = {
218 "RSA_PKCS1": 'pkcs1',
219 "RSA_PSS": 'pss',
220 }
221
222 priv_key = get_key_from_store(priv_key)
223
224 # Temporary files to store hash generated
225 hash_file_tmp = out_file+'.hash.tmp'
226 hash_file = out_file+'.hash'
227
228 # Generate hash using openssl dgst in hex format
229 cmdargs = [get_openssl_path(),
230 'dgst',
231 '-'+'%s' % _hash_type_string[hash_type],
232 '-out', '%s' % hash_file_tmp, '%s' % in_file]
233 run_process(cmdargs)
234
235 # Extract hash form dgst command output and convert to ascii
236 with open(hash_file_tmp, 'r') as fin:
237 hashdata = fin.read()
238 fin.close()
239
240 try:
241 hashdata = hashdata.rsplit('=', 1)[1].strip()
242 except Exception:
243 raise Exception('Hash Data not found for signing!')
244
245 if len(hashdata) != (_hash_digest_Size[hash_type] * 2):
246 raise Exception('Hash Data size do match with for hash type!')
247
248 hashdata_bytes = bytearray.fromhex(hashdata)
249 open(hash_file, 'wb').write(hashdata_bytes)
250
251 print("Key used for Singing %s !!" % priv_key)
252
253 # sign using Openssl pkeyutl
254 cmdargs = [get_openssl_path(),
255 'pkeyutl', '-sign', '-in', '%s' % hash_file,
256 '-inkey', '%s' % priv_key, '-out',
257 '%s' % out_file, '-pkeyopt',
258 'digest:%s' % _hash_type_string[hash_type],
259 '-pkeyopt', 'rsa_padding_mode:%s' %
260 _sign_scheme_string[sign_scheme]]
261
262 run_process(cmdargs)
263
264 return
265
266 #
267 # Extract public key using openssl
268 #
269 # in_key [Input] Private key or public key in pem format
270 # pub_key_file [Input/Output] Public Key to a file
271 #
272 # return keydata (mod, exp) in bin format
273 #
274
275
276 def single_sign_gen_pub_key(in_key, pub_key_file=None):
277
278 in_key = get_key_from_store(in_key)
279
280 # Expect key to be in PEM format
281 is_prv_key = False
282 cmdline = [get_openssl_path(), 'rsa', '-pubout', '-text', '-noout',
283 '-in', '%s' % in_key]
284 # Check if it is public key or private key
285 text = open(in_key, 'r').read()
286 if '-BEGIN RSA PRIVATE KEY-' in text:
287 is_prv_key = True
288 elif '-BEGIN PUBLIC KEY-' in text:
289 cmdline.extend(['-pubin'])
290 else:
291 raise Exception('Unknown key format "%s" !' % in_key)
292
293 if pub_key_file:
294 cmdline.extend(['-out', '%s' % pub_key_file])
295 capture = False
296 else:
297 capture = True
298
299 output = run_process(cmdline, capture_out=capture)
300 if not capture:
301 output = text = open(pub_key_file, 'r').read()
302 data = output.replace('\r', '')
303 data = data.replace('\n', '')
304 data = data.replace(' ', '')
305
306 # Extract the modulus
307 if is_prv_key:
308 match = re.search('modulus(.*)publicExponent:\\s+(\\d+)\\s+', data)
309 else:
310 match = re.search('Modulus(?:.*?):(.*)Exponent:\\s+(\\d+)\\s+', data)
311 if not match:
312 raise Exception('Public key not found!')
313 modulus = match.group(1).replace(':', '')
314 exponent = int(match.group(2))
315
316 mod = bytearray.fromhex(modulus)
317 # Remove the '00' from the front if the MSB is 1
318 if mod[0] == 0 and (mod[1] & 0x80):
319 mod = mod[1:]
320 exp = bytearray.fromhex('{:08x}'.format(exponent))
321
322 keydata = mod + exp
323
324 return keydata