Exemple #1
0
def test_cert_block_invalid():
    cb = CertBlockV2()
    cb.RKH_SIZE = 77777
    with pytest.raises(SPSDKError, match="Invalid length of data"):
        cb.rkht
    with pytest.raises(SPSDKError, match="Invalid image length"):
        cb.image_length = -2
    with pytest.raises(SPSDKError, match="Invalid alignment"):
        cb.alignment = -2
    cb = CertBlockV2()
    with pytest.raises(SPSDKError, match="Invalid index of root key hash in the table"):
        cb.set_root_key_hash(5, bytes(32))
    with pytest.raises(SPSDKError, match="Invalid length of key hash"):
        cb.set_root_key_hash(3, bytes(5))
Exemple #2
0
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()
Exemple #4
0
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
Exemple #5
0
    def parse(
        cls,
        data: bytes,
        offset: int = 0,
        dek: bytes = b"",
        mac: bytes = b"",
        counter: Optional[Counter] = None,
    ) -> "CertSectionV2":
        """Parse Certificate Section from bytes array.

        :param data: Raw data of parsed image
        :param offset: The offset of input data
        :param dek: The DEK value in bytes (required)
        :param mac: The MAC value in bytes (required)
        :param counter: The counter object (required)
        :return: parsed cert section v2 object
        :raises SPSDKError: Raised when dek, mac, counter are not valid
        :raises SPSDKError: Raised when there is invalid header HMAC, TAG, FLAGS, Mark
        :raises SPSDKError: Raised when there is invalid certificate block HMAC
        """
        if not isinstance(dek, bytes):
            raise SPSDKError("DEK value has invalid format")
        if not isinstance(mac, bytes):
            raise SPSDKError("MAC value has invalid format")
        if not isinstance(counter, Counter):
            raise SPSDKError("Counter value has invalid format")
        index = offset
        header_encrypted = data[index:index + CmdHeader.SIZE]
        index += CmdHeader.SIZE
        header_hmac = data[index:index + cls.HMAC_SIZE]
        index += cls.HMAC_SIZE
        cert_block_hmac = data[index:index + cls.HMAC_SIZE]
        index += cls.HMAC_SIZE
        if header_hmac != crypto_backend().hmac(mac, header_encrypted):
            raise SPSDKError("Invalid Header HMAC")
        header_encrypted = crypto_backend().aes_ctr_decrypt(
            dek, header_encrypted, counter.value)
        header = CmdHeader.parse(header_encrypted)
        if header.tag != EnumCmdTag.TAG:
            raise SPSDKError(f"Invalid Header TAG: 0x{header.tag:02X}")
        if header.flags != (EnumSectionFlag.CLEARTEXT
                            | EnumSectionFlag.LAST_SECT):
            raise SPSDKError(f"Invalid Header FLAGS: 0x{header.flags:02X}")
        if header.address != cls.SECT_MARK:
            raise SPSDKError(f"Invalid Section Mark: 0x{header.address:08X}")
        # Parse Certificate Block
        cert_block = CertBlockV2.parse(data, index)
        if cert_block_hmac != crypto_backend().hmac(
                mac, data[index:index + cert_block.raw_size]):
            raise SPSDKError("Invalid Certificate Block HMAC")
        index += cert_block.raw_size
        cert_section_obj = cls(cert_block)
        counter.increment(calc_cypher_block_count(index - offset))
        return cert_section_obj
Exemple #6
0
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
Exemple #7
0
def test_add_invalid_cert_in_cert_block(data_dir):
    cb = CertBlockV2()
    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()
    with pytest.raises(SPSDKError):
        cb.add_certificate(cert=5)
    with pytest.raises(
        SPSDKError, match="Chain certificate cannot be verified using parent public key"
    ):
        cb.add_certificate(cert=cert_data)
        cb.add_certificate(cert=ca0_cert_data)
Exemple #8
0
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 test_cert_block_basic():
    cb = CertBlockV2()
    # test default values
    assert cb.image_length == 0
    assert cb.alignment == 16
    assert cb.rkh_index is None
    # test setters
    cb.image_length = 1
    cb.alignment = 1
    assert cb.alignment == 1
    assert cb.image_length == 1
    assert cb.header.image_length == 1
    # invalid root key index
    with pytest.raises(AssertionError):
        cb.set_root_key_hash(4, bytes([0] * 32))
    # invalid root key size
    with pytest.raises(AssertionError):
        cb.set_root_key_hash(0, bytes())
