2021-11-10 11:36:23 +00:00
#!/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 " ,
2023-02-10 07:04:22 +08:00
# 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 " ,
2021-11-10 11:36:23 +00:00
}
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 ( )
2021-12-07 16:18:43 +00:00
if ' -BEGIN RSA PRIVATE KEY- ' in text or ' -BEGIN PRIVATE KEY- ' in text :
2021-11-10 11:36:23 +00:00
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