def test_basics(data_dir: str) -> None: """Test basic features of the Certificate class""" with open(os.path.join(data_dir, 'selfsign_2048_v3.der.crt'), 'rb') as f: cert_data = f.read() cert = Certificate(cert_data) # assert cert.version == 'v3' # assert not cert.ca # assert cert.self_signed == 'maybe' # assert cert.self_issued # assert cert.serial_number == 0x3CC30000BABADEDA # assert cert.hash_algo == 'sha256' # mod = cert.public_key_modulus assert isinstance(mod, int) and mod > 10e10 assert cert.public_key_exponent == 65537 # expected_hash = b'I\xad$\xeb=+\xddR\xa8\xef\x1b\xdf\xcaa-S\x10a\xfc\x13v\xff\xd4\xacVE~\xd0\x83\x80\xa6\'' assert cert.public_key_hash == expected_hash # usage = cert.public_key_usage assert usage is not None and len(usage) == 5 for item in usage: assert isinstance(item, str) # assert cert.signature_algo == 'rsassa_pkcs1v15' # sign = cert.signature assert isinstance(sign, bytes) and len(sign) == 256 # assert cert.max_path_length == 0 # issuer = cert.issuer assert isinstance(issuer, dict) and len(issuer) == 5 for key, value in issuer.items(): assert isinstance(key, str) assert isinstance(value, str) # no_before = cert.not_valid_before assert isinstance( no_before, datetime ) and no_before.year == 2019 and no_before.month == 5 and no_before.day == 6 no_after = cert.not_valid_after assert isinstance( no_after, datetime ) and no_after.year == 2039 and no_after.month == 5 and no_after.day == 1 # assert cert.raw_size == 1060 # assert cert.info() # data = cert.export() assert isinstance(data, bytes) and len(data) == cert.raw_size
def create_cert_block(data_dir: str) -> Tuple[CertBlockV2, bytes]: """Load 4 certificates and create certificate block :param data_dir: absolute path :return: tuple with the following items: - certificate block with 4 certificates, certificate 0 is selected - private key 0, decrypted binary data in PEM format """ # load certificates cert_path = os.path.join(data_dir, "keys_certs") cert_list = list() for cert_index in range(4): cert_bin = load_binary( cert_path, f"root_k{str(cert_index)}_signed_cert0_noca.der.cert") cert_list.append(Certificate(cert_bin)) # create certification block cert_block = CertBlockV2(build_number=1) cert_block.add_certificate(cert_list[0]) # add hashes for root_key_index, cert in enumerate(cert_list): if cert: cert_block.set_root_key_hash(root_key_index, cert) # private key that matches selected root cerificate priv_key_pem_data = load_binary( os.path.join(cert_path, "k0_cert0_2048.pem")) return cert_block, priv_key_pem_data
def certificate_block(data_dir, der_file_names, index=0, chain_der_file_names=None) -> CertBlockV2: """ :param data_dir: absolute path of data dir where the test keys are located :param der_file_names: list of filenames of the DER certificate :param index: of the root certificate (index to der_file_names list) :param chain_der_file_names: list of filenames of der certificates in chain (applied for `index`) :return: certificate block for testing """ # read public certificate cert_data_list = list() for der_file_name in der_file_names: if der_file_name: with open(os.path.join(data_dir, "keys_and_certs", der_file_name), "rb") as f: cert_data_list.append(f.read()) else: cert_data_list.append(None) # create certification block cert_block = CertBlockV2(build_number=1) cert_block.add_certificate(cert_data_list[index]) if chain_der_file_names: for der_file_name in chain_der_file_names: with open(os.path.join(data_dir, "keys_and_certs", der_file_name), "rb") as f: cert_block.add_certificate(f.read()) # add hashes for root_key_index, cert_data in enumerate(cert_data_list): if cert_data: cert_block.set_root_key_hash(root_key_index, Certificate(cert_data)) cert_block.export() # check export works cert_block.info() # check info works return cert_block
def test_cert_block(data_dir): with open(os.path.join(data_dir, 'selfsign_2048_v3.der.crt'), 'rb') as f: cert_data = f.read() cert_obj = Certificate(cert_data) cb = CertBlockV2() cb.set_root_key_hash(0, cert_obj.public_key_hash) cb.add_certificate(cert_data) assert cb.rkh_index == 0 cb.export() # test RKHT assert cb.rkht.hex( ) == 'db31d46c717711a8231cbc38b1de8a6e8657e1f733e04c2ee4b62fcea59149fa' fuses = cb.rkht_fuses assert len(fuses) == 8 assert fuses[0] == 1825845723 # test exception if child certificate in chain is not signed by parent certificate with open(os.path.join(data_dir, 'ca0_v3.der.crt'), 'rb') as f: ca0_cert_data = f.read() ca0_cert = Certificate(ca0_cert_data) with pytest.raises(ValueError): cb.add_certificate(ca0_cert) # test exception if no certificate specified cb = CertBlockV2() cb.set_root_key_hash(0, cert_obj.public_key_hash) with pytest.raises(ValueError): cb.export() # test exception last certificate is set as CA cb = CertBlockV2() cb.set_root_key_hash(0, ca0_cert.public_key_hash) cb.add_certificate(ca0_cert) with pytest.raises(ValueError): cb.export() # test exception if hash does not match any certificate cb = CertBlockV2() cb.set_root_key_hash(0, ca0_cert.public_key_hash) cb.add_certificate(cert_data) with pytest.raises(ValueError): cb.export()
def _create_cert_block_v2(data_dir: str) -> CertBlockV2: with open(os.path.join(data_dir, 'selfsign_v3.der.crt'), 'rb') as f: cert_data = f.read() cb = CertBlockV2() cert_obj = Certificate(cert_data) cb.set_root_key_hash(0, cert_obj.public_key_hash) cb.add_certificate(cert_obj) return cb
def gen_cert_block() -> CertBlockV2: """Generate a Certification Block.""" with open(f"{DATA_DIR}/selfsign_v3.der.crt", "rb") as cert_file: cert_data = cert_file.read() cert_obj = Certificate(cert_data) root_key = cert_obj.public_key_hash cert_block = CertBlockV2() cert_block.set_root_key_hash(0, root_key) cert_block.add_certificate(cert_data) return cert_block
def test_cert_block_export_invalid(data_dir): with open(os.path.join(data_dir, "selfsign_2048_v3.der.crt"), "rb") as f: cert_data = f.read() with open(os.path.join(data_dir, "ca0_v3.der.crt"), "rb") as f: ca0_cert_data = f.read() cert_obj = Certificate(cert_data) cb = CertBlockV2() cb.set_root_key_hash(0, cert_obj.public_key_hash) cb.add_certificate(cert_data) cb.add_certificate(cert_data) assert cb.rkh_index == 0 with pytest.raises( SPSDKError, match="All certificates except the last chain certificate must be CA" ): cb.export()
def gen_cert_block(data_dir, sign_bits) -> CertBlockV2: """Shared function to generate certificate block for SB2.x :param data_dir: absolute path to load certificate :param sign_bits: signature key length in bits :return: certificate block for SB2.x """ with open( os.path.join(data_dir, "sb2_x", "selfsign_" + str(sign_bits) + "_v3.der.crt"), "rb") as f: cert_data = f.read() cert_obj = Certificate(cert_data) root_key = cert_obj.public_key_hash cb = CertBlockV2() cb.set_root_key_hash(0, root_key) cb.add_certificate(cert_data) return cb
def generate_secure_binary_21( bd_file_path: click.Path, output_file_path: click.Path, key_file_path: click.Path, private_key_file_path: click.Path, signing_certificate_file_paths: List[click.Path], root_key_certificate_paths: List[click.Path], hoh_out_path: click.Path, external_files: List[click.Path], ) -> None: """Generate SecureBinary image from BD command file. :param bd_file_path: path to BD file. :param output_file_path: output path to generated secure binary file. :param key_file_path: path to key file. :param private_key_file_path: path to private key file for signing. This key relates to last certificate from signing certificate chain. :param signing_certificate_file_paths: signing certificate chain. :param root_key_certificate_paths: paths to root key certificate(s) for verifying other certificates. Only 4 root key certificates are allowed, others are ignored. One of the certificates must match the first certificate passed in signing_certificate_file_paths. :param hoh_out_path: output path to hash of hashes of root keys. If set to None, 'hash.bin' is created under working directory. :param external_files: external files referenced from BD file. :raises SPSDKError: If incorrect bf file is provided """ # Create lexer and parser, load the BD file content and parse it for # further execution - the parsed BD file is a dictionary in JSON format with open(str(bd_file_path)) as bd_file: bd_file_content = bd_file.read() parser = bd_parser.BDParser() parsed_bd_file = parser.parse(text=bd_file_content, extern=external_files) if parsed_bd_file is None: raise SPSDKError( "Invalid bd file, secure binary file generation terminated") # The dictionary contains following content: # { # options: { # opt1: value,... # }, # sections: [ # {section_id: value, options: {}, commands: {}}, # {section_id: value, options: {}, commands: {}} # ] # } # TODO check, that section_ids differ in sections??? # we need to encrypt and sign the image, let's check, whether we have # everything we need # It appears, that flags option in BD file are irrelevant for 2.1 secure # binary images regarding encryption/signing - SB 2.1 must be encrypted # and signed. # However, bit 15 represents, whether the final SB 2.1 must include a # SHA-256 of the botable section. flags = parsed_bd_file["options"].get( "flags", BootImageV21.FLAGS_SHA_PRESENT_BIT | BootImageV21.FLAGS_ENCRYPTED_SIGNED_BIT) if (private_key_file_path is None or signing_certificate_file_paths is None or root_key_certificate_paths is None): click.echo( "error: Signed image requires private key with -s option, " "one or more certificate(s) using -S option and one or more root key " "certificates using -R option") sys.exit(1) # Versions and build number are up to the user. If he doesn't provide any, # we set these to following values. product_version = parsed_bd_file["options"].get("productVersion", "") component_version = parsed_bd_file["options"].get("componentVersion", "") build_number = parsed_bd_file["options"].get("buildNumber", -1) if not product_version: product_version = "1.0.0" click.echo( "warning: production version not defined, defaults to '1.0.0'") if not component_version: component_version = "1.0.0" click.echo( "warning: component version not defined, defaults to '1.0.0'") if build_number == -1: build_number = 1 click.echo("warning: build number not defined, defaults to '1.0.0'") if key_file_path is None: # Legacy elf2sb doesn't report no key provided, but this should # be definitely reported to tell the user, what kind of key is being # used click.echo("warning: no KEK key provided, using a zero KEK key") sb_kek = bytes.fromhex("0" * 64) else: with open(str(key_file_path)) as kek_key_file: # TODO maybe we should validate the key length and content, to make # sure the key provided in the file is valid?? sb_kek = bytes.fromhex(kek_key_file.readline()) # validate keyblobs and perform appropriate actions keyblobs = parsed_bd_file.get("keyblobs", []) # Based on content of parsed BD file, create a BootSectionV2 and assign # commands to them. # The content of section looks like this: # sections: [ # { # section_id: <number>, # options: {}, this is left empty for now... # commands: [ # {<cmd1>: {<param1>: value, ...}}, # {<cmd2>: {<param1>: value, ...}}, # ... # ] # }, # { # section_id: <number>, # ... # } # ] sb_sections = [] bd_sections = parsed_bd_file["sections"] for bd_section in bd_sections: section_id = bd_section["section_id"] commands = [] for cmd in bd_section["commands"]: for key, value in cmd.items(): # we use a helper function, based on the key ('load', 'erase' # etc.) to create a command object. The helper function knows # how to handle the parameters of each command. # TODO Only load, fill, erase and enable commands are supported # for now. But there are few more to be supported... cmd_fce = elf2sb_helper21.get_command(key) if key in ("keywrap", "encrypt"): keyblob = {"keyblobs": keyblobs} value.update(keyblob) cmd = cmd_fce(value) commands.append(cmd) sb_sections.append(BootSectionV2(section_id, *commands)) # We have a list of sections and their respective commands, lets create # a boot image v2.1 object secure_binary = BootImageV21( sb_kek, *sb_sections, product_version=product_version, component_version=component_version, build_number=build_number, flags=flags, ) # create certificate block cert_block = CertBlockV2(build_number=build_number) for cert_path in signing_certificate_file_paths: cert_data = load_certificate_as_bytes(str(cert_path)) cert_block.add_certificate(cert_data) for cert_idx, cert_path in enumerate(root_key_certificate_paths): cert_data = load_certificate_as_bytes(str(cert_path)) cert_block.set_root_key_hash(cert_idx, Certificate(cert_data)) # We have our secure binary, now we attach to it the certificate block and # the private key content # TODO legacy elf2sb doesn't require you to use certificates and private key, # so maybe we should make sure this is not necessary??? # The -s/-R/-S are mandatory, 2.0 format not supported!!! secure_binary.cert_block = cert_block secure_binary.private_key_pem_data = load_binary( str(private_key_file_path)) if hoh_out_path is None: hoh_out_path = os.path.join(os.getcwd(), "hash.bin") with open(str(hoh_out_path), "wb") as rkht_file: rkht_file.write(secure_binary.cert_block.rkht) with open(str(output_file_path), "wb") as sb_file_output: sb_file_output.write(secure_binary.export()) click.echo(f"Success. (Secure binary 2.1: {output_file_path} created.)")