1#!/usr/bin/env python3 2# SPDX-License-Identifier: BSD-2-Clause 3# 4# Copyright (C) 2023, STMicroelectronics 5# 6 7try: 8 from elftools.elf.elffile import ELFFile 9 from elftools.elf.sections import SymbolTableSection 10 from elftools.elf.enums import ENUM_P_TYPE_ARM 11 from elftools.elf.enums import * 12except ImportError: 13 print(""" 14*** 15ERROR: "pyelftools" python module is not installed or version < 0.25. 16*** 17""") 18 raise 19 20try: 21 from Cryptodome.Hash import SHA256 22 from Cryptodome.Signature import pkcs1_15 23 from Cryptodome.PublicKey import RSA 24 from Cryptodome.Signature import DSS 25 from Cryptodome.PublicKey import ECC 26except ImportError: 27 print(""" 28 *** 29 ERROR: "pycryptodomex" python module should be installed. 30 *** 31 """) 32 raise 33 34import os 35import sys 36import struct 37import logging 38import binascii 39 40# Generated file structure: 41# 42# -----+-------------+ 43# / | Magic | 32-bit word, magic value equal to 44# / +-------------+ 0x3543A468 45# / +-------------+ 46# / | version | 32-bit word, version of the format 47# / +-------------+ 48# +-----------+ +-------------+ 49# | Header | | TLV size | 32-bit word, size of the TLV 50# +-----------+ +-------------+ (aligned on 64-bit), in bytes. 51# \ +-------------+ 52# \ | sign size | 32-bit word, size of the signature 53# \ +-------------+ (aligned on 64-bit), in bytes. 54# \ +-------------+ 55# \ | images size | 32-bit word, size of the images to 56# -----+-------------+ load (aligned on 64-bit), in bytes. 57# 58# +-------------+ Information used to authenticate the 59# | TLV | images and boot the remote processor, 60# | | stored in Type-Length-Value format. 61# +-------------+ 'Type' and 'Length' are 32-bit words. 62# 63# +-------------+ 64# | Signature | Signature of the header and the TLV. 65# +-------------+ 66# 67# +-------------+ 68# | Firmware | 69# | image 1 | 70# +-------------+ 71# ... 72# +-------------+ 73# | Firmware | 74# | image n | 75# +-------------+ 76 77# Generic type definitions 78TLV_TYPES = { 79 'SIGNTYPE': 0x00000001, # algorithm used for signature 80 'HASHTYPE': 0x00000002, # algorithm used for computing segment's hash 81 'NUM_IMG': 0x00000003, # number of images to load 82 'IMGTYPE': 0x00000004, # array of type of images to load 83 'IMGSIZE': 0x00000005, # array of the size of the images to load 84 'HASHTABLE': 0x000000010, # segment hash table for authentication 85 'PKEYINFO': 0x0000000011, # information to retrieve signature key 86} 87 88GENERIC_TLV_TYPE_RANGE = range(0x00000000, 0x00010000) 89PLATFORM_TLV_TYPE_RANGE = range(0x00010000, 0x00020000) 90 91HEADER_MAGIC = 0x3543A468 92 93logging.basicConfig(stream=sys.stderr, level=logging.INFO) 94 95ENUM_HASH_TYPE = dict( 96 SHA256=1, 97) 98 99ENUM_SIGNATURE_TYPE = dict( 100 RSA=1, 101 ECC=2, 102) 103 104ENUM_BINARY_TYPE = dict( 105 ELF=1, 106) 107 108 109def dump_buffer(buf, step=16, name="", logger=logging.debug, indent=""): 110 logger("%s%s:" % (indent, name)) 111 for i in range(0, len(buf), step): 112 logger("%s " % (indent) + " ". 113 join(["%02X" % c for c in buf[i:i+step]])) 114 logger("\n") 115 116 117class TLV(): 118 def __init__(self): 119 self.buf = bytearray() 120 self.tlvs = {} 121 122 def add(self, kind, payload): 123 """ 124 Add a TLV record. Argument type is either the type scalar ID or a 125 matching string defined in TLV_TYPES. 126 """ 127 if isinstance(kind, int): 128 buf = struct.pack('II', kind, len(payload)) 129 else: 130 buf = struct.pack('II', TLV_TYPES[kind], len(payload)) 131 132 # Ensure that each TLV is 64-bit aligned 133 align_64b = (len(payload) + len(buf)) % 8 134 self.buf += buf 135 self.buf += payload 136 if align_64b: 137 self.buf += bytearray(8 - align_64b) 138 139 def add_plat_tlv(self, cust_tlv): 140 # Get list of custom protected TLVs from the command-line 141 for tlv in cust_tlv: 142 type_id = int(tlv[0], 0) 143 144 if type_id not in PLATFORM_TLV_TYPE_RANGE: 145 raise Exception('TLV %s not in range' % hex(type_id)) 146 147 value = tlv[1] 148 if value.startswith('0x'): 149 int_val = int(value[2:], 16) 150 self.tlvs[type_id] = int_val.to_bytes(4, 'little') 151 else: 152 self.tlvs[type_id] = value.encode('utf-8') 153 154 if self.tlvs is not None: 155 for type_id, value in self.tlvs.items(): 156 self.add(type_id, value) 157 158 def get(self): 159 """ 160 Get a byte-array that concatenates all the TLV added. 161 """ 162 if len(self.buf) == 0: 163 return bytes() 164 return bytes(self.buf) 165 166 167class RSA_Signature(object): 168 169 def __init__(self, key): 170 self._hasher = SHA256.new() 171 self.signer = pkcs1_15.new(key) 172 173 def hash_compute(self, segment): 174 self._hasher.update(segment) 175 176 def sign(self): 177 return self.signer.sign(self._hasher) 178 179 180class ECC_Signature(object): 181 182 def __init__(self, key): 183 self._hasher = SHA256.new() 184 self.signer = DSS.new(key, 'fips-186-3') 185 186 def hash_compute(self, segment): 187 self._hasher.update(segment) 188 189 def sign(self): 190 return self.signer.sign(self._hasher) 191 192 193Signature = { 194 1: RSA_Signature, 195 2: ECC_Signature, 196} 197 198 199class SegmentHashStruct: 200 pass 201 202 203class SegmentHash(object): 204 ''' 205 Hash table based on Elf program segments 206 ''' 207 def __init__(self, img): 208 self._num_segments = img.num_segments() 209 self._pack_fmt = '<%dL' % 8 210 self.img = img 211 self.hashProgTable = bytes() 212 self._offset = 0 213 214 def get_table(self): 215 ''' 216 Create a segment hash table containing for each segment: 217 - the segments header 218 - a hash of the segment 219 ''' 220 h = SHA256.new() 221 seg = SegmentHashStruct() 222 self.size = (h.digest_size + 32) * self._num_segments 223 logging.debug("hash section size %d" % self.size) 224 del h 225 self.buf = bytearray(self.size) 226 self._bufview_ = memoryview(self.buf) 227 228 for i in range(self._num_segments): 229 h = SHA256.new() 230 segment = self.img.get_segment(i) 231 seg.header = self.img.get_segment(i).header 232 logging.debug("compute hash for segment offset %s" % seg.header) 233 h.update(segment.data()) 234 seg.hash = h.digest() 235 logging.debug("hash computed: %s" % seg.hash) 236 del h 237 struct.pack_into('<I', self._bufview_, self._offset, 238 ENUM_P_TYPE_ARM[seg.header.p_type]) 239 self._offset += 4 240 struct.pack_into('<7I', self._bufview_, self._offset, 241 seg.header.p_offset, seg.header.p_vaddr, 242 seg.header.p_paddr, seg.header.p_filesz, 243 seg.header.p_memsz, seg.header.p_flags, 244 seg.header.p_align) 245 self._offset += 28 246 struct.pack_into('<32B', self._bufview_, self._offset, *seg.hash) 247 self._offset += 32 248 dump_buffer(self.buf, name='hash table', indent="\t") 249 return self.buf 250 251 252class ImageHeader(object): 253 ''' 254 Image header 255 ''' 256 257 magic = 'HELF' # SHDR_MAGIC 258 version = 1 259 260 MAGIC_OFFSET = 0 261 VERSION_OFFSET = 4 262 SIGN_LEN_OFFSET = 8 263 IMG_LEN_OFFSET = 12 264 TLV_LEN_OFFSET = 16 265 PTLV_LEN_OFFSET = 20 266 267 def __init__(self): 268 self.size = 56 269 270 self.magic = HEADER_MAGIC 271 self.version = 1 272 self.tlv_length = 0 273 self.sign_length = 0 274 self.img_length = 0 275 276 self.shdr = struct.pack('<IIIII', 277 self.magic, self.version, 278 self.tlv_length, self.sign_length, 279 self.img_length) 280 281 def dump(self): 282 logging.debug("\tMAGIC\t\t= %08X" % (self.magic)) 283 logging.debug("\tHEADER_VERSION\t= %08X" % (self.version)) 284 logging.debug("\tTLV_LENGTH\t= %08X" % (self.tlv_length)) 285 logging.debug("\tSIGN_LENGTH\t= %08X" % (self.sign_length)) 286 logging.debug("\tIMAGE_LENGTH\t= %08X" % (self.img_length)) 287 288 def get_packed(self): 289 return struct.pack('<IIIII', 290 self.magic, self.version, 291 self.tlv_length, self.sign_length, self.img_length) 292 293 294def get_args(logger): 295 from argparse import ArgumentParser, RawDescriptionHelpFormatter 296 import textwrap 297 298 parser = ArgumentParser( 299 description='Sign a remote processor firmware loadable by OP-TEE.', 300 usage='\n %(prog)s [ arguments ]\n\n' 301 ' Generate signed loadable binary \n' + 302 ' Takes arguments --in, --out --key\n' + 303 ' %(prog)s --help show available arguments\n\n') 304 parser.add_argument('--in', required=True, dest='in_file', 305 help='Name of firmware input file ' + 306 '(can be used multiple times)', action='append') 307 parser.add_argument('--out', required=True, dest='out_file', 308 help='Name of the signed firmware output file') 309 parser.add_argument('--key', required=True, 310 help='Name of signing key file', 311 dest='key_file') 312 parser.add_argument('--key_info', required=False, 313 help='Name file containing extra key information', 314 dest='key_info') 315 parser.add_argument('--key_type', required=False, 316 help='Type of signing key: should be RSA or ECC', 317 default='RSA', 318 dest='key_type') 319 parser.add_argument('--key_pwd', required=False, 320 help='passphrase for the private key decryption', 321 dest='key_pwd') 322 parser.add_argument('--plat-tlv', required=False, nargs=2, 323 metavar=("ID", "value"), action='append', 324 help='Platform TLV that will be placed into image ' 325 'plat_tlv area. Add "0x" prefix to interpret ' 326 'the value as an integer, otherwise it will be ' 327 'interpreted as a string. Option can be used ' 328 'multiple times to add multiple TLVs.', 329 default=[], dest='plat_tlv') 330 331 parsed = parser.parse_args() 332 333 # Set defaults for optional arguments. 334 335 if parsed.out_file is None: 336 parsed.out_file = str(parsed.in_file)+'.sig' 337 338 return parsed 339 340 341def rsa_key(key_file, key_pwd): 342 return RSA.importKey(open(key_file).read(), key_pwd) 343 344 345def ecc_key(key_file, key_pwd): 346 return ECC.import_key(open(key_file).read(), key_pwd) 347 348 349key_type = { 350 1: rsa_key, 351 2: ecc_key, 352} 353 354 355def rsa_sig_size(key): 356 return key.size_in_bytes() 357 358 359def ecc_sig_size(key): 360 # to be improve... 361 # DSA size is N/4 so 64 for DSA (L,N) = (2048, 256) 362 return 64 363 364 365sig_size_type = { 366 1: rsa_sig_size, 367 2: ecc_sig_size, 368} 369 370 371def main(): 372 from Cryptodome.Signature import pss 373 from Cryptodome.Hash import SHA256 374 from Cryptodome.PublicKey import RSA 375 import base64 376 import logging 377 import struct 378 379 logging.basicConfig() 380 logger = logging.getLogger(os.path.basename(__file__)) 381 382 args = get_args(logger) 383 384 # Initialise the header */ 385 s_header = ImageHeader() 386 tlv = TLV() 387 388 sign_type = ENUM_SIGNATURE_TYPE[args.key_type] 389 get_key = key_type.get(sign_type, lambda: "Invalid sign type") 390 391 key = get_key(args.key_file, args.key_pwd) 392 393 if not key.has_private(): 394 logger.error('Provided key cannot be used for signing, ') 395 sys.exit(1) 396 397 tlv.add('SIGNTYPE', sign_type.to_bytes(1, 'little')) 398 399 images_type = [] 400 hash_tlv = bytearray() 401 images_size = [] 402 403 # Firmware image 404 for inputf in args.in_file: 405 logging.debug("image %s" % inputf) 406 input_file = open(inputf, 'rb') 407 img = ELFFile(input_file) 408 409 # Only ARM machine has been tested and well supported yet. 410 # Indeed this script uses of ENUM_P_TYPE_ARM dic 411 assert img.get_machine_arch() in ["ARM"] 412 413 # Need to reopen the file to get the raw data 414 with open(inputf, 'rb') as f: 415 bin_img = f.read() 416 size = len(bin_img) 417 align_64b = size % 8 418 if align_64b: 419 size += 8 - align_64b 420 421 images_size.extend(size.to_bytes(4, 'little')) 422 s_header.img_length += size 423 f.close() 424 425 # Store image type information 426 bin_type = ENUM_BINARY_TYPE['ELF'] 427 images_type += bin_type.to_bytes(1, 'little') 428 429 # Compute the hash table and add it to TLV blob 430 hash_table = SegmentHash(img) 431 hash_tlv.extend(hash_table.get_table()) 432 433 # Add image information 434 # The 'IMGTYPE' contains a byte array of the image type (ENUM_BINARY_TYPE). 435 # The 'IMGSIZE' contains a byte array of the size (32-bit) of each image. 436 tlv.add('NUM_IMG', len(args.in_file).to_bytes(1, 'little')) 437 tlv.add('IMGTYPE', bytearray(images_type)) 438 tlv.add('IMGSIZE', bytearray(images_size)) 439 440 # Add hash type information in TLV blob 441 # The 'HASHTYPE' TLV contains a byte associated to ENUM_HASH_TYPE. 442 hash_type = ENUM_HASH_TYPE['SHA256'] 443 tlv.add('HASHTYPE', hash_type.to_bytes(1, 'little')) 444 445 # Add hash table information in TLV blob 446 # The HASHTABLE TLV contains a byte array containing all the ELF segment 447 # with associated hash. 448 tlv.add('HASHTABLE', hash_tlv) 449 450 # Add optional key information to TLV 451 if args.key_info: 452 with open(args.key_info, 'rb') as f: 453 key_info = f.read() 454 tlv.add('PKEYINFO', key_info) 455 456 # Compute custom TLV that will be passed to the platform PTA 457 # Get list of custom protected TLVs from the command-line 458 if args.plat_tlv: 459 tlv.add_plat_tlv(args.plat_tlv) 460 461 # Get the TLV area and compute its size (with 64 bit alignment) 462 tlvs_buff = tlv.get() 463 s_header.tlv_length = len(tlvs_buff) 464 465 align_64b = 8 - (s_header.tlv_length % 8) 466 if align_64b: 467 s_header.tlv_length += 8 - align_64b 468 tlvs_buff += bytearray(8 - align_64b) 469 470 dump_buffer(tlvs_buff, name='TLVS', indent="\t") 471 472 # Signature chunk 473 sign_size = sig_size_type.get(ENUM_SIGNATURE_TYPE[args.key_type], 474 lambda: "Invalid sign type")(key) 475 s_header.sign_length = sign_size 476 477 # Construct the Header 478 header = s_header.get_packed() 479 480 # Generate signature 481 signer = Signature.get(ENUM_SIGNATURE_TYPE[args.key_type])(key) 482 483 signer.hash_compute(header) 484 signer.hash_compute(tlvs_buff) 485 signature = signer.sign() 486 if len(signature) != sign_size: 487 raise Exception(("Actual signature length is not equal to ", 488 "the computed one: {} != {}". 489 format(len(signature), sign_size))) 490 491 s_header.dump() 492 493 with open(args.out_file, 'wb') as f: 494 f.write(header) 495 f.write(tlvs_buff) 496 f.write(signature) 497 align_64b = sign_size % 8 498 if align_64b: 499 f.write(bytearray(8 - align_64b)) 500 for inputf in args.in_file: 501 with open(inputf, 'rb') as fin: 502 bin_img = fin.read() 503 f.write(bin_img) 504 fin.close() 505 align_64b = len(bin_img) % 8 506 if align_64b: 507 f.write(bytearray(8 - align_64b)) 508 509 510if __name__ == "__main__": 511 main() 512