Exemple #10
0
    def parse(
        cls,
        data: bytes,
        offset: int = 0,
        kek: bytes = bytes(),
        plain_sections: bool = False,
    ) -> "BootImageV21":
        """Parse image from bytes.

        :param data: Raw data of parsed image
        :param offset: The offset of input data
        :param kek: The Key for unwrapping DEK and MAC keys (required)
        :param plain_sections: Sections are not encrypted; this is used only for debugging,
            not supported by ROM code
        :return: BootImageV21 parsed object
        :raises Exception: raised when header is in incorrect format
        :raises Exception: raised when signature is incorrect
        :raises SPSDKError: Raised when kek is empty
        :raises Exception: raised when header's nonce not present"
        """
        if not kek:
            raise SPSDKError("kek cannot be empty")
        index = offset
        header_raw_data = data[index:index + ImageHeaderV2.SIZE]
        index += ImageHeaderV2.SIZE
        # TODO not used right now: hmac_data = data[index: index + cls.HEADER_MAC_SIZE]
        index += cls.HEADER_MAC_SIZE
        key_blob = data[index:index + cls.KEY_BLOB_SIZE]
        index += cls.KEY_BLOB_SIZE
        key_blob_unwrap = crypto_backend().aes_key_unwrap(kek, key_blob[:-8])
        dek = key_blob_unwrap[:32]
        mac = key_blob_unwrap[32:]
        # Parse Header
        header = ImageHeaderV2.parse(header_raw_data)
        if header.offset_to_certificate_block != (index - offset):
            raise Exception()
        # Parse Certificate Block
        cert_block = CertBlockV2.parse(data, index)
        index += cert_block.raw_size

        # Verify Signature
        signature_index = index
        # The image may containt SHA, in such a case the signature is placed
        # after SHA. Thus we must shift the index by SHA size.
        if header.flags & BootImageV21.FLAGS_SHA_PRESENT_BIT:
            signature_index += BootImageV21.SHA_256_SIZE
        result = cert_block.verify_data(
            data[signature_index:signature_index + cert_block.signature_size],
            data[offset:signature_index],
        )

        if not result:
            raise Exception()
        # Check flags, if 0x8000 bit is set, the SB file contains SHA-256 between
        # certificate and signature.
        if header.flags & BootImageV21.FLAGS_SHA_PRESENT_BIT:
            bootable_section_sha256 = data[index:index +
                                           BootImageV21.SHA_256_SIZE]
            index += BootImageV21.SHA_256_SIZE
        index += cert_block.signature_size
        # Check first Boot Section HMAC
        # TODO: not implemented yet
        # hmac_data_calc = crypto_backend().hmac(mac, data[index + CmdHeader.SIZE: index + CmdHeader.SIZE + ((2) * 32)])
        # if hmac_data != hmac_data_calc:
        #    raise Exception()
        if not header.nonce:
            raise SPSDKError("Header's nonce not present")
        counter = Counter(header.nonce)
        counter.increment(calc_cypher_block_count(index - offset))
        boot_section = BootSectionV2.parse(data,
                                           index,
                                           dek=dek,
                                           mac=mac,
                                           counter=counter,
                                           plain_sect=plain_sections)
        if header.flags & BootImageV21.FLAGS_SHA_PRESENT_BIT:
            computed_bootable_section_sha256 = internal_backend.hash(
                data[index:], algorithm="sha256")

            if bootable_section_sha256 != computed_bootable_section_sha256:
                raise SPSDKError(desc=(
                    "Error: invalid Bootable section SHA."
                    f"Expected {bootable_section_sha256.decode('utf-8')},"
                    f"got {computed_bootable_section_sha256.decode('utf-8')}"))
        adv_params = SBV2xAdvancedParams(dek=dek,
                                         mac=mac,
                                         nonce=header.nonce,
                                         timestamp=header.timestamp)
        obj = cls(
            kek=kek,
            product_version=str(header.product_version),
            component_version=str(header.component_version),
            build_number=header.build_number,
            advanced_params=adv_params,
        )
        obj.cert_block = cert_block
        obj.add_boot_section(boot_section)
        return obj
Exemple #11
0
    def parse(cls,
              data: bytes,
              offset: int = 0,
              kek: bytes = bytes(),
              plain_sections: bool = False) -> 'BootImageV21':
        """Parse image from bytes.

        :param data: Raw data of parsed image
        :param offset: The offset of input data
        :param kek: The Key for unwrapping DEK and MAC keys (required)
        :param plain_sections: Sections are not encrypted; this is used only for debugging, not supported by ROM code
        :return: BootImageV21 parsed object
        :raise Exception: raised when header is in incorrect format
        :raise Exception: raised when signature is incorrect
        """
        assert kek, 'kek cannot be empty'
        index = offset
        header_raw_data = data[index:index + ImageHeaderV2.SIZE]
        index += ImageHeaderV2.SIZE
        # TODO not used right now: hmac_data = data[index: index + cls.HEADER_MAC_SIZE]
        index += cls.HEADER_MAC_SIZE
        key_blob = data[index:index + cls.KEY_BLOB_SIZE]
        index += cls.KEY_BLOB_SIZE
        key_blob_unwrap = crypto_backend().aes_key_unwrap(kek, key_blob[:-8])
        dek = key_blob_unwrap[:32]
        mac = key_blob_unwrap[32:]
        # Parse Header
        header = ImageHeaderV2.parse(header_raw_data)
        if header.offset_to_certificate_block != (index - offset):
            raise Exception()
        # Parse Certificate Block
        cert_block = CertBlockV2.parse(data, index)
        index += cert_block.raw_size
        # Verify Signature
        if not cert_block.verify_data(
                data[index:index + cert_block.signature_size],
                data[offset:index]):
            raise Exception()
        index += cert_block.signature_size
        # Check first Boot Section HMAC
        # TODO: not implemented yet
        # hmac_data_calc = crypto_backend().hmac(mac, data[index + CmdHeader.SIZE: index + CmdHeader.SIZE + ((2) * 32)])
        # if hmac_data != hmac_data_calc:
        #    raise Exception()
        assert header.nonce
        counter = Counter(header.nonce)
        counter.increment(calc_cypher_block_count(index - offset))
        boot_section = BootSectionV2.parse(data,
                                           index,
                                           dek=dek,
                                           mac=mac,
                                           counter=counter,
                                           plain_sect=plain_sections)
        adv_params = SBV2xAdvancedParams(dek=dek,
                                         mac=mac,
                                         nonce=header.nonce,
                                         timestamp=header.timestamp)
        obj = cls(kek=kek,
                  product_version=str(header.product_version),
                  component_version=str(header.component_version),
                  build_number=header.build_number,
                  advanced_params=adv_params)
        obj.cert_block = cert_block
        obj.add_boot_section(boot_section)
        return obj
Exemple #12
0
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.)")