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 __len__(self): 123 return TLV_INFO_SIZE + len(self.buf) 124 125 def add(self, kind, payload): 126 """ 127 Add a TLV record. Argument type is either the type scalar ID or a 128 matching string defined in TLV_TYPES. 129 """ 130 if isinstance(kind, int): 131 buf = struct.pack('II', kind, len(payload)) 132 else: 133 buf = struct.pack('II', TLV_TYPES[kind], len(payload)) 134 135 # Ensure that each TLV is 64-bit aligned 136 align_64b = (len(payload) + len(buf)) % 8 137 self.buf += buf 138 self.buf += payload 139 if align_64b: 140 self.buf += bytearray(8 - align_64b) 141 142 def add_plat_tlv(self, cust_tlv): 143 # Get list of custom protected TLVs from the command-line 144 for tlv in cust_tlv: 145 type_id = int(tlv[0], 0) 146 147 if type_id not in PLATFORM_TLV_TYPE_RANGE: 148 raise Exception('TLV %s not in range' % hex(type_id)) 149 150 value = tlv[1] 151 if value.startswith('0x'): 152 int_val = int(value[2:], 16) 153 self.tlvs[type_id] = int_val.to_bytes(4, 'little') 154 else: 155 self.tlvs[type_id] = value.encode('utf-8') 156 157 if self.tlvs is not None: 158 for type_id, value in self.tlvs.items(): 159 self.add(type_id, value) 160 161 def get(self): 162 """ 163 Get a byte-array that concatenates all the TLV added. 164 """ 165 if len(self.buf) == 0: 166 return bytes() 167 return bytes(self.buf) 168 169 170class RSA_Signature(object): 171 172 def __init__(self, key): 173 self._hasher = SHA256.new() 174 self.signer = pkcs1_15.new(key) 175 176 def hash_compute(self, segment): 177 self._hasher.update(segment) 178 179 def sign(self): 180 return self.signer.sign(self._hasher) 181 182 183class ECC_Signature(object): 184 185 def __init__(self, key): 186 self._hasher = SHA256.new() 187 self.signer = DSS.new(key, 'fips-186-3') 188 189 def hash_compute(self, segment): 190 self._hasher.update(segment) 191 192 def sign(self): 193 return self.signer.sign(self._hasher) 194 195 196Signature = { 197 1: RSA_Signature, 198 2: ECC_Signature, 199} 200 201 202class SegmentHashStruct: 203 pass 204 205 206class SegmentHash(object): 207 ''' 208 Hash table based on Elf program segments 209 ''' 210 def __init__(self, img): 211 self._num_segments = img.num_segments() 212 self._pack_fmt = '<%dL' % 8 213 self.img = img 214 self.hashProgTable = bytes() 215 self._offset = 0 216 217 def get_table(self): 218 ''' 219 Create a segment hash table containing for each segment: 220 - the segments header 221 - a hash of the segment 222 ''' 223 h = SHA256.new() 224 seg = SegmentHashStruct() 225 self.size = (h.digest_size + 32) * self._num_segments 226 logging.debug("hash section size %d" % self.size) 227 del h 228 self.buf = bytearray(self.size) 229 self._bufview_ = memoryview(self.buf) 230 231 for i in range(self._num_segments): 232 h = SHA256.new() 233 segment = self.img.get_segment(i) 234 seg.header = self.img.get_segment(i).header 235 logging.debug("compute hash for segment offset %s" % seg.header) 236 h.update(segment.data()) 237 seg.hash = h.digest() 238 logging.debug("hash computed: %s" % seg.hash) 239 del h 240 struct.pack_into('<I', self._bufview_, self._offset, 241 ENUM_P_TYPE_ARM[seg.header.p_type]) 242 self._offset += 4 243 struct.pack_into('<7I', self._bufview_, self._offset, 244 seg.header.p_offset, seg.header.p_vaddr, 245 seg.header.p_paddr, seg.header.p_filesz, 246 seg.header.p_memsz, seg.header.p_flags, 247 seg.header.p_align) 248 self._offset += 28 249 struct.pack_into('<32B', self._bufview_, self._offset, *seg.hash) 250 self._offset += 32 251 dump_buffer(self.buf, name='hash table', indent="\t") 252 return self.buf 253 254 255class ImageHeader(object): 256 ''' 257 Image header 258 ''' 259 260 magic = 'HELF' # SHDR_MAGIC 261 version = 1 262 263 MAGIC_OFFSET = 0 264 VERSION_OFFSET = 4 265 SIGN_LEN_OFFSET = 8 266 IMG_LEN_OFFSET = 12 267 TLV_LEN_OFFSET = 16 268 PTLV_LEN_OFFSET = 20 269 270 def __init__(self): 271 self.size = 56 272 273 self.magic = HEADER_MAGIC 274 self.version = 1 275 self.tlv_length = 0 276 self.sign_length = 0 277 self.img_length = 0 278 279 self.shdr = struct.pack('<IIIII', 280 self.magic, self.version, 281 self.tlv_length, self.sign_length, 282 self.img_length) 283 284 def dump(self): 285 logging.debug("\tMAGIC\t\t= %08X" % (self.magic)) 286 logging.debug("\tHEADER_VERSION\t= %08X" % (self.version)) 287 logging.debug("\tTLV_LENGTH\t= %08X" % (self.tlv_length)) 288 logging.debug("\tSIGN_LENGTH\t= %08X" % (self.sign_length)) 289 logging.debug("\tIMAGE_LENGTH\t= %08X" % (self.img_length)) 290 291 def get_packed(self): 292 return struct.pack('<IIIII', 293 self.magic, self.version, 294 self.tlv_length, self.sign_length, self.img_length) 295 296 297def get_args(logger): 298 from argparse import ArgumentParser, RawDescriptionHelpFormatter 299 import textwrap 300 301 parser = ArgumentParser( 302 description='Sign a remote processor firmware loadable by OP-TEE.', 303 usage='\n %(prog)s [ arguments ]\n\n' 304 ' Generate signed loadable binary \n' + 305 ' Takes arguments --in, --out --key\n' + 306 ' %(prog)s --help show available arguments\n\n') 307 parser.add_argument('--in', required=True, dest='in_file', 308 help='Name of firmware input file ' + 309 '(can be used multiple times)', action='append') 310 parser.add_argument('--out', required=True, dest='out_file', 311 help='Name of the signed firmware output file') 312 parser.add_argument('--key', required=True, 313 help='Name of signing key file', 314 dest='key_file') 315 parser.add_argument('--key_info', required=False, 316 help='Name file containing extra key information', 317 dest='key_info') 318 parser.add_argument('--key_type', required=False, 319 help='Type of signing key: should be RSA or ECC', 320 default='RSA', 321 dest='key_type') 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): 342 return RSA.importKey(open(key_file).read()) 343 344 345def ecc_key(key_file): 346 return ECC.import_key(open(key_file).read()) 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) 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