slimbootloader/BootloaderCorePkg/Tools/SingleSign.py

305 lines
10 KiB
Python

#!/usr/bin/env python
## @ SingleSign.py
# Single signing script
#
# Copyright (c) 2020, Intel Corporation. All rights reserved.<BR>
# SPDX-License-Identifier: BSD-2-Clause-Patent
#
##
##
# Import Modules
#
import os
import sys
import re
import shutil
import subprocess
import struct
import hashlib
import string
SIGNING_KEY = {
# Key Id | Key File Name start |
# =================================================================
# KEY_ID_MASTER is used for signing Slimboot Key Hash Manifest container (KEYH Component)
"KEY_ID_MASTER_RSA2048" : "MasterTestKey_Priv_RSA2048.pem",
"KEY_ID_MASTER_RSA3072" : "MasterTestKey_Priv_RSA3072.pem",
# KEY_ID_CFGDATA is used for signing external Config data blob)
"KEY_ID_CFGDATA_RSA2048" : "ConfigTestKey_Priv_RSA2048.pem",
"KEY_ID_CFGDATA_RSA3072" : "ConfigTestKey_Priv_RSA3072.pem",
# KEY_ID_FIRMWAREUPDATE is used for signing capsule firmware update image)
"KEY_ID_FIRMWAREUPDATE_RSA2048" : "FirmwareUpdateTestKey_Priv_RSA2048.pem",
"KEY_ID_FIRMWAREUPDATE_RSA3072" : "FirmwareUpdateTestKey_Priv_RSA3072.pem",
# KEY_ID_CONTAINER is used for signing container header with mono signature
"KEY_ID_CONTAINER_RSA2048" : "ContainerTestKey_Priv_RSA2048.pem",
"KEY_ID_CONTAINER_RSA3072" : "ContainerTestKey_Priv_RSA3072.pem",
# CONTAINER_COMP1_KEY_ID is used for signing container components
"KEY_ID_CONTAINER_COMP_RSA2048" : "ContainerCompTestKey_Priv_RSA2048.pem",
"KEY_ID_CONTAINER_COMP_RSA3072" : "ContainerCompTestKey_Priv_RSA3072.pem",
# KEY_ID_OS1_PUBLIC, KEY_ID_OS2_PUBLIC is used for referencing Boot OS public keys
"KEY_ID_OS1_PUBLIC_RSA2048" : "OS1_TestKey_Pub_RSA2048.pem",
"KEY_ID_OS1_PUBLIC_RSA3072" : "OS1_TestKey_Pub_RSA3072.pem",
"KEY_ID_OS2_PUBLIC_RSA2048" : "OS2_TestKey_Pub_RSA2048.pem",
"KEY_ID_OS2_PUBLIC_RSA3072" : "OS2_TestKey_Pub_RSA3072.pem",
# KEY_ID_OS1_PRIVATE is used for signing container header with BOOT signature
"KEY_ID_OS1_PRIVATE_RSA2048" : "OS1_TestKey_Priv_RSA2048.pem",
"KEY_ID_OS1_PRIVATE_RSA3072" : "OS1_TestKey_Priv_RSA3072.pem",
}
MESSAGE_SBL_KEY_DIR = (
"!!! PRE-REQUISITE: Path to SBL_KEY_DIR has to be set with SBL KEYS DIRECTORY !!! \n"
"!!! Generate keys using GenerateKeys.py available in BootloaderCorePkg/Tools directory !!! \n"
"!!! Run $python BootloaderCorePkg/Tools/GenerateKeys.py -k $PATH_TO_SBL_KEY_DIR !!!\n"
"!!! Set SBL_KEY_DIR environ with path to SBL KEYS DIR !!!\n"
"!!! Windows $set SBL_KEY_DIR=$PATH_TO_SBL_KEY_DIR !!!\n"
"!!! Linux $export SBL_KEY_DIR=$PATH_TO_SBL_KEY_DIR !!!\n"
)
def get_openssl_path ():
if os.name == 'nt':
if 'OPENSSL_PATH' not in os.environ:
openssl_dir = "C:\\Openssl\\bin\\"
if os.path.exists (openssl_dir):
os.environ['OPENSSL_PATH'] = openssl_dir
else:
os.environ['OPENSSL_PATH'] = "C:\\Openssl\\"
if 'OPENSSL_CONF' not in os.environ:
openssl_cfg = "C:\\Openssl\\openssl.cfg"
if os.path.exists(openssl_cfg):
os.environ['OPENSSL_CONF'] = openssl_cfg
openssl = os.path.join(os.environ.get ('OPENSSL_PATH', ''), 'openssl.exe')
else:
# Get openssl path for Linux cases
openssl = shutil.which('openssl')
return openssl
def run_process (arg_list, print_cmd = False, capture_out = False):
sys.stdout.flush()
if print_cmd:
print (' '.join(arg_list))
exc = None
result = 0
output = ''
try:
if capture_out:
output = subprocess.check_output(arg_list).decode()
else:
result = subprocess.call (arg_list)
except Exception as ex:
result = 1
exc = ex
if result:
if not print_cmd:
print ('Error in running process:\n %s' % ' '.join(arg_list))
if exc is None:
sys.exit(1)
else:
raise exc
return output
def check_file_pem_format (priv_key):
# Check for file .pem format
key_name = os.path.basename(priv_key)
if os.path.splitext(key_name)[1] == ".pem":
return True
else:
return False
def get_key_id (priv_key):
# Extract base name if path is provided.
key_name = os.path.basename(priv_key)
# Check for KEY_ID in key naming.
if key_name.startswith('KEY_ID'):
return key_name
else:
return None
def get_sbl_key_dir ():
# Check Key store setting SBL_KEY_DIR path
if 'SBL_KEY_DIR' not in os.environ:
raise Exception ("ERROR: SBL_KEY_DIR is not defined. Set SBL_KEY_DIR with SBL Keys directory!!\n"
+ MESSAGE_SBL_KEY_DIR)
sbl_key_dir = os.environ.get('SBL_KEY_DIR')
if not os.path.exists(sbl_key_dir):
raise Exception (("ERROR:SBL_KEY_DIR set %s is not valid. Set the correct SBL_KEY_DIR path !!\n"
+ MESSAGE_SBL_KEY_DIR) % sbl_key_dir)
else:
return sbl_key_dir
def get_key_from_store (in_key):
#Check in_key is path to key
if os.path.exists(in_key):
return in_key
# Get Slimboot key dir path
sbl_key_dir = get_sbl_key_dir()
# Extract if in_key is key_id
priv_key = get_key_id (in_key)
if priv_key is not None:
if (priv_key in SIGNING_KEY):
# Generate key file name from key id
priv_key_file = SIGNING_KEY[priv_key]
else:
raise Exception('KEY_ID %s is not found in supported KEY IDs!!' % priv_key)
elif check_file_pem_format(in_key) == True:
# check if file name is provided in pem format
priv_key_file = in_key
else:
priv_key_file = None
raise Exception('key provided %s is not valid!' % in_key)
# Create a file path
# Join Key Dir and priv_key_file
try:
priv_key = os.path.join (sbl_key_dir, priv_key_file)
except:
raise Exception('priv_key is not found %s!' % priv_key)
# Check for priv_key construted based on KEY ID exists in specified path
if not os.path.isfile(priv_key):
raise Exception (("!!! ERROR: Key file corresponding to '%s' do not exist in Sbl key directory at '%s' !!! \n" + MESSAGE_SBL_KEY_DIR) % (in_key, sbl_key_dir))
return priv_key
#
# Sign an file using openssl
#
# priv_key [Input] Key Id or Path to Private key
# hash_type [Input] Signing hash
# sign_scheme[Input] Sign/padding scheme
# in_file [Input] Input file to be signed
# out_file [Input/Output] Signed data file
#
def single_sign_file (priv_key, hash_type, sign_scheme, in_file, out_file):
_hash_type_string = {
"SHA2_256" : 'sha256',
"SHA2_384" : 'sha384',
"SHA2_512" : 'sha512',
}
_hash_digest_Size = {
# Hash_string : Hash_Size
"SHA2_256" : 32,
"SHA2_384" : 48,
"SHA2_512" : 64,
"SM3_256" : 32,
}
_sign_scheme_string = {
"RSA_PKCS1" : 'pkcs1',
"RSA_PSS" : 'pss',
}
priv_key = get_key_from_store(priv_key)
# Temporary files to store hash generated
hash_file_tmp = out_file+'.hash.tmp'
hash_file = out_file+'.hash'
# Generate hash using openssl dgst in hex format
cmdargs = [get_openssl_path(), 'dgst', '-'+'%s' % _hash_type_string[hash_type], '-out', '%s' % hash_file_tmp, '%s' % in_file]
run_process (cmdargs)
# Extract hash form dgst command output and convert to ascii
with open(hash_file_tmp, 'r') as fin:
hashdata = fin.read()
fin.close()
try:
hashdata = hashdata.rsplit('=', 1)[1].strip()
except:
raise Exception('Hash Data not found for signing!')
if len(hashdata) != (_hash_digest_Size[hash_type] * 2):
raise Exception('Hash Data size do match with for hash type!')
hashdata_bytes = bytearray.fromhex(hashdata)
open (hash_file, 'wb').write(hashdata_bytes)
print ("Key used for Singing %s !!" % priv_key)
# sign using Openssl pkeyutl
cmdargs = [get_openssl_path(), 'pkeyutl', '-sign', '-in', '%s' % hash_file, '-inkey', '%s' % priv_key,
'-out', '%s' % out_file, '-pkeyopt', 'digest:%s' % _hash_type_string[hash_type],
'-pkeyopt', 'rsa_padding_mode:%s' % _sign_scheme_string[sign_scheme]]
run_process (cmdargs)
return
#
# Extract public key using openssl
#
# in_key [Input] Private key or public key in pem format
# pub_key_file [Input/Output] Public Key to a file
#
# return keydata (mod, exp) in bin format
#
def single_sign_gen_pub_key (in_key, pub_key_file = None):
in_key = get_key_from_store(in_key)
# Expect key to be in PEM format
is_prv_key = False
cmdline = [get_openssl_path(), 'rsa', '-pubout', '-text', '-noout', '-in', '%s' % in_key]
# Check if it is public key or private key
text = open(in_key, 'r').read()
if '-BEGIN RSA PRIVATE KEY-' in text or '-BEGIN PRIVATE KEY-' in text:
is_prv_key = True
elif '-BEGIN PUBLIC KEY-' in text:
cmdline.extend (['-pubin'])
else:
raise Exception('Unknown key format "%s" !' % in_key)
if pub_key_file:
cmdline.extend (['-out', '%s' % pub_key_file])
capture = False
else:
capture = True
output = run_process (cmdline, capture_out = capture)
if not capture:
output = text = open(pub_key_file, 'r').read()
data = output.replace('\r', '')
data = data.replace('\n', '')
data = data.replace(' ', '')
# Extract the modulus
if is_prv_key:
match = re.search('modulus(.*)publicExponent:\s+(\d+)\s+', data)
else:
match = re.search('Modulus(?:.*?):(.*)Exponent:\s+(\d+)\s+', data)
if not match:
raise Exception('Public key not found!')
modulus = match.group(1).replace(':', '')
exponent = int(match.group(2))
mod = bytearray.fromhex(modulus)
# Remove the '00' from the front if the MSB is 1
if mod[0] == 0 and (mod[1] & 0x80):
mod = mod[1:]
exp = bytearray.fromhex('{:08x}'.format(exponent))
keydata = mod + exp
return keydata