1# SPDX-License-Identifier: GPL-2.0+ 2# Copyright 2022 Google LLC 3# 4"""Bintool implementation for openssl 5 6openssl provides a number of features useful for signing images 7 8Documentation is at https://www.coreboot.org/CBFS 9 10Source code is at https://www.openssl.org/ 11""" 12 13import hashlib 14 15from binman import bintool 16from u_boot_pylib import tools 17 18 19VALID_SHAS = [256, 384, 512, 224] 20SHA_OIDS = {256:'2.16.840.1.101.3.4.2.1', 21 384:'2.16.840.1.101.3.4.2.2', 22 512:'2.16.840.1.101.3.4.2.3', 23 224:'2.16.840.1.101.3.4.2.4'} 24 25class Bintoolopenssl(bintool.Bintool): 26 """openssl tool 27 28 This bintool supports creating new openssl certificates. 29 30 It also supports fetching a binary openssl 31 32 Documentation about openssl is at https://www.openssl.org/ 33 """ 34 def __init__(self, name): 35 super().__init__( 36 name, 'openssl cryptography toolkit', 37 version_regex=r'OpenSSL (.*) \(', version_args='version') 38 39 def x509_cert(self, cert_fname, input_fname, key_fname, cn, revision, 40 config_fname): 41 """Create a certificate 42 43 Args: 44 cert_fname (str): Filename of certificate to create 45 input_fname (str): Filename containing data to sign 46 key_fname (str): Filename of .pem file 47 cn (str): Common name 48 revision (int): Revision number 49 config_fname (str): Filename to write fconfig into 50 51 Returns: 52 str: Tool output 53 """ 54 indata = tools.read_file(input_fname) 55 hashval = hashlib.sha512(indata).hexdigest() 56 with open(config_fname, 'w', encoding='utf-8') as outf: 57 print(f'''[ req ] 58distinguished_name = req_distinguished_name 59x509_extensions = v3_ca 60prompt = no 61dirstring_type = nobmp 62 63[ req_distinguished_name ] 64CN = {cert_fname} 65 66[ v3_ca ] 67basicConstraints = CA:true 681.3.6.1.4.1.294.1.3 = ASN1:SEQUENCE:swrv 691.3.6.1.4.1.294.1.34 = ASN1:SEQUENCE:sysfw_image_integrity 70 71[ swrv ] 72swrv = INTEGER:{revision} 73 74[ sysfw_image_integrity ] 75shaType = OID:2.16.840.1.101.3.4.2.3 76shaValue = FORMAT:HEX,OCT:{hashval} 77imageSize = INTEGER:{len(indata)} 78''', file=outf) 79 args = ['req', '-new', '-x509', '-key', key_fname, '-nodes', 80 '-outform', 'DER', '-out', cert_fname, '-config', config_fname, 81 '-sha512'] 82 return self.run_cmd(*args) 83 84 def x509_cert_sysfw(self, cert_fname, input_fname, key_fname, sw_rev, 85 config_fname, req_dist_name_dict, firewall_cert_data): 86 """Create a certificate to be booted by system firmware 87 88 Args: 89 cert_fname (str): Filename of certificate to create 90 input_fname (str): Filename containing data to sign 91 key_fname (str): Filename of .pem file 92 sw_rev (int): Software revision 93 config_fname (str): Filename to write fconfig into 94 req_dist_name_dict (dict): Dictionary containing key-value pairs of 95 req_distinguished_name section extensions, must contain extensions for 96 C, ST, L, O, OU, CN and emailAddress 97 firewall_cert_data (dict): 98 - auth_in_place (int): The Priv ID for copying as the 99 specific host in firewall protected region 100 - num_firewalls (int): The number of firewalls in the 101 extended certificate 102 - certificate (str): Extended firewall certificate with 103 the information for the firewall configurations. 104 105 Returns: 106 str: Tool output 107 """ 108 indata = tools.read_file(input_fname) 109 hashval = hashlib.sha512(indata).hexdigest() 110 with open(config_fname, 'w', encoding='utf-8') as outf: 111 print(f'''[ req ] 112distinguished_name = req_distinguished_name 113x509_extensions = v3_ca 114prompt = no 115dirstring_type = nobmp 116 117[ req_distinguished_name ] 118C = {req_dist_name_dict['C']} 119ST = {req_dist_name_dict['ST']} 120L = {req_dist_name_dict['L']} 121O = {req_dist_name_dict['O']} 122OU = {req_dist_name_dict['OU']} 123CN = {req_dist_name_dict['CN']} 124emailAddress = {req_dist_name_dict['emailAddress']} 125 126[ v3_ca ] 127basicConstraints = CA:true 1281.3.6.1.4.1.294.1.3 = ASN1:SEQUENCE:swrv 1291.3.6.1.4.1.294.1.34 = ASN1:SEQUENCE:sysfw_image_integrity 1301.3.6.1.4.1.294.1.35 = ASN1:SEQUENCE:sysfw_image_load 1311.3.6.1.4.1.294.1.37 = ASN1:SEQUENCE:firewall 132 133[ swrv ] 134swrv = INTEGER:{sw_rev} 135 136[ sysfw_image_integrity ] 137shaType = OID:2.16.840.1.101.3.4.2.3 138shaValue = FORMAT:HEX,OCT:{hashval} 139imageSize = INTEGER:{len(indata)} 140 141[ sysfw_image_load ] 142destAddr = FORMAT:HEX,OCT:00000000 143authInPlace = INTEGER:{hex(firewall_cert_data['auth_in_place'])} 144 145[ firewall ] 146numFirewallRegions = INTEGER:{firewall_cert_data['num_firewalls']} 147{firewall_cert_data['certificate']} 148''', file=outf) 149 args = ['req', '-new', '-x509', '-key', key_fname, '-nodes', 150 '-outform', 'DER', '-out', cert_fname, '-config', config_fname, 151 '-sha512'] 152 return self.run_cmd(*args) 153 154 def x509_cert_rom(self, cert_fname, input_fname, key_fname, sw_rev, 155 config_fname, req_dist_name_dict, cert_type, bootcore, 156 bootcore_opts, load_addr, sha, debug): 157 """Create a certificate 158 159 Args: 160 cert_fname (str): Filename of certificate to create 161 input_fname (str): Filename containing data to sign 162 key_fname (str): Filename of .pem file 163 sw_rev (int): Software revision 164 config_fname (str): Filename to write fconfig into 165 req_dist_name_dict (dict): Dictionary containing key-value pairs of 166 req_distinguished_name section extensions, must contain extensions for 167 C, ST, L, O, OU, CN and emailAddress 168 cert_type (int): Certification type 169 bootcore (int): Booting core 170 bootcore_opts(int): Booting core option, lockstep (0) or split (2) mode 171 load_addr (int): Load address of image 172 sha (int): Hash function 173 174 Returns: 175 str: Tool output 176 """ 177 indata = tools.read_file(input_fname) 178 hashval = hashlib.sha512(indata).hexdigest() 179 with open(config_fname, 'w', encoding='utf-8') as outf: 180 print(f''' 181[ req ] 182 distinguished_name = req_distinguished_name 183 x509_extensions = v3_ca 184 prompt = no 185 dirstring_type = nobmp 186 187 [ req_distinguished_name ] 188C = {req_dist_name_dict['C']} 189ST = {req_dist_name_dict['ST']} 190L = {req_dist_name_dict['L']} 191O = {req_dist_name_dict['O']} 192OU = {req_dist_name_dict['OU']} 193CN = {req_dist_name_dict['CN']} 194emailAddress = {req_dist_name_dict['emailAddress']} 195 196 [ v3_ca ] 197 basicConstraints = CA:true 198 1.3.6.1.4.1.294.1.1 = ASN1:SEQUENCE:boot_seq 199 1.3.6.1.4.1.294.1.2 = ASN1:SEQUENCE:image_integrity 200 1.3.6.1.4.1.294.1.3 = ASN1:SEQUENCE:swrv 201# 1.3.6.1.4.1.294.1.4 = ASN1:SEQUENCE:encryption 202 1.3.6.1.4.1.294.1.8 = ASN1:SEQUENCE:debug 203 204 [ boot_seq ] 205 certType = INTEGER:{cert_type} 206 bootCore = INTEGER:{bootcore} 207 bootCoreOpts = INTEGER:{bootcore_opts} 208 destAddr = FORMAT:HEX,OCT:{load_addr:08x} 209 imageSize = INTEGER:{len(indata)} 210 211 [ image_integrity ] 212 shaType = OID:{SHA_OIDS[sha]} 213 shaValue = FORMAT:HEX,OCT:{hashval} 214 215 [ swrv ] 216 swrv = INTEGER:{sw_rev} 217 218# [ encryption ] 219# initalVector = FORMAT:HEX,OCT:TEST_IMAGE_ENC_IV 220# randomString = FORMAT:HEX,OCT:TEST_IMAGE_ENC_RS 221# iterationCnt = INTEGER:TEST_IMAGE_KEY_DERIVE_INDEX 222# salt = FORMAT:HEX,OCT:TEST_IMAGE_KEY_DERIVE_SALT 223 224 # When debugging low level boot firmware it can be useful to have ROM or TIFS 225 # unlock JTAG access to the misbehaving CPUs. However in a production setting 226 # this can lead to code modification by outside parties after it's been 227 # authenticated. To gain JTAG access add the 'debug' flag to the binman config 228 [ debug ] 229 debugUID = FORMAT:HEX,OCT:0000000000000000000000000000000000000000000000000000000000000000 230 debugType = INTEGER:{ "4" if debug else "0" } 231 coreDbgEn = INTEGER:0 232 coreDbgSecEn = INTEGER:0 233''', file=outf) 234 args = ['req', '-new', '-x509', '-key', key_fname, '-nodes', 235 '-outform', 'DER', '-out', cert_fname, '-config', config_fname, 236 '-sha512'] 237 return self.run_cmd(*args) 238 239 def x509_cert_rom_combined(self, cert_fname, input_fname, key_fname, sw_rev, 240 config_fname, req_dist_name_dict, load_addr, sha, total_size, num_comps, 241 sysfw_inner_cert_ext_boot_sequence_string, dm_data_ext_boot_sequence_string, 242 imagesize_sbl, hashval_sbl, load_addr_sysfw, imagesize_sysfw, 243 hashval_sysfw, load_addr_sysfw_data, imagesize_sysfw_data, 244 hashval_sysfw_data, sysfw_inner_cert_ext_boot_block, 245 dm_data_ext_boot_block, bootcore_opts, debug): 246 """Create a certificate 247 248 Args: 249 cert_fname (str): Filename of certificate to create 250 input_fname (str): Filename containing data to sign 251 key_fname (str): Filename of .pem file 252 sw_rev (int): Software revision 253 config_fname (str): Filename to write fconfig into 254 req_dist_name_dict (dict): Dictionary containing key-value pairs of 255 req_distinguished_name section extensions, must contain extensions for 256 C, ST, L, O, OU, CN and emailAddress 257 cert_type (int): Certification type 258 bootcore (int): Booting core 259 load_addr (int): Load address of image 260 sha (int): Hash function 261 bootcore_opts (int): Booting core option, lockstep (0) or split (2) mode 262 263 Returns: 264 str: Tool output 265 """ 266 indata = tools.read_file(input_fname) 267 hashval = hashlib.sha512(indata).hexdigest() 268 sha_type = SHA_OIDS[sha] 269 with open(config_fname, 'w', encoding='utf-8') as outf: 270 print(f''' 271[ req ] 272distinguished_name = req_distinguished_name 273x509_extensions = v3_ca 274prompt = no 275dirstring_type = nobmp 276 277[ req_distinguished_name ] 278C = {req_dist_name_dict['C']} 279ST = {req_dist_name_dict['ST']} 280L = {req_dist_name_dict['L']} 281O = {req_dist_name_dict['O']} 282OU = {req_dist_name_dict['OU']} 283CN = {req_dist_name_dict['CN']} 284emailAddress = {req_dist_name_dict['emailAddress']} 285 286[ v3_ca ] 287basicConstraints = CA:true 2881.3.6.1.4.1.294.1.3=ASN1:SEQUENCE:swrv 2891.3.6.1.4.1.294.1.9=ASN1:SEQUENCE:ext_boot_info 2901.3.6.1.4.1.294.1.8=ASN1:SEQUENCE:debug 291 292[swrv] 293swrv=INTEGER:{sw_rev} 294 295[ext_boot_info] 296extImgSize=INTEGER:{total_size} 297numComp=INTEGER:{num_comps} 298sbl=SEQUENCE:sbl 299sysfw=SEQUENCE:sysfw 300sysfw_data=SEQUENCE:sysfw_data 301{sysfw_inner_cert_ext_boot_sequence_string} 302{dm_data_ext_boot_sequence_string} 303 304[sbl] 305compType = INTEGER:1 306bootCore = INTEGER:16 307compOpts = INTEGER:{bootcore_opts} 308destAddr = FORMAT:HEX,OCT:{load_addr:08x} 309compSize = INTEGER:{imagesize_sbl} 310shaType = OID:{sha_type} 311shaValue = FORMAT:HEX,OCT:{hashval_sbl} 312 313[sysfw] 314compType = INTEGER:2 315bootCore = INTEGER:0 316compOpts = INTEGER:0 317destAddr = FORMAT:HEX,OCT:{load_addr_sysfw:08x} 318compSize = INTEGER:{imagesize_sysfw} 319shaType = OID:{sha_type} 320shaValue = FORMAT:HEX,OCT:{hashval_sysfw} 321 322[sysfw_data] 323compType = INTEGER:18 324bootCore = INTEGER:0 325compOpts = INTEGER:0 326destAddr = FORMAT:HEX,OCT:{load_addr_sysfw_data:08x} 327compSize = INTEGER:{imagesize_sysfw_data} 328shaType = OID:{sha_type} 329shaValue = FORMAT:HEX,OCT:{hashval_sysfw_data} 330 331# When debugging low level boot firmware it can be useful to have ROM or TIFS 332# unlock JTAG access to the misbehaving CPUs. However in a production setting 333# this can lead to code modification by outside parties after it's been 334# authenticated. To gain JTAG access add the 'debug' flag to the binman config 335[ debug ] 336debugUID = FORMAT:HEX,OCT:0000000000000000000000000000000000000000000000000000000000000000 337debugType = INTEGER:{ "4" if debug else "0" } 338coreDbgEn = INTEGER:0 339coreDbgSecEn = INTEGER:0 340 341{sysfw_inner_cert_ext_boot_block} 342 343{dm_data_ext_boot_block} 344 ''', file=outf) 345 args = ['req', '-new', '-x509', '-key', key_fname, '-nodes', 346 '-outform', 'DER', '-out', cert_fname, '-config', config_fname, 347 '-sha512'] 348 return self.run_cmd(*args) 349 350 def fetch(self, method): 351 """Fetch handler for openssl 352 353 This installs the openssl package using the apt utility. 354 355 Args: 356 method (FETCH_...): Method to use 357 358 Returns: 359 True if the file was fetched and now installed, None if a method 360 other than FETCH_BIN was requested 361 362 Raises: 363 Valuerror: Fetching could not be completed 364 """ 365 if method != bintool.FETCH_BIN: 366 return None 367 return self.apt_install('openssl') 368